/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80: * 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/. */ // Implementation of JS FinalizationRegistry objects. #include "builtin/FinalizationRegistryObject.h" #include "mozilla/ScopeExit.h" #include "jsapi.h" #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* #include "vm/GlobalObject.h" #include "vm/Interpreter.h" #include "vm/WellKnownAtom.h" // js_*_str #include "gc/GCContext-inl.h" #include "vm/JSObject-inl.h" #include "vm/NativeObject-inl.h" using namespace js; /////////////////////////////////////////////////////////////////////////// // FinalizationRecordObject const JSClass FinalizationRecordObject::class_ = { "FinalizationRecord", JSCLASS_HAS_RESERVED_SLOTS(SlotCount)}; /* static */ FinalizationRecordObject* FinalizationRecordObject::create( JSContext* cx, HandleFinalizationQueueObject queue, HandleValue heldValue) { MOZ_ASSERT(queue); auto record = NewObjectWithGivenProto(cx, nullptr); if (!record) { return nullptr; } MOZ_ASSERT(queue->compartment() == record->compartment()); record->initReservedSlot(QueueSlot, ObjectValue(*queue)); record->initReservedSlot(HeldValueSlot, heldValue); record->initReservedSlot(InMapSlot, BooleanValue(false)); return record; } FinalizationQueueObject* FinalizationRecordObject::queue() const { Value value = getReservedSlot(QueueSlot); if (value.isUndefined()) { return nullptr; } return &value.toObject().as(); } Value FinalizationRecordObject::heldValue() const { return getReservedSlot(HeldValueSlot); } bool FinalizationRecordObject::isRegistered() const { MOZ_ASSERT_IF(!queue(), heldValue().isUndefined()); return queue(); } bool FinalizationRecordObject::isInRecordMap() const { return getReservedSlot(InMapSlot).toBoolean(); } void FinalizationRecordObject::setInRecordMap(bool newValue) { MOZ_ASSERT(newValue != isInRecordMap()); setReservedSlot(InMapSlot, BooleanValue(newValue)); } void FinalizationRecordObject::clear() { MOZ_ASSERT(queue()); setReservedSlot(QueueSlot, UndefinedValue()); setReservedSlot(HeldValueSlot, UndefinedValue()); MOZ_ASSERT(!isRegistered()); } /////////////////////////////////////////////////////////////////////////// // FinalizationRegistrationsObject const JSClass FinalizationRegistrationsObject::class_ = { "FinalizationRegistrations", JSCLASS_HAS_RESERVED_SLOTS(SlotCount) | JSCLASS_BACKGROUND_FINALIZE, &classOps_, JS_NULL_CLASS_SPEC}; const JSClassOps FinalizationRegistrationsObject::classOps_ = { nullptr, // addProperty nullptr, // delProperty nullptr, // enumerate nullptr, // newEnumerate nullptr, // resolve nullptr, // mayResolve FinalizationRegistrationsObject::finalize, // finalize nullptr, // call nullptr, // construct FinalizationRegistrationsObject::trace, // trace }; /* static */ FinalizationRegistrationsObject* FinalizationRegistrationsObject::create( JSContext* cx) { auto records = cx->make_unique(cx->zone()); if (!records) { return nullptr; } auto object = NewObjectWithGivenProto(cx, nullptr); if (!object) { return nullptr; } InitReservedSlot(object, RecordsSlot, records.release(), MemoryUse::FinalizationRecordVector); return object; } /* static */ void FinalizationRegistrationsObject::trace(JSTracer* trc, JSObject* obj) { if (!trc->traceWeakEdges()) { return; } auto* self = &obj->as(); if (WeakFinalizationRecordVector* records = self->records()) { TraceRange(trc, records->length(), records->begin(), "FinalizationRegistrationsObject records"); } } /* static */ void FinalizationRegistrationsObject::finalize(JS::GCContext* gcx, JSObject* obj) { auto* self = &obj->as(); gcx->delete_(obj, self->records(), MemoryUse::FinalizationRecordVector); } inline WeakFinalizationRecordVector* FinalizationRegistrationsObject::records() { return static_cast(privatePtr()); } inline const WeakFinalizationRecordVector* FinalizationRegistrationsObject::records() const { return static_cast(privatePtr()); } inline void* FinalizationRegistrationsObject::privatePtr() const { Value value = getReservedSlot(RecordsSlot); if (value.isUndefined()) { return nullptr; } void* ptr = value.toPrivate(); MOZ_ASSERT(ptr); return ptr; } inline bool FinalizationRegistrationsObject::isEmpty() const { MOZ_ASSERT(records()); return records()->empty(); } inline bool FinalizationRegistrationsObject::append( HandleFinalizationRecordObject record) { MOZ_ASSERT(records()); return records()->append(record); } inline void FinalizationRegistrationsObject::remove( HandleFinalizationRecordObject record) { MOZ_ASSERT(records()); records()->eraseIfEqual(record); } inline bool FinalizationRegistrationsObject::traceWeak(JSTracer* trc) { MOZ_ASSERT(records()); return records()->traceWeak(trc); } /////////////////////////////////////////////////////////////////////////// // FinalizationRegistryObject // Bug 1600300: FinalizationRegistryObject is foreground finalized so that // HeapPtr destructors never see referents with released arenas. When this is // fixed we may be able to make this background finalized again. const JSClass FinalizationRegistryObject::class_ = { "FinalizationRegistry", JSCLASS_HAS_CACHED_PROTO(JSProto_FinalizationRegistry) | JSCLASS_HAS_RESERVED_SLOTS(SlotCount) | JSCLASS_FOREGROUND_FINALIZE, &classOps_, &classSpec_}; const JSClass FinalizationRegistryObject::protoClass_ = { "FinalizationRegistry.prototype", JSCLASS_HAS_CACHED_PROTO(JSProto_FinalizationRegistry), JS_NULL_CLASS_OPS, &classSpec_}; const JSClassOps FinalizationRegistryObject::classOps_ = { nullptr, // addProperty nullptr, // delProperty nullptr, // enumerate nullptr, // newEnumerate nullptr, // resolve nullptr, // mayResolve FinalizationRegistryObject::finalize, // finalize nullptr, // call nullptr, // construct FinalizationRegistryObject::trace, // trace }; const ClassSpec FinalizationRegistryObject::classSpec_ = { GenericCreateConstructor, GenericCreatePrototype, nullptr, nullptr, methods_, properties_}; const JSFunctionSpec FinalizationRegistryObject::methods_[] = { JS_FN(js_register_str, register_, 2, 0), JS_FN(js_unregister_str, unregister, 1, 0), JS_FN(js_cleanupSome_str, cleanupSome, 0, 0), JS_FS_END}; const JSPropertySpec FinalizationRegistryObject::properties_[] = { JS_STRING_SYM_PS(toStringTag, "FinalizationRegistry", JSPROP_READONLY), JS_PS_END}; /* static */ bool FinalizationRegistryObject::construct(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); if (!ThrowIfNotConstructing(cx, args, "FinalizationRegistry")) { return false; } RootedObject proto(cx); if (!GetPrototypeFromBuiltinConstructor( cx, args, JSProto_FinalizationRegistry, &proto)) { return false; } RootedObject cleanupCallback( cx, ValueToCallable(cx, args.get(0), 1, NO_CONSTRUCT)); if (!cleanupCallback) { return false; } Rooted> registrations( cx, cx->make_unique(cx)); if (!registrations) { return false; } RootedFinalizationQueueObject queue( cx, FinalizationQueueObject::create(cx, cleanupCallback)); if (!queue) { return false; } RootedFinalizationRegistryObject registry( cx, NewObjectWithClassProto(cx, proto)); if (!registry) { return false; } registry->initReservedSlot(QueueSlot, ObjectValue(*queue)); InitReservedSlot(registry, RegistrationsSlot, registrations.release(), MemoryUse::FinalizationRegistryRegistrations); if (!cx->runtime()->gc.addFinalizationRegistry(cx, registry)) { return false; } queue->setHasRegistry(true); args.rval().setObject(*registry); return true; } /* static */ void FinalizationRegistryObject::trace(JSTracer* trc, JSObject* obj) { // Trace the registrations weak map. At most this traces the // FinalizationRegistrationsObject values of the map; the contents of those // objects are weakly held and are not traced by this method. auto* registry = &obj->as(); if (ObjectWeakMap* registrations = registry->registrations()) { registrations->trace(trc); } } void FinalizationRegistryObject::traceWeak(JSTracer* trc) { // Trace and update the contents of the registrations weak map's values, which // are weakly held. MOZ_ASSERT(registrations()); for (ObjectValueWeakMap::Enum e(registrations()->valueMap()); !e.empty(); e.popFront()) { auto* registrations = &e.front().value().toObject().as(); if (!registrations->traceWeak(trc)) { e.removeFront(); } } } /* static */ void FinalizationRegistryObject::finalize(JS::GCContext* gcx, JSObject* obj) { auto registry = &obj->as(); // The queue's flag should have been updated by // GCRuntime::sweepFinalizationRegistries. MOZ_ASSERT_IF(registry->queue(), !registry->queue()->hasRegistry()); gcx->delete_(obj, registry->registrations(), MemoryUse::FinalizationRegistryRegistrations); } FinalizationQueueObject* FinalizationRegistryObject::queue() const { Value value = getReservedSlot(QueueSlot); if (value.isUndefined()) { return nullptr; } return &value.toObject().as(); } ObjectWeakMap* FinalizationRegistryObject::registrations() const { Value value = getReservedSlot(RegistrationsSlot); if (value.isUndefined()) { return nullptr; } return static_cast(value.toPrivate()); } // FinalizationRegistry.prototype.register(target, heldValue [, unregisterToken // ]) // https://tc39.es/proposal-weakrefs/#sec-finalization-registry.prototype.register /* static */ bool FinalizationRegistryObject::register_(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); // 1. Let finalizationRegistry be the this value. // 2. If Type(finalizationRegistry) is not Object, throw a TypeError // exception. // 3. If finalizationRegistry does not have a [[Cells]] internal slot, throw a // TypeError exception. if (!args.thisv().isObject() || !args.thisv().toObject().is()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_A_FINALIZATION_REGISTRY, "Receiver of FinalizationRegistry.register call"); return false; } RootedFinalizationRegistryObject registry( cx, &args.thisv().toObject().as()); // 4. If Type(target) is not Object, throw a TypeError exception. if (!args.get(0).isObject()) { JS_ReportErrorNumberASCII( cx, GetErrorMessage, nullptr, JSMSG_OBJECT_REQUIRED, "target argument to FinalizationRegistry.register"); return false; } RootedObject target(cx, &args[0].toObject()); // 5. If SameValue(target, heldValue), throw a TypeError exception. if (args.get(1).isObject() && &args.get(1).toObject() == target) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_HELD_VALUE); return false; } HandleValue heldValue = args.get(1); // 6. If Type(unregisterToken) is not Object, // a. If unregisterToken is not undefined, throw a TypeError exception. if (!args.get(2).isUndefined() && !args.get(2).isObject()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_UNREGISTER_TOKEN, "FinalizationRegistry.register"); return false; } RootedObject unregisterToken(cx); if (!args.get(2).isUndefined()) { unregisterToken = &args[2].toObject(); } // Create the finalization record representing this target and heldValue. Rooted queue(cx, registry->queue()); Rooted record( cx, FinalizationRecordObject::create(cx, queue, heldValue)); if (!record) { return false; } // Add the record to the registrations if an unregister token was supplied. if (unregisterToken && !addRegistration(cx, registry, unregisterToken, record)) { return false; } auto registrationsGuard = mozilla::MakeScopeExit([&] { if (unregisterToken) { removeRegistrationOnError(registry, unregisterToken, record); } }); // Fully unwrap the target to pass it to the GC. RootedObject unwrappedTarget(cx); unwrappedTarget = CheckedUnwrapDynamic(target, cx); if (!unwrappedTarget) { ReportAccessDenied(cx); return false; } // If the target is a DOM wrapper, preserve it. if (!preserveDOMWrapper(cx, target)) { return false; } // Wrap the record into the compartment of the target. RootedObject wrappedRecord(cx, record); AutoRealm ar(cx, unwrappedTarget); if (!JS_WrapObject(cx, &wrappedRecord)) { return false; } if (JS_IsDeadWrapper(wrappedRecord)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT); return false; } // Register the record with the target. gc::GCRuntime* gc = &cx->runtime()->gc; if (!gc->registerWithFinalizationRegistry(cx, unwrappedTarget, wrappedRecord)) { return false; } registrationsGuard.release(); args.rval().setUndefined(); return true; } /* static */ bool FinalizationRegistryObject::preserveDOMWrapper(JSContext* cx, HandleObject obj) { if (!MaybePreserveDOMWrapper(cx, obj)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_FINALIZATION_REGISTRY_OBJECT); return false; } return true; } /* static */ bool FinalizationRegistryObject::addRegistration( JSContext* cx, HandleFinalizationRegistryObject registry, HandleObject unregisterToken, HandleFinalizationRecordObject record) { // Add the record to the list of records associated with this unregister // token. MOZ_ASSERT(unregisterToken); MOZ_ASSERT(registry->registrations()); auto& map = *registry->registrations(); Rooted recordsObject(cx); JSObject* obj = map.lookup(unregisterToken); if (obj) { recordsObject = &obj->as(); } else { recordsObject = FinalizationRegistrationsObject::create(cx); if (!recordsObject || !map.add(cx, unregisterToken, recordsObject)) { return false; } } if (!recordsObject->append(record)) { ReportOutOfMemory(cx); return false; } return true; } /* static */ void FinalizationRegistryObject::removeRegistrationOnError( HandleFinalizationRegistryObject registry, HandleObject unregisterToken, HandleFinalizationRecordObject record) { // Remove a registration if something went wrong before we added it to the // target zone's map. Note that this can't remove a registration after that // point. MOZ_ASSERT(unregisterToken); MOZ_ASSERT(registry->registrations()); JS::AutoAssertNoGC nogc; auto& map = *registry->registrations(); JSObject* obj = map.lookup(unregisterToken); MOZ_ASSERT(obj); auto records = &obj->as(); records->remove(record); if (records->empty()) { map.remove(unregisterToken); } } // FinalizationRegistry.prototype.unregister ( unregisterToken ) // https://tc39.es/proposal-weakrefs/#sec-finalization-registry.prototype.unregister /* static */ bool FinalizationRegistryObject::unregister(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); // 1. Let finalizationRegistry be the this value. // 2. If Type(finalizationRegistry) is not Object, throw a TypeError // exception. // 3. If finalizationRegistry does not have a [[Cells]] internal slot, throw a // TypeError exception. if (!args.thisv().isObject() || !args.thisv().toObject().is()) { JS_ReportErrorNumberASCII( cx, GetErrorMessage, nullptr, JSMSG_NOT_A_FINALIZATION_REGISTRY, "Receiver of FinalizationRegistry.unregister call"); return false; } RootedFinalizationRegistryObject registry( cx, &args.thisv().toObject().as()); // 4. If Type(unregisterToken) is not Object, throw a TypeError exception. if (!args.get(0).isObject()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_UNREGISTER_TOKEN, "FinalizationRegistry.unregister"); return false; } RootedObject unregisterToken(cx, &args[0].toObject()); // 5. Let removed be false. bool removed = false; // 6. For each Record { [[Target]], [[HeldValue]], [[UnregisterToken]] } cell // that is an element of finalizationRegistry.[[Cells]], do // a. If SameValue(cell.[[UnregisterToken]], unregisterToken) is true, then // i. Remove cell from finalizationRegistry.[[Cells]]. // ii. Set removed to true. RootedObject obj(cx, registry->registrations()->lookup(unregisterToken)); if (obj) { auto* records = obj->as().records(); MOZ_ASSERT(records); MOZ_ASSERT(!records->empty()); for (FinalizationRecordObject* record : *records) { if (unregisterRecord(record)) { removed = true; } } registry->registrations()->remove(unregisterToken); } // 7. Return removed. args.rval().setBoolean(removed); return true; } /* static */ bool FinalizationRegistryObject::unregisterRecord( FinalizationRecordObject* record) { if (!record->isRegistered()) { return false; } // Clear the fields of this record; it will be removed from the target's // list when it is next swept. record->clear(); return true; } // FinalizationRegistry.prototype.cleanupSome ( [ callback ] ) // https://tc39.es/proposal-weakrefs/#sec-finalization-registry.prototype.cleanupSome bool FinalizationRegistryObject::cleanupSome(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); // 1. Let finalizationRegistry be the this value. // 2. Perform ? RequireInternalSlot(finalizationRegistry, [[Cells]]). if (!args.thisv().isObject() || !args.thisv().toObject().is()) { JS_ReportErrorNumberASCII( cx, GetErrorMessage, nullptr, JSMSG_NOT_A_FINALIZATION_REGISTRY, "Receiver of FinalizationRegistry.cleanupSome call"); return false; } RootedFinalizationRegistryObject registry( cx, &args.thisv().toObject().as()); // 3. If callback is not undefined and IsCallable(callback) is false, throw a // TypeError exception. RootedObject cleanupCallback(cx); if (!args.get(0).isUndefined()) { cleanupCallback = ValueToCallable(cx, args.get(0), -1, NO_CONSTRUCT); if (!cleanupCallback) { return false; } } RootedFinalizationQueueObject queue(cx, registry->queue()); if (!FinalizationQueueObject::cleanupQueuedRecords(cx, queue, cleanupCallback)) { return false; } args.rval().setUndefined(); return true; } /////////////////////////////////////////////////////////////////////////// // FinalizationQueueObject // Bug 1600300: FinalizationQueueObject is foreground finalized so that // HeapPtr destructors never see referents with released arenas. When this is // fixed we may be able to make this background finalized again. const JSClass FinalizationQueueObject::class_ = { "FinalizationQueue", JSCLASS_HAS_RESERVED_SLOTS(SlotCount) | JSCLASS_FOREGROUND_FINALIZE, &classOps_}; const JSClassOps FinalizationQueueObject::classOps_ = { nullptr, // addProperty nullptr, // delProperty nullptr, // enumerate nullptr, // newEnumerate nullptr, // resolve nullptr, // mayResolve FinalizationQueueObject::finalize, // finalize nullptr, // call nullptr, // construct FinalizationQueueObject::trace, // trace }; /* static */ FinalizationQueueObject* FinalizationQueueObject::create( JSContext* cx, HandleObject cleanupCallback) { MOZ_ASSERT(cleanupCallback); Rooted> recordsToBeCleanedUp( cx, cx->make_unique(cx->zone())); if (!recordsToBeCleanedUp) { return nullptr; } Handle funName = cx->names().empty; RootedFunction doCleanupFunction( cx, NewNativeFunction(cx, doCleanup, 0, funName, gc::AllocKind::FUNCTION_EXTENDED)); if (!doCleanupFunction) { return nullptr; } // It's problematic storing a CCW to a global in another compartment because // you don't know how far to unwrap it to get the original object // back. Instead store a CCW to a plain object in the same compartment as the // global (this uses Object.prototype). RootedObject incumbentObject(cx); if (!GetObjectFromIncumbentGlobal(cx, &incumbentObject) || !incumbentObject) { return nullptr; } FinalizationQueueObject* queue = NewObjectWithGivenProto(cx, nullptr); if (!queue) { return nullptr; } queue->initReservedSlot(CleanupCallbackSlot, ObjectValue(*cleanupCallback)); queue->initReservedSlot(IncumbentObjectSlot, ObjectValue(*incumbentObject)); InitReservedSlot(queue, RecordsToBeCleanedUpSlot, recordsToBeCleanedUp.release(), MemoryUse::FinalizationRegistryRecordVector); queue->initReservedSlot(IsQueuedForCleanupSlot, BooleanValue(false)); queue->initReservedSlot(DoCleanupFunctionSlot, ObjectValue(*doCleanupFunction)); queue->initReservedSlot(HasRegistrySlot, BooleanValue(false)); doCleanupFunction->setExtendedSlot(DoCleanupFunction_QueueSlot, ObjectValue(*queue)); return queue; } /* static */ void FinalizationQueueObject::trace(JSTracer* trc, JSObject* obj) { auto queue = &obj->as(); if (FinalizationRecordVector* records = queue->recordsToBeCleanedUp()) { records->trace(trc); } } /* static */ void FinalizationQueueObject::finalize(JS::GCContext* gcx, JSObject* obj) { auto queue = &obj->as(); gcx->delete_(obj, queue->recordsToBeCleanedUp(), MemoryUse::FinalizationRegistryRecordVector); } void FinalizationQueueObject::setHasRegistry(bool newValue) { MOZ_ASSERT(hasRegistry() != newValue); // Suppress our assertions about touching grey things. It's OK for us to set a // boolean slot even if this object is gray. AutoTouchingGrayThings atgt; setReservedSlot(HasRegistrySlot, BooleanValue(newValue)); } bool FinalizationQueueObject::hasRegistry() const { return getReservedSlot(HasRegistrySlot).toBoolean(); } inline JSObject* FinalizationQueueObject::cleanupCallback() const { Value value = getReservedSlot(CleanupCallbackSlot); if (value.isUndefined()) { return nullptr; } return &value.toObject(); } JSObject* FinalizationQueueObject::incumbentObject() const { Value value = getReservedSlot(IncumbentObjectSlot); if (value.isUndefined()) { return nullptr; } return &value.toObject(); } FinalizationRecordVector* FinalizationQueueObject::recordsToBeCleanedUp() const { Value value = getReservedSlot(RecordsToBeCleanedUpSlot); if (value.isUndefined()) { return nullptr; } return static_cast(value.toPrivate()); } bool FinalizationQueueObject::isQueuedForCleanup() const { return getReservedSlot(IsQueuedForCleanupSlot).toBoolean(); } JSFunction* FinalizationQueueObject::doCleanupFunction() const { Value value = getReservedSlot(DoCleanupFunctionSlot); if (value.isUndefined()) { return nullptr; } return &value.toObject().as(); } void FinalizationQueueObject::queueRecordToBeCleanedUp( FinalizationRecordObject* record) { AutoEnterOOMUnsafeRegion oomUnsafe; if (!recordsToBeCleanedUp()->append(record)) { oomUnsafe.crash("FinalizationQueueObject::queueRecordsToBeCleanedUp"); } } void FinalizationQueueObject::setQueuedForCleanup(bool value) { MOZ_ASSERT(value != isQueuedForCleanup()); setReservedSlot(IsQueuedForCleanupSlot, BooleanValue(value)); } /* static */ bool FinalizationQueueObject::doCleanup(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); RootedFunction callee(cx, &args.callee().as()); Value value = callee->getExtendedSlot(DoCleanupFunction_QueueSlot); RootedFinalizationQueueObject queue( cx, &value.toObject().as()); queue->setQueuedForCleanup(false); return cleanupQueuedRecords(cx, queue); } // CleanupFinalizationRegistry ( finalizationRegistry [ , callback ] ) // https://tc39.es/proposal-weakrefs/#sec-cleanup-finalization-registry /* static */ bool FinalizationQueueObject::cleanupQueuedRecords( JSContext* cx, HandleFinalizationQueueObject queue, HandleObject callbackArg) { MOZ_ASSERT(cx->compartment() == queue->compartment()); // 2. If callback is undefined, set callback to // finalizationRegistry.[[CleanupCallback]]. RootedValue callback(cx); if (callbackArg) { callback.setObject(*callbackArg); } else { JSObject* cleanupCallback = queue->cleanupCallback(); MOZ_ASSERT(cleanupCallback); callback.setObject(*cleanupCallback); } // 3. While finalizationRegistry.[[Cells]] contains a Record cell such that // cell.[[WeakRefTarget]] is empty, then an implementation may perform the // following steps, // a. Choose any such cell. // b. Remove cell from finalizationRegistry.[[Cells]]. // c. Perform ? Call(callback, undefined, « cell.[[HeldValue]] »). RootedValue heldValue(cx); RootedValue rval(cx); FinalizationRecordVector* records = queue->recordsToBeCleanedUp(); while (!records->empty()) { FinalizationRecordObject* record = records->popCopy(); // Skip over records that have been unregistered. if (!record->isRegistered()) { continue; } heldValue.set(record->heldValue()); record->clear(); if (!Call(cx, callback, UndefinedHandleValue, heldValue, &rval)) { return false; } } return true; }