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 "base/strings/utf_string_conversions.h" 6#include "chrome/browser/ui/views/menu_test_base.h" 7#include "chrome/test/base/interactive_test_utils.h" 8#include "ui/base/dragdrop/drag_drop_types.h" 9#include "ui/base/dragdrop/os_exchange_data.h" 10#include "ui/views/controls/menu/menu_controller.h" 11#include "ui/views/controls/menu/menu_item_view.h" 12#include "ui/views/controls/menu/menu_runner.h" 13#include "ui/views/controls/menu/submenu_view.h" 14#include "ui/views/view.h" 15 16namespace { 17 18// Borrowed from chrome/browser/ui/views/bookmarks/bookmark_bar_view_test.cc, 19// since these are also disabled on Linux for drag and drop. 20// TODO(erg): Fix DND tests on linux_aura. crbug.com/163931 21#if defined(OS_LINUX) && defined(USE_AURA) 22#define MAYBE(x) DISABLED_##x 23#else 24#define MAYBE(x) x 25#endif 26 27const char kTestNestedDragData[] = "test_nested_drag_data"; 28const char kTestTopLevelDragData[] = "test_top_level_drag_data"; 29 30// A simple view which can be dragged. 31class TestDragView : public views::View { 32 public: 33 TestDragView(); 34 virtual ~TestDragView(); 35 36 private: 37 // views::View: 38 virtual int GetDragOperations(const gfx::Point& point) OVERRIDE; 39 virtual void WriteDragData(const gfx::Point& point, 40 ui::OSExchangeData* data) OVERRIDE; 41 42 DISALLOW_COPY_AND_ASSIGN(TestDragView); 43}; 44 45TestDragView::TestDragView() { 46} 47 48TestDragView::~TestDragView() { 49} 50 51int TestDragView::GetDragOperations(const gfx::Point& point) { 52 return ui::DragDropTypes::DRAG_MOVE; 53} 54 55void TestDragView::WriteDragData(const gfx::Point& point, 56 ui::OSExchangeData* data) { 57 data->SetString(base::ASCIIToUTF16(kTestNestedDragData)); 58} 59 60// A simple view to serve as a drop target. 61class TestTargetView : public views::View { 62 public: 63 TestTargetView(); 64 virtual ~TestTargetView(); 65 66 // Initializes this view to have the same bounds as |parent| and two draggable 67 // child views. 68 void Init(views::View* parent); 69 bool dragging() const { return dragging_; } 70 bool dropped() const { return dropped_; } 71 72 private: 73 // views::View: 74 virtual bool GetDropFormats( 75 int* formats, 76 std::set<OSExchangeData::CustomFormat>* custom_formats) OVERRIDE; 77 virtual bool AreDropTypesRequired() OVERRIDE; 78 virtual bool CanDrop(const OSExchangeData& data) OVERRIDE; 79 virtual void OnDragEntered(const ui::DropTargetEvent& event) OVERRIDE; 80 virtual int OnDragUpdated(const ui::DropTargetEvent& event) OVERRIDE; 81 virtual int OnPerformDrop(const ui::DropTargetEvent& event) OVERRIDE; 82 virtual void OnDragExited() OVERRIDE; 83 84 // Whether or not we are currently dragging. 85 bool dragging_; 86 87 // Whether or not a drop has been performed on the view. 88 bool dropped_; 89 90 DISALLOW_COPY_AND_ASSIGN(TestTargetView); 91}; 92 93TestTargetView::TestTargetView() : dragging_(false), dropped_(false) { 94} 95 96void TestTargetView::Init(views::View* parent) { 97 // First, match the parent's size. 98 SetSize(parent->size()); 99 100 // Then add two draggable views, each 10x2. 101 views::View* first = new TestDragView(); 102 AddChildView(first); 103 first->SetBounds(2, 2, 10, 2); 104 105 views::View* second = new TestDragView(); 106 AddChildView(second); 107 second->SetBounds(15, 2, 10, 2); 108} 109 110TestTargetView::~TestTargetView() { 111} 112 113bool TestTargetView::GetDropFormats( 114 int* formats, std::set<OSExchangeData::CustomFormat>* custom_formats) { 115 *formats = ui::OSExchangeData::STRING; 116 return true; 117} 118 119bool TestTargetView::AreDropTypesRequired() { 120 return true; 121} 122 123bool TestTargetView::CanDrop(const OSExchangeData& data) { 124 base::string16 contents; 125 return data.GetString(&contents) && 126 contents == base::ASCIIToUTF16(kTestNestedDragData); 127} 128 129void TestTargetView::OnDragEntered(const ui::DropTargetEvent& event) { 130 dragging_ = true; 131} 132 133int TestTargetView::OnDragUpdated(const ui::DropTargetEvent& event) { 134 return ui::DragDropTypes::DRAG_MOVE; 135} 136 137int TestTargetView::OnPerformDrop(const ui::DropTargetEvent& event) { 138 dragging_ = false; 139 dropped_ = true; 140 return ui::DragDropTypes::DRAG_MOVE; 141} 142 143void TestTargetView::OnDragExited() { 144 dragging_ = false; 145} 146 147} // namespace 148 149class MenuViewDragAndDropTest : public MenuTestBase { 150 public: 151 MenuViewDragAndDropTest(); 152 virtual ~MenuViewDragAndDropTest(); 153 154 protected: 155 TestTargetView* target_view() { return target_view_; } 156 bool asked_to_close() const { return asked_to_close_; } 157 bool performed_in_menu_drop() const { return performed_in_menu_drop_; } 158 159 private: 160 // MenuTestBase: 161 virtual void BuildMenu(views::MenuItemView* menu) OVERRIDE; 162 163 // views::MenuDelegate: 164 virtual bool GetDropFormats( 165 views::MenuItemView* menu, 166 int* formats, 167 std::set<ui::OSExchangeData::CustomFormat>* custom_formats) OVERRIDE; 168 virtual bool AreDropTypesRequired(views::MenuItemView* menu) OVERRIDE; 169 virtual bool CanDrop(views::MenuItemView* menu, 170 const ui::OSExchangeData& data) OVERRIDE; 171 virtual int GetDropOperation(views::MenuItemView* item, 172 const ui::DropTargetEvent& event, 173 DropPosition* position) OVERRIDE; 174 virtual int OnPerformDrop(views::MenuItemView* menu, 175 DropPosition position, 176 const ui::DropTargetEvent& event) OVERRIDE; 177 virtual bool CanDrag(views::MenuItemView* menu) OVERRIDE; 178 virtual void WriteDragData(views::MenuItemView* sender, 179 ui::OSExchangeData* data) OVERRIDE; 180 virtual int GetDragOperations(views::MenuItemView* sender) OVERRIDE; 181 virtual bool ShouldCloseOnDragComplete() OVERRIDE; 182 183 // The special view in the menu, which supports its own drag and drop. 184 TestTargetView* target_view_; 185 186 // Whether or not we have been asked to close on drag complete. 187 bool asked_to_close_; 188 189 // Whether or not a drop was performed in-menu (i.e., not including drops 190 // in separate child views). 191 bool performed_in_menu_drop_; 192 193 DISALLOW_COPY_AND_ASSIGN(MenuViewDragAndDropTest); 194}; 195 196MenuViewDragAndDropTest::MenuViewDragAndDropTest() 197 : target_view_(NULL), 198 asked_to_close_(false), 199 performed_in_menu_drop_(false) { 200} 201 202MenuViewDragAndDropTest::~MenuViewDragAndDropTest() { 203} 204 205void MenuViewDragAndDropTest::BuildMenu(views::MenuItemView* menu) { 206 // Build a menu item that has a nested view that supports its own drag and 207 // drop... 208 views::MenuItemView* menu_item_view = 209 menu->AppendMenuItem(1, 210 base::ASCIIToUTF16("item 1"), 211 views::MenuItemView::NORMAL); 212 target_view_ = new TestTargetView(); 213 menu_item_view->AddChildView(target_view_); 214 // ... as well as two other, normal items. 215 menu->AppendMenuItemWithLabel(2, base::ASCIIToUTF16("item 2")); 216 menu->AppendMenuItemWithLabel(3, base::ASCIIToUTF16("item 3")); 217} 218 219bool MenuViewDragAndDropTest::GetDropFormats( 220 views::MenuItemView* menu, 221 int* formats, 222 std::set<ui::OSExchangeData::CustomFormat>* custom_formats) { 223 *formats = ui::OSExchangeData::STRING; 224 return true; 225} 226 227bool MenuViewDragAndDropTest::AreDropTypesRequired(views::MenuItemView* menu) { 228 return true; 229} 230 231bool MenuViewDragAndDropTest::CanDrop(views::MenuItemView* menu, 232 const ui::OSExchangeData& data) { 233 base::string16 contents; 234 return data.GetString(&contents) && 235 contents == base::ASCIIToUTF16(kTestTopLevelDragData); 236} 237 238int MenuViewDragAndDropTest::GetDropOperation(views::MenuItemView* item, 239 const ui::DropTargetEvent& event, 240 DropPosition* position) { 241 return ui::DragDropTypes::DRAG_MOVE; 242} 243 244 245int MenuViewDragAndDropTest::OnPerformDrop(views::MenuItemView* menu, 246 DropPosition position, 247 const ui::DropTargetEvent& event) { 248 performed_in_menu_drop_ = true; 249 return ui::DragDropTypes::DRAG_MOVE; 250} 251 252bool MenuViewDragAndDropTest::CanDrag(views::MenuItemView* menu) { 253 return true; 254} 255 256void MenuViewDragAndDropTest::WriteDragData( 257 views::MenuItemView* sender, ui::OSExchangeData* data) { 258 data->SetString(base::ASCIIToUTF16(kTestTopLevelDragData)); 259} 260 261int MenuViewDragAndDropTest::GetDragOperations(views::MenuItemView* sender) { 262 return ui::DragDropTypes::DRAG_MOVE; 263} 264 265bool MenuViewDragAndDropTest::ShouldCloseOnDragComplete() { 266 asked_to_close_ = true; 267 return false; 268} 269 270class MenuViewDragAndDropTestTestInMenuDrag : public MenuViewDragAndDropTest { 271 public: 272 MenuViewDragAndDropTestTestInMenuDrag() {} 273 virtual ~MenuViewDragAndDropTestTestInMenuDrag() {} 274 275 private: 276 // MenuViewDragAndDropTest: 277 virtual void DoTestWithMenuOpen() OVERRIDE; 278 279 void Step2(); 280 void Step3(); 281 void Step4(); 282}; 283 284void MenuViewDragAndDropTestTestInMenuDrag::DoTestWithMenuOpen() { 285 // A few sanity checks to make sure the menu built correctly. 286 views::SubmenuView* submenu = menu()->GetSubmenu(); 287 ASSERT_TRUE(submenu); 288 ASSERT_TRUE(submenu->IsShowing()); 289 ASSERT_EQ(3, submenu->GetMenuItemCount()); 290 291 // We do this here (instead of in BuildMenu()) so that the menu is already 292 // built and the bounds are correct. 293 target_view()->Init(submenu->GetMenuItemAt(0)); 294 295 // We're going to drag the second menu element. 296 views::MenuItemView* drag_view = submenu->GetMenuItemAt(1); 297 ASSERT_TRUE(drag_view != NULL); 298 299 // Move mouse to center of menu and press button. 300 ui_test_utils::MoveMouseToCenterAndPress( 301 drag_view, 302 ui_controls::LEFT, 303 ui_controls::DOWN, 304 CreateEventTask(this, &MenuViewDragAndDropTestTestInMenuDrag::Step2)); 305} 306 307void MenuViewDragAndDropTestTestInMenuDrag::Step2() { 308 views::MenuItemView* drop_target = menu()->GetSubmenu()->GetMenuItemAt(2); 309 gfx::Point loc(1, drop_target->height() - 1); 310 views::View::ConvertPointToScreen(drop_target, &loc); 311 312 // Start a drag. 313 ui_controls::SendMouseMoveNotifyWhenDone( 314 loc.x() + 10, 315 loc.y(), 316 CreateEventTask(this, &MenuViewDragAndDropTestTestInMenuDrag::Step3)); 317 318 ScheduleMouseMoveInBackground(loc.x(), loc.y()); 319} 320 321void MenuViewDragAndDropTestTestInMenuDrag::Step3() { 322 // Drop the item on the target. 323 views::MenuItemView* drop_target = menu()->GetSubmenu()->GetMenuItemAt(2); 324 gfx::Point loc(1, drop_target->height() - 2); 325 views::View::ConvertPointToScreen(drop_target, &loc); 326 ui_controls::SendMouseMove(loc.x(), loc.y()); 327 328 ui_controls::SendMouseEventsNotifyWhenDone( 329 ui_controls::LEFT, 330 ui_controls::UP, 331 CreateEventTask(this, &MenuViewDragAndDropTestTestInMenuDrag::Step4)); 332} 333 334void MenuViewDragAndDropTestTestInMenuDrag::Step4() { 335 // Verify our state. 336 // We should have performed an in-menu drop, and the nested view should not 337 // have had a drag and drop. Since the drag happened in menu code, the 338 // delegate should not have been asked whether or not to close, and the menu 339 // should simply be closed. 340 EXPECT_TRUE(performed_in_menu_drop()); 341 EXPECT_FALSE(target_view()->dropped()); 342 EXPECT_FALSE(asked_to_close()); 343 EXPECT_FALSE(menu()->GetSubmenu()->IsShowing()); 344 345 Done(); 346} 347 348// Test that an in-menu (i.e., entirely implemented in the menu code) closes the 349// menu automatically once the drag is complete, and does not ask the delegate 350// to stay open. 351#if !defined(OS_WIN) // flaky http://crbug.com/401226 352VIEW_TEST(MenuViewDragAndDropTestTestInMenuDrag, MAYBE(TestInMenuDrag)) 353#endif 354 355class MenuViewDragAndDropTestNestedDrag : public MenuViewDragAndDropTest { 356 public: 357 MenuViewDragAndDropTestNestedDrag() {} 358 virtual ~MenuViewDragAndDropTestNestedDrag() {} 359 360 private: 361 // MenuViewDragAndDropTest: 362 virtual void DoTestWithMenuOpen() OVERRIDE; 363 364 void Step2(); 365 void Step3(); 366 void Step4(); 367}; 368 369void MenuViewDragAndDropTestNestedDrag::DoTestWithMenuOpen() { 370 // Sanity checks: We should be showing the menu, it should have three 371 // children, and the first of those children should have a nested view of the 372 // TestTargetView. 373 views::SubmenuView* submenu = menu()->GetSubmenu(); 374 ASSERT_TRUE(submenu); 375 ASSERT_TRUE(submenu->IsShowing()); 376 ASSERT_EQ(3, submenu->GetMenuItemCount()); 377 views::View* first_view = submenu->GetMenuItemAt(0); 378 ASSERT_EQ(1, first_view->child_count()); 379 views::View* child_view = first_view->child_at(0); 380 ASSERT_EQ(child_view, target_view()); 381 382 // We do this here (instead of in BuildMenu()) so that the menu is already 383 // built and the bounds are correct. 384 target_view()->Init(submenu->GetMenuItemAt(0)); 385 386 // The target view should now have two children. 387 ASSERT_EQ(2, target_view()->child_count()); 388 389 views::View* drag_view = target_view()->child_at(0); 390 ASSERT_TRUE(drag_view != NULL); 391 392 // Move mouse to center of menu and press button. 393 ui_test_utils::MoveMouseToCenterAndPress( 394 drag_view, 395 ui_controls::LEFT, 396 ui_controls::DOWN, 397 CreateEventTask(this, &MenuViewDragAndDropTestNestedDrag::Step2)); 398} 399 400void MenuViewDragAndDropTestNestedDrag::Step2() { 401 views::View* drop_target = target_view()->child_at(1); 402 gfx::Point loc(2, 0); 403 views::View::ConvertPointToScreen(drop_target, &loc); 404 405 // Start a drag. 406 ui_controls::SendMouseMoveNotifyWhenDone( 407 loc.x() + 3, 408 loc.y(), 409 CreateEventTask(this, &MenuViewDragAndDropTestNestedDrag::Step3)); 410 411 ScheduleMouseMoveInBackground(loc.x(), loc.y()); 412} 413 414void MenuViewDragAndDropTestNestedDrag::Step3() { 415 // The view should be dragging now. 416 EXPECT_TRUE(target_view()->dragging()); 417 418 // Drop the item so that it's now the second item. 419 views::View* drop_target = target_view()->child_at(1); 420 gfx::Point loc(5, 0); 421 views::View::ConvertPointToScreen(drop_target, &loc); 422 ui_controls::SendMouseMove(loc.x(), loc.y()); 423 424 ui_controls::SendMouseEventsNotifyWhenDone( 425 ui_controls::LEFT, 426 ui_controls::UP, 427 CreateEventTask(this, &MenuViewDragAndDropTestNestedDrag::Step4)); 428} 429 430void MenuViewDragAndDropTestNestedDrag::Step4() { 431 // Check our state. 432 // The target view should have finished its drag, and should have dropped the 433 // view. The main menu should not have done any drag, and the delegate should 434 // have been asked if it wanted to close. Since the delegate did not want to 435 // close, the menu should still be open. 436 EXPECT_FALSE(target_view()->dragging()); 437 EXPECT_TRUE(target_view()->dropped()); 438 EXPECT_FALSE(performed_in_menu_drop()); 439 EXPECT_TRUE(asked_to_close()); 440 EXPECT_TRUE(menu()->GetSubmenu()->IsShowing()); 441 442 // Clean up. 443 menu()->GetSubmenu()->Close(); 444 445 Done(); 446} 447 448// Test that a nested drag (i.e. one via a child view, and not entirely 449// implemented in menu code) will consult the delegate before closing the view 450// after the drag. 451#if !defined(OS_WIN) // http://crbug.com/401226 452VIEW_TEST(MenuViewDragAndDropTestNestedDrag, 453 MAYBE(MenuViewDragAndDropNestedDrag)) 454#endif 455 456class MenuViewDragAndDropForDropStayOpen : public MenuViewDragAndDropTest { 457 public: 458 MenuViewDragAndDropForDropStayOpen() {} 459 virtual ~MenuViewDragAndDropForDropStayOpen() {} 460 461 private: 462 // MenuViewDragAndDropTest: 463 virtual int GetMenuRunnerFlags() OVERRIDE; 464 virtual void DoTestWithMenuOpen() OVERRIDE; 465}; 466 467int MenuViewDragAndDropForDropStayOpen::GetMenuRunnerFlags() { 468 return views::MenuRunner::HAS_MNEMONICS | 469 views::MenuRunner::NESTED_DRAG | 470 views::MenuRunner::FOR_DROP; 471} 472 473void MenuViewDragAndDropForDropStayOpen::DoTestWithMenuOpen() { 474 views::SubmenuView* submenu = menu()->GetSubmenu(); 475 ASSERT_TRUE(submenu); 476 ASSERT_TRUE(submenu->IsShowing()); 477 478 views::MenuController* controller = menu()->GetMenuController(); 479 ASSERT_TRUE(controller); 480 EXPECT_FALSE(controller->IsCancelAllTimerRunningForTest()); 481 482 Done(); 483} 484 485// Test that if a menu is opened for a drop which is handled by a child view 486// that the menu does not immediately try to close. 487VIEW_TEST(MenuViewDragAndDropForDropStayOpen, MenuViewStaysOpenForNestedDrag) 488 489class MenuViewDragAndDropForDropCancel : public MenuViewDragAndDropTest { 490 public: 491 MenuViewDragAndDropForDropCancel() {} 492 virtual ~MenuViewDragAndDropForDropCancel() {} 493 494 private: 495 // MenuViewDragAndDropTest: 496 virtual int GetMenuRunnerFlags() OVERRIDE; 497 virtual void DoTestWithMenuOpen() OVERRIDE; 498}; 499 500int MenuViewDragAndDropForDropCancel::GetMenuRunnerFlags() { 501 return views::MenuRunner::HAS_MNEMONICS | views::MenuRunner::FOR_DROP; 502} 503 504void MenuViewDragAndDropForDropCancel::DoTestWithMenuOpen() { 505 views::SubmenuView* submenu = menu()->GetSubmenu(); 506 ASSERT_TRUE(submenu); 507 ASSERT_TRUE(submenu->IsShowing()); 508 509 views::MenuController* controller = menu()->GetMenuController(); 510 ASSERT_TRUE(controller); 511 EXPECT_TRUE(controller->IsCancelAllTimerRunningForTest()); 512 513 Done(); 514} 515 516// Test that if a menu is opened for a drop handled entirely by menu code, the 517// menu will try to close if it does not receive any drag updates. 518VIEW_TEST(MenuViewDragAndDropForDropCancel, MenuViewCancelsForOwnDrag) 519