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/events/event_targeter.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 13 namespace views { 14 namespace test { 15 16 typedef ViewsTestBase RootViewTest; 17 18 class DeleteOnKeyEventView : public View { 19 public: 20 explicit DeleteOnKeyEventView(bool* set_on_key) : set_on_key_(set_on_key) {} 21 virtual ~DeleteOnKeyEventView() {} 22 23 virtual bool OnKeyPressed(const ui::KeyEvent& event) OVERRIDE { 24 *set_on_key_ = true; 25 delete this; 26 return true; 27 } 28 29 private: 30 // Set to true in OnKeyPressed(). 31 bool* set_on_key_; 32 33 DISALLOW_COPY_AND_ASSIGN(DeleteOnKeyEventView); 34 }; 35 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) { 39 Widget widget; 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); 44 45 bool got_key_event = false; 46 47 View* content = new View; 48 widget.SetContentsView(content); 49 50 View* child = new DeleteOnKeyEventView(&got_key_event); 51 content->AddChildView(child); 52 53 // Give focus to |child| so that it will be the target of the key event. 54 child->SetFocusable(true); 55 child->RequestFocus(); 56 57 ui::EventTargeter* targeter = new ViewTargeter(); 58 internal::RootView* root_view = 59 static_cast<internal::RootView*>(widget.GetRootView()); 60 root_view->SetEventTargeter(make_scoped_ptr(targeter)); 61 62 ui::KeyEvent key_event(ui::ET_KEY_PRESSED, ui::VKEY_ESCAPE, 0, false); 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); 67 } 68 69 // Tracks whether a context menu is shown. 70 class TestContextMenuController : public ContextMenuController { 71 public: 72 TestContextMenuController() 73 : show_context_menu_calls_(0), 74 menu_source_view_(NULL), 75 menu_source_type_(ui::MENU_SOURCE_NONE) { 76 } 77 virtual ~TestContextMenuController() {} 78 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_; } 82 83 void Reset() { 84 show_context_menu_calls_ = 0; 85 menu_source_view_ = NULL; 86 menu_source_type_ = ui::MENU_SOURCE_NONE; 87 } 88 89 // ContextMenuController: 90 virtual void ShowContextMenuForView( 91 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; 97 } 98 99 private: 100 int show_context_menu_calls_; 101 View* menu_source_view_; 102 ui::MenuSourceType menu_source_type_; 103 104 DISALLOW_COPY_AND_ASSIGN(TestContextMenuController); 105 }; 106 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()); 117 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(); 124 125 // No context menu should be shown for a keypress of 'A'. 126 ui::KeyEvent nomenu_key_event(ui::ET_KEY_PRESSED, ui::VKEY_A, 0, true); 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(); 135 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, false); 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(); 146 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, 0, false); 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(); 156 } 157 158 // View which handles all gesture events. 159 class GestureHandlingView : public View { 160 public: 161 GestureHandlingView() { 162 } 163 164 virtual ~GestureHandlingView() { 165 } 166 167 virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE { 168 event->SetHandled(); 169 } 170 171 private: 172 DISALLOW_COPY_AND_ASSIGN(GestureHandlingView); 173 }; 174 175 // Tests that context menus are shown for long press by the post-target handler 176 // installed on the RootView only if the event is targetted at a view which can 177 // show a context menu. 178 TEST_F(RootViewTest, ContextMenuFromLongPress) { 179 Widget widget; 180 Widget::InitParams init_params = 181 CreateParams(Widget::InitParams::TYPE_POPUP); 182 init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; 183 init_params.bounds = gfx::Rect(100,100); 184 widget.Init(init_params); 185 internal::RootView* root_view = 186 static_cast<internal::RootView*>(widget.GetRootView()); 187 188 // Create a view capable of showing the context menu with two children one of 189 // which handles all gesture events (e.g. a button). 190 TestContextMenuController controller; 191 View* parent_view = new View; 192 parent_view->set_context_menu_controller(&controller); 193 widget.SetContentsView(parent_view); 194 195 View* gesture_handling_child_view = new GestureHandlingView; 196 gesture_handling_child_view->SetBoundsRect(gfx::Rect(10,10)); 197 parent_view->AddChildView(gesture_handling_child_view); 198 199 View* other_child_view = new View; 200 other_child_view->SetBoundsRect(gfx::Rect(20, 0, 10,10)); 201 parent_view->AddChildView(other_child_view); 202 203 // |parent_view| should not show a context menu as a result of a long press on 204 // |gesture_handling_child_view|. 205 ui::GestureEvent begin1(ui::ET_GESTURE_BEGIN, 5, 5, 0, base::TimeDelta(), 206 ui::GestureEventDetails(ui::ET_GESTURE_BEGIN, 0, 0), 1); 207 ui::EventDispatchDetails details = root_view->OnEventFromSource(&begin1); 208 209 ui::GestureEvent long_press1(ui::ET_GESTURE_LONG_PRESS, 5, 5, 0, 210 base::TimeDelta(), 211 ui::GestureEventDetails(ui::ET_GESTURE_LONG_PRESS, 0, 0), 1); 212 details = root_view->OnEventFromSource(&long_press1); 213 214 ui::GestureEvent end1(ui::ET_GESTURE_END, 5, 5, 0, base::TimeDelta(), 215 ui::GestureEventDetails(ui::ET_GESTURE_END, 0, 0), 1); 216 details = root_view->OnEventFromSource(&end1); 217 218 EXPECT_FALSE(details.target_destroyed); 219 EXPECT_FALSE(details.dispatcher_destroyed); 220 EXPECT_EQ(0, controller.show_context_menu_calls()); 221 222 // |parent_view| should show a context menu as a result of a long press on 223 // |other_child_view|. 224 ui::GestureEvent begin2(ui::ET_GESTURE_BEGIN, 25, 5, 0, base::TimeDelta(), 225 ui::GestureEventDetails(ui::ET_GESTURE_BEGIN, 0, 0), 1); 226 details = root_view->OnEventFromSource(&begin2); 227 228 ui::GestureEvent long_press2(ui::ET_GESTURE_LONG_PRESS, 25, 5, 0, 229 base::TimeDelta(), 230 ui::GestureEventDetails(ui::ET_GESTURE_LONG_PRESS, 0, 0), 1); 231 details = root_view->OnEventFromSource(&long_press2); 232 233 ui::GestureEvent end2(ui::ET_GESTURE_END, 25, 5, 0, base::TimeDelta(), 234 ui::GestureEventDetails(ui::ET_GESTURE_END, 0, 0), 1); 235 details = root_view->OnEventFromSource(&end2); 236 237 EXPECT_FALSE(details.target_destroyed); 238 EXPECT_FALSE(details.dispatcher_destroyed); 239 EXPECT_EQ(1, controller.show_context_menu_calls()); 240 } 241 242 } // namespace test 243 } // namespace views 244