Bug 1213601 - implement kinetic/inertial scrolling (fling) for Gtk. r=botond
authormyfreeweb <greg@unrelenting.technology>
Thu, 20 Jun 2019 16:18:45 -0400
changeset 479710 d9f7c87aa3b474e99c789b91708b35a288366f26
parent 479709 46fbd76b43e262194f58cbb015d022eee6104b7b
child 479711 51a70ac721c07aa77846d5320500960f15ee6fdb
push id113484
push userbballo@mozilla.com
push dateFri, 21 Jun 2019 00:25:37 +0000
treeherdermozilla-inbound@d9f7c87aa3b4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbotond
bugs1213601
milestone69.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1213601 - implement kinetic/inertial scrolling (fling) for Gtk. r=botond
gfx/layers/apz/src/AsyncPanZoomController.cpp
gfx/layers/ipc/LayersMessageUtils.h
ipc/glue/IPCMessageUtils.h
modules/libpref/init/StaticPrefList.h
widget/InputData.cpp
widget/InputData.h
widget/gtk/nsWindow.cpp
widget/gtk/nsWindow.h
widget/nsBaseWidget.cpp
widget/nsBaseWidget.h
widget/nsGUIEventIPC.h
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -1817,18 +1817,18 @@ nsEventStatus AsyncPanZoomController::On
         }
       }
     }
   }
   return nsEventStatus_eConsumeNoDefault;
 }
 
 nsEventStatus AsyncPanZoomController::HandleEndOfPan() {
-  MOZ_ASSERT(GetCurrentTouchBlock());
-  GetCurrentTouchBlock()->GetOverscrollHandoffChain()->FlushRepaints();
+  MOZ_ASSERT(GetCurrentTouchBlock() || GetCurrentPanGestureBlock());
+  GetCurrentInputBlock()->GetOverscrollHandoffChain()->FlushRepaints();
   ParentLayerPoint flingVelocity = GetVelocityVector();
 
   // Clear our velocities; if DispatchFling() gives the fling to us,
   // the fling velocity gets *added* to our existing velocity in
   // AcceptFling().
   mX.SetVelocity(0);
   mY.SetVelocity(0);
   // Clear our state so that we don't stay in the PANNING state
@@ -1840,29 +1840,29 @@ nsEventStatus AsyncPanZoomController::Ha
 
   APZC_LOG("%p starting a fling animation if %f >= %f\n", this,
            flingVelocity.Length().value,
            StaticPrefs::APZFlingMinVelocityThreshold());
 
   if (flingVelocity.Length() < StaticPrefs::APZFlingMinVelocityThreshold()) {
     // Relieve overscroll now if needed, since we will not transition to a fling
     // animation and then an overscroll animation, and relieve it then.
-    GetCurrentTouchBlock()
+    GetCurrentInputBlock()
         ->GetOverscrollHandoffChain()
         ->SnapBackOverscrolledApzc(this);
     return nsEventStatus_eConsumeNoDefault;
   }
 
   // Make a local copy of the tree manager pointer and check that it's not
   // null before calling DispatchFling(). This is necessary because Destroy(),
   // which nulls out mTreeManager, could be called concurrently.
   if (APZCTreeManager* treeManagerLocal = GetApzcTreeManager()) {
     const FlingHandoffState handoffState{
-        flingVelocity, GetCurrentTouchBlock()->GetOverscrollHandoffChain(),
-        false /* not handoff */, GetCurrentTouchBlock()->GetScrolledApzc()};
+        flingVelocity, GetCurrentInputBlock()->GetOverscrollHandoffChain(),
+        false /* not handoff */, GetCurrentInputBlock()->GetScrolledApzc()};
     treeManagerLocal->DispatchFling(this, handoffState);
   }
   return nsEventStatus_eConsumeNoDefault;
 }
 
 bool AsyncPanZoomController::ConvertToGecko(const ScreenIntPoint& aPoint,
                                             LayoutDevicePoint* aOut) {
   if (APZCTreeManager* treeManagerLocal = GetApzcTreeManager()) {
@@ -2551,16 +2551,45 @@ nsEventStatus AsyncPanZoomController::On
   // Note that there is a multiplier that applies onto the "physical" pan
   // displacement (how much the user's fingers moved) that produces the
   // "logical" pan displacement (how much the page should move). For some of the
   // code below it makes more sense to use the physical displacement rather than
   // the logical displacement, and vice-versa.
   ScreenPoint physicalPanDisplacement = aEvent.mPanDisplacement;
   ParentLayerPoint logicalPanDisplacement =
       aEvent.UserMultipliedLocalPanDisplacement();
+  if (aEvent.mDeltaType == PanGestureInput::PANDELTA_PAGE) {
+    // Pan events with page units are used by Gtk, so this replicates Gtk:
+    // https://gitlab.gnome.org/GNOME/gtk/blob/c734c7e9188b56f56c3a504abee05fa40c5475ac/gtk/gtkrange.c#L3065-3073
+    CSSSize pageScrollSize;
+    CSSToParentLayerScale2D zoom;
+    {
+      // Grab the lock to access the frame metrics.
+      RecursiveMutexAutoLock lock(mRecursiveMutex);
+      pageScrollSize = mScrollMetadata.GetPageScrollAmount() /
+                       Metrics().GetDevPixelsPerCSSPixel();
+      zoom = Metrics().GetZoom();
+    }
+    // scrollUnit* is in units of "ParentLayer pixels per page proportion"...
+    auto scrollUnitWidth = std::min(std::pow(pageScrollSize.width, 2.0 / 3.0),
+                                    pageScrollSize.width / 2.0) *
+                           zoom.xScale;
+    auto scrollUnitHeight = std::min(std::pow(pageScrollSize.height, 2.0 / 3.0),
+                                     pageScrollSize.height / 2.0) *
+                            zoom.yScale;
+    // ... and pan displacements are in units of "page proportion count"
+    // here, so the products of them and scrollUnit* are in ParentLayer pixels
+    ParentLayerPoint physicalPanDisplacementPL(
+        physicalPanDisplacement.x * scrollUnitWidth,
+        physicalPanDisplacement.y * scrollUnitHeight);
+    physicalPanDisplacement = ToScreenCoordinates(physicalPanDisplacementPL,
+        aEvent.mLocalPanStartPoint);
+    logicalPanDisplacement.x *= scrollUnitWidth;
+    logicalPanDisplacement.y *= scrollUnitHeight;
+  }
 
   MOZ_ASSERT(GetCurrentPanGestureBlock());
   AdjustDeltaForAllowedScrollDirections(
       logicalPanDisplacement,
       GetCurrentPanGestureBlock()->GetAllowedScrollDirections());
 
   // We need to update the axis velocity in order to get a useful display port
   // size and position. We need to do so even if this is a momentum pan (i.e.
@@ -2604,16 +2633,22 @@ nsEventStatus AsyncPanZoomController::On
   APZC_LOG("%p got a pan-end in state %d\n", this, mState);
 
   // Call into OnPan in order to process any delta included in this event.
   OnPan(aEvent, true);
 
   mX.EndTouch(aEvent.mTime);
   mY.EndTouch(aEvent.mTime);
 
+  // Use HandleEndOfPan for fling on platforms that don't
+  // emit momentum events (Gtk).
+  if (aEvent.mSimulateMomentum) {
+    return HandleEndOfPan();
+  }
+
   // Drop any velocity on axes where we don't have room to scroll anyways
   // (in this APZC, or an APZC further in the handoff chain).
   // This ensures that we don't enlarge the display port unnecessarily.
   MOZ_ASSERT(GetCurrentPanGestureBlock());
   RefPtr<const OverscrollHandoffChain> overscrollHandoffChain =
       GetCurrentPanGestureBlock()->GetOverscrollHandoffChain();
   if (!overscrollHandoffChain->CanScrollInDirection(
           this, ScrollDirection::eHorizontal)) {
--- a/gfx/layers/ipc/LayersMessageUtils.h
+++ b/gfx/layers/ipc/LayersMessageUtils.h
@@ -162,35 +162,16 @@ struct ParamTraits<mozilla::layers::Comp
     WriteParam(msg, param.mHandle);
   }
   static bool Read(const Message* msg, PickleIterator* iter,
                    paramType* result) {
     return ReadParam(msg, iter, &result->mHandle);
   }
 };
 
-// Helper class for reading bitfields.
-// If T has bitfields members, derive ParamTraits<T> from BitfieldHelper<T>.
-template <typename ParamType>
-struct BitfieldHelper {
-  // We need this helper because we can't get the address of a bitfield to
-  // pass directly to ReadParam. So instead we read it into a temporary bool
-  // and set the bitfield using a setter function
-  static bool ReadBoolForBitfield(const Message* aMsg, PickleIterator* aIter,
-                                  ParamType* aResult,
-                                  void (ParamType::*aSetter)(bool)) {
-    bool value;
-    if (ReadParam(aMsg, aIter, &value)) {
-      (aResult->*aSetter)(value);
-      return true;
-    }
-    return false;
-  }
-};
-
 template <>
 struct ParamTraits<mozilla::layers::FrameMetrics>
     : BitfieldHelper<mozilla::layers::FrameMetrics> {
   typedef mozilla::layers::FrameMetrics paramType;
 
   static void Write(Message* aMsg, const paramType& aParam) {
     WriteParam(aMsg, aParam.mScrollId);
     WriteParam(aMsg, aParam.mPresShellResolution);
--- a/ipc/glue/IPCMessageUtils.h
+++ b/ipc/glue/IPCMessageUtils.h
@@ -1083,11 +1083,30 @@ struct CrossOriginPolicyValidator {
   }
 };
 
 template <>
 struct ParamTraits<nsILoadInfo::CrossOriginPolicy>
     : EnumSerializer<nsILoadInfo::CrossOriginPolicy,
                      CrossOriginPolicyValidator> {};
 
+// Helper class for reading bitfields.
+// If T has bitfields members, derive ParamTraits<T> from BitfieldHelper<T>.
+template <typename ParamType>
+struct BitfieldHelper {
+  // We need this helper because we can't get the address of a bitfield to
+  // pass directly to ReadParam. So instead we read it into a temporary bool
+  // and set the bitfield using a setter function
+  static bool ReadBoolForBitfield(const Message* aMsg, PickleIterator* aIter,
+                                  ParamType* aResult,
+                                  void (ParamType::*aSetter)(bool)) {
+    bool value;
+    if (ReadParam(aMsg, aIter, &value)) {
+      (aResult->*aSetter)(value);
+      return true;
+    }
+    return false;
+  }
+};
+
 } /* namespace IPC */
 
 #endif /* __IPC_GLUE_IPCMESSAGEUTILS_H__ */
--- a/modules/libpref/init/StaticPrefList.h
+++ b/modules/libpref/init/StaticPrefList.h
@@ -401,16 +401,25 @@ VARCACHE_PREF(
 
 VARCACHE_PREF(
   Live,
   "apz.frame_delay.enabled",
   APZFrameDelayEnabled,
   RelaxedAtomicBool, false
 )
 
+#ifdef MOZ_WIDGET_GTK
+VARCACHE_PREF(
+  Live,
+  "apz.gtk.kinetic_scroll.enabled",
+  APZGTKKineticScrollEnabled,
+  RelaxedAtomicBool, false
+)
+#endif
+
 #if !defined(MOZ_WIDGET_ANDROID)
 # define PREF_VALUE true
 #else
 # define PREF_VALUE false
 #endif
 VARCACHE_PREF(
   Once,
   "apz.keyboard.enabled",
--- a/widget/InputData.cpp
+++ b/widget/InputData.cpp
@@ -432,17 +432,18 @@ PanGestureInput::PanGestureInput()
       mType(PANGESTURE_MAYSTART),
       mLineOrPageDeltaX(0),
       mLineOrPageDeltaY(0),
       mUserDeltaMultiplierX(1.0),
       mUserDeltaMultiplierY(1.0),
       mHandledByAPZ(false),
       mFollowedByMomentum(false),
       mRequiresContentResponseIfCannotScrollHorizontallyInStartDirection(false),
-      mOverscrollBehaviorAllowsSwipe(false) {}
+      mOverscrollBehaviorAllowsSwipe(false),
+      mSimulateMomentum(false) {}
 
 PanGestureInput::PanGestureInput(PanGestureType aType, uint32_t aTime,
                                  TimeStamp aTimeStamp,
                                  const ScreenPoint& aPanStartPoint,
                                  const ScreenPoint& aPanDisplacement,
                                  Modifiers aModifiers)
     : InputData(PANGESTURE_INPUT, aTime, aTimeStamp, aModifiers),
       mType(aType),
@@ -450,17 +451,18 @@ PanGestureInput::PanGestureInput(PanGest
       mPanDisplacement(aPanDisplacement),
       mLineOrPageDeltaX(0),
       mLineOrPageDeltaY(0),
       mUserDeltaMultiplierX(1.0),
       mUserDeltaMultiplierY(1.0),
       mHandledByAPZ(false),
       mFollowedByMomentum(false),
       mRequiresContentResponseIfCannotScrollHorizontallyInStartDirection(false),
-      mOverscrollBehaviorAllowsSwipe(false) {}
+      mOverscrollBehaviorAllowsSwipe(false),
+      mSimulateMomentum(false) {}
 
 bool PanGestureInput::IsMomentum() const {
   switch (mType) {
     case PanGestureInput::PANGESTURE_MOMENTUMSTART:
     case PanGestureInput::PANGESTURE_MOMENTUMPAN:
     case PanGestureInput::PANGESTURE_MOMENTUMEND:
       return true;
     default:
@@ -472,37 +474,54 @@ WidgetWheelEvent PanGestureInput::ToWidg
   WidgetWheelEvent wheelEvent(true, eWheel, aWidget);
   wheelEvent.mModifiers = this->modifiers;
   wheelEvent.mTime = mTime;
   wheelEvent.mTimeStamp = mTimeStamp;
   wheelEvent.mRefPoint = RoundedToInt(ViewAs<LayoutDevicePixel>(
       mPanStartPoint,
       PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent));
   wheelEvent.mButtons = 0;
-  wheelEvent.mDeltaMode = WheelEvent_Binding::DOM_DELTA_PIXEL;
   wheelEvent.mMayHaveMomentum = true;  // pan inputs may have momentum
   wheelEvent.mIsMomentum = IsMomentum();
   wheelEvent.mLineOrPageDeltaX = mLineOrPageDeltaX;
   wheelEvent.mLineOrPageDeltaY = mLineOrPageDeltaY;
   wheelEvent.mDeltaX = mPanDisplacement.x;
   wheelEvent.mDeltaY = mPanDisplacement.y;
   wheelEvent.mFlags.mHandledByAPZ = mHandledByAPZ;
   wheelEvent.mFocusSequenceNumber = mFocusSequenceNumber;
+  if (mDeltaType == PanGestureInput::PANDELTA_PAGE) {
+    // Emulate legacy widget/gtk behavior
+    wheelEvent.mDeltaMode = WheelEvent_Binding::DOM_DELTA_LINE;
+    wheelEvent.mIsNoLineOrPageDelta = true;
+    wheelEvent.mScrollType = WidgetWheelEvent::SCROLL_ASYNCHRONOUSELY;
+    wheelEvent.mDeltaX *= 3;
+    wheelEvent.mDeltaY *= 3;
+  } else {
+    wheelEvent.mDeltaMode = WheelEvent_Binding::DOM_DELTA_PIXEL;
+  }
   return wheelEvent;
 }
 
 bool PanGestureInput::TransformToLocal(
     const ScreenToParentLayerMatrix4x4& aTransform) {
   Maybe<ParentLayerPoint> panStartPoint =
       UntransformBy(aTransform, mPanStartPoint);
   if (!panStartPoint) {
     return false;
   }
   mLocalPanStartPoint = *panStartPoint;
 
+  if (mDeltaType == PanGestureInput::PANDELTA_PAGE) {
+    // Skip transforming the pan displacement because we want
+    // raw page proportion counts.
+    mLocalPanDisplacement.x = mPanDisplacement.x;
+    mLocalPanDisplacement.y = mPanDisplacement.y;
+    return true;
+  }
+
   Maybe<ParentLayerPoint> panDisplacement =
       UntransformVector(aTransform, mPanDisplacement, mPanStartPoint);
   if (!panDisplacement) {
     return false;
   }
   mLocalPanDisplacement = *panDisplacement;
   return true;
 }
--- a/widget/InputData.h
+++ b/widget/InputData.h
@@ -327,16 +327,27 @@ class PanGestureInput : public InputData
       // MomentumPan: The actual momentum motion by mPanDisplacement.
       PANGESTURE_MOMENTUMPAN,
 
       // MomentumEnd: The momentum animation has ended, for example because the
       // momentum velocity has gone below the stopping threshold, or because the
       // user has stopped the animation by putting their fingers on a touchpad.
       PANGESTURE_MOMENTUMEND
   ));
+
+  MOZ_DEFINE_ENUM_AT_CLASS_SCOPE(
+    PanDeltaType, (
+      // There are three kinds of scroll delta modes in Gecko: "page", "line"
+      // and "pixel". Touchpad pan gestures only support "page" and "pixel".
+      //
+      // NOTE: PANDELTA_PAGE currently replicates Gtk behavior
+      // (see AsyncPanZoomController::OnPan).
+      PANDELTA_PAGE,
+      PANDELTA_PIXEL
+  ));
   // clang-format on
 
   PanGestureInput(PanGestureType aType, uint32_t aTime, TimeStamp aTimeStamp,
                   const ScreenPoint& aPanStartPoint,
                   const ScreenPoint& aPanDisplacement, Modifiers aModifiers);
 
   bool IsMomentum() const;
 
@@ -363,37 +374,49 @@ class PanGestureInput : public InputData
   // See lineOrPageDeltaX/Y on WidgetWheelEvent.
   int32_t mLineOrPageDeltaX;
   int32_t mLineOrPageDeltaY;
 
   // User-set delta multipliers.
   double mUserDeltaMultiplierX;
   double mUserDeltaMultiplierY;
 
-  bool mHandledByAPZ;
+  PanDeltaType mDeltaType = PANDELTA_PIXEL;
+
+  bool mHandledByAPZ: 1;
 
   // true if this is a PANGESTURE_END event that will be followed by a
   // PANGESTURE_MOMENTUMSTART event.
-  bool mFollowedByMomentum;
+  bool mFollowedByMomentum: 1;
 
   // If this is true, and this event started a new input block that couldn't
   // find a scrollable target which is scrollable in the horizontal component
   // of the scroll start direction, then this input block needs to be put on
   // hold until a content response has arrived, even if the block has a
   // confirmed target.
   // This is used by events that can result in a swipe instead of a scroll.
-  bool mRequiresContentResponseIfCannotScrollHorizontallyInStartDirection;
+  bool mRequiresContentResponseIfCannotScrollHorizontallyInStartDirection: 1;
 
   // This is used by APZ to communicate to the macOS widget code whether
   // the overscroll-behavior of the scroll frame handling this swipe allows
   // non-local overscroll behaviors in the horizontal direction (such as
   // swipe navigation).
-  bool mOverscrollBehaviorAllowsSwipe;
+  bool mOverscrollBehaviorAllowsSwipe: 1;
+
+  // true if APZ should do a fling animation after this pan ends, like
+  // it would with touchscreens. (For platforms that don't emit momentum events.)
+  bool mSimulateMomentum: 1;
 
-  // XXX: If adding any more bools, switch to using bitfields instead.
+  void SetHandledByAPZ(bool aHandled) { mHandledByAPZ = aHandled; }
+  void SetFollowedByMomentum(bool aFollowed) { mFollowedByMomentum = aFollowed; }
+  void SetRequiresContentResponseIfCannotScrollHorizontallyInStartDirection(bool aRequires) {
+    mRequiresContentResponseIfCannotScrollHorizontallyInStartDirection = aRequires;
+  }
+  void SetOverscrollBehaviorAllowsSwipe(bool aAllows) { mOverscrollBehaviorAllowsSwipe = aAllows; }
+  void SetSimulateMomentum(bool aSimulate) { mSimulateMomentum = aSimulate; }
 };
 
 /**
  * Encapsulation class for pinch events. In general, these will be generated by
  * a gesture listener by looking at SingleTouchData/MultiTouchInput instances
  * and determining whether or not the user was trying to do a gesture.
  */
 class PinchGestureInput : public InputData {
--- a/widget/gtk/nsWindow.cpp
+++ b/widget/gtk/nsWindow.cpp
@@ -114,16 +114,19 @@ using namespace mozilla::widget;
 #include "GLContextProvider.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/gfx/HelpersCairo.h"
 #include "mozilla/gfx/GPUProcessManager.h"
 #include "mozilla/layers/CompositorBridgeParent.h"
 #include "mozilla/layers/CompositorThread.h"
 #include "mozilla/layers/KnowsCompositor.h"
 
+#include "mozilla/layers/APZInputBridge.h"
+#include "mozilla/layers/IAPZCTreeManager.h"
+
 #ifdef MOZ_X11
 #  include "GLContextGLX.h"  // for GLContextGLX::FindVisual()
 #  include "GtkCompositorWidget.h"
 #  include "gfxXlibSurface.h"
 #  include "WindowSurfaceX11Image.h"
 #  include "WindowSurfaceX11SHM.h"
 #  include "WindowSurfaceXRender.h"
 #endif  // MOZ_X11
@@ -3029,40 +3032,78 @@ gboolean nsWindow::OnKeyReleaseEvent(Gdk
 }
 
 void nsWindow::OnScrollEvent(GdkEventScroll* aEvent) {
   // check to see if we should rollup
   if (CheckForRollup(aEvent->x_root, aEvent->y_root, true, false)) return;
 #if GTK_CHECK_VERSION(3, 4, 0)
   // check for duplicate legacy scroll event, see GNOME bug 726878
   if (aEvent->direction != GDK_SCROLL_SMOOTH &&
-      mLastScrollEventTime == aEvent->time)
+      mLastScrollEventTime == aEvent->time) {
+    LOG(("[%d] duplicate legacy scroll event %d\n", aEvent->time,
+         aEvent->direction));
     return;
+  }
 #endif
   WidgetWheelEvent wheelEvent(true, eWheel, this);
   wheelEvent.mDeltaMode = dom::WheelEvent_Binding::DOM_DELTA_LINE;
   switch (aEvent->direction) {
 #if GTK_CHECK_VERSION(3, 4, 0)
     case GDK_SCROLL_SMOOTH: {
       // As of GTK 3.4, all directional scroll events are provided by
-      // the GDK_SCROLL_SMOOTH direction on XInput2 devices.
+      // the GDK_SCROLL_SMOOTH direction on XInput2 and Wayland devices.
       mLastScrollEventTime = aEvent->time;
+
+      // Special handling for touchpads to support flings
+      // (also known as kinetic/inertial/momentum scrolling)
+      GdkDevice* device = gdk_event_get_source_device((GdkEvent*)aEvent);
+      GdkInputSource source = gdk_device_get_source(device);
+      if (source == GDK_SOURCE_TOUCHSCREEN || source == GDK_SOURCE_TOUCHPAD) {
+        if (StaticPrefs::APZGTKKineticScrollEnabled() &&
+            gtk_check_version(3, 20, 0) == nullptr) {
+          static auto sGdkEventIsScrollStopEvent =
+              (gboolean(*)(const GdkEvent*))dlsym(
+                  RTLD_DEFAULT, "gdk_event_is_scroll_stop_event");
+
+          LOG(("[%d] pan smooth event dx=%f dy=%f inprogress=%d\n",
+               aEvent->time, aEvent->delta_x, aEvent->delta_y, mPanInProgress));
+          PanGestureInput::PanGestureType eventType =
+              PanGestureInput::PANGESTURE_PAN;
+          if (sGdkEventIsScrollStopEvent((GdkEvent*)aEvent)) {
+            eventType = PanGestureInput::PANGESTURE_END;
+            mPanInProgress = false;
+          } else if (!mPanInProgress) {
+            eventType = PanGestureInput::PANGESTURE_START;
+            mPanInProgress = true;
+          }
+
+          LayoutDeviceIntPoint touchPoint = GetRefPoint(this, aEvent);
+          PanGestureInput panEvent(
+              eventType, aEvent->time, GetEventTimeStamp(aEvent->time),
+              ScreenPoint(touchPoint.x, touchPoint.y),
+              ScreenPoint(aEvent->delta_x, aEvent->delta_y),
+              KeymapWrapper::ComputeKeyModifiers(aEvent->state));
+          panEvent.mDeltaType = PanGestureInput::PANDELTA_PAGE;
+          panEvent.mSimulateMomentum = true;
+
+          DispatchPanGestureInput(panEvent);
+
+          return;
+        }
+
+        // Older GTK doesn't support stop events, so we can't support fling
+        wheelEvent.mScrollType = WidgetWheelEvent::SCROLL_ASYNCHRONOUSELY;
+      }
+
       // TODO - use a more appropriate scrolling unit than lines.
       // Multiply event deltas by 3 to emulate legacy behaviour.
       wheelEvent.mDeltaX = aEvent->delta_x * 3;
       wheelEvent.mDeltaY = aEvent->delta_y * 3;
       wheelEvent.mIsNoLineOrPageDelta = true;
-      // This next step manually unsets smooth scrolling for touch devices
-      // that trigger GDK_SCROLL_SMOOTH. We use the slave device, which
-      // represents the actual input.
-      GdkDevice* device = gdk_event_get_source_device((GdkEvent*)aEvent);
-      GdkInputSource source = gdk_device_get_source(device);
-      if (source == GDK_SOURCE_TOUCHSCREEN || source == GDK_SOURCE_TOUCHPAD) {
-        wheelEvent.mScrollType = WidgetWheelEvent::SCROLL_ASYNCHRONOUSELY;
-      }
+
       break;
     }
 #endif
     case GDK_SCROLL_UP:
       wheelEvent.mDeltaY = wheelEvent.mLineOrPageDeltaY = -3;
       break;
     case GDK_SCROLL_DOWN:
       wheelEvent.mDeltaY = wheelEvent.mLineOrPageDeltaY = 3;
--- a/widget/gtk/nsWindow.h
+++ b/widget/gtk/nsWindow.h
@@ -487,16 +487,18 @@ class nsWindow final : public nsBaseWidg
   nsSizeMode mSizeState;
 
   nsIntPoint mClientOffset;
 
 #if GTK_CHECK_VERSION(3, 4, 0)
   // This field omits duplicate scroll events caused by GNOME bug 726878.
   guint32 mLastScrollEventTime;
 
+  bool mPanInProgress = false;
+
   // for touch event handling
   nsRefPtrHashtable<nsPtrHashKey<GdkEventSequence>, mozilla::dom::Touch>
       mTouches;
 #endif
 
 #ifdef MOZ_X11
   Display* mXDisplay;
   Window mXWindow;
--- a/widget/nsBaseWidget.cpp
+++ b/widget/nsBaseWidget.cpp
@@ -1127,16 +1127,39 @@ void nsBaseWidget::DispatchTouchInput(Mu
   } else {
     WidgetTouchEvent event = aInput.ToWidgetTouchEvent(this);
 
     nsEventStatus status;
     DispatchEvent(&event, status);
   }
 }
 
+void nsBaseWidget::DispatchPanGestureInput(PanGestureInput& aInput) {
+  MOZ_ASSERT(NS_IsMainThread());
+  if (mAPZC) {
+    MOZ_ASSERT(APZThreadUtils::IsControllerThread());
+    uint64_t inputBlockId = 0;
+    ScrollableLayerGuid guid;
+
+    nsEventStatus result =
+        mAPZC->InputBridge()->ReceiveInputEvent(aInput, &guid, &inputBlockId);
+    if (result == nsEventStatus_eConsumeNoDefault) {
+      return;
+    }
+
+    WidgetWheelEvent event = aInput.ToWidgetWheelEvent(this);
+    ProcessUntransformedAPZEvent(&event, guid, inputBlockId, result);
+  } else {
+    WidgetWheelEvent event = aInput.ToWidgetWheelEvent(this);
+
+    nsEventStatus status;
+    DispatchEvent(&event, status);
+  }
+}
+
 nsEventStatus nsBaseWidget::DispatchInputEvent(WidgetInputEvent* aEvent) {
   MOZ_ASSERT(NS_IsMainThread());
   if (mAPZC) {
     if (APZThreadUtils::IsControllerThread()) {
       uint64_t inputBlockId = 0;
       ScrollableLayerGuid guid;
 
       nsEventStatus result = mAPZC->InputBridge()->ReceiveInputEvent(
--- a/widget/nsBaseWidget.h
+++ b/widget/nsBaseWidget.h
@@ -614,16 +614,24 @@ class nsBaseWidget : public nsIWidget, p
   /**
    * Dispatch the given MultiTouchInput through APZ to Gecko (if APZ is enabled)
    * or directly to gecko (if APZ is not enabled). This function must only
    * be called from the main thread, and if APZ is enabled, that must also be
    * the APZ controller thread.
    */
   void DispatchTouchInput(mozilla::MultiTouchInput& aInput);
 
+  /**
+   * Dispatch the given PanGestureInput through APZ to Gecko (if APZ is enabled)
+   * or directly to gecko (if APZ is not enabled). This function must only
+   * be called from the main thread, and if APZ is enabled, that must also be
+   * the APZ controller thread.
+   */
+  void DispatchPanGestureInput(mozilla::PanGestureInput& aInput);
+
 #if defined(XP_WIN)
   void UpdateScrollCapture() override;
 
   /**
    * To be overridden by derived classes to return a snapshot that can be used
    * during scrolling. Returning null means we won't update the container.
    * @return an already AddRefed SourceSurface containing the snapshot
    */
--- a/widget/nsGUIEventIPC.h
+++ b/widget/nsGUIEventIPC.h
@@ -1206,58 +1206,74 @@ struct ParamTraits<mozilla::MouseInput> 
 template <>
 struct ParamTraits<mozilla::PanGestureInput::PanGestureType>
     : public ContiguousEnumSerializerInclusive<
           mozilla::PanGestureInput::PanGestureType,
           mozilla::PanGestureInput::PanGestureType::PANGESTURE_MAYSTART,
           mozilla::PanGestureInput::sHighestPanGestureType> {};
 
 template <>
-struct ParamTraits<mozilla::PanGestureInput> {
+struct ParamTraits<mozilla::PanGestureInput::PanDeltaType>
+    : public ContiguousEnumSerializerInclusive<
+          mozilla::PanGestureInput::PanDeltaType,
+          mozilla::PanGestureInput::PanDeltaType::PANDELTA_PAGE,
+          mozilla::PanGestureInput::sHighestPanDeltaType> {};
+
+template <>
+struct ParamTraits<mozilla::PanGestureInput>
+    : BitfieldHelper<mozilla::PanGestureInput> {
   typedef mozilla::PanGestureInput paramType;
 
   static void Write(Message* aMsg, const paramType& aParam) {
     WriteParam(aMsg, static_cast<const mozilla::InputData&>(aParam));
     WriteParam(aMsg, aParam.mType);
     WriteParam(aMsg, aParam.mPanStartPoint);
     WriteParam(aMsg, aParam.mPanDisplacement);
     WriteParam(aMsg, aParam.mLocalPanStartPoint);
     WriteParam(aMsg, aParam.mLocalPanDisplacement);
     WriteParam(aMsg, aParam.mLineOrPageDeltaX);
     WriteParam(aMsg, aParam.mLineOrPageDeltaY);
     WriteParam(aMsg, aParam.mUserDeltaMultiplierX);
     WriteParam(aMsg, aParam.mUserDeltaMultiplierY);
+    WriteParam(aMsg, aParam.mDeltaType);
     WriteParam(aMsg, aParam.mHandledByAPZ);
     WriteParam(aMsg, aParam.mFollowedByMomentum);
     WriteParam(
         aMsg,
         aParam
             .mRequiresContentResponseIfCannotScrollHorizontallyInStartDirection);
     WriteParam(aMsg, aParam.mOverscrollBehaviorAllowsSwipe);
+    WriteParam(aMsg, aParam.mSimulateMomentum);
   }
 
   static bool Read(const Message* aMsg, PickleIterator* aIter,
                    paramType* aResult) {
     return ReadParam(aMsg, aIter, static_cast<mozilla::InputData*>(aResult)) &&
            ReadParam(aMsg, aIter, &aResult->mType) &&
            ReadParam(aMsg, aIter, &aResult->mPanStartPoint) &&
            ReadParam(aMsg, aIter, &aResult->mPanDisplacement) &&
            ReadParam(aMsg, aIter, &aResult->mLocalPanStartPoint) &&
            ReadParam(aMsg, aIter, &aResult->mLocalPanDisplacement) &&
            ReadParam(aMsg, aIter, &aResult->mLineOrPageDeltaX) &&
            ReadParam(aMsg, aIter, &aResult->mLineOrPageDeltaY) &&
            ReadParam(aMsg, aIter, &aResult->mUserDeltaMultiplierX) &&
            ReadParam(aMsg, aIter, &aResult->mUserDeltaMultiplierY) &&
-           ReadParam(aMsg, aIter, &aResult->mHandledByAPZ) &&
-           ReadParam(aMsg, aIter, &aResult->mFollowedByMomentum) &&
-           ReadParam(
-               aMsg, aIter,
-               &aResult
-                    ->mRequiresContentResponseIfCannotScrollHorizontallyInStartDirection) &&
-           ReadParam(aMsg, aIter, &aResult->mOverscrollBehaviorAllowsSwipe);
+           ReadParam(aMsg, aIter, &aResult->mDeltaType) &&
+           ReadBoolForBitfield(aMsg, aIter, aResult,
+                               &paramType::SetHandledByAPZ) &&
+           ReadBoolForBitfield(aMsg, aIter, aResult,
+                               &paramType::SetFollowedByMomentum) &&
+           ReadBoolForBitfield(
+               aMsg, aIter, aResult,
+               &paramType::
+                   SetRequiresContentResponseIfCannotScrollHorizontallyInStartDirection) &&
+           ReadBoolForBitfield(aMsg, aIter, aResult,
+                               &paramType::SetOverscrollBehaviorAllowsSwipe) &&
+           ReadBoolForBitfield(aMsg, aIter, aResult,
+                               &paramType::SetSimulateMomentum);
   }
 };
 
 template <>
 struct ParamTraits<mozilla::PinchGestureInput::PinchGestureType>
     : public ContiguousEnumSerializerInclusive<
           mozilla::PinchGestureInput::PinchGestureType,
           mozilla::PinchGestureInput::PinchGestureType::PINCHGESTURE_START,