910e62b5创建于 1月15日历史提交
// Copyright 2021 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_INTERACTION_ELEMENT_IDENTIFIER_H_
#define UI_BASE_INTERACTION_ELEMENT_IDENTIFIER_H_

#include <stdint.h>

#include <concepts>
#include <cstdint>
#include <ostream>
#include <set>

#include "base/component_export.h"
#include "base/memory/raw_ptr_exclusion.h"
#include "base/numerics/safe_conversions.h"
#include "base/types/pass_key.h"

namespace views {
class ElementTrackerViews;
}

namespace user_education {
class HelpBubbleHandler;
}

// Overview:
// ElementIdentifier provides a named opaque value that can be used to identify
// individual (or potentially groups of) elements in the UI.
//
// Unique identifier constants must be both declared and defined. To create a
// publicly-visible identifier, declare a new unique value in your .h file,
// with the following; the string name of the identifier will be the same as
// the identifier's C++ identifier (in this case, kMyIdentifierName):
//
//   DECLARE_ELEMENT_IDENTIFIER_VALUE(kMyIdentifierName);
//
// If the identifier should be exported, declare it with the following instead:
//
//   DECLARE_EXPORTED_ELEMENT_IDENTIFIER_VALUE(MY_EXPORT, kMyIdentifierName);
//
// Regardless of whether the declared identifier is exported or not, make sure
// it is defined in the corresponding .cc file:
//
//   DEFINE_ELEMENT_IDENTIFIER_VALUE(kMyIdentifierName);
//
// If you want to add an identifier as a class member, use the following; the
// string name of the identifier will be in the form
// "MyClass::kMyIdentifierName":
//
//   class MyClass {
//     DECLARE_CLASS_ELEMENT_IDENTIFIER_VALUE(kMyIdentifierName);
//   };
//
// Then in the corresponding .cc file, add the following:
//
//   DEFINE_CLASS_ELEMENT_IDENTIFIER_VALUE(MyClass, kMyIdentifierValue);
//
// If you want to create an identifier local to a .cc file or to a method, you
// can instead use the following all-in-one declaration. Note that this is only
// really useful in tests and that the resulting identifier name is mangled
// with the file and line number:
//
//   DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kMyIdentifierName);
//
// That's it! You can now initialize an ElementIdentifier using this value, or
// pass it directly to a method:
//
//   ElementIdentifier my_id = kMyIdentifierName;
//   ElementIdentifier my_id2 = my_id;
//   MyFuncThatTakesAnIdentifier(kMyIdentifierName);
//   MyFuncThatTakesAnIdentifier(my_id2);
//
// ElementIdentifier behaves more or less like other mutable primitive types; it
// is default-constructable (producing a null value) and supports the ==, !=, <,
// !, and (bool) operators as well as assignment and copy [1]. This means you
// can use ElementIdentifier as a key in std::set and std::map, and it is safe
// to use in both DCHECK and test assertions:
//
//   ElementIdentifier id1;
//   ElementIdentifier id2 = kMyIdentifierName;
//   std::map<ElementIdentifier, int> map;
//   map.emplace(id2, 4);
//   DCHECK(!id1);
//   EXPECT_TRUE(static_cast<bool>(id2));
//   DCHECK_NE(id1, id2);
//   EXPECT_FALSE(base::Contains(map, id1));
//   ASSERT_EQ(4, map[id2]);
//
// -----
//
// [1] Please note that while operator < will provide a strict ordering, the
//   specific order of two ElementIdentifier constants may vary by build and
//   should not be relied upon; operator < is only provided for compatibility
//   with sorted STL containers.

namespace ui {

template <typename T>
class ClassPropertyCaster;

namespace internal {

// Defines the underlying value that an ElementIdentifier holds (namely, the
// address of an instance of this class). Because these objects are only
// declared statically, the value of an ElementIdentifier is always valid and
// two ElementIdentifiers are equal if and only if they hold the address of the
// same instance of this class.
//
// Instances of this object are named for debugging/logging purposes only, the
// value of name() should never be used for any other purpose.
struct ElementIdentifierImpl {
  // The name of the identifier; only used in testing via PrintTo().
  const char* const name;
};

}  // namespace internal

class ElementTracker;

// Holds a globally-unique, value-typed identifier from a set of identifiers
// which can be declared in any static scope.
//
// This type is comparable and supports operator bool and negation, where
// default-constructed instances have false value and all other values evaluate
// as true. It can also be used as the key in std::set, std::map, and similar
// collections.
class COMPONENT_EXPORT(UI_BASE_INTERACTION) ElementIdentifier final {
 public:
  // Creates a null identifier.
  constexpr ElementIdentifier() = default;

  // Avoid this constructor - it is used internally by the
  // DECLARE_ELEMENT_IDENTIFIER_VALUE() macro.
  explicit constexpr ElementIdentifier(
      const internal::ElementIdentifierImpl* provider)
      : handle_(provider) {}

  constexpr explicit operator bool() const { return handle_ != nullptr; }

  constexpr bool operator!() const { return !handle_; }

  friend constexpr bool operator==(const ElementIdentifier&,
                                   const ElementIdentifier&) = default;

  // TODO(crbug.com/333028921): Operator < cannot be constexpr because memory
  // order of Impl objects is not strictly known at compile time. Fix this...
  // somehow? Possibilities include compile-time hashing of identifier string.
  friend auto operator<=>(const ElementIdentifier&,
                          const ElementIdentifier&) = default;

  // Retrieves the element name, or the empty string if none.
  std::string GetName() const;

  // Retrieve a known ElementIdentifier by name. An ElementIdentifier is *known*
  // if a TrackedElement has been created with the id, or if the value of the
  // identifier has been serialized using GetRawValue() or GetName().
  static ElementIdentifier FromName(const char* name);

  // Included for interoperability with PropertyHandler. Retrieves an element
  // identifier from the result of calling GetRawValue(). The `value` passed in
  // MUST either have been generated by calling GetRawValue() or be zero (this
  // is strictly enforced even in release builds).
  static ElementIdentifier FromRawValue(intptr_t value);

 private:
  using KnownIdentifiers = std::set<const internal::ElementIdentifierImpl*>;

  friend class ClassPropertyCaster<ElementIdentifier>;
  friend class ElementTracker;
  friend class ElementIdentifierTest;
  friend class ElementTrackerIdentifierTest;
  friend COMPONENT_EXPORT(UI_BASE_INTERACTION) void PrintTo(
      ElementIdentifier element_identifier,
      std::ostream* os);

  // Included for interoperability with PropertyHandler.
  intptr_t GetRawValue() const;

  // Registers a non-null identifier as known. Has no effect if the element is
  // already registered.
  static void RegisterKnownIdentifier(ElementIdentifier element_dentifier);

  // Returns the singleton set of known identifiers.
  static KnownIdentifiers& GetKnownIdentifiers();

  // The value of the identifier. Because all non-null values point to static
  // ElementIdentifierImpl objects this can be treated as a value from a set of
  // unique, opaque handles.
  // RAW_PTR_EXCLUSION: Since all ElementIdentifierImpl instances are
  // statically-allocated, this pointer can never dangle.
  RAW_PTR_EXCLUSION const internal::ElementIdentifierImpl* handle_ = nullptr;
};

// The context of an element is unique to the top-level, primary window that the
// element is associated with. Elements in secondary UI (bubbles, menus,
// drop-downs, etc.) will be associated with the same context as elements in the
// primary window itself.
//
// The value used should be consistent across a toolkit and unique between
// primary windows; a memory address or handle of the window object can
// typically be used (e.g. in Views, we use the address of the primary window's
// Widget). A zero or null value should always correspond to "no context".
//
// Please note that you must consistently use the same pointer or handle type
// across your framework when creating contexts; because of the vagaries of C++
// up- and down-casting (especially with multiple inheritance) constructing an
// ElementContext from different pointer types can produce different results,
// even for the same underlying object.
//
// ElementContext objects are assignable, have boolean value based on whether
// the underlying value is null, and support operator < for use in maps and
// sets.
class COMPONENT_EXPORT(UI_BASE_INTERACTION) ElementContext {
 public:
  ElementContext() = default;

  // Only specific classes are allowed to be authoritative sources of element
  // contexts. All other code should defer to these classes.
  template <class T, class U>
    requires std::same_as<U, views::ElementTrackerViews> ||
             std::same_as<U, user_education::HelpBubbleHandler>
  explicit ElementContext(T* value, base::PassKey<U>)
      : value_(reinterpret_cast<uintptr_t>(value)) {}

  explicit operator const void*() const {
    return reinterpret_cast<const void*>(value_);
  }
  explicit operator uintptr_t() const { return value_; }
  explicit operator intptr_t() const { return static_cast<intptr_t>(value_); }
  explicit operator bool() const { return value_ != 0; }
  bool operator!() const { return !value_; }
  friend bool operator==(const ElementContext&,
                         const ElementContext&) = default;
  friend auto operator<=>(const ElementContext&,
                          const ElementContext&) = default;

  // Use this to create a fake context for testing. For normal contexts, rely
  // on one of the classes explicitly allowed by `ElementContext(T*)` above.
  template <typename T>
  static ElementContext CreateFakeContextForTesting(T* value) {
    return ElementContext(reinterpret_cast<uintptr_t>(value));
  }
  template <typename T>
    requires std::convertible_to<T, uintptr_t>
  static consteval ElementContext CreateFakeContextForTesting(T value) {
    return ElementContext(static_cast<uintptr_t>(value));
  }

 private:
  explicit constexpr ElementContext(uintptr_t value) : value_(value) {}

  uintptr_t value_ = 0;
};

COMPONENT_EXPORT(UI_BASE_INTERACTION)
extern void PrintTo(ElementIdentifier element_identifier, std::ostream* os);

COMPONENT_EXPORT(UI_BASE_INTERACTION)
extern void PrintTo(ElementContext element_context, std::ostream* os);

COMPONENT_EXPORT(UI_BASE_INTERACTION)
extern std::ostream& operator<<(std::ostream& os,
                                ElementIdentifier element_identifier);

COMPONENT_EXPORT(UI_BASE_INTERACTION)
extern std::ostream& operator<<(std::ostream& os,
                                ElementContext element_context);

// Required for interoperability with PropertyHandler.
template <>
class ClassPropertyCaster<ui::ElementIdentifier> {
 public:
  static int64_t ToInt64(ui::ElementIdentifier x) { return x.GetRawValue(); }
  static ui::ElementIdentifier FromInt64(int64_t x) {
    return ui::ElementIdentifier::FromRawValue(base::checked_cast<intptr_t>(x));
  }
};

}  // namespace ui

// Declaring identifiers outside a scope:
//
// Note: if you need to use the identifier outside the current component, use
// DECLARE/DEFINE_EXPORTED_... below.

// Use this code in the .h file to declare a new identifier.
#define DECLARE_ELEMENT_IDENTIFIER_VALUE(IdentifierName)                       \
  extern const ::ui::internal::ElementIdentifierImpl IdentifierName##Provider; \
  inline constexpr ::ui::ElementIdentifier IdentifierName(                     \
      &IdentifierName##Provider)

// Use this code in the .cc file to define a new identifier.
#define DEFINE_ELEMENT_IDENTIFIER_VALUE(IdentifierName)                  \
  const ::ui::internal::ElementIdentifierImpl IdentifierName##Provider { \
    #IdentifierName                                                      \
  }

// Declaring identifiers that can be used in other components:
//
// Note: unlike other declarations, this identifier will not be constexpr in
// most cases.

// Use this code in the .h file to declare a new exported identifier.
#define DECLARE_EXPORTED_ELEMENT_IDENTIFIER_VALUE(ExportName, IdentifierName) \
  ExportName extern const ::ui::internal::ElementIdentifierImpl               \
      IdentifierName##Provider;                                               \
  ExportName extern const ::ui::ElementIdentifier IdentifierName

// Use this code in the .cc file to define a new exported identifier.
#define DEFINE_EXPORTED_ELEMENT_IDENTIFIER_VALUE(IdentifierName)        \
  const ::ui::internal::ElementIdentifierImpl IdentifierName##Provider{ \
      #IdentifierName};                                                 \
  const ::ui::ElementIdentifier IdentifierName(&IdentifierName##Provider)

// Declaring identifiers in a class:

// Use this code in your class declaration in its .h file to declare an
// identifier that is scoped to your class.
#define DECLARE_CLASS_ELEMENT_IDENTIFIER_VALUE(IdentifierName)                 \
  static const ::ui::internal::ElementIdentifierImpl IdentifierName##Provider; \
  static constexpr ::ui::ElementIdentifier IdentifierName {                    \
    &IdentifierName##Provider                                                  \
  }

// Use this code in your class definition .cc file to define the member
// variables
#define DEFINE_CLASS_ELEMENT_IDENTIFIER_VALUE(ClassName, IdentifierName)    \
  const ::ui::internal::ElementIdentifierImpl                               \
      ClassName::IdentifierName##Provider{#ClassName "::" #IdentifierName}; \
  constexpr ::ui::ElementIdentifier ClassName::IdentifierName

// Declaring local identifiers in functions, class methods, or local to a .cc
// file (often used in tests). File and line are included to guarantee that the
// text of the name generated is unique, though that makes the exact text
// harder to predict.

// This helper macro is required because of how __LINE__ is handled when passed
// between macros, you need an intermediate macro in order to stringify it.
// DO NOT CALL DIRECTLY; used by DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE().
#define LOCAL_ELEMENT_IDENTIFIER_NAME(File, Line, Name) \
  File "::" #Line "::" #Name

// Use this code to declare a local identifier from within a macro; you should
// pass the __FILE__ and __LINE__ values for `File` and `Line`. The name will be
// mangled with the file and line so that it can be used in local or module
// scope (typically in tests) without having to worry about name collisions.
#define DEFINE_MACRO_ELEMENT_IDENTIFIER_VALUE(File, Line, IdentifierName) \
  static constexpr ::ui::internal::ElementIdentifierImpl                  \
      IdentifierName##Provider{                                           \
          LOCAL_ELEMENT_IDENTIFIER_NAME(File, Line, IdentifierName)};     \
  constexpr ::ui::ElementIdentifier IdentifierName(&IdentifierName##Provider)

// Use this code to declare a local identifier in a function body or module
// scope. The name will be mangled with the file and line so that it can be used
// (typically in tests) without having to worry about name collisions.
#define DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(IdentifierName) \
  DEFINE_MACRO_ELEMENT_IDENTIFIER_VALUE(__FILE__, __LINE__, IdentifierName)

#endif  // UI_BASE_INTERACTION_ELEMENT_IDENTIFIER_H_