tor-browser

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

commit 654639947879cebd1776509f4fd08b114f0339d1
parent f71ec309771be85a45b4518a4b16516c4d05a9aa
Author: Matthew Gaudet <mgaudet@mozilla.com>
Date:   Mon, 10 Nov 2025 18:10:39 +0000

Bug 1996695 - Allow an embedding to specify tracing for non-engine enqueued microtasks. r=jonco,smaug

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

Diffstat:
Mjs/public/Promise.h | 15+++++++++++++++
Mjs/src/vm/JSContext.cpp | 15+++++++++++++++
Mjs/src/vm/JSContext.h | 2+-
Mxpcom/base/CycleCollectedJSContext.cpp | 19+++++++++++++++++++
Mxpcom/base/CycleCollectedJSContext.h | 5+++++
5 files changed, 55 insertions(+), 1 deletion(-)

diff --git a/js/public/Promise.h b/js/public/Promise.h @@ -121,6 +121,21 @@ class JS_PUBLIC_API JobQueue { return false; } + /** + * Trace hook for non-GCThing microtask values. + * + * This hook is called during GC tracing for microtask queue values that are + * not GC-things. It allows the embedding to provide custom tracing for + * values stored in the microtask queue, particularly for Private values + * that represent embedding-specific task representations. + * + * If the value is not a GC thing, this method will be called to allow the + * embedding to trace any GC-reachable data associated with the value. + * + * The default implementation does nothing. + */ + virtual void traceNonGCThingMicroTask(JSTracer* trc, JS::Value* valuePtr) {} + protected: friend class AutoDebuggerJobQueueInterruption; diff --git a/js/src/vm/JSContext.cpp b/js/src/vm/JSContext.cpp @@ -1055,6 +1055,21 @@ js::UniquePtr<JS::JobQueue::SavedJobQueue> InternalJobQueue::saveJobQueue( return saved; } +void js::MicroTaskQueueElement::trace(JSTracer* trc) { + // For non-objects (like Private values), call the JobQueue hook + JSContext* cx = trc->runtime()->mainContextFromOwnThread(); + MOZ_ASSERT(cx); + auto* queue = cx->jobQueue.ref(); + + if (!queue || value.isGCThing()) { + TraceEdge(trc, &value, "microtask-queue-entry"); + } else { + // It's OK to use unbarriered address here because this is not a GC thing + // and so there are no worthwhile barriers to consider here. + queue->traceNonGCThingMicroTask(trc, value.unbarrieredAddress()); + } +} + JS::MicroTask js::MicroTaskQueueSet::popDebugFront() { JS_LOG(mtq, Info, "JS Drain Queue: popDebugFront"); if (!debugMicroTaskQueue.empty()) { diff --git a/js/src/vm/JSContext.h b/js/src/vm/JSContext.h @@ -163,7 +163,7 @@ struct MicroTaskQueueElement { operator JS::Value() const { return value; } - void trace(JSTracer* trc) { TraceEdge(trc, &value, "MicroTaskQueueElement"); } + void trace(JSTracer* trc); private: js::HeapPtr<JS::Value> value; diff --git a/xpcom/base/CycleCollectedJSContext.cpp b/xpcom/base/CycleCollectedJSContext.cpp @@ -327,6 +327,25 @@ bool CycleCollectedJSContext::getHostDefinedGlobal( return true; } +void CycleCollectedJSContext::traceNonGCThingMicroTask(JSTracer* trc, + JS::Value* valuePtr) { + // This hook is called for non-JSObject microtask values. + // In Gecko, the microtask queue should only contain JSObjects (JS microtasks) + // or Private values (Gecko MicroTaskRunnables). Private values are + // indistinguishable from doubles at the bit level, so if this hook is called, + // we know it's not an object, and by design it must be a Private value + // containing a MicroTaskRunnable pointer that was enqueued via + // EnqueueMicroTask. + + MOZ_ASSERT(!valuePtr->isObject(), + "This hook should only be called for non-objects"); + if (void* ptr = valuePtr->toPrivate()) { + // The pointer is a MicroTaskRunnable that may have GC-reachable data + auto* runnable = static_cast<MicroTaskRunnable*>(ptr); + runnable->TraceMicroTask(trc); + } +} + bool CycleCollectedJSContext::getHostDefinedData( JSContext* aCx, JS::MutableHandle<JSObject*> aData) const { nsIGlobalObject* global = mozilla::dom::GetIncumbentGlobal(); diff --git a/xpcom/base/CycleCollectedJSContext.h b/xpcom/base/CycleCollectedJSContext.h @@ -506,6 +506,11 @@ class CycleCollectedJSContext : dom::PerThreadAtomCache, public JS::JobQueue { void runJobs(JSContext* cx) override; bool empty() const override; bool isDrainingStopped() const override { return false; } + + // Trace hook for non-GCThing microtask values (e.g., Private values + // containing MicroTaskRunnable pointers). + void traceNonGCThingMicroTask(JSTracer* trc, JS::Value* valuePtr) override; + class SavedMicroTaskQueue; js::UniquePtr<SavedJobQueue> saveJobQueue(JSContext*) override;