| 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
5 #import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h"
6 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h"
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"
17 #include "flutter/shell/platform/common/accessibility_bridge.h"
18 #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h"
19 #import "flutter/shell/platform/darwin/common/framework/Source/FlutterBinaryMessengerRelay.h"
20 #import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterAppDelegate.h"
21 #import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterAppLifecycleDelegate.h"
22 #import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterPluginMacOS.h"
23 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngineTestUtils.h"
24 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewControllerTestUtils.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
37 @interface FlutterEngine (Test)
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
47 @interface TestPlatformViewFactory : NSObject <FlutterPlatformViewFactory>
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
123 TEST_F(FlutterEngineTest, CanLaunch) {
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
150 TEST_F(FlutterEngineTest, Switches) {
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 =
694 [FlutterMethodChannel methodChannelWithName:channel
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 =
705 [FlutterMethodChannel methodChannelWithName:channel
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();
759 FlutterBasicMessageChannel* channel = [[FlutterBasicMessageChannel alloc]
760 initWithName:@"foo"
761 binaryMessenger:engine.binaryMessenger
762 codec:[FlutterStandardMessageCodec sharedInstance]];
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();
779 FlutterBasicMessageChannel* channel = [[FlutterBasicMessageChannel alloc]
780 initWithName:@"foo"
781 binaryMessenger:engine.binaryMessenger
782 codec:[FlutterStandardMessageCodec sharedInstance]];
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.
961 CreateMockFlutterEngine(nil);
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) {
1001 sentState = flutter::AppLifecycleState::kDetached;
1002 }));
1003 OCMStub([engineMock setApplicationState:flutter::AppLifecycleState::kResumed])
1004 .andDo((^(NSInvocation* invocation) {
1005 sentState = flutter::AppLifecycleState::kResumed;
1006 }));
1007 OCMStub([engineMock setApplicationState:flutter::AppLifecycleState::kInactive])
1008 .andDo((^(NSInvocation* invocation) {
1009 sentState = flutter::AppLifecycleState::kInactive;
1010 }));
1011 OCMStub([engineMock setApplicationState:flutter::AppLifecycleState::kHidden])
1012 .andDo((^(NSInvocation* invocation) {
1013 sentState = flutter::AppLifecycleState::kHidden;
1014 }));
1015 OCMStub([engineMock setApplicationState:flutter::AppLifecycleState::kPaused])
1016 .andDo((^(NSInvocation* invocation) {
1017 sentState = flutter::AppLifecycleState::kPaused;
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