| Flutter macOS Embedder |

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
5 #import "flutter/shell/platform/common/isolate_scope.h"
6 #import "flutter/shell/platform/common/windowing.h"
7
8 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterDartProject_Internal.h"
9 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngineTestUtils.h"
10 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h"
11 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h"
12 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterWindowController.h"
13 #import "flutter/testing/testing.h"
14 #import "third_party/googletest/googletest/include/gtest/gtest.h"
15
16 namespace flutter::testing {
17
18 class FlutterWindowControllerTest : public FlutterEngineTest {
19 public:
20 FlutterWindowControllerTest() = default;
21
22 void SetUp() {
23 FlutterEngineTest::SetUp();
24
25 [GetFlutterEngine() runWithEntrypoint:@"testWindowController"];
26
27 signalled_ = false;
28
29 AddNativeCallback("SignalNativeTest", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
30 isolate_ = Isolate::Current();
31 signalled_ = true;
32 }));
33
34 while (!signalled_) {
35 CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, false);
36 }
37 }
38
39 void TearDown() {
40 [GetFlutterEngine().windowController closeAllWindows];
41 FlutterEngineTest::TearDown();
42 }
43
44 protected:
45 flutter::Isolate& isolate() {
46 if (isolate_) {
47 return *isolate_;
48 } else {
49 FML_LOG(ERROR) << "Isolate is not set.";
50 FML_UNREACHABLE();
51 }
52 }
53
54 std::optional<flutter::Isolate> isolate_;
55 bool signalled_;
56 };
57
58 class FlutterWindowControllerRetainTest : public ::testing::Test {};
59
60 TEST_F(FlutterWindowControllerTest, CreateRegularWindow) {
61 FlutterWindowCreationRequest request{
62 .has_size = true,
63 .size = {.width = 800, .height = 600},
64 .on_should_close = [] {},
65 .on_will_close = [] {},
66 .notify_listeners = [] {},
67 };
68
69 FlutterEngine* engine = GetFlutterEngine();
70 int64_t engineId = reinterpret_cast<int64_t>(engine);
71
72 {
73 IsolateScope isolate_scope(isolate());
74 int64_t handle = InternalFlutter_WindowController_CreateRegularWindow(engineId, &request);
75 EXPECT_EQ(handle, 1);
76
77 FlutterViewController* viewController = [engine viewControllerForIdentifier:handle];
78 EXPECT_NE(viewController, nil);
79 CGSize size = viewController.view.frame.size;
80 EXPECT_EQ(size.width, 800);
81 EXPECT_EQ(size.height, 600);
82 }
83 }
84
85 TEST_F(FlutterWindowControllerTest, CreateTooltipWindow) {
86 IsolateScope isolate_scope(isolate());
87 FlutterEngine* engine = GetFlutterEngine();
88 int64_t engineId = reinterpret_cast<int64_t>(engine);
89
90 auto request = FlutterWindowCreationRequest{
91 .has_size = true,
92 .size = {.width = 800, .height = 600},
93 .on_should_close = [] {},
94 .on_will_close = [] {},
95 .notify_listeners = [] {},
96 };
97 int64_t parentViewId = InternalFlutter_WindowController_CreateRegularWindow(engineId, &request);
98 EXPECT_EQ(parentViewId, 1);
99
100 auto position_callback = [](const FlutterWindowSize& child_size,
101 const FlutterWindowRect& parent_rect,
102 const FlutterWindowRect& output_rect) -> FlutterWindowRect* {
103 FlutterWindowRect* rect = static_cast<FlutterWindowRect*>(malloc(sizeof(FlutterWindowRect)));
104 rect->left = parent_rect.left + 10;
105 rect->top = parent_rect.top + 10;
106 rect->width = child_size.width;
107 rect->height = child_size.height;
108 return rect;
109 };
110
111 request = FlutterWindowCreationRequest{
112 .has_constraints = true,
113 .constraints{
114 .max_width = 1000,
115 .max_height = 1000,
116 },
117 .parent_view_id = parentViewId,
118 .on_should_close = [] {},
119 .on_will_close = [] {},
120 .notify_listeners = [] {},
121 .on_get_window_position = position_callback,
122 };
123
124 const int64_t tooltipViewId =
125 InternalFlutter_WindowController_CreateTooltipWindow(engineId, &request);
126 EXPECT_NE(tooltipViewId, 0);
127 }
128
129 TEST_F(FlutterWindowControllerTest, CreatePopupWindow) {
130 IsolateScope isolate_scope(isolate());
131 FlutterEngine* engine = GetFlutterEngine();
132 int64_t engineId = reinterpret_cast<int64_t>(engine);
133
134 auto request = FlutterWindowCreationRequest{
135 .has_size = true,
136 .size = {.width = 800, .height = 600},
137 .on_should_close = [] {},
138 .on_will_close = [] {},
139 .notify_listeners = [] {},
140 };
141 int64_t parentViewId = InternalFlutter_WindowController_CreateRegularWindow(engineId, &request);
142 EXPECT_EQ(parentViewId, 1);
143
144 auto position_callback = [](const FlutterWindowSize& child_size,
145 const FlutterWindowRect& parent_rect,
146 const FlutterWindowRect& output_rect) -> FlutterWindowRect* {
147 FlutterWindowRect* rect = static_cast<FlutterWindowRect*>(malloc(sizeof(FlutterWindowRect)));
148 rect->left = parent_rect.left + 10;
149 rect->top = parent_rect.top + 10;
150 rect->width = child_size.width;
151 rect->height = child_size.height;
152 return rect;
153 };
154
155 request = FlutterWindowCreationRequest{
156 .has_constraints = true,
157 .constraints{
158 .max_width = 1000,
159 .max_height = 1000,
160 },
161 .parent_view_id = parentViewId,
162 .on_should_close = [] {},
163 .on_will_close = [] {},
164 .notify_listeners = [] {},
165 .on_get_window_position = position_callback,
166 };
167
168 const int64_t tooltipViewId =
169 InternalFlutter_WindowController_CreatePopupWindow(engineId, &request);
170 EXPECT_NE(tooltipViewId, 0);
171 }
172
173 TEST_F(FlutterWindowControllerRetainTest, WindowControllerDoesNotRetainEngine) {
174 FlutterWindowCreationRequest request{
175 .has_size = true,
176 .size = {.width = 800, .height = 600},
177 .on_should_close = [] {},
178 .on_will_close = [] {},
179 .notify_listeners = [] {},
180 };
181
182 __weak FlutterEngine* weakEngine = nil;
183 @autoreleasepool {
184 NSString* fixtures = @(flutter::testing::GetFixturesPath());
185 NSLog(@"Fixtures path: %@", fixtures);
186 FlutterDartProject* project = [[FlutterDartProject alloc]
187 initWithAssetsPath:fixtures
188 ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]];
189
190 static std::optional<flutter::Isolate> isolate;
191 isolate = std::nullopt;
192
193 project.rootIsolateCreateCallback = [](void*) { isolate = flutter::Isolate::Current(); };
194 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"test"
195 project:project
196 allowHeadlessExecution:YES];
197 weakEngine = engine;
198 [engine runWithEntrypoint:@"testWindowControllerRetainCycle"];
199
200 int64_t engineId = reinterpret_cast<int64_t>(engine);
201
202 {
203 FML_DCHECK(isolate.has_value());
204 // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
205 IsolateScope isolateScope(*isolate);
206 int64_t handle = InternalFlutter_WindowController_CreateRegularWindow(engineId, &request);
207 EXPECT_EQ(handle, 1);
208 }
209
210 [engine.windowController closeAllWindows];
211 [engine shutDownEngine];
212 }
213 EXPECT_EQ(weakEngine, nil);
214 }
215
216 TEST_F(FlutterWindowControllerTest, DestroyRegularWindow) {
217 FlutterWindowCreationRequest request{
218 .has_size = true,
219 .size = {.width = 800, .height = 600},
220 .on_should_close = [] {},
221 .on_will_close = [] {},
222 .notify_listeners = [] {},
223 };
224
225 FlutterEngine* engine = GetFlutterEngine();
226 int64_t engine_id = reinterpret_cast<int64_t>(engine);
227
228 IsolateScope isolate_scope(isolate());
229 int64_t handle = InternalFlutter_WindowController_CreateRegularWindow(engine_id, &request);
230 FlutterViewController* viewController = [engine viewControllerForIdentifier:handle];
231
232 InternalFlutter_Window_Destroy(engine_id, (__bridge void*)viewController.view.window);
233 viewController = [engine viewControllerForIdentifier:handle];
234 EXPECT_EQ(viewController, nil);
235 }
236
237 TEST_F(FlutterWindowControllerTest, InternalFlutterWindowGetHandle) {
238 FlutterWindowCreationRequest request{
239 .has_size = true,
240 .size = {.width = 800, .height = 600},
241 .on_should_close = [] {},
242 .on_will_close = [] {},
243 .notify_listeners = [] {},
244 };
245
246 FlutterEngine* engine = GetFlutterEngine();
247 int64_t engine_id = reinterpret_cast<int64_t>(engine);
248
249 IsolateScope isolate_scope(isolate());
250 int64_t handle = InternalFlutter_WindowController_CreateRegularWindow(engine_id, &request);
251 FlutterViewController* viewController = [engine viewControllerForIdentifier:handle];
252
253 void* window_handle = InternalFlutter_Window_GetHandle(engine_id, handle);
254 EXPECT_EQ(window_handle, (__bridge void*)viewController.view.window);
255 }
256
257 TEST_F(FlutterWindowControllerTest, WindowStates) {
258 FlutterWindowCreationRequest request{
259 .has_size = true,
260 .size = {.width = 800, .height = 600},
261 .on_should_close = [] {},
262 .on_will_close = [] {},
263 .notify_listeners = [] {},
264 };
265
266 FlutterEngine* engine = GetFlutterEngine();
267 int64_t engine_id = reinterpret_cast<int64_t>(engine);
268
269 IsolateScope isolate_scope(isolate());
270 int64_t handle = InternalFlutter_WindowController_CreateRegularWindow(engine_id, &request);
271
272 FlutterViewController* viewController = [engine viewControllerForIdentifier:handle];
273 NSWindow* window = viewController.view.window;
274 void* windowHandle = (__bridge void*)window;
275
276 EXPECT_EQ(window.zoomed, NO);
277 EXPECT_EQ(window.miniaturized, NO);
278 EXPECT_EQ(window.styleMask & NSWindowStyleMaskFullScreen, 0u);
279
280 InternalFlutter_Window_SetMaximized(windowHandle, true);
281 CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.5, false);
282 EXPECT_EQ(window.zoomed, YES);
283
284 InternalFlutter_Window_SetMaximized(windowHandle, false);
285 CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.5, false);
286 EXPECT_EQ(window.zoomed, NO);
287
288 // FullScreen toggle does not seem to work when the application is not run from a bundle.
289
290 InternalFlutter_Window_Minimize(windowHandle);
291 CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.5, false);
292 EXPECT_EQ(window.miniaturized, YES);
293 }
294
295 TEST_F(FlutterWindowControllerTest, ClosesAllWindowsOnEngineRestart) {
296 FlutterWindowCreationRequest request{
297 .has_size = true,
298 .size = {.width = 800, .height = 600},
299 .on_should_close = [] {},
300 .on_will_close = [] {},
301 .notify_listeners = [] {},
302 };
303 FlutterEngine* engine = GetFlutterEngine();
304 int64_t engine_id = reinterpret_cast<int64_t>(engine);
305
306 IsolateScope isolate_scope(isolate());
307
308 // Create multiple windows
309 int64_t handle1 = InternalFlutter_WindowController_CreateRegularWindow(engine_id, &request);
310 int64_t handle2 = InternalFlutter_WindowController_CreateRegularWindow(engine_id, &request);
311 int64_t handle3 = InternalFlutter_WindowController_CreateRegularWindow(engine_id, &request);
312
313 // Verify windows are created
314 FlutterViewController* viewController1 = [engine viewControllerForIdentifier:handle1];
315 FlutterViewController* viewController2 = [engine viewControllerForIdentifier:handle2];
316 FlutterViewController* viewController3 = [engine viewControllerForIdentifier:handle3];
317 EXPECT_NE(viewController1, nil);
318 EXPECT_NE(viewController2, nil);
319 EXPECT_NE(viewController3, nil);
320
321 // Close all windows on engine restart
322 [engine engineCallbackOnPreEngineRestart];
323
324 // Verify all windows are closed and view controllers are disposed
325 viewController1 = [engine viewControllerForIdentifier:handle1];
326 viewController2 = [engine viewControllerForIdentifier:handle2];
327 viewController3 = [engine viewControllerForIdentifier:handle3];
328 EXPECT_EQ(viewController1, nil);
329 EXPECT_EQ(viewController2, nil);
330 EXPECT_EQ(viewController3, nil);
331 };
332
333 TEST_F(FlutterWindowControllerTest, ViewMetricsRespectPositionCallbackConstraints) {
334 IsolateScope isolate_scope(isolate());
335 FlutterEngine* engine = GetFlutterEngine();
336 int64_t engineId = reinterpret_cast<int64_t>(engine);
337
338 // Create parent window.
339 auto parentRequest = FlutterWindowCreationRequest{
340 .has_size = true,
341 .size = {.width = 800, .height = 600},
342 .on_should_close = [] {},
343 .on_will_close = [] {},
344 .notify_listeners = [] {},
345 };
346 int64_t parentViewId =
347 InternalFlutter_WindowController_CreateRegularWindow(engineId, &parentRequest);
348 EXPECT_EQ(parentViewId, 1);
349
350 auto position_callback = [](const FlutterWindowSize& child_size,
351 const FlutterWindowRect& parent_rect,
352 const FlutterWindowRect& output_rect) -> FlutterWindowRect* {
353 FlutterWindowRect* rect = static_cast<FlutterWindowRect*>(malloc(sizeof(FlutterWindowRect)));
354 rect->left = parent_rect.left;
355 rect->top = parent_rect.top;
356 rect->width = 500;
357 rect->height = 400;
358 return rect;
359 };
360
361 auto tooltipRequest = FlutterWindowCreationRequest{
362 .has_constraints = true,
363 .constraints{
364 .min_width = 0,
365 .min_height = 0,
366 .max_width = 1000,
367 .max_height = 1000,
368 },
369 .parent_view_id = parentViewId,
370 .on_should_close = [] {},
371 .on_will_close = [] {},
372 .notify_listeners = [] {},
373 .on_get_window_position = position_callback,
374 };
375
376 const int64_t tooltipViewId =
377 InternalFlutter_WindowController_CreateTooltipWindow(engineId, &tooltipRequest);
378 EXPECT_NE(tooltipViewId, 0);
379
380 FlutterViewController* viewController = [engine viewControllerForIdentifier:tooltipViewId];
381 FlutterView* flutterView = viewController.flutterView;
382
383 EXPECT_EQ(flutterView.sizedToContents, YES);
384
385 CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, false);
386
387 [flutterView.sizingDelegate viewDidUpdateContents:flutterView withSize:NSMakeSize(1000, 1000)];
388
389 // The constraints from request are 1000x1000, but additional constraints came from the positioner
390 // and must be respected.
391 CGSize maxSize = flutterView.maximumContentSize;
392 EXPECT_LE(maxSize.width, 500);
393 EXPECT_LE(maxSize.height, 400);
394 }
395
396 TEST_F(FlutterWindowControllerTest, GetOffsetInParent) {
397 NSWindow* parentWindow = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 800, 600)
398 styleMask:NSWindowStyleMaskTitled
399 backing:NSBackingStoreBuffered
400 defer:NO];
401 [parentWindow setReleasedWhenClosed:NO];
402 NSWindow* childWindow = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 100, 100)
403 styleMask:NSWindowStyleMaskBorderless
404 backing:NSBackingStoreBuffered
405 defer:NO];
406 [childWindow setReleasedWhenClosed:NO];
407
408 // Bottom left origin.
409 [parentWindow setFrame:NSMakeRect(100, 100, 800, 600) display:NO];
410 [childWindow setFrame:NSMakeRect(150, 150, 100, 100) display:NO];
411
412 // Establish the parent-child relationship required by GetOffsetInParent.
413 [parentWindow addChildWindow:childWindow ordered:NSWindowAbove];
414
415 FlutterWindowOffset offset =
416 InternalFlutter_Window_GetOffsetInParent((__bridge void*)childWindow);
417
418 // GetOffsetInParent has relative coordinates with top left origin.
419 NSRect parentContentRect = [parentWindow contentRectForFrameRect:parentWindow.frame];
420
421 double expectedX = 150 - parentContentRect.origin.x;
422 double expectedY = (parentContentRect.origin.y + parentContentRect.size.height) - (150 + 100);
423
424 EXPECT_NEAR(offset.x, expectedX, 0.001);
425 EXPECT_NEAR(offset.y, expectedY, 0.001);
426
427 [parentWindow removeChildWindow:childWindow];
428 [childWindow close];
429 [parentWindow close];
430 }
431 } // namespace flutter::testing
FlutterDartProject_Internal.h
FlutterEngine_Internal.h
FlutterEngineTestUtils.h
FlutterViewController_Internal.h
FlutterWindowController.h
InternalFlutter_WindowController_CreateRegularWindow
FLUTTER_DARWIN_EXPORT int64_t InternalFlutter_WindowController_CreateRegularWindow(int64_t engine_id, const FlutterWindowCreationRequest *request)
Definition: FlutterWindowController.mm:522
InternalFlutter_Window_GetOffsetInParent
FLUTTER_DARWIN_EXPORT FlutterWindowOffset InternalFlutter_Window_GetOffsetInParent(void *window)
Definition: FlutterWindowController.mm:659
InternalFlutter_WindowController_CreateTooltipWindow
FLUTTER_DARWIN_EXPORT int64_t InternalFlutter_WindowController_CreateTooltipWindow(int64_t engine_id, const FlutterWindowCreationRequest *request)
Definition: FlutterWindowController.mm:538
InternalFlutter_Window_SetMaximized
FLUTTER_DARWIN_EXPORT void InternalFlutter_Window_SetMaximized(void *window, bool maximized)
Definition: FlutterWindowController.mm:593
InternalFlutter_WindowController_CreatePopupWindow
FLUTTER_DARWIN_EXPORT int64_t InternalFlutter_WindowController_CreatePopupWindow(int64_t engine_id, const FlutterWindowCreationRequest *request)
Definition: FlutterWindowController.mm:546
InternalFlutter_Window_Destroy
FLUTTER_DARWIN_EXPORT void InternalFlutter_Window_Destroy(int64_t engine_id, void *window)
Definition: FlutterWindowController.mm:554
InternalFlutter_Window_GetHandle
FLUTTER_DARWIN_EXPORT void * InternalFlutter_Window_GetHandle(int64_t engine_id, FlutterViewIdentifier view_id)
Definition: FlutterWindowController.mm:560
InternalFlutter_Window_Minimize
FLUTTER_DARWIN_EXPORT void InternalFlutter_Window_Minimize(void *window)
Definition: FlutterWindowController.mm:607
FlutterDartProject
Definition: FlutterDartProject.mm:39
flutter::Isolate
Definition: isolate_scope.h:15
flutter::Isolate::Current
static Isolate Current()
Definition: isolate_scope.cc:9
flutter::IsolateScope
Definition: isolate_scope.h:33
flutter::testing::FlutterEngineTest
Definition: FlutterEngineTestUtils.h:18
flutter::testing::FlutterEngineTest::AddNativeCallback
void AddNativeCallback(const char *name, Dart_NativeFunction function)
Definition: FlutterEngineTestUtils.mm:72
flutter::testing::FlutterEngineTest::TearDown
void TearDown() override
Definition: FlutterEngineTestUtils.mm:57
flutter::testing::FlutterEngineTest::SetUp
void SetUp() override
Definition: FlutterEngineTestUtils.mm:45
flutter::testing::FlutterEngineTest::GetFlutterEngine
FlutterEngine * GetFlutterEngine()
Definition: FlutterEngineTestUtils.h:22
flutter::testing::FlutterWindowControllerRetainTest
Definition: FlutterWindowControllerTest.mm:58
flutter::testing::FlutterWindowControllerTest
Definition: FlutterWindowControllerTest.mm:18
flutter::testing::FlutterWindowControllerTest::SetUp
void SetUp()
Definition: FlutterWindowControllerTest.mm:22
flutter::testing::FlutterWindowControllerTest::signalled_
bool signalled_
Definition: FlutterWindowControllerTest.mm:55
flutter::testing::FlutterWindowControllerTest::isolate_
std::optional< flutter::Isolate > isolate_
Definition: FlutterWindowControllerTest.mm:54
flutter::testing::FlutterWindowControllerTest::FlutterWindowControllerTest
FlutterWindowControllerTest()=default
flutter::testing::FlutterWindowControllerTest::isolate
flutter::Isolate & isolate()
Definition: FlutterWindowControllerTest.mm:45
flutter::testing::FlutterWindowControllerTest::TearDown
void TearDown()
Definition: FlutterWindowControllerTest.mm:39
FlutterEngine
Definition: FlutterEngine.h:32
FlutterViewController
Definition: FlutterViewController.h:73
FlutterView
Definition: FlutterView.h:69
FlutterView::maximumContentSize
CGSize maximumContentSize
Definition: FlutterView.h:133
FlutterView::sizedToContents
BOOL sizedToContents
Definition: FlutterView.h:121
FlutterView::sizingDelegate
id< FlutterViewSizingDelegate > sizingDelegate
Definition: FlutterView.h:94
isolate_scope.h
flutter::testing
Definition: AccessibilityBridgeMacTest.mm:13
flutter::testing::TEST_F
TEST_F(AccessibilityBridgeMacWindowTest, SendsAccessibilityCreateNotificationFlutterViewWindow)
Definition: AccessibilityBridgeMacTest.mm:90
FlutterWindowCreationRequest
Definition: FlutterWindowController.h:72
FlutterWindowCreationRequest::has_constraints
bool has_constraints
Definition: FlutterWindowController.h:75
FlutterWindowCreationRequest::has_size
bool has_size
Definition: FlutterWindowController.h:73
FlutterWindowOffset
Definition: FlutterWindowController.h:53
FlutterWindowOffset::x
double x
Definition: FlutterWindowController.h:54
FlutterWindowOffset::y
double y
Definition: FlutterWindowController.h:55
FlutterWindowRect
Definition: FlutterWindowController.h:23
FlutterWindowRect::top
double top
Definition: FlutterWindowController.h:25
FlutterWindowRect::width
double width
Definition: FlutterWindowController.h:26
FlutterWindowRect::left
double left
Definition: FlutterWindowController.h:24
FlutterWindowRect::height
double height
Definition: FlutterWindowController.h:27
FlutterWindowSize
Definition: FlutterWindowController.h:41
FlutterWindowSize::height
double height
Definition: FlutterWindowController.h:43
FlutterWindowSize::width
double width
Definition: FlutterWindowController.h:42
windowing.h