summaryrefslogtreecommitdiffstats
path: root/js/src/builtin/FinalizationRegistryObject.h
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/builtin/FinalizationRegistryObject.h')
-rw-r--r--js/src/builtin/FinalizationRegistryObject.h274
1 files changed, 274 insertions, 0 deletions
diff --git a/js/src/builtin/FinalizationRegistryObject.h b/js/src/builtin/FinalizationRegistryObject.h
new file mode 100644
index 0000000000..288ebea424
--- /dev/null
+++ b/js/src/builtin/FinalizationRegistryObject.h
@@ -0,0 +1,274 @@
+/* -*- 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<FinalizationRegistryObject*>;
+using HandleFinalizationRecordObject = Handle<FinalizationRecordObject*>;
+using HandleFinalizationQueueObject = Handle<FinalizationQueueObject*>;
+using RootedFinalizationRegistryObject = Rooted<FinalizationRegistryObject*>;
+using RootedFinalizationRecordObject = Rooted<FinalizationRecordObject*>;
+using RootedFinalizationQueueObject = Rooted<FinalizationQueueObject*>;
+
+// 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<WeakHeapPtr<FinalizationRecordObject*>, 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<HeapPtr<FinalizationRecordObject*>, 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 */