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