Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
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/FlutterCompositor.h"
24#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngineTestUtils.h"
25#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewControllerTestUtils.h"
26#include "flutter/shell/platform/embedder/embedder.h"
27#include "flutter/shell/platform/embedder/embedder_engine.h"
28#include "flutter/shell/platform/embedder/test_utils/proc_table_replacement.h"
29#include "flutter/testing/stream_capture.h"
30#include "flutter/testing/test_dart_native_resolver.h"
31#include "gtest/gtest.h"
32
33// CREATE_NATIVE_ENTRY and MOCK_ENGINE_PROC are leaky by design
34// NOLINTBEGIN(clang-analyzer-core.StackAddressEscape)
35
36constexpr int64_t kImplicitViewId = 0ll;
37
38@interface FlutterEngine (Test)
39/**
40 * The FlutterCompositor object currently in use by the FlutterEngine.
41 *
42 * May be nil if the compositor has not been initialized yet.
43 */
44@property(nonatomic, readonly, nullable) flutter::FlutterCompositor* macOSCompositor;
45
46@end
47
49@end
50
51@implementation TestPlatformViewFactory
52- (nonnull NSView*)createWithViewIdentifier:(FlutterViewIdentifier)viewIdentifier
53 arguments:(nullable id)args {
54 return viewIdentifier == 42 ? [[NSView alloc] init] : nil;
55}
56
57@end
58
59@interface PlainAppDelegate : NSObject <NSApplicationDelegate>
60@end
61
62@implementation PlainAppDelegate
63- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication* _Nonnull)sender {
64 // Always cancel, so that the test doesn't exit.
65 return NSTerminateCancel;
66}
67@end
68
69#pragma mark -
70
71@interface FakeLifecycleProvider : NSObject <FlutterAppLifecycleProvider, NSApplicationDelegate>
72
73@property(nonatomic, strong, readonly) NSPointerArray* registeredDelegates;
74
75// True if the given delegate is currently registered.
76- (BOOL)hasDelegate:(nonnull NSObject<FlutterAppLifecycleDelegate>*)delegate;
77@end
78
79@implementation FakeLifecycleProvider {
80 /**
81 * All currently registered delegates.
82 *
83 * This does not use NSPointerArray or any other weak-pointer
84 * system, because a weak pointer will be nil'd out at the start of dealloc, which will break
85 * queries. E.g., if a delegate is dealloc'd without being unregistered, a weak pointer array
86 * would no longer contain that pointer even though removeApplicationLifecycleDelegate: was never
87 * called, causing tests to pass incorrectly.
88 */
89 std::vector<void*> _delegates;
90}
91
92- (void)addApplicationLifecycleDelegate:(nonnull NSObject<FlutterAppLifecycleDelegate>*)delegate {
93 _delegates.push_back((__bridge void*)delegate);
94}
95
96- (void)removeApplicationLifecycleDelegate:
97 (nonnull NSObject<FlutterAppLifecycleDelegate>*)delegate {
98 auto delegateIndex = std::find(_delegates.begin(), _delegates.end(), (__bridge void*)delegate);
99 NSAssert(delegateIndex != _delegates.end(),
100 @"Attempting to unregister a delegate that was not registered.");
101 _delegates.erase(delegateIndex);
102}
103
104- (BOOL)hasDelegate:(nonnull NSObject<FlutterAppLifecycleDelegate>*)delegate {
105 return std::find(_delegates.begin(), _delegates.end(), (__bridge void*)delegate) !=
106 _delegates.end();
107}
108
109@end
110
111#pragma mark -
112
114@end
115
116@implementation FakeAppDelegatePlugin
117+ (void)registerWithRegistrar:(id<FlutterPluginRegistrar>)registrar {
118}
119@end
120
121#pragma mark -
122
124@end
125
126@implementation MockableFlutterEngine
127- (NSArray<NSScreen*>*)screens {
128 id mockScreen = OCMClassMock([NSScreen class]);
129 OCMStub([mockScreen backingScaleFactor]).andReturn(2.0);
130 OCMStub([mockScreen deviceDescription]).andReturn(@{
131 @"NSScreenNumber" : [NSNumber numberWithInt:10]
132 });
133 OCMStub([mockScreen frame]).andReturn(NSMakeRect(10, 20, 30, 40));
134 return [NSArray arrayWithObject:mockScreen];
135}
136@end
137
138#pragma mark -
139
140namespace flutter::testing {
141
143 FlutterEngine* engine = GetFlutterEngine();
144 EXPECT_TRUE([engine runWithEntrypoint:@"main"]);
145 ASSERT_TRUE(engine.running);
146}
147
148TEST_F(FlutterEngineTest, HasNonNullExecutableName) {
149 FlutterEngine* engine = GetFlutterEngine();
150 std::string executable_name = [[engine executableName] UTF8String];
151 ASSERT_FALSE(executable_name.empty());
152
153 // Block until notified by the Dart test of the value of Platform.executable.
155 AddNativeCallback("NotifyStringValue", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
156 const auto dart_string = tonic::DartConverter<std::string>::FromDart(
158 EXPECT_EQ(executable_name, dart_string);
159 latch.Signal();
160 }));
161
162 // Launch the test entrypoint.
163 EXPECT_TRUE([engine runWithEntrypoint:@"executableNameNotNull"]);
164
165 latch.Wait();
166}
167
168#ifndef FLUTTER_RELEASE
170 setenv("FLUTTER_ENGINE_SWITCHES", "2", 1);
171 setenv("FLUTTER_ENGINE_SWITCH_1", "abc", 1);
172 setenv("FLUTTER_ENGINE_SWITCH_2", "foo=\"bar, baz\"", 1);
173
174 FlutterEngine* engine = GetFlutterEngine();
175 std::vector<std::string> switches = engine.switches;
176 ASSERT_EQ(switches.size(), 2UL);
177 EXPECT_EQ(switches[0], "--abc");
178 EXPECT_EQ(switches[1], "--foo=\"bar, baz\"");
179
180 unsetenv("FLUTTER_ENGINE_SWITCHES");
181 unsetenv("FLUTTER_ENGINE_SWITCH_1");
182 unsetenv("FLUTTER_ENGINE_SWITCH_2");
183}
184#endif // !FLUTTER_RELEASE
185
186TEST_F(FlutterEngineTest, MessengerSend) {
187 FlutterEngine* engine = GetFlutterEngine();
188 EXPECT_TRUE([engine runWithEntrypoint:@"main"]);
189
190 NSData* test_message = [@"a message" dataUsingEncoding:NSUTF8StringEncoding];
191 bool called = false;
192
194 SendPlatformMessage, ([&called, test_message](auto engine, auto message) {
195 called = true;
196 EXPECT_STREQ(message->channel, "test");
197 EXPECT_EQ(memcmp(message->message, test_message.bytes, message->message_size), 0);
198 return kSuccess;
199 }));
200
201 [engine.binaryMessenger sendOnChannel:@"test" message:test_message];
202 EXPECT_TRUE(called);
203}
204
205TEST_F(FlutterEngineTest, CanLogToStdout) {
206 // Block until completion of print statement.
208 AddNativeCallback("SignalNativeTest",
210
211 // Replace stdout stream buffer with our own.
212 StreamCapture stdout_capture(&std::cout);
213
214 // Launch the test entrypoint.
215 FlutterEngine* engine = GetFlutterEngine();
216 EXPECT_TRUE([engine runWithEntrypoint:@"canLogToStdout"]);
217 ASSERT_TRUE(engine.running);
218
219 latch.Wait();
220
221 stdout_capture.Stop();
222
223 // Verify hello world was written to stdout.
224 EXPECT_TRUE(stdout_capture.GetOutput().find("Hello logging") != std::string::npos);
225}
226
227TEST_F(FlutterEngineTest, DISABLED_BackgroundIsBlack) {
228 FlutterEngine* engine = GetFlutterEngine();
229
230 // Latch to ensure the entire layer tree has been generated and presented.
232 AddNativeCallback("SignalNativeTest", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
233 CALayer* rootLayer = engine.viewController.flutterView.layer;
234 EXPECT_TRUE(rootLayer.backgroundColor != nil);
235 if (rootLayer.backgroundColor != nil) {
236 NSColor* actualBackgroundColor =
237 [NSColor colorWithCGColor:rootLayer.backgroundColor];
238 EXPECT_EQ(actualBackgroundColor, [NSColor blackColor]);
239 }
240 latch.Signal();
241 }));
242
243 // Launch the test entrypoint.
244 EXPECT_TRUE([engine runWithEntrypoint:@"backgroundTest"]);
245 ASSERT_TRUE(engine.running);
246
247 FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine
248 nibName:nil
249 bundle:nil];
250 [viewController loadView];
251 viewController.flutterView.frame = CGRectMake(0, 0, 800, 600);
252
253 latch.Wait();
254}
255
256TEST_F(FlutterEngineTest, DISABLED_CanOverrideBackgroundColor) {
257 FlutterEngine* engine = GetFlutterEngine();
258
259 // Latch to ensure the entire layer tree has been generated and presented.
261 AddNativeCallback("SignalNativeTest", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
262 CALayer* rootLayer = engine.viewController.flutterView.layer;
263 EXPECT_TRUE(rootLayer.backgroundColor != nil);
264 if (rootLayer.backgroundColor != nil) {
265 NSColor* actualBackgroundColor =
266 [NSColor colorWithCGColor:rootLayer.backgroundColor];
267 EXPECT_EQ(actualBackgroundColor, [NSColor whiteColor]);
268 }
269 latch.Signal();
270 }));
271
272 // Launch the test entrypoint.
273 EXPECT_TRUE([engine runWithEntrypoint:@"backgroundTest"]);
274 ASSERT_TRUE(engine.running);
275
276 FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine
277 nibName:nil
278 bundle:nil];
279 [viewController loadView];
280 viewController.flutterView.frame = CGRectMake(0, 0, 800, 600);
281 viewController.flutterView.backgroundColor = [NSColor whiteColor];
282
283 latch.Wait();
284}
285
286TEST_F(FlutterEngineTest, CanToggleAccessibility) {
287 FlutterEngine* engine = GetFlutterEngine();
288 // Capture the update callbacks before the embedder API initializes.
289 auto original_init = engine.embedderAPI.Initialize;
290 std::function<void(const FlutterSemanticsUpdate2*, void*)> update_semantics_callback;
292 Initialize, ([&update_semantics_callback, &original_init](
293 size_t version, const FlutterRendererConfig* config,
294 const FlutterProjectArgs* args, void* user_data, auto engine_out) {
295 update_semantics_callback = args->update_semantics_callback2;
296 return original_init(version, config, args, user_data, engine_out);
297 }));
298 EXPECT_TRUE([engine runWithEntrypoint:@"main"]);
299 // Set up view controller.
300 FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine
301 nibName:nil
302 bundle:nil];
303 [viewController loadView];
304 // Enable the semantics.
305 bool enabled_called = false;
307 MOCK_ENGINE_PROC(UpdateSemanticsEnabled, ([&enabled_called](auto engine, bool enabled) {
308 enabled_called = enabled;
309 return kSuccess;
310 }));
312 EXPECT_TRUE(enabled_called);
313 // Send flutter semantics updates.
315 root.id = 0;
316 root.flags = static_cast<FlutterSemanticsFlag>(0);
317 root.actions = static_cast<FlutterSemanticsAction>(0);
318 root.text_selection_base = -1;
319 root.text_selection_extent = -1;
320 root.label = "root";
321 root.hint = "";
322 root.value = "";
323 root.increased_value = "";
324 root.decreased_value = "";
325 root.tooltip = "";
326 root.child_count = 1;
327 int32_t children[] = {1};
328 root.children_in_traversal_order = children;
329 root.custom_accessibility_actions_count = 0;
330
332 child1.id = 1;
333 child1.flags = static_cast<FlutterSemanticsFlag>(0);
334 child1.actions = static_cast<FlutterSemanticsAction>(0);
335 child1.text_selection_base = -1;
336 child1.text_selection_extent = -1;
337 child1.label = "child 1";
338 child1.hint = "";
339 child1.value = "";
340 child1.increased_value = "";
341 child1.decreased_value = "";
342 child1.tooltip = "";
343 child1.child_count = 0;
345
347 update.node_count = 2;
348 FlutterSemanticsNode2* nodes[] = {&root, &child1};
349 update.nodes = nodes;
350 update.custom_action_count = 0;
351 update_semantics_callback(&update, (__bridge void*)engine);
352
353 // Verify the accessibility tree is attached to the flutter view.
354 EXPECT_EQ([engine.viewController.flutterView.accessibilityChildren count], 1u);
355 NSAccessibilityElement* native_root = engine.viewController.flutterView.accessibilityChildren[0];
356 std::string root_label = [native_root.accessibilityLabel UTF8String];
357 EXPECT_TRUE(root_label == "root");
358 EXPECT_EQ(native_root.accessibilityRole, NSAccessibilityGroupRole);
359 EXPECT_EQ([native_root.accessibilityChildren count], 1u);
360 NSAccessibilityElement* native_child1 = native_root.accessibilityChildren[0];
361 std::string child1_value = [native_child1.accessibilityValue UTF8String];
362 EXPECT_TRUE(child1_value == "child 1");
363 EXPECT_EQ(native_child1.accessibilityRole, NSAccessibilityStaticTextRole);
364 EXPECT_EQ([native_child1.accessibilityChildren count], 0u);
365 // Disable the semantics.
366 bool semanticsEnabled = true;
368 MOCK_ENGINE_PROC(UpdateSemanticsEnabled, ([&semanticsEnabled](auto engine, bool enabled) {
369 semanticsEnabled = enabled;
370 return kSuccess;
371 }));
373 EXPECT_FALSE(semanticsEnabled);
374 // Verify the accessibility tree is removed from the view.
375 EXPECT_EQ([engine.viewController.flutterView.accessibilityChildren count], 0u);
376
377 [engine setViewController:nil];
378}
379
380TEST_F(FlutterEngineTest, CanToggleAccessibilityWhenHeadless) {
381 FlutterEngine* engine = GetFlutterEngine();
382 // Capture the update callbacks before the embedder API initializes.
383 auto original_init = engine.embedderAPI.Initialize;
384 std::function<void(const FlutterSemanticsUpdate2*, void*)> update_semantics_callback;
386 Initialize, ([&update_semantics_callback, &original_init](
387 size_t version, const FlutterRendererConfig* config,
388 const FlutterProjectArgs* args, void* user_data, auto engine_out) {
389 update_semantics_callback = args->update_semantics_callback2;
390 return original_init(version, config, args, user_data, engine_out);
391 }));
392 EXPECT_TRUE([engine runWithEntrypoint:@"main"]);
393
394 // Enable the semantics without attaching a view controller.
395 bool enabled_called = false;
397 MOCK_ENGINE_PROC(UpdateSemanticsEnabled, ([&enabled_called](auto engine, bool enabled) {
398 enabled_called = enabled;
399 return kSuccess;
400 }));
402 EXPECT_TRUE(enabled_called);
403 // Send flutter semantics updates.
405 root.id = 0;
406 root.flags = static_cast<FlutterSemanticsFlag>(0);
407 root.actions = static_cast<FlutterSemanticsAction>(0);
408 root.text_selection_base = -1;
409 root.text_selection_extent = -1;
410 root.label = "root";
411 root.hint = "";
412 root.value = "";
413 root.increased_value = "";
414 root.decreased_value = "";
415 root.tooltip = "";
416 root.child_count = 1;
417 int32_t children[] = {1};
418 root.children_in_traversal_order = children;
419 root.custom_accessibility_actions_count = 0;
420
422 child1.id = 1;
423 child1.flags = static_cast<FlutterSemanticsFlag>(0);
424 child1.actions = static_cast<FlutterSemanticsAction>(0);
425 child1.text_selection_base = -1;
426 child1.text_selection_extent = -1;
427 child1.label = "child 1";
428 child1.hint = "";
429 child1.value = "";
430 child1.increased_value = "";
431 child1.decreased_value = "";
432 child1.tooltip = "";
433 child1.child_count = 0;
435
437 update.node_count = 2;
438 FlutterSemanticsNode2* nodes[] = {&root, &child1};
439 update.nodes = nodes;
440 update.custom_action_count = 0;
441 // This call updates semantics for the implicit view, which does not exist,
442 // and therefore this call is invalid. But the engine should not crash.
443 update_semantics_callback(&update, (__bridge void*)engine);
444
445 // No crashes.
446 EXPECT_EQ(engine.viewController, nil);
447
448 // Disable the semantics.
449 bool semanticsEnabled = true;
451 MOCK_ENGINE_PROC(UpdateSemanticsEnabled, ([&semanticsEnabled](auto engine, bool enabled) {
452 semanticsEnabled = enabled;
453 return kSuccess;
454 }));
456 EXPECT_FALSE(semanticsEnabled);
457 // Still no crashes
458 EXPECT_EQ(engine.viewController, nil);
459}
460
461TEST_F(FlutterEngineTest, ProducesAccessibilityTreeWhenAddingViews) {
462 FlutterEngine* engine = GetFlutterEngine();
463 EXPECT_TRUE([engine runWithEntrypoint:@"main"]);
464
465 // Enable the semantics without attaching a view controller.
466 bool enabled_called = false;
468 MOCK_ENGINE_PROC(UpdateSemanticsEnabled, ([&enabled_called](auto engine, bool enabled) {
469 enabled_called = enabled;
470 return kSuccess;
471 }));
473 EXPECT_TRUE(enabled_called);
474
475 EXPECT_EQ(engine.viewController, nil);
476
477 // Assign the view controller after enabling semantics
478 FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine
479 nibName:nil
480 bundle:nil];
482
483 EXPECT_NE(viewController.accessibilityBridge.lock(), nullptr);
484}
485
486TEST_F(FlutterEngineTest, NativeCallbacks) {
488 bool latch_called = false;
489 AddNativeCallback("SignalNativeTest", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
490 latch_called = true;
491 latch.Signal();
492 }));
493
494 FlutterEngine* engine = GetFlutterEngine();
495 EXPECT_TRUE([engine runWithEntrypoint:@"nativeCallback"]);
496 ASSERT_TRUE(engine.running);
497
498 latch.Wait();
499 ASSERT_TRUE(latch_called);
500}
501
503 NSString* fixtures = @(flutter::testing::GetFixturesPath());
504 FlutterDartProject* project = [[FlutterDartProject alloc]
505 initWithAssetsPath:fixtures
506 ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]];
507 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"test" project:project];
508
509 FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine
510 nibName:nil
511 bundle:nil];
512 [viewController loadView];
513 [viewController viewDidLoad];
514 viewController.flutterView.frame = CGRectMake(0, 0, 800, 600);
515
516 EXPECT_TRUE([engine runWithEntrypoint:@"canCompositePlatformViews"]);
517
518 [engine.platformViewController registerViewFactory:[[TestPlatformViewFactory alloc] init]
519 withId:@"factory_id"];
520 [engine.platformViewController
521 handleMethodCall:[FlutterMethodCall methodCallWithMethodName:@"create"
522 arguments:@{
523 @"id" : @(42),
524 @"viewType" : @"factory_id",
525 }]
526 result:^(id result){
527 }];
528
529 [engine.testThreadSynchronizer blockUntilFrameAvailable];
530
531 CALayer* rootLayer = viewController.flutterView.layer;
532
533 // There are two layers with Flutter contents and one view
534 EXPECT_EQ(rootLayer.sublayers.count, 2u);
535 EXPECT_EQ(viewController.flutterView.subviews.count, 1u);
536
537 // TODO(gw280): add support for screenshot tests in this test harness
538
539 [engine shutDownEngine];
540}
541
542TEST_F(FlutterEngineTest, CompositorIgnoresUnknownView) {
543 FlutterEngine* engine = GetFlutterEngine();
544 auto original_init = engine.embedderAPI.Initialize;
545 ::FlutterCompositor compositor;
547 Initialize, ([&compositor, &original_init](
548 size_t version, const FlutterRendererConfig* config,
549 const FlutterProjectArgs* args, void* user_data, auto engine_out) {
550 compositor = *args->compositor;
551 return original_init(version, config, args, user_data, engine_out);
552 }));
553
554 FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine
555 nibName:nil
556 bundle:nil];
557 [viewController loadView];
558
559 EXPECT_TRUE([engine runWithEntrypoint:@"empty"]);
560
562 .struct_size = sizeof(FlutterBackingStoreConfig),
563 .size = FlutterSize{10, 10},
564 };
565 FlutterBackingStore backing_store = {};
566 EXPECT_NE(compositor.create_backing_store_callback, nullptr);
568 compositor.create_backing_store_callback(&config, &backing_store, compositor.user_data));
569
570 FlutterLayer layer{
572 .backing_store = &backing_store,
573 };
574 std::vector<FlutterLayer*> layers = {&layer};
575
578 .view_id = 123,
579 .layers = const_cast<const FlutterLayer**>(layers.data()),
580 .layers_count = 1,
581 .user_data = compositor.user_data,
582 };
583 EXPECT_NE(compositor.present_view_callback, nullptr);
584 EXPECT_FALSE(compositor.present_view_callback(&info));
585 EXPECT_TRUE(compositor.collect_backing_store_callback(&backing_store, compositor.user_data));
586
587 (void)viewController;
588 [engine shutDownEngine];
589}
590
591TEST_F(FlutterEngineTest, DartEntrypointArguments) {
592 NSString* fixtures = @(flutter::testing::GetFixturesPath());
593 FlutterDartProject* project = [[FlutterDartProject alloc]
594 initWithAssetsPath:fixtures
595 ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]];
596
597 project.dartEntrypointArguments = @[ @"arg1", @"arg2" ];
598 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"test" project:project];
599
600 bool called = false;
601 auto original_init = engine.embedderAPI.Initialize;
603 Initialize, ([&called, &original_init](size_t version, const FlutterRendererConfig* config,
604 const FlutterProjectArgs* args, void* user_data,
605 FLUTTER_API_SYMBOL(FlutterEngine) * engine_out) {
606 called = true;
607 EXPECT_EQ(args->dart_entrypoint_argc, 2);
608 NSString* arg1 = [[NSString alloc] initWithCString:args->dart_entrypoint_argv[0]
609 encoding:NSUTF8StringEncoding];
610 NSString* arg2 = [[NSString alloc] initWithCString:args->dart_entrypoint_argv[1]
611 encoding:NSUTF8StringEncoding];
612
613 EXPECT_TRUE([arg1 isEqualToString:@"arg1"]);
614 EXPECT_TRUE([arg2 isEqualToString:@"arg2"]);
615
616 return original_init(version, config, args, user_data, engine_out);
617 }));
618
619 EXPECT_TRUE([engine runWithEntrypoint:@"main"]);
620 EXPECT_TRUE(called);
621}
622
623// Verify that the engine is not retained indirectly via the binary messenger held by channels and
624// plugins. Previously, FlutterEngine.binaryMessenger returned the engine itself, and thus plugins
625// could cause a retain cycle, preventing the engine from being deallocated.
626// FlutterEngine.binaryMessenger now returns a FlutterBinaryMessengerRelay whose weak pointer back
627// to the engine is cleared when the engine is deallocated.
628// Issue: https://github.com/flutter/flutter/issues/116445
629TEST_F(FlutterEngineTest, FlutterBinaryMessengerDoesNotRetainEngine) {
630 __weak FlutterEngine* weakEngine;
631 id<FlutterBinaryMessenger> binaryMessenger = nil;
632 @autoreleasepool {
633 // Create a test engine.
634 NSString* fixtures = @(flutter::testing::GetFixturesPath());
635 FlutterDartProject* project = [[FlutterDartProject alloc]
636 initWithAssetsPath:fixtures
637 ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]];
638 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"test"
639 project:project
640 allowHeadlessExecution:YES];
641 weakEngine = engine;
642 binaryMessenger = engine.binaryMessenger;
643 }
644
645 // Once the engine has been deallocated, verify the weak engine pointer is nil, and thus not
646 // retained by the relay.
647 EXPECT_NE(binaryMessenger, nil);
648 EXPECT_EQ(weakEngine, nil);
649}
650
651// Verify that the engine is not retained indirectly via the texture registry held by plugins.
652// Issue: https://github.com/flutter/flutter/issues/116445
653TEST_F(FlutterEngineTest, FlutterTextureRegistryDoesNotReturnEngine) {
654 __weak FlutterEngine* weakEngine;
655 id<FlutterTextureRegistry> textureRegistry;
656 @autoreleasepool {
657 // Create a test engine.
658 NSString* fixtures = @(flutter::testing::GetFixturesPath());
659 FlutterDartProject* project = [[FlutterDartProject alloc]
660 initWithAssetsPath:fixtures
661 ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]];
662 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"test"
663 project:project
664 allowHeadlessExecution:YES];
665 id<FlutterPluginRegistrar> registrar = [engine registrarForPlugin:@"MyPlugin"];
666 textureRegistry = registrar.textures;
667 }
668
669 // Once the engine has been deallocated, verify the weak engine pointer is nil, and thus not
670 // retained via the texture registry.
671 EXPECT_NE(textureRegistry, nil);
672 EXPECT_EQ(weakEngine, nil);
673}
674
675TEST_F(FlutterEngineTest, PublishedValueNilForUnknownPlugin) {
676 NSString* fixtures = @(flutter::testing::GetFixturesPath());
677 FlutterDartProject* project = [[FlutterDartProject alloc]
678 initWithAssetsPath:fixtures
679 ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]];
680 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"test"
681 project:project
682 allowHeadlessExecution:YES];
683
684 EXPECT_EQ([engine valuePublishedByPlugin:@"NoSuchPlugin"], nil);
685}
686
687TEST_F(FlutterEngineTest, PublishedValueNSNullIfNoPublishedValue) {
688 NSString* fixtures = @(flutter::testing::GetFixturesPath());
689 FlutterDartProject* project = [[FlutterDartProject alloc]
690 initWithAssetsPath:fixtures
691 ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]];
692 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"test"
693 project:project
694 allowHeadlessExecution:YES];
695 NSString* pluginName = @"MyPlugin";
696 // Request the registarar to register the plugin as existing.
697 [engine registrarForPlugin:pluginName];
698
699 // The documented behavior is that a plugin that exists but hasn't published
700 // anything returns NSNull, rather than nil, as on iOS.
701 EXPECT_EQ([engine valuePublishedByPlugin:pluginName], [NSNull null]);
702}
703
704TEST_F(FlutterEngineTest, PublishedValueReturnsLastPublished) {
705 NSString* fixtures = @(flutter::testing::GetFixturesPath());
706 FlutterDartProject* project = [[FlutterDartProject alloc]
707 initWithAssetsPath:fixtures
708 ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]];
709 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"test"
710 project:project
711 allowHeadlessExecution:YES];
712 NSString* pluginName = @"MyPlugin";
713 id<FlutterPluginRegistrar> registrar = [engine registrarForPlugin:pluginName];
714
715 NSString* firstValue = @"A published value";
716 NSArray* secondValue = @[ @"A different published value" ];
717
718 [registrar publish:firstValue];
719 EXPECT_EQ([engine valuePublishedByPlugin:pluginName], firstValue);
720
721 [registrar publish:secondValue];
722 EXPECT_EQ([engine valuePublishedByPlugin:pluginName], secondValue);
723}
724
725// If a channel overrides a previous channel with the same name, cleaning
726// the previous channel should not affect the new channel.
727//
728// This is important when recreating classes that uses a channel, because the
729// new instance would create the channel before the first class is deallocated
730// and clears the channel.
731TEST_F(FlutterEngineTest, MessengerCleanupConnectionWorks) {
732 FlutterEngine* engine = GetFlutterEngine();
733 EXPECT_TRUE([engine runWithEntrypoint:@"main"]);
734
735 NSString* channel = @"_test_";
736 NSData* channel_data = [channel dataUsingEncoding:NSUTF8StringEncoding];
737
738 // Mock SendPlatformMessage so that if a message is sent to
739 // "test/send_message", act as if the framework has sent an empty message to
740 // the channel marked by the `sendOnChannel:message:` call's message.
742 SendPlatformMessage, ([](auto engine_, auto message_) {
743 if (strcmp(message_->channel, "test/send_message") == 0) {
744 // The simplest message that is acceptable to a method channel.
745 std::string message = R"|({"method": "a"})|";
746 std::string channel(reinterpret_cast<const char*>(message_->message),
747 message_->message_size);
748 reinterpret_cast<EmbedderEngine*>(engine_)
749 ->GetShell()
750 .GetPlatformView()
751 ->HandlePlatformMessage(std::make_unique<PlatformMessage>(
752 channel.c_str(), fml::MallocMapping::Copy(message.c_str(), message.length()),
753 fml::RefPtr<PlatformMessageResponse>()));
754 }
755 return kSuccess;
756 }));
757
758 __block int record = 0;
759
760 FlutterMethodChannel* channel1 =
762 binaryMessenger:engine.binaryMessenger
764 [channel1 setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
765 record += 1;
766 }];
767
768 [engine.binaryMessenger sendOnChannel:@"test/send_message" message:channel_data];
769 EXPECT_EQ(record, 1);
770
771 FlutterMethodChannel* channel2 =
773 binaryMessenger:engine.binaryMessenger
775 [channel2 setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
776 record += 10;
777 }];
778
779 [engine.binaryMessenger sendOnChannel:@"test/send_message" message:channel_data];
780 EXPECT_EQ(record, 11);
781
782 [channel1 setMethodCallHandler:nil];
783
784 [engine.binaryMessenger sendOnChannel:@"test/send_message" message:channel_data];
785 EXPECT_EQ(record, 21);
786}
787
788TEST_F(FlutterEngineTest, HasStringsWhenPasteboardEmpty) {
789 id engineMock = CreateMockFlutterEngine(nil);
790
791 // Call hasStrings and expect it to be false.
792 __block bool calledAfterClear = false;
793 __block bool valueAfterClear;
794 FlutterResult resultAfterClear = ^(id result) {
795 calledAfterClear = true;
796 NSNumber* valueNumber = [result valueForKey:@"value"];
797 valueAfterClear = [valueNumber boolValue];
798 };
799 FlutterMethodCall* methodCallAfterClear =
800 [FlutterMethodCall methodCallWithMethodName:@"Clipboard.hasStrings" arguments:nil];
801 [engineMock handleMethodCall:methodCallAfterClear result:resultAfterClear];
802 EXPECT_TRUE(calledAfterClear);
803 EXPECT_FALSE(valueAfterClear);
804}
805
806TEST_F(FlutterEngineTest, HasStringsWhenPasteboardFull) {
807 id engineMock = CreateMockFlutterEngine(@"some string");
808
809 // Call hasStrings and expect it to be true.
810 __block bool called = false;
811 __block bool value;
812 FlutterResult result = ^(id result) {
813 called = true;
814 NSNumber* valueNumber = [result valueForKey:@"value"];
815 value = [valueNumber boolValue];
816 };
817 FlutterMethodCall* methodCall =
818 [FlutterMethodCall methodCallWithMethodName:@"Clipboard.hasStrings" arguments:nil];
819 [engineMock handleMethodCall:methodCall result:result];
820 EXPECT_TRUE(called);
822}
823
824TEST_F(FlutterEngineTest, ResponseAfterEngineDied) {
825 FlutterEngine* engine = GetFlutterEngine();
827 initWithName:@"foo"
828 binaryMessenger:engine.binaryMessenger
830 __block BOOL didCallCallback = NO;
831 [channel setMessageHandler:^(id message, FlutterReply callback) {
832 ShutDownEngine();
833 callback(nil);
834 didCallCallback = YES;
835 }];
836 EXPECT_TRUE([engine runWithEntrypoint:@"sendFooMessage"]);
837 engine = nil;
838
839 while (!didCallCallback) {
840 [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
841 }
842}
843
844TEST_F(FlutterEngineTest, ResponseFromBackgroundThread) {
845 FlutterEngine* engine = GetFlutterEngine();
847 initWithName:@"foo"
848 binaryMessenger:engine.binaryMessenger
850 __block BOOL didCallCallback = NO;
851 [channel setMessageHandler:^(id message, FlutterReply callback) {
852 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
853 callback(nil);
854 dispatch_async(dispatch_get_main_queue(), ^{
855 didCallCallback = YES;
856 });
857 });
858 }];
859 EXPECT_TRUE([engine runWithEntrypoint:@"sendFooMessage"]);
860
861 while (!didCallCallback) {
862 [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
863 }
864}
865
866TEST_F(FlutterEngineTest, ThreadSynchronizerNotBlockingRasterThreadAfterShutdown) {
867 FlutterThreadSynchronizer* threadSynchronizer = [[FlutterThreadSynchronizer alloc] init];
868 [threadSynchronizer shutdown];
869
870 std::thread rasterThread([&threadSynchronizer] {
871 [threadSynchronizer performCommitForView:kImplicitViewId
872 size:CGSizeMake(100, 100)
873 notify:^{
874 }];
875 });
876
877 rasterThread.join();
878}
879
880TEST_F(FlutterEngineTest, ManageControllersIfInitiatedByController) {
881 NSString* fixtures = @(flutter::testing::GetFixturesPath());
882 FlutterDartProject* project = [[FlutterDartProject alloc]
883 initWithAssetsPath:fixtures
884 ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]];
885
887 FlutterViewController* viewController1;
888
889 @autoreleasepool {
890 // Create FVC1.
891 viewController1 = [[FlutterViewController alloc] initWithProject:project];
892 EXPECT_EQ(viewController1.viewIdentifier, 0ll);
893
894 engine = viewController1.engine;
896
897 // Create FVC2 based on the same engine.
898 FlutterViewController* viewController2 = [[FlutterViewController alloc] initWithEngine:engine
899 nibName:nil
900 bundle:nil];
901 EXPECT_EQ(engine.viewController, viewController2);
902 }
903 // FVC2 is deallocated but FVC1 is retained.
904
905 EXPECT_EQ(engine.viewController, nil);
906
907 engine.viewController = viewController1;
908 EXPECT_EQ(engine.viewController, viewController1);
909 EXPECT_EQ(viewController1.viewIdentifier, 0ll);
910}
911
912TEST_F(FlutterEngineTest, ManageControllersIfInitiatedByEngine) {
913 // Don't create the engine with `CreateMockFlutterEngine`, because it adds
914 // additional references to FlutterViewControllers, which is crucial to this
915 // test case.
916 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"io.flutter"
917 project:nil
918 allowHeadlessExecution:NO];
919 FlutterViewController* viewController1;
920
921 @autoreleasepool {
922 viewController1 = [[FlutterViewController alloc] initWithEngine:engine nibName:nil bundle:nil];
923 EXPECT_EQ(viewController1.viewIdentifier, 0ll);
924 EXPECT_EQ(engine.viewController, viewController1);
925
927
928 FlutterViewController* viewController2 = [[FlutterViewController alloc] initWithEngine:engine
929 nibName:nil
930 bundle:nil];
931 EXPECT_EQ(viewController2.viewIdentifier, 0ll);
932 EXPECT_EQ(engine.viewController, viewController2);
933 }
934 // FVC2 is deallocated but FVC1 is retained.
935
936 EXPECT_EQ(engine.viewController, nil);
937
938 engine.viewController = viewController1;
939 EXPECT_EQ(engine.viewController, viewController1);
940 EXPECT_EQ(viewController1.viewIdentifier, 0ll);
941}
942
943TEST_F(FlutterEngineTest, HandlesTerminationRequest) {
944 id engineMock = CreateMockFlutterEngine(nil);
945 __block NSString* nextResponse = @"exit";
946 __block BOOL triedToTerminate = NO;
947 FlutterEngineTerminationHandler* terminationHandler =
948 [[FlutterEngineTerminationHandler alloc] initWithEngine:engineMock
949 terminator:^(id sender) {
950 triedToTerminate = TRUE;
951 // Don't actually terminate, of course.
952 }];
953 OCMStub([engineMock terminationHandler]).andReturn(terminationHandler);
954 id binaryMessengerMock = OCMProtocolMock(@protocol(FlutterBinaryMessenger));
955 OCMStub( // NOLINT(google-objc-avoid-throwing-exception)
956 [engineMock binaryMessenger])
957 .andReturn(binaryMessengerMock);
958 OCMStub([engineMock sendOnChannel:@"flutter/platform"
959 message:[OCMArg any]
960 binaryReply:[OCMArg any]])
961 .andDo((^(NSInvocation* invocation) {
962 [invocation retainArguments];
964 NSData* returnedMessage;
965 [invocation getArgument:&callback atIndex:4];
966 if ([nextResponse isEqualToString:@"error"]) {
967 FlutterError* errorResponse = [FlutterError errorWithCode:@"Error"
968 message:@"Failed"
969 details:@"Details"];
970 returnedMessage =
971 [[FlutterJSONMethodCodec sharedInstance] encodeErrorEnvelope:errorResponse];
972 } else {
973 NSDictionary* responseDict = @{@"response" : nextResponse};
974 returnedMessage =
975 [[FlutterJSONMethodCodec sharedInstance] encodeSuccessEnvelope:responseDict];
976 }
977 callback(returnedMessage);
978 }));
979 __block NSString* calledAfterTerminate = @"";
980 FlutterResult appExitResult = ^(id result) {
981 NSDictionary* resultDict = result;
982 calledAfterTerminate = resultDict[@"response"];
983 };
984 FlutterMethodCall* methodExitApplication =
985 [FlutterMethodCall methodCallWithMethodName:@"System.exitApplication"
986 arguments:@{@"type" : @"cancelable"}];
987
988 // Always terminate when the binding isn't ready (which is the default).
989 triedToTerminate = NO;
990 calledAfterTerminate = @"";
991 nextResponse = @"cancel";
992 [engineMock handleMethodCall:methodExitApplication result:appExitResult];
993 EXPECT_STREQ([calledAfterTerminate UTF8String], "");
994 EXPECT_TRUE(triedToTerminate);
995
996 // Once the binding is ready, handle the request.
997 terminationHandler.acceptingRequests = YES;
998 triedToTerminate = NO;
999 calledAfterTerminate = @"";
1000 nextResponse = @"exit";
1001 [engineMock handleMethodCall:methodExitApplication result:appExitResult];
1002 EXPECT_STREQ([calledAfterTerminate UTF8String], "exit");
1003 EXPECT_TRUE(triedToTerminate);
1004
1005 triedToTerminate = NO;
1006 calledAfterTerminate = @"";
1007 nextResponse = @"cancel";
1008 [engineMock handleMethodCall:methodExitApplication result:appExitResult];
1009 EXPECT_STREQ([calledAfterTerminate UTF8String], "cancel");
1010 EXPECT_FALSE(triedToTerminate);
1011
1012 // Check that it doesn't crash on error.
1013 triedToTerminate = NO;
1014 calledAfterTerminate = @"";
1015 nextResponse = @"error";
1016 [engineMock handleMethodCall:methodExitApplication result:appExitResult];
1017 EXPECT_STREQ([calledAfterTerminate UTF8String], "");
1018 EXPECT_TRUE(triedToTerminate);
1019}
1020
1021TEST_F(FlutterEngineTest, IgnoresTerminationRequestIfNotFlutterAppDelegate) {
1022 id<NSApplicationDelegate> previousDelegate = [[NSApplication sharedApplication] delegate];
1023 id<NSApplicationDelegate> plainDelegate = [[PlainAppDelegate alloc] init];
1024 [NSApplication sharedApplication].delegate = plainDelegate;
1025
1026 // Creating the engine shouldn't fail here, even though the delegate isn't a
1027 // FlutterAppDelegate.
1029
1030 // Asking to terminate the app should cancel.
1031 EXPECT_EQ([[[NSApplication sharedApplication] delegate] applicationShouldTerminate:NSApp],
1032 NSTerminateCancel);
1033
1034 [NSApplication sharedApplication].delegate = previousDelegate;
1035}
1036
1037TEST_F(FlutterEngineTest, HandleAccessibilityEvent) {
1038 __block BOOL announced = NO;
1039 id engineMock = CreateMockFlutterEngine(nil);
1040
1041 OCMStub([engineMock announceAccessibilityMessage:[OCMArg any]
1042 withPriority:NSAccessibilityPriorityMedium])
1043 .andDo((^(NSInvocation* invocation) {
1044 announced = TRUE;
1045 [invocation retainArguments];
1046 NSString* message;
1047 [invocation getArgument:&message atIndex:2];
1048 EXPECT_EQ(message, @"error message");
1049 }));
1050
1051 NSDictionary<NSString*, id>* annotatedEvent =
1052 @{@"type" : @"announce",
1053 @"data" : @{@"message" : @"error message"}};
1054
1055 [engineMock handleAccessibilityEvent:annotatedEvent];
1056
1057 EXPECT_TRUE(announced);
1058}
1059
1060TEST_F(FlutterEngineTest, HandleLifecycleStates) API_AVAILABLE(macos(10.9)) {
1061 __block flutter::AppLifecycleState sentState;
1062 id engineMock = CreateMockFlutterEngine(nil);
1063
1064 // Have to enumerate all the values because OCMStub can't capture
1065 // non-Objective-C object arguments.
1066 OCMStub([engineMock setApplicationState:flutter::AppLifecycleState::kDetached])
1067 .andDo((^(NSInvocation* invocation) {
1069 }));
1070 OCMStub([engineMock setApplicationState:flutter::AppLifecycleState::kResumed])
1071 .andDo((^(NSInvocation* invocation) {
1073 }));
1074 OCMStub([engineMock setApplicationState:flutter::AppLifecycleState::kInactive])
1075 .andDo((^(NSInvocation* invocation) {
1077 }));
1078 OCMStub([engineMock setApplicationState:flutter::AppLifecycleState::kHidden])
1079 .andDo((^(NSInvocation* invocation) {
1081 }));
1082 OCMStub([engineMock setApplicationState:flutter::AppLifecycleState::kPaused])
1083 .andDo((^(NSInvocation* invocation) {
1085 }));
1086
1087 __block NSApplicationOcclusionState visibility = NSApplicationOcclusionStateVisible;
1088 id mockApplication = OCMPartialMock([NSApplication sharedApplication]);
1089 OCMStub((NSApplicationOcclusionState)[mockApplication occlusionState])
1090 .andDo(^(NSInvocation* invocation) {
1091 [invocation setReturnValue:&visibility];
1092 });
1093
1094 NSNotification* willBecomeActive =
1095 [[NSNotification alloc] initWithName:NSApplicationWillBecomeActiveNotification
1096 object:nil
1097 userInfo:nil];
1098 NSNotification* willResignActive =
1099 [[NSNotification alloc] initWithName:NSApplicationWillResignActiveNotification
1100 object:nil
1101 userInfo:nil];
1102
1103 NSNotification* didChangeOcclusionState;
1104 didChangeOcclusionState =
1105 [[NSNotification alloc] initWithName:NSApplicationDidChangeOcclusionStateNotification
1106 object:nil
1107 userInfo:nil];
1108
1109 [engineMock handleDidChangeOcclusionState:didChangeOcclusionState];
1110 EXPECT_EQ(sentState, flutter::AppLifecycleState::kInactive);
1111
1112 [engineMock handleWillBecomeActive:willBecomeActive];
1113 EXPECT_EQ(sentState, flutter::AppLifecycleState::kResumed);
1114
1115 [engineMock handleWillResignActive:willResignActive];
1116 EXPECT_EQ(sentState, flutter::AppLifecycleState::kInactive);
1117
1118 visibility = 0;
1119 [engineMock handleDidChangeOcclusionState:didChangeOcclusionState];
1120 EXPECT_EQ(sentState, flutter::AppLifecycleState::kHidden);
1121
1122 [engineMock handleWillBecomeActive:willBecomeActive];
1123 EXPECT_EQ(sentState, flutter::AppLifecycleState::kHidden);
1124
1125 [engineMock handleWillResignActive:willResignActive];
1126 EXPECT_EQ(sentState, flutter::AppLifecycleState::kHidden);
1127
1128 [mockApplication stopMocking];
1129}
1130
1131TEST_F(FlutterEngineTest, ForwardsPluginDelegateRegistration) {
1132 id<NSApplicationDelegate> previousDelegate = [[NSApplication sharedApplication] delegate];
1133 FakeLifecycleProvider* fakeAppDelegate = [[FakeLifecycleProvider alloc] init];
1134 [NSApplication sharedApplication].delegate = fakeAppDelegate;
1135
1136 FakeAppDelegatePlugin* plugin = [[FakeAppDelegatePlugin alloc] init];
1138
1139 [[engine registrarForPlugin:@"TestPlugin"] addApplicationDelegate:plugin];
1140
1141 EXPECT_TRUE([fakeAppDelegate hasDelegate:plugin]);
1142
1143 [NSApplication sharedApplication].delegate = previousDelegate;
1144}
1145
1146TEST_F(FlutterEngineTest, UnregistersPluginsOnEngineDestruction) {
1147 id<NSApplicationDelegate> previousDelegate = [[NSApplication sharedApplication] delegate];
1148 FakeLifecycleProvider* fakeAppDelegate = [[FakeLifecycleProvider alloc] init];
1149 [NSApplication sharedApplication].delegate = fakeAppDelegate;
1150
1151 FakeAppDelegatePlugin* plugin = [[FakeAppDelegatePlugin alloc] init];
1152
1153 @autoreleasepool {
1154 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"test" project:nil];
1155
1156 [[engine registrarForPlugin:@"TestPlugin"] addApplicationDelegate:plugin];
1157 EXPECT_TRUE([fakeAppDelegate hasDelegate:plugin]);
1158 }
1159
1160 // When the engine is released, it should unregister any plugins it had
1161 // registered on its behalf.
1162 EXPECT_FALSE([fakeAppDelegate hasDelegate:plugin]);
1163
1164 [NSApplication sharedApplication].delegate = previousDelegate;
1165}
1166
1167TEST_F(FlutterEngineTest, RunWithEntrypointUpdatesDisplayConfig) {
1168 BOOL updated = NO;
1169 FlutterEngine* engine = GetFlutterEngine();
1170 auto original_update_displays = engine.embedderAPI.NotifyDisplayUpdate;
1172 NotifyDisplayUpdate, ([&updated, &original_update_displays](
1173 auto engine, auto update_type, auto* displays, auto display_count) {
1174 updated = YES;
1175 return original_update_displays(engine, update_type, displays, display_count);
1176 }));
1177
1178 EXPECT_TRUE([engine runWithEntrypoint:@"main"]);
1179 EXPECT_TRUE(updated);
1180
1181 updated = NO;
1182 [[NSNotificationCenter defaultCenter]
1183 postNotificationName:NSApplicationDidChangeScreenParametersNotification
1184 object:nil];
1185 EXPECT_TRUE(updated);
1186}
1187
1188TEST_F(FlutterEngineTest, NotificationsUpdateDisplays) {
1189 BOOL updated = NO;
1190 FlutterEngine* engine = GetFlutterEngine();
1191 auto original_set_viewport_metrics = engine.embedderAPI.SendWindowMetricsEvent;
1193 SendWindowMetricsEvent,
1194 ([&updated, &original_set_viewport_metrics](auto engine, auto* window_metrics) {
1195 updated = YES;
1196 return original_set_viewport_metrics(engine, window_metrics);
1197 }));
1198
1199 EXPECT_TRUE([engine runWithEntrypoint:@"main"]);
1200
1201 updated = NO;
1202 [[NSNotificationCenter defaultCenter] postNotificationName:NSWindowDidChangeScreenNotification
1203 object:nil];
1204 // No VC.
1205 EXPECT_FALSE(updated);
1206
1207 FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine
1208 nibName:nil
1209 bundle:nil];
1210 [viewController loadView];
1211 viewController.flutterView.frame = CGRectMake(0, 0, 800, 600);
1212
1213 [[NSNotificationCenter defaultCenter] postNotificationName:NSWindowDidChangeScreenNotification
1214 object:nil];
1215 EXPECT_TRUE(updated);
1216}
1217
1218TEST_F(FlutterEngineTest, DisplaySizeIsInPhysicalPixel) {
1219 NSString* fixtures = @(testing::GetFixturesPath());
1220 FlutterDartProject* project = [[FlutterDartProject alloc]
1221 initWithAssetsPath:fixtures
1222 ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]];
1223 project.rootIsolateCreateCallback = FlutterEngineTest::IsolateCreateCallback;
1224 MockableFlutterEngine* engine = [[MockableFlutterEngine alloc] initWithName:@"foobar"
1225 project:project
1226 allowHeadlessExecution:true];
1227 BOOL updated = NO;
1228 auto original_update_displays = engine.embedderAPI.NotifyDisplayUpdate;
1230 NotifyDisplayUpdate, ([&updated, &original_update_displays](
1231 auto engine, auto update_type, auto* displays, auto display_count) {
1232 EXPECT_EQ(display_count, 1UL);
1233 EXPECT_EQ(displays->display_id, 10UL);
1234 EXPECT_EQ(displays->width, 60UL);
1235 EXPECT_EQ(displays->height, 80UL);
1236 EXPECT_EQ(displays->device_pixel_ratio, 2UL);
1237 updated = YES;
1238 return original_update_displays(engine, update_type, displays, display_count);
1239 }));
1240 EXPECT_TRUE([engine runWithEntrypoint:@"main"]);
1241 EXPECT_TRUE(updated);
1242 [engine shutDownEngine];
1243 engine = nil;
1244}
1245
1246} // namespace flutter::testing
1247
1248// NOLINTEND(clang-analyzer-core.StackAddressEscape)
static void info(const char *fmt,...) SK_PRINTF_LIKE(1
Definition DM.cpp:213
NS_ASSUME_NONNULL_BEGIN typedef void(^ FlutterBinaryReply)(NSData *_Nullable reply)
void(^ FlutterResult)(id _Nullable result)
NSPointerArray * _delegates
int count
flutter::FlutterCompositor * macOSCompositor
DART_EXPORT Dart_Handle Dart_GetNativeArgument(Dart_NativeArguments args, int index)
struct _Dart_NativeArguments * Dart_NativeArguments
Definition dart_api.h:3010
#define FLUTTER_API_SYMBOL(symbol)
Definition embedder.h:67
@ kFlutterLayerContentTypeBackingStore
Definition embedder.h:1791
@ kSuccess
Definition embedder.h:73
FlutterSemanticsAction
Definition embedder.h:113
FlutterSemanticsFlag
Definition embedder.h:170
FlutterEngine engine
Definition main.cc:68
double frame
Definition examples.cpp:31
G_BEGIN_DECLS G_MODULE_EXPORT FlValue * args
TEST_F(FlGnomeSettingsTest, ClockFormat)
FlKeyEvent uint64_t FlKeyResponderAsyncCallback callback
uint8_t value
GAsyncResult * result
void setMessageHandler:(FlutterMessageHandler _Nullable handler)
void(* rootIsolateCreateCallback)(void *_Nullable)
FlutterEngineProcTable & embedderAPI()
NSObject< FlutterBinaryMessenger > * binaryMessenger
std::vector< std::string > switches()
FlutterViewController * viewController
instancetype errorWithCode:message:details:(NSString *code,[message] NSString *_Nullable message,[details] id _Nullable details)
instancetype sharedInstance()
instancetype methodCallWithMethodName:arguments:(NSString *method,[arguments] id _Nullable arguments)
void setMethodCallHandler:(FlutterMethodCallHandler _Nullable handler)
instancetype methodChannelWithName:binaryMessenger:codec:(NSString *name,[binaryMessenger] NSObject< FlutterBinaryMessenger > *messenger,[codec] NSObject< FlutterMethodCodec > *codec)
void performCommitForView:size:notify:(FlutterViewIdentifier viewIdentifier,[size] CGSize size,[notify] nonnull dispatch_block_t notify)
std::weak_ptr< flutter::AccessibilityBridgeMac > accessibilityBridge()
FlutterViewController * viewController
UITextSmartQuotesType smartQuotesType API_AVAILABLE(ios(11.0))
Win32Message message
int64_t FlutterViewIdentifier
constexpr int64_t kImplicitViewId
const char * GetFixturesPath()
Returns the directory containing the test fixture for the target if this target has fixtures configur...
id CreateMockFlutterEngine(NSString *pasteboardString)
it will be possible to load the file into Perfetto s trace viewer disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive keep the shell running after the Dart script has completed enable serial On low power devices with low core running concurrent GC tasks on threads can cause them to contend with the UI thread which could potentially lead to jank This option turns off all concurrent GC activities domain network JSON encoded network policy per domain This overrides the DisallowInsecureConnections switch Embedder can specify whether to allow or disallow insecure connections at a domain level old gen heap size
Definition switches.h:259
#define MOCK_ENGINE_PROC(proc, mock_impl)
FlutterBackingStoreCreateCallback create_backing_store_callback
Definition embedder.h:1901
FlutterPresentViewCallback present_view_callback
Definition embedder.h:1930
FlutterBackingStoreCollectCallback collect_backing_store_callback
Definition embedder.h:1906
FlutterEngineSendWindowMetricsEventFnPtr SendWindowMetricsEvent
Definition embedder.h:3328
FlutterEngineInitializeFnPtr Initialize
Definition embedder.h:3325
FlutterEngineNotifyDisplayUpdateFnPtr NotifyDisplayUpdate
Definition embedder.h:3358
FlutterEngineSendPlatformMessageFnPtr SendPlatformMessage
Definition embedder.h:3331
FlutterEngineUpdateSemanticsEnabledFnPtr UpdateSemanticsEnabled
Definition embedder.h:3341
FlutterLayerContentType type
Definition embedder.h:1822
const char * increased_value
Definition embedder.h:1368
const char * tooltip
A textual tooltip attached to the node.
Definition embedder.h:1395
size_t custom_accessibility_actions_count
The number of custom accessibility action associated with this node.
Definition embedder.h:1387
int32_t text_selection_extent
The position at which the text selection terminates.
Definition embedder.h:1343
FlutterSemanticsAction actions
The set of semantics actions applicable to this node.
Definition embedder.h:1339
int32_t id
The unique identifier for this node.
Definition embedder.h:1335
size_t child_count
The number of children this node has.
Definition embedder.h:1381
const char * decreased_value
Definition embedder.h:1371
const char * label
A textual description of the node.
Definition embedder.h:1361
int32_t text_selection_base
The position at which the text selection originates.
Definition embedder.h:1341
const char * hint
A brief description of the result of performing an action on the node.
Definition embedder.h:1363
const char * value
A textual description of the current value of the node.
Definition embedder.h:1365
FlutterSemanticsFlag flags
The set of semantics flags associated with this node.
Definition embedder.h:1337
A batch of updates to semantics nodes and custom actions.
Definition embedder.h:1502
A structure to represent the width and height.
Definition embedder.h:421
#define CREATE_NATIVE_ENTRY(native_entry)
const uintptr_t id
#define EXPECT_TRUE(handle)
Definition unit_test.h:685
int BOOL