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:
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;