tor-browser

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

commit 22f7b88f85306bec9bac8a5eb964e203cdf28d42
parent a51e518403271d247626edb9565cd20931b70b68
Author: Matthew Gaudet <mgaudet@mozilla.com>
Date:   Wed, 12 Nov 2025 22:55:21 +0000

Bug 1993582 - Disambiguiate MicroTask kinds at the C++ type level r=arai,smaug

Differential Revision: https://phabricator.services.mozilla.com/D271833

Diffstat:
Mjs/public/friend/MicroTask.h | 22++++++++++++----------
Mjs/src/builtin/Promise.cpp | 97++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Mjs/src/shell/js.cpp | 4+++-
Mjs/src/vm/JSContext.cpp | 14+++++++++-----
Mxpcom/base/CycleCollectedJSContext.h | 37+++++++++++++++++++++++++++----------
5 files changed, 103 insertions(+), 71 deletions(-)

diff --git a/js/public/friend/MicroTask.h b/js/public/friend/MicroTask.h @@ -59,15 +59,20 @@ namespace JS { // An embedding is free to do with non-JS MicroTasks as it // sees fit. using GenericMicroTask = JS::Value; +using JSMicroTask = JSObject; JS_PUBLIC_API bool IsJSMicroTask(const JS::GenericMicroTask& hv); +JS_PUBLIC_API JSMicroTask* ToUnwrappedJSMicroTask( + const JS::GenericMicroTask& genericMicroTask); +JS_PUBLIC_API JSMicroTask* ToMaybeWrappedJSMicroTask( + const JS::GenericMicroTask& genericMicroTask); // Run a MicroTask that is known to be a JS MicroTask. This will crash // if provided an invalid task kind. // // This will return false if an exception is thrown while processing. JS_PUBLIC_API bool RunJSMicroTask(JSContext* cx, - Handle<GenericMicroTask> entry); + Handle<JS::JSMicroTask*> entry); // Queue Management. This is done per-JSContext. // @@ -122,8 +127,7 @@ JS_PUBLIC_API bool HasRegularMicroTasks(JSContext* cx); JS_PUBLIC_API size_t GetRegularMicroTaskCount(JSContext* cx); // This is the global associated with the realm RunJSMicroTask expects to be in. -JS_PUBLIC_API JSObject* GetExecutionGlobalFromJSMicroTask( - const GenericMicroTask& entry); +JS_PUBLIC_API JSObject* GetExecutionGlobalFromJSMicroTask(JSMicroTask* entry); // To handle cases where the queue needs to be set aside for some reason // (mostly the Debugger API), we provide a Save and Restore API. @@ -152,23 +156,21 @@ JS_PUBLIC_API void RestoreMicroTaskQueue( // All of these may return null if there's no data, or if there's a // security error. JS_PUBLIC_API JSObject* MaybeGetHostDefinedDataFromJSMicroTask( - const GenericMicroTask& entry); + JSMicroTask* entry); JS_PUBLIC_API JSObject* MaybeGetAllocationSiteFromJSMicroTask( - const GenericMicroTask& entry); + JSMicroTask* entry); // In some circumstances an entry may not have host defined data but may // still have a host defined global; JS_PUBLIC_API JSObject* MaybeGetHostDefinedGlobalFromJSMicroTask( - const GenericMicroTask& entry); + JSMicroTask* entry); -JS_PUBLIC_API JSObject* MaybeGetPromiseFromJSMicroTask( - const GenericMicroTask& entry); +JS_PUBLIC_API JSObject* MaybeGetPromiseFromJSMicroTask(JSMicroTask* entry); // Get the flow ID from a JS microtask for profiler markers. // This only returns false if entry has become a dead wrapper, // in which case the microtask doesn't run anyhow. -JS_PUBLIC_API bool GetFlowIdFromJSMicroTask(const GenericMicroTask& entry, - uint64_t* uid); +JS_PUBLIC_API bool GetFlowIdFromJSMicroTask(JSMicroTask* entry, uint64_t* uid); } // namespace JS diff --git a/js/src/builtin/Promise.cpp b/js/src/builtin/Promise.cpp @@ -1678,7 +1678,7 @@ static bool ResolvePromiseFunction(JSContext* cx, unsigned argc, Value* vp) { return true; } -static bool EnqueueJob(JSContext* cx, JS::GenericMicroTask&& job) { +static bool EnqueueJob(JSContext* cx, JS::JSMicroTask* job) { MOZ_ASSERT(cx->realm()); GeckoProfilerRuntime& profiler = cx->runtime()->geckoProfiler(); if (profiler.enabled()) { @@ -1690,12 +1690,17 @@ static bool EnqueueJob(JSContext* cx, JS::GenericMicroTask&& job) { } } + // We need to root this job because useDebugQueue can GC. + Rooted<JS::JSMicroTask*> rootedJob(cx, job); + // Only check if we need to use the debug queue when we're not on main thread. if (MOZ_UNLIKELY(!cx->runtime()->isMainRuntime() && cx->jobQueue->useDebugQueue(cx->global()))) { - return cx->microTaskQueues->enqueueDebugMicroTask(cx, std::move(job)); + return cx->microTaskQueues->enqueueDebugMicroTask(cx, + ObjectValue(*rootedJob)); } - return cx->microTaskQueues->enqueueRegularMicroTask(cx, std::move(job)); + return cx->microTaskQueues->enqueueRegularMicroTask(cx, + ObjectValue(*rootedJob)); } static bool PromiseReactionJob(JSContext* cx, unsigned argc, Value* vp); @@ -1923,7 +1928,7 @@ static bool PromiseReactionJob(JSContext* cx, unsigned argc, Value* vp); } // HostEnqueuePromiseJob(job.[[Job]], job.[[Realm]]). - return EnqueueJob(cx, std::move(reactionVal.get())); + return EnqueueJob(cx, &reactionVal.toObject()); } RootedField<JSObject*, 7> hostDefinedData(roots); @@ -2940,7 +2945,7 @@ static bool PromiseResolveBuiltinThenableJob(JSContext* cx, unsigned argc, thenableJob->setHostDefinedGlobalRepresentative( hostDefinedGlobalRepresentative); - return EnqueueJob(cx, ObjectValue(*thenableJob)); + return EnqueueJob(cx, thenableJob); } // Step 1. Let job be a new Job Abstract Closure with no parameters that @@ -3000,7 +3005,7 @@ static bool PromiseResolveBuiltinThenableJob(JSContext* cx, unsigned argc, return false; } - return EnqueueJob(cx, ObjectValue(*thenableJob)); + return EnqueueJob(cx, thenableJob); } // Step 1. Let job be a new Job Abstract Closure with no parameters that @@ -7634,17 +7639,16 @@ void PromiseObject::dumpOwnStringContent(js::GenericPrinter& out) const {} } JS_PUBLIC_API bool JS::RunJSMicroTask(JSContext* cx, - Handle<JS::GenericMicroTask> entry) { + Handle<JS::JSMicroTask*> entry) { #ifdef DEBUG - MOZ_ASSERT(entry.isObject()); JSObject* global = JS::GetExecutionGlobalFromJSMicroTask(entry); MOZ_ASSERT(global == cx->global()); #endif - RootedObject task(cx, &entry.toObject()); + RootedObject task(cx, entry); MOZ_ASSERT(!JS_IsDeadWrapper(task)); - RootedObject unwrappedTask(cx, UncheckedUnwrap(&entry.toObject())); + RootedObject unwrappedTask(cx, UncheckedUnwrap(entry)); MOZ_ASSERT(unwrappedTask); if (unwrappedTask->is<PromiseReactionRecord>()) { @@ -7692,12 +7696,9 @@ inline bool JSObject::is<MicroTaskEntry>() const { } JS_PUBLIC_API JSObject* JS::MaybeGetHostDefinedDataFromJSMicroTask( - const JS::GenericMicroTask& entry) { - if (!entry.isObject()) { - return nullptr; - } - MOZ_ASSERT(!JS_IsDeadWrapper(&entry.toObject())); - JSObject* task = CheckedUnwrapStatic(&entry.toObject()); + JS::JSMicroTask* entry) { + MOZ_ASSERT(!JS_IsDeadWrapper(entry)); + JSObject* task = CheckedUnwrapStatic(entry); if (!task) { return nullptr; } @@ -7715,11 +7716,8 @@ JS_PUBLIC_API JSObject* JS::MaybeGetHostDefinedDataFromJSMicroTask( } JS_PUBLIC_API JSObject* JS::MaybeGetAllocationSiteFromJSMicroTask( - const JS::GenericMicroTask& entry) { - if (!entry.isObject()) { - return nullptr; - } - JSObject* task = UncheckedUnwrap(&entry.toObject()); + JS::JSMicroTask* entry) { + JSObject* task = UncheckedUnwrap(entry); MOZ_ASSERT(task); if (JS_IsDeadWrapper(task)) { return nullptr; @@ -7740,11 +7738,8 @@ JS_PUBLIC_API JSObject* JS::MaybeGetAllocationSiteFromJSMicroTask( } JS_PUBLIC_API JSObject* JS::MaybeGetHostDefinedGlobalFromJSMicroTask( - const JS::GenericMicroTask& entry) { - if (!entry.isObject()) { - return nullptr; - } - JSObject* task = UncheckedUnwrap(&entry.toObject()); + JSMicroTask* entry) { + JSObject* task = UncheckedUnwrap(entry); MOZ_ASSERT(task->is<MicroTaskEntry>()); JSObject* maybeWrappedHostDefinedRepresentative = @@ -7759,10 +7754,8 @@ JS_PUBLIC_API JSObject* JS::MaybeGetHostDefinedGlobalFromJSMicroTask( } JS_PUBLIC_API JSObject* JS::GetExecutionGlobalFromJSMicroTask( - const JS::GenericMicroTask& entry) { - MOZ_RELEASE_ASSERT(entry.isObject(), "Only use on JSMicroTasks"); - - JSObject* unwrapped = UncheckedUnwrap(&entry.toObject()); + JS::JSMicroTask* entry) { + JSObject* unwrapped = UncheckedUnwrap(entry); if (unwrapped->is<PromiseReactionRecord>()) { // Use the stored equeue representative (which may need to be unwrapped) JSObject* enqueueGlobalRepresentative = @@ -7791,10 +7784,8 @@ JS_PUBLIC_API JSObject* JS::GetExecutionGlobalFromJSMicroTask( } JS_PUBLIC_API JSObject* JS::MaybeGetPromiseFromJSMicroTask( - const JS::GenericMicroTask& entry) { - MOZ_RELEASE_ASSERT(entry.isObject(), "Only use on JSMicroTasks"); - - JSObject* unwrapped = UncheckedUnwrap(&entry.toObject()); + JS::JSMicroTask* entry) { + JSObject* unwrapped = UncheckedUnwrap(entry); // We don't expect to ever lose the record a job points to. MOZ_RELEASE_ASSERT(!JS_IsDeadWrapper(unwrapped)); @@ -7805,13 +7796,11 @@ JS_PUBLIC_API JSObject* JS::MaybeGetPromiseFromJSMicroTask( return nullptr; } -JS_PUBLIC_API bool JS::GetFlowIdFromJSMicroTask( - const JS::GenericMicroTask& entry, uint64_t* uid) { - MOZ_RELEASE_ASSERT(entry.isObject(), "Only use on JSMicroTasks"); - +JS_PUBLIC_API bool JS::GetFlowIdFromJSMicroTask(JS::JSMicroTask* entry, + uint64_t* uid) { // We want to make sure we get the flow id from the target object // not the wrapper. - JSObject* unwrapped = UncheckedUnwrap(&entry.toObject()); + JSObject* unwrapped = UncheckedUnwrap(entry); if (JS_IsDeadWrapper(unwrapped)) { return false; } @@ -7822,18 +7811,36 @@ JS_PUBLIC_API bool JS::GetFlowIdFromJSMicroTask( return true; } -JS_PUBLIC_API bool JS::IsJSMicroTask(const JS::GenericMicroTask& hv) { - if (!hv.isObject()) { - return false; +JS_PUBLIC_API JS::JSMicroTask* JS::ToUnwrappedJSMicroTask( + const JS::GenericMicroTask& genericMicroTask) { + if (!genericMicroTask.isObject()) { + return nullptr; } - JSObject* unwrapped = UncheckedUnwrap(&hv.toObject()); + JSObject* unwrapped = UncheckedUnwrap(&genericMicroTask.toObject()); // On the off chance someone hands us a dead wrapper. if (JS_IsDeadWrapper(unwrapped)) { - return false; + return nullptr; + } + if (!unwrapped->is<MicroTaskEntry>()) { + return nullptr; } - return unwrapped->is<MicroTaskEntry>(); + + return unwrapped; +} + +JS_PUBLIC_API JS::JSMicroTask* JS::ToMaybeWrappedJSMicroTask( + const JS::GenericMicroTask& genericMicroTask) { + if (!genericMicroTask.isObject()) { + return nullptr; + } + + return &genericMicroTask.toObject(); +} + +JS_PUBLIC_API bool JS::IsJSMicroTask(const JS::GenericMicroTask& hv) { + return JS::ToUnwrappedJSMicroTask(hv) != nullptr; } JS::AutoDebuggerJobQueueInterruption::AutoDebuggerJobQueueInterruption() diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp @@ -1488,7 +1488,9 @@ static bool GlobalOfFirstJobInQueue(JSContext* cx, unsigned argc, Value* vp) { return false; } - auto& job = cx->microTaskQueues->microTaskQueue.front(); + auto& genericJob = cx->microTaskQueues->microTaskQueue.front(); + JS::JSMicroTask* job = JS::ToUnwrappedJSMicroTask(genericJob); + MOZ_ASSERT(job); RootedObject global(cx, JS::GetExecutionGlobalFromJSMicroTask(job)); MOZ_ASSERT(global); if (!cx->compartment()->wrap(cx, &global)) { diff --git a/js/src/vm/JSContext.cpp b/js/src/vm/JSContext.cpp @@ -794,9 +794,10 @@ JSObject* InternalJobQueue::copyJobs(JSContext* cx) { auto& queues = cx->microTaskQueues; auto addToArray = [&](auto& queue) -> bool { for (const auto& e : queue) { - if (JS::GetExecutionGlobalFromJSMicroTask(e)) { + JS::JSMicroTask* task = JS::ToUnwrappedJSMicroTask(e); + if (task) { // All any test cares about is the global of the job so let's do it. - RootedObject global(cx, JS::GetExecutionGlobalFromJSMicroTask(e)); + RootedObject global(cx, JS::GetExecutionGlobalFromJSMicroTask(task)); if (!cx->compartment()->wrap(cx, &global)) { return false; } @@ -901,7 +902,8 @@ void InternalJobQueue::runJobs(JSContext* cx) { if (JS::Prefs::use_js_microtask_queue()) { // Execute jobs in a loop until we've reached the end of the queue. - JS::Rooted<JS::GenericMicroTask> job(cx); + JS::Rooted<JS::JSMicroTask*> job(cx); + JS::Rooted<JS::GenericMicroTask> dequeueJob(cx); while (JS::HasAnyMicroTasks(cx)) { MOZ_ASSERT(queue.empty()); // A previous job might have set this flag. E.g., the js shell @@ -912,8 +914,10 @@ void InternalJobQueue::runJobs(JSContext* cx) { cx->runtime()->offThreadPromiseState.ref().internalDrain(cx); - job = JS::DequeueNextMicroTask(cx); - MOZ_ASSERT(!job.isNull()); + dequeueJob = JS::DequeueNextMicroTask(cx); + MOZ_ASSERT(!dequeueJob.isNull()); + job = JS::ToMaybeWrappedJSMicroTask(dequeueJob); + MOZ_ASSERT(job); // If the next job is the last job in the job queue, allow // skipping the standard job queuing behavior. diff --git a/xpcom/base/CycleCollectedJSContext.h b/xpcom/base/CycleCollectedJSContext.h @@ -196,7 +196,8 @@ class MustConsumeMicroTask { // consuming the contents. JSObject* GetExecutionGlobalFromJSMicroTask(JSContext* aCx) const { MOZ_ASSERT(IsJSMicroTask()); - JS::Rooted<JS::GenericMicroTask> task(aCx, mMicroTask); + JS::JSMicroTask* task = JS::ToUnwrappedJSMicroTask(mMicroTask); + MOZ_ASSERT(task); return JS::GetExecutionGlobalFromJSMicroTask(task); } @@ -206,30 +207,46 @@ class MustConsumeMicroTask { // These are documented in MicroTask.h. bool GetFlowIdFromJSMicroTask(uint64_t* aFlowId) { - MOZ_ASSERT(IsJSMicroTask()); - return JS::GetFlowIdFromJSMicroTask(mMicroTask, aFlowId); + JS::JSMicroTask* task = JS::ToUnwrappedJSMicroTask(mMicroTask); + MOZ_ASSERT(task); + return JS::GetFlowIdFromJSMicroTask(task, aFlowId); } JSObject* MaybeGetPromiseFromJSMicroTask() { - MOZ_ASSERT(IsJSMicroTask()); - return JS::MaybeGetPromiseFromJSMicroTask(mMicroTask); + JS::JSMicroTask* task = JS::ToUnwrappedJSMicroTask(mMicroTask); + MOZ_ASSERT(task); + return JS::MaybeGetPromiseFromJSMicroTask(task); } JSObject* MaybeGetHostDefinedDataFromJSMicroTask() { - return JS::MaybeGetHostDefinedDataFromJSMicroTask(mMicroTask); + JS::JSMicroTask* task = JS::ToUnwrappedJSMicroTask(mMicroTask); + if (!task) { + return nullptr; + } + return JS::MaybeGetHostDefinedDataFromJSMicroTask(task); } JSObject* MaybeGetAllocationSiteFromJSMicroTask() { - return JS::MaybeGetAllocationSiteFromJSMicroTask(mMicroTask); + JS::JSMicroTask* task = JS::ToUnwrappedJSMicroTask(mMicroTask); + if (!task) { + return nullptr; + } + return JS::MaybeGetAllocationSiteFromJSMicroTask(task); } JSObject* MaybeGetHostDefinedGlobalFromJSMicroTask() { - return JS::MaybeGetHostDefinedGlobalFromJSMicroTask(mMicroTask); + JS::JSMicroTask* task = JS::ToUnwrappedJSMicroTask(mMicroTask); + if (!task) { + return nullptr; + } + return JS::MaybeGetHostDefinedGlobalFromJSMicroTask(task); } bool RunAndConsumeJSMicroTask(JSContext* aCx) { - JS::Rooted<JS::Value> rootedTask(aCx, mMicroTask); - bool v = JS::RunJSMicroTask(aCx, rootedTask); + JS::Rooted<JS::JSMicroTask*> task( + aCx, JS::ToMaybeWrappedJSMicroTask(mMicroTask)); + MOZ_ASSERT(task); + bool v = JS::RunJSMicroTask(aCx, task); mMicroTask.setUndefined(); return v; }