[ios]limit web view not tappable workaround to a limited depth (flutter/engine#57193)

This PR limits the search depth, because we don't want to enable this workaround for AdMob banner, which has a WKWebView in the depth of 7. See the previous PR for more context: https://github.com/flutter/engine/pull/57168

I was able to confirm that this returns YES for the 3P plugin, and NO for AdMob. 

*List which issues are fixed by this PR. You must list at least one issue.*
https://github.com/flutter/flutter/issues/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
This commit is contained in:
hellohuanlin 2024-12-13 11:25:25 -08:00 committed by GitHub
parent ee9024da88
commit e2970df999
2 changed files with 127 additions and 3 deletions

View File

@ -566,12 +566,15 @@ static BOOL _preparedOnce = NO;
self.delayingRecognizer.state = UIGestureRecognizerStateFailed;
}
- (BOOL)containsWebView:(UIView*)view {
- (BOOL)containsWebView:(UIView*)view remainingSubviewDepth:(int)remainingSubviewDepth {
if (remainingSubviewDepth < 0) {
return NO;
}
if ([view isKindOfClass:[WKWebView class]]) {
return YES;
}
for (UIView* subview in view.subviews) {
if ([self containsWebView:subview]) {
if ([self containsWebView:subview remainingSubviewDepth:remainingSubviewDepth - 1]) {
return YES;
}
}
@ -593,7 +596,13 @@ static BOOL _preparedOnce = NO;
// FlutterPlatformViewGestureRecognizersBlockingPolicyEager, but we should try it if a similar
// issue arises for the other policy.
if (@available(iOS 18.2, *)) {
if ([self containsWebView:self.embeddedView]) {
// This workaround is designed for WKWebView only. The 1P web view plugin provides a
// WKWebView itself as the platform view. However, some 3P plugins provide wrappers of
// WKWebView instead. So we perform DFS to search the view hierarchy (with a depth limit).
// Passing a limit of 0 means only searching for platform view itself; Pass 1 to include its
// children as well, and so on. We should be conservative and start with a small number. The
// AdMob banner has a WKWebView at depth 7.
if ([self containsWebView:self.embeddedView remainingSubviewDepth:1]) {
[self removeGestureRecognizer:self.delayingRecognizer];
[self addGestureRecognizer:self.delayingRecognizer];
}

View File

@ -74,6 +74,9 @@ const float kFloatCompareEpsilon = 0.001;
self.viewCreated = YES;
}
- (void)dealloc {
gMockPlatformView = nil;
}
@end
@interface FlutterPlatformViewsTestMockFlutterPlatformFactory
@ -115,6 +118,10 @@ const float kFloatCompareEpsilon = 0.001;
}
self.viewCreated = YES;
}
- (void)dealloc {
gMockPlatformView = nil;
}
@end
@interface FlutterPlatformViewsTestMockWebViewFactory : NSObject <FlutterPlatformViewFactory>
@ -167,6 +174,10 @@ const float kFloatCompareEpsilon = 0.001;
}
self.viewCreated = YES;
}
- (void)dealloc {
gMockPlatformView = nil;
}
@end
@interface FlutterPlatformViewsTestMockWrapperWebViewFactory : NSObject <FlutterPlatformViewFactory>
@ -180,6 +191,49 @@ const float kFloatCompareEpsilon = 0.001;
}
@end
@interface FlutterPlatformViewsTestMockNestedWrapperWebView : NSObject <FlutterPlatformView>
@property(nonatomic, strong) UIView* view;
@property(nonatomic, assign) BOOL viewCreated;
@end
@implementation FlutterPlatformViewsTestMockNestedWrapperWebView
- (instancetype)init {
if (self = [super init]) {
_view = [[UIView alloc] init];
UIView* childView = [[UIView alloc] init];
[_view addSubview:childView];
[childView addSubview:[[WKWebView alloc] init]];
gMockPlatformView = _view;
_viewCreated = NO;
}
return self;
}
- (UIView*)view {
[self checkViewCreatedOnce];
return _view;
}
- (void)checkViewCreatedOnce {
if (self.viewCreated) {
abort();
}
self.viewCreated = YES;
}
@end
@interface FlutterPlatformViewsTestMockNestedWrapperWebViewFactory
: NSObject <FlutterPlatformViewFactory>
@end
@implementation FlutterPlatformViewsTestMockNestedWrapperWebViewFactory
- (NSObject<FlutterPlatformView>*)createWithFrame:(CGRect)frame
viewIdentifier:(int64_t)viewId
arguments:(id _Nullable)args {
return [[FlutterPlatformViewsTestMockNestedWrapperWebView alloc] init];
}
@end
namespace flutter {
namespace {
class FlutterPlatformViewsTestMockPlatformViewDelegate : public PlatformView::Delegate {
@ -3258,6 +3312,67 @@ fml::RefPtr<fml::TaskRunner> GetDefaultTaskRunner() {
}
}
- (void)
testFlutterPlatformViewBlockGestureUnderEagerPolicyShouldNotRemoveAndAddBackDelayingRecognizerForNestedWrapperWebView {
flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
/*platform=*/GetDefaultTaskRunner(),
/*raster=*/GetDefaultTaskRunner(),
/*ui=*/GetDefaultTaskRunner(),
/*io=*/GetDefaultTaskRunner());
FlutterPlatformViewsController* flutterPlatformViewsController =
[[FlutterPlatformViewsController alloc] init];
flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
/*delegate=*/mock_delegate,
/*rendering_api=*/mock_delegate.settings_.enable_impeller
? flutter::IOSRenderingAPI::kMetal
: flutter::IOSRenderingAPI::kSoftware,
/*platform_views_controller=*/flutterPlatformViewsController,
/*task_runners=*/runners,
/*worker_task_runner=*/nil,
/*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
FlutterPlatformViewsTestMockNestedWrapperWebViewFactory* factory =
[[FlutterPlatformViewsTestMockNestedWrapperWebViewFactory alloc] init];
[flutterPlatformViewsController
registerViewFactory:factory
withId:@"MockNestedWrapperWebView"
gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
FlutterResult result = ^(id result) {
};
[flutterPlatformViewsController
onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"create"
arguments:@{
@"id" : @2,
@"viewType" : @"MockNestedWrapperWebView"
}]
result:result];
XCTAssertNotNil(gMockPlatformView);
// Find touch inteceptor view
UIView* touchInteceptorView = gMockPlatformView;
while (touchInteceptorView != nil &&
![touchInteceptorView isKindOfClass:[FlutterTouchInterceptingView class]]) {
touchInteceptorView = touchInteceptorView.superview;
}
XCTAssertNotNil(touchInteceptorView);
XCTAssert(touchInteceptorView.gestureRecognizers.count == 2);
UIGestureRecognizer* delayingRecognizer = touchInteceptorView.gestureRecognizers[0];
UIGestureRecognizer* forwardingRecognizer = touchInteceptorView.gestureRecognizers[1];
XCTAssert([delayingRecognizer isKindOfClass:[FlutterDelayingGestureRecognizer class]]);
XCTAssert([forwardingRecognizer isKindOfClass:[ForwardingGestureRecognizer class]]);
[(FlutterTouchInterceptingView*)touchInteceptorView blockGesture];
XCTAssertEqual(touchInteceptorView.gestureRecognizers[0], delayingRecognizer);
XCTAssertEqual(touchInteceptorView.gestureRecognizers[1], forwardingRecognizer);
}
- (void)
testFlutterPlatformViewBlockGestureUnderEagerPolicyShouldNotRemoveAndAddBackDelayingRecognizerForNonWebView {
flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;