Bug 1193394 - Part 1: Microtasks and promises scheduling. r=bevis
☠☠ backed out by a416b0a21b13 ☠ ☠
authorOlli Pettay <Olli.Pettay@helsinki.fi>
Fri, 17 Nov 2017 11:01:27 +0800
changeset 444804 55c94c05c57f0bb34ca131c68c79e05a97aa4b6c
parent 444803 2a6481c5aabbaa7a4c9cf657a20c16c4084a6b2d
child 444805 a54ef2d8f89626be6809676ff3534f077910052c
push id8
push userbugmail@asutherland.org
push dateSat, 11 Aug 2018 16:11:21 +0000
reviewersbevis
bugs1193394
milestone60.0a1
Bug 1193394 - Part 1: Microtasks and promises scheduling. r=bevis
dom/animation/Animation.cpp
dom/animation/Animation.h
dom/animation/DocumentTimeline.cpp
dom/base/CustomElementRegistry.cpp
dom/base/nsContentUtils.cpp
dom/base/nsContentUtils.h
dom/base/nsDOMMutationObserver.cpp
dom/base/nsGlobalWindowInner.cpp
dom/bindings/CallbackObject.cpp
dom/events/EventListenerManager.cpp
dom/indexedDB/ActorsChild.cpp
dom/indexedDB/IDBFileHandle.cpp
dom/indexedDB/IDBTransaction.cpp
dom/promise/Promise.cpp
dom/promise/Promise.h
dom/script/ScriptSettings.cpp
dom/script/ScriptSettings.h
dom/serviceworkers/ServiceWorkerPrivate.cpp
dom/workers/RuntimeService.cpp
dom/workers/WorkerPrivate.cpp
js/xpconnect/src/XPCJSContext.cpp
xpcom/base/CycleCollectedJSContext.cpp
xpcom/base/CycleCollectedJSContext.h
--- a/dom/animation/Animation.cpp
+++ b/dom/animation/Animation.cpp
@@ -1560,28 +1560,49 @@ Animation::GetRenderedDocument() const
 {
   if (!mEffect || !mEffect->AsKeyframeEffect()) {
     return nullptr;
   }
 
   return mEffect->AsKeyframeEffect()->GetRenderedDocument();
 }
 
+class AsyncFinishNotification : public MicroTaskRunnable
+{
+public:
+  explicit AsyncFinishNotification(Animation* aAnimation)
+  : MicroTaskRunnable()
+  , mAnimation(aAnimation)
+  {}
+
+  virtual void Run(AutoSlowOperation& aAso) override
+  {
+    mAnimation->DoFinishNotificationImmediately(this);
+    mAnimation = nullptr;
+  }
+
+  virtual bool Suppressed() override
+  {
+    nsIGlobalObject* global = mAnimation->GetOwnerGlobal();
+    return global && global->IsInSyncOperation();
+  }
+
+private:
+  RefPtr<Animation> mAnimation;
+};
+
 void
 Animation::DoFinishNotification(SyncNotifyFlag aSyncNotifyFlag)
 {
   CycleCollectedJSContext* context = CycleCollectedJSContext::Get();
 
   if (aSyncNotifyFlag == SyncNotifyFlag::Sync) {
     DoFinishNotificationImmediately();
-  } else if (!mFinishNotificationTask.IsPending()) {
-    RefPtr<nsRunnableMethod<Animation>> runnable =
-      NewRunnableMethod("dom::Animation::DoFinishNotificationImmediately",
-                        this,
-                        &Animation::DoFinishNotificationImmediately);
+  } else if (!mFinishNotificationTask) {
+    RefPtr<MicroTaskRunnable> runnable = new AsyncFinishNotification(this);
     context->DispatchToMicroTask(do_AddRef(runnable));
     mFinishNotificationTask = runnable.forget();
   }
 }
 
 void
 Animation::ResetFinishedPromise()
 {
@@ -1594,19 +1615,23 @@ Animation::MaybeResolveFinishedPromise()
 {
   if (mFinished) {
     mFinished->MaybeResolve(this);
   }
   mFinishedIsResolved = true;
 }
 
 void
-Animation::DoFinishNotificationImmediately()
+Animation::DoFinishNotificationImmediately(MicroTaskRunnable* aAsync)
 {
-  mFinishNotificationTask.Revoke();
+  if (aAsync && aAsync != mFinishNotificationTask) {
+    return;
+  }
+
+  mFinishNotificationTask = nullptr;
 
   if (PlayState() != AnimationPlayState::Finished) {
     return;
   }
 
   MaybeResolveFinishedPromise();
 
   DispatchPlaybackEvent(NS_LITERAL_STRING("finish"));
--- a/dom/animation/Animation.h
+++ b/dom/animation/Animation.h
@@ -6,16 +6,17 @@
 
 #ifndef mozilla_dom_Animation_h
 #define mozilla_dom_Animation_h
 
 #include "nsWrapperCache.h"
 #include "nsCycleCollectionParticipant.h"
 #include "mozilla/AnimationPerformanceWarning.h"
 #include "mozilla/Attributes.h"
+#include "mozilla/CycleCollectedJSContext.h"
 #include "mozilla/DOMEventTargetHelper.h"
 #include "mozilla/EffectCompositor.h" // For EffectCompositor::CascadeLevel
 #include "mozilla/LinkedList.h"
 #include "mozilla/TimeStamp.h" // for TimeStamp, TimeDuration
 #include "mozilla/dom/AnimationBinding.h" // for AnimationPlayState
 #include "mozilla/dom/AnimationEffectReadOnly.h"
 #include "mozilla/dom/AnimationTimeline.h"
 #include "mozilla/dom/Promise.h"
@@ -39,16 +40,17 @@ class nsIDocument;
 class nsIFrame;
 
 namespace mozilla {
 
 struct AnimationRule;
 
 namespace dom {
 
+class AsyncFinishNotification;
 class CSSAnimation;
 class CSSTransition;
 
 class Animation
   : public DOMEventTargetHelper
   , public LinkedListElement<Animation>
 {
 protected:
@@ -444,17 +446,18 @@ protected:
   void UpdateFinishedState(SeekFlag aSeekFlag,
                            SyncNotifyFlag aSyncNotifyFlag);
   void UpdateEffect();
   void FlushStyle() const;
   void PostUpdate();
   void ResetFinishedPromise();
   void MaybeResolveFinishedPromise();
   void DoFinishNotification(SyncNotifyFlag aSyncNotifyFlag);
-  void DoFinishNotificationImmediately();
+  friend class AsyncFinishNotification;
+  void DoFinishNotificationImmediately(MicroTaskRunnable* aAsync = nullptr);
   void DispatchPlaybackEvent(const nsAString& aName);
 
   /**
    * Remove this animation from the pending animation tracker and reset
    * mPendingState as necessary. The caller is responsible for resolving or
    * aborting the mReady promise as necessary.
    */
   void CancelPendingTasks();
@@ -537,17 +540,17 @@ protected:
   // possible for two different objects to have the same index.
   uint64_t mAnimationIndex;
 
   bool mFinishedAtLastComposeStyle;
   // Indicates that the animation should be exposed in an element's
   // getAnimations() list.
   bool mIsRelevant;
 
-  nsRevocableEventPtr<nsRunnableMethod<Animation>> mFinishNotificationTask;
+  RefPtr<MicroTaskRunnable> mFinishNotificationTask;
   // True if mFinished is resolved or would be resolved if mFinished has
   // yet to be created. This is not set when mFinished is rejected since
   // in that case mFinished is immediately reset to represent a new current
   // finished promise.
   bool mFinishedIsResolved;
 
   // True if this animation was triggered at the same time as one or more
   // geometric animations and hence we should run any transform animations on
--- a/dom/animation/DocumentTimeline.cpp
+++ b/dom/animation/DocumentTimeline.cpp
@@ -2,17 +2,16 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "DocumentTimeline.h"
 #include "mozilla/ScopeExit.h"
 #include "mozilla/dom/DocumentTimelineBinding.h"
-#include "mozilla/dom/Promise.h"
 #include "AnimationUtils.h"
 #include "nsContentUtils.h"
 #include "nsDOMMutationObserver.h"
 #include "nsDOMNavigationTiming.h"
 #include "nsIPresShell.h"
 #include "nsPresContext.h"
 #include "nsRefreshDriver.h"
 
@@ -155,24 +154,21 @@ DocumentTimeline::WillRefresh(mozilla::T
   MOZ_ASSERT(GetRefreshDriver(),
              "Should be able to reach refresh driver from within WillRefresh");
 
   bool needsTicks = false;
   nsTArray<Animation*> animationsToRemove(mAnimations.Count());
 
   // https://drafts.csswg.org/web-animations-1/#update-animations-and-send-events,
   // step2.
-  // FIXME: This needs to be replaced with nsAutoMicroTask.
   // Note that this should be done before nsAutoAnimationMutationBatch.  If
   // PerformMicroTaskCheckpoint was called before nsAutoAnimationMutationBatch
   // is destroyed, some mutation records might not be delivered in this
   // checkpoint.
-  auto autoPerformMicrotaskCheckpoint = MakeScopeExit([] {
-    Promise::PerformMicroTaskCheckpoint();
-  });
+  nsAutoMicroTask mt;
   nsAutoAnimationMutationBatch mb(mDocument);
 
   for (Animation* animation = mAnimationOrder.getFirst(); animation;
        animation = animation->getNext()) {
     // Skip any animations that are longer need associated with this timeline.
     if (animation->GetTimeline() != this) {
       // If animation has some other timeline, it better not be also in the
       // animation list of this timeline object!
--- a/dom/base/CustomElementRegistry.cpp
+++ b/dom/base/CustomElementRegistry.cpp
@@ -1062,17 +1062,17 @@ CustomElementReactionsStack::Enqueue(Ele
   elementData->mReactionQueue.AppendElement(aReaction);
 
   if (mIsBackupQueueProcessing) {
     return;
   }
 
   CycleCollectedJSContext* context = CycleCollectedJSContext::Get();
   RefPtr<BackupQueueMicroTask> bqmt = new BackupQueueMicroTask(this);
-  context->DispatchMicroTaskRunnable(bqmt.forget());
+  context->DispatchToMicroTask(bqmt.forget());
 }
 
 void
 CustomElementReactionsStack::InvokeBackupQueue()
 {
   // Check backup queue size in order to reduce function call overhead.
   if (!mBackupQueue.IsEmpty()) {
     // Upgrade reactions won't be scheduled in backup queue and the exception of
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -5866,20 +5866,20 @@ void
 nsContentUtils::RunInStableState(already_AddRefed<nsIRunnable> aRunnable)
 {
   MOZ_ASSERT(CycleCollectedJSContext::Get(), "Must be on a script thread!");
   CycleCollectedJSContext::Get()->RunInStableState(Move(aRunnable));
 }
 
 /* static */
 void
-nsContentUtils::RunInMetastableState(already_AddRefed<nsIRunnable> aRunnable)
+nsContentUtils::AddPendingIDBTransaction(already_AddRefed<nsIRunnable> aTransaction)
 {
   MOZ_ASSERT(CycleCollectedJSContext::Get(), "Must be on a script thread!");
-  CycleCollectedJSContext::Get()->RunInMetastableState(Move(aRunnable));
+  CycleCollectedJSContext::Get()->AddPendingIDBTransaction(Move(aTransaction));
 }
 
 /* static */
 bool
 nsContentUtils::IsInStableOrMetaStableState()
 {
   MOZ_ASSERT(CycleCollectedJSContext::Get(), "Must be on a script thread!");
   return CycleCollectedJSContext::Get()->IsInStableOrMetaStableState();
@@ -6928,19 +6928,26 @@ nsContentUtils::IsSubDocumentTabbable(ns
   if (!contentViewer) {
     return false;
   }
 
   nsCOMPtr<nsIContentViewer> zombieViewer;
   contentViewer->GetPreviousViewer(getter_AddRefs(zombieViewer));
 
   // If there are 2 viewers for the current docshell, that
-  // means the current document is a zombie document.
-  // Only navigate into the subdocument if it's not a zombie.
-  return !zombieViewer;
+  // means the current document may be a zombie document.
+  // While load and pageshow events are dispatched, zombie viewer is the old,
+  // to be hidden document.
+  if (zombieViewer) {
+    bool inOnLoad = false;
+    docShell->GetIsExecutingOnLoadHandler(&inOnLoad);
+    return inOnLoad;
+  }
+
+  return true;
 }
 
 bool
 nsContentUtils::IsUserFocusIgnored(nsINode* aNode)
 {
   if (!nsGenericHTMLFrameElement::BrowserFramesEnabled()) {
     return false;
   }
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -2026,27 +2026,22 @@ public:
    * 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);
 
-  /* 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
+  /* Add a pending IDBTransaction to be cleaned up at the end of performing a
+   * microtask checkpoint.
+   * See the step of "Cleanup Indexed Database Transactions" in
+   * https://html.spec.whatwg.org/multipage/webappapis.html#perform-a-microtask-checkpoint
    */
-  static void RunInMetastableState(already_AddRefed<nsIRunnable> aRunnable);
+  static void AddPendingIDBTransaction(already_AddRefed<nsIRunnable> aTransaction);
 
   /**
    * Returns true if we are doing StableState/MetastableState.
    */
   static bool IsInStableOrMetaStableState();
 
   /* Process viewport META data. This gives us information for the scale
    * and zoom of a page on mobile devices. We stick the information in
--- a/dom/base/nsDOMMutationObserver.cpp
+++ b/dom/base/nsDOMMutationObserver.cpp
@@ -615,17 +615,17 @@ nsDOMMutationObserver::QueueMutationObse
 {
   CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
   if (!ccjs) {
     return;
   }
 
   RefPtr<MutationObserverMicroTask> momt =
     new MutationObserverMicroTask();
-  ccjs->DispatchMicroTaskRunnable(momt.forget());
+  ccjs->DispatchToMicroTask(momt.forget());
 }
 
 void
 nsDOMMutationObserver::HandleMutations(mozilla::AutoSlowOperation& aAso)
 {
   if (sScheduledMutationObservers ||
       mozilla::dom::DocGroup::sPendingDocGroups) {
     HandleMutationsInternal(aAso);
@@ -638,17 +638,17 @@ nsDOMMutationObserver::RescheduleForRun(
   if (!sScheduledMutationObservers) {
     CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
     if (!ccjs) {
       return;
     }
 
     RefPtr<MutationObserverMicroTask> momt =
       new MutationObserverMicroTask();
-    ccjs->DispatchMicroTaskRunnable(momt.forget());
+    ccjs->DispatchToMicroTask(momt.forget());
     sScheduledMutationObservers = new AutoTArray<RefPtr<nsDOMMutationObserver>, 4>;
   }
 
   bool didInsert = false;
   for (uint32_t i = 0; i < sScheduledMutationObservers->Length(); ++i) {
     if (static_cast<nsDOMMutationObserver*>((*sScheduledMutationObservers)[i])
           ->mId > mId) {
       sScheduledMutationObservers->InsertElementAt(i, this);
--- a/dom/base/nsGlobalWindowInner.cpp
+++ b/dom/base/nsGlobalWindowInner.cpp
@@ -6709,22 +6709,16 @@ nsGlobalWindowInner::RunTimeoutHandler(T
   // Call() on a Function here since we're in a loop
   // where we're likely to be running timeouts whose OS timers
   // didn't fire in time and we don't want to not fire those timers
   // now just because execution of one timer failed. We can't
   // propagate the error to anyone who cares about it from this
   // point anyway, and the script context should have already reported
   // the script error in the usual way - so we just drop it.
 
-  // Since we might be processing more timeouts, go ahead and flush the promise
-  // queue now before we do that.  We need to do that while we're still in our
-  // "running JS is safe" state (e.g. mRunningTimeout is set, timeout->mRunning
-  // is false).
-  Promise::PerformMicroTaskCheckpoint();
-
   if (trackNestingLevel) {
     TimeoutManager::SetNestingLevel(nestingLevel);
   }
 
   mTimeoutManager->EndRunningTimeout(last_running_timeout);
   timeout->mRunning = false;
 
   return timeout->mCleared;
--- a/dom/bindings/CallbackObject.cpp
+++ b/dom/bindings/CallbackObject.cpp
@@ -135,21 +135,19 @@ CallbackObject::CallSetup::CallSetup(Cal
                                      JSCompartment* aCompartment,
                                      bool aIsJSImplementedWebIDL)
   : mCx(nullptr)
   , mCompartment(aCompartment)
   , mErrorResult(aRv)
   , mExceptionHandling(aExceptionHandling)
   , mIsMainThread(NS_IsMainThread())
 {
-  if (mIsMainThread) {
-    CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
-    if (ccjs) {
-      ccjs->EnterMicroTask();
-    }
+  CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
+  if (ccjs) {
+    ccjs->EnterMicroTask();
   }
 
   // Compute the caller's subject principal (if necessary) early, before we
   // do anything that might perturb the relevant state.
   nsIPrincipal* webIDLCallerPrincipal = nullptr;
   if (aIsJSImplementedWebIDL) {
     webIDLCallerPrincipal = nsContentUtils::SubjectPrincipalOrSystemIfNativeCaller();
   }
@@ -346,21 +344,19 @@ CallbackObject::CallSetup::~CallSetup()
     }
   }
 
   mAutoIncumbentScript.reset();
   mAutoEntryScript.reset();
 
   // It is important that this is the last thing we do, after leaving the
   // compartment and undoing all our entry/incumbent script changes
-  if (mIsMainThread) {
-    CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
-    if (ccjs) {
-      ccjs->LeaveMicroTask();
-    }
+  CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
+  if (ccjs) {
+    ccjs->LeaveMicroTask();
   }
 }
 
 already_AddRefed<nsISupports>
 CallbackObjectHolderBase::ToXPCOMCallback(CallbackObject* aCallback,
                                           const nsIID& aIID) const
 {
   MOZ_ASSERT(NS_IsMainThread());
--- a/dom/events/EventListenerManager.cpp
+++ b/dom/events/EventListenerManager.cpp
@@ -1090,37 +1090,27 @@ EventListenerManager::HandleEventSubType
   // compiled the event handler itself
   if ((aListener->mListenerType == Listener::eJSEventListener) &&
       aListener->mHandlerIsString) {
     result = CompileEventHandlerInternal(aListener, nullptr, nullptr);
     aListener = nullptr;
   }
 
   if (NS_SUCCEEDED(result)) {
-    if (mIsMainThreadELM) {
-      CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
-      if (ccjs) {
-        ccjs->EnterMicroTask();
-      }
-    }
+    nsAutoMicroTask mt;
+
     // nsIDOMEvent::currentTarget is set in EventDispatcher.
     if (listenerHolder.HasWebIDLCallback()) {
       ErrorResult rv;
       listenerHolder.GetWebIDLCallback()->
         HandleEvent(aCurrentTarget, *(aDOMEvent->InternalDOMEvent()), rv);
       result = rv.StealNSResult();
     } else {
       result = listenerHolder.GetXPCOMCallback()->HandleEvent(aDOMEvent);
     }
-    if (mIsMainThreadELM) {
-      CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
-      if (ccjs) {
-        ccjs->LeaveMicroTask();
-      }
-    }
   }
 
   return result;
 }
 
 EventMessage
 EventListenerManager::GetLegacyEventMessage(EventMessage aEventMessage) const
 {
--- a/dom/indexedDB/ActorsChild.cpp
+++ b/dom/indexedDB/ActorsChild.cpp
@@ -844,32 +844,36 @@ DispatchSuccessEvent(ResultHelper* aResu
   } else {
     IDB_LOG_MARK("IndexedDB %s: Child  Request[%llu]: Firing %s event",
                  "IndexedDB %s: C R[%llu]: %s",
                  IDB_LOG_ID_STRING(),
                  request->LoggingSerialNumber(),
                  IDB_LOG_STRINGIFY(aEvent, kSuccessEventType));
   }
 
+  MOZ_ASSERT_IF(transaction,
+                transaction->IsOpen() && !transaction->IsAborted());
+
   bool dummy;
   nsresult rv = request->DispatchEvent(aEvent, &dummy);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return;
   }
 
-  MOZ_ASSERT_IF(transaction,
-                transaction->IsOpen() || transaction->IsAborted());
-
   WidgetEvent* internalEvent = aEvent->WidgetEventPtr();
   MOZ_ASSERT(internalEvent);
 
   if (transaction &&
-      transaction->IsOpen() &&
-      internalEvent->mFlags.mExceptionWasRaised) {
-    transaction->Abort(NS_ERROR_DOM_INDEXEDDB_ABORT_ERR);
+      transaction->IsOpen()) {
+    if (internalEvent->mFlags.mExceptionWasRaised) {
+      transaction->Abort(NS_ERROR_DOM_INDEXEDDB_ABORT_ERR);
+    } else {
+      // To handle upgrade transaction.
+      transaction->Run();
+    }
   }
 }
 
 PRFileDesc*
 GetFileDescriptorFromStream(nsIInputStream* aStream)
 {
   MOZ_ASSERT(aStream);
 
--- a/dom/indexedDB/IDBFileHandle.cpp
+++ b/dom/indexedDB/IDBFileHandle.cpp
@@ -94,17 +94,17 @@ IDBFileHandle::Create(IDBMutableFile* aM
     new IDBFileHandle(aMutableFile, aMode);
 
   fileHandle->BindToOwner(aMutableFile);
 
   // XXX Fix!
   MOZ_ASSERT(NS_IsMainThread(), "This won't work on non-main threads!");
 
   nsCOMPtr<nsIRunnable> runnable = do_QueryObject(fileHandle);
-  nsContentUtils::RunInMetastableState(runnable.forget());
+  nsContentUtils::AddPendingIDBTransaction(runnable.forget());
 
   fileHandle->mCreating = true;
 
   aMutableFile->RegisterFileHandle(fileHandle);
 
   return fileHandle.forget();
 }
 
--- a/dom/indexedDB/IDBTransaction.cpp
+++ b/dom/indexedDB/IDBTransaction.cpp
@@ -187,25 +187,21 @@ IDBTransaction::CreateVersionChange(
     new IDBTransaction(aDatabase,
                        emptyObjectStoreNames,
                        VERSION_CHANGE);
   aOpenRequest->GetCallerLocation(transaction->mFilename,
                                   &transaction->mLineNo, &transaction->mColumn);
 
   transaction->SetScriptOwner(aDatabase->GetScriptOwner());
 
-  nsCOMPtr<nsIRunnable> runnable = do_QueryObject(transaction);
-  nsContentUtils::RunInMetastableState(runnable.forget());
-
   transaction->NoteActiveTransaction();
 
   transaction->mBackgroundActor.mVersionChangeBackgroundActor = aActor;
   transaction->mNextObjectStoreId = aNextObjectStoreId;
   transaction->mNextIndexId = aNextIndexId;
-  transaction->mCreating = true;
 
   aDatabase->RegisterTransaction(transaction);
   transaction->mRegistered = true;
 
   return transaction.forget();
 }
 
 // static
@@ -245,17 +241,17 @@ IDBTransaction::Create(JSContext* aCx, I
 #endif
       return nullptr;
     }
 
     transaction->mWorkerHolder = Move(workerHolder);
   }
 
   nsCOMPtr<nsIRunnable> runnable = do_QueryObject(transaction);
-  nsContentUtils::RunInMetastableState(runnable.forget());
+  nsContentUtils::AddPendingIDBTransaction(runnable.forget());
 
   transaction->mCreating = true;
 
   aDatabase->RegisterTransaction(transaction);
   transaction->mRegistered = true;
 
   return transaction.forget();
 }
--- a/dom/promise/Promise.cpp
+++ b/dom/promise/Promise.cpp
@@ -505,136 +505,16 @@ Promise::ReportRejectedPromise(JSContext
   RefPtr<nsIRunnable> event = new AsyncErrorReporter(xpcReport);
   if (win) {
     win->Dispatch(mozilla::TaskCategory::Other, event.forget());
   } else {
     NS_DispatchToMainThread(event);
   }
 }
 
-bool
-Promise::PerformMicroTaskCheckpoint()
-{
-  MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
-
-  CycleCollectedJSContext* context = CycleCollectedJSContext::Get();
-
-  // On the main thread, we always use the main promise micro task queue.
-  std::queue<nsCOMPtr<nsIRunnable>>& microtaskQueue =
-    context->GetPromiseMicroTaskQueue();
-
-  if (microtaskQueue.empty()) {
-    return false;
-  }
-
-  AutoSlowOperation aso;
-
-  do {
-    nsCOMPtr<nsIRunnable> runnable = microtaskQueue.front().forget();
-    MOZ_ASSERT(runnable);
-
-    // This function can re-enter, so we remove the element before calling.
-    microtaskQueue.pop();
-    nsresult rv = runnable->Run();
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return false;
-    }
-    aso.CheckForInterrupt();
-    context->AfterProcessMicrotask();
-  } while (!microtaskQueue.empty());
-
-  return true;
-}
-
-bool
-Promise::IsWorkerDebuggerMicroTaskEmpty()
-{
-  MOZ_ASSERT(!NS_IsMainThread(), "Wrong thread!");
-
-  CycleCollectedJSContext* context = CycleCollectedJSContext::Get();
-  if (!context) {
-    return true;
-  }
-
-  std::queue<nsCOMPtr<nsIRunnable>>* microtaskQueue =
-    &context->GetDebuggerPromiseMicroTaskQueue();
-
-  return microtaskQueue->empty();
-}
-
-void
-Promise::PerformWorkerMicroTaskCheckpoint()
-{
-  MOZ_ASSERT(!NS_IsMainThread(), "Wrong thread!");
-
-  CycleCollectedJSContext* context = CycleCollectedJSContext::Get();
-  if (!context) {
-    return;
-  }
-
-  for (;;) {
-    // For a normal microtask checkpoint, we try to use the debugger microtask
-    // queue first. If the debugger queue is empty, we use the normal microtask
-    // queue instead.
-    std::queue<nsCOMPtr<nsIRunnable>>* microtaskQueue =
-      &context->GetDebuggerPromiseMicroTaskQueue();
-
-    if (microtaskQueue->empty()) {
-      microtaskQueue = &context->GetPromiseMicroTaskQueue();
-      if (microtaskQueue->empty()) {
-        break;
-      }
-    }
-
-    nsCOMPtr<nsIRunnable> runnable = microtaskQueue->front().forget();
-    MOZ_ASSERT(runnable);
-
-    // This function can re-enter, so we remove the element before calling.
-    microtaskQueue->pop();
-    nsresult rv = runnable->Run();
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return;
-    }
-    context->AfterProcessMicrotask();
-  }
-}
-
-void
-Promise::PerformWorkerDebuggerMicroTaskCheckpoint()
-{
-  MOZ_ASSERT(!NS_IsMainThread(), "Wrong thread!");
-
-  CycleCollectedJSContext* context = CycleCollectedJSContext::Get();
-  if (!context) {
-    return;
-  }
-
-  for (;;) {
-    // For a debugger microtask checkpoint, we always use the debugger microtask
-    // queue.
-    std::queue<nsCOMPtr<nsIRunnable>>* microtaskQueue =
-      &context->GetDebuggerPromiseMicroTaskQueue();
-
-    if (microtaskQueue->empty()) {
-      break;
-    }
-
-    nsCOMPtr<nsIRunnable> runnable = microtaskQueue->front().forget();
-    MOZ_ASSERT(runnable);
-
-    // This function can re-enter, so we remove the element before calling.
-    microtaskQueue->pop();
-    nsresult rv = runnable->Run();
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return;
-    }
-    context->AfterProcessMicrotask();
-  }
-}
-
 JSObject*
 Promise::GlobalJSObject() const
 {
   return mGlobal->GetGlobalJSObject();
 }
 
 JSCompartment*
 Promise::Compartment() const
--- a/dom/promise/Promise.h
+++ b/dom/promise/Promise.h
@@ -99,25 +99,16 @@ public:
   // every translation unit that includes this header, because that would
   // require use to include DOMException.h either here or in all those
   // translation units.
   template<typename T>
   void MaybeRejectBrokenly(const T& aArg); // Not implemented by default; see
                                            // specializations in the .cpp for
                                            // the T values we support.
 
-  // Called by DOM to let us execute our callbacks.  May be called recursively.
-  // Returns true if at least one microtask was processed.
-  static bool PerformMicroTaskCheckpoint();
-
-  static void PerformWorkerMicroTaskCheckpoint();
-
-  static void PerformWorkerDebuggerMicroTaskCheckpoint();
-  static bool IsWorkerDebuggerMicroTaskEmpty();
-
   // WebIDL
 
   nsIGlobalObject* GetParentObject() const
   {
     return mGlobal;
   }
 
   // Do the equivalent of Promise.resolve in the current compartment of aCx.
--- a/dom/script/ScriptSettings.cpp
+++ b/dom/script/ScriptSettings.cpp
@@ -825,24 +825,25 @@ AutoSafeJSContext::AutoSafeJSContext(MOZ
              "This is quite odd.  We should have crashed in the "
              "xpc::NativeGlobal() call if xpc::UnprivilegedJunkScope() "
              "returned null, and inited correctly otherwise!");
 }
 
 AutoSlowOperation::AutoSlowOperation(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMPL)
   : AutoJSAPI()
 {
-  MOZ_ASSERT(NS_IsMainThread());
-
   MOZ_GUARD_OBJECT_NOTIFIER_INIT;
 
   Init();
 }
 
 void
 AutoSlowOperation::CheckForInterrupt()
 {
-  // JS_CheckForInterrupt expects us to be in a compartment.
-  JSAutoCompartment ac(cx(), xpc::UnprivilegedJunkScope());
-  JS_CheckForInterrupt(cx());
+  // For now we support only main thread!
+  if (mIsMainThread) {
+    // JS_CheckForInterrupt expects us to be in a compartment.
+    JSAutoCompartment ac(cx(), xpc::UnprivilegedJunkScope());
+    JS_CheckForInterrupt(cx());
+  }
 }
 
 } // namespace mozilla
--- a/dom/script/ScriptSettings.h
+++ b/dom/script/ScriptSettings.h
@@ -288,25 +288,25 @@ public:
     JS_ClearPendingException(cx());
   }
 
 protected:
   // Protected constructor for subclasses.  This constructor initialises the
   // AutoJSAPI, so Init must NOT be called on subclasses that use this.
   AutoJSAPI(nsIGlobalObject* aGlobalObject, bool aIsMainThread, Type aType);
 
-private:
   mozilla::Maybe<JSAutoRequest> mAutoRequest;
   mozilla::Maybe<JSAutoNullableCompartment> mAutoNullableCompartment;
   JSContext *mCx;
 
   // Whether we're mainthread or not; set when we're initialized.
   bool mIsMainThread;
   Maybe<JS::WarningReporter> mOldWarningReporter;
 
+private:
   void InitInternal(nsIGlobalObject* aGlobalObject, JSObject* aGlobal,
                     JSContext* aCx, bool aIsMainThread);
 
   AutoJSAPI(const AutoJSAPI&) = delete;
   AutoJSAPI& operator= (const AutoJSAPI&) = delete;
 };
 
 /*
--- a/dom/serviceworkers/ServiceWorkerPrivate.cpp
+++ b/dom/serviceworkers/ServiceWorkerPrivate.cpp
@@ -328,17 +328,17 @@ public:
   }
 
   void
   MaybeDone()
   {
     MOZ_ASSERT(mWorkerPrivate);
     mWorkerPrivate->AssertIsOnWorkerThread();
 
-    if (mPendingPromisesCount) {
+    if (mPendingPromisesCount || !mKeepAliveToken) {
       return;
     }
     if (mCallback) {
       mCallback->FinishedWithResult(mRejected ? Rejected : Resolved);
     }
 
     MaybeCleanup();
   }
@@ -360,16 +360,28 @@ private:
     if (mWorkerHolderAdded) {
       ReleaseWorker();
     }
 
     mKeepAliveToken = nullptr;
     mSelfRef = nullptr;
   }
 
+  class MaybeDoneRunner : public MicroTaskRunnable
+  {
+  public:
+    explicit MaybeDoneRunner(KeepAliveHandler* aHandler) : mHandler(aHandler) {}
+    virtual void Run(AutoSlowOperation& aAso) override
+    {
+      mHandler->MaybeDone();
+    }
+
+    RefPtr<KeepAliveHandler> mHandler;
+  };
+
   void
   RemovePromise(ExtendableEventResult aResult)
   {
     MOZ_ASSERT(mWorkerPrivate);
     mWorkerPrivate->AssertIsOnWorkerThread();
     MOZ_DIAGNOSTIC_ASSERT(mPendingPromisesCount > 0);
 
     // Note: mSelfRef and mKeepAliveToken can be nullptr here
@@ -383,20 +395,17 @@ private:
     --mPendingPromisesCount;
     if (mPendingPromisesCount) {
       return;
     }
 
     CycleCollectedJSContext* cx = CycleCollectedJSContext::Get();
     MOZ_ASSERT(cx);
 
-    RefPtr<nsIRunnable> r =
-      NewRunnableMethod("dom::KeepAliveHandler::MaybeDone",
-                        this,
-                        &KeepAliveHandler::MaybeDone);
+    RefPtr<MaybeDoneRunner> r = new MaybeDoneRunner(this);
     cx->DispatchToMicroTask(r.forget());
   }
 };
 
 NS_IMPL_ISUPPORTS0(KeepAliveHandler)
 
 class RegistrationUpdateRunnable : public Runnable
 {
--- a/dom/workers/RuntimeService.cpp
+++ b/dom/workers/RuntimeService.cpp
@@ -1047,16 +1047,20 @@ class MOZ_STACK_CLASS WorkerJSContext fi
 public:
   // The heap size passed here doesn't matter, we will change it later in the
   // call to JS_SetGCParameter inside InitJSContextForWorker.
   explicit WorkerJSContext(WorkerPrivate* aWorkerPrivate)
     : mWorkerPrivate(aWorkerPrivate)
   {
     MOZ_COUNT_CTOR_INHERITED(WorkerJSContext, CycleCollectedJSContext);
     MOZ_ASSERT(aWorkerPrivate);
+    // Magical number 2. Workers have the base recursion depth 1, and normal
+    // runnables run at level 2, and we don't want to process microtasks
+    // at any other level.
+    SetTargetedMicroTaskRecursionDepth(2);
   }
 
   ~WorkerJSContext()
   {
     MOZ_COUNT_DTOR_INHERITED(WorkerJSContext, CycleCollectedJSContext);
     JSContext* cx = MaybeContext();
     if (!cx) {
       return;   // Initialize() must have failed
@@ -1100,54 +1104,42 @@ public:
     JS_SetWrapObjectCallbacks(cx, &WrapObjectCallbacks);
     if (mWorkerPrivate->IsDedicatedWorker()) {
       JS_SetFutexCanWait(cx);
     }
 
     return NS_OK;
   }
 
-  virtual void AfterProcessTask(uint32_t aRecursionDepth) override
+  virtual void DispatchToMicroTask(already_AddRefed<MicroTaskRunnable> aRunnable) 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) {
-      CycleCollectedJSContext::AfterProcessTask(aRecursionDepth);
-    } else if (aRecursionDepth > 2) {
-      AutoDisableMicroTaskCheckpoint disableMicroTaskCheckpoint;
-      CycleCollectedJSContext::AfterProcessTask(aRecursionDepth);
-    }
-  }
-
-  virtual void DispatchToMicroTask(already_AddRefed<nsIRunnable> aRunnable) override
-  {
-    RefPtr<nsIRunnable> runnable(aRunnable);
+    RefPtr<MicroTaskRunnable> runnable(aRunnable);
 
     MOZ_ASSERT(!NS_IsMainThread());
     MOZ_ASSERT(runnable);
 
-    std::queue<nsCOMPtr<nsIRunnable>>* microTaskQueue = nullptr;
+    std::queue<RefPtr<MicroTaskRunnable>>* microTaskQueue = nullptr;
 
     JSContext* cx = GetCurrentWorkerThreadJSContext();
     NS_ASSERTION(cx, "This should never be null!");
 
     JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx));
     NS_ASSERTION(global, "This should never be null!");
 
     // On worker threads, if the current global is the worker global, we use the
-    // main promise micro task queue. Otherwise, the current global must be
+    // main micro task queue. Otherwise, the current global must be
     // either the debugger global or a debugger sandbox, and we use the debugger
-    // promise micro task queue instead.
+    // micro task queue instead.
     if (IsWorkerGlobal(global)) {
-      microTaskQueue = &mPromiseMicroTaskQueue;
+      microTaskQueue = &GetMicroTaskQueue();
     } else {
       MOZ_ASSERT(IsWorkerDebuggerGlobal(global) ||
                  IsWorkerDebuggerSandbox(global));
 
-      microTaskQueue = &mDebuggerPromiseMicroTaskQueue;
+      microTaskQueue = &GetDebuggerMicroTaskQueue();
     }
 
     microTaskQueue->push(runnable.forget());
   }
 
 private:
   WorkerPrivate* mWorkerPrivate;
 };
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -3254,18 +3254,20 @@ WorkerPrivate::DoRunLoop(JSContext* aCx)
         mDebuggerQueue.Pop(runnable);
         debuggerRunnablesPending = !mDebuggerQueue.IsEmpty();
       }
 
       MOZ_ASSERT(runnable);
       static_cast<nsIRunnable*>(runnable)->Run();
       runnable->Release();
 
-      // Flush the promise queue.
-      Promise::PerformWorkerDebuggerMicroTaskCheckpoint();
+      CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
+      if (ccjs) {
+        ccjs->PerformDebuggerMicroTaskCheckpoint();
+      }
 
       if (debuggerRunnablesPending) {
         WorkerDebuggerGlobalScope* globalScope = DebuggerGlobalScope();
         MOZ_ASSERT(globalScope);
 
         // Now *might* be a good time to GC. Let the JS engine make the decision.
         JSAutoCompartment ac(aCx, globalScope->GetGlobalJSObject());
         JS_MaybeGC(aCx);
@@ -4339,28 +4341,32 @@ WorkerPrivate::EnterDebuggerEventLoop()
     if (!debuggerRunnablesPending) {
       SetGCTimerMode(IdleTimer);
     }
 
     // Wait for something to do
     {
       MutexAutoLock lock(mMutex);
 
+      CycleCollectedJSContext* context = CycleCollectedJSContext::Get();
+      std::queue<RefPtr<MicroTaskRunnable>>& debuggerMtQueue =
+        context->GetDebuggerMicroTaskQueue();
       while (mControlQueue.IsEmpty() &&
              !(debuggerRunnablesPending = !mDebuggerQueue.IsEmpty()) &&
-             Promise::IsWorkerDebuggerMicroTaskEmpty()) {
+             debuggerMtQueue.empty()) {
         WaitForWorkerEvents();
       }
 
       ProcessAllControlRunnablesLocked();
 
       // XXXkhuey should we abort JS on the stack here if we got Abort above?
     }
-    if (!Promise::IsWorkerDebuggerMicroTaskEmpty()) {
-      Promise::PerformWorkerDebuggerMicroTaskCheckpoint();
+    CycleCollectedJSContext* context = CycleCollectedJSContext::Get();
+    if (context) {
+      context->PerformDebuggerMicroTaskCheckpoint();
     }
     if (debuggerRunnablesPending) {
       // Start the periodic GC timer if it is not already running.
       SetGCTimerMode(PeriodicTimer);
 
       WorkerRunnable* runnable = nullptr;
 
       {
@@ -4368,18 +4374,20 @@ WorkerPrivate::EnterDebuggerEventLoop()
 
         mDebuggerQueue.Pop(runnable);
       }
 
       MOZ_ASSERT(runnable);
       static_cast<nsIRunnable*>(runnable)->Run();
       runnable->Release();
 
-      // Flush the promise queue.
-      Promise::PerformWorkerDebuggerMicroTaskCheckpoint();
+      CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
+      if (ccjs) {
+        ccjs->PerformDebuggerMicroTaskCheckpoint();
+      }
 
       // Now *might* be a good time to GC. Let the JS engine make the decision.
       if (JS::CurrentGlobalOrNull(cx)) {
         JS_MaybeGC(cx);
       }
     }
   }
 }
@@ -4734,18 +4742,18 @@ WorkerPrivate::RunExpiredTimeouts(JSCont
     if (info->mIsInterval) {
       reason = "setInterval handler";
     } else {
       reason = "setTimeout handler";
     }
 
     RefPtr<Function> callback = info->mHandler->GetCallback();
     if (!callback) {
-      // scope for the AutoEntryScript, so it comes off the stack before we do
-      // Promise::PerformMicroTaskCheckpoint.
+      nsAutoMicroTask mt;
+
       AutoEntryScript aes(global, reason, false);
 
       // Evaluate the timeout expression.
       const nsAString& script = info->mHandler->GetHandlerText();
 
       const char* filename = nullptr;
       uint32_t lineNo = 0, dummyColumn = 0;
       info->mHandler->GetLocation(&filename, &lineNo, &dummyColumn);
@@ -4770,20 +4778,16 @@ WorkerPrivate::RunExpiredTimeouts(JSCont
         rv.SuppressException();
         retval = false;
         break;
       }
 
       rv.SuppressException();
     }
 
-    // Since we might be processing more timeouts, go ahead and flush
-    // the promise queue now before we do that.
-    Promise::PerformWorkerMicroTaskCheckpoint();
-
     NS_ASSERTION(mRunningExpiredTimeouts, "Someone changed this!");
   }
 
   // No longer possible to be called recursively.
   mRunningExpiredTimeouts = false;
 
   // Now remove canceled and expired timeouts from the main list.
   // NB: The timeouts present in expiredTimeouts must have the same order
--- a/js/xpconnect/src/XPCJSContext.cpp
+++ b/js/xpconnect/src/XPCJSContext.cpp
@@ -1214,31 +1214,16 @@ XPCJSContext::NewXPCJSContext(XPCJSConte
     MOZ_CRASH("new XPCJSContext failed to initialize.");
 }
 
 void
 XPCJSContext::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.
-
-            NS_DispatchToMainThread(new Runnable("Empty_microtask_runnable"));
-        }
-    }
-
     // Start the slow script timer.
     mSlowScriptCheckpoint = mozilla::TimeStamp::NowLoRes();
     mSlowScriptSecondHalf = false;
     mSlowScriptActualWait = mozilla::TimeDuration();
     mTimeoutAccumulated = false;
 
     // As we may be entering a nested event loop, we need to
     // cancel any ongoing performance measurement.
--- a/xpcom/base/CycleCollectedJSContext.cpp
+++ b/xpcom/base/CycleCollectedJSContext.cpp
@@ -47,17 +47,17 @@ using namespace mozilla::dom;
 
 namespace mozilla {
 
 CycleCollectedJSContext::CycleCollectedJSContext()
   : mIsPrimaryContext(true)
   , mRuntime(nullptr)
   , mJSContext(nullptr)
   , mDoingStableStates(false)
-  , mDisableMicroTaskCheckpoint(false)
+  , mTargetedMicroTaskRecursionDepth(0)
   , mMicroTaskLevel(0)
   , mMicroTaskRecursionDepth(0)
 {
   MOZ_COUNT_CTOR(CycleCollectedJSContext);
   nsCOMPtr<nsIThread> thread = do_GetCurrentThread();
   mOwningThread = thread.forget().downcast<nsThread>().take();
   MOZ_RELEASE_ASSERT(mOwningThread);
 }
@@ -72,27 +72,27 @@ CycleCollectedJSContext::~CycleCollected
 
   mRuntime->RemoveContext(this);
 
   if (mIsPrimaryContext) {
     mRuntime->Shutdown(mJSContext);
   }
 
   // Last chance to process any events.
-  ProcessMetastableStateQueue(mBaseRecursionDepth);
-  MOZ_ASSERT(mMetastableStateEvents.IsEmpty());
+  CleanupIDBTransactions(mBaseRecursionDepth);
+  MOZ_ASSERT(mPendingIDBTransactions.IsEmpty());
 
   ProcessStableStateQueue();
   MOZ_ASSERT(mStableStateEvents.IsEmpty());
 
   // Clear mPendingException first, since it might be cycle collected.
   mPendingException = nullptr;
 
-  MOZ_ASSERT(mDebuggerPromiseMicroTaskQueue.empty());
-  MOZ_ASSERT(mPromiseMicroTaskQueue.empty());
+  MOZ_ASSERT(mDebuggerMicroTaskQueue.empty());
+  MOZ_ASSERT(mPendingMicroTaskRunnables.empty());
 
   mUncaughtRejections.reset();
   mConsumedRejections.reset();
 
   JS_DestroyContext(mJSContext);
   mJSContext = nullptr;
 
   if (mIsPrimaryContext) {
@@ -176,42 +176,47 @@ CycleCollectedJSContext::InitializeNonPr
 }
 
 size_t
 CycleCollectedJSContext::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
 {
   return 0;
 }
 
-class PromiseJobRunnable final : public Runnable
+class PromiseJobRunnable final : public MicroTaskRunnable
 {
 public:
   PromiseJobRunnable(JS::HandleObject aCallback,
                      JS::HandleObject aAllocationSite,
                      nsIGlobalObject* aIncumbentGlobal)
-    : Runnable("PromiseJobRunnable")
-    , mCallback(
-        new PromiseJobCallback(aCallback, aAllocationSite, aIncumbentGlobal))
+    :mCallback(
+       new PromiseJobCallback(aCallback, aAllocationSite, aIncumbentGlobal))
   {
   }
 
   virtual ~PromiseJobRunnable()
   {
   }
 
 protected:
-  NS_IMETHOD
-  Run() override
+  virtual void Run(AutoSlowOperation& aAso) override
   {
     JSObject* callback = mCallback->CallbackPreserveColor();
     nsIGlobalObject* global = callback ? xpc::NativeGlobal(callback) : nullptr;
     if (global && !global->IsDying()) {
       mCallback->Call("promise callback");
+      aAso.CheckForInterrupt();
     }
-    return NS_OK;
+  }
+
+  virtual bool Suppressed() override
+  {
+    nsIGlobalObject* global =
+      xpc::NativeGlobal(mCallback->CallbackPreserveColor());
+    return global && global->IsInSyncOperation();
   }
 
 private:
   RefPtr<PromiseJobCallback> mCallback;
 };
 
 /* static */
 JSObject*
@@ -235,17 +240,17 @@ CycleCollectedJSContext::EnqueuePromiseJ
   CycleCollectedJSContext* self = static_cast<CycleCollectedJSContext*>(aData);
   MOZ_ASSERT(aCx == self->Context());
   MOZ_ASSERT(Get() == self);
 
   nsIGlobalObject* global = nullptr;
   if (aIncumbentGlobal) {
     global = xpc::NativeGlobal(aIncumbentGlobal);
   }
-  nsCOMPtr<nsIRunnable> runnable = new PromiseJobRunnable(aJob, aAllocationSite, global);
+  RefPtr<MicroTaskRunnable> runnable = new PromiseJobRunnable(aJob, aAllocationSite, global);
   self->DispatchToMicroTask(runnable.forget());
   return true;
 }
 
 /* static */
 void
 CycleCollectedJSContext::PromiseRejectionTrackerCallback(JSContext* aCx,
                                                          JS::HandleObject aPromise,
@@ -276,28 +281,28 @@ CycleCollectedJSContext::GetPendingExcep
 
 void
 CycleCollectedJSContext::SetPendingException(Exception* aException)
 {
   MOZ_ASSERT(mJSContext);
   mPendingException = aException;
 }
 
-std::queue<nsCOMPtr<nsIRunnable>>&
-CycleCollectedJSContext::GetPromiseMicroTaskQueue()
+std::queue<RefPtr<MicroTaskRunnable>>&
+CycleCollectedJSContext::GetMicroTaskQueue()
 {
   MOZ_ASSERT(mJSContext);
-  return mPromiseMicroTaskQueue;
+  return mPendingMicroTaskRunnables;
 }
 
-std::queue<nsCOMPtr<nsIRunnable>>&
-CycleCollectedJSContext::GetDebuggerPromiseMicroTaskQueue()
+std::queue<RefPtr<MicroTaskRunnable>>&
+CycleCollectedJSContext::GetDebuggerMicroTaskQueue()
 {
   MOZ_ASSERT(mJSContext);
-  return mDebuggerPromiseMicroTaskQueue;
+  return mDebuggerMicroTaskQueue;
 }
 
 void
 CycleCollectedJSContext::ProcessStableStateQueue()
 {
   MOZ_ASSERT(mJSContext);
   MOZ_RELEASE_ASSERT(!mDoingStableStates);
   mDoingStableStates = true;
@@ -307,79 +312,86 @@ CycleCollectedJSContext::ProcessStableSt
     event->Run();
   }
 
   mStableStateEvents.Clear();
   mDoingStableStates = false;
 }
 
 void
-CycleCollectedJSContext::ProcessMetastableStateQueue(uint32_t aRecursionDepth)
+CycleCollectedJSContext::CleanupIDBTransactions(uint32_t aRecursionDepth)
 {
   MOZ_ASSERT(mJSContext);
   MOZ_RELEASE_ASSERT(!mDoingStableStates);
   mDoingStableStates = true;
 
-  nsTArray<RunInMetastableStateData> localQueue = Move(mMetastableStateEvents);
+  nsTArray<PendingIDBTransactionData> localQueue = Move(mPendingIDBTransactions);
 
   for (uint32_t i = 0; i < localQueue.Length(); ++i)
   {
-    RunInMetastableStateData& data = localQueue[i];
+    PendingIDBTransactionData& data = localQueue[i];
     if (data.mRecursionDepth != aRecursionDepth) {
       continue;
     }
 
     {
-      nsCOMPtr<nsIRunnable> runnable = data.mRunnable.forget();
-      runnable->Run();
+      nsCOMPtr<nsIRunnable> transaction = data.mTransaction.forget();
+      transaction->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);
+  localQueue.AppendElements(mPendingIDBTransactions);
+  localQueue.SwapElements(mPendingIDBTransactions);
   mDoingStableStates = false;
 }
 
 void
+CycleCollectedJSContext::BeforeProcessTask(bool aMightBlock)
+{
+  // If ProcessNextEvent was called during a microtask 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 && 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 Runnable("BeforeProcessTask"));
+  }
+}
+
+void
 CycleCollectedJSContext::AfterProcessTask(uint32_t aRecursionDepth)
 {
   MOZ_ASSERT(mJSContext);
 
   // 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 (!mDisableMicroTaskCheckpoint) {
-    PerformMicroTaskCheckPoint();
-    if (NS_IsMainThread()) {
-      Promise::PerformMicroTaskCheckpoint();
-    } else {
-      Promise::PerformWorkerMicroTaskCheckpoint();
-    }
-  }
+  PerformMicroTaskCheckPoint();
 
   // Step 4.2 Execute any events that were waiting for a stable state.
   ProcessStableStateQueue();
 
   // This should be a fast test so that it won't affect the next task processing.
   IsIdleGCTaskNeeded();
 }
 
 void
-CycleCollectedJSContext::AfterProcessMicrotask()
+CycleCollectedJSContext::AfterProcessMicrotasks()
 {
   MOZ_ASSERT(mJSContext);
-  AfterProcessMicrotask(RecursionDepth());
+  // Cleanup Indexed Database transactions:
+  // https://html.spec.whatwg.org/multipage/webappapis.html#perform-a-microtask-checkpoint
+  CleanupIDBTransactions(RecursionDepth());
 }
 
 void CycleCollectedJSContext::IsIdleGCTaskNeeded()
 {
   class IdleTimeGCTaskRunnable : public mozilla::IdleRunnable
   {
   public:
     using mozilla::IdleRunnable::IdleRunnable;
@@ -402,74 +414,65 @@ void CycleCollectedJSContext::IsIdleGCTa
 
   if (Runtime()->IsIdleGCTaskNeeded()) {
     nsCOMPtr<nsIRunnable> gc_task = new IdleTimeGCTaskRunnable();
     NS_IdleDispatchToCurrentThread(gc_task.forget());
     Runtime()->SetPendingIdleGCTask();
   }
 }
 
-void
-CycleCollectedJSContext::AfterProcessMicrotask(uint32_t aRecursionDepth)
-{
-  MOZ_ASSERT(mJSContext);
-
-  // Between microtasks, execute any events that were waiting for a microtask
-  // to complete.
-  ProcessMetastableStateQueue(aRecursionDepth);
-}
-
 uint32_t
 CycleCollectedJSContext::RecursionDepth()
 {
   return mOwningThread->RecursionDepth();
 }
 
 void
 CycleCollectedJSContext::RunInStableState(already_AddRefed<nsIRunnable>&& aRunnable)
 {
   MOZ_ASSERT(mJSContext);
   mStableStateEvents.AppendElement(Move(aRunnable));
 }
 
 void
-CycleCollectedJSContext::RunInMetastableState(already_AddRefed<nsIRunnable>&& aRunnable)
+CycleCollectedJSContext::AddPendingIDBTransaction(already_AddRefed<nsIRunnable>&& aTransaction)
 {
   MOZ_ASSERT(mJSContext);
 
-  RunInMetastableStateData data;
-  data.mRunnable = aRunnable;
+  PendingIDBTransactionData data;
+  data.mTransaction = aTransaction;
 
   MOZ_ASSERT(mOwningThread);
   data.mRecursionDepth = RecursionDepth();
 
   // There must be an event running to get here.
 #ifndef MOZ_WIDGET_COCOA
   MOZ_ASSERT(data.mRecursionDepth > mBaseRecursionDepth);
 #else
   // XXX bug 1261143
   // Recursion depth should be greater than mBaseRecursionDepth,
   // or the runnable will stay in the queue forever.
   if (data.mRecursionDepth <= mBaseRecursionDepth) {
     data.mRecursionDepth = mBaseRecursionDepth + 1;
   }
 #endif
 
-  mMetastableStateEvents.AppendElement(Move(data));
+  mPendingIDBTransactions.AppendElement(Move(data));
 }
 
 void
-CycleCollectedJSContext::DispatchToMicroTask(already_AddRefed<nsIRunnable> aRunnable)
+CycleCollectedJSContext::DispatchToMicroTask(
+    already_AddRefed<MicroTaskRunnable> aRunnable)
 {
-  RefPtr<nsIRunnable> runnable(aRunnable);
+  RefPtr<MicroTaskRunnable> runnable(aRunnable);
 
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(runnable);
 
-  mPromiseMicroTaskQueue.push(runnable.forget());
+  mPendingMicroTaskRunnables.push(runnable.forget());
 }
 
 class AsyncMutationHandler final : public mozilla::Runnable
 {
 public:
   AsyncMutationHandler() : mozilla::Runnable("AsyncMutationHandler") {}
 
   NS_IMETHOD Run() override
@@ -477,62 +480,106 @@ public:
     CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
     if (ccjs) {
       ccjs->PerformMicroTaskCheckPoint();
     }
     return NS_OK;
   }
 };
 
-void
+bool
 CycleCollectedJSContext::PerformMicroTaskCheckPoint()
 {
-  if (mPendingMicroTaskRunnables.empty()) {
+  if (mPendingMicroTaskRunnables.empty() && mDebuggerMicroTaskQueue.empty()) {
+    AfterProcessMicrotasks();
     // Nothing to do, return early.
-    return;
+    return false;
   }
 
   uint32_t currentDepth = RecursionDepth();
   if (mMicroTaskRecursionDepth >= currentDepth) {
     // We are already executing microtasks for the current recursion depth.
-    return;
+    return false;
+  }
+
+  if (mTargetedMicroTaskRecursionDepth != 0 &&
+      mTargetedMicroTaskRecursionDepth != currentDepth) {
+    return false;
   }
 
   if (NS_IsMainThread() && !nsContentUtils::IsSafeToRunScript()) {
     // Special case for main thread where DOM mutations may happen when
     // it is not safe to run scripts.
     nsContentUtils::AddScriptRunner(new AsyncMutationHandler());
-    return;
+    return false;
   }
 
   mozilla::AutoRestore<uint32_t> restore(mMicroTaskRecursionDepth);
   MOZ_ASSERT(currentDepth > 0);
   mMicroTaskRecursionDepth = currentDepth;
 
+  bool didProcess = false;
   AutoSlowOperation aso;
 
   std::queue<RefPtr<MicroTaskRunnable>> suppressed;
-  while (!mPendingMicroTaskRunnables.empty()) {
-    RefPtr<MicroTaskRunnable> runnable =
-      mPendingMicroTaskRunnables.front().forget();
-    mPendingMicroTaskRunnables.pop();
+  for (;;) {
+    RefPtr<MicroTaskRunnable> runnable;
+    if (!mDebuggerMicroTaskQueue.empty()) {
+      runnable = mDebuggerMicroTaskQueue.front().forget();
+      mDebuggerMicroTaskQueue.pop();
+    } else if (!mPendingMicroTaskRunnables.empty()) {
+      runnable = mPendingMicroTaskRunnables.front().forget();
+      mPendingMicroTaskRunnables.pop();
+    } else {
+      break;
+    }
+
     if (runnable->Suppressed()) {
+      // Microtasks in worker shall never be suppressed.
+      // Otherwise, mPendingMicroTaskRunnables will be replaced later with
+      // all suppressed tasks in mDebuggerMicroTaskQueue unexpectedly.
+      MOZ_ASSERT(NS_IsMainThread());
       suppressed.push(runnable);
     } else {
+      didProcess = true;
       runnable->Run(aso);
     }
   }
 
   // Put back the suppressed microtasks so that they will be run later.
   // Note, it is possible that we end up keeping these suppressed tasks around
   // for some time, but no longer than spinning the event loop nestedly
   // (sync XHR, alert, etc.)
   mPendingMicroTaskRunnables.swap(suppressed);
+
+  AfterProcessMicrotasks();
+
+  return didProcess;
 }
 
 void
-CycleCollectedJSContext::DispatchMicroTaskRunnable(
-  already_AddRefed<MicroTaskRunnable> aRunnable)
-{
-  mPendingMicroTaskRunnables.push(aRunnable);
+CycleCollectedJSContext::PerformDebuggerMicroTaskCheckpoint()
+ {
+  // Don't do normal microtask handling checks here, since whoever is calling
+  // this method is supposed to know what they are doing.
+
+  AutoSlowOperation aso;
+  for (;;) {
+    // For a debugger microtask checkpoint, we always use the debugger microtask
+    // queue.
+    std::queue<RefPtr<MicroTaskRunnable>>* microtaskQueue =
+      &GetDebuggerMicroTaskQueue();
+
+    if (microtaskQueue->empty()) {
+      break;
+    }
+
+    RefPtr<MicroTaskRunnable> runnable = microtaskQueue->front().forget();
+    MOZ_ASSERT(runnable);
+
+    // This function can re-enter, so we remove the element before calling.
+    microtaskQueue->pop();
+    runnable->Run(aso);
+  }
+
+  AfterProcessMicrotasks();
 }
-
 } // namespace mozilla
--- a/xpcom/base/CycleCollectedJSContext.h
+++ b/xpcom/base/CycleCollectedJSContext.h
@@ -98,127 +98,96 @@ protected:
   // See explanation in mIsPrimaryContext.
   MOZ_IS_CLASS_INIT
   nsresult InitializeNonPrimary(CycleCollectedJSContext* aPrimaryContext);
 
   virtual CycleCollectedJSRuntime* CreateRuntime(JSContext* aCx) = 0;
 
   size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
 
-  std::queue<nsCOMPtr<nsIRunnable>> mPromiseMicroTaskQueue;
-  std::queue<nsCOMPtr<nsIRunnable>> mDebuggerPromiseMicroTaskQueue;
-
 private:
   MOZ_IS_CLASS_INIT
   void InitializeCommon();
 
   static JSObject* GetIncumbentGlobalCallback(JSContext* aCx);
   static bool EnqueuePromiseJobCallback(JSContext* aCx,
                                         JS::HandleObject aJob,
                                         JS::HandleObject aAllocationSite,
                                         JS::HandleObject aIncumbentGlobal,
                                         void* aData);
   static void PromiseRejectionTrackerCallback(JSContext* aCx,
                                               JS::HandleObject aPromise,
                                               JS::PromiseRejectionHandlingState state,
                                               void* aData);
 
-  void AfterProcessMicrotask(uint32_t aRecursionDepth);
+  void AfterProcessMicrotasks();
 public:
   void ProcessStableStateQueue();
 private:
-  void ProcessMetastableStateQueue(uint32_t aRecursionDepth);
+  void CleanupIDBTransactions(uint32_t aRecursionDepth);
 
 public:
   enum DeferredFinalizeType {
     FinalizeIncrementally,
     FinalizeNow,
   };
 
   CycleCollectedJSRuntime* Runtime() const
   {
     MOZ_ASSERT(mRuntime);
     return mRuntime;
   }
 
   already_AddRefed<dom::Exception> GetPendingException() const;
   void SetPendingException(dom::Exception* aException);
 
-  std::queue<nsCOMPtr<nsIRunnable>>& GetPromiseMicroTaskQueue();
-  std::queue<nsCOMPtr<nsIRunnable>>& GetDebuggerPromiseMicroTaskQueue();
+  std::queue<RefPtr<MicroTaskRunnable>>& GetMicroTaskQueue();
+  std::queue<RefPtr<MicroTaskRunnable>>& GetDebuggerMicroTaskQueue();
 
   JSContext* Context() const
   {
     MOZ_ASSERT(mJSContext);
     return mJSContext;
   }
 
   JS::RootingContext* RootingCx() const
   {
     MOZ_ASSERT(mJSContext);
     return JS::RootingContext::get(mJSContext);
   }
 
-  bool MicroTaskCheckpointDisabled() const
-  {
-    return mDisableMicroTaskCheckpoint;
-  }
-
-  void DisableMicroTaskCheckpoint(bool aDisable)
-  {
-    mDisableMicroTaskCheckpoint = aDisable;
-  }
-
-  class MOZ_RAII AutoDisableMicroTaskCheckpoint
+  void SetTargetedMicroTaskRecursionDepth(uint32_t aDepth)
   {
-    public:
-    AutoDisableMicroTaskCheckpoint()
-    : mCCJSCX(CycleCollectedJSContext::Get())
-    {
-      mOldValue = mCCJSCX->MicroTaskCheckpointDisabled();
-      mCCJSCX->DisableMicroTaskCheckpoint(true);
-    }
-
-    ~AutoDisableMicroTaskCheckpoint()
-    {
-      mCCJSCX->DisableMicroTaskCheckpoint(mOldValue);
-    }
-
-    CycleCollectedJSContext* mCCJSCX;
-    bool mOldValue;
-  };
+    mTargetedMicroTaskRecursionDepth = aDepth;
+  }
 
 protected:
   JSContext* MaybeContext() const { return mJSContext; }
 
 public:
   // nsThread entrypoints
-  virtual void BeforeProcessTask(bool aMightBlock) { };
+  virtual void BeforeProcessTask(bool aMightBlock);
   virtual void AfterProcessTask(uint32_t aRecursionDepth);
 
-  // microtask processor entry point
-  void AfterProcessMicrotask();
-
   // Check whether we need an idle GC task.
   void IsIdleGCTaskNeeded();
 
   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);
+
+  void AddPendingIDBTransaction(already_AddRefed<nsIRunnable>&& aTransaction);
 
   // Get the current thread's CycleCollectedJSContext.  Returns null if there
   // isn't one.
   static CycleCollectedJSContext* Get();
 
   // Queue an async microtask to the current main or worker thread.
-  virtual void DispatchToMicroTask(already_AddRefed<nsIRunnable> aRunnable);
+  virtual void DispatchToMicroTask(already_AddRefed<MicroTaskRunnable> aRunnable);
 
   // Call EnterMicroTask when you're entering JS execution.
   // Usually the best way to do this is to use nsAutoMicroTask.
   void EnterMicroTask()
   {
     ++mMicroTaskLevel;
   }
 
@@ -239,19 +208,19 @@ public:
     return mMicroTaskLevel;
   }
 
   void SetMicroTaskLevel(uint32_t aLevel)
   {
     mMicroTaskLevel = aLevel;
   }
 
-  void PerformMicroTaskCheckPoint();
+  bool PerformMicroTaskCheckPoint();
 
-  void DispatchMicroTaskRunnable(already_AddRefed<MicroTaskRunnable> aRunnable);
+  void PerformDebuggerMicroTaskCheckpoint();
 
   bool IsInStableOrMetaStableState()
   {
     return mDoingStableStates;
   }
 
   // Storage for watching rejected promises waiting for some client to
   // consume their rejection.
@@ -276,31 +245,35 @@ private:
 
   CycleCollectedJSRuntime* mRuntime;
 
   JSContext* mJSContext;
 
   nsCOMPtr<dom::Exception> mPendingException;
   nsThread* mOwningThread; // Manual refcounting to avoid include hell.
 
-  struct RunInMetastableStateData
+  struct PendingIDBTransactionData
   {
-    nsCOMPtr<nsIRunnable> mRunnable;
+    nsCOMPtr<nsIRunnable> mTransaction;
     uint32_t mRecursionDepth;
   };
 
   nsTArray<nsCOMPtr<nsIRunnable>> mStableStateEvents;
-  nsTArray<RunInMetastableStateData> mMetastableStateEvents;
+  nsTArray<PendingIDBTransactionData> mPendingIDBTransactions;
   uint32_t mBaseRecursionDepth;
   bool mDoingStableStates;
 
-  bool mDisableMicroTaskCheckpoint;
+  // If set to none 0, microtasks will be processed only when recursion depth
+  // is the set value.
+  uint32_t mTargetedMicroTaskRecursionDepth;
 
   uint32_t mMicroTaskLevel;
+
   std::queue<RefPtr<MicroTaskRunnable>> mPendingMicroTaskRunnables;
+  std::queue<RefPtr<MicroTaskRunnable>> mDebuggerMicroTaskQueue;
 
   uint32_t mMicroTaskRecursionDepth;
 };
 
 class MOZ_STACK_CLASS nsAutoMicroTask
 {
 public:
   nsAutoMicroTask()