#include <memory>
#include <optional>
#include "base/callback_list.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/ptr_util.h"
#include "base/test/bind.h"
#include "build/build_config.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_element_identifiers.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/interaction/browser_elements.h"
#include "chrome/browser/ui/toolbar/app_menu_model.h"
#include "chrome/browser/ui/views/bubble/webui_bubble_dialog_view.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/interaction/browser_elements_views.h"
#include "chrome/browser/ui/views/omnibox/omnibox_view_views.h"
#include "chrome/browser/ui/views/side_panel/side_panel_entry.h"
#include "chrome/browser/ui/views/toolbar/browser_app_menu_button.h"
#include "chrome/browser/ui/views/toolbar/toolbar_view.h"
#include "chrome/common/webui_url_constants.h"
#include "chrome/test/interaction/interactive_browser_test.h"
#include "chrome/test/interaction/tracked_element_webcontents.h"
#include "content/public/browser/page_navigator.h"
#include "content/public/test/browser_test.h"
#include "ui/base/interaction/element_identifier.h"
#include "ui/base/interaction/element_tracker.h"
#include "ui/base/interaction/interaction_sequence.h"
#include "ui/base/interaction/state_observer.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/page_transition_types.h"
#include "ui/base/window_open_disposition.h"
#include "ui/display/screen.h"
#include "ui/events/types/event_type.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/geometry/vector2d.h"
#include "ui/gfx/native_ui_types.h"
#include "ui/views/bubble/bubble_border.h"
#include "ui/views/bubble/bubble_dialog_delegate_view.h"
#include "ui/views/controls/webview/webview.h"
#include "ui/views/event_monitor.h"
#include "ui/views/interaction/element_tracker_views.h"
#include "ui/views/interaction/interaction_sequence_views.h"
#include "ui/views/interaction/widget_focus_observer.h"
#include "ui/views/layout/fill_layout.h"
#include "ui/views/layout/flex_layout_view.h"
#include "ui/views/layout/layout_types.h"
#include "ui/views/test/widget_activation_waiter.h"
#include "ui/views/view.h"
#include "ui/views/view_class_properties.h"
#include "ui/views/widget/widget_observer.h"
#include "url/gurl.h"
namespace {
constexpr char kDocumentWithNamedElement[] = "/select.html";
constexpr char kDocumentWithTitle[] = "/title3.html";
constexpr char kDocumentWithTextField[] = "/form_interaction.html";
}
class InteractiveBrowserTestUiTest : public InteractiveBrowserTest {
public:
InteractiveBrowserTestUiTest() = default;
~InteractiveBrowserTestUiTest() override = default;
void SetUp() override {
set_open_about_blank_on_browser_launch(true);
ASSERT_TRUE(embedded_test_server()->InitializeAndListen());
InteractiveBrowserTest::SetUp();
}
void SetUpOnMainThread() override {
InteractiveBrowserTest::SetUpOnMainThread();
embedded_test_server()->StartAcceptingConnections();
}
void TearDownOnMainThread() override {
EXPECT_TRUE(embedded_test_server()->ShutdownAndWaitUntilComplete());
InteractiveBrowserTest::TearDownOnMainThread();
}
};
IN_PROC_BROWSER_TEST_F(InteractiveBrowserTestUiTest,
PressButtonAndMouseMoveClick) {
RelativePositionSpecifier pos = CenterPoint();
#if BUILDFLAG(IS_WIN)
pos = base::BindOnce([](ui::TrackedElement* el) {
gfx::Rect bounds = el->GetScreenBounds();
auto* const menu_item =
ui::ElementTracker::GetElementTracker()->GetElementInAnyContext(
AppMenuModel::kMoreToolsMenuItem);
const gfx::Rect widget_bounds = menu_item->AsA<views::TrackedElementViews>()
->view()
->GetWidget()
->GetWindowBoundsInScreen();
bounds.Inset(gfx::Insets::TLBR(1, 1, 2, 2));
for (const auto& point :
{bounds.CenterPoint(), bounds.bottom_center(), bounds.left_center(),
bounds.right_center(), bounds.origin(), bounds.top_right(),
bounds.bottom_right(), bounds.bottom_left()}) {
if (!widget_bounds.Contains(point)) {
return point;
}
}
NOTREACHED() << "Menu widget ()" << widget_bounds.ToString()
<< ") significantly overlaps menu button ("
<< bounds.ToString() << ") cannot target button.";
});
#endif
RunTestSequence(
MoveMouseTo(kTabStripElementId),
PressButton(kToolbarAppMenuButtonElementId),
WaitForShow(AppMenuModel::kMoreToolsMenuItem),
MoveMouseTo(kToolbarAppMenuButtonElementId, std::move(pos)), ClickMouse(),
WaitForHide(AppMenuModel::kMoreToolsMenuItem));
}
IN_PROC_BROWSER_TEST_F(InteractiveBrowserTestUiTest, TestNameAndDrag) {
const char kWebContentsName[] = "WebContents";
gfx::Point p1;
gfx::Point p2;
auto p2gen = base::BindLambdaForTesting([&](ui::TrackedElement* el) {
p2 = el->AsA<views::TrackedElementViews>()
->view()
->GetBoundsInScreen()
.bottom_right() -
gfx::Vector2d(5, 5);
return p2;
});
RunTestSequence(
NameViewRelative(
kBrowserViewElementId, kWebContentsName,
base::BindOnce([](BrowserView* browser_view) -> views::View* {
return browser_view->contents_web_view();
})),
WithView(kWebContentsName,
base::BindLambdaForTesting([&p1](views::View* view) {
p1 = view->GetBoundsInScreen().origin() + gfx::Vector2d(5, 5);
})),
MoveMouseTo(std::ref(p1)),
Check(base::BindLambdaForTesting([&]() {
gfx::Rect rect(p1, gfx::Size());
rect.Inset(-1);
const gfx::Point point = display::Screen::Get()->GetCursorScreenPoint();
if (!rect.Contains(point)) {
LOG(ERROR) << "Expected cursor pos " << point.ToString()
<< " to be roughly " << p1.ToString();
return false;
}
return true;
})),
DragMouseTo(kWebContentsName, std::move(p2gen), false),
Check(base::BindLambdaForTesting([&]() {
gfx::Rect rect(p2, gfx::Size());
rect.Inset(-1);
const gfx::Point point = display::Screen::Get()->GetCursorScreenPoint();
if (!rect.Contains(point)) {
LOG(ERROR) << "Expected cursor pos " << point.ToString()
<< " to be roughly " << p2.ToString();
return false;
}
return true;
})),
ReleaseMouse());
}
IN_PROC_BROWSER_TEST_F(InteractiveBrowserTestUiTest,
MouseToNewWindowAndDoActionsInSameContext) {
Browser* const incognito = CreateIncognitoBrowser();
const auto context = BrowserElements::From(incognito)->GetContext();
RunTestSequenceInContext(
context, WaitForShow(kBrowserViewElementId),
ActivateSurface(kBrowserViewElementId),
MoveMouseTo(kToolbarAppMenuButtonElementId), ClickMouse(),
SelectMenuItem(AppMenuModel::kDownloadsMenuItem),
WaitForHide(AppMenuModel::kDownloadsMenuItem),
EnsureNotPresent(AppMenuModel::kDownloadsMenuItem),
CheckElement(
kToolbarAppMenuButtonElementId,
[](ui::TrackedElement* el) { return el->context(); }, context));
}
IN_PROC_BROWSER_TEST_F(InteractiveBrowserTestUiTest,
MouseToNewWindowAndDoActionsInSpecificContext) {
auto* const incognito = CreateIncognitoBrowser();
const auto context = BrowserElements::From(incognito)->GetContext();
RunTestSequenceInContext(
context, ActivateSurface(kBrowserViewElementId),
MoveMouseTo(kToolbarAppMenuButtonElementId), ClickMouse(),
SelectMenuItem(AppMenuModel::kDownloadsMenuItem),
WaitForHide(AppMenuModel::kDownloadsMenuItem),
EnsureNotPresent(AppMenuModel::kDownloadsMenuItem),
CheckElement(
kToolbarAppMenuButtonElementId,
[](ui::TrackedElement* el) { return el->context(); }, context));
}
IN_PROC_BROWSER_TEST_F(InteractiveBrowserTestUiTest, ActivateMultipleSurfaces) {
auto* const incognito = CreateIncognitoBrowser();
const auto context = BrowserElements::From(incognito)->GetContext();
RunTestSequence(
SetOnIncompatibleAction(OnIncompatibleAction::kHaltTest,
"Some Linux window managers do not allow "
"programmatically raising/activating windows. "
"This invalidates the rest of the test."),
InContext(context, ActivateSurface(kBrowserViewElementId),
MoveMouseTo(kToolbarAppMenuButtonElementId), ClickMouse(),
SelectMenuItem(AppMenuModel::kDownloadsMenuItem),
WaitForHide(AppMenuModel::kDownloadsMenuItem)),
ActivateSurface(kBrowserViewElementId),
MoveMouseTo(kToolbarAppMenuButtonElementId), ClickMouse(),
WaitForShow(AppMenuModel::kDownloadsMenuItem));
}
IN_PROC_BROWSER_TEST_F(InteractiveBrowserTestUiTest,
WatchForBrowserActivation) {
auto* const incognito = CreateIncognitoBrowser();
const auto context = BrowserElements::From(incognito)->GetContext();
RunTestSequence(
SetOnIncompatibleAction(OnIncompatibleAction::kHaltTest,
"Some Linux window managers do not allow "
"programmatically raising/activating windows. "
"This invalidates the rest of the test."),
ObserveState(views::test::kCurrentWidgetFocus),
InContext(context, ActivateSurface(kBrowserViewElementId),
MoveMouseTo(kToolbarAppMenuButtonElementId), ClickMouse(),
SelectMenuItem(AppMenuModel::kDownloadsMenuItem),
WaitForHide(AppMenuModel::kDownloadsMenuItem)),
ActivateSurface(kBrowserViewElementId),
WaitForState(views::test::kCurrentWidgetFocus, [this]() {
return BrowserView::GetBrowserViewForBrowser(browser())->GetWidget();
}));
}
IN_PROC_BROWSER_TEST_F(InteractiveBrowserTestUiTest,
WatchForTabWebContentsActivation) {
DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kWebContentsElementId);
auto* const incognito = CreateIncognitoBrowser();
const auto context = BrowserElements::From(incognito)->GetContext();
RunTestSequence(
SetOnIncompatibleAction(OnIncompatibleAction::kHaltTest,
"Some Linux window managers do not allow "
"programmatically raising/activating windows. "
"This invalidates the rest of the test."),
ObserveState(views::test::kCurrentWidgetFocus),
InContext(context, ActivateSurface(kBrowserViewElementId),
MoveMouseTo(kToolbarAppMenuButtonElementId), ClickMouse(),
SelectMenuItem(AppMenuModel::kDownloadsMenuItem),
WaitForHide(AppMenuModel::kDownloadsMenuItem)),
InstrumentTab(kWebContentsElementId),
ActivateSurface(kWebContentsElementId),
WaitForState(views::test::kCurrentWidgetFocus, [this]() {
return BrowserView::GetBrowserViewForBrowser(browser())->GetWidget();
}));
}
#if BUILDFLAG(IS_CHROMEOS)
#define MAYBE_WatchForNonTabWebContentsActivation \
DISABLED_WatchForNonTabWebContentsActivation
#else
#define MAYBE_WatchForNonTabWebContentsActivation \
WatchForNonTabWebContentsActivation
#endif
IN_PROC_BROWSER_TEST_F(InteractiveBrowserTestUiTest,
MAYBE_WatchForNonTabWebContentsActivation) {
DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kWebContentsElementId);
constexpr char kWebViewName[] = "Web View";
auto* const incognito = CreateIncognitoBrowser();
const auto context = BrowserElements::From(incognito)->GetContext();
views::Widget* expected_widget = nullptr;
RunTestSequence(
SetOnIncompatibleAction(OnIncompatibleAction::kHaltTest,
"Some Linux window managers do not allow "
"programmatically raising/activating windows. "
"This invalidates the rest of the test."),
ObserveState(views::test::kCurrentWidgetFocus),
InContext(context, ActivateSurface(kBrowserViewElementId),
MoveMouseTo(kToolbarAppMenuButtonElementId), ClickMouse(),
SelectMenuItem(AppMenuModel::kDownloadsMenuItem),
WaitForHide(AppMenuModel::kDownloadsMenuItem)),
PressButton(kTabSearchButtonElementId),
WaitForShow(kTabSearchBubbleElementId),
NameDescendantViewByType<views::WebView>(kTabSearchBubbleElementId,
kWebViewName),
InstrumentNonTabWebView(kWebContentsElementId, kWebViewName),
ActivateSurface(kWebContentsElementId),
WithView(kTabSearchBubbleElementId,
[&expected_widget](views::View* view) {
expected_widget = view->GetWidget();
}),
WaitForState(views::test::kCurrentWidgetFocus,
std::ref(expected_widget)));
}
IN_PROC_BROWSER_TEST_F(InteractiveBrowserTestUiTest,
WebPageNavigateStateAndLocation) {
const GURL url = embedded_test_server()->GetURL(kDocumentWithNamedElement);
DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kWebPageId);
DEFINE_LOCAL_CUSTOM_ELEMENT_EVENT_TYPE(kElementReadyEvent);
const DeepQuery kDeepQuery{"#select"};
StateChange state_change;
state_change.event = kElementReadyEvent;
state_change.type = StateChange::Type::kExists;
state_change.where = kDeepQuery;
RunTestSequence(
InstrumentTab(kWebPageId),
WithElement(kWebPageId, base::BindOnce(
[](GURL url, ui::TrackedElement* el) {
auto* const tab =
AsInstrumentedWebContents(el);
tab->LoadPage(url);
},
url)),
WaitForWebContentsNavigation(kWebPageId, url),
WaitForStateChange(kWebPageId, state_change),
MoveMouseTo(kWebPageId, kDeepQuery),
Check(base::BindLambdaForTesting([&]() {
BrowserView* const browser_view =
BrowserView::GetBrowserViewForBrowser(browser());
const gfx::Rect web_contents_bounds =
browser_view->contents_web_view()->GetBoundsInScreen();
const gfx::Point point = display::Screen::Get()->GetCursorScreenPoint();
if (!web_contents_bounds.Contains(point)) {
LOG(ERROR) << "Expected cursor pos " << point.ToString() << " to in "
<< web_contents_bounds.ToString();
return false;
}
return true;
})));
}
IN_PROC_BROWSER_TEST_F(InteractiveBrowserTestUiTest,
InAnyContextAndEnsureNotPresent) {
DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kBrowserPageId);
DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kIncognitoPageId);
Browser* const incognito = this->CreateIncognitoBrowser();
const auto context = BrowserElements::From(incognito)->GetContext();
RunTestSequenceInContext(
context,
InstrumentTab(kIncognitoPageId, std::nullopt, CurrentBrowser(),
false),
InstrumentTab(kBrowserPageId, std::nullopt, browser(),
false),
WaitForWebContentsReady(kIncognitoPageId)
.SetContext(ui::InteractionSequence::ContextMode::kInitial),
WaitForWebContentsReady(kBrowserPageId),
EnsureNotPresent(kBrowserPageId),
InAnyContext(EnsurePresent(kIncognitoPageId)));
}
IN_PROC_BROWSER_TEST_F(InteractiveBrowserTestUiTest,
InstrumentNonTabAsTestStep) {
DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kWebContentsId);
const char kTabSearchWebViewName[] = "Tab Search WebView";
RunTestSequence(
PressButton(kTabSearchButtonElementId),
WaitForShow(kTabSearchBubbleElementId),
NameChildViewByType<views::WebView>(kTabSearchBubbleElementId,
kTabSearchWebViewName),
InstrumentNonTabWebView(kWebContentsId, kTabSearchWebViewName));
}
IN_PROC_BROWSER_TEST_F(InteractiveBrowserTestUiTest,
SendAcceleratorToWebContents) {
DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kWebContentsId);
DEFINE_LOCAL_CUSTOM_ELEMENT_EVENT_TYPE(kClearAllClickEvent);
const DeepQuery kClearAllDownloadsButton = {"downloads-manager",
"downloads-toolbar", "#clearAll"};
const ui::Accelerator kClickWebButtonAccelerator(ui::KeyboardCode::VKEY_SPACE,
ui::EF_NONE);
StateChange clear_all_downloads_click;
clear_all_downloads_click.type = StateChange::Type::kExists;
clear_all_downloads_click.where = kClearAllDownloadsButton;
clear_all_downloads_click.event = kClearAllClickEvent;
RunTestSequence(
InstrumentTab(kWebContentsId),
PressButton(kToolbarAppMenuButtonElementId),
SelectMenuItem(AppMenuModel::kDownloadsMenuItem),
WaitForWebContentsNavigation(kWebContentsId,
GURL(chrome::kChromeUIDownloadsURL)),
FocusElement(kWebContentsId),
ExecuteJsAt(kWebContentsId, kClearAllDownloadsButton, "el => el.focus()"),
SendAccelerator(kWebContentsId, kClickWebButtonAccelerator),
WaitForStateChange(kWebContentsId, clear_all_downloads_click));
}
IN_PROC_BROWSER_TEST_F(InteractiveBrowserTestUiTest, SendKeyToWebContents) {
DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kWebContentsId);
const GURL url = embedded_test_server()->GetURL(kDocumentWithTextField);
const DeepQuery kTextField = {"#value"};
RunTestSequence(
InstrumentTab(kWebContentsId), NavigateWebContents(kWebContentsId, url),
FocusWebContents(kWebContentsId),
ExecuteJsAt(kWebContentsId, kTextField,
"el => { el.focus(); el.value = ''; }"),
SendKeyPress(kWebContentsId, ui::VKEY_A),
SendKeyPress(kWebContentsId, ui::VKEY_B, ui::EF_SHIFT_DOWN),
CheckJsResultAt(kWebContentsId, kTextField, "el => el.value", u"aB"));
}
IN_PROC_BROWSER_TEST_F(InteractiveBrowserTestUiTest, FocusElement) {
RunTestSequence(
FocusElement(kToolbarAppMenuButtonElementId),
CheckViewProperty(kToolbarAppMenuButtonElementId, &views::View::HasFocus,
true),
FocusElement(kOmniboxElementId),
CheckViewProperty(kOmniboxElementId, &views::View::HasFocus, true));
}
IN_PROC_BROWSER_TEST_F(InteractiveBrowserTestUiTest, SendKeyPress) {
RunTestSequence(
FocusElement(kOmniboxElementId),
SendKeyPress(kOmniboxElementId, ui::VKEY_A),
SendKeyPress(kOmniboxElementId, ui::VKEY_B, ui::EF_SHIFT_DOWN),
CheckViewProperty(kOmniboxElementId, &OmniboxViewViews::GetText, u"aB"));
}
class WebBubbleView : public views::BubbleDialogDelegateView {
METADATA_HEADER(WebBubbleView, views::BubbleDialogDelegateView)
public:
~WebBubbleView() override = default;
static WebBubbleView* CreateBubble(Browser* browser, GURL url) {
BrowserView* const browser_view =
BrowserView::GetBrowserViewForBrowser(browser);
auto bubble_ptr = base::WrapUnique(
new WebBubbleView(browser_view->toolbar(), browser->profile(), url));
auto* const bubble = bubble_ptr.get();
views::BubbleDialogDelegateView::CreateBubble(std::move(bubble_ptr))
->Show();
return bubble;
}
void SwapWebContents(GURL url) {
owned_web_contents_ = content::WebContents::Create(
content::WebContents::CreateParams(profile_));
web_view_->SetWebContents(owned_web_contents_.get());
web_view_->LoadInitialURL(url);
}
views::WebView* web_view() { return web_view_; }
private:
WebBubbleView(views::View* anchor_view, Profile* profile, GURL url)
: BubbleDialogDelegateView(anchor_view, views::BubbleBorder::TOP_LEFT),
profile_(profile) {
SetLayoutManager(std::make_unique<views::FillLayout>());
web_view_ = AddChildView(std::make_unique<views::WebView>(profile));
web_view_->LoadInitialURL(url);
}
gfx::Size CalculatePreferredSize(
const views::SizeBounds& available_size) const override {
return gfx::Size(300, 400);
}
const raw_ptr<Profile> profile_;
raw_ptr<views::WebView> web_view_;
std::unique_ptr<content::WebContents> owned_web_contents_;
};
BEGIN_METADATA(WebBubbleView)
END_METADATA
IN_PROC_BROWSER_TEST_F(InteractiveBrowserTestUiTest,
SwappingWebViewWebContentsTreatedAsNavigation) {
DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kWebContentsId);
const GURL url = embedded_test_server()->GetURL(kDocumentWithNamedElement);
const GURL url2 = embedded_test_server()->GetURL(kDocumentWithTitle);
auto* const bubble = WebBubbleView::CreateBubble(browser(), url);
RunTestSequence(InstrumentNonTabWebView(kWebContentsId, bubble->web_view()),
Do([&]() { bubble->SwapWebContents(url2); }),
WaitForWebContentsNavigation(kWebContentsId, url2));
bubble->GetWidget()->CloseNow();
}
IN_PROC_BROWSER_TEST_F(InteractiveBrowserTestUiTest,
WaitForWebContentsPainted) {
DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kWebContentsId);
const GURL url = embedded_test_server()->GetURL(kDocumentWithNamedElement);
auto* const bubble = WebBubbleView::CreateBubble(browser(), url);
RunTestSequence(
InstrumentNonTabWebView(kWebContentsId, bubble->web_view(), false),
WaitForWebContentsPainted(kWebContentsId),
WaitForWebContentsPainted(kWebContentsId));
bubble->GetWidget()->CloseNow();
}
IN_PROC_BROWSER_TEST_F(InteractiveBrowserTestUiTest, InitialWindowActive) {
auto* const widget =
BrowserView::GetBrowserViewForBrowser(browser())->GetWidget();
views::test::WaitForWidgetActive(widget, true);
RunTestSequence(ObserveState(views::test::kCurrentWidgetFocus),
WaitForState(views::test::kCurrentWidgetFocus, widget));
}
namespace {
DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kHoverView1Id);
DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kHoverView2Id);
DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kHoverView3Id);
class HoverDetectionView : public views::View {
METADATA_HEADER(HoverDetectionView, views::View)
public:
~HoverDetectionView() override = default;
gfx::Size CalculatePreferredSize(const views::SizeBounds&) const override {
return gfx::Size(200, 50);
}
void OnMouseMoved(const ui::MouseEvent& event) override {
on_mouse_move_callbacks_.Notify(event);
}
void OnMouseEntered(const ui::MouseEvent& event) override {
on_mouse_move_callbacks_.Notify(event);
}
void OnMouseExited(const ui::MouseEvent& event) override {
on_mouse_move_callbacks_.Notify(event);
}
using MouseMoveCallback =
base::RepeatingCallback<void(const ui::MouseEvent&)>;
auto AddOnMouseMoveCallback(MouseMoveCallback callback) {
return on_mouse_move_callbacks_.Add(callback);
}
private:
base::RepeatingCallbackList<void(const ui::MouseEvent&)>
on_mouse_move_callbacks_;
};
class LastHoverEventObserver
: public ui::test::StateObserver<std::set<ui::EventType>> {
public:
explicit LastHoverEventObserver(HoverDetectionView* view)
: subscription_(view->AddOnMouseMoveCallback(
base::BindRepeating(&LastHoverEventObserver::OnHoverEvent,
base::Unretained(this)))) {}
~LastHoverEventObserver() override = default;
void OnHoverEvent(const ui::MouseEvent& event) {
observed_events_.insert(event.type());
OnStateObserverStateChanged(observed_events_);
}
private:
base::CallbackListSubscription subscription_;
std::set<ui::EventType> observed_events_;
};
DEFINE_LOCAL_STATE_IDENTIFIER_VALUE(LastHoverEventObserver, kHoverView1State);
DEFINE_LOCAL_STATE_IDENTIFIER_VALUE(LastHoverEventObserver, kHoverView2State);
DEFINE_LOCAL_STATE_IDENTIFIER_VALUE(LastHoverEventObserver, kHoverView3State);
BEGIN_METADATA(HoverDetectionView)
END_METADATA
}
class HoverDetectionBubbleView : public views::FlexLayoutView,
public views::BubbleDialogDelegate {
METADATA_HEADER(HoverDetectionBubbleView, views::FlexLayoutView)
public:
explicit HoverDetectionBubbleView(views::View* anchor_view)
: BubbleDialogDelegate(anchor_view, views::BubbleBorder::TOP_RIGHT) {
SetOwnedByWidget(OwnedByWidgetPassKey());
auto* view = AddChildView(std::make_unique<HoverDetectionView>());
view->SetProperty(views::kElementIdentifierKey, kHoverView1Id);
views_.push_back(view);
view = AddChildView(std::make_unique<HoverDetectionView>());
view->SetProperty(views::kElementIdentifierKey, kHoverView2Id);
views_.push_back(view);
view = AddChildView(std::make_unique<HoverDetectionView>());
view->SetProperty(views::kElementIdentifierKey, kHoverView3Id);
views_.push_back(view);
SetOrientation(views::LayoutOrientation::kVertical);
}
~HoverDetectionBubbleView() override { views_.clear(); }
views::View* GetContentsView() override { return this; }
HoverDetectionView* view(size_t idx) { return views_[idx].get(); }
private:
std::vector<raw_ptr<HoverDetectionView>> views_;
};
BEGIN_METADATA(HoverDetectionBubbleView)
END_METADATA
class InteractiveBrowserTestHoverUiTest : public InteractiveBrowserTestUiTest {
public:
InteractiveBrowserTestHoverUiTest() = default;
~InteractiveBrowserTestHoverUiTest() override = default;
void SetUpOnMainThread() override {
InteractiveBrowserTestUiTest::SetUpOnMainThread();
auto* const browser_view = BrowserView::GetBrowserViewForBrowser(browser());
mouse_util().PerformGestures(
{browser_view->GetNativeWindow(), false},
views::test::InteractionTestUtilMouse::MoveTo(
browser_view->GetBoundsInScreen().origin() +
gfx::Vector2d(10, 10)));
auto* const anchor_view = BrowserElementsViews::From(browser())->GetView(
kToolbarAppMenuButtonElementId);
CHECK(anchor_view);
auto bubble_view = std::make_unique<HoverDetectionBubbleView>(anchor_view);
bubble_view_ = bubble_view.get();
bubble_widget_ = base::WrapUnique(views::BubbleDialogDelegate::CreateBubble(
std::move(bubble_view), views::Widget::InitParams::CLIENT_OWNS_WIDGET));
bubble_widget_->Show();
bubble_view_->SizeToContents();
}
void TearDownOnMainThread() override {
bubble_view_ = nullptr;
bubble_widget_.reset();
InteractiveBrowserTestUiTest::TearDownOnMainThread();
}
protected:
raw_ptr<HoverDetectionBubbleView> bubble_view_ = nullptr;
std::unique_ptr<views::Widget> bubble_widget_;
};
IN_PROC_BROWSER_TEST_F(InteractiveBrowserTestHoverUiTest, MoveMouseHoversView) {
RunTestSequence(
WaitForShow(kHoverView1Id),
ObserveState(kHoverView1State, bubble_view_->view(0)),
MoveMouseTo(kHoverView1Id),
WaitForState(kHoverView1State,
testing::UnorderedElementsAre(ui::EventType::kMouseEntered,
ui::EventType::kMouseMoved)));
}
IN_PROC_BROWSER_TEST_F(InteractiveBrowserTestHoverUiTest,
MoveMouseHoverMultipleViews) {
RunTestSequence(
WaitForShow(kHoverView1Id),
ObserveState(kHoverView1State, bubble_view_->view(0)),
ObserveState(kHoverView2State, bubble_view_->view(1)),
ObserveState(kHoverView3State, bubble_view_->view(2)),
MoveMouseTo(kHoverView1Id),
WaitForState(kHoverView1State,
testing::UnorderedElementsAre(ui::EventType::kMouseEntered,
ui::EventType::kMouseMoved)),
MoveMouseTo(kHoverView2Id),
WaitForState(kHoverView1State,
testing::Contains(ui::EventType::kMouseExited)),
WaitForState(kHoverView2State,
testing::UnorderedElementsAre(ui::EventType::kMouseEntered,
ui::EventType::kMouseMoved)),
MoveMouseTo(kHoverView3Id),
WaitForState(kHoverView2State,
testing::Contains(ui::EventType::kMouseExited)),
WaitForState(kHoverView3State,
testing::UnorderedElementsAre(ui::EventType::kMouseEntered,
ui::EventType::kMouseMoved)),
MoveMouseTo(
kBrowserViewElementId, base::BindOnce([](ui::TrackedElement* el) {
return el->GetScreenBounds().origin() + gfx::Vector2d(10, 10);
})),
WaitForState(kHoverView3State,
testing::Contains(ui::EventType::kMouseExited)));
}