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 = std::make_unique<flutter::EmbeddedViewParams>(
346  flutter::ToSkMatrix(finalMatrix), SkSize::Make(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  flutter::ToSkMatrix(screenScaleMatrix), SkSize::Make(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  flutter::ToSkMatrix(screenScaleMatrix), SkSize::Make(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  flutter::ToSkMatrix(screenScaleMatrix), SkSize::Make(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  flutter::ToSkMatrix(screenScaleMatrix), SkSize::Make(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  flutter::ToSkMatrix(screenScaleMatrix), SkSize::Make(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  flutter::ToSkMatrix(screenScaleMatrix), SkSize::Make(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  flutter::ToSkMatrix(screenScaleMatrix), SkSize::Make(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  flutter::ToSkMatrix(screenScaleMatrix), SkSize::Make(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  flutter::ToSkMatrix(screenScaleMatrix), SkSize::Make(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  flutter::ToSkMatrix(screenScaleMatrix), SkSize::Make(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  flutter::ToSkMatrix(screenScaleMatrix), SkSize::Make(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  flutter::ToSkMatrix(screenScaleMatrix), SkSize::Make(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  flutter::ToSkMatrix(screenScaleMatrix), SkSize::Make(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  flutter::ToSkMatrix(screenScaleMatrix), SkSize::Make(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  flutter::ToSkMatrix(screenScaleMatrix), SkSize::Make(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  flutter::ToSkMatrix(screenScaleMatrix), SkSize::Make(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  flutter::ToSkMatrix(screenScaleMatrix), SkSize::Make(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  flutter::ToSkMatrix(screenScaleMatrix), SkSize::Make(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 = std::make_unique<flutter::EmbeddedViewParams>(
1669  flutter::ToSkMatrix(finalMatrix), SkSize::Make(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  flutter::ToSkMatrix(screenScaleMatrix), SkSize::Make(10, 10), stack);
1730 
1731  [flutterPlatformViewsController beginFrameWithSize:SkISize::Make(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:SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10)];
1739  [flutterPlatformViewsController
1740  compositeView:2
1741  withParams:[flutterPlatformViewsController compositionParamsForView:2]];
1742 
1743  XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:[ChildClippingView class]]);
1744  ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
1745  [flutterView addSubview:childClippingView];
1746 
1747  [flutterView setNeedsLayout];
1748  [flutterView layoutIfNeeded];
1749 
1750  // childClippingView has visual effect view with the correct configurations.
1751  NSUInteger numberOfExpectedVisualEffectView = 0;
1752  for (UIView* subview in childClippingView.subviews) {
1753  if (![subview isKindOfClass:[UIVisualEffectView class]]) {
1754  continue;
1755  }
1756  XCTAssertLessThan(numberOfExpectedVisualEffectView, 1u);
1757  if ([self validateOneVisualEffectView:subview
1758  expectedFrame:CGRectMake(0, 0, 10, 10)
1759  inputRadius:5]) {
1760  numberOfExpectedVisualEffectView++;
1761  }
1762  }
1763  XCTAssertEqual(numberOfExpectedVisualEffectView, 1u);
1764 
1765  // New frame, with no filter pushed.
1766  auto embeddedViewParams2 = std::make_unique<flutter::EmbeddedViewParams>(
1767  flutter::ToSkMatrix(screenScaleMatrix), SkSize::Make(10, 10), stack);
1768  [flutterPlatformViewsController beginFrameWithSize:SkISize::Make(0, 0)];
1769  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
1770  withParams:std::move(embeddedViewParams2)];
1771  [flutterPlatformViewsController
1772  compositeView:2
1773  withParams:[flutterPlatformViewsController compositionParamsForView:2]];
1774 
1775  XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:[ChildClippingView class]]);
1776 
1777  [flutterView setNeedsLayout];
1778  [flutterView layoutIfNeeded];
1779 
1780  numberOfExpectedVisualEffectView = 0;
1781  for (UIView* subview in childClippingView.subviews) {
1782  if (![subview isKindOfClass:[UIVisualEffectView class]]) {
1783  continue;
1784  }
1785  numberOfExpectedVisualEffectView++;
1786  }
1787  XCTAssertEqual(numberOfExpectedVisualEffectView, 0u);
1788 }
1789 
1790 - (void)testChildClippingViewShouldBeTheBoundingRectOfPlatformView {
1791  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
1792 
1793  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
1794  /*platform=*/GetDefaultTaskRunner(),
1795  /*raster=*/GetDefaultTaskRunner(),
1796  /*ui=*/GetDefaultTaskRunner(),
1797  /*io=*/GetDefaultTaskRunner());
1798  FlutterPlatformViewsController* flutterPlatformViewsController =
1799  [[FlutterPlatformViewsController alloc] init];
1800  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
1801  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1802  /*delegate=*/mock_delegate,
1803  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
1804  /*platform_views_controller=*/flutterPlatformViewsController,
1805  /*task_runners=*/runners,
1806  /*worker_task_runner=*/nil,
1807  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
1808 
1811  [flutterPlatformViewsController
1812  registerViewFactory:factory
1813  withId:@"MockFlutterPlatformView"
1814  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
1815  FlutterResult result = ^(id result) {
1816  };
1817  [flutterPlatformViewsController
1819  arguments:@{
1820  @"id" : @2,
1821  @"viewType" : @"MockFlutterPlatformView"
1822  }]
1823  result:result];
1824 
1825  XCTAssertNotNil(gMockPlatformView);
1826 
1827  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
1828  flutterPlatformViewsController.flutterView = flutterView;
1829  // Create embedded view params
1830  flutter::MutatorsStack stack;
1831  // Layer tree always pushes a screen scale factor to the stack
1832  flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
1833  flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
1834  stack.PushTransform(screenScaleMatrix);
1835  // Push a rotate matrix
1836  flutter::DlMatrix rotateMatrix = flutter::DlMatrix::MakeRotationZ(flutter::DlDegrees(10));
1837  stack.PushTransform(rotateMatrix);
1838  flutter::DlMatrix finalMatrix = screenScaleMatrix * rotateMatrix;
1839 
1840  auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
1841  flutter::ToSkMatrix(finalMatrix), SkSize::Make(300, 300), stack);
1842 
1843  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
1844  withParams:std::move(embeddedViewParams)];
1845  [flutterPlatformViewsController
1846  compositeView:2
1847  withParams:[flutterPlatformViewsController compositionParamsForView:2]];
1848 
1849  CGRect platformViewRectInFlutterView = [gMockPlatformView convertRect:gMockPlatformView.bounds
1850  toView:flutterView];
1851  XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]);
1852  ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
1853  // The childclippingview's frame is set based on flow, but the platform view's frame is set based
1854  // on quartz. Although they should be the same, but we should tolerate small floating point
1855  // errors.
1856  XCTAssertLessThan(fabs(platformViewRectInFlutterView.origin.x - childClippingView.frame.origin.x),
1858  XCTAssertLessThan(fabs(platformViewRectInFlutterView.origin.y - childClippingView.frame.origin.y),
1860  XCTAssertLessThan(
1861  fabs(platformViewRectInFlutterView.size.width - childClippingView.frame.size.width),
1863  XCTAssertLessThan(
1864  fabs(platformViewRectInFlutterView.size.height - childClippingView.frame.size.height),
1866 }
1867 
1868 - (void)testClipsDoNotInterceptWithPlatformViewShouldNotAddMaskView {
1869  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
1870 
1871  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
1872  /*platform=*/GetDefaultTaskRunner(),
1873  /*raster=*/GetDefaultTaskRunner(),
1874  /*ui=*/GetDefaultTaskRunner(),
1875  /*io=*/GetDefaultTaskRunner());
1876  FlutterPlatformViewsController* flutterPlatformViewsController =
1877  [[FlutterPlatformViewsController alloc] init];
1878  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
1879  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1880  /*delegate=*/mock_delegate,
1881  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
1882  /*platform_views_controller=*/flutterPlatformViewsController,
1883  /*task_runners=*/runners,
1884  /*worker_task_runner=*/nil,
1885  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
1886 
1889  [flutterPlatformViewsController
1890  registerViewFactory:factory
1891  withId:@"MockFlutterPlatformView"
1892  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
1893  FlutterResult result = ^(id result) {
1894  };
1895  [flutterPlatformViewsController
1897  arguments:@{
1898  @"id" : @2,
1899  @"viewType" : @"MockFlutterPlatformView"
1900  }]
1901  result:result];
1902 
1903  XCTAssertNotNil(gMockPlatformView);
1904 
1905  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 30, 30)];
1906  flutterPlatformViewsController.flutterView = flutterView;
1907  // Create embedded view params.
1908  flutter::MutatorsStack stack;
1909  // Layer tree always pushes a screen scale factor to the stack.
1910  flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
1911  flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
1912  stack.PushTransform(screenScaleMatrix);
1913  flutter::DlMatrix translateMatrix = flutter::DlMatrix::MakeTranslation({5, 5});
1914  // The platform view's rect for this test will be (5, 5, 10, 10).
1915  stack.PushTransform(translateMatrix);
1916  // Push a clip rect, big enough to contain the entire platform view bound.
1917  flutter::DlRect rect = flutter::DlRect::MakeXYWH(0, 0, 25, 25);
1918  stack.PushClipRect(rect);
1919  // Push a clip rrect, big enough to contain the entire platform view bound without clipping it.
1920  // Make the origin (-1, -1) so that the top left rounded corner isn't clipping the PlatformView.
1921  flutter::DlRect rect_for_rrect = flutter::DlRect::MakeXYWH(-1, -1, 25, 25);
1922  flutter::DlRoundRect rrect = flutter::DlRoundRect::MakeRectXY(rect_for_rrect, 1, 1);
1923  stack.PushClipRRect(rrect);
1924 
1925  auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
1926  flutter::ToSkMatrix(screenScaleMatrix * translateMatrix), SkSize::Make(5, 5), stack);
1927 
1928  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
1929  withParams:std::move(embeddedViewParams)];
1930  [flutterPlatformViewsController
1931  compositeView:2
1932  withParams:[flutterPlatformViewsController compositionParamsForView:2]];
1933 
1934  gMockPlatformView.backgroundColor = UIColor.redColor;
1935  XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]);
1936  ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
1937  [flutterView addSubview:childClippingView];
1938 
1939  [flutterView setNeedsLayout];
1940  [flutterView layoutIfNeeded];
1941  XCTAssertNil(childClippingView.maskView);
1942 }
1943 
1944 - (void)testClipRRectOnlyHasCornersInterceptWithPlatformViewShouldAddMaskView {
1945  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
1946 
1947  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
1948  /*platform=*/GetDefaultTaskRunner(),
1949  /*raster=*/GetDefaultTaskRunner(),
1950  /*ui=*/GetDefaultTaskRunner(),
1951  /*io=*/GetDefaultTaskRunner());
1952  FlutterPlatformViewsController* flutterPlatformViewsController =
1953  [[FlutterPlatformViewsController alloc] init];
1954  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
1955  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1956  /*delegate=*/mock_delegate,
1957  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
1958  /*platform_views_controller=*/flutterPlatformViewsController,
1959  /*task_runners=*/runners,
1960  /*worker_task_runner=*/nil,
1961  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
1962 
1965  [flutterPlatformViewsController
1966  registerViewFactory:factory
1967  withId:@"MockFlutterPlatformView"
1968  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
1969  FlutterResult result = ^(id result) {
1970  };
1971  [flutterPlatformViewsController
1973  arguments:@{
1974  @"id" : @2,
1975  @"viewType" : @"MockFlutterPlatformView"
1976  }]
1977  result:result];
1978 
1979  XCTAssertNotNil(gMockPlatformView);
1980 
1981  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 30, 30)];
1982  flutterPlatformViewsController.flutterView = flutterView;
1983  // Create embedded view params
1984  flutter::MutatorsStack stack;
1985  // Layer tree always pushes a screen scale factor to the stack.
1986  flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
1987  flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
1988  stack.PushTransform(screenScaleMatrix);
1989  flutter::DlMatrix translateMatrix = flutter::DlMatrix::MakeTranslation({5, 5});
1990  // The platform view's rect for this test will be (5, 5, 10, 10).
1991  stack.PushTransform(translateMatrix);
1992 
1993  // Push a clip rrect, the rect of the rrect is the same as the PlatformView of the corner should.
1994  // clip the PlatformView.
1995  flutter::DlRect rect_for_rrect = flutter::DlRect::MakeXYWH(0, 0, 10, 10);
1996  flutter::DlRoundRect rrect = flutter::DlRoundRect::MakeRectXY(rect_for_rrect, 1, 1);
1997  stack.PushClipRRect(rrect);
1998 
1999  auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
2000  flutter::ToSkMatrix(screenScaleMatrix * translateMatrix), SkSize::Make(5, 5), stack);
2001 
2002  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
2003  withParams:std::move(embeddedViewParams)];
2004  [flutterPlatformViewsController
2005  compositeView:2
2006  withParams:[flutterPlatformViewsController compositionParamsForView:2]];
2007 
2008  gMockPlatformView.backgroundColor = UIColor.redColor;
2009  XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]);
2010  ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
2011  [flutterView addSubview:childClippingView];
2012 
2013  [flutterView setNeedsLayout];
2014  [flutterView layoutIfNeeded];
2015 
2016  XCTAssertNotNil(childClippingView.maskView);
2017 }
2018 
2019 - (void)testClipRect {
2020  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
2021 
2022  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
2023  /*platform=*/GetDefaultTaskRunner(),
2024  /*raster=*/GetDefaultTaskRunner(),
2025  /*ui=*/GetDefaultTaskRunner(),
2026  /*io=*/GetDefaultTaskRunner());
2027  FlutterPlatformViewsController* flutterPlatformViewsController =
2028  [[FlutterPlatformViewsController alloc] init];
2029  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
2030  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2031  /*delegate=*/mock_delegate,
2032  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
2033  /*platform_views_controller=*/flutterPlatformViewsController,
2034  /*task_runners=*/runners,
2035  /*worker_task_runner=*/nil,
2036  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
2037 
2040  [flutterPlatformViewsController
2041  registerViewFactory:factory
2042  withId:@"MockFlutterPlatformView"
2043  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
2044  FlutterResult result = ^(id result) {
2045  };
2046  [flutterPlatformViewsController
2048  arguments:@{
2049  @"id" : @2,
2050  @"viewType" : @"MockFlutterPlatformView"
2051  }]
2052  result:result];
2053 
2054  XCTAssertNotNil(gMockPlatformView);
2055 
2056  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
2057  flutterPlatformViewsController.flutterView = flutterView;
2058  // Create embedded view params
2059  flutter::MutatorsStack stack;
2060  // Layer tree always pushes a screen scale factor to the stack
2061  flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
2062  flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
2063  stack.PushTransform(screenScaleMatrix);
2064  // Push a clip rect
2065  flutter::DlRect rect = flutter::DlRect::MakeXYWH(2, 2, 3, 3);
2066  stack.PushClipRect(rect);
2067 
2068  auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
2069  flutter::ToSkMatrix(screenScaleMatrix), SkSize::Make(10, 10), stack);
2070 
2071  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
2072  withParams:std::move(embeddedViewParams)];
2073  [flutterPlatformViewsController
2074  compositeView:2
2075  withParams:[flutterPlatformViewsController compositionParamsForView:2]];
2076 
2077  gMockPlatformView.backgroundColor = UIColor.redColor;
2078  XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]);
2079  ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
2080  [flutterView addSubview:childClippingView];
2081 
2082  [flutterView setNeedsLayout];
2083  [flutterView layoutIfNeeded];
2084 
2085  CGRect insideClipping = CGRectMake(2, 2, 3, 3);
2086  for (int i = 0; i < 10; i++) {
2087  for (int j = 0; j < 10; j++) {
2088  CGPoint point = CGPointMake(i, j);
2089  int alpha = [self alphaOfPoint:CGPointMake(i, j) onView:flutterView];
2090  if (CGRectContainsPoint(insideClipping, point)) {
2091  XCTAssertEqual(alpha, 255);
2092  } else {
2093  XCTAssertEqual(alpha, 0);
2094  }
2095  }
2096  }
2097 }
2098 
2099 - (void)testClipRect_multipleClips {
2100  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
2101 
2102  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
2103  /*platform=*/GetDefaultTaskRunner(),
2104  /*raster=*/GetDefaultTaskRunner(),
2105  /*ui=*/GetDefaultTaskRunner(),
2106  /*io=*/GetDefaultTaskRunner());
2107  FlutterPlatformViewsController* flutterPlatformViewsController =
2108  [[FlutterPlatformViewsController alloc] init];
2109  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
2110  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2111  /*delegate=*/mock_delegate,
2112  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
2113  /*platform_views_controller=*/flutterPlatformViewsController,
2114  /*task_runners=*/runners,
2115  /*worker_task_runner=*/nil,
2116  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
2117 
2120  [flutterPlatformViewsController
2121  registerViewFactory:factory
2122  withId:@"MockFlutterPlatformView"
2123  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
2124  FlutterResult result = ^(id result) {
2125  };
2126  [flutterPlatformViewsController
2128  arguments:@{
2129  @"id" : @2,
2130  @"viewType" : @"MockFlutterPlatformView"
2131  }]
2132  result:result];
2133 
2134  XCTAssertNotNil(gMockPlatformView);
2135 
2136  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
2137  flutterPlatformViewsController.flutterView = flutterView;
2138  // Create embedded view params
2139  flutter::MutatorsStack stack;
2140  // Layer tree always pushes a screen scale factor to the stack
2141  flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
2142  flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
2143  stack.PushTransform(screenScaleMatrix);
2144  // Push a clip rect
2145  flutter::DlRect rect1 = flutter::DlRect::MakeXYWH(2, 2, 3, 3);
2146  stack.PushClipRect(rect1);
2147  // Push another clip rect
2148  flutter::DlRect rect2 = flutter::DlRect::MakeXYWH(3, 3, 3, 3);
2149  stack.PushClipRect(rect2);
2150 
2151  auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
2152  flutter::ToSkMatrix(screenScaleMatrix), SkSize::Make(10, 10), stack);
2153 
2154  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
2155  withParams:std::move(embeddedViewParams)];
2156  [flutterPlatformViewsController
2157  compositeView:2
2158  withParams:[flutterPlatformViewsController compositionParamsForView:2]];
2159 
2160  gMockPlatformView.backgroundColor = UIColor.redColor;
2161  XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]);
2162  ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
2163  [flutterView addSubview:childClippingView];
2164 
2165  [flutterView setNeedsLayout];
2166  [flutterView layoutIfNeeded];
2167 
2168  /*
2169  clip 1 clip 2
2170  2 3 4 5 6 2 3 4 5 6
2171  2 + - - + 2
2172  3 | | 3 + - - +
2173  4 | | 4 | |
2174  5 + - - + 5 | |
2175  6 6 + - - +
2176 
2177  Result should be the intersection of 2 clips
2178  2 3 4 5 6
2179  2
2180  3 + - +
2181  4 | |
2182  5 + - +
2183  6
2184  */
2185  CGRect insideClipping = CGRectMake(3, 3, 2, 2);
2186  for (int i = 0; i < 10; i++) {
2187  for (int j = 0; j < 10; j++) {
2188  CGPoint point = CGPointMake(i, j);
2189  int alpha = [self alphaOfPoint:CGPointMake(i, j) onView:flutterView];
2190  if (CGRectContainsPoint(insideClipping, point)) {
2191  XCTAssertEqual(alpha, 255);
2192  } else {
2193  XCTAssertEqual(alpha, 0);
2194  }
2195  }
2196  }
2197 }
2198 
2199 - (void)testClipRRect {
2200  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
2201 
2202  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
2203  /*platform=*/GetDefaultTaskRunner(),
2204  /*raster=*/GetDefaultTaskRunner(),
2205  /*ui=*/GetDefaultTaskRunner(),
2206  /*io=*/GetDefaultTaskRunner());
2207  FlutterPlatformViewsController* flutterPlatformViewsController =
2208  [[FlutterPlatformViewsController alloc] init];
2209  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
2210  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2211  /*delegate=*/mock_delegate,
2212  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
2213  /*platform_views_controller=*/flutterPlatformViewsController,
2214  /*task_runners=*/runners,
2215  /*worker_task_runner=*/nil,
2216  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
2217 
2220  [flutterPlatformViewsController
2221  registerViewFactory:factory
2222  withId:@"MockFlutterPlatformView"
2223  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
2224  FlutterResult result = ^(id result) {
2225  };
2226  [flutterPlatformViewsController
2228  arguments:@{
2229  @"id" : @2,
2230  @"viewType" : @"MockFlutterPlatformView"
2231  }]
2232  result:result];
2233 
2234  XCTAssertNotNil(gMockPlatformView);
2235 
2236  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
2237  flutterPlatformViewsController.flutterView = flutterView;
2238  // Create embedded view params
2239  flutter::MutatorsStack stack;
2240  // Layer tree always pushes a screen scale factor to the stack
2241  flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
2242  flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
2243  stack.PushTransform(screenScaleMatrix);
2244  // Push a clip rrect
2245  flutter::DlRoundRect rrect =
2246  flutter::DlRoundRect::MakeRectXY(flutter::DlRect::MakeXYWH(2, 2, 6, 6), 1, 1);
2247  stack.PushClipRRect(rrect);
2248 
2249  auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
2250  flutter::ToSkMatrix(screenScaleMatrix), SkSize::Make(10, 10), stack);
2251 
2252  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
2253  withParams:std::move(embeddedViewParams)];
2254  [flutterPlatformViewsController
2255  compositeView:2
2256  withParams:[flutterPlatformViewsController compositionParamsForView:2]];
2257 
2258  gMockPlatformView.backgroundColor = UIColor.redColor;
2259  XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]);
2260  ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
2261  [flutterView addSubview:childClippingView];
2262 
2263  [flutterView setNeedsLayout];
2264  [flutterView layoutIfNeeded];
2265 
2266  /*
2267  ClippingMask outterClipping
2268  2 3 4 5 6 7 2 3 4 5 6 7
2269  2 / - - - - \ 2 + - - - - +
2270  3 | | 3 | |
2271  4 | | 4 | |
2272  5 | | 5 | |
2273  6 | | 6 | |
2274  7 \ - - - - / 7 + - - - - +
2275 
2276  innerClipping1 innerClipping2
2277  2 3 4 5 6 7 2 3 4 5 6 7
2278  2 + - - + 2
2279  3 | | 3 + - - - - +
2280  4 | | 4 | |
2281  5 | | 5 | |
2282  6 | | 6 + - - - - +
2283  7 + - - + 7
2284  */
2285  CGRect innerClipping1 = CGRectMake(3, 2, 4, 6);
2286  CGRect innerClipping2 = CGRectMake(2, 3, 6, 4);
2287  CGRect outterClipping = CGRectMake(2, 2, 6, 6);
2288  for (int i = 0; i < 10; i++) {
2289  for (int j = 0; j < 10; j++) {
2290  CGPoint point = CGPointMake(i, j);
2291  int alpha = [self alphaOfPoint:CGPointMake(i, j) onView:flutterView];
2292  if (CGRectContainsPoint(innerClipping1, point) ||
2293  CGRectContainsPoint(innerClipping2, point)) {
2294  // Pixels inside either of the 2 inner clippings should be fully opaque.
2295  XCTAssertEqual(alpha, 255);
2296  } else if (CGRectContainsPoint(outterClipping, point)) {
2297  // Corner pixels (i.e. (2, 2), (2, 7), (7, 2) and (7, 7)) should be partially transparent.
2298  XCTAssert(0 < alpha && alpha < 255);
2299  } else {
2300  // Pixels outside outterClipping should be fully transparent.
2301  XCTAssertEqual(alpha, 0);
2302  }
2303  }
2304  }
2305 }
2306 
2307 - (void)testClipRRect_multipleClips {
2308  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
2309 
2310  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
2311  /*platform=*/GetDefaultTaskRunner(),
2312  /*raster=*/GetDefaultTaskRunner(),
2313  /*ui=*/GetDefaultTaskRunner(),
2314  /*io=*/GetDefaultTaskRunner());
2315  FlutterPlatformViewsController* flutterPlatformViewsController =
2316  [[FlutterPlatformViewsController alloc] init];
2317  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
2318  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2319  /*delegate=*/mock_delegate,
2320  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
2321  /*platform_views_controller=*/flutterPlatformViewsController,
2322  /*task_runners=*/runners,
2323  /*worker_task_runner=*/nil,
2324  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
2325 
2328  [flutterPlatformViewsController
2329  registerViewFactory:factory
2330  withId:@"MockFlutterPlatformView"
2331  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
2332  FlutterResult result = ^(id result) {
2333  };
2334  [flutterPlatformViewsController
2336  arguments:@{
2337  @"id" : @2,
2338  @"viewType" : @"MockFlutterPlatformView"
2339  }]
2340  result:result];
2341 
2342  XCTAssertNotNil(gMockPlatformView);
2343 
2344  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
2345  flutterPlatformViewsController.flutterView = flutterView;
2346  // Create embedded view params
2347  flutter::MutatorsStack stack;
2348  // Layer tree always pushes a screen scale factor to the stack
2349  flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
2350  flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
2351  stack.PushTransform(screenScaleMatrix);
2352  // Push a clip rrect
2353  flutter::DlRoundRect rrect =
2354  flutter::DlRoundRect::MakeRectXY(flutter::DlRect::MakeXYWH(2, 2, 6, 6), 1, 1);
2355  stack.PushClipRRect(rrect);
2356  // Push a clip rect
2357  flutter::DlRect rect = flutter::DlRect::MakeXYWH(4, 2, 6, 6);
2358  stack.PushClipRect(rect);
2359 
2360  auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
2361  flutter::ToSkMatrix(screenScaleMatrix), SkSize::Make(10, 10), stack);
2362 
2363  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
2364  withParams:std::move(embeddedViewParams)];
2365  [flutterPlatformViewsController
2366  compositeView:2
2367  withParams:[flutterPlatformViewsController compositionParamsForView:2]];
2368 
2369  gMockPlatformView.backgroundColor = UIColor.redColor;
2370  XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]);
2371  ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
2372  [flutterView addSubview:childClippingView];
2373 
2374  [flutterView setNeedsLayout];
2375  [flutterView layoutIfNeeded];
2376 
2377  /*
2378  clip 1 clip 2
2379  2 3 4 5 6 7 8 9 2 3 4 5 6 7 8 9
2380  2 / - - - - \ 2 + - - - - +
2381  3 | | 3 | |
2382  4 | | 4 | |
2383  5 | | 5 | |
2384  6 | | 6 | |
2385  7 \ - - - - / 7 + - - - - +
2386 
2387  Result should be the intersection of 2 clips
2388  2 3 4 5 6 7 8 9
2389  2 + - - \
2390  3 | |
2391  4 | |
2392  5 | |
2393  6 | |
2394  7 + - - /
2395  */
2396  CGRect clipping = CGRectMake(4, 2, 4, 6);
2397  for (int i = 0; i < 10; i++) {
2398  for (int j = 0; j < 10; j++) {
2399  CGPoint point = CGPointMake(i, j);
2400  int alpha = [self alphaOfPoint:CGPointMake(i, j) onView:flutterView];
2401  if (i == 7 && (j == 2 || j == 7)) {
2402  // Upper and lower right corners should be partially transparent.
2403  XCTAssert(0 < alpha && alpha < 255);
2404  } else if (
2405  // left
2406  (i == 4 && j >= 2 && j <= 7) ||
2407  // right
2408  (i == 7 && j >= 2 && j <= 7) ||
2409  // top
2410  (j == 2 && i >= 4 && i <= 7) ||
2411  // bottom
2412  (j == 7 && i >= 4 && i <= 7)) {
2413  // Since we are falling back to software rendering for this case
2414  // The edge pixels can be anti-aliased, so it may not be fully opaque.
2415  XCTAssert(alpha > 127);
2416  } else if ((i == 3 && j >= 1 && j <= 8) || (i == 8 && j >= 1 && j <= 8) ||
2417  (j == 1 && i >= 3 && i <= 8) || (j == 8 && i >= 3 && i <= 8)) {
2418  // Since we are falling back to software rendering for this case
2419  // The edge pixels can be anti-aliased, so it may not be fully transparent.
2420  XCTAssert(alpha < 127);
2421  } else if (CGRectContainsPoint(clipping, point)) {
2422  // Other pixels inside clipping should be fully opaque.
2423  XCTAssertEqual(alpha, 255);
2424  } else {
2425  // Pixels outside clipping should be fully transparent.
2426  XCTAssertEqual(alpha, 0);
2427  }
2428  }
2429  }
2430 }
2431 
2432 - (void)testClipPath {
2433  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
2434 
2435  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
2436  /*platform=*/GetDefaultTaskRunner(),
2437  /*raster=*/GetDefaultTaskRunner(),
2438  /*ui=*/GetDefaultTaskRunner(),
2439  /*io=*/GetDefaultTaskRunner());
2440  FlutterPlatformViewsController* flutterPlatformViewsController =
2441  [[FlutterPlatformViewsController alloc] init];
2442  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
2443  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2444  /*delegate=*/mock_delegate,
2445  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
2446  /*platform_views_controller=*/flutterPlatformViewsController,
2447  /*task_runners=*/runners,
2448  /*worker_task_runner=*/nil,
2449  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
2450 
2453  [flutterPlatformViewsController
2454  registerViewFactory:factory
2455  withId:@"MockFlutterPlatformView"
2456  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
2457  FlutterResult result = ^(id result) {
2458  };
2459  [flutterPlatformViewsController
2461  arguments:@{
2462  @"id" : @2,
2463  @"viewType" : @"MockFlutterPlatformView"
2464  }]
2465  result:result];
2466 
2467  XCTAssertNotNil(gMockPlatformView);
2468 
2469  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
2470  flutterPlatformViewsController.flutterView = flutterView;
2471  // Create embedded view params
2472  flutter::MutatorsStack stack;
2473  // Layer tree always pushes a screen scale factor to the stack
2474  flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
2475  flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
2476  stack.PushTransform(screenScaleMatrix);
2477  // Push a clip path
2478  flutter::DlPath path =
2479  flutter::DlPath::MakeRoundRectXY(flutter::DlRect::MakeXYWH(2, 2, 6, 6), 1, 1);
2480  stack.PushClipPath(path);
2481 
2482  auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
2483  flutter::ToSkMatrix(screenScaleMatrix), SkSize::Make(10, 10), stack);
2484 
2485  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
2486  withParams:std::move(embeddedViewParams)];
2487  [flutterPlatformViewsController
2488  compositeView:2
2489  withParams:[flutterPlatformViewsController compositionParamsForView:2]];
2490 
2491  gMockPlatformView.backgroundColor = UIColor.redColor;
2492  XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]);
2493  ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
2494  [flutterView addSubview:childClippingView];
2495 
2496  [flutterView setNeedsLayout];
2497  [flutterView layoutIfNeeded];
2498 
2499  /*
2500  ClippingMask outterClipping
2501  2 3 4 5 6 7 2 3 4 5 6 7
2502  2 / - - - - \ 2 + - - - - +
2503  3 | | 3 | |
2504  4 | | 4 | |
2505  5 | | 5 | |
2506  6 | | 6 | |
2507  7 \ - - - - / 7 + - - - - +
2508 
2509  innerClipping1 innerClipping2
2510  2 3 4 5 6 7 2 3 4 5 6 7
2511  2 + - - + 2
2512  3 | | 3 + - - - - +
2513  4 | | 4 | |
2514  5 | | 5 | |
2515  6 | | 6 + - - - - +
2516  7 + - - + 7
2517  */
2518  CGRect innerClipping1 = CGRectMake(3, 2, 4, 6);
2519  CGRect innerClipping2 = CGRectMake(2, 3, 6, 4);
2520  CGRect outterClipping = CGRectMake(2, 2, 6, 6);
2521  for (int i = 0; i < 10; i++) {
2522  for (int j = 0; j < 10; j++) {
2523  CGPoint point = CGPointMake(i, j);
2524  int alpha = [self alphaOfPoint:CGPointMake(i, j) onView:flutterView];
2525  if (CGRectContainsPoint(innerClipping1, point) ||
2526  CGRectContainsPoint(innerClipping2, point)) {
2527  // Pixels inside either of the 2 inner clippings should be fully opaque.
2528  XCTAssertEqual(alpha, 255);
2529  } else if (CGRectContainsPoint(outterClipping, point)) {
2530  // Corner pixels (i.e. (2, 2), (2, 7), (7, 2) and (7, 7)) should be partially transparent.
2531  XCTAssert(0 < alpha && alpha < 255);
2532  } else {
2533  // Pixels outside outterClipping should be fully transparent.
2534  XCTAssertEqual(alpha, 0);
2535  }
2536  }
2537  }
2538 }
2539 
2540 - (void)testClipPath_multipleClips {
2541  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
2542 
2543  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
2544  /*platform=*/GetDefaultTaskRunner(),
2545  /*raster=*/GetDefaultTaskRunner(),
2546  /*ui=*/GetDefaultTaskRunner(),
2547  /*io=*/GetDefaultTaskRunner());
2548  FlutterPlatformViewsController* flutterPlatformViewsController =
2549  [[FlutterPlatformViewsController alloc] init];
2550  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
2551  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2552  /*delegate=*/mock_delegate,
2553  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
2554  /*platform_views_controller=*/flutterPlatformViewsController,
2555  /*task_runners=*/runners,
2556  /*worker_task_runner=*/nil,
2557  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
2558 
2561  [flutterPlatformViewsController
2562  registerViewFactory:factory
2563  withId:@"MockFlutterPlatformView"
2564  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
2565  FlutterResult result = ^(id result) {
2566  };
2567  [flutterPlatformViewsController
2569  arguments:@{
2570  @"id" : @2,
2571  @"viewType" : @"MockFlutterPlatformView"
2572  }]
2573  result:result];
2574 
2575  XCTAssertNotNil(gMockPlatformView);
2576 
2577  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
2578  flutterPlatformViewsController.flutterView = flutterView;
2579  // Create embedded view params
2580  flutter::MutatorsStack stack;
2581  // Layer tree always pushes a screen scale factor to the stack
2582  flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
2583  flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
2584  stack.PushTransform(screenScaleMatrix);
2585  // Push a clip path
2586  flutter::DlPath path =
2587  flutter::DlPath::MakeRoundRectXY(flutter::DlRect::MakeXYWH(2, 2, 6, 6), 1, 1);
2588  stack.PushClipPath(path);
2589  // Push a clip rect
2590  flutter::DlRect rect = flutter::DlRect::MakeXYWH(4, 2, 6, 6);
2591  stack.PushClipRect(rect);
2592 
2593  auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
2594  flutter::ToSkMatrix(screenScaleMatrix), SkSize::Make(10, 10), stack);
2595 
2596  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
2597  withParams:std::move(embeddedViewParams)];
2598  [flutterPlatformViewsController
2599  compositeView:2
2600  withParams:[flutterPlatformViewsController compositionParamsForView:2]];
2601 
2602  gMockPlatformView.backgroundColor = UIColor.redColor;
2603  XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]);
2604  ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
2605  [flutterView addSubview:childClippingView];
2606 
2607  [flutterView setNeedsLayout];
2608  [flutterView layoutIfNeeded];
2609 
2610  /*
2611  clip 1 clip 2
2612  2 3 4 5 6 7 8 9 2 3 4 5 6 7 8 9
2613  2 / - - - - \ 2 + - - - - +
2614  3 | | 3 | |
2615  4 | | 4 | |
2616  5 | | 5 | |
2617  6 | | 6 | |
2618  7 \ - - - - / 7 + - - - - +
2619 
2620  Result should be the intersection of 2 clips
2621  2 3 4 5 6 7 8 9
2622  2 + - - \
2623  3 | |
2624  4 | |
2625  5 | |
2626  6 | |
2627  7 + - - /
2628  */
2629  CGRect clipping = CGRectMake(4, 2, 4, 6);
2630  for (int i = 0; i < 10; i++) {
2631  for (int j = 0; j < 10; j++) {
2632  CGPoint point = CGPointMake(i, j);
2633  int alpha = [self alphaOfPoint:CGPointMake(i, j) onView:flutterView];
2634  if (i == 7 && (j == 2 || j == 7)) {
2635  // Upper and lower right corners should be partially transparent.
2636  XCTAssert(0 < alpha && alpha < 255);
2637  } else if (
2638  // left
2639  (i == 4 && j >= 2 && j <= 7) ||
2640  // right
2641  (i == 7 && j >= 2 && j <= 7) ||
2642  // top
2643  (j == 2 && i >= 4 && i <= 7) ||
2644  // bottom
2645  (j == 7 && i >= 4 && i <= 7)) {
2646  // Since we are falling back to software rendering for this case
2647  // The edge pixels can be anti-aliased, so it may not be fully opaque.
2648  XCTAssert(alpha > 127);
2649  } else if ((i == 3 && j >= 1 && j <= 8) || (i == 8 && j >= 1 && j <= 8) ||
2650  (j == 1 && i >= 3 && i <= 8) || (j == 8 && i >= 3 && i <= 8)) {
2651  // Since we are falling back to software rendering for this case
2652  // The edge pixels can be anti-aliased, so it may not be fully transparent.
2653  XCTAssert(alpha < 127);
2654  } else if (CGRectContainsPoint(clipping, point)) {
2655  // Other pixels inside clipping should be fully opaque.
2656  XCTAssertEqual(alpha, 255);
2657  } else {
2658  // Pixels outside clipping should be fully transparent.
2659  XCTAssertEqual(alpha, 0);
2660  }
2661  }
2662  }
2663 }
2664 
2665 - (void)testSetFlutterViewControllerAfterCreateCanStillDispatchTouchEvents {
2666  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
2667 
2668  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
2669  /*platform=*/GetDefaultTaskRunner(),
2670  /*raster=*/GetDefaultTaskRunner(),
2671  /*ui=*/GetDefaultTaskRunner(),
2672  /*io=*/GetDefaultTaskRunner());
2673  FlutterPlatformViewsController* flutterPlatformViewsController =
2674  [[FlutterPlatformViewsController alloc] init];
2675  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
2676  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2677  /*delegate=*/mock_delegate,
2678  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
2679  /*platform_views_controller=*/flutterPlatformViewsController,
2680  /*task_runners=*/runners,
2681  /*worker_task_runner=*/nil,
2682  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
2683 
2686  [flutterPlatformViewsController
2687  registerViewFactory:factory
2688  withId:@"MockFlutterPlatformView"
2689  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
2690  FlutterResult result = ^(id result) {
2691  };
2692  [flutterPlatformViewsController
2694  arguments:@{
2695  @"id" : @2,
2696  @"viewType" : @"MockFlutterPlatformView"
2697  }]
2698  result:result];
2699 
2700  XCTAssertNotNil(gMockPlatformView);
2701 
2702  // Find touch inteceptor view
2703  UIView* touchInteceptorView = gMockPlatformView;
2704  while (touchInteceptorView != nil &&
2705  ![touchInteceptorView isKindOfClass:[FlutterTouchInterceptingView class]]) {
2706  touchInteceptorView = touchInteceptorView.superview;
2707  }
2708  XCTAssertNotNil(touchInteceptorView);
2709 
2710  // Find ForwardGestureRecognizer
2711  UIGestureRecognizer* forwardGectureRecognizer = nil;
2712  for (UIGestureRecognizer* gestureRecognizer in touchInteceptorView.gestureRecognizers) {
2713  if ([gestureRecognizer isKindOfClass:[ForwardingGestureRecognizer class]]) {
2714  forwardGectureRecognizer = gestureRecognizer;
2715  break;
2716  }
2717  }
2718 
2719  // Before setting flutter view controller, events are not dispatched.
2720  NSSet* touches1 = [[NSSet alloc] init];
2721  id event1 = OCMClassMock([UIEvent class]);
2722  id flutterViewController = OCMClassMock([FlutterViewController class]);
2723  [forwardGectureRecognizer touchesBegan:touches1 withEvent:event1];
2724  OCMReject([flutterViewController touchesBegan:touches1 withEvent:event1]);
2725 
2726  // Set flutter view controller allows events to be dispatched.
2727  NSSet* touches2 = [[NSSet alloc] init];
2728  id event2 = OCMClassMock([UIEvent class]);
2729  flutterPlatformViewsController.flutterViewController = flutterViewController;
2730  [forwardGectureRecognizer touchesBegan:touches2 withEvent:event2];
2731  OCMVerify([flutterViewController touchesBegan:touches2 withEvent:event2]);
2732 }
2733 
2734 - (void)testSetFlutterViewControllerInTheMiddleOfTouchEventShouldStillAllowGesturesToBeHandled {
2735  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
2736 
2737  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
2738  /*platform=*/GetDefaultTaskRunner(),
2739  /*raster=*/GetDefaultTaskRunner(),
2740  /*ui=*/GetDefaultTaskRunner(),
2741  /*io=*/GetDefaultTaskRunner());
2742  FlutterPlatformViewsController* flutterPlatformViewsController =
2743  [[FlutterPlatformViewsController alloc] init];
2744  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
2745  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2746  /*delegate=*/mock_delegate,
2747  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
2748  /*platform_views_controller=*/flutterPlatformViewsController,
2749  /*task_runners=*/runners,
2750  /*worker_task_runner=*/nil,
2751  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
2752 
2755  [flutterPlatformViewsController
2756  registerViewFactory:factory
2757  withId:@"MockFlutterPlatformView"
2758  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
2759  FlutterResult result = ^(id result) {
2760  };
2761  [flutterPlatformViewsController
2763  arguments:@{
2764  @"id" : @2,
2765  @"viewType" : @"MockFlutterPlatformView"
2766  }]
2767  result:result];
2768 
2769  XCTAssertNotNil(gMockPlatformView);
2770 
2771  // Find touch inteceptor view
2772  UIView* touchInteceptorView = gMockPlatformView;
2773  while (touchInteceptorView != nil &&
2774  ![touchInteceptorView isKindOfClass:[FlutterTouchInterceptingView class]]) {
2775  touchInteceptorView = touchInteceptorView.superview;
2776  }
2777  XCTAssertNotNil(touchInteceptorView);
2778 
2779  // Find ForwardGestureRecognizer
2780  UIGestureRecognizer* forwardGectureRecognizer = nil;
2781  for (UIGestureRecognizer* gestureRecognizer in touchInteceptorView.gestureRecognizers) {
2782  if ([gestureRecognizer isKindOfClass:[ForwardingGestureRecognizer class]]) {
2783  forwardGectureRecognizer = gestureRecognizer;
2784  break;
2785  }
2786  }
2787  id flutterViewController = OCMClassMock([FlutterViewController class]);
2788  {
2789  // ***** Sequence 1, finishing touch event with touchEnded ***** //
2790  flutterPlatformViewsController.flutterViewController = flutterViewController;
2791 
2792  NSSet* touches1 = [[NSSet alloc] init];
2793  id event1 = OCMClassMock([UIEvent class]);
2794  [forwardGectureRecognizer touchesBegan:touches1 withEvent:event1];
2795  OCMVerify([flutterViewController touchesBegan:touches1 withEvent:event1]);
2796 
2797  flutterPlatformViewsController.flutterViewController = nil;
2798 
2799  // Allow the touch events to finish
2800  NSSet* touches2 = [[NSSet alloc] init];
2801  id event2 = OCMClassMock([UIEvent class]);
2802  [forwardGectureRecognizer touchesMoved:touches2 withEvent:event2];
2803  OCMVerify([flutterViewController touchesMoved:touches2 withEvent:event2]);
2804 
2805  NSSet* touches3 = [[NSSet alloc] init];
2806  id event3 = OCMClassMock([UIEvent class]);
2807  [forwardGectureRecognizer touchesEnded:touches3 withEvent:event3];
2808  OCMVerify([flutterViewController touchesEnded:touches3 withEvent:event3]);
2809 
2810  // Now the 2nd touch sequence should not be allowed.
2811  NSSet* touches4 = [[NSSet alloc] init];
2812  id event4 = OCMClassMock([UIEvent class]);
2813  [forwardGectureRecognizer touchesBegan:touches4 withEvent:event4];
2814  OCMReject([flutterViewController touchesBegan:touches4 withEvent:event4]);
2815 
2816  NSSet* touches5 = [[NSSet alloc] init];
2817  id event5 = OCMClassMock([UIEvent class]);
2818  [forwardGectureRecognizer touchesEnded:touches5 withEvent:event5];
2819  OCMReject([flutterViewController touchesEnded:touches5 withEvent:event5]);
2820  }
2821 
2822  {
2823  // ***** Sequence 2, finishing touch event with touchCancelled ***** //
2824  flutterPlatformViewsController.flutterViewController = flutterViewController;
2825 
2826  NSSet* touches1 = [[NSSet alloc] init];
2827  id event1 = OCMClassMock([UIEvent class]);
2828  [forwardGectureRecognizer touchesBegan:touches1 withEvent:event1];
2829  OCMVerify([flutterViewController touchesBegan:touches1 withEvent:event1]);
2830 
2831  flutterPlatformViewsController.flutterViewController = nil;
2832 
2833  // Allow the touch events to finish
2834  NSSet* touches2 = [[NSSet alloc] init];
2835  id event2 = OCMClassMock([UIEvent class]);
2836  [forwardGectureRecognizer touchesMoved:touches2 withEvent:event2];
2837  OCMVerify([flutterViewController touchesMoved:touches2 withEvent:event2]);
2838 
2839  NSSet* touches3 = [[NSSet alloc] init];
2840  id event3 = OCMClassMock([UIEvent class]);
2841  [forwardGectureRecognizer touchesCancelled:touches3 withEvent:event3];
2842  OCMVerify([flutterViewController forceTouchesCancelled:touches3]);
2843 
2844  // Now the 2nd touch sequence should not be allowed.
2845  NSSet* touches4 = [[NSSet alloc] init];
2846  id event4 = OCMClassMock([UIEvent class]);
2847  [forwardGectureRecognizer touchesBegan:touches4 withEvent:event4];
2848  OCMReject([flutterViewController touchesBegan:touches4 withEvent:event4]);
2849 
2850  NSSet* touches5 = [[NSSet alloc] init];
2851  id event5 = OCMClassMock([UIEvent class]);
2852  [forwardGectureRecognizer touchesEnded:touches5 withEvent:event5];
2853  OCMReject([flutterViewController touchesEnded:touches5 withEvent:event5]);
2854  }
2855 
2856  [flutterPlatformViewsController reset];
2857 }
2858 
2859 - (void)
2860  testSetFlutterViewControllerInTheMiddleOfTouchEventAllowsTheNewControllerToHandleSecondTouchSequence {
2861  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
2862 
2863  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
2864  /*platform=*/GetDefaultTaskRunner(),
2865  /*raster=*/GetDefaultTaskRunner(),
2866  /*ui=*/GetDefaultTaskRunner(),
2867  /*io=*/GetDefaultTaskRunner());
2868  FlutterPlatformViewsController* flutterPlatformViewsController =
2869  [[FlutterPlatformViewsController alloc] init];
2870  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
2871  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2872  /*delegate=*/mock_delegate,
2873  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
2874  /*platform_views_controller=*/flutterPlatformViewsController,
2875  /*task_runners=*/runners,
2876  /*worker_task_runner=*/nil,
2877  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
2878 
2881  [flutterPlatformViewsController
2882  registerViewFactory:factory
2883  withId:@"MockFlutterPlatformView"
2884  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
2885  FlutterResult result = ^(id result) {
2886  };
2887  [flutterPlatformViewsController
2889  arguments:@{
2890  @"id" : @2,
2891  @"viewType" : @"MockFlutterPlatformView"
2892  }]
2893  result:result];
2894 
2895  XCTAssertNotNil(gMockPlatformView);
2896 
2897  // Find touch inteceptor view
2898  UIView* touchInteceptorView = gMockPlatformView;
2899  while (touchInteceptorView != nil &&
2900  ![touchInteceptorView isKindOfClass:[FlutterTouchInterceptingView class]]) {
2901  touchInteceptorView = touchInteceptorView.superview;
2902  }
2903  XCTAssertNotNil(touchInteceptorView);
2904 
2905  // Find ForwardGestureRecognizer
2906  UIGestureRecognizer* forwardGectureRecognizer = nil;
2907  for (UIGestureRecognizer* gestureRecognizer in touchInteceptorView.gestureRecognizers) {
2908  if ([gestureRecognizer isKindOfClass:[ForwardingGestureRecognizer class]]) {
2909  forwardGectureRecognizer = gestureRecognizer;
2910  break;
2911  }
2912  }
2913  id flutterViewController = OCMClassMock([FlutterViewController class]);
2914  flutterPlatformViewsController.flutterViewController = flutterViewController;
2915 
2916  // The touches in this sequence requires 1 touch object, we always create the NSSet with one item.
2917  NSSet* touches1 = [NSSet setWithObject:@1];
2918  id event1 = OCMClassMock([UIEvent class]);
2919  [forwardGectureRecognizer touchesBegan:touches1 withEvent:event1];
2920  OCMVerify([flutterViewController touchesBegan:touches1 withEvent:event1]);
2921 
2922  FlutterViewController* flutterViewController2 = OCMClassMock([FlutterViewController class]);
2923  flutterPlatformViewsController.flutterViewController = flutterViewController2;
2924 
2925  // Touch events should still send to the old FlutterViewController if FlutterViewController
2926  // is updated in between.
2927  NSSet* touches2 = [NSSet setWithObject:@1];
2928  id event2 = OCMClassMock([UIEvent class]);
2929  [forwardGectureRecognizer touchesBegan:touches2 withEvent:event2];
2930  OCMVerify([flutterViewController touchesBegan:touches2 withEvent:event2]);
2931  OCMReject([flutterViewController2 touchesBegan:touches2 withEvent:event2]);
2932 
2933  NSSet* touches3 = [NSSet setWithObject:@1];
2934  id event3 = OCMClassMock([UIEvent class]);
2935  [forwardGectureRecognizer touchesMoved:touches3 withEvent:event3];
2936  OCMVerify([flutterViewController touchesMoved:touches3 withEvent:event3]);
2937  OCMReject([flutterViewController2 touchesMoved:touches3 withEvent:event3]);
2938 
2939  NSSet* touches4 = [NSSet setWithObject:@1];
2940  id event4 = OCMClassMock([UIEvent class]);
2941  [forwardGectureRecognizer touchesEnded:touches4 withEvent:event4];
2942  OCMVerify([flutterViewController touchesEnded:touches4 withEvent:event4]);
2943  OCMReject([flutterViewController2 touchesEnded:touches4 withEvent:event4]);
2944 
2945  NSSet* touches5 = [NSSet setWithObject:@1];
2946  id event5 = OCMClassMock([UIEvent class]);
2947  [forwardGectureRecognizer touchesEnded:touches5 withEvent:event5];
2948  OCMVerify([flutterViewController touchesEnded:touches5 withEvent:event5]);
2949  OCMReject([flutterViewController2 touchesEnded:touches5 withEvent:event5]);
2950 
2951  // Now the 2nd touch sequence should go to the new FlutterViewController
2952 
2953  NSSet* touches6 = [NSSet setWithObject:@1];
2954  id event6 = OCMClassMock([UIEvent class]);
2955  [forwardGectureRecognizer touchesBegan:touches6 withEvent:event6];
2956  OCMVerify([flutterViewController2 touchesBegan:touches6 withEvent:event6]);
2957  OCMReject([flutterViewController touchesBegan:touches6 withEvent:event6]);
2958 
2959  // Allow the touch events to finish
2960  NSSet* touches7 = [NSSet setWithObject:@1];
2961  id event7 = OCMClassMock([UIEvent class]);
2962  [forwardGectureRecognizer touchesMoved:touches7 withEvent:event7];
2963  OCMVerify([flutterViewController2 touchesMoved:touches7 withEvent:event7]);
2964  OCMReject([flutterViewController touchesMoved:touches7 withEvent:event7]);
2965 
2966  NSSet* touches8 = [NSSet setWithObject:@1];
2967  id event8 = OCMClassMock([UIEvent class]);
2968  [forwardGectureRecognizer touchesEnded:touches8 withEvent:event8];
2969  OCMVerify([flutterViewController2 touchesEnded:touches8 withEvent:event8]);
2970  OCMReject([flutterViewController touchesEnded:touches8 withEvent:event8]);
2971 
2972  [flutterPlatformViewsController reset];
2973 }
2974 
2975 - (void)testFlutterPlatformViewTouchesCancelledEventAreForcedToBeCancelled {
2976  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
2977 
2978  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
2979  /*platform=*/GetDefaultTaskRunner(),
2980  /*raster=*/GetDefaultTaskRunner(),
2981  /*ui=*/GetDefaultTaskRunner(),
2982  /*io=*/GetDefaultTaskRunner());
2983  FlutterPlatformViewsController* flutterPlatformViewsController =
2984  [[FlutterPlatformViewsController alloc] init];
2985  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
2986  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2987  /*delegate=*/mock_delegate,
2988  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
2989  /*platform_views_controller=*/flutterPlatformViewsController,
2990  /*task_runners=*/runners,
2991  /*worker_task_runner=*/nil,
2992  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
2993 
2996  [flutterPlatformViewsController
2997  registerViewFactory:factory
2998  withId:@"MockFlutterPlatformView"
2999  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
3000  FlutterResult result = ^(id result) {
3001  };
3002  [flutterPlatformViewsController
3004  arguments:@{
3005  @"id" : @2,
3006  @"viewType" : @"MockFlutterPlatformView"
3007  }]
3008  result:result];
3009 
3010  XCTAssertNotNil(gMockPlatformView);
3011 
3012  // Find touch inteceptor view
3013  UIView* touchInteceptorView = gMockPlatformView;
3014  while (touchInteceptorView != nil &&
3015  ![touchInteceptorView isKindOfClass:[FlutterTouchInterceptingView class]]) {
3016  touchInteceptorView = touchInteceptorView.superview;
3017  }
3018  XCTAssertNotNil(touchInteceptorView);
3019 
3020  // Find ForwardGestureRecognizer
3021  UIGestureRecognizer* forwardGectureRecognizer = nil;
3022  for (UIGestureRecognizer* gestureRecognizer in touchInteceptorView.gestureRecognizers) {
3023  if ([gestureRecognizer isKindOfClass:[ForwardingGestureRecognizer class]]) {
3024  forwardGectureRecognizer = gestureRecognizer;
3025  break;
3026  }
3027  }
3028  id flutterViewController = OCMClassMock([FlutterViewController class]);
3029  flutterPlatformViewsController.flutterViewController = flutterViewController;
3030 
3031  NSSet* touches1 = [NSSet setWithObject:@1];
3032  id event1 = OCMClassMock([UIEvent class]);
3033  [forwardGectureRecognizer touchesBegan:touches1 withEvent:event1];
3034 
3035  [forwardGectureRecognizer touchesCancelled:touches1 withEvent:event1];
3036  OCMVerify([flutterViewController forceTouchesCancelled:touches1]);
3037 
3038  [flutterPlatformViewsController reset];
3039 }
3040 
3041 - (void)testFlutterPlatformViewTouchesEndedOrTouchesCancelledEventDoesNotFailTheGestureRecognizer {
3042  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
3043 
3044  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
3045  /*platform=*/GetDefaultTaskRunner(),
3046  /*raster=*/GetDefaultTaskRunner(),
3047  /*ui=*/GetDefaultTaskRunner(),
3048  /*io=*/GetDefaultTaskRunner());
3049  FlutterPlatformViewsController* flutterPlatformViewsController =
3050  [[FlutterPlatformViewsController alloc] init];
3051  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
3052  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
3053  /*delegate=*/mock_delegate,
3054  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
3055  /*platform_views_controller=*/flutterPlatformViewsController,
3056  /*task_runners=*/runners,
3057  /*worker_task_runner=*/nil,
3058  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
3059 
3062  [flutterPlatformViewsController
3063  registerViewFactory:factory
3064  withId:@"MockFlutterPlatformView"
3065  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
3066  FlutterResult result = ^(id result) {
3067  };
3068  [flutterPlatformViewsController
3070  arguments:@{
3071  @"id" : @2,
3072  @"viewType" : @"MockFlutterPlatformView"
3073  }]
3074  result:result];
3075 
3076  XCTAssertNotNil(gMockPlatformView);
3077 
3078  // Find touch inteceptor view
3079  UIView* touchInteceptorView = gMockPlatformView;
3080  while (touchInteceptorView != nil &&
3081  ![touchInteceptorView isKindOfClass:[FlutterTouchInterceptingView class]]) {
3082  touchInteceptorView = touchInteceptorView.superview;
3083  }
3084  XCTAssertNotNil(touchInteceptorView);
3085 
3086  // Find ForwardGestureRecognizer
3087  __block UIGestureRecognizer* forwardGestureRecognizer = nil;
3088  for (UIGestureRecognizer* gestureRecognizer in touchInteceptorView.gestureRecognizers) {
3089  if ([gestureRecognizer isKindOfClass:[ForwardingGestureRecognizer class]]) {
3090  forwardGestureRecognizer = gestureRecognizer;
3091  break;
3092  }
3093  }
3094  id flutterViewController = OCMClassMock([FlutterViewController class]);
3095  flutterPlatformViewsController.flutterViewController = flutterViewController;
3096 
3097  NSSet* touches1 = [NSSet setWithObject:@1];
3098  id event1 = OCMClassMock([UIEvent class]);
3099  XCTAssert(forwardGestureRecognizer.state == UIGestureRecognizerStatePossible,
3100  @"Forwarding gesture recognizer must start with possible state.");
3101  [forwardGestureRecognizer touchesBegan:touches1 withEvent:event1];
3102  [forwardGestureRecognizer touchesEnded:touches1 withEvent:event1];
3103  XCTAssert(forwardGestureRecognizer.state == UIGestureRecognizerStateFailed,
3104  @"Forwarding gesture recognizer must end with failed state.");
3105 
3106  XCTestExpectation* touchEndedExpectation =
3107  [self expectationWithDescription:@"Wait for gesture recognizer's state change."];
3108  dispatch_async(dispatch_get_main_queue(), ^{
3109  // Re-query forward gesture recognizer since it's recreated.
3110  for (UIGestureRecognizer* gestureRecognizer in touchInteceptorView.gestureRecognizers) {
3111  if ([gestureRecognizer isKindOfClass:[ForwardingGestureRecognizer class]]) {
3112  forwardGestureRecognizer = gestureRecognizer;
3113  break;
3114  }
3115  }
3116  XCTAssert(forwardGestureRecognizer.state == UIGestureRecognizerStatePossible,
3117  @"Forwarding gesture recognizer must be reset to possible state.");
3118  [touchEndedExpectation fulfill];
3119  });
3120  [self waitForExpectationsWithTimeout:30 handler:nil];
3121 
3122  XCTAssert(forwardGestureRecognizer.state == UIGestureRecognizerStatePossible,
3123  @"Forwarding gesture recognizer must start with possible state.");
3124  [forwardGestureRecognizer touchesBegan:touches1 withEvent:event1];
3125  [forwardGestureRecognizer touchesCancelled:touches1 withEvent:event1];
3126  XCTAssert(forwardGestureRecognizer.state == UIGestureRecognizerStateFailed,
3127  @"Forwarding gesture recognizer must end with failed state.");
3128  XCTestExpectation* touchCancelledExpectation =
3129  [self expectationWithDescription:@"Wait for gesture recognizer's state change."];
3130  dispatch_async(dispatch_get_main_queue(), ^{
3131  // Re-query forward gesture recognizer since it's recreated.
3132  for (UIGestureRecognizer* gestureRecognizer in touchInteceptorView.gestureRecognizers) {
3133  if ([gestureRecognizer isKindOfClass:[ForwardingGestureRecognizer class]]) {
3134  forwardGestureRecognizer = gestureRecognizer;
3135  break;
3136  }
3137  }
3138  XCTAssert(forwardGestureRecognizer.state == UIGestureRecognizerStatePossible,
3139  @"Forwarding gesture recognizer must be reset to possible state.");
3140  [touchCancelledExpectation fulfill];
3141  });
3142  [self waitForExpectationsWithTimeout:30 handler:nil];
3143 
3144  [flutterPlatformViewsController reset];
3145 }
3146 
3147 - (void)
3148  testFlutterPlatformViewBlockGestureUnderEagerPolicyShouldRemoveAndAddBackDelayingRecognizerForWebView {
3149  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
3150 
3151  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
3152  /*platform=*/GetDefaultTaskRunner(),
3153  /*raster=*/GetDefaultTaskRunner(),
3154  /*ui=*/GetDefaultTaskRunner(),
3155  /*io=*/GetDefaultTaskRunner());
3156  FlutterPlatformViewsController* flutterPlatformViewsController =
3157  [[FlutterPlatformViewsController alloc] init];
3158  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
3159  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
3160  /*delegate=*/mock_delegate,
3161  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
3162  /*platform_views_controller=*/flutterPlatformViewsController,
3163  /*task_runners=*/runners,
3164  /*worker_task_runner=*/nil,
3165  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
3166 
3169  [flutterPlatformViewsController
3170  registerViewFactory:factory
3171  withId:@"MockWebView"
3172  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
3173  FlutterResult result = ^(id result) {
3174  };
3175  [flutterPlatformViewsController
3177  methodCallWithMethodName:@"create"
3178  arguments:@{@"id" : @2, @"viewType" : @"MockWebView"}]
3179  result:result];
3180 
3181  XCTAssertNotNil(gMockPlatformView);
3182 
3183  // Find touch inteceptor view
3184  UIView* touchInteceptorView = gMockPlatformView;
3185  while (touchInteceptorView != nil &&
3186  ![touchInteceptorView isKindOfClass:[FlutterTouchInterceptingView class]]) {
3187  touchInteceptorView = touchInteceptorView.superview;
3188  }
3189  XCTAssertNotNil(touchInteceptorView);
3190 
3191  XCTAssert(touchInteceptorView.gestureRecognizers.count == 2);
3192  UIGestureRecognizer* delayingRecognizer = touchInteceptorView.gestureRecognizers[0];
3193  UIGestureRecognizer* forwardingRecognizer = touchInteceptorView.gestureRecognizers[1];
3194 
3195  XCTAssert([delayingRecognizer isKindOfClass:[FlutterDelayingGestureRecognizer class]]);
3196  XCTAssert([forwardingRecognizer isKindOfClass:[ForwardingGestureRecognizer class]]);
3197 
3199 
3200  BOOL shouldReAddDelayingRecognizer = NO;
3201  if (@available(iOS 26.0, *)) {
3202  // TODO(hellohuanlin): find a solution for iOS 26,
3203  // https://github.com/flutter/flutter/issues/175099.
3204  } else if (@available(iOS 18.2, *)) {
3205  shouldReAddDelayingRecognizer = YES;
3206  }
3207  if (shouldReAddDelayingRecognizer) {
3208  // Since we remove and add back delayingRecognizer, it would be reordered to the last.
3209  XCTAssertEqual(touchInteceptorView.gestureRecognizers[0], forwardingRecognizer);
3210  XCTAssertEqual(touchInteceptorView.gestureRecognizers[1], delayingRecognizer);
3211  } else {
3212  XCTAssertEqual(touchInteceptorView.gestureRecognizers[0], delayingRecognizer);
3213  XCTAssertEqual(touchInteceptorView.gestureRecognizers[1], forwardingRecognizer);
3214  }
3215 }
3216 
3217 - (void)
3218  testFlutterPlatformViewBlockGestureUnderEagerPolicyShouldRemoveAndAddBackDelayingRecognizerForWrapperWebView {
3219  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
3220 
3221  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
3222  /*platform=*/GetDefaultTaskRunner(),
3223  /*raster=*/GetDefaultTaskRunner(),
3224  /*ui=*/GetDefaultTaskRunner(),
3225  /*io=*/GetDefaultTaskRunner());
3226  FlutterPlatformViewsController* flutterPlatformViewsController =
3227  [[FlutterPlatformViewsController alloc] init];
3228  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
3229  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
3230  /*delegate=*/mock_delegate,
3231  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
3232  /*platform_views_controller=*/flutterPlatformViewsController,
3233  /*task_runners=*/runners,
3234  /*worker_task_runner=*/nil,
3235  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
3236 
3239  [flutterPlatformViewsController
3240  registerViewFactory:factory
3241  withId:@"MockWrapperWebView"
3242  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
3243  FlutterResult result = ^(id result) {
3244  };
3245  [flutterPlatformViewsController
3247  methodCallWithMethodName:@"create"
3248  arguments:@{@"id" : @2, @"viewType" : @"MockWrapperWebView"}]
3249  result:result];
3250 
3251  XCTAssertNotNil(gMockPlatformView);
3252 
3253  // Find touch inteceptor view
3254  UIView* touchInteceptorView = gMockPlatformView;
3255  while (touchInteceptorView != nil &&
3256  ![touchInteceptorView isKindOfClass:[FlutterTouchInterceptingView class]]) {
3257  touchInteceptorView = touchInteceptorView.superview;
3258  }
3259  XCTAssertNotNil(touchInteceptorView);
3260 
3261  XCTAssert(touchInteceptorView.gestureRecognizers.count == 2);
3262  UIGestureRecognizer* delayingRecognizer = touchInteceptorView.gestureRecognizers[0];
3263  UIGestureRecognizer* forwardingRecognizer = touchInteceptorView.gestureRecognizers[1];
3264 
3265  XCTAssert([delayingRecognizer isKindOfClass:[FlutterDelayingGestureRecognizer class]]);
3266  XCTAssert([forwardingRecognizer isKindOfClass:[ForwardingGestureRecognizer class]]);
3267 
3269 
3270  BOOL shouldReAddDelayingRecognizer = NO;
3271  if (@available(iOS 26.0, *)) {
3272  // TODO(hellohuanlin): find a solution for iOS 26,
3273  // https://github.com/flutter/flutter/issues/175099.
3274  } else if (@available(iOS 18.2, *)) {
3275  shouldReAddDelayingRecognizer = YES;
3276  }
3277  if (shouldReAddDelayingRecognizer) {
3278  // Since we remove and add back delayingRecognizer, it would be reordered to the last.
3279  XCTAssertEqual(touchInteceptorView.gestureRecognizers[0], forwardingRecognizer);
3280  XCTAssertEqual(touchInteceptorView.gestureRecognizers[1], delayingRecognizer);
3281  } else {
3282  XCTAssertEqual(touchInteceptorView.gestureRecognizers[0], delayingRecognizer);
3283  XCTAssertEqual(touchInteceptorView.gestureRecognizers[1], forwardingRecognizer);
3284  }
3285 }
3286 
3287 - (void)
3288  testFlutterPlatformViewBlockGestureUnderEagerPolicyShouldNotRemoveAndAddBackDelayingRecognizerForNestedWrapperWebView {
3289  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
3290 
3291  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
3292  /*platform=*/GetDefaultTaskRunner(),
3293  /*raster=*/GetDefaultTaskRunner(),
3294  /*ui=*/GetDefaultTaskRunner(),
3295  /*io=*/GetDefaultTaskRunner());
3296  FlutterPlatformViewsController* flutterPlatformViewsController =
3297  [[FlutterPlatformViewsController alloc] init];
3298  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
3299  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
3300  /*delegate=*/mock_delegate,
3301  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
3302  /*platform_views_controller=*/flutterPlatformViewsController,
3303  /*task_runners=*/runners,
3304  /*worker_task_runner=*/nil,
3305  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
3306 
3309  [flutterPlatformViewsController
3310  registerViewFactory:factory
3311  withId:@"MockNestedWrapperWebView"
3312  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
3313  FlutterResult result = ^(id result) {
3314  };
3315  [flutterPlatformViewsController
3317  arguments:@{
3318  @"id" : @2,
3319  @"viewType" : @"MockNestedWrapperWebView"
3320  }]
3321  result:result];
3322 
3323  XCTAssertNotNil(gMockPlatformView);
3324 
3325  // Find touch inteceptor view
3326  UIView* touchInteceptorView = gMockPlatformView;
3327  while (touchInteceptorView != nil &&
3328  ![touchInteceptorView isKindOfClass:[FlutterTouchInterceptingView class]]) {
3329  touchInteceptorView = touchInteceptorView.superview;
3330  }
3331  XCTAssertNotNil(touchInteceptorView);
3332 
3333  XCTAssert(touchInteceptorView.gestureRecognizers.count == 2);
3334  UIGestureRecognizer* delayingRecognizer = touchInteceptorView.gestureRecognizers[0];
3335  UIGestureRecognizer* forwardingRecognizer = touchInteceptorView.gestureRecognizers[1];
3336 
3337  XCTAssert([delayingRecognizer isKindOfClass:[FlutterDelayingGestureRecognizer class]]);
3338  XCTAssert([forwardingRecognizer isKindOfClass:[ForwardingGestureRecognizer class]]);
3339 
3341 
3342  XCTAssertEqual(touchInteceptorView.gestureRecognizers[0], delayingRecognizer);
3343  XCTAssertEqual(touchInteceptorView.gestureRecognizers[1], forwardingRecognizer);
3344 }
3345 
3346 - (void)
3347  testFlutterPlatformViewBlockGestureUnderEagerPolicyShouldNotRemoveAndAddBackDelayingRecognizerForNonWebView {
3348  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
3349 
3350  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
3351  /*platform=*/GetDefaultTaskRunner(),
3352  /*raster=*/GetDefaultTaskRunner(),
3353  /*ui=*/GetDefaultTaskRunner(),
3354  /*io=*/GetDefaultTaskRunner());
3355  FlutterPlatformViewsController* flutterPlatformViewsController =
3356  [[FlutterPlatformViewsController alloc] init];
3357  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
3358  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
3359  /*delegate=*/mock_delegate,
3360  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
3361  /*platform_views_controller=*/flutterPlatformViewsController,
3362  /*task_runners=*/runners,
3363  /*worker_task_runner=*/nil,
3364  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
3365 
3368  [flutterPlatformViewsController
3369  registerViewFactory:factory
3370  withId:@"MockFlutterPlatformView"
3371  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
3372  FlutterResult result = ^(id result) {
3373  };
3374  [flutterPlatformViewsController
3376  arguments:@{
3377  @"id" : @2,
3378  @"viewType" : @"MockFlutterPlatformView"
3379  }]
3380  result:result];
3381 
3382  XCTAssertNotNil(gMockPlatformView);
3383 
3384  // Find touch inteceptor view
3385  UIView* touchInteceptorView = gMockPlatformView;
3386  while (touchInteceptorView != nil &&
3387  ![touchInteceptorView isKindOfClass:[FlutterTouchInterceptingView class]]) {
3388  touchInteceptorView = touchInteceptorView.superview;
3389  }
3390  XCTAssertNotNil(touchInteceptorView);
3391 
3392  XCTAssert(touchInteceptorView.gestureRecognizers.count == 2);
3393  UIGestureRecognizer* delayingRecognizer = touchInteceptorView.gestureRecognizers[0];
3394  UIGestureRecognizer* forwardingRecognizer = touchInteceptorView.gestureRecognizers[1];
3395 
3396  XCTAssert([delayingRecognizer isKindOfClass:[FlutterDelayingGestureRecognizer class]]);
3397  XCTAssert([forwardingRecognizer isKindOfClass:[ForwardingGestureRecognizer class]]);
3398 
3400 
3401  XCTAssertEqual(touchInteceptorView.gestureRecognizers[0], delayingRecognizer);
3402  XCTAssertEqual(touchInteceptorView.gestureRecognizers[1], forwardingRecognizer);
3403 }
3404 
3405 - (void)testFlutterPlatformViewControllerSubmitFrameWithoutFlutterViewNotCrashing {
3406  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
3407 
3408  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
3409  /*platform=*/GetDefaultTaskRunner(),
3410  /*raster=*/GetDefaultTaskRunner(),
3411  /*ui=*/GetDefaultTaskRunner(),
3412  /*io=*/GetDefaultTaskRunner());
3413  FlutterPlatformViewsController* flutterPlatformViewsController =
3414  [[FlutterPlatformViewsController alloc] init];
3415  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
3416  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
3417  /*delegate=*/mock_delegate,
3418  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
3419  /*platform_views_controller=*/flutterPlatformViewsController,
3420  /*task_runners=*/runners,
3421  /*worker_task_runner=*/nil,
3422  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
3423 
3426  [flutterPlatformViewsController
3427  registerViewFactory:factory
3428  withId:@"MockFlutterPlatformView"
3429  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
3430  FlutterResult result = ^(id result) {
3431  };
3432  [flutterPlatformViewsController
3434  arguments:@{
3435  @"id" : @2,
3436  @"viewType" : @"MockFlutterPlatformView"
3437  }]
3438  result:result];
3439 
3440  XCTAssertNotNil(gMockPlatformView);
3441 
3442  // Create embedded view params
3443  flutter::MutatorsStack stack;
3444  flutter::DlMatrix finalMatrix;
3445 
3446  auto embeddedViewParams_1 = std::make_unique<flutter::EmbeddedViewParams>(
3447  flutter::ToSkMatrix(finalMatrix), SkSize::Make(300, 300), stack);
3448 
3449  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
3450  withParams:std::move(embeddedViewParams_1)];
3451  [flutterPlatformViewsController
3452  compositeView:2
3453  withParams:[flutterPlatformViewsController compositionParamsForView:2]];
3454 
3455  flutter::SurfaceFrame::FramebufferInfo framebuffer_info;
3456  auto mock_surface = std::make_unique<flutter::SurfaceFrame>(
3457  nullptr, framebuffer_info,
3458  [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return false; },
3459  [](const flutter::SurfaceFrame& surface_frame) { return true; },
3460  /*frame_size=*/SkISize::Make(800, 600));
3461  XCTAssertFalse([flutterPlatformViewsController
3462  submitFrame:std::move(mock_surface)
3463  withIosContext:std::make_shared<flutter::IOSContextNoop>()]);
3464 
3465  auto embeddedViewParams_2 = std::make_unique<flutter::EmbeddedViewParams>(
3466  flutter::ToSkMatrix(finalMatrix), SkSize::Make(300, 300), stack);
3467  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
3468  withParams:std::move(embeddedViewParams_2)];
3469  [flutterPlatformViewsController
3470  compositeView:2
3471  withParams:[flutterPlatformViewsController compositionParamsForView:2]];
3472 
3473  auto mock_surface_submit_true = std::make_unique<flutter::SurfaceFrame>(
3474  nullptr, framebuffer_info,
3475  [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; },
3476  [](const flutter::SurfaceFrame& surface_frame) { return true; },
3477  /*frame_size=*/SkISize::Make(800, 600));
3478  XCTAssertTrue([flutterPlatformViewsController
3479  submitFrame:std::move(mock_surface_submit_true)
3480  withIosContext:std::make_shared<flutter::IOSContextNoop>()]);
3481 }
3482 
3483 - (void)
3484  testFlutterPlatformViewControllerResetDeallocsPlatformViewWhenRootViewsNotBindedToFlutterView {
3485  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
3486 
3487  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
3488  /*platform=*/GetDefaultTaskRunner(),
3489  /*raster=*/GetDefaultTaskRunner(),
3490  /*ui=*/GetDefaultTaskRunner(),
3491  /*io=*/GetDefaultTaskRunner());
3492  FlutterPlatformViewsController* flutterPlatformViewsController =
3493  [[FlutterPlatformViewsController alloc] init];
3494  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
3495  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
3496  /*delegate=*/mock_delegate,
3497  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
3498  /*platform_views_controller=*/flutterPlatformViewsController,
3499  /*task_runners=*/runners,
3500  /*worker_task_runner=*/nil,
3501  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
3502 
3503  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
3504  flutterPlatformViewsController.flutterView = flutterView;
3505 
3508  [flutterPlatformViewsController
3509  registerViewFactory:factory
3510  withId:@"MockFlutterPlatformView"
3511  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
3512  FlutterResult result = ^(id result) {
3513  };
3514  // autorelease pool to trigger an autorelease for all the root_views_ and touch_interceptors_.
3515  @autoreleasepool {
3516  [flutterPlatformViewsController
3518  arguments:@{
3519  @"id" : @2,
3520  @"viewType" : @"MockFlutterPlatformView"
3521  }]
3522  result:result];
3523 
3524  flutter::MutatorsStack stack;
3525  flutter::DlMatrix finalMatrix;
3526  auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
3527  flutter::ToSkMatrix(finalMatrix), SkSize::Make(300, 300), stack);
3528  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
3529  withParams:std::move(embeddedViewParams)];
3530 
3531  // Not calling |[flutterPlatformViewsController submitFrame:withIosContext:]| so that
3532  // the platform views are not added to flutter_view_.
3533 
3534  XCTAssertNotNil(gMockPlatformView);
3535  [flutterPlatformViewsController reset];
3536  }
3537  XCTAssertNil(gMockPlatformView);
3538 }
3539 
3540 - (void)testFlutterPlatformViewControllerBeginFrameShouldResetCompisitionOrder {
3541  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
3542 
3543  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
3544  /*platform=*/GetDefaultTaskRunner(),
3545  /*raster=*/GetDefaultTaskRunner(),
3546  /*ui=*/GetDefaultTaskRunner(),
3547  /*io=*/GetDefaultTaskRunner());
3548  FlutterPlatformViewsController* flutterPlatformViewsController =
3549  [[FlutterPlatformViewsController alloc] init];
3550  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
3551  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
3552  /*delegate=*/mock_delegate,
3553  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
3554  /*platform_views_controller=*/flutterPlatformViewsController,
3555  /*task_runners=*/runners,
3556  /*worker_task_runner=*/nil,
3557  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
3558 
3559  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
3560  flutterPlatformViewsController.flutterView = flutterView;
3561 
3564  [flutterPlatformViewsController
3565  registerViewFactory:factory
3566  withId:@"MockFlutterPlatformView"
3567  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
3568  FlutterResult result = ^(id result) {
3569  };
3570 
3571  [flutterPlatformViewsController
3573  arguments:@{
3574  @"id" : @0,
3575  @"viewType" : @"MockFlutterPlatformView"
3576  }]
3577  result:result];
3578 
3579  // First frame, |embeddedViewCount| is not empty after composite.
3580  [flutterPlatformViewsController beginFrameWithSize:SkISize::Make(300, 300)];
3581  flutter::MutatorsStack stack;
3582  flutter::DlMatrix finalMatrix;
3583  auto embeddedViewParams1 = std::make_unique<flutter::EmbeddedViewParams>(
3584  flutter::ToSkMatrix(finalMatrix), SkSize::Make(300, 300), stack);
3585  [flutterPlatformViewsController prerollCompositeEmbeddedView:0
3586  withParams:std::move(embeddedViewParams1)];
3587  [flutterPlatformViewsController
3588  compositeView:0
3589  withParams:[flutterPlatformViewsController compositionParamsForView:0]];
3590 
3591  XCTAssertEqual(flutterPlatformViewsController.embeddedViewCount, 1UL);
3592 
3593  // Second frame, |embeddedViewCount| should be empty at the start
3594  [flutterPlatformViewsController beginFrameWithSize:SkISize::Make(300, 300)];
3595  XCTAssertEqual(flutterPlatformViewsController.embeddedViewCount, 0UL);
3596 
3597  auto embeddedViewParams2 = std::make_unique<flutter::EmbeddedViewParams>(
3598  flutter::ToSkMatrix(finalMatrix), SkSize::Make(300, 300), stack);
3599  [flutterPlatformViewsController prerollCompositeEmbeddedView:0
3600  withParams:std::move(embeddedViewParams2)];
3601  [flutterPlatformViewsController
3602  compositeView:0
3603  withParams:[flutterPlatformViewsController compositionParamsForView:0]];
3604 
3605  XCTAssertEqual(flutterPlatformViewsController.embeddedViewCount, 1UL);
3606 }
3607 
3608 - (void)
3609  testFlutterPlatformViewControllerSubmitFrameShouldOrderSubviewsCorrectlyWithDifferentViewHierarchy {
3610  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
3611 
3612  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
3613  /*platform=*/GetDefaultTaskRunner(),
3614  /*raster=*/GetDefaultTaskRunner(),
3615  /*ui=*/GetDefaultTaskRunner(),
3616  /*io=*/GetDefaultTaskRunner());
3617  FlutterPlatformViewsController* flutterPlatformViewsController =
3618  [[FlutterPlatformViewsController alloc] init];
3619  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
3620  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
3621  /*delegate=*/mock_delegate,
3622  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
3623  /*platform_views_controller=*/flutterPlatformViewsController,
3624  /*task_runners=*/runners,
3625  /*worker_task_runner=*/nil,
3626  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
3627 
3628  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
3629  flutterPlatformViewsController.flutterView = flutterView;
3630 
3633  [flutterPlatformViewsController
3634  registerViewFactory:factory
3635  withId:@"MockFlutterPlatformView"
3636  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
3637  FlutterResult result = ^(id result) {
3638  };
3639  [flutterPlatformViewsController
3641  arguments:@{
3642  @"id" : @0,
3643  @"viewType" : @"MockFlutterPlatformView"
3644  }]
3645  result:result];
3646  UIView* view1 = gMockPlatformView;
3647 
3648  // This overwrites `gMockPlatformView` to another view.
3649  [flutterPlatformViewsController
3651  arguments:@{
3652  @"id" : @1,
3653  @"viewType" : @"MockFlutterPlatformView"
3654  }]
3655  result:result];
3656  UIView* view2 = gMockPlatformView;
3657 
3658  [flutterPlatformViewsController beginFrameWithSize:SkISize::Make(300, 300)];
3659  flutter::MutatorsStack stack;
3660  flutter::DlMatrix finalMatrix;
3661  auto embeddedViewParams1 = std::make_unique<flutter::EmbeddedViewParams>(
3662  flutter::ToSkMatrix(finalMatrix), SkSize::Make(300, 300), stack);
3663  [flutterPlatformViewsController prerollCompositeEmbeddedView:0
3664  withParams:std::move(embeddedViewParams1)];
3665 
3666  auto embeddedViewParams2 = std::make_unique<flutter::EmbeddedViewParams>(
3667  flutter::ToSkMatrix(finalMatrix), SkSize::Make(500, 500), stack);
3668  [flutterPlatformViewsController prerollCompositeEmbeddedView:1
3669  withParams:std::move(embeddedViewParams2)];
3670 
3671  flutter::SurfaceFrame::FramebufferInfo framebuffer_info;
3672  auto mock_surface = std::make_unique<flutter::SurfaceFrame>(
3673  nullptr, framebuffer_info,
3674  [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; },
3675  [](const flutter::SurfaceFrame& surface_frame) { return true; },
3676  /*frame_size=*/SkISize::Make(800, 600), nullptr, /*display_list_fallback=*/true);
3677  XCTAssertTrue([flutterPlatformViewsController
3678  submitFrame:std::move(mock_surface)
3679  withIosContext:std::make_shared<flutter::IOSContextNoop>()]);
3680 
3681  // platform view is wrapped by touch interceptor, which itself is wrapped by clipping view.
3682  UIView* clippingView1 = view1.superview.superview;
3683  UIView* clippingView2 = view2.superview.superview;
3684  XCTAssertTrue([flutterView.subviews indexOfObject:clippingView1] <
3685  [flutterView.subviews indexOfObject:clippingView2],
3686  @"The first clipping view should be added before the second clipping view.");
3687 
3688  // Need to recreate these params since they are `std::move`ed.
3689  [flutterPlatformViewsController beginFrameWithSize:SkISize::Make(300, 300)];
3690  // Process the second frame in the opposite order.
3691  embeddedViewParams2 = std::make_unique<flutter::EmbeddedViewParams>(
3692  flutter::ToSkMatrix(finalMatrix), SkSize::Make(500, 500), stack);
3693  [flutterPlatformViewsController prerollCompositeEmbeddedView:1
3694  withParams:std::move(embeddedViewParams2)];
3695 
3696  embeddedViewParams1 = std::make_unique<flutter::EmbeddedViewParams>(
3697  flutter::ToSkMatrix(finalMatrix), SkSize::Make(300, 300), stack);
3698  [flutterPlatformViewsController prerollCompositeEmbeddedView:0
3699  withParams:std::move(embeddedViewParams1)];
3700 
3701  mock_surface = std::make_unique<flutter::SurfaceFrame>(
3702  nullptr, framebuffer_info,
3703  [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; },
3704  [](const flutter::SurfaceFrame& surface_frame) { return true; },
3705  /*frame_size=*/SkISize::Make(800, 600), nullptr, /*display_list_fallback=*/true);
3706  XCTAssertTrue([flutterPlatformViewsController
3707  submitFrame:std::move(mock_surface)
3708  withIosContext:std::make_shared<flutter::IOSContextNoop>()]);
3709 
3710  XCTAssertTrue([flutterView.subviews indexOfObject:clippingView1] >
3711  [flutterView.subviews indexOfObject:clippingView2],
3712  @"The first clipping view should be added after the second clipping view.");
3713 }
3714 
3715 - (void)
3716  testFlutterPlatformViewControllerSubmitFrameShouldOrderSubviewsCorrectlyWithSameViewHierarchy {
3717  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
3718 
3719  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
3720  /*platform=*/GetDefaultTaskRunner(),
3721  /*raster=*/GetDefaultTaskRunner(),
3722  /*ui=*/GetDefaultTaskRunner(),
3723  /*io=*/GetDefaultTaskRunner());
3724  FlutterPlatformViewsController* flutterPlatformViewsController =
3725  [[FlutterPlatformViewsController alloc] init];
3726  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
3727  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
3728  /*delegate=*/mock_delegate,
3729  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
3730  /*platform_views_controller=*/flutterPlatformViewsController,
3731  /*task_runners=*/runners,
3732  /*worker_task_runner=*/nil,
3733  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
3734 
3735  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
3736  flutterPlatformViewsController.flutterView = flutterView;
3737 
3740  [flutterPlatformViewsController
3741  registerViewFactory:factory
3742  withId:@"MockFlutterPlatformView"
3743  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
3744  FlutterResult result = ^(id result) {
3745  };
3746  [flutterPlatformViewsController
3748  arguments:@{
3749  @"id" : @0,
3750  @"viewType" : @"MockFlutterPlatformView"
3751  }]
3752  result:result];
3753  UIView* view1 = gMockPlatformView;
3754 
3755  // This overwrites `gMockPlatformView` to another view.
3756  [flutterPlatformViewsController
3758  arguments:@{
3759  @"id" : @1,
3760  @"viewType" : @"MockFlutterPlatformView"
3761  }]
3762  result:result];
3763  UIView* view2 = gMockPlatformView;
3764 
3765  [flutterPlatformViewsController beginFrameWithSize:SkISize::Make(300, 300)];
3766  flutter::MutatorsStack stack;
3767  flutter::DlMatrix finalMatrix;
3768  auto embeddedViewParams1 = std::make_unique<flutter::EmbeddedViewParams>(
3769  flutter::ToSkMatrix(finalMatrix), SkSize::Make(300, 300), stack);
3770  [flutterPlatformViewsController prerollCompositeEmbeddedView:0
3771  withParams:std::move(embeddedViewParams1)];
3772 
3773  auto embeddedViewParams2 = std::make_unique<flutter::EmbeddedViewParams>(
3774  flutter::ToSkMatrix(finalMatrix), SkSize::Make(500, 500), stack);
3775  [flutterPlatformViewsController prerollCompositeEmbeddedView:1
3776  withParams:std::move(embeddedViewParams2)];
3777 
3778  flutter::SurfaceFrame::FramebufferInfo framebuffer_info;
3779  auto mock_surface = std::make_unique<flutter::SurfaceFrame>(
3780  nullptr, framebuffer_info,
3781  [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; },
3782  [](const flutter::SurfaceFrame& surface_frame) { return true; },
3783  /*frame_size=*/SkISize::Make(800, 600), nullptr, /*display_list_fallback=*/true);
3784  XCTAssertTrue([flutterPlatformViewsController
3785  submitFrame:std::move(mock_surface)
3786  withIosContext:std::make_shared<flutter::IOSContextNoop>()]);
3787 
3788  // platform view is wrapped by touch interceptor, which itself is wrapped by clipping view.
3789  UIView* clippingView1 = view1.superview.superview;
3790  UIView* clippingView2 = view2.superview.superview;
3791  XCTAssertTrue([flutterView.subviews indexOfObject:clippingView1] <
3792  [flutterView.subviews indexOfObject:clippingView2],
3793  @"The first clipping view should be added before the second clipping view.");
3794 
3795  // Need to recreate these params since they are `std::move`ed.
3796  [flutterPlatformViewsController beginFrameWithSize:SkISize::Make(300, 300)];
3797  // Process the second frame in the same order.
3798  embeddedViewParams1 = std::make_unique<flutter::EmbeddedViewParams>(
3799  flutter::ToSkMatrix(finalMatrix), SkSize::Make(300, 300), stack);
3800  [flutterPlatformViewsController prerollCompositeEmbeddedView:0
3801  withParams:std::move(embeddedViewParams1)];
3802 
3803  embeddedViewParams2 = std::make_unique<flutter::EmbeddedViewParams>(
3804  flutter::ToSkMatrix(finalMatrix), SkSize::Make(500, 500), stack);
3805  [flutterPlatformViewsController prerollCompositeEmbeddedView:1
3806  withParams:std::move(embeddedViewParams2)];
3807 
3808  mock_surface = std::make_unique<flutter::SurfaceFrame>(
3809  nullptr, framebuffer_info,
3810  [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; },
3811  [](const flutter::SurfaceFrame& surface_frame) { return true; },
3812  /*frame_size=*/SkISize::Make(800, 600), nullptr, /*display_list_fallback=*/true);
3813  XCTAssertTrue([flutterPlatformViewsController
3814  submitFrame:std::move(mock_surface)
3815  withIosContext:std::make_shared<flutter::IOSContextNoop>()]);
3816 
3817  XCTAssertTrue([flutterView.subviews indexOfObject:clippingView1] <
3818  [flutterView.subviews indexOfObject:clippingView2],
3819  @"The first clipping view should be added before the second clipping view.");
3820 }
3821 
3822 - (int)alphaOfPoint:(CGPoint)point onView:(UIView*)view {
3823  unsigned char pixel[4] = {0};
3824 
3825  CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
3826 
3827  // Draw the pixel on `point` in the context.
3828  CGContextRef context = CGBitmapContextCreate(
3829  pixel, 1, 1, 8, 4, colorSpace, kCGBitmapAlphaInfoMask & kCGImageAlphaPremultipliedLast);
3830  CGContextTranslateCTM(context, -point.x, -point.y);
3831  [view.layer renderInContext:context];
3832 
3833  CGContextRelease(context);
3834  CGColorSpaceRelease(colorSpace);
3835  // Get the alpha from the pixel that we just rendered.
3836  return pixel[3];
3837 }
3838 
3839 - (void)testHasFirstResponderInViewHierarchySubtree_viewItselfBecomesFirstResponder {
3840  // For view to become the first responder, it must be a descendant of a UIWindow
3841  UIWindow* window = [[UIWindow alloc] init];
3842  UITextField* textField = [[UITextField alloc] init];
3843  [window addSubview:textField];
3844 
3845  [textField becomeFirstResponder];
3846  XCTAssertTrue(textField.isFirstResponder);
3847  XCTAssertTrue(textField.flt_hasFirstResponderInViewHierarchySubtree);
3848  [textField resignFirstResponder];
3849  XCTAssertFalse(textField.isFirstResponder);
3850  XCTAssertFalse(textField.flt_hasFirstResponderInViewHierarchySubtree);
3851 }
3852 
3853 - (void)testHasFirstResponderInViewHierarchySubtree_descendantViewBecomesFirstResponder {
3854  // For view to become the first responder, it must be a descendant of a UIWindow
3855  UIWindow* window = [[UIWindow alloc] init];
3856  UIView* view = [[UIView alloc] init];
3857  UIView* childView = [[UIView alloc] init];
3858  UITextField* textField = [[UITextField alloc] init];
3859  [window addSubview:view];
3860  [view addSubview:childView];
3861  [childView addSubview:textField];
3862 
3863  [textField becomeFirstResponder];
3864  XCTAssertTrue(textField.isFirstResponder);
3865  XCTAssertTrue(view.flt_hasFirstResponderInViewHierarchySubtree);
3866  [textField resignFirstResponder];
3867  XCTAssertFalse(textField.isFirstResponder);
3868  XCTAssertFalse(view.flt_hasFirstResponderInViewHierarchySubtree);
3869 }
3870 
3871 - (void)testFlutterClippingMaskViewPoolReuseViewsAfterRecycle {
3872  FlutterClippingMaskViewPool* pool = [[FlutterClippingMaskViewPool alloc] initWithCapacity:2];
3873  FlutterClippingMaskView* view1 = [pool getMaskViewWithFrame:CGRectZero];
3874  FlutterClippingMaskView* view2 = [pool getMaskViewWithFrame:CGRectZero];
3875  [pool insertViewToPoolIfNeeded:view1];
3876  [pool insertViewToPoolIfNeeded:view2];
3877  CGRect newRect = CGRectMake(0, 0, 10, 10);
3878  FlutterClippingMaskView* view3 = [pool getMaskViewWithFrame:newRect];
3879  FlutterClippingMaskView* view4 = [pool getMaskViewWithFrame:newRect];
3880  // view3 and view4 should randomly get either of view1 and view2.
3881  NSSet* set1 = [NSSet setWithObjects:view1, view2, nil];
3882  NSSet* set2 = [NSSet setWithObjects:view3, view4, nil];
3883  XCTAssertEqualObjects(set1, set2);
3884  XCTAssertTrue(CGRectEqualToRect(view3.frame, newRect));
3885  XCTAssertTrue(CGRectEqualToRect(view4.frame, newRect));
3886 }
3887 
3888 - (void)testFlutterClippingMaskViewPoolAllocsNewMaskViewsAfterReachingCapacity {
3889  FlutterClippingMaskViewPool* pool = [[FlutterClippingMaskViewPool alloc] initWithCapacity:2];
3890  FlutterClippingMaskView* view1 = [pool getMaskViewWithFrame:CGRectZero];
3891  FlutterClippingMaskView* view2 = [pool getMaskViewWithFrame:CGRectZero];
3892  FlutterClippingMaskView* view3 = [pool getMaskViewWithFrame:CGRectZero];
3893  XCTAssertNotEqual(view1, view3);
3894  XCTAssertNotEqual(view2, view3);
3895 }
3896 
3897 - (void)testMaskViewsReleasedWhenPoolIsReleased {
3898  __weak UIView* weakView;
3899  @autoreleasepool {
3900  FlutterClippingMaskViewPool* pool = [[FlutterClippingMaskViewPool alloc] initWithCapacity:2];
3901  FlutterClippingMaskView* view = [pool getMaskViewWithFrame:CGRectZero];
3902  weakView = view;
3903  XCTAssertNotNil(weakView);
3904  }
3905  XCTAssertNil(weakView);
3906 }
3907 
3908 - (void)testClipMaskViewIsReused {
3909  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
3910 
3911  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
3912  /*platform=*/GetDefaultTaskRunner(),
3913  /*raster=*/GetDefaultTaskRunner(),
3914  /*ui=*/GetDefaultTaskRunner(),
3915  /*io=*/GetDefaultTaskRunner());
3916  FlutterPlatformViewsController* flutterPlatformViewsController =
3917  [[FlutterPlatformViewsController alloc] init];
3918  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
3919  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
3920  /*delegate=*/mock_delegate,
3921  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
3922  /*platform_views_controller=*/flutterPlatformViewsController,
3923  /*task_runners=*/runners,
3924  /*worker_task_runner=*/nil,
3925  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
3926 
3929  [flutterPlatformViewsController
3930  registerViewFactory:factory
3931  withId:@"MockFlutterPlatformView"
3932  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
3933  FlutterResult result = ^(id result) {
3934  };
3935  [flutterPlatformViewsController
3937  arguments:@{
3938  @"id" : @1,
3939  @"viewType" : @"MockFlutterPlatformView"
3940  }]
3941  result:result];
3942 
3943  XCTAssertNotNil(gMockPlatformView);
3944  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
3945  flutterPlatformViewsController.flutterView = flutterView;
3946  // Create embedded view params
3947  flutter::MutatorsStack stack1;
3948  // Layer tree always pushes a screen scale factor to the stack
3949  flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
3950  flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
3951  stack1.PushTransform(screenScaleMatrix);
3952  // Push a clip rect
3953  flutter::DlRect rect = flutter::DlRect::MakeXYWH(2, 2, 3, 3);
3954  stack1.PushClipRect(rect);
3955 
3956  auto embeddedViewParams1 = std::make_unique<flutter::EmbeddedViewParams>(
3957  flutter::ToSkMatrix(screenScaleMatrix), SkSize::Make(10, 10), stack1);
3958 
3959  [flutterPlatformViewsController prerollCompositeEmbeddedView:1
3960  withParams:std::move(embeddedViewParams1)];
3961  [flutterPlatformViewsController
3962  compositeView:1
3963  withParams:[flutterPlatformViewsController compositionParamsForView:1]];
3964 
3965  UIView* childClippingView1 = gMockPlatformView.superview.superview;
3966  UIView* maskView1 = childClippingView1.maskView;
3967  XCTAssertNotNil(maskView1);
3968 
3969  // Composite a new frame.
3970  [flutterPlatformViewsController beginFrameWithSize:SkISize::Make(100, 100)];
3971  flutter::MutatorsStack stack2;
3972  auto embeddedViewParams2 = std::make_unique<flutter::EmbeddedViewParams>(
3973  flutter::ToSkMatrix(screenScaleMatrix), SkSize::Make(10, 10), stack2);
3974  auto embeddedViewParams3 = std::make_unique<flutter::EmbeddedViewParams>(
3975  flutter::ToSkMatrix(screenScaleMatrix), SkSize::Make(10, 10), stack2);
3976  [flutterPlatformViewsController prerollCompositeEmbeddedView:1
3977  withParams:std::move(embeddedViewParams3)];
3978  [flutterPlatformViewsController
3979  compositeView:1
3980  withParams:[flutterPlatformViewsController compositionParamsForView:1]];
3981 
3982  childClippingView1 = gMockPlatformView.superview.superview;
3983 
3984  // This overrides gMockPlatformView to point to the newly created platform view.
3985  [flutterPlatformViewsController
3987  arguments:@{
3988  @"id" : @2,
3989  @"viewType" : @"MockFlutterPlatformView"
3990  }]
3991  result:result];
3992 
3993  auto embeddedViewParams4 = std::make_unique<flutter::EmbeddedViewParams>(
3994  flutter::ToSkMatrix(screenScaleMatrix), SkSize::Make(10, 10), stack1);
3995  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
3996  withParams:std::move(embeddedViewParams4)];
3997  [flutterPlatformViewsController
3998  compositeView:2
3999  withParams:[flutterPlatformViewsController compositionParamsForView:2]];
4000 
4001  UIView* childClippingView2 = gMockPlatformView.superview.superview;
4002 
4003  UIView* maskView2 = childClippingView2.maskView;
4004  XCTAssertEqual(maskView1, maskView2);
4005  XCTAssertNotNil(childClippingView2.maskView);
4006  XCTAssertNil(childClippingView1.maskView);
4007 }
4008 
4009 - (void)testDifferentClipMaskViewIsUsedForEachView {
4010  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
4011 
4012  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
4013  /*platform=*/GetDefaultTaskRunner(),
4014  /*raster=*/GetDefaultTaskRunner(),
4015  /*ui=*/GetDefaultTaskRunner(),
4016  /*io=*/GetDefaultTaskRunner());
4017  FlutterPlatformViewsController* flutterPlatformViewsController =
4018  [[FlutterPlatformViewsController alloc] init];
4019  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
4020  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
4021  /*delegate=*/mock_delegate,
4022  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
4023  /*platform_views_controller=*/flutterPlatformViewsController,
4024  /*task_runners=*/runners,
4025  /*worker_task_runner=*/nil,
4026  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
4027 
4030  [flutterPlatformViewsController
4031  registerViewFactory:factory
4032  withId:@"MockFlutterPlatformView"
4033  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
4034  FlutterResult result = ^(id result) {
4035  };
4036 
4037  [flutterPlatformViewsController
4039  arguments:@{
4040  @"id" : @1,
4041  @"viewType" : @"MockFlutterPlatformView"
4042  }]
4043  result:result];
4044  UIView* view1 = gMockPlatformView;
4045 
4046  // This overwrites `gMockPlatformView` to another view.
4047  [flutterPlatformViewsController
4049  arguments:@{
4050  @"id" : @2,
4051  @"viewType" : @"MockFlutterPlatformView"
4052  }]
4053  result:result];
4054  UIView* view2 = gMockPlatformView;
4055 
4056  XCTAssertNotNil(gMockPlatformView);
4057  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
4058  flutterPlatformViewsController.flutterView = flutterView;
4059  // Create embedded view params
4060  flutter::MutatorsStack stack1;
4061  // Layer tree always pushes a screen scale factor to the stack
4062  flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
4063  flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
4064  stack1.PushTransform(screenScaleMatrix);
4065  // Push a clip rect
4066  flutter::DlRect rect = flutter::DlRect::MakeXYWH(2, 2, 3, 3);
4067  stack1.PushClipRect(rect);
4068 
4069  auto embeddedViewParams1 = std::make_unique<flutter::EmbeddedViewParams>(
4070  flutter::ToSkMatrix(screenScaleMatrix), SkSize::Make(10, 10), stack1);
4071 
4072  flutter::MutatorsStack stack2;
4073  stack2.PushClipRect(rect);
4074  auto embeddedViewParams2 = std::make_unique<flutter::EmbeddedViewParams>(
4075  flutter::ToSkMatrix(screenScaleMatrix), SkSize::Make(10, 10), stack2);
4076 
4077  [flutterPlatformViewsController prerollCompositeEmbeddedView:1
4078  withParams:std::move(embeddedViewParams1)];
4079  [flutterPlatformViewsController
4080  compositeView:1
4081  withParams:[flutterPlatformViewsController compositionParamsForView:1]];
4082 
4083  UIView* childClippingView1 = view1.superview.superview;
4084 
4085  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
4086  withParams:std::move(embeddedViewParams2)];
4087  [flutterPlatformViewsController
4088  compositeView:2
4089  withParams:[flutterPlatformViewsController compositionParamsForView:2]];
4090 
4091  UIView* childClippingView2 = view2.superview.superview;
4092  UIView* maskView1 = childClippingView1.maskView;
4093  UIView* maskView2 = childClippingView2.maskView;
4094  XCTAssertNotEqual(maskView1, maskView2);
4095 }
4096 
4097 - (void)testMaskViewUsesCAShapeLayerAsTheBackingLayer {
4098  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
4099 
4100  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
4101  /*platform=*/GetDefaultTaskRunner(),
4102  /*raster=*/GetDefaultTaskRunner(),
4103  /*ui=*/GetDefaultTaskRunner(),
4104  /*io=*/GetDefaultTaskRunner());
4105  FlutterPlatformViewsController* flutterPlatformViewsController =
4106  [[FlutterPlatformViewsController alloc] init];
4107  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
4108  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
4109  /*delegate=*/mock_delegate,
4110  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
4111  /*platform_views_controller=*/flutterPlatformViewsController,
4112  /*task_runners=*/runners,
4113  /*worker_task_runner=*/nil,
4114  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
4115 
4118  [flutterPlatformViewsController
4119  registerViewFactory:factory
4120  withId:@"MockFlutterPlatformView"
4121  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
4122  FlutterResult result = ^(id result) {
4123  };
4124 
4125  [flutterPlatformViewsController
4127  arguments:@{
4128  @"id" : @1,
4129  @"viewType" : @"MockFlutterPlatformView"
4130  }]
4131  result:result];
4132 
4133  XCTAssertNotNil(gMockPlatformView);
4134  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
4135  flutterPlatformViewsController.flutterView = flutterView;
4136  // Create embedded view params
4137  flutter::MutatorsStack stack1;
4138  // Layer tree always pushes a screen scale factor to the stack
4139  flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
4140  flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
4141  stack1.PushTransform(screenScaleMatrix);
4142  // Push a clip rect
4143  flutter::DlRect rect = flutter::DlRect::MakeXYWH(2, 2, 3, 3);
4144  stack1.PushClipRect(rect);
4145 
4146  auto embeddedViewParams1 = std::make_unique<flutter::EmbeddedViewParams>(
4147  flutter::ToSkMatrix(screenScaleMatrix), SkSize::Make(10, 10), stack1);
4148 
4149  flutter::MutatorsStack stack2;
4150  stack2.PushClipRect(rect);
4151  auto embeddedViewParams2 = std::make_unique<flutter::EmbeddedViewParams>(
4152  flutter::ToSkMatrix(screenScaleMatrix), SkSize::Make(10, 10), stack2);
4153 
4154  [flutterPlatformViewsController prerollCompositeEmbeddedView:1
4155  withParams:std::move(embeddedViewParams1)];
4156  [flutterPlatformViewsController
4157  compositeView:1
4158  withParams:[flutterPlatformViewsController compositionParamsForView:1]];
4159 
4160  UIView* childClippingView = gMockPlatformView.superview.superview;
4161 
4162  UIView* maskView = childClippingView.maskView;
4163  XCTAssert([maskView.layer isKindOfClass:[CAShapeLayer class]],
4164  @"Mask view must use CAShapeLayer as its backing layer.");
4165 }
4166 
4167 // Return true if a correct visual effect view is found. It also implies all the validation in this
4168 // method passes.
4169 //
4170 // There are two fail states for this method. 1. One of the XCTAssert method failed; or 2. No
4171 // correct visual effect view found.
4172 - (BOOL)validateOneVisualEffectView:(UIView*)visualEffectView
4173  expectedFrame:(CGRect)frame
4174  inputRadius:(CGFloat)inputRadius {
4175  XCTAssertTrue(CGRectEqualToRect(visualEffectView.frame, frame));
4176  for (UIView* view in visualEffectView.subviews) {
4177  if (![NSStringFromClass([view class]) hasSuffix:@"BackdropView"]) {
4178  continue;
4179  }
4180  XCTAssertEqual(view.layer.filters.count, 1u);
4181  NSObject* filter = view.layer.filters.firstObject;
4182 
4183  XCTAssertEqualObjects([filter valueForKey:@"name"], @"gaussianBlur");
4184 
4185  NSObject* inputRadiusInFilter = [filter valueForKey:@"inputRadius"];
4186  XCTAssertTrue([inputRadiusInFilter isKindOfClass:[NSNumber class]] &&
4187  flutter::BlurRadiusEqualToBlurRadius(((NSNumber*)inputRadiusInFilter).floatValue,
4188  inputRadius));
4189  return YES;
4190  }
4191  return NO;
4192 }
4193 
4194 - (void)testDisposingViewInCompositionOrderDoNotCrash {
4195  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
4196 
4197  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
4198  /*platform=*/GetDefaultTaskRunner(),
4199  /*raster=*/GetDefaultTaskRunner(),
4200  /*ui=*/GetDefaultTaskRunner(),
4201  /*io=*/GetDefaultTaskRunner());
4202  FlutterPlatformViewsController* flutterPlatformViewsController =
4203  [[FlutterPlatformViewsController alloc] init];
4204  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
4205  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
4206  /*delegate=*/mock_delegate,
4207  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
4208  /*platform_views_controller=*/flutterPlatformViewsController,
4209  /*task_runners=*/runners,
4210  /*worker_task_runner=*/nil,
4211  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
4212 
4213  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
4214  flutterPlatformViewsController.flutterView = flutterView;
4215 
4218  [flutterPlatformViewsController
4219  registerViewFactory:factory
4220  withId:@"MockFlutterPlatformView"
4221  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
4222  FlutterResult result = ^(id result) {
4223  };
4224 
4225  [flutterPlatformViewsController
4227  arguments:@{
4228  @"id" : @0,
4229  @"viewType" : @"MockFlutterPlatformView"
4230  }]
4231  result:result];
4232  [flutterPlatformViewsController
4234  arguments:@{
4235  @"id" : @1,
4236  @"viewType" : @"MockFlutterPlatformView"
4237  }]
4238  result:result];
4239 
4240  {
4241  // **** First frame, view id 0, 1 in the composition_order_, disposing view 0 is called. **** //
4242  // No view should be disposed, or removed from the composition order.
4243  [flutterPlatformViewsController beginFrameWithSize:SkISize::Make(300, 300)];
4244  flutter::MutatorsStack stack;
4245  flutter::DlMatrix finalMatrix;
4246  auto embeddedViewParams0 = std::make_unique<flutter::EmbeddedViewParams>(
4247  flutter::ToSkMatrix(finalMatrix), SkSize::Make(300, 300), stack);
4248  [flutterPlatformViewsController prerollCompositeEmbeddedView:0
4249  withParams:std::move(embeddedViewParams0)];
4250 
4251  auto embeddedViewParams1 = std::make_unique<flutter::EmbeddedViewParams>(
4252  flutter::ToSkMatrix(finalMatrix), SkSize::Make(300, 300), stack);
4253  [flutterPlatformViewsController prerollCompositeEmbeddedView:1
4254  withParams:std::move(embeddedViewParams1)];
4255 
4256  XCTAssertEqual(flutterPlatformViewsController.embeddedViewCount, 2UL);
4257 
4258  XCTestExpectation* expectation = [self expectationWithDescription:@"dispose call ended."];
4259  FlutterResult disposeResult = ^(id result) {
4260  [expectation fulfill];
4261  };
4262 
4263  [flutterPlatformViewsController
4265  result:disposeResult];
4266  [self waitForExpectationsWithTimeout:30 handler:nil];
4267 
4268  flutter::SurfaceFrame::FramebufferInfo framebuffer_info;
4269  auto mock_surface = std::make_unique<flutter::SurfaceFrame>(
4270  nullptr, framebuffer_info,
4271  [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; },
4272  [](const flutter::SurfaceFrame& surface_frame) { return true; },
4273  /*frame_size=*/SkISize::Make(800, 600), nullptr,
4274  /*display_list_fallback=*/true);
4275  XCTAssertTrue([flutterPlatformViewsController
4276  submitFrame:std::move(mock_surface)
4277  withIosContext:std::make_shared<flutter::IOSContextNoop>()]);
4278 
4279  // Disposing won't remove embedded views until the view is removed from the composition_order_
4280  XCTAssertEqual(flutterPlatformViewsController.embeddedViewCount, 2UL);
4281  XCTAssertNotNil([flutterPlatformViewsController platformViewForId:0]);
4282  XCTAssertNotNil([flutterPlatformViewsController platformViewForId:1]);
4283  }
4284 
4285  {
4286  // **** Second frame, view id 1 in the composition_order_, no disposing view is called, **** //
4287  // View 0 is removed from the composition order in this frame, hence also disposed.
4288  [flutterPlatformViewsController beginFrameWithSize:SkISize::Make(300, 300)];
4289  flutter::MutatorsStack stack;
4290  flutter::DlMatrix finalMatrix;
4291  auto embeddedViewParams1 = std::make_unique<flutter::EmbeddedViewParams>(
4292  flutter::ToSkMatrix(finalMatrix), SkSize::Make(300, 300), stack);
4293  [flutterPlatformViewsController prerollCompositeEmbeddedView:1
4294  withParams:std::move(embeddedViewParams1)];
4295 
4296  flutter::SurfaceFrame::FramebufferInfo framebuffer_info;
4297  auto mock_surface = std::make_unique<flutter::SurfaceFrame>(
4298  nullptr, framebuffer_info,
4299  [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; },
4300  [](const flutter::SurfaceFrame& surface_frame) { return true; },
4301  /*frame_size=*/SkISize::Make(800, 600), nullptr, /*display_list_fallback=*/true);
4302  XCTAssertTrue([flutterPlatformViewsController
4303  submitFrame:std::move(mock_surface)
4304  withIosContext:std::make_shared<flutter::IOSContextNoop>()]);
4305 
4306  // Disposing won't remove embedded views until the view is removed from the composition_order_
4307  XCTAssertEqual(flutterPlatformViewsController.embeddedViewCount, 1UL);
4308  XCTAssertNil([flutterPlatformViewsController platformViewForId:0]);
4309  XCTAssertNotNil([flutterPlatformViewsController platformViewForId:1]);
4310  }
4311 }
4312 - (void)testOnlyPlatformViewsAreRemovedWhenReset {
4313  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
4314 
4315  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
4316  /*platform=*/GetDefaultTaskRunner(),
4317  /*raster=*/GetDefaultTaskRunner(),
4318  /*ui=*/GetDefaultTaskRunner(),
4319  /*io=*/GetDefaultTaskRunner());
4320  FlutterPlatformViewsController* flutterPlatformViewsController =
4321  [[FlutterPlatformViewsController alloc] init];
4322  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
4323  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
4324  /*delegate=*/mock_delegate,
4325  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
4326  /*platform_views_controller=*/flutterPlatformViewsController,
4327  /*task_runners=*/runners,
4328  /*worker_task_runner=*/nil,
4329  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
4330 
4333  [flutterPlatformViewsController
4334  registerViewFactory:factory
4335  withId:@"MockFlutterPlatformView"
4336  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
4337  FlutterResult result = ^(id result) {
4338  };
4339  [flutterPlatformViewsController
4341  arguments:@{
4342  @"id" : @2,
4343  @"viewType" : @"MockFlutterPlatformView"
4344  }]
4345  result:result];
4346  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
4347  flutterPlatformViewsController.flutterView = flutterView;
4348  // Create embedded view params
4349  flutter::MutatorsStack stack;
4350  // Layer tree always pushes a screen scale factor to the stack
4351  flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
4352  flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
4353  stack.PushTransform(screenScaleMatrix);
4354  // Push a translate matrix
4355  flutter::DlMatrix translateMatrix = flutter::DlMatrix::MakeTranslation({100, 100});
4356  stack.PushTransform(translateMatrix);
4357  flutter::DlMatrix finalMatrix = screenScaleMatrix * translateMatrix;
4358 
4359  auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
4360  flutter::ToSkMatrix(finalMatrix), SkSize::Make(300, 300), stack);
4361 
4362  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
4363  withParams:std::move(embeddedViewParams)];
4364 
4365  flutter::SurfaceFrame::FramebufferInfo framebuffer_info;
4366  auto mock_surface = std::make_unique<flutter::SurfaceFrame>(
4367  nullptr, framebuffer_info,
4368  [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; },
4369  [](const flutter::SurfaceFrame& surface_frame) { return true; },
4370  /*frame_size=*/SkISize::Make(800, 600), nullptr, /*display_list_fallback=*/true);
4371  [flutterPlatformViewsController submitFrame:std::move(mock_surface)
4372  withIosContext:std::make_shared<flutter::IOSContextNoop>()];
4373 
4374  UIView* someView = [[UIView alloc] init];
4375  [flutterView addSubview:someView];
4376 
4377  [flutterPlatformViewsController reset];
4378  XCTAssertEqual(flutterView.subviews.count, 1u);
4379  XCTAssertEqual(flutterView.subviews.firstObject, someView);
4380 }
4381 
4382 - (void)testResetClearsPreviousCompositionOrder {
4383  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
4384 
4385  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
4386  /*platform=*/GetDefaultTaskRunner(),
4387  /*raster=*/GetDefaultTaskRunner(),
4388  /*ui=*/GetDefaultTaskRunner(),
4389  /*io=*/GetDefaultTaskRunner());
4390  FlutterPlatformViewsController* flutterPlatformViewsController =
4391  [[FlutterPlatformViewsController alloc] init];
4392  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
4393  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
4394  /*delegate=*/mock_delegate,
4395  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
4396  /*platform_views_controller=*/flutterPlatformViewsController,
4397  /*task_runners=*/runners,
4398  /*worker_task_runner=*/nil,
4399  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
4400 
4403  [flutterPlatformViewsController
4404  registerViewFactory:factory
4405  withId:@"MockFlutterPlatformView"
4406  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
4407  FlutterResult result = ^(id result) {
4408  };
4409  [flutterPlatformViewsController
4411  arguments:@{
4412  @"id" : @2,
4413  @"viewType" : @"MockFlutterPlatformView"
4414  }]
4415  result:result];
4416  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
4417  flutterPlatformViewsController.flutterView = flutterView;
4418  // Create embedded view params
4419  flutter::MutatorsStack stack;
4420  // Layer tree always pushes a screen scale factor to the stack
4421  flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
4422  flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
4423  stack.PushTransform(screenScaleMatrix);
4424  // Push a translate matrix
4425  flutter::DlMatrix translateMatrix = flutter::DlMatrix::MakeTranslation({100, 100});
4426  stack.PushTransform(translateMatrix);
4427  flutter::DlMatrix finalMatrix = screenScaleMatrix * translateMatrix;
4428 
4429  auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
4430  flutter::ToSkMatrix(finalMatrix), SkSize::Make(300, 300), stack);
4431 
4432  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
4433  withParams:std::move(embeddedViewParams)];
4434 
4435  flutter::SurfaceFrame::FramebufferInfo framebuffer_info;
4436  auto mock_surface = std::make_unique<flutter::SurfaceFrame>(
4437  nullptr, framebuffer_info,
4438  [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; },
4439  [](const flutter::SurfaceFrame& surface_frame) { return true; },
4440  /*frame_size=*/SkISize::Make(800, 600), nullptr, /*display_list_fallback=*/true);
4441  [flutterPlatformViewsController submitFrame:std::move(mock_surface)
4442  withIosContext:std::make_shared<flutter::IOSContextNoop>()];
4443 
4444  // The above code should result in previousCompositionOrder having one viewId in it
4445  XCTAssertEqual(flutterPlatformViewsController.previousCompositionOrder.size(), 1ul);
4446 
4447  // reset should clear previousCompositionOrder
4448  [flutterPlatformViewsController reset];
4449 
4450  // previousCompositionOrder should now be empty
4451  XCTAssertEqual(flutterPlatformViewsController.previousCompositionOrder.size(), 0ul);
4452 }
4453 
4454 - (void)testNilPlatformViewDoesntCrash {
4455  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
4456 
4457  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
4458  /*platform=*/GetDefaultTaskRunner(),
4459  /*raster=*/GetDefaultTaskRunner(),
4460  /*ui=*/GetDefaultTaskRunner(),
4461  /*io=*/GetDefaultTaskRunner());
4462  FlutterPlatformViewsController* flutterPlatformViewsController =
4463  [[FlutterPlatformViewsController alloc] init];
4464  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
4465  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
4466  /*delegate=*/mock_delegate,
4467  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
4468  /*platform_views_controller=*/flutterPlatformViewsController,
4469  /*task_runners=*/runners,
4470  /*worker_task_runner=*/nil,
4471  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
4472 
4475  [flutterPlatformViewsController
4476  registerViewFactory:factory
4477  withId:@"MockFlutterPlatformView"
4478  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
4479  FlutterResult result = ^(id result) {
4480  };
4481  [flutterPlatformViewsController
4483  arguments:@{
4484  @"id" : @2,
4485  @"viewType" : @"MockFlutterPlatformView"
4486  }]
4487  result:result];
4488  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
4489  flutterPlatformViewsController.flutterView = flutterView;
4490 
4491  // Create embedded view params
4492  flutter::MutatorsStack stack;
4493  // Layer tree always pushes a screen scale factor to the stack
4494  flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
4495  flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
4496  stack.PushTransform(screenScaleMatrix);
4497  // Push a translate matrix
4498  flutter::DlMatrix translateMatrix = flutter::DlMatrix::MakeTranslation({100, 100});
4499  stack.PushTransform(translateMatrix);
4500  flutter::DlMatrix finalMatrix = screenScaleMatrix * translateMatrix;
4501 
4502  auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
4503  flutter::ToSkMatrix(finalMatrix), SkSize::Make(300, 300), stack);
4504 
4505  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
4506  withParams:std::move(embeddedViewParams)];
4507 
4508  flutter::SurfaceFrame::FramebufferInfo framebuffer_info;
4509  auto mock_surface = std::make_unique<flutter::SurfaceFrame>(
4510  nullptr, framebuffer_info,
4511  [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; },
4512  [](const flutter::SurfaceFrame& surface_frame) { return true; },
4513  /*frame_size=*/SkISize::Make(800, 600), nullptr, /*display_list_fallback=*/true);
4514  [flutterPlatformViewsController submitFrame:std::move(mock_surface)
4515  withIosContext:std::make_shared<flutter::IOSContextNoop>()];
4516 
4517  XCTAssertEqual(flutterView.subviews.count, 1u);
4518 }
4519 
4520 - (void)testFlutterTouchInterceptingViewLinksToAccessibilityContainer {
4521  FlutterTouchInterceptingView* touchInteceptorView = [[FlutterTouchInterceptingView alloc] init];
4522  NSObject* container = [[NSObject alloc] init];
4523  [touchInteceptorView setFlutterAccessibilityContainer:container];
4524  XCTAssertEqualObjects([touchInteceptorView accessibilityContainer], container);
4525 }
4526 
4527 - (void)testLayerPool {
4528  // Create an IOSContext.
4529  FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar"];
4530  [engine run];
4531  XCTAssertTrue(engine.platformView != nullptr);
4532  auto ios_context = engine.platformView->GetIosContext();
4533 
4534  auto pool = flutter::OverlayLayerPool{};
4535 
4536  // Add layers to the pool.
4537  pool.CreateLayer(ios_context, MTLPixelFormatBGRA8Unorm);
4538  XCTAssertEqual(pool.size(), 1u);
4539  pool.CreateLayer(ios_context, MTLPixelFormatBGRA8Unorm);
4540  XCTAssertEqual(pool.size(), 2u);
4541 
4542  // Mark all layers as unused.
4543  pool.RecycleLayers();
4544  XCTAssertEqual(pool.size(), 2u);
4545 
4546  // Free the unused layers. One should remain.
4547  auto unused_layers = pool.RemoveUnusedLayers();
4548  XCTAssertEqual(unused_layers.size(), 2u);
4549  XCTAssertEqual(pool.size(), 1u);
4550 }
4551 
4552 - (void)testFlutterPlatformViewControllerSubmitFramePreservingFrameDamage {
4553  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
4554 
4555  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
4556  /*platform=*/GetDefaultTaskRunner(),
4557  /*raster=*/GetDefaultTaskRunner(),
4558  /*ui=*/GetDefaultTaskRunner(),
4559  /*io=*/GetDefaultTaskRunner());
4560  FlutterPlatformViewsController* flutterPlatformViewsController =
4561  [[FlutterPlatformViewsController alloc] init];
4562  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
4563  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
4564  /*delegate=*/mock_delegate,
4565  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
4566  /*platform_views_controller=*/flutterPlatformViewsController,
4567  /*task_runners=*/runners,
4568  /*worker_task_runner=*/nil,
4569  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
4570 
4571  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
4572  flutterPlatformViewsController.flutterView = flutterView;
4573 
4576  [flutterPlatformViewsController
4577  registerViewFactory:factory
4578  withId:@"MockFlutterPlatformView"
4579  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
4580  FlutterResult result = ^(id result) {
4581  };
4582  [flutterPlatformViewsController
4584  arguments:@{
4585  @"id" : @0,
4586  @"viewType" : @"MockFlutterPlatformView"
4587  }]
4588  result:result];
4589 
4590  // This overwrites `gMockPlatformView` to another view.
4591  [flutterPlatformViewsController
4593  arguments:@{
4594  @"id" : @1,
4595  @"viewType" : @"MockFlutterPlatformView"
4596  }]
4597  result:result];
4598 
4599  [flutterPlatformViewsController beginFrameWithSize:SkISize::Make(300, 300)];
4600  flutter::MutatorsStack stack;
4601  flutter::DlMatrix finalMatrix;
4602  auto embeddedViewParams1 = std::make_unique<flutter::EmbeddedViewParams>(
4603  flutter::ToSkMatrix(finalMatrix), SkSize::Make(300, 300), stack);
4604  [flutterPlatformViewsController prerollCompositeEmbeddedView:0
4605  withParams:std::move(embeddedViewParams1)];
4606 
4607  auto embeddedViewParams2 = std::make_unique<flutter::EmbeddedViewParams>(
4608  flutter::ToSkMatrix(finalMatrix), SkSize::Make(500, 500), stack);
4609  [flutterPlatformViewsController prerollCompositeEmbeddedView:1
4610  withParams:std::move(embeddedViewParams2)];
4611 
4612  flutter::SurfaceFrame::FramebufferInfo framebuffer_info;
4613  std::optional<flutter::SurfaceFrame::SubmitInfo> submit_info;
4614  auto mock_surface = std::make_unique<flutter::SurfaceFrame>(
4615  nullptr, framebuffer_info,
4616  [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; },
4617  [&](const flutter::SurfaceFrame& surface_frame) {
4618  submit_info = surface_frame.submit_info();
4619  return true;
4620  },
4621  /*frame_size=*/SkISize::Make(800, 600), nullptr,
4622  /*display_list_fallback=*/true);
4623  mock_surface->set_submit_info({
4624  .frame_damage = SkIRect::MakeWH(800, 600),
4625  .buffer_damage = SkIRect::MakeWH(400, 600),
4626  });
4627 
4628  [flutterPlatformViewsController submitFrame:std::move(mock_surface)
4629  withIosContext:std::make_shared<flutter::IOSContextNoop>()];
4630 
4631  XCTAssertTrue(submit_info.has_value());
4632  XCTAssertEqual(*submit_info->frame_damage, SkIRect::MakeWH(800, 600));
4633  XCTAssertEqual(*submit_info->buffer_damage, SkIRect::MakeWH(400, 600));
4634 }
4635 
4636 - (void)testClipSuperellipse {
4637  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
4638 
4639  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
4640  /*platform=*/GetDefaultTaskRunner(),
4641  /*raster=*/GetDefaultTaskRunner(),
4642  /*ui=*/GetDefaultTaskRunner(),
4643  /*io=*/GetDefaultTaskRunner());
4644  FlutterPlatformViewsController* flutterPlatformViewsController =
4645  [[FlutterPlatformViewsController alloc] init];
4646  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
4647  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
4648  /*delegate=*/mock_delegate,
4649  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
4650  /*platform_views_controller=*/flutterPlatformViewsController,
4651  /*task_runners=*/runners,
4652  /*worker_task_runner=*/nil,
4653  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
4654 
4657  [flutterPlatformViewsController
4658  registerViewFactory:factory
4659  withId:@"MockFlutterPlatformView"
4660  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
4661  FlutterResult result = ^(id result) {
4662  };
4663  [flutterPlatformViewsController
4665  arguments:@{
4666  @"id" : @2,
4667  @"viewType" : @"MockFlutterPlatformView"
4668  }]
4669  result:result];
4670 
4671  XCTAssertNotNil(gMockPlatformView);
4672 
4673  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
4674  flutterPlatformViewsController.flutterView = flutterView;
4675  // Create embedded view params
4676  flutter::MutatorsStack stack;
4677  // Layer tree always pushes a screen scale factor to the stack
4678  flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
4679  flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
4680  stack.PushTransform(screenScaleMatrix);
4681  // Push a clip superellipse
4682  flutter::DlRect rect = flutter::DlRect::MakeXYWH(3, 3, 5, 5);
4683  stack.PushClipRSE(flutter::DlRoundSuperellipse::MakeOval(rect));
4684 
4685  auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
4686  flutter::ToSkMatrix(screenScaleMatrix), SkSize::Make(10, 10), stack);
4687 
4688  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
4689  withParams:std::move(embeddedViewParams)];
4690  [flutterPlatformViewsController
4691  compositeView:2
4692  withParams:[flutterPlatformViewsController compositionParamsForView:2]];
4693 
4694  gMockPlatformView.backgroundColor = UIColor.redColor;
4695  XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]);
4696  ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
4697  [flutterView addSubview:childClippingView];
4698 
4699  [flutterView setNeedsLayout];
4700  [flutterView layoutIfNeeded];
4701 
4702  CGPoint corners[] = {CGPointMake(rect.GetLeft(), rect.GetTop()),
4703  CGPointMake(rect.GetRight(), rect.GetTop()),
4704  CGPointMake(rect.GetLeft(), rect.GetBottom()),
4705  CGPointMake(rect.GetRight(), rect.GetBottom())};
4706  for (auto point : corners) {
4707  int alpha = [self alphaOfPoint:point onView:flutterView];
4708  XCTAssertNotEqual(alpha, 255);
4709  }
4710  CGPoint center =
4711  CGPointMake(rect.GetLeft() + rect.GetWidth() / 2, rect.GetTop() + rect.GetHeight() / 2);
4712  int alpha = [self alphaOfPoint:center onView:flutterView];
4713  XCTAssertEqual(alpha, 255);
4714 }
4715 
4716 @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