Title: [280492] trunk/Source/WebCore
Revision
280492
Author
[email protected]
Date
2021-07-30 13:26:51 -0700 (Fri, 30 Jul 2021)

Log Message

Add key-driven smooth scrolling to macOS
https://bugs.webkit.org/show_bug.cgi?id=228009

Patch by Dana Estra <[email protected]> on 2021-07-30
Reviewed by Tim Horton.

Cause keyboard scroll event to trigger start of smooth scroll animation, using same physics implementation as iOS smooth scrolling.

No tests yet.

* WebCore.xcodeproj/project.pbxproj:
* dom/Node.cpp:
(WebCore::Node::defaultEventHandler):
* page/EventHandler.cpp:
(WebCore::EventHandler::defaultKeyboardEventHandler):
(WebCore::EventHandler::scrollDistance):
(WebCore::EventHandler::stopKeyboardScrolling):
(WebCore::EventHandler::startKeyboardScrolling):
* platform/KeyboardScrollingAnimator.cpp: Added.
* platform/KeyboardScrollingAnimator.h: Added.
* platform/ScrollAnimator.cpp:
(WebCore::ScrollAnimator::startAnimationCallback):
* platform/ScrollAnimator.h:
* platform/ScrollController.cpp:
(WebCore::ScrollController::animationCallback):
(WebCore::ScrollController::startOrStopAnimationCallbacks):
(WebCore::ScrollController::beginKeyboardScrolling):
(WebCore::ScrollController::stopKeyboardScrolling):
(WebCore::ScrollController::setIsAnimatingKeyboardScrolling):
(WebCore::ScrollController::updateKeyboardScrollingAnimatingState):
* platform/ScrollController.h:
(WebCore::ScrollControllerClient::updateKeyboardScrollPosition):

Modified Paths

Added Paths

Diff

Modified: trunk/Source/WebCore/ChangeLog (280491 => 280492)


--- trunk/Source/WebCore/ChangeLog	2021-07-30 20:25:46 UTC (rev 280491)
+++ trunk/Source/WebCore/ChangeLog	2021-07-30 20:26:51 UTC (rev 280492)
@@ -1,3 +1,37 @@
+2021-07-30  Dana Estra  <[email protected]>
+
+        Add key-driven smooth scrolling to macOS
+        https://bugs.webkit.org/show_bug.cgi?id=228009
+
+        Reviewed by Tim Horton.
+
+        Cause keyboard scroll event to trigger start of smooth scroll animation, using same physics implementation as iOS smooth scrolling.
+
+        No tests yet.
+
+        * WebCore.xcodeproj/project.pbxproj:
+        * dom/Node.cpp:
+        (WebCore::Node::defaultEventHandler):
+        * page/EventHandler.cpp:
+        (WebCore::EventHandler::defaultKeyboardEventHandler):
+        (WebCore::EventHandler::scrollDistance):
+        (WebCore::EventHandler::stopKeyboardScrolling):
+        (WebCore::EventHandler::startKeyboardScrolling):
+        * platform/KeyboardScrollingAnimator.cpp: Added.
+        * platform/KeyboardScrollingAnimator.h: Added.
+        * platform/ScrollAnimator.cpp:
+        (WebCore::ScrollAnimator::startAnimationCallback):
+        * platform/ScrollAnimator.h:
+        * platform/ScrollController.cpp:
+        (WebCore::ScrollController::animationCallback):
+        (WebCore::ScrollController::startOrStopAnimationCallbacks):
+        (WebCore::ScrollController::beginKeyboardScrolling):
+        (WebCore::ScrollController::stopKeyboardScrolling):
+        (WebCore::ScrollController::setIsAnimatingKeyboardScrolling):
+        (WebCore::ScrollController::updateKeyboardScrollingAnimatingState):
+        * platform/ScrollController.h:
+        (WebCore::ScrollControllerClient::updateKeyboardScrollPosition):
+        
 2021-07-30  Chris Dumez  <[email protected]>
 
         Document's fallback base URL should be deduced from its creator when URL is about:blank

Modified: trunk/Source/WebCore/Sources.txt (280491 => 280492)


--- trunk/Source/WebCore/Sources.txt	2021-07-30 20:25:46 UTC (rev 280491)
+++ trunk/Source/WebCore/Sources.txt	2021-07-30 20:26:51 UTC (rev 280492)
@@ -1745,6 +1745,7 @@
 platform/FileMonitor.cpp
 platform/FileStream.cpp
 platform/FrameRateMonitor.cpp
+platform/KeyboardScrollingAnimator.cpp
 platform/LayoutUnit.cpp
 platform/LegacySchemeRegistry.cpp
 platform/Length.cpp

Modified: trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj (280491 => 280492)


--- trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj	2021-07-30 20:25:46 UTC (rev 280491)
+++ trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj	2021-07-30 20:26:51 UTC (rev 280492)
@@ -673,6 +673,7 @@
 		1F8756B21E22C3350042C40D /* WebSQLiteDatabaseTrackerClient.h in Headers */ = {isa = PBXBuildFile; fileRef = 1F8756B11E22BEEF0042C40D /* WebSQLiteDatabaseTrackerClient.h */; settings = {ATTRIBUTES = (Private, ); }; };
 		1FAFBF1915A5FA7400083A20 /* UTIUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 1FAFBF1615A5FA5200083A20 /* UTIUtilities.h */; settings = {ATTRIBUTES = (Private, ); }; };
 		1FC40FBA1655CCB90040F29E /* SubimageCacheWithTimer.h in Headers */ = {isa = PBXBuildFile; fileRef = 1FC40FB71655C5910040F29E /* SubimageCacheWithTimer.h */; };
+		1FD992F826AA24F90088E596 /* KeyboardScrollingAnimator.h in Headers */ = {isa = PBXBuildFile; fileRef = 1FD992F626AA24F80088E596 /* KeyboardScrollingAnimator.h */; settings = {ATTRIBUTES = (Private, ); }; };
 		20D629271253690B00081543 /* InspectorInstrumentation.h in Headers */ = {isa = PBXBuildFile; fileRef = 20D629251253690B00081543 /* InspectorInstrumentation.h */; };
 		225A16B50D5C11E900090295 /* WebEventRegion.h in Headers */ = {isa = PBXBuildFile; fileRef = 225A16B30D5C11E900090295 /* WebEventRegion.h */; settings = {ATTRIBUTES = (Private, ); }; };
 		228C284510D82500009D0D0E /* ScriptWrappable.h in Headers */ = {isa = PBXBuildFile; fileRef = 228C284410D82500009D0D0E /* ScriptWrappable.h */; settings = {ATTRIBUTES = (Private, ); }; };
@@ -7013,6 +7014,8 @@
 		1FAFBF1715A5FA5200083A20 /* UTIUtilities.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = UTIUtilities.mm; sourceTree = "<group>"; };
 		1FC40FB71655C5910040F29E /* SubimageCacheWithTimer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SubimageCacheWithTimer.h; sourceTree = "<group>"; };
 		1FC40FB81655C5910040F29E /* SubimageCacheWithTimer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SubimageCacheWithTimer.cpp; sourceTree = "<group>"; };
+		1FD992F626AA24F80088E596 /* KeyboardScrollingAnimator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KeyboardScrollingAnimator.h; sourceTree = "<group>"; };
+		1FD992F926AA254D0088E596 /* KeyboardScrollingAnimator.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = KeyboardScrollingAnimator.cpp; sourceTree = "<group>"; };
 		20D629241253690B00081543 /* InspectorInstrumentation.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = InspectorInstrumentation.cpp; sourceTree = "<group>"; };
 		20D629251253690B00081543 /* InspectorInstrumentation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InspectorInstrumentation.h; sourceTree = "<group>"; };
 		225A16B30D5C11E900090295 /* WebEventRegion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WebEventRegion.h; sourceTree = "<group>"; };
@@ -27552,6 +27555,8 @@
 				4190F3A3249D152800531C57 /* FrameRateMonitor.h */,
 				A8748BDF12CBF2DC001FBA41 /* HashTools.h */,
 				BC3BC29B0E91AB0F00835588 /* HostWindow.h */,
+				1FD992F926AA254D0088E596 /* KeyboardScrollingAnimator.cpp */,
+				1FD992F626AA24F80088E596 /* KeyboardScrollingAnimator.h */,
 				1AE00D57182DAC8D00087DD7 /* KeyedCoding.h */,
 				E15FF7D418C9553800FE4C87 /* KeypressCommand.h */,
 				A3D42A841F33BA3600A64B62 /* LayoutUnit.cpp */,
@@ -33333,6 +33338,7 @@
 				83B74EF61F3E0BF200996BC7 /* KeepaliveRequestTracker.h in Headers */,
 				85031B440A44EFC700F992E0 /* KeyboardEvent.h in Headers */,
 				1F020AEF26780FCE0000809A /* KeyboardScroll.h in Headers */,
+				1FD992F826AA24F90088E596 /* KeyboardScrollingAnimator.h in Headers */,
 				1AE00D59182DAC8D00087DD7 /* KeyedCoding.h in Headers */,
 				517A63C51B74318F00E7DCDC /* KeyedDecoderCF.h in Headers */,
 				517A63C61B74319200E7DCDC /* KeyedEncoderCF.h in Headers */,

Modified: trunk/Source/WebCore/dom/Node.cpp (280491 => 280492)


--- trunk/Source/WebCore/dom/Node.cpp	2021-07-30 20:25:46 UTC (rev 280491)
+++ trunk/Source/WebCore/dom/Node.cpp	2021-07-30 20:26:51 UTC (rev 280492)
@@ -2432,7 +2432,7 @@
     if (event.target() != this)
         return;
     const AtomString& eventType = event.type();
-    if (eventType == eventNames().keydownEvent || eventType == eventNames().keypressEvent) {
+    if (eventType == eventNames().keydownEvent || eventType == eventNames().keypressEvent || eventType == eventNames().keyupEvent) {
         if (is<KeyboardEvent>(event)) {
             if (Frame* frame = document().frame())
                 frame->eventHandler().defaultKeyboardEventHandler(downcast<KeyboardEvent>(event));

Modified: trunk/Source/WebCore/page/EventHandler.cpp (280491 => 280492)


--- trunk/Source/WebCore/page/EventHandler.cpp	2021-07-30 20:25:46 UTC (rev 280491)
+++ trunk/Source/WebCore/page/EventHandler.cpp	2021-07-30 20:26:51 UTC (rev 280492)
@@ -68,7 +68,7 @@
 #include "ImageOverlayController.h"
 #include "InspectorInstrumentation.h"
 #include "KeyboardEvent.h"
-#include "KeyboardScroll.h"
+#include "KeyboardScrollingAnimator.h"
 #include "Logging.h"
 #include "MouseEvent.h"
 #include "MouseEventWithHitTestResults.h"
@@ -3803,6 +3803,12 @@
         if (event.charCode() == ' ')
             defaultSpaceEventHandler(event);
     }
+    if (event.type() == eventNames().keyupEvent) {
+        m_frame.editor().handleKeyboardEvent(event);
+        if (event.defaultHandled())
+            return;
+        stopKeyboardScrolling();
+    }
 }
 
 #if ENABLE(DRAG_SUPPORT)
@@ -4197,7 +4203,7 @@
     if (!view)
         return;
 
-    bool defaultHandled = m_frame.settings().eventHandlerDrivenSmoothKeyboardScrollingEnabled() ? handleKeyboardScrolling(event) : view->logicalScroll(direction, ScrollByPage);
+    bool defaultHandled = m_frame.settings().eventHandlerDrivenSmoothKeyboardScrollingEnabled() ? startKeyboardScrolling(event) : view->logicalScroll(direction, ScrollByPage);
     if (defaultHandled)
         event.setDefaultHandled();
 }
@@ -4237,7 +4243,7 @@
             return m_frame.view()->verticalScrollbar();
         return m_frame.view()->horizontalScrollbar();
     }();
-    
+
     switch (granularity) {
     case ScrollGranularity::ScrollByLine:
         return scrollbar->lineStep();
@@ -4252,62 +4258,28 @@
     RELEASE_ASSERT_NOT_REACHED();
 }
 
-bool EventHandler::handleKeyboardScrolling(KeyboardEvent& event)
+void EventHandler::stopKeyboardScrolling()
 {
     Ref protectedFrame = m_frame;
-    // FIXME (bug 227459): This logic does not account for writing-mode.
+    FrameView* view = m_frame.view();
 
-    enum class Key : uint8_t { LeftArrow, RightArrow, UpArrow, DownArrow, Space };
+    KeyboardScrollingAnimator* animator = view->scrollAnimator().keyboardScrollingAnimator();
 
-    Key key;
-    if (event.keyIdentifier() == "Left")
-        key = Key::LeftArrow;
-    else if (event.keyIdentifier() == "Right")
-        key = Key::RightArrow;
-    else if (event.keyIdentifier() == "Up")
-        key = Key::UpArrow;
-    else if (event.keyIdentifier() == "Down")
-        key = Key::DownArrow;
-    else if (event.charCode() == ' ')
-        key = Key::Space;
-    else
-        return false;
+    if (animator)
+        animator->handleKeyUpEvent();
+}
 
-    auto granularity = [&] {
-        switch (key) {
-        case Key::LeftArrow:
-        case Key::RightArrow:
-            return event.altKey() ? ScrollGranularity::ScrollByPage : ScrollGranularity::ScrollByLine;
-        case Key::UpArrow:
-        case Key::DownArrow:
-            if (event.metaKey())
-                return ScrollGranularity::ScrollByDocument;
-            if (event.altKey())
-                return ScrollGranularity::ScrollByPage;
-            return ScrollGranularity::ScrollByLine;
-        case Key::Space:
-            return ScrollGranularity::ScrollByPage;
-        };
-        RELEASE_ASSERT_NOT_REACHED();
-    }();
+bool EventHandler::startKeyboardScrolling(KeyboardEvent& event)
+{
+    Ref protectedFrame = m_frame;
+    FrameView* view = m_frame.view();
 
-    auto direction = [&] {
-        switch (key) {
-        case Key::LeftArrow:
-            return ScrollDirection::ScrollLeft;
-        case Key::RightArrow:
-            return ScrollDirection::ScrollRight;
-        case Key::UpArrow:
-            return ScrollDirection::ScrollUp;
-        case Key::DownArrow:
-            return ScrollDirection::ScrollDown;
-        case Key::Space:
-            return event.shiftKey() ? ScrollDirection::ScrollUp : ScrollDirection::ScrollDown;
-        }
-        RELEASE_ASSERT_NOT_REACHED();
-    }();
+    KeyboardScrollingAnimator* animator = view->scrollAnimator().keyboardScrollingAnimator();
 
-    return EventHandler::scrollRecursively(direction, granularity, nullptr);
+    if (animator)
+        return animator->beginKeyboardScrollGesture(event);
+
+    return false;
 }
 
 void EventHandler::defaultArrowEventHandler(FocusDirection focusDirection, KeyboardEvent& event)
@@ -4316,7 +4288,7 @@
 
     if (!isSpatialNavigationEnabled(&m_frame)) {
         if (m_frame.settings().eventHandlerDrivenSmoothKeyboardScrollingEnabled())
-            handleKeyboardScrolling(event);
+            startKeyboardScrolling(event);
         return;
     }
 

Modified: trunk/Source/WebCore/page/EventHandler.h (280491 => 280492)


--- trunk/Source/WebCore/page/EventHandler.h	2021-07-30 20:25:46 UTC (rev 280491)
+++ trunk/Source/WebCore/page/EventHandler.h	2021-07-30 20:26:51 UTC (rev 280492)
@@ -377,7 +377,8 @@
     bool handleMousePressEventTripleClick(const MouseEventWithHitTestResults&);
 
     float scrollDistance(ScrollDirection, ScrollGranularity);
-    bool handleKeyboardScrolling(KeyboardEvent&);
+    bool startKeyboardScrolling(KeyboardEvent&);
+    void stopKeyboardScrolling();
 
 #if ENABLE(DRAG_SUPPORT)
     bool handleMouseDraggedEvent(const MouseEventWithHitTestResults&, CheckDragHysteresis = ShouldCheckDragHysteresis);

Added: trunk/Source/WebCore/platform/KeyboardScrollingAnimator.cpp (0 => 280492)


--- trunk/Source/WebCore/platform/KeyboardScrollingAnimator.cpp	                        (rev 0)
+++ trunk/Source/WebCore/platform/KeyboardScrollingAnimator.cpp	2021-07-30 20:26:51 UTC (rev 280492)
@@ -0,0 +1,308 @@
+/*
+ * Copyright (C) 2021 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 "KeyboardScrollingAnimator.h"
+
+#include "EventNames.h"
+#include "ScrollTypes.h"
+#include "ScrollableArea.h"
+#include "WritingMode.h"
+
+namespace WebCore {
+
+KeyboardScrollingAnimator::KeyboardScrollingAnimator(ScrollAnimator& scrollAnimator, ScrollController& scrollController)
+    : m_scrollAnimator(scrollAnimator)
+    , m_scrollController(scrollController)
+{
+}
+
+RectEdges<bool> KeyboardScrollingAnimator::scrollableDirectionsFromOffset(FloatPoint offset) const
+{
+    auto minimumScrollPosition = m_scrollAnimator.scrollableArea().minimumScrollPosition();
+    auto maximumScrollPosition = m_scrollAnimator.scrollableArea().maximumScrollPosition();
+
+    RectEdges<bool> edges;
+
+    edges.setTop(offset.y() > minimumScrollPosition.y());
+    edges.setBottom(offset.y() < maximumScrollPosition.y());
+    edges.setLeft(offset.x() > minimumScrollPosition.x());
+    edges.setRight(offset.x() < maximumScrollPosition.x());
+
+    return edges;
+}
+
+static BoxSide boxSideForDirection(ScrollDirection direction)
+{
+    switch (direction) {
+    case ScrollDirection::ScrollUp:
+        return BoxSide::Top;
+    case ScrollDirection::ScrollDown:
+        return BoxSide::Bottom;
+    case ScrollDirection::ScrollLeft:
+        return BoxSide::Left;
+    case ScrollDirection::ScrollRight:
+        return BoxSide::Right;
+    }
+    ASSERT_NOT_REACHED();
+    return BoxSide::Top;
+}
+
+static FloatSize perpendicularAbsoluteUnitVector(ScrollDirection direction)
+{
+    switch (direction) {
+    case ScrollDirection::ScrollUp:
+    case ScrollDirection::ScrollDown:
+        return { 1, 0 };
+    case ScrollDirection::ScrollLeft:
+    case ScrollDirection::ScrollRight:
+        return { 0, 1 };
+    }
+    ASSERT_NOT_REACHED();
+    return { };
+}
+
+void KeyboardScrollingAnimator::updateKeyboardScrollPosition(MonotonicTime currentTime)
+{
+    auto force = FloatSize { };
+    auto axesToApplySpring = FloatSize { 1, 1 };
+    KeyboardScrollParameters params = KeyboardScrollParameters::parameters();
+
+    if (m_currentKeyboardScroll) {
+        auto scrollableDirections = scrollableDirectionsFromOffset(m_scrollAnimator.currentPosition());
+        auto direction = m_currentKeyboardScroll->direction;
+
+        if (scrollableDirections.at(boxSideForDirection(direction))) {
+            // Apply the scrolling force. Only apply the spring in the perpendicular axis,
+            // otherwise it drags against the direction of motion.
+            axesToApplySpring = perpendicularAbsoluteUnitVector(direction);
+            force = m_currentKeyboardScroll->force;
+        } else {
+            // The scroll view cannot scroll in this direction, and is rubber-banding.
+            // Apply a constant and significant force; otherwise, the force for a
+            // single-line increment is not strong enough to rubber-band perceptibly.
+            force = unitVectorForScrollDirection(direction).scaled(params.rubberBandForce);
+        }
+
+        if (fabs(m_velocity.width()) >= fabs(m_currentKeyboardScroll->maximumVelocity.width()))
+            force.setWidth(0);
+
+        if (fabs(m_velocity.height()) >= fabs(m_currentKeyboardScroll->maximumVelocity.height()))
+            force.setHeight(0);
+    }
+
+    ScrollPosition idealPosition = m_scrollAnimator.scrollableArea().constrainScrollPosition(IntPoint(m_currentKeyboardScroll ? m_scrollAnimator.currentPosition() : m_idealPosition));
+    FloatSize displacement = m_scrollAnimator.currentPosition() - idealPosition;
+
+    auto springForce = -displacement.scaled(params.springStiffness) - m_velocity.scaled(params.springDamping);
+    force += springForce * axesToApplySpring;
+
+    float frameDuration = (currentTime - m_timeAtLastFrame).value();
+    m_timeAtLastFrame = currentTime;
+
+    FloatSize acceleration = force.scaled(1. / params.springMass);
+    m_velocity += acceleration.scaled(frameDuration);
+    FloatPoint newPosition = m_scrollAnimator.currentPosition() + m_velocity.scaled(frameDuration);
+
+    m_scrollAnimator.scrollToPositionWithoutAnimation(newPosition);
+
+    if (!m_scrollTriggeringKeyIsPressed && m_velocity.diagonalLengthSquared() < 1) {
+        m_scrollController.stopKeyboardScrolling();
+        m_velocity = { };
+    }
+}
+
+float KeyboardScrollingAnimator::scrollDistance(ScrollDirection direction, ScrollGranularity granularity) const
+{
+    auto scrollbar = [&] {
+        if (direction == ScrollDirection::ScrollUp || direction == ScrollDirection::ScrollDown)
+            return m_scrollAnimator.scrollableArea().verticalScrollbar();
+        return m_scrollAnimator.scrollableArea().horizontalScrollbar();
+    }();
+
+    switch (granularity) {
+    case ScrollGranularity::ScrollByLine:
+        return scrollbar->lineStep();
+    case ScrollGranularity::ScrollByPage:
+        return scrollbar->pageStep();
+    case ScrollGranularity::ScrollByDocument:
+        return scrollbar->totalSize();
+    case ScrollGranularity::ScrollByPixel:
+        return scrollbar->pixelStep();
+    }
+
+    return 0;
+}
+
+std::optional<KeyboardScroll> KeyboardScrollingAnimator::keyboardScrollForKeyboardEvent(KeyboardEvent& event) const
+{
+    // FIXME (bug 227459): This logic does not account for writing-mode.
+
+    enum class Key : uint8_t { LeftArrow, RightArrow, UpArrow, DownArrow, Space };
+
+    Key key;
+    if (event.keyIdentifier() == "Left")
+        key = Key::LeftArrow;
+    else if (event.keyIdentifier() == "Right")
+        key = Key::RightArrow;
+    else if (event.keyIdentifier() == "Up")
+        key = Key::UpArrow;
+    else if (event.keyIdentifier() == "Down")
+        key = Key::DownArrow;
+    else if (event.charCode() == ' ')
+        key = Key::Space;
+    else
+        return std::nullopt;
+
+    auto granularity = [&] {
+        switch (key) {
+        case Key::LeftArrow:
+        case Key::RightArrow:
+            return event.altKey() ? ScrollGranularity::ScrollByPage : ScrollGranularity::ScrollByLine;
+        case Key::UpArrow:
+        case Key::DownArrow:
+            if (event.metaKey())
+                return ScrollGranularity::ScrollByDocument;
+            if (event.altKey())
+                return ScrollGranularity::ScrollByPage;
+            return ScrollGranularity::ScrollByLine;
+        case Key::Space:
+            return ScrollGranularity::ScrollByPage;
+        };
+    }();
+
+    auto direction = [&] {
+        switch (key) {
+        case Key::LeftArrow:
+            return ScrollDirection::ScrollLeft;
+        case Key::RightArrow:
+            return ScrollDirection::ScrollRight;
+        case Key::UpArrow:
+            return ScrollDirection::ScrollUp;
+        case Key::DownArrow:
+            return ScrollDirection::ScrollDown;
+        case Key::Space:
+            return event.shiftKey() ? ScrollDirection::ScrollUp : ScrollDirection::ScrollDown;
+        }
+    }();
+
+    float distance = scrollDistance(direction, granularity);
+
+    KeyboardScroll scroll;
+
+    scroll.offset = unitVectorForScrollDirection(direction).scaled(distance);
+    scroll.granularity = granularity;
+    scroll.direction = direction;
+    scroll.maximumVelocity = scroll.offset.scaled(KeyboardScrollParameters::parameters().maximumVelocityMultiplier);
+    scroll.force = scroll.maximumVelocity.scaled(KeyboardScrollParameters::parameters().springMass / KeyboardScrollParameters::parameters().timeToMaximumVelocity);
+
+    return scroll;
+}
+
+bool KeyboardScrollingAnimator::beginKeyboardScrollGesture(KeyboardEvent& event)
+{
+    auto scroll = keyboardScrollForKeyboardEvent(event);
+
+    if (!scroll)
+        return false;
+
+    m_currentKeyboardScroll = scroll;
+
+    if (event.type() != eventNames().keydownEvent)
+        return false;
+
+    if (m_scrollTriggeringKeyIsPressed)
+        return false;
+
+    if (m_currentKeyboardScroll->granularity == ScrollGranularity::ScrollByDocument) {
+        m_velocity = { };
+        stopKeyboardScrollAnimation();
+        auto newPosition = IntPoint(m_scrollAnimator.currentPosition() + m_currentKeyboardScroll->offset);
+        m_scrollAnimator.scrollToPositionWithAnimation(newPosition);
+        return true;
+    }
+
+    m_timeAtLastFrame = MonotonicTime::now();
+    m_scrollTriggeringKeyIsPressed = true;
+
+    m_idealPositionForMinimumTravel = m_scrollAnimator.currentPosition() + m_currentKeyboardScroll->offset;
+    m_scrollController.beginKeyboardScrolling();
+
+    return true;
+}
+
+static ScrollPosition farthestPointInDirection(FloatPoint a, FloatPoint b, ScrollDirection direction)
+{
+    switch (direction) {
+    case ScrollDirection::ScrollUp:
+        return ScrollPosition(a.x(), std::min(a.y(), b.y()));
+    case ScrollDirection::ScrollDown:
+        return ScrollPosition(a.x(), std::max(a.y(), b.y()));
+    case ScrollDirection::ScrollLeft:
+        return ScrollPosition(std::min(a.x(), b.x()), a.y());
+    case ScrollDirection::ScrollRight:
+        return ScrollPosition(std::max(a.x(), b.x()), a.y());
+    }
+
+    ASSERT_NOT_REACHED();
+    return { };
+}
+
+void KeyboardScrollingAnimator::stopKeyboardScrollAnimation()
+{
+    if (!m_currentKeyboardScroll)
+        return;
+
+    auto params = KeyboardScrollParameters::parameters();
+
+    // Determine the settling position of the spring, conserving the system's current energy.
+    // Kinetic = elastic potential
+    // 1/2 * m * v^2 = 1/2 * k * x^2
+    // x = sqrt(v^2 * m / k)
+    auto displacementMagnitudeSquared = (m_velocity * m_velocity).scaled(params.springMass / params.springStiffness);
+    FloatSize displacement = {
+        std::copysign(sqrt(displacementMagnitudeSquared.width()), m_velocity.width()),
+        std::copysign(sqrt(displacementMagnitudeSquared.height()), m_velocity.height())
+    };
+
+    // If the spring would settle before the minimum travel distance
+    // for an instantaneous tap, move the settling position of the spring
+    // out to that point.
+    ScrollPosition farthestPoint = farthestPointInDirection(m_scrollAnimator.currentPosition() + displacement, m_idealPositionForMinimumTravel, m_currentKeyboardScroll->direction);
+    m_idealPosition = m_scrollAnimator.scrollableArea().constrainScrollPosition(farthestPoint);
+
+    m_currentKeyboardScroll = std::nullopt;
+}
+
+void KeyboardScrollingAnimator::handleKeyUpEvent()
+{
+    if (!m_scrollTriggeringKeyIsPressed)
+        return;
+
+    stopKeyboardScrollAnimation();
+    m_scrollTriggeringKeyIsPressed = false;
+}
+
+} // namespace WebCore

Added: trunk/Source/WebCore/platform/KeyboardScrollingAnimator.h (0 => 280492)


--- trunk/Source/WebCore/platform/KeyboardScrollingAnimator.h	                        (rev 0)
+++ trunk/Source/WebCore/platform/KeyboardScrollingAnimator.h	2021-07-30 20:26:51 UTC (rev 280492)
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2021 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
+
+#include "KeyboardEvent.h"
+#include "KeyboardScroll.h"
+#include "RectEdges.h"
+#include "ScrollAnimator.h"
+
+namespace WebCore {
+
+class KeyboardScrollingAnimator {
+    WTF_MAKE_NONCOPYABLE(KeyboardScrollingAnimator);
+    WTF_MAKE_FAST_ALLOCATED;
+public:
+    KeyboardScrollingAnimator(ScrollAnimator&, ScrollController&);
+
+    bool beginKeyboardScrollGesture(KeyboardEvent&);
+    void handleKeyUpEvent();
+    void updateKeyboardScrollPosition(MonotonicTime);
+
+private:
+    void stopKeyboardScrollAnimation();
+    RectEdges<bool> scrollableDirectionsFromOffset(FloatPoint) const;
+    std::optional<KeyboardScroll> keyboardScrollForKeyboardEvent(KeyboardEvent&) const;
+    float scrollDistance(ScrollDirection, ScrollGranularity) const;
+
+    ScrollAnimator& m_scrollAnimator;
+    ScrollController& m_scrollController;
+    std::optional<WebCore::KeyboardScroll> m_currentKeyboardScroll;
+    bool m_scrollTriggeringKeyIsPressed;
+    FloatSize m_velocity;
+    MonotonicTime m_timeAtLastFrame;
+    FloatPoint m_idealPositionForMinimumTravel;
+    FloatPoint m_idealPosition;
+};
+
+} // namespace WebCore

Modified: trunk/Source/WebCore/platform/ScrollAnimator.cpp (280491 => 280492)


--- trunk/Source/WebCore/platform/ScrollAnimator.cpp	2021-07-30 20:25:46 UTC (rev 280491)
+++ trunk/Source/WebCore/platform/ScrollAnimator.cpp	2021-07-30 20:26:51 UTC (rev 280492)
@@ -33,6 +33,7 @@
 #include "ScrollAnimator.h"
 
 #include "FloatPoint.h"
+#include "KeyboardScrollingAnimator.h"
 #include "LayoutSize.h"
 #include "PlatformWheelEvent.h"
 #include "ScrollAnimationSmooth.h"
@@ -67,6 +68,7 @@
         [this] {
             m_scrollableArea.setScrollBehaviorStatus(ScrollBehaviorStatus::NotInAnimation);
         }))
+    , m_keyboardScrollingAnimator(makeUnique<KeyboardScrollingAnimator>(*this, m_scrollController))
 {
 }
 
@@ -332,7 +334,7 @@
 {
     if (m_scrollControllerAnimationTimer.isActive())
         return;
-        
+
     m_scrollControllerAnimationTimer.startRepeating(1_s / 60.);
 }
 

Modified: trunk/Source/WebCore/platform/ScrollAnimator.h (280491 => 280492)


--- trunk/Source/WebCore/platform/ScrollAnimator.h	2021-07-30 20:25:46 UTC (rev 280491)
+++ trunk/Source/WebCore/platform/ScrollAnimator.h	2021-07-30 20:26:51 UTC (rev 280492)
@@ -34,7 +34,6 @@
 #include "FloatPoint.h"
 #include "PlatformWheelEvent.h"
 #include "ScrollController.h"
-#include "ScrollTypes.h"
 #include "Timer.h"
 #include "WheelEventTestMonitor.h"
 #include <wtf/FastMalloc.h>
@@ -43,6 +42,7 @@
 namespace WebCore {
 
 class FloatPoint;
+class KeyboardScrollingAnimator;
 class PlatformTouchEvent;
 class ScrollAnimation;
 class ScrollableArea;
@@ -81,6 +81,8 @@
 
     virtual bool handleWheelEvent(const PlatformWheelEvent&);
 
+    KeyboardScrollingAnimator *keyboardScrollingAnimator() const override { return m_keyboardScrollingAnimator.get(); }
+
 #if ENABLE(TOUCH_EVENTS)
     virtual bool handleTouchEvent(const PlatformTouchEvent&);
 #endif
@@ -178,6 +180,7 @@
     FloatPoint m_currentPosition;
 
     std::unique_ptr<ScrollAnimation> m_scrollAnimation;
+    std::unique_ptr<KeyboardScrollingAnimator> m_keyboardScrollingAnimator;
 };
 
 } // namespace WebCore

Modified: trunk/Source/WebCore/platform/ScrollController.cpp (280491 => 280492)


--- trunk/Source/WebCore/platform/ScrollController.cpp	2021-07-30 20:25:46 UTC (rev 280491)
+++ trunk/Source/WebCore/platform/ScrollController.cpp	2021-07-30 20:26:51 UTC (rev 280492)
@@ -26,6 +26,7 @@
 #include "config.h"
 #include "ScrollController.h"
 
+#include "KeyboardScrollingAnimator.h"
 #include "LayoutSize.h"
 #include "Logging.h"
 #include "PlatformWheelEvent.h"
@@ -43,15 +44,16 @@
 
 void ScrollController::animationCallback(MonotonicTime currentTime)
 {
-    LOG_WITH_STREAM(Scrolling, stream << "ScrollController " << this << " animationCallback: isAnimatingRubberBand " << m_isAnimatingRubberBand << " isAnimatingScrollSnap " << m_isAnimatingScrollSnap);
+    LOG_WITH_STREAM(Scrolling, stream << "ScrollController " << this << " animationCallback: isAnimatingRubberBand " << m_isAnimatingRubberBand << " isAnimatingScrollSnap " << m_isAnimatingScrollSnap << "isAnimatingKeyboardScrolling" << m_isAnimatingKeyboardScrolling);
 
     updateScrollSnapAnimatingState(currentTime);
     updateRubberBandAnimatingState(currentTime);
+    updateKeyboardScrollingAnimatingState(currentTime);
 }
 
 void ScrollController::startOrStopAnimationCallbacks()
 {
-    bool needsCallbacks = m_isAnimatingRubberBand || m_isAnimatingScrollSnap;
+    bool needsCallbacks = m_isAnimatingRubberBand || m_isAnimatingScrollSnap || m_isAnimatingKeyboardScrolling;
     if (needsCallbacks == m_isRunningAnimatingCallback)
         return;
 
@@ -65,6 +67,16 @@
     m_isRunningAnimatingCallback = false;
 }
 
+void ScrollController::beginKeyboardScrolling()
+{
+    setIsAnimatingKeyboardScrolling(true);
+}
+
+void ScrollController::stopKeyboardScrolling()
+{
+    setIsAnimatingKeyboardScrolling(false);
+}
+
 void ScrollController::setIsAnimatingRubberBand(bool isAnimatingRubberBand)
 {
     if (isAnimatingRubberBand == m_isAnimatingRubberBand)
@@ -83,6 +95,15 @@
     startOrStopAnimationCallbacks();
 }
 
+void ScrollController::setIsAnimatingKeyboardScrolling(bool isAnimatingKeyboardScrolling)
+{
+    if (isAnimatingKeyboardScrolling == m_isAnimatingKeyboardScrolling)
+        return;
+
+    m_isAnimatingKeyboardScrolling = isAnimatingKeyboardScrolling;
+    startOrStopAnimationCallbacks();
+}
+
 bool ScrollController::usesScrollSnap() const
 {
     return !!m_scrollSnapState;
@@ -198,6 +219,15 @@
         setNearestScrollSnapIndexForAxisAndOffset(ScrollEventAxis::Vertical, offset);
 
 }
+
+void ScrollController::updateKeyboardScrollingAnimatingState(MonotonicTime currentTime)
+{
+    if (!m_isAnimatingKeyboardScrolling)
+        return;
+
+    m_client.keyboardScrollingAnimator()->updateKeyboardScrollPosition(currentTime);
+}
+
 // Currently, only Mac supports momentum srolling-based scrollsnapping and rubber banding
 // so all of these methods are a noop on non-Mac platforms.
 #if !PLATFORM(MAC)

Modified: trunk/Source/WebCore/platform/ScrollController.h (280491 => 280492)


--- trunk/Source/WebCore/platform/ScrollController.h	2021-07-30 20:25:46 UTC (rev 280491)
+++ trunk/Source/WebCore/platform/ScrollController.h	2021-07-30 20:26:51 UTC (rev 280492)
@@ -38,6 +38,7 @@
 
 namespace WebCore {
 
+class KeyboardScrollingAnimator;
 class LayoutSize;
 class PlatformWheelEvent;
 class ScrollController;
@@ -72,6 +73,9 @@
     virtual void startAnimationCallback(ScrollController&) = 0;
     virtual void stopAnimationCallback(ScrollController&) = 0;
 
+    virtual void updateKeyboardScrollPosition(MonotonicTime) { }
+    virtual KeyboardScrollingAnimator *keyboardScrollingAnimator() const { return nullptr; }
+
 #if ENABLE(RUBBER_BANDING)
     virtual bool allowsHorizontalStretching(const PlatformWheelEvent&) const = 0;
     virtual bool allowsVerticalStretching(const PlatformWheelEvent&) const = 0;
@@ -121,6 +125,9 @@
     bool usesScrollSnap() const;
     void stopAllTimers();
     void scrollPositionChanged();
+
+    void beginKeyboardScrolling();
+    void stopKeyboardScrolling();
     
     // Should be called periodically by the client. Started by startAnimationCallback(), stopped by stopAnimationCallback().
     void animationCallback(MonotonicTime);
@@ -163,9 +170,11 @@
 
     void updateScrollSnapAnimatingState(MonotonicTime);
     void updateRubberBandAnimatingState(MonotonicTime);
-    
+    void updateKeyboardScrollingAnimatingState(MonotonicTime);
+
     void setIsAnimatingRubberBand(bool);
     void setIsAnimatingScrollSnap(bool);
+    void setIsAnimatingKeyboardScrolling(bool);
 
 #if PLATFORM(MAC)
     void startScrollSnapAnimation();
@@ -199,6 +208,7 @@
     bool m_isRunningAnimatingCallback { false };
     bool m_isAnimatingRubberBand { false };
     bool m_isAnimatingScrollSnap { false };
+    bool m_isAnimatingKeyboardScrolling { false };
 
 #if PLATFORM(MAC)
     WallTime m_lastMomentumScrollTimestamp;
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to