tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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:
Mjs/public/friend/MicroTask.h | 2++
Mjs/src/vm/JSContext.cpp | 2+-
Mxpcom/base/CycleCollectedJSContext.cpp | 131++++++++++++++++++++++++++++++++++++-------------------------------------------
Mxpcom/base/CycleCollectedJSContext.h | 146++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mxpcom/threads/nsThreadUtils.cpp | 1+
Mxpcom/threads/nsThreadUtils.h | 4++--
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