Bug 944521 - Scroll-grabbing elements shouldn't grab tap gestures. r=Cwiiis

This commit is contained in:
Botond Ballo 2013-11-29 16:40:21 -05:00
parent f7efa63f13
commit cc2d1d28c8
5 changed files with 129 additions and 71 deletions

View File

@ -273,8 +273,11 @@ APZCTreeManager::ReceiveInputEvent(const InputData& aEvent,
if (multiTouchInput.mType == MultiTouchInput::MULTITOUCH_START) {
mTouchCount++;
mApzcForInputBlock = GetTargetAPZC(ScreenPoint(multiTouchInput.mTouches[0].mScreenPoint));
if (multiTouchInput.mTouches.Length() == 1) // pan, not pinch
mApzcForInputBlock = AdjustForScrollGrab(mApzcForInputBlock);
if (multiTouchInput.mTouches.Length() == 1) {
// If we have one touch point, this might be the start of a pan.
// Prepare for possible overscroll handoff.
BuildOverscrollHandoffChain(mApzcForInputBlock);
}
for (size_t i = 1; i < multiTouchInput.mTouches.Length(); i++) {
nsRefPtr<AsyncPanZoomController> apzc2 = GetTargetAPZC(ScreenPoint(multiTouchInput.mTouches[i].mScreenPoint));
mApzcForInputBlock = CommonAncestor(mApzcForInputBlock.get(), apzc2.get());
@ -353,8 +356,11 @@ APZCTreeManager::GetTouchInputBlockAPZC(const WidgetTouchEvent& aEvent,
ScreenPoint aPoint)
{
nsRefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(aPoint);
if (aEvent.touches.Length() == 1) // pan, not pinch
apzc = AdjustForScrollGrab(apzc);
if (aEvent.touches.Length() == 1) {
// If we have one touch point, this might be the start of a pan.
// Prepare for possible overscroll handoff.
BuildOverscrollHandoffChain(apzc);
}
gfx3DMatrix transformToApzc, transformToGecko;
// Reset the cached apz transform
mCachedTransformToApzcForInputBlock = transformToApzc;
@ -620,14 +626,11 @@ APZCTreeManager::ClearTree()
}
void
APZCTreeManager::HandleOverscroll(AsyncPanZoomController* aPrev, ScreenPoint aStartPoint, ScreenPoint aEndPoint,
uint32_t aOverscrollHandoffChainIndex)
APZCTreeManager::DispatchScroll(AsyncPanZoomController* aPrev, ScreenPoint aStartPoint, ScreenPoint aEndPoint,
uint32_t aOverscrollHandoffChainIndex)
{
// Increment the current index into the chain of APZCs that handle overscroll
// for the current pan gesture. Since we are in overscroll, we have scrolled
// the previous APZC as far as possible, and will now hand off the remaining
// scroll to the next one.
++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;
@ -637,19 +640,28 @@ APZCTreeManager::HandleOverscroll(AsyncPanZoomController* aPrev, ScreenPoint aSt
if (next == nullptr)
return;
gfx3DMatrix transformToApzc;
gfx3DMatrix transformToGecko; // ignored
// Convert the start and end points from |aPrev|'s coordinate space to
// |next|'s coordinate space. Since |aPrev| may be the same as |next|
// (if |aPrev| is the APZC that is initiating the scroll and there is no
// 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 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);
// Convert start and end points to next's transformed screen coordinates.
GetInputTransforms(next, transformToApzc, transformToGecko);
ApplyTransform(&aStartPoint, transformToApzc);
ApplyTransform(&aEndPoint, transformToApzc);
}
// Scroll |next|. If this causes overscroll, it will call DispatchScroll()
// again with an incremented index.
next->AttemptScroll(aStartPoint, aEndPoint, aOverscrollHandoffChainIndex);
}
@ -707,8 +719,8 @@ APZCTreeManager::GetTargetAPZC(const ScreenPoint& aPoint)
return target.forget();
}
already_AddRefed<AsyncPanZoomController>
APZCTreeManager::AdjustForScrollGrab(const nsRefPtr<AsyncPanZoomController>& aInitialTarget)
void
APZCTreeManager::BuildOverscrollHandoffChain(const nsRefPtr<AsyncPanZoomController>& aInitialTarget)
{
// Scroll grabbing is a mechanism that allows content to specify that
// the initial target of a pan should be not the innermost scrollable
@ -727,7 +739,7 @@ APZCTreeManager::AdjustForScrollGrab(const nsRefPtr<AsyncPanZoomController>& aIn
if (!mOverscrollHandoffChain.append(apzc)) {
NS_WARNING("Vector::append failed");
mOverscrollHandoffChain.clear();
return nullptr;
return;
}
}
@ -741,10 +753,6 @@ APZCTreeManager::AdjustForScrollGrab(const nsRefPtr<AsyncPanZoomController>& aIn
// and users of 'scrollgrab' should not rely on this.)
std::stable_sort(mOverscrollHandoffChain.begin(), mOverscrollHandoffChain.end(),
CompareByScrollPriority());
// The initial target is the first APZC in the handoff chain.
nsRefPtr<AsyncPanZoomController> result = mOverscrollHandoffChain.length() > 0 ? mOverscrollHandoffChain[0] : nullptr;
return result.forget();
}
void

View File

@ -213,22 +213,43 @@ public:
static float GetDPI() { return sDPI; }
/**
* This is a callback for AsyncPanZoomController to call when a touch-move
* event causes overscroll. The overscroll will be passed on to the next
* APZC in the overscroll handoff chain, which was determined in the
* GetTargetAPZC() call for the first touch event of the touch block (usually
* the handoff chain is child -> parent, but scroll grabbing can change this).
* This is a callback for AsyncPanZoomController to call when it wants to
* scroll in response to a touch-move event, or when it needs to hand off
* overscroll to the next APZC. Note that because of scroll grabbing, the
* first APZC to scroll may not be the one that is receiving the touch events.
*
* |aAPZC| is the APZC that received the touch events triggering the scroll
* (in the case of an initial scroll), or the last APZC to scroll (in the
* case of overscroll)
* |aStartPoint| and |aEndPoint| are in |aAPZC|'s transformed screen
* coordinates (i.e. the same coordinates in which touch points are given to
* APZCs). The amount of the overscroll is represented by two points rather
* than a displacement because with certain 3D transforms, the same
* displacement between different points in transformed coordinates can
* represent different displacements in untransformed coordinates.
* |aOverscrollHandoffChainIndex| is |aAPZC|'s current position in the
* overscroll handoff chain.
* coordinates (i.e. the same coordinates in which touch points are given to
* APZCs). The amount of (over)scroll is represented by two points rather
* than a displacement because with certain 3D transforms, the same
* displacement between different points in transformed coordinates can
* represent different displacements in untransformed coordinates.
* |aOverscrollHandoffChainIndex| is the next position in the overscroll
* handoff chain that should be scrolled.
*
* The way this method works is best illustrated with an example.
* Consider three nested APZCs, A, B, and C, with C being the innermost one.
* Say B is scroll-grabbing.
* The touch events go to C because it's the innermost one (so e.g. taps
* should go through C), but the overscroll handoff chain is B -> C -> A
* because B is scroll-grabbing.
* For convenience I'll refer to the three APZC objects as A, B, and C, and
* to the tree manager object as TM.
* Here's what happens when C receives a touch-move event:
* - C.TrackTouch() calls TM.DispatchScroll() with index = 0.
* - TM.DispatchScroll() calls B.AttemptScroll() (since B is at index 0 in the chain).
* - B.AttemptScroll() scrolls B. If there is overscroll, it calls TM.DispatchScroll() with index = 1.
* - TM.DispatchScroll() calls C.AttemptScroll() (since C is at index 1 in the chain)
* - C.AttemptScroll() scrolls C. If there is overscroll, it calls TM.DispatchScroll() with index = 2.
* - 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.
*/
void HandleOverscroll(AsyncPanZoomController* aAPZC, ScreenPoint aStartPoint, ScreenPoint aEndPoint,
uint32_t aOverscrollHandoffChainIndex);
void DispatchScroll(AsyncPanZoomController* aAPZC, ScreenPoint aStartPoint, ScreenPoint aEndPoint,
uint32_t aOverscrollHandoffChainIndex);
protected:
/**
@ -237,6 +258,10 @@ protected:
*/
virtual void AssertOnCompositorThread();
/*
* Build the chain of APZCs that will handle overscroll for a pan starting at |aInitialTarget|.
*/
void BuildOverscrollHandoffChain(const nsRefPtr<AsyncPanZoomController>& aInitialTarget);
public:
/* Some helper functions to find an APZC given some identifying input. These functions
lock the tree of APZCs while they find the right one, and then return an addref'd
@ -246,10 +271,6 @@ public:
*/
already_AddRefed<AsyncPanZoomController> GetTargetAPZC(const ScrollableLayerGuid& aGuid);
already_AddRefed<AsyncPanZoomController> GetTargetAPZC(const ScreenPoint& aPoint);
/*
* Adjust the target APZC of an input event to account for scroll grabbing.
*/
already_AddRefed<AsyncPanZoomController> AdjustForScrollGrab(const nsRefPtr<AsyncPanZoomController>& aInitialTarget);
void GetRootAPZCsFor(const uint64_t& aLayersId,
nsTArray< nsRefPtr<AsyncPanZoomController> >* aOutRootApzcs);
void GetInputTransforms(AsyncPanZoomController *aApzc, gfx3DMatrix& aTransformToApzcOut,

View File

@ -924,15 +924,21 @@ void AsyncPanZoomController::AttemptScroll(const ScreenPoint& aStartPoint,
}
if (fabs(overscroll.x) > EPSILON || fabs(overscroll.y) > EPSILON) {
// Make a local copy of the tree manager pointer and check if it's not
// null before calling HandleOverscroll(). This is necessary because
// Destroy(), which nulls out mTreeManager, could be called concurrently.
APZCTreeManager* treeManagerLocal = mTreeManager;
if (treeManagerLocal) {
// "+ overscroll" rather than "- overscroll" for the same reason as above.
treeManagerLocal->HandleOverscroll(this, aEndPoint + overscroll, aEndPoint,
aOverscrollHandoffChainIndex);
}
// "+ overscroll" rather than "- overscroll" because "overscroll" is what's
// left of "displacement", and "displacement" is "start - end".
CallDispatchScroll(aEndPoint + overscroll, aEndPoint, aOverscrollHandoffChainIndex + 1);
}
}
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
// null before calling HandleOverscroll(). This is necessary because
// Destroy(), which nulls out mTreeManager, could be called concurrently.
APZCTreeManager* treeManagerLocal = mTreeManager;
if (treeManagerLocal) {
treeManagerLocal->DispatchScroll(this, aStartPoint, aEndPoint,
aOverscrollHandoffChainIndex);
}
}
@ -974,7 +980,7 @@ void AsyncPanZoomController::TrackTouch(const MultiTouchInput& aEvent) {
UpdateWithTouchAtDevicePoint(aEvent);
AttemptScroll(prevTouchPoint, touchPoint);
CallDispatchScroll(prevTouchPoint, touchPoint, 0);
}
ScreenIntPoint& AsyncPanZoomController::GetFirstTouchScreenPoint(const MultiTouchInput& aEvent) {

View File

@ -270,15 +270,23 @@ public:
* at these points, but this function will scroll as if there had been.
* If this attempt causes overscroll (i.e. the layer cannot be scrolled
* by the entire amount requested), the overscroll is passed back to the
* tree manager via APZCTreeManager::HandleOverscroll().
* tree manager via APZCTreeManager::DispatchScroll().
* |aOverscrollHandoffChainIndex| is used by the tree manager to keep track
* of which APZC to hand off the overscroll to; this function simply
* propagates it to APZCTreeManager::HandleOverscroll() in the event of
* of which APZC to hand off the overscroll to; this function increments it
* and passes it on to APZCTreeManager::DispatchScroll() in the event of
* overscroll.
*/
void AttemptScroll(const ScreenPoint& aStartPoint, const ScreenPoint& aEndPoint,
uint32_t aOverscrollHandoffChainIndex = 0);
/**
* A helper function for calling APZCTreeManager::DispatchScroll().
* Guards against the case where the APZC is being concurrently destroyed
* (and thus mTreeManager is being nulled out).
*/
void CallDispatchScroll(const ScreenPoint& aStartPoint, const ScreenPoint& aEndPoint,
uint32_t aOverscrollHandoffChainIndex);
/**
* Returns whether this APZC is for an element marked with the 'scrollgrab'
* attribute.

View File

@ -45,8 +45,9 @@ class TestAPZCContainerLayer : public ContainerLayer {
class TestAsyncPanZoomController : public AsyncPanZoomController {
public:
TestAsyncPanZoomController(uint64_t aLayersId, MockContentController* aMcc)
: AsyncPanZoomController(aLayersId, nullptr, aMcc)
TestAsyncPanZoomController(uint64_t aLayersId, MockContentController* aMcc,
APZCTreeManager* aTreeManager = nullptr)
: AsyncPanZoomController(aLayersId, aTreeManager, aMcc)
{}
void SetFrameMetrics(const FrameMetrics& metrics) {
@ -63,6 +64,12 @@ public:
class TestAPZCTreeManager : public APZCTreeManager {
protected:
void AssertOnCompositorThread() MOZ_OVERRIDE { /* no-op */ }
public:
// Expose this so test code can call it directly.
void BuildOverscrollHandoffChain(AsyncPanZoomController* aApzc) {
APZCTreeManager::BuildOverscrollHandoffChain(aApzc);
}
};
static
@ -79,13 +86,18 @@ FrameMetrics TestFrameMetrics() {
}
static
void ApzcPan(AsyncPanZoomController* apzc, int& aTime, int aTouchStartY, int aTouchEndY) {
void ApzcPan(AsyncPanZoomController* apzc, TestAPZCTreeManager* aTreeManager, int& aTime, int aTouchStartY, int aTouchEndY) {
const int TIME_BETWEEN_TOUCH_EVENT = 100;
const int OVERCOME_TOUCH_TOLERANCE = 100;
MultiTouchInput mti;
nsEventStatus status;
// Since we're passing inputs directly to the APZC instead of going through
// the tree manager, we need to build the overscroll handoff chain explicitly
// for panning to work correctly.
aTreeManager->BuildOverscrollHandoffChain(apzc);
mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_START, aTime, 0);
aTime += TIME_BETWEEN_TOUCH_EVENT;
// Make sure the move is large enough to not be handled as a tap
@ -335,7 +347,8 @@ TEST(AsyncPanZoomController, Pan) {
AsyncPanZoomController::SetFrameTime(testStartTime);
nsRefPtr<MockContentController> mcc = new MockContentController();
nsRefPtr<TestAsyncPanZoomController> apzc = new TestAsyncPanZoomController(0, mcc);
nsRefPtr<TestAPZCTreeManager> tm = new TestAPZCTreeManager();
nsRefPtr<TestAsyncPanZoomController> apzc = new TestAsyncPanZoomController(0, mcc, tm);
apzc->SetFrameMetrics(TestFrameMetrics());
apzc->NotifyLayersUpdated(TestFrameMetrics(), true);
@ -350,13 +363,13 @@ TEST(AsyncPanZoomController, Pan) {
ViewTransform viewTransformOut;
// Pan down
ApzcPan(apzc, time, touchStart, touchEnd);
ApzcPan(apzc, tm, time, touchStart, touchEnd);
apzc->SampleContentTransformForFrame(testStartTime, &viewTransformOut, pointOut);
EXPECT_EQ(pointOut, ScreenPoint(0, -(touchEnd-touchStart)));
EXPECT_NE(viewTransformOut, ViewTransform());
// Pan back
ApzcPan(apzc, time, touchEnd, touchStart);
ApzcPan(apzc, tm, time, touchEnd, touchStart);
apzc->SampleContentTransformForFrame(testStartTime, &viewTransformOut, pointOut);
EXPECT_EQ(pointOut, ScreenPoint());
EXPECT_EQ(viewTransformOut, ViewTransform());
@ -367,7 +380,8 @@ TEST(AsyncPanZoomController, Fling) {
AsyncPanZoomController::SetFrameTime(testStartTime);
nsRefPtr<MockContentController> mcc = new MockContentController();
nsRefPtr<TestAsyncPanZoomController> apzc = new TestAsyncPanZoomController(0, mcc);
nsRefPtr<TestAPZCTreeManager> tm = new TestAPZCTreeManager();
nsRefPtr<TestAsyncPanZoomController> apzc = new TestAsyncPanZoomController(0, mcc, tm);
apzc->SetFrameMetrics(TestFrameMetrics());
apzc->NotifyLayersUpdated(TestFrameMetrics(), true);
@ -382,7 +396,7 @@ TEST(AsyncPanZoomController, Fling) {
ViewTransform viewTransformOut;
// Fling down. Each step scroll further down
ApzcPan(apzc, time, touchStart, touchEnd);
ApzcPan(apzc, tm, time, touchStart, touchEnd);
ScreenPoint lastPoint;
for (int i = 1; i < 50; i+=1) {
apzc->SampleContentTransformForFrame(testStartTime+TimeDuration::FromMilliseconds(i), &viewTransformOut, pointOut);
@ -396,7 +410,8 @@ TEST(AsyncPanZoomController, OverScrollPanning) {
AsyncPanZoomController::SetFrameTime(testStartTime);
nsRefPtr<MockContentController> mcc = new MockContentController();
nsRefPtr<TestAsyncPanZoomController> apzc = new TestAsyncPanZoomController(0, mcc);
nsRefPtr<TestAPZCTreeManager> tm = new TestAPZCTreeManager();
nsRefPtr<TestAsyncPanZoomController> apzc = new TestAsyncPanZoomController(0, mcc, tm);
apzc->SetFrameMetrics(TestFrameMetrics());
apzc->NotifyLayersUpdated(TestFrameMetrics(), true);
@ -412,7 +427,7 @@ TEST(AsyncPanZoomController, OverScrollPanning) {
ViewTransform viewTransformOut;
// Pan down
ApzcPan(apzc, time, touchStart, touchEnd);
ApzcPan(apzc, tm, time, touchStart, touchEnd);
apzc->SampleContentTransformForFrame(testStartTime+TimeDuration::FromMilliseconds(1000), &viewTransformOut, pointOut);
EXPECT_EQ(pointOut, ScreenPoint(0, 90));
}
@ -584,7 +599,7 @@ TEST(APZCTreeManager, HitTesting2) {
nsRefPtr<MockContentController> mcc = new MockContentController();
ScopedLayerTreeRegistration controller(0, root, mcc);
nsRefPtr<APZCTreeManager> manager = new TestAPZCTreeManager();
nsRefPtr<TestAPZCTreeManager> manager = new TestAPZCTreeManager();
nsRefPtr<AsyncPanZoomController> hit;
gfx3DMatrix transformToApzc;
gfx3DMatrix transformToGecko;
@ -660,7 +675,7 @@ TEST(APZCTreeManager, HitTesting2) {
EXPECT_CALL(*mcc, PostDelayedTask(_,_)).Times(1);
EXPECT_CALL(*mcc, SendAsyncScrollDOMEvent(_,_,_)).Times(2);
EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(1);
ApzcPan(apzcroot, time, 100, 50);
ApzcPan(apzcroot, manager, time, 100, 50);
// Hit where layers[3] used to be. It should now hit the root.
hit = GetTargetAPZC(manager, ScreenPoint(75, 75), transformToApzc, transformToGecko);