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 #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:
     34   TestMenuItemView() : MenuItemView(NULL) {}
     35   virtual ~TestMenuItemView() {}
     36 
     37  private:
     38   DISALLOW_COPY_AND_ASSIGN(TestMenuItemView);
     39 };
     40 
     41 class 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 
     58 class 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 
     72 class 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 
    111 class 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 
    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.
    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