| 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/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
36 @interface FlutterEngine (Test)
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
46 @interface TestPlatformViewFactory : NSObject <FlutterPlatformViewFactory>
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
122 TEST_F(FlutterEngineTest, CanLaunch) {
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
149 TEST_F(FlutterEngineTest, Switches) {
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 =
697 [FlutterMethodChannel methodChannelWithName:channel
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 =
708 [FlutterMethodChannel methodChannelWithName:channel
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();
762 FlutterBasicMessageChannel* channel = [[FlutterBasicMessageChannel alloc]
763 initWithName:@"foo"
764 binaryMessenger:engine.binaryMessenger
765 codec:[FlutterStandardMessageCodec sharedInstance]];
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();
782 FlutterBasicMessageChannel* channel = [[FlutterBasicMessageChannel alloc]
783 initWithName:@"foo"
784 binaryMessenger:engine.binaryMessenger
785 codec:[FlutterStandardMessageCodec sharedInstance]];
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.
964 CreateMockFlutterEngine(nil);
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) {
1004 sentState = flutter::AppLifecycleState::kDetached;
1005 }));
1006 OCMStub([engineMock setApplicationState:flutter::AppLifecycleState::kResumed])
1007 .andDo((^(NSInvocation* invocation) {
1008 sentState = flutter::AppLifecycleState::kResumed;
1009 }));
1010 OCMStub([engineMock setApplicationState:flutter::AppLifecycleState::kInactive])
1011 .andDo((^(NSInvocation* invocation) {
1012 sentState = flutter::AppLifecycleState::kInactive;
1013 }));
1014 OCMStub([engineMock setApplicationState:flutter::AppLifecycleState::kHidden])
1015 .andDo((^(NSInvocation* invocation) {
1016 sentState = flutter::AppLifecycleState::kHidden;
1017 }));
1018 OCMStub([engineMock setApplicationState:flutter::AppLifecycleState::kPaused])
1019 .andDo((^(NSInvocation* invocation) {
1020 sentState = flutter::AppLifecycleState::kPaused;
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