Bug 1280666 - Use StackScroller from Chrome for fling animations r=rbarker
authorJames Willcox <snorp@snorp.net>
Wed, 29 Jun 2016 13:42:12 -0700
changeset 303152 ab9fc4342f54bd6f4be03f7788151fda931266a3
parent 303151 0f13ded95511ebd1146a3332b8db974bcca7d1de
child 303153 a8e0bbf386df5bc77c365c61acb957f5f389ee51
push id78996
push userrbarker@mozilla.com
push dateThu, 30 Jun 2016 00:10:05 +0000
treeherdermozilla-inbound@a8e0bbf386df [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrbarker
bugs1280666
milestone50.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 1280666 - Use StackScroller from Chrome for fling animations r=rbarker
gfx/layers/apz/src/AndroidAPZ.cpp
gfx/layers/apz/src/AndroidAPZ.h
mobile/android/base/java/org/mozilla/gecko/gfx/StackScroller.java
mobile/android/base/moz.build
widget/android/GeneratedJNIWrappers.cpp
widget/android/GeneratedJNIWrappers.h
widget/android/bindings/OverScroller-classes.txt
widget/android/bindings/moz.build
--- a/gfx/layers/apz/src/AndroidAPZ.cpp
+++ b/gfx/layers/apz/src/AndroidAPZ.cpp
@@ -5,17 +5,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "AndroidAPZ.h"
 
 #include "AsyncPanZoomController.h"
 #include "GeneratedJNIWrappers.h"
 #include "gfxPrefs.h"
 #include "OverscrollHandoffState.h"
-#include "OverScroller.h"
 #include "ViewConfiguration.h"
 
 #define ANDROID_APZ_LOG(...)
 // #define ANDROID_APZ_LOG(...) printf_stderr("ANDROID_APZ: " __VA_ARGS__)
 
 static float sMaxFlingSpeed = 0.0f;
 
 namespace mozilla {
@@ -28,19 +27,19 @@ AndroidSpecificState::AndroidSpecificSta
     if (config->GetScaledMaximumFlingVelocity(&speed) == NS_OK) {
       sMaxFlingSpeed = (float)speed * 0.001f;
     } else {
       ANDROID_APZ_LOG("%p Failed to query ViewConfiguration for scaled maximum fling velocity\n", this);
     }
   } else {
     ANDROID_APZ_LOG("%p Failed to get ViewConfiguration\n", this);
   }
-  widget::sdk::OverScroller::LocalRef scroller;
-  if (widget::sdk::OverScroller::New(widget::GeckoAppShell::GetApplicationContext(), &scroller) != NS_OK) {
-    ANDROID_APZ_LOG("%p Failed to create Android OverScroller\n", this);
+  widget::StackScroller::LocalRef scroller;
+  if (widget::StackScroller::New(widget::GeckoAppShell::GetApplicationContext(), &scroller) != NS_OK) {
+    ANDROID_APZ_LOG("%p Failed to create Android StackScroller\n", this);
     return;
   }
   mOverScroller = scroller;
 }
 
 const float BOUNDS_EPSILON = 1.0f;
 
 // This function is used to convert the scroll offset from a float to an integer
@@ -70,16 +69,17 @@ AndroidFlingAnimation::AndroidFlingAnima
                                              const RefPtr<const OverscrollHandoffChain>& aOverscrollHandoffChain,
                                              bool aFlingIsHandoff,
                                              const RefPtr<const AsyncPanZoomController>& aScrolledApzc)
   : mApzc(aApzc)
   , mOverscrollHandoffChain(aOverscrollHandoffChain)
   , mScrolledApzc(aScrolledApzc)
   , mSentBounceX(false)
   , mSentBounceY(false)
+  , mFlingDuration(0)
 {
   MOZ_ASSERT(mOverscrollHandoffChain);
   MOZ_ASSERT(aPlatformSpecificState->AsAndroidSpecificState());
   mOverScroller = aPlatformSpecificState->AsAndroidSpecificState()->mOverScroller;
   MOZ_ASSERT(mOverScroller);
 
   // 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).
@@ -115,32 +115,33 @@ AndroidFlingAnimation::AndroidFlingAnima
 
   int32_t originX = ClampStart(mStartOffset.x, scrollRangeStartX, scrollRangeEndX);
   int32_t originY = ClampStart(mStartOffset.y, scrollRangeStartY, scrollRangeEndY);
   mOverScroller->Fling(originX, originY,
                        // Android needs the velocity in pixels per second and it is in pixels per ms.
                        (int32_t)(velocity.x * 1000.0f), (int32_t)(velocity.y * 1000.0f),
                        (int32_t)floor(scrollRangeStartX), (int32_t)ceil(scrollRangeEndX),
                        (int32_t)floor(scrollRangeStartY), (int32_t)ceil(scrollRangeEndY),
-                       0, 0);
+                       0, 0, 0);
 }
 
 /**
  * Advances a fling by an interpolated amount based on the Android OverScroller.
  * This should be called whenever sampling the content transform for this
  * frame. Returns true if the fling animation should be advanced by one frame,
  * or false if there is no fling or the fling has ended.
  */
 bool
 AndroidFlingAnimation::DoSample(FrameMetrics& aFrameMetrics,
                                 const TimeDuration& aDelta)
 {
   bool shouldContinueFling = true;
 
-  mOverScroller->ComputeScrollOffset(&shouldContinueFling);
+  mFlingDuration += aDelta.ToMilliseconds();
+  mOverScroller->ComputeScrollOffset(mFlingDuration, &shouldContinueFling);
   // OverScroller::GetCurrVelocity will sometimes return NaN. So need to externally
   // calculate current velocity and not rely on what the OverScroller calculates.
   // mOverScroller->GetCurrVelocity(&speed);
   int32_t currentX = 0;
   int32_t currentY = 0;
   mOverScroller->GetCurrX(&currentX);
   mOverScroller->GetCurrY(&currentY);
   ParentLayerPoint offset((float)currentX, (float)currentY);
--- a/gfx/layers/apz/src/AndroidAPZ.h
+++ b/gfx/layers/apz/src/AndroidAPZ.h
@@ -5,30 +5,29 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_layers_AndroidAPZ_h_
 #define mozilla_layers_AndroidAPZ_h_
 
 #include "AsyncPanZoomAnimation.h"
 #include "AsyncPanZoomController.h"
 #include "GeneratedJNIWrappers.h"
-#include "OverScroller.h"
 
 namespace mozilla {
 namespace layers {
 
 class AndroidSpecificState : public PlatformSpecificStateBase {
 public:
   AndroidSpecificState();
 
   virtual AndroidSpecificState* AsAndroidSpecificState() override {
     return this;
   }
 
-  widget::sdk::OverScroller::GlobalRef mOverScroller;
+  widget::StackScroller::GlobalRef mOverScroller;
 };
 
 class AndroidFlingAnimation: public AsyncPanZoomAnimation {
 public:
   AndroidFlingAnimation(AsyncPanZoomController& aApzc,
                         PlatformSpecificStateBase* aPlatformSpecificState,
                         const RefPtr<const OverscrollHandoffChain>& aOverscrollHandoffChain,
                         bool aFlingIsHandoff /* ignored */,
@@ -36,21 +35,22 @@ public:
   virtual bool DoSample(FrameMetrics& aFrameMetrics,
                         const TimeDuration& aDelta) override;
 private:
   void DeferHandleFlingOverscroll(ParentLayerPoint& aVelocity);
   // Returns true if value is on or outside of axis bounds.
   bool CheckBounds(Axis& aAxis, float aValue, float aDirection, float* aClamped);
 
   AsyncPanZoomController& mApzc;
-  widget::sdk::OverScroller::GlobalRef mOverScroller;
+  widget::StackScroller::GlobalRef mOverScroller;
   RefPtr<const OverscrollHandoffChain> mOverscrollHandoffChain;
   RefPtr<const AsyncPanZoomController> mScrolledApzc;
   bool mSentBounceX;
   bool mSentBounceY;
+  long mFlingDuration;
   ParentLayerPoint mStartOffset;
   ParentLayerPoint mPreviousOffset;
   // Unit vector in the direction of the fling.
   ParentLayerPoint mFlingDirection;
   ParentLayerPoint mPreviousVelocity;
 };
 
 
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/gfx/StackScroller.java
@@ -0,0 +1,677 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.mozilla.gecko.gfx;
+
+import android.content.Context;
+import android.hardware.SensorManager;
+import android.util.Log;
+import android.view.ViewConfiguration;
+
+import org.mozilla.gecko.annotation.WrapForJNI;
+
+/**
+ * This class is vastly copied from {@link android.widget.OverScroller} but decouples the time
+ * from the app time so it can be specified manually.
+ */
+@WrapForJNI(allowMultithread = true, catchException = true)
+public class StackScroller {
+    private int mMode;
+
+    private final SplineStackScroller mScrollerX;
+    private final SplineStackScroller mScrollerY;
+
+    private final boolean mFlywheel;
+
+    private static final int SCROLL_MODE = 0;
+    private static final int FLING_MODE = 1;
+
+    private static float sViscousFluidScale;
+    private static float sViscousFluidNormalize;
+
+    /**
+     * Creates an StackScroller with a viscous fluid scroll interpolator and flywheel.
+     * @param context
+     */
+    public StackScroller(Context context) {
+        mFlywheel = true;
+        mScrollerX = new SplineStackScroller(context);
+        mScrollerY = new SplineStackScroller(context);
+        initContants();
+    }
+
+    private static void initContants() {
+        // This controls the viscous fluid effect (how much of it)
+        sViscousFluidScale = 8.0f;
+        // must be set to 1.0 (used in viscousFluid())
+        sViscousFluidNormalize = 1.0f;
+        sViscousFluidNormalize = 1.0f / viscousFluid(1.0f);
+    }
+
+    /**
+     *
+     * Returns whether the scroller has finished scrolling.
+     *
+     * @return True if the scroller has finished scrolling, false otherwise.
+     */
+    public final boolean isFinished() {
+        return mScrollerX.mFinished && mScrollerY.mFinished;
+    }
+
+    /**
+     * Force the finished field to a particular value. Contrary to
+     * {@link #abortAnimation()}, forcing the animation to finished
+     * does NOT cause the scroller to move to the final x and y
+     * position.
+     *
+     * @param finished The new finished value.
+     */
+    public final void forceFinished(boolean finished) {
+        mScrollerX.mFinished = mScrollerY.mFinished = finished;
+    }
+
+    /**
+     * Returns the current X offset in the scroll.
+     *
+     * @return The new X offset as an absolute distance from the origin.
+     */
+    public final int getCurrX() {
+        return mScrollerX.mCurrentPosition;
+    }
+
+    /**
+     * Returns the current Y offset in the scroll.
+     *
+     * @return The new Y offset as an absolute distance from the origin.
+     */
+    public final int getCurrY() {
+        return mScrollerY.mCurrentPosition;
+    }
+
+    /**
+     * Returns where the scroll will end. Valid only for "fling" scrolls.
+     *
+     * @return The final X offset as an absolute distance from the origin.
+     */
+    public final int getFinalX() {
+        return mScrollerX.mFinal;
+    }
+
+    /**
+     * Returns where the scroll will end. Valid only for "fling" scrolls.
+     *
+     * @return The final Y offset as an absolute distance from the origin.
+     */
+    public final int getFinalY() {
+        return mScrollerY.mFinal;
+    }
+
+    /**
+     * Sets where the scroll will end.  Valid only for "fling" scrolls.
+     *
+     * @param x The final X offset as an absolute distance from the origin.
+     */
+    public final void setFinalX(int x) {
+        mScrollerX.setFinalPosition(x);
+    }
+
+    private static float viscousFluid(float x) {
+        x *= sViscousFluidScale;
+        if (x < 1.0f) {
+            x -= (1.0f - (float) Math.exp(-x));
+        } else {
+            float start = 0.36787944117f; // 1/e == exp(-1)
+            x = 1.0f - (float) Math.exp(1.0f - x);
+            x = start + x * (1.0f - start);
+        }
+        x *= sViscousFluidNormalize;
+        return x;
+    }
+
+    /**
+     * Call this when you want to know the new location. If it returns true, the
+     * animation is not yet finished.
+     */
+    public boolean computeScrollOffset(long time) {
+        if (isFinished()) {
+            return false;
+        }
+
+        switch (mMode) {
+            case SCROLL_MODE:
+                // Any scroller can be used for time, since they were started
+                // together in scroll mode. We use X here.
+                final long elapsedTime = time - mScrollerX.mStartTime;
+
+                final int duration = mScrollerX.mDuration;
+                if (elapsedTime < duration) {
+                    float q = (float) (elapsedTime) / duration;
+                    q = viscousFluid(q);
+                    mScrollerX.updateScroll(q);
+                    mScrollerY.updateScroll(q);
+                } else {
+                    abortAnimation();
+                }
+                break;
+
+            case FLING_MODE:
+                if (!mScrollerX.mFinished) {
+                    if (!mScrollerX.update(time)) {
+                        if (!mScrollerX.continueWhenFinished(time)) {
+                            mScrollerX.finish();
+                        }
+                    }
+                }
+
+                if (!mScrollerY.mFinished) {
+                    if (!mScrollerY.update(time)) {
+                        if (!mScrollerY.continueWhenFinished(time)) {
+                            mScrollerY.finish();
+                        }
+                    }
+                }
+
+                break;
+
+            default:
+                break;
+        }
+
+        return true;
+    }
+
+    /**
+     * Start scrolling by providing a starting point and the distance to travel.
+     *
+     * @param startX Starting horizontal scroll offset in pixels. Positive
+     *        numbers will scroll the content to the left.
+     * @param startY Starting vertical scroll offset in pixels. Positive numbers
+     *        will scroll the content up.
+     * @param dx Horizontal distance to travel. Positive numbers will scroll the
+     *        content to the left.
+     * @param dy Vertical distance to travel. Positive numbers will scroll the
+     *        content up.
+     * @param duration Duration of the scroll in milliseconds.
+     */
+    public void startScroll(int startX, int startY, int dx, int dy, long startTime, int duration) {
+        mMode = SCROLL_MODE;
+        mScrollerX.startScroll(startX, dx, startTime, duration);
+        mScrollerY.startScroll(startY, dy, startTime, duration);
+    }
+
+    /**
+     * Call this when you want to 'spring back' into a valid coordinate range.
+     *
+     * @param startX Starting X coordinate
+     * @param startY Starting Y coordinate
+     * @param minX Minimum valid X value
+     * @param maxX Maximum valid X value
+     * @param minY Minimum valid Y value
+     * @param maxY Minimum valid Y value
+     * @return true if a springback was initiated, false if startX and startY were
+     *          already within the valid range.
+     */
+    public boolean springBack(
+            int startX, int startY, int minX, int maxX, int minY, int maxY, long time) {
+        mMode = FLING_MODE;
+
+        // Make sure both methods are called.
+        final boolean spingbackX = mScrollerX.springback(startX, minX, maxX, time);
+        final boolean spingbackY = mScrollerY.springback(startY, minY, maxY, time);
+        return spingbackX || spingbackY;
+    }
+
+    /**
+     * Start scrolling based on a fling gesture. The distance traveled will
+     * depend on the initial velocity of the fling.
+     *
+     * @param startX Starting point of the scroll (X)
+     * @param startY Starting point of the scroll (Y)
+     * @param velocityX Initial velocity of the fling (X) measured in pixels per second.
+     * @param velocityY Initial velocity of the fling (Y) measured in pixels per second
+     * @param minX Minimum X value. The scroller will not scroll past this point
+     *            unless overX > 0. If overfling is allowed, it will use minX as
+     *            a springback boundary.
+     * @param maxX Maximum X value. The scroller will not scroll past this point
+     *            unless overX > 0. If overfling is allowed, it will use maxX as
+     *            a springback boundary.
+     * @param minY Minimum Y value. The scroller will not scroll past this point
+     *            unless overY > 0. If overfling is allowed, it will use minY as
+     *            a springback boundary.
+     * @param maxY Maximum Y value. The scroller will not scroll past this point
+     *            unless overY > 0. If overfling is allowed, it will use maxY as
+     *            a springback boundary.
+     * @param overX Overfling range. If > 0, horizontal overfling in either
+     *            direction will be possible.
+     * @param overY Overfling range. If > 0, vertical overfling in either
+     *            direction will be possible.
+     */
+    public void fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX,
+            int minY, int maxY, int overX, int overY, long time) {
+        // Continue a scroll or fling in progress
+        if (mFlywheel && !isFinished()) {
+            float oldVelocityX = mScrollerX.mCurrVelocity;
+            float oldVelocityY = mScrollerY.mCurrVelocity;
+            if (Math.signum(velocityX) == Math.signum(oldVelocityX)
+                    && Math.signum(velocityY) == Math.signum(oldVelocityY)) {
+                velocityX += oldVelocityX;
+                velocityY += oldVelocityY;
+            }
+        }
+
+        mMode = FLING_MODE;
+        mScrollerX.fling(startX, velocityX, minX, maxX, overX, time);
+        mScrollerY.fling(startY, velocityY, minY, maxY, overY, time);
+    }
+
+    /**
+     * Stops the animation. Contrary to {@link #forceFinished(boolean)},
+     * aborting the animating causes the scroller to move to the final x and y
+     * positions.
+     *
+     * @see #forceFinished(boolean)
+     */
+    public void abortAnimation() {
+        mScrollerX.finish();
+        mScrollerY.finish();
+    }
+
+    static class SplineStackScroller {
+        // Initial position
+        private int mStart;
+
+        // Current position
+        private int mCurrentPosition;
+
+        // Final position
+        private int mFinal;
+
+        // Initial velocity
+        private int mVelocity;
+
+        // Current velocity
+        private float mCurrVelocity;
+
+        // Constant current deceleration
+        private float mDeceleration;
+
+        // Animation starting time, in system milliseconds
+        private long mStartTime;
+
+        // Animation duration, in milliseconds
+        private int mDuration;
+
+        // Duration to complete spline component of animation
+        private int mSplineDuration;
+
+        // Distance to travel along spline animation
+        private int mSplineDistance;
+
+        // Whether the animation is currently in progress
+        private boolean mFinished;
+
+        // The allowed overshot distance before boundary is reached.
+        private int mOver;
+
+        // Fling friction
+        private final float mFlingFriction = ViewConfiguration.getScrollFriction();
+
+        // Current state of the animation.
+        private int mState = SPLINE;
+
+        // Constant gravity value, used in the deceleration phase.
+        private static final float GRAVITY = 2000.0f;
+
+        // A context-specific coefficient adjusted to physical values.
+        private final float mPhysicalCoeff;
+
+        private static final float DECELERATION_RATE = (float) (Math.log(0.78) / Math.log(0.9));
+        private static final float INFLEXION = 0.35f; // Tension lines cross at (INFLEXION, 1)
+        private static final float START_TENSION = 0.5f;
+        private static final float END_TENSION = 1.0f;
+        private static final float P1 = START_TENSION * INFLEXION;
+        private static final float P2 = 1.0f - END_TENSION * (1.0f - INFLEXION);
+
+        private static final int NB_SAMPLES = 100;
+        private static final float[] SPLINE_POSITION = new float[NB_SAMPLES + 1];
+        private static final float[] SPLINE_TIME = new float[NB_SAMPLES + 1];
+
+        private static final int SPLINE = 0;
+        private static final int CUBIC = 1;
+        private static final int BALLISTIC = 2;
+
+        static {
+            float xMin = 0.0f;
+            float yMin = 0.0f;
+            for (int i = 0; i < NB_SAMPLES; i++) {
+                final float alpha = (float) i / NB_SAMPLES;
+
+                float xMax = 1.0f;
+                float x, tx, coef;
+                while (true) {
+                    x = xMin + (xMax - xMin) / 2.0f;
+                    coef = 3.0f * x * (1.0f - x);
+                    tx = coef * ((1.0f - x) * P1 + x * P2) + x * x * x;
+                    if (Math.abs(tx - alpha) < 1E-5) break;
+                    if (tx > alpha) {
+                        xMax = x;
+                    } else {
+                        xMin = x;
+                    }
+                }
+                SPLINE_POSITION[i] = coef * ((1.0f - x) * START_TENSION + x) + x * x * x;
+
+                float yMax = 1.0f;
+                float y, dy;
+                while (true) {
+                    y = yMin + (yMax - yMin) / 2.0f;
+                    coef = 3.0f * y * (1.0f - y);
+                    dy = coef * ((1.0f - y) * START_TENSION + y) + y * y * y;
+                    if (Math.abs(dy - alpha) < 1E-5) break;
+                    if (dy > alpha) {
+                        yMax = y;
+                    } else {
+                        yMin = y;
+                    }
+                }
+                SPLINE_TIME[i] = coef * ((1.0f - y) * P1 + y * P2) + y * y * y;
+            }
+            SPLINE_POSITION[NB_SAMPLES] = SPLINE_TIME[NB_SAMPLES] = 1.0f;
+        }
+
+        SplineStackScroller(Context context) {
+            mFinished = true;
+            final float ppi = context.getResources().getDisplayMetrics().density * 160.0f;
+            mPhysicalCoeff = SensorManager.GRAVITY_EARTH // g (m/s^2)
+                    * 39.37f // inch/meter
+                    * ppi * 0.84f; // look and feel tuning
+        }
+
+        void updateScroll(float q) {
+            mCurrentPosition = mStart + Math.round(q * (mFinal - mStart));
+        }
+
+        /*
+         * Get a signed deceleration that will reduce the velocity.
+         */
+        private static float getDeceleration(int velocity) {
+            return velocity > 0 ? -GRAVITY : GRAVITY;
+        }
+
+        /*
+         * Modifies mDuration to the duration it takes to get from start to newFinal using the
+         * spline interpolation. The previous duration was needed to get to oldFinal.
+         */
+        private void adjustDuration(int start, int oldFinal, int newFinal) {
+            final int oldDistance = oldFinal - start;
+            final int newDistance = newFinal - start;
+            final float x = Math.abs((float) newDistance / oldDistance);
+            final int index = (int) (NB_SAMPLES * x);
+            if (index < NB_SAMPLES) {
+                final float xInf = (float) index / NB_SAMPLES;
+                final float xSup = (float) (index + 1) / NB_SAMPLES;
+                final float tInf = SPLINE_TIME[index];
+                final float tSup = SPLINE_TIME[index + 1];
+                final float timeCoef = tInf + (x - xInf) / (xSup - xInf) * (tSup - tInf);
+                mDuration *= timeCoef;
+            }
+        }
+
+        void startScroll(int start, int distance, long startTime, int duration) {
+            mFinished = false;
+
+            mStart = start;
+            mFinal = start + distance;
+
+            mStartTime = startTime;
+            mDuration = duration;
+
+            // Unused
+            mDeceleration = 0.0f;
+            mVelocity = 0;
+        }
+
+        void finish() {
+            mCurrentPosition = mFinal;
+            // Not reset since WebView relies on this value for fast fling.
+            // TODO: restore when WebView uses the fast fling implemented in this class.
+            // mCurrVelocity = 0.0f;
+            mFinished = true;
+        }
+
+        void setFinalPosition(int position) {
+            mFinal = position;
+            mFinished = false;
+        }
+
+        boolean springback(int start, int min, int max, long time) {
+            mFinished = true;
+
+            mStart = mFinal = start;
+            mVelocity = 0;
+
+            mStartTime = time;
+            mDuration = 0;
+
+            if (start < min) {
+                startSpringback(start, min, 0);
+            } else if (start > max) {
+                startSpringback(start, max, 0);
+            }
+
+            return !mFinished;
+        }
+
+        private void startSpringback(int start, int end, int velocity) {
+            // mStartTime has been set
+            mFinished = false;
+            mState = CUBIC;
+            mStart = start;
+            mFinal = end;
+            final int delta = start - end;
+            mDeceleration = getDeceleration(delta);
+            // TODO take velocity into account
+            mVelocity = -delta; // only sign is used
+            mOver = Math.abs(delta);
+            mDuration = (int) (1000.0 * Math.sqrt(-2.0 * delta / mDeceleration));
+        }
+
+        void fling(int start, int velocity, int min, int max, int over, long time) {
+            mOver = over;
+            mFinished = false;
+            mCurrVelocity = mVelocity = velocity;
+            mDuration = mSplineDuration = 0;
+            mStartTime = time;
+            mCurrentPosition = mStart = start;
+
+            if (start > max || start < min) {
+                startAfterEdge(start, min, max, velocity, time);
+                return;
+            }
+
+            mState = SPLINE;
+            double totalDistance = 0.0;
+
+            if (velocity != 0) {
+                mDuration = mSplineDuration = getSplineFlingDuration(velocity);
+                totalDistance = getSplineFlingDistance(velocity);
+            }
+
+            mSplineDistance = (int) (totalDistance * Math.signum(velocity));
+            mFinal = start + mSplineDistance;
+
+            // Clamp to a valid final position
+            if (mFinal < min) {
+                adjustDuration(mStart, mFinal, min);
+                mFinal = min;
+            }
+
+            if (mFinal > max) {
+                adjustDuration(mStart, mFinal, max);
+                mFinal = max;
+            }
+        }
+
+        private double getSplineDeceleration(int velocity) {
+            return Math.log(INFLEXION * Math.abs(velocity) / (mFlingFriction * mPhysicalCoeff));
+        }
+
+        private double getSplineFlingDistance(int velocity) {
+            final double l = getSplineDeceleration(velocity);
+            final double decelMinusOne = DECELERATION_RATE - 1.0;
+            return mFlingFriction * mPhysicalCoeff
+                    * Math.exp(DECELERATION_RATE / decelMinusOne * l);
+        }
+
+        /* Returns the duration, expressed in milliseconds */
+        private int getSplineFlingDuration(int velocity) {
+            final double l = getSplineDeceleration(velocity);
+            final double decelMinusOne = DECELERATION_RATE - 1.0;
+            return (int) (1000.0 * Math.exp(l / decelMinusOne));
+        }
+
+        private void fitOnBounceCurve(int start, int end, int velocity) {
+            // Simulate a bounce that started from edge
+            final float durationToApex = -velocity / mDeceleration;
+            final float distanceToApex = velocity * velocity / 2.0f / Math.abs(mDeceleration);
+            final float distanceToEdge = Math.abs(end - start);
+            final float totalDuration = (float) Math.sqrt(
+                    2.0 * (distanceToApex + distanceToEdge) / Math.abs(mDeceleration));
+            mStartTime -= (int) (1000.0f * (totalDuration - durationToApex));
+            mStart = end;
+            mVelocity = (int) (-mDeceleration * totalDuration);
+        }
+
+        private void startBounceAfterEdge(int start, int end, int velocity) {
+            mDeceleration = getDeceleration(velocity == 0 ? start - end : velocity);
+            fitOnBounceCurve(start, end, velocity);
+            onEdgeReached();
+        }
+
+        private void startAfterEdge(int start, int min, int max, int velocity, long time) {
+            if (start > min && start < max) {
+                Log.e("StackScroller", "startAfterEdge called from a valid position");
+                mFinished = true;
+                return;
+            }
+            final boolean positive = start > max;
+            final int edge = positive ? max : min;
+            final int overDistance = start - edge;
+            boolean keepIncreasing = overDistance * velocity >= 0;
+            if (keepIncreasing) {
+                // Will result in a bounce or a to_boundary depending on velocity.
+                startBounceAfterEdge(start, edge, velocity);
+            } else {
+                final double totalDistance = getSplineFlingDistance(velocity);
+                if (totalDistance > Math.abs(overDistance)) {
+                    fling(start, velocity, positive ? min : start, positive ? start : max, mOver,
+                            time);
+                } else {
+                    startSpringback(start, edge, velocity);
+                }
+            }
+        }
+
+        private void onEdgeReached() {
+            // mStart, mVelocity and mStartTime were adjusted to their values when edge was reached.
+            float distance = mVelocity * mVelocity / (2.0f * Math.abs(mDeceleration));
+            final float sign = Math.signum(mVelocity);
+
+            if (distance > mOver) {
+                // Default deceleration is not sufficient to slow us down before boundary
+                mDeceleration = -sign * mVelocity * mVelocity / (2.0f * mOver);
+                distance = mOver;
+            }
+
+            mOver = (int) distance;
+            mState = BALLISTIC;
+            mFinal = mStart + (int) (mVelocity > 0 ? distance : -distance);
+            mDuration = -(int) (1000.0f * mVelocity / mDeceleration);
+        }
+
+        boolean continueWhenFinished(long time) {
+            switch (mState) {
+                case SPLINE:
+                    // Duration from start to null velocity
+                    if (mDuration < mSplineDuration) {
+                        // If the animation was clamped, we reached the edge
+                        mStart = mFinal;
+                        // TODO Better compute speed when edge was reached
+                        mVelocity = (int) mCurrVelocity;
+                        mDeceleration = getDeceleration(mVelocity);
+                        mStartTime += mDuration;
+                        onEdgeReached();
+                    } else {
+                        // Normal stop, no need to continue
+                        return false;
+                    }
+                    break;
+                case BALLISTIC:
+                    mStartTime += mDuration;
+                    startSpringback(mFinal, mStart, 0);
+                    break;
+                case CUBIC:
+                    return false;
+            }
+
+            update(time);
+            return true;
+        }
+
+        /*
+         * Update the current position and velocity for current time. Returns
+         * true if update has been done and false if animation duration has been
+         * reached.
+         */
+        boolean update(long time) {
+            final long currentTime = time - mStartTime;
+
+            if (currentTime > mDuration) {
+                return false;
+            }
+
+            double distance = 0.0;
+            switch (mState) {
+                case SPLINE: {
+                    final float t = (float) currentTime / mSplineDuration;
+                    final int index = (int) (NB_SAMPLES * t);
+                    float distanceCoef = 1.f;
+                    float velocityCoef = 0.f;
+                    if (index < NB_SAMPLES) {
+                        final float tInf = (float) index / NB_SAMPLES;
+                        final float tSup = (float) (index + 1) / NB_SAMPLES;
+                        final float dInf = SPLINE_POSITION[index];
+                        final float dSup = SPLINE_POSITION[index + 1];
+                        velocityCoef = (dSup - dInf) / (tSup - tInf);
+                        distanceCoef = dInf + (t - tInf) * velocityCoef;
+                    }
+
+                    distance = distanceCoef * mSplineDistance;
+                    mCurrVelocity = velocityCoef * mSplineDistance / mSplineDuration * 1000.0f;
+                    break;
+                }
+
+                case BALLISTIC: {
+                    final float t = currentTime / 1000.0f;
+                    mCurrVelocity = mVelocity + mDeceleration * t;
+                    distance = mVelocity * t + mDeceleration * t * t / 2.0f;
+                    break;
+                }
+
+                case CUBIC: {
+                    final float t = (float) (currentTime) / mDuration;
+                    final float t2 = t * t;
+                    final float sign = Math.signum(mVelocity);
+                    distance = sign * mOver * (3.0f * t2 - 2.0f * t * t2);
+                    mCurrVelocity = sign * mOver * 6.0f * (-t + t2);
+                    break;
+                }
+            }
+
+            mCurrentPosition = mStart + (int) Math.round(distance);
+
+            return true;
+        }
+    }
+}
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -382,16 +382,17 @@ gbjar.sources += ['java/org/mozilla/geck
     'gfx/PanZoomTarget.java',
     'gfx/PluginLayer.java',
     'gfx/PointUtils.java',
     'gfx/ProgressiveUpdateData.java',
     'gfx/RectUtils.java',
     'gfx/RenderTask.java',
     'gfx/ScrollbarLayer.java',
     'gfx/SimpleScaleGestureDetector.java',
+    'gfx/StackScroller.java',
     'gfx/SubdocumentScrollHelper.java',
     'gfx/TextureGenerator.java',
     'gfx/TextureReaper.java',
     'gfx/TouchEventHandler.java',
     'gfx/ViewTransform.java',
     'gfx/VirtualLayer.java',
     'GlobalHistory.java',
     'GuestSession.java',
--- a/widget/android/GeneratedJNIWrappers.cpp
+++ b/widget/android/GeneratedJNIWrappers.cpp
@@ -1613,16 +1613,265 @@ auto ProgressiveUpdateData::Y() const ->
     return mozilla::jni::Field<Y_t>::Get(ProgressiveUpdateData::mCtx, nullptr);
 }
 
 auto ProgressiveUpdateData::Y(float a0) const -> void
 {
     return mozilla::jni::Field<Y_t>::Set(ProgressiveUpdateData::mCtx, nullptr, a0);
 }
 
+const char StackScroller::name[] =
+        "org/mozilla/gecko/gfx/StackScroller";
+
+constexpr char StackScroller::New_t::name[];
+constexpr char StackScroller::New_t::signature[];
+
+auto StackScroller::New(mozilla::jni::Object::Param a0, StackScroller::LocalRef* a1) -> nsresult
+{
+    MOZ_ASSERT(a1);
+    nsresult rv = NS_OK;
+    *a1 = mozilla::jni::Constructor<New_t>::Call(StackScroller::Context(), &rv, a0);
+    return rv;
+}
+
+constexpr char StackScroller::AbortAnimation_t::name[];
+constexpr char StackScroller::AbortAnimation_t::signature[];
+
+auto StackScroller::AbortAnimation() const -> nsresult
+{
+    nsresult rv = NS_OK;
+    mozilla::jni::Method<AbortAnimation_t>::Call(StackScroller::mCtx, &rv);
+    return rv;
+}
+
+constexpr char StackScroller::ComputeScrollOffset_t::name[];
+constexpr char StackScroller::ComputeScrollOffset_t::signature[];
+
+auto StackScroller::ComputeScrollOffset(int64_t a0, bool* a1) const -> nsresult
+{
+    MOZ_ASSERT(a1);
+    nsresult rv = NS_OK;
+    *a1 = mozilla::jni::Method<ComputeScrollOffset_t>::Call(StackScroller::mCtx, &rv, a0);
+    return rv;
+}
+
+constexpr char StackScroller::Fling_t::name[];
+constexpr char StackScroller::Fling_t::signature[];
+
+auto StackScroller::Fling(int32_t a0, int32_t a1, int32_t a2, int32_t a3, int32_t a4, int32_t a5, int32_t a6, int32_t a7, int32_t a8, int32_t a9, int64_t a10) const -> nsresult
+{
+    nsresult rv = NS_OK;
+    mozilla::jni::Method<Fling_t>::Call(StackScroller::mCtx, &rv, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10);
+    return rv;
+}
+
+constexpr char StackScroller::ForceFinished_t::name[];
+constexpr char StackScroller::ForceFinished_t::signature[];
+
+auto StackScroller::ForceFinished(bool a0) const -> nsresult
+{
+    nsresult rv = NS_OK;
+    mozilla::jni::Method<ForceFinished_t>::Call(StackScroller::mCtx, &rv, a0);
+    return rv;
+}
+
+constexpr char StackScroller::GetCurrX_t::name[];
+constexpr char StackScroller::GetCurrX_t::signature[];
+
+auto StackScroller::GetCurrX(int32_t* a0) const -> nsresult
+{
+    MOZ_ASSERT(a0);
+    nsresult rv = NS_OK;
+    *a0 = mozilla::jni::Method<GetCurrX_t>::Call(StackScroller::mCtx, &rv);
+    return rv;
+}
+
+constexpr char StackScroller::GetCurrY_t::name[];
+constexpr char StackScroller::GetCurrY_t::signature[];
+
+auto StackScroller::GetCurrY(int32_t* a0) const -> nsresult
+{
+    MOZ_ASSERT(a0);
+    nsresult rv = NS_OK;
+    *a0 = mozilla::jni::Method<GetCurrY_t>::Call(StackScroller::mCtx, &rv);
+    return rv;
+}
+
+constexpr char StackScroller::GetFinalX_t::name[];
+constexpr char StackScroller::GetFinalX_t::signature[];
+
+auto StackScroller::GetFinalX(int32_t* a0) const -> nsresult
+{
+    MOZ_ASSERT(a0);
+    nsresult rv = NS_OK;
+    *a0 = mozilla::jni::Method<GetFinalX_t>::Call(StackScroller::mCtx, &rv);
+    return rv;
+}
+
+constexpr char StackScroller::GetFinalY_t::name[];
+constexpr char StackScroller::GetFinalY_t::signature[];
+
+auto StackScroller::GetFinalY(int32_t* a0) const -> nsresult
+{
+    MOZ_ASSERT(a0);
+    nsresult rv = NS_OK;
+    *a0 = mozilla::jni::Method<GetFinalY_t>::Call(StackScroller::mCtx, &rv);
+    return rv;
+}
+
+constexpr char StackScroller::InitContants_t::name[];
+constexpr char StackScroller::InitContants_t::signature[];
+
+auto StackScroller::InitContants() -> nsresult
+{
+    nsresult rv = NS_OK;
+    mozilla::jni::Method<InitContants_t>::Call(StackScroller::Context(), &rv);
+    return rv;
+}
+
+constexpr char StackScroller::IsFinished_t::name[];
+constexpr char StackScroller::IsFinished_t::signature[];
+
+auto StackScroller::IsFinished(bool* a0) const -> nsresult
+{
+    MOZ_ASSERT(a0);
+    nsresult rv = NS_OK;
+    *a0 = mozilla::jni::Method<IsFinished_t>::Call(StackScroller::mCtx, &rv);
+    return rv;
+}
+
+constexpr char StackScroller::SetFinalX_t::name[];
+constexpr char StackScroller::SetFinalX_t::signature[];
+
+auto StackScroller::SetFinalX(int32_t a0) const -> nsresult
+{
+    nsresult rv = NS_OK;
+    mozilla::jni::Method<SetFinalX_t>::Call(StackScroller::mCtx, &rv, a0);
+    return rv;
+}
+
+constexpr char StackScroller::SpringBack_t::name[];
+constexpr char StackScroller::SpringBack_t::signature[];
+
+auto StackScroller::SpringBack(int32_t a0, int32_t a1, int32_t a2, int32_t a3, int32_t a4, int32_t a5, int64_t a6, bool* a7) const -> nsresult
+{
+    MOZ_ASSERT(a7);
+    nsresult rv = NS_OK;
+    *a7 = mozilla::jni::Method<SpringBack_t>::Call(StackScroller::mCtx, &rv, a0, a1, a2, a3, a4, a5, a6);
+    return rv;
+}
+
+constexpr char StackScroller::StartScroll_t::name[];
+constexpr char StackScroller::StartScroll_t::signature[];
+
+auto StackScroller::StartScroll(int32_t a0, int32_t a1, int32_t a2, int32_t a3, int64_t a4, int32_t a5) const -> nsresult
+{
+    nsresult rv = NS_OK;
+    mozilla::jni::Method<StartScroll_t>::Call(StackScroller::mCtx, &rv, a0, a1, a2, a3, a4, a5);
+    return rv;
+}
+
+constexpr char StackScroller::ViscousFluid_t::name[];
+constexpr char StackScroller::ViscousFluid_t::signature[];
+
+auto StackScroller::ViscousFluid(float a0, float* a1) -> nsresult
+{
+    MOZ_ASSERT(a1);
+    nsresult rv = NS_OK;
+    *a1 = mozilla::jni::Method<ViscousFluid_t>::Call(StackScroller::Context(), &rv, a0);
+    return rv;
+}
+
+constexpr char StackScroller::MFlywheel_t::name[];
+constexpr char StackScroller::MFlywheel_t::signature[];
+
+auto StackScroller::MFlywheel(bool* a0) const -> nsresult
+{
+    MOZ_ASSERT(a0);
+    nsresult rv = NS_OK;
+    *a0 = mozilla::jni::Field<MFlywheel_t>::Get(StackScroller::mCtx, &rv);
+    return rv;
+}
+
+constexpr char StackScroller::MMode_t::name[];
+constexpr char StackScroller::MMode_t::signature[];
+
+auto StackScroller::MMode(int32_t* a0) const -> nsresult
+{
+    MOZ_ASSERT(a0);
+    nsresult rv = NS_OK;
+    *a0 = mozilla::jni::Field<MMode_t>::Get(StackScroller::mCtx, &rv);
+    return rv;
+}
+
+auto StackScroller::MMode(int32_t a0) const -> nsresult
+{
+    nsresult rv = NS_OK;
+    mozilla::jni::Field<MMode_t>::Set(StackScroller::mCtx, &rv, a0);
+    return rv;
+}
+
+constexpr char StackScroller::MScrollerX_t::name[];
+constexpr char StackScroller::MScrollerX_t::signature[];
+
+auto StackScroller::MScrollerX(mozilla::jni::Object::LocalRef* a0) const -> nsresult
+{
+    MOZ_ASSERT(a0);
+    nsresult rv = NS_OK;
+    *a0 = mozilla::jni::Field<MScrollerX_t>::Get(StackScroller::mCtx, &rv);
+    return rv;
+}
+
+constexpr char StackScroller::MScrollerY_t::name[];
+constexpr char StackScroller::MScrollerY_t::signature[];
+
+auto StackScroller::MScrollerY(mozilla::jni::Object::LocalRef* a0) const -> nsresult
+{
+    MOZ_ASSERT(a0);
+    nsresult rv = NS_OK;
+    *a0 = mozilla::jni::Field<MScrollerY_t>::Get(StackScroller::mCtx, &rv);
+    return rv;
+}
+
+constexpr char StackScroller::SViscousFluidNormalize_t::name[];
+constexpr char StackScroller::SViscousFluidNormalize_t::signature[];
+
+auto StackScroller::SViscousFluidNormalize(float* a0) -> nsresult
+{
+    MOZ_ASSERT(a0);
+    nsresult rv = NS_OK;
+    *a0 = mozilla::jni::Field<SViscousFluidNormalize_t>::Get(StackScroller::Context(), &rv);
+    return rv;
+}
+
+auto StackScroller::SViscousFluidNormalize(float a0) -> nsresult
+{
+    nsresult rv = NS_OK;
+    mozilla::jni::Field<SViscousFluidNormalize_t>::Set(StackScroller::Context(), &rv, a0);
+    return rv;
+}
+
+constexpr char StackScroller::SViscousFluidScale_t::name[];
+constexpr char StackScroller::SViscousFluidScale_t::signature[];
+
+auto StackScroller::SViscousFluidScale(float* a0) -> nsresult
+{
+    MOZ_ASSERT(a0);
+    nsresult rv = NS_OK;
+    *a0 = mozilla::jni::Field<SViscousFluidScale_t>::Get(StackScroller::Context(), &rv);
+    return rv;
+}
+
+auto StackScroller::SViscousFluidScale(float a0) -> nsresult
+{
+    nsresult rv = NS_OK;
+    mozilla::jni::Field<SViscousFluidScale_t>::Set(StackScroller::Context(), &rv, a0);
+    return rv;
+}
+
 const char ViewTransform::name[] =
         "org/mozilla/gecko/gfx/ViewTransform";
 
 constexpr char ViewTransform::New_t::name[];
 constexpr char ViewTransform::New_t::signature[];
 
 auto ViewTransform::New(float a0, float a1, float a2) -> ViewTransform::LocalRef
 {
--- a/widget/android/GeneratedJNIWrappers.h
+++ b/widget/android/GeneratedJNIWrappers.h
@@ -3929,16 +3929,381 @@ public:
     auto Y() const -> float;
 
     auto Y(float) const -> void;
 
     static const bool isMultithreaded = true;
 
 };
 
+class StackScroller : public mozilla::jni::ObjectBase<StackScroller, jobject>
+{
+public:
+    static const char name[];
+
+    explicit StackScroller(const Context& ctx) : ObjectBase<StackScroller, jobject>(ctx) {}
+
+    struct New_t {
+        typedef StackScroller Owner;
+        typedef StackScroller::LocalRef ReturnType;
+        typedef StackScroller::Param SetterType;
+        typedef mozilla::jni::Args<
+                mozilla::jni::Object::Param> Args;
+        static constexpr char name[] = "<init>";
+        static constexpr char signature[] =
+                "(Landroid/content/Context;)V";
+        static const bool isStatic = false;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::NSRESULT;
+    };
+
+    static auto New(mozilla::jni::Object::Param, StackScroller::LocalRef*) -> nsresult;
+
+    struct AbortAnimation_t {
+        typedef StackScroller Owner;
+        typedef void ReturnType;
+        typedef void SetterType;
+        typedef mozilla::jni::Args<> Args;
+        static constexpr char name[] = "abortAnimation";
+        static constexpr char signature[] =
+                "()V";
+        static const bool isStatic = false;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::NSRESULT;
+    };
+
+    auto AbortAnimation() const -> nsresult;
+
+    struct ComputeScrollOffset_t {
+        typedef StackScroller Owner;
+        typedef bool ReturnType;
+        typedef bool SetterType;
+        typedef mozilla::jni::Args<
+                int64_t> Args;
+        static constexpr char name[] = "computeScrollOffset";
+        static constexpr char signature[] =
+                "(J)Z";
+        static const bool isStatic = false;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::NSRESULT;
+    };
+
+    auto ComputeScrollOffset(int64_t, bool*) const -> nsresult;
+
+    struct Fling_t {
+        typedef StackScroller Owner;
+        typedef void ReturnType;
+        typedef void SetterType;
+        typedef mozilla::jni::Args<
+                int32_t,
+                int32_t,
+                int32_t,
+                int32_t,
+                int32_t,
+                int32_t,
+                int32_t,
+                int32_t,
+                int32_t,
+                int32_t,
+                int64_t> Args;
+        static constexpr char name[] = "fling";
+        static constexpr char signature[] =
+                "(IIIIIIIIIIJ)V";
+        static const bool isStatic = false;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::NSRESULT;
+    };
+
+    auto Fling(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int64_t) const -> nsresult;
+
+    struct ForceFinished_t {
+        typedef StackScroller Owner;
+        typedef void ReturnType;
+        typedef void SetterType;
+        typedef mozilla::jni::Args<
+                bool> Args;
+        static constexpr char name[] = "forceFinished";
+        static constexpr char signature[] =
+                "(Z)V";
+        static const bool isStatic = false;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::NSRESULT;
+    };
+
+    auto ForceFinished(bool) const -> nsresult;
+
+    struct GetCurrX_t {
+        typedef StackScroller Owner;
+        typedef int32_t ReturnType;
+        typedef int32_t SetterType;
+        typedef mozilla::jni::Args<> Args;
+        static constexpr char name[] = "getCurrX";
+        static constexpr char signature[] =
+                "()I";
+        static const bool isStatic = false;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::NSRESULT;
+    };
+
+    auto GetCurrX(int32_t*) const -> nsresult;
+
+    struct GetCurrY_t {
+        typedef StackScroller Owner;
+        typedef int32_t ReturnType;
+        typedef int32_t SetterType;
+        typedef mozilla::jni::Args<> Args;
+        static constexpr char name[] = "getCurrY";
+        static constexpr char signature[] =
+                "()I";
+        static const bool isStatic = false;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::NSRESULT;
+    };
+
+    auto GetCurrY(int32_t*) const -> nsresult;
+
+    struct GetFinalX_t {
+        typedef StackScroller Owner;
+        typedef int32_t ReturnType;
+        typedef int32_t SetterType;
+        typedef mozilla::jni::Args<> Args;
+        static constexpr char name[] = "getFinalX";
+        static constexpr char signature[] =
+                "()I";
+        static const bool isStatic = false;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::NSRESULT;
+    };
+
+    auto GetFinalX(int32_t*) const -> nsresult;
+
+    struct GetFinalY_t {
+        typedef StackScroller Owner;
+        typedef int32_t ReturnType;
+        typedef int32_t SetterType;
+        typedef mozilla::jni::Args<> Args;
+        static constexpr char name[] = "getFinalY";
+        static constexpr char signature[] =
+                "()I";
+        static const bool isStatic = false;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::NSRESULT;
+    };
+
+    auto GetFinalY(int32_t*) const -> nsresult;
+
+    struct InitContants_t {
+        typedef StackScroller Owner;
+        typedef void ReturnType;
+        typedef void SetterType;
+        typedef mozilla::jni::Args<> Args;
+        static constexpr char name[] = "initContants";
+        static constexpr char signature[] =
+                "()V";
+        static const bool isStatic = true;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::NSRESULT;
+    };
+
+    static auto InitContants() -> nsresult;
+
+    struct IsFinished_t {
+        typedef StackScroller Owner;
+        typedef bool ReturnType;
+        typedef bool SetterType;
+        typedef mozilla::jni::Args<> Args;
+        static constexpr char name[] = "isFinished";
+        static constexpr char signature[] =
+                "()Z";
+        static const bool isStatic = false;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::NSRESULT;
+    };
+
+    auto IsFinished(bool*) const -> nsresult;
+
+    struct SetFinalX_t {
+        typedef StackScroller Owner;
+        typedef void ReturnType;
+        typedef void SetterType;
+        typedef mozilla::jni::Args<
+                int32_t> Args;
+        static constexpr char name[] = "setFinalX";
+        static constexpr char signature[] =
+                "(I)V";
+        static const bool isStatic = false;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::NSRESULT;
+    };
+
+    auto SetFinalX(int32_t) const -> nsresult;
+
+    struct SpringBack_t {
+        typedef StackScroller Owner;
+        typedef bool ReturnType;
+        typedef bool SetterType;
+        typedef mozilla::jni::Args<
+                int32_t,
+                int32_t,
+                int32_t,
+                int32_t,
+                int32_t,
+                int32_t,
+                int64_t> Args;
+        static constexpr char name[] = "springBack";
+        static constexpr char signature[] =
+                "(IIIIIIJ)Z";
+        static const bool isStatic = false;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::NSRESULT;
+    };
+
+    auto SpringBack(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int64_t, bool*) const -> nsresult;
+
+    struct StartScroll_t {
+        typedef StackScroller Owner;
+        typedef void ReturnType;
+        typedef void SetterType;
+        typedef mozilla::jni::Args<
+                int32_t,
+                int32_t,
+                int32_t,
+                int32_t,
+                int64_t,
+                int32_t> Args;
+        static constexpr char name[] = "startScroll";
+        static constexpr char signature[] =
+                "(IIIIJI)V";
+        static const bool isStatic = false;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::NSRESULT;
+    };
+
+    auto StartScroll(int32_t, int32_t, int32_t, int32_t, int64_t, int32_t) const -> nsresult;
+
+    struct ViscousFluid_t {
+        typedef StackScroller Owner;
+        typedef float ReturnType;
+        typedef float SetterType;
+        typedef mozilla::jni::Args<
+                float> Args;
+        static constexpr char name[] = "viscousFluid";
+        static constexpr char signature[] =
+                "(F)F";
+        static const bool isStatic = true;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::NSRESULT;
+    };
+
+    static auto ViscousFluid(float, float*) -> nsresult;
+
+    static const int32_t FLING_MODE = 1;
+
+    static const int32_t SCROLL_MODE = 0;
+
+    struct MFlywheel_t {
+        typedef StackScroller Owner;
+        typedef bool ReturnType;
+        typedef bool SetterType;
+        typedef mozilla::jni::Args<> Args;
+        static constexpr char name[] = "mFlywheel";
+        static constexpr char signature[] =
+                "Z";
+        static const bool isStatic = false;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::NSRESULT;
+    };
+
+    auto MFlywheel(bool*) const -> nsresult;
+
+    struct MMode_t {
+        typedef StackScroller Owner;
+        typedef int32_t ReturnType;
+        typedef int32_t SetterType;
+        typedef mozilla::jni::Args<> Args;
+        static constexpr char name[] = "mMode";
+        static constexpr char signature[] =
+                "I";
+        static const bool isStatic = false;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::NSRESULT;
+    };
+
+    auto MMode(int32_t*) const -> nsresult;
+
+    auto MMode(int32_t) const -> nsresult;
+
+    struct MScrollerX_t {
+        typedef StackScroller Owner;
+        typedef mozilla::jni::Object::LocalRef ReturnType;
+        typedef mozilla::jni::Object::Param SetterType;
+        typedef mozilla::jni::Args<> Args;
+        static constexpr char name[] = "mScrollerX";
+        static constexpr char signature[] =
+                "Lorg/mozilla/gecko/gfx/StackScroller$SplineStackScroller;";
+        static const bool isStatic = false;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::NSRESULT;
+    };
+
+    auto MScrollerX(mozilla::jni::Object::LocalRef*) const -> nsresult;
+
+    struct MScrollerY_t {
+        typedef StackScroller Owner;
+        typedef mozilla::jni::Object::LocalRef ReturnType;
+        typedef mozilla::jni::Object::Param SetterType;
+        typedef mozilla::jni::Args<> Args;
+        static constexpr char name[] = "mScrollerY";
+        static constexpr char signature[] =
+                "Lorg/mozilla/gecko/gfx/StackScroller$SplineStackScroller;";
+        static const bool isStatic = false;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::NSRESULT;
+    };
+
+    auto MScrollerY(mozilla::jni::Object::LocalRef*) const -> nsresult;
+
+    struct SViscousFluidNormalize_t {
+        typedef StackScroller Owner;
+        typedef float ReturnType;
+        typedef float SetterType;
+        typedef mozilla::jni::Args<> Args;
+        static constexpr char name[] = "sViscousFluidNormalize";
+        static constexpr char signature[] =
+                "F";
+        static const bool isStatic = true;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::NSRESULT;
+    };
+
+    static auto SViscousFluidNormalize(float*) -> nsresult;
+
+    static auto SViscousFluidNormalize(float) -> nsresult;
+
+    struct SViscousFluidScale_t {
+        typedef StackScroller Owner;
+        typedef float ReturnType;
+        typedef float SetterType;
+        typedef mozilla::jni::Args<> Args;
+        static constexpr char name[] = "sViscousFluidScale";
+        static constexpr char signature[] =
+                "F";
+        static const bool isStatic = true;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::NSRESULT;
+    };
+
+    static auto SViscousFluidScale(float*) -> nsresult;
+
+    static auto SViscousFluidScale(float) -> nsresult;
+
+    static const bool isMultithreaded = true;
+
+};
+
 class ViewTransform : public mozilla::jni::ObjectBase<ViewTransform, jobject>
 {
 public:
     static const char name[];
 
     explicit ViewTransform(const Context& ctx) : ObjectBase<ViewTransform, jobject>(ctx) {}
 
     struct New_t {
deleted file mode 100644
--- a/widget/android/bindings/OverScroller-classes.txt
+++ /dev/null
@@ -1,1 +0,0 @@
-android.widget.OverScroller
--- a/widget/android/bindings/moz.build
+++ b/widget/android/bindings/moz.build
@@ -7,17 +7,16 @@
 # List of stems to generate .cpp and .h files for.  To add a stem, add it to
 # this list and ensure that $(stem)-classes.txt exists in this directory.
 generated = [
     'AndroidRect',
     'Bundle',
     'KeyEvent',
     'MediaCodec',
     'MotionEvent',
-    'OverScroller',
     'SurfaceTexture',
     'ViewConfiguration'
 ]
 
 SOURCES += ['!%s.cpp' % stem for stem in generated]
 
 EXPORTS += ['!%s.h' % stem for stem in generated]