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

Commit 16ea940

Browse files
committed
[Windows - TextInput] Insert new line only when TextInputAction.newline
1 parent 168b0bf commit 16ea940

File tree

2 files changed

+165
-1
lines changed

2 files changed

+165
-1
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: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,14 @@ static constexpr char kTextPlainFormat[] = "text/plain";
2727
static constexpr char kChannelName[] = "flutter/textinput";
2828
static constexpr char kEnableDeltaModel[] = "enableDeltaModel";
2929
static constexpr char kSetClientMethod[] = "TextInput.setClient";
30+
static constexpr char kAffinityDownstream[] = "TextAffinity.downstream";
31+
static constexpr char kTextKey[] = "text";
32+
static constexpr char kSelectionBaseKey[] = "selectionBase";
33+
static constexpr char kSelectionExtentKey[] = "selectionExtent";
34+
static constexpr char kSelectionAffinityKey[] = "selectionAffinity";
35+
static constexpr char kSelectionIsDirectionalKey[] = "selectionIsDirectional";
36+
static constexpr char kComposingBaseKey[] = "composingBase";
37+
static constexpr char kComposingExtentKey[] = "composingExtent";
3038

3139
static std::unique_ptr<std::vector<uint8_t>> CreateResponse(bool handled) {
3240
auto response_doc =
@@ -36,6 +44,56 @@ static std::unique_ptr<std::vector<uint8_t>> CreateResponse(bool handled) {
3644
return JsonMessageCodec::GetInstance().EncodeMessage(*response_doc);
3745
}
3846

47+
static std::unique_ptr<rapidjson::Document> EncodedClientConfig(
48+
std::string type_name,
49+
std::string input_action) {
50+
auto arguments = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
51+
auto& allocator = arguments->GetAllocator();
52+
arguments->PushBack(42, allocator);
53+
54+
rapidjson::Value config(rapidjson::kObjectType);
55+
config.AddMember("inputAction", input_action, allocator);
56+
config.AddMember(kEnableDeltaModel, false, allocator);
57+
rapidjson::Value type_info(rapidjson::kObjectType);
58+
type_info.AddMember("name", type_name, allocator);
59+
config.AddMember("inputType", type_info, allocator);
60+
arguments->PushBack(config, allocator);
61+
62+
return arguments;
63+
}
64+
65+
static std::unique_ptr<rapidjson::Document> EncodedEditingState(
66+
std::string text,
67+
TextRange selection) {
68+
auto model = std::make_unique<TextInputModel>();
69+
model->SetText(text);
70+
model->SetSelection(selection);
71+
72+
auto arguments = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
73+
auto& allocator = arguments->GetAllocator();
74+
arguments->PushBack(42, allocator);
75+
76+
rapidjson::Value editing_state(rapidjson::kObjectType);
77+
editing_state.AddMember(kSelectionAffinityKey, kAffinityDownstream,
78+
allocator);
79+
editing_state.AddMember(kSelectionBaseKey, selection.base(), allocator);
80+
editing_state.AddMember(kSelectionExtentKey, selection.extent(), allocator);
81+
editing_state.AddMember(kSelectionIsDirectionalKey, false, allocator);
82+
83+
int composing_base =
84+
model->composing() ? model->composing_range().base() : -1;
85+
int composing_extent =
86+
model->composing() ? model->composing_range().extent() : -1;
87+
editing_state.AddMember(kComposingBaseKey, composing_base, allocator);
88+
editing_state.AddMember(kComposingExtentKey, composing_extent, allocator);
89+
editing_state.AddMember(kTextKey,
90+
rapidjson::Value(model->GetText(), allocator).Move(),
91+
allocator);
92+
arguments->PushBack(editing_state, allocator);
93+
94+
return arguments;
95+
}
96+
3997
class MockTextInputPluginDelegate : public TextInputPluginDelegate {
4098
public:
4199
MockTextInputPluginDelegate() {}
@@ -148,6 +206,109 @@ TEST(TextInputPluginTest, VerifyComposingSendStateUpdate) {
148206
EXPECT_TRUE(sent_message);
149207
}
150208

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

0 commit comments

Comments
 (0)