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  if (@available(iOS 18.2, *)) {
3201  // Since we remove and add back delayingRecognizer, it would be reordered to the last.
3202  XCTAssertEqual(touchInteceptorView.gestureRecognizers[0], forwardingRecognizer);
3203  XCTAssertEqual(touchInteceptorView.gestureRecognizers[1], delayingRecognizer);
3204  } else {
3205  XCTAssertEqual(touchInteceptorView.gestureRecognizers[0], delayingRecognizer);
3206  XCTAssertEqual(touchInteceptorView.gestureRecognizers[1], forwardingRecognizer);
3207  }
3208 }
3209 
3210 - (void)
3211  testFlutterPlatformViewBlockGestureUnderEagerPolicyShouldRemoveAndAddBackDelayingRecognizerForWrapperWebView {
3212  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
3213 
3214  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
3215  /*platform=*/GetDefaultTaskRunner(),
3216  /*raster=*/GetDefaultTaskRunner(),
3217  /*ui=*/GetDefaultTaskRunner(),
3218  /*io=*/GetDefaultTaskRunner());
3219  FlutterPlatformViewsController* flutterPlatformViewsController =
3220  [[FlutterPlatformViewsController alloc] init];
3221  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
3222  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
3223  /*delegate=*/mock_delegate,
3224  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
3225  /*platform_views_controller=*/flutterPlatformViewsController,
3226  /*task_runners=*/runners,
3227  /*worker_task_runner=*/nil,
3228  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
3229 
3232  [flutterPlatformViewsController
3233  registerViewFactory:factory
3234  withId:@"MockWrapperWebView"
3235  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
3236  FlutterResult result = ^(id result) {
3237  };
3238  [flutterPlatformViewsController
3240  methodCallWithMethodName:@"create"
3241  arguments:@{@"id" : @2, @"viewType" : @"MockWrapperWebView"}]
3242  result:result];
3243 
3244  XCTAssertNotNil(gMockPlatformView);
3245 
3246  // Find touch inteceptor view
3247  UIView* touchInteceptorView = gMockPlatformView;
3248  while (touchInteceptorView != nil &&
3249  ![touchInteceptorView isKindOfClass:[FlutterTouchInterceptingView class]]) {
3250  touchInteceptorView = touchInteceptorView.superview;
3251  }
3252  XCTAssertNotNil(touchInteceptorView);
3253 
3254  XCTAssert(touchInteceptorView.gestureRecognizers.count == 2);
3255  UIGestureRecognizer* delayingRecognizer = touchInteceptorView.gestureRecognizers[0];
3256  UIGestureRecognizer* forwardingRecognizer = touchInteceptorView.gestureRecognizers[1];
3257 
3258  XCTAssert([delayingRecognizer isKindOfClass:[FlutterDelayingGestureRecognizer class]]);
3259  XCTAssert([forwardingRecognizer isKindOfClass:[ForwardingGestureRecognizer class]]);
3260 
3262 
3263  if (@available(iOS 18.2, *)) {
3264  // Since we remove and add back delayingRecognizer, it would be reordered to the last.
3265  XCTAssertEqual(touchInteceptorView.gestureRecognizers[0], forwardingRecognizer);
3266  XCTAssertEqual(touchInteceptorView.gestureRecognizers[1], delayingRecognizer);
3267  } else {
3268  XCTAssertEqual(touchInteceptorView.gestureRecognizers[0], delayingRecognizer);
3269  XCTAssertEqual(touchInteceptorView.gestureRecognizers[1], forwardingRecognizer);
3270  }
3271 }
3272 
3273 - (void)
3274  testFlutterPlatformViewBlockGestureUnderEagerPolicyShouldNotRemoveAndAddBackDelayingRecognizerForNestedWrapperWebView {
3275  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
3276 
3277  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
3278  /*platform=*/GetDefaultTaskRunner(),
3279  /*raster=*/GetDefaultTaskRunner(),
3280  /*ui=*/GetDefaultTaskRunner(),
3281  /*io=*/GetDefaultTaskRunner());
3282  FlutterPlatformViewsController* flutterPlatformViewsController =
3283  [[FlutterPlatformViewsController alloc] init];
3284  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
3285  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
3286  /*delegate=*/mock_delegate,
3287  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
3288  /*platform_views_controller=*/flutterPlatformViewsController,
3289  /*task_runners=*/runners,
3290  /*worker_task_runner=*/nil,
3291  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
3292 
3295  [flutterPlatformViewsController
3296  registerViewFactory:factory
3297  withId:@"MockNestedWrapperWebView"
3298  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
3299  FlutterResult result = ^(id result) {
3300  };
3301  [flutterPlatformViewsController
3303  arguments:@{
3304  @"id" : @2,
3305  @"viewType" : @"MockNestedWrapperWebView"
3306  }]
3307  result:result];
3308 
3309  XCTAssertNotNil(gMockPlatformView);
3310 
3311  // Find touch inteceptor view
3312  UIView* touchInteceptorView = gMockPlatformView;
3313  while (touchInteceptorView != nil &&
3314  ![touchInteceptorView isKindOfClass:[FlutterTouchInterceptingView class]]) {
3315  touchInteceptorView = touchInteceptorView.superview;
3316  }
3317  XCTAssertNotNil(touchInteceptorView);
3318 
3319  XCTAssert(touchInteceptorView.gestureRecognizers.count == 2);
3320  UIGestureRecognizer* delayingRecognizer = touchInteceptorView.gestureRecognizers[0];
3321  UIGestureRecognizer* forwardingRecognizer = touchInteceptorView.gestureRecognizers[1];
3322 
3323  XCTAssert([delayingRecognizer isKindOfClass:[FlutterDelayingGestureRecognizer class]]);
3324  XCTAssert([forwardingRecognizer isKindOfClass:[ForwardingGestureRecognizer class]]);
3325 
3327 
3328  XCTAssertEqual(touchInteceptorView.gestureRecognizers[0], delayingRecognizer);
3329  XCTAssertEqual(touchInteceptorView.gestureRecognizers[1], forwardingRecognizer);
3330 }
3331 
3332 - (void)
3333  testFlutterPlatformViewBlockGestureUnderEagerPolicyShouldNotRemoveAndAddBackDelayingRecognizerForNonWebView {
3334  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
3335 
3336  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
3337  /*platform=*/GetDefaultTaskRunner(),
3338  /*raster=*/GetDefaultTaskRunner(),
3339  /*ui=*/GetDefaultTaskRunner(),
3340  /*io=*/GetDefaultTaskRunner());
3341  FlutterPlatformViewsController* flutterPlatformViewsController =
3342  [[FlutterPlatformViewsController alloc] init];
3343  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
3344  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
3345  /*delegate=*/mock_delegate,
3346  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
3347  /*platform_views_controller=*/flutterPlatformViewsController,
3348  /*task_runners=*/runners,
3349  /*worker_task_runner=*/nil,
3350  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
3351 
3354  [flutterPlatformViewsController
3355  registerViewFactory:factory
3356  withId:@"MockFlutterPlatformView"
3357  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
3358  FlutterResult result = ^(id result) {
3359  };
3360  [flutterPlatformViewsController
3362  arguments:@{
3363  @"id" : @2,
3364  @"viewType" : @"MockFlutterPlatformView"
3365  }]
3366  result:result];
3367 
3368  XCTAssertNotNil(gMockPlatformView);
3369 
3370  // Find touch inteceptor view
3371  UIView* touchInteceptorView = gMockPlatformView;
3372  while (touchInteceptorView != nil &&
3373  ![touchInteceptorView isKindOfClass:[FlutterTouchInterceptingView class]]) {
3374  touchInteceptorView = touchInteceptorView.superview;
3375  }
3376  XCTAssertNotNil(touchInteceptorView);
3377 
3378  XCTAssert(touchInteceptorView.gestureRecognizers.count == 2);
3379  UIGestureRecognizer* delayingRecognizer = touchInteceptorView.gestureRecognizers[0];
3380  UIGestureRecognizer* forwardingRecognizer = touchInteceptorView.gestureRecognizers[1];
3381 
3382  XCTAssert([delayingRecognizer isKindOfClass:[FlutterDelayingGestureRecognizer class]]);
3383  XCTAssert([forwardingRecognizer isKindOfClass:[ForwardingGestureRecognizer class]]);
3384 
3386 
3387  XCTAssertEqual(touchInteceptorView.gestureRecognizers[0], delayingRecognizer);
3388  XCTAssertEqual(touchInteceptorView.gestureRecognizers[1], forwardingRecognizer);
3389 }
3390 
3391 - (void)testFlutterPlatformViewControllerSubmitFrameWithoutFlutterViewNotCrashing {
3392  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
3393 
3394  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
3395  /*platform=*/GetDefaultTaskRunner(),
3396  /*raster=*/GetDefaultTaskRunner(),
3397  /*ui=*/GetDefaultTaskRunner(),
3398  /*io=*/GetDefaultTaskRunner());
3399  FlutterPlatformViewsController* flutterPlatformViewsController =
3400  [[FlutterPlatformViewsController alloc] init];
3401  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
3402  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
3403  /*delegate=*/mock_delegate,
3404  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
3405  /*platform_views_controller=*/flutterPlatformViewsController,
3406  /*task_runners=*/runners,
3407  /*worker_task_runner=*/nil,
3408  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
3409 
3412  [flutterPlatformViewsController
3413  registerViewFactory:factory
3414  withId:@"MockFlutterPlatformView"
3415  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
3416  FlutterResult result = ^(id result) {
3417  };
3418  [flutterPlatformViewsController
3420  arguments:@{
3421  @"id" : @2,
3422  @"viewType" : @"MockFlutterPlatformView"
3423  }]
3424  result:result];
3425 
3426  XCTAssertNotNil(gMockPlatformView);
3427 
3428  // Create embedded view params
3429  flutter::MutatorsStack stack;
3430  flutter::DlMatrix finalMatrix;
3431 
3432  auto embeddedViewParams_1 = std::make_unique<flutter::EmbeddedViewParams>(
3433  flutter::ToSkMatrix(finalMatrix), SkSize::Make(300, 300), stack);
3434 
3435  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
3436  withParams:std::move(embeddedViewParams_1)];
3437  [flutterPlatformViewsController
3438  compositeView:2
3439  withParams:[flutterPlatformViewsController compositionParamsForView:2]];
3440 
3441  flutter::SurfaceFrame::FramebufferInfo framebuffer_info;
3442  auto mock_surface = std::make_unique<flutter::SurfaceFrame>(
3443  nullptr, framebuffer_info,
3444  [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return false; },
3445  [](const flutter::SurfaceFrame& surface_frame) { return true; },
3446  /*frame_size=*/SkISize::Make(800, 600));
3447  XCTAssertFalse([flutterPlatformViewsController
3448  submitFrame:std::move(mock_surface)
3449  withIosContext:std::make_shared<flutter::IOSContextNoop>()]);
3450 
3451  auto embeddedViewParams_2 = std::make_unique<flutter::EmbeddedViewParams>(
3452  flutter::ToSkMatrix(finalMatrix), SkSize::Make(300, 300), stack);
3453  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
3454  withParams:std::move(embeddedViewParams_2)];
3455  [flutterPlatformViewsController
3456  compositeView:2
3457  withParams:[flutterPlatformViewsController compositionParamsForView:2]];
3458 
3459  auto mock_surface_submit_true = std::make_unique<flutter::SurfaceFrame>(
3460  nullptr, framebuffer_info,
3461  [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; },
3462  [](const flutter::SurfaceFrame& surface_frame) { return true; },
3463  /*frame_size=*/SkISize::Make(800, 600));
3464  XCTAssertTrue([flutterPlatformViewsController
3465  submitFrame:std::move(mock_surface_submit_true)
3466  withIosContext:std::make_shared<flutter::IOSContextNoop>()]);
3467 }
3468 
3469 - (void)
3470  testFlutterPlatformViewControllerResetDeallocsPlatformViewWhenRootViewsNotBindedToFlutterView {
3471  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
3472 
3473  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
3474  /*platform=*/GetDefaultTaskRunner(),
3475  /*raster=*/GetDefaultTaskRunner(),
3476  /*ui=*/GetDefaultTaskRunner(),
3477  /*io=*/GetDefaultTaskRunner());
3478  FlutterPlatformViewsController* flutterPlatformViewsController =
3479  [[FlutterPlatformViewsController alloc] init];
3480  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
3481  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
3482  /*delegate=*/mock_delegate,
3483  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
3484  /*platform_views_controller=*/flutterPlatformViewsController,
3485  /*task_runners=*/runners,
3486  /*worker_task_runner=*/nil,
3487  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
3488 
3489  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
3490  flutterPlatformViewsController.flutterView = flutterView;
3491 
3494  [flutterPlatformViewsController
3495  registerViewFactory:factory
3496  withId:@"MockFlutterPlatformView"
3497  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
3498  FlutterResult result = ^(id result) {
3499  };
3500  // autorelease pool to trigger an autorelease for all the root_views_ and touch_interceptors_.
3501  @autoreleasepool {
3502  [flutterPlatformViewsController
3504  arguments:@{
3505  @"id" : @2,
3506  @"viewType" : @"MockFlutterPlatformView"
3507  }]
3508  result:result];
3509 
3510  flutter::MutatorsStack stack;
3511  flutter::DlMatrix finalMatrix;
3512  auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
3513  flutter::ToSkMatrix(finalMatrix), SkSize::Make(300, 300), stack);
3514  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
3515  withParams:std::move(embeddedViewParams)];
3516 
3517  // Not calling |[flutterPlatformViewsController submitFrame:withIosContext:]| so that
3518  // the platform views are not added to flutter_view_.
3519 
3520  XCTAssertNotNil(gMockPlatformView);
3521  [flutterPlatformViewsController reset];
3522  }
3523  XCTAssertNil(gMockPlatformView);
3524 }
3525 
3526 - (void)testFlutterPlatformViewControllerBeginFrameShouldResetCompisitionOrder {
3527  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
3528 
3529  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
3530  /*platform=*/GetDefaultTaskRunner(),
3531  /*raster=*/GetDefaultTaskRunner(),
3532  /*ui=*/GetDefaultTaskRunner(),
3533  /*io=*/GetDefaultTaskRunner());
3534  FlutterPlatformViewsController* flutterPlatformViewsController =
3535  [[FlutterPlatformViewsController alloc] init];
3536  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
3537  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
3538  /*delegate=*/mock_delegate,
3539  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
3540  /*platform_views_controller=*/flutterPlatformViewsController,
3541  /*task_runners=*/runners,
3542  /*worker_task_runner=*/nil,
3543  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
3544 
3545  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
3546  flutterPlatformViewsController.flutterView = flutterView;
3547 
3550  [flutterPlatformViewsController
3551  registerViewFactory:factory
3552  withId:@"MockFlutterPlatformView"
3553  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
3554  FlutterResult result = ^(id result) {
3555  };
3556 
3557  [flutterPlatformViewsController
3559  arguments:@{
3560  @"id" : @0,
3561  @"viewType" : @"MockFlutterPlatformView"
3562  }]
3563  result:result];
3564 
3565  // First frame, |embeddedViewCount| is not empty after composite.
3566  [flutterPlatformViewsController beginFrameWithSize:SkISize::Make(300, 300)];
3567  flutter::MutatorsStack stack;
3568  flutter::DlMatrix finalMatrix;
3569  auto embeddedViewParams1 = std::make_unique<flutter::EmbeddedViewParams>(
3570  flutter::ToSkMatrix(finalMatrix), SkSize::Make(300, 300), stack);
3571  [flutterPlatformViewsController prerollCompositeEmbeddedView:0
3572  withParams:std::move(embeddedViewParams1)];
3573  [flutterPlatformViewsController
3574  compositeView:0
3575  withParams:[flutterPlatformViewsController compositionParamsForView:0]];
3576 
3577  XCTAssertEqual(flutterPlatformViewsController.embeddedViewCount, 1UL);
3578 
3579  // Second frame, |embeddedViewCount| should be empty at the start
3580  [flutterPlatformViewsController beginFrameWithSize:SkISize::Make(300, 300)];
3581  XCTAssertEqual(flutterPlatformViewsController.embeddedViewCount, 0UL);
3582 
3583  auto embeddedViewParams2 = std::make_unique<flutter::EmbeddedViewParams>(
3584  flutter::ToSkMatrix(finalMatrix), SkSize::Make(300, 300), stack);
3585  [flutterPlatformViewsController prerollCompositeEmbeddedView:0
3586  withParams:std::move(embeddedViewParams2)];
3587  [flutterPlatformViewsController
3588  compositeView:0
3589  withParams:[flutterPlatformViewsController compositionParamsForView:0]];
3590 
3591  XCTAssertEqual(flutterPlatformViewsController.embeddedViewCount, 1UL);
3592 }
3593 
3594 - (void)
3595  testFlutterPlatformViewControllerSubmitFrameShouldOrderSubviewsCorrectlyWithDifferentViewHierarchy {
3596  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
3597 
3598  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
3599  /*platform=*/GetDefaultTaskRunner(),
3600  /*raster=*/GetDefaultTaskRunner(),
3601  /*ui=*/GetDefaultTaskRunner(),
3602  /*io=*/GetDefaultTaskRunner());
3603  FlutterPlatformViewsController* flutterPlatformViewsController =
3604  [[FlutterPlatformViewsController alloc] init];
3605  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
3606  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
3607  /*delegate=*/mock_delegate,
3608  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
3609  /*platform_views_controller=*/flutterPlatformViewsController,
3610  /*task_runners=*/runners,
3611  /*worker_task_runner=*/nil,
3612  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
3613 
3614  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
3615  flutterPlatformViewsController.flutterView = flutterView;
3616 
3619  [flutterPlatformViewsController
3620  registerViewFactory:factory
3621  withId:@"MockFlutterPlatformView"
3622  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
3623  FlutterResult result = ^(id result) {
3624  };
3625  [flutterPlatformViewsController
3627  arguments:@{
3628  @"id" : @0,
3629  @"viewType" : @"MockFlutterPlatformView"
3630  }]
3631  result:result];
3632  UIView* view1 = gMockPlatformView;
3633 
3634  // This overwrites `gMockPlatformView` to another view.
3635  [flutterPlatformViewsController
3637  arguments:@{
3638  @"id" : @1,
3639  @"viewType" : @"MockFlutterPlatformView"
3640  }]
3641  result:result];
3642  UIView* view2 = gMockPlatformView;
3643 
3644  [flutterPlatformViewsController beginFrameWithSize:SkISize::Make(300, 300)];
3645  flutter::MutatorsStack stack;
3646  flutter::DlMatrix finalMatrix;
3647  auto embeddedViewParams1 = std::make_unique<flutter::EmbeddedViewParams>(
3648  flutter::ToSkMatrix(finalMatrix), SkSize::Make(300, 300), stack);
3649  [flutterPlatformViewsController prerollCompositeEmbeddedView:0
3650  withParams:std::move(embeddedViewParams1)];
3651 
3652  auto embeddedViewParams2 = std::make_unique<flutter::EmbeddedViewParams>(
3653  flutter::ToSkMatrix(finalMatrix), SkSize::Make(500, 500), stack);
3654  [flutterPlatformViewsController prerollCompositeEmbeddedView:1
3655  withParams:std::move(embeddedViewParams2)];
3656 
3657  flutter::SurfaceFrame::FramebufferInfo framebuffer_info;
3658  auto mock_surface = std::make_unique<flutter::SurfaceFrame>(
3659  nullptr, framebuffer_info,
3660  [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; },
3661  [](const flutter::SurfaceFrame& surface_frame) { return true; },
3662  /*frame_size=*/SkISize::Make(800, 600), nullptr, /*display_list_fallback=*/true);
3663  XCTAssertTrue([flutterPlatformViewsController
3664  submitFrame:std::move(mock_surface)
3665  withIosContext:std::make_shared<flutter::IOSContextNoop>()]);
3666 
3667  // platform view is wrapped by touch interceptor, which itself is wrapped by clipping view.
3668  UIView* clippingView1 = view1.superview.superview;
3669  UIView* clippingView2 = view2.superview.superview;
3670  XCTAssertTrue([flutterView.subviews indexOfObject:clippingView1] <
3671  [flutterView.subviews indexOfObject:clippingView2],
3672  @"The first clipping view should be added before the second clipping view.");
3673 
3674  // Need to recreate these params since they are `std::move`ed.
3675  [flutterPlatformViewsController beginFrameWithSize:SkISize::Make(300, 300)];
3676  // Process the second frame in the opposite order.
3677  embeddedViewParams2 = std::make_unique<flutter::EmbeddedViewParams>(
3678  flutter::ToSkMatrix(finalMatrix), SkSize::Make(500, 500), stack);
3679  [flutterPlatformViewsController prerollCompositeEmbeddedView:1
3680  withParams:std::move(embeddedViewParams2)];
3681 
3682  embeddedViewParams1 = std::make_unique<flutter::EmbeddedViewParams>(
3683  flutter::ToSkMatrix(finalMatrix), SkSize::Make(300, 300), stack);
3684  [flutterPlatformViewsController prerollCompositeEmbeddedView:0
3685  withParams:std::move(embeddedViewParams1)];
3686 
3687  mock_surface = std::make_unique<flutter::SurfaceFrame>(
3688  nullptr, framebuffer_info,
3689  [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; },
3690  [](const flutter::SurfaceFrame& surface_frame) { return true; },
3691  /*frame_size=*/SkISize::Make(800, 600), nullptr, /*display_list_fallback=*/true);
3692  XCTAssertTrue([flutterPlatformViewsController
3693  submitFrame:std::move(mock_surface)
3694  withIosContext:std::make_shared<flutter::IOSContextNoop>()]);
3695 
3696  XCTAssertTrue([flutterView.subviews indexOfObject:clippingView1] >
3697  [flutterView.subviews indexOfObject:clippingView2],
3698  @"The first clipping view should be added after the second clipping view.");
3699 }
3700 
3701 - (void)
3702  testFlutterPlatformViewControllerSubmitFrameShouldOrderSubviewsCorrectlyWithSameViewHierarchy {
3703  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
3704 
3705  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
3706  /*platform=*/GetDefaultTaskRunner(),
3707  /*raster=*/GetDefaultTaskRunner(),
3708  /*ui=*/GetDefaultTaskRunner(),
3709  /*io=*/GetDefaultTaskRunner());
3710  FlutterPlatformViewsController* flutterPlatformViewsController =
3711  [[FlutterPlatformViewsController alloc] init];
3712  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
3713  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
3714  /*delegate=*/mock_delegate,
3715  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
3716  /*platform_views_controller=*/flutterPlatformViewsController,
3717  /*task_runners=*/runners,
3718  /*worker_task_runner=*/nil,
3719  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
3720 
3721  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
3722  flutterPlatformViewsController.flutterView = flutterView;
3723 
3726  [flutterPlatformViewsController
3727  registerViewFactory:factory
3728  withId:@"MockFlutterPlatformView"
3729  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
3730  FlutterResult result = ^(id result) {
3731  };
3732  [flutterPlatformViewsController
3734  arguments:@{
3735  @"id" : @0,
3736  @"viewType" : @"MockFlutterPlatformView"
3737  }]
3738  result:result];
3739  UIView* view1 = gMockPlatformView;
3740 
3741  // This overwrites `gMockPlatformView` to another view.
3742  [flutterPlatformViewsController
3744  arguments:@{
3745  @"id" : @1,
3746  @"viewType" : @"MockFlutterPlatformView"
3747  }]
3748  result:result];
3749  UIView* view2 = gMockPlatformView;
3750 
3751  [flutterPlatformViewsController beginFrameWithSize:SkISize::Make(300, 300)];
3752  flutter::MutatorsStack stack;
3753  flutter::DlMatrix finalMatrix;
3754  auto embeddedViewParams1 = std::make_unique<flutter::EmbeddedViewParams>(
3755  flutter::ToSkMatrix(finalMatrix), SkSize::Make(300, 300), stack);
3756  [flutterPlatformViewsController prerollCompositeEmbeddedView:0
3757  withParams:std::move(embeddedViewParams1)];
3758 
3759  auto embeddedViewParams2 = std::make_unique<flutter::EmbeddedViewParams>(
3760  flutter::ToSkMatrix(finalMatrix), SkSize::Make(500, 500), stack);
3761  [flutterPlatformViewsController prerollCompositeEmbeddedView:1
3762  withParams:std::move(embeddedViewParams2)];
3763 
3764  flutter::SurfaceFrame::FramebufferInfo framebuffer_info;
3765  auto mock_surface = std::make_unique<flutter::SurfaceFrame>(
3766  nullptr, framebuffer_info,
3767  [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; },
3768  [](const flutter::SurfaceFrame& surface_frame) { return true; },
3769  /*frame_size=*/SkISize::Make(800, 600), nullptr, /*display_list_fallback=*/true);
3770  XCTAssertTrue([flutterPlatformViewsController
3771  submitFrame:std::move(mock_surface)
3772  withIosContext:std::make_shared<flutter::IOSContextNoop>()]);
3773 
3774  // platform view is wrapped by touch interceptor, which itself is wrapped by clipping view.
3775  UIView* clippingView1 = view1.superview.superview;
3776  UIView* clippingView2 = view2.superview.superview;
3777  XCTAssertTrue([flutterView.subviews indexOfObject:clippingView1] <
3778  [flutterView.subviews indexOfObject:clippingView2],
3779  @"The first clipping view should be added before the second clipping view.");
3780 
3781  // Need to recreate these params since they are `std::move`ed.
3782  [flutterPlatformViewsController beginFrameWithSize:SkISize::Make(300, 300)];
3783  // Process the second frame in the same order.
3784  embeddedViewParams1 = std::make_unique<flutter::EmbeddedViewParams>(
3785  flutter::ToSkMatrix(finalMatrix), SkSize::Make(300, 300), stack);
3786  [flutterPlatformViewsController prerollCompositeEmbeddedView:0
3787  withParams:std::move(embeddedViewParams1)];
3788 
3789  embeddedViewParams2 = std::make_unique<flutter::EmbeddedViewParams>(
3790  flutter::ToSkMatrix(finalMatrix), SkSize::Make(500, 500), stack);
3791  [flutterPlatformViewsController prerollCompositeEmbeddedView:1
3792  withParams:std::move(embeddedViewParams2)];
3793 
3794  mock_surface = std::make_unique<flutter::SurfaceFrame>(
3795  nullptr, framebuffer_info,
3796  [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; },
3797  [](const flutter::SurfaceFrame& surface_frame) { return true; },
3798  /*frame_size=*/SkISize::Make(800, 600), nullptr, /*display_list_fallback=*/true);
3799  XCTAssertTrue([flutterPlatformViewsController
3800  submitFrame:std::move(mock_surface)
3801  withIosContext:std::make_shared<flutter::IOSContextNoop>()]);
3802 
3803  XCTAssertTrue([flutterView.subviews indexOfObject:clippingView1] <
3804  [flutterView.subviews indexOfObject:clippingView2],
3805  @"The first clipping view should be added before the second clipping view.");
3806 }
3807 
3808 - (int)alphaOfPoint:(CGPoint)point onView:(UIView*)view {
3809  unsigned char pixel[4] = {0};
3810 
3811  CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
3812 
3813  // Draw the pixel on `point` in the context.
3814  CGContextRef context = CGBitmapContextCreate(
3815  pixel, 1, 1, 8, 4, colorSpace, kCGBitmapAlphaInfoMask & kCGImageAlphaPremultipliedLast);
3816  CGContextTranslateCTM(context, -point.x, -point.y);
3817  [view.layer renderInContext:context];
3818 
3819  CGContextRelease(context);
3820  CGColorSpaceRelease(colorSpace);
3821  // Get the alpha from the pixel that we just rendered.
3822  return pixel[3];
3823 }
3824 
3825 - (void)testHasFirstResponderInViewHierarchySubtree_viewItselfBecomesFirstResponder {
3826  // For view to become the first responder, it must be a descendant of a UIWindow
3827  UIWindow* window = [[UIWindow alloc] init];
3828  UITextField* textField = [[UITextField alloc] init];
3829  [window addSubview:textField];
3830 
3831  [textField becomeFirstResponder];
3832  XCTAssertTrue(textField.isFirstResponder);
3833  XCTAssertTrue(textField.flt_hasFirstResponderInViewHierarchySubtree);
3834  [textField resignFirstResponder];
3835  XCTAssertFalse(textField.isFirstResponder);
3836  XCTAssertFalse(textField.flt_hasFirstResponderInViewHierarchySubtree);
3837 }
3838 
3839 - (void)testHasFirstResponderInViewHierarchySubtree_descendantViewBecomesFirstResponder {
3840  // For view to become the first responder, it must be a descendant of a UIWindow
3841  UIWindow* window = [[UIWindow alloc] init];
3842  UIView* view = [[UIView alloc] init];
3843  UIView* childView = [[UIView alloc] init];
3844  UITextField* textField = [[UITextField alloc] init];
3845  [window addSubview:view];
3846  [view addSubview:childView];
3847  [childView addSubview:textField];
3848 
3849  [textField becomeFirstResponder];
3850  XCTAssertTrue(textField.isFirstResponder);
3851  XCTAssertTrue(view.flt_hasFirstResponderInViewHierarchySubtree);
3852  [textField resignFirstResponder];
3853  XCTAssertFalse(textField.isFirstResponder);
3854  XCTAssertFalse(view.flt_hasFirstResponderInViewHierarchySubtree);
3855 }
3856 
3857 - (void)testFlutterClippingMaskViewPoolReuseViewsAfterRecycle {
3858  FlutterClippingMaskViewPool* pool = [[FlutterClippingMaskViewPool alloc] initWithCapacity:2];
3859  FlutterClippingMaskView* view1 = [pool getMaskViewWithFrame:CGRectZero];
3860  FlutterClippingMaskView* view2 = [pool getMaskViewWithFrame:CGRectZero];
3861  [pool insertViewToPoolIfNeeded:view1];
3862  [pool insertViewToPoolIfNeeded:view2];
3863  CGRect newRect = CGRectMake(0, 0, 10, 10);
3864  FlutterClippingMaskView* view3 = [pool getMaskViewWithFrame:newRect];
3865  FlutterClippingMaskView* view4 = [pool getMaskViewWithFrame:newRect];
3866  // view3 and view4 should randomly get either of view1 and view2.
3867  NSSet* set1 = [NSSet setWithObjects:view1, view2, nil];
3868  NSSet* set2 = [NSSet setWithObjects:view3, view4, nil];
3869  XCTAssertEqualObjects(set1, set2);
3870  XCTAssertTrue(CGRectEqualToRect(view3.frame, newRect));
3871  XCTAssertTrue(CGRectEqualToRect(view4.frame, newRect));
3872 }
3873 
3874 - (void)testFlutterClippingMaskViewPoolAllocsNewMaskViewsAfterReachingCapacity {
3875  FlutterClippingMaskViewPool* pool = [[FlutterClippingMaskViewPool alloc] initWithCapacity:2];
3876  FlutterClippingMaskView* view1 = [pool getMaskViewWithFrame:CGRectZero];
3877  FlutterClippingMaskView* view2 = [pool getMaskViewWithFrame:CGRectZero];
3878  FlutterClippingMaskView* view3 = [pool getMaskViewWithFrame:CGRectZero];
3879  XCTAssertNotEqual(view1, view3);
3880  XCTAssertNotEqual(view2, view3);
3881 }
3882 
3883 - (void)testMaskViewsReleasedWhenPoolIsReleased {
3884  __weak UIView* weakView;
3885  @autoreleasepool {
3886  FlutterClippingMaskViewPool* pool = [[FlutterClippingMaskViewPool alloc] initWithCapacity:2];
3887  FlutterClippingMaskView* view = [pool getMaskViewWithFrame:CGRectZero];
3888  weakView = view;
3889  XCTAssertNotNil(weakView);
3890  }
3891  XCTAssertNil(weakView);
3892 }
3893 
3894 - (void)testClipMaskViewIsReused {
3895  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
3896 
3897  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
3898  /*platform=*/GetDefaultTaskRunner(),
3899  /*raster=*/GetDefaultTaskRunner(),
3900  /*ui=*/GetDefaultTaskRunner(),
3901  /*io=*/GetDefaultTaskRunner());
3902  FlutterPlatformViewsController* flutterPlatformViewsController =
3903  [[FlutterPlatformViewsController alloc] init];
3904  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
3905  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
3906  /*delegate=*/mock_delegate,
3907  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
3908  /*platform_views_controller=*/flutterPlatformViewsController,
3909  /*task_runners=*/runners,
3910  /*worker_task_runner=*/nil,
3911  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
3912 
3915  [flutterPlatformViewsController
3916  registerViewFactory:factory
3917  withId:@"MockFlutterPlatformView"
3918  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
3919  FlutterResult result = ^(id result) {
3920  };
3921  [flutterPlatformViewsController
3923  arguments:@{
3924  @"id" : @1,
3925  @"viewType" : @"MockFlutterPlatformView"
3926  }]
3927  result:result];
3928 
3929  XCTAssertNotNil(gMockPlatformView);
3930  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
3931  flutterPlatformViewsController.flutterView = flutterView;
3932  // Create embedded view params
3933  flutter::MutatorsStack stack1;
3934  // Layer tree always pushes a screen scale factor to the stack
3935  flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
3936  flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
3937  stack1.PushTransform(screenScaleMatrix);
3938  // Push a clip rect
3939  flutter::DlRect rect = flutter::DlRect::MakeXYWH(2, 2, 3, 3);
3940  stack1.PushClipRect(rect);
3941 
3942  auto embeddedViewParams1 = std::make_unique<flutter::EmbeddedViewParams>(
3943  flutter::ToSkMatrix(screenScaleMatrix), SkSize::Make(10, 10), stack1);
3944 
3945  [flutterPlatformViewsController prerollCompositeEmbeddedView:1
3946  withParams:std::move(embeddedViewParams1)];
3947  [flutterPlatformViewsController
3948  compositeView:1
3949  withParams:[flutterPlatformViewsController compositionParamsForView:1]];
3950 
3951  UIView* childClippingView1 = gMockPlatformView.superview.superview;
3952  UIView* maskView1 = childClippingView1.maskView;
3953  XCTAssertNotNil(maskView1);
3954 
3955  // Composite a new frame.
3956  [flutterPlatformViewsController beginFrameWithSize:SkISize::Make(100, 100)];
3957  flutter::MutatorsStack stack2;
3958  auto embeddedViewParams2 = std::make_unique<flutter::EmbeddedViewParams>(
3959  flutter::ToSkMatrix(screenScaleMatrix), SkSize::Make(10, 10), stack2);
3960  auto embeddedViewParams3 = std::make_unique<flutter::EmbeddedViewParams>(
3961  flutter::ToSkMatrix(screenScaleMatrix), SkSize::Make(10, 10), stack2);
3962  [flutterPlatformViewsController prerollCompositeEmbeddedView:1
3963  withParams:std::move(embeddedViewParams3)];
3964  [flutterPlatformViewsController
3965  compositeView:1
3966  withParams:[flutterPlatformViewsController compositionParamsForView:1]];
3967 
3968  childClippingView1 = gMockPlatformView.superview.superview;
3969 
3970  // This overrides gMockPlatformView to point to the newly created platform view.
3971  [flutterPlatformViewsController
3973  arguments:@{
3974  @"id" : @2,
3975  @"viewType" : @"MockFlutterPlatformView"
3976  }]
3977  result:result];
3978 
3979  auto embeddedViewParams4 = std::make_unique<flutter::EmbeddedViewParams>(
3980  flutter::ToSkMatrix(screenScaleMatrix), SkSize::Make(10, 10), stack1);
3981  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
3982  withParams:std::move(embeddedViewParams4)];
3983  [flutterPlatformViewsController
3984  compositeView:2
3985  withParams:[flutterPlatformViewsController compositionParamsForView:2]];
3986 
3987  UIView* childClippingView2 = gMockPlatformView.superview.superview;
3988 
3989  UIView* maskView2 = childClippingView2.maskView;
3990  XCTAssertEqual(maskView1, maskView2);
3991  XCTAssertNotNil(childClippingView2.maskView);
3992  XCTAssertNil(childClippingView1.maskView);
3993 }
3994 
3995 - (void)testDifferentClipMaskViewIsUsedForEachView {
3996  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
3997 
3998  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
3999  /*platform=*/GetDefaultTaskRunner(),
4000  /*raster=*/GetDefaultTaskRunner(),
4001  /*ui=*/GetDefaultTaskRunner(),
4002  /*io=*/GetDefaultTaskRunner());
4003  FlutterPlatformViewsController* flutterPlatformViewsController =
4004  [[FlutterPlatformViewsController alloc] init];
4005  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
4006  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
4007  /*delegate=*/mock_delegate,
4008  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
4009  /*platform_views_controller=*/flutterPlatformViewsController,
4010  /*task_runners=*/runners,
4011  /*worker_task_runner=*/nil,
4012  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
4013 
4016  [flutterPlatformViewsController
4017  registerViewFactory:factory
4018  withId:@"MockFlutterPlatformView"
4019  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
4020  FlutterResult result = ^(id result) {
4021  };
4022 
4023  [flutterPlatformViewsController
4025  arguments:@{
4026  @"id" : @1,
4027  @"viewType" : @"MockFlutterPlatformView"
4028  }]
4029  result:result];
4030  UIView* view1 = gMockPlatformView;
4031 
4032  // This overwrites `gMockPlatformView` to another view.
4033  [flutterPlatformViewsController
4035  arguments:@{
4036  @"id" : @2,
4037  @"viewType" : @"MockFlutterPlatformView"
4038  }]
4039  result:result];
4040  UIView* view2 = gMockPlatformView;
4041 
4042  XCTAssertNotNil(gMockPlatformView);
4043  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
4044  flutterPlatformViewsController.flutterView = flutterView;
4045  // Create embedded view params
4046  flutter::MutatorsStack stack1;
4047  // Layer tree always pushes a screen scale factor to the stack
4048  flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
4049  flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
4050  stack1.PushTransform(screenScaleMatrix);
4051  // Push a clip rect
4052  flutter::DlRect rect = flutter::DlRect::MakeXYWH(2, 2, 3, 3);
4053  stack1.PushClipRect(rect);
4054 
4055  auto embeddedViewParams1 = std::make_unique<flutter::EmbeddedViewParams>(
4056  flutter::ToSkMatrix(screenScaleMatrix), SkSize::Make(10, 10), stack1);
4057 
4058  flutter::MutatorsStack stack2;
4059  stack2.PushClipRect(rect);
4060  auto embeddedViewParams2 = std::make_unique<flutter::EmbeddedViewParams>(
4061  flutter::ToSkMatrix(screenScaleMatrix), SkSize::Make(10, 10), stack2);
4062 
4063  [flutterPlatformViewsController prerollCompositeEmbeddedView:1
4064  withParams:std::move(embeddedViewParams1)];
4065  [flutterPlatformViewsController
4066  compositeView:1
4067  withParams:[flutterPlatformViewsController compositionParamsForView:1]];
4068 
4069  UIView* childClippingView1 = view1.superview.superview;
4070 
4071  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
4072  withParams:std::move(embeddedViewParams2)];
4073  [flutterPlatformViewsController
4074  compositeView:2
4075  withParams:[flutterPlatformViewsController compositionParamsForView:2]];
4076 
4077  UIView* childClippingView2 = view2.superview.superview;
4078  UIView* maskView1 = childClippingView1.maskView;
4079  UIView* maskView2 = childClippingView2.maskView;
4080  XCTAssertNotEqual(maskView1, maskView2);
4081 }
4082 
4083 - (void)testMaskViewUsesCAShapeLayerAsTheBackingLayer {
4084  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
4085 
4086  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
4087  /*platform=*/GetDefaultTaskRunner(),
4088  /*raster=*/GetDefaultTaskRunner(),
4089  /*ui=*/GetDefaultTaskRunner(),
4090  /*io=*/GetDefaultTaskRunner());
4091  FlutterPlatformViewsController* flutterPlatformViewsController =
4092  [[FlutterPlatformViewsController alloc] init];
4093  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
4094  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
4095  /*delegate=*/mock_delegate,
4096  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
4097  /*platform_views_controller=*/flutterPlatformViewsController,
4098  /*task_runners=*/runners,
4099  /*worker_task_runner=*/nil,
4100  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
4101 
4104  [flutterPlatformViewsController
4105  registerViewFactory:factory
4106  withId:@"MockFlutterPlatformView"
4107  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
4108  FlutterResult result = ^(id result) {
4109  };
4110 
4111  [flutterPlatformViewsController
4113  arguments:@{
4114  @"id" : @1,
4115  @"viewType" : @"MockFlutterPlatformView"
4116  }]
4117  result:result];
4118 
4119  XCTAssertNotNil(gMockPlatformView);
4120  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
4121  flutterPlatformViewsController.flutterView = flutterView;
4122  // Create embedded view params
4123  flutter::MutatorsStack stack1;
4124  // Layer tree always pushes a screen scale factor to the stack
4125  flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
4126  flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
4127  stack1.PushTransform(screenScaleMatrix);
4128  // Push a clip rect
4129  flutter::DlRect rect = flutter::DlRect::MakeXYWH(2, 2, 3, 3);
4130  stack1.PushClipRect(rect);
4131 
4132  auto embeddedViewParams1 = std::make_unique<flutter::EmbeddedViewParams>(
4133  flutter::ToSkMatrix(screenScaleMatrix), SkSize::Make(10, 10), stack1);
4134 
4135  flutter::MutatorsStack stack2;
4136  stack2.PushClipRect(rect);
4137  auto embeddedViewParams2 = std::make_unique<flutter::EmbeddedViewParams>(
4138  flutter::ToSkMatrix(screenScaleMatrix), SkSize::Make(10, 10), stack2);
4139 
4140  [flutterPlatformViewsController prerollCompositeEmbeddedView:1
4141  withParams:std::move(embeddedViewParams1)];
4142  [flutterPlatformViewsController
4143  compositeView:1
4144  withParams:[flutterPlatformViewsController compositionParamsForView:1]];
4145 
4146  UIView* childClippingView = gMockPlatformView.superview.superview;
4147 
4148  UIView* maskView = childClippingView.maskView;
4149  XCTAssert([maskView.layer isKindOfClass:[CAShapeLayer class]],
4150  @"Mask view must use CAShapeLayer as its backing layer.");
4151 }
4152 
4153 // Return true if a correct visual effect view is found. It also implies all the validation in this
4154 // method passes.
4155 //
4156 // There are two fail states for this method. 1. One of the XCTAssert method failed; or 2. No
4157 // correct visual effect view found.
4158 - (BOOL)validateOneVisualEffectView:(UIView*)visualEffectView
4159  expectedFrame:(CGRect)frame
4160  inputRadius:(CGFloat)inputRadius {
4161  XCTAssertTrue(CGRectEqualToRect(visualEffectView.frame, frame));
4162  for (UIView* view in visualEffectView.subviews) {
4163  if (![NSStringFromClass([view class]) hasSuffix:@"BackdropView"]) {
4164  continue;
4165  }
4166  XCTAssertEqual(view.layer.filters.count, 1u);
4167  NSObject* filter = view.layer.filters.firstObject;
4168 
4169  XCTAssertEqualObjects([filter valueForKey:@"name"], @"gaussianBlur");
4170 
4171  NSObject* inputRadiusInFilter = [filter valueForKey:@"inputRadius"];
4172  XCTAssertTrue([inputRadiusInFilter isKindOfClass:[NSNumber class]] &&
4173  flutter::BlurRadiusEqualToBlurRadius(((NSNumber*)inputRadiusInFilter).floatValue,
4174  inputRadius));
4175  return YES;
4176  }
4177  return NO;
4178 }
4179 
4180 - (void)testDisposingViewInCompositionOrderDoNotCrash {
4181  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
4182 
4183  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
4184  /*platform=*/GetDefaultTaskRunner(),
4185  /*raster=*/GetDefaultTaskRunner(),
4186  /*ui=*/GetDefaultTaskRunner(),
4187  /*io=*/GetDefaultTaskRunner());
4188  FlutterPlatformViewsController* flutterPlatformViewsController =
4189  [[FlutterPlatformViewsController alloc] init];
4190  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
4191  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
4192  /*delegate=*/mock_delegate,
4193  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
4194  /*platform_views_controller=*/flutterPlatformViewsController,
4195  /*task_runners=*/runners,
4196  /*worker_task_runner=*/nil,
4197  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
4198 
4199  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
4200  flutterPlatformViewsController.flutterView = flutterView;
4201 
4204  [flutterPlatformViewsController
4205  registerViewFactory:factory
4206  withId:@"MockFlutterPlatformView"
4207  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
4208  FlutterResult result = ^(id result) {
4209  };
4210 
4211  [flutterPlatformViewsController
4213  arguments:@{
4214  @"id" : @0,
4215  @"viewType" : @"MockFlutterPlatformView"
4216  }]
4217  result:result];
4218  [flutterPlatformViewsController
4220  arguments:@{
4221  @"id" : @1,
4222  @"viewType" : @"MockFlutterPlatformView"
4223  }]
4224  result:result];
4225 
4226  {
4227  // **** First frame, view id 0, 1 in the composition_order_, disposing view 0 is called. **** //
4228  // No view should be disposed, or removed from the composition order.
4229  [flutterPlatformViewsController beginFrameWithSize:SkISize::Make(300, 300)];
4230  flutter::MutatorsStack stack;
4231  flutter::DlMatrix finalMatrix;
4232  auto embeddedViewParams0 = std::make_unique<flutter::EmbeddedViewParams>(
4233  flutter::ToSkMatrix(finalMatrix), SkSize::Make(300, 300), stack);
4234  [flutterPlatformViewsController prerollCompositeEmbeddedView:0
4235  withParams:std::move(embeddedViewParams0)];
4236 
4237  auto embeddedViewParams1 = std::make_unique<flutter::EmbeddedViewParams>(
4238  flutter::ToSkMatrix(finalMatrix), SkSize::Make(300, 300), stack);
4239  [flutterPlatformViewsController prerollCompositeEmbeddedView:1
4240  withParams:std::move(embeddedViewParams1)];
4241 
4242  XCTAssertEqual(flutterPlatformViewsController.embeddedViewCount, 2UL);
4243 
4244  XCTestExpectation* expectation = [self expectationWithDescription:@"dispose call ended."];
4245  FlutterResult disposeResult = ^(id result) {
4246  [expectation fulfill];
4247  };
4248 
4249  [flutterPlatformViewsController
4251  result:disposeResult];
4252  [self waitForExpectationsWithTimeout:30 handler:nil];
4253 
4254  flutter::SurfaceFrame::FramebufferInfo framebuffer_info;
4255  auto mock_surface = std::make_unique<flutter::SurfaceFrame>(
4256  nullptr, framebuffer_info,
4257  [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; },
4258  [](const flutter::SurfaceFrame& surface_frame) { return true; },
4259  /*frame_size=*/SkISize::Make(800, 600), nullptr,
4260  /*display_list_fallback=*/true);
4261  XCTAssertTrue([flutterPlatformViewsController
4262  submitFrame:std::move(mock_surface)
4263  withIosContext:std::make_shared<flutter::IOSContextNoop>()]);
4264 
4265  // Disposing won't remove embedded views until the view is removed from the composition_order_
4266  XCTAssertEqual(flutterPlatformViewsController.embeddedViewCount, 2UL);
4267  XCTAssertNotNil([flutterPlatformViewsController platformViewForId:0]);
4268  XCTAssertNotNil([flutterPlatformViewsController platformViewForId:1]);
4269  }
4270 
4271  {
4272  // **** Second frame, view id 1 in the composition_order_, no disposing view is called, **** //
4273  // View 0 is removed from the composition order in this frame, hence also disposed.
4274  [flutterPlatformViewsController beginFrameWithSize:SkISize::Make(300, 300)];
4275  flutter::MutatorsStack stack;
4276  flutter::DlMatrix finalMatrix;
4277  auto embeddedViewParams1 = std::make_unique<flutter::EmbeddedViewParams>(
4278  flutter::ToSkMatrix(finalMatrix), SkSize::Make(300, 300), stack);
4279  [flutterPlatformViewsController prerollCompositeEmbeddedView:1
4280  withParams:std::move(embeddedViewParams1)];
4281 
4282  flutter::SurfaceFrame::FramebufferInfo framebuffer_info;
4283  auto mock_surface = std::make_unique<flutter::SurfaceFrame>(
4284  nullptr, framebuffer_info,
4285  [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; },
4286  [](const flutter::SurfaceFrame& surface_frame) { return true; },
4287  /*frame_size=*/SkISize::Make(800, 600), nullptr, /*display_list_fallback=*/true);
4288  XCTAssertTrue([flutterPlatformViewsController
4289  submitFrame:std::move(mock_surface)
4290  withIosContext:std::make_shared<flutter::IOSContextNoop>()]);
4291 
4292  // Disposing won't remove embedded views until the view is removed from the composition_order_
4293  XCTAssertEqual(flutterPlatformViewsController.embeddedViewCount, 1UL);
4294  XCTAssertNil([flutterPlatformViewsController platformViewForId:0]);
4295  XCTAssertNotNil([flutterPlatformViewsController platformViewForId:1]);
4296  }
4297 }
4298 - (void)testOnlyPlatformViewsAreRemovedWhenReset {
4299  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
4300 
4301  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
4302  /*platform=*/GetDefaultTaskRunner(),
4303  /*raster=*/GetDefaultTaskRunner(),
4304  /*ui=*/GetDefaultTaskRunner(),
4305  /*io=*/GetDefaultTaskRunner());
4306  FlutterPlatformViewsController* flutterPlatformViewsController =
4307  [[FlutterPlatformViewsController alloc] init];
4308  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
4309  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
4310  /*delegate=*/mock_delegate,
4311  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
4312  /*platform_views_controller=*/flutterPlatformViewsController,
4313  /*task_runners=*/runners,
4314  /*worker_task_runner=*/nil,
4315  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
4316 
4319  [flutterPlatformViewsController
4320  registerViewFactory:factory
4321  withId:@"MockFlutterPlatformView"
4322  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
4323  FlutterResult result = ^(id result) {
4324  };
4325  [flutterPlatformViewsController
4327  arguments:@{
4328  @"id" : @2,
4329  @"viewType" : @"MockFlutterPlatformView"
4330  }]
4331  result:result];
4332  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
4333  flutterPlatformViewsController.flutterView = flutterView;
4334  // Create embedded view params
4335  flutter::MutatorsStack stack;
4336  // Layer tree always pushes a screen scale factor to the stack
4337  flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
4338  flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
4339  stack.PushTransform(screenScaleMatrix);
4340  // Push a translate matrix
4341  flutter::DlMatrix translateMatrix = flutter::DlMatrix::MakeTranslation({100, 100});
4342  stack.PushTransform(translateMatrix);
4343  flutter::DlMatrix finalMatrix = screenScaleMatrix * translateMatrix;
4344 
4345  auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
4346  flutter::ToSkMatrix(finalMatrix), SkSize::Make(300, 300), stack);
4347 
4348  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
4349  withParams:std::move(embeddedViewParams)];
4350 
4351  flutter::SurfaceFrame::FramebufferInfo framebuffer_info;
4352  auto mock_surface = std::make_unique<flutter::SurfaceFrame>(
4353  nullptr, framebuffer_info,
4354  [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; },
4355  [](const flutter::SurfaceFrame& surface_frame) { return true; },
4356  /*frame_size=*/SkISize::Make(800, 600), nullptr, /*display_list_fallback=*/true);
4357  [flutterPlatformViewsController submitFrame:std::move(mock_surface)
4358  withIosContext:std::make_shared<flutter::IOSContextNoop>()];
4359 
4360  UIView* someView = [[UIView alloc] init];
4361  [flutterView addSubview:someView];
4362 
4363  [flutterPlatformViewsController reset];
4364  XCTAssertEqual(flutterView.subviews.count, 1u);
4365  XCTAssertEqual(flutterView.subviews.firstObject, someView);
4366 }
4367 
4368 - (void)testResetClearsPreviousCompositionOrder {
4369  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
4370 
4371  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
4372  /*platform=*/GetDefaultTaskRunner(),
4373  /*raster=*/GetDefaultTaskRunner(),
4374  /*ui=*/GetDefaultTaskRunner(),
4375  /*io=*/GetDefaultTaskRunner());
4376  FlutterPlatformViewsController* flutterPlatformViewsController =
4377  [[FlutterPlatformViewsController alloc] init];
4378  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
4379  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
4380  /*delegate=*/mock_delegate,
4381  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
4382  /*platform_views_controller=*/flutterPlatformViewsController,
4383  /*task_runners=*/runners,
4384  /*worker_task_runner=*/nil,
4385  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
4386 
4389  [flutterPlatformViewsController
4390  registerViewFactory:factory
4391  withId:@"MockFlutterPlatformView"
4392  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
4393  FlutterResult result = ^(id result) {
4394  };
4395  [flutterPlatformViewsController
4397  arguments:@{
4398  @"id" : @2,
4399  @"viewType" : @"MockFlutterPlatformView"
4400  }]
4401  result:result];
4402  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
4403  flutterPlatformViewsController.flutterView = flutterView;
4404  // Create embedded view params
4405  flutter::MutatorsStack stack;
4406  // Layer tree always pushes a screen scale factor to the stack
4407  flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
4408  flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
4409  stack.PushTransform(screenScaleMatrix);
4410  // Push a translate matrix
4411  flutter::DlMatrix translateMatrix = flutter::DlMatrix::MakeTranslation({100, 100});
4412  stack.PushTransform(translateMatrix);
4413  flutter::DlMatrix finalMatrix = screenScaleMatrix * translateMatrix;
4414 
4415  auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
4416  flutter::ToSkMatrix(finalMatrix), SkSize::Make(300, 300), stack);
4417 
4418  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
4419  withParams:std::move(embeddedViewParams)];
4420 
4421  flutter::SurfaceFrame::FramebufferInfo framebuffer_info;
4422  auto mock_surface = std::make_unique<flutter::SurfaceFrame>(
4423  nullptr, framebuffer_info,
4424  [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; },
4425  [](const flutter::SurfaceFrame& surface_frame) { return true; },
4426  /*frame_size=*/SkISize::Make(800, 600), nullptr, /*display_list_fallback=*/true);
4427  [flutterPlatformViewsController submitFrame:std::move(mock_surface)
4428  withIosContext:std::make_shared<flutter::IOSContextNoop>()];
4429 
4430  // The above code should result in previousCompositionOrder having one viewId in it
4431  XCTAssertEqual(flutterPlatformViewsController.previousCompositionOrder.size(), 1ul);
4432 
4433  // reset should clear previousCompositionOrder
4434  [flutterPlatformViewsController reset];
4435 
4436  // previousCompositionOrder should now be empty
4437  XCTAssertEqual(flutterPlatformViewsController.previousCompositionOrder.size(), 0ul);
4438 }
4439 
4440 - (void)testNilPlatformViewDoesntCrash {
4441  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
4442 
4443  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
4444  /*platform=*/GetDefaultTaskRunner(),
4445  /*raster=*/GetDefaultTaskRunner(),
4446  /*ui=*/GetDefaultTaskRunner(),
4447  /*io=*/GetDefaultTaskRunner());
4448  FlutterPlatformViewsController* flutterPlatformViewsController =
4449  [[FlutterPlatformViewsController alloc] init];
4450  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
4451  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
4452  /*delegate=*/mock_delegate,
4453  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
4454  /*platform_views_controller=*/flutterPlatformViewsController,
4455  /*task_runners=*/runners,
4456  /*worker_task_runner=*/nil,
4457  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
4458 
4461  [flutterPlatformViewsController
4462  registerViewFactory:factory
4463  withId:@"MockFlutterPlatformView"
4464  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
4465  FlutterResult result = ^(id result) {
4466  };
4467  [flutterPlatformViewsController
4469  arguments:@{
4470  @"id" : @2,
4471  @"viewType" : @"MockFlutterPlatformView"
4472  }]
4473  result:result];
4474  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
4475  flutterPlatformViewsController.flutterView = flutterView;
4476 
4477  // Create embedded view params
4478  flutter::MutatorsStack stack;
4479  // Layer tree always pushes a screen scale factor to the stack
4480  flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
4481  flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
4482  stack.PushTransform(screenScaleMatrix);
4483  // Push a translate matrix
4484  flutter::DlMatrix translateMatrix = flutter::DlMatrix::MakeTranslation({100, 100});
4485  stack.PushTransform(translateMatrix);
4486  flutter::DlMatrix finalMatrix = screenScaleMatrix * translateMatrix;
4487 
4488  auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
4489  flutter::ToSkMatrix(finalMatrix), SkSize::Make(300, 300), stack);
4490 
4491  [flutterPlatformViewsController prerollCompositeEmbeddedView:2
4492  withParams:std::move(embeddedViewParams)];
4493 
4494  flutter::SurfaceFrame::FramebufferInfo framebuffer_info;
4495  auto mock_surface = std::make_unique<flutter::SurfaceFrame>(
4496  nullptr, framebuffer_info,
4497  [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; },
4498  [](const flutter::SurfaceFrame& surface_frame) { return true; },
4499  /*frame_size=*/SkISize::Make(800, 600), nullptr, /*display_list_fallback=*/true);
4500  [flutterPlatformViewsController submitFrame:std::move(mock_surface)
4501  withIosContext:std::make_shared<flutter::IOSContextNoop>()];
4502 
4503  XCTAssertEqual(flutterView.subviews.count, 1u);
4504 }
4505 
4506 - (void)testFlutterTouchInterceptingViewLinksToAccessibilityContainer {
4507  FlutterTouchInterceptingView* touchInteceptorView = [[FlutterTouchInterceptingView alloc] init];
4508  NSObject* container = [[NSObject alloc] init];
4509  [touchInteceptorView setFlutterAccessibilityContainer:container];
4510  XCTAssertEqualObjects([touchInteceptorView accessibilityContainer], container);
4511 }
4512 
4513 - (void)testLayerPool {
4514  // Create an IOSContext.
4515  FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar"];
4516  [engine run];
4517  XCTAssertTrue(engine.platformView != nullptr);
4518  auto ios_context = engine.platformView->GetIosContext();
4519 
4520  auto pool = flutter::OverlayLayerPool{};
4521 
4522  // Add layers to the pool.
4523  pool.CreateLayer(ios_context, MTLPixelFormatBGRA8Unorm);
4524  XCTAssertEqual(pool.size(), 1u);
4525  pool.CreateLayer(ios_context, MTLPixelFormatBGRA8Unorm);
4526  XCTAssertEqual(pool.size(), 2u);
4527 
4528  // Mark all layers as unused.
4529  pool.RecycleLayers();
4530  XCTAssertEqual(pool.size(), 2u);
4531 
4532  // Free the unused layers. One should remain.
4533  auto unused_layers = pool.RemoveUnusedLayers();
4534  XCTAssertEqual(unused_layers.size(), 2u);
4535  XCTAssertEqual(pool.size(), 1u);
4536 }
4537 
4538 - (void)testFlutterPlatformViewControllerSubmitFramePreservingFrameDamage {
4539  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
4540 
4541  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
4542  /*platform=*/GetDefaultTaskRunner(),
4543  /*raster=*/GetDefaultTaskRunner(),
4544  /*ui=*/GetDefaultTaskRunner(),
4545  /*io=*/GetDefaultTaskRunner());
4546  FlutterPlatformViewsController* flutterPlatformViewsController =
4547  [[FlutterPlatformViewsController alloc] init];
4548  flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
4549  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
4550  /*delegate=*/mock_delegate,
4551  /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
4552  /*platform_views_controller=*/flutterPlatformViewsController,
4553  /*task_runners=*/runners,
4554  /*worker_task_runner=*/nil,
4555  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
4556 
4557  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
4558  flutterPlatformViewsController.flutterView = flutterView;
4559 
4562  [flutterPlatformViewsController
4563  registerViewFactory:factory
4564  withId:@"MockFlutterPlatformView"
4565  gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
4566  FlutterResult result = ^(id result) {
4567  };
4568  [flutterPlatformViewsController
4570  arguments:@{
4571  @"id" : @0,
4572  @"viewType" : @"MockFlutterPlatformView"
4573  }]
4574  result:result];
4575 
4576  // This overwrites `gMockPlatformView` to another view.
4577  [flutterPlatformViewsController
4579  arguments:@{
4580  @"id" : @1,
4581  @"viewType" : @"MockFlutterPlatformView"
4582  }]
4583  result:result];
4584 
4585  [flutterPlatformViewsController beginFrameWithSize:SkISize::Make(300, 300)];
4586  flutter::MutatorsStack stack;
4587  flutter::DlMatrix finalMatrix;
4588  auto embeddedViewParams1 = std::make_unique<flutter::EmbeddedViewParams>(
4589  flutter::ToSkMatrix(finalMatrix), SkSize::Make(300, 300), stack);
4590  [flutterPlatformViewsController prerollCompositeEmbeddedView:0
4591  withParams:std::move(embeddedViewParams1)];
4592 
4593  auto embeddedViewParams2 = std::make_unique<flutter::EmbeddedViewParams>(
4594  flutter::ToSkMatrix(finalMatrix), SkSize::Make(500, 500), stack);
4595  [flutterPlatformViewsController prerollCompositeEmbeddedView:1
4596  withParams:std::move(embeddedViewParams2)];
4597 
4598  flutter::SurfaceFrame::FramebufferInfo framebuffer_info;
4599  std::optional<flutter::SurfaceFrame::SubmitInfo> submit_info;
4600  auto mock_surface = std::make_unique<flutter::SurfaceFrame>(
4601  nullptr, framebuffer_info,
4602  [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; },
4603  [&](const flutter::SurfaceFrame& surface_frame) {
4604  submit_info = surface_frame.submit_info();
4605  return true;
4606  },
4607  /*frame_size=*/SkISize::Make(800, 600), nullptr,
4608  /*display_list_fallback=*/true);
4609  mock_surface->set_submit_info({
4610  .frame_damage = SkIRect::MakeWH(800, 600),
4611  .buffer_damage = SkIRect::MakeWH(400, 600),
4612  });
4613 
4614  [flutterPlatformViewsController submitFrame:std::move(mock_surface)
4615  withIosContext:std::make_shared<flutter::IOSContextNoop>()];
4616 
4617  XCTAssertTrue(submit_info.has_value());
4618  XCTAssertEqual(*submit_info->frame_damage, SkIRect::MakeWH(800, 600));
4619  XCTAssertEqual(*submit_info->buffer_damage, SkIRect::MakeWH(400, 600));
4620 }
4621 
4622 @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