Flutter Engine Uber Docs
Docs for the entire Flutter Engine repo.
 
Loading...
Searching...
No Matches
FlutterWindowControllerTest.mm
Go to the documentation of this file.
1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
7
14#import "third_party/googletest/googletest/include/gtest/gtest.h"
15
16namespace flutter::testing {
17
19 public:
21
22 void SetUp() {
24
25 [GetFlutterEngine() runWithEntrypoint:@"testWindowController"];
26
27 signalled_ = false;
28
29 AddNativeCallback("SignalNativeTest", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
31 signalled_ = true;
32 }));
33
34 while (!signalled_) {
35 CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, false);
36 }
37 }
38
39 void TearDown() {
40 [GetFlutterEngine().windowController closeAllWindows];
42 }
43
44 protected:
46 if (isolate_) {
47 return *isolate_;
48 } else {
49 FML_LOG(ERROR) << "Isolate is not set.";
51 }
52 }
53
54 std::optional<flutter::Isolate> isolate_;
56};
57
58class FlutterWindowControllerRetainTest : public ::testing::Test {};
59
60TEST_F(FlutterWindowControllerTest, FixMoveRunLoopMode) {
61 FlutterEngine* engine = GetFlutterEngine();
62 [engine.windowController fixMoveRunLoopModeIfNeeded];
63
64 // Make sure that after fixMoveRunLoopMode _NSMoveTimerRunLoopMode becomes
65 // common run loop mode.
66 __block BOOL signalled = NO;
67 CFRunLoopPerformBlock(CFRunLoopGetCurrent(), kCFRunLoopCommonModes, ^{
68 signalled = YES;
69 });
70 CFRunLoopRunInMode(CFSTR("_NSMoveTimerRunLoopMode"), 1.0, YES);
71 EXPECT_TRUE(signalled);
72}
73
74TEST_F(FlutterWindowControllerTest, CreateRegularWindow) {
76 .has_size = true,
77 .size = {.width = 800, .height = 600},
78 .on_should_close = [] {},
79 .on_will_close = [] {},
80 .notify_listeners = [] {},
81 };
82
83 FlutterEngine* engine = GetFlutterEngine();
84 int64_t engineId = reinterpret_cast<int64_t>(engine);
85
86 {
87 IsolateScope isolate_scope(isolate());
88 int64_t handle = InternalFlutter_WindowController_CreateRegularWindow(engineId, &request);
89 EXPECT_EQ(handle, 1);
90
91 FlutterViewController* viewController = [engine viewControllerForIdentifier:handle];
92 EXPECT_NE(viewController, nil);
93 CGSize size = viewController.view.frame.size;
94 EXPECT_EQ(size.width, 800);
95 EXPECT_EQ(size.height, 600);
96 }
97}
98
99TEST_F(FlutterWindowControllerTest, CreateTooltipWindow) {
100 IsolateScope isolate_scope(isolate());
101 FlutterEngine* engine = GetFlutterEngine();
102 int64_t engineId = reinterpret_cast<int64_t>(engine);
103
104 auto request = FlutterWindowCreationRequest{
105 .has_size = true,
106 .size = {.width = 800, .height = 600},
107 .on_should_close = [] {},
108 .on_will_close = [] {},
109 .notify_listeners = [] {},
110 };
111 int64_t parentViewId = InternalFlutter_WindowController_CreateRegularWindow(engineId, &request);
112 EXPECT_EQ(parentViewId, 1);
113
114 auto position_callback = [](const FlutterWindowSize& child_size,
115 const FlutterWindowRect& parent_rect,
116 const FlutterWindowRect& output_rect) -> FlutterWindowRect* {
117 FlutterWindowRect* rect = static_cast<FlutterWindowRect*>(malloc(sizeof(FlutterWindowRect)));
118 rect->left = parent_rect.left + 10;
119 rect->top = parent_rect.top + 10;
120 rect->width = child_size.width;
121 rect->height = child_size.height;
122 return rect;
123 };
124
126 .has_constraints = true,
127 .constraints{
128 .max_width = 1000,
129 .max_height = 1000,
130 },
131 .parent_view_id = parentViewId,
132 .on_should_close = [] {},
133 .on_will_close = [] {},
134 .notify_listeners = [] {},
135 .on_get_window_position = position_callback,
136 };
137
138 const int64_t tooltipViewId =
140 EXPECT_NE(tooltipViewId, 0);
141}
142
144 IsolateScope isolate_scope(isolate());
145 FlutterEngine* engine = GetFlutterEngine();
146 int64_t engineId = reinterpret_cast<int64_t>(engine);
147
148 auto request = FlutterWindowCreationRequest{
149 .has_size = true,
150 .size = {.width = 800, .height = 600},
151 .on_should_close = [] {},
152 .on_will_close = [] {},
153 .notify_listeners = [] {},
154 };
155 int64_t parentViewId = InternalFlutter_WindowController_CreateRegularWindow(engineId, &request);
156 EXPECT_EQ(parentViewId, 1);
157
158 auto position_callback = [](const FlutterWindowSize& child_size,
159 const FlutterWindowRect& parent_rect,
160 const FlutterWindowRect& output_rect) -> FlutterWindowRect* {
161 FlutterWindowRect* rect = static_cast<FlutterWindowRect*>(malloc(sizeof(FlutterWindowRect)));
162 rect->left = parent_rect.left + 10;
163 rect->top = parent_rect.top + 10;
164 rect->width = child_size.width;
165 rect->height = child_size.height;
166 return rect;
167 };
168
170 .has_constraints = true,
171 .constraints{
172 .max_width = 1000,
173 .max_height = 1000,
174 },
175 .parent_view_id = parentViewId,
176 .on_should_close = [] {},
177 .on_will_close = [] {},
178 .notify_listeners = [] {},
179 .on_get_window_position = position_callback,
180 };
181
182 const int64_t tooltipViewId =
184 EXPECT_NE(tooltipViewId, 0);
185}
186
187TEST_F(FlutterWindowControllerRetainTest, WindowControllerDoesNotRetainEngine) {
189 .has_size = true,
190 .size = {.width = 800, .height = 600},
191 .on_should_close = [] {},
192 .on_will_close = [] {},
193 .notify_listeners = [] {},
194 };
195
196 __weak FlutterEngine* weakEngine = nil;
197 @autoreleasepool {
198 NSString* fixtures = @(flutter::testing::GetFixturesPath());
199 NSLog(@"Fixtures path: %@", fixtures);
200 FlutterDartProject* project = [[FlutterDartProject alloc]
201 initWithAssetsPath:fixtures
202 ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]];
203
204 static std::optional<flutter::Isolate> isolate;
205 isolate = std::nullopt;
206
207 project.rootIsolateCreateCallback = [](void*) { isolate = flutter::Isolate::Current(); };
208 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"test"
209 project:project
210 allowHeadlessExecution:YES];
211 weakEngine = engine;
212 [engine runWithEntrypoint:@"testWindowControllerRetainCycle"];
213
214 int64_t engineId = reinterpret_cast<int64_t>(engine);
215
216 {
217 FML_DCHECK(isolate.has_value());
218 // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
219 IsolateScope isolateScope(*isolate);
220 int64_t handle = InternalFlutter_WindowController_CreateRegularWindow(engineId, &request);
221 EXPECT_EQ(handle, 1);
222 }
223
224 [engine.windowController closeAllWindows];
225 [engine shutDownEngine];
226 }
227 EXPECT_EQ(weakEngine, nil);
228}
229
230TEST_F(FlutterWindowControllerTest, DestroyRegularWindow) {
232 .has_size = true,
233 .size = {.width = 800, .height = 600},
234 .on_should_close = [] {},
235 .on_will_close = [] {},
236 .notify_listeners = [] {},
237 };
238
239 FlutterEngine* engine = GetFlutterEngine();
240 int64_t engine_id = reinterpret_cast<int64_t>(engine);
241
242 IsolateScope isolate_scope(isolate());
243 int64_t handle = InternalFlutter_WindowController_CreateRegularWindow(engine_id, &request);
244 FlutterViewController* viewController = [engine viewControllerForIdentifier:handle];
245
246 InternalFlutter_Window_Destroy(engine_id, (__bridge void*)viewController.view.window);
247 viewController = [engine viewControllerForIdentifier:handle];
248 EXPECT_EQ(viewController, nil);
249}
250
251TEST_F(FlutterWindowControllerTest, InternalFlutterWindowGetHandle) {
253 .has_size = true,
254 .size = {.width = 800, .height = 600},
255 .on_should_close = [] {},
256 .on_will_close = [] {},
257 .notify_listeners = [] {},
258 };
259
260 FlutterEngine* engine = GetFlutterEngine();
261 int64_t engine_id = reinterpret_cast<int64_t>(engine);
262
263 IsolateScope isolate_scope(isolate());
264 int64_t handle = InternalFlutter_WindowController_CreateRegularWindow(engine_id, &request);
265 FlutterViewController* viewController = [engine viewControllerForIdentifier:handle];
266
267 void* window_handle = InternalFlutter_Window_GetHandle(engine_id, handle);
268 EXPECT_EQ(window_handle, (__bridge void*)viewController.view.window);
269}
270
273 .has_size = true,
274 .size = {.width = 800, .height = 600},
275 .on_should_close = [] {},
276 .on_will_close = [] {},
277 .notify_listeners = [] {},
278 };
279
280 FlutterEngine* engine = GetFlutterEngine();
281 int64_t engine_id = reinterpret_cast<int64_t>(engine);
282
283 IsolateScope isolate_scope(isolate());
284 int64_t handle = InternalFlutter_WindowController_CreateRegularWindow(engine_id, &request);
285
286 FlutterViewController* viewController = [engine viewControllerForIdentifier:handle];
287 NSWindow* window = viewController.view.window;
288 void* windowHandle = (__bridge void*)window;
289
290 EXPECT_EQ(window.zoomed, NO);
291 EXPECT_EQ(window.miniaturized, NO);
292 EXPECT_EQ(window.styleMask & NSWindowStyleMaskFullScreen, 0u);
293
294 InternalFlutter_Window_SetMaximized(windowHandle, true);
295 CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.5, false);
296 EXPECT_EQ(window.zoomed, YES);
297
298 InternalFlutter_Window_SetMaximized(windowHandle, false);
299 CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.5, false);
300 EXPECT_EQ(window.zoomed, NO);
301
302 // FullScreen toggle does not seem to work when the application is not run from a bundle.
303
305 CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.5, false);
306 EXPECT_EQ(window.miniaturized, YES);
307}
308
309TEST_F(FlutterWindowControllerTest, ClosesAllWindowsOnEngineRestart) {
311 .has_size = true,
312 .size = {.width = 800, .height = 600},
313 .on_should_close = [] {},
314 .on_will_close = [] {},
315 .notify_listeners = [] {},
316 };
317 FlutterEngine* engine = GetFlutterEngine();
318 int64_t engine_id = reinterpret_cast<int64_t>(engine);
319
320 IsolateScope isolate_scope(isolate());
321
322 // Create multiple windows
323 int64_t handle1 = InternalFlutter_WindowController_CreateRegularWindow(engine_id, &request);
324 int64_t handle2 = InternalFlutter_WindowController_CreateRegularWindow(engine_id, &request);
325 int64_t handle3 = InternalFlutter_WindowController_CreateRegularWindow(engine_id, &request);
326
327 // Verify windows are created
328 FlutterViewController* viewController1 = [engine viewControllerForIdentifier:handle1];
329 FlutterViewController* viewController2 = [engine viewControllerForIdentifier:handle2];
330 FlutterViewController* viewController3 = [engine viewControllerForIdentifier:handle3];
331 EXPECT_NE(viewController1, nil);
332 EXPECT_NE(viewController2, nil);
333 EXPECT_NE(viewController3, nil);
334
335 // Close all windows on engine restart
336 [engine engineCallbackOnPreEngineRestart];
337
338 // Verify all windows are closed and view controllers are disposed
339 viewController1 = [engine viewControllerForIdentifier:handle1];
340 viewController2 = [engine viewControllerForIdentifier:handle2];
341 viewController3 = [engine viewControllerForIdentifier:handle3];
342 EXPECT_EQ(viewController1, nil);
343 EXPECT_EQ(viewController2, nil);
344 EXPECT_EQ(viewController3, nil);
345};
346
347TEST_F(FlutterWindowControllerTest, ViewMetricsRespectPositionCallbackConstraints) {
348 IsolateScope isolate_scope(isolate());
349 FlutterEngine* engine = GetFlutterEngine();
350 int64_t engineId = reinterpret_cast<int64_t>(engine);
351
352 // Create parent window.
353 auto parentRequest = FlutterWindowCreationRequest{
354 .has_size = true,
355 .size = {.width = 800, .height = 600},
356 .on_should_close = [] {},
357 .on_will_close = [] {},
358 .notify_listeners = [] {},
359 };
360 int64_t parentViewId =
362 EXPECT_EQ(parentViewId, 1);
363
364 auto position_callback = [](const FlutterWindowSize& child_size,
365 const FlutterWindowRect& parent_rect,
366 const FlutterWindowRect& output_rect) -> FlutterWindowRect* {
367 FlutterWindowRect* rect = static_cast<FlutterWindowRect*>(malloc(sizeof(FlutterWindowRect)));
368 rect->left = parent_rect.left;
369 rect->top = parent_rect.top;
370 rect->width = 500;
371 rect->height = 400;
372 return rect;
373 };
374
375 auto tooltipRequest = FlutterWindowCreationRequest{
376 .has_constraints = true,
377 .constraints{
378 .min_width = 0,
379 .min_height = 0,
380 .max_width = 1000,
381 .max_height = 1000,
382 },
383 .parent_view_id = parentViewId,
384 .on_should_close = [] {},
385 .on_will_close = [] {},
386 .notify_listeners = [] {},
387 .on_get_window_position = position_callback,
388 };
389
390 const int64_t tooltipViewId =
392 EXPECT_NE(tooltipViewId, 0);
393
394 FlutterViewController* viewController = [engine viewControllerForIdentifier:tooltipViewId];
395 FlutterView* flutterView = viewController.flutterView;
396
397 EXPECT_EQ(flutterView.sizedToContents, YES);
398
399 CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, false);
400
401 [flutterView.sizingDelegate viewDidUpdateContents:flutterView withSize:NSMakeSize(1000, 1000)];
402
403 // The constraints from request are 1000x1000, but additional constraints came from the positioner
404 // and must be respected.
405 CGSize maxSize = flutterView.maximumContentSize;
406 EXPECT_LE(maxSize.width, 500);
407 EXPECT_LE(maxSize.height, 400);
408}
409
411 NSWindow* parentWindow = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 800, 600)
412 styleMask:NSWindowStyleMaskTitled
413 backing:NSBackingStoreBuffered
414 defer:NO];
415 [parentWindow setReleasedWhenClosed:NO];
416 NSWindow* childWindow = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 100, 100)
417 styleMask:NSWindowStyleMaskBorderless
418 backing:NSBackingStoreBuffered
419 defer:NO];
420 [childWindow setReleasedWhenClosed:NO];
421
422 // Bottom left origin.
423 [parentWindow setFrame:NSMakeRect(100, 100, 800, 600) display:NO];
424 [childWindow setFrame:NSMakeRect(150, 150, 100, 100) display:NO];
425
426 // Establish the parent-child relationship required by GetOffsetInParent.
427 [parentWindow addChildWindow:childWindow ordered:NSWindowAbove];
428
429 FlutterWindowOffset offset =
430 InternalFlutter_Window_GetOffsetInParent((__bridge void*)childWindow);
431
432 // GetOffsetInParent has relative coordinates with top left origin.
433 NSRect parentContentRect = [parentWindow contentRectForFrameRect:parentWindow.frame];
434
435 double expectedX = 150 - parentContentRect.origin.x;
436 double expectedY = (parentContentRect.origin.y + parentContentRect.size.height) - (150 + 100);
437
438 EXPECT_NEAR(offset.x, expectedX, 0.001);
439 EXPECT_NEAR(offset.y, expectedY, 0.001);
440
441 [parentWindow removeChildWindow:childWindow];
442 [childWindow close];
443 [parentWindow close];
444}
445} // namespace flutter::testing
FLUTTER_DARWIN_EXPORT int64_t InternalFlutter_WindowController_CreateRegularWindow(int64_t engine_id, const FlutterWindowCreationRequest *request)
FLUTTER_DARWIN_EXPORT void * InternalFlutter_Window_GetHandle(int64_t engine_id, FlutterViewIdentifier view_id)
FLUTTER_DARWIN_EXPORT FlutterWindowOffset InternalFlutter_Window_GetOffsetInParent(void *window)
FLUTTER_DARWIN_EXPORT int64_t InternalFlutter_WindowController_CreateTooltipWindow(int64_t engine_id, const FlutterWindowCreationRequest *request)
FLUTTER_DARWIN_EXPORT void InternalFlutter_Window_SetMaximized(void *window, bool maximized)
FLUTTER_DARWIN_EXPORT int64_t InternalFlutter_WindowController_CreatePopupWindow(int64_t engine_id, const FlutterWindowCreationRequest *request)
FLUTTER_DARWIN_EXPORT void InternalFlutter_Window_Destroy(int64_t engine_id, void *window)
FLUTTER_DARWIN_EXPORT void InternalFlutter_Window_Minimize(void *window)
static Isolate Current()
void AddNativeCallback(const char *name, Dart_NativeFunction function)
GLFWwindow * window
Definition main.cc:60
FlutterEngine engine
Definition main.cc:84
G_BEGIN_DECLS G_MODULE_EXPORT FlValue * args
#define FML_LOG(severity)
Definition logging.h:101
#define FML_UNREACHABLE()
Definition logging.h:128
#define FML_DCHECK(condition)
Definition logging.h:122
void(* rootIsolateCreateCallback)(void *_Nullable)
FlutterViewController * viewController
TEST_F(DisplayListTest, Defaults)
const char * GetFixturesPath()
Returns the directory containing the test fixture for the target if this target has fixtures configur...
it will be possible to load the file into Perfetto s trace viewer use test Running tests that layout and measure text will not yield consistent results across various platforms Enabling this option will make font resolution default to the Ahem test font on all 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
#define CREATE_NATIVE_ENTRY(native_entry)
int BOOL