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