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