Issue MoonchildProductions/UXP#2306: Implement WeakRef functionality

This commit is contained in:
Basilisk-Dev 2025-12-16 22:28:11 -05:00 committed by wuggy
commit 2a6ec52f2f
5 changed files with 263 additions and 0 deletions

View file

@ -0,0 +1,204 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "builtin/WeakRefObject.h"
#include "jsapi.h"
#include "jscntxt.h"
#include "gc/Nursery.h"
#include "gc/Tracer.h"
#include "vm/GlobalObject.h"
#include "jsobjinlines.h"
#include "vm/Interpreter-inl.h"
#include "vm/NativeObject-inl.h"
using namespace js;
static WeakRefObject::Referent*
GetReferent(JSObject* obj)
{
return obj->as<WeakRefObject>().getData();
}
static MOZ_ALWAYS_INLINE bool
IsWeakRef(HandleValue v)
{
return v.isObject() && v.toObject().is<WeakRefObject>();
}
static bool
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
args.rval().setUndefined();
return true;
}
const JSPropertySpec WeakRefObject::properties[] = {
JS_PS_END
};
const JSFunctionSpec WeakRefObject::methods[] = {
JS_FN("deref", WeakRefObject::deref, 0, 0),
JS_FS_END
};
static JSObject*
InitWeakRefClass(JSContext* cx, HandleObject obj, bool defineMembers)
{
Handle<GlobalObject*> global = obj.as<GlobalObject>();
RootedPlainObject proto(cx, NewBuiltinClassInstance<PlainObject>(cx));
if (!proto)
return nullptr;
RootedFunction ctor(cx, GlobalObject::createConstructor(cx, WeakRefObject::construct,
ClassName(JSProto_WeakRef, cx), 1));
if (!ctor)
return nullptr;
if (!LinkConstructorAndPrototype(cx, ctor, proto))
return nullptr;
if (defineMembers) {
if (!DefinePropertiesAndFunctions(cx, proto, WeakRefObject::properties, WeakRefObject::methods))
return nullptr;
if (!DefineToStringTag(cx, proto, cx->names().WeakRef))
return nullptr;
}
if (!GlobalObject::initBuiltinConstructor(cx, global, JSProto_WeakRef, ctor, proto))
return nullptr;
return proto;
}
/* static */ WeakRefObject*
WeakRefObject::create(JSContext* cx, HandleObject target, HandleObject proto /* = nullptr */)
{
Rooted<WeakRefObject*> obj(cx, NewObjectWithClassProto<WeakRefObject>(cx, proto));
if (!obj)
return nullptr;
Referent* data = cx->new_<Referent>(target);
if (!data)
return nullptr;
obj->setPrivate(data);
return obj;
}
/* static */ bool
WeakRefObject::construct(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, 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());
return false;
}
RootedObject target(cx, &args[0].toObject());
RootedObject proto(cx);
RootedObject newTarget(cx, &args.newTarget().toObject());
if (!GetPrototypeFromConstructor(cx, newTarget, &proto))
return false;
Rooted<WeakRefObject*> obj(cx, WeakRefObject::create(cx, target, proto));
if (!obj)
return false;
args.rval().setObject(*obj);
return true;
}
/* static */ bool
WeakRefObject::deref(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsWeakRef, WeakRef_deref_impl>(cx, args);
}
/* static */ void
WeakRefObject::trace(JSTracer* trc, JSObject* obj)
{
if (Referent* data = GetReferent(obj)) {
JSObject* target = data->target.unbarrieredGet();
if (!target)
return;
// Weak edges must be tenured; fall back to a strong trace while the
// referent is still in the nursery to avoid crashing the GC.
if (IsInsideNursery(target))
TraceManuallyBarrieredEdge(trc, data->target.unsafeGet(), "WeakRef nursery referent");
else
TraceWeakEdge(trc, &data->target, "WeakRef referent");
}
}
/* static */ void
WeakRefObject::finalize(FreeOp* fop, JSObject* obj)
{
if (Referent* data = GetReferent(obj))
fop->delete_(data);
}
static const ClassOps WeakRefObjectClassOps = {
nullptr, /* addProperty */
nullptr, /* delProperty */
nullptr, /* getProperty */
nullptr, /* setProperty */
nullptr, /* enumerate */
nullptr, /* resolve */
nullptr, /* mayResolve */
WeakRefObject::finalize,
nullptr, /* call */
nullptr, /* hasInstance */
nullptr, /* construct */
WeakRefObject::trace
};
const Class WeakRefObject::class_ = {
"WeakRef",
JSCLASS_HAS_PRIVATE |
JSCLASS_HAS_CACHED_PROTO(JSProto_WeakRef) |
JSCLASS_BACKGROUND_FINALIZE,
&WeakRefObjectClassOps
};
/* static */ JSObject*
WeakRefObject::initClass(JSContext* cx, HandleObject obj)
{
return ::InitWeakRefClass(cx, obj, true);
}
JSObject*
js::InitWeakRefClass(JSContext* cx, HandleObject obj)
{
return WeakRefObject::initClass(cx, obj);
}
JSObject*
js::InitBareWeakRefCtor(JSContext* cx, HandleObject obj)
{
return ::InitWeakRefClass(cx, obj, false);
}

View file

@ -0,0 +1,55 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef builtin_WeakRefObject_h
#define builtin_WeakRefObject_h
#include "gc/Barrier.h"
#include "vm/NativeObject.h"
namespace js {
class WeakRefObject : public NativeObject
{
public:
struct Referent {
explicit Referent(JSObject* obj) : target(obj) {}
WeakRef<JSObject*> target;
};
static const Class class_;
static JSObject* initClass(JSContext* cx, HandleObject obj);
static WeakRefObject* create(JSContext* cx, HandleObject target, HandleObject proto = nullptr);
static void trace(JSTracer* trc, JSObject* obj);
static void finalize(FreeOp* fop, JSObject* obj);
[[nodiscard]] static bool construct(JSContext* cx, unsigned argc, Value* vp);
static bool deref(JSContext* cx, unsigned argc, Value* vp);
Referent* getData() const {
return static_cast<Referent*>(getPrivate());
}
WeakRef<JSObject*>& target() {
MOZ_ASSERT(getData());
return getData()->target;
}
static const JSPropertySpec properties[];
static const JSFunctionSpec methods[];
private:
};
extern JSObject*
InitWeakRefClass(JSContext* cx, HandleObject obj);
extern JSObject*
InitBareWeakRefCtor(JSContext* cx, HandleObject obj);
} // namespace js
#endif /* builtin_WeakRefObject_h */

View file

@ -119,6 +119,7 @@ IF_SAB(real,imaginary)(Atomics, InitAtomicsClass, OCLASP(Atomics)) \
imaginary(WritableStreamDefaultController,dummy, dummy) \
real(ByteLengthQueuingStrategy, InitViaClassSpec, &js::ByteLengthQueuingStrategy::class_) \
real(CountQueuingStrategy, InitViaClassSpec, &js::CountQueuingStrategy::class_) \
real(WeakRef, InitWeakRefClass, OCLASP(WeakRef)) \
#define JS_FOR_EACH_PROTOTYPE(macro) JS_FOR_PROTOTYPES(macro,macro)

View file

@ -142,6 +142,7 @@ main_deunified_sources = [
'builtin/TestingFunctions.cpp',
'builtin/TypedObject.cpp',
'builtin/WeakMapObject.cpp',
'builtin/WeakRefObject.cpp',
'builtin/WeakSetObject.cpp',
'devtools/sharkctl.cpp',
'ds/LifoAlloc.cpp',

View file

@ -27,6 +27,7 @@
#include "builtin/SymbolObject.h"
#include "builtin/TypedObject.h"
#include "builtin/WeakMapObject.h"
#include "builtin/WeakRefObject.h"
#include "builtin/WeakSetObject.h"
#include "vm/Debugger.h"
#include "vm/EnvironmentObject.h"
@ -539,6 +540,7 @@ GlobalObject::initSelfHostingBuiltins(JSContext* cx, Handle<GlobalObject*> globa
InitBareBuiltinCtor(cx, global, JSProto_Int32Array) &&
InitBareSymbolCtor(cx, global) &&
InitBareWeakMapCtor(cx, global) &&
InitBareWeakRefCtor(cx, global) &&
InitStopIterationClass(cx, global) &&
DefineFunctions(cx, global, builtins, AsIntrinsic);
}