Flutter iOS Embedder
FlutterPlatformViewsTest.mm
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #import <OCMock/OCMock.h>
6 #import <UIKit/UIKit.h>
7 #import <XCTest/XCTest.h>
8 
9 #import "flutter/fml/thread.h"
16 
18 
21 const float kFloatCompareEpsilon = 0.001;
22 
24 @end
26 
27 - (instancetype)init {
28  self = [super init];
29  if (self) {
30  gMockPlatformView = self;
31  }
32  return self;
33 }
34 
35 - (void)dealloc {
36  gMockPlatformView = nil;
37 }
38 
39 @end
40 
42 @property(nonatomic, strong) UIView* view;
43 @property(nonatomic, assign) BOOL viewCreated;
44 @end
45 
47 
48 - (instancetype)init {
49  if (self = [super init]) {
50  _view = [[FlutterPlatformViewsTestMockPlatformView alloc] init];
51  _viewCreated = NO;
52  }
53  return self;
54 }
55 
56 - (UIView*)view {
57  [self checkViewCreatedOnce];
58  return _view;
59 }
60 
61 - (void)checkViewCreatedOnce {
62  if (self.viewCreated) {
63  abort();
64  }
65  self.viewCreated = YES;
66 }
67 
68 @end
69 
71  : NSObject <FlutterPlatformViewFactory>
72 @end
73 
75 - (NSObject<FlutterPlatformView>*)createWithFrame:(CGRect)frame
76  viewIdentifier:(int64_t)viewId
77  arguments:(id _Nullable)args {
79 }
80 
81 @end
82 
83 namespace flutter {
84 namespace {
85 class FlutterPlatformViewsTestMockPlatformViewDelegate : public PlatformView::Delegate {
86  public:
87  void OnPlatformViewCreated(std::unique_ptr<Surface> surface) override {}
88  void OnPlatformViewDestroyed() override {}
89  void OnPlatformViewScheduleFrame() override {}
90  void OnPlatformViewAddView(int64_t view_id,
91  const ViewportMetrics& viewport_metrics,
92  AddViewCallback callback) override {}
93  void OnPlatformViewRemoveView(int64_t view_id, RemoveViewCallback callback) override {}
94  void OnPlatformViewSetNextFrameCallback(const fml::closure& closure) override {}
95  void OnPlatformViewSetViewportMetrics(int64_t view_id, const ViewportMetrics& metrics) override {}
96  const flutter::Settings& OnPlatformViewGetSettings() const override { return settings_; }
97  void OnPlatformViewDispatchPlatformMessage(std::unique_ptr<PlatformMessage> message) override {}
98  void OnPlatformViewDispatchPointerDataPacket(std::unique_ptr<PointerDataPacket> packet) override {
99  }
100  void OnPlatformViewDispatchSemanticsAction(int32_t id,
101  SemanticsAction action,
102  fml::MallocMapping args) override {}
103  void OnPlatformViewSetSemanticsEnabled(bool enabled) override {}
104  void OnPlatformViewSetAccessibilityFeatures(int32_t flags) override {}
105  void OnPlatformViewRegisterTexture(std::shared_ptr<Texture> texture) override {}
106  void OnPlatformViewUnregisterTexture(int64_t texture_id) override {}
107  void OnPlatformViewMarkTextureFrameAvailable(int64_t texture_id) override {}
108 
109  void LoadDartDeferredLibrary(intptr_t loading_unit_id,
110  std::unique_ptr<const fml::Mapping> snapshot_data,
111  std::unique_ptr<const fml::Mapping> snapshot_instructions) override {
112  }
113  void LoadDartDeferredLibraryError(intptr_t loading_unit_id,
114  const std::string error_message,
115  bool transient) override {}
116  void UpdateAssetResolverByType(std::unique_ptr<flutter::AssetResolver> updated_asset_resolver,
117  flutter::AssetResolver::AssetResolverType type) override {}
118 
119  flutter::Settings settings_;
120 };
121 
122 } // namespace
123 } // namespace flutter
124 
125 namespace {
126 fml::RefPtr<fml::TaskRunner> CreateNewThread(const std::string& name) {
127  auto thread = std::make_unique<fml::Thread>(name);
128  auto runner = thread->GetTaskRunner();
129  return runner;
130 }
131 } // namespace
132 
133 @interface FlutterPlatformViewsTest : XCTestCase
134 @end
135 
136 @implementation FlutterPlatformViewsTest
137 
138 - (void)testFlutterViewOnlyCreateOnceInOneFrame {
139  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
140  auto thread_task_runner = CreateNewThread("FlutterPlatformViewsTest");
141  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
142  /*platform=*/thread_task_runner,
143  /*raster=*/thread_task_runner,
144  /*ui=*/thread_task_runner,
145  /*io=*/thread_task_runner);
146  auto flutterPlatformViewsController = std::make_shared<flutter::FlutterPlatformViewsController>();
147  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
148  /*delegate=*/mock_delegate,
149  /*rendering_api=*/mock_delegate.settings_.enable_impeller
152  /*platform_views_controller=*/flutterPlatformViewsController,
153  /*task_runners=*/runners,
154  /*worker_task_runner=*/nil,
155  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
156 
159  flutterPlatformViewsController->RegisterViewFactory(
160  factory, @"MockFlutterPlatformView",
162  FlutterResult result = ^(id result) {
163  };
164  flutterPlatformViewsController->OnMethodCall(
166  methodCallWithMethodName:@"create"
167  arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}],
168  result);
169  UIView* mockFlutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
170  flutterPlatformViewsController->SetFlutterView(mockFlutterView);
171  // Create embedded view params
172  flutter::MutatorsStack stack;
173  // Layer tree always pushes a screen scale factor to the stack
174  SkMatrix screenScaleMatrix =
175  SkMatrix::Scale([UIScreen mainScreen].scale, [UIScreen mainScreen].scale);
176  stack.PushTransform(screenScaleMatrix);
177  // Push a translate matrix
178  SkMatrix translateMatrix = SkMatrix::Translate(100, 100);
179  stack.PushTransform(translateMatrix);
180  SkMatrix finalMatrix;
181  finalMatrix.setConcat(screenScaleMatrix, translateMatrix);
182 
183  auto embeddedViewParams =
184  std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, SkSize::Make(300, 300), stack);
185 
186  flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams));
187  flutterPlatformViewsController->CompositeEmbeddedView(2);
188 
189  flutterPlatformViewsController->GetPlatformViewRect(2);
190 
191  XCTAssertNotNil(gMockPlatformView);
192 
193  flutterPlatformViewsController->Reset();
194 }
195 
196 - (void)testCanCreatePlatformViewWithoutFlutterView {
197  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
198  auto thread_task_runner = CreateNewThread("FlutterPlatformViewsTest");
199  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
200  /*platform=*/thread_task_runner,
201  /*raster=*/thread_task_runner,
202  /*ui=*/thread_task_runner,
203  /*io=*/thread_task_runner);
204  auto flutterPlatformViewsController = std::make_shared<flutter::FlutterPlatformViewsController>();
205  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
206  /*delegate=*/mock_delegate,
207  /*rendering_api=*/mock_delegate.settings_.enable_impeller
210  /*platform_views_controller=*/flutterPlatformViewsController,
211  /*task_runners=*/runners,
212  /*worker_task_runner=*/nil,
213  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
214 
217  flutterPlatformViewsController->RegisterViewFactory(
218  factory, @"MockFlutterPlatformView",
220  FlutterResult result = ^(id result) {
221  };
222  flutterPlatformViewsController->OnMethodCall(
224  methodCallWithMethodName:@"create"
225  arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}],
226  result);
227 
228  XCTAssertNotNil(gMockPlatformView);
229 }
230 
231 - (void)testChildClippingViewHitTests {
232  ChildClippingView* childClippingView =
233  [[ChildClippingView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
234  UIView* childView = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
235  [childClippingView addSubview:childView];
236 
237  XCTAssertFalse([childClippingView pointInside:CGPointMake(50, 50) withEvent:nil]);
238  XCTAssertFalse([childClippingView pointInside:CGPointMake(99, 100) withEvent:nil]);
239  XCTAssertFalse([childClippingView pointInside:CGPointMake(100, 99) withEvent:nil]);
240  XCTAssertFalse([childClippingView pointInside:CGPointMake(201, 200) withEvent:nil]);
241  XCTAssertFalse([childClippingView pointInside:CGPointMake(200, 201) withEvent:nil]);
242  XCTAssertFalse([childClippingView pointInside:CGPointMake(99, 200) withEvent:nil]);
243  XCTAssertFalse([childClippingView pointInside:CGPointMake(200, 299) withEvent:nil]);
244 
245  XCTAssertTrue([childClippingView pointInside:CGPointMake(150, 150) withEvent:nil]);
246  XCTAssertTrue([childClippingView pointInside:CGPointMake(100, 100) withEvent:nil]);
247  XCTAssertTrue([childClippingView pointInside:CGPointMake(199, 100) withEvent:nil]);
248  XCTAssertTrue([childClippingView pointInside:CGPointMake(100, 199) withEvent:nil]);
249  XCTAssertTrue([childClippingView pointInside:CGPointMake(199, 199) withEvent:nil]);
250 }
251 
252 - (void)testReleasesBackdropFilterSubviewsOnChildClippingViewDealloc {
253  __weak NSMutableArray<UIVisualEffectView*>* weakBackdropFilterSubviews = nil;
254  __weak UIVisualEffectView* weakVisualEffectView1 = nil;
255  __weak UIVisualEffectView* weakVisualEffectView2 = nil;
256 
257  @autoreleasepool {
258  ChildClippingView* clippingView = [[ChildClippingView alloc] initWithFrame:CGRectZero];
259  UIVisualEffectView* visualEffectView1 = [[UIVisualEffectView alloc]
260  initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]];
261  weakVisualEffectView1 = visualEffectView1;
262  PlatformViewFilter* platformViewFilter1 =
263  [[PlatformViewFilter alloc] initWithFrame:CGRectMake(0, 0, 10, 10)
264  blurRadius:5
265  visualEffectView:visualEffectView1];
266 
267  [clippingView applyBlurBackdropFilters:@[ platformViewFilter1 ]];
268 
269  // Replace the blur filter to validate the original and new UIVisualEffectView are released.
270  UIVisualEffectView* visualEffectView2 = [[UIVisualEffectView alloc]
271  initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleDark]];
272  weakVisualEffectView2 = visualEffectView2;
273  PlatformViewFilter* platformViewFilter2 =
274  [[PlatformViewFilter alloc] initWithFrame:CGRectMake(0, 0, 10, 10)
275  blurRadius:5
276  visualEffectView:visualEffectView2];
277  [clippingView applyBlurBackdropFilters:@[ platformViewFilter2 ]];
278 
279  weakBackdropFilterSubviews = clippingView.backdropFilterSubviews;
280  XCTAssertNotNil(weakBackdropFilterSubviews);
281  clippingView = nil;
282  }
283  XCTAssertNil(weakBackdropFilterSubviews);
284  XCTAssertNil(weakVisualEffectView1);
285  XCTAssertNil(weakVisualEffectView2);
286 }
287 
288 - (void)testApplyBackdropFilter {
289  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
290  auto thread_task_runner = CreateNewThread("FlutterPlatformViewsTest");
291  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
292  /*platform=*/thread_task_runner,
293  /*raster=*/thread_task_runner,
294  /*ui=*/thread_task_runner,
295  /*io=*/thread_task_runner);
296  auto flutterPlatformViewsController = std::make_shared<flutter::FlutterPlatformViewsController>();
297  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
298  /*delegate=*/mock_delegate,
299  /*rendering_api=*/mock_delegate.settings_.enable_impeller
302  /*platform_views_controller=*/flutterPlatformViewsController,
303  /*task_runners=*/runners,
304  /*worker_task_runner=*/nil,
305  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
306 
309  flutterPlatformViewsController->RegisterViewFactory(
310  factory, @"MockFlutterPlatformView",
312  FlutterResult result = ^(id result) {
313  };
314  flutterPlatformViewsController->OnMethodCall(
316  methodCallWithMethodName:@"create"
317  arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}],
318  result);
319 
320  XCTAssertNotNil(gMockPlatformView);
321 
322  UIView* mockFlutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
323  flutterPlatformViewsController->SetFlutterView(mockFlutterView);
324  // Create embedded view params
325  flutter::MutatorsStack stack;
326  // Layer tree always pushes a screen scale factor to the stack
327  CGFloat screenScale = [UIScreen mainScreen].scale;
328  SkMatrix screenScaleMatrix = SkMatrix::Scale(screenScale, screenScale);
329  stack.PushTransform(screenScaleMatrix);
330  // Push a backdrop filter
331  auto filter = std::make_shared<flutter::DlBlurImageFilter>(5, 2, flutter::DlTileMode::kClamp);
332  stack.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
333 
334  auto embeddedViewParams =
335  std::make_unique<flutter::EmbeddedViewParams>(screenScaleMatrix, SkSize::Make(10, 10), stack);
336 
337  flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams));
338  flutterPlatformViewsController->CompositeEmbeddedView(2);
339  XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:[ChildClippingView class]]);
340  ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
341  [mockFlutterView addSubview:childClippingView];
342 
343  [mockFlutterView setNeedsLayout];
344  [mockFlutterView layoutIfNeeded];
345 
346  // childClippingView has visual effect view with the correct configurations.
347  NSUInteger numberOfExpectedVisualEffectView = 0;
348  for (UIView* subview in childClippingView.subviews) {
349  if (![subview isKindOfClass:[UIVisualEffectView class]]) {
350  continue;
351  }
352  XCTAssertLessThan(numberOfExpectedVisualEffectView, 1u);
353  if ([self validateOneVisualEffectView:subview
354  expectedFrame:CGRectMake(0, 0, 10, 10)
355  inputRadius:5]) {
356  numberOfExpectedVisualEffectView++;
357  }
358  }
359  XCTAssertEqual(numberOfExpectedVisualEffectView, 1u);
360 }
361 
362 - (void)testApplyBackdropFilterWithCorrectFrame {
363  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
364  auto thread_task_runner = CreateNewThread("FlutterPlatformViewsTest");
365  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
366  /*platform=*/thread_task_runner,
367  /*raster=*/thread_task_runner,
368  /*ui=*/thread_task_runner,
369  /*io=*/thread_task_runner);
370  auto flutterPlatformViewsController = std::make_shared<flutter::FlutterPlatformViewsController>();
371  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
372  /*delegate=*/mock_delegate,
373  /*rendering_api=*/mock_delegate.settings_.enable_impeller
376  /*platform_views_controller=*/flutterPlatformViewsController,
377  /*task_runners=*/runners,
378  /*worker_task_runner=*/nil,
379  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
380 
383  flutterPlatformViewsController->RegisterViewFactory(
384  factory, @"MockFlutterPlatformView",
386  FlutterResult result = ^(id result) {
387  };
388  flutterPlatformViewsController->OnMethodCall(
390  methodCallWithMethodName:@"create"
391  arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}],
392  result);
393 
394  XCTAssertNotNil(gMockPlatformView);
395 
396  UIView* mockFlutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
397  flutterPlatformViewsController->SetFlutterView(mockFlutterView);
398  // Create embedded view params
399  flutter::MutatorsStack stack;
400  // Layer tree always pushes a screen scale factor to the stack
401  CGFloat screenScale = [UIScreen mainScreen].scale;
402  SkMatrix screenScaleMatrix = SkMatrix::Scale(screenScale, screenScale);
403  stack.PushTransform(screenScaleMatrix);
404  // Push a backdrop filter
405  auto filter = std::make_shared<flutter::DlBlurImageFilter>(5, 2, flutter::DlTileMode::kClamp);
406  stack.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, screenScale * 8, screenScale * 8));
407 
408  auto embeddedViewParams =
409  std::make_unique<flutter::EmbeddedViewParams>(screenScaleMatrix, SkSize::Make(5, 10), stack);
410 
411  flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams));
412  flutterPlatformViewsController->CompositeEmbeddedView(2);
413  XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:[ChildClippingView class]]);
414  ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
415  [mockFlutterView addSubview:childClippingView];
416 
417  [mockFlutterView setNeedsLayout];
418  [mockFlutterView layoutIfNeeded];
419 
420  // childClippingView has visual effect view with the correct configurations.
421  NSUInteger numberOfExpectedVisualEffectView = 0;
422  for (UIView* subview in childClippingView.subviews) {
423  if (![subview isKindOfClass:[UIVisualEffectView class]]) {
424  continue;
425  }
426  XCTAssertLessThan(numberOfExpectedVisualEffectView, 1u);
427  if ([self validateOneVisualEffectView:subview
428  expectedFrame:CGRectMake(0, 0, 5, 8)
429  inputRadius:5]) {
430  numberOfExpectedVisualEffectView++;
431  }
432  }
433  XCTAssertEqual(numberOfExpectedVisualEffectView, 1u);
434 }
435 
436 - (void)testApplyMultipleBackdropFilters {
437  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
438  auto thread_task_runner = CreateNewThread("FlutterPlatformViewsTest");
439  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
440  /*platform=*/thread_task_runner,
441  /*raster=*/thread_task_runner,
442  /*ui=*/thread_task_runner,
443  /*io=*/thread_task_runner);
444  auto flutterPlatformViewsController = std::make_shared<flutter::FlutterPlatformViewsController>();
445  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
446  /*delegate=*/mock_delegate,
447  /*rendering_api=*/mock_delegate.settings_.enable_impeller
450  /*platform_views_controller=*/flutterPlatformViewsController,
451  /*task_runners=*/runners,
452  /*worker_task_runner=*/nil,
453  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
454 
457  flutterPlatformViewsController->RegisterViewFactory(
458  factory, @"MockFlutterPlatformView",
460  FlutterResult result = ^(id result) {
461  };
462  flutterPlatformViewsController->OnMethodCall(
464  methodCallWithMethodName:@"create"
465  arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}],
466  result);
467 
468  XCTAssertNotNil(gMockPlatformView);
469 
470  UIView* mockFlutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
471  flutterPlatformViewsController->SetFlutterView(mockFlutterView);
472  // Create embedded view params
473  flutter::MutatorsStack stack;
474  // Layer tree always pushes a screen scale factor to the stack
475  CGFloat screenScale = [UIScreen mainScreen].scale;
476  SkMatrix screenScaleMatrix = SkMatrix::Scale(screenScale, screenScale);
477  stack.PushTransform(screenScaleMatrix);
478  // Push backdrop filters
479  for (int i = 0; i < 50; i++) {
480  auto filter = std::make_shared<flutter::DlBlurImageFilter>(i, 2, flutter::DlTileMode::kClamp);
481  stack.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
482  }
483 
484  auto embeddedViewParams =
485  std::make_unique<flutter::EmbeddedViewParams>(screenScaleMatrix, SkSize::Make(20, 20), stack);
486 
487  flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams));
488  flutterPlatformViewsController->CompositeEmbeddedView(2);
489  XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]);
490  ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
491  [mockFlutterView addSubview:childClippingView];
492 
493  [mockFlutterView setNeedsLayout];
494  [mockFlutterView layoutIfNeeded];
495 
496  NSUInteger numberOfExpectedVisualEffectView = 0;
497  for (UIView* subview in childClippingView.subviews) {
498  if (![subview isKindOfClass:[UIVisualEffectView class]]) {
499  continue;
500  }
501  XCTAssertLessThan(numberOfExpectedVisualEffectView, 50u);
502  if ([self validateOneVisualEffectView:subview
503  expectedFrame:CGRectMake(0, 0, 10, 10)
504  inputRadius:(CGFloat)numberOfExpectedVisualEffectView]) {
505  numberOfExpectedVisualEffectView++;
506  }
507  }
508  XCTAssertEqual(numberOfExpectedVisualEffectView, (NSUInteger)numberOfExpectedVisualEffectView);
509 }
510 
511 - (void)testAddBackdropFilters {
512  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
513  auto thread_task_runner = CreateNewThread("FlutterPlatformViewsTest");
514  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
515  /*platform=*/thread_task_runner,
516  /*raster=*/thread_task_runner,
517  /*ui=*/thread_task_runner,
518  /*io=*/thread_task_runner);
519  auto flutterPlatformViewsController = std::make_shared<flutter::FlutterPlatformViewsController>();
520  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
521  /*delegate=*/mock_delegate,
522  /*rendering_api=*/mock_delegate.settings_.enable_impeller
525  /*platform_views_controller=*/flutterPlatformViewsController,
526  /*task_runners=*/runners,
527  /*worker_task_runner=*/nil,
528  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
529 
532  flutterPlatformViewsController->RegisterViewFactory(
533  factory, @"MockFlutterPlatformView",
535  FlutterResult result = ^(id result) {
536  };
537  flutterPlatformViewsController->OnMethodCall(
539  methodCallWithMethodName:@"create"
540  arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}],
541  result);
542 
543  XCTAssertNotNil(gMockPlatformView);
544 
545  UIView* mockFlutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
546  flutterPlatformViewsController->SetFlutterView(mockFlutterView);
547  // Create embedded view params
548  flutter::MutatorsStack stack;
549  // Layer tree always pushes a screen scale factor to the stack
550  CGFloat screenScale = [UIScreen mainScreen].scale;
551  SkMatrix screenScaleMatrix = SkMatrix::Scale(screenScale, screenScale);
552  stack.PushTransform(screenScaleMatrix);
553  // Push a backdrop filter
554  auto filter = std::make_shared<flutter::DlBlurImageFilter>(5, 2, flutter::DlTileMode::kClamp);
555  stack.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
556 
557  auto embeddedViewParams =
558  std::make_unique<flutter::EmbeddedViewParams>(screenScaleMatrix, SkSize::Make(10, 10), stack);
559 
560  flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams));
561  flutterPlatformViewsController->CompositeEmbeddedView(2);
562  XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:[ChildClippingView class]]);
563  ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
564  [mockFlutterView addSubview:childClippingView];
565 
566  [mockFlutterView setNeedsLayout];
567  [mockFlutterView layoutIfNeeded];
568 
569  NSMutableArray* originalVisualEffectViews = [[NSMutableArray alloc] init];
570  for (UIView* subview in childClippingView.subviews) {
571  if (![subview isKindOfClass:[UIVisualEffectView class]]) {
572  continue;
573  }
574  XCTAssertLessThan(originalVisualEffectViews.count, 1u);
575  if ([self validateOneVisualEffectView:subview
576  expectedFrame:CGRectMake(0, 0, 10, 10)
577  inputRadius:(CGFloat)5]) {
578  [originalVisualEffectViews addObject:subview];
579  }
580  }
581  XCTAssertEqual(originalVisualEffectViews.count, 1u);
582 
583  //
584  // Simulate adding 1 backdrop filter (create a new mutators stack)
585  // Create embedded view params
586  flutter::MutatorsStack stack2;
587  // Layer tree always pushes a screen scale factor to the stack
588  stack2.PushTransform(screenScaleMatrix);
589  // Push backdrop filters
590  for (int i = 0; i < 2; i++) {
591  stack2.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
592  }
593 
594  embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(screenScaleMatrix,
595  SkSize::Make(10, 10), stack2);
596 
597  flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams));
598  flutterPlatformViewsController->CompositeEmbeddedView(2);
599  [mockFlutterView setNeedsLayout];
600  [mockFlutterView layoutIfNeeded];
601 
602  NSMutableArray* newVisualEffectViews = [[NSMutableArray alloc] init];
603  for (UIView* subview in childClippingView.subviews) {
604  if (![subview isKindOfClass:[UIVisualEffectView class]]) {
605  continue;
606  }
607  XCTAssertLessThan(newVisualEffectViews.count, 2u);
608 
609  if ([self validateOneVisualEffectView:subview
610  expectedFrame:CGRectMake(0, 0, 10, 10)
611  inputRadius:(CGFloat)5]) {
612  [newVisualEffectViews addObject:subview];
613  }
614  }
615  XCTAssertEqual(newVisualEffectViews.count, 2u);
616  for (NSUInteger i = 0; i < originalVisualEffectViews.count; i++) {
617  UIView* originalView = originalVisualEffectViews[i];
618  UIView* newView = newVisualEffectViews[i];
619  // Compare reference.
620  XCTAssertEqual(originalView, newView);
621  id mockOrignalView = OCMPartialMock(originalView);
622  OCMReject([mockOrignalView removeFromSuperview]);
623  [mockOrignalView stopMocking];
624  }
625 }
626 
627 - (void)testRemoveBackdropFilters {
628  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
629  auto thread_task_runner = CreateNewThread("FlutterPlatformViewsTest");
630  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
631  /*platform=*/thread_task_runner,
632  /*raster=*/thread_task_runner,
633  /*ui=*/thread_task_runner,
634  /*io=*/thread_task_runner);
635  auto flutterPlatformViewsController = std::make_shared<flutter::FlutterPlatformViewsController>();
636  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
637  /*delegate=*/mock_delegate,
638  /*rendering_api=*/mock_delegate.settings_.enable_impeller
641  /*platform_views_controller=*/flutterPlatformViewsController,
642  /*task_runners=*/runners,
643  /*worker_task_runner=*/nil,
644  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
645 
648  flutterPlatformViewsController->RegisterViewFactory(
649  factory, @"MockFlutterPlatformView",
651  FlutterResult result = ^(id result) {
652  };
653  flutterPlatformViewsController->OnMethodCall(
655  methodCallWithMethodName:@"create"
656  arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}],
657  result);
658 
659  XCTAssertNotNil(gMockPlatformView);
660 
661  UIView* mockFlutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
662  flutterPlatformViewsController->SetFlutterView(mockFlutterView);
663  // Create embedded view params
664  flutter::MutatorsStack stack;
665  // Layer tree always pushes a screen scale factor to the stack
666  CGFloat screenScale = [UIScreen mainScreen].scale;
667  SkMatrix screenScaleMatrix = SkMatrix::Scale(screenScale, screenScale);
668  stack.PushTransform(screenScaleMatrix);
669  // Push backdrop filters
670  auto filter = std::make_shared<flutter::DlBlurImageFilter>(5, 2, flutter::DlTileMode::kClamp);
671  for (int i = 0; i < 5; i++) {
672  stack.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
673  }
674 
675  auto embeddedViewParams =
676  std::make_unique<flutter::EmbeddedViewParams>(screenScaleMatrix, SkSize::Make(10, 10), stack);
677 
678  flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams));
679  flutterPlatformViewsController->CompositeEmbeddedView(2);
680  XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]);
681  ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
682  [mockFlutterView addSubview:childClippingView];
683 
684  [mockFlutterView setNeedsLayout];
685  [mockFlutterView layoutIfNeeded];
686 
687  NSMutableArray* originalVisualEffectViews = [[NSMutableArray alloc] init];
688  for (UIView* subview in childClippingView.subviews) {
689  if (![subview isKindOfClass:[UIVisualEffectView class]]) {
690  continue;
691  }
692  XCTAssertLessThan(originalVisualEffectViews.count, 5u);
693  if ([self validateOneVisualEffectView:subview
694  expectedFrame:CGRectMake(0, 0, 10, 10)
695  inputRadius:(CGFloat)5]) {
696  [originalVisualEffectViews addObject:subview];
697  }
698  }
699 
700  // Simulate removing 1 backdrop filter (create a new mutators stack)
701  // Create embedded view params
702  flutter::MutatorsStack stack2;
703  // Layer tree always pushes a screen scale factor to the stack
704  stack2.PushTransform(screenScaleMatrix);
705  // Push backdrop filters
706  for (int i = 0; i < 4; i++) {
707  stack2.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
708  }
709 
710  embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(screenScaleMatrix,
711  SkSize::Make(10, 10), stack2);
712 
713  flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams));
714  flutterPlatformViewsController->CompositeEmbeddedView(2);
715  [mockFlutterView setNeedsLayout];
716  [mockFlutterView layoutIfNeeded];
717 
718  NSMutableArray* newVisualEffectViews = [[NSMutableArray alloc] init];
719  for (UIView* subview in childClippingView.subviews) {
720  if (![subview isKindOfClass:[UIVisualEffectView class]]) {
721  continue;
722  }
723  XCTAssertLessThan(newVisualEffectViews.count, 4u);
724  if ([self validateOneVisualEffectView:subview
725  expectedFrame:CGRectMake(0, 0, 10, 10)
726  inputRadius:(CGFloat)5]) {
727  [newVisualEffectViews addObject:subview];
728  }
729  }
730  XCTAssertEqual(newVisualEffectViews.count, 4u);
731 
732  for (NSUInteger i = 0; i < newVisualEffectViews.count; i++) {
733  UIView* newView = newVisualEffectViews[i];
734  id mockNewView = OCMPartialMock(newView);
735  UIView* originalView = originalVisualEffectViews[i];
736  // Compare reference.
737  XCTAssertEqual(originalView, newView);
738  OCMReject([mockNewView removeFromSuperview]);
739  [mockNewView stopMocking];
740  }
741 
742  // Simulate removing all backdrop filters (replace the mutators stack)
743  // Update embedded view params, delete except screenScaleMatrix
744  for (int i = 0; i < 5; i++) {
745  stack2.Pop();
746  }
747  // No backdrop filters in the stack, so no nothing to push
748 
749  embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(screenScaleMatrix,
750  SkSize::Make(10, 10), stack2);
751 
752  flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams));
753  flutterPlatformViewsController->CompositeEmbeddedView(2);
754  [mockFlutterView setNeedsLayout];
755  [mockFlutterView layoutIfNeeded];
756 
757  NSUInteger numberOfExpectedVisualEffectView = 0u;
758  for (UIView* subview in childClippingView.subviews) {
759  if ([subview isKindOfClass:[UIVisualEffectView class]]) {
760  numberOfExpectedVisualEffectView++;
761  }
762  }
763  XCTAssertEqual(numberOfExpectedVisualEffectView, 0u);
764 }
765 
766 - (void)testEditBackdropFilters {
767  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
768  auto thread_task_runner = CreateNewThread("FlutterPlatformViewsTest");
769  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
770  /*platform=*/thread_task_runner,
771  /*raster=*/thread_task_runner,
772  /*ui=*/thread_task_runner,
773  /*io=*/thread_task_runner);
774  auto flutterPlatformViewsController = std::make_shared<flutter::FlutterPlatformViewsController>();
775  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
776  /*delegate=*/mock_delegate,
777  /*rendering_api=*/mock_delegate.settings_.enable_impeller
780  /*platform_views_controller=*/flutterPlatformViewsController,
781  /*task_runners=*/runners,
782  /*worker_task_runner=*/nil,
783  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
784 
787  flutterPlatformViewsController->RegisterViewFactory(
788  factory, @"MockFlutterPlatformView",
790  FlutterResult result = ^(id result) {
791  };
792  flutterPlatformViewsController->OnMethodCall(
794  methodCallWithMethodName:@"create"
795  arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}],
796  result);
797 
798  XCTAssertNotNil(gMockPlatformView);
799 
800  UIView* mockFlutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
801  flutterPlatformViewsController->SetFlutterView(mockFlutterView);
802  // Create embedded view params
803  flutter::MutatorsStack stack;
804  // Layer tree always pushes a screen scale factor to the stack
805  CGFloat screenScale = [UIScreen mainScreen].scale;
806  SkMatrix screenScaleMatrix = SkMatrix::Scale(screenScale, screenScale);
807  stack.PushTransform(screenScaleMatrix);
808  // Push backdrop filters
809  auto filter = std::make_shared<flutter::DlBlurImageFilter>(5, 2, flutter::DlTileMode::kClamp);
810  for (int i = 0; i < 5; i++) {
811  stack.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
812  }
813 
814  auto embeddedViewParams =
815  std::make_unique<flutter::EmbeddedViewParams>(screenScaleMatrix, SkSize::Make(10, 10), stack);
816 
817  flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams));
818  flutterPlatformViewsController->CompositeEmbeddedView(2);
819  XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]);
820  ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
821  [mockFlutterView addSubview:childClippingView];
822 
823  [mockFlutterView setNeedsLayout];
824  [mockFlutterView layoutIfNeeded];
825 
826  NSMutableArray* originalVisualEffectViews = [[NSMutableArray alloc] init];
827  for (UIView* subview in childClippingView.subviews) {
828  if (![subview isKindOfClass:[UIVisualEffectView class]]) {
829  continue;
830  }
831  XCTAssertLessThan(originalVisualEffectViews.count, 5u);
832  if ([self validateOneVisualEffectView:subview
833  expectedFrame:CGRectMake(0, 0, 10, 10)
834  inputRadius:(CGFloat)5]) {
835  [originalVisualEffectViews addObject:subview];
836  }
837  }
838 
839  // Simulate editing 1 backdrop filter in the middle of the stack (create a new mutators stack)
840  // Create embedded view params
841  flutter::MutatorsStack stack2;
842  // Layer tree always pushes a screen scale factor to the stack
843  stack2.PushTransform(screenScaleMatrix);
844  // Push backdrop filters
845  for (int i = 0; i < 5; i++) {
846  if (i == 3) {
847  auto filter2 =
848  std::make_shared<flutter::DlBlurImageFilter>(2, 5, flutter::DlTileMode::kClamp);
849 
850  stack2.PushBackdropFilter(filter2,
851  SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
852  continue;
853  }
854 
855  stack2.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
856  }
857 
858  embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(screenScaleMatrix,
859  SkSize::Make(10, 10), stack2);
860 
861  flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams));
862  flutterPlatformViewsController->CompositeEmbeddedView(2);
863  [mockFlutterView setNeedsLayout];
864  [mockFlutterView layoutIfNeeded];
865 
866  NSMutableArray* newVisualEffectViews = [[NSMutableArray alloc] init];
867  for (UIView* subview in childClippingView.subviews) {
868  if (![subview isKindOfClass:[UIVisualEffectView class]]) {
869  continue;
870  }
871  XCTAssertLessThan(newVisualEffectViews.count, 5u);
872  CGFloat expectInputRadius = 5;
873  if (newVisualEffectViews.count == 3) {
874  expectInputRadius = 2;
875  }
876  if ([self validateOneVisualEffectView:subview
877  expectedFrame:CGRectMake(0, 0, 10, 10)
878  inputRadius:(CGFloat)expectInputRadius]) {
879  [newVisualEffectViews addObject:subview];
880  }
881  }
882  XCTAssertEqual(newVisualEffectViews.count, 5u);
883  for (NSUInteger i = 0; i < newVisualEffectViews.count; i++) {
884  UIView* newView = newVisualEffectViews[i];
885  id mockNewView = OCMPartialMock(newView);
886  UIView* originalView = originalVisualEffectViews[i];
887  // Compare reference.
888  XCTAssertEqual(originalView, newView);
889  OCMReject([mockNewView removeFromSuperview]);
890  [mockNewView stopMocking];
891  }
892  [newVisualEffectViews removeAllObjects];
893 
894  // Simulate editing 1 backdrop filter in the beginning of the stack (replace the mutators stack)
895  // Update embedded view params, delete except screenScaleMatrix
896  for (int i = 0; i < 5; i++) {
897  stack2.Pop();
898  }
899  // Push backdrop filters
900  for (int i = 0; i < 5; i++) {
901  if (i == 0) {
902  auto filter2 =
903  std::make_shared<flutter::DlBlurImageFilter>(2, 5, flutter::DlTileMode::kClamp);
904  stack2.PushBackdropFilter(filter2,
905  SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
906  continue;
907  }
908 
909  stack2.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
910  }
911 
912  embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(screenScaleMatrix,
913  SkSize::Make(10, 10), stack2);
914 
915  flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams));
916  flutterPlatformViewsController->CompositeEmbeddedView(2);
917  [mockFlutterView setNeedsLayout];
918  [mockFlutterView layoutIfNeeded];
919 
920  for (UIView* subview in childClippingView.subviews) {
921  if (![subview isKindOfClass:[UIVisualEffectView class]]) {
922  continue;
923  }
924  XCTAssertLessThan(newVisualEffectViews.count, 5u);
925  CGFloat expectInputRadius = 5;
926  if (newVisualEffectViews.count == 0) {
927  expectInputRadius = 2;
928  }
929  if ([self validateOneVisualEffectView:subview
930  expectedFrame:CGRectMake(0, 0, 10, 10)
931  inputRadius:(CGFloat)expectInputRadius]) {
932  [newVisualEffectViews addObject:subview];
933  }
934  }
935  for (NSUInteger i = 0; i < newVisualEffectViews.count; i++) {
936  UIView* newView = newVisualEffectViews[i];
937  id mockNewView = OCMPartialMock(newView);
938  UIView* originalView = originalVisualEffectViews[i];
939  // Compare reference.
940  XCTAssertEqual(originalView, newView);
941  OCMReject([mockNewView removeFromSuperview]);
942  [mockNewView stopMocking];
943  }
944  [newVisualEffectViews removeAllObjects];
945 
946  // Simulate editing 1 backdrop filter in the end of the stack (replace the mutators stack)
947  // Update embedded view params, delete except screenScaleMatrix
948  for (int i = 0; i < 5; i++) {
949  stack2.Pop();
950  }
951  // Push backdrop filters
952  for (int i = 0; i < 5; i++) {
953  if (i == 4) {
954  auto filter2 =
955  std::make_shared<flutter::DlBlurImageFilter>(2, 5, flutter::DlTileMode::kClamp);
956  stack2.PushBackdropFilter(filter2,
957  SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
958  continue;
959  }
960 
961  stack2.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
962  }
963 
964  embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(screenScaleMatrix,
965  SkSize::Make(10, 10), stack2);
966 
967  flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams));
968  flutterPlatformViewsController->CompositeEmbeddedView(2);
969  [mockFlutterView setNeedsLayout];
970  [mockFlutterView layoutIfNeeded];
971 
972  for (UIView* subview in childClippingView.subviews) {
973  if (![subview isKindOfClass:[UIVisualEffectView class]]) {
974  continue;
975  }
976  XCTAssertLessThan(newVisualEffectViews.count, 5u);
977  CGFloat expectInputRadius = 5;
978  if (newVisualEffectViews.count == 4) {
979  expectInputRadius = 2;
980  }
981  if ([self validateOneVisualEffectView:subview
982  expectedFrame:CGRectMake(0, 0, 10, 10)
983  inputRadius:(CGFloat)expectInputRadius]) {
984  [newVisualEffectViews addObject:subview];
985  }
986  }
987  XCTAssertEqual(newVisualEffectViews.count, 5u);
988 
989  for (NSUInteger i = 0; i < newVisualEffectViews.count; i++) {
990  UIView* newView = newVisualEffectViews[i];
991  id mockNewView = OCMPartialMock(newView);
992  UIView* originalView = originalVisualEffectViews[i];
993  // Compare reference.
994  XCTAssertEqual(originalView, newView);
995  OCMReject([mockNewView removeFromSuperview]);
996  [mockNewView stopMocking];
997  }
998  [newVisualEffectViews removeAllObjects];
999 
1000  // Simulate editing all backdrop filters in the stack (replace the mutators stack)
1001  // Update embedded view params, delete except screenScaleMatrix
1002  for (int i = 0; i < 5; i++) {
1003  stack2.Pop();
1004  }
1005  // Push backdrop filters
1006  for (int i = 0; i < 5; i++) {
1007  auto filter2 = std::make_shared<flutter::DlBlurImageFilter>(i, 2, flutter::DlTileMode::kClamp);
1008 
1009  stack2.PushBackdropFilter(filter2, SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
1010  }
1011 
1012  embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(screenScaleMatrix,
1013  SkSize::Make(10, 10), stack2);
1014 
1015  flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams));
1016  flutterPlatformViewsController->CompositeEmbeddedView(2);
1017  [mockFlutterView setNeedsLayout];
1018  [mockFlutterView layoutIfNeeded];
1019 
1020  for (UIView* subview in childClippingView.subviews) {
1021  if (![subview isKindOfClass:[UIVisualEffectView class]]) {
1022  continue;
1023  }
1024  XCTAssertLessThan(newVisualEffectViews.count, 5u);
1025  if ([self validateOneVisualEffectView:subview
1026  expectedFrame:CGRectMake(0, 0, 10, 10)
1027  inputRadius:(CGFloat)newVisualEffectViews.count]) {
1028  [newVisualEffectViews addObject:subview];
1029  }
1030  }
1031  XCTAssertEqual(newVisualEffectViews.count, 5u);
1032 
1033  for (NSUInteger i = 0; i < newVisualEffectViews.count; i++) {
1034  UIView* newView = newVisualEffectViews[i];
1035  id mockNewView = OCMPartialMock(newView);
1036  UIView* originalView = originalVisualEffectViews[i];
1037  // Compare reference.
1038  XCTAssertEqual(originalView, newView);
1039  OCMReject([mockNewView removeFromSuperview]);
1040  [mockNewView stopMocking];
1041  }
1042  [newVisualEffectViews removeAllObjects];
1043 }
1044 
1045 - (void)testApplyBackdropFilterNotDlBlurImageFilter {
1046  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
1047  auto thread_task_runner = CreateNewThread("FlutterPlatformViewsTest");
1048  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
1049  /*platform=*/thread_task_runner,
1050  /*raster=*/thread_task_runner,
1051  /*ui=*/thread_task_runner,
1052  /*io=*/thread_task_runner);
1053  auto flutterPlatformViewsController = std::make_shared<flutter::FlutterPlatformViewsController>();
1054  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1055  /*delegate=*/mock_delegate,
1056  /*rendering_api=*/mock_delegate.settings_.enable_impeller
1059  /*platform_views_controller=*/flutterPlatformViewsController,
1060  /*task_runners=*/runners,
1061  /*worker_task_runner=*/nil,
1062  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
1063 
1066  flutterPlatformViewsController->RegisterViewFactory(
1067  factory, @"MockFlutterPlatformView",
1069  FlutterResult result = ^(id result) {
1070  };
1071  flutterPlatformViewsController->OnMethodCall(
1073  methodCallWithMethodName:@"create"
1074  arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}],
1075  result);
1076 
1077  XCTAssertNotNil(gMockPlatformView);
1078 
1079  UIView* mockFlutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
1080  flutterPlatformViewsController->SetFlutterView(mockFlutterView);
1081  // Create embedded view params
1082  flutter::MutatorsStack stack;
1083  // Layer tree always pushes a screen scale factor to the stack
1084  CGFloat screenScale = [UIScreen mainScreen].scale;
1085  SkMatrix screenScaleMatrix = SkMatrix::Scale(screenScale, screenScale);
1086  stack.PushTransform(screenScaleMatrix);
1087  // Push a dilate backdrop filter
1088  auto dilateFilter = std::make_shared<flutter::DlDilateImageFilter>(5, 2);
1089  stack.PushBackdropFilter(dilateFilter, SkRect::MakeEmpty());
1090 
1091  auto embeddedViewParams =
1092  std::make_unique<flutter::EmbeddedViewParams>(screenScaleMatrix, SkSize::Make(10, 10), stack);
1093 
1094  flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams));
1095  flutterPlatformViewsController->CompositeEmbeddedView(2);
1096  XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:[ChildClippingView class]]);
1097  ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
1098 
1099  [mockFlutterView addSubview:childClippingView];
1100 
1101  [mockFlutterView setNeedsLayout];
1102  [mockFlutterView layoutIfNeeded];
1103 
1104  NSUInteger numberOfExpectedVisualEffectView = 0;
1105  for (UIView* subview in childClippingView.subviews) {
1106  if ([subview isKindOfClass:[UIVisualEffectView class]]) {
1107  numberOfExpectedVisualEffectView++;
1108  }
1109  }
1110  XCTAssertEqual(numberOfExpectedVisualEffectView, 0u);
1111 
1112  // Simulate adding a non-DlBlurImageFilter in the middle of the stack (create a new mutators
1113  // stack) Create embedded view params
1114  flutter::MutatorsStack stack2;
1115  // Layer tree always pushes a screen scale factor to the stack
1116  stack2.PushTransform(screenScaleMatrix);
1117  // Push backdrop filters and dilate filter
1118  auto blurFilter = std::make_shared<flutter::DlBlurImageFilter>(5, 2, flutter::DlTileMode::kClamp);
1119 
1120  for (int i = 0; i < 5; i++) {
1121  if (i == 2) {
1122  stack2.PushBackdropFilter(dilateFilter,
1123  SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
1124  continue;
1125  }
1126 
1127  stack2.PushBackdropFilter(blurFilter,
1128  SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
1129  }
1130 
1131  embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(screenScaleMatrix,
1132  SkSize::Make(10, 10), stack2);
1133 
1134  flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams));
1135  flutterPlatformViewsController->CompositeEmbeddedView(2);
1136  [mockFlutterView setNeedsLayout];
1137  [mockFlutterView layoutIfNeeded];
1138 
1139  numberOfExpectedVisualEffectView = 0;
1140  for (UIView* subview in childClippingView.subviews) {
1141  if (![subview isKindOfClass:[UIVisualEffectView class]]) {
1142  continue;
1143  }
1144  XCTAssertLessThan(numberOfExpectedVisualEffectView, 4u);
1145  if ([self validateOneVisualEffectView:subview
1146  expectedFrame:CGRectMake(0, 0, 10, 10)
1147  inputRadius:(CGFloat)5]) {
1148  numberOfExpectedVisualEffectView++;
1149  }
1150  }
1151  XCTAssertEqual(numberOfExpectedVisualEffectView, 4u);
1152 
1153  // Simulate adding a non-DlBlurImageFilter to the beginning of the stack (replace the mutators
1154  // stack) Update embedded view params, delete except screenScaleMatrix
1155  for (int i = 0; i < 5; i++) {
1156  stack2.Pop();
1157  }
1158  // Push backdrop filters and dilate filter
1159  for (int i = 0; i < 5; i++) {
1160  if (i == 0) {
1161  stack2.PushBackdropFilter(dilateFilter,
1162  SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
1163  continue;
1164  }
1165 
1166  stack2.PushBackdropFilter(blurFilter,
1167  SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
1168  }
1169 
1170  embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(screenScaleMatrix,
1171  SkSize::Make(10, 10), stack2);
1172 
1173  flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams));
1174  flutterPlatformViewsController->CompositeEmbeddedView(2);
1175  [mockFlutterView setNeedsLayout];
1176  [mockFlutterView layoutIfNeeded];
1177 
1178  numberOfExpectedVisualEffectView = 0;
1179  for (UIView* subview in childClippingView.subviews) {
1180  if (![subview isKindOfClass:[UIVisualEffectView class]]) {
1181  continue;
1182  }
1183  XCTAssertLessThan(numberOfExpectedVisualEffectView, 4u);
1184  if ([self validateOneVisualEffectView:subview
1185  expectedFrame:CGRectMake(0, 0, 10, 10)
1186  inputRadius:(CGFloat)5]) {
1187  numberOfExpectedVisualEffectView++;
1188  }
1189  }
1190  XCTAssertEqual(numberOfExpectedVisualEffectView, 4u);
1191 
1192  // Simulate adding a non-DlBlurImageFilter to the end of the stack (replace the mutators stack)
1193  // Update embedded view params, delete except screenScaleMatrix
1194  for (int i = 0; i < 5; i++) {
1195  stack2.Pop();
1196  }
1197  // Push backdrop filters and dilate filter
1198  for (int i = 0; i < 5; i++) {
1199  if (i == 4) {
1200  stack2.PushBackdropFilter(dilateFilter,
1201  SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
1202  continue;
1203  }
1204 
1205  stack2.PushBackdropFilter(blurFilter,
1206  SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
1207  }
1208 
1209  embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(screenScaleMatrix,
1210  SkSize::Make(10, 10), stack2);
1211 
1212  flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams));
1213  flutterPlatformViewsController->CompositeEmbeddedView(2);
1214  [mockFlutterView setNeedsLayout];
1215  [mockFlutterView layoutIfNeeded];
1216 
1217  numberOfExpectedVisualEffectView = 0;
1218  for (UIView* subview in childClippingView.subviews) {
1219  if (![subview isKindOfClass:[UIVisualEffectView class]]) {
1220  continue;
1221  }
1222  XCTAssertLessThan(numberOfExpectedVisualEffectView, 4u);
1223  if ([self validateOneVisualEffectView:subview
1224  expectedFrame:CGRectMake(0, 0, 10, 10)
1225  inputRadius:(CGFloat)5]) {
1226  numberOfExpectedVisualEffectView++;
1227  }
1228  }
1229  XCTAssertEqual(numberOfExpectedVisualEffectView, 4u);
1230 
1231  // Simulate adding only non-DlBlurImageFilter to the stack (replace the mutators stack)
1232  // Update embedded view params, delete except screenScaleMatrix
1233  for (int i = 0; i < 5; i++) {
1234  stack2.Pop();
1235  }
1236  // Push dilate filters
1237  for (int i = 0; i < 5; i++) {
1238  stack2.PushBackdropFilter(dilateFilter,
1239  SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
1240  }
1241 
1242  embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(screenScaleMatrix,
1243  SkSize::Make(10, 10), stack2);
1244 
1245  flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams));
1246  flutterPlatformViewsController->CompositeEmbeddedView(2);
1247  [mockFlutterView setNeedsLayout];
1248  [mockFlutterView layoutIfNeeded];
1249 
1250  numberOfExpectedVisualEffectView = 0;
1251  for (UIView* subview in childClippingView.subviews) {
1252  if ([subview isKindOfClass:[UIVisualEffectView class]]) {
1253  numberOfExpectedVisualEffectView++;
1254  }
1255  }
1256  XCTAssertEqual(numberOfExpectedVisualEffectView, 0u);
1257 }
1258 
1259 - (void)testApplyBackdropFilterCorrectAPI {
1261  // The gaussianBlur filter is extracted from UIVisualEffectView.
1262  // Each test requires a new PlatformViewFilter
1263  // Valid UIVisualEffectView API
1264  UIVisualEffectView* visualEffectView = [[UIVisualEffectView alloc]
1265  initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]];
1266  PlatformViewFilter* platformViewFilter =
1267  [[PlatformViewFilter alloc] initWithFrame:CGRectMake(0, 0, 10, 10)
1268  blurRadius:5
1269  visualEffectView:visualEffectView];
1270  XCTAssertNotNil(platformViewFilter);
1271 }
1272 
1273 - (void)testApplyBackdropFilterAPIChangedInvalidUIVisualEffectView {
1275  UIVisualEffectView* visualEffectView = [[UIVisualEffectView alloc] init];
1276  PlatformViewFilter* platformViewFilter =
1277  [[PlatformViewFilter alloc] initWithFrame:CGRectMake(0, 0, 10, 10)
1278  blurRadius:5
1279  visualEffectView:visualEffectView];
1280  XCTAssertNil(platformViewFilter);
1281 }
1282 
1283 - (void)testApplyBackdropFilterAPIChangedNoGaussianBlurFilter {
1285  UIVisualEffectView* editedUIVisualEffectView = [[UIVisualEffectView alloc]
1286  initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]];
1287  NSArray* subviews = editedUIVisualEffectView.subviews;
1288  for (UIView* view in subviews) {
1289  if ([NSStringFromClass([view class]) hasSuffix:@"BackdropView"]) {
1290  for (CIFilter* filter in view.layer.filters) {
1291  if ([[filter valueForKey:@"name"] isEqual:@"gaussianBlur"]) {
1292  [filter setValue:@"notGaussianBlur" forKey:@"name"];
1293  break;
1294  }
1295  }
1296  break;
1297  }
1298  }
1299  PlatformViewFilter* platformViewFilter =
1300  [[PlatformViewFilter alloc] initWithFrame:CGRectMake(0, 0, 10, 10)
1301  blurRadius:5
1302  visualEffectView:editedUIVisualEffectView];
1303  XCTAssertNil(platformViewFilter);
1304 }
1305 
1306 - (void)testApplyBackdropFilterAPIChangedInvalidInputRadius {
1308  UIVisualEffectView* editedUIVisualEffectView = [[UIVisualEffectView alloc]
1309  initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]];
1310  NSArray* subviews = editedUIVisualEffectView.subviews;
1311  for (UIView* view in subviews) {
1312  if ([NSStringFromClass([view class]) hasSuffix:@"BackdropView"]) {
1313  for (CIFilter* filter in view.layer.filters) {
1314  if ([[filter valueForKey:@"name"] isEqual:@"gaussianBlur"]) {
1315  [filter setValue:@"invalidInputRadius" forKey:@"inputRadius"];
1316  break;
1317  }
1318  }
1319  break;
1320  }
1321  }
1322 
1323  PlatformViewFilter* platformViewFilter =
1324  [[PlatformViewFilter alloc] initWithFrame:CGRectMake(0, 0, 10, 10)
1325  blurRadius:5
1326  visualEffectView:editedUIVisualEffectView];
1327  XCTAssertNil(platformViewFilter);
1328 }
1329 
1330 - (void)testBackdropFilterVisualEffectSubviewBackgroundColor {
1331  __weak UIVisualEffectView* weakVisualEffectView;
1332 
1333  @autoreleasepool {
1334  UIVisualEffectView* visualEffectView = [[UIVisualEffectView alloc]
1335  initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]];
1336  weakVisualEffectView = visualEffectView;
1337  PlatformViewFilter* platformViewFilter =
1338  [[PlatformViewFilter alloc] initWithFrame:CGRectMake(0, 0, 10, 10)
1339  blurRadius:5
1340  visualEffectView:visualEffectView];
1341  CGColorRef visualEffectSubviewBackgroundColor = nil;
1342  for (UIView* view in [platformViewFilter backdropFilterView].subviews) {
1343  if ([NSStringFromClass([view class]) hasSuffix:@"VisualEffectSubview"]) {
1344  visualEffectSubviewBackgroundColor = view.layer.backgroundColor;
1345  }
1346  }
1347  XCTAssertTrue(
1348  CGColorEqualToColor(visualEffectSubviewBackgroundColor, UIColor.clearColor.CGColor));
1349  }
1350  XCTAssertNil(weakVisualEffectView);
1351 }
1352 
1353 - (void)testCompositePlatformView {
1354  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
1355  auto thread_task_runner = CreateNewThread("FlutterPlatformViewsTest");
1356  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
1357  /*platform=*/thread_task_runner,
1358  /*raster=*/thread_task_runner,
1359  /*ui=*/thread_task_runner,
1360  /*io=*/thread_task_runner);
1361  auto flutterPlatformViewsController = std::make_shared<flutter::FlutterPlatformViewsController>();
1362  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1363  /*delegate=*/mock_delegate,
1364  /*rendering_api=*/mock_delegate.settings_.enable_impeller
1367  /*platform_views_controller=*/flutterPlatformViewsController,
1368  /*task_runners=*/runners,
1369  /*worker_task_runner=*/nil,
1370  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
1371 
1374  flutterPlatformViewsController->RegisterViewFactory(
1375  factory, @"MockFlutterPlatformView",
1377  FlutterResult result = ^(id result) {
1378  };
1379  flutterPlatformViewsController->OnMethodCall(
1381  methodCallWithMethodName:@"create"
1382  arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}],
1383  result);
1384 
1385  XCTAssertNotNil(gMockPlatformView);
1386 
1387  UIView* mockFlutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
1388  flutterPlatformViewsController->SetFlutterView(mockFlutterView);
1389  // Create embedded view params
1390  flutter::MutatorsStack stack;
1391  // Layer tree always pushes a screen scale factor to the stack
1392  SkMatrix screenScaleMatrix =
1393  SkMatrix::Scale([UIScreen mainScreen].scale, [UIScreen mainScreen].scale);
1394  stack.PushTransform(screenScaleMatrix);
1395  // Push a translate matrix
1396  SkMatrix translateMatrix = SkMatrix::Translate(100, 100);
1397  stack.PushTransform(translateMatrix);
1398  SkMatrix finalMatrix;
1399  finalMatrix.setConcat(screenScaleMatrix, translateMatrix);
1400 
1401  auto embeddedViewParams =
1402  std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, SkSize::Make(300, 300), stack);
1403 
1404  flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams));
1405  flutterPlatformViewsController->CompositeEmbeddedView(2);
1406  CGRect platformViewRectInFlutterView = [gMockPlatformView convertRect:gMockPlatformView.bounds
1407  toView:mockFlutterView];
1408  XCTAssertTrue(CGRectEqualToRect(platformViewRectInFlutterView, CGRectMake(100, 100, 300, 300)));
1409 }
1410 
1411 - (void)testBackdropFilterCorrectlyPushedAndReset {
1412  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
1413  auto thread_task_runner = CreateNewThread("FlutterPlatformViewsTest");
1414  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
1415  /*platform=*/thread_task_runner,
1416  /*raster=*/thread_task_runner,
1417  /*ui=*/thread_task_runner,
1418  /*io=*/thread_task_runner);
1419  auto flutterPlatformViewsController = std::make_shared<flutter::FlutterPlatformViewsController>();
1420  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1421  /*delegate=*/mock_delegate,
1422  /*rendering_api=*/mock_delegate.settings_.enable_impeller
1425  /*platform_views_controller=*/flutterPlatformViewsController,
1426  /*task_runners=*/runners,
1427  /*worker_task_runner=*/nil,
1428  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
1429 
1432  flutterPlatformViewsController->RegisterViewFactory(
1433  factory, @"MockFlutterPlatformView",
1435  FlutterResult result = ^(id result) {
1436  };
1437  flutterPlatformViewsController->OnMethodCall(
1439  methodCallWithMethodName:@"create"
1440  arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}],
1441  result);
1442 
1443  XCTAssertNotNil(gMockPlatformView);
1444 
1445  UIView* mockFlutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
1446  flutterPlatformViewsController->SetFlutterView(mockFlutterView);
1447  // Create embedded view params
1448  flutter::MutatorsStack stack;
1449  // Layer tree always pushes a screen scale factor to the stack
1450  CGFloat screenScale = [UIScreen mainScreen].scale;
1451  SkMatrix screenScaleMatrix = SkMatrix::Scale(screenScale, screenScale);
1452  stack.PushTransform(screenScaleMatrix);
1453 
1454  auto embeddedViewParams =
1455  std::make_unique<flutter::EmbeddedViewParams>(screenScaleMatrix, SkSize::Make(10, 10), stack);
1456 
1457  flutterPlatformViewsController->BeginFrame(SkISize::Make(0, 0));
1458  flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams));
1459  flutterPlatformViewsController->PushVisitedPlatformView(2);
1460  auto filter = std::make_shared<flutter::DlBlurImageFilter>(5, 2, flutter::DlTileMode::kClamp);
1461  flutterPlatformViewsController->PushFilterToVisitedPlatformViews(
1462  filter, SkRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
1463  flutterPlatformViewsController->CompositeEmbeddedView(2);
1464  XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:[ChildClippingView class]]);
1465  ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
1466  [mockFlutterView addSubview:childClippingView];
1467 
1468  [mockFlutterView setNeedsLayout];
1469  [mockFlutterView layoutIfNeeded];
1470 
1471  // childClippingView has visual effect view with the correct configurations.
1472  NSUInteger numberOfExpectedVisualEffectView = 0;
1473  for (UIView* subview in childClippingView.subviews) {
1474  if (![subview isKindOfClass:[UIVisualEffectView class]]) {
1475  continue;
1476  }
1477  XCTAssertLessThan(numberOfExpectedVisualEffectView, 1u);
1478  if ([self validateOneVisualEffectView:subview
1479  expectedFrame:CGRectMake(0, 0, 10, 10)
1480  inputRadius:5]) {
1481  numberOfExpectedVisualEffectView++;
1482  }
1483  }
1484  XCTAssertEqual(numberOfExpectedVisualEffectView, 1u);
1485 
1486  // New frame, with no filter pushed.
1487  auto embeddedViewParams2 =
1488  std::make_unique<flutter::EmbeddedViewParams>(screenScaleMatrix, SkSize::Make(10, 10), stack);
1489  flutterPlatformViewsController->BeginFrame(SkISize::Make(0, 0));
1490  flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams2));
1491  flutterPlatformViewsController->CompositeEmbeddedView(2);
1492  XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:[ChildClippingView class]]);
1493 
1494  [mockFlutterView setNeedsLayout];
1495  [mockFlutterView layoutIfNeeded];
1496 
1497  numberOfExpectedVisualEffectView = 0;
1498  for (UIView* subview in childClippingView.subviews) {
1499  if (![subview isKindOfClass:[UIVisualEffectView class]]) {
1500  continue;
1501  }
1502  numberOfExpectedVisualEffectView++;
1503  }
1504  XCTAssertEqual(numberOfExpectedVisualEffectView, 0u);
1505 }
1506 
1507 - (void)testChildClippingViewShouldBeTheBoundingRectOfPlatformView {
1508  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
1509  auto thread_task_runner = CreateNewThread("FlutterPlatformViewsTest");
1510  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
1511  /*platform=*/thread_task_runner,
1512  /*raster=*/thread_task_runner,
1513  /*ui=*/thread_task_runner,
1514  /*io=*/thread_task_runner);
1515  auto flutterPlatformViewsController = std::make_shared<flutter::FlutterPlatformViewsController>();
1516  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1517  /*delegate=*/mock_delegate,
1518  /*rendering_api=*/mock_delegate.settings_.enable_impeller
1521  /*platform_views_controller=*/flutterPlatformViewsController,
1522  /*task_runners=*/runners,
1523  /*worker_task_runner=*/nil,
1524  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
1525 
1528  flutterPlatformViewsController->RegisterViewFactory(
1529  factory, @"MockFlutterPlatformView",
1531  FlutterResult result = ^(id result) {
1532  };
1533  flutterPlatformViewsController->OnMethodCall(
1535  methodCallWithMethodName:@"create"
1536  arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}],
1537  result);
1538 
1539  XCTAssertNotNil(gMockPlatformView);
1540 
1541  UIView* mockFlutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
1542  flutterPlatformViewsController->SetFlutterView(mockFlutterView);
1543  // Create embedded view params
1544  flutter::MutatorsStack stack;
1545  // Layer tree always pushes a screen scale factor to the stack
1546  SkMatrix screenScaleMatrix =
1547  SkMatrix::Scale([UIScreen mainScreen].scale, [UIScreen mainScreen].scale);
1548  stack.PushTransform(screenScaleMatrix);
1549  // Push a rotate matrix
1550  SkMatrix rotateMatrix;
1551  rotateMatrix.setRotate(10);
1552  stack.PushTransform(rotateMatrix);
1553  SkMatrix finalMatrix;
1554  finalMatrix.setConcat(screenScaleMatrix, rotateMatrix);
1555 
1556  auto embeddedViewParams =
1557  std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, SkSize::Make(300, 300), stack);
1558 
1559  flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams));
1560  flutterPlatformViewsController->CompositeEmbeddedView(2);
1561  CGRect platformViewRectInFlutterView = [gMockPlatformView convertRect:gMockPlatformView.bounds
1562  toView:mockFlutterView];
1563  XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]);
1564  ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
1565  // The childclippingview's frame is set based on flow, but the platform view's frame is set based
1566  // on quartz. Although they should be the same, but we should tolerate small floating point
1567  // errors.
1568  XCTAssertLessThan(fabs(platformViewRectInFlutterView.origin.x - childClippingView.frame.origin.x),
1570  XCTAssertLessThan(fabs(platformViewRectInFlutterView.origin.y - childClippingView.frame.origin.y),
1572  XCTAssertLessThan(
1573  fabs(platformViewRectInFlutterView.size.width - childClippingView.frame.size.width),
1575  XCTAssertLessThan(
1576  fabs(platformViewRectInFlutterView.size.height - childClippingView.frame.size.height),
1578 }
1579 
1580 - (void)testClipsDoNotInterceptWithPlatformViewShouldNotAddMaskView {
1581  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
1582  auto thread_task_runner = CreateNewThread("FlutterPlatformViewsTest");
1583  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
1584  /*platform=*/thread_task_runner,
1585  /*raster=*/thread_task_runner,
1586  /*ui=*/thread_task_runner,
1587  /*io=*/thread_task_runner);
1588  auto flutterPlatformViewsController = std::make_shared<flutter::FlutterPlatformViewsController>();
1589  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1590  /*delegate=*/mock_delegate,
1591  /*rendering_api=*/mock_delegate.settings_.enable_impeller
1594  /*platform_views_controller=*/flutterPlatformViewsController,
1595  /*task_runners=*/runners,
1596  /*worker_task_runner=*/nil,
1597  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
1598 
1601  flutterPlatformViewsController->RegisterViewFactory(
1602  factory, @"MockFlutterPlatformView",
1604  FlutterResult result = ^(id result) {
1605  };
1606  flutterPlatformViewsController->OnMethodCall(
1608  methodCallWithMethodName:@"create"
1609  arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}],
1610  result);
1611 
1612  XCTAssertNotNil(gMockPlatformView);
1613 
1614  UIView* mockFlutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 30, 30)];
1615  flutterPlatformViewsController->SetFlutterView(mockFlutterView);
1616  // Create embedded view params.
1617  flutter::MutatorsStack stack;
1618  // Layer tree always pushes a screen scale factor to the stack.
1619  SkMatrix screenScaleMatrix =
1620  SkMatrix::Scale([UIScreen mainScreen].scale, [UIScreen mainScreen].scale);
1621  stack.PushTransform(screenScaleMatrix);
1622  SkMatrix translateMatrix = SkMatrix::Translate(5, 5);
1623  // The platform view's rect for this test will be (5, 5, 10, 10).
1624  stack.PushTransform(translateMatrix);
1625  // Push a clip rect, big enough to contain the entire platform view bound.
1626  SkRect rect = SkRect::MakeXYWH(0, 0, 25, 25);
1627  stack.PushClipRect(rect);
1628  // Push a clip rrect, big enough to contain the entire platform view bound without clipping it.
1629  // Make the origin (-1, -1) so that the top left rounded corner isn't clipping the PlatformView.
1630  SkRect rect_for_rrect = SkRect::MakeXYWH(-1, -1, 25, 25);
1631  SkRRect rrect = SkRRect::MakeRectXY(rect_for_rrect, 1, 1);
1632  stack.PushClipRRect(rrect);
1633 
1634  auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
1635  SkMatrix::Concat(screenScaleMatrix, translateMatrix), SkSize::Make(5, 5), stack);
1636 
1637  flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams));
1638  flutterPlatformViewsController->CompositeEmbeddedView(2);
1639  gMockPlatformView.backgroundColor = UIColor.redColor;
1640  XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]);
1641  ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
1642  [mockFlutterView addSubview:childClippingView];
1643 
1644  [mockFlutterView setNeedsLayout];
1645  [mockFlutterView layoutIfNeeded];
1646  XCTAssertNil(childClippingView.maskView);
1647 }
1648 
1649 - (void)testClipRRectOnlyHasCornersInterceptWithPlatformViewShouldAddMaskView {
1650  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
1651  auto thread_task_runner = CreateNewThread("FlutterPlatformViewsTest");
1652  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
1653  /*platform=*/thread_task_runner,
1654  /*raster=*/thread_task_runner,
1655  /*ui=*/thread_task_runner,
1656  /*io=*/thread_task_runner);
1657  auto flutterPlatformViewsController = std::make_shared<flutter::FlutterPlatformViewsController>();
1658  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1659  /*delegate=*/mock_delegate,
1660  /*rendering_api=*/mock_delegate.settings_.enable_impeller
1663  /*platform_views_controller=*/flutterPlatformViewsController,
1664  /*task_runners=*/runners,
1665  /*worker_task_runner=*/nil,
1666  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
1667 
1670  flutterPlatformViewsController->RegisterViewFactory(
1671  factory, @"MockFlutterPlatformView",
1673  FlutterResult result = ^(id result) {
1674  };
1675  flutterPlatformViewsController->OnMethodCall(
1677  methodCallWithMethodName:@"create"
1678  arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}],
1679  result);
1680 
1681  XCTAssertNotNil(gMockPlatformView);
1682 
1683  UIView* mockFlutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 30, 30)];
1684  flutterPlatformViewsController->SetFlutterView(mockFlutterView);
1685  // Create embedded view params
1686  flutter::MutatorsStack stack;
1687  // Layer tree always pushes a screen scale factor to the stack.
1688  SkMatrix screenScaleMatrix =
1689  SkMatrix::Scale([UIScreen mainScreen].scale, [UIScreen mainScreen].scale);
1690  stack.PushTransform(screenScaleMatrix);
1691  SkMatrix translateMatrix = SkMatrix::Translate(5, 5);
1692  // The platform view's rect for this test will be (5, 5, 10, 10).
1693  stack.PushTransform(translateMatrix);
1694 
1695  // Push a clip rrect, the rect of the rrect is the same as the PlatformView of the corner should.
1696  // clip the PlatformView.
1697  SkRect rect_for_rrect = SkRect::MakeXYWH(0, 0, 10, 10);
1698  SkRRect rrect = SkRRect::MakeRectXY(rect_for_rrect, 1, 1);
1699  stack.PushClipRRect(rrect);
1700 
1701  auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
1702  SkMatrix::Concat(screenScaleMatrix, translateMatrix), SkSize::Make(5, 5), stack);
1703 
1704  flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams));
1705  flutterPlatformViewsController->CompositeEmbeddedView(2);
1706  gMockPlatformView.backgroundColor = UIColor.redColor;
1707  XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]);
1708  ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
1709  [mockFlutterView addSubview:childClippingView];
1710 
1711  [mockFlutterView setNeedsLayout];
1712  [mockFlutterView layoutIfNeeded];
1713 
1714  XCTAssertNotNil(childClippingView.maskView);
1715 }
1716 
1717 - (void)testClipRect {
1718  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
1719  auto thread_task_runner = CreateNewThread("FlutterPlatformViewsTest");
1720  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
1721  /*platform=*/thread_task_runner,
1722  /*raster=*/thread_task_runner,
1723  /*ui=*/thread_task_runner,
1724  /*io=*/thread_task_runner);
1725  auto flutterPlatformViewsController = std::make_shared<flutter::FlutterPlatformViewsController>();
1726  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1727  /*delegate=*/mock_delegate,
1728  /*rendering_api=*/mock_delegate.settings_.enable_impeller
1731  /*platform_views_controller=*/flutterPlatformViewsController,
1732  /*task_runners=*/runners,
1733  /*worker_task_runner=*/nil,
1734  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
1735 
1738  flutterPlatformViewsController->RegisterViewFactory(
1739  factory, @"MockFlutterPlatformView",
1741  FlutterResult result = ^(id result) {
1742  };
1743  flutterPlatformViewsController->OnMethodCall(
1745  methodCallWithMethodName:@"create"
1746  arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}],
1747  result);
1748 
1749  XCTAssertNotNil(gMockPlatformView);
1750 
1751  UIView* mockFlutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
1752  flutterPlatformViewsController->SetFlutterView(mockFlutterView);
1753  // Create embedded view params
1754  flutter::MutatorsStack stack;
1755  // Layer tree always pushes a screen scale factor to the stack
1756  SkMatrix screenScaleMatrix =
1757  SkMatrix::Scale([UIScreen mainScreen].scale, [UIScreen mainScreen].scale);
1758  stack.PushTransform(screenScaleMatrix);
1759  // Push a clip rect
1760  SkRect rect = SkRect::MakeXYWH(2, 2, 3, 3);
1761  stack.PushClipRect(rect);
1762 
1763  auto embeddedViewParams =
1764  std::make_unique<flutter::EmbeddedViewParams>(screenScaleMatrix, SkSize::Make(10, 10), stack);
1765 
1766  flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams));
1767  flutterPlatformViewsController->CompositeEmbeddedView(2);
1768  gMockPlatformView.backgroundColor = UIColor.redColor;
1769  XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]);
1770  ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
1771  [mockFlutterView addSubview:childClippingView];
1772 
1773  [mockFlutterView setNeedsLayout];
1774  [mockFlutterView layoutIfNeeded];
1775 
1776  CGRect insideClipping = CGRectMake(2, 2, 3, 3);
1777  for (int i = 0; i < 10; i++) {
1778  for (int j = 0; j < 10; j++) {
1779  CGPoint point = CGPointMake(i, j);
1780  int alpha = [self alphaOfPoint:CGPointMake(i, j) onView:mockFlutterView];
1781  if (CGRectContainsPoint(insideClipping, point)) {
1782  XCTAssertEqual(alpha, 255);
1783  } else {
1784  XCTAssertEqual(alpha, 0);
1785  }
1786  }
1787  }
1788 }
1789 
1790 - (void)testClipRect_multipleClips {
1791  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
1792  auto thread_task_runner = CreateNewThread("FlutterPlatformViewsTest");
1793  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
1794  /*platform=*/thread_task_runner,
1795  /*raster=*/thread_task_runner,
1796  /*ui=*/thread_task_runner,
1797  /*io=*/thread_task_runner);
1798  auto flutterPlatformViewsController = std::make_shared<flutter::FlutterPlatformViewsController>();
1799  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1800  /*delegate=*/mock_delegate,
1801  /*rendering_api=*/mock_delegate.settings_.enable_impeller
1804  /*platform_views_controller=*/flutterPlatformViewsController,
1805  /*task_runners=*/runners,
1806  /*worker_task_runner=*/nil,
1807  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
1808 
1811  flutterPlatformViewsController->RegisterViewFactory(
1812  factory, @"MockFlutterPlatformView",
1814  FlutterResult result = ^(id result) {
1815  };
1816  flutterPlatformViewsController->OnMethodCall(
1818  methodCallWithMethodName:@"create"
1819  arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}],
1820  result);
1821 
1822  XCTAssertNotNil(gMockPlatformView);
1823 
1824  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
1825  flutterPlatformViewsController->SetFlutterView(flutterView);
1826  // Create embedded view params
1827  flutter::MutatorsStack stack;
1828  // Layer tree always pushes a screen scale factor to the stack
1829  SkMatrix screenScaleMatrix =
1830  SkMatrix::Scale([UIScreen mainScreen].scale, [UIScreen mainScreen].scale);
1831  stack.PushTransform(screenScaleMatrix);
1832  // Push a clip rect
1833  SkRect rect1 = SkRect::MakeXYWH(2, 2, 3, 3);
1834  stack.PushClipRect(rect1);
1835  // Push another clip rect
1836  SkRect rect2 = SkRect::MakeXYWH(3, 3, 3, 3);
1837  stack.PushClipRect(rect2);
1838 
1839  auto embeddedViewParams =
1840  std::make_unique<flutter::EmbeddedViewParams>(screenScaleMatrix, SkSize::Make(10, 10), stack);
1841 
1842  flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams));
1843  flutterPlatformViewsController->CompositeEmbeddedView(2);
1844 
1845  gMockPlatformView.backgroundColor = UIColor.redColor;
1846  XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]);
1847  ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
1848  [flutterView addSubview:childClippingView];
1849 
1850  [flutterView setNeedsLayout];
1851  [flutterView layoutIfNeeded];
1852 
1853  /*
1854  clip 1 clip 2
1855  2 3 4 5 6 2 3 4 5 6
1856  2 + - - + 2
1857  3 | | 3 + - - +
1858  4 | | 4 | |
1859  5 + - - + 5 | |
1860  6 6 + - - +
1861 
1862  Result should be the intersection of 2 clips
1863  2 3 4 5 6
1864  2
1865  3 + - +
1866  4 | |
1867  5 + - +
1868  6
1869  */
1870  CGRect insideClipping = CGRectMake(3, 3, 2, 2);
1871  for (int i = 0; i < 10; i++) {
1872  for (int j = 0; j < 10; j++) {
1873  CGPoint point = CGPointMake(i, j);
1874  int alpha = [self alphaOfPoint:CGPointMake(i, j) onView:flutterView];
1875  if (CGRectContainsPoint(insideClipping, point)) {
1876  XCTAssertEqual(alpha, 255);
1877  } else {
1878  XCTAssertEqual(alpha, 0);
1879  }
1880  }
1881  }
1882 }
1883 
1884 - (void)testClipRRect {
1885  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
1886  auto thread_task_runner = CreateNewThread("FlutterPlatformViewsTest");
1887  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
1888  /*platform=*/thread_task_runner,
1889  /*raster=*/thread_task_runner,
1890  /*ui=*/thread_task_runner,
1891  /*io=*/thread_task_runner);
1892  auto flutterPlatformViewsController = std::make_shared<flutter::FlutterPlatformViewsController>();
1893  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1894  /*delegate=*/mock_delegate,
1895  /*rendering_api=*/mock_delegate.settings_.enable_impeller
1898  /*platform_views_controller=*/flutterPlatformViewsController,
1899  /*task_runners=*/runners,
1900  /*worker_task_runner=*/nil,
1901  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
1902 
1905  flutterPlatformViewsController->RegisterViewFactory(
1906  factory, @"MockFlutterPlatformView",
1908  FlutterResult result = ^(id result) {
1909  };
1910  flutterPlatformViewsController->OnMethodCall(
1912  methodCallWithMethodName:@"create"
1913  arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}],
1914  result);
1915 
1916  XCTAssertNotNil(gMockPlatformView);
1917 
1918  UIView* mockFlutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
1919  flutterPlatformViewsController->SetFlutterView(mockFlutterView);
1920  // Create embedded view params
1921  flutter::MutatorsStack stack;
1922  // Layer tree always pushes a screen scale factor to the stack
1923  SkMatrix screenScaleMatrix =
1924  SkMatrix::Scale([UIScreen mainScreen].scale, [UIScreen mainScreen].scale);
1925  stack.PushTransform(screenScaleMatrix);
1926  // Push a clip rrect
1927  SkRRect rrect = SkRRect::MakeRectXY(SkRect::MakeXYWH(2, 2, 6, 6), 1, 1);
1928  stack.PushClipRRect(rrect);
1929 
1930  auto embeddedViewParams =
1931  std::make_unique<flutter::EmbeddedViewParams>(screenScaleMatrix, SkSize::Make(10, 10), stack);
1932 
1933  flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams));
1934  flutterPlatformViewsController->CompositeEmbeddedView(2);
1935  gMockPlatformView.backgroundColor = UIColor.redColor;
1936  XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]);
1937  ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
1938  [mockFlutterView addSubview:childClippingView];
1939 
1940  [mockFlutterView setNeedsLayout];
1941  [mockFlutterView layoutIfNeeded];
1942 
1943  /*
1944  ClippingMask outterClipping
1945  2 3 4 5 6 7 2 3 4 5 6 7
1946  2 / - - - - \ 2 + - - - - +
1947  3 | | 3 | |
1948  4 | | 4 | |
1949  5 | | 5 | |
1950  6 | | 6 | |
1951  7 \ - - - - / 7 + - - - - +
1952 
1953  innerClipping1 innerClipping2
1954  2 3 4 5 6 7 2 3 4 5 6 7
1955  2 + - - + 2
1956  3 | | 3 + - - - - +
1957  4 | | 4 | |
1958  5 | | 5 | |
1959  6 | | 6 + - - - - +
1960  7 + - - + 7
1961  */
1962  CGRect innerClipping1 = CGRectMake(3, 2, 4, 6);
1963  CGRect innerClipping2 = CGRectMake(2, 3, 6, 4);
1964  CGRect outterClipping = CGRectMake(2, 2, 6, 6);
1965  for (int i = 0; i < 10; i++) {
1966  for (int j = 0; j < 10; j++) {
1967  CGPoint point = CGPointMake(i, j);
1968  int alpha = [self alphaOfPoint:CGPointMake(i, j) onView:mockFlutterView];
1969  if (CGRectContainsPoint(innerClipping1, point) ||
1970  CGRectContainsPoint(innerClipping2, point)) {
1971  // Pixels inside either of the 2 inner clippings should be fully opaque.
1972  XCTAssertEqual(alpha, 255);
1973  } else if (CGRectContainsPoint(outterClipping, point)) {
1974  // Corner pixels (i.e. (2, 2), (2, 7), (7, 2) and (7, 7)) should be partially transparent.
1975  XCTAssert(0 < alpha && alpha < 255);
1976  } else {
1977  // Pixels outside outterClipping should be fully transparent.
1978  XCTAssertEqual(alpha, 0);
1979  }
1980  }
1981  }
1982 }
1983 
1984 - (void)testClipRRect_multipleClips {
1985  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
1986  auto thread_task_runner = CreateNewThread("FlutterPlatformViewsTest");
1987  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
1988  /*platform=*/thread_task_runner,
1989  /*raster=*/thread_task_runner,
1990  /*ui=*/thread_task_runner,
1991  /*io=*/thread_task_runner);
1992  auto flutterPlatformViewsController = std::make_shared<flutter::FlutterPlatformViewsController>();
1993  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1994  /*delegate=*/mock_delegate,
1995  /*rendering_api=*/mock_delegate.settings_.enable_impeller
1998  /*platform_views_controller=*/flutterPlatformViewsController,
1999  /*task_runners=*/runners,
2000  /*worker_task_runner=*/nil,
2001  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
2002 
2005  flutterPlatformViewsController->RegisterViewFactory(
2006  factory, @"MockFlutterPlatformView",
2008  FlutterResult result = ^(id result) {
2009  };
2010  flutterPlatformViewsController->OnMethodCall(
2012  methodCallWithMethodName:@"create"
2013  arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}],
2014  result);
2015 
2016  XCTAssertNotNil(gMockPlatformView);
2017 
2018  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
2019  flutterPlatformViewsController->SetFlutterView(flutterView);
2020  // Create embedded view params
2021  flutter::MutatorsStack stack;
2022  // Layer tree always pushes a screen scale factor to the stack
2023  SkMatrix screenScaleMatrix =
2024  SkMatrix::Scale([UIScreen mainScreen].scale, [UIScreen mainScreen].scale);
2025  stack.PushTransform(screenScaleMatrix);
2026  // Push a clip rrect
2027  SkRRect rrect = SkRRect::MakeRectXY(SkRect::MakeXYWH(2, 2, 6, 6), 1, 1);
2028  stack.PushClipRRect(rrect);
2029  // Push a clip rect
2030  SkRect rect = SkRect::MakeXYWH(4, 2, 6, 6);
2031  stack.PushClipRect(rect);
2032 
2033  auto embeddedViewParams =
2034  std::make_unique<flutter::EmbeddedViewParams>(screenScaleMatrix, SkSize::Make(10, 10), stack);
2035 
2036  flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams));
2037  flutterPlatformViewsController->CompositeEmbeddedView(2);
2038 
2039  gMockPlatformView.backgroundColor = UIColor.redColor;
2040  XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]);
2041  ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
2042  [flutterView addSubview:childClippingView];
2043 
2044  [flutterView setNeedsLayout];
2045  [flutterView layoutIfNeeded];
2046 
2047  /*
2048  clip 1 clip 2
2049  2 3 4 5 6 7 8 9 2 3 4 5 6 7 8 9
2050  2 / - - - - \ 2 + - - - - +
2051  3 | | 3 | |
2052  4 | | 4 | |
2053  5 | | 5 | |
2054  6 | | 6 | |
2055  7 \ - - - - / 7 + - - - - +
2056 
2057  Result should be the intersection of 2 clips
2058  2 3 4 5 6 7 8 9
2059  2 + - - \
2060  3 | |
2061  4 | |
2062  5 | |
2063  6 | |
2064  7 + - - /
2065  */
2066  CGRect clipping = CGRectMake(4, 2, 4, 6);
2067  for (int i = 0; i < 10; i++) {
2068  for (int j = 0; j < 10; j++) {
2069  CGPoint point = CGPointMake(i, j);
2070  int alpha = [self alphaOfPoint:CGPointMake(i, j) onView:flutterView];
2071  if (i == 7 && (j == 2 || j == 7)) {
2072  // Upper and lower right corners should be partially transparent.
2073  XCTAssert(0 < alpha && alpha < 255);
2074  } else if (
2075  // left
2076  (i == 4 && j >= 2 && j <= 7) ||
2077  // right
2078  (i == 7 && j >= 2 && j <= 7) ||
2079  // top
2080  (j == 2 && i >= 4 && i <= 7) ||
2081  // bottom
2082  (j == 7 && i >= 4 && i <= 7)) {
2083  // Since we are falling back to software rendering for this case
2084  // The edge pixels can be anti-aliased, so it may not be fully opaque.
2085  XCTAssert(alpha > 127);
2086  } else if ((i == 3 && j >= 1 && j <= 8) || (i == 8 && j >= 1 && j <= 8) ||
2087  (j == 1 && i >= 3 && i <= 8) || (j == 8 && i >= 3 && i <= 8)) {
2088  // Since we are falling back to software rendering for this case
2089  // The edge pixels can be anti-aliased, so it may not be fully transparent.
2090  XCTAssert(alpha < 127);
2091  } else if (CGRectContainsPoint(clipping, point)) {
2092  // Other pixels inside clipping should be fully opaque.
2093  XCTAssertEqual(alpha, 255);
2094  } else {
2095  // Pixels outside clipping should be fully transparent.
2096  XCTAssertEqual(alpha, 0);
2097  }
2098  }
2099  }
2100 }
2101 
2102 - (void)testClipPath {
2103  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
2104  auto thread_task_runner = CreateNewThread("FlutterPlatformViewsTest");
2105  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
2106  /*platform=*/thread_task_runner,
2107  /*raster=*/thread_task_runner,
2108  /*ui=*/thread_task_runner,
2109  /*io=*/thread_task_runner);
2110  auto flutterPlatformViewsController = std::make_shared<flutter::FlutterPlatformViewsController>();
2111  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2112  /*delegate=*/mock_delegate,
2113  /*rendering_api=*/mock_delegate.settings_.enable_impeller
2116  /*platform_views_controller=*/flutterPlatformViewsController,
2117  /*task_runners=*/runners,
2118  /*worker_task_runner=*/nil,
2119  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
2120 
2123  flutterPlatformViewsController->RegisterViewFactory(
2124  factory, @"MockFlutterPlatformView",
2126  FlutterResult result = ^(id result) {
2127  };
2128  flutterPlatformViewsController->OnMethodCall(
2130  methodCallWithMethodName:@"create"
2131  arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}],
2132  result);
2133 
2134  XCTAssertNotNil(gMockPlatformView);
2135 
2136  UIView* mockFlutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
2137  flutterPlatformViewsController->SetFlutterView(mockFlutterView);
2138  // Create embedded view params
2139  flutter::MutatorsStack stack;
2140  // Layer tree always pushes a screen scale factor to the stack
2141  SkMatrix screenScaleMatrix =
2142  SkMatrix::Scale([UIScreen mainScreen].scale, [UIScreen mainScreen].scale);
2143  stack.PushTransform(screenScaleMatrix);
2144  // Push a clip path
2145  SkPath path;
2146  path.addRoundRect(SkRect::MakeXYWH(2, 2, 6, 6), 1, 1);
2147  stack.PushClipPath(path);
2148 
2149  auto embeddedViewParams =
2150  std::make_unique<flutter::EmbeddedViewParams>(screenScaleMatrix, SkSize::Make(10, 10), stack);
2151 
2152  flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams));
2153  flutterPlatformViewsController->CompositeEmbeddedView(2);
2154  gMockPlatformView.backgroundColor = UIColor.redColor;
2155  XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]);
2156  ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
2157  [mockFlutterView addSubview:childClippingView];
2158 
2159  [mockFlutterView setNeedsLayout];
2160  [mockFlutterView layoutIfNeeded];
2161 
2162  /*
2163  ClippingMask outterClipping
2164  2 3 4 5 6 7 2 3 4 5 6 7
2165  2 / - - - - \ 2 + - - - - +
2166  3 | | 3 | |
2167  4 | | 4 | |
2168  5 | | 5 | |
2169  6 | | 6 | |
2170  7 \ - - - - / 7 + - - - - +
2171 
2172  innerClipping1 innerClipping2
2173  2 3 4 5 6 7 2 3 4 5 6 7
2174  2 + - - + 2
2175  3 | | 3 + - - - - +
2176  4 | | 4 | |
2177  5 | | 5 | |
2178  6 | | 6 + - - - - +
2179  7 + - - + 7
2180  */
2181  CGRect innerClipping1 = CGRectMake(3, 2, 4, 6);
2182  CGRect innerClipping2 = CGRectMake(2, 3, 6, 4);
2183  CGRect outterClipping = CGRectMake(2, 2, 6, 6);
2184  for (int i = 0; i < 10; i++) {
2185  for (int j = 0; j < 10; j++) {
2186  CGPoint point = CGPointMake(i, j);
2187  int alpha = [self alphaOfPoint:CGPointMake(i, j) onView:mockFlutterView];
2188  if (CGRectContainsPoint(innerClipping1, point) ||
2189  CGRectContainsPoint(innerClipping2, point)) {
2190  // Pixels inside either of the 2 inner clippings should be fully opaque.
2191  XCTAssertEqual(alpha, 255);
2192  } else if (CGRectContainsPoint(outterClipping, point)) {
2193  // Corner pixels (i.e. (2, 2), (2, 7), (7, 2) and (7, 7)) should be partially transparent.
2194  XCTAssert(0 < alpha && alpha < 255);
2195  } else {
2196  // Pixels outside outterClipping should be fully transparent.
2197  XCTAssertEqual(alpha, 0);
2198  }
2199  }
2200  }
2201 }
2202 
2203 - (void)testClipPath_multipleClips {
2204  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
2205  auto thread_task_runner = CreateNewThread("FlutterPlatformViewsTest");
2206  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
2207  /*platform=*/thread_task_runner,
2208  /*raster=*/thread_task_runner,
2209  /*ui=*/thread_task_runner,
2210  /*io=*/thread_task_runner);
2211  auto flutterPlatformViewsController = std::make_shared<flutter::FlutterPlatformViewsController>();
2212  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2213  /*delegate=*/mock_delegate,
2214  /*rendering_api=*/mock_delegate.settings_.enable_impeller
2217  /*platform_views_controller=*/flutterPlatformViewsController,
2218  /*task_runners=*/runners,
2219  /*worker_task_runner=*/nil,
2220  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
2221 
2224  flutterPlatformViewsController->RegisterViewFactory(
2225  factory, @"MockFlutterPlatformView",
2227  FlutterResult result = ^(id result) {
2228  };
2229  flutterPlatformViewsController->OnMethodCall(
2231  methodCallWithMethodName:@"create"
2232  arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}],
2233  result);
2234 
2235  XCTAssertNotNil(gMockPlatformView);
2236 
2237  UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
2238  flutterPlatformViewsController->SetFlutterView(flutterView);
2239  // Create embedded view params
2240  flutter::MutatorsStack stack;
2241  // Layer tree always pushes a screen scale factor to the stack
2242  SkMatrix screenScaleMatrix =
2243  SkMatrix::Scale([UIScreen mainScreen].scale, [UIScreen mainScreen].scale);
2244  stack.PushTransform(screenScaleMatrix);
2245  // Push a clip path
2246  SkPath path;
2247  path.addRoundRect(SkRect::MakeXYWH(2, 2, 6, 6), 1, 1);
2248  stack.PushClipPath(path);
2249  // Push a clip rect
2250  SkRect rect = SkRect::MakeXYWH(4, 2, 6, 6);
2251  stack.PushClipRect(rect);
2252 
2253  auto embeddedViewParams =
2254  std::make_unique<flutter::EmbeddedViewParams>(screenScaleMatrix, SkSize::Make(10, 10), stack);
2255 
2256  flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams));
2257  flutterPlatformViewsController->CompositeEmbeddedView(2);
2258 
2259  gMockPlatformView.backgroundColor = UIColor.redColor;
2260  XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]);
2261  ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
2262  [flutterView addSubview:childClippingView];
2263 
2264  [flutterView setNeedsLayout];
2265  [flutterView layoutIfNeeded];
2266 
2267  /*
2268  clip 1 clip 2
2269  2 3 4 5 6 7 8 9 2 3 4 5 6 7 8 9
2270  2 / - - - - \ 2 + - - - - +
2271  3 | | 3 | |
2272  4 | | 4 | |
2273  5 | | 5 | |
2274  6 | | 6 | |
2275  7 \ - - - - / 7 + - - - - +
2276 
2277  Result should be the intersection of 2 clips
2278  2 3 4 5 6 7 8 9
2279  2 + - - \
2280  3 | |
2281  4 | |
2282  5 | |
2283  6 | |
2284  7 + - - /
2285  */
2286  CGRect clipping = CGRectMake(4, 2, 4, 6);
2287  for (int i = 0; i < 10; i++) {
2288  for (int j = 0; j < 10; j++) {
2289  CGPoint point = CGPointMake(i, j);
2290  int alpha = [self alphaOfPoint:CGPointMake(i, j) onView:flutterView];
2291  if (i == 7 && (j == 2 || j == 7)) {
2292  // Upper and lower right corners should be partially transparent.
2293  XCTAssert(0 < alpha && alpha < 255);
2294  } else if (
2295  // left
2296  (i == 4 && j >= 2 && j <= 7) ||
2297  // right
2298  (i == 7 && j >= 2 && j <= 7) ||
2299  // top
2300  (j == 2 && i >= 4 && i <= 7) ||
2301  // bottom
2302  (j == 7 && i >= 4 && i <= 7)) {
2303  // Since we are falling back to software rendering for this case
2304  // The edge pixels can be anti-aliased, so it may not be fully opaque.
2305  XCTAssert(alpha > 127);
2306  } else if ((i == 3 && j >= 1 && j <= 8) || (i == 8 && j >= 1 && j <= 8) ||
2307  (j == 1 && i >= 3 && i <= 8) || (j == 8 && i >= 3 && i <= 8)) {
2308  // Since we are falling back to software rendering for this case
2309  // The edge pixels can be anti-aliased, so it may not be fully transparent.
2310  XCTAssert(alpha < 127);
2311  } else if (CGRectContainsPoint(clipping, point)) {
2312  // Other pixels inside clipping should be fully opaque.
2313  XCTAssertEqual(alpha, 255);
2314  } else {
2315  // Pixels outside clipping should be fully transparent.
2316  XCTAssertEqual(alpha, 0);
2317  }
2318  }
2319  }
2320 }
2321 
2322 - (void)testSetFlutterViewControllerAfterCreateCanStillDispatchTouchEvents {
2323  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
2324  auto thread_task_runner = CreateNewThread("FlutterPlatformViewsTest");
2325  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
2326  /*platform=*/thread_task_runner,
2327  /*raster=*/thread_task_runner,
2328  /*ui=*/thread_task_runner,
2329  /*io=*/thread_task_runner);
2330  auto flutterPlatformViewsController = std::make_shared<flutter::FlutterPlatformViewsController>();
2331  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2332  /*delegate=*/mock_delegate,
2333  /*rendering_api=*/mock_delegate.settings_.enable_impeller
2336  /*platform_views_controller=*/flutterPlatformViewsController,
2337  /*task_runners=*/runners,
2338  /*worker_task_runner=*/nil,
2339  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
2340 
2343  flutterPlatformViewsController->RegisterViewFactory(
2344  factory, @"MockFlutterPlatformView",
2346  FlutterResult result = ^(id result) {
2347  };
2348  flutterPlatformViewsController->OnMethodCall(
2350  methodCallWithMethodName:@"create"
2351  arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}],
2352  result);
2353 
2354  XCTAssertNotNil(gMockPlatformView);
2355 
2356  // Find touch inteceptor view
2357  UIView* touchInteceptorView = gMockPlatformView;
2358  while (touchInteceptorView != nil &&
2359  ![touchInteceptorView isKindOfClass:[FlutterTouchInterceptingView class]]) {
2360  touchInteceptorView = touchInteceptorView.superview;
2361  }
2362  XCTAssertNotNil(touchInteceptorView);
2363 
2364  // Find ForwardGestureRecognizer
2365  UIGestureRecognizer* forwardGectureRecognizer = nil;
2366  for (UIGestureRecognizer* gestureRecognizer in touchInteceptorView.gestureRecognizers) {
2367  if ([gestureRecognizer isKindOfClass:NSClassFromString(@"ForwardingGestureRecognizer")]) {
2368  forwardGectureRecognizer = gestureRecognizer;
2369  break;
2370  }
2371  }
2372 
2373  // Before setting flutter view controller, events are not dispatched.
2374  NSSet* touches1 = [[NSSet alloc] init];
2375  id event1 = OCMClassMock([UIEvent class]);
2376  id mockFlutterViewContoller = OCMClassMock([FlutterViewController class]);
2377  [forwardGectureRecognizer touchesBegan:touches1 withEvent:event1];
2378  OCMReject([mockFlutterViewContoller touchesBegan:touches1 withEvent:event1]);
2379 
2380  // Set flutter view controller allows events to be dispatched.
2381  NSSet* touches2 = [[NSSet alloc] init];
2382  id event2 = OCMClassMock([UIEvent class]);
2383  flutterPlatformViewsController->SetFlutterViewController(mockFlutterViewContoller);
2384  [forwardGectureRecognizer touchesBegan:touches2 withEvent:event2];
2385  OCMVerify([mockFlutterViewContoller touchesBegan:touches2 withEvent:event2]);
2386 }
2387 
2388 - (void)testSetFlutterViewControllerInTheMiddleOfTouchEventShouldStillAllowGesturesToBeHandled {
2389  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
2390  auto thread_task_runner = CreateNewThread("FlutterPlatformViewsTest");
2391  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
2392  /*platform=*/thread_task_runner,
2393  /*raster=*/thread_task_runner,
2394  /*ui=*/thread_task_runner,
2395  /*io=*/thread_task_runner);
2396  auto flutterPlatformViewsController = std::make_shared<flutter::FlutterPlatformViewsController>();
2397  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2398  /*delegate=*/mock_delegate,
2399  /*rendering_api=*/mock_delegate.settings_.enable_impeller
2402  /*platform_views_controller=*/flutterPlatformViewsController,
2403  /*task_runners=*/runners,
2404  /*worker_task_runner=*/nil,
2405  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
2406 
2409  flutterPlatformViewsController->RegisterViewFactory(
2410  factory, @"MockFlutterPlatformView",
2412  FlutterResult result = ^(id result) {
2413  };
2414  flutterPlatformViewsController->OnMethodCall(
2416  methodCallWithMethodName:@"create"
2417  arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}],
2418  result);
2419 
2420  XCTAssertNotNil(gMockPlatformView);
2421 
2422  // Find touch inteceptor view
2423  UIView* touchInteceptorView = gMockPlatformView;
2424  while (touchInteceptorView != nil &&
2425  ![touchInteceptorView isKindOfClass:[FlutterTouchInterceptingView class]]) {
2426  touchInteceptorView = touchInteceptorView.superview;
2427  }
2428  XCTAssertNotNil(touchInteceptorView);
2429 
2430  // Find ForwardGestureRecognizer
2431  UIGestureRecognizer* forwardGectureRecognizer = nil;
2432  for (UIGestureRecognizer* gestureRecognizer in touchInteceptorView.gestureRecognizers) {
2433  if ([gestureRecognizer isKindOfClass:NSClassFromString(@"ForwardingGestureRecognizer")]) {
2434  forwardGectureRecognizer = gestureRecognizer;
2435  break;
2436  }
2437  }
2438  id mockFlutterViewContoller = OCMClassMock([FlutterViewController class]);
2439  {
2440  // ***** Sequence 1, finishing touch event with touchEnded ***** //
2441  flutterPlatformViewsController->SetFlutterViewController(mockFlutterViewContoller);
2442 
2443  NSSet* touches1 = [[NSSet alloc] init];
2444  id event1 = OCMClassMock([UIEvent class]);
2445  [forwardGectureRecognizer touchesBegan:touches1 withEvent:event1];
2446  OCMVerify([mockFlutterViewContoller touchesBegan:touches1 withEvent:event1]);
2447 
2448  flutterPlatformViewsController->SetFlutterViewController(nil);
2449 
2450  // Allow the touch events to finish
2451  NSSet* touches2 = [[NSSet alloc] init];
2452  id event2 = OCMClassMock([UIEvent class]);
2453  [forwardGectureRecognizer touchesMoved:touches2 withEvent:event2];
2454  OCMVerify([mockFlutterViewContoller touchesMoved:touches2 withEvent:event2]);
2455 
2456  NSSet* touches3 = [[NSSet alloc] init];
2457  id event3 = OCMClassMock([UIEvent class]);
2458  [forwardGectureRecognizer touchesEnded:touches3 withEvent:event3];
2459  OCMVerify([mockFlutterViewContoller touchesEnded:touches3 withEvent:event3]);
2460 
2461  // Now the 2nd touch sequence should not be allowed.
2462  NSSet* touches4 = [[NSSet alloc] init];
2463  id event4 = OCMClassMock([UIEvent class]);
2464  [forwardGectureRecognizer touchesBegan:touches4 withEvent:event4];
2465  OCMReject([mockFlutterViewContoller touchesBegan:touches4 withEvent:event4]);
2466 
2467  NSSet* touches5 = [[NSSet alloc] init];
2468  id event5 = OCMClassMock([UIEvent class]);
2469  [forwardGectureRecognizer touchesEnded:touches5 withEvent:event5];
2470  OCMReject([mockFlutterViewContoller touchesEnded:touches5 withEvent:event5]);
2471  }
2472 
2473  {
2474  // ***** Sequence 2, finishing touch event with touchCancelled ***** //
2475  flutterPlatformViewsController->SetFlutterViewController(mockFlutterViewContoller);
2476 
2477  NSSet* touches1 = [[NSSet alloc] init];
2478  id event1 = OCMClassMock([UIEvent class]);
2479  [forwardGectureRecognizer touchesBegan:touches1 withEvent:event1];
2480  OCMVerify([mockFlutterViewContoller touchesBegan:touches1 withEvent:event1]);
2481 
2482  flutterPlatformViewsController->SetFlutterViewController(nil);
2483 
2484  // Allow the touch events to finish
2485  NSSet* touches2 = [[NSSet alloc] init];
2486  id event2 = OCMClassMock([UIEvent class]);
2487  [forwardGectureRecognizer touchesMoved:touches2 withEvent:event2];
2488  OCMVerify([mockFlutterViewContoller touchesMoved:touches2 withEvent:event2]);
2489 
2490  NSSet* touches3 = [[NSSet alloc] init];
2491  id event3 = OCMClassMock([UIEvent class]);
2492  [forwardGectureRecognizer touchesCancelled:touches3 withEvent:event3];
2493  OCMVerify([mockFlutterViewContoller forceTouchesCancelled:touches3]);
2494 
2495  // Now the 2nd touch sequence should not be allowed.
2496  NSSet* touches4 = [[NSSet alloc] init];
2497  id event4 = OCMClassMock([UIEvent class]);
2498  [forwardGectureRecognizer touchesBegan:touches4 withEvent:event4];
2499  OCMReject([mockFlutterViewContoller touchesBegan:touches4 withEvent:event4]);
2500 
2501  NSSet* touches5 = [[NSSet alloc] init];
2502  id event5 = OCMClassMock([UIEvent class]);
2503  [forwardGectureRecognizer touchesEnded:touches5 withEvent:event5];
2504  OCMReject([mockFlutterViewContoller touchesEnded:touches5 withEvent:event5]);
2505  }
2506 
2507  flutterPlatformViewsController->Reset();
2508 }
2509 
2510 - (void)
2511  testSetFlutterViewControllerInTheMiddleOfTouchEventAllowsTheNewControllerToHandleSecondTouchSequence {
2512  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
2513  auto thread_task_runner = CreateNewThread("FlutterPlatformViewsTest");
2514  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
2515  /*platform=*/thread_task_runner,
2516  /*raster=*/thread_task_runner,
2517  /*ui=*/thread_task_runner,
2518  /*io=*/thread_task_runner);
2519  auto flutterPlatformViewsController = std::make_shared<flutter::FlutterPlatformViewsController>();
2520  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2521  /*delegate=*/mock_delegate,
2522  /*rendering_api=*/mock_delegate.settings_.enable_impeller
2525  /*platform_views_controller=*/flutterPlatformViewsController,
2526  /*task_runners=*/runners,
2527  /*worker_task_runner=*/nil,
2528  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
2529 
2532  flutterPlatformViewsController->RegisterViewFactory(
2533  factory, @"MockFlutterPlatformView",
2535  FlutterResult result = ^(id result) {
2536  };
2537  flutterPlatformViewsController->OnMethodCall(
2539  methodCallWithMethodName:@"create"
2540  arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}],
2541  result);
2542 
2543  XCTAssertNotNil(gMockPlatformView);
2544 
2545  // Find touch inteceptor view
2546  UIView* touchInteceptorView = gMockPlatformView;
2547  while (touchInteceptorView != nil &&
2548  ![touchInteceptorView isKindOfClass:[FlutterTouchInterceptingView class]]) {
2549  touchInteceptorView = touchInteceptorView.superview;
2550  }
2551  XCTAssertNotNil(touchInteceptorView);
2552 
2553  // Find ForwardGestureRecognizer
2554  UIGestureRecognizer* forwardGectureRecognizer = nil;
2555  for (UIGestureRecognizer* gestureRecognizer in touchInteceptorView.gestureRecognizers) {
2556  if ([gestureRecognizer isKindOfClass:NSClassFromString(@"ForwardingGestureRecognizer")]) {
2557  forwardGectureRecognizer = gestureRecognizer;
2558  break;
2559  }
2560  }
2561  id mockFlutterViewContoller = OCMClassMock([FlutterViewController class]);
2562 
2563  flutterPlatformViewsController->SetFlutterViewController(mockFlutterViewContoller);
2564 
2565  // The touches in this sequence requires 1 touch object, we always create the NSSet with one item.
2566  NSSet* touches1 = [NSSet setWithObject:@1];
2567  id event1 = OCMClassMock([UIEvent class]);
2568  [forwardGectureRecognizer touchesBegan:touches1 withEvent:event1];
2569  OCMVerify([mockFlutterViewContoller touchesBegan:touches1 withEvent:event1]);
2570 
2571  FlutterViewController* mockFlutterViewContoller2 = OCMClassMock([FlutterViewController class]);
2572  flutterPlatformViewsController->SetFlutterViewController(mockFlutterViewContoller2);
2573 
2574  // Touch events should still send to the old FlutterViewController if FlutterViewController
2575  // is updated in between.
2576  NSSet* touches2 = [NSSet setWithObject:@1];
2577  id event2 = OCMClassMock([UIEvent class]);
2578  [forwardGectureRecognizer touchesBegan:touches2 withEvent:event2];
2579  OCMVerify([mockFlutterViewContoller touchesBegan:touches2 withEvent:event2]);
2580  OCMReject([mockFlutterViewContoller2 touchesBegan:touches2 withEvent:event2]);
2581 
2582  NSSet* touches3 = [NSSet setWithObject:@1];
2583  id event3 = OCMClassMock([UIEvent class]);
2584  [forwardGectureRecognizer touchesMoved:touches3 withEvent:event3];
2585  OCMVerify([mockFlutterViewContoller touchesMoved:touches3 withEvent:event3]);
2586  OCMReject([mockFlutterViewContoller2 touchesMoved:touches3 withEvent:event3]);
2587 
2588  NSSet* touches4 = [NSSet setWithObject:@1];
2589  id event4 = OCMClassMock([UIEvent class]);
2590  [forwardGectureRecognizer touchesEnded:touches4 withEvent:event4];
2591  OCMVerify([mockFlutterViewContoller touchesEnded:touches4 withEvent:event4]);
2592  OCMReject([mockFlutterViewContoller2 touchesEnded:touches4 withEvent:event4]);
2593 
2594  NSSet* touches5 = [NSSet setWithObject:@1];
2595  id event5 = OCMClassMock([UIEvent class]);
2596  [forwardGectureRecognizer touchesEnded:touches5 withEvent:event5];
2597  OCMVerify([mockFlutterViewContoller touchesEnded:touches5 withEvent:event5]);
2598  OCMReject([mockFlutterViewContoller2 touchesEnded:touches5 withEvent:event5]);
2599 
2600  // Now the 2nd touch sequence should go to the new FlutterViewController
2601 
2602  NSSet* touches6 = [NSSet setWithObject:@1];
2603  id event6 = OCMClassMock([UIEvent class]);
2604  [forwardGectureRecognizer touchesBegan:touches6 withEvent:event6];
2605  OCMVerify([mockFlutterViewContoller2 touchesBegan:touches6 withEvent:event6]);
2606  OCMReject([mockFlutterViewContoller touchesBegan:touches6 withEvent:event6]);
2607 
2608  // Allow the touch events to finish
2609  NSSet* touches7 = [NSSet setWithObject:@1];
2610  id event7 = OCMClassMock([UIEvent class]);
2611  [forwardGectureRecognizer touchesMoved:touches7 withEvent:event7];
2612  OCMVerify([mockFlutterViewContoller2 touchesMoved:touches7 withEvent:event7]);
2613  OCMReject([mockFlutterViewContoller touchesMoved:touches7 withEvent:event7]);
2614 
2615  NSSet* touches8 = [NSSet setWithObject:@1];
2616  id event8 = OCMClassMock([UIEvent class]);
2617  [forwardGectureRecognizer touchesEnded:touches8 withEvent:event8];
2618  OCMVerify([mockFlutterViewContoller2 touchesEnded:touches8 withEvent:event8]);
2619  OCMReject([mockFlutterViewContoller touchesEnded:touches8 withEvent:event8]);
2620 
2621  flutterPlatformViewsController->Reset();
2622 }
2623 
2624 - (void)testFlutterPlatformViewTouchesCancelledEventAreForcedToBeCancelled {
2625  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
2626  auto thread_task_runner = CreateNewThread("FlutterPlatformViewsTest");
2627  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
2628  /*platform=*/thread_task_runner,
2629  /*raster=*/thread_task_runner,
2630  /*ui=*/thread_task_runner,
2631  /*io=*/thread_task_runner);
2632  auto flutterPlatformViewsController = std::make_shared<flutter::FlutterPlatformViewsController>();
2633  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2634  /*delegate=*/mock_delegate,
2635  /*rendering_api=*/mock_delegate.settings_.enable_impeller
2638  /*platform_views_controller=*/flutterPlatformViewsController,
2639  /*task_runners=*/runners,
2640  /*worker_task_runner=*/nil,
2641  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
2642 
2645  flutterPlatformViewsController->RegisterViewFactory(
2646  factory, @"MockFlutterPlatformView",
2648  FlutterResult result = ^(id result) {
2649  };
2650  flutterPlatformViewsController->OnMethodCall(
2652  methodCallWithMethodName:@"create"
2653  arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}],
2654  result);
2655 
2656  XCTAssertNotNil(gMockPlatformView);
2657 
2658  // Find touch inteceptor view
2659  UIView* touchInteceptorView = gMockPlatformView;
2660  while (touchInteceptorView != nil &&
2661  ![touchInteceptorView isKindOfClass:[FlutterTouchInterceptingView class]]) {
2662  touchInteceptorView = touchInteceptorView.superview;
2663  }
2664  XCTAssertNotNil(touchInteceptorView);
2665 
2666  // Find ForwardGestureRecognizer
2667  UIGestureRecognizer* forwardGectureRecognizer = nil;
2668  for (UIGestureRecognizer* gestureRecognizer in touchInteceptorView.gestureRecognizers) {
2669  if ([gestureRecognizer isKindOfClass:NSClassFromString(@"ForwardingGestureRecognizer")]) {
2670  forwardGectureRecognizer = gestureRecognizer;
2671  break;
2672  }
2673  }
2674  id mockFlutterViewContoller = OCMClassMock([FlutterViewController class]);
2675 
2676  flutterPlatformViewsController->SetFlutterViewController(mockFlutterViewContoller);
2677 
2678  NSSet* touches1 = [NSSet setWithObject:@1];
2679  id event1 = OCMClassMock([UIEvent class]);
2680  [forwardGectureRecognizer touchesBegan:touches1 withEvent:event1];
2681 
2682  [forwardGectureRecognizer touchesCancelled:touches1 withEvent:event1];
2683  OCMVerify([mockFlutterViewContoller forceTouchesCancelled:touches1]);
2684 
2685  flutterPlatformViewsController->Reset();
2686 }
2687 
2688 - (void)testFlutterPlatformViewControllerSubmitFrameWithoutFlutterViewNotCrashing {
2689  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
2690  auto thread_task_runner = CreateNewThread("FlutterPlatformViewsTest");
2691  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
2692  /*platform=*/thread_task_runner,
2693  /*raster=*/thread_task_runner,
2694  /*ui=*/thread_task_runner,
2695  /*io=*/thread_task_runner);
2696  auto flutterPlatformViewsController = std::make_shared<flutter::FlutterPlatformViewsController>();
2697  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2698  /*delegate=*/mock_delegate,
2699  /*rendering_api=*/mock_delegate.settings_.enable_impeller
2702  /*platform_views_controller=*/flutterPlatformViewsController,
2703  /*task_runners=*/runners,
2704  /*worker_task_runner=*/nil,
2705  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
2706 
2709  flutterPlatformViewsController->RegisterViewFactory(
2710  factory, @"MockFlutterPlatformView",
2712  FlutterResult result = ^(id result) {
2713  };
2714  flutterPlatformViewsController->OnMethodCall(
2716  methodCallWithMethodName:@"create"
2717  arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}],
2718  result);
2719 
2720  XCTAssertNotNil(gMockPlatformView);
2721 
2722  // Create embedded view params
2723  flutter::MutatorsStack stack;
2724  SkMatrix finalMatrix;
2725 
2726  auto embeddedViewParams_1 =
2727  std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, SkSize::Make(300, 300), stack);
2728 
2729  flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams_1));
2730  flutterPlatformViewsController->CompositeEmbeddedView(2);
2731  flutter::SurfaceFrame::FramebufferInfo framebuffer_info;
2732  auto mock_surface = std::make_unique<flutter::SurfaceFrame>(
2733  nullptr, framebuffer_info,
2734  [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return false; },
2735  /*frame_size=*/SkISize::Make(800, 600));
2736  XCTAssertFalse(
2737  flutterPlatformViewsController->SubmitFrame(nullptr, nullptr, std::move(mock_surface)));
2738 
2739  auto embeddedViewParams_2 =
2740  std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, SkSize::Make(300, 300), stack);
2741  flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams_2));
2742  flutterPlatformViewsController->CompositeEmbeddedView(2);
2743  auto mock_surface_submit_true = std::make_unique<flutter::SurfaceFrame>(
2744  nullptr, framebuffer_info,
2745  [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; },
2746  /*frame_size=*/SkISize::Make(800, 600));
2747  XCTAssertTrue(flutterPlatformViewsController->SubmitFrame(nullptr, nullptr,
2748  std::move(mock_surface_submit_true)));
2749 }
2750 
2751 - (void)
2752  testFlutterPlatformViewControllerResetDeallocsPlatformViewWhenRootViewsNotBindedToFlutterView {
2753  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
2754  auto thread_task_runner = CreateNewThread("FlutterPlatformViewsTest");
2755  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
2756  /*platform=*/thread_task_runner,
2757  /*raster=*/thread_task_runner,
2758  /*ui=*/thread_task_runner,
2759  /*io=*/thread_task_runner);
2760  auto flutterPlatformViewsController = std::make_shared<flutter::FlutterPlatformViewsController>();
2761  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2762  /*delegate=*/mock_delegate,
2763  /*rendering_api=*/mock_delegate.settings_.enable_impeller
2766  /*platform_views_controller=*/flutterPlatformViewsController,
2767  /*task_runners=*/runners,
2768  /*worker_task_runner=*/nil,
2769  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
2770 
2771  UIView* mockFlutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
2772  flutterPlatformViewsController->SetFlutterView(mockFlutterView);
2773 
2776  flutterPlatformViewsController->RegisterViewFactory(
2777  factory, @"MockFlutterPlatformView",
2779  FlutterResult result = ^(id result) {
2780  };
2781  // autorelease pool to trigger an autorelease for all the root_views_ and touch_interceptors_.
2782  @autoreleasepool {
2783  flutterPlatformViewsController->OnMethodCall(
2785  methodCallWithMethodName:@"create"
2786  arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}],
2787  result);
2788 
2789  flutter::MutatorsStack stack;
2790  SkMatrix finalMatrix;
2791  auto embeddedViewParams =
2792  std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, SkSize::Make(300, 300), stack);
2793  flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams));
2794  flutterPlatformViewsController->CompositeEmbeddedView(2);
2795  // Not calling |flutterPlatformViewsController::SubmitFrame| so that the platform views are not
2796  // added to flutter_view_.
2797 
2798  XCTAssertNotNil(gMockPlatformView);
2799  flutterPlatformViewsController->Reset();
2800  }
2801  XCTAssertNil(gMockPlatformView);
2802 }
2803 
2804 - (void)testFlutterPlatformViewControllerBeginFrameShouldResetCompisitionOrder {
2805  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
2806  auto thread_task_runner = CreateNewThread("FlutterPlatformViewsTest");
2807  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
2808  /*platform=*/thread_task_runner,
2809  /*raster=*/thread_task_runner,
2810  /*ui=*/thread_task_runner,
2811  /*io=*/thread_task_runner);
2812  auto flutterPlatformViewsController = std::make_shared<flutter::FlutterPlatformViewsController>();
2813  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2814  /*delegate=*/mock_delegate,
2815  /*rendering_api=*/mock_delegate.settings_.enable_impeller
2818  /*platform_views_controller=*/flutterPlatformViewsController,
2819  /*task_runners=*/runners,
2820  /*worker_task_runner=*/nil,
2821  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
2822 
2823  UIView* mockFlutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
2824  flutterPlatformViewsController->SetFlutterView(mockFlutterView);
2825 
2828  flutterPlatformViewsController->RegisterViewFactory(
2829  factory, @"MockFlutterPlatformView",
2831  FlutterResult result = ^(id result) {
2832  };
2833 
2834  flutterPlatformViewsController->OnMethodCall(
2836  methodCallWithMethodName:@"create"
2837  arguments:@{@"id" : @0, @"viewType" : @"MockFlutterPlatformView"}],
2838  result);
2839 
2840  // First frame, |EmbeddedViewCount| is not empty after composite.
2841  flutterPlatformViewsController->BeginFrame(SkISize::Make(300, 300));
2842  flutter::MutatorsStack stack;
2843  SkMatrix finalMatrix;
2844  auto embeddedViewParams1 =
2845  std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, SkSize::Make(300, 300), stack);
2846  flutterPlatformViewsController->PrerollCompositeEmbeddedView(0, std::move(embeddedViewParams1));
2847  flutterPlatformViewsController->CompositeEmbeddedView(0);
2848  XCTAssertEqual(flutterPlatformViewsController->EmbeddedViewCount(), 1UL);
2849 
2850  // Second frame, |EmbeddedViewCount| should be empty at the start
2851  flutterPlatformViewsController->BeginFrame(SkISize::Make(300, 300));
2852  XCTAssertEqual(flutterPlatformViewsController->EmbeddedViewCount(), 0UL);
2853 
2854  auto embeddedViewParams2 =
2855  std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, SkSize::Make(300, 300), stack);
2856  flutterPlatformViewsController->PrerollCompositeEmbeddedView(0, std::move(embeddedViewParams2));
2857  flutterPlatformViewsController->CompositeEmbeddedView(0);
2858  XCTAssertEqual(flutterPlatformViewsController->EmbeddedViewCount(), 1UL);
2859 }
2860 
2861 - (void)
2862  testFlutterPlatformViewControllerSubmitFrameShouldOrderSubviewsCorrectlyWithDifferentViewHierarchy {
2863  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
2864  auto thread_task_runner = CreateNewThread("FlutterPlatformViewsTest");
2865  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
2866  /*platform=*/thread_task_runner,
2867  /*raster=*/thread_task_runner,
2868  /*ui=*/thread_task_runner,
2869  /*io=*/thread_task_runner);
2870  auto flutterPlatformViewsController = std::make_shared<flutter::FlutterPlatformViewsController>();
2871  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2872  /*delegate=*/mock_delegate,
2873  /*rendering_api=*/mock_delegate.settings_.enable_impeller
2876  /*platform_views_controller=*/flutterPlatformViewsController,
2877  /*task_runners=*/runners,
2878  /*worker_task_runner=*/nil,
2879  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
2880 
2881  UIView* mockFlutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
2882  flutterPlatformViewsController->SetFlutterView(mockFlutterView);
2883 
2886  flutterPlatformViewsController->RegisterViewFactory(
2887  factory, @"MockFlutterPlatformView",
2889  FlutterResult result = ^(id result) {
2890  };
2891  flutterPlatformViewsController->OnMethodCall(
2893  methodCallWithMethodName:@"create"
2894  arguments:@{@"id" : @0, @"viewType" : @"MockFlutterPlatformView"}],
2895  result);
2896  UIView* view1 = gMockPlatformView;
2897 
2898  // This overwrites `gMockPlatformView` to another view.
2899  flutterPlatformViewsController->OnMethodCall(
2901  methodCallWithMethodName:@"create"
2902  arguments:@{@"id" : @1, @"viewType" : @"MockFlutterPlatformView"}],
2903  result);
2904  UIView* view2 = gMockPlatformView;
2905 
2906  flutterPlatformViewsController->BeginFrame(SkISize::Make(300, 300));
2907  flutter::MutatorsStack stack;
2908  SkMatrix finalMatrix;
2909  auto embeddedViewParams1 =
2910  std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, SkSize::Make(300, 300), stack);
2911  flutterPlatformViewsController->PrerollCompositeEmbeddedView(0, std::move(embeddedViewParams1));
2912  flutterPlatformViewsController->CompositeEmbeddedView(0);
2913  auto embeddedViewParams2 =
2914  std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, SkSize::Make(500, 500), stack);
2915  flutterPlatformViewsController->PrerollCompositeEmbeddedView(1, std::move(embeddedViewParams2));
2916  flutterPlatformViewsController->CompositeEmbeddedView(1);
2917 
2918  // SKSurface is required if the root FlutterView is present.
2919  const SkImageInfo image_info = SkImageInfo::MakeN32Premul(1000, 1000);
2920  sk_sp<SkSurface> mock_sk_surface = SkSurfaces::Raster(image_info);
2921  flutter::SurfaceFrame::FramebufferInfo framebuffer_info;
2922  auto mock_surface = std::make_unique<flutter::SurfaceFrame>(
2923  std::move(mock_sk_surface), framebuffer_info,
2924  [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; },
2925  /*frame_size=*/SkISize::Make(800, 600));
2926 
2927  XCTAssertTrue(
2928  flutterPlatformViewsController->SubmitFrame(nullptr, nullptr, std::move(mock_surface)));
2929  // platform view is wrapped by touch interceptor, which itself is wrapped by clipping view.
2930  UIView* clippingView1 = view1.superview.superview;
2931  UIView* clippingView2 = view2.superview.superview;
2932  UIView* flutterView = clippingView1.superview;
2933  XCTAssertTrue([flutterView.subviews indexOfObject:clippingView1] <
2934  [flutterView.subviews indexOfObject:clippingView2],
2935  @"The first clipping view should be added before the second clipping view.");
2936 
2937  // Need to recreate these params since they are `std::move`ed.
2938  flutterPlatformViewsController->BeginFrame(SkISize::Make(300, 300));
2939  // Process the second frame in the opposite order.
2940  embeddedViewParams2 =
2941  std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, SkSize::Make(500, 500), stack);
2942  flutterPlatformViewsController->PrerollCompositeEmbeddedView(1, std::move(embeddedViewParams2));
2943  flutterPlatformViewsController->CompositeEmbeddedView(1);
2944  embeddedViewParams1 =
2945  std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, SkSize::Make(300, 300), stack);
2946  flutterPlatformViewsController->PrerollCompositeEmbeddedView(0, std::move(embeddedViewParams1));
2947  flutterPlatformViewsController->CompositeEmbeddedView(0);
2948 
2949  mock_sk_surface = SkSurfaces::Raster(image_info);
2950  mock_surface = std::make_unique<flutter::SurfaceFrame>(
2951  std::move(mock_sk_surface), framebuffer_info,
2952  [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; },
2953  /*frame_size=*/SkISize::Make(800, 600));
2954  XCTAssertTrue(
2955  flutterPlatformViewsController->SubmitFrame(nullptr, nullptr, std::move(mock_surface)));
2956  XCTAssertTrue([flutterView.subviews indexOfObject:clippingView1] >
2957  [flutterView.subviews indexOfObject:clippingView2],
2958  @"The first clipping view should be added after the second clipping view.");
2959 }
2960 
2961 - (void)
2962  testFlutterPlatformViewControllerSubmitFrameShouldOrderSubviewsCorrectlyWithSameViewHierarchy {
2963  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
2964  auto thread_task_runner = CreateNewThread("FlutterPlatformViewsTest");
2965  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
2966  /*platform=*/thread_task_runner,
2967  /*raster=*/thread_task_runner,
2968  /*ui=*/thread_task_runner,
2969  /*io=*/thread_task_runner);
2970  auto flutterPlatformViewsController = std::make_shared<flutter::FlutterPlatformViewsController>();
2971  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2972  /*delegate=*/mock_delegate,
2973  /*rendering_api=*/mock_delegate.settings_.enable_impeller
2976  /*platform_views_controller=*/flutterPlatformViewsController,
2977  /*task_runners=*/runners,
2978  /*worker_task_runner=*/nil,
2979  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
2980 
2981  UIView* mockFlutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
2982  flutterPlatformViewsController->SetFlutterView(mockFlutterView);
2983 
2986  flutterPlatformViewsController->RegisterViewFactory(
2987  factory, @"MockFlutterPlatformView",
2989  FlutterResult result = ^(id result) {
2990  };
2991  flutterPlatformViewsController->OnMethodCall(
2993  methodCallWithMethodName:@"create"
2994  arguments:@{@"id" : @0, @"viewType" : @"MockFlutterPlatformView"}],
2995  result);
2996  UIView* view1 = gMockPlatformView;
2997 
2998  // This overwrites `gMockPlatformView` to another view.
2999  flutterPlatformViewsController->OnMethodCall(
3001  methodCallWithMethodName:@"create"
3002  arguments:@{@"id" : @1, @"viewType" : @"MockFlutterPlatformView"}],
3003  result);
3004  UIView* view2 = gMockPlatformView;
3005 
3006  flutterPlatformViewsController->BeginFrame(SkISize::Make(300, 300));
3007  flutter::MutatorsStack stack;
3008  SkMatrix finalMatrix;
3009  auto embeddedViewParams1 =
3010  std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, SkSize::Make(300, 300), stack);
3011  flutterPlatformViewsController->PrerollCompositeEmbeddedView(0, std::move(embeddedViewParams1));
3012  flutterPlatformViewsController->CompositeEmbeddedView(0);
3013  auto embeddedViewParams2 =
3014  std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, SkSize::Make(500, 500), stack);
3015  flutterPlatformViewsController->PrerollCompositeEmbeddedView(1, std::move(embeddedViewParams2));
3016  flutterPlatformViewsController->CompositeEmbeddedView(1);
3017 
3018  // SKSurface is required if the root FlutterView is present.
3019  const SkImageInfo image_info = SkImageInfo::MakeN32Premul(1000, 1000);
3020  sk_sp<SkSurface> mock_sk_surface = SkSurfaces::Raster(image_info);
3021  flutter::SurfaceFrame::FramebufferInfo framebuffer_info;
3022  auto mock_surface = std::make_unique<flutter::SurfaceFrame>(
3023  std::move(mock_sk_surface), framebuffer_info,
3024  [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; },
3025  /*frame_size=*/SkISize::Make(800, 600));
3026 
3027  XCTAssertTrue(
3028  flutterPlatformViewsController->SubmitFrame(nullptr, nullptr, std::move(mock_surface)));
3029  // platform view is wrapped by touch interceptor, which itself is wrapped by clipping view.
3030  UIView* clippingView1 = view1.superview.superview;
3031  UIView* clippingView2 = view2.superview.superview;
3032  UIView* flutterView = clippingView1.superview;
3033  XCTAssertTrue([flutterView.subviews indexOfObject:clippingView1] <
3034  [flutterView.subviews indexOfObject:clippingView2],
3035  @"The first clipping view should be added before the second clipping view.");
3036 
3037  // Need to recreate these params since they are `std::move`ed.
3038  flutterPlatformViewsController->BeginFrame(SkISize::Make(300, 300));
3039  // Process the second frame in the same order.
3040  embeddedViewParams1 =
3041  std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, SkSize::Make(300, 300), stack);
3042  flutterPlatformViewsController->PrerollCompositeEmbeddedView(0, std::move(embeddedViewParams1));
3043  flutterPlatformViewsController->CompositeEmbeddedView(0);
3044  embeddedViewParams2 =
3045  std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, SkSize::Make(500, 500), stack);
3046  flutterPlatformViewsController->PrerollCompositeEmbeddedView(1, std::move(embeddedViewParams2));
3047  flutterPlatformViewsController->CompositeEmbeddedView(1);
3048 
3049  mock_sk_surface = SkSurfaces::Raster(image_info);
3050  mock_surface = std::make_unique<flutter::SurfaceFrame>(
3051  std::move(mock_sk_surface), framebuffer_info,
3052  [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; },
3053  /*frame_size=*/SkISize::Make(800, 600));
3054  XCTAssertTrue(
3055  flutterPlatformViewsController->SubmitFrame(nullptr, nullptr, std::move(mock_surface)));
3056  XCTAssertTrue([flutterView.subviews indexOfObject:clippingView1] <
3057  [flutterView.subviews indexOfObject:clippingView2],
3058  @"The first clipping view should be added before the second clipping view.");
3059 }
3060 
3061 - (void)testThreadMergeAtEndFrame {
3062  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
3063  auto thread_task_runner_platform = CreateNewThread("FlutterPlatformViewsTest1");
3064  auto thread_task_runner_other = CreateNewThread("FlutterPlatformViewsTest2");
3065  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
3066  /*platform=*/thread_task_runner_platform,
3067  /*raster=*/thread_task_runner_other,
3068  /*ui=*/thread_task_runner_other,
3069  /*io=*/thread_task_runner_other);
3070  auto flutterPlatformViewsController = std::make_shared<flutter::FlutterPlatformViewsController>();
3071  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
3072  /*delegate=*/mock_delegate,
3073  /*rendering_api=*/mock_delegate.settings_.enable_impeller
3076  /*platform_views_controller=*/flutterPlatformViewsController,
3077  /*task_runners=*/runners,
3078  /*worker_task_runner=*/nil,
3079  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
3080 
3081  UIView* mockFlutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
3082  flutterPlatformViewsController->SetFlutterView(mockFlutterView);
3083 
3086  flutterPlatformViewsController->RegisterViewFactory(
3087  factory, @"MockFlutterPlatformView",
3089  XCTestExpectation* waitForPlatformView =
3090  [self expectationWithDescription:@"wait for platform view to be created"];
3091  FlutterResult result = ^(id result) {
3092  [waitForPlatformView fulfill];
3093  };
3094 
3095  flutterPlatformViewsController->OnMethodCall(
3097  methodCallWithMethodName:@"create"
3098  arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}],
3099  result);
3100  [self waitForExpectations:@[ waitForPlatformView ] timeout:30];
3101  XCTAssertNotNil(gMockPlatformView);
3102 
3103  flutterPlatformViewsController->BeginFrame(SkISize::Make(300, 300));
3104  SkMatrix finalMatrix;
3105  flutter::MutatorsStack stack;
3106  auto embeddedViewParams =
3107  std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, SkSize::Make(300, 300), stack);
3108  flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams));
3109 
3110  fml::RefPtr<fml::RasterThreadMerger> raster_thread_merger =
3111  fml::MakeRefCounted<fml::RasterThreadMerger>(thread_task_runner_platform->GetTaskQueueId(),
3112  thread_task_runner_other->GetTaskQueueId());
3113  XCTAssertEqual(flutterPlatformViewsController->PostPrerollAction(raster_thread_merger),
3114  flutter::PostPrerollResult::kSkipAndRetryFrame);
3115  XCTAssertFalse(raster_thread_merger->IsMerged());
3116 
3117  flutterPlatformViewsController->EndFrame(true, raster_thread_merger);
3118  XCTAssertTrue(raster_thread_merger->IsMerged());
3119 
3120  // Unmerge threads before the end of the test
3121  // TaskRunners are required to be unmerged before destruction.
3122  while (raster_thread_merger->DecrementLease() != fml::RasterThreadStatus::kUnmergedNow) {
3123  }
3124 }
3125 
3126 - (int)alphaOfPoint:(CGPoint)point onView:(UIView*)view {
3127  unsigned char pixel[4] = {0};
3128 
3129  CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
3130 
3131  // Draw the pixel on `point` in the context.
3132  CGContextRef context = CGBitmapContextCreate(
3133  pixel, 1, 1, 8, 4, colorSpace, kCGBitmapAlphaInfoMask & kCGImageAlphaPremultipliedLast);
3134  CGContextTranslateCTM(context, -point.x, -point.y);
3135  [view.layer renderInContext:context];
3136 
3137  CGContextRelease(context);
3138  CGColorSpaceRelease(colorSpace);
3139  // Get the alpha from the pixel that we just rendered.
3140  return pixel[3];
3141 }
3142 
3143 - (void)testHasFirstResponderInViewHierarchySubtree_viewItselfBecomesFirstResponder {
3144  // For view to become the first responder, it must be a descendant of a UIWindow
3145  UIWindow* window = [[UIWindow alloc] init];
3146  UITextField* textField = [[UITextField alloc] init];
3147  [window addSubview:textField];
3148 
3149  [textField becomeFirstResponder];
3150  XCTAssertTrue(textField.isFirstResponder);
3151  XCTAssertTrue(textField.flt_hasFirstResponderInViewHierarchySubtree);
3152  [textField resignFirstResponder];
3153  XCTAssertFalse(textField.isFirstResponder);
3154  XCTAssertFalse(textField.flt_hasFirstResponderInViewHierarchySubtree);
3155 }
3156 
3157 - (void)testHasFirstResponderInViewHierarchySubtree_descendantViewBecomesFirstResponder {
3158  // For view to become the first responder, it must be a descendant of a UIWindow
3159  UIWindow* window = [[UIWindow alloc] init];
3160  UIView* view = [[UIView alloc] init];
3161  UIView* childView = [[UIView alloc] init];
3162  UITextField* textField = [[UITextField alloc] init];
3163  [window addSubview:view];
3164  [view addSubview:childView];
3165  [childView addSubview:textField];
3166 
3167  [textField becomeFirstResponder];
3168  XCTAssertTrue(textField.isFirstResponder);
3169  XCTAssertTrue(view.flt_hasFirstResponderInViewHierarchySubtree);
3170  [textField resignFirstResponder];
3171  XCTAssertFalse(textField.isFirstResponder);
3172  XCTAssertFalse(view.flt_hasFirstResponderInViewHierarchySubtree);
3173 }
3174 
3175 - (void)testFlutterClippingMaskViewPoolReuseViewsAfterRecycle {
3176  FlutterClippingMaskViewPool* pool = [[FlutterClippingMaskViewPool alloc] initWithCapacity:2];
3177  FlutterClippingMaskView* view1 = [pool getMaskViewWithFrame:CGRectZero];
3178  FlutterClippingMaskView* view2 = [pool getMaskViewWithFrame:CGRectZero];
3179  [pool insertViewToPoolIfNeeded:view1];
3180  [pool insertViewToPoolIfNeeded:view2];
3181  CGRect newRect = CGRectMake(0, 0, 10, 10);
3182  FlutterClippingMaskView* view3 = [pool getMaskViewWithFrame:newRect];
3183  FlutterClippingMaskView* view4 = [pool getMaskViewWithFrame:newRect];
3184  // view3 and view4 should randomly get either of view1 and view2.
3185  NSSet* set1 = [NSSet setWithObjects:view1, view2, nil];
3186  NSSet* set2 = [NSSet setWithObjects:view3, view4, nil];
3187  XCTAssertEqualObjects(set1, set2);
3188  XCTAssertTrue(CGRectEqualToRect(view3.frame, newRect));
3189  XCTAssertTrue(CGRectEqualToRect(view4.frame, newRect));
3190 }
3191 
3192 - (void)testFlutterClippingMaskViewPoolAllocsNewMaskViewsAfterReachingCapacity {
3193  FlutterClippingMaskViewPool* pool = [[FlutterClippingMaskViewPool alloc] initWithCapacity:2];
3194  FlutterClippingMaskView* view1 = [pool getMaskViewWithFrame:CGRectZero];
3195  FlutterClippingMaskView* view2 = [pool getMaskViewWithFrame:CGRectZero];
3196  FlutterClippingMaskView* view3 = [pool getMaskViewWithFrame:CGRectZero];
3197  XCTAssertNotEqual(view1, view3);
3198  XCTAssertNotEqual(view2, view3);
3199 }
3200 
3201 - (void)testMaskViewsReleasedWhenPoolIsReleased {
3202  __weak UIView* weakView;
3203  @autoreleasepool {
3204  FlutterClippingMaskViewPool* pool = [[FlutterClippingMaskViewPool alloc] initWithCapacity:2];
3205  FlutterClippingMaskView* view = [pool getMaskViewWithFrame:CGRectZero];
3206  weakView = view;
3207  XCTAssertNotNil(weakView);
3208  }
3209  XCTAssertNil(weakView);
3210 }
3211 
3212 - (void)testClipMaskViewIsReused {
3213  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
3214  auto thread_task_runner = CreateNewThread("FlutterPlatformViewsTest");
3215  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
3216  /*platform=*/thread_task_runner,
3217  /*raster=*/thread_task_runner,
3218  /*ui=*/thread_task_runner,
3219  /*io=*/thread_task_runner);
3220  auto flutterPlatformViewsController = std::make_shared<flutter::FlutterPlatformViewsController>();
3221  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
3222  /*delegate=*/mock_delegate,
3223  /*rendering_api=*/mock_delegate.settings_.enable_impeller
3226  /*platform_views_controller=*/flutterPlatformViewsController,
3227  /*task_runners=*/runners,
3228  /*worker_task_runner=*/nil,
3229  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
3230 
3233  flutterPlatformViewsController->RegisterViewFactory(
3234  factory, @"MockFlutterPlatformView",
3236  FlutterResult result = ^(id result) {
3237  };
3238  flutterPlatformViewsController->OnMethodCall(
3240  methodCallWithMethodName:@"create"
3241  arguments:@{@"id" : @1, @"viewType" : @"MockFlutterPlatformView"}],
3242  result);
3243 
3244  XCTAssertNotNil(gMockPlatformView);
3245  UIView* mockFlutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
3246  flutterPlatformViewsController->SetFlutterView(mockFlutterView);
3247  // Create embedded view params
3248  flutter::MutatorsStack stack1;
3249  // Layer tree always pushes a screen scale factor to the stack
3250  SkMatrix screenScaleMatrix =
3251  SkMatrix::Scale([UIScreen mainScreen].scale, [UIScreen mainScreen].scale);
3252  stack1.PushTransform(screenScaleMatrix);
3253  // Push a clip rect
3254  SkRect rect = SkRect::MakeXYWH(2, 2, 3, 3);
3255  stack1.PushClipRect(rect);
3256 
3257  auto embeddedViewParams1 = std::make_unique<flutter::EmbeddedViewParams>(
3258  screenScaleMatrix, SkSize::Make(10, 10), stack1);
3259 
3260  flutterPlatformViewsController->PrerollCompositeEmbeddedView(1, std::move(embeddedViewParams1));
3261  flutterPlatformViewsController->CompositeEmbeddedView(1);
3262  UIView* childClippingView1 = gMockPlatformView.superview.superview;
3263  UIView* maskView1 = childClippingView1.maskView;
3264  XCTAssertNotNil(maskView1);
3265 
3266  // Composite a new frame.
3267  flutterPlatformViewsController->BeginFrame(SkISize::Make(100, 100));
3268  flutter::MutatorsStack stack2;
3269  auto embeddedViewParams2 = std::make_unique<flutter::EmbeddedViewParams>(
3270  screenScaleMatrix, SkSize::Make(10, 10), stack2);
3271  auto embeddedViewParams3 = std::make_unique<flutter::EmbeddedViewParams>(
3272  screenScaleMatrix, SkSize::Make(10, 10), stack2);
3273  flutterPlatformViewsController->PrerollCompositeEmbeddedView(1, std::move(embeddedViewParams3));
3274  flutterPlatformViewsController->CompositeEmbeddedView(1);
3275  childClippingView1 = gMockPlatformView.superview.superview;
3276 
3277  // This overrides gMockPlatformView to point to the newly created platform view.
3278  flutterPlatformViewsController->OnMethodCall(
3280  methodCallWithMethodName:@"create"
3281  arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}],
3282  result);
3283 
3284  auto embeddedViewParams4 = std::make_unique<flutter::EmbeddedViewParams>(
3285  screenScaleMatrix, SkSize::Make(10, 10), stack1);
3286  flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams4));
3287  flutterPlatformViewsController->CompositeEmbeddedView(2);
3288  UIView* childClippingView2 = gMockPlatformView.superview.superview;
3289 
3290  UIView* maskView2 = childClippingView2.maskView;
3291  XCTAssertEqual(maskView1, maskView2);
3292  XCTAssertNotNil(childClippingView2.maskView);
3293  XCTAssertNil(childClippingView1.maskView);
3294 }
3295 
3296 - (void)testDifferentClipMaskViewIsUsedForEachView {
3297  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
3298  auto thread_task_runner = CreateNewThread("FlutterPlatformViewsTest");
3299  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
3300  /*platform=*/thread_task_runner,
3301  /*raster=*/thread_task_runner,
3302  /*ui=*/thread_task_runner,
3303  /*io=*/thread_task_runner);
3304  auto flutterPlatformViewsController = std::make_shared<flutter::FlutterPlatformViewsController>();
3305  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
3306  /*delegate=*/mock_delegate,
3307  /*rendering_api=*/mock_delegate.settings_.enable_impeller
3310  /*platform_views_controller=*/flutterPlatformViewsController,
3311  /*task_runners=*/runners,
3312  /*worker_task_runner=*/nil,
3313  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
3314 
3317  flutterPlatformViewsController->RegisterViewFactory(
3318  factory, @"MockFlutterPlatformView",
3320  FlutterResult result = ^(id result) {
3321  };
3322 
3323  flutterPlatformViewsController->OnMethodCall(
3325  methodCallWithMethodName:@"create"
3326  arguments:@{@"id" : @1, @"viewType" : @"MockFlutterPlatformView"}],
3327  result);
3328  UIView* view1 = gMockPlatformView;
3329 
3330  // This overwrites `gMockPlatformView` to another view.
3331  flutterPlatformViewsController->OnMethodCall(
3333  methodCallWithMethodName:@"create"
3334  arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}],
3335  result);
3336  UIView* view2 = gMockPlatformView;
3337 
3338  XCTAssertNotNil(gMockPlatformView);
3339  UIView* mockFlutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
3340  flutterPlatformViewsController->SetFlutterView(mockFlutterView);
3341  // Create embedded view params
3342  flutter::MutatorsStack stack1;
3343  // Layer tree always pushes a screen scale factor to the stack
3344  SkMatrix screenScaleMatrix =
3345  SkMatrix::Scale([UIScreen mainScreen].scale, [UIScreen mainScreen].scale);
3346  stack1.PushTransform(screenScaleMatrix);
3347  // Push a clip rect
3348  SkRect rect = SkRect::MakeXYWH(2, 2, 3, 3);
3349  stack1.PushClipRect(rect);
3350 
3351  auto embeddedViewParams1 = std::make_unique<flutter::EmbeddedViewParams>(
3352  screenScaleMatrix, SkSize::Make(10, 10), stack1);
3353 
3354  flutter::MutatorsStack stack2;
3355  stack2.PushClipRect(rect);
3356  auto embeddedViewParams2 = std::make_unique<flutter::EmbeddedViewParams>(
3357  screenScaleMatrix, SkSize::Make(10, 10), stack2);
3358 
3359  flutterPlatformViewsController->PrerollCompositeEmbeddedView(1, std::move(embeddedViewParams1));
3360  flutterPlatformViewsController->CompositeEmbeddedView(1);
3361  UIView* childClippingView1 = view1.superview.superview;
3362 
3363  flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams2));
3364  flutterPlatformViewsController->CompositeEmbeddedView(2);
3365  UIView* childClippingView2 = view2.superview.superview;
3366  UIView* maskView1 = childClippingView1.maskView;
3367  UIView* maskView2 = childClippingView2.maskView;
3368  XCTAssertNotEqual(maskView1, maskView2);
3369 }
3370 
3371 - (void)testMaskViewUsesCAShapeLayerAsTheBackingLayer {
3372  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
3373  auto thread_task_runner = CreateNewThread("FlutterPlatformViewsTest");
3374  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
3375  /*platform=*/thread_task_runner,
3376  /*raster=*/thread_task_runner,
3377  /*ui=*/thread_task_runner,
3378  /*io=*/thread_task_runner);
3379  auto flutterPlatformViewsController = std::make_shared<flutter::FlutterPlatformViewsController>();
3380  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
3381  /*delegate=*/mock_delegate,
3382  /*rendering_api=*/mock_delegate.settings_.enable_impeller
3385  /*platform_views_controller=*/flutterPlatformViewsController,
3386  /*task_runners=*/runners,
3387  /*worker_task_runner=*/nil,
3388  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
3389 
3392  flutterPlatformViewsController->RegisterViewFactory(
3393  factory, @"MockFlutterPlatformView",
3395  FlutterResult result = ^(id result) {
3396  };
3397 
3398  flutterPlatformViewsController->OnMethodCall(
3400  methodCallWithMethodName:@"create"
3401  arguments:@{@"id" : @1, @"viewType" : @"MockFlutterPlatformView"}],
3402  result);
3403 
3404  XCTAssertNotNil(gMockPlatformView);
3405  UIView* mockFlutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
3406  flutterPlatformViewsController->SetFlutterView(mockFlutterView);
3407  // Create embedded view params
3408  flutter::MutatorsStack stack1;
3409  // Layer tree always pushes a screen scale factor to the stack
3410  SkMatrix screenScaleMatrix =
3411  SkMatrix::Scale([UIScreen mainScreen].scale, [UIScreen mainScreen].scale);
3412  stack1.PushTransform(screenScaleMatrix);
3413  // Push a clip rect
3414  SkRect rect = SkRect::MakeXYWH(2, 2, 3, 3);
3415  stack1.PushClipRect(rect);
3416 
3417  auto embeddedViewParams1 = std::make_unique<flutter::EmbeddedViewParams>(
3418  screenScaleMatrix, SkSize::Make(10, 10), stack1);
3419 
3420  flutter::MutatorsStack stack2;
3421  stack2.PushClipRect(rect);
3422  auto embeddedViewParams2 = std::make_unique<flutter::EmbeddedViewParams>(
3423  screenScaleMatrix, SkSize::Make(10, 10), stack2);
3424 
3425  flutterPlatformViewsController->PrerollCompositeEmbeddedView(1, std::move(embeddedViewParams1));
3426  flutterPlatformViewsController->CompositeEmbeddedView(1);
3427  UIView* childClippingView = gMockPlatformView.superview.superview;
3428 
3429  UIView* maskView = childClippingView.maskView;
3430  XCTAssert([maskView.layer isKindOfClass:[CAShapeLayer class]],
3431  @"Mask view must use CAShapeLayer as its backing layer.");
3432 }
3433 
3434 // Return true if a correct visual effect view is found. It also implies all the validation in this
3435 // method passes.
3436 //
3437 // There are two fail states for this method. 1. One of the XCTAssert method failed; or 2. No
3438 // correct visual effect view found.
3439 - (BOOL)validateOneVisualEffectView:(UIView*)visualEffectView
3440  expectedFrame:(CGRect)frame
3441  inputRadius:(CGFloat)inputRadius {
3442  XCTAssertTrue(CGRectEqualToRect(visualEffectView.frame, frame));
3443  for (UIView* view in visualEffectView.subviews) {
3444  if (![NSStringFromClass([view class]) hasSuffix:@"BackdropView"]) {
3445  continue;
3446  }
3447  XCTAssertEqual(view.layer.filters.count, 1u);
3448  NSObject* filter = view.layer.filters.firstObject;
3449 
3450  XCTAssertEqualObjects([filter valueForKey:@"name"], @"gaussianBlur");
3451 
3452  NSObject* inputRadiusInFilter = [filter valueForKey:@"inputRadius"];
3453  XCTAssertTrue([inputRadiusInFilter isKindOfClass:[NSNumber class]] &&
3454  flutter::BlurRadiusEqualToBlurRadius(((NSNumber*)inputRadiusInFilter).floatValue,
3455  inputRadius));
3456  return YES;
3457  }
3458  return NO;
3459 }
3460 
3461 - (void)testDisposingViewInCompositionOrderDoNotCrash {
3462  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
3463  auto thread_task_runner = CreateNewThread("FlutterPlatformViewsTest");
3464  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
3465  /*platform=*/thread_task_runner,
3466  /*raster=*/thread_task_runner,
3467  /*ui=*/thread_task_runner,
3468  /*io=*/thread_task_runner);
3469  auto flutterPlatformViewsController = std::make_shared<flutter::FlutterPlatformViewsController>();
3470  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
3471  /*delegate=*/mock_delegate,
3472  /*rendering_api=*/mock_delegate.settings_.enable_impeller
3475  /*platform_views_controller=*/flutterPlatformViewsController,
3476  /*task_runners=*/runners,
3477  /*worker_task_runner=*/nil,
3478  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
3479 
3480  UIView* mockFlutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
3481  flutterPlatformViewsController->SetFlutterView(mockFlutterView);
3482 
3485  flutterPlatformViewsController->RegisterViewFactory(
3486  factory, @"MockFlutterPlatformView",
3488  FlutterResult result = ^(id result) {
3489  };
3490 
3491  flutterPlatformViewsController->OnMethodCall(
3493  methodCallWithMethodName:@"create"
3494  arguments:@{@"id" : @0, @"viewType" : @"MockFlutterPlatformView"}],
3495  result);
3496  flutterPlatformViewsController->OnMethodCall(
3498  methodCallWithMethodName:@"create"
3499  arguments:@{@"id" : @1, @"viewType" : @"MockFlutterPlatformView"}],
3500  result);
3501 
3502  {
3503  // **** First frame, view id 0, 1 in the composition_order_, disposing view 0 is called. **** //
3504  // No view should be disposed, or removed from the composition order.
3505  flutterPlatformViewsController->BeginFrame(SkISize::Make(300, 300));
3506  flutter::MutatorsStack stack;
3507  SkMatrix finalMatrix;
3508  auto embeddedViewParams0 =
3509  std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, SkSize::Make(300, 300), stack);
3510  flutterPlatformViewsController->PrerollCompositeEmbeddedView(0, std::move(embeddedViewParams0));
3511  flutterPlatformViewsController->CompositeEmbeddedView(0);
3512 
3513  auto embeddedViewParams1 =
3514  std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, SkSize::Make(300, 300), stack);
3515  flutterPlatformViewsController->PrerollCompositeEmbeddedView(1, std::move(embeddedViewParams1));
3516  flutterPlatformViewsController->CompositeEmbeddedView(1);
3517  XCTAssertEqual(flutterPlatformViewsController->EmbeddedViewCount(), 2UL);
3518 
3519  XCTestExpectation* expectation = [self expectationWithDescription:@"dispose call ended."];
3520  FlutterResult disposeResult = ^(id result) {
3521  [expectation fulfill];
3522  };
3523 
3524  flutterPlatformViewsController->OnMethodCall(
3525  [FlutterMethodCall methodCallWithMethodName:@"dispose" arguments:@0], disposeResult);
3526  [self waitForExpectationsWithTimeout:30 handler:nil];
3527 
3528  const SkImageInfo image_info = SkImageInfo::MakeN32Premul(1000, 1000);
3529  sk_sp<SkSurface> mock_sk_surface = SkSurfaces::Raster(image_info);
3530  flutter::SurfaceFrame::FramebufferInfo framebuffer_info;
3531  auto mock_surface = std::make_unique<flutter::SurfaceFrame>(
3532  std::move(mock_sk_surface), framebuffer_info,
3533  [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; },
3534  /*frame_size=*/SkISize::Make(800, 600));
3535  XCTAssertTrue(
3536  flutterPlatformViewsController->SubmitFrame(nullptr, nullptr, std::move(mock_surface)));
3537 
3538  // Disposing won't remove embedded views until the view is removed from the composition_order_
3539  XCTAssertEqual(flutterPlatformViewsController->EmbeddedViewCount(), 2UL);
3540  XCTAssertNotNil(flutterPlatformViewsController->GetPlatformViewByID(0));
3541  XCTAssertNotNil(flutterPlatformViewsController->GetPlatformViewByID(1));
3542  }
3543 
3544  {
3545  // **** Second frame, view id 1 in the composition_order_, no disposing view is called, **** //
3546  // View 0 is removed from the composition order in this frame, hence also disposed.
3547  flutterPlatformViewsController->BeginFrame(SkISize::Make(300, 300));
3548  flutter::MutatorsStack stack;
3549  SkMatrix finalMatrix;
3550  auto embeddedViewParams1 =
3551  std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, SkSize::Make(300, 300), stack);
3552  flutterPlatformViewsController->PrerollCompositeEmbeddedView(1, std::move(embeddedViewParams1));
3553  flutterPlatformViewsController->CompositeEmbeddedView(1);
3554 
3555  const SkImageInfo image_info = SkImageInfo::MakeN32Premul(1000, 1000);
3556  sk_sp<SkSurface> mock_sk_surface = SkSurfaces::Raster(image_info);
3557  flutter::SurfaceFrame::FramebufferInfo framebuffer_info;
3558  auto mock_surface = std::make_unique<flutter::SurfaceFrame>(
3559  std::move(mock_sk_surface), framebuffer_info,
3560  [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; },
3561  /*frame_size=*/SkISize::Make(800, 600));
3562  XCTAssertTrue(
3563  flutterPlatformViewsController->SubmitFrame(nullptr, nullptr, std::move(mock_surface)));
3564 
3565  // Disposing won't remove embedded views until the view is removed from the composition_order_
3566  XCTAssertEqual(flutterPlatformViewsController->EmbeddedViewCount(), 1UL);
3567  XCTAssertNil(flutterPlatformViewsController->GetPlatformViewByID(0));
3568  XCTAssertNotNil(flutterPlatformViewsController->GetPlatformViewByID(1));
3569  }
3570 }
3571 - (void)testOnlyPlatformViewsAreRemovedWhenReset {
3572  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
3573  auto thread_task_runner = CreateNewThread("FlutterPlatformViewsTest");
3574  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
3575  /*platform=*/thread_task_runner,
3576  /*raster=*/thread_task_runner,
3577  /*ui=*/thread_task_runner,
3578  /*io=*/thread_task_runner);
3579  auto flutterPlatformViewsController = std::make_shared<flutter::FlutterPlatformViewsController>();
3580  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
3581  /*delegate=*/mock_delegate,
3582  /*rendering_api=*/mock_delegate.settings_.enable_impeller
3585  /*platform_views_controller=*/flutterPlatformViewsController,
3586  /*task_runners=*/runners,
3587  /*worker_task_runner=*/nil,
3588  /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
3589 
3592  flutterPlatformViewsController->RegisterViewFactory(
3593  factory, @"MockFlutterPlatformView",
3595  FlutterResult result = ^(id result) {
3596  };
3597  flutterPlatformViewsController->OnMethodCall(
3599  methodCallWithMethodName:@"create"
3600  arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}],
3601  result);
3602  UIView* mockFlutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
3603  flutterPlatformViewsController->SetFlutterView(mockFlutterView);
3604  // Create embedded view params
3605  flutter::MutatorsStack stack;
3606  // Layer tree always pushes a screen scale factor to the stack
3607  SkMatrix screenScaleMatrix =
3608  SkMatrix::Scale([UIScreen mainScreen].scale, [UIScreen mainScreen].scale);
3609  stack.PushTransform(screenScaleMatrix);
3610  // Push a translate matrix
3611  SkMatrix translateMatrix = SkMatrix::Translate(100, 100);
3612  stack.PushTransform(translateMatrix);
3613  SkMatrix finalMatrix;
3614  finalMatrix.setConcat(screenScaleMatrix, translateMatrix);
3615 
3616  auto embeddedViewParams =
3617  std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, SkSize::Make(300, 300), stack);
3618 
3619  flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams));
3620  flutterPlatformViewsController->CompositeEmbeddedView(2);
3621 
3622  // SKSurface is required if the root FlutterView is present.
3623  const SkImageInfo image_info = SkImageInfo::MakeN32Premul(1000, 1000);
3624  sk_sp<SkSurface> mock_sk_surface = SkSurfaces::Raster(image_info);
3625  flutter::SurfaceFrame::FramebufferInfo framebuffer_info;
3626  auto mock_surface = std::make_unique<flutter::SurfaceFrame>(
3627  std::move(mock_sk_surface), framebuffer_info,
3628  [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; },
3629  /*frame_size=*/SkISize::Make(800, 600));
3630 
3631  flutterPlatformViewsController->SubmitFrame(nullptr, nullptr, std::move(mock_surface));
3632 
3633  UIView* someView = [[UIView alloc] init];
3634  [mockFlutterView addSubview:someView];
3635 
3636  flutterPlatformViewsController->Reset();
3637  XCTAssertEqual(mockFlutterView.subviews.count, 1u);
3638  XCTAssertEqual(mockFlutterView.subviews.firstObject, someView);
3639 }
3640 
3641 - (void)testFlutterTouchInterceptingViewLinksToAccessibilityContainer {
3642  FlutterTouchInterceptingView* touchInteceptorView = [[FlutterTouchInterceptingView alloc] init];
3643  NSObject* container = [[NSObject alloc] init];
3644  [touchInteceptorView setFlutterAccessibilityContainer:container];
3645  XCTAssertEqualObjects([touchInteceptorView accessibilityContainer], container);
3646 }
3647 
3648 @end
gMockPlatformView
static __weak FlutterPlatformViewsTestMockPlatformView * gMockPlatformView
Definition: FlutterPlatformViewsTest.mm:20
FlutterPlatformViews.h
FlutterViewController
Definition: FlutterViewController.h:56
FlutterPlatformViewsTestMockFlutterPlatformView::viewCreated
BOOL viewCreated
Definition: FlutterPlatformViewsTest.mm:43
FlutterPlatformViewsTestMockPlatformView
Definition: FlutterPlatformViewsTest.mm:23
flutter::BlurRadiusEqualToBlurRadius
BOOL BlurRadiusEqualToBlurRadius(CGFloat radius1, CGFloat radius2)
Definition: FlutterPlatformViews_Internal.mm:69
FLUTTER_ASSERT_ARC::CreateNewThread
fml::RefPtr< fml::TaskRunner > CreateNewThread(const std::string &name)
Definition: VsyncWaiterIosTest.mm:16
-[ChildClippingView applyBlurBackdropFilters:]
void applyBlurBackdropFilters:(NSArray< PlatformViewFilter * > *filters)
Definition: FlutterPlatformViews_Internal.mm:194
FlutterMacros.h
platform_view
std::unique_ptr< flutter::PlatformViewIOS > platform_view
Definition: FlutterEnginePlatformViewTest.mm:65
-[ChildClippingView backdropFilterSubviews]
NSMutableArray * backdropFilterSubviews()
Definition: FlutterPlatformViews_Internal.mm:218
FlutterPlatformViewsTestMockFlutterPlatformFactory
Definition: FlutterPlatformViewsTest.mm:70
FlutterMethodCall
Definition: FlutterCodecs.h:220
FlutterPlatformViewsTest
Definition: FlutterPlatformViewsTest.mm:133
FlutterPlatformViewGestureRecognizersBlockingPolicyEager
@ FlutterPlatformViewGestureRecognizersBlockingPolicyEager
Definition: FlutterPlugin.h:261
flutter
Definition: accessibility_bridge.h:28
FlutterPlatformViews_Internal.h
FlutterPlatformViewsTestMockFlutterPlatformView::view
UIView * view
Definition: FlutterPlatformViewsTest.mm:42
settings_
flutter::Settings settings_
Definition: FlutterEnginePlatformViewTest.mm:55
FlutterResult
void(^ FlutterResult)(id _Nullable result)
Definition: FlutterChannels.h:194
FlutterClippingMaskViewPool
Definition: FlutterPlatformViews_Internal.h:62
-[FlutterClippingMaskViewPool insertViewToPoolIfNeeded:]
void insertViewToPoolIfNeeded:(FlutterClippingMaskView *maskView)
flutter::IOSRenderingAPI::kMetal
@ kMetal
-[FlutterClippingMaskViewPool getMaskViewWithFrame:]
FlutterClippingMaskView * getMaskViewWithFrame:(CGRect frame)
ChildClippingView
Definition: FlutterPlatformViews_Internal.h:119
+[PlatformViewFilter resetPreparation]
void resetPreparation()
Definition: FlutterPlatformViews_Internal.mm:115
FlutterTouchInterceptingView
Definition: FlutterPlatformViews.mm:990
platform_view_ios.h
FlutterTouchInterceptingView_Test.h
FlutterPlatformView-p
Definition: FlutterPlatformViews.h:18
PlatformViewFilter
Definition: FlutterPlatformViews_Internal.h:80
texture_id
int64_t texture_id
Definition: texture_registrar_unittests.cc:24
flutter::IOSRenderingAPI::kSoftware
@ kSoftware
FLUTTER_ASSERT_ARC
Definition: FlutterChannelKeyResponder.mm:13
FlutterPlatformViewsTestMockFlutterPlatformView
Definition: FlutterPlatformViewsTest.mm:41
FlutterViewController.h
kFloatCompareEpsilon
const float kFloatCompareEpsilon
Definition: FlutterPlatformViewsTest.mm:21
FlutterClippingMaskView
Definition: FlutterPlatformViews_Internal.h:31