// Copyright 2017 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "components/exo/data_offer.h" #include #include #include #include #include #include "base/callback_forward.h" #include "base/containers/flat_set.h" #include "base/files/file_util.h" #include "base/pickle.h" #include "base/run_loop.h" #include "base/strings/utf_string_conversions.h" #include "build/chromeos_buildflags.h" #include "cc/test/pixel_comparator.h" #include "cc/test/pixel_test_utils.h" #include "components/exo/data_device.h" #include "components/exo/data_exchange_delegate.h" #include "components/exo/data_offer_delegate.h" #include "components/exo/test/exo_test_base.h" #include "components/exo/test/exo_test_data_exchange_delegate.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/skia/include/core/SkBitmap.h" #include "ui/base/clipboard/clipboard.h" #include "ui/base/clipboard/clipboard_format_type.h" #include "ui/base/clipboard/scoped_clipboard_writer.h" #include "ui/base/data_transfer_policy/data_transfer_policy_controller.h" #include "ui/base/dragdrop/os_exchange_data.h" #include "ui/gfx/codec/png_codec.h" #include "url/gurl.h" namespace exo { namespace { class DataOfferTest : public test::ExoTestBase { public: void TearDown() override { ui::Clipboard::DestroyClipboardForCurrentThread(); test::ExoTestBase::TearDown(); } }; class TestDataOfferDelegate : public DataOfferDelegate { public: TestDataOfferDelegate() {} TestDataOfferDelegate(const TestDataOfferDelegate&) = delete; TestDataOfferDelegate& operator=(const TestDataOfferDelegate&) = delete; // Called at the top of the data device's destructor, to give observers a // chance to remove themselves. void OnDataOfferDestroying(DataOffer* offer) override {} // Called when |mime_type| is offered by the client. void OnOffer(const std::string& mime_type) override { mime_types_.insert(mime_type); } // Called when possible |source_actions| is offered by the client. void OnSourceActions( const base::flat_set& source_actions) override { source_actions_ = source_actions; } // Called when current |action| is offered by the client. void OnAction(DndAction dnd_action) override { dnd_action_ = dnd_action; } const base::flat_set& mime_types() const { return mime_types_; } const base::flat_set& source_actions() const { return source_actions_; } DndAction dnd_action() const { return dnd_action_; } private: base::flat_set mime_types_; base::flat_set source_actions_; DndAction dnd_action_ = DndAction::kNone; }; class TestDataTransferPolicyController : ui::DataTransferPolicyController { public: TestDataTransferPolicyController() = default; TestDataTransferPolicyController(TestDataTransferPolicyController&) = delete; TestDataTransferPolicyController& operator=( const TestDataTransferPolicyController&) = delete; ui::EndpointType last_src_type() const { return last_src_type_; } ui::EndpointType last_dst_type() const { return last_dst_type_; } private: // ui::DataTransferPolicyController: bool IsClipboardReadAllowed(const ui::DataTransferEndpoint* const data_src, const ui::DataTransferEndpoint* const data_dst, const absl::optional size) override { if (data_src) last_src_type_ = data_src->type(); last_dst_type_ = data_dst->type(); return true; } void PasteIfAllowed(const ui::DataTransferEndpoint* const data_src, const ui::DataTransferEndpoint* const data_dst, const absl::optional size, content::RenderFrameHost* web_contents, base::OnceCallback callback) override {} void DropIfAllowed(const ui::DataTransferEndpoint* const data_src, const ui::DataTransferEndpoint* const data_dst, base::OnceClosure drop_cb) override { std::move(drop_cb).Run(); } ui::EndpointType last_src_type_ = ui::EndpointType::kUnknownVm; ui::EndpointType last_dst_type_ = ui::EndpointType::kUnknownVm; }; bool ReadString(base::ScopedFD fd, std::string* out) { std::array buffer; char* it = buffer.begin(); while (it != buffer.end()) { int result = read(fd.get(), it, buffer.end() - it); PCHECK(-1 != result); if (result == 0) break; it += result; } *out = std::string(reinterpret_cast(buffer.data()), (it - buffer.begin()) / sizeof(char)); return true; } bool ReadString16(base::ScopedFD fd, std::u16string* out) { std::array buffer; char* it = buffer.begin(); while (it != buffer.end()) { int result = read(fd.get(), it, buffer.end() - it); PCHECK(-1 != result); if (result == 0) break; it += result; } *out = std::u16string(reinterpret_cast(buffer.data()), (it - buffer.begin()) / sizeof(char16_t)); return true; } TEST_F(DataOfferTest, SetTextDropData) { base::flat_set source_actions; source_actions.insert(DndAction::kCopy); source_actions.insert(DndAction::kMove); ui::OSExchangeData data; data.SetString(std::u16string(u"Test data")); TestDataOfferDelegate delegate; DataOffer data_offer(&delegate); EXPECT_EQ(0u, delegate.mime_types().size()); EXPECT_EQ(0u, delegate.source_actions().size()); EXPECT_EQ(DndAction::kNone, delegate.dnd_action()); TestDataExchangeDelegate data_exchange_delegate; data_offer.SetDropData(&data_exchange_delegate, nullptr, data); data_offer.SetSourceActions(source_actions); data_offer.SetActions(base::flat_set(), DndAction::kMove); EXPECT_EQ(1u, delegate.mime_types().count("text/plain;charset=utf-8")); EXPECT_EQ(1u, delegate.mime_types().count("text/plain;charset=utf-16")); EXPECT_EQ(2u, delegate.source_actions().size()); EXPECT_EQ(1u, delegate.source_actions().count(DndAction::kCopy)); EXPECT_EQ(1u, delegate.source_actions().count(DndAction::kMove)); EXPECT_EQ(DndAction::kMove, delegate.dnd_action()); } TEST_F(DataOfferTest, SetHTMLDropData) { const std::string html_data = "Test HTML data 🔥 ❄"; base::flat_set source_actions; source_actions.insert(DndAction::kCopy); source_actions.insert(DndAction::kMove); ui::OSExchangeData data; data.SetHtml(base::UTF8ToUTF16(html_data), GURL()); TestDataOfferDelegate delegate; DataOffer data_offer(&delegate); EXPECT_EQ(0u, delegate.mime_types().size()); EXPECT_EQ(0u, delegate.source_actions().size()); EXPECT_EQ(DndAction::kNone, delegate.dnd_action()); TestDataExchangeDelegate data_exchange_delegate; data_offer.SetDropData(&data_exchange_delegate, nullptr, data); data_offer.SetSourceActions(source_actions); data_offer.SetActions(base::flat_set(), DndAction::kMove); EXPECT_EQ(1u, delegate.mime_types().count("text/html;charset=utf-8")); EXPECT_EQ(1u, delegate.mime_types().count("text/html;charset=utf-16")); EXPECT_EQ(2u, delegate.source_actions().size()); EXPECT_EQ(1u, delegate.source_actions().count(DndAction::kCopy)); EXPECT_EQ(1u, delegate.source_actions().count(DndAction::kMove)); EXPECT_EQ(DndAction::kMove, delegate.dnd_action()); base::ScopedFD read, write; std::string result; EXPECT_TRUE(base::CreatePipe(&read, &write)); data_offer.Receive("text/html;charset=utf-8", std::move(write)); ReadString(std::move(read), &result); EXPECT_EQ(result, html_data); std::u16string result16; EXPECT_TRUE(base::CreatePipe(&read, &write)); data_offer.Receive("text/html;charset=utf-16", std::move(write)); ReadString16(std::move(read), &result16); EXPECT_EQ(result16, base::UTF8ToUTF16(html_data)); } TEST_F(DataOfferTest, SetFileDropData) { TestDataOfferDelegate delegate; DataOffer data_offer(&delegate); TestDataExchangeDelegate data_exchange_delegate; ui::OSExchangeData data; data.SetFilename(base::FilePath("/test/downloads/file")); data_offer.SetDropData(&data_exchange_delegate, nullptr, data); EXPECT_EQ(1u, delegate.mime_types().size()); EXPECT_EQ(1u, delegate.mime_types().count("text/uri-list")); } TEST_F(DataOfferTest, SetPickleDropData) { TestDataOfferDelegate delegate; DataOffer data_offer(&delegate); TestDataExchangeDelegate data_exchange_delegate; ui::OSExchangeData data; base::Pickle pickle; pickle.WriteUInt32(1); // num files pickle.WriteString("filesystem:chrome-extension://path/to/file1"); pickle.WriteInt64(1000); // file size pickle.WriteString("id"); // filesystem id data.SetPickledData( ui::ClipboardFormatType::GetType("chromium/x-file-system-files"), pickle); data_offer.SetDropData(&data_exchange_delegate, nullptr, data); EXPECT_EQ(1u, delegate.mime_types().size()); EXPECT_EQ(1u, delegate.mime_types().count("text/uri-list")); } TEST_F(DataOfferTest, SetFileContentsDropData) { TestDataOfferDelegate delegate; DataOffer data_offer(&delegate); TestDataExchangeDelegate data_exchange_delegate; ui::OSExchangeData data; data.provider().SetFileContents(base::FilePath("\"test file\".jpg"), std::string("test data")); data_offer.SetDropData(&data_exchange_delegate, nullptr, data); EXPECT_EQ(1u, delegate.mime_types().size()); EXPECT_EQ(1u, delegate.mime_types().count( "application/octet-stream;name=\"\\\"test file\\\".jpg\"")); } TEST_F(DataOfferTest, ReceiveString) { TestDataOfferDelegate delegate; DataOffer data_offer(&delegate); TestDataExchangeDelegate data_exchange_delegate; ui::OSExchangeData data; data.SetString(u"Test data"); data_offer.SetDropData(&data_exchange_delegate, nullptr, data); base::ScopedFD read_pipe; base::ScopedFD write_pipe; ASSERT_TRUE(base::CreatePipe(&read_pipe, &write_pipe)); data_offer.Receive("text/plain", std::move(write_pipe)); std::string result; ASSERT_TRUE(ReadString(std::move(read_pipe), &result)); EXPECT_EQ("Test data", result); base::ScopedFD read_pipe_16; base::ScopedFD write_pipe_16; ASSERT_TRUE(base::CreatePipe(&read_pipe_16, &write_pipe_16)); data_offer.Receive("text/plain;charset=utf-16", std::move(write_pipe_16)); std::u16string result_16; ASSERT_TRUE(ReadString16(std::move(read_pipe_16), &result_16)); EXPECT_EQ(u"Test data", result_16); base::ScopedFD read_pipe_8; base::ScopedFD write_pipe_8; ASSERT_TRUE(base::CreatePipe(&read_pipe_8, &write_pipe_8)); data_offer.Receive("text/plain;charset=utf-8", std::move(write_pipe_8)); std::string result_8; ASSERT_TRUE(ReadString(std::move(read_pipe_8), &result_8)); EXPECT_EQ("Test data", result_8); } TEST_F(DataOfferTest, ReceiveHTML) { TestDataOfferDelegate delegate; DataOffer data_offer(&delegate); TestDataExchangeDelegate data_exchange_delegate; ui::OSExchangeData data; data.SetHtml(u"Test HTML data", GURL()); data_offer.SetDropData(&data_exchange_delegate, nullptr, data); base::ScopedFD read_pipe_16; base::ScopedFD write_pipe_16; ASSERT_TRUE(base::CreatePipe(&read_pipe_16, &write_pipe_16)); data_offer.Receive("text/html;charset=utf-16", std::move(write_pipe_16)); std::u16string result_16; ASSERT_TRUE(ReadString16(std::move(read_pipe_16), &result_16)); EXPECT_EQ(u"Test HTML data", result_16); base::ScopedFD read_pipe_8; base::ScopedFD write_pipe_8; ASSERT_TRUE(base::CreatePipe(&read_pipe_8, &write_pipe_8)); data_offer.Receive("text/html;charset=utf-8", std::move(write_pipe_8)); std::string result_8; ASSERT_TRUE(ReadString(std::move(read_pipe_8), &result_8)); EXPECT_EQ("Test HTML data", result_8); } TEST_F(DataOfferTest, ReceiveUriList) { TestDataOfferDelegate delegate; DataOffer data_offer(&delegate); TestDataExchangeDelegate data_exchange_delegate; ui::OSExchangeData data; data.SetFilename(base::FilePath("/test/downloads/file")); data_offer.SetDropData(&data_exchange_delegate, nullptr, data); base::ScopedFD read_pipe; base::ScopedFD write_pipe; ASSERT_TRUE(base::CreatePipe(&read_pipe, &write_pipe)); data_offer.Receive("text/uri-list", std::move(write_pipe)); std::string result; ASSERT_TRUE(ReadString(std::move(read_pipe), &result)); EXPECT_EQ("file:///test/downloads/file", result); } TEST_F(DataOfferTest, ReceiveUriListFromPickle_ReceiveBeforeUrlIsResolved) { TestDataOfferDelegate delegate; DataOffer data_offer(&delegate); TestDataExchangeDelegate data_exchange_delegate; ui::OSExchangeData data; base::Pickle pickle; pickle.WriteUInt32(1); // num files pickle.WriteString("filesystem:chrome-extension://path/to/file1"); pickle.WriteInt64(1000); // file size pickle.WriteString("id"); // filesystem id data.SetPickledData( ui::ClipboardFormatType::GetType("chromium/x-file-system-files"), pickle); data_offer.SetDropData(&data_exchange_delegate, nullptr, data); base::ScopedFD read_pipe1; base::ScopedFD write_pipe1; ASSERT_TRUE(base::CreatePipe(&read_pipe1, &write_pipe1)); base::ScopedFD read_pipe2; base::ScopedFD write_pipe2; ASSERT_TRUE(base::CreatePipe(&read_pipe2, &write_pipe2)); // Receive is called (twice) before UrlsFromPickleCallback runs. data_offer.Receive("text/uri-list", std::move(write_pipe1)); data_offer.Receive("text/uri-list", std::move(write_pipe2)); // Run callback with a resolved URL. std::vector urls; urls.push_back( GURL("content://org.chromium.arc.chromecontentprovider/path/to/file1")); data_exchange_delegate.RunSendPickleCallback(urls); std::string result1; ASSERT_TRUE(ReadString(std::move(read_pipe1), &result1)); EXPECT_EQ("content://org.chromium.arc.chromecontentprovider/path/to/file1", result1); std::string result2; ASSERT_TRUE(ReadString(std::move(read_pipe2), &result2)); EXPECT_EQ("content://org.chromium.arc.chromecontentprovider/path/to/file1", result2); } TEST_F(DataOfferTest, ReceiveUriListFromPickle_ReceiveBeforeEmptyUrlIsReturned) { TestDataOfferDelegate delegate; DataOffer data_offer(&delegate); TestDataExchangeDelegate data_exchange_delegate; ui::OSExchangeData data; base::Pickle pickle; pickle.WriteUInt32(1); // num files pickle.WriteString("filesystem:chrome-extension://path/to/file1"); pickle.WriteInt64(1000); // file size pickle.WriteString("id"); // filesystem id data.SetPickledData( ui::ClipboardFormatType::GetType("chromium/x-file-system-files"), pickle); data_offer.SetDropData(&data_exchange_delegate, nullptr, data); base::ScopedFD read_pipe; base::ScopedFD write_pipe; ASSERT_TRUE(base::CreatePipe(&read_pipe, &write_pipe)); // Receive is called before UrlsCallback runs. data_offer.Receive("text/uri-list", std::move(write_pipe)); // Run callback with an empty URL. std::vector urls; urls.push_back(GURL("")); data_exchange_delegate.RunSendPickleCallback(urls); std::u16string result; ASSERT_TRUE(ReadString16(std::move(read_pipe), &result)); EXPECT_EQ(u"", result); } TEST_F(DataOfferTest, ReceiveFileContentsDropData) { TestDataOfferDelegate delegate; DataOffer data_offer(&delegate); TestDataExchangeDelegate data_exchange_delegate; ui::OSExchangeData data; const std::string expected = "test data"; data.provider().SetFileContents(base::FilePath("test.jpg"), expected); data_offer.SetDropData(&data_exchange_delegate, nullptr, data); base::ScopedFD read_pipe; base::ScopedFD write_pipe; ASSERT_TRUE(base::CreatePipe(&read_pipe, &write_pipe)); data_offer.Receive("application/octet-stream;name=\"test.jpg\"", std::move(write_pipe)); std::string result; ASSERT_TRUE(ReadString(std::move(read_pipe), &result)); EXPECT_EQ(expected, result); } TEST_F(DataOfferTest, SetClipboardDataPlainText) { TestDataOfferDelegate delegate; DataOffer data_offer(&delegate); TestDataExchangeDelegate data_exchange_delegate; { ui::ScopedClipboardWriter writer(ui::ClipboardBuffer::kCopyPaste); writer.WriteText(u"Test data"); } auto* window = CreateTestWindowInShellWithBounds(gfx::Rect()); data_offer.SetClipboardData( &data_exchange_delegate, *ui::Clipboard::GetForCurrentThread(), data_exchange_delegate.GetDataTransferEndpointType(window)); EXPECT_EQ(3u, delegate.mime_types().size()); EXPECT_EQ(1u, delegate.mime_types().count("text/plain;charset=utf-8")); EXPECT_EQ(1u, delegate.mime_types().count("text/plain;charset=utf-16")); EXPECT_EQ(1u, delegate.mime_types().count("UTF8_STRING")); base::ScopedFD read_pipe; base::ScopedFD write_pipe; // Read as utf-8. ASSERT_TRUE(base::CreatePipe(&read_pipe, &write_pipe)); data_offer.Receive("text/plain;charset=utf-8", std::move(write_pipe)); std::string result; ASSERT_TRUE(ReadString(std::move(read_pipe), &result)); EXPECT_EQ("Test data", result); // Read a second time. ASSERT_TRUE(base::CreatePipe(&read_pipe, &write_pipe)); data_offer.Receive("text/plain;charset=utf-8", std::move(write_pipe)); ASSERT_TRUE(ReadString(std::move(read_pipe), &result)); EXPECT_EQ("Test data", result); // Read as utf-16. ASSERT_TRUE(base::CreatePipe(&read_pipe, &write_pipe)); data_offer.Receive("text/plain;charset=utf-16", std::move(write_pipe)); std::u16string result16; ASSERT_TRUE(ReadString16(std::move(read_pipe), &result16)); EXPECT_EQ("Test data", base::UTF16ToUTF8(result16)); } #if BUILDFLAG(IS_CHROMEOS_ASH) TEST_F(DataOfferTest, SetClipboardDataOfferDteToLacros) { TestDataOfferDelegate delegate; DataOffer data_offer(&delegate); TestDataExchangeDelegate data_exchange_delegate; data_exchange_delegate.set_endpoint_type(ui::EndpointType::kLacros); { ui::ScopedClipboardWriter writer(ui::ClipboardBuffer::kCopyPaste); writer.SetDataSource(std::make_unique( (GURL("https://www.google.com")))); writer.WriteText(u"Test data"); } auto* window = CreateTestWindowInShellWithBounds(gfx::Rect()); data_offer.SetClipboardData( &data_exchange_delegate, *ui::Clipboard::GetForCurrentThread(), data_exchange_delegate.GetDataTransferEndpointType(window)); EXPECT_EQ(4u, delegate.mime_types().size()); EXPECT_EQ(1u, delegate.mime_types().count("text/plain;charset=utf-8")); EXPECT_EQ(1u, delegate.mime_types().count("text/plain;charset=utf-16")); EXPECT_EQ(1u, delegate.mime_types().count("UTF8_STRING")); EXPECT_EQ(1u, delegate.mime_types().count("chromium/x-data-transfer-endpoint")); base::ScopedFD read_pipe; base::ScopedFD write_pipe; // Read as utf-8. ASSERT_TRUE(base::CreatePipe(&read_pipe, &write_pipe)); data_offer.Receive("text/plain;charset=utf-8", std::move(write_pipe)); std::string text_result; ASSERT_TRUE(ReadString(std::move(read_pipe), &text_result)); EXPECT_EQ("Test data", text_result); // Retrieve encoded clipboard source data transfer endpoint. ASSERT_TRUE(base::CreatePipe(&read_pipe, &write_pipe)); data_offer.Receive("chromium/x-data-transfer-endpoint", std::move(write_pipe)); std::string dte_json_result; ASSERT_TRUE(ReadString(std::move(read_pipe), &dte_json_result)); EXPECT_EQ(R"({"endpoint_type":"url","url":"https://www.google.com/"})", dte_json_result); } TEST_F(DataOfferTest, SetClipboardDataDoNotOfferDteToNonLacros) { TestDataOfferDelegate delegate; DataOffer data_offer(&delegate); TestDataExchangeDelegate data_exchange_delegate; data_exchange_delegate.set_endpoint_type(ui::EndpointType::kArc); { ui::ScopedClipboardWriter writer(ui::ClipboardBuffer::kCopyPaste); writer.SetDataSource(std::make_unique( (GURL("https://www.google.com")))); writer.WriteText(u"Test data"); } auto* window = CreateTestWindowInShellWithBounds(gfx::Rect()); data_offer.SetClipboardData( &data_exchange_delegate, *ui::Clipboard::GetForCurrentThread(), data_exchange_delegate.GetDataTransferEndpointType(window)); EXPECT_EQ(3u, delegate.mime_types().size()); EXPECT_EQ(1u, delegate.mime_types().count("text/plain;charset=utf-8")); EXPECT_EQ(1u, delegate.mime_types().count("text/plain;charset=utf-16")); EXPECT_EQ(1u, delegate.mime_types().count("UTF8_STRING")); EXPECT_EQ(0u, delegate.mime_types().count("chromium/x-data-transfer-endpoint")); base::ScopedFD read_pipe; base::ScopedFD write_pipe; // Read as utf-8. ASSERT_TRUE(base::CreatePipe(&read_pipe, &write_pipe)); data_offer.Receive("text/plain;charset=utf-8", std::move(write_pipe)); std::string text_result; ASSERT_TRUE(ReadString(std::move(read_pipe), &text_result)); EXPECT_EQ("Test data", text_result); // Attempt to retrieve encoded clipboard source data transfer endpoint. // Nothing should be returned. ASSERT_TRUE(base::CreatePipe(&read_pipe, &write_pipe)); data_offer.Receive("chromium/x-data-transfer-endpoint", std::move(write_pipe)); std::string dte_json_result; ASSERT_TRUE(ReadString(std::move(read_pipe), &dte_json_result)); EXPECT_EQ("", dte_json_result); } // See crbug.com/1339344 TEST_F(DataOfferTest, SetClipboardDataOfferDteToLacrosSourceChanged) { TestDataOfferDelegate delegate; DataOffer data_offer(&delegate); TestDataExchangeDelegate data_exchange_delegate; data_exchange_delegate.set_endpoint_type(ui::EndpointType::kLacros); { ui::ScopedClipboardWriter writer(ui::ClipboardBuffer::kCopyPaste); writer.SetDataSource(std::make_unique( (GURL("https://www.google.com")))); writer.WriteText(u"Test data"); } auto* window = CreateTestWindowInShellWithBounds(gfx::Rect()); data_offer.SetClipboardData( &data_exchange_delegate, *ui::Clipboard::GetForCurrentThread(), data_exchange_delegate.GetDataTransferEndpointType(window)); EXPECT_EQ(4u, delegate.mime_types().size()); EXPECT_EQ(1u, delegate.mime_types().count("text/plain;charset=utf-8")); EXPECT_EQ(1u, delegate.mime_types().count("text/plain;charset=utf-16")); EXPECT_EQ(1u, delegate.mime_types().count("UTF8_STRING")); EXPECT_EQ(1u, delegate.mime_types().count("chromium/x-data-transfer-endpoint")); // Clipboard data changed { ui::ScopedClipboardWriter writer(ui::ClipboardBuffer::kCopyPaste); writer.WriteText(u"Data changed with no source"); } base::ScopedFD read_pipe; base::ScopedFD write_pipe; // Read as utf-8. ASSERT_TRUE(base::CreatePipe(&read_pipe, &write_pipe)); data_offer.Receive("text/plain;charset=utf-8", std::move(write_pipe)); std::string text_result; ASSERT_TRUE(ReadString(std::move(read_pipe), &text_result)); EXPECT_EQ("Data changed with no source", text_result); // Retrieve encoded clipboard source data transfer endpoint. ASSERT_TRUE(base::CreatePipe(&read_pipe, &write_pipe)); #if DCHECK_IS_ON() EXPECT_DEATH_IF_SUPPORTED( data_offer.Receive("chromium/x-data-transfer-endpoint", std::move(write_pipe)); , "Check failed: data_src. Clipboard source DataTransferEndpoint has " "changed after initial MIME advertising. If you see this please file a " "bug and contact the chromeos-dlp team."); #else data_offer.Receive("chromium/x-data-transfer-endpoint", std::move(write_pipe)); std::string dte_json_result; ASSERT_TRUE(ReadString(std::move(read_pipe), &dte_json_result)); EXPECT_EQ("", dte_json_result); #endif } TEST_F(DataOfferTest, SetDropDataOfferDteToLacros) { base::flat_set source_actions; source_actions.insert(DndAction::kCopy); source_actions.insert(DndAction::kMove); ui::OSExchangeData data; data.SetString(std::u16string(u"Test data")); data.SetSource(std::make_unique( (GURL("https://www.google.com")))); TestDataOfferDelegate delegate; DataOffer data_offer(&delegate); EXPECT_EQ(0u, delegate.mime_types().size()); EXPECT_EQ(0u, delegate.source_actions().size()); EXPECT_EQ(DndAction::kNone, delegate.dnd_action()); TestDataExchangeDelegate data_exchange_delegate; data_exchange_delegate.set_endpoint_type(ui::EndpointType::kLacros); data_offer.SetDropData(&data_exchange_delegate, nullptr, data); data_offer.SetSourceActions(source_actions); data_offer.SetActions(base::flat_set(), DndAction::kMove); EXPECT_EQ(1u, delegate.mime_types().count("text/plain;charset=utf-8")); EXPECT_EQ(1u, delegate.mime_types().count("text/plain;charset=utf-16")); EXPECT_EQ(1u, delegate.mime_types().count("chromium/x-data-transfer-endpoint")); EXPECT_EQ(2u, delegate.source_actions().size()); EXPECT_EQ(1u, delegate.source_actions().count(DndAction::kCopy)); EXPECT_EQ(1u, delegate.source_actions().count(DndAction::kMove)); EXPECT_EQ(DndAction::kMove, delegate.dnd_action()); base::ScopedFD read_pipe; base::ScopedFD write_pipe; // Read as utf-8. ASSERT_TRUE(base::CreatePipe(&read_pipe, &write_pipe)); data_offer.Receive("text/plain;charset=utf-8", std::move(write_pipe)); std::string text_result; ASSERT_TRUE(ReadString(std::move(read_pipe), &text_result)); EXPECT_EQ("Test data", text_result); // Retrieve encoded drag source data transfer endpoint. ASSERT_TRUE(base::CreatePipe(&read_pipe, &write_pipe)); data_offer.Receive("chromium/x-data-transfer-endpoint", std::move(write_pipe)); std::string dte_json_result; ASSERT_TRUE(ReadString(std::move(read_pipe), &dte_json_result)); EXPECT_EQ(R"({"endpoint_type":"url","url":"https://www.google.com/"})", dte_json_result); } TEST_F(DataOfferTest, SetDropDataDoNotOfferDteToNonLacros) { base::flat_set source_actions; source_actions.insert(DndAction::kCopy); source_actions.insert(DndAction::kMove); ui::OSExchangeData data; data.SetString(std::u16string(u"Test data")); data.SetSource(std::make_unique( (GURL("https://www.google.com")))); TestDataOfferDelegate delegate; DataOffer data_offer(&delegate); EXPECT_EQ(0u, delegate.mime_types().size()); EXPECT_EQ(0u, delegate.source_actions().size()); EXPECT_EQ(DndAction::kNone, delegate.dnd_action()); TestDataExchangeDelegate data_exchange_delegate; data_exchange_delegate.set_endpoint_type(ui::EndpointType::kCrostini); data_offer.SetDropData(&data_exchange_delegate, nullptr, data); data_offer.SetSourceActions(source_actions); data_offer.SetActions(base::flat_set(), DndAction::kMove); EXPECT_EQ(1u, delegate.mime_types().count("text/plain;charset=utf-8")); EXPECT_EQ(1u, delegate.mime_types().count("text/plain;charset=utf-16")); EXPECT_EQ(0u, delegate.mime_types().count("chromium/x-data-transfer-endpoint")); EXPECT_EQ(2u, delegate.source_actions().size()); EXPECT_EQ(1u, delegate.source_actions().count(DndAction::kCopy)); EXPECT_EQ(1u, delegate.source_actions().count(DndAction::kMove)); EXPECT_EQ(DndAction::kMove, delegate.dnd_action()); base::ScopedFD read_pipe; base::ScopedFD write_pipe; // Read as utf-8. ASSERT_TRUE(base::CreatePipe(&read_pipe, &write_pipe)); data_offer.Receive("text/plain;charset=utf-8", std::move(write_pipe)); std::string text_result; ASSERT_TRUE(ReadString(std::move(read_pipe), &text_result)); EXPECT_EQ("Test data", text_result); // Attempt to retrieve encoded drag source data transfer endpoint. // Nothing should be returned. ASSERT_TRUE(base::CreatePipe(&read_pipe, &write_pipe)); data_offer.Receive("chromium/x-data-transfer-endpoint", std::move(write_pipe)); std::string dte_json_result; ASSERT_TRUE(ReadString(std::move(read_pipe), &dte_json_result)); EXPECT_EQ("", dte_json_result); } #endif // BUILDFLAG(IS_CHROMEOS_ASH) TEST_F(DataOfferTest, SetClipboardDataHTML) { TestDataOfferDelegate delegate; DataOffer data_offer(&delegate); TestDataExchangeDelegate data_exchange_delegate; { ui::ScopedClipboardWriter writer(ui::ClipboardBuffer::kCopyPaste); writer.WriteHTML(u"Test data", ""); } auto* window = CreateTestWindowInShellWithBounds(gfx::Rect()); data_offer.SetClipboardData( &data_exchange_delegate, *ui::Clipboard::GetForCurrentThread(), data_exchange_delegate.GetDataTransferEndpointType(window)); EXPECT_EQ(2u, delegate.mime_types().size()); EXPECT_EQ(1u, delegate.mime_types().count("text/html;charset=utf-8")); EXPECT_EQ(1u, delegate.mime_types().count("text/html;charset=utf-16")); base::ScopedFD read_pipe; base::ScopedFD write_pipe; ASSERT_TRUE(base::CreatePipe(&read_pipe, &write_pipe)); data_offer.Receive("text/html;charset=utf-8", std::move(write_pipe)); std::string result; ASSERT_TRUE(ReadString(std::move(read_pipe), &result)); EXPECT_EQ("Test data", result); ASSERT_TRUE(base::CreatePipe(&read_pipe, &write_pipe)); data_offer.Receive("text/html;charset=utf-16", std::move(write_pipe)); std::u16string result16; ASSERT_TRUE(ReadString16(std::move(read_pipe), &result16)); EXPECT_EQ("Test data", base::UTF16ToUTF8(result16)); } TEST_F(DataOfferTest, SetClipboardDataRTF) { TestDataOfferDelegate delegate; DataOffer data_offer(&delegate); TestDataExchangeDelegate data_exchange_delegate; { ui::ScopedClipboardWriter writer(ui::ClipboardBuffer::kCopyPaste); writer.WriteRTF("Test data"); } auto* window = CreateTestWindowInShellWithBounds(gfx::Rect()); data_offer.SetClipboardData( &data_exchange_delegate, *ui::Clipboard::GetForCurrentThread(), data_exchange_delegate.GetDataTransferEndpointType(window)); EXPECT_EQ(1u, delegate.mime_types().size()); EXPECT_EQ(1u, delegate.mime_types().count("text/rtf")); base::ScopedFD read_pipe; base::ScopedFD write_pipe; ASSERT_TRUE(base::CreatePipe(&read_pipe, &write_pipe)); data_offer.Receive("text/rtf", std::move(write_pipe)); std::string result; ASSERT_TRUE(ReadString(std::move(read_pipe), &result)); EXPECT_EQ("Test data", result); } TEST_F(DataOfferTest, SetClipboardDataImage) { TestDataOfferDelegate delegate; DataOffer data_offer(&delegate); SkBitmap image; image.allocN32Pixels(10, 10); image.eraseColor(SK_ColorMAGENTA); TestDataExchangeDelegate data_exchange_delegate; { ui::ScopedClipboardWriter writer(ui::ClipboardBuffer::kCopyPaste); writer.WriteImage(image); } auto* window = CreateTestWindowInShellWithBounds(gfx::Rect()); data_offer.SetClipboardData( &data_exchange_delegate, *ui::Clipboard::GetForCurrentThread(), data_exchange_delegate.GetDataTransferEndpointType(window)); EXPECT_EQ(1u, delegate.mime_types().size()); EXPECT_EQ(1u, delegate.mime_types().count("image/png")); base::ScopedFD read_pipe; base::ScopedFD write_pipe; base::ScopedFD read_pipe2; base::ScopedFD write_pipe2; std::string result; // Call Receive() twice in quick succession. Requires RunUntilIdle() since // processing is done on worker thread. ASSERT_TRUE(base::CreatePipe(&read_pipe, &write_pipe)); ASSERT_TRUE(base::CreatePipe(&read_pipe2, &write_pipe2)); data_offer.Receive("image/png", std::move(write_pipe)); data_offer.Receive("image/png", std::move(write_pipe2)); task_environment()->RunUntilIdle(); ASSERT_TRUE(ReadString(std::move(read_pipe), &result)); SkBitmap decoded; ASSERT_TRUE(gfx::PNGCodec::Decode( reinterpret_cast(result.data()), result.size(), &decoded)); EXPECT_TRUE(cc::MatchesBitmap( image, decoded, cc::ExactPixelComparator(/*discard_alpha=*/false))); std::string good = result; ASSERT_TRUE(ReadString(std::move(read_pipe2), &result)); EXPECT_EQ(good, result); // Receive() should now return immediately with result from cache. ASSERT_TRUE(base::CreatePipe(&read_pipe, &write_pipe)); data_offer.Receive("image/png", std::move(write_pipe)); ASSERT_TRUE(ReadString(std::move(read_pipe), &result)); EXPECT_EQ(good, result); } TEST_F(DataOfferTest, SetClipboardDataFilenames) { TestDataOfferDelegate delegate; DataOffer data_offer(&delegate); base::Pickle pickle; pickle.WriteString("file:///test/path"); TestDataExchangeDelegate data_exchange_delegate; { ui::ScopedClipboardWriter writer(ui::ClipboardBuffer::kCopyPaste); writer.WritePickledData(pickle, ui::ClipboardFormatType::WebCustomDataType()); } auto* window = CreateTestWindowInShellWithBounds(gfx::Rect()); data_offer.SetClipboardData( &data_exchange_delegate, *ui::Clipboard::GetForCurrentThread(), data_exchange_delegate.GetDataTransferEndpointType(window)); EXPECT_EQ(1u, delegate.mime_types().size()); EXPECT_EQ(1u, delegate.mime_types().count("text/uri-list")); base::ScopedFD read_pipe; base::ScopedFD write_pipe; ASSERT_TRUE(base::CreatePipe(&read_pipe, &write_pipe)); data_offer.Receive("text/uri-list", std::move(write_pipe)); std::string result; ASSERT_TRUE(ReadString(std::move(read_pipe), &result)); EXPECT_EQ("file:///test/path", result); } TEST_F(DataOfferTest, AcceptWithNull) { TestDataOfferDelegate delegate; DataOffer data_offer(&delegate); data_offer.Accept(nullptr); } TEST_F(DataOfferTest, SetClipboardDataWithTransferPolicy) { TestDataTransferPolicyController policy_controller; TestDataOfferDelegate delegate; DataOffer data_offer(&delegate); TestDataExchangeDelegate data_exchange_delegate; data_exchange_delegate.set_endpoint_type(ui::EndpointType::kCrostini); { ui::ScopedClipboardWriter writer( ui::ClipboardBuffer::kCopyPaste, std::make_unique(ui::EndpointType::kArc)); writer.WriteText(u"Test data"); } auto* window = CreateTestWindowInShellWithBounds(gfx::Rect()); data_offer.SetClipboardData( &data_exchange_delegate, *ui::Clipboard::GetForCurrentThread(), data_exchange_delegate.GetDataTransferEndpointType(window)); EXPECT_EQ(3u, delegate.mime_types().size()); EXPECT_EQ(1u, delegate.mime_types().count("text/plain;charset=utf-8")); EXPECT_EQ(1u, delegate.mime_types().count("text/plain;charset=utf-16")); EXPECT_EQ(1u, delegate.mime_types().count("UTF8_STRING")); base::ScopedFD read_pipe; base::ScopedFD write_pipe; // Read as utf-8. ASSERT_TRUE(base::CreatePipe(&read_pipe, &write_pipe)); data_offer.Receive("text/plain;charset=utf-8", std::move(write_pipe)); std::string result; ASSERT_TRUE(ReadString(std::move(read_pipe), &result)); EXPECT_EQ("Test data", result); EXPECT_EQ(ui::EndpointType::kArc, policy_controller.last_src_type()); EXPECT_EQ(ui::EndpointType::kCrostini, policy_controller.last_dst_type()); } } // namespace } // namespace exo