commit 29ea9091ad03f3a0dd2227729952a313f30f355a
parent e382faaaecea798286ac5bd1c883b8e4bfb1152d
Author: Matthew Gaudet <mgaudet@mozilla.com>
Date: Tue, 28 Oct 2025 19:32:52 +0000
Bug 1990842 - Enforce copying restrictions in Gecko r=smaug
Differential Revision: https://phabricator.services.mozilla.com/D268607
Diffstat:
6 files changed, 210 insertions(+), 76 deletions(-)
diff --git a/js/public/friend/MicroTask.h b/js/public/friend/MicroTask.h
@@ -13,6 +13,7 @@
#include "js/RootingAPI.h"
#include "js/TypeDecls.h"
#include "js/UniquePtr.h"
+#include "js/Value.h"
#include "js/ValueArray.h"
namespace JS {
@@ -165,4 +166,5 @@ JS_PUBLIC_API bool GetFlowIdFromJSMicroTask(const MicroTask& entry,
uint64_t* uid);
} // namespace JS
+
#endif /* js_friend_MicroTask_h */
diff --git a/js/src/vm/JSContext.cpp b/js/src/vm/JSContext.cpp
@@ -1177,7 +1177,7 @@ JS_PUBLIC_API bool JS::HasRegularMicroTasks(JSContext* cx) {
JS_PUBLIC_API JS::MicroTask JS::DequeueNextRegularMicroTask(JSContext* cx) {
auto& queue = cx->microTaskQueues->microTaskQueue;
if (!queue.empty()) {
- auto p = std::move(queue.front());
+ JS::MicroTask p = queue.front();
queue.popFront();
return p;
}
diff --git a/xpcom/base/CycleCollectedJSContext.cpp b/xpcom/base/CycleCollectedJSContext.cpp
@@ -432,14 +432,9 @@ bool CycleCollectedJSContext::empty() const {
return mPendingMicroTaskRunnables.empty();
}
-// Unwrap (without interacting with refcounting) a Gecko MicroTaskRunnable if
-// the task is not a JS MicroTask; otherwise, return nullptr.
-//
-// This is a non-owning conversion: the JS::MicroTask still owns the refcount.
-static MicroTaskRunnable* MaybeUnwrapTaskToRunnable(
- JS::Handle<JS::MicroTask> task) {
- if (!JS::IsJSMicroTask(task)) {
- void* nonJSTask = task.toPrivate();
+MicroTaskRunnable* MustConsumeMicroTask::MaybeUnwrapTaskToRunnable() const {
+ if (!IsJSMicroTask()) {
+ void* nonJSTask = mMicroTask.toPrivate();
MicroTaskRunnable* task = reinterpret_cast<MicroTaskRunnable*>(nonJSTask);
return task;
}
@@ -447,34 +442,14 @@ static MicroTaskRunnable* MaybeUnwrapTaskToRunnable(
return nullptr;
}
-// Take ownership of a task inside a JS::MicroTask - This clears the
-// contents of the value to make it clear that we've transfered ownership.
-// Task is only edited if unwrapping succeeds.
-//
-// Note: this is not foolproof because JS::MicroTask is a copyable type, and
-// so nothing currently prevents:
-//
-// Rooted<JS::MicroTask> mt(cx, JS::DequeueNextMicroTask(cx));
-// JS::MicroTask c = mt; // This is a JS::Value copy -- we don't really have
-// mechanism to prevent this RefPtr<Runnable> r =
-// MaybeUnwrapTaskToOwnedRunnable(&mt);
-//
-// At this point c still has a private value pointer to the microtask;
-// conceivably one could do:
-//
-// Rooted<JS::MicroTask> cr(cx, c);
-// RefPtr<Runnable> rFromC = MaybeUnwrapTaskToOwnedRunnable(&cr);
-//
-// Which would result in a double free.
-//
-// This will be fixed in Bug 1990842, which will make this safer.
-static already_AddRefed<MicroTaskRunnable> MaybeUnwrapTaskToOwnedRunnable(
- JS::MutableHandle<JS::MicroTask> task) {
- auto* mtr = MaybeUnwrapTaskToRunnable(task);
+already_AddRefed<MicroTaskRunnable>
+MustConsumeMicroTask::MaybeConsumeAsOwnedRunnable() {
+ MOZ_ASSERT(!IsConsumed(), "Attempting to consume an already-consumed task");
+ MicroTaskRunnable* mtr = MaybeUnwrapTaskToRunnable();
if (!mtr) {
return nullptr;
}
- task.setUndefined();
+ mMicroTask.setUndefined();
return already_AddRefed(mtr);
}
@@ -522,18 +497,19 @@ class CycleCollectedJSContext::SavedMicroTaskQueue
if (StaticPrefs::javascript_options_use_js_microtask_queue()) {
JSContext* cx = ccjs->Context();
- JS::Rooted<JS::MicroTask> suppressedTasks(cx);
+ JS::Rooted<MustConsumeMicroTask> suppressedTasks(cx);
MOZ_ASSERT(JS::GetRegularMicroTaskCount(cx) <= 1);
if (JS::HasRegularMicroTasks(cx)) {
- suppressedTasks = JS::DequeueNextRegularMicroTask(cx);
- MOZ_ASSERT(MaybeUnwrapTaskToRunnable(suppressedTasks) ==
+ suppressedTasks = DequeueNextRegularMicroTask(cx);
+ MOZ_ASSERT(suppressedTasks.get().MaybeUnwrapTaskToRunnable() ==
ccjs->mSuppressedMicroTaskList);
}
MOZ_RELEASE_ASSERT(!JS::HasRegularMicroTasks(cx));
JS::RestoreMicroTaskQueue(cx, std::move(mSavedQueue));
- if (!suppressedTasks.isNullOrUndefined()) {
- JS::EnqueueMicroTask(cx, suppressedTasks.get());
+ if (suppressedTasks.get()) {
+ EnqueueMicroTask(cx,
+ suppressedTasks.get().MaybeConsumeAsOwnedRunnable());
}
} else {
MOZ_RELEASE_ASSERT(ccjs->mPendingMicroTaskRunnables.size() <= 1);
@@ -954,8 +930,8 @@ bool SuppressedMicroTaskList::Suppressed() {
MOZ_LOG_FMT(gLog, LogLevel::Verbose, "Prepending %zu suppressed microtasks",
mSuppressedMicroTaskRunnables.get().length());
for (size_t i = mSuppressedMicroTaskRunnables.get().length(); i > 0; i--) {
- JS::PrependMicroTask(mContext->Context(),
- mSuppressedMicroTaskRunnables.get()[i - 1]);
+ mSuppressedMicroTaskRunnables.get()[i - 1].ConsumeByPrependToQueue(
+ mContext->Context());
}
mSuppressedMicroTaskRunnables.get().clear();
@@ -975,12 +951,12 @@ SuppressedMicroTaskList::~SuppressedMicroTaskList() {
// Run a microtask. Handles both non-JS (enqueued MicroTaskRunnables) and JS
// microtasks.
-static void MOZ_CAN_RUN_SCRIPT
-RunMicroTask(JSContext* aCx, JS::MutableHandle<JS::MicroTask> task) {
- if (RefPtr<MicroTaskRunnable> runnable =
- MaybeUnwrapTaskToOwnedRunnable(task)) {
- LogMicroTaskRunnable::Run log(runnable);
+static void MOZ_CAN_RUN_SCRIPT RunMicroTask(
+ JSContext* aCx, JS::MutableHandle<MustConsumeMicroTask> aMicroTask) {
+ LogMustConsumeMicroTask::Run log(&aMicroTask.get());
+ if (RefPtr<MicroTaskRunnable> runnable =
+ aMicroTask.get().MaybeConsumeAsOwnedRunnable()) {
AUTO_PROFILER_TERMINATING_FLOW_MARKER_FLOW_ONLY(
"RunMicroTaskRunnable", OTHER, Flow::FromPointer(runnable.get()));
AutoSlowOperation aso;
@@ -988,12 +964,6 @@ RunMicroTask(JSContext* aCx, JS::MutableHandle<JS::MicroTask> task) {
return;
}
- MOZ_ASSERT(task.isObject());
-
- // Note this simply prints the address & does not hold on to it, so
- // this is fine from a GC perspective.
- LogJSMicroTask::Run log(&task.toObject());
-
// Avoid the overhead of GetFlowIdFromJSMicroTask in the common case
// of not having the profiler enabled.
mozilla::Maybe<AutoProfilerTerminatingFlowMarkerFlowOnly> terminatingMarker;
@@ -1002,15 +972,15 @@ RunMicroTask(JSContext* aCx, JS::MutableHandle<JS::MicroTask> task) {
uint64_t flowId = 0;
// Since this only returns false when the microtask won't run (dead wrapper)
// we can elide the marker if it does fail.
- if (JS::GetFlowIdFromJSMicroTask(task.get(), &flowId)) {
+ if (aMicroTask.get().GetFlowIdFromJSMicroTask(&flowId)) {
terminatingMarker.emplace("RunMicroTask",
mozilla::baseprofiler::category::OTHER,
Flow::ProcessScoped(flowId));
}
}
- JS::Rooted<JSObject*> maybePromise(aCx,
- JS::MaybeGetPromiseFromJSMicroTask(task));
+ JS::Rooted<JSObject*> maybePromise(
+ aCx, aMicroTask.get().MaybeGetPromiseFromJSMicroTask());
auto state = maybePromise
? JS::GetPromiseUserInputEventHandlingState(maybePromise)
: JS::PromiseUserInputEventHandlingState::DontCare;
@@ -1022,11 +992,11 @@ RunMicroTask(JSContext* aCx, JS::MutableHandle<JS::MicroTask> task) {
JS::RootedTuple<JSObject*, JSObject*, JSObject*> roots(aCx);
JS::RootedField<JSObject*, 0> callbackGlobal(
- roots, JS::GetExecutionGlobalFromJSMicroTask(task));
+ roots, aMicroTask.get().GetExecutionGlobalFromJSMicroTask(aCx));
JS::RootedField<JSObject*, 1> hostDefinedData(
- roots, JS::MaybeGetHostDefinedDataFromJSMicroTask(task));
+ roots, aMicroTask.get().MaybeGetHostDefinedDataFromJSMicroTask());
JS::RootedField<JSObject*, 2> allocStack(
- roots, JS::MaybeGetAllocationSiteFromJSMicroTask(task));
+ roots, aMicroTask.get().MaybeGetAllocationSiteFromJSMicroTask());
nsIGlobalObject* incumbentGlobal = nullptr;
@@ -1055,7 +1025,7 @@ RunMicroTask(JSContext* aCx, JS::MutableHandle<JS::MicroTask> task) {
// harmonious through co-evolution with the JS engine, but I have tried to
// avoid doing too much divergence for now.
JSObject* incumbentGlobalJS =
- JS::MaybeGetHostDefinedGlobalFromJSMicroTask(task);
+ aMicroTask.get().MaybeGetHostDefinedGlobalFromJSMicroTask();
MOZ_ASSERT_IF(incumbentGlobalJS, !js::IsWrapper(incumbentGlobalJS));
if (incumbentGlobalJS) {
incumbentGlobal = xpc::NativeGlobal(incumbentGlobalJS);
@@ -1080,13 +1050,16 @@ RunMicroTask(JSContext* aCx, JS::MutableHandle<JS::MicroTask> task) {
"promise callback" /* Some tests care about this string. */,
dom::CallbackObject::eReportExceptions);
if (!setup.GetContext()) {
+ // We can't run, so we must ignore here!
+ aMicroTask.get().IgnoreJSMicroTask();
+
return;
}
// Note: We're dropping the return value on the floor here, however
// cleanup and exception handling are done as part of the CallSetup
// destructor if necessary.
- (void)JS::RunJSMicroTask(aCx, task);
+ (void)aMicroTask.get().RunAndConsumeJSMicroTask(aCx);
// (The step after step 7): Set event loop’s current scheduling
// state to null
@@ -1095,9 +1068,22 @@ RunMicroTask(JSContext* aCx, JS::MutableHandle<JS::MicroTask> task) {
}
}
-static bool IsSuppressed(JS::Handle<JS::MicroTask> task) {
- if (JS::IsJSMicroTask(task)) {
- JSObject* jsGlobal = JS::GetExecutionGlobalFromJSMicroTask(task);
+MustConsumeMicroTask DequeueNextMicroTask(JSContext* aCx) {
+ return MustConsumeMicroTask(JS::DequeueNextMicroTask(aCx));
+}
+
+MustConsumeMicroTask DequeueNextRegularMicroTask(JSContext* aCx) {
+ return MustConsumeMicroTask(JS::DequeueNextRegularMicroTask(aCx));
+}
+
+MustConsumeMicroTask DequeueNextDebuggerMicroTask(JSContext* aCx) {
+ return MustConsumeMicroTask(JS::DequeueNextDebuggerMicroTask(aCx));
+}
+
+static bool IsSuppressed(JSContext* aCx,
+ JS::Handle<MustConsumeMicroTask> task) {
+ if (task.get().IsJSMicroTask()) {
+ JSObject* jsGlobal = task.get().GetExecutionGlobalFromJSMicroTask(aCx);
if (!jsGlobal) {
return false;
}
@@ -1105,7 +1091,7 @@ static bool IsSuppressed(JS::Handle<JS::MicroTask> task) {
return global && global->IsInSyncOperation();
}
- MicroTaskRunnable* runnable = MaybeUnwrapTaskToRunnable(task);
+ MicroTaskRunnable* runnable = task.get().MaybeUnwrapTaskToRunnable();
// If it's not a JS microtask, it must be a MicroTaskRunnable,
// and so MaybeUnwrapTaskToRunnable must return non-null.
@@ -1175,24 +1161,24 @@ bool CycleCollectedJSContext::PerformMicroTaskCheckPoint(bool aForce) {
MOZ_ASSERT(mDebuggerMicroTaskQueue.empty());
MOZ_ASSERT(mPendingMicroTaskRunnables.empty());
MOZ_ASSERT(!mSuppressedMicroTasks);
- JS::Rooted<JS::MicroTask> job(cx);
+ JS::Rooted<MustConsumeMicroTask> job(cx);
while (JS::HasAnyMicroTasks(cx)) {
MOZ_ASSERT(mDebuggerMicroTaskQueue.empty());
MOZ_ASSERT(mPendingMicroTaskRunnables.empty());
- job = JS::DequeueNextMicroTask(cx);
+ job = DequeueNextMicroTask(cx);
// To avoid us accidentally re-enqueing a SuppressionMicroTaskList in
// itself, we determine here if the job is actually the suppression task
// list.
- bool isSuppressionJob =
- mSuppressedMicroTaskList
- ? MaybeUnwrapTaskToRunnable(job) == mSuppressedMicroTaskList
- : false;
+ bool isSuppressionJob = mSuppressedMicroTaskList
+ ? job.get().MaybeUnwrapTaskToRunnable() ==
+ mSuppressedMicroTaskList
+ : false;
// No need to check Suppressed if there aren't ongoing sync operations nor
// pending mSuppressedMicroTasks.s
if ((IsInSyncOperation() || mSuppressedMicroTaskList) &&
- IsSuppressed(job)) {
+ IsSuppressed(cx, job)) {
// Microtasks in worker shall never be suppressed.
// Otherwise, the micro tasks queue will be replaced later with
// all suppressed tasks in mDebuggerMicroTaskQueue unexpectedly.
@@ -1210,7 +1196,7 @@ bool CycleCollectedJSContext::PerformMicroTaskCheckPoint(bool aForce) {
} else {
// Consume the runnable & simultaneously drop a ref count.
RefPtr<MicroTaskRunnable> refToDrop(
- MaybeUnwrapTaskToOwnedRunnable(&job));
+ job.get().MaybeConsumeAsOwnedRunnable());
MOZ_ASSERT(refToDrop);
}
} else {
@@ -1313,7 +1299,8 @@ void CycleCollectedJSContext::PerformDebuggerMicroTaskCheckpoint() {
MOZ_ASSERT(mDebuggerMicroTaskQueue.empty());
MOZ_ASSERT(mPendingMicroTaskRunnables.empty());
- JS::Rooted<JS::MicroTask> job(cx, JS::DequeueNextDebuggerMicroTask(cx));
+ JS::Rooted<MustConsumeMicroTask> job(cx);
+ job.set(DequeueNextDebuggerMicroTask(cx));
RunMicroTask(cx, &job);
}
diff --git a/xpcom/base/CycleCollectedJSContext.h b/xpcom/base/CycleCollectedJSContext.h
@@ -9,6 +9,7 @@
#include <deque>
+#include "js/TracingAPI.h"
#include "mozilla/Attributes.h"
#include "mozilla/LinkedList.h"
#include "mozilla/MemoryReporting.h"
@@ -105,6 +106,145 @@ class SuppressedMicroTasks : public MicroTaskRunnable {
std::deque<RefPtr<MicroTaskRunnable>> mSuppressedMicroTaskRunnables;
};
+// A gecko wrapper for the JS::MicroTask type. Used to enforce both
+// that this is handled move only, but also that we have succesfully
+// consumed this microtask before destruction.
+class MustConsumeMicroTask {
+ public:
+ // We need a public constructor to allow forward declared Rooted
+ MustConsumeMicroTask() = default;
+
+ // The only way to get a (filled) MustConsumeMicroTask is through these
+ // mechanisms.
+ friend MustConsumeMicroTask DequeueNextMicroTask(JSContext* aCx);
+ friend MustConsumeMicroTask DequeueNextRegularMicroTask(JSContext* aCx);
+ friend MustConsumeMicroTask DequeueNextDebuggerMicroTask(JSContext* aCx);
+
+ ~MustConsumeMicroTask() {
+ if (!mMicroTask.isUndefined()) {
+ MOZ_CRASH("Didn't consume MicroTask");
+ }
+ }
+
+ // Move only semantics
+ MustConsumeMicroTask(const MustConsumeMicroTask&) = delete;
+ MustConsumeMicroTask& operator=(const MustConsumeMicroTask&) = delete;
+ MustConsumeMicroTask(MustConsumeMicroTask&& other) {
+ mMicroTask = other.mMicroTask;
+ other.mMicroTask.setUndefined();
+ }
+ MustConsumeMicroTask& operator=(MustConsumeMicroTask&& other) noexcept {
+ if (this != &other) {
+ mMicroTask = other.mMicroTask;
+ other.mMicroTask.setUndefined();
+ }
+ return *this;
+ }
+
+ // Indicate if this still holds a task or not.
+ bool IsConsumed() const { return mMicroTask.isUndefined(); }
+
+ // Allow testing for contentfulness.
+ explicit operator bool() const { return !IsConsumed(); }
+
+ // Check if this holds a "JS Microtask" (see MicroTask.h),
+ // which is a task enqueued by the JS engine rather than
+ // Gecko.
+ bool IsJSMicroTask() const { return JS::IsJSMicroTask(mMicroTask); }
+
+ // Unwrap (without interacting with refcounting) a Gecko MicroTaskRunnable if
+ // the task is not a JS MicroTask (see MicroTask.h for "JS MicroTask");
+ // otherwise, return nullptr.
+ //
+ // This is a non-owning conversion: This class still owns the refcount.
+ MicroTaskRunnable* MaybeUnwrapTaskToRunnable() const;
+
+ // Take ownership of a non-JS task inside a JS::MicroTask - This clears the
+ // contents of the value to make it clear that we've transfered ownership.
+ // `this` is marked is only edited if unwrapping succeeds, and so
+ // you can conditionally try to consume as owned;
+ //
+ // MOZ_ASSERT(!mustConsume.IsConsumed())
+ // if (RefPtr<MicroTaskRunnable> geckoTask =
+ // mustConsume.MaybeConsumeAsOwnedRunnable()) {
+ // // mustConsume is now empty
+ // } else {
+ // // mustConsume still holds a JS microtask
+ // }
+ //
+ already_AddRefed<MicroTaskRunnable> MaybeConsumeAsOwnedRunnable();
+
+ // Intentionally ignore a JS microtask. This can happen when script
+ // execution is disallowed during CallSetup
+ void IgnoreJSMicroTask() {
+ MOZ_ASSERT(IsJSMicroTask());
+ mMicroTask.setUndefined();
+ }
+
+ // Consume this by prepending this MustConsumeMicroTask back into
+ // the MicroTaskQueue.
+ void ConsumeByPrependToQueue(JSContext* aCx) {
+ MOZ_ASSERT(!IsConsumed(), "Attempting to consume an already-consumed task");
+ if (!JS::PrependMicroTask(aCx, mMicroTask)) {
+ // Can't lose tasks.
+ NS_ABORT_OOM(0);
+ }
+ mMicroTask.setUndefined();
+ }
+
+ // Get the execution global for this task without
+ // consuming the contents.
+ JSObject* GetExecutionGlobalFromJSMicroTask(JSContext* aCx) const {
+ MOZ_ASSERT(IsJSMicroTask());
+ JS::Rooted<JS::MicroTask> task(aCx, mMicroTask);
+ return JS::GetExecutionGlobalFromJSMicroTask(task);
+ }
+
+ // Below: A number of wrappers to allow working with a MustConsume without
+ // exposing the contained task which could then be misused.
+ //
+ // These are documented in MicroTask.h.
+
+ bool GetFlowIdFromJSMicroTask(uint64_t* aFlowId) {
+ MOZ_ASSERT(IsJSMicroTask());
+ return JS::GetFlowIdFromJSMicroTask(mMicroTask, aFlowId);
+ }
+
+ JSObject* MaybeGetPromiseFromJSMicroTask() {
+ MOZ_ASSERT(IsJSMicroTask());
+ return JS::MaybeGetPromiseFromJSMicroTask(mMicroTask);
+ }
+
+ JSObject* MaybeGetHostDefinedDataFromJSMicroTask() {
+ return JS::MaybeGetHostDefinedDataFromJSMicroTask(mMicroTask);
+ }
+
+ JSObject* MaybeGetAllocationSiteFromJSMicroTask() {
+ return JS::MaybeGetAllocationSiteFromJSMicroTask(mMicroTask);
+ }
+
+ JSObject* MaybeGetHostDefinedGlobalFromJSMicroTask() {
+ return JS::MaybeGetHostDefinedGlobalFromJSMicroTask(mMicroTask);
+ }
+
+ bool RunAndConsumeJSMicroTask(JSContext* aCx) {
+ JS::Rooted<JS::Value> rootedTask(aCx, mMicroTask);
+ bool v = JS::RunJSMicroTask(aCx, rootedTask);
+ mMicroTask.setUndefined();
+ return v;
+ }
+
+ void trace(JSTracer* aTrc) {
+ TraceEdge(aTrc, &mMicroTask, "MustConsumeMicroTask value");
+ }
+
+ private:
+ explicit MustConsumeMicroTask(JS::MicroTask&& aMicroTask)
+ : mMicroTask(aMicroTask) {}
+
+ JS::Heap<JS::MicroTask> mMicroTask;
+};
+
class SuppressedMicroTaskList final : public MicroTaskRunnable {
public:
SuppressedMicroTaskList() = delete;
@@ -118,7 +258,7 @@ class SuppressedMicroTaskList final : public MicroTaskRunnable {
CycleCollectedJSContext* mContext = nullptr;
uint64_t mSuppressionGeneration = 0;
- JS::PersistentRooted<JS::GCVector<JS::MicroTask>>
+ JS::PersistentRooted<JS::GCVector<MustConsumeMicroTask>>
mSuppressedMicroTaskRunnables;
private:
@@ -164,6 +304,10 @@ bool EnqueueMicroTask(JSContext* aCx,
bool EnqueueDebugMicroTask(JSContext* aCx,
already_AddRefed<MicroTaskRunnable> aRunnable);
+MustConsumeMicroTask DequeueNextMicroTask(JSContext* aCx);
+MustConsumeMicroTask DequeueNextRegularMicroTask(JSContext* aCx);
+MustConsumeMicroTask DequeueNextDebuggerMicroTask(JSContext* aCx);
+
class CycleCollectedJSContext : dom::PerThreadAtomCache, public JS::JobQueue {
friend class CycleCollectedJSRuntime;
friend class SuppressedMicroTasks;
diff --git a/xpcom/threads/nsThreadUtils.cpp b/xpcom/threads/nsThreadUtils.cpp
@@ -652,6 +652,7 @@ LogTaskBase<T>::Run::~Run() {
template class LogTaskBase<nsIRunnable>;
template class LogTaskBase<MicroTaskRunnable>;
+template class LogTaskBase<MustConsumeMicroTask>;
template class LogTaskBase<IPC::Message>;
template class LogTaskBase<nsTimerImpl>;
template class LogTaskBase<Task>;
diff --git a/xpcom/threads/nsThreadUtils.h b/xpcom/threads/nsThreadUtils.h
@@ -35,7 +35,6 @@
class MessageLoop;
class nsIThread;
-class JSObject;
//-----------------------------------------------------------------------------
// These methods are alternatives to the methods on nsIThreadManager, provided
@@ -1812,6 +1811,7 @@ class LogTaskBase {
};
class MicroTaskRunnable;
+class MustConsumeMicroTask;
class Task; // TaskController
class PresShell;
namespace dom {
@@ -1834,6 +1834,7 @@ LogTaskBase<nsTimerImpl>::Run::Run(nsTimerImpl* aEvent, bool aWillRunAgain);
typedef LogTaskBase<nsIRunnable> LogRunnable;
typedef LogTaskBase<MicroTaskRunnable> LogMicroTaskRunnable;
+typedef LogTaskBase<MustConsumeMicroTask> LogMustConsumeMicroTask;
typedef LogTaskBase<IPC::Message> LogIPCMessage;
typedef LogTaskBase<nsTimerImpl> LogTimerEvent;
typedef LogTaskBase<Task> LogTask;
@@ -1841,7 +1842,6 @@ typedef LogTaskBase<PresShell> LogPresShellObserver;
typedef LogTaskBase<dom::FrameRequestCallback> LogFrameRequestCallback;
typedef LogTaskBase<dom::VideoFrameRequestCallback>
LogVideoFrameRequestCallback;
-typedef LogTaskBase<JSObject> LogJSMicroTask;
// If you add new types don't forget to add:
// `template class LogTaskBase<YourType>;` to nsThreadUtils.cpp