// 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 #include #include #include "ash/shell.h" #include "base/strings/string16.h" #include "base/strings/utf_string_conversions.h" #include "components/exo/data_device_delegate.h" #include "components/exo/data_offer.h" #include "components/exo/data_offer_delegate.h" #include "components/exo/file_helper.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_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/os_exchange_data.h" #include "ui/events/event.h" namespace exo { namespace { 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& source_actions) override {} void OnAction(DndAction action) override {} }; class TestDataDeviceDelegate : public DataDeviceDelegate { public: TestDataDeviceDelegate() {} size_t PopEvents(std::vector* 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(DataOffer::Purpose purpose) override { events_.push_back(DataEvent::kOffer); data_offer_.reset(new DataOffer(new TestDataOfferDelegate, purpose)); 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 events_; std::unique_ptr data_offer_; Surface* entered_surface_ = nullptr; bool can_accept_data_events_for_surface_ = true; DISALLOW_COPY_AND_ASSIGN(TestDataDeviceDelegate); }; class TestFileHelper : public FileHelper { public: TestFileHelper() = default; // Overridden from FileHelper: std::string GetMimeTypeForUriList() const override { return ""; } bool GetUrlFromPath(const std::string& app_id, const base::FilePath& path, GURL* out) override { return true; } bool HasUrlsInPickle(const base::Pickle& pickle) override { return false; } void GetUrlsFromPickle(const std::string& app_id, const base::Pickle& pickle, UrlsFromPickleCallback callback) override {} private: DISALLOW_COPY_AND_ASSIGN(TestFileHelper); }; class TestSeat : public Seat { public: TestSeat() {} void set_focused_surface(Surface* surface) { surface_ = surface; } // Overriden from Seat: Surface* GetFocusedSurface() override { return surface_; } private: Surface* surface_ = nullptr; DISALLOW_COPY_AND_ASSIGN(TestSeat); }; class DataDeviceTest : public test::ExoTestBase { public: void SetUp() override { test::ExoTestBase::SetUp(); seat_ = std::make_unique(); device_ = std::make_unique(&delegate_, seat_.get(), &file_helper_); data_.SetString(base::string16(base::ASCIIToUTF16("Test data"))); surface_ = std::make_unique(); } void TearDown() override { surface_.reset(); device_.reset(); seat_.reset(); test::ExoTestBase::TearDown(); } protected: TestDataDeviceDelegate delegate_; std::unique_ptr seat_; TestFileHelper file_helper_; std::unique_ptr device_; ui::OSExchangeData data_; std::unique_ptr surface_; }; TEST_F(DataDeviceTest, Destroy) { std::vector 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 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)); 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)); int result = device_->OnPerformDrop(event); EXPECT_EQ(ui::DragDropTypes::DRAG_LINK, result); ASSERT_EQ(1u, delegate_.PopEvents(&events)); EXPECT_EQ(DataEvent::kDrop, events[0]); } 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 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)); 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, DeleteDataOfferDuringDrag) { ui::DropTargetEvent event(data_, gfx::PointF(), gfx::PointF(), ui::DragDropTypes::DRAG_MOVE); ui::Event::DispatcherApi(&event).set_target(surface_->window()); std::vector 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)); EXPECT_EQ(0u, delegate_.PopEvents(&events)); device_->OnPerformDrop(event); 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 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)); 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)); int result = device_->OnPerformDrop(event); EXPECT_EQ(ui::DragDropTypes::DRAG_NONE, result); 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 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)); EXPECT_EQ(0u, delegate_.PopEvents(&events)); device_->OnPerformDrop(event); EXPECT_EQ(0u, delegate_.PopEvents(&events)); } TEST_F(DataDeviceTest, ClipboardCopy) { // Selection event sent when getting a focus. device_->OnSurfaceFocusing(surface_.get()); std::vector 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_->OnSurfaceFocusing(surface_.get()); 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 focuse does not create events. device_->OnSurfaceFocusing(nullptr); EXPECT_EQ(0u, delegate_.PopEvents(&events)); } TEST_F(DataDeviceTest, ClipboardCopyWithoutFocus) { device_->OnClipboardDataChanged(); std::vector events; EXPECT_EQ(0u, delegate_.PopEvents(&events)); } TEST_F(DataDeviceTest, ClipboardDeviceCreatedAfterFocus) { seat_->set_focused_surface(surface_.get()); device_.reset(); std::vector events; delegate_.PopEvents(&events); device_ = std::make_unique(&delegate_, seat_.get(), &file_helper_); ASSERT_EQ(2u, delegate_.PopEvents(&events)); EXPECT_EQ(DataEvent::kOffer, events[0]); EXPECT_EQ(DataEvent::kSelection, events[1]); } TEST_F(DataDeviceTest, ClipboardFocusedSurfaceDestroyed) { device_->OnSurfaceFocusing(surface_.get()); surface_.reset(); std::vector events; delegate_.PopEvents(&events); device_->OnClipboardDataChanged(); EXPECT_EQ(0u, delegate_.PopEvents(&events)); } } // namespace } // namespace exo