diff --git a/shell/platform/windows/text_input_plugin.cc b/shell/platform/windows/text_input_plugin.cc index 15e497e512de0..543b0c002337d 100644 --- a/shell/platform/windows/text_input_plugin.cc +++ b/shell/platform/windows/text_input_plugin.cc @@ -111,7 +111,29 @@ void TextInputPlugin::ComposeCommitHook() { return; } active_model_->CommitComposing(); - SendStateUpdate(*active_model_); + + // We do not trigger SendStateUpdate here. + // + // Until a WM_IME_ENDCOMPOSING event, the user is still composing from the OS + // point of view. Commit events are always immediately followed by another + // composing event or an end composing event. However, in the brief window + // between the commit event and the following event, the composing region is + // collapsed. Notifying the framework of this intermediate state will trigger + // any framework code designed to execute at the end of composing, such as + // input formatters, which may try to update the text and send a message back + // to the engine with changes. + // + // This is a particular problem with Korean IMEs, which build up one + // character at a time in their composing region until a keypress that makes + // no sense for the in-progress character. At that point, the result + // character is committed and a compose event is immedidately received with + // the new composing region. + // + // In the case where this event is immediately followed by a composing event, + // the state will be sent in ComposeChangeHook. + // + // In the case where this event is immediately followed by an end composing + // event, the state will be sent in ComposeEndHook. } void TextInputPlugin::ComposeEndHook() { diff --git a/shell/platform/windows/text_input_plugin_unittest.cc b/shell/platform/windows/text_input_plugin_unittest.cc index 2da9c77aae3d5..30af07d96e42d 100644 --- a/shell/platform/windows/text_input_plugin_unittest.cc +++ b/shell/platform/windows/text_input_plugin_unittest.cc @@ -81,5 +81,61 @@ TEST(TextInputPluginTest, ClearClientResetsComposing) { EXPECT_TRUE(delegate.ime_was_reset()); } +// Verify that the embedder sends state update messages to the framework during +// IME composing. +TEST(TextInputPluginTest, VerifyComposingSendStateUpdate) { + bool sent_message = false; + TestBinaryMessenger messenger( + [&sent_message](const std::string& channel, const uint8_t* message, + size_t message_size, + BinaryReply reply) { sent_message = true; }); + BinaryReply reply_handler = [](const uint8_t* reply, size_t reply_size) {}; + + EmptyTextInputPluginDelegate delegate; + TextInputPlugin handler(&messenger, &delegate); + + auto& codec = JsonMethodCodec::GetInstance(); + + // Call TextInput.setClient to initialize the TextInputModel. + auto arguments = std::make_unique(rapidjson::kArrayType); + auto& allocator = arguments->GetAllocator(); + arguments->PushBack(42, allocator); + rapidjson::Value config(rapidjson::kObjectType); + config.AddMember("inputAction", "done", allocator); + config.AddMember("inputType", "text", allocator); + arguments->PushBack(config, allocator); + auto message = + codec.EncodeMethodCall({"TextInput.setClient", std::move(arguments)}); + messenger.SimulateEngineMessage("flutter/textinput", message->data(), + message->size(), reply_handler); + + // ComposeBeginHook should send state update. + sent_message = false; + handler.ComposeBeginHook(); + EXPECT_TRUE(sent_message); + + // ComposeChangeHook should send state update. + sent_message = false; + handler.ComposeChangeHook(u"4", 1); + EXPECT_TRUE(sent_message); + + // ComposeCommitHook should NOT send state update. + // + // Commit messages are always immediately followed by a change message or an + // end message, both of which will send an update. Sending intermediate state + // with a collapsed composing region will trigger the framework to assume + // composing has ended, which is not the case until a WM_IME_ENDCOMPOSING + // event is received in the main event loop, which will trigger a call to + // ComposeEndHook. + sent_message = false; + handler.ComposeCommitHook(); + EXPECT_FALSE(sent_message); + + // ComposeEndHook should send state update. + sent_message = false; + handler.ComposeEndHook(); + EXPECT_TRUE(sent_message); +} + } // namespace testing } // namespace flutter