1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #ifndef mozilla_layers_GenericFlingAnimation_h_
8 #define mozilla_layers_GenericFlingAnimation_h_
9 
10 #include "APZUtils.h"
11 #include "AsyncPanZoomAnimation.h"
12 #include "AsyncPanZoomController.h"
13 #include "FrameMetrics.h"
14 #include "Layers.h"
15 #include "Units.h"
16 #include "OverscrollHandoffState.h"
17 #include "mozilla/Assertions.h"
18 #include "mozilla/Monitor.h"
19 #include "mozilla/RefPtr.h"
20 #include "mozilla/StaticPrefs_apz.h"
21 #include "mozilla/TimeStamp.h"
22 #include "mozilla/ToString.h"
23 #include "nsThreadUtils.h"
24 
25 static mozilla::LazyLogModule sApzFlgLog("apz.fling");
26 #define FLING_LOG(...) MOZ_LOG(sApzFlgLog, LogLevel::Debug, (__VA_ARGS__))
27 
28 namespace mozilla {
29 namespace layers {
30 
31 /**
32  * The FlingPhysics template parameter determines the physics model
33  * that the fling animation follows. It must have the following methods:
34  *
35  *   - Default constructor.
36  *
37  *   - Init(const ParentLayerPoint& aStartingVelocity, float aPLPPI).
38  *     Called at the beginning of the fling, with the fling's starting velocity,
39  *     and the number of ParentLayer pixels per (Screen) inch at the point of
40  *     the fling's start in the fling's direction.
41  *
42  *   - Sample(const TimeDuration& aDelta,
43  *            ParentLayerPoint* aOutVelocity,
44  *            ParentLayerPoint* aOutOffset);
45  *     Called on each sample of the fling.
46  *     |aDelta| is the time elapsed since the last sample.
47  *     |aOutVelocity| should be the desired velocity after the current sample,
48  *                    in ParentLayer pixels per millisecond.
49  *     |aOutOffset| should be the desired _delta_ to the scroll offset after
50  *     the current sample. |aOutOffset| should _not_ be clamped to the APZC's
51  *     scrollable bounds; the caller will do the clamping, and it needs to
52  *     know the unclamped value to handle handoff/overscroll correctly.
53  */
54 template <typename FlingPhysics>
55 class GenericFlingAnimation : public AsyncPanZoomAnimation,
56                               public FlingPhysics {
57  public:
GenericFlingAnimation(AsyncPanZoomController & aApzc,const FlingHandoffState & aHandoffState,float aPLPPI)58   GenericFlingAnimation(AsyncPanZoomController& aApzc,
59                         const FlingHandoffState& aHandoffState, float aPLPPI)
60       : mApzc(aApzc),
61         mOverscrollHandoffChain(aHandoffState.mChain),
62         mScrolledApzc(aHandoffState.mScrolledApzc) {
63     MOZ_ASSERT(mOverscrollHandoffChain);
64 
65     // Drop any velocity on axes where we don't have room to scroll anyways
66     // (in this APZC, or an APZC further in the handoff chain).
67     // This ensures that we don't take the 'overscroll' path in Sample()
68     // on account of one axis which can't scroll having a velocity.
69     if (!mOverscrollHandoffChain->CanScrollInDirection(
70             &mApzc, ScrollDirection::eHorizontal)) {
71       RecursiveMutexAutoLock lock(mApzc.mRecursiveMutex);
72       mApzc.mX.SetVelocity(0);
73     }
74     if (!mOverscrollHandoffChain->CanScrollInDirection(
75             &mApzc, ScrollDirection::eVertical)) {
76       RecursiveMutexAutoLock lock(mApzc.mRecursiveMutex);
77       mApzc.mY.SetVelocity(0);
78     }
79 
80     if (aHandoffState.mIsHandoff) {
81       // Only apply acceleration in the APZC that originated the fling, not in
82       // APZCs further down the handoff chain during handoff.
83       mApzc.mFlingAccelerator.Reset();
84     }
85 
86     ParentLayerPoint velocity =
87         mApzc.mFlingAccelerator.GetFlingStartingVelocity(
88             aApzc.GetFrameTime(), mApzc.GetVelocityVector(), aHandoffState);
89 
90     mApzc.SetVelocityVector(velocity);
91 
92     FlingPhysics::Init(mApzc.GetVelocityVector(), aPLPPI);
93   }
94 
95   /**
96    * Advances a fling by an interpolated amount based on the passed in |aDelta|.
97    * This should be called whenever sampling the content transform for this
98    * frame. Returns true if the fling animation should be advanced by one frame,
99    * or false if there is no fling or the fling has ended.
100    */
DoSample(FrameMetrics & aFrameMetrics,const TimeDuration & aDelta)101   virtual bool DoSample(FrameMetrics& aFrameMetrics,
102                         const TimeDuration& aDelta) override {
103     ParentLayerPoint velocity;
104     ParentLayerPoint offset;
105     FlingPhysics::Sample(aDelta, &velocity, &offset);
106 
107     mApzc.SetVelocityVector(velocity);
108 
109     // If we shouldn't continue the fling, let's just stop and repaint.
110     if (IsZero(velocity)) {
111       FLING_LOG("%p ending fling animation. overscrolled=%d\n", &mApzc,
112                 mApzc.IsOverscrolled());
113       // This APZC or an APZC further down the handoff chain may be be
114       // overscrolled. Start a snap-back animation on the overscrolled APZC.
115       // Note:
116       //   This needs to be a deferred task even though it can safely run
117       //   while holding mRecursiveMutex, because otherwise, if the overscrolled
118       //   APZC is this one, then the SetState(NOTHING) in UpdateAnimation will
119       //   stomp on the SetState(SNAP_BACK) it does.
120       mDeferredTasks.AppendElement(NewRunnableMethod<AsyncPanZoomController*>(
121           "layers::OverscrollHandoffChain::SnapBackOverscrolledApzc",
122           mOverscrollHandoffChain.get(),
123           &OverscrollHandoffChain::SnapBackOverscrolledApzc, &mApzc));
124       return false;
125     }
126 
127     // Ordinarily we might need to do a ScheduleComposite if either of
128     // the following AdjustDisplacement calls returns true, but this
129     // is already running as part of a FlingAnimation, so we'll be compositing
130     // per frame of animation anyway.
131     ParentLayerPoint overscroll;
132     ParentLayerPoint adjustedOffset;
133     mApzc.mX.AdjustDisplacement(offset.x, adjustedOffset.x, overscroll.x);
134     mApzc.mY.AdjustDisplacement(offset.y, adjustedOffset.y, overscroll.y);
135     if (aFrameMetrics.GetZoom() != CSSToParentLayerScale2D(0, 0)) {
136       mApzc.ScrollBy(adjustedOffset / aFrameMetrics.GetZoom());
137     }
138 
139     // The fling may have caused us to reach the end of our scroll range.
140     if (!IsZero(overscroll)) {
141       // Hand off the fling to the next APZC in the overscroll handoff chain.
142 
143       // We may have reached the end of the scroll range along one axis but
144       // not the other. In such a case we only want to hand off the relevant
145       // component of the fling.
146       if (FuzzyEqualsAdditive(overscroll.x, 0.0f, COORDINATE_EPSILON)) {
147         velocity.x = 0;
148       } else if (FuzzyEqualsAdditive(overscroll.y, 0.0f, COORDINATE_EPSILON)) {
149         velocity.y = 0;
150       }
151 
152       // To hand off the fling, we attempt to find a target APZC and start a new
153       // fling with the same velocity on that APZC. For simplicity, the actual
154       // overscroll of the current sample is discarded rather than being handed
155       // off. The compositor should sample animations sufficiently frequently
156       // that this is not noticeable. The target APZC is chosen by seeing if
157       // there is an APZC further in the handoff chain which is pannable; if
158       // there isn't, we take the new fling ourselves, entering an overscrolled
159       // state.
160       // Note: APZC is holding mRecursiveMutex, so directly calling
161       // HandleFlingOverscroll() (which acquires the tree lock) would violate
162       // the lock ordering. Instead we schedule HandleFlingOverscroll() to be
163       // called after mRecursiveMutex is released.
164       FLING_LOG("%p fling went into overscroll, handing off with velocity %s\n",
165                 &mApzc, ToString(velocity).c_str());
166       mDeferredTasks.AppendElement(
167           NewRunnableMethod<ParentLayerPoint, SideBits,
168                             RefPtr<const OverscrollHandoffChain>,
169                             RefPtr<const AsyncPanZoomController>>(
170               "layers::AsyncPanZoomController::HandleFlingOverscroll", &mApzc,
171               &AsyncPanZoomController::HandleFlingOverscroll, velocity,
172               apz::GetOverscrollSideBits(overscroll), mOverscrollHandoffChain,
173               mScrolledApzc));
174 
175       // If there is a remaining velocity on this APZC, continue this fling
176       // as well. (This fling and the handed-off fling will run concurrently.)
177       // Note that AdjustDisplacement() will have zeroed out the velocity
178       // along the axes where we're overscrolled.
179       return !IsZero(mApzc.GetVelocityVector());
180     }
181 
182     return true;
183   }
184 
Cancel(CancelAnimationFlags aFlags)185   void Cancel(CancelAnimationFlags aFlags) override {
186     mApzc.mFlingAccelerator.ObserveFlingCanceled(mApzc.GetVelocityVector());
187   }
188 
HandleScrollOffsetUpdate(const Maybe<CSSPoint> & aRelativeDelta)189   virtual bool HandleScrollOffsetUpdate(
190       const Maybe<CSSPoint>& aRelativeDelta) override {
191     return true;
192   }
193 
194  private:
195   AsyncPanZoomController& mApzc;
196   RefPtr<const OverscrollHandoffChain> mOverscrollHandoffChain;
197   RefPtr<const AsyncPanZoomController> mScrolledApzc;
198 };
199 
200 }  // namespace layers
201 }  // namespace mozilla
202 
203 #endif  // mozilla_layers_GenericFlingAnimation_h_
204