blob: aa279a4b63bf563ffa488cfaf20f7f156d14915e [file] [log] [blame]
// Copyright 2017 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 "components/exo/data_device.h"
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "ash/shell.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind.h"
#include "build/chromeos_buildflags.h"
#include "components/exo/data_device_delegate.h"
#include "components/exo/data_exchange_delegate.h"
#include "components/exo/data_offer.h"
#include "components/exo/data_offer_delegate.h"
#include "components/exo/seat.h"
#include "components/exo/surface.h"
#include "components/exo/test/exo_test_base.h"
#include "components/exo/test/exo_test_data_exchange_delegate.h"
#include "components/exo/test/exo_test_helper.h"
#include "ui/aura/client/focus_client.h"
#include "ui/base/clipboard/scoped_clipboard_writer.h"
#include "ui/base/dragdrop/drag_drop_types.h"
#include "ui/base/dragdrop/drop_target_event.h"
#include "ui/base/dragdrop/mojom/drag_drop_types.mojom.h"
#include "ui/base/dragdrop/os_exchange_data.h"
#include "ui/events/event.h"
#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "chromeos/ui/base/window_properties.h"
#include "components/exo/data_source.h"
#include "components/exo/data_source_delegate.h"
#include "components/exo/extended_drag_source.h"
#include "components/exo/shell_surface.h"
#include "components/exo/surface_delegate.h"
#include "components/exo/test/shell_surface_builder.h"
#endif
namespace exo {
namespace {
using ::ui::mojom::DragOperation;
enum class DataEvent {
kOffer,
kEnter,
kLeave,
kMotion,
kDrop,
kDestroy,
kSelection
};
class TestDataOfferDelegate : public DataOfferDelegate {
public:
~TestDataOfferDelegate() override {}
// Overridden from DataOfferDelegate:
void OnDataOfferDestroying(DataOffer* offer) override { delete this; }
void OnOffer(const std::string& mime_type) override {}
void OnSourceActions(
const base::flat_set<DndAction>& source_actions) override {}
void OnAction(DndAction action) override {}
};
class TestDataDeviceDelegate : public DataDeviceDelegate {
public:
TestDataDeviceDelegate() {}
TestDataDeviceDelegate(const TestDataDeviceDelegate&) = delete;
TestDataDeviceDelegate& operator=(const TestDataDeviceDelegate&) = delete;
size_t PopEvents(std::vector<DataEvent>* out) {
out->swap(events_);
events_.clear();
return out->size();
}
Surface* entered_surface() const { return entered_surface_; }
void DeleteDataOffer(bool finished) {
if (finished)
data_offer_->Finish();
data_offer_.reset();
}
void set_can_accept_data_events_for_surface(bool value) {
can_accept_data_events_for_surface_ = value;
}
// Overridden from DataDeviceDelegate:
void OnDataDeviceDestroying(DataDevice* data_device) override {
events_.push_back(DataEvent::kDestroy);
}
DataOffer* OnDataOffer() override {
events_.push_back(DataEvent::kOffer);
data_offer_ = std::make_unique<DataOffer>(new TestDataOfferDelegate);
return data_offer_.get();
}
void OnEnter(Surface* surface,
const gfx::PointF& location,
const DataOffer& data_offer) override {
events_.push_back(DataEvent::kEnter);
entered_surface_ = surface;
}
void OnLeave() override { events_.push_back(DataEvent::kLeave); }
void OnMotion(base::TimeTicks time_stamp,
const gfx::PointF& location) override {
events_.push_back(DataEvent::kMotion);
}
void OnDrop() override { events_.push_back(DataEvent::kDrop); }
void OnSelection(const DataOffer& data_offer) override {
events_.push_back(DataEvent::kSelection);
}
bool CanAcceptDataEventsForSurface(Surface* surface) const override {
return can_accept_data_events_for_surface_;
}
private:
std::vector<DataEvent> events_;
std::unique_ptr<DataOffer> data_offer_;
Surface* entered_surface_ = nullptr;
bool can_accept_data_events_for_surface_ = true;
};
class TestSeat : public Seat {
public:
TestSeat() : Seat(std::make_unique<TestDataExchangeDelegate>()) {}
TestSeat(const TestSeat&) = delete;
TestSeat& operator=(const TestSeat&) = delete;
void set_focused_surface(Surface* surface) { surface_ = surface; }
// Overriden from Seat:
Surface* GetFocusedSurface() override { return surface_; }
private:
Surface* surface_ = nullptr;
};
class DataDeviceTest : public test::ExoTestBase {
public:
void SetUp() override {
test::ExoTestBase::SetUp();
seat_ = std::make_unique<TestSeat>();
device_ = std::make_unique<DataDevice>(&delegate_, seat_.get());
data_.SetString(std::u16string(u"Test data"));
surface_ = std::make_unique<Surface>();
}
void TearDown() override {
surface_.reset();
device_.reset();
seat_.reset();
test::ExoTestBase::TearDown();
}
protected:
TestDataDeviceDelegate delegate_;
std::unique_ptr<TestSeat> seat_;
std::unique_ptr<DataDevice> device_;
ui::OSExchangeData data_;
std::unique_ptr<Surface> surface_;
};
TEST_F(DataDeviceTest, Destroy) {
std::vector<DataEvent> events;
device_.reset();
ASSERT_EQ(1u, delegate_.PopEvents(&events));
EXPECT_EQ(DataEvent::kDestroy, events[0]);
}
TEST_F(DataDeviceTest, DataEventsDrop) {
ui::DropTargetEvent event(data_, gfx::PointF(), gfx::PointF(),
ui::DragDropTypes::DRAG_MOVE);
ui::Event::DispatcherApi(&event).set_target(surface_->window());
std::vector<DataEvent> events;
device_->OnDragEntered(event);
ASSERT_EQ(2u, delegate_.PopEvents(&events));
EXPECT_EQ(DataEvent::kOffer, events[0]);
EXPECT_EQ(DataEvent::kEnter, events[1]);
EXPECT_EQ(ui::DragDropTypes::DRAG_LINK,
device_->OnDragUpdated(event).drag_operation);
ASSERT_EQ(1u, delegate_.PopEvents(&events));
EXPECT_EQ(DataEvent::kMotion, events[0]);
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&TestDataDeviceDelegate::DeleteDataOffer,
base::Unretained(&delegate_), true));
auto drop_cb = device_->GetDropCallback();
DragOperation output_drag_op;
std::move(drop_cb).Run(output_drag_op);
EXPECT_EQ(DragOperation::kLink, output_drag_op);
ASSERT_EQ(1u, delegate_.PopEvents(&events));
EXPECT_EQ(DataEvent::kDrop, events[0]);
}
#if BUILDFLAG(IS_CHROMEOS_ASH)
// Helper class to plumb the ExtendedDragSource instance.
class TestExtendedDragSourceDelegate : public ExtendedDragSource::Delegate {
public:
TestExtendedDragSourceDelegate() = default;
TestExtendedDragSourceDelegate(const TestExtendedDragSourceDelegate&) =
delete;
TestExtendedDragSourceDelegate& operator=(
const TestExtendedDragSourceDelegate&) = delete;
~TestExtendedDragSourceDelegate() override = default;
// ExtendedDragSource::Delegate:
bool ShouldAllowDropAnywhere() const override { return false; }
bool ShouldLockCursor() const override { return false; }
void OnSwallowed(const std::string& mime_type) override {}
void OnUnswallowed(const std::string& mime_type,
const gfx::Vector2d& offset) override {}
void OnDataSourceDestroying() override {}
};
// Helper class to plumb the DataSource instance.
class TestDataSourceDelegate : public DataSourceDelegate {
public:
TestDataSourceDelegate() = default;
~TestDataSourceDelegate() override = default;
void OnDataSourceDestroying(DataSource* source) override {}
void OnTarget(const absl::optional<std::string>& mime_type) override {}
void OnSend(const std::string& mime_type, base::ScopedFD fd) override {}
void OnCancelled() override {}
void OnDndDropPerformed() override {}
void OnDndFinished() override {}
void OnAction(DndAction dnd_action) override {}
bool CanAcceptDataEventsForSurface(Surface* surface) const override {
return true;
}
};
TEST_F(DataDeviceTest, DataEventsPreventMotion) {
// Create a DataDevice with a focused Surface.
seat_->set_focused_surface(surface_.get());
device_.reset();
std::vector<DataEvent> events;
delegate_.PopEvents(&events);
device_ = std::make_unique<DataDevice>(&delegate_, seat_.get());
// Start a drag operation.
ui::DropTargetEvent event(data_, gfx::PointF(), gfx::PointF(),
ui::DragDropTypes::DRAG_MOVE);
ui::Event::DispatcherApi(&event).set_target(surface_->window());
device_->OnDragEntered(event);
delegate_.PopEvents(&events);
// Minic a window detach (new Surface creation).
auto shell_surface =
exo::test::ShellSurfaceBuilder({10, 10}).BuildShellSurface();
auto* other_surface = shell_surface->root_surface();
device_->OnSurfaceFocused(other_surface, nullptr, true);
delegate_.PopEvents(&events);
// Mimic an extended_drag_source drag operation.
TestDataSourceDelegate data_source_delegate;
auto data_source = std::make_unique<DataSource>(&data_source_delegate);
TestExtendedDragSourceDelegate extended_drag_source_delegate;
auto extended_drag_source = std::make_unique<ExtendedDragSource>(
data_source.get(), &extended_drag_source_delegate);
extended_drag_source->Drag(other_surface, gfx::Vector2d());
// Prevent drag.motion events to be sent.
other_surface->window()->GetToplevelWindow()->SetProperty(
chromeos::kCanAttachToAnotherWindowKey, false);
EXPECT_EQ(ui::DragDropTypes::DRAG_LINK,
device_->OnDragUpdated(event).drag_operation);
ASSERT_EQ(0u, delegate_.PopEvents(&events));
other_surface->window()->GetToplevelWindow()->ClearProperty(
chromeos::kCanAttachToAnotherWindowKey);
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&TestDataDeviceDelegate::DeleteDataOffer,
base::Unretained(&delegate_), true));
auto drop_cb = device_->GetDropCallback();
DragOperation output_drag_op;
std::move(drop_cb).Run(output_drag_op);
EXPECT_EQ(DragOperation::kLink, output_drag_op);
ASSERT_EQ(1u, delegate_.PopEvents(&events));
EXPECT_EQ(DataEvent::kDrop, events[0]);
}
#endif
TEST_F(DataDeviceTest, DataEventsExit) {
ui::DropTargetEvent event(data_, gfx::PointF(), gfx::PointF(),
ui::DragDropTypes::DRAG_MOVE);
ui::Event::DispatcherApi(&event).set_target(surface_->window());
std::vector<DataEvent> events;
device_->OnDragEntered(event);
ASSERT_EQ(2u, delegate_.PopEvents(&events));
EXPECT_EQ(DataEvent::kOffer, events[0]);
EXPECT_EQ(DataEvent::kEnter, events[1]);
EXPECT_EQ(ui::DragDropTypes::DRAG_LINK,
device_->OnDragUpdated(event).drag_operation);
ASSERT_EQ(1u, delegate_.PopEvents(&events));
EXPECT_EQ(DataEvent::kMotion, events[0]);
device_->OnDragExited();
ASSERT_EQ(1u, delegate_.PopEvents(&events));
EXPECT_EQ(DataEvent::kLeave, events[0]);
}
TEST_F(DataDeviceTest, DeleteDataDeviceDuringDrop) {
ui::DropTargetEvent event(data_, gfx::PointF(), gfx::PointF(),
ui::DragDropTypes::DRAG_MOVE);
ui::Event::DispatcherApi(&event).set_target(surface_->window());
device_->OnDragEntered(event);
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindLambdaForTesting([&]() { device_.reset(); }));
auto drop_cb = device_->GetDropCallback();
DragOperation output_drag_op;
std::move(drop_cb).Run(output_drag_op);
EXPECT_EQ(DragOperation::kNone, output_drag_op);
}
TEST_F(DataDeviceTest, DeleteDataOfferDuringDrag) {
ui::DropTargetEvent event(data_, gfx::PointF(), gfx::PointF(),
ui::DragDropTypes::DRAG_MOVE);
ui::Event::DispatcherApi(&event).set_target(surface_->window());
std::vector<DataEvent> events;
device_->OnDragEntered(event);
ASSERT_EQ(2u, delegate_.PopEvents(&events));
EXPECT_EQ(DataEvent::kOffer, events[0]);
EXPECT_EQ(DataEvent::kEnter, events[1]);
delegate_.DeleteDataOffer(false);
EXPECT_EQ(ui::DragDropTypes::DRAG_NONE,
device_->OnDragUpdated(event).drag_operation);
EXPECT_EQ(0u, delegate_.PopEvents(&events));
auto drop_cb = device_->GetDropCallback();
DragOperation output_drag_op;
std::move(drop_cb).Run(output_drag_op);
EXPECT_EQ(0u, delegate_.PopEvents(&events));
}
TEST_F(DataDeviceTest, DataOfferNotFinished) {
ui::DropTargetEvent event(data_, gfx::PointF(), gfx::PointF(),
ui::DragDropTypes::DRAG_MOVE);
ui::Event::DispatcherApi(&event).set_target(surface_->window());
std::vector<DataEvent> events;
device_->OnDragEntered(event);
ASSERT_EQ(2u, delegate_.PopEvents(&events));
EXPECT_EQ(DataEvent::kOffer, events[0]);
EXPECT_EQ(DataEvent::kEnter, events[1]);
EXPECT_EQ(ui::DragDropTypes::DRAG_LINK,
device_->OnDragUpdated(event).drag_operation);
ASSERT_EQ(1u, delegate_.PopEvents(&events));
EXPECT_EQ(DataEvent::kMotion, events[0]);
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&TestDataDeviceDelegate::DeleteDataOffer,
base::Unretained(&delegate_), false));
auto drop_cb = device_->GetDropCallback();
DragOperation output_drag_op;
std::move(drop_cb).Run(output_drag_op);
EXPECT_EQ(DragOperation::kNone, output_drag_op);
ASSERT_EQ(1u, delegate_.PopEvents(&events));
EXPECT_EQ(DataEvent::kDrop, events[0]);
}
TEST_F(DataDeviceTest, NotAcceptDataEventsForSurface) {
ui::DropTargetEvent event(data_, gfx::PointF(), gfx::PointF(),
ui::DragDropTypes::DRAG_MOVE);
ui::Event::DispatcherApi(&event).set_target(surface_->window());
std::vector<DataEvent> events;
delegate_.set_can_accept_data_events_for_surface(false);
device_->OnDragEntered(event);
EXPECT_EQ(0u, delegate_.PopEvents(&events));
EXPECT_EQ(ui::DragDropTypes::DRAG_NONE,
device_->OnDragUpdated(event).drag_operation);
EXPECT_EQ(0u, delegate_.PopEvents(&events));
auto drop_cb = device_->GetDropCallback();
DragOperation output_drag_op;
std::move(drop_cb).Run(output_drag_op);
EXPECT_EQ(0u, delegate_.PopEvents(&events));
}
TEST_F(DataDeviceTest, DropCallback_Run) {
ui::DropTargetEvent event(data_, gfx::PointF(), gfx::PointF(),
ui::DragDropTypes::DRAG_MOVE);
ui::Event::DispatcherApi(&event).set_target(surface_->window());
std::vector<DataEvent> events;
device_->OnDragEntered(event);
ASSERT_EQ(2u, delegate_.PopEvents(&events));
EXPECT_EQ(DataEvent::kOffer, events[0]);
EXPECT_EQ(DataEvent::kEnter, events[1]);
EXPECT_EQ(ui::DragDropTypes::DRAG_LINK,
device_->OnDragUpdated(event).drag_operation);
ASSERT_EQ(1u, delegate_.PopEvents(&events));
EXPECT_EQ(DataEvent::kMotion, events[0]);
auto drop_cb = device_->GetDropCallback();
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&TestDataDeviceDelegate::DeleteDataOffer,
base::Unretained(&delegate_), true));
DragOperation output_drag_op = DragOperation::kNone;
std::move(drop_cb).Run(output_drag_op);
EXPECT_EQ(DragOperation::kLink, output_drag_op);
ASSERT_EQ(1u, delegate_.PopEvents(&events));
EXPECT_EQ(DataEvent::kDrop, events[0]);
}
TEST_F(DataDeviceTest, DropCallback_Invalidated) {
ui::DropTargetEvent event(data_, gfx::PointF(), gfx::PointF(),
ui::DragDropTypes::DRAG_MOVE);
ui::Event::DispatcherApi(&event).set_target(surface_->window());
std::vector<DataEvent> events;
device_->OnDragEntered(event);
ASSERT_EQ(2u, delegate_.PopEvents(&events));
EXPECT_EQ(DataEvent::kOffer, events[0]);
EXPECT_EQ(DataEvent::kEnter, events[1]);
EXPECT_EQ(ui::DragDropTypes::DRAG_LINK,
device_->OnDragUpdated(event).drag_operation);
ASSERT_EQ(1u, delegate_.PopEvents(&events));
EXPECT_EQ(DataEvent::kMotion, events[0]);
auto drop_cb = device_->GetDropCallback();
delegate_.DeleteDataOffer(false);
DragOperation output_drag_op = DragOperation::kNone;
std::move(drop_cb).Run(output_drag_op);
EXPECT_EQ(DragOperation::kNone, output_drag_op);
EXPECT_EQ(0u, delegate_.PopEvents(&events));
}
TEST_F(DataDeviceTest, DropCallback_Reset) {
ui::DropTargetEvent event(data_, gfx::PointF(), gfx::PointF(),
ui::DragDropTypes::DRAG_MOVE);
ui::Event::DispatcherApi(&event).set_target(surface_->window());
std::vector<DataEvent> events;
device_->OnDragEntered(event);
ASSERT_EQ(2u, delegate_.PopEvents(&events));
EXPECT_EQ(DataEvent::kOffer, events[0]);
EXPECT_EQ(DataEvent::kEnter, events[1]);
EXPECT_EQ(ui::DragDropTypes::DRAG_LINK,
device_->OnDragUpdated(event).drag_operation);
ASSERT_EQ(1u, delegate_.PopEvents(&events));
EXPECT_EQ(DataEvent::kMotion, events[0]);
auto drop_cb = device_->GetDropCallback();
drop_cb.Reset();
ASSERT_EQ(1u, delegate_.PopEvents(&events));
EXPECT_EQ(DataEvent::kLeave, events[0]);
}
TEST_F(DataDeviceTest, ClipboardCopy) {
// Selection event sent when getting a focus.
device_->OnSurfaceFocused(surface_.get(), nullptr, true);
std::vector<DataEvent> events;
ASSERT_EQ(2u, delegate_.PopEvents(&events));
EXPECT_EQ(DataEvent::kOffer, events[0]);
EXPECT_EQ(DataEvent::kSelection, events[1]);
// Next focus does not send selection.
device_->OnSurfaceFocused(surface_.get(), nullptr, true);
EXPECT_EQ(0u, delegate_.PopEvents(&events));
// Clipboard change
device_->OnClipboardDataChanged();
ASSERT_EQ(2u, delegate_.PopEvents(&events));
EXPECT_EQ(DataEvent::kOffer, events[0]);
EXPECT_EQ(DataEvent::kSelection, events[1]);
// Losing focus does not create events.
device_->OnSurfaceFocused(nullptr, nullptr, true);
EXPECT_EQ(0u, delegate_.PopEvents(&events));
}
TEST_F(DataDeviceTest, ClipboardCopyWithoutFocus) {
device_->OnClipboardDataChanged();
std::vector<DataEvent> events;
EXPECT_EQ(0u, delegate_.PopEvents(&events));
}
TEST_F(DataDeviceTest, ClipboardDeviceCreatedAfterFocus) {
seat_->set_focused_surface(surface_.get());
device_.reset();
std::vector<DataEvent> events;
delegate_.PopEvents(&events);
device_ = std::make_unique<DataDevice>(&delegate_, seat_.get());
ASSERT_EQ(2u, delegate_.PopEvents(&events));
EXPECT_EQ(DataEvent::kOffer, events[0]);
EXPECT_EQ(DataEvent::kSelection, events[1]);
}
TEST_F(DataDeviceTest, ClipboardFocusedSurfaceDestroyed) {
device_->OnSurfaceFocused(surface_.get(), nullptr, true);
surface_.reset();
std::vector<DataEvent> events;
delegate_.PopEvents(&events);
device_->OnClipboardDataChanged();
EXPECT_EQ(0u, delegate_.PopEvents(&events));
}
} // namespace
} // namespace exo