diff --git a/gfx/layers/apz/src/AsyncPanZoomController.cpp b/gfx/layers/apz/src/AsyncPanZoomController.cpp index 822777b8abbd..461993feec9e 100644 --- a/gfx/layers/apz/src/AsyncPanZoomController.cpp +++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp @@ -3155,6 +3155,12 @@ AsyncPanZoomController::CurrentTouchBlock() return GetInputQueue()->CurrentTouchBlock(); } +PanGestureBlockState* +AsyncPanZoomController::CurrentPanGestureBlock() +{ + return GetInputQueue()->CurrentPanGestureBlock(); +} + void AsyncPanZoomController::ResetInputState() { diff --git a/gfx/layers/apz/src/AsyncPanZoomController.h b/gfx/layers/apz/src/AsyncPanZoomController.h index d86cc1758104..c45f4e6d0cfc 100644 --- a/gfx/layers/apz/src/AsyncPanZoomController.h +++ b/gfx/layers/apz/src/AsyncPanZoomController.h @@ -49,6 +49,7 @@ class AsyncPanZoomAnimation; class FlingAnimation; class InputBlockState; class TouchBlockState; +class PanGestureBlockState; class OverscrollHandoffChain; class StateChangeNotificationBlocker; @@ -832,6 +833,7 @@ private: TouchBlockState* CurrentTouchBlock(); bool HasReadyTouchBlock(); + PanGestureBlockState* CurrentPanGestureBlock(); /* =================================================================== * The functions and members in this section are used to manage diff --git a/gfx/layers/apz/src/InputBlockState.cpp b/gfx/layers/apz/src/InputBlockState.cpp index 266462753bfd..f6a12200233b 100644 --- a/gfx/layers/apz/src/InputBlockState.cpp +++ b/gfx/layers/apz/src/InputBlockState.cpp @@ -421,6 +421,103 @@ WheelBlockState::EndTransaction() mTransactionEnded = true; } +PanGestureBlockState::PanGestureBlockState(const nsRefPtr& aTargetApzc, + bool aTargetConfirmed, + const PanGestureInput& aInitialEvent) + : CancelableBlockState(aTargetApzc, aTargetConfirmed) + , mInterrupted(false) +{ + 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 apzc = + mOverscrollHandoffChain->FindFirstScrollable(aInitialEvent); + + if (apzc && apzc != GetTargetApzc()) { + UpdateTargetApzc(apzc); + } + } +} + +bool +PanGestureBlockState::SetConfirmedTargetApzc(const nsRefPtr& aTargetApzc) +{ + // The APZC that we find via APZCCallbackHelpers may not be the same APZC + // ESM or OverscrollHandoff would have computed. Make sure we get the right + // one by looking for the first apzc the next pending event can scroll. + nsRefPtr apzc = aTargetApzc; + if (apzc && mEvents.Length() > 0) { + const PanGestureInput& event = mEvents.ElementAt(0); + nsRefPtr scrollableApzc = + apzc->BuildOverscrollHandoffChain()->FindFirstScrollable(event); + if (scrollableApzc) { + apzc = scrollableApzc; + } + } + + InputBlockState::SetConfirmedTargetApzc(apzc); + return true; +} + +void +PanGestureBlockState::AddEvent(const PanGestureInput& aEvent) +{ + mEvents.AppendElement(aEvent); +} + +bool +PanGestureBlockState::HasEvents() const +{ + return !mEvents.IsEmpty(); +} + +void +PanGestureBlockState::DropEvents() +{ + TBS_LOG("%p dropping %" PRIuSIZE " events\n", this, mEvents.Length()); + mEvents.Clear(); +} + +void +PanGestureBlockState::HandleEvents() +{ + while (HasEvents()) { + TBS_LOG("%p returning first of %" PRIuSIZE " events\n", this, mEvents.Length()); + PanGestureInput event = mEvents[0]; + mEvents.RemoveElementAt(0); + DispatchEvent(event); + } +} + +bool +PanGestureBlockState::MustStayActive() +{ + return !mInterrupted; +} + +const char* +PanGestureBlockState::Type() +{ + return "pan gesture"; +} + +bool +PanGestureBlockState::SetContentResponse(bool aPreventDefault) +{ + if (aPreventDefault) { + mInterrupted = true; + } + return CancelableBlockState::SetContentResponse(aPreventDefault); +} + +bool +PanGestureBlockState::AllowScrollHandoff() const +{ + return false; +} + TouchBlockState::TouchBlockState(const nsRefPtr& aTargetApzc, bool aTargetConfirmed, TouchCounter& aCounter) : CancelableBlockState(aTargetApzc, aTargetConfirmed) diff --git a/gfx/layers/apz/src/InputBlockState.h b/gfx/layers/apz/src/InputBlockState.h index 418fb3293e1d..7d5e7cc2e568 100644 --- a/gfx/layers/apz/src/InputBlockState.h +++ b/gfx/layers/apz/src/InputBlockState.h @@ -20,6 +20,7 @@ class OverscrollHandoffChain; class CancelableBlockState; class TouchBlockState; class WheelBlockState; +class PanGestureBlockState; /** * A base class that stores state common to various input blocks. @@ -85,6 +86,9 @@ public: virtual WheelBlockState *AsWheelBlock() { return nullptr; } + virtual PanGestureBlockState *AsPanGestureBlock() { + return nullptr; + } /** * Record whether or not content cancelled this block of events. @@ -247,6 +251,42 @@ private: bool mTransactionEnded; }; +/** + * A single block of pan gesture events. + */ +class PanGestureBlockState : public CancelableBlockState +{ +public: + PanGestureBlockState(const nsRefPtr& aTargetApzc, + bool aTargetConfirmed, + const PanGestureInput& aEvent); + + bool SetContentResponse(bool aPreventDefault) override; + bool HasEvents() const override; + void DropEvents() override; + void HandleEvents() override; + bool MustStayActive() override; + const char* Type() override; + bool SetConfirmedTargetApzc(const nsRefPtr& aTargetApzc) override; + + void AddEvent(const PanGestureInput& aEvent); + + PanGestureBlockState *AsPanGestureBlock() override { + return this; + } + + /** + * @return Whether or not overscrolling is prevented for this block. + */ + bool AllowScrollHandoff() const; + + bool WasInterrupted() const { return mInterrupted; } + +private: + nsTArray mEvents; + bool mInterrupted; +}; + /** * This class represents a single touch block. A touch block is * a set of touch events that can be cancelled by web content via diff --git a/gfx/layers/apz/src/InputQueue.cpp b/gfx/layers/apz/src/InputQueue.cpp index c01fb780afb5..fae2d7f2ab74 100644 --- a/gfx/layers/apz/src/InputQueue.cpp +++ b/gfx/layers/apz/src/InputQueue.cpp @@ -45,6 +45,11 @@ InputQueue::ReceiveInputEvent(const nsRefPtr& aTarget, return ReceiveScrollWheelInput(aTarget, aTargetConfirmed, event, aOutInputBlockId); } + case PANGESTURE_INPUT: { + const PanGestureInput& event = aEvent.AsPanGestureInput(); + return ReceivePanGestureInput(aTarget, aTargetConfirmed, event, aOutInputBlockId); + } + default: // The return value for non-touch input is only used by tests, so just pass // through the return value for now. This can be changed later if needed. @@ -209,6 +214,56 @@ InputQueue::ReceiveScrollWheelInput(const nsRefPtr& aTar return nsEventStatus_eConsumeDoDefault; } +nsEventStatus +InputQueue::ReceivePanGestureInput(const nsRefPtr& aTarget, + bool aTargetConfirmed, + const PanGestureInput& aEvent, + uint64_t* aOutInputBlockId) { + if (aEvent.mType == PanGestureInput::PANGESTURE_MAYSTART || + aEvent.mType == PanGestureInput::PANGESTURE_CANCELLED) { + // Ignore these events for now. + return nsEventStatus_eConsumeDoDefault; + } + + PanGestureBlockState* block = nullptr; + if (!mInputBlockQueue.IsEmpty() && + aEvent.mType != PanGestureInput::PANGESTURE_START) { + block = mInputBlockQueue.LastElement()->AsPanGestureBlock(); + } + + if (!block || block->WasInterrupted()) { + if (aEvent.mType != PanGestureInput::PANGESTURE_START) { + // Only PANGESTURE_START events are allowed to start a new pan gesture block. + return nsEventStatus_eConsumeDoDefault; + } + block = new PanGestureBlockState(aTarget, aTargetConfirmed, aEvent); + INPQ_LOG("started new pan gesture block %p for target %p\n", block, aTarget.get()); + + SweepDepletedBlocks(); + mInputBlockQueue.AppendElement(block); + + CancelAnimationsForNewBlock(block); + MaybeRequestContentResponse(aTarget, block); + } else { + INPQ_LOG("received new event in block %p\n", block); + } + + if (aOutInputBlockId) { + *aOutInputBlockId = block->GetBlockId(); + } + + // Note that the |aTarget| the APZCTM sent us may contradict the confirmed + // target set on the block. In this case the confirmed target (which may be + // null) should take priority. This is equivalent to just always using the + // target (confirmed or not) from the block, which is what + // MaybeHandleCurrentBlock() does. + if (!MaybeHandleCurrentBlock(block, aEvent)) { + block->AddEvent(aEvent.AsPanGestureInput()); + } + + return nsEventStatus_eConsumeDoDefault; +} + void InputQueue::CancelAnimationsForNewBlock(CancelableBlockState* aBlock) { @@ -321,6 +376,14 @@ InputQueue::CurrentWheelBlock() const return block; } +PanGestureBlockState* +InputQueue::CurrentPanGestureBlock() const +{ + PanGestureBlockState* block = CurrentBlock()->AsPanGestureBlock(); + MOZ_ASSERT(block); + return block; +} + WheelBlockState* InputQueue::GetCurrentWheelTransaction() const { diff --git a/gfx/layers/apz/src/InputQueue.h b/gfx/layers/apz/src/InputQueue.h index 1521ad54a8de..b54daff44cf9 100644 --- a/gfx/layers/apz/src/InputQueue.h +++ b/gfx/layers/apz/src/InputQueue.h @@ -23,6 +23,7 @@ class AsyncPanZoomController; class CancelableBlockState; class TouchBlockState; class WheelBlockState; +class PanGestureBlockState; /** * This class stores incoming input events, separated into "input blocks", until @@ -80,15 +81,13 @@ public: */ CancelableBlockState* CurrentBlock() const; /** - * Returns the current pending input block as a touch block. It must only be - * called if the current pending block is a touch block. + * Returns the current pending input block as a specific kind of block. + * These methods must only be called if the current pending block is of the + * requested type. */ 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; + PanGestureBlockState* CurrentPanGestureBlock() const; /** * Returns true iff the pending block at the head of the queue is ready for * handling. @@ -131,6 +130,10 @@ private: bool aTargetConfirmed, const ScrollWheelInput& aEvent, uint64_t* aOutInputBlockId); + nsEventStatus ReceivePanGestureInput(const nsRefPtr& aTarget, + bool aTargetConfirmed, + const PanGestureInput& aEvent, + uint64_t* aOutInputBlockId); /** * Remove any blocks that are inactive - not ready, and having no events.