Add wheel transaction support to APZ. (bug 1142866 part 1, r=kats)

This commit is contained in:
David Anderson 2015-03-22 00:36:13 -07:00
parent 275609c516
commit a0a7580fcf
11 changed files with 281 additions and 22 deletions

View File

@ -682,7 +682,11 @@ public:
, mYAxisModel(aInitialPosition.y, aDestination.y, aInitialVelocity.y,
aSpringConstant, aDampingRatio)
, mSource(aSource)
, mAllowOverscroll(true)
{
if (mSource == ScrollSource::Wheel) {
mAllowOverscroll = mApzc.AllowScrollHandoffInWheelTransaction();
}
}
/**
@ -739,8 +743,7 @@ public:
// This can happen if either the layout.css.scroll-behavior.damping-ratio
// preference is set to less than 1 (underdamped) or if a smooth scroll
// inherits velocity from a fling gesture.
if (!IsZero(overscroll)) {
if (!IsZero(overscroll) && mAllowOverscroll) {
// Hand off a fling with the remaining momentum to the next APZC in the
// overscroll handoff chain.
@ -778,6 +781,7 @@ private:
AsyncPanZoomController& mApzc;
AxisPhysicsMSDModel mXAxisModel, mYAxisModel;
ScrollSource mSource;
bool mAllowOverscroll;
};
void
@ -1418,21 +1422,59 @@ AsyncPanZoomController::ConvertToGecko(const ParentLayerPoint& aPoint, CSSPoint*
return false;
}
nsEventStatus AsyncPanZoomController::OnScrollWheel(const ScrollWheelInput& aEvent)
void
AsyncPanZoomController::GetScrollWheelDelta(const ScrollWheelInput& aEvent,
double& aOutDeltaX,
double& aOutDeltaY) const
{
double deltaX = aEvent.mDeltaX;
double deltaY = aEvent.mDeltaY;
ReentrantMonitorAutoEnter lock(mMonitor);
aOutDeltaX = aEvent.mDeltaX;
aOutDeltaY = aEvent.mDeltaY;
switch (aEvent.mDeltaType) {
case ScrollWheelInput::SCROLLDELTA_LINE: {
LayoutDeviceIntSize scrollAmount = mFrameMetrics.GetLineScrollAmount();
deltaX *= scrollAmount.width;
deltaY *= scrollAmount.height;
aOutDeltaX *= scrollAmount.width;
aOutDeltaY *= scrollAmount.height;
break;
}
default:
MOZ_ASSERT_UNREACHABLE("unexpected scroll delta type");
return nsEventStatus_eConsumeNoDefault;
}
}
// Return whether or not the underlying layer can be scrolled on either axis.
bool
AsyncPanZoomController::CanScroll(const ScrollWheelInput& aEvent) const
{
double deltaX, deltaY;
GetScrollWheelDelta(aEvent, deltaX, deltaY);
if (!deltaX && !deltaY) {
return false;
}
return CanScroll(deltaX, deltaY);
}
bool
AsyncPanZoomController::CanScroll(double aDeltaX, double aDeltaY) const
{
ReentrantMonitorAutoEnter lock(mMonitor);
return mX.CanScroll(aDeltaX) || mY.CanScroll(aDeltaY);
}
bool
AsyncPanZoomController::AllowScrollHandoffInWheelTransaction() const
{
WheelBlockState* block = mInputQueue->CurrentWheelBlock();
return block->AllowScrollHandoff();
}
nsEventStatus AsyncPanZoomController::OnScrollWheel(const ScrollWheelInput& aEvent)
{
double deltaX, deltaY;
GetScrollWheelDelta(aEvent, deltaX, deltaY);
switch (aEvent.mScrollMode) {
case ScrollWheelInput::SCROLLMODE_INSTANT: {
@ -1916,6 +1958,13 @@ bool AsyncPanZoomController::AttemptScroll(const ParentLayerPoint& aStartPoint,
return true;
}
// If in a wheel transaction that has not ended, we drop overscroll.
if (aOverscrollHandoffState.mScrollSource == ScrollSource::Wheel &&
!AllowScrollHandoffInWheelTransaction())
{
return true;
}
// If there is overscroll, first try to hand it off to an APZC later
// in the handoff chain to consume (either as a normal scroll or as
// overscroll).

View File

@ -361,6 +361,14 @@ public:
ParentLayerPoint ToParentLayerCoordinates(const ScreenPoint& aVector,
const ScreenPoint& aAnchor) const;
// Return whether or not a wheel event will be able to scroll in either
// direction.
bool CanScroll(const ScrollWheelInput& aEvent) const;
// Return whether or not a scroll delta will be able to scroll in either
// direction.
bool CanScroll(double aDeltaX, double aDeltaY) const;
protected:
// Protected destructor, to discourage deletion outside of Release():
~AsyncPanZoomController();
@ -423,6 +431,10 @@ protected:
*/
nsEventStatus OnScrollWheel(const ScrollWheelInput& aEvent);
void GetScrollWheelDelta(const ScrollWheelInput& aEvent,
double& aOutDeltaX,
double& aOutDeltaY) const;
/**
* Helper methods for long press gestures.
*/
@ -849,6 +861,9 @@ private:
void StartSmoothScroll(ScrollSource aSource);
// Returns whether overscroll is allowed during a wheel event.
bool AllowScrollHandoffInWheelTransaction() const;
/* ===================================================================
* The functions and members in this section are used to make ancestor chains
* out of APZC instances. These chains can only be walked or manipulated

View File

@ -394,6 +394,16 @@ bool Axis::CanScroll() const {
return GetPageLength() - GetCompositionLength() > COORDINATE_EPSILON;
}
bool Axis::CanScroll(double aDelta) const
{
if (!CanScroll() || mAxisLocked) {
return false;
}
ParentLayerCoord delta = aDelta;
return DisplacementWillOverscrollAmount(delta) != delta;
}
bool Axis::CanScrollNow() const {
return !mAxisLocked && CanScroll();
}

View File

@ -147,6 +147,11 @@ public:
*/
bool CanScroll() const;
/**
* Returns whether this axis can scroll any more in a particular direction.
*/
bool CanScroll(double aDelta) const;
/**
* Returns true if the page has room to be scrolled along this axis
* and this axis is not scroll-locked.

View File

@ -49,11 +49,17 @@ InputBlockState::SetConfirmedTargetApzc(const nsRefPtr<AsyncPanZoomController>&
printf_stderr("%p replacing unconfirmed target %p with real target %p\n",
this, mTargetApzc.get(), aTargetApzc.get());
UpdateTargetApzc(aTargetApzc);
return true;
}
void
InputBlockState::UpdateTargetApzc(const nsRefPtr<AsyncPanZoomController>& aTargetApzc)
{
// note that aTargetApzc MAY be null here.
mTargetApzc = aTargetApzc;
mTransformToApzc = aTargetApzc ? aTargetApzc->GetTransformToThis() : gfx::Matrix4x4();
mOverscrollHandoffChain = (mTargetApzc ? mTargetApzc->BuildOverscrollHandoffChain() : nullptr);
return true;
}
const nsRefPtr<AsyncPanZoomController>&
@ -135,25 +141,58 @@ CancelableBlockState::IsReadyForHandling() const
}
void
CancelableBlockState::DispatchImmediate(const InputData& aEvent) const
CancelableBlockState::DispatchImmediate(const InputData& aEvent)
{
MOZ_ASSERT(!HasEvents());
MOZ_ASSERT(GetTargetApzc());
GetTargetApzc()->HandleInputEvent(aEvent, mTransformToApzc);
}
// This is used to track the current wheel transaction.
static uint64_t sLastWheelBlockId = InputBlockState::NO_BLOCK_ID;
WheelBlockState::WheelBlockState(const nsRefPtr<AsyncPanZoomController>& aTargetApzc,
bool aTargetConfirmed)
bool aTargetConfirmed,
const ScrollWheelInput& aInitialEvent)
: CancelableBlockState(aTargetApzc, aTargetConfirmed)
, mTransactionEnded(false)
{
sLastWheelBlockId = GetBlockId();
Update(aInitialEvent);
if (aTargetConfirmed) {
// Find the nearest APZC in the overscroll handoff chain that is scrollable.
// If we get a content confirmation later that the apzc is different, then
// content should have found a scrollable apzc, so we don't need to handle
// that case.
nsRefPtr<AsyncPanZoomController> apzc =
mOverscrollHandoffChain->FindFirstScrollable(aInitialEvent);
if (apzc && apzc != GetTargetApzc()) {
UpdateTargetApzc(apzc);
}
}
}
void
WheelBlockState::Update(const ScrollWheelInput& aEvent)
{
mLastEventTime = aEvent.mTimeStamp;
}
void
WheelBlockState::AddEvent(const ScrollWheelInput& aEvent)
{
Update(aEvent);
mEvents.AppendElement(aEvent);
}
void
WheelBlockState::DispatchImmediate(const InputData& aEvent)
{
Update(aEvent.AsScrollWheelInput());
CancelableBlockState::DispatchImmediate(aEvent);
}
bool
WheelBlockState::IsReadyForHandling() const
{
@ -190,7 +229,7 @@ WheelBlockState::HandleEvents()
bool
WheelBlockState::MustStayActive()
{
return false;
return !mTransactionEnded;
}
const char*
@ -199,6 +238,54 @@ WheelBlockState::Type()
return "scroll wheel";
}
bool
WheelBlockState::ShouldAcceptNewEvent(const ScrollWheelInput& aEvent) const
{
if (!InTransaction()) {
// If we're not in a transaction, start a new one.
return false;
}
nsRefPtr<AsyncPanZoomController> apzc = GetTargetApzc();
if (!apzc || apzc->IsDestroyed()) {
return false;
}
// End the transaction if the event occurred > 1.5s after the most recently
// seen wheel event.
TimeDuration duration = aEvent.mTimeStamp - mLastEventTime;
if (duration.ToMilliseconds() >= gfxPrefs::MouseWheelTransactionTimeoutMs()) {
return false;
}
// Accept the event, even if we can't scroll anymore.
return true;
}
bool
WheelBlockState::InTransaction() const
{
// We consider a wheel block to be in a transaction if it has a confirmed
// target and is the most recent wheel input block to be created.
if (GetBlockId() != sLastWheelBlockId) {
return false;
}
return !mTransactionEnded;
}
bool
WheelBlockState::AllowScrollHandoff() const
{
// If we're in a wheel transaction, we do not allow overscroll handoff until
// a new event ends the wheel transaction.
return !IsTargetConfirmed() || !InTransaction();
}
void
WheelBlockState::EndTransaction()
{
mTransactionEnded = true;
}
TouchBlockState::TouchBlockState(const nsRefPtr<AsyncPanZoomController>& aTargetApzc,
bool aTargetConfirmed)
: CancelableBlockState(aTargetApzc, aTargetConfirmed)

View File

@ -45,12 +45,16 @@ public:
bool IsTargetConfirmed() const;
protected:
void UpdateTargetApzc(const nsRefPtr<AsyncPanZoomController>& aTargetApzc);
private:
nsRefPtr<AsyncPanZoomController> mTargetApzc;
nsRefPtr<const OverscrollHandoffChain> mOverscrollHandoffChain;
bool mTargetConfirmed;
const uint64_t mBlockId;
protected:
nsRefPtr<const OverscrollHandoffChain> mOverscrollHandoffChain;
// Used to transform events from global screen space to |mTargetApzc|'s
// screen space. It's cached at the beginning of the input block so that
// all events in the block are in the same coordinate space.
@ -106,7 +110,7 @@ public:
* This input block must not have pending events, and its apzc must not be
* nullptr.
*/
void DispatchImmediate(const InputData& aEvent) const;
virtual void DispatchImmediate(const InputData& aEvent);
/**
* @return true iff this block has received all the information needed
@ -154,7 +158,8 @@ class WheelBlockState : public CancelableBlockState
{
public:
WheelBlockState(const nsRefPtr<AsyncPanZoomController>& aTargetApzc,
bool aTargetConfirmed);
bool aTargetConfirmed,
const ScrollWheelInput& aEvent);
bool IsReadyForHandling() const override;
bool HasEvents() const override;
@ -162,6 +167,7 @@ public:
void HandleEvents() override;
bool MustStayActive() override;
const char* Type() override;
void DispatchImmediate(const InputData& aEvent) override;
void AddEvent(const ScrollWheelInput& aEvent);
@ -169,8 +175,46 @@ public:
return this;
}
/**
* Returns whether or not the block should accept new events. If the APZC
* has been destroyed, or the block is not part of a wheel transaction, then
* this will return false.
*
* @return True if the event should be accepted, false otherwise.
*/
bool ShouldAcceptNewEvent(const ScrollWheelInput& aEvent) const;
/**
* Returns whether or not the block is participating in a wheel transaction.
* This means that the block is the most recent input block to be created,
* and no events have occurred that would require scrolling a different
* frame.
*
* @return True if in a transaction, false otherwise.
*/
bool InTransaction() const;
/**
* Mark the block as no longer participating in a wheel transaction. This
* will force future wheel events to begin a new input block.
*/
void EndTransaction();
/**
* @return Whether or not overscrolling is prevented for this wheel block.
*/
bool AllowScrollHandoff() const;
private:
/**
* Update the wheel transaction state for a new event.
*/
void Update(const ScrollWheelInput& aEvent);
private:
nsTArray<ScrollWheelInput> mEvents;
TimeStamp mLastEventTime;
bool mTransactionEnded;
};
/**

View File

@ -160,14 +160,18 @@ InputQueue::ReceiveScrollWheelInput(const nsRefPtr<AsyncPanZoomController>& aTar
if (!mInputBlockQueue.IsEmpty()) {
block = mInputBlockQueue.LastElement()->AsWheelBlock();
// If the block's APZC has been destroyed, request a new block.
if (block && block->GetTargetApzc()->IsDestroyed()) {
// If the block is not accepting new events we'll create a new input block
// (and therefore a new wheel transaction).
if (block && !block->ShouldAcceptNewEvent(aEvent)) {
block = nullptr;
}
}
// If we have a block, it should be in a wheel transaction.
MOZ_ASSERT(!block || block->InTransaction());
if (!block) {
block = new WheelBlockState(aTarget, aTargetConfirmed);
block = new WheelBlockState(aTarget, aTargetConfirmed, aEvent);
INPQ_LOG("started new scroll wheel block %p for target %p\n", block, aTarget.get());
SweepDepletedBlocks();
@ -286,11 +290,32 @@ InputQueue::CurrentBlock() const
TouchBlockState*
InputQueue::CurrentTouchBlock() const
{
TouchBlockState *block = CurrentBlock()->AsTouchBlock();
TouchBlockState* block = CurrentBlock()->AsTouchBlock();
MOZ_ASSERT(block);
return block;
}
WheelBlockState*
InputQueue::CurrentWheelBlock() const
{
WheelBlockState* block = CurrentBlock()->AsWheelBlock();
MOZ_ASSERT(block);
return block;
}
WheelBlockState*
InputQueue::GetCurrentWheelTransaction() const
{
if (mInputBlockQueue.IsEmpty()) {
return nullptr;
}
WheelBlockState* block = CurrentBlock()->AsWheelBlock();
if (!block || !block->InTransaction()) {
return nullptr;
}
return block;
}
bool
InputQueue::HasReadyTouchBlock() const
{
@ -426,7 +451,7 @@ InputQueue::ProcessInputBlocks() {
// If we get here, we know there are more touch blocks in the queue after
// |curBlock|, so we can remove |curBlock| and try to process the next one.
INPQ_LOG("discarding depleted touch block %p\n", curBlock);
INPQ_LOG("discarding processed %s block %p\n", curBlock->Type(), curBlock);
mInputBlockQueue.RemoveElementAt(0);
} while (!mInputBlockQueue.IsEmpty());
}

View File

@ -23,6 +23,7 @@ class AsyncPanZoomController;
class OverscrollHandoffChain;
class CancelableBlockState;
class TouchBlockState;
class WheelBlockState;
/**
* This class stores incoming input events, separated into "input blocks", until
@ -80,15 +81,25 @@ public:
*/
CancelableBlockState* CurrentBlock() const;
/**
* Returns the current pending input block as a touch block. It must only
* Returns the current pending input block as a touch block. It must only be
* called if the current pending block is a touch block.
*/
TouchBlockState* CurrentTouchBlock() const;
/**
* Returns the current pending input block as a wheel block. It must only be
* called if the current pending block is a wheel block.
*/
WheelBlockState* CurrentWheelBlock() const;
/**
* Returns true iff the pending block at the head of the queue is ready for
* handling.
*/
bool HasReadyTouchBlock() const;
/**
* If there is a wheel transaction, returns the WheelBlockState representing
* the transaction. Otherwise, returns null.
*/
WheelBlockState* GetCurrentWheelTransaction() const;
private:
~InputQueue();

View File

@ -63,7 +63,6 @@ OverscrollHandoffChain::IndexOf(const AsyncPanZoomController* aApzc) const
void
OverscrollHandoffChain::ForEachApzc(APZCMethod aMethod) const
{
MOZ_ASSERT(Length() > 0);
for (uint32_t i = 0; i < Length(); ++i) {
(mChain[i]->*aMethod)();
}
@ -156,6 +155,16 @@ OverscrollHandoffChain::HasFastMovingApzc() const
return AnyApzc(&AsyncPanZoomController::IsMovingFast);
}
nsRefPtr<AsyncPanZoomController>
OverscrollHandoffChain::FindFirstScrollable(const ScrollWheelInput& aInput) const
{
for (size_t i = 0; i < Length(); i++) {
if (mChain[i]->CanScroll(aInput)) {
return mChain[i];
}
}
return nullptr;
}
} // namespace layers
} // namespace mozilla

View File

@ -113,6 +113,8 @@ public:
// Determine whether any APZC along this handoff chain is moving fast.
bool HasFastMovingApzc() const;
nsRefPtr<AsyncPanZoomController> FindFirstScrollable(const ScrollWheelInput& aInput) const;
private:
std::vector<nsRefPtr<AsyncPanZoomController>> mChain;

View File

@ -337,6 +337,8 @@ private:
// This affects whether events will be routed through APZ or not.
DECL_GFX_PREF(Once, "mousewheel.system_scroll_override_on_root_content.enabled",
MouseWheelHasScrollDeltaOverride, bool, false);
DECL_GFX_PREF(Live, "mousewheel.transaction.timeout", MouseWheelTransactionTimeoutMs, int32_t, (int32_t)1500);
DECL_GFX_PREF(Live, "nglayout.debug.widget_update_flashing", WidgetUpdateFlashing, bool, false);
DECL_GFX_PREF(Live, "ui.click_hold_context_menus.delay", UiClickHoldContextMenusDelay, int32_t, 500);