Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit f382b85

Browse files
authored
[Windows - TextInput] Insert new line only when TextInputAction.newline (#42244)
## Description This PR updates the Windows text input plugin to avoid adding a new line on a multiline text field when action is not set to `TextInputAction.newline`. ## Related Issue Fixes flutter/flutter#125879 as Linux and macOS implementations are merged. Linux PR: #41895 macOS PR: #41977 ## Tests Adds 2 tests.
1 parent 58d9941 commit f382b85

File tree

2 files changed

+167
-2
lines changed

2 files changed

+167
-2
lines changed

shell/platform/windows/text_input_plugin.cc

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ static constexpr char kBadArgumentError[] = "Bad Arguments";
5959
static constexpr char kInternalConsistencyError[] =
6060
"Internal Consistency Error";
6161

62+
static constexpr char kInputActionNewline[] = "TextInputAction.newline";
63+
6264
namespace flutter {
6365

6466
void TextInputPlugin::TextHook(const std::u16string& text) {
@@ -446,7 +448,8 @@ void TextInputPlugin::SendStateUpdateWithDelta(const TextInputModel& model,
446448
}
447449

448450
void TextInputPlugin::EnterPressed(TextInputModel* model) {
449-
if (input_type_ == kMultilineInputType) {
451+
if (input_type_ == kMultilineInputType &&
452+
input_action_ == kInputActionNewline) {
450453
std::u16string text_before_change = fml::Utf8ToUtf16(model->GetText());
451454
TextRange selection_before_change = model->selection();
452455
model->AddText(u"\n");

shell/platform/windows/text_input_plugin_unittest.cc

Lines changed: 163 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,19 @@ static constexpr char kScanCodeKey[] = "scanCode";
2323
static constexpr int kHandledScanCode = 20;
2424
static constexpr int kUnhandledScanCode = 21;
2525
static constexpr char kTextPlainFormat[] = "text/plain";
26+
static constexpr int kDefaultClientId = 42;
2627
// Should be identical to constants in text_input_plugin.cc.
2728
static constexpr char kChannelName[] = "flutter/textinput";
2829
static constexpr char kEnableDeltaModel[] = "enableDeltaModel";
2930
static constexpr char kSetClientMethod[] = "TextInput.setClient";
31+
static constexpr char kAffinityDownstream[] = "TextAffinity.downstream";
32+
static constexpr char kTextKey[] = "text";
33+
static constexpr char kSelectionBaseKey[] = "selectionBase";
34+
static constexpr char kSelectionExtentKey[] = "selectionExtent";
35+
static constexpr char kSelectionAffinityKey[] = "selectionAffinity";
36+
static constexpr char kSelectionIsDirectionalKey[] = "selectionIsDirectional";
37+
static constexpr char kComposingBaseKey[] = "composingBase";
38+
static constexpr char kComposingExtentKey[] = "composingExtent";
3039

3140
static std::unique_ptr<std::vector<uint8_t>> CreateResponse(bool handled) {
3241
auto response_doc =
@@ -36,6 +45,56 @@ static std::unique_ptr<std::vector<uint8_t>> CreateResponse(bool handled) {
3645
return JsonMessageCodec::GetInstance().EncodeMessage(*response_doc);
3746
}
3847

48+
static std::unique_ptr<rapidjson::Document> EncodedClientConfig(
49+
std::string type_name,
50+
std::string input_action) {
51+
auto arguments = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
52+
auto& allocator = arguments->GetAllocator();
53+
arguments->PushBack(kDefaultClientId, allocator);
54+
55+
rapidjson::Value config(rapidjson::kObjectType);
56+
config.AddMember("inputAction", input_action, allocator);
57+
config.AddMember(kEnableDeltaModel, false, allocator);
58+
rapidjson::Value type_info(rapidjson::kObjectType);
59+
type_info.AddMember("name", type_name, allocator);
60+
config.AddMember("inputType", type_info, allocator);
61+
arguments->PushBack(config, allocator);
62+
63+
return arguments;
64+
}
65+
66+
static std::unique_ptr<rapidjson::Document> EncodedEditingState(
67+
std::string text,
68+
TextRange selection) {
69+
auto model = std::make_unique<TextInputModel>();
70+
model->SetText(text);
71+
model->SetSelection(selection);
72+
73+
auto arguments = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
74+
auto& allocator = arguments->GetAllocator();
75+
arguments->PushBack(kDefaultClientId, allocator);
76+
77+
rapidjson::Value editing_state(rapidjson::kObjectType);
78+
editing_state.AddMember(kSelectionAffinityKey, kAffinityDownstream,
79+
allocator);
80+
editing_state.AddMember(kSelectionBaseKey, selection.base(), allocator);
81+
editing_state.AddMember(kSelectionExtentKey, selection.extent(), allocator);
82+
editing_state.AddMember(kSelectionIsDirectionalKey, false, allocator);
83+
84+
int composing_base =
85+
model->composing() ? model->composing_range().base() : -1;
86+
int composing_extent =
87+
model->composing() ? model->composing_range().extent() : -1;
88+
editing_state.AddMember(kComposingBaseKey, composing_base, allocator);
89+
editing_state.AddMember(kComposingExtentKey, composing_extent, allocator);
90+
editing_state.AddMember(kTextKey,
91+
rapidjson::Value(model->GetText(), allocator).Move(),
92+
allocator);
93+
arguments->PushBack(editing_state, allocator);
94+
95+
return arguments;
96+
}
97+
3998
class MockTextInputPluginDelegate : public TextInputPluginDelegate {
4099
public:
41100
MockTextInputPluginDelegate() {}
@@ -109,7 +168,7 @@ TEST(TextInputPluginTest, VerifyComposingSendStateUpdate) {
109168
// Call TextInput.setClient to initialize the TextInputModel.
110169
auto arguments = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
111170
auto& allocator = arguments->GetAllocator();
112-
arguments->PushBack(42, allocator);
171+
arguments->PushBack(kDefaultClientId, allocator);
113172
rapidjson::Value config(rapidjson::kObjectType);
114173
config.AddMember("inputAction", "done", allocator);
115174
config.AddMember("inputType", "text", allocator);
@@ -148,6 +207,109 @@ TEST(TextInputPluginTest, VerifyComposingSendStateUpdate) {
148207
EXPECT_TRUE(sent_message);
149208
}
150209

210+
TEST(TextInputPluginTest, VerifyInputActionNewlineInsertNewLine) {
211+
// Store messages as std::string for convenience.
212+
std::vector<std::string> messages;
213+
214+
TestBinaryMessenger messenger(
215+
[&messages](const std::string& channel, const uint8_t* message,
216+
size_t message_size, BinaryReply reply) {
217+
std::string last_message(reinterpret_cast<const char*>(message),
218+
message_size);
219+
messages.push_back(last_message);
220+
});
221+
BinaryReply reply_handler = [](const uint8_t* reply, size_t reply_size) {};
222+
223+
MockTextInputPluginDelegate delegate;
224+
TextInputPlugin handler(&messenger, &delegate);
225+
226+
auto& codec = JsonMethodCodec::GetInstance();
227+
228+
// Call TextInput.setClient to initialize the TextInputModel.
229+
auto set_client_arguments =
230+
EncodedClientConfig("TextInputType.multiline", "TextInputAction.newline");
231+
auto message = codec.EncodeMethodCall(
232+
{"TextInput.setClient", std::move(set_client_arguments)});
233+
messenger.SimulateEngineMessage("flutter/textinput", message->data(),
234+
message->size(), reply_handler);
235+
236+
// Simulate a key down event for '\n'.
237+
handler.KeyboardHook(VK_RETURN, 100, WM_KEYDOWN, '\n', false, false);
238+
239+
// Two messages are expected, the first is TextInput.updateEditingState and
240+
// the second is TextInputClient.performAction.
241+
EXPECT_EQ(messages.size(), 2);
242+
243+
// Editing state should have been updated.
244+
auto encoded_arguments = EncodedEditingState("\n", TextRange(1));
245+
auto update_state_message = codec.EncodeMethodCall(
246+
{"TextInputClient.updateEditingState", std::move(encoded_arguments)});
247+
248+
EXPECT_TRUE(std::equal(update_state_message->begin(),
249+
update_state_message->end(),
250+
messages.front().begin()));
251+
252+
// TextInputClient.performAction should have been called.
253+
auto arguments = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
254+
auto& allocator = arguments->GetAllocator();
255+
arguments->PushBack(kDefaultClientId, allocator);
256+
arguments->PushBack(
257+
rapidjson::Value("TextInputAction.newline", allocator).Move(), allocator);
258+
auto invoke_action_message = codec.EncodeMethodCall(
259+
{"TextInputClient.performAction", std::move(arguments)});
260+
261+
EXPECT_TRUE(std::equal(invoke_action_message->begin(),
262+
invoke_action_message->end(),
263+
messages.back().begin()));
264+
}
265+
266+
// Regression test for https://github.com/flutter/flutter/issues/125879.
267+
TEST(TextInputPluginTest, VerifyInputActionSendDoesNotInsertNewLine) {
268+
std::vector<std::vector<uint8_t>> messages;
269+
270+
TestBinaryMessenger messenger(
271+
[&messages](const std::string& channel, const uint8_t* message,
272+
size_t message_size, BinaryReply reply) {
273+
int length = static_cast<int>(message_size);
274+
std::vector<uint8_t> last_message(length);
275+
memcpy(&last_message[0], &message[0], length * sizeof(uint8_t));
276+
messages.push_back(last_message);
277+
});
278+
BinaryReply reply_handler = [](const uint8_t* reply, size_t reply_size) {};
279+
280+
MockTextInputPluginDelegate delegate;
281+
TextInputPlugin handler(&messenger, &delegate);
282+
283+
auto& codec = JsonMethodCodec::GetInstance();
284+
285+
// Call TextInput.setClient to initialize the TextInputModel.
286+
auto set_client_arguments =
287+
EncodedClientConfig("TextInputType.multiline", "TextInputAction.send");
288+
auto message = codec.EncodeMethodCall(
289+
{"TextInput.setClient", std::move(set_client_arguments)});
290+
messenger.SimulateEngineMessage("flutter/textinput", message->data(),
291+
message->size(), reply_handler);
292+
293+
// Simulate a key down event for '\n'.
294+
handler.KeyboardHook(VK_RETURN, 100, WM_KEYDOWN, '\n', false, false);
295+
296+
// Only a call to TextInputClient.performAction is expected.
297+
EXPECT_EQ(messages.size(), 1);
298+
299+
// TextInputClient.performAction should have been called.
300+
auto arguments = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
301+
auto& allocator = arguments->GetAllocator();
302+
arguments->PushBack(kDefaultClientId, allocator);
303+
arguments->PushBack(
304+
rapidjson::Value("TextInputAction.send", allocator).Move(), allocator);
305+
auto invoke_action_message = codec.EncodeMethodCall(
306+
{"TextInputClient.performAction", std::move(arguments)});
307+
308+
EXPECT_TRUE(std::equal(invoke_action_message->begin(),
309+
invoke_action_message->end(),
310+
messages.front().begin()));
311+
}
312+
151313
TEST(TextInputPluginTest, TextEditingWorksWithDeltaModel) {
152314
auto handled_message = CreateResponse(true);
153315
auto unhandled_message = CreateResponse(false);

0 commit comments

Comments
 (0)