Bug 965871 - Implement overscroll handoff for flings. r=kats

This commit is contained in:
Botond Ballo 2014-03-10 20:04:44 -04:00
parent a631992b46
commit daffded693
6 changed files with 228 additions and 33 deletions

View File

@ -403,7 +403,7 @@ APZCTreeManager::ReceiveInputEvent(const InputData& aEvent,
// then null it out so we don't keep a dangling reference and leak things.
if (mTouchCount == 0) {
mApzcForInputBlock = nullptr;
mOverscrollHandoffChain.clear();
ClearOverscrollHandoffChain();
}
}
break;
@ -517,7 +517,7 @@ APZCTreeManager::ProcessTouchEvent(WidgetTouchEvent& aEvent,
}
if (mTouchCount == 0) {
mApzcForInputBlock = nullptr;
mOverscrollHandoffChain.clear();
ClearOverscrollHandoffChain();
}
}
return ret;
@ -677,18 +677,58 @@ APZCTreeManager::ClearTree()
mRootApzc = nullptr;
}
/**
* Transform a displacement from the screen coordinates of a source APZC to
* the screen coordinates of a target APZC.
* @param aTreeManager the tree manager for the APZC tree containing |aSource|
* and |aTarget|
* @param aSource the source APZC
* @param aTarget the target APZC
* @param aStartPoint the start point of the displacement
* @param aEndPoint the end point of the displacement
*/
static void
TransformDisplacement(APZCTreeManager* aTreeManager,
AsyncPanZoomController* aSource,
AsyncPanZoomController* aTarget,
ScreenPoint& aStartPoint,
ScreenPoint& aEndPoint) {
gfx3DMatrix transformToApzc;
gfx3DMatrix transformToGecko; // ignored
// Convert start and end points to untransformed screen coordinates.
aTreeManager->GetInputTransforms(aSource, transformToApzc, transformToGecko);
ApplyTransform(&aStartPoint, transformToApzc.Inverse());
ApplyTransform(&aEndPoint, transformToApzc.Inverse());
// Convert start and end points to aTarget's transformed screen coordinates.
aTreeManager->GetInputTransforms(aTarget, transformToApzc, transformToGecko);
ApplyTransform(&aStartPoint, transformToApzc);
ApplyTransform(&aEndPoint, transformToApzc);
}
void
APZCTreeManager::DispatchScroll(AsyncPanZoomController* aPrev, ScreenPoint aStartPoint, ScreenPoint aEndPoint,
uint32_t aOverscrollHandoffChainIndex)
{
// If we have reached the end of the overscroll handoff chain, there is
// nothing more to scroll, so we ignore the rest of the pan gesture.
if (aOverscrollHandoffChainIndex >= mOverscrollHandoffChain.length()) {
// Nothing more to scroll - ignore the rest of the pan gesture.
return;
nsRefPtr<AsyncPanZoomController> next;
{
// Grab tree lock to protect mOverscrollHandoffChain from concurrent
// access from the input and compositor threads.
// Release it before calling TransformDisplacement() as that grabs the
// lock itself.
MonitorAutoLock lock(mTreeLock);
// If we have reached the end of the overscroll handoff chain, there is
// nothing more to scroll, so we ignore the rest of the pan gesture.
if (aOverscrollHandoffChainIndex >= mOverscrollHandoffChain.length()) {
// Nothing more to scroll - ignore the rest of the pan gesture.
return;
}
next = mOverscrollHandoffChain[aOverscrollHandoffChainIndex];
}
nsRefPtr<AsyncPanZoomController> next = mOverscrollHandoffChain[aOverscrollHandoffChainIndex];
if (next == nullptr)
return;
@ -698,18 +738,7 @@ APZCTreeManager::DispatchScroll(AsyncPanZoomController* aPrev, ScreenPoint aStar
// scroll grabbing to grab the scroll from it), don't bother doing the
// transformations in that case.
if (next != aPrev) {
gfx3DMatrix transformToApzc;
gfx3DMatrix transformToGecko; // ignored
// Convert start and end points to untransformed screen coordinates.
GetInputTransforms(aPrev, transformToApzc, transformToGecko);
ApplyTransform(&aStartPoint, transformToApzc.Inverse());
ApplyTransform(&aEndPoint, transformToApzc.Inverse());
// Convert start and end points to next's transformed screen coordinates.
GetInputTransforms(next, transformToApzc, transformToGecko);
ApplyTransform(&aStartPoint, transformToApzc);
ApplyTransform(&aEndPoint, transformToApzc);
TransformDisplacement(this, aPrev, next, aStartPoint, aEndPoint);
}
// Scroll |next|. If this causes overscroll, it will call DispatchScroll()
@ -717,9 +746,69 @@ APZCTreeManager::DispatchScroll(AsyncPanZoomController* aPrev, ScreenPoint aStar
next->AttemptScroll(aStartPoint, aEndPoint, aOverscrollHandoffChainIndex);
}
void
APZCTreeManager::HandOffFling(AsyncPanZoomController* aPrev, ScreenPoint aVelocity)
{
// Build the overscroll handoff chain. This is necessary because it is
// otherwise built on touch-start and cleared on touch-end, and a fling
// happens after touch-end. Note that, unlike DispatchScroll() which is
// called on every touch-move during overscroll panning,
// HandleFlingOverscroll() is only called once during a fling handoff,
// so it's not worth trying to avoid building the handoff chain here.
BuildOverscrollHandoffChain(aPrev);
nsRefPtr<AsyncPanZoomController> next; // will be used outside monitor block
{
// Grab tree lock to protect mOverscrollHandoffChain from concurrent
// access from the input and compositor threads.
// Release it before calling GetInputTransforms() as that grabs the
// lock itself.
MonitorAutoLock lock(mTreeLock);
// Find |aPrev| in the handoff chain.
uint32_t i;
for (i = 0; i < mOverscrollHandoffChain.length(); ++i) {
if (mOverscrollHandoffChain[i] == aPrev) {
break;
}
}
// Get the next APZC in the handoff chain, if any.
if (i + 1 < mOverscrollHandoffChain.length()) {
next = mOverscrollHandoffChain[i + 1];
}
// Clear the handoff chain so we don't maintain references to APZCs
// unnecessarily.
mOverscrollHandoffChain.clear();
}
// Nothing to hand off fling to.
if (next == nullptr) {
return;
}
// The fling's velocity needs to be transformed from the screen coordinates
// of |aPrev| to the screen coordinates of |next|. To transform a velocity
// correctly, we need to convert it to a displacement. For now, we do this
// by anchoring it to a start point of (0, 0).
// TODO: For this to be correct in the presence of 3D transforms, we should
// use the end point of the touch that started the fling as the start point
// rather than (0, 0).
ScreenPoint startPoint; // (0, 0)
ScreenPoint endPoint = startPoint + aVelocity;
TransformDisplacement(this, aPrev, next, startPoint, endPoint);
ScreenPoint transformedVelocity = endPoint - startPoint;
// Tell |next| to start a fling with the transformed velocity.
next->TakeOverFling(transformedVelocity);
}
bool
APZCTreeManager::FlushRepaintsForOverscrollHandoffChain()
{
MonitorAutoLock lock(mTreeLock); // to access mOverscrollHandoffChain
if (mOverscrollHandoffChain.length() == 0) {
return false;
}
@ -799,6 +888,10 @@ APZCTreeManager::BuildOverscrollHandoffChain(const nsRefPtr<AsyncPanZoomControll
// handoff order can be different, so we build a chain of APZCs in the
// order in which scroll will be handed off to them.
// Grab tree lock to protect mOverscrollHandoffChain from concurrent
// access between the input and compositor threads.
MonitorAutoLock lock(mTreeLock);
mOverscrollHandoffChain.clear();
// Start with the child -> parent chain.
@ -822,6 +915,13 @@ APZCTreeManager::BuildOverscrollHandoffChain(const nsRefPtr<AsyncPanZoomControll
CompareByScrollPriority());
}
void
APZCTreeManager::ClearOverscrollHandoffChain()
{
MonitorAutoLock lock(mTreeLock);
mOverscrollHandoffChain.clear();
}
AsyncPanZoomController*
APZCTreeManager::FindTargetAPZC(AsyncPanZoomController* aApzc, const ScrollableLayerGuid& aGuid)
{

View File

@ -255,10 +255,22 @@ public:
* - TM.DispatchScroll() calls A.AttemptScroll() (since A is at index 2 in the chain)
* - A.AttemptScroll() scrolls A. If there is overscroll, it calls TM.DispatchScroll() with index = 3.
* - TM.DispatchScroll() discards the rest of the scroll as there are no more elements in the chain.
*
* Note: this should be used for panning only. For handing off overscroll for
* a fling, use HandOffFling().
*/
void DispatchScroll(AsyncPanZoomController* aAPZC, ScreenPoint aStartPoint, ScreenPoint aEndPoint,
uint32_t aOverscrollHandoffChainIndex);
/**
* This is a callback for AsyncPanZoomController to call when it wants to
* hand off overscroll from a fling.
* @param aApzc the APZC that is handing off the fling
* @param aVelocity the current velocity of the fling, in |aApzc|'s screen
* pixels per millisecond
*/
void HandOffFling(AsyncPanZoomController* aApzc, ScreenPoint aVelocity);
bool FlushRepaintsForOverscrollHandoffChain();
protected:
@ -295,6 +307,7 @@ private:
nsEventStatus ProcessEvent(WidgetInputEvent& inputEvent, ScrollableLayerGuid* aOutTargetGuid);
void UpdateZoomConstraintsRecursively(AsyncPanZoomController* aApzc,
const ZoomConstraints& aConstraints);
void ClearOverscrollHandoffChain();
/**
* Recursive helper function to build the APZC tree. The tree of APZC instances has
@ -320,6 +333,7 @@ private:
* isolation (that is, if its tree pointers are not being accessed or mutated). The
* lock also needs to be held when accessing the mRootApzc instance variable, as that
* is considered part of the APZC tree management state.
* Finally, the lock needs to be held when accessing mOverscrollHandoffChain.
* IMPORTANT: See the note about lock ordering at the top of this file. */
mozilla::Monitor mTreeLock;
nsRefPtr<AsyncPanZoomController> mRootApzc;

View File

@ -346,10 +346,9 @@ GetFrameTime() {
class FlingAnimation: public AsyncPanZoomAnimation {
public:
FlingAnimation(AxisX& aX, AxisY& aY)
FlingAnimation(AsyncPanZoomController& aApzc)
: AsyncPanZoomAnimation(TimeDuration::FromMilliseconds(gFlingRepaintInterval))
, mX(aX)
, mY(aY)
, mApzc(aApzc)
{}
/**
* Advances a fling by an interpolated amount based on the passed in |aDelta|.
@ -361,8 +360,7 @@ public:
const TimeDuration& aDelta);
private:
AxisX& mX;
AxisY& mY;
AsyncPanZoomController& mApzc;
};
class ZoomAnimation: public AsyncPanZoomAnimation {
@ -772,7 +770,7 @@ nsEventStatus AsyncPanZoomController::OnTouchEnd(const MultiTouchInput& aEvent)
mX.EndTouch();
mY.EndTouch();
SetState(FLING);
StartAnimation(new FlingAnimation(mX, mY));
StartAnimation(new FlingAnimation(*this));
return nsEventStatus_eConsumeNoDefault;
case PINCHING:
@ -1189,6 +1187,15 @@ void AsyncPanZoomController::AttemptScroll(const ScreenPoint& aStartPoint,
}
}
void AsyncPanZoomController::TakeOverFling(ScreenPoint aVelocity) {
// We may have a pre-existing velocity for whatever reason (for example,
// a previously handed off fling). We don't want to clobber that.
mX.SetVelocity(mX.GetVelocity() + aVelocity.x);
mY.SetVelocity(mY.GetVelocity() + aVelocity.y);
SetState(FLING);
StartAnimation(new FlingAnimation(*this));
}
void AsyncPanZoomController::CallDispatchScroll(const ScreenPoint& aStartPoint, const ScreenPoint& aEndPoint,
uint32_t aOverscrollHandoffChainIndex) {
// Make a local copy of the tree manager pointer and check if it's not
@ -1248,25 +1255,77 @@ ScreenIntPoint& AsyncPanZoomController::GetFirstTouchScreenPoint(const MultiTouc
bool FlingAnimation::Sample(FrameMetrics& aFrameMetrics,
const TimeDuration& aDelta) {
bool shouldContinueFlingX = mX.FlingApplyFrictionOrCancel(aDelta),
shouldContinueFlingY = mY.FlingApplyFrictionOrCancel(aDelta);
// If the fling is handed off to our APZC from a child, on the first call to
// Sample() aDelta might be negative because it's computed as the sample time
// from SampleContentTransformForFrame() minus our APZC's mLastSampleTime
// which is the time the child handed off the fling from its call to
// SampleContentTransformForFrame() with the same sample time. If we allow
// the negative aDelta to be processed, it will yield a displacement in the
// direction opposite to the fling, which can cause us to overscroll and
// hand off the fling to _our_ parent, which effectively kills the fling.
if (aDelta.ToMilliseconds() <= 0) {
return true;
}
bool shouldContinueFlingX = mApzc.mX.FlingApplyFrictionOrCancel(aDelta),
shouldContinueFlingY = mApzc.mY.FlingApplyFrictionOrCancel(aDelta);
// If we shouldn't continue the fling, let's just stop and repaint.
if (!shouldContinueFlingX && !shouldContinueFlingY) {
return false;
}
CSSPoint overscroll; // overscroll is ignored for flings
ScreenPoint offset(aDelta.ToMilliseconds() * mX.GetVelocity(),
aDelta.ToMilliseconds() * mY.GetVelocity());
// AdjustDisplacement() zeroes out the Axis velocity if we're in overscroll.
// Since we need to hand off the velocity to the tree manager in such a case,
// we save it here. Would be ScreenVector instead of ScreenPoint if we had
// vector classes.
ScreenPoint velocity(mApzc.mX.GetVelocity(), mApzc.mY.GetVelocity());
ScreenPoint offset = velocity * aDelta.ToMilliseconds();
// Inversely scale the offset by the resolution (when you're zoomed further in,
// the same swipe should move you a shorter distance).
CSSPoint cssOffset = offset / aFrameMetrics.mZoom;
CSSPoint overscroll;
aFrameMetrics.mScrollOffset += CSSPoint(
mX.AdjustDisplacement(cssOffset.x, overscroll.x),
mY.AdjustDisplacement(cssOffset.y, overscroll.y)
mApzc.mX.AdjustDisplacement(cssOffset.x, overscroll.x),
mApzc.mY.AdjustDisplacement(cssOffset.y, overscroll.y)
);
// If the fling has caused us to reach the end of our scroll range, hand
// off the fling to the next APZC in the overscroll handoff chain.
if (!IsZero(overscroll)) {
// 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 (FuzzyEqualsMultiplicative(overscroll.x, 0.0f)) {
velocity.x = 0;
} else if (FuzzyEqualsMultiplicative(overscroll.y, 0.0f)) {
velocity.y = 0;
}
// To hand off the fling, we call APZCTreeManager::HandleFlingOverscroll()
// which starts a new fling in the next APZC in the handoff chain with
// the same velocity. 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.
// Make a local copy of the tree manager pointer and check if it's not
// null before calling HandleFlingOverscroll(). This is necessary because
// Destroy(), which nulls out mTreeManager, could be called concurrently.
APZCTreeManager* treeManagerLocal = mApzc.mTreeManager;
if (treeManagerLocal) {
// APZC is holding mMonitor, so directly calling HandleFlingOverscroll()
// (which acquires the tree lock) would violate the lock ordering. Instead
// we schedule HandleFlingOverscroll() to be called after mMonitor is
// released.
mDeferredTasks.append(NewRunnableMethod(treeManagerLocal,
&APZCTreeManager::HandOffFling,
&mApzc,
velocity));
}
}
return true;
}

View File

@ -40,6 +40,7 @@ class PCompositorParent;
class ViewTransform;
class APZCTreeManager;
class AsyncPanZoomAnimation;
class FlingAnimation;
/**
* Controller for all panning and zooming logic. Any time a user input is
@ -301,6 +302,12 @@ public:
void AttemptScroll(const ScreenPoint& aStartPoint, const ScreenPoint& aEndPoint,
uint32_t aOverscrollHandoffChainIndex = 0);
/**
* Take over a fling with the given velocity from another APZC. Used for
* during overscroll handoff for a fling.
*/
void TakeOverFling(ScreenPoint aVelocity);
/**
* Returns allowed touch behavior for the given point on the scrollable layer.
* Internally performs a kind of hit testing based on the regions constructed
@ -748,6 +755,7 @@ private:
RefPtr<AsyncPanZoomAnimation> mAnimation;
friend class Axis;
friend class FlingAnimation;
/* The functions and members in this section are used to build a tree
* structure out of APZC instances. This tree can only be walked or

View File

@ -254,6 +254,10 @@ float Axis::GetVelocity() {
return mAxisLocked ? 0 : mVelocity;
}
void Axis::SetVelocity(float aVelocity) {
mVelocity = aVelocity;
}
float Axis::GetCompositionEnd() {
return GetOrigin() + GetCompositionLength();
}

View File

@ -128,6 +128,16 @@ public:
*/
float GetVelocity();
/**
* Sets the raw velocity of this axis at this moment.
* Intended to be called only when the axis "takes over" a velocity from
* another APZC, in which case there are no touch points available to call
* UpdateWithTouchAtDevicePoint. In other circumstances,
* UpdateWithTouchAtDevicePoint should be used and the velocity calculated
* there.
*/
void SetVelocity(float aVelocity);
/**
* Gets the overscroll state of the axis given an additional displacement.
* That is to say, if the given displacement is applied, this will tell you