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