Implement capture for Pointer Events on iOS
authorgraouts@webkit.org <graouts@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 29 Jan 2019 03:15:02 +0000 (03:15 +0000)
committergraouts@webkit.org <graouts@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 29 Jan 2019 03:15:02 +0000 (03:15 +0000)
https://bugs.webkit.org/show_bug.cgi?id=193917
<rdar://problem/47605689>

Reviewed by Dean Jackson.

Source/WebCore:

We add a new PointerCaptureController object which gets notified upon dispatch of pointer events
to implement implicit pointer capture, dispatch the gotpointercapture and lostpointercaptiure events,
and implement the Element APIs for pointer capture: hasPointerCapture(), setPointerCapture() and
releasePointerCapture().

Tests: pointerevents/ios/pointer-events-implicit-capture-has-pointer-capture-in-pointer-down.html
       pointerevents/ios/pointer-events-implicit-capture-release-exception.html
       pointerevents/ios/pointer-events-implicit-capture-release.html
       pointerevents/ios/pointer-events-implicit-capture.html
       pointerevents/ios/pointer-events-set-pointer-capture-exceptions.html

* Sources.txt:
* WebCore.xcodeproj/project.pbxproj:
* dom/Element.cpp:
(WebCore::Element::setPointerCapture):
(WebCore::Element::releasePointerCapture):
(WebCore::Element::hasPointerCapture):
* dom/Element.h:
* dom/Element.idl:
* dom/EventNames.h:
* dom/PointerEvent.h:
* page/Page.cpp:
(WebCore::Page::Page):
* page/Page.h:
(WebCore::Page::pointerCaptureController const):
* page/PointerCaptureController.cpp: Added.
(WebCore::PointerCaptureController::PointerCaptureController):
(WebCore::PointerCaptureController::setPointerCapture):
(WebCore::PointerCaptureController::releasePointerCapture):
(WebCore::PointerCaptureController::hasPointerCapture):
(WebCore::PointerCaptureController::pointerLockWasApplied):
(WebCore::PointerCaptureController::touchEndedOrWasCancelledForIdentifier):
(WebCore::PointerCaptureController::pointerEventWillBeDispatched):
(WebCore::PointerCaptureController::pointerEventWasDispatched):
(WebCore::PointerCaptureController::processPendingPointerCapture):
* page/PointerCaptureController.h: Added.
* page/PointerLockController.cpp:
(WebCore::PointerLockController::requestPointerLock):
* page/PointerLockController.h:

LayoutTests:

New tests for implicit pointer capture and the Element APIs related to pointer capture.

* pointerevents/ios/pointer-events-implicit-capture-expected.txt: Added.
* pointerevents/ios/pointer-events-implicit-capture-has-pointer-capture-in-pointer-down-expected.txt: Added.
* pointerevents/ios/pointer-events-implicit-capture-has-pointer-capture-in-pointer-down.html: Added.
* pointerevents/ios/pointer-events-implicit-capture-release-exception-expected.txt: Added.
* pointerevents/ios/pointer-events-implicit-capture-release-exception.html: Added.
* pointerevents/ios/pointer-events-implicit-capture-release-expected.txt: Added.
* pointerevents/ios/pointer-events-implicit-capture-release.html: Added.
* pointerevents/ios/pointer-events-implicit-capture.html: Added.
* pointerevents/ios/pointer-events-set-pointer-capture-exceptions-expected.txt: Added.
* pointerevents/ios/pointer-events-set-pointer-capture-exceptions.html: Added.

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

25 files changed:
LayoutTests/ChangeLog
LayoutTests/pointerevents/ios/pointer-events-implicit-capture-expected.txt [new file with mode: 0644]
LayoutTests/pointerevents/ios/pointer-events-implicit-capture-has-pointer-capture-in-pointer-down-expected.txt [new file with mode: 0644]
LayoutTests/pointerevents/ios/pointer-events-implicit-capture-has-pointer-capture-in-pointer-down.html [new file with mode: 0644]
LayoutTests/pointerevents/ios/pointer-events-implicit-capture-release-exception-expected.txt [new file with mode: 0644]
LayoutTests/pointerevents/ios/pointer-events-implicit-capture-release-exception.html [new file with mode: 0644]
LayoutTests/pointerevents/ios/pointer-events-implicit-capture-release-expected.txt [new file with mode: 0644]
LayoutTests/pointerevents/ios/pointer-events-implicit-capture-release.html [new file with mode: 0644]
LayoutTests/pointerevents/ios/pointer-events-implicit-capture.html [new file with mode: 0644]
LayoutTests/pointerevents/ios/pointer-events-set-pointer-capture-exceptions-expected.txt [new file with mode: 0644]
LayoutTests/pointerevents/ios/pointer-events-set-pointer-capture-exceptions.html [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/Sources.txt
Source/WebCore/WebCore.xcodeproj/project.pbxproj
Source/WebCore/dom/Element.cpp
Source/WebCore/dom/Element.h
Source/WebCore/dom/Element.idl
Source/WebCore/dom/EventNames.h
Source/WebCore/dom/PointerEvent.h
Source/WebCore/page/Page.cpp
Source/WebCore/page/Page.h
Source/WebCore/page/PointerCaptureController.cpp [new file with mode: 0644]
Source/WebCore/page/PointerCaptureController.h [new file with mode: 0644]
Source/WebCore/page/PointerLockController.cpp
Source/WebCore/page/PointerLockController.h

index c38cf41..3fd69c1 100644 (file)
@@ -1,3 +1,24 @@
+2019-01-28  Antoine Quint  <graouts@apple.com>
+
+        Implement capture for Pointer Events on iOS
+        https://bugs.webkit.org/show_bug.cgi?id=193917
+        <rdar://problem/47605689>
+
+        Reviewed by Dean Jackson.
+
+        New tests for implicit pointer capture and the Element APIs related to pointer capture.
+
+        * pointerevents/ios/pointer-events-implicit-capture-expected.txt: Added.
+        * pointerevents/ios/pointer-events-implicit-capture-has-pointer-capture-in-pointer-down-expected.txt: Added.
+        * pointerevents/ios/pointer-events-implicit-capture-has-pointer-capture-in-pointer-down.html: Added.
+        * pointerevents/ios/pointer-events-implicit-capture-release-exception-expected.txt: Added.
+        * pointerevents/ios/pointer-events-implicit-capture-release-exception.html: Added.
+        * pointerevents/ios/pointer-events-implicit-capture-release-expected.txt: Added.
+        * pointerevents/ios/pointer-events-implicit-capture-release.html: Added.
+        * pointerevents/ios/pointer-events-implicit-capture.html: Added.
+        * pointerevents/ios/pointer-events-set-pointer-capture-exceptions-expected.txt: Added.
+        * pointerevents/ios/pointer-events-set-pointer-capture-exceptions.html: Added.
+
 2019-01-28  Dean Jackson  <dino@apple.com>
 
         Produce "pen" Pointer Events if using a stylus (e.g. Apple Pencil)
diff --git a/LayoutTests/pointerevents/ios/pointer-events-implicit-capture-expected.txt b/LayoutTests/pointerevents/ios/pointer-events-implicit-capture-expected.txt
new file mode 100644 (file)
index 0000000..8922b28
--- /dev/null
@@ -0,0 +1,3 @@
+
+PASS Pointer events for a direct manipulation device trigger 'gotpointercapture' and 'lostpointercapture' events due to implicit capture. 
+
diff --git a/LayoutTests/pointerevents/ios/pointer-events-implicit-capture-has-pointer-capture-in-pointer-down-expected.txt b/LayoutTests/pointerevents/ios/pointer-events-implicit-capture-has-pointer-capture-in-pointer-down-expected.txt
new file mode 100644 (file)
index 0000000..b42ff73
--- /dev/null
@@ -0,0 +1,3 @@
+
+PASS Calling hasPointerCapture() in the 'pointerdown' event handler returns true for direct manipulation devices. 
+
diff --git a/LayoutTests/pointerevents/ios/pointer-events-implicit-capture-has-pointer-capture-in-pointer-down.html b/LayoutTests/pointerevents/ios/pointer-events-implicit-capture-has-pointer-capture-in-pointer-down.html
new file mode 100644 (file)
index 0000000..3587115
--- /dev/null
@@ -0,0 +1,22 @@
+<!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((target, test) => {
+    target.addEventListener("pointerdown", event => assert_true(target.hasPointerCapture(event.pointerId)));
+    ui.beginTouches({ x: 50, y: 50 }).then(() => test.done());
+}, "Calling hasPointerCapture() in the 'pointerdown' event handler returns true for direct manipulation devices.");
+
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/LayoutTests/pointerevents/ios/pointer-events-implicit-capture-release-exception-expected.txt b/LayoutTests/pointerevents/ios/pointer-events-implicit-capture-release-exception-expected.txt
new file mode 100644 (file)
index 0000000..a4ccb78
--- /dev/null
@@ -0,0 +1,3 @@
+
+PASS Calling releasePointerCapture() in the 'pointerdown' event handler with a bogus pointer id raises an exception and does not alter pointer capture. 
+
diff --git a/LayoutTests/pointerevents/ios/pointer-events-implicit-capture-release-exception.html b/LayoutTests/pointerevents/ios/pointer-events-implicit-capture-release-exception.html
new file mode 100644 (file)
index 0000000..8c9d1ec
--- /dev/null
@@ -0,0 +1,26 @@
+<!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((target, test) => {
+    target.addEventListener("pointerdown", event => {
+        assert_true(target.hasPointerCapture(event.pointerId));
+        assert_throws("NotFoundError", () => target.releasePointerCapture(event.pointerId + 1));
+        assert_true(target.hasPointerCapture(event.pointerId));
+    });
+    ui.beginTouches({ x: 50, y: 50 }).then(() => test.done());
+}, "Calling releasePointerCapture() in the 'pointerdown' event handler with a bogus pointer id raises an exception and does not alter pointer capture.");
+
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/LayoutTests/pointerevents/ios/pointer-events-implicit-capture-release-expected.txt b/LayoutTests/pointerevents/ios/pointer-events-implicit-capture-release-expected.txt
new file mode 100644 (file)
index 0000000..4641447
--- /dev/null
@@ -0,0 +1,3 @@
+
+PASS Calling releasePointerCapture() in the 'pointerdown' event handler makes hasPointerCapture() return false. 
+
diff --git a/LayoutTests/pointerevents/ios/pointer-events-implicit-capture-release.html b/LayoutTests/pointerevents/ios/pointer-events-implicit-capture-release.html
new file mode 100644 (file)
index 0000000..0f3b673
--- /dev/null
@@ -0,0 +1,26 @@
+<!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((target, test) => {
+    target.addEventListener("pointerdown", event => {
+        assert_true(target.hasPointerCapture(event.pointerId));
+        target.releasePointerCapture(event.pointerId);
+        assert_false(target.hasPointerCapture(event.pointerId));
+    });
+    ui.beginTouches({ x: 50, y: 50 }).then(() => test.done());
+}, "Calling releasePointerCapture() in the 'pointerdown' event handler makes hasPointerCapture() return false.");
+
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/LayoutTests/pointerevents/ios/pointer-events-implicit-capture.html b/LayoutTests/pointerevents/ios/pointer-events-implicit-capture.html
new file mode 100644 (file)
index 0000000..c1d08dc
--- /dev/null
@@ -0,0 +1,46 @@
+<!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((target, test) => {
+    const eventTracker = new EventTracker(target, ["pointerdown", "gotpointercapture", "pointermove", "pointerup", "lostpointercapture"]);
+
+    const one = ui.finger();
+    const two = ui.finger();
+    ui.sequence([
+        one.begin({ x: 10, y: 10 }),
+        two.begin({ x: 50, y: 50 }),
+        one.move({ x: 30, y: 30 }),
+        two.move({ x: 70, y: 70 }),
+        one.end(),
+        two.end()
+    ]).then(() => {
+        eventTracker.assertMatchesEvents([
+            { id: 1, type: "pointerdown" },
+            { id: 1, type: "gotpointercapture", isPrimary: true },
+            { id: 2, type: "pointerdown" },
+            { id: 2, type: "gotpointercapture", isPrimary: false },
+            { id: 1, type: "pointermove" },
+            { id: 2, type: "pointermove" },
+            { id: 1, type: "pointerup" },
+            { id: 1, type: "lostpointercapture", isPrimary: false },
+            { id: 2, type: "pointerup" },
+            { id: 2, type: "lostpointercapture", isPrimary: false }
+        ]);
+        test.done();
+    });
+}, "Pointer events for a direct manipulation device trigger 'gotpointercapture' and 'lostpointercapture' events due to implicit capture.");
+
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/LayoutTests/pointerevents/ios/pointer-events-set-pointer-capture-exceptions-expected.txt b/LayoutTests/pointerevents/ios/pointer-events-set-pointer-capture-exceptions-expected.txt
new file mode 100644 (file)
index 0000000..a53ec75
--- /dev/null
@@ -0,0 +1,3 @@
+
+PASS The setPointerCapture() method can throw. 
+
diff --git a/LayoutTests/pointerevents/ios/pointer-events-set-pointer-capture-exceptions.html b/LayoutTests/pointerevents/ios/pointer-events-set-pointer-capture-exceptions.html
new file mode 100644 (file)
index 0000000..e397491
--- /dev/null
@@ -0,0 +1,27 @@
+<!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((target, test) => {
+    assert_throws("NotFoundError", () => document.body.setPointerCapture(10), "Calling setPointerCapture() with an invalid pointer id throws a NotFoundError exception.");
+
+    target.addEventListener("pointerdown", event => {
+        assert_true(target.hasPointerCapture(event.pointerId));
+        assert_throws("InvalidStateError", () => document.createElement("div").setPointerCapture(event.pointerId), "Calling setPointerCapture() with a valid pointer id on a disconnected element throws an InvalidStateError exception.");
+    });
+    ui.beginTouches({ x: 50, y: 50 }).then(() => test.done());
+}, "The setPointerCapture() method can throw.");
+
+</script>
+</body>
+</html>
\ No newline at end of file
index 2d3186b..c7f7179 100644 (file)
@@ -1,3 +1,51 @@
+2019-01-28  Antoine Quint  <graouts@apple.com>
+
+        Implement capture for Pointer Events on iOS
+        https://bugs.webkit.org/show_bug.cgi?id=193917
+        <rdar://problem/47605689>
+
+        Reviewed by Dean Jackson.
+
+        We add a new PointerCaptureController object which gets notified upon dispatch of pointer events
+        to implement implicit pointer capture, dispatch the gotpointercapture and lostpointercaptiure events,
+        and implement the Element APIs for pointer capture: hasPointerCapture(), setPointerCapture() and
+        releasePointerCapture().
+
+        Tests: pointerevents/ios/pointer-events-implicit-capture-has-pointer-capture-in-pointer-down.html
+               pointerevents/ios/pointer-events-implicit-capture-release-exception.html
+               pointerevents/ios/pointer-events-implicit-capture-release.html
+               pointerevents/ios/pointer-events-implicit-capture.html
+               pointerevents/ios/pointer-events-set-pointer-capture-exceptions.html
+
+        * Sources.txt:
+        * WebCore.xcodeproj/project.pbxproj:
+        * dom/Element.cpp:
+        (WebCore::Element::setPointerCapture):
+        (WebCore::Element::releasePointerCapture):
+        (WebCore::Element::hasPointerCapture):
+        * dom/Element.h:
+        * dom/Element.idl:
+        * dom/EventNames.h:
+        * dom/PointerEvent.h:
+        * page/Page.cpp:
+        (WebCore::Page::Page):
+        * page/Page.h:
+        (WebCore::Page::pointerCaptureController const):
+        * page/PointerCaptureController.cpp: Added.
+        (WebCore::PointerCaptureController::PointerCaptureController):
+        (WebCore::PointerCaptureController::setPointerCapture):
+        (WebCore::PointerCaptureController::releasePointerCapture):
+        (WebCore::PointerCaptureController::hasPointerCapture):
+        (WebCore::PointerCaptureController::pointerLockWasApplied):
+        (WebCore::PointerCaptureController::touchEndedOrWasCancelledForIdentifier):
+        (WebCore::PointerCaptureController::pointerEventWillBeDispatched):
+        (WebCore::PointerCaptureController::pointerEventWasDispatched):
+        (WebCore::PointerCaptureController::processPendingPointerCapture):
+        * page/PointerCaptureController.h: Added.
+        * page/PointerLockController.cpp:
+        (WebCore::PointerLockController::requestPointerLock):
+        * page/PointerLockController.h:
+
 2019-01-28  Andy Estes  <aestes@apple.com>
 
         [watchOS] Enable Parental Controls content filtering
index de5421f..cb5143d 100644 (file)
@@ -1523,6 +1523,7 @@ page/PerformanceResourceTiming.cpp
 page/PerformanceServerTiming.cpp
 page/PerformanceTiming.cpp
 page/PerformanceUserTiming.cpp
+page/PointerCaptureController.cpp
 page/PointerLockController.cpp
 page/PrintContext.cpp
 page/ProcessWarming.cpp
index 9cc3bae..4a22387 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 */; };
                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, ); }; };
                71B0460A1DD3C2EE00EE19CF /* status-support.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = "status-support.js"; sourceTree = "<group>"; };
                71B28424203CEC0B0036AA5D /* JSCSSAnimation.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSCSSAnimation.cpp; sourceTree = "<group>"; };
                71B28426203CEC0D0036AA5D /* JSCSSAnimation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSCSSAnimation.h; sourceTree = "<group>"; };
+               71B5AB2421F1D9E200376E5C /* PointerCaptureController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PointerCaptureController.h; sourceTree = "<group>"; };
+               71B5AB2521F1D9E300376E5C /* PointerCaptureController.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PointerCaptureController.cpp; sourceTree = "<group>"; };
                71C29E2E203CE76B008F36D2 /* CSSAnimation.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CSSAnimation.cpp; sourceTree = "<group>"; };
                71C29E30203CE76B008F36D2 /* CSSAnimation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSSAnimation.h; sourceTree = "<group>"; };
                71C29E31203CE76C008F36D2 /* CSSAnimation.idl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = CSSAnimation.idl; sourceTree = "<group>"; };
                                A554B5F01E383936001D4E03 /* PerformanceUserTiming.cpp */,
                                A554B5F11E383936001D4E03 /* PerformanceUserTiming.h */,
                                31D591B116697A6C00E6BF02 /* PlugInClient.h */,
+                               71B5AB2521F1D9E300376E5C /* PointerCaptureController.cpp */,
+                               71B5AB2421F1D9E200376E5C /* PointerCaptureController.h */,
                                5CFC434E192406A900A0D3B5 /* PointerLockController.cpp */,
                                5CFC434F192406A900A0D3B5 /* PointerLockController.h */,
                                3772B09516535856000A49CA /* PopupOpeningObserver.h */,
                                26E944DD1AC4B4EA007B85B5 /* Term.h in Headers */,
                                6550B6A6099DF0270090D781 /* Text.h in Headers */,
                                93309E17099E64920056E581 /* TextAffinity.h in Headers */,
+                               71B5AB2621F1D9F400376E5C /* PointerCaptureController.h in Headers */,
                                CE7B2DB51586ABAD0098B3FA /* TextAlternativeWithRange.h in Headers */,
                                0F54DCE61881051D003EEDBB /* TextAutoSizing.h in Headers */,
                                B2C3DA340D006C1D00EF6F26 /* TextBoundaries.h in Headers */,
index 1664585..c3c23b1 100644 (file)
@@ -78,6 +78,7 @@
 #include "MutationRecord.h"
 #include "NodeRenderStyle.h"
 #include "PlatformWheelEvent.h"
+#include "PointerCaptureController.h"
 #include "PointerLockController.h"
 #include "RenderFragmentContainer.h"
 #include "RenderLayer.h"
@@ -3434,6 +3435,29 @@ void Element::setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(boo
 }
 #endif
 
+#if ENABLE(POINTER_EVENTS)
+ExceptionOr<void> Element::setPointerCapture(int32_t pointerId)
+{
+    if (document().page())
+        return document().page()->pointerCaptureController().setPointerCapture(this, pointerId);
+    return { };
+}
+
+ExceptionOr<void> Element::releasePointerCapture(int32_t pointerId)
+{
+    if (document().page())
+        return document().page()->pointerCaptureController().releasePointerCapture(this, pointerId);
+    return { };
+}
+
+bool Element::hasPointerCapture(int32_t pointerId)
+{
+    if (document().page())
+        return document().page()->pointerCaptureController().hasPointerCapture(this, pointerId);
+    return false;
+}
+#endif
+
 #if ENABLE(POINTER_LOCK)
 void Element::requestPointerLock()
 {
index 6ae1618..a2599ad 100644 (file)
@@ -497,6 +497,12 @@ public:
     WEBCORE_EXPORT virtual void webkitRequestFullscreen();
 #endif
 
+#if ENABLE(POINTER_EVENTS)
+    ExceptionOr<void> setPointerCapture(int32_t);
+    ExceptionOr<void> releasePointerCapture(int32_t);
+    bool hasPointerCapture(int32_t);
+#endif
+
 #if ENABLE(POINTER_LOCK)
     WEBCORE_EXPORT void requestPointerLock();
 #endif
index 77894f7..36d36ac 100644 (file)
     [Conditional=FULLSCREEN_API, EnabledBySetting=FullScreen, ImplementedAs=webkitRequestFullscreen] void webkitRequestFullScreen(); // Prefixed Mozilla version.
     [Conditional=FULLSCREEN_API, EnabledBySetting=FullScreen] void webkitRequestFullscreen(); // Prefixed W3C version.
 
+    // Extensions from Pointer Events API (https://w3c.github.io/pointerevents/#extensions-to-the-element-interface).
+    [Conditional=POINTER_EVENTS, EnabledAtRuntime=PointerEvents, MayThrowException] void setPointerCapture(long pointerId);
+    [Conditional=POINTER_EVENTS, EnabledAtRuntime=PointerEvents, MayThrowException] void releasePointerCapture(long pointerId);
+    [Conditional=POINTER_EVENTS, EnabledAtRuntime=PointerEvents] boolean hasPointerCapture(long pointerId);
+
     // Extensions from Pointer Lock API (https://w3c.github.io/pointerlock/#extensions-to-the-element-interface).
     [Conditional=POINTER_LOCK] void requestPointerLock();
 
index 3f88b75..463ed43 100644 (file)
@@ -140,6 +140,7 @@ namespace WebCore {
     macro(gesturestart) \
     macro(gesturetap) \
     macro(gesturetapdown) \
+    macro(gotpointercapture) \
     macro(hashchange) \
     macro(icecandidate) \
     macro(iceconnectionstatechange) \
@@ -162,6 +163,7 @@ namespace WebCore {
     macro(loadingdone) \
     macro(loadingerror) \
     macro(loadstart) \
+    macro(lostpointercapture) \
     macro(mark) \
     macro(merchantvalidation) \
     macro(message) \
index bdaee5e..25059af 100644 (file)
@@ -39,7 +39,7 @@ namespace WebCore {
 class PointerEvent final : public MouseEvent {
 public:
     struct Init : MouseEventInit {
-        long pointerId { 0 };
+        int32_t pointerId { 0 };
         double width { 1 };
         double height { 1 };
         float pressure { 0 };
@@ -56,6 +56,18 @@ public:
         return adoptRef(*new PointerEvent(type, WTFMove(initializer)));
     }
 
+#if ENABLE(POINTER_EVENTS)
+    static Ref<PointerEvent> createForPointerCapture(const AtomicString& type, const PointerEvent& pointerEvent)
+    {
+        Init initializer;
+        initializer.bubbles = true;
+        initializer.pointerId = pointerEvent.pointerId();
+        initializer.isPrimary = pointerEvent.isPrimary();
+        initializer.pointerType = pointerEvent.pointerType();
+        return adoptRef(*new PointerEvent(type, WTFMove(initializer)));
+    }
+#endif
+
     static Ref<PointerEvent> createForBindings()
     {
         return adoptRef(*new PointerEvent);
@@ -67,7 +79,7 @@ public:
 
     virtual ~PointerEvent();
 
-    long pointerId() const { return m_pointerId; }
+    int32_t pointerId() const { return m_pointerId; }
     double width() const { return m_width; }
     double height() const { return m_height; }
     float pressure() const { return m_pressure; }
@@ -89,7 +101,7 @@ private:
     PointerEvent(const AtomicString& type, const PlatformTouchEvent&, IsCancelable isCancelable, unsigned touchIndex, bool isPrimary, Ref<WindowProxy>&&);
 #endif
 
-    long m_pointerId { 0 };
+    int32_t m_pointerId { 0 };
     double m_width { 1 };
     double m_height { 1 };
     float m_pressure { 0 };
index 26a2581..fd66607 100644 (file)
@@ -86,6 +86,7 @@
 #include "PluginData.h"
 #include "PluginInfoProvider.h"
 #include "PluginViewBase.h"
+#include "PointerCaptureController.h"
 #include "PointerLockController.h"
 #include "ProgressTracker.h"
 #include "PublicSuffix.h"
@@ -214,6 +215,9 @@ Page::Page(PageConfiguration&& pageConfiguration)
 #endif
     , 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>())
+#endif
 #if ENABLE(POINTER_LOCK)
     , m_pointerLockController(std::make_unique<PointerLockController>(*this))
 #endif
index 7f7b370..335d6a0 100644 (file)
@@ -122,6 +122,7 @@ class PlugInClient;
 class PluginData;
 class PluginInfoProvider;
 class PluginViewBase;
+class PointerCaptureController;
 class PointerLockController;
 class ProgressTracker;
 class ProgressTrackerClient;
@@ -244,6 +245,9 @@ public:
 #endif
     UserInputBridge& userInputBridge() const { return *m_userInputBridge; }
     InspectorController& inspectorController() const { return *m_inspectorController; }
+#if ENABLE(POINTER_EVENTS)
+    PointerCaptureController& pointerCaptureController() const { return *m_pointerCaptureController; }
+#endif
 #if ENABLE(POINTER_LOCK)
     PointerLockController& pointerLockController() const { return *m_pointerLockController; }
 #endif
@@ -747,6 +751,9 @@ private:
 #endif
     const std::unique_ptr<UserInputBridge> m_userInputBridge;
     const std::unique_ptr<InspectorController> m_inspectorController;
+#if ENABLE(POINTER_EVENTS)
+    const std::unique_ptr<PointerCaptureController> m_pointerCaptureController;
+#endif
 #if ENABLE(POINTER_LOCK)
     const std::unique_ptr<PointerLockController> m_pointerLockController;
 #endif
diff --git a/Source/WebCore/page/PointerCaptureController.cpp b/Source/WebCore/page/PointerCaptureController.cpp
new file mode 100644 (file)
index 0000000..65d813d
--- /dev/null
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1.  Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ * 2.  Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "PointerCaptureController.h"
+
+#if ENABLE(POINTER_EVENTS)
+
+#include "Document.h"
+#include "Element.h"
+#include "EventNames.h"
+#include "EventTarget.h"
+#include "Page.h"
+#include "PointerEvent.h"
+
+namespace WebCore {
+
+PointerCaptureController::PointerCaptureController() = default;
+
+ExceptionOr<void> PointerCaptureController::setPointerCapture(Element* capturingTarget, int32_t pointerId)
+{
+    // https://w3c.github.io/pointerevents/#setting-pointer-capture
+
+    // 1. If the pointerId provided as the method's argument does not match any of the active pointers, then throw a DOMException with the name NotFoundError.
+    auto iterator = m_activePointerIdsToCapturingData.find(pointerId);
+    if (iterator == m_activePointerIdsToCapturingData.end())
+        return Exception { NotFoundError };
+
+    // 2. If the Element on which this method is invoked is not connected, throw an exception with the name InvalidStateError.
+    if (!capturingTarget->isConnected())
+        return Exception { InvalidStateError };
+
+#if ENABLE(POINTER_LOCK)
+    // 3. If this method is invoked while the document has a locked element, throw an exception with the name InvalidStateError.
+    if (auto* page = capturingTarget->document().page()) {
+        if (page->pointerLockController().isLocked())
+            return Exception { InvalidStateError };
+    }
+#endif
+
+    // 4. If the pointer is not in the active buttons state, then terminate these steps.
+    // FIXME: implement when we support mouse events.
+
+    // 5. For the specified pointerId, set the pending pointer capture target override to the Element on which this method was invoked.
+    iterator->value.pendingTargetOverride = capturingTarget;
+
+    return { };
+}
+
+ExceptionOr<void> PointerCaptureController::releasePointerCapture(Element* capturingTarget, int32_t pointerId, ImplicitCapture implicit)
+{
+    // https://w3c.github.io/pointerevents/#releasing-pointer-capture
+
+    // Pointer capture is released on an element explicitly by calling the element.releasePointerCapture(pointerId) method.
+    // When this method is called, a user agent MUST run the following steps:
+
+    // 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())
+        return Exception { NotFoundError };
+
+    // 2. If hasPointerCapture is false for the Element with the specified pointerId, then terminate these steps.
+    if (!hasPointerCapture(capturingTarget, pointerId))
+        return { };
+
+    // 3. For the specified pointerId, clear the pending pointer capture target override, if set.
+    iterator->value.pendingTargetOverride = nullptr;
+
+    return { };
+}
+
+bool PointerCaptureController::hasPointerCapture(Element* capturingTarget, int32_t pointerId)
+{
+    // https://w3c.github.io/pointerevents/#dom-element-haspointercapture
+
+    // Indicates whether the element on which this method is invoked has pointer capture for the pointer identified by the argument pointerId.
+    // In particular, returns true if the pending pointer capture target override for pointerId is set to the element on which this method is
+    // invoked, and false otherwise.
+
+    auto iterator = m_activePointerIdsToCapturingData.find(pointerId);
+    if (iterator == m_activePointerIdsToCapturingData.end())
+        return false;
+
+    auto& capturingData = iterator->value;
+    return capturingData.pendingTargetOverride == capturingTarget || capturingData.targetOverride == capturingTarget;
+}
+
+void PointerCaptureController::pointerLockWasApplied()
+{
+    // https://w3c.github.io/pointerevents/#implicit-release-of-pointer-capture
+
+    // When a pointer lock is successfully applied on an element, a user agent MUST run the steps as if the releasePointerCapture()
+    // method has been called if any element is set to be captured or pending to be captured.
+    for (auto& capturingData : m_activePointerIdsToCapturingData.values()) {
+        capturingData.pendingTargetOverride = nullptr;
+        capturingData.targetOverride = nullptr;
+    }
+}
+
+void PointerCaptureController::touchEndedOrWasCancelledForIdentifier(int32_t pointerId)
+{
+    m_activePointerIdsToCapturingData.remove(pointerId);
+}
+
+void PointerCaptureController::pointerEventWillBeDispatched(const PointerEvent& event, EventTarget* target)
+{
+    // https://w3c.github.io/pointerevents/#implicit-pointer-capture
+
+    // Some input devices (such as touchscreens) implement a "direct manipulation" metaphor where a pointer is intended to act primarily on the UI
+    // element it became active upon (providing a physical illusion of direct contact, instead of indirect contact via a cursor that conceptually
+    // floats above the UI). Such devices are identified by the InputDeviceCapabilities.pointerMovementScrolls property and should have "implicit
+    // pointer capture" behavior as follows.
+
+    // Direct manipulation devices should behave exactly as if setPointerCapture was called on the target element just before the invocation of any
+    // pointerdown listeners. The hasPointerCapture API may be used (eg. within any pointerdown listener) to determine whether this has occurred. If
+    // releasePointerCapture is not called for the pointer before the next pointer event is fired, then a gotpointercapture event will be dispatched
+    // to the target (as normal) indicating that capture is active.
+
+    if (!is<Element>(target) || event.type() != eventNames().pointerdownEvent)
+        return;
+
+    auto pointerId = event.pointerId();
+    m_activePointerIdsToCapturingData.set(pointerId, CapturingData { });
+    setPointerCapture(downcast<Element>(target), pointerId);
+}
+
+void PointerCaptureController::pointerEventWasDispatched(const PointerEvent& event)
+{
+    // https://w3c.github.io/pointerevents/#implicit-release-of-pointer-capture
+
+    auto iterator = m_activePointerIdsToCapturingData.find(event.pointerId());
+    if (iterator != m_activePointerIdsToCapturingData.end()) {
+        auto& capturingData = iterator->value;
+        // 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.
+        if (event.type() == eventNames().pointerupEvent)
+            capturingData.pendingTargetOverride = nullptr;
+        
+        // When the pointer capture target override is no longer connected, the pending pointer capture target override and pointer
+        // capture target override nodes SHOULD be cleared and also a PointerEvent named lostpointercapture corresponding to the captured
+        // pointer SHOULD be fired at the document.
+        if (capturingData.targetOverride && !capturingData.targetOverride->isConnected()) {
+            capturingData.pendingTargetOverride = nullptr;
+            capturingData.targetOverride = nullptr;
+        }
+    }
+
+    processPendingPointerCapture(event);
+}
+
+void PointerCaptureController::processPendingPointerCapture(const PointerEvent& event)
+{
+    // https://w3c.github.io/pointerevents/#process-pending-pointer-capture
+
+    auto iterator = m_activePointerIdsToCapturingData.find(event.pointerId());
+    if (iterator == m_activePointerIdsToCapturingData.end())
+        return;
+
+    auto& capturingData = iterator->value;
+
+    // 1. If the pointer capture target override for this pointer is set and is not equal to the pending pointer capture target override,
+    // then fire a pointer event named lostpointercapture at the pointer capture target override node.
+    if (capturingData.targetOverride && capturingData.targetOverride != capturingData.pendingTargetOverride)
+        capturingData.targetOverride->dispatchEvent(PointerEvent::createForPointerCapture(eventNames().lostpointercaptureEvent, event));
+
+    // 2. If the pending pointer capture target override for this pointer is set and is not equal to the pointer capture target override,
+    // then fire a pointer event named gotpointercapture at the pending pointer capture target override.
+    if (capturingData.pendingTargetOverride && capturingData.targetOverride != capturingData.pendingTargetOverride)
+        capturingData.pendingTargetOverride->dispatchEvent(PointerEvent::createForPointerCapture(eventNames().gotpointercaptureEvent, event));
+
+    // 3. Set the pointer capture target override to the pending pointer capture target override, if set. Otherwise, clear the pointer
+    // capture target override.
+    capturingData.targetOverride = capturingData.pendingTargetOverride;
+}
+
+} // namespace WebCore
+
+#endif // ENABLE(POINTER_EVENTS)
diff --git a/Source/WebCore/page/PointerCaptureController.h b/Source/WebCore/page/PointerCaptureController.h
new file mode 100644 (file)
index 0000000..8e7d220
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1.  Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ * 2.  Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#if ENABLE(POINTER_EVENTS)
+
+#include <wtf/HashMap.h>
+
+namespace WebCore {
+
+class Element;
+class EventTarget;
+class PointerEvent;
+
+class PointerCaptureController {
+    WTF_MAKE_NONCOPYABLE(PointerCaptureController);
+    WTF_MAKE_FAST_ALLOCATED;
+public:
+    explicit PointerCaptureController();
+
+    enum class ImplicitCapture : uint8_t { Yes, No };
+
+    ExceptionOr<void> setPointerCapture(Element*, int32_t);
+    ExceptionOr<void> releasePointerCapture(Element*, int32_t, ImplicitCapture implicit = ImplicitCapture::No);
+    bool hasPointerCapture(Element*, int32_t);
+
+    void pointerLockWasApplied();
+
+    void touchEndedOrWasCancelledForIdentifier(int32_t);
+    void pointerEventWillBeDispatched(const PointerEvent&, EventTarget*);
+    void pointerEventWasDispatched(const PointerEvent&);
+
+private:
+    struct CapturingData {
+        Element* pendingTargetOverride;
+        Element* targetOverride;
+    };
+
+    void processPendingPointerCapture(const PointerEvent&);
+    HashMap<int32_t, CapturingData> m_activePointerIdsToCapturingData;
+};
+
+} // namespace WebCore
+
+#endif // ENABLE(POINTER_EVENTS)
index cfae41b..6580f23 100644 (file)
@@ -71,6 +71,9 @@ void PointerLockController::requestPointerLock(Element* target)
         }
         m_element = target;
         enqueueEvent(eventNames().pointerlockchangeEvent, target);
+#if ENABLE(POINTER_EVENTS)
+        m_page.pointerCaptureController().pointerLockWasApplied();
+#endif
     } else {
         m_lockPending = true;
         m_element = target;
index 49097ae..5077465 100644 (file)
@@ -27,6 +27,7 @@
 #if ENABLE(POINTER_LOCK)
 
 #include <wtf/RefPtr.h>
+#include <wtf/WeakPtr.h>
 #include <wtf/text/AtomicString.h>
 
 namespace WebCore {