[Win32, Keyboard] Fix text submission (flutter/engine#30990)
Fixes text submission not working on win32.
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user