diff --git a/DEPS b/DEPS index 1c757829230ae..52fdd0d061fcf 100644 --- a/DEPS +++ b/DEPS @@ -36,7 +36,7 @@ vars = { # Dart is: https://github.com/dart-lang/sdk/blob/main/DEPS. # You can use //tools/dart/create_updated_flutter_deps.py to produce # updated revision list of existing dependencies. - 'dart_revision': 'c9dc429fb986010b6d57133c16bc58ccffacd00e', + 'dart_revision': 'ae4da4b1300c8b779d53cccc6ef3ba7435a5b6fd', # WARNING: DO NOT EDIT MANUALLY # The lines between blank lines above and below are generated by a script. See create_updated_flutter_deps.py diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 6b43cac4a16a6..cf00c43c74d5f 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -2412,9 +2412,6 @@ FILE: ../../../flutter/shell/platform/windows/keyboard_utils_unittests.cc FILE: ../../../flutter/shell/platform/windows/platform_handler.cc FILE: ../../../flutter/shell/platform/windows/platform_handler.h FILE: ../../../flutter/shell/platform/windows/platform_handler_unittests.cc -FILE: ../../../flutter/shell/platform/windows/platform_handler_win32.cc -FILE: ../../../flutter/shell/platform/windows/platform_handler_win32.h -FILE: ../../../flutter/shell/platform/windows/platform_handler_win32_unittests.cc FILE: ../../../flutter/shell/platform/windows/public/flutter_windows.h FILE: ../../../flutter/shell/platform/windows/sequential_id_generator.cc FILE: ../../../flutter/shell/platform/windows/sequential_id_generator.h diff --git a/shell/platform/windows/BUILD.gn b/shell/platform/windows/BUILD.gn index d8b39d099be59..5531f8a41310d 100644 --- a/shell/platform/windows/BUILD.gn +++ b/shell/platform/windows/BUILD.gn @@ -82,8 +82,6 @@ source_set("flutter_windows_source") { "keyboard_utils.h", "platform_handler.cc", "platform_handler.h", - "platform_handler_win32.cc", - "platform_handler_win32.h", "sequential_id_generator.cc", "sequential_id_generator.h", "settings_plugin.cc", @@ -182,7 +180,6 @@ executable("flutter_windows_unittests") { "keyboard_unittests.cc", "keyboard_utils_unittests.cc", "platform_handler_unittests.cc", - "platform_handler_win32_unittests.cc", "sequential_id_generator_unittests.cc", "settings_plugin_unittests.cc", "system_utils_unittests.cc", diff --git a/shell/platform/windows/flutter_windows_view.cc b/shell/platform/windows/flutter_windows_view.cc index 23d3ed17e94c8..d9f901219b664 100644 --- a/shell/platform/windows/flutter_windows_view.cc +++ b/shell/platform/windows/flutter_windows_view.cc @@ -63,7 +63,8 @@ void FlutterWindowsView::SetEngine( // Set up the system channel handlers. auto internal_plugin_messenger = internal_plugin_registrar_->messenger(); InitializeKeyboard(); - platform_handler_ = PlatformHandler::Create(internal_plugin_messenger, this); + platform_handler_ = + std::make_unique(internal_plugin_messenger, this); cursor_handler_ = std::make_unique(internal_plugin_messenger, binding_handler_.get()); diff --git a/shell/platform/windows/platform_handler.cc b/shell/platform/windows/platform_handler.cc index c3791f152376f..d1ab70090ed5b 100644 --- a/shell/platform/windows/platform_handler.cc +++ b/shell/platform/windows/platform_handler.cc @@ -4,7 +4,15 @@ #include "flutter/shell/platform/windows/platform_handler.h" +#include + +#include +#include +#include + +#include "flutter/fml/platform/win/wstring_conversion.h" #include "flutter/shell/platform/common/json_method_codec.h" +#include "flutter/shell/platform/windows/flutter_windows_view.h" static constexpr char kChannelName[] = "flutter/platform"; @@ -15,26 +23,304 @@ static constexpr char kPlaySoundMethod[] = "SystemSound.play"; static constexpr char kTextPlainFormat[] = "text/plain"; static constexpr char kTextKey[] = "text"; - static constexpr char kUnknownClipboardFormatMessage[] = "Unknown clipboard format"; +static constexpr char kValueKey[] = "value"; +static constexpr int kAccessDeniedErrorCode = 5; +static constexpr int kErrorSuccess = 0; + namespace flutter { -PlatformHandler::PlatformHandler(BinaryMessenger* messenger) +namespace { + +// A scoped wrapper for GlobalAlloc/GlobalFree. +class ScopedGlobalMemory { + public: + // Allocates |bytes| bytes of global memory with the given flags. + ScopedGlobalMemory(unsigned int flags, size_t bytes) { + memory_ = ::GlobalAlloc(flags, bytes); + if (!memory_) { + std::cerr << "Unable to allocate global memory: " << ::GetLastError(); + } + } + + ~ScopedGlobalMemory() { + if (memory_) { + if (::GlobalFree(memory_) != nullptr) { + std::cerr << "Failed to free global allocation: " << ::GetLastError(); + } + } + } + + // Prevent copying. + ScopedGlobalMemory(ScopedGlobalMemory const&) = delete; + ScopedGlobalMemory& operator=(ScopedGlobalMemory const&) = delete; + + // Returns the memory pointer, which will be nullptr if allocation failed. + void* get() { return memory_; } + + void* release() { + void* memory = memory_; + memory_ = nullptr; + return memory; + } + + private: + HGLOBAL memory_; +}; + +// A scoped wrapper for GlobalLock/GlobalUnlock. +class ScopedGlobalLock { + public: + // Attempts to acquire a global lock on |memory| for the life of this object. + ScopedGlobalLock(HGLOBAL memory) { + source_ = memory; + if (memory) { + locked_memory_ = ::GlobalLock(memory); + if (!locked_memory_) { + std::cerr << "Unable to acquire global lock: " << ::GetLastError(); + } + } + } + + ~ScopedGlobalLock() { + if (locked_memory_) { + if (!::GlobalUnlock(source_)) { + DWORD error = ::GetLastError(); + if (error != NO_ERROR) { + std::cerr << "Unable to release global lock: " << ::GetLastError(); + } + } + } + } + + // Prevent copying. + ScopedGlobalLock(ScopedGlobalLock const&) = delete; + ScopedGlobalLock& operator=(ScopedGlobalLock const&) = delete; + + // Returns the locked memory pointer, which will be nullptr if acquiring the + // lock failed. + void* get() { return locked_memory_; } + + private: + HGLOBAL source_; + void* locked_memory_; +}; + +// A Clipboard wrapper that automatically closes the clipboard when it goes out +// of scope. +class ScopedClipboard : public ScopedClipboardInterface { + public: + ScopedClipboard(); + virtual ~ScopedClipboard(); + + // Prevent copying. + ScopedClipboard(ScopedClipboard const&) = delete; + ScopedClipboard& operator=(ScopedClipboard const&) = delete; + + int Open(HWND window) override; + + bool HasString() override; + + std::variant GetString() override; + + int SetString(const std::wstring string) override; + + private: + bool opened_ = false; +}; + +ScopedClipboard::ScopedClipboard() {} + +ScopedClipboard::~ScopedClipboard() { + if (opened_) { + ::CloseClipboard(); + } +} + +int ScopedClipboard::Open(HWND window) { + opened_ = ::OpenClipboard(window); + + if (!opened_) { + return ::GetLastError(); + } + + return kErrorSuccess; +} + +bool ScopedClipboard::HasString() { + // Allow either plain text format, since getting data will auto-interpolate. + return ::IsClipboardFormatAvailable(CF_UNICODETEXT) || + ::IsClipboardFormatAvailable(CF_TEXT); +} + +std::variant ScopedClipboard::GetString() { + assert(opened_); + + HANDLE data = ::GetClipboardData(CF_UNICODETEXT); + if (data == nullptr) { + return ::GetLastError(); + } + ScopedGlobalLock locked_data(data); + + if (!locked_data.get()) { + return ::GetLastError(); + } + return static_cast(locked_data.get()); +} + +int ScopedClipboard::SetString(const std::wstring string) { + assert(opened_); + if (!::EmptyClipboard()) { + return ::GetLastError(); + } + size_t null_terminated_byte_count = + sizeof(decltype(string)::traits_type::char_type) * (string.size() + 1); + ScopedGlobalMemory destination_memory(GMEM_MOVEABLE, + null_terminated_byte_count); + ScopedGlobalLock locked_memory(destination_memory.get()); + if (!locked_memory.get()) { + return ::GetLastError(); + } + memcpy(locked_memory.get(), string.c_str(), null_terminated_byte_count); + if (!::SetClipboardData(CF_UNICODETEXT, locked_memory.get())) { + return ::GetLastError(); + } + // The clipboard now owns the global memory. + destination_memory.release(); + return kErrorSuccess; +} + +} // namespace + +PlatformHandler::PlatformHandler( + BinaryMessenger* messenger, + FlutterWindowsView* view, + std::optional()>> + scoped_clipboard_provider) : channel_(std::make_unique>( messenger, kChannelName, - &JsonMethodCodec::GetInstance())) { + &JsonMethodCodec::GetInstance())), + view_(view) { channel_->SetMethodCallHandler( [this](const MethodCall& call, std::unique_ptr> result) { HandleMethodCall(call, std::move(result)); }); + if (scoped_clipboard_provider.has_value()) { + scoped_clipboard_provider_ = scoped_clipboard_provider.value(); + } else { + scoped_clipboard_provider_ = []() { + return std::make_unique(); + }; + } } PlatformHandler::~PlatformHandler() = default; +void PlatformHandler::GetPlainText( + std::unique_ptr> result, + std::string_view key) { + std::unique_ptr clipboard = + scoped_clipboard_provider_(); + + int open_result = clipboard->Open(std::get(*view_->GetRenderTarget())); + if (open_result != kErrorSuccess) { + rapidjson::Document error_code; + error_code.SetInt(open_result); + result->Error(kClipboardError, "Unable to open clipboard", error_code); + return; + } + if (!clipboard->HasString()) { + result->Success(rapidjson::Document()); + return; + } + std::variant get_string_result = clipboard->GetString(); + if (std::holds_alternative(get_string_result)) { + rapidjson::Document error_code; + error_code.SetInt(std::get(get_string_result)); + result->Error(kClipboardError, "Unable to get clipboard data", error_code); + return; + } + + rapidjson::Document document; + document.SetObject(); + rapidjson::Document::AllocatorType& allocator = document.GetAllocator(); + document.AddMember( + rapidjson::Value(key.data(), allocator), + rapidjson::Value( + fml::WideStringToUtf8(std::get(get_string_result)), + allocator), + allocator); + result->Success(document); +} + +void PlatformHandler::GetHasStrings( + std::unique_ptr> result) { + std::unique_ptr clipboard = + scoped_clipboard_provider_(); + + bool hasStrings; + int open_result = clipboard->Open(std::get(*view_->GetRenderTarget())); + if (open_result != kErrorSuccess) { + // Swallow errors of type ERROR_ACCESS_DENIED. These happen when the app is + // not in the foreground and GetHasStrings is irrelevant. + // See https://github.com/flutter/flutter/issues/95817. + if (open_result != kAccessDeniedErrorCode) { + rapidjson::Document error_code; + error_code.SetInt(open_result); + result->Error(kClipboardError, "Unable to open clipboard", error_code); + return; + } + hasStrings = false; + } else { + hasStrings = clipboard->HasString(); + } + + rapidjson::Document document; + document.SetObject(); + rapidjson::Document::AllocatorType& allocator = document.GetAllocator(); + document.AddMember(rapidjson::Value(kValueKey, allocator), + rapidjson::Value(hasStrings), allocator); + result->Success(document); +} + +void PlatformHandler::SetPlainText( + const std::string& text, + std::unique_ptr> result) { + std::unique_ptr clipboard = + scoped_clipboard_provider_(); + + int open_result = clipboard->Open(std::get(*view_->GetRenderTarget())); + if (open_result != kErrorSuccess) { + rapidjson::Document error_code; + error_code.SetInt(open_result); + result->Error(kClipboardError, "Unable to open clipboard", error_code); + return; + } + int set_result = clipboard->SetString(fml::Utf8ToWideString(text)); + if (set_result != kErrorSuccess) { + rapidjson::Document error_code; + error_code.SetInt(set_result); + result->Error(kClipboardError, "Unable to set clipboard data", error_code); + return; + } + result->Success(); +} + +void PlatformHandler::SystemSoundPlay( + const std::string& sound_type, + std::unique_ptr> result) { + if (sound_type.compare(kSoundTypeAlert) == 0) { + MessageBeep(MB_OK); + result->Success(); + } else { + result->NotImplemented(); + } +} + void PlatformHandler::HandleMethodCall( const MethodCall& method_call, std::unique_ptr> result) { diff --git a/shell/platform/windows/platform_handler.h b/shell/platform/windows/platform_handler.h index 3483273e961a4..4da0ab2180527 100644 --- a/shell/platform/windows/platform_handler.h +++ b/shell/platform/windows/platform_handler.h @@ -5,6 +5,13 @@ #ifndef FLUTTER_SHELL_PLATFORM_WINDOWS_PLATFORM_HANDLER_H_ #define FLUTTER_SHELL_PLATFORM_WINDOWS_PLATFORM_HANDLER_H_ +#include + +#include +#include +#include +#include + #include "flutter/shell/platform/common/client_wrapper/include/flutter/binary_messenger.h" #include "flutter/shell/platform/common/client_wrapper/include/flutter/method_channel.h" #include "rapidjson/document.h" @@ -12,39 +19,40 @@ namespace flutter { class FlutterWindowsView; +class ScopedClipboardInterface; // Handler for internal system channels. class PlatformHandler { public: - explicit PlatformHandler(BinaryMessenger* messenger); + explicit PlatformHandler( + BinaryMessenger* messenger, + FlutterWindowsView* view, + std::optional()>> + scoped_clipboard_provider = std::nullopt); virtual ~PlatformHandler(); - // Creates a new platform handler using the given messenger and view. - static std::unique_ptr Create(BinaryMessenger* messenger, - FlutterWindowsView* view); - protected: // Gets plain text from the clipboard and provides it to |result| as the // value in a dictionary with the given |key|. virtual void GetPlainText( std::unique_ptr> result, - std::string_view key) = 0; + std::string_view key); // Provides a boolean to |result| as the value in a dictionary at key // "value" representing whether or not the clipboard has a non-empty string. virtual void GetHasStrings( - std::unique_ptr> result) = 0; + std::unique_ptr> result); // Sets the clipboard's plain text to |text|, and reports the result (either // an error, or null for success) to |result|. virtual void SetPlainText( const std::string& text, - std::unique_ptr> result) = 0; + std::unique_ptr> result); virtual void SystemSoundPlay( const std::string& sound_type, - std::unique_ptr> result) = 0; + std::unique_ptr> result); // A error type to use for error responses. static constexpr char kClipboardError[] = "Clipboard error"; @@ -59,6 +67,42 @@ class PlatformHandler { // The MethodChannel used for communication with the Flutter engine. std::unique_ptr> channel_; + + // A reference to the Flutter view. + FlutterWindowsView* view_; + + // A scoped clipboard provider that can be passed in for mocking in tests. + // Use this to acquire clipboard in each operation to avoid blocking clipboard + // unnecessarily. See flutter/flutter#103205. + std::function()> + scoped_clipboard_provider_; +}; + +// A public interface for ScopedClipboard, so that it can be injected into +// PlatformHandler. +class ScopedClipboardInterface { + public: + virtual ~ScopedClipboardInterface(){}; + + // Attempts to open the clipboard for the given window, returning the error + // code in the case of failure and 0 otherwise. + virtual int Open(HWND window) = 0; + + // Returns true if there is string data available to get. + virtual bool HasString() = 0; + + // Returns string data from the clipboard. + // + // If getting a string fails, returns the error code. + // + // Open(...) must have succeeded to call this method. + virtual std::variant GetString() = 0; + + // Sets the string content of the clipboard, returning the error code on + // failure and 0 otherwise. + // + // Open(...) must have succeeded to call this method. + virtual int SetString(const std::wstring string) = 0; }; } // namespace flutter diff --git a/shell/platform/windows/platform_handler_unittests.cc b/shell/platform/windows/platform_handler_unittests.cc index bcbeb070e80e8..02a98281f4295 100644 --- a/shell/platform/windows/platform_handler_unittests.cc +++ b/shell/platform/windows/platform_handler_unittests.cc @@ -7,6 +7,8 @@ #include #include "flutter/shell/platform/common/json_method_codec.h" +#include "flutter/shell/platform/windows/flutter_windows_view.h" +#include "flutter/shell/platform/windows/testing/mock_window_binding_handler.h" #include "flutter/shell/platform/windows/testing/test_binary_messenger.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -30,16 +32,24 @@ static constexpr char kFakeContentType[] = "text/madeupcontenttype"; static constexpr char kSoundTypeAlert[] = "SystemSoundType.alert"; +static constexpr char kValueKey[] = "value"; +static constexpr int kAccessDeniedErrorCode = 5; +static constexpr int kErrorSuccess = 0; +static constexpr int kArbitraryErrorCode = 1; + // Test implementation of PlatformHandler to allow testing the PlatformHandler // logic. class TestPlatformHandler : public PlatformHandler { public: - explicit TestPlatformHandler(BinaryMessenger* messenger) - : PlatformHandler(messenger) {} + explicit TestPlatformHandler( + BinaryMessenger* messenger, + FlutterWindowsView* view, + std::optional()>> + scoped_clipboard_provider = std::nullopt) + : PlatformHandler(messenger, view, scoped_clipboard_provider) {} - virtual ~TestPlatformHandler() {} + virtual ~TestPlatformHandler() = default; - // |PlatformHandler| MOCK_METHOD2(GetPlainText, void(std::unique_ptr>, std::string_view key)); @@ -64,11 +74,86 @@ class MockMethodResult : public MethodResult { MOCK_METHOD0(NotImplementedInternal, void()); }; +// A test version of system clipboard. +class MockSystemClipboard { + public: + void OpenClipboard() { opened = true; } + void CloseClipboard() { opened = false; } + bool opened = false; +}; + +// A test version of the private ScopedClipboard. +class TestScopedClipboard : public ScopedClipboardInterface { + public: + TestScopedClipboard(int open_error, + bool has_strings, + std::shared_ptr clipboard); + ~TestScopedClipboard(); + + // Prevent copying. + TestScopedClipboard(TestScopedClipboard const&) = delete; + TestScopedClipboard& operator=(TestScopedClipboard const&) = delete; + + int Open(HWND window) override; + + bool HasString() override; + + std::variant GetString() override; + + int SetString(const std::wstring string) override; + + private: + bool opened_ = false; + bool has_strings_; + int open_error_; + std::shared_ptr clipboard_; +}; + +TestScopedClipboard::TestScopedClipboard( + int open_error, + bool has_strings, + std::shared_ptr clipboard = nullptr) { + open_error_ = open_error; + has_strings_ = has_strings; + clipboard_ = clipboard; +} + +TestScopedClipboard::~TestScopedClipboard() { + if ((!open_error_) && clipboard_ != nullptr) { + clipboard_->CloseClipboard(); + } +} + +int TestScopedClipboard::Open(HWND window) { + if ((!open_error_) && clipboard_ != nullptr) { + clipboard_->OpenClipboard(); + } + return open_error_; +} + +bool TestScopedClipboard::HasString() { + return has_strings_; +} + +std::variant TestScopedClipboard::GetString() { + return -1; +} + +int TestScopedClipboard::SetString(const std::wstring string) { + return -1; +} + } // namespace TEST(PlatformHandler, GettingTextCallsThrough) { TestBinaryMessenger messenger; - TestPlatformHandler platform_handler(&messenger); + FlutterWindowsView view( + std::make_unique<::testing::NiceMock>()); + auto system_clipboard = std::make_shared(); + TestPlatformHandler platform_handler(&messenger, &view, [system_clipboard]() { + return std::make_unique(kErrorSuccess, false, + system_clipboard); + }); auto args = std::make_unique(rapidjson::kStringType); auto& allocator = args->GetAllocator(); @@ -92,7 +177,13 @@ TEST(PlatformHandler, GettingTextCallsThrough) { TEST(PlatformHandler, RejectsGettingUnknownTypes) { TestBinaryMessenger messenger; - TestPlatformHandler platform_handler(&messenger); + FlutterWindowsView view( + std::make_unique<::testing::NiceMock>()); + auto system_clipboard = std::make_shared(); + TestPlatformHandler platform_handler(&messenger, &view, [system_clipboard]() { + return std::make_unique(kErrorSuccess, false, + system_clipboard); + }); auto args = std::make_unique(rapidjson::kStringType); auto& allocator = args->GetAllocator(); @@ -114,7 +205,13 @@ TEST(PlatformHandler, RejectsGettingUnknownTypes) { TEST(PlatformHandler, GetHasStringsCallsThrough) { TestBinaryMessenger messenger; - TestPlatformHandler platform_handler(&messenger); + FlutterWindowsView view( + std::make_unique<::testing::NiceMock>()); + auto system_clipboard = std::make_shared(); + TestPlatformHandler platform_handler(&messenger, &view, [system_clipboard]() { + return std::make_unique(kErrorSuccess, false, + system_clipboard); + }); auto args = std::make_unique(rapidjson::kStringType); auto& allocator = args->GetAllocator(); @@ -139,7 +236,13 @@ TEST(PlatformHandler, GetHasStringsCallsThrough) { TEST(PlatformHandler, RejectsGetHasStringsOnUnknownTypes) { TestBinaryMessenger messenger; - TestPlatformHandler platform_handler(&messenger); + FlutterWindowsView view( + std::make_unique<::testing::NiceMock>()); + auto system_clipboard = std::make_shared(); + TestPlatformHandler platform_handler(&messenger, &view, [system_clipboard]() { + return std::make_unique(kErrorSuccess, false, + system_clipboard); + }); auto args = std::make_unique(rapidjson::kStringType); auto& allocator = args->GetAllocator(); @@ -161,7 +264,13 @@ TEST(PlatformHandler, RejectsGetHasStringsOnUnknownTypes) { TEST(PlatformHandler, SettingTextCallsThrough) { TestBinaryMessenger messenger; - TestPlatformHandler platform_handler(&messenger); + FlutterWindowsView view( + std::make_unique<::testing::NiceMock>()); + auto system_clipboard = std::make_shared(); + TestPlatformHandler platform_handler(&messenger, &view, [system_clipboard]() { + return std::make_unique(kErrorSuccess, false, + system_clipboard); + }); auto args = std::make_unique(rapidjson::kObjectType); auto& allocator = args->GetAllocator(); @@ -187,7 +296,13 @@ TEST(PlatformHandler, SettingTextCallsThrough) { TEST(PlatformHandler, RejectsSettingUnknownTypes) { TestBinaryMessenger messenger; - TestPlatformHandler platform_handler(&messenger); + FlutterWindowsView view( + std::make_unique<::testing::NiceMock>()); + auto system_clipboard = std::make_shared(); + TestPlatformHandler platform_handler(&messenger, &view, [system_clipboard]() { + return std::make_unique(kErrorSuccess, false, + system_clipboard); + }); auto args = std::make_unique(rapidjson::kObjectType); auto& allocator = args->GetAllocator(); @@ -209,7 +324,13 @@ TEST(PlatformHandler, RejectsSettingUnknownTypes) { TEST(PlatformHandler, PlayingSystemSoundCallsThrough) { TestBinaryMessenger messenger; - TestPlatformHandler platform_handler(&messenger); + FlutterWindowsView view( + std::make_unique<::testing::NiceMock>()); + auto system_clipboard = std::make_shared(); + TestPlatformHandler platform_handler(&messenger, &view, [system_clipboard]() { + return std::make_unique(kErrorSuccess, false, + system_clipboard); + }); auto args = std::make_unique(rapidjson::kStringType); auto& allocator = args->GetAllocator(); @@ -233,5 +354,171 @@ TEST(PlatformHandler, PlayingSystemSoundCallsThrough) { [](const uint8_t* reply, size_t reply_size) {})); } +// Regression test for https://github.com/flutter/flutter/issues/95817. +TEST(PlatformHandler, HasStringsAccessDeniedReturnsFalseWithoutError) { + TestBinaryMessenger messenger; + FlutterWindowsView view( + std::make_unique<::testing::NiceMock>()); + // HasStrings will receive access denied on the clipboard, but will return + // false without error. + PlatformHandler platform_handler(&messenger, &view, []() { + return std::make_unique(kAccessDeniedErrorCode, true); + }); + + auto args = std::make_unique(rapidjson::kStringType); + auto& allocator = args->GetAllocator(); + args->SetString(kTextPlainFormat); + auto encoded = JsonMethodCodec::GetInstance().EncodeMethodCall( + MethodCall(kHasStringsClipboardMethod, + std::move(args))); + + MockMethodResult result; + rapidjson::Document document; + document.SetObject(); + rapidjson::Document::AllocatorType& document_allocator = + document.GetAllocator(); + document.AddMember(rapidjson::Value(kValueKey, document_allocator), + rapidjson::Value(false), document_allocator); + + EXPECT_CALL(result, SuccessInternal(_)) + .WillOnce([](const rapidjson::Document* document) { + ASSERT_FALSE((*document)[kValueKey].GetBool()); + }); + EXPECT_TRUE(messenger.SimulateEngineMessage( + kChannelName, encoded->data(), encoded->size(), + [&](const uint8_t* reply, size_t reply_size) { + JsonMethodCodec::GetInstance().DecodeAndProcessResponseEnvelope( + reply, reply_size, &result); + })); +} + +TEST(PlatformHandler, HasStringsSuccessWithStrings) { + TestBinaryMessenger messenger; + FlutterWindowsView view( + std::make_unique<::testing::NiceMock>()); + // HasStrings will succeed and return true. + PlatformHandler platform_handler(&messenger, &view, []() { + return std::make_unique(kErrorSuccess, true); + }); + + auto args = std::make_unique(rapidjson::kStringType); + auto& allocator = args->GetAllocator(); + args->SetString(kTextPlainFormat); + auto encoded = JsonMethodCodec::GetInstance().EncodeMethodCall( + MethodCall(kHasStringsClipboardMethod, + std::move(args))); + + MockMethodResult result; + rapidjson::Document document; + document.SetObject(); + rapidjson::Document::AllocatorType& document_allocator = + document.GetAllocator(); + document.AddMember(rapidjson::Value(kValueKey, document_allocator), + rapidjson::Value(false), document_allocator); + + EXPECT_CALL(result, SuccessInternal(_)) + .WillOnce([](const rapidjson::Document* document) { + ASSERT_TRUE((*document)[kValueKey].GetBool()); + }); + EXPECT_TRUE(messenger.SimulateEngineMessage( + kChannelName, encoded->data(), encoded->size(), + [&](const uint8_t* reply, size_t reply_size) { + JsonMethodCodec::GetInstance().DecodeAndProcessResponseEnvelope( + reply, reply_size, &result); + })); +} + +TEST(PlatformHandler, HasStringsSuccessWithoutStrings) { + TestBinaryMessenger messenger; + FlutterWindowsView view( + std::make_unique<::testing::NiceMock>()); + // HasStrings will succeed and return false. + PlatformHandler platform_handler(&messenger, &view, []() { + return std::make_unique(kErrorSuccess, false); + }); + + auto args = std::make_unique(rapidjson::kStringType); + auto& allocator = args->GetAllocator(); + args->SetString(kTextPlainFormat); + auto encoded = JsonMethodCodec::GetInstance().EncodeMethodCall( + MethodCall(kHasStringsClipboardMethod, + std::move(args))); + + MockMethodResult result; + rapidjson::Document document; + document.SetObject(); + rapidjson::Document::AllocatorType& document_allocator = + document.GetAllocator(); + document.AddMember(rapidjson::Value(kValueKey, document_allocator), + rapidjson::Value(false), document_allocator); + + EXPECT_CALL(result, SuccessInternal(_)) + .WillOnce([](const rapidjson::Document* document) { + ASSERT_FALSE((*document)[kValueKey].GetBool()); + }); + EXPECT_TRUE(messenger.SimulateEngineMessage( + kChannelName, encoded->data(), encoded->size(), + [&](const uint8_t* reply, size_t reply_size) { + JsonMethodCodec::GetInstance().DecodeAndProcessResponseEnvelope( + reply, reply_size, &result); + })); +} + +TEST(PlatformHandler, HasStringsError) { + TestBinaryMessenger messenger; + FlutterWindowsView view( + std::make_unique<::testing::NiceMock>()); + // HasStrings will fail. + PlatformHandler platform_handler(&messenger, &view, []() { + return std::make_unique(kArbitraryErrorCode, true); + }); + + auto args = std::make_unique(rapidjson::kStringType); + auto& allocator = args->GetAllocator(); + args->SetString(kTextPlainFormat); + auto encoded = JsonMethodCodec::GetInstance().EncodeMethodCall( + MethodCall(kHasStringsClipboardMethod, + std::move(args))); + + MockMethodResult result; + rapidjson::Document document; + document.SetObject(); + rapidjson::Document::AllocatorType& document_allocator = + document.GetAllocator(); + document.AddMember(rapidjson::Value(kValueKey, document_allocator), + rapidjson::Value(false), document_allocator); + + EXPECT_CALL(result, SuccessInternal(_)).Times(0); + EXPECT_CALL(result, ErrorInternal(_, _, _)).Times(1); + EXPECT_TRUE(messenger.SimulateEngineMessage( + kChannelName, encoded->data(), encoded->size(), + [&](const uint8_t* reply, size_t reply_size) { + JsonMethodCodec::GetInstance().DecodeAndProcessResponseEnvelope( + reply, reply_size, &result); + })); +} + +// Regression test for https://github.com/flutter/flutter/issues/103205. +TEST(PlatformHandler, ReleaseClipboard) { + auto system_clipboard = std::make_shared(); + + TestBinaryMessenger messenger; + FlutterWindowsView view( + std::make_unique<::testing::NiceMock>()); + TestPlatformHandler platform_handler(&messenger, &view, [system_clipboard]() { + return std::make_unique(kErrorSuccess, false, + system_clipboard); + }); + + platform_handler.GetPlainText(std::make_unique(), "text"); + ASSERT_FALSE(system_clipboard->opened); + + platform_handler.GetHasStrings(std::make_unique()); + ASSERT_FALSE(system_clipboard->opened); + + platform_handler.SetPlainText("", std::make_unique()); + ASSERT_FALSE(system_clipboard->opened); +} + } // namespace testing } // namespace flutter diff --git a/shell/platform/windows/platform_handler_win32.cc b/shell/platform/windows/platform_handler_win32.cc deleted file mode 100644 index 6932e8e0dd7fd..0000000000000 --- a/shell/platform/windows/platform_handler_win32.cc +++ /dev/null @@ -1,309 +0,0 @@ -// Copyright 2013 The Flutter 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 "flutter/shell/platform/windows/platform_handler_win32.h" - -#include - -#include -#include -#include - -#include "flutter/fml/platform/win/wstring_conversion.h" -#include "flutter/shell/platform/windows/flutter_windows_view.h" - -static constexpr char kValueKey[] = "value"; -static constexpr int kAccessDeniedErrorCode = 5; -static constexpr int kErrorSuccess = 0; - -namespace flutter { - -namespace { - -// A scoped wrapper for GlobalAlloc/GlobalFree. -class ScopedGlobalMemory { - public: - // Allocates |bytes| bytes of global memory with the given flags. - ScopedGlobalMemory(unsigned int flags, size_t bytes) { - memory_ = ::GlobalAlloc(flags, bytes); - if (!memory_) { - std::cerr << "Unable to allocate global memory: " << ::GetLastError(); - } - } - - ~ScopedGlobalMemory() { - if (memory_) { - if (::GlobalFree(memory_) != nullptr) { - std::cerr << "Failed to free global allocation: " << ::GetLastError(); - } - } - } - - // Prevent copying. - ScopedGlobalMemory(ScopedGlobalMemory const&) = delete; - ScopedGlobalMemory& operator=(ScopedGlobalMemory const&) = delete; - - // Returns the memory pointer, which will be nullptr if allocation failed. - void* get() { return memory_; } - - void* release() { - void* memory = memory_; - memory_ = nullptr; - return memory; - } - - private: - HGLOBAL memory_; -}; - -// A scoped wrapper for GlobalLock/GlobalUnlock. -class ScopedGlobalLock { - public: - // Attempts to acquire a global lock on |memory| for the life of this object. - ScopedGlobalLock(HGLOBAL memory) { - source_ = memory; - if (memory) { - locked_memory_ = ::GlobalLock(memory); - if (!locked_memory_) { - std::cerr << "Unable to acquire global lock: " << ::GetLastError(); - } - } - } - - ~ScopedGlobalLock() { - if (locked_memory_) { - if (!::GlobalUnlock(source_)) { - DWORD error = ::GetLastError(); - if (error != NO_ERROR) { - std::cerr << "Unable to release global lock: " << ::GetLastError(); - } - } - } - } - - // Prevent copying. - ScopedGlobalLock(ScopedGlobalLock const&) = delete; - ScopedGlobalLock& operator=(ScopedGlobalLock const&) = delete; - - // Returns the locked memory pointer, which will be nullptr if acquiring the - // lock failed. - void* get() { return locked_memory_; } - - private: - HGLOBAL source_; - void* locked_memory_; -}; - -// A Clipboard wrapper that automatically closes the clipboard when it goes out -// of scope. -class ScopedClipboard : public ScopedClipboardInterface { - public: - ScopedClipboard(); - virtual ~ScopedClipboard(); - - // Prevent copying. - ScopedClipboard(ScopedClipboard const&) = delete; - ScopedClipboard& operator=(ScopedClipboard const&) = delete; - - int Open(HWND window) override; - - bool HasString() override; - - std::variant GetString() override; - - int SetString(const std::wstring string) override; - - private: - bool opened_ = false; -}; - -ScopedClipboard::ScopedClipboard() {} - -ScopedClipboard::~ScopedClipboard() { - if (opened_) { - ::CloseClipboard(); - } -} - -int ScopedClipboard::Open(HWND window) { - opened_ = ::OpenClipboard(window); - - if (!opened_) { - return ::GetLastError(); - } - - return kErrorSuccess; -} - -bool ScopedClipboard::HasString() { - // Allow either plain text format, since getting data will auto-interpolate. - return ::IsClipboardFormatAvailable(CF_UNICODETEXT) || - ::IsClipboardFormatAvailable(CF_TEXT); -} - -std::variant ScopedClipboard::GetString() { - assert(opened_); - - HANDLE data = ::GetClipboardData(CF_UNICODETEXT); - if (data == nullptr) { - return ::GetLastError(); - } - ScopedGlobalLock locked_data(data); - - if (!locked_data.get()) { - return ::GetLastError(); - } - return static_cast(locked_data.get()); -} - -int ScopedClipboard::SetString(const std::wstring string) { - assert(opened_); - if (!::EmptyClipboard()) { - return ::GetLastError(); - } - size_t null_terminated_byte_count = - sizeof(decltype(string)::traits_type::char_type) * (string.size() + 1); - ScopedGlobalMemory destination_memory(GMEM_MOVEABLE, - null_terminated_byte_count); - ScopedGlobalLock locked_memory(destination_memory.get()); - if (!locked_memory.get()) { - return ::GetLastError(); - } - memcpy(locked_memory.get(), string.c_str(), null_terminated_byte_count); - if (!::SetClipboardData(CF_UNICODETEXT, locked_memory.get())) { - return ::GetLastError(); - } - // The clipboard now owns the global memory. - destination_memory.release(); - return kErrorSuccess; -} - -} // namespace - -// static -std::unique_ptr PlatformHandler::Create( - BinaryMessenger* messenger, - FlutterWindowsView* view) { - return std::make_unique(messenger, view); -} - -PlatformHandlerWin32::PlatformHandlerWin32( - BinaryMessenger* messenger, - FlutterWindowsView* view, - std::optional()>> - scoped_clipboard_provider) - : PlatformHandler(messenger), view_(view) { - if (scoped_clipboard_provider.has_value()) { - scoped_clipboard_provider_ = scoped_clipboard_provider.value(); - } else { - scoped_clipboard_provider_ = []() { - return std::make_unique(); - }; - } -} - -PlatformHandlerWin32::~PlatformHandlerWin32() = default; - -void PlatformHandlerWin32::GetPlainText( - std::unique_ptr> result, - std::string_view key) { - std::unique_ptr clipboard = - scoped_clipboard_provider_(); - - int open_result = clipboard->Open(std::get(*view_->GetRenderTarget())); - if (open_result != kErrorSuccess) { - rapidjson::Document error_code; - error_code.SetInt(open_result); - result->Error(kClipboardError, "Unable to open clipboard", error_code); - return; - } - if (!clipboard->HasString()) { - result->Success(rapidjson::Document()); - return; - } - std::variant get_string_result = clipboard->GetString(); - if (std::holds_alternative(get_string_result)) { - rapidjson::Document error_code; - error_code.SetInt(std::get(get_string_result)); - result->Error(kClipboardError, "Unable to get clipboard data", error_code); - return; - } - - rapidjson::Document document; - document.SetObject(); - rapidjson::Document::AllocatorType& allocator = document.GetAllocator(); - document.AddMember( - rapidjson::Value(key.data(), allocator), - rapidjson::Value( - fml::WideStringToUtf8(std::get(get_string_result)), - allocator), - allocator); - result->Success(document); -} - -void PlatformHandlerWin32::GetHasStrings( - std::unique_ptr> result) { - std::unique_ptr clipboard = - scoped_clipboard_provider_(); - - bool hasStrings; - int open_result = clipboard->Open(std::get(*view_->GetRenderTarget())); - if (open_result != kErrorSuccess) { - // Swallow errors of type ERROR_ACCESS_DENIED. These happen when the app is - // not in the foreground and GetHasStrings is irrelevant. - // See https://github.com/flutter/flutter/issues/95817. - if (open_result != kAccessDeniedErrorCode) { - rapidjson::Document error_code; - error_code.SetInt(open_result); - result->Error(kClipboardError, "Unable to open clipboard", error_code); - return; - } - hasStrings = false; - } else { - hasStrings = clipboard->HasString(); - } - - rapidjson::Document document; - document.SetObject(); - rapidjson::Document::AllocatorType& allocator = document.GetAllocator(); - document.AddMember(rapidjson::Value(kValueKey, allocator), - rapidjson::Value(hasStrings), allocator); - result->Success(document); -} - -void PlatformHandlerWin32::SetPlainText( - const std::string& text, - std::unique_ptr> result) { - std::unique_ptr clipboard = - scoped_clipboard_provider_(); - - int open_result = clipboard->Open(std::get(*view_->GetRenderTarget())); - if (open_result != kErrorSuccess) { - rapidjson::Document error_code; - error_code.SetInt(open_result); - result->Error(kClipboardError, "Unable to open clipboard", error_code); - return; - } - int set_result = clipboard->SetString(fml::Utf8ToWideString(text)); - if (set_result != kErrorSuccess) { - rapidjson::Document error_code; - error_code.SetInt(set_result); - result->Error(kClipboardError, "Unable to set clipboard data", error_code); - return; - } - result->Success(); -} - -void PlatformHandlerWin32::SystemSoundPlay( - const std::string& sound_type, - std::unique_ptr> result) { - if (sound_type.compare(kSoundTypeAlert) == 0) { - MessageBeep(MB_OK); - result->Success(); - } else { - result->NotImplemented(); - } -} - -} // namespace flutter diff --git a/shell/platform/windows/platform_handler_win32.h b/shell/platform/windows/platform_handler_win32.h deleted file mode 100644 index 86cf2a3adfe1f..0000000000000 --- a/shell/platform/windows/platform_handler_win32.h +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef FLUTTER_SHELL_PLATFORM_WINDOWS_PLATFORM_HANDLER_WIN32_H_ -#define FLUTTER_SHELL_PLATFORM_WINDOWS_PLATFORM_HANDLER_WIN32_H_ - -#include "flutter/shell/platform/common/client_wrapper/include/flutter/binary_messenger.h" -#include "flutter/shell/platform/common/client_wrapper/include/flutter/method_channel.h" -#include "flutter/shell/platform/windows/flutter_windows_view.h" -#include "flutter/shell/platform/windows/platform_handler.h" -#include "rapidjson/document.h" - -namespace flutter { - -class FlutterWindowsView; - -// A public interface for ScopedClipboard, so that it can be injected into -// PlatformHandlerWin32. -class ScopedClipboardInterface { - public: - virtual ~ScopedClipboardInterface(){}; - - // Attempts to open the clipboard for the given window, returning the error - // code in the case of failure and 0 otherwise. - virtual int Open(HWND window) = 0; - - // Returns true if there is string data available to get. - virtual bool HasString() = 0; - - // Returns string data from the clipboard. - // - // If getting a string fails, returns the error code. - // - // Open(...) must have succeeded to call this method. - virtual std::variant GetString() = 0; - - // Sets the string content of the clipboard, returning the error code on - // failure and 0 otherwise. - // - // Open(...) must have succeeded to call this method. - virtual int SetString(const std::wstring string) = 0; -}; - -// Win32 implementation of PlatformHandler. -class PlatformHandlerWin32 : public PlatformHandler { - public: - explicit PlatformHandlerWin32( - BinaryMessenger* messenger, - FlutterWindowsView* view, - std::optional()>> - scoped_clipboard_provider = std::nullopt); - - virtual ~PlatformHandlerWin32(); - - protected: - // |PlatformHandler| - void GetPlainText(std::unique_ptr> result, - std::string_view key) override; - - // |PlatformHandler| - void GetHasStrings( - std::unique_ptr> result) override; - - // |PlatformHandler| - void SetPlainText( - const std::string& text, - std::unique_ptr> result) override; - - // |PlatformHandler| - void SystemSoundPlay( - const std::string& sound_type, - std::unique_ptr> result) override; - - private: - // A reference to the Flutter view. - FlutterWindowsView* view_; - // A scoped clipboard provider that can be passed in for mocking in tests. - // Use this to acquire clipboard in each operation to avoid blocking clipboard - // unnecessarily. See flutter/flutter#103205. - std::function()> - scoped_clipboard_provider_; -}; - -} // namespace flutter - -#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_PLATFORM_HANDLER_WIN32_H_ diff --git a/shell/platform/windows/platform_handler_win32_unittests.cc b/shell/platform/windows/platform_handler_win32_unittests.cc deleted file mode 100644 index 6b1afabae8a75..0000000000000 --- a/shell/platform/windows/platform_handler_win32_unittests.cc +++ /dev/null @@ -1,296 +0,0 @@ -// Copyright 2013 The Flutter 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 "flutter/shell/platform/windows/platform_handler_win32.h" - -#include - -#include "flutter/shell/platform/common/json_method_codec.h" -#include "flutter/shell/platform/windows/testing/mock_window_binding_handler.h" -#include "flutter/shell/platform/windows/testing/test_binary_messenger.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "rapidjson/document.h" - -namespace flutter { -namespace testing { - -namespace { -using ::testing::_; - -static constexpr char kChannelName[] = "flutter/platform"; - -static constexpr char kHasStringsClipboardMethod[] = "Clipboard.hasStrings"; - -static constexpr char kTextPlainFormat[] = "text/plain"; - -static constexpr char kValueKey[] = "value"; -static constexpr int kAccessDeniedErrorCode = 5; -static constexpr int kErrorSuccess = 0; -static constexpr int kArbitraryErrorCode = 1; - -} // namespace - -// A test version of system clipboard. -class MockSystemClipboard { - public: - void OpenClipboard() { opened = true; } - void CloseClipboard() { opened = false; } - bool opened = false; -}; - -class TestPlatformHandlerWin32 : public PlatformHandlerWin32 { - public: - explicit TestPlatformHandlerWin32( - BinaryMessenger* messenger, - FlutterWindowsView* view, - std::optional()>> - scoped_clipboard_provider = std::nullopt) - : PlatformHandlerWin32(messenger, view, scoped_clipboard_provider) {} - - virtual ~TestPlatformHandlerWin32() = default; - - FRIEND_TEST(PlatformHandlerWin32, ReleaseClipboard); -}; - -// A test version of the private ScopedClipboard. -class TestScopedClipboard : public ScopedClipboardInterface { - public: - TestScopedClipboard(int open_error, - bool has_strings, - std::shared_ptr clipboard); - ~TestScopedClipboard(); - - // Prevent copying. - TestScopedClipboard(TestScopedClipboard const&) = delete; - TestScopedClipboard& operator=(TestScopedClipboard const&) = delete; - - int Open(HWND window) override; - - bool HasString() override; - - std::variant GetString() override; - - int SetString(const std::wstring string) override; - - private: - bool opened_ = false; - bool has_strings_; - int open_error_; - std::shared_ptr clipboard_; -}; - -TestScopedClipboard::TestScopedClipboard( - int open_error, - bool has_strings, - std::shared_ptr clipboard = nullptr) { - open_error_ = open_error; - has_strings_ = has_strings; - clipboard_ = clipboard; -} - -TestScopedClipboard::~TestScopedClipboard() { - if ((!open_error_) && clipboard_ != nullptr) { - clipboard_->CloseClipboard(); - } -} - -int TestScopedClipboard::Open(HWND window) { - if ((!open_error_) && clipboard_ != nullptr) { - clipboard_->OpenClipboard(); - } - return open_error_; -} - -bool TestScopedClipboard::HasString() { - return has_strings_; -} - -std::variant TestScopedClipboard::GetString() { - return -1; -} - -int TestScopedClipboard::SetString(const std::wstring string) { - return -1; -} - -class MockMethodResult : public MethodResult { - public: - MOCK_METHOD1(SuccessInternal, void(const rapidjson::Document*)); - MOCK_METHOD3(ErrorInternal, - void(const std::string&, - const std::string&, - const rapidjson::Document*)); - MOCK_METHOD0(NotImplementedInternal, void()); -}; - -// Regression test for https://github.com/flutter/flutter/issues/95817. -TEST(PlatformHandlerWin32, HasStringsAccessDeniedReturnsFalseWithoutError) { - TestBinaryMessenger messenger; - FlutterWindowsView view( - std::make_unique<::testing::NiceMock>()); - // HasStrings will receive access denied on the clipboard, but will return - // false without error. - PlatformHandlerWin32 platform_handler(&messenger, &view, []() { - return std::make_unique(kAccessDeniedErrorCode, true); - }); - - auto args = std::make_unique(rapidjson::kStringType); - auto& allocator = args->GetAllocator(); - args->SetString(kTextPlainFormat); - auto encoded = JsonMethodCodec::GetInstance().EncodeMethodCall( - MethodCall(kHasStringsClipboardMethod, - std::move(args))); - - MockMethodResult result; - rapidjson::Document document; - document.SetObject(); - rapidjson::Document::AllocatorType& document_allocator = - document.GetAllocator(); - document.AddMember(rapidjson::Value(kValueKey, document_allocator), - rapidjson::Value(false), document_allocator); - - EXPECT_CALL(result, SuccessInternal(_)) - .WillOnce([](const rapidjson::Document* document) { - ASSERT_FALSE((*document)[kValueKey].GetBool()); - }); - EXPECT_TRUE(messenger.SimulateEngineMessage( - kChannelName, encoded->data(), encoded->size(), - [&](const uint8_t* reply, size_t reply_size) { - JsonMethodCodec::GetInstance().DecodeAndProcessResponseEnvelope( - reply, reply_size, &result); - })); -} - -TEST(PlatformHandlerWin32, HasStringsSuccessWithStrings) { - TestBinaryMessenger messenger; - FlutterWindowsView view( - std::make_unique<::testing::NiceMock>()); - // HasStrings will succeed and return true. - PlatformHandlerWin32 platform_handler(&messenger, &view, []() { - return std::make_unique(kErrorSuccess, true); - }); - - auto args = std::make_unique(rapidjson::kStringType); - auto& allocator = args->GetAllocator(); - args->SetString(kTextPlainFormat); - auto encoded = JsonMethodCodec::GetInstance().EncodeMethodCall( - MethodCall(kHasStringsClipboardMethod, - std::move(args))); - - MockMethodResult result; - rapidjson::Document document; - document.SetObject(); - rapidjson::Document::AllocatorType& document_allocator = - document.GetAllocator(); - document.AddMember(rapidjson::Value(kValueKey, document_allocator), - rapidjson::Value(false), document_allocator); - - EXPECT_CALL(result, SuccessInternal(_)) - .WillOnce([](const rapidjson::Document* document) { - ASSERT_TRUE((*document)[kValueKey].GetBool()); - }); - EXPECT_TRUE(messenger.SimulateEngineMessage( - kChannelName, encoded->data(), encoded->size(), - [&](const uint8_t* reply, size_t reply_size) { - JsonMethodCodec::GetInstance().DecodeAndProcessResponseEnvelope( - reply, reply_size, &result); - })); -} - -TEST(PlatformHandlerWin32, HasStringsSuccessWithoutStrings) { - TestBinaryMessenger messenger; - FlutterWindowsView view( - std::make_unique<::testing::NiceMock>()); - // HasStrings will succeed and return false. - PlatformHandlerWin32 platform_handler(&messenger, &view, []() { - return std::make_unique(kErrorSuccess, false); - }); - - auto args = std::make_unique(rapidjson::kStringType); - auto& allocator = args->GetAllocator(); - args->SetString(kTextPlainFormat); - auto encoded = JsonMethodCodec::GetInstance().EncodeMethodCall( - MethodCall(kHasStringsClipboardMethod, - std::move(args))); - - MockMethodResult result; - rapidjson::Document document; - document.SetObject(); - rapidjson::Document::AllocatorType& document_allocator = - document.GetAllocator(); - document.AddMember(rapidjson::Value(kValueKey, document_allocator), - rapidjson::Value(false), document_allocator); - - EXPECT_CALL(result, SuccessInternal(_)) - .WillOnce([](const rapidjson::Document* document) { - ASSERT_FALSE((*document)[kValueKey].GetBool()); - }); - EXPECT_TRUE(messenger.SimulateEngineMessage( - kChannelName, encoded->data(), encoded->size(), - [&](const uint8_t* reply, size_t reply_size) { - JsonMethodCodec::GetInstance().DecodeAndProcessResponseEnvelope( - reply, reply_size, &result); - })); -} - -TEST(PlatformHandlerWin32, HasStringsError) { - TestBinaryMessenger messenger; - FlutterWindowsView view( - std::make_unique<::testing::NiceMock>()); - // HasStrings will fail. - PlatformHandlerWin32 platform_handler(&messenger, &view, []() { - return std::make_unique(kArbitraryErrorCode, true); - }); - - auto args = std::make_unique(rapidjson::kStringType); - auto& allocator = args->GetAllocator(); - args->SetString(kTextPlainFormat); - auto encoded = JsonMethodCodec::GetInstance().EncodeMethodCall( - MethodCall(kHasStringsClipboardMethod, - std::move(args))); - - MockMethodResult result; - rapidjson::Document document; - document.SetObject(); - rapidjson::Document::AllocatorType& document_allocator = - document.GetAllocator(); - document.AddMember(rapidjson::Value(kValueKey, document_allocator), - rapidjson::Value(false), document_allocator); - - EXPECT_CALL(result, SuccessInternal(_)).Times(0); - EXPECT_CALL(result, ErrorInternal(_, _, _)).Times(1); - EXPECT_TRUE(messenger.SimulateEngineMessage( - kChannelName, encoded->data(), encoded->size(), - [&](const uint8_t* reply, size_t reply_size) { - JsonMethodCodec::GetInstance().DecodeAndProcessResponseEnvelope( - reply, reply_size, &result); - })); -} - -// Regression test for https://github.com/flutter/flutter/issues/103205. -TEST(PlatformHandlerWin32, ReleaseClipboard) { - auto system_clipboard = std::make_shared(); - - TestBinaryMessenger messenger; - FlutterWindowsView view( - std::make_unique<::testing::NiceMock>()); - TestPlatformHandlerWin32 platform_handler( - &messenger, &view, [system_clipboard]() { - return std::make_unique(kErrorSuccess, false, - system_clipboard); - }); - - platform_handler.GetPlainText(std::make_unique(), "text"); - ASSERT_FALSE(system_clipboard->opened); - - platform_handler.GetHasStrings(std::make_unique()); - ASSERT_FALSE(system_clipboard->opened); - - platform_handler.SetPlainText("", std::make_unique()); - ASSERT_FALSE(system_clipboard->opened); -} - -} // namespace testing -} // namespace flutter