| // Copyright 2018 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ui/ozone/platform/wayland/host/wayland_clipboard.h" |
| |
| #include <linux/input.h> |
| #include <wayland-server.h> |
| |
| #include <cstring> |
| #include <memory> |
| #include <string> |
| #include <string_view> |
| #include <vector> |
| |
| #include "base/containers/flat_set.h" |
| #include "base/containers/span.h" |
| #include "base/containers/to_vector.h" |
| #include "base/functional/bind.h" |
| #include "base/location.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/run_loop.h" |
| #include "base/strings/string_view_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "base/task/thread_pool/thread_pool_instance.h" |
| #include "base/test/bind.h" |
| #include "base/test/mock_callback.h" |
| #include "build/buildflag.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/base/clipboard/clipboard_buffer.h" |
| #include "ui/base/clipboard/clipboard_constants.h" |
| #include "ui/events/base_event_utils.h" |
| #include "ui/gfx/geometry/point.h" |
| #include "ui/ozone/platform/wayland/host/wayland_keyboard.h" |
| #include "ui/ozone/platform/wayland/host/wayland_pointer.h" |
| #include "ui/ozone/platform/wayland/host/wayland_seat.h" |
| #include "ui/ozone/platform/wayland/host/wayland_serial_tracker.h" |
| #include "ui/ozone/platform/wayland/host/wayland_touch.h" |
| #include "ui/ozone/platform/wayland/test/mock_pointer.h" |
| #include "ui/ozone/platform/wayland/test/mock_surface.h" |
| #include "ui/ozone/platform/wayland/test/test_data_device.h" |
| #include "ui/ozone/platform/wayland/test/test_data_device_manager.h" |
| #include "ui/ozone/platform/wayland/test/test_data_offer.h" |
| #include "ui/ozone/platform/wayland/test/test_data_source.h" |
| #include "ui/ozone/platform/wayland/test/test_keyboard.h" |
| #include "ui/ozone/platform/wayland/test/test_selection_device_manager.h" |
| #include "ui/ozone/platform/wayland/test/test_touch.h" |
| #include "ui/ozone/platform/wayland/test/test_wayland_server_thread.h" |
| #include "ui/ozone/platform/wayland/test/wayland_test.h" |
| #include "ui/ozone/public/platform_clipboard.h" |
| |
| using testing::_; |
| using testing::Eq; |
| using testing::IsEmpty; |
| using testing::IsNull; |
| using testing::Mock; |
| using testing::Values; |
| |
| namespace ui { |
| |
| namespace { |
| |
| constexpr char kSampleClipboardText[] = "This is a sample text for clipboard."; |
| |
| ui::PlatformClipboard::Data ToClipboardData(std::string_view data_string) { |
| return base::MakeRefCounted<base::RefCountedBytes>( |
| base::ToVector(base::as_byte_span(data_string))); |
| } |
| |
| // This must be called on the server thread. |
| wl::TestSelectionDevice* GetSelectionDevice(wl::TestWaylandServerThread* server, |
| ClipboardBuffer buffer) { |
| DCHECK(server->task_runner()->BelongsToCurrentThread()); |
| |
| return buffer == ClipboardBuffer::kSelection |
| ? server->primary_selection_device_manager()->device() |
| : server->data_device_manager()->data_device(); |
| } |
| |
| // This must be called on the server thread. |
| wl::TestSelectionSource* GetSelectionSource(wl::TestWaylandServerThread* server, |
| ClipboardBuffer buffer) { |
| DCHECK(server->task_runner()->BelongsToCurrentThread()); |
| |
| return buffer == ClipboardBuffer::kSelection |
| ? server->primary_selection_device_manager()->source() |
| : server->data_device_manager()->data_source(); |
| } |
| |
| } // namespace |
| |
| class WaylandClipboardTestBase : public WaylandTest { |
| public: |
| WaylandClipboardTestBase() = default; |
| WaylandClipboardTestBase(const WaylandClipboardTestBase&) = delete; |
| WaylandClipboardTestBase& operator=(const WaylandClipboardTestBase&) = delete; |
| ~WaylandClipboardTestBase() override = default; |
| |
| void SetUp() override { |
| WaylandTest::SetUp(); |
| |
| PostToServerAndWait([](wl::TestWaylandServerThread* server) { |
| wl_seat_send_capabilities(server->seat()->resource(), |
| WL_SEAT_CAPABILITY_POINTER | |
| WL_SEAT_CAPABILITY_TOUCH | |
| WL_SEAT_CAPABILITY_KEYBOARD); |
| |
| ASSERT_TRUE(server->data_device_manager()); |
| }); |
| ASSERT_TRUE(connection_->seat()->pointer()); |
| ASSERT_TRUE(connection_->seat()->touch()); |
| ASSERT_TRUE(connection_->seat()->keyboard()); |
| |
| clipboard_ = connection_->clipboard(); |
| ASSERT_TRUE(clipboard_); |
| |
| MaybeSetUpXkb(); |
| } |
| |
| void TearDown() override { |
| WaylandTest::TearDown(); |
| clipboard_ = nullptr; |
| } |
| |
| protected: |
| // Ensure the requests/events are flushed and posted tasks get processed. |
| // wl::TestSelection{Source,Offer} use ThreadPool task runners to read/write |
| // selection data, so the pool must be explicitly flushed as well. |
| void WaitForClipboardTasks() { |
| WaylandTestBase::SyncDisplay(); |
| base::ThreadPoolInstance::Get()->FlushForTesting(); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| void SentPointerButtonPress(const gfx::Point& location) { |
| PostToServerAndWait( |
| [&location, surface_id = window_->root_surface()->get_surface_id()]( |
| wl::TestWaylandServerThread* server) { |
| auto* const pointer = server->seat()->pointer()->resource(); |
| auto* const surface = |
| server->GetObject<wl::MockSurface>(surface_id)->resource(); |
| |
| wl_pointer_send_enter(pointer, server->GetNextSerial(), surface, |
| wl_fixed_from_int(location.x()), |
| wl_fixed_from_int(location.y())); |
| wl_pointer_send_button(pointer, server->GetNextSerial(), |
| server->GetNextTime(), BTN_LEFT, |
| WL_POINTER_BUTTON_STATE_PRESSED); |
| }); |
| } |
| void SendTouchDown(const gfx::Point& location) { |
| PostToServerAndWait( |
| [location, surface_id = window_->root_surface()->get_surface_id()]( |
| wl::TestWaylandServerThread* server) { |
| auto* const touch = server->seat()->touch()->resource(); |
| auto* const surface = |
| server->GetObject<wl::MockSurface>(surface_id)->resource(); |
| |
| wl_touch_send_down(touch, server->GetNextSerial(), |
| server->GetNextTime(), surface, 0 /* id */, |
| wl_fixed_from_int(location.x()), |
| wl_fixed_from_int(location.y())); |
| wl_touch_send_frame(touch); |
| }); |
| } |
| |
| void SendTouchUp() { |
| PostToServerAndWait([](wl::TestWaylandServerThread* server) { |
| auto* const touch = server->seat()->touch()->resource(); |
| |
| wl_touch_send_up(touch, server->GetNextSerial(), server->GetNextTime(), |
| 0 /* id */); |
| }); |
| } |
| |
| void SendKeyboardKey() { |
| PostToServerAndWait( |
| [surface_id = window_->root_surface()->get_surface_id()]( |
| wl::TestWaylandServerThread* server) { |
| auto* const keyboard = server->seat()->keyboard()->resource(); |
| auto* const surface = |
| server->GetObject<wl::MockSurface>(surface_id)->resource(); |
| |
| wl::ScopedWlArray empty({}); |
| wl_keyboard_send_enter(keyboard, server->GetNextSerial(), surface, |
| empty.get()); |
| wl_keyboard_send_key(keyboard, server->GetNextSerial(), |
| server->GetNextTime(), 30 /* a */, |
| WL_KEYBOARD_KEY_STATE_PRESSED); |
| }); |
| } |
| |
| raw_ptr<WaylandClipboard> clipboard_ = nullptr; |
| }; |
| |
| class WaylandClipboardTest : public WaylandClipboardTestBase { |
| public: |
| WaylandClipboardTest() = default; |
| WaylandClipboardTest(const WaylandClipboardTest&) = delete; |
| WaylandClipboardTest& operator=(const WaylandClipboardTest&) = delete; |
| ~WaylandClipboardTest() override = default; |
| |
| void SetUp() override { |
| WaylandClipboardTestBase::SetUp(); |
| |
| PostToServerAndWait( |
| [primary_selection_protocol = GetParam().primary_selection_protocol]( |
| wl::TestWaylandServerThread* server) { |
| ASSERT_TRUE(primary_selection_protocol == |
| wl::PrimarySelectionProtocol::kNone || |
| server->primary_selection_device_manager()); |
| }); |
| |
| // Make sure clipboard instance for the available primary selection protocol |
| // gets properly created, ie: the corresponding 'get_device' request is |
| // issued, so server-side objects are created prior to test-case specific |
| // calls, otherwise tests, such as ReadFromClipboard, would crash. |
| ASSERT_EQ(WhichBufferToUse() == ClipboardBuffer::kSelection, |
| !!clipboard_->GetClipboard(ClipboardBuffer::kSelection)); |
| WaylandTestBase::SyncDisplay(); |
| |
| offered_data_.clear(); |
| } |
| |
| protected: |
| ClipboardBuffer WhichBufferToUse() const { |
| return GetParam().primary_selection_protocol != |
| wl::PrimarySelectionProtocol::kNone |
| ? ClipboardBuffer::kSelection |
| : ClipboardBuffer::kCopyPaste; |
| } |
| |
| // Fill the clipboard backing store with sample data. |
| void OfferData(ClipboardBuffer buffer, |
| std::string_view data, |
| const std::string& mime_type) { |
| offered_data_[mime_type] = base::MakeRefCounted<base::RefCountedBytes>( |
| base::ToVector(base::as_byte_span(data))); |
| |
| base::MockCallback<PlatformClipboard::OfferDataClosure> offer_callback; |
| EXPECT_CALL(offer_callback, Run()).Times(1); |
| clipboard_->OfferClipboardData(buffer, offered_data_, offer_callback.Get()); |
| } |
| |
| PlatformClipboard::DataMap offered_data_; |
| }; |
| |
| class CopyPasteOnlyClipboardTest : public WaylandClipboardTestBase { |
| public: |
| void SetUp() override { |
| WaylandClipboardTestBase::SetUp(); |
| |
| ASSERT_FALSE(clipboard_->IsSelectionBufferAvailable()); |
| |
| ASSERT_EQ(wl::PrimarySelectionProtocol::kNone, |
| GetParam().primary_selection_protocol); |
| |
| PostToServerAndWait([](wl::TestWaylandServerThread* server) { |
| ASSERT_TRUE(server->data_device_manager()); |
| ASSERT_FALSE(server->primary_selection_device_manager()); |
| }); |
| } |
| }; |
| |
| // Verifies that copy-to-clipboard works as expected. Actual Wayland input |
| // events are used in order to exercise all the components involved, e.g: |
| // Wayland{Pointer,Keyboard,Touch}, Serial tracker and WaylandClipboard. |
| // |
| // Regression test for https://crbug.com/1282220. |
| // TODO(crbug.com/41495216): Flaky test. |
| TEST_P(WaylandClipboardTest, DISABLED_WriteToClipboard) { |
| const base::RepeatingClosure send_input_event_closures[]{ |
| // Mouse button press |
| base::BindLambdaForTesting([&]() { |
| SentPointerButtonPress({10, 10}); |
| }), |
| // Key press |
| base::BindLambdaForTesting([&]() { SendKeyboardKey(); }), |
| // Touch down |
| base::BindLambdaForTesting([&]() { |
| SendTouchDown({200, 200}); |
| }), |
| // Touch tap (down > up) |
| base::BindLambdaForTesting([&]() { |
| SendTouchDown({300, 300}); |
| SendTouchUp(); |
| })}; |
| |
| auto* window_manager = connection_->window_manager(); |
| |
| // Triggering copy on touch-down event. |
| for (auto send_input_event : send_input_event_closures) { |
| PostToServerAndWait([](wl::TestWaylandServerThread* server) { |
| if (GetParam().primary_selection_protocol != |
| wl::PrimarySelectionProtocol::kNone) { |
| server->primary_selection_device_manager()->set_source(nullptr); |
| } else { |
| server->data_device_manager()->set_data_source(nullptr); |
| } |
| }); |
| |
| send_input_event.Run(); |
| auto client_selection_serial = connection_->serial_tracker().GetSerial( |
| {wl::SerialType::kTouchPress, wl::SerialType::kMousePress, |
| wl::SerialType::kKeyPress}); |
| ASSERT_TRUE(client_selection_serial.has_value()); |
| |
| // 1. Offer sample text as selection data. |
| OfferData(WhichBufferToUse(), kSampleClipboardText, |
| {kMimeTypeUtf8PlainText}); |
| WaylandTestBase::SyncDisplay(); |
| |
| // 2. Emulate an external client requesting to read the offered data and |
| // make sure the appropriate string gets delivered. |
| // These three objects are accessed from the server thread, but must persist |
| // several server calls, that is why they are allocated here. |
| std::string delivered_text; |
| base::MockCallback<wl::TestSelectionSource::ReadDataCallback> callback; |
| base::RunLoop run_loop; |
| |
| PostToServerAndWait([&delivered_text, &callback, |
| buffer = WhichBufferToUse(), |
| serial = client_selection_serial->value, |
| quit_closure = run_loop.QuitClosure()]( |
| wl::TestWaylandServerThread* server) { |
| ASSERT_TRUE(GetSelectionSource(server, buffer)); |
| |
| EXPECT_EQ(serial, GetSelectionDevice(server, buffer)->selection_serial()); |
| |
| EXPECT_CALL(callback, Run(_)) |
| .WillOnce( |
| [&delivered_text, quit_closure](std::vector<uint8_t>&& data) { |
| delivered_text = std::string(data.begin(), data.end()); |
| quit_closure.Run(); |
| }); |
| |
| GetSelectionSource(server, buffer) |
| ->ReadData(kMimeTypeUtf8PlainText, callback.Get()); |
| }); |
| |
| WaitForClipboardTasks(); |
| |
| PostToServerAndWait([&delivered_text](wl::TestWaylandServerThread* server) { |
| EXPECT_EQ(kSampleClipboardText, delivered_text); |
| }); |
| |
| window_manager->SetPointerFocusedWindow(nullptr); |
| window_manager->SetTouchFocusedWindow(nullptr); |
| window_manager->SetKeyboardFocusedWindow(nullptr); |
| } |
| } |
| |
| TEST_P(WaylandClipboardTest, ReadFromClipboard) { |
| // 1. Emulate a selection data offer coming in. |
| PostToServerAndWait( |
| [buffer = WhichBufferToUse()](wl::TestWaylandServerThread* server) { |
| auto* device = GetSelectionDevice(server, buffer); |
| auto* data_offer = device->OnDataOffer(); |
| data_offer->OnOffer(kMimeTypeUtf8PlainText, |
| ToClipboardData(kSampleClipboardText)); |
| device->OnSelection(data_offer); |
| }); |
| |
| // 2. Request to read the offered data and check whether the read text matches |
| // the previously offered one. |
| std::string text; |
| base::MockCallback<PlatformClipboard::RequestDataClosure> callback; |
| EXPECT_CALL(callback, Run(_)).WillOnce([&text](PlatformClipboard::Data data) { |
| ASSERT_TRUE(data); |
| text = std::string(base::as_string_view(*data)); |
| }); |
| |
| clipboard_->RequestClipboardData(WhichBufferToUse(), kMimeTypeUtf8PlainText, |
| callback.Get()); |
| EXPECT_EQ(kSampleClipboardText, text); |
| } |
| |
| // Regression test for crbug.com/1183939. Ensures unicode mime types take |
| // priority over text/plain when reading text. |
| TEST_P(WaylandClipboardTest, ReadFromClipboardPrioritizeUtf) { |
| PostToServerAndWait( |
| [buffer = WhichBufferToUse()](wl::TestWaylandServerThread* server) { |
| auto* data_offer = GetSelectionDevice(server, buffer)->OnDataOffer(); |
| data_offer->OnOffer(kMimeTypePlainText, ToClipboardData("ascii_text")); |
| data_offer->OnOffer(kMimeTypeUtf8PlainText, |
| ToClipboardData("utf8_text")); |
| GetSelectionDevice(server, buffer)->OnSelection(data_offer); |
| }); |
| |
| std::string text; |
| base::MockCallback<PlatformClipboard::RequestDataClosure> callback; |
| EXPECT_CALL(callback, Run(_)).WillOnce([&text](PlatformClipboard::Data data) { |
| ASSERT_TRUE(data); |
| text = std::string(base::as_string_view(*data)); |
| }); |
| |
| clipboard_->RequestClipboardData(WhichBufferToUse(), kMimeTypeUtf8PlainText, |
| callback.Get()); |
| EXPECT_EQ("utf8_text", text); |
| } |
| |
| TEST_P(WaylandClipboardTest, ReadFromClipboardWithoutOffer) { |
| // When no data offer is advertised and client requests clipboard data from |
| // the server, the response callback should be gracefully called with null |
| // data. |
| base::MockCallback<PlatformClipboard::RequestDataClosure> callback; |
| EXPECT_CALL(callback, Run(Eq(nullptr))).Times(1); |
| clipboard_->RequestClipboardData(WhichBufferToUse(), kMimeTypeUtf8PlainText, |
| callback.Get()); |
| } |
| |
| TEST_P(WaylandClipboardTest, IsSelectionOwner) { |
| connection_->serial_tracker().UpdateSerial(wl::SerialType::kMousePress, 1); |
| |
| OfferData(WhichBufferToUse(), kSampleClipboardText, {kMimeTypeUtf8PlainText}); |
| |
| PostToServerAndWait( |
| [buffer = WhichBufferToUse()](wl::TestWaylandServerThread* server) { |
| ASSERT_TRUE(GetSelectionSource(server, buffer)); |
| }); |
| |
| ASSERT_TRUE(clipboard_->IsSelectionOwner(WhichBufferToUse())); |
| |
| // The compositor sends OnCancelled whenever another application on the system |
| // sets a new selection. It means we are not the application that owns the |
| // current selection data. |
| PostToServerAndWait( |
| [buffer = WhichBufferToUse()](wl::TestWaylandServerThread* server) { |
| GetSelectionSource(server, buffer)->OnCancelled(); |
| }); |
| |
| ASSERT_FALSE(clipboard_->IsSelectionOwner(WhichBufferToUse())); |
| |
| connection_->serial_tracker().ResetSerial(wl::SerialType::kMousePress); |
| } |
| |
| // Ensures WaylandClipboard correctly handles overlapping read requests for |
| // different clipboard buffers. |
| TEST_P(WaylandClipboardTest, OverlapReadingFromDifferentBuffers) { |
| // Offer a sample text as selection data. |
| PostToServerAndWait( |
| [buffer = WhichBufferToUse()](wl::TestWaylandServerThread* server) { |
| auto* data_offer = GetSelectionDevice(server, buffer)->OnDataOffer(); |
| data_offer->OnOffer(kMimeTypeUtf8PlainText, |
| ToClipboardData(kSampleClipboardText)); |
| GetSelectionDevice(server, buffer)->OnSelection(data_offer); |
| }); |
| |
| // Post a read request for the other buffer, which will start its execution |
| // after the request above. |
| auto other_buffer = WhichBufferToUse() == ClipboardBuffer::kSelection |
| ? ClipboardBuffer::kCopyPaste |
| : ClipboardBuffer::kSelection; |
| base::MockCallback<PlatformClipboard::RequestDataClosure> callback; |
| EXPECT_CALL(callback, Run(Eq(nullptr))).Times(1); |
| base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(&PlatformClipboard::RequestClipboardData, |
| base::Unretained(clipboard_), other_buffer, |
| kMimeTypeUtf8PlainText, callback.Get())); |
| |
| // Instantly start a clipboard read request for kCopyPaste buffer (the actual |
| // data transfer will take place asynchronously. See WaylandDataDevice impl) |
| // and ensure read callback is called with the expected resulting data, |
| // regardless any other request that may arrive in the meantime. |
| std::string text; |
| base::MockCallback<PlatformClipboard::RequestDataClosure> got_text; |
| EXPECT_CALL(got_text, Run(_)).WillOnce([&](PlatformClipboard::Data data) { |
| ASSERT_NE(nullptr, data); |
| text = std::string(base::as_string_view(*data)); |
| }); |
| clipboard_->RequestClipboardData(WhichBufferToUse(), kMimeTypeUtf8PlainText, |
| got_text.Get()); |
| |
| WaitForClipboardTasks(); |
| EXPECT_EQ(kSampleClipboardText, text); |
| } |
| |
| // Ensures clipboard change callback is fired only once per read/write. |
| TEST_P(WaylandClipboardTest, ClipboardChangeNotifications) { |
| base::MockCallback<PlatformClipboard::ClipboardDataChangedCallback> |
| clipboard_changed_callback; |
| clipboard_->SetClipboardDataChangedCallback(clipboard_changed_callback.Get()); |
| const auto buffer = WhichBufferToUse(); |
| |
| // 1. For selection offered by an external application. |
| EXPECT_CALL(clipboard_changed_callback, Run(buffer)).Times(1); |
| PostToServerAndWait( |
| [buffer = WhichBufferToUse()](wl::TestWaylandServerThread* server) { |
| auto* data_offer = GetSelectionDevice(server, buffer)->OnDataOffer(); |
| data_offer->OnOffer(kMimeTypeUtf8PlainText, |
| ToClipboardData(kSampleClipboardText)); |
| GetSelectionDevice(server, buffer)->OnSelection(data_offer); |
| }); |
| EXPECT_FALSE(clipboard_->IsSelectionOwner(buffer)); |
| |
| // 2. For selection offered by Chromium. |
| connection_->serial_tracker().UpdateSerial(wl::SerialType::kMousePress, 1); |
| EXPECT_CALL(clipboard_changed_callback, Run(buffer)).Times(1); |
| OfferData(buffer, kSampleClipboardText, {kMimeTypeUtf8PlainText}); |
| PostToServerAndWait( |
| [buffer = WhichBufferToUse()](wl::TestWaylandServerThread* server) { |
| ASSERT_TRUE(GetSelectionSource(server, buffer)); |
| }); |
| EXPECT_TRUE(clipboard_->IsSelectionOwner(buffer)); |
| connection_->serial_tracker().ResetSerial(wl::SerialType::kMousePress); |
| } |
| |
| // Verifies clipboard calls targeting primary selection buffer no-op and run |
| // gracefully when no primary selection protocol is available. |
| TEST_P(CopyPasteOnlyClipboardTest, PrimarySelectionRequestsNoop) { |
| const auto buffer = ClipboardBuffer::kSelection; |
| |
| base::MockCallback<PlatformClipboard::OfferDataClosure> offer_done; |
| EXPECT_CALL(offer_done, Run()).Times(1); |
| clipboard_->OfferClipboardData(buffer, {}, offer_done.Get()); |
| EXPECT_FALSE(clipboard_->IsSelectionOwner(buffer)); |
| |
| base::MockCallback<PlatformClipboard::RequestDataClosure> got_data; |
| EXPECT_CALL(got_data, Run(IsNull())).Times(1); |
| clipboard_->RequestClipboardData(buffer, kMimeTypeUtf8PlainText, |
| got_data.Get()); |
| |
| base::MockCallback<PlatformClipboard::GetMimeTypesClosure> got_mime_types; |
| EXPECT_CALL(got_mime_types, Run(IsEmpty())).Times(1); |
| clipboard_->GetAvailableMimeTypes(buffer, got_mime_types.Get()); |
| } |
| |
| // Makes sure overlapping read requests for the same clipboard buffer are |
| // properly handled. |
| // TODO(crbug.com/40398800): Re-enable once Clipboard API becomes async. |
| TEST_P(CopyPasteOnlyClipboardTest, DISABLED_OverlappingReadRequests) { |
| // Create an selection data offer containing plain and html mime types. |
| PostToServerAndWait([](wl::TestWaylandServerThread* server) { |
| auto* data_device = server->data_device_manager()->data_device(); |
| auto* data_offer = data_device->OnDataOffer(); |
| data_offer->OnOffer(kMimeTypePlainText, ToClipboardData("text")); |
| data_offer->OnOffer(kMimeTypeHtml, ToClipboardData("html")); |
| data_device->OnSelection(data_offer); |
| }); |
| |
| // Schedule a clipboard read task (for text/html mime type). As read requests |
| // are processed asynchronously, this will actually start when the request |
| // below is already under processing, thus emulating 2 "overlapping" requests. |
| std::string html; |
| base::MockCallback<PlatformClipboard::RequestDataClosure> got_html; |
| EXPECT_CALL(got_html, Run(_)).WillOnce([&](PlatformClipboard::Data data) { |
| html = std::string(base::as_string_view(*data)); |
| }); |
| base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&PlatformClipboard::RequestClipboardData, |
| base::Unretained(clipboard_), ClipboardBuffer::kCopyPaste, |
| kMimeTypeHtml, got_html.Get())); |
| |
| // Instantly start a read request for text/plain mime type. |
| std::string text; |
| base::MockCallback<PlatformClipboard::RequestDataClosure> got_text; |
| EXPECT_CALL(got_text, Run(_)).WillOnce([&](PlatformClipboard::Data data) { |
| text = std::string(base::as_string_view(*data)); |
| }); |
| clipboard_->RequestClipboardData(ClipboardBuffer::kCopyPaste, |
| kMimeTypePlainText, got_text.Get()); |
| |
| // Wait for clipboard tasks to complete and ensure both requests were |
| // processed correctly. |
| WaitForClipboardTasks(); |
| EXPECT_EQ("html", html); |
| EXPECT_EQ("text", text); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| WithZwpPrimarySelection, |
| WaylandClipboardTest, |
| Values(wl::ServerConfig{ |
| .primary_selection_protocol = wl::PrimarySelectionProtocol::kZwp})); |
| |
| INSTANTIATE_TEST_SUITE_P( |
| WithGtkPrimarySelection, |
| WaylandClipboardTest, |
| Values(wl::ServerConfig{ |
| .primary_selection_protocol = wl::PrimarySelectionProtocol::kGtk})); |
| |
| INSTANTIATE_TEST_SUITE_P( |
| WithoutPrimarySelection, |
| WaylandClipboardTest, |
| Values(wl::ServerConfig{ |
| .primary_selection_protocol = wl::PrimarySelectionProtocol::kNone})); |
| |
| INSTANTIATE_TEST_SUITE_P( |
| WithoutPrimarySelection, |
| CopyPasteOnlyClipboardTest, |
| Values(wl::ServerConfig{ |
| .primary_selection_protocol = wl::PrimarySelectionProtocol::kNone})); |
| |
| } // namespace ui |