Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit eed6905

Browse files
authored
[CP][3.27][ios]enable the webview non tappable workaround by checking subviews recursively #57168 (#57172)
CP for #57168 and #57193 *List which issues are fixed by this PR. You must list at least one issue.* flutter/flutter#158961 *If you had to change anything in the [flutter/tests] repo, include a link to the migration guide as per the [breaking change policy].* [C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
1 parent 83bacfc commit eed6905

File tree

2 files changed

+235
-1
lines changed

2 files changed

+235
-1
lines changed

shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ - (void)checkViewCreatedOnce {
6969
self.viewCreated = YES;
7070
}
7171

72+
- (void)dealloc {
73+
gMockPlatformView = nil;
74+
}
7275
@end
7376

7477
@interface FlutterPlatformViewsTestMockFlutterPlatformFactory
@@ -110,6 +113,10 @@ - (void)checkViewCreatedOnce {
110113
}
111114
self.viewCreated = YES;
112115
}
116+
117+
- (void)dealloc {
118+
gMockPlatformView = nil;
119+
}
113120
@end
114121

115122
@interface FlutterPlatformViewsTestMockWebViewFactory : NSObject <FlutterPlatformViewFactory>
@@ -135,6 +142,93 @@ @implementation FlutterPlatformViewsTestNilFlutterPlatformFactory
135142

136143
@end
137144

145+
@interface FlutterPlatformViewsTestMockWrapperWebView : NSObject <FlutterPlatformView>
146+
@property(nonatomic, strong) UIView* view;
147+
@property(nonatomic, assign) BOOL viewCreated;
148+
@end
149+
150+
@implementation FlutterPlatformViewsTestMockWrapperWebView
151+
- (instancetype)init {
152+
if (self = [super init]) {
153+
_view = [[UIView alloc] init];
154+
[_view addSubview:[[WKWebView alloc] init]];
155+
gMockPlatformView = _view;
156+
_viewCreated = NO;
157+
}
158+
return self;
159+
}
160+
161+
- (UIView*)view {
162+
[self checkViewCreatedOnce];
163+
return _view;
164+
}
165+
166+
- (void)checkViewCreatedOnce {
167+
if (self.viewCreated) {
168+
abort();
169+
}
170+
self.viewCreated = YES;
171+
}
172+
173+
- (void)dealloc {
174+
gMockPlatformView = nil;
175+
}
176+
@end
177+
178+
@interface FlutterPlatformViewsTestMockWrapperWebViewFactory : NSObject <FlutterPlatformViewFactory>
179+
@end
180+
181+
@implementation FlutterPlatformViewsTestMockWrapperWebViewFactory
182+
- (NSObject<FlutterPlatformView>*)createWithFrame:(CGRect)frame
183+
viewIdentifier:(int64_t)viewId
184+
arguments:(id _Nullable)args {
185+
return [[FlutterPlatformViewsTestMockWrapperWebView alloc] init];
186+
}
187+
@end
188+
189+
@interface FlutterPlatformViewsTestMockNestedWrapperWebView : NSObject <FlutterPlatformView>
190+
@property(nonatomic, strong) UIView* view;
191+
@property(nonatomic, assign) BOOL viewCreated;
192+
@end
193+
194+
@implementation FlutterPlatformViewsTestMockNestedWrapperWebView
195+
- (instancetype)init {
196+
if (self = [super init]) {
197+
_view = [[UIView alloc] init];
198+
UIView* childView = [[UIView alloc] init];
199+
[_view addSubview:childView];
200+
[childView addSubview:[[WKWebView alloc] init]];
201+
gMockPlatformView = _view;
202+
_viewCreated = NO;
203+
}
204+
return self;
205+
}
206+
207+
- (UIView*)view {
208+
[self checkViewCreatedOnce];
209+
return _view;
210+
}
211+
212+
- (void)checkViewCreatedOnce {
213+
if (self.viewCreated) {
214+
abort();
215+
}
216+
self.viewCreated = YES;
217+
}
218+
@end
219+
220+
@interface FlutterPlatformViewsTestMockNestedWrapperWebViewFactory
221+
: NSObject <FlutterPlatformViewFactory>
222+
@end
223+
224+
@implementation FlutterPlatformViewsTestMockNestedWrapperWebViewFactory
225+
- (NSObject<FlutterPlatformView>*)createWithFrame:(CGRect)frame
226+
viewIdentifier:(int64_t)viewId
227+
arguments:(id _Nullable)args {
228+
return [[FlutterPlatformViewsTestMockNestedWrapperWebView alloc] init];
229+
}
230+
@end
231+
138232
namespace flutter {
139233
namespace {
140234
class FlutterPlatformViewsTestMockPlatformViewDelegate : public PlatformView::Delegate {
@@ -2883,6 +2977,125 @@ - (void)testFlutterPlatformViewTouchesCancelledEventAreForcedToBeCancelled {
28832977
}
28842978
}
28852979

2980+
- (void)
2981+
testFlutterPlatformViewBlockGestureUnderEagerPolicyShouldRemoveAndAddBackDelayingRecognizerForWrapperWebView {
2982+
flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
2983+
2984+
flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
2985+
/*platform=*/GetDefaultTaskRunner(),
2986+
/*raster=*/GetDefaultTaskRunner(),
2987+
/*ui=*/GetDefaultTaskRunner(),
2988+
/*io=*/GetDefaultTaskRunner());
2989+
auto flutterPlatformViewsController = std::make_shared<flutter::PlatformViewsController>();
2990+
flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner());
2991+
auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2992+
/*delegate=*/mock_delegate,
2993+
/*rendering_api=*/mock_delegate.settings_.enable_impeller
2994+
? flutter::IOSRenderingAPI::kMetal
2995+
: flutter::IOSRenderingAPI::kSoftware,
2996+
/*platform_views_controller=*/flutterPlatformViewsController,
2997+
/*task_runners=*/runners,
2998+
/*worker_task_runner=*/nil,
2999+
/*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
3000+
3001+
FlutterPlatformViewsTestMockWrapperWebViewFactory* factory =
3002+
[[FlutterPlatformViewsTestMockWrapperWebViewFactory alloc] init];
3003+
flutterPlatformViewsController->RegisterViewFactory(
3004+
factory, @"MockWrapperWebView", FlutterPlatformViewGestureRecognizersBlockingPolicyEager);
3005+
FlutterResult result = ^(id result) {
3006+
};
3007+
flutterPlatformViewsController->OnMethodCall(
3008+
[FlutterMethodCall
3009+
methodCallWithMethodName:@"create"
3010+
arguments:@{@"id" : @2, @"viewType" : @"MockWrapperWebView"}],
3011+
result);
3012+
3013+
XCTAssertNotNil(gMockPlatformView);
3014+
3015+
// Find touch inteceptor view
3016+
UIView* touchInteceptorView = gMockPlatformView;
3017+
while (touchInteceptorView != nil &&
3018+
![touchInteceptorView isKindOfClass:[FlutterTouchInterceptingView class]]) {
3019+
touchInteceptorView = touchInteceptorView.superview;
3020+
}
3021+
XCTAssertNotNil(touchInteceptorView);
3022+
3023+
XCTAssert(touchInteceptorView.gestureRecognizers.count == 2);
3024+
UIGestureRecognizer* delayingRecognizer = touchInteceptorView.gestureRecognizers[0];
3025+
UIGestureRecognizer* forwardingRecognizer = touchInteceptorView.gestureRecognizers[1];
3026+
3027+
XCTAssert([delayingRecognizer isKindOfClass:[FlutterDelayingGestureRecognizer class]]);
3028+
XCTAssert([forwardingRecognizer isKindOfClass:[ForwardingGestureRecognizer class]]);
3029+
3030+
[(FlutterTouchInterceptingView*)touchInteceptorView blockGesture];
3031+
3032+
if (@available(iOS 18.2, *)) {
3033+
// Since we remove and add back delayingRecognizer, it would be reordered to the last.
3034+
XCTAssertEqual(touchInteceptorView.gestureRecognizers[0], forwardingRecognizer);
3035+
XCTAssertEqual(touchInteceptorView.gestureRecognizers[1], delayingRecognizer);
3036+
} else {
3037+
XCTAssertEqual(touchInteceptorView.gestureRecognizers[0], delayingRecognizer);
3038+
XCTAssertEqual(touchInteceptorView.gestureRecognizers[1], forwardingRecognizer);
3039+
}
3040+
}
3041+
3042+
- (void)
3043+
testFlutterPlatformViewBlockGestureUnderEagerPolicyShouldNotRemoveAndAddBackDelayingRecognizerForNestedWrapperWebView {
3044+
flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
3045+
3046+
flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
3047+
/*platform=*/GetDefaultTaskRunner(),
3048+
/*raster=*/GetDefaultTaskRunner(),
3049+
/*ui=*/GetDefaultTaskRunner(),
3050+
/*io=*/GetDefaultTaskRunner());
3051+
auto flutterPlatformViewsController = std::make_shared<flutter::PlatformViewsController>();
3052+
flutterPlatformViewsController->SetTaskRunner(GetDefaultTaskRunner());
3053+
auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
3054+
/*delegate=*/mock_delegate,
3055+
/*rendering_api=*/mock_delegate.settings_.enable_impeller
3056+
? flutter::IOSRenderingAPI::kMetal
3057+
: flutter::IOSRenderingAPI::kSoftware,
3058+
/*platform_views_controller=*/flutterPlatformViewsController,
3059+
/*task_runners=*/runners,
3060+
/*worker_task_runner=*/nil,
3061+
/*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
3062+
3063+
FlutterPlatformViewsTestMockNestedWrapperWebViewFactory* factory =
3064+
[[FlutterPlatformViewsTestMockNestedWrapperWebViewFactory alloc] init];
3065+
flutterPlatformViewsController->RegisterViewFactory(
3066+
factory, @"MockNestedWrapperWebView",
3067+
FlutterPlatformViewGestureRecognizersBlockingPolicyEager);
3068+
FlutterResult result = ^(id result) {
3069+
};
3070+
flutterPlatformViewsController->OnMethodCall(
3071+
[FlutterMethodCall
3072+
methodCallWithMethodName:@"create"
3073+
arguments:@{@"id" : @2, @"viewType" : @"MockNestedWrapperWebView"}],
3074+
result);
3075+
3076+
XCTAssertNotNil(gMockPlatformView);
3077+
3078+
// Find touch inteceptor view
3079+
UIView* touchInteceptorView = gMockPlatformView;
3080+
while (touchInteceptorView != nil &&
3081+
![touchInteceptorView isKindOfClass:[FlutterTouchInterceptingView class]]) {
3082+
touchInteceptorView = touchInteceptorView.superview;
3083+
}
3084+
XCTAssertNotNil(touchInteceptorView);
3085+
3086+
XCTAssert(touchInteceptorView.gestureRecognizers.count == 2);
3087+
UIGestureRecognizer* delayingRecognizer = touchInteceptorView.gestureRecognizers[0];
3088+
UIGestureRecognizer* forwardingRecognizer = touchInteceptorView.gestureRecognizers[1];
3089+
3090+
XCTAssert([delayingRecognizer isKindOfClass:[FlutterDelayingGestureRecognizer class]]);
3091+
XCTAssert([forwardingRecognizer isKindOfClass:[ForwardingGestureRecognizer class]]);
3092+
3093+
[(FlutterTouchInterceptingView*)touchInteceptorView blockGesture];
3094+
3095+
XCTAssertEqual(touchInteceptorView.gestureRecognizers[0], delayingRecognizer);
3096+
XCTAssertEqual(touchInteceptorView.gestureRecognizers[1], forwardingRecognizer);
3097+
}
3098+
28863099
- (void)
28873100
testFlutterPlatformViewBlockGestureUnderEagerPolicyShouldNotRemoveAndAddBackDelayingRecognizerForNonWebView {
28883101
flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;

shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -553,6 +553,21 @@ - (void)releaseGesture {
553553
self.delayingRecognizer.state = UIGestureRecognizerStateFailed;
554554
}
555555

556+
- (BOOL)containsWebView:(UIView*)view remainingSubviewDepth:(int)remainingSubviewDepth {
557+
if (remainingSubviewDepth < 0) {
558+
return NO;
559+
}
560+
if ([view isKindOfClass:[WKWebView class]]) {
561+
return YES;
562+
}
563+
for (UIView* subview in view.subviews) {
564+
if ([self containsWebView:subview remainingSubviewDepth:remainingSubviewDepth - 1]) {
565+
return YES;
566+
}
567+
}
568+
return NO;
569+
}
570+
556571
- (void)blockGesture {
557572
switch (_blockingPolicy) {
558573
case FlutterPlatformViewGestureRecognizersBlockingPolicyEager:
@@ -568,7 +583,13 @@ - (void)blockGesture {
568583
// FlutterPlatformViewGestureRecognizersBlockingPolicyEager, but we should try it if a similar
569584
// issue arises for the other policy.
570585
if (@available(iOS 18.2, *)) {
571-
if ([self.embeddedView isKindOfClass:[WKWebView class]]) {
586+
// This workaround is designed for WKWebView only. The 1P web view plugin provides a
587+
// WKWebView itself as the platform view. However, some 3P plugins provide wrappers of
588+
// WKWebView instead. So we perform DFS to search the view hierarchy (with a depth limit).
589+
// Passing a limit of 0 means only searching for platform view itself; Pass 1 to include its
590+
// children as well, and so on. We should be conservative and start with a small number. The
591+
// AdMob banner has a WKWebView at depth 7.
592+
if ([self containsWebView:self.embeddedView remainingSubviewDepth:1]) {
572593
[self removeGestureRecognizer:self.delayingRecognizer];
573594
[self addGestureRecognizer:self.delayingRecognizer];
574595
}

0 commit comments

Comments
 (0)