blob: d42294f4cb453d30c525aa4e39bbaf71c5a2033c [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/linux/fl_keyboard_manager.h"
#include <cstring>
#include "flutter/shell/platform/linux/testing/mock_text_input_plugin.h"
#include "gtest/gtest.h"
#define FL_KEY_EVENT(target) reinterpret_cast<FlKeyEvent*>(target)
namespace {
typedef void (*CallbackHandler)(FlKeyResponderAsyncCallback callback,
gpointer user_data);
constexpr guint16 kKeyCodeKeyA = 0x26u;
constexpr guint16 kKeyCodeKeyB = 0x38u;
G_DECLARE_FINAL_TYPE(FlKeyboardCallRecord,
fl_keyboard_call_record,
FL,
KEYBOARD_CALL_RECORD,
GObject);
typedef struct _FlKeyMockResponder FlKeyMockResponder;
struct _FlKeyboardCallRecord {
GObject parent_instance;
FlKeyMockResponder* responder;
FlKeyEvent* event;
FlKeyResponderAsyncCallback callback;
gpointer user_data;
};
#define FL_TYPE_KEY_MOCK_RESPONDER fl_key_mock_responder_get_type()
G_DECLARE_FINAL_TYPE(FlKeyMockResponder,
fl_key_mock_responder,
FL,
KEY_MOCK_RESPONDER,
GObject);
struct _FlKeyMockResponder {
GObject parent_instance;
// A weak pointer for a list of FlKeyboardCallRecord.
GPtrArray* call_records;
CallbackHandler callback_handler;
int delegate_id;
};
G_DEFINE_TYPE(FlKeyboardCallRecord, fl_keyboard_call_record, G_TYPE_OBJECT)
static void fl_keyboard_call_record_init(FlKeyboardCallRecord* self) {}
// Dispose method for FlKeyboardCallRecord.
static void fl_keyboard_call_record_dispose(GObject* object) {
g_return_if_fail(FL_IS_KEYBOARD_CALL_RECORD(object));
FlKeyboardCallRecord* self = FL_KEYBOARD_CALL_RECORD(object);
fl_key_event_dispose(self->event);
G_OBJECT_CLASS(fl_keyboard_call_record_parent_class)->dispose(object);
}
// Class Initialization method for FlKeyboardCallRecord class.
static void fl_keyboard_call_record_class_init(
FlKeyboardCallRecordClass* klass) {
G_OBJECT_CLASS(klass)->dispose = fl_keyboard_call_record_dispose;
}
static FlKeyboardCallRecord* fl_keyboard_call_record_new(
FlKeyMockResponder* responder,
FlKeyEvent* event,
FlKeyResponderAsyncCallback callback,
gpointer user_data) {
g_return_val_if_fail(FL_IS_KEY_MOCK_RESPONDER(responder), nullptr);
g_return_val_if_fail(event != nullptr, nullptr);
g_return_val_if_fail(callback != nullptr, nullptr);
g_return_val_if_fail(user_data != nullptr, nullptr);
FlKeyboardCallRecord* self = FL_KEYBOARD_CALL_RECORD(
g_object_new(fl_keyboard_call_record_get_type(), nullptr));
self->responder = responder;
self->event = event;
self->callback = callback;
self->user_data = user_data;
return self;
}
static void dont_respond(FlKeyResponderAsyncCallback callback,
gpointer user_data) {}
static void respond_true(FlKeyResponderAsyncCallback callback,
gpointer user_data) {
callback(true, user_data);
}
static void respond_false(FlKeyResponderAsyncCallback callback,
gpointer user_data) {
callback(false, user_data);
}
static gboolean filter_keypress_returns_true(FlTextInputPlugin* self,
FlKeyEvent* event) {
return TRUE;
}
static gboolean filter_keypress_returns_false(FlTextInputPlugin* self,
FlKeyEvent* event) {
return FALSE;
}
static void fl_key_mock_responder_iface_init(FlKeyResponderInterface* iface);
G_DEFINE_TYPE_WITH_CODE(FlKeyMockResponder,
fl_key_mock_responder,
G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE(FL_TYPE_KEY_RESPONDER,
fl_key_mock_responder_iface_init))
static void fl_key_mock_responder_handle_event(
FlKeyResponder* responder,
FlKeyEvent* event,
FlKeyResponderAsyncCallback callback,
gpointer user_data);
static void fl_key_mock_responder_iface_init(FlKeyResponderInterface* iface) {
iface->handle_event = fl_key_mock_responder_handle_event;
}
// Return a newly allocated #FlKeyEvent that is a clone to the given #event
// but with #origin and #dispose set to 0.
static FlKeyEvent* fl_key_event_clone_information_only(FlKeyEvent* event) {
FlKeyEvent* new_event = fl_key_event_clone(event);
new_event->origin = nullptr;
new_event->dispose_origin = nullptr;
return new_event;
}
static void fl_key_mock_responder_handle_event(
FlKeyResponder* responder,
FlKeyEvent* event,
FlKeyResponderAsyncCallback callback,
gpointer user_data) {
FlKeyMockResponder* self = FL_KEY_MOCK_RESPONDER(responder);
g_ptr_array_add(self->call_records,
FL_KEYBOARD_CALL_RECORD(fl_keyboard_call_record_new(
self, fl_key_event_clone_information_only(event),
callback, user_data)));
self->callback_handler(callback, user_data);
}
static void fl_key_mock_responder_class_init(FlKeyMockResponderClass* klass) {}
static void fl_key_mock_responder_init(FlKeyMockResponder* self) {}
static FlKeyMockResponder* fl_key_mock_responder_new(GPtrArray* call_records,
int delegate_id) {
FlKeyMockResponder* self = FL_KEY_MOCK_RESPONDER(
g_object_new(fl_key_mock_responder_get_type(), nullptr));
self->call_records = call_records;
self->callback_handler = dont_respond;
self->delegate_id = delegate_id;
return self;
}
static void g_ptr_array_clear(GPtrArray* array) {
g_ptr_array_remove_range(array, 0, array->len);
}
static gpointer g_ptr_array_last(GPtrArray* array) {
return g_ptr_array_index(array, array->len - 1);
}
static void fl_key_event_free_origin_by_mock(gpointer origin) {
g_free(origin);
}
// Create a new #FlKeyEvent with the given information.
//
// The #origin will be another #FlKeyEvent with the exact information,
// so that it can be used to redispatch, and is freed upon disposal.
static FlKeyEvent* fl_key_event_new_by_mock(bool is_press,
guint keyval,
guint16 keycode,
int state,
gboolean is_modifier) {
FlKeyEvent* event = g_new(FlKeyEvent, 1);
event->is_press = is_press;
event->time = 0;
event->state = state;
event->keyval = keyval;
event->string = nullptr;
event->keycode = keycode;
FlKeyEvent* origin_event = fl_key_event_clone_information_only(event);
event->origin = origin_event;
event->dispose_origin = fl_key_event_free_origin_by_mock;
return event;
}
namespace {
// A global variable to store redispatched #FlKeyEvent. It is a global variable
// so that it can be used in a function without user_data.
//
// This array does not free elements upon removal.
GPtrArray* _g_redispatched_events;
} // namespace
static GPtrArray* redispatched_events() {
if (_g_redispatched_events == nullptr) {
_g_redispatched_events = g_ptr_array_new();
}
return _g_redispatched_events;
}
static void store_redispatched_event(gpointer event) {
FlKeyEvent* new_event = g_new(FlKeyEvent, 1);
*new_event = *reinterpret_cast<FlKeyEvent*>(event);
g_ptr_array_add(redispatched_events(), new_event);
}
// Make sure that the keyboard can be disposed without crashes when there are
// unresolved pending events.
TEST(FlKeyboardManagerTest, DisposeWithUnresolvedPends) {
FlKeyboardManager* manager =
fl_keyboard_manager_new(nullptr, store_redispatched_event);
GPtrArray* call_records = g_ptr_array_new_with_free_func(g_object_unref);
FlKeyMockResponder* responder = fl_key_mock_responder_new(call_records, 1);
fl_keyboard_manager_add_responder(manager, FL_KEY_RESPONDER(responder));
responder->callback_handler = dont_respond;
fl_keyboard_manager_handle_event(
manager,
fl_key_event_new_by_mock(true, GDK_KEY_a, kKeyCodeKeyA, 0x10, false));
responder->callback_handler = respond_true;
fl_keyboard_manager_handle_event(
manager,
fl_key_event_new_by_mock(true, GDK_KEY_a, kKeyCodeKeyA, 0x10, false));
// Passes if the cleanup of `manager` does not crash.
g_object_unref(manager);
g_ptr_array_unref(call_records);
}
TEST(FlKeyboardManagerTest, SingleDelegateWithAsyncResponds) {
GPtrArray* call_records = g_ptr_array_new_with_free_func(g_object_unref);
FlKeyboardCallRecord* record;
gboolean manager_handled = false;
g_autoptr(FlKeyboardManager) manager =
fl_keyboard_manager_new(nullptr, store_redispatched_event);
fl_keyboard_manager_add_responder(
manager, FL_KEY_RESPONDER(fl_key_mock_responder_new(call_records, 1)));
/// Test 1: One event that is handled by the framework
// Dispatch a key event
manager_handled = fl_keyboard_manager_handle_event(
manager,
fl_key_event_new_by_mock(true, GDK_KEY_a, kKeyCodeKeyA, 0x10, false));
EXPECT_EQ(manager_handled, true);
EXPECT_EQ(redispatched_events()->len, 0u);
EXPECT_EQ(call_records->len, 1u);
record = FL_KEYBOARD_CALL_RECORD(g_ptr_array_index(call_records, 0));
EXPECT_EQ(record->responder->delegate_id, 1);
EXPECT_EQ(record->event->keyval, 0x61u);
EXPECT_EQ(record->event->keycode, 0x26u);
record->callback(true, record->user_data);
EXPECT_EQ(redispatched_events()->len, 0u);
EXPECT_TRUE(fl_keyboard_manager_is_state_clear(manager));
g_ptr_array_clear(call_records);
/// Test 2: Two events that are unhandled by the framework
manager_handled = fl_keyboard_manager_handle_event(
manager,
fl_key_event_new_by_mock(true, GDK_KEY_a, kKeyCodeKeyA, 0x10, false));
EXPECT_EQ(manager_handled, true);
EXPECT_EQ(redispatched_events()->len, 0u);
EXPECT_EQ(call_records->len, 1u);
record = FL_KEYBOARD_CALL_RECORD(g_ptr_array_index(call_records, 0));
EXPECT_EQ(record->responder->delegate_id, 1);
EXPECT_EQ(record->event->keyval, 0x61u);
EXPECT_EQ(record->event->keycode, 0x26u);
// Dispatch another key event
manager_handled = fl_keyboard_manager_handle_event(
manager,
fl_key_event_new_by_mock(true, GDK_KEY_b, kKeyCodeKeyB, 0x10, false));
EXPECT_EQ(manager_handled, true);
EXPECT_EQ(redispatched_events()->len, 0u);
EXPECT_EQ(call_records->len, 2u);
record = FL_KEYBOARD_CALL_RECORD(g_ptr_array_index(call_records, 1));
EXPECT_EQ(record->responder->delegate_id, 1);
EXPECT_EQ(record->event->keyval, 0x62u);
EXPECT_EQ(record->event->keycode, 0x38u);
// Resolve the second event first to test out-of-order response
record = FL_KEYBOARD_CALL_RECORD(g_ptr_array_index(call_records, 1));
record->callback(false, record->user_data);
EXPECT_EQ(redispatched_events()->len, 1u);
EXPECT_EQ(FL_KEY_EVENT(g_ptr_array_last(redispatched_events()))->keyval,
0x62u);
record = FL_KEYBOARD_CALL_RECORD(g_ptr_array_index(call_records, 0));
record->callback(false, record->user_data);
EXPECT_EQ(redispatched_events()->len, 2u);
EXPECT_EQ(FL_KEY_EVENT(g_ptr_array_last(redispatched_events()))->keyval,
0x61u);
g_ptr_array_clear(call_records);
// Resolve redispatches
manager_handled = fl_keyboard_manager_handle_event(
manager, FL_KEY_EVENT(g_ptr_array_index(redispatched_events(), 0)));
EXPECT_EQ(manager_handled, false);
manager_handled = fl_keyboard_manager_handle_event(
manager, FL_KEY_EVENT(g_ptr_array_index(redispatched_events(), 1)));
EXPECT_EQ(manager_handled, false);
EXPECT_EQ(call_records->len, 0u);
g_ptr_array_clear(redispatched_events());
EXPECT_TRUE(fl_keyboard_manager_is_state_clear(manager));
/// Test 3: Dispatch the same event again to ensure that prevention from
/// redispatching only works once.
manager_handled = fl_keyboard_manager_handle_event(
manager,
fl_key_event_new_by_mock(true, GDK_KEY_a, kKeyCodeKeyA, 0x10, false));
EXPECT_EQ(manager_handled, true);
EXPECT_EQ(redispatched_events()->len, 0u);
EXPECT_EQ(call_records->len, 1u);
record = FL_KEYBOARD_CALL_RECORD(g_ptr_array_index(call_records, 0));
record->callback(true, record->user_data);
g_ptr_array_clear(redispatched_events());
EXPECT_TRUE(fl_keyboard_manager_is_state_clear(manager));
g_ptr_array_unref(call_records);
}
TEST(FlKeyboardManagerTest, SingleDelegateWithSyncResponds) {
GPtrArray* call_records = g_ptr_array_new_with_free_func(g_object_unref);
FlKeyboardCallRecord* record;
gboolean manager_handled = false;
g_autoptr(FlKeyboardManager) manager =
fl_keyboard_manager_new(nullptr, store_redispatched_event);
FlKeyMockResponder* responder = fl_key_mock_responder_new(call_records, 1);
fl_keyboard_manager_add_responder(manager, FL_KEY_RESPONDER(responder));
/// Test 1: One event that is handled by the framework
// Dispatch a key event
responder->callback_handler = respond_true;
manager_handled = fl_keyboard_manager_handle_event(
manager,
fl_key_event_new_by_mock(true, GDK_KEY_a, kKeyCodeKeyA, 0x10, false));
EXPECT_EQ(manager_handled, true);
EXPECT_EQ(call_records->len, 1u);
record = FL_KEYBOARD_CALL_RECORD(g_ptr_array_index(call_records, 0));
EXPECT_EQ(record->responder->delegate_id, 1);
EXPECT_EQ(record->event->keyval, 0x61u);
EXPECT_EQ(record->event->keycode, 0x26u);
EXPECT_EQ(redispatched_events()->len, 0u);
EXPECT_TRUE(fl_keyboard_manager_is_state_clear(manager));
EXPECT_TRUE(fl_keyboard_manager_is_state_clear(manager));
g_ptr_array_clear(call_records);
/// Test 2: An event unhandled by the framework
responder->callback_handler = respond_false;
manager_handled = fl_keyboard_manager_handle_event(
manager,
fl_key_event_new_by_mock(true, GDK_KEY_a, kKeyCodeKeyA, 0x10, false));
EXPECT_EQ(manager_handled, true);
EXPECT_EQ(call_records->len, 1u);
record = FL_KEYBOARD_CALL_RECORD(g_ptr_array_index(call_records, 0));
EXPECT_EQ(record->responder->delegate_id, 1);
EXPECT_EQ(record->event->keyval, 0x61u);
EXPECT_EQ(record->event->keycode, 0x26u);
EXPECT_EQ(redispatched_events()->len, 1u);
g_ptr_array_clear(call_records);
// Resolve redispatch
manager_handled = fl_keyboard_manager_handle_event(
manager, FL_KEY_EVENT(g_ptr_array_index(redispatched_events(), 0)));
EXPECT_EQ(manager_handled, false);
EXPECT_EQ(call_records->len, 0u);
EXPECT_TRUE(fl_keyboard_manager_is_state_clear(manager));
g_ptr_array_clear(redispatched_events());
g_ptr_array_unref(call_records);
}
TEST(FlKeyboardManagerTest, WithTwoAsyncDelegates) {
GPtrArray* call_records = g_ptr_array_new_with_free_func(g_object_unref);
FlKeyboardCallRecord* record;
gboolean manager_handled = false;
g_autoptr(FlKeyboardManager) manager =
fl_keyboard_manager_new(nullptr, store_redispatched_event);
fl_keyboard_manager_add_responder(
manager, FL_KEY_RESPONDER(fl_key_mock_responder_new(call_records, 1)));
fl_keyboard_manager_add_responder(
manager, FL_KEY_RESPONDER(fl_key_mock_responder_new(call_records, 2)));
/// Test 1: One delegate responds true, the other false
manager_handled = fl_keyboard_manager_handle_event(
manager,
fl_key_event_new_by_mock(true, GDK_KEY_a, kKeyCodeKeyA, 0x10, false));
EXPECT_EQ(manager_handled, true);
EXPECT_EQ(redispatched_events()->len, 0u);
EXPECT_EQ(call_records->len, 2u);
record = FL_KEYBOARD_CALL_RECORD(g_ptr_array_index(call_records, 0));
EXPECT_EQ(record->responder->delegate_id, 1);
EXPECT_EQ(record->event->keyval, 0x61u);
record = FL_KEYBOARD_CALL_RECORD(g_ptr_array_index(call_records, 1));
EXPECT_EQ(record->responder->delegate_id, 2);
EXPECT_EQ(record->event->keyval, 0x61u);
record = FL_KEYBOARD_CALL_RECORD(g_ptr_array_index(call_records, 0));
record->callback(true, record->user_data);
record = FL_KEYBOARD_CALL_RECORD(g_ptr_array_index(call_records, 1));
record->callback(false, record->user_data);
EXPECT_EQ(redispatched_events()->len, 0u);
EXPECT_TRUE(fl_keyboard_manager_is_state_clear(manager));
g_ptr_array_clear(call_records);
/// Test 2: All delegates respond false
manager_handled = fl_keyboard_manager_handle_event(
manager,
fl_key_event_new_by_mock(true, GDK_KEY_a, kKeyCodeKeyA, 0x10, false));
EXPECT_EQ(manager_handled, true);
EXPECT_EQ(redispatched_events()->len, 0u);
EXPECT_EQ(call_records->len, 2u);
record = FL_KEYBOARD_CALL_RECORD(g_ptr_array_index(call_records, 0));
record->callback(false, record->user_data);
EXPECT_EQ(redispatched_events()->len, 0u);
record = FL_KEYBOARD_CALL_RECORD(g_ptr_array_index(call_records, 1));
record->callback(false, record->user_data);
EXPECT_EQ(redispatched_events()->len, 1u);
g_ptr_array_clear(call_records);
// Resolve redispatch
manager_handled = fl_keyboard_manager_handle_event(
manager, FL_KEY_EVENT(g_ptr_array_index(redispatched_events(), 0)));
EXPECT_EQ(manager_handled, false);
EXPECT_EQ(call_records->len, 0u);
EXPECT_TRUE(fl_keyboard_manager_is_state_clear(manager));
g_ptr_array_clear(call_records);
g_ptr_array_clear(redispatched_events());
g_ptr_array_unref(call_records);
}
TEST(FlKeyboardManagerTest, TextInputPluginReturnsFalse) {
GPtrArray* call_records = g_ptr_array_new_with_free_func(g_object_unref);
gboolean manager_handled = false;
// The text input plugin doesn't handle events.
g_autoptr(FlKeyboardManager) manager = fl_keyboard_manager_new(
FL_TEXT_INPUT_PLUGIN(
fl_mock_text_input_plugin_new(filter_keypress_returns_false)),
store_redispatched_event);
// The responder never handles events.
FlKeyMockResponder* responder = fl_key_mock_responder_new(call_records, 1);
fl_keyboard_manager_add_responder(manager, FL_KEY_RESPONDER(responder));
responder->callback_handler = respond_false;
// Dispatch a key event.
manager_handled = fl_keyboard_manager_handle_event(
manager,
fl_key_event_new_by_mock(true, GDK_KEY_a, kKeyCodeKeyA, 0, false));
EXPECT_EQ(manager_handled, true);
// The event was redispatched because no one handles it.
EXPECT_EQ(redispatched_events()->len, 1u);
// Resolve redispatched event.
manager_handled = fl_keyboard_manager_handle_event(
manager, FL_KEY_EVENT(g_ptr_array_index(redispatched_events(), 0)));
EXPECT_EQ(manager_handled, false);
g_ptr_array_clear(redispatched_events());
EXPECT_TRUE(fl_keyboard_manager_is_state_clear(manager));
g_ptr_array_unref(call_records);
}
TEST(FlKeyboardManagerTest, TextInputPluginReturnsTrue) {
GPtrArray* call_records = g_ptr_array_new_with_free_func(g_object_unref);
gboolean manager_handled = false;
g_autoptr(FlKeyboardManager) manager = fl_keyboard_manager_new(
FL_TEXT_INPUT_PLUGIN(
fl_mock_text_input_plugin_new(filter_keypress_returns_true)),
store_redispatched_event);
// The responder never handles events.
FlKeyMockResponder* responder = fl_key_mock_responder_new(call_records, 1);
fl_keyboard_manager_add_responder(manager, FL_KEY_RESPONDER(responder));
responder->callback_handler = respond_false;
// Dispatch a key event.
manager_handled = fl_keyboard_manager_handle_event(
manager,
fl_key_event_new_by_mock(true, GDK_KEY_a, kKeyCodeKeyA, 0, false));
EXPECT_EQ(manager_handled, true);
// The event was not redispatched because text input plugin handles it.
EXPECT_EQ(redispatched_events()->len, 0u);
EXPECT_TRUE(fl_keyboard_manager_is_state_clear(manager));
g_ptr_array_unref(call_records);
}
} // namespace