6 #include <rapidjson/document.h>
10 #include "flutter/fml/macros.h"
14 #include "flutter/shell/platform/windows/testing/engine_modifier.h"
15 #include "flutter/shell/platform/windows/testing/flutter_windows_engine_builder.h"
16 #include "flutter/shell/platform/windows/testing/mock_window_binding_handler.h"
17 #include "flutter/shell/platform/windows/testing/test_binary_messenger.h"
18 #include "flutter/shell/platform/windows/testing/windows_test.h"
19 #include "gmock/gmock.h"
20 #include "gtest/gtest.h"
27 : text_input_plugin(text_input_plugin) {}
30 text_input_plugin->view_id_ = view_id;
35 bool HasActiveModel() {
return text_input_plugin->active_model_ !=
nullptr; }
46 using ::testing::Return;
48 static constexpr
char kScanCodeKey[] =
"scanCode";
49 static constexpr
int kHandledScanCode = 20;
50 static constexpr
int kUnhandledScanCode = 21;
52 static constexpr
int kDefaultClientId = 42;
54 static constexpr
char kChannelName[] =
"flutter/textinput";
56 static constexpr
char kViewId[] =
"viewId";
59 static constexpr
char kTextKey[] =
"text";
67 "TextInputClient.updateEditingState";
69 static std::unique_ptr<std::vector<uint8_t>> CreateResponse(
bool handled) {
71 std::make_unique<rapidjson::Document>(rapidjson::kObjectType);
72 auto& allocator = response_doc->GetAllocator();
73 response_doc->AddMember(
"handled", handled, allocator);
77 static std::unique_ptr<rapidjson::Document> EncodedClientConfig(
78 std::string type_name,
79 std::string input_action) {
80 auto arguments = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
81 auto& allocator = arguments->GetAllocator();
82 arguments->PushBack(kDefaultClientId, allocator);
84 rapidjson::Value config(rapidjson::kObjectType);
85 config.AddMember(
"inputAction", input_action, allocator);
87 config.AddMember(
kViewId, 456, allocator);
88 rapidjson::Value type_info(rapidjson::kObjectType);
89 type_info.AddMember(
"name", type_name, allocator);
90 config.AddMember(
"inputType", type_info, allocator);
91 arguments->PushBack(config, allocator);
96 static std::unique_ptr<rapidjson::Document> EncodedEditingState(
98 TextRange selection) {
99 auto model = std::make_unique<TextInputModel>();
100 model->SetText(
text);
101 model->SetSelection(selection);
103 auto arguments = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
104 auto& allocator = arguments->GetAllocator();
105 arguments->PushBack(kDefaultClientId, allocator);
107 rapidjson::Value editing_state(rapidjson::kObjectType);
115 model->composing() ? model->composing_range().base() : -1;
116 int composing_extent =
117 model->composing() ? model->composing_range().extent() : -1;
121 rapidjson::Value(model->GetText(), allocator).Move(),
123 arguments->PushBack(editing_state, allocator);
128 class MockFlutterWindowsView :
public FlutterWindowsView {
131 std::unique_ptr<WindowBindingHandler> window)
139 MOCK_METHOD(
void, OnCursorRectUpdated, (
const Rect&), (
override));
140 MOCK_METHOD(
void, OnResetImeComposing, (), (
override));
143 FML_DISALLOW_COPY_AND_ASSIGN(MockFlutterWindowsView);
155 MockFlutterWindowsView*
view() {
return view_.get(); }
156 MockWindowBindingHandler*
window() {
return window_; }
159 FlutterWindowsEngineBuilder builder{GetContext()};
161 engine_ = builder.Build();
165 FlutterWindowsEngineBuilder builder{GetContext()};
167 auto window = std::make_unique<MockWindowBindingHandler>();
170 EXPECT_CALL(*window_, SetView).Times(1);
171 EXPECT_CALL(*
window, GetWindowHandle).WillRepeatedly(Return(
nullptr));
173 engine_ = builder.Build();
174 view_ = std::make_unique<MockFlutterWindowsView>(engine_.get(),
177 EngineModifier modifier{engine_.get()};
178 modifier.SetViewById(view_.get(), 456);
182 EXPECT_NE(engine_,
nullptr);
183 auto window = std::make_unique<MockWindowBindingHandler>();
184 EXPECT_CALL(*
window, SetView).Times(1);
185 EXPECT_CALL(*
window, GetWindowHandle).WillRepeatedly(Return(
nullptr));
186 auto view = std::make_unique<MockFlutterWindowsView>(engine_.get(),
189 EngineModifier modifier{engine_.get()};
190 modifier.SetViewById(view_.get(), view_id);
195 std::unique_ptr<FlutterWindowsEngine> engine_;
196 std::unique_ptr<MockFlutterWindowsView> view_;
197 MockWindowBindingHandler* window_;
205 auto handled_message = CreateResponse(
true);
206 auto unhandled_message = CreateResponse(
false);
207 int received_scancode = 0;
209 TestBinaryMessenger messenger(
210 [&received_scancode, &handled_message, &unhandled_message](
211 const std::string& channel,
const uint8_t*
message,
214 int redispatch_scancode = 0;
217 handler.
KeyboardHook(VK_RETURN, 100, WM_KEYDOWN,
'\n',
false,
false);
220 text.push_back(
'\n');
230 TestBinaryMessenger messenger([](
const std::string& channel,
231 const uint8_t*
message,
size_t message_size,
233 BinaryReply reply_handler = [](
const uint8_t* reply,
size_t reply_size) {};
239 EXPECT_CALL(*view(), OnResetImeComposing());
242 auto message = codec.EncodeMethodCall({
"TextInput.clearClient",
nullptr});
244 message->size(), reply_handler);
251 TestBinaryMessenger messenger([](
const std::string& channel,
252 const uint8_t*
message,
size_t message_size,
256 BinaryReply reply_handler = [&reply](
const uint8_t* reply_bytes,
258 reply = std::string(
reinterpret_cast<const char*
>(reply_bytes), reply_size);
264 auto message = codec.EncodeMethodCall({
"TextInput.clearClient",
nullptr});
266 message->size(), reply_handler);
270 "[\"Internal Consistency Error\",\"Text input is not available because "
271 "view with view_id=0 cannot be found\",null]");
279 bool sent_message =
false;
280 TestBinaryMessenger messenger(
281 [&sent_message](
const std::string& channel,
const uint8_t*
message,
284 BinaryReply reply_handler = [](
const uint8_t* reply,
size_t reply_size) {};
291 auto arguments = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
292 auto& allocator = arguments->GetAllocator();
293 arguments->PushBack(kDefaultClientId, allocator);
294 rapidjson::Value config(rapidjson::kObjectType);
295 config.AddMember(
"inputAction",
"done", allocator);
296 config.AddMember(
"inputType",
"text", allocator);
298 config.AddMember(
kViewId, 456, allocator);
299 arguments->PushBack(config, allocator);
301 codec.EncodeMethodCall({
"TextInput.setClient", std::move(arguments)});
302 messenger.SimulateEngineMessage(
"flutter/textinput",
message->data(),
303 message->size(), reply_handler);
306 sent_message =
false;
308 EXPECT_TRUE(sent_message);
311 sent_message =
false;
313 EXPECT_TRUE(sent_message);
323 sent_message =
false;
325 EXPECT_FALSE(sent_message);
328 sent_message =
false;
330 EXPECT_TRUE(sent_message);
337 std::vector<std::string> messages;
339 TestBinaryMessenger messenger(
340 [&messages](
const std::string& channel,
const uint8_t*
message,
342 std::string last_message(
reinterpret_cast<const char*
>(
message),
344 messages.push_back(last_message);
346 BinaryReply reply_handler = [](
const uint8_t* reply,
size_t reply_size) {};
353 auto set_client_arguments =
354 EncodedClientConfig(
"TextInputType.multiline",
"TextInputAction.newline");
355 auto message = codec.EncodeMethodCall(
356 {
"TextInput.setClient", std::move(set_client_arguments)});
357 messenger.SimulateEngineMessage(
"flutter/textinput",
message->data(),
358 message->size(), reply_handler);
361 handler.
KeyboardHook(VK_RETURN, 100, WM_KEYDOWN,
'\n',
false,
false);
365 EXPECT_EQ(messages.size(), 2);
368 auto encoded_arguments = EncodedEditingState(
"\n",
TextRange(1));
369 auto update_state_message = codec.EncodeMethodCall(
372 EXPECT_TRUE(std::equal(update_state_message->begin(),
373 update_state_message->end(),
374 messages.front().begin()));
377 auto arguments = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
378 auto& allocator = arguments->GetAllocator();
379 arguments->PushBack(kDefaultClientId, allocator);
381 rapidjson::Value(
"TextInputAction.newline", allocator).Move(), allocator);
382 auto invoke_action_message = codec.EncodeMethodCall(
383 {
"TextInputClient.performAction", std::move(arguments)});
385 EXPECT_TRUE(std::equal(invoke_action_message->begin(),
386 invoke_action_message->end(),
387 messages.back().begin()));
394 std::vector<std::vector<uint8_t>> messages;
396 TestBinaryMessenger messenger(
397 [&messages](
const std::string& channel,
const uint8_t*
message,
399 int length =
static_cast<int>(message_size);
400 std::vector<uint8_t> last_message(length);
401 memcpy(&last_message[0], &
message[0], length *
sizeof(uint8_t));
402 messages.push_back(last_message);
404 BinaryReply reply_handler = [](
const uint8_t* reply,
size_t reply_size) {};
411 auto set_client_arguments =
412 EncodedClientConfig(
"TextInputType.multiline",
"TextInputAction.send");
413 auto message = codec.EncodeMethodCall(
414 {
"TextInput.setClient", std::move(set_client_arguments)});
415 messenger.SimulateEngineMessage(
"flutter/textinput",
message->data(),
416 message->size(), reply_handler);
419 handler.
KeyboardHook(VK_RETURN, 100, WM_KEYDOWN,
'\n',
false,
false);
422 EXPECT_EQ(messages.size(), 1);
425 auto arguments = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
426 auto& allocator = arguments->GetAllocator();
427 arguments->PushBack(kDefaultClientId, allocator);
429 rapidjson::Value(
"TextInputAction.send", allocator).Move(), allocator);
430 auto invoke_action_message = codec.EncodeMethodCall(
431 {
"TextInputClient.performAction", std::move(arguments)});
433 EXPECT_TRUE(std::equal(invoke_action_message->begin(),
434 invoke_action_message->end(),
435 messages.front().begin()));
441 TestBinaryMessenger messenger([](
const std::string& channel,
442 const uint8_t*
message,
size_t message_size,
447 auto args = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
448 auto& allocator = args->GetAllocator();
449 args->PushBack(123, allocator);
451 rapidjson::Value client_config(rapidjson::kObjectType);
453 args->PushBack(client_config, allocator);
458 BinaryReply reply_handler = [&reply](
const uint8_t* reply_bytes,
460 reply = std::string(
reinterpret_cast<const char*
>(reply_bytes), reply_size);
463 EXPECT_TRUE(messenger.SimulateEngineMessage(
kChannelName, encoded->data(),
464 encoded->size(), reply_handler));
467 "[\"Bad Arguments\",\"Could not set client, view ID is null.\",null]");
473 TestBinaryMessenger messenger([](
const std::string& channel,
474 const uint8_t*
message,
size_t message_size,
479 auto args = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
480 auto& allocator = args->GetAllocator();
481 args->PushBack(123, allocator);
483 rapidjson::Value client_config(rapidjson::kObjectType);
484 client_config.AddMember(
kViewId,
"Not an integer", allocator);
486 args->PushBack(client_config, allocator);
491 BinaryReply reply_handler = [&reply](
const uint8_t* reply_bytes,
493 reply = std::string(
reinterpret_cast<const char*
>(reply_bytes), reply_size);
496 EXPECT_TRUE(messenger.SimulateEngineMessage(
kChannelName, encoded->data(),
497 encoded->size(), reply_handler));
500 "[\"Bad Arguments\",\"Could not set client, view ID is null.\",null]");
506 auto handled_message = CreateResponse(
true);
507 auto unhandled_message = CreateResponse(
false);
508 int received_scancode = 0;
510 TestBinaryMessenger messenger(
511 [&received_scancode, &handled_message, &unhandled_message](
512 const std::string& channel,
const uint8_t*
message,
515 int redispatch_scancode = 0;
518 auto args = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
519 auto& allocator = args->GetAllocator();
520 args->PushBack(123, allocator);
522 rapidjson::Value client_config(rapidjson::kObjectType);
524 client_config.AddMember(
kViewId, 456, allocator);
526 args->PushBack(client_config, allocator);
530 EXPECT_TRUE(messenger.SimulateEngineMessage(
532 [](
const uint8_t* reply,
size_t reply_size) {}));
534 handler.
KeyboardHook(VK_RETURN, 100, WM_KEYDOWN,
'\n',
false,
false);
537 text.push_back(
'\n');
541 handler.
KeyboardHook(0x4E, 100, WM_KEYDOWN,
'n',
false,
false);
543 std::u16string textN;
546 handler.
KeyboardHook(0x49, 100, WM_KEYDOWN,
'i',
false,
false);
547 std::u16string textNi;
551 handler.
KeyboardHook(VK_RETURN, 100, WM_KEYDOWN,
'\n',
false,
false);
552 std::u16string textChineseCharacter;
553 text.push_back(u
'\u4F60');
565 int selection_base = -1;
566 TestBinaryMessenger messenger([&](
const std::string& channel,
567 const uint8_t*
message,
size_t size,
572 const auto& args = *method->arguments();
573 const auto& editing_state = args[1];
576 ASSERT_NE(base, editing_state.MemberEnd());
577 ASSERT_TRUE(base->value.IsInt());
578 ASSERT_NE(extent, editing_state.MemberEnd());
579 ASSERT_TRUE(extent->value.IsInt());
580 selection_base = base->value.GetInt();
581 EXPECT_EQ(extent->value.GetInt(), selection_base);
587 auto args = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
588 auto& allocator = args->GetAllocator();
589 args->PushBack(123, allocator);
590 rapidjson::Value client_config(rapidjson::kObjectType);
591 client_config.AddMember(
kViewId, 456, allocator);
592 args->PushBack(client_config, allocator);
595 EXPECT_TRUE(messenger.SimulateEngineMessage(
597 [](
const uint8_t* reply,
size_t reply_size) {}));
599 plugin.ComposeBeginHook();
600 EXPECT_EQ(selection_base, 0);
601 plugin.ComposeChangeHook(u
"abc", 3);
602 EXPECT_EQ(selection_base, 3);
604 plugin.ComposeCommitHook();
605 plugin.ComposeEndHook();
606 EXPECT_EQ(selection_base, 3);
608 plugin.ComposeBeginHook();
609 plugin.ComposeChangeHook(u
"1", 1);
610 EXPECT_EQ(selection_base, 4);
612 plugin.ComposeChangeHook(u
"12", 2);
613 EXPECT_EQ(selection_base, 5);
615 plugin.ComposeChangeHook(u
"12", 1);
616 EXPECT_EQ(selection_base, 4);
618 plugin.ComposeChangeHook(u
"12", 2);
619 EXPECT_EQ(selection_base, 5);
632 double ime_width = 50;
633 double ime_height = 60;
636 std::array<std::array<double, 4>, 4> editabletext_transform = {
637 1.0, 0.0, 0.0, view_x,
638 0.0, 1.0, 0.0, view_y,
642 TestBinaryMessenger messenger([](
const std::string& channel,
643 const uint8_t*
message,
size_t message_size,
645 BinaryReply reply_handler = [](
const uint8_t* reply,
size_t reply_size) {};
653 EXPECT_CALL(*view(), OnCursorRectUpdated(Rect{{view_x, view_y}, {0, 0}}));
657 std::make_unique<rapidjson::Document>(rapidjson::kObjectType);
658 auto& allocator = arguments->GetAllocator();
660 rapidjson::Value transoform(rapidjson::kArrayType);
661 for (
int i = 0; i < 4 * 4; i++) {
663 transoform.PushBack(editabletext_transform[i % 4][i / 4], allocator);
666 arguments->AddMember(
"transform", transoform, allocator);
668 auto message = codec.EncodeMethodCall(
669 {
"TextInput.setEditableSizeAndTransform", std::move(arguments)});
671 message->size(), reply_handler);
675 OnCursorRectUpdated(Rect{{view_x + ime_x, view_y + ime_y},
676 {ime_width, ime_height}}));
680 std::make_unique<rapidjson::Document>(rapidjson::kObjectType);
681 auto& allocator = arguments->GetAllocator();
683 arguments->AddMember(
"x", ime_x, allocator);
684 arguments->AddMember(
"y", ime_y, allocator);
685 arguments->AddMember(
"width", ime_width, allocator);
686 arguments->AddMember(
"height", ime_height, allocator);
688 auto message = codec.EncodeMethodCall(
689 {
"TextInput.setMarkedTextRect", std::move(arguments)});
691 message->size(), reply_handler);
698 TestBinaryMessenger messenger([](
const std::string& channel,
699 const uint8_t*
message,
size_t message_size,
703 BinaryReply reply_handler = [&reply](
const uint8_t* reply_bytes,
705 reply = std::string(
reinterpret_cast<const char*
>(reply_bytes), reply_size);
713 std::make_unique<rapidjson::Document>(rapidjson::kObjectType);
714 auto& allocator = arguments->GetAllocator();
716 arguments->AddMember(
"x", 0, allocator);
717 arguments->AddMember(
"y", 0, allocator);
718 arguments->AddMember(
"width", 0, allocator);
719 arguments->AddMember(
"height", 0, allocator);
721 auto message = codec.EncodeMethodCall(
722 {
"TextInput.setMarkedTextRect", std::move(arguments)});
724 message->size(), reply_handler);
728 "[\"Internal Consistency Error\",\"Text input is not available because "
729 "view with view_id=0 cannot be found\",null]");
736 bool sent_message =
false;
737 TestBinaryMessenger messenger(
738 [&sent_message](
const std::string& channel,
const uint8_t*
message,
744 auto const set_client_and_send_message = [&](
int client_id,
int view_id) {
745 auto args = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
746 auto& allocator = args->GetAllocator();
747 args->PushBack(client_id, allocator);
749 rapidjson::Value client_config(rapidjson::kObjectType);
750 client_config.AddMember(
kViewId, view_id, allocator);
752 args->PushBack(client_config, allocator);
757 BinaryReply reply_handler = [&reply](
const uint8_t* reply_bytes,
760 std::string(
reinterpret_cast<const char*
>(reply_bytes), reply_size);
763 EXPECT_TRUE(messenger.SimulateEngineMessage(
764 kChannelName, encoded->data(), encoded->size(), reply_handler));
766 sent_message =
false;
767 handler.ComposeBeginHook();
768 EXPECT_TRUE(sent_message);
769 sent_message =
false;
770 handler.ComposeChangeHook(u
"4", 1);
771 EXPECT_TRUE(sent_message);
772 sent_message =
false;
773 handler.ComposeCommitHook();
774 EXPECT_FALSE(sent_message);
775 sent_message =
false;
776 handler.ComposeEndHook();
777 EXPECT_TRUE(sent_message);
780 set_client_and_send_message(123, 456);
781 set_client_and_send_message(123, 789);
789 TestBinaryMessenger messenger([](
const std::string& channel,
790 const uint8_t*
message,
size_t message_size,
792 BinaryReply reply_handler = [](
const uint8_t* reply,
size_t reply_size) {};
800 auto set_client_message = codec.EncodeMethodCall(
801 {
"TextInput.setClient",
802 EncodedClientConfig(
"TextInputType.text",
"TextInputAction.done")});
803 messenger.SimulateEngineMessage(
kChannelName, set_client_message->data(),
804 set_client_message->size(), reply_handler);
821 TestBinaryMessenger messenger([](
const std::string& channel,
822 const uint8_t*
message,
size_t message_size,
824 BinaryReply reply_handler = [](
const uint8_t* reply,
size_t reply_size) {};
832 auto set_client_message = codec.EncodeMethodCall(
833 {
"TextInput.setClient",
834 EncodedClientConfig(
"TextInputType.text",
"TextInputAction.done")});
835 messenger.SimulateEngineMessage(
kChannelName, set_client_message->data(),
836 set_client_message->size(), reply_handler);
853 TestBinaryMessenger messenger([](
const std::string& channel,
854 const uint8_t*
message,
size_t message_size,
856 BinaryReply reply_handler = [](
const uint8_t* reply,
size_t reply_size) {};
864 auto set_client_message = codec.EncodeMethodCall(
865 {
"TextInput.setClient",
866 EncodedClientConfig(
"TextInputType.text",
"TextInputAction.done")});
867 messenger.SimulateEngineMessage(
kChannelName, set_client_message->data(),
868 set_client_message->size(), reply_handler);
FlutterWindowsView(FlutterViewId view_id, FlutterWindowsEngine *engine, std::unique_ptr< WindowBindingHandler > window_binding, bool is_sized_to_content, const BoxConstraints &box_constraints, FlutterWindowsViewSizingDelegate *sizing_delegate=nullptr, std::shared_ptr< WindowsProcTable > windows_proc_table=nullptr)
static const JsonMessageCodec & GetInstance()
static const JsonMethodCodec & GetInstance()
std::unique_ptr< std::vector< uint8_t > > EncodeMessage(const T &message) const
std::unique_ptr< MethodCall< T > > DecodeMethodCall(const uint8_t *message, size_t message_size) const
std::unique_ptr< std::vector< uint8_t > > EncodeMethodCall(const MethodCall< T > &method_call) const
virtual void ComposeCommitHook()
void OnViewRemoved(FlutterViewId view_id)
virtual void ComposeChangeHook(const std::u16string &text, int cursor_pos)
virtual void ComposeEndHook()
virtual void KeyboardHook(int key, int scancode, int action, char32_t character, bool extended, bool was_down)
virtual void ComposeBeginHook()
FlutterViewId GetViewId()
void SetViewId(FlutterViewId view_id)
TextInputPluginModifier(TextInputPlugin *text_input_plugin)
MOCK_METHOD(void, NotifyWinEventWrapper,(ui::AXPlatformNodeWin *, ax::mojom::Event),(override))
MockFlutterWindowsView(FlutterWindowsEngine *engine, std::unique_ptr< WindowBindingHandler > wbh)
TextInputPluginTest()=default
MockWindowBindingHandler * window()
virtual ~TextInputPluginTest()=default
FlutterWindowsEngine * engine()
MockFlutterWindowsView * view()
std::unique_ptr< MockFlutterWindowsView > AddViewWithId(int view_id)
static constexpr char kChannelName[]
TEST_F(AccessibilityPluginTest, DirectAnnounceCall)
std::function< void(const uint8_t *reply, size_t reply_size)> BinaryReply
constexpr FlutterViewId kImplicitViewId
static constexpr char kEnableDeltaModel[]
static constexpr char kSelectionIsDirectionalKey[]
static constexpr char kSelectionExtentKey[]
static constexpr char kSetClientMethod[]
static constexpr char kUpdateEditingStateMethod[]
static constexpr char kComposingExtentKey[]
static constexpr char kComposingBaseKey[]
static constexpr char kAffinityDownstream[]
static constexpr char kSelectionAffinityKey[]
static constexpr char kViewId[]
static constexpr char kSelectionBaseKey[]