Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm
Original file line number Diff line number Diff line change
Expand Up @@ -566,12 +566,15 @@ - (void)releaseGesture {
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;
}
}
Expand All @@ -593,7 +596,13 @@ - (void)blockGesture {
// 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]) {
Copy link
Contributor Author

@hellohuanlin hellohuanlin Dec 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Passing 1 means searching for itself and its immediate children. AdMob is at depth 7. I think we should start small here to be conservative, and increase only when necessary.

[self removeGestureRecognizer:self.delayingRecognizer];
[self addGestureRecognizer:self.delayingRecognizer];
}
Expand Down
115 changes: 115 additions & 0 deletions shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ - (void)checkViewCreatedOnce {
self.viewCreated = YES;
}

- (void)dealloc {
gMockPlatformView = nil;
}
@end

@interface FlutterPlatformViewsTestMockFlutterPlatformFactory
Expand Down Expand Up @@ -115,6 +118,10 @@ - (void)checkViewCreatedOnce {
}
self.viewCreated = YES;
}

- (void)dealloc {
gMockPlatformView = nil;
}
@end

@interface FlutterPlatformViewsTestMockWebViewFactory : NSObject <FlutterPlatformViewFactory>
Expand Down Expand Up @@ -167,6 +174,10 @@ - (void)checkViewCreatedOnce {
}
self.viewCreated = YES;
}

- (void)dealloc {
gMockPlatformView = nil;
}
@end

@interface FlutterPlatformViewsTestMockWrapperWebViewFactory : NSObject <FlutterPlatformViewFactory>
Expand All @@ -180,6 +191,49 @@ @implementation FlutterPlatformViewsTestMockWrapperWebViewFactory
}
@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 {
Expand Down Expand Up @@ -3258,6 +3312,67 @@ - (void)testFlutterPlatformViewTouchesEndedOrTouchesCancelledEventDoesNotFailThe
}
}

- (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;
Expand Down
Loading