Bug 1179909: Refactor stable state handling. r=smaug
authorKyle Huey <khuey@kylehuey.com>
Tue, 11 Aug 2015 06:10:46 -0700
changeset 277167 a13c1f26e351dd6251da641fe7a9eb53790fc2d0
parent 277166 f78347b683ee4be6d28adb49b94ea1335750e87d
child 277168 cd84db0152b02a85e005058d19fe02a25cb1b012
push id5245
push userraliiev@mozilla.com
push dateThu, 29 Oct 2015 11:30:51 +0000
treeherdermozilla-esr52@dac831dc1bd0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1179909
milestone43.0a1
Bug 1179909: Refactor stable state handling. r=smaug This is motivated by three separate but related problems: 1. Our concept of recursion depth is broken for things that run from AfterProcessNextEvent observers (e.g. Promises). We decrement the recursionDepth counter before firing observers, so a Promise callback running at the lowest event loop depth has a recursion depth of 0 (whereas a regular nsIRunnable would be 1). This is a problem because it's impossible to distinguish a Promise running after a sync XHR's onreadystatechange handler from a top-level event (since the former runs with depth 2 - 1 = 1, and the latter runs with just 1). 2. The nsIThreadObserver mechanism that is used by a lot of code to run "after" the current event is a poor fit for anything that runs script. First, the order the observers fire in is the order they were added, not anything fixed by spec. Additionally, running script can cause the event loop to spin, which is a big source of pain here (bholley has some nasty bug caused by this). 3. We run Promises from different points in the code for workers and main thread. The latter runs from XPConnect's nsIThreadObserver callbacks, while the former runs from a hardcoded call to run Promises in the worker event loop. What workers do is particularly problematic because it means we can't get the right recursion depth no matter what we do to nsThread. The solve this, this patch does the following: 1. Consolidate some handling of microtasks and all handling of stable state from appshell and WorkerPrivate into CycleCollectedJSRuntime. 2. Make the recursionDepth counter only available to CycleCollectedJSRuntime (and its consumers) and remove it from the nsIThreadInternal and nsIThreadObserver APIs. 3. Adjust the recursionDepth counter so that microtasks run with the recursionDepth of the task they are associated with. 4. Introduce the concept of metastable state to replace appshell's RunBeforeNextEvent. Metastable state is reached after every microtask or task is completed. This provides the semantics that bent and I want for IndexedDB, where transactions autocommit at the end of a microtask and do not "spill" from one microtask into a subsequent microtask. This differs from appshell's RunBeforeNextEvent in two ways: a) It fires between microtasks, which was the motivation for starting this. b) It no longer ensures that we're at the same event loop depth in the native event queue. bent decided we don't care about this. 5. Reorder stable state to happen after microtasks such as Promises, per HTML. Right now we call the regular thread observers, including appshell, before the main thread observer (XPConnect), so stable state tasks happen before microtasks.
dom/base/nsContentUtils.cpp
dom/base/nsContentUtils.h
dom/base/nsDOMWindowUtils.cpp
dom/indexedDB/ActorsParent.cpp
dom/indexedDB/IDBFileHandle.cpp
dom/indexedDB/IDBTransaction.cpp
dom/interfaces/base/nsIDOMWindowUtils.idl
dom/media/webaudio/AudioDestinationNode.cpp
dom/promise/Promise.cpp
dom/promise/tests/test_promise.html
dom/storage/DOMStorageDBThread.cpp
dom/workers/RuntimeService.cpp
dom/workers/WorkerPrivate.cpp
dom/workers/WorkerPrivate.h
dom/workers/WorkerThread.cpp
dom/workers/test/promise_worker.js
image/test/mochitest/test_synchronized_animation.html
ipc/glue/MessagePump.cpp
js/xpconnect/src/XPCJSRuntime.cpp
js/xpconnect/src/nsXPConnect.cpp
js/xpconnect/src/xpcprivate.h
layout/style/Loader.cpp
netwerk/base/nsSocketTransportService2.cpp
netwerk/cache2/CacheIOThread.cpp
widget/ScreenProxy.cpp
widget/cocoa/nsAppShell.h
widget/cocoa/nsAppShell.mm
widget/nsBaseAppShell.cpp
widget/nsBaseAppShell.h
widget/nsIAppShell.idl
widget/nsScreenManagerProxy.cpp
widget/tests/moz.build
xpcom/base/CycleCollectedJSRuntime.cpp
xpcom/base/CycleCollectedJSRuntime.h
xpcom/threads/LazyIdleThread.cpp
xpcom/threads/nsIThreadInternal.idl
xpcom/threads/nsThread.cpp
xpcom/threads/nsThread.h
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -5202,26 +5202,28 @@ nsContentUtils::AddScriptRunner(nsIRunna
   nsCOMPtr<nsIRunnable> run = aRunnable;
   run->Run();
 
   return true;
 }
 
 /* static */
 void
-nsContentUtils::RunInStableState(already_AddRefed<nsIRunnable> aRunnable,
-                                 DispatchFailureHandling aHandling)
-{
-  nsCOMPtr<nsIRunnable> runnable = aRunnable;
-  nsCOMPtr<nsIAppShell> appShell(do_GetService(kAppShellCID));
-  if (!appShell) {
-    MOZ_ASSERT(aHandling == DispatchFailureHandling::IgnoreFailure);
-    return;
-  }
-  appShell->RunInStableState(runnable.forget());
+nsContentUtils::RunInStableState(already_AddRefed<nsIRunnable> aRunnable)
+{
+  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
 nsContentUtils::EnterMicroTask()
 {
   MOZ_ASSERT(NS_IsMainThread());
   ++sMicroTaskLevel;
 }
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -1593,35 +1593,39 @@ public:
   /**
    * Call this function if !IsSafeToRunScript() and we fail to run the script
    * (rather than using AddScriptRunner as we usually do). |aDocument| is
    * optional as it is only used for showing the URL in the console.
    */
   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
    * 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.
    */
-  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.
    * This will return information in the viewport META data section
    * of the document. This can be used in lieu of ProcessViewportInfo(),
    * which places the viewport information in the document header instead
    * of returning it directly.
    *
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -83,18 +83,17 @@
 #include "mozilla/dom/quota/PersistenceType.h"
 #include "mozilla/dom/quota/QuotaManager.h"
 #include "mozilla/layers/FrameUniformityData.h"
 #include "mozilla/layers/ShadowLayers.h"
 #include "nsPrintfCString.h"
 #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"
 #include "nsIBaseWindow.h"
 #include "nsIDocShellTreeOwner.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "GeckoProfiler.h"
 #include "mozilla/Preferences.h"
@@ -3524,40 +3523,16 @@ nsDOMWindowUtils::DispatchEventToChromeO
   MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
   NS_ENSURE_STATE(aTarget && aEvent);
   aEvent->GetInternalNSEvent()->mFlags.mOnlyChromeDispatch = true;
   aTarget->DispatchEvent(aEvent, aRetVal);
   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)
 {
   MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
 
   if (nsIWidget* widget = GetWidget()) {
     mozilla::layers::LayerManager* manager = widget->GetLayerManager();
     if (manager) {
--- a/dom/indexedDB/ActorsParent.cpp
+++ b/dom/indexedDB/ActorsParent.cpp
@@ -25412,25 +25412,23 @@ NS_IMPL_ISUPPORTS(DEBUGThreadSlower, nsI
 NS_IMETHODIMP
 DEBUGThreadSlower::OnDispatchedEvent(nsIThreadInternal* /* aThread */)
 {
   MOZ_CRASH("Should never be called!");
 }
 
 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);
 
   MOZ_ALWAYS_TRUE(PR_Sleep(PR_MillisecondsToInterval(kDEBUGThreadSleepMS)) ==
                     PR_SUCCESS);
   return NS_OK;
 }
--- a/dom/indexedDB/IDBFileHandle.cpp
+++ b/dom/indexedDB/IDBFileHandle.cpp
@@ -7,30 +7,23 @@
 #include "IDBFileHandle.h"
 
 #include "IDBEvents.h"
 #include "IDBMutableFile.h"
 #include "mozilla/dom/FileService.h"
 #include "mozilla/dom/IDBFileHandleBinding.h"
 #include "mozilla/dom/MetadataHelper.h"
 #include "mozilla/EventDispatcher.h"
-#include "nsIAppShell.h"
 #include "nsServiceManagerUtils.h"
 #include "nsWidgetsCID.h"
 
 namespace mozilla {
 namespace dom {
 namespace indexedDB {
 
-namespace {
-
-NS_DEFINE_CID(kAppShellCID2, NS_APPSHELL_CID);
-
-} // namespace
-
 IDBFileHandle::IDBFileHandle(FileMode aMode,
                              RequestMode aRequestMode,
                              IDBMutableFile* aMutableFile)
   : FileHandleBase(aMode, aRequestMode)
   , mMutableFile(aMutableFile)
 {
 }
 
@@ -46,34 +39,27 @@ IDBFileHandle::Create(FileMode aMode,
 {
   MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
 
   nsRefPtr<IDBFileHandle> fileHandle =
     new IDBFileHandle(aMode, aRequestMode, aMutableFile);
 
   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();
 
   FileService* service = FileService::GetOrCreate();
   if (NS_WARN_IF(!service)) {
     return nullptr;
   }
 
-  rv = service->Enqueue(fileHandle, nullptr);
+  nsresult rv = service->Enqueue(fileHandle, nullptr);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return nullptr;
   }
 
   aMutableFile->Database()->OnNewFileHandle();
 
   return fileHandle.forget();
 }
--- a/dom/indexedDB/IDBTransaction.cpp
+++ b/dom/indexedDB/IDBTransaction.cpp
@@ -11,66 +11,34 @@
 #include "IDBEvents.h"
 #include "IDBObjectStore.h"
 #include "IDBRequest.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/EventDispatcher.h"
 #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"
 #include "WorkerPrivate.h"
 
 // Include this last to avoid path problems on Windows.
 #include "ActorsChild.h"
 
 namespace mozilla {
 namespace dom {
 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
 {
   WorkerPrivate* mWorkerPrivate;
 
   // The IDBTransaction owns this object so we only need a weak reference back
   // to it.
   IDBTransaction* mTransaction;
@@ -217,25 +185,18 @@ IDBTransaction::CreateVersionChange(
     new IDBTransaction(aDatabase,
                        emptyObjectStoreNames,
                        VERSION_CHANGE);
   aOpenRequest->GetCallerLocation(transaction->mFilename,
                                   &transaction->mLineNo);
 
   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;
   transaction->mNextIndexId = aNextIndexId;
   transaction->mCreating = true;
 
   aDatabase->RegisterTransaction(transaction);
   transaction->mRegistered = true;
@@ -257,20 +218,18 @@ IDBTransaction::Create(IDBDatabase* aDat
              aMode == READ_WRITE_FLUSH);
 
   nsRefPtr<IDBTransaction> transaction =
     new IDBTransaction(aDatabase, aObjectStoreNames, aMode);
   IDBRequest::CaptureCaller(transaction->mFilename, &transaction->mLineNo);
 
   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;
 
   aDatabase->RegisterTransaction(transaction);
   transaction->mRegistered = true;
 
   if (!NS_IsMainThread()) {
     WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -44,17 +44,17 @@ interface nsIDOMClientRect;
 interface nsIURI;
 interface nsIDOMEventTarget;
 interface nsIRunnable;
 interface nsITranslationNodeList;
 interface nsIJSRAIIHelper;
 interface nsIContentPermissionRequest;
 interface nsIObserver;
 
-[scriptable, uuid(7a37e173-ea6e-495e-8702-013f8063352a)]
+[scriptable, uuid(6064615a-a782-4d08-86db-26ef3851208a)]
 interface nsIDOMWindowUtils : nsISupports {
 
   /**
    * Image animation mode of the window. When this attribute's value
    * is changed, the implementation should set all images in the window
    * to the given value. That is, when set to kDontAnimMode, all images
    * will stop animating. The attribute's value must be one of the
    * animationMode values from imgIContainer.
@@ -1717,42 +1717,16 @@ interface nsIDOMWindowUtils : nsISupport
    */
   boolean isNodeDisabledForEvents(in nsIDOMNode aNode);
 
   /**
    * Setting paintFlashing to true will flash newly painted area.
    */
   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,
    * returns an empty string.
    */
   AString getOMTAStyle(in nsIDOMElement aElement, in AString aProperty,
                        [optional] in AString aPseudoElement);
 
--- a/dom/media/webaudio/AudioDestinationNode.cpp
+++ b/dom/media/webaudio/AudioDestinationNode.cpp
@@ -628,19 +628,17 @@ AudioDestinationNode::NotifyStableState(
 
 void
 AudioDestinationNode::ScheduleStableStateNotification()
 {
   nsCOMPtr<nsIRunnable> event =
     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
 AudioDestinationNode::ExtraCurrentTime()
 {
   if (!mStartedBlockingDueToBeingOnlyNode.IsNull() &&
       !mExtraCurrentTimeUpdatedSinceLastStableState) {
     mExtraCurrentTimeUpdatedSinceLastStableState = true;
--- a/dom/promise/Promise.cpp
+++ b/dom/promise/Promise.cpp
@@ -505,16 +505,17 @@ Promise::PerformMicroTaskCheckpoint()
     microtaskQueue.pop();
     nsresult rv = runnable->Run();
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return false;
     }
     if (cx.isSome()) {
       JS_CheckForInterrupt(cx.ref());
     }
+    runtime->AfterProcessMicrotask();
   } while (!microtaskQueue.empty());
 
   return true;
 }
 
 /* static */ bool
 Promise::JSCallback(JSContext* aCx, unsigned aArgc, JS::Value* aVp)
 {
--- a/dom/promise/tests/test_promise.html
+++ b/dom/promise/tests/test_promise.html
@@ -164,16 +164,36 @@ function promiseAsync_ResolveThenTimeout
 
     // Allow other assertions to run so the test could fail before the next one.
     setTimeout(runTest, 0);
   }, 0);
 
   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) {
     r1(42);
   });
 
   promise.then(function(what) {
     ok(true, "Then.resolve has been called");
@@ -751,16 +771,17 @@ function promiseWrapperAsyncResolution()
   });
 }
 
 var tests = [ promiseResolve, promiseReject,
               promiseException, promiseGC,
               promiseAsync_TimeoutResolveThen,
               promiseAsync_ResolveTimeoutThen,
               promiseAsync_ResolveThenTimeout,
+              promiseAsync_SyncXHR,
               promiseDoubleThen, promiseThenException,
               promiseThenCatchThen, promiseRejectThenCatchThen,
               promiseRejectThenCatchThen2,
               promiseRejectThenCatchExceptionThen,
               promiseThenCatchOrderingResolve,
               promiseThenCatchOrderingReject,
               promiseNestedPromise, promiseNestedNestedPromise,
               promiseWrongNestedPromise, promiseLoop,
--- a/dom/storage/DOMStorageDBThread.cpp
+++ b/dom/storage/DOMStorageDBThread.cpp
@@ -357,25 +357,23 @@ DOMStorageDBThread::ThreadObserver::OnDi
   MonitorAutoLock lock(mMonitor);
   mHasPendingEvents = true;
   lock.Notify();
   return NS_OK;
 }
 
 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;
 }
 
 
 extern void
 ReverseString(const nsCSubstring& aSource, nsCSubstring& aResult);
--- a/dom/workers/RuntimeService.cpp
+++ b/dom/workers/RuntimeService.cpp
@@ -991,16 +991,25 @@ public:
 
     mWorkerPrivate->AssertIsOnWorkerThread();
 
     if (aStatus == JSGC_END) {
       nsCycleCollector_collect(nullptr);
     }
   }
 
+  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;
 };
 
 #ifdef ENABLE_TESTS
 
 class TestPBackgroundCreateCallback final :
   public nsIIPCBackgroundChildCreateCallback
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -2583,32 +2583,16 @@ NS_IMPL_ISUPPORTS(WorkerPrivate::MemoryR
 WorkerPrivate::SyncLoopInfo::SyncLoopInfo(EventTarget* aEventTarget)
 : mEventTarget(aEventTarget), mCompleted(false), mResult(false)
 #ifdef DEBUG
   , mHasRun(false)
 #endif
 {
 }
 
-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
 {
   AssertIsOnMainThread();
   if (mLoadInfo.mWindow) {
     return mLoadInfo.mWindow->GetExtantDoc();
   }
@@ -5182,20 +5166,16 @@ WorkerPrivate::DoRunLoop(JSContext* aCx)
         JS_MaybeGC(aCx);
       }
     } else if (normalRunnablesPending) {
       MOZ_ASSERT(NS_HasPendingEvents(mThread));
 
       // 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.
         JSAutoCompartment ac(aCx, GlobalScope()->GetGlobalJSObject());
         JS_MaybeGC(aCx);
       }
     }
 
@@ -5205,95 +5185,38 @@ WorkerPrivate::DoRunLoop(JSContext* aCx)
       SetGCTimerMode(IdleTimer);
     }
   }
 
   MOZ_CRASH("Shouldn't get here!");
 }
 
 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)
-{
-  AssertIsOnWorkerThread();
-  MOZ_ASSERT(aRecursionDepth);
-}
-
-bool
-WorkerPrivate::RunBeforeNextEvent(nsIRunnable* aRunnable)
+WorkerPrivate::AfterProcessNextEvent()
 {
   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
 WorkerPrivate::MaybeDispatchLoadFailedRunnable()
 {
   AssertIsOnWorkerThread();
 
   nsCOMPtr<nsIRunnable> runnable = StealLoadFailedAsyncRunnable();
--- a/dom/workers/WorkerPrivate.h
+++ b/dom/workers/WorkerPrivate.h
@@ -944,19 +944,16 @@ class WorkerPrivate : public WorkerPriva
 #endif
   };
 
   // This is only modified on the worker thread, but in DEBUG builds
   // AssertValidSyncLoop function iterates it on other threads. Therefore
   // 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;
   nsCOMPtr<nsIEventTarget> mPeriodicGCTimerTarget;
   nsCOMPtr<nsIEventTarget> mIdleGCTimerTarget;
 
   nsRefPtr<MemoryReporter> mMemoryReporter;
 
@@ -1357,20 +1354,20 @@ public:
   {
     return mCancelAllPendingRunnables;
   }
 
   void
   ClearMainEventQueue(WorkerRanOrNot aRanOrNot);
 
   void
-  OnProcessNextEvent(uint32_t aRecursionDepth);
+  OnProcessNextEvent();
 
   void
-  AfterProcessNextEvent(uint32_t aRecursionDepth);
+  AfterProcessNextEvent();
 
   void
   AssertValidSyncLoop(nsIEventTarget* aSyncLoopTarget)
 #ifdef DEBUG
   ;
 #else
   { }
 #endif
@@ -1387,21 +1384,16 @@ public:
   // Only valid after CompileScriptRunnable has finished running!
   bool
   WorkerScriptExecutedSuccessfully() const
   {
     AssertIsOnWorkerThread();
     return mWorkerScriptExecutedSuccessfully;
   }
 
-  // Just like nsIAppShell::RunBeforeNextEvent. May only be called on the worker
-  // thread.
-  bool
-  RunBeforeNextEvent(nsIRunnable* aRunnable);
-
   void
   MaybeDispatchLoadFailedRunnable();
 
 private:
   WorkerPrivate(JSContext* aCx, WorkerPrivate* aParent,
                 const nsAString& aScriptURL, bool aIsChromeWorker,
                 WorkerType aWorkerType, const nsACString& aSharedWorkerName,
                 WorkerLoadInfo& aLoadInfo);
--- a/dom/workers/WorkerThread.cpp
+++ b/dom/workers/WorkerThread.cpp
@@ -305,42 +305,40 @@ NS_IMPL_ISUPPORTS(WorkerThread::Observer
 NS_IMETHODIMP
 WorkerThread::Observer::OnDispatchedEvent(nsIThreadInternal* /* aThread */)
 {
   MOZ_CRASH("OnDispatchedEvent() should never be called!");
 }
 
 NS_IMETHODIMP
 WorkerThread::Observer::OnProcessNextEvent(nsIThreadInternal* /* aThread */,
-                                           bool aMayWait,
-                                           uint32_t aRecursionDepth)
+                                           bool aMayWait)
 {
   mWorkerPrivate->AssertIsOnWorkerThread();
 
   // If the PBackground child is not created yet, then we must permit
   // blocking event processing to support SynchronouslyCreatePBackground().
   // If this occurs then we are spinning on the event queue at the start of
   // 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;
 }
 
 } // namespace workers
 } // namespace dom
 } // namespace mozilla
--- a/dom/workers/test/promise_worker.js
+++ b/dom/workers/test/promise_worker.js
@@ -1,13 +1,18 @@
 function ok(a, msg) {
   dump("OK: " + !!a + "  =>  " + a + " " + msg + "\n");
   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 });
 }
 
 function isnot(a, b, msg) {
   dump("ISNOT: " + (a!==b) + "  =>  " + a + " | " + b + " " + msg + "\n");
   postMessage({type: 'status', status: a !== b, msg: a + " !== " + b + ": " + msg });
@@ -143,17 +148,17 @@ function promiseAsync_ResolveThenTimeout
 
     // Allow other assertions to run so the test could fail before the next one.
     setTimeout(runTest, 0);
   }, 0);
 
   ok(!handlerExecuted, "Handlers are not called before 'then' returns.");
 }
 
-function promiseAsync_SyncHXRAndImportScripts()
+function promiseAsync_SyncXHRAndImportScripts()
 {
   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);
@@ -785,17 +790,17 @@ function promiseWrapperAsyncResolution()
 
 var tests = [
     promiseResolve,
     promiseReject,
     promiseException,
     promiseAsync_TimeoutResolveThen,
     promiseAsync_ResolveTimeoutThen,
     promiseAsync_ResolveThenTimeout,
-    promiseAsync_SyncHXRAndImportScripts,
+    promiseAsync_SyncXHRAndImportScripts,
     promiseDoubleThen,
     promiseThenException,
     promiseThenCatchThen,
     promiseRejectThenCatchThen,
     promiseRejectThenCatchThen2,
     promiseRejectThenCatchExceptionThen,
     promiseThenCatchOrderingResolve,
     promiseThenCatchOrderingReject,
--- a/image/test/mochitest/test_synchronized_animation.html
+++ b/image/test/mochitest/test_synchronized_animation.html
@@ -50,18 +50,17 @@ function cleanUpAndFinish() {
 
   SimpleTest.finish();
 
   gFinished = true;
 }
 
 function frameUpdate(aRequest) {
   if (!gDispatched) {
-    var util = window.getInterface(Ci.nsIDOMWindowUtils);
-    util.runBeforeNextEvent(function() { 
+    Promise.resolve().then(function() { 
       gRanEvent = true;
     });
     gDispatched = true;
     gFirstRequest = aRequest;
   } else if (aRequest != gFirstRequest) {
     ok(!gRanEvent, "Should not have run event before all frame update events occurred!");
     cleanUpAndFinish();
   }
--- a/ipc/glue/MessagePump.cpp
+++ b/ipc/glue/MessagePump.cpp
@@ -428,23 +428,21 @@ MessagePumpForNonMainUIThreads::OnDispat
   if (GetInWait()) {
     ScheduleWork();
   }
   return NS_OK;
 }
 
 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;
 }
 
 #endif // XP_WIN
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -3569,16 +3569,69 @@ XPCJSRuntime::NoteCustomGCThingXPCOMChil
     // will be held alive through the parent of the JSObject of the tearoff.
     XPCWrappedNativeTearOff* to =
         static_cast<XPCWrappedNativeTearOff*>(xpc_GetJSPrivate(obj));
     NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "xpc_GetJSPrivate(obj)->mNative");
     cb.NoteXPCOMChild(to->GetNative());
     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
 XPCJSRuntime::DebugDump(int16_t depth)
 {
 #ifdef DEBUG
     depth--;
     XPC_LOG_ALWAYS(("XPCJSRuntime @ %x", this));
--- a/js/xpconnect/src/nsXPConnect.cpp
+++ b/js/xpconnect/src/nsXPConnect.cpp
@@ -21,30 +21,27 @@
 #include "AccessCheck.h"
 
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/Exceptions.h"
 #include "mozilla/dom/Promise.h"
 
 #include "nsDOMMutationObserver.h"
 #include "nsICycleCollectorListener.h"
-#include "nsThread.h"
 #include "mozilla/XPTInterfaceInfoManager.h"
 #include "nsIObjectInputStream.h"
 #include "nsIObjectOutputStream.h"
 #include "nsScriptSecurityManager.h"
 
 using namespace mozilla;
 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;
 uint32_t     nsXPConnect::gReportAllJSExceptions = 0;
 
 // Global cache of the default script security manager (QI'd to
 // nsIScriptSecurityManager) and the system principal.
 nsIScriptSecurityManager* nsXPConnect::gScriptSecurityManager = nullptr;
@@ -56,18 +53,17 @@ const char XPC_CONSOLE_CONTRACTID[]     
 const char XPC_SCRIPT_ERROR_CONTRACTID[]  = "@mozilla.org/scripterror;1";
 const char XPC_ID_CONTRACTID[]            = "@mozilla.org/js/xpc/ID;1";
 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);
 
     char* reportableEnv = PR_GetEnv("MOZ_REPORT_ALL_JS_EXCEPTIONS");
     if (reportableEnv && *reportableEnv)
         gReportAllJSExceptions = 1;
 }
 
@@ -115,21 +111,16 @@ nsXPConnect::InitStatics()
     if (!gSelf->mRuntime) {
         NS_RUNTIMEABORT("Couldn't create XPCJSRuntime.");
     }
 
     // Initial extra ref to keep the singleton alive
     // 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();
     gScriptSecurityManager->GetSystemPrincipal(&gSystemPrincipal);
     MOZ_RELEASE_ASSERT(gSystemPrincipal);
 
     // Initialize the SafeJSContext.
     gSelf->mRuntime->GetJSContextStack()->InitSafeJSContext();
@@ -147,18 +138,16 @@ nsXPConnect::GetSingleton()
 }
 
 // static
 void
 nsXPConnect::ReleaseXPConnectSingleton()
 {
     nsXPConnect* xpc = gSelf;
     if (xpc) {
-        nsThread::SetMainThreadObserver(nullptr);
-
         nsrefcnt cnt;
         NS_RELEASE2(xpc, cnt);
     }
 }
 
 // static
 XPCJSRuntime*
 nsXPConnect::GetRuntimeInstance()
@@ -928,91 +917,16 @@ nsXPConnect::JSToVariant(JSContext* ctx,
     nsRefPtr<XPCVariant> variant = XPCVariant::newVariant(ctx, value);
     variant.forget(_retval);
     if (!(*_retval))
         return NS_ERROR_FAILURE;
 
     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)
 {
     // Ignore if the environment variable was set.
     if (gReportAllJSExceptions != 1)
         gReportAllJSExceptions = newval ? 2 : 0;
 
     return NS_OK;
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -148,17 +148,16 @@
 #include "nsDeque.h"
 
 #include "nsIScriptSecurityManager.h"
 
 #include "nsIPrincipal.h"
 #include "nsJSPrincipals.h"
 #include "nsIScriptObjectPrincipal.h"
 #include "xpcObjectHelper.h"
-#include "nsIThreadInternal.h"
 
 #include "SandboxPrivate.h"
 #include "BackstagePass.h"
 #include "nsAXPCNativeCallContext.h"
 
 #ifdef XP_WIN
 // Nasty MS defines
 #ifdef GetClassInfo
@@ -235,24 +234,22 @@ static inline bool IS_WN_REFLECTOR(JSObj
 ****************************************************************************
 ***************************************************************************/
 
 // We have a general rule internally that getters that return addref'd interface
 // pointer generally do so using an 'out' parm. When interface pointers are
 // 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:
     // These get non-addref'd pointers
     static nsXPConnect*  XPConnect()
     {
         // Do a release-mode assert that we're not doing anything significant in
         // XPConnect off the main thread. If you're an extension developer hitting
@@ -319,23 +316,16 @@ protected:
 private:
     // Singleton instance
     static nsXPConnect*             gSelf;
     static bool                     gOnceAliveNowDead;
 
     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:
     static nsIScriptSecurityManager* gScriptSecurityManager;
     static nsIPrincipal* gSystemPrincipal;
 };
 
 /***************************************************************************/
@@ -484,16 +474,19 @@ public:
 
     virtual bool
     DescribeCustomObjects(JSObject* aObject, const js::Class* aClasp,
                           char (&aName)[72]) const override;
     virtual bool
     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
      * destroy during the GC.
      */
 
 public:
     bool GetDoingFinalization() const {return mDoingFinalization;}
@@ -610,26 +603,16 @@ public:
     JSObject* PrivilegedJunkScope() { return mPrivilegedJunkScope; }
     JSObject* CompilationScope() { return mCompilationScope; }
 
     void InitSingletonScopes();
     void DeleteSingletonScopes();
 
     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:
     XPCJSRuntime(); // no implementation
     explicit XPCJSRuntime(nsXPConnect* aXPConnect);
 
     void ReleaseIncrementally(nsTArray<nsISupports*>& array);
 
--- a/layout/style/Loader.cpp
+++ b/layout/style/Loader.cpp
@@ -426,30 +426,30 @@ SheetLoadData::Run()
 NS_IMETHODIMP
 SheetLoadData::OnDispatchedEvent(nsIThreadInternal* aThread)
 {
   return NS_OK;
 }
 
 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);
   return NS_OK;
 }
 
 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);
   return NS_OK;
 }
 
 void
 SheetLoadData::FireLoadEvent(nsIThreadInternal* aThread)
--- a/netwerk/base/nsSocketTransportService2.cpp
+++ b/netwerk/base/nsSocketTransportService2.cpp
@@ -758,24 +758,23 @@ nsSocketTransportService::OnDispatchedEv
     DebugMutexAutoLock lock(mLock);
     if (mThreadEvent)
         PR_SetPollableEvent(mThreadEvent);
     return NS_OK;
 }
 
 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;
 }
 
 void
 nsSocketTransportService::MarkTheLastElementOfPendingQueue()
 {
--- a/netwerk/cache2/CacheIOThread.cpp
+++ b/netwerk/cache2/CacheIOThread.cpp
@@ -310,22 +310,22 @@ NS_IMETHODIMP CacheIOThread::OnDispatche
 {
   MonitorAutoLock lock(mMonitor);
   mHasXPCOMEvents = true;
   MOZ_ASSERT(mInsideLoop);
   lock.Notify();
   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;
 }
 
 // Memory reporting
 
 size_t CacheIOThread::SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const
--- a/widget/ScreenProxy.cpp
+++ b/widget/ScreenProxy.cpp
@@ -164,26 +164,19 @@ void
 ScreenProxy::InvalidateCacheOnNextTick()
 {
   if (mCacheWillInvalidate) {
     return;
   }
 
   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
 ScreenProxy::InvalidateCache()
 {
   mCacheValid = false;
   mCacheWillInvalidate = false;
 }
--- a/widget/cocoa/nsAppShell.h
+++ b/widget/cocoa/nsAppShell.h
@@ -30,20 +30,18 @@ public:
   NS_IMETHOD ResumeNative(void);
 
   nsAppShell();
 
   nsresult Init();
 
   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
   void WillTerminate();
 
 protected:
   virtual ~nsAppShell();
 
--- a/widget/cocoa/nsAppShell.mm
+++ b/widget/cocoa/nsAppShell.mm
@@ -730,58 +730,55 @@ nsAppShell::Exit(void)
 // Set up an autorelease pool that will service any autoreleased Cocoa
 // objects during this event.  This includes native events processed by
 // ProcessNextNativeEvent.  The autorelease pool will be popped by
 // AfterProcessNextEvent, it is important for these two methods to be
 // tightly coupled.
 //
 // 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;
 
   NS_ASSERTION(mAutoreleasePools,
                "No stack on which to store autorelease pool");
 
   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;
 }
 
 // AfterProcessNextEvent
 //
 // This nsIThreadObserver method is called after event processing is complete.
 // The Cocoa implementation cleans up the autorelease pool create by the
 // previous OnProcessNextEvent call.
 //
 // public
 NS_IMETHODIMP
 nsAppShell::AfterProcessNextEvent(nsIThreadInternal *aThread,
-                                  uint32_t aRecursionDepth,
                                   bool aEventWasProcessed)
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
 
   CFIndex count = ::CFArrayGetCount(mAutoreleasePools);
 
   NS_ASSERTION(mAutoreleasePools && count,
                "Processed an event, but there's no autorelease pool?");
 
   const NSAutoreleasePool* pool = static_cast<const NSAutoreleasePool*>
     (::CFArrayGetValueAtIndex(mAutoreleasePools, count - 1));
   ::CFArrayRemoveValueAtIndex(mAutoreleasePools, count - 1);
   [pool release];
 
-  return nsBaseAppShell::AfterProcessNextEvent(aThread, aRecursionDepth,
-                                               aEventWasProcessed);
+  return nsBaseAppShell::AfterProcessNextEvent(aThread, aEventWasProcessed);
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
 }
 
 
 // AppShellDelegate implementation
 
 
--- a/widget/nsBaseAppShell.cpp
+++ b/widget/nsBaseAppShell.cpp
@@ -26,26 +26,24 @@ nsBaseAppShell::nsBaseAppShell()
   , mEventloopNestingLevel(0)
   , mBlockedWait(nullptr)
   , mFavorPerf(0)
   , mNativeEventPending(false)
   , mStarvationDelay(0)
   , mSwitchTime(0)
   , mLastNativeEventTime(0)
   , mEventloopNestingState(eEventloopNone)
-  , mRunningSyncSections(false)
   , mRunning(false)
   , mExiting(false)
   , mBlockNativeEvent(false)
 {
 }
 
 nsBaseAppShell::~nsBaseAppShell()
 {
-  NS_ASSERTION(mSyncSections.IsEmpty(), "Must have run all sync sections");
 }
 
 nsresult
 nsBaseAppShell::Init()
 {
   // Configure ourselves as an observer for the current thread:
 
   nsCOMPtr<nsIThreadInternal> threadInt =
@@ -115,41 +113,34 @@ void
 nsBaseAppShell::DoProcessMoreGeckoEvents()
 {
   OnDispatchedEvent(nullptr);
 }
 
 
 // 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
   // do that when this function returns.
   //
   // If the next native event is not our NativeEventCallback, then we may end
   // up recursing into this function.
   //
   // However, if the next native event is not our NativeEventCallback, but it
   // results in another native event loop, then our NativeEventCallback could
   // fire and it will see mEventloopNestingState as eEventloopOther.
   //
   EventloopNestingState prevVal = mEventloopNestingState;
   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;
   return result;
 }
 
 //-------------------------------------------------------------------------
 // nsIAppShell methods:
@@ -234,18 +225,17 @@ nsBaseAppShell::OnDispatchedEvent(nsIThr
 
   // Returns on the main thread in NativeEventCallback above
   ScheduleNativeEventCallback();
   return NS_OK;
 }
 
 // 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)
       return NS_OK;
     // Hmm, we're in a nested native event loop and would like to get
     // back to it ASAP, but it seems a gecko event has caused us to
     // spin up a nested XPCOM event loop (eg. modal window), so we
     // really must start processing native events here again.
@@ -273,50 +263,47 @@ nsBaseAppShell::OnProcessNextEvent(nsITh
   mProcessedGeckoEvents = false;
 
   if (mFavorPerf <= 0 && start > mSwitchTime + mStarvationDelay) {
     // Favor pending native events
     PRIntervalTime now = start;
     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);
     }
   }
 
   while (!NS_HasPendingEvents(thr) && !mProcessedGeckoEvents) {
     // If we have been asked to exit from Run, then we should not wait for
     // events to process.  Note that an inner nested event loop causes
     // 'mayWait' to become false too, through 'mBlockedWait'.
     if (mExiting)
       mayWait = false;
 
     mLastNativeEventTime = PR_IntervalNow();
-    if (!DoProcessNextNativeEvent(mayWait, recursionDepth) || !mayWait)
+    if (!DoProcessNextNativeEvent(mayWait) || !mayWait)
       break;
   }
 
   mBlockedWait = oldBlockedWait;
 
   // Make sure that the thread event queue does not block on its monitor, as
   // it normally would do if it did not have any pending events.  To avoid
   // that, we simply insert a dummy event into its queue during shutdown.
   if (needEvent && !mExiting && !NS_HasPendingEvents(thr)) {
     DispatchDummyEvent(thr);
   }
 
-  // We're about to run an event, so we're in a stable state.
-  RunSyncSections(true, recursionDepth);
-
   return NS_OK;
 }
 
 bool
 nsBaseAppShell::DispatchDummyEvent(nsIThread* aTarget)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
@@ -339,122 +326,24 @@ void
 nsBaseAppShell::DecrementEventloopNestingLevel()
 {
   --mEventloopNestingLevel;
 #if defined(MOZ_CRASHREPORTER)
   CrashReporter::SetEventloopNestingLevel(mEventloopNestingLevel);
 #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;
 }
 
 NS_IMETHODIMP
 nsBaseAppShell::Observe(nsISupports *subject, const char *topic,
                         const char16_t *data)
 {
   NS_ASSERTION(!strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID), "oops");
   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;
-}
--- a/widget/nsBaseAppShell.h
+++ b/widget/nsBaseAppShell.h
@@ -20,17 +20,16 @@
  * to enable platform-specific event queue support.
  */
 class nsBaseAppShell : public nsIAppShell, public nsIThreadObserver,
                        public nsIObserver
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIAPPSHELL
-  void RunInStableState(already_AddRefed<nsIRunnable> runnable) override;
 
   NS_DECL_NSITHREADOBSERVER
   NS_DECL_NSIOBSERVER
 
   nsBaseAppShell();
 
 protected:
   virtual ~nsBaseAppShell();
@@ -72,55 +71,23 @@ protected:
    *   This method returns "true" if a native event was processed.
    */
   virtual bool ProcessNextNativeEvent(bool mayWait) = 0;
 
   int32_t mSuspendNativeCount;
   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
    * an outer OnProcessNextEvent invocation.  Nested calls always set
    * it to false to unblock an outer loop, since all events may
    * have been consumed by the inner event loop(s).
    */
   bool *mBlockedWait;
@@ -130,18 +97,16 @@ private:
   PRIntervalTime mSwitchTime;
   PRIntervalTime mLastNativeEventTime;
   enum EventloopNestingState {
     eEventloopNone,  // top level thread execution
     eEventloopXPCOM, // innermost native event loop is ProcessNextNativeEvent
     eEventloopOther  // innermost native event loop is a native library/plugin etc
   };
   EventloopNestingState mEventloopNestingState;
-  nsTArray<SyncSection> mSyncSections;
-  bool mRunningSyncSections;
   bool mRunning;
   bool mExiting;
   /**
    * mBlockNativeEvent blocks the appshell from processing native events.
    * It is set to true while a nested native event loop (eEventloopOther)
    * is processing gecko events in NativeEventCallback(), thus queuing up
    * native events until we return to that loop (bug 420148).
    * We force mBlockNativeEvent to false in case handling one of the gecko
--- a/widget/nsIAppShell.idl
+++ b/widget/nsIAppShell.idl
@@ -10,17 +10,17 @@ interface nsIRunnable;
 %{ C++
 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
 {
   /**
    * Enter an event loop.  Don't leave until exit() is called.
    */
   void run();
 
   /**
@@ -68,31 +68,9 @@ interface nsIAppShell : nsISupports
    * resumeNative() may be nested.  On some platforms this will be a no-op.
    */
   void resumeNative();
 
   /**
    * 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);
 };
--- a/widget/nsScreenManagerProxy.cpp
+++ b/widget/nsScreenManagerProxy.cpp
@@ -193,26 +193,19 @@ void
 nsScreenManagerProxy::InvalidateCacheOnNextTick()
 {
   if (mCacheWillInvalidate) {
     return;
   }
 
   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
 nsScreenManagerProxy::InvalidateCache()
 {
   mCacheValid = false;
   mCacheWillInvalidate = false;
 
--- a/widget/tests/moz.build
+++ b/widget/tests/moz.build
@@ -3,20 +3,16 @@
 # 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/.
 
 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':
 #     if CONFIG['NS_ENABLE_TSF']:
 #         Test disabled because it uses the internal string APIs incorrectly
 #         (see bug 582863)
 #         CPP_UNIT_TESTS += ['TestWinTSF']
 #
--- a/xpcom/base/CycleCollectedJSRuntime.cpp
+++ b/xpcom/base/CycleCollectedJSRuntime.cpp
@@ -58,30 +58,32 @@
 #include <algorithm>
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/AutoRestore.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/Telemetry.h"
 #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"
 #include "nsCycleCollectionNoteRootCallback.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsCycleCollector.h"
 #include "nsDOMJSUtils.h"
 #include "nsJSUtils.h"
 
 #ifdef MOZ_CRASHREPORTER
 #include "nsExceptionHandler.h"
 #endif
 
 #include "nsIException.h"
+#include "nsThread.h"
 #include "nsThreadUtils.h"
 #include "xpcpublic.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 namespace mozilla {
 
@@ -397,19 +399,28 @@ static const JSZoneParticipant sJSZoneCy
 CycleCollectedJSRuntime::CycleCollectedJSRuntime(JSRuntime* aParentRuntime,
                                                  uint32_t aMaxBytes,
                                                  uint32_t aMaxNurseryBytes)
   : mGCThingCycleCollectorGlobal(sGCThingCycleCollectorGlobal)
   , mJSZoneCycleCollectorGlobal(sJSZoneCycleCollectorGlobal)
   , 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);
   if (!mJSRuntime) {
     MOZ_CRASH();
   }
 
   if (!JS_AddExtraGCRootsTracer(mJSRuntime, TraceBlackJS, this)) {
@@ -435,24 +446,34 @@ CycleCollectedJSRuntime::CycleCollectedJ
   nsCycleCollector_registerJSRuntime(this);
 }
 
 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;
 
   JS_DestroyRuntime(mJSRuntime);
   mJSRuntime = nullptr;
   nsCycleCollector_forgetJSRuntime();
 
   mozilla::dom::DestroyScriptSettings();
+
+  mOwningThread->SetScriptObserver(nullptr);
+  NS_RELEASE(mOwningThread);
 }
 
 size_t
 CycleCollectedJSRuntime::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
 {
   size_t n = 0;
 
   // We're deliberately not measuring anything hanging off the entries in
@@ -1010,16 +1031,122 @@ CycleCollectedJSRuntime::DeferredFinaliz
 }
 
 void
 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)
   : mRuntime(aRt)
   , mFinalizeFunctionToRun(0)
   , mReleasing(false)
 {
   for (auto iter = aFinalizers.Iter(); !iter.Done(); iter.Next()) {
--- a/xpcom/base/CycleCollectedJSRuntime.h
+++ b/xpcom/base/CycleCollectedJSRuntime.h
@@ -16,16 +16,17 @@
 #include "nsCycleCollectionParticipant.h"
 #include "nsDataHashtable.h"
 #include "nsHashKeys.h"
 #include "nsTArray.h"
 
 class nsCycleCollectionNoteRootCallback;
 class nsIException;
 class nsIRunnable;
+class nsThread;
 
 namespace js {
 struct Class;
 } // namespace js
 
 namespace mozilla {
 
 class JSGCThingParticipant: public nsCycleCollectionParticipant
@@ -146,17 +147,16 @@ protected:
   virtual void CustomOutOfMemoryCallback() {}
   virtual void CustomLargeAllocationFailureCallback() {}
   virtual bool CustomContextCallback(JSContext* aCx, unsigned aOperation)
   {
     return true; // Don't block context creation.
   }
 
 private:
-
   void
   DescribeGCThing(bool aIsMarked, JS::GCCellPtr aThing,
                   nsCycleCollectionTraversalCallback& aCb) const;
 
   virtual bool
   DescribeCustomObjects(JSObject* aObject, const js::Class* aClasp,
                         char (&aName)[72]) const
   {
@@ -203,16 +203,20 @@ private:
   static void OutOfMemoryCallback(JSContext* aContext, void* aData);
   static void LargeAllocationFailureCallback(void* aData);
   static bool ContextCallback(JSContext* aCx, unsigned aOperation,
                               void* aData);
 
   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,
     FinalizeNow,
   };
 
   void FinalizeDeferredThings(DeferredFinalizeType aType);
 
@@ -289,16 +293,31 @@ public:
   virtual void DispatchDeferredDeletion(bool aContinuation) = 0;
 
   JSRuntime* Runtime() const
   {
     MOZ_ASSERT(mJSRuntime);
     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();
 
   // Storage for watching rejected promises waiting for some client to
   // consume their rejection.
   // We store values as `nsISupports` to avoid adding compile-time dependencies
   // from xpcom to dom/promise, but they can really only have a single concrete
@@ -320,19 +339,31 @@ private:
 
   typedef nsDataHashtable<nsFuncPtrHashKey<DeferredFinalizeFunction>, void*>
     DeferredFinalizerTable;
   DeferredFinalizerTable mDeferredFinalizerTable;
 
   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;
 };
 
 void TraceScriptHolder(nsISupports* aHolder, JSTracer* aTracer);
 
 // Returns true if the JS::TraceKind is one the cycle collector cares about.
 inline bool AddToCCKind(JS::TraceKind aKind)
--- a/xpcom/threads/LazyIdleThread.cpp
+++ b/xpcom/threads/LazyIdleThread.cpp
@@ -521,25 +521,23 @@ NS_IMETHODIMP
 LazyIdleThread::OnDispatchedEvent(nsIThreadInternal* /*aThread */)
 {
   MOZ_ASSERT(NS_GetCurrentThread() == mOwningThread, "Wrong thread!");
   return NS_OK;
 }
 
 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;
   {
     MutexAutoLock lock(mMutex);
 
     if (aEventWasProcessed) {
       MOZ_ASSERT(mPendingEventCount, "Mismatched calls to observer methods!");
--- a/xpcom/threads/nsIThreadInternal.idl
+++ b/xpcom/threads/nsIThreadInternal.idl
@@ -8,36 +8,29 @@
 
 interface nsIRunnable;
 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
 {
   /**
    * Get/set the current thread observer (may be null).  This attribute may be
    * read from any thread, but must only be set on the thread corresponding to
    * this thread object.  The observer will be released on the thread
    * corresponding to this thread object after all other events have been
    * processed during a call to Shutdown.
    */
   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
    * on the target thread, and the implementation does not have to be
    * threadsafe. Order of callbacks is not guaranteed (i.e.
    * afterProcessNextEvent may be called first depending on whether or not the
    * observer is added in a nested loop). Holds a strong ref.
    */
   void addObserver(in nsIThreadObserver observer);
@@ -76,17 +69,17 @@ interface nsIThreadInternal : nsIThread
  * event queue.  For example, it is possible to overlay processing events
  * for a GUI toolkit on top of the events for a thread:
  *
  *   var NativeQueue;
  *   Observer = {
  *     onDispatchedEvent(thread) {
  *       NativeQueue.signal();
  *     }
- *     onProcessNextEvent(thread, mayWait, recursionDepth) {
+ *     onProcessNextEvent(thread, mayWait) {
  *       if (NativeQueue.hasNextEvent())
  *         NativeQueue.processNextEvent();
  *       while (mayWait && !thread.hasPendingEvent()) {
  *         NativeQueue.wait();
  *         NativeQueue.processNextEvent();
  *       }
  *     }
  *   };
@@ -95,17 +88,17 @@ interface nsIThreadInternal : nsIThread
  * 
  * NOTE: It is valid to change the thread's observer during a call to an
  *       observer method.
  *
  * NOTE: Will be split into two interfaces soon: one for onProcessNextEvent and
  *       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
 {
   /**
    * This method is called after an event has been dispatched to the thread.
    * This method may be called from any thread. 
    *
    * @param thread
    *   The thread where the event is being dispatched.
@@ -117,34 +110,26 @@ interface nsIThreadObserver : nsISupport
    * not guarantee that an event is actually going to be processed.  This method
    * is only called on the target thread.
    *
    * @param thread
    *   The thread being asked to process another event.
    * @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);
 };
--- a/xpcom/threads/nsThread.cpp
+++ b/xpcom/threads/nsThread.cpp
@@ -17,16 +17,17 @@
 
 #include "mozilla/ReentrantMonitor.h"
 #include "nsMemoryPressure.h"
 #include "nsThreadManager.h"
 #include "nsIClassInfoImpl.h"
 #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)
 #include "mozilla/HangMonitor.h"
 #include "mozilla/IOInterposer.h"
 #include "mozilla/ipc/MessageChannel.h"
 #include "mozilla/ipc/BackgroundChild.h"
 #endif // defined(MOZILLA_XPCOMRT_API)
@@ -89,18 +90,16 @@ GetThreadLog()
 }
 #ifdef LOG
 #undef LOG
 #endif
 #define LOG(args) MOZ_LOG(GetThreadLog(), mozilla::LogLevel::Debug, args)
 
 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.
 
 class nsThreadClassInfo : public nsIClassInfo
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED  // no mRefCnt
@@ -435,16 +434,17 @@ static bool SaveMemoryReportNearOOM()
 #endif
 
 #ifdef MOZ_CANARY
 int sCanaryOutputFD = -1;
 #endif
 
 nsThread::nsThread(MainThreadFlag aMainThread, uint32_t aStackSize)
   : mLock("nsThread.mLock")
+  , mScriptObserver(nullptr)
   , mEvents(&mEventsRoot)
   , mPriority(PRIORITY_NORMAL)
   , mThread(nullptr)
   , mNestedEventLoopDepth(0)
   , mStackSize(aStackSize)
   , mShutdownContext(nullptr)
   , mShutdownRequired(false)
   , mEventsAreDoomed(false)
@@ -819,32 +819,29 @@ nsThread::ProcessNextEvent(bool aMayWait
         nextCheck = now + TimeDuration::FromSeconds(LOW_MEMORY_SAVE_SECONDS);
       } else {
         nextCheck = now + TimeDuration::FromSeconds(LOW_MEMORY_CHECK_SECONDS);
       }
     }
   }
 #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;
 #endif
   nsresult rv = NS_OK;
 
   {
     // Scope for |event| to make sure that its destructor fires while
@@ -867,30 +864,28 @@ nsThread::ProcessNextEvent(bool aMayWait
       event->Run();
     } else if (aMayWait) {
       MOZ_ASSERT(ShuttingDown(),
                  "This should only happen when shutting down");
       rv = NS_ERROR_UNEXPECTED;
     }
   }
 
-  --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;
 }
 
 //-----------------------------------------------------------------------------
 // nsISupportsPriority
 
 NS_IMETHODIMP
 nsThread::GetPriority(int32_t* aPriority)
@@ -957,25 +952,21 @@ nsThread::SetObserver(nsIThreadObserver*
     return NS_ERROR_NOT_SAME_THREAD;
   }
 
   MutexAutoLock lock(mLock);
   mObserver = 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
 nsThread::AddObserver(nsIThreadObserver* aObserver)
 {
   if (NS_WARN_IF(!aObserver)) {
     return NS_ERROR_INVALID_ARG;
   }
@@ -1064,29 +1055,26 @@ nsThread::PopEventQueue(nsIEventTarget* 
     // Don't let the event target post any more events.
     queue->mEventTarget.swap(target);
     target->mQueue = nullptr;
   }
 
   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;
 }
 
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 nsThreadSyncDispatch::Run()
 {
   if (mSyncTask) {
--- a/xpcom/threads/nsThread.h
+++ b/xpcom/threads/nsThread.h
@@ -13,16 +13,20 @@
 #include "nsEventQueue.h"
 #include "nsThreadUtils.h"
 #include "nsString.h"
 #include "nsTObserverArray.h"
 #include "mozilla/Attributes.h"
 #include "nsAutoPtr.h"
 #include "mozilla/AlreadyAddRefed.h"
 
+namespace mozilla {
+class CycleCollectedJSRuntime;
+}
+
 // A native thread
 class nsThread
   : public nsIThreadInternal
   , public nsISupportsPriority
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIEVENTTARGET
@@ -62,22 +66,23 @@ public:
   }
 
   // Clear the observer list.
   void ClearObservers()
   {
     mEventObservers.Clear();
   }
 
-  static nsresult
-  SetMainThreadObserver(nsIThreadObserver* aObserver);
+  void
+  SetScriptObserver(mozilla::CycleCollectedJSRuntime* aScriptObserver);
+
+  uint32_t
+  RecursionDepth() const;
 
 protected:
-  static nsIThreadObserver* sMainThreadObserver;
-
   class nsChainedEventQueue;
 
   class nsNestedEventTarget;
   friend class nsNestedEventTarget;
 
   friend class nsThreadShutdownEvent;
 
   virtual ~nsThread();
@@ -170,16 +175,17 @@ protected:
   // This lock protects access to mObserver, mEvents and mEventsAreDoomed.
   // All of those fields are only modified on the thread itself (never from
   // another thread).  This means that we can avoid holding the lock while
   // using mObserver and mEvents on the thread itself.  When calling PutEvent
   // on mEvents, we have to hold the lock to synchronize with PopEventQueue.
   mozilla::Mutex mLock;
 
   nsCOMPtr<nsIThreadObserver> mObserver;
+  mozilla::CycleCollectedJSRuntime* mScriptObserver;
 
   // Only accessed on the target thread.
   nsAutoTObserverArray<nsCOMPtr<nsIThreadObserver>, 2> mEventObservers;
 
   nsChainedEventQueue* mEvents;  // never null
   nsChainedEventQueue  mEventsRoot;
 
   int32_t   mPriority;