Make WeakRef support always enabled

This commit is contained in:
Basilisk-Dev
2026-05-10 11:47:18 -04:00
committed by roytam1
parent a02580dae4
commit 6861bedff6
15 changed files with 244 additions and 56 deletions
-1
View File
@@ -301,7 +301,6 @@ LoadContextOptions(const char* aPrefName, void* /* aClosure */)
.setAsyncStack(GetWorkerPref<bool>(NS_LITERAL_CSTRING("asyncstack")))
.setWerror(GetWorkerPref<bool>(NS_LITERAL_CSTRING("werror")))
.setStreams(GetWorkerPref<bool>(NS_LITERAL_CSTRING("streams")))
.setWeakRefs(GetWorkerPref<bool>(NS_LITERAL_CSTRING("weakrefs")))
.setExtraWarnings(GetWorkerPref<bool>(NS_LITERAL_CSTRING("strict")))
.setArrayProtoValues(GetWorkerPref<bool>(
NS_LITERAL_CSTRING("array_prototype_values")));
-1
View File
@@ -35,7 +35,6 @@ WORKER_SIMPLE_PREF("dom.serviceWorkers.openWindow.enabled", OpenWindowEnabled, O
WORKER_SIMPLE_PREF("dom.storageManager.enabled", StorageManagerEnabled, STORAGEMANAGER_ENABLED)
WORKER_SIMPLE_PREF("dom.push.enabled", PushEnabled, PUSH_ENABLED)
WORKER_SIMPLE_PREF("dom.streams.enabled", StreamsEnabled, STREAMS_ENABLED)
WORKER_SIMPLE_PREF("javascript.options.weakrefs", WeakRefsEnabled, WEAKREFS_ENABLED)
WORKER_SIMPLE_PREF("dom.requestcontext.enabled", RequestContextEnabled, REQUESTCONTEXT_ENABLED)
WORKER_SIMPLE_PREF("gfx.offscreencanvas.enabled", OffscreenCanvasEnabled, OFFSCREENCANVAS_ENABLED)
WORKER_SIMPLE_PREF("dom.webkitBlink.dirPicker.enabled", WebkitBlinkDirectoryPickerEnabled, DOM_WEBKITBLINK_DIRPICKER_WEBKITBLINK)
+68 -27
View File
@@ -11,6 +11,7 @@
#include "gc/Nursery.h"
#include "gc/Tracer.h"
#include "vm/GlobalObject.h"
#include "vm/Symbol.h"
#include "jsobjinlines.h"
@@ -37,11 +38,34 @@ WeakRef_deref_impl(JSContext* cx, const CallArgs& args)
MOZ_ASSERT(IsWeakRef(args.thisv()));
WeakRefObject::Referent* data = GetReferent(&args.thisv().toObject());
JSObject* target = data ? data->target.get() : nullptr;
if (target)
args.rval().setObject(*target);
else
if (!data) {
args.rval().setUndefined();
return true;
}
if (data->isObject()) {
JSObject* target = data->objectTarget.get();
if (target) {
RootedValue kept(cx, ObjectValue(*target));
if (!cx->runtime()->addWeakRefKeptObject(cx, kept))
return false;
args.rval().set(kept);
} else {
args.rval().setUndefined();
}
} else {
MOZ_ASSERT(data->isSymbol());
JS::Symbol* target = data->symbolTarget.get();
if (target) {
RootedValue kept(cx, SymbolValue(target));
if (!cx->runtime()->addWeakRefKeptObject(cx, kept))
return false;
args.rval().set(kept);
} else {
args.rval().setUndefined();
}
}
return true;
}
@@ -83,13 +107,18 @@ InitWeakRefClass(JSContext* cx, HandleObject obj, bool defineMembers)
}
/* static */ WeakRefObject*
WeakRefObject::create(JSContext* cx, HandleObject target, HandleObject proto /* = nullptr */)
WeakRefObject::create(JSContext* cx, HandleValue target, HandleObject proto /* = nullptr */)
{
Rooted<WeakRefObject*> obj(cx, NewObjectWithClassProto<WeakRefObject>(cx, proto));
if (!obj)
return nullptr;
Referent* data = cx->new_<Referent>(target, cx->options().weakRefs());
Referent* data;
if (target.isObject())
data = cx->new_<Referent>(&target.toObject());
else
data = cx->new_<Referent>(target.toSymbol());
if (!data)
return nullptr;
@@ -97,6 +126,18 @@ WeakRefObject::create(JSContext* cx, HandleObject target, HandleObject proto /*
return obj;
}
static bool
CanBeHeldWeakly(HandleValue target)
{
if (target.isObject())
return true;
if (target.isSymbol())
return target.toSymbol()->code() != JS::SymbolCode::InSymbolRegistry;
return false;
}
/* static */ bool
WeakRefObject::construct(JSContext* cx, unsigned argc, Value* vp)
{
@@ -105,18 +146,12 @@ WeakRefObject::construct(JSContext* cx, unsigned argc, Value* vp)
if (!ThrowIfNotConstructing(cx, args, "WeakRef"))
return false;
if (!args.get(0).isObject()) {
UniqueChars bytes =
DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, args.get(0), nullptr);
if (!bytes)
return false;
JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT,
bytes.get());
if (!CanBeHeldWeakly(args.get(0))) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_WEAKREF_TARGET);
return false;
}
RootedObject target(cx, &args[0].toObject());
RootedValue target(cx, args[0]);
RootedObject proto(cx);
RootedObject newTarget(cx, &args.newTarget().toObject());
@@ -127,6 +162,9 @@ WeakRefObject::construct(JSContext* cx, unsigned argc, Value* vp)
if (!obj)
return false;
if (!cx->runtime()->addWeakRefKeptObject(cx, target))
return false;
args.rval().setObject(*obj);
return true;
}
@@ -142,19 +180,23 @@ WeakRefObject::deref(JSContext* cx, unsigned argc, Value* vp)
WeakRefObject::trace(JSTracer* trc, JSObject* obj)
{
if (Referent* data = GetReferent(obj)) {
JSObject* target = data->target.unbarrieredGet();
if (!target)
return;
if (data->isObject()) {
JSObject* target = data->objectTarget.unbarrieredGet();
if (!target)
return;
// When pref-disabled, keep referent alive via strong trace so deref()
// stays usable as a stub without touching GC internals.
if (!data->enabled) {
TraceManuallyBarrieredEdge(trc, data->target.unsafeGet(), "WeakRef stub referent");
} else if (IsInsideNursery(target)) {
// Weak edges must be tenured; trace strongly while referent is in the nursery.
TraceManuallyBarrieredEdge(trc, data->target.unsafeGet(), "WeakRef nursery referent");
if (IsInsideNursery(target)) {
// Weak edges must be tenured; trace strongly while referent is in the nursery.
TraceManuallyBarrieredEdge(trc, data->objectTarget.unsafeGet(),
"WeakRef nursery referent");
} else {
TraceWeakEdge(trc, &data->objectTarget, "WeakRef referent");
}
} else {
TraceWeakEdge(trc, &data->target, "WeakRef referent");
MOZ_ASSERT(data->isSymbol());
JS::Symbol* target = data->symbolTarget.unbarrieredGet();
if (target)
TraceWeakEdge(trc, &data->symbolTarget, "WeakRef symbol referent");
}
}
}
@@ -162,7 +204,6 @@ WeakRefObject::trace(JSTracer* trc, JSObject* obj)
/* static */ void
WeakRefObject::finalize(FreeOp* fop, JSObject* obj)
{
MOZ_ASSERT(!fop->maybeOffMainThread());
if (Referent* data = GetReferent(obj))
fop->delete_(data);
}
+19 -6
View File
@@ -15,16 +15,28 @@ class WeakRefObject : public NativeObject
{
public:
struct Referent {
explicit Referent(JSObject* obj, bool enabled)
: target(obj), enabled(enabled) {}
WeakRef<JSObject*> target;
bool enabled;
enum class Kind {
Object,
Symbol
};
explicit Referent(JSObject* obj)
: kind(Kind::Object), objectTarget(obj), symbolTarget(nullptr) {}
explicit Referent(JS::Symbol* sym)
: kind(Kind::Symbol), objectTarget(nullptr), symbolTarget(sym) {}
bool isObject() const { return kind == Kind::Object; }
bool isSymbol() const { return kind == Kind::Symbol; }
Kind kind;
WeakRef<JSObject*> objectTarget;
WeakRef<JS::Symbol*> symbolTarget;
};
static const Class class_;
static JSObject* initClass(JSContext* cx, HandleObject obj);
static WeakRefObject* create(JSContext* cx, HandleObject target, HandleObject proto = nullptr);
static WeakRefObject* create(JSContext* cx, HandleValue target, HandleObject proto = nullptr);
static void trace(JSTracer* trc, JSObject* obj);
static void finalize(FreeOp* fop, JSObject* obj);
@@ -37,7 +49,8 @@ class WeakRefObject : public NativeObject
WeakRef<JSObject*>& target() {
MOZ_ASSERT(getData());
return getData()->target;
MOZ_ASSERT(getData()->isObject());
return getData()->objectTarget;
}
static const JSPropertySpec properties[];
+1
View File
@@ -81,6 +81,7 @@ MSG_DEF(JSMSG_EMPTY_ARRAY_REDUCE, 0, JSEXN_TYPEERR, "reduce of empty array
MSG_DEF(JSMSG_UNEXPECTED_TYPE, 2, JSEXN_TYPEERR, "{0} is {1}")
MSG_DEF(JSMSG_MISSING_FUN_ARG, 2, JSEXN_TYPEERR, "missing argument {0} when calling function {1}")
MSG_DEF(JSMSG_NOT_NONNULL_OBJECT, 1, JSEXN_TYPEERR, "{0} is not a non-null object")
MSG_DEF(JSMSG_NOT_WEAKREF_TARGET, 0, JSEXN_TYPEERR, "WeakRef target must be an object or a non-registered symbol")
MSG_DEF(JSMSG_SET_NON_OBJECT_RECEIVER, 1, JSEXN_TYPEERR, "can't assign to properties of {0}: not an object")
MSG_DEF(JSMSG_INVALID_DESCRIPTOR, 0, JSEXN_TYPEERR, "property descriptors must not specify a value or be writable when a getter or setter has been specified")
MSG_DEF(JSMSG_OBJECT_NOT_EXTENSIBLE, 1, JSEXN_TYPEERR, "{0}: Object is not extensible")
+58 -2
View File
@@ -21,6 +21,64 @@ struct MyHeap
BEGIN_TEST(testGCWeakRef)
{
CHECK(cx->options().weakRefs());
cx->options().setWeakRefs(false);
CHECK(cx->options().weakRefs());
JS::RootedValue v(cx);
EXEC("var weakRefTarget = { x: 42 };\n"
"var weakRef = new WeakRef(weakRefTarget);\n"
"weakRefTarget = null;\n");
// The constructor keeps the target alive until the host clears kept
// objects. This must remain true even after callers try the old disabled
// option path above.
JS_GC(cx);
JS_GC(cx);
EVAL("weakRef.deref()", &v);
CHECK(v.isObject());
JS::ClearWeakRefKeptObjects(cx);
v = JS::UndefinedValue();
JS_GC(cx);
JS_GC(cx);
EVAL("weakRef.deref()", &v);
CHECK(v.isUndefined());
EXEC("var weakRefSymbol = Symbol('weak-ref-symbol');\n"
"var symbolRef = new WeakRef(weakRefSymbol);\n"
"if (symbolRef.deref() !== weakRefSymbol)\n"
" throw new Error('WeakRef must accept unique symbols');\n"
"var registeredSymbolRejected = false;\n"
"try {\n"
" new WeakRef(Symbol.for('weak-ref-symbol'));\n"
"} catch (e) {\n"
" registeredSymbolRejected = e instanceof TypeError;\n"
"}\n"
"if (!registeredSymbolRejected)\n"
" throw new Error('WeakRef must reject registered symbols');\n");
EXEC("var keptTarget = { y: 7 };\n"
"var keptRef = new WeakRef(keptTarget);\n");
JS::ClearWeakRefKeptObjects(cx);
EXEC("var keptResult = keptRef.deref();\n"
"if (keptResult !== keptTarget)\n"
" throw new Error('WeakRef deref must return the target');\n"
"keptTarget = null;\n"
"keptResult = null;\n");
JS_GC(cx);
JS_GC(cx);
EVAL("keptRef.deref()", &v);
CHECK(v.isObject());
JS::ClearWeakRefKeptObjects(cx);
v = JS::UndefinedValue();
JS_GC(cx);
JS_GC(cx);
EVAL("keptRef.deref()", &v);
CHECK(v.isUndefined());
// Create an object and add a property to it so that we can read the
// property back later to verify that object internals are not garbage.
JS::RootedObject obj(cx, JS_NewPlainObject(cx));
@@ -38,7 +96,6 @@ BEGIN_TEST(testGCWeakRef)
// references.
CHECK(heap.get().weak.unbarrieredGet() != nullptr);
obj = heap.get().weak;
JS::RootedValue v(cx);
CHECK(JS_GetProperty(cx, obj, "x", &v));
CHECK(v.isInt32());
CHECK(v.toInt32() == 42);
@@ -61,4 +118,3 @@ BEGIN_TEST(testGCWeakRef)
return true;
}
END_TEST(testGCWeakRef)
+9
View File
@@ -627,6 +627,15 @@ JS::InitSelfHostedCode(JSContext* cx)
return true;
}
JS_PUBLIC_API(void)
JS::ClearWeakRefKeptObjects(JSContext* cx)
{
MOZ_ASSERT(cx);
MOZ_ASSERT(!cx->runtime()->isHeapBusy());
cx->runtime()->clearWeakRefKeptObjects();
}
JS_PUBLIC_API(const char*)
JS_GetImplementationVersion(void)
{
+10 -5
View File
@@ -999,7 +999,7 @@ class JS_PUBLIC_API(ContextOptions) {
strictMode_(false),
extraWarnings_(false),
arrayProtoValues_(true),
weakRefs_(false)
streams_(false)
{
}
@@ -1139,13 +1139,12 @@ class JS_PUBLIC_API(ContextOptions) {
return *this;
}
bool weakRefs() const { return weakRefs_; }
bool weakRefs() const { return true; }
ContextOptions& setWeakRefs(bool flag) {
weakRefs_ = flag;
(void) flag;
return *this;
}
ContextOptions& toggleWeakRefs() {
weakRefs_ = !weakRefs_;
return *this;
}
@@ -1166,7 +1165,6 @@ class JS_PUBLIC_API(ContextOptions) {
bool extraWarnings_ : 1;
bool arrayProtoValues_ : 1;
bool streams_ : 1;
bool weakRefs_ : 1;
};
JS_PUBLIC_API(ContextOptions&)
@@ -1187,6 +1185,13 @@ InitSelfHostedCode(JSContext* cx);
JS_PUBLIC_API(void)
AssertObjectBelongsToCurrentThread(JSObject* obj);
/**
* Clear objects and symbols that WeakRef.prototype.deref kept alive for the
* current synchronous JavaScript execution.
*/
JS_PUBLIC_API(void)
ClearWeakRefKeptObjects(JSContext* cx);
} /* namespace JS */
extern JS_PUBLIC_API(const char*)
+4 -1
View File
@@ -676,7 +676,9 @@ RunFile(JSContext* cx, const char* filename, FILE* file, bool compileOnly)
AnalyzeEntrainedVariables(cx, script);
#endif
if (!compileOnly) {
if (!JS_ExecuteScript(cx, script))
bool ok = JS_ExecuteScript(cx, script);
JS::ClearWeakRefKeptObjects(cx);
if (!ok)
return false;
int64_t t2 = PRMJ_Now() - t1;
if (printTiming)
@@ -857,6 +859,7 @@ DrainJobQueue(JSContext* cx)
}
sc->jobQueue.clear();
sc->drainingJobQueue = false;
JS::ClearWeakRefKeptObjects(cx);
return true;
}
+5 -7
View File
@@ -1,15 +1,13 @@
// Manual WeakRef smoke tests for browser console use.
//
// Usage:
// 1) Optionally flip `javascript.options.weakrefs` in about:config and reload.
// 2) Paste this file into the browser console (or load via file://) and run:
// 1) Paste this file into the browser console (or load via file://) and run:
// runWeakRefManual();
// 3) Inspect the returned array of results; no throws or crashes are expected.
// 2) Inspect the returned array of results; no throws or crashes are expected.
//
// Notes:
// - When the pref is ON, deref() should return the target.
// - When the pref is OFF, the stub traces strongly so deref() should also
// return the target.
// - WeakRef is always enabled. Once all strong references are gone, a full GC
// may clear the referent and make deref() return undefined.
function runWeakRefManual() {
const results = [];
@@ -21,7 +19,7 @@ function runWeakRefManual() {
log("initial deref tag", ref.deref()?.tag);
log("repeat deref identity", ref.deref() === target);
// Clear the strong reference; the WeakRef should still be able to return it.
// Clear the strong reference. A future full GC may clear the WeakRef.
target = null;
const afterClear = ref.deref();
+58
View File
@@ -194,6 +194,7 @@ JSRuntime::JSRuntime(JSRuntime* parentRuntime)
simulator_(nullptr),
#endif
scriptAndCountsVector(nullptr),
weakRefKeptObjects(nullptr),
lcovOutput(),
NaNValue(DoubleNaNValue()),
negativeInfinityValue(DoubleValue(NegativeInfinity<double>())),
@@ -372,6 +373,8 @@ JSRuntime::destroyRuntime()
MOZ_ASSERT(!isHeapBusy());
MOZ_ASSERT(childRuntimeCount == 0);
clearWeakRefKeptObjects();
fx.destroyInstance();
sharedIntlData.destroyInstance();
@@ -460,6 +463,61 @@ JSRuntime::destroyRuntime()
#endif
}
static bool
SameWeakRefKeptObject(const JS::Value& kept, JS::HandleValue target)
{
MOZ_ASSERT(kept.isObject() || kept.isSymbol());
MOZ_ASSERT(target.isObject() || target.isSymbol());
if (kept.isObject())
return target.isObject() && &kept.toObject() == &target.toObject();
return target.isSymbol() && kept.toSymbol() == target.toSymbol();
}
bool
JSRuntime::addWeakRefKeptObject(JSContext* cx, JS::HandleValue target)
{
MOZ_ASSERT(cx->runtime() == this);
MOZ_ASSERT(target.isObject() || target.isSymbol());
MOZ_ASSERT(!isHeapBusy());
if (!weakRefKeptObjects) {
auto* keptObjects =
cx->new_<JS::PersistentRooted<js::WeakRefKeptObjectVector>>(
cx, js::WeakRefKeptObjectVector(js::SystemAllocPolicy()));
if (!keptObjects)
return false;
weakRefKeptObjects = keptObjects;
}
for (size_t i = 0; i < weakRefKeptObjects->length(); i++) {
const JS::Value& kept = (*weakRefKeptObjects)[i];
if (SameWeakRefKeptObject(kept, target))
return true;
}
if (!weakRefKeptObjects->append(target.get())) {
ReportOutOfMemory(cx);
return false;
}
return true;
}
void
JSRuntime::clearWeakRefKeptObjects()
{
MOZ_ASSERT(!isHeapBusy());
if (!weakRefKeptObjects)
return;
defaultFreeOp()->delete_(weakRefKeptObjects);
weakRefKeptObjects = nullptr;
}
void
JSRuntime::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, JS::RuntimeSizes* rtSizes)
{
+7
View File
@@ -360,6 +360,7 @@ class PerThreadData
};
using ScriptAndCountsVector = GCVector<ScriptAndCounts, 0, SystemAllocPolicy>;
using WeakRefKeptObjectVector = JS::GCVector<JS::Value, 0, SystemAllocPolicy>;
class AutoLockForExclusiveAccess;
} // namespace js
@@ -887,6 +888,12 @@ struct JSRuntime : public JS::shadow::Runtime,
/* Strong references on scripts held for PCCount profiling API. */
JS::PersistentRooted<js::ScriptAndCountsVector>* scriptAndCountsVector;
/* Strong references to live WeakRef targets kept until the next job boundary. */
JS::PersistentRooted<js::WeakRefKeptObjectVector>* weakRefKeptObjects;
[[nodiscard]] bool addWeakRefKeptObject(JSContext* cx, JS::HandleValue target);
void clearWeakRefKeptObjects();
/* Code coverage output. */
js::coverage::LCovRuntime lcovOutput;
+1 -3
View File
@@ -1442,7 +1442,6 @@ ReloadPrefsCallback(const char* pref, void* data)
bool extraWarnings = Preferences::GetBool(JS_OPTIONS_DOT_STR "strict");
bool streams = Preferences::GetBool(JS_OPTIONS_DOT_STR "streams");
bool weakRefs = Preferences::GetBool(JS_OPTIONS_DOT_STR "weakrefs");
bool unboxedObjects = Preferences::GetBool(JS_OPTIONS_DOT_STR "unboxed_objects");
@@ -1469,8 +1468,7 @@ ReloadPrefsCallback(const char* pref, void* data)
.setWerror(werror)
.setExtraWarnings(extraWarnings)
.setArrayProtoValues(arrayProtoValues)
.setStreams(streams)
.setWeakRefs(weakRefs);
.setStreams(streams);
JS_SetParallelParsingEnabled(cx, parallelParsing);
JS_SetOffthreadIonCompilationEnabled(cx, offthreadIonCompilation);
-3
View File
@@ -1340,9 +1340,6 @@ pref("javascript.options.dynamicImport", true);
// Streams API
pref("javascript.options.streams", true);
// Enable garbage collection of weakrefed objects
pref("javascript.options.weakrefs", false);
// advanced prefs
pref("advanced.mailftp", false);
pref("image.animation_mode", "normal");
+4
View File
@@ -1479,6 +1479,8 @@ CycleCollectedJSContext::AfterProcessTask(uint32_t aRecursionDepth)
// Step 4.2 Execute any events that were waiting for a stable state.
ProcessStableStateQueue();
JS::ClearWeakRefKeptObjects(mJSContext);
}
void
@@ -1517,6 +1519,8 @@ CycleCollectedJSContext::AfterProcessMicrotasks()
});
RunInStableState(cleanupRunnable.forget());
};
JS::ClearWeakRefKeptObjects(mJSContext);
}
uint32_t