• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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