1 // Copyright 2014 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/controls/menu/menu_controller.h" 6 7 #include "base/run_loop.h" 8 #include "ui/aura/scoped_window_targeter.h" 9 #include "ui/aura/window.h" 10 #include "ui/events/event_targeter.h" 11 #include "ui/events/platform/platform_event_source.h" 12 #include "ui/views/controls/menu/menu_item_view.h" 13 #include "ui/views/test/views_test_base.h" 14 #include "ui/wm/public/dispatcher_client.h" 15 16 #if defined(OS_WIN) 17 #include "base/message_loop/message_pump_dispatcher.h" 18 #elif defined(USE_X11) 19 #include <X11/Xlib.h> 20 #undef Bool 21 #undef None 22 #include "ui/events/test/events_test_utils_x11.h" 23 #include "ui/events/x/device_data_manager_x11.h" 24 #elif defined(USE_OZONE) 25 #include "ui/events/event.h" 26 #endif 27 28 namespace views { 29 30 namespace { 31 32 class TestMenuItemView : public MenuItemView { 33 public: 34 TestMenuItemView() : MenuItemView(NULL) {} 35 virtual ~TestMenuItemView() {} 36 37 private: 38 DISALLOW_COPY_AND_ASSIGN(TestMenuItemView); 39 }; 40 41 class TestPlatformEventSource : public ui::PlatformEventSource { 42 public: 43 TestPlatformEventSource() { 44 #if defined(USE_X11) 45 ui::DeviceDataManagerX11::CreateInstance(); 46 #endif 47 } 48 virtual ~TestPlatformEventSource() {} 49 50 uint32_t Dispatch(const ui::PlatformEvent& event) { 51 return DispatchEvent(event); 52 } 53 54 private: 55 DISALLOW_COPY_AND_ASSIGN(TestPlatformEventSource); 56 }; 57 58 class TestNullTargeter : public ui::EventTargeter { 59 public: 60 TestNullTargeter() {} 61 virtual ~TestNullTargeter() {} 62 63 virtual ui::EventTarget* FindTargetForEvent(ui::EventTarget* root, 64 ui::Event* event) OVERRIDE { 65 return NULL; 66 } 67 68 private: 69 DISALLOW_COPY_AND_ASSIGN(TestNullTargeter); 70 }; 71 72 class TestDispatcherClient : public aura::client::DispatcherClient { 73 public: 74 TestDispatcherClient() : dispatcher_(NULL) {} 75 virtual ~TestDispatcherClient() {} 76 77 base::MessagePumpDispatcher* dispatcher() { 78 return dispatcher_; 79 } 80 81 // aura::client::DispatcherClient: 82 virtual void PrepareNestedLoopClosures( 83 base::MessagePumpDispatcher* dispatcher, 84 base::Closure* run_closure, 85 base::Closure* quit_closure) OVERRIDE { 86 scoped_ptr<base::RunLoop> run_loop(new base::RunLoop()); 87 *quit_closure = run_loop->QuitClosure(); 88 *run_closure = base::Bind(&TestDispatcherClient::RunNestedDispatcher, 89 base::Unretained(this), 90 base::Passed(&run_loop), 91 dispatcher); 92 } 93 94 private: 95 void RunNestedDispatcher(scoped_ptr<base::RunLoop> run_loop, 96 base::MessagePumpDispatcher* dispatcher) { 97 base::AutoReset<base::MessagePumpDispatcher*> reset_dispatcher(&dispatcher_, 98 dispatcher); 99 base::MessageLoopForUI* loop = base::MessageLoopForUI::current(); 100 base::MessageLoop::ScopedNestableTaskAllower allow(loop); 101 run_loop->Run(); 102 } 103 104 base::MessagePumpDispatcher* dispatcher_; 105 106 DISALLOW_COPY_AND_ASSIGN(TestDispatcherClient); 107 }; 108 109 } // namespace 110 111 class MenuControllerTest : public ViewsTestBase { 112 public: 113 MenuControllerTest() : controller_(NULL) {} 114 virtual ~MenuControllerTest() { 115 ResetMenuController(); 116 } 117 118 // Dispatches |count| number of items, each in a separate iteration of the 119 // message-loop, by posting a task. 120 void Step3_DispatchEvents(int count) { 121 base::MessageLoopForUI* loop = base::MessageLoopForUI::current(); 122 base::MessageLoop::ScopedNestableTaskAllower allow(loop); 123 controller_->exit_type_ = MenuController::EXIT_ALL; 124 125 DispatchEvent(); 126 if (count) { 127 base::MessageLoop::current()->PostTask( 128 FROM_HERE, 129 base::Bind(&MenuControllerTest::Step3_DispatchEvents, 130 base::Unretained(this), 131 count - 1)); 132 } else { 133 EXPECT_TRUE(run_loop_->running()); 134 run_loop_->Quit(); 135 } 136 } 137 138 // Runs a nested message-loop that does not involve the menu itself. 139 void Step2_RunNestedLoop() { 140 base::MessageLoopForUI* loop = base::MessageLoopForUI::current(); 141 base::MessageLoop::ScopedNestableTaskAllower allow(loop); 142 base::MessageLoop::current()->PostTask( 143 FROM_HERE, 144 base::Bind(&MenuControllerTest::Step3_DispatchEvents, 145 base::Unretained(this), 146 3)); 147 run_loop_.reset(new base::RunLoop()); 148 run_loop_->Run(); 149 } 150 151 void Step1_RunMenu() { 152 base::MessageLoop::current()->PostTask( 153 FROM_HERE, 154 base::Bind(&MenuControllerTest::Step2_RunNestedLoop, 155 base::Unretained(this))); 156 scoped_ptr<Widget> owner(CreateOwnerWidget()); 157 RunMenu(owner.get()); 158 } 159 160 scoped_ptr<Widget> CreateOwnerWidget() { 161 scoped_ptr<Widget> widget(new Widget); 162 Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_POPUP); 163 params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; 164 widget->Init(params); 165 widget->Show(); 166 167 aura::client::SetDispatcherClient( 168 widget->GetNativeWindow()->GetRootWindow(), &dispatcher_client_); 169 return widget.Pass(); 170 } 171 172 void RunMenu(views::Widget* owner) { 173 scoped_ptr<TestMenuItemView> menu_item(new TestMenuItemView); 174 ResetMenuController(); 175 controller_ = new MenuController(NULL, true, NULL); 176 controller_->owner_ = owner; 177 controller_->showing_ = true; 178 controller_->SetSelection(menu_item.get(), 179 MenuController::SELECTION_UPDATE_IMMEDIATELY); 180 controller_->RunMessageLoop(false); 181 } 182 183 #if defined(USE_X11) 184 void DispatchEscapeAndExpect(MenuController::ExitType exit_type) { 185 ui::ScopedXI2Event key_event; 186 key_event.InitKeyEvent(ui::ET_KEY_PRESSED, ui::VKEY_ESCAPE, 0); 187 event_source_.Dispatch(key_event); 188 EXPECT_EQ(exit_type, controller_->exit_type()); 189 controller_->exit_type_ = MenuController::EXIT_ALL; 190 DispatchEvent(); 191 } 192 #endif 193 194 void DispatchEvent() { 195 #if defined(USE_X11) 196 XEvent xevent; 197 memset(&xevent, 0, sizeof(xevent)); 198 event_source_.Dispatch(&xevent); 199 #elif defined(OS_WIN) 200 MSG msg; 201 memset(&msg, 0, sizeof(MSG)); 202 dispatcher_client_.dispatcher()->Dispatch(msg); 203 #elif defined(USE_OZONE) 204 ui::KeyEvent event(' ', ui::VKEY_SPACE, ui::EF_NONE); 205 event_source_.Dispatch(&event); 206 #else 207 #error Unsupported platform 208 #endif 209 } 210 211 private: 212 void ResetMenuController() { 213 if (controller_) { 214 // These properties are faked by RunMenu for the purposes of testing and 215 // need to be undone before we call the destructor. 216 controller_->owner_ = NULL; 217 controller_->showing_ = false; 218 delete controller_; 219 controller_ = NULL; 220 } 221 } 222 223 // A weak pointer to the MenuController owned by this class. 224 MenuController* controller_; 225 scoped_ptr<base::RunLoop> run_loop_; 226 TestPlatformEventSource event_source_; 227 TestDispatcherClient dispatcher_client_; 228 229 DISALLOW_COPY_AND_ASSIGN(MenuControllerTest); 230 }; 231 232 TEST_F(MenuControllerTest, Basic) { 233 base::MessageLoop::ScopedNestableTaskAllower allow_nested( 234 base::MessageLoop::current()); 235 message_loop()->PostTask( 236 FROM_HERE, 237 base::Bind(&MenuControllerTest::Step1_RunMenu, base::Unretained(this))); 238 } 239 240 #if defined(OS_LINUX) && defined(USE_X11) 241 // Tests that an event targeter which blocks events will be honored by the menu 242 // event dispatcher. 243 TEST_F(MenuControllerTest, EventTargeter) { 244 { 245 // Verify that the menu handles the escape key under normal circumstances. 246 scoped_ptr<Widget> owner(CreateOwnerWidget()); 247 message_loop()->PostTask( 248 FROM_HERE, 249 base::Bind(&MenuControllerTest::DispatchEscapeAndExpect, 250 base::Unretained(this), 251 MenuController::EXIT_OUTERMOST)); 252 RunMenu(owner.get()); 253 } 254 255 { 256 // With the NULL targeter instantiated and assigned we expect the menu to 257 // not handle the key event. 258 scoped_ptr<Widget> owner(CreateOwnerWidget()); 259 aura::ScopedWindowTargeter scoped_targeter( 260 owner->GetNativeWindow()->GetRootWindow(), 261 scoped_ptr<ui::EventTargeter>(new TestNullTargeter)); 262 message_loop()->PostTask( 263 FROM_HERE, 264 base::Bind(&MenuControllerTest::DispatchEscapeAndExpect, 265 base::Unretained(this), 266 MenuController::EXIT_NONE)); 267 RunMenu(owner.get()); 268 } 269 } 270 #endif 271 272 } // namespace views 273