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
28namespace views {
29
30namespace {
31
32class TestMenuItemView : public MenuItemView {
33 public:
34  TestMenuItemView() : MenuItemView(NULL) {}
35  virtual ~TestMenuItemView() {}
36
37 private:
38  DISALLOW_COPY_AND_ASSIGN(TestMenuItemView);
39};
40
41class 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
58class 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
72class 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
111class 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
232TEST_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.
243TEST_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