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:
TestMenuItemView()34 TestMenuItemView() : MenuItemView(NULL) {}
~TestMenuItemView()35 virtual ~TestMenuItemView() {}
36
37 private:
38 DISALLOW_COPY_AND_ASSIGN(TestMenuItemView);
39 };
40
41 class TestPlatformEventSource : public ui::PlatformEventSource {
42 public:
TestPlatformEventSource()43 TestPlatformEventSource() {
44 #if defined(USE_X11)
45 ui::DeviceDataManagerX11::CreateInstance();
46 #endif
47 }
~TestPlatformEventSource()48 virtual ~TestPlatformEventSource() {}
49
Dispatch(const ui::PlatformEvent & event)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:
TestNullTargeter()60 TestNullTargeter() {}
~TestNullTargeter()61 virtual ~TestNullTargeter() {}
62
FindTargetForEvent(ui::EventTarget * root,ui::Event * event)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:
TestDispatcherClient()74 TestDispatcherClient() : dispatcher_(NULL) {}
~TestDispatcherClient()75 virtual ~TestDispatcherClient() {}
76
dispatcher()77 base::MessagePumpDispatcher* dispatcher() {
78 return dispatcher_;
79 }
80
81 // aura::client::DispatcherClient:
PrepareNestedLoopClosures(base::MessagePumpDispatcher * dispatcher,base::Closure * run_closure,base::Closure * quit_closure)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:
RunNestedDispatcher(scoped_ptr<base::RunLoop> run_loop,base::MessagePumpDispatcher * dispatcher)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:
MenuControllerTest()113 MenuControllerTest() : controller_(NULL) {}
~MenuControllerTest()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.
Step3_DispatchEvents(int count)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.
Step2_RunNestedLoop()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
Step1_RunMenu()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
CreateOwnerWidget()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
RunMenu(views::Widget * owner)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)
DispatchEscapeAndExpect(MenuController::ExitType exit_type)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
DispatchEvent()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:
ResetMenuController()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
TEST_F(MenuControllerTest,Basic)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.
TEST_F(MenuControllerTest,EventTargeter)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