diff --git a/shell/platform/windows/flutter_window_win32.cc b/shell/platform/windows/flutter_window_win32.cc index 064eb711b2aa0..31d551a59a8b4 100644 --- a/shell/platform/windows/flutter_window_win32.cc +++ b/shell/platform/windows/flutter_window_win32.cc @@ -232,6 +232,10 @@ void FlutterWindowWin32::OnCursorRectUpdated(const Rect& rect) { UpdateCursorRect(Rect(origin, size)); } +void FlutterWindowWin32::OnResetImeComposing() { + AbortImeComposing(); +} + bool FlutterWindowWin32::OnBitmapSurfaceUpdated(const void* allocation, size_t row_bytes, size_t height) { diff --git a/shell/platform/windows/flutter_window_win32.h b/shell/platform/windows/flutter_window_win32.h index 86969749d21a2..f1f13f37d8e41 100644 --- a/shell/platform/windows/flutter_window_win32.h +++ b/shell/platform/windows/flutter_window_win32.h @@ -89,6 +89,9 @@ class FlutterWindowWin32 : public WindowWin32, public WindowBindingHandler { // |FlutterWindowBindingHandler| void OnCursorRectUpdated(const Rect& rect) override; + // |FlutterWindowBindingHandler| + void OnResetImeComposing() override; + // |WindowWin32| void OnScroll(double delta_x, double delta_y, diff --git a/shell/platform/windows/flutter_window_win32_unittests.cc b/shell/platform/windows/flutter_window_win32_unittests.cc index 84af45a610946..a5e6683ce5c1f 100644 --- a/shell/platform/windows/flutter_window_win32_unittests.cc +++ b/shell/platform/windows/flutter_window_win32_unittests.cc @@ -100,6 +100,7 @@ class SpyTextInputPlugin : public KeyboardHandlerBase, void(const std::u16string& text, int cursor_pos)); virtual void OnCursorRectUpdated(const Rect& rect) {} + virtual void OnResetImeComposing() {} private: std::unique_ptr real_implementation_; @@ -155,6 +156,7 @@ class MockFlutterWindowWin32 : public FlutterWindowWin32, MOCK_METHOD0(GetDpiScale, float()); MOCK_METHOD0(IsVisible, bool()); MOCK_METHOD1(UpdateCursorRect, void(const Rect&)); + MOCK_METHOD0(OnResetImeComposing, void()); protected: virtual BOOL Win32PeekMessage(LPMSG lpMsg, diff --git a/shell/platform/windows/flutter_window_winuwp.cc b/shell/platform/windows/flutter_window_winuwp.cc index 29781837fd8ee..d8f7b55fb67e0 100644 --- a/shell/platform/windows/flutter_window_winuwp.cc +++ b/shell/platform/windows/flutter_window_winuwp.cc @@ -133,6 +133,11 @@ void FlutterWindowWinUWP::OnCursorRectUpdated(const Rect& rect) { // TODO(cbracken): Implement IMM candidate window positioning. } +void FlutterWindowWinUWP::OnResetImeComposing() { + // TODO(cbracken): Cancel composing, close the candidates view, and clear the + // composing text. +} + void FlutterWindowWinUWP::OnWindowResized() {} FlutterWindowWinUWP::~FlutterWindowWinUWP() {} diff --git a/shell/platform/windows/flutter_window_winuwp.h b/shell/platform/windows/flutter_window_winuwp.h index 235caf409fec0..39a3eaa29fd19 100644 --- a/shell/platform/windows/flutter_window_winuwp.h +++ b/shell/platform/windows/flutter_window_winuwp.h @@ -55,6 +55,9 @@ class FlutterWindowWinUWP : public WindowBindingHandler { // |WindowBindingHandler| void OnCursorRectUpdated(const Rect& rect) override; + // |FlutterWindowBindingHandler| + void OnResetImeComposing() override; + // |WindowBindingHandler| void OnWindowResized() override; diff --git a/shell/platform/windows/flutter_windows_view.cc b/shell/platform/windows/flutter_windows_view.cc index 268fa29606387..35cb908bebf9a 100644 --- a/shell/platform/windows/flutter_windows_view.cc +++ b/shell/platform/windows/flutter_windows_view.cc @@ -252,6 +252,10 @@ void FlutterWindowsView::OnCursorRectUpdated(const Rect& rect) { binding_handler_->OnCursorRectUpdated(rect); } +void FlutterWindowsView::OnResetImeComposing() { + binding_handler_->OnResetImeComposing(); +} + void FlutterWindowsView::InitializeKeyboard() { auto internal_plugin_messenger = internal_plugin_registrar_->messenger(); #ifdef WINUWP diff --git a/shell/platform/windows/flutter_windows_view.h b/shell/platform/windows/flutter_windows_view.h index 2fc4a83af7219..1dd820d81529a 100644 --- a/shell/platform/windows/flutter_windows_view.h +++ b/shell/platform/windows/flutter_windows_view.h @@ -161,6 +161,9 @@ class FlutterWindowsView : public WindowBindingHandlerDelegate, // |TextInputPluginDelegate| void OnCursorRectUpdated(const Rect& rect) override; + // |TextInputPluginDelegate| + void OnResetImeComposing() override; + protected: // Called to create the keyboard hook handlers. // diff --git a/shell/platform/windows/testing/mock_window_binding_handler.h b/shell/platform/windows/testing/mock_window_binding_handler.h index c6be8e18c8089..a5395c96cca02 100644 --- a/shell/platform/windows/testing/mock_window_binding_handler.h +++ b/shell/platform/windows/testing/mock_window_binding_handler.h @@ -1,42 +1,43 @@ -// 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_TESTING_MOCK_WINDOW_BINDING_HANDLER_H_ -#define FLUTTER_SHELL_PLATFORM_WINDOWS_TESTING_MOCK_WINDOW_BINDING_HANDLER_H_ - -#include - -#include "flutter/shell/platform/windows/window_binding_handler.h" -#include "gmock/gmock.h" - -namespace flutter { -namespace testing { - -/// Mock for the |WindowWin32| base class. -class MockWindowBindingHandler : public WindowBindingHandler { - public: - MockWindowBindingHandler(); - virtual ~MockWindowBindingHandler(); - - // Prevent copying. - MockWindowBindingHandler(MockWindowBindingHandler const&) = delete; - MockWindowBindingHandler& operator=(MockWindowBindingHandler const&) = delete; - - MOCK_METHOD1(SetView, void(WindowBindingHandlerDelegate* view)); - MOCK_METHOD0(GetRenderTarget, WindowsRenderTarget()); - MOCK_METHOD0(GetPlatformWindow, PlatformWindow()); - MOCK_METHOD0(GetDpiScale, float()); - MOCK_METHOD0(IsVisible, bool()); - MOCK_METHOD0(OnWindowResized, void()); - MOCK_METHOD0(GetPhysicalWindowBounds, PhysicalWindowBounds()); - MOCK_METHOD1(UpdateFlutterCursor, void(const std::string& cursor_name)); - MOCK_METHOD1(OnCursorRectUpdated, void(const Rect& rect)); - MOCK_METHOD3(OnBitmapSurfaceUpdated, - bool(const void* allocation, size_t row_bytes, size_t height)); -}; - -} // namespace testing -} // namespace flutter - -#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_TESTING_MOCK_WINDOW_BINDING_HANDLER_H_ +// 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_TESTING_MOCK_WINDOW_BINDING_HANDLER_H_ +#define FLUTTER_SHELL_PLATFORM_WINDOWS_TESTING_MOCK_WINDOW_BINDING_HANDLER_H_ + +#include + +#include "flutter/shell/platform/windows/window_binding_handler.h" +#include "gmock/gmock.h" + +namespace flutter { +namespace testing { + +/// Mock for the |WindowWin32| base class. +class MockWindowBindingHandler : public WindowBindingHandler { + public: + MockWindowBindingHandler(); + virtual ~MockWindowBindingHandler(); + + // Prevent copying. + MockWindowBindingHandler(MockWindowBindingHandler const&) = delete; + MockWindowBindingHandler& operator=(MockWindowBindingHandler const&) = delete; + + MOCK_METHOD1(SetView, void(WindowBindingHandlerDelegate* view)); + MOCK_METHOD0(GetRenderTarget, WindowsRenderTarget()); + MOCK_METHOD0(GetPlatformWindow, PlatformWindow()); + MOCK_METHOD0(GetDpiScale, float()); + MOCK_METHOD0(IsVisible, bool()); + MOCK_METHOD0(OnWindowResized, void()); + MOCK_METHOD0(GetPhysicalWindowBounds, PhysicalWindowBounds()); + MOCK_METHOD1(UpdateFlutterCursor, void(const std::string& cursor_name)); + MOCK_METHOD1(OnCursorRectUpdated, void(const Rect& rect)); + MOCK_METHOD0(OnResetImeComposing, void()); + MOCK_METHOD3(OnBitmapSurfaceUpdated, + bool(const void* allocation, size_t row_bytes, size_t height)); +}; + +} // namespace testing +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_TESTING_MOCK_WINDOW_BINDING_HANDLER_H_ diff --git a/shell/platform/windows/text_input_manager_win32.cc b/shell/platform/windows/text_input_manager_win32.cc index 7c0ed0850e212..c630666103477 100644 --- a/shell/platform/windows/text_input_manager_win32.cc +++ b/shell/platform/windows/text_input_manager_win32.cc @@ -128,6 +128,21 @@ std::optional TextInputManagerWin32::GetResultString() const { return GetString(GCS_RESULTSTR); } +void TextInputManagerWin32::AbortComposing() { + ImmContext imm_context(window_handle_); + if (imm_context.IsValid()) { + // Cancel composing and close the candidates window. + ::ImmNotifyIME(imm_context.get(), NI_COMPOSITIONSTR, CPS_CANCEL, 0); + ::ImmNotifyIME(imm_context.get(), NI_CLOSECANDIDATE, 0, 0); + + // Clear the composing string. + wchar_t completion_str[] = L""; + wchar_t reading_str[] = L""; + ::ImmSetCompositionStringW(imm_context.get(), SCS_SETSTR, completion_str, + sizeof(wchar_t), reading_str, sizeof(wchar_t)); + } +} + std::optional TextInputManagerWin32::GetString(int type) const { if (window_handle_ == nullptr || !ime_active_) { return std::nullopt; diff --git a/shell/platform/windows/text_input_manager_win32.h b/shell/platform/windows/text_input_manager_win32.h index d7c0319412edf..0565efb92c38e 100644 --- a/shell/platform/windows/text_input_manager_win32.h +++ b/shell/platform/windows/text_input_manager_win32.h @@ -77,6 +77,12 @@ class TextInputManagerWin32 { // be committed in the composing region when composition is ended. std::optional GetResultString() const; + /// Aborts IME composing. + /// + /// Aborts composing, closes the candidates window, and clears the contents + /// of the composing string. + void AbortComposing(); + private: // Returns either the composing string or result string based on the value of // the |type| parameter. diff --git a/shell/platform/windows/text_input_plugin.cc b/shell/platform/windows/text_input_plugin.cc index f898b30b813c6..db27e4f7a233e 100644 --- a/shell/platform/windows/text_input_plugin.cc +++ b/shell/platform/windows/text_input_plugin.cc @@ -7,7 +7,6 @@ #include #include -#include #include "flutter/shell/platform/common/json_method_codec.h" #include "flutter/shell/platform/windows/flutter_windows_view.h" @@ -148,6 +147,7 @@ void TextInputPlugin::HandleMethodCall( if (method.compare(kShowMethod) == 0 || method.compare(kHideMethod) == 0) { // These methods are no-ops. } else if (method.compare(kClearClientMethod) == 0) { + delegate_->OnResetImeComposing(); active_model_ = nullptr; } else if (method.compare(kSetClientMethod) == 0) { if (!method_call.arguments() || method_call.arguments()->IsNull()) { diff --git a/shell/platform/windows/text_input_plugin_delegate.h b/shell/platform/windows/text_input_plugin_delegate.h index 3975f3a0f342f..0d41ff4bdc3a0 100644 --- a/shell/platform/windows/text_input_plugin_delegate.h +++ b/shell/platform/windows/text_input_plugin_delegate.h @@ -15,6 +15,9 @@ class TextInputPluginDelegate { // Notifies the delegate of the updated the cursor rect in Flutter root view // coordinates. virtual void OnCursorRectUpdated(const Rect& rect) = 0; + + // Notifies the delegate that the system IME composing state should be reset. + virtual void OnResetImeComposing() = 0; }; } // namespace flutter diff --git a/shell/platform/windows/text_input_plugin_unittest.cc b/shell/platform/windows/text_input_plugin_unittest.cc index aac581e50b4f3..c587c670dc0d0 100644 --- a/shell/platform/windows/text_input_plugin_unittest.cc +++ b/shell/platform/windows/text_input_plugin_unittest.cc @@ -7,6 +7,7 @@ #include #include "flutter/shell/platform/common/json_message_codec.h" +#include "flutter/shell/platform/common/json_method_codec.h" #include "flutter/shell/platform/windows/flutter_windows_view.h" #include "flutter/shell/platform/windows/testing/test_binary_messenger.h" #include "gmock/gmock.h" @@ -30,8 +31,10 @@ static std::unique_ptr> CreateResponse(bool handled) { class EmptyTextInputPluginDelegate : public TextInputPluginDelegate { public: - // Notifies delegate that the cursor position has changed. - void OnCursorRectUpdated(const Rect& rect) {} + void OnCursorRectUpdated(const Rect& rect) override {} + void OnResetImeComposing() override { ime_was_reset = true; } + + bool ime_was_reset = false; }; } // namespace @@ -59,5 +62,21 @@ TEST(TextInputPluginTest, TextMethodsWorksWithEmptyModel) { // Passes if it did not crash } +TEST(TextInputPluginTest, ClearClientResetsComposing) { + TestBinaryMessenger messenger([](const std::string& channel, + const uint8_t* message, size_t message_size, + BinaryReply reply) {}); + BinaryReply reply_handler = [](const uint8_t* reply, size_t reply_size) {}; + + EmptyTextInputPluginDelegate delegate; + TextInputPlugin handler(&messenger, &delegate); + + auto& codec = JsonMethodCodec::GetInstance(); + auto message = codec.EncodeMethodCall({"TextInput.clearClient", nullptr}); + messenger.SimulateEngineMessage("flutter/textinput", message->data(), + message->size(), reply_handler); + EXPECT_TRUE(delegate.ime_was_reset); +} + } // namespace testing } // namespace flutter diff --git a/shell/platform/windows/window_binding_handler.h b/shell/platform/windows/window_binding_handler.h index 60f93ca8aee66..6f310217aa890 100644 --- a/shell/platform/windows/window_binding_handler.h +++ b/shell/platform/windows/window_binding_handler.h @@ -91,6 +91,10 @@ class WindowBindingHandler { virtual bool OnBitmapSurfaceUpdated(const void* allocation, size_t row_bytes, size_t height) = 0; + + // Invoked when the app ends IME composing, such when the active text input + // client is cleared. + virtual void OnResetImeComposing() = 0; }; } // namespace flutter diff --git a/shell/platform/windows/window_win32.cc b/shell/platform/windows/window_win32.cc index 20f7ebbc49acc..7240463fbcae8 100644 --- a/shell/platform/windows/window_win32.cc +++ b/shell/platform/windows/window_win32.cc @@ -195,6 +195,10 @@ void WindowWin32::OnImeRequest(UINT const message, // https://github.com/flutter/flutter/issues/74547 } +void WindowWin32::AbortImeComposing() { + text_input_manager_.AbortComposing(); +} + void WindowWin32::UpdateCursorRect(const Rect& rect) { text_input_manager_.UpdateCaretRect(rect); } diff --git a/shell/platform/windows/window_win32.h b/shell/platform/windows/window_win32.h index 4e14faa4680c8..3607faf820231 100644 --- a/shell/platform/windows/window_win32.h +++ b/shell/platform/windows/window_win32.h @@ -161,6 +161,10 @@ class WindowWin32 { WPARAM const wparam, LPARAM const lparam); + // Called when the app ends IME composing, such as when the text input client + // is cleared or changed. + virtual void AbortImeComposing(); + // Called when the cursor rect has been updated. // // |rect| is in Win32 window coordinates.