mirror of
https://github.com/roytam1/UXP.git
synced 2026-05-27 10:08:35 +00:00
Revert "Implement FinalizationRegistry" and related commits.
this cause a crash in GC when idling sometimes.
This commit is contained in:
@@ -1,608 +0,0 @@
|
||||
/* -*- 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/FinalizationRegistryObject.h"
|
||||
|
||||
#include "jsapi.h"
|
||||
#include "jscntxt.h"
|
||||
|
||||
#include "builtin/WeakRefObject.h"
|
||||
#include "gc/Nursery.h"
|
||||
#include "gc/Tracer.h"
|
||||
#include "vm/EqualityOperations.h"
|
||||
#include "vm/GlobalObject.h"
|
||||
#include "vm/Interpreter.h"
|
||||
#include "vm/Symbol.h"
|
||||
|
||||
#include "jswrapper.h"
|
||||
#include "jsobjinlines.h"
|
||||
|
||||
#include "vm/Interpreter-inl.h"
|
||||
#include "vm/NativeObject-inl.h"
|
||||
|
||||
using namespace js;
|
||||
|
||||
enum class WeakCellKind {
|
||||
Empty,
|
||||
Object,
|
||||
Symbol
|
||||
};
|
||||
|
||||
static JSObject*
|
||||
NormalizeWeakObject(JSObject* obj)
|
||||
{
|
||||
if (JSObject* unwrapped = CheckedUnwrap(obj, /* stopAtWindowProxy = */ false))
|
||||
return unwrapped;
|
||||
return obj;
|
||||
}
|
||||
|
||||
static bool
|
||||
GetPrototypeFromFinalizationRegistryConstructor(JSContext* cx, HandleObject newTarget,
|
||||
MutableHandleObject proto)
|
||||
{
|
||||
if (!GetPrototypeFromConstructor(cx, newTarget, proto))
|
||||
return false;
|
||||
if (proto)
|
||||
return true;
|
||||
|
||||
RootedObject realmObject(cx, CheckedUnwrap(newTarget, /* stopAtWindowProxy = */ false));
|
||||
if (!realmObject)
|
||||
return false;
|
||||
|
||||
{
|
||||
JSAutoCompartment ac(cx, realmObject);
|
||||
Rooted<GlobalObject*> global(cx, &realmObject->global());
|
||||
if (!GlobalObject::ensureConstructor(cx, global, JSProto_FinalizationRegistry))
|
||||
return false;
|
||||
proto.set(&global->getPrototype(JSProto_FinalizationRegistry).toObject());
|
||||
}
|
||||
|
||||
return cx->compartment()->wrap(cx, proto);
|
||||
}
|
||||
|
||||
struct WeakCell {
|
||||
WeakCellKind kind;
|
||||
WeakRef<JSObject*> object;
|
||||
WeakRef<JS::Symbol*> symbol;
|
||||
|
||||
WeakCell() : kind(WeakCellKind::Empty), object(nullptr), symbol(nullptr) {}
|
||||
explicit WeakCell(JSObject* obj) : kind(WeakCellKind::Object), object(obj), symbol(nullptr) {}
|
||||
explicit WeakCell(JS::Symbol* sym) : kind(WeakCellKind::Symbol), object(nullptr), symbol(sym) {}
|
||||
|
||||
void clear() {
|
||||
kind = WeakCellKind::Empty;
|
||||
object = nullptr;
|
||||
symbol = nullptr;
|
||||
}
|
||||
|
||||
bool isEmpty() const { return kind == WeakCellKind::Empty; }
|
||||
|
||||
bool isDead() const {
|
||||
if (kind == WeakCellKind::Object)
|
||||
return !object.unbarrieredGet();
|
||||
if (kind == WeakCellKind::Symbol)
|
||||
return !symbol.unbarrieredGet();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool sameValue(HandleValue value) const {
|
||||
if (kind == WeakCellKind::Object)
|
||||
return value.isObject() && object.get() == NormalizeWeakObject(&value.toObject());
|
||||
if (kind == WeakCellKind::Symbol)
|
||||
return value.isSymbol() && symbol.get() == value.toSymbol();
|
||||
return false;
|
||||
}
|
||||
|
||||
void trace(JSTracer* trc, const char* name) {
|
||||
if (kind == WeakCellKind::Object) {
|
||||
JSObject* target = object.unbarrieredGet();
|
||||
if (!target)
|
||||
return;
|
||||
|
||||
if (IsInsideNursery(target)) {
|
||||
TraceManuallyBarrieredEdge(trc, object.unsafeGet(), name);
|
||||
} else {
|
||||
if (trc->isMarkingTracer() && !target->asTenured().zone()->isCollecting())
|
||||
return;
|
||||
TraceWeakEdge(trc, &object, name);
|
||||
}
|
||||
} else if (kind == WeakCellKind::Symbol) {
|
||||
JS::Symbol* target = symbol.unbarrieredGet();
|
||||
if (target) {
|
||||
if (trc->isMarkingTracer() && !target->asTenured().zone()->isCollecting())
|
||||
return;
|
||||
TraceWeakEdge(trc, &symbol, name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void traceIfZoneIsCollecting(JSTracer* trc, const char* name) {
|
||||
if (kind == WeakCellKind::Object) {
|
||||
JSObject* target = object.unbarrieredGet();
|
||||
if (!target)
|
||||
return;
|
||||
|
||||
if (IsInsideNursery(target)) {
|
||||
TraceManuallyBarrieredEdge(trc, object.unsafeGet(), name);
|
||||
} else if (target->asTenured().zone()->isCollecting()) {
|
||||
TraceWeakEdge(trc, &object, name);
|
||||
}
|
||||
} else if (kind == WeakCellKind::Symbol) {
|
||||
JS::Symbol* target = symbol.unbarrieredGet();
|
||||
if (target && target->asTenured().zone()->isCollecting())
|
||||
TraceWeakEdge(trc, &symbol, name);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct FinalizationRecord {
|
||||
WeakCell target;
|
||||
JS::Heap<Value> heldValue;
|
||||
WeakCell unregisterToken;
|
||||
bool active;
|
||||
bool queued;
|
||||
|
||||
FinalizationRecord(HandleValue targetValue, HandleValue heldValue,
|
||||
HandleValue unregisterTokenValue)
|
||||
: heldValue(heldValue),
|
||||
active(true),
|
||||
queued(false)
|
||||
{
|
||||
if (targetValue.isObject())
|
||||
target = WeakCell(NormalizeWeakObject(&targetValue.toObject()));
|
||||
else
|
||||
target = WeakCell(targetValue.toSymbol());
|
||||
|
||||
if (unregisterTokenValue.isObject())
|
||||
unregisterToken = WeakCell(NormalizeWeakObject(&unregisterTokenValue.toObject()));
|
||||
else if (unregisterTokenValue.isSymbol())
|
||||
unregisterToken = WeakCell(unregisterTokenValue.toSymbol());
|
||||
}
|
||||
|
||||
void trace(JSTracer* trc) {
|
||||
if (active || queued)
|
||||
JS::TraceEdge(trc, &heldValue, "FinalizationRegistry held value");
|
||||
|
||||
if (active) {
|
||||
target.trace(trc, "FinalizationRegistry target");
|
||||
if (!unregisterToken.isEmpty())
|
||||
unregisterToken.trace(trc, "FinalizationRegistry unregister token");
|
||||
}
|
||||
}
|
||||
|
||||
void traceWeakEdgesForCollectedZones(JSTracer* trc) {
|
||||
if (!active)
|
||||
return;
|
||||
|
||||
target.traceIfZoneIsCollecting(trc, "FinalizationRegistry target");
|
||||
if (!unregisterToken.isEmpty())
|
||||
unregisterToken.traceIfZoneIsCollecting(trc,
|
||||
"FinalizationRegistry unregister token");
|
||||
}
|
||||
|
||||
bool targetIsDead() const {
|
||||
return active && target.isDead();
|
||||
}
|
||||
|
||||
bool matchesToken(HandleValue token) const {
|
||||
return active && !queued && !unregisterToken.isEmpty() &&
|
||||
unregisterToken.sameValue(token);
|
||||
}
|
||||
|
||||
void queueForCleanup() {
|
||||
MOZ_ASSERT(active);
|
||||
MOZ_ASSERT(!queued);
|
||||
active = false;
|
||||
queued = true;
|
||||
target.clear();
|
||||
unregisterToken.clear();
|
||||
}
|
||||
|
||||
void clear() {
|
||||
active = false;
|
||||
queued = false;
|
||||
target.clear();
|
||||
unregisterToken.clear();
|
||||
heldValue.setUndefined();
|
||||
}
|
||||
};
|
||||
|
||||
using FinalizationRecordVector = Vector<FinalizationRecord*, 0, SystemAllocPolicy>;
|
||||
|
||||
struct FinalizationRegistryObject::Data {
|
||||
JS::Heap<JSObject*> cleanupCallback;
|
||||
JS::Heap<JSObject*> cleanupJob;
|
||||
FinalizationRecordVector records;
|
||||
FinalizationRecordVector cleanupQueue;
|
||||
bool queuedForCleanup;
|
||||
|
||||
explicit Data(JSObject* cleanupCallback)
|
||||
: cleanupCallback(cleanupCallback),
|
||||
cleanupJob(nullptr),
|
||||
records(SystemAllocPolicy()),
|
||||
cleanupQueue(SystemAllocPolicy()),
|
||||
queuedForCleanup(false)
|
||||
{}
|
||||
|
||||
~Data() {
|
||||
for (size_t i = 0; i < records.length(); i++)
|
||||
js_delete(records[i]);
|
||||
}
|
||||
|
||||
void trace(JSTracer* trc) {
|
||||
JS::TraceEdge(trc, &cleanupCallback, "FinalizationRegistry cleanup callback");
|
||||
if (cleanupJob.unbarrieredGet())
|
||||
JS::TraceEdge(trc, &cleanupJob, "FinalizationRegistry cleanup job");
|
||||
for (size_t i = 0; i < records.length(); i++)
|
||||
records[i]->trace(trc);
|
||||
}
|
||||
|
||||
bool appendRecord(FinalizationRecord* record) {
|
||||
return records.append(record);
|
||||
}
|
||||
|
||||
bool appendCleanupRecord(FinalizationRecord* record) {
|
||||
return cleanupQueue.append(record);
|
||||
}
|
||||
|
||||
void compactRecords() {
|
||||
for (size_t i = 0; i < records.length();) {
|
||||
FinalizationRecord* record = records[i];
|
||||
if (!record->active && !record->queued) {
|
||||
js_delete(record);
|
||||
records.erase(records.begin() + i);
|
||||
continue;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
void unregister(HandleValue token, bool* removed) {
|
||||
*removed = false;
|
||||
for (size_t i = 0; i < records.length(); i++) {
|
||||
FinalizationRecord* record = records[i];
|
||||
if (record->matchesToken(token)) {
|
||||
record->clear();
|
||||
*removed = true;
|
||||
}
|
||||
}
|
||||
compactRecords();
|
||||
}
|
||||
};
|
||||
|
||||
static FinalizationRegistryObject::Data*
|
||||
GetData(JSObject* obj)
|
||||
{
|
||||
return obj->as<FinalizationRegistryObject>().getData();
|
||||
}
|
||||
|
||||
static MOZ_ALWAYS_INLINE bool
|
||||
IsFinalizationRegistry(HandleValue v)
|
||||
{
|
||||
return v.isObject() && v.toObject().is<FinalizationRegistryObject>();
|
||||
}
|
||||
|
||||
static bool
|
||||
FinalizationRegistry_register_impl(JSContext* cx, const CallArgs& args)
|
||||
{
|
||||
MOZ_ASSERT(IsFinalizationRegistry(args.thisv()));
|
||||
|
||||
if (!CanBeHeldWeakly(args.get(0))) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_BAD_FINALIZATION_REGISTRY_TARGET);
|
||||
return false;
|
||||
}
|
||||
|
||||
RootedValue target(cx, args[0]);
|
||||
RootedValue heldValue(cx, args.get(1));
|
||||
|
||||
bool same;
|
||||
if (!SameValue(cx, target, heldValue, &same))
|
||||
return false;
|
||||
if (same) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_BAD_FINALIZATION_REGISTRY_HELD_VALUE);
|
||||
return false;
|
||||
}
|
||||
|
||||
RootedValue unregisterToken(cx, args.get(2));
|
||||
if (!CanBeHeldWeakly(unregisterToken)) {
|
||||
if (!unregisterToken.isUndefined()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_BAD_FINALIZATION_REGISTRY_TOKEN);
|
||||
return false;
|
||||
}
|
||||
unregisterToken.setUndefined();
|
||||
}
|
||||
|
||||
FinalizationRecord* record = cx->new_<FinalizationRecord>(target, heldValue, unregisterToken);
|
||||
if (!record)
|
||||
return false;
|
||||
|
||||
auto* data = GetData(&args.thisv().toObject());
|
||||
if (!data->appendRecord(record)) {
|
||||
js_delete(record);
|
||||
ReportOutOfMemory(cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
FinalizationRegistry_unregister_impl(JSContext* cx, const CallArgs& args)
|
||||
{
|
||||
MOZ_ASSERT(IsFinalizationRegistry(args.thisv()));
|
||||
|
||||
RootedValue unregisterToken(cx, args.get(0));
|
||||
if (!CanBeHeldWeakly(unregisterToken)) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_BAD_FINALIZATION_REGISTRY_TOKEN);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool removed = false;
|
||||
auto* data = GetData(&args.thisv().toObject());
|
||||
data->unregister(unregisterToken, &removed);
|
||||
|
||||
args.rval().setBoolean(removed);
|
||||
return true;
|
||||
}
|
||||
|
||||
const JSPropertySpec FinalizationRegistryObject::properties[] = {
|
||||
JS_PS_END
|
||||
};
|
||||
|
||||
const JSFunctionSpec FinalizationRegistryObject::methods[] = {
|
||||
JS_FN("register", FinalizationRegistryObject::register_, 2, 0),
|
||||
JS_FN("unregister", FinalizationRegistryObject::unregister, 1, 0),
|
||||
JS_FS_END
|
||||
};
|
||||
|
||||
static JSObject*
|
||||
InitFinalizationRegistryClass(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, FinalizationRegistryObject::construct,
|
||||
ClassName(JSProto_FinalizationRegistry, cx), 1));
|
||||
if (!ctor)
|
||||
return nullptr;
|
||||
|
||||
if (!LinkConstructorAndPrototype(cx, ctor, proto))
|
||||
return nullptr;
|
||||
|
||||
if (defineMembers) {
|
||||
if (!DefinePropertiesAndFunctions(cx, proto, FinalizationRegistryObject::properties,
|
||||
FinalizationRegistryObject::methods)) {
|
||||
return nullptr;
|
||||
}
|
||||
if (!DefineToStringTag(cx, proto, cx->names().FinalizationRegistry))
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!GlobalObject::initBuiltinConstructor(cx, global, JSProto_FinalizationRegistry, ctor, proto))
|
||||
return nullptr;
|
||||
return proto;
|
||||
}
|
||||
|
||||
/* static */ FinalizationRegistryObject*
|
||||
FinalizationRegistryObject::create(JSContext* cx, HandleObject cleanupCallback,
|
||||
HandleObject proto /* = nullptr */)
|
||||
{
|
||||
Rooted<FinalizationRegistryObject*> obj(cx,
|
||||
NewObjectWithClassProto<FinalizationRegistryObject>(cx, proto));
|
||||
if (!obj)
|
||||
return nullptr;
|
||||
|
||||
Data* data = cx->new_<Data>(cleanupCallback);
|
||||
if (!data)
|
||||
return nullptr;
|
||||
|
||||
obj->setPrivate(data);
|
||||
|
||||
RootedAtom funName(cx, cx->names().empty);
|
||||
RootedFunction cleanupJob(cx, NewNativeFunction(cx, FinalizationRegistryObject::cleanupJob, 0,
|
||||
funName, gc::AllocKind::FUNCTION_EXTENDED,
|
||||
GenericObject));
|
||||
if (!cleanupJob)
|
||||
return nullptr;
|
||||
|
||||
cleanupJob->setExtendedSlot(0, ObjectValue(*obj));
|
||||
data->cleanupJob = cleanupJob;
|
||||
|
||||
if (!cx->zone()->finalizationRegistries.append(WeakRef<JSObject*>(obj))) {
|
||||
ReportOutOfMemory(cx);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
FinalizationRegistryObject::construct(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
if (!ThrowIfNotConstructing(cx, args, "FinalizationRegistry"))
|
||||
return false;
|
||||
|
||||
RootedValue cleanupCallbackValue(cx, args.get(0));
|
||||
RootedObject cleanupCallback(cx, ValueToCallable(cx, cleanupCallbackValue, 0, NO_CONSTRUCT));
|
||||
if (!cleanupCallback)
|
||||
return false;
|
||||
|
||||
RootedObject proto(cx);
|
||||
RootedObject newTarget(cx, &args.newTarget().toObject());
|
||||
if (!GetPrototypeFromFinalizationRegistryConstructor(cx, newTarget, &proto))
|
||||
return false;
|
||||
|
||||
Rooted<FinalizationRegistryObject*> obj(cx,
|
||||
FinalizationRegistryObject::create(cx, cleanupCallback, proto));
|
||||
if (!obj)
|
||||
return false;
|
||||
|
||||
args.rval().setObject(*obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
FinalizationRegistryObject::register_(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
return CallNonGenericMethod<IsFinalizationRegistry, FinalizationRegistry_register_impl>(cx, args);
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
FinalizationRegistryObject::unregister(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
return CallNonGenericMethod<IsFinalizationRegistry, FinalizationRegistry_unregister_impl>(cx, args);
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
FinalizationRegistryObject::cleanupJob(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
RootedFunction job(cx, &args.callee().as<JSFunction>());
|
||||
Rooted<FinalizationRegistryObject*> registry(cx,
|
||||
&job->getExtendedSlot(0).toObject().as<FinalizationRegistryObject>());
|
||||
Data* data = registry->getData();
|
||||
if (!data) {
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
data->queuedForCleanup = false;
|
||||
|
||||
RootedValue callback(cx, ObjectValue(*data->cleanupCallback.get()));
|
||||
RootedValue heldValue(cx);
|
||||
RootedValue ignored(cx);
|
||||
RootedValue undefined(cx, UndefinedValue());
|
||||
|
||||
while (data->cleanupQueue.length() > 0) {
|
||||
FinalizationRecord* record = data->cleanupQueue[0];
|
||||
data->cleanupQueue.erase(data->cleanupQueue.begin());
|
||||
|
||||
if (!record->queued) {
|
||||
data->compactRecords();
|
||||
continue;
|
||||
}
|
||||
|
||||
heldValue.set(record->heldValue.get());
|
||||
record->clear();
|
||||
data->compactRecords();
|
||||
|
||||
if (!Call(cx, callback, undefined, heldValue, &ignored))
|
||||
return false;
|
||||
}
|
||||
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
FinalizationRegistryObject::trace(JSTracer* trc, JSObject* obj)
|
||||
{
|
||||
if (Data* data = GetData(obj))
|
||||
data->trace(trc);
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
FinalizationRegistryObject::finalize(FreeOp* fop, JSObject* obj)
|
||||
{
|
||||
if (Data* data = GetData(obj))
|
||||
fop->delete_(data);
|
||||
}
|
||||
|
||||
void
|
||||
FinalizationRegistryObject::traceWeakEdgesForCollectedZones(JSTracer* trc)
|
||||
{
|
||||
Data* data = getData();
|
||||
if (!data)
|
||||
return;
|
||||
|
||||
for (size_t i = 0; i < data->records.length(); i++)
|
||||
data->records[i]->traceWeakEdgesForCollectedZones(trc);
|
||||
}
|
||||
|
||||
void
|
||||
FinalizationRegistryObject::sweepAfterGC(JSRuntime* rt)
|
||||
{
|
||||
Data* data = getData();
|
||||
if (!data)
|
||||
return;
|
||||
|
||||
bool needsCleanupJob = false;
|
||||
for (size_t i = 0; i < data->records.length(); i++) {
|
||||
FinalizationRecord* record = data->records[i];
|
||||
if (!record->targetIsDead())
|
||||
continue;
|
||||
|
||||
record->queueForCleanup();
|
||||
if (!data->appendCleanupRecord(record)) {
|
||||
AutoEnterOOMUnsafeRegion oomUnsafe;
|
||||
oomUnsafe.crash("queueing FinalizationRegistry cleanup record");
|
||||
}
|
||||
needsCleanupJob = true;
|
||||
}
|
||||
|
||||
if (needsCleanupJob && !data->queuedForCleanup) {
|
||||
JSContext* cx = rt->contextFromMainThread();
|
||||
RootedObject cleanupJob(cx, data->cleanupJob.unbarrieredGet());
|
||||
if (!rt->enqueueFinalizationRegistryCleanupJob(cx, cleanupJob)) {
|
||||
AutoEnterOOMUnsafeRegion oomUnsafe;
|
||||
oomUnsafe.crash("queueing FinalizationRegistry cleanup job");
|
||||
}
|
||||
data->queuedForCleanup = true;
|
||||
}
|
||||
|
||||
data->compactRecords();
|
||||
}
|
||||
|
||||
static const ClassOps FinalizationRegistryObjectClassOps = {
|
||||
nullptr, /* addProperty */
|
||||
nullptr, /* delProperty */
|
||||
nullptr, /* getProperty */
|
||||
nullptr, /* setProperty */
|
||||
nullptr, /* enumerate */
|
||||
nullptr, /* resolve */
|
||||
nullptr, /* mayResolve */
|
||||
FinalizationRegistryObject::finalize,
|
||||
nullptr, /* call */
|
||||
nullptr, /* hasInstance */
|
||||
nullptr, /* construct */
|
||||
FinalizationRegistryObject::trace
|
||||
};
|
||||
|
||||
const Class FinalizationRegistryObject::class_ = {
|
||||
"FinalizationRegistry",
|
||||
JSCLASS_HAS_PRIVATE |
|
||||
JSCLASS_HAS_CACHED_PROTO(JSProto_FinalizationRegistry) |
|
||||
JSCLASS_FOREGROUND_FINALIZE,
|
||||
&FinalizationRegistryObjectClassOps
|
||||
};
|
||||
|
||||
/* static */ JSObject*
|
||||
FinalizationRegistryObject::initClass(JSContext* cx, HandleObject obj)
|
||||
{
|
||||
return ::InitFinalizationRegistryClass(cx, obj, true);
|
||||
}
|
||||
|
||||
JSObject*
|
||||
js::InitFinalizationRegistryClass(JSContext* cx, HandleObject obj)
|
||||
{
|
||||
return FinalizationRegistryObject::initClass(cx, obj);
|
||||
}
|
||||
|
||||
JSObject*
|
||||
js::InitBareFinalizationRegistryCtor(JSContext* cx, HandleObject obj)
|
||||
{
|
||||
return ::InitFinalizationRegistryClass(cx, obj, false);
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
/* -*- 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_FinalizationRegistryObject_h
|
||||
#define builtin_FinalizationRegistryObject_h
|
||||
|
||||
#include "gc/Barrier.h"
|
||||
#include "vm/NativeObject.h"
|
||||
|
||||
namespace js {
|
||||
|
||||
class FinalizationRegistryObject : public NativeObject
|
||||
{
|
||||
public:
|
||||
struct Data;
|
||||
|
||||
static const Class class_;
|
||||
|
||||
static JSObject* initClass(JSContext* cx, HandleObject obj);
|
||||
static FinalizationRegistryObject* create(JSContext* cx, HandleObject cleanupCallback,
|
||||
HandleObject proto = nullptr);
|
||||
|
||||
static void trace(JSTracer* trc, JSObject* obj);
|
||||
static void finalize(FreeOp* fop, JSObject* obj);
|
||||
void traceWeakEdgesForCollectedZones(JSTracer* trc);
|
||||
[[nodiscard]] static bool construct(JSContext* cx, unsigned argc, Value* vp);
|
||||
[[nodiscard]] static bool register_(JSContext* cx, unsigned argc, Value* vp);
|
||||
[[nodiscard]] static bool unregister(JSContext* cx, unsigned argc, Value* vp);
|
||||
[[nodiscard]] static bool cleanupJob(JSContext* cx, unsigned argc, Value* vp);
|
||||
|
||||
void sweepAfterGC(JSRuntime* rt);
|
||||
|
||||
Data* getData() const {
|
||||
return static_cast<Data*>(getPrivate());
|
||||
}
|
||||
|
||||
static const JSPropertySpec properties[];
|
||||
static const JSFunctionSpec methods[];
|
||||
};
|
||||
|
||||
extern JSObject*
|
||||
InitFinalizationRegistryClass(JSContext* cx, HandleObject obj);
|
||||
|
||||
extern JSObject*
|
||||
InitBareFinalizationRegistryCtor(JSContext* cx, HandleObject obj);
|
||||
|
||||
} // namespace js
|
||||
|
||||
#endif /* builtin_FinalizationRegistryObject_h */
|
||||
@@ -151,8 +151,8 @@ WeakRefObject::create(JSContext* cx, HandleValue target, HandleObject proto /* =
|
||||
return obj;
|
||||
}
|
||||
|
||||
bool
|
||||
js::CanBeHeldWeakly(HandleValue target)
|
||||
static bool
|
||||
CanBeHeldWeakly(HandleValue target)
|
||||
{
|
||||
if (target.isObject())
|
||||
return true;
|
||||
|
||||
@@ -11,8 +11,6 @@
|
||||
|
||||
namespace js {
|
||||
|
||||
bool CanBeHeldWeakly(HandleValue target);
|
||||
|
||||
class WeakRefObject : public NativeObject
|
||||
{
|
||||
public:
|
||||
|
||||
@@ -947,8 +947,6 @@ class GCRuntime
|
||||
IncrementalProgress drainMarkStack(SliceBudget& sliceBudget, gcstats::Phase phase);
|
||||
template <class CompartmentIterT> void markWeakReferences(gcstats::Phase phase);
|
||||
void markWeakReferencesInCurrentGroup(gcstats::Phase phase);
|
||||
template <class ZoneIterT> void traceFinalizationRegistryWeakRefs();
|
||||
void traceFinalizationRegistryWeakRefsInCurrentGroup();
|
||||
template <class ZoneIterT, class CompartmentIterT> void markGrayReferences(gcstats::Phase phase);
|
||||
void markBufferedGrayRoots(JS::Zone* zone);
|
||||
void markGrayReferencesInCurrentGroup(gcstats::Phase phase);
|
||||
@@ -961,7 +959,6 @@ class GCRuntime
|
||||
void getNextZoneGroup();
|
||||
void endMarkingZoneGroup();
|
||||
void beginSweepingZoneGroup(AutoLockForExclusiveAccess& lock);
|
||||
void sweepFinalizationRegistries();
|
||||
bool shouldReleaseObservedTypes();
|
||||
void endSweepingZoneGroup();
|
||||
IncrementalProgress performSweepActions(SliceBudget& sliceBudget, AutoLockForExclusiveAccess& lock);
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
#include "jscntxt.h"
|
||||
|
||||
#include "ds/SplayTree.h"
|
||||
#include "gc/Barrier.h"
|
||||
#include "gc/FindSCCs.h"
|
||||
#include "gc/GCRuntime.h"
|
||||
#include "js/GCHashTable.h"
|
||||
@@ -326,10 +325,6 @@ struct Zone : public JS::shadow::Zone,
|
||||
using WeakEdges = js::Vector<js::gc::TenuredCell**, 0, js::SystemAllocPolicy>;
|
||||
WeakEdges gcWeakRefs;
|
||||
|
||||
// FinalizationRegistry objects with weakly-held target cells in this zone.
|
||||
using FinalizationRegistryVector = js::Vector<js::WeakRef<JSObject*>, 0, js::SystemAllocPolicy>;
|
||||
FinalizationRegistryVector finalizationRegistries;
|
||||
|
||||
// List of non-ephemeron weak containers to sweep during beginSweepingZoneGroup.
|
||||
mozilla::LinkedList<WeakCache<void*>> weakCaches_;
|
||||
void registerWeakCache(WeakCache<void*>* cachep) {
|
||||
|
||||
@@ -82,9 +82,6 @@ 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_BAD_FINALIZATION_REGISTRY_TARGET, 0, JSEXN_TYPEERR, "FinalizationRegistry target must be an object or a non-registered symbol")
|
||||
MSG_DEF(JSMSG_BAD_FINALIZATION_REGISTRY_HELD_VALUE, 0, JSEXN_TYPEERR, "FinalizationRegistry held value must not be the target")
|
||||
MSG_DEF(JSMSG_BAD_FINALIZATION_REGISTRY_TOKEN, 0, JSEXN_TYPEERR, "FinalizationRegistry unregister token 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")
|
||||
|
||||
@@ -211,7 +211,6 @@
|
||||
# include "jswin.h"
|
||||
#endif
|
||||
|
||||
#include "builtin/FinalizationRegistryObject.h"
|
||||
#include "gc/FindSCCs.h"
|
||||
#include "gc/GCInternals.h"
|
||||
#include "gc/GCTrace.h"
|
||||
@@ -4030,8 +4029,6 @@ GCRuntime::markWeakReferences(gcstats::Phase phase)
|
||||
}
|
||||
MOZ_ASSERT(marker.isDrained());
|
||||
|
||||
traceFinalizationRegistryWeakRefs<ZoneIterT>();
|
||||
|
||||
marker.leaveWeakMarkingMode();
|
||||
}
|
||||
|
||||
@@ -4041,33 +4038,6 @@ GCRuntime::markWeakReferencesInCurrentGroup(gcstats::Phase phase)
|
||||
markWeakReferences<GCZoneGroupIter>(phase);
|
||||
}
|
||||
|
||||
template <class ZoneIterT>
|
||||
void
|
||||
GCRuntime::traceFinalizationRegistryWeakRefs()
|
||||
{
|
||||
for (ZoneIterT zone(rt); !zone.done(); zone.next()) {
|
||||
for (size_t i = 0; i < zone->finalizationRegistries.length(); i++) {
|
||||
WeakRef<JSObject*>& registry = zone->finalizationRegistries[i];
|
||||
if (registry.unbarrieredGet())
|
||||
TraceWeakEdge(&marker, ®istry, "FinalizationRegistry registry");
|
||||
}
|
||||
}
|
||||
|
||||
for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) {
|
||||
for (size_t i = 0; i < zone->finalizationRegistries.length(); i++) {
|
||||
JSObject* registry = zone->finalizationRegistries[i].unbarrieredGet();
|
||||
if (registry && registry->is<FinalizationRegistryObject>())
|
||||
registry->as<FinalizationRegistryObject>().traceWeakEdgesForCollectedZones(&marker);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
GCRuntime::traceFinalizationRegistryWeakRefsInCurrentGroup()
|
||||
{
|
||||
traceFinalizationRegistryWeakRefs<GCZoneGroupIter>();
|
||||
}
|
||||
|
||||
template <class ZoneIterT, class CompartmentIterT>
|
||||
void
|
||||
GCRuntime::markGrayReferences(gcstats::Phase phase)
|
||||
@@ -4779,8 +4749,6 @@ GCRuntime::beginSweepingZoneGroup(AutoLockForExclusiveAccess& lock)
|
||||
oomUnsafe.crash("clearing weak keys in beginSweepingZoneGroup()");
|
||||
}
|
||||
|
||||
sweepFinalizationRegistries();
|
||||
|
||||
{
|
||||
gcstats::AutoPhase ap(stats, gcstats::PHASE_FINALIZE_START);
|
||||
callFinalizeCallbacks(&fop, JSFINALIZE_GROUP_START);
|
||||
@@ -4936,23 +4904,6 @@ GCRuntime::beginSweepingZoneGroup(AutoLockForExclusiveAccess& lock)
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
GCRuntime::sweepFinalizationRegistries()
|
||||
{
|
||||
for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) {
|
||||
for (size_t i = 0; i < zone->finalizationRegistries.length();) {
|
||||
JSObject* obj = zone->finalizationRegistries[i].unbarrieredGet();
|
||||
if (!obj || !obj->is<FinalizationRegistryObject>()) {
|
||||
zone->finalizationRegistries.erase(zone->finalizationRegistries.begin() + i);
|
||||
continue;
|
||||
}
|
||||
|
||||
obj->as<FinalizationRegistryObject>().sweepAfterGC(rt);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
GCRuntime::endSweepingZoneGroup()
|
||||
{
|
||||
@@ -6136,13 +6087,6 @@ GCRuntime::collect(bool nonincrementalByAPI, SliceBudget budget, JS::gcreason::R
|
||||
repeat = (poked && cleanUpEverything) || wasReset || repeatForDeadZone;
|
||||
} while (repeat);
|
||||
|
||||
if (rt->isBeingDestroyed()) {
|
||||
rt->clearFinalizationRegistryCleanupJobs();
|
||||
} else if (!rt->drainFinalizationRegistryCleanupJobs(rt->contextFromMainThread())) {
|
||||
AutoEnterOOMUnsafeRegion oomUnsafe;
|
||||
oomUnsafe.crash("draining FinalizationRegistry cleanup jobs");
|
||||
}
|
||||
|
||||
if (reason == JS::gcreason::COMPARTMENT_REVIVED)
|
||||
maybeDoCycleCollection();
|
||||
}
|
||||
|
||||
@@ -97,7 +97,6 @@
|
||||
IF_SAB(real,imaginary)(SharedArrayBuffer, InitViaClassSpec, OCLASP(SharedArrayBuffer)) \
|
||||
IF_INTL(real,imaginary) (Intl, InitIntlClass, CLASP(Intl)) \
|
||||
IF_BDATA(real,imaginary)(TypedObject, InitTypedObjectModuleObject, OCLASP(TypedObjectModule)) \
|
||||
real(FinalizationRegistry, InitFinalizationRegistryClass, OCLASP(FinalizationRegistry)) \
|
||||
real(Reflect, InitReflect, nullptr) \
|
||||
real(WeakSet, InitWeakSetClass, OCLASP(WeakSet)) \
|
||||
real(TypedArray, InitViaClassSpec, &js::TypedArrayObject::sharedTypedArrayPrototypeClass) \
|
||||
|
||||
@@ -120,7 +120,6 @@ EXPORTS.js += [
|
||||
main_deunified_sources = [
|
||||
'builtin/AtomicsObject.cpp',
|
||||
'builtin/Eval.cpp',
|
||||
'builtin/FinalizationRegistryObject.cpp',
|
||||
'builtin/intl/Collator.cpp',
|
||||
'builtin/intl/CommonFunctions.cpp',
|
||||
'builtin/intl/DateTimeFormat.cpp',
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
#include "builtin/AtomicsObject.h"
|
||||
#include "builtin/BigInt.h"
|
||||
#include "builtin/Eval.h"
|
||||
#include "builtin/FinalizationRegistryObject.h"
|
||||
#include "builtin/MapObject.h"
|
||||
#include "builtin/ModuleObject.h"
|
||||
#include "builtin/Object.h"
|
||||
@@ -539,7 +538,6 @@ GlobalObject::initSelfHostingBuiltins(JSContext* cx, Handle<GlobalObject*> globa
|
||||
InitBareBuiltinCtor(cx, global, JSProto_Uint8Array) &&
|
||||
InitBareBuiltinCtor(cx, global, JSProto_Int32Array) &&
|
||||
InitBareSymbolCtor(cx, global) &&
|
||||
InitBareFinalizationRegistryCtor(cx, global) &&
|
||||
InitBareWeakMapCtor(cx, global) &&
|
||||
InitBareWeakRefCtor(cx, global) &&
|
||||
InitStopIterationClass(cx, global) &&
|
||||
|
||||
@@ -195,7 +195,6 @@ JSRuntime::JSRuntime(JSRuntime* parentRuntime)
|
||||
#endif
|
||||
scriptAndCountsVector(nullptr),
|
||||
weakRefKeptObjects(nullptr),
|
||||
finalizationRegistryCleanupJobs(nullptr),
|
||||
lcovOutput(),
|
||||
NaNValue(DoubleNaNValue()),
|
||||
negativeInfinityValue(DoubleValue(NegativeInfinity<double>())),
|
||||
@@ -375,7 +374,6 @@ JSRuntime::destroyRuntime()
|
||||
MOZ_ASSERT(childRuntimeCount == 0);
|
||||
|
||||
clearWeakRefKeptObjects();
|
||||
clearFinalizationRegistryCleanupJobs();
|
||||
|
||||
fx.destroyInstance();
|
||||
|
||||
@@ -520,74 +518,6 @@ JSRuntime::clearWeakRefKeptObjects()
|
||||
weakRefKeptObjects = nullptr;
|
||||
}
|
||||
|
||||
bool
|
||||
JSRuntime::enqueueFinalizationRegistryCleanupJob(JSContext* cx, JS::HandleObject job)
|
||||
{
|
||||
MOZ_ASSERT(cx->runtime() == this);
|
||||
MOZ_ASSERT(job);
|
||||
MOZ_ASSERT(job->is<JSFunction>());
|
||||
MOZ_ASSERT(isHeapBusy());
|
||||
|
||||
if (!finalizationRegistryCleanupJobs) {
|
||||
auto* cleanupJobs =
|
||||
cx->new_<JS::PersistentRooted<js::FinalizationRegistryCleanupJobVector>>(
|
||||
cx, js::FinalizationRegistryCleanupJobVector(js::SystemAllocPolicy()));
|
||||
if (!cleanupJobs)
|
||||
return false;
|
||||
|
||||
finalizationRegistryCleanupJobs = cleanupJobs;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < finalizationRegistryCleanupJobs->length(); i++) {
|
||||
if ((*finalizationRegistryCleanupJobs)[i] == job)
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!finalizationRegistryCleanupJobs->append(job)) {
|
||||
ReportOutOfMemory(cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
JSRuntime::drainFinalizationRegistryCleanupJobs(JSContext* cx)
|
||||
{
|
||||
MOZ_ASSERT(cx->runtime() == this);
|
||||
MOZ_ASSERT(!isHeapBusy());
|
||||
|
||||
if (!finalizationRegistryCleanupJobs)
|
||||
return true;
|
||||
|
||||
if (!enqueuePromiseJobCallback) {
|
||||
finalizationRegistryCleanupJobs->clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t length = finalizationRegistryCleanupJobs->length();
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
RootedFunction job(cx, &(*finalizationRegistryCleanupJobs)[i]->as<JSFunction>());
|
||||
if (!enqueuePromiseJob(cx, job, nullptr, nullptr))
|
||||
return false;
|
||||
}
|
||||
|
||||
finalizationRegistryCleanupJobs->clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
JSRuntime::clearFinalizationRegistryCleanupJobs()
|
||||
{
|
||||
MOZ_ASSERT(!isHeapBusy());
|
||||
|
||||
if (!finalizationRegistryCleanupJobs)
|
||||
return;
|
||||
|
||||
defaultFreeOp()->delete_(finalizationRegistryCleanupJobs);
|
||||
finalizationRegistryCleanupJobs = nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
JSRuntime::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, JS::RuntimeSizes* rtSizes)
|
||||
{
|
||||
|
||||
@@ -361,7 +361,6 @@ class PerThreadData
|
||||
|
||||
using ScriptAndCountsVector = GCVector<ScriptAndCounts, 0, SystemAllocPolicy>;
|
||||
using WeakRefKeptObjectVector = JS::GCVector<JS::Value, 0, SystemAllocPolicy>;
|
||||
using FinalizationRegistryCleanupJobVector = JS::GCVector<JSObject*, 0, SystemAllocPolicy>;
|
||||
|
||||
class AutoLockForExclusiveAccess;
|
||||
} // namespace js
|
||||
@@ -895,13 +894,6 @@ struct JSRuntime : public JS::shadow::Runtime,
|
||||
[[nodiscard]] bool addWeakRefKeptObject(JSContext* cx, JS::HandleValue target);
|
||||
void clearWeakRefKeptObjects();
|
||||
|
||||
/* Cleanup jobs produced by FinalizationRegistry sweeping, enqueued after GC. */
|
||||
JS::PersistentRooted<js::FinalizationRegistryCleanupJobVector>* finalizationRegistryCleanupJobs;
|
||||
|
||||
[[nodiscard]] bool enqueueFinalizationRegistryCleanupJob(JSContext* cx, JS::HandleObject job);
|
||||
[[nodiscard]] bool drainFinalizationRegistryCleanupJobs(JSContext* cx);
|
||||
void clearFinalizationRegistryCleanupJobs();
|
||||
|
||||
/* Code coverage output. */
|
||||
js::coverage::LCovRuntime lcovOutput;
|
||||
|
||||
|
||||
@@ -1,108 +0,0 @@
|
||||
/* 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/. */
|
||||
|
||||
function assertThrowsTypeError(fn) {
|
||||
try {
|
||||
fn();
|
||||
} catch (e) {
|
||||
do_check_true(e instanceof TypeError);
|
||||
return;
|
||||
}
|
||||
do_throw("expected TypeError");
|
||||
}
|
||||
|
||||
add_task(function* test_finalization_registry_api_surface() {
|
||||
do_check_eq(typeof FinalizationRegistry, "function");
|
||||
do_check_eq(FinalizationRegistry.name, "FinalizationRegistry");
|
||||
do_check_eq(FinalizationRegistry.length, 1);
|
||||
|
||||
assertThrowsTypeError(() => FinalizationRegistry(function() {}));
|
||||
assertThrowsTypeError(() => new FinalizationRegistry(1));
|
||||
|
||||
let registry = new FinalizationRegistry(function() {});
|
||||
do_check_eq(Object.prototype.toString.call(registry), "[object FinalizationRegistry]");
|
||||
do_check_eq(typeof FinalizationRegistry.prototype.register, "function");
|
||||
do_check_eq(FinalizationRegistry.prototype.register.length, 2);
|
||||
do_check_eq(typeof FinalizationRegistry.prototype.unregister, "function");
|
||||
do_check_eq(FinalizationRegistry.prototype.unregister.length, 1);
|
||||
do_check_eq(FinalizationRegistry.prototype.cleanupSome, undefined);
|
||||
|
||||
let desc = Object.getOwnPropertyDescriptor(FinalizationRegistry.prototype,
|
||||
Symbol.toStringTag);
|
||||
do_check_eq(desc.value, "FinalizationRegistry");
|
||||
do_check_eq(desc.writable, false);
|
||||
do_check_eq(desc.enumerable, false);
|
||||
do_check_eq(desc.configurable, true);
|
||||
});
|
||||
|
||||
add_task(function* test_register_validation_and_unregister() {
|
||||
let registry = new FinalizationRegistry(function() {});
|
||||
let target = {};
|
||||
let token = {};
|
||||
|
||||
do_check_eq(registry.register(target, "held"), undefined);
|
||||
do_check_eq(registry.register({}, "held", token), undefined);
|
||||
do_check_eq(registry.unregister(token), true);
|
||||
do_check_eq(registry.unregister({}), false);
|
||||
|
||||
assertThrowsTypeError(() => registry.register(1, "held"));
|
||||
assertThrowsTypeError(() => registry.register(target, target));
|
||||
assertThrowsTypeError(() => registry.register(target, "held", 1));
|
||||
assertThrowsTypeError(() => registry.register(Symbol.for("registered"), "held"));
|
||||
assertThrowsTypeError(() => registry.unregister(undefined));
|
||||
|
||||
do_check_eq(registry.register(Symbol("target"), "held"), undefined);
|
||||
let symbolToken = Symbol("token");
|
||||
do_check_eq(registry.register({}, "held", symbolToken), undefined);
|
||||
do_check_eq(registry.unregister(symbolToken), true);
|
||||
});
|
||||
|
||||
add_task(function* test_proto_from_constructor_realm() {
|
||||
let other = new Components.utils.Sandbox("http://example.com", { freshZone: true });
|
||||
Components.utils.evalInSandbox("var newTarget = new Function();", other);
|
||||
|
||||
for (let proto of [undefined, null, true, "", Symbol(), 1]) {
|
||||
other.newTarget.prototype = proto;
|
||||
let registry = Reflect.construct(FinalizationRegistry, [function() {}],
|
||||
other.newTarget);
|
||||
do_check_true(Object.getPrototypeOf(registry) ===
|
||||
other.FinalizationRegistry.prototype);
|
||||
}
|
||||
});
|
||||
|
||||
add_task(function* test_cleanup_callback_after_gc() {
|
||||
let cleaned = [];
|
||||
let registry = new FinalizationRegistry(value => cleaned.push(value));
|
||||
let token = {};
|
||||
|
||||
(function() {
|
||||
let target = {};
|
||||
registry.register(target, "held", token);
|
||||
})();
|
||||
|
||||
for (let i = 0; i < 4 && cleaned.length == 0; i++) {
|
||||
Components.utils.forceGC();
|
||||
yield Promise.resolve();
|
||||
}
|
||||
|
||||
do_check_eq(cleaned.length, 1);
|
||||
do_check_eq(cleaned[0], "held");
|
||||
do_check_eq(registry.unregister(token), false);
|
||||
});
|
||||
|
||||
add_task(function* test_unregister_prevents_cleanup() {
|
||||
let cleaned = [];
|
||||
let registry = new FinalizationRegistry(value => cleaned.push(value));
|
||||
let token = {};
|
||||
|
||||
(function() {
|
||||
let target = {};
|
||||
registry.register(target, "held", token);
|
||||
})();
|
||||
|
||||
do_check_eq(registry.unregister(token), true);
|
||||
Components.utils.forceGC();
|
||||
yield Promise.resolve();
|
||||
do_check_eq(cleaned.length, 0);
|
||||
});
|
||||
@@ -71,7 +71,6 @@ support-files =
|
||||
[test_import_fail.js]
|
||||
[test_interposition.js]
|
||||
[test_isModuleLoaded.js]
|
||||
[test_finalization_registry.js]
|
||||
[test_js_weak_references.js]
|
||||
[test_weakref.js]
|
||||
[test_onGarbageCollection-01.js]
|
||||
|
||||
Reference in New Issue
Block a user