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