blob: 0e28e9aba9ee2c30815b26caac5bd0ea58cc6d90 [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef GIN_WEAK_CELL_H_
#define GIN_WEAK_CELL_H_
#include "base/check.h"
#include "base/functional/bind.h"
#include "base/types/pass_key.h"
#include "v8/include/cppgc/allocation.h"
#include "v8/include/cppgc/garbage-collected.h"
#include "v8/include/cppgc/member.h"
#include "v8/include/cppgc/persistent.h"
#include "v8/include/cppgc/visitor.h"
#include "v8/include/v8-cppgc.h"
#include "v8/include/v8-isolate.h"
namespace gin {
template <typename T>
class WeakCellFactory;
// A `WeakCell<T>` provides a GC-safe pattern for classes that want to:
// - expose weak references to themselves
// - invalidate the weak references *before* the object becomes unreachable.
//
// This differs from `cppgc::WeakMember<T>` in the last point, as
// `cppgc::WeakMember<T>` only becomes implicitly null after the referenced `T`
// is no longer reachable. In Chrome, a common use of early invalidation is to
// cancel callbacks that have not yet run.
//
// If early invalidation is not needed, please use `cppgc::WeakMember<T>`!
//
// Like many Oilpan types, this class is thread-unsafe.
//
// Note: This is the GC-safe version of `base::WeakPtrFactory<T>` +
// `base::WeakPtr<T>`. `base::WeakPtrFactory<T>` + `base::WeakPtr<T>` are
// GC-unsafe and should not be embedded in objects that live on the Oilpan heap.
template <typename T>
class WeakCell final : public cppgc::GarbageCollected<WeakCell<T>> {
public:
// Returns a pointer to the referenced object, or null if:
// - the cell has been invalidated by its factory
// - or the referenced object is no longer reachable.
T* Get() const { return ptr_.Get(); }
void Trace(cppgc::Visitor* visitor) const { visitor->Trace(ptr_); }
// Internal helpers for `WeakCellFactory` implementation.
explicit WeakCell(base::PassKey<WeakCellFactory<T>>, T* ptr) : ptr_(ptr) {}
void Invalidate(base::PassKey<WeakCellFactory<T>>) { ptr_ = nullptr; }
private:
cppgc::WeakMember<T> ptr_;
};
#define GIN_DISALLOW_NEW() \
public: \
void* operator new(size_t, void* location) { \
return location; \
} \
\
private: \
void* operator new(size_t) = delete
// A `WeakCellFactory<T>` vends out a pointer to a `WeakCell<T>`, and allows the
// owning class to invalidate `WeakCell<T>`s that have already been handed out.
//
// Usage overview:
//
// class DatabaseScheduler : public GarbageCollected<DatabaseScheduler> {
// public:
// ...
//
// void DoWork();
// void CancelWork();
//
// private:
// // Note: field ordering for `WeakCellFactory` does not matter, and it does
// // *not* have to be the last field in a class.
// WeakCellFactory<DatabaseScheduler> weak_factory_{this};
// // Note: this is *not* a cross-thread task runner. In Blink, many task
// // queues are multiplexed onto one thread.
// scoped_refptr<base::TaskRunner> db_task_queue;
// };
//
// void DatabaseScheduler::DoWork() {
// // IMPORTANT: the `WrapPersistent()` around the `WeakCell<T>` argument is
// // mandatory, as `WeakCell<T>` itself is allocated on the Oilpan heap.
// db_task_queue_->PostTask(
// FROM_HERE,
// base::BindOnce(
// &DatabaseScheduler::DoRealWork,
// WrapPersistent(weak_factory_.GetWeakCell(
// v8::Isolate::GetCurrent()->GetCppHeap()
// ->GetAllocationHandle()))));
// }
//
// void DatabaseScheduler::CancelWork() {
// // Any already-posted but not-yet-run tasks using a `WeakCell<T>` as the
// // receiver will not run.
// // However, any subsequent calls to `DoWork()` above *will* schedule new
// // callbacks that will run unless `CancelWork()` is called again.
// weak_factory_.Invalidate();
// }
template <typename T>
class WeakCellFactory {
GIN_DISALLOW_NEW();
public:
explicit WeakCellFactory(T* ptr) : ptr_(ptr) {}
WeakCell<T>* GetWeakCell(cppgc::AllocationHandle& allocation_handle) {
if (!weak_cell_) {
weak_cell_ = cppgc::MakeGarbageCollected<WeakCell<T>>(
allocation_handle, base::PassKey<WeakCellFactory<T>>(), ptr_.Get());
}
DCHECK(weak_cell_);
return weak_cell_.Get();
}
bool HasWeakCells() const { return weak_cell_; }
// Invalidates the previous `WeakCell<T>` so that `previous_cell->Get()`
// returns null. Future calls to `GetWeakCell()` will return a *new* and
// *non-null* cell.
void Invalidate() {
if (!weak_cell_) {
return;
}
weak_cell_->Invalidate(base::PassKey<WeakCellFactory<T>>());
weak_cell_ = nullptr;
}
void Trace(cppgc::Visitor* visitor) const {
visitor->Trace(ptr_);
visitor->Trace(weak_cell_);
}
private:
const cppgc::WeakMember<T> ptr_;
cppgc::Member<WeakCell<T>> weak_cell_;
};
} // namespace gin
namespace base {
template <typename T>
struct IsWeakReceiver<cppgc::Persistent<gin::WeakCell<T>>> : std::true_type {};
template <typename T>
struct BindUnwrapTraits<cppgc::Persistent<gin::WeakCell<T>>> {
static T* Unwrap(const cppgc::Persistent<gin::WeakCell<T>>& wrapped) {
return wrapped->Get();
}
};
template <typename T>
struct MaybeValidTraits<cppgc::Persistent<gin::WeakCell<T>>> {
static constexpr bool MaybeValid(
const cppgc::Persistent<gin::WeakCell<T>>& p) {
// Not necessarily called on `Persistent<T>` and `WeakCell<T>`'s owning
// thread, so the only possible implementation is to assume the weak cell
// has not been invalidated.
return true;
}
};
} // namespace base
#endif // GIN_WEAK_CELL_H_