-
Notifications
You must be signed in to change notification settings - Fork 6k
Windows HasStrings error when backgrounded #32038
Conversation
|
It looks like this pull request may not have tests. Please make sure to add tests before merging. If you need an exemption to this rule, contact Hixie on the #hackers channel in Chat (don't just cc him here, he won't see it! He's on Discord!). If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix? Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing. |
|
@stuartmorgan This PR is following up on your comment flutter/flutter#95817 (comment). I'm not sure how to test this since existing tests (added in #27749) are mocking PlatformHandlerWin32::GetHasStrings, the method that would need to be tested directly here. |
What I've been doing for plugins where we need to mock out Win32 APIs is wrapping them with a very thin class, and mocking that instead. Conveniently, there's already a wrapper class around most of the APIs used here, there's just the
Then you can write tests that inject a fake clipboard that has controlled behavior (like emitting this specific error). |
|
@stuartmorgan Thanks for the idea! I've done it and was able to actually test HasStrings. It might need some cleanup still, let me know what you think. |
| // successful. | ||
| bool Open(HWND window); | ||
| // Attempts to open the clipboard for the given window, returning the error | ||
| // code in the case of failure and -1 otherwise. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are you using -1 for success? That's pretty confusing, especially since ERROR_SUCCESS in Win32 is 0.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah that was super confusing, switching to 0.
| // | ||
| // On failure, get error information with ::GetLastError(). | ||
| // Sets the string content of the clipboard, returning the error code on | ||
| // failure and -1 otherwise. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same.
| } | ||
| std::optional<std::wstring> string = static_cast<wchar_t*>(locked_data.get()); | ||
| if (!string) { | ||
| return ::GetLastError(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the purpose of this new code? I'm not sure why there's an optional, or why you're checking for failure since I'm not clear on how the string creation would fail.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the way I wrote this was redundant. I've updated it so it will return an error if locked_data.get fails, otherwise it returns the string.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm still not sure why there's a std::optional at all. Why not just:
if (!locked_data.get()) {
return ::GetLastError();
}
return std::string(static_cast<wchar_t*>(locked_data.get()));
| private: | ||
| // A reference to the Flutter view. | ||
| FlutterWindowsView* view_; | ||
| // A clipboard instance that can be passed in. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
'can be passed in for mocking in tests'
| PlatformHandlerWin32::PlatformHandlerWin32( | ||
| BinaryMessenger* messenger, | ||
| FlutterWindowsView* view, | ||
| std::optional<ScopedClipboardInterface*> clipboard_reference = nullptr) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Default values should be in the declaration, not the definition.
| explicit PlatformHandlerWin32( | ||
| BinaryMessenger* messenger, | ||
| FlutterWindowsView* view, | ||
| std::optional<ScopedClipboardInterface*> clipboard_reference); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cbracken Do you have a preference as the primary owner of this code on having constructor-based DI, vs. a private setter with a friend class, for things that will only ever be overridden for test mocks?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm going to go ahead and merge, but @cbracken if you would prefer it a different way when you see this then I'm happy to open a new PR to fix it.
| MOCK_METHOD0(NotImplementedInternal, void()); | ||
| }; | ||
|
|
||
| TEST(PlatformHandlerWin32, HasStringsAccessDeniedReturnsFalseWithoutError) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should add other test cases now that this code is being made testable. E.g., testing that other errors are still passed through, since that would have been easy to accidentally break in this PR. (Also that normal success cases work.)
|
Gold has detected about 18 new digest(s) on patchset 7. |
|
Gold has detected about 18 new digest(s) on patchset 11. |
|
Gold has detected about 18 new digest(s) on patchset 12. |
|
Gold has detected about 18 new digest(s) on patchset 13. |
eb256b1 to
1ce47a1
Compare
|
Gold has detected about 18 new digest(s) on patchset 13. |
|
This gold bot isn't indicating any actual failures, and per @Piinks it should be ignored here. |
|
@stuartmorgan Ready for re-review. Thanks for all of the help here. Let me know if I should make a change for #32038 (comment). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Small nits, otherwise looks good. Adding @cbracken as a reviewer for the question above about test object injection approach.
| } | ||
| std::optional<std::wstring> string = static_cast<wchar_t*>(locked_data.get()); | ||
| if (!string) { | ||
| return ::GetLastError(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm still not sure why there's a std::optional at all. Why not just:
if (!locked_data.get()) {
return ::GetLastError();
}
return std::string(static_cast<wchar_t*>(locked_data.get()));
|
@stuartmorgan Thanks for the suggestion, done and ready for re-review. |
9667cd9 to
35ae1b9
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM with nits.
| // A test version of the private ScopedClipboard. | ||
| class TestScopedClipboard : public ScopedClipboardInterface { | ||
| public: | ||
| TestScopedClipboard(int open_error, bool has_strings); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: bool elements are really hard to read at the call site for non-trivial cases (e.g., SetSomeBool(bool)); consider making an enum instead (enum class ClipboardState { kHasString, kNoString }) and using that for the argument type.
| TestScopedClipboard(TestScopedClipboard const&) = delete; | ||
| TestScopedClipboard& operator=(TestScopedClipboard const&) = delete; | ||
|
|
||
| int Open(HWND window); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The overrides need override annotations.
| std::make_unique<::testing::NiceMock<MockWindowBindingHandler>>()); | ||
| // HasStrings will fail. | ||
| PlatformHandlerWin32 platform_handler( | ||
| &messenger, &view, std::make_unique<TestScopedClipboard>(1, true)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To make this self-documenting, I would make a local const int arbitraryErrorCode = 1; and pass that in.
e46521f to
853c6ee
Compare
|
Hi @justinmc, In the commit I see this: Shouldn't the comparison be |
But it would be clearer (and more efficient in the case of this specific error) to move the creation of |
|
Here's a quick PR to clean that up, thanks for the suggestions: #33174 |
This PR works around an error that happened when HasStrings checked the clipboard (in order to see if the Paste button could be shown) when the app was not in the foreground. That information isn't relevant when the app is in the background, so instead of logging an error and worrying developers, I'm swallowing the error and returning false.
Fixes flutter/flutter#95817