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