mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-12-02 18:08:58 +00:00
Bug 1083395 - Extract an InputQueue class from the APZC input code. r=botond
This commit is contained in:
parent
2489aa5b2e
commit
541e8f016d
@ -16,6 +16,7 @@
|
|||||||
#include "GestureEventListener.h" // for GestureEventListener
|
#include "GestureEventListener.h" // for GestureEventListener
|
||||||
#include "InputData.h" // for MultiTouchInput, etc
|
#include "InputData.h" // for MultiTouchInput, etc
|
||||||
#include "InputBlockState.h" // for InputBlockState, TouchBlockState
|
#include "InputBlockState.h" // for InputBlockState, TouchBlockState
|
||||||
|
#include "InputQueue.h" // for InputQueue
|
||||||
#include "OverscrollHandoffState.h" // for OverscrollHandoffState
|
#include "OverscrollHandoffState.h" // for OverscrollHandoffState
|
||||||
#include "TaskThrottler.h" // for TaskThrottler
|
#include "TaskThrottler.h" // for TaskThrottler
|
||||||
#include "Units.h" // for CSSRect, CSSPoint, etc
|
#include "Units.h" // for CSSRect, CSSPoint, etc
|
||||||
@ -885,7 +886,7 @@ AsyncPanZoomController::AsyncPanZoomController(uint64_t aLayersId,
|
|||||||
mAsyncScrollTimeoutTask(nullptr),
|
mAsyncScrollTimeoutTask(nullptr),
|
||||||
mState(NOTHING),
|
mState(NOTHING),
|
||||||
mNotificationBlockers(0),
|
mNotificationBlockers(0),
|
||||||
mTouchBlockBalance(0),
|
mInputQueue(new InputQueue()),
|
||||||
mTreeManager(aTreeManager),
|
mTreeManager(aTreeManager),
|
||||||
mAPZCId(sAsyncPanZoomControllerCount++),
|
mAPZCId(sAsyncPanZoomControllerCount++),
|
||||||
mSharedLock(nullptr),
|
mSharedLock(nullptr),
|
||||||
@ -926,6 +927,13 @@ AsyncPanZoomController::GetGestureEventListener() const {
|
|||||||
return listener.forget();
|
return listener.forget();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nsRefPtr<InputQueue>
|
||||||
|
AsyncPanZoomController::GetInputQueue() const {
|
||||||
|
MonitorAutoLock lock(mRefPtrMonitor);
|
||||||
|
MOZ_ASSERT(mInputQueue);
|
||||||
|
return mInputQueue;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
AsyncPanZoomController::Destroy()
|
AsyncPanZoomController::Destroy()
|
||||||
{
|
{
|
||||||
@ -933,12 +941,11 @@ AsyncPanZoomController::Destroy()
|
|||||||
|
|
||||||
CancelAnimation();
|
CancelAnimation();
|
||||||
|
|
||||||
mTouchBlockQueue.Clear();
|
|
||||||
|
|
||||||
{ // scope the lock
|
{ // scope the lock
|
||||||
MonitorAutoLock lock(mRefPtrMonitor);
|
MonitorAutoLock lock(mRefPtrMonitor);
|
||||||
mGeckoContentController = nullptr;
|
mGeckoContentController = nullptr;
|
||||||
mGestureEventListener = nullptr;
|
mGestureEventListener = nullptr;
|
||||||
|
mInputQueue = nullptr; // XXX this is temporary and will be removed in a future patch
|
||||||
}
|
}
|
||||||
mPrevSibling = nullptr;
|
mPrevSibling = nullptr;
|
||||||
mLastChild = nullptr;
|
mLastChild = nullptr;
|
||||||
@ -1007,76 +1014,7 @@ AsyncPanZoomController::ArePointerEventsConsumable(TouchBlockState* aBlock, uint
|
|||||||
}
|
}
|
||||||
|
|
||||||
nsEventStatus AsyncPanZoomController::ReceiveInputEvent(const InputData& aEvent) {
|
nsEventStatus AsyncPanZoomController::ReceiveInputEvent(const InputData& aEvent) {
|
||||||
AssertOnControllerThread();
|
return GetInputQueue()->ReceiveInputEvent(this, aEvent);
|
||||||
|
|
||||||
if (aEvent.mInputType != MULTITOUCH_INPUT) {
|
|
||||||
HandleInputEvent(aEvent);
|
|
||||||
// The return value for non-touch input isn't really used, so just return
|
|
||||||
// ConsumeDoDefault for now. This can be changed later if needed.
|
|
||||||
return nsEventStatus_eConsumeDoDefault;
|
|
||||||
}
|
|
||||||
|
|
||||||
TouchBlockState* block = nullptr;
|
|
||||||
if (aEvent.AsMultiTouchInput().mType == MultiTouchInput::MULTITOUCH_START) {
|
|
||||||
block = StartNewTouchBlock(false);
|
|
||||||
APZC_LOG("%p started new touch block %p\n", this, block);
|
|
||||||
|
|
||||||
// We want to cancel animations here as soon as possible (i.e. without waiting for
|
|
||||||
// content responses) because a finger has gone down and we don't want to keep moving
|
|
||||||
// the content under the finger. However, to prevent "future" touchstart events from
|
|
||||||
// interfering with "past" animations (i.e. from a previous touch block that is still
|
|
||||||
// being processed) we only do this animation-cancellation if there are no older
|
|
||||||
// touch blocks still in the queue.
|
|
||||||
if (block == CurrentTouchBlock()) {
|
|
||||||
if (block->GetOverscrollHandoffChain()->HasFastMovingApzc()) {
|
|
||||||
// If we're already in a fast fling, then we want the touch event to stop the fling
|
|
||||||
// and to disallow the touch event from being used as part of a fling.
|
|
||||||
block->DisallowSingleTap();
|
|
||||||
}
|
|
||||||
block->GetOverscrollHandoffChain()->CancelAnimations();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (NeedToWaitForContent()) {
|
|
||||||
// Content may intercept the touch events and prevent-default them. So we schedule
|
|
||||||
// a timeout to give content time to do that.
|
|
||||||
ScheduleContentResponseTimeout();
|
|
||||||
} else {
|
|
||||||
// Content won't prevent-default this, so we can just pretend like we scheduled
|
|
||||||
// a timeout and it expired. Note that we will still receive a ContentReceivedTouch
|
|
||||||
// callback for this block, and so we need to make sure we adjust the touch balance.
|
|
||||||
APZC_LOG("%p not waiting for content response on block %p\n", this, block);
|
|
||||||
mTouchBlockBalance++;
|
|
||||||
block->TimeoutContentResponse();
|
|
||||||
}
|
|
||||||
} else if (mTouchBlockQueue.IsEmpty()) {
|
|
||||||
NS_WARNING("Received a non-start touch event while no touch blocks active!");
|
|
||||||
} else {
|
|
||||||
// this touch is part of the most-recently created block
|
|
||||||
block = mTouchBlockQueue.LastElement().get();
|
|
||||||
APZC_LOG("%p received new event in block %p\n", this, block);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!block) {
|
|
||||||
return nsEventStatus_eIgnore;
|
|
||||||
}
|
|
||||||
|
|
||||||
nsEventStatus result = ArePointerEventsConsumable(block, aEvent.AsMultiTouchInput().mTouches.Length())
|
|
||||||
? nsEventStatus_eConsumeDoDefault
|
|
||||||
: nsEventStatus_eIgnore;
|
|
||||||
|
|
||||||
if (block == CurrentTouchBlock() && block->IsReadyForHandling()) {
|
|
||||||
APZC_LOG("%p's current touch block is ready with preventdefault %d\n",
|
|
||||||
this, block->IsDefaultPrevented());
|
|
||||||
if (block->IsDefaultPrevented()) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
HandleInputEvent(aEvent);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, add it to the queue for the touch block
|
|
||||||
block->AddEvent(aEvent.AsMultiTouchInput());
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
nsEventStatus AsyncPanZoomController::HandleInputEvent(const InputData& aEvent) {
|
nsEventStatus AsyncPanZoomController::HandleInputEvent(const InputData& aEvent) {
|
||||||
@ -1534,7 +1472,7 @@ nsEventStatus AsyncPanZoomController::OnPanBegin(const PanGestureInput& aEvent)
|
|||||||
CancelAnimation();
|
CancelAnimation();
|
||||||
}
|
}
|
||||||
|
|
||||||
mPanGestureState = MakeUnique<InputBlockState>(BuildOverscrollHandoffChain());
|
mPanGestureState = MakeUnique<InputBlockState>(this);
|
||||||
|
|
||||||
mX.StartTouch(aEvent.mPanStartPoint.x, aEvent.mTime);
|
mX.StartTouch(aEvent.mPanStartPoint.x, aEvent.mTime);
|
||||||
mY.StartTouch(aEvent.mPanStartPoint.y, aEvent.mTime);
|
mY.StartTouch(aEvent.mPanStartPoint.y, aEvent.mTime);
|
||||||
@ -1613,7 +1551,7 @@ nsEventStatus AsyncPanZoomController::OnPanMomentumStart(const PanGestureInput&
|
|||||||
CancelAnimation();
|
CancelAnimation();
|
||||||
}
|
}
|
||||||
|
|
||||||
mPanGestureState = MakeUnique<InputBlockState>(BuildOverscrollHandoffChain());
|
mPanGestureState = MakeUnique<InputBlockState>(this);
|
||||||
|
|
||||||
return nsEventStatus_eConsumeNoDefault;
|
return nsEventStatus_eConsumeNoDefault;
|
||||||
}
|
}
|
||||||
@ -1641,8 +1579,7 @@ nsEventStatus AsyncPanZoomController::OnLongPress(const TapGestureInput& aEvent)
|
|||||||
int32_t modifiers = WidgetModifiersToDOMModifiers(aEvent.modifiers);
|
int32_t modifiers = WidgetModifiersToDOMModifiers(aEvent.modifiers);
|
||||||
CSSPoint geckoScreenPoint;
|
CSSPoint geckoScreenPoint;
|
||||||
if (ConvertToGecko(aEvent.mPoint, &geckoScreenPoint)) {
|
if (ConvertToGecko(aEvent.mPoint, &geckoScreenPoint)) {
|
||||||
StartNewTouchBlock(true);
|
GetInputQueue()->InjectNewTouchBlock(this);
|
||||||
ScheduleContentResponseTimeout();
|
|
||||||
controller->HandleLongTap(geckoScreenPoint, modifiers, GetGuid());
|
controller->HandleLongTap(geckoScreenPoint, modifiers, GetGuid());
|
||||||
return nsEventStatus_eConsumeNoDefault;
|
return nsEventStatus_eConsumeNoDefault;
|
||||||
}
|
}
|
||||||
@ -2917,115 +2854,14 @@ void AsyncPanZoomController::ZoomToRect(CSSRect aRect) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
|
||||||
AsyncPanZoomController::ScheduleContentResponseTimeout() {
|
|
||||||
APZC_LOG("%p scheduling content response timeout\n", this);
|
|
||||||
PostDelayedTask(
|
|
||||||
NewRunnableMethod(this, &AsyncPanZoomController::ContentResponseTimeout),
|
|
||||||
gfxPrefs::APZContentResponseTimeout());
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
AsyncPanZoomController::ContentResponseTimeout() {
|
|
||||||
AssertOnControllerThread();
|
|
||||||
|
|
||||||
mTouchBlockBalance++;
|
|
||||||
APZC_LOG("%p got a content response timeout; balance %d\n", this, mTouchBlockBalance);
|
|
||||||
if (mTouchBlockBalance > 0) {
|
|
||||||
// Find the first touch block in the queue that hasn't already received
|
|
||||||
// the content response timeout callback, and notify it.
|
|
||||||
bool found = false;
|
|
||||||
for (size_t i = 0; i < mTouchBlockQueue.Length(); i++) {
|
|
||||||
if (mTouchBlockQueue[i]->TimeoutContentResponse()) {
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (found) {
|
|
||||||
ProcessPendingInputBlocks();
|
|
||||||
} else {
|
|
||||||
NS_WARNING("APZC received more ContentResponseTimeout calls than it has unprocessed touch blocks\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
AsyncPanZoomController::ContentReceivedTouch(bool aPreventDefault) {
|
AsyncPanZoomController::ContentReceivedTouch(bool aPreventDefault) {
|
||||||
AssertOnControllerThread();
|
GetInputQueue()->ContentReceivedTouch(aPreventDefault);
|
||||||
|
|
||||||
mTouchBlockBalance--;
|
|
||||||
APZC_LOG("%p got a content response; balance %d\n", this, mTouchBlockBalance);
|
|
||||||
if (mTouchBlockBalance < 0) {
|
|
||||||
// Find the first touch block in the queue that hasn't already received
|
|
||||||
// its response from content, and notify it.
|
|
||||||
bool found = false;
|
|
||||||
for (size_t i = 0; i < mTouchBlockQueue.Length(); i++) {
|
|
||||||
if (mTouchBlockQueue[i]->SetContentResponse(aPreventDefault)) {
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (found) {
|
|
||||||
ProcessPendingInputBlocks();
|
|
||||||
} else {
|
|
||||||
NS_WARNING("APZC received more ContentReceivedTouch calls than it has unprocessed touch blocks\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
AsyncPanZoomController::SetAllowedTouchBehavior(const nsTArray<TouchBehaviorFlags>& aBehaviors) {
|
AsyncPanZoomController::SetAllowedTouchBehavior(const nsTArray<TouchBehaviorFlags>& aBehaviors) {
|
||||||
AssertOnControllerThread();
|
GetInputQueue()->SetAllowedTouchBehavior(aBehaviors);
|
||||||
|
|
||||||
bool found = false;
|
|
||||||
for (size_t i = 0; i < mTouchBlockQueue.Length(); i++) {
|
|
||||||
if (mTouchBlockQueue[i]->SetAllowedTouchBehaviors(aBehaviors)) {
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (found) {
|
|
||||||
ProcessPendingInputBlocks();
|
|
||||||
} else {
|
|
||||||
NS_WARNING("APZC received more SetAllowedTouchBehavior calls than it has unprocessed touch blocks\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
AsyncPanZoomController::ProcessPendingInputBlocks() {
|
|
||||||
AssertOnControllerThread();
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
TouchBlockState* curBlock = CurrentTouchBlock();
|
|
||||||
if (!curBlock->IsReadyForHandling()) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
APZC_LOG("%p processing input block %p; preventDefault %d\n",
|
|
||||||
this, curBlock, curBlock->IsDefaultPrevented());
|
|
||||||
if (curBlock->IsDefaultPrevented()) {
|
|
||||||
curBlock->DropEvents();
|
|
||||||
ResetInputState();
|
|
||||||
} else {
|
|
||||||
while (curBlock->HasEvents()) {
|
|
||||||
HandleInputEvent(curBlock->RemoveFirstEvent());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MOZ_ASSERT(!curBlock->HasEvents());
|
|
||||||
|
|
||||||
if (mTouchBlockQueue.Length() == 1) {
|
|
||||||
// If |curBlock| is the only touch block in the queue, then it is still
|
|
||||||
// active and we cannot remove it yet. We only know that a touch block is
|
|
||||||
// over when we start the next one. This block will be removed by the code
|
|
||||||
// in StartNewTouchBlock, where new touch blocks are added.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
APZC_LOG("%p discarding depleted touch block %p\n", this, curBlock);
|
|
||||||
mTouchBlockQueue.RemoveElementAt(0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
@ -3034,6 +2870,12 @@ AsyncPanZoomController::NeedToWaitForContent() const
|
|||||||
return (mFrameMetrics.GetMayHaveTouchListeners() || mFrameMetrics.GetMayHaveTouchCaret());
|
return (mFrameMetrics.GetMayHaveTouchListeners() || mFrameMetrics.GetMayHaveTouchCaret());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TouchBlockState*
|
||||||
|
AsyncPanZoomController::CurrentTouchBlock()
|
||||||
|
{
|
||||||
|
return GetInputQueue()->CurrentTouchBlock();
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
AsyncPanZoomController::ResetInputState()
|
AsyncPanZoomController::ResetInputState()
|
||||||
{
|
{
|
||||||
@ -3046,43 +2888,10 @@ AsyncPanZoomController::ResetInputState()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TouchBlockState*
|
|
||||||
AsyncPanZoomController::StartNewTouchBlock(bool aCopyAllowedTouchBehaviorFromCurrent)
|
|
||||||
{
|
|
||||||
TouchBlockState* newBlock = new TouchBlockState(BuildOverscrollHandoffChain());
|
|
||||||
if (gfxPrefs::TouchActionEnabled() && aCopyAllowedTouchBehaviorFromCurrent) {
|
|
||||||
newBlock->CopyAllowedTouchBehaviorsFrom(*CurrentTouchBlock());
|
|
||||||
}
|
|
||||||
|
|
||||||
// We're going to start a new block, so clear out any depleted blocks at the head of the queue.
|
|
||||||
// See corresponding comment in ProcessPendingInputBlocks.
|
|
||||||
while (!mTouchBlockQueue.IsEmpty()) {
|
|
||||||
if (mTouchBlockQueue[0]->IsReadyForHandling() && !mTouchBlockQueue[0]->HasEvents()) {
|
|
||||||
APZC_LOG("%p discarding depleted touch block %p\n", this, mTouchBlockQueue[0].get());
|
|
||||||
mTouchBlockQueue.RemoveElementAt(0);
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the new block to the queue.
|
|
||||||
mTouchBlockQueue.AppendElement(newBlock);
|
|
||||||
return newBlock;
|
|
||||||
}
|
|
||||||
|
|
||||||
TouchBlockState*
|
|
||||||
AsyncPanZoomController::CurrentTouchBlock()
|
|
||||||
{
|
|
||||||
AssertOnControllerThread();
|
|
||||||
|
|
||||||
MOZ_ASSERT(!mTouchBlockQueue.IsEmpty());
|
|
||||||
return mTouchBlockQueue[0].get();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
bool
|
||||||
AsyncPanZoomController::HasReadyTouchBlock()
|
AsyncPanZoomController::HasReadyTouchBlock()
|
||||||
{
|
{
|
||||||
return !mTouchBlockQueue.IsEmpty() && mTouchBlockQueue[0]->IsReadyForHandling();
|
return GetInputQueue()->HasReadyTouchBlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncPanZoomController::TouchBehaviorFlags
|
AsyncPanZoomController::TouchBehaviorFlags
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
#include "mozilla/Atomics.h"
|
#include "mozilla/Atomics.h"
|
||||||
#include "InputData.h"
|
#include "InputData.h"
|
||||||
#include "Axis.h"
|
#include "Axis.h"
|
||||||
|
#include "InputQueue.h"
|
||||||
#include "TaskThrottler.h"
|
#include "TaskThrottler.h"
|
||||||
#include "mozilla/gfx/Matrix.h"
|
#include "mozilla/gfx/Matrix.h"
|
||||||
#include "nsRegion.h"
|
#include "nsRegion.h"
|
||||||
@ -583,15 +584,6 @@ protected:
|
|||||||
*/
|
*/
|
||||||
void FireAsyncScrollOnTimeout();
|
void FireAsyncScrollOnTimeout();
|
||||||
|
|
||||||
private:
|
|
||||||
/**
|
|
||||||
* Given the number of touch points in an input event and touch block they
|
|
||||||
* belong to, check if the event can result in a panning/zooming behavior.
|
|
||||||
* This is primarily used to figure out when to dispatch the pointercancel
|
|
||||||
* event for the pointer events spec.
|
|
||||||
*/
|
|
||||||
bool ArePointerEventsConsumable(TouchBlockState* aBlock, uint32_t aTouchPoints);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert ScreenPoint relative to this APZC to CSSPoint relative
|
* Convert ScreenPoint relative to this APZC to CSSPoint relative
|
||||||
* to the parent document. This excludes the transient compositor transform.
|
* to the parent document. This excludes the transient compositor transform.
|
||||||
@ -638,6 +630,7 @@ private:
|
|||||||
/* Utility functions that return a addrefed pointer to the corresponding fields. */
|
/* Utility functions that return a addrefed pointer to the corresponding fields. */
|
||||||
already_AddRefed<GeckoContentController> GetGeckoContentController() const;
|
already_AddRefed<GeckoContentController> GetGeckoContentController() const;
|
||||||
already_AddRefed<GestureEventListener> GetGestureEventListener() const;
|
already_AddRefed<GestureEventListener> GetGestureEventListener() const;
|
||||||
|
nsRefPtr<InputQueue> GetInputQueue() const;
|
||||||
|
|
||||||
// If we are sharing our frame metrics with content across processes
|
// If we are sharing our frame metrics with content across processes
|
||||||
bool mSharingFrameMetricsAcrossProcesses;
|
bool mSharingFrameMetricsAcrossProcesses;
|
||||||
@ -781,20 +774,12 @@ private:
|
|||||||
*/
|
*/
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
* This function is invoked by the APZCTreeManager which in turn is invoked
|
* See InputQueue::ContentReceivedTouch
|
||||||
* by the widget when web content decides whether or not it wants to
|
|
||||||
* cancel a block of events. This automatically gets applied to the next
|
|
||||||
* block of events that has not yet been responded to. This function MUST
|
|
||||||
* be invoked exactly once for each touch block.
|
|
||||||
*/
|
*/
|
||||||
void ContentReceivedTouch(bool aPreventDefault);
|
void ContentReceivedTouch(bool aPreventDefault);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets allowed touch behavior for current touch session.
|
* See InputQueue::SetAllowedTouchBehavior
|
||||||
* This method is invoked by the APZCTreeManager which in its turn invoked by
|
|
||||||
* the widget after performing touch-action values retrieving.
|
|
||||||
* Must be called after receiving the TOUCH_START even that started the
|
|
||||||
* touch session.
|
|
||||||
*/
|
*/
|
||||||
void SetAllowedTouchBehavior(const nsTArray<TouchBehaviorFlags>& aBehaviors);
|
void SetAllowedTouchBehavior(const nsTArray<TouchBehaviorFlags>& aBehaviors);
|
||||||
|
|
||||||
@ -804,74 +789,29 @@ public:
|
|||||||
*/
|
*/
|
||||||
void FlushRepaintForNewInputBlock();
|
void FlushRepaintForNewInputBlock();
|
||||||
|
|
||||||
private:
|
|
||||||
void ScheduleContentResponseTimeout();
|
|
||||||
void ContentResponseTimeout();
|
|
||||||
/**
|
/**
|
||||||
* Processes any pending input blocks that are ready for processing. There
|
* Given the number of touch points in an input event and touch block they
|
||||||
* must be at least one input block in the queue when this function is called.
|
* belong to, check if the event can result in a panning/zooming behavior.
|
||||||
|
* This is primarily used to figure out when to dispatch the pointercancel
|
||||||
|
* event for the pointer events spec.
|
||||||
|
*/
|
||||||
|
bool ArePointerEventsConsumable(TouchBlockState* aBlock, uint32_t aTouchPoints);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if there are are touch listeners registered on content
|
||||||
|
* scrolled by this APZC.
|
||||||
*/
|
*/
|
||||||
void ProcessPendingInputBlocks();
|
|
||||||
TouchBlockState* StartNewTouchBlock(bool aCopyAllowedTouchBehaviorFromCurrent);
|
|
||||||
TouchBlockState* CurrentTouchBlock();
|
|
||||||
bool HasReadyTouchBlock();
|
|
||||||
bool NeedToWaitForContent() const;
|
bool NeedToWaitForContent() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear internal state relating to input handling.
|
||||||
|
*/
|
||||||
void ResetInputState();
|
void ResetInputState();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// The queue of touch blocks that have not yet been processed by this APZC.
|
nsRefPtr<InputQueue> mInputQueue;
|
||||||
// This member must only be accessed on the controller/UI thread.
|
TouchBlockState* CurrentTouchBlock();
|
||||||
nsTArray<UniquePtr<TouchBlockState>> mTouchBlockQueue;
|
bool HasReadyTouchBlock();
|
||||||
|
|
||||||
// This variable requires some explanation. Strap yourself in.
|
|
||||||
//
|
|
||||||
// For each block of events, we do two things: (1) send the events to gecko and expect
|
|
||||||
// exactly one call to ContentReceivedTouch in return, and (2) kick off a timeout
|
|
||||||
// that triggers in case we don't hear from web content in a timely fashion.
|
|
||||||
// Since events are constantly coming in, we need to be able to handle more than one
|
|
||||||
// block of input events sitting in the queue.
|
|
||||||
//
|
|
||||||
// There are ordering restrictions on events that we can take advantage of, and that
|
|
||||||
// we need to abide by. Blocks of events in the queue will always be in the order that
|
|
||||||
// the user generated them. Responses we get from content will be in the same order as
|
|
||||||
// as the blocks of events in the queue. The timeout callbacks that have been posted
|
|
||||||
// will also fire in the same order as the blocks of events in the queue.
|
|
||||||
// HOWEVER, we may get multiple responses from content interleaved with multiple
|
|
||||||
// timeout expirations, and that interleaving is not predictable.
|
|
||||||
//
|
|
||||||
// Therefore, we need to make sure that for each block of events, we process the queued
|
|
||||||
// events exactly once, either when we get the response from content, or when the
|
|
||||||
// timeout expires (whichever happens first). There is no way to associate the timeout
|
|
||||||
// or response from content with a particular block of events other than via ordering.
|
|
||||||
//
|
|
||||||
// So, what we do to accomplish this is to track a "touch block balance", which is the
|
|
||||||
// number of timeout expirations that have fired, minus the number of content responses
|
|
||||||
// that have been received. (Think "balance" as in teeter-totter balance). This
|
|
||||||
// value is:
|
|
||||||
// - zero when we are in a state where the next content response we expect to receive
|
|
||||||
// and the next timeout expiration we expect to fire both correspond to the next
|
|
||||||
// unprocessed block of events in the queue.
|
|
||||||
// - negative when we are in a state where we have received more content responses than
|
|
||||||
// timeout expirations. This means that the next content repsonse we receive will
|
|
||||||
// correspond to the first unprocessed block, but the next n timeout expirations need
|
|
||||||
// to be ignored as they are for blocks we have already processed. (n is the absolute
|
|
||||||
// value of the balance.)
|
|
||||||
// - positive when we are in a state where we have received more timeout expirations
|
|
||||||
// than content responses. This means that the next timeout expiration that we will
|
|
||||||
// receive will correspond to the first unprocessed block, but the next n content
|
|
||||||
// responses need to be ignored as they are for blocks we have already processed.
|
|
||||||
// (n is the absolute value of the balance.)
|
|
||||||
//
|
|
||||||
// Note that each touch block internally carries flags that indicate whether or not it
|
|
||||||
// has received a content response and/or timeout expiration. However, we cannot rely
|
|
||||||
// on that alone to deliver these notifications to the right input block, because
|
|
||||||
// once an input block has been processed, it can potentially be removed from the queue.
|
|
||||||
// Therefore the information in that block is lost. An alternative approach would
|
|
||||||
// be to keep around those blocks until they have received both the content response
|
|
||||||
// and timeout expiration, but that involves a higher level of memory usage.
|
|
||||||
//
|
|
||||||
// This member must only be accessed on the controller/UI thread.
|
|
||||||
int32_t mTouchBlockBalance;
|
|
||||||
|
|
||||||
|
|
||||||
/* ===================================================================
|
/* ===================================================================
|
||||||
@ -1030,6 +970,27 @@ public:
|
|||||||
*/
|
*/
|
||||||
bool SnapBackIfOverscrolled();
|
bool SnapBackIfOverscrolled();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the chain of APZCs along which scroll will be handed off when
|
||||||
|
* this APZC receives input events.
|
||||||
|
*
|
||||||
|
* Notes on lifetime and const-correctness:
|
||||||
|
* - The returned handoff chain is |const|, to indicate that it cannot be
|
||||||
|
* changed after being built.
|
||||||
|
* - When passing the chain to a function that uses it without storing it,
|
||||||
|
* pass it by reference-to-const (as in |const OverscrollHandoffChain&|).
|
||||||
|
* - When storing the chain, store it by RefPtr-to-const (as in
|
||||||
|
* |nsRefPtr<const OverscrollHandoffChain>|). This ensures the chain is
|
||||||
|
* kept alive. Note that queueing a task that uses the chain as an
|
||||||
|
* argument constitutes storing, as the task may outlive its queuer.
|
||||||
|
* - When passing the chain to a function that will store it, pass it as
|
||||||
|
* |const nsRefPtr<const OverscrollHandoffChain>&|. This allows the
|
||||||
|
* function to copy it into the |nsRefPtr<const OverscrollHandoffChain>|
|
||||||
|
* that will store it, while avoiding an unnecessary copy (and thus
|
||||||
|
* AddRef() and Release()) when passing it.
|
||||||
|
*/
|
||||||
|
nsRefPtr<const OverscrollHandoffChain> BuildOverscrollHandoffChain();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
* A helper function for calling APZCTreeManager::DispatchScroll().
|
* A helper function for calling APZCTreeManager::DispatchScroll().
|
||||||
@ -1056,26 +1017,6 @@ private:
|
|||||||
*/
|
*/
|
||||||
bool OverscrollBy(const ScreenPoint& aOverscroll);
|
bool OverscrollBy(const ScreenPoint& aOverscroll);
|
||||||
|
|
||||||
/**
|
|
||||||
* Build the chain of APZCs along which scroll will be handed off when
|
|
||||||
* this APZC receives input events.
|
|
||||||
*
|
|
||||||
* Notes on lifetime and const-correctness:
|
|
||||||
* - The returned handoff chain is |const|, to indicate that it cannot be
|
|
||||||
* changed after being built.
|
|
||||||
* - When passing the chain to a function that uses it without storing it,
|
|
||||||
* pass it by reference-to-const (as in |const OverscrollHandoffChain&|).
|
|
||||||
* - When storing the chain, store it by RefPtr-to-const (as in
|
|
||||||
* |nsRefPtr<const OverscrollHandoffChain>|). This ensures the chain is
|
|
||||||
* kept alive. Note that queueing a task that uses the chain as an
|
|
||||||
* argument constitutes storing, as the task may outlive its queuer.
|
|
||||||
* - When passing the chain to a function that will store it, pass it as
|
|
||||||
* |const nsRefPtr<const OverscrollHandoffChain>&|. This allows the
|
|
||||||
* function to copy it into the |nsRefPtr<const OverscrollHandoffChain>|
|
|
||||||
* that will store it, while avoiding an unnecessary copy (and thus
|
|
||||||
* AddRef() and Release()) when passing it.
|
|
||||||
*/
|
|
||||||
nsRefPtr<const OverscrollHandoffChain> BuildOverscrollHandoffChain();
|
|
||||||
|
|
||||||
/* ===================================================================
|
/* ===================================================================
|
||||||
* The functions and members in this section are used to maintain the
|
* The functions and members in this section are used to maintain the
|
||||||
|
@ -15,11 +15,18 @@
|
|||||||
namespace mozilla {
|
namespace mozilla {
|
||||||
namespace layers {
|
namespace layers {
|
||||||
|
|
||||||
InputBlockState::InputBlockState(const nsRefPtr<const OverscrollHandoffChain>& aOverscrollHandoffChain)
|
InputBlockState::InputBlockState(const nsRefPtr<AsyncPanZoomController>& aTargetApzc)
|
||||||
: mOverscrollHandoffChain(aOverscrollHandoffChain)
|
: mTargetApzc(aTargetApzc)
|
||||||
{
|
{
|
||||||
// We should never be constructed with a nullptr handoff chain.
|
// We should never be constructed with a nullptr target.
|
||||||
MOZ_ASSERT(mOverscrollHandoffChain);
|
MOZ_ASSERT(mTargetApzc);
|
||||||
|
mOverscrollHandoffChain = mTargetApzc->BuildOverscrollHandoffChain();
|
||||||
|
}
|
||||||
|
|
||||||
|
const nsRefPtr<AsyncPanZoomController>&
|
||||||
|
InputBlockState::GetTargetApzc() const
|
||||||
|
{
|
||||||
|
return mTargetApzc;
|
||||||
}
|
}
|
||||||
|
|
||||||
const nsRefPtr<const OverscrollHandoffChain>&
|
const nsRefPtr<const OverscrollHandoffChain>&
|
||||||
@ -28,8 +35,8 @@ InputBlockState::GetOverscrollHandoffChain() const
|
|||||||
return mOverscrollHandoffChain;
|
return mOverscrollHandoffChain;
|
||||||
}
|
}
|
||||||
|
|
||||||
TouchBlockState::TouchBlockState(const nsRefPtr<const OverscrollHandoffChain>& aOverscrollHandoffChain)
|
TouchBlockState::TouchBlockState(const nsRefPtr<AsyncPanZoomController>& aTargetApzc)
|
||||||
: InputBlockState(aOverscrollHandoffChain)
|
: InputBlockState(aTargetApzc)
|
||||||
, mAllowedTouchBehaviorSet(false)
|
, mAllowedTouchBehaviorSet(false)
|
||||||
, mPreventDefault(false)
|
, mPreventDefault(false)
|
||||||
, mContentResponded(false)
|
, mContentResponded(false)
|
||||||
|
@ -23,10 +23,13 @@ class OverscrollHandoffChain;
|
|||||||
class InputBlockState
|
class InputBlockState
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit InputBlockState(const nsRefPtr<const OverscrollHandoffChain>& aOverscrollHandoffChain);
|
explicit InputBlockState(const nsRefPtr<AsyncPanZoomController>& aTargetApzc);
|
||||||
|
|
||||||
|
const nsRefPtr<AsyncPanZoomController>& GetTargetApzc() const;
|
||||||
const nsRefPtr<const OverscrollHandoffChain>& GetOverscrollHandoffChain() const;
|
const nsRefPtr<const OverscrollHandoffChain>& GetOverscrollHandoffChain() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
nsRefPtr<AsyncPanZoomController> mTargetApzc;
|
||||||
nsRefPtr<const OverscrollHandoffChain> mOverscrollHandoffChain;
|
nsRefPtr<const OverscrollHandoffChain> mOverscrollHandoffChain;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -66,7 +69,7 @@ class TouchBlockState : public InputBlockState
|
|||||||
public:
|
public:
|
||||||
typedef uint32_t TouchBehaviorFlags;
|
typedef uint32_t TouchBehaviorFlags;
|
||||||
|
|
||||||
explicit TouchBlockState(const nsRefPtr<const OverscrollHandoffChain>& aOverscrollHandoffChain);
|
explicit TouchBlockState(const nsRefPtr<AsyncPanZoomController>& aTargetApzc);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Record whether or not content cancelled this block of events.
|
* Record whether or not content cancelled this block of events.
|
||||||
|
263
gfx/layers/apz/src/InputQueue.cpp
Normal file
263
gfx/layers/apz/src/InputQueue.cpp
Normal file
@ -0,0 +1,263 @@
|
|||||||
|
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||||
|
/* vim: set sw=2 ts=8 et 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 http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
#include "InputQueue.h"
|
||||||
|
|
||||||
|
#include "AsyncPanZoomController.h"
|
||||||
|
#include "gfxPrefs.h"
|
||||||
|
#include "InputBlockState.h"
|
||||||
|
#include "OverscrollHandoffState.h"
|
||||||
|
|
||||||
|
#define INPQ_LOG(...)
|
||||||
|
// #define INPQ_LOG(...) printf_stderr("INPQ: " __VA_ARGS__)
|
||||||
|
|
||||||
|
namespace mozilla {
|
||||||
|
namespace layers {
|
||||||
|
|
||||||
|
InputQueue::InputQueue()
|
||||||
|
: mTouchBlockBalance(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
InputQueue::~InputQueue() {
|
||||||
|
mTouchBlockQueue.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
nsEventStatus
|
||||||
|
InputQueue::ReceiveInputEvent(const nsRefPtr<AsyncPanZoomController>& aTarget, const InputData& aEvent) {
|
||||||
|
AsyncPanZoomController::AssertOnControllerThread();
|
||||||
|
|
||||||
|
if (aEvent.mInputType != MULTITOUCH_INPUT) {
|
||||||
|
aTarget->HandleInputEvent(aEvent);
|
||||||
|
// The return value for non-touch input isn't really used, so just return
|
||||||
|
// ConsumeDoDefault for now. This can be changed later if needed.
|
||||||
|
return nsEventStatus_eConsumeDoDefault;
|
||||||
|
}
|
||||||
|
|
||||||
|
TouchBlockState* block = nullptr;
|
||||||
|
if (aEvent.AsMultiTouchInput().mType == MultiTouchInput::MULTITOUCH_START) {
|
||||||
|
block = StartNewTouchBlock(aTarget, false);
|
||||||
|
INPQ_LOG("%p started new touch block %p for target %p\n", this, block, aTarget.get());
|
||||||
|
|
||||||
|
// We want to cancel animations here as soon as possible (i.e. without waiting for
|
||||||
|
// content responses) because a finger has gone down and we don't want to keep moving
|
||||||
|
// the content under the finger. However, to prevent "future" touchstart events from
|
||||||
|
// interfering with "past" animations (i.e. from a previous touch block that is still
|
||||||
|
// being processed) we only do this animation-cancellation if there are no older
|
||||||
|
// touch blocks still in the queue.
|
||||||
|
if (block == CurrentTouchBlock()) {
|
||||||
|
if (block->GetOverscrollHandoffChain()->HasFastMovingApzc()) {
|
||||||
|
// If we're already in a fast fling, then we want the touch event to stop the fling
|
||||||
|
// and to disallow the touch event from being used as part of a fling.
|
||||||
|
block->DisallowSingleTap();
|
||||||
|
}
|
||||||
|
block->GetOverscrollHandoffChain()->CancelAnimations();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aTarget->NeedToWaitForContent()) {
|
||||||
|
// Content may intercept the touch events and prevent-default them. So we schedule
|
||||||
|
// a timeout to give content time to do that.
|
||||||
|
ScheduleContentResponseTimeout(aTarget);
|
||||||
|
} else {
|
||||||
|
// Content won't prevent-default this, so we can just pretend like we scheduled
|
||||||
|
// a timeout and it expired. Note that we will still receive a ContentReceivedTouch
|
||||||
|
// callback for this block, and so we need to make sure we adjust the touch balance.
|
||||||
|
INPQ_LOG("%p not waiting for content response on block %p\n", this, block);
|
||||||
|
mTouchBlockBalance++;
|
||||||
|
block->TimeoutContentResponse();
|
||||||
|
}
|
||||||
|
} else if (mTouchBlockQueue.IsEmpty()) {
|
||||||
|
NS_WARNING("Received a non-start touch event while no touch blocks active!");
|
||||||
|
} else {
|
||||||
|
// this touch is part of the most-recently created block
|
||||||
|
block = mTouchBlockQueue.LastElement().get();
|
||||||
|
INPQ_LOG("%p received new event in block %p\n", this, block);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!block) {
|
||||||
|
return nsEventStatus_eIgnore;
|
||||||
|
}
|
||||||
|
|
||||||
|
nsEventStatus result = aTarget->ArePointerEventsConsumable(block, aEvent.AsMultiTouchInput().mTouches.Length())
|
||||||
|
? nsEventStatus_eConsumeDoDefault
|
||||||
|
: nsEventStatus_eIgnore;
|
||||||
|
|
||||||
|
if (block == CurrentTouchBlock() && block->IsReadyForHandling()) {
|
||||||
|
INPQ_LOG("%p's current touch block is ready with preventdefault %d\n",
|
||||||
|
this, block->IsDefaultPrevented());
|
||||||
|
if (block->IsDefaultPrevented()) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
aTarget->HandleInputEvent(aEvent);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, add it to the queue for the touch block
|
||||||
|
block->AddEvent(aEvent.AsMultiTouchInput());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
InputQueue::InjectNewTouchBlock(AsyncPanZoomController* aTarget)
|
||||||
|
{
|
||||||
|
StartNewTouchBlock(aTarget, true);
|
||||||
|
ScheduleContentResponseTimeout(aTarget);
|
||||||
|
}
|
||||||
|
|
||||||
|
TouchBlockState*
|
||||||
|
InputQueue::StartNewTouchBlock(const nsRefPtr<AsyncPanZoomController>& aTarget, bool aCopyAllowedTouchBehaviorFromCurrent)
|
||||||
|
{
|
||||||
|
TouchBlockState* newBlock = new TouchBlockState(aTarget);
|
||||||
|
if (gfxPrefs::TouchActionEnabled() && aCopyAllowedTouchBehaviorFromCurrent) {
|
||||||
|
newBlock->CopyAllowedTouchBehaviorsFrom(*CurrentTouchBlock());
|
||||||
|
}
|
||||||
|
|
||||||
|
// We're going to start a new block, so clear out any depleted blocks at the head of the queue.
|
||||||
|
// See corresponding comment in ProcessPendingInputBlocks.
|
||||||
|
while (!mTouchBlockQueue.IsEmpty()) {
|
||||||
|
if (mTouchBlockQueue[0]->IsReadyForHandling() && !mTouchBlockQueue[0]->HasEvents()) {
|
||||||
|
INPQ_LOG("%p discarding depleted touch block %p\n", this, mTouchBlockQueue[0].get());
|
||||||
|
mTouchBlockQueue.RemoveElementAt(0);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the new block to the queue.
|
||||||
|
mTouchBlockQueue.AppendElement(newBlock);
|
||||||
|
return newBlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
TouchBlockState*
|
||||||
|
InputQueue::CurrentTouchBlock() const
|
||||||
|
{
|
||||||
|
AsyncPanZoomController::AssertOnControllerThread();
|
||||||
|
|
||||||
|
MOZ_ASSERT(!mTouchBlockQueue.IsEmpty());
|
||||||
|
return mTouchBlockQueue[0].get();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
InputQueue::HasReadyTouchBlock() const
|
||||||
|
{
|
||||||
|
return !mTouchBlockQueue.IsEmpty() && mTouchBlockQueue[0]->IsReadyForHandling();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
InputQueue::ScheduleContentResponseTimeout(const nsRefPtr<AsyncPanZoomController>& aTarget) {
|
||||||
|
INPQ_LOG("%p scheduling content response timeout for target %p\n", this, aTarget.get());
|
||||||
|
aTarget->PostDelayedTask(
|
||||||
|
NewRunnableMethod(this, &InputQueue::ContentResponseTimeout),
|
||||||
|
gfxPrefs::APZContentResponseTimeout());
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
InputQueue::ContentResponseTimeout() {
|
||||||
|
AsyncPanZoomController::AssertOnControllerThread();
|
||||||
|
|
||||||
|
mTouchBlockBalance++;
|
||||||
|
INPQ_LOG("%p got a content response timeout; balance %d\n", this, mTouchBlockBalance);
|
||||||
|
if (mTouchBlockBalance > 0) {
|
||||||
|
// Find the first touch block in the queue that hasn't already received
|
||||||
|
// the content response timeout callback, and notify it.
|
||||||
|
bool found = false;
|
||||||
|
for (size_t i = 0; i < mTouchBlockQueue.Length(); i++) {
|
||||||
|
if (mTouchBlockQueue[i]->TimeoutContentResponse()) {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (found) {
|
||||||
|
ProcessPendingInputBlocks();
|
||||||
|
} else {
|
||||||
|
NS_WARNING("INPQ received more ContentResponseTimeout calls than it has unprocessed touch blocks\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
InputQueue::ContentReceivedTouch(bool aPreventDefault) {
|
||||||
|
AsyncPanZoomController::AssertOnControllerThread();
|
||||||
|
|
||||||
|
mTouchBlockBalance--;
|
||||||
|
INPQ_LOG("%p got a content response; balance %d\n", this, mTouchBlockBalance);
|
||||||
|
if (mTouchBlockBalance < 0) {
|
||||||
|
// Find the first touch block in the queue that hasn't already received
|
||||||
|
// its response from content, and notify it.
|
||||||
|
bool found = false;
|
||||||
|
for (size_t i = 0; i < mTouchBlockQueue.Length(); i++) {
|
||||||
|
if (mTouchBlockQueue[i]->SetContentResponse(aPreventDefault)) {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (found) {
|
||||||
|
ProcessPendingInputBlocks();
|
||||||
|
} else {
|
||||||
|
NS_WARNING("INPQ received more ContentReceivedTouch calls than it has unprocessed touch blocks\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
InputQueue::SetAllowedTouchBehavior(const nsTArray<TouchBehaviorFlags>& aBehaviors) {
|
||||||
|
AsyncPanZoomController::AssertOnControllerThread();
|
||||||
|
|
||||||
|
bool found = false;
|
||||||
|
for (size_t i = 0; i < mTouchBlockQueue.Length(); i++) {
|
||||||
|
if (mTouchBlockQueue[i]->SetAllowedTouchBehaviors(aBehaviors)) {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (found) {
|
||||||
|
ProcessPendingInputBlocks();
|
||||||
|
} else {
|
||||||
|
NS_WARNING("INPQ received more SetAllowedTouchBehavior calls than it has unprocessed touch blocks\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
InputQueue::ProcessPendingInputBlocks() {
|
||||||
|
AsyncPanZoomController::AssertOnControllerThread();
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
TouchBlockState* curBlock = CurrentTouchBlock();
|
||||||
|
if (!curBlock->IsReadyForHandling()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
INPQ_LOG("%p processing input block %p; preventDefault %d target %p\n",
|
||||||
|
this, curBlock, curBlock->IsDefaultPrevented(),
|
||||||
|
curBlock->GetTargetApzc().get());
|
||||||
|
nsRefPtr<AsyncPanZoomController> target = curBlock->GetTargetApzc();
|
||||||
|
if (curBlock->IsDefaultPrevented()) {
|
||||||
|
curBlock->DropEvents();
|
||||||
|
target->ResetInputState();
|
||||||
|
} else {
|
||||||
|
while (curBlock->HasEvents()) {
|
||||||
|
target->HandleInputEvent(curBlock->RemoveFirstEvent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MOZ_ASSERT(!curBlock->HasEvents());
|
||||||
|
|
||||||
|
if (mTouchBlockQueue.Length() == 1) {
|
||||||
|
// If |curBlock| is the only touch block in the queue, then it is still
|
||||||
|
// active and we cannot remove it yet. We only know that a touch block is
|
||||||
|
// over when we start the next one. This block will be removed by the code
|
||||||
|
// in StartNewTouchBlock, where new touch blocks are added.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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("%p discarding depleted touch block %p\n", this, curBlock);
|
||||||
|
mTouchBlockQueue.RemoveElementAt(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace layers
|
||||||
|
} // namespace mozilla
|
147
gfx/layers/apz/src/InputQueue.h
Normal file
147
gfx/layers/apz/src/InputQueue.h
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||||
|
/* 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 http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
#ifndef mozilla_layers_InputQueue_h
|
||||||
|
#define mozilla_layers_InputQueue_h
|
||||||
|
|
||||||
|
#include "mozilla/EventForwards.h"
|
||||||
|
#include "mozilla/UniquePtr.h"
|
||||||
|
#include "nsAutoPtr.h"
|
||||||
|
#include "nsTArray.h"
|
||||||
|
|
||||||
|
namespace mozilla {
|
||||||
|
|
||||||
|
class InputData;
|
||||||
|
|
||||||
|
namespace layers {
|
||||||
|
|
||||||
|
class AsyncPanZoomController;
|
||||||
|
class OverscrollHandoffChain;
|
||||||
|
class TouchBlockState;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class stores incoming input events, separated into "input blocks", until
|
||||||
|
* they are ready for handling. Currently input blocks are only created from
|
||||||
|
* touch input.
|
||||||
|
*/
|
||||||
|
class InputQueue {
|
||||||
|
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(InputQueue)
|
||||||
|
|
||||||
|
public:
|
||||||
|
typedef uint32_t TouchBehaviorFlags;
|
||||||
|
|
||||||
|
public:
|
||||||
|
InputQueue();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies the InputQueue of a new incoming input event. The APZC that the
|
||||||
|
* input event was targeted to should be provided in the |aTarget| parameter.
|
||||||
|
* See the documentation on APZCTreeManager::ReceiveInputEvent for info on
|
||||||
|
* return values from this function.
|
||||||
|
*/
|
||||||
|
nsEventStatus ReceiveInputEvent(const nsRefPtr<AsyncPanZoomController>& aTarget, const InputData& aEvent);
|
||||||
|
/**
|
||||||
|
* This function should be invoked to notify the InputQueue when web content
|
||||||
|
* decides whether or not it wants to cancel a block of events. This
|
||||||
|
* automatically gets applied to the next block of events that has not yet
|
||||||
|
* been responded to. This function MUST be invoked exactly once for each
|
||||||
|
* touch block, after the touch-start event that creates the block is sent to
|
||||||
|
* ReceiveInputEvent.
|
||||||
|
*/
|
||||||
|
void ContentReceivedTouch(bool aPreventDefault);
|
||||||
|
/**
|
||||||
|
* This function should be invoked to notify the InputQueue of the touch-
|
||||||
|
* action properties for the different touch points in an input block. This
|
||||||
|
* automatically gets applied to the next block of events that has not yet
|
||||||
|
* received a touch behaviour notification. This function MUST be invoked
|
||||||
|
* exactly once for each touch block, after the touch-start event that creates
|
||||||
|
* the block is sent to ReceiveInputEvent. If touch-action is not enabled on
|
||||||
|
* the platform, this function does nothing and need not be called.
|
||||||
|
*/
|
||||||
|
void SetAllowedTouchBehavior(const nsTArray<TouchBehaviorFlags>& aBehaviors);
|
||||||
|
/**
|
||||||
|
* Adds a new touch block at the end of the input queue that has the same
|
||||||
|
* allowed touch behaviour flags as the the touch block currently being
|
||||||
|
* processed. This should only be called when processing of a touch block
|
||||||
|
* triggers the creation of a new touch block.
|
||||||
|
*/
|
||||||
|
void InjectNewTouchBlock(AsyncPanZoomController* aTarget);
|
||||||
|
/**
|
||||||
|
* Returns the touch block at the head of the queue.
|
||||||
|
*/
|
||||||
|
TouchBlockState* CurrentTouchBlock() const;
|
||||||
|
/**
|
||||||
|
* Returns true iff the touch block at the head of the queue is ready for
|
||||||
|
* handling.
|
||||||
|
*/
|
||||||
|
bool HasReadyTouchBlock() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
~InputQueue();
|
||||||
|
TouchBlockState* StartNewTouchBlock(const nsRefPtr<AsyncPanZoomController>& aTarget, bool aCopyAllowedTouchBehaviorFromCurrent);
|
||||||
|
void ScheduleContentResponseTimeout(const nsRefPtr<AsyncPanZoomController>& aTarget);
|
||||||
|
void ContentResponseTimeout();
|
||||||
|
void ProcessPendingInputBlocks();
|
||||||
|
|
||||||
|
private:
|
||||||
|
// The queue of touch blocks that have not yet been processed.
|
||||||
|
// This member must only be accessed on the controller/UI thread.
|
||||||
|
nsTArray<UniquePtr<TouchBlockState>> mTouchBlockQueue;
|
||||||
|
|
||||||
|
// This variable requires some explanation. Strap yourself in.
|
||||||
|
//
|
||||||
|
// For each block of events, we do two things: (1) send the events to gecko and expect
|
||||||
|
// exactly one call to ContentReceivedTouch in return, and (2) kick off a timeout
|
||||||
|
// that triggers in case we don't hear from web content in a timely fashion.
|
||||||
|
// Since events are constantly coming in, we need to be able to handle more than one
|
||||||
|
// block of input events sitting in the queue.
|
||||||
|
//
|
||||||
|
// There are ordering restrictions on events that we can take advantage of, and that
|
||||||
|
// we need to abide by. Blocks of events in the queue will always be in the order that
|
||||||
|
// the user generated them. Responses we get from content will be in the same order as
|
||||||
|
// as the blocks of events in the queue. The timeout callbacks that have been posted
|
||||||
|
// will also fire in the same order as the blocks of events in the queue.
|
||||||
|
// HOWEVER, we may get multiple responses from content interleaved with multiple
|
||||||
|
// timeout expirations, and that interleaving is not predictable.
|
||||||
|
//
|
||||||
|
// Therefore, we need to make sure that for each block of events, we process the queued
|
||||||
|
// events exactly once, either when we get the response from content, or when the
|
||||||
|
// timeout expires (whichever happens first). There is no way to associate the timeout
|
||||||
|
// or response from content with a particular block of events other than via ordering.
|
||||||
|
//
|
||||||
|
// So, what we do to accomplish this is to track a "touch block balance", which is the
|
||||||
|
// number of timeout expirations that have fired, minus the number of content responses
|
||||||
|
// that have been received. (Think "balance" as in teeter-totter balance). This
|
||||||
|
// value is:
|
||||||
|
// - zero when we are in a state where the next content response we expect to receive
|
||||||
|
// and the next timeout expiration we expect to fire both correspond to the next
|
||||||
|
// unprocessed block of events in the queue.
|
||||||
|
// - negative when we are in a state where we have received more content responses than
|
||||||
|
// timeout expirations. This means that the next content repsonse we receive will
|
||||||
|
// correspond to the first unprocessed block, but the next n timeout expirations need
|
||||||
|
// to be ignored as they are for blocks we have already processed. (n is the absolute
|
||||||
|
// value of the balance.)
|
||||||
|
// - positive when we are in a state where we have received more timeout expirations
|
||||||
|
// than content responses. This means that the next timeout expiration that we will
|
||||||
|
// receive will correspond to the first unprocessed block, but the next n content
|
||||||
|
// responses need to be ignored as they are for blocks we have already processed.
|
||||||
|
// (n is the absolute value of the balance.)
|
||||||
|
//
|
||||||
|
// Note that each touch block internally carries flags that indicate whether or not it
|
||||||
|
// has received a content response and/or timeout expiration. However, we cannot rely
|
||||||
|
// on that alone to deliver these notifications to the right input block, because
|
||||||
|
// once an input block has been processed, it can potentially be removed from the queue.
|
||||||
|
// Therefore the information in that block is lost. An alternative approach would
|
||||||
|
// be to keep around those blocks until they have received both the content response
|
||||||
|
// and timeout expiration, but that involves a higher level of memory usage.
|
||||||
|
//
|
||||||
|
// This member must only be accessed on the controller/UI thread.
|
||||||
|
int32_t mTouchBlockBalance;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // mozilla_layers_InputQueue_h
|
@ -240,6 +240,7 @@ UNIFIED_SOURCES += [
|
|||||||
'apz/src/Axis.cpp',
|
'apz/src/Axis.cpp',
|
||||||
'apz/src/GestureEventListener.cpp',
|
'apz/src/GestureEventListener.cpp',
|
||||||
'apz/src/InputBlockState.cpp',
|
'apz/src/InputBlockState.cpp',
|
||||||
|
'apz/src/InputQueue.cpp',
|
||||||
'apz/src/OverscrollHandoffState.cpp',
|
'apz/src/OverscrollHandoffState.cpp',
|
||||||
'apz/src/TaskThrottler.cpp',
|
'apz/src/TaskThrottler.cpp',
|
||||||
'apz/testutil/APZTestData.cpp',
|
'apz/testutil/APZTestData.cpp',
|
||||||
|
Loading…
Reference in New Issue
Block a user