Flutter iOS Embedder
FlutterPlatformViewsTest.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 
6 
7 #import <OCMock/OCMock.h>
8 #import <UIKit/UIKit.h>
9 #import <WebKit/WebKit.h>
10 #import <XCTest/XCTest.h>
11 
12 #include <memory>
13 
14 #include "flutter/display_list/effects/dl_image_filters.h"
15 #include "flutter/fml/synchronization/count_down_latch.h"
16 #include "flutter/fml/thread.h"
25 
27 
29 __weak static UIView* gMockPlatformView = nil;
30 const float kFloatCompareEpsilon = 0.001;
31 
33 @end
35 
36 - (instancetype)init {
37  self = [super init];
38  if (self) {
39  gMockPlatformView = self;
40  }
41  return self;
42 }
43 
44 - (void)dealloc {
45  gMockPlatformView = nil;
46 }
47 
48 @end
49 
51 @property(nonatomic, strong) UIView* view;
52 @property(nonatomic, assign) BOOL viewCreated;
53 @end
54 
56 
57 - (instancetype)init {
58  if (self = [super init]) {
59  _view = [[FlutterPlatformViewsTestMockPlatformView alloc] init];
60  _viewCreated = NO;
61  }
62  return self;
63 }
64 
65 - (UIView*)view {
66  [self checkViewCreatedOnce];
67  return _view;
68 }
69 
70 - (void)checkViewCreatedOnce {
71  if (self.viewCreated) {
72  abort();
73  }
74  self.viewCreated = YES;
75 }
76 
77 - (void)dealloc {
78  gMockPlatformView = nil;
79 }
80 @end
81 
83  : NSObject <FlutterPlatformViewFactory>
84 @end
85 
87 - (NSObject<FlutterPlatformView>*)createWithFrame:(CGRect)frame
88  viewIdentifier:(int64_t)viewId
89  arguments:(id _Nullable)args {
91 }
92 
93 @end
94 
96 @property(nonatomic, strong) UIView* view;
97 @property(nonatomic, assign) BOOL viewCreated;
98 @end
99 
101 - (instancetype)init {
102  if (self = [super init]) {
103  _view = [[WKWebView alloc] init];
104  gMockPlatformView = _view;
105  _viewCreated = NO;
106  }
107  return self;
108 }
109 
110 - (UIView*)view {
111  [self checkViewCreatedOnce];
112  return _view;
113 }
114 
115 - (void)checkViewCreatedOnce {
116  if (self.viewCreated) {
117  abort();
118  }
119  self.viewCreated = YES;
120 }
121 
122 - (void)dealloc {
123  gMockPlatformView = nil;
124 }
125 @end
126 
128 @end
129 
131 - (NSObject<FlutterPlatformView>*)createWithFrame:(CGRect)frame
132  viewIdentifier:(int64_t)viewId
133  arguments:(id _Nullable)args {
134  return [[FlutterPlatformViewsTestMockWebView alloc] init];
135 }
136 @end
137 
139 @end
140 
142 - (NSObject<FlutterPlatformView>*)createWithFrame:(CGRect)frame
143  viewIdentifier:(int64_t)viewId
144  arguments:(id _Nullable)args {
145  return nil;
146 }
147 
148 @end
149 
151 @property(nonatomic, strong) UIView* view;
152 @property(nonatomic, assign) BOOL viewCreated;
153 @end
154 
156 - (instancetype)init {
157  if (self = [super init]) {
158  _view = [[UIView alloc] init];
159  [_view addSubview:[[WKWebView alloc] init]];
160  gMockPlatformView = _view;
161  _viewCreated = NO;
162  }
163  return self;
164 }
165 
166 - (UIView*)view {
167  [self checkViewCreatedOnce];
168  return _view;
169 }
170 
171 - (void)checkViewCreatedOnce {
172  if (self.viewCreated) {
173  abort();
174  }
175  self.viewCreated = YES;
176 }
177 
178 - (void)dealloc {
179  gMockPlatformView = nil;
180 }
181 @end
182 
184 @end
185 
187 - (NSObject<FlutterPlatformView>*)createWithFrame:(CGRect)frame
188  viewIdentifier:(int64_t)viewId
189  arguments:(id _Nullable)args {
190  return [[FlutterPlatformViewsTestMockWrapperWebView alloc] init];
191 }
192 @end
193 
195 @property(nonatomic, strong) UIView* view;
196 @property(nonatomic, assign) BOOL viewCreated;
197 @end
198 
200 - (instancetype)init {
201  if (self = [super init]) {
202  _view = [[UIView alloc] init];
203  UIView* childView = [[UIView alloc] init];
204  [_view addSubview:childView];
205  [childView addSubview:[[WKWebView alloc] init]];
206  gMockPlatformView = _view;
207  _viewCreated = NO;
208  }
209  return self;
210 }
211 
212 - (UIView*)view {
213  [self checkViewCreatedOnce];
214  return _view;
215 }
216 
217 - (void)checkViewCreatedOnce {
218  if (self.viewCreated) {
219  abort();
220  }
221  self.viewCreated = YES;
222 }
223 @end
224 
226  : NSObject <FlutterPlatformViewFactory>
227 @end
228 
230 - (NSObject<FlutterPlatformView>*)createWithFrame:(CGRect)frame
231  viewIdentifier:(int64_t)viewId
232  arguments:(id _Nullable)args {
234 }
235 @end
236 
237 namespace flutter {
238 namespace {
239 class FlutterPlatformViewsTestMockPlatformViewDelegate : public PlatformView::Delegate {
240  public:
241  void OnPlatformViewCreated(std::unique_ptr<Surface> surface) override {}
242  void OnPlatformViewDestroyed() override {}
243  void OnPlatformViewScheduleFrame() override {}
244  void OnPlatformViewAddView(int64_t view_id,
245  const ViewportMetrics& viewport_metrics,
246  AddViewCallback callback) override {}
247  void OnPlatformViewRemoveView(int64_t view_id, RemoveViewCallback callback) override {}
248  void OnPlatformViewSendViewFocusEvent(const ViewFocusEvent& event) override {};
249  void OnPlatformViewSetNextFrameCallback(const fml::closure& closure) override {}
250  void OnPlatformViewSetViewportMetrics(int64_t view_id, const ViewportMetrics& metrics) override {}
251  const flutter::Settings& OnPlatformViewGetSettings() const override { return settings_; }
252  void OnPlatformViewDispatchPlatformMessage(std::unique_ptr<PlatformMessage> message) override {}
253  void OnPlatformViewDispatchPointerDataPacket(std::unique_ptr<PointerDataPacket> packet) override {
254  }
255  void OnPlatformViewDispatchSemanticsAction(int64_t view_id,
256  int32_t node_id,
257  SemanticsAction action,
258  fml::MallocMapping args) override {}
259  void OnPlatformViewSetSemanticsEnabled(bool enabled) override {}
260  void OnPlatformViewSetAccessibilityFeatures(int32_t flags) override {}
261  void OnPlatformViewRegisterTexture(std::shared_ptr<Texture> texture) override {}
262  void OnPlatformViewUnregisterTexture(int64_t texture_id) override {}
263  void OnPlatformViewMarkTextureFrameAvailable(int64_t texture_id) override {}
264 
265  void LoadDartDeferredLibrary(intptr_t loading_unit_id,
266  std::unique_ptr<const fml::Mapping> snapshot_data,
267  std::unique_ptr<const fml::Mapping> snapshot_instructions) override {
268  }
269  void LoadDartDeferredLibraryError(intptr_t loading_unit_id,
270  const std::string error_message,
271  bool transient) override {}
272  void UpdateAssetResolverByType(std::unique_ptr<flutter::AssetResolver> updated_asset_resolver,
273  flutter::AssetResolver::AssetResolverType type) override {}
274 
275  flutter::Settings settings_;
276 };
277 
278 BOOL BlurRadiusEqualToBlurRadius(CGFloat radius1, CGFloat radius2) {
279  const CGFloat epsilon = 0.01;
280  return std::abs(radius1 - radius2) < epsilon;
281 }
282 
283 } // namespace
284 } // namespace flutter
285 
286 @interface FlutterPlatformViewsTest : XCTestCase
287 @end
288 
289 @implementation FlutterPlatformViewsTest
290 
291 namespace {
292 fml::RefPtr<fml::TaskRunner> GetDefaultTaskRunner() {
293  fml::MessageLoop::EnsureInitializedForCurrentThread();
294  return fml::MessageLoop::GetCurrent().GetTaskRunner();
295 }
296 } // namespace
297 
298 - (void)testFlutterViewOnlyCreateOnceInOneFrame {
299  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
300 
301  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
302  /*platform=*/GetDefaultTaskRunner(),
303  /*raster=*/GetDefaultTaskRunner(),
304  /*ui=*/GetDefaultTaskRunner(),
305  /*io=*/GetDefaultTaskRunner());
306  FlutterPlatformViewsController* flutterPlatformViewsController =
307  [[FlutterPlatformViewsController alloc] init];
308  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
309  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
310  /*delegate=*/mock_delegate,
311  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
312  /*platform_views_controller=*/flutterPlatformViewsController,
313  /*task_runners=*/runners,
314  /*worker_task_runner=*/nil,
315  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
316 
319  [flutterPlatformViewsController
320  registerViewFactory:factory
321  withId:@"MockFlutterPlatformView"
322  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
323  FlutterResult result = ^(id result) {
324  };
325  [flutterPlatformViewsController
327  arguments:@{
328  @"id" : @2,
329  @"viewType" : @"MockFlutterPlatformView"
330  }]
331  result:result];
332  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
333  flutterPlatformViewsController.flutterView = flutterView;
334  // Create embedded view params
335  flutter::MutatorsStack stack;
336  // Layer tree always pushes a screen scale factor to the stack
337  flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
338  flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
339  stack.PushTransform(screenScaleMatrix);
340  // Push a translate matrix
341  flutter::DlMatrix translateMatrix = flutter::DlMatrix::MakeTranslation({100, 100});
342  stack.PushTransform(translateMatrix);
343  flutter::DlMatrix finalMatrix = screenScaleMatrix * translateMatrix;
344 
345  auto embeddedViewParams =
346  std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, flutter::DlSize(300, 300), stack);
347 
348  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
349  withParams:std::move(embeddedViewParams)];
350 
351  XCTAssertNotNil(gMockPlatformView);
352 
353  [flutterPlatformViewsController reset];
354 }
355 
356 - (void)testCanCreatePlatformViewWithoutFlutterView {
357  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
358 
359  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
360  /*platform=*/GetDefaultTaskRunner(),
361  /*raster=*/GetDefaultTaskRunner(),
362  /*ui=*/GetDefaultTaskRunner(),
363  /*io=*/GetDefaultTaskRunner());
364  FlutterPlatformViewsController* flutterPlatformViewsController =
365  [[FlutterPlatformViewsController alloc] init];
366  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
367  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
368  /*delegate=*/mock_delegate,
369  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
370  /*platform_views_controller=*/flutterPlatformViewsController,
371  /*task_runners=*/runners,
372  /*worker_task_runner=*/nil,
373  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
374 
377  [flutterPlatformViewsController
378  registerViewFactory:factory
379  withId:@"MockFlutterPlatformView"
380  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
381  FlutterResult result = ^(id result) {
382  };
383  [flutterPlatformViewsController
385  arguments:@{
386  @"id" : @2,
387  @"viewType" : @"MockFlutterPlatformView"
388  }]
389  result:result];
390 
391  XCTAssertNotNil(gMockPlatformView);
392 }
393 
394 - (void)testChildClippingViewHitTests {
395  ChildClippingView* childClippingView =
396  [[ChildClippingView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
397  UIView* childView = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
398  [childClippingView addSubview:childView];
399 
400  XCTAssertFalse([childClippingView pointInside:CGPointMake(50, 50) withEvent:nil]);
401  XCTAssertFalse([childClippingView pointInside:CGPointMake(99, 100) withEvent:nil]);
402  XCTAssertFalse([childClippingView pointInside:CGPointMake(100, 99) withEvent:nil]);
403  XCTAssertFalse([childClippingView pointInside:CGPointMake(201, 200) withEvent:nil]);
404  XCTAssertFalse([childClippingView pointInside:CGPointMake(200, 201) withEvent:nil]);
405  XCTAssertFalse([childClippingView pointInside:CGPointMake(99, 200) withEvent:nil]);
406  XCTAssertFalse([childClippingView pointInside:CGPointMake(200, 299) withEvent:nil]);
407 
408  XCTAssertTrue([childClippingView pointInside:CGPointMake(150, 150) withEvent:nil]);
409  XCTAssertTrue([childClippingView pointInside:CGPointMake(100, 100) withEvent:nil]);
410  XCTAssertTrue([childClippingView pointInside:CGPointMake(199, 100) withEvent:nil]);
411  XCTAssertTrue([childClippingView pointInside:CGPointMake(100, 199) withEvent:nil]);
412  XCTAssertTrue([childClippingView pointInside:CGPointMake(199, 199) withEvent:nil]);
413 }
414 
415 - (void)testReleasesBackdropFilterSubviewsOnChildClippingViewDealloc {
416  __weak NSMutableArray<UIVisualEffectView*>* weakBackdropFilterSubviews = nil;
417  __weak UIVisualEffectView* weakVisualEffectView1 = nil;
418  __weak UIVisualEffectView* weakVisualEffectView2 = nil;
419 
420  @autoreleasepool {
421  ChildClippingView* clippingView = [[ChildClippingView alloc] initWithFrame:CGRectZero];
422  UIVisualEffectView* visualEffectView1 = [[UIVisualEffectView alloc]
423  initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]];
424  weakVisualEffectView1 = visualEffectView1;
425  PlatformViewFilter* platformViewFilter1 =
426  [[PlatformViewFilter alloc] initWithFrame:CGRectMake(0, 0, 10, 10)
427  blurRadius:5
428  visualEffectView:visualEffectView1];
429 
430  [clippingView applyBlurBackdropFilters:@[ platformViewFilter1 ]];
431 
432  // Replace the blur filter to validate the original and new UIVisualEffectView are released.
433  UIVisualEffectView* visualEffectView2 = [[UIVisualEffectView alloc]
434  initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleDark]];
435  weakVisualEffectView2 = visualEffectView2;
436  PlatformViewFilter* platformViewFilter2 =
437  [[PlatformViewFilter alloc] initWithFrame:CGRectMake(0, 0, 10, 10)
438  blurRadius:5
439  visualEffectView:visualEffectView2];
440  [clippingView applyBlurBackdropFilters:@[ platformViewFilter2 ]];
441 
442  weakBackdropFilterSubviews = clippingView.backdropFilterSubviews;
443  XCTAssertNotNil(weakBackdropFilterSubviews);
444  clippingView = nil;
445  }
446  XCTAssertNil(weakBackdropFilterSubviews);
447  XCTAssertNil(weakVisualEffectView1);
448  XCTAssertNil(weakVisualEffectView2);
449 }
450 
451 - (void)testApplyBackdropFilter {
452  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
453 
454  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
455  /*platform=*/GetDefaultTaskRunner(),
456  /*raster=*/GetDefaultTaskRunner(),
457  /*ui=*/GetDefaultTaskRunner(),
458  /*io=*/GetDefaultTaskRunner());
459  FlutterPlatformViewsController* flutterPlatformViewsController =
460  [[FlutterPlatformViewsController alloc] init];
461  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
462  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
463  /*delegate=*/mock_delegate,
464  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
465  /*platform_views_controller=*/flutterPlatformViewsController,
466  /*task_runners=*/runners,
467  /*worker_task_runner=*/nil,
468  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
469 
472  [flutterPlatformViewsController
473  registerViewFactory:factory
474  withId:@"MockFlutterPlatformView"
475  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
476  FlutterResult result = ^(id result) {
477  };
478  [flutterPlatformViewsController
480  arguments:@{
481  @"id" : @2,
482  @"viewType" : @"MockFlutterPlatformView"
483  }]
484  result:result];
485 
486  XCTAssertNotNil(gMockPlatformView);
487 
488  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
489  flutterPlatformViewsController.flutterView = flutterView;
490  // Create embedded view params
491  flutter::MutatorsStack stack;
492  // Layer tree always pushes a screen scale factor to the stack
493  flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
494  flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
495  stack.PushTransform(screenScaleMatrix);
496  // Push a backdrop filter
497  auto filter = flutter::DlBlurImageFilter::Make(5, 2, flutter::DlTileMode::kClamp);
498  stack.PushBackdropFilter(filter,
499  flutter::DlRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
500 
501  auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
502  screenScaleMatrix, flutter::DlSize(10, 10), stack);
503 
504  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
505  withParams:std::move(embeddedViewParams)];
506  [flutterPlatformViewsController
507  compositeView:2
508  withParams:[flutterPlatformViewsController compositionParamsForView:2]];
509 
510  XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:[ChildClippingView class]]);
511  ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
512  [flutterView addSubview:childClippingView];
513 
514  [flutterView setNeedsLayout];
515  [flutterView layoutIfNeeded];
516 
517  // childClippingView has visual effect view with the correct configurations.
518  NSUInteger numberOfExpectedVisualEffectView = 0;
519  for (UIView* subview in childClippingView.subviews) {
520  if (![subview isKindOfClass:[UIVisualEffectView class]]) {
521  continue;
522  }
523  XCTAssertLessThan(numberOfExpectedVisualEffectView, 1u);
524  if ([self validateOneVisualEffectView:subview
525  expectedFrame:CGRectMake(0, 0, 10, 10)
526  inputRadius:5]) {
527  numberOfExpectedVisualEffectView++;
528  }
529  }
530  XCTAssertEqual(numberOfExpectedVisualEffectView, 1u);
531 }
532 
533 - (void)testApplyBackdropFilterWithCorrectFrame {
534  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
535 
536  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
537  /*platform=*/GetDefaultTaskRunner(),
538  /*raster=*/GetDefaultTaskRunner(),
539  /*ui=*/GetDefaultTaskRunner(),
540  /*io=*/GetDefaultTaskRunner());
541  FlutterPlatformViewsController* flutterPlatformViewsController =
542  [[FlutterPlatformViewsController alloc] init];
543  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
544  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
545  /*delegate=*/mock_delegate,
546  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
547  /*platform_views_controller=*/flutterPlatformViewsController,
548  /*task_runners=*/runners,
549  /*worker_task_runner=*/nil,
550  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
551 
554  [flutterPlatformViewsController
555  registerViewFactory:factory
556  withId:@"MockFlutterPlatformView"
557  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
558  FlutterResult result = ^(id result) {
559  };
560  [flutterPlatformViewsController
562  arguments:@{
563  @"id" : @2,
564  @"viewType" : @"MockFlutterPlatformView"
565  }]
566  result:result];
567 
568  XCTAssertNotNil(gMockPlatformView);
569 
570  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
571  flutterPlatformViewsController.flutterView = flutterView;
572  // Create embedded view params
573  flutter::MutatorsStack stack;
574  // Layer tree always pushes a screen scale factor to the stack
575  flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
576  flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
577  stack.PushTransform(screenScaleMatrix);
578  // Push a backdrop filter
579  auto filter = flutter::DlBlurImageFilter::Make(5, 2, flutter::DlTileMode::kClamp);
580  stack.PushBackdropFilter(filter,
581  flutter::DlRect::MakeXYWH(0, 0, screenScale * 8, screenScale * 8));
582 
583  auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
584  screenScaleMatrix, flutter::DlSize(5, 10), stack);
585 
586  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
587  withParams:std::move(embeddedViewParams)];
588  [flutterPlatformViewsController
589  compositeView:2
590  withParams:[flutterPlatformViewsController compositionParamsForView:2]];
591 
592  XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:[ChildClippingView class]]);
593  ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
594  [flutterView addSubview:childClippingView];
595 
596  [flutterView setNeedsLayout];
597  [flutterView layoutIfNeeded];
598 
599  // childClippingView has visual effect view with the correct configurations.
600  NSUInteger numberOfExpectedVisualEffectView = 0;
601  for (UIView* subview in childClippingView.subviews) {
602  if (![subview isKindOfClass:[UIVisualEffectView class]]) {
603  continue;
604  }
605  XCTAssertLessThan(numberOfExpectedVisualEffectView, 1u);
606  if ([self validateOneVisualEffectView:subview
607  expectedFrame:CGRectMake(0, 0, 5, 8)
608  inputRadius:5]) {
609  numberOfExpectedVisualEffectView++;
610  }
611  }
612  XCTAssertEqual(numberOfExpectedVisualEffectView, 1u);
613 }
614 
615 - (void)testApplyMultipleBackdropFilters {
616  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
617 
618  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
619  /*platform=*/GetDefaultTaskRunner(),
620  /*raster=*/GetDefaultTaskRunner(),
621  /*ui=*/GetDefaultTaskRunner(),
622  /*io=*/GetDefaultTaskRunner());
623  FlutterPlatformViewsController* flutterPlatformViewsController =
624  [[FlutterPlatformViewsController alloc] init];
625  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
626  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
627  /*delegate=*/mock_delegate,
628  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
629  /*platform_views_controller=*/flutterPlatformViewsController,
630  /*task_runners=*/runners,
631  /*worker_task_runner=*/nil,
632  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
633 
636  [flutterPlatformViewsController
637  registerViewFactory:factory
638  withId:@"MockFlutterPlatformView"
639  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
640  FlutterResult result = ^(id result) {
641  };
642  [flutterPlatformViewsController
644  arguments:@{
645  @"id" : @2,
646  @"viewType" : @"MockFlutterPlatformView"
647  }]
648  result:result];
649 
650  XCTAssertNotNil(gMockPlatformView);
651 
652  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
653  flutterPlatformViewsController.flutterView = flutterView;
654  // Create embedded view params
655  flutter::MutatorsStack stack;
656  // Layer tree always pushes a screen scale factor to the stack
657  flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
658  flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
659  stack.PushTransform(screenScaleMatrix);
660  // Push backdrop filters
661  for (int i = 0; i < 50; i++) {
662  auto filter = flutter::DlBlurImageFilter::Make(i, 2, flutter::DlTileMode::kClamp);
663  stack.PushBackdropFilter(filter,
664  flutter::DlRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
665  }
666 
667  auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
668  screenScaleMatrix, flutter::DlSize(20, 20), stack);
669 
670  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
671  withParams:std::move(embeddedViewParams)];
672  [flutterPlatformViewsController
673  compositeView:2
674  withParams:[flutterPlatformViewsController compositionParamsForView:2]];
675 
676  XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]);
677  ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
678  [flutterView addSubview:childClippingView];
679 
680  [flutterView setNeedsLayout];
681  [flutterView layoutIfNeeded];
682 
683  NSUInteger numberOfExpectedVisualEffectView = 0;
684  for (UIView* subview in childClippingView.subviews) {
685  if (![subview isKindOfClass:[UIVisualEffectView class]]) {
686  continue;
687  }
688  XCTAssertLessThan(numberOfExpectedVisualEffectView, 50u);
689  if ([self validateOneVisualEffectView:subview
690  expectedFrame:CGRectMake(0, 0, 10, 10)
691  inputRadius:(CGFloat)numberOfExpectedVisualEffectView]) {
692  numberOfExpectedVisualEffectView++;
693  }
694  }
695  XCTAssertEqual(numberOfExpectedVisualEffectView, (NSUInteger)numberOfExpectedVisualEffectView);
696 }
697 
698 - (void)testAddBackdropFilters {
699  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
700 
701  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
702  /*platform=*/GetDefaultTaskRunner(),
703  /*raster=*/GetDefaultTaskRunner(),
704  /*ui=*/GetDefaultTaskRunner(),
705  /*io=*/GetDefaultTaskRunner());
706  FlutterPlatformViewsController* flutterPlatformViewsController =
707  [[FlutterPlatformViewsController alloc] init];
708  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
709  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
710  /*delegate=*/mock_delegate,
711  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
712  /*platform_views_controller=*/flutterPlatformViewsController,
713  /*task_runners=*/runners,
714  /*worker_task_runner=*/nil,
715  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
716 
719  [flutterPlatformViewsController
720  registerViewFactory:factory
721  withId:@"MockFlutterPlatformView"
722  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
723  FlutterResult result = ^(id result) {
724  };
725  [flutterPlatformViewsController
727  arguments:@{
728  @"id" : @2,
729  @"viewType" : @"MockFlutterPlatformView"
730  }]
731  result:result];
732 
733  XCTAssertNotNil(gMockPlatformView);
734 
735  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
736  flutterPlatformViewsController.flutterView = flutterView;
737  // Create embedded view params
738  flutter::MutatorsStack stack;
739  // Layer tree always pushes a screen scale factor to the stack
740  flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
741  flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
742  stack.PushTransform(screenScaleMatrix);
743  // Push a backdrop filter
744  auto filter = flutter::DlBlurImageFilter::Make(5, 2, flutter::DlTileMode::kClamp);
745  stack.PushBackdropFilter(filter,
746  flutter::DlRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
747 
748  auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
749  screenScaleMatrix, flutter::DlSize(10, 10), stack);
750 
751  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
752  withParams:std::move(embeddedViewParams)];
753  [flutterPlatformViewsController
754  compositeView:2
755  withParams:[flutterPlatformViewsController compositionParamsForView:2]];
756 
757  XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:[ChildClippingView class]]);
758  ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
759  [flutterView addSubview:childClippingView];
760 
761  [flutterView setNeedsLayout];
762  [flutterView layoutIfNeeded];
763 
764  NSMutableArray* originalVisualEffectViews = [[NSMutableArray alloc] init];
765  for (UIView* subview in childClippingView.subviews) {
766  if (![subview isKindOfClass:[UIVisualEffectView class]]) {
767  continue;
768  }
769  XCTAssertLessThan(originalVisualEffectViews.count, 1u);
770  if ([self validateOneVisualEffectView:subview
771  expectedFrame:CGRectMake(0, 0, 10, 10)
772  inputRadius:(CGFloat)5]) {
773  [originalVisualEffectViews addObject:subview];
774  }
775  }
776  XCTAssertEqual(originalVisualEffectViews.count, 1u);
777 
778  //
779  // Simulate adding 1 backdrop filter (create a new mutators stack)
780  // Create embedded view params
781  flutter::MutatorsStack stack2;
782  // Layer tree always pushes a screen scale factor to the stack
783  stack2.PushTransform(screenScaleMatrix);
784  // Push backdrop filters
785  for (int i = 0; i < 2; i++) {
786  stack2.PushBackdropFilter(filter,
787  flutter::DlRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
788  }
789 
790  embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
791  screenScaleMatrix, flutter::DlSize(10, 10), stack2);
792 
793  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
794  withParams:std::move(embeddedViewParams)];
795  [flutterPlatformViewsController
796  compositeView:2
797  withParams:[flutterPlatformViewsController compositionParamsForView:2]];
798 
799  [flutterView setNeedsLayout];
800  [flutterView layoutIfNeeded];
801 
802  NSMutableArray* newVisualEffectViews = [[NSMutableArray alloc] init];
803  for (UIView* subview in childClippingView.subviews) {
804  if (![subview isKindOfClass:[UIVisualEffectView class]]) {
805  continue;
806  }
807  XCTAssertLessThan(newVisualEffectViews.count, 2u);
808 
809  if ([self validateOneVisualEffectView:subview
810  expectedFrame:CGRectMake(0, 0, 10, 10)
811  inputRadius:(CGFloat)5]) {
812  [newVisualEffectViews addObject:subview];
813  }
814  }
815  XCTAssertEqual(newVisualEffectViews.count, 2u);
816  for (NSUInteger i = 0; i < originalVisualEffectViews.count; i++) {
817  UIView* originalView = originalVisualEffectViews[i];
818  UIView* newView = newVisualEffectViews[i];
819  // Compare reference.
820  XCTAssertEqual(originalView, newView);
821  id mockOrignalView = OCMPartialMock(originalView);
822  OCMReject([mockOrignalView removeFromSuperview]);
823  [mockOrignalView stopMocking];
824  }
825 }
826 
827 - (void)testRemoveBackdropFilters {
828  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
829 
830  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
831  /*platform=*/GetDefaultTaskRunner(),
832  /*raster=*/GetDefaultTaskRunner(),
833  /*ui=*/GetDefaultTaskRunner(),
834  /*io=*/GetDefaultTaskRunner());
835  FlutterPlatformViewsController* flutterPlatformViewsController =
836  [[FlutterPlatformViewsController alloc] init];
837  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
838  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
839  /*delegate=*/mock_delegate,
840  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
841  /*platform_views_controller=*/flutterPlatformViewsController,
842  /*task_runners=*/runners,
843  /*worker_task_runner=*/nil,
844  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
845 
848  [flutterPlatformViewsController
849  registerViewFactory:factory
850  withId:@"MockFlutterPlatformView"
851  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
852  FlutterResult result = ^(id result) {
853  };
854  [flutterPlatformViewsController
856  arguments:@{
857  @"id" : @2,
858  @"viewType" : @"MockFlutterPlatformView"
859  }]
860  result:result];
861 
862  XCTAssertNotNil(gMockPlatformView);
863 
864  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
865  flutterPlatformViewsController.flutterView = flutterView;
866  // Create embedded view params
867  flutter::MutatorsStack stack;
868  // Layer tree always pushes a screen scale factor to the stack
869  flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
870  flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
871  stack.PushTransform(screenScaleMatrix);
872  // Push backdrop filters
873  auto filter = flutter::DlBlurImageFilter::Make(5, 2, flutter::DlTileMode::kClamp);
874  for (int i = 0; i < 5; i++) {
875  stack.PushBackdropFilter(filter,
876  flutter::DlRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
877  }
878 
879  auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
880  screenScaleMatrix, flutter::DlSize(10, 10), stack);
881 
882  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
883  withParams:std::move(embeddedViewParams)];
884  [flutterPlatformViewsController
885  compositeView:2
886  withParams:[flutterPlatformViewsController compositionParamsForView:2]];
887 
888  XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]);
889  ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
890  [flutterView addSubview:childClippingView];
891 
892  [flutterView setNeedsLayout];
893  [flutterView layoutIfNeeded];
894 
895  NSMutableArray* originalVisualEffectViews = [[NSMutableArray alloc] init];
896  for (UIView* subview in childClippingView.subviews) {
897  if (![subview isKindOfClass:[UIVisualEffectView class]]) {
898  continue;
899  }
900  XCTAssertLessThan(originalVisualEffectViews.count, 5u);
901  if ([self validateOneVisualEffectView:subview
902  expectedFrame:CGRectMake(0, 0, 10, 10)
903  inputRadius:(CGFloat)5]) {
904  [originalVisualEffectViews addObject:subview];
905  }
906  }
907 
908  // Simulate removing 1 backdrop filter (create a new mutators stack)
909  // Create embedded view params
910  flutter::MutatorsStack stack2;
911  // Layer tree always pushes a screen scale factor to the stack
912  stack2.PushTransform(screenScaleMatrix);
913  // Push backdrop filters
914  for (int i = 0; i < 4; i++) {
915  stack2.PushBackdropFilter(filter,
916  flutter::DlRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
917  }
918 
919  embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
920  screenScaleMatrix, flutter::DlSize(10, 10), stack2);
921 
922  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
923  withParams:std::move(embeddedViewParams)];
924  [flutterPlatformViewsController
925  compositeView:2
926  withParams:[flutterPlatformViewsController compositionParamsForView:2]];
927 
928  [flutterView setNeedsLayout];
929  [flutterView layoutIfNeeded];
930 
931  NSMutableArray* newVisualEffectViews = [[NSMutableArray alloc] init];
932  for (UIView* subview in childClippingView.subviews) {
933  if (![subview isKindOfClass:[UIVisualEffectView class]]) {
934  continue;
935  }
936  XCTAssertLessThan(newVisualEffectViews.count, 4u);
937  if ([self validateOneVisualEffectView:subview
938  expectedFrame:CGRectMake(0, 0, 10, 10)
939  inputRadius:(CGFloat)5]) {
940  [newVisualEffectViews addObject:subview];
941  }
942  }
943  XCTAssertEqual(newVisualEffectViews.count, 4u);
944 
945  for (NSUInteger i = 0; i < newVisualEffectViews.count; i++) {
946  UIView* newView = newVisualEffectViews[i];
947  id mockNewView = OCMPartialMock(newView);
948  UIView* originalView = originalVisualEffectViews[i];
949  // Compare reference.
950  XCTAssertEqual(originalView, newView);
951  OCMReject([mockNewView removeFromSuperview]);
952  [mockNewView stopMocking];
953  }
954 
955  // Simulate removing all backdrop filters (replace the mutators stack)
956  // Update embedded view params, delete except screenScaleMatrix
957  for (int i = 0; i < 5; i++) {
958  stack2.Pop();
959  }
960  // No backdrop filters in the stack, so no nothing to push
961 
962  embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
963  screenScaleMatrix, flutter::DlSize(10, 10), stack2);
964 
965  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
966  withParams:std::move(embeddedViewParams)];
967  [flutterPlatformViewsController
968  compositeView:2
969  withParams:[flutterPlatformViewsController compositionParamsForView:2]];
970 
971  [flutterView setNeedsLayout];
972  [flutterView layoutIfNeeded];
973 
974  NSUInteger numberOfExpectedVisualEffectView = 0u;
975  for (UIView* subview in childClippingView.subviews) {
976  if ([subview isKindOfClass:[UIVisualEffectView class]]) {
977  numberOfExpectedVisualEffectView++;
978  }
979  }
980  XCTAssertEqual(numberOfExpectedVisualEffectView, 0u);
981 }
982 
983 - (void)testEditBackdropFilters {
984  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
985 
986  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
987  /*platform=*/GetDefaultTaskRunner(),
988  /*raster=*/GetDefaultTaskRunner(),
989  /*ui=*/GetDefaultTaskRunner(),
990  /*io=*/GetDefaultTaskRunner());
991  FlutterPlatformViewsController* flutterPlatformViewsController =
992  [[FlutterPlatformViewsController alloc] init];
993  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
994  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
995  /*delegate=*/mock_delegate,
996  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
997  /*platform_views_controller=*/flutterPlatformViewsController,
998  /*task_runners=*/runners,
999  /*worker_task_runner=*/nil,
1000  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
1001 
1004  [flutterPlatformViewsController
1005  registerViewFactory:factory
1006  withId:@"MockFlutterPlatformView"
1007  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
1008  FlutterResult result = ^(id result) {
1009  };
1010  [flutterPlatformViewsController
1012  arguments:@{
1013  @"id" : @2,
1014  @"viewType" : @"MockFlutterPlatformView"
1015  }]
1016  result:result];
1017 
1018  XCTAssertNotNil(gMockPlatformView);
1019 
1020  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
1021  flutterPlatformViewsController.flutterView = flutterView;
1022  // Create embedded view params
1023  flutter::MutatorsStack stack;
1024  // Layer tree always pushes a screen scale factor to the stack
1025  flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
1026  flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
1027  stack.PushTransform(screenScaleMatrix);
1028  // Push backdrop filters
1029  auto filter = flutter::DlBlurImageFilter::Make(5, 2, flutter::DlTileMode::kClamp);
1030  for (int i = 0; i < 5; i++) {
1031  stack.PushBackdropFilter(filter,
1032  flutter::DlRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
1033  }
1034 
1035  auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
1036  screenScaleMatrix, flutter::DlSize(10, 10), stack);
1037 
1038  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
1039  withParams:std::move(embeddedViewParams)];
1040  [flutterPlatformViewsController
1041  compositeView:2
1042  withParams:[flutterPlatformViewsController compositionParamsForView:2]];
1043 
1044  XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]);
1045  ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
1046  [flutterView addSubview:childClippingView];
1047 
1048  [flutterView setNeedsLayout];
1049  [flutterView layoutIfNeeded];
1050 
1051  NSMutableArray* originalVisualEffectViews = [[NSMutableArray alloc] init];
1052  for (UIView* subview in childClippingView.subviews) {
1053  if (![subview isKindOfClass:[UIVisualEffectView class]]) {
1054  continue;
1055  }
1056  XCTAssertLessThan(originalVisualEffectViews.count, 5u);
1057  if ([self validateOneVisualEffectView:subview
1058  expectedFrame:CGRectMake(0, 0, 10, 10)
1059  inputRadius:(CGFloat)5]) {
1060  [originalVisualEffectViews addObject:subview];
1061  }
1062  }
1063 
1064  // Simulate editing 1 backdrop filter in the middle of the stack (create a new mutators stack)
1065  // Create embedded view params
1066  flutter::MutatorsStack stack2;
1067  // Layer tree always pushes a screen scale factor to the stack
1068  stack2.PushTransform(screenScaleMatrix);
1069  // Push backdrop filters
1070  for (int i = 0; i < 5; i++) {
1071  if (i == 3) {
1072  auto filter2 = flutter::DlBlurImageFilter::Make(2, 5, flutter::DlTileMode::kClamp);
1073 
1074  stack2.PushBackdropFilter(
1075  filter2, flutter::DlRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
1076  continue;
1077  }
1078 
1079  stack2.PushBackdropFilter(filter,
1080  flutter::DlRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
1081  }
1082 
1083  embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
1084  screenScaleMatrix, flutter::DlSize(10, 10), stack2);
1085 
1086  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
1087  withParams:std::move(embeddedViewParams)];
1088  [flutterPlatformViewsController
1089  compositeView:2
1090  withParams:[flutterPlatformViewsController compositionParamsForView:2]];
1091 
1092  [flutterView setNeedsLayout];
1093  [flutterView layoutIfNeeded];
1094 
1095  NSMutableArray* newVisualEffectViews = [[NSMutableArray alloc] init];
1096  for (UIView* subview in childClippingView.subviews) {
1097  if (![subview isKindOfClass:[UIVisualEffectView class]]) {
1098  continue;
1099  }
1100  XCTAssertLessThan(newVisualEffectViews.count, 5u);
1101  CGFloat expectInputRadius = 5;
1102  if (newVisualEffectViews.count == 3) {
1103  expectInputRadius = 2;
1104  }
1105  if ([self validateOneVisualEffectView:subview
1106  expectedFrame:CGRectMake(0, 0, 10, 10)
1107  inputRadius:expectInputRadius]) {
1108  [newVisualEffectViews addObject:subview];
1109  }
1110  }
1111  XCTAssertEqual(newVisualEffectViews.count, 5u);
1112  for (NSUInteger i = 0; i < newVisualEffectViews.count; i++) {
1113  UIView* newView = newVisualEffectViews[i];
1114  id mockNewView = OCMPartialMock(newView);
1115  UIView* originalView = originalVisualEffectViews[i];
1116  // Compare reference.
1117  XCTAssertEqual(originalView, newView);
1118  OCMReject([mockNewView removeFromSuperview]);
1119  [mockNewView stopMocking];
1120  }
1121  [newVisualEffectViews removeAllObjects];
1122 
1123  // Simulate editing 1 backdrop filter in the beginning of the stack (replace the mutators stack)
1124  // Update embedded view params, delete except screenScaleMatrix
1125  for (int i = 0; i < 5; i++) {
1126  stack2.Pop();
1127  }
1128  // Push backdrop filters
1129  for (int i = 0; i < 5; i++) {
1130  if (i == 0) {
1131  auto filter2 = flutter::DlBlurImageFilter::Make(2, 5, flutter::DlTileMode::kClamp);
1132  stack2.PushBackdropFilter(
1133  filter2, flutter::DlRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
1134  continue;
1135  }
1136 
1137  stack2.PushBackdropFilter(filter,
1138  flutter::DlRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
1139  }
1140 
1141  embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
1142  screenScaleMatrix, flutter::DlSize(10, 10), stack2);
1143 
1144  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
1145  withParams:std::move(embeddedViewParams)];
1146  [flutterPlatformViewsController
1147  compositeView:2
1148  withParams:[flutterPlatformViewsController compositionParamsForView:2]];
1149 
1150  [flutterView setNeedsLayout];
1151  [flutterView layoutIfNeeded];
1152 
1153  for (UIView* subview in childClippingView.subviews) {
1154  if (![subview isKindOfClass:[UIVisualEffectView class]]) {
1155  continue;
1156  }
1157  XCTAssertLessThan(newVisualEffectViews.count, 5u);
1158  CGFloat expectInputRadius = 5;
1159  if (newVisualEffectViews.count == 0) {
1160  expectInputRadius = 2;
1161  }
1162  if ([self validateOneVisualEffectView:subview
1163  expectedFrame:CGRectMake(0, 0, 10, 10)
1164  inputRadius:expectInputRadius]) {
1165  [newVisualEffectViews addObject:subview];
1166  }
1167  }
1168  for (NSUInteger i = 0; i < newVisualEffectViews.count; i++) {
1169  UIView* newView = newVisualEffectViews[i];
1170  id mockNewView = OCMPartialMock(newView);
1171  UIView* originalView = originalVisualEffectViews[i];
1172  // Compare reference.
1173  XCTAssertEqual(originalView, newView);
1174  OCMReject([mockNewView removeFromSuperview]);
1175  [mockNewView stopMocking];
1176  }
1177  [newVisualEffectViews removeAllObjects];
1178 
1179  // Simulate editing 1 backdrop filter in the end of the stack (replace the mutators stack)
1180  // Update embedded view params, delete except screenScaleMatrix
1181  for (int i = 0; i < 5; i++) {
1182  stack2.Pop();
1183  }
1184  // Push backdrop filters
1185  for (int i = 0; i < 5; i++) {
1186  if (i == 4) {
1187  auto filter2 = flutter::DlBlurImageFilter::Make(2, 5, flutter::DlTileMode::kClamp);
1188  stack2.PushBackdropFilter(
1189  filter2, flutter::DlRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
1190  continue;
1191  }
1192 
1193  stack2.PushBackdropFilter(filter,
1194  flutter::DlRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
1195  }
1196 
1197  embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
1198  screenScaleMatrix, flutter::DlSize(10, 10), stack2);
1199 
1200  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
1201  withParams:std::move(embeddedViewParams)];
1202  [flutterPlatformViewsController
1203  compositeView:2
1204  withParams:[flutterPlatformViewsController compositionParamsForView:2]];
1205 
1206  [flutterView setNeedsLayout];
1207  [flutterView layoutIfNeeded];
1208 
1209  for (UIView* subview in childClippingView.subviews) {
1210  if (![subview isKindOfClass:[UIVisualEffectView class]]) {
1211  continue;
1212  }
1213  XCTAssertLessThan(newVisualEffectViews.count, 5u);
1214  CGFloat expectInputRadius = 5;
1215  if (newVisualEffectViews.count == 4) {
1216  expectInputRadius = 2;
1217  }
1218  if ([self validateOneVisualEffectView:subview
1219  expectedFrame:CGRectMake(0, 0, 10, 10)
1220  inputRadius:expectInputRadius]) {
1221  [newVisualEffectViews addObject:subview];
1222  }
1223  }
1224  XCTAssertEqual(newVisualEffectViews.count, 5u);
1225 
1226  for (NSUInteger i = 0; i < newVisualEffectViews.count; i++) {
1227  UIView* newView = newVisualEffectViews[i];
1228  id mockNewView = OCMPartialMock(newView);
1229  UIView* originalView = originalVisualEffectViews[i];
1230  // Compare reference.
1231  XCTAssertEqual(originalView, newView);
1232  OCMReject([mockNewView removeFromSuperview]);
1233  [mockNewView stopMocking];
1234  }
1235  [newVisualEffectViews removeAllObjects];
1236 
1237  // Simulate editing all backdrop filters in the stack (replace the mutators stack)
1238  // Update embedded view params, delete except screenScaleMatrix
1239  for (int i = 0; i < 5; i++) {
1240  stack2.Pop();
1241  }
1242  // Push backdrop filters
1243  for (int i = 0; i < 5; i++) {
1244  auto filter2 = flutter::DlBlurImageFilter::Make(i, 2, flutter::DlTileMode::kClamp);
1245 
1246  stack2.PushBackdropFilter(filter2,
1247  flutter::DlRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
1248  }
1249 
1250  embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
1251  screenScaleMatrix, flutter::DlSize(10, 10), stack2);
1252 
1253  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
1254  withParams:std::move(embeddedViewParams)];
1255  [flutterPlatformViewsController
1256  compositeView:2
1257  withParams:[flutterPlatformViewsController compositionParamsForView:2]];
1258 
1259  [flutterView setNeedsLayout];
1260  [flutterView layoutIfNeeded];
1261 
1262  for (UIView* subview in childClippingView.subviews) {
1263  if (![subview isKindOfClass:[UIVisualEffectView class]]) {
1264  continue;
1265  }
1266  XCTAssertLessThan(newVisualEffectViews.count, 5u);
1267  if ([self validateOneVisualEffectView:subview
1268  expectedFrame:CGRectMake(0, 0, 10, 10)
1269  inputRadius:(CGFloat)newVisualEffectViews.count]) {
1270  [newVisualEffectViews addObject:subview];
1271  }
1272  }
1273  XCTAssertEqual(newVisualEffectViews.count, 5u);
1274 
1275  for (NSUInteger i = 0; i < newVisualEffectViews.count; i++) {
1276  UIView* newView = newVisualEffectViews[i];
1277  id mockNewView = OCMPartialMock(newView);
1278  UIView* originalView = originalVisualEffectViews[i];
1279  // Compare reference.
1280  XCTAssertEqual(originalView, newView);
1281  OCMReject([mockNewView removeFromSuperview]);
1282  [mockNewView stopMocking];
1283  }
1284  [newVisualEffectViews removeAllObjects];
1285 }
1286 
1287 - (void)testApplyBackdropFilterNotDlBlurImageFilter {
1288  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
1289 
1290  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
1291  /*platform=*/GetDefaultTaskRunner(),
1292  /*raster=*/GetDefaultTaskRunner(),
1293  /*ui=*/GetDefaultTaskRunner(),
1294  /*io=*/GetDefaultTaskRunner());
1295  FlutterPlatformViewsController* flutterPlatformViewsController =
1296  [[FlutterPlatformViewsController alloc] init];
1297  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
1298  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1299  /*delegate=*/mock_delegate,
1300  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
1301  /*platform_views_controller=*/flutterPlatformViewsController,
1302  /*task_runners=*/runners,
1303  /*worker_task_runner=*/nil,
1304  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
1305 
1308  [flutterPlatformViewsController
1309  registerViewFactory:factory
1310  withId:@"MockFlutterPlatformView"
1311  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
1312  FlutterResult result = ^(id result) {
1313  };
1314  [flutterPlatformViewsController
1316  arguments:@{
1317  @"id" : @2,
1318  @"viewType" : @"MockFlutterPlatformView"
1319  }]
1320  result:result];
1321 
1322  XCTAssertNotNil(gMockPlatformView);
1323 
1324  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
1325  flutterPlatformViewsController.flutterView = flutterView;
1326  // Create embedded view params
1327  flutter::MutatorsStack stack;
1328  // Layer tree always pushes a screen scale factor to the stack
1329  flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
1330  flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
1331  stack.PushTransform(screenScaleMatrix);
1332  // Push a dilate backdrop filter
1333  auto dilateFilter = flutter::DlDilateImageFilter::Make(5, 2);
1334  stack.PushBackdropFilter(dilateFilter, flutter::DlRect());
1335 
1336  auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
1337  screenScaleMatrix, flutter::DlSize(10, 10), stack);
1338 
1339  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
1340  withParams:std::move(embeddedViewParams)];
1341  [flutterPlatformViewsController
1342  compositeView:2
1343  withParams:[flutterPlatformViewsController compositionParamsForView:2]];
1344 
1345  XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:[ChildClippingView class]]);
1346  ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
1347 
1348  [flutterView addSubview:childClippingView];
1349 
1350  [flutterView setNeedsLayout];
1351  [flutterView layoutIfNeeded];
1352 
1353  NSUInteger numberOfExpectedVisualEffectView = 0;
1354  for (UIView* subview in childClippingView.subviews) {
1355  if ([subview isKindOfClass:[UIVisualEffectView class]]) {
1356  numberOfExpectedVisualEffectView++;
1357  }
1358  }
1359  XCTAssertEqual(numberOfExpectedVisualEffectView, 0u);
1360 
1361  // Simulate adding a non-DlBlurImageFilter in the middle of the stack (create a new mutators
1362  // stack) Create embedded view params
1363  flutter::MutatorsStack stack2;
1364  // Layer tree always pushes a screen scale factor to the stack
1365  stack2.PushTransform(screenScaleMatrix);
1366  // Push backdrop filters and dilate filter
1367  auto blurFilter = flutter::DlBlurImageFilter::Make(5, 2, flutter::DlTileMode::kClamp);
1368 
1369  for (int i = 0; i < 5; i++) {
1370  if (i == 2) {
1371  stack2.PushBackdropFilter(
1372  dilateFilter, flutter::DlRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
1373  continue;
1374  }
1375 
1376  stack2.PushBackdropFilter(blurFilter,
1377  flutter::DlRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
1378  }
1379 
1380  embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
1381  screenScaleMatrix, flutter::DlSize(10, 10), stack2);
1382 
1383  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
1384  withParams:std::move(embeddedViewParams)];
1385  [flutterPlatformViewsController
1386  compositeView:2
1387  withParams:[flutterPlatformViewsController compositionParamsForView:2]];
1388 
1389  [flutterView setNeedsLayout];
1390  [flutterView layoutIfNeeded];
1391 
1392  numberOfExpectedVisualEffectView = 0;
1393  for (UIView* subview in childClippingView.subviews) {
1394  if (![subview isKindOfClass:[UIVisualEffectView class]]) {
1395  continue;
1396  }
1397  XCTAssertLessThan(numberOfExpectedVisualEffectView, 4u);
1398  if ([self validateOneVisualEffectView:subview
1399  expectedFrame:CGRectMake(0, 0, 10, 10)
1400  inputRadius:(CGFloat)5]) {
1401  numberOfExpectedVisualEffectView++;
1402  }
1403  }
1404  XCTAssertEqual(numberOfExpectedVisualEffectView, 4u);
1405 
1406  // Simulate adding a non-DlBlurImageFilter to the beginning of the stack (replace the mutators
1407  // stack) Update embedded view params, delete except screenScaleMatrix
1408  for (int i = 0; i < 5; i++) {
1409  stack2.Pop();
1410  }
1411  // Push backdrop filters and dilate filter
1412  for (int i = 0; i < 5; i++) {
1413  if (i == 0) {
1414  stack2.PushBackdropFilter(
1415  dilateFilter, flutter::DlRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
1416  continue;
1417  }
1418 
1419  stack2.PushBackdropFilter(blurFilter,
1420  flutter::DlRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
1421  }
1422 
1423  embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
1424  screenScaleMatrix, flutter::DlSize(10, 10), stack2);
1425 
1426  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
1427  withParams:std::move(embeddedViewParams)];
1428  [flutterPlatformViewsController
1429  compositeView:2
1430  withParams:[flutterPlatformViewsController compositionParamsForView:2]];
1431 
1432  [flutterView setNeedsLayout];
1433  [flutterView layoutIfNeeded];
1434 
1435  numberOfExpectedVisualEffectView = 0;
1436  for (UIView* subview in childClippingView.subviews) {
1437  if (![subview isKindOfClass:[UIVisualEffectView class]]) {
1438  continue;
1439  }
1440  XCTAssertLessThan(numberOfExpectedVisualEffectView, 4u);
1441  if ([self validateOneVisualEffectView:subview
1442  expectedFrame:CGRectMake(0, 0, 10, 10)
1443  inputRadius:(CGFloat)5]) {
1444  numberOfExpectedVisualEffectView++;
1445  }
1446  }
1447  XCTAssertEqual(numberOfExpectedVisualEffectView, 4u);
1448 
1449  // Simulate adding a non-DlBlurImageFilter to the end of the stack (replace the mutators stack)
1450  // Update embedded view params, delete except screenScaleMatrix
1451  for (int i = 0; i < 5; i++) {
1452  stack2.Pop();
1453  }
1454  // Push backdrop filters and dilate filter
1455  for (int i = 0; i < 5; i++) {
1456  if (i == 4) {
1457  stack2.PushBackdropFilter(
1458  dilateFilter, flutter::DlRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
1459  continue;
1460  }
1461 
1462  stack2.PushBackdropFilter(blurFilter,
1463  flutter::DlRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
1464  }
1465 
1466  embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
1467  screenScaleMatrix, flutter::DlSize(10, 10), stack2);
1468 
1469  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
1470  withParams:std::move(embeddedViewParams)];
1471  [flutterPlatformViewsController
1472  compositeView:2
1473  withParams:[flutterPlatformViewsController compositionParamsForView:2]];
1474 
1475  [flutterView setNeedsLayout];
1476  [flutterView layoutIfNeeded];
1477 
1478  numberOfExpectedVisualEffectView = 0;
1479  for (UIView* subview in childClippingView.subviews) {
1480  if (![subview isKindOfClass:[UIVisualEffectView class]]) {
1481  continue;
1482  }
1483  XCTAssertLessThan(numberOfExpectedVisualEffectView, 4u);
1484  if ([self validateOneVisualEffectView:subview
1485  expectedFrame:CGRectMake(0, 0, 10, 10)
1486  inputRadius:(CGFloat)5]) {
1487  numberOfExpectedVisualEffectView++;
1488  }
1489  }
1490  XCTAssertEqual(numberOfExpectedVisualEffectView, 4u);
1491 
1492  // Simulate adding only non-DlBlurImageFilter to the stack (replace the mutators stack)
1493  // Update embedded view params, delete except screenScaleMatrix
1494  for (int i = 0; i < 5; i++) {
1495  stack2.Pop();
1496  }
1497  // Push dilate filters
1498  for (int i = 0; i < 5; i++) {
1499  stack2.PushBackdropFilter(dilateFilter,
1500  flutter::DlRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
1501  }
1502 
1503  embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
1504  screenScaleMatrix, flutter::DlSize(10, 10), stack2);
1505 
1506  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
1507  withParams:std::move(embeddedViewParams)];
1508  [flutterPlatformViewsController
1509  compositeView:2
1510  withParams:[flutterPlatformViewsController compositionParamsForView:2]];
1511 
1512  [flutterView setNeedsLayout];
1513  [flutterView layoutIfNeeded];
1514 
1515  numberOfExpectedVisualEffectView = 0;
1516  for (UIView* subview in childClippingView.subviews) {
1517  if ([subview isKindOfClass:[UIVisualEffectView class]]) {
1518  numberOfExpectedVisualEffectView++;
1519  }
1520  }
1521  XCTAssertEqual(numberOfExpectedVisualEffectView, 0u);
1522 }
1523 
1524 - (void)testApplyBackdropFilterCorrectAPI {
1526  // The gaussianBlur filter is extracted from UIVisualEffectView.
1527  // Each test requires a new PlatformViewFilter
1528  // Valid UIVisualEffectView API
1529  UIVisualEffectView* visualEffectView = [[UIVisualEffectView alloc]
1530  initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]];
1531  PlatformViewFilter* platformViewFilter =
1532  [[PlatformViewFilter alloc] initWithFrame:CGRectMake(0, 0, 10, 10)
1533  blurRadius:5
1534  visualEffectView:visualEffectView];
1535  XCTAssertNotNil(platformViewFilter);
1536 }
1537 
1538 - (void)testApplyBackdropFilterAPIChangedInvalidUIVisualEffectView {
1540  UIVisualEffectView* visualEffectView = [[UIVisualEffectView alloc] init];
1541  PlatformViewFilter* platformViewFilter =
1542  [[PlatformViewFilter alloc] initWithFrame:CGRectMake(0, 0, 10, 10)
1543  blurRadius:5
1544  visualEffectView:visualEffectView];
1545  XCTAssertNil(platformViewFilter);
1546 }
1547 
1548 - (void)testApplyBackdropFilterAPIChangedNoGaussianBlurFilter {
1550  UIVisualEffectView* editedUIVisualEffectView = [[UIVisualEffectView alloc]
1551  initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]];
1552  NSArray* subviews = editedUIVisualEffectView.subviews;
1553  for (UIView* view in subviews) {
1554  if ([NSStringFromClass([view class]) hasSuffix:@"BackdropView"]) {
1555  for (CIFilter* filter in view.layer.filters) {
1556  if ([[filter valueForKey:@"name"] isEqual:@"gaussianBlur"]) {
1557  [filter setValue:@"notGaussianBlur" forKey:@"name"];
1558  break;
1559  }
1560  }
1561  break;
1562  }
1563  }
1564  PlatformViewFilter* platformViewFilter =
1565  [[PlatformViewFilter alloc] initWithFrame:CGRectMake(0, 0, 10, 10)
1566  blurRadius:5
1567  visualEffectView:editedUIVisualEffectView];
1568  XCTAssertNil(platformViewFilter);
1569 }
1570 
1571 - (void)testApplyBackdropFilterAPIChangedInvalidInputRadius {
1573  UIVisualEffectView* editedUIVisualEffectView = [[UIVisualEffectView alloc]
1574  initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]];
1575  NSArray* subviews = editedUIVisualEffectView.subviews;
1576  for (UIView* view in subviews) {
1577  if ([NSStringFromClass([view class]) hasSuffix:@"BackdropView"]) {
1578  for (CIFilter* filter in view.layer.filters) {
1579  if ([[filter valueForKey:@"name"] isEqual:@"gaussianBlur"]) {
1580  [filter setValue:@"invalidInputRadius" forKey:@"inputRadius"];
1581  break;
1582  }
1583  }
1584  break;
1585  }
1586  }
1587 
1588  PlatformViewFilter* platformViewFilter =
1589  [[PlatformViewFilter alloc] initWithFrame:CGRectMake(0, 0, 10, 10)
1590  blurRadius:5
1591  visualEffectView:editedUIVisualEffectView];
1592  XCTAssertNil(platformViewFilter);
1593 }
1594 
1595 - (void)testBackdropFilterVisualEffectSubviewBackgroundColor {
1596  __weak UIVisualEffectView* weakVisualEffectView;
1597 
1598  @autoreleasepool {
1599  UIVisualEffectView* visualEffectView = [[UIVisualEffectView alloc]
1600  initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]];
1601  weakVisualEffectView = visualEffectView;
1602  PlatformViewFilter* platformViewFilter =
1603  [[PlatformViewFilter alloc] initWithFrame:CGRectMake(0, 0, 10, 10)
1604  blurRadius:5
1605  visualEffectView:visualEffectView];
1606  CGColorRef visualEffectSubviewBackgroundColor = nil;
1607  for (UIView* view in [platformViewFilter backdropFilterView].subviews) {
1608  if ([NSStringFromClass([view class]) hasSuffix:@"VisualEffectSubview"]) {
1609  visualEffectSubviewBackgroundColor = view.layer.backgroundColor;
1610  }
1611  }
1612  XCTAssertTrue(
1613  CGColorEqualToColor(visualEffectSubviewBackgroundColor, UIColor.clearColor.CGColor));
1614  }
1615  XCTAssertNil(weakVisualEffectView);
1616 }
1617 
1618 - (void)testCompositePlatformView {
1619  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
1620 
1621  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
1622  /*platform=*/GetDefaultTaskRunner(),
1623  /*raster=*/GetDefaultTaskRunner(),
1624  /*ui=*/GetDefaultTaskRunner(),
1625  /*io=*/GetDefaultTaskRunner());
1626  FlutterPlatformViewsController* flutterPlatformViewsController =
1627  [[FlutterPlatformViewsController alloc] init];
1628  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
1629  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1630  /*delegate=*/mock_delegate,
1631  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
1632  /*platform_views_controller=*/flutterPlatformViewsController,
1633  /*task_runners=*/runners,
1634  /*worker_task_runner=*/nil,
1635  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
1636 
1639  [flutterPlatformViewsController
1640  registerViewFactory:factory
1641  withId:@"MockFlutterPlatformView"
1642  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
1643  FlutterResult result = ^(id result) {
1644  };
1645  [flutterPlatformViewsController
1647  arguments:@{
1648  @"id" : @2,
1649  @"viewType" : @"MockFlutterPlatformView"
1650  }]
1651  result:result];
1652 
1653  XCTAssertNotNil(gMockPlatformView);
1654 
1655  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
1656  flutterPlatformViewsController.flutterView = flutterView;
1657  // Create embedded view params
1658  flutter::MutatorsStack stack;
1659  // Layer tree always pushes a screen scale factor to the stack
1660  flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
1661  flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
1662  stack.PushTransform(screenScaleMatrix);
1663  // Push a translate matrix
1664  flutter::DlMatrix translateMatrix = flutter::DlMatrix::MakeTranslation({100, 100});
1665  stack.PushTransform(translateMatrix);
1666  flutter::DlMatrix finalMatrix = screenScaleMatrix * translateMatrix;
1667 
1668  auto embeddedViewParams =
1669  std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, flutter::DlSize(300, 300), stack);
1670 
1671  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
1672  withParams:std::move(embeddedViewParams)];
1673  [flutterPlatformViewsController
1674  compositeView:2
1675  withParams:[flutterPlatformViewsController compositionParamsForView:2]];
1676 
1677  CGRect platformViewRectInFlutterView = [gMockPlatformView convertRect:gMockPlatformView.bounds
1678  toView:flutterView];
1679  XCTAssertTrue(CGRectEqualToRect(platformViewRectInFlutterView, CGRectMake(100, 100, 300, 300)));
1680 }
1681 
1682 - (void)testBackdropFilterCorrectlyPushedAndReset {
1683  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
1684 
1685  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
1686  /*platform=*/GetDefaultTaskRunner(),
1687  /*raster=*/GetDefaultTaskRunner(),
1688  /*ui=*/GetDefaultTaskRunner(),
1689  /*io=*/GetDefaultTaskRunner());
1690  FlutterPlatformViewsController* flutterPlatformViewsController =
1691  [[FlutterPlatformViewsController alloc] init];
1692  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
1693  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1694  /*delegate=*/mock_delegate,
1695  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
1696  /*platform_views_controller=*/flutterPlatformViewsController,
1697  /*task_runners=*/runners,
1698  /*worker_task_runner=*/nil,
1699  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
1700 
1703  [flutterPlatformViewsController
1704  registerViewFactory:factory
1705  withId:@"MockFlutterPlatformView"
1706  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
1707  FlutterResult result = ^(id result) {
1708  };
1709  [flutterPlatformViewsController
1711  arguments:@{
1712  @"id" : @2,
1713  @"viewType" : @"MockFlutterPlatformView"
1714  }]
1715  result:result];
1716 
1717  XCTAssertNotNil(gMockPlatformView);
1718 
1719  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
1720  flutterPlatformViewsController.flutterView = flutterView;
1721  // Create embedded view params
1722  flutter::MutatorsStack stack;
1723  // Layer tree always pushes a screen scale factor to the stack
1724  flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
1725  flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
1726  stack.PushTransform(screenScaleMatrix);
1727 
1728  auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
1729  screenScaleMatrix, flutter::DlSize(10, 10), stack);
1730 
1731  [flutterPlatformViewsController beginFrameWithSize:flutter::DlISize(0, 0)];
1732  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
1733  withParams:std::move(embeddedViewParams)];
1734  [flutterPlatformViewsController pushVisitedPlatformViewId:2];
1735  auto filter = flutter::DlBlurImageFilter::Make(5, 2, flutter::DlTileMode::kClamp);
1736  [flutterPlatformViewsController
1737  pushFilterToVisitedPlatformViews:filter
1738  withRect:flutter::DlRect::MakeXYWH(0, 0, screenScale * 10,
1739  screenScale * 10)];
1740  [flutterPlatformViewsController
1741  compositeView:2
1742  withParams:[flutterPlatformViewsController compositionParamsForView:2]];
1743 
1744  XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:[ChildClippingView class]]);
1745  ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
1746  [flutterView addSubview:childClippingView];
1747 
1748  [flutterView setNeedsLayout];
1749  [flutterView layoutIfNeeded];
1750 
1751  // childClippingView has visual effect view with the correct configurations.
1752  NSUInteger numberOfExpectedVisualEffectView = 0;
1753  for (UIView* subview in childClippingView.subviews) {
1754  if (![subview isKindOfClass:[UIVisualEffectView class]]) {
1755  continue;
1756  }
1757  XCTAssertLessThan(numberOfExpectedVisualEffectView, 1u);
1758  if ([self validateOneVisualEffectView:subview
1759  expectedFrame:CGRectMake(0, 0, 10, 10)
1760  inputRadius:5]) {
1761  numberOfExpectedVisualEffectView++;
1762  }
1763  }
1764  XCTAssertEqual(numberOfExpectedVisualEffectView, 1u);
1765 
1766  // New frame, with no filter pushed.
1767  auto embeddedViewParams2 = std::make_unique<flutter::EmbeddedViewParams>(
1768  screenScaleMatrix, flutter::DlSize(10, 10), stack);
1769  [flutterPlatformViewsController beginFrameWithSize:flutter::DlISize(0, 0)];
1770  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
1771  withParams:std::move(embeddedViewParams2)];
1772  [flutterPlatformViewsController
1773  compositeView:2
1774  withParams:[flutterPlatformViewsController compositionParamsForView:2]];
1775 
1776  XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:[ChildClippingView class]]);
1777 
1778  [flutterView setNeedsLayout];
1779  [flutterView layoutIfNeeded];
1780 
1781  numberOfExpectedVisualEffectView = 0;
1782  for (UIView* subview in childClippingView.subviews) {
1783  if (![subview isKindOfClass:[UIVisualEffectView class]]) {
1784  continue;
1785  }
1786  numberOfExpectedVisualEffectView++;
1787  }
1788  XCTAssertEqual(numberOfExpectedVisualEffectView, 0u);
1789 }
1790 
1791 - (void)testChildClippingViewShouldBeTheBoundingRectOfPlatformView {
1792  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
1793 
1794  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
1795  /*platform=*/GetDefaultTaskRunner(),
1796  /*raster=*/GetDefaultTaskRunner(),
1797  /*ui=*/GetDefaultTaskRunner(),
1798  /*io=*/GetDefaultTaskRunner());
1799  FlutterPlatformViewsController* flutterPlatformViewsController =
1800  [[FlutterPlatformViewsController alloc] init];
1801  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
1802  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1803  /*delegate=*/mock_delegate,
1804  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
1805  /*platform_views_controller=*/flutterPlatformViewsController,
1806  /*task_runners=*/runners,
1807  /*worker_task_runner=*/nil,
1808  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
1809 
1812  [flutterPlatformViewsController
1813  registerViewFactory:factory
1814  withId:@"MockFlutterPlatformView"
1815  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
1816  FlutterResult result = ^(id result) {
1817  };
1818  [flutterPlatformViewsController
1820  arguments:@{
1821  @"id" : @2,
1822  @"viewType" : @"MockFlutterPlatformView"
1823  }]
1824  result:result];
1825 
1826  XCTAssertNotNil(gMockPlatformView);
1827 
1828  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
1829  flutterPlatformViewsController.flutterView = flutterView;
1830  // Create embedded view params
1831  flutter::MutatorsStack stack;
1832  // Layer tree always pushes a screen scale factor to the stack
1833  flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
1834  flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
1835  stack.PushTransform(screenScaleMatrix);
1836  // Push a rotate matrix
1837  flutter::DlMatrix rotateMatrix = flutter::DlMatrix::MakeRotationZ(flutter::DlDegrees(10));
1838  stack.PushTransform(rotateMatrix);
1839  flutter::DlMatrix finalMatrix = screenScaleMatrix * rotateMatrix;
1840 
1841  auto embeddedViewParams =
1842  std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, flutter::DlSize(300, 300), stack);
1843 
1844  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
1845  withParams:std::move(embeddedViewParams)];
1846  [flutterPlatformViewsController
1847  compositeView:2
1848  withParams:[flutterPlatformViewsController compositionParamsForView:2]];
1849 
1850  CGRect platformViewRectInFlutterView = [gMockPlatformView convertRect:gMockPlatformView.bounds
1851  toView:flutterView];
1852  XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]);
1853  ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
1854  // The childclippingview's frame is set based on flow, but the platform view's frame is set based
1855  // on quartz. Although they should be the same, but we should tolerate small floating point
1856  // errors.
1857  XCTAssertLessThan(fabs(platformViewRectInFlutterView.origin.x - childClippingView.frame.origin.x),
1859  XCTAssertLessThan(fabs(platformViewRectInFlutterView.origin.y - childClippingView.frame.origin.y),
1861  XCTAssertLessThan(
1862  fabs(platformViewRectInFlutterView.size.width - childClippingView.frame.size.width),
1864  XCTAssertLessThan(
1865  fabs(platformViewRectInFlutterView.size.height - childClippingView.frame.size.height),
1867 }
1868 
1869 - (void)testClipsDoNotInterceptWithPlatformViewShouldNotAddMaskView {
1870  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
1871 
1872  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
1873  /*platform=*/GetDefaultTaskRunner(),
1874  /*raster=*/GetDefaultTaskRunner(),
1875  /*ui=*/GetDefaultTaskRunner(),
1876  /*io=*/GetDefaultTaskRunner());
1877  FlutterPlatformViewsController* flutterPlatformViewsController =
1878  [[FlutterPlatformViewsController alloc] init];
1879  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
1880  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1881  /*delegate=*/mock_delegate,
1882  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
1883  /*platform_views_controller=*/flutterPlatformViewsController,
1884  /*task_runners=*/runners,
1885  /*worker_task_runner=*/nil,
1886  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
1887 
1890  [flutterPlatformViewsController
1891  registerViewFactory:factory
1892  withId:@"MockFlutterPlatformView"
1893  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
1894  FlutterResult result = ^(id result) {
1895  };
1896  [flutterPlatformViewsController
1898  arguments:@{
1899  @"id" : @2,
1900  @"viewType" : @"MockFlutterPlatformView"
1901  }]
1902  result:result];
1903 
1904  XCTAssertNotNil(gMockPlatformView);
1905 
1906  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 30, 30)];
1907  flutterPlatformViewsController.flutterView = flutterView;
1908  // Create embedded view params.
1909  flutter::MutatorsStack stack;
1910  // Layer tree always pushes a screen scale factor to the stack.
1911  flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
1912  flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
1913  stack.PushTransform(screenScaleMatrix);
1914  flutter::DlMatrix translateMatrix = flutter::DlMatrix::MakeTranslation({5, 5});
1915  // The platform view's rect for this test will be (5, 5, 10, 10).
1916  stack.PushTransform(translateMatrix);
1917  // Push a clip rect, big enough to contain the entire platform view bound.
1918  flutter::DlRect rect = flutter::DlRect::MakeXYWH(0, 0, 25, 25);
1919  stack.PushClipRect(rect);
1920  // Push a clip rrect, big enough to contain the entire platform view bound without clipping it.
1921  // Make the origin (-1, -1) so that the top left rounded corner isn't clipping the PlatformView.
1922  flutter::DlRect rect_for_rrect = flutter::DlRect::MakeXYWH(-1, -1, 25, 25);
1923  flutter::DlRoundRect rrect = flutter::DlRoundRect::MakeRectXY(rect_for_rrect, 1, 1);
1924  stack.PushClipRRect(rrect);
1925 
1926  auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
1927  screenScaleMatrix * translateMatrix, flutter::DlSize(5, 5), stack);
1928 
1929  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
1930  withParams:std::move(embeddedViewParams)];
1931  [flutterPlatformViewsController
1932  compositeView:2
1933  withParams:[flutterPlatformViewsController compositionParamsForView:2]];
1934 
1935  gMockPlatformView.backgroundColor = UIColor.redColor;
1936  XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]);
1937  ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
1938  [flutterView addSubview:childClippingView];
1939 
1940  [flutterView setNeedsLayout];
1941  [flutterView layoutIfNeeded];
1942  XCTAssertNil(childClippingView.maskView);
1943 }
1944 
1945 - (void)testClipRRectOnlyHasCornersInterceptWithPlatformViewShouldAddMaskView {
1946  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
1947 
1948  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
1949  /*platform=*/GetDefaultTaskRunner(),
1950  /*raster=*/GetDefaultTaskRunner(),
1951  /*ui=*/GetDefaultTaskRunner(),
1952  /*io=*/GetDefaultTaskRunner());
1953  FlutterPlatformViewsController* flutterPlatformViewsController =
1954  [[FlutterPlatformViewsController alloc] init];
1955  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
1956  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1957  /*delegate=*/mock_delegate,
1958  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
1959  /*platform_views_controller=*/flutterPlatformViewsController,
1960  /*task_runners=*/runners,
1961  /*worker_task_runner=*/nil,
1962  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
1963 
1966  [flutterPlatformViewsController
1967  registerViewFactory:factory
1968  withId:@"MockFlutterPlatformView"
1969  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
1970  FlutterResult result = ^(id result) {
1971  };
1972  [flutterPlatformViewsController
1974  arguments:@{
1975  @"id" : @2,
1976  @"viewType" : @"MockFlutterPlatformView"
1977  }]
1978  result:result];
1979 
1980  XCTAssertNotNil(gMockPlatformView);
1981 
1982  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 30, 30)];
1983  flutterPlatformViewsController.flutterView = flutterView;
1984  // Create embedded view params
1985  flutter::MutatorsStack stack;
1986  // Layer tree always pushes a screen scale factor to the stack.
1987  flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
1988  flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
1989  stack.PushTransform(screenScaleMatrix);
1990  flutter::DlMatrix translateMatrix = flutter::DlMatrix::MakeTranslation({5, 5});
1991  // The platform view's rect for this test will be (5, 5, 10, 10).
1992  stack.PushTransform(translateMatrix);
1993 
1994  // Push a clip rrect, the rect of the rrect is the same as the PlatformView of the corner should.
1995  // clip the PlatformView.
1996  flutter::DlRect rect_for_rrect = flutter::DlRect::MakeXYWH(0, 0, 10, 10);
1997  flutter::DlRoundRect rrect = flutter::DlRoundRect::MakeRectXY(rect_for_rrect, 1, 1);
1998  stack.PushClipRRect(rrect);
1999 
2000  auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
2001  screenScaleMatrix * translateMatrix, flutter::DlSize(5, 5), stack);
2002 
2003  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
2004  withParams:std::move(embeddedViewParams)];
2005  [flutterPlatformViewsController
2006  compositeView:2
2007  withParams:[flutterPlatformViewsController compositionParamsForView:2]];
2008 
2009  gMockPlatformView.backgroundColor = UIColor.redColor;
2010  XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]);
2011  ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
2012  [flutterView addSubview:childClippingView];
2013 
2014  [flutterView setNeedsLayout];
2015  [flutterView layoutIfNeeded];
2016 
2017  XCTAssertNotNil(childClippingView.maskView);
2018 }
2019 
2020 - (void)testClipRect {
2021  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
2022 
2023  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
2024  /*platform=*/GetDefaultTaskRunner(),
2025  /*raster=*/GetDefaultTaskRunner(),
2026  /*ui=*/GetDefaultTaskRunner(),
2027  /*io=*/GetDefaultTaskRunner());
2028  FlutterPlatformViewsController* flutterPlatformViewsController =
2029  [[FlutterPlatformViewsController alloc] init];
2030  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
2031  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2032  /*delegate=*/mock_delegate,
2033  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
2034  /*platform_views_controller=*/flutterPlatformViewsController,
2035  /*task_runners=*/runners,
2036  /*worker_task_runner=*/nil,
2037  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
2038 
2041  [flutterPlatformViewsController
2042  registerViewFactory:factory
2043  withId:@"MockFlutterPlatformView"
2044  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
2045  FlutterResult result = ^(id result) {
2046  };
2047  [flutterPlatformViewsController
2049  arguments:@{
2050  @"id" : @2,
2051  @"viewType" : @"MockFlutterPlatformView"
2052  }]
2053  result:result];
2054 
2055  XCTAssertNotNil(gMockPlatformView);
2056 
2057  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
2058  flutterPlatformViewsController.flutterView = flutterView;
2059  // Create embedded view params
2060  flutter::MutatorsStack stack;
2061  // Layer tree always pushes a screen scale factor to the stack
2062  flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
2063  flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
2064  stack.PushTransform(screenScaleMatrix);
2065  // Push a clip rect
2066  flutter::DlRect rect = flutter::DlRect::MakeXYWH(2, 2, 3, 3);
2067  stack.PushClipRect(rect);
2068 
2069  auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
2070  screenScaleMatrix, flutter::DlSize(10, 10), stack);
2071 
2072  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
2073  withParams:std::move(embeddedViewParams)];
2074  [flutterPlatformViewsController
2075  compositeView:2
2076  withParams:[flutterPlatformViewsController compositionParamsForView:2]];
2077 
2078  gMockPlatformView.backgroundColor = UIColor.redColor;
2079  XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]);
2080  ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
2081  [flutterView addSubview:childClippingView];
2082 
2083  [flutterView setNeedsLayout];
2084  [flutterView layoutIfNeeded];
2085 
2086  CGRect insideClipping = CGRectMake(2, 2, 3, 3);
2087  for (int i = 0; i < 10; i++) {
2088  for (int j = 0; j < 10; j++) {
2089  CGPoint point = CGPointMake(i, j);
2090  int alpha = [self alphaOfPoint:CGPointMake(i, j) onView:flutterView];
2091  if (CGRectContainsPoint(insideClipping, point)) {
2092  XCTAssertEqual(alpha, 255);
2093  } else {
2094  XCTAssertEqual(alpha, 0);
2095  }
2096  }
2097  }
2098 }
2099 
2100 - (void)testClipRect_multipleClips {
2101  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
2102 
2103  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
2104  /*platform=*/GetDefaultTaskRunner(),
2105  /*raster=*/GetDefaultTaskRunner(),
2106  /*ui=*/GetDefaultTaskRunner(),
2107  /*io=*/GetDefaultTaskRunner());
2108  FlutterPlatformViewsController* flutterPlatformViewsController =
2109  [[FlutterPlatformViewsController alloc] init];
2110  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
2111  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2112  /*delegate=*/mock_delegate,
2113  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
2114  /*platform_views_controller=*/flutterPlatformViewsController,
2115  /*task_runners=*/runners,
2116  /*worker_task_runner=*/nil,
2117  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
2118 
2121  [flutterPlatformViewsController
2122  registerViewFactory:factory
2123  withId:@"MockFlutterPlatformView"
2124  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
2125  FlutterResult result = ^(id result) {
2126  };
2127  [flutterPlatformViewsController
2129  arguments:@{
2130  @"id" : @2,
2131  @"viewType" : @"MockFlutterPlatformView"
2132  }]
2133  result:result];
2134 
2135  XCTAssertNotNil(gMockPlatformView);
2136 
2137  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
2138  flutterPlatformViewsController.flutterView = flutterView;
2139  // Create embedded view params
2140  flutter::MutatorsStack stack;
2141  // Layer tree always pushes a screen scale factor to the stack
2142  flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
2143  flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
2144  stack.PushTransform(screenScaleMatrix);
2145  // Push a clip rect
2146  flutter::DlRect rect1 = flutter::DlRect::MakeXYWH(2, 2, 3, 3);
2147  stack.PushClipRect(rect1);
2148  // Push another clip rect
2149  flutter::DlRect rect2 = flutter::DlRect::MakeXYWH(3, 3, 3, 3);
2150  stack.PushClipRect(rect2);
2151 
2152  auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
2153  screenScaleMatrix, flutter::DlSize(10, 10), stack);
2154 
2155  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
2156  withParams:std::move(embeddedViewParams)];
2157  [flutterPlatformViewsController
2158  compositeView:2
2159  withParams:[flutterPlatformViewsController compositionParamsForView:2]];
2160 
2161  gMockPlatformView.backgroundColor = UIColor.redColor;
2162  XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]);
2163  ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
2164  [flutterView addSubview:childClippingView];
2165 
2166  [flutterView setNeedsLayout];
2167  [flutterView layoutIfNeeded];
2168 
2169  /*
2170  clip 1 clip 2
2171  2 3 4 5 6 2 3 4 5 6
2172  2 + - - + 2
2173  3 | | 3 + - - +
2174  4 | | 4 | |
2175  5 + - - + 5 | |
2176  6 6 + - - +
2177 
2178  Result should be the intersection of 2 clips
2179  2 3 4 5 6
2180  2
2181  3 + - +
2182  4 | |
2183  5 + - +
2184  6
2185  */
2186  CGRect insideClipping = CGRectMake(3, 3, 2, 2);
2187  for (int i = 0; i < 10; i++) {
2188  for (int j = 0; j < 10; j++) {
2189  CGPoint point = CGPointMake(i, j);
2190  int alpha = [self alphaOfPoint:CGPointMake(i, j) onView:flutterView];
2191  if (CGRectContainsPoint(insideClipping, point)) {
2192  XCTAssertEqual(alpha, 255);
2193  } else {
2194  XCTAssertEqual(alpha, 0);
2195  }
2196  }
2197  }
2198 }
2199 
2200 - (void)testClipRRect {
2201  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
2202 
2203  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
2204  /*platform=*/GetDefaultTaskRunner(),
2205  /*raster=*/GetDefaultTaskRunner(),
2206  /*ui=*/GetDefaultTaskRunner(),
2207  /*io=*/GetDefaultTaskRunner());
2208  FlutterPlatformViewsController* flutterPlatformViewsController =
2209  [[FlutterPlatformViewsController alloc] init];
2210  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
2211  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2212  /*delegate=*/mock_delegate,
2213  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
2214  /*platform_views_controller=*/flutterPlatformViewsController,
2215  /*task_runners=*/runners,
2216  /*worker_task_runner=*/nil,
2217  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
2218 
2221  [flutterPlatformViewsController
2222  registerViewFactory:factory
2223  withId:@"MockFlutterPlatformView"
2224  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
2225  FlutterResult result = ^(id result) {
2226  };
2227  [flutterPlatformViewsController
2229  arguments:@{
2230  @"id" : @2,
2231  @"viewType" : @"MockFlutterPlatformView"
2232  }]
2233  result:result];
2234 
2235  XCTAssertNotNil(gMockPlatformView);
2236 
2237  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
2238  flutterPlatformViewsController.flutterView = flutterView;
2239  // Create embedded view params
2240  flutter::MutatorsStack stack;
2241  // Layer tree always pushes a screen scale factor to the stack
2242  flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
2243  flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
2244  stack.PushTransform(screenScaleMatrix);
2245  // Push a clip rrect
2246  flutter::DlRoundRect rrect =
2247  flutter::DlRoundRect::MakeRectXY(flutter::DlRect::MakeXYWH(2, 2, 6, 6), 1, 1);
2248  stack.PushClipRRect(rrect);
2249 
2250  auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
2251  screenScaleMatrix, flutter::DlSize(10, 10), stack);
2252 
2253  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
2254  withParams:std::move(embeddedViewParams)];
2255  [flutterPlatformViewsController
2256  compositeView:2
2257  withParams:[flutterPlatformViewsController compositionParamsForView:2]];
2258 
2259  gMockPlatformView.backgroundColor = UIColor.redColor;
2260  XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]);
2261  ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
2262  [flutterView addSubview:childClippingView];
2263 
2264  [flutterView setNeedsLayout];
2265  [flutterView layoutIfNeeded];
2266 
2267  /*
2268  ClippingMask outterClipping
2269  2 3 4 5 6 7 2 3 4 5 6 7
2270  2 / - - - - \ 2 + - - - - +
2271  3 | | 3 | |
2272  4 | | 4 | |
2273  5 | | 5 | |
2274  6 | | 6 | |
2275  7 \ - - - - / 7 + - - - - +
2276 
2277  innerClipping1 innerClipping2
2278  2 3 4 5 6 7 2 3 4 5 6 7
2279  2 + - - + 2
2280  3 | | 3 + - - - - +
2281  4 | | 4 | |
2282  5 | | 5 | |
2283  6 | | 6 + - - - - +
2284  7 + - - + 7
2285  */
2286  CGRect innerClipping1 = CGRectMake(3, 2, 4, 6);
2287  CGRect innerClipping2 = CGRectMake(2, 3, 6, 4);
2288  CGRect outterClipping = CGRectMake(2, 2, 6, 6);
2289  for (int i = 0; i < 10; i++) {
2290  for (int j = 0; j < 10; j++) {
2291  CGPoint point = CGPointMake(i, j);
2292  int alpha = [self alphaOfPoint:CGPointMake(i, j) onView:flutterView];
2293  if (CGRectContainsPoint(innerClipping1, point) ||
2294  CGRectContainsPoint(innerClipping2, point)) {
2295  // Pixels inside either of the 2 inner clippings should be fully opaque.
2296  XCTAssertEqual(alpha, 255);
2297  } else if (CGRectContainsPoint(outterClipping, point)) {
2298  // Corner pixels (i.e. (2, 2), (2, 7), (7, 2) and (7, 7)) should be partially transparent.
2299  XCTAssert(0 < alpha && alpha < 255);
2300  } else {
2301  // Pixels outside outterClipping should be fully transparent.
2302  XCTAssertEqual(alpha, 0);
2303  }
2304  }
2305  }
2306 }
2307 
2308 - (void)testClipRRect_multipleClips {
2309  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
2310 
2311  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
2312  /*platform=*/GetDefaultTaskRunner(),
2313  /*raster=*/GetDefaultTaskRunner(),
2314  /*ui=*/GetDefaultTaskRunner(),
2315  /*io=*/GetDefaultTaskRunner());
2316  FlutterPlatformViewsController* flutterPlatformViewsController =
2317  [[FlutterPlatformViewsController alloc] init];
2318  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
2319  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2320  /*delegate=*/mock_delegate,
2321  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
2322  /*platform_views_controller=*/flutterPlatformViewsController,
2323  /*task_runners=*/runners,
2324  /*worker_task_runner=*/nil,
2325  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
2326 
2329  [flutterPlatformViewsController
2330  registerViewFactory:factory
2331  withId:@"MockFlutterPlatformView"
2332  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
2333  FlutterResult result = ^(id result) {
2334  };
2335  [flutterPlatformViewsController
2337  arguments:@{
2338  @"id" : @2,
2339  @"viewType" : @"MockFlutterPlatformView"
2340  }]
2341  result:result];
2342 
2343  XCTAssertNotNil(gMockPlatformView);
2344 
2345  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
2346  flutterPlatformViewsController.flutterView = flutterView;
2347  // Create embedded view params
2348  flutter::MutatorsStack stack;
2349  // Layer tree always pushes a screen scale factor to the stack
2350  flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
2351  flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
2352  stack.PushTransform(screenScaleMatrix);
2353  // Push a clip rrect
2354  flutter::DlRoundRect rrect =
2355  flutter::DlRoundRect::MakeRectXY(flutter::DlRect::MakeXYWH(2, 2, 6, 6), 1, 1);
2356  stack.PushClipRRect(rrect);
2357  // Push a clip rect
2358  flutter::DlRect rect = flutter::DlRect::MakeXYWH(4, 2, 6, 6);
2359  stack.PushClipRect(rect);
2360 
2361  auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
2362  screenScaleMatrix, flutter::DlSize(10, 10), stack);
2363 
2364  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
2365  withParams:std::move(embeddedViewParams)];
2366  [flutterPlatformViewsController
2367  compositeView:2
2368  withParams:[flutterPlatformViewsController compositionParamsForView:2]];
2369 
2370  gMockPlatformView.backgroundColor = UIColor.redColor;
2371  XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]);
2372  ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
2373  [flutterView addSubview:childClippingView];
2374 
2375  [flutterView setNeedsLayout];
2376  [flutterView layoutIfNeeded];
2377 
2378  /*
2379  clip 1 clip 2
2380  2 3 4 5 6 7 8 9 2 3 4 5 6 7 8 9
2381  2 / - - - - \ 2 + - - - - +
2382  3 | | 3 | |
2383  4 | | 4 | |
2384  5 | | 5 | |
2385  6 | | 6 | |
2386  7 \ - - - - / 7 + - - - - +
2387 
2388  Result should be the intersection of 2 clips
2389  2 3 4 5 6 7 8 9
2390  2 + - - \
2391  3 | |
2392  4 | |
2393  5 | |
2394  6 | |
2395  7 + - - /
2396  */
2397  CGRect clipping = CGRectMake(4, 2, 4, 6);
2398  for (int i = 0; i < 10; i++) {
2399  for (int j = 0; j < 10; j++) {
2400  CGPoint point = CGPointMake(i, j);
2401  int alpha = [self alphaOfPoint:CGPointMake(i, j) onView:flutterView];
2402  if (i == 7 && (j == 2 || j == 7)) {
2403  // Upper and lower right corners should be partially transparent.
2404  XCTAssert(0 < alpha && alpha < 255);
2405  } else if (
2406  // left
2407  (i == 4 && j >= 2 && j <= 7) ||
2408  // right
2409  (i == 7 && j >= 2 && j <= 7) ||
2410  // top
2411  (j == 2 && i >= 4 && i <= 7) ||
2412  // bottom
2413  (j == 7 && i >= 4 && i <= 7)) {
2414  // Since we are falling back to software rendering for this case
2415  // The edge pixels can be anti-aliased, so it may not be fully opaque.
2416  XCTAssert(alpha > 127);
2417  } else if ((i == 3 && j >= 1 && j <= 8) || (i == 8 && j >= 1 && j <= 8) ||
2418  (j == 1 && i >= 3 && i <= 8) || (j == 8 && i >= 3 && i <= 8)) {
2419  // Since we are falling back to software rendering for this case
2420  // The edge pixels can be anti-aliased, so it may not be fully transparent.
2421  XCTAssert(alpha < 127);
2422  } else if (CGRectContainsPoint(clipping, point)) {
2423  // Other pixels inside clipping should be fully opaque.
2424  XCTAssertEqual(alpha, 255);
2425  } else {
2426  // Pixels outside clipping should be fully transparent.
2427  XCTAssertEqual(alpha, 0);
2428  }
2429  }
2430  }
2431 }
2432 
2433 - (void)testClipPath {
2434  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
2435 
2436  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
2437  /*platform=*/GetDefaultTaskRunner(),
2438  /*raster=*/GetDefaultTaskRunner(),
2439  /*ui=*/GetDefaultTaskRunner(),
2440  /*io=*/GetDefaultTaskRunner());
2441  FlutterPlatformViewsController* flutterPlatformViewsController =
2442  [[FlutterPlatformViewsController alloc] init];
2443  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
2444  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2445  /*delegate=*/mock_delegate,
2446  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
2447  /*platform_views_controller=*/flutterPlatformViewsController,
2448  /*task_runners=*/runners,
2449  /*worker_task_runner=*/nil,
2450  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
2451 
2454  [flutterPlatformViewsController
2455  registerViewFactory:factory
2456  withId:@"MockFlutterPlatformView"
2457  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
2458  FlutterResult result = ^(id result) {
2459  };
2460  [flutterPlatformViewsController
2462  arguments:@{
2463  @"id" : @2,
2464  @"viewType" : @"MockFlutterPlatformView"
2465  }]
2466  result:result];
2467 
2468  XCTAssertNotNil(gMockPlatformView);
2469 
2470  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
2471  flutterPlatformViewsController.flutterView = flutterView;
2472  // Create embedded view params
2473  flutter::MutatorsStack stack;
2474  // Layer tree always pushes a screen scale factor to the stack
2475  flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
2476  flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
2477  stack.PushTransform(screenScaleMatrix);
2478  // Push a clip path
2479  flutter::DlPath path =
2480  flutter::DlPath::MakeRoundRectXY(flutter::DlRect::MakeXYWH(2, 2, 6, 6), 1, 1);
2481  stack.PushClipPath(path);
2482 
2483  auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
2484  screenScaleMatrix, flutter::DlSize(10, 10), stack);
2485 
2486  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
2487  withParams:std::move(embeddedViewParams)];
2488  [flutterPlatformViewsController
2489  compositeView:2
2490  withParams:[flutterPlatformViewsController compositionParamsForView:2]];
2491 
2492  gMockPlatformView.backgroundColor = UIColor.redColor;
2493  XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]);
2494  ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
2495  [flutterView addSubview:childClippingView];
2496 
2497  [flutterView setNeedsLayout];
2498  [flutterView layoutIfNeeded];
2499 
2500  /*
2501  ClippingMask outterClipping
2502  2 3 4 5 6 7 2 3 4 5 6 7
2503  2 / - - - - \ 2 + - - - - +
2504  3 | | 3 | |
2505  4 | | 4 | |
2506  5 | | 5 | |
2507  6 | | 6 | |
2508  7 \ - - - - / 7 + - - - - +
2509 
2510  innerClipping1 innerClipping2
2511  2 3 4 5 6 7 2 3 4 5 6 7
2512  2 + - - + 2
2513  3 | | 3 + - - - - +
2514  4 | | 4 | |
2515  5 | | 5 | |
2516  6 | | 6 + - - - - +
2517  7 + - - + 7
2518  */
2519  CGRect innerClipping1 = CGRectMake(3, 2, 4, 6);
2520  CGRect innerClipping2 = CGRectMake(2, 3, 6, 4);
2521  CGRect outterClipping = CGRectMake(2, 2, 6, 6);
2522  for (int i = 0; i < 10; i++) {
2523  for (int j = 0; j < 10; j++) {
2524  CGPoint point = CGPointMake(i, j);
2525  int alpha = [self alphaOfPoint:CGPointMake(i, j) onView:flutterView];
2526  if (CGRectContainsPoint(innerClipping1, point) ||
2527  CGRectContainsPoint(innerClipping2, point)) {
2528  // Pixels inside either of the 2 inner clippings should be fully opaque.
2529  XCTAssertEqual(alpha, 255);
2530  } else if (CGRectContainsPoint(outterClipping, point)) {
2531  // Corner pixels (i.e. (2, 2), (2, 7), (7, 2) and (7, 7)) should be partially transparent.
2532  XCTAssert(0 < alpha && alpha < 255);
2533  } else {
2534  // Pixels outside outterClipping should be fully transparent.
2535  XCTAssertEqual(alpha, 0);
2536  }
2537  }
2538  }
2539 }
2540 
2541 - (void)testClipPath_multipleClips {
2542  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
2543 
2544  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
2545  /*platform=*/GetDefaultTaskRunner(),
2546  /*raster=*/GetDefaultTaskRunner(),
2547  /*ui=*/GetDefaultTaskRunner(),
2548  /*io=*/GetDefaultTaskRunner());
2549  FlutterPlatformViewsController* flutterPlatformViewsController =
2550  [[FlutterPlatformViewsController alloc] init];
2551  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
2552  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2553  /*delegate=*/mock_delegate,
2554  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
2555  /*platform_views_controller=*/flutterPlatformViewsController,
2556  /*task_runners=*/runners,
2557  /*worker_task_runner=*/nil,
2558  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
2559 
2562  [flutterPlatformViewsController
2563  registerViewFactory:factory
2564  withId:@"MockFlutterPlatformView"
2565  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
2566  FlutterResult result = ^(id result) {
2567  };
2568  [flutterPlatformViewsController
2570  arguments:@{
2571  @"id" : @2,
2572  @"viewType" : @"MockFlutterPlatformView"
2573  }]
2574  result:result];
2575 
2576  XCTAssertNotNil(gMockPlatformView);
2577 
2578  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
2579  flutterPlatformViewsController.flutterView = flutterView;
2580  // Create embedded view params
2581  flutter::MutatorsStack stack;
2582  // Layer tree always pushes a screen scale factor to the stack
2583  flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
2584  flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
2585  stack.PushTransform(screenScaleMatrix);
2586  // Push a clip path
2587  flutter::DlPath path =
2588  flutter::DlPath::MakeRoundRectXY(flutter::DlRect::MakeXYWH(2, 2, 6, 6), 1, 1);
2589  stack.PushClipPath(path);
2590  // Push a clip rect
2591  flutter::DlRect rect = flutter::DlRect::MakeXYWH(4, 2, 6, 6);
2592  stack.PushClipRect(rect);
2593 
2594  auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
2595  screenScaleMatrix, flutter::DlSize(10, 10), stack);
2596 
2597  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
2598  withParams:std::move(embeddedViewParams)];
2599  [flutterPlatformViewsController
2600  compositeView:2
2601  withParams:[flutterPlatformViewsController compositionParamsForView:2]];
2602 
2603  gMockPlatformView.backgroundColor = UIColor.redColor;
2604  XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]);
2605  ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
2606  [flutterView addSubview:childClippingView];
2607 
2608  [flutterView setNeedsLayout];
2609  [flutterView layoutIfNeeded];
2610 
2611  /*
2612  clip 1 clip 2
2613  2 3 4 5 6 7 8 9 2 3 4 5 6 7 8 9
2614  2 / - - - - \ 2 + - - - - +
2615  3 | | 3 | |
2616  4 | | 4 | |
2617  5 | | 5 | |
2618  6 | | 6 | |
2619  7 \ - - - - / 7 + - - - - +
2620 
2621  Result should be the intersection of 2 clips
2622  2 3 4 5 6 7 8 9
2623  2 + - - \
2624  3 | |
2625  4 | |
2626  5 | |
2627  6 | |
2628  7 + - - /
2629  */
2630  CGRect clipping = CGRectMake(4, 2, 4, 6);
2631  for (int i = 0; i < 10; i++) {
2632  for (int j = 0; j < 10; j++) {
2633  CGPoint point = CGPointMake(i, j);
2634  int alpha = [self alphaOfPoint:CGPointMake(i, j) onView:flutterView];
2635  if (i == 7 && (j == 2 || j == 7)) {
2636  // Upper and lower right corners should be partially transparent.
2637  XCTAssert(0 < alpha && alpha < 255);
2638  } else if (
2639  // left
2640  (i == 4 && j >= 2 && j <= 7) ||
2641  // right
2642  (i == 7 && j >= 2 && j <= 7) ||
2643  // top
2644  (j == 2 && i >= 4 && i <= 7) ||
2645  // bottom
2646  (j == 7 && i >= 4 && i <= 7)) {
2647  // Since we are falling back to software rendering for this case
2648  // The edge pixels can be anti-aliased, so it may not be fully opaque.
2649  XCTAssert(alpha > 127);
2650  } else if ((i == 3 && j >= 1 && j <= 8) || (i == 8 && j >= 1 && j <= 8) ||
2651  (j == 1 && i >= 3 && i <= 8) || (j == 8 && i >= 3 && i <= 8)) {
2652  // Since we are falling back to software rendering for this case
2653  // The edge pixels can be anti-aliased, so it may not be fully transparent.
2654  XCTAssert(alpha < 127);
2655  } else if (CGRectContainsPoint(clipping, point)) {
2656  // Other pixels inside clipping should be fully opaque.
2657  XCTAssertEqual(alpha, 255);
2658  } else {
2659  // Pixels outside clipping should be fully transparent.
2660  XCTAssertEqual(alpha, 0);
2661  }
2662  }
2663  }
2664 }
2665 
2666 - (void)testSetFlutterViewControllerAfterCreateCanStillDispatchTouchEvents {
2667  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
2668 
2669  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
2670  /*platform=*/GetDefaultTaskRunner(),
2671  /*raster=*/GetDefaultTaskRunner(),
2672  /*ui=*/GetDefaultTaskRunner(),
2673  /*io=*/GetDefaultTaskRunner());
2674  FlutterPlatformViewsController* flutterPlatformViewsController =
2675  [[FlutterPlatformViewsController alloc] init];
2676  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
2677  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2678  /*delegate=*/mock_delegate,
2679  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
2680  /*platform_views_controller=*/flutterPlatformViewsController,
2681  /*task_runners=*/runners,
2682  /*worker_task_runner=*/nil,
2683  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
2684 
2687  [flutterPlatformViewsController
2688  registerViewFactory:factory
2689  withId:@"MockFlutterPlatformView"
2690  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
2691  FlutterResult result = ^(id result) {
2692  };
2693  [flutterPlatformViewsController
2695  arguments:@{
2696  @"id" : @2,
2697  @"viewType" : @"MockFlutterPlatformView"
2698  }]
2699  result:result];
2700 
2701  XCTAssertNotNil(gMockPlatformView);
2702 
2703  // Find touch inteceptor view
2704  UIView* touchInteceptorView = gMockPlatformView;
2705  while (touchInteceptorView != nil &&
2706  ![touchInteceptorView isKindOfClass:[FlutterTouchInterceptingView class]]) {
2707  touchInteceptorView = touchInteceptorView.superview;
2708  }
2709  XCTAssertNotNil(touchInteceptorView);
2710 
2711  // Find ForwardGestureRecognizer
2712  UIGestureRecognizer* forwardGectureRecognizer = nil;
2713  for (UIGestureRecognizer* gestureRecognizer in touchInteceptorView.gestureRecognizers) {
2714  if ([gestureRecognizer isKindOfClass:[ForwardingGestureRecognizer class]]) {
2715  forwardGectureRecognizer = gestureRecognizer;
2716  break;
2717  }
2718  }
2719 
2720  // Before setting flutter view controller, events are not dispatched.
2721  NSSet* touches1 = [[NSSet alloc] init];
2722  id event1 = OCMClassMock([UIEvent class]);
2723  id flutterViewController = OCMClassMock([FlutterViewController class]);
2724  [forwardGectureRecognizer touchesBegan:touches1 withEvent:event1];
2725  OCMReject([flutterViewController touchesBegan:touches1 withEvent:event1]);
2726 
2727  // Set flutter view controller allows events to be dispatched.
2728  NSSet* touches2 = [[NSSet alloc] init];
2729  id event2 = OCMClassMock([UIEvent class]);
2730  flutterPlatformViewsController.flutterViewController = flutterViewController;
2731  [forwardGectureRecognizer touchesBegan:touches2 withEvent:event2];
2732  OCMVerify([flutterViewController touchesBegan:touches2 withEvent:event2]);
2733 }
2734 
2735 - (void)testSetFlutterViewControllerInTheMiddleOfTouchEventShouldStillAllowGesturesToBeHandled {
2736  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
2737 
2738  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
2739  /*platform=*/GetDefaultTaskRunner(),
2740  /*raster=*/GetDefaultTaskRunner(),
2741  /*ui=*/GetDefaultTaskRunner(),
2742  /*io=*/GetDefaultTaskRunner());
2743  FlutterPlatformViewsController* flutterPlatformViewsController =
2744  [[FlutterPlatformViewsController alloc] init];
2745  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
2746  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2747  /*delegate=*/mock_delegate,
2748  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
2749  /*platform_views_controller=*/flutterPlatformViewsController,
2750  /*task_runners=*/runners,
2751  /*worker_task_runner=*/nil,
2752  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
2753 
2756  [flutterPlatformViewsController
2757  registerViewFactory:factory
2758  withId:@"MockFlutterPlatformView"
2759  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
2760  FlutterResult result = ^(id result) {
2761  };
2762  [flutterPlatformViewsController
2764  arguments:@{
2765  @"id" : @2,
2766  @"viewType" : @"MockFlutterPlatformView"
2767  }]
2768  result:result];
2769 
2770  XCTAssertNotNil(gMockPlatformView);
2771 
2772  // Find touch inteceptor view
2773  UIView* touchInteceptorView = gMockPlatformView;
2774  while (touchInteceptorView != nil &&
2775  ![touchInteceptorView isKindOfClass:[FlutterTouchInterceptingView class]]) {
2776  touchInteceptorView = touchInteceptorView.superview;
2777  }
2778  XCTAssertNotNil(touchInteceptorView);
2779 
2780  // Find ForwardGestureRecognizer
2781  UIGestureRecognizer* forwardGectureRecognizer = nil;
2782  for (UIGestureRecognizer* gestureRecognizer in touchInteceptorView.gestureRecognizers) {
2783  if ([gestureRecognizer isKindOfClass:[ForwardingGestureRecognizer class]]) {
2784  forwardGectureRecognizer = gestureRecognizer;
2785  break;
2786  }
2787  }
2788  id flutterViewController = OCMClassMock([FlutterViewController class]);
2789  {
2790  // ***** Sequence 1, finishing touch event with touchEnded ***** //
2791  flutterPlatformViewsController.flutterViewController = flutterViewController;
2792 
2793  NSSet* touches1 = [[NSSet alloc] init];
2794  id event1 = OCMClassMock([UIEvent class]);
2795  [forwardGectureRecognizer touchesBegan:touches1 withEvent:event1];
2796  OCMVerify([flutterViewController touchesBegan:touches1 withEvent:event1]);
2797 
2798  flutterPlatformViewsController.flutterViewController = nil;
2799 
2800  // Allow the touch events to finish
2801  NSSet* touches2 = [[NSSet alloc] init];
2802  id event2 = OCMClassMock([UIEvent class]);
2803  [forwardGectureRecognizer touchesMoved:touches2 withEvent:event2];
2804  OCMVerify([flutterViewController touchesMoved:touches2 withEvent:event2]);
2805 
2806  NSSet* touches3 = [[NSSet alloc] init];
2807  id event3 = OCMClassMock([UIEvent class]);
2808  [forwardGectureRecognizer touchesEnded:touches3 withEvent:event3];
2809  OCMVerify([flutterViewController touchesEnded:touches3 withEvent:event3]);
2810 
2811  // Now the 2nd touch sequence should not be allowed.
2812  NSSet* touches4 = [[NSSet alloc] init];
2813  id event4 = OCMClassMock([UIEvent class]);
2814  [forwardGectureRecognizer touchesBegan:touches4 withEvent:event4];
2815  OCMReject([flutterViewController touchesBegan:touches4 withEvent:event4]);
2816 
2817  NSSet* touches5 = [[NSSet alloc] init];
2818  id event5 = OCMClassMock([UIEvent class]);
2819  [forwardGectureRecognizer touchesEnded:touches5 withEvent:event5];
2820  OCMReject([flutterViewController touchesEnded:touches5 withEvent:event5]);
2821  }
2822 
2823  {
2824  // ***** Sequence 2, finishing touch event with touchCancelled ***** //
2825  flutterPlatformViewsController.flutterViewController = flutterViewController;
2826 
2827  NSSet* touches1 = [[NSSet alloc] init];
2828  id event1 = OCMClassMock([UIEvent class]);
2829  [forwardGectureRecognizer touchesBegan:touches1 withEvent:event1];
2830  OCMVerify([flutterViewController touchesBegan:touches1 withEvent:event1]);
2831 
2832  flutterPlatformViewsController.flutterViewController = nil;
2833 
2834  // Allow the touch events to finish
2835  NSSet* touches2 = [[NSSet alloc] init];
2836  id event2 = OCMClassMock([UIEvent class]);
2837  [forwardGectureRecognizer touchesMoved:touches2 withEvent:event2];
2838  OCMVerify([flutterViewController touchesMoved:touches2 withEvent:event2]);
2839 
2840  NSSet* touches3 = [[NSSet alloc] init];
2841  id event3 = OCMClassMock([UIEvent class]);
2842  [forwardGectureRecognizer touchesCancelled:touches3 withEvent:event3];
2843  OCMVerify([flutterViewController forceTouchesCancelled:touches3]);
2844 
2845  // Now the 2nd touch sequence should not be allowed.
2846  NSSet* touches4 = [[NSSet alloc] init];
2847  id event4 = OCMClassMock([UIEvent class]);
2848  [forwardGectureRecognizer touchesBegan:touches4 withEvent:event4];
2849  OCMReject([flutterViewController touchesBegan:touches4 withEvent:event4]);
2850 
2851  NSSet* touches5 = [[NSSet alloc] init];
2852  id event5 = OCMClassMock([UIEvent class]);
2853  [forwardGectureRecognizer touchesEnded:touches5 withEvent:event5];
2854  OCMReject([flutterViewController touchesEnded:touches5 withEvent:event5]);
2855  }
2856 
2857  [flutterPlatformViewsController reset];
2858 }
2859 
2860 - (void)
2861  testSetFlutterViewControllerInTheMiddleOfTouchEventAllowsTheNewControllerToHandleSecondTouchSequence {
2862  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
2863 
2864  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
2865  /*platform=*/GetDefaultTaskRunner(),
2866  /*raster=*/GetDefaultTaskRunner(),
2867  /*ui=*/GetDefaultTaskRunner(),
2868  /*io=*/GetDefaultTaskRunner());
2869  FlutterPlatformViewsController* flutterPlatformViewsController =
2870  [[FlutterPlatformViewsController alloc] init];
2871  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
2872  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2873  /*delegate=*/mock_delegate,
2874  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
2875  /*platform_views_controller=*/flutterPlatformViewsController,
2876  /*task_runners=*/runners,
2877  /*worker_task_runner=*/nil,
2878  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
2879 
2882  [flutterPlatformViewsController
2883  registerViewFactory:factory
2884  withId:@"MockFlutterPlatformView"
2885  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
2886  FlutterResult result = ^(id result) {
2887  };
2888  [flutterPlatformViewsController
2890  arguments:@{
2891  @"id" : @2,
2892  @"viewType" : @"MockFlutterPlatformView"
2893  }]
2894  result:result];
2895 
2896  XCTAssertNotNil(gMockPlatformView);
2897 
2898  // Find touch inteceptor view
2899  UIView* touchInteceptorView = gMockPlatformView;
2900  while (touchInteceptorView != nil &&
2901  ![touchInteceptorView isKindOfClass:[FlutterTouchInterceptingView class]]) {
2902  touchInteceptorView = touchInteceptorView.superview;
2903  }
2904  XCTAssertNotNil(touchInteceptorView);
2905 
2906  // Find ForwardGestureRecognizer
2907  UIGestureRecognizer* forwardGectureRecognizer = nil;
2908  for (UIGestureRecognizer* gestureRecognizer in touchInteceptorView.gestureRecognizers) {
2909  if ([gestureRecognizer isKindOfClass:[ForwardingGestureRecognizer class]]) {
2910  forwardGectureRecognizer = gestureRecognizer;
2911  break;
2912  }
2913  }
2914  id flutterViewController = OCMClassMock([FlutterViewController class]);
2915  flutterPlatformViewsController.flutterViewController = flutterViewController;
2916 
2917  // The touches in this sequence requires 1 touch object, we always create the NSSet with one item.
2918  NSSet* touches1 = [NSSet setWithObject:@1];
2919  id event1 = OCMClassMock([UIEvent class]);
2920  [forwardGectureRecognizer touchesBegan:touches1 withEvent:event1];
2921  OCMVerify([flutterViewController touchesBegan:touches1 withEvent:event1]);
2922 
2923  FlutterViewController* flutterViewController2 = OCMClassMock([FlutterViewController class]);
2924  flutterPlatformViewsController.flutterViewController = flutterViewController2;
2925 
2926  // Touch events should still send to the old FlutterViewController if FlutterViewController
2927  // is updated in between.
2928  NSSet* touches2 = [NSSet setWithObject:@1];
2929  id event2 = OCMClassMock([UIEvent class]);
2930  [forwardGectureRecognizer touchesBegan:touches2 withEvent:event2];
2931  OCMVerify([flutterViewController touchesBegan:touches2 withEvent:event2]);
2932  OCMReject([flutterViewController2 touchesBegan:touches2 withEvent:event2]);
2933 
2934  NSSet* touches3 = [NSSet setWithObject:@1];
2935  id event3 = OCMClassMock([UIEvent class]);
2936  [forwardGectureRecognizer touchesMoved:touches3 withEvent:event3];
2937  OCMVerify([flutterViewController touchesMoved:touches3 withEvent:event3]);
2938  OCMReject([flutterViewController2 touchesMoved:touches3 withEvent:event3]);
2939 
2940  NSSet* touches4 = [NSSet setWithObject:@1];
2941  id event4 = OCMClassMock([UIEvent class]);
2942  [forwardGectureRecognizer touchesEnded:touches4 withEvent:event4];
2943  OCMVerify([flutterViewController touchesEnded:touches4 withEvent:event4]);
2944  OCMReject([flutterViewController2 touchesEnded:touches4 withEvent:event4]);
2945 
2946  NSSet* touches5 = [NSSet setWithObject:@1];
2947  id event5 = OCMClassMock([UIEvent class]);
2948  [forwardGectureRecognizer touchesEnded:touches5 withEvent:event5];
2949  OCMVerify([flutterViewController touchesEnded:touches5 withEvent:event5]);
2950  OCMReject([flutterViewController2 touchesEnded:touches5 withEvent:event5]);
2951 
2952  // Now the 2nd touch sequence should go to the new FlutterViewController
2953 
2954  NSSet* touches6 = [NSSet setWithObject:@1];
2955  id event6 = OCMClassMock([UIEvent class]);
2956  [forwardGectureRecognizer touchesBegan:touches6 withEvent:event6];
2957  OCMVerify([flutterViewController2 touchesBegan:touches6 withEvent:event6]);
2958  OCMReject([flutterViewController touchesBegan:touches6 withEvent:event6]);
2959 
2960  // Allow the touch events to finish
2961  NSSet* touches7 = [NSSet setWithObject:@1];
2962  id event7 = OCMClassMock([UIEvent class]);
2963  [forwardGectureRecognizer touchesMoved:touches7 withEvent:event7];
2964  OCMVerify([flutterViewController2 touchesMoved:touches7 withEvent:event7]);
2965  OCMReject([flutterViewController touchesMoved:touches7 withEvent:event7]);
2966 
2967  NSSet* touches8 = [NSSet setWithObject:@1];
2968  id event8 = OCMClassMock([UIEvent class]);
2969  [forwardGectureRecognizer touchesEnded:touches8 withEvent:event8];
2970  OCMVerify([flutterViewController2 touchesEnded:touches8 withEvent:event8]);
2971  OCMReject([flutterViewController touchesEnded:touches8 withEvent:event8]);
2972 
2973  [flutterPlatformViewsController reset];
2974 }
2975 
2976 - (void)testFlutterPlatformViewTouchesCancelledEventAreForcedToBeCancelled {
2977  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
2978 
2979  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
2980  /*platform=*/GetDefaultTaskRunner(),
2981  /*raster=*/GetDefaultTaskRunner(),
2982  /*ui=*/GetDefaultTaskRunner(),
2983  /*io=*/GetDefaultTaskRunner());
2984  FlutterPlatformViewsController* flutterPlatformViewsController =
2985  [[FlutterPlatformViewsController alloc] init];
2986  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
2987  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2988  /*delegate=*/mock_delegate,
2989  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
2990  /*platform_views_controller=*/flutterPlatformViewsController,
2991  /*task_runners=*/runners,
2992  /*worker_task_runner=*/nil,
2993  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
2994 
2997  [flutterPlatformViewsController
2998  registerViewFactory:factory
2999  withId:@"MockFlutterPlatformView"
3000  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
3001  FlutterResult result = ^(id result) {
3002  };
3003  [flutterPlatformViewsController
3005  arguments:@{
3006  @"id" : @2,
3007  @"viewType" : @"MockFlutterPlatformView"
3008  }]
3009  result:result];
3010 
3011  XCTAssertNotNil(gMockPlatformView);
3012 
3013  // Find touch inteceptor view
3014  UIView* touchInteceptorView = gMockPlatformView;
3015  while (touchInteceptorView != nil &&
3016  ![touchInteceptorView isKindOfClass:[FlutterTouchInterceptingView class]]) {
3017  touchInteceptorView = touchInteceptorView.superview;
3018  }
3019  XCTAssertNotNil(touchInteceptorView);
3020 
3021  // Find ForwardGestureRecognizer
3022  UIGestureRecognizer* forwardGectureRecognizer = nil;
3023  for (UIGestureRecognizer* gestureRecognizer in touchInteceptorView.gestureRecognizers) {
3024  if ([gestureRecognizer isKindOfClass:[ForwardingGestureRecognizer class]]) {
3025  forwardGectureRecognizer = gestureRecognizer;
3026  break;
3027  }
3028  }
3029  id flutterViewController = OCMClassMock([FlutterViewController class]);
3030  flutterPlatformViewsController.flutterViewController = flutterViewController;
3031 
3032  NSSet* touches1 = [NSSet setWithObject:@1];
3033  id event1 = OCMClassMock([UIEvent class]);
3034  [forwardGectureRecognizer touchesBegan:touches1 withEvent:event1];
3035 
3036  [forwardGectureRecognizer touchesCancelled:touches1 withEvent:event1];
3037  OCMVerify([flutterViewController forceTouchesCancelled:touches1]);
3038 
3039  [flutterPlatformViewsController reset];
3040 }
3041 
3042 - (void)testFlutterPlatformViewTouchesEndedOrTouchesCancelledEventDoesNotFailTheGestureRecognizer {
3043  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
3044 
3045  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
3046  /*platform=*/GetDefaultTaskRunner(),
3047  /*raster=*/GetDefaultTaskRunner(),
3048  /*ui=*/GetDefaultTaskRunner(),
3049  /*io=*/GetDefaultTaskRunner());
3050  FlutterPlatformViewsController* flutterPlatformViewsController =
3051  [[FlutterPlatformViewsController alloc] init];
3052  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
3053  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
3054  /*delegate=*/mock_delegate,
3055  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
3056  /*platform_views_controller=*/flutterPlatformViewsController,
3057  /*task_runners=*/runners,
3058  /*worker_task_runner=*/nil,
3059  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
3060 
3063  [flutterPlatformViewsController
3064  registerViewFactory:factory
3065  withId:@"MockFlutterPlatformView"
3066  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
3067  FlutterResult result = ^(id result) {
3068  };
3069  [flutterPlatformViewsController
3071  arguments:@{
3072  @"id" : @2,
3073  @"viewType" : @"MockFlutterPlatformView"
3074  }]
3075  result:result];
3076 
3077  XCTAssertNotNil(gMockPlatformView);
3078 
3079  // Find touch inteceptor view
3080  UIView* touchInteceptorView = gMockPlatformView;
3081  while (touchInteceptorView != nil &&
3082  ![touchInteceptorView isKindOfClass:[FlutterTouchInterceptingView class]]) {
3083  touchInteceptorView = touchInteceptorView.superview;
3084  }
3085  XCTAssertNotNil(touchInteceptorView);
3086 
3087  // Find ForwardGestureRecognizer
3088  __block UIGestureRecognizer* forwardGestureRecognizer = nil;
3089  for (UIGestureRecognizer* gestureRecognizer in touchInteceptorView.gestureRecognizers) {
3090  if ([gestureRecognizer isKindOfClass:[ForwardingGestureRecognizer class]]) {
3091  forwardGestureRecognizer = gestureRecognizer;
3092  break;
3093  }
3094  }
3095  id flutterViewController = OCMClassMock([FlutterViewController class]);
3096  flutterPlatformViewsController.flutterViewController = flutterViewController;
3097 
3098  NSSet* touches1 = [NSSet setWithObject:@1];
3099  id event1 = OCMClassMock([UIEvent class]);
3100  XCTAssert(forwardGestureRecognizer.state == UIGestureRecognizerStatePossible,
3101  @"Forwarding gesture recognizer must start with possible state.");
3102  [forwardGestureRecognizer touchesBegan:touches1 withEvent:event1];
3103  [forwardGestureRecognizer touchesEnded:touches1 withEvent:event1];
3104  XCTAssert(forwardGestureRecognizer.state == UIGestureRecognizerStateFailed,
3105  @"Forwarding gesture recognizer must end with failed state.");
3106 
3107  XCTestExpectation* touchEndedExpectation =
3108  [self expectationWithDescription:@"Wait for gesture recognizer's state change."];
3109  dispatch_async(dispatch_get_main_queue(), ^{
3110  // Re-query forward gesture recognizer since it's recreated.
3111  for (UIGestureRecognizer* gestureRecognizer in touchInteceptorView.gestureRecognizers) {
3112  if ([gestureRecognizer isKindOfClass:[ForwardingGestureRecognizer class]]) {
3113  forwardGestureRecognizer = gestureRecognizer;
3114  break;
3115  }
3116  }
3117  XCTAssert(forwardGestureRecognizer.state == UIGestureRecognizerStatePossible,
3118  @"Forwarding gesture recognizer must be reset to possible state.");
3119  [touchEndedExpectation fulfill];
3120  });
3121  [self waitForExpectationsWithTimeout:30 handler:nil];
3122 
3123  XCTAssert(forwardGestureRecognizer.state == UIGestureRecognizerStatePossible,
3124  @"Forwarding gesture recognizer must start with possible state.");
3125  [forwardGestureRecognizer touchesBegan:touches1 withEvent:event1];
3126  [forwardGestureRecognizer touchesCancelled:touches1 withEvent:event1];
3127  XCTAssert(forwardGestureRecognizer.state == UIGestureRecognizerStateFailed,
3128  @"Forwarding gesture recognizer must end with failed state.");
3129  XCTestExpectation* touchCancelledExpectation =
3130  [self expectationWithDescription:@"Wait for gesture recognizer's state change."];
3131  dispatch_async(dispatch_get_main_queue(), ^{
3132  // Re-query forward gesture recognizer since it's recreated.
3133  for (UIGestureRecognizer* gestureRecognizer in touchInteceptorView.gestureRecognizers) {
3134  if ([gestureRecognizer isKindOfClass:[ForwardingGestureRecognizer class]]) {
3135  forwardGestureRecognizer = gestureRecognizer;
3136  break;
3137  }
3138  }
3139  XCTAssert(forwardGestureRecognizer.state == UIGestureRecognizerStatePossible,
3140  @"Forwarding gesture recognizer must be reset to possible state.");
3141  [touchCancelledExpectation fulfill];
3142  });
3143  [self waitForExpectationsWithTimeout:30 handler:nil];
3144 
3145  [flutterPlatformViewsController reset];
3146 }
3147 
3148 - (void)
3149  testFlutterPlatformViewBlockGestureUnderEagerPolicyShouldRemoveAndAddBackDelayingRecognizerForWebView {
3150  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
3151 
3152  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
3153  /*platform=*/GetDefaultTaskRunner(),
3154  /*raster=*/GetDefaultTaskRunner(),
3155  /*ui=*/GetDefaultTaskRunner(),
3156  /*io=*/GetDefaultTaskRunner());
3157  FlutterPlatformViewsController* flutterPlatformViewsController =
3158  [[FlutterPlatformViewsController alloc] init];
3159  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
3160  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
3161  /*delegate=*/mock_delegate,
3162  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
3163  /*platform_views_controller=*/flutterPlatformViewsController,
3164  /*task_runners=*/runners,
3165  /*worker_task_runner=*/nil,
3166  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
3167 
3170  [flutterPlatformViewsController
3171  registerViewFactory:factory
3172  withId:@"MockWebView"
3173  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
3174  FlutterResult result = ^(id result) {
3175  };
3176  [flutterPlatformViewsController
3178  methodCallWithMethodName:@"create"
3179  arguments:@{@"id" : @2, @"viewType" : @"MockWebView"}]
3180  result:result];
3181 
3182  XCTAssertNotNil(gMockPlatformView);
3183 
3184  // Find touch inteceptor view
3185  UIView* touchInteceptorView = gMockPlatformView;
3186  while (touchInteceptorView != nil &&
3187  ![touchInteceptorView isKindOfClass:[FlutterTouchInterceptingView class]]) {
3188  touchInteceptorView = touchInteceptorView.superview;
3189  }
3190  XCTAssertNotNil(touchInteceptorView);
3191 
3192  XCTAssert(touchInteceptorView.gestureRecognizers.count == 2);
3193  UIGestureRecognizer* delayingRecognizer = touchInteceptorView.gestureRecognizers[0];
3194  UIGestureRecognizer* forwardingRecognizer = touchInteceptorView.gestureRecognizers[1];
3195 
3196  XCTAssert([delayingRecognizer isKindOfClass:[FlutterDelayingGestureRecognizer class]]);
3197  XCTAssert([forwardingRecognizer isKindOfClass:[ForwardingGestureRecognizer class]]);
3198 
3200 
3201  if (@available(iOS 18.2, *)) {
3202  // Since we remove and add back delayingRecognizer, it would be reordered to the last.
3203  XCTAssertEqual(touchInteceptorView.gestureRecognizers[0], forwardingRecognizer);
3204  XCTAssertEqual(touchInteceptorView.gestureRecognizers[1], delayingRecognizer);
3205  } else {
3206  XCTAssertEqual(touchInteceptorView.gestureRecognizers[0], delayingRecognizer);
3207  XCTAssertEqual(touchInteceptorView.gestureRecognizers[1], forwardingRecognizer);
3208  }
3209 }
3210 
3211 - (void)
3212  testFlutterPlatformViewBlockGestureUnderEagerPolicyShouldRemoveAndAddBackDelayingRecognizerForWrapperWebView {
3213  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
3214 
3215  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
3216  /*platform=*/GetDefaultTaskRunner(),
3217  /*raster=*/GetDefaultTaskRunner(),
3218  /*ui=*/GetDefaultTaskRunner(),
3219  /*io=*/GetDefaultTaskRunner());
3220  FlutterPlatformViewsController* flutterPlatformViewsController =
3221  [[FlutterPlatformViewsController alloc] init];
3222  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
3223  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
3224  /*delegate=*/mock_delegate,
3225  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
3226  /*platform_views_controller=*/flutterPlatformViewsController,
3227  /*task_runners=*/runners,
3228  /*worker_task_runner=*/nil,
3229  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
3230 
3233  [flutterPlatformViewsController
3234  registerViewFactory:factory
3235  withId:@"MockWrapperWebView"
3236  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
3237  FlutterResult result = ^(id result) {
3238  };
3239  [flutterPlatformViewsController
3241  methodCallWithMethodName:@"create"
3242  arguments:@{@"id" : @2, @"viewType" : @"MockWrapperWebView"}]
3243  result:result];
3244 
3245  XCTAssertNotNil(gMockPlatformView);
3246 
3247  // Find touch inteceptor view
3248  UIView* touchInteceptorView = gMockPlatformView;
3249  while (touchInteceptorView != nil &&
3250  ![touchInteceptorView isKindOfClass:[FlutterTouchInterceptingView class]]) {
3251  touchInteceptorView = touchInteceptorView.superview;
3252  }
3253  XCTAssertNotNil(touchInteceptorView);
3254 
3255  XCTAssert(touchInteceptorView.gestureRecognizers.count == 2);
3256  UIGestureRecognizer* delayingRecognizer = touchInteceptorView.gestureRecognizers[0];
3257  UIGestureRecognizer* forwardingRecognizer = touchInteceptorView.gestureRecognizers[1];
3258 
3259  XCTAssert([delayingRecognizer isKindOfClass:[FlutterDelayingGestureRecognizer class]]);
3260  XCTAssert([forwardingRecognizer isKindOfClass:[ForwardingGestureRecognizer class]]);
3261 
3263 
3264  if (@available(iOS 18.2, *)) {
3265  // Since we remove and add back delayingRecognizer, it would be reordered to the last.
3266  XCTAssertEqual(touchInteceptorView.gestureRecognizers[0], forwardingRecognizer);
3267  XCTAssertEqual(touchInteceptorView.gestureRecognizers[1], delayingRecognizer);
3268  } else {
3269  XCTAssertEqual(touchInteceptorView.gestureRecognizers[0], delayingRecognizer);
3270  XCTAssertEqual(touchInteceptorView.gestureRecognizers[1], forwardingRecognizer);
3271  }
3272 }
3273 
3274 - (void)
3275  testFlutterPlatformViewBlockGestureUnderEagerPolicyShouldNotRemoveAndAddBackDelayingRecognizerForNestedWrapperWebView {
3276  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
3277 
3278  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
3279  /*platform=*/GetDefaultTaskRunner(),
3280  /*raster=*/GetDefaultTaskRunner(),
3281  /*ui=*/GetDefaultTaskRunner(),
3282  /*io=*/GetDefaultTaskRunner());
3283  FlutterPlatformViewsController* flutterPlatformViewsController =
3284  [[FlutterPlatformViewsController alloc] init];
3285  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
3286  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
3287  /*delegate=*/mock_delegate,
3288  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
3289  /*platform_views_controller=*/flutterPlatformViewsController,
3290  /*task_runners=*/runners,
3291  /*worker_task_runner=*/nil,
3292  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
3293 
3296  [flutterPlatformViewsController
3297  registerViewFactory:factory
3298  withId:@"MockNestedWrapperWebView"
3299  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
3300  FlutterResult result = ^(id result) {
3301  };
3302  [flutterPlatformViewsController
3304  arguments:@{
3305  @"id" : @2,
3306  @"viewType" : @"MockNestedWrapperWebView"
3307  }]
3308  result:result];
3309 
3310  XCTAssertNotNil(gMockPlatformView);
3311 
3312  // Find touch inteceptor view
3313  UIView* touchInteceptorView = gMockPlatformView;
3314  while (touchInteceptorView != nil &&
3315  ![touchInteceptorView isKindOfClass:[FlutterTouchInterceptingView class]]) {
3316  touchInteceptorView = touchInteceptorView.superview;
3317  }
3318  XCTAssertNotNil(touchInteceptorView);
3319 
3320  XCTAssert(touchInteceptorView.gestureRecognizers.count == 2);
3321  UIGestureRecognizer* delayingRecognizer = touchInteceptorView.gestureRecognizers[0];
3322  UIGestureRecognizer* forwardingRecognizer = touchInteceptorView.gestureRecognizers[1];
3323 
3324  XCTAssert([delayingRecognizer isKindOfClass:[FlutterDelayingGestureRecognizer class]]);
3325  XCTAssert([forwardingRecognizer isKindOfClass:[ForwardingGestureRecognizer class]]);
3326 
3328 
3329  XCTAssertEqual(touchInteceptorView.gestureRecognizers[0], delayingRecognizer);
3330  XCTAssertEqual(touchInteceptorView.gestureRecognizers[1], forwardingRecognizer);
3331 }
3332 
3333 - (void)
3334  testFlutterPlatformViewBlockGestureUnderEagerPolicyShouldNotRemoveAndAddBackDelayingRecognizerForNonWebView {
3335  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
3336 
3337  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
3338  /*platform=*/GetDefaultTaskRunner(),
3339  /*raster=*/GetDefaultTaskRunner(),
3340  /*ui=*/GetDefaultTaskRunner(),
3341  /*io=*/GetDefaultTaskRunner());
3342  FlutterPlatformViewsController* flutterPlatformViewsController =
3343  [[FlutterPlatformViewsController alloc] init];
3344  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
3345  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
3346  /*delegate=*/mock_delegate,
3347  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
3348  /*platform_views_controller=*/flutterPlatformViewsController,
3349  /*task_runners=*/runners,
3350  /*worker_task_runner=*/nil,
3351  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
3352 
3355  [flutterPlatformViewsController
3356  registerViewFactory:factory
3357  withId:@"MockFlutterPlatformView"
3358  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
3359  FlutterResult result = ^(id result) {
3360  };
3361  [flutterPlatformViewsController
3363  arguments:@{
3364  @"id" : @2,
3365  @"viewType" : @"MockFlutterPlatformView"
3366  }]
3367  result:result];
3368 
3369  XCTAssertNotNil(gMockPlatformView);
3370 
3371  // Find touch inteceptor view
3372  UIView* touchInteceptorView = gMockPlatformView;
3373  while (touchInteceptorView != nil &&
3374  ![touchInteceptorView isKindOfClass:[FlutterTouchInterceptingView class]]) {
3375  touchInteceptorView = touchInteceptorView.superview;
3376  }
3377  XCTAssertNotNil(touchInteceptorView);
3378 
3379  XCTAssert(touchInteceptorView.gestureRecognizers.count == 2);
3380  UIGestureRecognizer* delayingRecognizer = touchInteceptorView.gestureRecognizers[0];
3381  UIGestureRecognizer* forwardingRecognizer = touchInteceptorView.gestureRecognizers[1];
3382 
3383  XCTAssert([delayingRecognizer isKindOfClass:[FlutterDelayingGestureRecognizer class]]);
3384  XCTAssert([forwardingRecognizer isKindOfClass:[ForwardingGestureRecognizer class]]);
3385 
3387 
3388  XCTAssertEqual(touchInteceptorView.gestureRecognizers[0], delayingRecognizer);
3389  XCTAssertEqual(touchInteceptorView.gestureRecognizers[1], forwardingRecognizer);
3390 }
3391 
3392 - (void)testFlutterPlatformViewControllerSubmitFrameWithoutFlutterViewNotCrashing {
3393  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
3394 
3395  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
3396  /*platform=*/GetDefaultTaskRunner(),
3397  /*raster=*/GetDefaultTaskRunner(),
3398  /*ui=*/GetDefaultTaskRunner(),
3399  /*io=*/GetDefaultTaskRunner());
3400  FlutterPlatformViewsController* flutterPlatformViewsController =
3401  [[FlutterPlatformViewsController alloc] init];
3402  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
3403  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
3404  /*delegate=*/mock_delegate,
3405  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
3406  /*platform_views_controller=*/flutterPlatformViewsController,
3407  /*task_runners=*/runners,
3408  /*worker_task_runner=*/nil,
3409  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
3410 
3413  [flutterPlatformViewsController
3414  registerViewFactory:factory
3415  withId:@"MockFlutterPlatformView"
3416  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
3417  FlutterResult result = ^(id result) {
3418  };
3419  [flutterPlatformViewsController
3421  arguments:@{
3422  @"id" : @2,
3423  @"viewType" : @"MockFlutterPlatformView"
3424  }]
3425  result:result];
3426 
3427  XCTAssertNotNil(gMockPlatformView);
3428 
3429  // Create embedded view params
3430  flutter::MutatorsStack stack;
3431  flutter::DlMatrix finalMatrix;
3432 
3433  auto embeddedViewParams_1 =
3434  std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, flutter::DlSize(300, 300), stack);
3435 
3436  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
3437  withParams:std::move(embeddedViewParams_1)];
3438  [flutterPlatformViewsController
3439  compositeView:2
3440  withParams:[flutterPlatformViewsController compositionParamsForView:2]];
3441 
3442  flutter::SurfaceFrame::FramebufferInfo framebuffer_info;
3443  auto mock_surface = std::make_unique<flutter::SurfaceFrame>(
3444  nullptr, framebuffer_info,
3445  [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return false; },
3446  [](const flutter::SurfaceFrame& surface_frame) { return true; },
3447  /*frame_size=*/flutter::DlISize(800, 600));
3448  XCTAssertFalse([flutterPlatformViewsController
3449  submitFrame:std::move(mock_surface)
3450  withIosContext:std::make_shared<flutter::IOSContextNoop>()]);
3451 
3452  auto embeddedViewParams_2 =
3453  std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, flutter::DlSize(300, 300), stack);
3454  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
3455  withParams:std::move(embeddedViewParams_2)];
3456  [flutterPlatformViewsController
3457  compositeView:2
3458  withParams:[flutterPlatformViewsController compositionParamsForView:2]];
3459 
3460  auto mock_surface_submit_true = std::make_unique<flutter::SurfaceFrame>(
3461  nullptr, framebuffer_info,
3462  [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; },
3463  [](const flutter::SurfaceFrame& surface_frame) { return true; },
3464  /*frame_size=*/flutter::DlISize(800, 600));
3465  XCTAssertTrue([flutterPlatformViewsController
3466  submitFrame:std::move(mock_surface_submit_true)
3467  withIosContext:std::make_shared<flutter::IOSContextNoop>()]);
3468 }
3469 
3470 - (void)
3471  testFlutterPlatformViewControllerResetDeallocsPlatformViewWhenRootViewsNotBindedToFlutterView {
3472  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
3473 
3474  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
3475  /*platform=*/GetDefaultTaskRunner(),
3476  /*raster=*/GetDefaultTaskRunner(),
3477  /*ui=*/GetDefaultTaskRunner(),
3478  /*io=*/GetDefaultTaskRunner());
3479  FlutterPlatformViewsController* flutterPlatformViewsController =
3480  [[FlutterPlatformViewsController alloc] init];
3481  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
3482  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
3483  /*delegate=*/mock_delegate,
3484  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
3485  /*platform_views_controller=*/flutterPlatformViewsController,
3486  /*task_runners=*/runners,
3487  /*worker_task_runner=*/nil,
3488  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
3489 
3490  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
3491  flutterPlatformViewsController.flutterView = flutterView;
3492 
3495  [flutterPlatformViewsController
3496  registerViewFactory:factory
3497  withId:@"MockFlutterPlatformView"
3498  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
3499  FlutterResult result = ^(id result) {
3500  };
3501  // autorelease pool to trigger an autorelease for all the root_views_ and touch_interceptors_.
3502  @autoreleasepool {
3503  [flutterPlatformViewsController
3505  arguments:@{
3506  @"id" : @2,
3507  @"viewType" : @"MockFlutterPlatformView"
3508  }]
3509  result:result];
3510 
3511  flutter::MutatorsStack stack;
3512  flutter::DlMatrix finalMatrix;
3513  auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
3514  finalMatrix, flutter::DlSize(300, 300), stack);
3515  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
3516  withParams:std::move(embeddedViewParams)];
3517 
3518  // Not calling |[flutterPlatformViewsController submitFrame:withIosContext:]| so that
3519  // the platform views are not added to flutter_view_.
3520 
3521  XCTAssertNotNil(gMockPlatformView);
3522  [flutterPlatformViewsController reset];
3523  }
3524  XCTAssertNil(gMockPlatformView);
3525 }
3526 
3527 - (void)testFlutterPlatformViewControllerBeginFrameShouldResetCompisitionOrder {
3528  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
3529 
3530  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
3531  /*platform=*/GetDefaultTaskRunner(),
3532  /*raster=*/GetDefaultTaskRunner(),
3533  /*ui=*/GetDefaultTaskRunner(),
3534  /*io=*/GetDefaultTaskRunner());
3535  FlutterPlatformViewsController* flutterPlatformViewsController =
3536  [[FlutterPlatformViewsController alloc] init];
3537  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
3538  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
3539  /*delegate=*/mock_delegate,
3540  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
3541  /*platform_views_controller=*/flutterPlatformViewsController,
3542  /*task_runners=*/runners,
3543  /*worker_task_runner=*/nil,
3544  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
3545 
3546  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
3547  flutterPlatformViewsController.flutterView = flutterView;
3548 
3551  [flutterPlatformViewsController
3552  registerViewFactory:factory
3553  withId:@"MockFlutterPlatformView"
3554  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
3555  FlutterResult result = ^(id result) {
3556  };
3557 
3558  [flutterPlatformViewsController
3560  arguments:@{
3561  @"id" : @0,
3562  @"viewType" : @"MockFlutterPlatformView"
3563  }]
3564  result:result];
3565 
3566  // First frame, |embeddedViewCount| is not empty after composite.
3567  [flutterPlatformViewsController beginFrameWithSize:flutter::DlISize(300, 300)];
3568  flutter::MutatorsStack stack;
3569  flutter::DlMatrix finalMatrix;
3570  auto embeddedViewParams1 =
3571  std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, flutter::DlSize(300, 300), stack);
3572  [flutterPlatformViewsController prerollCompositeEmbeddedView:0
3573  withParams:std::move(embeddedViewParams1)];
3574  [flutterPlatformViewsController
3575  compositeView:0
3576  withParams:[flutterPlatformViewsController compositionParamsForView:0]];
3577 
3578  XCTAssertEqual(flutterPlatformViewsController.embeddedViewCount, 1UL);
3579 
3580  // Second frame, |embeddedViewCount| should be empty at the start
3581  [flutterPlatformViewsController beginFrameWithSize:flutter::DlISize(300, 300)];
3582  XCTAssertEqual(flutterPlatformViewsController.embeddedViewCount, 0UL);
3583 
3584  auto embeddedViewParams2 =
3585  std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, flutter::DlSize(300, 300), stack);
3586  [flutterPlatformViewsController prerollCompositeEmbeddedView:0
3587  withParams:std::move(embeddedViewParams2)];
3588  [flutterPlatformViewsController
3589  compositeView:0
3590  withParams:[flutterPlatformViewsController compositionParamsForView:0]];
3591 
3592  XCTAssertEqual(flutterPlatformViewsController.embeddedViewCount, 1UL);
3593 }
3594 
3595 - (void)
3596  testFlutterPlatformViewControllerSubmitFrameShouldOrderSubviewsCorrectlyWithDifferentViewHierarchy {
3597  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
3598 
3599  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
3600  /*platform=*/GetDefaultTaskRunner(),
3601  /*raster=*/GetDefaultTaskRunner(),
3602  /*ui=*/GetDefaultTaskRunner(),
3603  /*io=*/GetDefaultTaskRunner());
3604  FlutterPlatformViewsController* flutterPlatformViewsController =
3605  [[FlutterPlatformViewsController alloc] init];
3606  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
3607  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
3608  /*delegate=*/mock_delegate,
3609  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
3610  /*platform_views_controller=*/flutterPlatformViewsController,
3611  /*task_runners=*/runners,
3612  /*worker_task_runner=*/nil,
3613  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
3614 
3615  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
3616  flutterPlatformViewsController.flutterView = flutterView;
3617 
3620  [flutterPlatformViewsController
3621  registerViewFactory:factory
3622  withId:@"MockFlutterPlatformView"
3623  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
3624  FlutterResult result = ^(id result) {
3625  };
3626  [flutterPlatformViewsController
3628  arguments:@{
3629  @"id" : @0,
3630  @"viewType" : @"MockFlutterPlatformView"
3631  }]
3632  result:result];
3633  UIView* view1 = gMockPlatformView;
3634 
3635  // This overwrites `gMockPlatformView` to another view.
3636  [flutterPlatformViewsController
3638  arguments:@{
3639  @"id" : @1,
3640  @"viewType" : @"MockFlutterPlatformView"
3641  }]
3642  result:result];
3643  UIView* view2 = gMockPlatformView;
3644 
3645  [flutterPlatformViewsController beginFrameWithSize:flutter::DlISize(300, 300)];
3646  flutter::MutatorsStack stack;
3647  flutter::DlMatrix finalMatrix;
3648  auto embeddedViewParams1 =
3649  std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, flutter::DlSize(300, 300), stack);
3650  [flutterPlatformViewsController prerollCompositeEmbeddedView:0
3651  withParams:std::move(embeddedViewParams1)];
3652 
3653  auto embeddedViewParams2 =
3654  std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, flutter::DlSize(500, 500), stack);
3655  [flutterPlatformViewsController prerollCompositeEmbeddedView:1
3656  withParams:std::move(embeddedViewParams2)];
3657 
3658  flutter::SurfaceFrame::FramebufferInfo framebuffer_info;
3659  auto mock_surface = std::make_unique<flutter::SurfaceFrame>(
3660  nullptr, framebuffer_info,
3661  [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; },
3662  [](const flutter::SurfaceFrame& surface_frame) { return true; },
3663  /*frame_size=*/flutter::DlISize(800, 600), nullptr, /*display_list_fallback=*/true);
3664  XCTAssertTrue([flutterPlatformViewsController
3665  submitFrame:std::move(mock_surface)
3666  withIosContext:std::make_shared<flutter::IOSContextNoop>()]);
3667 
3668  // platform view is wrapped by touch interceptor, which itself is wrapped by clipping view.
3669  UIView* clippingView1 = view1.superview.superview;
3670  UIView* clippingView2 = view2.superview.superview;
3671  XCTAssertTrue([flutterView.subviews indexOfObject:clippingView1] <
3672  [flutterView.subviews indexOfObject:clippingView2],
3673  @"The first clipping view should be added before the second clipping view.");
3674 
3675  // Need to recreate these params since they are `std::move`ed.
3676  [flutterPlatformViewsController beginFrameWithSize:flutter::DlISize(300, 300)];
3677  // Process the second frame in the opposite order.
3678  embeddedViewParams2 =
3679  std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, flutter::DlSize(500, 500), stack);
3680  [flutterPlatformViewsController prerollCompositeEmbeddedView:1
3681  withParams:std::move(embeddedViewParams2)];
3682 
3683  embeddedViewParams1 =
3684  std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, flutter::DlSize(300, 300), stack);
3685  [flutterPlatformViewsController prerollCompositeEmbeddedView:0
3686  withParams:std::move(embeddedViewParams1)];
3687 
3688  mock_surface = std::make_unique<flutter::SurfaceFrame>(
3689  nullptr, framebuffer_info,
3690  [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; },
3691  [](const flutter::SurfaceFrame& surface_frame) { return true; },
3692  /*frame_size=*/flutter::DlISize(800, 600), nullptr, /*display_list_fallback=*/true);
3693  XCTAssertTrue([flutterPlatformViewsController
3694  submitFrame:std::move(mock_surface)
3695  withIosContext:std::make_shared<flutter::IOSContextNoop>()]);
3696 
3697  XCTAssertTrue([flutterView.subviews indexOfObject:clippingView1] >
3698  [flutterView.subviews indexOfObject:clippingView2],
3699  @"The first clipping view should be added after the second clipping view.");
3700 }
3701 
3702 - (void)
3703  testFlutterPlatformViewControllerSubmitFrameShouldOrderSubviewsCorrectlyWithSameViewHierarchy {
3704  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
3705 
3706  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
3707  /*platform=*/GetDefaultTaskRunner(),
3708  /*raster=*/GetDefaultTaskRunner(),
3709  /*ui=*/GetDefaultTaskRunner(),
3710  /*io=*/GetDefaultTaskRunner());
3711  FlutterPlatformViewsController* flutterPlatformViewsController =
3712  [[FlutterPlatformViewsController alloc] init];
3713  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
3714  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
3715  /*delegate=*/mock_delegate,
3716  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
3717  /*platform_views_controller=*/flutterPlatformViewsController,
3718  /*task_runners=*/runners,
3719  /*worker_task_runner=*/nil,
3720  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
3721 
3722  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
3723  flutterPlatformViewsController.flutterView = flutterView;
3724 
3727  [flutterPlatformViewsController
3728  registerViewFactory:factory
3729  withId:@"MockFlutterPlatformView"
3730  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
3731  FlutterResult result = ^(id result) {
3732  };
3733  [flutterPlatformViewsController
3735  arguments:@{
3736  @"id" : @0,
3737  @"viewType" : @"MockFlutterPlatformView"
3738  }]
3739  result:result];
3740  UIView* view1 = gMockPlatformView;
3741 
3742  // This overwrites `gMockPlatformView` to another view.
3743  [flutterPlatformViewsController
3745  arguments:@{
3746  @"id" : @1,
3747  @"viewType" : @"MockFlutterPlatformView"
3748  }]
3749  result:result];
3750  UIView* view2 = gMockPlatformView;
3751 
3752  [flutterPlatformViewsController beginFrameWithSize:flutter::DlISize(300, 300)];
3753  flutter::MutatorsStack stack;
3754  flutter::DlMatrix finalMatrix;
3755  auto embeddedViewParams1 =
3756  std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, flutter::DlSize(300, 300), stack);
3757  [flutterPlatformViewsController prerollCompositeEmbeddedView:0
3758  withParams:std::move(embeddedViewParams1)];
3759 
3760  auto embeddedViewParams2 =
3761  std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, flutter::DlSize(500, 500), stack);
3762  [flutterPlatformViewsController prerollCompositeEmbeddedView:1
3763  withParams:std::move(embeddedViewParams2)];
3764 
3765  flutter::SurfaceFrame::FramebufferInfo framebuffer_info;
3766  auto mock_surface = std::make_unique<flutter::SurfaceFrame>(
3767  nullptr, framebuffer_info,
3768  [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; },
3769  [](const flutter::SurfaceFrame& surface_frame) { return true; },
3770  /*frame_size=*/flutter::DlISize(800, 600), nullptr, /*display_list_fallback=*/true);
3771  XCTAssertTrue([flutterPlatformViewsController
3772  submitFrame:std::move(mock_surface)
3773  withIosContext:std::make_shared<flutter::IOSContextNoop>()]);
3774 
3775  // platform view is wrapped by touch interceptor, which itself is wrapped by clipping view.
3776  UIView* clippingView1 = view1.superview.superview;
3777  UIView* clippingView2 = view2.superview.superview;
3778  XCTAssertTrue([flutterView.subviews indexOfObject:clippingView1] <
3779  [flutterView.subviews indexOfObject:clippingView2],
3780  @"The first clipping view should be added before the second clipping view.");
3781 
3782  // Need to recreate these params since they are `std::move`ed.
3783  [flutterPlatformViewsController beginFrameWithSize:flutter::DlISize(300, 300)];
3784  // Process the second frame in the same order.
3785  embeddedViewParams1 =
3786  std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, flutter::DlSize(300, 300), stack);
3787  [flutterPlatformViewsController prerollCompositeEmbeddedView:0
3788  withParams:std::move(embeddedViewParams1)];
3789 
3790  embeddedViewParams2 =
3791  std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, flutter::DlSize(500, 500), stack);
3792  [flutterPlatformViewsController prerollCompositeEmbeddedView:1
3793  withParams:std::move(embeddedViewParams2)];
3794 
3795  mock_surface = std::make_unique<flutter::SurfaceFrame>(
3796  nullptr, framebuffer_info,
3797  [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; },
3798  [](const flutter::SurfaceFrame& surface_frame) { return true; },
3799  /*frame_size=*/flutter::DlISize(800, 600), nullptr, /*display_list_fallback=*/true);
3800  XCTAssertTrue([flutterPlatformViewsController
3801  submitFrame:std::move(mock_surface)
3802  withIosContext:std::make_shared<flutter::IOSContextNoop>()]);
3803 
3804  XCTAssertTrue([flutterView.subviews indexOfObject:clippingView1] <
3805  [flutterView.subviews indexOfObject:clippingView2],
3806  @"The first clipping view should be added before the second clipping view.");
3807 }
3808 
3809 - (int)alphaOfPoint:(CGPoint)point onView:(UIView*)view {
3810  unsigned char pixel[4] = {0};
3811 
3812  CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
3813 
3814  // Draw the pixel on `point` in the context.
3815  CGContextRef context =
3816  CGBitmapContextCreate(pixel, 1, 1, 8, 4, colorSpace,
3817  static_cast<uint32_t>(kCGBitmapAlphaInfoMask) &
3818  static_cast<uint32_t>(kCGImageAlphaPremultipliedLast));
3819  CGContextTranslateCTM(context, -point.x, -point.y);
3820  [view.layer renderInContext:context];
3821 
3822  CGContextRelease(context);
3823  CGColorSpaceRelease(colorSpace);
3824  // Get the alpha from the pixel that we just rendered.
3825  return pixel[3];
3826 }
3827 
3828 - (void)testHasFirstResponderInViewHierarchySubtree_viewItselfBecomesFirstResponder {
3829  // For view to become the first responder, it must be a descendant of a UIWindow
3830  UIWindow* window = [[UIWindow alloc] init];
3831  UITextField* textField = [[UITextField alloc] init];
3832  [window addSubview:textField];
3833 
3834  [textField becomeFirstResponder];
3835  XCTAssertTrue(textField.isFirstResponder);
3836  XCTAssertTrue(textField.flt_hasFirstResponderInViewHierarchySubtree);
3837  [textField resignFirstResponder];
3838  XCTAssertFalse(textField.isFirstResponder);
3839  XCTAssertFalse(textField.flt_hasFirstResponderInViewHierarchySubtree);
3840 }
3841 
3842 - (void)testHasFirstResponderInViewHierarchySubtree_descendantViewBecomesFirstResponder {
3843  // For view to become the first responder, it must be a descendant of a UIWindow
3844  UIWindow* window = [[UIWindow alloc] init];
3845  UIView* view = [[UIView alloc] init];
3846  UIView* childView = [[UIView alloc] init];
3847  UITextField* textField = [[UITextField alloc] init];
3848  [window addSubview:view];
3849  [view addSubview:childView];
3850  [childView addSubview:textField];
3851 
3852  [textField becomeFirstResponder];
3853  XCTAssertTrue(textField.isFirstResponder);
3854  XCTAssertTrue(view.flt_hasFirstResponderInViewHierarchySubtree);
3855  [textField resignFirstResponder];
3856  XCTAssertFalse(textField.isFirstResponder);
3857  XCTAssertFalse(view.flt_hasFirstResponderInViewHierarchySubtree);
3858 }
3859 
3860 - (void)testFlutterClippingMaskViewPoolReuseViewsAfterRecycle {
3861  FlutterClippingMaskViewPool* pool = [[FlutterClippingMaskViewPool alloc] initWithCapacity:2];
3862  FlutterClippingMaskView* view1 = [pool getMaskViewWithFrame:CGRectZero];
3863  FlutterClippingMaskView* view2 = [pool getMaskViewWithFrame:CGRectZero];
3864  [pool insertViewToPoolIfNeeded:view1];
3865  [pool insertViewToPoolIfNeeded:view2];
3866  CGRect newRect = CGRectMake(0, 0, 10, 10);
3867  FlutterClippingMaskView* view3 = [pool getMaskViewWithFrame:newRect];
3868  FlutterClippingMaskView* view4 = [pool getMaskViewWithFrame:newRect];
3869  // view3 and view4 should randomly get either of view1 and view2.
3870  NSSet* set1 = [NSSet setWithObjects:view1, view2, nil];
3871  NSSet* set2 = [NSSet setWithObjects:view3, view4, nil];
3872  XCTAssertEqualObjects(set1, set2);
3873  XCTAssertTrue(CGRectEqualToRect(view3.frame, newRect));
3874  XCTAssertTrue(CGRectEqualToRect(view4.frame, newRect));
3875 }
3876 
3877 - (void)testFlutterClippingMaskViewPoolAllocsNewMaskViewsAfterReachingCapacity {
3878  FlutterClippingMaskViewPool* pool = [[FlutterClippingMaskViewPool alloc] initWithCapacity:2];
3879  FlutterClippingMaskView* view1 = [pool getMaskViewWithFrame:CGRectZero];
3880  FlutterClippingMaskView* view2 = [pool getMaskViewWithFrame:CGRectZero];
3881  FlutterClippingMaskView* view3 = [pool getMaskViewWithFrame:CGRectZero];
3882  XCTAssertNotEqual(view1, view3);
3883  XCTAssertNotEqual(view2, view3);
3884 }
3885 
3886 - (void)testMaskViewsReleasedWhenPoolIsReleased {
3887  __weak UIView* weakView;
3888  @autoreleasepool {
3889  FlutterClippingMaskViewPool* pool = [[FlutterClippingMaskViewPool alloc] initWithCapacity:2];
3890  FlutterClippingMaskView* view = [pool getMaskViewWithFrame:CGRectZero];
3891  weakView = view;
3892  XCTAssertNotNil(weakView);
3893  }
3894  XCTAssertNil(weakView);
3895 }
3896 
3897 - (void)testClipMaskViewIsReused {
3898  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
3899 
3900  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
3901  /*platform=*/GetDefaultTaskRunner(),
3902  /*raster=*/GetDefaultTaskRunner(),
3903  /*ui=*/GetDefaultTaskRunner(),
3904  /*io=*/GetDefaultTaskRunner());
3905  FlutterPlatformViewsController* flutterPlatformViewsController =
3906  [[FlutterPlatformViewsController alloc] init];
3907  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
3908  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
3909  /*delegate=*/mock_delegate,
3910  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
3911  /*platform_views_controller=*/flutterPlatformViewsController,
3912  /*task_runners=*/runners,
3913  /*worker_task_runner=*/nil,
3914  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
3915 
3918  [flutterPlatformViewsController
3919  registerViewFactory:factory
3920  withId:@"MockFlutterPlatformView"
3921  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
3922  FlutterResult result = ^(id result) {
3923  };
3924  [flutterPlatformViewsController
3926  arguments:@{
3927  @"id" : @1,
3928  @"viewType" : @"MockFlutterPlatformView"
3929  }]
3930  result:result];
3931 
3932  XCTAssertNotNil(gMockPlatformView);
3933  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
3934  flutterPlatformViewsController.flutterView = flutterView;
3935  // Create embedded view params
3936  flutter::MutatorsStack stack1;
3937  // Layer tree always pushes a screen scale factor to the stack
3938  flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
3939  flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
3940  stack1.PushTransform(screenScaleMatrix);
3941  // Push a clip rect
3942  flutter::DlRect rect = flutter::DlRect::MakeXYWH(2, 2, 3, 3);
3943  stack1.PushClipRect(rect);
3944 
3945  auto embeddedViewParams1 = std::make_unique<flutter::EmbeddedViewParams>(
3946  screenScaleMatrix, flutter::DlSize(10, 10), stack1);
3947 
3948  [flutterPlatformViewsController prerollCompositeEmbeddedView:1
3949  withParams:std::move(embeddedViewParams1)];
3950  [flutterPlatformViewsController
3951  compositeView:1
3952  withParams:[flutterPlatformViewsController compositionParamsForView:1]];
3953 
3954  UIView* childClippingView1 = gMockPlatformView.superview.superview;
3955  UIView* maskView1 = childClippingView1.maskView;
3956  XCTAssertNotNil(maskView1);
3957 
3958  // Composite a new frame.
3959  [flutterPlatformViewsController beginFrameWithSize:flutter::DlISize(100, 100)];
3960  flutter::MutatorsStack stack2;
3961  auto embeddedViewParams2 = std::make_unique<flutter::EmbeddedViewParams>(
3962  screenScaleMatrix, flutter::DlSize(10, 10), stack2);
3963  auto embeddedViewParams3 = std::make_unique<flutter::EmbeddedViewParams>(
3964  screenScaleMatrix, flutter::DlSize(10, 10), stack2);
3965  [flutterPlatformViewsController prerollCompositeEmbeddedView:1
3966  withParams:std::move(embeddedViewParams3)];
3967  [flutterPlatformViewsController
3968  compositeView:1
3969  withParams:[flutterPlatformViewsController compositionParamsForView:1]];
3970 
3971  childClippingView1 = gMockPlatformView.superview.superview;
3972 
3973  // This overrides gMockPlatformView to point to the newly created platform view.
3974  [flutterPlatformViewsController
3976  arguments:@{
3977  @"id" : @2,
3978  @"viewType" : @"MockFlutterPlatformView"
3979  }]
3980  result:result];
3981 
3982  auto embeddedViewParams4 = std::make_unique<flutter::EmbeddedViewParams>(
3983  screenScaleMatrix, flutter::DlSize(10, 10), stack1);
3984  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
3985  withParams:std::move(embeddedViewParams4)];
3986  [flutterPlatformViewsController
3987  compositeView:2
3988  withParams:[flutterPlatformViewsController compositionParamsForView:2]];
3989 
3990  UIView* childClippingView2 = gMockPlatformView.superview.superview;
3991 
3992  UIView* maskView2 = childClippingView2.maskView;
3993  XCTAssertEqual(maskView1, maskView2);
3994  XCTAssertNotNil(childClippingView2.maskView);
3995  XCTAssertNil(childClippingView1.maskView);
3996 }
3997 
3998 - (void)testDifferentClipMaskViewIsUsedForEachView {
3999  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
4000 
4001  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
4002  /*platform=*/GetDefaultTaskRunner(),
4003  /*raster=*/GetDefaultTaskRunner(),
4004  /*ui=*/GetDefaultTaskRunner(),
4005  /*io=*/GetDefaultTaskRunner());
4006  FlutterPlatformViewsController* flutterPlatformViewsController =
4007  [[FlutterPlatformViewsController alloc] init];
4008  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
4009  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
4010  /*delegate=*/mock_delegate,
4011  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
4012  /*platform_views_controller=*/flutterPlatformViewsController,
4013  /*task_runners=*/runners,
4014  /*worker_task_runner=*/nil,
4015  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
4016 
4019  [flutterPlatformViewsController
4020  registerViewFactory:factory
4021  withId:@"MockFlutterPlatformView"
4022  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
4023  FlutterResult result = ^(id result) {
4024  };
4025 
4026  [flutterPlatformViewsController
4028  arguments:@{
4029  @"id" : @1,
4030  @"viewType" : @"MockFlutterPlatformView"
4031  }]
4032  result:result];
4033  UIView* view1 = gMockPlatformView;
4034 
4035  // This overwrites `gMockPlatformView` to another view.
4036  [flutterPlatformViewsController
4038  arguments:@{
4039  @"id" : @2,
4040  @"viewType" : @"MockFlutterPlatformView"
4041  }]
4042  result:result];
4043  UIView* view2 = gMockPlatformView;
4044 
4045  XCTAssertNotNil(gMockPlatformView);
4046  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
4047  flutterPlatformViewsController.flutterView = flutterView;
4048  // Create embedded view params
4049  flutter::MutatorsStack stack1;
4050  // Layer tree always pushes a screen scale factor to the stack
4051  flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
4052  flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
4053  stack1.PushTransform(screenScaleMatrix);
4054  // Push a clip rect
4055  flutter::DlRect rect = flutter::DlRect::MakeXYWH(2, 2, 3, 3);
4056  stack1.PushClipRect(rect);
4057 
4058  auto embeddedViewParams1 = std::make_unique<flutter::EmbeddedViewParams>(
4059  screenScaleMatrix, flutter::DlSize(10, 10), stack1);
4060 
4061  flutter::MutatorsStack stack2;
4062  stack2.PushClipRect(rect);
4063  auto embeddedViewParams2 = std::make_unique<flutter::EmbeddedViewParams>(
4064  screenScaleMatrix, flutter::DlSize(10, 10), stack2);
4065 
4066  [flutterPlatformViewsController prerollCompositeEmbeddedView:1
4067  withParams:std::move(embeddedViewParams1)];
4068  [flutterPlatformViewsController
4069  compositeView:1
4070  withParams:[flutterPlatformViewsController compositionParamsForView:1]];
4071 
4072  UIView* childClippingView1 = view1.superview.superview;
4073 
4074  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
4075  withParams:std::move(embeddedViewParams2)];
4076  [flutterPlatformViewsController
4077  compositeView:2
4078  withParams:[flutterPlatformViewsController compositionParamsForView:2]];
4079 
4080  UIView* childClippingView2 = view2.superview.superview;
4081  UIView* maskView1 = childClippingView1.maskView;
4082  UIView* maskView2 = childClippingView2.maskView;
4083  XCTAssertNotEqual(maskView1, maskView2);
4084 }
4085 
4086 - (void)testMaskViewUsesCAShapeLayerAsTheBackingLayer {
4087  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
4088 
4089  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
4090  /*platform=*/GetDefaultTaskRunner(),
4091  /*raster=*/GetDefaultTaskRunner(),
4092  /*ui=*/GetDefaultTaskRunner(),
4093  /*io=*/GetDefaultTaskRunner());
4094  FlutterPlatformViewsController* flutterPlatformViewsController =
4095  [[FlutterPlatformViewsController alloc] init];
4096  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
4097  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
4098  /*delegate=*/mock_delegate,
4099  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
4100  /*platform_views_controller=*/flutterPlatformViewsController,
4101  /*task_runners=*/runners,
4102  /*worker_task_runner=*/nil,
4103  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
4104 
4107  [flutterPlatformViewsController
4108  registerViewFactory:factory
4109  withId:@"MockFlutterPlatformView"
4110  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
4111  FlutterResult result = ^(id result) {
4112  };
4113 
4114  [flutterPlatformViewsController
4116  arguments:@{
4117  @"id" : @1,
4118  @"viewType" : @"MockFlutterPlatformView"
4119  }]
4120  result:result];
4121 
4122  XCTAssertNotNil(gMockPlatformView);
4123  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
4124  flutterPlatformViewsController.flutterView = flutterView;
4125  // Create embedded view params
4126  flutter::MutatorsStack stack1;
4127  // Layer tree always pushes a screen scale factor to the stack
4128  flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
4129  flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
4130  stack1.PushTransform(screenScaleMatrix);
4131  // Push a clip rect
4132  flutter::DlRect rect = flutter::DlRect::MakeXYWH(2, 2, 3, 3);
4133  stack1.PushClipRect(rect);
4134 
4135  auto embeddedViewParams1 = std::make_unique<flutter::EmbeddedViewParams>(
4136  screenScaleMatrix, flutter::DlSize(10, 10), stack1);
4137 
4138  flutter::MutatorsStack stack2;
4139  stack2.PushClipRect(rect);
4140  auto embeddedViewParams2 = std::make_unique<flutter::EmbeddedViewParams>(
4141  screenScaleMatrix, flutter::DlSize(10, 10), stack2);
4142 
4143  [flutterPlatformViewsController prerollCompositeEmbeddedView:1
4144  withParams:std::move(embeddedViewParams1)];
4145  [flutterPlatformViewsController
4146  compositeView:1
4147  withParams:[flutterPlatformViewsController compositionParamsForView:1]];
4148 
4149  UIView* childClippingView = gMockPlatformView.superview.superview;
4150 
4151  UIView* maskView = childClippingView.maskView;
4152  XCTAssert([maskView.layer isKindOfClass:[CAShapeLayer class]],
4153  @"Mask view must use CAShapeLayer as its backing layer.");
4154 }
4155 
4156 // Return true if a correct visual effect view is found. It also implies all the validation in this
4157 // method passes.
4158 //
4159 // There are two fail states for this method. 1. One of the XCTAssert method failed; or 2. No
4160 // correct visual effect view found.
4161 - (BOOL)validateOneVisualEffectView:(UIView*)visualEffectView
4162  expectedFrame:(CGRect)frame
4163  inputRadius:(CGFloat)inputRadius {
4164  XCTAssertTrue(CGRectEqualToRect(visualEffectView.frame, frame));
4165  for (UIView* view in visualEffectView.subviews) {
4166  if (![NSStringFromClass([view class]) hasSuffix:@"BackdropView"]) {
4167  continue;
4168  }
4169  XCTAssertEqual(view.layer.filters.count, 1u);
4170  NSObject* filter = view.layer.filters.firstObject;
4171 
4172  XCTAssertEqualObjects([filter valueForKey:@"name"], @"gaussianBlur");
4173 
4174  NSObject* inputRadiusInFilter = [filter valueForKey:@"inputRadius"];
4175  XCTAssertTrue([inputRadiusInFilter isKindOfClass:[NSNumber class]] &&
4176  flutter::BlurRadiusEqualToBlurRadius(((NSNumber*)inputRadiusInFilter).floatValue,
4177  inputRadius));
4178  return YES;
4179  }
4180  return NO;
4181 }
4182 
4183 - (void)testDisposingViewInCompositionOrderDoNotCrash {
4184  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
4185 
4186  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
4187  /*platform=*/GetDefaultTaskRunner(),
4188  /*raster=*/GetDefaultTaskRunner(),
4189  /*ui=*/GetDefaultTaskRunner(),
4190  /*io=*/GetDefaultTaskRunner());
4191  FlutterPlatformViewsController* flutterPlatformViewsController =
4192  [[FlutterPlatformViewsController alloc] init];
4193  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
4194  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
4195  /*delegate=*/mock_delegate,
4196  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
4197  /*platform_views_controller=*/flutterPlatformViewsController,
4198  /*task_runners=*/runners,
4199  /*worker_task_runner=*/nil,
4200  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
4201 
4202  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
4203  flutterPlatformViewsController.flutterView = flutterView;
4204 
4207  [flutterPlatformViewsController
4208  registerViewFactory:factory
4209  withId:@"MockFlutterPlatformView"
4210  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
4211  FlutterResult result = ^(id result) {
4212  };
4213 
4214  [flutterPlatformViewsController
4216  arguments:@{
4217  @"id" : @0,
4218  @"viewType" : @"MockFlutterPlatformView"
4219  }]
4220  result:result];
4221  [flutterPlatformViewsController
4223  arguments:@{
4224  @"id" : @1,
4225  @"viewType" : @"MockFlutterPlatformView"
4226  }]
4227  result:result];
4228 
4229  {
4230  // **** First frame, view id 0, 1 in the composition_order_, disposing view 0 is called. **** //
4231  // No view should be disposed, or removed from the composition order.
4232  [flutterPlatformViewsController beginFrameWithSize:flutter::DlISize(300, 300)];
4233  flutter::MutatorsStack stack;
4234  flutter::DlMatrix finalMatrix;
4235  auto embeddedViewParams0 = std::make_unique<flutter::EmbeddedViewParams>(
4236  finalMatrix, flutter::DlSize(300, 300), stack);
4237  [flutterPlatformViewsController prerollCompositeEmbeddedView:0
4238  withParams:std::move(embeddedViewParams0)];
4239 
4240  auto embeddedViewParams1 = std::make_unique<flutter::EmbeddedViewParams>(
4241  finalMatrix, flutter::DlSize(300, 300), stack);
4242  [flutterPlatformViewsController prerollCompositeEmbeddedView:1
4243  withParams:std::move(embeddedViewParams1)];
4244 
4245  XCTAssertEqual(flutterPlatformViewsController.embeddedViewCount, 2UL);
4246 
4247  XCTestExpectation* expectation = [self expectationWithDescription:@"dispose call ended."];
4248  FlutterResult disposeResult = ^(id result) {
4249  [expectation fulfill];
4250  };
4251 
4252  [flutterPlatformViewsController
4254  result:disposeResult];
4255  [self waitForExpectationsWithTimeout:30 handler:nil];
4256 
4257  flutter::SurfaceFrame::FramebufferInfo framebuffer_info;
4258  auto mock_surface = std::make_unique<flutter::SurfaceFrame>(
4259  nullptr, framebuffer_info,
4260  [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; },
4261  [](const flutter::SurfaceFrame& surface_frame) { return true; },
4262  /*frame_size=*/flutter::DlISize(800, 600), nullptr,
4263  /*display_list_fallback=*/true);
4264  XCTAssertTrue([flutterPlatformViewsController
4265  submitFrame:std::move(mock_surface)
4266  withIosContext:std::make_shared<flutter::IOSContextNoop>()]);
4267 
4268  // Disposing won't remove embedded views until the view is removed from the composition_order_
4269  XCTAssertEqual(flutterPlatformViewsController.embeddedViewCount, 2UL);
4270  XCTAssertNotNil([flutterPlatformViewsController platformViewForId:0]);
4271  XCTAssertNotNil([flutterPlatformViewsController platformViewForId:1]);
4272  }
4273 
4274  {
4275  // **** Second frame, view id 1 in the composition_order_, no disposing view is called, **** //
4276  // View 0 is removed from the composition order in this frame, hence also disposed.
4277  [flutterPlatformViewsController beginFrameWithSize:flutter::DlISize(300, 300)];
4278  flutter::MutatorsStack stack;
4279  flutter::DlMatrix finalMatrix;
4280  auto embeddedViewParams1 = std::make_unique<flutter::EmbeddedViewParams>(
4281  finalMatrix, flutter::DlSize(300, 300), stack);
4282  [flutterPlatformViewsController prerollCompositeEmbeddedView:1
4283  withParams:std::move(embeddedViewParams1)];
4284 
4285  flutter::SurfaceFrame::FramebufferInfo framebuffer_info;
4286  auto mock_surface = std::make_unique<flutter::SurfaceFrame>(
4287  nullptr, framebuffer_info,
4288  [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; },
4289  [](const flutter::SurfaceFrame& surface_frame) { return true; },
4290  /*frame_size=*/flutter::DlISize(800, 600), nullptr, /*display_list_fallback=*/true);
4291  XCTAssertTrue([flutterPlatformViewsController
4292  submitFrame:std::move(mock_surface)
4293  withIosContext:std::make_shared<flutter::IOSContextNoop>()]);
4294 
4295  // Disposing won't remove embedded views until the view is removed from the composition_order_
4296  XCTAssertEqual(flutterPlatformViewsController.embeddedViewCount, 1UL);
4297  XCTAssertNil([flutterPlatformViewsController platformViewForId:0]);
4298  XCTAssertNotNil([flutterPlatformViewsController platformViewForId:1]);
4299  }
4300 }
4301 - (void)testOnlyPlatformViewsAreRemovedWhenReset {
4302  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
4303 
4304  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
4305  /*platform=*/GetDefaultTaskRunner(),
4306  /*raster=*/GetDefaultTaskRunner(),
4307  /*ui=*/GetDefaultTaskRunner(),
4308  /*io=*/GetDefaultTaskRunner());
4309  FlutterPlatformViewsController* flutterPlatformViewsController =
4310  [[FlutterPlatformViewsController alloc] init];
4311  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
4312  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
4313  /*delegate=*/mock_delegate,
4314  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
4315  /*platform_views_controller=*/flutterPlatformViewsController,
4316  /*task_runners=*/runners,
4317  /*worker_task_runner=*/nil,
4318  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
4319 
4322  [flutterPlatformViewsController
4323  registerViewFactory:factory
4324  withId:@"MockFlutterPlatformView"
4325  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
4326  FlutterResult result = ^(id result) {
4327  };
4328  [flutterPlatformViewsController
4330  arguments:@{
4331  @"id" : @2,
4332  @"viewType" : @"MockFlutterPlatformView"
4333  }]
4334  result:result];
4335  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
4336  flutterPlatformViewsController.flutterView = flutterView;
4337  // Create embedded view params
4338  flutter::MutatorsStack stack;
4339  // Layer tree always pushes a screen scale factor to the stack
4340  flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
4341  flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
4342  stack.PushTransform(screenScaleMatrix);
4343  // Push a translate matrix
4344  flutter::DlMatrix translateMatrix = flutter::DlMatrix::MakeTranslation({100, 100});
4345  stack.PushTransform(translateMatrix);
4346  flutter::DlMatrix finalMatrix = screenScaleMatrix * translateMatrix;
4347 
4348  auto embeddedViewParams =
4349  std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, flutter::DlSize(300, 300), stack);
4350 
4351  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
4352  withParams:std::move(embeddedViewParams)];
4353 
4354  flutter::SurfaceFrame::FramebufferInfo framebuffer_info;
4355  auto mock_surface = std::make_unique<flutter::SurfaceFrame>(
4356  nullptr, framebuffer_info,
4357  [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; },
4358  [](const flutter::SurfaceFrame& surface_frame) { return true; },
4359  /*frame_size=*/flutter::DlISize(800, 600), nullptr, /*display_list_fallback=*/true);
4360  [flutterPlatformViewsController submitFrame:std::move(mock_surface)
4361  withIosContext:std::make_shared<flutter::IOSContextNoop>()];
4362 
4363  UIView* someView = [[UIView alloc] init];
4364  [flutterView addSubview:someView];
4365 
4366  [flutterPlatformViewsController reset];
4367  XCTAssertEqual(flutterView.subviews.count, 1u);
4368  XCTAssertEqual(flutterView.subviews.firstObject, someView);
4369 }
4370 
4371 - (void)testResetClearsPreviousCompositionOrder {
4372  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
4373 
4374  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
4375  /*platform=*/GetDefaultTaskRunner(),
4376  /*raster=*/GetDefaultTaskRunner(),
4377  /*ui=*/GetDefaultTaskRunner(),
4378  /*io=*/GetDefaultTaskRunner());
4379  FlutterPlatformViewsController* flutterPlatformViewsController =
4380  [[FlutterPlatformViewsController alloc] init];
4381  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
4382  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
4383  /*delegate=*/mock_delegate,
4384  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
4385  /*platform_views_controller=*/flutterPlatformViewsController,
4386  /*task_runners=*/runners,
4387  /*worker_task_runner=*/nil,
4388  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
4389 
4392  [flutterPlatformViewsController
4393  registerViewFactory:factory
4394  withId:@"MockFlutterPlatformView"
4395  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
4396  FlutterResult result = ^(id result) {
4397  };
4398  [flutterPlatformViewsController
4400  arguments:@{
4401  @"id" : @2,
4402  @"viewType" : @"MockFlutterPlatformView"
4403  }]
4404  result:result];
4405  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
4406  flutterPlatformViewsController.flutterView = flutterView;
4407  // Create embedded view params
4408  flutter::MutatorsStack stack;
4409  // Layer tree always pushes a screen scale factor to the stack
4410  flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
4411  flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
4412  stack.PushTransform(screenScaleMatrix);
4413  // Push a translate matrix
4414  flutter::DlMatrix translateMatrix = flutter::DlMatrix::MakeTranslation({100, 100});
4415  stack.PushTransform(translateMatrix);
4416  flutter::DlMatrix finalMatrix = screenScaleMatrix * translateMatrix;
4417 
4418  auto embeddedViewParams =
4419  std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, flutter::DlSize(300, 300), stack);
4420 
4421  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
4422  withParams:std::move(embeddedViewParams)];
4423 
4424  flutter::SurfaceFrame::FramebufferInfo framebuffer_info;
4425  auto mock_surface = std::make_unique<flutter::SurfaceFrame>(
4426  nullptr, framebuffer_info,
4427  [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; },
4428  [](const flutter::SurfaceFrame& surface_frame) { return true; },
4429  /*frame_size=*/flutter::DlISize(800, 600), nullptr, /*display_list_fallback=*/true);
4430  [flutterPlatformViewsController submitFrame:std::move(mock_surface)
4431  withIosContext:std::make_shared<flutter::IOSContextNoop>()];
4432 
4433  // The above code should result in previousCompositionOrder having one viewId in it
4434  XCTAssertEqual(flutterPlatformViewsController.previousCompositionOrder.size(), 1ul);
4435 
4436  // reset should clear previousCompositionOrder
4437  [flutterPlatformViewsController reset];
4438 
4439  // previousCompositionOrder should now be empty
4440  XCTAssertEqual(flutterPlatformViewsController.previousCompositionOrder.size(), 0ul);
4441 }
4442 
4443 - (void)testNilPlatformViewDoesntCrash {
4444  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
4445 
4446  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
4447  /*platform=*/GetDefaultTaskRunner(),
4448  /*raster=*/GetDefaultTaskRunner(),
4449  /*ui=*/GetDefaultTaskRunner(),
4450  /*io=*/GetDefaultTaskRunner());
4451  FlutterPlatformViewsController* flutterPlatformViewsController =
4452  [[FlutterPlatformViewsController alloc] init];
4453  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
4454  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
4455  /*delegate=*/mock_delegate,
4456  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
4457  /*platform_views_controller=*/flutterPlatformViewsController,
4458  /*task_runners=*/runners,
4459  /*worker_task_runner=*/nil,
4460  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
4461 
4464  [flutterPlatformViewsController
4465  registerViewFactory:factory
4466  withId:@"MockFlutterPlatformView"
4467  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
4468  FlutterResult result = ^(id result) {
4469  };
4470  [flutterPlatformViewsController
4472  arguments:@{
4473  @"id" : @2,
4474  @"viewType" : @"MockFlutterPlatformView"
4475  }]
4476  result:result];
4477  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
4478  flutterPlatformViewsController.flutterView = flutterView;
4479 
4480  // Create embedded view params
4481  flutter::MutatorsStack stack;
4482  // Layer tree always pushes a screen scale factor to the stack
4483  flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
4484  flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
4485  stack.PushTransform(screenScaleMatrix);
4486  // Push a translate matrix
4487  flutter::DlMatrix translateMatrix = flutter::DlMatrix::MakeTranslation({100, 100});
4488  stack.PushTransform(translateMatrix);
4489  flutter::DlMatrix finalMatrix = screenScaleMatrix * translateMatrix;
4490 
4491  auto embeddedViewParams =
4492  std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, flutter::DlSize(300, 300), stack);
4493 
4494  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
4495  withParams:std::move(embeddedViewParams)];
4496 
4497  flutter::SurfaceFrame::FramebufferInfo framebuffer_info;
4498  auto mock_surface = std::make_unique<flutter::SurfaceFrame>(
4499  nullptr, framebuffer_info,
4500  [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; },
4501  [](const flutter::SurfaceFrame& surface_frame) { return true; },
4502  /*frame_size=*/flutter::DlISize(800, 600), nullptr, /*display_list_fallback=*/true);
4503  [flutterPlatformViewsController submitFrame:std::move(mock_surface)
4504  withIosContext:std::make_shared<flutter::IOSContextNoop>()];
4505 
4506  XCTAssertEqual(flutterView.subviews.count, 1u);
4507 }
4508 
4509 - (void)testFlutterTouchInterceptingViewLinksToAccessibilityContainer {
4510  FlutterTouchInterceptingView* touchInteceptorView = [[FlutterTouchInterceptingView alloc] init];
4511  NSObject* container = [[NSObject alloc] init];
4512  [touchInteceptorView setFlutterAccessibilityContainer:container];
4513  XCTAssertEqualObjects([touchInteceptorView accessibilityContainer], container);
4514 }
4515 
4516 - (void)testLayerPool {
4517  // Create an IOSContext.
4518  FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar"];
4519  [engine run];
4520  XCTAssertTrue(engine.platformView != nullptr);
4521  auto ios_context = engine.platformView->GetIosContext();
4522 
4523  auto pool = flutter::OverlayLayerPool{};
4524 
4525  // Add layers to the pool.
4526  pool.CreateLayer(ios_context, MTLPixelFormatBGRA8Unorm);
4527  XCTAssertEqual(pool.size(), 1u);
4528  pool.CreateLayer(ios_context, MTLPixelFormatBGRA8Unorm);
4529  XCTAssertEqual(pool.size(), 2u);
4530 
4531  // Mark all layers as unused.
4532  pool.RecycleLayers();
4533  XCTAssertEqual(pool.size(), 2u);
4534 
4535  // Free the unused layers. One should remain.
4536  auto unused_layers = pool.RemoveUnusedLayers();
4537  XCTAssertEqual(unused_layers.size(), 2u);
4538  XCTAssertEqual(pool.size(), 1u);
4539 }
4540 
4541 - (void)testFlutterPlatformViewControllerSubmitFramePreservingFrameDamage {
4542  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
4543 
4544  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
4545  /*platform=*/GetDefaultTaskRunner(),
4546  /*raster=*/GetDefaultTaskRunner(),
4547  /*ui=*/GetDefaultTaskRunner(),
4548  /*io=*/GetDefaultTaskRunner());
4549  FlutterPlatformViewsController* flutterPlatformViewsController =
4550  [[FlutterPlatformViewsController alloc] init];
4551  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
4552  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
4553  /*delegate=*/mock_delegate,
4554  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
4555  /*platform_views_controller=*/flutterPlatformViewsController,
4556  /*task_runners=*/runners,
4557  /*worker_task_runner=*/nil,
4558  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
4559 
4560  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
4561  flutterPlatformViewsController.flutterView = flutterView;
4562 
4565  [flutterPlatformViewsController
4566  registerViewFactory:factory
4567  withId:@"MockFlutterPlatformView"
4568  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
4569  FlutterResult result = ^(id result) {
4570  };
4571  [flutterPlatformViewsController
4573  arguments:@{
4574  @"id" : @0,
4575  @"viewType" : @"MockFlutterPlatformView"
4576  }]
4577  result:result];
4578 
4579  // This overwrites `gMockPlatformView` to another view.
4580  [flutterPlatformViewsController
4582  arguments:@{
4583  @"id" : @1,
4584  @"viewType" : @"MockFlutterPlatformView"
4585  }]
4586  result:result];
4587 
4588  [flutterPlatformViewsController beginFrameWithSize:flutter::DlISize(300, 300)];
4589  flutter::MutatorsStack stack;
4590  flutter::DlMatrix finalMatrix;
4591  auto embeddedViewParams1 =
4592  std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, flutter::DlSize(300, 300), stack);
4593  [flutterPlatformViewsController prerollCompositeEmbeddedView:0
4594  withParams:std::move(embeddedViewParams1)];
4595 
4596  auto embeddedViewParams2 =
4597  std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, flutter::DlSize(500, 500), stack);
4598  [flutterPlatformViewsController prerollCompositeEmbeddedView:1
4599  withParams:std::move(embeddedViewParams2)];
4600 
4601  flutter::SurfaceFrame::FramebufferInfo framebuffer_info;
4602  std::optional<flutter::SurfaceFrame::SubmitInfo> submit_info;
4603  auto mock_surface = std::make_unique<flutter::SurfaceFrame>(
4604  nullptr, framebuffer_info,
4605  [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; },
4606  [&](const flutter::SurfaceFrame& surface_frame) {
4607  submit_info = surface_frame.submit_info();
4608  return true;
4609  },
4610  /*frame_size=*/flutter::DlISize(800, 600), nullptr,
4611  /*display_list_fallback=*/true);
4612  mock_surface->set_submit_info({
4613  .frame_damage = flutter::DlIRect::MakeWH(800, 600),
4614  .buffer_damage = flutter::DlIRect::MakeWH(400, 600),
4615  });
4616 
4617  [flutterPlatformViewsController submitFrame:std::move(mock_surface)
4618  withIosContext:std::make_shared<flutter::IOSContextNoop>()];
4619 
4620  XCTAssertTrue(submit_info.has_value());
4621  XCTAssertEqual(*submit_info->frame_damage, flutter::DlIRect::MakeWH(800, 600));
4622  XCTAssertEqual(*submit_info->buffer_damage, flutter::DlIRect::MakeWH(400, 600));
4623 }
4624 
4625 - (void)testClipSuperellipse {
4626  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
4627 
4628  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
4629  /*platform=*/GetDefaultTaskRunner(),
4630  /*raster=*/GetDefaultTaskRunner(),
4631  /*ui=*/GetDefaultTaskRunner(),
4632  /*io=*/GetDefaultTaskRunner());
4633  FlutterPlatformViewsController* flutterPlatformViewsController =
4634  [[FlutterPlatformViewsController alloc] init];
4635  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
4636  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
4637  /*delegate=*/mock_delegate,
4638  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
4639  /*platform_views_controller=*/flutterPlatformViewsController,
4640  /*task_runners=*/runners,
4641  /*worker_task_runner=*/nil,
4642  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
4643 
4646  [flutterPlatformViewsController
4647  registerViewFactory:factory
4648  withId:@"MockFlutterPlatformView"
4649  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
4650  FlutterResult result = ^(id result) {
4651  };
4652  [flutterPlatformViewsController
4654  arguments:@{
4655  @"id" : @2,
4656  @"viewType" : @"MockFlutterPlatformView"
4657  }]
4658  result:result];
4659 
4660  XCTAssertNotNil(gMockPlatformView);
4661 
4662  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
4663  flutterPlatformViewsController.flutterView = flutterView;
4664  // Create embedded view params
4665  flutter::MutatorsStack stack;
4666  // Layer tree always pushes a screen scale factor to the stack
4667  flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
4668  flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
4669  stack.PushTransform(screenScaleMatrix);
4670  // Push a clip superellipse
4671  flutter::DlRect rect = flutter::DlRect::MakeXYWH(3, 3, 5, 5);
4672  stack.PushClipRSE(flutter::DlRoundSuperellipse::MakeOval(rect));
4673 
4674  auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
4675  screenScaleMatrix, flutter::DlSize(10, 10), stack);
4676 
4677  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
4678  withParams:std::move(embeddedViewParams)];
4679  [flutterPlatformViewsController
4680  compositeView:2
4681  withParams:[flutterPlatformViewsController compositionParamsForView:2]];
4682 
4683  gMockPlatformView.backgroundColor = UIColor.redColor;
4684  XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]);
4685  ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
4686  [flutterView addSubview:childClippingView];
4687 
4688  [flutterView setNeedsLayout];
4689  [flutterView layoutIfNeeded];
4690 
4691  CGPoint corners[] = {CGPointMake(rect.GetLeft(), rect.GetTop()),
4692  CGPointMake(rect.GetRight(), rect.GetTop()),
4693  CGPointMake(rect.GetLeft(), rect.GetBottom()),
4694  CGPointMake(rect.GetRight(), rect.GetBottom())};
4695  for (auto point : corners) {
4696  int alpha = [self alphaOfPoint:point onView:flutterView];
4697  XCTAssertNotEqual(alpha, 255);
4698  }
4699  CGPoint center =
4700  CGPointMake(rect.GetLeft() + rect.GetWidth() / 2, rect.GetTop() + rect.GetHeight() / 2);
4701  int alpha = [self alphaOfPoint:center onView:flutterView];
4702  XCTAssertEqual(alpha, 255);
4703 }
4704 
4705 @end
void(^ FlutterResult)(id _Nullable result)
flutter::Settings settings_
std::unique_ptr< flutter::PlatformViewIOS > platform_view
static __weak UIView * gMockPlatformView
const float kFloatCompareEpsilon
NSMutableArray * backdropFilterSubviews()
void applyBlurBackdropFilters:(NSArray< PlatformViewFilter * > *filters)
FlutterClippingMaskView * getMaskViewWithFrame:(CGRect frame)
void insertViewToPoolIfNeeded:(FlutterClippingMaskView *maskView)
Storage for Overlay layers across frames.
void CreateLayer(const std::shared_ptr< IOSContext > &ios_context, MTLPixelFormat pixel_format)
Create a new overlay layer.
instancetype methodCallWithMethodName:arguments:(NSString *method,[arguments] id _Nullable arguments)
const flutter::EmbeddedViewParams & compositionParamsForView:(int64_t viewId)
void pushVisitedPlatformViewId:(int64_t viewId)
Pushes the view id of a visted platform view to the list of visied platform views.
void reset()
Discards all platform views instances and auxiliary resources.
void registerViewFactory:withId:gestureRecognizersBlockingPolicy:(NSObject< FlutterPlatformViewFactory > *factory,[withId] NSString *factoryId,[gestureRecognizersBlockingPolicy] FlutterPlatformViewGestureRecognizersBlockingPolicy gestureRecognizerBlockingPolicy)
set the factory used to construct embedded UI Views.
std::vector< int64_t > & previousCompositionOrder()
const fml::RefPtr< fml::TaskRunner > & taskRunner
The task runner used to post rendering tasks to the platform thread.
UIViewController< FlutterViewResponder > *_Nullable flutterViewController
The flutter view controller.
void compositeView:withParams:(int64_t viewId,[withParams] const flutter::EmbeddedViewParams &params)
UIView *_Nullable flutterView
The flutter view.
void onMethodCall:result:(FlutterMethodCall *call,[result] FlutterResult result)
Handler for platform view message channels.
int64_t texture_id