Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at */
#ifndef mozilla_layers_GenericFlingAnimation_h_
#define mozilla_layers_GenericFlingAnimation_h_
#include "APZUtils.h"
#include "AsyncPanZoomAnimation.h"
#include "AsyncPanZoomController.h"
#include "FrameMetrics.h"
#include "Units.h"
#include "OverscrollHandoffState.h"
#include "mozilla/Assertions.h"
#include "mozilla/Monitor.h"
#include "mozilla/RefPtr.h"
#include "mozilla/StaticPrefs_apz.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/ToString.h"
#include "nsThreadUtils.h"
static mozilla::LazyLogModule sApzFlgLog("apz.fling");
#define FLING_LOG(...) MOZ_LOG(sApzFlgLog, LogLevel::Debug, (__VA_ARGS__))
namespace mozilla {
namespace layers {
* The FlingPhysics template parameter determines the physics model
* that the fling animation follows. It must have the following methods:
* - Default constructor.
* - Init(const ParentLayerPoint& aStartingVelocity, float aPLPPI).
* Called at the beginning of the fling, with the fling's starting velocity,
* and the number of ParentLayer pixels per (Screen) inch at the point of
* the fling's start in the fling's direction.
* - Sample(const TimeDuration& aDelta,
* ParentLayerPoint* aOutVelocity,
* ParentLayerPoint* aOutOffset);
* Called on each sample of the fling.
* |aDelta| is the time elapsed since the last sample.
* |aOutVelocity| should be the desired velocity after the current sample,
* in ParentLayer pixels per millisecond.
* |aOutOffset| should be the desired _delta_ to the scroll offset after
* the current sample. |aOutOffset| should _not_ be clamped to the APZC's
* scrollable bounds; the caller will do the clamping, and it needs to
* know the unclamped value to handle handoff/overscroll correctly.
template <typename FlingPhysics>
class GenericFlingAnimation : public AsyncPanZoomAnimation,
public FlingPhysics {
GenericFlingAnimation(AsyncPanZoomController& aApzc,
const FlingHandoffState& aHandoffState, float aPLPPI)
: mApzc(aApzc),
mScrolledApzc(aHandoffState.mScrolledApzc) {
// Drop any velocity on axes where we don't have room to scroll anyways
// (in this APZC, or an APZC further in the handoff chain).
// This ensures that we don't take the 'overscroll' path in Sample()
// on account of one axis which can't scroll having a velocity.
if (!mOverscrollHandoffChain->CanScrollInDirection(
&mApzc, ScrollDirection::eHorizontal)) {
RecursiveMutexAutoLock lock(mApzc.mRecursiveMutex);
if (!mOverscrollHandoffChain->CanScrollInDirection(
&mApzc, ScrollDirection::eVertical)) {
RecursiveMutexAutoLock lock(mApzc.mRecursiveMutex);
if (aHandoffState.mIsHandoff) {
// Only apply acceleration in the APZC that originated the fling, not in
// APZCs further down the handoff chain during handoff.
ParentLayerPoint velocity =
aApzc.GetFrameTime(), mApzc.GetVelocityVector(), aHandoffState);
FlingPhysics::Init(mApzc.GetVelocityVector(), aPLPPI);
* Advances a fling by an interpolated amount based on the passed in |aDelta|.
* 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.
virtual bool DoSample(FrameMetrics& aFrameMetrics,
const TimeDuration& aDelta) override {
CSSToParentLayerScale zoom(aFrameMetrics.GetZoom());
if (zoom == CSSToParentLayerScale(0)) {
return false;
ParentLayerPoint velocity;
ParentLayerPoint offset;
FlingPhysics::Sample(aDelta, &velocity, &offset);
// If we shouldn't continue the fling, let's just stop and repaint.
if (IsZero(velocity / zoom)) {
FLING_LOG("%p ending fling animation. overscrolled=%d\n", &mApzc,
// This APZC or an APZC further down the handoff chain may be be
// overscrolled. Start a snap-back animation on the overscrolled APZC.
// Note:
// This needs to be a deferred task even though it can safely run
// while holding mRecursiveMutex, because otherwise, if the overscrolled
// APZC is this one, then the SetState(NOTHING) in UpdateAnimation will
// stomp on the SetState(SNAP_BACK) it does.
&OverscrollHandoffChain::SnapBackOverscrolledApzc, &mApzc));
return false;
// Ordinarily we might need to do a ScheduleComposite if either of
// the following AdjustDisplacement calls returns true, but this
// is already running as part of a FlingAnimation, so we'll be compositing
// per frame of animation anyway.
ParentLayerPoint overscroll;
ParentLayerPoint adjustedOffset;
mApzc.mX.AdjustDisplacement(offset.x, adjustedOffset.x, overscroll.x);
mApzc.mY.AdjustDisplacement(offset.y, adjustedOffset.y, overscroll.y);
if (aFrameMetrics.GetZoom() != CSSToParentLayerScale(0)) {
mApzc.ScrollBy(adjustedOffset / aFrameMetrics.GetZoom());
// The fling may have caused us to reach the end of our scroll range.
if (!IsZero(overscroll / zoom)) {
// Hand off the fling to the next APZC in the overscroll handoff chain.
// We may have reached the end of the scroll range along one axis but
// not the other. In such a case we only want to hand off the relevant
// component of the fling.
if (mApzc.IsZero(overscroll.x)) {
velocity.x = 0;
} else if (mApzc.IsZero(overscroll.y)) {
velocity.y = 0;
// To hand off the fling, we attempt to find a target APZC and start a new
// fling with the same velocity on that APZC. For simplicity, the actual
// overscroll of the current sample is discarded rather than being handed
// off. The compositor should sample animations sufficiently frequently
// that this is not noticeable. The target APZC is chosen by seeing if
// there is an APZC further in the handoff chain which is pannable; if
// there isn't, we take the new fling ourselves, entering an overscrolled
// state.
// Note: APZC is holding mRecursiveMutex, so directly calling
// HandleFlingOverscroll() (which acquires the tree lock) would violate
// the lock ordering. Instead we schedule HandleFlingOverscroll() to be
// called after mRecursiveMutex is released.
FLING_LOG("%p fling went into overscroll, handing off with velocity %s\n",
&mApzc, ToString(velocity).c_str());
NewRunnableMethod<ParentLayerPoint, SideBits,
RefPtr<const OverscrollHandoffChain>,
RefPtr<const AsyncPanZoomController>>(
"layers::AsyncPanZoomController::HandleFlingOverscroll", &mApzc,
&AsyncPanZoomController::HandleFlingOverscroll, velocity,
apz::GetOverscrollSideBits(overscroll), mOverscrollHandoffChain,
// If there is a remaining velocity on this APZC, continue this fling
// as well. (This fling and the handed-off fling will run concurrently.)
// Note that AdjustDisplacement() will have zeroed out the velocity
// along the axes where we're overscrolled.
return !IsZero(mApzc.GetVelocityVector() / zoom);
return true;
void Cancel(CancelAnimationFlags aFlags) override {
virtual bool HandleScrollOffsetUpdate(
const Maybe<CSSPoint>& aRelativeDelta) override {
return true;
AsyncPanZoomController& mApzc;
RefPtr<const OverscrollHandoffChain> mOverscrollHandoffChain;
RefPtr<const AsyncPanZoomController> mScrolledApzc;
} // namespace layers
} // namespace mozilla
#endif // mozilla_layers_GenericFlingAnimation_h_