Home | History | Annotate | Download | only in menu
      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 #elif defined(USE_OZONE)
     24 #include "ui/events/event.h"
     25 #endif
     26 
     27 namespace views {
     28 
     29 namespace {
     30 
     31 class TestMenuItemView : public MenuItemView {
     32  public:
     33   TestMenuItemView() : MenuItemView(NULL) {}
     34   virtual ~TestMenuItemView() {}
     35 
     36  private:
     37   DISALLOW_COPY_AND_ASSIGN(TestMenuItemView);
     38 };
     39 
     40 class TestPlatformEventSource : public ui::PlatformEventSource {
     41  public:
     42   TestPlatformEventSource() {}
     43   virtual ~TestPlatformEventSource() {}
     44 
     45   uint32_t Dispatch(const ui::PlatformEvent& event) {
     46     return DispatchEvent(event);
     47   }
     48 
     49  private:
     50   DISALLOW_COPY_AND_ASSIGN(TestPlatformEventSource);
     51 };
     52 
     53 class TestNullTargeter : public ui::EventTargeter {
     54  public:
     55   TestNullTargeter() {}
     56   virtual ~TestNullTargeter() {}
     57 
     58   virtual ui::EventTarget* FindTargetForEvent(ui::EventTarget* root,
     59                                               ui::Event* event) OVERRIDE {
     60     return NULL;
     61   }
     62 
     63  private:
     64   DISALLOW_COPY_AND_ASSIGN(TestNullTargeter);
     65 };
     66 
     67 class TestDispatcherClient : public aura::client::DispatcherClient {
     68  public:
     69   TestDispatcherClient() : dispatcher_(NULL) {}
     70   virtual ~TestDispatcherClient() {}
     71 
     72   base::MessagePumpDispatcher* dispatcher() {
     73     return dispatcher_;
     74   }
     75 
     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),
     86                               dispatcher);
     87   }
     88 
     89  private:
     90   void RunNestedDispatcher(scoped_ptr<base::RunLoop> run_loop,
     91                            base::MessagePumpDispatcher* dispatcher) {
     92     base::AutoReset<base::MessagePumpDispatcher*> reset_dispatcher(&dispatcher_,
     93                                                                    dispatcher);
     94     base::MessageLoopForUI* loop = base::MessageLoopForUI::current();
     95     base::MessageLoop::ScopedNestableTaskAllower allow(loop);
     96     run_loop->Run();
     97   }
     98 
     99   base::MessagePumpDispatcher* dispatcher_;
    100 
    101   DISALLOW_COPY_AND_ASSIGN(TestDispatcherClient);
    102 };
    103 
    104 }  // namespace
    105 
    106 class MenuControllerTest : public ViewsTestBase {
    107  public:
    108   MenuControllerTest() : controller_(NULL) {}
    109   virtual ~MenuControllerTest() {
    110     ResetMenuController();
    111   }
    112 
    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;
    119 
    120     DispatchEvent();
    121     if (count) {
    122       base::MessageLoop::current()->PostTask(
    123           FROM_HERE,
    124           base::Bind(&MenuControllerTest::Step3_DispatchEvents,
    125                      base::Unretained(this),
    126                      count - 1));
    127     } else {
    128       EXPECT_TRUE(run_loop_->running());
    129       run_loop_->Quit();
    130     }
    131   }
    132 
    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(
    138         FROM_HERE,
    139         base::Bind(&MenuControllerTest::Step3_DispatchEvents,
    140                    base::Unretained(this),
    141                    3));
    142     run_loop_.reset(new base::RunLoop());
    143     run_loop_->Run();
    144   }
    145 
    146   void Step1_RunMenu() {
    147     base::MessageLoop::current()->PostTask(
    148         FROM_HERE,
    149         base::Bind(&MenuControllerTest::Step2_RunNestedLoop,
    150                    base::Unretained(this)));
    151     scoped_ptr<Widget> owner(CreateOwnerWidget());
    152     RunMenu(owner.get());
    153   }
    154 
    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);
    160     widget->Show();
    161 
    162     aura::client::SetDispatcherClient(
    163         widget->GetNativeWindow()->GetRootWindow(), &dispatcher_client_);
    164     return widget.Pass();
    165   }
    166 
    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);
    176   }
    177 
    178 #if defined(USE_X11)
    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;
    185     DispatchEvent();
    186   }
    187 #endif
    188 
    189   void DispatchEvent() {
    190 #if defined(USE_X11)
    191     XEvent xevent;
    192     memset(&xevent, 0, sizeof(xevent));
    193     event_source_.Dispatch(&xevent);
    194 #elif defined(OS_WIN)
    195     MSG msg;
    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);
    201 #else
    202 #error Unsupported platform
    203 #endif
    204   }
    205 
    206  private:
    207   void ResetMenuController() {
    208     if (controller_) {
    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;
    213       delete controller_;
    214       controller_ = NULL;
    215     }
    216   }
    217 
    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_;
    223 
    224   DISALLOW_COPY_AND_ASSIGN(MenuControllerTest);
    225 };
    226 
    227 TEST_F(MenuControllerTest, Basic) {
    228   base::MessageLoop::ScopedNestableTaskAllower allow_nested(
    229       base::MessageLoop::current());
    230   message_loop()->PostTask(
    231       FROM_HERE,
    232       base::Bind(&MenuControllerTest::Step1_RunMenu, base::Unretained(this)));
    233 }
    234 
    235 #if defined(OS_LINUX) && defined(USE_X11)
    236 // Tests that an event targeter which blocks events will be honored by the menu
    237 // event dispatcher.
    238 TEST_F(MenuControllerTest, EventTargeter) {
    239   {
    240     // Verify that the menu handles the escape key under normal circumstances.
    241     scoped_ptr<Widget> owner(CreateOwnerWidget());
    242     message_loop()->PostTask(
    243         FROM_HERE,
    244         base::Bind(&MenuControllerTest::DispatchEscapeAndExpect,
    245                    base::Unretained(this),
    246                    MenuController::EXIT_OUTERMOST));
    247     RunMenu(owner.get());
    248   }
    249 
    250   {
    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(
    258         FROM_HERE,
    259         base::Bind(&MenuControllerTest::DispatchEscapeAndExpect,
    260                    base::Unretained(this),
    261                    MenuController::EXIT_NONE));
    262     RunMenu(owner.get());
    263   }
    264 }
    265 #endif
    266 
    267 }  // namespace views
    268