blob: 4d2109a657108f5d6ec01c9727cfbbf18bdb66e3 [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "flutter/shell/platform/windows/testing/test_keyboard.h"
#include <rapidjson/document.h>
#include "flutter/fml/logging.h"
#include "flutter/shell/platform/common/json_message_codec.h"
#include "flutter/shell/platform/embedder/test_utils/proc_table_replacement.h"
namespace flutter {
namespace testing {
char* clone_string(const char* string) {
if (string == nullptr) {
return nullptr;
}
size_t len = strlen(string);
char* result = new char[len + 1];
strcpy(result, string);
return result;
}
namespace {
std::string _print_character(const char* s) {
if (s == nullptr) {
return "nullptr";
}
return std::string("\"") + s + "\"";
}
std::unique_ptr<std::vector<uint8_t>> _keyHandlingResponse(bool handled) {
rapidjson::Document document;
auto& allocator = document.GetAllocator();
document.SetObject();
document.AddMember("handled", handled, allocator);
return flutter::JsonMessageCodec::GetInstance().EncodeMessage(document);
}
static std::string ordinal(int num) {
switch (num) {
case 1:
return "st";
case 2:
return "nd";
case 3:
return "rd";
default:
return "th";
}
}
} // namespace
#define _RETURN_IF_NOT_EQUALS(val1, val2) \
if ((val1) != (val2)) { \
return ::testing::AssertionFailure() \
<< "Expected equality of these values:\n " #val1 "\n " << val2 \
<< "\n Actual: \n " << val1; \
}
::testing::AssertionResult _EventEquals(const char* expr_event,
const char* expr_expected,
const FlutterKeyEvent& event,
const FlutterKeyEvent& expected) {
_RETURN_IF_NOT_EQUALS(event.struct_size, sizeof(FlutterKeyEvent));
_RETURN_IF_NOT_EQUALS(event.type, expected.type);
_RETURN_IF_NOT_EQUALS(event.physical, expected.physical);
_RETURN_IF_NOT_EQUALS(event.logical, expected.logical);
if ((event.character == nullptr) != (expected.character == nullptr) ||
strcmp(event.character, expected.character) != 0) {
return ::testing::AssertionFailure()
<< "Expected equality of these values:\n expected.character\n "
<< _print_character(expected.character) << "\n Actual: \n "
<< _print_character(event.character);
}
_RETURN_IF_NOT_EQUALS(event.synthesized, expected.synthesized);
return ::testing::AssertionSuccess();
}
LPARAM CreateKeyEventLparam(USHORT scancode,
bool extended,
bool was_down,
USHORT repeat_count,
bool context_code,
bool transition_state) {
return ((LPARAM(transition_state) << 31) | (LPARAM(was_down) << 30) |
(LPARAM(context_code) << 29) | (LPARAM(extended ? 0x1 : 0x0) << 24) |
(LPARAM(scancode) << 16) | LPARAM(repeat_count));
}
static std::shared_ptr<MockKeyResponseController> stored_response_controller;
// Set EngineModifier, listen to event messages that go through the channel and
// the embedder API, while disabling other methods so that the engine can be
// run headlessly.
//
// The |channel_handler| and |embedder_handler| should return a boolean
// indicating whether the framework decides to handle the event.
void MockEmbedderApiForKeyboard(
EngineModifier& modifier,
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;
}
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);
}
});
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 =
[](FLUTTER_API_SYMBOL(FlutterEngine) engine, const FlutterKeyEvent* event,
FlutterKeyEventCallback callback, void* user_data) {
stored_response_controller->HandleEmbedderMessage(
event, [callback, user_data](bool handled) {
if (callback != nullptr) {
callback(handled, user_data);
}
});
return kSuccess;
};
// The following mocks enable channel mocking.
modifier.embedder_api().PlatformMessageCreateResponseHandle =
[](auto engine, auto data_callback, auto user_data, auto response_out) {
auto response_handle = new FlutterPlatformMessageResponseHandle();
response_handle->user_data = user_data;
response_handle->callback = data_callback;
*response_out = response_handle;
return kSuccess;
};
modifier.embedder_api().PlatformMessageReleaseResponseHandle =
[](FLUTTER_API_SYMBOL(FlutterEngine) engine,
FlutterPlatformMessageResponseHandle* response) {
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.
modifier.embedder_api().Run =
[](size_t version, const FlutterRendererConfig* config,
const FlutterProjectArgs* args, void* user_data,
FLUTTER_API_SYMBOL(FlutterEngine) * engine_out) {
*engine_out = reinterpret_cast<FLUTTER_API_SYMBOL(FlutterEngine)>(1);
return kSuccess;
};
// Any time the associated EmbedderEngine will be mocked, such as here,
// the Update accessibility features must not attempt to actually push the
// update
modifier.embedder_api().UpdateAccessibilityFeatures =
[](FLUTTER_API_SYMBOL(FlutterEngine) engine,
FlutterAccessibilityFeature flags) { return kSuccess; };
modifier.embedder_api().UpdateLocales =
[](auto engine, const FlutterLocale** locales, size_t locales_count) {
return kSuccess;
};
modifier.embedder_api().SendWindowMetricsEvent =
[](auto engine, const FlutterWindowMetricsEvent* event) {
return kSuccess;
};
modifier.embedder_api().Shutdown = [](auto engine) { return kSuccess; };
modifier.embedder_api().NotifyDisplayUpdate =
[](auto engine, const FlutterEngineDisplaysUpdateType update_type,
const FlutterEngineDisplay* embedder_displays,
size_t display_count) { return kSuccess; };
}
void MockMessageQueue::PushBack(const Win32Message* message) {
_pending_messages.push_back(*message);
}
LRESULT MockMessageQueue::DispatchFront() {
FML_DCHECK(!_pending_messages.empty())
<< "Called DispatchFront while pending message queue is empty";
Win32Message message = _pending_messages.front();
_pending_messages.pop_front();
_sent_messages.push_back(message);
LRESULT result =
Win32SendMessage(message.message, message.wParam, message.lParam);
if (message.expected_result != kWmResultDontCheck) {
EXPECT_EQ(result, message.expected_result)
<< " This is the " << _sent_messages.size()
<< ordinal(_sent_messages.size()) << " event, with\n " << std::hex
<< "Message 0x" << message.message << " LParam 0x" << message.lParam
<< " WParam 0x" << message.wParam;
}
return result;
}
BOOL MockMessageQueue::Win32PeekMessage(LPMSG lpMsg,
UINT wMsgFilterMin,
UINT wMsgFilterMax,
UINT wRemoveMsg) {
for (auto iter = _pending_messages.begin(); iter != _pending_messages.end();
++iter) {
if (iter->message >= wMsgFilterMin && iter->message <= wMsgFilterMax) {
*lpMsg = MSG{
.message = iter->message,
.wParam = iter->wParam,
.lParam = iter->lParam,
};
if ((wRemoveMsg & PM_REMOVE) == PM_REMOVE) {
_pending_messages.erase(iter);
}
return TRUE;
}
}
return FALSE;
}
} // namespace testing
} // namespace flutter