mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
[Windows] Merge PlatformHandlerWin32 to superclass (flutter/engine#35056)
With the removal of the UWP embedder, we can merge Win32-specific implementation classes with their abstract superclasses where those superclasses existed only to support both UWP and Win32. No new tests since this is purely a restructuring of existing code within the Win32 embedder, with no expected change in behaviour. Covered by existing tests in task_runner_unittests.cc. Issue: https://github.com/flutter/flutter/issues/108386
This commit is contained in:
parent
8b4cc52f2d
commit
f33f5c486e
@ -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
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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<PlatformHandler>(internal_plugin_messenger, this);
|
||||
cursor_handler_ = std::make_unique<CursorHandler>(internal_plugin_messenger,
|
||||
binding_handler_.get());
|
||||
|
||||
|
||||
@ -4,7 +4,15 @@
|
||||
|
||||
#include "flutter/shell/platform/windows/platform_handler.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
|
||||
#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<std::wstring, int> 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<std::wstring, int> 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<wchar_t*>(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<std::function<std::unique_ptr<ScopedClipboardInterface>()>>
|
||||
scoped_clipboard_provider)
|
||||
: channel_(std::make_unique<MethodChannel<rapidjson::Document>>(
|
||||
messenger,
|
||||
kChannelName,
|
||||
&JsonMethodCodec::GetInstance())) {
|
||||
&JsonMethodCodec::GetInstance())),
|
||||
view_(view) {
|
||||
channel_->SetMethodCallHandler(
|
||||
[this](const MethodCall<rapidjson::Document>& call,
|
||||
std::unique_ptr<MethodResult<rapidjson::Document>> 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<ScopedClipboard>();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
PlatformHandler::~PlatformHandler() = default;
|
||||
|
||||
void PlatformHandler::GetPlainText(
|
||||
std::unique_ptr<MethodResult<rapidjson::Document>> result,
|
||||
std::string_view key) {
|
||||
std::unique_ptr<ScopedClipboardInterface> clipboard =
|
||||
scoped_clipboard_provider_();
|
||||
|
||||
int open_result = clipboard->Open(std::get<HWND>(*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<std::wstring, int> get_string_result = clipboard->GetString();
|
||||
if (std::holds_alternative<int>(get_string_result)) {
|
||||
rapidjson::Document error_code;
|
||||
error_code.SetInt(std::get<int>(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<std::wstring>(get_string_result)),
|
||||
allocator),
|
||||
allocator);
|
||||
result->Success(document);
|
||||
}
|
||||
|
||||
void PlatformHandler::GetHasStrings(
|
||||
std::unique_ptr<MethodResult<rapidjson::Document>> result) {
|
||||
std::unique_ptr<ScopedClipboardInterface> clipboard =
|
||||
scoped_clipboard_provider_();
|
||||
|
||||
bool hasStrings;
|
||||
int open_result = clipboard->Open(std::get<HWND>(*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<MethodResult<rapidjson::Document>> result) {
|
||||
std::unique_ptr<ScopedClipboardInterface> clipboard =
|
||||
scoped_clipboard_provider_();
|
||||
|
||||
int open_result = clipboard->Open(std::get<HWND>(*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<MethodResult<rapidjson::Document>> result) {
|
||||
if (sound_type.compare(kSoundTypeAlert) == 0) {
|
||||
MessageBeep(MB_OK);
|
||||
result->Success();
|
||||
} else {
|
||||
result->NotImplemented();
|
||||
}
|
||||
}
|
||||
|
||||
void PlatformHandler::HandleMethodCall(
|
||||
const MethodCall<rapidjson::Document>& method_call,
|
||||
std::unique_ptr<MethodResult<rapidjson::Document>> result) {
|
||||
|
||||
@ -5,6 +5,13 @@
|
||||
#ifndef FLUTTER_SHELL_PLATFORM_WINDOWS_PLATFORM_HANDLER_H_
|
||||
#define FLUTTER_SHELL_PLATFORM_WINDOWS_PLATFORM_HANDLER_H_
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <variant>
|
||||
|
||||
#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<std::function<std::unique_ptr<ScopedClipboardInterface>()>>
|
||||
scoped_clipboard_provider = std::nullopt);
|
||||
|
||||
virtual ~PlatformHandler();
|
||||
|
||||
// Creates a new platform handler using the given messenger and view.
|
||||
static std::unique_ptr<PlatformHandler> 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<MethodResult<rapidjson::Document>> 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<MethodResult<rapidjson::Document>> result) = 0;
|
||||
std::unique_ptr<MethodResult<rapidjson::Document>> 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<MethodResult<rapidjson::Document>> result) = 0;
|
||||
std::unique_ptr<MethodResult<rapidjson::Document>> result);
|
||||
|
||||
virtual void SystemSoundPlay(
|
||||
const std::string& sound_type,
|
||||
std::unique_ptr<MethodResult<rapidjson::Document>> result) = 0;
|
||||
std::unique_ptr<MethodResult<rapidjson::Document>> 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<MethodChannel<rapidjson::Document>> 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<std::unique_ptr<ScopedClipboardInterface>()>
|
||||
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<std::wstring, int> 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
|
||||
|
||||
@ -7,6 +7,8 @@
|
||||
#include <memory>
|
||||
|
||||
#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<std::function<std::unique_ptr<ScopedClipboardInterface>()>>
|
||||
scoped_clipboard_provider = std::nullopt)
|
||||
: PlatformHandler(messenger, view, scoped_clipboard_provider) {}
|
||||
|
||||
virtual ~TestPlatformHandler() {}
|
||||
virtual ~TestPlatformHandler() = default;
|
||||
|
||||
// |PlatformHandler|
|
||||
MOCK_METHOD2(GetPlainText,
|
||||
void(std::unique_ptr<MethodResult<rapidjson::Document>>,
|
||||
std::string_view key));
|
||||
@ -64,11 +74,86 @@ class MockMethodResult : public MethodResult<rapidjson::Document> {
|
||||
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<MockSystemClipboard> clipboard);
|
||||
~TestScopedClipboard();
|
||||
|
||||
// Prevent copying.
|
||||
TestScopedClipboard(TestScopedClipboard const&) = delete;
|
||||
TestScopedClipboard& operator=(TestScopedClipboard const&) = delete;
|
||||
|
||||
int Open(HWND window) override;
|
||||
|
||||
bool HasString() override;
|
||||
|
||||
std::variant<std::wstring, int> GetString() override;
|
||||
|
||||
int SetString(const std::wstring string) override;
|
||||
|
||||
private:
|
||||
bool opened_ = false;
|
||||
bool has_strings_;
|
||||
int open_error_;
|
||||
std::shared_ptr<MockSystemClipboard> clipboard_;
|
||||
};
|
||||
|
||||
TestScopedClipboard::TestScopedClipboard(
|
||||
int open_error,
|
||||
bool has_strings,
|
||||
std::shared_ptr<MockSystemClipboard> 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<std::wstring, int> 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<MockWindowBindingHandler>>());
|
||||
auto system_clipboard = std::make_shared<MockSystemClipboard>();
|
||||
TestPlatformHandler platform_handler(&messenger, &view, [system_clipboard]() {
|
||||
return std::make_unique<TestScopedClipboard>(kErrorSuccess, false,
|
||||
system_clipboard);
|
||||
});
|
||||
|
||||
auto args = std::make_unique<rapidjson::Document>(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<MockWindowBindingHandler>>());
|
||||
auto system_clipboard = std::make_shared<MockSystemClipboard>();
|
||||
TestPlatformHandler platform_handler(&messenger, &view, [system_clipboard]() {
|
||||
return std::make_unique<TestScopedClipboard>(kErrorSuccess, false,
|
||||
system_clipboard);
|
||||
});
|
||||
|
||||
auto args = std::make_unique<rapidjson::Document>(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<MockWindowBindingHandler>>());
|
||||
auto system_clipboard = std::make_shared<MockSystemClipboard>();
|
||||
TestPlatformHandler platform_handler(&messenger, &view, [system_clipboard]() {
|
||||
return std::make_unique<TestScopedClipboard>(kErrorSuccess, false,
|
||||
system_clipboard);
|
||||
});
|
||||
|
||||
auto args = std::make_unique<rapidjson::Document>(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<MockWindowBindingHandler>>());
|
||||
auto system_clipboard = std::make_shared<MockSystemClipboard>();
|
||||
TestPlatformHandler platform_handler(&messenger, &view, [system_clipboard]() {
|
||||
return std::make_unique<TestScopedClipboard>(kErrorSuccess, false,
|
||||
system_clipboard);
|
||||
});
|
||||
|
||||
auto args = std::make_unique<rapidjson::Document>(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<MockWindowBindingHandler>>());
|
||||
auto system_clipboard = std::make_shared<MockSystemClipboard>();
|
||||
TestPlatformHandler platform_handler(&messenger, &view, [system_clipboard]() {
|
||||
return std::make_unique<TestScopedClipboard>(kErrorSuccess, false,
|
||||
system_clipboard);
|
||||
});
|
||||
|
||||
auto args = std::make_unique<rapidjson::Document>(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<MockWindowBindingHandler>>());
|
||||
auto system_clipboard = std::make_shared<MockSystemClipboard>();
|
||||
TestPlatformHandler platform_handler(&messenger, &view, [system_clipboard]() {
|
||||
return std::make_unique<TestScopedClipboard>(kErrorSuccess, false,
|
||||
system_clipboard);
|
||||
});
|
||||
|
||||
auto args = std::make_unique<rapidjson::Document>(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<MockWindowBindingHandler>>());
|
||||
auto system_clipboard = std::make_shared<MockSystemClipboard>();
|
||||
TestPlatformHandler platform_handler(&messenger, &view, [system_clipboard]() {
|
||||
return std::make_unique<TestScopedClipboard>(kErrorSuccess, false,
|
||||
system_clipboard);
|
||||
});
|
||||
|
||||
auto args = std::make_unique<rapidjson::Document>(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<MockWindowBindingHandler>>());
|
||||
// HasStrings will receive access denied on the clipboard, but will return
|
||||
// false without error.
|
||||
PlatformHandler platform_handler(&messenger, &view, []() {
|
||||
return std::make_unique<TestScopedClipboard>(kAccessDeniedErrorCode, true);
|
||||
});
|
||||
|
||||
auto args = std::make_unique<rapidjson::Document>(rapidjson::kStringType);
|
||||
auto& allocator = args->GetAllocator();
|
||||
args->SetString(kTextPlainFormat);
|
||||
auto encoded = JsonMethodCodec::GetInstance().EncodeMethodCall(
|
||||
MethodCall<rapidjson::Document>(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<MockWindowBindingHandler>>());
|
||||
// HasStrings will succeed and return true.
|
||||
PlatformHandler platform_handler(&messenger, &view, []() {
|
||||
return std::make_unique<TestScopedClipboard>(kErrorSuccess, true);
|
||||
});
|
||||
|
||||
auto args = std::make_unique<rapidjson::Document>(rapidjson::kStringType);
|
||||
auto& allocator = args->GetAllocator();
|
||||
args->SetString(kTextPlainFormat);
|
||||
auto encoded = JsonMethodCodec::GetInstance().EncodeMethodCall(
|
||||
MethodCall<rapidjson::Document>(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<MockWindowBindingHandler>>());
|
||||
// HasStrings will succeed and return false.
|
||||
PlatformHandler platform_handler(&messenger, &view, []() {
|
||||
return std::make_unique<TestScopedClipboard>(kErrorSuccess, false);
|
||||
});
|
||||
|
||||
auto args = std::make_unique<rapidjson::Document>(rapidjson::kStringType);
|
||||
auto& allocator = args->GetAllocator();
|
||||
args->SetString(kTextPlainFormat);
|
||||
auto encoded = JsonMethodCodec::GetInstance().EncodeMethodCall(
|
||||
MethodCall<rapidjson::Document>(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<MockWindowBindingHandler>>());
|
||||
// HasStrings will fail.
|
||||
PlatformHandler platform_handler(&messenger, &view, []() {
|
||||
return std::make_unique<TestScopedClipboard>(kArbitraryErrorCode, true);
|
||||
});
|
||||
|
||||
auto args = std::make_unique<rapidjson::Document>(rapidjson::kStringType);
|
||||
auto& allocator = args->GetAllocator();
|
||||
args->SetString(kTextPlainFormat);
|
||||
auto encoded = JsonMethodCodec::GetInstance().EncodeMethodCall(
|
||||
MethodCall<rapidjson::Document>(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<MockSystemClipboard>();
|
||||
|
||||
TestBinaryMessenger messenger;
|
||||
FlutterWindowsView view(
|
||||
std::make_unique<::testing::NiceMock<MockWindowBindingHandler>>());
|
||||
TestPlatformHandler platform_handler(&messenger, &view, [system_clipboard]() {
|
||||
return std::make_unique<TestScopedClipboard>(kErrorSuccess, false,
|
||||
system_clipboard);
|
||||
});
|
||||
|
||||
platform_handler.GetPlainText(std::make_unique<MockMethodResult>(), "text");
|
||||
ASSERT_FALSE(system_clipboard->opened);
|
||||
|
||||
platform_handler.GetHasStrings(std::make_unique<MockMethodResult>());
|
||||
ASSERT_FALSE(system_clipboard->opened);
|
||||
|
||||
platform_handler.SetPlainText("", std::make_unique<MockMethodResult>());
|
||||
ASSERT_FALSE(system_clipboard->opened);
|
||||
}
|
||||
|
||||
} // namespace testing
|
||||
} // namespace flutter
|
||||
|
||||
@ -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 <windows.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
|
||||
#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<std::wstring, int> 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<std::wstring, int> 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<wchar_t*>(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> PlatformHandler::Create(
|
||||
BinaryMessenger* messenger,
|
||||
FlutterWindowsView* view) {
|
||||
return std::make_unique<PlatformHandlerWin32>(messenger, view);
|
||||
}
|
||||
|
||||
PlatformHandlerWin32::PlatformHandlerWin32(
|
||||
BinaryMessenger* messenger,
|
||||
FlutterWindowsView* view,
|
||||
std::optional<std::function<std::unique_ptr<ScopedClipboardInterface>()>>
|
||||
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<ScopedClipboard>();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
PlatformHandlerWin32::~PlatformHandlerWin32() = default;
|
||||
|
||||
void PlatformHandlerWin32::GetPlainText(
|
||||
std::unique_ptr<MethodResult<rapidjson::Document>> result,
|
||||
std::string_view key) {
|
||||
std::unique_ptr<ScopedClipboardInterface> clipboard =
|
||||
scoped_clipboard_provider_();
|
||||
|
||||
int open_result = clipboard->Open(std::get<HWND>(*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<std::wstring, int> get_string_result = clipboard->GetString();
|
||||
if (std::holds_alternative<int>(get_string_result)) {
|
||||
rapidjson::Document error_code;
|
||||
error_code.SetInt(std::get<int>(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<std::wstring>(get_string_result)),
|
||||
allocator),
|
||||
allocator);
|
||||
result->Success(document);
|
||||
}
|
||||
|
||||
void PlatformHandlerWin32::GetHasStrings(
|
||||
std::unique_ptr<MethodResult<rapidjson::Document>> result) {
|
||||
std::unique_ptr<ScopedClipboardInterface> clipboard =
|
||||
scoped_clipboard_provider_();
|
||||
|
||||
bool hasStrings;
|
||||
int open_result = clipboard->Open(std::get<HWND>(*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<MethodResult<rapidjson::Document>> result) {
|
||||
std::unique_ptr<ScopedClipboardInterface> clipboard =
|
||||
scoped_clipboard_provider_();
|
||||
|
||||
int open_result = clipboard->Open(std::get<HWND>(*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<MethodResult<rapidjson::Document>> result) {
|
||||
if (sound_type.compare(kSoundTypeAlert) == 0) {
|
||||
MessageBeep(MB_OK);
|
||||
result->Success();
|
||||
} else {
|
||||
result->NotImplemented();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace flutter
|
||||
@ -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<std::wstring, int> 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<std::function<std::unique_ptr<ScopedClipboardInterface>()>>
|
||||
scoped_clipboard_provider = std::nullopt);
|
||||
|
||||
virtual ~PlatformHandlerWin32();
|
||||
|
||||
protected:
|
||||
// |PlatformHandler|
|
||||
void GetPlainText(std::unique_ptr<MethodResult<rapidjson::Document>> result,
|
||||
std::string_view key) override;
|
||||
|
||||
// |PlatformHandler|
|
||||
void GetHasStrings(
|
||||
std::unique_ptr<MethodResult<rapidjson::Document>> result) override;
|
||||
|
||||
// |PlatformHandler|
|
||||
void SetPlainText(
|
||||
const std::string& text,
|
||||
std::unique_ptr<MethodResult<rapidjson::Document>> result) override;
|
||||
|
||||
// |PlatformHandler|
|
||||
void SystemSoundPlay(
|
||||
const std::string& sound_type,
|
||||
std::unique_ptr<MethodResult<rapidjson::Document>> 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<std::unique_ptr<ScopedClipboardInterface>()>
|
||||
scoped_clipboard_provider_;
|
||||
};
|
||||
|
||||
} // namespace flutter
|
||||
|
||||
#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_PLATFORM_HANDLER_WIN32_H_
|
||||
@ -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 <memory>
|
||||
|
||||
#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<std::function<std::unique_ptr<ScopedClipboardInterface>()>>
|
||||
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<MockSystemClipboard> clipboard);
|
||||
~TestScopedClipboard();
|
||||
|
||||
// Prevent copying.
|
||||
TestScopedClipboard(TestScopedClipboard const&) = delete;
|
||||
TestScopedClipboard& operator=(TestScopedClipboard const&) = delete;
|
||||
|
||||
int Open(HWND window) override;
|
||||
|
||||
bool HasString() override;
|
||||
|
||||
std::variant<std::wstring, int> GetString() override;
|
||||
|
||||
int SetString(const std::wstring string) override;
|
||||
|
||||
private:
|
||||
bool opened_ = false;
|
||||
bool has_strings_;
|
||||
int open_error_;
|
||||
std::shared_ptr<MockSystemClipboard> clipboard_;
|
||||
};
|
||||
|
||||
TestScopedClipboard::TestScopedClipboard(
|
||||
int open_error,
|
||||
bool has_strings,
|
||||
std::shared_ptr<MockSystemClipboard> 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<std::wstring, int> TestScopedClipboard::GetString() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int TestScopedClipboard::SetString(const std::wstring string) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
class MockMethodResult : public MethodResult<rapidjson::Document> {
|
||||
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<MockWindowBindingHandler>>());
|
||||
// HasStrings will receive access denied on the clipboard, but will return
|
||||
// false without error.
|
||||
PlatformHandlerWin32 platform_handler(&messenger, &view, []() {
|
||||
return std::make_unique<TestScopedClipboard>(kAccessDeniedErrorCode, true);
|
||||
});
|
||||
|
||||
auto args = std::make_unique<rapidjson::Document>(rapidjson::kStringType);
|
||||
auto& allocator = args->GetAllocator();
|
||||
args->SetString(kTextPlainFormat);
|
||||
auto encoded = JsonMethodCodec::GetInstance().EncodeMethodCall(
|
||||
MethodCall<rapidjson::Document>(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<MockWindowBindingHandler>>());
|
||||
// HasStrings will succeed and return true.
|
||||
PlatformHandlerWin32 platform_handler(&messenger, &view, []() {
|
||||
return std::make_unique<TestScopedClipboard>(kErrorSuccess, true);
|
||||
});
|
||||
|
||||
auto args = std::make_unique<rapidjson::Document>(rapidjson::kStringType);
|
||||
auto& allocator = args->GetAllocator();
|
||||
args->SetString(kTextPlainFormat);
|
||||
auto encoded = JsonMethodCodec::GetInstance().EncodeMethodCall(
|
||||
MethodCall<rapidjson::Document>(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<MockWindowBindingHandler>>());
|
||||
// HasStrings will succeed and return false.
|
||||
PlatformHandlerWin32 platform_handler(&messenger, &view, []() {
|
||||
return std::make_unique<TestScopedClipboard>(kErrorSuccess, false);
|
||||
});
|
||||
|
||||
auto args = std::make_unique<rapidjson::Document>(rapidjson::kStringType);
|
||||
auto& allocator = args->GetAllocator();
|
||||
args->SetString(kTextPlainFormat);
|
||||
auto encoded = JsonMethodCodec::GetInstance().EncodeMethodCall(
|
||||
MethodCall<rapidjson::Document>(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<MockWindowBindingHandler>>());
|
||||
// HasStrings will fail.
|
||||
PlatformHandlerWin32 platform_handler(&messenger, &view, []() {
|
||||
return std::make_unique<TestScopedClipboard>(kArbitraryErrorCode, true);
|
||||
});
|
||||
|
||||
auto args = std::make_unique<rapidjson::Document>(rapidjson::kStringType);
|
||||
auto& allocator = args->GetAllocator();
|
||||
args->SetString(kTextPlainFormat);
|
||||
auto encoded = JsonMethodCodec::GetInstance().EncodeMethodCall(
|
||||
MethodCall<rapidjson::Document>(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<MockSystemClipboard>();
|
||||
|
||||
TestBinaryMessenger messenger;
|
||||
FlutterWindowsView view(
|
||||
std::make_unique<::testing::NiceMock<MockWindowBindingHandler>>());
|
||||
TestPlatformHandlerWin32 platform_handler(
|
||||
&messenger, &view, [system_clipboard]() {
|
||||
return std::make_unique<TestScopedClipboard>(kErrorSuccess, false,
|
||||
system_clipboard);
|
||||
});
|
||||
|
||||
platform_handler.GetPlainText(std::make_unique<MockMethodResult>(), "text");
|
||||
ASSERT_FALSE(system_clipboard->opened);
|
||||
|
||||
platform_handler.GetHasStrings(std::make_unique<MockMethodResult>());
|
||||
ASSERT_FALSE(system_clipboard->opened);
|
||||
|
||||
platform_handler.SetPlainText("", std::make_unique<MockMethodResult>());
|
||||
ASSERT_FALSE(system_clipboard->opened);
|
||||
}
|
||||
|
||||
} // namespace testing
|
||||
} // namespace flutter
|
||||
Loading…
x
Reference in New Issue
Block a user