Flutter Engine Uber Docs
Docs for the entire Flutter Engine repo.
 
Loading...
Searching...
No Matches
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
7
8#import <OCMock/OCMock.h>
9#import <UIKit/UIKit.h>
10#import <WebKit/WebKit.h>
11#import <XCTest/XCTest.h>
12
13#include <memory>
14
17#include "flutter/fml/thread.h"
31
33
35__weak static UIView* gMockPlatformView = nil;
36const float kFloatCompareEpsilon = 0.001;
37
39@end
41
42- (instancetype)init {
43 self = [super init];
44 if (self) {
46 }
47 return self;
48}
49
50- (void)dealloc {
52}
53
54@end
55
56// A mock recognizer without "TouchEventsGestureRecognizer" suffix in class name.
57// This is to verify a fix to a bug on iOS 26 where web view link is not tappable.
58// We reset the web view's WKTouchEventsGestureRecognizer in a bad state
59// by disabling and re-enabling it.
60// See: https://github.com/flutter/flutter/issues/175099.
61@interface MockGestureRecognizer : UIGestureRecognizer
62@property(nonatomic, strong) NSMutableArray<NSNumber*>* toggleHistory;
63@end
64
65@implementation MockGestureRecognizer
66- (instancetype)init {
67 self = [super init];
68 if (self) {
69 _toggleHistory = [NSMutableArray array];
70 }
71 return self;
72}
73- (void)setEnabled:(BOOL)enabled {
74 [super setEnabled:enabled];
75 [self.toggleHistory addObject:@(enabled)];
76}
77@end
78
79// A mock recognizer with "TouchEventsGestureRecognizer" suffix in class name.
81@end
82
84@end
85
87@property(nonatomic, strong) UIView* view;
88@property(nonatomic, assign) BOOL viewCreated;
89@end
90
92
93- (instancetype)init {
94 if (self = [super init]) {
95 _view = [[FlutterPlatformViewsTestMockPlatformView alloc] init];
96 _viewCreated = NO;
97 }
98 return self;
99}
100
101- (UIView*)view {
102 [self checkViewCreatedOnce];
103 return _view;
104}
105
106- (void)checkViewCreatedOnce {
107 if (self.viewCreated) {
108 abort();
109 }
110 self.viewCreated = YES;
111}
112
113- (void)dealloc {
114 gMockPlatformView = nil;
115}
116@end
117
119 : NSObject <FlutterPlatformViewFactory>
120@end
121
123- (NSObject<FlutterPlatformView>*)createWithFrame:(CGRect)frame
124 viewIdentifier:(int64_t)viewId
125 arguments:(id _Nullable)args {
127}
128
129@end
130
132@property(nonatomic, strong) UIView* view;
133@property(nonatomic, assign) BOOL viewCreated;
134@end
135
137- (instancetype)init {
138 if (self = [super init]) {
139 _view = [[WKWebView alloc] init];
140 gMockPlatformView = _view;
141 _viewCreated = NO;
142 }
143 return self;
144}
145
146- (UIView*)view {
147 [self checkViewCreatedOnce];
148 return _view;
149}
150
151- (void)checkViewCreatedOnce {
152 if (self.viewCreated) {
153 abort();
154 }
155 self.viewCreated = YES;
156}
157
158- (void)dealloc {
159 gMockPlatformView = nil;
160}
161@end
162
164@end
165
167- (NSObject<FlutterPlatformView>*)createWithFrame:(CGRect)frame
168 viewIdentifier:(int64_t)viewId
169 arguments:(id _Nullable)args {
170 return [[FlutterPlatformViewsTestMockWebView alloc] init];
171}
172@end
173
175@end
176
178- (NSObject<FlutterPlatformView>*)createWithFrame:(CGRect)frame
179 viewIdentifier:(int64_t)viewId
180 arguments:(id _Nullable)args {
181 return nil;
182}
183
184@end
185
187@property(nonatomic, strong) UIView* view;
188@property(nonatomic, assign) BOOL viewCreated;
189@end
190
192- (instancetype)init {
193 if (self = [super init]) {
194 _view = [[UIView alloc] init];
195 [_view addSubview:[[WKWebView alloc] init]];
196 gMockPlatformView = _view;
197 _viewCreated = NO;
198 }
199 return self;
200}
201
202- (UIView*)view {
203 [self checkViewCreatedOnce];
204 return _view;
205}
206
207- (void)checkViewCreatedOnce {
208 if (self.viewCreated) {
209 abort();
210 }
211 self.viewCreated = YES;
212}
213
214- (void)dealloc {
215 gMockPlatformView = nil;
216}
217@end
218
220@end
221
223- (NSObject<FlutterPlatformView>*)createWithFrame:(CGRect)frame
224 viewIdentifier:(int64_t)viewId
225 arguments:(id _Nullable)args {
226 return [[FlutterPlatformViewsTestMockWrapperWebView alloc] init];
227}
228@end
229
231@property(nonatomic, strong) UIView* view;
232@property(nonatomic, assign) BOOL viewCreated;
233@end
234
236- (instancetype)init {
237 if (self = [super init]) {
238 _view = [[UIView alloc] init];
239 UIView* childView = [[UIView alloc] init];
240 [_view addSubview:childView];
241 [childView addSubview:[[WKWebView alloc] init]];
242 gMockPlatformView = _view;
243 _viewCreated = NO;
244 }
245 return self;
246}
247
248- (UIView*)view {
249 [self checkViewCreatedOnce];
250 return _view;
251}
252
253- (void)checkViewCreatedOnce {
254 if (self.viewCreated) {
255 abort();
256 }
257 self.viewCreated = YES;
258}
259@end
260
262 : NSObject <FlutterPlatformViewFactory>
263@end
264
266- (NSObject<FlutterPlatformView>*)createWithFrame:(CGRect)frame
267 viewIdentifier:(int64_t)viewId
268 arguments:(id _Nullable)args {
270}
271@end
272
273namespace flutter {
274namespace {
275class FlutterPlatformViewsTestMockPlatformViewDelegate : public PlatformView::Delegate {
276 public:
277 void OnPlatformViewCreated(std::unique_ptr<Surface> surface) override {}
278 void OnPlatformViewDestroyed() override {}
279 void OnPlatformViewScheduleFrame() override {}
280 void OnPlatformViewAddView(int64_t view_id,
281 const ViewportMetrics& viewport_metrics,
282 AddViewCallback callback) override {}
283 void OnPlatformViewRemoveView(int64_t view_id, RemoveViewCallback callback) override {}
284 void OnPlatformViewSendViewFocusEvent(const ViewFocusEvent& event) override {};
285 void OnPlatformViewSetNextFrameCallback(const fml::closure& closure) override {}
286 void OnPlatformViewSetViewportMetrics(int64_t view_id, const ViewportMetrics& metrics) override {}
287 const flutter::Settings& OnPlatformViewGetSettings() const override { return settings_; }
288 void OnPlatformViewDispatchPlatformMessage(std::unique_ptr<PlatformMessage> message) override {}
289 void OnPlatformViewDispatchPointerDataPacket(std::unique_ptr<PointerDataPacket> packet) override {
290 }
291 HitTestResponse OnPlatformViewHitTest(int64_t view_id, const flutter::PointData offset) override {
292 return {.has_platform_view = false};
293 }
294 void OnPlatformViewDispatchSemanticsAction(int64_t view_id,
295 int32_t node_id,
296 SemanticsAction action,
297 fml::MallocMapping args) override {}
298 void OnPlatformViewSetSemanticsEnabled(bool enabled) override {}
299 void OnPlatformViewSetAccessibilityFeatures(int32_t flags) override {}
300 void OnPlatformViewRegisterTexture(std::shared_ptr<Texture> texture) override {}
301 void OnPlatformViewUnregisterTexture(int64_t texture_id) override {}
302 void OnPlatformViewMarkTextureFrameAvailable(int64_t texture_id) override {}
303
304 void LoadDartDeferredLibrary(intptr_t loading_unit_id,
305 std::unique_ptr<const fml::Mapping> snapshot_data,
306 std::unique_ptr<const fml::Mapping> snapshot_instructions) override {
307 }
308 void LoadDartDeferredLibraryError(intptr_t loading_unit_id,
309 const std::string error_message,
310 bool transient) override {}
311 void UpdateAssetResolverByType(std::unique_ptr<flutter::AssetResolver> updated_asset_resolver,
313
315};
316
317BOOL BlurRadiusEqualToBlurRadius(CGFloat radius1, CGFloat radius2) {
318 const CGFloat epsilon = 0.01;
319 return std::abs(radius1 - radius2) < epsilon;
320}
321
322} // namespace
323} // namespace flutter
324
325@interface FlutterPlatformViewsTest : XCTestCase
326@end
327
328@implementation FlutterPlatformViewsTest
329
330namespace {
331using namespace flutter::testing;
332} // namespace
333
334// Walks up the superview chain from the given view and returns the first
335// FlutterTouchInterceptingView found, or nil if none exists.
336static FlutterTouchInterceptingView* FindTouchInterceptingView(UIView* view) {
337 while (view != nil && ![view isKindOfClass:[FlutterTouchInterceptingView class]]) {
338 view = view.superview;
339 }
341}
342
343// Returns the first ForwardingGestureRecognizer attached to the given view,
344// or nil if none exists.
345static UIGestureRecognizer* FindForwardingGestureRecognizer(UIView* view) {
346 for (UIGestureRecognizer* gestureRecognizer in view.gestureRecognizers) {
347 if ([gestureRecognizer isKindOfClass:[ForwardingGestureRecognizer class]]) {
348 return gestureRecognizer;
349 }
350 }
351 return nil;
352}
353
354- (void)testFlutterViewOnlyCreateOnceInOneFrame {
355 flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
356
358 FlutterPlatformViewsController* flutterPlatformViewsController =
360 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
361 /*delegate=*/mock_delegate,
362 /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
363 /*platform_views_controller=*/flutterPlatformViewsController,
364 /*task_runners=*/runners.taskRunners,
365 /*worker_task_runner=*/nil,
366 /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
367
370 [flutterPlatformViewsController
371 registerViewFactory:factory
372 withId:@"MockFlutterPlatformView"
373 gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
374 FlutterResult result = ^(id result) {
375 };
376 [flutterPlatformViewsController
378 arguments:@{
379 @"id" : @2,
380 @"viewType" : @"MockFlutterPlatformView",
381 @"gestureBlockingPolicy" : @"eager"
382 }]
383 result:result];
384 UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
385 flutterPlatformViewsController.flutterView = flutterView;
386 // Create embedded view params
388 // Layer tree always pushes a screen scale factor to the stack
389 flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
390 flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
391 stack.PushTransform(screenScaleMatrix);
392 // Push a translate matrix
393 flutter::DlMatrix translateMatrix = flutter::DlMatrix::MakeTranslation({100, 100});
394 stack.PushTransform(translateMatrix);
395 flutter::DlMatrix finalMatrix = screenScaleMatrix * translateMatrix;
396
397 auto embeddedViewParams =
398 std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, flutter::DlSize(300, 300), stack);
399
400 [flutterPlatformViewsController prerollCompositeEmbeddedView:2
401 withParams:std::move(embeddedViewParams)];
402
403 XCTAssertNotNil(gMockPlatformView);
404
405 [flutterPlatformViewsController reset];
406}
407
408- (void)testCanCreatePlatformViewWithoutFlutterView {
409 flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
410
412 FlutterPlatformViewsController* flutterPlatformViewsController =
414 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
415 /*delegate=*/mock_delegate,
416 /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
417 /*platform_views_controller=*/flutterPlatformViewsController,
418 /*task_runners=*/runners.taskRunners,
419 /*worker_task_runner=*/nil,
420 /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
421
424 [flutterPlatformViewsController
425 registerViewFactory:factory
426 withId:@"MockFlutterPlatformView"
427 gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
428 FlutterResult result = ^(id result) {
429 };
430 [flutterPlatformViewsController
432 arguments:@{
433 @"id" : @2,
434 @"viewType" : @"MockFlutterPlatformView",
435 @"gestureBlockingPolicy" : @"eager"
436 }]
437 result:result];
438
439 XCTAssertNotNil(gMockPlatformView);
440}
441
442- (void)testChildClippingViewHitTests {
443 ChildClippingView* childClippingView =
444 [[ChildClippingView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
445 UIView* childView = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
446 [childClippingView addSubview:childView];
447
448 XCTAssertFalse([childClippingView pointInside:CGPointMake(50, 50) withEvent:nil]);
449 XCTAssertFalse([childClippingView pointInside:CGPointMake(99, 100) withEvent:nil]);
450 XCTAssertFalse([childClippingView pointInside:CGPointMake(100, 99) withEvent:nil]);
451 XCTAssertFalse([childClippingView pointInside:CGPointMake(201, 200) withEvent:nil]);
452 XCTAssertFalse([childClippingView pointInside:CGPointMake(200, 201) withEvent:nil]);
453 XCTAssertFalse([childClippingView pointInside:CGPointMake(99, 200) withEvent:nil]);
454 XCTAssertFalse([childClippingView pointInside:CGPointMake(200, 299) withEvent:nil]);
455
456 XCTAssertTrue([childClippingView pointInside:CGPointMake(150, 150) withEvent:nil]);
457 XCTAssertTrue([childClippingView pointInside:CGPointMake(100, 100) withEvent:nil]);
458 XCTAssertTrue([childClippingView pointInside:CGPointMake(199, 100) withEvent:nil]);
459 XCTAssertTrue([childClippingView pointInside:CGPointMake(100, 199) withEvent:nil]);
460 XCTAssertTrue([childClippingView pointInside:CGPointMake(199, 199) withEvent:nil]);
461}
462
463- (void)testReleasesBackdropFilterSubviewsOnChildClippingViewDealloc {
464 __weak NSMutableArray<UIVisualEffectView*>* weakBackdropFilterSubviews = nil;
465 __weak UIVisualEffectView* weakVisualEffectView1 = nil;
466 __weak UIVisualEffectView* weakVisualEffectView2 = nil;
467
468 @autoreleasepool {
469 ChildClippingView* clippingView = [[ChildClippingView alloc] initWithFrame:CGRectZero];
470 UIVisualEffectView* visualEffectView1 = [[UIVisualEffectView alloc]
471 initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]];
472 weakVisualEffectView1 = visualEffectView1;
473 PlatformViewFilter* platformViewFilter1 =
474 [[PlatformViewFilter alloc] initWithFrame:CGRectMake(0, 0, 10, 10)
475 blurRadius:5
476 cornerRadius:0
477 isRoundedSuperellipse:NO
478 visualEffectView:visualEffectView1];
479
480 [clippingView applyBlurBackdropFilters:@[ platformViewFilter1 ]];
481
482 // Replace the blur filter to validate the original and new UIVisualEffectView are released.
483 UIVisualEffectView* visualEffectView2 = [[UIVisualEffectView alloc]
484 initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleDark]];
485 weakVisualEffectView2 = visualEffectView2;
486 PlatformViewFilter* platformViewFilter2 =
487 [[PlatformViewFilter alloc] initWithFrame:CGRectMake(0, 0, 10, 10)
488 blurRadius:5
489 cornerRadius:0
490 isRoundedSuperellipse:NO
491 visualEffectView:visualEffectView2];
492 [clippingView applyBlurBackdropFilters:@[ platformViewFilter2 ]];
493
494 weakBackdropFilterSubviews = clippingView.backdropFilterSubviews;
495 XCTAssertNotNil(weakBackdropFilterSubviews);
496 clippingView = nil;
497 }
498 XCTAssertNil(weakBackdropFilterSubviews);
499 XCTAssertNil(weakVisualEffectView1);
500 XCTAssertNil(weakVisualEffectView2);
501}
502
503- (void)testApplyBackdropFilter {
504 flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
505
507 FlutterPlatformViewsController* flutterPlatformViewsController =
509 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
510 /*delegate=*/mock_delegate,
511 /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
512 /*platform_views_controller=*/flutterPlatformViewsController,
513 /*task_runners=*/runners.taskRunners,
514 /*worker_task_runner=*/nil,
515 /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
516
519 [flutterPlatformViewsController
520 registerViewFactory:factory
521 withId:@"MockFlutterPlatformView"
522 gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
523 FlutterResult result = ^(id result) {
524 };
525 [flutterPlatformViewsController
527 arguments:@{
528 @"id" : @2,
529 @"viewType" : @"MockFlutterPlatformView",
530 @"gestureBlockingPolicy" : @"eager"
531 }]
532 result:result];
533
534 XCTAssertNotNil(gMockPlatformView);
535
536 UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
537 flutterPlatformViewsController.flutterView = flutterView;
538 // Create embedded view params
540 // Layer tree always pushes a screen scale factor to the stack
541 flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
542 flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
543 stack.PushTransform(screenScaleMatrix);
544 // Push a backdrop filter
546 stack.PushBackdropFilter(filter,
547 flutter::DlRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
548
549 auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
550 screenScaleMatrix, flutter::DlSize(10, 10), stack);
551
552 [flutterPlatformViewsController prerollCompositeEmbeddedView:2
553 withParams:std::move(embeddedViewParams)];
554 [flutterPlatformViewsController
556 withParams:[flutterPlatformViewsController compositionParamsForView:2]];
557
558 XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:[ChildClippingView class]]);
559 ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
560 [flutterView addSubview:childClippingView];
561
562 [flutterView setNeedsLayout];
563 [flutterView layoutIfNeeded];
564
565 // childClippingView has visual effect view with the correct configurations.
566 NSUInteger numberOfExpectedVisualEffectView = 0;
567 for (UIView* subview in childClippingView.subviews) {
568 if (![subview isKindOfClass:[UIVisualEffectView class]]) {
569 continue;
570 }
571 XCTAssertLessThan(numberOfExpectedVisualEffectView, 1u);
572 if ([self validateOneVisualEffectView:subview
573 expectedFrame:CGRectMake(0, 0, 10, 10)
574 inputRadius:5]) {
575 numberOfExpectedVisualEffectView++;
576 }
577 }
578 XCTAssertEqual(numberOfExpectedVisualEffectView, 1u);
579}
580
581- (void)testApplyBackdropFilterWithCorrectFrame {
582 flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
583
585 FlutterPlatformViewsController* flutterPlatformViewsController =
587 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
588 /*delegate=*/mock_delegate,
589 /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
590 /*platform_views_controller=*/flutterPlatformViewsController,
591 /*task_runners=*/runners.taskRunners,
592 /*worker_task_runner=*/nil,
593 /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
594
597 [flutterPlatformViewsController
598 registerViewFactory:factory
599 withId:@"MockFlutterPlatformView"
600 gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
601 FlutterResult result = ^(id result) {
602 };
603 [flutterPlatformViewsController
605 arguments:@{
606 @"id" : @2,
607 @"viewType" : @"MockFlutterPlatformView",
608 @"gestureBlockingPolicy" : @"eager"
609 }]
610 result:result];
611
612 XCTAssertNotNil(gMockPlatformView);
613
614 UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
615 flutterPlatformViewsController.flutterView = flutterView;
616 // Create embedded view params
618 // Layer tree always pushes a screen scale factor to the stack
619 flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
620 flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
621 stack.PushTransform(screenScaleMatrix);
622 // Push a backdrop filter
624 stack.PushBackdropFilter(filter,
625 flutter::DlRect::MakeXYWH(0, 0, screenScale * 8, screenScale * 8));
626
627 auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
628 screenScaleMatrix, flutter::DlSize(5, 10), stack);
629
630 [flutterPlatformViewsController prerollCompositeEmbeddedView:2
631 withParams:std::move(embeddedViewParams)];
632 [flutterPlatformViewsController
634 withParams:[flutterPlatformViewsController compositionParamsForView:2]];
635
636 XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:[ChildClippingView class]]);
637 ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
638 [flutterView addSubview:childClippingView];
639
640 [flutterView setNeedsLayout];
641 [flutterView layoutIfNeeded];
642
643 // childClippingView has visual effect view with the correct configurations.
644 NSUInteger numberOfExpectedVisualEffectView = 0;
645 for (UIView* subview in childClippingView.subviews) {
646 if (![subview isKindOfClass:[UIVisualEffectView class]]) {
647 continue;
648 }
649 XCTAssertLessThan(numberOfExpectedVisualEffectView, 1u);
650 if ([self validateOneVisualEffectView:subview
651 expectedFrame:CGRectMake(0, 0, 5, 8)
652 inputRadius:5]) {
653 numberOfExpectedVisualEffectView++;
654 }
655 }
656 XCTAssertEqual(numberOfExpectedVisualEffectView, 1u);
657}
658
659- (void)testApplyMultipleBackdropFilters {
660 flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
661
663 FlutterPlatformViewsController* flutterPlatformViewsController =
665 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
666 /*delegate=*/mock_delegate,
667 /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
668 /*platform_views_controller=*/flutterPlatformViewsController,
669 /*task_runners=*/runners.taskRunners,
670 /*worker_task_runner=*/nil,
671 /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
672
675 [flutterPlatformViewsController
676 registerViewFactory:factory
677 withId:@"MockFlutterPlatformView"
678 gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
679 FlutterResult result = ^(id result) {
680 };
681 [flutterPlatformViewsController
683 arguments:@{
684 @"id" : @2,
685 @"viewType" : @"MockFlutterPlatformView",
686 @"gestureBlockingPolicy" : @"eager"
687 }]
688 result:result];
689
690 XCTAssertNotNil(gMockPlatformView);
691
692 UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
693 flutterPlatformViewsController.flutterView = flutterView;
694 // Create embedded view params
696 // Layer tree always pushes a screen scale factor to the stack
697 flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
698 flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
699 stack.PushTransform(screenScaleMatrix);
700 // Push backdrop filters
701 for (int i = 0; i < 50; i++) {
703 stack.PushBackdropFilter(filter,
704 flutter::DlRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
705 }
706
707 auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
708 screenScaleMatrix, flutter::DlSize(20, 20), stack);
709
710 [flutterPlatformViewsController prerollCompositeEmbeddedView:2
711 withParams:std::move(embeddedViewParams)];
712 [flutterPlatformViewsController
714 withParams:[flutterPlatformViewsController compositionParamsForView:2]];
715
716 XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]);
717 ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
718 [flutterView addSubview:childClippingView];
719
720 [flutterView setNeedsLayout];
721 [flutterView layoutIfNeeded];
722
723 NSUInteger numberOfExpectedVisualEffectView = 0;
724 for (UIView* subview in childClippingView.subviews) {
725 if (![subview isKindOfClass:[UIVisualEffectView class]]) {
726 continue;
727 }
728 XCTAssertLessThan(numberOfExpectedVisualEffectView, 50u);
729 if ([self validateOneVisualEffectView:subview
730 expectedFrame:CGRectMake(0, 0, 10, 10)
731 inputRadius:(CGFloat)numberOfExpectedVisualEffectView]) {
732 numberOfExpectedVisualEffectView++;
733 }
734 }
735 XCTAssertEqual(numberOfExpectedVisualEffectView, (NSUInteger)numberOfExpectedVisualEffectView);
736}
737
738- (void)testAddBackdropFilters {
739 flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
740
742 FlutterPlatformViewsController* flutterPlatformViewsController =
743 [[FlutterPlatformViewsController alloc] init];
744 flutterPlatformViewsController.taskRunner = flutter::testing::GetDefaultTaskRunner();
745 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
746 /*delegate=*/mock_delegate,
747 /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
748 /*platform_views_controller=*/flutterPlatformViewsController,
749 /*task_runners=*/runners.taskRunners,
750 /*worker_task_runner=*/nil,
751 /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
752
755 [flutterPlatformViewsController
756 registerViewFactory:factory
757 withId:@"MockFlutterPlatformView"
758 gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
759 FlutterResult result = ^(id result) {
760 };
761 [flutterPlatformViewsController
763 arguments:@{
764 @"id" : @2,
765 @"viewType" : @"MockFlutterPlatformView",
766 @"gestureBlockingPolicy" : @"eager"
767 }]
768 result:result];
769
770 XCTAssertNotNil(gMockPlatformView);
771
772 UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
773 flutterPlatformViewsController.flutterView = flutterView;
774 // Create embedded view params
776 // Layer tree always pushes a screen scale factor to the stack
777 flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
778 flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
779 stack.PushTransform(screenScaleMatrix);
780 // Push a backdrop filter
782 stack.PushBackdropFilter(filter,
783 flutter::DlRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
784
785 auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
786 screenScaleMatrix, flutter::DlSize(10, 10), stack);
787
788 [flutterPlatformViewsController prerollCompositeEmbeddedView:2
789 withParams:std::move(embeddedViewParams)];
790 [flutterPlatformViewsController
792 withParams:[flutterPlatformViewsController compositionParamsForView:2]];
793
794 XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:[ChildClippingView class]]);
795 ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
796 [flutterView addSubview:childClippingView];
797
798 [flutterView setNeedsLayout];
799 [flutterView layoutIfNeeded];
800
801 NSMutableArray* originalVisualEffectViews = [[NSMutableArray alloc] init];
802 for (UIView* subview in childClippingView.subviews) {
803 if (![subview isKindOfClass:[UIVisualEffectView class]]) {
804 continue;
805 }
806 XCTAssertLessThan(originalVisualEffectViews.count, 1u);
807 if ([self validateOneVisualEffectView:subview
808 expectedFrame:CGRectMake(0, 0, 10, 10)
809 inputRadius:(CGFloat)5]) {
810 [originalVisualEffectViews addObject:subview];
811 }
812 }
813 XCTAssertEqual(originalVisualEffectViews.count, 1u);
814
815 //
816 // Simulate adding 1 backdrop filter (create a new mutators stack)
817 // Create embedded view params
819 // Layer tree always pushes a screen scale factor to the stack
820 stack2.PushTransform(screenScaleMatrix);
821 // Push backdrop filters
822 for (int i = 0; i < 2; i++) {
823 stack2.PushBackdropFilter(filter,
824 flutter::DlRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
825 }
826
827 embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
828 screenScaleMatrix, flutter::DlSize(10, 10), stack2);
829
830 [flutterPlatformViewsController prerollCompositeEmbeddedView:2
831 withParams:std::move(embeddedViewParams)];
832 [flutterPlatformViewsController
834 withParams:[flutterPlatformViewsController compositionParamsForView:2]];
835
836 [flutterView setNeedsLayout];
837 [flutterView layoutIfNeeded];
838
839 NSMutableArray* newVisualEffectViews = [[NSMutableArray alloc] init];
840 for (UIView* subview in childClippingView.subviews) {
841 if (![subview isKindOfClass:[UIVisualEffectView class]]) {
842 continue;
843 }
844 XCTAssertLessThan(newVisualEffectViews.count, 2u);
845
846 if ([self validateOneVisualEffectView:subview
847 expectedFrame:CGRectMake(0, 0, 10, 10)
848 inputRadius:(CGFloat)5]) {
849 [newVisualEffectViews addObject:subview];
850 }
851 }
852 XCTAssertEqual(newVisualEffectViews.count, 2u);
853 for (NSUInteger i = 0; i < originalVisualEffectViews.count; i++) {
854 UIView* originalView = originalVisualEffectViews[i];
855 UIView* newView = newVisualEffectViews[i];
856 // Compare reference.
857 XCTAssertEqual(originalView, newView);
858 id mockOrignalView = OCMPartialMock(originalView);
859 OCMReject([mockOrignalView removeFromSuperview]);
860 [mockOrignalView stopMocking];
861 }
862}
863
864- (void)testRemoveBackdropFilters {
865 flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
866
868 FlutterPlatformViewsController* flutterPlatformViewsController =
869 [[FlutterPlatformViewsController alloc] init];
870 flutterPlatformViewsController.taskRunner = flutter::testing::GetDefaultTaskRunner();
871 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
872 /*delegate=*/mock_delegate,
873 /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
874 /*platform_views_controller=*/flutterPlatformViewsController,
875 /*task_runners=*/runners.taskRunners,
876 /*worker_task_runner=*/nil,
877 /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
878
881 [flutterPlatformViewsController
882 registerViewFactory:factory
883 withId:@"MockFlutterPlatformView"
884 gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
885 FlutterResult result = ^(id result) {
886 };
887 [flutterPlatformViewsController
889 arguments:@{
890 @"id" : @2,
891 @"viewType" : @"MockFlutterPlatformView",
892 @"gestureBlockingPolicy" : @"eager"
893 }]
894 result:result];
895
896 XCTAssertNotNil(gMockPlatformView);
897
898 UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
899 flutterPlatformViewsController.flutterView = flutterView;
900 // Create embedded view params
902 // Layer tree always pushes a screen scale factor to the stack
903 flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
904 flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
905 stack.PushTransform(screenScaleMatrix);
906 // Push backdrop filters
908 for (int i = 0; i < 5; i++) {
909 stack.PushBackdropFilter(filter,
910 flutter::DlRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
911 }
912
913 auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
914 screenScaleMatrix, flutter::DlSize(10, 10), stack);
915
916 [flutterPlatformViewsController prerollCompositeEmbeddedView:2
917 withParams:std::move(embeddedViewParams)];
918 [flutterPlatformViewsController
920 withParams:[flutterPlatformViewsController compositionParamsForView:2]];
921
922 XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]);
923 ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
924 [flutterView addSubview:childClippingView];
925
926 [flutterView setNeedsLayout];
927 [flutterView layoutIfNeeded];
928
929 NSMutableArray* originalVisualEffectViews = [[NSMutableArray alloc] init];
930 for (UIView* subview in childClippingView.subviews) {
931 if (![subview isKindOfClass:[UIVisualEffectView class]]) {
932 continue;
933 }
934 XCTAssertLessThan(originalVisualEffectViews.count, 5u);
935 if ([self validateOneVisualEffectView:subview
936 expectedFrame:CGRectMake(0, 0, 10, 10)
937 inputRadius:(CGFloat)5]) {
938 [originalVisualEffectViews addObject:subview];
939 }
940 }
941
942 // Simulate removing 1 backdrop filter (create a new mutators stack)
943 // Create embedded view params
945 // Layer tree always pushes a screen scale factor to the stack
946 stack2.PushTransform(screenScaleMatrix);
947 // Push backdrop filters
948 for (int i = 0; i < 4; i++) {
949 stack2.PushBackdropFilter(filter,
950 flutter::DlRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
951 }
952
953 embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
954 screenScaleMatrix, flutter::DlSize(10, 10), stack2);
955
956 [flutterPlatformViewsController prerollCompositeEmbeddedView:2
957 withParams:std::move(embeddedViewParams)];
958 [flutterPlatformViewsController
960 withParams:[flutterPlatformViewsController compositionParamsForView:2]];
961
962 [flutterView setNeedsLayout];
963 [flutterView layoutIfNeeded];
964
965 NSMutableArray* newVisualEffectViews = [[NSMutableArray alloc] init];
966 for (UIView* subview in childClippingView.subviews) {
967 if (![subview isKindOfClass:[UIVisualEffectView class]]) {
968 continue;
969 }
970 XCTAssertLessThan(newVisualEffectViews.count, 4u);
971 if ([self validateOneVisualEffectView:subview
972 expectedFrame:CGRectMake(0, 0, 10, 10)
973 inputRadius:(CGFloat)5]) {
974 [newVisualEffectViews addObject:subview];
975 }
976 }
977 XCTAssertEqual(newVisualEffectViews.count, 4u);
978
979 for (NSUInteger i = 0; i < newVisualEffectViews.count; i++) {
980 UIView* newView = newVisualEffectViews[i];
981 id mockNewView = OCMPartialMock(newView);
982 UIView* originalView = originalVisualEffectViews[i];
983 // Compare reference.
984 XCTAssertEqual(originalView, newView);
985 OCMReject([mockNewView removeFromSuperview]);
986 [mockNewView stopMocking];
987 }
988
989 // Simulate removing all backdrop filters (replace the mutators stack)
990 // Update embedded view params, delete except screenScaleMatrix
991 for (int i = 0; i < 5; i++) {
992 stack2.Pop();
993 }
994 // No backdrop filters in the stack, so no nothing to push
995
996 embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
997 screenScaleMatrix, flutter::DlSize(10, 10), stack2);
998
999 [flutterPlatformViewsController prerollCompositeEmbeddedView:2
1000 withParams:std::move(embeddedViewParams)];
1001 [flutterPlatformViewsController
1003 withParams:[flutterPlatformViewsController compositionParamsForView:2]];
1004
1005 [flutterView setNeedsLayout];
1006 [flutterView layoutIfNeeded];
1007
1008 NSUInteger numberOfExpectedVisualEffectView = 0u;
1009 for (UIView* subview in childClippingView.subviews) {
1010 if ([subview isKindOfClass:[UIVisualEffectView class]]) {
1011 numberOfExpectedVisualEffectView++;
1012 }
1013 }
1014 XCTAssertEqual(numberOfExpectedVisualEffectView, 0u);
1015}
1016
1017- (void)testEditBackdropFilters {
1018 flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
1019
1021 FlutterPlatformViewsController* flutterPlatformViewsController =
1022 [[FlutterPlatformViewsController alloc] init];
1023 flutterPlatformViewsController.taskRunner = flutter::testing::GetDefaultTaskRunner();
1024 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1025 /*delegate=*/mock_delegate,
1026 /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
1027 /*platform_views_controller=*/flutterPlatformViewsController,
1028 /*task_runners=*/runners.taskRunners,
1029 /*worker_task_runner=*/nil,
1030 /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
1031
1034 [flutterPlatformViewsController
1035 registerViewFactory:factory
1036 withId:@"MockFlutterPlatformView"
1037 gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
1038 FlutterResult result = ^(id result) {
1039 };
1040 [flutterPlatformViewsController
1042 arguments:@{
1043 @"id" : @2,
1044 @"viewType" : @"MockFlutterPlatformView",
1045 @"gestureBlockingPolicy" : @"eager"
1046 }]
1047 result:result];
1048
1049 XCTAssertNotNil(gMockPlatformView);
1050
1051 UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
1052 flutterPlatformViewsController.flutterView = flutterView;
1053 // Create embedded view params
1055 // Layer tree always pushes a screen scale factor to the stack
1056 flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
1057 flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
1058 stack.PushTransform(screenScaleMatrix);
1059 // Push backdrop filters
1061 for (int i = 0; i < 5; i++) {
1062 stack.PushBackdropFilter(filter,
1063 flutter::DlRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
1064 }
1065
1066 auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
1067 screenScaleMatrix, flutter::DlSize(10, 10), stack);
1068
1069 [flutterPlatformViewsController prerollCompositeEmbeddedView:2
1070 withParams:std::move(embeddedViewParams)];
1071 [flutterPlatformViewsController
1073 withParams:[flutterPlatformViewsController compositionParamsForView:2]];
1074
1075 XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]);
1076 ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
1077 [flutterView addSubview:childClippingView];
1078
1079 [flutterView setNeedsLayout];
1080 [flutterView layoutIfNeeded];
1081
1082 NSMutableArray* originalVisualEffectViews = [[NSMutableArray alloc] init];
1083 for (UIView* subview in childClippingView.subviews) {
1084 if (![subview isKindOfClass:[UIVisualEffectView class]]) {
1085 continue;
1086 }
1087 XCTAssertLessThan(originalVisualEffectViews.count, 5u);
1088 if ([self validateOneVisualEffectView:subview
1089 expectedFrame:CGRectMake(0, 0, 10, 10)
1090 inputRadius:(CGFloat)5]) {
1091 [originalVisualEffectViews addObject:subview];
1092 }
1093 }
1094
1095 // Simulate editing 1 backdrop filter in the middle of the stack (create a new mutators stack)
1096 // Create embedded view params
1098 // Layer tree always pushes a screen scale factor to the stack
1099 stack2.PushTransform(screenScaleMatrix);
1100 // Push backdrop filters
1101 for (int i = 0; i < 5; i++) {
1102 if (i == 3) {
1104
1105 stack2.PushBackdropFilter(
1106 filter2, flutter::DlRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
1107 continue;
1108 }
1109
1110 stack2.PushBackdropFilter(filter,
1111 flutter::DlRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
1112 }
1113
1114 embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
1115 screenScaleMatrix, flutter::DlSize(10, 10), stack2);
1116
1117 [flutterPlatformViewsController prerollCompositeEmbeddedView:2
1118 withParams:std::move(embeddedViewParams)];
1119 [flutterPlatformViewsController
1121 withParams:[flutterPlatformViewsController compositionParamsForView:2]];
1122
1123 [flutterView setNeedsLayout];
1124 [flutterView layoutIfNeeded];
1125
1126 NSMutableArray* newVisualEffectViews = [[NSMutableArray alloc] init];
1127 for (UIView* subview in childClippingView.subviews) {
1128 if (![subview isKindOfClass:[UIVisualEffectView class]]) {
1129 continue;
1130 }
1131 XCTAssertLessThan(newVisualEffectViews.count, 5u);
1132 CGFloat expectInputRadius = 5;
1133 if (newVisualEffectViews.count == 3) {
1134 expectInputRadius = 2;
1135 }
1136 if ([self validateOneVisualEffectView:subview
1137 expectedFrame:CGRectMake(0, 0, 10, 10)
1138 inputRadius:expectInputRadius]) {
1139 [newVisualEffectViews addObject:subview];
1140 }
1141 }
1142 XCTAssertEqual(newVisualEffectViews.count, 5u);
1143 for (NSUInteger i = 0; i < newVisualEffectViews.count; i++) {
1144 UIView* newView = newVisualEffectViews[i];
1145 id mockNewView = OCMPartialMock(newView);
1146 UIView* originalView = originalVisualEffectViews[i];
1147 // Compare reference.
1148 XCTAssertEqual(originalView, newView);
1149 OCMReject([mockNewView removeFromSuperview]);
1150 [mockNewView stopMocking];
1151 }
1152 [newVisualEffectViews removeAllObjects];
1153
1154 // Simulate editing 1 backdrop filter in the beginning of the stack (replace the mutators stack)
1155 // Update embedded view params, delete except screenScaleMatrix
1156 for (int i = 0; i < 5; i++) {
1157 stack2.Pop();
1158 }
1159 // Push backdrop filters
1160 for (int i = 0; i < 5; i++) {
1161 if (i == 0) {
1163 stack2.PushBackdropFilter(
1164 filter2, flutter::DlRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
1165 continue;
1166 }
1167
1168 stack2.PushBackdropFilter(filter,
1169 flutter::DlRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
1170 }
1171
1172 embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
1173 screenScaleMatrix, flutter::DlSize(10, 10), stack2);
1174
1175 [flutterPlatformViewsController prerollCompositeEmbeddedView:2
1176 withParams:std::move(embeddedViewParams)];
1177 [flutterPlatformViewsController
1179 withParams:[flutterPlatformViewsController compositionParamsForView:2]];
1180
1181 [flutterView setNeedsLayout];
1182 [flutterView layoutIfNeeded];
1183
1184 for (UIView* subview in childClippingView.subviews) {
1185 if (![subview isKindOfClass:[UIVisualEffectView class]]) {
1186 continue;
1187 }
1188 XCTAssertLessThan(newVisualEffectViews.count, 5u);
1189 CGFloat expectInputRadius = 5;
1190 if (newVisualEffectViews.count == 0) {
1191 expectInputRadius = 2;
1192 }
1193 if ([self validateOneVisualEffectView:subview
1194 expectedFrame:CGRectMake(0, 0, 10, 10)
1195 inputRadius:expectInputRadius]) {
1196 [newVisualEffectViews addObject:subview];
1197 }
1198 }
1199 for (NSUInteger i = 0; i < newVisualEffectViews.count; i++) {
1200 UIView* newView = newVisualEffectViews[i];
1201 id mockNewView = OCMPartialMock(newView);
1202 UIView* originalView = originalVisualEffectViews[i];
1203 // Compare reference.
1204 XCTAssertEqual(originalView, newView);
1205 OCMReject([mockNewView removeFromSuperview]);
1206 [mockNewView stopMocking];
1207 }
1208 [newVisualEffectViews removeAllObjects];
1209
1210 // Simulate editing 1 backdrop filter in the end of the stack (replace the mutators stack)
1211 // Update embedded view params, delete except screenScaleMatrix
1212 for (int i = 0; i < 5; i++) {
1213 stack2.Pop();
1214 }
1215 // Push backdrop filters
1216 for (int i = 0; i < 5; i++) {
1217 if (i == 4) {
1219 stack2.PushBackdropFilter(
1220 filter2, flutter::DlRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
1221 continue;
1222 }
1223
1224 stack2.PushBackdropFilter(filter,
1225 flutter::DlRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
1226 }
1227
1228 embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
1229 screenScaleMatrix, flutter::DlSize(10, 10), stack2);
1230
1231 [flutterPlatformViewsController prerollCompositeEmbeddedView:2
1232 withParams:std::move(embeddedViewParams)];
1233 [flutterPlatformViewsController
1235 withParams:[flutterPlatformViewsController compositionParamsForView:2]];
1236
1237 [flutterView setNeedsLayout];
1238 [flutterView layoutIfNeeded];
1239
1240 for (UIView* subview in childClippingView.subviews) {
1241 if (![subview isKindOfClass:[UIVisualEffectView class]]) {
1242 continue;
1243 }
1244 XCTAssertLessThan(newVisualEffectViews.count, 5u);
1245 CGFloat expectInputRadius = 5;
1246 if (newVisualEffectViews.count == 4) {
1247 expectInputRadius = 2;
1248 }
1249 if ([self validateOneVisualEffectView:subview
1250 expectedFrame:CGRectMake(0, 0, 10, 10)
1251 inputRadius:expectInputRadius]) {
1252 [newVisualEffectViews addObject:subview];
1253 }
1254 }
1255 XCTAssertEqual(newVisualEffectViews.count, 5u);
1256
1257 for (NSUInteger i = 0; i < newVisualEffectViews.count; i++) {
1258 UIView* newView = newVisualEffectViews[i];
1259 id mockNewView = OCMPartialMock(newView);
1260 UIView* originalView = originalVisualEffectViews[i];
1261 // Compare reference.
1262 XCTAssertEqual(originalView, newView);
1263 OCMReject([mockNewView removeFromSuperview]);
1264 [mockNewView stopMocking];
1265 }
1266 [newVisualEffectViews removeAllObjects];
1267
1268 // Simulate editing all backdrop filters in the stack (replace the mutators stack)
1269 // Update embedded view params, delete except screenScaleMatrix
1270 for (int i = 0; i < 5; i++) {
1271 stack2.Pop();
1272 }
1273 // Push backdrop filters
1274 for (int i = 0; i < 5; i++) {
1276
1277 stack2.PushBackdropFilter(filter2,
1278 flutter::DlRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
1279 }
1280
1281 embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
1282 screenScaleMatrix, flutter::DlSize(10, 10), stack2);
1283
1284 [flutterPlatformViewsController prerollCompositeEmbeddedView:2
1285 withParams:std::move(embeddedViewParams)];
1286 [flutterPlatformViewsController
1288 withParams:[flutterPlatformViewsController compositionParamsForView:2]];
1289
1290 [flutterView setNeedsLayout];
1291 [flutterView layoutIfNeeded];
1292
1293 for (UIView* subview in childClippingView.subviews) {
1294 if (![subview isKindOfClass:[UIVisualEffectView class]]) {
1295 continue;
1296 }
1297 XCTAssertLessThan(newVisualEffectViews.count, 5u);
1298 if ([self validateOneVisualEffectView:subview
1299 expectedFrame:CGRectMake(0, 0, 10, 10)
1300 inputRadius:(CGFloat)newVisualEffectViews.count]) {
1301 [newVisualEffectViews addObject:subview];
1302 }
1303 }
1304 XCTAssertEqual(newVisualEffectViews.count, 5u);
1305
1306 for (NSUInteger i = 0; i < newVisualEffectViews.count; i++) {
1307 UIView* newView = newVisualEffectViews[i];
1308 id mockNewView = OCMPartialMock(newView);
1309 UIView* originalView = originalVisualEffectViews[i];
1310 // Compare reference.
1311 XCTAssertEqual(originalView, newView);
1312 OCMReject([mockNewView removeFromSuperview]);
1313 [mockNewView stopMocking];
1314 }
1315 [newVisualEffectViews removeAllObjects];
1316}
1317
1318- (void)testApplyBackdropFilterNotDlBlurImageFilter {
1319 flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
1320
1322 FlutterPlatformViewsController* flutterPlatformViewsController =
1323 [[FlutterPlatformViewsController alloc] init];
1324 flutterPlatformViewsController.taskRunner = flutter::testing::GetDefaultTaskRunner();
1325 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1326 /*delegate=*/mock_delegate,
1327 /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
1328 /*platform_views_controller=*/flutterPlatformViewsController,
1329 /*task_runners=*/runners.taskRunners,
1330 /*worker_task_runner=*/nil,
1331 /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
1332
1335 [flutterPlatformViewsController
1336 registerViewFactory:factory
1337 withId:@"MockFlutterPlatformView"
1338 gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
1339 FlutterResult result = ^(id result) {
1340 };
1341 [flutterPlatformViewsController
1343 arguments:@{
1344 @"id" : @2,
1345 @"viewType" : @"MockFlutterPlatformView",
1346 @"gestureBlockingPolicy" : @"eager"
1347 }]
1348 result:result];
1349
1350 XCTAssertNotNil(gMockPlatformView);
1351
1352 UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
1353 flutterPlatformViewsController.flutterView = flutterView;
1354 // Create embedded view params
1356 // Layer tree always pushes a screen scale factor to the stack
1357 flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
1358 flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
1359 stack.PushTransform(screenScaleMatrix);
1360 // Push a dilate backdrop filter
1361 auto dilateFilter = flutter::DlDilateImageFilter::Make(5, 2);
1362 stack.PushBackdropFilter(dilateFilter, flutter::DlRect());
1363
1364 auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
1365 screenScaleMatrix, flutter::DlSize(10, 10), stack);
1366
1367 [flutterPlatformViewsController prerollCompositeEmbeddedView:2
1368 withParams:std::move(embeddedViewParams)];
1369 [flutterPlatformViewsController
1371 withParams:[flutterPlatformViewsController compositionParamsForView:2]];
1372
1373 XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:[ChildClippingView class]]);
1374 ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
1375
1376 [flutterView addSubview:childClippingView];
1377
1378 [flutterView setNeedsLayout];
1379 [flutterView layoutIfNeeded];
1380
1381 NSUInteger numberOfExpectedVisualEffectView = 0;
1382 for (UIView* subview in childClippingView.subviews) {
1383 if ([subview isKindOfClass:[UIVisualEffectView class]]) {
1384 numberOfExpectedVisualEffectView++;
1385 }
1386 }
1387 XCTAssertEqual(numberOfExpectedVisualEffectView, 0u);
1388
1389 // Simulate adding a non-DlBlurImageFilter in the middle of the stack (create a new mutators
1390 // stack) Create embedded view params
1392 // Layer tree always pushes a screen scale factor to the stack
1393 stack2.PushTransform(screenScaleMatrix);
1394 // Push backdrop filters and dilate filter
1396
1397 for (int i = 0; i < 5; i++) {
1398 if (i == 2) {
1399 stack2.PushBackdropFilter(
1400 dilateFilter, flutter::DlRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
1401 continue;
1402 }
1403
1404 stack2.PushBackdropFilter(blurFilter,
1405 flutter::DlRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
1406 }
1407
1408 embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
1409 screenScaleMatrix, flutter::DlSize(10, 10), stack2);
1410
1411 [flutterPlatformViewsController prerollCompositeEmbeddedView:2
1412 withParams:std::move(embeddedViewParams)];
1413 [flutterPlatformViewsController
1415 withParams:[flutterPlatformViewsController compositionParamsForView:2]];
1416
1417 [flutterView setNeedsLayout];
1418 [flutterView layoutIfNeeded];
1419
1420 numberOfExpectedVisualEffectView = 0;
1421 for (UIView* subview in childClippingView.subviews) {
1422 if (![subview isKindOfClass:[UIVisualEffectView class]]) {
1423 continue;
1424 }
1425 XCTAssertLessThan(numberOfExpectedVisualEffectView, 4u);
1426 if ([self validateOneVisualEffectView:subview
1427 expectedFrame:CGRectMake(0, 0, 10, 10)
1428 inputRadius:(CGFloat)5]) {
1429 numberOfExpectedVisualEffectView++;
1430 }
1431 }
1432 XCTAssertEqual(numberOfExpectedVisualEffectView, 4u);
1433
1434 // Simulate adding a non-DlBlurImageFilter to the beginning of the stack (replace the mutators
1435 // stack) Update embedded view params, delete except screenScaleMatrix
1436 for (int i = 0; i < 5; i++) {
1437 stack2.Pop();
1438 }
1439 // Push backdrop filters and dilate filter
1440 for (int i = 0; i < 5; i++) {
1441 if (i == 0) {
1442 stack2.PushBackdropFilter(
1443 dilateFilter, flutter::DlRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
1444 continue;
1445 }
1446
1447 stack2.PushBackdropFilter(blurFilter,
1448 flutter::DlRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
1449 }
1450
1451 embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
1452 screenScaleMatrix, flutter::DlSize(10, 10), stack2);
1453
1454 [flutterPlatformViewsController prerollCompositeEmbeddedView:2
1455 withParams:std::move(embeddedViewParams)];
1456 [flutterPlatformViewsController
1458 withParams:[flutterPlatformViewsController compositionParamsForView:2]];
1459
1460 [flutterView setNeedsLayout];
1461 [flutterView layoutIfNeeded];
1462
1463 numberOfExpectedVisualEffectView = 0;
1464 for (UIView* subview in childClippingView.subviews) {
1465 if (![subview isKindOfClass:[UIVisualEffectView class]]) {
1466 continue;
1467 }
1468 XCTAssertLessThan(numberOfExpectedVisualEffectView, 4u);
1469 if ([self validateOneVisualEffectView:subview
1470 expectedFrame:CGRectMake(0, 0, 10, 10)
1471 inputRadius:(CGFloat)5]) {
1472 numberOfExpectedVisualEffectView++;
1473 }
1474 }
1475 XCTAssertEqual(numberOfExpectedVisualEffectView, 4u);
1476
1477 // Simulate adding a non-DlBlurImageFilter to the end of the stack (replace the mutators stack)
1478 // Update embedded view params, delete except screenScaleMatrix
1479 for (int i = 0; i < 5; i++) {
1480 stack2.Pop();
1481 }
1482 // Push backdrop filters and dilate filter
1483 for (int i = 0; i < 5; i++) {
1484 if (i == 4) {
1485 stack2.PushBackdropFilter(
1486 dilateFilter, flutter::DlRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
1487 continue;
1488 }
1489
1490 stack2.PushBackdropFilter(blurFilter,
1491 flutter::DlRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
1492 }
1493
1494 embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
1495 screenScaleMatrix, flutter::DlSize(10, 10), stack2);
1496
1497 [flutterPlatformViewsController prerollCompositeEmbeddedView:2
1498 withParams:std::move(embeddedViewParams)];
1499 [flutterPlatformViewsController
1501 withParams:[flutterPlatformViewsController compositionParamsForView:2]];
1502
1503 [flutterView setNeedsLayout];
1504 [flutterView layoutIfNeeded];
1505
1506 numberOfExpectedVisualEffectView = 0;
1507 for (UIView* subview in childClippingView.subviews) {
1508 if (![subview isKindOfClass:[UIVisualEffectView class]]) {
1509 continue;
1510 }
1511 XCTAssertLessThan(numberOfExpectedVisualEffectView, 4u);
1512 if ([self validateOneVisualEffectView:subview
1513 expectedFrame:CGRectMake(0, 0, 10, 10)
1514 inputRadius:(CGFloat)5]) {
1515 numberOfExpectedVisualEffectView++;
1516 }
1517 }
1518 XCTAssertEqual(numberOfExpectedVisualEffectView, 4u);
1519
1520 // Simulate adding only non-DlBlurImageFilter to the stack (replace the mutators stack)
1521 // Update embedded view params, delete except screenScaleMatrix
1522 for (int i = 0; i < 5; i++) {
1523 stack2.Pop();
1524 }
1525 // Push dilate filters
1526 for (int i = 0; i < 5; i++) {
1527 stack2.PushBackdropFilter(dilateFilter,
1528 flutter::DlRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
1529 }
1530
1531 embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
1532 screenScaleMatrix, flutter::DlSize(10, 10), stack2);
1533
1534 [flutterPlatformViewsController prerollCompositeEmbeddedView:2
1535 withParams:std::move(embeddedViewParams)];
1536 [flutterPlatformViewsController
1538 withParams:[flutterPlatformViewsController compositionParamsForView:2]];
1539
1540 [flutterView setNeedsLayout];
1541 [flutterView layoutIfNeeded];
1542
1543 numberOfExpectedVisualEffectView = 0;
1544 for (UIView* subview in childClippingView.subviews) {
1545 if ([subview isKindOfClass:[UIVisualEffectView class]]) {
1546 numberOfExpectedVisualEffectView++;
1547 }
1548 }
1549 XCTAssertEqual(numberOfExpectedVisualEffectView, 0u);
1550}
1551
1552- (void)testApplyBackdropFilterCorrectAPI {
1554 // The gaussianBlur filter is extracted from UIVisualEffectView.
1555 // Each test requires a new PlatformViewFilter
1556 // Valid UIVisualEffectView API
1557 UIVisualEffectView* visualEffectView = [[UIVisualEffectView alloc]
1558 initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]];
1559 PlatformViewFilter* platformViewFilter =
1560 [[PlatformViewFilter alloc] initWithFrame:CGRectMake(0, 0, 10, 10)
1561 blurRadius:5
1562 cornerRadius:0
1563 isRoundedSuperellipse:NO
1564 visualEffectView:visualEffectView];
1565 XCTAssertNotNil(platformViewFilter);
1566}
1567
1568- (void)testApplyBackdropFilterAPIChangedInvalidUIVisualEffectView {
1570 UIVisualEffectView* visualEffectView = [[UIVisualEffectView alloc] init];
1571 PlatformViewFilter* platformViewFilter =
1572 [[PlatformViewFilter alloc] initWithFrame:CGRectMake(0, 0, 10, 10)
1573 blurRadius:5
1574 cornerRadius:0
1575 isRoundedSuperellipse:NO
1576 visualEffectView:visualEffectView];
1577 XCTAssertNil(platformViewFilter);
1578}
1579
1580- (void)testApplyBackdropFilterAPIChangedNoGaussianBlurFilter {
1582 UIVisualEffectView* editedUIVisualEffectView = [[UIVisualEffectView alloc]
1583 initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]];
1584 NSArray* subviews = editedUIVisualEffectView.subviews;
1585 for (UIView* view in subviews) {
1586 if ([NSStringFromClass([view class]) hasSuffix:@"BackdropView"]) {
1587 for (CIFilter* filter in view.layer.filters) {
1588 if ([[filter valueForKey:@"name"] isEqual:@"gaussianBlur"]) {
1589 [filter setValue:@"notGaussianBlur" forKey:@"name"];
1590 break;
1591 }
1592 }
1593 break;
1594 }
1595 }
1596 PlatformViewFilter* platformViewFilter =
1597 [[PlatformViewFilter alloc] initWithFrame:CGRectMake(0, 0, 10, 10)
1598 blurRadius:5
1599 cornerRadius:0
1600 isRoundedSuperellipse:NO
1601 visualEffectView:editedUIVisualEffectView];
1602 XCTAssertNil(platformViewFilter);
1603}
1604
1605- (void)testApplyBackdropFilterAPIChangedInvalidInputRadius {
1607 UIVisualEffectView* editedUIVisualEffectView = [[UIVisualEffectView alloc]
1608 initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]];
1609 NSArray* subviews = editedUIVisualEffectView.subviews;
1610 for (UIView* view in subviews) {
1611 if ([NSStringFromClass([view class]) hasSuffix:@"BackdropView"]) {
1612 for (CIFilter* filter in view.layer.filters) {
1613 if ([[filter valueForKey:@"name"] isEqual:@"gaussianBlur"]) {
1614 [filter setValue:@"invalidInputRadius" forKey:@"inputRadius"];
1615 break;
1616 }
1617 }
1618 break;
1619 }
1620 }
1621
1622 PlatformViewFilter* platformViewFilter =
1623 [[PlatformViewFilter alloc] initWithFrame:CGRectMake(0, 0, 10, 10)
1624 blurRadius:5
1625 cornerRadius:0
1626 isRoundedSuperellipse:NO
1627 visualEffectView:editedUIVisualEffectView];
1628 XCTAssertNil(platformViewFilter);
1629}
1630
1631- (void)testApplyBackdropFilterRespectsClipRRect {
1632 flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
1633
1635 FlutterPlatformViewsController* flutterPlatformViewsController =
1636 [[FlutterPlatformViewsController alloc] init];
1637 flutterPlatformViewsController.taskRunner = flutter::testing::GetDefaultTaskRunner();
1638 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1639 /*delegate=*/mock_delegate,
1640 /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
1641 /*platform_views_controller=*/flutterPlatformViewsController,
1642 /*task_runners=*/runners.taskRunners,
1643 /*worker_task_runner=*/nil,
1644 /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
1645
1648 [flutterPlatformViewsController
1649 registerViewFactory:factory
1650 withId:@"MockFlutterPlatformView"
1651 gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
1652 FlutterResult result = ^(id result) {
1653 };
1654 [flutterPlatformViewsController
1656 methodCallWithMethodName:@"create"
1657 arguments:@{
1658 @"id" : @2,
1659 @"viewType" : @"MockFlutterPlatformView",
1660 @"gestureBlockingPolicy" : @"fallbackToPluginDefault"
1661 }]
1662 result:result];
1663
1664 XCTAssertNotNil(gMockPlatformView);
1665
1666 UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
1667 flutterPlatformViewsController.flutterView = flutterView;
1668 // Create embedded view params
1670 // Layer tree always pushes a screen scale factor to the stack
1671 flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
1672 flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
1673 stack.PushTransform(screenScaleMatrix);
1674
1675 // Push a rounded rect clip
1676 auto clipRect = flutter::DlRect::MakeXYWH(2, 2, 6, 6);
1677 auto clipRRect = flutter::DlRoundRect::MakeRectXY(clipRect, 3, 3);
1678 stack.PushPlatformViewClipRRect(clipRRect);
1679
1680 // Push a backdrop filter
1682 stack.PushBackdropFilter(filter,
1683 flutter::DlRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
1684
1685 auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
1686 screenScaleMatrix, flutter::DlSize(10, 10), stack);
1687
1688 [flutterPlatformViewsController prerollCompositeEmbeddedView:2
1689 withParams:std::move(embeddedViewParams)];
1690 [flutterPlatformViewsController
1692 withParams:[flutterPlatformViewsController compositionParamsForView:2]];
1693
1694 XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:[ChildClippingView class]]);
1695 ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
1696 [flutterView addSubview:childClippingView];
1697
1698 [flutterView setNeedsLayout];
1699 [flutterView layoutIfNeeded];
1700
1701 NSArray<UIVisualEffectView*>* filters = childClippingView.backdropFilterSubviews;
1702 XCTAssertEqual(filters.count, 1u);
1703
1704 UIVisualEffectView* visualEffectView = filters[0];
1705 auto radii = clipRRect.GetRadii();
1706
1707 XCTAssertEqual(visualEffectView.layer.cornerRadius, radii.top_left.width);
1708 XCTAssertEqual(visualEffectView.layer.cornerCurve, kCACornerCurveCircular);
1709}
1710
1711- (void)testApplyBackdropFilterRespectsClipRSuperellipse {
1712 flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
1713
1715 FlutterPlatformViewsController* flutterPlatformViewsController =
1716 [[FlutterPlatformViewsController alloc] init];
1717 flutterPlatformViewsController.taskRunner = flutter::testing::GetDefaultTaskRunner();
1718 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1719 /*delegate=*/mock_delegate,
1720 /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
1721 /*platform_views_controller=*/flutterPlatformViewsController,
1722 /*task_runners=*/runners.taskRunners,
1723 /*worker_task_runner=*/nil,
1724 /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
1725
1728 [flutterPlatformViewsController
1729 registerViewFactory:factory
1730 withId:@"MockFlutterPlatformView"
1731 gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
1732 FlutterResult result = ^(id result) {
1733 };
1734 [flutterPlatformViewsController
1736 methodCallWithMethodName:@"create"
1737 arguments:@{
1738 @"id" : @2,
1739 @"viewType" : @"MockFlutterPlatformView",
1740 @"gestureBlockingPolicy" : @"fallbackToPluginDefault"
1741 }]
1742 result:result];
1743
1744 XCTAssertNotNil(gMockPlatformView);
1745
1746 UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
1747 flutterPlatformViewsController.flutterView = flutterView;
1748 // Create embedded view params
1750 // Layer tree always pushes a screen scale factor to the stack
1751 flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
1752 flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
1753 stack.PushTransform(screenScaleMatrix);
1754
1755 // Push a rounded superellipse clip
1756 auto clipRect = flutter::DlRect::MakeXYWH(2, 2, 6, 6);
1757 auto clipRSE = flutter::DlRoundSuperellipse::MakeRectXY(clipRect, 3, 3);
1758 stack.PushPlatformViewClipRSuperellipse(clipRSE);
1759
1760 // Push a backdrop filter
1762 stack.PushBackdropFilter(filter,
1763 flutter::DlRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10));
1764
1765 auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
1766 screenScaleMatrix, flutter::DlSize(10, 10), stack);
1767
1768 [flutterPlatformViewsController prerollCompositeEmbeddedView:2
1769 withParams:std::move(embeddedViewParams)];
1770 [flutterPlatformViewsController
1772 withParams:[flutterPlatformViewsController compositionParamsForView:2]];
1773
1774 XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:[ChildClippingView class]]);
1775 ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
1776 [flutterView addSubview:childClippingView];
1777
1778 [flutterView setNeedsLayout];
1779 [flutterView layoutIfNeeded];
1780
1781 NSArray<UIVisualEffectView*>* filters = childClippingView.backdropFilterSubviews;
1782 XCTAssertEqual(filters.count, 1u);
1783
1784 UIVisualEffectView* visualEffectView = filters[0];
1785 auto radii = clipRSE.GetRadii();
1786
1787 XCTAssertEqual(visualEffectView.layer.cornerRadius, radii.top_left.width);
1788 XCTAssertEqual(visualEffectView.layer.cornerCurve, kCACornerCurveContinuous);
1789}
1790
1791- (void)testBackdropFilterVisualEffectSubviewBackgroundColor {
1792 __weak UIVisualEffectView* weakVisualEffectView;
1793
1794 @autoreleasepool {
1795 UIVisualEffectView* visualEffectView = [[UIVisualEffectView alloc]
1796 initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]];
1797 weakVisualEffectView = visualEffectView;
1798 PlatformViewFilter* platformViewFilter =
1799 [[PlatformViewFilter alloc] initWithFrame:CGRectMake(0, 0, 10, 10)
1800 blurRadius:5
1801 cornerRadius:0
1802 isRoundedSuperellipse:NO
1803 visualEffectView:visualEffectView];
1804 CGColorRef visualEffectSubviewBackgroundColor = nil;
1805 for (UIView* view in [platformViewFilter backdropFilterView].subviews) {
1806 if ([NSStringFromClass([view class]) hasSuffix:@"VisualEffectSubview"]) {
1807 visualEffectSubviewBackgroundColor = view.layer.backgroundColor;
1808 }
1809 }
1810 XCTAssertTrue(
1811 CGColorEqualToColor(visualEffectSubviewBackgroundColor, UIColor.clearColor.CGColor));
1812 }
1813 XCTAssertNil(weakVisualEffectView);
1814}
1815
1816- (void)testCompositePlatformView {
1817 flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
1818
1820 FlutterPlatformViewsController* flutterPlatformViewsController =
1821 [[FlutterPlatformViewsController alloc] init];
1822 flutterPlatformViewsController.taskRunner = flutter::testing::GetDefaultTaskRunner();
1823 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1824 /*delegate=*/mock_delegate,
1825 /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
1826 /*platform_views_controller=*/flutterPlatformViewsController,
1827 /*task_runners=*/runners.taskRunners,
1828 /*worker_task_runner=*/nil,
1829 /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
1830
1833 [flutterPlatformViewsController
1834 registerViewFactory:factory
1835 withId:@"MockFlutterPlatformView"
1836 gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
1837 FlutterResult result = ^(id result) {
1838 };
1839 [flutterPlatformViewsController
1841 arguments:@{
1842 @"id" : @2,
1843 @"viewType" : @"MockFlutterPlatformView",
1844 @"gestureBlockingPolicy" : @"eager"
1845 }]
1846 result:result];
1847
1848 XCTAssertNotNil(gMockPlatformView);
1849
1850 UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
1851 flutterPlatformViewsController.flutterView = flutterView;
1852 // Create embedded view params
1854 // Layer tree always pushes a screen scale factor to the stack
1855 flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
1856 flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
1857 stack.PushTransform(screenScaleMatrix);
1858 // Push a translate matrix
1859 flutter::DlMatrix translateMatrix = flutter::DlMatrix::MakeTranslation({100, 100});
1860 stack.PushTransform(translateMatrix);
1861 flutter::DlMatrix finalMatrix = screenScaleMatrix * translateMatrix;
1862
1863 auto embeddedViewParams =
1864 std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, flutter::DlSize(300, 300), stack);
1865
1866 [flutterPlatformViewsController prerollCompositeEmbeddedView:2
1867 withParams:std::move(embeddedViewParams)];
1868 [flutterPlatformViewsController
1870 withParams:[flutterPlatformViewsController compositionParamsForView:2]];
1871
1872 CGRect platformViewRectInFlutterView = [gMockPlatformView convertRect:gMockPlatformView.bounds
1873 toView:flutterView];
1874 XCTAssertTrue(CGRectEqualToRect(platformViewRectInFlutterView, CGRectMake(100, 100, 300, 300)));
1875}
1876
1877- (void)testBackdropFilterCorrectlyPushedAndReset {
1878 flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
1879
1881 FlutterPlatformViewsController* flutterPlatformViewsController =
1882 [[FlutterPlatformViewsController alloc] init];
1883 flutterPlatformViewsController.taskRunner = flutter::testing::GetDefaultTaskRunner();
1884 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1885 /*delegate=*/mock_delegate,
1886 /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
1887 /*platform_views_controller=*/flutterPlatformViewsController,
1888 /*task_runners=*/runners.taskRunners,
1889 /*worker_task_runner=*/nil,
1890 /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
1891
1894 [flutterPlatformViewsController
1895 registerViewFactory:factory
1896 withId:@"MockFlutterPlatformView"
1897 gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
1898 FlutterResult result = ^(id result) {
1899 };
1900 [flutterPlatformViewsController
1902 arguments:@{
1903 @"id" : @2,
1904 @"viewType" : @"MockFlutterPlatformView",
1905 @"gestureBlockingPolicy" : @"eager"
1906 }]
1907 result:result];
1908
1909 XCTAssertNotNil(gMockPlatformView);
1910
1911 UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
1912 flutterPlatformViewsController.flutterView = flutterView;
1913 // Create embedded view params
1915 // Layer tree always pushes a screen scale factor to the stack
1916 flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
1917 flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
1918 stack.PushTransform(screenScaleMatrix);
1919
1920 auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
1921 screenScaleMatrix, flutter::DlSize(10, 10), stack);
1922
1923 [flutterPlatformViewsController beginFrameWithSize:flutter::DlISize(0, 0)];
1924 [flutterPlatformViewsController prerollCompositeEmbeddedView:2
1925 withParams:std::move(embeddedViewParams)];
1926 [flutterPlatformViewsController pushVisitedPlatformViewId:2];
1927 auto filter = flutter::DlBlurImageFilter::Make(5, 2, flutter::DlTileMode::kClamp, std::nullopt);
1928 [flutterPlatformViewsController
1929 pushFilterToVisitedPlatformViews:filter
1930 withRect:flutter::DlRect::MakeXYWH(0, 0, screenScale * 10,
1931 screenScale * 10)];
1932 [flutterPlatformViewsController
1934 withParams:[flutterPlatformViewsController compositionParamsForView:2]];
1935
1936 XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:[ChildClippingView class]]);
1937 ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
1938 [flutterView addSubview:childClippingView];
1939
1940 [flutterView setNeedsLayout];
1941 [flutterView layoutIfNeeded];
1942
1943 // childClippingView has visual effect view with the correct configurations.
1944 NSUInteger numberOfExpectedVisualEffectView = 0;
1945 for (UIView* subview in childClippingView.subviews) {
1946 if (![subview isKindOfClass:[UIVisualEffectView class]]) {
1947 continue;
1948 }
1949 XCTAssertLessThan(numberOfExpectedVisualEffectView, 1u);
1950 if ([self validateOneVisualEffectView:subview
1951 expectedFrame:CGRectMake(0, 0, 10, 10)
1952 inputRadius:5]) {
1953 numberOfExpectedVisualEffectView++;
1954 }
1955 }
1956 XCTAssertEqual(numberOfExpectedVisualEffectView, 1u);
1957
1958 // New frame, with no filter pushed.
1959 auto embeddedViewParams2 = std::make_unique<flutter::EmbeddedViewParams>(
1960 screenScaleMatrix, flutter::DlSize(10, 10), stack);
1961 [flutterPlatformViewsController beginFrameWithSize:flutter::DlISize(0, 0)];
1962 [flutterPlatformViewsController prerollCompositeEmbeddedView:2
1963 withParams:std::move(embeddedViewParams2)];
1964 [flutterPlatformViewsController
1966 withParams:[flutterPlatformViewsController compositionParamsForView:2]];
1967
1968 XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:[ChildClippingView class]]);
1969
1970 [flutterView setNeedsLayout];
1971 [flutterView layoutIfNeeded];
1972
1973 numberOfExpectedVisualEffectView = 0;
1974 for (UIView* subview in childClippingView.subviews) {
1975 if (![subview isKindOfClass:[UIVisualEffectView class]]) {
1976 continue;
1977 }
1978 numberOfExpectedVisualEffectView++;
1979 }
1980 XCTAssertEqual(numberOfExpectedVisualEffectView, 0u);
1981}
1982
1983- (void)testChildClippingViewShouldBeTheBoundingRectOfPlatformView {
1984 flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
1985
1987 FlutterPlatformViewsController* flutterPlatformViewsController =
1988 [[FlutterPlatformViewsController alloc] init];
1989 flutterPlatformViewsController.taskRunner = flutter::testing::GetDefaultTaskRunner();
1990 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1991 /*delegate=*/mock_delegate,
1992 /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
1993 /*platform_views_controller=*/flutterPlatformViewsController,
1994 /*task_runners=*/runners.taskRunners,
1995 /*worker_task_runner=*/nil,
1996 /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
1997
2000 [flutterPlatformViewsController
2001 registerViewFactory:factory
2002 withId:@"MockFlutterPlatformView"
2003 gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
2004 FlutterResult result = ^(id result) {
2005 };
2006 [flutterPlatformViewsController
2008 arguments:@{
2009 @"id" : @2,
2010 @"viewType" : @"MockFlutterPlatformView",
2011 @"gestureBlockingPolicy" : @"eager"
2012 }]
2013 result:result];
2014
2015 XCTAssertNotNil(gMockPlatformView);
2016
2017 UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
2018 flutterPlatformViewsController.flutterView = flutterView;
2019 // Create embedded view params
2021 // Layer tree always pushes a screen scale factor to the stack
2022 flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
2023 flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
2024 stack.PushTransform(screenScaleMatrix);
2025 // Push a rotate matrix
2027 stack.PushTransform(rotateMatrix);
2028 flutter::DlMatrix finalMatrix = screenScaleMatrix * rotateMatrix;
2029
2030 auto embeddedViewParams =
2031 std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, flutter::DlSize(300, 300), stack);
2032
2033 [flutterPlatformViewsController prerollCompositeEmbeddedView:2
2034 withParams:std::move(embeddedViewParams)];
2035 [flutterPlatformViewsController
2037 withParams:[flutterPlatformViewsController compositionParamsForView:2]];
2038
2039 CGRect platformViewRectInFlutterView = [gMockPlatformView convertRect:gMockPlatformView.bounds
2040 toView:flutterView];
2041 XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]);
2042 ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
2043 // The childclippingview's frame is set based on flow, but the platform view's frame is set based
2044 // on quartz. Although they should be the same, but we should tolerate small floating point
2045 // errors.
2046 XCTAssertLessThan(fabs(platformViewRectInFlutterView.origin.x - childClippingView.frame.origin.x),
2048 XCTAssertLessThan(fabs(platformViewRectInFlutterView.origin.y - childClippingView.frame.origin.y),
2050 XCTAssertLessThan(
2051 fabs(platformViewRectInFlutterView.size.width - childClippingView.frame.size.width),
2053 XCTAssertLessThan(
2054 fabs(platformViewRectInFlutterView.size.height - childClippingView.frame.size.height),
2056}
2057
2058- (void)testClipsDoNotInterceptWithPlatformViewShouldNotAddMaskView {
2059 flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
2060
2062 FlutterPlatformViewsController* flutterPlatformViewsController =
2063 [[FlutterPlatformViewsController alloc] init];
2064 flutterPlatformViewsController.taskRunner = flutter::testing::GetDefaultTaskRunner();
2065 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2066 /*delegate=*/mock_delegate,
2067 /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
2068 /*platform_views_controller=*/flutterPlatformViewsController,
2069 /*task_runners=*/runners.taskRunners,
2070 /*worker_task_runner=*/nil,
2071 /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
2072
2075 [flutterPlatformViewsController
2076 registerViewFactory:factory
2077 withId:@"MockFlutterPlatformView"
2078 gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
2079 FlutterResult result = ^(id result) {
2080 };
2081 [flutterPlatformViewsController
2083 arguments:@{
2084 @"id" : @2,
2085 @"viewType" : @"MockFlutterPlatformView",
2086 @"gestureBlockingPolicy" : @"eager"
2087 }]
2088 result:result];
2089
2090 XCTAssertNotNil(gMockPlatformView);
2091
2092 UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 30, 30)];
2093 flutterPlatformViewsController.flutterView = flutterView;
2094 // Create embedded view params.
2096 // Layer tree always pushes a screen scale factor to the stack.
2097 flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
2098 flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
2099 stack.PushTransform(screenScaleMatrix);
2100 flutter::DlMatrix translateMatrix = flutter::DlMatrix::MakeTranslation({5, 5});
2101 // The platform view's rect for this test will be (5, 5, 10, 10).
2102 stack.PushTransform(translateMatrix);
2103 // Push a clip rect, big enough to contain the entire platform view bound.
2104 flutter::DlRect rect = flutter::DlRect::MakeXYWH(0, 0, 25, 25);
2105 stack.PushClipRect(rect);
2106 // Push a clip rrect, big enough to contain the entire platform view bound without clipping it.
2107 // Make the origin (-1, -1) so that the top left rounded corner isn't clipping the PlatformView.
2108 flutter::DlRect rect_for_rrect = flutter::DlRect::MakeXYWH(-1, -1, 25, 25);
2109 flutter::DlRoundRect rrect = flutter::DlRoundRect::MakeRectXY(rect_for_rrect, 1, 1);
2110 stack.PushClipRRect(rrect);
2111
2112 auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
2113 screenScaleMatrix * translateMatrix, flutter::DlSize(5, 5), stack);
2114
2115 [flutterPlatformViewsController prerollCompositeEmbeddedView:2
2116 withParams:std::move(embeddedViewParams)];
2117 [flutterPlatformViewsController
2119 withParams:[flutterPlatformViewsController compositionParamsForView:2]];
2120
2121 gMockPlatformView.backgroundColor = UIColor.redColor;
2122 XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]);
2123 ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
2124 [flutterView addSubview:childClippingView];
2125
2126 [flutterView setNeedsLayout];
2127 [flutterView layoutIfNeeded];
2128 XCTAssertNil(childClippingView.maskView);
2129}
2130
2131- (void)testClipRRectOnlyHasCornersInterceptWithPlatformViewShouldAddMaskView {
2132 flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
2133
2135 FlutterPlatformViewsController* flutterPlatformViewsController =
2136 [[FlutterPlatformViewsController alloc] init];
2137 flutterPlatformViewsController.taskRunner = flutter::testing::GetDefaultTaskRunner();
2138 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2139 /*delegate=*/mock_delegate,
2140 /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
2141 /*platform_views_controller=*/flutterPlatformViewsController,
2142 /*task_runners=*/runners.taskRunners,
2143 /*worker_task_runner=*/nil,
2144 /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
2145
2148 [flutterPlatformViewsController
2149 registerViewFactory:factory
2150 withId:@"MockFlutterPlatformView"
2151 gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
2152 FlutterResult result = ^(id result) {
2153 };
2154 [flutterPlatformViewsController
2156 arguments:@{
2157 @"id" : @2,
2158 @"viewType" : @"MockFlutterPlatformView",
2159 @"gestureBlockingPolicy" : @"eager"
2160 }]
2161 result:result];
2162
2163 XCTAssertNotNil(gMockPlatformView);
2164
2165 UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 30, 30)];
2166 flutterPlatformViewsController.flutterView = flutterView;
2167 // Create embedded view params
2169 // Layer tree always pushes a screen scale factor to the stack.
2170 flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
2171 flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
2172 stack.PushTransform(screenScaleMatrix);
2173 flutter::DlMatrix translateMatrix = flutter::DlMatrix::MakeTranslation({5, 5});
2174 // The platform view's rect for this test will be (5, 5, 10, 10).
2175 stack.PushTransform(translateMatrix);
2176
2177 // Push a clip rrect, the rect of the rrect is the same as the PlatformView of the corner should.
2178 // clip the PlatformView.
2179 flutter::DlRect rect_for_rrect = flutter::DlRect::MakeXYWH(0, 0, 10, 10);
2180 flutter::DlRoundRect rrect = flutter::DlRoundRect::MakeRectXY(rect_for_rrect, 1, 1);
2181 stack.PushClipRRect(rrect);
2182
2183 auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
2184 screenScaleMatrix * translateMatrix, flutter::DlSize(5, 5), stack);
2185
2186 [flutterPlatformViewsController prerollCompositeEmbeddedView:2
2187 withParams:std::move(embeddedViewParams)];
2188 [flutterPlatformViewsController
2190 withParams:[flutterPlatformViewsController compositionParamsForView:2]];
2191
2192 gMockPlatformView.backgroundColor = UIColor.redColor;
2193 XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]);
2194 ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
2195 [flutterView addSubview:childClippingView];
2196
2197 [flutterView setNeedsLayout];
2198 [flutterView layoutIfNeeded];
2199
2200 XCTAssertNotNil(childClippingView.maskView);
2201}
2202
2203- (void)testClipRect {
2204 flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
2205
2207 FlutterPlatformViewsController* flutterPlatformViewsController =
2208 [[FlutterPlatformViewsController alloc] init];
2209 flutterPlatformViewsController.taskRunner = flutter::testing::GetDefaultTaskRunner();
2210 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2211 /*delegate=*/mock_delegate,
2212 /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
2213 /*platform_views_controller=*/flutterPlatformViewsController,
2214 /*task_runners=*/runners.taskRunners,
2215 /*worker_task_runner=*/nil,
2216 /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
2217
2220 [flutterPlatformViewsController
2221 registerViewFactory:factory
2222 withId:@"MockFlutterPlatformView"
2223 gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
2224 FlutterResult result = ^(id result) {
2225 };
2226 [flutterPlatformViewsController
2228 arguments:@{
2229 @"id" : @2,
2230 @"viewType" : @"MockFlutterPlatformView",
2231 @"gestureBlockingPolicy" : @"eager"
2232 }]
2233 result:result];
2234
2235 XCTAssertNotNil(gMockPlatformView);
2236
2237 UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
2238 flutterPlatformViewsController.flutterView = flutterView;
2239 // Create embedded view params
2241 // Layer tree always pushes a screen scale factor to the stack
2242 flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
2243 flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
2244 stack.PushTransform(screenScaleMatrix);
2245 // Push a clip rect
2246 flutter::DlRect rect = flutter::DlRect::MakeXYWH(2, 2, 3, 3);
2247 stack.PushClipRect(rect);
2248
2249 auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
2250 screenScaleMatrix, flutter::DlSize(10, 10), stack);
2251
2252 [flutterPlatformViewsController prerollCompositeEmbeddedView:2
2253 withParams:std::move(embeddedViewParams)];
2254 [flutterPlatformViewsController
2256 withParams:[flutterPlatformViewsController compositionParamsForView:2]];
2257
2258 gMockPlatformView.backgroundColor = UIColor.redColor;
2259 XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]);
2260 ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
2261 [flutterView addSubview:childClippingView];
2262
2263 [flutterView setNeedsLayout];
2264 [flutterView layoutIfNeeded];
2265
2266 CGRect insideClipping = CGRectMake(2, 2, 3, 3);
2267 for (int i = 0; i < 10; i++) {
2268 for (int j = 0; j < 10; j++) {
2269 CGPoint point = CGPointMake(i, j);
2270 int alpha = [self alphaOfPoint:CGPointMake(i, j) onView:flutterView];
2271 if (CGRectContainsPoint(insideClipping, point)) {
2272 XCTAssertEqual(alpha, 255);
2273 } else {
2274 XCTAssertEqual(alpha, 0);
2275 }
2276 }
2277 }
2278}
2279
2280- (void)testClipRect_multipleClips {
2281 flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
2282
2284 FlutterPlatformViewsController* flutterPlatformViewsController =
2285 [[FlutterPlatformViewsController alloc] init];
2286 flutterPlatformViewsController.taskRunner = flutter::testing::GetDefaultTaskRunner();
2287 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2288 /*delegate=*/mock_delegate,
2289 /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
2290 /*platform_views_controller=*/flutterPlatformViewsController,
2291 /*task_runners=*/runners.taskRunners,
2292 /*worker_task_runner=*/nil,
2293 /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
2294
2297 [flutterPlatformViewsController
2298 registerViewFactory:factory
2299 withId:@"MockFlutterPlatformView"
2300 gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
2301 FlutterResult result = ^(id result) {
2302 };
2303 [flutterPlatformViewsController
2305 arguments:@{
2306 @"id" : @2,
2307 @"viewType" : @"MockFlutterPlatformView",
2308 @"gestureBlockingPolicy" : @"eager"
2309 }]
2310 result:result];
2311
2312 XCTAssertNotNil(gMockPlatformView);
2313
2314 UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
2315 flutterPlatformViewsController.flutterView = flutterView;
2316 // Create embedded view params
2318 // Layer tree always pushes a screen scale factor to the stack
2319 flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
2320 flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
2321 stack.PushTransform(screenScaleMatrix);
2322 // Push a clip rect
2323 flutter::DlRect rect1 = flutter::DlRect::MakeXYWH(2, 2, 3, 3);
2324 stack.PushClipRect(rect1);
2325 // Push another clip rect
2326 flutter::DlRect rect2 = flutter::DlRect::MakeXYWH(3, 3, 3, 3);
2327 stack.PushClipRect(rect2);
2328
2329 auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
2330 screenScaleMatrix, flutter::DlSize(10, 10), stack);
2331
2332 [flutterPlatformViewsController prerollCompositeEmbeddedView:2
2333 withParams:std::move(embeddedViewParams)];
2334 [flutterPlatformViewsController
2336 withParams:[flutterPlatformViewsController compositionParamsForView:2]];
2337
2338 gMockPlatformView.backgroundColor = UIColor.redColor;
2339 XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]);
2340 ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
2341 [flutterView addSubview:childClippingView];
2342
2343 [flutterView setNeedsLayout];
2344 [flutterView layoutIfNeeded];
2345
2346 /*
2347 clip 1 clip 2
2348 2 3 4 5 6 2 3 4 5 6
2349 2 + - - + 2
2350 3 | | 3 + - - +
2351 4 | | 4 | |
2352 5 + - - + 5 | |
2353 6 6 + - - +
2354
2355 Result should be the intersection of 2 clips
2356 2 3 4 5 6
2357 2
2358 3 + - +
2359 4 | |
2360 5 + - +
2361 6
2362 */
2363 CGRect insideClipping = CGRectMake(3, 3, 2, 2);
2364 for (int i = 0; i < 10; i++) {
2365 for (int j = 0; j < 10; j++) {
2366 CGPoint point = CGPointMake(i, j);
2367 int alpha = [self alphaOfPoint:CGPointMake(i, j) onView:flutterView];
2368 if (CGRectContainsPoint(insideClipping, point)) {
2369 XCTAssertEqual(alpha, 255);
2370 } else {
2371 XCTAssertEqual(alpha, 0);
2372 }
2373 }
2374 }
2375}
2376
2377- (void)testClipRRect {
2378 flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
2379
2381 FlutterPlatformViewsController* flutterPlatformViewsController =
2382 [[FlutterPlatformViewsController alloc] init];
2383 flutterPlatformViewsController.taskRunner = flutter::testing::GetDefaultTaskRunner();
2384 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2385 /*delegate=*/mock_delegate,
2386 /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
2387 /*platform_views_controller=*/flutterPlatformViewsController,
2388 /*task_runners=*/runners.taskRunners,
2389 /*worker_task_runner=*/nil,
2390 /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
2391
2394 [flutterPlatformViewsController
2395 registerViewFactory:factory
2396 withId:@"MockFlutterPlatformView"
2397 gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
2398 FlutterResult result = ^(id result) {
2399 };
2400 [flutterPlatformViewsController
2402 arguments:@{
2403 @"id" : @2,
2404 @"viewType" : @"MockFlutterPlatformView",
2405 @"gestureBlockingPolicy" : @"eager"
2406 }]
2407 result:result];
2408
2409 XCTAssertNotNil(gMockPlatformView);
2410
2411 UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
2412 flutterPlatformViewsController.flutterView = flutterView;
2413 // Create embedded view params
2415 // Layer tree always pushes a screen scale factor to the stack
2416 flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
2417 flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
2418 stack.PushTransform(screenScaleMatrix);
2419 // Push a clip rrect
2420 flutter::DlRoundRect rrect =
2422 stack.PushClipRRect(rrect);
2423
2424 auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
2425 screenScaleMatrix, flutter::DlSize(10, 10), stack);
2426
2427 [flutterPlatformViewsController prerollCompositeEmbeddedView:2
2428 withParams:std::move(embeddedViewParams)];
2429 [flutterPlatformViewsController
2431 withParams:[flutterPlatformViewsController compositionParamsForView:2]];
2432
2433 gMockPlatformView.backgroundColor = UIColor.redColor;
2434 XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]);
2435 ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
2436 [flutterView addSubview:childClippingView];
2437
2438 [flutterView setNeedsLayout];
2439 [flutterView layoutIfNeeded];
2440
2441 /*
2442 ClippingMask outterClipping
2443 2 3 4 5 6 7 2 3 4 5 6 7
2444 2 / - - - - \ 2 + - - - - +
2445 3 | | 3 | |
2446 4 | | 4 | |
2447 5 | | 5 | |
2448 6 | | 6 | |
2449 7 \ - - - - / 7 + - - - - +
2450
2451 innerClipping1 innerClipping2
2452 2 3 4 5 6 7 2 3 4 5 6 7
2453 2 + - - + 2
2454 3 | | 3 + - - - - +
2455 4 | | 4 | |
2456 5 | | 5 | |
2457 6 | | 6 + - - - - +
2458 7 + - - + 7
2459 */
2460 CGRect innerClipping1 = CGRectMake(3, 2, 4, 6);
2461 CGRect innerClipping2 = CGRectMake(2, 3, 6, 4);
2462 CGRect outterClipping = CGRectMake(2, 2, 6, 6);
2463 for (int i = 0; i < 10; i++) {
2464 for (int j = 0; j < 10; j++) {
2465 CGPoint point = CGPointMake(i, j);
2466 int alpha = [self alphaOfPoint:CGPointMake(i, j) onView:flutterView];
2467 if (CGRectContainsPoint(innerClipping1, point) ||
2468 CGRectContainsPoint(innerClipping2, point)) {
2469 // Pixels inside either of the 2 inner clippings should be fully opaque.
2470 XCTAssertEqual(alpha, 255);
2471 } else if (CGRectContainsPoint(outterClipping, point)) {
2472 // Corner pixels (i.e. (2, 2), (2, 7), (7, 2) and (7, 7)) should be partially transparent.
2473 XCTAssert(0 < alpha && alpha < 255);
2474 } else {
2475 // Pixels outside outterClipping should be fully transparent.
2476 XCTAssertEqual(alpha, 0);
2477 }
2478 }
2479 }
2480}
2481
2482- (void)testClipRRect_multipleClips {
2483 flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
2484
2486 FlutterPlatformViewsController* flutterPlatformViewsController =
2487 [[FlutterPlatformViewsController alloc] init];
2488 flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
2489 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2490 /*delegate=*/mock_delegate,
2491 /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
2492 /*platform_views_controller=*/flutterPlatformViewsController,
2493 /*task_runners=*/runners.taskRunners,
2494 /*worker_task_runner=*/nil,
2495 /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
2496
2499 [flutterPlatformViewsController
2500 registerViewFactory:factory
2501 withId:@"MockFlutterPlatformView"
2502 gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
2503 FlutterResult result = ^(id result) {
2504 };
2505 [flutterPlatformViewsController
2507 arguments:@{
2508 @"id" : @2,
2509 @"viewType" : @"MockFlutterPlatformView",
2510 @"gestureBlockingPolicy" : @"eager"
2511 }]
2512 result:result];
2513
2514 XCTAssertNotNil(gMockPlatformView);
2515
2516 UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
2517 flutterPlatformViewsController.flutterView = flutterView;
2518 // Create embedded view params
2520 // Layer tree always pushes a screen scale factor to the stack
2521 flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
2522 flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
2523 stack.PushTransform(screenScaleMatrix);
2524 // Push a clip rrect
2525 flutter::DlRoundRect rrect =
2527 stack.PushClipRRect(rrect);
2528 // Push a clip rect
2529 flutter::DlRect rect = flutter::DlRect::MakeXYWH(4, 2, 6, 6);
2530 stack.PushClipRect(rect);
2531
2532 auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
2533 screenScaleMatrix, flutter::DlSize(10, 10), stack);
2534
2535 [flutterPlatformViewsController prerollCompositeEmbeddedView:2
2536 withParams:std::move(embeddedViewParams)];
2537 [flutterPlatformViewsController
2539 withParams:[flutterPlatformViewsController compositionParamsForView:2]];
2540
2541 gMockPlatformView.backgroundColor = UIColor.redColor;
2542 XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]);
2543 ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
2544 [flutterView addSubview:childClippingView];
2545
2546 [flutterView setNeedsLayout];
2547 [flutterView layoutIfNeeded];
2548
2549 /*
2550 clip 1 clip 2
2551 2 3 4 5 6 7 8 9 2 3 4 5 6 7 8 9
2552 2 / - - - - \ 2 + - - - - +
2553 3 | | 3 | |
2554 4 | | 4 | |
2555 5 | | 5 | |
2556 6 | | 6 | |
2557 7 \ - - - - / 7 + - - - - +
2558
2559 Result should be the intersection of 2 clips
2560 2 3 4 5 6 7 8 9
2561 2 + - - \
2562 3 | |
2563 4 | |
2564 5 | |
2565 6 | |
2566 7 + - - /
2567 */
2568 CGRect clipping = CGRectMake(4, 2, 4, 6);
2569 for (int i = 0; i < 10; i++) {
2570 for (int j = 0; j < 10; j++) {
2571 CGPoint point = CGPointMake(i, j);
2572 int alpha = [self alphaOfPoint:CGPointMake(i, j) onView:flutterView];
2573 if (i == 7 && (j == 2 || j == 7)) {
2574 // Upper and lower right corners should be partially transparent.
2575 XCTAssert(0 < alpha && alpha < 255);
2576 } else if (
2577 // left
2578 (i == 4 && j >= 2 && j <= 7) ||
2579 // right
2580 (i == 7 && j >= 2 && j <= 7) ||
2581 // top
2582 (j == 2 && i >= 4 && i <= 7) ||
2583 // bottom
2584 (j == 7 && i >= 4 && i <= 7)) {
2585 // Since we are falling back to software rendering for this case
2586 // The edge pixels can be anti-aliased, so it may not be fully opaque.
2587 XCTAssert(alpha > 127);
2588 } else if ((i == 3 && j >= 1 && j <= 8) || (i == 8 && j >= 1 && j <= 8) ||
2589 (j == 1 && i >= 3 && i <= 8) || (j == 8 && i >= 3 && i <= 8)) {
2590 // Since we are falling back to software rendering for this case
2591 // The edge pixels can be anti-aliased, so it may not be fully transparent.
2592 XCTAssert(alpha < 127);
2593 } else if (CGRectContainsPoint(clipping, point)) {
2594 // Other pixels inside clipping should be fully opaque.
2595 XCTAssertEqual(alpha, 255);
2596 } else {
2597 // Pixels outside clipping should be fully transparent.
2598 XCTAssertEqual(alpha, 0);
2599 }
2600 }
2601 }
2602}
2603
2604- (void)testClipPath {
2605 flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
2606
2608 FlutterPlatformViewsController* flutterPlatformViewsController =
2609 [[FlutterPlatformViewsController alloc] init];
2610 flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
2611 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2612 /*delegate=*/mock_delegate,
2613 /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
2614 /*platform_views_controller=*/flutterPlatformViewsController,
2615 /*task_runners=*/runners.taskRunners,
2616 /*worker_task_runner=*/nil,
2617 /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
2618
2621 [flutterPlatformViewsController
2622 registerViewFactory:factory
2623 withId:@"MockFlutterPlatformView"
2624 gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
2625 FlutterResult result = ^(id result) {
2626 };
2627 [flutterPlatformViewsController
2629 arguments:@{
2630 @"id" : @2,
2631 @"viewType" : @"MockFlutterPlatformView",
2632 @"gestureBlockingPolicy" : @"eager"
2633 }]
2634 result:result];
2635
2636 XCTAssertNotNil(gMockPlatformView);
2637
2638 UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
2639 flutterPlatformViewsController.flutterView = flutterView;
2640 // Create embedded view params
2642 // Layer tree always pushes a screen scale factor to the stack
2643 flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
2644 flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
2645 stack.PushTransform(screenScaleMatrix);
2646 // Push a clip path
2647 flutter::DlPath path =
2649 stack.PushClipPath(path);
2650
2651 auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
2652 screenScaleMatrix, flutter::DlSize(10, 10), stack);
2653
2654 [flutterPlatformViewsController prerollCompositeEmbeddedView:2
2655 withParams:std::move(embeddedViewParams)];
2656 [flutterPlatformViewsController
2658 withParams:[flutterPlatformViewsController compositionParamsForView:2]];
2659
2660 gMockPlatformView.backgroundColor = UIColor.redColor;
2661 XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]);
2662 ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
2663 [flutterView addSubview:childClippingView];
2664
2665 [flutterView setNeedsLayout];
2666 [flutterView layoutIfNeeded];
2667
2668 /*
2669 ClippingMask outterClipping
2670 2 3 4 5 6 7 2 3 4 5 6 7
2671 2 / - - - - \ 2 + - - - - +
2672 3 | | 3 | |
2673 4 | | 4 | |
2674 5 | | 5 | |
2675 6 | | 6 | |
2676 7 \ - - - - / 7 + - - - - +
2677
2678 innerClipping1 innerClipping2
2679 2 3 4 5 6 7 2 3 4 5 6 7
2680 2 + - - + 2
2681 3 | | 3 + - - - - +
2682 4 | | 4 | |
2683 5 | | 5 | |
2684 6 | | 6 + - - - - +
2685 7 + - - + 7
2686 */
2687 CGRect innerClipping1 = CGRectMake(3, 2, 4, 6);
2688 CGRect innerClipping2 = CGRectMake(2, 3, 6, 4);
2689 CGRect outterClipping = CGRectMake(2, 2, 6, 6);
2690 for (int i = 0; i < 10; i++) {
2691 for (int j = 0; j < 10; j++) {
2692 CGPoint point = CGPointMake(i, j);
2693 int alpha = [self alphaOfPoint:CGPointMake(i, j) onView:flutterView];
2694 if (CGRectContainsPoint(innerClipping1, point) ||
2695 CGRectContainsPoint(innerClipping2, point)) {
2696 // Pixels inside either of the 2 inner clippings should be fully opaque.
2697 XCTAssertEqual(alpha, 255);
2698 } else if (CGRectContainsPoint(outterClipping, point)) {
2699 // Corner pixels (i.e. (2, 2), (2, 7), (7, 2) and (7, 7)) should be partially transparent.
2700 XCTAssert(0 < alpha && alpha < 255);
2701 } else {
2702 // Pixels outside outterClipping should be fully transparent.
2703 XCTAssertEqual(alpha, 0);
2704 }
2705 }
2706 }
2707}
2708
2709- (void)testClipPath_multipleClips {
2710 flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
2711
2713 FlutterPlatformViewsController* flutterPlatformViewsController =
2714 [[FlutterPlatformViewsController alloc] init];
2715 flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
2716 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2717 /*delegate=*/mock_delegate,
2718 /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
2719 /*platform_views_controller=*/flutterPlatformViewsController,
2720 /*task_runners=*/runners.taskRunners,
2721 /*worker_task_runner=*/nil,
2722 /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
2723
2726 [flutterPlatformViewsController
2727 registerViewFactory:factory
2728 withId:@"MockFlutterPlatformView"
2729 gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
2730 FlutterResult result = ^(id result) {
2731 };
2732 [flutterPlatformViewsController
2734 arguments:@{
2735 @"id" : @2,
2736 @"viewType" : @"MockFlutterPlatformView",
2737 @"gestureBlockingPolicy" : @"eager"
2738 }]
2739 result:result];
2740
2741 XCTAssertNotNil(gMockPlatformView);
2742
2743 UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
2744 flutterPlatformViewsController.flutterView = flutterView;
2745 // Create embedded view params
2747 // Layer tree always pushes a screen scale factor to the stack
2748 flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
2749 flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
2750 stack.PushTransform(screenScaleMatrix);
2751 // Push a clip path
2752 flutter::DlPath path =
2754 stack.PushClipPath(path);
2755 // Push a clip rect
2756 flutter::DlRect rect = flutter::DlRect::MakeXYWH(4, 2, 6, 6);
2757 stack.PushClipRect(rect);
2758
2759 auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
2760 screenScaleMatrix, flutter::DlSize(10, 10), stack);
2761
2762 [flutterPlatformViewsController prerollCompositeEmbeddedView:2
2763 withParams:std::move(embeddedViewParams)];
2764 [flutterPlatformViewsController
2766 withParams:[flutterPlatformViewsController compositionParamsForView:2]];
2767
2768 gMockPlatformView.backgroundColor = UIColor.redColor;
2769 XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]);
2770 ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
2771 [flutterView addSubview:childClippingView];
2772
2773 [flutterView setNeedsLayout];
2774 [flutterView layoutIfNeeded];
2775
2776 /*
2777 clip 1 clip 2
2778 2 3 4 5 6 7 8 9 2 3 4 5 6 7 8 9
2779 2 / - - - - \ 2 + - - - - +
2780 3 | | 3 | |
2781 4 | | 4 | |
2782 5 | | 5 | |
2783 6 | | 6 | |
2784 7 \ - - - - / 7 + - - - - +
2785
2786 Result should be the intersection of 2 clips
2787 2 3 4 5 6 7 8 9
2788 2 + - - \
2789 3 | |
2790 4 | |
2791 5 | |
2792 6 | |
2793 7 + - - /
2794 */
2795 CGRect clipping = CGRectMake(4, 2, 4, 6);
2796 for (int i = 0; i < 10; i++) {
2797 for (int j = 0; j < 10; j++) {
2798 CGPoint point = CGPointMake(i, j);
2799 int alpha = [self alphaOfPoint:CGPointMake(i, j) onView:flutterView];
2800 if (i == 7 && (j == 2 || j == 7)) {
2801 // Upper and lower right corners should be partially transparent.
2802 XCTAssert(0 < alpha && alpha < 255);
2803 } else if (
2804 // left
2805 (i == 4 && j >= 2 && j <= 7) ||
2806 // right
2807 (i == 7 && j >= 2 && j <= 7) ||
2808 // top
2809 (j == 2 && i >= 4 && i <= 7) ||
2810 // bottom
2811 (j == 7 && i >= 4 && i <= 7)) {
2812 // Since we are falling back to software rendering for this case
2813 // The edge pixels can be anti-aliased, so it may not be fully opaque.
2814 XCTAssert(alpha > 127);
2815 } else if ((i == 3 && j >= 1 && j <= 8) || (i == 8 && j >= 1 && j <= 8) ||
2816 (j == 1 && i >= 3 && i <= 8) || (j == 8 && i >= 3 && i <= 8)) {
2817 // Since we are falling back to software rendering for this case
2818 // The edge pixels can be anti-aliased, so it may not be fully transparent.
2819 XCTAssert(alpha < 127);
2820 } else if (CGRectContainsPoint(clipping, point)) {
2821 // Other pixels inside clipping should be fully opaque.
2822 XCTAssertEqual(alpha, 255);
2823 } else {
2824 // Pixels outside clipping should be fully transparent.
2825 XCTAssertEqual(alpha, 0);
2826 }
2827 }
2828 }
2829}
2830
2831- (void)testSetFlutterViewControllerAfterCreateCanStillDispatchTouchEvents {
2832 flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
2833
2835 FlutterPlatformViewsController* flutterPlatformViewsController =
2836 [[FlutterPlatformViewsController alloc] init];
2837 flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
2838 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2839 /*delegate=*/mock_delegate,
2840 /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
2841 /*platform_views_controller=*/flutterPlatformViewsController,
2842 /*task_runners=*/runners.taskRunners,
2843 /*worker_task_runner=*/nil,
2844 /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
2845
2848 [flutterPlatformViewsController
2849 registerViewFactory:factory
2850 withId:@"MockFlutterPlatformView"
2851 gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
2852 FlutterResult result = ^(id result) {
2853 };
2854 [flutterPlatformViewsController
2856 arguments:@{
2857 @"id" : @2,
2858 @"viewType" : @"MockFlutterPlatformView",
2859 @"gestureBlockingPolicy" : @"eager"
2860 }]
2861 result:result];
2862
2863 XCTAssertNotNil(gMockPlatformView);
2864
2865 FlutterTouchInterceptingView* touchInterceptorView = FindTouchInterceptingView(gMockPlatformView);
2866 XCTAssertNotNil(touchInterceptorView);
2867
2868 UIGestureRecognizer* forwardGectureRecognizer =
2869 FindForwardingGestureRecognizer(touchInterceptorView);
2870
2871 // Before setting flutter view controller, events are not dispatched.
2872 NSSet* touches1 = [[NSSet alloc] init];
2873 id event1 = OCMClassMock([UIEvent class]);
2874 id flutterViewController = OCMClassMock([FlutterViewController class]);
2875 [forwardGectureRecognizer touchesBegan:touches1 withEvent:event1];
2876 OCMReject([flutterViewController touchesBegan:touches1 withEvent:event1]);
2877
2878 // Set flutter view controller allows events to be dispatched.
2879 NSSet* touches2 = [[NSSet alloc] init];
2880 id event2 = OCMClassMock([UIEvent class]);
2881 flutterPlatformViewsController.flutterViewController = flutterViewController;
2882 [forwardGectureRecognizer touchesBegan:touches2 withEvent:event2];
2883 OCMVerify([flutterViewController touchesBegan:touches2 withEvent:event2]);
2884}
2885
2886- (void)testSetFlutterViewControllerInTheMiddleOfTouchEventShouldStillAllowGesturesToBeHandled {
2887 flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
2888
2890 FlutterPlatformViewsController* flutterPlatformViewsController =
2891 [[FlutterPlatformViewsController alloc] init];
2892 flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
2893 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2894 /*delegate=*/mock_delegate,
2895 /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
2896 /*platform_views_controller=*/flutterPlatformViewsController,
2897 /*task_runners=*/runners.taskRunners,
2898 /*worker_task_runner=*/nil,
2899 /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
2900
2903 [flutterPlatformViewsController
2904 registerViewFactory:factory
2905 withId:@"MockFlutterPlatformView"
2906 gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
2907 FlutterResult result = ^(id result) {
2908 };
2909 [flutterPlatformViewsController
2911 arguments:@{
2912 @"id" : @2,
2913 @"viewType" : @"MockFlutterPlatformView",
2914 @"gestureBlockingPolicy" : @"eager"
2915 }]
2916 result:result];
2917
2918 XCTAssertNotNil(gMockPlatformView);
2919
2920 FlutterTouchInterceptingView* touchInterceptorView = FindTouchInterceptingView(gMockPlatformView);
2921 XCTAssertNotNil(touchInterceptorView);
2922
2923 UIGestureRecognizer* forwardGectureRecognizer =
2924 FindForwardingGestureRecognizer(touchInterceptorView);
2925 id flutterViewController = OCMClassMock([FlutterViewController class]);
2926 {
2927 // ***** Sequence 1, finishing touch event with touchEnded ***** //
2928 flutterPlatformViewsController.flutterViewController = flutterViewController;
2929
2930 NSSet* touches1 = [[NSSet alloc] init];
2931 id event1 = OCMClassMock([UIEvent class]);
2932 [forwardGectureRecognizer touchesBegan:touches1 withEvent:event1];
2933 OCMVerify([flutterViewController touchesBegan:touches1 withEvent:event1]);
2934
2935 flutterPlatformViewsController.flutterViewController = nil;
2936
2937 // Allow the touch events to finish
2938 NSSet* touches2 = [[NSSet alloc] init];
2939 id event2 = OCMClassMock([UIEvent class]);
2940 [forwardGectureRecognizer touchesMoved:touches2 withEvent:event2];
2941 OCMVerify([flutterViewController touchesMoved:touches2 withEvent:event2]);
2942
2943 NSSet* touches3 = [[NSSet alloc] init];
2944 id event3 = OCMClassMock([UIEvent class]);
2945 [forwardGectureRecognizer touchesEnded:touches3 withEvent:event3];
2946 OCMVerify([flutterViewController touchesEnded:touches3 withEvent:event3]);
2947
2948 // Now the 2nd touch sequence should not be allowed.
2949 NSSet* touches4 = [[NSSet alloc] init];
2950 id event4 = OCMClassMock([UIEvent class]);
2951 [forwardGectureRecognizer touchesBegan:touches4 withEvent:event4];
2952 OCMReject([flutterViewController touchesBegan:touches4 withEvent:event4]);
2953
2954 NSSet* touches5 = [[NSSet alloc] init];
2955 id event5 = OCMClassMock([UIEvent class]);
2956 [forwardGectureRecognizer touchesEnded:touches5 withEvent:event5];
2957 OCMReject([flutterViewController touchesEnded:touches5 withEvent:event5]);
2958 }
2959
2960 {
2961 // ***** Sequence 2, finishing touch event with touchCancelled ***** //
2962 flutterPlatformViewsController.flutterViewController = flutterViewController;
2963
2964 NSSet* touches1 = [[NSSet alloc] init];
2965 id event1 = OCMClassMock([UIEvent class]);
2966 [forwardGectureRecognizer touchesBegan:touches1 withEvent:event1];
2967 OCMVerify([flutterViewController touchesBegan:touches1 withEvent:event1]);
2968
2969 flutterPlatformViewsController.flutterViewController = nil;
2970
2971 // Allow the touch events to finish
2972 NSSet* touches2 = [[NSSet alloc] init];
2973 id event2 = OCMClassMock([UIEvent class]);
2974 [forwardGectureRecognizer touchesMoved:touches2 withEvent:event2];
2975 OCMVerify([flutterViewController touchesMoved:touches2 withEvent:event2]);
2976
2977 NSSet* touches3 = [[NSSet alloc] init];
2978 id event3 = OCMClassMock([UIEvent class]);
2979 [forwardGectureRecognizer touchesCancelled:touches3 withEvent:event3];
2980 OCMVerify([flutterViewController forceTouchesCancelled:touches3]);
2981
2982 // Now the 2nd touch sequence should not be allowed.
2983 NSSet* touches4 = [[NSSet alloc] init];
2984 id event4 = OCMClassMock([UIEvent class]);
2985 [forwardGectureRecognizer touchesBegan:touches4 withEvent:event4];
2986 OCMReject([flutterViewController touchesBegan:touches4 withEvent:event4]);
2987
2988 NSSet* touches5 = [[NSSet alloc] init];
2989 id event5 = OCMClassMock([UIEvent class]);
2990 [forwardGectureRecognizer touchesEnded:touches5 withEvent:event5];
2991 OCMReject([flutterViewController touchesEnded:touches5 withEvent:event5]);
2992 }
2993
2994 [flutterPlatformViewsController reset];
2995}
2996
2997- (void)
2998 testSetFlutterViewControllerInTheMiddleOfTouchEventAllowsTheNewControllerToHandleSecondTouchSequence {
2999 flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
3000
3002 FlutterPlatformViewsController* flutterPlatformViewsController =
3003 [[FlutterPlatformViewsController alloc] init];
3004 flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
3005 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
3006 /*delegate=*/mock_delegate,
3007 /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
3008 /*platform_views_controller=*/flutterPlatformViewsController,
3009 /*task_runners=*/runners.taskRunners,
3010 /*worker_task_runner=*/nil,
3011 /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
3012
3015 [flutterPlatformViewsController
3016 registerViewFactory:factory
3017 withId:@"MockFlutterPlatformView"
3018 gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
3019 FlutterResult result = ^(id result) {
3020 };
3021 [flutterPlatformViewsController
3023 arguments:@{
3024 @"id" : @2,
3025 @"viewType" : @"MockFlutterPlatformView",
3026 @"gestureBlockingPolicy" : @"eager"
3027 }]
3028 result:result];
3029
3030 XCTAssertNotNil(gMockPlatformView);
3031
3032 FlutterTouchInterceptingView* touchInterceptorView = FindTouchInterceptingView(gMockPlatformView);
3033 XCTAssertNotNil(touchInterceptorView);
3034
3035 UIGestureRecognizer* forwardGectureRecognizer =
3036 FindForwardingGestureRecognizer(touchInterceptorView);
3037 id flutterViewController = OCMClassMock([FlutterViewController class]);
3038 flutterPlatformViewsController.flutterViewController = flutterViewController;
3039
3040 // The touches in this sequence requires 1 touch object, we always create the NSSet with one item.
3041 NSSet* touches1 = [NSSet setWithObject:@1];
3042 id event1 = OCMClassMock([UIEvent class]);
3043 [forwardGectureRecognizer touchesBegan:touches1 withEvent:event1];
3044 OCMVerify([flutterViewController touchesBegan:touches1 withEvent:event1]);
3045
3046 FlutterViewController* flutterViewController2 = OCMClassMock([FlutterViewController class]);
3047 flutterPlatformViewsController.flutterViewController = flutterViewController2;
3048
3049 // Touch events should still send to the old FlutterViewController if FlutterViewController
3050 // is updated in between.
3051 NSSet* touches2 = [NSSet setWithObject:@1];
3052 id event2 = OCMClassMock([UIEvent class]);
3053 [forwardGectureRecognizer touchesBegan:touches2 withEvent:event2];
3054 OCMVerify([flutterViewController touchesBegan:touches2 withEvent:event2]);
3055 OCMReject([flutterViewController2 touchesBegan:touches2 withEvent:event2]);
3056
3057 NSSet* touches3 = [NSSet setWithObject:@1];
3058 id event3 = OCMClassMock([UIEvent class]);
3059 [forwardGectureRecognizer touchesMoved:touches3 withEvent:event3];
3060 OCMVerify([flutterViewController touchesMoved:touches3 withEvent:event3]);
3061 OCMReject([flutterViewController2 touchesMoved:touches3 withEvent:event3]);
3062
3063 NSSet* touches4 = [NSSet setWithObject:@1];
3064 id event4 = OCMClassMock([UIEvent class]);
3065 [forwardGectureRecognizer touchesEnded:touches4 withEvent:event4];
3066 OCMVerify([flutterViewController touchesEnded:touches4 withEvent:event4]);
3067 OCMReject([flutterViewController2 touchesEnded:touches4 withEvent:event4]);
3068
3069 NSSet* touches5 = [NSSet setWithObject:@1];
3070 id event5 = OCMClassMock([UIEvent class]);
3071 [forwardGectureRecognizer touchesEnded:touches5 withEvent:event5];
3072 OCMVerify([flutterViewController touchesEnded:touches5 withEvent:event5]);
3073 OCMReject([flutterViewController2 touchesEnded:touches5 withEvent:event5]);
3074
3075 // Now the 2nd touch sequence should go to the new FlutterViewController
3076
3077 NSSet* touches6 = [NSSet setWithObject:@1];
3078 id event6 = OCMClassMock([UIEvent class]);
3079 [forwardGectureRecognizer touchesBegan:touches6 withEvent:event6];
3080 OCMVerify([flutterViewController2 touchesBegan:touches6 withEvent:event6]);
3081 OCMReject([flutterViewController touchesBegan:touches6 withEvent:event6]);
3082
3083 // Allow the touch events to finish
3084 NSSet* touches7 = [NSSet setWithObject:@1];
3085 id event7 = OCMClassMock([UIEvent class]);
3086 [forwardGectureRecognizer touchesMoved:touches7 withEvent:event7];
3087 OCMVerify([flutterViewController2 touchesMoved:touches7 withEvent:event7]);
3088 OCMReject([flutterViewController touchesMoved:touches7 withEvent:event7]);
3089
3090 NSSet* touches8 = [NSSet setWithObject:@1];
3091 id event8 = OCMClassMock([UIEvent class]);
3092 [forwardGectureRecognizer touchesEnded:touches8 withEvent:event8];
3093 OCMVerify([flutterViewController2 touchesEnded:touches8 withEvent:event8]);
3094 OCMReject([flutterViewController touchesEnded:touches8 withEvent:event8]);
3095
3096 [flutterPlatformViewsController reset];
3097}
3098
3099- (void)testFlutterPlatformViewTouchesCancelledEventAreForcedToBeCancelled {
3100 flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
3101
3103 FlutterPlatformViewsController* flutterPlatformViewsController =
3104 [[FlutterPlatformViewsController alloc] init];
3105 flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
3106 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
3107 /*delegate=*/mock_delegate,
3108 /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
3109 /*platform_views_controller=*/flutterPlatformViewsController,
3110 /*task_runners=*/runners.taskRunners,
3111 /*worker_task_runner=*/nil,
3112 /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
3113
3116 [flutterPlatformViewsController
3117 registerViewFactory:factory
3118 withId:@"MockFlutterPlatformView"
3119 gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
3120 FlutterResult result = ^(id result) {
3121 };
3122 [flutterPlatformViewsController
3124 arguments:@{
3125 @"id" : @2,
3126 @"viewType" : @"MockFlutterPlatformView",
3127 @"gestureBlockingPolicy" : @"eager"
3128 }]
3129 result:result];
3130
3131 XCTAssertNotNil(gMockPlatformView);
3132
3133 FlutterTouchInterceptingView* touchInterceptorView = FindTouchInterceptingView(gMockPlatformView);
3134 XCTAssertNotNil(touchInterceptorView);
3135
3136 UIGestureRecognizer* forwardGectureRecognizer =
3137 FindForwardingGestureRecognizer(touchInterceptorView);
3138 id flutterViewController = OCMClassMock([FlutterViewController class]);
3139 flutterPlatformViewsController.flutterViewController = flutterViewController;
3140
3141 NSSet* touches1 = [NSSet setWithObject:@1];
3142 id event1 = OCMClassMock([UIEvent class]);
3143 [forwardGectureRecognizer touchesBegan:touches1 withEvent:event1];
3144
3145 [forwardGectureRecognizer touchesCancelled:touches1 withEvent:event1];
3146 OCMVerify([flutterViewController forceTouchesCancelled:touches1]);
3147
3148 [flutterPlatformViewsController reset];
3149}
3150
3151- (void)testFlutterPlatformViewTouchesEndedOrTouchesCancelledEventDoesNotFailTheGestureRecognizer {
3152 // This test verifies a workaround for an Apple bug (flutter/flutter#136244) where the gesture
3153 // recognizer gets stuck at UIGestureRecognizerStateFailed. Apple fixed this bug in iOS 26,
3154 // so the workaround (and this test) is no longer needed on iOS 26+.
3155 // See: https://github.com/flutter/flutter/issues/179907
3156 if (@available(iOS 26.0, *)) {
3157 return;
3158 }
3159
3160 flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
3161
3163 FlutterPlatformViewsController* flutterPlatformViewsController =
3164 [[FlutterPlatformViewsController alloc] init];
3165 flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
3166 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
3167 /*delegate=*/mock_delegate,
3168 /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
3169 /*platform_views_controller=*/flutterPlatformViewsController,
3170 /*task_runners=*/runners.taskRunners,
3171 /*worker_task_runner=*/nil,
3172 /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
3173
3176 [flutterPlatformViewsController
3177 registerViewFactory:factory
3178 withId:@"MockFlutterPlatformView"
3179 gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
3180 FlutterResult result = ^(id result) {
3181 };
3182 [flutterPlatformViewsController
3184 arguments:@{
3185 @"id" : @2,
3186 @"viewType" : @"MockFlutterPlatformView",
3187 @"gestureBlockingPolicy" : @"eager"
3188 }]
3189 result:result];
3190
3191 XCTAssertNotNil(gMockPlatformView);
3192
3193 FlutterTouchInterceptingView* touchInterceptorView = FindTouchInterceptingView(gMockPlatformView);
3194 XCTAssertNotNil(touchInterceptorView);
3195
3196 __block UIGestureRecognizer* forwardGestureRecognizer =
3197 FindForwardingGestureRecognizer(touchInterceptorView);
3198 id flutterViewController = OCMClassMock([FlutterViewController class]);
3199 flutterPlatformViewsController.flutterViewController = flutterViewController;
3200
3201 NSSet* touches1 = [NSSet setWithObject:@1];
3202 id event1 = OCMClassMock([UIEvent class]);
3203 XCTAssert(forwardGestureRecognizer.state == UIGestureRecognizerStatePossible,
3204 @"Forwarding gesture recognizer must start with possible state.");
3205 [forwardGestureRecognizer touchesBegan:touches1 withEvent:event1];
3206 [forwardGestureRecognizer touchesEnded:touches1 withEvent:event1];
3207 XCTAssert(forwardGestureRecognizer.state == UIGestureRecognizerStateFailed,
3208 @"Forwarding gesture recognizer must end with failed state.");
3209
3210 XCTestExpectation* touchEndedExpectation =
3211 [self expectationWithDescription:@"Wait for gesture recognizer's state change."];
3212 dispatch_async(dispatch_get_main_queue(), ^{
3213 // Re-query forward gesture recognizer since it's recreated.
3214 forwardGestureRecognizer = FindForwardingGestureRecognizer(touchInterceptorView);
3215 XCTAssert(forwardGestureRecognizer.state == UIGestureRecognizerStatePossible,
3216 @"Forwarding gesture recognizer must be reset to possible state.");
3217 [touchEndedExpectation fulfill];
3218 });
3219 [self waitForExpectationsWithTimeout:30 handler:nil];
3220
3221 XCTAssert(forwardGestureRecognizer.state == UIGestureRecognizerStatePossible,
3222 @"Forwarding gesture recognizer must start with possible state.");
3223 [forwardGestureRecognizer touchesBegan:touches1 withEvent:event1];
3224 [forwardGestureRecognizer touchesCancelled:touches1 withEvent:event1];
3225 XCTAssert(forwardGestureRecognizer.state == UIGestureRecognizerStateFailed,
3226 @"Forwarding gesture recognizer must end with failed state.");
3227 XCTestExpectation* touchCancelledExpectation =
3228 [self expectationWithDescription:@"Wait for gesture recognizer's state change."];
3229 dispatch_async(dispatch_get_main_queue(), ^{
3230 // Re-query forward gesture recognizer since it's recreated.
3231 forwardGestureRecognizer = FindForwardingGestureRecognizer(touchInterceptorView);
3232 XCTAssert(forwardGestureRecognizer.state == UIGestureRecognizerStatePossible,
3233 @"Forwarding gesture recognizer must be reset to possible state.");
3234 [touchCancelledExpectation fulfill];
3235 });
3236 [self waitForExpectationsWithTimeout:30 handler:nil];
3237
3238 [flutterPlatformViewsController reset];
3239}
3240
3241- (void)
3242 testFlutterPlatformViewBlockGestureUnderEagerPolicyShouldRemoveAndAddBackDelayingRecognizerForWebView {
3243 flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
3244
3246 FlutterPlatformViewsController* flutterPlatformViewsController =
3247 [[FlutterPlatformViewsController alloc] init];
3248 flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
3249 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
3250 /*delegate=*/mock_delegate,
3251 /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
3252 /*platform_views_controller=*/flutterPlatformViewsController,
3253 /*task_runners=*/runners.taskRunners,
3254 /*worker_task_runner=*/nil,
3255 /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
3256
3259 [flutterPlatformViewsController
3260 registerViewFactory:factory
3261 withId:@"MockWebView"
3262 gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
3263 FlutterResult result = ^(id result) {
3264 };
3265 [flutterPlatformViewsController
3267 arguments:@{
3268 @"id" : @2,
3269 @"viewType" : @"MockWebView",
3270 @"gestureBlockingPolicy" : @"eager"
3271 }]
3272 result:result];
3273
3274 XCTAssertNotNil(gMockPlatformView);
3275
3276 FlutterTouchInterceptingView* touchInterceptorView = FindTouchInterceptingView(gMockPlatformView);
3277 XCTAssertNotNil(touchInterceptorView);
3278
3279 XCTAssert(touchInterceptorView.gestureRecognizers.count == 2);
3280 UIGestureRecognizer* delayingRecognizer = touchInterceptorView.gestureRecognizers[0];
3281 UIGestureRecognizer* forwardingRecognizer = touchInterceptorView.gestureRecognizers[1];
3282
3283 XCTAssert([delayingRecognizer isKindOfClass:[FlutterDelayingGestureRecognizer class]]);
3284 XCTAssert([forwardingRecognizer isKindOfClass:[ForwardingGestureRecognizer class]]);
3285
3286 [touchInterceptorView blockGesture];
3287
3288 BOOL shouldReAddDelayingRecognizer = NO;
3289 if (@available(iOS 26.0, *)) {
3290 // TODO(hellohuanlin): find a solution for iOS 26,
3291 // https://github.com/flutter/flutter/issues/175099.
3292 } else if (@available(iOS 18.2, *)) {
3293 shouldReAddDelayingRecognizer = YES;
3294 }
3295 if (shouldReAddDelayingRecognizer) {
3296 // Since we remove and add back delayingRecognizer, it would be reordered to the last.
3297 XCTAssertEqual(touchInterceptorView.gestureRecognizers[0], forwardingRecognizer);
3298 XCTAssertEqual(touchInterceptorView.gestureRecognizers[1], delayingRecognizer);
3299 } else {
3300 XCTAssertEqual(touchInterceptorView.gestureRecognizers[0], delayingRecognizer);
3301 XCTAssertEqual(touchInterceptorView.gestureRecognizers[1], forwardingRecognizer);
3302 }
3303}
3304
3305- (void)
3306 testFlutterPlatformViewBlockGestureUnderEagerPolicyShouldRemoveAndAddBackDelayingRecognizerForWrapperWebView {
3307 flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
3308
3310 FlutterPlatformViewsController* flutterPlatformViewsController =
3311 [[FlutterPlatformViewsController alloc] init];
3312 flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
3313 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
3314 /*delegate=*/mock_delegate,
3315 /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
3316 /*platform_views_controller=*/flutterPlatformViewsController,
3317 /*task_runners=*/runners.taskRunners,
3318 /*worker_task_runner=*/nil,
3319 /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
3320
3323 [flutterPlatformViewsController
3324 registerViewFactory:factory
3325 withId:@"MockWrapperWebView"
3326 gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
3327 FlutterResult result = ^(id result) {
3328 };
3329 [flutterPlatformViewsController
3331 methodCallWithMethodName:@"create"
3332 arguments:@{
3333 @"id" : @2,
3334 @"viewType" : @"MockWrapperWebView",
3335 @"gestureBlockingPolicy" : @"fallbackToPluginDefault"
3336 }]
3337 result:result];
3338
3339 XCTAssertNotNil(gMockPlatformView);
3340
3341 FlutterTouchInterceptingView* touchInterceptorView = FindTouchInterceptingView(gMockPlatformView);
3342 XCTAssertNotNil(touchInterceptorView);
3343
3344 XCTAssert(touchInterceptorView.gestureRecognizers.count == 2);
3345 UIGestureRecognizer* delayingRecognizer = touchInterceptorView.gestureRecognizers[0];
3346 UIGestureRecognizer* forwardingRecognizer = touchInterceptorView.gestureRecognizers[1];
3347
3348 XCTAssert([delayingRecognizer isKindOfClass:[FlutterDelayingGestureRecognizer class]]);
3349 XCTAssert([forwardingRecognizer isKindOfClass:[ForwardingGestureRecognizer class]]);
3350
3351 [touchInterceptorView blockGesture];
3352
3353 BOOL shouldReAddDelayingRecognizer = NO;
3354 if (@available(iOS 26.0, *)) {
3355 // We use a different workaround for iOS 26 and it is tested separately.
3356 // See:
3357 // testFlutterPlatformViewBlockGestureUnderEagerPolicyShouldDisableAndReEnableTouchEventsGestureRecognizerForSimpleWebView.
3358 } else if (@available(iOS 18.2, *)) {
3359 shouldReAddDelayingRecognizer = YES;
3360 }
3361 if (shouldReAddDelayingRecognizer) {
3362 // Since we remove and add back delayingRecognizer, it would be reordered to the last.
3363 XCTAssertEqual(touchInterceptorView.gestureRecognizers[0], forwardingRecognizer);
3364 XCTAssertEqual(touchInterceptorView.gestureRecognizers[1], delayingRecognizer);
3365 } else {
3366 XCTAssertEqual(touchInterceptorView.gestureRecognizers[0], delayingRecognizer);
3367 XCTAssertEqual(touchInterceptorView.gestureRecognizers[1], forwardingRecognizer);
3368 }
3369}
3370
3371- (void)
3372 testFlutterPlatformViewBlockGestureUnderEagerPolicyShouldRemoveAndAddBackDelayingRecognizerForNestedWrapperWebView {
3373 flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
3374
3376 FlutterPlatformViewsController* flutterPlatformViewsController =
3377 [[FlutterPlatformViewsController alloc] init];
3378 flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
3379 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
3380 /*delegate=*/mock_delegate,
3381 /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
3382 /*platform_views_controller=*/flutterPlatformViewsController,
3383 /*task_runners=*/runners.taskRunners,
3384 /*worker_task_runner=*/nil,
3385 /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
3386
3389 [flutterPlatformViewsController
3390 registerViewFactory:factory
3391 withId:@"MockNestedWrapperWebView"
3392 gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
3393 FlutterResult result = ^(id result) {
3394 };
3395 [flutterPlatformViewsController
3397 arguments:@{
3398 @"id" : @2,
3399 @"viewType" : @"MockNestedWrapperWebView",
3400 @"gestureBlockingPolicy" : @"eager"
3401 }]
3402 result:result];
3403
3404 XCTAssertNotNil(gMockPlatformView);
3405
3406 FlutterTouchInterceptingView* touchInterceptorView = FindTouchInterceptingView(gMockPlatformView);
3407 XCTAssertNotNil(touchInterceptorView);
3408
3409 XCTAssert(touchInterceptorView.gestureRecognizers.count == 2);
3410 UIGestureRecognizer* delayingRecognizer = touchInterceptorView.gestureRecognizers[0];
3411 UIGestureRecognizer* forwardingRecognizer = touchInterceptorView.gestureRecognizers[1];
3412
3413 XCTAssert([delayingRecognizer isKindOfClass:[FlutterDelayingGestureRecognizer class]]);
3414 XCTAssert([forwardingRecognizer isKindOfClass:[ForwardingGestureRecognizer class]]);
3415
3416 [touchInterceptorView blockGesture];
3417
3418 BOOL shouldReAddDelayingRecognizer = NO;
3419 if (@available(iOS 26.0, *)) {
3420 // We use a different workaround for iOS 26 and it is tested separately.
3421 // See:
3422 // testFlutterPlatformViewBlockGestureUnderEagerPolicyShouldDisableAndReEnableTouchEventsGestureRecognizerForSimpleWebView.
3423 } else if (@available(iOS 18.2, *)) {
3424 shouldReAddDelayingRecognizer = YES;
3425 }
3426 if (shouldReAddDelayingRecognizer) {
3427 // Since we remove and add back delayingRecognizer, it would be reordered to the last.
3428 XCTAssertEqual(touchInterceptorView.gestureRecognizers[0], forwardingRecognizer);
3429 XCTAssertEqual(touchInterceptorView.gestureRecognizers[1], delayingRecognizer);
3430 } else {
3431 XCTAssertEqual(touchInterceptorView.gestureRecognizers[0], delayingRecognizer);
3432 XCTAssertEqual(touchInterceptorView.gestureRecognizers[1], forwardingRecognizer);
3433 }
3434}
3435
3436- (void)
3437 testFlutterPlatformViewBlockGestureUnderEagerPolicyShouldNotRemoveAndAddBackDelayingRecognizerForNonWebView {
3438 flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
3439
3441 FlutterPlatformViewsController* flutterPlatformViewsController =
3442 [[FlutterPlatformViewsController alloc] init];
3443 flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
3444 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
3445 /*delegate=*/mock_delegate,
3446 /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
3447 /*platform_views_controller=*/flutterPlatformViewsController,
3448 /*task_runners=*/runners.taskRunners,
3449 /*worker_task_runner=*/nil,
3450 /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
3451
3454 [flutterPlatformViewsController
3455 registerViewFactory:factory
3456 withId:@"MockFlutterPlatformView"
3457 gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
3458 FlutterResult result = ^(id result) {
3459 };
3460 [flutterPlatformViewsController
3462 arguments:@{
3463 @"id" : @2,
3464 @"viewType" : @"MockFlutterPlatformView",
3465 @"gestureBlockingPolicy" : @"eager"
3466 }]
3467 result:result];
3468
3469 XCTAssertNotNil(gMockPlatformView);
3470
3471 FlutterTouchInterceptingView* touchInterceptorView = FindTouchInterceptingView(gMockPlatformView);
3472 XCTAssertNotNil(touchInterceptorView);
3473
3474 XCTAssert(touchInterceptorView.gestureRecognizers.count == 2);
3475 UIGestureRecognizer* delayingRecognizer = touchInterceptorView.gestureRecognizers[0];
3476 UIGestureRecognizer* forwardingRecognizer = touchInterceptorView.gestureRecognizers[1];
3477
3478 XCTAssert([delayingRecognizer isKindOfClass:[FlutterDelayingGestureRecognizer class]]);
3479 XCTAssert([forwardingRecognizer isKindOfClass:[ForwardingGestureRecognizer class]]);
3480
3481 [touchInterceptorView blockGesture];
3482
3483 XCTAssertEqual(touchInterceptorView.gestureRecognizers[0], delayingRecognizer);
3484 XCTAssertEqual(touchInterceptorView.gestureRecognizers[1], forwardingRecognizer);
3485}
3486
3487- (void)
3488 testFlutterPlatformViewBlockGestureUnderEagerPolicyShouldDisableAndReEnableTouchEventsGestureRecognizerForSimpleWebView {
3489 if (@available(iOS 26.0, *)) {
3490 flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
3491
3493 FlutterPlatformViewsController* flutterPlatformViewsController =
3494 [[FlutterPlatformViewsController alloc] init];
3495 flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
3496 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
3497 /*delegate=*/mock_delegate,
3498 /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
3499 /*platform_views_controller=*/flutterPlatformViewsController,
3500 /*task_runners=*/runners.taskRunners,
3501 /*worker_task_runner=*/nil,
3502 /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
3503
3506 [flutterPlatformViewsController
3507 registerViewFactory:factory
3508 withId:@"MockWebView"
3509 gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
3510 FlutterResult result = ^(id result) {
3511 };
3512 [flutterPlatformViewsController
3514 methodCallWithMethodName:@"create"
3515 arguments:@{
3516 @"id" : @2,
3517 @"viewType" : @"MockWebView",
3518 @"gestureBlockingPolicy" : @"fallbackToPluginDefault"
3519 }]
3520 result:result];
3521
3522 XCTAssertNotNil(gMockPlatformView);
3523
3524 FlutterTouchInterceptingView* touchInterceptorView =
3525 FindTouchInterceptingView(gMockPlatformView);
3526 XCTAssertNotNil(touchInterceptorView);
3527
3528 /*
3529 Simple Web View at root, with [*] indicating views containing
3530 MockTouchEventsGestureRecognizer.
3531
3532 Root (Web View) [*]
3533 ├── Child 1
3534 └── Child 2
3535 ├── Child 2.1
3536 └── Child 2.2 [*]
3537 */
3538
3539 UIView* root = gMockPlatformView;
3540 root.gestureRecognizers = nil;
3541 for (UIView* subview in root.subviews) {
3542 [subview removeFromSuperview];
3543 }
3544
3545 MockGestureRecognizer* normalRecognizer0 = [[MockGestureRecognizer alloc] init];
3546 [root addGestureRecognizer:normalRecognizer0];
3547
3548 UIView* child1 = [[UIView alloc] init];
3549 [root addSubview:child1];
3550 MockGestureRecognizer* normalRecognizer1 = [[MockGestureRecognizer alloc] init];
3551 [child1 addGestureRecognizer:normalRecognizer1];
3552
3553 UIView* child2 = [[UIView alloc] init];
3554 [root addSubview:child2];
3555 MockGestureRecognizer* normalRecognizer2 = [[MockGestureRecognizer alloc] init];
3556 [child2 addGestureRecognizer:normalRecognizer2];
3557
3558 UIView* child2_1 = [[UIView alloc] init];
3559 [child2 addSubview:child2_1];
3560 MockGestureRecognizer* normalRecognizer2_1 = [[MockGestureRecognizer alloc] init];
3561 [child2_1 addGestureRecognizer:normalRecognizer2_1];
3562
3563 UIView* child2_2 = [[UIView alloc] init];
3564 [child2 addSubview:child2_2];
3565 MockGestureRecognizer* normalRecognizer2_2 = [[MockGestureRecognizer alloc] init];
3566 [child2_2 addGestureRecognizer:normalRecognizer2_2];
3567
3568 // Add the target recognizer at root & child2_2.
3569 MockTouchEventsGestureRecognizer* targetRecognizer0 =
3570 [[MockTouchEventsGestureRecognizer alloc] init];
3571 [root addGestureRecognizer:targetRecognizer0];
3572
3573 MockTouchEventsGestureRecognizer* targetRecognizer2_2 =
3574 [[MockTouchEventsGestureRecognizer alloc] init];
3575 [child2_2 addGestureRecognizer:targetRecognizer2_2];
3576
3577 [touchInterceptorView blockGesture];
3578
3579 NSArray* normalRecognizers = @[
3580 normalRecognizer0, normalRecognizer1, normalRecognizer2, normalRecognizer2_1,
3581 normalRecognizer2_2
3582 ];
3583
3584 NSArray* targetRecognizers = @[ targetRecognizer0, targetRecognizer2_2 ];
3585
3586 NSArray* expectedEmptyHistory = @[];
3587 NSArray* expectedToggledHistory = @[ @NO, @YES ];
3588
3589 for (MockGestureRecognizer* recognizer in normalRecognizers) {
3590 XCTAssertEqualObjects(recognizer.toggleHistory, expectedEmptyHistory);
3591 }
3592 for (MockGestureRecognizer* recognizer in targetRecognizers) {
3593 XCTAssertEqualObjects(recognizer.toggleHistory, expectedToggledHistory);
3594 }
3595 }
3596}
3597
3598- (void)
3599 testFlutterPlatformViewBlockGestureUnderEagerPolicyShouldDisableAndReEnableTouchEventsGestureRecognizerForMultipleWebViewInDifferentBranches {
3600 if (@available(iOS 26.0, *)) {
3601 flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
3602
3604 FlutterPlatformViewsController* flutterPlatformViewsController =
3605 [[FlutterPlatformViewsController alloc] init];
3606 flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
3607 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
3608 /*delegate=*/mock_delegate,
3609 /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
3610 /*platform_views_controller=*/flutterPlatformViewsController,
3611 /*task_runners=*/runners.taskRunners,
3612 /*worker_task_runner=*/nil,
3613 /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
3614
3617 [flutterPlatformViewsController
3618 registerViewFactory:factory
3619 withId:@"MockWrapperWebView"
3620 gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
3621 FlutterResult result = ^(id result) {
3622 };
3623 [flutterPlatformViewsController
3625 methodCallWithMethodName:@"create"
3626 arguments:@{
3627 @"id" : @2,
3628 @"viewType" : @"MockWrapperWebView",
3629 @"gestureBlockingPolicy" : @"fallbackToPluginDefault"
3630 }]
3631 result:result];
3632
3633 XCTAssertNotNil(gMockPlatformView);
3634
3635 FlutterTouchInterceptingView* touchInterceptorView =
3636 FindTouchInterceptingView(gMockPlatformView);
3637 XCTAssertNotNil(touchInterceptorView);
3638
3639 /*
3640 Platform View with Multiple Web Views in different branches, with [*] indicating views
3641 containing MockTouchEventsGestureRecognizer.
3642
3643 Root (Platform View)
3644 ├── Child 1
3645 ├── Child 2 (Web View)
3646 | ├── Child 2.1
3647 | └── Child 2.2 [*]
3648 └── Child 3
3649 └── Child 3.1 (Web View)
3650 ├── Child 3.1.1
3651 └── Child 3.1.2 [*]
3652 */
3653
3654 UIView* root = gMockPlatformView;
3655 for (UIView* subview in root.subviews) {
3656 [subview removeFromSuperview];
3657 }
3658
3659 MockGestureRecognizer* normalRecognizer0 = [[MockGestureRecognizer alloc] init];
3660 [root addGestureRecognizer:normalRecognizer0];
3661
3662 UIView* child1 = [[UIView alloc] init];
3663 [root addSubview:child1];
3664 MockGestureRecognizer* normalRecognizer1 = [[MockGestureRecognizer alloc] init];
3665 [child1 addGestureRecognizer:normalRecognizer1];
3666
3667 UIView* child2 = [[WKWebView alloc] init];
3668 child2.gestureRecognizers = nil;
3669 for (UIView* subview in child2.subviews) {
3670 [subview removeFromSuperview];
3671 }
3672 [root addSubview:child2];
3673 MockGestureRecognizer* normalRecognizer2 = [[MockGestureRecognizer alloc] init];
3674 [child2 addGestureRecognizer:normalRecognizer2];
3675
3676 UIView* child2_1 = [[UIView alloc] init];
3677 [child2 addSubview:child2_1];
3678 MockGestureRecognizer* normalRecognizer2_1 = [[MockGestureRecognizer alloc] init];
3679 [child2_1 addGestureRecognizer:normalRecognizer2_1];
3680
3681 UIView* child2_2 = [[UIView alloc] init];
3682 [child2 addSubview:child2_2];
3683 MockGestureRecognizer* normalRecognizer2_2 = [[MockGestureRecognizer alloc] init];
3684 [child2_2 addGestureRecognizer:normalRecognizer2_2];
3685
3686 UIView* child3 = [[UIView alloc] init];
3687 [root addSubview:child3];
3688 MockGestureRecognizer* normalRecognizer3 = [[MockGestureRecognizer alloc] init];
3689 [child3 addGestureRecognizer:normalRecognizer3];
3690
3691 UIView* child3_1 = [[WKWebView alloc] init];
3692 child3_1.gestureRecognizers = nil;
3693 for (UIView* subview in child3_1.subviews) {
3694 [subview removeFromSuperview];
3695 }
3696 [child3 addSubview:child3_1];
3697 MockGestureRecognizer* normalRecognizer3_1 = [[MockGestureRecognizer alloc] init];
3698 [child3_1 addGestureRecognizer:normalRecognizer3_1];
3699
3700 UIView* child3_1_1 = [[UIView alloc] init];
3701 [child3_1 addSubview:child3_1_1];
3702 MockGestureRecognizer* normalRecognizer3_1_1 = [[MockGestureRecognizer alloc] init];
3703 [child3_1_1 addGestureRecognizer:normalRecognizer3_1_1];
3704
3705 UIView* child3_1_2 = [[UIView alloc] init];
3706 [child3_1 addSubview:child3_1_2];
3707 MockGestureRecognizer* normalRecognizer3_1_2 = [[MockGestureRecognizer alloc] init];
3708 [child3_1_2 addGestureRecognizer:normalRecognizer3_1_2];
3709
3710 // Add the target recognizer at child2_2 & child3_1_2
3711
3712 MockTouchEventsGestureRecognizer* targetRecognizer2_2 =
3713 [[MockTouchEventsGestureRecognizer alloc] init];
3714 [child2_2 addGestureRecognizer:targetRecognizer2_2];
3715
3716 MockTouchEventsGestureRecognizer* targetRecognizer3_1_2 =
3717 [[MockTouchEventsGestureRecognizer alloc] init];
3718 [child3_1_2 addGestureRecognizer:targetRecognizer3_1_2];
3719
3720 [touchInterceptorView blockGesture];
3721
3722 NSArray* normalRecognizers = @[
3723 normalRecognizer0, normalRecognizer1, normalRecognizer2, normalRecognizer2_1,
3724 normalRecognizer2_2, normalRecognizer3, normalRecognizer3_1, normalRecognizer3_1_1,
3725 normalRecognizer3_1_2
3726 ];
3727 NSArray* targetRecognizers = @[ targetRecognizer2_2, targetRecognizer3_1_2 ];
3728
3729 NSArray* expectedEmptyHistory = @[];
3730 NSArray* expectedToggledHistory = @[ @NO, @YES ];
3731
3732 for (MockGestureRecognizer* recognizer in normalRecognizers) {
3733 XCTAssertEqualObjects(recognizer.toggleHistory, expectedEmptyHistory);
3734 }
3735
3736 for (MockGestureRecognizer* recognizer in targetRecognizers) {
3737 XCTAssertEqualObjects(recognizer.toggleHistory, expectedToggledHistory);
3738 }
3739 }
3740}
3741
3742- (void)
3743 testFlutterPlatformViewBlockGestureUnderEagerPolicyShouldDisableAndReEnableTouchEventsGestureRecognizerForNestedMultipleWebView {
3744 if (@available(iOS 26.0, *)) {
3745 flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
3746
3748 FlutterPlatformViewsController* flutterPlatformViewsController =
3749 [[FlutterPlatformViewsController alloc] init];
3750 flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
3751 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
3752 /*delegate=*/mock_delegate,
3753 /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
3754 /*platform_views_controller=*/flutterPlatformViewsController,
3755 /*task_runners=*/runners.taskRunners,
3756 /*worker_task_runner=*/nil,
3757 /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
3758
3761 [flutterPlatformViewsController
3762 registerViewFactory:factory
3763 withId:@"MockWebView"
3764 gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
3765 FlutterResult result = ^(id result) {
3766 };
3767 [flutterPlatformViewsController
3769 methodCallWithMethodName:@"create"
3770 arguments:@{
3771 @"id" : @2,
3772 @"viewType" : @"MockWebView",
3773 @"gestureBlockingPolicy" : @"fallbackToPluginDefault"
3774 }]
3775 result:result];
3776
3777 XCTAssertNotNil(gMockPlatformView);
3778
3779 FlutterTouchInterceptingView* touchInterceptorView =
3780 FindTouchInterceptingView(gMockPlatformView);
3781 XCTAssertNotNil(touchInterceptorView);
3782
3783 /*
3784 Platform View with nested web views, with [*] indicating views containing
3785 MockTouchEventsGestureRecognizer.
3786
3787 Root (Web View)
3788 ├── Child 1
3789 ├── Child 2
3790 | ├── Child 2.1
3791 | └── Child 2.2 [*]
3792 └── Child 3
3793 └── Child 3.1 (Another Web View)
3794 └── Child 3.1.1
3795 └── Child 3.1.2
3796 ├── Child 3.1.2.1
3797 └── Child 3.1.2.2 [*]
3798 */
3799
3800 UIView* root = gMockPlatformView;
3801 root.gestureRecognizers = nil;
3802 for (UIView* subview in root.subviews) {
3803 [subview removeFromSuperview];
3804 }
3805
3806 MockGestureRecognizer* normalRecognizer0 = [[MockGestureRecognizer alloc] init];
3807 [root addGestureRecognizer:normalRecognizer0];
3808
3809 UIView* child1 = [[UIView alloc] init];
3810 [root addSubview:child1];
3811 MockGestureRecognizer* normalRecognizer1 = [[MockGestureRecognizer alloc] init];
3812 [child1 addGestureRecognizer:normalRecognizer1];
3813
3814 UIView* child2 = [[UIView alloc] init];
3815 [root addSubview:child2];
3816 MockGestureRecognizer* normalRecognizer2 = [[MockGestureRecognizer alloc] init];
3817 [child2 addGestureRecognizer:normalRecognizer2];
3818
3819 UIView* child2_1 = [[UIView alloc] init];
3820 [child2 addSubview:child2_1];
3821 MockGestureRecognizer* normalRecognizer2_1 = [[MockGestureRecognizer alloc] init];
3822 [child2_1 addGestureRecognizer:normalRecognizer2_1];
3823
3824 UIView* child2_2 = [[UIView alloc] init];
3825 [child2 addSubview:child2_2];
3826 MockGestureRecognizer* normalRecognizer2_2 = [[MockGestureRecognizer alloc] init];
3827 [child2_2 addGestureRecognizer:normalRecognizer2_2];
3828
3829 UIView* child3 = [[UIView alloc] init];
3830 [root addSubview:child3];
3831 MockGestureRecognizer* normalRecognizer3 = [[MockGestureRecognizer alloc] init];
3832 [child3 addGestureRecognizer:normalRecognizer3];
3833
3834 UIView* child3_1 = [[WKWebView alloc] init];
3835 child3_1.gestureRecognizers = nil;
3836 for (UIView* subview in child3_1.subviews) {
3837 [subview removeFromSuperview];
3838 }
3839 [child3 addSubview:child3_1];
3840 MockGestureRecognizer* normalRecognizer3_1 = [[MockGestureRecognizer alloc] init];
3841 [child3_1 addGestureRecognizer:normalRecognizer3_1];
3842
3843 UIView* child3_1_1 = [[UIView alloc] init];
3844 [child3_1 addSubview:child3_1_1];
3845 MockGestureRecognizer* normalRecognizer3_1_1 = [[MockGestureRecognizer alloc] init];
3846 [child3_1_1 addGestureRecognizer:normalRecognizer3_1_1];
3847
3848 UIView* child3_1_2 = [[UIView alloc] init];
3849 [child3_1 addSubview:child3_1_2];
3850 MockGestureRecognizer* normalRecognizer3_1_2 = [[MockGestureRecognizer alloc] init];
3851 [child3_1_2 addGestureRecognizer:normalRecognizer3_1_2];
3852
3853 UIView* child3_1_2_1 = [[UIView alloc] init];
3854 [child3_1_2 addSubview:child3_1_2_1];
3855 MockGestureRecognizer* normalRecognizer3_1_2_1 = [[MockGestureRecognizer alloc] init];
3856 [child3_1_2_1 addGestureRecognizer:normalRecognizer3_1_2_1];
3857
3858 UIView* child3_1_2_2 = [[UIView alloc] init];
3859 [child3_1_2 addSubview:child3_1_2_2];
3860 MockGestureRecognizer* normalRecognizer3_1_2_2 = [[MockGestureRecognizer alloc] init];
3861 [child3_1_2_2 addGestureRecognizer:normalRecognizer3_1_2_2];
3862
3863 // Add the target recognizer at child2_2 & child3_1_2_2
3864
3865 MockTouchEventsGestureRecognizer* targetRecognizer2_2 =
3866 [[MockTouchEventsGestureRecognizer alloc] init];
3867 [child2_2 addGestureRecognizer:targetRecognizer2_2];
3868
3869 MockTouchEventsGestureRecognizer* targetRecognizer3_1_2_2 =
3870 [[MockTouchEventsGestureRecognizer alloc] init];
3871 [child3_1_2_2 addGestureRecognizer:targetRecognizer3_1_2_2];
3872
3873 [touchInterceptorView blockGesture];
3874
3875 NSArray* normalRecognizers = @[
3876 normalRecognizer0, normalRecognizer1, normalRecognizer2, normalRecognizer2_1,
3877 normalRecognizer2_2, normalRecognizer3, normalRecognizer3_1, normalRecognizer3_1_1,
3878 normalRecognizer3_1_2, normalRecognizer3_1_2_1, normalRecognizer3_1_2_2
3879 ];
3880
3881 NSArray* targetRecognizers = @[ targetRecognizer2_2, targetRecognizer3_1_2_2 ];
3882
3883 NSArray* expectedEmptyHistory = @[];
3884 NSArray* expectedToggledHistory = @[ @NO, @YES ];
3885
3886 for (MockGestureRecognizer* recognizer in normalRecognizers) {
3887 XCTAssertEqualObjects(recognizer.toggleHistory, expectedEmptyHistory);
3888 }
3889
3890 for (MockGestureRecognizer* recognizer in targetRecognizers) {
3891 XCTAssertEqualObjects(recognizer.toggleHistory, expectedToggledHistory);
3892 }
3893 }
3894}
3895
3896- (void)
3897 testFlutterPlatformViewGestureBlockingPolicy_ShouldNotAddDelayingRecognizerUnderDoNotBlockGesturePolicy {
3898 flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
3899
3901 FlutterPlatformViewsController* flutterPlatformViewsController =
3902 [[FlutterPlatformViewsController alloc] init];
3903 flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
3904 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
3905 /*delegate=*/mock_delegate,
3906 /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
3907 /*platform_views_controller=*/flutterPlatformViewsController,
3908 /*task_runners=*/runners.taskRunners,
3909 /*worker_task_runner=*/nil,
3910 /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
3911
3914 [flutterPlatformViewsController registerViewFactory:factory
3915 withId:@"MockFlutterPlatformView"
3917 FlutterPlatformViewGestureRecognizersBlockingPolicyDoNotBlockGesture];
3918 FlutterResult result = ^(id result) {
3919 };
3920 [flutterPlatformViewsController
3922 methodCallWithMethodName:@"create"
3923 arguments:@{
3924 @"id" : @2,
3925 @"viewType" : @"MockFlutterPlatformView",
3926 @"gestureBlockingPolicy" : @"doNotBlockGesture"
3927 }]
3928 result:result];
3929
3930 XCTAssertNotNil(gMockPlatformView);
3931
3932 FlutterTouchInterceptingView* touchInterceptorView = FindTouchInterceptingView(gMockPlatformView);
3933 XCTAssertNotNil(touchInterceptorView);
3934
3935 XCTAssert(touchInterceptorView.gestureRecognizers.count == 1);
3936 UIGestureRecognizer* forwardingRecognizer = touchInterceptorView.gestureRecognizers[0];
3937 XCTAssert([forwardingRecognizer isKindOfClass:[ForwardingGestureRecognizer class]]);
3938}
3939
3940- (void)testFlutterPlatformViewGestureBlockingPolicy_ShouldAddDelayingRecognizerUnderEagerPolicy {
3941 flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
3942
3944 FlutterPlatformViewsController* flutterPlatformViewsController =
3945 [[FlutterPlatformViewsController alloc] init];
3946 flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
3947 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
3948 /*delegate=*/mock_delegate,
3949 /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
3950 /*platform_views_controller=*/flutterPlatformViewsController,
3951 /*task_runners=*/runners.taskRunners,
3952 /*worker_task_runner=*/nil,
3953 /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
3954
3957 [flutterPlatformViewsController
3958 registerViewFactory:factory
3959 withId:@"MockFlutterPlatformView"
3960 gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
3961 FlutterResult result = ^(id result) {
3962 };
3963 [flutterPlatformViewsController
3965 arguments:@{
3966 @"id" : @2,
3967 @"viewType" : @"MockFlutterPlatformView",
3968 @"gestureBlockingPolicy" : @"eager"
3969 }]
3970 result:result];
3971
3972 XCTAssertNotNil(gMockPlatformView);
3973
3974 FlutterTouchInterceptingView* touchInterceptorView = FindTouchInterceptingView(gMockPlatformView);
3975 XCTAssertNotNil(touchInterceptorView);
3976
3977 XCTAssert(touchInterceptorView.gestureRecognizers.count == 2);
3978 UIGestureRecognizer* delayingRecognizer = touchInterceptorView.gestureRecognizers[0];
3979 UIGestureRecognizer* forwardingRecognizer = touchInterceptorView.gestureRecognizers[1];
3980 XCTAssert([delayingRecognizer isKindOfClass:[FlutterDelayingGestureRecognizer class]]);
3981 XCTAssert([forwardingRecognizer isKindOfClass:[ForwardingGestureRecognizer class]]);
3982}
3983
3984- (void)testFlutterPlatformViewHitTest_AcceptTouchIfInstructedByFramework {
3985 flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
3986
3988 FlutterPlatformViewsController* flutterPlatformViewsController =
3989 [[FlutterPlatformViewsController alloc] init];
3990
3991 FlutterViewController* mockFlutterViewController = OCMClassMock([FlutterViewController class]);
3992 flutterPlatformViewsController.flutterViewController = mockFlutterViewController;
3993
3994 flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
3995 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
3996 /*delegate=*/mock_delegate,
3997 /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
3998 /*platform_views_controller=*/flutterPlatformViewsController,
3999 /*task_runners=*/runners.taskRunners,
4000 /*worker_task_runner=*/nil,
4001 /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
4002
4005 [flutterPlatformViewsController registerViewFactory:factory
4006 withId:@"MockFlutterPlatformView"
4008 FlutterPlatformViewGestureRecognizersBlockingPolicyDoNotBlockGesture];
4009 FlutterResult result = ^(id result) {
4010 };
4011 [flutterPlatformViewsController
4013 methodCallWithMethodName:@"create"
4014 arguments:@{
4015 @"id" : @2,
4016 @"viewType" : @"MockFlutterPlatformView",
4017 @"gestureBlockingPolicy" : @"doNotBlockGesture"
4018 }]
4019 result:result];
4020
4021 XCTAssertNotNil(gMockPlatformView);
4022
4023 FlutterTouchInterceptingView* touchInterceptorView = FindTouchInterceptingView(gMockPlatformView);
4024 XCTAssertNotNil(touchInterceptorView);
4025
4026 touchInterceptorView.frame = CGRectMake(0, 0, 100, 100);
4027 CGPoint touchBeganLocation = CGPointMake(1, 1);
4028
4029 UIEvent* mockEvent = OCMClassMock([UIEvent class]);
4030 OCMStub([mockEvent type]).andReturn(UIEventTypeTouches);
4031
4032 // Framework instructs "accept touch".
4033 OCMStub([mockFlutterViewController
4034 platformViewShouldAcceptTouchAtTouchBeganLocation:touchBeganLocation])
4035 .andReturn(YES);
4036 UIView* hitTestResult = [touchInterceptorView hitTest:touchBeganLocation withEvent:mockEvent];
4037 // HitTest returning the platform view means platform view will receive touch events.
4038 XCTAssert(hitTestResult == gMockPlatformView);
4039}
4040
4041- (void)testFlutterPlatformViewHitTest_RejectTouchIfInstructedByFramework {
4042 flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
4043
4045 FlutterPlatformViewsController* flutterPlatformViewsController =
4046 [[FlutterPlatformViewsController alloc] init];
4047
4048 FlutterViewController* mockFlutterViewController = OCMClassMock([FlutterViewController class]);
4049 flutterPlatformViewsController.flutterViewController = mockFlutterViewController;
4050
4051 flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
4052 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
4053 /*delegate=*/mock_delegate,
4054 /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
4055 /*platform_views_controller=*/flutterPlatformViewsController,
4056 /*task_runners=*/runners.taskRunners,
4057 /*worker_task_runner=*/nil,
4058 /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
4059
4062 [flutterPlatformViewsController registerViewFactory:factory
4063 withId:@"MockFlutterPlatformView"
4065 FlutterPlatformViewGestureRecognizersBlockingPolicyDoNotBlockGesture];
4066 FlutterResult result = ^(id result) {
4067 };
4068 [flutterPlatformViewsController
4070 methodCallWithMethodName:@"create"
4071 arguments:@{
4072 @"id" : @2,
4073 @"viewType" : @"MockFlutterPlatformView",
4074 @"gestureBlockingPolicy" : @"doNotBlockGesture"
4075 }]
4076 result:result];
4077
4078 XCTAssertNotNil(gMockPlatformView);
4079
4080 FlutterTouchInterceptingView* touchInterceptorView = FindTouchInterceptingView(gMockPlatformView);
4081 XCTAssertNotNil(touchInterceptorView);
4082
4083 touchInterceptorView.frame = CGRectMake(0, 0, 100, 100);
4084 CGPoint touchBeganLocation = CGPointMake(1, 1);
4085
4086 UIEvent* mockEvent = OCMClassMock([UIEvent class]);
4087 OCMStub([mockEvent type]).andReturn(UIEventTypeTouches);
4088
4089 // Framework instructs "reject touch".
4090 OCMStub([mockFlutterViewController
4091 platformViewShouldAcceptTouchAtTouchBeganLocation:touchBeganLocation])
4092 .andReturn(NO);
4093 UIView* hitTestResult = [touchInterceptorView hitTest:touchBeganLocation withEvent:mockEvent];
4094 // HitTest returning the touch interceptor view means platform view will not receive touch events.
4095 XCTAssert(hitTestResult == touchInterceptorView);
4096}
4097
4098- (void)testFlutterPlatformViewGestureBlockingPolicy_PolicyMappingIsCorrect {
4099 flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
4100
4102 FlutterPlatformViewsController* flutterPlatformViewsController =
4103 [[FlutterPlatformViewsController alloc] init];
4104
4105 FlutterViewController* mockFlutterViewController = OCMClassMock([FlutterViewController class]);
4106 flutterPlatformViewsController.flutterViewController = mockFlutterViewController;
4107
4108 flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
4109 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
4110 /*delegate=*/mock_delegate,
4111 /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
4112 /*platform_views_controller=*/flutterPlatformViewsController,
4113 /*task_runners=*/runners.taskRunners,
4114 /*worker_task_runner=*/nil,
4115 /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
4116
4119 [flutterPlatformViewsController registerViewFactory:factory
4120 withId:@"MockFlutterPlatformView"
4121 // the policy set by engine API will be overwritten by the dart API, unless
4122 // `fallbackToPluginDefault` is used in dart API.
4124 FlutterPlatformViewGestureRecognizersBlockingPolicyWaitUntilTouchesEnded];
4125 __block id methodResult;
4126 FlutterResult result = ^(id result) {
4127 methodResult = result;
4128 };
4129
4130 // eager
4131 [flutterPlatformViewsController
4133 arguments:@{
4134 @"id" : @2,
4135 @"viewType" : @"MockFlutterPlatformView",
4136 @"gestureBlockingPolicy" : @"eager"
4137 }]
4138 result:result];
4139
4140 FlutterTouchInterceptingView* touchInterceptorView = FindTouchInterceptingView(gMockPlatformView);
4141 XCTAssertEqual(touchInterceptorView.blockingPolicy,
4143
4144 // waitUntilTouchesEnded
4145 [flutterPlatformViewsController
4147 methodCallWithMethodName:@"create"
4148 arguments:@{
4149 @"id" : @3,
4150 @"viewType" : @"MockFlutterPlatformView",
4151 @"gestureBlockingPolicy" : @"waitUntilTouchesEnded"
4152 }]
4153 result:result];
4154 touchInterceptorView = FindTouchInterceptingView(gMockPlatformView);
4155 XCTAssertEqual(touchInterceptorView.blockingPolicy,
4157
4158 [flutterPlatformViewsController
4160 methodCallWithMethodName:@"create"
4161 arguments:@{
4162 @"id" : @4,
4163 @"viewType" : @"MockFlutterPlatformView",
4164 @"gestureBlockingPolicy" : @"doNotBlockGesture"
4165 }]
4166 result:result];
4167 touchInterceptorView = FindTouchInterceptingView(gMockPlatformView);
4168 XCTAssertEqual(touchInterceptorView.blockingPolicy,
4170 XCTAssertNil(methodResult);
4171
4172 // fallbackToPluginDefault (which is waitUntilTouchesEnded)
4173 [flutterPlatformViewsController
4175 methodCallWithMethodName:@"create"
4176 arguments:@{
4177 @"id" : @5,
4178 @"viewType" : @"MockFlutterPlatformView",
4179 @"gestureBlockingPolicy" : @"fallbackToPluginDefault"
4180 }]
4181 result:result];
4182 touchInterceptorView = FindTouchInterceptingView(gMockPlatformView);
4183 XCTAssertEqual(touchInterceptorView.blockingPolicy,
4185 XCTAssertNil(methodResult);
4186
4187 // Unkonwn gesture blocking policy should fail
4188 [flutterPlatformViewsController
4190 arguments:@{
4191 @"id" : @6,
4192 @"viewType" : @"MockFlutterPlatformView",
4193 @"gestureBlockingPolicy" : @"unknownPolicy"
4194 }]
4195 result:result];
4196 XCTAssertTrue([methodResult isKindOfClass:[FlutterError class]]);
4197 FlutterError* resultError = (FlutterError*)methodResult;
4198 XCTAssertEqualObjects(resultError.code, @"unknown_gesture_blocking_policy");
4199 XCTAssertEqualObjects(
4200 resultError.message,
4201 @"Trying to create a platform view with an unknown gesture blocking policy");
4202 XCTAssertEqualObjects(resultError.details, @"view id: '6'");
4203}
4204
4205- (void)testFlutterPlatformViewControllerSubmitFrameWithoutFlutterViewNotCrashing {
4206 flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
4207
4209 FlutterPlatformViewsController* flutterPlatformViewsController =
4210 [[FlutterPlatformViewsController alloc] init];
4211 flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
4212 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
4213 /*delegate=*/mock_delegate,
4214 /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
4215 /*platform_views_controller=*/flutterPlatformViewsController,
4216 /*task_runners=*/runners.taskRunners,
4217 /*worker_task_runner=*/nil,
4218 /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
4219
4222 [flutterPlatformViewsController
4223 registerViewFactory:factory
4224 withId:@"MockFlutterPlatformView"
4225 gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
4226 FlutterResult result = ^(id result) {
4227 };
4228 [flutterPlatformViewsController
4230 arguments:@{
4231 @"id" : @2,
4232 @"viewType" : @"MockFlutterPlatformView",
4233 @"gestureBlockingPolicy" : @"eager"
4234 }]
4235 result:result];
4236
4237 XCTAssertNotNil(gMockPlatformView);
4238
4239 // Create embedded view params
4241 flutter::DlMatrix finalMatrix;
4242
4243 auto embeddedViewParams_1 =
4244 std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, flutter::DlSize(300, 300), stack);
4245
4246 [flutterPlatformViewsController prerollCompositeEmbeddedView:2
4247 withParams:std::move(embeddedViewParams_1)];
4248 [flutterPlatformViewsController
4250 withParams:[flutterPlatformViewsController compositionParamsForView:2]];
4251
4253 auto mock_surface = std::make_unique<flutter::SurfaceFrame>(
4254 nullptr, framebuffer_info,
4255 [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return false; },
4256 [](const flutter::SurfaceFrame& surface_frame) { return true; },
4257 /*frame_size=*/flutter::DlISize(800, 600));
4258 XCTAssertFalse([flutterPlatformViewsController
4259 submitFrame:std::move(mock_surface)
4260 withIosContext:std::make_shared<flutter::IOSContextNoop>()]);
4261
4262 auto embeddedViewParams_2 =
4263 std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, flutter::DlSize(300, 300), stack);
4264 [flutterPlatformViewsController prerollCompositeEmbeddedView:2
4265 withParams:std::move(embeddedViewParams_2)];
4266 [flutterPlatformViewsController
4268 withParams:[flutterPlatformViewsController compositionParamsForView:2]];
4269
4270 auto mock_surface_submit_true = std::make_unique<flutter::SurfaceFrame>(
4271 nullptr, framebuffer_info,
4272 [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; },
4273 [](const flutter::SurfaceFrame& surface_frame) { return true; },
4274 /*frame_size=*/flutter::DlISize(800, 600));
4275 XCTAssertTrue([flutterPlatformViewsController
4276 submitFrame:std::move(mock_surface_submit_true)
4277 withIosContext:std::make_shared<flutter::IOSContextNoop>()]);
4278}
4279
4280- (void)
4281 testFlutterPlatformViewControllerResetDeallocsPlatformViewWhenRootViewsNotBindedToFlutterView {
4282 flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
4283
4285 FlutterPlatformViewsController* flutterPlatformViewsController =
4286 [[FlutterPlatformViewsController alloc] init];
4287 flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
4288 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
4289 /*delegate=*/mock_delegate,
4290 /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
4291 /*platform_views_controller=*/flutterPlatformViewsController,
4292 /*task_runners=*/runners.taskRunners,
4293 /*worker_task_runner=*/nil,
4294 /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
4295
4296 UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
4297 flutterPlatformViewsController.flutterView = flutterView;
4298
4301 [flutterPlatformViewsController
4302 registerViewFactory:factory
4303 withId:@"MockFlutterPlatformView"
4304 gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
4305 FlutterResult result = ^(id result) {
4306 };
4307 // autorelease pool to trigger an autorelease for all the root_views_ and touch_interceptors_.
4308 @autoreleasepool {
4309 [flutterPlatformViewsController
4311 arguments:@{
4312 @"id" : @2,
4313 @"viewType" : @"MockFlutterPlatformView",
4314 @"gestureBlockingPolicy" : @"eager"
4315 }]
4316 result:result];
4317
4319 flutter::DlMatrix finalMatrix;
4320 auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
4321 finalMatrix, flutter::DlSize(300, 300), stack);
4322 [flutterPlatformViewsController prerollCompositeEmbeddedView:2
4323 withParams:std::move(embeddedViewParams)];
4324
4325 // Not calling |[flutterPlatformViewsController submitFrame:withIosContext:]| so that
4326 // the platform views are not added to flutter_view_.
4327
4328 XCTAssertNotNil(gMockPlatformView);
4329 [flutterPlatformViewsController reset];
4330 }
4331 XCTAssertNil(gMockPlatformView);
4332}
4333
4334- (void)testFlutterPlatformViewControllerBeginFrameShouldResetCompisitionOrder {
4335 flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
4336
4338 FlutterPlatformViewsController* flutterPlatformViewsController =
4339 [[FlutterPlatformViewsController alloc] init];
4340 flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
4341 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
4342 /*delegate=*/mock_delegate,
4343 /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
4344 /*platform_views_controller=*/flutterPlatformViewsController,
4345 /*task_runners=*/runners.taskRunners,
4346 /*worker_task_runner=*/nil,
4347 /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
4348
4349 UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
4350 flutterPlatformViewsController.flutterView = flutterView;
4351
4354 [flutterPlatformViewsController
4355 registerViewFactory:factory
4356 withId:@"MockFlutterPlatformView"
4357 gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
4358 FlutterResult result = ^(id result) {
4359 };
4360
4361 [flutterPlatformViewsController
4363 arguments:@{
4364 @"id" : @0,
4365 @"viewType" : @"MockFlutterPlatformView",
4366 @"gestureBlockingPolicy" : @"eager"
4367
4368 }]
4369 result:result];
4370
4371 // First frame, |embeddedViewCount| is not empty after composite.
4372 [flutterPlatformViewsController beginFrameWithSize:flutter::DlISize(300, 300)];
4374 flutter::DlMatrix finalMatrix;
4375 auto embeddedViewParams1 =
4376 std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, flutter::DlSize(300, 300), stack);
4377 [flutterPlatformViewsController prerollCompositeEmbeddedView:0
4378 withParams:std::move(embeddedViewParams1)];
4379 [flutterPlatformViewsController
4381 withParams:[flutterPlatformViewsController compositionParamsForView:0]];
4382
4383 XCTAssertEqual(flutterPlatformViewsController.embeddedViewCount, 1UL);
4384
4385 // Second frame, |embeddedViewCount| should be empty at the start
4386 [flutterPlatformViewsController beginFrameWithSize:flutter::DlISize(300, 300)];
4387 XCTAssertEqual(flutterPlatformViewsController.embeddedViewCount, 0UL);
4388
4389 auto embeddedViewParams2 =
4390 std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, flutter::DlSize(300, 300), stack);
4391 [flutterPlatformViewsController prerollCompositeEmbeddedView:0
4392 withParams:std::move(embeddedViewParams2)];
4393 [flutterPlatformViewsController
4395 withParams:[flutterPlatformViewsController compositionParamsForView:0]];
4396
4397 XCTAssertEqual(flutterPlatformViewsController.embeddedViewCount, 1UL);
4398}
4399
4400- (void)
4401 testFlutterPlatformViewControllerSubmitFrameShouldOrderSubviewsCorrectlyWithDifferentViewHierarchy {
4402 flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
4403
4405 FlutterPlatformViewsController* flutterPlatformViewsController =
4406 [[FlutterPlatformViewsController alloc] init];
4407 flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
4408 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
4409 /*delegate=*/mock_delegate,
4410 /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
4411 /*platform_views_controller=*/flutterPlatformViewsController,
4412 /*task_runners=*/runners.taskRunners,
4413 /*worker_task_runner=*/nil,
4414 /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
4415
4416 UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
4417 flutterPlatformViewsController.flutterView = flutterView;
4418
4421 [flutterPlatformViewsController
4422 registerViewFactory:factory
4423 withId:@"MockFlutterPlatformView"
4424 gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
4425 FlutterResult result = ^(id result) {
4426 };
4427 [flutterPlatformViewsController
4429 arguments:@{
4430 @"id" : @0,
4431 @"viewType" : @"MockFlutterPlatformView",
4432 @"gestureBlockingPolicy" : @"eager"
4433 }]
4434 result:result];
4435 UIView* view1 = gMockPlatformView;
4436
4437 // This overwrites `gMockPlatformView` to another view.
4438 [flutterPlatformViewsController
4440 arguments:@{
4441 @"id" : @1,
4442 @"viewType" : @"MockFlutterPlatformView",
4443 @"gestureBlockingPolicy" : @"eager"
4444 }]
4445 result:result];
4446 UIView* view2 = gMockPlatformView;
4447
4448 [flutterPlatformViewsController beginFrameWithSize:flutter::DlISize(300, 300)];
4450 flutter::DlMatrix finalMatrix;
4451 auto embeddedViewParams1 =
4452 std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, flutter::DlSize(300, 300), stack);
4453 [flutterPlatformViewsController prerollCompositeEmbeddedView:0
4454 withParams:std::move(embeddedViewParams1)];
4455
4456 auto embeddedViewParams2 =
4457 std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, flutter::DlSize(500, 500), stack);
4458 [flutterPlatformViewsController prerollCompositeEmbeddedView:1
4459 withParams:std::move(embeddedViewParams2)];
4460
4462 auto mock_surface = std::make_unique<flutter::SurfaceFrame>(
4463 nullptr, framebuffer_info,
4464 [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; },
4465 [](const flutter::SurfaceFrame& surface_frame) { return true; },
4466 /*frame_size=*/flutter::DlISize(800, 600), nullptr, /*display_list_fallback=*/true);
4467 XCTAssertTrue([flutterPlatformViewsController
4468 submitFrame:std::move(mock_surface)
4469 withIosContext:std::make_shared<flutter::IOSContextNoop>()]);
4470
4471 // platform view is wrapped by touch interceptor, which itself is wrapped by clipping view.
4472 UIView* clippingView1 = view1.superview.superview;
4473 UIView* clippingView2 = view2.superview.superview;
4474 XCTAssertTrue([flutterView.subviews indexOfObject:clippingView1] <
4475 [flutterView.subviews indexOfObject:clippingView2],
4476 @"The first clipping view should be added before the second clipping view.");
4477
4478 // Need to recreate these params since they are `std::move`ed.
4479 [flutterPlatformViewsController beginFrameWithSize:flutter::DlISize(300, 300)];
4480 // Process the second frame in the opposite order.
4481 embeddedViewParams2 =
4482 std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, flutter::DlSize(500, 500), stack);
4483 [flutterPlatformViewsController prerollCompositeEmbeddedView:1
4484 withParams:std::move(embeddedViewParams2)];
4485
4486 embeddedViewParams1 =
4487 std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, flutter::DlSize(300, 300), stack);
4488 [flutterPlatformViewsController prerollCompositeEmbeddedView:0
4489 withParams:std::move(embeddedViewParams1)];
4490
4491 mock_surface = std::make_unique<flutter::SurfaceFrame>(
4492 nullptr, framebuffer_info,
4493 [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; },
4494 [](const flutter::SurfaceFrame& surface_frame) { return true; },
4495 /*frame_size=*/flutter::DlISize(800, 600), nullptr, /*display_list_fallback=*/true);
4496 XCTAssertTrue([flutterPlatformViewsController
4497 submitFrame:std::move(mock_surface)
4498 withIosContext:std::make_shared<flutter::IOSContextNoop>()]);
4499
4500 XCTAssertTrue([flutterView.subviews indexOfObject:clippingView1] >
4501 [flutterView.subviews indexOfObject:clippingView2],
4502 @"The first clipping view should be added after the second clipping view.");
4503}
4504
4505- (void)
4506 testFlutterPlatformViewControllerSubmitFrameShouldOrderSubviewsCorrectlyWithSameViewHierarchy {
4507 flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
4508
4510 FlutterPlatformViewsController* flutterPlatformViewsController =
4511 [[FlutterPlatformViewsController alloc] init];
4512 flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
4513 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
4514 /*delegate=*/mock_delegate,
4515 /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
4516 /*platform_views_controller=*/flutterPlatformViewsController,
4517 /*task_runners=*/runners.taskRunners,
4518 /*worker_task_runner=*/nil,
4519 /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
4520
4521 UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
4522 flutterPlatformViewsController.flutterView = flutterView;
4523
4526 [flutterPlatformViewsController
4527 registerViewFactory:factory
4528 withId:@"MockFlutterPlatformView"
4529 gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
4530 FlutterResult result = ^(id result) {
4531 };
4532 [flutterPlatformViewsController
4534 arguments:@{
4535 @"id" : @0,
4536 @"viewType" : @"MockFlutterPlatformView",
4537 @"gestureBlockingPolicy" : @"eager"
4538 }]
4539 result:result];
4540 UIView* view1 = gMockPlatformView;
4541
4542 // This overwrites `gMockPlatformView` to another view.
4543 [flutterPlatformViewsController
4545 arguments:@{
4546 @"id" : @1,
4547 @"viewType" : @"MockFlutterPlatformView",
4548 @"gestureBlockingPolicy" : @"eager"
4549 }]
4550 result:result];
4551 UIView* view2 = gMockPlatformView;
4552
4553 [flutterPlatformViewsController beginFrameWithSize:flutter::DlISize(300, 300)];
4555 flutter::DlMatrix finalMatrix;
4556 auto embeddedViewParams1 =
4557 std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, flutter::DlSize(300, 300), stack);
4558 [flutterPlatformViewsController prerollCompositeEmbeddedView:0
4559 withParams:std::move(embeddedViewParams1)];
4560
4561 auto embeddedViewParams2 =
4562 std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, flutter::DlSize(500, 500), stack);
4563 [flutterPlatformViewsController prerollCompositeEmbeddedView:1
4564 withParams:std::move(embeddedViewParams2)];
4565
4567 auto mock_surface = std::make_unique<flutter::SurfaceFrame>(
4568 nullptr, framebuffer_info,
4569 [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; },
4570 [](const flutter::SurfaceFrame& surface_frame) { return true; },
4571 /*frame_size=*/flutter::DlISize(800, 600), nullptr, /*display_list_fallback=*/true);
4572 XCTAssertTrue([flutterPlatformViewsController
4573 submitFrame:std::move(mock_surface)
4574 withIosContext:std::make_shared<flutter::IOSContextNoop>()]);
4575
4576 // platform view is wrapped by touch interceptor, which itself is wrapped by clipping view.
4577 UIView* clippingView1 = view1.superview.superview;
4578 UIView* clippingView2 = view2.superview.superview;
4579 XCTAssertTrue([flutterView.subviews indexOfObject:clippingView1] <
4580 [flutterView.subviews indexOfObject:clippingView2],
4581 @"The first clipping view should be added before the second clipping view.");
4582
4583 // Need to recreate these params since they are `std::move`ed.
4584 [flutterPlatformViewsController beginFrameWithSize:flutter::DlISize(300, 300)];
4585 // Process the second frame in the same order.
4586 embeddedViewParams1 =
4587 std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, flutter::DlSize(300, 300), stack);
4588 [flutterPlatformViewsController prerollCompositeEmbeddedView:0
4589 withParams:std::move(embeddedViewParams1)];
4590
4591 embeddedViewParams2 =
4592 std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, flutter::DlSize(500, 500), stack);
4593 [flutterPlatformViewsController prerollCompositeEmbeddedView:1
4594 withParams:std::move(embeddedViewParams2)];
4595
4596 mock_surface = std::make_unique<flutter::SurfaceFrame>(
4597 nullptr, framebuffer_info,
4598 [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; },
4599 [](const flutter::SurfaceFrame& surface_frame) { return true; },
4600 /*frame_size=*/flutter::DlISize(800, 600), nullptr, /*display_list_fallback=*/true);
4601 XCTAssertTrue([flutterPlatformViewsController
4602 submitFrame:std::move(mock_surface)
4603 withIosContext:std::make_shared<flutter::IOSContextNoop>()]);
4604
4605 XCTAssertTrue([flutterView.subviews indexOfObject:clippingView1] <
4606 [flutterView.subviews indexOfObject:clippingView2],
4607 @"The first clipping view should be added before the second clipping view.");
4608}
4609
4610- (int)alphaOfPoint:(CGPoint)point onView:(UIView*)view {
4611 unsigned char pixel[4] = {0};
4612
4613 CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
4614
4615 // Draw the pixel on `point` in the context.
4616 CGContextRef context =
4617 CGBitmapContextCreate(pixel, 1, 1, 8, 4, colorSpace,
4618 static_cast<uint32_t>(kCGBitmapAlphaInfoMask) &
4619 static_cast<uint32_t>(kCGImageAlphaPremultipliedLast));
4620 CGContextTranslateCTM(context, -point.x, -point.y);
4621 [view.layer renderInContext:context];
4622
4623 CGContextRelease(context);
4624 CGColorSpaceRelease(colorSpace);
4625 // Get the alpha from the pixel that we just rendered.
4626 return pixel[3];
4627}
4628
4629- (void)testHasFirstResponderInViewHierarchySubtree_viewItselfBecomesFirstResponder {
4630 // For view to become the first responder, it must be a descendant of a UIWindow
4631 UIWindow* window = [[UIWindow alloc] init];
4632 UITextField* textField = [[UITextField alloc] init];
4633 [window addSubview:textField];
4634
4635 [textField becomeFirstResponder];
4636 XCTAssertTrue(textField.isFirstResponder);
4637 XCTAssertTrue(textField.flt_hasFirstResponderInViewHierarchySubtree);
4638 [textField resignFirstResponder];
4639 XCTAssertFalse(textField.isFirstResponder);
4640 XCTAssertFalse(textField.flt_hasFirstResponderInViewHierarchySubtree);
4641}
4642
4643- (void)testHasFirstResponderInViewHierarchySubtree_descendantViewBecomesFirstResponder {
4644 // For view to become the first responder, it must be a descendant of a UIWindow
4645 UIWindow* window = [[UIWindow alloc] init];
4646 UIView* view = [[UIView alloc] init];
4647 UIView* childView = [[UIView alloc] init];
4648 UITextField* textField = [[UITextField alloc] init];
4649 [window addSubview:view];
4650 [view addSubview:childView];
4651 [childView addSubview:textField];
4652
4653 [textField becomeFirstResponder];
4654 XCTAssertTrue(textField.isFirstResponder);
4655 XCTAssertTrue(view.flt_hasFirstResponderInViewHierarchySubtree);
4656 [textField resignFirstResponder];
4657 XCTAssertFalse(textField.isFirstResponder);
4658 XCTAssertFalse(view.flt_hasFirstResponderInViewHierarchySubtree);
4659}
4660
4661- (void)testFlutterClippingMaskViewPoolReuseViewsAfterRecycle {
4662 FlutterClippingMaskViewPool* pool = [[FlutterClippingMaskViewPool alloc] initWithCapacity:2];
4663 FlutterClippingMaskView* view1 = [pool getMaskViewWithFrame:CGRectZero];
4664 FlutterClippingMaskView* view2 = [pool getMaskViewWithFrame:CGRectZero];
4665 [pool insertViewToPoolIfNeeded:view1];
4666 [pool insertViewToPoolIfNeeded:view2];
4667 CGRect newRect = CGRectMake(0, 0, 10, 10);
4668 FlutterClippingMaskView* view3 = [pool getMaskViewWithFrame:newRect];
4669 FlutterClippingMaskView* view4 = [pool getMaskViewWithFrame:newRect];
4670 // view3 and view4 should randomly get either of view1 and view2.
4671 NSSet* set1 = [NSSet setWithObjects:view1, view2, nil];
4672 NSSet* set2 = [NSSet setWithObjects:view3, view4, nil];
4673 XCTAssertEqualObjects(set1, set2);
4674 XCTAssertTrue(CGRectEqualToRect(view3.frame, newRect));
4675 XCTAssertTrue(CGRectEqualToRect(view4.frame, newRect));
4676}
4677
4678- (void)testFlutterClippingMaskViewPoolAllocsNewMaskViewsAfterReachingCapacity {
4679 FlutterClippingMaskViewPool* pool = [[FlutterClippingMaskViewPool alloc] initWithCapacity:2];
4680 FlutterClippingMaskView* view1 = [pool getMaskViewWithFrame:CGRectZero];
4681 FlutterClippingMaskView* view2 = [pool getMaskViewWithFrame:CGRectZero];
4682 FlutterClippingMaskView* view3 = [pool getMaskViewWithFrame:CGRectZero];
4683 XCTAssertNotEqual(view1, view3);
4684 XCTAssertNotEqual(view2, view3);
4685}
4686
4687- (void)testMaskViewsReleasedWhenPoolIsReleased {
4688 __weak UIView* weakView;
4689 @autoreleasepool {
4690 FlutterClippingMaskViewPool* pool = [[FlutterClippingMaskViewPool alloc] initWithCapacity:2];
4692 weakView = view;
4693 XCTAssertNotNil(weakView);
4694 }
4695 XCTAssertNil(weakView);
4696}
4697
4698- (void)testClipMaskViewIsReused {
4699 flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
4700
4702 FlutterPlatformViewsController* flutterPlatformViewsController =
4703 [[FlutterPlatformViewsController alloc] init];
4704 flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
4705 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
4706 /*delegate=*/mock_delegate,
4707 /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
4708 /*platform_views_controller=*/flutterPlatformViewsController,
4709 /*task_runners=*/runners.taskRunners,
4710 /*worker_task_runner=*/nil,
4711 /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
4712
4715 [flutterPlatformViewsController
4716 registerViewFactory:factory
4717 withId:@"MockFlutterPlatformView"
4718 gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
4719 FlutterResult result = ^(id result) {
4720 };
4721 [flutterPlatformViewsController
4723 arguments:@{
4724 @"id" : @1,
4725 @"viewType" : @"MockFlutterPlatformView",
4726 @"gestureBlockingPolicy" : @"eager"
4727 }]
4728 result:result];
4729
4730 XCTAssertNotNil(gMockPlatformView);
4731 UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
4732 flutterPlatformViewsController.flutterView = flutterView;
4733 // Create embedded view params
4735 // Layer tree always pushes a screen scale factor to the stack
4736 flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
4737 flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
4738 stack1.PushTransform(screenScaleMatrix);
4739 // Push a clip rect
4740 flutter::DlRect rect = flutter::DlRect::MakeXYWH(2, 2, 3, 3);
4741 stack1.PushClipRect(rect);
4742
4743 auto embeddedViewParams1 = std::make_unique<flutter::EmbeddedViewParams>(
4744 screenScaleMatrix, flutter::DlSize(10, 10), stack1);
4745
4746 [flutterPlatformViewsController prerollCompositeEmbeddedView:1
4747 withParams:std::move(embeddedViewParams1)];
4748 [flutterPlatformViewsController
4750 withParams:[flutterPlatformViewsController compositionParamsForView:1]];
4751
4752 UIView* childClippingView1 = gMockPlatformView.superview.superview;
4753 UIView* maskView1 = childClippingView1.maskView;
4754 XCTAssertNotNil(maskView1);
4755
4756 // Composite a new frame.
4757 [flutterPlatformViewsController beginFrameWithSize:flutter::DlISize(100, 100)];
4759 auto embeddedViewParams2 = std::make_unique<flutter::EmbeddedViewParams>(
4760 screenScaleMatrix, flutter::DlSize(10, 10), stack2);
4761 auto embeddedViewParams3 = std::make_unique<flutter::EmbeddedViewParams>(
4762 screenScaleMatrix, flutter::DlSize(10, 10), stack2);
4763 [flutterPlatformViewsController prerollCompositeEmbeddedView:1
4764 withParams:std::move(embeddedViewParams3)];
4765 [flutterPlatformViewsController
4767 withParams:[flutterPlatformViewsController compositionParamsForView:1]];
4768
4769 childClippingView1 = gMockPlatformView.superview.superview;
4770
4771 // This overrides gMockPlatformView to point to the newly created platform view.
4772 [flutterPlatformViewsController
4774 arguments:@{
4775 @"id" : @2,
4776 @"viewType" : @"MockFlutterPlatformView",
4777 @"gestureBlockingPolicy" : @"eager"
4778 }]
4779 result:result];
4780
4781 auto embeddedViewParams4 = std::make_unique<flutter::EmbeddedViewParams>(
4782 screenScaleMatrix, flutter::DlSize(10, 10), stack1);
4783 [flutterPlatformViewsController prerollCompositeEmbeddedView:2
4784 withParams:std::move(embeddedViewParams4)];
4785 [flutterPlatformViewsController
4787 withParams:[flutterPlatformViewsController compositionParamsForView:2]];
4788
4789 UIView* childClippingView2 = gMockPlatformView.superview.superview;
4790
4791 UIView* maskView2 = childClippingView2.maskView;
4792 XCTAssertEqual(maskView1, maskView2);
4793 XCTAssertNotNil(childClippingView2.maskView);
4794 XCTAssertNil(childClippingView1.maskView);
4795}
4796
4797- (void)testDifferentClipMaskViewIsUsedForEachView {
4798 flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
4799
4801 FlutterPlatformViewsController* flutterPlatformViewsController =
4802 [[FlutterPlatformViewsController alloc] init];
4803 flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
4804 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
4805 /*delegate=*/mock_delegate,
4806 /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
4807 /*platform_views_controller=*/flutterPlatformViewsController,
4808 /*task_runners=*/runners.taskRunners,
4809 /*worker_task_runner=*/nil,
4810 /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
4811
4814 [flutterPlatformViewsController
4815 registerViewFactory:factory
4816 withId:@"MockFlutterPlatformView"
4817 gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
4818 FlutterResult result = ^(id result) {
4819 };
4820
4821 [flutterPlatformViewsController
4823 arguments:@{
4824 @"id" : @1,
4825 @"viewType" : @"MockFlutterPlatformView",
4826 @"gestureBlockingPolicy" : @"eager"
4827 }]
4828 result:result];
4829 UIView* view1 = gMockPlatformView;
4830
4831 // This overwrites `gMockPlatformView` to another view.
4832 [flutterPlatformViewsController
4834 arguments:@{
4835 @"id" : @2,
4836 @"viewType" : @"MockFlutterPlatformView",
4837 @"gestureBlockingPolicy" : @"eager"
4838 }]
4839 result:result];
4840 UIView* view2 = gMockPlatformView;
4841
4842 XCTAssertNotNil(gMockPlatformView);
4843 UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
4844 flutterPlatformViewsController.flutterView = flutterView;
4845 // Create embedded view params
4847 // Layer tree always pushes a screen scale factor to the stack
4848 flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
4849 flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
4850 stack1.PushTransform(screenScaleMatrix);
4851 // Push a clip rect
4852 flutter::DlRect rect = flutter::DlRect::MakeXYWH(2, 2, 3, 3);
4853 stack1.PushClipRect(rect);
4854
4855 auto embeddedViewParams1 = std::make_unique<flutter::EmbeddedViewParams>(
4856 screenScaleMatrix, flutter::DlSize(10, 10), stack1);
4857
4859 stack2.PushClipRect(rect);
4860 auto embeddedViewParams2 = std::make_unique<flutter::EmbeddedViewParams>(
4861 screenScaleMatrix, flutter::DlSize(10, 10), stack2);
4862
4863 [flutterPlatformViewsController prerollCompositeEmbeddedView:1
4864 withParams:std::move(embeddedViewParams1)];
4865 [flutterPlatformViewsController
4867 withParams:[flutterPlatformViewsController compositionParamsForView:1]];
4868
4869 UIView* childClippingView1 = view1.superview.superview;
4870
4871 [flutterPlatformViewsController prerollCompositeEmbeddedView:2
4872 withParams:std::move(embeddedViewParams2)];
4873 [flutterPlatformViewsController
4875 withParams:[flutterPlatformViewsController compositionParamsForView:2]];
4876
4877 UIView* childClippingView2 = view2.superview.superview;
4878 UIView* maskView1 = childClippingView1.maskView;
4879 UIView* maskView2 = childClippingView2.maskView;
4880 XCTAssertNotEqual(maskView1, maskView2);
4881}
4882
4883- (void)testMaskViewUsesCAShapeLayerAsTheBackingLayer {
4884 flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
4885
4887 FlutterPlatformViewsController* flutterPlatformViewsController =
4888 [[FlutterPlatformViewsController alloc] init];
4889 flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
4890 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
4891 /*delegate=*/mock_delegate,
4892 /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
4893 /*platform_views_controller=*/flutterPlatformViewsController,
4894 /*task_runners=*/runners.taskRunners,
4895 /*worker_task_runner=*/nil,
4896 /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
4897
4900 [flutterPlatformViewsController
4901 registerViewFactory:factory
4902 withId:@"MockFlutterPlatformView"
4903 gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
4904 FlutterResult result = ^(id result) {
4905 };
4906
4907 [flutterPlatformViewsController
4909 arguments:@{
4910 @"id" : @1,
4911 @"viewType" : @"MockFlutterPlatformView",
4912 @"gestureBlockingPolicy" : @"eager"
4913 }]
4914 result:result];
4915
4916 XCTAssertNotNil(gMockPlatformView);
4917 UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
4918 flutterPlatformViewsController.flutterView = flutterView;
4919 // Create embedded view params
4921 // Layer tree always pushes a screen scale factor to the stack
4922 flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
4923 flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
4924 stack1.PushTransform(screenScaleMatrix);
4925 // Push a clip rect
4926 flutter::DlRect rect = flutter::DlRect::MakeXYWH(2, 2, 3, 3);
4927 stack1.PushClipRect(rect);
4928
4929 auto embeddedViewParams1 = std::make_unique<flutter::EmbeddedViewParams>(
4930 screenScaleMatrix, flutter::DlSize(10, 10), stack1);
4931
4933 stack2.PushClipRect(rect);
4934 auto embeddedViewParams2 = std::make_unique<flutter::EmbeddedViewParams>(
4935 screenScaleMatrix, flutter::DlSize(10, 10), stack2);
4936
4937 [flutterPlatformViewsController prerollCompositeEmbeddedView:1
4938 withParams:std::move(embeddedViewParams1)];
4939 [flutterPlatformViewsController
4941 withParams:[flutterPlatformViewsController compositionParamsForView:1]];
4942
4943 UIView* childClippingView = gMockPlatformView.superview.superview;
4944
4945 UIView* maskView = childClippingView.maskView;
4946 XCTAssert([maskView.layer isKindOfClass:[CAShapeLayer class]],
4947 @"Mask view must use CAShapeLayer as its backing layer.");
4948}
4949
4950// Return true if a correct visual effect view is found. It also implies all the validation in this
4951// method passes.
4952//
4953// There are two fail states for this method. 1. One of the XCTAssert method failed; or 2. No
4954// correct visual effect view found.
4955- (BOOL)validateOneVisualEffectView:(UIView*)visualEffectView
4956 expectedFrame:(CGRect)frame
4957 inputRadius:(CGFloat)inputRadius {
4958 XCTAssertTrue(CGRectEqualToRect(visualEffectView.frame, frame));
4959 for (UIView* view in visualEffectView.subviews) {
4960 if (![NSStringFromClass([view class]) hasSuffix:@"BackdropView"]) {
4961 continue;
4962 }
4963 XCTAssertEqual(view.layer.filters.count, 1u);
4964 NSObject* filter = view.layer.filters.firstObject;
4965
4966 XCTAssertEqualObjects([filter valueForKey:@"name"], @"gaussianBlur");
4967
4968 NSObject* inputRadiusInFilter = [filter valueForKey:@"inputRadius"];
4969 XCTAssertTrue([inputRadiusInFilter isKindOfClass:[NSNumber class]] &&
4970 flutter::BlurRadiusEqualToBlurRadius(((NSNumber*)inputRadiusInFilter).floatValue,
4971 inputRadius));
4972 return YES;
4973 }
4974 return NO;
4975}
4976
4977- (void)testDisposingViewInCompositionOrderDoNotCrash {
4978 flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
4979
4981 FlutterPlatformViewsController* flutterPlatformViewsController =
4982 [[FlutterPlatformViewsController alloc] init];
4983 flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
4984 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
4985 /*delegate=*/mock_delegate,
4986 /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
4987 /*platform_views_controller=*/flutterPlatformViewsController,
4988 /*task_runners=*/runners.taskRunners,
4989 /*worker_task_runner=*/nil,
4990 /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
4991
4992 UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
4993 flutterPlatformViewsController.flutterView = flutterView;
4994
4997 [flutterPlatformViewsController
4998 registerViewFactory:factory
4999 withId:@"MockFlutterPlatformView"
5000 gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
5001 FlutterResult result = ^(id result) {
5002 };
5003
5004 [flutterPlatformViewsController
5006 arguments:@{
5007 @"id" : @0,
5008 @"viewType" : @"MockFlutterPlatformView",
5009 @"gestureBlockingPolicy" : @"eager"
5010 }]
5011 result:result];
5012 [flutterPlatformViewsController
5014 arguments:@{
5015 @"id" : @1,
5016 @"viewType" : @"MockFlutterPlatformView",
5017 @"gestureBlockingPolicy" : @"eager"
5018 }]
5019 result:result];
5020
5021 {
5022 // **** First frame, view id 0, 1 in the composition_order_, disposing view 0 is called. **** //
5023 // No view should be disposed, or removed from the composition order.
5024 [flutterPlatformViewsController beginFrameWithSize:flutter::DlISize(300, 300)];
5026 flutter::DlMatrix finalMatrix;
5027 auto embeddedViewParams0 = std::make_unique<flutter::EmbeddedViewParams>(
5028 finalMatrix, flutter::DlSize(300, 300), stack);
5029 [flutterPlatformViewsController prerollCompositeEmbeddedView:0
5030 withParams:std::move(embeddedViewParams0)];
5031
5032 auto embeddedViewParams1 = std::make_unique<flutter::EmbeddedViewParams>(
5033 finalMatrix, flutter::DlSize(300, 300), stack);
5034 [flutterPlatformViewsController prerollCompositeEmbeddedView:1
5035 withParams:std::move(embeddedViewParams1)];
5036
5037 XCTAssertEqual(flutterPlatformViewsController.embeddedViewCount, 2UL);
5038
5039 XCTestExpectation* expectation = [self expectationWithDescription:@"dispose call ended."];
5040 FlutterResult disposeResult = ^(id result) {
5041 [expectation fulfill];
5042 };
5043
5044 [flutterPlatformViewsController
5046 result:disposeResult];
5047 [self waitForExpectationsWithTimeout:30 handler:nil];
5048
5050 auto mock_surface = std::make_unique<flutter::SurfaceFrame>(
5051 nullptr, framebuffer_info,
5052 [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; },
5053 [](const flutter::SurfaceFrame& surface_frame) { return true; },
5054 /*frame_size=*/flutter::DlISize(800, 600), nullptr,
5055 /*display_list_fallback=*/true);
5056 XCTAssertTrue([flutterPlatformViewsController
5057 submitFrame:std::move(mock_surface)
5058 withIosContext:std::make_shared<flutter::IOSContextNoop>()]);
5059
5060 // Disposing won't remove embedded views until the view is removed from the composition_order_
5061 XCTAssertEqual(flutterPlatformViewsController.embeddedViewCount, 2UL);
5062 XCTAssertNotNil([flutterPlatformViewsController platformViewForId:0]);
5063 XCTAssertNotNil([flutterPlatformViewsController platformViewForId:1]);
5064 }
5065
5066 {
5067 // **** Second frame, view id 1 in the composition_order_, no disposing view is called, **** //
5068 // View 0 is removed from the composition order in this frame, hence also disposed.
5069 [flutterPlatformViewsController beginFrameWithSize:flutter::DlISize(300, 300)];
5071 flutter::DlMatrix finalMatrix;
5072 auto embeddedViewParams1 = std::make_unique<flutter::EmbeddedViewParams>(
5073 finalMatrix, flutter::DlSize(300, 300), stack);
5074 [flutterPlatformViewsController prerollCompositeEmbeddedView:1
5075 withParams:std::move(embeddedViewParams1)];
5076
5078 auto mock_surface = std::make_unique<flutter::SurfaceFrame>(
5079 nullptr, framebuffer_info,
5080 [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; },
5081 [](const flutter::SurfaceFrame& surface_frame) { return true; },
5082 /*frame_size=*/flutter::DlISize(800, 600), nullptr, /*display_list_fallback=*/true);
5083 XCTAssertTrue([flutterPlatformViewsController
5084 submitFrame:std::move(mock_surface)
5085 withIosContext:std::make_shared<flutter::IOSContextNoop>()]);
5086
5087 // Disposing won't remove embedded views until the view is removed from the composition_order_
5088 XCTAssertEqual(flutterPlatformViewsController.embeddedViewCount, 1UL);
5089 XCTAssertNil([flutterPlatformViewsController platformViewForId:0]);
5090 XCTAssertNotNil([flutterPlatformViewsController platformViewForId:1]);
5091 }
5092}
5093- (void)testOnlyPlatformViewsAreRemovedWhenReset {
5094 flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
5095
5097 FlutterPlatformViewsController* flutterPlatformViewsController =
5098 [[FlutterPlatformViewsController alloc] init];
5099 flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
5100 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
5101 /*delegate=*/mock_delegate,
5102 /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
5103 /*platform_views_controller=*/flutterPlatformViewsController,
5104 /*task_runners=*/runners.taskRunners,
5105 /*worker_task_runner=*/nil,
5106 /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
5107
5110 [flutterPlatformViewsController
5111 registerViewFactory:factory
5112 withId:@"MockFlutterPlatformView"
5113 gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
5114 FlutterResult result = ^(id result) {
5115 };
5116 [flutterPlatformViewsController
5118 arguments:@{
5119 @"id" : @2,
5120 @"viewType" : @"MockFlutterPlatformView",
5121 @"gestureBlockingPolicy" : @"eager"
5122 }]
5123 result:result];
5124 UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
5125 flutterPlatformViewsController.flutterView = flutterView;
5126 // Create embedded view params
5128 // Layer tree always pushes a screen scale factor to the stack
5129 flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
5130 flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
5131 stack.PushTransform(screenScaleMatrix);
5132 // Push a translate matrix
5133 flutter::DlMatrix translateMatrix = flutter::DlMatrix::MakeTranslation({100, 100});
5134 stack.PushTransform(translateMatrix);
5135 flutter::DlMatrix finalMatrix = screenScaleMatrix * translateMatrix;
5136
5137 auto embeddedViewParams =
5138 std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, flutter::DlSize(300, 300), stack);
5139
5140 [flutterPlatformViewsController prerollCompositeEmbeddedView:2
5141 withParams:std::move(embeddedViewParams)];
5142
5144 auto mock_surface = std::make_unique<flutter::SurfaceFrame>(
5145 nullptr, framebuffer_info,
5146 [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; },
5147 [](const flutter::SurfaceFrame& surface_frame) { return true; },
5148 /*frame_size=*/flutter::DlISize(800, 600), nullptr, /*display_list_fallback=*/true);
5149 [flutterPlatformViewsController submitFrame:std::move(mock_surface)
5150 withIosContext:std::make_shared<flutter::IOSContextNoop>()];
5151
5152 UIView* someView = [[UIView alloc] init];
5153 [flutterView addSubview:someView];
5154
5155 [flutterPlatformViewsController reset];
5156 XCTAssertEqual(flutterView.subviews.count, 1u);
5157 XCTAssertEqual(flutterView.subviews.firstObject, someView);
5158}
5159
5160- (void)testResetClearsPreviousCompositionOrder {
5161 flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
5162
5164 FlutterPlatformViewsController* flutterPlatformViewsController =
5165 [[FlutterPlatformViewsController alloc] init];
5166 flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
5167 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
5168 /*delegate=*/mock_delegate,
5169 /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
5170 /*platform_views_controller=*/flutterPlatformViewsController,
5171 /*task_runners=*/runners.taskRunners,
5172 /*worker_task_runner=*/nil,
5173 /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
5174
5177 [flutterPlatformViewsController
5178 registerViewFactory:factory
5179 withId:@"MockFlutterPlatformView"
5180 gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
5181 FlutterResult result = ^(id result) {
5182 };
5183 [flutterPlatformViewsController
5185 arguments:@{
5186 @"id" : @2,
5187 @"viewType" : @"MockFlutterPlatformView",
5188 @"gestureBlockingPolicy" : @"eager"
5189 }]
5190 result:result];
5191 UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
5192 flutterPlatformViewsController.flutterView = flutterView;
5193 // Create embedded view params
5195 // Layer tree always pushes a screen scale factor to the stack
5196 flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
5197 flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
5198 stack.PushTransform(screenScaleMatrix);
5199 // Push a translate matrix
5200 flutter::DlMatrix translateMatrix = flutter::DlMatrix::MakeTranslation({100, 100});
5201 stack.PushTransform(translateMatrix);
5202 flutter::DlMatrix finalMatrix = screenScaleMatrix * translateMatrix;
5203
5204 auto embeddedViewParams =
5205 std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, flutter::DlSize(300, 300), stack);
5206
5207 [flutterPlatformViewsController prerollCompositeEmbeddedView:2
5208 withParams:std::move(embeddedViewParams)];
5209
5211 auto mock_surface = std::make_unique<flutter::SurfaceFrame>(
5212 nullptr, framebuffer_info,
5213 [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; },
5214 [](const flutter::SurfaceFrame& surface_frame) { return true; },
5215 /*frame_size=*/flutter::DlISize(800, 600), nullptr, /*display_list_fallback=*/true);
5216 [flutterPlatformViewsController submitFrame:std::move(mock_surface)
5217 withIosContext:std::make_shared<flutter::IOSContextNoop>()];
5218
5219 // The above code should result in previousCompositionOrder having one viewId in it
5220 XCTAssertEqual(flutterPlatformViewsController.previousCompositionOrder.count, 1ul);
5221
5222 // reset should clear previousCompositionOrder
5223 [flutterPlatformViewsController reset];
5224
5225 // previousCompositionOrder should now be empty
5226 XCTAssertEqual(flutterPlatformViewsController.previousCompositionOrder.count, 0ul);
5227}
5228
5229- (void)testNilPlatformViewDoesntCrash {
5230 flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
5231
5233 FlutterPlatformViewsController* flutterPlatformViewsController =
5234 [[FlutterPlatformViewsController alloc] init];
5235 flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
5236 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
5237 /*delegate=*/mock_delegate,
5238 /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
5239 /*platform_views_controller=*/flutterPlatformViewsController,
5240 /*task_runners=*/runners.taskRunners,
5241 /*worker_task_runner=*/nil,
5242 /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
5243
5246 [flutterPlatformViewsController
5247 registerViewFactory:factory
5248 withId:@"MockFlutterPlatformView"
5249 gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
5250 FlutterResult result = ^(id result) {
5251 };
5252 [flutterPlatformViewsController
5254 arguments:@{
5255 @"id" : @2,
5256 @"viewType" : @"MockFlutterPlatformView",
5257 @"gestureBlockingPolicy" : @"eager"
5258 }]
5259 result:result];
5260 UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
5261 flutterPlatformViewsController.flutterView = flutterView;
5262
5263 // Create embedded view params
5265 // Layer tree always pushes a screen scale factor to the stack
5266 flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
5267 flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
5268 stack.PushTransform(screenScaleMatrix);
5269 // Push a translate matrix
5270 flutter::DlMatrix translateMatrix = flutter::DlMatrix::MakeTranslation({100, 100});
5271 stack.PushTransform(translateMatrix);
5272 flutter::DlMatrix finalMatrix = screenScaleMatrix * translateMatrix;
5273
5274 auto embeddedViewParams =
5275 std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, flutter::DlSize(300, 300), stack);
5276
5277 [flutterPlatformViewsController prerollCompositeEmbeddedView:2
5278 withParams:std::move(embeddedViewParams)];
5279
5281 auto mock_surface = std::make_unique<flutter::SurfaceFrame>(
5282 nullptr, framebuffer_info,
5283 [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; },
5284 [](const flutter::SurfaceFrame& surface_frame) { return true; },
5285 /*frame_size=*/flutter::DlISize(800, 600), nullptr, /*display_list_fallback=*/true);
5286 [flutterPlatformViewsController submitFrame:std::move(mock_surface)
5287 withIosContext:std::make_shared<flutter::IOSContextNoop>()];
5288
5289 XCTAssertEqual(flutterView.subviews.count, 1u);
5290}
5291
5292- (void)testFlutterTouchInterceptingViewLinksToAccessibilityContainer {
5293 FlutterTouchInterceptingView* touchInterceptorView = [[FlutterTouchInterceptingView alloc] init];
5294 NSObject* container = [[NSObject alloc] init];
5295 [touchInterceptorView setFlutterAccessibilityContainer:container];
5296 XCTAssertEqualObjects([touchInterceptorView accessibilityContainer], container);
5297}
5298
5299- (void)testLayerPool {
5300 // Create an IOSContext.
5301 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar"];
5302 [engine run];
5303 XCTAssertTrue(engine.platformView != nullptr);
5304 auto ios_context = engine.platformView->GetIosContext();
5305
5306 auto pool = flutter::OverlayLayerPool{};
5307
5308 // Add layers to the pool.
5309 pool.CreateLayer(ios_context, MTLPixelFormatBGRA8Unorm);
5310 XCTAssertEqual(pool.size(), 1u);
5311 pool.CreateLayer(ios_context, MTLPixelFormatBGRA8Unorm);
5312 XCTAssertEqual(pool.size(), 2u);
5313
5314 // Mark all layers as unused.
5315 pool.RecycleLayers();
5316 XCTAssertEqual(pool.size(), 2u);
5317
5318 // Free the unused layers. One should remain.
5319 auto unused_layers = pool.RemoveUnusedLayers();
5320 XCTAssertEqual(unused_layers.size(), 2u);
5321 XCTAssertEqual(pool.size(), 1u);
5322}
5323
5324- (void)testFlutterPlatformViewControllerSubmitFramePreservingFrameDamage {
5325 flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
5326
5328 FlutterPlatformViewsController* flutterPlatformViewsController =
5329 [[FlutterPlatformViewsController alloc] init];
5330 flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
5331 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
5332 /*delegate=*/mock_delegate,
5333 /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
5334 /*platform_views_controller=*/flutterPlatformViewsController,
5335 /*task_runners=*/runners.taskRunners,
5336 /*worker_task_runner=*/nil,
5337 /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
5338
5339 UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
5340 flutterPlatformViewsController.flutterView = flutterView;
5341
5344 [flutterPlatformViewsController
5345 registerViewFactory:factory
5346 withId:@"MockFlutterPlatformView"
5347 gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
5348 FlutterResult result = ^(id result) {
5349 };
5350 [flutterPlatformViewsController
5352 arguments:@{
5353 @"id" : @0,
5354 @"viewType" : @"MockFlutterPlatformView",
5355 @"gestureBlockingPolicy" : @"eager"
5356 }]
5357 result:result];
5358
5359 // This overwrites `gMockPlatformView` to another view.
5360 [flutterPlatformViewsController
5362 arguments:@{
5363 @"id" : @1,
5364 @"viewType" : @"MockFlutterPlatformView",
5365 @"gestureBlockingPolicy" : @"eager"
5366 }]
5367 result:result];
5368
5369 [flutterPlatformViewsController beginFrameWithSize:flutter::DlISize(300, 300)];
5371 flutter::DlMatrix finalMatrix;
5372 auto embeddedViewParams1 =
5373 std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, flutter::DlSize(300, 300), stack);
5374 [flutterPlatformViewsController prerollCompositeEmbeddedView:0
5375 withParams:std::move(embeddedViewParams1)];
5376
5377 auto embeddedViewParams2 =
5378 std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, flutter::DlSize(500, 500), stack);
5379 [flutterPlatformViewsController prerollCompositeEmbeddedView:1
5380 withParams:std::move(embeddedViewParams2)];
5381
5383 std::optional<flutter::SurfaceFrame::SubmitInfo> submit_info;
5384 auto mock_surface = std::make_unique<flutter::SurfaceFrame>(
5385 nullptr, framebuffer_info,
5386 [](const flutter::SurfaceFrame& surface_frame, flutter::DlCanvas* canvas) { return true; },
5387 [&](const flutter::SurfaceFrame& surface_frame) {
5388 submit_info = surface_frame.submit_info();
5389 return true;
5390 },
5391 /*frame_size=*/flutter::DlISize(800, 600), nullptr,
5392 /*display_list_fallback=*/true);
5393 mock_surface->set_submit_info({
5394 .frame_damage = flutter::DlIRect::MakeWH(800, 600),
5395 .buffer_damage = flutter::DlIRect::MakeWH(400, 600),
5396 });
5397
5398 [flutterPlatformViewsController submitFrame:std::move(mock_surface)
5399 withIosContext:std::make_shared<flutter::IOSContextNoop>()];
5400
5401 XCTAssertTrue(submit_info.has_value());
5402 XCTAssertEqual(*submit_info->frame_damage, flutter::DlIRect::MakeWH(800, 600));
5403 XCTAssertEqual(*submit_info->buffer_damage, flutter::DlIRect::MakeWH(400, 600));
5404}
5405
5406- (void)testClipSuperellipse {
5407 flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
5408
5410 FlutterPlatformViewsController* flutterPlatformViewsController =
5411 [[FlutterPlatformViewsController alloc] init];
5412 flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
5413 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
5414 /*delegate=*/mock_delegate,
5415 /*rendering_api=*/flutter::IOSRenderingAPI::kMetal,
5416 /*platform_views_controller=*/flutterPlatformViewsController,
5417 /*task_runners=*/runners.taskRunners,
5418 /*worker_task_runner=*/nil,
5419 /*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
5420
5423 [flutterPlatformViewsController
5424 registerViewFactory:factory
5425 withId:@"MockFlutterPlatformView"
5426 gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
5427 FlutterResult result = ^(id result) {
5428 };
5429 [flutterPlatformViewsController
5431 arguments:@{
5432 @"id" : @2,
5433 @"viewType" : @"MockFlutterPlatformView",
5434 @"gestureBlockingPolicy" : @"eager"
5435 }]
5436 result:result];
5437
5438 XCTAssertNotNil(gMockPlatformView);
5439
5440 UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
5441 flutterPlatformViewsController.flutterView = flutterView;
5442 // Create embedded view params
5444 // Layer tree always pushes a screen scale factor to the stack
5445 flutter::DlScalar screenScale = [UIScreen mainScreen].scale;
5446 flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1});
5447 stack.PushTransform(screenScaleMatrix);
5448 // Push a clip superellipse
5449 flutter::DlRect rect = flutter::DlRect::MakeXYWH(3, 3, 5, 5);
5451
5452 auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
5453 screenScaleMatrix, flutter::DlSize(10, 10), stack);
5454
5455 [flutterPlatformViewsController prerollCompositeEmbeddedView:2
5456 withParams:std::move(embeddedViewParams)];
5457 [flutterPlatformViewsController
5459 withParams:[flutterPlatformViewsController compositionParamsForView:2]];
5460
5461 gMockPlatformView.backgroundColor = UIColor.redColor;
5462 XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]);
5463 ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
5464 [flutterView addSubview:childClippingView];
5465
5466 [flutterView setNeedsLayout];
5467 [flutterView layoutIfNeeded];
5468
5469 CGPoint corners[] = {CGPointMake(rect.GetLeft(), rect.GetTop()),
5470 CGPointMake(rect.GetRight(), rect.GetTop()),
5471 CGPointMake(rect.GetLeft(), rect.GetBottom()),
5472 CGPointMake(rect.GetRight(), rect.GetBottom())};
5473 for (auto point : corners) {
5474 int alpha = [self alphaOfPoint:point onView:flutterView];
5475 XCTAssertNotEqual(alpha, 255);
5476 }
5477 CGPoint center =
5478 CGPointMake(rect.GetLeft() + rect.GetWidth() / 2, rect.GetTop() + rect.GetHeight() / 2);
5479 int alpha = [self alphaOfPoint:center onView:flutterView];
5480 XCTAssertEqual(alpha, 255);
5481}
5482
5483@end
void(^ FlutterResult)(id _Nullable result)
std::unique_ptr< flutter::PlatformViewIOS > platform_view
static __weak UIView * gMockPlatformView
const float kFloatCompareEpsilon
@ FlutterPlatformViewGestureRecognizersBlockingPolicyEager
@ FlutterPlatformViewGestureRecognizersBlockingPolicyWaitUntilTouchesEnded
@ FlutterPlatformViewGestureRecognizersBlockingPolicyDoNotBlockGesture
BOOL _viewCreated
static __weak MockPlatformView * gMockPlatformView
NSMutableArray * backdropFilterSubviews()
void applyBlurBackdropFilters:(NSArray< PlatformViewFilter * > *filters)
FlutterClippingMaskView * getMaskViewWithFrame:(CGRect frame)
void insertViewToPoolIfNeeded:(FlutterClippingMaskView *maskView)
FlutterPlatformViewGestureRecognizersBlockingPolicy blockingPolicy
AssetResolverType
Identifies the type of AssetResolver an instance is.
static std::shared_ptr< DlImageFilter > Make(DlScalar sigma_x, DlScalar sigma_y, DlTileMode tile_mode, std::optional< DlRect > bounds=std::nullopt)
Developer-facing API for rendering anything within the engine.
Definition dl_canvas.h:32
static std::shared_ptr< DlImageFilter > Make(DlScalar radius_x, DlScalar radius_y)
static DlPath MakeRoundRectXY(const DlRect &rect, DlScalar x_radius, DlScalar y_radius, bool counter_clock_wise=false)
Definition dl_path.cc:76
void PushTransform(const DlMatrix &matrix)
void PushBackdropFilter(const std::shared_ptr< DlImageFilter > &filter, const DlRect &filter_rect)
void PushClipRect(const DlRect &rect)
void PushClipPath(const DlPath &path)
void PushPlatformViewClipRRect(const DlRoundRect &rrect)
void PushClipRRect(const DlRoundRect &rrect)
void PushClipRSE(const DlRoundSuperellipse &rrect)
void PushPlatformViewClipRSuperellipse(const DlRoundSuperellipse &rse)
Storage for Overlay layers across frames.
void CreateLayer(const std::shared_ptr< IOSContext > &ios_context, MTLPixelFormat pixel_format)
Create a new overlay layer.
const std::shared_ptr< IOSContext > & GetIosContext()
const SubmitInfo & submit_info() const
A Mapping like NonOwnedMapping, but uses Free as its release proc.
Definition mapping.h:144
Settings settings_
GLFWwindow * window
Definition main.cc:60
FlutterEngine engine
Definition main.cc:84
FlView * view
const char * message
G_BEGIN_DECLS G_MODULE_EXPORT FlValue * args
G_BEGIN_DECLS FlutterViewId view_id
FlutterDesktopBinaryReply callback
flutter::PlatformViewIOS * platformView()
const flutter::TaskRunners & taskRunners
instancetype methodCallWithMethodName:arguments:(NSString *method,[arguments] id _Nullable arguments)
void pushVisitedPlatformViewId:(int64_t viewId)
Pushes the view id of a visted platform view to the list of visied platform views.
void reset()
Discards all platform views instances and auxiliary resources.
const flutter::EmbeddedViewParams & compositionParamsForView:(int64_t viewId)
void registerViewFactory:withId:gestureRecognizersBlockingPolicy:(NSObject< FlutterPlatformViewFactory > *factory,[withId] NSString *factoryId,[gestureRecognizersBlockingPolicy] FlutterPlatformViewGestureRecognizersBlockingPolicy gestureRecognizerBlockingPolicy)
set the factory used to construct embedded UI Views.
FlutterFMLTaskRunner * taskRunner
The task runner used to post rendering tasks to the platform thread.
UIViewController< FlutterViewResponder > *_Nullable flutterViewController
The flutter view controller.
void compositeView:withParams:(int64_t viewId,[withParams] const flutter::EmbeddedViewParams &params)
UIView *_Nullable flutterView
The flutter view.
NSArray< NSNumber * > * previousCompositionOrder()
void onMethodCall:result:(FlutterMethodCall *call,[result] FlutterResult result)
Handler for platform view message channels.
static MockSurface mock_surface
Definition mock_epoxy.cc:58
FlTexture * texture
FlutterFMLTaskRunner * GetDefaultTaskRunner()
FlutterPlatformViewsController * CreateTestPlatformViewsController(NSString *label)
FlutterFMLTaskRunners * CreateTestTaskRunners(NSString *label)
impeller::Scalar DlScalar
impeller::ISize32 DlISize
std::function< void()> closure
Definition closure.h:14
impeller::ShaderType type
A 4x4 matrix using column-major storage.
Definition matrix.h:37
static constexpr Matrix MakeTranslation(const Vector3 &t)
Definition matrix.h:95
static Matrix MakeRotationZ(Radians r)
Definition matrix.h:223
static constexpr Matrix MakeScale(const Vector3 &s)
Definition matrix.h:104
static RoundRect MakeRectXY(const Rect &rect, Scalar x_radius, Scalar y_radius)
Definition round_rect.h:31
static RoundSuperellipse MakeOval(const Rect &rect)
static RoundSuperellipse MakeRectXY(const Rect &rect, Scalar x_radius, Scalar y_radius)
constexpr auto GetBottom() const
Definition rect.h:391
static constexpr TRect MakeWH(Type width, Type height)
Definition rect.h:140
constexpr auto GetTop() const
Definition rect.h:387
constexpr Type GetHeight() const
Returns the height of the rectangle, equivalent to |GetSize().height|.
Definition rect.h:381
constexpr auto GetLeft() const
Definition rect.h:385
constexpr auto GetRight() const
Definition rect.h:389
static constexpr TRect MakeXYWH(Type x, Type y, Type width, Type height)
Definition rect.h:136
constexpr Type GetWidth() const
Returns the width of the rectangle, equivalent to |GetSize().width|.
Definition rect.h:375
int64_t texture_id
int BOOL