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