| Differences between
and this patch
- a/Source/WebCore/ChangeLog +29 lines
Lines 1-3 a/Source/WebCore/ChangeLog_sec1
1
2020-11-30  Simon Fraser  <simon.fraser@apple.com>
2
3
        [WK1] Only the first wheel event in a gesture should be cancelable
4
        https://bugs.webkit.org/show_bug.cgi?id=219384
5
6
        Reviewed by NOBODY (OOPS!).
7
8
        Implement the logic described at <https://w3c.github.io/uievents/#cancelability-of-wheel-events>,
9
        where only the first wheel event in a sequence is cancelable, and, if not canceled, then the
10
        rest of the events in the sequence become non-cancelable.
11
12
        This is done for the non-async scrolling code path (i.e. WebKitLegacy) by storing
13
        a Optional<WheelScrollGestureState> on EventHandler, which is cleared when we receive
14
        the "begin" event, set when we finish processing that event, then consulted for subsequent
15
        move events.
16
17
        Tests: fast/events/wheel/first-wheel-event-cancelable.html
18
               fast/events/wheel/wheel-events-become-non-cancelable.html
19
20
        * page/EventHandler.h:
21
        * page/ios/EventHandlerIOS.mm:
22
        (WebCore::EventHandler::wheelEvent):
23
        * page/mac/EventHandlerMac.mm:
24
        (WebCore::EventHandler::wheelEvent):
25
        (WebCore::EventHandler::wheelEventWasProcessedByMainThread):
26
        * platform/PlatformWheelEvent.cpp:
27
        (WebCore::operator<<):
28
        * platform/PlatformWheelEvent.h:
29
1
2020-11-30  Andres Gonzalez  <andresg_22@apple.com>
30
2020-11-30  Andres Gonzalez  <andresg_22@apple.com>
2
31
3
        [WebAccessibilityObjectWrapper doAXAttributedStringForRange] needs to run on the main thread.
32
        [WebAccessibilityObjectWrapper doAXAttributedStringForRange] needs to run on the main thread.
- a/Source/WebCore/page/EventHandler.h +2 lines
Lines 97-102 class Widget; a/Source/WebCore/page/EventHandler.h_sec1
97
struct DragState;
97
struct DragState;
98
98
99
enum class WheelEventProcessingSteps : uint8_t;
99
enum class WheelEventProcessingSteps : uint8_t;
100
enum class WheelScrollGestureState : uint8_t;
100
101
101
#if ENABLE(DRAG_SUPPORT)
102
#if ENABLE(DRAG_SUPPORT)
102
extern const int LinkDragHysteresis;
103
extern const int LinkDragHysteresis;
Lines 610-615 private: a/Source/WebCore/page/EventHandler.h_sec2
610
611
611
#if PLATFORM(MAC)
612
#if PLATFORM(MAC)
612
    int m_activationEventNumber { -1 };
613
    int m_activationEventNumber { -1 };
614
    Optional<WheelScrollGestureState> m_wheelScrollGestureState;
613
#endif
615
#endif
614
616
615
#if ENABLE(TOUCH_EVENTS) && !ENABLE(IOS_TOUCH_EVENTS)
617
#if ENABLE(TOUCH_EVENTS) && !ENABLE(IOS_TOUCH_EVENTS)
- a/Source/WebCore/page/ios/EventHandlerIOS.mm -1 / +11 lines
Lines 107-113 bool EventHandler::wheelEvent(WebEvent *event) a/Source/WebCore/page/ios/EventHandlerIOS.mm_sec1
107
107
108
    CurrentEventScope scope(event);
108
    CurrentEventScope scope(event);
109
109
110
    bool eventWasHandled = handleWheelEvent(PlatformEventFactory::createPlatformWheelEvent(event), { WheelEventProcessingSteps::MainThreadForScrolling, WheelEventProcessingSteps::MainThreadForBlockingDOMEventDispatch });
110
    auto wheelEvent = PlatformEventFactory::createPlatformWheelEvent(event, page->chrome().platformPageClient());
111
    OptionSet<WheelEventProcessingSteps> processingSteps = { WheelEventProcessingSteps::MainThreadForScrolling, WheelEventProcessingSteps::MainThreadForBlockingDOMEventDispatch };
112
113
    if (wheelEvent.isGestureStart())
114
        m_wheelScrollGestureState = WTF::nullopt;
115
    else if (wheelEvent.phase() == PlatformWheelEventPhase::Changed || wheelEvent.momentumPhase() == PlatformWheelEventPhase::Changed) {
116
        if (m_wheelScrollGestureState && *m_wheelScrollGestureState == WheelScrollGestureState::NonBlocking)
117
            processingSteps = { WheelEventProcessingSteps::MainThreadForScrolling, WheelEventProcessingSteps::MainThreadForNonBlockingDOMEventDispatch };
118
    }
119
120
    bool eventWasHandled = handleWheelEvent(wheelEvent, processingSteps);
111
    event.wasHandled = eventWasHandled;
121
    event.wasHandled = eventWasHandled;
112
    return eventWasHandled;
122
    return eventWasHandled;
113
}
123
}
- a/Source/WebCore/page/mac/EventHandlerMac.mm +10 lines
Lines 149-154 bool EventHandler::wheelEvent(NSEvent *event) a/Source/WebCore/page/mac/EventHandlerMac.mm_sec1
149
    CurrentEventScope scope(event, nil);
149
    CurrentEventScope scope(event, nil);
150
    auto wheelEvent = PlatformEventFactory::createPlatformWheelEvent(event, page->chrome().platformPageClient());
150
    auto wheelEvent = PlatformEventFactory::createPlatformWheelEvent(event, page->chrome().platformPageClient());
151
    OptionSet<WheelEventProcessingSteps> processingSteps = { WheelEventProcessingSteps::MainThreadForScrolling, WheelEventProcessingSteps::MainThreadForBlockingDOMEventDispatch };
151
    OptionSet<WheelEventProcessingSteps> processingSteps = { WheelEventProcessingSteps::MainThreadForScrolling, WheelEventProcessingSteps::MainThreadForBlockingDOMEventDispatch };
152
153
    if (wheelEvent.isGestureStart())
154
        m_wheelScrollGestureState = WTF::nullopt;
155
    else if (wheelEvent.phase() == PlatformWheelEventPhase::Changed || wheelEvent.momentumPhase() == PlatformWheelEventPhase::Changed) {
156
        if (m_frame.settings().wheelEventGesturesBecomeNonBlocking() && m_wheelScrollGestureState && *m_wheelScrollGestureState == WheelScrollGestureState::NonBlocking)
157
            processingSteps = { WheelEventProcessingSteps::MainThreadForScrolling, WheelEventProcessingSteps::MainThreadForNonBlockingDOMEventDispatch };
158
    }
152
    return handleWheelEvent(wheelEvent, processingSteps);
159
    return handleWheelEvent(wheelEvent, processingSteps);
153
}
160
}
154
161
Lines 969-974 void EventHandler::wheelEventWasProcessedByMainThread(const PlatformWheelEvent& a/Source/WebCore/page/mac/EventHandlerMac.mm_sec2
969
        if (scrollingCoordinator->coordinatesScrollingForFrameView(*view))
976
        if (scrollingCoordinator->coordinatesScrollingForFrameView(*view))
970
            scrollingCoordinator->wheelEventWasProcessedByMainThread(wheelEvent, eventHandling);
977
            scrollingCoordinator->wheelEventWasProcessedByMainThread(wheelEvent, eventHandling);
971
    }
978
    }
979
980
    if (wheelEvent.isGestureStart() && eventHandling.contains(EventHandling::DispatchedToDOM))
981
        m_wheelScrollGestureState = eventHandling.contains(EventHandling::DefaultPrevented) ? WheelScrollGestureState::Blocking : WheelScrollGestureState::NonBlocking;
972
#else
982
#else
973
    UNUSED_PARAM(wheelEvent);
983
    UNUSED_PARAM(wheelEvent);
974
    UNUSED_PARAM(eventHandling);
984
    UNUSED_PARAM(eventHandling);
- a/Source/WebCore/platform/PlatformWheelEvent.cpp +9 lines
Lines 80-83 TextStream& operator<<(TextStream& ts, EventHandling steps) a/Source/WebCore/platform/PlatformWheelEvent.cpp_sec1
80
    return ts;
80
    return ts;
81
}
81
}
82
82
83
TextStream& operator<<(TextStream& ts, WheelScrollGestureState state)
84
{
85
    switch (state) {
86
    case WheelScrollGestureState::Blocking: ts << "blocking"; break;
87
    case WheelScrollGestureState::NonBlocking: ts << "non-blocking"; break;
88
    }
89
    return ts;
90
}
91
83
} // namespace WebCore
92
} // namespace WebCore
- a/Source/WebCore/platform/PlatformWheelEvent.h +6 lines
Lines 43-48 enum class WheelEventProcessingSteps : uint8_t { a/Source/WebCore/platform/PlatformWheelEvent.h_sec1
43
    MainThreadForBlockingDOMEventDispatch       = 1 << 3,
43
    MainThreadForBlockingDOMEventDispatch       = 1 << 3,
44
};
44
};
45
45
46
enum class WheelScrollGestureState : uint8_t {
47
    Blocking,
48
    NonBlocking
49
};
50
46
// The ScrollByPixelWheelEvent is a fine-grained event that specifies the precise number of pixels to scroll.
51
// The ScrollByPixelWheelEvent is a fine-grained event that specifies the precise number of pixels to scroll.
47
// It is sent directly by touch pads on macOS, or synthesized when platforms generate line-by-line scrolling events.
52
// It is sent directly by touch pads on macOS, or synthesized when platforms generate line-by-line scrolling events.
48
//
53
//
Lines 267-271 inline FloatPoint PlatformWheelEvent::swipeVelocity() const a/Source/WebCore/platform/PlatformWheelEvent.h_sec2
267
WEBCORE_EXPORT WTF::TextStream& operator<<(WTF::TextStream&, const PlatformWheelEvent&);
272
WEBCORE_EXPORT WTF::TextStream& operator<<(WTF::TextStream&, const PlatformWheelEvent&);
268
WEBCORE_EXPORT WTF::TextStream& operator<<(WTF::TextStream&, WheelEventProcessingSteps);
273
WEBCORE_EXPORT WTF::TextStream& operator<<(WTF::TextStream&, WheelEventProcessingSteps);
269
WEBCORE_EXPORT WTF::TextStream& operator<<(WTF::TextStream&, EventHandling);
274
WEBCORE_EXPORT WTF::TextStream& operator<<(WTF::TextStream&, EventHandling);
275
WEBCORE_EXPORT WTF::TextStream& operator<<(WTF::TextStream&, WheelScrollGestureState);
270
276
271
} // namespace WebCore
277
} // namespace WebCore
- a/LayoutTests/ChangeLog +16 lines
Lines 1-3 a/LayoutTests/ChangeLog_sec1
1
2020-11-30  Simon Fraser  <simon.fraser@apple.com>
2
3
        [WK1] Only the first wheel event in a gesture should be cancelable
4
        https://bugs.webkit.org/show_bug.cgi?id=219384
5
6
        Reviewed by NOBODY (OOPS!).
7
8
        Add a temporary failing result for WK2 until the async scrolling implementation lands.
9
10
        * fast/events/wheel/first-wheel-event-cancelable-expected.txt: Added.
11
        * fast/events/wheel/first-wheel-event-cancelable.html: Added.
12
        * fast/events/wheel/wheel-events-become-non-cancelable-expected.txt: Added.
13
        * fast/events/wheel/wheel-events-become-non-cancelable.html: Added.
14
        * platform/mac-wk2/fast/events/wheel/wheel-events-become-non-cancelable-expected.txt: Added.
15
        * platform/win/TestExpectations:
16
1
2020-11-30  Ryan Haddad  <ryanhaddad@apple.com>
17
2020-11-30  Ryan Haddad  <ryanhaddad@apple.com>
2
18
3
        Unreviewed, reverting r270129, r270194, and r270258.
19
        Unreviewed, reverting r270129, r270194, and r270258.
- a/LayoutTests/fast/events/wheel/first-wheel-event-cancelable-expected.txt +10 lines
Line 0 a/LayoutTests/fast/events/wheel/first-wheel-event-cancelable-expected.txt_sec1
1
Tests preventDefault on the first event results in the rest of the events being cancelable
2
PASS windowScrollEventCount is 0
3
PASS firstWasCancelable is true
4
PASS firstWasCanceled is true
5
PASS secondWasCancelable is true
6
PASS secondWasCanceled is true
7
PASS successfullyParsed is true
8
9
TEST COMPLETE
10
- a/LayoutTests/fast/events/wheel/first-wheel-event-cancelable.html +73 lines
Line 0 a/LayoutTests/fast/events/wheel/first-wheel-event-cancelable.html_sec1
1
<!DOCTYPE html>
2
<html>
3
<head>
4
    <style>
5
        body {
6
            height: 5000px;
7
        }
8
        
9
        #target {
10
            width: 200px;
11
            height: 200px;
12
            background-color: silver;
13
            margin: 20px;
14
        }
15
    </style>
16
    <script src="../../../resources/js-test-pre.js"></script>
17
    <script src="../../../resources/ui-helper.js"></script>
18
    <script>
19
        var jsTestIsAsync = true;
20
21
        let wheelEventCount = 0;
22
        let windowScrollEventCount = 0;
23
24
        let firstWasCancelable;
25
        let firstWasCanceled;
26
        let secondWasCancelable;
27
        let secondWasCanceled;
28
29
        async function testScroll()
30
        {
31
            await UIHelper.mouseWheelScrollAt(100, 100);
32
            shouldBe('windowScrollEventCount', '0');
33
34
            shouldBeTrue('firstWasCancelable');
35
            shouldBeTrue('firstWasCanceled');
36
37
            shouldBeTrue('secondWasCancelable');
38
            shouldBeTrue('secondWasCanceled');
39
40
            finishJSTest();
41
        }
42
43
        window.addEventListener('load', () => {
44
            debug('Tests preventDefault on the first event results in the rest of the events being cancelable')
45
            let target = document.getElementById('target');
46
47
            target.addEventListener('wheel', (event) => {
48
                if (!wheelEventCount) {
49
                    firstWasCancelable = event.cancelable;
50
                    event.preventDefault();
51
                    firstWasCanceled = event.defaultPrevented;
52
                } else if (wheelEventCount == 1) {
53
                    secondWasCancelable = event.cancelable;
54
                    event.preventDefault();
55
                    secondWasCanceled = event.defaultPrevented;
56
                }
57
                ++wheelEventCount;
58
            });
59
60
            window.addEventListener('scroll', () => {
61
                ++windowScrollEventCount;
62
            }, false);
63
64
            setTimeout(testScroll, 0);
65
        }, false);
66
    </script>
67
</head>
68
<body>
69
    <div id="target"></div>
70
    <div id="console"></div>
71
    <script src="../../../resources/js-test-post.js"></script>
72
</body>
73
</html>
- a/LayoutTests/fast/events/wheel/wheel-events-become-non-cancelable-expected.txt +7 lines
Line 0 a/LayoutTests/fast/events/wheel/wheel-events-become-non-cancelable-expected.txt_sec1
1
Tests that events in the gesture are non-cancelable if preventDefault was not called on the first event
2
PASS firstWasCancelable is true
3
PASS becameNonCancelable is true
4
PASS successfullyParsed is true
5
6
TEST COMPLETE
7
- a/LayoutTests/fast/events/wheel/wheel-events-become-non-cancelable.html +81 lines
Line 0 a/LayoutTests/fast/events/wheel/wheel-events-become-non-cancelable.html_sec1
1
<!DOCTYPE html>
2
<html>
3
<head>
4
    <style>
5
        body {
6
            height: 5000px;
7
        }
8
        
9
        #target {
10
            width: 200px;
11
            height: 1000px;
12
            background-color: silver;
13
            margin: 20px;
14
        }
15
    </style>
16
    <script src="../../../resources/js-test-pre.js"></script>
17
    <script src="../../../resources/ui-helper.js"></script>
18
    <script>
19
        var jsTestIsAsync = true;
20
21
        let wheelEventCount = 0;
22
23
        let firstWasCancelable;
24
        let becameNonCancelable;
25
26
        async function testScroll()
27
        {
28
            if (!eventSender) {
29
                finishJSTest();
30
                return;
31
            }
32
            eventSender.monitorWheelEvents();
33
            eventSender.mouseMoveTo(100, 100);
34
            eventSender.mouseScrollByWithWheelAndMomentumPhases(0, -1, "began", "none");
35
            await UIHelper.renderingUpdate();
36
            eventSender.mouseScrollByWithWheelAndMomentumPhases(0, -5, "changed", "none");
37
            await UIHelper.renderingUpdate();
38
            eventSender.mouseScrollByWithWheelAndMomentumPhases(0, -5, "changed", "none");
39
            await UIHelper.renderingUpdate();
40
            eventSender.mouseScrollByWithWheelAndMomentumPhases(0, -5, "changed", "none");
41
            await UIHelper.renderingUpdate();
42
            eventSender.mouseScrollByWithWheelAndMomentumPhases(0, -5, "changed", "none");
43
            await UIHelper.renderingUpdate();
44
            eventSender.mouseScrollByWithWheelAndMomentumPhases(0, 0, "ended", "none");
45
            
46
            await UIHelper.waitForScrollCompletion();
47
48
            shouldBeTrue('firstWasCancelable');
49
            shouldBeTrue('becameNonCancelable');
50
51
            finishJSTest();
52
        }
53
54
        window.addEventListener('load', () => {
55
            debug('Tests that events in the gesture are non-cancelable if preventDefault was not called on the first event');
56
            let target = document.getElementById('target');
57
58
            target.addEventListener('wheel', (event) => {
59
                if (wheelEventCount == 0) {
60
                    firstWasCancelable = event.cancelable;
61
                } else {
62
                    // Wait for the first non-cancelable event and check that all subsequent events are non-cancelable.
63
                    if (becameNonCancelable && event.cancelable)
64
                        testFailed('Should not see cancelable event after non-cancelable event')
65
66
                    if (!event.cancelable)
67
                        becameNonCancelable = true;
68
                }
69
                ++wheelEventCount;
70
            });
71
72
            setTimeout(testScroll, 0);
73
        }, false);
74
    </script>
75
</head>
76
<body>
77
    <div id="target"></div>
78
    <div id="console"></div>
79
    <script src="../../../resources/js-test-post.js"></script>
80
</body>
81
</html>
- a/LayoutTests/platform/mac-wk2/fast/events/wheel/wheel-events-become-non-cancelable-expected.txt +7 lines
Line 0 a/LayoutTests/platform/mac-wk2/fast/events/wheel/wheel-events-become-non-cancelable-expected.txt_sec1
1
Tests that events in the gesture are non-cancelable if preventDefault was not called on the first event
2
PASS firstWasCancelable is true
3
FAIL becameNonCancelable should be true (of type boolean). Was undefined (of type undefined).
4
PASS successfullyParsed is true
5
6
TEST COMPLETE
7
- a/LayoutTests/platform/win/TestExpectations +1 lines
Lines 282-287 fast/events/wheel/wheel-event-listeners-on-document-made-passive.html [ Skip ] a/LayoutTests/platform/win/TestExpectations_sec1
282
fast/events/wheel/wheel-event-listeners-on-window-left-active.html [ Skip ]
282
fast/events/wheel/wheel-event-listeners-on-window-left-active.html [ Skip ]
283
fast/events/wheel/wheel-event-listeners-on-window-made-passive.html [ Skip ]
283
fast/events/wheel/wheel-event-listeners-on-window-made-passive.html [ Skip ]
284
fast/events/wheel/wheel-event-in-passive-region-non-cancelable.html [ Skip ]
284
fast/events/wheel/wheel-event-in-passive-region-non-cancelable.html [ Skip ]
285
fast/events/wheel/wheel-events-become-non-cancelable.html [ Skip ]
285
286
286
scrollbars/scroll-rtl-or-bt-layer.html [ Timeout ]
287
scrollbars/scroll-rtl-or-bt-layer.html [ Timeout ]
287
webkit.org/b/208559 fast/scrolling/arrow-key-scroll-in-rtl-document.html [ Skip ]
288
webkit.org/b/208559 fast/scrolling/arrow-key-scroll-in-rtl-document.html [ Skip ]

Return to Bug 219384