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.
4
5#include "ui/views/widget/root_view.h"
6
7#include "ui/views/context_menu_controller.h"
8#include "ui/views/test/views_test_base.h"
9#include "ui/views/view_targeter.h"
10#include "ui/views/widget/root_view.h"
11
12namespace views {
13namespace test {
14
15typedef ViewsTestBase RootViewTest;
16
17class DeleteOnKeyEventView : public View {
18 public:
19  explicit DeleteOnKeyEventView(bool* set_on_key) : set_on_key_(set_on_key) {}
20  virtual ~DeleteOnKeyEventView() {}
21
22  virtual bool OnKeyPressed(const ui::KeyEvent& event) OVERRIDE {
23    *set_on_key_ = true;
24    delete this;
25    return true;
26  }
27
28 private:
29  // Set to true in OnKeyPressed().
30  bool* set_on_key_;
31
32  DISALLOW_COPY_AND_ASSIGN(DeleteOnKeyEventView);
33};
34
35// Verifies deleting a View in OnKeyPressed() doesn't crash and that the
36// target is marked as destroyed in the returned EventDispatchDetails.
37TEST_F(RootViewTest, DeleteViewDuringKeyEventDispatch) {
38  Widget widget;
39  Widget::InitParams init_params =
40      CreateParams(Widget::InitParams::TYPE_POPUP);
41  init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
42  widget.Init(init_params);
43
44  bool got_key_event = false;
45
46  View* content = new View;
47  widget.SetContentsView(content);
48
49  View* child = new DeleteOnKeyEventView(&got_key_event);
50  content->AddChildView(child);
51
52  // Give focus to |child| so that it will be the target of the key event.
53  child->SetFocusable(true);
54  child->RequestFocus();
55
56  internal::RootView* root_view =
57      static_cast<internal::RootView*>(widget.GetRootView());
58  ViewTargeter* view_targeter = new ViewTargeter(root_view);
59  root_view->SetEventTargeter(make_scoped_ptr(view_targeter));
60
61  ui::KeyEvent key_event(ui::ET_KEY_PRESSED, ui::VKEY_ESCAPE, ui::EF_NONE);
62  ui::EventDispatchDetails details = root_view->OnEventFromSource(&key_event);
63  EXPECT_TRUE(details.target_destroyed);
64  EXPECT_FALSE(details.dispatcher_destroyed);
65  EXPECT_TRUE(got_key_event);
66}
67
68// Tracks whether a context menu is shown.
69class TestContextMenuController : public ContextMenuController {
70 public:
71  TestContextMenuController()
72      : show_context_menu_calls_(0),
73        menu_source_view_(NULL),
74        menu_source_type_(ui::MENU_SOURCE_NONE) {
75  }
76  virtual ~TestContextMenuController() {}
77
78  int show_context_menu_calls() const { return show_context_menu_calls_; }
79  View* menu_source_view() const { return menu_source_view_; }
80  ui::MenuSourceType menu_source_type() const { return menu_source_type_; }
81
82  void Reset() {
83    show_context_menu_calls_ = 0;
84    menu_source_view_ = NULL;
85    menu_source_type_ = ui::MENU_SOURCE_NONE;
86  }
87
88  // ContextMenuController:
89  virtual void ShowContextMenuForView(
90      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;
96  }
97
98 private:
99  int show_context_menu_calls_;
100  View* menu_source_view_;
101  ui::MenuSourceType menu_source_type_;
102
103  DISALLOW_COPY_AND_ASSIGN(TestContextMenuController);
104};
105
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.
108TEST_F(RootViewTest, ContextMenuFromKeyEvent) {
109  Widget widget;
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());
116
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();
123
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());
133  controller.Reset();
134
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());
144  controller.Reset();
145
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());
154  controller.Reset();
155}
156
157// View which handles all gesture events.
158class GestureHandlingView : public View {
159 public:
160  GestureHandlingView() {
161  }
162
163  virtual ~GestureHandlingView() {
164  }
165
166  virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE {
167    event->SetHandled();
168  }
169
170 private:
171  DISALLOW_COPY_AND_ASSIGN(GestureHandlingView);
172};
173
174// Tests that context menus are shown for long press by the post-target handler
175// installed on the RootView only if the event is targetted at a view which can
176// show a context menu.
177TEST_F(RootViewTest, ContextMenuFromLongPress) {
178  Widget widget;
179  Widget::InitParams init_params =
180      CreateParams(Widget::InitParams::TYPE_POPUP);
181  init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
182  init_params.bounds = gfx::Rect(100, 100);
183  widget.Init(init_params);
184  internal::RootView* root_view =
185      static_cast<internal::RootView*>(widget.GetRootView());
186
187  // Create a view capable of showing the context menu with two children one of
188  // which handles all gesture events (e.g. a button).
189  TestContextMenuController controller;
190  View* parent_view = new View;
191  parent_view->set_context_menu_controller(&controller);
192  widget.SetContentsView(parent_view);
193
194  View* gesture_handling_child_view = new GestureHandlingView;
195  gesture_handling_child_view->SetBoundsRect(gfx::Rect(10, 10));
196  parent_view->AddChildView(gesture_handling_child_view);
197
198  View* other_child_view = new View;
199  other_child_view->SetBoundsRect(gfx::Rect(20, 0, 10, 10));
200  parent_view->AddChildView(other_child_view);
201
202  // |parent_view| should not show a context menu as a result of a long press on
203  // |gesture_handling_child_view|.
204  ui::GestureEvent long_press1(
205      5,
206      5,
207      0,
208      base::TimeDelta(),
209      ui::GestureEventDetails(ui::ET_GESTURE_LONG_PRESS));
210  ui::EventDispatchDetails details = root_view->OnEventFromSource(&long_press1);
211
212  ui::GestureEvent end1(
213      5, 5, 0, base::TimeDelta(), ui::GestureEventDetails(ui::ET_GESTURE_END));
214  details = root_view->OnEventFromSource(&end1);
215
216  EXPECT_FALSE(details.target_destroyed);
217  EXPECT_FALSE(details.dispatcher_destroyed);
218  EXPECT_EQ(0, controller.show_context_menu_calls());
219  controller.Reset();
220
221  // |parent_view| should show a context menu as a result of a long press on
222  // |other_child_view|.
223  ui::GestureEvent long_press2(
224      25,
225      5,
226      0,
227      base::TimeDelta(),
228      ui::GestureEventDetails(ui::ET_GESTURE_LONG_PRESS));
229  details = root_view->OnEventFromSource(&long_press2);
230
231  ui::GestureEvent end2(
232      25, 5, 0, base::TimeDelta(), ui::GestureEventDetails(ui::ET_GESTURE_END));
233  details = root_view->OnEventFromSource(&end2);
234
235  EXPECT_FALSE(details.target_destroyed);
236  EXPECT_FALSE(details.dispatcher_destroyed);
237  EXPECT_EQ(1, controller.show_context_menu_calls());
238  controller.Reset();
239
240  // |parent_view| should show a context menu as a result of a long press on
241  // itself.
242  ui::GestureEvent long_press3(
243      50,
244      50,
245      0,
246      base::TimeDelta(),
247      ui::GestureEventDetails(ui::ET_GESTURE_LONG_PRESS));
248  details = root_view->OnEventFromSource(&long_press3);
249
250  ui::GestureEvent end3(
251      25, 5, 0, base::TimeDelta(), ui::GestureEventDetails(ui::ET_GESTURE_END));
252  details = root_view->OnEventFromSource(&end3);
253
254  EXPECT_FALSE(details.target_destroyed);
255  EXPECT_FALSE(details.dispatcher_destroyed);
256  EXPECT_EQ(1, controller.show_context_menu_calls());
257}
258
259// Tests that context menus are not shown for disabled views on a long press.
260TEST_F(RootViewTest, ContextMenuFromLongPressOnDisabledView) {
261  Widget widget;
262  Widget::InitParams init_params =
263      CreateParams(Widget::InitParams::TYPE_POPUP);
264  init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
265  init_params.bounds = gfx::Rect(100, 100);
266  widget.Init(init_params);
267  internal::RootView* root_view =
268      static_cast<internal::RootView*>(widget.GetRootView());
269
270  // Create a view capable of showing the context menu with two children one of
271  // which handles all gesture events (e.g. a button). Also mark this view
272  // as disabled.
273  TestContextMenuController controller;
274  View* parent_view = new View;
275  parent_view->set_context_menu_controller(&controller);
276  parent_view->SetEnabled(false);
277  widget.SetContentsView(parent_view);
278
279  View* gesture_handling_child_view = new GestureHandlingView;
280  gesture_handling_child_view->SetBoundsRect(gfx::Rect(10, 10));
281  parent_view->AddChildView(gesture_handling_child_view);
282
283  View* other_child_view = new View;
284  other_child_view->SetBoundsRect(gfx::Rect(20, 0, 10, 10));
285  parent_view->AddChildView(other_child_view);
286
287  // |parent_view| should not show a context menu as a result of a long press on
288  // |gesture_handling_child_view|.
289  ui::GestureEvent long_press1(
290      5,
291      5,
292      0,
293      base::TimeDelta(),
294      ui::GestureEventDetails(ui::ET_GESTURE_LONG_PRESS));
295  ui::EventDispatchDetails details = root_view->OnEventFromSource(&long_press1);
296
297  ui::GestureEvent end1(
298      5, 5, 0, base::TimeDelta(), ui::GestureEventDetails(ui::ET_GESTURE_END));
299  details = root_view->OnEventFromSource(&end1);
300
301  EXPECT_FALSE(details.target_destroyed);
302  EXPECT_FALSE(details.dispatcher_destroyed);
303  EXPECT_EQ(0, controller.show_context_menu_calls());
304  controller.Reset();
305
306  // |parent_view| should not show a context menu as a result of a long press on
307  // |other_child_view|.
308  ui::GestureEvent long_press2(
309      25,
310      5,
311      0,
312      base::TimeDelta(),
313      ui::GestureEventDetails(ui::ET_GESTURE_LONG_PRESS));
314  details = root_view->OnEventFromSource(&long_press2);
315
316  ui::GestureEvent end2(
317      25, 5, 0, base::TimeDelta(), ui::GestureEventDetails(ui::ET_GESTURE_END));
318  details = root_view->OnEventFromSource(&end2);
319
320  EXPECT_FALSE(details.target_destroyed);
321  EXPECT_FALSE(details.dispatcher_destroyed);
322  EXPECT_EQ(0, controller.show_context_menu_calls());
323  controller.Reset();
324
325  // |parent_view| should not show a context menu as a result of a long press on
326  // itself.
327  ui::GestureEvent long_press3(
328      50,
329      50,
330      0,
331      base::TimeDelta(),
332      ui::GestureEventDetails(ui::ET_GESTURE_LONG_PRESS));
333  details = root_view->OnEventFromSource(&long_press3);
334
335  ui::GestureEvent end3(
336      25, 5, 0, base::TimeDelta(), ui::GestureEventDetails(ui::ET_GESTURE_END));
337  details = root_view->OnEventFromSource(&end3);
338
339  EXPECT_FALSE(details.target_destroyed);
340  EXPECT_FALSE(details.dispatcher_destroyed);
341  EXPECT_EQ(0, controller.show_context_menu_calls());
342}
343
344}  // namespace test
345}  // namespace views
346