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