blob: ac87f78d70da3886d2ea58c1d857d652b3ee7cee [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 "ui/base/x/x11_drag_drop_client.h"
#include <map>
#include <memory>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/location.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/thread_task_runner_handle.h"
#include "ui/aura/client/drag_drop_client.h"
#include "ui/aura/client/drag_drop_delegate.h"
#include "ui/aura/test/test_screen.h"
#include "ui/aura/window.h"
#include "ui/aura/window_tree_host.h"
#include "ui/base/dragdrop/mojom/drag_drop_types.mojom-shared.h"
#include "ui/base/dragdrop/os_exchange_data.h"
#include "ui/base/x/x11_cursor.h"
#include "ui/base/x/x11_move_loop.h"
#include "ui/base/x/x11_move_loop_delegate.h"
#include "ui/base/x/x11_os_exchange_data_provider.h"
#include "ui/base/x/x11_util.h"
#include "ui/events/event_utils.h"
#include "ui/gfx/x/x11_atom_cache.h"
#include "ui/gfx/x/xproto.h"
#include "ui/gfx/x/xproto_util.h"
#include "ui/views/test/views_test_base.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"
namespace views {
namespace {
class TestDragDropClient;
// Collects messages which would otherwise be sent to |window_| via
// SendXClientEvent().
class ClientMessageEventCollector {
public:
ClientMessageEventCollector(x11::Window window, 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<x11::ClientMessageEvent> PopAllEvents();
// Adds |event| to the stack.
void RecordEvent(const x11::ClientMessageEvent& event);
private:
x11::Window window_;
// Not owned.
TestDragDropClient* client_;
std::vector<x11::ClientMessageEvent> events_;
DISALLOW_COPY_AND_ASSIGN(ClientMessageEventCollector);
};
// An implementation of ui::X11MoveLoop where RunMoveLoop() always starts the
// move loop.
class TestMoveLoop : public ui::X11MoveLoop {
public:
explicit TestMoveLoop(ui::X11MoveLoopDelegate* delegate);
~TestMoveLoop() override;
// Returns true if the move loop is running.
bool IsRunning() const;
// ui::X11MoveLoop:
bool RunMoveLoop(bool can_grab_pointer,
scoped_refptr<ui::X11Cursor> old_cursor,
scoped_refptr<ui::X11Cursor> new_cursor) override;
void UpdateCursor(scoped_refptr<ui::X11Cursor> cursor) override;
void EndMoveLoop() override;
private:
// Not owned.
ui::X11MoveLoopDelegate* delegate_;
// Ends the move loop.
base::OnceClosure quit_closure_;
bool is_running_ = false;
};
// Implementation of XDragDropClient which short circuits FindWindowFor().
class SimpleTestDragDropClient : public aura::client::DragDropClient,
public ui::XDragDropClient,
public ui::XDragDropClient::Delegate,
public ui::X11MoveLoopDelegate {
public:
SimpleTestDragDropClient(aura::Window*,
DesktopNativeCursorManager* cursor_manager);
~SimpleTestDragDropClient() override;
// Sets |window| as the topmost window for all mouse positions.
void SetTopmostXWindow(x11::Window window);
// Returns true if the move loop is running.
bool IsMoveLoopRunning();
// aura::client::DragDropClient:
int StartDragAndDrop(std::unique_ptr<ui::OSExchangeData> data,
aura::Window* root_window,
aura::Window* source_window,
const gfx::Point& screen_location,
int operation,
ui::mojom::DragEventSource source) override;
void DragCancel() override;
bool IsDragDropInProgress() override;
void AddObserver(aura::client::DragDropClientObserver* observer) override;
void RemoveObserver(aura::client::DragDropClientObserver* observer) override;
private:
// ui::XDragDropClient::Delegate:
std::unique_ptr<ui::XTopmostWindowFinder> CreateWindowFinder() override;
int UpdateDrag(const gfx::Point& screen_point) override;
void UpdateCursor(
ui::DragDropTypes::DragOperation negotiated_operation) override;
void OnBeginForeignDrag(x11::Window window) override;
void OnEndForeignDrag() override;
void OnBeforeDragLeave() override;
int PerformDrop() override;
void EndDragLoop() override;
// XDragDropClient:
x11::Window FindWindowFor(const gfx::Point& screen_point) override;
// ui::X11MoveLoopDelegate:
void OnMouseMovement(const gfx::Point& screen_point,
int flags,
base::TimeTicks event_time) override;
void OnMouseReleased() override;
void OnMoveLoopEnded() override;
std::unique_ptr<ui::X11MoveLoop> CreateMoveLoop(
ui::X11MoveLoopDelegate* delegate);
// The x11::Window of the window which is simulated to be the topmost window.
x11::Window target_window_ = x11::Window::None;
// The move loop. Not owned.
TestMoveLoop* loop_ = nullptr;
base::OnceClosure quit_closure_;
DISALLOW_COPY_AND_ASSIGN(SimpleTestDragDropClient);
};
// Implementation of XDragDropClient which works with a fake
// |XDragDropClient::source_current_window_|.
class TestDragDropClient : public SimpleTestDragDropClient {
public:
// The location in screen coordinates used for the synthetic mouse moves
// generated in SetTopmostXWindowAndMoveMouse().
static constexpr int kMouseMoveX = 100;
static constexpr int kMouseMoveY = 200;
TestDragDropClient(aura::Window* window,
DesktopNativeCursorManager* cursor_manager);
~TestDragDropClient() override;
// Returns the x11::Window of the window which initiated the drag.
x11::Window source_xwindow() { return source_window_; }
// Returns the Atom with |name|.
x11::Atom GetAtom(const char* name);
// Returns true if the event's message has |type|.
bool MessageHasType(const x11::ClientMessageEvent& event, const char* type);
// Sets |collector| to collect x11::ClientMessageEvents which would otherwise
// have been sent to the drop target window.
void SetEventCollectorFor(x11::Window window,
ClientMessageEventCollector* collector);
// Builds an XdndStatus message and sends it to
// XDragDropClient.
void OnStatus(x11::Window target_window,
bool will_accept_drop,
x11::Atom accepted_action);
// Builds an XdndFinished message and sends it to
// XDragDropClient.
void OnFinished(x11::Window target_window,
bool accepted_drop,
x11::Atom performed_action);
// Sets |window| as the topmost window at the current mouse position and
// generates a synthetic mouse move.
void SetTopmostXWindowAndMoveMouse(x11::Window window);
private:
// XDragDropClient:
void SendXClientEvent(x11::Window window,
const x11::ClientMessageEvent& event) override;
// The x11::Window of the window which initiated the drag.
x11::Window source_window_;
// Map of x11::Windows to the collector which intercepts
// x11::ClientMessageEvents for that window.
std::map<x11::Window, ClientMessageEventCollector*> collectors_;
DISALLOW_COPY_AND_ASSIGN(TestDragDropClient);
};
///////////////////////////////////////////////////////////////////////////////
// ClientMessageEventCollector
ClientMessageEventCollector::ClientMessageEventCollector(
x11::Window window,
TestDragDropClient* client)
: window_(window), client_(client) {
client->SetEventCollectorFor(window, this);
}
ClientMessageEventCollector::~ClientMessageEventCollector() {
client_->SetEventCollectorFor(window_, nullptr);
}
std::vector<x11::ClientMessageEvent>
ClientMessageEventCollector::PopAllEvents() {
std::vector<x11::ClientMessageEvent> to_return;
to_return.swap(events_);
return to_return;
}
void ClientMessageEventCollector::RecordEvent(
const x11::ClientMessageEvent& event) {
events_.push_back(event);
}
///////////////////////////////////////////////////////////////////////////////
// TestMoveLoop
TestMoveLoop::TestMoveLoop(ui::X11MoveLoopDelegate* delegate)
: delegate_(delegate) {}
TestMoveLoop::~TestMoveLoop() = default;
bool TestMoveLoop::IsRunning() const {
return is_running_;
}
bool TestMoveLoop::RunMoveLoop(bool can_grab_pointer,
scoped_refptr<ui::X11Cursor> old_cursor,
scoped_refptr<ui::X11Cursor> new_cursor) {
is_running_ = true;
base::RunLoop run_loop;
quit_closure_ = run_loop.QuitClosure();
run_loop.Run();
return true;
}
void TestMoveLoop::UpdateCursor(scoped_refptr<ui::X11Cursor> cursor) {}
void TestMoveLoop::EndMoveLoop() {
if (is_running_) {
delegate_->OnMoveLoopEnded();
is_running_ = false;
std::move(quit_closure_).Run();
}
}
///////////////////////////////////////////////////////////////////////////////
// SimpleTestDragDropClient
SimpleTestDragDropClient::SimpleTestDragDropClient(
aura::Window* window,
DesktopNativeCursorManager* cursor_manager)
: ui::XDragDropClient(
this,
static_cast<x11::Window>(window->GetHost()->GetAcceleratedWidget())) {
}
SimpleTestDragDropClient::~SimpleTestDragDropClient() = default;
void SimpleTestDragDropClient::SetTopmostXWindow(x11::Window window) {
target_window_ = window;
}
bool SimpleTestDragDropClient::IsMoveLoopRunning() {
return loop_->IsRunning();
}
std::unique_ptr<ui::X11MoveLoop> SimpleTestDragDropClient::CreateMoveLoop(
ui::X11MoveLoopDelegate* delegate) {
loop_ = new TestMoveLoop(delegate);
return base::WrapUnique(loop_);
}
int SimpleTestDragDropClient::StartDragAndDrop(
std::unique_ptr<ui::OSExchangeData> data,
aura::Window* root_window,
aura::Window* source_window,
const gfx::Point& screen_location,
int operation,
ui::mojom::DragEventSource source) {
InitDrag(operation, data.get());
auto loop = CreateMoveLoop(this);
// Windows has a specific method, DoDragDrop(), which performs the entire
// drag. We have to emulate this, so we spin off a nested runloop which will
// track all cursor movement and reroute events to a specific handler.
auto cursor_manager_ = std::make_unique<DesktopNativeCursorManager>();
auto* last_cursor = static_cast<ui::X11Cursor*>(
source_window->GetHost()->last_cursor().platform());
loop_->RunMoveLoop(
!source_window->HasCapture(), last_cursor,
static_cast<ui::X11Cursor*>(
cursor_manager_
->GetInitializedCursor(ui::mojom::CursorType::kGrabbing)
.platform()));
auto resulting_operation = negotiated_operation();
CleanupDrag();
return resulting_operation;
}
void SimpleTestDragDropClient::DragCancel() {}
bool SimpleTestDragDropClient::IsDragDropInProgress() {
return false;
}
void SimpleTestDragDropClient::AddObserver(
aura::client::DragDropClientObserver* observer) {}
void SimpleTestDragDropClient::RemoveObserver(
aura::client::DragDropClientObserver* observer) {}
int SimpleTestDragDropClient::UpdateDrag(const gfx::Point& screen_point) {
return 0;
}
std::unique_ptr<ui::XTopmostWindowFinder>
SimpleTestDragDropClient::CreateWindowFinder() {
return {};
}
void SimpleTestDragDropClient::UpdateCursor(
ui::DragDropTypes::DragOperation negotiated_operation) {}
void SimpleTestDragDropClient::OnBeginForeignDrag(x11::Window window) {}
void SimpleTestDragDropClient::OnEndForeignDrag() {}
void SimpleTestDragDropClient::OnBeforeDragLeave() {}
int SimpleTestDragDropClient::PerformDrop() {
return 0;
}
void SimpleTestDragDropClient::EndDragLoop() {
// std::move(quit_closure_).Run();
loop_->EndMoveLoop();
}
x11::Window SimpleTestDragDropClient::FindWindowFor(
const gfx::Point& screen_point) {
return target_window_;
}
void SimpleTestDragDropClient::OnMouseMovement(const gfx::Point& screen_point,
int flags,
base::TimeTicks event_time) {
HandleMouseMovement(screen_point, flags, event_time);
}
void SimpleTestDragDropClient::OnMouseReleased() {
HandleMouseReleased();
}
void SimpleTestDragDropClient::OnMoveLoopEnded() {
HandleMoveLoopEnded();
}
///////////////////////////////////////////////////////////////////////////////
// TestDragDropClient
TestDragDropClient::TestDragDropClient(
aura::Window* window,
DesktopNativeCursorManager* cursor_manager)
: SimpleTestDragDropClient(window, cursor_manager),
source_window_(
static_cast<x11::Window>(window->GetHost()->GetAcceleratedWidget())) {
}
TestDragDropClient::~TestDragDropClient() = default;
x11::Atom TestDragDropClient::GetAtom(const char* name) {
return x11::GetAtom(name);
}
bool TestDragDropClient::MessageHasType(const x11::ClientMessageEvent& event,
const char* type) {
return event.type == GetAtom(type);
}
void TestDragDropClient::SetEventCollectorFor(
x11::Window window,
ClientMessageEventCollector* collector) {
if (collector)
collectors_[window] = collector;
else
collectors_.erase(window);
}
void TestDragDropClient::OnStatus(x11::Window target_window,
bool will_accept_drop,
x11::Atom accepted_action) {
x11::ClientMessageEvent event;
event.type = GetAtom("XdndStatus");
event.format = 32;
event.window = source_window_;
event.data.data32[0] = static_cast<uint32_t>(target_window);
event.data.data32[1] = will_accept_drop ? 1 : 0;
event.data.data32[2] = 0;
event.data.data32[3] = 0;
event.data.data32[4] = static_cast<uint32_t>(accepted_action);
HandleXdndEvent(event);
}
void TestDragDropClient::OnFinished(x11::Window target_window,
bool accepted_drop,
x11::Atom performed_action) {
x11::ClientMessageEvent event;
event.type = GetAtom("XdndFinished");
event.format = 32;
event.window = source_window_;
event.data.data32[0] = static_cast<uint32_t>(target_window);
event.data.data32[1] = accepted_drop ? 1 : 0;
event.data.data32[2] = static_cast<uint32_t>(performed_action);
event.data.data32[3] = 0;
event.data.data32[4] = 0;
HandleXdndEvent(event);
}
void TestDragDropClient::SetTopmostXWindowAndMoveMouse(x11::Window window) {
SetTopmostXWindow(window);
HandleMouseMovement(gfx::Point(kMouseMoveX, kMouseMoveY), ui::EF_NONE,
ui::EventTimeForNow());
}
void TestDragDropClient::SendXClientEvent(
x11::Window window,
const x11::ClientMessageEvent& event) {
auto it = collectors_.find(window);
if (it != collectors_.end())
it->second->RecordEvent(event);
}
} // namespace
class X11DragDropClientTest : public ViewsTestBase {
public:
X11DragDropClientTest() = default;
~X11DragDropClientTest() override = default;
int StartDragAndDrop() {
auto data(std::make_unique<ui::OSExchangeData>());
data->SetString(base::ASCIIToUTF16("Test"));
SkBitmap drag_bitmap;
drag_bitmap.allocN32Pixels(10, 10);
drag_bitmap.eraseARGB(0xFF, 0, 0, 0);
gfx::ImageSkia drag_image(gfx::ImageSkia::CreateFrom1xBitmap(drag_bitmap));
data->provider().SetDragImage(drag_image, gfx::Vector2d());
return client_->StartDragAndDrop(
std::move(data), widget_->GetNativeWindow()->GetRootWindow(),
widget_->GetNativeWindow(), gfx::Point(), ui::DragDropTypes::DRAG_COPY,
ui::mojom::DragEventSource::kMouse);
}
// ViewsTestBase:
void SetUp() override {
set_native_widget_type(NativeWidgetType::kDesktop);
ViewsTestBase::SetUp();
// TODO(crbug.com/1096425): Once non-Ozone X11 is deprecated, re-work this.
if (features::IsUsingOzonePlatform())
GTEST_SKIP();
// Create widget to initiate the drags.
widget_ = std::make_unique<Widget>();
Widget::InitParams params(Widget::InitParams::TYPE_WINDOW);
params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
params.bounds = gfx::Rect(100, 100);
widget_->Init(std::move(params));
widget_->Show();
cursor_manager_ = std::make_unique<DesktopNativeCursorManager>();
client_ = std::make_unique<TestDragDropClient>(widget_->GetNativeWindow(),
cursor_manager_.get());
// client_->Init();
}
void TearDown() override {
client_.reset();
cursor_manager_.reset();
widget_.reset();
ViewsTestBase::TearDown();
}
TestDragDropClient* client() { return client_.get(); }
private:
std::unique_ptr<TestDragDropClient> client_;
std::unique_ptr<DesktopNativeCursorManager> cursor_manager_;
// The widget used to initiate drags.
std::unique_ptr<Widget> widget_;
DISALLOW_COPY_AND_ASSIGN(X11DragDropClientTest);
};
namespace {
void BasicStep2(TestDragDropClient* client, x11::Window 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<x11::ClientMessageEvent> events = collector.PopAllEvents();
ASSERT_EQ(2u, events.size());
EXPECT_TRUE(client->MessageHasType(events[0], "XdndEnter"));
EXPECT_EQ(client->source_xwindow(),
static_cast<x11::Window>(events[0].data.data32[0]));
EXPECT_EQ(1u, events[0].data.data32[1] & 1);
std::vector<x11::Atom> targets;
GetArrayProperty(client->source_xwindow(), x11::GetAtom("XdndTypeList"),
&targets);
EXPECT_FALSE(targets.empty());
EXPECT_TRUE(client->MessageHasType(events[1], "XdndPosition"));
EXPECT_EQ(client->source_xwindow(),
static_cast<x11::Window>(events[0].data.data32[0]));
const uint32_t kCoords =
TestDragDropClient::kMouseMoveX << 16 | TestDragDropClient::kMouseMoveY;
EXPECT_EQ(kCoords, events[1].data.data32[2]);
EXPECT_EQ(client->GetAtom("XdndActionCopy"),
static_cast<x11::Atom>(events[1].data.data32[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->HandleMouseReleased();
events = collector.PopAllEvents();
ASSERT_EQ(1u, events.size());
EXPECT_TRUE(client->MessageHasType(events[0], "XdndDrop"));
EXPECT_EQ(client->source_xwindow(),
static_cast<x11::Window>(events[0].data.data32[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, x11::Window toplevel) {
EXPECT_TRUE(client->IsMoveLoopRunning());
ClientMessageEventCollector collector(toplevel, client);
client->SetTopmostXWindowAndMoveMouse(toplevel);
std::vector<x11::ClientMessageEvent> 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->HandleMouseReleased();
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(X11DragDropClientTest, Basic) {
x11::Window toplevel = static_cast<x11::Window>(1);
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&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::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&BasicStep3, client(), toplevel));
result = StartDragAndDrop();
EXPECT_EQ(ui::DragDropTypes::DRAG_COPY, result);
}
namespace {
void TargetDoesNotRespondStep2(TestDragDropClient* client) {
EXPECT_TRUE(client->IsMoveLoopRunning());
x11::Window toplevel = static_cast<x11::Window>(1);
ClientMessageEventCollector collector(toplevel, client);
client->SetTopmostXWindowAndMoveMouse(toplevel);
std::vector<x11::ClientMessageEvent> events = collector.PopAllEvents();
ASSERT_EQ(2u, events.size());
EXPECT_TRUE(client->MessageHasType(events[0], "XdndEnter"));
EXPECT_TRUE(client->MessageHasType(events[1], "XdndPosition"));
client->HandleMouseReleased();
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(X11DragDropClientTest, TargetDoesNotRespond) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&TargetDoesNotRespondStep2, client()));
int result = StartDragAndDrop();
EXPECT_EQ(ui::DragDropTypes::DRAG_NONE, result);
}
namespace {
void QueuePositionStep2(TestDragDropClient* client) {
EXPECT_TRUE(client->IsMoveLoopRunning());
x11::Window toplevel = static_cast<x11::Window>(1);
ClientMessageEventCollector collector(toplevel, client);
client->SetTopmostXWindowAndMoveMouse(toplevel);
client->SetTopmostXWindowAndMoveMouse(toplevel);
client->SetTopmostXWindowAndMoveMouse(toplevel);
std::vector<x11::ClientMessageEvent> 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->HandleMouseReleased();
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(X11DragDropClientTest, QueuePosition) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&QueuePositionStep2, client()));
int result = StartDragAndDrop();
EXPECT_EQ(ui::DragDropTypes::DRAG_COPY, result);
}
namespace {
void TargetChangesStep2(TestDragDropClient* client) {
EXPECT_TRUE(client->IsMoveLoopRunning());
x11::Window toplevel1 = static_cast<x11::Window>(1);
ClientMessageEventCollector collector1(toplevel1, client);
client->SetTopmostXWindowAndMoveMouse(toplevel1);
std::vector<x11::ClientMessageEvent> events1 = collector1.PopAllEvents();
ASSERT_EQ(2u, events1.size());
EXPECT_TRUE(client->MessageHasType(events1[0], "XdndEnter"));
EXPECT_TRUE(client->MessageHasType(events1[1], "XdndPosition"));
x11::Window toplevel2 = static_cast<x11::Window>(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<x11::ClientMessageEvent> 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->HandleMouseReleased();
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(X11DragDropClientTest, TargetChanges) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&TargetChangesStep2, client()));
int result = StartDragAndDrop();
EXPECT_EQ(ui::DragDropTypes::DRAG_COPY, result);
}
namespace {
void RejectAfterMouseReleaseStep2(TestDragDropClient* client) {
EXPECT_TRUE(client->IsMoveLoopRunning());
x11::Window toplevel = static_cast<x11::Window>(1);
ClientMessageEventCollector collector(toplevel, client);
client->SetTopmostXWindowAndMoveMouse(toplevel);
std::vector<x11::ClientMessageEvent> 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->HandleMouseReleased();
// Reject the drop.
client->OnStatus(toplevel, false, x11::Atom::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());
x11::Window toplevel = static_cast<x11::Window>(2);
ClientMessageEventCollector collector(toplevel, client);
client->SetTopmostXWindowAndMoveMouse(toplevel);
std::vector<x11::ClientMessageEvent> 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->HandleMouseReleased();
events = collector.PopAllEvents();
ASSERT_EQ(1u, events.size());
EXPECT_TRUE(client->MessageHasType(events[0], "XdndDrop"));
EXPECT_TRUE(client->IsMoveLoopRunning());
client->OnFinished(toplevel, false, x11::Atom::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(X11DragDropClientTest, RejectAfterMouseRelease) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&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::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&RejectAfterMouseReleaseStep3, client()));
result = StartDragAndDrop();
EXPECT_EQ(ui::DragDropTypes::DRAG_NONE, result);
}
} // namespace views