[Win32, Keyboard] Fix text submission (flutter/engine#30990)

Fixes text submission not working on win32.
This commit is contained in:
Tong Mu
2022-01-24 12:18:35 -08:00
committed by GitHub
parent 43e0762d68
commit 3538465b2b
4 changed files with 139 additions and 41 deletions

View File

@@ -381,7 +381,7 @@ void FlutterWindowsView::SendKey(int key,
KeyEventCallback callback) {
keyboard_key_handler_->KeyboardHook(
key, scancode, action, character, extended, was_down,
[&, callback = std::move(callback)](bool handled) {
[=, callback = std::move(callback)](bool handled) {
if (!handled) {
text_input_plugin_->KeyboardHook(key, scancode, action, character,
extended, was_down);

View File

@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "flutter/shell/platform/common/json_message_codec.h"
#include "flutter/shell/platform/embedder/embedder.h"
#include "flutter/shell/platform/embedder/test_utils/key_codes.h"
#include "flutter/shell/platform/windows/flutter_windows_engine.h"
@@ -16,6 +17,8 @@
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "rapidjson/stringbuffer.h"
#include "rapidjson/writer.h"
#include <functional>
#include <vector>
@@ -188,6 +191,36 @@ class TestFlutterWindowsView : public FlutterWindowsView {
key_state_.Set(key, pressed, toggled_on);
}
void HandleMessage(const char* channel,
const char* method,
const char* args) {
rapidjson::Document args_doc;
args_doc.Parse(args);
assert(!args_doc.HasParseError());
rapidjson::Document message_doc(rapidjson::kObjectType);
auto& allocator = message_doc.GetAllocator();
message_doc.AddMember("method", rapidjson::Value(method, allocator),
allocator);
message_doc.AddMember("args", args_doc, allocator);
rapidjson::StringBuffer buffer;
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
message_doc.Accept(writer);
std::unique_ptr<std::vector<uint8_t>> data =
JsonMessageCodec::GetInstance().EncodeMessage(message_doc);
FlutterPlatformMessageResponseHandle response_handle;
const FlutterPlatformMessage message = {
sizeof(FlutterPlatformMessage), // struct_size
channel, // channel
data->data(), // message
data->size(), // message_size
&response_handle, // response_handle
};
GetEngine()->HandlePlatformMessage(&message);
}
protected:
std::unique_ptr<KeyboardHandlerBase> CreateKeyboardKeyHandler(
BinaryMessenger* messenger,
@@ -204,14 +237,16 @@ class TestFlutterWindowsView : public FlutterWindowsView {
typedef enum {
kKeyCallOnKey,
kKeyCallOnText,
kKeyCallTextMethodCall,
} KeyCallType;
typedef struct {
KeyCallType type;
// Only one of the following fields should be assigned.
FlutterKeyEvent key_event;
std::u16string text;
FlutterKeyEvent key_event; // For kKeyCallOnKey
std::u16string text; // For kKeyCallOnText
std::string text_method_call; // For kKeyCallTextMethodCall
} KeyCall;
static std::vector<KeyCall> key_calls;
@@ -256,6 +291,8 @@ class KeyboardTester {
window_ = std::make_unique<MockKeyboardManagerWin32Delegate>(view_.get());
}
TestFlutterWindowsView& GetView() { return *view_; }
void SetKeyState(uint32_t key, bool pressed, bool toggled_on) {
view_->SetKeyState(key, pressed, toggled_on);
}
@@ -322,6 +359,16 @@ class KeyboardTester {
std::make_shared<MockKeyResponseController>();
key_response_controller->SetEmbedderResponse(
std::move(embedder_callback_handler));
key_response_controller->SetTextInputResponse(
[](std::unique_ptr<rapidjson::Document> document) {
rapidjson::StringBuffer buffer;
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
document->Accept(writer);
key_calls.push_back(KeyCall{
.type = kKeyCallTextMethodCall,
.text_method_call = buffer.GetString(),
});
});
MockEmbedderApiForKeyboard(modifier, key_response_controller);
@@ -355,6 +402,7 @@ constexpr uint64_t kScanCodeShiftLeft = 0x2a;
constexpr uint64_t kScanCodeShiftRight = 0x36;
constexpr uint64_t kScanCodeBracketLeft = 0x1a;
constexpr uint64_t kScanCodeArrowLeft = 0x4b;
constexpr uint64_t kScanCodeEnter = 0x1c;
constexpr uint64_t kVirtualDigit1 = 0x31;
constexpr uint64_t kVirtualKeyA = 0x41;
@@ -379,6 +427,10 @@ constexpr bool kNotSynthesized = false;
EXPECT_EQ(_key_call.type, kKeyCallOnText); \
EXPECT_EQ(_key_call.text, u16_string);
#define EXPECT_CALL_IS_TEXT_METHOD_CALL(_key_call, json_string) \
EXPECT_EQ(_key_call.type, kKeyCallTextMethodCall); \
EXPECT_STREQ(_key_call.text_method_call.c_str(), json_string);
TEST(KeyboardTest, LowerCaseAHandled) {
KeyboardTester tester;
tester.Responding(true);
@@ -1749,5 +1801,32 @@ TEST(KeyboardTest, SlowFrameworkResponse) {
clear_key_calls();
}
TEST(KeyboardTest, TextInputSubmit) {
KeyboardTester tester;
tester.Responding(false);
// US Keyboard layout
tester.GetView().HandleMessage(
"flutter/textinput", "TextInput.setClient",
R"|([108, {"inputAction": "TextInputAction.none"}])|");
// Press Enter
tester.InjectMessages(
1, WmKeyDownInfo{VK_RETURN, kScanCodeEnter, kNotExtended, kWasUp}.Build(
kWmResultZero));
EXPECT_EQ(key_calls.size(), 2);
EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, kPhysicalEnter,
kLogicalEnter, "", kNotSynthesized);
EXPECT_CALL_IS_TEXT_METHOD_CALL(
key_calls[1],
"{"
R"|("method":"TextInputClient.performAction",)|"
R"|("args":[108,"TextInputAction.none"])|"
"}");
clear_key_calls();
}
} // namespace testing
} // namespace flutter

View File

@@ -50,15 +50,6 @@ static std::string ordinal(int num) {
return "th";
}
}
// A struct to use as a FlutterPlatformMessageResponseHandle so it can keep the
// callbacks and user data passed to the engine's
// PlatformMessageCreateResponseHandle for use in the SendPlatformMessage
// overridden function.
struct TestResponseHandle {
FlutterDesktopBinaryReply callback;
void* user_data;
};
} // namespace
#define _RETURN_IF_NOT_EQUALS(val1, val2) \
@@ -111,28 +102,35 @@ void MockEmbedderApiForKeyboard(
std::shared_ptr<MockKeyResponseController> response_controller) {
stored_response_controller = response_controller;
// This mock handles channel messages.
modifier.embedder_api().SendPlatformMessage =
[](FLUTTER_API_SYMBOL(FlutterEngine) engine,
const FlutterPlatformMessage* message) {
if (std::string(message->channel) == std::string("flutter/settings")) {
return kSuccess;
modifier.embedder_api()
.SendPlatformMessage = [](FLUTTER_API_SYMBOL(FlutterEngine) engine,
const FlutterPlatformMessage* message) {
if (std::string(message->channel) == std::string("flutter/settings")) {
return kSuccess;
}
if (std::string(message->channel) == std::string("flutter/keyevent")) {
stored_response_controller->HandleChannelMessage([message](bool handled) {
auto response = _keyHandlingResponse(handled);
auto response_handle = message->response_handle;
if (response_handle->callback != nullptr) {
response_handle->callback(response->data(), response->size(),
response_handle->user_data);
}
if (std::string(message->channel) == std::string("flutter/keyevent")) {
stored_response_controller->HandleChannelMessage(
[message](bool handled) {
auto response = _keyHandlingResponse(handled);
const TestResponseHandle* response_handle =
reinterpret_cast<const TestResponseHandle*>(
message->response_handle);
if (response_handle->callback != nullptr) {
response_handle->callback(response->data(), response->size(),
response_handle->user_data);
}
});
return kSuccess;
}
return kSuccess;
};
});
return kSuccess;
}
if (std::string(message->channel) == std::string("flutter/textinput")) {
std::unique_ptr<rapidjson::Document> document =
flutter::JsonMessageCodec::GetInstance().DecodeMessage(
message->message, message->message_size);
if (document == nullptr) {
return kInvalidArguments;
}
stored_response_controller->HandleTextInputMessage(std::move(document));
return kSuccess;
}
return kSuccess;
};
// This mock handles key events sent through the embedder API.
modifier.embedder_api().SendKeyEvent =
@@ -150,23 +148,27 @@ void MockEmbedderApiForKeyboard(
// The following mocks enable channel mocking.
modifier.embedder_api().PlatformMessageCreateResponseHandle =
[](auto engine, auto data_callback, auto user_data, auto response_out) {
TestResponseHandle* response_handle = new TestResponseHandle();
auto response_handle = new FlutterPlatformMessageResponseHandle();
response_handle->user_data = user_data;
response_handle->callback = data_callback;
*response_out = reinterpret_cast<FlutterPlatformMessageResponseHandle*>(
response_handle);
*response_out = response_handle;
return kSuccess;
};
modifier.embedder_api().PlatformMessageReleaseResponseHandle =
[](FLUTTER_API_SYMBOL(FlutterEngine) engine,
FlutterPlatformMessageResponseHandle* response) {
const TestResponseHandle* response_handle =
reinterpret_cast<const TestResponseHandle*>(response);
delete response_handle;
delete response;
return kSuccess;
};
// The following mock disables responses for method channels sent from the
// embedding to the framework. (No code uses the response yet.)
modifier.embedder_api().SendPlatformMessageResponse =
[](FLUTTER_API_SYMBOL(FlutterEngine) engine,
const FlutterPlatformMessageResponseHandle* handle,
const uint8_t* data, size_t data_length) { return kSuccess; };
// The following mocks allows RunWithEntrypoint to be run, which creates a
// non-empty FlutterEngine and enables SendKeyEvent.

View File

@@ -16,9 +16,13 @@
#include "gtest/gtest.h"
struct _FlutterPlatformMessageResponseHandle {
FlutterDesktopBinaryReply callback;
void* user_data;
};
namespace flutter {
namespace testing {
::testing::AssertionResult _EventEquals(const char* expr_event,
const char* expr_expected,
const FlutterKeyEvent& event,
@@ -48,10 +52,14 @@ class MockKeyResponseController {
using EmbedderCallbackHandler =
std::function<void(const FlutterKeyEvent*, ResponseCallback)>;
using ChannelCallbackHandler = std::function<void(ResponseCallback)>;
using TextInputCallbackHandler =
std::function<void(std::unique_ptr<rapidjson::Document>)>;
MockKeyResponseController()
: channel_response_(ChannelRespondFalse),
embedder_response_(EmbedderRespondFalse) {}
embedder_response_(EmbedderRespondFalse),
text_input_response_(
[](std::unique_ptr<rapidjson::Document> document) {}) {}
void SetChannelResponse(ChannelCallbackHandler handler) {
channel_response_ = std::move(handler);
@@ -61,6 +69,10 @@ class MockKeyResponseController {
embedder_response_ = std::move(handler);
}
void SetTextInputResponse(TextInputCallbackHandler handler) {
text_input_response_ = std::move(handler);
}
void HandleChannelMessage(ResponseCallback callback) {
channel_response_(callback);
}
@@ -70,9 +82,14 @@ class MockKeyResponseController {
embedder_response_(event, std::move(callback));
}
void HandleTextInputMessage(std::unique_ptr<rapidjson::Document> document) {
text_input_response_(std::move(document));
}
private:
EmbedderCallbackHandler embedder_response_;
ChannelCallbackHandler channel_response_;
TextInputCallbackHandler text_input_response_;
static void ChannelRespondFalse(ResponseCallback callback) {
callback(false);