910e62b5创建于 1月15日历史提交
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef UI_BASE_CLASS_PROPERTY_H_
#define UI_BASE_CLASS_PROPERTY_H_

#include <stdint.h>

#include <concepts>
#include <map>
#include <memory>
#include <set>
#include <type_traits>
#include <utility>

#include "base/bit_cast.h"
#include "base/component_export.h"
#include "base/time/time.h"
#include "base/types/is_complete.h"
#include "ui/base/ui_base_types.h"

// To define a new `ClassProperty`:
// ```
//  #include "foo/foo_export.h"
//  #include "ui/base/class_property.h"
//
//  DECLARE_EXPORTED_UI_CLASS_PROPERTY_TYPE(FOO_EXPORT, MyType)
//  namespace foo {
//    // A value type or a pointer you don't want automatically deleted.
//    // `MyType` must fit in 64 bits.
//    DEFINE_UI_CLASS_PROPERTY_KEY(MyType, kMyKey, MyDefault)
//
//    // An object of any size, which will be stored on the heap.
//    DEFINE_OWNED_UI_CLASS_PROPERTY_KEY(gfx::Rect, kRestoreBoundsKey)
//  }  // namespace foo
// ```
//
// To define a new type used for a `ClassProperty`:
// ```
//  // outside all namespaces:
//  DEFINE_EXPORTED_UI_CLASS_PROPERTY_TYPE(FOO_EXPORT, MyType)
// ```
// If a property type is not exported, use
// `DEFINE_UI_CLASS_PROPERTY_TYPE(MyType)`, which is shorthand for
// `DEFINE_EXPORTED_UI_CLASS_PROPERTY_TYPE(, MyType)`.
//
// Cascading properties:
//
// Use the `DEFINE_CASCADING_XXX` macros to create a class property type that
// will automatically search up an instance hierarchy for the first defined
// property. This only affects the `GetProperty()` call. `SetProperty()` will
// still explicitly set the value on the given instance. This is useful for
// hierarchies of instances which a single set property can effect a whole sub-
// tree of instances.
//
// In order to use this feature, you must override `GetParentHandler()` on the
// class that extends `PropertyHandler`.

namespace ui {

// Type of a function to delete a property that this window owns.
using PropertyDeallocator = void(*)(int64_t value);

template<typename T>
struct ClassProperty {
  T default_value;
  const char* name;
  bool cascading = false;
  PropertyDeallocator deallocator;
};

namespace subtle {
class PropertyHelper;
}

class COMPONENT_EXPORT(UI_BASE) PropertyHandler {
 public:
  PropertyHandler();
  PropertyHandler(PropertyHandler&&);
  PropertyHandler& operator=(PropertyHandler&&);
  virtual ~PropertyHandler();

  // Sets the `value` of the given unowned `property`. Setting to the default
  // value (e.g. null) removes the property. If `T` is a pointer, the lifetime
  // of the underlying object is managed by the caller. For caller convenience
  // (e.g. for tail calls), returns the new value.
  //
  // CAUTION: Do not use this to pass in a raw pointer for an owned property,
  // due to the risk of UAF/double-frees. Use the owned property setters below
  // that set `ClassProperty<T*>`s.
  template <typename T>
  T SetProperty(const ClassProperty<T>* property, T value);

  // Convenience wrapper for the above, for when the supplied value is not the
  // same type as the property.
  template <typename T, typename U>
    requires(std::constructible_from<T, U &&> &&
             !std::same_as<T, std::remove_cvref_t<U>>)
  T SetProperty(const ClassProperty<T>* property, U&& value);

  // Sets the `value` of the given owned `property`. Setting to null removes the
  // property. Manages a heap allocation for any set value, so callers can
  // mutate or destroy the supplied value without risk of UAF. For caller
  // convenience (e.g. for tail calls), returns (a non-owning pointer to) the
  // new value.
  template <typename T, typename U>
    requires(!std::same_as<T*, std::remove_cvref_t<U>> &&
             std::constructible_from<T, U &&> && std::assignable_from<T&, U &&>)
  T* SetProperty(const ClassProperty<T*>* property, U&& value);

  // As above, but for callers who already have a heap-allocated object.
  template <typename T, typename U>
    requires(std::convertible_to<U*, T*>)
  T* SetProperty(const ClassProperty<T*>* property, std::unique_ptr<U> value);

  // For unowned properties: Returns the value of the given `property`, or the
  // property-specific default value if the property is not currently set.
  // For owned properties: Returns (a non-owning pointer to) the value of the
  // given `property`, or null if the property is not currently set.
  template<typename T>
  T GetProperty(const ClassProperty<T>* property) const;

  // Removes any currently-set value for `property`. For unowned properties,
  // future calls to `GetProperty()` will return the property-specific default
  // value. For owned properties, runs the deallocator before returning.
  template <typename T>
  void ClearProperty(const ClassProperty<T>* property);

  // Takes the ownership of all the properties in |other|, overwriting any
  // similarly-keyed existing properties without affecting existing ones with
  // different keys.
  void AcquireAllPropertiesFrom(PropertyHandler&& other);

  // Returns the value of all properties with a non-default value.
  std::set<const void*> GetAllPropertyKeys() const;

 protected:
  friend class subtle::PropertyHelper;

  // TODO(pkasting): Consider calling this only when the values differ.
  virtual void AfterPropertyChange(const void* key, int64_t old_value) {}

  // Override this function when inheriting this class on a class or classes
  // in which instances are arranged in a parent-child relationship and
  // the intent is to use cascading properties.
  virtual PropertyHandler* GetParentHandler() const;

  void ClearProperties();

  // Implements `SetProperty()`. Returns the old value of the property; for
  // owned properties, the caller must deallocate this.
  int64_t SetPropertyInternal(const void* key,
                              const char* name,
                              PropertyDeallocator deallocator,
                              int64_t value,
                              int64_t default_value);

  // Implements `GetProperty()`. If `search_parent` is true, uses
  // `GetParentHandler()` to traverse cascading values.
  int64_t GetPropertyInternal(const void* key,
                              int64_t default_value,
                              bool search_parent) const;

 private:
  // Value struct to keep the name and deallocator for this property.
  // Key cannot be used for this purpose because it can be char* or
  // ClassProperty<>.
  struct Value {
    const char* name;
    int64_t value;
    PropertyDeallocator deallocator;
  };
  using PropMap = std::map<const void*, Value>;

  PropMap prop_map_;
};

// No single new-style cast works for every conversion to/from int64_t, so we
// need this helper class.
template<typename T>
class ClassPropertyCaster {
 public:
  static int64_t ToInt64(T x)
    requires(sizeof(T) <= sizeof(int64_t))
  {
    return static_cast<int64_t>(x);
  }
  static T FromInt64(int64_t x) { return static_cast<T>(x); }
};
template<typename T>
class ClassPropertyCaster<T*> {
 public:
  static int64_t ToInt64(T* x) {
    static_assert(sizeof(T*) <= sizeof(int64_t));
    return reinterpret_cast<int64_t>(x);
  }
  static T* FromInt64(int64_t x) { return reinterpret_cast<T*>(x); }
};
template <>
class ClassPropertyCaster<base::TimeDelta> {
 public:
  static int64_t ToInt64(base::TimeDelta x) { return x.InMicroseconds(); }
  static base::TimeDelta FromInt64(int64_t x) { return base::Microseconds(x); }
};
template <>
class ClassPropertyCaster<float> {
 public:
  static int64_t ToInt64(float x) {
    static_assert(sizeof(float) == sizeof(int32_t));
    return base::bit_cast<int32_t>(x);
  }
  static float FromInt64(int64_t x) {
    return base::bit_cast<float>(static_cast<int32_t>(x));
  }
};

namespace subtle {

class COMPONENT_EXPORT(UI_BASE) PropertyHelper {
 public:
  template <typename T>
  static T Set(::ui::PropertyHandler* handler,
               const ::ui::ClassProperty<T>* property,
               T value) {
    const int64_t old = handler->SetPropertyInternal(
        property, property->name, property->deallocator,
        ClassPropertyCaster<T>::ToInt64(value),
        ClassPropertyCaster<T>::ToInt64(property->default_value));
    if (property->deallocator &&
        old != ClassPropertyCaster<T>::ToInt64(property->default_value)) {
      (*property->deallocator)(old);
    }
    return value;
  }
  template <typename T>
  static T Get(const ::ui::PropertyHandler* handler,
               const ::ui::ClassProperty<T>* property,
               bool allow_cascade) {
    return ClassPropertyCaster<T>::FromInt64(handler->GetPropertyInternal(
        property, ClassPropertyCaster<T>::ToInt64(property->default_value),
        property->cascading && allow_cascade));
  }
  template <typename T>
  static void Clear(::ui::PropertyHandler* handler,
                    const ::ui::ClassProperty<T>* property) {
    Set(handler, property, property->default_value);
  }
};

}  // namespace subtle

template <typename T, typename U>
  requires(std::constructible_from<T, U &&> &&
           !std::same_as<T, std::remove_cvref_t<U>>)
T PropertyHandler::SetProperty(const ClassProperty<T>* property, U&& value) {
  return SetProperty(property, T(std::forward<U>(value)));
}

template <typename T, typename U>
  requires(!std::same_as<T*, std::remove_cvref_t<U>> &&
           std::constructible_from<T, U &&> && std::assignable_from<T&, U &&>)
T* PropertyHandler::SetProperty(const ClassProperty<T*>* property, U&& value) {
  // Reuse any existing allocation.
  if (T* const old = subtle::PropertyHelper::Get<T*>(this, property, false)) {
    T temp = std::exchange(*old, std::forward<U>(value));
    AfterPropertyChange(property, ClassPropertyCaster<T*>::ToInt64(&temp));
    return old;
  }
  return SetProperty(property, std::make_unique<T>(std::forward<U>(value)));
}

template <typename T, typename U>
  requires(std::convertible_to<U*, T*>)
T* PropertyHandler::SetProperty(const ClassProperty<T*>* property,
                                std::unique_ptr<U> value) {
  DCHECK(property->deallocator);
  return subtle::PropertyHelper::Set<T*>(this, property, value.release());
}

}  // namespace ui

// Macros to declare the property getter/setter template functions.
#define DECLARE_EXPORTED_UI_CLASS_PROPERTY_TYPE(EXPORT, T)                \
  namespace ui {                                                          \
  template <>                                                             \
  EXPORT T PropertyHandler::SetProperty(const ClassProperty<T>* property, \
                                        T value);                         \
  template <>                                                             \
  EXPORT T                                                                \
  PropertyHandler::GetProperty(const ClassProperty<T>* property) const;   \
  template <>                                                             \
  EXPORT void PropertyHandler::ClearProperty(                             \
      const ClassProperty<T>* property);                                  \
  }  // namespace ui

// Macros to instantiate the property getter/setter template functions.
#define DEFINE_EXPORTED_UI_CLASS_PROPERTY_TYPE(EXPORT, T)                    \
  namespace ui {                                                             \
  template <>                                                                \
  EXPORT T PropertyHandler::SetProperty(const ClassProperty<T>* property,    \
                                        T value) {                           \
    /* TODO(kylixrd, pbos): Once all the call-sites are fixed to only use */ \
    /* the unique_ptr version for owned properties, add the following */     \
    /* DCHECK to guard against passing raw pointers for owned properties. */ \
    /* DCHECK(!std::is_pointer<T>::value || !property->deallocator); */      \
    return subtle::PropertyHelper::Set<T>(this, property, value);            \
  }                                                                          \
  template <>                                                                \
  EXPORT T                                                                   \
  PropertyHandler::GetProperty(const ClassProperty<T>* property) const {     \
    return subtle::PropertyHelper::Get<T>(this, property, true);             \
  }                                                                          \
  template <>                                                                \
  EXPORT void PropertyHandler::ClearProperty(                                \
      const ClassProperty<T>* property) {                                    \
    subtle::PropertyHelper::Clear<T>(this, property);                        \
  }                                                                          \
  }  // namespace ui

#define DEFINE_UI_CLASS_PROPERTY_TYPE(T) \
  DEFINE_EXPORTED_UI_CLASS_PROPERTY_TYPE(, T)

#define DEFINE_UI_CLASS_PROPERTY_KEY_IMPL(TYPE, NAME, DEFAULT, CASCADES)     \
  static_assert(sizeof(TYPE) <= sizeof(int64_t), "property type too large"); \
  const ::ui::ClassProperty<TYPE> NAME##_Value = {DEFAULT, #NAME, CASCADES,  \
                                                  nullptr};                  \
  const ::ui::ClassProperty<TYPE>* const NAME = &NAME##_Value;

#define DEFINE_UI_CLASS_PROPERTY_KEY(TYPE, NAME, DEFAULT) \
  DEFINE_UI_CLASS_PROPERTY_KEY_IMPL(TYPE, NAME, DEFAULT, false)

#define DEFINE_CASCADING_UI_CLASS_PROPERTY_KEY(TYPE, NAME, DEFAULT) \
  DEFINE_UI_CLASS_PROPERTY_KEY_IMPL(TYPE, NAME, DEFAULT, true)

#define DEFINE_OWNED_UI_CLASS_PROPERTY_KEY_IMPL(TYPE, NAME, CASCADES) \
  static_assert(base::IsComplete<TYPE>);                              \
  static_assert(sizeof(TYPE*) <= sizeof(int64_t));                    \
  namespace {                                                         \
  void Deallocator##NAME(int64_t p) {                                 \
    delete ::ui::ClassPropertyCaster<TYPE*>::FromInt64(p);            \
  }                                                                   \
  constexpr ::ui::ClassProperty<TYPE*> NAME##_Value = {               \
      nullptr, #NAME, CASCADES, &Deallocator##NAME};                  \
  } /* namespace */                                                   \
  constexpr const ::ui::ClassProperty<TYPE*>* NAME = &NAME##_Value;

#define DEFINE_OWNED_UI_CLASS_PROPERTY_KEY(TYPE, NAME) \
  DEFINE_OWNED_UI_CLASS_PROPERTY_KEY_IMPL(TYPE, NAME, false)

#define DEFINE_CASCADING_OWNED_UI_CLASS_PROPERTY_KEY(TYPE, NAME) \
  DEFINE_OWNED_UI_CLASS_PROPERTY_KEY_IMPL(TYPE, NAME, true)

#endif  // UI_BASE_CLASS_PROPERTY_H_