mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
[CP-beta][ios][pv] quick fix to enable and re-enable web view's gesture recognizer (#179974)
This pull request is created by [automatic cherry pick workflow](https://github.com/flutter/flutter/blob/main/docs/releases/Flutter-Cherrypick-Process.md#automatically-creates-a-cherry-pick-request) Please fill in the form below, and a flutter domain expert will evaluate this cherry pick request. ### Issue Link: What is the link to the issue this cherry-pick is addressing? https://github.com/flutter/flutter/issues/175099 https://github.com/flutter/flutter/issues/165787 ### Impact Description: What is the impact (ex. visual jank on Samsung phones, app crash, cannot ship an iOS app)? Does it impact development (ex. flutter doctor crashes when Android Studio is installed), or the shipping of production apps (the app crashes on launch). This information is for domain experts and release engineers to understand the consequences of saying yes or no to the cherry pick. WebViews (including ads) on iOS 26 are unclickable after being scrolled or after a Flutter menu is expanded. This is a serious regression that impacts revenue. This impacts all Flutter apps running on iOS 26 that use WebViews. ### Changelog Description: Explain this cherry pick: * In one line that is accessible to most Flutter developers. * That describes the state prior to the fix. * That includes which platforms are impacted. See [best practices](https://github.com/flutter/flutter/blob/main/docs/releases/Hotfix-Documentation-Best-Practices.md) for examples. < Replace with changelog description here > [flutter/165787, flutter/175099] When WebViews are scrolled on iOS 26, they become unclickable. ### Workaround: Is there a workaround for this issue? None ### Risk: What is the risk level of this cherry-pick? ### Test Coverage: Are you confident that your fix is well-tested by automated tests? ### Validation Steps: What are the steps to validate that this fix works? Use sample project from https://github.com/flutter/flutter/issues/165787#issuecomment-3429452722. Touch and scroll on an ad, then try to click the ad. The ad will be unclickable.
This commit is contained in:
parent
0bf2b707d9
commit
9828a8bf3f
@ -573,6 +573,39 @@ static BOOL _preparedOnce = NO;
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)searchAndFixWebView:(UIView*)view {
|
||||
if ([view isKindOfClass:[WKWebView class]]) {
|
||||
return [self searchAndFixWebViewGestureRecognzier:view];
|
||||
} else {
|
||||
for (UIView* subview in view.subviews) {
|
||||
[self searchAndFixWebView:subview];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)searchAndFixWebViewGestureRecognzier:(UIView*)view {
|
||||
for (UIGestureRecognizer* recognizer in view.gestureRecognizers) {
|
||||
// This is to fix a bug on iOS 26 where web view link is not tappable.
|
||||
// We reset the web view's WKTouchEventsGestureRecognizer in a bad state
|
||||
// by disabling and re-enabling it.
|
||||
// See: https://github.com/flutter/flutter/issues/175099.
|
||||
// See also: https://github.com/flutter/engine/pull/56804 for an explanation of the
|
||||
// bug on iOS 18.2, which is still valid on iOS 26.
|
||||
// Warning: This is just a quick fix that patches the bug. For example,
|
||||
// touches on a drawing website is still not completely blocked. A proper solution
|
||||
// should rely on overriding the hitTest behavior.
|
||||
// See: https://github.com/flutter/flutter/issues/179916.
|
||||
if (recognizer.enabled &&
|
||||
[NSStringFromClass([recognizer class]) hasSuffix:@"TouchEventsGestureRecognizer"]) {
|
||||
recognizer.enabled = NO;
|
||||
recognizer.enabled = YES;
|
||||
}
|
||||
}
|
||||
for (UIView* subview in view.subviews) {
|
||||
[self searchAndFixWebViewGestureRecognzier:subview];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)blockGesture {
|
||||
switch (_blockingPolicy) {
|
||||
case FlutterPlatformViewGestureRecognizersBlockingPolicyEager:
|
||||
@ -588,9 +621,15 @@ static BOOL _preparedOnce = NO;
|
||||
// FlutterPlatformViewGestureRecognizersBlockingPolicyEager, but we should try it if a similar
|
||||
// issue arises for the other policy.
|
||||
if (@available(iOS 26.0, *)) {
|
||||
// This workaround does not work on iOS 26.
|
||||
// TODO(hellohuanlin): find a solution for iOS 26,
|
||||
// https://github.com/flutter/flutter/issues/175099.
|
||||
// This performs a nested DFS, with the outer one searching for any web view, and the inner
|
||||
// one searching for a TouchEventsGestureRecognizer inside the web view. Once found, disable
|
||||
// and immediately reenable it to reset its state.
|
||||
// TODO(hellohuanlin): remove this flag after it is battle tested.
|
||||
NSNumber* isWorkaroundDisabled =
|
||||
[[NSBundle mainBundle] objectForInfoDictionaryKey:@"FLTDisableWebViewGestureReset"];
|
||||
if (!isWorkaroundDisabled.boolValue) {
|
||||
[self searchAndFixWebView:self.embeddedView];
|
||||
}
|
||||
} else if (@available(iOS 18.2, *)) {
|
||||
// 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
|
||||
|
||||
@ -47,6 +47,36 @@ const float kFloatCompareEpsilon = 0.001;
|
||||
|
||||
@end
|
||||
|
||||
// A mock recognizer without "TouchEventsGestureRecognizer" suffix in class name.
|
||||
// This is to verify a fix to a bug on iOS 26 where web view link is not tappable.
|
||||
// We reset the web view's WKTouchEventsGestureRecognizer in a bad state
|
||||
// by disabling and re-enabling it.
|
||||
// See: https://github.com/flutter/flutter/issues/175099.
|
||||
@interface MockGestureRecognizer : UIGestureRecognizer
|
||||
@property(nonatomic, strong) NSMutableArray<NSNumber*>* toggleHistory;
|
||||
@end
|
||||
|
||||
@implementation MockGestureRecognizer
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_toggleHistory = [NSMutableArray array];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
- (void)setEnabled:(BOOL)enabled {
|
||||
[super setEnabled:enabled];
|
||||
[self.toggleHistory addObject:@(enabled)];
|
||||
}
|
||||
@end
|
||||
|
||||
// A mock recognizer with "TouchEventsGestureRecognizer" suffix in class name.
|
||||
@interface MockTouchEventsGestureRecognizer : MockGestureRecognizer
|
||||
@end
|
||||
|
||||
@implementation MockTouchEventsGestureRecognizer
|
||||
@end
|
||||
|
||||
@interface FlutterPlatformViewsTestMockFlutterPlatformView : NSObject <FlutterPlatformView>
|
||||
@property(nonatomic, strong) UIView* view;
|
||||
@property(nonatomic, assign) BOOL viewCreated;
|
||||
@ -3403,6 +3433,429 @@ fml::RefPtr<fml::TaskRunner> GetDefaultTaskRunner() {
|
||||
XCTAssertEqual(touchInteceptorView.gestureRecognizers[1], forwardingRecognizer);
|
||||
}
|
||||
|
||||
- (void)
|
||||
testFlutterPlatformViewBlockGestureUnderEagerPolicyShouldDisableAndReEnableTouchEventsGestureRecognizerForSimpleWebView {
|
||||
if (@available(iOS 26.0, *)) {
|
||||
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=*/flutter::IOSRenderingAPI::kMetal,
|
||||
/*platform_views_controller=*/flutterPlatformViewsController,
|
||||
/*task_runners=*/runners,
|
||||
/*worker_task_runner=*/nil,
|
||||
/*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
|
||||
|
||||
FlutterPlatformViewsTestMockWebViewFactory* factory =
|
||||
[[FlutterPlatformViewsTestMockWebViewFactory alloc] init];
|
||||
[flutterPlatformViewsController
|
||||
registerViewFactory:factory
|
||||
withId:@"MockWebView"
|
||||
gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
|
||||
FlutterResult result = ^(id result) {
|
||||
};
|
||||
[flutterPlatformViewsController
|
||||
onMethodCall:[FlutterMethodCall
|
||||
methodCallWithMethodName:@"create"
|
||||
arguments:@{@"id" : @2, @"viewType" : @"MockWebView"}]
|
||||
result:result];
|
||||
|
||||
XCTAssertNotNil(gMockPlatformView);
|
||||
|
||||
// Find touch inteceptor view
|
||||
UIView* touchInteceptorView = gMockPlatformView;
|
||||
while (touchInteceptorView != nil &&
|
||||
![touchInteceptorView isKindOfClass:[FlutterTouchInterceptingView class]]) {
|
||||
touchInteceptorView = touchInteceptorView.superview;
|
||||
}
|
||||
XCTAssertNotNil(touchInteceptorView);
|
||||
|
||||
/*
|
||||
Simple Web View at root, with [*] indicating views containing
|
||||
MockTouchEventsGestureRecognizer.
|
||||
|
||||
Root (Web View) [*]
|
||||
├── Child 1
|
||||
└── Child 2
|
||||
├── Child 2.1
|
||||
└── Child 2.2 [*]
|
||||
*/
|
||||
|
||||
UIView* root = gMockPlatformView;
|
||||
root.gestureRecognizers = nil;
|
||||
for (UIView* subview in root.subviews) {
|
||||
[subview removeFromSuperview];
|
||||
}
|
||||
|
||||
MockGestureRecognizer* normalRecognizer0 = [[MockGestureRecognizer alloc] init];
|
||||
[root addGestureRecognizer:normalRecognizer0];
|
||||
|
||||
UIView* child1 = [[UIView alloc] init];
|
||||
[root addSubview:child1];
|
||||
MockGestureRecognizer* normalRecognizer1 = [[MockGestureRecognizer alloc] init];
|
||||
[child1 addGestureRecognizer:normalRecognizer1];
|
||||
|
||||
UIView* child2 = [[UIView alloc] init];
|
||||
[root addSubview:child2];
|
||||
MockGestureRecognizer* normalRecognizer2 = [[MockGestureRecognizer alloc] init];
|
||||
[child2 addGestureRecognizer:normalRecognizer2];
|
||||
|
||||
UIView* child2_1 = [[UIView alloc] init];
|
||||
[child2 addSubview:child2_1];
|
||||
MockGestureRecognizer* normalRecognizer2_1 = [[MockGestureRecognizer alloc] init];
|
||||
[child2_1 addGestureRecognizer:normalRecognizer2_1];
|
||||
|
||||
UIView* child2_2 = [[UIView alloc] init];
|
||||
[child2 addSubview:child2_2];
|
||||
MockGestureRecognizer* normalRecognizer2_2 = [[MockGestureRecognizer alloc] init];
|
||||
[child2_2 addGestureRecognizer:normalRecognizer2_2];
|
||||
|
||||
// Add the target recognizer at root & child2_2.
|
||||
MockTouchEventsGestureRecognizer* targetRecognizer0 =
|
||||
[[MockTouchEventsGestureRecognizer alloc] init];
|
||||
[root addGestureRecognizer:targetRecognizer0];
|
||||
|
||||
MockTouchEventsGestureRecognizer* targetRecognizer2_2 =
|
||||
[[MockTouchEventsGestureRecognizer alloc] init];
|
||||
[child2_2 addGestureRecognizer:targetRecognizer2_2];
|
||||
|
||||
[(FlutterTouchInterceptingView*)touchInteceptorView blockGesture];
|
||||
|
||||
NSArray* normalRecognizers = @[
|
||||
normalRecognizer0, normalRecognizer1, normalRecognizer2, normalRecognizer2_1,
|
||||
normalRecognizer2_2
|
||||
];
|
||||
|
||||
NSArray* targetRecognizers = @[ targetRecognizer0, targetRecognizer2_2 ];
|
||||
|
||||
NSArray* expectedEmptyHistory = @[];
|
||||
NSArray* expectedToggledHistory = @[ @NO, @YES ];
|
||||
|
||||
for (MockGestureRecognizer* recognizer in normalRecognizers) {
|
||||
XCTAssertEqualObjects(recognizer.toggleHistory, expectedEmptyHistory);
|
||||
}
|
||||
for (MockGestureRecognizer* recognizer in targetRecognizers) {
|
||||
XCTAssertEqualObjects(recognizer.toggleHistory, expectedToggledHistory);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)
|
||||
testFlutterPlatformViewBlockGestureUnderEagerPolicyShouldDisableAndReEnableTouchEventsGestureRecognizerForMultipleWebViewInDifferentBranches {
|
||||
if (@available(iOS 26.0, *)) {
|
||||
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=*/flutter::IOSRenderingAPI::kMetal,
|
||||
/*platform_views_controller=*/flutterPlatformViewsController,
|
||||
/*task_runners=*/runners,
|
||||
/*worker_task_runner=*/nil,
|
||||
/*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
|
||||
|
||||
FlutterPlatformViewsTestMockWrapperWebViewFactory* factory =
|
||||
[[FlutterPlatformViewsTestMockWrapperWebViewFactory alloc] init];
|
||||
[flutterPlatformViewsController
|
||||
registerViewFactory:factory
|
||||
withId:@"MockWrapperWebView"
|
||||
gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
|
||||
FlutterResult result = ^(id result) {
|
||||
};
|
||||
[flutterPlatformViewsController
|
||||
onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"create"
|
||||
arguments:@{
|
||||
@"id" : @2,
|
||||
@"viewType" : @"MockWrapperWebView"
|
||||
}]
|
||||
result:result];
|
||||
|
||||
XCTAssertNotNil(gMockPlatformView);
|
||||
|
||||
// Find touch inteceptor view
|
||||
UIView* touchInteceptorView = gMockPlatformView;
|
||||
while (touchInteceptorView != nil &&
|
||||
![touchInteceptorView isKindOfClass:[FlutterTouchInterceptingView class]]) {
|
||||
touchInteceptorView = touchInteceptorView.superview;
|
||||
}
|
||||
XCTAssertNotNil(touchInteceptorView);
|
||||
|
||||
/*
|
||||
Platform View with Multiple Web Views in different branches, with [*] indicating views
|
||||
containing MockTouchEventsGestureRecognizer.
|
||||
|
||||
Root (Platform View)
|
||||
├── Child 1
|
||||
├── Child 2 (Web View)
|
||||
| ├── Child 2.1
|
||||
| └── Child 2.2 [*]
|
||||
└── Child 3
|
||||
└── Child 3.1 (Web View)
|
||||
├── Child 3.1.1
|
||||
└── Child 3.1.2 [*]
|
||||
*/
|
||||
|
||||
UIView* root = gMockPlatformView;
|
||||
for (UIView* subview in root.subviews) {
|
||||
[subview removeFromSuperview];
|
||||
}
|
||||
|
||||
MockGestureRecognizer* normalRecognizer0 = [[MockGestureRecognizer alloc] init];
|
||||
[root addGestureRecognizer:normalRecognizer0];
|
||||
|
||||
UIView* child1 = [[UIView alloc] init];
|
||||
[root addSubview:child1];
|
||||
MockGestureRecognizer* normalRecognizer1 = [[MockGestureRecognizer alloc] init];
|
||||
[child1 addGestureRecognizer:normalRecognizer1];
|
||||
|
||||
UIView* child2 = [[WKWebView alloc] init];
|
||||
child2.gestureRecognizers = nil;
|
||||
for (UIView* subview in child2.subviews) {
|
||||
[subview removeFromSuperview];
|
||||
}
|
||||
[root addSubview:child2];
|
||||
MockGestureRecognizer* normalRecognizer2 = [[MockGestureRecognizer alloc] init];
|
||||
[child2 addGestureRecognizer:normalRecognizer2];
|
||||
|
||||
UIView* child2_1 = [[UIView alloc] init];
|
||||
[child2 addSubview:child2_1];
|
||||
MockGestureRecognizer* normalRecognizer2_1 = [[MockGestureRecognizer alloc] init];
|
||||
[child2_1 addGestureRecognizer:normalRecognizer2_1];
|
||||
|
||||
UIView* child2_2 = [[UIView alloc] init];
|
||||
[child2 addSubview:child2_2];
|
||||
MockGestureRecognizer* normalRecognizer2_2 = [[MockGestureRecognizer alloc] init];
|
||||
[child2_2 addGestureRecognizer:normalRecognizer2_2];
|
||||
|
||||
UIView* child3 = [[UIView alloc] init];
|
||||
[root addSubview:child3];
|
||||
MockGestureRecognizer* normalRecognizer3 = [[MockGestureRecognizer alloc] init];
|
||||
[child3 addGestureRecognizer:normalRecognizer3];
|
||||
|
||||
UIView* child3_1 = [[WKWebView alloc] init];
|
||||
child3_1.gestureRecognizers = nil;
|
||||
for (UIView* subview in child3_1.subviews) {
|
||||
[subview removeFromSuperview];
|
||||
}
|
||||
[child3 addSubview:child3_1];
|
||||
MockGestureRecognizer* normalRecognizer3_1 = [[MockGestureRecognizer alloc] init];
|
||||
[child3_1 addGestureRecognizer:normalRecognizer3_1];
|
||||
|
||||
UIView* child3_1_1 = [[UIView alloc] init];
|
||||
[child3_1 addSubview:child3_1_1];
|
||||
MockGestureRecognizer* normalRecognizer3_1_1 = [[MockGestureRecognizer alloc] init];
|
||||
[child3_1_1 addGestureRecognizer:normalRecognizer3_1_1];
|
||||
|
||||
UIView* child3_1_2 = [[UIView alloc] init];
|
||||
[child3_1 addSubview:child3_1_2];
|
||||
MockGestureRecognizer* normalRecognizer3_1_2 = [[MockGestureRecognizer alloc] init];
|
||||
[child3_1_2 addGestureRecognizer:normalRecognizer3_1_2];
|
||||
|
||||
// Add the target recognizer at child2_2 & child3_1_2
|
||||
|
||||
MockTouchEventsGestureRecognizer* targetRecognizer2_2 =
|
||||
[[MockTouchEventsGestureRecognizer alloc] init];
|
||||
[child2_2 addGestureRecognizer:targetRecognizer2_2];
|
||||
|
||||
MockTouchEventsGestureRecognizer* targetRecognizer3_1_2 =
|
||||
[[MockTouchEventsGestureRecognizer alloc] init];
|
||||
[child3_1_2 addGestureRecognizer:targetRecognizer3_1_2];
|
||||
|
||||
[(FlutterTouchInterceptingView*)touchInteceptorView blockGesture];
|
||||
|
||||
NSArray* normalRecognizers = @[
|
||||
normalRecognizer0, normalRecognizer1, normalRecognizer2, normalRecognizer2_1,
|
||||
normalRecognizer2_2, normalRecognizer3, normalRecognizer3_1, normalRecognizer3_1_1,
|
||||
normalRecognizer3_1_2
|
||||
];
|
||||
NSArray* targetRecognizers = @[ targetRecognizer2_2, targetRecognizer3_1_2 ];
|
||||
|
||||
NSArray* expectedEmptyHistory = @[];
|
||||
NSArray* expectedToggledHistory = @[ @NO, @YES ];
|
||||
|
||||
for (MockGestureRecognizer* recognizer in normalRecognizers) {
|
||||
XCTAssertEqualObjects(recognizer.toggleHistory, expectedEmptyHistory);
|
||||
}
|
||||
|
||||
for (MockGestureRecognizer* recognizer in targetRecognizers) {
|
||||
XCTAssertEqualObjects(recognizer.toggleHistory, expectedToggledHistory);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)
|
||||
testFlutterPlatformViewBlockGestureUnderEagerPolicyShouldDisableAndReEnableTouchEventsGestureRecognizerForNestedMultipleWebView {
|
||||
if (@available(iOS 26.0, *)) {
|
||||
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=*/flutter::IOSRenderingAPI::kMetal,
|
||||
/*platform_views_controller=*/flutterPlatformViewsController,
|
||||
/*task_runners=*/runners,
|
||||
/*worker_task_runner=*/nil,
|
||||
/*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());
|
||||
|
||||
FlutterPlatformViewsTestMockWebViewFactory* factory =
|
||||
[[FlutterPlatformViewsTestMockWebViewFactory alloc] init];
|
||||
[flutterPlatformViewsController
|
||||
registerViewFactory:factory
|
||||
withId:@"MockWebView"
|
||||
gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
|
||||
FlutterResult result = ^(id result) {
|
||||
};
|
||||
[flutterPlatformViewsController
|
||||
onMethodCall:[FlutterMethodCall
|
||||
methodCallWithMethodName:@"create"
|
||||
arguments:@{@"id" : @2, @"viewType" : @"MockWebView"}]
|
||||
result:result];
|
||||
|
||||
XCTAssertNotNil(gMockPlatformView);
|
||||
|
||||
// Find touch inteceptor view
|
||||
UIView* touchInteceptorView = gMockPlatformView;
|
||||
while (touchInteceptorView != nil &&
|
||||
![touchInteceptorView isKindOfClass:[FlutterTouchInterceptingView class]]) {
|
||||
touchInteceptorView = touchInteceptorView.superview;
|
||||
}
|
||||
XCTAssertNotNil(touchInteceptorView);
|
||||
|
||||
/*
|
||||
Platform View with nested web views, with [*] indicating views containing
|
||||
MockTouchEventsGestureRecognizer.
|
||||
|
||||
Root (Web View)
|
||||
├── Child 1
|
||||
├── Child 2
|
||||
| ├── Child 2.1
|
||||
| └── Child 2.2 [*]
|
||||
└── Child 3
|
||||
└── Child 3.1 (Another Web View)
|
||||
└── Child 3.1.1
|
||||
└── Child 3.1.2
|
||||
├── Child 3.1.2.1
|
||||
└── Child 3.1.2.2 [*]
|
||||
*/
|
||||
|
||||
UIView* root = gMockPlatformView;
|
||||
root.gestureRecognizers = nil;
|
||||
for (UIView* subview in root.subviews) {
|
||||
[subview removeFromSuperview];
|
||||
}
|
||||
|
||||
MockGestureRecognizer* normalRecognizer0 = [[MockGestureRecognizer alloc] init];
|
||||
[root addGestureRecognizer:normalRecognizer0];
|
||||
|
||||
UIView* child1 = [[UIView alloc] init];
|
||||
[root addSubview:child1];
|
||||
MockGestureRecognizer* normalRecognizer1 = [[MockGestureRecognizer alloc] init];
|
||||
[child1 addGestureRecognizer:normalRecognizer1];
|
||||
|
||||
UIView* child2 = [[UIView alloc] init];
|
||||
[root addSubview:child2];
|
||||
MockGestureRecognizer* normalRecognizer2 = [[MockGestureRecognizer alloc] init];
|
||||
[child2 addGestureRecognizer:normalRecognizer2];
|
||||
|
||||
UIView* child2_1 = [[UIView alloc] init];
|
||||
[child2 addSubview:child2_1];
|
||||
MockGestureRecognizer* normalRecognizer2_1 = [[MockGestureRecognizer alloc] init];
|
||||
[child2_1 addGestureRecognizer:normalRecognizer2_1];
|
||||
|
||||
UIView* child2_2 = [[UIView alloc] init];
|
||||
[child2 addSubview:child2_2];
|
||||
MockGestureRecognizer* normalRecognizer2_2 = [[MockGestureRecognizer alloc] init];
|
||||
[child2_2 addGestureRecognizer:normalRecognizer2_2];
|
||||
|
||||
UIView* child3 = [[UIView alloc] init];
|
||||
[root addSubview:child3];
|
||||
MockGestureRecognizer* normalRecognizer3 = [[MockGestureRecognizer alloc] init];
|
||||
[child3 addGestureRecognizer:normalRecognizer3];
|
||||
|
||||
UIView* child3_1 = [[WKWebView alloc] init];
|
||||
child3_1.gestureRecognizers = nil;
|
||||
for (UIView* subview in child3_1.subviews) {
|
||||
[subview removeFromSuperview];
|
||||
}
|
||||
[child3 addSubview:child3_1];
|
||||
MockGestureRecognizer* normalRecognizer3_1 = [[MockGestureRecognizer alloc] init];
|
||||
[child3_1 addGestureRecognizer:normalRecognizer3_1];
|
||||
|
||||
UIView* child3_1_1 = [[UIView alloc] init];
|
||||
[child3_1 addSubview:child3_1_1];
|
||||
MockGestureRecognizer* normalRecognizer3_1_1 = [[MockGestureRecognizer alloc] init];
|
||||
[child3_1_1 addGestureRecognizer:normalRecognizer3_1_1];
|
||||
|
||||
UIView* child3_1_2 = [[UIView alloc] init];
|
||||
[child3_1 addSubview:child3_1_2];
|
||||
MockGestureRecognizer* normalRecognizer3_1_2 = [[MockGestureRecognizer alloc] init];
|
||||
[child3_1_2 addGestureRecognizer:normalRecognizer3_1_2];
|
||||
|
||||
UIView* child3_1_2_1 = [[UIView alloc] init];
|
||||
[child3_1_2 addSubview:child3_1_2_1];
|
||||
MockGestureRecognizer* normalRecognizer3_1_2_1 = [[MockGestureRecognizer alloc] init];
|
||||
[child3_1_2_1 addGestureRecognizer:normalRecognizer3_1_2_1];
|
||||
|
||||
UIView* child3_1_2_2 = [[UIView alloc] init];
|
||||
[child3_1_2 addSubview:child3_1_2_2];
|
||||
MockGestureRecognizer* normalRecognizer3_1_2_2 = [[MockGestureRecognizer alloc] init];
|
||||
[child3_1_2_2 addGestureRecognizer:normalRecognizer3_1_2_2];
|
||||
|
||||
// Add the target recognizer at child2_2 & child3_1_2_2
|
||||
|
||||
MockTouchEventsGestureRecognizer* targetRecognizer2_2 =
|
||||
[[MockTouchEventsGestureRecognizer alloc] init];
|
||||
[child2_2 addGestureRecognizer:targetRecognizer2_2];
|
||||
|
||||
MockTouchEventsGestureRecognizer* targetRecognizer3_1_2_2 =
|
||||
[[MockTouchEventsGestureRecognizer alloc] init];
|
||||
[child3_1_2_2 addGestureRecognizer:targetRecognizer3_1_2_2];
|
||||
|
||||
[(FlutterTouchInterceptingView*)touchInteceptorView blockGesture];
|
||||
|
||||
NSArray* normalRecognizers = @[
|
||||
normalRecognizer0, normalRecognizer1, normalRecognizer2, normalRecognizer2_1,
|
||||
normalRecognizer2_2, normalRecognizer3, normalRecognizer3_1, normalRecognizer3_1_1,
|
||||
normalRecognizer3_1_2, normalRecognizer3_1_2_1, normalRecognizer3_1_2_2
|
||||
];
|
||||
|
||||
NSArray* targetRecognizers = @[ targetRecognizer2_2, targetRecognizer3_1_2_2 ];
|
||||
|
||||
NSArray* expectedEmptyHistory = @[];
|
||||
NSArray* expectedToggledHistory = @[ @NO, @YES ];
|
||||
|
||||
for (MockGestureRecognizer* recognizer in normalRecognizers) {
|
||||
XCTAssertEqualObjects(recognizer.toggleHistory, expectedEmptyHistory);
|
||||
}
|
||||
|
||||
for (MockGestureRecognizer* recognizer in targetRecognizers) {
|
||||
XCTAssertEqualObjects(recognizer.toggleHistory, expectedToggledHistory);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testFlutterPlatformViewControllerSubmitFrameWithoutFlutterViewNotCrashing {
|
||||
flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user