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