[Pointer Events] touch-action set to pan-x or pan-y alone should disable scrolling...
authorgraouts@webkit.org <graouts@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 21 Sep 2019 16:08:34 +0000 (16:08 +0000)
committergraouts@webkit.org <graouts@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 21 Sep 2019 16:08:34 +0000 (16:08 +0000)
https://bugs.webkit.org/show_bug.cgi?id=202053
<rdar://problem/54542190>

Reviewed by Tim Horton.

Source/WebKit:

Although the Pointer Events specification does not specify this clearly (see https://github.com/w3c/pointerevents/issues/303), setting "touch-action" to a value
that only allows scrolling a specific direction ("pan-x" or "pan-y") should disable scrolling in the specified direction if the panning gesture initially is directed
in the opposite direction. In practice, this means that setting "touch-action: pan-y" on an element should disable scrolling if the user initially pans horizontally,
even if later on in the gesture the user pans vertically. This allows for sites that want to offer a programmatic horizontal scroller to disable vertical scrolling
if the user pans horizontally.

In order to support this, we add four UISwipeGestureRecognizers, one for each direction, and we selectively allows touches to be recognizer for them based on the
"touch-action" value specified at the initial touch location for a given gesture. In the case of "touch-action: pan-y" we only allow the left and right swipe recognizers
to be enabled, and in the case of "touch-action: pan-x" we only allow the up and down swipe recognizers to be enabled. If any of those gesture recognizers is recognized,
scrolling will be disabled for the duration of this gesture. If a UIScrollView panning gesture recognizer is recognized prior to a swipe, they won't have a chance to be
recognized.

* UIProcess/ios/WKContentViewInteraction.h:
* UIProcess/ios/WKContentViewInteraction.mm:
(-[WKContentView setupInteraction]):
(-[WKContentView cleanupInteraction]):
(-[WKContentView _removeDefaultGestureRecognizers]):
(-[WKContentView _addDefaultGestureRecognizers]):
(-[WKContentView gestureRecognizer:shouldReceiveTouch:]):

LayoutTests:

Add new tests checking that setting "touch-action: pan-y" on an element and initiating a horizontal panning gesture will disallow scrolling vertically
if a vertical scrolling gesture follows. We test both the case where scrolling would apply to the whole page and the case where scrolling would apply
to an "overflow: scroll" element.

* pointerevents/ios/touch-action-pan-y-horizontal-gesture-prevents-vertical-scrolling-expected.txt: Added.
* pointerevents/ios/touch-action-pan-y-horizontal-gesture-prevents-vertical-scrolling.html: Added.
* pointerevents/ios/touch-action-pan-y-in-overflow-scroll-horizontal-gesture-prevents-vertical-scrolling-expected.txt: Added.
* pointerevents/ios/touch-action-pan-y-in-overflow-scroll-horizontal-gesture-prevents-vertical-scrolling.html: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@250183 268f45cc-cd09-0410-ab3c-d52691b4dbfc

LayoutTests/ChangeLog
LayoutTests/pointerevents/ios/touch-action-pan-y-horizontal-gesture-prevents-vertical-scrolling-expected.txt [new file with mode: 0644]
LayoutTests/pointerevents/ios/touch-action-pan-y-horizontal-gesture-prevents-vertical-scrolling.html [new file with mode: 0644]
LayoutTests/pointerevents/ios/touch-action-pan-y-in-overflow-scroll-horizontal-gesture-prevents-vertical-scrolling-expected.txt [new file with mode: 0644]
LayoutTests/pointerevents/ios/touch-action-pan-y-in-overflow-scroll-horizontal-gesture-prevents-vertical-scrolling.html [new file with mode: 0644]
Source/WebKit/ChangeLog
Source/WebKit/UIProcess/ios/WKContentViewInteraction.h
Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm

index bb69410..325f6c5 100644 (file)
@@ -1,3 +1,20 @@
+2019-09-21  Antoine Quint  <graouts@apple.com>
+
+        [Pointer Events] touch-action set to pan-x or pan-y alone should disable scrolling altogether if the intial gesture is in the disallowed direction
+        https://bugs.webkit.org/show_bug.cgi?id=202053
+        <rdar://problem/54542190>
+
+        Reviewed by Tim Horton.
+
+        Add new tests checking that setting "touch-action: pan-y" on an element and initiating a horizontal panning gesture will disallow scrolling vertically
+        if a vertical scrolling gesture follows. We test both the case where scrolling would apply to the whole page and the case where scrolling would apply
+        to an "overflow: scroll" element.
+
+        * pointerevents/ios/touch-action-pan-y-horizontal-gesture-prevents-vertical-scrolling-expected.txt: Added.
+        * pointerevents/ios/touch-action-pan-y-horizontal-gesture-prevents-vertical-scrolling.html: Added.
+        * pointerevents/ios/touch-action-pan-y-in-overflow-scroll-horizontal-gesture-prevents-vertical-scrolling-expected.txt: Added.
+        * pointerevents/ios/touch-action-pan-y-in-overflow-scroll-horizontal-gesture-prevents-vertical-scrolling.html: Added.
+
 2019-09-20  Antoine Quint  <graouts@apple.com>
 
         releasePointerCapture() not working for implicit capture; can't opt-in to pointerenter/leave for touches
diff --git a/LayoutTests/pointerevents/ios/touch-action-pan-y-horizontal-gesture-prevents-vertical-scrolling-expected.txt b/LayoutTests/pointerevents/ios/touch-action-pan-y-horizontal-gesture-prevents-vertical-scrolling-expected.txt
new file mode 100644 (file)
index 0000000..ab322ce
--- /dev/null
@@ -0,0 +1,3 @@
+
+PASS Testing that panning in the x-axis first on an element with touch-action: pan-y prevents future vertical scrolling. 
+
diff --git a/LayoutTests/pointerevents/ios/touch-action-pan-y-horizontal-gesture-prevents-vertical-scrolling.html b/LayoutTests/pointerevents/ios/touch-action-pan-y-horizontal-gesture-prevents-vertical-scrolling.html
new file mode 100644 (file)
index 0000000..c7cb6be
--- /dev/null
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset=utf-8>
+<meta name="viewport" content="width=device-width, initial-scale=1">
+</head>
+<body>
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<script src="../utils.js"></script>
+<script src="../../resources/basic-gestures.js"></script>
+<script>
+
+'use strict';
+
+target_test({ width: "200px", height: "200px" }, (target, test) => {
+    document.body.style.height = "2000px";
+
+    target.style.touchAction = "pan-y";
+    target.style.backgroundColor = "blue";
+
+    const eventTracker = new EventTracker(target, ["pointerdown", "pointerup", "pointercancel"]);
+
+    touchAndDragFromPointToPoint(150, 100, 50, 100)
+    .then(() => touchAndDragFromPointToPoint(50, 100, 50, 50))
+    .then(() => liftUpAtPoint(50, 50))
+    .then(() => {
+        assert_equals(window.pageYOffset, 0, "The page was not scrolled vertically.");
+        eventTracker.assertMatchesEvents([
+            { type: "pointerdown" },
+            { type: "pointerup" }
+        ]);
+        test.done();
+    });
+}, "Testing that panning in the x-axis first on an element with touch-action: pan-y prevents future vertical scrolling.");
+
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/LayoutTests/pointerevents/ios/touch-action-pan-y-in-overflow-scroll-horizontal-gesture-prevents-vertical-scrolling-expected.txt b/LayoutTests/pointerevents/ios/touch-action-pan-y-in-overflow-scroll-horizontal-gesture-prevents-vertical-scrolling-expected.txt
new file mode 100644 (file)
index 0000000..d47b077
--- /dev/null
@@ -0,0 +1,3 @@
+
+PASS Testing that panning in the x-axis first on an element with touch-action: pan-y prevents future vertical scrolling within an 'overflow: scroll' element. 
+
diff --git a/LayoutTests/pointerevents/ios/touch-action-pan-y-in-overflow-scroll-horizontal-gesture-prevents-vertical-scrolling.html b/LayoutTests/pointerevents/ios/touch-action-pan-y-in-overflow-scroll-horizontal-gesture-prevents-vertical-scrolling.html
new file mode 100644 (file)
index 0000000..8acfa1e
--- /dev/null
@@ -0,0 +1,55 @@
+<!DOCTYPE html> <!-- webkit-test-runner [ internal:AsyncOverflowScrollingEnabled=true internal:LegacyOverflowScrollingTouchEnabled=false ] -->
+<html>
+<head>
+<meta charset=utf-8>
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<style>
+
+#scrollable {
+    position: absolute;
+    left: 0;
+    top: 0;
+    width: 200px;
+    height: 200px;
+
+    overflow: scroll;
+    background-color: green;
+}
+
+</style>
+</head>
+<body>
+<div id="scrollable"></div>
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<script src="../utils.js"></script>
+<script src="../../resources/basic-gestures.js"></script>
+<script>
+
+'use strict';
+
+target_test({ width: "200px", height: "2000px" }, (target, test) => {
+    const scrollable = document.querySelector("#scrollable");
+    scrollable.appendChild(target);
+
+    target.style.touchAction = "pan-y";
+    target.style.backgroundColor = "blue";
+
+    const eventTracker = new EventTracker(target, ["pointerdown", "pointerup", "pointercancel"]);
+
+    touchAndDragFromPointToPoint(150, 100, 50, 100)
+    .then(() => touchAndDragFromPointToPoint(50, 100, 50, 50))
+    .then(() => liftUpAtPoint(50, 50))
+    .then(() => {
+        assert_equals(scrollable.scrollTop, 0, "The page was not scrolled vertically.");
+        eventTracker.assertMatchesEvents([
+            { type: "pointerdown" },
+            { type: "pointerup" }
+        ]);
+        test.done();
+    });
+}, "Testing that panning in the x-axis first on an element with touch-action: pan-y prevents future vertical scrolling within an 'overflow: scroll' element.");
+
+</script>
+</body>
+</html>
\ No newline at end of file
index 14cc840..316a1b7 100644 (file)
@@ -1,3 +1,31 @@
+2019-09-21  Antoine Quint  <graouts@apple.com>
+
+        [Pointer Events] touch-action set to pan-x or pan-y alone should disable scrolling altogether if the intial gesture is in the disallowed direction
+        https://bugs.webkit.org/show_bug.cgi?id=202053
+        <rdar://problem/54542190>
+
+        Reviewed by Tim Horton.
+
+        Although the Pointer Events specification does not specify this clearly (see https://github.com/w3c/pointerevents/issues/303), setting "touch-action" to a value
+        that only allows scrolling a specific direction ("pan-x" or "pan-y") should disable scrolling in the specified direction if the panning gesture initially is directed
+        in the opposite direction. In practice, this means that setting "touch-action: pan-y" on an element should disable scrolling if the user initially pans horizontally,
+        even if later on in the gesture the user pans vertically. This allows for sites that want to offer a programmatic horizontal scroller to disable vertical scrolling
+        if the user pans horizontally.
+
+        In order to support this, we add four UISwipeGestureRecognizers, one for each direction, and we selectively allows touches to be recognizer for them based on the
+        "touch-action" value specified at the initial touch location for a given gesture. In the case of "touch-action: pan-y" we only allow the left and right swipe recognizers
+        to be enabled, and in the case of "touch-action: pan-x" we only allow the up and down swipe recognizers to be enabled. If any of those gesture recognizers is recognized,
+        scrolling will be disabled for the duration of this gesture. If a UIScrollView panning gesture recognizer is recognized prior to a swipe, they won't have a chance to be
+        recognized.
+
+        * UIProcess/ios/WKContentViewInteraction.h:
+        * UIProcess/ios/WKContentViewInteraction.mm:
+        (-[WKContentView setupInteraction]):
+        (-[WKContentView cleanupInteraction]):
+        (-[WKContentView _removeDefaultGestureRecognizers]):
+        (-[WKContentView _addDefaultGestureRecognizers]):
+        (-[WKContentView gestureRecognizer:shouldReceiveTouch:]):
+
 2019-09-19  Andy Estes  <aestes@apple.com>
 
         [Apple Pay] Clean up handling of summary items and payment method updates
index 8070d3d..f0b8ad8 100644 (file)
@@ -225,6 +225,10 @@ struct WKAutoCorrectionData {
 
 #if ENABLE(POINTER_EVENTS)
     RetainPtr<WKTouchActionGestureRecognizer> _touchActionGestureRecognizer;
+    RetainPtr<UISwipeGestureRecognizer> _touchActionLeftSwipeGestureRecognizer;
+    RetainPtr<UISwipeGestureRecognizer> _touchActionRightSwipeGestureRecognizer;
+    RetainPtr<UISwipeGestureRecognizer> _touchActionUpSwipeGestureRecognizer;
+    RetainPtr<UISwipeGestureRecognizer> _touchActionDownSwipeGestureRecognizer;
 #endif
 
 #if PLATFORM(MACCATALYST)
index b6ba18b..7f6fd02 100644 (file)
@@ -716,6 +716,28 @@ static inline bool hasFocusedElement(WebKit::FocusedElementInformation focusedEl
 
     [self.layer addObserver:self forKeyPath:@"transform" options:NSKeyValueObservingOptionInitial context:nil];
 
+#if ENABLE(POINTER_EVENTS)
+    _touchActionLeftSwipeGestureRecognizer = adoptNS([[UISwipeGestureRecognizer alloc] initWithTarget:nil action:nil]);
+    [_touchActionLeftSwipeGestureRecognizer setDirection:UISwipeGestureRecognizerDirectionLeft];
+    [_touchActionLeftSwipeGestureRecognizer setDelegate:self];
+    [self addGestureRecognizer:_touchActionLeftSwipeGestureRecognizer.get()];
+
+    _touchActionRightSwipeGestureRecognizer = adoptNS([[UISwipeGestureRecognizer alloc] initWithTarget:nil action:nil]);
+    [_touchActionRightSwipeGestureRecognizer setDirection:UISwipeGestureRecognizerDirectionRight];
+    [_touchActionRightSwipeGestureRecognizer setDelegate:self];
+    [self addGestureRecognizer:_touchActionRightSwipeGestureRecognizer.get()];
+
+    _touchActionUpSwipeGestureRecognizer = adoptNS([[UISwipeGestureRecognizer alloc] initWithTarget:nil action:nil]);
+    [_touchActionUpSwipeGestureRecognizer setDirection:UISwipeGestureRecognizerDirectionUp];
+    [_touchActionUpSwipeGestureRecognizer setDelegate:self];
+    [self addGestureRecognizer:_touchActionUpSwipeGestureRecognizer.get()];
+
+    _touchActionDownSwipeGestureRecognizer = adoptNS([[UISwipeGestureRecognizer alloc] initWithTarget:nil action:nil]);
+    [_touchActionDownSwipeGestureRecognizer setDirection:UISwipeGestureRecognizerDirectionDown];
+    [_touchActionDownSwipeGestureRecognizer setDelegate:self];
+    [self addGestureRecognizer:_touchActionDownSwipeGestureRecognizer.get()];
+#endif
+
     _touchEventGestureRecognizer = adoptNS([[UIWebTouchEventsGestureRecognizer alloc] initWithTarget:self action:@selector(_webTouchEventsRecognized:) touchDelegate:self]);
     [_touchEventGestureRecognizer setDelegate:self];
     [self addGestureRecognizer:_touchEventGestureRecognizer.get()];
@@ -916,6 +938,10 @@ static inline bool hasFocusedElement(WebKit::FocusedElementInformation focusedEl
 
 #if ENABLE(POINTER_EVENTS)
     [self removeGestureRecognizer:_touchActionGestureRecognizer.get()];
+    [self removeGestureRecognizer:_touchActionLeftSwipeGestureRecognizer.get()];
+    [self removeGestureRecognizer:_touchActionRightSwipeGestureRecognizer.get()];
+    [self removeGestureRecognizer:_touchActionUpSwipeGestureRecognizer.get()];
+    [self removeGestureRecognizer:_touchActionDownSwipeGestureRecognizer.get()];
 #endif
 
     _layerTreeTransactionIdAtLastTouchStart = { };
@@ -993,6 +1019,10 @@ static inline bool hasFocusedElement(WebKit::FocusedElementInformation focusedEl
 #endif
 #if ENABLE(POINTER_EVENTS)
     [self removeGestureRecognizer:_touchActionGestureRecognizer.get()];
+    [self removeGestureRecognizer:_touchActionLeftSwipeGestureRecognizer.get()];
+    [self removeGestureRecognizer:_touchActionRightSwipeGestureRecognizer.get()];
+    [self removeGestureRecognizer:_touchActionUpSwipeGestureRecognizer.get()];
+    [self removeGestureRecognizer:_touchActionDownSwipeGestureRecognizer.get()];
 #endif
 }
 
@@ -1015,6 +1045,10 @@ static inline bool hasFocusedElement(WebKit::FocusedElementInformation focusedEl
 #endif
 #if ENABLE(POINTER_EVENTS)
     [self addGestureRecognizer:_touchActionGestureRecognizer.get()];
+    [self addGestureRecognizer:_touchActionLeftSwipeGestureRecognizer.get()];
+    [self addGestureRecognizer:_touchActionRightSwipeGestureRecognizer.get()];
+    [self addGestureRecognizer:_touchActionUpSwipeGestureRecognizer.get()];
+    [self addGestureRecognizer:_touchActionDownSwipeGestureRecognizer.get()];
 #endif
 }
 
@@ -1409,6 +1443,22 @@ inline static UIKeyModifierFlags gestureRecognizerModifierFlags(UIGestureRecogni
         }
     }
 }
+
+- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
+{
+    if (gestureRecognizer != _touchActionLeftSwipeGestureRecognizer && gestureRecognizer != _touchActionRightSwipeGestureRecognizer && gestureRecognizer != _touchActionUpSwipeGestureRecognizer && gestureRecognizer != _touchActionDownSwipeGestureRecognizer)
+        return true;
+
+    // We update the enabled state of the various swipe gesture recognizers such that if we have a unidirectional touch-action
+    // specified (only pan-x or only pan-y) we enable the two recognizers in the opposite axis to prevent scrolling from starting
+    // if the initial gesture is such a swipe. Since the recognizers are specified to use a single finger for recognition, we don't
+    // need to worry about the case where there may be more than a single touch for a given UIScrollView.
+    auto touchActions = WebKit::touchActionsForPoint(self, WebCore::roundedIntPoint([touch locationInView:self]));
+    if (gestureRecognizer == _touchActionLeftSwipeGestureRecognizer || gestureRecognizer == _touchActionRightSwipeGestureRecognizer)
+        return touchActions == WebCore::TouchAction::PanY;
+    return touchActions == WebCore::TouchAction::PanX;
+}
+
 #endif
 
 #pragma mark - WKTouchActionGestureRecognizerDelegate implementation