| OLD | NEW |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "ui/views/controls/menu/menu_controller.h" | 5 #include "ui/views/controls/menu/menu_controller.h" |
| 6 | 6 |
| 7 #include "base/run_loop.h" | |
| 8 #include "base/strings/utf_string_conversions.h" | 7 #include "base/strings/utf_string_conversions.h" |
| 8 #include "ui/aura/scoped_window_targeter.h" |
| 9 #include "ui/aura/window.h" |
| 9 #include "ui/events/event_handler.h" | 10 #include "ui/events/event_handler.h" |
| 10 #include "ui/events/null_event_targeter.h" | 11 #include "ui/events/null_event_targeter.h" |
| 11 #include "ui/events/platform/platform_event_source.h" | 12 #include "ui/events/test/event_generator.h" |
| 12 #include "ui/events/test/platform_event_source_test_api.h" | |
| 13 #include "ui/views/controls/menu/menu_item_view.h" | 13 #include "ui/views/controls/menu/menu_item_view.h" |
| 14 #include "ui/views/controls/menu/submenu_view.h" | 14 #include "ui/views/controls/menu/submenu_view.h" |
| 15 #include "ui/views/test/views_test_base.h" | 15 #include "ui/views/test/views_test_base.h" |
| 16 | 16 |
| 17 #if defined(USE_AURA) | 17 #if defined(USE_AURA) |
| 18 #include "ui/aura/env.h" | |
| 19 #include "ui/aura/scoped_window_targeter.h" | 18 #include "ui/aura/scoped_window_targeter.h" |
| 20 #include "ui/aura/window.h" | 19 #include "ui/aura/window.h" |
| 21 #include "ui/wm/public/dispatcher_client.h" | |
| 22 #endif | 20 #endif |
| 23 | 21 |
| 24 #if defined(OS_WIN) | 22 #if defined(USE_X11) |
| 25 #include "base/message_loop/message_pump_dispatcher.h" | |
| 26 #elif defined(USE_X11) | |
| 27 #include <X11/Xlib.h> | 23 #include <X11/Xlib.h> |
| 28 #undef Bool | 24 #undef Bool |
| 29 #undef None | 25 #undef None |
| 30 #include "ui/events/test/events_test_utils_x11.h" | 26 #include "ui/events/test/events_test_utils_x11.h" |
| 31 #elif defined(USE_OZONE) | |
| 32 #include "ui/events/event.h" | |
| 33 #elif defined(OS_MACOSX) | |
| 34 #include "ui/events/test/event_generator.h" | |
| 35 #endif | 27 #endif |
| 36 | 28 |
| 37 namespace views { | 29 namespace views { |
| 38 namespace test { | 30 namespace test { |
| 39 | 31 |
| 40 namespace { | 32 namespace { |
| 41 | 33 |
| 42 class TestMenuItemView : public MenuItemView { | |
| 43 public: | |
| 44 TestMenuItemView() : MenuItemView(nullptr) {} | |
| 45 ~TestMenuItemView() override {} | |
| 46 | |
| 47 private: | |
| 48 DISALLOW_COPY_AND_ASSIGN(TestMenuItemView); | |
| 49 }; | |
| 50 | |
| 51 class SubmenuViewShown : public SubmenuView { | 34 class SubmenuViewShown : public SubmenuView { |
| 52 public: | 35 public: |
| 53 SubmenuViewShown(MenuItemView* parent) : SubmenuView(parent) {} | 36 SubmenuViewShown(MenuItemView* parent) : SubmenuView(parent) {} |
| 54 ~SubmenuViewShown() override {} | 37 ~SubmenuViewShown() override {} |
| 55 bool IsShowing() override { return true; } | 38 bool IsShowing() override { return true; } |
| 56 | 39 |
| 57 private: | 40 private: |
| 58 DISALLOW_COPY_AND_ASSIGN(SubmenuViewShown); | 41 DISALLOW_COPY_AND_ASSIGN(SubmenuViewShown); |
| 59 }; | 42 }; |
| 60 | 43 |
| 61 #if defined(USE_AURA) | 44 class TestEventHandler : public ui::EventHandler { |
| 62 class TestDispatcherClient : public aura::client::DispatcherClient { | |
| 63 public: | 45 public: |
| 64 TestDispatcherClient() : dispatcher_(nullptr) {} | 46 TestEventHandler() : outstanding_touches_(0) {} |
| 65 ~TestDispatcherClient() override {} | |
| 66 | 47 |
| 67 base::MessagePumpDispatcher* dispatcher() { | 48 void OnTouchEvent(ui::TouchEvent* event) override { |
| 68 return dispatcher_; | 49 switch(event->type()) { |
| 50 case ui::ET_TOUCH_PRESSED: |
| 51 outstanding_touches_++; |
| 52 break; |
| 53 case ui::ET_TOUCH_RELEASED: |
| 54 case ui::ET_TOUCH_CANCELLED: |
| 55 outstanding_touches_--; |
| 56 break; |
| 57 default: |
| 58 break; |
| 59 } |
| 69 } | 60 } |
| 70 | 61 |
| 71 // aura::client::DispatcherClient: | 62 int outstanding_touches() const { return outstanding_touches_; } |
| 72 void PrepareNestedLoopClosures(base::MessagePumpDispatcher* dispatcher, | |
| 73 base::Closure* run_closure, | |
| 74 base::Closure* quit_closure) override { | |
| 75 scoped_ptr<base::RunLoop> run_loop(new base::RunLoop()); | |
| 76 *quit_closure = run_loop->QuitClosure(); | |
| 77 *run_closure = base::Bind(&TestDispatcherClient::RunNestedDispatcher, | |
| 78 base::Unretained(this), | |
| 79 base::Passed(&run_loop), | |
| 80 dispatcher); | |
| 81 } | |
| 82 | 63 |
| 83 private: | 64 private: |
| 84 void RunNestedDispatcher(scoped_ptr<base::RunLoop> run_loop, | 65 int outstanding_touches_; |
| 85 base::MessagePumpDispatcher* dispatcher) { | 66 DISALLOW_COPY_AND_ASSIGN(TestEventHandler); |
| 86 base::AutoReset<base::MessagePumpDispatcher*> reset_dispatcher(&dispatcher_, | |
| 87 dispatcher); | |
| 88 base::MessageLoopForUI* loop = base::MessageLoopForUI::current(); | |
| 89 base::MessageLoop::ScopedNestableTaskAllower allow(loop); | |
| 90 run_loop->Run(); | |
| 91 } | |
| 92 | |
| 93 base::MessagePumpDispatcher* dispatcher_; | |
| 94 | |
| 95 DISALLOW_COPY_AND_ASSIGN(TestDispatcherClient); | |
| 96 }; | 67 }; |
| 97 #endif // USE_AURA | |
| 98 | 68 |
| 99 } // namespace | 69 } // namespace |
| 100 | 70 |
| 101 class TestMenuItemViewShown : public MenuItemView { | 71 class TestMenuItemViewShown : public MenuItemView { |
| 102 public: | 72 public: |
| 103 TestMenuItemViewShown() : MenuItemView(nullptr) { | 73 TestMenuItemViewShown() : MenuItemView(nullptr) { |
| 104 submenu_ = new SubmenuViewShown(this); | 74 submenu_ = new SubmenuViewShown(this); |
| 105 } | 75 } |
| 106 ~TestMenuItemViewShown() override {} | 76 ~TestMenuItemViewShown() override {} |
| 107 | 77 |
| 108 private: | 78 private: |
| 109 DISALLOW_COPY_AND_ASSIGN(TestMenuItemViewShown); | 79 DISALLOW_COPY_AND_ASSIGN(TestMenuItemViewShown); |
| 110 }; | 80 }; |
| 111 | 81 |
| 112 class MenuControllerTest : public ViewsTestBase { | 82 class MenuControllerTest : public ViewsTestBase { |
| 113 public: | 83 public: |
| 114 MenuControllerTest() | 84 MenuControllerTest() : menu_controller_(nullptr) { |
| 115 : controller_(nullptr), | 85 } |
| 116 event_source_(ui::PlatformEventSource::GetInstance()) {} | 86 |
| 117 ~MenuControllerTest() override { ResetMenuController(); } | 87 ~MenuControllerTest() override {} |
| 118 | 88 |
| 119 // Dispatches |count| number of items, each in a separate iteration of the | 89 // ViewsTestBase: |
| 120 // message-loop, by posting a task. | 90 void SetUp() override { |
| 121 void Step3_DispatchEvents(int count) { | 91 ViewsTestBase::SetUp(); |
| 122 base::MessageLoopForUI* loop = base::MessageLoopForUI::current(); | 92 Init(); |
| 123 base::MessageLoop::ScopedNestableTaskAllower allow(loop); | 93 } |
| 124 controller_->exit_type_ = MenuController::EXIT_ALL; | 94 |
| 125 | 95 void TearDown() override { |
| 126 DispatchEvent(); | 96 owner_->CloseNow(); |
| 127 if (count) { | 97 |
| 128 base::MessageLoop::current()->PostTask( | 98 menu_controller_->showing_ = false; |
| 129 FROM_HERE, | 99 menu_controller_->owner_ = nullptr; |
| 130 base::Bind(&MenuControllerTest::Step3_DispatchEvents, | 100 delete menu_controller_; |
| 131 base::Unretained(this), | 101 menu_controller_ = nullptr; |
| 132 count - 1)); | 102 |
| 133 } else { | 103 ViewsTestBase::TearDown(); |
| 134 EXPECT_TRUE(run_loop_->running()); | 104 } |
| 135 run_loop_->Quit(); | 105 |
| 106 void ReleaseTouchId(int id) { |
| 107 event_generator_->ReleaseTouchId(id); |
| 108 } |
| 109 |
| 110 void PressKey(ui::KeyboardCode key_code) { |
| 111 event_generator_->PressKey(key_code, 0); |
| 112 } |
| 113 |
| 114 #if defined(OS_LINUX) && defined(USE_X11) |
| 115 void TestEventTargeter() { |
| 116 { |
| 117 // With the |ui::NullEventTargeter| instantiated and assigned we expect |
| 118 // the menu to not handle the key event. |
| 119 aura::ScopedWindowTargeter scoped_targeter( |
| 120 owner()->GetNativeWindow()->GetRootWindow(), |
| 121 scoped_ptr<ui::EventTargeter>(new ui::NullEventTargeter)); |
| 122 event_generator_->PressKey(ui::VKEY_ESCAPE, 0); |
| 123 EXPECT_EQ(MenuController::EXIT_NONE, menu_exit_type()); |
| 136 } | 124 } |
| 137 } | 125 // Now that the targeter has been destroyed, expect to exit the menu |
| 138 | 126 // normally when hitting escape. |
| 139 // Runs a nested message-loop that does not involve the menu itself. | 127 event_generator_->PressKey(ui::VKEY_ESCAPE, 0); |
| 140 void Step2_RunNestedLoop() { | 128 EXPECT_EQ(MenuController::EXIT_OUTERMOST, menu_exit_type()); |
| 141 base::MessageLoopForUI* loop = base::MessageLoopForUI::current(); | 129 } |
| 142 base::MessageLoop::ScopedNestableTaskAllower allow(loop); | 130 #endif // defined(OS_LINUX) && defined(USE_X11) |
| 143 base::MessageLoop::current()->PostTask( | 131 |
| 144 FROM_HERE, | 132 protected: |
| 145 base::Bind(&MenuControllerTest::Step3_DispatchEvents, | 133 void SetPendingStateItem(MenuItemView* item) { |
| 146 base::Unretained(this), | 134 menu_controller_->pending_state_.item = item; |
| 147 3)); | 135 menu_controller_->pending_state_.submenu_open = true; |
| 148 run_loop_.reset(new base::RunLoop()); | 136 } |
| 149 run_loop_->Run(); | 137 |
| 150 } | 138 void ResetSelection() { |
| 151 | 139 menu_controller_->SetSelection( |
| 152 void Step1_RunMenu() { | 140 nullptr, |
| 153 base::MessageLoop::current()->PostTask( | 141 MenuController::SELECTION_EXIT | |
| 154 FROM_HERE, | 142 MenuController::SELECTION_UPDATE_IMMEDIATELY); |
| 155 base::Bind(&MenuControllerTest::Step2_RunNestedLoop, | 143 } |
| 156 base::Unretained(this))); | 144 |
| 157 scoped_ptr<Widget> owner(CreateOwnerWidget()); | 145 void IncrementSelection() { |
| 158 RunMenu(owner.get()); | 146 menu_controller_->IncrementSelection( |
| 159 } | 147 MenuController::INCREMENT_SELECTION_DOWN); |
| 160 | 148 } |
| 161 scoped_ptr<Widget> CreateOwnerWidget() { | 149 |
| 162 scoped_ptr<Widget> widget(new Widget); | 150 void DecrementSelection() { |
| 151 menu_controller_->IncrementSelection( |
| 152 MenuController::INCREMENT_SELECTION_UP); |
| 153 } |
| 154 |
| 155 MenuItemView* FindInitialSelectableMenuItemDown(MenuItemView* parent) { |
| 156 return menu_controller_->FindInitialSelectableMenuItem( |
| 157 parent, MenuController::INCREMENT_SELECTION_DOWN); |
| 158 } |
| 159 |
| 160 MenuItemView* FindInitialSelectableMenuItemUp(MenuItemView* parent) { |
| 161 return menu_controller_->FindInitialSelectableMenuItem( |
| 162 parent, MenuController::INCREMENT_SELECTION_UP); |
| 163 } |
| 164 |
| 165 MenuItemView* FindNextSelectableMenuItem(MenuItemView* parent, |
| 166 int index) { |
| 167 |
| 168 return menu_controller_->FindNextSelectableMenuItem( |
| 169 parent, index, MenuController::INCREMENT_SELECTION_DOWN); |
| 170 } |
| 171 |
| 172 MenuItemView* FindPreviousSelectableMenuItem(MenuItemView* parent, |
| 173 int index) { |
| 174 return menu_controller_->FindNextSelectableMenuItem( |
| 175 parent, index, MenuController::INCREMENT_SELECTION_UP); |
| 176 } |
| 177 |
| 178 void RunMenu() { |
| 179 menu_controller_->RunMessageLoop(false); |
| 180 } |
| 181 |
| 182 Widget* owner() { return owner_.get(); } |
| 183 ui::test::EventGenerator* event_generator() { return event_generator_.get(); } |
| 184 TestMenuItemViewShown* menu_item() { return menu_item_.get(); } |
| 185 MenuController* menu_controller() { return menu_controller_; } |
| 186 const MenuItemView* pending_state_item() const { |
| 187 return menu_controller_->pending_state_.item; |
| 188 } |
| 189 MenuController::ExitType menu_exit_type() const { |
| 190 return menu_controller_->exit_type_; |
| 191 } |
| 192 |
| 193 private: |
| 194 void Init() { |
| 195 owner_.reset(new Widget); |
| 163 Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_POPUP); | 196 Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_POPUP); |
| 164 params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; | 197 params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; |
| 165 widget->Init(params); | 198 owner_->Init(params); |
| 166 widget->Show(); | 199 event_generator_.reset( |
| 167 | 200 new ui::test::EventGenerator(GetContext(), owner_->GetNativeWindow())); |
| 168 #if defined(USE_AURA) | 201 owner_->Show(); |
| 169 aura::client::SetDispatcherClient( | 202 |
| 170 widget->GetNativeWindow()->GetRootWindow(), &dispatcher_client_); | 203 SetupMenuItem(); |
| 171 #endif | 204 |
| 172 return widget.Pass(); | 205 SetupMenuController(); |
| 173 } | 206 } |
| 174 | 207 |
| 175 const MenuItemView* pending_state_item() const { | 208 void SetupMenuItem() { |
| 176 return controller_->pending_state_.item; | 209 menu_item_.reset(new TestMenuItemViewShown); |
| 177 } | 210 menu_item_->AppendMenuItemWithLabel(1, base::ASCIIToUTF16("One")); |
| 178 | 211 menu_item_->AppendMenuItemWithLabel(2, base::ASCIIToUTF16("Two")); |
| 179 void SetPendingStateItem(MenuItemView* item) { | 212 menu_item_->AppendMenuItemWithLabel(3, base::ASCIIToUTF16("Three")); |
| 180 controller_->pending_state_.item = item; | 213 menu_item_->AppendMenuItemWithLabel(4, base::ASCIIToUTF16("Four")); |
| 181 controller_->pending_state_.submenu_open = true; | 214 } |
| 182 } | 215 |
| 183 | 216 void SetupMenuController() { |
| 184 void ResetSelection() { | 217 menu_controller_= new MenuController(nullptr, true, nullptr); |
| 185 controller_->SetSelection(nullptr, | 218 menu_controller_->owner_ = owner_.get(); |
| 186 MenuController::SELECTION_EXIT | | 219 menu_controller_->showing_ = true; |
| 187 MenuController::SELECTION_UPDATE_IMMEDIATELY); | 220 menu_controller_->SetSelection( |
| 188 } | 221 menu_item_.get(), MenuController::SELECTION_UPDATE_IMMEDIATELY); |
| 189 | 222 } |
| 190 void IncrementSelection() { | 223 |
| 191 controller_->IncrementSelection(MenuController::INCREMENT_SELECTION_DOWN); | 224 scoped_ptr<Widget> owner_; |
| 192 } | 225 scoped_ptr<ui::test::EventGenerator> event_generator_; |
| 193 | 226 scoped_ptr<TestMenuItemViewShown> menu_item_; |
| 194 void DecrementSelection() { | 227 MenuController* menu_controller_; |
| 195 controller_->IncrementSelection(MenuController::INCREMENT_SELECTION_UP); | |
| 196 } | |
| 197 | |
| 198 MenuItemView* FindInitialSelectableMenuItemDown(MenuItemView* parent) { | |
| 199 return controller_->FindInitialSelectableMenuItem( | |
| 200 parent, MenuController::INCREMENT_SELECTION_DOWN); | |
| 201 } | |
| 202 | |
| 203 MenuItemView* FindInitialSelectableMenuItemUp(MenuItemView* parent) { | |
| 204 return controller_->FindInitialSelectableMenuItem( | |
| 205 parent, MenuController::INCREMENT_SELECTION_UP); | |
| 206 } | |
| 207 | |
| 208 MenuItemView* FindNextSelectableMenuItem(MenuItemView* parent, | |
| 209 int index) { | |
| 210 return controller_->FindNextSelectableMenuItem( | |
| 211 parent, index, MenuController::INCREMENT_SELECTION_DOWN); | |
| 212 } | |
| 213 | |
| 214 MenuItemView* FindPreviousSelectableMenuItem(MenuItemView* parent, | |
| 215 int index) { | |
| 216 return controller_->FindNextSelectableMenuItem( | |
| 217 parent, index, MenuController::INCREMENT_SELECTION_UP); | |
| 218 } | |
| 219 | |
| 220 void SetupMenu(views::Widget* owner, views::MenuItemView* item) { | |
| 221 ResetMenuController(); | |
| 222 controller_ = new MenuController(nullptr, true, nullptr); | |
| 223 controller_->owner_ = owner; | |
| 224 controller_->showing_ = true; | |
| 225 controller_->SetSelection(item, | |
| 226 MenuController::SELECTION_UPDATE_IMMEDIATELY); | |
| 227 } | |
| 228 | |
| 229 void RunMenu(views::Widget* owner) { | |
| 230 scoped_ptr<TestMenuItemView> menu_item(new TestMenuItemView); | |
| 231 SetupMenu(owner, menu_item.get()); | |
| 232 controller_->RunMessageLoop(false); | |
| 233 } | |
| 234 | |
| 235 #if defined(USE_X11) | |
| 236 void DispatchEscapeAndExpect(MenuController::ExitType exit_type) { | |
| 237 ui::ScopedXI2Event key_event; | |
| 238 key_event.InitKeyEvent(ui::ET_KEY_PRESSED, ui::VKEY_ESCAPE, 0); | |
| 239 event_source_.Dispatch(key_event); | |
| 240 EXPECT_EQ(exit_type, controller_->exit_type()); | |
| 241 controller_->exit_type_ = MenuController::EXIT_ALL; | |
| 242 DispatchEvent(); | |
| 243 } | |
| 244 | |
| 245 void DispatchTouch(int evtype, int id) { | |
| 246 ui::ScopedXI2Event touch_event; | |
| 247 std::vector<ui::Valuator> valuators; | |
| 248 touch_event.InitTouchEvent(1, evtype, id, gfx::Point(10, 10), valuators); | |
| 249 event_source_.Dispatch(touch_event); | |
| 250 DispatchEvent(); | |
| 251 } | |
| 252 #endif | |
| 253 | |
| 254 void DispatchEvent() { | |
| 255 #if defined(USE_X11) | |
| 256 XEvent xevent; | |
| 257 memset(&xevent, 0, sizeof(xevent)); | |
| 258 event_source_.Dispatch(&xevent); | |
| 259 #elif defined(OS_WIN) | |
| 260 MSG msg; | |
| 261 memset(&msg, 0, sizeof(MSG)); | |
| 262 dispatcher_client_.dispatcher()->Dispatch(msg); | |
| 263 #elif defined(USE_OZONE) | |
| 264 ui::KeyEvent event(' ', ui::VKEY_SPACE, ui::EF_NONE); | |
| 265 event_source_.Dispatch(&event); | |
| 266 #elif defined(OS_MACOSX) && !defined(USE_AURA) | |
| 267 // Since this is not an interactive test, on Mac there will be no key | |
| 268 // window. Any system event will just get ignored, so use the EventGenerator | |
| 269 // to generate a dummy event. Without Aura, these will be native events. | |
| 270 gfx::NativeWindow window = controller_->owner()->GetNativeWindow(); | |
| 271 ui::test::EventGenerator generator(window, window); | |
| 272 // Send "up", since this will not activate a menu item. But note that the | |
| 273 // test has already set exit_type_ = EXIT_ALL just before the first call | |
| 274 // to this function. | |
| 275 generator.PressKey(ui::VKEY_UP, 0); | |
| 276 #else | |
| 277 #error Unsupported platform | |
| 278 #endif | |
| 279 } | |
| 280 | |
| 281 private: | |
| 282 void ResetMenuController() { | |
| 283 if (controller_) { | |
| 284 // These properties are faked by RunMenu for the purposes of testing and | |
| 285 // need to be undone before we call the destructor. | |
| 286 controller_->owner_ = nullptr; | |
| 287 controller_->showing_ = false; | |
| 288 delete controller_; | |
| 289 controller_ = nullptr; | |
| 290 } | |
| 291 } | |
| 292 | |
| 293 // A weak pointer to the MenuController owned by this class. | |
| 294 MenuController* controller_; | |
| 295 scoped_ptr<base::RunLoop> run_loop_; | |
| 296 ui::test::PlatformEventSourceTestAPI event_source_; | |
| 297 #if defined(USE_AURA) | |
| 298 TestDispatcherClient dispatcher_client_; | |
| 299 #endif | |
| 300 | 228 |
| 301 DISALLOW_COPY_AND_ASSIGN(MenuControllerTest); | 229 DISALLOW_COPY_AND_ASSIGN(MenuControllerTest); |
| 302 }; | 230 }; |
| 303 | 231 |
| 304 TEST_F(MenuControllerTest, Basic) { | |
| 305 base::MessageLoop::ScopedNestableTaskAllower allow_nested( | |
| 306 base::MessageLoop::current()); | |
| 307 message_loop()->PostTask( | |
| 308 FROM_HERE, | |
| 309 base::Bind(&MenuControllerTest::Step1_RunMenu, base::Unretained(this))); | |
| 310 } | |
| 311 | |
| 312 #if defined(OS_LINUX) && defined(USE_X11) | 232 #if defined(OS_LINUX) && defined(USE_X11) |
| 313 // Tests that an event targeter which blocks events will be honored by the menu | 233 // Tests that an event targeter which blocks events will be honored by the menu |
| 314 // event dispatcher. | 234 // event dispatcher. |
| 315 TEST_F(MenuControllerTest, EventTargeter) { | 235 TEST_F(MenuControllerTest, EventTargeter) { |
| 316 { | 236 base::MessageLoopForUI::current()->PostTask( |
| 317 // Verify that the menu handles the escape key under normal circumstances. | 237 FROM_HERE, |
| 318 scoped_ptr<Widget> owner(CreateOwnerWidget()); | 238 base::Bind(&MenuControllerTest::TestEventTargeter, |
| 319 message_loop()->PostTask( | 239 base::Unretained(this))); |
| 320 FROM_HERE, | 240 RunMenu(); |
| 321 base::Bind(&MenuControllerTest::DispatchEscapeAndExpect, | |
| 322 base::Unretained(this), | |
| 323 MenuController::EXIT_OUTERMOST)); | |
| 324 RunMenu(owner.get()); | |
| 325 } | |
| 326 | |
| 327 { | |
| 328 // With the NULL targeter instantiated and assigned we expect the menu to | |
| 329 // not handle the key event. | |
| 330 scoped_ptr<Widget> owner(CreateOwnerWidget()); | |
| 331 aura::ScopedWindowTargeter scoped_targeter( | |
| 332 owner->GetNativeWindow()->GetRootWindow(), | |
| 333 scoped_ptr<ui::EventTargeter>(new ui::NullEventTargeter)); | |
| 334 message_loop()->PostTask( | |
| 335 FROM_HERE, | |
| 336 base::Bind(&MenuControllerTest::DispatchEscapeAndExpect, | |
| 337 base::Unretained(this), | |
| 338 MenuController::EXIT_NONE)); | |
| 339 RunMenu(owner.get()); | |
| 340 } | |
| 341 } | 241 } |
| 342 #endif | 242 #endif // defined(OS_LINUX) && defined(USE_X11) |
| 343 | 243 |
| 344 #if defined(USE_X11) | 244 #if defined(USE_X11) |
| 345 | |
| 346 class TestEventHandler : public ui::EventHandler { | |
| 347 public: | |
| 348 TestEventHandler() : outstanding_touches_(0) {} | |
| 349 | |
| 350 void OnTouchEvent(ui::TouchEvent* event) override { | |
| 351 switch(event->type()) { | |
| 352 case ui::ET_TOUCH_PRESSED: | |
| 353 outstanding_touches_++; | |
| 354 break; | |
| 355 case ui::ET_TOUCH_RELEASED: | |
| 356 case ui::ET_TOUCH_CANCELLED: | |
| 357 outstanding_touches_--; | |
| 358 break; | |
| 359 default: | |
| 360 break; | |
| 361 } | |
| 362 } | |
| 363 | |
| 364 int outstanding_touches() const { return outstanding_touches_; } | |
| 365 | |
| 366 private: | |
| 367 int outstanding_touches_; | |
| 368 }; | |
| 369 | |
| 370 // Tests that touch event ids are released correctly. See crbug.com/439051 for | 245 // Tests that touch event ids are released correctly. See crbug.com/439051 for |
| 371 // details. When the ids aren't managed correctly, we get stuck down touches. | 246 // details. When the ids aren't managed correctly, we get stuck down touches. |
| 372 TEST_F(MenuControllerTest, TouchIdsReleasedCorrectly) { | 247 TEST_F(MenuControllerTest, TouchIdsReleasedCorrectly) { |
| 373 scoped_ptr<Widget> owner(CreateOwnerWidget()); | |
| 374 TestEventHandler test_event_handler; | 248 TestEventHandler test_event_handler; |
| 375 owner->GetNativeWindow()->GetRootWindow()->AddPreTargetHandler( | 249 owner()->GetNativeWindow()->GetRootWindow()->AddPreTargetHandler( |
| 376 &test_event_handler); | 250 &test_event_handler); |
| 377 | 251 |
| 378 std::vector<int> devices; | 252 std::vector<int> devices; |
| 379 devices.push_back(1); | 253 devices.push_back(1); |
| 380 ui::SetUpTouchDevicesForTest(devices); | 254 ui::SetUpTouchDevicesForTest(devices); |
| 381 | 255 |
| 382 DispatchTouch(XI_TouchBegin, 0); | 256 event_generator()->PressTouchId(0); |
| 383 DispatchTouch(XI_TouchBegin, 1); | 257 event_generator()->PressTouchId(1); |
| 384 DispatchTouch(XI_TouchEnd, 0); | 258 event_generator()->ReleaseTouchId(0); |
| 385 | 259 |
| 386 message_loop()->PostTask(FROM_HERE, | 260 base::MessageLoopForUI::current()->PostTask( |
| 387 base::Bind(&MenuControllerTest::DispatchTouch, | |
| 388 base::Unretained(this), XI_TouchEnd, 1)); | |
| 389 | |
| 390 message_loop()->PostTask( | |
| 391 FROM_HERE, | 261 FROM_HERE, |
| 392 base::Bind(&MenuControllerTest::DispatchEscapeAndExpect, | 262 base::Bind(&MenuControllerTest::ReleaseTouchId, |
| 393 base::Unretained(this), MenuController::EXIT_OUTERMOST)); | 263 base::Unretained(this), |
| 394 | 264 1)); |
| 395 RunMenu(owner.get()); | 265 |
| 266 base::MessageLoopForUI::current()->PostTask( |
| 267 FROM_HERE, |
| 268 base::Bind(&MenuControllerTest::PressKey, |
| 269 base::Unretained(this), |
| 270 ui::VKEY_ESCAPE)); |
| 271 |
| 272 RunMenu(); |
| 273 |
| 274 EXPECT_EQ(MenuController::EXIT_OUTERMOST, menu_exit_type()); |
| 396 EXPECT_EQ(0, test_event_handler.outstanding_touches()); | 275 EXPECT_EQ(0, test_event_handler.outstanding_touches()); |
| 397 | 276 |
| 398 owner->GetNativeWindow()->GetRootWindow()->RemovePreTargetHandler( | 277 owner()->GetNativeWindow()->GetRootWindow()->RemovePreTargetHandler( |
| 399 &test_event_handler); | 278 &test_event_handler); |
| 400 } | 279 } |
| 401 #endif // defined(USE_X11) | 280 #endif // defined(USE_X11) |
| 402 | 281 |
| 403 // Tests that initial selected menu items are correct when items are enabled or | 282 // Tests that initial selected menu items are correct when items are enabled or |
| 404 // disabled. | 283 // disabled. |
| 405 TEST_F(MenuControllerTest, InitialSelectedItem) { | 284 TEST_F(MenuControllerTest, InitialSelectedItem) { |
| 406 scoped_ptr<Widget> owner(CreateOwnerWidget()); | 285 // Leave items "Two", "Three", and "Four" enabled. |
| 407 scoped_ptr<TestMenuItemView> menu_item(new TestMenuItemView); | 286 menu_item()->GetSubmenu()->GetMenuItemAt(0)->SetEnabled(false); |
| 408 menu_item->AppendMenuItemWithLabel(1, base::ASCIIToUTF16("One")); | |
| 409 menu_item->AppendMenuItemWithLabel(2, base::ASCIIToUTF16("Two")); | |
| 410 menu_item->AppendMenuItemWithLabel(3, base::ASCIIToUTF16("Three")); | |
| 411 SetupMenu(owner.get(), menu_item.get()); | |
| 412 | |
| 413 // Leave items "Two" and "Three" enabled. | |
| 414 menu_item->GetSubmenu()->GetMenuItemAt(0)->SetEnabled(false); | |
| 415 // The first selectable item should be item "Two". | 287 // The first selectable item should be item "Two". |
| 416 MenuItemView* first_selectable = | 288 MenuItemView* first_selectable = |
| 417 FindInitialSelectableMenuItemDown(menu_item.get()); | 289 FindInitialSelectableMenuItemDown(menu_item()); |
| 418 ASSERT_NE(nullptr, first_selectable); | 290 ASSERT_NE(nullptr, first_selectable); |
| 419 EXPECT_EQ(2, first_selectable->GetCommand()); | 291 EXPECT_EQ(2, first_selectable->GetCommand()); |
| 420 // The last selectable item should be item "Three". | 292 // The last selectable item should be item "Four". |
| 421 MenuItemView* last_selectable = | 293 MenuItemView* last_selectable = |
| 422 FindInitialSelectableMenuItemUp(menu_item.get()); | 294 FindInitialSelectableMenuItemUp(menu_item()); |
| 423 ASSERT_NE(nullptr, last_selectable); | 295 ASSERT_NE(nullptr, last_selectable); |
| 424 EXPECT_EQ(3, last_selectable->GetCommand()); | 296 EXPECT_EQ(4, last_selectable->GetCommand()); |
| 425 | 297 |
| 426 // Leave items "One" and "Two" enabled. | 298 // Leave items "One" and "Two" enabled. |
| 427 menu_item->GetSubmenu()->GetMenuItemAt(0)->SetEnabled(true); | 299 menu_item()->GetSubmenu()->GetMenuItemAt(0)->SetEnabled(true); |
| 428 menu_item->GetSubmenu()->GetMenuItemAt(1)->SetEnabled(true); | 300 menu_item()->GetSubmenu()->GetMenuItemAt(1)->SetEnabled(true); |
| 429 menu_item->GetSubmenu()->GetMenuItemAt(2)->SetEnabled(false); | 301 menu_item()->GetSubmenu()->GetMenuItemAt(2)->SetEnabled(false); |
| 302 menu_item()->GetSubmenu()->GetMenuItemAt(3)->SetEnabled(false); |
| 430 // The first selectable item should be item "One". | 303 // The first selectable item should be item "One". |
| 431 first_selectable = FindInitialSelectableMenuItemDown(menu_item.get()); | 304 first_selectable = FindInitialSelectableMenuItemDown(menu_item()); |
| 432 ASSERT_NE(nullptr, first_selectable); | 305 ASSERT_NE(nullptr, first_selectable); |
| 433 EXPECT_EQ(1, first_selectable->GetCommand()); | 306 EXPECT_EQ(1, first_selectable->GetCommand()); |
| 434 // The last selectable item should be item "Two". | 307 // The last selectable item should be item "Two". |
| 435 last_selectable = FindInitialSelectableMenuItemUp(menu_item.get()); | 308 last_selectable = FindInitialSelectableMenuItemUp(menu_item()); |
| 436 ASSERT_NE(nullptr, last_selectable); | 309 ASSERT_NE(nullptr, last_selectable); |
| 437 EXPECT_EQ(2, last_selectable->GetCommand()); | 310 EXPECT_EQ(2, last_selectable->GetCommand()); |
| 438 | 311 |
| 439 // Leave only a single item "One" enabled. | 312 // Leave only a single item "One" enabled. |
| 440 menu_item->GetSubmenu()->GetMenuItemAt(0)->SetEnabled(true); | 313 menu_item()->GetSubmenu()->GetMenuItemAt(0)->SetEnabled(true); |
| 441 menu_item->GetSubmenu()->GetMenuItemAt(1)->SetEnabled(false); | 314 menu_item()->GetSubmenu()->GetMenuItemAt(1)->SetEnabled(false); |
| 442 menu_item->GetSubmenu()->GetMenuItemAt(2)->SetEnabled(false); | 315 menu_item()->GetSubmenu()->GetMenuItemAt(2)->SetEnabled(false); |
| 316 menu_item()->GetSubmenu()->GetMenuItemAt(3)->SetEnabled(false); |
| 443 // The first selectable item should be item "One". | 317 // The first selectable item should be item "One". |
| 444 first_selectable = FindInitialSelectableMenuItemDown(menu_item.get()); | 318 first_selectable = FindInitialSelectableMenuItemDown(menu_item()); |
| 445 ASSERT_NE(nullptr, first_selectable); | 319 ASSERT_NE(nullptr, first_selectable); |
| 446 EXPECT_EQ(1, first_selectable->GetCommand()); | 320 EXPECT_EQ(1, first_selectable->GetCommand()); |
| 447 // The last selectable item should be item "One". | 321 // The last selectable item should be item "One". |
| 448 last_selectable = FindInitialSelectableMenuItemUp(menu_item.get()); | 322 last_selectable = FindInitialSelectableMenuItemUp(menu_item()); |
| 449 ASSERT_NE(nullptr, last_selectable); | 323 ASSERT_NE(nullptr, last_selectable); |
| 450 EXPECT_EQ(1, last_selectable->GetCommand()); | 324 EXPECT_EQ(1, last_selectable->GetCommand()); |
| 451 | 325 |
| 452 // Leave only a single item "Three" enabled. | 326 // Leave only a single item "Three" enabled. |
| 453 menu_item->GetSubmenu()->GetMenuItemAt(0)->SetEnabled(false); | 327 menu_item()->GetSubmenu()->GetMenuItemAt(0)->SetEnabled(false); |
| 454 menu_item->GetSubmenu()->GetMenuItemAt(1)->SetEnabled(false); | 328 menu_item()->GetSubmenu()->GetMenuItemAt(1)->SetEnabled(false); |
| 455 menu_item->GetSubmenu()->GetMenuItemAt(2)->SetEnabled(true); | 329 menu_item()->GetSubmenu()->GetMenuItemAt(2)->SetEnabled(true); |
| 330 menu_item()->GetSubmenu()->GetMenuItemAt(3)->SetEnabled(false); |
| 456 // The first selectable item should be item "Three". | 331 // The first selectable item should be item "Three". |
| 457 first_selectable = FindInitialSelectableMenuItemDown(menu_item.get()); | 332 first_selectable = FindInitialSelectableMenuItemDown(menu_item()); |
| 458 ASSERT_NE(nullptr, first_selectable); | 333 ASSERT_NE(nullptr, first_selectable); |
| 459 EXPECT_EQ(3, first_selectable->GetCommand()); | 334 EXPECT_EQ(3, first_selectable->GetCommand()); |
| 460 // The last selectable item should be item "Three". | 335 // The last selectable item should be item "Three". |
| 461 last_selectable = FindInitialSelectableMenuItemUp(menu_item.get()); | 336 last_selectable = FindInitialSelectableMenuItemUp(menu_item()); |
| 462 ASSERT_NE(nullptr, last_selectable); | 337 ASSERT_NE(nullptr, last_selectable); |
| 463 EXPECT_EQ(3, last_selectable->GetCommand()); | 338 EXPECT_EQ(3, last_selectable->GetCommand()); |
| 464 | 339 |
| 465 // Leave only a single item ("Two") selected. It should be the first and the | 340 // Leave only a single item ("Two") selected. It should be the first and the |
| 466 // last selectable item. | 341 // last selectable item. |
| 467 menu_item->GetSubmenu()->GetMenuItemAt(0)->SetEnabled(false); | 342 menu_item()->GetSubmenu()->GetMenuItemAt(0)->SetEnabled(false); |
| 468 menu_item->GetSubmenu()->GetMenuItemAt(1)->SetEnabled(true); | 343 menu_item()->GetSubmenu()->GetMenuItemAt(1)->SetEnabled(true); |
| 469 menu_item->GetSubmenu()->GetMenuItemAt(2)->SetEnabled(false); | 344 menu_item()->GetSubmenu()->GetMenuItemAt(2)->SetEnabled(false); |
| 470 first_selectable = FindInitialSelectableMenuItemDown(menu_item.get()); | 345 menu_item()->GetSubmenu()->GetMenuItemAt(3)->SetEnabled(false); |
| 346 first_selectable = FindInitialSelectableMenuItemDown(menu_item()); |
| 471 ASSERT_NE(nullptr, first_selectable); | 347 ASSERT_NE(nullptr, first_selectable); |
| 472 EXPECT_EQ(2, first_selectable->GetCommand()); | 348 EXPECT_EQ(2, first_selectable->GetCommand()); |
| 473 last_selectable = FindInitialSelectableMenuItemUp(menu_item.get()); | 349 last_selectable = FindInitialSelectableMenuItemUp(menu_item()); |
| 474 ASSERT_NE(nullptr, last_selectable); | 350 ASSERT_NE(nullptr, last_selectable); |
| 475 EXPECT_EQ(2, last_selectable->GetCommand()); | 351 EXPECT_EQ(2, last_selectable->GetCommand()); |
| 476 | 352 |
| 477 // There should be no next or previous selectable item since there is only a | 353 // There should be no next or previous selectable item since there is only a |
| 478 // single enabled item in the menu. | 354 // single enabled item in the menu. |
| 479 EXPECT_EQ(nullptr, FindNextSelectableMenuItem(menu_item.get(), 1)); | 355 EXPECT_EQ(nullptr, FindNextSelectableMenuItem(menu_item(), 1)); |
| 480 EXPECT_EQ(nullptr, FindPreviousSelectableMenuItem(menu_item.get(), 1)); | 356 EXPECT_EQ(nullptr, FindPreviousSelectableMenuItem(menu_item(), 1)); |
| 481 | 357 |
| 482 // Clear references in menu controller to the menu item that is going away. | 358 // Clear references in menu controller to the menu item that is going away. |
| 483 ResetSelection(); | 359 ResetSelection(); |
| 484 } | 360 } |
| 485 | 361 |
| 486 // Tests that opening menu and pressing 'Down' and 'Up' iterates over enabled | 362 // Tests that opening menu and pressing 'Down' and 'Up' iterates over enabled |
| 487 // items. | 363 // items. |
| 488 TEST_F(MenuControllerTest, NextSelectedItem) { | 364 TEST_F(MenuControllerTest, NextSelectedItem) { |
| 489 scoped_ptr<Widget> owner(CreateOwnerWidget()); | |
| 490 scoped_ptr<TestMenuItemView> menu_item(new TestMenuItemView); | |
| 491 menu_item->AppendMenuItemWithLabel(1, base::ASCIIToUTF16("One")); | |
| 492 menu_item->AppendMenuItemWithLabel(2, base::ASCIIToUTF16("Two")); | |
| 493 menu_item->AppendMenuItemWithLabel(3, base::ASCIIToUTF16("Three")); | |
| 494 menu_item->AppendMenuItemWithLabel(4, base::ASCIIToUTF16("Four")); | |
| 495 // Disabling the item "Three" gets it skipped when using keyboard to navigate. | 365 // Disabling the item "Three" gets it skipped when using keyboard to navigate. |
| 496 menu_item->GetSubmenu()->GetMenuItemAt(2)->SetEnabled(false); | 366 menu_item()->GetSubmenu()->GetMenuItemAt(2)->SetEnabled(false); |
| 497 | |
| 498 SetupMenu(owner.get(), menu_item.get()); | |
| 499 | 367 |
| 500 // Fake initial hot selection. | 368 // Fake initial hot selection. |
| 501 SetPendingStateItem(menu_item->GetSubmenu()->GetMenuItemAt(0)); | 369 SetPendingStateItem(menu_item()->GetSubmenu()->GetMenuItemAt(0)); |
| 502 EXPECT_EQ(1, pending_state_item()->GetCommand()); | 370 EXPECT_EQ(1, pending_state_item()->GetCommand()); |
| 503 | 371 |
| 504 // Move down in the menu. | 372 // Move down in the menu. |
| 505 // Select next item. | 373 // Select next item. |
| 506 IncrementSelection(); | 374 IncrementSelection(); |
| 507 EXPECT_EQ(2, pending_state_item()->GetCommand()); | 375 EXPECT_EQ(2, pending_state_item()->GetCommand()); |
| 508 | 376 |
| 509 // Skip disabled item. | 377 // Skip disabled item. |
| 510 IncrementSelection(); | 378 IncrementSelection(); |
| 511 EXPECT_EQ(4, pending_state_item()->GetCommand()); | 379 EXPECT_EQ(4, pending_state_item()->GetCommand()); |
| (...skipping 14 matching lines...) Expand all Loading... |
| 526 // Select previous item. | 394 // Select previous item. |
| 527 DecrementSelection(); | 395 DecrementSelection(); |
| 528 EXPECT_EQ(1, pending_state_item()->GetCommand()); | 396 EXPECT_EQ(1, pending_state_item()->GetCommand()); |
| 529 | 397 |
| 530 // Clear references in menu controller to the menu item that is going away. | 398 // Clear references in menu controller to the menu item that is going away. |
| 531 ResetSelection(); | 399 ResetSelection(); |
| 532 } | 400 } |
| 533 | 401 |
| 534 // Tests that opening menu and pressing 'Up' selects the last enabled menu item. | 402 // Tests that opening menu and pressing 'Up' selects the last enabled menu item. |
| 535 TEST_F(MenuControllerTest, PreviousSelectedItem) { | 403 TEST_F(MenuControllerTest, PreviousSelectedItem) { |
| 536 scoped_ptr<Widget> owner(CreateOwnerWidget()); | |
| 537 scoped_ptr<TestMenuItemViewShown> menu_item(new TestMenuItemViewShown); | |
| 538 menu_item->AppendMenuItemWithLabel(1, base::ASCIIToUTF16("One")); | |
| 539 menu_item->AppendMenuItemWithLabel(2, base::ASCIIToUTF16("Two")); | |
| 540 menu_item->AppendMenuItemWithLabel(3, base::ASCIIToUTF16("Three")); | |
| 541 menu_item->AppendMenuItemWithLabel(4, base::ASCIIToUTF16("Four")); | |
| 542 // Disabling the item "Four" gets it skipped when using keyboard to navigate. | 404 // Disabling the item "Four" gets it skipped when using keyboard to navigate. |
| 543 menu_item->GetSubmenu()->GetMenuItemAt(3)->SetEnabled(false); | 405 menu_item()->GetSubmenu()->GetMenuItemAt(3)->SetEnabled(false); |
| 544 | |
| 545 SetupMenu(owner.get(), menu_item.get()); | |
| 546 | 406 |
| 547 // Fake initial root item selection and submenu showing. | 407 // Fake initial root item selection and submenu showing. |
| 548 SetPendingStateItem(menu_item.get()); | 408 SetPendingStateItem(menu_item()); |
| 549 EXPECT_EQ(0, pending_state_item()->GetCommand()); | 409 EXPECT_EQ(0, pending_state_item()->GetCommand()); |
| 550 | 410 |
| 551 // Move up and select a previous (in our case the last enabled) item. | 411 // Move up and select a previous (in our case the last enabled) item. |
| 552 DecrementSelection(); | 412 DecrementSelection(); |
| 553 EXPECT_EQ(3, pending_state_item()->GetCommand()); | 413 EXPECT_EQ(3, pending_state_item()->GetCommand()); |
| 554 | 414 |
| 555 // Clear references in menu controller to the menu item that is going away. | 415 // Clear references in menu controller to the menu item that is going away. |
| 556 ResetSelection(); | 416 ResetSelection(); |
| 557 } | 417 } |
| 558 | 418 |
| 559 } // namespace test | 419 } // namespace test |
| 560 } // namespace views | 420 } // namespace views |
| OLD | NEW |