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/test/events_test_utils_x11.h"
23 #elif defined(USE_OZONE)
24 #include "ui/events/event.h"
31 class TestMenuItemView
: public MenuItemView
{
33 TestMenuItemView() : MenuItemView(NULL
) {}
34 virtual ~TestMenuItemView() {}
37 DISALLOW_COPY_AND_ASSIGN(TestMenuItemView
);
40 class TestPlatformEventSource
: public ui::PlatformEventSource
{
42 TestPlatformEventSource() {}
43 virtual ~TestPlatformEventSource() {}
45 uint32_t Dispatch(const ui::PlatformEvent
& event
) {
46 return DispatchEvent(event
);
50 DISALLOW_COPY_AND_ASSIGN(TestPlatformEventSource
);
53 class TestNullTargeter
: public ui::EventTargeter
{
56 virtual ~TestNullTargeter() {}
58 virtual ui::EventTarget
* FindTargetForEvent(ui::EventTarget
* root
,
59 ui::Event
* event
) OVERRIDE
{
64 DISALLOW_COPY_AND_ASSIGN(TestNullTargeter
);
67 class TestDispatcherClient
: public aura::client::DispatcherClient
{
69 TestDispatcherClient() : dispatcher_(NULL
) {}
70 virtual ~TestDispatcherClient() {}
72 base::MessagePumpDispatcher
* dispatcher() {
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
),
90 void RunNestedDispatcher(scoped_ptr
<base::RunLoop
> run_loop
,
91 base::MessagePumpDispatcher
* dispatcher
) {
92 base::AutoReset
<base::MessagePumpDispatcher
*> reset_dispatcher(&dispatcher_
,
94 base::MessageLoopForUI
* loop
= base::MessageLoopForUI::current();
95 base::MessageLoop::ScopedNestableTaskAllower
allow(loop
);
99 base::MessagePumpDispatcher
* dispatcher_
;
101 DISALLOW_COPY_AND_ASSIGN(TestDispatcherClient
);
106 class MenuControllerTest
: public ViewsTestBase
{
108 MenuControllerTest() : controller_(NULL
) {}
109 virtual ~MenuControllerTest() {
110 ResetMenuController();
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
;
122 base::MessageLoop::current()->PostTask(
124 base::Bind(&MenuControllerTest::Step3_DispatchEvents
,
125 base::Unretained(this),
128 EXPECT_TRUE(run_loop_
->running());
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(
139 base::Bind(&MenuControllerTest::Step3_DispatchEvents
,
140 base::Unretained(this),
142 run_loop_
.reset(new base::RunLoop());
146 void Step1_RunMenu() {
147 base::MessageLoop::current()->PostTask(
149 base::Bind(&MenuControllerTest::Step2_RunNestedLoop
,
150 base::Unretained(this)));
151 scoped_ptr
<Widget
> owner(CreateOwnerWidget());
152 RunMenu(owner
.get());
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
);
162 aura::client::SetDispatcherClient(
163 widget
->GetNativeWindow()->GetRootWindow(), &dispatcher_client_
);
164 return widget
.Pass();
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);
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
;
189 void DispatchEvent() {
192 memset(&xevent
, 0, sizeof(xevent
));
193 event_source_
.Dispatch(&xevent
);
194 #elif defined(OS_WIN)
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
);
202 #error Unsupported platform
207 void ResetMenuController() {
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;
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_
;
224 DISALLOW_COPY_AND_ASSIGN(MenuControllerTest
);
227 TEST_F(MenuControllerTest
, Basic
) {
228 base::MessageLoop::ScopedNestableTaskAllower
allow_nested(
229 base::MessageLoop::current());
230 message_loop()->PostTask(
232 base::Bind(&MenuControllerTest::Step1_RunMenu
, base::Unretained(this)));
235 #if defined(OS_LINUX) && defined(USE_X11)
236 // Tests that an event targeter which blocks events will be honored by the menu
238 TEST_F(MenuControllerTest
, EventTargeter
) {
240 // Verify that the menu handles the escape key under normal circumstances.
241 scoped_ptr
<Widget
> owner(CreateOwnerWidget());
242 message_loop()->PostTask(
244 base::Bind(&MenuControllerTest::DispatchEscapeAndExpect
,
245 base::Unretained(this),
246 MenuController::EXIT_OUTERMOST
));
247 RunMenu(owner
.get());
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(
259 base::Bind(&MenuControllerTest::DispatchEscapeAndExpect
,
260 base::Unretained(this),
261 MenuController::EXIT_NONE
));
262 RunMenu(owner
.get());