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