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