| // 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_VIEWS_INTERACTION_ELEMENT_TRACKER_VIEWS_H_ |
| #define UI_VIEWS_INTERACTION_ELEMENT_TRACKER_VIEWS_H_ |
| |
| #include <map> |
| #include <string> |
| #include <vector> |
| |
| #include "base/functional/callback_forward.h" |
| #include "base/gtest_prod_util.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/no_destructor.h" |
| #include "base/scoped_multi_source_observation.h" |
| #include "ui/base/interaction/element_identifier.h" |
| #include "ui/base/interaction/element_tracker.h" |
| #include "ui/views/view_utils.h" |
| #include "ui/views/views_export.h" |
| |
| namespace views { |
| |
| class View; |
| class Widget; |
| |
| // Wraps a View in an ui::TrackedElement. |
| class VIEWS_EXPORT TrackedElementViews : public ui::TrackedElement { |
| public: |
| TrackedElementViews(View* view, |
| ui::ElementIdentifier identifier, |
| ui::ElementContext context); |
| ~TrackedElementViews() override; |
| |
| View* view() { return view_; } |
| const View* view() const { return view_; } |
| |
| // TrackedElement: |
| gfx::Rect GetScreenBounds() const override; |
| gfx::NativeView GetNativeView() const override; |
| std::string ToString() const override; |
| |
| DECLARE_FRAMEWORK_SPECIFIC_METADATA() |
| |
| private: |
| const raw_ptr<View> view_; |
| }; |
| |
| // Manages TrackedElements associated with View objects. |
| class VIEWS_EXPORT ElementTrackerViews { |
| public: |
| using ViewList = std::vector<View*>; |
| |
| // Returns the context to use for `primary_widget`, which will be a logical |
| // top-level widget, or null to use the default computation. Used e.g. to |
| // ensure that all system widgets on ChromeOS share a context. |
| using ContextOverrideCallback = |
| base::RepeatingCallback<ui::ElementContext(Widget* primary_widget)>; |
| |
| // Changes the computation of contexts for some or all widgets. See |
| // `ContextOverrideCallback` for details. |
| static void SetContextOverrideCallback(ContextOverrideCallback callback); |
| |
| // Gets the global instance of the tracker for Views. |
| static ElementTrackerViews* GetInstance(); |
| |
| // Returns the context associated with a particular View. The context will be |
| // the same across all Views associated with a root Widget (such as an |
| // application window), or as specified by the current |
| // `ContextOverrideCallback` if one is set. |
| static ui::ElementContext GetContextForView(View* view); |
| |
| // Returns the context associated with a particular Widget. The context will |
| // be the same across all Widgets associated with a root Widget (such as an |
| // application window), or as specified by the current |
| // `ContextOverrideCallback` if one is set. |
| static ui::ElementContext GetContextForWidget(Widget* widget); |
| |
| // ---------- |
| // The following methods retrieve Views directly without having to retrieve a |
| // TrackedElement from ElementTracker, call, AsA<>, etc. Use if you know that |
| // the element(s) in question are guaranteed to be Views. |
| |
| // Returns the corresponding TrackedElementViews for the given view, or |
| // null if none exists. Note that views which are not visible or not added to |
| // a Widget may not have associated elements, and that the returned object |
| // may be transient. |
| // |
| // For the non-const version, if `assign_temporary_id` is set and `view` does |
| // not have an identifier, ui::ElementTracker::kTemporaryIdentifier will be |
| // assigned. |
| TrackedElementViews* GetElementForView(View* view, |
| bool assign_temporary_id = false); |
| const TrackedElementViews* GetElementForView(const View* view) const; |
| |
| // Returns either the unique View matching the given `id` in the given |
| // `context`, or null if there is none. |
| // |
| // Views which are not [yet] visible are not counted (you can use |
| // `GetFirstMatchingView()` if you need to retrieve non-visible views). |
| // |
| // Use if you are sure there's at most one matching element in the context |
| // which is visible, and (if present) is a View; will DCHECK/crash otherwise. |
| View* GetUniqueView(ui::ElementIdentifier id, ui::ElementContext context); |
| |
| // Convenience method that calls GetUniqueView() and then safely converts the |
| // result to `T`, which must be a View subclass with metadata. Fails if a View |
| // is found but is not of the expected subtype. |
| template <class T> |
| T* GetUniqueViewAs(ui::ElementIdentifier id, ui::ElementContext context); |
| |
| // Returns the first View with the given `id` in the given `context`; null if |
| // none is found. Ignores all other Views and any matching elements that are |
| // not Views. |
| // |
| // Use when you just need *a* View in the given context, and don't care if |
| // there's more than one. |
| // |
| // Unlike other methods, this is capable of retrieving views which have not |
| // yet become visible (this is done for both speed and convenience). If there |
| // are a mix of visible and non-visible views with `id`, you can set |
| // `require_visible` to `true` to force only visible views to be returned. |
| View* GetFirstMatchingView(ui::ElementIdentifier id, |
| ui::ElementContext context, |
| bool require_visible = false); |
| |
| // Convenience method that calls GetFirstMatchingView() and then safely |
| // converts the result to `T`, which must be a view subclass with metadata. |
| // Fails if a View is found but is not of the expected subtype. |
| template <class T> |
| T* GetFirstMatchingViewAs(ui::ElementIdentifier id, |
| ui::ElementContext context, |
| bool require_visible = false); |
| |
| // Returns a list of all visible Views with identifier `id` in `context`. |
| // The list may be empty. Ignores any non-Views elements which might match. |
| ViewList GetAllMatchingViews(ui::ElementIdentifier id, |
| ui::ElementContext context); |
| |
| // Returns a list of all visible Views with identifier `id` in any context. |
| // Order is not guaranteed. Ignores any non-Views elements with the same |
| // identifier. |
| ViewList GetAllMatchingViewsInAnyContext(ui::ElementIdentifier id); |
| |
| // Returns a widget that matches the given context. A valid |
| // TrackedElementViews must exist within the widget. |
| Widget* GetWidgetForContext(ui::ElementContext context); |
| |
| // ---------- |
| // Notifies listeners that a specific custom event has occurred for the given |
| // view. Calls GetElementForView(view, true) under the hood; returns false if |
| // an element cannot be found or created for the view (e.g. in the case where |
| // it is not visible or associated with a widget). |
| bool NotifyCustomEvent(ui::CustomElementEventType event_type, View* view); |
| |
| // ---------- |
| // The following methods are used by View and derived classes to send events |
| // to ElementTracker. ElementTrackerViews maintains additional observers and |
| // states that make sure shown and hidden events get sent at the correct |
| // times. |
| |
| // Called by View after the kUniqueElementKey property is set. |
| void RegisterView(ui::ElementIdentifier element_id, View* view); |
| |
| // Called by View if the kUniqueElementKey property is changed from a non-null |
| // value. |
| void UnregisterView(ui::ElementIdentifier element_id, View* view); |
| |
| // Called by a View when the user activates it (clicks a button, selects a |
| // menu item, etc.) |
| void NotifyViewActivated(ui::ElementIdentifier element_id, View* view); |
| |
| private: |
| friend class base::NoDestructor<ElementTrackerViews>; |
| FRIEND_TEST_ALL_PREFIXES(ElementTrackerViewsTest, CleansUpWidgetTrackers); |
| class ElementDataViews; |
| class WidgetTracker; |
| |
| ElementTrackerViews(); |
| ~ElementTrackerViews(); |
| |
| // Returns the current `ContextOverrideCallback`, which may be null. |
| static ContextOverrideCallback& GetContextOverrideCallback(); |
| |
| // We do not get notified at the View level if a view's widget has not yet |
| // been shown. We need this notification to know when the view is actually |
| // visible to the user. So if a view is added to the tracker or is added to |
| // a widget, and its widget is not visible, we watch it until it is (or it is |
| // destroyed). |
| void MaybeTrackWidget(Widget* widget); |
| |
| // Keep track of widgets for which we've received an |
| // OnWidgetVisibilityChanged(true) event for but which are still reporting |
| // IsVisible() = false. This happens because visibility of native window in |
| // Aura is not exactly synced with our event reporting. |
| bool IsWidgetVisible(const Widget* widget) const; |
| |
| std::map<ui::ElementIdentifier, ElementDataViews> element_data_; |
| std::map<const Widget*, WidgetTracker> widget_trackers_; |
| }; |
| |
| // Template implementations. |
| |
| template <class T> |
| T* ElementTrackerViews::GetUniqueViewAs(ui::ElementIdentifier id, |
| ui::ElementContext context) { |
| views::View* const view = GetUniqueView(id, context); |
| if (!view) { |
| return nullptr; |
| } |
| T* const result = views::AsViewClass<T>(view); |
| DCHECK(result); |
| return result; |
| } |
| |
| template <class T> |
| T* ElementTrackerViews::GetFirstMatchingViewAs(ui::ElementIdentifier id, |
| ui::ElementContext context, |
| bool require_visible) { |
| views::View* const view = GetFirstMatchingView(id, context, require_visible); |
| if (!view) { |
| return nullptr; |
| } |
| T* const result = views::AsViewClass<T>(view); |
| DCHECK(result); |
| return result; |
| } |
| |
| } // namespace views |
| |
| #endif // UI_VIEWS_INTERACTION_ELEMENT_TRACKER_VIEWS_H_ |