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 212541 47a470e57e9d677df4dc933ed110cbe319e7de8f
parent 212540 048a239423086c8ea77b78dba035bd892b35d50f
child 212542 dcfb1ac1935ceaa2caf1cba02aa2f4251aa90c61
push id9605
push userpaolo.mozmail@amadzone.org
push dateTue, 28 Oct 2014 12:08:58 +0000
treeherderfx-team@47a470e57e9d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz, khuey
bugs1013625
milestone36.0a1
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);