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"
16 typedef ViewsTestBase RootViewTest
;
18 class DeleteOnKeyEventView
: public View
{
20 explicit DeleteOnKeyEventView(bool* set_on_key
) : set_on_key_(set_on_key
) {}
21 ~DeleteOnKeyEventView() override
{}
23 bool OnKeyPressed(const ui::KeyEvent
& event
) override
{
30 // Set to true in OnKeyPressed().
33 DISALLOW_COPY_AND_ASSIGN(DeleteOnKeyEventView
);
36 // Verifies deleting a View in OnKeyPressed() doesn't crash and that the
37 // target is marked as destroyed in the returned EventDispatchDetails.
38 TEST_F(RootViewTest
, DeleteViewDuringKeyEventDispatch
) {
40 Widget::InitParams init_params
=
41 CreateParams(Widget::InitParams::TYPE_POPUP
);
42 init_params
.ownership
= Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
43 widget
.Init(init_params
);
45 bool got_key_event
= false;
47 View
* content
= new View
;
48 widget
.SetContentsView(content
);
50 View
* child
= new DeleteOnKeyEventView(&got_key_event
);
51 content
->AddChildView(child
);
53 // Give focus to |child| so that it will be the target of the key event.
54 child
->SetFocusable(true);
55 child
->RequestFocus();
57 internal::RootView
* root_view
=
58 static_cast<internal::RootView
*>(widget
.GetRootView());
59 ViewTargeter
* view_targeter
= new ViewTargeter(root_view
);
60 root_view
->SetEventTargeter(make_scoped_ptr(view_targeter
));
62 ui::KeyEvent
key_event(ui::ET_KEY_PRESSED
, ui::VKEY_ESCAPE
, ui::EF_NONE
);
63 ui::EventDispatchDetails details
= root_view
->OnEventFromSource(&key_event
);
64 EXPECT_TRUE(details
.target_destroyed
);
65 EXPECT_FALSE(details
.dispatcher_destroyed
);
66 EXPECT_TRUE(got_key_event
);
69 // Tracks whether a context menu is shown.
70 class TestContextMenuController
: public ContextMenuController
{
72 TestContextMenuController()
73 : show_context_menu_calls_(0),
74 menu_source_view_(NULL
),
75 menu_source_type_(ui::MENU_SOURCE_NONE
) {
77 ~TestContextMenuController() override
{}
79 int show_context_menu_calls() const { return show_context_menu_calls_
; }
80 View
* menu_source_view() const { return menu_source_view_
; }
81 ui::MenuSourceType
menu_source_type() const { return menu_source_type_
; }
84 show_context_menu_calls_
= 0;
85 menu_source_view_
= NULL
;
86 menu_source_type_
= ui::MENU_SOURCE_NONE
;
89 // ContextMenuController:
90 void ShowContextMenuForView(View
* source
,
91 const gfx::Point
& point
,
92 ui::MenuSourceType source_type
) override
{
93 show_context_menu_calls_
++;
94 menu_source_view_
= source
;
95 menu_source_type_
= source_type
;
99 int show_context_menu_calls_
;
100 View
* menu_source_view_
;
101 ui::MenuSourceType menu_source_type_
;
103 DISALLOW_COPY_AND_ASSIGN(TestContextMenuController
);
106 // Tests that context menus are shown for certain key events (Shift+F10
107 // and VKEY_APPS) by the pre-target handler installed on RootView.
108 TEST_F(RootViewTest
, ContextMenuFromKeyEvent
) {
110 Widget::InitParams init_params
=
111 CreateParams(Widget::InitParams::TYPE_POPUP
);
112 init_params
.ownership
= Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
113 widget
.Init(init_params
);
114 internal::RootView
* root_view
=
115 static_cast<internal::RootView
*>(widget
.GetRootView());
117 TestContextMenuController controller
;
118 View
* focused_view
= new View
;
119 focused_view
->set_context_menu_controller(&controller
);
120 widget
.SetContentsView(focused_view
);
121 focused_view
->SetFocusable(true);
122 focused_view
->RequestFocus();
124 // No context menu should be shown for a keypress of 'A'.
125 ui::KeyEvent
nomenu_key_event('a', ui::VKEY_A
, ui::EF_NONE
);
126 ui::EventDispatchDetails details
=
127 root_view
->OnEventFromSource(&nomenu_key_event
);
128 EXPECT_FALSE(details
.target_destroyed
);
129 EXPECT_FALSE(details
.dispatcher_destroyed
);
130 EXPECT_EQ(0, controller
.show_context_menu_calls());
131 EXPECT_EQ(NULL
, controller
.menu_source_view());
132 EXPECT_EQ(ui::MENU_SOURCE_NONE
, controller
.menu_source_type());
135 // A context menu should be shown for a keypress of Shift+F10.
136 ui::KeyEvent
menu_key_event(
137 ui::ET_KEY_PRESSED
, ui::VKEY_F10
, ui::EF_SHIFT_DOWN
);
138 details
= root_view
->OnEventFromSource(&menu_key_event
);
139 EXPECT_FALSE(details
.target_destroyed
);
140 EXPECT_FALSE(details
.dispatcher_destroyed
);
141 EXPECT_EQ(1, controller
.show_context_menu_calls());
142 EXPECT_EQ(focused_view
, controller
.menu_source_view());
143 EXPECT_EQ(ui::MENU_SOURCE_KEYBOARD
, controller
.menu_source_type());
146 // A context menu should be shown for a keypress of VKEY_APPS.
147 ui::KeyEvent
menu_key_event2(ui::ET_KEY_PRESSED
, ui::VKEY_APPS
, ui::EF_NONE
);
148 details
= root_view
->OnEventFromSource(&menu_key_event2
);
149 EXPECT_FALSE(details
.target_destroyed
);
150 EXPECT_FALSE(details
.dispatcher_destroyed
);
151 EXPECT_EQ(1, controller
.show_context_menu_calls());
152 EXPECT_EQ(focused_view
, controller
.menu_source_view());
153 EXPECT_EQ(ui::MENU_SOURCE_KEYBOARD
, controller
.menu_source_type());
157 // View which handles all gesture events.
158 class GestureHandlingView
: public View
{
160 GestureHandlingView() {
163 ~GestureHandlingView() override
{}
165 void OnGestureEvent(ui::GestureEvent
* event
) override
{ event
->SetHandled(); }
168 DISALLOW_COPY_AND_ASSIGN(GestureHandlingView
);
171 // Tests that context menus are shown for long press by the post-target handler
172 // installed on the RootView only if the event is targetted at a view which can
173 // show a context menu.
174 TEST_F(RootViewTest
, ContextMenuFromLongPress
) {
176 Widget::InitParams init_params
=
177 CreateParams(Widget::InitParams::TYPE_POPUP
);
178 init_params
.ownership
= Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
179 init_params
.bounds
= gfx::Rect(100, 100);
180 widget
.Init(init_params
);
181 internal::RootView
* root_view
=
182 static_cast<internal::RootView
*>(widget
.GetRootView());
184 // Create a view capable of showing the context menu with two children one of
185 // which handles all gesture events (e.g. a button).
186 TestContextMenuController controller
;
187 View
* parent_view
= new View
;
188 parent_view
->set_context_menu_controller(&controller
);
189 widget
.SetContentsView(parent_view
);
191 View
* gesture_handling_child_view
= new GestureHandlingView
;
192 gesture_handling_child_view
->SetBoundsRect(gfx::Rect(10, 10));
193 parent_view
->AddChildView(gesture_handling_child_view
);
195 View
* other_child_view
= new View
;
196 other_child_view
->SetBoundsRect(gfx::Rect(20, 0, 10, 10));
197 parent_view
->AddChildView(other_child_view
);
199 // |parent_view| should not show a context menu as a result of a long press on
200 // |gesture_handling_child_view|.
201 ui::GestureEvent
long_press1(
206 ui::GestureEventDetails(ui::ET_GESTURE_LONG_PRESS
));
207 ui::EventDispatchDetails details
= root_view
->OnEventFromSource(&long_press1
);
209 ui::GestureEvent
end1(
210 5, 5, 0, base::TimeDelta(), ui::GestureEventDetails(ui::ET_GESTURE_END
));
211 details
= root_view
->OnEventFromSource(&end1
);
213 EXPECT_FALSE(details
.target_destroyed
);
214 EXPECT_FALSE(details
.dispatcher_destroyed
);
215 EXPECT_EQ(0, controller
.show_context_menu_calls());
218 // |parent_view| should show a context menu as a result of a long press on
219 // |other_child_view|.
220 ui::GestureEvent
long_press2(
225 ui::GestureEventDetails(ui::ET_GESTURE_LONG_PRESS
));
226 details
= root_view
->OnEventFromSource(&long_press2
);
228 ui::GestureEvent
end2(
229 25, 5, 0, base::TimeDelta(), ui::GestureEventDetails(ui::ET_GESTURE_END
));
230 details
= root_view
->OnEventFromSource(&end2
);
232 EXPECT_FALSE(details
.target_destroyed
);
233 EXPECT_FALSE(details
.dispatcher_destroyed
);
234 EXPECT_EQ(1, controller
.show_context_menu_calls());
237 // |parent_view| should show a context menu as a result of a long press on
239 ui::GestureEvent
long_press3(
244 ui::GestureEventDetails(ui::ET_GESTURE_LONG_PRESS
));
245 details
= root_view
->OnEventFromSource(&long_press3
);
247 ui::GestureEvent
end3(
248 25, 5, 0, base::TimeDelta(), ui::GestureEventDetails(ui::ET_GESTURE_END
));
249 details
= root_view
->OnEventFromSource(&end3
);
251 EXPECT_FALSE(details
.target_destroyed
);
252 EXPECT_FALSE(details
.dispatcher_destroyed
);
253 EXPECT_EQ(1, controller
.show_context_menu_calls());
256 // Tests that context menus are not shown for disabled views on a long press.
257 TEST_F(RootViewTest
, ContextMenuFromLongPressOnDisabledView
) {
259 Widget::InitParams init_params
=
260 CreateParams(Widget::InitParams::TYPE_POPUP
);
261 init_params
.ownership
= Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
262 init_params
.bounds
= gfx::Rect(100, 100);
263 widget
.Init(init_params
);
264 internal::RootView
* root_view
=
265 static_cast<internal::RootView
*>(widget
.GetRootView());
267 // Create a view capable of showing the context menu with two children one of
268 // which handles all gesture events (e.g. a button). Also mark this view
270 TestContextMenuController controller
;
271 View
* parent_view
= new View
;
272 parent_view
->set_context_menu_controller(&controller
);
273 parent_view
->SetEnabled(false);
274 widget
.SetContentsView(parent_view
);
276 View
* gesture_handling_child_view
= new GestureHandlingView
;
277 gesture_handling_child_view
->SetBoundsRect(gfx::Rect(10, 10));
278 parent_view
->AddChildView(gesture_handling_child_view
);
280 View
* other_child_view
= new View
;
281 other_child_view
->SetBoundsRect(gfx::Rect(20, 0, 10, 10));
282 parent_view
->AddChildView(other_child_view
);
284 // |parent_view| should not show a context menu as a result of a long press on
285 // |gesture_handling_child_view|.
286 ui::GestureEvent
long_press1(
291 ui::GestureEventDetails(ui::ET_GESTURE_LONG_PRESS
));
292 ui::EventDispatchDetails details
= root_view
->OnEventFromSource(&long_press1
);
294 ui::GestureEvent
end1(
295 5, 5, 0, base::TimeDelta(), ui::GestureEventDetails(ui::ET_GESTURE_END
));
296 details
= root_view
->OnEventFromSource(&end1
);
298 EXPECT_FALSE(details
.target_destroyed
);
299 EXPECT_FALSE(details
.dispatcher_destroyed
);
300 EXPECT_EQ(0, controller
.show_context_menu_calls());
303 // |parent_view| should not show a context menu as a result of a long press on
304 // |other_child_view|.
305 ui::GestureEvent
long_press2(
310 ui::GestureEventDetails(ui::ET_GESTURE_LONG_PRESS
));
311 details
= root_view
->OnEventFromSource(&long_press2
);
313 ui::GestureEvent
end2(
314 25, 5, 0, base::TimeDelta(), ui::GestureEventDetails(ui::ET_GESTURE_END
));
315 details
= root_view
->OnEventFromSource(&end2
);
317 EXPECT_FALSE(details
.target_destroyed
);
318 EXPECT_FALSE(details
.dispatcher_destroyed
);
319 EXPECT_EQ(0, controller
.show_context_menu_calls());
322 // |parent_view| should not show a context menu as a result of a long press on
324 ui::GestureEvent
long_press3(
329 ui::GestureEventDetails(ui::ET_GESTURE_LONG_PRESS
));
330 details
= root_view
->OnEventFromSource(&long_press3
);
332 ui::GestureEvent
end3(
333 25, 5, 0, base::TimeDelta(), ui::GestureEventDetails(ui::ET_GESTURE_END
));
334 details
= root_view
->OnEventFromSource(&end3
);
336 EXPECT_FALSE(details
.target_destroyed
);
337 EXPECT_FALSE(details
.dispatcher_destroyed
);
338 EXPECT_EQ(0, controller
.show_context_menu_calls());
341 // This view class provides functionality to delete itself in the context of
342 // mouse exit event and helps test that we don't crash when we return from
343 // the mouse exit handler.
344 class DeleteViewOnMouseExit
: public View
{
346 explicit DeleteViewOnMouseExit(bool* got_mouse_exit
)
347 : got_mouse_exit_(got_mouse_exit
) {
350 ~DeleteViewOnMouseExit() override
{}
352 void OnMouseExited(const ui::MouseEvent
& event
) override
{
353 *got_mouse_exit_
= true;
358 // Set to true in OnMouseExited().
359 bool* got_mouse_exit_
;
361 DISALLOW_COPY_AND_ASSIGN(DeleteViewOnMouseExit
);
364 // Verifies deleting a View in OnMouseExited() doesn't crash.
365 TEST_F(RootViewTest
, DeleteViewOnMouseExitDispatch
) {
367 Widget::InitParams init_params
=
368 CreateParams(Widget::InitParams::TYPE_POPUP
);
369 init_params
.ownership
= Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
370 widget
.Init(init_params
);
371 widget
.SetBounds(gfx::Rect(10, 10, 500, 500));
373 View
* content
= new View
;
374 widget
.SetContentsView(content
);
376 bool got_mouse_exit
= false;
377 View
* child
= new DeleteViewOnMouseExit(&got_mouse_exit
);
378 content
->AddChildView(child
);
379 child
->SetBounds(10, 10, 500, 500);
381 internal::RootView
* root_view
=
382 static_cast<internal::RootView
*>(widget
.GetRootView());
384 // Generate a mouse move event which ensures that the mouse_moved_handler_
385 // member is set in the RootView class.
386 ui::MouseEvent
moved_event(ui::ET_MOUSE_MOVED
, gfx::Point(15, 15),
387 gfx::Point(100, 100), ui::EventTimeForNow(), 0,
389 root_view
->OnMouseMoved(moved_event
);
390 EXPECT_FALSE(got_mouse_exit
);
392 // Generate a mouse exit event which in turn will delete the child view which
393 // was the target of the mouse move event above. This should not crash when
394 // the mouse exit handler returns from the child.
395 ui::MouseEvent
exit_event(ui::ET_MOUSE_EXITED
, gfx::Point(), gfx::Point(),
396 ui::EventTimeForNow(), 0, 0);
397 root_view
->OnMouseExited(exit_event
);
399 EXPECT_TRUE(got_mouse_exit
);
400 EXPECT_FALSE(content
->has_children());