Roll src/third_party/skia d32087a:1052f51
[chromium-blink-merge.git] / ui / views / widget / root_view_unittest.cc
blobea739ae9229269e7f39920d64948cd54c4d75f96
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"
14 namespace views {
15 namespace test {
17 typedef ViewsTestBase RootViewTest;
19 class DeleteOnKeyEventView : public View {
20 public:
21 explicit DeleteOnKeyEventView(bool* set_on_key) : set_on_key_(set_on_key) {}
22 ~DeleteOnKeyEventView() override {}
24 bool OnKeyPressed(const ui::KeyEvent& event) override {
25 *set_on_key_ = true;
26 delete this;
27 return true;
30 private:
31 // Set to true in OnKeyPressed().
32 bool* set_on_key_;
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) {
40 Widget widget;
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 {
72 public:
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_; }
84 void Reset() {
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;
99 private:
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) {
110 Widget widget;
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());
134 controller.Reset();
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());
145 controller.Reset();
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());
155 controller.Reset();
158 // View which handles all gesture events.
159 class GestureHandlingView : public View {
160 public:
161 GestureHandlingView() {
164 ~GestureHandlingView() override {}
166 void OnGestureEvent(ui::GestureEvent* event) override { event->SetHandled(); }
168 private:
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) {
176 Widget widget;
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(
206 base::TimeDelta(),
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());
217 controller.Reset();
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(
225 base::TimeDelta(),
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());
236 controller.Reset();
238 // |parent_view| should show a context menu as a result of a long press on
239 // itself.
240 ui::GestureEvent long_press3(
244 base::TimeDelta(),
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) {
259 Widget widget;
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
270 // as disabled.
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(
291 base::TimeDelta(),
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());
302 controller.Reset();
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(
310 base::TimeDelta(),
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());
321 controller.Reset();
323 // |parent_view| should not show a context menu as a result of a long press on
324 // itself.
325 ui::GestureEvent long_press3(
329 base::TimeDelta(),
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());
342 namespace {
344 // View class which destroys itself when it gets an event of type
345 // |delete_event_type|.
346 class DeleteViewOnEvent : public View {
347 public:
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_)
357 delete this;
360 private:
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);
370 } // namespace
372 // Verifies deleting a View in OnMouseExited() doesn't crash.
373 TEST_F(RootViewTest, DeleteViewOnMouseExitDispatch) {
374 Widget widget;
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) {
413 Widget widget;
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());
452 namespace {
454 // View class which deletes its owning Widget when it gets a mouse exit event.
455 class DeleteWidgetOnMouseExit : public View {
456 public:
457 explicit DeleteWidgetOnMouseExit(Widget* widget)
458 : widget_(widget) {
461 ~DeleteWidgetOnMouseExit() override {}
463 void OnMouseExited(const ui::MouseEvent& event) override {
464 delete widget_;
467 private:
468 Widget* widget_;
470 DISALLOW_COPY_AND_ASSIGN(DeleteWidgetOnMouseExit);
473 } // namespace
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
532 // RootView.
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());
557 } // namespace test
558 } // namespace views