Bug 1402498 - Clean up ScrollAnimationPhysics code after the separation. r?rhunt draft
authorMarkus Stange <mstange@themasta.com>
Fri, 22 Sep 2017 15:08:39 -0400
changeset 672907 81237dbc3a41424bbaaad2e3c94c0aa1b41f1dd4
parent 672906 2879bfab982715010c6e65483b8a049b309cdada
child 672908 d2b8c4f1811f4b86719379154ac8e52e9f2e08a4
push id82420
push userbmo:mstange@themasta.com
push dateFri, 29 Sep 2017 22:38:50 +0000
reviewersrhunt
bugs1402498
milestone58.0a1
Bug 1402498 - Clean up ScrollAnimationPhysics code after the separation. r?rhunt MozReview-Commit-ID: Ldm96zHxqWD
gfx/layers/apz/src/GenericScrollAnimation.cpp
gfx/layers/apz/src/GenericScrollAnimation.h
gfx/layers/apz/src/KeyboardScrollAnimation.cpp
gfx/layers/apz/src/WheelScrollAnimation.cpp
layout/generic/ScrollAnimationPhysics.cpp
layout/generic/ScrollAnimationPhysics.h
layout/generic/nsGfxScrollFrame.cpp
--- a/gfx/layers/apz/src/GenericScrollAnimation.cpp
+++ b/gfx/layers/apz/src/GenericScrollAnimation.cpp
@@ -9,19 +9,20 @@
 #include "AsyncPanZoomController.h"
 #include "gfxPrefs.h"
 #include "nsPoint.h"
 
 namespace mozilla {
 namespace layers {
 
 GenericScrollAnimation::GenericScrollAnimation(AsyncPanZoomController& aApzc,
-                                               const nsPoint& aInitialPosition)
+                                               const nsPoint& aInitialPosition,
+                                               const ScrollAnimationPhysicsSettings& aSettings)
   : mApzc(aApzc)
-  , mAnimationPhysics(aInitialPosition)
+  , mAnimationPhysics(MakeUnique<ScrollAnimationPhysics>(aInitialPosition, aSettings))
   , mFinalDestination(aInitialPosition)
   , mForceVerticalOverscroll(false)
 {
 }
 
 void
 GenericScrollAnimation::UpdateDelta(TimeStamp aTime, nsPoint aDelta, const nsSize& aCurrentVelocity)
 {
@@ -36,51 +37,45 @@ GenericScrollAnimation::UpdateDestinatio
   mFinalDestination = aDestination;
 
   Update(aTime, aCurrentVelocity);
 }
 
 void
 GenericScrollAnimation::Update(TimeStamp aTime, const nsSize& aCurrentVelocity)
 {
-  if (mAnimationPhysics.mIsFirstIteration) {
-    mAnimationPhysics.InitializeHistory(aTime);
-  }
-
   // Clamp the final destination to the scrollable area.
   CSSPoint clamped = CSSPoint::FromAppUnits(mFinalDestination);
   clamped.x = mApzc.mX.ClampOriginToScrollableRect(clamped.x);
   clamped.y = mApzc.mY.ClampOriginToScrollableRect(clamped.y);
   mFinalDestination = CSSPoint::ToAppUnits(clamped);
 
-  mAnimationPhysics.Update(aTime, mFinalDestination, aCurrentVelocity);
+  mAnimationPhysics->Update(aTime, mFinalDestination, aCurrentVelocity);
 }
 
 bool
 GenericScrollAnimation::DoSample(FrameMetrics& aFrameMetrics, const TimeDuration& aDelta)
 {
   TimeStamp now = mApzc.GetFrameTime();
   CSSToParentLayerScale2D zoom = aFrameMetrics.GetZoom();
 
   // If the animation is finished, make sure the final position is correct by
   // using one last displacement. Otherwise, compute the delta via the timing
   // function as normal.
-  bool finished = mAnimationPhysics.IsFinished(now);
-  nsPoint sampledDest = finished
-                        ? mAnimationPhysics.mDestination
-                        : mAnimationPhysics.PositionAt(now);
+  bool finished = mAnimationPhysics->IsFinished(now);
+  nsPoint sampledDest = mAnimationPhysics->PositionAt(now);
   ParentLayerPoint displacement =
     (CSSPoint::FromAppUnits(sampledDest) - aFrameMetrics.GetScrollOffset()) * zoom;
 
   if (finished) {
     mApzc.mX.SetVelocity(0);
     mApzc.mY.SetVelocity(0);
   } else if (!IsZero(displacement)) {
     // Convert velocity from AppUnits/Seconds to ParentLayerCoords/Milliseconds
-    nsSize velocity = mAnimationPhysics.VelocityAt(now);
+    nsSize velocity = mAnimationPhysics->VelocityAt(now);
     ParentLayerPoint velocityPL =
       CSSPoint::FromAppUnits(nsPoint(velocity.width, velocity.height)) * zoom;
     mApzc.mX.SetVelocity(velocityPL.x / 1000.0);
     mApzc.mY.SetVelocity(velocityPL.y / 1000.0);
   }
 
   // Note: we ignore overscroll for generic animations.
   ParentLayerPoint adjustedOffset, overscroll;
--- a/gfx/layers/apz/src/GenericScrollAnimation.h
+++ b/gfx/layers/apz/src/GenericScrollAnimation.h
@@ -15,33 +15,34 @@ namespace layers {
 
 class AsyncPanZoomController;
 
 class GenericScrollAnimation
   : public AsyncPanZoomAnimation
 {
 public:
   GenericScrollAnimation(AsyncPanZoomController& aApzc,
-                         const nsPoint& aInitialPosition);
+                         const nsPoint& aInitialPosition,
+                         const ScrollAnimationPhysicsSettings& aSettings);
 
   bool DoSample(FrameMetrics& aFrameMetrics, const TimeDuration& aDelta) override;
 
   void UpdateDelta(TimeStamp aTime, nsPoint aDelta, const nsSize& aCurrentVelocity);
   void UpdateDestination(TimeStamp aTime, nsPoint aDestination, const nsSize& aCurrentVelocity);
 
   CSSPoint GetDestination() const {
     return CSSPoint::FromAppUnits(mFinalDestination);
   }
 
 private:
   void Update(TimeStamp aTime, const nsSize& aCurrentVelocity);
- 
+
 protected:
   AsyncPanZoomController& mApzc;
-  ScrollAnimationPhysics mAnimationPhysics;
+  UniquePtr<ScrollAnimationPhysics> mAnimationPhysics;
   nsPoint mFinalDestination;
   bool mForceVerticalOverscroll;
 };
 
 } // namespace layers
 } // namespace mozilla
 
 #endif // mozilla_layers_GenericScrollAnimation_h_
--- a/gfx/layers/apz/src/KeyboardScrollAnimation.cpp
+++ b/gfx/layers/apz/src/KeyboardScrollAnimation.cpp
@@ -6,39 +6,48 @@
 
 #include "KeyboardScrollAnimation.h"
 
 #include "gfxPrefs.h"
 
 namespace mozilla {
 namespace layers {
 
-KeyboardScrollAnimation::KeyboardScrollAnimation(AsyncPanZoomController& aApzc,
-                                                 const nsPoint& aInitialPosition,
-                                                 KeyboardScrollAction::KeyboardScrollActionType aType)
-  : GenericScrollAnimation(aApzc, aInitialPosition)
+static ScrollAnimationPhysicsSettings
+SettingsForType(KeyboardScrollAction::KeyboardScrollActionType aType)
 {
+  int32_t minMS = 0;
+  int32_t maxMS = 0;
+
   switch (aType) {
     case KeyboardScrollAction::eScrollCharacter:
     case KeyboardScrollAction::eScrollLine: {
-      mAnimationPhysics.mOriginMaxMS = clamped(gfxPrefs::LineSmoothScrollMaxDurationMs(), 0, 10000);
-      mAnimationPhysics.mOriginMinMS = clamped(gfxPrefs::LineSmoothScrollMinDurationMs(), 0, mAnimationPhysics.mOriginMaxMS);
+      maxMS = clamped(gfxPrefs::LineSmoothScrollMaxDurationMs(), 0, 10000);
+      minMS = clamped(gfxPrefs::LineSmoothScrollMinDurationMs(), 0, maxMS);
       break;
     }
     case KeyboardScrollAction::eScrollPage: {
-      mAnimationPhysics.mOriginMaxMS = clamped(gfxPrefs::PageSmoothScrollMaxDurationMs(), 0, 10000);
-      mAnimationPhysics.mOriginMinMS = clamped(gfxPrefs::PageSmoothScrollMinDurationMs(), 0, mAnimationPhysics.mOriginMaxMS);
+      maxMS = clamped(gfxPrefs::PageSmoothScrollMaxDurationMs(), 0, 10000);
+      minMS = clamped(gfxPrefs::PageSmoothScrollMinDurationMs(), 0, maxMS);
       break;
     }
     case KeyboardScrollAction::eScrollComplete: {
-      mAnimationPhysics.mOriginMaxMS = clamped(gfxPrefs::OtherSmoothScrollMaxDurationMs(), 0, 10000);
-      mAnimationPhysics.mOriginMinMS = clamped(gfxPrefs::OtherSmoothScrollMinDurationMs(), 0, mAnimationPhysics.mOriginMaxMS);
+      maxMS = clamped(gfxPrefs::OtherSmoothScrollMaxDurationMs(), 0, 10000);
+      minMS = clamped(gfxPrefs::OtherSmoothScrollMinDurationMs(), 0, maxMS);
       break;
     }
   }
 
   // The pref is 100-based int percentage, while mIntervalRatio is 1-based ratio
-  mAnimationPhysics.mIntervalRatio = ((double)gfxPrefs::SmoothScrollDurationToIntervalRatio()) / 100.0;
-  mAnimationPhysics.mIntervalRatio = std::max(1.0, mAnimationPhysics.mIntervalRatio);
+  double intervalRatio = ((double)gfxPrefs::SmoothScrollDurationToIntervalRatio()) / 100.0;
+  intervalRatio = std::max(1.0, intervalRatio);
+  return ScrollAnimationPhysicsSettings { minMS, maxMS, intervalRatio };
+}
+
+KeyboardScrollAnimation::KeyboardScrollAnimation(AsyncPanZoomController& aApzc,
+                                                 const nsPoint& aInitialPosition,
+                                                 KeyboardScrollAction::KeyboardScrollActionType aType)
+  : GenericScrollAnimation(aApzc, aInitialPosition, SettingsForType(aType))
+{
 }
 
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/apz/src/WheelScrollAnimation.cpp
+++ b/gfx/layers/apz/src/WheelScrollAnimation.cpp
@@ -8,37 +8,45 @@
 
 #include "AsyncPanZoomController.h"
 #include "gfxPrefs.h"
 #include "nsPoint.h"
 
 namespace mozilla {
 namespace layers {
 
+static ScrollAnimationPhysicsSettings
+SettingsForDeltaType(ScrollWheelInput::ScrollDeltaType aDeltaType)
+{
+  int32_t minMS = 0;
+  int32_t maxMS = 0;
+
+  switch (aDeltaType) {
+    case ScrollWheelInput::SCROLLDELTA_PAGE:
+      maxMS = clamped(gfxPrefs::PageSmoothScrollMaxDurationMs(), 0, 10000);
+      minMS = clamped(gfxPrefs::PageSmoothScrollMinDurationMs(), 0, maxMS);
+      break;
+    case ScrollWheelInput::SCROLLDELTA_PIXEL:
+      maxMS = clamped(gfxPrefs::PixelSmoothScrollMaxDurationMs(), 0, 10000);
+      minMS = clamped(gfxPrefs::PixelSmoothScrollMinDurationMs(), 0, maxMS);
+      break;
+    case ScrollWheelInput::SCROLLDELTA_LINE:
+      maxMS = clamped(gfxPrefs::WheelSmoothScrollMaxDurationMs(), 0, 10000);
+      minMS = clamped(gfxPrefs::WheelSmoothScrollMinDurationMs(), 0, maxMS);
+      break;
+  }
+
+  // The pref is 100-based int percentage, while mIntervalRatio is 1-based ratio
+  double intervalRatio = ((double)gfxPrefs::SmoothScrollDurationToIntervalRatio()) / 100.0;
+  intervalRatio = std::max(1.0, intervalRatio);
+  return ScrollAnimationPhysicsSettings { minMS, maxMS, intervalRatio };
+}
+
 WheelScrollAnimation::WheelScrollAnimation(AsyncPanZoomController& aApzc,
                                            const nsPoint& aInitialPosition,
                                            ScrollWheelInput::ScrollDeltaType aDeltaType)
-  : GenericScrollAnimation(aApzc, aInitialPosition)
+  : GenericScrollAnimation(aApzc, aInitialPosition, SettingsForDeltaType(aDeltaType))
 {
   mForceVerticalOverscroll = !mApzc.mScrollMetadata.AllowVerticalScrollWithWheel();
-
-  switch (aDeltaType) {
-  case ScrollWheelInput::SCROLLDELTA_PAGE:
-    mAnimationPhysics.mOriginMaxMS = clamped(gfxPrefs::PageSmoothScrollMaxDurationMs(), 0, 10000);
-    mAnimationPhysics.mOriginMinMS = clamped(gfxPrefs::PageSmoothScrollMinDurationMs(), 0, mAnimationPhysics.mOriginMaxMS);
-    break;
-  case ScrollWheelInput::SCROLLDELTA_PIXEL:
-    mAnimationPhysics.mOriginMaxMS = clamped(gfxPrefs::PixelSmoothScrollMaxDurationMs(), 0, 10000);
-    mAnimationPhysics.mOriginMinMS = clamped(gfxPrefs::PixelSmoothScrollMinDurationMs(), 0, mAnimationPhysics.mOriginMaxMS);
-    break;
-  case ScrollWheelInput::SCROLLDELTA_LINE:
-    mAnimationPhysics.mOriginMaxMS = clamped(gfxPrefs::WheelSmoothScrollMaxDurationMs(), 0, 10000);
-    mAnimationPhysics.mOriginMinMS = clamped(gfxPrefs::WheelSmoothScrollMinDurationMs(), 0, mAnimationPhysics.mOriginMaxMS);
-    break;
-  }
-
-  // The pref is 100-based int percentage, while mIntervalRatio is 1-based ratio
-  mAnimationPhysics.mIntervalRatio = ((double)gfxPrefs::SmoothScrollDurationToIntervalRatio()) / 100.0;
-  mAnimationPhysics.mIntervalRatio = std::max(1.0, mAnimationPhysics.mIntervalRatio);
 }
 
 } // namespace layers
 } // namespace mozilla
--- a/layout/generic/ScrollAnimationPhysics.cpp
+++ b/layout/generic/ScrollAnimationPhysics.cpp
@@ -3,27 +3,33 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "ScrollAnimationPhysics.h"
 #include "gfxPrefs.h"
 
 using namespace mozilla;
 
-ScrollAnimationPhysics::ScrollAnimationPhysics(nsPoint aStartPos)
- : mIsFirstIteration(true)
+ScrollAnimationPhysics::ScrollAnimationPhysics(nsPoint aStartPos,
+                                const ScrollAnimationPhysicsSettings& aSettings)
+ : mSettings(aSettings)
  , mStartPos(aStartPos)
+ , mIsFirstIteration(true)
 {
 }
 
 void
 ScrollAnimationPhysics::Update(TimeStamp aTime,
                                nsPoint aDestination,
                                const nsSize& aCurrentVelocity)
 {
+  if (mIsFirstIteration) {
+    InitializeHistory(aTime);
+  }
+
   TimeDuration duration = ComputeDuration(aTime);
   nsSize currentVelocity = aCurrentVelocity;
 
   if (!mIsFirstIteration) {
     // If an additional event has not changed the destination, then do not let
     // another minimum duration reset slow things down.  If it would then
     // instead continue with the existing timing function.
     if (aDestination == mDestination &&
@@ -55,29 +61,32 @@ ScrollAnimationPhysics::ComputeDuration(
   mPrevEventTime[1] = mPrevEventTime[0];
   mPrevEventTime[0] = aTime;
 
   // Modulate duration according to events rate (quicker events -> shorter durations).
   // The desired effect is to use longer duration when scrolling slowly, such that
   // it's easier to follow, but reduce the duration to make it feel more snappy when
   // scrolling quickly. To reduce fluctuations of the duration, we average event
   // intervals using the recent 4 timestamps (now + three prev -> 3 intervals).
-  int32_t durationMS = clamped<int32_t>(eventsDeltaMs * mIntervalRatio, mOriginMinMS, mOriginMaxMS);
+  int32_t durationMS =
+    clamped<int32_t>(eventsDeltaMs * mSettings.mIntervalRatio,
+                     mSettings.mMinMS, mSettings.mMaxMS);
 
   return TimeDuration::FromMilliseconds(durationMS);
 }
 
 void
 ScrollAnimationPhysics::InitializeHistory(TimeStamp aTime)
 {
   // Starting a new scroll (i.e. not when extending an existing scroll animation),
   // create imaginary prev timestamps with maximum relevant intervals between them.
 
   // Longest relevant interval (which results in maximum duration)
-  TimeDuration maxDelta = TimeDuration::FromMilliseconds(mOriginMaxMS / mIntervalRatio);
+  TimeDuration maxDelta =
+    TimeDuration::FromMilliseconds(mSettings.mMaxMS / mSettings.mIntervalRatio);
   mPrevEventTime[0] = aTime              - maxDelta;
   mPrevEventTime[1] = mPrevEventTime[0]  - maxDelta;
   mPrevEventTime[2] = mPrevEventTime[1]  - maxDelta;
 }
 
 void
 ScrollAnimationPhysics::InitTimingFunction(nsSMILKeySpline& aTimingFunction,
                                            nscoord aCurrentPos,
@@ -93,27 +102,35 @@ ScrollAnimationPhysics::InitTimingFuncti
   double slope = aCurrentVelocity * (mDuration / oneSecond) / (aDestination - aCurrentPos);
   double normalization = sqrt(1.0 + slope * slope);
   double dt = 1.0 / normalization * gfxPrefs::SmoothScrollCurrentVelocityWeighting();
   double dxy = slope / normalization * gfxPrefs::SmoothScrollCurrentVelocityWeighting();
   aTimingFunction.Init(dt, dxy, 1 - gfxPrefs::SmoothScrollStopDecelerationWeighting(), 1);
 }
 
 nsPoint
-ScrollAnimationPhysics::PositionAt(TimeStamp aTime) const
+ScrollAnimationPhysics::PositionAt(TimeStamp aTime)
 {
+  if (IsFinished(aTime)) {
+    return mDestination;
+  }
+
   double progressX = mTimingFunctionX.GetSplineValue(ProgressAt(aTime));
   double progressY = mTimingFunctionY.GetSplineValue(ProgressAt(aTime));
   return nsPoint(NSToCoordRound((1 - progressX) * mStartPos.x + progressX * mDestination.x),
                  NSToCoordRound((1 - progressY) * mStartPos.y + progressY * mDestination.y));
 }
 
 nsSize
-ScrollAnimationPhysics::VelocityAt(TimeStamp aTime) const
+ScrollAnimationPhysics::VelocityAt(TimeStamp aTime)
 {
+  if (IsFinished(aTime)) {
+    return nsSize(0, 0);
+  }
+
   double timeProgress = ProgressAt(aTime);
   return nsSize(VelocityComponent(timeProgress, mTimingFunctionX,
                                   mStartPos.x, mDestination.x),
                 VelocityComponent(timeProgress, mTimingFunctionY,
                                   mStartPos.y, mDestination.y));
 }
 
 nscoord
--- a/layout/generic/ScrollAnimationPhysics.h
+++ b/layout/generic/ScrollAnimationPhysics.h
@@ -7,56 +7,53 @@
 #define mozilla_layout_ScrollAnimationPhysics_h_
 
 #include "mozilla/TimeStamp.h"
 #include "nsPoint.h"
 #include "nsSMILKeySpline.h"
 
 namespace mozilla {
 
+struct ScrollAnimationPhysicsSettings
+{
+  // These values are minimum and maximum animation duration per event,
+  // and a global ratio which defines how longer is the animation's duration
+  // compared to the average recent events intervals (such that for a relatively
+  // consistent events rate, the next event arrives before current animation ends)
+  int32_t mMinMS;
+  int32_t mMaxMS;
+  double mIntervalRatio;
+};
+
 // This is the base class for driving scroll wheel animation on both the
 // compositor and main thread.
 class ScrollAnimationPhysics
 {
 public:
   typedef mozilla::TimeStamp TimeStamp;
   typedef mozilla::TimeDuration TimeDuration;
 
-  explicit ScrollAnimationPhysics(nsPoint aStartPos);
+  explicit ScrollAnimationPhysics(nsPoint aStartPos,
+                                  const ScrollAnimationPhysicsSettings& aSettings);
 
   void Update(TimeStamp aTime,
               nsPoint aDestination,
               const nsSize& aCurrentVelocity);
 
   // Get the velocity at a point in time in nscoords/sec.
-  nsSize VelocityAt(TimeStamp aTime) const;
+  nsSize VelocityAt(TimeStamp aTime);
 
   // Returns the expected scroll position at a given point in time, in app
   // units, relative to the scroll frame.
-  nsPoint PositionAt(TimeStamp aTime) const;
+  nsPoint PositionAt(TimeStamp aTime);
 
   bool IsFinished(TimeStamp aTime) {
     return aTime > mStartTime + mDuration;
   }
 
-  // Initialize event history.
-  void InitializeHistory(TimeStamp aTime);
-
-  // Cached Preferences value.
-  //
-  // These values are minimum and maximum animation duration per event origin,
-  // and a global ratio which defines how longer is the animation's duration
-  // compared to the average recent events intervals (such that for a relatively
-  // consistent events rate, the next event arrives before current animation ends)
-  int32_t mOriginMinMS;
-  int32_t mOriginMaxMS;
-  double mIntervalRatio;
-  nsPoint mDestination;
-  bool mIsFirstIteration;
-
 protected:
   double ProgressAt(TimeStamp aTime) const {
     return clamped((aTime - mStartTime) / mDuration, 0.0, 1.0);
   }
 
   nscoord VelocityComponent(double aTimeProgress,
                             const nsSMILKeySpline& aTimingFunction,
                             nscoord aStart, nscoord aDestination) const;
@@ -67,29 +64,37 @@ protected:
   TimeDuration ComputeDuration(TimeStamp aTime);
 
   // Initializes the timing function in such a way that the current velocity is
   // preserved.
   void InitTimingFunction(nsSMILKeySpline& aTimingFunction,
                           nscoord aCurrentPos, nscoord aCurrentVelocity,
                           nscoord aDestination);
 
+  // Initialize event history.
+  void InitializeHistory(TimeStamp aTime);
+
+  // Cached Preferences values.
+  ScrollAnimationPhysicsSettings mSettings;
+
   // mPrevEventTime holds previous 3 timestamps for intervals averaging (to
   // reduce duration fluctuations). When AsyncScroll is constructed and no
   // previous timestamps are available (indicated with mIsFirstIteration),
   // initialize mPrevEventTime using imaginary previous timestamps with maximum
   // relevant intervals between them.
   TimeStamp mPrevEventTime[3];
 
   TimeStamp mStartTime;
 
   nsPoint mStartPos;
+  nsPoint mDestination;
   TimeDuration mDuration;
   nsSMILKeySpline mTimingFunctionX;
   nsSMILKeySpline mTimingFunctionY;
+  bool mIsFirstIteration;
 };
 
 // Helper for accelerated wheel deltas. This can be called from the main thread
 // or the APZ Controller thread.
 static inline double
 ComputeAcceleratedWheelDelta(double aDelta, int32_t aCounter, int32_t aFactor)
 {
   if (!aDelta) {
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -1800,52 +1800,69 @@ private:
 // AsyncScroll has ref counting.
 class ScrollFrameHelper::AsyncScroll final
   : public nsARefreshObserver
 {
 public:
   typedef mozilla::TimeStamp TimeStamp;
   typedef mozilla::TimeDuration TimeDuration;
 
-  explicit AsyncScroll(nsPoint aStartPos)
-    : mAnimationPhysics(aStartPos)
-    , mCallee(nullptr)
+  explicit AsyncScroll()
+    : mCallee(nullptr)
   {
     Telemetry::SetHistogramRecordingEnabled(
       Telemetry::FX_REFRESH_DRIVER_SYNC_SCROLL_FRAME_DELAY_MS, true);
   }
 
 private:
   // Private destructor, to discourage deletion outside of Release():
   ~AsyncScroll() {
     RemoveObserver();
     Telemetry::SetHistogramRecordingEnabled(
       Telemetry::FX_REFRESH_DRIVER_SYNC_SCROLL_FRAME_DELAY_MS, false);
   }
 
 public:
-  void InitSmoothScroll(TimeStamp aTime, nsPoint aDestination,
+  void InitSmoothScroll(TimeStamp aTime,
+                        nsPoint aInitialPosition, nsPoint aDestination,
                         nsIAtom *aOrigin, const nsRect& aRange,
                         const nsSize& aCurrentVelocity);
   void Init(const nsRect& aRange) {
+    mAnimationPhysics = nullptr;
     mRange = aRange;
   }
 
-  ScrollAnimationPhysics mAnimationPhysics;
+  bool IsSmoothScroll() { return mAnimationPhysics != nullptr; }
+
+  bool IsFinished(const TimeStamp& aTime) const {
+    MOZ_RELEASE_ASSERT(mAnimationPhysics);
+    return mAnimationPhysics->IsFinished(aTime);
+  }
+
+  nsPoint PositionAt(const TimeStamp& aTime) const {
+    MOZ_RELEASE_ASSERT(mAnimationPhysics);
+    return mAnimationPhysics->PositionAt(aTime);
+  }
+
+  nsSize VelocityAt(const TimeStamp& aTime) const {
+    MOZ_RELEASE_ASSERT(mAnimationPhysics);
+    return mAnimationPhysics->VelocityAt(aTime);
+  }
 
   // Most recent scroll origin.
   RefPtr<nsIAtom> mOrigin;
 
   // Allowed destination positions around mDestination
   nsRect mRange;
-  bool mIsSmoothScroll;
 
 private:
   void InitPreferences(TimeStamp aTime, nsIAtom *aOrigin);
 
+  UniquePtr<ScrollAnimationPhysics> mAnimationPhysics;
+
 // The next section is observer/callback management
 // Bodies of WillRefresh and RefreshDriver contain ScrollFrameHelper specific code.
 public:
   NS_INLINE_DECL_REFCOUNTING(AsyncScroll, override)
 
   /*
    * Set a refresh observer for smooth scroll iterations (and start observing).
    * Should be used at most once during the lifetime of this object.
@@ -1888,84 +1905,84 @@ private:
     }
   }
 };
 
 /*
  * Calculate duration, possibly dynamically according to events rate and event origin.
  * (also maintain previous timestamps - which are only used here).
  */
-void
-ScrollFrameHelper::AsyncScroll::InitPreferences(TimeStamp aTime, nsIAtom *aOrigin)
-{
-  if (!aOrigin || aOrigin == nsGkAtoms::restore) {
-    // We don't have special prefs for "restore", just treat it as "other".
-    // "restore" scrolls are (for now) always instant anyway so unless something
-    // changes we should never have aOrigin == nsGkAtoms::restore here.
-    aOrigin = nsGkAtoms::other;
-  }
-  // Likewise we should never get APZ-triggered scrolls here, and if that changes
-  // something is likely broken somewhere.
-  MOZ_ASSERT(aOrigin != nsGkAtoms::apz);
-
-  // Read preferences only on first iteration or for a different event origin.
-  if (!mAnimationPhysics.mIsFirstIteration && aOrigin == mOrigin) {
-    return;
-  }
-
-  mOrigin = aOrigin;
-  mAnimationPhysics.mOriginMinMS = mAnimationPhysics.mOriginMaxMS = 0;
+static ScrollAnimationPhysicsSettings
+ComputeAnimationSettingsForOrigin(nsIAtom *aOrigin)
+{
+  int32_t minMS = 0;
+  int32_t maxMS = 0;
   bool isOriginSmoothnessEnabled = false;
-  mAnimationPhysics.mIntervalRatio = 1;
+  double intervalRatio = 1;
 
   // Default values for all preferences are defined in all.js
   static const int32_t kDefaultMinMS = 150, kDefaultMaxMS = 150;
   static const bool kDefaultIsSmoothEnabled = true;
 
   nsAutoCString originName;
   aOrigin->ToUTF8String(originName);
   nsAutoCString prefBase = NS_LITERAL_CSTRING("general.smoothScroll.") + originName;
 
   isOriginSmoothnessEnabled = Preferences::GetBool(prefBase.get(), kDefaultIsSmoothEnabled);
   if (isOriginSmoothnessEnabled) {
     nsAutoCString prefMin = prefBase + NS_LITERAL_CSTRING(".durationMinMS");
     nsAutoCString prefMax = prefBase + NS_LITERAL_CSTRING(".durationMaxMS");
-    mAnimationPhysics.mOriginMinMS = Preferences::GetInt(prefMin.get(), kDefaultMinMS);
-    mAnimationPhysics.mOriginMaxMS = Preferences::GetInt(prefMax.get(), kDefaultMaxMS);
+    minMS = Preferences::GetInt(prefMin.get(), kDefaultMinMS);
+    maxMS = Preferences::GetInt(prefMax.get(), kDefaultMaxMS);
 
     static const int32_t kSmoothScrollMaxAllowedAnimationDurationMS = 10000;
-    mAnimationPhysics.mOriginMaxMS = clamped(mAnimationPhysics.mOriginMaxMS, 0, kSmoothScrollMaxAllowedAnimationDurationMS);
-    mAnimationPhysics.mOriginMinMS = clamped(mAnimationPhysics.mOriginMinMS, 0, mAnimationPhysics.mOriginMaxMS);
+    maxMS = clamped(maxMS, 0, kSmoothScrollMaxAllowedAnimationDurationMS);
+    minMS = clamped(minMS, 0, maxMS);
   }
 
   // Keep the animation duration longer than the average event intervals
   //   (to "connect" consecutive scroll animations before the scroll comes to a stop).
   static const double kDefaultDurationToIntervalRatio = 2; // Duplicated at all.js
-  mAnimationPhysics.mIntervalRatio = Preferences::GetInt("general.smoothScroll.durationToIntervalRatio",
+  intervalRatio = Preferences::GetInt("general.smoothScroll.durationToIntervalRatio",
                                                       kDefaultDurationToIntervalRatio * 100) / 100.0;
 
   // Duration should be at least as long as the intervals -> ratio is at least 1
-  mAnimationPhysics.mIntervalRatio = std::max(1.0, mAnimationPhysics.mIntervalRatio);
-
-  if (mAnimationPhysics.mIsFirstIteration) {
-    mAnimationPhysics.InitializeHistory(aTime);
-  }
+  intervalRatio = std::max(1.0, intervalRatio);
+
+  return ScrollAnimationPhysicsSettings { minMS, maxMS, intervalRatio };
 }
 
 void
 ScrollFrameHelper::AsyncScroll::InitSmoothScroll(TimeStamp aTime,
+                                                 nsPoint aInitialPosition,
                                                  nsPoint aDestination,
                                                  nsIAtom *aOrigin,
                                                  const nsRect& aRange,
                                                  const nsSize& aCurrentVelocity)
 {
-  InitPreferences(aTime, aOrigin);
+  if (!aOrigin || aOrigin == nsGkAtoms::restore) {
+    // We don't have special prefs for "restore", just treat it as "other".
+    // "restore" scrolls are (for now) always instant anyway so unless something
+    // changes we should never have aOrigin == nsGkAtoms::restore here.
+    aOrigin = nsGkAtoms::other;
+  }
+  // Likewise we should never get APZ-triggered scrolls here, and if that changes
+  // something is likely broken somewhere.
+  MOZ_ASSERT(aOrigin != nsGkAtoms::apz);
+
+  // Read preferences only on first iteration or for a different event origin.
+  if (!mAnimationPhysics || aOrigin != mOrigin) {
+    mOrigin = aOrigin;
+    ScrollAnimationPhysicsSettings settings = ComputeAnimationSettingsForOrigin(mOrigin);
+    mAnimationPhysics = MakeUnique<ScrollAnimationPhysics>(aInitialPosition, settings);
+  }
+
   mRange = aRange;
 
-  mAnimationPhysics.Update(aTime, aDestination, aCurrentVelocity);
+  mAnimationPhysics->Update(aTime, aDestination, aCurrentVelocity);
 }
 
 bool
 ScrollFrameHelper::IsSmoothScrollingEnabled()
 {
   return Preferences::GetBool(SMOOTH_SCROLL_PREF_NAME, false);
 }
 
@@ -2123,19 +2140,19 @@ ScrollFrameHelper::AsyncScrollCallback(S
   MOZ_ASSERT(aInstance->mAsyncScroll,
     "Did not expect AsyncScrollCallback without an active async scroll.");
 
   if (!aInstance || !aInstance->mAsyncScroll) {
     return;  // XXX wallpaper bug 1107353 for now.
   }
 
   nsRect range = aInstance->mAsyncScroll->mRange;
-  if (aInstance->mAsyncScroll->mIsSmoothScroll) {
-    if (!aInstance->mAsyncScroll->mAnimationPhysics.IsFinished(aTime)) {
-      nsPoint destination = aInstance->mAsyncScroll->mAnimationPhysics.PositionAt(aTime);
+  if (aInstance->mAsyncScroll->IsSmoothScroll()) {
+    if (!aInstance->mAsyncScroll->IsFinished(aTime)) {
+      nsPoint destination = aInstance->mAsyncScroll->PositionAt(aTime);
       // Allow this scroll operation to land on any pixel boundary between the
       // current position and the final allowed range.  (We don't want
       // intermediate steps to be more constrained than the final step!)
       nsRect intermediateRange =
         nsRect(aInstance->GetScrollPosition(), nsSize()).UnionEdges(range);
       aInstance->ScrollToImpl(destination, intermediateRange);
       // 'aInstance' might be destroyed here
       return;
@@ -2287,18 +2304,18 @@ ScrollFrameHelper::ScrollToWithOrigin(ns
   if (gfxPrefs::ScrollBehaviorEnabled()) {
     if (aMode == nsIScrollableFrame::SMOOTH_MSD) {
       mIgnoreMomentumScroll = true;
       if (!mAsyncSmoothMSDScroll) {
         nsPoint sv = mVelocityQueue.GetVelocity();
         currentVelocity.width = sv.x;
         currentVelocity.height = sv.y;
         if (mAsyncScroll) {
-          if (mAsyncScroll->mIsSmoothScroll) {
-            currentVelocity = mAsyncScroll->mAnimationPhysics.VelocityAt(now);
+          if (mAsyncScroll->IsSmoothScroll()) {
+            currentVelocity = mAsyncScroll->VelocityAt(now);
           }
           mAsyncScroll = nullptr;
         }
 
         if (nsLayoutUtils::AsyncPanZoomEnabled(mOuter) && WantAsyncScroll()) {
           if (mApzSmoothScrollDestination == Some(mDestination) &&
               mScrollGeneration == sScrollGenerationCounter) {
             // If we already sent APZ a smooth-scroll request to this
@@ -2368,28 +2385,27 @@ ScrollFrameHelper::ScrollToWithOrigin(ns
       if (mAsyncSmoothMSDScroll) {
         currentVelocity = mAsyncSmoothMSDScroll->GetVelocity();
         mAsyncSmoothMSDScroll = nullptr;
       }
     }
   }
 
   if (!mAsyncScroll) {
-    mAsyncScroll = new AsyncScroll(GetScrollPosition());
+    mAsyncScroll = new AsyncScroll();
     if (!mAsyncScroll->SetRefreshObserver(this)) {
       // Observer setup failed. Scroll the normal way.
       CompleteAsyncScroll(range, aOrigin);
       return;
     }
   }
 
-  mAsyncScroll->mIsSmoothScroll = isSmoothScroll;
-
   if (isSmoothScroll) {
-    mAsyncScroll->InitSmoothScroll(now, mDestination, aOrigin, range, currentVelocity);
+    mAsyncScroll->InitSmoothScroll(now, GetScrollPosition(), mDestination,
+                                   aOrigin, range, currentVelocity);
   } else {
     mAsyncScroll->Init(range);
   }
 }
 
 // We can't use nsContainerFrame::PositionChildViews here because
 // we don't want to invalidate views that have moved.
 static void AdjustViews(nsIFrame* aFrame)