[GTK] Add kinetic scrolling
authorutatane.tea@gmail.com <utatane.tea@gmail.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 9 Jun 2017 08:58:30 +0000 (08:58 +0000)
committerutatane.tea@gmail.com <utatane.tea@gmail.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 9 Jun 2017 08:58:30 +0000 (08:58 +0000)
https://bugs.webkit.org/show_bug.cgi?id=155750

Patch by Adrien Plazas <aplazas@igalia.com> on 2017-06-09
Reviewed by Carlos Garcia Campos.

Patch by Adrien Plazas and Yusuke Suzuki.

Source/WebCore:

Add the ScrollAnimationKinetic scrolling animation allowing to perform momentum scrolling; it is based on GTK+'s
GtkKineticScrolling type.

Add the notion of phase, momentum phase and swipe velocity to PlatformWheelEvent.

Depending on whether the scrolling ended normally or triggered a swipe, the scroll animator will either compute
the swipe velocity from the previous scrolling events or use the one from the swipe gesture to initiate the
momentum scrolling.

* PlatformGTK.cmake:
* platform/PlatformWheelEvent.h:
(WebCore::PlatformWheelEvent::setHasPreciseScrollingDeltas):
(WebCore::PlatformWheelEvent::phase):
(WebCore::PlatformWheelEvent::momentumPhase):
(WebCore::PlatformWheelEvent::isTransitioningToMomentumScroll):
* platform/ScrollAnimation.h:
(WebCore::ScrollAnimation::scroll):
(WebCore::ScrollAnimation::updateVisibleLengths):
(WebCore::ScrollAnimation::setCurrentPosition):
* platform/ScrollAnimationKinetic.cpp: Added.
(WebCore::ScrollAnimationKinetic::PerAxisData::PerAxisData):
(WebCore::ScrollAnimationKinetic::PerAxisData::animateScroll):
(WebCore::ScrollAnimationKinetic::ScrollAnimationKinetic):
(WebCore::ScrollAnimationKinetic::~ScrollAnimationKinetic):
(WebCore::ScrollAnimationKinetic::stop):
(WebCore::ScrollAnimationKinetic::start):
(WebCore::ScrollAnimationKinetic::animationTimerFired):
* platform/ScrollAnimationKinetic.h: Copied from Source/WebCore/platform/ScrollAnimation.h.
* platform/gtk/PlatformWheelEventGtk.cpp:
(WebCore::PlatformWheelEvent::PlatformWheelEvent):
(WebCore::PlatformWheelEvent::swipeVelocity):
* platform/gtk/ScrollAnimatorGtk.cpp:
(WebCore::ScrollAnimatorGtk::ScrollAnimatorGtk):
(WebCore::ScrollAnimatorGtk::ensureSmoothScrollingAnimation):
(WebCore::ScrollAnimatorGtk::scroll):
(WebCore::ScrollAnimatorGtk::scrollToOffsetWithoutAnimation):
(WebCore::ScrollAnimatorGtk::computeVelocity):
(WebCore::ScrollAnimatorGtk::handleWheelEvent):
(WebCore::ScrollAnimatorGtk::willEndLiveResize):
(WebCore::ScrollAnimatorGtk::updatePosition):
(WebCore::ScrollAnimatorGtk::didAddVerticalScrollbar):
(WebCore::ScrollAnimatorGtk::didAddHorizontalScrollbar):
* platform/gtk/ScrollAnimatorGtk.h:

Source/WebKit2:

Add the notion of phase and momentum phase to WebWheelEvent.

Make WebWheelEvent manage the 'is_stop' attribute of GdkEventScroll to create the corresponding WebWheelEvent
with the correct phases and deltas.

Make GestureController manage swipes to create the corresponding WebWheelEvent with the correct phases and
deltas.

* Shared/NativeWebWheelEvent.h:
* Shared/WebEvent.h:
* Shared/WebEventConversion.cpp:
(WebKit::WebKit2PlatformWheelEvent::WebKit2PlatformWheelEvent):
* Shared/WebWheelEvent.cpp:
(WebKit::WebWheelEvent::WebWheelEvent):
(WebKit::WebWheelEvent::encode):
(WebKit::WebWheelEvent::decode):
* Shared/gtk/NativeWebWheelEventGtk.cpp:
(WebKit::NativeWebWheelEvent::NativeWebWheelEvent):
* Shared/gtk/WebEventFactory.cpp:
(WebKit::WebEventFactory::createWebWheelEvent):
* Shared/gtk/WebEventFactory.h:
* UIProcess/WebPageProxy.cpp:
(WebKit::WebPageProxy::handleWheelEvent):
(WebKit::WebPageProxy::shouldProcessWheelEventNow):
* UIProcess/WebPageProxy.h:
* UIProcess/gtk/GestureController.cpp:
(WebKit::GestureController::GestureController):
(WebKit::GestureController::handleEvent):
(WebKit::GestureController::isProcessingGestures):
(WebKit::createScrollEvent):
(WebKit::GestureController::DragGesture::startDrag):
(WebKit::GestureController::DragGesture::handleDrag):
(WebKit::GestureController::DragGesture::begin):
(WebKit::GestureController::DragGesture::end):
(WebKit::GestureController::SwipeGesture::startMomentumScroll):
(WebKit::GestureController::SwipeGesture::swipe):
(WebKit::GestureController::SwipeGesture::SwipeGesture):
* UIProcess/gtk/GestureController.h:

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

21 files changed:
Source/WebCore/ChangeLog
Source/WebCore/PlatformGTK.cmake
Source/WebCore/platform/PlatformWheelEvent.h
Source/WebCore/platform/ScrollAnimation.h
Source/WebCore/platform/ScrollAnimationKinetic.cpp [new file with mode: 0644]
Source/WebCore/platform/ScrollAnimationKinetic.h [new file with mode: 0644]
Source/WebCore/platform/gtk/PlatformWheelEventGtk.cpp
Source/WebCore/platform/gtk/ScrollAnimatorGtk.cpp
Source/WebCore/platform/gtk/ScrollAnimatorGtk.h
Source/WebKit2/ChangeLog
Source/WebKit2/Shared/NativeWebWheelEvent.h
Source/WebKit2/Shared/WebEvent.h
Source/WebKit2/Shared/WebEventConversion.cpp
Source/WebKit2/Shared/WebWheelEvent.cpp
Source/WebKit2/Shared/gtk/NativeWebWheelEventGtk.cpp
Source/WebKit2/Shared/gtk/WebEventFactory.cpp
Source/WebKit2/Shared/gtk/WebEventFactory.h
Source/WebKit2/UIProcess/WebPageProxy.cpp
Source/WebKit2/UIProcess/WebPageProxy.h
Source/WebKit2/UIProcess/gtk/GestureController.cpp
Source/WebKit2/UIProcess/gtk/GestureController.h

index be1a053..1cf3b75 100644 (file)
@@ -1,3 +1,56 @@
+2017-06-09  Adrien Plazas  <aplazas@igalia.com>
+
+        [GTK] Add kinetic scrolling
+        https://bugs.webkit.org/show_bug.cgi?id=155750
+
+        Reviewed by Carlos Garcia Campos.
+
+        Patch by Adrien Plazas and Yusuke Suzuki.
+
+        Add the ScrollAnimationKinetic scrolling animation allowing to perform momentum scrolling; it is based on GTK+'s
+        GtkKineticScrolling type.
+
+        Add the notion of phase, momentum phase and swipe velocity to PlatformWheelEvent.
+
+        Depending on whether the scrolling ended normally or triggered a swipe, the scroll animator will either compute
+        the swipe velocity from the previous scrolling events or use the one from the swipe gesture to initiate the
+        momentum scrolling.
+
+        * PlatformGTK.cmake:
+        * platform/PlatformWheelEvent.h:
+        (WebCore::PlatformWheelEvent::setHasPreciseScrollingDeltas):
+        (WebCore::PlatformWheelEvent::phase):
+        (WebCore::PlatformWheelEvent::momentumPhase):
+        (WebCore::PlatformWheelEvent::isTransitioningToMomentumScroll):
+        * platform/ScrollAnimation.h:
+        (WebCore::ScrollAnimation::scroll):
+        (WebCore::ScrollAnimation::updateVisibleLengths):
+        (WebCore::ScrollAnimation::setCurrentPosition):
+        * platform/ScrollAnimationKinetic.cpp: Added.
+        (WebCore::ScrollAnimationKinetic::PerAxisData::PerAxisData):
+        (WebCore::ScrollAnimationKinetic::PerAxisData::animateScroll):
+        (WebCore::ScrollAnimationKinetic::ScrollAnimationKinetic):
+        (WebCore::ScrollAnimationKinetic::~ScrollAnimationKinetic):
+        (WebCore::ScrollAnimationKinetic::stop):
+        (WebCore::ScrollAnimationKinetic::start):
+        (WebCore::ScrollAnimationKinetic::animationTimerFired):
+        * platform/ScrollAnimationKinetic.h: Copied from Source/WebCore/platform/ScrollAnimation.h.
+        * platform/gtk/PlatformWheelEventGtk.cpp:
+        (WebCore::PlatformWheelEvent::PlatformWheelEvent):
+        (WebCore::PlatformWheelEvent::swipeVelocity):
+        * platform/gtk/ScrollAnimatorGtk.cpp:
+        (WebCore::ScrollAnimatorGtk::ScrollAnimatorGtk):
+        (WebCore::ScrollAnimatorGtk::ensureSmoothScrollingAnimation):
+        (WebCore::ScrollAnimatorGtk::scroll):
+        (WebCore::ScrollAnimatorGtk::scrollToOffsetWithoutAnimation):
+        (WebCore::ScrollAnimatorGtk::computeVelocity):
+        (WebCore::ScrollAnimatorGtk::handleWheelEvent):
+        (WebCore::ScrollAnimatorGtk::willEndLiveResize):
+        (WebCore::ScrollAnimatorGtk::updatePosition):
+        (WebCore::ScrollAnimatorGtk::didAddVerticalScrollbar):
+        (WebCore::ScrollAnimatorGtk::didAddHorizontalScrollbar):
+        * platform/gtk/ScrollAnimatorGtk.h:
+
 2017-06-09  Zan Dobersek  <zdobersek@igalia.com>
 
         [GCrypt] ECDSA signing results can be smaller than the EC key size
index 24699fd..90e1fe1 100644 (file)
@@ -63,6 +63,7 @@ list(APPEND WebCore_SOURCES
     page/linux/ResourceUsageThreadLinux.cpp
 
     platform/KillRingNone.cpp
+    platform/ScrollAnimationKinetic.cpp
     platform/StaticPasteboard.cpp
     platform/UserAgentQuirks.cpp
 
index 45dd806..6d458be 100644 (file)
@@ -48,7 +48,7 @@ enum PlatformWheelEventGranularity : uint8_t {
     ScrollByPixelWheelEvent,
 };
 
-#if PLATFORM(COCOA)
+#if PLATFORM(COCOA) || PLATFORM(GTK)
 
 enum PlatformWheelEventPhase : uint8_t {
     PlatformWheelEventPhaseNone = 0,
@@ -117,13 +117,12 @@ public:
 
 #if PLATFORM(GTK)
     explicit PlatformWheelEvent(GdkEventScroll*);
+    FloatPoint swipeVelocity() const;
 #endif
 
 #if PLATFORM(COCOA)
     bool hasPreciseScrollingDeltas() const { return m_hasPreciseScrollingDeltas; }
     void setHasPreciseScrollingDeltas(bool hasPreciseScrollingDeltas) { m_hasPreciseScrollingDeltas = hasPreciseScrollingDeltas; }
-    PlatformWheelEventPhase phase() const { return m_phase; }
-    PlatformWheelEventPhase momentumPhase() const { return m_momentumPhase; }
     unsigned scrollCount() const { return m_scrollCount; }
     float unacceleratedScrollingDeltaX() const { return m_unacceleratedScrollingDeltaX; }
     float unacceleratedScrollingDeltaY() const { return m_unacceleratedScrollingDeltaY; }
@@ -131,12 +130,17 @@ public:
     bool shouldConsiderLatching() const;
     bool shouldResetLatching() const;
     bool isEndOfMomentumScroll() const;
-    bool isEndOfNonMomentumScroll() const;
-    bool isTransitioningToMomentumScroll() const;
 #else
     bool useLatchedEventElement() const { return false; }
 #endif
 
+#if PLATFORM(COCOA) || PLATFORM(GTK)
+    PlatformWheelEventPhase phase() const { return m_phase; }
+    PlatformWheelEventPhase momentumPhase() const { return m_momentumPhase; }
+    bool isEndOfNonMomentumScroll() const;
+    bool isTransitioningToMomentumScroll() const;
+#endif
+
 #if PLATFORM(WIN)
     PlatformWheelEvent(HWND, WPARAM, LPARAM, bool isMouseHWheel);
     PlatformWheelEvent(HWND, const FloatSize& delta, const FloatPoint& location);
@@ -155,10 +159,12 @@ protected:
     // Scrolling velocity in pixels per second.
     FloatSize m_scrollingVelocity;
 
-#if PLATFORM(COCOA)
-    bool m_hasPreciseScrollingDeltas { false };
+#if PLATFORM(COCOA) || PLATFORM(GTK)
     PlatformWheelEventPhase m_phase { PlatformWheelEventPhaseNone };
     PlatformWheelEventPhase m_momentumPhase { PlatformWheelEventPhaseNone };
+#endif
+#if PLATFORM(COCOA)
+    bool m_hasPreciseScrollingDeltas { false };
     unsigned m_scrollCount { 0 };
     float m_unacceleratedScrollingDeltaX { 0 };
     float m_unacceleratedScrollingDeltaY { 0 };
@@ -191,6 +197,10 @@ inline bool PlatformWheelEvent::isEndOfMomentumScroll() const
     return m_phase == PlatformWheelEventPhaseNone && m_momentumPhase == PlatformWheelEventPhaseEnded;
 }
 
+#endif
+
+#if PLATFORM(COCOA) || PLATFORM(GTK)
+
 inline bool PlatformWheelEvent::isEndOfNonMomentumScroll() const
 {
     return m_phase == PlatformWheelEventPhaseEnded && m_momentumPhase == PlatformWheelEventPhaseNone;
@@ -200,7 +210,6 @@ inline bool PlatformWheelEvent::isTransitioningToMomentumScroll() const
 {
     return m_phase == PlatformWheelEventPhaseNone && m_momentumPhase == PlatformWheelEventPhaseBegan;
 }
-
 #endif
 
 } // namespace WebCore
index 3929997..2e5f63b 100644 (file)
@@ -36,10 +36,10 @@ class ScrollableArea;
 class ScrollAnimation {
 public:
     virtual ~ScrollAnimation() { };
-    virtual bool scroll(ScrollbarOrientation, ScrollGranularity, float step, float multiplier) = 0;
+    virtual bool scroll(ScrollbarOrientation, ScrollGranularity, float /* step */, float /* multiplier */) { return true; };
     virtual void stop() = 0;
-    virtual void updateVisibleLengths() = 0;
-    virtual void setCurrentPosition(const FloatPoint&) = 0;
+    virtual void updateVisibleLengths() { };
+    virtual void setCurrentPosition(const FloatPoint&) { };
     virtual void serviceAnimation() { };
 
 protected:
diff --git a/Source/WebCore/platform/ScrollAnimationKinetic.cpp b/Source/WebCore/platform/ScrollAnimationKinetic.cpp
new file mode 100644 (file)
index 0000000..6e9b9a0
--- /dev/null
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2016 Igalia S.L.
+ *
+ * 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 "ScrollAnimationKinetic.h"
+
+#include "ScrollableArea.h"
+#include <wtf/CurrentTime.h>
+
+/*
+ * PerAxisData is a port of GtkKineticScrolling as of GTK+ 3.20,
+ * mimicking its API and its behavior.
+ *
+ * All our curves are second degree linear differential equations, and
+ * so they can always be written as linear combinations of 2 base
+ * solutions. coef1 and coef2 are the coefficients to these two base
+ * solutions, and are computed from the initial position and velocity.
+ *
+ * In the case of simple deceleration, the differential equation is
+ *
+ *   y'' = -my'
+ *
+ * With m the resistence factor. For this we use the following 2
+ * base solutions:
+ *
+ *   f1(x) = 1
+ *   f2(x) = exp(-mx)
+ *
+ * In the case of overshoot, the differential equation is
+ *
+ *   y'' = -my' - ky
+ *
+ * With m the resistance, and k the spring stiffness constant. We let
+ * k = m^2 / 4, so that the system is critically damped (ie, returns to its
+ * equilibrium position as quickly as possible, without oscillating), and offset
+ * the whole thing, such that the equilibrium position is at 0. This gives the
+ * base solutions
+ *
+ *   f1(x) = exp(-mx / 2)
+ *   f2(x) = t exp(-mx / 2)
+ */
+
+static const double decelFriction = 4;
+static const double frameRate = 60;
+static const Seconds tickTime = 1_s / frameRate;
+static const Seconds minimumTimerInterval { 1_ms };
+
+namespace WebCore {
+
+ScrollAnimationKinetic::PerAxisData::PerAxisData(double lower, double upper, double initialPosition, double initialVelocity)
+    : m_lower(lower)
+    , m_upper(upper)
+    , m_coef1(initialVelocity / decelFriction + initialPosition)
+    , m_coef2(-initialVelocity / decelFriction)
+    , m_position(clampTo(initialPosition, lower, upper))
+    , m_velocity(initialPosition < lower || initialPosition > upper ? 0 : initialVelocity)
+{
+}
+
+bool ScrollAnimationKinetic::PerAxisData::animateScroll(Seconds timeDelta)
+{
+    m_elapsedTime += timeDelta;
+
+    double exponentialPart = exp(-decelFriction * m_elapsedTime.value());
+    m_position = m_coef1 + m_coef2 * exponentialPart;
+    m_velocity = -decelFriction * m_coef2 * exponentialPart;
+
+    if (m_position < m_lower) {
+        m_position = m_lower;
+        m_velocity = 0;
+    } else if (m_position > m_upper) {
+        m_position = m_upper;
+        m_velocity = 0;
+    } else if (fabs(m_velocity) < 1) {
+        m_position = round(m_position);
+        m_velocity = 0;
+    }
+
+    return m_velocity;
+}
+
+ScrollAnimationKinetic::ScrollAnimationKinetic(ScrollableArea& scrollableArea, std::function<void(FloatPoint&&)>&& notifyPositionChangedFunction)
+    : ScrollAnimation(scrollableArea)
+    , m_notifyPositionChangedFunction(WTFMove(notifyPositionChangedFunction))
+    , m_animationTimer(*this, &ScrollAnimationKinetic::animationTimerFired)
+{
+}
+
+ScrollAnimationKinetic::~ScrollAnimationKinetic()
+{
+}
+
+void ScrollAnimationKinetic::stop()
+{
+    m_animationTimer.stop();
+    m_horizontalData = std::nullopt;
+    m_verticalData = std::nullopt;
+}
+
+void ScrollAnimationKinetic::start(const FloatPoint& initialPosition, const FloatPoint& velocity, bool mayHScroll, bool mayVScroll)
+{
+    stop();
+
+    m_position = initialPosition;
+
+    if (!velocity.x() && !velocity.y())
+        return;
+
+    if (mayHScroll) {
+        m_horizontalData = PerAxisData(m_scrollableArea.minimumScrollPosition().x(),
+            m_scrollableArea.maximumScrollPosition().x(),
+            initialPosition.x(), velocity.x());
+    }
+    if (mayVScroll) {
+        m_verticalData = PerAxisData(m_scrollableArea.minimumScrollPosition().y(),
+            m_scrollableArea.maximumScrollPosition().y(),
+            initialPosition.y(), velocity.y());
+    }
+
+    m_startTime = MonotonicTime::now() - tickTime / 2.;
+    animationTimerFired();
+}
+
+void ScrollAnimationKinetic::animationTimerFired()
+{
+    MonotonicTime currentTime = MonotonicTime::now();
+    Seconds deltaToNextFrame = 1_s * ceil((currentTime - m_startTime).value() * frameRate) / frameRate - (currentTime - m_startTime);
+
+    if (m_horizontalData && !m_horizontalData.value().animateScroll(deltaToNextFrame))
+        m_horizontalData = std::nullopt;
+
+    if (m_verticalData && !m_verticalData.value().animateScroll(deltaToNextFrame))
+        m_verticalData = std::nullopt;
+
+    // If one of the axes didn't finish its animation we must continue it.
+    if (m_horizontalData || m_verticalData)
+        m_animationTimer.startOneShot(std::max(minimumTimerInterval, deltaToNextFrame));
+
+    double x = m_horizontalData ? m_horizontalData.value().position() : m_position.x();
+    double y = m_verticalData ? m_verticalData.value().position() : m_position.y();
+    m_position = FloatPoint(x, y);
+    m_notifyPositionChangedFunction(FloatPoint(m_position));
+}
+
+} // namespace WebCore
diff --git a/Source/WebCore/platform/ScrollAnimationKinetic.h b/Source/WebCore/platform/ScrollAnimationKinetic.h
new file mode 100644 (file)
index 0000000..5458c53
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2016 Igalia S.L.
+ *
+ * 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 "FloatPoint.h"
+#include "ScrollAnimation.h"
+#include "Timer.h"
+
+#include <wtf/Optional.h>
+
+namespace WebCore {
+
+class ScrollableArea;
+
+class ScrollAnimationKinetic final: public ScrollAnimation {
+private:
+    class PerAxisData {
+    public:
+        PerAxisData(double lower, double upper, double initialPosition, double initialVelocity);
+
+        double position() { return m_position; }
+
+        bool animateScroll(Seconds timeDelta);
+
+    private:
+        double m_lower { 0 };
+        double m_upper { 0 };
+
+        double m_coef1 { 0 };
+        double m_coef2 { 0 };
+
+        Seconds m_elapsedTime;
+        double m_position { 0 };
+        double m_velocity { 0 };
+    };
+
+public:
+    ScrollAnimationKinetic(ScrollableArea&, std::function<void(FloatPoint&&)>&& notifyPositionChangedFunction);
+    virtual ~ScrollAnimationKinetic();
+
+    void start(const FloatPoint& initialPosition, const FloatPoint& velocity, bool mayHScroll, bool mayVScroll);
+
+private:
+    void stop() override;
+    void animationTimerFired();
+
+    std::function<void(FloatPoint&&)> m_notifyPositionChangedFunction;
+
+    std::optional<PerAxisData> m_horizontalData;
+    std::optional<PerAxisData> m_verticalData;
+
+    MonotonicTime m_startTime;
+    Timer m_animationTimer;
+    FloatPoint m_position;
+};
+
+} // namespace WebCore
index d826075..3b0e590 100644 (file)
@@ -28,6 +28,7 @@
 #include "config.h"
 #include "PlatformWheelEvent.h"
 
+#include "FloatPoint.h"
 #include "PlatformKeyboardEvent.h"
 #include "Scrollbar.h"
 #include <gdk/gdk.h>
@@ -85,6 +86,20 @@ PlatformWheelEvent::PlatformWheelEvent(GdkEventScroll* event)
     m_wheelTicksX = m_deltaX;
     m_wheelTicksY = m_deltaY;
 
+#ifndef GTK_API_VERSION_2
+#if GTK_CHECK_VERSION(3, 20, 0)
+    m_phase = event->is_stop ?
+        PlatformWheelEventPhaseEnded :
+        PlatformWheelEventPhaseChanged;
+#else
+    m_phase = event->direction == GDK_SCROLL_SMOOTH && !m_deltaX && !m_deltaY ?
+        PlatformWheelEventPhaseEnded :
+        PlatformWheelEventPhaseChanged;
+#endif
+#else
+    m_phase = PlatformWheelEventPhaseChanged;
+#endif // GTK_API_VERSION_2
+
     m_position = IntPoint(static_cast<int>(event->x), static_cast<int>(event->y));
     m_globalPosition = IntPoint(static_cast<int>(event->x_root), static_cast<int>(event->y_root));
     m_granularity = ScrollByPixelWheelEvent;
@@ -95,4 +110,10 @@ PlatformWheelEvent::PlatformWheelEvent(GdkEventScroll* event)
     m_deltaY *= static_cast<float>(Scrollbar::pixelsPerLineStep());
 }
 
+FloatPoint PlatformWheelEvent::swipeVelocity() const
+{
+    // The swiping velocity is stored in the deltas of the event declaring it.
+    return isTransitioningToMomentumScroll() ? FloatPoint(m_wheelTicksX, m_wheelTicksY) : FloatPoint();
+}
+
 }
index 1b95481..c2aa9dc 100644 (file)
@@ -31,6 +31,7 @@
 #include "config.h"
 #include "ScrollAnimatorGtk.h"
 
+#include "ScrollAnimationKinetic.h"
 #include "ScrollAnimationSmooth.h"
 #include "ScrollableArea.h"
 #include "ScrollbarTheme.h"
@@ -40,6 +41,7 @@ namespace WebCore {
 
 static const Seconds overflowScrollbarsAnimationDuration { 1_s };
 static const Seconds overflowScrollbarsAnimationHideDelay { 2_s };
+static const Seconds scrollCaptureThreshold { 150_ms };
 
 std::unique_ptr<ScrollAnimator> ScrollAnimator::create(ScrollableArea& scrollableArea)
 {
@@ -50,6 +52,14 @@ ScrollAnimatorGtk::ScrollAnimatorGtk(ScrollableArea& scrollableArea)
     : ScrollAnimator(scrollableArea)
     , m_overlayScrollbarAnimationTimer(*this, &ScrollAnimatorGtk::overlayScrollbarAnimationTimerFired)
 {
+    m_kineticAnimation = std::make_unique<ScrollAnimationKinetic>(m_scrollableArea, [this](FloatPoint&& position) {
+#if ENABLE(SMOOTH_SCROLLING)
+        if (m_smoothAnimation)
+            m_smoothAnimation->setCurrentPosition(position);
+#endif
+        updatePosition(WTFMove(position));
+    });
+
 #if ENABLE(SMOOTH_SCROLLING)
     if (scrollableArea.scrollAnimatorEnabled())
         ensureSmoothScrollingAnimation();
@@ -63,48 +73,108 @@ ScrollAnimatorGtk::~ScrollAnimatorGtk()
 #if ENABLE(SMOOTH_SCROLLING)
 void ScrollAnimatorGtk::ensureSmoothScrollingAnimation()
 {
-    if (m_animation)
+    if (m_smoothAnimation)
         return;
 
-    m_animation = std::make_unique<ScrollAnimationSmooth>(m_scrollableArea, m_currentPosition, [this](FloatPoint&& position) {
-        FloatSize delta = position - m_currentPosition;
-        m_currentPosition = WTFMove(position);
-        notifyPositionChanged(delta);
+    m_smoothAnimation = std::make_unique<ScrollAnimationSmooth>(m_scrollableArea, m_currentPosition, [this](FloatPoint&& position) {
+        updatePosition(WTFMove(position));
     });
 }
+#endif
 
+#if ENABLE(SMOOTH_SCROLLING)
 bool ScrollAnimatorGtk::scroll(ScrollbarOrientation orientation, ScrollGranularity granularity, float step, float multiplier)
 {
     if (!m_scrollableArea.scrollAnimatorEnabled() || granularity == ScrollByPrecisePixel)
         return ScrollAnimator::scroll(orientation, granularity, step, multiplier);
 
     ensureSmoothScrollingAnimation();
-    return m_animation->scroll(orientation, granularity, step, multiplier);
+    return m_smoothAnimation->scroll(orientation, granularity, step, multiplier);
 }
+#endif
 
 void ScrollAnimatorGtk::scrollToOffsetWithoutAnimation(const FloatPoint& offset)
 {
     FloatPoint position = ScrollableArea::scrollPositionFromOffset(offset, toFloatSize(m_scrollableArea.scrollOrigin()));
-    if (m_animation)
-        m_animation->setCurrentPosition(position);
+    m_kineticAnimation->stop();
+    m_scrollHistory.clear();
 
-    FloatSize delta = position - m_currentPosition;
-    m_currentPosition = position;
-    notifyPositionChanged(delta);
+#if ENABLE(SMOOTH_SCROLLING)
+    if (m_smoothAnimation)
+        m_smoothAnimation->setCurrentPosition(position);
+#endif
+
+    updatePosition(WTFMove(position));
 }
 
-void ScrollAnimatorGtk::willEndLiveResize()
+FloatPoint ScrollAnimatorGtk::computeVelocity()
 {
-    if (m_animation)
-        m_animation->updateVisibleLengths();
+    if (m_scrollHistory.isEmpty())
+        return { };
+
+    double first = m_scrollHistory[0].timestamp();
+    double last = m_scrollHistory.rbegin()->timestamp();
+
+    if (last == first)
+        return { };
+
+    FloatPoint accumDelta;
+    for (const auto& scrollEvent : m_scrollHistory)
+        accumDelta += FloatPoint(scrollEvent.deltaX(), scrollEvent.deltaY());
+
+    m_scrollHistory.clear();
+
+    return FloatPoint(accumDelta.x() * -1000 / (last - first), accumDelta.y() * -1000 / (last - first));
 }
+
+bool ScrollAnimatorGtk::handleWheelEvent(const PlatformWheelEvent& event)
+{
+    m_kineticAnimation->stop();
+
+    m_scrollHistory.removeAllMatching([&event] (PlatformWheelEvent& otherEvent) -> bool {
+        return Seconds::fromMilliseconds(event.timestamp() - otherEvent.timestamp()) > scrollCaptureThreshold;
+    });
+
+    if (event.isEndOfNonMomentumScroll()) {
+        // We don't need to add the event to the history as its delta will be (0, 0).
+        static_cast<ScrollAnimationKinetic*>(m_kineticAnimation.get())->start(m_currentPosition, computeVelocity(), m_scrollableArea.horizontalScrollbar(), m_scrollableArea.verticalScrollbar());
+        return true;
+    }
+    if (event.isTransitioningToMomentumScroll()) {
+        m_scrollHistory.clear();
+        static_cast<ScrollAnimationKinetic*>(m_kineticAnimation.get())->start(m_currentPosition, event.swipeVelocity(), m_scrollableArea.horizontalScrollbar(), m_scrollableArea.verticalScrollbar());
+        return true;
+    }
+
+    m_scrollHistory.append(event);
+
+    return ScrollAnimator::handleWheelEvent(event);
+}
+
+void ScrollAnimatorGtk::willEndLiveResize()
+{
+    m_kineticAnimation->updateVisibleLengths();
+
+#if ENABLE(SMOOTH_SCROLLING)
+    if (m_smoothAnimation)
+        m_smoothAnimation->updateVisibleLengths();
 #endif
+}
+
+void ScrollAnimatorGtk::updatePosition(FloatPoint&& position)
+{
+    FloatSize delta = position - m_currentPosition;
+    m_currentPosition = WTFMove(position);
+    notifyPositionChanged(delta);
+}
 
 void ScrollAnimatorGtk::didAddVerticalScrollbar(Scrollbar* scrollbar)
 {
+    m_kineticAnimation->updateVisibleLengths();
+
 #if ENABLE(SMOOTH_SCROLLING)
-    if (m_animation)
-        m_animation->updateVisibleLengths();
+    if (m_smoothAnimation)
+        m_smoothAnimation->updateVisibleLengths();
 #endif
     if (!scrollbar->isOverlayScrollbar())
         return;
@@ -117,9 +187,11 @@ void ScrollAnimatorGtk::didAddVerticalScrollbar(Scrollbar* scrollbar)
 
 void ScrollAnimatorGtk::didAddHorizontalScrollbar(Scrollbar* scrollbar)
 {
+    m_kineticAnimation->updateVisibleLengths();
+
 #if ENABLE(SMOOTH_SCROLLING)
-    if (m_animation)
-        m_animation->updateVisibleLengths();
+    if (m_smoothAnimation)
+        m_smoothAnimation->updateVisibleLengths();
 #endif
     if (!scrollbar->isOverlayScrollbar())
         return;
index c4253e5..9614e63 100644 (file)
@@ -46,9 +46,11 @@ public:
 private:
 #if ENABLE(SMOOTH_SCROLLING)
     bool scroll(ScrollbarOrientation, ScrollGranularity, float step, float multiplier) override;
+#endif
     void scrollToOffsetWithoutAnimation(const FloatPoint&) override;
     void willEndLiveResize() override;
-#endif
+
+    bool handleWheelEvent(const PlatformWheelEvent&) override;
 
     void didAddVerticalScrollbar(Scrollbar*) override;
     void didAddHorizontalScrollbar(Scrollbar*) override;
@@ -63,16 +65,22 @@ private:
     void notifyContentAreaScrolled(const FloatSize& delta) override;
     void lockOverlayScrollbarStateToHidden(bool) override;
 
+    void updatePosition(FloatPoint&&);
+
     void overlayScrollbarAnimationTimerFired();
     void showOverlayScrollbars();
     void hideOverlayScrollbars();
     void updateOverlayScrollbarsOpacity();
 
+    FloatPoint computeVelocity();
+
 #if ENABLE(SMOOTH_SCROLLING)
     void ensureSmoothScrollingAnimation();
 
-    std::unique_ptr<ScrollAnimation> m_animation;
+    std::unique_ptr<ScrollAnimation> m_smoothAnimation;
 #endif
+    std::unique_ptr<ScrollAnimation> m_kineticAnimation;
+    Vector<PlatformWheelEvent> m_scrollHistory;
     Scrollbar* m_horizontalOverlayScrollbar { nullptr };
     Scrollbar* m_verticalOverlayScrollbar { nullptr };
     bool m_overlayScrollbarsLocked { false };
index e088177..3ea94b2 100644 (file)
@@ -1,3 +1,51 @@
+2017-06-09  Adrien Plazas  <aplazas@igalia.com>
+
+        [GTK] Add kinetic scrolling
+        https://bugs.webkit.org/show_bug.cgi?id=155750
+
+        Reviewed by Carlos Garcia Campos.
+
+        Patch by Adrien Plazas and Yusuke Suzuki.
+
+        Add the notion of phase and momentum phase to WebWheelEvent.
+
+        Make WebWheelEvent manage the 'is_stop' attribute of GdkEventScroll to create the corresponding WebWheelEvent
+        with the correct phases and deltas.
+
+        Make GestureController manage swipes to create the corresponding WebWheelEvent with the correct phases and
+        deltas.
+
+        * Shared/NativeWebWheelEvent.h:
+        * Shared/WebEvent.h:
+        * Shared/WebEventConversion.cpp:
+        (WebKit::WebKit2PlatformWheelEvent::WebKit2PlatformWheelEvent):
+        * Shared/WebWheelEvent.cpp:
+        (WebKit::WebWheelEvent::WebWheelEvent):
+        (WebKit::WebWheelEvent::encode):
+        (WebKit::WebWheelEvent::decode):
+        * Shared/gtk/NativeWebWheelEventGtk.cpp:
+        (WebKit::NativeWebWheelEvent::NativeWebWheelEvent):
+        * Shared/gtk/WebEventFactory.cpp:
+        (WebKit::WebEventFactory::createWebWheelEvent):
+        * Shared/gtk/WebEventFactory.h:
+        * UIProcess/WebPageProxy.cpp:
+        (WebKit::WebPageProxy::handleWheelEvent):
+        (WebKit::WebPageProxy::shouldProcessWheelEventNow):
+        * UIProcess/WebPageProxy.h:
+        * UIProcess/gtk/GestureController.cpp:
+        (WebKit::GestureController::GestureController):
+        (WebKit::GestureController::handleEvent):
+        (WebKit::GestureController::isProcessingGestures):
+        (WebKit::createScrollEvent):
+        (WebKit::GestureController::DragGesture::startDrag):
+        (WebKit::GestureController::DragGesture::handleDrag):
+        (WebKit::GestureController::DragGesture::begin):
+        (WebKit::GestureController::DragGesture::end):
+        (WebKit::GestureController::SwipeGesture::startMomentumScroll):
+        (WebKit::GestureController::SwipeGesture::swipe):
+        (WebKit::GestureController::SwipeGesture::SwipeGesture):
+        * UIProcess/gtk/GestureController.h:
+
 2017-06-09  Jer Noble  <jer.noble@apple.com>
 
         Crash in -[WKWebView _initializeWithConfiguration:]
index 15f9586..f3572af 100644 (file)
@@ -51,6 +51,7 @@ public:
 #elif PLATFORM(GTK)
     NativeWebWheelEvent(const NativeWebWheelEvent&);
     NativeWebWheelEvent(GdkEvent*);
+    NativeWebWheelEvent(GdkEvent*, WebWheelEvent::Phase, WebWheelEvent::Phase momentumPhase);
 #elif PLATFORM(WPE)
     NativeWebWheelEvent(struct wpe_input_axis_event*, float deviceScaleFactor);
 #endif
index 4eb51e3..ecd7c38 100644 (file)
@@ -183,7 +183,7 @@ public:
         ScrollByPixelWheelEvent
     };
 
-#if PLATFORM(COCOA)
+#if PLATFORM(COCOA) || PLATFORM(GTK)
     enum Phase {
         PhaseNone        = 0,
         PhaseBegan       = 1 << 0,
@@ -200,6 +200,8 @@ public:
     WebWheelEvent(Type, const WebCore::IntPoint& position, const WebCore::IntPoint& globalPosition, const WebCore::FloatSize& delta, const WebCore::FloatSize& wheelTicks, Granularity, Modifiers, double timestamp);
 #if PLATFORM(COCOA)
     WebWheelEvent(Type, const WebCore::IntPoint& position, const WebCore::IntPoint& globalPosition, const WebCore::FloatSize& delta, const WebCore::FloatSize& wheelTicks, Granularity, bool directionInvertedFromDevice, Phase, Phase momentumPhase, bool hasPreciseScrollingDeltas, uint32_t scrollCount, const WebCore::FloatSize& unacceleratedScrollingDelta, Modifiers, double timestamp);
+#elif PLATFORM(GTK)
+    WebWheelEvent(Type, const WebCore::IntPoint& position, const WebCore::IntPoint& globalPosition, const WebCore::FloatSize& delta, const WebCore::FloatSize& wheelTicks, Phase, Phase momentumPhase, Granularity, Modifiers, double timestamp);
 #endif
 
     const WebCore::IntPoint position() const { return m_position; }
@@ -208,9 +210,11 @@ public:
     const WebCore::FloatSize wheelTicks() const { return m_wheelTicks; }
     Granularity granularity() const { return static_cast<Granularity>(m_granularity); }
     bool directionInvertedFromDevice() const { return m_directionInvertedFromDevice; }
-#if PLATFORM(COCOA)
+#if PLATFORM(COCOA) || PLATFORM(GTK)
     Phase phase() const { return static_cast<Phase>(m_phase); }
     Phase momentumPhase() const { return static_cast<Phase>(m_momentumPhase); }
+#endif
+#if PLATFORM(COCOA)
     bool hasPreciseScrollingDeltas() const { return m_hasPreciseScrollingDeltas; }
     uint32_t scrollCount() const { return m_scrollCount; }
     const WebCore::FloatSize& unacceleratedScrollingDelta() const { return m_unacceleratedScrollingDelta; }
@@ -228,9 +232,11 @@ private:
     WebCore::FloatSize m_wheelTicks;
     uint32_t m_granularity; // Granularity
     bool m_directionInvertedFromDevice;
+#if PLATFORM(COCOA) || PLATFORM(GTK)
+    uint32_t m_phase { Phase::PhaseNone };
+    uint32_t m_momentumPhase { Phase::PhaseNone };
+#endif
 #if PLATFORM(COCOA)
-    uint32_t m_phase; // Phase
-    uint32_t m_momentumPhase; // Phase
     bool m_hasPreciseScrollingDeltas;
     uint32_t m_scrollCount;
     WebCore::FloatSize m_unacceleratedScrollingDelta;
index c46bf74..70b9280 100644 (file)
@@ -155,9 +155,11 @@ public:
         m_wheelTicksY = webEvent.wheelTicks().height();
         m_granularity = (webEvent.granularity() == WebWheelEvent::ScrollByPageWheelEvent) ? WebCore::ScrollByPageWheelEvent : WebCore::ScrollByPixelWheelEvent;
         m_directionInvertedFromDevice = webEvent.directionInvertedFromDevice();
-#if PLATFORM(COCOA)
+#if PLATFORM(COCOA) || PLATFORM(GTK)
         m_phase = static_cast<WebCore::PlatformWheelEventPhase>(webEvent.phase());
         m_momentumPhase = static_cast<WebCore::PlatformWheelEventPhase>(webEvent.momentumPhase());
+#endif
+#if PLATFORM(COCOA)
         m_hasPreciseScrollingDeltas = webEvent.hasPreciseScrollingDeltas();
         m_scrollCount = webEvent.scrollCount();
         m_unacceleratedScrollingDeltaX = webEvent.unacceleratedScrollingDelta().width();
index 7ddce0c..13fe48d 100644 (file)
@@ -66,6 +66,20 @@ WebWheelEvent::WebWheelEvent(Type type, const IntPoint& position, const IntPoint
 {
     ASSERT(isWheelEventType(type));
 }
+#elif PLATFORM(GTK)
+WebWheelEvent::WebWheelEvent(Type type, const IntPoint& position, const IntPoint& globalPosition, const FloatSize& delta, const FloatSize& wheelTicks, Phase phase, Phase momentumPhase, Granularity granularity, Modifiers modifiers, double timestamp)
+    : WebEvent(type, modifiers, timestamp)
+    , m_position(position)
+    , m_globalPosition(globalPosition)
+    , m_delta(delta)
+    , m_wheelTicks(wheelTicks)
+    , m_granularity(granularity)
+    , m_directionInvertedFromDevice(false)
+    , m_phase(phase)
+    , m_momentumPhase(momentumPhase)
+{
+    ASSERT(isWheelEventType(type));
+}
 #endif
 
 void WebWheelEvent::encode(IPC::Encoder& encoder) const
@@ -78,9 +92,11 @@ void WebWheelEvent::encode(IPC::Encoder& encoder) const
     encoder << m_wheelTicks;
     encoder << m_granularity;
     encoder << m_directionInvertedFromDevice;
-#if PLATFORM(COCOA)
+#if PLATFORM(COCOA) || PLATFORM(GTK)
     encoder << m_phase;
     encoder << m_momentumPhase;
+#endif
+#if PLATFORM(COCOA)
     encoder << m_hasPreciseScrollingDeltas;
     encoder << m_scrollCount;
     encoder << m_unacceleratedScrollingDelta;
@@ -103,11 +119,13 @@ bool WebWheelEvent::decode(IPC::Decoder& decoder, WebWheelEvent& t)
         return false;
     if (!decoder.decode(t.m_directionInvertedFromDevice))
         return false;
-#if PLATFORM(COCOA)
+#if PLATFORM(COCOA) || PLATFORM(GTK)
     if (!decoder.decode(t.m_phase))
         return false;
     if (!decoder.decode(t.m_momentumPhase))
         return false;
+#endif
+#if PLATFORM(COCOA)
     if (!decoder.decode(t.m_hasPreciseScrollingDeltas))
         return false;
     if (!decoder.decode(t.m_scrollCount))
index b367612..5caf3d2 100644 (file)
@@ -37,8 +37,14 @@ NativeWebWheelEvent::NativeWebWheelEvent(GdkEvent* event)
 {
 }
 
+NativeWebWheelEvent::NativeWebWheelEvent(GdkEvent* event, WebWheelEvent::Phase phase, WebWheelEvent::Phase momentumPhase)
+    : WebWheelEvent(WebEventFactory::createWebWheelEvent(event, phase, momentumPhase))
+    , m_nativeEvent(gdk_event_copy(event))
+{
+}
+
 NativeWebWheelEvent::NativeWebWheelEvent(const NativeWebWheelEvent& event)
-    : WebWheelEvent(WebEventFactory::createWebWheelEvent(event.nativeEvent()))
+    : WebWheelEvent(WebEventFactory::createWebWheelEvent(event.nativeEvent(), event.phase(), event.momentumPhase()))
     , m_nativeEvent(gdk_event_copy(event.nativeEvent()))
 {
 }
index afb5f84..68d6d38 100644 (file)
@@ -145,6 +145,27 @@ WebMouseEvent WebEventFactory::createWebMouseEvent(const GdkEvent* event, int cu
 
 WebWheelEvent WebEventFactory::createWebWheelEvent(const GdkEvent* event)
 {
+#ifndef GTK_API_VERSION_2
+#if GTK_CHECK_VERSION(3, 20, 0)
+    WebWheelEvent::Phase phase = gdk_event_is_scroll_stop_event(event) ?
+        WebWheelEvent::Phase::PhaseEnded :
+        WebWheelEvent::Phase::PhaseChanged;
+#else
+    double deltaX, deltaY;
+    gdk_event_get_scroll_deltas(event, &deltaX, &deltaY);
+    WebWheelEvent::Phase phase = event->scroll.direction == GDK_SCROLL_SMOOTH && !deltaX && !deltaY ?
+        WebWheelEvent::Phase::PhaseEnded :
+        WebWheelEvent::Phase::PhaseChanged;
+#endif
+#else
+    WebWheelEvent::Phase phase = WebWheelEvent::Phase::PhaseChanged;
+#endif // GTK_API_VERSION_2
+
+    return createWebWheelEvent(event, phase, WebWheelEvent::Phase::PhaseNone);
+}
+
+WebWheelEvent WebEventFactory::createWebWheelEvent(const GdkEvent* event, WebWheelEvent::Phase phase, WebWheelEvent::Phase momentumPhase)
+{
     double x, y, xRoot, yRoot;
     gdk_event_get_coords(event, &x, &y);
     gdk_event_get_root_coords(event, &xRoot, &yRoot);
@@ -181,13 +202,15 @@ WebWheelEvent WebEventFactory::createWebWheelEvent(const GdkEvent* event)
     FloatSize delta(wheelTicks.width() * step, wheelTicks.height() * step);
 
     return WebWheelEvent(WebEvent::Wheel,
-                         IntPoint(x, y),
-                         IntPoint(xRoot, yRoot),
-                         delta,
-                         wheelTicks,
-                         WebWheelEvent::ScrollByPixelWheelEvent,
-                         modifiersForEvent(event),
-                         gdk_event_get_time(event));
+        IntPoint(x, y),
+        IntPoint(xRoot, yRoot),
+        delta,
+        wheelTicks,
+        phase,
+        momentumPhase,
+        WebWheelEvent::ScrollByPixelWheelEvent,
+        modifiersForEvent(event),
+        gdk_event_get_time(event));
 }
 
 WebKeyboardEvent WebEventFactory::createWebKeyboardEvent(const GdkEvent* event, const WebCore::CompositionResults& compositionResults, Vector<String>&& commands)
index 28b6953..8fe7cdc 100644 (file)
@@ -38,6 +38,7 @@ class WebEventFactory {
 public:
     static WebMouseEvent createWebMouseEvent(const GdkEvent*, int);
     static WebWheelEvent createWebWheelEvent(const GdkEvent*);
+    static WebWheelEvent createWebWheelEvent(const GdkEvent*, WebWheelEvent::Phase, WebWheelEvent::Phase momentumPhase);
     static WebKeyboardEvent createWebKeyboardEvent(const GdkEvent*, const WebCore::CompositionResults&, Vector<String>&& commands);
 #if ENABLE(TOUCH_EVENTS)
     static WebTouchEvent createWebTouchEvent(const GdkEvent*, Vector<WebPlatformTouchPoint>&&);
index 3a47894..b1702c5 100644 (file)
@@ -1886,7 +1886,7 @@ void WebPageProxy::handleWheelEvent(const NativeWebWheelEvent& event)
 
     if (!m_currentlyProcessedWheelEvents.isEmpty()) {
         m_wheelEventQueue.append(event);
-        if (m_wheelEventQueue.size() < wheelEventQueueSizeThreshold)
+        if (!shouldProcessWheelEventNow(event))
             return;
         // The queue has too many wheel events, so push a new event.
     }
@@ -1927,6 +1927,27 @@ void WebPageProxy::sendWheelEvent(const WebWheelEvent& event)
     m_process->isResponsive(nullptr);
 }
 
+bool WebPageProxy::shouldProcessWheelEventNow(const WebWheelEvent& event) const
+{
+#if PLATFORM(GTK)
+    // Don't queue events representing a non-trivial scrolling phase to
+    // avoid having them trapped in the queue, potentially preventing a
+    // scrolling session to beginning or end correctly.
+    // This is only needed by platforms whose WebWheelEvent has this phase
+    // information (Cocoa and GTK+) but Cocoa was fine without it.
+    if (event.phase() == WebWheelEvent::Phase::PhaseNone
+        || event.phase() == WebWheelEvent::Phase::PhaseChanged
+        || event.momentumPhase() == WebWheelEvent::Phase::PhaseNone
+        || event.momentumPhase() == WebWheelEvent::Phase::PhaseChanged)
+        return true;
+#else
+    UNUSED_PARAM(event);
+#endif
+    if (m_wheelEventQueue.size() >= wheelEventQueueSizeThreshold)
+        return true;
+    return false;
+}
+
 void WebPageProxy::handleKeyboardEvent(const NativeWebKeyboardEvent& event)
 {
     if (!isValid())
index f82c020..e535bac 100644 (file)
@@ -1557,6 +1557,7 @@ private:
 
     void processNextQueuedWheelEvent();
     void sendWheelEvent(const WebWheelEvent&);
+    bool shouldProcessWheelEventNow(const WebWheelEvent&) const;
 
 #if ENABLE(TOUCH_EVENTS)
     void updateTouchEventTracking(const WebTouchEvent&);
index a373429..114cc5f 100644 (file)
@@ -41,6 +41,7 @@ namespace WebKit {
 
 GestureController::GestureController(WebPageProxy& page)
     : m_dragGesture(page)
+    , m_swipeGesture(page)
     , m_zoomGesture(page)
 {
 }
@@ -49,13 +50,14 @@ bool GestureController::handleEvent(const GdkEvent* event)
 {
     bool wasProcessingGestures = isProcessingGestures();
     m_dragGesture.handleEvent(event);
+    m_swipeGesture.handleEvent(event);
     m_zoomGesture.handleEvent(event);
     return event->type == GDK_TOUCH_END ? wasProcessingGestures : isProcessingGestures();
 }
 
 bool GestureController::isProcessingGestures() const
 {
-    return m_dragGesture.isActive() || m_zoomGesture.isActive();
+    return m_dragGesture.isActive() || m_swipeGesture.isActive() || m_zoomGesture.isActive();
 }
 
 GestureController::Gesture::Gesture(GtkGesture* gesture, WebPageProxy& page)
@@ -75,20 +77,40 @@ void GestureController::Gesture::handleEvent(const GdkEvent* event)
     gtk_event_controller_handle_event(GTK_EVENT_CONTROLLER(m_gesture.get()), event);
 }
 
-void GestureController::DragGesture::handleDrag(const GdkEvent* event, double x, double y)
+static GUniquePtr<GdkEvent> createScrollEvent(const GdkEvent* event, double x, double y, double deltaX, double deltaY, gboolean isStop)
 {
-    ASSERT(m_inDrag);
     GUniquePtr<GdkEvent> scrollEvent(gdk_event_new(GDK_SCROLL));
     scrollEvent->scroll.time = event->touch.time;
-    scrollEvent->scroll.x = m_start.x();
-    scrollEvent->scroll.y = m_start.y();
+    scrollEvent->scroll.x = x;
+    scrollEvent->scroll.y = y;
     scrollEvent->scroll.x_root = event->touch.x_root;
     scrollEvent->scroll.y_root = event->touch.y_root;
     scrollEvent->scroll.direction = GDK_SCROLL_SMOOTH;
-    scrollEvent->scroll.delta_x = (m_offset.x() - x) / Scrollbar::pixelsPerLineStep();
-    scrollEvent->scroll.delta_y = (m_offset.y() - y) / Scrollbar::pixelsPerLineStep();
+    scrollEvent->scroll.delta_x = deltaX;
+    scrollEvent->scroll.delta_y = deltaY;
     scrollEvent->scroll.state = event->touch.state;
-    m_page.handleWheelEvent(NativeWebWheelEvent(scrollEvent.get()));
+#if GTK_CHECK_VERSION(3, 20, 0)
+    scrollEvent->scroll.is_stop = isStop;
+#endif
+    return scrollEvent;
+}
+
+void GestureController::DragGesture::startDrag(const GdkEvent* event)
+{
+    ASSERT(!m_inDrag);
+    GUniquePtr<GdkEvent> scrollEvent = createScrollEvent(event, m_start.x(), m_start.y(), 0, 0, FALSE);
+    m_page.handleWheelEvent(NativeWebWheelEvent(scrollEvent.get(), WebWheelEvent::Phase::PhaseBegan, WebWheelEvent::Phase::PhaseNone));
+}
+
+void GestureController::DragGesture::handleDrag(const GdkEvent* event, double x, double y)
+{
+    ASSERT(m_inDrag);
+    GUniquePtr<GdkEvent> scrollEvent = createScrollEvent(event,
+        m_start.x(), m_start.y(),
+        (m_offset.x() - x) / Scrollbar::pixelsPerLineStep(),
+        (m_offset.y() - y) / Scrollbar::pixelsPerLineStep(),
+        FALSE);
+    m_page.handleWheelEvent(NativeWebWheelEvent(scrollEvent.get(), WebWheelEvent::Phase::PhaseChanged, WebWheelEvent::Phase::PhaseNone));
 }
 
 void GestureController::DragGesture::handleTap(const GdkEvent* event)
@@ -128,6 +150,7 @@ void GestureController::DragGesture::begin(DragGesture* dragGesture, double x, d
     unsigned delay;
     g_object_get(gtk_widget_get_settings(widget), "gtk-long-press-time", &delay, nullptr);
     dragGesture->m_longPressTimeout.startOneShot(1_ms * delay);
+    dragGesture->startDrag(gtk_gesture_get_last_event(gesture, sequence));
 }
 
 void GestureController::DragGesture::update(DragGesture* dragGesture, double x, double y, GtkGesture* gesture)
@@ -149,11 +172,14 @@ void GestureController::DragGesture::update(DragGesture* dragGesture, double x,
 void GestureController::DragGesture::end(DragGesture* dragGesture, GdkEventSequence* sequence, GtkGesture* gesture)
 {
     dragGesture->m_longPressTimeout.stop();
+    if (!gtk_gesture_handles_sequence(gesture, sequence)) {
+        gtk_gesture_set_state(gesture, GTK_EVENT_SEQUENCE_DENIED);
+        return;
+    }
     if (!dragGesture->m_inDrag) {
         dragGesture->handleTap(gtk_gesture_get_last_event(gesture, sequence));
         gtk_gesture_set_state(gesture, GTK_EVENT_SEQUENCE_DENIED);
-    } else if (!gtk_gesture_handles_sequence(gesture, sequence))
-        gtk_gesture_set_state(gesture, GTK_EVENT_SEQUENCE_DENIED);
+    }
 }
 
 void GestureController::DragGesture::longPressFired()
@@ -172,6 +198,30 @@ GestureController::DragGesture::DragGesture(WebPageProxy& page)
     g_signal_connect_swapped(m_gesture.get(), "end", G_CALLBACK(end), this);
 }
 
+void GestureController::SwipeGesture::startMomentumScroll(const GdkEvent* event, double velocityX, double velocityY)
+{
+    GUniquePtr<GdkEvent> scrollEvent = createScrollEvent(event, event->touch.x, event->touch.y, velocityX, velocityY, TRUE);
+    m_page.handleWheelEvent(NativeWebWheelEvent(scrollEvent.get(), WebWheelEvent::Phase::PhaseNone, WebWheelEvent::Phase::PhaseBegan));
+}
+
+void GestureController::SwipeGesture::swipe(SwipeGesture* swipeGesture, double velocityX, double velocityY, GtkGesture* gesture)
+{
+    GdkEventSequence* sequence = gtk_gesture_single_get_current_sequence(GTK_GESTURE_SINGLE(gesture));
+    if (!gtk_gesture_handles_sequence(gesture, sequence))
+        return;
+
+    gtk_gesture_set_sequence_state(gesture, sequence, GTK_EVENT_SEQUENCE_CLAIMED);
+
+    swipeGesture->startMomentumScroll(gtk_gesture_get_last_event(gesture, sequence), velocityX, velocityY);
+}
+
+GestureController::SwipeGesture::SwipeGesture(WebPageProxy& page)
+    : Gesture(gtk_gesture_swipe_new(page.viewWidget()), page)
+{
+    gtk_gesture_single_set_touch_only(GTK_GESTURE_SINGLE(m_gesture.get()), TRUE);
+    g_signal_connect_swapped(m_gesture.get(), "swipe", G_CALLBACK(swipe), this);
+}
+
 IntPoint GestureController::ZoomGesture::center() const
 {
     double x, y;
index 86792df..e96031f 100644 (file)
@@ -67,6 +67,8 @@ private:
         DragGesture(WebPageProxy&);
 
     private:
+        // Notify that a drag started, allowing to stop kinetic deceleration.
+        void startDrag(const GdkEvent*);
         void handleDrag(const GdkEvent*, double x, double y);
         void handleTap(const GdkEvent*);
         void longPressFired();
@@ -82,6 +84,16 @@ private:
         bool m_inDrag;
     };
 
+    class SwipeGesture final : public Gesture {
+    public:
+        SwipeGesture(WebPageProxy&);
+
+    private:
+        void startMomentumScroll(const GdkEvent*, double velocityX, double velocityY);
+
+        static void swipe(SwipeGesture*, double velocityX, double velocityY, GtkGesture*);
+    };
+
     class ZoomGesture final : public Gesture {
     public:
         ZoomGesture(WebPageProxy&);
@@ -101,6 +113,7 @@ private:
     };
 
     DragGesture m_dragGesture;
+    SwipeGesture m_swipeGesture;
     ZoomGesture m_zoomGesture;
 };