blob: d8af8feb01e7456a6a34ba242ff82b0244297bb5 [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_UI_TABS_SUPPORTS_HANDLES_H_
#define CHROME_BROWSER_UI_TABS_SUPPORTS_HANDLES_H_
// SupportsHandles is a way to add "handle" support to an object, such as a tab
// or browser window which:
// - may be transient (i.e. the reference could later become invalid)
// - needs to be safely referenced from code in other languages, such as
// JavaScript code in extensions
//
// If you do not need *both* of these, consider a raw or weak pointer instead.
//
// A handle is a semi-opaque value that is safe to store and pass around even
// after the underlying object is destroyed. They behave more or less like weak
// pointers, but have the added benefit that they contain an integral
// `raw_value` which can be copied around, and even passed between programming
// languages.
//
// To use handles with a class, inherit publicly from
// `SupportsHandles<YourClassName>`. Then a handle can be retrieved from an
// instance, and the instance retrieved from the handle:
// ```
// MyClass::Handle handle = my_object->GetHandle();
// // Do a bunch of stuff that might delete `my_object`.
// if (MyClass* obj = handle.Get()) {
// obj->DoAThing();
// }
// ```
//
// Notes:
//
// Handle values do not persist across process restart (though restoring handle
// values at startup could be implemented in some future iteration of this
// library.)
//
// Objects with handles may only be generated and retrieved on the primary UI
// thread, though their handles and handle values may be copied to and stored on
// any thread.
//
// The default raw handle type `V` is a 32-bit signed integer, since (a) there
// are usually not more than 4 billion of any UI object, and (b) signed integers
// provide the broadest possible language support. However, you may choose any
// integral type for `V`.
//
// Regardless of choice of `V`, the null value for handles is always zero.
//
// It is a fatal error to try to create a new object if all valid values of `V`
// have already been used. If an object is likely to run through all possible
// values of V (that is, have more than 4 billion constructed over the life of
// a chrome instance) then it is probably a poor candidate for handles (or at
// the very least, you need to pick a larger V).
//
// TODO(dfried, tbergquist): move this file down to c/b/ui if it's used outside
// of the tabstrip.
#include <concepts>
#include <cstdint>
#include <map>
#include "base/check.h"
#include "base/no_destructor.h"
#include "base/sequence_checker.h"
// Inherit from this type to have your class support handles. Objects that
// support handles cannot be copyable or assignable:
// ```
// class C : public SupportsHandles<C> { ... }
// ```
//
// `V` defines the type (and therefore size in bits) of the underlying handle
// value. A wider value type will provide more unique handle values.
//
// It is required that `T` derive from this class. This constraint is enforced
// via a helper class, as it cannot be enforced before SupportsHandles is
// defined.
template <typename T, std::integral V = int32_t>
class SupportsHandles {
public:
SupportsHandles();
virtual ~SupportsHandles();
SupportsHandles(const SupportsHandles& other) = delete;
void operator=(const SupportsHandles& other) = delete;
// The handle type for this class.
class Handle;
// Returns a unique handle value for this object.
Handle GetHandle() const;
private:
V handle_value_;
};
// The handle type for an object of type `T`.
//
// This is a default-constructable, orderable, comparable, copyable value type.
//
// Unlike WeakPtr there is some overhead in looking up a handle, so convenience
// operators (bool, !, ->, *) are not provided.
template <typename T, std::integral V>
class SupportsHandles<T, V>::Handle {
public:
Handle() = default;
Handle(const Handle& other) = default;
~Handle() = default;
Handle& operator=(const Handle& other) = default;
// The underlying opaque handle value type.
using RawValueType = V;
// The object type returned by `Get()`.
using ObjectType = T;
// Convert to/from a raw, opaque handle value. It is safe to pass this value
// around, including to code running in other languages.
explicit Handle(RawValueType raw_value) : raw_value_(raw_value) {}
RawValueType raw_value() const { return raw_value_; }
// Retrieves the underlying object, or null if it is no longer present.
ObjectType* Get() const;
// Handles are comparable and sortable.
bool operator==(const Handle& other) const {
return raw_value_ == other.raw_value_;
}
bool operator!=(const Handle& other) const {
return raw_value_ != other.raw_value_;
}
bool operator<(const Handle& other) const {
return raw_value_ < other.raw_value_;
}
// Explicitly provide the null value and handle.
static constexpr RawValueType NullValue = 0;
static Handle Null() { return Handle(NullValue); }
private:
RawValueType raw_value_ = NullValue;
};
class SupportsHandlesTest;
namespace internal {
// Provides handle lookup table storage for each class that supports handles.
//
// This object is strictly sequence-checked and should only ever be accessed
// from the primary UI thread.
template <typename T, std::integral V>
requires std::derived_from<T, SupportsHandles<T, V>>
class HandleHelper {
public:
using StoredPointerType = SupportsHandles<T, V>*;
// Assigns a new, unused handle value for `object` and returns the value.
// Called from the constructor of `SupportsHandles`.
V AssignHandleValue(StoredPointerType object) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
CHECK(object);
// Use the next available handle value; it is an error if the value rolls
// back over to zero.
++last_handle_value_;
CHECK(last_handle_value_)
<< "Fatal handle reuse! Please use a larger raw value type (V) or "
"curtail object creation.";
lookup_table_.emplace(last_handle_value_, object);
return last_handle_value_;
}
// Frees a handle with `handle_value`. Must be called from the destructor of
// `SupportsHandles`.
void FreeHandleValue(V handle_value) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
CHECK(lookup_table_.erase(handle_value));
}
// Retrieves the object associated with the given `handle_value`, or null if
// no such object exists.
T* LookupObject(V handle_value) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
const auto it = lookup_table_.find(handle_value);
return it != lookup_table_.end() ? static_cast<T*>(it->second) : nullptr;
}
// The lookup object is a singleton per `SupportsHandle`-derived type.
static HandleHelper& GetInstance() {
static base::NoDestructor<HandleHelper> instance;
return *instance;
}
private:
friend SupportsHandlesTest;
friend class base::NoDestructor<HandleHelper<T, V>>;
HandleHelper() = default;
~HandleHelper() = default;
V last_handle_value_ GUARDED_BY_CONTEXT(sequence_) = 0;
std::map<V, StoredPointerType> lookup_table_ GUARDED_BY_CONTEXT(sequence_);
SEQUENCE_CHECKER(sequence_);
};
} // namespace internal
template <typename T, std::integral V>
SupportsHandles<T, V>::SupportsHandles()
: handle_value_(
internal::HandleHelper<T, V>::GetInstance().AssignHandleValue(this)) {
}
template <typename T, std::integral V>
SupportsHandles<T, V>::~SupportsHandles() {
internal::HandleHelper<T, V>::GetInstance().FreeHandleValue(handle_value_);
}
template <typename T, std::integral V>
typename SupportsHandles<T, V>::Handle SupportsHandles<T, V>::GetHandle()
const {
return Handle(handle_value_);
}
template <typename T, std::integral V>
T* SupportsHandles<T, V>::Handle::Get() const {
return internal::HandleHelper<T, V>::GetInstance().LookupObject(raw_value_);
}
#endif // CHROME_BROWSER_UI_TABS_SUPPORTS_HANDLES_H_