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