0

[User Education] Preconditions can now cache data

This prevents having to re-acquire resources held by preconditions
associated with a particular promo, such as anchor elements and promo
lifecycle objects.

With ExtractCachedData(), this data can be transferred back to the
controller when the promo is selected to be shown. It is similarly
discarded if the promo is eliminated from the queue.

Care is taken to prohibit types that can cause leaks and dangling
references (via template constraints).

In order to achieve this, type idea of type-bearing identifiers has been
expanded from the Kombucha StateObserver library to a general concept.

API changes to expose cached data when a promo is returned from the
queue will be added in a follow-up CL.

Bug: 369887050
Change-Id: Ie3b631b22303ce538bec5c1fcb8b75105f447161
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6023112
Commit-Queue: Dana Fried <dfried@chromium.org>
Code-Coverage: findit-for-me@appspot.gserviceaccount.com <findit-for-me@appspot.gserviceaccount.com>
Reviewed-by: Emily Shack <emshack@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1383333}
This commit is contained in:
Dana Fried
2024-11-15 00:03:04 +00:00
committed by Chromium LUCI CQ
parent 7dce85630f
commit 81c3b0370d
13 changed files with 388 additions and 57 deletions

@ -35,6 +35,7 @@ source_set("common") {
"feature_promo/impl/feature_promo_queue.h",
"feature_promo/impl/feature_promo_queue_set.cc",
"feature_promo/impl/feature_promo_queue_set.h",
"feature_promo/impl/precondition_data.h",
"feature_promo/impl/precondition_list_provider.cc",
"feature_promo/impl/precondition_list_provider.h",
"help_bubble/help_bubble.cc",

@ -32,6 +32,16 @@ const std::string& FeaturePromoPreconditionBase::GetDescription() const {
return description_;
}
void FeaturePromoPreconditionBase::ExtractCachedData(
internal::PreconditionData::Collection& to_add_to) {
for (auto& [id, data] : data_) {
const auto result = to_add_to.emplace(id, std::move(data));
CHECK(result.second) << "Two different providers for precondition data: "
<< id;
}
data_.clear();
}
CachingFeaturePromoPrecondition::CachingFeaturePromoPrecondition(
Identifier identifier,
FeaturePromoResult::Failure failure,

@ -10,8 +10,10 @@
#include <vector>
#include "base/functional/callback.h"
#include "base/gtest_prod_util.h"
#include "base/memory/raw_ref.h"
#include "components/user_education/common/feature_promo/feature_promo_result.h"
#include "components/user_education/common/feature_promo/impl/precondition_data.h"
#include "ui/base/interaction/element_identifier.h"
namespace user_education {
@ -40,12 +42,19 @@ class FeaturePromoPrecondition {
// Gets whether the precondition is met and promos are allowed.
virtual bool IsAllowed() const = 0;
// Extracts any cached data from this precondition and adds it to `to_add_to`;
// future calls to this object may fail. Cached data likely reflects the most
// recent time `IsAllowed()` was called, and therefore that method should
// always be called first.
virtual void ExtractCachedData(
internal::PreconditionData::Collection& to_add_to) {}
protected:
FeaturePromoPrecondition() = default;
};
// Same as `FeaturePromoPrecondition`, but stores values for identifier,
// failure, and description.
// failure, and description, along with optional cached data.
class FeaturePromoPreconditionBase : public FeaturePromoPrecondition {
public:
using Identifier = ui::ElementIdentifier;
@ -57,6 +66,8 @@ class FeaturePromoPreconditionBase : public FeaturePromoPrecondition {
Identifier GetIdentifier() const override;
FeaturePromoResult::Failure GetFailure() const override;
const std::string& GetDescription() const override;
void ExtractCachedData(
internal::PreconditionData::Collection& to_add_to) override;
void set_failure(FeaturePromoResult::Failure failure) { failure_ = failure; }
@ -65,10 +76,46 @@ class FeaturePromoPreconditionBase : public FeaturePromoPrecondition {
FeaturePromoResult::Failure failure,
std::string description);
// Use this method to initialize the various types of data the precondition
// will support by passing in appropriate typed identifiers.
//
// Can be called any number of times with unique typed identifiers, or all
// at once.
template <typename... Args>
void InitCache(internal::PreconditionData::TypedIdentifier<Args>... args) {
(data_.emplace(
args.identifier(),
std::make_unique<internal::TypedPreconditionData<Args>>(args)),
...);
}
// Retrieve a reference to cached data held by the precondition, which can be
// used to get or set the value. InitCache() must have been called with
// the same `id`, and `ExtractData()` must not have been called.
//
// The data returned is mutable even thought the method is const, because it
// is expected to be used to cache data.
template <typename T>
T& GetCachedData(internal::PreconditionData::TypedIdentifier<T> id) const {
auto* const result = internal::PreconditionData::Get<T>(data_, id);
CHECK(result);
return *result;
}
private:
FRIEND_TEST_ALL_PREFIXES(FeaturePromoPreconditionTest, SetAndGetCachedData);
FRIEND_TEST_ALL_PREFIXES(FeaturePromoPreconditionTest,
GetCachedDataCrashesIfDataNotPresent);
FRIEND_TEST_ALL_PREFIXES(FeaturePromoPreconditionTest, ExtractCachedData);
FRIEND_TEST_ALL_PREFIXES(FeaturePromoPreconditionTest,
GetAfterExtractCachedDataFails);
const Identifier identifier_;
FeaturePromoResult::Failure failure_;
const std::string description_;
// Mutable so that data can be cached during retrieval.
mutable internal::PreconditionData::Collection data_;
};
// Represents a precondition that returns a cached value that is updated as it

@ -5,10 +5,13 @@
#include "components/user_education/common/feature_promo/feature_promo_precondition.h"
#include "base/functional/callback_forward.h"
#include "base/test/gtest_util.h"
#include "components/user_education/common/feature_promo/feature_promo_result.h"
#include "components/user_education/common/feature_promo/impl/precondition_data.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/interaction/expect_call_in_scope.h"
#include "ui/base/interaction/typed_identifier.h"
namespace user_education {
@ -25,8 +28,55 @@ constexpr FeaturePromoResult::Failure kPrecondFailure3 =
constexpr char kPrecondName[] = "Precond";
constexpr char kPrecondName2[] = "Precond2";
constexpr char kPrecondName3[] = "Precond3";
DEFINE_LOCAL_TYPED_IDENTIFIER_VALUE(int, kIntegerData);
DEFINE_LOCAL_TYPED_IDENTIFIER_VALUE(std::string, kStringData);
} // namespace
TEST(FeaturePromoPreconditionTest, SetAndGetCachedData) {
CachingFeaturePromoPrecondition precond1(kTestId, kPrecondFailure,
kPrecondName, false);
precond1.InitCache(kIntegerData);
precond1.InitCache(kStringData);
EXPECT_EQ(0, precond1.GetCachedData(kIntegerData));
EXPECT_EQ("", precond1.GetCachedData(kStringData));
precond1.GetCachedData(kIntegerData) = 2;
precond1.GetCachedData(kStringData) = "3";
EXPECT_EQ(2, precond1.GetCachedData(kIntegerData));
EXPECT_EQ("3", precond1.GetCachedData(kStringData));
}
TEST(FeaturePromoPreconditionTest, GetCachedDataCrashesIfDataNotPresent) {
CachingFeaturePromoPrecondition precond1(kTestId, kPrecondFailure,
kPrecondName, false);
EXPECT_CHECK_DEATH(precond1.GetCachedData(kIntegerData));
}
TEST(FeaturePromoPreconditionTest, ExtractCachedData) {
CachingFeaturePromoPrecondition precond1(kTestId, kPrecondFailure,
kPrecondName, false);
precond1.InitCache(kIntegerData, kStringData);
precond1.GetCachedData(kIntegerData) = 2;
precond1.GetCachedData(kStringData) = "3";
internal::PreconditionData::Collection coll;
precond1.ExtractCachedData(coll);
EXPECT_EQ(2, *internal::PreconditionData::Get(coll, kIntegerData));
EXPECT_EQ("3", *internal::PreconditionData::Get(coll, kStringData));
}
TEST(FeaturePromoPreconditionTest, GetAfterExtractCachedDataFails) {
CachingFeaturePromoPrecondition precond1(kTestId, kPrecondFailure,
kPrecondName, false);
precond1.InitCache(kIntegerData, kStringData);
precond1.GetCachedData(kIntegerData) = 2;
precond1.GetCachedData(kStringData) = "3";
internal::PreconditionData::Collection coll;
precond1.ExtractCachedData(coll);
EXPECT_CHECK_DEATH(precond1.GetCachedData(kIntegerData));
}
TEST(FeaturePromoPreconditionTest, CachingFeaturePromoPrecondition) {
CachingFeaturePromoPrecondition precond1(kTestId, kPrecondFailure,
kPrecondName, false);

@ -8,6 +8,8 @@
#include "components/feature_engagement/public/tracker.h"
#include "components/user_education/common/feature_promo/feature_promo_precondition.h"
#include "components/user_education/common/feature_promo/feature_promo_result.h"
#include "ui/base/interaction/element_tracker.h"
#include "ui/base/interaction/typed_identifier.h"
namespace user_education {
@ -75,6 +77,10 @@ bool MeetsFeatureEngagementCriteriaPrecondition::IsAllowed() const {
return true;
}
DEFINE_CLASS_TYPED_IDENTIFIER_VALUE(AnchorElementPrecondition,
ui::SafeElementReference,
kAnchorElement);
AnchorElementPrecondition::AnchorElementPrecondition(
const AnchorElementProvider& provider,
ui::ElementContext default_context)
@ -82,12 +88,16 @@ AnchorElementPrecondition::AnchorElementPrecondition(
FeaturePromoResult::kBlockedByUi,
"Anchor Element Visible"),
provider_(provider),
default_context_(default_context) {}
default_context_(default_context) {
InitCache(kAnchorElement);
}
AnchorElementPrecondition::~AnchorElementPrecondition() = default;
bool AnchorElementPrecondition::IsAllowed() const {
return nullptr != provider_->GetAnchorElement(default_context_);
auto* const element = provider_->GetAnchorElement(default_context_);
GetCachedData(kAnchorElement) = element;
return element != nullptr;
}
} // namespace user_education

@ -10,6 +10,8 @@
#include "components/user_education/common/anchor_element_provider.h"
#include "components/user_education/common/feature_promo/feature_promo_precondition.h"
#include "ui/base/interaction/element_identifier.h"
#include "ui/base/interaction/element_tracker.h"
#include "ui/base/interaction/typed_identifier.h"
namespace user_education {
@ -60,6 +62,9 @@ class MeetsFeatureEngagementCriteriaPrecondition
// Represents the requirement that an anchor element is present and visible.
class AnchorElementPrecondition : public FeaturePromoPreconditionBase {
public:
DECLARE_CLASS_TYPED_IDENTIFIER_VALUE(ui::SafeElementReference,
kAnchorElement);
explicit AnchorElementPrecondition(const AnchorElementProvider& provider,
ui::ElementContext default_context);
~AnchorElementPrecondition() override;

@ -9,6 +9,7 @@
#include "components/feature_engagement/test/mock_tracker.h"
#include "components/user_education/common/feature_promo/feature_promo_precondition.h"
#include "components/user_education/common/feature_promo/feature_promo_result.h"
#include "components/user_education/common/feature_promo/impl/precondition_data.h"
#include "components/user_education/test/mock_anchor_element_provider.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
@ -122,4 +123,45 @@ TEST(CommonPreconditionsTest, AnchorElementPrecondition) {
EXPECT_FALSE(precond.IsAllowed());
}
TEST(CommonPreconditionsTest,
AnchorElementPrecondition_ExtractCachedDataReturnsElement) {
DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kTestId);
static const ui::ElementContext kTestContext(1);
ui::test::TestElement el(kTestId, kTestContext);
el.Show();
test::MockAnchorElementProvider provider;
AnchorElementPrecondition precond(provider, kTestContext);
EXPECT_CALL(provider, GetAnchorElement(kTestContext))
.WillOnce(testing::Return(&el));
EXPECT_TRUE(precond.IsAllowed());
internal::PreconditionData::Collection coll;
precond.ExtractCachedData(coll);
auto* data = internal::PreconditionData::Get(
coll, AnchorElementPrecondition::kAnchorElement);
ASSERT_NE(nullptr, data);
EXPECT_EQ(&el, data->get());
}
TEST(CommonPreconditionsTest,
AnchorElementPrecondition_ExtractCachedDataReturnsNull) {
static const ui::ElementContext kTestContext(1);
test::MockAnchorElementProvider provider;
AnchorElementPrecondition precond(provider, kTestContext);
EXPECT_CALL(provider, GetAnchorElement(kTestContext))
.WillOnce(testing::Return(nullptr));
EXPECT_FALSE(precond.IsAllowed());
internal::PreconditionData::Collection coll;
precond.ExtractCachedData(coll);
auto* data = internal::PreconditionData::Get(
coll, AnchorElementPrecondition::kAnchorElement);
ASSERT_NE(nullptr, data);
EXPECT_EQ(nullptr, data->get());
}
} // namespace user_education

@ -0,0 +1,112 @@
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef COMPONENTS_USER_EDUCATION_COMMON_FEATURE_PROMO_IMPL_PRECONDITION_DATA_H_
#define COMPONENTS_USER_EDUCATION_COMMON_FEATURE_PROMO_IMPL_PRECONDITION_DATA_H_
#include <concepts>
#include <type_traits>
#include "base/memory/raw_ptr.h"
#include "base/memory/raw_ref.h"
#include "base/types/is_instantiation.h"
#include "ui/base/interaction/element_identifier.h"
#include "ui/base/interaction/typed_identifier.h"
namespace user_education::internal {
// Base already has an is_raw_ref, but there is no is_raw_ptr, so implement it
// here.
template <typename T>
struct is_raw_ptr : std::false_type {};
template <typename T, base::RawPtrTraits Traits>
struct is_raw_ptr<raw_ptr<T, Traits>> : std::true_type {};
// A value is cacheable if:
// - it is moveable
// - it is default-constructable
// - it is not a pointer or pointer-like type
// (pointers can lead to dangling references)
//
// To hold polymorphic or non-moveable objects, use std::unique_ptr.
template <typename T>
concept PreconditionCacheable =
std::movable<T> && std::default_initializable<T> && !std::is_pointer_v<T> &&
!is_raw_ptr<T>::value && !base::internal::is_raw_ref_v<T>;
template <typename T>
requires PreconditionCacheable<T>
class TypedPreconditionData;
// Preconditions can cache and retrieve data; this ensures that computations
// aren't done multiple times, and that computed data can be retrieved from
// preconditions before they are removed from a queue and discarded.
//
// This is a base class that ensures proper polymorphism and cleanup in the
// typed implementation below.
class PreconditionData {
public:
using Identifier = ui::ElementIdentifier;
template <typename T>
using TypedIdentifier = ui::TypedIdentifier<T>;
using Collection = std::map<Identifier, std::unique_ptr<PreconditionData>>;
explicit PreconditionData(Identifier identifier) : identifier_(identifier) {}
PreconditionData(const PreconditionData& other) = delete;
void operator=(const PreconditionData& other) = delete;
virtual ~PreconditionData() = default;
Identifier identifier() const { return identifier_; }
// Retrieves typed data from a data collection, or null if not found.
template <typename T>
static T* Get(Collection& coll, TypedIdentifier<T> id) {
const auto it = coll.find(id.identifier());
return it == coll.end() ? nullptr : &it->second->AsTyped(id).data();
}
private:
// Retrieves this object as a typed object. The identifier must match.
template <typename T>
TypedPreconditionData<T>& AsTyped(TypedIdentifier<T> id);
const Identifier identifier_;
};
// This represents typed cached data retrieved from preconditions.
// Use `PreconditionData::AsTyped<>()` to retrieve this version, then `data()`
// to access the data.
//
// Type is enforced by the use of a unique `TypedIdentifier<T>`, which must be
// supplied both to create and retrieve the corresponding typed data. This
// ensures a `PreconditionData` object is never cast to the wrong type of
// `TypedPreconditionData`.
template <typename T>
requires PreconditionCacheable<T>
class TypedPreconditionData : public PreconditionData {
public:
using Type = T;
template <typename U>
requires std::same_as<T, U>
explicit TypedPreconditionData(TypedIdentifier<U> identifier)
: PreconditionData(identifier.identifier()) {}
~TypedPreconditionData() override = default;
T& data() { return data_; }
const T& data() const { return data_; }
private:
T data_ = T();
};
template <typename T>
TypedPreconditionData<T>& PreconditionData::AsTyped(TypedIdentifier<T> id) {
CHECK_EQ(id.identifier(), identifier_);
return *static_cast<TypedPreconditionData<T>*>(this);
}
} // namespace user_education::internal
#endif // COMPONENTS_USER_EDUCATION_COMMON_FEATURE_PROMO_IMPL_PRECONDITION_DATA_H_

@ -138,6 +138,7 @@ component("base") {
"interaction/framework_specific_implementation.h",
"interaction/interaction_sequence.cc",
"interaction/interaction_sequence.h",
"interaction/typed_identifier.h",
"l10n/formatter.cc",
"l10n/formatter.h",
"l10n/l10n_font_util.cc",

@ -527,6 +527,14 @@ SafeElementReference::SafeElementReference(const SafeElementReference& other)
Subscribe();
}
SafeElementReference& SafeElementReference::operator=(TrackedElement* el) {
if (element_ != el) {
element_ = el;
Subscribe();
}
return *this;
}
SafeElementReference& SafeElementReference::operator=(
SafeElementReference&& other) {
if (&other != this) {

@ -271,6 +271,7 @@ class COMPONENT_EXPORT(UI_BASE) SafeElementReference {
explicit SafeElementReference(TrackedElement* element);
SafeElementReference(SafeElementReference&& other);
SafeElementReference(const SafeElementReference& other);
SafeElementReference& operator=(TrackedElement* element);
SafeElementReference& operator=(SafeElementReference&& other);
SafeElementReference& operator=(const SafeElementReference& other);
~SafeElementReference();

@ -13,6 +13,7 @@
#include "base/memory/raw_ptr.h"
#include "base/scoped_observation.h"
#include "ui/base/interaction/element_identifier.h"
#include "ui/base/interaction/typed_identifier.h"
namespace ui::test {
@ -102,54 +103,8 @@ class ObservationStateObserver : public StateObserver<T>, public Observer {
base::ScopedObservation<Source, Observer> observation_{this};
};
// Uniquely identifies a state associated with `ObserverType`.
//
// Use the DECLARE/DEFINE macros below to create unique identifiers, similarly
// to how ElementIdentifier, etc. work.
template <typename ObserverType>
class StateIdentifier final {
public:
constexpr StateIdentifier() = default;
explicit constexpr StateIdentifier(ElementIdentifier identifier)
: identifier_(identifier) {}
constexpr ElementIdentifier identifier() const { return identifier_; }
constexpr explicit operator bool() const {
return static_cast<bool>(identifier_);
}
constexpr bool operator!() const { return !identifier_; }
constexpr bool operator==(const StateIdentifier<ObserverType>& other) const {
return identifier_ == other.identifier_;
}
constexpr bool operator!=(const StateIdentifier<ObserverType>& other) const {
return identifier_ != other.identifier_;
}
constexpr bool operator<(const StateIdentifier<ObserverType>& other) const {
return identifier_ < other.identifier_;
}
private:
ElementIdentifier identifier_;
};
template <typename T>
extern void PrintTo(StateIdentifier<T> state_identifier, std::ostream* os) {
*os << "StateIdentifier " << state_identifier.identifier().GetRawValue()
<< " [" << state_identifier.identifier().GetName() << "]";
}
template <typename T>
extern std::ostream& operator<<(std::ostream& os,
StateIdentifier<T> state_identifier) {
PrintTo(state_identifier, os);
return os;
}
using StateIdentifier = TypedIdentifier<T>;
} // namespace ui::test
@ -171,15 +126,12 @@ extern std::ostream& operator<<(std::ostream& os,
// identifiers.
#define DECLARE_STATE_IDENTIFIER_VALUE(ObserverType, Name) \
DECLARE_ELEMENT_IDENTIFIER_VALUE(Name##Impl); \
extern const ui::test::StateIdentifier<ObserverType> Name
DECLARE_TYPED_IDENTIFIER_VALUE(ObserverType, Name)
#define DEFINE_STATE_IDENTIFIER_VALUE(ObserverType, Name) \
DEFINE_ELEMENT_IDENTIFIER_VALUE(Name##Impl); \
constexpr ui::test::StateIdentifier<ObserverType> Name(Name##Impl)
DEFINE_TYPED_IDENTIFIER_VALUE(ObserverType, Name)
#define DEFINE_LOCAL_STATE_IDENTIFIER_VALUE(ObserverType, Name) \
DEFINE_MACRO_ELEMENT_IDENTIFIER_VALUE(__FILE__, __LINE__, Name##Impl); \
constexpr ui::test::StateIdentifier<ObserverType> Name(Name##Impl)
#define DEFINE_LOCAL_STATE_IDENTIFIER_VALUE(ObserverType, Name) \
DEFINE_MACRO_TYPED_IDENTIFIER_VALUE(__FILE__, __LINE__, ObserverType, Name)
#endif // UI_BASE_INTERACTION_STATE_OBSERVER_H_

@ -0,0 +1,92 @@
// Copyright 2024 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_TYPED_IDENTIFIER_H_
#define UI_BASE_INTERACTION_TYPED_IDENTIFIER_H_
#include "ui/base/interaction/element_identifier.h"
namespace ui {
// Identifier that also carries type information.
//
// Use the DECLARE/DEFINE macros below to create unique identifiers, similarly
// to how ElementIdentifier, etc. work.
template <typename Type>
class TypedIdentifier final {
public:
constexpr TypedIdentifier() = default;
explicit constexpr TypedIdentifier(ElementIdentifier identifier)
: identifier_(identifier) {}
constexpr ElementIdentifier identifier() const { return identifier_; }
constexpr explicit operator bool() const {
return static_cast<bool>(identifier_);
}
constexpr bool operator!() const { return !identifier_; }
constexpr bool operator==(const TypedIdentifier<Type>& other) const {
return identifier_ == other.identifier_;
}
constexpr bool operator!=(const TypedIdentifier<Type>& other) const {
return identifier_ != other.identifier_;
}
constexpr bool operator<(const TypedIdentifier<Type>& other) const {
return identifier_ < other.identifier_;
}
private:
ElementIdentifier identifier_;
};
template <typename T>
extern void PrintTo(TypedIdentifier<T> identifier, std::ostream* os) {
*os << "TypedIdentifier " << identifier.identifier().GetRawValue() << " ["
<< identifier.identifier().GetName() << "]";
}
template <typename T>
extern std::ostream& operator<<(std::ostream& os,
TypedIdentifier<T> identifier) {
PrintTo(identifier, os);
return os;
}
} // namespace ui
// The following macros create a typed identifier value, and mimic the similar
// macros for ElementIdentifier, except that they also include a type.
#define DECLARE_TYPED_IDENTIFIER_VALUE(Type, Name) \
DECLARE_ELEMENT_IDENTIFIER_VALUE(Name##Impl); \
extern const ui::TypedIdentifier<Type> Name
#define DEFINE_TYPED_IDENTIFIER_VALUE(Type, Name) \
DEFINE_ELEMENT_IDENTIFIER_VALUE(Name##Impl); \
constexpr ui::TypedIdentifier<Type> Name(Name##Impl)
#define DECLARE_CLASS_TYPED_IDENTIFIER_VALUE(Type, Name) \
DECLARE_CLASS_ELEMENT_IDENTIFIER_VALUE(Name##Impl); \
static constexpr ui::TypedIdentifier<Type> Name { \
Name##Impl \
}
#define DEFINE_CLASS_TYPED_IDENTIFIER_VALUE(Class, Type, Name) \
DEFINE_CLASS_ELEMENT_IDENTIFIER_VALUE(Class, Name##Impl); \
constexpr ui::TypedIdentifier<Type> Class::Name
#define DEFINE_LOCAL_TYPED_IDENTIFIER_VALUE(Type, Name) \
DEFINE_MACRO_ELEMENT_IDENTIFIER_VALUE(__FILE__, __LINE__, Name##Impl); \
constexpr ui::TypedIdentifier<Type> Name(Name##Impl)
#define DEFINE_MACRO_TYPED_IDENTIFIER_VALUE(File, Line, Type, Name) \
DEFINE_MACRO_ELEMENT_IDENTIFIER_VALUE(File, Line, Name##Impl); \
constexpr ui::TypedIdentifier<Type> Name(Name##Impl)
#endif // UI_BASE_INTERACTION_TYPED_IDENTIFIER_H_