Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
keyboard_key_handler_unittests.cc
Go to the documentation of this file.
1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4#include "flutter/shell/platform/windows/keyboard_key_handler.h"
5
6#include <rapidjson/document.h>
7#include <map>
8#include <memory>
9#include "flutter/shell/platform/common/client_wrapper/include/flutter/method_result_functions.h"
10#include "flutter/shell/platform/common/client_wrapper/include/flutter/standard_message_codec.h"
11#include "flutter/shell/platform/common/client_wrapper/include/flutter/standard_method_codec.h"
12#include "flutter/shell/platform/embedder/test_utils/key_codes.g.h"
13#include "flutter/shell/platform/embedder/test_utils/proc_table_replacement.h"
14#include "flutter/shell/platform/windows/keyboard_utils.h"
15#include "flutter/shell/platform/windows/testing/engine_modifier.h"
16#include "flutter/shell/platform/windows/testing/test_binary_messenger.h"
17
18#include "flutter/fml/macros.h"
19#include "gmock/gmock.h"
20#include "gtest/gtest.h"
21
22namespace flutter {
23namespace testing {
24
25namespace {
26
27static constexpr char kChannelName[] = "flutter/keyboard";
28static constexpr char kGetKeyboardStateMethod[] = "getKeyboardState";
29
30constexpr SHORT kStateMaskToggled = 0x01;
31constexpr SHORT kStateMaskPressed = 0x80;
32
33class TestFlutterKeyEvent : public FlutterKeyEvent {
34 public:
35 TestFlutterKeyEvent(const FlutterKeyEvent& src,
37 void* user_data)
38 : character_str(src.character), callback(callback), user_data(user_data) {
39 struct_size = src.struct_size;
40 timestamp = src.timestamp;
41 type = src.type;
42 physical = src.physical;
43 logical = src.logical;
44 character = character_str.c_str();
45 synthesized = src.synthesized;
46 }
47
48 TestFlutterKeyEvent(TestFlutterKeyEvent&& source)
52 character = character_str.c_str();
53 }
54
56 void* user_data;
57
58 private:
59 const std::string character_str;
60};
61
62class TestKeystate {
63 public:
64 void Set(int virtual_key, bool pressed, bool toggled_on = false) {
65 state_[virtual_key] = (pressed ? kStateMaskPressed : 0) |
66 (toggled_on ? kStateMaskToggled : 0);
67 }
68
69 SHORT Get(int virtual_key) { return state_[virtual_key]; }
70
72 return [this](int virtual_key) { return Get(virtual_key); };
73 }
74
75 private:
76 std::map<int, SHORT> state_;
77};
78
79UINT DefaultMapVkToScan(UINT virtual_key, bool extended) {
80 return MapVirtualKey(virtual_key,
81 extended ? MAPVK_VK_TO_VSC_EX : MAPVK_VK_TO_VSC);
82}
83
84static constexpr int kHandledScanCode = 20;
85static constexpr int kHandledScanCode2 = 22;
86static constexpr int kUnhandledScanCode = 21;
87
88constexpr uint64_t kScanCodeShiftRight = 0x36;
89constexpr uint64_t kScanCodeControl = 0x1D;
90constexpr uint64_t kScanCodeAltLeft = 0x38;
91
92constexpr uint64_t kScanCodeKeyA = 0x1e;
93constexpr uint64_t kVirtualKeyA = 0x41;
94
95typedef std::function<void(bool)> Callback;
96typedef std::function<void(Callback&)> CallbackHandler;
97void dont_respond(Callback& callback) {}
98void respond_true(Callback& callback) {
99 callback(true);
100}
101void respond_false(Callback& callback) {
102 callback(false);
103}
104
105// A testing |KeyHandlerDelegate| that records all calls
106// to |KeyboardHook| and can be customized with whether
107// the hook is handled in async.
108class MockKeyHandlerDelegate
109 : public KeyboardKeyHandler::KeyboardKeyHandlerDelegate {
110 public:
111 class KeyboardHookCall {
112 public:
114 int key;
116 int action;
117 char32_t character;
120 std::function<void(bool)> callback;
121 };
122
123 // Create a |MockKeyHandlerDelegate|.
124 //
125 // The |delegate_id| is an arbitrary ID to tell between delegates
126 // that will be recorded in |KeyboardHookCall|.
127 //
128 // The |hook_history| will store every call to |KeyboardHookCall| that are
129 // handled asynchronously.
130 //
131 // The |is_async| is a function that the class calls upon every
132 // |KeyboardHookCall| to decide whether the call is handled asynchronously.
133 // Defaults to always returning true (async).
134 MockKeyHandlerDelegate(int delegate_id,
135 std::list<KeyboardHookCall>* hook_history)
138 callback_handler(dont_respond) {}
139 virtual ~MockKeyHandlerDelegate() = default;
140
141 virtual void KeyboardHook(int key,
142 int scancode,
143 int action,
144 char32_t character,
145 bool extended,
146 bool was_down,
147 std::function<void(bool)> callback) {
148 hook_history->push_back(KeyboardHookCall{
149 .delegate_id = delegate_id,
150 .key = key,
151 .scancode = scancode,
152 .character = character,
153 .extended = extended,
154 .was_down = was_down,
155 .callback = std::move(callback),
156 });
157 callback_handler(hook_history->back().callback);
158 }
159
160 virtual void SyncModifiersIfNeeded(int modifiers_state) {
161 // Do Nothing
162 }
163
164 virtual std::map<uint64_t, uint64_t> GetPressedState() {
165 std::map<uint64_t, uint64_t> Empty_State;
166 return Empty_State;
167 }
168
169 CallbackHandler callback_handler;
170 int delegate_id;
171 std::list<KeyboardHookCall>* hook_history;
172
173 private:
174 FML_DISALLOW_COPY_AND_ASSIGN(MockKeyHandlerDelegate);
175};
176
177enum KeyEventResponse {
178 kNoResponse,
179 kHandled,
180 kUnhandled,
181};
182
183static KeyEventResponse key_event_response = kNoResponse;
184
185void OnKeyEventResult(bool handled) {
186 key_event_response = handled ? kHandled : kUnhandled;
187}
188
189void SimulateKeyboardMessage(TestBinaryMessenger* messenger,
190 const std::string& method_name,
191 std::unique_ptr<EncodableValue> arguments,
192 MethodResult<EncodableValue>* result_handler) {
193 MethodCall<> call(method_name, std::move(arguments));
195
196 EXPECT_TRUE(messenger->SimulateEngineMessage(
197 kChannelName, message->data(), message->size(),
198 [&result_handler](const uint8_t* reply, size_t reply_size) {
199 StandardMethodCodec::GetInstance().DecodeAndProcessResponseEnvelope(
200 reply, reply_size, result_handler);
201 }));
202}
203
204} // namespace
205
206using namespace ::flutter::testing::keycodes;
207
208TEST(KeyboardKeyHandlerTest, SingleDelegateWithAsyncResponds) {
209 std::list<MockKeyHandlerDelegate::KeyboardHookCall> hook_history;
210
211 TestBinaryMessenger messenger([](const std::string& channel,
212 const uint8_t* message, size_t message_size,
213 BinaryReply reply) {});
214 KeyboardKeyHandler handler(&messenger);
215
216 // Add one delegate
217 auto delegate = std::make_unique<MockKeyHandlerDelegate>(1, &hook_history);
218 handler.AddDelegate(std::move(delegate));
219
220 /// Test 1: One event that is handled by the framework
221
222 // Dispatch a key event
223 handler.KeyboardHook(64, kHandledScanCode, WM_KEYDOWN, L'a', false, true,
224 OnKeyEventResult);
225 EXPECT_EQ(key_event_response, kNoResponse);
226 EXPECT_EQ(hook_history.size(), 1);
227 EXPECT_EQ(hook_history.back().delegate_id, 1);
228 EXPECT_EQ(hook_history.back().scancode, kHandledScanCode);
229 EXPECT_EQ(hook_history.back().was_down, true);
230
231 EXPECT_EQ(key_event_response, kNoResponse);
232 hook_history.back().callback(true);
233 EXPECT_EQ(key_event_response, kHandled);
234
235 key_event_response = kNoResponse;
236 hook_history.clear();
237
238 /// Test 2: Two events that are unhandled by the framework
239
240 handler.KeyboardHook(64, kHandledScanCode, WM_KEYDOWN, L'a', false, false,
241 OnKeyEventResult);
242 EXPECT_EQ(key_event_response, kNoResponse);
243 EXPECT_EQ(hook_history.size(), 1);
244 EXPECT_EQ(hook_history.back().delegate_id, 1);
245 EXPECT_EQ(hook_history.back().scancode, kHandledScanCode);
246 EXPECT_EQ(hook_history.back().was_down, false);
247
248 // Dispatch another key event
249 handler.KeyboardHook(65, kHandledScanCode2, WM_KEYUP, L'b', false, true,
250 OnKeyEventResult);
251 EXPECT_EQ(key_event_response, kNoResponse);
252 EXPECT_EQ(hook_history.size(), 2);
253 EXPECT_EQ(hook_history.back().delegate_id, 1);
254 EXPECT_EQ(hook_history.back().scancode, kHandledScanCode2);
255 EXPECT_EQ(hook_history.back().was_down, true);
256
257 // Resolve the second event first to test out-of-order response
258 hook_history.back().callback(false);
259 EXPECT_EQ(key_event_response, kUnhandled);
260 key_event_response = kNoResponse;
261
262 // Resolve the first event then
263 hook_history.front().callback(false);
264 EXPECT_EQ(key_event_response, kUnhandled);
265
266 hook_history.clear();
267 key_event_response = kNoResponse;
268}
269
270TEST(KeyboardKeyHandlerTest, SingleDelegateWithSyncResponds) {
271 std::list<MockKeyHandlerDelegate::KeyboardHookCall> hook_history;
272
273 TestBinaryMessenger messenger([](const std::string& channel,
274 const uint8_t* message, size_t message_size,
275 BinaryReply reply) {});
276 KeyboardKeyHandler handler(&messenger);
277 // Add one delegate
278 auto delegate = std::make_unique<MockKeyHandlerDelegate>(1, &hook_history);
279 CallbackHandler& delegate_handler = delegate->callback_handler;
280 handler.AddDelegate(std::move(delegate));
281
282 /// Test 1: One event that is handled by the framework
283
284 // Dispatch a key event
285 delegate_handler = respond_true;
286 handler.KeyboardHook(64, kHandledScanCode, WM_KEYDOWN, L'a', false, false,
287 OnKeyEventResult);
288 EXPECT_EQ(key_event_response, kHandled);
289 EXPECT_EQ(hook_history.size(), 1);
290 EXPECT_EQ(hook_history.back().delegate_id, 1);
291 EXPECT_EQ(hook_history.back().scancode, kHandledScanCode);
292 EXPECT_EQ(hook_history.back().was_down, false);
293
294 hook_history.clear();
295
296 /// Test 2: An event unhandled by the framework
297
298 delegate_handler = respond_false;
299 handler.KeyboardHook(64, kHandledScanCode, WM_KEYDOWN, L'a', false, false,
300 OnKeyEventResult);
301 EXPECT_EQ(key_event_response, kUnhandled);
302 EXPECT_EQ(hook_history.size(), 1);
303 EXPECT_EQ(hook_history.back().delegate_id, 1);
304 EXPECT_EQ(hook_history.back().scancode, kHandledScanCode);
305 EXPECT_EQ(hook_history.back().was_down, false);
306
307 hook_history.clear();
308 key_event_response = kNoResponse;
309}
310
311TEST(KeyboardKeyHandlerTest, HandlerGetPressedState) {
312 TestKeystate key_state;
313
314 TestBinaryMessenger messenger([](const std::string& channel,
315 const uint8_t* message, size_t message_size,
316 BinaryReply reply) {});
317 KeyboardKeyHandler handler(&messenger);
318
319 std::unique_ptr<KeyboardKeyEmbedderHandler> embedder_handler =
320 std::make_unique<KeyboardKeyEmbedderHandler>(
322 void* user_data) {},
323 key_state.Getter(), DefaultMapVkToScan);
324 handler.AddDelegate(std::move(embedder_handler));
325
326 // Dispatch a key event.
327 handler.KeyboardHook(kVirtualKeyA, kScanCodeKeyA, WM_KEYDOWN, 'a', false,
328 false, OnKeyEventResult);
329
330 std::map<uint64_t, uint64_t> pressed_state = handler.GetPressedState();
331 EXPECT_EQ(pressed_state.size(), 1);
332 EXPECT_EQ(pressed_state.at(kPhysicalKeyA), kLogicalKeyA);
333}
334
335TEST(KeyboardKeyHandlerTest, KeyboardChannelGetPressedState) {
336 TestKeystate key_state;
337 TestBinaryMessenger messenger;
338 KeyboardKeyHandler handler(&messenger);
339
340 std::unique_ptr<KeyboardKeyEmbedderHandler> embedder_handler =
341 std::make_unique<KeyboardKeyEmbedderHandler>(
343 void* user_data) {},
344 key_state.Getter(), DefaultMapVkToScan);
345 handler.AddDelegate(std::move(embedder_handler));
346 handler.InitKeyboardChannel();
347
348 // Dispatch a key event.
349 handler.KeyboardHook(kVirtualKeyA, kScanCodeKeyA, WM_KEYDOWN, 'a', false,
350 false, OnKeyEventResult);
351
352 bool success = false;
353
354 MethodResultFunctions<> result_handler(
355 [&success](const EncodableValue* result) {
356 success = true;
357 auto& map = std::get<EncodableMap>(*result);
358 EXPECT_EQ(map.size(), 1);
359 EncodableValue physical_value(static_cast<long long>(kPhysicalKeyA));
360 EncodableValue logical_value(static_cast<long long>(kLogicalKeyA));
361 EXPECT_EQ(map.at(physical_value), logical_value);
362 },
363 nullptr, nullptr);
364
365 SimulateKeyboardMessage(&messenger, kGetKeyboardStateMethod, nullptr,
366 &result_handler);
367 EXPECT_TRUE(success);
368}
369
370} // namespace testing
371} // namespace flutter
#define TEST(S, s, D, expected)
static NSString *const kChannelName
void KeyboardHook(int key, int scancode, int action, char32_t character, bool extended, bool was_down, KeyEventCallback callback) override
void AddDelegate(std::unique_ptr< KeyboardKeyHandlerDelegate > delegate)
std::unique_ptr< std::vector< uint8_t > > EncodeMethodCall(const MethodCall< T > &method_call) const
static const StandardMethodCodec & GetInstance(const StandardCodecSerializer *serializer=nullptr)
void(* FlutterKeyEventCallback)(bool, void *)
Definition embedder.h:1153
SkBitmap source
Definition examples.cpp:28
FlKeyEvent uint64_t FlKeyResponderAsyncCallback callback
FlKeyEvent * event
static constexpr char kGetKeyboardStateMethod[]
GAsyncResult * result
#define FML_DISALLOW_COPY_AND_ASSIGN(TypeName)
Definition macros.h:27
std::list< KeyboardHookCall > * hook_history
CallbackHandler callback_handler
bool pressed
bool toggled_on
Win32Message message
const GrXPFactory * Get(SkBlendMode mode)
call(args)
Definition dom.py:159
constexpr int kScanCodeShiftRight
std::function< void(const uint8_t *reply, size_t reply_size)> BinaryReply
std::function< void(MTLRenderPipelineDescriptor *)> Callback
Definition ref_ptr.h:256
#define EXPECT_TRUE(handle)
Definition unit_test.h:685
unsigned int UINT
short SHORT