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
12 namespace views {
13 namespace test {
14
15 typedef ViewsTestBase RootViewTest;
16
17 class DeleteOnKeyEventView : public View {
18 public:
DeleteOnKeyEventView(bool * set_on_key)19 explicit DeleteOnKeyEventView(bool* set_on_key) : set_on_key_(set_on_key) {}
~DeleteOnKeyEventView()20 virtual ~DeleteOnKeyEventView() {}
21
OnKeyPressed(const ui::KeyEvent & event)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.
TEST_F(RootViewTest,DeleteViewDuringKeyEventDispatch)37 TEST_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.
69 class TestContextMenuController : public ContextMenuController {
70 public:
TestContextMenuController()71 TestContextMenuController()
72 : show_context_menu_calls_(0),
73 menu_source_view_(NULL),
74 menu_source_type_(ui::MENU_SOURCE_NONE) {
75 }
~TestContextMenuController()76 virtual ~TestContextMenuController() {}
77
show_context_menu_calls() const78 int show_context_menu_calls() const { return show_context_menu_calls_; }
menu_source_view() const79 View* menu_source_view() const { return menu_source_view_; }
menu_source_type() const80 ui::MenuSourceType menu_source_type() const { return menu_source_type_; }
81
Reset()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:
ShowContextMenuForView(View * source,const gfx::Point & point,ui::MenuSourceType source_type)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.
TEST_F(RootViewTest,ContextMenuFromKeyEvent)108 TEST_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.
158 class GestureHandlingView : public View {
159 public:
GestureHandlingView()160 GestureHandlingView() {
161 }
162
~GestureHandlingView()163 virtual ~GestureHandlingView() {
164 }
165
OnGestureEvent(ui::GestureEvent * event)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.
TEST_F(RootViewTest,ContextMenuFromLongPress)177 TEST_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.
TEST_F(RootViewTest,ContextMenuFromLongPressOnDisabledView)260 TEST_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