diff --git a/dom/base/nsContentUtils.cpp b/dom/base/nsContentUtils.cpp
index e628b274188a..257100d0ed18 100644
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -5207,16 +5207,18 @@ nsContentUtils::AddScriptRunner(nsIRunnable* aRunnable)
 
 /* static */
 void
-nsContentUtils::RunInStableState(already_AddRefed<nsIRunnable> aRunnable,
-                                 DispatchFailureHandling aHandling)
+nsContentUtils::RunInStableState(already_AddRefed<nsIRunnable> aRunnable)
 {
-  nsCOMPtr<nsIRunnable> runnable = aRunnable;
-  nsCOMPtr<nsIAppShell> appShell(do_GetService(kAppShellCID));
-  if (!appShell) {
-    MOZ_ASSERT(aHandling == DispatchFailureHandling::IgnoreFailure);
-    return;
-  }
-  appShell->RunInStableState(runnable.forget());
+  MOZ_ASSERT(CycleCollectedJSRuntime::Get(), "Must be on a script thread!");
+  CycleCollectedJSRuntime::Get()->RunInStableState(Move(aRunnable));
+}
+
+/* static */
+void
+nsContentUtils::RunInMetastableState(already_AddRefed<nsIRunnable> aRunnable)
+{
+  MOZ_ASSERT(CycleCollectedJSRuntime::Get(), "Must be on a script thread!");
+  CycleCollectedJSRuntime::Get()->RunInMetastableState(Move(aRunnable));
 }
 
 void
diff --git a/dom/base/nsContentUtils.h b/dom/base/nsContentUtils.h
index 089b67bebde9..9c541a75716c 100644
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -1597,12 +1597,6 @@ public:
    */
   static void WarnScriptWasIgnored(nsIDocument* aDocument);
 
-  /**
-   * Whether to assert that RunInStableState() succeeds, or ignore failure,
-   * which may happen late in shutdown.
-   */
-  enum class DispatchFailureHandling { AssertSuccess, IgnoreFailure };
-
   /**
    * Add a "synchronous section", in the form of an nsIRunnable run once the
    * event loop has reached a "stable state". |aRunnable| must not cause any
@@ -1614,9 +1608,19 @@ public:
    * finishes. If called multiple times per task/event, all the runnables will
    * be executed, in the order in which RunInStableState() was called.
    */
-  static void RunInStableState(already_AddRefed<nsIRunnable> aRunnable,
-                               DispatchFailureHandling aHandling =
-                                 DispatchFailureHandling::AssertSuccess);
+  static void RunInStableState(already_AddRefed<nsIRunnable> aRunnable);
+
+  /* Add a "synchronous section", in the form of an nsIRunnable run once the
+   * event loop has reached a "metastable state". |aRunnable| must not cause any
+   * queued events to be processed (i.e. must not spin the event loop).
+   * We've reached a metastable state when the currently executing task or
+   * microtask has finished.  This is not specced at this time.
+   * In practice this runs aRunnable once the currently executing task or
+   * microtask finishes.  If called multiple times per microtask, all the
+   * runnables will be executed, in the order in which RunInMetastableState()
+   * was called
+   */
+  static void RunInMetastableState(already_AddRefed<nsIRunnable> aRunnable);
 
   /**
    * Retrieve information about the viewport as a data structure.
diff --git a/dom/base/nsDOMWindowUtils.cpp b/dom/base/nsDOMWindowUtils.cpp
index ae3ffc3fa07e..d870ba24bb55 100644
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -88,8 +88,7 @@
 #include "nsViewportInfo.h"
 #include "nsIFormControl.h"
 #include "nsIScriptError.h"
-#include "nsIAppShell.h"
-#include "nsWidgetsCID.h"
+//#include "nsWidgetsCID.h"
 #include "FrameLayerBuilder.h"
 #include "nsDisplayList.h"
 #include "nsROCSSPrimitiveValue.h"
@@ -3528,30 +3527,6 @@ nsDOMWindowUtils::DispatchEventToChromeOnly(nsIDOMEventTarget* aTarget,
   return NS_OK;
 }
 
-NS_IMETHODIMP
-nsDOMWindowUtils::RunInStableState(nsIRunnable *aRunnable)
-{
-  MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
-
-  nsCOMPtr<nsIRunnable> runnable = aRunnable;
-  nsContentUtils::RunInStableState(runnable.forget());
-
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-nsDOMWindowUtils::RunBeforeNextEvent(nsIRunnable *runnable)
-{
-  MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
-
-  nsCOMPtr<nsIAppShell> appShell(do_GetService(kAppShellCID));
-  if (!appShell) {
-    return NS_ERROR_NOT_AVAILABLE;
-  }
-
-  return appShell->RunBeforeNextEvent(runnable);
-}
-
 NS_IMETHODIMP
 nsDOMWindowUtils::RequestCompositorProperty(const nsAString& property,
                                             float* aResult)
diff --git a/dom/indexedDB/ActorsParent.cpp b/dom/indexedDB/ActorsParent.cpp
index ec2eb0b2c9a4..aad6d8800327 100644
--- a/dom/indexedDB/ActorsParent.cpp
+++ b/dom/indexedDB/ActorsParent.cpp
@@ -25417,15 +25417,13 @@ DEBUGThreadSlower::OnDispatchedEvent(nsIThreadInternal* /* aThread */)
 
 NS_IMETHODIMP
 DEBUGThreadSlower::OnProcessNextEvent(nsIThreadInternal* /* aThread */,
-                                      bool /* aMayWait */,
-                                      uint32_t /* aRecursionDepth */)
+                                      bool /* aMayWait */)
 {
   return NS_OK;
 }
 
 NS_IMETHODIMP
 DEBUGThreadSlower::AfterProcessNextEvent(nsIThreadInternal* /* aThread */,
-                                         uint32_t /* aRecursionDepth */,
                                          bool /* aEventWasProcessed */)
 {
   MOZ_ASSERT(kDEBUGThreadSleepMS);
diff --git a/dom/indexedDB/IDBFileHandle.cpp b/dom/indexedDB/IDBFileHandle.cpp
index 702c70e25200..36564214ff0d 100644
--- a/dom/indexedDB/IDBFileHandle.cpp
+++ b/dom/indexedDB/IDBFileHandle.cpp
@@ -12,7 +12,6 @@
 #include "mozilla/dom/IDBFileHandleBinding.h"
 #include "mozilla/dom/MetadataHelper.h"
 #include "mozilla/EventDispatcher.h"
-#include "nsIAppShell.h"
 #include "nsServiceManagerUtils.h"
 #include "nsWidgetsCID.h"
 
@@ -20,12 +19,6 @@ namespace mozilla {
 namespace dom {
 namespace indexedDB {
 
-namespace {
-
-NS_DEFINE_CID(kAppShellCID2, NS_APPSHELL_CID);
-
-} // namespace
-
 IDBFileHandle::IDBFileHandle(FileMode aMode,
                              RequestMode aRequestMode,
                              IDBMutableFile* aMutableFile)
@@ -51,15 +44,8 @@ IDBFileHandle::Create(FileMode aMode,
 
   fileHandle->BindToOwner(aMutableFile);
 
-  nsCOMPtr<nsIAppShell> appShell = do_GetService(kAppShellCID2);
-  if (NS_WARN_IF(!appShell)) {
-    return nullptr;
-  }
-
-  nsresult rv = appShell->RunBeforeNextEvent(fileHandle);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return nullptr;
-  }
+  nsCOMPtr<nsIRunnable> runnable = do_QueryObject(fileHandle);
+  nsContentUtils::RunInMetastableState(runnable.forget());
 
   fileHandle->SetCreating();
 
@@ -68,7 +54,7 @@ IDBFileHandle::Create(FileMode aMode,
     return nullptr;
   }
 
-  rv = service->Enqueue(fileHandle, nullptr);
+  nsresult rv = service->Enqueue(fileHandle, nullptr);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return nullptr;
   }
diff --git a/dom/indexedDB/IDBTransaction.cpp b/dom/indexedDB/IDBTransaction.cpp
index d8d5aa3fd6ab..9aba23ebee20 100644
--- a/dom/indexedDB/IDBTransaction.cpp
+++ b/dom/indexedDB/IDBTransaction.cpp
@@ -16,11 +16,9 @@
 #include "mozilla/dom/DOMError.h"
 #include "mozilla/dom/DOMStringList.h"
 #include "mozilla/ipc/BackgroundChild.h"
-#include "nsIAppShell.h"
 #include "nsPIDOMWindow.h"
 #include "nsServiceManagerUtils.h"
 #include "nsTHashtable.h"
-#include "nsWidgetsCID.h"
 #include "ProfilerHelpers.h"
 #include "ReportInternalError.h"
 #include "WorkerFeature.h"
@@ -36,36 +34,6 @@ namespace indexedDB {
 using namespace mozilla::dom::workers;
 using namespace mozilla::ipc;
 
-namespace {
-
-NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID);
-
-bool
-RunBeforeNextEvent(IDBTransaction* aTransaction)
-{
-  MOZ_ASSERT(aTransaction);
-
-  if (NS_IsMainThread()) {
-    nsCOMPtr<nsIAppShell> appShell = do_GetService(kAppShellCID);
-    MOZ_ASSERT(appShell);
-
-    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(appShell->RunBeforeNextEvent(aTransaction)));
-
-    return true;
-  }
-
-  WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
-  MOZ_ASSERT(workerPrivate);
-
-  if (NS_WARN_IF(!workerPrivate->RunBeforeNextEvent(aTransaction))) {
-    return false;
-  }
-
-  return true;
-}
-
-} // namespace
-
 class IDBTransaction::WorkerFeature final
   : public mozilla::dom::workers::WorkerFeature
 {
@@ -222,15 +190,8 @@ IDBTransaction::CreateVersionChange(
 
   transaction->SetScriptOwner(aDatabase->GetScriptOwner());
 
-  if (NS_WARN_IF(!RunBeforeNextEvent(transaction))) {
-    MOZ_ASSERT(!NS_IsMainThread());
-#ifdef DEBUG
-    // Silence assertions.
-    transaction->mSentCommitOrAbort = true;
-#endif
-    aActor->SendDeleteMeInternal(/* aFailedConstructor */ true);
-    return nullptr;
-  }
+  nsCOMPtr<nsIRunnable> runnable = do_QueryObject(transaction);
+  nsContentUtils::RunInMetastableState(runnable.forget());
 
   transaction->mBackgroundActor.mVersionChangeBackgroundActor = aActor;
   transaction->mNextObjectStoreId = aNextObjectStoreId;
@@ -262,10 +223,8 @@ IDBTransaction::Create(IDBDatabase* aDatabase,
 
   transaction->SetScriptOwner(aDatabase->GetScriptOwner());
 
-  if (NS_WARN_IF(!RunBeforeNextEvent(transaction))) {
-    MOZ_ASSERT(!NS_IsMainThread());
-    return nullptr;
-  }
+  nsCOMPtr<nsIRunnable> runnable = do_QueryObject(transaction);
+  nsContentUtils::RunInMetastableState(runnable.forget());
 
   transaction->mCreating = true;
 
diff --git a/dom/interfaces/base/nsIDOMWindowUtils.idl b/dom/interfaces/base/nsIDOMWindowUtils.idl
index 5df7f9ac8993..1181a3d0f4b8 100644
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -49,7 +49,7 @@ interface nsIJSRAIIHelper;
 interface nsIContentPermissionRequest;
 interface nsIObserver;
 
-[scriptable, uuid(7a37e173-ea6e-495e-8702-013f8063352a)]
+[scriptable, uuid(6064615a-a782-4d08-86db-26ef3851208a)]
 interface nsIDOMWindowUtils : nsISupports {
 
   /**
@@ -1722,32 +1722,6 @@ interface nsIDOMWindowUtils : nsISupports {
    */
   attribute boolean paintFlashing;
 
-  /**
-   * Add a "synchronous section", in the form of an nsIRunnable run once the
-   * event loop has reached a "stable state". |runnable| must not cause any
-   * queued events to be processed (i.e. must not spin the event loop).
-   * We've reached a stable state when the currently executing task/event has
-   * finished, see:
-   * http://www.whatwg.org/specs/web-apps/current-work/multipage/webappapis.html#synchronous-section
-   * In practice this runs aRunnable once the currently executing event
-   * finishes. If called multiple times per task/event, all the runnables will
-   * be executed, in the order in which runInStableState() was called.
-   *
-   * XXX - This can wreak havoc if you're not using this for very simple
-   * purposes, eg testing or setting a flag.
-   */
-  void runInStableState(in nsIRunnable runnable);
-
-  /**
-   * Run the given runnable before the next iteration of the event loop (this
-   * includes native events too). If a nested loop is spawned within the current
-   * event then the runnable will not be run until that loop has terminated.
-   *
-   * XXX - This can wreak havoc if you're not using this for very simple
-   * purposes, eg testing or setting a flag.
-   */
-  void runBeforeNextEvent(in nsIRunnable runnable);
-
   /*
    * Returns the value of a given property animated on the compositor thread.
    * If the property is NOT currently being animated on the compositor thread,
diff --git a/dom/media/webaudio/AudioDestinationNode.cpp b/dom/media/webaudio/AudioDestinationNode.cpp
index 302503c63508..b13f3bcd8b06 100644
--- a/dom/media/webaudio/AudioDestinationNode.cpp
+++ b/dom/media/webaudio/AudioDestinationNode.cpp
@@ -633,9 +633,7 @@ AudioDestinationNode::ScheduleStableStateNotification()
     NS_NewRunnableMethod(this, &AudioDestinationNode::NotifyStableState);
   // Dispatch will fail if this is called on AudioNode destruction during
   // shutdown, in which case failure can be ignored.
-  nsContentUtils::RunInStableState(event.forget(),
-                                   nsContentUtils::
-                                     DispatchFailureHandling::IgnoreFailure);
+  nsContentUtils::RunInStableState(event.forget());
 }
 
 double
diff --git a/dom/promise/Promise.cpp b/dom/promise/Promise.cpp
index 8d7a73a17ca9..a1a5e3635d2b 100644
--- a/dom/promise/Promise.cpp
+++ b/dom/promise/Promise.cpp
@@ -510,6 +510,7 @@ Promise::PerformMicroTaskCheckpoint()
     if (cx.isSome()) {
       JS_CheckForInterrupt(cx.ref());
     }
+    runtime->AfterProcessMicrotask();
   } while (!microtaskQueue.empty());
 
   return true;
diff --git a/dom/promise/tests/test_promise.html b/dom/promise/tests/test_promise.html
index 4dc81e4233ce..af185efcdaf8 100644
--- a/dom/promise/tests/test_promise.html
+++ b/dom/promise/tests/test_promise.html
@@ -169,6 +169,26 @@ function promiseAsync_ResolveThenTimeout() {
   ok(!handlerExecuted, "Handlers are not called before 'then' returns.");
 }
 
+function promiseAsync_SyncXHR()
+{
+  var handlerExecuted = false;
+
+  Promise.resolve().then(function() {
+    handlerExecuted = true;
+
+    // Allow other assertions to run so the test could fail before the next one.
+    setTimeout(runTest, 0);
+  });
+
+  ok(!handlerExecuted, "Handlers are not called until the next microtask.");
+
+  var xhr = new XMLHttpRequest();
+  xhr.open("GET", "testXHR.txt", false);
+  xhr.send(null);
+
+  todo(!handlerExecuted, "Sync XHR should not trigger microtask execution.");
+}
+
 function promiseDoubleThen() {
   var steps = 0;
   var promise = new Promise(function(r1, r2) {
@@ -756,6 +776,7 @@ var tests = [ promiseResolve, promiseReject,
               promiseAsync_TimeoutResolveThen,
               promiseAsync_ResolveTimeoutThen,
               promiseAsync_ResolveThenTimeout,
+              promiseAsync_SyncXHR,
               promiseDoubleThen, promiseThenException,
               promiseThenCatchThen, promiseRejectThenCatchThen,
               promiseRejectThenCatchThen2,
diff --git a/dom/storage/DOMStorageDBThread.cpp b/dom/storage/DOMStorageDBThread.cpp
index 5ac98661220d..27ac7a2ecc14 100644
--- a/dom/storage/DOMStorageDBThread.cpp
+++ b/dom/storage/DOMStorageDBThread.cpp
@@ -362,15 +362,13 @@ DOMStorageDBThread::ThreadObserver::OnDispatchedEvent(nsIThreadInternal *thread)
 
 NS_IMETHODIMP
 DOMStorageDBThread::ThreadObserver::OnProcessNextEvent(nsIThreadInternal *thread,
-                                       bool mayWait,
-                                       uint32_t recursionDepth)
+                                       bool mayWait)
 {
   return NS_OK;
 }
 
 NS_IMETHODIMP
 DOMStorageDBThread::ThreadObserver::AfterProcessNextEvent(nsIThreadInternal *thread,
-                                          uint32_t recursionDepth,
                                           bool eventWasProcessed)
 {
   return NS_OK;
diff --git a/dom/workers/RuntimeService.cpp b/dom/workers/RuntimeService.cpp
index 0fc26d9efc59..ca126f9c9ca7 100644
--- a/dom/workers/RuntimeService.cpp
+++ b/dom/workers/RuntimeService.cpp
@@ -996,6 +996,15 @@ public:
     }
   }
 
+  virtual void AfterProcessTask(uint32_t aRecursionDepth) override
+  {
+    // Only perform the Promise microtask checkpoint on the outermost event
+    // loop.  Don't run it, for example, during sync XHR or importScripts.
+    if (aRecursionDepth == 2) {
+      CycleCollectedJSRuntime::AfterProcessTask(aRecursionDepth);
+    }
+  }
+
 private:
   WorkerPrivate* mWorkerPrivate;
 };
diff --git a/dom/workers/WorkerPrivate.cpp b/dom/workers/WorkerPrivate.cpp
index a0b74a3ec8e3..50c33056d99f 100644
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -2588,22 +2588,6 @@ WorkerPrivate::SyncLoopInfo::SyncLoopInfo(EventTarget* aEventTarget)
 {
 }
 
-struct WorkerPrivate::PreemptingRunnableInfo final
-{
-  nsCOMPtr<nsIRunnable> mRunnable;
-  uint32_t mRecursionDepth;
-
-  PreemptingRunnableInfo()
-  {
-    MOZ_COUNT_CTOR(WorkerPrivate::PreemptingRunnableInfo);
-  }
-
-  ~PreemptingRunnableInfo()
-  {
-    MOZ_COUNT_DTOR(WorkerPrivate::PreemptingRunnableInfo);
-  }
-};
-
 template <class Derived>
 nsIDocument*
 WorkerPrivateParent<Derived>::GetDocument() const
@@ -5187,10 +5171,6 @@ WorkerPrivate::DoRunLoop(JSContext* aCx)
       // Process a single runnable from the main queue.
       MOZ_ALWAYS_TRUE(NS_ProcessNextEvent(mThread, false));
 
-      // Only perform the Promise microtask checkpoint on the outermost event
-      // loop.  Don't run it, for example, during sync XHR or importScripts.
-      (void)Promise::PerformMicroTaskCheckpoint();
-
       normalRunnablesPending = NS_HasPendingEvents(mThread);
       if (normalRunnablesPending && GlobalScope()) {
         // Now *might* be a good time to GC. Let the JS engine make the decision.
@@ -5210,85 +5190,28 @@ WorkerPrivate::DoRunLoop(JSContext* aCx)
 }
 
 void
-WorkerPrivate::OnProcessNextEvent(uint32_t aRecursionDepth)
+WorkerPrivate::OnProcessNextEvent()
 {
   AssertIsOnWorkerThread();
-  MOZ_ASSERT(aRecursionDepth);
+
+  uint32_t recursionDepth = CycleCollectedJSRuntime::Get()->RecursionDepth();
+  MOZ_ASSERT(recursionDepth);
 
   // Normally we process control runnables in DoRunLoop or RunCurrentSyncLoop.
   // However, it's possible that non-worker C++ could spin its own nested event
   // loop, and in that case we must ensure that we continue to process control
   // runnables here.
-  if (aRecursionDepth > 1 &&
-      mSyncLoopStack.Length() < aRecursionDepth - 1) {
+  if (recursionDepth > 1 &&
+      mSyncLoopStack.Length() < recursionDepth - 1) {
     ProcessAllControlRunnables();
   }
-
-  // Run any preempting runnables that match this depth.
-  if (!mPreemptingRunnableInfos.IsEmpty()) {
-    nsTArray<PreemptingRunnableInfo> pendingRunnableInfos;
-
-    for (uint32_t index = 0;
-         index < mPreemptingRunnableInfos.Length();
-         index++) {
-      PreemptingRunnableInfo& preemptingRunnableInfo =
-        mPreemptingRunnableInfos[index];
-
-      if (preemptingRunnableInfo.mRecursionDepth == aRecursionDepth) {
-        preemptingRunnableInfo.mRunnable->Run();
-        preemptingRunnableInfo.mRunnable = nullptr;
-      } else {
-        PreemptingRunnableInfo* pending = pendingRunnableInfos.AppendElement();
-        pending->mRunnable.swap(preemptingRunnableInfo.mRunnable);
-        pending->mRecursionDepth = preemptingRunnableInfo.mRecursionDepth;
-      }
-    }
-
-    mPreemptingRunnableInfos.SwapElements(pendingRunnableInfos);
-  }
 }
 
 void
-WorkerPrivate::AfterProcessNextEvent(uint32_t aRecursionDepth)
+WorkerPrivate::AfterProcessNextEvent()
 {
   AssertIsOnWorkerThread();
-  MOZ_ASSERT(aRecursionDepth);
-}
-
-bool
-WorkerPrivate::RunBeforeNextEvent(nsIRunnable* aRunnable)
-{
-  AssertIsOnWorkerThread();
-  MOZ_ASSERT(aRunnable);
-  MOZ_ASSERT_IF(!mPreemptingRunnableInfos.IsEmpty(),
-                NS_HasPendingEvents(mThread));
-
-  const uint32_t recursionDepth =
-    mThread->RecursionDepth(WorkerThreadFriendKey());
-
-  PreemptingRunnableInfo* preemptingRunnableInfo =
-    mPreemptingRunnableInfos.AppendElement();
-
-  preemptingRunnableInfo->mRunnable = aRunnable;
-
-  // Due to the weird way that the thread recursion counter is implemented we
-  // subtract one from the recursion level if we have one.
-  preemptingRunnableInfo->mRecursionDepth =
-    recursionDepth ? recursionDepth - 1 : 0;
-
-  // Ensure that we have a pending event so that the runnable will be guaranteed
-  // to run.
-  if (mPreemptingRunnableInfos.Length() == 1 && !NS_HasPendingEvents(mThread)) {
-    nsRefPtr<DummyRunnable> dummyRunnable = new DummyRunnable(this);
-    if (NS_FAILED(Dispatch(dummyRunnable.forget()))) {
-      NS_WARNING("RunBeforeNextEvent called after the thread is shutting "
-                 "down!");
-      mPreemptingRunnableInfos.Clear();
-      return false;
-    }
-  }
-
-  return true;
+  MOZ_ASSERT(CycleCollectedJSRuntime::Get()->RecursionDepth());
 }
 
 void
diff --git a/dom/workers/WorkerPrivate.h b/dom/workers/WorkerPrivate.h
index d536c171160f..0b2dfc966d6b 100644
--- a/dom/workers/WorkerPrivate.h
+++ b/dom/workers/WorkerPrivate.h
@@ -949,9 +949,6 @@ class WorkerPrivate : public WorkerPrivateParent<WorkerPrivate>
   // modifications are done with mMutex held *only* in DEBUG builds.
   nsTArray<nsAutoPtr<SyncLoopInfo>> mSyncLoopStack;
 
-  struct PreemptingRunnableInfo;
-  nsTArray<PreemptingRunnableInfo> mPreemptingRunnableInfos;
-
   nsCOMPtr<nsITimer> mTimer;
 
   nsCOMPtr<nsITimer> mGCTimer;
@@ -1362,10 +1359,10 @@ public:
   ClearMainEventQueue(WorkerRanOrNot aRanOrNot);
 
   void
-  OnProcessNextEvent(uint32_t aRecursionDepth);
+  OnProcessNextEvent();
 
   void
-  AfterProcessNextEvent(uint32_t aRecursionDepth);
+  AfterProcessNextEvent();
 
   void
   AssertValidSyncLoop(nsIEventTarget* aSyncLoopTarget)
@@ -1392,11 +1389,6 @@ public:
     return mWorkerScriptExecutedSuccessfully;
   }
 
-  // Just like nsIAppShell::RunBeforeNextEvent. May only be called on the worker
-  // thread.
-  bool
-  RunBeforeNextEvent(nsIRunnable* aRunnable);
-
   void
   MaybeDispatchLoadFailedRunnable();
 
diff --git a/dom/workers/WorkerThread.cpp b/dom/workers/WorkerThread.cpp
index 29fa26556de4..89ee469942da 100644
--- a/dom/workers/WorkerThread.cpp
+++ b/dom/workers/WorkerThread.cpp
@@ -310,8 +310,7 @@ WorkerThread::Observer::OnDispatchedEvent(nsIThreadInternal* /* aThread */)
 
 NS_IMETHODIMP
 WorkerThread::Observer::OnProcessNextEvent(nsIThreadInternal* /* aThread */,
-                                           bool aMayWait,
-                                           uint32_t aRecursionDepth)
+                                           bool aMayWait)
 {
   mWorkerPrivate->AssertIsOnWorkerThread();
 
@@ -321,23 +320,22 @@ WorkerThread::Observer::OnProcessNextEvent(nsIThreadInternal* /* aThread */,
   // PrimaryWorkerRunnable::Run() and don't want to process the event in
   // mWorkerPrivate yet.
   if (aMayWait) {
-    MOZ_ASSERT(aRecursionDepth == 2);
+    MOZ_ASSERT(CycleCollectedJSRuntime::Get()->RecursionDepth() == 2);
     MOZ_ASSERT(!BackgroundChild::GetForCurrentThread());
     return NS_OK;
   }
 
-  mWorkerPrivate->OnProcessNextEvent(aRecursionDepth);
+  mWorkerPrivate->OnProcessNextEvent();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 WorkerThread::Observer::AfterProcessNextEvent(nsIThreadInternal* /* aThread */,
-                                              uint32_t aRecursionDepth,
                                               bool /* aEventWasProcessed */)
 {
   mWorkerPrivate->AssertIsOnWorkerThread();
 
-  mWorkerPrivate->AfterProcessNextEvent(aRecursionDepth);
+  mWorkerPrivate->AfterProcessNextEvent();
   return NS_OK;
 }
 
diff --git a/dom/workers/test/promise_worker.js b/dom/workers/test/promise_worker.js
index b4e6d7b23c9e..329c54f06b95 100644
--- a/dom/workers/test/promise_worker.js
+++ b/dom/workers/test/promise_worker.js
@@ -3,6 +3,11 @@ function ok(a, msg) {
   postMessage({type: 'status', status: !!a, msg: a + ": " + msg });
 }
 
+function todo(a, msg) {
+  dump("TODO: " + !a + "  =>  " + a + " " + msg + "\n");
+  postMessage({type: 'status', status: !a, msg: a + ": " + msg });
+}
+
 function is(a, b, msg) {
   dump("IS: " + (a===b) + "  =>  " + a + " | " + b + " " + msg + "\n");
   postMessage({type: 'status', status: a === b, msg: a + " === " + b + ": " + msg });
@@ -148,7 +153,7 @@ function promiseAsync_ResolveThenTimeout() {
   ok(!handlerExecuted, "Handlers are not called before 'then' returns.");
 }
 
-function promiseAsync_SyncHXRAndImportScripts()
+function promiseAsync_SyncXHRAndImportScripts()
 {
   var handlerExecuted = false;
 
@@ -790,7 +795,7 @@ var tests = [
     promiseAsync_TimeoutResolveThen,
     promiseAsync_ResolveTimeoutThen,
     promiseAsync_ResolveThenTimeout,
-    promiseAsync_SyncHXRAndImportScripts,
+    promiseAsync_SyncXHRAndImportScripts,
     promiseDoubleThen,
     promiseThenException,
     promiseThenCatchThen,
diff --git a/image/test/mochitest/test_synchronized_animation.html b/image/test/mochitest/test_synchronized_animation.html
index f537d490ef86..53e709fb95ef 100644
--- a/image/test/mochitest/test_synchronized_animation.html
+++ b/image/test/mochitest/test_synchronized_animation.html
@@ -55,8 +55,7 @@ function cleanUpAndFinish() {
 
 function frameUpdate(aRequest) {
   if (!gDispatched) {
-    var util = window.getInterface(Ci.nsIDOMWindowUtils);
-    util.runBeforeNextEvent(function() { 
+    Promise.resolve().then(function() { 
       gRanEvent = true;
     });
     gDispatched = true;
diff --git a/ipc/glue/MessagePump.cpp b/ipc/glue/MessagePump.cpp
index 62c0f1cde2df..01fb71483779 100644
--- a/ipc/glue/MessagePump.cpp
+++ b/ipc/glue/MessagePump.cpp
@@ -433,15 +433,13 @@ MessagePumpForNonMainUIThreads::OnDispatchedEvent(nsIThreadInternal *thread)
 
 NS_IMETHODIMP
 MessagePumpForNonMainUIThreads::OnProcessNextEvent(nsIThreadInternal *thread,
-                                                   bool mayWait,
-                                                   uint32_t recursionDepth)
+                                                   bool mayWait)
 {
   return NS_OK;
 }
 
 NS_IMETHODIMP
 MessagePumpForNonMainUIThreads::AfterProcessNextEvent(nsIThreadInternal *thread,
-                                                      uint32_t recursionDepth,
                                                       bool eventWasProcessed)
 {
   return NS_OK;
diff --git a/js/xpconnect/src/XPCJSRuntime.cpp b/js/xpconnect/src/XPCJSRuntime.cpp
index 233f7af19c0b..ae0b702bfe2f 100644
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -3574,6 +3574,59 @@ XPCJSRuntime::NoteCustomGCThingXPCOMChildren(const js::Class* clasp, JSObject* o
     return true;
 }
 
+void
+XPCJSRuntime::BeforeProcessTask(bool aMightBlock)
+{
+    MOZ_ASSERT(NS_IsMainThread());
+
+    // If ProcessNextEvent was called during a Promise "then" callback, we
+    // must process any pending microtasks before blocking in the event loop,
+    // otherwise we may deadlock until an event enters the queue later.
+    if (aMightBlock) {
+        if (Promise::PerformMicroTaskCheckpoint()) {
+            // If any microtask was processed, we post a dummy event in order to
+            // force the ProcessNextEvent call not to block.  This is required
+            // to support nested event loops implemented using a pattern like
+            // "while (condition) thread.processNextEvent(true)", in case the
+            // condition is triggered here by a Promise "then" callback.
+
+            class DummyRunnable : public nsRunnable {
+            public:
+                NS_IMETHOD Run() { return NS_OK; }
+            };
+
+            NS_DispatchToMainThread(new DummyRunnable());
+        }
+    }
+
+    // Start the slow script timer.
+    mSlowScriptCheckpoint = mozilla::TimeStamp::NowLoRes();
+    mSlowScriptSecondHalf = false;
+    js::ResetStopwatches(Get()->Runtime());
+
+    // Push a null JSContext so that we don't see any script during
+    // event processing.
+    PushNullJSContext();
+
+    CycleCollectedJSRuntime::BeforeProcessTask(aMightBlock);
+}
+
+void
+XPCJSRuntime::AfterProcessTask(uint32_t aNewRecursionDepth)
+{
+    // Now that we're back to the event loop, reset the slow script checkpoint.
+    mSlowScriptCheckpoint = mozilla::TimeStamp();
+    mSlowScriptSecondHalf = false;
+
+    // Call cycle collector occasionally.
+    MOZ_ASSERT(NS_IsMainThread());
+    nsJSContext::MaybePokeCC();
+
+    CycleCollectedJSRuntime::AfterProcessTask(aNewRecursionDepth);
+
+    PopNullJSContext();
+}
+
 /***************************************************************************/
 
 void
diff --git a/js/xpconnect/src/nsXPConnect.cpp b/js/xpconnect/src/nsXPConnect.cpp
index 043929d4c2c6..3d7e56242871 100644
--- a/js/xpconnect/src/nsXPConnect.cpp
+++ b/js/xpconnect/src/nsXPConnect.cpp
@@ -26,7 +26,6 @@
 
 #include "nsDOMMutationObserver.h"
 #include "nsICycleCollectorListener.h"
-#include "nsThread.h"
 #include "mozilla/XPTInterfaceInfoManager.h"
 #include "nsIObjectInputStream.h"
 #include "nsIObjectOutputStream.h"
@@ -37,9 +36,7 @@ using namespace mozilla::dom;
 using namespace xpc;
 using namespace JS;
 
-NS_IMPL_ISUPPORTS(nsXPConnect,
-                  nsIXPConnect,
-                  nsIThreadObserver)
+NS_IMPL_ISUPPORTS(nsXPConnect, nsIXPConnect)
 
 nsXPConnect* nsXPConnect::gSelf = nullptr;
 bool         nsXPConnect::gOnceAliveNowDead = false;
@@ -61,8 +58,7 @@ const char XPC_XPCONNECT_CONTRACTID[]     = "@mozilla.org/js/xpc/XPConnect;1";
 
 nsXPConnect::nsXPConnect()
     :   mRuntime(nullptr),
-        mShuttingDown(false),
-        mEventDepth(0)
+        mShuttingDown(false)
 {
     mRuntime = XPCJSRuntime::newXPCJSRuntime(this);
 
@@ -120,11 +116,6 @@ nsXPConnect::InitStatics()
     // balanced by explicit call to ReleaseXPConnectSingleton()
     NS_ADDREF(gSelf);
 
-    // Set XPConnect as the main thread observer.
-    if (NS_FAILED(nsThread::SetMainThreadObserver(gSelf))) {
-        MOZ_CRASH();
-    }
-
     // Fire up the SSM.
     nsScriptSecurityManager::InitStatics();
     gScriptSecurityManager = nsScriptSecurityManager::GetScriptSecurityManager();
@@ -152,8 +143,6 @@ nsXPConnect::ReleaseXPConnectSingleton()
 {
     nsXPConnect* xpc = gSelf;
     if (xpc) {
-        nsThread::SetMainThreadObserver(nullptr);
-
         nsrefcnt cnt;
         NS_RELEASE2(xpc, cnt);
     }
@@ -933,81 +922,6 @@ nsXPConnect::JSToVariant(JSContext* ctx, HandleValue value, nsIVariant** _retval
     return NS_OK;
 }
 
-namespace {
-
-class DummyRunnable : public nsRunnable {
-public:
-    NS_IMETHOD Run() { return NS_OK; }
-};
-
-} // namespace
-
-NS_IMETHODIMP
-nsXPConnect::OnProcessNextEvent(nsIThreadInternal* aThread, bool aMayWait,
-                                uint32_t aRecursionDepth)
-{
-    MOZ_ASSERT(NS_IsMainThread());
-
-    // If ProcessNextEvent was called during a Promise "then" callback, we
-    // must process any pending microtasks before blocking in the event loop,
-    // otherwise we may deadlock until an event enters the queue later.
-    if (aMayWait) {
-        if (Promise::PerformMicroTaskCheckpoint()) {
-            // If any microtask was processed, we post a dummy event in order to
-            // force the ProcessNextEvent call not to block.  This is required
-            // to support nested event loops implemented using a pattern like
-            // "while (condition) thread.processNextEvent(true)", in case the
-            // condition is triggered here by a Promise "then" callback.
-            NS_DispatchToMainThread(new DummyRunnable());
-        }
-    }
-
-    // Record this event.
-    mEventDepth++;
-
-    // Start the slow script timer.
-    mRuntime->OnProcessNextEvent();
-
-    // Push a null JSContext so that we don't see any script during
-    // event processing.
-    bool ok = PushNullJSContext();
-    NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE);
-    return NS_OK;
-}
-
-NS_IMETHODIMP
-nsXPConnect::AfterProcessNextEvent(nsIThreadInternal* aThread,
-                                   uint32_t aRecursionDepth,
-                                   bool aEventWasProcessed)
-{
-    // Watch out for unpaired events during observer registration.
-    if (MOZ_UNLIKELY(mEventDepth == 0))
-        return NS_OK;
-    mEventDepth--;
-
-    // Now that we're back to the event loop, reset the slow script checkpoint.
-    mRuntime->OnAfterProcessNextEvent();
-
-    // Call cycle collector occasionally.
-    MOZ_ASSERT(NS_IsMainThread());
-    nsJSContext::MaybePokeCC();
-
-    nsContentUtils::PerformMainThreadMicroTaskCheckpoint();
-
-    Promise::PerformMicroTaskCheckpoint();
-
-    PopNullJSContext();
-
-    return NS_OK;
-}
-
-NS_IMETHODIMP
-nsXPConnect::OnDispatchedEvent(nsIThreadInternal* aThread)
-{
-    NS_NOTREACHED("Why tell us?");
-    return NS_ERROR_UNEXPECTED;
-}
-
 NS_IMETHODIMP
 nsXPConnect::SetReportAllJSExceptions(bool newval)
 {
diff --git a/js/xpconnect/src/xpcprivate.h b/js/xpconnect/src/xpcprivate.h
index fe5bc257c07e..9f09a8fa4490 100644
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -153,7 +153,6 @@
 #include "nsJSPrincipals.h"
 #include "nsIScriptObjectPrincipal.h"
 #include "xpcObjectHelper.h"
-#include "nsIThreadInternal.h"
 
 #include "SandboxPrivate.h"
 #include "BackstagePass.h"
@@ -240,14 +239,12 @@ static inline bool IS_WN_REFLECTOR(JSObject* obj)
 // returned as function call result values they are not addref'd. Exceptions
 // to this rule are noted explicitly.
 
-class nsXPConnect final : public nsIXPConnect,
-                          public nsIThreadObserver
+class nsXPConnect final : public nsIXPConnect
 {
 public:
     // all the interface method declarations...
     NS_DECL_ISUPPORTS
     NS_DECL_NSIXPCONNECT
-    NS_DECL_NSITHREADOBSERVER
 
     // non-interface implementation
 public:
@@ -324,13 +321,6 @@ private:
     XPCJSRuntime*                   mRuntime;
     bool                            mShuttingDown;
 
-    // nsIThreadInternal doesn't remember which observers it called
-    // OnProcessNextEvent on when it gets around to calling AfterProcessNextEvent.
-    // So if XPConnect gets initialized mid-event (which can happen), we'll get
-    // an 'after' notification without getting an 'on' notification. If we don't
-    // watch out for this, we'll do an unmatched |pop| on the context stack.
-    uint16_t                 mEventDepth;
-
     static uint32_t gReportAllJSExceptions;
 
 public:
@@ -489,6 +479,9 @@ public:
     NoteCustomGCThingXPCOMChildren(const js::Class* aClasp, JSObject* aObj,
                                    nsCycleCollectionTraversalCallback& aCb) const override;
 
+    virtual void BeforeProcessTask(bool aMightBlock) override;
+    virtual void AfterProcessTask(uint32_t aNewRecursionDepth) override;
+
     /**
      * Infrastructure for classes that need to defer part of the finalization
      * until after the GC has run, for example for objects that we don't want to
@@ -615,16 +608,6 @@ public:
 
     PRTime GetWatchdogTimestamp(WatchdogTimestampCategory aCategory);
 
-    void OnProcessNextEvent() {
-        mSlowScriptCheckpoint = mozilla::TimeStamp::NowLoRes();
-        mSlowScriptSecondHalf = false;
-        js::ResetStopwatches(Get()->Runtime());
-    }
-    void OnAfterProcessNextEvent() {
-        mSlowScriptCheckpoint = mozilla::TimeStamp();
-        mSlowScriptSecondHalf = false;
-    }
-
     nsTArray<nsXPCWrappedJS*>& WrappedJSToReleaseArray() { return mWrappedJSToReleaseArray; }
 
 private:
diff --git a/layout/style/Loader.cpp b/layout/style/Loader.cpp
index 16fd8495680a..8b7d7de4f8f2 100644
--- a/layout/style/Loader.cpp
+++ b/layout/style/Loader.cpp
@@ -431,9 +431,9 @@ SheetLoadData::OnDispatchedEvent(nsIThreadInternal* aThread)
 
 NS_IMETHODIMP
 SheetLoadData::OnProcessNextEvent(nsIThreadInternal* aThread,
-                                  bool aMayWait,
-                                  uint32_t aRecursionDepth)
+                                  bool aMayWait)
 {
+  // XXXkhuey this is insane!
   // We want to fire our load even before or after event processing,
   // whichever comes first.
   FireLoadEvent(aThread);
@@ -442,9 +442,9 @@ SheetLoadData::OnProcessNextEvent(nsIThreadInternal* aThread,
 
 NS_IMETHODIMP
 SheetLoadData::AfterProcessNextEvent(nsIThreadInternal* aThread,
-                                     uint32_t aRecursionDepth,
                                      bool aEventWasProcessed)
 {
+  // XXXkhuey this too!
   // We want to fire our load even before or after event processing,
   // whichever comes first.
   FireLoadEvent(aThread);
diff --git a/netwerk/base/nsSocketTransportService2.cpp b/netwerk/base/nsSocketTransportService2.cpp
index 5c9c09b57de6..fec1d0a93a4d 100644
--- a/netwerk/base/nsSocketTransportService2.cpp
+++ b/netwerk/base/nsSocketTransportService2.cpp
@@ -763,14 +763,13 @@ nsSocketTransportService::OnDispatchedEvent(nsIThreadInternal *thread)
 
 NS_IMETHODIMP
 nsSocketTransportService::OnProcessNextEvent(nsIThreadInternal *thread,
-                                             bool mayWait, uint32_t depth)
+                                             bool mayWait)
 {
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSocketTransportService::AfterProcessNextEvent(nsIThreadInternal* thread,
-                                                uint32_t depth,
                                                 bool eventWasProcessed)
 {
     return NS_OK;
diff --git a/netwerk/cache2/CacheIOThread.cpp b/netwerk/cache2/CacheIOThread.cpp
index 72ecbc0a5057..856ce2dd1b92 100644
--- a/netwerk/cache2/CacheIOThread.cpp
+++ b/netwerk/cache2/CacheIOThread.cpp
@@ -315,12 +315,12 @@ NS_IMETHODIMP CacheIOThread::OnDispatchedEvent(nsIThreadInternal *thread)
   return NS_OK;
 }
 
-NS_IMETHODIMP CacheIOThread::OnProcessNextEvent(nsIThreadInternal *thread, bool mayWait, uint32_t recursionDepth)
+NS_IMETHODIMP CacheIOThread::OnProcessNextEvent(nsIThreadInternal *thread, bool mayWait)
 {
   return NS_OK;
 }
 
-NS_IMETHODIMP CacheIOThread::AfterProcessNextEvent(nsIThreadInternal *thread, uint32_t recursionDepth,
+NS_IMETHODIMP CacheIOThread::AfterProcessNextEvent(nsIThreadInternal *thread,
                                                    bool eventWasProcessed)
 {
   return NS_OK;
diff --git a/widget/ScreenProxy.cpp b/widget/ScreenProxy.cpp
index 936841431f8e..6481e85d9f3e 100644
--- a/widget/ScreenProxy.cpp
+++ b/widget/ScreenProxy.cpp
@@ -169,16 +169,9 @@ ScreenProxy::InvalidateCacheOnNextTick()
 
   mCacheWillInvalidate = true;
 
-  nsCOMPtr<nsIAppShell> appShell = do_GetService(kAppShellCID);
-  if (appShell) {
-    nsCOMPtr<nsIRunnable> r =
-      NS_NewRunnableMethod(this, &ScreenProxy::InvalidateCache);
-    appShell->RunInStableState(r.forget());
-  } else {
-    // It's pretty bad news if we can't get the appshell. In that case,
-    // let's just invalidate the cache right away.
-    InvalidateCache();
-  }
+  nsCOMPtr<nsIRunnable> r =
+    NS_NewRunnableMethod(this, &ScreenProxy::InvalidateCache);
+  nsContentUtils::RunInStableState(r.forget());
 }
 
 void
diff --git a/widget/cocoa/nsAppShell.h b/widget/cocoa/nsAppShell.h
index bf5b06a49437..b7836b63918b 100644
--- a/widget/cocoa/nsAppShell.h
+++ b/widget/cocoa/nsAppShell.h
@@ -35,10 +35,8 @@ public:
 
   NS_IMETHOD Run(void);
   NS_IMETHOD Exit(void);
-  NS_IMETHOD OnProcessNextEvent(nsIThreadInternal *aThread, bool aMayWait,
-                                uint32_t aRecursionDepth);
+  NS_IMETHOD OnProcessNextEvent(nsIThreadInternal *aThread, bool aMayWait);
   NS_IMETHOD AfterProcessNextEvent(nsIThreadInternal *aThread,
-                                   uint32_t aRecursionDepth,
                                    bool aEventWasProcessed);
 
   // public only to be visible to Objective-C code that must call it
diff --git a/widget/cocoa/nsAppShell.mm b/widget/cocoa/nsAppShell.mm
index c94245f63295..0f753530ca19 100644
--- a/widget/cocoa/nsAppShell.mm
+++ b/widget/cocoa/nsAppShell.mm
@@ -735,8 +735,7 @@ nsAppShell::Exit(void)
 //
 // public
 NS_IMETHODIMP
-nsAppShell::OnProcessNextEvent(nsIThreadInternal *aThread, bool aMayWait,
-                               uint32_t aRecursionDepth)
+nsAppShell::OnProcessNextEvent(nsIThreadInternal *aThread, bool aMayWait)
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
 
@@ -746,7 +745,7 @@ nsAppShell::OnProcessNextEvent(nsIThreadInternal *aThread, bool aMayWait,
   NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
   ::CFArrayAppendValue(mAutoreleasePools, pool);
 
-  return nsBaseAppShell::OnProcessNextEvent(aThread, aMayWait, aRecursionDepth);
+  return nsBaseAppShell::OnProcessNextEvent(aThread, aMayWait);
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
 }
@@ -760,7 +759,6 @@ nsAppShell::OnProcessNextEvent(nsIThreadInternal *aThread, bool aMayWait,
 // public
 NS_IMETHODIMP
 nsAppShell::AfterProcessNextEvent(nsIThreadInternal *aThread,
-                                  uint32_t aRecursionDepth,
                                   bool aEventWasProcessed)
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
@@ -775,8 +773,7 @@ nsAppShell::AfterProcessNextEvent(nsIThreadInternal *aThread,
   ::CFArrayRemoveValueAtIndex(mAutoreleasePools, count - 1);
   [pool release];
 
-  return nsBaseAppShell::AfterProcessNextEvent(aThread, aRecursionDepth,
-                                               aEventWasProcessed);
+  return nsBaseAppShell::AfterProcessNextEvent(aThread, aEventWasProcessed);
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
 }
diff --git a/widget/nsBaseAppShell.cpp b/widget/nsBaseAppShell.cpp
index db59bf80dac6..eee3eaf3d073 100644
--- a/widget/nsBaseAppShell.cpp
+++ b/widget/nsBaseAppShell.cpp
@@ -31,7 +31,6 @@ nsBaseAppShell::nsBaseAppShell()
   , mSwitchTime(0)
   , mLastNativeEventTime(0)
   , mEventloopNestingState(eEventloopNone)
-  , mRunningSyncSections(false)
   , mRunning(false)
   , mExiting(false)
   , mBlockNativeEvent(false)
@@ -40,7 +39,6 @@ nsBaseAppShell::nsBaseAppShell()
 
 nsBaseAppShell::~nsBaseAppShell()
 {
-  NS_ASSERTION(mSyncSections.IsEmpty(), "Must have run all sync sections");
 }
 
 nsresult
@@ -120,7 +118,7 @@ nsBaseAppShell::DoProcessMoreGeckoEvents()
 
 // Main thread via OnProcessNextEvent below
 bool
-nsBaseAppShell::DoProcessNextNativeEvent(bool mayWait, uint32_t recursionDepth)
+nsBaseAppShell::DoProcessNextNativeEvent(bool mayWait)
 {
   // The next native event to be processed may trigger our NativeEventCallback,
   // in which case we do not want it to process any thread events since we'll
@@ -137,14 +135,7 @@ nsBaseAppShell::DoProcessNextNativeEvent(bool mayWait, uint32_t recursionDepth)
   mEventloopNestingState = eEventloopXPCOM;
 
   IncrementEventloopNestingLevel();
-
   bool result = ProcessNextNativeEvent(mayWait);
-
-  // Make sure that any sync sections registered during this most recent event
-  // are run now. This is not considered a stable state because we're not back
-  // to the event loop yet.
-  RunSyncSections(false, recursionDepth);
-
   DecrementEventloopNestingLevel();
 
   mEventloopNestingState = prevVal;
@@ -239,8 +230,7 @@ nsBaseAppShell::OnDispatchedEvent(nsIThreadInternal *thr)
 
 // Called from the main thread
 NS_IMETHODIMP
-nsBaseAppShell::OnProcessNextEvent(nsIThreadInternal *thr, bool mayWait,
-                                   uint32_t recursionDepth)
+nsBaseAppShell::OnProcessNextEvent(nsIThreadInternal *thr, bool mayWait)
 {
   if (mBlockNativeEvent) {
     if (!mayWait)
@@ -278,13 +268,13 @@ nsBaseAppShell::OnProcessNextEvent(nsIThreadInternal *thr, bool mayWait,
     bool keepGoing;
     do {
       mLastNativeEventTime = now;
-      keepGoing = DoProcessNextNativeEvent(false, recursionDepth);
+      keepGoing = DoProcessNextNativeEvent(false);
     } while (keepGoing && ((now = PR_IntervalNow()) - start) < limit);
   } else {
     // Avoid starving native events completely when in performance mode
     if (start - mLastNativeEventTime > limit) {
       mLastNativeEventTime = start;
-      DoProcessNextNativeEvent(false, recursionDepth);
+      DoProcessNextNativeEvent(false);
     }
   }
 
@@ -296,7 +286,7 @@ nsBaseAppShell::OnProcessNextEvent(nsIThreadInternal *thr, bool mayWait,
       mayWait = false;
 
     mLastNativeEventTime = PR_IntervalNow();
-    if (!DoProcessNextNativeEvent(mayWait, recursionDepth) || !mayWait)
+    if (!DoProcessNextNativeEvent(mayWait) || !mayWait)
       break;
   }
 
@@ -309,9 +299,6 @@ nsBaseAppShell::OnProcessNextEvent(nsIThreadInternal *thr, bool mayWait,
     DispatchDummyEvent(thr);
   }
 
-  // We're about to run an event, so we're in a stable state.
-  RunSyncSections(true, recursionDepth);
-
   return NS_OK;
 }
 
@@ -344,95 +331,11 @@ nsBaseAppShell::DecrementEventloopNestingLevel()
 #endif
 }
 
-void
-nsBaseAppShell::RunSyncSectionsInternal(bool aStable,
-                                        uint32_t aThreadRecursionLevel)
-{
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-  NS_ASSERTION(!mSyncSections.IsEmpty(), "Nothing to do!");
-
-  // We don't support re-entering sync sections. This effectively means that
-  // sync sections may not spin the event loop.
-  MOZ_RELEASE_ASSERT(!mRunningSyncSections);
-  mRunningSyncSections = true;
-
-  // We've got synchronous sections. Run all of them that are are awaiting a
-  // stable state if aStable is true (i.e. we really are in a stable state).
-  // Also run the synchronous sections that are simply waiting for the right
-  // combination of event loop nesting level and thread recursion level.
-  // Note that a synchronous section could add another synchronous section, so
-  // we don't remove elements from mSyncSections until all sections have been
-  // run, or else we'll screw up our iteration. Any sync sections that are not
-  // ready to be run are saved for later.
-
-  nsTArray<SyncSection> pendingSyncSections;
-
-  for (uint32_t i = 0; i < mSyncSections.Length(); i++) {
-    SyncSection& section = mSyncSections[i];
-    if ((aStable && section.mStable) ||
-        (!section.mStable &&
-         section.mEventloopNestingLevel == mEventloopNestingLevel &&
-         section.mThreadRecursionLevel == aThreadRecursionLevel)) {
-      section.mRunnable->Run();
-    }
-    else {
-      // Add to pending list.
-      SyncSection* pending = pendingSyncSections.AppendElement();
-      section.Forget(pending);
-    }
-  }
-
-  mSyncSections.SwapElements(pendingSyncSections);
-  mRunningSyncSections = false;
-}
-
-void
-nsBaseAppShell::ScheduleSyncSection(already_AddRefed<nsIRunnable> aRunnable,
-                                    bool aStable)
-{
-  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
-
-  nsIThread* thread = NS_GetCurrentThread();
-
-  // Add this runnable to our list of synchronous sections.
-  SyncSection* section = mSyncSections.AppendElement();
-  section->mStable = aStable;
-  section->mRunnable = aRunnable;
-
-  // If aStable is false then this synchronous section is supposed to run before
-  // the next event at the current nesting level. Record the event loop nesting
-  // level and the thread recursion level so that the synchronous section will
-  // run at the proper time.
-  if (!aStable) {
-    section->mEventloopNestingLevel = mEventloopNestingLevel;
-
-    nsCOMPtr<nsIThreadInternal> threadInternal = do_QueryInterface(thread);
-    NS_ASSERTION(threadInternal, "This should never fail!");
-
-    uint32_t recursionLevel;
-    if (NS_FAILED(threadInternal->GetRecursionDepth(&recursionLevel))) {
-      NS_ERROR("This should never fail!");
-    }
-
-    // Due to the weird way that the thread recursion counter is implemented we
-    // subtract one from the recursion level if we have one.
-    section->mThreadRecursionLevel = recursionLevel ? recursionLevel - 1 : 0;
-  }
-
-  // Ensure we've got a pending event, else the callbacks will never run.
-  if (!NS_HasPendingEvents(thread) && !DispatchDummyEvent(thread)) {
-    RunSyncSections(true, 0);
-  }
-}
-
 // Called from the main thread
 NS_IMETHODIMP
 nsBaseAppShell::AfterProcessNextEvent(nsIThreadInternal *thr,
-                                      uint32_t recursionDepth,
                                       bool eventWasProcessed)
 {
-  // We've just finished running an event, so we're in a stable state.
-  RunSyncSections(true, recursionDepth);
   return NS_OK;
 }
 
@@ -444,17 +347,3 @@ nsBaseAppShell::Observe(nsISupports *subject, const char *topic,
   Exit();
   return NS_OK;
 }
-
-void
-nsBaseAppShell::RunInStableState(already_AddRefed<nsIRunnable> aRunnable)
-{
-  ScheduleSyncSection(mozilla::Move(aRunnable), true);
-}
-
-NS_IMETHODIMP
-nsBaseAppShell::RunBeforeNextEvent(nsIRunnable* aRunnable)
-{
-  nsCOMPtr<nsIRunnable> runnable = aRunnable;
-  ScheduleSyncSection(runnable.forget(), false);
-  return NS_OK;
-}
diff --git a/widget/nsBaseAppShell.h b/widget/nsBaseAppShell.h
index 09f8e47b71ca..4ee9e3d2e79d 100644
--- a/widget/nsBaseAppShell.h
+++ b/widget/nsBaseAppShell.h
@@ -25,7 +25,6 @@ class nsBaseAppShell : public nsIAppShell, public nsIThreadObserver,
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIAPPSHELL
-  void RunInStableState(already_AddRefed<nsIRunnable> runnable) override;
 
   NS_DECL_NSITHREADOBSERVER
   NS_DECL_NSIOBSERVER
@@ -77,45 +76,13 @@ protected:
   uint32_t mEventloopNestingLevel;
 
 private:
-  bool DoProcessNextNativeEvent(bool mayWait, uint32_t recursionDepth);
+  bool DoProcessNextNativeEvent(bool mayWait);
 
   bool DispatchDummyEvent(nsIThread* target);
 
   void IncrementEventloopNestingLevel();
   void DecrementEventloopNestingLevel();
 
-  /**
-   * Runs all synchronous sections which are queued up in mSyncSections.
-   */
-  void RunSyncSectionsInternal(bool stable, uint32_t threadRecursionLevel);
-
-  void RunSyncSections(bool stable, uint32_t threadRecursionLevel)
-  {
-    if (!mSyncSections.IsEmpty()) {
-      RunSyncSectionsInternal(stable, threadRecursionLevel);
-    }
-  }
-
-  void ScheduleSyncSection(already_AddRefed<nsIRunnable> runnable, bool stable);
-
-  struct SyncSection {
-    SyncSection()
-    : mStable(false), mEventloopNestingLevel(0), mThreadRecursionLevel(0)
-    { }
-
-    void Forget(SyncSection* other) {
-      other->mStable = mStable;
-      other->mEventloopNestingLevel = mEventloopNestingLevel;
-      other->mThreadRecursionLevel = mThreadRecursionLevel;
-      other->mRunnable = mRunnable.forget();
-    }
-
-    bool mStable;
-    uint32_t mEventloopNestingLevel;
-    uint32_t mThreadRecursionLevel;
-    nsCOMPtr<nsIRunnable> mRunnable;
-  };
-
   nsCOMPtr<nsIRunnable> mDummyEvent;
   /**
    * mBlockedWait points back to a slot that controls the wait loop in
@@ -135,8 +102,6 @@ private:
     eEventloopOther  // innermost native event loop is a native library/plugin etc
   };
   EventloopNestingState mEventloopNestingState;
-  nsTArray<SyncSection> mSyncSections;
-  bool mRunningSyncSections;
   bool mRunning;
   bool mExiting;
   /**
diff --git a/widget/nsIAppShell.idl b/widget/nsIAppShell.idl
index bb089d20ae86..c21c4d107b9e 100644
--- a/widget/nsIAppShell.idl
+++ b/widget/nsIAppShell.idl
@@ -15,7 +15,7 @@ template <class T> struct already_AddRefed;
  * Interface for the native event system layer.  This interface is designed
  * to be used on the main application thread only.
  */
-[uuid(3d09973e-3975-4fd4-b103-276300cc8437)]
+[uuid(7cd5c71d-223b-4afe-931d-5eedb1f2b01f)]
 interface nsIAppShell : nsISupports
 {
   /**
@@ -73,26 +73,4 @@ interface nsIAppShell : nsISupports
    * The current event loop nesting level.
    */
   readonly attribute unsigned long eventloopNestingLevel;
-  
-%{ C++
-  /**
-   * Add a "synchronous section", in the form of an nsIRunnable run once the
-   * event loop has reached a "stable state". |runnable| must not cause any
-   * queued events to be processed (i.e. must not spin the event loop). We've
-   * reached a stable state when the currently executing task/event has
-   * finished, see:
-   * http://www.whatwg.org/specs/web-apps/current-work/multipage/webappapis.html#synchronous-section
-   * In practice this runs aRunnable once the currently executing event
-   * finishes. If called multiple times per task/event, all the runnables will
-   * be executed, in the order in which runInStableState() was called.
-   */
-  virtual void RunInStableState(already_AddRefed<nsIRunnable> runnable) = 0;
-%}
-
-  /**
-   * Run the given runnable before the next iteration of the event loop (this
-   * includes native events too). If a nested loop is spawned within the current
-   * event then the runnable will not be run until that loop has terminated.
-   */
-  void runBeforeNextEvent(in nsIRunnable runnable);
 };
diff --git a/widget/nsScreenManagerProxy.cpp b/widget/nsScreenManagerProxy.cpp
index b768a53091ae..956529971ab4 100644
--- a/widget/nsScreenManagerProxy.cpp
+++ b/widget/nsScreenManagerProxy.cpp
@@ -198,16 +198,9 @@ nsScreenManagerProxy::InvalidateCacheOnNextTick()
 
   mCacheWillInvalidate = true;
 
-  nsCOMPtr<nsIAppShell> appShell = do_GetService(kAppShellCID);
-  if (appShell) {
-    nsCOMPtr<nsIRunnable> r =
-      NS_NewRunnableMethod(this, &nsScreenManagerProxy::InvalidateCache);
-    appShell->RunInStableState(r.forget());
-  } else {
-    // It's pretty bad news if we can't get the appshell. In that case,
-    // let's just invalidate the cache right away.
-    InvalidateCache();
-  }
+  nsCOMPtr<nsIRunnable> r =
+    NS_NewRunnableMethod(this, &nsScreenManagerProxy::InvalidateCache);
+  nsContentUtils::RunInStableState(r.forget());
 }
 
 void
diff --git a/widget/tests/moz.build b/widget/tests/moz.build
index 8fd299185720..698f455f8b3d 100644
--- a/widget/tests/moz.build
+++ b/widget/tests/moz.build
@@ -8,10 +8,6 @@ XPCSHELL_TESTS_MANIFESTS += ['unit/xpcshell.ini']
 MOCHITEST_MANIFESTS += ['mochitest.ini']
 MOCHITEST_CHROME_MANIFESTS += ['chrome.ini']
 
-GeckoCppUnitTests([
-    'TestAppShellSteadyState',
-])
-
 FAIL_ON_WARNINGS = True
 
 # if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
diff --git a/xpcom/base/CycleCollectedJSRuntime.cpp b/xpcom/base/CycleCollectedJSRuntime.cpp
index 492c7483c530..109821ad2286 100644
--- a/xpcom/base/CycleCollectedJSRuntime.cpp
+++ b/xpcom/base/CycleCollectedJSRuntime.cpp
@@ -63,6 +63,7 @@
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/DebuggerOnGCRunnable.h"
 #include "mozilla/dom/DOMJSClass.h"
+#include "mozilla/dom/Promise.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "jsprf.h"
 #include "js/Debug.h"
@@ -77,6 +78,7 @@
 #endif
 
 #include "nsIException.h"
+#include "nsThread.h"
 #include "nsThreadUtils.h"
 #include "xpcpublic.h"
 
@@ -402,9 +404,18 @@ CycleCollectedJSRuntime::CycleCollectedJSRuntime(JSRuntime* aParentRuntime,
   , mJSRuntime(nullptr)
   , mPrevGCSliceCallback(nullptr)
   , mJSHolders(256)
+  , mDoingStableStates(false)
   , mOutOfMemoryState(OOMState::OK)
   , mLargeAllocationFailureState(OOMState::OK)
 {
+  nsCOMPtr<nsIThread> thread = do_GetCurrentThread();
+  mOwningThread = thread.forget().downcast<nsThread>().take();
+  MOZ_RELEASE_ASSERT(mOwningThread);
+
+  mOwningThread->SetScriptObserver(this);
+  // The main thread has a base recursion depth of 0, workers of 1.
+  mBaseRecursionDepth = RecursionDepth();
+
   mozilla::dom::InitScriptSettings();
 
   mJSRuntime = JS_NewRuntime(aMaxBytes, aMaxNurseryBytes, aParentRuntime);
@@ -440,6 +451,13 @@ CycleCollectedJSRuntime::~CycleCollectedJSRuntime()
   MOZ_ASSERT(mJSRuntime);
   MOZ_ASSERT(!mDeferredFinalizerTable.Count());
 
+  // Last chance to process any events.
+  ProcessMetastableStateQueue(mBaseRecursionDepth);
+  MOZ_ASSERT(mMetastableStateEvents.IsEmpty());
+
+  ProcessStableStateQueue();
+  MOZ_ASSERT(mStableStateEvents.IsEmpty());
+
   // Clear mPendingException first, since it might be cycle collected.
   mPendingException = nullptr;
 
@@ -448,6 +466,9 @@ CycleCollectedJSRuntime::~CycleCollectedJSRuntime()
   nsCycleCollector_forgetJSRuntime();
 
   mozilla::dom::DestroyScriptSettings();
+
+  mOwningThread->SetScriptObserver(nullptr);
+  NS_RELEASE(mOwningThread);
 }
 
 size_t
@@ -1015,6 +1036,112 @@ CycleCollectedJSRuntime::DumpJSHeap(FILE* aFile)
   js::DumpHeap(Runtime(), aFile, js::CollectNurseryBeforeDump);
 }
 
+void
+CycleCollectedJSRuntime::ProcessStableStateQueue()
+{
+  MOZ_RELEASE_ASSERT(!mDoingStableStates);
+  mDoingStableStates = true;
+
+  for (uint32_t i = 0; i < mStableStateEvents.Length(); ++i) {
+    nsCOMPtr<nsIRunnable> event = mStableStateEvents[i].forget();
+    event->Run();
+  }
+
+  mStableStateEvents.Clear();
+  mDoingStableStates = false;
+}
+
+void
+CycleCollectedJSRuntime::ProcessMetastableStateQueue(uint32_t aRecursionDepth)
+{
+  MOZ_RELEASE_ASSERT(!mDoingStableStates);
+  mDoingStableStates = true;
+
+  nsTArray<RunInMetastableStateData> localQueue = Move(mMetastableStateEvents);
+
+  for (uint32_t i = 0; i < localQueue.Length(); ++i)
+  {
+    RunInMetastableStateData& data = localQueue[i];
+    if (data.mRecursionDepth != aRecursionDepth) {
+      continue;
+    }
+
+    {
+      nsCOMPtr<nsIRunnable> runnable = data.mRunnable.forget();
+      runnable->Run();
+    }
+
+    localQueue.RemoveElementAt(i--);
+  }
+
+  // If the queue has events in it now, they were added from something we called,
+  // so they belong at the end of the queue.
+  localQueue.AppendElements(mMetastableStateEvents);
+  localQueue.SwapElements(mMetastableStateEvents);
+  mDoingStableStates = false;
+}
+
+void
+CycleCollectedJSRuntime::AfterProcessTask(uint32_t aRecursionDepth)
+{
+  // See HTML 6.1.4.2 Processing model
+
+  // Execute any events that were waiting for a microtask to complete.
+  // This is not (yet) in the spec.
+  ProcessMetastableStateQueue(aRecursionDepth);
+
+  // Step 4.1: Execute microtasks.
+  if (NS_IsMainThread()) {
+    nsContentUtils::PerformMainThreadMicroTaskCheckpoint();
+  }
+
+  Promise::PerformMicroTaskCheckpoint();
+
+  // Step 4.2 Execute any events that were waiting for a stable state.
+  ProcessStableStateQueue();
+}
+
+void
+CycleCollectedJSRuntime::AfterProcessMicrotask()
+{
+  AfterProcessMicrotask(RecursionDepth());
+}
+
+void
+CycleCollectedJSRuntime::AfterProcessMicrotask(uint32_t aRecursionDepth)
+{
+  // Between microtasks, execute any events that were waiting for a microtask
+  // to complete.
+  ProcessMetastableStateQueue(aRecursionDepth);
+}
+
+uint32_t
+CycleCollectedJSRuntime::RecursionDepth()
+{
+  return mOwningThread->RecursionDepth();
+}
+
+void
+CycleCollectedJSRuntime::RunInStableState(already_AddRefed<nsIRunnable>&& aRunnable)
+{
+  MOZ_ASSERT(mJSRuntime);
+  mStableStateEvents.AppendElement(Move(aRunnable));
+}
+
+void
+CycleCollectedJSRuntime::RunInMetastableState(already_AddRefed<nsIRunnable>&& aRunnable)
+{
+  RunInMetastableStateData data;
+  data.mRunnable = aRunnable;
+
+  MOZ_ASSERT(mOwningThread);
+  data.mRecursionDepth = RecursionDepth();
+
+  // There must be an event running to get here.
+  MOZ_ASSERT(data.mRecursionDepth > mBaseRecursionDepth);
+
+  mMetastableStateEvents.AppendElement(Move(data));
+}
 
 IncrementalFinalizeRunnable::IncrementalFinalizeRunnable(CycleCollectedJSRuntime* aRt,
                                                          DeferredFinalizerTable& aFinalizers)
diff --git a/xpcom/base/CycleCollectedJSRuntime.h b/xpcom/base/CycleCollectedJSRuntime.h
index dfe8997619d3..d48ae4e8b5e2 100644
--- a/xpcom/base/CycleCollectedJSRuntime.h
+++ b/xpcom/base/CycleCollectedJSRuntime.h
@@ -21,6 +21,7 @@
 class nsCycleCollectionNoteRootCallback;
 class nsIException;
 class nsIRunnable;
+class nsThread;
 
 namespace js {
 struct Class;
@@ -151,7 +152,6 @@ protected:
   }
 
 private:
-
   void
   DescribeGCThing(bool aIsMarked, JS::GCCellPtr aThing,
                   nsCycleCollectionTraversalCallback& aCb) const;
@@ -208,6 +208,10 @@ private:
   virtual void TraceNativeBlackRoots(JSTracer* aTracer) { };
   void TraceNativeGrayRoots(JSTracer* aTracer);
 
+  void AfterProcessMicrotask(uint32_t aRecursionDepth);
+  void ProcessStableStateQueue();
+  void ProcessMetastableStateQueue(uint32_t aRecursionDepth);
+
 public:
   enum DeferredFinalizeType {
     FinalizeIncrementally,
@@ -294,6 +298,21 @@ public:
     return mJSRuntime;
   }
 
+  // nsThread entrypoints
+  virtual void BeforeProcessTask(bool aMightBlock) { };
+  virtual void AfterProcessTask(uint32_t aRecursionDepth);
+
+  // microtask processor entry point
+  void AfterProcessMicrotask();
+
+  uint32_t RecursionDepth();
+
+  // Run in stable state (call through nsContentUtils)
+  void RunInStableState(already_AddRefed<nsIRunnable>&& aRunnable);
+  // This isn't in the spec at all yet, but this gets the behavior we want for IDB.
+  // Runs after the current microtask completes.
+  void RunInMetastableState(already_AddRefed<nsIRunnable>&& aRunnable);
+
   // Get the current thread's CycleCollectedJSRuntime.  Returns null if there
   // isn't one.
   static CycleCollectedJSRuntime* Get();
@@ -325,9 +344,21 @@ private:
   nsRefPtr<IncrementalFinalizeRunnable> mFinalizeRunnable;
 
   nsCOMPtr<nsIException> mPendingException;
+  nsThread* mOwningThread; // Manual refcounting to avoid include hell.
 
   std::queue<nsCOMPtr<nsIRunnable>> mPromiseMicroTaskQueue;
 
+  struct RunInMetastableStateData
+  {
+    nsCOMPtr<nsIRunnable> mRunnable;
+    uint32_t mRecursionDepth;
+  };
+
+  nsTArray<nsCOMPtr<nsIRunnable>> mStableStateEvents;
+  nsTArray<RunInMetastableStateData> mMetastableStateEvents;
+  uint32_t mBaseRecursionDepth;
+  bool mDoingStableStates;
+
   OOMState mOutOfMemoryState;
   OOMState mLargeAllocationFailureState;
 };
diff --git a/xpcom/threads/LazyIdleThread.cpp b/xpcom/threads/LazyIdleThread.cpp
index d7e1c8d34740..1ed33ef03673 100644
--- a/xpcom/threads/LazyIdleThread.cpp
+++ b/xpcom/threads/LazyIdleThread.cpp
@@ -526,15 +526,13 @@ LazyIdleThread::OnDispatchedEvent(nsIThreadInternal* /*aThread */)
 
 NS_IMETHODIMP
 LazyIdleThread::OnProcessNextEvent(nsIThreadInternal* /* aThread */,
-                                   bool /* aMayWait */,
-                                   uint32_t /* aRecursionDepth */)
+                                   bool /* aMayWait */)
 {
   return NS_OK;
 }
 
 NS_IMETHODIMP
 LazyIdleThread::AfterProcessNextEvent(nsIThreadInternal* /* aThread */,
-                                      uint32_t /* aRecursionDepth */,
                                       bool aEventWasProcessed)
 {
   bool shouldNotifyIdle;
diff --git a/xpcom/threads/nsIThreadInternal.idl b/xpcom/threads/nsIThreadInternal.idl
index 1c2782e4cedf..9287bf5d79f5 100644
--- a/xpcom/threads/nsIThreadInternal.idl
+++ b/xpcom/threads/nsIThreadInternal.idl
@@ -13,7 +13,7 @@ interface nsIThreadObserver;
  * The XPCOM thread object implements this interface, which allows a consumer
  * to observe dispatch activity on the thread.
  */
-[scriptable, uuid(b24c5af3-43c2-4d17-be14-94d6648a305f)]
+[scriptable, uuid(9cc51754-2eb3-4b46-ae99-38a61881c622)]
 interface nsIThreadInternal : nsIThread
 {
   /**
@@ -25,13 +25,6 @@ interface nsIThreadInternal : nsIThread
    */
   attribute nsIThreadObserver observer;
 
-  /**
-   * The current recursion depth, 0 when no events are running, 1 when a single
-   * event is running, and higher when nested events are running. Must only be
-   * called on the target thread.
-   */
-  readonly attribute unsigned long recursionDepth;
-
   /**
    * Add an observer that will *only* receive onProcessNextEvent,
    * beforeProcessNextEvent. and afterProcessNextEvent callbacks. Always called
@@ -81,7 +74,7 @@ interface nsIThreadInternal : nsIThread
  *     onDispatchedEvent(thread) {
  *       NativeQueue.signal();
  *     }
- *     onProcessNextEvent(thread, mayWait, recursionDepth) {
+ *     onProcessNextEvent(thread, mayWait) {
  *       if (NativeQueue.hasNextEvent())
  *         NativeQueue.processNextEvent();
  *       while (mayWait && !thread.hasPendingEvent()) {
@@ -100,7 +93,7 @@ interface nsIThreadInternal : nsIThread
  *       afterProcessNextEvent, then another that inherits the first and adds
  *       onDispatchedEvent.
  */
-[scriptable, uuid(09b424c3-26b0-4128-9039-d66f85b02c63)]
+[uuid(cc8da053-1776-44c2-9199-b5a629d0a19d)]
 interface nsIThreadObserver : nsISupports
 {
   /**
@@ -122,29 +115,21 @@ interface nsIThreadObserver : nsISupports
    * @param mayWait
    *   Indicates whether or not the method is allowed to block the calling
    *   thread.  For example, this parameter is false during thread shutdown.
-   * @param recursionDepth
-   *   Indicates the number of calls to ProcessNextEvent on the call stack in
-   *   addition to the current call.
    */
-  void onProcessNextEvent(in nsIThreadInternal thread, in boolean mayWait,
-                          in unsigned long recursionDepth);
+  void onProcessNextEvent(in nsIThreadInternal thread, in boolean mayWait);
 
   /**
    * This method is called (from nsIThread::ProcessNextEvent) after an event
    * is processed.  It does not guarantee that an event was actually processed
    * (depends on the value of |eventWasProcessed|.  This method is only called
-   * on the target thread.
+   * on the target thread.  DO NOT EVER RUN SCRIPT FROM THIS CALLBACK!!!
    *
    * @param thread
    *   The thread that processed another event.
-   * @param recursionDepth
-   *   Indicates the number of calls to ProcessNextEvent on the call stack in
-   *   addition to the current call.
    * @param eventWasProcessed
    *   Indicates whether an event was actually processed. May be false if the
    *   |mayWait| flag was false when calling nsIThread::ProcessNextEvent().
    */
   void afterProcessNextEvent(in nsIThreadInternal thread,
-                             in unsigned long recursionDepth,
                              in bool eventWasProcessed);
 };
diff --git a/xpcom/threads/nsThread.cpp b/xpcom/threads/nsThread.cpp
index 62b82a36f3cc..eaf569f6f240 100644
--- a/xpcom/threads/nsThread.cpp
+++ b/xpcom/threads/nsThread.cpp
@@ -22,6 +22,7 @@
 #include "nsAutoPtr.h"
 #include "nsCOMPtr.h"
 #include "pratom.h"
+#include "mozilla/CycleCollectedJSRuntime.h"
 #include "mozilla/Logging.h"
 #include "nsIObserverService.h"
 #if !defined(MOZILLA_XPCOMRT_API)
@@ -94,8 +95,6 @@ GetThreadLog()
 
 NS_DECL_CI_INTERFACE_GETTER(nsThread)
 
-nsIThreadObserver* nsThread::sMainThreadObserver = nullptr;
-
 //-----------------------------------------------------------------------------
 // Because we do not have our own nsIFactory, we have to implement nsIClassInfo
 // somewhat manually.
@@ -440,6 +439,7 @@ int sCanaryOutputFD = -1;
 
 nsThread::nsThread(MainThreadFlag aMainThread, uint32_t aStackSize)
   : mLock("nsThread.mLock")
+  , mScriptObserver(nullptr)
   , mEvents(&mEventsRoot)
   , mPriority(PRIORITY_NORMAL)
   , mThread(nullptr)
@@ -824,22 +824,19 @@ nsThread::ProcessNextEvent(bool aMayWait, bool* aResult)
   }
 #endif
 
-  bool notifyMainThreadObserver =
-    (MAIN_THREAD == mIsMainThread) && sMainThreadObserver;
-  if (notifyMainThreadObserver) {
-    sMainThreadObserver->OnProcessNextEvent(this, reallyWait,
-                                            mNestedEventLoopDepth);
+  ++mNestedEventLoopDepth;
+
+  bool callScriptObserver = !!mScriptObserver;
+  if (callScriptObserver) {
+    mScriptObserver->BeforeProcessTask(reallyWait);
   }
 
   nsCOMPtr<nsIThreadObserver> obs = mObserver;
   if (obs) {
-    obs->OnProcessNextEvent(this, reallyWait, mNestedEventLoopDepth);
+    obs->OnProcessNextEvent(this, reallyWait);
   }
 
-  NOTIFY_EVENT_OBSERVERS(OnProcessNextEvent,
-                         (this, reallyWait, mNestedEventLoopDepth));
-
-  ++mNestedEventLoopDepth;
+  NOTIFY_EVENT_OBSERVERS(OnProcessNextEvent, (this, reallyWait));
 
 #ifdef MOZ_CANARY
   Canary canary;
@@ -872,20 +869,18 @@ nsThread::ProcessNextEvent(bool aMayWait, bool* aResult)
     }
   }
 
-  --mNestedEventLoopDepth;
-
-  NOTIFY_EVENT_OBSERVERS(AfterProcessNextEvent,
-                         (this, mNestedEventLoopDepth, *aResult));
+  NOTIFY_EVENT_OBSERVERS(AfterProcessNextEvent, (this, *aResult));
 
   if (obs) {
-    obs->AfterProcessNextEvent(this, mNestedEventLoopDepth, *aResult);
+    obs->AfterProcessNextEvent(this, *aResult);
   }
 
-  if (notifyMainThreadObserver && sMainThreadObserver) {
-    sMainThreadObserver->AfterProcessNextEvent(this, mNestedEventLoopDepth,
-                                               *aResult);
+  if (callScriptObserver && mScriptObserver) {
+    mScriptObserver->AfterProcessTask(mNestedEventLoopDepth);
   }
 
+  --mNestedEventLoopDepth;
+
   return rv;
 }
 
@@ -962,15 +957,11 @@ nsThread::SetObserver(nsIThreadObserver* aObs)
   return NS_OK;
 }
 
-NS_IMETHODIMP
-nsThread::GetRecursionDepth(uint32_t* aDepth)
+uint32_t
+nsThread::RecursionDepth() const
 {
-  if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) {
-    return NS_ERROR_NOT_SAME_THREAD;
-  }
-
-  *aDepth = mNestedEventLoopDepth;
-  return NS_OK;
+  MOZ_ASSERT(PR_GetCurrentThread() == mThread);
+  return mNestedEventLoopDepth;
 }
 
 NS_IMETHODIMP
@@ -1069,19 +1060,16 @@ nsThread::PopEventQueue(nsIEventTarget* aInnermostTarget)
   return NS_OK;
 }
 
-nsresult
-nsThread::SetMainThreadObserver(nsIThreadObserver* aObserver)
+void
+nsThread::SetScriptObserver(mozilla::CycleCollectedJSRuntime* aScriptObserver)
 {
-  if (aObserver && nsThread::sMainThreadObserver) {
-    return NS_ERROR_NOT_AVAILABLE;
+  if (!aScriptObserver) {
+    mScriptObserver = nullptr;
+    return;
   }
 
-  if (!NS_IsMainThread()) {
-    return NS_ERROR_UNEXPECTED;
-  }
-
-  nsThread::sMainThreadObserver = aObserver;
-  return NS_OK;
+  MOZ_ASSERT(!mScriptObserver);
+  mScriptObserver = aScriptObserver;
 }
 
 //-----------------------------------------------------------------------------
diff --git a/xpcom/threads/nsThread.h b/xpcom/threads/nsThread.h
index 7b972f0cf959..b42a939c0707 100644
--- a/xpcom/threads/nsThread.h
+++ b/xpcom/threads/nsThread.h
@@ -18,6 +18,10 @@
 #include "nsAutoPtr.h"
 #include "mozilla/AlreadyAddRefed.h"
 
+namespace mozilla {
+class CycleCollectedJSRuntime;
+}
+
 // A native thread
 class nsThread
   : public nsIThreadInternal
@@ -67,12 +71,13 @@ public:
     mEventObservers.Clear();
   }
 
-  static nsresult
-  SetMainThreadObserver(nsIThreadObserver* aObserver);
+  void
+  SetScriptObserver(mozilla::CycleCollectedJSRuntime* aScriptObserver);
+
+  uint32_t
+  RecursionDepth() const;
 
 protected:
-  static nsIThreadObserver* sMainThreadObserver;
-
   class nsChainedEventQueue;
 
   class nsNestedEventTarget;
@@ -175,6 +180,7 @@ protected:
   mozilla::Mutex mLock;
 
   nsCOMPtr<nsIThreadObserver> mObserver;
+  mozilla::CycleCollectedJSRuntime* mScriptObserver;
 
   // Only accessed on the target thread.
   nsAutoTObserverArray<nsCOMPtr<nsIThreadObserver>, 2> mEventObservers;