Dispatch pointercancel events when content is panned or zoomed on iOS
authorgraouts@webkit.org <graouts@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 1 Feb 2019 21:53:40 +0000 (21:53 +0000)
committergraouts@webkit.org <graouts@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 1 Feb 2019 21:53:40 +0000 (21:53 +0000)
https://bugs.webkit.org/show_bug.cgi?id=193962
<rdar://problem/47629134>

Reviewed by Dean Jackson.

Source/WebCore:

Expose two new methods on PointerCaptureController so that, given a pointer id, it can be established whether this pointer
has been cancelled, which is important because a cancelled pointer should no longer dispatch any further pointer events, and
to cancel a pointer.

Tests: pointerevents/ios/touch-action-pointercancel-pan-x.html
       pointerevents/ios/touch-action-pointercancel-pan-y.html
       pointerevents/ios/touch-action-pointercancel-pinch-zoom.html

* WebCore.xcodeproj/project.pbxproj: Make PointerCaptureController.h Private so that it can be imported from WebKit.
* dom/PointerEvent.h: Remove an unnecessary #if ENABLE(POINTER_EVENTS) since the entire file is already contained in one.
Then we add a new create() method that takes an event type, a pointer id and a pointer type (touch vs. pen) that we use
to create pointercancel events in PointerCaptureController::cancelPointer().
* page/Page.cpp:
(WebCore::Page::Page): Pass the Page as a parameter when creating the PointerCaptureController.
* page/PointerCaptureController.cpp:
(WebCore::PointerCaptureController::PointerCaptureController): Add a Page reference to the constructor since we'll need
the page to access its main frame's EventHandler to perform hit testing in case we do not have a capture target override
in cancelPointer().
(WebCore::PointerCaptureController::releasePointerCapture): Drive-by, remove the the implicit parameter since on iOS we
don't need to differentiate. We'll bring this back for the macOS work.
(WebCore::PointerCaptureController::hasCancelledPointerEventForIdentifier): New method we'll use when dispatching pointer
events to identify whether a pointer id has already been cancelled which will allow for _not_ dispatching any further
pointer events for this pointer id.
(WebCore::PointerCaptureController::pointerEventWillBeDispatched): Keep track of the pointer type so we can preserve it
when dispatching pointercancel events for a given pointer id.
(WebCore::PointerCaptureController::cancelPointer): Dispatch a pointercancel for the provided pointer id, using the capture
target override as the event's target, if there is one, and otherwise hit-testing at the provided location to figure out
what the target should be.
* page/PointerCaptureController.h: Switch the target overrides from Element* to RefPtr<Element> to ensure it may not be
deleted while we still need them. Existing code already ensures these get set to nullptr.

Source/WebKit:

When a user-agent-provided interaction, such as panning or zooming on iOS, uses a set of touches, we should dispatch a pointercancel
event for the pointer ids of the touches involved. To facilitate this, we add a new method on WKContentView to cancel all the pointers
matching active touches for a provided UIGestureRecognizer through an async IPC call into the Web process using the new method
PointerCaptureController::cancelPointer().

* Platform/spi/ios/UIKitSPI.h: Add the necessary forward declaration for a necessary UIKit SPI allowing us to get the set of last-seen
UITouches by the identifier generated for the matching WebKit touch.
* UIProcess/API/Cocoa/WKWebView.mm:
(-[WKWebView scrollViewWillBeginZooming:withView:]): Dispatch touchcancel events for all pointers involved in a pinch gesture on the
top-level UIScrollView.
(-[WKWebView _scrollView:adjustedOffsetForOffset:translation:startPoint:locationInView:horizontalVelocity:verticalVelocity:]): Dispatch
touchcancel events for all pointers involved in a pan gesture on the top-level UIScrollView. We can infer this by looking at whether the
adjusted content offset, after accounting for the permitted touch actions, is different from the original content offset.
* UIProcess/PageClient.h: Expose a new virtual cancelPointersForGestureRecognizer() method which will allow the iOS implementation to
forward the call to WKContentViewInteraction.
(WebKit::PageClient::cancelPointersForGestureRecognizer):
* UIProcess/RemoteLayerTree/RemoteScrollingCoordinatorProxy.h: Expose the WebPageProxy such that we may access it to cancel pointers for
a given gesture recognizer from within ScrollingTreeScrollingNodeDelegateIOS.
(WebKit::RemoteScrollingCoordinatorProxy::webPageProxy const):
* UIProcess/RemoteLayerTree/ios/ScrollingTreeScrollingNodeDelegateIOS.h:
* UIProcess/RemoteLayerTree/ios/ScrollingTreeScrollingNodeDelegateIOS.mm:
(-[WKScrollingNodeScrollViewDelegate _scrollView:adjustedOffsetForOffset:translation:startPoint:locationInView:horizontalVelocity:verticalVelocity:]):
Dispatch touchcancel events for all pointers involved in a pan gesture on a nested UIScrollView. We can infer this by looking at
whether the adjusted content offset, after accounting for the permitted touch actions, is different from the original content offset.
(-[WKScrollingNodeScrollViewDelegate scrollViewWillBeginZooming:withView:]): Dispatch touchcancel events for all pointers involved in a
pinch gesture on a nested UIScrollView.
(-[WKScrollingNodeScrollViewDelegate cancelPointersForGestureRecognizer:]):
(WebKit::ScrollingTreeScrollingNodeDelegateIOS::cancelPointersForGestureRecognizer):
* UIProcess/WebPageProxy.cpp:
(WebKit::WebPageProxy::cancelPointer):
* UIProcess/WebPageProxy.h:
* UIProcess/ios/PageClientImplIOS.h:
* UIProcess/ios/PageClientImplIOS.mm:
(WebKit::PageClientImpl::cancelPointersForGestureRecognizer):
* UIProcess/ios/WKContentViewInteraction.h:
* UIProcess/ios/WKContentViewInteraction.mm:
(-[WKContentView cancelPointersForGestureRecognizer:]): Obtain all active UITouch objects for the view and dispatch a pointercancel event,
through the WebPageProxy, for all touches associated with the provided gesture recognizer.
* WebProcess/WebPage/WebPage.cpp:
(WebKit::WebPage::cancelPointer):
* WebProcess/WebPage/WebPage.h:
* WebProcess/WebPage/WebPage.messages.in:

LayoutTests:

Adding a few tests for "pointercancel" and adding "touch-action: none" on tests that would now be affected by canceling pointers. We also unflake a few tests.

* pointerevents/ios/pointer-events-implicit-capture.html:
* pointerevents/ios/pointer-events-is-primary.html:
* pointerevents/ios/touch-action-pan-x-pan-y.html:
* pointerevents/ios/touch-action-pan-x.html:
* pointerevents/ios/touch-action-pan-y-expected.txt:
* pointerevents/ios/touch-action-pan-y.html:
* pointerevents/ios/touch-action-pinch-zoom-allows-zooming.html:
* pointerevents/ios/touch-action-pointercancel-pan-x-expected.txt: Added.
* pointerevents/ios/touch-action-pointercancel-pan-x.html: Added.
* pointerevents/ios/touch-action-pointercancel-pan-y-expected.txt: Added.
* pointerevents/ios/touch-action-pointercancel-pan-y.html: Added.
* pointerevents/ios/touch-action-pointercancel-pinch-zoom-expected.txt: Added.
* pointerevents/ios/touch-action-pointercancel-pinch-zoom.html: Added.

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

36 files changed:
LayoutTests/ChangeLog
LayoutTests/pointerevents/ios/pointer-events-implicit-capture.html
LayoutTests/pointerevents/ios/pointer-events-is-primary.html
LayoutTests/pointerevents/ios/touch-action-pan-x-pan-y.html
LayoutTests/pointerevents/ios/touch-action-pan-x.html
LayoutTests/pointerevents/ios/touch-action-pan-y-expected.txt
LayoutTests/pointerevents/ios/touch-action-pan-y.html
LayoutTests/pointerevents/ios/touch-action-pinch-zoom-allows-zooming.html
LayoutTests/pointerevents/ios/touch-action-pointercancel-pan-x-expected.txt [new file with mode: 0644]
LayoutTests/pointerevents/ios/touch-action-pointercancel-pan-x.html [new file with mode: 0644]
LayoutTests/pointerevents/ios/touch-action-pointercancel-pan-y-expected.txt [new file with mode: 0644]
LayoutTests/pointerevents/ios/touch-action-pointercancel-pan-y.html [new file with mode: 0644]
LayoutTests/pointerevents/ios/touch-action-pointercancel-pinch-zoom-expected.txt [new file with mode: 0644]
LayoutTests/pointerevents/ios/touch-action-pointercancel-pinch-zoom.html [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/WebCore.xcodeproj/project.pbxproj
Source/WebCore/dom/PointerEvent.h
Source/WebCore/page/Page.cpp
Source/WebCore/page/PointerCaptureController.cpp
Source/WebCore/page/PointerCaptureController.h
Source/WebKit/ChangeLog
Source/WebKit/Platform/spi/ios/UIKitSPI.h
Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm
Source/WebKit/UIProcess/PageClient.h
Source/WebKit/UIProcess/RemoteLayerTree/RemoteScrollingCoordinatorProxy.h
Source/WebKit/UIProcess/RemoteLayerTree/ios/ScrollingTreeScrollingNodeDelegateIOS.h
Source/WebKit/UIProcess/RemoteLayerTree/ios/ScrollingTreeScrollingNodeDelegateIOS.mm
Source/WebKit/UIProcess/WebPageProxy.cpp
Source/WebKit/UIProcess/WebPageProxy.h
Source/WebKit/UIProcess/ios/PageClientImplIOS.h
Source/WebKit/UIProcess/ios/PageClientImplIOS.mm
Source/WebKit/UIProcess/ios/WKContentViewInteraction.h
Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm
Source/WebKit/WebProcess/WebPage/WebPage.cpp
Source/WebKit/WebProcess/WebPage/WebPage.h
Source/WebKit/WebProcess/WebPage/WebPage.messages.in

index 8495db8..395610b 100644 (file)
@@ -1,3 +1,27 @@
+2019-02-01  Antoine Quint  <graouts@apple.com>
+
+        Dispatch pointercancel events when content is panned or zoomed on iOS
+        https://bugs.webkit.org/show_bug.cgi?id=193962
+        <rdar://problem/47629134>
+
+        Reviewed by Dean Jackson.
+
+        Adding a few tests for "pointercancel" and adding "touch-action: none" on tests that would now be affected by canceling pointers. We also unflake a few tests.
+
+        * pointerevents/ios/pointer-events-implicit-capture.html:
+        * pointerevents/ios/pointer-events-is-primary.html:
+        * pointerevents/ios/touch-action-pan-x-pan-y.html:
+        * pointerevents/ios/touch-action-pan-x.html:
+        * pointerevents/ios/touch-action-pan-y-expected.txt:
+        * pointerevents/ios/touch-action-pan-y.html:
+        * pointerevents/ios/touch-action-pinch-zoom-allows-zooming.html:
+        * pointerevents/ios/touch-action-pointercancel-pan-x-expected.txt: Added.
+        * pointerevents/ios/touch-action-pointercancel-pan-x.html: Added.
+        * pointerevents/ios/touch-action-pointercancel-pan-y-expected.txt: Added.
+        * pointerevents/ios/touch-action-pointercancel-pan-y.html: Added.
+        * pointerevents/ios/touch-action-pointercancel-pinch-zoom-expected.txt: Added.
+        * pointerevents/ios/touch-action-pointercancel-pinch-zoom.html: Added.
+
 2019-02-01  Carlos Garcia Campos  <cgarcia@igalia.com>
 
         REGRESSION(r239915): css3/font-feature-font-face-local.html failing on WPE
index c1d08dc..6962f45 100644 (file)
@@ -13,6 +13,7 @@
 'use strict';
 
 target_test((target, test) => {
+    target.style.touchAction = "none";
     const eventTracker = new EventTracker(target, ["pointerdown", "gotpointercapture", "pointermove", "pointerup", "lostpointercapture"]);
 
     const one = ui.finger();
index f31f93b..9dc5c79 100644 (file)
@@ -13,6 +13,7 @@
 'use strict';
 
 target_test((target, test) => {
+    target.style.touchAction = "none";
     const eventTracker = new EventTracker(target, ["pointerdown", "pointermove"]);
 
     const one = ui.finger();
index 52291a4..82b5008 100644 (file)
@@ -19,10 +19,13 @@ target_test({ width: "200px", height: "200px" }, (target, test) => {
     target.style.touchAction = "pan-x pan-y";
 
     ui.swipe({ x: 150, y: 150 }, { x: 50, y: 50 }).then(() => {
-        assert_not_equals(window.pageXOffset, 0, "The page was scrolled in the x-axis.");
-        assert_not_equals(window.pageYOffset, 0, "The page was scrolled in the y-axis.");
-        test.done();
+        requestAnimationFrame(() => {
+            assert_not_equals(window.pageXOffset, 0, "The page was scrolled in the x-axis.");
+            assert_not_equals(window.pageYOffset, 0, "The page was scrolled in the y-axis.");
+            test.done();
+        });
     });
+
 }, "Testing that setting 'touch-action: pan-x pan-y' on an element allows page scrolling in both axes.");
 
 </script>
index cb9205f..fd9c145 100644 (file)
@@ -19,9 +19,11 @@ target_test({ width: "200px", height: "200px" }, (target, test) => {
     target.style.touchAction = "pan-x";
 
     ui.swipe({ x: 150, y: 150 }, { x: 50, y: 50 }).then(() => {
-        assert_not_equals(window.pageXOffset, 0, "The page was scrolled in the x-axis.");
-        assert_equals(window.pageYOffset, 0, "The page was not scrolled in the y-axis.");
-        test.done();
+        requestAnimationFrame(() => {
+            assert_not_equals(window.pageXOffset, 0, "The page was scrolled in the x-axis.");
+            assert_equals(window.pageYOffset, 0, "The page was not scrolled in the y-axis.");
+            test.done();
+        });
     });
 }, "Testing that setting touch-action: pan-x on an element prevents page scrolling in the y-axis.");
 
index 3ff658c..d11c596 100644 (file)
@@ -1,3 +1,3 @@
 
-PASS Testing that setting touch-action: pan-x on an element prevents page scrolling in the x-axis. 
+PASS Testing that setting touch-action: pan-y on an element prevents page scrolling in the x-axis. 
 
index 9090d39..6ba9c81 100644 (file)
@@ -19,11 +19,13 @@ target_test({ width: "200px", height: "200px" }, (target, test) => {
     target.style.touchAction = "pan-y";
 
     ui.swipe({ x: 150, y: 150 }, { x: 50, y: 50 }).then(() => {
-        assert_equals(window.pageXOffset, 0, "The page was not scrolled in the x-axis.");
-        assert_not_equals(window.pageYOffset, 0, "The page was scrolled in the y-axis.");
-        test.done();
+        requestAnimationFrame(() => {
+            assert_equals(window.pageXOffset, 0, "The page was not scrolled in the x-axis.");
+            assert_not_equals(window.pageYOffset, 0, "The page was scrolled in the y-axis.");
+            test.done();
+        });
     });
-}, "Testing that setting touch-action: pan-x on an element prevents page scrolling in the x-axis.");
+}, "Testing that setting touch-action: pan-y on an element prevents page scrolling in the x-axis.");
 
 </script>
 </body>
index 2da63c9..2795760 100644 (file)
@@ -19,8 +19,10 @@ target_test({ width: "400px", height: "400px" }, (target, test) => {
     target.style.touchAction = "pinch-zoom";
 
     ui.pinchOut({ x: 50, y: 50, width: 100, height: 100, scale: 0.5 }).then(() => {
-        assert_not_equals(window.internals.pageScaleFactor(), 1, "The page was scaled.");
-        test.done();
+        requestAnimationFrame(() => {
+            assert_not_equals(window.internals.pageScaleFactor(), 1, "The page was scaled.");
+            test.done();
+        });
     });
 }, "Testing that setting touch-action: pinch-zoom on an element allows page zooming.");
 
diff --git a/LayoutTests/pointerevents/ios/touch-action-pointercancel-pan-x-expected.txt b/LayoutTests/pointerevents/ios/touch-action-pointercancel-pan-x-expected.txt
new file mode 100644 (file)
index 0000000..9f36d91
--- /dev/null
@@ -0,0 +1,3 @@
+
+PASS Testing that panning in the x-axis on an element with touch-action: pan-x triggers a pointercancel event. 
+
diff --git a/LayoutTests/pointerevents/ios/touch-action-pointercancel-pan-x.html b/LayoutTests/pointerevents/ios/touch-action-pointercancel-pan-x.html
new file mode 100644 (file)
index 0000000..04e06d6
--- /dev/null
@@ -0,0 +1,45 @@
+<!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>
+
+'use strict';
+
+target_test({ width: "200px", height: "200px" }, (target, test) => {
+    document.body.style.width = "2000px";
+    document.body.style.height = "2000px";
+
+    target.style.touchAction = "pan-x";
+
+    const eventTracker = new EventTracker(target, ["pointerdown", "pointermove", "pointerup", "pointercancel"]);
+
+    const one = ui.finger();
+    ui.sequence([
+        one.begin({ x: 150, y: 150 }),
+        one.move({ x: 149, y: 150 }),
+        one.move({ x: 148, y: 150 }),
+        one.move({ x: 120, y: 150 }),
+        one.move({ x: 100, y: 150 }),
+        one.end()
+    ]).then(() => {
+        eventTracker.assertMatchesEvents([
+            { type: "pointerdown" },
+            { type: "pointermove" },
+            { type: "pointermove" },
+            { type: "pointermove" },
+            { type: "pointercancel" }
+        ]);
+        test.done();
+    });
+}, "Testing that panning in the x-axis on an element with touch-action: pan-x triggers a pointercancel event.");
+
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/LayoutTests/pointerevents/ios/touch-action-pointercancel-pan-y-expected.txt b/LayoutTests/pointerevents/ios/touch-action-pointercancel-pan-y-expected.txt
new file mode 100644 (file)
index 0000000..f41d601
--- /dev/null
@@ -0,0 +1,3 @@
+
+PASS Testing that panning in the y-axis on an element with touch-action: pan-y triggers a pointercancel event. 
+
diff --git a/LayoutTests/pointerevents/ios/touch-action-pointercancel-pan-y.html b/LayoutTests/pointerevents/ios/touch-action-pointercancel-pan-y.html
new file mode 100644 (file)
index 0000000..5398c67
--- /dev/null
@@ -0,0 +1,45 @@
+<!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>
+
+'use strict';
+
+target_test({ width: "200px", height: "200px" }, (target, test) => {
+    document.body.style.width = "2000px";
+    document.body.style.height = "2000px";
+
+    target.style.touchAction = "pan-y";
+
+    const eventTracker = new EventTracker(target, ["pointerdown", "pointermove", "pointerup", "pointercancel"]);
+
+    const one = ui.finger();
+    ui.sequence([
+        one.begin({ x: 150, y: 150 }),
+        one.move({ x: 150, y: 149 }),
+        one.move({ x: 150, y: 148 }),
+        one.move({ x: 150, y: 120 }),
+        one.move({ x: 150, y: 100 }),
+        one.end()
+    ]).then(() => {
+        eventTracker.assertMatchesEvents([
+            { type: "pointerdown" },
+            { type: "pointermove" },
+            { type: "pointermove" },
+            { type: "pointermove" },
+            { type: "pointercancel" }
+        ]);
+        test.done();
+    });
+}, "Testing that panning in the y-axis on an element with touch-action: pan-y triggers a pointercancel event.");
+
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/LayoutTests/pointerevents/ios/touch-action-pointercancel-pinch-zoom-expected.txt b/LayoutTests/pointerevents/ios/touch-action-pointercancel-pinch-zoom-expected.txt
new file mode 100644 (file)
index 0000000..7b6fa4d
--- /dev/null
@@ -0,0 +1,3 @@
+
+PASS Testing that pinching on an element with touch-action: pinch-zoom triggers a pointercancel event for each touch. 
+
diff --git a/LayoutTests/pointerevents/ios/touch-action-pointercancel-pinch-zoom.html b/LayoutTests/pointerevents/ios/touch-action-pointercancel-pinch-zoom.html
new file mode 100644 (file)
index 0000000..310972a
--- /dev/null
@@ -0,0 +1,36 @@
+<!DOCTYPE html><!-- webkit-test-runner [ useFlexibleViewport=true ] -->
+<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>
+
+'use strict';
+
+target_test({ width: "400px", height: "400px" }, (target, test) => {
+    document.body.style.width = "2000px";
+    document.body.style.height = "2000px";
+
+    target.style.touchAction = "pinch-zoom";
+
+    const eventTracker = new EventTracker(target, ["pointerdown", "pointercancel"]);
+
+    ui.pinchOut({ x: 50, y: 50, width: 100, height: 100, scale: 0.5 }).then(() => {
+        eventTracker.assertMatchesEvents([
+            { type: "pointerdown" },
+            { type: "pointerdown" },
+            { type: "pointercancel" },
+            { type: "pointercancel" }
+        ]);
+        test.done();
+    });
+}, "Testing that pinching on an element with touch-action: pinch-zoom triggers a pointercancel event for each touch.");
+
+</script>
+</body>
+</html>
\ No newline at end of file
index 4a23ea3..5723d7d 100644 (file)
@@ -1,3 +1,42 @@
+2019-02-01  Antoine Quint  <graouts@apple.com>
+
+        Dispatch pointercancel events when content is panned or zoomed on iOS
+        https://bugs.webkit.org/show_bug.cgi?id=193962
+        <rdar://problem/47629134>
+
+        Reviewed by Dean Jackson.
+
+        Expose two new methods on PointerCaptureController so that, given a pointer id, it can be established whether this pointer
+        has been cancelled, which is important because a cancelled pointer should no longer dispatch any further pointer events, and
+        to cancel a pointer.
+
+        Tests: pointerevents/ios/touch-action-pointercancel-pan-x.html
+               pointerevents/ios/touch-action-pointercancel-pan-y.html
+               pointerevents/ios/touch-action-pointercancel-pinch-zoom.html
+
+        * WebCore.xcodeproj/project.pbxproj: Make PointerCaptureController.h Private so that it can be imported from WebKit.
+        * dom/PointerEvent.h: Remove an unnecessary #if ENABLE(POINTER_EVENTS) since the entire file is already contained in one.
+        Then we add a new create() method that takes an event type, a pointer id and a pointer type (touch vs. pen) that we use
+        to create pointercancel events in PointerCaptureController::cancelPointer().
+        * page/Page.cpp:
+        (WebCore::Page::Page): Pass the Page as a parameter when creating the PointerCaptureController.
+        * page/PointerCaptureController.cpp:
+        (WebCore::PointerCaptureController::PointerCaptureController): Add a Page reference to the constructor since we'll need
+        the page to access its main frame's EventHandler to perform hit testing in case we do not have a capture target override
+        in cancelPointer().
+        (WebCore::PointerCaptureController::releasePointerCapture): Drive-by, remove the the implicit parameter since on iOS we
+        don't need to differentiate. We'll bring this back for the macOS work.
+        (WebCore::PointerCaptureController::hasCancelledPointerEventForIdentifier): New method we'll use when dispatching pointer
+        events to identify whether a pointer id has already been cancelled which will allow for _not_ dispatching any further
+        pointer events for this pointer id.
+        (WebCore::PointerCaptureController::pointerEventWillBeDispatched): Keep track of the pointer type so we can preserve it
+        when dispatching pointercancel events for a given pointer id.
+        (WebCore::PointerCaptureController::cancelPointer): Dispatch a pointercancel for the provided pointer id, using the capture
+        target override as the event's target, if there is one, and otherwise hit-testing at the provided location to figure out
+        what the target should be.
+        * page/PointerCaptureController.h: Switch the target overrides from Element* to RefPtr<Element> to ensure it may not be
+        deleted while we still need them. Existing code already ensures these get set to nullptr.
+
 2019-02-01  Jer Noble  <jer.noble@apple.com>
 
         Make the WebKit default for media source based on the WebCore default.
index dd9174d..821e2f9 100644 (file)
                71A1B6081DEE5AD70073BCFB /* modern-media-controls-localized-strings.js in Resources */ = {isa = PBXBuildFile; fileRef = 71A1B6061DEE5A820073BCFB /* modern-media-controls-localized-strings.js */; };
                71A57DF2154BE25C0009D120 /* SVGPathUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 71A57DF0154BE25C0009D120 /* SVGPathUtilities.h */; };
                71B28427203CEC4C0036AA5D /* JSCSSAnimation.h in Headers */ = {isa = PBXBuildFile; fileRef = 71B28426203CEC0D0036AA5D /* JSCSSAnimation.h */; settings = {ATTRIBUTES = (Private, ); }; };
-               71B5AB2621F1D9F400376E5C /* PointerCaptureController.h in Headers */ = {isa = PBXBuildFile; fileRef = 71B5AB2421F1D9E200376E5C /* PointerCaptureController.h */; };
+               71B5AB2621F1D9F400376E5C /* PointerCaptureController.h in Headers */ = {isa = PBXBuildFile; fileRef = 71B5AB2421F1D9E200376E5C /* PointerCaptureController.h */; settings = {ATTRIBUTES = (Private, ); }; };
                71B7EE0D21B5C6870031C1EF /* TouchAction.h in Headers */ = {isa = PBXBuildFile; fileRef = 71AEE4EB21B5A49C00DDB036 /* TouchAction.h */; settings = {ATTRIBUTES = (Private, ); }; };
                71C29E32203CE781008F36D2 /* CSSAnimation.h in Headers */ = {isa = PBXBuildFile; fileRef = 71C29E30203CE76B008F36D2 /* CSSAnimation.h */; settings = {ATTRIBUTES = (Private, ); }; };
                71C916081D1483A300ACA47D /* UserInterfaceLayoutDirection.h in Headers */ = {isa = PBXBuildFile; fileRef = 71C916071D1483A300ACA47D /* UserInterfaceLayoutDirection.h */; settings = {ATTRIBUTES = (Private, ); }; };
index 25059af..3bcfa95 100644 (file)
@@ -56,7 +56,15 @@ public:
         return adoptRef(*new PointerEvent(type, WTFMove(initializer)));
     }
 
-#if ENABLE(POINTER_EVENTS)
+    static Ref<PointerEvent> create(const AtomicString& type, int32_t pointerId, String pointerType)
+    {
+        Init initializer;
+        initializer.bubbles = true;
+        initializer.pointerId = pointerId;
+        initializer.pointerType = pointerType;
+        return adoptRef(*new PointerEvent(type, WTFMove(initializer)));
+    }
+
     static Ref<PointerEvent> createForPointerCapture(const AtomicString& type, const PointerEvent& pointerEvent)
     {
         Init initializer;
@@ -66,7 +74,6 @@ public:
         initializer.pointerType = pointerEvent.pointerType();
         return adoptRef(*new PointerEvent(type, WTFMove(initializer)));
     }
-#endif
 
     static Ref<PointerEvent> createForBindings()
     {
index fd66607..0783004 100644 (file)
@@ -216,7 +216,7 @@ Page::Page(PageConfiguration&& pageConfiguration)
     , m_userInputBridge(std::make_unique<UserInputBridge>(*this))
     , m_inspectorController(std::make_unique<InspectorController>(*this, pageConfiguration.inspectorClient))
 #if ENABLE(POINTER_EVENTS)
-    , m_pointerCaptureController(std::make_unique<PointerCaptureController>())
+    , m_pointerCaptureController(std::make_unique<PointerCaptureController>(*this))
 #endif
 #if ENABLE(POINTER_LOCK)
     , m_pointerLockController(std::make_unique<PointerLockController>(*this))
index 65d813d..1c95922 100644 (file)
@@ -29,6 +29,7 @@
 
 #include "Document.h"
 #include "Element.h"
+#include "EventHandler.h"
 #include "EventNames.h"
 #include "EventTarget.h"
 #include "Page.h"
 
 namespace WebCore {
 
-PointerCaptureController::PointerCaptureController() = default;
+PointerCaptureController::PointerCaptureController(Page& page)
+    : m_page(page)
+{
+}
 
 ExceptionOr<void> PointerCaptureController::setPointerCapture(Element* capturingTarget, int32_t pointerId)
 {
@@ -68,7 +72,7 @@ ExceptionOr<void> PointerCaptureController::setPointerCapture(Element* capturing
     return { };
 }
 
-ExceptionOr<void> PointerCaptureController::releasePointerCapture(Element* capturingTarget, int32_t pointerId, ImplicitCapture implicit)
+ExceptionOr<void> PointerCaptureController::releasePointerCapture(Element* capturingTarget, int32_t pointerId)
 {
     // https://w3c.github.io/pointerevents/#releasing-pointer-capture
 
@@ -78,7 +82,7 @@ ExceptionOr<void> PointerCaptureController::releasePointerCapture(Element* captu
     // 1. If the pointerId provided as the method's argument does not match any of the active pointers and these steps are not
     // being invoked as a result of the implicit release of pointer capture, then throw a DOMException with the name NotFoundError.
     auto iterator = m_activePointerIdsToCapturingData.find(pointerId);
-    if (implicit == ImplicitCapture::No && iterator == m_activePointerIdsToCapturingData.end())
+    if (iterator == m_activePointerIdsToCapturingData.end())
         return Exception { NotFoundError };
 
     // 2. If hasPointerCapture is false for the Element with the specified pointerId, then terminate these steps.
@@ -124,6 +128,12 @@ void PointerCaptureController::touchEndedOrWasCancelledForIdentifier(int32_t poi
     m_activePointerIdsToCapturingData.remove(pointerId);
 }
 
+bool PointerCaptureController::hasCancelledPointerEventForIdentifier(int32_t pointerId)
+{
+    auto iterator = m_activePointerIdsToCapturingData.find(pointerId);
+    return iterator != m_activePointerIdsToCapturingData.end() && iterator->value.cancelled;
+}
+
 void PointerCaptureController::pointerEventWillBeDispatched(const PointerEvent& event, EventTarget* target)
 {
     // https://w3c.github.io/pointerevents/#implicit-pointer-capture
@@ -142,7 +152,9 @@ void PointerCaptureController::pointerEventWillBeDispatched(const PointerEvent&
         return;
 
     auto pointerId = event.pointerId();
-    m_activePointerIdsToCapturingData.set(pointerId, CapturingData { });
+    CapturingData capturingData;
+    capturingData.pointerType = event.pointerType();
+    m_activePointerIdsToCapturingData.set(pointerId, capturingData);
     setPointerCapture(downcast<Element>(target), pointerId);
 }
 
@@ -171,6 +183,48 @@ void PointerCaptureController::pointerEventWasDispatched(const PointerEvent& eve
     processPendingPointerCapture(event);
 }
 
+void PointerCaptureController::cancelPointer(int32_t pointerId, const IntPoint& documentPoint)
+{
+    // https://w3c.github.io/pointerevents/#the-pointercancel-event
+
+    // A user agent MUST fire a pointer event named pointercancel in the following circumstances:
+    //
+    // The user agent has determined that a pointer is unlikely to continue to produce events (for example, because of a hardware event).
+    // After having fired the pointerdown event, if the pointer is subsequently used to manipulate the page viewport (e.g. panning or zooming).
+    // Immediately before drag operation starts [HTML], for the pointer that caused the drag operation.
+    // After firing the pointercancel event, a user agent MUST also fire a pointer event named pointerout followed by firing a pointer event named pointerleave.
+
+    // https://w3c.github.io/pointerevents/#implicit-release-of-pointer-capture
+
+    // Immediately after firing the pointerup or pointercancel events, a user agent MUST clear the pending pointer capture target
+    // override for the pointerId of the pointerup or pointercancel event that was just dispatched, and then run Process Pending
+    // Pointer Capture steps to fire lostpointercapture if necessary. After running Process Pending Pointer Capture steps, if the
+    // pointer supports hover, user agent MUST also send corresponding boundary events necessary to reflect the current position of
+    // the pointer with no capture.
+
+    auto iterator = m_activePointerIdsToCapturingData.find(pointerId);
+    if (iterator == m_activePointerIdsToCapturingData.end())
+        return;
+
+    auto& capturingData = iterator->value;
+    if (capturingData.cancelled)
+        return;
+
+    capturingData.pendingTargetOverride = nullptr;
+    capturingData.cancelled = true;
+
+    auto& target = capturingData.targetOverride;
+    if (!target)
+        target = m_page.mainFrame().eventHandler().hitTestResultAtPoint(documentPoint, HitTestRequest::ReadOnly | HitTestRequest::Active | HitTestRequest::DisallowUserAgentShadowContent | HitTestRequest::AllowChildFrameContent).innerNonSharedElement();
+
+    if (!target)
+        return;
+
+    auto event = PointerEvent::create(eventNames().pointercancelEvent, pointerId, capturingData.pointerType);
+    target->dispatchEvent(event);
+    processPendingPointerCapture(WTFMove(event));
+}
+
 void PointerCaptureController::processPendingPointerCapture(const PointerEvent& event)
 {
     // https://w3c.github.io/pointerevents/#process-pending-pointer-capture
index 8e7d220..720a577 100644 (file)
@@ -38,27 +38,31 @@ class PointerCaptureController {
     WTF_MAKE_NONCOPYABLE(PointerCaptureController);
     WTF_MAKE_FAST_ALLOCATED;
 public:
-    explicit PointerCaptureController();
-
-    enum class ImplicitCapture : uint8_t { Yes, No };
+    explicit PointerCaptureController(Page&);
 
     ExceptionOr<void> setPointerCapture(Element*, int32_t);
-    ExceptionOr<void> releasePointerCapture(Element*, int32_t, ImplicitCapture implicit = ImplicitCapture::No);
+    ExceptionOr<void> releasePointerCapture(Element*, int32_t);
     bool hasPointerCapture(Element*, int32_t);
 
     void pointerLockWasApplied();
 
     void touchEndedOrWasCancelledForIdentifier(int32_t);
+    bool hasCancelledPointerEventForIdentifier(int32_t);
     void pointerEventWillBeDispatched(const PointerEvent&, EventTarget*);
     void pointerEventWasDispatched(const PointerEvent&);
+    WEBCORE_EXPORT void cancelPointer(int32_t, const IntPoint&);
 
 private:
     struct CapturingData {
-        Element* pendingTargetOverride;
-        Element* targetOverride;
+        RefPtr<Element> pendingTargetOverride;
+        RefPtr<Element> targetOverride;
+        String pointerType;
+        bool cancelled { false };
     };
 
     void processPendingPointerCapture(const PointerEvent&);
+
+    Page& m_page;
     HashMap<int32_t, CapturingData> m_activePointerIdsToCapturingData;
 };
 
index 56c1ec3..b75ca1c 100644 (file)
@@ -1,3 +1,54 @@
+2019-02-01  Antoine Quint  <graouts@apple.com>
+
+        Dispatch pointercancel events when content is panned or zoomed on iOS
+        https://bugs.webkit.org/show_bug.cgi?id=193962
+        <rdar://problem/47629134>
+
+        Reviewed by Dean Jackson.
+
+        When a user-agent-provided interaction, such as panning or zooming on iOS, uses a set of touches, we should dispatch a pointercancel
+        event for the pointer ids of the touches involved. To facilitate this, we add a new method on WKContentView to cancel all the pointers
+        matching active touches for a provided UIGestureRecognizer through an async IPC call into the Web process using the new method
+        PointerCaptureController::cancelPointer().
+
+        * Platform/spi/ios/UIKitSPI.h: Add the necessary forward declaration for a necessary UIKit SPI allowing us to get the set of last-seen
+        UITouches by the identifier generated for the matching WebKit touch.
+        * UIProcess/API/Cocoa/WKWebView.mm:
+        (-[WKWebView scrollViewWillBeginZooming:withView:]): Dispatch touchcancel events for all pointers involved in a pinch gesture on the
+        top-level UIScrollView.
+        (-[WKWebView _scrollView:adjustedOffsetForOffset:translation:startPoint:locationInView:horizontalVelocity:verticalVelocity:]): Dispatch
+        touchcancel events for all pointers involved in a pan gesture on the top-level UIScrollView. We can infer this by looking at whether the
+        adjusted content offset, after accounting for the permitted touch actions, is different from the original content offset.
+        * UIProcess/PageClient.h: Expose a new virtual cancelPointersForGestureRecognizer() method which will allow the iOS implementation to
+        forward the call to WKContentViewInteraction.
+        (WebKit::PageClient::cancelPointersForGestureRecognizer):
+        * UIProcess/RemoteLayerTree/RemoteScrollingCoordinatorProxy.h: Expose the WebPageProxy such that we may access it to cancel pointers for
+        a given gesture recognizer from within ScrollingTreeScrollingNodeDelegateIOS.
+        (WebKit::RemoteScrollingCoordinatorProxy::webPageProxy const):
+        * UIProcess/RemoteLayerTree/ios/ScrollingTreeScrollingNodeDelegateIOS.h:
+        * UIProcess/RemoteLayerTree/ios/ScrollingTreeScrollingNodeDelegateIOS.mm:
+        (-[WKScrollingNodeScrollViewDelegate _scrollView:adjustedOffsetForOffset:translation:startPoint:locationInView:horizontalVelocity:verticalVelocity:]):
+        Dispatch touchcancel events for all pointers involved in a pan gesture on a nested UIScrollView. We can infer this by looking at
+        whether the adjusted content offset, after accounting for the permitted touch actions, is different from the original content offset.
+        (-[WKScrollingNodeScrollViewDelegate scrollViewWillBeginZooming:withView:]): Dispatch touchcancel events for all pointers involved in a
+        pinch gesture on a nested UIScrollView.
+        (-[WKScrollingNodeScrollViewDelegate cancelPointersForGestureRecognizer:]):
+        (WebKit::ScrollingTreeScrollingNodeDelegateIOS::cancelPointersForGestureRecognizer):
+        * UIProcess/WebPageProxy.cpp:
+        (WebKit::WebPageProxy::cancelPointer):
+        * UIProcess/WebPageProxy.h:
+        * UIProcess/ios/PageClientImplIOS.h:
+        * UIProcess/ios/PageClientImplIOS.mm:
+        (WebKit::PageClientImpl::cancelPointersForGestureRecognizer):
+        * UIProcess/ios/WKContentViewInteraction.h:
+        * UIProcess/ios/WKContentViewInteraction.mm:
+        (-[WKContentView cancelPointersForGestureRecognizer:]): Obtain all active UITouch objects for the view and dispatch a pointercancel event,
+        through the WebPageProxy, for all touches associated with the provided gesture recognizer.
+        * WebProcess/WebPage/WebPage.cpp:
+        (WebKit::WebPage::cancelPointer):
+        * WebProcess/WebPage/WebPage.h:
+        * WebProcess/WebPage/WebPage.messages.in:
+
 2019-02-01  Jer Noble  <jer.noble@apple.com>
 
         Make the WebKit default for media source based on the WebCore default.
index 53f3ff7..0978e6d 100644 (file)
@@ -1031,6 +1031,11 @@ typedef NSInteger UICompositingMode;
 
 #endif // USE(APPLE_INTERNAL_SDK)
 
+// FIXME: <rdar://problem/47714562>
+@interface UIWebTouchEventsGestureRecognizer ()
+@property (nonatomic, readonly) NSMapTable<NSNumber *, UITouch *> *activeTouchesByIdentifier;
+@end
+
 @interface UIPhysicalKeyboardEvent : UIPressesEvent
 @end
 
index 878e3e2..9a73a36 100644 (file)
@@ -2551,6 +2551,10 @@ static WebCore::FloatPoint constrainContentOffset(WebCore::FloatPoint contentOff
         [_contentView scrollViewWillStartPanOrPinchGesture];
     }
     [_contentView willStartZoomOrScroll];
+
+#if ENABLE(POINTER_EVENTS)
+    [_contentView cancelPointersForGestureRecognizer:scrollView.pinchGestureRecognizer];
+#endif
 }
 
 - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
@@ -2635,8 +2639,10 @@ static WebCore::FloatPoint constrainContentOffset(WebCore::FloatPoint contentOff
 #if ENABLE(POINTER_EVENTS)
 - (CGPoint)_scrollView:(UIScrollView *)scrollView adjustedOffsetForOffset:(CGPoint)offset translation:(CGPoint)translation startPoint:(CGPoint)start locationInView:(CGPoint)locationInView horizontalVelocity:(inout double *)hv verticalVelocity:(inout double *)vv
 {
-    if (![_contentView preventsPanningInXAxis] && ![_contentView preventsPanningInYAxis])
+    if (![_contentView preventsPanningInXAxis] && ![_contentView preventsPanningInYAxis]) {
+        [_contentView cancelPointersForGestureRecognizer:scrollView.panGestureRecognizer];
         return offset;
+    }
 
     CGPoint adjustedContentOffset = CGPointMake(offset.x, offset.y);
     if ([_contentView preventsPanningInXAxis])
@@ -2644,6 +2650,11 @@ static WebCore::FloatPoint constrainContentOffset(WebCore::FloatPoint contentOff
     if ([_contentView preventsPanningInYAxis])
         adjustedContentOffset.y = start.y;
 
+    if ((![_contentView preventsPanningInXAxis] && adjustedContentOffset.x != start.x)
+        || (![_contentView preventsPanningInYAxis] && adjustedContentOffset.y != start.y)) {
+        [_contentView cancelPointersForGestureRecognizer:scrollView.panGestureRecognizer];
+    }
+
     return adjustedContentOffset;
 }
 #endif
index 4872c94..9617d0d 100644 (file)
@@ -52,6 +52,7 @@ OBJC_CLASS CALayer;
 OBJC_CLASS NSFileWrapper;
 OBJC_CLASS NSMenu;
 OBJC_CLASS NSSet;
+OBJC_CLASS UIGestureRecognizer;
 OBJC_CLASS WKDrawingView;
 OBJC_CLASS _WKRemoteObjectRegistry;
 
@@ -477,6 +478,11 @@ public:
 #if HAVE(PENCILKIT)
     virtual RetainPtr<WKDrawingView> createDrawingView(WebCore::GraphicsLayer::EmbeddedViewID) { return nullptr; }
 #endif
+
+#if ENABLE(POINTER_EVENTS)
+    virtual void cancelPointersForGestureRecognizer(UIGestureRecognizer*) { }
+#endif
+
 };
 
 } // namespace WebKit
index df07156..0a30709 100644 (file)
@@ -71,6 +71,7 @@ public:
     WebCore::ScrollingNodeID rootScrollingNodeID() const;
 
     const RemoteLayerTreeHost* layerTreeHost() const;
+    WebPageProxy& webPageProxy() const { return m_webPageProxy; }
 
     struct RequestedScrollInfo {
         bool requestsScrollPositionUpdate { };
index a34c80c..92dbe79 100644 (file)
@@ -67,6 +67,7 @@ public:
     void updateChildNodesAfterScroll(const WebCore::FloatPoint& scrollPosition);
 #if ENABLE(POINTER_EVENTS)
     Optional<TouchActionData> touchActionData() const;
+    void cancelPointersForGestureRecognizer(UIGestureRecognizer*);
 #endif
 
 private:
index afd0ae2..ca997b0 100644 (file)
 #import <WebCore/ScrollSnapOffsetsInfo.h>
 #endif
 
+#if ENABLE(POINTER_EVENTS)
+#import "PageClient.h"
+#endif
+
 @implementation WKScrollingNodeScrollViewDelegate
 
 - (instancetype)initWithScrollingTreeNodeDelegate:(WebKit::ScrollingTreeScrollingNodeDelegateIOS*)delegate
 - (CGPoint)_scrollView:(UIScrollView *)scrollView adjustedOffsetForOffset:(CGPoint)offset translation:(CGPoint)translation startPoint:(CGPoint)start locationInView:(CGPoint)locationInView horizontalVelocity:(inout double *)hv verticalVelocity:(inout double *)vv
 {
     auto touchActionData = _scrollingTreeNodeDelegate->touchActionData();
-    if (!touchActionData)
+    if (!touchActionData) {
+        [self cancelPointersForGestureRecognizer:scrollView.panGestureRecognizer];
         return offset;
+    }
 
     auto touchActions = touchActionData->touchActions;
     if (touchActions == WebCore::TouchAction::Auto || touchActions == WebCore::TouchAction::Manipulation)
     if (!touchActions.contains(WebCore::TouchAction::PanY))
         adjustedContentOffset.y = start.y;
 
+    if ((touchActions.contains(WebCore::TouchAction::PanX) && adjustedContentOffset.x != start.x)
+        || (touchActions.contains(WebCore::TouchAction::PanY) && adjustedContentOffset.y != start.y)) {
+        [self cancelPointersForGestureRecognizer:scrollView.panGestureRecognizer];
+    }
+
     return adjustedContentOffset;
 }
+
+- (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view
+{
+    [self cancelPointersForGestureRecognizer:scrollView.pinchGestureRecognizer];
+}
+
+- (void)cancelPointersForGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
+{
+    _scrollingTreeNodeDelegate->cancelPointersForGestureRecognizer(gestureRecognizer);
+}
 #endif
 
 @end
@@ -345,6 +366,11 @@ Optional<TouchActionData> ScrollingTreeScrollingNodeDelegateIOS::touchActionData
 {
     return downcast<RemoteScrollingTree>(scrollingTree()).scrollingCoordinatorProxy().touchActionDataForScrollNodeID(scrollingNode().scrollingNodeID());
 }
+
+void ScrollingTreeScrollingNodeDelegateIOS::cancelPointersForGestureRecognizer(UIGestureRecognizer* gestureRecognizer)
+{
+    downcast<RemoteScrollingTree>(scrollingTree()).scrollingCoordinatorProxy().webPageProxy().pageClient().cancelPointersForGestureRecognizer(gestureRecognizer);
+}
 #endif
 
 } // namespace WebKit
index 71996fa..fc0655d 100644 (file)
@@ -2649,6 +2649,13 @@ void WebPageProxy::handleTouchEvent(const NativeWebTouchEvent& event)
 }
 #endif // ENABLE(TOUCH_EVENTS)
 
+#if ENABLE(POINTER_EVENTS)
+void WebPageProxy::cancelPointer(int32_t pointerId, const WebCore::IntPoint& documentPoint)
+{
+    m_process->send(Messages::WebPage::CancelPointer(pointerId, documentPoint), m_pageID);
+}
+#endif
+
 void WebPageProxy::scrollBy(ScrollDirection direction, ScrollGranularity granularity)
 {
     if (!isValid())
index 892ffcd..9408071 100644 (file)
@@ -790,6 +790,10 @@ public:
     void handleTouchEvent(const NativeWebTouchEvent&);
 #endif
 
+#if ENABLE(POINTER_EVENTS)
+    void cancelPointer(int32_t, const WebCore::IntPoint&);
+#endif
+
     void scrollBy(WebCore::ScrollDirection, WebCore::ScrollGranularity);
     void centerSelectionInVisibleArea();
 
index 58be76b..a3c6d3a 100644 (file)
@@ -239,6 +239,10 @@ private:
     RetainPtr<WKDrawingView> createDrawingView(WebCore::GraphicsLayer::EmbeddedViewID) override;
 #endif
 
+#if ENABLE(POINTER_EVENTS)
+    void cancelPointersForGestureRecognizer(UIGestureRecognizer*) override;
+#endif
+
     WKContentView *m_contentView;
     RetainPtr<WKEditorUndoTarget> m_undoTarget;
 };
index 4a992bf..1509cc2 100644 (file)
@@ -844,6 +844,13 @@ RetainPtr<WKDrawingView> PageClientImpl::createDrawingView(WebCore::GraphicsLaye
 }
 #endif
 
+#if ENABLE(POINTER_EVENTS)
+void PageClientImpl::cancelPointersForGestureRecognizer(UIGestureRecognizer* gestureRecognizer)
+{
+    [m_contentView cancelPointersForGestureRecognizer:gestureRecognizer];
+}
+#endif
+
 } // namespace WebKit
 
 #endif // PLATFORM(IOS_FAMILY)
index af2f109..fad0bcb 100644 (file)
@@ -378,6 +378,10 @@ struct WKAutoCorrectionData {
 - (BOOL)canPerformActionForWebView:(SEL)action withSender:(id)sender;
 - (id)targetForActionForWebView:(SEL)action withSender:(id)sender;
 
+#if ENABLE(POINTER_EVENTS)
+- (void)cancelPointersForGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer;
+#endif
+
 #define DECLARE_WKCONTENTVIEW_ACTION_FOR_WEB_VIEW(_action) \
     - (void)_action ## ForWebView:(id)sender;
 FOR_EACH_WKCONTENTVIEW_ACTION(DECLARE_WKCONTENTVIEW_ACTION_FOR_WEB_VIEW)
index 5eaba0f..7a98642 100644 (file)
@@ -1146,6 +1146,22 @@ static inline bool hasFocusedElement(WebKit::FocusedElementInformation focusedEl
     return superDidResign;
 }
 
+#if ENABLE(POINTER_EVENTS)
+- (void)cancelPointersForGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
+{
+    // FIXME: <rdar://problem/47714562>
+    if (![_touchEventGestureRecognizer respondsToSelector:@selector(activeTouchesByIdentifier)])
+        return;
+
+    NSMapTable<NSNumber *, UITouch *> *activeTouches = [_touchEventGestureRecognizer activeTouchesByIdentifier];
+    for (NSNumber *touchIdentifier in activeTouches) {
+        UITouch *touch = [activeTouches objectForKey:touchIdentifier];
+        if ([touch.gestureRecognizers containsObject:gestureRecognizer])
+            _page->cancelPointer([touchIdentifier unsignedIntValue], WebCore::roundedIntPoint([touch locationInView:self]));
+    }
+}
+#endif
+
 - (void)_webTouchEventsRecognized:(UIWebTouchEventsGestureRecognizer *)gestureRecognizer
 {
     if (!_page->isValid())
index 360d4ad..2a5e4c6 100644 (file)
 #include <WebCore/PlatformKeyboardEvent.h>
 #include <WebCore/PlatformMediaSessionManager.h>
 #include <WebCore/PluginDocument.h>
+#include <WebCore/PointerCaptureController.h>
 #include <WebCore/PrintContext.h>
 #include <WebCore/PromisedAttachmentInfo.h>
 #include <WebCore/Range.h>
@@ -2797,6 +2798,13 @@ void WebPage::touchEvent(const WebTouchEvent& touchEvent)
 }
 #endif
 
+#if ENABLE(POINTER_EVENTS)
+void WebPage::cancelPointer(int32_t pointerId, const WebCore::IntPoint& documentPoint)
+{
+    m_page->pointerCaptureController().cancelPointer(pointerId, documentPoint);
+}
+#endif
+
 #if ENABLE(MAC_GESTURE_EVENTS)
 static bool handleGestureEvent(const WebGestureEvent& event, Page* page)
 {
index 1be5906..22d05d5 100644 (file)
@@ -1240,6 +1240,10 @@ private:
     void touchEvent(const WebTouchEvent&);
 #endif
 
+#if ENABLE(POINTER_EVENTS)
+    void cancelPointer(int32_t, const WebCore::IntPoint&);
+#endif
+
 #if ENABLE(CONTEXT_MENUS)
     void contextMenuHidden() { m_isShowingContextMenu = false; }
     void contextMenuForKeyEvent();
index 47e9ce6..fc1ce33 100644 (file)
@@ -127,6 +127,10 @@ messages -> WebPage LegacyReceiver {
     TouchEvent(WebKit::WebTouchEvent event)
 #endif
 
+#if ENABLE(POINTER_EVENTS)
+    CancelPointer(int32_t pointerId, WebCore::IntPoint documentPoint)
+#endif
+
 #if ENABLE(INPUT_TYPE_COLOR)
     DidEndColorPicker()
     DidChooseColor(WebCore::Color color)