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 
7 
13 #import "flutter/testing/testing.h"
14 #import "third_party/googletest/googletest/include/gtest/gtest.h"
15 
16 namespace 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.";
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) {
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 
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 =
126  EXPECT_NE(tooltipViewId, 0);
127 }
128 
129 TEST_F(FlutterWindowControllerRetainTest, WindowControllerDoesNotRetainEngine) {
131  .has_size = true,
132  .size = {.width = 800, .height = 600},
133  .on_should_close = [] {},
134  .on_will_close = [] {},
135  .notify_listeners = [] {},
136  };
137 
138  __weak FlutterEngine* weakEngine = nil;
139  @autoreleasepool {
140  NSString* fixtures = @(flutter::testing::GetFixturesPath());
141  NSLog(@"Fixtures path: %@", fixtures);
142  FlutterDartProject* project = [[FlutterDartProject alloc]
143  initWithAssetsPath:fixtures
144  ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]];
145 
146  static std::optional<flutter::Isolate> isolate;
147  isolate = std::nullopt;
148 
149  project.rootIsolateCreateCallback = [](void*) { isolate = flutter::Isolate::Current(); };
150  FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"test"
151  project:project
152  allowHeadlessExecution:YES];
153  weakEngine = engine;
154  [engine runWithEntrypoint:@"testWindowControllerRetainCycle"];
155 
156  int64_t engineId = reinterpret_cast<int64_t>(engine);
157 
158  {
159  FML_DCHECK(isolate.has_value());
160  // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
161  IsolateScope isolateScope(*isolate);
162  int64_t handle = InternalFlutter_WindowController_CreateRegularWindow(engineId, &request);
163  EXPECT_EQ(handle, 1);
164  }
165 
166  [engine.windowController closeAllWindows];
167  [engine shutDownEngine];
168  }
169  EXPECT_EQ(weakEngine, nil);
170 }
171 
172 TEST_F(FlutterWindowControllerTest, DestroyRegularWindow) {
174  .has_size = true,
175  .size = {.width = 800, .height = 600},
176  .on_should_close = [] {},
177  .on_will_close = [] {},
178  .notify_listeners = [] {},
179  };
180 
181  FlutterEngine* engine = GetFlutterEngine();
182  int64_t engine_id = reinterpret_cast<int64_t>(engine);
183 
184  IsolateScope isolate_scope(isolate());
185  int64_t handle = InternalFlutter_WindowController_CreateRegularWindow(engine_id, &request);
186  FlutterViewController* viewController = [engine viewControllerForIdentifier:handle];
187 
188  InternalFlutter_Window_Destroy(engine_id, (__bridge void*)viewController.view.window);
189  viewController = [engine viewControllerForIdentifier:handle];
190  EXPECT_EQ(viewController, nil);
191 }
192 
193 TEST_F(FlutterWindowControllerTest, InternalFlutterWindowGetHandle) {
195  .has_size = true,
196  .size = {.width = 800, .height = 600},
197  .on_should_close = [] {},
198  .on_will_close = [] {},
199  .notify_listeners = [] {},
200  };
201 
202  FlutterEngine* engine = GetFlutterEngine();
203  int64_t engine_id = reinterpret_cast<int64_t>(engine);
204 
205  IsolateScope isolate_scope(isolate());
206  int64_t handle = InternalFlutter_WindowController_CreateRegularWindow(engine_id, &request);
207  FlutterViewController* viewController = [engine viewControllerForIdentifier:handle];
208 
209  void* window_handle = InternalFlutter_Window_GetHandle(engine_id, handle);
210  EXPECT_EQ(window_handle, (__bridge void*)viewController.view.window);
211 }
212 
215  .has_size = true,
216  .size = {.width = 800, .height = 600},
217  .on_should_close = [] {},
218  .on_will_close = [] {},
219  .notify_listeners = [] {},
220  };
221 
222  FlutterEngine* engine = GetFlutterEngine();
223  int64_t engine_id = reinterpret_cast<int64_t>(engine);
224 
225  IsolateScope isolate_scope(isolate());
226  int64_t handle = InternalFlutter_WindowController_CreateRegularWindow(engine_id, &request);
227 
228  FlutterViewController* viewController = [engine viewControllerForIdentifier:handle];
229  NSWindow* window = viewController.view.window;
230  void* windowHandle = (__bridge void*)window;
231 
232  EXPECT_EQ(window.zoomed, NO);
233  EXPECT_EQ(window.miniaturized, NO);
234  EXPECT_EQ(window.styleMask & NSWindowStyleMaskFullScreen, 0u);
235 
236  InternalFlutter_Window_SetMaximized(windowHandle, true);
237  CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.5, false);
238  EXPECT_EQ(window.zoomed, YES);
239 
240  InternalFlutter_Window_SetMaximized(windowHandle, false);
241  CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.5, false);
242  EXPECT_EQ(window.zoomed, NO);
243 
244  // FullScreen toggle does not seem to work when the application is not run from a bundle.
245 
246  InternalFlutter_Window_Minimize(windowHandle);
247  CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.5, false);
248  EXPECT_EQ(window.miniaturized, YES);
249 }
250 
251 TEST_F(FlutterWindowControllerTest, ClosesAllWindowsOnEngineRestart) {
253  .has_size = true,
254  .size = {.width = 800, .height = 600},
255  .on_should_close = [] {},
256  .on_will_close = [] {},
257  .notify_listeners = [] {},
258  };
259  FlutterEngine* engine = GetFlutterEngine();
260  int64_t engine_id = reinterpret_cast<int64_t>(engine);
261 
262  IsolateScope isolate_scope(isolate());
263 
264  // Create multiple windows
265  int64_t handle1 = InternalFlutter_WindowController_CreateRegularWindow(engine_id, &request);
266  int64_t handle2 = InternalFlutter_WindowController_CreateRegularWindow(engine_id, &request);
267  int64_t handle3 = InternalFlutter_WindowController_CreateRegularWindow(engine_id, &request);
268 
269  // Verify windows are created
270  FlutterViewController* viewController1 = [engine viewControllerForIdentifier:handle1];
271  FlutterViewController* viewController2 = [engine viewControllerForIdentifier:handle2];
272  FlutterViewController* viewController3 = [engine viewControllerForIdentifier:handle3];
273  EXPECT_NE(viewController1, nil);
274  EXPECT_NE(viewController2, nil);
275  EXPECT_NE(viewController3, nil);
276 
277  // Close all windows on engine restart
278  [engine engineCallbackOnPreEngineRestart];
279 
280  // Verify all windows are closed and view controllers are disposed
281  viewController1 = [engine viewControllerForIdentifier:handle1];
282  viewController2 = [engine viewControllerForIdentifier:handle2];
283  viewController3 = [engine viewControllerForIdentifier:handle3];
284  EXPECT_EQ(viewController1, nil);
285  EXPECT_EQ(viewController2, nil);
286  EXPECT_EQ(viewController3, nil);
287 };
288 
289 TEST_F(FlutterWindowControllerTest, ViewMetricsRespectPositionCallbackConstraints) {
290  IsolateScope isolate_scope(isolate());
291  FlutterEngine* engine = GetFlutterEngine();
292  int64_t engineId = reinterpret_cast<int64_t>(engine);
293 
294  // Create parent window.
295  auto parentRequest = FlutterWindowCreationRequest{
296  .has_size = true,
297  .size = {.width = 800, .height = 600},
298  .on_should_close = [] {},
299  .on_will_close = [] {},
300  .notify_listeners = [] {},
301  };
302  int64_t parentViewId =
304  EXPECT_EQ(parentViewId, 1);
305 
306  auto position_callback = [](const FlutterWindowSize& child_size,
307  const FlutterWindowRect& parent_rect,
308  const FlutterWindowRect& output_rect) -> FlutterWindowRect* {
309  FlutterWindowRect* rect = static_cast<FlutterWindowRect*>(malloc(sizeof(FlutterWindowRect)));
310  rect->left = parent_rect.left;
311  rect->top = parent_rect.top;
312  rect->width = 500;
313  rect->height = 400;
314  return rect;
315  };
316 
317  auto tooltipRequest = FlutterWindowCreationRequest{
318  .has_constraints = true,
319  .constraints{
320  .min_width = 0,
321  .min_height = 0,
322  .max_width = 1000,
323  .max_height = 1000,
324  },
325  .parent_view_id = parentViewId,
326  .on_should_close = [] {},
327  .on_will_close = [] {},
328  .notify_listeners = [] {},
329  .on_get_window_position = position_callback,
330  };
331 
332  const int64_t tooltipViewId =
333  InternalFlutter_WindowController_CreateTooltipWindow(engineId, &tooltipRequest);
334  EXPECT_NE(tooltipViewId, 0);
335 
336  FlutterViewController* viewController = [engine viewControllerForIdentifier:tooltipViewId];
337  FlutterView* flutterView = viewController.flutterView;
338 
339  EXPECT_EQ(flutterView.sizedToContents, YES);
340 
341  CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, false);
342 
343  [flutterView.sizingDelegate viewDidUpdateContents:flutterView withSize:NSMakeSize(1000, 1000)];
344 
345  // The constraints from request are 1000x1000, but additional constraints came from the positioner
346  // and must be respected.
347  CGSize maxSize = flutterView.maximumContentSize;
348  EXPECT_LE(maxSize.width, 500);
349  EXPECT_LE(maxSize.height, 400);
350 }
351 } // namespace flutter::testing
FLUTTER_DARWIN_EXPORT int64_t InternalFlutter_WindowController_CreateRegularWindow(int64_t engine_id, const FlutterWindowCreationRequest *request)
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 void InternalFlutter_Window_Destroy(int64_t engine_id, void *window)
FLUTTER_DARWIN_EXPORT void * InternalFlutter_Window_GetHandle(int64_t engine_id, FlutterViewIdentifier view_id)
FLUTTER_DARWIN_EXPORT void InternalFlutter_Window_Minimize(void *window)
static Isolate Current()
Definition: isolate_scope.cc:9
void AddNativeCallback(const char *name, Dart_NativeFunction function)
CGSize maximumContentSize
Definition: FlutterView.h:133
BOOL sizedToContents
Definition: FlutterView.h:121
id< FlutterViewSizingDelegate > sizingDelegate
Definition: FlutterView.h:94
TEST_F(AccessibilityBridgeMacWindowTest, SendsAccessibilityCreateNotificationFlutterViewWindow)