Flutter macOS Embedder
FlutterEngineTest.mm
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 
7 
8 #include <objc/objc.h>
9 
10 #include <algorithm>
11 #include <functional>
12 #include <thread>
13 #include <vector>
14 
15 #include "flutter/fml/synchronization/waitable_event.h"
16 #include "flutter/lib/ui/window/platform_message.h"
25 #include "flutter/shell/platform/embedder/embedder.h"
26 #include "flutter/shell/platform/embedder/embedder_engine.h"
27 #include "flutter/shell/platform/embedder/test_utils/proc_table_replacement.h"
28 #include "flutter/testing/stream_capture.h"
29 #include "flutter/testing/test_dart_native_resolver.h"
30 #include "gtest/gtest.h"
31 
32 // CREATE_NATIVE_ENTRY and MOCK_ENGINE_PROC are leaky by design
33 // NOLINTBEGIN(clang-analyzer-core.StackAddressEscape)
34 
35 constexpr int64_t kImplicitViewId = 0ll;
36 
38 /**
39  * The FlutterCompositor object currently in use by the FlutterEngine.
40  *
41  * May be nil if the compositor has not been initialized yet.
42  */
43 @property(nonatomic, readonly, nullable) flutter::FlutterCompositor* macOSCompositor;
44 
45 @end
46 
48 @end
49 
50 @implementation TestPlatformViewFactory
51 - (nonnull NSView*)createWithViewIdentifier:(int64_t)viewId arguments:(nullable id)args {
52  return viewId == 42 ? [[NSView alloc] init] : nil;
53 }
54 
55 @end
56 
57 @interface PlainAppDelegate : NSObject <NSApplicationDelegate>
58 @end
59 
60 @implementation PlainAppDelegate
61 - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication* _Nonnull)sender {
62  // Always cancel, so that the test doesn't exit.
63  return NSTerminateCancel;
64 }
65 @end
66 
67 #pragma mark -
68 
69 @interface FakeLifecycleProvider : NSObject <FlutterAppLifecycleProvider, NSApplicationDelegate>
70 
71 @property(nonatomic, strong, readonly) NSPointerArray* registeredDelegates;
72 
73 // True if the given delegate is currently registered.
74 - (BOOL)hasDelegate:(nonnull NSObject<FlutterAppLifecycleDelegate>*)delegate;
75 @end
76 
77 @implementation FakeLifecycleProvider {
78  /**
79  * All currently registered delegates.
80  *
81  * This does not use NSPointerArray or any other weak-pointer
82  * system, because a weak pointer will be nil'd out at the start of dealloc, which will break
83  * queries. E.g., if a delegate is dealloc'd without being unregistered, a weak pointer array
84  * would no longer contain that pointer even though removeApplicationLifecycleDelegate: was never
85  * called, causing tests to pass incorrectly.
86  */
87  std::vector<void*> _delegates;
88 }
89 
90 - (void)addApplicationLifecycleDelegate:(nonnull NSObject<FlutterAppLifecycleDelegate>*)delegate {
91  _delegates.push_back((__bridge void*)delegate);
92 }
93 
94 - (void)removeApplicationLifecycleDelegate:
95  (nonnull NSObject<FlutterAppLifecycleDelegate>*)delegate {
96  auto delegateIndex = std::find(_delegates.begin(), _delegates.end(), (__bridge void*)delegate);
97  NSAssert(delegateIndex != _delegates.end(),
98  @"Attempting to unregister a delegate that was not registered.");
99  _delegates.erase(delegateIndex);
100 }
101 
102 - (BOOL)hasDelegate:(nonnull NSObject<FlutterAppLifecycleDelegate>*)delegate {
103  return std::find(_delegates.begin(), _delegates.end(), (__bridge void*)delegate) !=
104  _delegates.end();
105 }
106 
107 @end
108 
109 #pragma mark -
110 
111 @interface FakeAppDelegatePlugin : NSObject <FlutterPlugin>
112 @end
113 
114 @implementation FakeAppDelegatePlugin
115 + (void)registerWithRegistrar:(id<FlutterPluginRegistrar>)registrar {
116 }
117 @end
118 
119 #pragma mark -
120 
121 namespace flutter::testing {
122 
124  FlutterEngine* engine = GetFlutterEngine();
125  EXPECT_TRUE([engine runWithEntrypoint:@"main"]);
126  ASSERT_TRUE(engine.running);
127 }
128 
129 TEST_F(FlutterEngineTest, HasNonNullExecutableName) {
130  FlutterEngine* engine = GetFlutterEngine();
131  std::string executable_name = [[engine executableName] UTF8String];
132  ASSERT_FALSE(executable_name.empty());
133 
134  // Block until notified by the Dart test of the value of Platform.executable.
135  fml::AutoResetWaitableEvent latch;
136  AddNativeCallback("NotifyStringValue", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
137  const auto dart_string = tonic::DartConverter<std::string>::FromDart(
138  Dart_GetNativeArgument(args, 0));
139  EXPECT_EQ(executable_name, dart_string);
140  latch.Signal();
141  }));
142 
143  // Launch the test entrypoint.
144  EXPECT_TRUE([engine runWithEntrypoint:@"executableNameNotNull"]);
145 
146  latch.Wait();
147 }
148 
149 #ifndef FLUTTER_RELEASE
151  setenv("FLUTTER_ENGINE_SWITCHES", "2", 1);
152  setenv("FLUTTER_ENGINE_SWITCH_1", "abc", 1);
153  setenv("FLUTTER_ENGINE_SWITCH_2", "foo=\"bar, baz\"", 1);
154 
155  FlutterEngine* engine = GetFlutterEngine();
156  std::vector<std::string> switches = engine.switches;
157  ASSERT_EQ(switches.size(), 2UL);
158  EXPECT_EQ(switches[0], "--abc");
159  EXPECT_EQ(switches[1], "--foo=\"bar, baz\"");
160 
161  unsetenv("FLUTTER_ENGINE_SWITCHES");
162  unsetenv("FLUTTER_ENGINE_SWITCH_1");
163  unsetenv("FLUTTER_ENGINE_SWITCH_2");
164 }
165 #endif // !FLUTTER_RELEASE
166 
167 TEST_F(FlutterEngineTest, MessengerSend) {
168  FlutterEngine* engine = GetFlutterEngine();
169  EXPECT_TRUE([engine runWithEntrypoint:@"main"]);
170 
171  NSData* test_message = [@"a message" dataUsingEncoding:NSUTF8StringEncoding];
172  bool called = false;
173 
174  engine.embedderAPI.SendPlatformMessage = MOCK_ENGINE_PROC(
175  SendPlatformMessage, ([&called, test_message](auto engine, auto message) {
176  called = true;
177  EXPECT_STREQ(message->channel, "test");
178  EXPECT_EQ(memcmp(message->message, test_message.bytes, message->message_size), 0);
179  return kSuccess;
180  }));
181 
182  [engine.binaryMessenger sendOnChannel:@"test" message:test_message];
183  EXPECT_TRUE(called);
184 }
185 
186 TEST_F(FlutterEngineTest, CanLogToStdout) {
187  // Block until completion of print statement.
188  fml::AutoResetWaitableEvent latch;
189  AddNativeCallback("SignalNativeTest",
190  CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { latch.Signal(); }));
191 
192  // Replace stdout stream buffer with our own.
193  StreamCapture stdout_capture(&std::cout);
194 
195  // Launch the test entrypoint.
196  FlutterEngine* engine = GetFlutterEngine();
197  EXPECT_TRUE([engine runWithEntrypoint:@"canLogToStdout"]);
198  ASSERT_TRUE(engine.running);
199 
200  latch.Wait();
201 
202  stdout_capture.Stop();
203 
204  // Verify hello world was written to stdout.
205  EXPECT_TRUE(stdout_capture.GetOutput().find("Hello logging") != std::string::npos);
206 }
207 
208 // TODO(cbracken): Needs deflaking. https://github.com/flutter/flutter/issues/124677
209 TEST_F(FlutterEngineTest, DISABLED_BackgroundIsBlack) {
210  FlutterEngine* engine = GetFlutterEngine();
211 
212  // Latch to ensure the entire layer tree has been generated and presented.
213  fml::AutoResetWaitableEvent latch;
214  AddNativeCallback("SignalNativeTest", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
215  CALayer* rootLayer = engine.viewController.flutterView.layer;
216  EXPECT_TRUE(rootLayer.backgroundColor != nil);
217  if (rootLayer.backgroundColor != nil) {
218  NSColor* actualBackgroundColor =
219  [NSColor colorWithCGColor:rootLayer.backgroundColor];
220  EXPECT_EQ(actualBackgroundColor, [NSColor blackColor]);
221  }
222  latch.Signal();
223  }));
224 
225  // Launch the test entrypoint.
226  EXPECT_TRUE([engine runWithEntrypoint:@"backgroundTest"]);
227  ASSERT_TRUE(engine.running);
228 
229  FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine
230  nibName:nil
231  bundle:nil];
232  [viewController loadView];
233  viewController.flutterView.frame = CGRectMake(0, 0, 800, 600);
234 
235  latch.Wait();
236 }
237 
238 // TODO(cbracken): Needs deflaking. https://github.com/flutter/flutter/issues/124677
239 TEST_F(FlutterEngineTest, DISABLED_CanOverrideBackgroundColor) {
240  FlutterEngine* engine = GetFlutterEngine();
241 
242  // Latch to ensure the entire layer tree has been generated and presented.
243  fml::AutoResetWaitableEvent latch;
244  AddNativeCallback("SignalNativeTest", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
245  CALayer* rootLayer = engine.viewController.flutterView.layer;
246  EXPECT_TRUE(rootLayer.backgroundColor != nil);
247  if (rootLayer.backgroundColor != nil) {
248  NSColor* actualBackgroundColor =
249  [NSColor colorWithCGColor:rootLayer.backgroundColor];
250  EXPECT_EQ(actualBackgroundColor, [NSColor whiteColor]);
251  }
252  latch.Signal();
253  }));
254 
255  // Launch the test entrypoint.
256  EXPECT_TRUE([engine runWithEntrypoint:@"backgroundTest"]);
257  ASSERT_TRUE(engine.running);
258 
259  FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine
260  nibName:nil
261  bundle:nil];
262  [viewController loadView];
263  viewController.flutterView.frame = CGRectMake(0, 0, 800, 600);
264  viewController.flutterView.backgroundColor = [NSColor whiteColor];
265 
266  latch.Wait();
267 }
268 
269 TEST_F(FlutterEngineTest, CanToggleAccessibility) {
270  FlutterEngine* engine = GetFlutterEngine();
271  // Capture the update callbacks before the embedder API initializes.
272  auto original_init = engine.embedderAPI.Initialize;
273  std::function<void(const FlutterSemanticsUpdate2*, void*)> update_semantics_callback;
274  engine.embedderAPI.Initialize = MOCK_ENGINE_PROC(
275  Initialize, ([&update_semantics_callback, &original_init](
276  size_t version, const FlutterRendererConfig* config,
277  const FlutterProjectArgs* args, void* user_data, auto engine_out) {
278  update_semantics_callback = args->update_semantics_callback2;
279  return original_init(version, config, args, user_data, engine_out);
280  }));
281  EXPECT_TRUE([engine runWithEntrypoint:@"main"]);
282  // Set up view controller.
283  FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine
284  nibName:nil
285  bundle:nil];
286  [viewController loadView];
287  // Enable the semantics.
288  bool enabled_called = false;
289  engine.embedderAPI.UpdateSemanticsEnabled =
290  MOCK_ENGINE_PROC(UpdateSemanticsEnabled, ([&enabled_called](auto engine, bool enabled) {
291  enabled_called = enabled;
292  return kSuccess;
293  }));
294  engine.semanticsEnabled = YES;
295  EXPECT_TRUE(enabled_called);
296  // Send flutter semantics updates.
297  FlutterSemanticsNode2 root;
298  root.id = 0;
299  root.flags = static_cast<FlutterSemanticsFlag>(0);
300  root.actions = static_cast<FlutterSemanticsAction>(0);
301  root.text_selection_base = -1;
302  root.text_selection_extent = -1;
303  root.label = "root";
304  root.hint = "";
305  root.value = "";
306  root.increased_value = "";
307  root.decreased_value = "";
308  root.tooltip = "";
309  root.child_count = 1;
310  int32_t children[] = {1};
311  root.children_in_traversal_order = children;
312  root.custom_accessibility_actions_count = 0;
313 
314  FlutterSemanticsNode2 child1;
315  child1.id = 1;
316  child1.flags = static_cast<FlutterSemanticsFlag>(0);
317  child1.actions = static_cast<FlutterSemanticsAction>(0);
318  child1.text_selection_base = -1;
319  child1.text_selection_extent = -1;
320  child1.label = "child 1";
321  child1.hint = "";
322  child1.value = "";
323  child1.increased_value = "";
324  child1.decreased_value = "";
325  child1.tooltip = "";
326  child1.child_count = 0;
327  child1.custom_accessibility_actions_count = 0;
328 
329  FlutterSemanticsUpdate2 update;
330  update.node_count = 2;
331  FlutterSemanticsNode2* nodes[] = {&root, &child1};
332  update.nodes = nodes;
333  update.custom_action_count = 0;
334  update_semantics_callback(&update, (__bridge void*)engine);
335 
336  // Verify the accessibility tree is attached to the flutter view.
337  EXPECT_EQ([engine.viewController.flutterView.accessibilityChildren count], 1u);
338  NSAccessibilityElement* native_root = engine.viewController.flutterView.accessibilityChildren[0];
339  std::string root_label = [native_root.accessibilityLabel UTF8String];
340  EXPECT_TRUE(root_label == "root");
341  EXPECT_EQ(native_root.accessibilityRole, NSAccessibilityGroupRole);
342  EXPECT_EQ([native_root.accessibilityChildren count], 1u);
343  NSAccessibilityElement* native_child1 = native_root.accessibilityChildren[0];
344  std::string child1_value = [native_child1.accessibilityValue UTF8String];
345  EXPECT_TRUE(child1_value == "child 1");
346  EXPECT_EQ(native_child1.accessibilityRole, NSAccessibilityStaticTextRole);
347  EXPECT_EQ([native_child1.accessibilityChildren count], 0u);
348  // Disable the semantics.
349  bool semanticsEnabled = true;
350  engine.embedderAPI.UpdateSemanticsEnabled =
351  MOCK_ENGINE_PROC(UpdateSemanticsEnabled, ([&semanticsEnabled](auto engine, bool enabled) {
352  semanticsEnabled = enabled;
353  return kSuccess;
354  }));
355  engine.semanticsEnabled = NO;
356  EXPECT_FALSE(semanticsEnabled);
357  // Verify the accessibility tree is removed from the view.
358  EXPECT_EQ([engine.viewController.flutterView.accessibilityChildren count], 0u);
359 
360  [engine setViewController:nil];
361 }
362 
363 TEST_F(FlutterEngineTest, CanToggleAccessibilityWhenHeadless) {
364  FlutterEngine* engine = GetFlutterEngine();
365  // Capture the update callbacks before the embedder API initializes.
366  auto original_init = engine.embedderAPI.Initialize;
367  std::function<void(const FlutterSemanticsUpdate2*, void*)> update_semantics_callback;
368  engine.embedderAPI.Initialize = MOCK_ENGINE_PROC(
369  Initialize, ([&update_semantics_callback, &original_init](
370  size_t version, const FlutterRendererConfig* config,
371  const FlutterProjectArgs* args, void* user_data, auto engine_out) {
372  update_semantics_callback = args->update_semantics_callback2;
373  return original_init(version, config, args, user_data, engine_out);
374  }));
375  EXPECT_TRUE([engine runWithEntrypoint:@"main"]);
376 
377  // Enable the semantics without attaching a view controller.
378  bool enabled_called = false;
379  engine.embedderAPI.UpdateSemanticsEnabled =
380  MOCK_ENGINE_PROC(UpdateSemanticsEnabled, ([&enabled_called](auto engine, bool enabled) {
381  enabled_called = enabled;
382  return kSuccess;
383  }));
384  engine.semanticsEnabled = YES;
385  EXPECT_TRUE(enabled_called);
386  // Send flutter semantics updates.
387  FlutterSemanticsNode2 root;
388  root.id = 0;
389  root.flags = static_cast<FlutterSemanticsFlag>(0);
390  root.actions = static_cast<FlutterSemanticsAction>(0);
391  root.text_selection_base = -1;
392  root.text_selection_extent = -1;
393  root.label = "root";
394  root.hint = "";
395  root.value = "";
396  root.increased_value = "";
397  root.decreased_value = "";
398  root.tooltip = "";
399  root.child_count = 1;
400  int32_t children[] = {1};
401  root.children_in_traversal_order = children;
402  root.custom_accessibility_actions_count = 0;
403 
404  FlutterSemanticsNode2 child1;
405  child1.id = 1;
406  child1.flags = static_cast<FlutterSemanticsFlag>(0);
407  child1.actions = static_cast<FlutterSemanticsAction>(0);
408  child1.text_selection_base = -1;
409  child1.text_selection_extent = -1;
410  child1.label = "child 1";
411  child1.hint = "";
412  child1.value = "";
413  child1.increased_value = "";
414  child1.decreased_value = "";
415  child1.tooltip = "";
416  child1.child_count = 0;
417  child1.custom_accessibility_actions_count = 0;
418 
419  FlutterSemanticsUpdate2 update;
420  update.node_count = 2;
421  FlutterSemanticsNode2* nodes[] = {&root, &child1};
422  update.nodes = nodes;
423  update.custom_action_count = 0;
424  // This call updates semantics for the implicit view, which does not exist,
425  // and therefore this call is invalid. But the engine should not crash.
426  update_semantics_callback(&update, (__bridge void*)engine);
427 
428  // No crashes.
429  EXPECT_EQ(engine.viewController, nil);
430 
431  // Disable the semantics.
432  bool semanticsEnabled = true;
433  engine.embedderAPI.UpdateSemanticsEnabled =
434  MOCK_ENGINE_PROC(UpdateSemanticsEnabled, ([&semanticsEnabled](auto engine, bool enabled) {
435  semanticsEnabled = enabled;
436  return kSuccess;
437  }));
438  engine.semanticsEnabled = NO;
439  EXPECT_FALSE(semanticsEnabled);
440  // Still no crashes
441  EXPECT_EQ(engine.viewController, nil);
442 }
443 
444 TEST_F(FlutterEngineTest, ProducesAccessibilityTreeWhenAddingViews) {
445  FlutterEngine* engine = GetFlutterEngine();
446  EXPECT_TRUE([engine runWithEntrypoint:@"main"]);
447 
448  // Enable the semantics without attaching a view controller.
449  bool enabled_called = false;
450  engine.embedderAPI.UpdateSemanticsEnabled =
451  MOCK_ENGINE_PROC(UpdateSemanticsEnabled, ([&enabled_called](auto engine, bool enabled) {
452  enabled_called = enabled;
453  return kSuccess;
454  }));
455  engine.semanticsEnabled = YES;
456  EXPECT_TRUE(enabled_called);
457 
458  EXPECT_EQ(engine.viewController, nil);
459 
460  // Assign the view controller after enabling semantics
461  FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine
462  nibName:nil
463  bundle:nil];
464  engine.viewController = viewController;
465 
466  EXPECT_NE(viewController.accessibilityBridge.lock(), nullptr);
467 }
468 
469 TEST_F(FlutterEngineTest, NativeCallbacks) {
470  fml::AutoResetWaitableEvent latch;
471  bool latch_called = false;
472  AddNativeCallback("SignalNativeTest", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
473  latch_called = true;
474  latch.Signal();
475  }));
476 
477  FlutterEngine* engine = GetFlutterEngine();
478  EXPECT_TRUE([engine runWithEntrypoint:@"nativeCallback"]);
479  ASSERT_TRUE(engine.running);
480 
481  latch.Wait();
482  ASSERT_TRUE(latch_called);
483 }
484 
485 TEST_F(FlutterEngineTest, Compositor) {
486  NSString* fixtures = @(flutter::testing::GetFixturesPath());
487  FlutterDartProject* project = [[FlutterDartProject alloc]
488  initWithAssetsPath:fixtures
489  ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]];
490  FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"test" project:project];
491 
492  FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine
493  nibName:nil
494  bundle:nil];
495  [viewController loadView];
496  viewController.flutterView.frame = CGRectMake(0, 0, 800, 600);
497 
498  EXPECT_TRUE([engine runWithEntrypoint:@"canCompositePlatformViews"]);
499 
500  [engine.platformViewController registerViewFactory:[[TestPlatformViewFactory alloc] init]
501  withId:@"factory_id"];
502  [engine.platformViewController
503  handleMethodCall:[FlutterMethodCall methodCallWithMethodName:@"create"
504  arguments:@{
505  @"id" : @(42),
506  @"viewType" : @"factory_id",
507  }]
508  result:^(id result){
509  }];
510 
511  [engine.testThreadSynchronizer blockUntilFrameAvailable];
512 
513  CALayer* rootLayer = viewController.flutterView.layer;
514 
515  // There are two layers with Flutter contents and one view
516  EXPECT_EQ(rootLayer.sublayers.count, 2u);
517  EXPECT_EQ(viewController.flutterView.subviews.count, 1u);
518 
519  // TODO(gw280): add support for screenshot tests in this test harness
520 
521  [engine shutDownEngine];
522 } // namespace flutter::testing
523 
524 TEST_F(FlutterEngineTest, DartEntrypointArguments) {
525  NSString* fixtures = @(flutter::testing::GetFixturesPath());
526  FlutterDartProject* project = [[FlutterDartProject alloc]
527  initWithAssetsPath:fixtures
528  ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]];
529 
530  project.dartEntrypointArguments = @[ @"arg1", @"arg2" ];
531  FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"test" project:project];
532 
533  bool called = false;
534  auto original_init = engine.embedderAPI.Initialize;
535  engine.embedderAPI.Initialize = MOCK_ENGINE_PROC(
536  Initialize, ([&called, &original_init](size_t version, const FlutterRendererConfig* config,
537  const FlutterProjectArgs* args, void* user_data,
538  FLUTTER_API_SYMBOL(FlutterEngine) * engine_out) {
539  called = true;
540  EXPECT_EQ(args->dart_entrypoint_argc, 2);
541  NSString* arg1 = [[NSString alloc] initWithCString:args->dart_entrypoint_argv[0]
542  encoding:NSUTF8StringEncoding];
543  NSString* arg2 = [[NSString alloc] initWithCString:args->dart_entrypoint_argv[1]
544  encoding:NSUTF8StringEncoding];
545 
546  EXPECT_TRUE([arg1 isEqualToString:@"arg1"]);
547  EXPECT_TRUE([arg2 isEqualToString:@"arg2"]);
548 
549  return original_init(version, config, args, user_data, engine_out);
550  }));
551 
552  EXPECT_TRUE([engine runWithEntrypoint:@"main"]);
553  EXPECT_TRUE(called);
554 }
555 
556 // Verify that the engine is not retained indirectly via the binary messenger held by channels and
557 // plugins. Previously, FlutterEngine.binaryMessenger returned the engine itself, and thus plugins
558 // could cause a retain cycle, preventing the engine from being deallocated.
559 // FlutterEngine.binaryMessenger now returns a FlutterBinaryMessengerRelay whose weak pointer back
560 // to the engine is cleared when the engine is deallocated.
561 // Issue: https://github.com/flutter/flutter/issues/116445
562 TEST_F(FlutterEngineTest, FlutterBinaryMessengerDoesNotRetainEngine) {
563  __weak FlutterEngine* weakEngine;
564  id<FlutterBinaryMessenger> binaryMessenger = nil;
565  @autoreleasepool {
566  // Create a test engine.
567  NSString* fixtures = @(flutter::testing::GetFixturesPath());
568  FlutterDartProject* project = [[FlutterDartProject alloc]
569  initWithAssetsPath:fixtures
570  ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]];
571  FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"test"
572  project:project
573  allowHeadlessExecution:YES];
574  weakEngine = engine;
575  binaryMessenger = engine.binaryMessenger;
576  }
577 
578  // Once the engine has been deallocated, verify the weak engine pointer is nil, and thus not
579  // retained by the relay.
580  EXPECT_NE(binaryMessenger, nil);
581  EXPECT_EQ(weakEngine, nil);
582 }
583 
584 // Verify that the engine is not retained indirectly via the texture registry held by plugins.
585 // Issue: https://github.com/flutter/flutter/issues/116445
586 TEST_F(FlutterEngineTest, FlutterTextureRegistryDoesNotReturnEngine) {
587  __weak FlutterEngine* weakEngine;
588  id<FlutterTextureRegistry> textureRegistry;
589  @autoreleasepool {
590  // Create a test engine.
591  NSString* fixtures = @(flutter::testing::GetFixturesPath());
592  FlutterDartProject* project = [[FlutterDartProject alloc]
593  initWithAssetsPath:fixtures
594  ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]];
595  FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"test"
596  project:project
597  allowHeadlessExecution:YES];
598  id<FlutterPluginRegistrar> registrar = [engine registrarForPlugin:@"MyPlugin"];
599  textureRegistry = registrar.textures;
600  }
601 
602  // Once the engine has been deallocated, verify the weak engine pointer is nil, and thus not
603  // retained via the texture registry.
604  EXPECT_NE(textureRegistry, nil);
605  EXPECT_EQ(weakEngine, nil);
606 }
607 
608 TEST_F(FlutterEngineTest, PublishedValueNilForUnknownPlugin) {
609  NSString* fixtures = @(flutter::testing::GetFixturesPath());
610  FlutterDartProject* project = [[FlutterDartProject alloc]
611  initWithAssetsPath:fixtures
612  ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]];
613  FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"test"
614  project:project
615  allowHeadlessExecution:YES];
616 
617  EXPECT_EQ([engine valuePublishedByPlugin:@"NoSuchPlugin"], nil);
618 }
619 
620 TEST_F(FlutterEngineTest, PublishedValueNSNullIfNoPublishedValue) {
621  NSString* fixtures = @(flutter::testing::GetFixturesPath());
622  FlutterDartProject* project = [[FlutterDartProject alloc]
623  initWithAssetsPath:fixtures
624  ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]];
625  FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"test"
626  project:project
627  allowHeadlessExecution:YES];
628  NSString* pluginName = @"MyPlugin";
629  // Request the registarar to register the plugin as existing.
630  [engine registrarForPlugin:pluginName];
631 
632  // The documented behavior is that a plugin that exists but hasn't published
633  // anything returns NSNull, rather than nil, as on iOS.
634  EXPECT_EQ([engine valuePublishedByPlugin:pluginName], [NSNull null]);
635 }
636 
637 TEST_F(FlutterEngineTest, PublishedValueReturnsLastPublished) {
638  NSString* fixtures = @(flutter::testing::GetFixturesPath());
639  FlutterDartProject* project = [[FlutterDartProject alloc]
640  initWithAssetsPath:fixtures
641  ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]];
642  FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"test"
643  project:project
644  allowHeadlessExecution:YES];
645  NSString* pluginName = @"MyPlugin";
646  id<FlutterPluginRegistrar> registrar = [engine registrarForPlugin:pluginName];
647 
648  NSString* firstValue = @"A published value";
649  NSArray* secondValue = @[ @"A different published value" ];
650 
651  [registrar publish:firstValue];
652  EXPECT_EQ([engine valuePublishedByPlugin:pluginName], firstValue);
653 
654  [registrar publish:secondValue];
655  EXPECT_EQ([engine valuePublishedByPlugin:pluginName], secondValue);
656 }
657 
658 // If a channel overrides a previous channel with the same name, cleaning
659 // the previous channel should not affect the new channel.
660 //
661 // This is important when recreating classes that uses a channel, because the
662 // new instance would create the channel before the first class is deallocated
663 // and clears the channel.
664 TEST_F(FlutterEngineTest, MessengerCleanupConnectionWorks) {
665  FlutterEngine* engine = GetFlutterEngine();
666  EXPECT_TRUE([engine runWithEntrypoint:@"main"]);
667 
668  NSString* channel = @"_test_";
669  NSData* channel_data = [channel dataUsingEncoding:NSUTF8StringEncoding];
670 
671  // Mock SendPlatformMessage so that if a message is sent to
672  // "test/send_message", act as if the framework has sent an empty message to
673  // the channel marked by the `sendOnChannel:message:` call's message.
674  engine.embedderAPI.SendPlatformMessage = MOCK_ENGINE_PROC(
675  SendPlatformMessage, ([](auto engine_, auto message_) {
676  if (strcmp(message_->channel, "test/send_message") == 0) {
677  // The simplest message that is acceptable to a method channel.
678  std::string message = R"|({"method": "a"})|";
679  std::string channel(reinterpret_cast<const char*>(message_->message),
680  message_->message_size);
681  reinterpret_cast<EmbedderEngine*>(engine_)
682  ->GetShell()
683  .GetPlatformView()
684  ->HandlePlatformMessage(std::make_unique<PlatformMessage>(
685  channel.c_str(), fml::MallocMapping::Copy(message.c_str(), message.length()),
686  fml::RefPtr<PlatformMessageResponse>()));
687  }
688  return kSuccess;
689  }));
690 
691  __block int record = 0;
692 
693  FlutterMethodChannel* channel1 =
695  binaryMessenger:engine.binaryMessenger
696  codec:[FlutterJSONMethodCodec sharedInstance]];
697  [channel1 setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
698  record += 1;
699  }];
700 
701  [engine.binaryMessenger sendOnChannel:@"test/send_message" message:channel_data];
702  EXPECT_EQ(record, 1);
703 
704  FlutterMethodChannel* channel2 =
706  binaryMessenger:engine.binaryMessenger
707  codec:[FlutterJSONMethodCodec sharedInstance]];
708  [channel2 setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
709  record += 10;
710  }];
711 
712  [engine.binaryMessenger sendOnChannel:@"test/send_message" message:channel_data];
713  EXPECT_EQ(record, 11);
714 
715  [channel1 setMethodCallHandler:nil];
716 
717  [engine.binaryMessenger sendOnChannel:@"test/send_message" message:channel_data];
718  EXPECT_EQ(record, 21);
719 }
720 
721 TEST_F(FlutterEngineTest, HasStringsWhenPasteboardEmpty) {
722  id engineMock = CreateMockFlutterEngine(nil);
723 
724  // Call hasStrings and expect it to be false.
725  __block bool calledAfterClear = false;
726  __block bool valueAfterClear;
727  FlutterResult resultAfterClear = ^(id result) {
728  calledAfterClear = true;
729  NSNumber* valueNumber = [result valueForKey:@"value"];
730  valueAfterClear = [valueNumber boolValue];
731  };
732  FlutterMethodCall* methodCallAfterClear =
733  [FlutterMethodCall methodCallWithMethodName:@"Clipboard.hasStrings" arguments:nil];
734  [engineMock handleMethodCall:methodCallAfterClear result:resultAfterClear];
735  EXPECT_TRUE(calledAfterClear);
736  EXPECT_FALSE(valueAfterClear);
737 }
738 
739 TEST_F(FlutterEngineTest, HasStringsWhenPasteboardFull) {
740  id engineMock = CreateMockFlutterEngine(@"some string");
741 
742  // Call hasStrings and expect it to be true.
743  __block bool called = false;
744  __block bool value;
745  FlutterResult result = ^(id result) {
746  called = true;
747  NSNumber* valueNumber = [result valueForKey:@"value"];
748  value = [valueNumber boolValue];
749  };
750  FlutterMethodCall* methodCall =
751  [FlutterMethodCall methodCallWithMethodName:@"Clipboard.hasStrings" arguments:nil];
752  [engineMock handleMethodCall:methodCall result:result];
753  EXPECT_TRUE(called);
754  EXPECT_TRUE(value);
755 }
756 
757 TEST_F(FlutterEngineTest, ResponseAfterEngineDied) {
758  FlutterEngine* engine = GetFlutterEngine();
760  initWithName:@"foo"
761  binaryMessenger:engine.binaryMessenger
763  __block BOOL didCallCallback = NO;
764  [channel setMessageHandler:^(id message, FlutterReply callback) {
765  ShutDownEngine();
766  callback(nil);
767  didCallCallback = YES;
768  }];
769  EXPECT_TRUE([engine runWithEntrypoint:@"sendFooMessage"]);
770  engine = nil;
771 
772  while (!didCallCallback) {
773  [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
774  }
775 }
776 
777 TEST_F(FlutterEngineTest, ResponseFromBackgroundThread) {
778  FlutterEngine* engine = GetFlutterEngine();
780  initWithName:@"foo"
781  binaryMessenger:engine.binaryMessenger
783  __block BOOL didCallCallback = NO;
784  [channel setMessageHandler:^(id message, FlutterReply callback) {
785  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
786  callback(nil);
787  dispatch_async(dispatch_get_main_queue(), ^{
788  didCallCallback = YES;
789  });
790  });
791  }];
792  EXPECT_TRUE([engine runWithEntrypoint:@"sendFooMessage"]);
793 
794  while (!didCallCallback) {
795  [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
796  }
797 }
798 
799 TEST_F(FlutterEngineTest, ThreadSynchronizerNotBlockingRasterThreadAfterShutdown) {
800  FlutterThreadSynchronizer* threadSynchronizer = [[FlutterThreadSynchronizer alloc] init];
801  [threadSynchronizer shutdown];
802 
803  std::thread rasterThread([&threadSynchronizer] {
804  [threadSynchronizer performCommitForView:kImplicitViewId
805  size:CGSizeMake(100, 100)
806  notify:^{
807  }];
808  });
809 
810  rasterThread.join();
811 }
812 
813 TEST_F(FlutterEngineTest, ManageControllersIfInitiatedByController) {
814  NSString* fixtures = @(flutter::testing::GetFixturesPath());
815  FlutterDartProject* project = [[FlutterDartProject alloc]
816  initWithAssetsPath:fixtures
817  ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]];
818 
819  FlutterEngine* engine;
820  FlutterViewController* viewController1;
821 
822  @autoreleasepool {
823  // Create FVC1.
824  viewController1 = [[FlutterViewController alloc] initWithProject:project];
825  EXPECT_EQ(viewController1.viewId, 0ll);
826 
827  engine = viewController1.engine;
828  engine.viewController = nil;
829 
830  // Create FVC2 based on the same engine.
831  FlutterViewController* viewController2 = [[FlutterViewController alloc] initWithEngine:engine
832  nibName:nil
833  bundle:nil];
834  EXPECT_EQ(engine.viewController, viewController2);
835  }
836  // FVC2 is deallocated but FVC1 is retained.
837 
838  EXPECT_EQ(engine.viewController, nil);
839 
840  engine.viewController = viewController1;
841  EXPECT_EQ(engine.viewController, viewController1);
842  EXPECT_EQ(viewController1.viewId, 0ll);
843 }
844 
845 TEST_F(FlutterEngineTest, ManageControllersIfInitiatedByEngine) {
846  // Don't create the engine with `CreateMockFlutterEngine`, because it adds
847  // additional references to FlutterViewControllers, which is crucial to this
848  // test case.
849  FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"io.flutter"
850  project:nil
851  allowHeadlessExecution:NO];
852  FlutterViewController* viewController1;
853 
854  @autoreleasepool {
855  viewController1 = [[FlutterViewController alloc] initWithEngine:engine nibName:nil bundle:nil];
856  EXPECT_EQ(viewController1.viewId, 0ll);
857  EXPECT_EQ(engine.viewController, viewController1);
858 
859  engine.viewController = nil;
860 
861  FlutterViewController* viewController2 = [[FlutterViewController alloc] initWithEngine:engine
862  nibName:nil
863  bundle:nil];
864  EXPECT_EQ(viewController2.viewId, 0ll);
865  EXPECT_EQ(engine.viewController, viewController2);
866  }
867  // FVC2 is deallocated but FVC1 is retained.
868 
869  EXPECT_EQ(engine.viewController, nil);
870 
871  engine.viewController = viewController1;
872  EXPECT_EQ(engine.viewController, viewController1);
873  EXPECT_EQ(viewController1.viewId, 0ll);
874 }
875 
876 TEST_F(FlutterEngineTest, HandlesTerminationRequest) {
877  id engineMock = CreateMockFlutterEngine(nil);
878  __block NSString* nextResponse = @"exit";
879  __block BOOL triedToTerminate = NO;
880  FlutterEngineTerminationHandler* terminationHandler =
881  [[FlutterEngineTerminationHandler alloc] initWithEngine:engineMock
882  terminator:^(id sender) {
883  triedToTerminate = TRUE;
884  // Don't actually terminate, of course.
885  }];
886  OCMStub([engineMock terminationHandler]).andReturn(terminationHandler);
887  id binaryMessengerMock = OCMProtocolMock(@protocol(FlutterBinaryMessenger));
888  OCMStub( // NOLINT(google-objc-avoid-throwing-exception)
889  [engineMock binaryMessenger])
890  .andReturn(binaryMessengerMock);
891  OCMStub([engineMock sendOnChannel:@"flutter/platform"
892  message:[OCMArg any]
893  binaryReply:[OCMArg any]])
894  .andDo((^(NSInvocation* invocation) {
895  [invocation retainArguments];
896  FlutterBinaryReply callback;
897  NSData* returnedMessage;
898  [invocation getArgument:&callback atIndex:4];
899  if ([nextResponse isEqualToString:@"error"]) {
900  FlutterError* errorResponse = [FlutterError errorWithCode:@"Error"
901  message:@"Failed"
902  details:@"Details"];
903  returnedMessage =
904  [[FlutterJSONMethodCodec sharedInstance] encodeErrorEnvelope:errorResponse];
905  } else {
906  NSDictionary* responseDict = @{@"response" : nextResponse};
907  returnedMessage =
908  [[FlutterJSONMethodCodec sharedInstance] encodeSuccessEnvelope:responseDict];
909  }
910  callback(returnedMessage);
911  }));
912  __block NSString* calledAfterTerminate = @"";
913  FlutterResult appExitResult = ^(id result) {
914  NSDictionary* resultDict = result;
915  calledAfterTerminate = resultDict[@"response"];
916  };
917  FlutterMethodCall* methodExitApplication =
918  [FlutterMethodCall methodCallWithMethodName:@"System.exitApplication"
919  arguments:@{@"type" : @"cancelable"}];
920 
921  // Always terminate when the binding isn't ready (which is the default).
922  triedToTerminate = NO;
923  calledAfterTerminate = @"";
924  nextResponse = @"cancel";
925  [engineMock handleMethodCall:methodExitApplication result:appExitResult];
926  EXPECT_STREQ([calledAfterTerminate UTF8String], "");
927  EXPECT_TRUE(triedToTerminate);
928 
929  // Once the binding is ready, handle the request.
930  terminationHandler.acceptingRequests = YES;
931  triedToTerminate = NO;
932  calledAfterTerminate = @"";
933  nextResponse = @"exit";
934  [engineMock handleMethodCall:methodExitApplication result:appExitResult];
935  EXPECT_STREQ([calledAfterTerminate UTF8String], "exit");
936  EXPECT_TRUE(triedToTerminate);
937 
938  triedToTerminate = NO;
939  calledAfterTerminate = @"";
940  nextResponse = @"cancel";
941  [engineMock handleMethodCall:methodExitApplication result:appExitResult];
942  EXPECT_STREQ([calledAfterTerminate UTF8String], "cancel");
943  EXPECT_FALSE(triedToTerminate);
944 
945  // Check that it doesn't crash on error.
946  triedToTerminate = NO;
947  calledAfterTerminate = @"";
948  nextResponse = @"error";
949  [engineMock handleMethodCall:methodExitApplication result:appExitResult];
950  EXPECT_STREQ([calledAfterTerminate UTF8String], "");
951  EXPECT_TRUE(triedToTerminate);
952 }
953 
954 TEST_F(FlutterEngineTest, IgnoresTerminationRequestIfNotFlutterAppDelegate) {
955  id<NSApplicationDelegate> previousDelegate = [[NSApplication sharedApplication] delegate];
956  id<NSApplicationDelegate> plainDelegate = [[PlainAppDelegate alloc] init];
957  [NSApplication sharedApplication].delegate = plainDelegate;
958 
959  // Creating the engine shouldn't fail here, even though the delegate isn't a
960  // FlutterAppDelegate.
962 
963  // Asking to terminate the app should cancel.
964  EXPECT_EQ([[[NSApplication sharedApplication] delegate] applicationShouldTerminate:NSApp],
965  NSTerminateCancel);
966 
967  [NSApplication sharedApplication].delegate = previousDelegate;
968 }
969 
970 TEST_F(FlutterEngineTest, HandleAccessibilityEvent) {
971  __block BOOL announced = NO;
972  id engineMock = CreateMockFlutterEngine(nil);
973 
974  OCMStub([engineMock announceAccessibilityMessage:[OCMArg any]
975  withPriority:NSAccessibilityPriorityMedium])
976  .andDo((^(NSInvocation* invocation) {
977  announced = TRUE;
978  [invocation retainArguments];
979  NSString* message;
980  [invocation getArgument:&message atIndex:2];
981  EXPECT_EQ(message, @"error message");
982  }));
983 
984  NSDictionary<NSString*, id>* annotatedEvent =
985  @{@"type" : @"announce",
986  @"data" : @{@"message" : @"error message"}};
987 
988  [engineMock handleAccessibilityEvent:annotatedEvent];
989 
990  EXPECT_TRUE(announced);
991 }
992 
993 TEST_F(FlutterEngineTest, HandleLifecycleStates) API_AVAILABLE(macos(10.9)) {
994  __block flutter::AppLifecycleState sentState;
995  id engineMock = CreateMockFlutterEngine(nil);
996 
997  // Have to enumerate all the values because OCMStub can't capture
998  // non-Objective-C object arguments.
999  OCMStub([engineMock setApplicationState:flutter::AppLifecycleState::kDetached])
1000  .andDo((^(NSInvocation* invocation) {
1002  }));
1003  OCMStub([engineMock setApplicationState:flutter::AppLifecycleState::kResumed])
1004  .andDo((^(NSInvocation* invocation) {
1006  }));
1007  OCMStub([engineMock setApplicationState:flutter::AppLifecycleState::kInactive])
1008  .andDo((^(NSInvocation* invocation) {
1010  }));
1011  OCMStub([engineMock setApplicationState:flutter::AppLifecycleState::kHidden])
1012  .andDo((^(NSInvocation* invocation) {
1014  }));
1015  OCMStub([engineMock setApplicationState:flutter::AppLifecycleState::kPaused])
1016  .andDo((^(NSInvocation* invocation) {
1018  }));
1019 
1020  __block NSApplicationOcclusionState visibility = NSApplicationOcclusionStateVisible;
1021  id mockApplication = OCMPartialMock([NSApplication sharedApplication]);
1022  OCMStub((NSApplicationOcclusionState)[mockApplication occlusionState])
1023  .andDo(^(NSInvocation* invocation) {
1024  [invocation setReturnValue:&visibility];
1025  });
1026 
1027  NSNotification* willBecomeActive =
1028  [[NSNotification alloc] initWithName:NSApplicationWillBecomeActiveNotification
1029  object:nil
1030  userInfo:nil];
1031  NSNotification* willResignActive =
1032  [[NSNotification alloc] initWithName:NSApplicationWillResignActiveNotification
1033  object:nil
1034  userInfo:nil];
1035 
1036  NSNotification* didChangeOcclusionState;
1037  didChangeOcclusionState =
1038  [[NSNotification alloc] initWithName:NSApplicationDidChangeOcclusionStateNotification
1039  object:nil
1040  userInfo:nil];
1041 
1042  [engineMock handleDidChangeOcclusionState:didChangeOcclusionState];
1043  EXPECT_EQ(sentState, flutter::AppLifecycleState::kInactive);
1044 
1045  [engineMock handleWillBecomeActive:willBecomeActive];
1046  EXPECT_EQ(sentState, flutter::AppLifecycleState::kResumed);
1047 
1048  [engineMock handleWillResignActive:willResignActive];
1049  EXPECT_EQ(sentState, flutter::AppLifecycleState::kInactive);
1050 
1051  visibility = 0;
1052  [engineMock handleDidChangeOcclusionState:didChangeOcclusionState];
1053  EXPECT_EQ(sentState, flutter::AppLifecycleState::kHidden);
1054 
1055  [engineMock handleWillBecomeActive:willBecomeActive];
1056  EXPECT_EQ(sentState, flutter::AppLifecycleState::kHidden);
1057 
1058  [engineMock handleWillResignActive:willResignActive];
1059  EXPECT_EQ(sentState, flutter::AppLifecycleState::kHidden);
1060 
1061  [mockApplication stopMocking];
1062 }
1063 
1064 TEST_F(FlutterEngineTest, ForwardsPluginDelegateRegistration) {
1065  id<NSApplicationDelegate> previousDelegate = [[NSApplication sharedApplication] delegate];
1066  FakeLifecycleProvider* fakeAppDelegate = [[FakeLifecycleProvider alloc] init];
1067  [NSApplication sharedApplication].delegate = fakeAppDelegate;
1068 
1069  FakeAppDelegatePlugin* plugin = [[FakeAppDelegatePlugin alloc] init];
1070  FlutterEngine* engine = CreateMockFlutterEngine(nil);
1071 
1072  [[engine registrarForPlugin:@"TestPlugin"] addApplicationDelegate:plugin];
1073 
1074  EXPECT_TRUE([fakeAppDelegate hasDelegate:plugin]);
1075 
1076  [NSApplication sharedApplication].delegate = previousDelegate;
1077 }
1078 
1079 TEST_F(FlutterEngineTest, UnregistersPluginsOnEngineDestruction) {
1080  id<NSApplicationDelegate> previousDelegate = [[NSApplication sharedApplication] delegate];
1081  FakeLifecycleProvider* fakeAppDelegate = [[FakeLifecycleProvider alloc] init];
1082  [NSApplication sharedApplication].delegate = fakeAppDelegate;
1083 
1084  FakeAppDelegatePlugin* plugin = [[FakeAppDelegatePlugin alloc] init];
1085 
1086  @autoreleasepool {
1087  FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"test" project:nil];
1088 
1089  [[engine registrarForPlugin:@"TestPlugin"] addApplicationDelegate:plugin];
1090  EXPECT_TRUE([fakeAppDelegate hasDelegate:plugin]);
1091  }
1092 
1093  // When the engine is released, it should unregister any plugins it had
1094  // registered on its behalf.
1095  EXPECT_FALSE([fakeAppDelegate hasDelegate:plugin]);
1096 
1097  [NSApplication sharedApplication].delegate = previousDelegate;
1098 }
1099 
1100 } // namespace flutter::testing
1101 
1102 // NOLINTEND(clang-analyzer-core.StackAddressEscape)
flutter::testing::TEST_F
TEST_F(FlutterEngineTest, UnregistersPluginsOnEngineDestruction)
Definition: FlutterEngineTest.mm:1079
flutter::AppLifecycleState::kHidden
@ kHidden
FlutterEngine(Test)::macOSCompositor
flutter::FlutterCompositor * macOSCompositor
Definition: FlutterEngineTest.mm:43
FlutterEngine
Definition: FlutterEngine.h:30
FlutterPlugin-p
Definition: FlutterPluginMacOS.h:26
+[FlutterMethodCall methodCallWithMethodName:arguments:]
instancetype methodCallWithMethodName:arguments:(NSString *method,[arguments] id _Nullable arguments)
FlutterBasicMessageChannel
Definition: FlutterChannels.h:37
kImplicitViewId
constexpr int64_t kImplicitViewId
Definition: FlutterEngineTest.mm:35
FlutterViewController
Definition: FlutterViewController.h:62
FlutterMethodChannel
Definition: FlutterChannels.h:220
FlutterEngine.h
-[FlutterThreadSynchronizer performCommitForView:size:notify:]
void performCommitForView:size:notify:(int64_t viewId,[size] CGSize size,[notify] nonnull dispatch_block_t notify)
Definition: FlutterThreadSynchronizer.mm:137
flutter::testing::CreateMockFlutterEngine
id CreateMockFlutterEngine(NSString *pasteboardString)
Definition: FlutterEngineTestUtils.mm:76
FlutterPluginMacOS.h
user_data
void * user_data
Definition: texture_registrar_unittests.cc:27
FlutterEngine_Internal.h
FlutterError
Definition: FlutterCodecs.h:246
FlutterChannels.h
FlutterEngine::viewController
FlutterViewController * viewController
Definition: FlutterEngine.h:86
flutter::FlutterCompositor
Definition: FlutterCompositor.h:25
TestPlatformViewFactory
Definition: FlutterEngineTest.mm:47
FakeLifecycleProvider
Definition: FlutterEngineTest.mm:69
flutter::testing
Definition: AccessibilityBridgeMacTest.mm:13
FakeAppDelegatePlugin
Definition: FlutterEngineTest.mm:111
FlutterEngineTestUtils.h
FlutterViewController::engine
FlutterEngine * engine
Definition: FlutterViewController.h:67
FlutterViewControllerTestUtils.h
+[FlutterError errorWithCode:message:details:]
instancetype errorWithCode:message:details:(NSString *code,[message] NSString *_Nullable message,[details] id _Nullable details)
FlutterAppLifecycleProvider-p
Definition: FlutterAppDelegate.h:21
FlutterEngine::binaryMessenger
id< FlutterBinaryMessenger > binaryMessenger
Definition: FlutterEngine.h:91
FlutterAppLifecycleDelegate-p
Definition: FlutterAppLifecycleDelegate.h:21
flutter::AppLifecycleState::kInactive
@ kInactive
-[FlutterMethodChannel setMethodCallHandler:]
void setMethodCallHandler:(FlutterMethodCallHandler _Nullable handler)
FlutterStandardMessageCodec
Definition: FlutterCodecs.h:209
FlutterBinaryMessengerRelay.h
flutter::testing::FlutterEngineTest
Definition: FlutterEngineTestUtils.h:15
FlutterMethodCall
Definition: FlutterCodecs.h:220
FlutterViewController::backgroundColor
NSColor * backgroundColor
Definition: FlutterViewController.h:188
FlutterThreadSynchronizer
Definition: FlutterThreadSynchronizer.h:13
PlainAppDelegate
Definition: FlutterEngineTest.mm:57
FlutterResult
void(^ FlutterResult)(id _Nullable result)
Definition: FlutterChannels.h:194
FlutterAppDelegate.h
+[FlutterMethodChannel methodChannelWithName:binaryMessenger:codec:]
instancetype methodChannelWithName:binaryMessenger:codec:(NSString *name,[binaryMessenger] NSObject< FlutterBinaryMessenger > *messenger,[codec] NSObject< FlutterMethodCodec > *codec)
FlutterPlatformViewFactory-p
Definition: FlutterPlatformViews.h:13
flutter::AppLifecycleState::kResumed
@ kResumed
flutter::AppLifecycleState::kDetached
@ kDetached
FlutterJSONMethodCodec
Definition: FlutterCodecs.h:455
-[FlutterBasicMessageChannel setMessageHandler:]
void setMessageHandler:(FlutterMessageHandler _Nullable handler)
FlutterEngine(Test)
Definition: FlutterEngineTest.mm:37
flutter::AppLifecycleState
AppLifecycleState
Definition: app_lifecycle_state.h:32
-[FlutterEngine shutDownEngine]
void shutDownEngine()
Definition: FlutterEngine.mm:1035
FlutterDartProject
Definition: FlutterDartProject.mm:24
FlutterBinaryMessenger-p
Definition: FlutterBinaryMessenger.h:49
accessibility_bridge.h
FlutterEngineTerminationHandler
Definition: FlutterEngine.mm:180
-[FlutterThreadSynchronizer shutdown]
void shutdown()
Definition: FlutterThreadSynchronizer.mm:179
FlutterBinaryReply
NS_ASSUME_NONNULL_BEGIN typedef void(^ FlutterBinaryReply)(NSData *_Nullable reply)
FlutterAppLifecycleDelegate.h
+[FlutterMessageCodec-p sharedInstance]
instancetype sharedInstance()
flutter::AppLifecycleState::kPaused
@ kPaused