Title: [270312] trunk
Revision
270312
Author
[email protected]
Date
2020-12-01 10:01:31 -0800 (Tue, 01 Dec 2020)

Log Message

[WK1] Only the first wheel event in a gesture should be cancelable
https://bugs.webkit.org/show_bug.cgi?id=219384

Reviewed by Chris Dumez.

Source/WebCore:

Implement the logic described at <https://w3c.github.io/uievents/#cancelability-of-wheel-events>,
where only the first wheel event in a sequence is cancelable, and, if not canceled, then the
rest of the events in the sequence become non-cancelable.

This is done for the non-async scrolling code path (i.e. WebKitLegacy) by storing
a Optional<WheelScrollGestureState> on EventHandler, which is cleared when we receive
the "begin" event, set when we finish processing that event, then consulted for subsequent
move events.

Tests: fast/events/wheel/first-wheel-event-cancelable.html
       fast/events/wheel/wheel-events-become-non-cancelable.html

* page/EventHandler.h:
* page/ios/EventHandlerIOS.mm:
(WebCore::EventHandler::wheelEvent):
* page/mac/EventHandlerMac.mm:
(WebCore::EventHandler::wheelEvent):
(WebCore::EventHandler::wheelEventWasProcessedByMainThread):
* platform/PlatformWheelEvent.cpp:
(WebCore::operator<<):
* platform/PlatformWheelEvent.h:

LayoutTests:

Add a temporary failing result for WK2 until the async scrolling implementation lands.

* fast/events/wheel/first-wheel-event-cancelable-expected.txt: Added.
* fast/events/wheel/first-wheel-event-cancelable.html: Added.
* fast/events/wheel/wheel-events-become-non-cancelable-expected.txt: Added.
* fast/events/wheel/wheel-events-become-non-cancelable.html: Added.
* platform/mac-wk2/fast/events/wheel/wheel-events-become-non-cancelable-expected.txt: Added.
* platform/win/TestExpectations:

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (270311 => 270312)


--- trunk/LayoutTests/ChangeLog	2020-12-01 18:00:27 UTC (rev 270311)
+++ trunk/LayoutTests/ChangeLog	2020-12-01 18:01:31 UTC (rev 270312)
@@ -1,3 +1,19 @@
+2020-11-30  Simon Fraser  <[email protected]>
+
+        [WK1] Only the first wheel event in a gesture should be cancelable
+        https://bugs.webkit.org/show_bug.cgi?id=219384
+
+        Reviewed by Chris Dumez.
+
+        Add a temporary failing result for WK2 until the async scrolling implementation lands.
+
+        * fast/events/wheel/first-wheel-event-cancelable-expected.txt: Added.
+        * fast/events/wheel/first-wheel-event-cancelable.html: Added.
+        * fast/events/wheel/wheel-events-become-non-cancelable-expected.txt: Added.
+        * fast/events/wheel/wheel-events-become-non-cancelable.html: Added.
+        * platform/mac-wk2/fast/events/wheel/wheel-events-become-non-cancelable-expected.txt: Added.
+        * platform/win/TestExpectations:
+
 2020-12-01  Youenn Fablet  <[email protected]>
 
         REGRESSION(STP116): wpt.live/webrtc/RTCRtpTransceiver.https.html crashes on load

Added: trunk/LayoutTests/fast/events/wheel/first-wheel-event-cancelable-expected.txt (0 => 270312)


--- trunk/LayoutTests/fast/events/wheel/first-wheel-event-cancelable-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/fast/events/wheel/first-wheel-event-cancelable-expected.txt	2020-12-01 18:01:31 UTC (rev 270312)
@@ -0,0 +1,14 @@
+Tests preventDefault on the first event results in the rest of the events being cancelable
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS windowScrollEventCount is 0
+PASS firstWasCancelable is true
+PASS firstWasCanceled is true
+PASS secondWasCancelable is true
+PASS secondWasCanceled is true
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/fast/events/wheel/first-wheel-event-cancelable.html (0 => 270312)


--- trunk/LayoutTests/fast/events/wheel/first-wheel-event-cancelable.html	                        (rev 0)
+++ trunk/LayoutTests/fast/events/wheel/first-wheel-event-cancelable.html	2020-12-01 18:01:31 UTC (rev 270312)
@@ -0,0 +1,73 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <style>
+        body {
+            height: 5000px;
+        }
+        
+        #target {
+            width: 200px;
+            height: 200px;
+            background-color: silver;
+            margin: 20px;
+        }
+    </style>
+    <script src=""
+    <script src=""
+    <script>
+        var jsTestIsAsync = true;
+
+        let wheelEventCount = 0;
+        let windowScrollEventCount = 0;
+
+        let firstWasCancelable;
+        let firstWasCanceled;
+        let secondWasCancelable;
+        let secondWasCanceled;
+
+        async function testScroll()
+        {
+            await UIHelper.mouseWheelScrollAt(100, 100);
+            shouldBe('windowScrollEventCount', '0');
+
+            shouldBeTrue('firstWasCancelable');
+            shouldBeTrue('firstWasCanceled');
+
+            shouldBeTrue('secondWasCancelable');
+            shouldBeTrue('secondWasCanceled');
+
+            finishJSTest();
+        }
+
+        window.addEventListener('load', () => {
+            description('Tests preventDefault on the first event results in the rest of the events being cancelable')
+            let target = document.getElementById('target');
+
+            target.addEventListener('wheel', (event) => {
+                if (!wheelEventCount) {
+                    firstWasCancelable = event.cancelable;
+                    event.preventDefault();
+                    firstWasCanceled = event.defaultPrevented;
+                } else if (wheelEventCount == 1) {
+                    secondWasCancelable = event.cancelable;
+                    event.preventDefault();
+                    secondWasCanceled = event.defaultPrevented;
+                }
+                ++wheelEventCount;
+            });
+
+            window.addEventListener('scroll', () => {
+                ++windowScrollEventCount;
+            }, false);
+
+            setTimeout(testScroll, 0);
+        }, false);
+    </script>
+</head>
+<body>
+    <div id="target"></div>
+    <div id="console"></div>
+    <script src=""
+</body>
+</html>

Added: trunk/LayoutTests/fast/events/wheel/wheel-events-become-non-cancelable-expected.txt (0 => 270312)


--- trunk/LayoutTests/fast/events/wheel/wheel-events-become-non-cancelable-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/fast/events/wheel/wheel-events-become-non-cancelable-expected.txt	2020-12-01 18:01:31 UTC (rev 270312)
@@ -0,0 +1,11 @@
+Tests that events in the gesture are non-cancelable if preventDefault was not called on the first event
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS firstWasCancelable is true
+PASS becameNonCancelable is true
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/fast/events/wheel/wheel-events-become-non-cancelable.html (0 => 270312)


--- trunk/LayoutTests/fast/events/wheel/wheel-events-become-non-cancelable.html	                        (rev 0)
+++ trunk/LayoutTests/fast/events/wheel/wheel-events-become-non-cancelable.html	2020-12-01 18:01:31 UTC (rev 270312)
@@ -0,0 +1,81 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <style>
+        body {
+            height: 5000px;
+        }
+        
+        #target {
+            width: 200px;
+            height: 1000px;
+            background-color: silver;
+            margin: 20px;
+        }
+    </style>
+    <script src=""
+    <script src=""
+    <script>
+        var jsTestIsAsync = true;
+
+        let wheelEventCount = 0;
+
+        let firstWasCancelable;
+        let becameNonCancelable;
+
+        async function testScroll()
+        {
+            if (!eventSender) {
+                finishJSTest();
+                return;
+            }
+            eventSender.monitorWheelEvents();
+            eventSender.mouseMoveTo(100, 100);
+            eventSender.mouseScrollByWithWheelAndMomentumPhases(0, -1, "began", "none");
+            await UIHelper.renderingUpdate();
+            eventSender.mouseScrollByWithWheelAndMomentumPhases(0, -5, "changed", "none");
+            await UIHelper.renderingUpdate();
+            eventSender.mouseScrollByWithWheelAndMomentumPhases(0, -5, "changed", "none");
+            await UIHelper.renderingUpdate();
+            eventSender.mouseScrollByWithWheelAndMomentumPhases(0, -5, "changed", "none");
+            await UIHelper.renderingUpdate();
+            eventSender.mouseScrollByWithWheelAndMomentumPhases(0, -5, "changed", "none");
+            await UIHelper.renderingUpdate();
+            eventSender.mouseScrollByWithWheelAndMomentumPhases(0, 0, "ended", "none");
+            
+            await UIHelper.waitForScrollCompletion();
+
+            shouldBeTrue('firstWasCancelable');
+            shouldBeTrue('becameNonCancelable');
+
+            finishJSTest();
+        }
+
+        window.addEventListener('load', () => {
+            description('Tests that events in the gesture are non-cancelable if preventDefault was not called on the first event');
+            let target = document.getElementById('target');
+
+            target.addEventListener('wheel', (event) => {
+                if (wheelEventCount == 0) {
+                    firstWasCancelable = event.cancelable;
+                } else {
+                    // Wait for the first non-cancelable event and check that all subsequent events are non-cancelable.
+                    if (becameNonCancelable && event.cancelable)
+                        testFailed('Should not see cancelable event after non-cancelable event')
+
+                    if (!event.cancelable)
+                        becameNonCancelable = true;
+                }
+                ++wheelEventCount;
+            });
+
+            setTimeout(testScroll, 0);
+        }, false);
+    </script>
+</head>
+<body>
+    <div id="target"></div>
+    <div id="console"></div>
+    <script src=""
+</body>
+</html>

Added: trunk/LayoutTests/platform/mac-wk2/fast/events/wheel/wheel-events-become-non-cancelable-expected.txt (0 => 270312)


--- trunk/LayoutTests/platform/mac-wk2/fast/events/wheel/wheel-events-become-non-cancelable-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/platform/mac-wk2/fast/events/wheel/wheel-events-become-non-cancelable-expected.txt	2020-12-01 18:01:31 UTC (rev 270312)
@@ -0,0 +1,11 @@
+Tests that events in the gesture are non-cancelable if preventDefault was not called on the first event
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS firstWasCancelable is true
+FAIL becameNonCancelable should be true (of type boolean). Was undefined (of type undefined).
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Modified: trunk/LayoutTests/platform/win/TestExpectations (270311 => 270312)


--- trunk/LayoutTests/platform/win/TestExpectations	2020-12-01 18:00:27 UTC (rev 270311)
+++ trunk/LayoutTests/platform/win/TestExpectations	2020-12-01 18:01:31 UTC (rev 270312)
@@ -282,6 +282,8 @@
 fast/events/wheel/wheel-event-listeners-on-window-left-active.html [ Skip ]
 fast/events/wheel/wheel-event-listeners-on-window-made-passive.html [ Skip ]
 fast/events/wheel/wheel-event-in-passive-region-non-cancelable.html [ Skip ]
+fast/events/wheel/wheel-events-become-non-cancelable.html [ Skip ]
+fast/events/wheel/first-wheel-event-cancelable.html [ Skip ]
 
 scrollbars/scroll-rtl-or-bt-layer.html [ Timeout ]
 webkit.org/b/208559 fast/scrolling/arrow-key-scroll-in-rtl-document.html [ Skip ]

Modified: trunk/Source/WebCore/ChangeLog (270311 => 270312)


--- trunk/Source/WebCore/ChangeLog	2020-12-01 18:00:27 UTC (rev 270311)
+++ trunk/Source/WebCore/ChangeLog	2020-12-01 18:01:31 UTC (rev 270312)
@@ -1,3 +1,32 @@
+2020-11-30  Simon Fraser  <[email protected]>
+
+        [WK1] Only the first wheel event in a gesture should be cancelable
+        https://bugs.webkit.org/show_bug.cgi?id=219384
+
+        Reviewed by Chris Dumez.
+
+        Implement the logic described at <https://w3c.github.io/uievents/#cancelability-of-wheel-events>,
+        where only the first wheel event in a sequence is cancelable, and, if not canceled, then the
+        rest of the events in the sequence become non-cancelable.
+
+        This is done for the non-async scrolling code path (i.e. WebKitLegacy) by storing
+        a Optional<WheelScrollGestureState> on EventHandler, which is cleared when we receive
+        the "begin" event, set when we finish processing that event, then consulted for subsequent
+        move events.
+
+        Tests: fast/events/wheel/first-wheel-event-cancelable.html
+               fast/events/wheel/wheel-events-become-non-cancelable.html
+
+        * page/EventHandler.h:
+        * page/ios/EventHandlerIOS.mm:
+        (WebCore::EventHandler::wheelEvent):
+        * page/mac/EventHandlerMac.mm:
+        (WebCore::EventHandler::wheelEvent):
+        (WebCore::EventHandler::wheelEventWasProcessedByMainThread):
+        * platform/PlatformWheelEvent.cpp:
+        (WebCore::operator<<):
+        * platform/PlatformWheelEvent.h:
+
 2020-12-01  Youenn Fablet  <[email protected]>
 
         REGRESSION(STP116): wpt.live/webrtc/RTCRtpTransceiver.https.html crashes on load

Modified: trunk/Source/WebCore/page/EventHandler.h (270311 => 270312)


--- trunk/Source/WebCore/page/EventHandler.h	2020-12-01 18:00:27 UTC (rev 270311)
+++ trunk/Source/WebCore/page/EventHandler.h	2020-12-01 18:01:31 UTC (rev 270312)
@@ -97,6 +97,7 @@
 struct DragState;
 
 enum class WheelEventProcessingSteps : uint8_t;
+enum class WheelScrollGestureState : uint8_t;
 
 #if ENABLE(DRAG_SUPPORT)
 extern const int LinkDragHysteresis;
@@ -606,6 +607,7 @@
 #if PLATFORM(COCOA)
     NSView *m_mouseDownView { nullptr };
     bool m_sendingEventToSubview { false };
+    Optional<WheelScrollGestureState> m_wheelScrollGestureState;
 #endif
 
 #if PLATFORM(MAC)

Modified: trunk/Source/WebCore/page/ios/EventHandlerIOS.mm (270311 => 270312)


--- trunk/Source/WebCore/page/ios/EventHandlerIOS.mm	2020-12-01 18:00:27 UTC (rev 270311)
+++ trunk/Source/WebCore/page/ios/EventHandlerIOS.mm	2020-12-01 18:01:31 UTC (rev 270312)
@@ -107,7 +107,17 @@
 
     CurrentEventScope scope(event);
 
-    bool eventWasHandled = handleWheelEvent(PlatformEventFactory::createPlatformWheelEvent(event), { WheelEventProcessingSteps::MainThreadForScrolling, WheelEventProcessingSteps::MainThreadForBlockingDOMEventDispatch });
+    auto wheelEvent = PlatformEventFactory::createPlatformWheelEvent(event);
+    OptionSet<WheelEventProcessingSteps> processingSteps = { WheelEventProcessingSteps::MainThreadForScrolling, WheelEventProcessingSteps::MainThreadForBlockingDOMEventDispatch };
+
+    if (wheelEvent.isGestureStart())
+        m_wheelScrollGestureState = WTF::nullopt;
+    else if (wheelEvent.phase() == PlatformWheelEventPhase::Changed || wheelEvent.momentumPhase() == PlatformWheelEventPhase::Changed) {
+        if (m_wheelScrollGestureState && *m_wheelScrollGestureState == WheelScrollGestureState::NonBlocking)
+            processingSteps = { WheelEventProcessingSteps::MainThreadForScrolling, WheelEventProcessingSteps::MainThreadForNonBlockingDOMEventDispatch };
+    }
+
+    bool eventWasHandled = handleWheelEvent(wheelEvent, processingSteps);
     event.wasHandled = eventWasHandled;
     return eventWasHandled;
 }

Modified: trunk/Source/WebCore/page/mac/EventHandlerMac.mm (270311 => 270312)


--- trunk/Source/WebCore/page/mac/EventHandlerMac.mm	2020-12-01 18:00:27 UTC (rev 270311)
+++ trunk/Source/WebCore/page/mac/EventHandlerMac.mm	2020-12-01 18:01:31 UTC (rev 270312)
@@ -149,6 +149,13 @@
     CurrentEventScope scope(event, nil);
     auto wheelEvent = PlatformEventFactory::createPlatformWheelEvent(event, page->chrome().platformPageClient());
     OptionSet<WheelEventProcessingSteps> processingSteps = { WheelEventProcessingSteps::MainThreadForScrolling, WheelEventProcessingSteps::MainThreadForBlockingDOMEventDispatch };
+
+    if (wheelEvent.isGestureStart())
+        m_wheelScrollGestureState = WTF::nullopt;
+    else if (wheelEvent.phase() == PlatformWheelEventPhase::Changed || wheelEvent.momentumPhase() == PlatformWheelEventPhase::Changed) {
+        if (m_frame.settings().wheelEventGesturesBecomeNonBlocking() && m_wheelScrollGestureState && *m_wheelScrollGestureState == WheelScrollGestureState::NonBlocking)
+            processingSteps = { WheelEventProcessingSteps::MainThreadForScrolling, WheelEventProcessingSteps::MainThreadForNonBlockingDOMEventDispatch };
+    }
     return handleWheelEvent(wheelEvent, processingSteps);
 }
 
@@ -969,6 +976,9 @@
         if (scrollingCoordinator->coordinatesScrollingForFrameView(*view))
             scrollingCoordinator->wheelEventWasProcessedByMainThread(wheelEvent, eventHandling);
     }
+
+    if (wheelEvent.isGestureStart() && eventHandling.contains(EventHandling::DispatchedToDOM))
+        m_wheelScrollGestureState = eventHandling.contains(EventHandling::DefaultPrevented) ? WheelScrollGestureState::Blocking : WheelScrollGestureState::NonBlocking;
 #else
     UNUSED_PARAM(wheelEvent);
     UNUSED_PARAM(eventHandling);

Modified: trunk/Source/WebCore/platform/PlatformWheelEvent.cpp (270311 => 270312)


--- trunk/Source/WebCore/platform/PlatformWheelEvent.cpp	2020-12-01 18:00:27 UTC (rev 270311)
+++ trunk/Source/WebCore/platform/PlatformWheelEvent.cpp	2020-12-01 18:01:31 UTC (rev 270312)
@@ -80,4 +80,13 @@
     return ts;
 }
 
+TextStream& operator<<(TextStream& ts, WheelScrollGestureState state)
+{
+    switch (state) {
+    case WheelScrollGestureState::Blocking: ts << "blocking"; break;
+    case WheelScrollGestureState::NonBlocking: ts << "non-blocking"; break;
+    }
+    return ts;
+}
+
 } // namespace WebCore

Modified: trunk/Source/WebCore/platform/PlatformWheelEvent.h (270311 => 270312)


--- trunk/Source/WebCore/platform/PlatformWheelEvent.h	2020-12-01 18:00:27 UTC (rev 270311)
+++ trunk/Source/WebCore/platform/PlatformWheelEvent.h	2020-12-01 18:01:31 UTC (rev 270312)
@@ -43,6 +43,11 @@
     MainThreadForBlockingDOMEventDispatch       = 1 << 3,
 };
 
+enum class WheelScrollGestureState : uint8_t {
+    Blocking,
+    NonBlocking
+};
+
 // The ScrollByPixelWheelEvent is a fine-grained event that specifies the precise number of pixels to scroll.
 // It is sent directly by touch pads on macOS, or synthesized when platforms generate line-by-line scrolling events.
 //
@@ -267,5 +272,6 @@
 WEBCORE_EXPORT WTF::TextStream& operator<<(WTF::TextStream&, const PlatformWheelEvent&);
 WEBCORE_EXPORT WTF::TextStream& operator<<(WTF::TextStream&, WheelEventProcessingSteps);
 WEBCORE_EXPORT WTF::TextStream& operator<<(WTF::TextStream&, EventHandling);
+WEBCORE_EXPORT WTF::TextStream& operator<<(WTF::TextStream&, WheelScrollGestureState);
 
 } // namespace WebCore
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to