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 <map>
6#include <vector>
7
8// Include views_test_base.h first because the definition of None in X.h
9// conflicts with the definition of None in gtest-type-util.h
10#include "ui/views/test/views_test_base.h"
11
12#include "base/memory/scoped_ptr.h"
13#include "base/run_loop.h"
14#include "base/strings/utf_string_conversions.h"
15#include "ui/aura/window.h"
16#include "ui/aura/window_tree_host.h"
17#include "ui/base/dragdrop/os_exchange_data.h"
18#include "ui/base/x/x11_util.h"
19#include "ui/gfx/x/x11_atom_cache.h"
20#include "ui/gfx/x/x11_types.h"
21#include "ui/views/widget/desktop_aura/desktop_cursor_loader_updater.h"
22#include "ui/views/widget/desktop_aura/desktop_drag_drop_client_aurax11.h"
23#include "ui/views/widget/desktop_aura/desktop_native_cursor_manager.h"
24#include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h"
25#include "ui/views/widget/desktop_aura/x11_move_loop.h"
26#include "ui/views/widget/widget.h"
27
28#include <X11/Xlib.h>
29
30namespace views {
31
32namespace {
33
34const char* kAtomsToCache[] = {
35  "XdndActionCopy",
36  "XdndDrop",
37  "XdndEnter",
38  "XdndFinished",
39  "XdndLeave",
40  "XdndPosition",
41  "XdndStatus",
42  "XdndTypeList",
43  NULL
44};
45
46class TestDragDropClient;
47
48// Collects messages which would otherwise be sent to |xid_| via
49// SendXClientEvent().
50class ClientMessageEventCollector {
51 public:
52  ClientMessageEventCollector(::Window xid, TestDragDropClient* client);
53  virtual ~ClientMessageEventCollector();
54
55  // Returns true if |events_| is non-empty.
56  bool HasEvents() const {
57    return !events_.empty();
58  }
59
60  // Pops all of |events_| and returns the popped events in the order that they
61  // were on the stack
62  std::vector<XClientMessageEvent> PopAllEvents();
63
64  // Adds |event| to the stack.
65  void RecordEvent(const XClientMessageEvent& event);
66
67 private:
68  ::Window xid_;
69
70  // Not owned.
71  TestDragDropClient* client_;
72
73  std::vector<XClientMessageEvent> events_;
74
75  DISALLOW_COPY_AND_ASSIGN(ClientMessageEventCollector);
76};
77
78// An implementation of X11MoveLoop where RunMoveLoop() always starts the move
79// loop.
80class TestMoveLoop : public X11MoveLoop {
81 public:
82  explicit TestMoveLoop(X11MoveLoopDelegate* delegate);
83  virtual ~TestMoveLoop();
84
85  // Returns true if the move loop is running.
86  bool IsRunning() const;
87
88  // X11MoveLoop:
89  virtual bool RunMoveLoop(aura::Window* window,
90                           gfx::NativeCursor cursor) OVERRIDE;
91  virtual void UpdateCursor(gfx::NativeCursor cursor) OVERRIDE;
92  virtual void EndMoveLoop() OVERRIDE;
93
94 private:
95  // Not owned.
96  X11MoveLoopDelegate* delegate_;
97
98  // Ends the move loop.
99  base::Closure quit_closure_;
100
101  bool is_running_;
102};
103
104// Implementation of DesktopDragDropClientAuraX11 which works with a fake
105// |DesktopDragDropClientAuraX11::source_current_window_|.
106class TestDragDropClient : public DesktopDragDropClientAuraX11 {
107 public:
108  // The location in screen coordinates used for the synthetic mouse moves
109  // generated in SetTopmostXWindowAndMoveMouse().
110  static const int kMouseMoveX;
111  static const int kMouseMoveY;
112
113  TestDragDropClient(aura::Window* window,
114                     DesktopNativeCursorManager* cursor_manager);
115  virtual ~TestDragDropClient();
116
117  // Returns the XID of the window which initiated the drag.
118  ::Window source_xwindow() {
119    return source_xid_;
120  }
121
122  // Returns the Atom with |name|.
123  Atom GetAtom(const char* name);
124
125  // Returns true if the event's message has |type|.
126  bool MessageHasType(const XClientMessageEvent& event,
127                      const char* type);
128
129  // Sets |collector| to collect XClientMessageEvents which would otherwise
130  // have been sent to the drop target window.
131  void SetEventCollectorFor(::Window xid,
132                            ClientMessageEventCollector* collector);
133
134  // Builds an XdndStatus message and sends it to
135  // DesktopDragDropClientAuraX11.
136  void OnStatus(XID target_window,
137                bool will_accept_drop,
138                ::Atom accepted_action);
139
140  // Builds an XdndFinished message and sends it to
141  // DesktopDragDropClientAuraX11.
142  void OnFinished(XID target_window,
143                  bool accepted_drop,
144                  ::Atom performed_action);
145
146  // Sets |xid| as the topmost window at the current mouse position and
147  // generates a synthetic mouse move.
148  void SetTopmostXWindowAndMoveMouse(::Window xid);
149
150  // Returns true if the move loop is running.
151  bool IsMoveLoopRunning();
152
153 private:
154  // DesktopDragDropClientAuraX11:
155  virtual scoped_ptr<X11MoveLoop> CreateMoveLoop(
156      X11MoveLoopDelegate* delegate) OVERRIDE;
157  virtual ::Window FindWindowFor(const gfx::Point& screen_point) OVERRIDE;
158  virtual void SendXClientEvent(::Window xid, XEvent* event) OVERRIDE;
159
160  // The XID of the window which initiated the drag.
161  ::Window source_xid_;
162
163  // The XID of the window which is simulated to be the topmost window at the
164  // current mouse position.
165  ::Window target_xid_;
166
167  // The move loop. Not owned.
168  TestMoveLoop* loop_;
169
170  // Map of ::Windows to the collector which intercepts XClientMessageEvents
171  // for that window.
172  std::map< ::Window, ClientMessageEventCollector*> collectors_;
173
174  ui::X11AtomCache atom_cache_;
175
176  DISALLOW_COPY_AND_ASSIGN(TestDragDropClient);
177};
178
179///////////////////////////////////////////////////////////////////////////////
180// ClientMessageEventCollector
181
182ClientMessageEventCollector::ClientMessageEventCollector(
183    ::Window xid,
184    TestDragDropClient* client)
185    : xid_(xid),
186      client_(client) {
187  client->SetEventCollectorFor(xid, this);
188}
189
190ClientMessageEventCollector::~ClientMessageEventCollector() {
191  client_->SetEventCollectorFor(xid_, NULL);
192}
193
194std::vector<XClientMessageEvent> ClientMessageEventCollector::PopAllEvents() {
195  std::vector<XClientMessageEvent> to_return;
196  to_return.swap(events_);
197  return to_return;
198}
199
200void ClientMessageEventCollector::RecordEvent(
201    const XClientMessageEvent& event) {
202  events_.push_back(event);
203}
204
205///////////////////////////////////////////////////////////////////////////////
206// TestMoveLoop
207
208TestMoveLoop::TestMoveLoop(X11MoveLoopDelegate* delegate)
209    : delegate_(delegate),
210      is_running_(false) {
211}
212
213TestMoveLoop::~TestMoveLoop() {
214}
215
216bool TestMoveLoop::IsRunning() const {
217  return is_running_;
218}
219
220bool TestMoveLoop::RunMoveLoop(
221    aura::Window* window,
222    gfx::NativeCursor cursor) {
223  is_running_ = true;
224  base::RunLoop run_loop;
225  quit_closure_ = run_loop.QuitClosure();
226  run_loop.Run();
227  return true;
228}
229
230void TestMoveLoop::UpdateCursor(gfx::NativeCursor cursor) {
231}
232
233void TestMoveLoop::EndMoveLoop() {
234  if (is_running_) {
235    delegate_->OnMoveLoopEnded();
236    is_running_ = false;
237    quit_closure_.Run();
238  }
239}
240
241///////////////////////////////////////////////////////////////////////////////
242// TestDragDropClient
243
244// static
245const int TestDragDropClient::kMouseMoveX = 100;
246
247// static
248const int TestDragDropClient::kMouseMoveY = 200;
249
250TestDragDropClient::TestDragDropClient(
251    aura::Window* window,
252    DesktopNativeCursorManager* cursor_manager)
253    : DesktopDragDropClientAuraX11(window,
254                                   cursor_manager,
255                                   gfx::GetXDisplay(),
256                                   window->GetHost()->GetAcceleratedWidget()),
257      source_xid_(window->GetHost()->GetAcceleratedWidget()),
258      target_xid_(None),
259      loop_(NULL),
260      atom_cache_(gfx::GetXDisplay(), kAtomsToCache) {
261}
262
263TestDragDropClient::~TestDragDropClient() {
264}
265
266Atom TestDragDropClient::GetAtom(const char* name) {
267  return atom_cache_.GetAtom(name);
268}
269
270bool TestDragDropClient::MessageHasType(const XClientMessageEvent& event,
271                                        const char* type) {
272  return event.message_type == atom_cache_.GetAtom(type);
273}
274
275void TestDragDropClient::SetEventCollectorFor(
276    ::Window xid,
277    ClientMessageEventCollector* collector) {
278  if (collector)
279    collectors_[xid] = collector;
280  else
281    collectors_.erase(xid);
282}
283
284void TestDragDropClient::OnStatus(XID target_window,
285                                  bool will_accept_drop,
286                                  ::Atom accepted_action) {
287  XClientMessageEvent event;
288  event.message_type = atom_cache_.GetAtom("XdndStatus");
289  event.format = 32;
290  event.window = source_xid_;
291  event.data.l[0] = target_window;
292  event.data.l[1] = will_accept_drop ? 1 : 0;
293  event.data.l[2] = 0;
294  event.data.l[3] = 0;
295  event.data.l[4] = accepted_action;
296  OnXdndStatus(event);
297}
298
299void TestDragDropClient::OnFinished(XID target_window,
300                                    bool accepted_drop,
301                                    ::Atom performed_action) {
302  XClientMessageEvent event;
303  event.message_type = atom_cache_.GetAtom("XdndFinished");
304  event.format = 32;
305  event.window = source_xid_;
306  event.data.l[0] = target_window;
307  event.data.l[1] = accepted_drop ? 1 : 0;
308  event.data.l[2] = performed_action;
309  event.data.l[3] = 0;
310  event.data.l[4] = 0;
311  OnXdndFinished(event);
312}
313
314void TestDragDropClient::SetTopmostXWindowAndMoveMouse(::Window xid) {
315  target_xid_ = xid;
316
317  XMotionEvent event;
318  event.time = CurrentTime;
319  event.x_root = kMouseMoveX;
320  event.y_root = kMouseMoveY;
321  OnMouseMovement(&event);
322}
323
324bool TestDragDropClient::IsMoveLoopRunning() {
325  return loop_->IsRunning();
326}
327
328scoped_ptr<X11MoveLoop> TestDragDropClient::CreateMoveLoop(
329    X11MoveLoopDelegate* delegate) {
330  loop_ = new TestMoveLoop(delegate);
331  return scoped_ptr<X11MoveLoop>(loop_);
332}
333
334::Window TestDragDropClient::FindWindowFor(const gfx::Point& screen_point) {
335  return target_xid_;
336}
337
338void TestDragDropClient::SendXClientEvent(::Window xid, XEvent* event) {
339  std::map< ::Window, ClientMessageEventCollector*>::iterator it =
340      collectors_.find(xid);
341  if (it != collectors_.end())
342    it->second->RecordEvent(event->xclient);
343}
344
345}  // namespace
346
347class DesktopDragDropClientAuraX11Test : public ViewsTestBase {
348 public:
349  DesktopDragDropClientAuraX11Test() {
350  }
351
352  virtual ~DesktopDragDropClientAuraX11Test() {
353  }
354
355  int StartDragAndDrop() {
356    ui::OSExchangeData data;
357    data.SetString(base::ASCIIToUTF16("Test"));
358
359    return client_->StartDragAndDrop(
360        data,
361        widget_->GetNativeWindow()->GetRootWindow(),
362        widget_->GetNativeWindow(),
363        gfx::Point(),
364        ui::DragDropTypes::DRAG_COPY,
365        ui::DragDropTypes::DRAG_EVENT_SOURCE_MOUSE);
366  }
367
368  // ViewsTestBase:
369  virtual void SetUp() OVERRIDE {
370    ViewsTestBase::SetUp();
371
372    // Create widget to initiate the drags.
373    widget_.reset(new Widget);
374    Widget::InitParams params(Widget::InitParams::TYPE_WINDOW);
375    params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
376    params.native_widget = new DesktopNativeWidgetAura(widget_.get());
377    params.bounds = gfx::Rect(100, 100);
378    widget_->Init(params);
379    widget_->Show();
380
381    cursor_manager_.reset(new DesktopNativeCursorManager(
382        DesktopCursorLoaderUpdater::Create()));
383
384    client_.reset(new TestDragDropClient(widget_->GetNativeWindow(),
385                                         cursor_manager_.get()));
386    client_->Init();
387  }
388
389  virtual void TearDown() OVERRIDE {
390    client_.reset();
391    cursor_manager_.reset();
392    widget_.reset();
393    ViewsTestBase::TearDown();
394  }
395
396  TestDragDropClient* client() {
397    return client_.get();
398  }
399
400 private:
401  scoped_ptr<TestDragDropClient> client_;
402  scoped_ptr<DesktopNativeCursorManager> cursor_manager_;
403
404  // The widget used to initiate drags.
405  scoped_ptr<Widget> widget_;
406
407  DISALLOW_COPY_AND_ASSIGN(DesktopDragDropClientAuraX11Test);
408};
409
410namespace {
411
412void BasicStep2(TestDragDropClient* client, XID toplevel) {
413  EXPECT_TRUE(client->IsMoveLoopRunning());
414
415  ClientMessageEventCollector collector(toplevel, client);
416  client->SetTopmostXWindowAndMoveMouse(toplevel);
417
418  // XdndEnter should have been sent to |toplevel| before the XdndPosition
419  // message.
420  std::vector<XClientMessageEvent> events = collector.PopAllEvents();
421  ASSERT_EQ(2u, events.size());
422
423  EXPECT_TRUE(client->MessageHasType(events[0], "XdndEnter"));
424  EXPECT_EQ(client->source_xwindow(),
425            static_cast<XID>(events[0].data.l[0]));
426  EXPECT_EQ(1, events[0].data.l[1] & 1);
427  std::vector<Atom> targets;
428  ui::GetAtomArrayProperty(client->source_xwindow(), "XdndTypeList", &targets);
429  EXPECT_FALSE(targets.empty());
430
431  EXPECT_TRUE(client->MessageHasType(events[1], "XdndPosition"));
432  EXPECT_EQ(client->source_xwindow(),
433            static_cast<XID>(events[0].data.l[0]));
434  const long kCoords =
435      TestDragDropClient::kMouseMoveX << 16 | TestDragDropClient::kMouseMoveY;
436  EXPECT_EQ(kCoords, events[1].data.l[2]);
437  EXPECT_EQ(client->GetAtom("XdndActionCopy"),
438            static_cast<Atom>(events[1].data.l[4]));
439
440  client->OnStatus(toplevel, true, client->GetAtom("XdndActionCopy"));
441
442  // Because there is no unprocessed XdndPosition, the drag drop client should
443  // send XdndDrop immediately after the mouse is released.
444  client->OnMouseReleased();
445
446  events = collector.PopAllEvents();
447  ASSERT_EQ(1u, events.size());
448  EXPECT_TRUE(client->MessageHasType(events[0], "XdndDrop"));
449  EXPECT_EQ(client->source_xwindow(),
450            static_cast<XID>(events[0].data.l[0]));
451
452  // Send XdndFinished to indicate that the drag drop client can cleanup any
453  // data related to this drag. The move loop should end only after the
454  // XdndFinished message was received.
455  EXPECT_TRUE(client->IsMoveLoopRunning());
456  client->OnFinished(toplevel, true, client->GetAtom("XdndActionCopy"));
457  EXPECT_FALSE(client->IsMoveLoopRunning());
458}
459
460void BasicStep3(TestDragDropClient* client, XID toplevel) {
461  EXPECT_TRUE(client->IsMoveLoopRunning());
462
463  ClientMessageEventCollector collector(toplevel, client);
464  client->SetTopmostXWindowAndMoveMouse(toplevel);
465
466  std::vector<XClientMessageEvent> events = collector.PopAllEvents();
467  ASSERT_EQ(2u, events.size());
468  EXPECT_TRUE(client->MessageHasType(events[0], "XdndEnter"));
469  EXPECT_TRUE(client->MessageHasType(events[1], "XdndPosition"));
470
471  client->OnStatus(toplevel, true, client->GetAtom("XdndActionCopy"));
472  client->SetTopmostXWindowAndMoveMouse(toplevel);
473  events = collector.PopAllEvents();
474  ASSERT_EQ(1u, events.size());
475  EXPECT_TRUE(client->MessageHasType(events[0], "XdndPosition"));
476
477  // We have not received an XdndStatus ack for the second XdndPosition message.
478  // Test that sending XdndDrop is delayed till the XdndStatus ack is received.
479  client->OnMouseReleased();
480  EXPECT_FALSE(collector.HasEvents());
481
482  client->OnStatus(toplevel, true, client->GetAtom("XdndActionCopy"));
483  events = collector.PopAllEvents();
484  ASSERT_EQ(1u, events.size());
485  EXPECT_TRUE(client->MessageHasType(events[0], "XdndDrop"));
486
487  EXPECT_TRUE(client->IsMoveLoopRunning());
488  client->OnFinished(toplevel, true, client->GetAtom("XdndActionCopy"));
489  EXPECT_FALSE(client->IsMoveLoopRunning());
490}
491
492}  // namespace
493
494TEST_F(DesktopDragDropClientAuraX11Test, Basic) {
495  XID toplevel = 1;
496
497  base::MessageLoop::current()->PostTask(FROM_HERE,
498                                         base::Bind(&BasicStep2,
499                                         client(),
500                                         toplevel));
501  int result = StartDragAndDrop();
502  EXPECT_EQ(ui::DragDropTypes::DRAG_COPY, result);
503
504  // Do another drag and drop to test that the data is properly cleaned up as a
505  // result of the XdndFinished message.
506  base::MessageLoop::current()->PostTask(FROM_HERE,
507                                         base::Bind(&BasicStep3,
508                                         client(),
509                                         toplevel));
510  result = StartDragAndDrop();
511  EXPECT_EQ(ui::DragDropTypes::DRAG_COPY, result);
512}
513
514namespace {
515
516void TargetDoesNotRespondStep2(TestDragDropClient* client) {
517  EXPECT_TRUE(client->IsMoveLoopRunning());
518
519  XID toplevel = 1;
520  ClientMessageEventCollector collector(toplevel, client);
521  client->SetTopmostXWindowAndMoveMouse(toplevel);
522
523  std::vector<XClientMessageEvent> events = collector.PopAllEvents();
524  ASSERT_EQ(2u, events.size());
525  EXPECT_TRUE(client->MessageHasType(events[0], "XdndEnter"));
526  EXPECT_TRUE(client->MessageHasType(events[1], "XdndPosition"));
527
528  client->OnMouseReleased();
529  events = collector.PopAllEvents();
530  ASSERT_EQ(1u, events.size());
531  EXPECT_TRUE(client->MessageHasType(events[0], "XdndLeave"));
532  EXPECT_FALSE(client->IsMoveLoopRunning());
533}
534
535}  // namespace
536
537// Test that we do not wait for the target to send XdndStatus if we have not
538// received any XdndStatus messages at all from the target. The Unity
539// DNDCollectionWindow is an example of an XdndAware target which does not
540// respond to XdndPosition messages at all.
541TEST_F(DesktopDragDropClientAuraX11Test, TargetDoesNotRespond) {
542  base::MessageLoop::current()->PostTask(
543      FROM_HERE,
544      base::Bind(&TargetDoesNotRespondStep2, client()));
545  int result = StartDragAndDrop();
546  EXPECT_EQ(ui::DragDropTypes::DRAG_NONE, result);
547}
548
549namespace {
550
551void QueuePositionStep2(TestDragDropClient* client) {
552  EXPECT_TRUE(client->IsMoveLoopRunning());
553
554  XID toplevel = 1;
555  ClientMessageEventCollector collector(toplevel, client);
556  client->SetTopmostXWindowAndMoveMouse(toplevel);
557  client->SetTopmostXWindowAndMoveMouse(toplevel);
558  client->SetTopmostXWindowAndMoveMouse(toplevel);
559
560  std::vector<XClientMessageEvent> events = collector.PopAllEvents();
561  ASSERT_EQ(2u, events.size());
562  EXPECT_TRUE(client->MessageHasType(events[0], "XdndEnter"));
563  EXPECT_TRUE(client->MessageHasType(events[1], "XdndPosition"));
564
565  client->OnStatus(toplevel, true, client->GetAtom("XdndActionCopy"));
566  events = collector.PopAllEvents();
567  ASSERT_EQ(1u, events.size());
568  EXPECT_TRUE(client->MessageHasType(events[0], "XdndPosition"));
569
570  client->OnStatus(toplevel, true, client->GetAtom("XdndActionCopy"));
571  EXPECT_FALSE(collector.HasEvents());
572
573  client->OnMouseReleased();
574  events = collector.PopAllEvents();
575  ASSERT_EQ(1u, events.size());
576  EXPECT_TRUE(client->MessageHasType(events[0], "XdndDrop"));
577
578  EXPECT_TRUE(client->IsMoveLoopRunning());
579  client->OnFinished(toplevel, true, client->GetAtom("XdndActionCopy"));
580  EXPECT_FALSE(client->IsMoveLoopRunning());
581}
582
583}  // namespace
584
585// Test that XdndPosition messages are queued till the pending XdndPosition
586// message is acked via an XdndStatus message.
587TEST_F(DesktopDragDropClientAuraX11Test, QueuePosition) {
588  base::MessageLoop::current()->PostTask(
589      FROM_HERE,
590      base::Bind(&QueuePositionStep2, client()));
591  int result = StartDragAndDrop();
592  EXPECT_EQ(ui::DragDropTypes::DRAG_COPY, result);
593}
594
595namespace {
596
597void TargetChangesStep2(TestDragDropClient* client) {
598  EXPECT_TRUE(client->IsMoveLoopRunning());
599
600  XID toplevel1 = 1;
601  ClientMessageEventCollector collector1(toplevel1, client);
602  client->SetTopmostXWindowAndMoveMouse(toplevel1);
603
604  std::vector<XClientMessageEvent> events1 = collector1.PopAllEvents();
605  ASSERT_EQ(2u, events1.size());
606  EXPECT_TRUE(client->MessageHasType(events1[0], "XdndEnter"));
607  EXPECT_TRUE(client->MessageHasType(events1[1], "XdndPosition"));
608
609  XID toplevel2 = 2;
610  ClientMessageEventCollector collector2(toplevel2, client);
611  client->SetTopmostXWindowAndMoveMouse(toplevel2);
612
613  // It is possible for |toplevel1| to send XdndStatus after the source has sent
614  // XdndLeave but before |toplevel1| has received the XdndLeave message. The
615  // XdndStatus message should be ignored.
616  client->OnStatus(toplevel1, true, client->GetAtom("XdndActionCopy"));
617  events1 = collector1.PopAllEvents();
618  ASSERT_EQ(1u, events1.size());
619  EXPECT_TRUE(client->MessageHasType(events1[0], "XdndLeave"));
620
621  std::vector<XClientMessageEvent> events2 = collector2.PopAllEvents();
622  ASSERT_EQ(2u, events2.size());
623  EXPECT_TRUE(client->MessageHasType(events2[0], "XdndEnter"));
624  EXPECT_TRUE(client->MessageHasType(events2[1], "XdndPosition"));
625
626  client->OnStatus(toplevel2, true, client->GetAtom("XdndActionCopy"));
627  client->OnMouseReleased();
628  events2 = collector2.PopAllEvents();
629  ASSERT_EQ(1u, events2.size());
630  EXPECT_TRUE(client->MessageHasType(events2[0], "XdndDrop"));
631
632  EXPECT_TRUE(client->IsMoveLoopRunning());
633  client->OnFinished(toplevel2, true, client->GetAtom("XdndActionCopy"));
634  EXPECT_FALSE(client->IsMoveLoopRunning());
635}
636
637}  // namespace
638
639// Test the behavior when the target changes during a drag.
640TEST_F(DesktopDragDropClientAuraX11Test, TargetChanges) {
641  base::MessageLoop::current()->PostTask(
642      FROM_HERE,
643      base::Bind(&TargetChangesStep2, client()));
644  int result = StartDragAndDrop();
645  EXPECT_EQ(ui::DragDropTypes::DRAG_COPY, result);
646}
647
648namespace {
649
650void RejectAfterMouseReleaseStep2(TestDragDropClient* client) {
651  EXPECT_TRUE(client->IsMoveLoopRunning());
652
653  XID toplevel = 1;
654  ClientMessageEventCollector collector(toplevel, client);
655  client->SetTopmostXWindowAndMoveMouse(toplevel);
656
657  std::vector<XClientMessageEvent> events = collector.PopAllEvents();
658  ASSERT_EQ(2u, events.size());
659  EXPECT_TRUE(client->MessageHasType(events[0], "XdndEnter"));
660  EXPECT_TRUE(client->MessageHasType(events[1], "XdndPosition"));
661
662  client->OnStatus(toplevel, true, client->GetAtom("XdndActionCopy"));
663  EXPECT_FALSE(collector.HasEvents());
664
665  // Send another mouse move such that there is a pending XdndPosition.
666  client->SetTopmostXWindowAndMoveMouse(toplevel);
667  events = collector.PopAllEvents();
668  ASSERT_EQ(1u, events.size());
669  EXPECT_TRUE(client->MessageHasType(events[0], "XdndPosition"));
670
671  client->OnMouseReleased();
672  // Reject the drop.
673  client->OnStatus(toplevel, false, None);
674
675  events = collector.PopAllEvents();
676  ASSERT_EQ(1u, events.size());
677  EXPECT_TRUE(client->MessageHasType(events[0], "XdndLeave"));
678  EXPECT_FALSE(client->IsMoveLoopRunning());
679}
680
681void RejectAfterMouseReleaseStep3(TestDragDropClient* client) {
682  EXPECT_TRUE(client->IsMoveLoopRunning());
683
684  XID toplevel = 2;
685  ClientMessageEventCollector collector(toplevel, client);
686  client->SetTopmostXWindowAndMoveMouse(toplevel);
687
688  std::vector<XClientMessageEvent> events = collector.PopAllEvents();
689  ASSERT_EQ(2u, events.size());
690  EXPECT_TRUE(client->MessageHasType(events[0], "XdndEnter"));
691  EXPECT_TRUE(client->MessageHasType(events[1], "XdndPosition"));
692
693  client->OnStatus(toplevel, true, client->GetAtom("XdndActionCopy"));
694  EXPECT_FALSE(collector.HasEvents());
695
696  client->OnMouseReleased();
697  events = collector.PopAllEvents();
698  ASSERT_EQ(1u, events.size());
699  EXPECT_TRUE(client->MessageHasType(events[0], "XdndDrop"));
700
701  EXPECT_TRUE(client->IsMoveLoopRunning());
702  client->OnFinished(toplevel, false, None);
703  EXPECT_FALSE(client->IsMoveLoopRunning());
704}
705
706}  // namespace
707
708// Test that the source sends XdndLeave instead of XdndDrop if the drag
709// operation is rejected after the mouse is released.
710TEST_F(DesktopDragDropClientAuraX11Test, RejectAfterMouseRelease) {
711  base::MessageLoop::current()->PostTask(
712      FROM_HERE,
713      base::Bind(&RejectAfterMouseReleaseStep2, client()));
714  int result = StartDragAndDrop();
715  EXPECT_EQ(ui::DragDropTypes::DRAG_NONE, result);
716
717  // Repeat the test but reject the drop in the XdndFinished message instead.
718  base::MessageLoop::current()->PostTask(
719      FROM_HERE,
720      base::Bind(&RejectAfterMouseReleaseStep3, client()));
721  result = StartDragAndDrop();
722  EXPECT_EQ(ui::DragDropTypes::DRAG_NONE, result);
723}
724
725}  // namespace views
726