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.
5 #include "ui/views/controls/menu/menu_controller.h"
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"
17 #include "base/message_loop/message_pump_dispatcher.h"
18 #elif defined(USE_X11)
22 #include "ui/events/devices/x11/device_data_manager_x11.h"
23 #include "ui/events/test/events_test_utils_x11.h"
24 #elif defined(USE_OZONE)
25 #include "ui/events/event.h"
32 class TestMenuItemView
: public MenuItemView
{
34 TestMenuItemView() : MenuItemView(NULL
) {}
35 ~TestMenuItemView() override
{}
38 DISALLOW_COPY_AND_ASSIGN(TestMenuItemView
);
41 class TestPlatformEventSource
: public ui::PlatformEventSource
{
43 TestPlatformEventSource() {
45 ui::DeviceDataManagerX11::CreateInstance();
48 ~TestPlatformEventSource() override
{}
50 uint32_t Dispatch(const ui::PlatformEvent
& event
) {
51 return DispatchEvent(event
);
55 DISALLOW_COPY_AND_ASSIGN(TestPlatformEventSource
);
58 class TestNullTargeter
: public ui::EventTargeter
{
61 ~TestNullTargeter() override
{}
63 ui::EventTarget
* FindTargetForEvent(ui::EventTarget
* root
,
64 ui::Event
* event
) override
{
69 DISALLOW_COPY_AND_ASSIGN(TestNullTargeter
);
72 class TestDispatcherClient
: public aura::client::DispatcherClient
{
74 TestDispatcherClient() : dispatcher_(NULL
) {}
75 ~TestDispatcherClient() override
{}
77 base::MessagePumpDispatcher
* dispatcher() {
81 // aura::client::DispatcherClient:
82 void PrepareNestedLoopClosures(base::MessagePumpDispatcher
* dispatcher
,
83 base::Closure
* run_closure
,
84 base::Closure
* quit_closure
) override
{
85 scoped_ptr
<base::RunLoop
> run_loop(new base::RunLoop());
86 *quit_closure
= run_loop
->QuitClosure();
87 *run_closure
= base::Bind(&TestDispatcherClient::RunNestedDispatcher
,
88 base::Unretained(this),
89 base::Passed(&run_loop
),
94 void RunNestedDispatcher(scoped_ptr
<base::RunLoop
> run_loop
,
95 base::MessagePumpDispatcher
* dispatcher
) {
96 base::AutoReset
<base::MessagePumpDispatcher
*> reset_dispatcher(&dispatcher_
,
98 base::MessageLoopForUI
* loop
= base::MessageLoopForUI::current();
99 base::MessageLoop::ScopedNestableTaskAllower
allow(loop
);
103 base::MessagePumpDispatcher
* dispatcher_
;
105 DISALLOW_COPY_AND_ASSIGN(TestDispatcherClient
);
110 class MenuControllerTest
: public ViewsTestBase
{
112 MenuControllerTest() : controller_(NULL
) {}
113 ~MenuControllerTest() override
{ ResetMenuController(); }
115 // Dispatches |count| number of items, each in a separate iteration of the
116 // message-loop, by posting a task.
117 void Step3_DispatchEvents(int count
) {
118 base::MessageLoopForUI
* loop
= base::MessageLoopForUI::current();
119 base::MessageLoop::ScopedNestableTaskAllower
allow(loop
);
120 controller_
->exit_type_
= MenuController::EXIT_ALL
;
124 base::MessageLoop::current()->PostTask(
126 base::Bind(&MenuControllerTest::Step3_DispatchEvents
,
127 base::Unretained(this),
130 EXPECT_TRUE(run_loop_
->running());
135 // Runs a nested message-loop that does not involve the menu itself.
136 void Step2_RunNestedLoop() {
137 base::MessageLoopForUI
* loop
= base::MessageLoopForUI::current();
138 base::MessageLoop::ScopedNestableTaskAllower
allow(loop
);
139 base::MessageLoop::current()->PostTask(
141 base::Bind(&MenuControllerTest::Step3_DispatchEvents
,
142 base::Unretained(this),
144 run_loop_
.reset(new base::RunLoop());
148 void Step1_RunMenu() {
149 base::MessageLoop::current()->PostTask(
151 base::Bind(&MenuControllerTest::Step2_RunNestedLoop
,
152 base::Unretained(this)));
153 scoped_ptr
<Widget
> owner(CreateOwnerWidget());
154 RunMenu(owner
.get());
157 scoped_ptr
<Widget
> CreateOwnerWidget() {
158 scoped_ptr
<Widget
> widget(new Widget
);
159 Widget::InitParams params
= CreateParams(Widget::InitParams::TYPE_POPUP
);
160 params
.ownership
= Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
161 widget
->Init(params
);
164 aura::client::SetDispatcherClient(
165 widget
->GetNativeWindow()->GetRootWindow(), &dispatcher_client_
);
166 return widget
.Pass();
169 void RunMenu(views::Widget
* owner
) {
170 scoped_ptr
<TestMenuItemView
> menu_item(new TestMenuItemView
);
171 ResetMenuController();
172 controller_
= new MenuController(NULL
, true, NULL
);
173 controller_
->owner_
= owner
;
174 controller_
->showing_
= true;
175 controller_
->SetSelection(menu_item
.get(),
176 MenuController::SELECTION_UPDATE_IMMEDIATELY
);
177 controller_
->RunMessageLoop(false);
181 void DispatchEscapeAndExpect(MenuController::ExitType exit_type
) {
182 ui::ScopedXI2Event key_event
;
183 key_event
.InitKeyEvent(ui::ET_KEY_PRESSED
, ui::VKEY_ESCAPE
, 0);
184 event_source_
.Dispatch(key_event
);
185 EXPECT_EQ(exit_type
, controller_
->exit_type());
186 controller_
->exit_type_
= MenuController::EXIT_ALL
;
191 void DispatchEvent() {
194 memset(&xevent
, 0, sizeof(xevent
));
195 event_source_
.Dispatch(&xevent
);
196 #elif defined(OS_WIN)
198 memset(&msg
, 0, sizeof(MSG
));
199 dispatcher_client_
.dispatcher()->Dispatch(msg
);
200 #elif defined(USE_OZONE)
201 ui::KeyEvent
event(' ', ui::VKEY_SPACE
, ui::EF_NONE
);
202 event_source_
.Dispatch(&event
);
204 #error Unsupported platform
209 void ResetMenuController() {
211 // These properties are faked by RunMenu for the purposes of testing and
212 // need to be undone before we call the destructor.
213 controller_
->owner_
= NULL
;
214 controller_
->showing_
= false;
220 // A weak pointer to the MenuController owned by this class.
221 MenuController
* controller_
;
222 scoped_ptr
<base::RunLoop
> run_loop_
;
223 TestPlatformEventSource event_source_
;
224 TestDispatcherClient dispatcher_client_
;
226 DISALLOW_COPY_AND_ASSIGN(MenuControllerTest
);
229 TEST_F(MenuControllerTest
, Basic
) {
230 base::MessageLoop::ScopedNestableTaskAllower
allow_nested(
231 base::MessageLoop::current());
232 message_loop()->PostTask(
234 base::Bind(&MenuControllerTest::Step1_RunMenu
, base::Unretained(this)));
237 #if defined(OS_LINUX) && defined(USE_X11)
238 // Tests that an event targeter which blocks events will be honored by the menu
240 TEST_F(MenuControllerTest
, EventTargeter
) {
242 // Verify that the menu handles the escape key under normal circumstances.
243 scoped_ptr
<Widget
> owner(CreateOwnerWidget());
244 message_loop()->PostTask(
246 base::Bind(&MenuControllerTest::DispatchEscapeAndExpect
,
247 base::Unretained(this),
248 MenuController::EXIT_OUTERMOST
));
249 RunMenu(owner
.get());
253 // With the NULL targeter instantiated and assigned we expect the menu to
254 // not handle the key event.
255 scoped_ptr
<Widget
> owner(CreateOwnerWidget());
256 aura::ScopedWindowTargeter
scoped_targeter(
257 owner
->GetNativeWindow()->GetRootWindow(),
258 scoped_ptr
<ui::EventTargeter
>(new TestNullTargeter
));
259 message_loop()->PostTask(
261 base::Bind(&MenuControllerTest::DispatchEscapeAndExpect
,
262 base::Unretained(this),
263 MenuController::EXIT_NONE
));
264 RunMenu(owner
.get());