Bug 1013625 - Process Promise resolution runnables outside of main event queue. r=bz,khuey
authorPaolo Amadini <paolo.mozmail@amadzone.org>
Tue, 28 Oct 2014 12:08:19 +0000
changeset 212704 47a470e57e9d677df4dc933ed110cbe319e7de8f
parent 212703 048a239423086c8ea77b78dba035bd892b35d50f
child 212705 dcfb1ac1935ceaa2caf1cba02aa2f4251aa90c61
push id51042
push userryanvm@gmail.com
push dateTue, 28 Oct 2014 20:25:03 +0000
treeherdermozilla-inbound@53d84829b2b8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz, khuey
bugs1013625
milestone36.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1013625 - Process Promise resolution runnables outside of main event queue. r=bz,khuey
dom/base/nsContentUtils.cpp
dom/base/nsContentUtils.h
dom/promise/AbortablePromise.cpp
dom/promise/Promise.cpp
dom/promise/Promise.h
dom/promise/tests/test_promise.html
dom/workers/WorkerPrivate.cpp
dom/workers/test/promise_worker.js
js/xpconnect/src/nsXPConnect.cpp
xpcom/base/CycleCollectedJSRuntime.cpp
xpcom/base/CycleCollectedJSRuntime.h
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -34,16 +34,17 @@
 #include "mozilla/DebugOnly.h"
 #include "mozilla/LoadInfo.h"
 #include "mozilla/dom/DocumentFragment.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/HTMLMediaElement.h"
 #include "mozilla/dom/HTMLTemplateElement.h"
 #include "mozilla/dom/HTMLContentElement.h"
 #include "mozilla/dom/HTMLShadowElement.h"
+#include "mozilla/dom/Promise.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/TextDecoder.h"
 #include "mozilla/dom/TouchEvent.h"
 #include "mozilla/dom/ShadowRoot.h"
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/EventListenerManager.h"
 #include "mozilla/EventStateManager.h"
 #include "mozilla/IMEStateManager.h"
@@ -5076,17 +5077,17 @@ nsContentUtils::EnterMicroTask()
   ++sMicroTaskLevel;
 }
 
 void
 nsContentUtils::LeaveMicroTask()
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (--sMicroTaskLevel == 0) {
-    nsDOMMutationObserver::HandleMutations();
+    PerformMainThreadMicroTaskCheckpoint();
     nsDocument::ProcessBaseElementQueue();
   }
 }
 
 bool
 nsContentUtils::IsInMicroTask()
 {
   MOZ_ASSERT(NS_IsMainThread());
@@ -5102,16 +5103,24 @@ nsContentUtils::MicroTaskLevel()
 
 void
 nsContentUtils::SetMicroTaskLevel(uint32_t aLevel)
 {
   MOZ_ASSERT(NS_IsMainThread());
   sMicroTaskLevel = aLevel;
 }
 
+void
+nsContentUtils::PerformMainThreadMicroTaskCheckpoint()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  nsDOMMutationObserver::HandleMutations();
+}
+
 /* 
  * Helper function for nsContentUtils::ProcessViewportInfo.
  *
  * Handles a single key=value pair. If it corresponds to a valid viewport
  * attribute, add it to the document header data. No validation is done on the
  * value itself (this is done at display time).
  */
 static void ProcessViewportToken(nsIDocument *aDocument, 
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -1537,16 +1537,18 @@ public:
   // Usually the best way to do this is to use nsAutoMicroTask.
   static void EnterMicroTask();
   static void LeaveMicroTask();
 
   static bool IsInMicroTask();
   static uint32_t MicroTaskLevel();
   static void SetMicroTaskLevel(uint32_t aLevel);
 
+  static void PerformMainThreadMicroTaskCheckpoint();
+
   /* Process viewport META data. This gives us information for the scale
    * and zoom of a page on mobile devices. We stick the information in
    * the document header and use it later on after rendering.
    *
    * See Bug #436083
    */
   static nsresult ProcessViewportInfo(nsIDocument *aDocument,
                                       const nsAString &viewportInfo);
--- a/dom/promise/AbortablePromise.cpp
+++ b/dom/promise/AbortablePromise.cpp
@@ -95,17 +95,17 @@ AbortablePromise::Abort()
 {
   if (IsPending()) {
     return;
   }
   MaybeReject(NS_ERROR_ABORT);
 
   nsCOMPtr<nsIRunnable> runnable =
     NS_NewRunnableMethod(this, &AbortablePromise::DoAbort);
-  Promise::DispatchToMainOrWorkerThread(runnable);
+  Promise::DispatchToMicroTask(runnable);
 }
 
 void
 AbortablePromise::DoAbort()
 {
   if (mAbortCallback.HasWebIDLCallback()) {
     ErrorResult rv;
     mAbortCallback.GetWebIDLCallback()->Call(rv);
--- a/dom/promise/Promise.cpp
+++ b/dom/promise/Promise.cpp
@@ -349,16 +349,33 @@ Promise::MaybeResolve(JSContext* aCx,
 
 void
 Promise::MaybeReject(JSContext* aCx,
                      JS::Handle<JS::Value> aValue)
 {
   MaybeRejectInternal(aCx, aValue);
 }
 
+void
+Promise::PerformMicroTaskCheckpoint()
+{
+  CycleCollectedJSRuntime* runtime = CycleCollectedJSRuntime::Get();
+  nsTArray<nsRefPtr<nsIRunnable>>& microtaskQueue =
+    runtime->GetPromiseMicroTaskQueue();
+
+  while (!microtaskQueue.IsEmpty()) {
+    nsRefPtr<nsIRunnable> runnable = microtaskQueue.ElementAt(0);
+    MOZ_ASSERT(runnable);
+
+    // This function can re-enter, so we remove the element before calling.
+    microtaskQueue.RemoveElementAt(0);
+    runnable->Run();
+  }
+}
+
 /* static */ bool
 Promise::JSCallback(JSContext* aCx, unsigned aArgc, JS::Value* aVp)
 {
   JS::CallArgs args = CallArgsFromVp(aArgc, aVp);
 
   JS::Rooted<JS::Value> v(aCx,
                           js::GetFunctionNativeReserved(&args.callee(),
                                                         SLOT_PROMISE));
@@ -949,27 +966,25 @@ private:
     NS_ASSERT_OWNINGTHREAD(WrappedWorkerRunnable);
   }
 
   nsCOMPtr<nsIRunnable> mRunnable;
   NS_DECL_OWNINGTHREAD
 };
 
 /* static */ void
-Promise::DispatchToMainOrWorkerThread(nsIRunnable* aRunnable)
+Promise::DispatchToMicroTask(nsIRunnable* aRunnable)
 {
   MOZ_ASSERT(aRunnable);
-  if (NS_IsMainThread()) {
-    NS_DispatchToCurrentThread(aRunnable);
-    return;
-  }
-  WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
-  MOZ_ASSERT(worker);
-  nsRefPtr<WrappedWorkerRunnable> task = new WrappedWorkerRunnable(worker, aRunnable);
-  task->Dispatch(worker->GetJSContext());
+
+  CycleCollectedJSRuntime* runtime = CycleCollectedJSRuntime::Get();
+  nsTArray<nsRefPtr<nsIRunnable>>& microtaskQueue =
+    runtime->GetPromiseMicroTaskQueue();
+
+  microtaskQueue.AppendElement(aRunnable);
 }
 
 void
 Promise::MaybeReportRejected()
 {
   if (mState != Rejected || mHadRejectCallback || mResult.isUndefined()) {
     return;
   }
@@ -1063,17 +1078,17 @@ Promise::ResolveInternal(JSContext* aCx,
 
     if (then.isObject() && JS::IsCallable(&then.toObject())) {
       // This is the then() function of the thenable aValueObj.
       JS::Rooted<JSObject*> thenObj(aCx, &then.toObject());
       nsRefPtr<PromiseInit> thenCallback =
         new PromiseInit(thenObj, mozilla::dom::GetIncumbentGlobal());
       nsRefPtr<ThenableResolverTask> task =
         new ThenableResolverTask(this, valueObj, thenCallback);
-      DispatchToMainOrWorkerThread(task);
+      DispatchToMicroTask(task);
       return;
     }
   }
 
   MaybeSettle(aValue, Resolved);
 }
 
 void
@@ -1129,17 +1144,17 @@ Promise::EnqueueCallbackTasks()
   callbacks.SwapElements(mState == Resolved ? mResolveCallbacks
                                             : mRejectCallbacks);
   mResolveCallbacks.Clear();
   mRejectCallbacks.Clear();
 
   for (uint32_t i = 0; i < callbacks.Length(); ++i) {
     nsRefPtr<PromiseCallbackTask> task =
       new PromiseCallbackTask(this, callbacks[i], mResult);
-    DispatchToMainOrWorkerThread(task);
+    DispatchToMicroTask(task);
   }
 }
 
 void
 Promise::RemoveFeature()
 {
   if (mFeature) {
     WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
--- a/dom/promise/Promise.h
+++ b/dom/promise/Promise.h
@@ -106,16 +106,19 @@ public:
   // every translation unit that includes this header, because that would
   // require use to include DOMError.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.
+  static void PerformMicroTaskCheckpoint();
+
   // WebIDL
 
   nsIGlobalObject* GetParentObject() const
   {
     return mGlobal;
   }
 
   virtual JSObject*
@@ -160,19 +163,19 @@ public:
 
 protected:
   // Do NOT call this unless you're Promise::Create.  I wish we could enforce
   // that from inside this class too, somehow.
   explicit Promise(nsIGlobalObject* aGlobal);
 
   virtual ~Promise();
 
-  // Queue an async task to current main or worker thread.
+  // Queue an async microtask to current main or worker thread.
   static void
-  DispatchToMainOrWorkerThread(nsIRunnable* aRunnable);
+  DispatchToMicroTask(nsIRunnable* aRunnable);
 
   // Do JS-wrapping after Promise creation.
   void CreateWrapper(ErrorResult& aRv);
 
   // Create the JS resolving functions of resolve() and reject(). And provide
   // references to the two functions by calling PromiseInit passed from Promise
   // constructor.
   void CallInitFunction(const GlobalObject& aGlobal, PromiseInit& aInit,
--- a/dom/promise/tests/test_promise.html
+++ b/dom/promise/tests/test_promise.html
@@ -111,30 +111,67 @@ function promiseGC() {
 
   SpecialPowers.gc();
   SpecialPowers.forceGC();
   SpecialPowers.forceCC();
 
   resolve(42);
 }
 
-function promiseAsync() {
-  var global = "foo";
-  var f = new Promise(function(r1, r2) {
-    is(global, "foo", "Global should be foo");
-    r1(42);
-    is(global, "foo", "Global should still be foo");
-    setTimeout(function() {
-      // is(global, "bar", "Global should still be bar!"); // Bug 1013625
-      runTest();
-    }, 0);
-  }).then(function() {
-    global = "bar";
+function promiseAsync_TimeoutResolveThen() {
+  var handlerExecuted = false;
+
+  setTimeout(function() {
+    ok(handlerExecuted, "Handler should have been called before the timeout.");
+
+    // Allow other assertions to run so the test could fail before the next one.
+    setTimeout(runTest, 0);
+  }, 0);
+
+  Promise.resolve().then(function() {
+    handlerExecuted = true;
   });
-  is(global, "foo", "Global should still be foo (2)");
+
+  ok(!handlerExecuted, "Handlers are not called before 'then' returns.");
+}
+
+function promiseAsync_ResolveTimeoutThen() {
+  var handlerExecuted = false;
+
+  var promise = Promise.resolve();
+
+  setTimeout(function() {
+    ok(handlerExecuted, "Handler should have been called before the timeout.");
+
+    // Allow other assertions to run so the test could fail before the next one.
+    setTimeout(runTest, 0);
+  }, 0);
+
+  promise.then(function() {
+    handlerExecuted = true;
+  });
+
+  ok(!handlerExecuted, "Handlers are not called before 'then' returns.");
+}
+
+function promiseAsync_ResolveThenTimeout() {
+  var handlerExecuted = false;
+
+  Promise.resolve().then(function() {
+    handlerExecuted = true;
+  });
+
+  setTimeout(function() {
+    ok(handlerExecuted, "Handler should have been called before the timeout.");
+
+    // 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 promiseDoubleThen() {
   var steps = 0;
   var promise = new Promise(function(r1, r2) {
     r1(42);
   });
 
@@ -710,17 +747,20 @@ function promiseWrapperAsyncResolution()
     runTest();
   }, function() {
     ok(false, "promiseWrapperAsyncResolution: One of the promises failed.");
     runTest();
   });
 }
 
 var tests = [ promiseResolve, promiseReject,
-              promiseException, promiseGC, promiseAsync,
+              promiseException, promiseGC,
+              promiseAsync_TimeoutResolveThen,
+              promiseAsync_ResolveTimeoutThen,
+              promiseAsync_ResolveThenTimeout,
               promiseDoubleThen, promiseThenException,
               promiseThenCatchThen, promiseRejectThenCatchThen,
               promiseRejectThenCatchThen2,
               promiseRejectThenCatchExceptionThen,
               promiseThenCatchOrderingResolve,
               promiseThenCatchOrderingReject,
               promiseNestedPromise, promiseNestedNestedPromise,
               promiseWrongNestedPromise, promiseLoop,
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -44,16 +44,17 @@
 #include "mozilla/dom/ErrorEventBinding.h"
 #include "mozilla/dom/Exceptions.h"
 #include "mozilla/dom/FunctionBinding.h"
 #include "mozilla/dom/ImageData.h"
 #include "mozilla/dom/ImageDataBinding.h"
 #include "mozilla/dom/MessageEvent.h"
 #include "mozilla/dom/MessageEventBinding.h"
 #include "mozilla/dom/MessagePortList.h"
+#include "mozilla/dom/Promise.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/StructuredClone.h"
 #include "mozilla/dom/WorkerBinding.h"
 #include "mozilla/Preferences.h"
 #include "nsAlgorithm.h"
 #include "nsContentUtils.h"
 #include "nsError.h"
 #include "nsDOMJSUtils.h"
@@ -4107,16 +4108,20 @@ WorkerPrivate::DoRunLoop(JSContext* aCx)
     MOZ_ASSERT(NS_HasPendingEvents(mThread));
 
     // Start the periodic GC timer if it is not already running.
     SetGCTimerMode(PeriodicTimer);
 
     // 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.
+    Promise::PerformMicroTaskCheckpoint();
+
     if (NS_HasPendingEvents(mThread)) {
       // Now *might* be a good time to GC. Let the JS engine make the decision.
       if (workerCompartment) {
         JS_MaybeGC(aCx);
       }
     }
     else {
       // The normal event queue has been exhausted, cancel the periodic GC timer
--- a/dom/workers/test/promise_worker.js
+++ b/dom/workers/test/promise_worker.js
@@ -90,30 +90,91 @@ function promiseException() {
     runTest();
   }, function(what) {
     ok(true, "Then - rejectCb has been called");
     is(what, 42, "RejectCb received 42");
     runTest();
   });
 }
 
-function promiseAsync() {
-  var global = "foo";
-  var f = new Promise(function(r1, r2) {
-    is(global, "foo", "Global should be foo");
-    r1(42);
-    is(global, "foo", "Global should still be foo");
-    setTimeout(function() {
-      is(global, "bar", "Global should still be bar!");
-      runTest();
-    }, 0);
-  }).then(function() {
-    global = "bar";
+function promiseAsync_TimeoutResolveThen() {
+  var handlerExecuted = false;
+
+  setTimeout(function() {
+    ok(handlerExecuted, "Handler should have been called before the timeout.");
+
+    // Allow other assertions to run so the test could fail before the next one.
+    setTimeout(runTest, 0);
+  }, 0);
+
+  Promise.resolve().then(function() {
+    handlerExecuted = true;
+  });
+
+  ok(!handlerExecuted, "Handlers are not called before 'then' returns.");
+}
+
+function promiseAsync_ResolveTimeoutThen() {
+  var handlerExecuted = false;
+
+  var promise = Promise.resolve();
+
+  setTimeout(function() {
+    ok(handlerExecuted, "Handler should have been called before the timeout.");
+
+    // Allow other assertions to run so the test could fail before the next one.
+    setTimeout(runTest, 0);
+  }, 0);
+
+  promise.then(function() {
+    handlerExecuted = true;
   });
-  is(global, "foo", "Global should still be foo (2)");
+
+  ok(!handlerExecuted, "Handlers are not called before 'then' returns.");
+}
+
+function promiseAsync_ResolveThenTimeout() {
+  var handlerExecuted = false;
+
+  Promise.resolve().then(function() {
+    handlerExecuted = true;
+  });
+
+  setTimeout(function() {
+    ok(handlerExecuted, "Handler should have been called before the timeout.");
+
+    // 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()
+{
+  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);
+
+  ok(!handlerExecuted, "Sync XHR should not trigger microtask execution.");
+
+  importScripts("relativeLoad_import.js");
+
+  ok(!handlerExecuted, "importScripts should not trigger microtask execution.");
 }
 
 function promiseDoubleThen() {
   var steps = 0;
   var promise = new Promise(function(r1, r2) {
     r1(42);
   });
 
@@ -678,21 +739,63 @@ function promiseResolveThenableCleanStac
     p.then(function() {
       results.push(x);
       is(results[1], 2, "Expected thenable to be called asynchronously");
       runTest();
     });
   },1000);
 }
 
+// Bug 1062323
+function promiseWrapperAsyncResolution()
+{
+  var p = new Promise(function(resolve, reject){
+    resolve();
+  });
+
+  var results = [];
+  var q = p.then(function () {
+    results.push("1-1");
+  }).then(function () {
+    results.push("1-2");
+  }).then(function () {
+    results.push("1-3");
+  });
+
+  var r = p.then(function () {
+    results.push("2-1");
+  }).then(function () {
+    results.push("2-2");
+  }).then(function () {
+    results.push("2-3");
+  });
+
+  Promise.all([q, r]).then(function() {
+    var match = results[0] == "1-1" &&
+                results[1] == "2-1" &&
+                results[2] == "1-2" &&
+                results[3] == "2-2" &&
+                results[4] == "1-3" &&
+                results[5] == "2-3";
+    ok(match, "Chained promises should resolve asynchronously.");
+    runTest();
+  }, function() {
+    ok(false, "promiseWrapperAsyncResolution: One of the promises failed.");
+    runTest();
+  });
+}
+
 var tests = [
     promiseResolve,
     promiseReject,
     promiseException,
-    promiseAsync,
+    promiseAsync_TimeoutResolveThen,
+    promiseAsync_ResolveTimeoutThen,
+    promiseAsync_ResolveThenTimeout,
+    promiseAsync_SyncHXRAndImportScripts,
     promiseDoubleThen,
     promiseThenException,
     promiseThenCatchThen,
     promiseRejectThenCatchThen,
     promiseRejectThenCatchThen2,
     promiseRejectThenCatchExceptionThen,
     promiseThenCatchOrderingResolve,
     promiseThenCatchOrderingReject,
@@ -724,16 +827,18 @@ var tests = [
     promiseRaceReject,
     promiseRaceThrow,
 
     promiseResolveArray,
     promiseResolveThenable,
     promiseResolvePromise,
 
     promiseResolveThenableCleanStack,
+
+    promiseWrapperAsyncResolution,
 ];
 
 function runTest() {
   if (!tests.length) {
     postMessage({ type: 'finish' });
     return;
   }
 
--- a/js/xpconnect/src/nsXPConnect.cpp
+++ b/js/xpconnect/src/nsXPConnect.cpp
@@ -17,17 +17,17 @@
 #include "nsThreadUtils.h"
 #include "nsDOMJSUtils.h"
 
 #include "WrapperFactory.h"
 #include "AccessCheck.h"
 
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/Exceptions.h"
-#include "mozilla/dom/PromiseBinding.h"
+#include "mozilla/dom/Promise.h"
 #include "mozilla/dom/TextDecoderBinding.h"
 #include "mozilla/dom/TextEncoderBinding.h"
 #include "mozilla/dom/DOMErrorBinding.h"
 
 #include "nsDOMMutationObserver.h"
 #include "nsICycleCollectorListener.h"
 #include "nsThread.h"
 #include "mozilla/XPTInterfaceInfoManager.h"
@@ -1048,17 +1048,20 @@ nsXPConnect::AfterProcessNextEvent(nsITh
     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();
-    nsDOMMutationObserver::HandleMutations();
+
+    nsContentUtils::PerformMainThreadMicroTaskCheckpoint();
+
+    Promise::PerformMicroTaskCheckpoint();
 
     PopJSContextNoScriptContext();
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsXPConnect::OnDispatchedEvent(nsIThreadInternal* aThread)
--- a/xpcom/base/CycleCollectedJSRuntime.cpp
+++ b/xpcom/base/CycleCollectedJSRuntime.cpp
@@ -950,16 +950,22 @@ CycleCollectedJSRuntime::GetPendingExcep
 }
 
 void
 CycleCollectedJSRuntime::SetPendingException(nsIException* aException)
 {
   mPendingException = aException;
 }
 
+nsTArray<nsRefPtr<nsIRunnable>>&
+CycleCollectedJSRuntime::GetPromiseMicroTaskQueue()
+{
+  return mPromiseMicroTaskQueue;
+}
+
 nsCycleCollectionParticipant*
 CycleCollectedJSRuntime::GCThingParticipant()
 {
   return &mGCThingCycleCollectorGlobal;
 }
 
 nsCycleCollectionParticipant*
 CycleCollectedJSRuntime::ZoneParticipant()
--- a/xpcom/base/CycleCollectedJSRuntime.h
+++ b/xpcom/base/CycleCollectedJSRuntime.h
@@ -13,16 +13,17 @@
 #include "nsCycleCollector.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsDataHashtable.h"
 #include "nsHashKeys.h"
 #include "nsTArray.h"
 
 class nsCycleCollectionNoteRootCallback;
 class nsIException;
+class nsIRunnable;
 
 namespace js {
 struct Class;
 }
 
 namespace mozilla {
 
 class JSGCThingParticipant: public nsCycleCollectionParticipant
@@ -252,16 +253,18 @@ public:
 #ifdef DEBUG
   bool IsJSHolder(void* aHolder);
   void AssertNoObjectsToTrace(void* aPossibleJSHolder);
 #endif
 
   already_AddRefed<nsIException> GetPendingException() const;
   void SetPendingException(nsIException* aException);
 
+  nsTArray<nsRefPtr<nsIRunnable>>& GetPromiseMicroTaskQueue();
+
   nsCycleCollectionParticipant* GCThingParticipant();
   nsCycleCollectionParticipant* ZoneParticipant();
 
   nsresult TraverseRoots(nsCycleCollectionNoteRootCallback& aCb);
   bool UsefulToMergeZones() const;
   void FixWeakMappingGrayBits() const;
   bool AreGCGrayBitsValid() const;
   void GarbageCollect(uint32_t aReason) const;
@@ -301,16 +304,18 @@ private:
   typedef nsDataHashtable<nsFuncPtrHashKey<DeferredFinalizeFunction>, void*>
     DeferredFinalizerTable;
   DeferredFinalizerTable mDeferredFinalizerTable;
 
   nsRefPtr<IncrementalFinalizeRunnable> mFinalizeRunnable;
 
   nsCOMPtr<nsIException> mPendingException;
 
+  nsTArray<nsRefPtr<nsIRunnable>> mPromiseMicroTaskQueue;
+
   OOMState mOutOfMemoryState;
   OOMState mLargeAllocationFailureState;
 };
 
 MOZ_FINISH_NESTED_ENUM_CLASS(CycleCollectedJSRuntime::OOMState)
 
 void TraceScriptHolder(nsISupports* aHolder, JSTracer* aTracer);