blob: 4286d1873a51805d09053f9c4e900e3213a46332 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <map>
#include <vector>
// Include views_test_base.h first because the definition of None in X.h
// conflicts with the definition of None in gtest-type-util.h
#include "ui/views/test/views_test_base.h"
#include "base/memory/scoped_ptr.h"
#include "base/strings/utf_string_conversions.h"
#include "ui/aura/window.h"
#include "ui/aura/window_tree_host.h"
#include "ui/base/dragdrop/os_exchange_data.h"
#include "ui/base/x/x11_util.h"
#include "ui/gfx/x/x11_atom_cache.h"
#include "ui/gfx/x/x11_types.h"
#include "ui/views/widget/desktop_aura/desktop_cursor_loader_updater.h"
#include "ui/views/widget/desktop_aura/desktop_drag_drop_client_aurax11.h"
#include "ui/views/widget/desktop_aura/desktop_native_cursor_manager.h"
#include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h"
#include "ui/views/widget/widget.h"
#include <X11/Xlib.h>
namespace views {
namespace {
const char* kAtomsToCache[] = {
"XdndActionCopy",
"XdndDrop",
"XdndEnter",
"XdndFinished",
"XdndLeave",
"XdndPosition",
"XdndStatus",
"XdndTypeList",
NULL
};
class TestDragDropClient;
// Collects messages which would otherwise be sent to |xid_| via
// SendXClientEvent().
class ClientMessageEventCollector {
public:
ClientMessageEventCollector(::Window xid, TestDragDropClient* client);
virtual ~ClientMessageEventCollector();
// Returns true if |events_| is non-empty.
bool HasEvents() const {
return !events_.empty();
}
// Pops all of |events_| and returns the popped events in the order that they
// were on the stack
std::vector<XClientMessageEvent> PopAllEvents();
// Adds |event| to the stack.
void RecordEvent(const XClientMessageEvent& event);
private:
::Window xid_;
// Not owned.
TestDragDropClient* client_;
std::vector<XClientMessageEvent> events_;
DISALLOW_COPY_AND_ASSIGN(ClientMessageEventCollector);
};
// Implementation of DesktopDragDropClientAuraX11 which works with a fake
// |DesktopDragDropClientAuraX11::source_current_window_|.
class TestDragDropClient : public DesktopDragDropClientAuraX11 {
public:
// The location in screen coordinates used for the synthetic mouse moves
// generated in SetTopmostXWindowAndMoveMouse().
static const int kMouseMoveX;
static const int kMouseMoveY;
TestDragDropClient(aura::Window* window,
DesktopNativeCursorManager* cursor_manager);
virtual ~TestDragDropClient();
// Returns the XID of the window which initiated the drag.
::Window source_xwindow() {
return source_xid_;
}
// Returns the Atom with |name|.
Atom GetAtom(const char* name);
// Returns true if the event's message has |type|.
bool MessageHasType(const XClientMessageEvent& event,
const char* type);
// Sets |collector| to collect XClientMessageEvents which would otherwise
// have been sent to the drop target window.
void SetEventCollectorFor(::Window xid,
ClientMessageEventCollector* collector);
// Builds an XdndStatus message and sends it to
// DesktopDragDropClientAuraX11.
void OnStatus(XID target_window,
bool will_accept_drop,
::Atom accepted_action);
// Builds an XdndFinished message and sends it to
// DesktopDragDropClientAuraX11.
void OnFinished(XID target_window,
bool accepted_drop,
::Atom performed_action);
// Sets |xid| as the topmost window at the current mouse position and
// generates a synthetic mouse move.
void SetTopmostXWindowAndMoveMouse(::Window xid);
// Returns true if the move loop is running.
bool IsMoveLoopRunning();
// DesktopDragDropClientAuraX11:
virtual int StartDragAndDrop(
const ui::OSExchangeData& data,
aura::Window* root_window,
aura::Window* source_window,
const gfx::Point& root_location,
int operation,
ui::DragDropTypes::DragEventSource source) OVERRIDE;
virtual void OnMoveLoopEnded() OVERRIDE;
private:
// DesktopDragDropClientAuraX11:
virtual ::Window FindWindowFor(const gfx::Point& screen_point) OVERRIDE;
virtual void SendXClientEvent(::Window xid, XEvent* event) OVERRIDE;
// The XID of the window which initiated the drag.
::Window source_xid_;
// The XID of the window which is simulated to be the topmost window at the
// current mouse position.
::Window target_xid_;
// Whether the move loop is running.
bool move_loop_running_;
// Map of ::Windows to the collector which intercepts XClientMessageEvents
// for that window.
std::map< ::Window, ClientMessageEventCollector*> collectors_;
ui::X11AtomCache atom_cache_;
DISALLOW_COPY_AND_ASSIGN(TestDragDropClient);
};
///////////////////////////////////////////////////////////////////////////////
// ClientMessageEventCollector
ClientMessageEventCollector::ClientMessageEventCollector(
::Window xid,
TestDragDropClient* client)
: xid_(xid),
client_(client) {
client->SetEventCollectorFor(xid, this);
}
ClientMessageEventCollector::~ClientMessageEventCollector() {
client_->SetEventCollectorFor(xid_, NULL);
}
std::vector<XClientMessageEvent> ClientMessageEventCollector::PopAllEvents() {
std::vector<XClientMessageEvent> to_return;
to_return.swap(events_);
return to_return;
}
void ClientMessageEventCollector::RecordEvent(
const XClientMessageEvent& event) {
events_.push_back(event);
}
///////////////////////////////////////////////////////////////////////////////
// TestDragDropClient
// static
const int TestDragDropClient::kMouseMoveX = 100;
// static
const int TestDragDropClient::kMouseMoveY = 200;
TestDragDropClient::TestDragDropClient(
aura::Window* window,
DesktopNativeCursorManager* cursor_manager)
: DesktopDragDropClientAuraX11(window,
cursor_manager,
gfx::GetXDisplay(),
window->GetHost()->GetAcceleratedWidget()),
source_xid_(window->GetHost()->GetAcceleratedWidget()),
target_xid_(None),
move_loop_running_(false),
atom_cache_(gfx::GetXDisplay(), kAtomsToCache) {
}
TestDragDropClient::~TestDragDropClient() {
}
Atom TestDragDropClient::GetAtom(const char* name) {
return atom_cache_.GetAtom(name);
}
bool TestDragDropClient::MessageHasType(const XClientMessageEvent& event,
const char* type) {
return event.message_type == atom_cache_.GetAtom(type);
}
void TestDragDropClient::SetEventCollectorFor(
::Window xid,
ClientMessageEventCollector* collector) {
if (collector)
collectors_[xid] = collector;
else
collectors_.erase(xid);
}
void TestDragDropClient::OnStatus(XID target_window,
bool will_accept_drop,
::Atom accepted_action) {
XClientMessageEvent event;
event.message_type = atom_cache_.GetAtom("XdndStatus");
event.format = 32;
event.window = source_xid_;
event.data.l[0] = target_window;
event.data.l[1] = will_accept_drop ? 1 : 0;
event.data.l[2] = 0;
event.data.l[3] = 0;
event.data.l[4] = accepted_action;
OnXdndStatus(event);
}
void TestDragDropClient::OnFinished(XID target_window,
bool accepted_drop,
::Atom performed_action) {
XClientMessageEvent event;
event.message_type = atom_cache_.GetAtom("XdndFinished");
event.format = 32;
event.window = source_xid_;
event.data.l[0] = target_window;
event.data.l[1] = accepted_drop ? 1 : 0;
event.data.l[2] = performed_action;
event.data.l[3] = 0;
event.data.l[4] = 0;
OnXdndFinished(event);
}
void TestDragDropClient::SetTopmostXWindowAndMoveMouse(::Window xid) {
target_xid_ = xid;
XMotionEvent event;
event.time = CurrentTime;
event.x_root = kMouseMoveX;
event.y_root = kMouseMoveY;
OnMouseMovement(&event);
}
bool TestDragDropClient::IsMoveLoopRunning() {
return move_loop_running_;
}
int TestDragDropClient::StartDragAndDrop(
const ui::OSExchangeData& data,
aura::Window* root_window,
aura::Window* source_window,
const gfx::Point& root_location,
int operation,
ui::DragDropTypes::DragEventSource source) {
move_loop_running_ = true;
return DesktopDragDropClientAuraX11::StartDragAndDrop(data, root_window,
source_window, root_location, operation, source);
}
void TestDragDropClient::OnMoveLoopEnded() {
DesktopDragDropClientAuraX11::OnMoveLoopEnded();
move_loop_running_ = false;
}
::Window TestDragDropClient::FindWindowFor(const gfx::Point& screen_point) {
return target_xid_;
}
void TestDragDropClient::SendXClientEvent(::Window xid, XEvent* event) {
std::map< ::Window, ClientMessageEventCollector*>::iterator it =
collectors_.find(xid);
if (it != collectors_.end())
it->second->RecordEvent(event->xclient);
}
} // namespace
class DesktopDragDropClientAuraX11Test : public ViewsTestBase {
public:
DesktopDragDropClientAuraX11Test() {
}
virtual ~DesktopDragDropClientAuraX11Test() {
}
int StartDragAndDrop() {
ui::OSExchangeData data;
data.SetString(base::ASCIIToUTF16("Test"));
return client_->StartDragAndDrop(
data,
widget_->GetNativeWindow()->GetRootWindow(),
widget_->GetNativeWindow(),
gfx::Point(),
ui::DragDropTypes::DRAG_COPY,
ui::DragDropTypes::DRAG_EVENT_SOURCE_MOUSE);
}
// ViewsTestBase:
virtual void SetUp() OVERRIDE {
ViewsTestBase::SetUp();
// Create widget to initiate the drags.
widget_.reset(new Widget);
Widget::InitParams params(Widget::InitParams::TYPE_WINDOW);
params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
params.native_widget = new DesktopNativeWidgetAura(widget_.get());
params.bounds = gfx::Rect(100, 100);
widget_->Init(params);
widget_->Show();
cursor_manager_.reset(new DesktopNativeCursorManager(
DesktopCursorLoaderUpdater::Create()));
client_.reset(new TestDragDropClient(widget_->GetNativeWindow(),
cursor_manager_.get()));
}
virtual void TearDown() OVERRIDE {
client_.reset();
cursor_manager_.reset();
widget_.reset();
ViewsTestBase::TearDown();
}
TestDragDropClient* client() {
return client_.get();
}
private:
scoped_ptr<TestDragDropClient> client_;
scoped_ptr<DesktopNativeCursorManager> cursor_manager_;
// The widget used to initiate drags.
scoped_ptr<Widget> widget_;
DISALLOW_COPY_AND_ASSIGN(DesktopDragDropClientAuraX11Test);
};
namespace {
void BasicStep2(TestDragDropClient* client, XID toplevel) {
EXPECT_TRUE(client->IsMoveLoopRunning());
ClientMessageEventCollector collector(toplevel, client);
client->SetTopmostXWindowAndMoveMouse(toplevel);
// XdndEnter should have been sent to |toplevel| before the XdndPosition
// message.
std::vector<XClientMessageEvent> events = collector.PopAllEvents();
ASSERT_EQ(2u, events.size());
EXPECT_TRUE(client->MessageHasType(events[0], "XdndEnter"));
EXPECT_EQ(client->source_xwindow(),
static_cast<XID>(events[0].data.l[0]));
EXPECT_EQ(1, events[0].data.l[1] & 1);
std::vector<Atom> targets;
ui::GetAtomArrayProperty(client->source_xwindow(), "XdndTypeList", &targets);
EXPECT_FALSE(targets.empty());
EXPECT_TRUE(client->MessageHasType(events[1], "XdndPosition"));
EXPECT_EQ(client->source_xwindow(),
static_cast<XID>(events[0].data.l[0]));
const long kCoords =
TestDragDropClient::kMouseMoveX << 16 | TestDragDropClient::kMouseMoveY;
EXPECT_EQ(kCoords, events[1].data.l[2]);
EXPECT_EQ(client->GetAtom("XdndActionCopy"),
static_cast<Atom>(events[1].data.l[4]));
client->OnStatus(toplevel, true, client->GetAtom("XdndActionCopy"));
// Because there is no unprocessed XdndPosition, the drag drop client should
// send XdndDrop immediately after the mouse is released.
client->OnMouseReleased();
events = collector.PopAllEvents();
ASSERT_EQ(1u, events.size());
EXPECT_TRUE(client->MessageHasType(events[0], "XdndDrop"));
EXPECT_EQ(client->source_xwindow(),
static_cast<XID>(events[0].data.l[0]));
// Send XdndFinished to indicate that the drag drop client can cleanup any
// data related to this drag. The move loop should end only after the
// XdndFinished message was received.
EXPECT_TRUE(client->IsMoveLoopRunning());
client->OnFinished(toplevel, true, client->GetAtom("XdndActionCopy"));
EXPECT_FALSE(client->IsMoveLoopRunning());
}
void BasicStep3(TestDragDropClient* client, XID toplevel) {
EXPECT_TRUE(client->IsMoveLoopRunning());
ClientMessageEventCollector collector(toplevel, client);
client->SetTopmostXWindowAndMoveMouse(toplevel);
std::vector<XClientMessageEvent> events = collector.PopAllEvents();
ASSERT_EQ(2u, events.size());
EXPECT_TRUE(client->MessageHasType(events[0], "XdndEnter"));
EXPECT_TRUE(client->MessageHasType(events[1], "XdndPosition"));
client->OnStatus(toplevel, true, client->GetAtom("XdndActionCopy"));
client->SetTopmostXWindowAndMoveMouse(toplevel);
events = collector.PopAllEvents();
ASSERT_EQ(1u, events.size());
EXPECT_TRUE(client->MessageHasType(events[0], "XdndPosition"));
// We have not received an XdndStatus ack for the second XdndPosition message.
// Test that sending XdndDrop is delayed till the XdndStatus ack is received.
client->OnMouseReleased();
EXPECT_FALSE(collector.HasEvents());
client->OnStatus(toplevel, true, client->GetAtom("XdndActionCopy"));
events = collector.PopAllEvents();
ASSERT_EQ(1u, events.size());
EXPECT_TRUE(client->MessageHasType(events[0], "XdndDrop"));
EXPECT_TRUE(client->IsMoveLoopRunning());
client->OnFinished(toplevel, true, client->GetAtom("XdndActionCopy"));
EXPECT_FALSE(client->IsMoveLoopRunning());
}
} // namespace
TEST_F(DesktopDragDropClientAuraX11Test, Basic) {
XID toplevel = 1;
base::MessageLoop::current()->PostTask(FROM_HERE,
base::Bind(&BasicStep2,
client(),
toplevel));
int result = StartDragAndDrop();
EXPECT_EQ(ui::DragDropTypes::DRAG_COPY, result);
// Do another drag and drop to test that the data is properly cleaned up as a
// result of the XdndFinished message.
base::MessageLoop::current()->PostTask(FROM_HERE,
base::Bind(&BasicStep3,
client(),
toplevel));
result = StartDragAndDrop();
EXPECT_EQ(ui::DragDropTypes::DRAG_COPY, result);
}
namespace {
void TargetDoesNotRespondStep2(TestDragDropClient* client) {
EXPECT_TRUE(client->IsMoveLoopRunning());
XID toplevel = 1;
ClientMessageEventCollector collector(toplevel, client);
client->SetTopmostXWindowAndMoveMouse(toplevel);
std::vector<XClientMessageEvent> events = collector.PopAllEvents();
ASSERT_EQ(2u, events.size());
EXPECT_TRUE(client->MessageHasType(events[0], "XdndEnter"));
EXPECT_TRUE(client->MessageHasType(events[1], "XdndPosition"));
client->OnMouseReleased();
events = collector.PopAllEvents();
ASSERT_EQ(1u, events.size());
EXPECT_TRUE(client->MessageHasType(events[0], "XdndLeave"));
EXPECT_FALSE(client->IsMoveLoopRunning());
}
} // namespace
// Test that we do not wait for the target to send XdndStatus if we have not
// received any XdndStatus messages at all from the target. The Unity
// DNDCollectionWindow is an example of an XdndAware target which does not
// respond to XdndPosition messages at all.
TEST_F(DesktopDragDropClientAuraX11Test, TargetDoesNotRespond) {
base::MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(&TargetDoesNotRespondStep2, client()));
int result = StartDragAndDrop();
EXPECT_EQ(ui::DragDropTypes::DRAG_NONE, result);
}
namespace {
void QueuePositionStep2(TestDragDropClient* client) {
EXPECT_TRUE(client->IsMoveLoopRunning());
XID toplevel = 1;
ClientMessageEventCollector collector(toplevel, client);
client->SetTopmostXWindowAndMoveMouse(toplevel);
client->SetTopmostXWindowAndMoveMouse(toplevel);
client->SetTopmostXWindowAndMoveMouse(toplevel);
std::vector<XClientMessageEvent> events = collector.PopAllEvents();
ASSERT_EQ(2u, events.size());
EXPECT_TRUE(client->MessageHasType(events[0], "XdndEnter"));
EXPECT_TRUE(client->MessageHasType(events[1], "XdndPosition"));
client->OnStatus(toplevel, true, client->GetAtom("XdndActionCopy"));
events = collector.PopAllEvents();
ASSERT_EQ(1u, events.size());
EXPECT_TRUE(client->MessageHasType(events[0], "XdndPosition"));
client->OnStatus(toplevel, true, client->GetAtom("XdndActionCopy"));
EXPECT_FALSE(collector.HasEvents());
client->OnMouseReleased();
events = collector.PopAllEvents();
ASSERT_EQ(1u, events.size());
EXPECT_TRUE(client->MessageHasType(events[0], "XdndDrop"));
EXPECT_TRUE(client->IsMoveLoopRunning());
client->OnFinished(toplevel, true, client->GetAtom("XdndActionCopy"));
EXPECT_FALSE(client->IsMoveLoopRunning());
}
} // namespace
// Test that XdndPosition messages are queued till the pending XdndPosition
// message is acked via an XdndStatus message.
TEST_F(DesktopDragDropClientAuraX11Test, QueuePosition) {
base::MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(&QueuePositionStep2, client()));
int result = StartDragAndDrop();
EXPECT_EQ(ui::DragDropTypes::DRAG_COPY, result);
}
namespace {
void TargetChangesStep2(TestDragDropClient* client) {
EXPECT_TRUE(client->IsMoveLoopRunning());
XID toplevel1 = 1;
ClientMessageEventCollector collector1(toplevel1, client);
client->SetTopmostXWindowAndMoveMouse(toplevel1);
std::vector<XClientMessageEvent> events1 = collector1.PopAllEvents();
ASSERT_EQ(2u, events1.size());
EXPECT_TRUE(client->MessageHasType(events1[0], "XdndEnter"));
EXPECT_TRUE(client->MessageHasType(events1[1], "XdndPosition"));
XID toplevel2 = 2;
ClientMessageEventCollector collector2(toplevel2, client);
client->SetTopmostXWindowAndMoveMouse(toplevel2);
// It is possible for |toplevel1| to send XdndStatus after the source has sent
// XdndLeave but before |toplevel1| has received the XdndLeave message. The
// XdndStatus message should be ignored.
client->OnStatus(toplevel1, true, client->GetAtom("XdndActionCopy"));
events1 = collector1.PopAllEvents();
ASSERT_EQ(1u, events1.size());
EXPECT_TRUE(client->MessageHasType(events1[0], "XdndLeave"));
std::vector<XClientMessageEvent> events2 = collector2.PopAllEvents();
ASSERT_EQ(2u, events2.size());
EXPECT_TRUE(client->MessageHasType(events2[0], "XdndEnter"));
EXPECT_TRUE(client->MessageHasType(events2[1], "XdndPosition"));
client->OnStatus(toplevel2, true, client->GetAtom("XdndActionCopy"));
client->OnMouseReleased();
events2 = collector2.PopAllEvents();
ASSERT_EQ(1u, events2.size());
EXPECT_TRUE(client->MessageHasType(events2[0], "XdndDrop"));
EXPECT_TRUE(client->IsMoveLoopRunning());
client->OnFinished(toplevel2, true, client->GetAtom("XdndActionCopy"));
EXPECT_FALSE(client->IsMoveLoopRunning());
}
} // namespace
// Test the behavior when the target changes during a drag.
TEST_F(DesktopDragDropClientAuraX11Test, TargetChanges) {
base::MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(&TargetChangesStep2, client()));
int result = StartDragAndDrop();
EXPECT_EQ(ui::DragDropTypes::DRAG_COPY, result);
}
namespace {
void RejectAfterMouseReleaseStep2(TestDragDropClient* client) {
EXPECT_TRUE(client->IsMoveLoopRunning());
XID toplevel = 1;
ClientMessageEventCollector collector(toplevel, client);
client->SetTopmostXWindowAndMoveMouse(toplevel);
std::vector<XClientMessageEvent> events = collector.PopAllEvents();
ASSERT_EQ(2u, events.size());
EXPECT_TRUE(client->MessageHasType(events[0], "XdndEnter"));
EXPECT_TRUE(client->MessageHasType(events[1], "XdndPosition"));
client->OnStatus(toplevel, true, client->GetAtom("XdndActionCopy"));
EXPECT_FALSE(collector.HasEvents());
// Send another mouse move such that there is a pending XdndPosition.
client->SetTopmostXWindowAndMoveMouse(toplevel);
events = collector.PopAllEvents();
ASSERT_EQ(1u, events.size());
EXPECT_TRUE(client->MessageHasType(events[0], "XdndPosition"));
client->OnMouseReleased();
// Reject the drop.
client->OnStatus(toplevel, false, None);
events = collector.PopAllEvents();
ASSERT_EQ(1u, events.size());
EXPECT_TRUE(client->MessageHasType(events[0], "XdndLeave"));
EXPECT_FALSE(client->IsMoveLoopRunning());
}
void RejectAfterMouseReleaseStep3(TestDragDropClient* client) {
EXPECT_TRUE(client->IsMoveLoopRunning());
XID toplevel = 2;
ClientMessageEventCollector collector(toplevel, client);
client->SetTopmostXWindowAndMoveMouse(toplevel);
std::vector<XClientMessageEvent> events = collector.PopAllEvents();
ASSERT_EQ(2u, events.size());
EXPECT_TRUE(client->MessageHasType(events[0], "XdndEnter"));
EXPECT_TRUE(client->MessageHasType(events[1], "XdndPosition"));
client->OnStatus(toplevel, true, client->GetAtom("XdndActionCopy"));
EXPECT_FALSE(collector.HasEvents());
client->OnMouseReleased();
events = collector.PopAllEvents();
ASSERT_EQ(1u, events.size());
EXPECT_TRUE(client->MessageHasType(events[0], "XdndDrop"));
EXPECT_TRUE(client->IsMoveLoopRunning());
client->OnFinished(toplevel, false, None);
EXPECT_FALSE(client->IsMoveLoopRunning());
}
} // namespace
// Test that the source sends XdndLeave instead of XdndDrop if the drag
// operation is rejected after the mouse is released.
TEST_F(DesktopDragDropClientAuraX11Test, RejectAfterMouseRelease) {
base::MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(&RejectAfterMouseReleaseStep2, client()));
int result = StartDragAndDrop();
EXPECT_EQ(ui::DragDropTypes::DRAG_NONE, result);
// Repeat the test but reject the drop in the XdndFinished message instead.
base::MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(&RejectAfterMouseReleaseStep3, client()));
result = StartDragAndDrop();
EXPECT_EQ(ui::DragDropTypes::DRAG_NONE, result);
}
} // namespace views