/* -*- 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/. */ /* * FinalizationRegistry objects allow a program to register to receive a * callback after a 'target' object dies. The callback is passed a 'held value' * (that hopefully doesn't entrain the target). An 'unregister token' is an * object which can be used to remove multiple previous registrations in one go. * * To arrange this, the following data structures are used: * * +---------------------------------------+-------------------------------+ * | FinalizationRegistry compartment | Target zone / compartment | * | | | * | +----------------------+ | +------------------+ | * | +-----+ FinalizationRegistry | | | Zone | | * | | +----------+-----------+ | +---------+--------+ | * | | | | | | * | | v | v | * | | +-------------+-------------+ | +-------------+------------+ | * | | | Registrations | | | FinalizationObservers | | * | | | weak map | | +-------------+------------+ | * | | +---------------------------+ | | | * | | | Unregister : Records | | v | * | | | token : object | | +------------+------------+ | * | | +--------------------+------+ | | RecordMap map | | * | | | | +-------------------------+ | * | | v | | Target : Finalization | | * | | +--------------------+------+ | | object : RecordVector | | * | | | Finalization | | +----+-------------+------+ | * | | | RegistrationsObject | | | | | * | | +---------------------------+ | v v | * | | | RecordVector | | +----+-----+ +----+-----+ | * | | +-------------+-------------+ | | Target | | (CCW if | | * | | | | | JSObject | | needed) | | * | | * v | +----------+ +----+-----+ | * | | +-------------+-------------+ * | | | * | | | FinalizationRecordObject +<--------------------------+ | * | | +---------------------------+ | | * | | | Queue +--+ | | * | | +---------------------------+ | | | * | | | Held value | | | | * | | +---------------------------+ | | | * | | | | | * | +--------------+ +--------------+ | | * | | | | | * | v v | | * | +----------+---+----------+ | | * | | FinalizationQueueObject | | | * | +-------------------------+ | | * | | | * +---------------------------------------+-------------------------------+ * * A FinalizationRegistry consists of two parts: the FinalizationRegistry that * consumers see and a FinalizationQueue used internally to queue and call the * cleanup callbacks. * * Registering a target with a FinalizationRegistry creates a FinalizationRecord * containing a pointer to the queue and the heldValue. This is added to a * vector of records associated with the target, implemented as a map on the * target's Zone. All finalization records are treated as GC roots. * * When a target is registered an unregister token may be supplied. If so, this * is also recorded by the registry and is stored in a weak map of * registrations. The values of this map are FinalizationRegistrationsObject * objects. It's necessary to have another JSObject here because our weak map * implementation only supports JS types as values. * * When targets are unregistered, the registration is looked up in the weakmap * and the corresponding records are cleared. * The finalization record maps are swept during GC to check for records that * have been cleared by unregistration, for FinalizationRecords that are dead * and for nuked CCWs. In all cases the record is removed and the cleanup * callback is not run. * * Following this the targets are checked to see if they are dying. For such * targets the associated record list is processed and for each record the * heldValue is queued on the FinalizationQueue. At a later time this causes the * client's cleanup callback to be run. */ #ifndef builtin_FinalizationRegistryObject_h #define builtin_FinalizationRegistryObject_h #include "gc/Barrier.h" #include "js/GCVector.h" #include "vm/NativeObject.h" namespace js { class FinalizationRegistryObject; class FinalizationRecordObject; class FinalizationQueueObject; class ObjectWeakMap; using HandleFinalizationRegistryObject = Handle; using HandleFinalizationRecordObject = Handle; using HandleFinalizationQueueObject = Handle; using RootedFinalizationRegistryObject = Rooted; using RootedFinalizationRecordObject = Rooted; using RootedFinalizationQueueObject = Rooted; // A finalization record: a pair of finalization queue and held value. // // A finalization record represents the registered interest of a finalization // registry in a target's finalization. // // Finalization records created in the 'registered' state but may be // unregistered. This happens when: // - the heldValue is passed to the registry's cleanup callback // - the registry's unregister method removes the registration // // Finalization records are added to a per-zone record map. They are removed // when the record is queued for cleanup, or if the interest in finalization is // cancelled. See FinalizationObservers::shouldRemoveRecord for the possible // reasons. class FinalizationRecordObject : public NativeObject { enum { QueueSlot = 0, HeldValueSlot, InMapSlot, SlotCount }; public: static const JSClass class_; static FinalizationRecordObject* create(JSContext* cx, HandleFinalizationQueueObject queue, HandleValue heldValue); FinalizationQueueObject* queue() const; Value heldValue() const; bool isRegistered() const; bool isInRecordMap() const; void setInRecordMap(bool newValue); void clear(); }; // A vector of weakly-held FinalizationRecordObjects. using WeakFinalizationRecordVector = GCVector, 1, js::CellAllocPolicy>; // A JS object containing a vector of weakly-held FinalizationRecordObjects, // which holds the records corresponding to the registrations for a particular // registration token. These are used as the values in the registration // weakmap. Since the contents of the vector are weak references they are not // traced. class FinalizationRegistrationsObject : public NativeObject { enum { RecordsSlot = 0, SlotCount }; public: static const JSClass class_; static FinalizationRegistrationsObject* create(JSContext* cx); WeakFinalizationRecordVector* records(); const WeakFinalizationRecordVector* records() const; bool isEmpty() const; bool append(HandleFinalizationRecordObject record); void remove(HandleFinalizationRecordObject record); bool traceWeak(JSTracer* trc); private: static const JSClassOps classOps_; void* privatePtr() const; static void trace(JSTracer* trc, JSObject* obj); static void finalize(JS::GCContext* gcx, JSObject* obj); }; using FinalizationRecordVector = GCVector, 1, js::CellAllocPolicy>; // The JS FinalizationRegistry object itself. class FinalizationRegistryObject : public NativeObject { enum { QueueSlot = 0, RegistrationsSlot, SlotCount }; public: static const JSClass class_; static const JSClass protoClass_; FinalizationQueueObject* queue() const; ObjectWeakMap* registrations() const; void traceWeak(JSTracer* trc); static bool unregisterRecord(FinalizationRecordObject* record); static bool cleanupQueuedRecords(JSContext* cx, HandleFinalizationRegistryObject registry, HandleObject callback = nullptr); private: static const JSClassOps classOps_; static const ClassSpec classSpec_; static const JSFunctionSpec methods_[]; static const JSPropertySpec properties_[]; static bool construct(JSContext* cx, unsigned argc, Value* vp); static bool register_(JSContext* cx, unsigned argc, Value* vp); static bool unregister(JSContext* cx, unsigned argc, Value* vp); static bool cleanupSome(JSContext* cx, unsigned argc, Value* vp); static bool addRegistration(JSContext* cx, HandleFinalizationRegistryObject registry, HandleObject unregisterToken, HandleFinalizationRecordObject record); static void removeRegistrationOnError( HandleFinalizationRegistryObject registry, HandleObject unregisterToken, HandleFinalizationRecordObject record); static bool preserveDOMWrapper(JSContext* cx, HandleObject obj); static void trace(JSTracer* trc, JSObject* obj); static void finalize(JS::GCContext* gcx, JSObject* obj); }; // Contains information about the cleanup callback and the records queued to // be cleaned up. This is not exposed to content JS. class FinalizationQueueObject : public NativeObject { enum { CleanupCallbackSlot = 0, IncumbentObjectSlot, RecordsToBeCleanedUpSlot, IsQueuedForCleanupSlot, DoCleanupFunctionSlot, HasRegistrySlot, SlotCount }; enum DoCleanupFunctionSlots { DoCleanupFunction_QueueSlot = 0, }; public: static const JSClass class_; JSObject* cleanupCallback() const; JSObject* incumbentObject() const; FinalizationRecordVector* recordsToBeCleanedUp() const; bool isQueuedForCleanup() const; JSFunction* doCleanupFunction() const; bool hasRegistry() const; void queueRecordToBeCleanedUp(FinalizationRecordObject* record); void setQueuedForCleanup(bool value); void setHasRegistry(bool newValue); static FinalizationQueueObject* create(JSContext* cx, HandleObject cleanupCallback); static bool cleanupQueuedRecords(JSContext* cx, HandleFinalizationQueueObject registry, HandleObject callback = nullptr); private: static const JSClassOps classOps_; static bool doCleanup(JSContext* cx, unsigned argc, Value* vp); static void trace(JSTracer* trc, JSObject* obj); static void finalize(JS::GCContext* gcx, JSObject* obj); }; } // namespace js #endif /* builtin_FinalizationRegistryObject_h */