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