4#include "flutter/shell/platform/windows/text_input_plugin.h"
6#include <rapidjson/document.h>
10#include "flutter/fml/macros.h"
11#include "flutter/shell/platform/common/json_message_codec.h"
12#include "flutter/shell/platform/common/json_method_codec.h"
13#include "flutter/shell/platform/windows/flutter_windows_view.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"
26using ::testing::Return;
29static constexpr int kHandledScanCode = 20;
30static constexpr int kUnhandledScanCode = 21;
32static constexpr int kDefaultClientId = 42;
34static constexpr char kChannelName[] =
"flutter/textinput";
38static constexpr char kTextKey[] =
"text";
46 "TextInputClient.updateEditingState";
48static std::unique_ptr<std::vector<uint8_t>> CreateResponse(
bool handled) {
50 std::make_unique<rapidjson::Document>(rapidjson::kObjectType);
51 auto& allocator = response_doc->GetAllocator();
52 response_doc->AddMember(
"handled", handled, allocator);
56static std::unique_ptr<rapidjson::Document> EncodedClientConfig(
57 std::string type_name,
58 std::string input_action) {
59 auto arguments = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
60 auto& allocator = arguments->GetAllocator();
61 arguments->PushBack(kDefaultClientId, allocator);
63 rapidjson::Value config(rapidjson::kObjectType);
64 config.AddMember(
"inputAction", input_action, allocator);
66 rapidjson::Value type_info(rapidjson::kObjectType);
67 type_info.AddMember(
"name", type_name, allocator);
68 config.AddMember(
"inputType", type_info, allocator);
69 arguments->PushBack(config, allocator);
74static std::unique_ptr<rapidjson::Document> EncodedEditingState(
77 auto model = std::make_unique<TextInputModel>();
79 model->SetSelection(selection);
81 auto arguments = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
82 auto& allocator = arguments->GetAllocator();
83 arguments->PushBack(kDefaultClientId, allocator);
85 rapidjson::Value editing_state(rapidjson::kObjectType);
93 model->composing() ? model->composing_range().base() : -1;
94 int composing_extent =
95 model->composing() ? model->composing_range().extent() : -1;
99 rapidjson::Value(model->GetText(), allocator).Move(),
101 arguments->PushBack(editing_state, allocator);
106class MockFlutterWindowsView :
public FlutterWindowsView {
109 std::unique_ptr<WindowBindingHandler>
window)
114 MOCK_METHOD(
void, OnResetImeComposing, (), (
override));
129 MockFlutterWindowsView*
view() {
return view_.get(); }
141 auto window = std::make_unique<MockWindowBindingHandler>();
144 EXPECT_CALL(*window_, SetView).Times(1);
145 EXPECT_CALL(*
window, GetWindowHandle).WillRepeatedly(Return(
nullptr));
148 view_ = std::make_unique<MockFlutterWindowsView>(engine_.get(),
152 modifier.SetImplicitView(view_.get());
156 std::unique_ptr<FlutterWindowsEngine> engine_;
157 std::unique_ptr<MockFlutterWindowsView> view_;
166 auto handled_message = CreateResponse(
true);
167 auto unhandled_message = CreateResponse(
false);
168 int received_scancode = 0;
170 TestBinaryMessenger messenger(
171 [&received_scancode, &handled_message, &unhandled_message](
172 const std::string& channel,
const uint8_t*
message,
175 int redispatch_scancode = 0;
178 handler.
KeyboardHook(VK_RETURN, 100, WM_KEYDOWN,
'\n',
false,
false);
181 text.push_back(
'\n');
191 TestBinaryMessenger messenger([](
const std::string& channel,
192 const uint8_t*
message,
size_t message_size,
194 BinaryReply reply_handler = [](
const uint8_t* reply,
size_t reply_size) {};
198 EXPECT_CALL(*view(), OnResetImeComposing());
201 auto message = codec.EncodeMethodCall({
"TextInput.clearClient",
nullptr});
203 message->size(), reply_handler);
210 TestBinaryMessenger messenger([](
const std::string& channel,
211 const uint8_t*
message,
size_t message_size,
215 BinaryReply reply_handler = [&reply](
const uint8_t* reply_bytes,
217 reply = std::string(
reinterpret_cast<const char*
>(reply_bytes), reply_size);
223 auto message = codec.EncodeMethodCall({
"TextInput.clearClient",
nullptr});
225 message->size(), reply_handler);
228 "[\"Internal Consistency Error\",\"Text input is not available in "
229 "Windows headless mode\",null]");
237 bool sent_message =
false;
238 TestBinaryMessenger messenger(
239 [&sent_message](
const std::string& channel,
const uint8_t*
message,
242 BinaryReply reply_handler = [](
const uint8_t* reply,
size_t reply_size) {};
249 auto arguments = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
250 auto& allocator = arguments->GetAllocator();
251 arguments->PushBack(kDefaultClientId, allocator);
252 rapidjson::Value config(rapidjson::kObjectType);
253 config.AddMember(
"inputAction",
"done", allocator);
254 config.AddMember(
"inputType",
"text", allocator);
256 arguments->PushBack(config, allocator);
258 codec.EncodeMethodCall({
"TextInput.setClient", std::move(arguments)});
259 messenger.SimulateEngineMessage(
"flutter/textinput",
message->data(),
260 message->size(), reply_handler);
263 sent_message =
false;
268 sent_message =
false;
280 sent_message =
false;
282 EXPECT_FALSE(sent_message);
285 sent_message =
false;
294 std::vector<std::string> messages;
296 TestBinaryMessenger messenger(
297 [&messages](
const std::string& channel,
const uint8_t*
message,
299 std::string last_message(
reinterpret_cast<const char*
>(
message),
301 messages.push_back(last_message);
303 BinaryReply reply_handler = [](
const uint8_t* reply,
size_t reply_size) {};
310 auto set_client_arguments =
311 EncodedClientConfig(
"TextInputType.multiline",
"TextInputAction.newline");
312 auto message = codec.EncodeMethodCall(
313 {
"TextInput.setClient", std::move(set_client_arguments)});
314 messenger.SimulateEngineMessage(
"flutter/textinput",
message->data(),
315 message->size(), reply_handler);
318 handler.
KeyboardHook(VK_RETURN, 100, WM_KEYDOWN,
'\n',
false,
false);
322 EXPECT_EQ(messages.size(), 2);
325 auto encoded_arguments = EncodedEditingState(
"\n",
TextRange(1));
326 auto update_state_message = codec.EncodeMethodCall(
330 update_state_message->end(),
331 messages.front().begin()));
334 auto arguments = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
335 auto& allocator = arguments->GetAllocator();
336 arguments->PushBack(kDefaultClientId, allocator);
338 rapidjson::Value(
"TextInputAction.newline", allocator).Move(), allocator);
339 auto invoke_action_message = codec.EncodeMethodCall(
340 {
"TextInputClient.performAction", std::move(arguments)});
343 invoke_action_message->end(),
344 messages.back().begin()));
351 std::vector<std::vector<uint8_t>> messages;
353 TestBinaryMessenger messenger(
354 [&messages](
const std::string& channel,
const uint8_t*
message,
356 int length =
static_cast<int>(message_size);
357 std::vector<uint8_t> last_message(
length);
358 memcpy(&last_message[0], &
message[0],
length *
sizeof(uint8_t));
359 messages.push_back(last_message);
361 BinaryReply reply_handler = [](
const uint8_t* reply,
size_t reply_size) {};
368 auto set_client_arguments =
369 EncodedClientConfig(
"TextInputType.multiline",
"TextInputAction.send");
370 auto message = codec.EncodeMethodCall(
371 {
"TextInput.setClient", std::move(set_client_arguments)});
372 messenger.SimulateEngineMessage(
"flutter/textinput",
message->data(),
373 message->size(), reply_handler);
376 handler.
KeyboardHook(VK_RETURN, 100, WM_KEYDOWN,
'\n',
false,
false);
379 EXPECT_EQ(messages.size(), 1);
382 auto arguments = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
383 auto& allocator = arguments->GetAllocator();
384 arguments->PushBack(kDefaultClientId, allocator);
386 rapidjson::Value(
"TextInputAction.send", allocator).Move(), allocator);
387 auto invoke_action_message = codec.EncodeMethodCall(
388 {
"TextInputClient.performAction", std::move(arguments)});
391 invoke_action_message->end(),
392 messages.front().begin()));
398 auto handled_message = CreateResponse(
true);
399 auto unhandled_message = CreateResponse(
false);
400 int received_scancode = 0;
402 TestBinaryMessenger messenger(
403 [&received_scancode, &handled_message, &unhandled_message](
404 const std::string& channel,
const uint8_t*
message,
407 int redispatch_scancode = 0;
410 auto args = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
411 auto& allocator =
args->GetAllocator();
412 args->PushBack(123, allocator);
414 rapidjson::Value client_config(rapidjson::kObjectType);
417 args->PushBack(client_config, allocator);
423 [](
const uint8_t* reply,
size_t reply_size) {}));
425 handler.
KeyboardHook(VK_RETURN, 100, WM_KEYDOWN,
'\n',
false,
false);
428 text.push_back(
'\n');
432 handler.
KeyboardHook(0x4E, 100, WM_KEYDOWN,
'n',
false,
false);
434 std::u16string textN;
437 handler.
KeyboardHook(0x49, 100, WM_KEYDOWN,
'i',
false,
false);
438 std::u16string textNi;
442 handler.
KeyboardHook(VK_RETURN, 100, WM_KEYDOWN,
'\n',
false,
false);
443 std::u16string textChineseCharacter;
444 text.push_back(u
'\u4F60');
456 int selection_base = -1;
457 TestBinaryMessenger messenger([&](
const std::string& channel,
463 const auto&
args = *method->arguments();
464 const auto& editing_state =
args[1];
467 ASSERT_NE(
base, editing_state.MemberEnd());
468 ASSERT_TRUE(
base->value.IsInt());
469 ASSERT_NE(extent, editing_state.MemberEnd());
470 ASSERT_TRUE(extent->value.IsInt());
471 selection_base =
base->value.GetInt();
472 EXPECT_EQ(extent->value.GetInt(), selection_base);
478 auto args = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
479 auto& allocator =
args->GetAllocator();
480 args->PushBack(123, allocator);
481 rapidjson::Value client_config(rapidjson::kObjectType);
482 args->PushBack(client_config, allocator);
487 [](
const uint8_t* reply,
size_t reply_size) {}));
489 plugin.ComposeBeginHook();
490 EXPECT_EQ(selection_base, 0);
491 plugin.ComposeChangeHook(u
"abc", 3);
492 EXPECT_EQ(selection_base, 3);
494 plugin.ComposeCommitHook();
495 plugin.ComposeEndHook();
496 EXPECT_EQ(selection_base, 3);
498 plugin.ComposeBeginHook();
499 plugin.ComposeChangeHook(u
"1", 1);
500 EXPECT_EQ(selection_base, 4);
502 plugin.ComposeChangeHook(u
"12", 2);
503 EXPECT_EQ(selection_base, 5);
505 plugin.ComposeChangeHook(u
"12", 1);
506 EXPECT_EQ(selection_base, 4);
508 plugin.ComposeChangeHook(u
"12", 2);
509 EXPECT_EQ(selection_base, 5);
522 double ime_width = 50;
523 double ime_height = 60;
526 std::array<std::array<double, 4>, 4> editabletext_transform = {
527 1.0, 0.0, 0.0, view_x,
528 0.0, 1.0, 0.0, view_y,
532 TestBinaryMessenger messenger([](
const std::string& channel,
533 const uint8_t*
message,
size_t message_size,
535 BinaryReply reply_handler = [](
const uint8_t* reply,
size_t reply_size) {};
541 EXPECT_CALL(*view(), OnCursorRectUpdated(
Rect{{view_x, view_y}, {0, 0}}));
545 std::make_unique<rapidjson::Document>(rapidjson::kObjectType);
546 auto& allocator = arguments->GetAllocator();
548 rapidjson::Value transoform(rapidjson::kArrayType);
549 for (
int i = 0;
i < 4 * 4;
i++) {
551 transoform.PushBack(editabletext_transform[
i % 4][
i / 4], allocator);
554 arguments->AddMember(
"transform", transoform, allocator);
556 auto message = codec.EncodeMethodCall(
557 {
"TextInput.setEditableSizeAndTransform", std::move(arguments)});
559 message->size(), reply_handler);
563 OnCursorRectUpdated(
Rect{{view_x + ime_x, view_y + ime_y},
564 {ime_width, ime_height}}));
568 std::make_unique<rapidjson::Document>(rapidjson::kObjectType);
569 auto& allocator = arguments->GetAllocator();
571 arguments->AddMember(
"x", ime_x, allocator);
572 arguments->AddMember(
"y", ime_y, allocator);
573 arguments->AddMember(
"width", ime_width, allocator);
574 arguments->AddMember(
"height", ime_height, allocator);
576 auto message = codec.EncodeMethodCall(
577 {
"TextInput.setMarkedTextRect", std::move(arguments)});
579 message->size(), reply_handler);
586 TestBinaryMessenger messenger([](
const std::string& channel,
587 const uint8_t*
message,
size_t message_size,
591 BinaryReply reply_handler = [&reply](
const uint8_t* reply_bytes,
593 reply = std::string(
reinterpret_cast<const char*
>(reply_bytes), reply_size);
601 std::make_unique<rapidjson::Document>(rapidjson::kObjectType);
602 auto& allocator = arguments->GetAllocator();
604 arguments->AddMember(
"x", 0, allocator);
605 arguments->AddMember(
"y", 0, allocator);
606 arguments->AddMember(
"width", 0, allocator);
607 arguments->AddMember(
"height", 0, allocator);
609 auto message = codec.EncodeMethodCall(
610 {
"TextInput.setMarkedTextRect", std::move(arguments)});
612 message->size(), reply_handler);
615 "[\"Internal Consistency Error\",\"Text input is not available in "
616 "Windows headless mode\",null]");
static bool equal(const SkBitmap &a, const SkBitmap &b)
FlutterWindowsView(FlutterViewId view_id, FlutterWindowsEngine *engine, std::unique_ptr< WindowBindingHandler > window_binding, 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
void KeyboardHook(GLFWwindow *window, int key, int scancode, int action, int mods) override
virtual void ComposeCommitHook()
virtual void ComposeChangeHook(const std::u16string &text, int cursor_pos)
virtual void ComposeEndHook()
virtual void ComposeBeginHook()
~MockFlutterWindowsView()
MOCK_METHOD(void, NotifyWinEventWrapper,(ui::AXPlatformNodeWin *, ax::mojom::Event),(override))
MockFlutterWindowsView(FlutterWindowsEngine *engine, std::unique_ptr< WindowBindingHandler > wbh)
Mock for the |Window| base class.
TextInputPluginTest()=default
FlutterWindowsEngine * engine()
MockWindowBindingHandler * window()
virtual ~TextInputPluginTest()=default
MockFlutterWindowsView * view()
WindowsTestContext & GetContext()
G_BEGIN_DECLS G_MODULE_EXPORT FlValue * args
#define FML_DISALLOW_COPY_AND_ASSIGN(TypeName)
static constexpr char kUpdateEditingStateMethod[]
static constexpr char kAffinityDownstream[]
static NSString *const kEnableDeltaModel
static NSString *const kSetClientMethod
static constexpr char kScanCodeKey[]
constexpr char kTextPlainFormat[]
Clipboard plain text format.
static NSString *const kTextKey
static NSString *const kSelectionBaseKey
static NSString *const kSelectionAffinityKey
static NSString *const kComposingExtentKey
static NSString *const kSelectionExtentKey
static NSString *const kComposingBaseKey
static NSString *const kSelectionIsDirectionalKey
TEST_F(DisplayListTest, Defaults)
constexpr int64_t kImplicitViewId
std::function< void(const uint8_t *reply, size_t reply_size)> BinaryReply
it will be possible to load the file into Perfetto s trace viewer disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive keep the shell running after the Dart script has completed enable serial On low power devices with low core running concurrent GC tasks on threads can cause them to contend with the UI thread which could potentially lead to jank This option turns off all concurrent GC activities domain network JSON encoded network policy per domain This overrides the DisallowInsecureConnections switch Embedder can specify whether to allow or disallow insecure connections at a domain level old gen heap size
SkRange< size_t > TextRange
#define EXPECT_TRUE(handle)