1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "ui/views/widget/root_view.h"
7 #include "ui/events/event_utils.h"
8 #include "ui/views/context_menu_controller.h"
9 #include "ui/views/test/views_test_base.h"
10 #include "ui/views/view_targeter.h"
11 #include "ui/views/widget/root_view.h"
12 #include "ui/views/widget/widget_deletion_observer.h"
17 typedef ViewsTestBase RootViewTest
;
19 class DeleteOnKeyEventView
: public View
{
21 explicit DeleteOnKeyEventView(bool* set_on_key
) : set_on_key_(set_on_key
) {}
22 ~DeleteOnKeyEventView() override
{}
24 bool OnKeyPressed(const ui::KeyEvent
& event
) override
{
31 // Set to true in OnKeyPressed().
34 DISALLOW_COPY_AND_ASSIGN(DeleteOnKeyEventView
);
37 // Verifies deleting a View in OnKeyPressed() doesn't crash and that the
38 // target is marked as destroyed in the returned EventDispatchDetails.
39 TEST_F(RootViewTest
, DeleteViewDuringKeyEventDispatch
) {
41 Widget::InitParams init_params
=
42 CreateParams(Widget::InitParams::TYPE_POPUP
);
43 init_params
.ownership
= Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
44 widget
.Init(init_params
);
46 bool got_key_event
= false;
48 View
* content
= new View
;
49 widget
.SetContentsView(content
);
51 View
* child
= new DeleteOnKeyEventView(&got_key_event
);
52 content
->AddChildView(child
);
54 // Give focus to |child| so that it will be the target of the key event.
55 child
->SetFocusable(true);
56 child
->RequestFocus();
58 internal::RootView
* root_view
=
59 static_cast<internal::RootView
*>(widget
.GetRootView());
60 ViewTargeter
* view_targeter
= new ViewTargeter(root_view
);
61 root_view
->SetEventTargeter(make_scoped_ptr(view_targeter
));
63 ui::KeyEvent
key_event(ui::ET_KEY_PRESSED
, ui::VKEY_ESCAPE
, ui::EF_NONE
);
64 ui::EventDispatchDetails details
= root_view
->OnEventFromSource(&key_event
);
65 EXPECT_TRUE(details
.target_destroyed
);
66 EXPECT_FALSE(details
.dispatcher_destroyed
);
67 EXPECT_TRUE(got_key_event
);
70 // Tracks whether a context menu is shown.
71 class TestContextMenuController
: public ContextMenuController
{
73 TestContextMenuController()
74 : show_context_menu_calls_(0),
75 menu_source_view_(NULL
),
76 menu_source_type_(ui::MENU_SOURCE_NONE
) {
78 ~TestContextMenuController() override
{}
80 int show_context_menu_calls() const { return show_context_menu_calls_
; }
81 View
* menu_source_view() const { return menu_source_view_
; }
82 ui::MenuSourceType
menu_source_type() const { return menu_source_type_
; }
85 show_context_menu_calls_
= 0;
86 menu_source_view_
= NULL
;
87 menu_source_type_
= ui::MENU_SOURCE_NONE
;
90 // ContextMenuController:
91 void ShowContextMenuForView(View
* source
,
92 const gfx::Point
& point
,
93 ui::MenuSourceType source_type
) override
{
94 show_context_menu_calls_
++;
95 menu_source_view_
= source
;
96 menu_source_type_
= source_type
;
100 int show_context_menu_calls_
;
101 View
* menu_source_view_
;
102 ui::MenuSourceType menu_source_type_
;
104 DISALLOW_COPY_AND_ASSIGN(TestContextMenuController
);
107 // Tests that context menus are shown for certain key events (Shift+F10
108 // and VKEY_APPS) by the pre-target handler installed on RootView.
109 TEST_F(RootViewTest
, ContextMenuFromKeyEvent
) {
111 Widget::InitParams init_params
=
112 CreateParams(Widget::InitParams::TYPE_POPUP
);
113 init_params
.ownership
= Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
114 widget
.Init(init_params
);
115 internal::RootView
* root_view
=
116 static_cast<internal::RootView
*>(widget
.GetRootView());
118 TestContextMenuController controller
;
119 View
* focused_view
= new View
;
120 focused_view
->set_context_menu_controller(&controller
);
121 widget
.SetContentsView(focused_view
);
122 focused_view
->SetFocusable(true);
123 focused_view
->RequestFocus();
125 // No context menu should be shown for a keypress of 'A'.
126 ui::KeyEvent
nomenu_key_event('a', ui::VKEY_A
, ui::EF_NONE
);
127 ui::EventDispatchDetails details
=
128 root_view
->OnEventFromSource(&nomenu_key_event
);
129 EXPECT_FALSE(details
.target_destroyed
);
130 EXPECT_FALSE(details
.dispatcher_destroyed
);
131 EXPECT_EQ(0, controller
.show_context_menu_calls());
132 EXPECT_EQ(NULL
, controller
.menu_source_view());
133 EXPECT_EQ(ui::MENU_SOURCE_NONE
, controller
.menu_source_type());
136 // A context menu should be shown for a keypress of Shift+F10.
137 ui::KeyEvent
menu_key_event(
138 ui::ET_KEY_PRESSED
, ui::VKEY_F10
, ui::EF_SHIFT_DOWN
);
139 details
= root_view
->OnEventFromSource(&menu_key_event
);
140 EXPECT_FALSE(details
.target_destroyed
);
141 EXPECT_FALSE(details
.dispatcher_destroyed
);
142 EXPECT_EQ(1, controller
.show_context_menu_calls());
143 EXPECT_EQ(focused_view
, controller
.menu_source_view());
144 EXPECT_EQ(ui::MENU_SOURCE_KEYBOARD
, controller
.menu_source_type());
147 // A context menu should be shown for a keypress of VKEY_APPS.
148 ui::KeyEvent
menu_key_event2(ui::ET_KEY_PRESSED
, ui::VKEY_APPS
, ui::EF_NONE
);
149 details
= root_view
->OnEventFromSource(&menu_key_event2
);
150 EXPECT_FALSE(details
.target_destroyed
);
151 EXPECT_FALSE(details
.dispatcher_destroyed
);
152 EXPECT_EQ(1, controller
.show_context_menu_calls());
153 EXPECT_EQ(focused_view
, controller
.menu_source_view());
154 EXPECT_EQ(ui::MENU_SOURCE_KEYBOARD
, controller
.menu_source_type());
158 // View which handles all gesture events.
159 class GestureHandlingView
: public View
{
161 GestureHandlingView() {
164 ~GestureHandlingView() override
{}
166 void OnGestureEvent(ui::GestureEvent
* event
) override
{ event
->SetHandled(); }
169 DISALLOW_COPY_AND_ASSIGN(GestureHandlingView
);
172 // Tests that context menus are shown for long press by the post-target handler
173 // installed on the RootView only if the event is targetted at a view which can
174 // show a context menu.
175 TEST_F(RootViewTest
, ContextMenuFromLongPress
) {
177 Widget::InitParams init_params
=
178 CreateParams(Widget::InitParams::TYPE_POPUP
);
179 init_params
.ownership
= Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
180 init_params
.bounds
= gfx::Rect(100, 100);
181 widget
.Init(init_params
);
182 internal::RootView
* root_view
=
183 static_cast<internal::RootView
*>(widget
.GetRootView());
185 // Create a view capable of showing the context menu with two children one of
186 // which handles all gesture events (e.g. a button).
187 TestContextMenuController controller
;
188 View
* parent_view
= new View
;
189 parent_view
->set_context_menu_controller(&controller
);
190 widget
.SetContentsView(parent_view
);
192 View
* gesture_handling_child_view
= new GestureHandlingView
;
193 gesture_handling_child_view
->SetBoundsRect(gfx::Rect(10, 10));
194 parent_view
->AddChildView(gesture_handling_child_view
);
196 View
* other_child_view
= new View
;
197 other_child_view
->SetBoundsRect(gfx::Rect(20, 0, 10, 10));
198 parent_view
->AddChildView(other_child_view
);
200 // |parent_view| should not show a context menu as a result of a long press on
201 // |gesture_handling_child_view|.
202 ui::GestureEvent
long_press1(
207 ui::GestureEventDetails(ui::ET_GESTURE_LONG_PRESS
));
208 ui::EventDispatchDetails details
= root_view
->OnEventFromSource(&long_press1
);
210 ui::GestureEvent
end1(
211 5, 5, 0, base::TimeDelta(), ui::GestureEventDetails(ui::ET_GESTURE_END
));
212 details
= root_view
->OnEventFromSource(&end1
);
214 EXPECT_FALSE(details
.target_destroyed
);
215 EXPECT_FALSE(details
.dispatcher_destroyed
);
216 EXPECT_EQ(0, controller
.show_context_menu_calls());
219 // |parent_view| should show a context menu as a result of a long press on
220 // |other_child_view|.
221 ui::GestureEvent
long_press2(
226 ui::GestureEventDetails(ui::ET_GESTURE_LONG_PRESS
));
227 details
= root_view
->OnEventFromSource(&long_press2
);
229 ui::GestureEvent
end2(
230 25, 5, 0, base::TimeDelta(), ui::GestureEventDetails(ui::ET_GESTURE_END
));
231 details
= root_view
->OnEventFromSource(&end2
);
233 EXPECT_FALSE(details
.target_destroyed
);
234 EXPECT_FALSE(details
.dispatcher_destroyed
);
235 EXPECT_EQ(1, controller
.show_context_menu_calls());
238 // |parent_view| should show a context menu as a result of a long press on
240 ui::GestureEvent
long_press3(
245 ui::GestureEventDetails(ui::ET_GESTURE_LONG_PRESS
));
246 details
= root_view
->OnEventFromSource(&long_press3
);
248 ui::GestureEvent
end3(
249 25, 5, 0, base::TimeDelta(), ui::GestureEventDetails(ui::ET_GESTURE_END
));
250 details
= root_view
->OnEventFromSource(&end3
);
252 EXPECT_FALSE(details
.target_destroyed
);
253 EXPECT_FALSE(details
.dispatcher_destroyed
);
254 EXPECT_EQ(1, controller
.show_context_menu_calls());
257 // Tests that context menus are not shown for disabled views on a long press.
258 TEST_F(RootViewTest
, ContextMenuFromLongPressOnDisabledView
) {
260 Widget::InitParams init_params
=
261 CreateParams(Widget::InitParams::TYPE_POPUP
);
262 init_params
.ownership
= Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
263 init_params
.bounds
= gfx::Rect(100, 100);
264 widget
.Init(init_params
);
265 internal::RootView
* root_view
=
266 static_cast<internal::RootView
*>(widget
.GetRootView());
268 // Create a view capable of showing the context menu with two children one of
269 // which handles all gesture events (e.g. a button). Also mark this view
271 TestContextMenuController controller
;
272 View
* parent_view
= new View
;
273 parent_view
->set_context_menu_controller(&controller
);
274 parent_view
->SetEnabled(false);
275 widget
.SetContentsView(parent_view
);
277 View
* gesture_handling_child_view
= new GestureHandlingView
;
278 gesture_handling_child_view
->SetBoundsRect(gfx::Rect(10, 10));
279 parent_view
->AddChildView(gesture_handling_child_view
);
281 View
* other_child_view
= new View
;
282 other_child_view
->SetBoundsRect(gfx::Rect(20, 0, 10, 10));
283 parent_view
->AddChildView(other_child_view
);
285 // |parent_view| should not show a context menu as a result of a long press on
286 // |gesture_handling_child_view|.
287 ui::GestureEvent
long_press1(
292 ui::GestureEventDetails(ui::ET_GESTURE_LONG_PRESS
));
293 ui::EventDispatchDetails details
= root_view
->OnEventFromSource(&long_press1
);
295 ui::GestureEvent
end1(
296 5, 5, 0, base::TimeDelta(), ui::GestureEventDetails(ui::ET_GESTURE_END
));
297 details
= root_view
->OnEventFromSource(&end1
);
299 EXPECT_FALSE(details
.target_destroyed
);
300 EXPECT_FALSE(details
.dispatcher_destroyed
);
301 EXPECT_EQ(0, controller
.show_context_menu_calls());
304 // |parent_view| should not show a context menu as a result of a long press on
305 // |other_child_view|.
306 ui::GestureEvent
long_press2(
311 ui::GestureEventDetails(ui::ET_GESTURE_LONG_PRESS
));
312 details
= root_view
->OnEventFromSource(&long_press2
);
314 ui::GestureEvent
end2(
315 25, 5, 0, base::TimeDelta(), ui::GestureEventDetails(ui::ET_GESTURE_END
));
316 details
= root_view
->OnEventFromSource(&end2
);
318 EXPECT_FALSE(details
.target_destroyed
);
319 EXPECT_FALSE(details
.dispatcher_destroyed
);
320 EXPECT_EQ(0, controller
.show_context_menu_calls());
323 // |parent_view| should not show a context menu as a result of a long press on
325 ui::GestureEvent
long_press3(
330 ui::GestureEventDetails(ui::ET_GESTURE_LONG_PRESS
));
331 details
= root_view
->OnEventFromSource(&long_press3
);
333 ui::GestureEvent
end3(
334 25, 5, 0, base::TimeDelta(), ui::GestureEventDetails(ui::ET_GESTURE_END
));
335 details
= root_view
->OnEventFromSource(&end3
);
337 EXPECT_FALSE(details
.target_destroyed
);
338 EXPECT_FALSE(details
.dispatcher_destroyed
);
339 EXPECT_EQ(0, controller
.show_context_menu_calls());
344 // View class which destroys itself when it gets an event of type
345 // |delete_event_type|.
346 class DeleteViewOnEvent
: public View
{
348 DeleteViewOnEvent(ui::EventType delete_event_type
, bool* was_destroyed
)
349 : delete_event_type_(delete_event_type
), was_destroyed_(was_destroyed
) {}
351 ~DeleteViewOnEvent() override
{
352 *was_destroyed_
= true;
355 void OnEvent(ui::Event
* event
) override
{
356 if (event
->type() == delete_event_type_
)
361 // The event type which causes the view to destroy itself.
362 ui::EventType delete_event_type_
;
364 // Tracks whether the view was destroyed.
365 bool* was_destroyed_
;
367 DISALLOW_COPY_AND_ASSIGN(DeleteViewOnEvent
);
372 // Verifies deleting a View in OnMouseExited() doesn't crash.
373 TEST_F(RootViewTest
, DeleteViewOnMouseExitDispatch
) {
375 Widget::InitParams init_params
=
376 CreateParams(Widget::InitParams::TYPE_POPUP
);
377 init_params
.ownership
= Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
378 widget
.Init(init_params
);
379 widget
.SetBounds(gfx::Rect(10, 10, 500, 500));
381 View
* content
= new View
;
382 widget
.SetContentsView(content
);
384 bool view_destroyed
= false;
385 View
* child
= new DeleteViewOnEvent(ui::ET_MOUSE_EXITED
, &view_destroyed
);
386 content
->AddChildView(child
);
387 child
->SetBounds(10, 10, 500, 500);
389 internal::RootView
* root_view
=
390 static_cast<internal::RootView
*>(widget
.GetRootView());
392 // Generate a mouse move event which ensures that the mouse_moved_handler_
393 // member is set in the RootView class.
394 ui::MouseEvent
moved_event(ui::ET_MOUSE_MOVED
, gfx::Point(15, 15),
395 gfx::Point(15, 15), ui::EventTimeForNow(), 0,
397 root_view
->OnMouseMoved(moved_event
);
398 ASSERT_FALSE(view_destroyed
);
400 // Generate a mouse exit event which in turn will delete the child view which
401 // was the target of the mouse move event above. This should not crash when
402 // the mouse exit handler returns from the child.
403 ui::MouseEvent
exit_event(ui::ET_MOUSE_EXITED
, gfx::Point(), gfx::Point(),
404 ui::EventTimeForNow(), 0, 0);
405 root_view
->OnMouseExited(exit_event
);
407 EXPECT_TRUE(view_destroyed
);
408 EXPECT_FALSE(content
->has_children());
411 // Verifies deleting a View in OnMouseEntered() doesn't crash.
412 TEST_F(RootViewTest
, DeleteViewOnMouseEnterDispatch
) {
414 Widget::InitParams init_params
=
415 CreateParams(Widget::InitParams::TYPE_POPUP
);
416 init_params
.ownership
= Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
417 widget
.Init(init_params
);
418 widget
.SetBounds(gfx::Rect(10, 10, 500, 500));
420 View
* content
= new View
;
421 widget
.SetContentsView(content
);
423 bool view_destroyed
= false;
424 View
* child
= new DeleteViewOnEvent(ui::ET_MOUSE_ENTERED
, &view_destroyed
);
425 content
->AddChildView(child
);
427 // Make |child| smaller than the containing Widget and RootView.
428 child
->SetBounds(100, 100, 100, 100);
430 internal::RootView
* root_view
=
431 static_cast<internal::RootView
*>(widget
.GetRootView());
433 // Move the mouse within |widget| but outside of |child|.
434 ui::MouseEvent
moved_event(ui::ET_MOUSE_MOVED
, gfx::Point(15, 15),
435 gfx::Point(15, 15), ui::EventTimeForNow(), 0,
437 root_view
->OnMouseMoved(moved_event
);
438 ASSERT_FALSE(view_destroyed
);
440 // Move the mouse within |child|, which should dispatch a mouse enter event to
441 // |child| and destroy the view. This should not crash when the mouse enter
442 // handler returns from the child.
443 ui::MouseEvent
moved_event2(ui::ET_MOUSE_MOVED
, gfx::Point(115, 115),
444 gfx::Point(115, 115), ui::EventTimeForNow(), 0,
446 root_view
->OnMouseMoved(moved_event2
);
448 EXPECT_TRUE(view_destroyed
);
449 EXPECT_FALSE(content
->has_children());
454 // View class which deletes its owning Widget when it gets a mouse exit event.
455 class DeleteWidgetOnMouseExit
: public View
{
457 explicit DeleteWidgetOnMouseExit(Widget
* widget
)
461 ~DeleteWidgetOnMouseExit() override
{}
463 void OnMouseExited(const ui::MouseEvent
& event
) override
{
470 DISALLOW_COPY_AND_ASSIGN(DeleteWidgetOnMouseExit
);
475 // Test that there is no crash if a View deletes its parent Widget in
476 // View::OnMouseExited().
477 TEST_F(RootViewTest
, DeleteWidgetOnMouseExitDispatch
) {
478 Widget
* widget
= new Widget
;
479 Widget::InitParams init_params
=
480 CreateParams(Widget::InitParams::TYPE_POPUP
);
481 init_params
.ownership
= Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
482 widget
->Init(init_params
);
483 widget
->SetBounds(gfx::Rect(10, 10, 500, 500));
484 WidgetDeletionObserver
widget_deletion_observer(widget
);
486 View
* content
= new View();
487 View
* child
= new DeleteWidgetOnMouseExit(widget
);
488 content
->AddChildView(child
);
489 widget
->SetContentsView(content
);
491 // Make |child| smaller than the containing Widget and RootView.
492 child
->SetBounds(100, 100, 100, 100);
494 internal::RootView
* root_view
=
495 static_cast<internal::RootView
*>(widget
->GetRootView());
497 // Move the mouse within |child|.
498 ui::MouseEvent
moved_event(ui::ET_MOUSE_MOVED
, gfx::Point(115, 115),
499 gfx::Point(115, 115), ui::EventTimeForNow(), 0,
501 root_view
->OnMouseMoved(moved_event
);
502 ASSERT_TRUE(widget_deletion_observer
.IsWidgetAlive());
504 // Move the mouse outside of |child| which should dispatch a mouse exit event
505 // to |child| and destroy the widget. This should not crash when the mouse
506 // exit handler returns from the child.
507 ui::MouseEvent
move_event2(ui::ET_MOUSE_MOVED
, gfx::Point(15, 15),
508 gfx::Point(15, 15), ui::EventTimeForNow(), 0, 0);
509 root_view
->OnMouseMoved(move_event2
);
510 EXPECT_FALSE(widget_deletion_observer
.IsWidgetAlive());
513 // Test that there is no crash if a View deletes its parent widget as a result
514 // of a mouse exited event which was propagated from one of its children.
515 TEST_F(RootViewTest
, DeleteWidgetOnMouseExitDispatchFromChild
) {
516 Widget
* widget
= new Widget
;
517 Widget::InitParams init_params
=
518 CreateParams(Widget::InitParams::TYPE_POPUP
);
519 init_params
.ownership
= Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
520 widget
->Init(init_params
);
521 widget
->SetBounds(gfx::Rect(10, 10, 500, 500));
522 WidgetDeletionObserver
widget_deletion_observer(widget
);
524 View
* content
= new View();
525 View
* child
= new DeleteWidgetOnMouseExit(widget
);
526 View
* subchild
= new View();
527 widget
->SetContentsView(content
);
528 content
->AddChildView(child
);
529 child
->AddChildView(subchild
);
531 // Make |child| and |subchild| smaller than the containing Widget and
533 child
->SetBounds(100, 100, 100, 100);
534 subchild
->SetBounds(0, 0, 100, 100);
536 // Make mouse enter and exit events get propagated from |subchild| to |child|.
537 child
->set_notify_enter_exit_on_child(true);
539 internal::RootView
* root_view
=
540 static_cast<internal::RootView
*>(widget
->GetRootView());
542 // Move the mouse within |subchild| and |child|.
543 ui::MouseEvent
moved_event(ui::ET_MOUSE_MOVED
, gfx::Point(115, 115),
544 gfx::Point(115, 115), ui::EventTimeForNow(), 0, 0);
545 root_view
->OnMouseMoved(moved_event
);
546 ASSERT_TRUE(widget_deletion_observer
.IsWidgetAlive());
548 // Move the mouse outside of |subchild| and |child| which should dispatch a
549 // mouse exit event to |subchild| and destroy the widget. This should not
550 // crash when the mouse exit handler returns from |subchild|.
551 ui::MouseEvent
move_event2(ui::ET_MOUSE_MOVED
, gfx::Point(15, 15),
552 gfx::Point(15, 15), ui::EventTimeForNow(), 0, 0);
553 root_view
->OnMouseMoved(move_event2
);
554 EXPECT_FALSE(widget_deletion_observer
.IsWidgetAlive());