1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348
|
// 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_TRACKER_H_
#define UI_BASE_INTERACTION_ELEMENT_TRACKER_H_
#include <concepts>
#include <list>
#include <map>
#include <memory>
#include <optional>
#include <vector>
#include "base/callback_list.h"
#include "base/component_export.h"
#include "base/gtest_prod_util.h"
#include "base/memory/raw_ptr.h"
#include "base/no_destructor.h"
#include "ui/base/interaction/element_identifier.h"
#include "ui/base/interaction/framework_specific_implementation.h"
#include "ui/gfx/geometry/rect.h"
namespace ui {
// Represents a unique type of event, you may create these as needed using the
// DECLARE_CUSTOM_ELEMENT_EVENT_TYPE() and DEFINE_CUSTOM_ELEMENT_EVENT_TYPE()
// macros (see definitions at the bottom of this file).
//
// For testing purposes, if you need a local event type guaranteed to avoid
// global name collisions, use DEFINE_LOCAL_ELEMENT_EVENT_TYPE() instead.
//
// Currently, custom event types are imlpemented using ElementIdentifier, since
// both have the same API requirements.
using CustomElementEventType = ElementIdentifier;
// Represents a visible UI element in a platform-agnostic manner.
//
// A pointer to this object may be stored after the element becomes visible, but
// is only valid until the "element hidden" event is called for this element;
// see `ElementTracker` below. If you want to hold a pointer that will be valid
// only as long as the element is visible, use a SafeElementReference.
//
// You should derive a class for each UI framework whose elements you wish to
// track. See README.md for information on how to create your own framework
// implementations.
class COMPONENT_EXPORT(UI_BASE) TrackedElement
: public FrameworkSpecificImplementation {
public:
~TrackedElement() override;
ElementIdentifier identifier() const { return identifier_; }
ElementContext context() const { return context_; }
// Returns the bounds of the element on the screen, or an empty rect if it
// cannot be determined.
//
// Note: it is not yet necessary to set up a general method for listening to
// bounds changes, as they are (a) somewhat difficult to track and (b) tend to
// be handled correctly by most frameworks in terms of element positioning
// (e.g. anchoring logic for User Education help bubbles). Specific
// implementations that need to do additional tracking can implement their own
// methods.
virtual gfx::Rect GetScreenBounds() const;
// FrameworkSpecificImplementation:
std::string ToString() const override;
protected:
TrackedElement(ElementIdentifier identifier, ElementContext context);
private:
// The identifier for this element that will be used by ElementTracker to
// retrieve it.
const ElementIdentifier identifier_;
// The context of the element, corresponding to the main window the element is
// associated with. See the ElementContext documentation in
// element_identifier.h for more information on how to create appropriate
// contexts for each UI framework.
const ElementContext context_;
};
// Provides a delegate for UI framework-specific implementations to notify of
// element tracker events.
//
// An element must be visible before events can be sent for that element;
// NotifyElementHidden() must be called before the element is destroyed or
// changes context or identifier.
class COMPONENT_EXPORT(UI_BASE) ElementTrackerFrameworkDelegate {
public:
virtual void NotifyElementShown(TrackedElement* element) = 0;
virtual void NotifyElementActivated(TrackedElement* element) = 0;
virtual void NotifyElementHidden(TrackedElement* element) = 0;
virtual void NotifyCustomEvent(TrackedElement* element,
CustomElementEventType event_type) = 0;
};
// Tracks elements as they become visible, are activated by the user, and
// eventually become hidden. Tracks only visible elements.
//
// NOT THREAD SAFE. Should only be accessed from the main UI thread.
class COMPONENT_EXPORT(UI_BASE) ElementTracker
: ElementTrackerFrameworkDelegate {
public:
// Callback that subscribers receive when the specified event occurs.
// Note that if an element is destroyed in the middle of calling callbacks,
// some callbacks may not be called and others may be called with a null
// argument, so please check the validity of the element pointer.
using Callback = base::RepeatingCallback<void(TrackedElement*)>;
using Subscription = base::CallbackListSubscription;
using ElementList = std::vector<TrackedElement*>;
using Contexts = std::set<ElementContext>;
// Identifier that should be used by each framework to create a
// TrackedElement from an element that does not alreayd have an identifier.
//
// Currently, the identifier is not removed when the code that needs the
// element completes, but in the future we may implement a ref-counting
// system for systems that use a temporary identifier so that it does not
// persist longer than it is needed.
DECLARE_CLASS_ELEMENT_IDENTIFIER_VALUE(kTemporaryIdentifier);
// Gets the element tracker to be used by clients to subscribe to and receive
// events.
static ElementTracker* GetElementTracker();
// Gets the delegate to be used by specific UI frameworks to send events.
static ElementTrackerFrameworkDelegate* GetFrameworkDelegate();
// Returns either the one element matching the given `id` and `context`, or
// null if there are none. Will generate an error if there is more than one
// element with `id` in `context`. Only visible elements are returned.
//
// Use when you want to verify that there's only one matching element in the
// given context.
TrackedElement* GetUniqueElement(ElementIdentifier id,
ElementContext context);
// Returns the same result as GetUniqueElement() except that no error is
// generated if there is more than one matching element.
//
// Use when you just need *an* element in the given context, and don't care if
// there's more than one.
TrackedElement* GetFirstMatchingElement(ElementIdentifier id,
ElementContext context);
// Returns an element with identifier `id` from any context, or null if not
// found. Contexts are not guaranteed to be searched in any particular order.
TrackedElement* GetElementInAnyContext(ElementIdentifier id);
// Returns a list of all visible elements with identifier `id` in `context`.
// The list may be empty.
ElementList GetAllMatchingElements(ElementIdentifier id,
ElementContext context);
// Returns all known elements with the given `id`. The context for each can
// be retrieved from the TrackedElement itself. No order is guaranteed.
ElementList GetAllMatchingElementsInAnyContext(ElementIdentifier id);
// Returns whether an element with identifier `id` in `context` is visible.
bool IsElementVisible(ElementIdentifier id, ElementContext context);
// Adds a callback that will be called whenever an element with identifier
// `id` in `context` becomes visible.
Subscription AddElementShownCallback(ElementIdentifier id,
ElementContext context,
Callback callback);
// Adds a callback that will be called whenever an element with identifier
// `id` becomes visible in any context.
Subscription AddElementShownInAnyContextCallback(ElementIdentifier id,
Callback callback);
// Adds a callback that will be called whenever an element with identifier
// `id` in `context` is activated by the user.
Subscription AddElementActivatedCallback(ElementIdentifier id,
ElementContext context,
Callback callback);
// Adds a callback that will be called whenever an element with identifier
// `id` is activated in any context.
Subscription AddElementActivatedInAnyContextCallback(ElementIdentifier id,
Callback callback);
// Adds a callback that will be called whenever an element with identifier
// `id` in `context` is hidden.
//
// Note: the TrackedElement* passed to the callback may not remain
// valid after the call, even if the same element object in its UI framework
// is re-shown (a new TrackedElement may be generated).
Subscription AddElementHiddenCallback(ElementIdentifier id,
ElementContext context,
Callback callback);
// Adds a callback that will be called whenever an element with identifier
// `id` is hidden in any context.
//
// Note: the TrackedElement* passed to the callback may not remain
// valid after the call, even if the same element object in its UI framework
// is re-shown (a new TrackedElement may be generated).
Subscription AddElementHiddenInAnyContextCallback(ElementIdentifier id,
Callback callback);
// Adds a callback that will be called whenever an event of `event_type` is
// generated within `context` by any element.
Subscription AddCustomEventCallback(CustomElementEventType event_type,
ElementContext context,
Callback callback);
// Adds a callback that will be called whenever an event of `event_type` is
// generated within any context.
Subscription AddCustomEventInAnyContextCallback(
CustomElementEventType event_type,
Callback callback);
// Returns all known contexts.
Contexts GetAllContextsForTesting() const;
// Returns a list of all elements. Should only be used in a meta-testing
// context, e.g. for testing the tracker itself, or for getting lists of
// candidate elements for fuzzing input.
//
// If `in_context` is specified, only elements in that context will be
// returned.
ElementList GetAllElementsForTesting(
std::optional<ElementContext> in_context = std::nullopt);
// Adds a callback when any element is shown.
Subscription AddAnyElementShownCallbackForTesting(Callback callback);
private:
friend class base::NoDestructor<ElementTracker>;
class ElementData;
class GarbageCollector;
using LookupKey = std::pair<ElementIdentifier, ElementContext>;
FRIEND_TEST_ALL_PREFIXES(ElementTrackerTest, CleanupAfterElementHidden);
FRIEND_TEST_ALL_PREFIXES(ElementTrackerTest, CleanupAfterCallbacksRemoved);
FRIEND_TEST_ALL_PREFIXES(ElementTrackerTest, HideDuringShowCallback);
ElementTracker();
~ElementTracker();
// ElementTrackerFrameworkDelegate:
void NotifyElementShown(TrackedElement* element) override;
void NotifyElementActivated(TrackedElement* element) override;
void NotifyElementHidden(TrackedElement* element) override;
void NotifyCustomEvent(TrackedElement* element,
CustomElementEventType event_type) override;
ElementData* GetOrAddElementData(ElementIdentifier id,
ElementContext context);
void MaybeCleanup(ElementData* data);
// Use a list to keep track of elements we're in the process of sending
// notifications for; this allows us to zero out the reference in realtime if
// the element is deleted. We use a list because the individual elements need
// to be memory-stable.
std::list<raw_ptr<TrackedElement, CtnExperimental>> notification_elements_;
std::map<LookupKey, ElementData> element_data_;
base::RepeatingCallbackList<void(TrackedElement*)>
any_element_shown_callbacks_;
std::unique_ptr<GarbageCollector> gc_;
};
// Holds an TrackedElement reference and nulls it out if the element goes
// away. In other words, acts as a weak reference for TrackedElements.
class COMPONENT_EXPORT(UI_BASE) SafeElementReference {
public:
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();
TrackedElement* get() const { return element_; }
explicit operator bool() const { return element_; }
bool operator!() const { return !element_; }
bool operator==(const SafeElementReference& other) const {
return element_ == other.element_;
}
bool operator==(const TrackedElement* other) const {
return element_ == other;
}
// Gets the held element as type T if present, null if not present or not a T.
template <typename T>
requires std::derived_from<T, TrackedElement>
T* get_as() const {
return element_ ? element_->AsA<T>() : nullptr;
}
private:
void Subscribe();
void OnElementHidden(TrackedElement* element);
ElementTracker::Subscription subscription_;
raw_ptr<TrackedElement> element_ = nullptr;
};
} // namespace ui
// Macros for declaring custom element event types. Put the DECLARE call in
// your public header file and the DEFINE in corresponding .cc file. For local
// values to be used in tests, use DEFINE_LOCAL_CUSTOM_ELEMENT_EVENT_TYPE()
// defined below instead.
//
// Note: if you need to use the identifier outside the current component, use
// DECLARE/DEFINE_EXPORTED_... below.
#define DECLARE_CUSTOM_ELEMENT_EVENT_TYPE(EventName) \
DECLARE_ELEMENT_IDENTIFIER_VALUE(EventName)
#define DEFINE_CUSTOM_ELEMENT_EVENT_TYPE(EventName) \
DEFINE_ELEMENT_IDENTIFIER_VALUE(EventName)
// Macros for declaring custom element event types that can be accessed in other
// components. Put the DECLARE call in your public header file and the DEFINE
// call in the corresponding .cc file.
#define DECLARE_EXPORTED_CUSTOM_ELEMENT_EVENT_TYPE(ExportName, EventName) \
DECLARE_EXPORTED_ELEMENT_IDENTIFIER_VALUE(ExportName, EventName)
#define DEFINE_EXPORTED_CUSTOM_ELEMENT_EVENT_TYPE(EventName) \
DEFINE_EXPORTED_ELEMENT_IDENTIFIER_VALUE(EventName)
// Macros for declaring custom class element event type. Put the DECLARE call in
// your .h file in your class declaration, and the DEFINE in the corresponding
// .cc file.
#define DECLARE_CLASS_CUSTOM_ELEMENT_EVENT_TYPE(EventName) \
DECLARE_CLASS_ELEMENT_IDENTIFIER_VALUE(EventName)
#define DEFINE_CLASS_CUSTOM_ELEMENT_EVENT_TYPE(ClassName, EventName) \
DEFINE_CLASS_ELEMENT_IDENTIFIER_VALUE(ClassName, EventName)
// This produces a unique, mangled name that can safely be used in macros called
// by tests without having to worry about global name collisions. For production
// code, use DECLARE/DEFINE above instead. You should pass __FILE__ and __LINE__
// for `File`, and `Line`, respectively.
#define DEFINE_MACRO_CUSTOM_ELEMENT_EVENT_TYPE(File, Line, EventName) \
DEFINE_MACRO_ELEMENT_IDENTIFIER_VALUE(File, Line, EventName)
// This produces a unique, mangled name that can safely be used in tests
// without having to worry about global name collisions. For production code,
// use DECLARE/DEFINE above instead.
#define DEFINE_LOCAL_CUSTOM_ELEMENT_EVENT_TYPE(EventName) \
DEFINE_MACRO_ELEMENT_IDENTIFIER_VALUE(__FILE__, __LINE__, EventName)
#endif // UI_BASE_INTERACTION_ELEMENT_TRACKER_H_
|