Flutter Linux Embedder
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros
fl_engine_test.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 
5 // Included first as it collides with the X11 headers.
6 #include "gtest/gtest.h"
7 
8 #include "flutter/shell/platform/embedder/test_utils/proc_table_replacement.h"
13 
14 // MOCK_ENGINE_PROC is leaky by design
15 // NOLINTBEGIN(clang-analyzer-core.StackAddressEscape)
16 
17 // Checks notifying display updates works.
18 TEST(FlEngineTest, NotifyDisplayUpdate) {
19  g_autoptr(FlDartProject) project = fl_dart_project_new();
20  g_autoptr(FlEngine) engine = fl_engine_new(project);
21 
22  g_autoptr(GError) error = nullptr;
23  EXPECT_TRUE(fl_engine_start(engine, &error));
24  EXPECT_EQ(error, nullptr);
25 
26  bool called = false;
27  fl_engine_get_embedder_api(engine)->NotifyDisplayUpdate = MOCK_ENGINE_PROC(
28  NotifyDisplayUpdate,
29  ([&called](auto engine, FlutterEngineDisplaysUpdateType update_type,
30  const FlutterEngineDisplay* displays, size_t displays_length) {
31  called = true;
32  EXPECT_EQ(update_type, kFlutterEngineDisplaysUpdateTypeStartup);
33  EXPECT_EQ(displays_length, 2u);
34 
35  EXPECT_EQ(displays[0].display_id, 1u);
36  EXPECT_EQ(displays[0].refresh_rate, 60);
37  EXPECT_EQ(displays[0].width, 1024u);
38  EXPECT_EQ(displays[0].height, 768u);
39  EXPECT_EQ(displays[0].device_pixel_ratio, 1.0);
40 
41  EXPECT_EQ(displays[1].display_id, 2u);
42  EXPECT_EQ(displays[1].refresh_rate, 120);
43  EXPECT_EQ(displays[1].width, 3840u);
44  EXPECT_EQ(displays[1].height, 2160u);
45  EXPECT_EQ(displays[1].device_pixel_ratio, 2.0);
46 
47  return kSuccess;
48  }));
49 
50  FlutterEngineDisplay displays[2] = {
51  {
52  .struct_size = sizeof(FlutterEngineDisplay),
53  .display_id = 1,
54  .single_display = false,
55  .refresh_rate = 60.0,
56  .width = 1024,
57  .height = 768,
58  .device_pixel_ratio = 1.0,
59  },
60  {
61  .struct_size = sizeof(FlutterEngineDisplay),
62  .display_id = 2,
63  .single_display = false,
64  .refresh_rate = 120.0,
65  .width = 3840,
66  .height = 2160,
67  .device_pixel_ratio = 2.0,
68  }};
69  fl_engine_notify_display_update(engine, displays, 2);
70 
71  EXPECT_TRUE(called);
72 }
73 
74 // Checks sending window metrics events works.
75 TEST(FlEngineTest, WindowMetrics) {
76  g_autoptr(FlDartProject) project = fl_dart_project_new();
77  g_autoptr(FlEngine) engine = fl_engine_new(project);
78 
79  g_autoptr(GError) error = nullptr;
80  EXPECT_TRUE(fl_engine_start(engine, &error));
81  EXPECT_EQ(error, nullptr);
82 
83  bool called = false;
84  fl_engine_get_embedder_api(engine)->SendWindowMetricsEvent = MOCK_ENGINE_PROC(
85  SendWindowMetricsEvent,
86  ([&called](auto engine, const FlutterWindowMetricsEvent* event) {
87  called = true;
88  EXPECT_EQ(event->display_id, 99u);
89  EXPECT_EQ(event->view_id, 1);
90  EXPECT_EQ(event->width, static_cast<size_t>(3840));
91  EXPECT_EQ(event->height, static_cast<size_t>(2160));
92  EXPECT_EQ(event->pixel_ratio, 2.0);
93 
94  return kSuccess;
95  }));
96 
97  fl_engine_send_window_metrics_event(engine, 99, 1, 3840, 2160, 2.0);
98 
99  EXPECT_TRUE(called);
100 }
101 
102 // Checks sending mouse pointer events works.
103 TEST(FlEngineTest, MousePointer) {
104  g_autoptr(FlDartProject) project = fl_dart_project_new();
105  g_autoptr(FlEngine) engine = fl_engine_new(project);
106 
107  bool called = false;
108  fl_engine_get_embedder_api(engine)->SendPointerEvent = MOCK_ENGINE_PROC(
109  SendPointerEvent,
110  ([&called](auto engine, const FlutterPointerEvent* events,
111  size_t events_count) {
112  called = true;
113  EXPECT_EQ(events_count, static_cast<size_t>(1));
114  EXPECT_EQ(events[0].view_id, 1);
115  EXPECT_EQ(events[0].phase, kDown);
116  EXPECT_EQ(events[0].timestamp, static_cast<size_t>(1234567890));
117  EXPECT_EQ(events[0].x, 800);
118  EXPECT_EQ(events[0].y, 600);
119  EXPECT_EQ(events[0].device, static_cast<int32_t>(0));
120  EXPECT_EQ(events[0].signal_kind, kFlutterPointerSignalKindScroll);
121  EXPECT_EQ(events[0].scroll_delta_x, 1.2);
122  EXPECT_EQ(events[0].scroll_delta_y, -3.4);
123  EXPECT_EQ(events[0].device_kind, kFlutterPointerDeviceKindMouse);
124  EXPECT_EQ(events[0].buttons, kFlutterPointerButtonMouseSecondary);
125 
126  return kSuccess;
127  }));
128 
129  g_autoptr(GError) error = nullptr;
130  EXPECT_TRUE(fl_engine_start(engine, &error));
131  EXPECT_EQ(error, nullptr);
132  fl_engine_send_mouse_pointer_event(engine, 1, kDown, 1234567890, 800, 600,
133  kFlutterPointerDeviceKindMouse, 1.2, -3.4,
134  kFlutterPointerButtonMouseSecondary);
135 
136  EXPECT_TRUE(called);
137 }
138 
139 // Checks sending pan/zoom events works.
140 TEST(FlEngineTest, PointerPanZoom) {
141  g_autoptr(FlDartProject) project = fl_dart_project_new();
142  g_autoptr(FlEngine) engine = fl_engine_new(project);
143 
144  bool called = false;
145  fl_engine_get_embedder_api(engine)->SendPointerEvent = MOCK_ENGINE_PROC(
146  SendPointerEvent,
147  ([&called](auto engine, const FlutterPointerEvent* events,
148  size_t events_count) {
149  called = true;
150  EXPECT_EQ(events_count, static_cast<size_t>(1));
151  EXPECT_EQ(events[0].view_id, 1);
152  EXPECT_EQ(events[0].phase, kPanZoomUpdate);
153  EXPECT_EQ(events[0].timestamp, static_cast<size_t>(1234567890));
154  EXPECT_EQ(events[0].x, 800);
155  EXPECT_EQ(events[0].y, 600);
156  EXPECT_EQ(events[0].device, static_cast<int32_t>(1));
157  EXPECT_EQ(events[0].signal_kind, kFlutterPointerSignalKindNone);
158  EXPECT_EQ(events[0].pan_x, 1.5);
159  EXPECT_EQ(events[0].pan_y, 2.5);
160  EXPECT_EQ(events[0].scale, 3.5);
161  EXPECT_EQ(events[0].rotation, 4.5);
162  EXPECT_EQ(events[0].device_kind, kFlutterPointerDeviceKindTrackpad);
163  EXPECT_EQ(events[0].buttons, 0);
164 
165  return kSuccess;
166  }));
167 
168  g_autoptr(GError) error = nullptr;
169  EXPECT_TRUE(fl_engine_start(engine, &error));
170  EXPECT_EQ(error, nullptr);
171  fl_engine_send_pointer_pan_zoom_event(engine, 1, 1234567890, 800, 600,
172  kPanZoomUpdate, 1.5, 2.5, 3.5, 4.5);
173 
174  EXPECT_TRUE(called);
175 }
176 
177 // Checks dispatching a semantics action works.
178 TEST(FlEngineTest, DispatchSemanticsAction) {
179  g_autoptr(FlDartProject) project = fl_dart_project_new();
180  g_autoptr(FlEngine) engine = fl_engine_new(project);
181 
182  bool called = false;
183  fl_engine_get_embedder_api(engine)->DispatchSemanticsAction =
184  MOCK_ENGINE_PROC(
185  DispatchSemanticsAction,
186  ([&called](auto engine, uint64_t id, FlutterSemanticsAction action,
187  const uint8_t* data, size_t data_length) {
188  EXPECT_EQ(id, static_cast<uint64_t>(42));
189  EXPECT_EQ(action, kFlutterSemanticsActionTap);
190  EXPECT_EQ(data_length, static_cast<size_t>(4));
191  EXPECT_EQ(data[0], 't');
192  EXPECT_EQ(data[1], 'e');
193  EXPECT_EQ(data[2], 's');
194  EXPECT_EQ(data[3], 't');
195  called = true;
196 
197  return kSuccess;
198  }));
199 
200  g_autoptr(GError) error = nullptr;
201  EXPECT_TRUE(fl_engine_start(engine, &error));
202  EXPECT_EQ(error, nullptr);
203  g_autoptr(GBytes) data = g_bytes_new_static("test", 4);
204  fl_engine_dispatch_semantics_action(engine, 42, kFlutterSemanticsActionTap,
205  data);
206 
207  EXPECT_TRUE(called);
208 }
209 
210 // Checks sending platform messages works.
211 TEST(FlEngineTest, PlatformMessage) {
212  g_autoptr(FlDartProject) project = fl_dart_project_new();
213  g_autoptr(FlEngine) engine = fl_engine_new(project);
214 
215  bool called = false;
216  FlutterEngineSendPlatformMessageFnPtr old_handler =
217  fl_engine_get_embedder_api(engine)->SendPlatformMessage;
218  fl_engine_get_embedder_api(engine)->SendPlatformMessage = MOCK_ENGINE_PROC(
219  SendPlatformMessage,
220  ([&called, old_handler](auto engine,
221  const FlutterPlatformMessage* message) {
222  if (strcmp(message->channel, "test") != 0) {
223  return old_handler(engine, message);
224  }
225 
226  called = true;
227 
228  EXPECT_EQ(message->message_size, static_cast<size_t>(4));
229  EXPECT_EQ(message->message[0], 't');
230  EXPECT_EQ(message->message[1], 'e');
231  EXPECT_EQ(message->message[2], 's');
232  EXPECT_EQ(message->message[3], 't');
233 
234  return kSuccess;
235  }));
236 
237  g_autoptr(GError) error = nullptr;
238  EXPECT_TRUE(fl_engine_start(engine, &error));
239  EXPECT_EQ(error, nullptr);
240  g_autoptr(GBytes) message = g_bytes_new_static("test", 4);
241  fl_engine_send_platform_message(engine, "test", message, nullptr, nullptr,
242  nullptr);
243 
244  EXPECT_TRUE(called);
245 }
246 
247 // Checks sending platform message responses works.
248 TEST(FlEngineTest, PlatformMessageResponse) {
249  g_autoptr(FlDartProject) project = fl_dart_project_new();
250  g_autoptr(FlEngine) engine = fl_engine_new(project);
251 
252  bool called = false;
253  fl_engine_get_embedder_api(engine)->SendPlatformMessageResponse =
254  MOCK_ENGINE_PROC(
255  SendPlatformMessageResponse,
256  ([&called](auto engine,
257  const FlutterPlatformMessageResponseHandle* handle,
258  const uint8_t* data, size_t data_length) {
259  called = true;
260 
261  EXPECT_EQ(
262  handle,
263  reinterpret_cast<const FlutterPlatformMessageResponseHandle*>(
264  42));
265  EXPECT_EQ(data_length, static_cast<size_t>(4));
266  EXPECT_EQ(data[0], 't');
267  EXPECT_EQ(data[1], 'e');
268  EXPECT_EQ(data[2], 's');
269  EXPECT_EQ(data[3], 't');
270 
271  return kSuccess;
272  }));
273 
274  g_autoptr(GError) error = nullptr;
275  EXPECT_TRUE(fl_engine_start(engine, &error));
276  EXPECT_EQ(error, nullptr);
277  g_autoptr(GBytes) response = g_bytes_new_static("test", 4);
279  engine, reinterpret_cast<const FlutterPlatformMessageResponseHandle*>(42),
280  response, &error));
281  EXPECT_EQ(error, nullptr);
282 
283  EXPECT_TRUE(called);
284 }
285 
286 // Checks settings handler sends settings on startup.
287 TEST(FlEngineTest, SettingsHandler) {
288  g_autoptr(FlDartProject) project = fl_dart_project_new();
289  g_autoptr(FlEngine) engine = fl_engine_new(project);
290 
291  bool called = false;
292  fl_engine_get_embedder_api(engine)->SendPlatformMessage = MOCK_ENGINE_PROC(
293  SendPlatformMessage,
294  ([&called](auto engine, const FlutterPlatformMessage* message) {
295  called = true;
296 
297  EXPECT_STREQ(message->channel, "flutter/settings");
298 
299  g_autoptr(FlJsonMessageCodec) codec = fl_json_message_codec_new();
300  g_autoptr(GBytes) data =
301  g_bytes_new(message->message, message->message_size);
302  g_autoptr(GError) error = nullptr;
303  g_autoptr(FlValue) settings = fl_message_codec_decode_message(
304  FL_MESSAGE_CODEC(codec), data, &error);
305  EXPECT_NE(settings, nullptr);
306  EXPECT_EQ(error, nullptr);
307 
308  FlValue* text_scale_factor =
309  fl_value_lookup_string(settings, "textScaleFactor");
310  EXPECT_NE(text_scale_factor, nullptr);
311  EXPECT_EQ(fl_value_get_type(text_scale_factor), FL_VALUE_TYPE_FLOAT);
312 
313  FlValue* always_use_24hr_format =
314  fl_value_lookup_string(settings, "alwaysUse24HourFormat");
315  EXPECT_NE(always_use_24hr_format, nullptr);
316  EXPECT_EQ(fl_value_get_type(always_use_24hr_format),
318 
319  FlValue* platform_brightness =
320  fl_value_lookup_string(settings, "platformBrightness");
321  EXPECT_NE(platform_brightness, nullptr);
322  EXPECT_EQ(fl_value_get_type(platform_brightness), FL_VALUE_TYPE_STRING);
323 
324  return kSuccess;
325  }));
326 
327  g_autoptr(GError) error = nullptr;
328  EXPECT_TRUE(fl_engine_start(engine, &error));
329  EXPECT_EQ(error, nullptr);
330 
331  EXPECT_TRUE(called);
332 }
333 
334 void on_pre_engine_restart_cb(FlEngine* engine, gpointer user_data) {
335  int* count = reinterpret_cast<int*>(user_data);
336  *count += 1;
337 }
338 
339 // Checks restarting the engine invokes the correct callback.
340 TEST(FlEngineTest, OnPreEngineRestart) {
341  g_autoptr(FlDartProject) project = fl_dart_project_new();
342  g_autoptr(FlEngine) engine = fl_engine_new(project);
343 
344  OnPreEngineRestartCallback callback;
345  void* callback_user_data;
346 
347  bool called = false;
348  fl_engine_get_embedder_api(engine)->Initialize = MOCK_ENGINE_PROC(
349  Initialize, ([&callback, &callback_user_data, &called](
350  size_t version, const FlutterRendererConfig* config,
351  const FlutterProjectArgs* args, void* user_data,
352  FLUTTER_API_SYMBOL(FlutterEngine) * engine_out) {
353  called = true;
354  callback = args->on_pre_engine_restart_callback;
355  callback_user_data = user_data;
356 
357  return kSuccess;
358  }));
359  fl_engine_get_embedder_api(engine)->RunInitialized =
360  MOCK_ENGINE_PROC(RunInitialized, ([](auto engine) { return kSuccess; }));
361 
362  g_autoptr(GError) error = nullptr;
363  EXPECT_TRUE(fl_engine_start(engine, &error));
364  EXPECT_EQ(error, nullptr);
365 
366  EXPECT_TRUE(called);
367  EXPECT_NE(callback, nullptr);
368 
369  // The following call has no effect but should not crash.
370  callback(callback_user_data);
371 
372  int count = 0;
373 
374  // Set handler so that:
375  //
376  // * When the engine restarts, count += 1;
377  // * When the engine is freed, count += 10.
378  g_signal_connect(engine, "on-pre-engine-restart",
379  G_CALLBACK(on_pre_engine_restart_cb), &count);
380 
381  callback(callback_user_data);
382  EXPECT_EQ(count, 1);
383 }
384 
385 TEST(FlEngineTest, DartEntrypointArgs) {
386  GPtrArray* args_array = g_ptr_array_new();
387  g_ptr_array_add(args_array, const_cast<char*>("arg_one"));
388  g_ptr_array_add(args_array, const_cast<char*>("arg_two"));
389  g_ptr_array_add(args_array, const_cast<char*>("arg_three"));
390  g_ptr_array_add(args_array, nullptr);
391  gchar** args = reinterpret_cast<gchar**>(g_ptr_array_free(args_array, false));
392 
393  g_autoptr(FlDartProject) project = fl_dart_project_new();
395  g_autoptr(FlEngine) engine = fl_engine_new(project);
396 
397  bool called = false;
398  fl_engine_get_embedder_api(engine)->Initialize = MOCK_ENGINE_PROC(
399  Initialize, ([&called, &set_args = args](
400  size_t version, const FlutterRendererConfig* config,
401  const FlutterProjectArgs* args, void* user_data,
402  FLUTTER_API_SYMBOL(FlutterEngine) * engine_out) {
403  called = true;
404  EXPECT_NE(set_args, args->dart_entrypoint_argv);
405  EXPECT_EQ(args->dart_entrypoint_argc, 3);
406 
407  return kSuccess;
408  }));
409  fl_engine_get_embedder_api(engine)->RunInitialized =
410  MOCK_ENGINE_PROC(RunInitialized, ([](auto engine) { return kSuccess; }));
411 
412  g_autoptr(GError) error = nullptr;
413  EXPECT_TRUE(fl_engine_start(engine, &error));
414  EXPECT_EQ(error, nullptr);
415 
416  EXPECT_TRUE(called);
417 }
418 
419 TEST(FlEngineTest, Locales) {
420  g_autofree gchar* initial_language = g_strdup(g_getenv("LANGUAGE"));
421  g_setenv("LANGUAGE", "de:en_US", TRUE);
422  g_autoptr(FlDartProject) project = fl_dart_project_new();
423 
424  g_autoptr(FlEngine) engine = fl_engine_new(project);
425 
426  bool called = false;
427  fl_engine_get_embedder_api(engine)->UpdateLocales = MOCK_ENGINE_PROC(
428  UpdateLocales, ([&called](auto engine, const FlutterLocale** locales,
429  size_t locales_count) {
430  called = true;
431 
432  EXPECT_EQ(locales_count, static_cast<size_t>(4));
433 
434  EXPECT_STREQ(locales[0]->language_code, "de");
435  EXPECT_STREQ(locales[0]->country_code, nullptr);
436  EXPECT_STREQ(locales[0]->script_code, nullptr);
437  EXPECT_STREQ(locales[0]->variant_code, nullptr);
438 
439  EXPECT_STREQ(locales[1]->language_code, "en");
440  EXPECT_STREQ(locales[1]->country_code, "US");
441  EXPECT_STREQ(locales[1]->script_code, nullptr);
442  EXPECT_STREQ(locales[1]->variant_code, nullptr);
443 
444  EXPECT_STREQ(locales[2]->language_code, "en");
445  EXPECT_STREQ(locales[2]->country_code, nullptr);
446  EXPECT_STREQ(locales[2]->script_code, nullptr);
447  EXPECT_STREQ(locales[2]->variant_code, nullptr);
448 
449  EXPECT_STREQ(locales[3]->language_code, "C");
450  EXPECT_STREQ(locales[3]->country_code, nullptr);
451  EXPECT_STREQ(locales[3]->script_code, nullptr);
452  EXPECT_STREQ(locales[3]->variant_code, nullptr);
453 
454  return kSuccess;
455  }));
456 
457  g_autoptr(GError) error = nullptr;
458  EXPECT_TRUE(fl_engine_start(engine, &error));
459  EXPECT_EQ(error, nullptr);
460 
461  EXPECT_TRUE(called);
462 
463  if (initial_language) {
464  g_setenv("LANGUAGE", initial_language, TRUE);
465  } else {
466  g_unsetenv("LANGUAGE");
467  }
468 }
469 
470 TEST(FlEngineTest, CLocale) {
471  g_autofree gchar* initial_language = g_strdup(g_getenv("LANGUAGE"));
472  g_setenv("LANGUAGE", "C", TRUE);
473  g_autoptr(FlDartProject) project = fl_dart_project_new();
474 
475  g_autoptr(FlEngine) engine = fl_engine_new(project);
476 
477  bool called = false;
478  fl_engine_get_embedder_api(engine)->UpdateLocales = MOCK_ENGINE_PROC(
479  UpdateLocales, ([&called](auto engine, const FlutterLocale** locales,
480  size_t locales_count) {
481  called = true;
482 
483  EXPECT_EQ(locales_count, static_cast<size_t>(1));
484 
485  EXPECT_STREQ(locales[0]->language_code, "C");
486  EXPECT_STREQ(locales[0]->country_code, nullptr);
487  EXPECT_STREQ(locales[0]->script_code, nullptr);
488  EXPECT_STREQ(locales[0]->variant_code, nullptr);
489 
490  return kSuccess;
491  }));
492 
493  g_autoptr(GError) error = nullptr;
494  EXPECT_TRUE(fl_engine_start(engine, &error));
495  EXPECT_EQ(error, nullptr);
496 
497  EXPECT_TRUE(called);
498 
499  if (initial_language) {
500  g_setenv("LANGUAGE", initial_language, TRUE);
501  } else {
502  g_unsetenv("LANGUAGE");
503  }
504 }
505 
506 TEST(FlEngineTest, DuplicateLocale) {
507  g_autofree gchar* initial_language = g_strdup(g_getenv("LANGUAGE"));
508  g_setenv("LANGUAGE", "en:en", TRUE);
509  g_autoptr(FlDartProject) project = fl_dart_project_new();
510 
511  g_autoptr(FlEngine) engine = fl_engine_new(project);
512 
513  bool called = false;
514  fl_engine_get_embedder_api(engine)->UpdateLocales = MOCK_ENGINE_PROC(
515  UpdateLocales, ([&called](auto engine, const FlutterLocale** locales,
516  size_t locales_count) {
517  called = true;
518 
519  EXPECT_EQ(locales_count, static_cast<size_t>(2));
520 
521  EXPECT_STREQ(locales[0]->language_code, "en");
522  EXPECT_STREQ(locales[0]->country_code, nullptr);
523  EXPECT_STREQ(locales[0]->script_code, nullptr);
524  EXPECT_STREQ(locales[0]->variant_code, nullptr);
525 
526  EXPECT_STREQ(locales[1]->language_code, "C");
527  EXPECT_STREQ(locales[1]->country_code, nullptr);
528  EXPECT_STREQ(locales[1]->script_code, nullptr);
529  EXPECT_STREQ(locales[1]->variant_code, nullptr);
530 
531  return kSuccess;
532  }));
533 
534  g_autoptr(GError) error = nullptr;
535  EXPECT_TRUE(fl_engine_start(engine, &error));
536  EXPECT_EQ(error, nullptr);
537 
538  EXPECT_TRUE(called);
539 
540  if (initial_language) {
541  g_setenv("LANGUAGE", initial_language, TRUE);
542  } else {
543  g_unsetenv("LANGUAGE");
544  }
545 }
546 
547 TEST(FlEngineTest, EmptyLocales) {
548  g_autofree gchar* initial_language = g_strdup(g_getenv("LANGUAGE"));
549  g_setenv("LANGUAGE", "de:: :en_US", TRUE);
550  g_autoptr(FlDartProject) project = fl_dart_project_new();
551 
552  g_autoptr(FlEngine) engine = fl_engine_new(project);
553 
554  bool called = false;
555  fl_engine_get_embedder_api(engine)->UpdateLocales = MOCK_ENGINE_PROC(
556  UpdateLocales, ([&called](auto engine, const FlutterLocale** locales,
557  size_t locales_count) {
558  called = true;
559 
560  EXPECT_EQ(locales_count, static_cast<size_t>(4));
561 
562  EXPECT_STREQ(locales[0]->language_code, "de");
563  EXPECT_STREQ(locales[0]->country_code, nullptr);
564  EXPECT_STREQ(locales[0]->script_code, nullptr);
565  EXPECT_STREQ(locales[0]->variant_code, nullptr);
566 
567  EXPECT_STREQ(locales[1]->language_code, "en");
568  EXPECT_STREQ(locales[1]->country_code, "US");
569  EXPECT_STREQ(locales[1]->script_code, nullptr);
570  EXPECT_STREQ(locales[1]->variant_code, nullptr);
571 
572  EXPECT_STREQ(locales[2]->language_code, "en");
573  EXPECT_STREQ(locales[2]->country_code, nullptr);
574  EXPECT_STREQ(locales[2]->script_code, nullptr);
575  EXPECT_STREQ(locales[2]->variant_code, nullptr);
576 
577  EXPECT_STREQ(locales[3]->language_code, "C");
578  EXPECT_STREQ(locales[3]->country_code, nullptr);
579  EXPECT_STREQ(locales[3]->script_code, nullptr);
580  EXPECT_STREQ(locales[3]->variant_code, nullptr);
581 
582  return kSuccess;
583  }));
584 
585  g_autoptr(GError) error = nullptr;
586  EXPECT_TRUE(fl_engine_start(engine, &error));
587  EXPECT_EQ(error, nullptr);
588 
589  EXPECT_TRUE(called);
590 
591  if (initial_language) {
592  g_setenv("LANGUAGE", initial_language, TRUE);
593  } else {
594  g_unsetenv("LANGUAGE");
595  }
596 }
597 
598 static void add_view_cb(GObject* object,
599  GAsyncResult* result,
600  gpointer user_data) {
601  g_autoptr(GError) error = nullptr;
602  gboolean r = fl_engine_add_view_finish(FL_ENGINE(object), result, &error);
603  EXPECT_TRUE(r);
604  EXPECT_EQ(error, nullptr);
605 
606  g_main_loop_quit(static_cast<GMainLoop*>(user_data));
607 }
608 
609 TEST(FlEngineTest, AddView) {
610  g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0);
611 
612  g_autoptr(FlDartProject) project = fl_dart_project_new();
613  g_autoptr(FlEngine) engine = fl_engine_new(project);
614 
615  bool called = false;
616  fl_engine_get_embedder_api(engine)->AddView = MOCK_ENGINE_PROC(
617  AddView, ([&called](auto engine, const FlutterAddViewInfo* info) {
618  called = true;
619  EXPECT_EQ(info->view_metrics->width, 123u);
620  EXPECT_EQ(info->view_metrics->height, 456u);
621  EXPECT_EQ(info->view_metrics->pixel_ratio, 2.0);
622 
623  FlutterAddViewResult result;
624  result.struct_size = sizeof(FlutterAddViewResult);
625  result.added = true;
626  result.user_data = info->user_data;
627  info->add_view_callback(&result);
628 
629  return kSuccess;
630  }));
631 
632  FlutterViewId view_id =
633  fl_engine_add_view(engine, 123, 456, 2.0, nullptr, add_view_cb, loop);
634  EXPECT_GT(view_id, 0);
635  EXPECT_TRUE(called);
636 
637  // Blocks here until add_view_cb is called.
638  g_main_loop_run(loop);
639 }
640 
641 static void add_view_error_cb(GObject* object,
642  GAsyncResult* result,
643  gpointer user_data) {
644  g_autoptr(GError) error = nullptr;
645  gboolean r = fl_engine_add_view_finish(FL_ENGINE(object), result, &error);
646  EXPECT_FALSE(r);
647  EXPECT_NE(error, nullptr);
648 
649  g_main_loop_quit(static_cast<GMainLoop*>(user_data));
650 }
651 
652 TEST(FlEngineTest, AddViewError) {
653  g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0);
654 
655  g_autoptr(FlDartProject) project = fl_dart_project_new();
656  g_autoptr(FlEngine) engine = fl_engine_new(project);
657 
658  fl_engine_get_embedder_api(engine)->AddView = MOCK_ENGINE_PROC(
659  AddView, ([](auto engine, const FlutterAddViewInfo* info) {
660  FlutterAddViewResult result;
661  result.struct_size = sizeof(FlutterAddViewResult);
662  result.added = false;
663  result.user_data = info->user_data;
664  info->add_view_callback(&result);
665 
666  return kSuccess;
667  }));
668 
669  FlutterViewId view_id = fl_engine_add_view(engine, 123, 456, 2.0, nullptr,
670  add_view_error_cb, loop);
671  EXPECT_GT(view_id, 0);
672 
673  // Blocks here until add_view_error_cb is called.
674  g_main_loop_run(loop);
675 }
676 
677 static void add_view_engine_error_cb(GObject* object,
678  GAsyncResult* result,
679  gpointer user_data) {
680  g_autoptr(GError) error = nullptr;
681  gboolean r = fl_engine_add_view_finish(FL_ENGINE(object), result, &error);
682  EXPECT_FALSE(r);
683  EXPECT_NE(error, nullptr);
684 
685  g_main_loop_quit(static_cast<GMainLoop*>(user_data));
686 }
687 
688 TEST(FlEngineTest, AddViewEngineError) {
689  g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0);
690 
691  g_autoptr(FlDartProject) project = fl_dart_project_new();
692  g_autoptr(FlEngine) engine = fl_engine_new(project);
693 
694  fl_engine_get_embedder_api(engine)->AddView = MOCK_ENGINE_PROC(
695  AddView, ([](auto engine, const FlutterAddViewInfo* info) {
696  return kInvalidArguments;
697  }));
698 
699  FlutterViewId view_id = fl_engine_add_view(engine, 123, 456, 2.0, nullptr,
701  EXPECT_GT(view_id, 0);
702 
703  // Blocks here until remove_view_engine_error_cb is called.
704  g_main_loop_run(loop);
705 }
706 
707 static void remove_view_cb(GObject* object,
708  GAsyncResult* result,
709  gpointer user_data) {
710  g_autoptr(GError) error = nullptr;
711  gboolean r = fl_engine_remove_view_finish(FL_ENGINE(object), result, &error);
712  EXPECT_TRUE(r);
713  EXPECT_EQ(error, nullptr);
714 
715  g_main_loop_quit(static_cast<GMainLoop*>(user_data));
716 }
717 
718 TEST(FlEngineTest, RemoveView) {
719  g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0);
720 
721  g_autoptr(FlDartProject) project = fl_dart_project_new();
722  g_autoptr(FlEngine) engine = fl_engine_new(project);
723 
724  bool called = false;
725  fl_engine_get_embedder_api(engine)->RemoveView = MOCK_ENGINE_PROC(
726  RemoveView, ([&called](auto engine, const FlutterRemoveViewInfo* info) {
727  called = true;
728  EXPECT_EQ(info->view_id, 123);
729 
730  FlutterRemoveViewResult result;
731  result.struct_size = sizeof(FlutterRemoveViewResult);
732  result.removed = true;
733  result.user_data = info->user_data;
734  info->remove_view_callback(&result);
735 
736  return kSuccess;
737  }));
738 
739  fl_engine_remove_view(engine, 123, nullptr, remove_view_cb, loop);
740  EXPECT_TRUE(called);
741 
742  // Blocks here until remove_view_cb is called.
743  g_main_loop_run(loop);
744 }
745 
746 static void remove_view_error_cb(GObject* object,
747  GAsyncResult* result,
748  gpointer user_data) {
749  g_autoptr(GError) error = nullptr;
750  gboolean r = fl_engine_remove_view_finish(FL_ENGINE(object), result, &error);
751  EXPECT_FALSE(r);
752  EXPECT_NE(error, nullptr);
753 
754  g_main_loop_quit(static_cast<GMainLoop*>(user_data));
755 }
756 
757 TEST(FlEngineTest, RemoveViewError) {
758  g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0);
759 
760  g_autoptr(FlDartProject) project = fl_dart_project_new();
761  g_autoptr(FlEngine) engine = fl_engine_new(project);
762 
763  fl_engine_get_embedder_api(engine)->RemoveView = MOCK_ENGINE_PROC(
764  RemoveView, ([](auto engine, const FlutterRemoveViewInfo* info) {
765  FlutterRemoveViewResult result;
766  result.struct_size = sizeof(FlutterRemoveViewResult);
767  result.removed = false;
768  result.user_data = info->user_data;
769  info->remove_view_callback(&result);
770 
771  return kSuccess;
772  }));
773 
774  fl_engine_remove_view(engine, 123, nullptr, remove_view_error_cb, loop);
775 
776  // Blocks here until remove_view_error_cb is called.
777  g_main_loop_run(loop);
778 }
779 
780 static void remove_view_engine_error_cb(GObject* object,
781  GAsyncResult* result,
782  gpointer user_data) {
783  g_autoptr(GError) error = nullptr;
784  gboolean r = fl_engine_remove_view_finish(FL_ENGINE(object), result, &error);
785  EXPECT_FALSE(r);
786  EXPECT_NE(error, nullptr);
787 
788  g_main_loop_quit(static_cast<GMainLoop*>(user_data));
789 }
790 
791 TEST(FlEngineTest, RemoveViewEngineError) {
792  g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0);
793 
794  g_autoptr(FlDartProject) project = fl_dart_project_new();
795  g_autoptr(FlEngine) engine = fl_engine_new(project);
796 
797  fl_engine_get_embedder_api(engine)->RemoveView = MOCK_ENGINE_PROC(
798  RemoveView, ([](auto engine, const FlutterRemoveViewInfo* info) {
799  return kInvalidArguments;
800  }));
801 
803  loop);
804 
805  // Blocks here until remove_view_engine_error_cb is called.
806  g_main_loop_run(loop);
807 }
808 
809 TEST(FlEngineTest, SendKeyEvent) {
810  g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0);
811 
812  g_autoptr(FlDartProject) project = fl_dart_project_new();
813  g_autoptr(FlEngine) engine = fl_engine_new(project);
814 
815  g_autoptr(GError) error = nullptr;
816  EXPECT_TRUE(fl_engine_start(engine, &error));
817  EXPECT_EQ(error, nullptr);
818 
819  bool called;
820  fl_engine_get_embedder_api(engine)->SendKeyEvent = MOCK_ENGINE_PROC(
821  SendKeyEvent,
822  ([&called](auto engine, const FlutterKeyEvent* event,
823  FlutterKeyEventCallback callback, void* user_data) {
824  called = true;
825  EXPECT_EQ(event->timestamp, 1234);
826  EXPECT_EQ(event->type, kFlutterKeyEventTypeUp);
827  EXPECT_EQ(event->physical, static_cast<uint64_t>(42));
828  EXPECT_EQ(event->logical, static_cast<uint64_t>(123));
829  EXPECT_TRUE(event->synthesized);
830  EXPECT_EQ(event->device_type, kFlutterKeyEventDeviceTypeKeyboard);
831  callback(TRUE, user_data);
832  return kSuccess;
833  }));
834 
835  FlutterKeyEvent event = {.struct_size = sizeof(FlutterKeyEvent),
836  .timestamp = 1234,
837  .type = kFlutterKeyEventTypeUp,
838  .physical = 42,
839  .logical = 123,
840  .character = nullptr,
841  .synthesized = true,
842  .device_type = kFlutterKeyEventDeviceTypeKeyboard};
844  engine, &event, nullptr,
845  [](GObject* object, GAsyncResult* result, gpointer user_data) {
846  gboolean handled;
847  g_autoptr(GError) error = nullptr;
848  EXPECT_TRUE(fl_engine_send_key_event_finish(FL_ENGINE(object), result,
849  &handled, &error));
850  EXPECT_EQ(error, nullptr);
851  EXPECT_TRUE(handled);
852  g_main_loop_quit(static_cast<GMainLoop*>(user_data));
853  },
854  loop);
855 
856  g_main_loop_run(loop);
857  EXPECT_TRUE(called);
858 }
859 
860 TEST(FlEngineTest, SendKeyEventNotHandled) {
861  g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0);
862 
863  g_autoptr(FlDartProject) project = fl_dart_project_new();
864  g_autoptr(FlEngine) engine = fl_engine_new(project);
865 
866  g_autoptr(GError) error = nullptr;
867  EXPECT_TRUE(fl_engine_start(engine, &error));
868  EXPECT_EQ(error, nullptr);
869 
870  bool called;
871  fl_engine_get_embedder_api(engine)->SendKeyEvent = MOCK_ENGINE_PROC(
872  SendKeyEvent,
873  ([&called](auto engine, const FlutterKeyEvent* event,
874  FlutterKeyEventCallback callback, void* user_data) {
875  called = true;
876  callback(FALSE, user_data);
877  return kSuccess;
878  }));
879 
880  FlutterKeyEvent event = {.struct_size = sizeof(FlutterKeyEvent),
881  .timestamp = 1234,
882  .type = kFlutterKeyEventTypeUp,
883  .physical = 42,
884  .logical = 123,
885  .character = nullptr,
886  .synthesized = true,
887  .device_type = kFlutterKeyEventDeviceTypeKeyboard};
889  engine, &event, nullptr,
890  [](GObject* object, GAsyncResult* result, gpointer user_data) {
891  gboolean handled;
892  g_autoptr(GError) error = nullptr;
893  EXPECT_TRUE(fl_engine_send_key_event_finish(FL_ENGINE(object), result,
894  &handled, &error));
895  EXPECT_EQ(error, nullptr);
896  EXPECT_FALSE(handled);
897  g_main_loop_quit(static_cast<GMainLoop*>(user_data));
898  },
899  loop);
900 
901  g_main_loop_run(loop);
902  EXPECT_TRUE(called);
903 }
904 
905 TEST(FlEngineTest, SendKeyEventError) {
906  g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0);
907 
908  g_autoptr(FlDartProject) project = fl_dart_project_new();
909  g_autoptr(FlEngine) engine = fl_engine_new(project);
910 
911  g_autoptr(GError) error = nullptr;
912  EXPECT_TRUE(fl_engine_start(engine, &error));
913  EXPECT_EQ(error, nullptr);
914 
915  bool called;
916  fl_engine_get_embedder_api(engine)->SendKeyEvent = MOCK_ENGINE_PROC(
917  SendKeyEvent,
918  ([&called](auto engine, const FlutterKeyEvent* event,
919  FlutterKeyEventCallback callback, void* user_data) {
920  called = true;
921  return kInvalidArguments;
922  }));
923 
924  FlutterKeyEvent event = {.struct_size = sizeof(FlutterKeyEvent),
925  .timestamp = 1234,
926  .type = kFlutterKeyEventTypeUp,
927  .physical = 42,
928  .logical = 123,
929  .character = nullptr,
930  .synthesized = true,
931  .device_type = kFlutterKeyEventDeviceTypeKeyboard};
933  engine, &event, nullptr,
934  [](GObject* object, GAsyncResult* result, gpointer user_data) {
935  gboolean handled;
936  g_autoptr(GError) error = nullptr;
937  EXPECT_FALSE(fl_engine_send_key_event_finish(FL_ENGINE(object), result,
938  &handled, &error));
939  EXPECT_NE(error, nullptr);
940  EXPECT_STREQ(error->message, "Failed to send key event");
941  g_main_loop_quit(static_cast<GMainLoop*>(user_data));
942  },
943  loop);
944 
945  g_main_loop_run(loop);
946  EXPECT_TRUE(called);
947 }
948 
949 // NOLINTEND(clang-analyzer-core.StackAddressEscape)
G_MODULE_EXPORT FlDartProject * fl_dart_project_new()
G_MODULE_EXPORT void fl_dart_project_set_dart_entrypoint_arguments(FlDartProject *self, char **argv)
void fl_engine_send_mouse_pointer_event(FlEngine *self, FlutterViewId view_id, FlutterPointerPhase phase, size_t timestamp, double x, double y, FlutterPointerDeviceKind device_kind, double scroll_delta_x, double scroll_delta_y, int64_t buttons)
Definition: fl_engine.cc:918
gboolean fl_engine_send_key_event_finish(FlEngine *self, GAsyncResult *result, gboolean *handled, GError **error)
Definition: fl_engine.cc:1144
FlutterEngineProcTable * fl_engine_get_embedder_api(FlEngine *self)
Definition: fl_engine.cc:661
void fl_engine_notify_display_update(FlEngine *self, const FlutterEngineDisplay *displays, size_t displays_length)
Definition: fl_engine.cc:665
void fl_engine_send_window_metrics_event(FlEngine *self, FlutterEngineDisplayId display_id, FlutterViewId view_id, size_t width, size_t height, double pixel_ratio)
Definition: fl_engine.cc:896
gboolean fl_engine_remove_view_finish(FlEngine *self, GAsyncResult *result, GError **error)
Definition: fl_engine.cc:753
gboolean fl_engine_send_platform_message_response(FlEngine *self, const FlutterPlatformMessageResponseHandle *handle, GBytes *response, GError **error)
Definition: fl_engine.cc:796
void fl_engine_send_platform_message(FlEngine *self, const gchar *channel, GBytes *message, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data)
Definition: fl_engine.cc:828
void fl_engine_remove_view(FlEngine *self, FlutterViewId view_id, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data)
Definition: fl_engine.cc:728
FlutterViewId fl_engine_add_view(FlEngine *self, size_t width, size_t height, double pixel_ratio, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data)
Definition: fl_engine.cc:678
gboolean fl_engine_add_view_finish(FlEngine *self, GAsyncResult *result, GError **error)
Definition: fl_engine.cc:721
void fl_engine_send_key_event(FlEngine *self, const FlutterKeyEvent *event, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data)
Definition: fl_engine.cc:1121
void fl_engine_dispatch_semantics_action(FlEngine *self, uint64_t id, FlutterSemanticsAction action, GBytes *data)
Definition: fl_engine.cc:1161
void fl_engine_send_pointer_pan_zoom_event(FlEngine *self, FlutterViewId view_id, size_t timestamp, double x, double y, FlutterPointerPhase phase, double pan_x, double pan_y, double scale, double rotation)
Definition: fl_engine.cc:1082
gboolean fl_engine_start(FlEngine *self, GError **error)
Definition: fl_engine.cc:544
G_MODULE_EXPORT FlEngine * fl_engine_new(FlDartProject *project)
Definition: fl_engine.cc:524
void on_pre_engine_restart_cb(FlEngine *engine, gpointer user_data)
static void remove_view_engine_error_cb(GObject *object, GAsyncResult *result, gpointer user_data)
TEST(FlEngineTest, NotifyDisplayUpdate)
static void remove_view_cb(GObject *object, GAsyncResult *result, gpointer user_data)
static void add_view_error_cb(GObject *object, GAsyncResult *result, gpointer user_data)
static void add_view_engine_error_cb(GObject *object, GAsyncResult *result, gpointer user_data)
static void remove_view_error_cb(GObject *object, GAsyncResult *result, gpointer user_data)
static void add_view_cb(GObject *object, GAsyncResult *result, gpointer user_data)
G_BEGIN_DECLS G_MODULE_EXPORT FlValue * args
G_BEGIN_DECLS G_MODULE_EXPORT FlValue gpointer user_data
G_MODULE_EXPORT FlJsonMessageCodec * fl_json_message_codec_new()
G_MODULE_EXPORT FlValue * fl_message_codec_decode_message(FlMessageCodec *self, GBytes *message, GError **error)
const uint8_t uint32_t uint32_t * height
const uint8_t uint32_t * width
const uint8_t uint32_t uint32_t GError ** error
G_MODULE_EXPORT FlValue * fl_value_lookup_string(FlValue *self, const gchar *key)
Definition: fl_value.cc:811
G_MODULE_EXPORT FlValueType fl_value_get_type(FlValue *self)
Definition: fl_value.cc:466
typedefG_BEGIN_DECLS struct _FlValue FlValue
Definition: fl_value.h:42
@ FL_VALUE_TYPE_STRING
Definition: fl_value.h:68
@ FL_VALUE_TYPE_BOOL
Definition: fl_value.h:65
@ FL_VALUE_TYPE_FLOAT
Definition: fl_value.h:67