Skip to content

Commit 130004b

Browse files
authored
Windows HasStrings error when backgrounded (flutter#32038)
Ignore errors from GetHasStrings when the app is backgrounded, as it's irrelevant then.
1 parent 37c3d89 commit 130004b

File tree

5 files changed

+343
-55
lines changed

5 files changed

+343
-55
lines changed

ci/licenses_golden/licenses_flutter

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2271,6 +2271,7 @@ FILE: ../../../flutter/shell/platform/windows/platform_handler.h
22712271
FILE: ../../../flutter/shell/platform/windows/platform_handler_unittests.cc
22722272
FILE: ../../../flutter/shell/platform/windows/platform_handler_win32.cc
22732273
FILE: ../../../flutter/shell/platform/windows/platform_handler_win32.h
2274+
FILE: ../../../flutter/shell/platform/windows/platform_handler_win32_unittests.cc
22742275
FILE: ../../../flutter/shell/platform/windows/public/flutter_windows.h
22752276
FILE: ../../../flutter/shell/platform/windows/sequential_id_generator.cc
22762277
FILE: ../../../flutter/shell/platform/windows/sequential_id_generator.h

shell/platform/windows/BUILD.gn

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ executable("flutter_windows_unittests") {
183183
"keyboard_key_handler_unittests.cc",
184184
"keyboard_win32_unittests.cc",
185185
"platform_handler_unittests.cc",
186+
"platform_handler_win32_unittests.cc",
186187
"sequential_id_generator_unittests.cc",
187188
"settings_plugin_unittests.cc",
188189
"system_utils_unittests.cc",

shell/platform/windows/platform_handler_win32.cc

Lines changed: 67 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
#include "flutter/shell/platform/windows/flutter_windows_view.h"
1515

1616
static constexpr char kValueKey[] = "value";
17+
static constexpr int kAccessDeniedErrorCode = 5;
18+
static constexpr int kErrorSuccess = 0;
1719

1820
namespace flutter {
1921

@@ -95,36 +97,22 @@ class ScopedGlobalLock {
9597

9698
// A Clipboard wrapper that automatically closes the clipboard when it goes out
9799
// of scope.
98-
class ScopedClipboard {
100+
class ScopedClipboard : public ScopedClipboardInterface {
99101
public:
100102
ScopedClipboard();
101-
~ScopedClipboard();
103+
virtual ~ScopedClipboard();
102104

103105
// Prevent copying.
104106
ScopedClipboard(ScopedClipboard const&) = delete;
105107
ScopedClipboard& operator=(ScopedClipboard const&) = delete;
106108

107-
// Attempts to open the clipboard for the given window, returning true if
108-
// successful.
109-
bool Open(HWND window);
109+
int Open(HWND window) override;
110110

111-
// Returns true if there is string data available to get.
112-
bool HasString();
111+
bool HasString() override;
113112

114-
// Returns string data from the clipboard.
115-
//
116-
// If getting a string fails, returns no value. Get error information with
117-
// ::GetLastError().
118-
//
119-
// Open(...) must have succeeded to call this method.
120-
std::optional<std::wstring> GetString();
113+
std::variant<std::wstring, int> GetString() override;
121114

122-
// Sets the string content of the clipboard, returning true on success.
123-
//
124-
// On failure, get error information with ::GetLastError().
125-
//
126-
// Open(...) must have succeeded to call this method.
127-
bool SetString(const std::wstring string);
115+
int SetString(const std::wstring string) override;
128116

129117
private:
130118
bool opened_ = false;
@@ -138,9 +126,14 @@ ScopedClipboard::~ScopedClipboard() {
138126
}
139127
}
140128

141-
bool ScopedClipboard::Open(HWND window) {
129+
int ScopedClipboard::Open(HWND window) {
142130
opened_ = ::OpenClipboard(window);
143-
return opened_;
131+
132+
if (!opened_) {
133+
return ::GetLastError();
134+
}
135+
136+
return kErrorSuccess;
144137
}
145138

146139
bool ScopedClipboard::HasString() {
@@ -149,40 +142,41 @@ bool ScopedClipboard::HasString() {
149142
::IsClipboardFormatAvailable(CF_TEXT);
150143
}
151144

152-
std::optional<std::wstring> ScopedClipboard::GetString() {
145+
std::variant<std::wstring, int> ScopedClipboard::GetString() {
153146
assert(opened_);
154147

155148
HANDLE data = ::GetClipboardData(CF_UNICODETEXT);
156149
if (data == nullptr) {
157-
return std::nullopt;
150+
return ::GetLastError();
158151
}
159152
ScopedGlobalLock locked_data(data);
153+
160154
if (!locked_data.get()) {
161-
return std::nullopt;
155+
return ::GetLastError();
162156
}
163-
return std::optional<std::wstring>(static_cast<wchar_t*>(locked_data.get()));
157+
return static_cast<wchar_t*>(locked_data.get());
164158
}
165159

166-
bool ScopedClipboard::SetString(const std::wstring string) {
160+
int ScopedClipboard::SetString(const std::wstring string) {
167161
assert(opened_);
168162
if (!::EmptyClipboard()) {
169-
return false;
163+
return ::GetLastError();
170164
}
171165
size_t null_terminated_byte_count =
172166
sizeof(decltype(string)::traits_type::char_type) * (string.size() + 1);
173167
ScopedGlobalMemory destination_memory(GMEM_MOVEABLE,
174168
null_terminated_byte_count);
175169
ScopedGlobalLock locked_memory(destination_memory.get());
176170
if (!locked_memory.get()) {
177-
return false;
171+
return ::GetLastError();
178172
}
179173
memcpy(locked_memory.get(), string.c_str(), null_terminated_byte_count);
180174
if (!::SetClipboardData(CF_UNICODETEXT, locked_memory.get())) {
181-
return false;
175+
return ::GetLastError();
182176
}
183177
// The clipboard now owns the global memory.
184178
destination_memory.release();
185-
return true;
179+
return kErrorSuccess;
186180
}
187181

188182
} // namespace
@@ -194,30 +188,38 @@ std::unique_ptr<PlatformHandler> PlatformHandler::Create(
194188
return std::make_unique<PlatformHandlerWin32>(messenger, view);
195189
}
196190

197-
PlatformHandlerWin32::PlatformHandlerWin32(BinaryMessenger* messenger,
198-
FlutterWindowsView* view)
199-
: PlatformHandler(messenger), view_(view) {}
191+
PlatformHandlerWin32::PlatformHandlerWin32(
192+
BinaryMessenger* messenger,
193+
FlutterWindowsView* view,
194+
std::unique_ptr<ScopedClipboardInterface> clipboard)
195+
: PlatformHandler(messenger), view_(view) {
196+
if (clipboard == nullptr) {
197+
clipboard_ = std::make_unique<ScopedClipboard>();
198+
} else {
199+
clipboard_ = std::move(clipboard);
200+
}
201+
}
200202

201203
PlatformHandlerWin32::~PlatformHandlerWin32() = default;
202204

203205
void PlatformHandlerWin32::GetPlainText(
204206
std::unique_ptr<MethodResult<rapidjson::Document>> result,
205207
std::string_view key) {
206-
ScopedClipboard clipboard;
207-
if (!clipboard.Open(std::get<HWND>(*view_->GetRenderTarget()))) {
208+
int open_result = clipboard_->Open(std::get<HWND>(*view_->GetRenderTarget()));
209+
if (open_result != kErrorSuccess) {
208210
rapidjson::Document error_code;
209-
error_code.SetInt(::GetLastError());
211+
error_code.SetInt(open_result);
210212
result->Error(kClipboardError, "Unable to open clipboard", error_code);
211213
return;
212214
}
213-
if (!clipboard.HasString()) {
215+
if (!clipboard_->HasString()) {
214216
result->Success(rapidjson::Document());
215217
return;
216218
}
217-
std::optional<std::wstring> clipboard_string = clipboard.GetString();
218-
if (!clipboard_string) {
219+
std::variant<std::wstring, int> get_string_result = clipboard_->GetString();
220+
if (std::holds_alternative<int>(get_string_result)) {
219221
rapidjson::Document error_code;
220-
error_code.SetInt(::GetLastError());
222+
error_code.SetInt(std::get<int>(get_string_result));
221223
result->Error(kClipboardError, "Unable to get clipboard data", error_code);
222224
return;
223225
}
@@ -227,42 +229,54 @@ void PlatformHandlerWin32::GetPlainText(
227229
rapidjson::Document::AllocatorType& allocator = document.GetAllocator();
228230
document.AddMember(
229231
rapidjson::Value(key.data(), allocator),
230-
rapidjson::Value(fml::WideStringToUtf8(*clipboard_string), allocator),
232+
rapidjson::Value(
233+
fml::WideStringToUtf8(std::get<std::wstring>(get_string_result)),
234+
allocator),
231235
allocator);
232236
result->Success(document);
233237
}
234238

235239
void PlatformHandlerWin32::GetHasStrings(
236240
std::unique_ptr<MethodResult<rapidjson::Document>> result) {
237-
ScopedClipboard clipboard;
238-
if (!clipboard.Open(std::get<HWND>(*view_->GetRenderTarget()))) {
241+
bool hasStrings;
242+
int open_result = clipboard_->Open(std::get<HWND>(*view_->GetRenderTarget()));
243+
if (open_result != kErrorSuccess) {
239244
rapidjson::Document error_code;
240-
error_code.SetInt(::GetLastError());
241-
result->Error(kClipboardError, "Unable to open clipboard", error_code);
242-
return;
245+
error_code.SetInt(open_result);
246+
// Swallow errors of type ERROR_ACCESS_DENIED. These happen when the app is
247+
// not in the foreground and GetHasStrings is irrelevant.
248+
// See https://github.com/flutter/flutter/issues/95817.
249+
if (error_code != kAccessDeniedErrorCode) {
250+
result->Error(kClipboardError, "Unable to open clipboard", error_code);
251+
return;
252+
}
253+
hasStrings = false;
254+
} else {
255+
hasStrings = clipboard_->HasString();
243256
}
244257

245258
rapidjson::Document document;
246259
document.SetObject();
247260
rapidjson::Document::AllocatorType& allocator = document.GetAllocator();
248261
document.AddMember(rapidjson::Value(kValueKey, allocator),
249-
rapidjson::Value(clipboard.HasString()), allocator);
262+
rapidjson::Value(hasStrings), allocator);
250263
result->Success(document);
251264
}
252265

253266
void PlatformHandlerWin32::SetPlainText(
254267
const std::string& text,
255268
std::unique_ptr<MethodResult<rapidjson::Document>> result) {
256-
ScopedClipboard clipboard;
257-
if (!clipboard.Open(std::get<HWND>(*view_->GetRenderTarget()))) {
269+
int open_result = clipboard_->Open(std::get<HWND>(*view_->GetRenderTarget()));
270+
if (open_result != kErrorSuccess) {
258271
rapidjson::Document error_code;
259-
error_code.SetInt(::GetLastError());
272+
error_code.SetInt(open_result);
260273
result->Error(kClipboardError, "Unable to open clipboard", error_code);
261274
return;
262275
}
263-
if (!clipboard.SetString(fml::Utf8ToWideString(text))) {
276+
int set_result = clipboard_->SetString(fml::Utf8ToWideString(text));
277+
if (set_result != kErrorSuccess) {
264278
rapidjson::Document error_code;
265-
error_code.SetInt(::GetLastError());
279+
error_code.SetInt(set_result);
266280
result->Error(kClipboardError, "Unable to set clipboard data", error_code);
267281
return;
268282
}

shell/platform/windows/platform_handler_win32.h

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,40 @@ namespace flutter {
1515

1616
class FlutterWindowsView;
1717

18+
// A public interface for ScopedClipboard, so that it can be injected into
19+
// PlatformHandlerWin32.
20+
class ScopedClipboardInterface {
21+
public:
22+
virtual ~ScopedClipboardInterface(){};
23+
24+
// Attempts to open the clipboard for the given window, returning the error
25+
// code in the case of failure and 0 otherwise.
26+
virtual int Open(HWND window) = 0;
27+
28+
// Returns true if there is string data available to get.
29+
virtual bool HasString() = 0;
30+
31+
// Returns string data from the clipboard.
32+
//
33+
// If getting a string fails, returns the error code.
34+
//
35+
// Open(...) must have succeeded to call this method.
36+
virtual std::variant<std::wstring, int> GetString() = 0;
37+
38+
// Sets the string content of the clipboard, returning the error code on
39+
// failure and 0 otherwise.
40+
//
41+
// Open(...) must have succeeded to call this method.
42+
virtual int SetString(const std::wstring string) = 0;
43+
};
44+
1845
// Win32 implementation of PlatformHandler.
1946
class PlatformHandlerWin32 : public PlatformHandler {
2047
public:
21-
explicit PlatformHandlerWin32(BinaryMessenger* messenger,
22-
FlutterWindowsView* view);
48+
explicit PlatformHandlerWin32(
49+
BinaryMessenger* messenger,
50+
FlutterWindowsView* view,
51+
std::unique_ptr<ScopedClipboardInterface> clipboard = nullptr);
2352

2453
virtual ~PlatformHandlerWin32();
2554

@@ -45,6 +74,8 @@ class PlatformHandlerWin32 : public PlatformHandler {
4574
private:
4675
// A reference to the Flutter view.
4776
FlutterWindowsView* view_;
77+
// A clipboard instance that can be passed in for mocking in tests.
78+
std::unique_ptr<ScopedClipboardInterface> clipboard_;
4879
};
4980

5081
} // namespace flutter

0 commit comments

Comments
 (0)