#include "ui/views/widget/root_view.h"
#include <memory>
#include <utility>
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "build/build_config.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/events/event_utils.h"
#include "ui/events/keycodes/dom/dom_code.h"
#include "ui/views/context_menu_controller.h"
#include "ui/views/test/test_views.h"
#include "ui/views/test/views_test_base.h"
#include "ui/views/test/views_test_utils.h"
#include "ui/views/view_targeter.h"
#include "ui/views/widget/widget_deletion_observer.h"
#include "ui/views/window/dialog_delegate.h"
#if BUILDFLAG(IS_MAC)
#include "base/mac/mac_util.h"
#endif
namespace views::test {
namespace {
struct RootViewTestStateInit {
gfx::Rect bounds;
Widget::InitParams::Type type = Widget::InitParams::TYPE_WINDOW_FRAMELESS;
};
class RootViewTestState {
public:
explicit RootViewTestState(ViewsTestBase* delegate,
RootViewTestStateInit init = {}) {
Widget::InitParams init_params = delegate->CreateParams(init.type);
init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
if (init.bounds != gfx::Rect())
init_params.bounds = init.bounds;
widget_.Init(std::move(init_params));
widget_.Show();
widget_.SetContentsView(std::make_unique<View>());
}
Widget* widget() { return &widget_; }
internal::RootView* GetRootView() {
return static_cast<internal::RootView*>(widget_.GetRootView());
}
template <typename T>
T* AddChildView(std::unique_ptr<T> view) {
return widget_.GetContentsView()->AddChildView(std::move(view));
}
private:
Widget widget_;
};
class DeleteOnKeyEventView : public View {
public:
explicit DeleteOnKeyEventView(bool* set_on_key) : set_on_key_(set_on_key) {}
DeleteOnKeyEventView(const DeleteOnKeyEventView&) = delete;
DeleteOnKeyEventView& operator=(const DeleteOnKeyEventView&) = delete;
~DeleteOnKeyEventView() override = default;
bool OnKeyPressed(const ui::KeyEvent& event) override {
*set_on_key_ = true;
delete this;
return true;
}
private:
raw_ptr<bool> set_on_key_;
};
}
using RootViewTest = ViewsTestBase;
TEST_F(RootViewTest, DeleteViewDuringKeyEventDispatch) {
RootViewTestState state(this);
internal::RootView* root_view = state.GetRootView();
bool got_key_event = false;
View* child = state.AddChildView(
std::make_unique<DeleteOnKeyEventView>(&got_key_event));
child->SetFocusBehavior(View::FocusBehavior::ALWAYS);
child->RequestFocus();
ViewTargeter* view_targeter = new ViewTargeter(root_view);
root_view->SetEventTargeter(base::WrapUnique(view_targeter));
ui::KeyEvent key_event(ui::ET_KEY_PRESSED, ui::VKEY_ESCAPE, ui::EF_NONE);
ui::EventDispatchDetails details = root_view->OnEventFromSource(&key_event);
EXPECT_TRUE(details.target_destroyed);
EXPECT_FALSE(details.dispatcher_destroyed);
EXPECT_TRUE(got_key_event);
}
class TestContextMenuController : public ContextMenuController {
public:
TestContextMenuController() = default;
TestContextMenuController(const TestContextMenuController&) = delete;
TestContextMenuController& operator=(const TestContextMenuController&) =
delete;
~TestContextMenuController() override = default;
int show_context_menu_calls() const { return show_context_menu_calls_; }
View* menu_source_view() const { return menu_source_view_; }
ui::MenuSourceType menu_source_type() const { return menu_source_type_; }
void Reset() {
show_context_menu_calls_ = 0;
menu_source_view_ = nullptr;
menu_source_type_ = ui::MENU_SOURCE_NONE;
}
void ShowContextMenuForViewImpl(View* source,
const gfx::Point& point,
ui::MenuSourceType source_type) override {
show_context_menu_calls_++;
menu_source_view_ = source;
menu_source_type_ = source_type;
}
private:
int show_context_menu_calls_ = 0;
raw_ptr<View> menu_source_view_ = nullptr;
ui::MenuSourceType menu_source_type_ = ui::MENU_SOURCE_NONE;
};
TEST_F(RootViewTest, ContextMenuFromKeyEvent) {
#if !BUILDFLAG(IS_MAC)
RootViewTestState state(this);
internal::RootView* root_view = state.GetRootView();
TestContextMenuController controller;
View* focused_view = root_view->GetContentsView();
focused_view->set_context_menu_controller(&controller);
focused_view->SetFocusBehavior(View::FocusBehavior::ALWAYS);
focused_view->RequestFocus();
ui::KeyEvent nomenu_key_event('a', ui::VKEY_A, ui::DomCode::NONE,
ui::EF_NONE);
ui::EventDispatchDetails details =
root_view->OnEventFromSource(&nomenu_key_event);
EXPECT_FALSE(details.target_destroyed);
EXPECT_FALSE(details.dispatcher_destroyed);
EXPECT_EQ(0, controller.show_context_menu_calls());
EXPECT_EQ(nullptr, controller.menu_source_view());
EXPECT_EQ(ui::MENU_SOURCE_NONE, controller.menu_source_type());
controller.Reset();
ui::KeyEvent menu_key_event(ui::ET_KEY_PRESSED, ui::VKEY_F10,
ui::EF_SHIFT_DOWN);
details = root_view->OnEventFromSource(&menu_key_event);
EXPECT_FALSE(details.target_destroyed);
EXPECT_FALSE(details.dispatcher_destroyed);
EXPECT_EQ(1, controller.show_context_menu_calls());
EXPECT_EQ(focused_view, controller.menu_source_view());
EXPECT_EQ(ui::MENU_SOURCE_KEYBOARD, controller.menu_source_type());
controller.Reset();
ui::KeyEvent menu_key_event2(ui::ET_KEY_PRESSED, ui::VKEY_APPS, ui::EF_NONE);
details = root_view->OnEventFromSource(&menu_key_event2);
EXPECT_FALSE(details.target_destroyed);
EXPECT_FALSE(details.dispatcher_destroyed);
EXPECT_EQ(1, controller.show_context_menu_calls());
EXPECT_EQ(focused_view, controller.menu_source_view());
EXPECT_EQ(ui::MENU_SOURCE_KEYBOARD, controller.menu_source_type());
controller.Reset();
#endif
}
class GestureHandlingView : public View {
public:
GestureHandlingView() = default;
GestureHandlingView(const GestureHandlingView&) = delete;
GestureHandlingView& operator=(const GestureHandlingView&) = delete;
~GestureHandlingView() override = default;
void OnGestureEvent(ui::GestureEvent* event) override { event->SetHandled(); }
};
class MouseHandlingView : public View {
public:
MouseHandlingView() = default;
MouseHandlingView(const MouseHandlingView&) = delete;
MouseHandlingView& operator=(const MouseHandlingView&) = delete;
~MouseHandlingView() override = default;
void OnMouseEvent(ui::MouseEvent* event) override { event->SetHandled(); }
};
TEST_F(RootViewTest, EventHandlersResetWhenDeleted) {
RootViewTestState state(this, {.bounds = {100, 100}});
internal::RootView* root_view = state.GetRootView();
View* event_handler = state.AddChildView(std::make_unique<View>());
root_view->SetMouseAndGestureHandler(event_handler);
ASSERT_EQ(event_handler, root_view->gesture_handler_for_testing());
ASSERT_EQ(event_handler, root_view->mouse_pressed_handler_for_testing());
root_view->GetContentsView()->RemoveChildViewT(event_handler);
EXPECT_EQ(nullptr, root_view->gesture_handler_for_testing());
EXPECT_EQ(nullptr, root_view->mouse_pressed_handler_for_testing());
}
TEST_F(RootViewTest, EventHandlersNotResetWhenReparented) {
RootViewTestState state(this, {.bounds = {100, 100}});
internal::RootView* root_view = state.GetRootView();
View* event_handler = state.AddChildView(std::make_unique<View>());
root_view->SetMouseAndGestureHandler(event_handler);
ASSERT_EQ(event_handler, root_view->gesture_handler_for_testing());
View* other_parent = state.AddChildView(std::make_unique<View>());
other_parent->AddChildView(event_handler);
EXPECT_EQ(event_handler, root_view->gesture_handler_for_testing());
}
TEST_F(RootViewTest, GestureHandlerResetAfterMouseReleased) {
RootViewTestState state(this, {.bounds = {100, 100}});
internal::RootView* root_view = state.GetRootView();
View* gesture_handler =
state.AddChildView(std::make_unique<GestureHandlingView>());
gesture_handler->SetBoundsRect(gfx::Rect(gfx::Size{50, 50}));
View* mouse_handler =
state.AddChildView(std::make_unique<MouseHandlingView>());
mouse_handler->SetBoundsRect(
gfx::Rect(gesture_handler->bounds().bottom_right(), gfx::Size{50, 50}));
const gfx::Point gesture_handler_center_point =
gesture_handler->GetBoundsInScreen().CenterPoint();
ui::GestureEvent scroll_begin(
gesture_handler_center_point.x(), gesture_handler_center_point.y(),
ui::EF_NONE, base::TimeTicks(),
ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_BEGIN));
root_view->OnEventFromSource(&scroll_begin);
ui::GestureEvent scroll_update(
gesture_handler_center_point.x(), gesture_handler_center_point.y(),
ui::EF_NONE, base::TimeTicks(),
ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_UPDATE, 20,
10));
root_view->OnEventFromSource(&scroll_update);
const gfx::Point mouse_handler_center_point =
mouse_handler->GetBoundsInScreen().CenterPoint();
ui::MouseEvent pressed_event(ui::ET_MOUSE_PRESSED, mouse_handler_center_point,
mouse_handler_center_point,
ui::EventTimeForNow(), ui::EF_NONE,
0);
ui::MouseEvent released_event(
ui::ET_MOUSE_RELEASED, mouse_handler_center_point,
mouse_handler_center_point, ui::EventTimeForNow(), ui::EF_NONE,
0);
root_view->OnMousePressed(pressed_event);
root_view->OnMouseReleased(released_event);
EXPECT_EQ(nullptr, root_view->gesture_handler_for_testing());
}
TEST_F(RootViewTest, ContextMenuFromLongPress) {
RootViewTestState state(
this, {.bounds = {100, 100}, .type = Widget::InitParams::TYPE_POPUP});
internal::RootView* root_view = state.GetRootView();
View* parent_view = root_view->GetContentsView();
TestContextMenuController controller;
parent_view->set_context_menu_controller(&controller);
View* gesture_handling_child_view = new GestureHandlingView;
gesture_handling_child_view->SetBoundsRect(gfx::Rect(10, 10));
parent_view->AddChildView(gesture_handling_child_view);
View* other_child_view = new View;
other_child_view->SetBoundsRect(gfx::Rect(20, 0, 10, 10));
parent_view->AddChildView(other_child_view);
ui::GestureEvent long_press1(
5, 5, 0, base::TimeTicks(),
ui::GestureEventDetails(ui::ET_GESTURE_LONG_PRESS));
ui::EventDispatchDetails details = root_view->OnEventFromSource(&long_press1);
ui::GestureEvent end1(5, 5, 0, base::TimeTicks(),
ui::GestureEventDetails(ui::ET_GESTURE_END));
details = root_view->OnEventFromSource(&end1);
EXPECT_FALSE(details.target_destroyed);
EXPECT_FALSE(details.dispatcher_destroyed);
EXPECT_EQ(0, controller.show_context_menu_calls());
controller.Reset();
ui::GestureEvent long_press2(
25, 5, 0, base::TimeTicks(),
ui::GestureEventDetails(ui::ET_GESTURE_LONG_PRESS));
details = root_view->OnEventFromSource(&long_press2);
ui::GestureEvent end2(25, 5, 0, base::TimeTicks(),
ui::GestureEventDetails(ui::ET_GESTURE_END));
details = root_view->OnEventFromSource(&end2);
EXPECT_FALSE(details.target_destroyed);
EXPECT_FALSE(details.dispatcher_destroyed);
EXPECT_EQ(1, controller.show_context_menu_calls());
controller.Reset();
ui::GestureEvent long_press3(
50, 50, 0, base::TimeTicks(),
ui::GestureEventDetails(ui::ET_GESTURE_LONG_PRESS));
details = root_view->OnEventFromSource(&long_press3);
ui::GestureEvent end3(25, 5, 0, base::TimeTicks(),
ui::GestureEventDetails(ui::ET_GESTURE_END));
details = root_view->OnEventFromSource(&end3);
EXPECT_FALSE(details.target_destroyed);
EXPECT_FALSE(details.dispatcher_destroyed);
EXPECT_EQ(1, controller.show_context_menu_calls());
}
TEST_F(RootViewTest, ContextMenuFromLongPressOnDisabledView) {
RootViewTestState state(
this, {.bounds = {100, 100}, .type = Widget::InitParams::TYPE_POPUP});
internal::RootView* root_view = state.GetRootView();
View* parent_view = root_view->GetContentsView();
TestContextMenuController controller;
parent_view->set_context_menu_controller(&controller);
parent_view->SetEnabled(false);
View* gesture_handling_child_view = new GestureHandlingView;
gesture_handling_child_view->SetBoundsRect(gfx::Rect(10, 10));
parent_view->AddChildView(gesture_handling_child_view);
View* other_child_view = new View;
other_child_view->SetBoundsRect(gfx::Rect(20, 0, 10, 10));
parent_view->AddChildView(other_child_view);
ui::GestureEvent long_press1(
5, 5, 0, base::TimeTicks(),
ui::GestureEventDetails(ui::ET_GESTURE_LONG_PRESS));
ui::EventDispatchDetails details = root_view->OnEventFromSource(&long_press1);
ui::GestureEvent end1(5, 5, 0, base::TimeTicks(),
ui::GestureEventDetails(ui::ET_GESTURE_END));
details = root_view->OnEventFromSource(&end1);
EXPECT_FALSE(details.target_destroyed);
EXPECT_FALSE(details.dispatcher_destroyed);
EXPECT_EQ(0, controller.show_context_menu_calls());
controller.Reset();
ui::GestureEvent long_press2(
25, 5, 0, base::TimeTicks(),
ui::GestureEventDetails(ui::ET_GESTURE_LONG_PRESS));
details = root_view->OnEventFromSource(&long_press2);
ui::GestureEvent end2(25, 5, 0, base::TimeTicks(),
ui::GestureEventDetails(ui::ET_GESTURE_END));
details = root_view->OnEventFromSource(&end2);
EXPECT_FALSE(details.target_destroyed);
EXPECT_FALSE(details.dispatcher_destroyed);
EXPECT_EQ(0, controller.show_context_menu_calls());
controller.Reset();
ui::GestureEvent long_press3(
50, 50, 0, base::TimeTicks(),
ui::GestureEventDetails(ui::ET_GESTURE_LONG_PRESS));
details = root_view->OnEventFromSource(&long_press3);
ui::GestureEvent end3(25, 5, 0, base::TimeTicks(),
ui::GestureEventDetails(ui::ET_GESTURE_END));
details = root_view->OnEventFromSource(&end3);
EXPECT_FALSE(details.target_destroyed);
EXPECT_FALSE(details.dispatcher_destroyed);
EXPECT_EQ(0, controller.show_context_menu_calls());
}
namespace {
class DeleteViewOnEvent : public View {
public:
DeleteViewOnEvent(ui::EventType delete_event_type, bool* was_destroyed)
: delete_event_type_(delete_event_type), was_destroyed_(was_destroyed) {}
DeleteViewOnEvent(const DeleteViewOnEvent&) = delete;
DeleteViewOnEvent& operator=(const DeleteViewOnEvent&) = delete;
~DeleteViewOnEvent() override { *was_destroyed_ = true; }
void OnEvent(ui::Event* event) override {
if (event->type() == delete_event_type_)
delete this;
}
private:
ui::EventType delete_event_type_;
raw_ptr<bool> was_destroyed_;
};
class RemoveViewOnEvent : public View {
public:
explicit RemoveViewOnEvent(ui::EventType remove_event_type)
: remove_event_type_(remove_event_type) {}
RemoveViewOnEvent(const RemoveViewOnEvent&) = delete;
RemoveViewOnEvent& operator=(const RemoveViewOnEvent&) = delete;
void OnEvent(ui::Event* event) override {
if (event->type() == remove_event_type_)
parent()->RemoveChildView(this);
}
private:
ui::EventType remove_event_type_;
};
class NestedEventOnEvent : public View {
public:
NestedEventOnEvent(ui::EventType nested_event_type, View* root_view)
: nested_event_type_(nested_event_type), root_view_(root_view) {}
NestedEventOnEvent(const NestedEventOnEvent&) = delete;
NestedEventOnEvent& operator=(const NestedEventOnEvent&) = delete;
void OnEvent(ui::Event* event) override {
if (event->type() == nested_event_type_) {
ui::MouseEvent exit_event(ui::ET_MOUSE_EXITED, gfx::Point(), gfx::Point(),
ui::EventTimeForNow(), ui::EF_NONE,
ui::EF_NONE);
nested_event_type_ = ui::ET_UNKNOWN;
root_view_->OnMouseExited(exit_event);
}
}
private:
ui::EventType nested_event_type_;
raw_ptr<View> root_view_;
};
}
TEST_F(RootViewTest, DeleteViewOnMouseExitDispatch) {
RootViewTestState state(this, {.bounds = {10, 10, 500, 500},
.type = Widget::InitParams::TYPE_POPUP});
internal::RootView* root_view = state.GetRootView();
bool view_destroyed = false;
View* child = state.AddChildView(std::make_unique<DeleteViewOnEvent>(
ui::ET_MOUSE_EXITED, &view_destroyed));
child->SetBounds(10, 10, 500, 500);
ui::MouseEvent moved_event(ui::ET_MOUSE_MOVED, gfx::Point(15, 15),
gfx::Point(15, 15), ui::EventTimeForNow(), 0, 0);
root_view->OnMouseMoved(moved_event);
ASSERT_FALSE(view_destroyed);
ui::MouseEvent exit_event(ui::ET_MOUSE_EXITED, gfx::Point(), gfx::Point(),
ui::EventTimeForNow(), 0, 0);
root_view->OnMouseExited(exit_event);
EXPECT_TRUE(view_destroyed);
EXPECT_TRUE(root_view->GetContentsView()->children().empty());
}
TEST_F(RootViewTest, DeleteViewOnMouseEnterDispatch) {
RootViewTestState state(this, {.bounds = {10, 10, 500, 500},
.type = Widget::InitParams::TYPE_POPUP});
internal::RootView* root_view = state.GetRootView();
bool view_destroyed = false;
View* child = state.AddChildView(std::make_unique<DeleteViewOnEvent>(
ui::ET_MOUSE_ENTERED, &view_destroyed));
child->SetBounds(100, 100, 100, 100);
ui::MouseEvent moved_event(ui::ET_MOUSE_MOVED, gfx::Point(15, 15),
gfx::Point(15, 15), ui::EventTimeForNow(), 0, 0);
root_view->OnMouseMoved(moved_event);
ASSERT_FALSE(view_destroyed);
ui::MouseEvent moved_event2(ui::ET_MOUSE_MOVED, gfx::Point(115, 115),
gfx::Point(115, 115), ui::EventTimeForNow(), 0,
0);
root_view->OnMouseMoved(moved_event2);
EXPECT_TRUE(view_destroyed);
EXPECT_TRUE(root_view->GetContentsView()->children().empty());
}
TEST_F(RootViewTest, RemoveViewOnMouseEnterDispatch) {
RootViewTestState state(this, {.bounds = {10, 10, 500, 500},
.type = Widget::InitParams::TYPE_POPUP});
internal::RootView* root_view = state.GetRootView();
View* content = root_view->GetContentsView();
RemoveViewOnEvent child(ui::ET_MOUSE_ENTERED);
content->AddChildView(&child);
child.SetBounds(100, 100, 100, 100);
ui::MouseEvent moved_event(ui::ET_MOUSE_MOVED, gfx::Point(15, 15),
gfx::Point(15, 15), ui::EventTimeForNow(), 0, 0);
root_view->OnMouseMoved(moved_event);
ui::MouseEvent moved_event2(ui::ET_MOUSE_MOVED, gfx::Point(115, 115),
gfx::Point(115, 115), ui::EventTimeForNow(), 0,
0);
root_view->OnMouseMoved(moved_event2);
EXPECT_TRUE(content->children().empty());
}
TEST_F(RootViewTest, ClearMouseMoveHandlerOnMouseExitDispatch) {
RootViewTestState state(this, {.bounds = {10, 10, 500, 500},
.type = Widget::InitParams::TYPE_POPUP});
internal::RootView* root_view = state.GetRootView();
View* child = state.AddChildView(
std::make_unique<NestedEventOnEvent>(ui::ET_MOUSE_EXITED, root_view));
child->SetBounds(100, 100, 100, 100);
ui::MouseEvent moved_event(ui::ET_MOUSE_MOVED, gfx::Point(110, 110),
gfx::Point(110, 110), ui::EventTimeForNow(), 0, 0);
root_view->OnMouseMoved(moved_event);
ui::MouseEvent move_event2(ui::ET_MOUSE_MOVED, gfx::Point(15, 15),
gfx::Point(15, 15), ui::EventTimeForNow(), 0, 0);
root_view->OnMouseMoved(move_event2);
}
TEST_F(RootViewTest,
ClearMouseMoveHandlerOnMouseExitDispatchWithContentViewDisabled) {
RootViewTestState state(this, {.bounds = {10, 10, 500, 500},
.type = Widget::InitParams::TYPE_POPUP});
internal::RootView* root_view = state.GetRootView();
View* child = state.AddChildView(
std::make_unique<NestedEventOnEvent>(ui::ET_MOUSE_EXITED, root_view));
child->SetBounds(100, 100, 100, 100);
ui::MouseEvent moved_event(ui::ET_MOUSE_MOVED, gfx::Point(110, 110),
gfx::Point(110, 110), ui::EventTimeForNow(), 0, 0);
root_view->OnMouseMoved(moved_event);
root_view->GetContentsView()->SetEnabled(false);
ui::MouseEvent move_event2(ui::ET_MOUSE_MOVED, gfx::Point(200, 200),
gfx::Point(200, 200), ui::EventTimeForNow(), 0, 0);
root_view->OnMouseMoved(move_event2);
}
TEST_F(RootViewTest, ClearMouseMoveHandlerOnMouseEnterDispatch) {
RootViewTestState state(this, {.bounds = {10, 10, 500, 500},
.type = Widget::InitParams::TYPE_POPUP});
internal::RootView* root_view = state.GetRootView();
View* child = state.AddChildView(
std::make_unique<NestedEventOnEvent>(ui::ET_MOUSE_ENTERED, root_view));
child->SetBounds(100, 100, 100, 100);
ui::MouseEvent moved_event(ui::ET_MOUSE_MOVED, gfx::Point(15, 15),
gfx::Point(15, 15), ui::EventTimeForNow(), 0, 0);
root_view->OnMouseMoved(moved_event);
ui::MouseEvent moved_event2(ui::ET_MOUSE_MOVED, gfx::Point(115, 115),
gfx::Point(115, 115), ui::EventTimeForNow(), 0,
0);
root_view->OnMouseMoved(moved_event2);
}
namespace {
class DeleteWidgetOnMouseExit : public View {
public:
explicit DeleteWidgetOnMouseExit(Widget* widget) : widget_(widget) {}
DeleteWidgetOnMouseExit(const DeleteWidgetOnMouseExit&) = delete;
DeleteWidgetOnMouseExit& operator=(const DeleteWidgetOnMouseExit&) = delete;
~DeleteWidgetOnMouseExit() override = default;
void OnMouseExited(const ui::MouseEvent& event) override { delete widget_; }
private:
raw_ptr<Widget> widget_;
};
}
TEST_F(RootViewTest, DeleteWidgetOnMouseExitDispatch) {
Widget* widget = new Widget;
Widget::InitParams init_params = CreateParams(Widget::InitParams::TYPE_POPUP);
init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
widget->Init(std::move(init_params));
widget->SetBounds(gfx::Rect(10, 10, 500, 500));
WidgetDeletionObserver widget_deletion_observer(widget);
auto content = std::make_unique<View>();
View* child = new DeleteWidgetOnMouseExit(widget);
content->AddChildView(child);
widget->SetContentsView(std::move(content));
child->SetBounds(100, 100, 100, 100);
internal::RootView* root_view =
static_cast<internal::RootView*>(widget->GetRootView());
ui::MouseEvent moved_event(ui::ET_MOUSE_MOVED, gfx::Point(115, 115),
gfx::Point(115, 115), ui::EventTimeForNow(), 0, 0);
root_view->OnMouseMoved(moved_event);
ASSERT_TRUE(widget_deletion_observer.IsWidgetAlive());
ui::MouseEvent move_event2(ui::ET_MOUSE_MOVED, gfx::Point(15, 15),
gfx::Point(15, 15), ui::EventTimeForNow(), 0, 0);
root_view->OnMouseMoved(move_event2);
EXPECT_FALSE(widget_deletion_observer.IsWidgetAlive());
}
TEST_F(RootViewTest, DeleteWidgetOnMouseExitDispatchFromChild) {
Widget* widget = new Widget;
Widget::InitParams init_params = CreateParams(Widget::InitParams::TYPE_POPUP);
init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
widget->Init(std::move(init_params));
widget->SetBounds(gfx::Rect(10, 10, 500, 500));
WidgetDeletionObserver widget_deletion_observer(widget);
View* child = new DeleteWidgetOnMouseExit(widget);
View* subchild = new View();
View* content = widget->SetContentsView(std::make_unique<View>());
content->AddChildView(child);
child->AddChildView(subchild);
child->SetBounds(100, 100, 100, 100);
subchild->SetBounds(0, 0, 100, 100);
child->SetNotifyEnterExitOnChild(true);
internal::RootView* root_view =
static_cast<internal::RootView*>(widget->GetRootView());
ui::MouseEvent moved_event(ui::ET_MOUSE_MOVED, gfx::Point(115, 115),
gfx::Point(115, 115), ui::EventTimeForNow(), 0, 0);
root_view->OnMouseMoved(moved_event);
ASSERT_TRUE(widget_deletion_observer.IsWidgetAlive());
ui::MouseEvent move_event2(ui::ET_MOUSE_MOVED, gfx::Point(15, 15),
gfx::Point(15, 15), ui::EventTimeForNow(), 0, 0);
root_view->OnMouseMoved(move_event2);
EXPECT_FALSE(widget_deletion_observer.IsWidgetAlive());
}
namespace {
class RootViewTestDialogDelegate : public DialogDelegateView {
public:
RootViewTestDialogDelegate() {
DialogDelegate::SetButtons(ui::DIALOG_BUTTON_NONE);
}
RootViewTestDialogDelegate(const RootViewTestDialogDelegate&) = delete;
RootViewTestDialogDelegate& operator=(const RootViewTestDialogDelegate&) =
delete;
~RootViewTestDialogDelegate() override = default;
int layout_count() const { return layout_count_; }
gfx::Size CalculatePreferredSize() const override { return preferred_size_; }
void Layout() override {
EXPECT_EQ(size(), preferred_size_);
++layout_count_;
}
private:
const gfx::Size preferred_size_ = gfx::Size(111, 111);
int layout_count_ = 0;
};
}
TEST_F(RootViewTest, SingleLayoutDuringInit) {
RootViewTestDialogDelegate* delegate = new RootViewTestDialogDelegate();
Widget* widget =
DialogDelegate::CreateDialogWidget(delegate, GetContext(), nullptr);
EXPECT_EQ(1, delegate->layout_count());
widget->CloseNow();
}
using RootViewDesktopNativeWidgetTest = ViewsTestWithDesktopNativeWidget;
TEST_F(RootViewDesktopNativeWidgetTest, SingleLayoutDuringInit) {
RootViewTestDialogDelegate* delegate = new RootViewTestDialogDelegate();
Widget* widget =
DialogDelegate::CreateDialogWidget(delegate, GetContext(), nullptr);
EXPECT_EQ(1, delegate->layout_count());
widget->CloseNow();
}
#if !BUILDFLAG(IS_MAC)
TEST_F(RootViewTest, AnnounceTextTest) {
RootViewTestState state(this, {.bounds = {100, 100, 100, 100}});
internal::RootView* root_view = state.GetRootView();
EXPECT_EQ(1U, root_view->children().size());
const std::u16string kText = u"Text";
root_view->AnnounceText(kText);
EXPECT_EQ(2U, root_view->children().size());
views::test::RunScheduledLayout(root_view);
EXPECT_FALSE(root_view->children()[0]->size().IsEmpty());
EXPECT_TRUE(root_view->children()[1]->size().IsEmpty());
View* const hidden_view = root_view->children()[1];
ui::AXNodeData node_data;
hidden_view->GetAccessibleNodeData(&node_data);
EXPECT_EQ(kText,
node_data.GetString16Attribute(ax::mojom::StringAttribute::kName));
}
#endif
TEST_F(RootViewTest, MouseEventDispatchedToClosestEnabledView) {
RootViewTestState state(this, {.bounds = {100, 100, 100, 100}});
internal::RootView* root_view = state.GetRootView();
View* const contents_view = root_view->GetContentsView();
EventCountView* const v1 =
contents_view->AddChildView(std::make_unique<EventCountView>());
EventCountView* const v2 =
v1->AddChildView(std::make_unique<EventCountView>());
EventCountView* const v3 =
v2->AddChildView(std::make_unique<EventCountView>());
contents_view->SetBoundsRect(gfx::Rect(0, 0, 10, 10));
v1->SetBoundsRect(gfx::Rect(0, 0, 10, 10));
v2->SetBoundsRect(gfx::Rect(0, 0, 10, 10));
v3->SetBoundsRect(gfx::Rect(0, 0, 10, 10));
v1->set_handle_mode(EventCountView::CONSUME_EVENTS);
v2->set_handle_mode(EventCountView::CONSUME_EVENTS);
v3->set_handle_mode(EventCountView::CONSUME_EVENTS);
ui::MouseEvent pressed_event(ui::ET_MOUSE_PRESSED, gfx::Point(5, 5),
gfx::Point(5, 5), ui::EventTimeForNow(), 0, 0);
ui::MouseEvent released_event(ui::ET_MOUSE_RELEASED, gfx::Point(5, 5),
gfx::Point(5, 5), ui::EventTimeForNow(), 0, 0);
root_view->OnMousePressed(pressed_event);
root_view->OnMouseReleased(released_event);
EXPECT_EQ(0, v1->GetEventCount(ui::ET_MOUSE_PRESSED));
EXPECT_EQ(0, v2->GetEventCount(ui::ET_MOUSE_PRESSED));
EXPECT_EQ(1, v3->GetEventCount(ui::ET_MOUSE_PRESSED));
v3->SetEnabled(false);
root_view->OnMousePressed(pressed_event);
root_view->OnMouseReleased(released_event);
EXPECT_EQ(0, v1->GetEventCount(ui::ET_MOUSE_PRESSED));
EXPECT_EQ(1, v2->GetEventCount(ui::ET_MOUSE_PRESSED));
EXPECT_EQ(1, v3->GetEventCount(ui::ET_MOUSE_PRESSED));
v3->SetEnabled(true);
v2->SetEnabled(false);
root_view->OnMousePressed(pressed_event);
root_view->OnMouseReleased(released_event);
EXPECT_EQ(1, v1->GetEventCount(ui::ET_MOUSE_PRESSED));
EXPECT_EQ(1, v2->GetEventCount(ui::ET_MOUSE_PRESSED));
EXPECT_EQ(1, v3->GetEventCount(ui::ET_MOUSE_PRESSED));
}
TEST_F(RootViewTest, DoubleClickHandledIffFirstClickHandled) {
RootViewTestState state(this, {.bounds = {100, 100, 100, 100}});
internal::RootView* root_view = state.GetRootView();
View* const contents_view = root_view->GetContentsView();
EventCountView* const v1 =
contents_view->AddChildView(std::make_unique<EventCountView>());
contents_view->SetBoundsRect(gfx::Rect(0, 0, 10, 10));
v1->SetBoundsRect(gfx::Rect(0, 0, 10, 10));
ui::MouseEvent pressed_event(ui::ET_MOUSE_PRESSED, gfx::Point(5, 5),
gfx::Point(5, 5), ui::EventTimeForNow(), 0, 0);
ui::MouseEvent released_event(ui::ET_MOUSE_RELEASED, gfx::Point(5, 5),
gfx::Point(5, 5), ui::EventTimeForNow(), 0, 0);
v1->set_handle_mode(EventCountView::CONSUME_EVENTS);
pressed_event.SetClickCount(1);
released_event.SetClickCount(1);
EXPECT_TRUE(root_view->OnMousePressed(pressed_event));
root_view->OnMouseReleased(released_event);
v1->set_handle_mode(EventCountView::PROPAGATE_EVENTS);
pressed_event.SetClickCount(2);
released_event.SetClickCount(2);
EXPECT_TRUE(root_view->OnMousePressed(pressed_event));
root_view->OnMouseReleased(released_event);
v1->set_handle_mode(EventCountView::PROPAGATE_EVENTS);
pressed_event.SetClickCount(1);
released_event.SetClickCount(1);
EXPECT_FALSE(root_view->OnMousePressed(pressed_event));
root_view->OnMouseReleased(released_event);
pressed_event.SetClickCount(2);
released_event.SetClickCount(2);
EXPECT_FALSE(root_view->OnMousePressed(pressed_event));
root_view->OnMouseReleased(released_event);
}
}