blob: 0131d5f3d0cc26cc0f2abd3d2730aa630f33217b [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/common/json_message_codec.h"
#include "flutter/shell/platform/embedder/embedder.h"
#include "flutter/shell/platform/embedder/test_utils/proc_table_replacement.h"
#include "flutter/shell/platform/windows/flutter_windows_engine.h"
#include "flutter/shell/platform/windows/testing/engine_embedder_api_modifier.h"
#include "flutter/shell/platform/windows/testing/mock_window_binding_handler.h"
#include "flutter/shell/platform/windows/testing/win32_flutter_window_test.h"
#include "flutter/shell/platform/windows/text_input_plugin_delegate.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include <rapidjson/document.h>
using testing::_;
using testing::Invoke;
namespace flutter {
namespace testing {
namespace {
// Creates a valid Windows LPARAM for WM_KEYDOWN and WM_CHAR from parameters
// given.
static LPARAM CreateKeyEventLparam(USHORT ScanCode,
bool extended = false,
USHORT RepeatCount = 1,
bool ContextCode = 0,
bool PreviousKeyState = 1,
bool TransitionState = 1) {
return ((LPARAM(TransitionState) << 31) | (LPARAM(PreviousKeyState) << 30) |
(LPARAM(ContextCode) << 29) | (LPARAM(extended ? 0x1 : 0x0) << 24) |
(LPARAM(ScanCode) << 16) | LPARAM(RepeatCount));
}
// A struc to hold simulated events that will be delivered after the framework
// response is handled.
struct SimulatedEvent {
UINT message;
WPARAM wparam;
LPARAM lparam;
};
// A key event handler that can be spied on while it forwards calls to the real
// key event handler.
class SpyKeyEventHandler : public KeyboardHookHandler {
public:
SpyKeyEventHandler(flutter::BinaryMessenger* messenger,
KeyEventHandler::SendInputDelegate delegate) {
real_implementation_ =
std::make_unique<KeyEventHandler>(messenger, delegate);
ON_CALL(*this, KeyboardHook(_, _, _, _, _, _))
.WillByDefault(
Invoke(real_implementation_.get(), &KeyEventHandler::KeyboardHook));
ON_CALL(*this, TextHook(_, _))
.WillByDefault(
Invoke(real_implementation_.get(), &KeyEventHandler::TextHook));
}
MOCK_METHOD6(KeyboardHook,
bool(FlutterWindowsView* window,
int key,
int scancode,
int action,
char32_t character,
bool extended));
MOCK_METHOD2(TextHook,
void(FlutterWindowsView* window, const std::u16string& text));
MOCK_METHOD0(ComposeBeginHook, void());
MOCK_METHOD0(ComposeEndHook, void());
MOCK_METHOD2(ComposeChangeHook,
void(const std::u16string& text, int cursor_pos));
private:
std::unique_ptr<KeyEventHandler> real_implementation_;
};
// A text input plugin that can be spied on while it forwards calls to the real
// text input plugin.
class SpyTextInputPlugin : public KeyboardHookHandler,
public TextInputPluginDelegate {
public:
SpyTextInputPlugin(flutter::BinaryMessenger* messenger) {
real_implementation_ = std::make_unique<TextInputPlugin>(messenger, this);
ON_CALL(*this, KeyboardHook(_, _, _, _, _, _))
.WillByDefault(
Invoke(real_implementation_.get(), &TextInputPlugin::KeyboardHook));
ON_CALL(*this, TextHook(_, _))
.WillByDefault(
Invoke(real_implementation_.get(), &TextInputPlugin::TextHook));
}
MOCK_METHOD6(KeyboardHook,
bool(FlutterWindowsView* window,
int key,
int scancode,
int action,
char32_t character,
bool extended));
MOCK_METHOD2(TextHook,
void(FlutterWindowsView* window, const std::u16string& text));
MOCK_METHOD0(ComposeBeginHook, void());
MOCK_METHOD0(ComposeEndHook, void());
MOCK_METHOD2(ComposeChangeHook,
void(const std::u16string& text, int cursor_pos));
virtual void OnCursorRectUpdated(const Rect& rect) {}
private:
std::unique_ptr<TextInputPlugin> real_implementation_;
};
class MockWin32FlutterWindow : public Win32FlutterWindow {
public:
MockWin32FlutterWindow() : Win32FlutterWindow(800, 600) {}
virtual ~MockWin32FlutterWindow() {}
// Prevent copying.
MockWin32FlutterWindow(MockWin32FlutterWindow const&) = delete;
MockWin32FlutterWindow& operator=(MockWin32FlutterWindow const&) = delete;
// Wrapper for GetCurrentDPI() which is a protected method.
UINT GetDpi() { return GetCurrentDPI(); }
// Simulates a WindowProc message from the OS.
LRESULT InjectWindowMessage(UINT const message,
WPARAM const wparam,
LPARAM const lparam) {
return HandleMessage(message, wparam, lparam);
}
MOCK_METHOD1(OnDpiScale, void(unsigned int));
MOCK_METHOD2(OnResize, void(unsigned int, unsigned int));
MOCK_METHOD2(OnPointerMove, void(double, double));
MOCK_METHOD3(OnPointerDown, void(double, double, UINT));
MOCK_METHOD3(OnPointerUp, void(double, double, UINT));
MOCK_METHOD0(OnPointerLeave, void());
MOCK_METHOD0(OnSetCursor, void());
MOCK_METHOD2(OnScroll, void(double, double));
};
// A FlutterWindowsView that overrides the RegisterKeyboardHookHandlers function
// to register the keyboard hook handlers that can be spied upon.
class TestFlutterWindowsView : public FlutterWindowsView {
public:
TestFlutterWindowsView(std::unique_ptr<WindowBindingHandler> window_binding,
WPARAM virtual_key,
bool is_printable = true)
: FlutterWindowsView(std::move(window_binding)),
virtual_key_(virtual_key),
is_printable_(is_printable) {}
SpyKeyEventHandler* key_event_handler;
SpyTextInputPlugin* text_input_plugin;
void InjectPendingEvents(MockWin32FlutterWindow* win32window) {
while (pending_events_.size() > 0) {
SimulatedEvent event = pending_events_.front();
win32window->InjectWindowMessage(event.message, event.wparam,
event.lparam);
pending_events_.pop_front();
}
}
protected:
void RegisterKeyboardHookHandlers(
flutter::BinaryMessenger* messenger) override {
auto spy_key_event_handler = std::make_unique<SpyKeyEventHandler>(
messenger, [this](UINT cInputs, LPINPUT pInputs, int cbSize) -> UINT {
return this->SendInput(cInputs, pInputs, cbSize);
});
auto spy_text_input_plugin =
std::make_unique<SpyTextInputPlugin>(messenger);
key_event_handler = spy_key_event_handler.get();
text_input_plugin = spy_text_input_plugin.get();
AddKeyboardHookHandler(std::move(spy_key_event_handler));
AddKeyboardHookHandler(std::move(spy_text_input_plugin));
}
private:
UINT SendInput(UINT cInputs, LPINPUT pInputs, int cbSize) {
// Simulate the event loop by just sending the event sent to
// "SendInput" directly to the window.
const KEYBDINPUT kbdinput = pInputs->ki;
const UINT message =
(kbdinput.dwFlags & KEYEVENTF_KEYUP) ? WM_KEYUP : WM_KEYDOWN;
const LPARAM lparam = CreateKeyEventLparam(
kbdinput.wScan, kbdinput.dwFlags & KEYEVENTF_EXTENDEDKEY);
// Windows would normally fill in the virtual key code for us, so we
// simulate it for the test with the key we know is in the test. The
// KBDINPUT we're passed doesn't have it filled in (on purpose, so that
// Windows will fill it in).
pending_events_.push_back(SimulatedEvent{message, virtual_key_, lparam});
if (is_printable_ && (kbdinput.dwFlags & KEYEVENTF_KEYUP) == 0) {
pending_events_.push_back(SimulatedEvent{WM_CHAR, virtual_key_, lparam});
}
return 1;
}
std::deque<SimulatedEvent> pending_events_;
WPARAM virtual_key_;
bool is_printable_;
};
// 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;
};
// The static value to return as the "handled" value from the framework for key
// events. Individual tests set this to change the framework response that the
// test engine simulates.
static bool test_response = false;
// Returns an engine instance configured with dummy project path values, and
// overridden methods for sending platform messages, so that the engine can
// respond as if the framework were connected.
std::unique_ptr<FlutterWindowsEngine> GetTestEngine() {
FlutterDesktopEngineProperties properties = {};
properties.assets_path = L"C:\\foo\\flutter_assets";
properties.icu_data_path = L"C:\\foo\\icudtl.dat";
properties.aot_library_path = L"C:\\foo\\aot.so";
FlutterProjectBundle project(properties);
auto engine = std::make_unique<FlutterWindowsEngine>(project);
EngineEmbedderApiModifier modifier(engine.get());
// Force the non-AOT path unless overridden by the test.
modifier.embedder_api().RunsAOTCompiledDartCode = []() { return false; };
modifier.embedder_api().PlatformMessageCreateResponseHandle =
[](auto engine, auto data_callback, auto user_data, auto response_out) {
TestResponseHandle* response_handle = new TestResponseHandle();
response_handle->user_data = user_data;
response_handle->callback = data_callback;
*response_out = reinterpret_cast<FlutterPlatformMessageResponseHandle*>(
response_handle);
return kSuccess;
};
modifier.embedder_api().SendPlatformMessage =
[](FLUTTER_API_SYMBOL(FlutterEngine) engine,
const FlutterPlatformMessage* message) {
rapidjson::Document document;
auto& allocator = document.GetAllocator();
document.SetObject();
document.AddMember("handled", test_response, allocator);
auto encoded =
flutter::JsonMessageCodec::GetInstance().EncodeMessage(document);
const TestResponseHandle* response_handle =
reinterpret_cast<const TestResponseHandle*>(
message->response_handle);
if (response_handle->callback != nullptr) {
response_handle->callback(encoded->data(), encoded->size(),
response_handle->user_data);
}
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;
return kSuccess;
};
return engine;
}
} // namespace
TEST(Win32FlutterWindowTest, CreateDestroy) {
Win32FlutterWindowTest window(800, 600);
ASSERT_TRUE(TRUE);
}
// Tests key event propagation of non-printable, non-modifier key down events.
TEST(Win32FlutterWindowTest, NonPrintableKeyDownPropagation) {
constexpr WPARAM virtual_key = VK_LEFT;
constexpr WPARAM scan_code = 10;
constexpr char32_t character = 0;
MockWin32FlutterWindow win32window;
std::deque<SimulatedEvent> pending_events;
auto window_binding_handler =
std::make_unique<::testing::NiceMock<MockWindowBindingHandler>>();
TestFlutterWindowsView flutter_windows_view(
std::move(window_binding_handler), virtual_key, false /* is_printable */);
win32window.SetView(&flutter_windows_view);
LPARAM lparam = CreateKeyEventLparam(scan_code);
// Test an event not handled by the framework
{
test_response = false;
flutter_windows_view.SetEngine(std::move(GetTestEngine()));
EXPECT_CALL(*flutter_windows_view.key_event_handler,
KeyboardHook(_, virtual_key, scan_code, WM_KEYDOWN, character,
false /* extended */))
.Times(2)
.RetiresOnSaturation();
EXPECT_CALL(*flutter_windows_view.text_input_plugin,
KeyboardHook(_, _, _, _, _, _))
.Times(1)
.RetiresOnSaturation();
EXPECT_CALL(*flutter_windows_view.key_event_handler, TextHook(_, _))
.Times(0);
EXPECT_CALL(*flutter_windows_view.text_input_plugin, TextHook(_, _))
.Times(0);
EXPECT_EQ(win32window.InjectWindowMessage(WM_KEYDOWN, virtual_key, lparam),
0);
flutter_windows_view.InjectPendingEvents(&win32window);
}
// Test an event handled by the framework
{
test_response = true;
EXPECT_CALL(*flutter_windows_view.key_event_handler,
KeyboardHook(_, virtual_key, scan_code, WM_KEYDOWN, character,
false /* extended */))
.Times(1)
.RetiresOnSaturation();
EXPECT_CALL(*flutter_windows_view.text_input_plugin,
KeyboardHook(_, _, _, _, _, _))
.Times(0);
EXPECT_EQ(win32window.InjectWindowMessage(WM_KEYDOWN, virtual_key, lparam),
0);
flutter_windows_view.InjectPendingEvents(&win32window);
}
}
// Tests key event propagation of printable character key down events. These
// differ from non-printable characters in that they follow a different code
// path in the WndProc (HandleMessage), producing a follow-on WM_CHAR event.
TEST(Win32FlutterWindowTest, CharKeyDownPropagation) {
constexpr WPARAM virtual_key = 65; // The "A" key, which produces a character
constexpr WPARAM scan_code = 30;
constexpr char32_t character = 65;
MockWin32FlutterWindow win32window;
auto window_binding_handler =
std::make_unique<::testing::NiceMock<MockWindowBindingHandler>>();
TestFlutterWindowsView flutter_windows_view(
std::move(window_binding_handler), virtual_key, true /* is_printable */);
win32window.SetView(&flutter_windows_view);
LPARAM lparam = CreateKeyEventLparam(scan_code);
// Test an event not handled by the framework
{
test_response = false;
flutter_windows_view.SetEngine(std::move(GetTestEngine()));
EXPECT_CALL(
*flutter_windows_view.key_event_handler,
KeyboardHook(_, virtual_key, scan_code, WM_KEYDOWN, character, false))
.Times(2)
.RetiresOnSaturation();
EXPECT_CALL(*flutter_windows_view.text_input_plugin,
KeyboardHook(_, _, _, _, _, _))
.Times(1)
.RetiresOnSaturation();
EXPECT_CALL(*flutter_windows_view.key_event_handler, TextHook(_, _))
.Times(1)
.RetiresOnSaturation();
EXPECT_CALL(*flutter_windows_view.text_input_plugin, TextHook(_, _))
.Times(1)
.RetiresOnSaturation();
EXPECT_EQ(win32window.InjectWindowMessage(WM_KEYDOWN, virtual_key, lparam),
0);
EXPECT_EQ(win32window.InjectWindowMessage(WM_CHAR, virtual_key, lparam), 0);
flutter_windows_view.InjectPendingEvents(&win32window);
}
// Test an event handled by the framework
{
test_response = true;
EXPECT_CALL(*flutter_windows_view.key_event_handler,
KeyboardHook(_, virtual_key, scan_code, WM_KEYDOWN, character,
false /* is_printable */))
.Times(1)
.RetiresOnSaturation();
EXPECT_CALL(*flutter_windows_view.text_input_plugin,
KeyboardHook(_, _, _, _, _, _))
.Times(0);
EXPECT_CALL(*flutter_windows_view.key_event_handler, TextHook(_, _))
.Times(0);
EXPECT_CALL(*flutter_windows_view.text_input_plugin, TextHook(_, _))
.Times(0);
EXPECT_EQ(win32window.InjectWindowMessage(WM_KEYDOWN, virtual_key, lparam),
0);
EXPECT_EQ(win32window.InjectWindowMessage(WM_CHAR, virtual_key, lparam), 0);
flutter_windows_view.InjectPendingEvents(&win32window);
}
}
// Tests key event propagation of modifier key down events. This are different
// from non-printable events in that they call MapVirtualKey, resulting in a
// slightly different code path.
TEST(Win32FlutterWindowTest, ModifierKeyDownPropagation) {
constexpr WPARAM virtual_key = VK_LSHIFT;
constexpr WPARAM scan_code = 20;
constexpr char32_t character = 0;
MockWin32FlutterWindow win32window;
std::deque<SimulatedEvent> pending_events;
auto window_binding_handler =
std::make_unique<::testing::NiceMock<MockWindowBindingHandler>>();
TestFlutterWindowsView flutter_windows_view(
std::move(window_binding_handler), virtual_key, false /* is_printable */);
win32window.SetView(&flutter_windows_view);
LPARAM lparam = CreateKeyEventLparam(scan_code);
// Test an event not handled by the framework
{
test_response = false;
flutter_windows_view.SetEngine(std::move(GetTestEngine()));
EXPECT_CALL(*flutter_windows_view.key_event_handler,
KeyboardHook(_, virtual_key, scan_code, WM_KEYDOWN, character,
false /* extended */))
.Times(2)
.RetiresOnSaturation();
EXPECT_CALL(*flutter_windows_view.text_input_plugin,
KeyboardHook(_, _, _, _, _, _))
.Times(1)
.RetiresOnSaturation();
EXPECT_CALL(*flutter_windows_view.key_event_handler, TextHook(_, _))
.Times(0);
EXPECT_CALL(*flutter_windows_view.text_input_plugin, TextHook(_, _))
.Times(0);
EXPECT_EQ(win32window.InjectWindowMessage(WM_KEYDOWN, virtual_key, lparam),
0);
flutter_windows_view.InjectPendingEvents(&win32window);
}
// Test an event handled by the framework
{
test_response = true;
EXPECT_CALL(*flutter_windows_view.key_event_handler,
KeyboardHook(_, virtual_key, scan_code, WM_KEYDOWN, character,
false /* extended */))
.Times(1)
.RetiresOnSaturation();
EXPECT_CALL(*flutter_windows_view.text_input_plugin,
KeyboardHook(_, _, _, _, _, _))
.Times(0);
EXPECT_EQ(win32window.InjectWindowMessage(WM_KEYDOWN, virtual_key, lparam),
0);
flutter_windows_view.InjectPendingEvents(&win32window);
}
}
} // namespace testing
} // namespace flutter