Refactor InputQueue to hold more than touch events. (bug 1013432 part 2, r=kats)

--HG--
extra : rebase_source : cd3691a2bda6aaf315cf3b844e4fdd3aa8b30334
This commit is contained in:
David Anderson 2014-12-09 02:34:27 -08:00
Родитель fd05150dca
Коммит d2831e46f4
4 изменённых файлов: 280 добавлений и 130 удалений

Просмотреть файл

@ -220,6 +220,18 @@ TouchBlockState::AddEvent(const MultiTouchInput& aEvent)
mEvents.AppendElement(aEvent);
}
bool
TouchBlockState::MustStayActive()
{
return true;
}
const char*
TouchBlockState::Type()
{
return "touch";
}
void
TouchBlockState::DropEvents()
{
@ -227,6 +239,14 @@ TouchBlockState::DropEvents()
mEvents.Clear();
}
void
TouchBlockState::HandleEvents(const nsRefPtr<AsyncPanZoomController>& aTarget)
{
while (HasEvents()) {
aTarget->HandleInputEvent(RemoveFirstEvent());
}
}
MultiTouchInput
TouchBlockState::RemoveFirstEvent()
{

Просмотреть файл

@ -16,6 +16,9 @@ namespace layers {
class AsyncPanZoomController;
class OverscrollHandoffChain;
class CancelableBlockState;
class TouchBlockState;
class WheelBlockState;
/**
* A base class that stores state common to various input blocks.
@ -28,14 +31,16 @@ public:
explicit InputBlockState(const nsRefPtr<AsyncPanZoomController>& aTargetApzc,
bool aTargetConfirmed);
virtual ~InputBlockState()
{}
bool SetConfirmedTargetApzc(const nsRefPtr<AsyncPanZoomController>& aTargetApzc);
const nsRefPtr<AsyncPanZoomController>& GetTargetApzc() const;
const nsRefPtr<const OverscrollHandoffChain>& GetOverscrollHandoffChain() const;
uint64_t GetBlockId() const;
protected:
bool IsTargetConfirmed() const;
private:
nsRefPtr<AsyncPanZoomController> mTargetApzc;
nsRefPtr<const OverscrollHandoffChain> mOverscrollHandoffChain;
@ -61,6 +66,10 @@ public:
CancelableBlockState(const nsRefPtr<AsyncPanZoomController>& aTargetApzc,
bool aTargetConfirmed);
virtual TouchBlockState *AsTouchBlock() {
return nullptr;
}
/**
* Record whether or not content cancelled this block of events.
* @param aPreventDefault true iff the block is cancelled.
@ -81,10 +90,37 @@ public:
bool IsDefaultPrevented() const;
/**
* @returns true if web content responded or timed out.
* @return true iff this block has received all the information needed
* to properly dispatch the events in the block.
*/
virtual bool IsReadyForHandling() const;
/**
* Returns whether or not this block has pending events.
*/
virtual bool HasEvents() const = 0;
/**
* Throw away all the events in this input block.
*/
virtual void DropEvents() = 0;
/**
* Process all events given an apzc, leaving ths block depleted.
*/
virtual void HandleEvents(const nsRefPtr<AsyncPanZoomController>& aTarget) = 0;
/**
* Return true if this input block must stay active if it would otherwise
* be removed as the last item in the pending queue.
*/
virtual bool MustStayActive() = 0;
/**
* Return a descriptive name for the block kind.
*/
virtual const char* Type() = 0;
private:
bool mPreventDefault;
bool mContentResponded;
@ -122,6 +158,10 @@ public:
explicit TouchBlockState(const nsRefPtr<AsyncPanZoomController>& aTargetApzc,
bool aTargetConfirmed);
TouchBlockState *AsTouchBlock() MOZ_OVERRIDE {
return this;
}
/**
* Set the allowed touch behavior flags for this block.
* @return false if this block already has these flags set, true if not.
@ -161,23 +201,10 @@ public:
*/
bool SingleTapOccurred() const;
/**
* @return true iff there are pending events in this touch block.
*/
bool HasEvents() const;
/**
* Add a new touch event to the queue of events in this input block.
*/
void AddEvent(const MultiTouchInput& aEvent);
/**
* Throw away all the events in this input block.
*/
void DropEvents();
/**
* @return the first event in the queue. The event is removed from the queue
* before it is returned.
*/
MultiTouchInput RemoveFirstEvent();
/**
* @return false iff touch-action is enabled and the allowed touch behaviors for
@ -197,6 +224,19 @@ public:
bool TouchActionAllowsPanningY() const;
bool TouchActionAllowsPanningXY() const;
bool HasEvents() const MOZ_OVERRIDE;
void DropEvents() MOZ_OVERRIDE;
void HandleEvents(const nsRefPtr<AsyncPanZoomController>& aTarget) MOZ_OVERRIDE;
bool MustStayActive() MOZ_OVERRIDE;
const char* Type() MOZ_OVERRIDE;
private:
/**
* @return the first event in the queue. The event is removed from the queue
* before it is returned.
*/
MultiTouchInput RemoveFirstEvent();
private:
nsTArray<TouchBehaviorFlags> mAllowedTouchBehaviors;
bool mAllowedTouchBehaviorSet;

Просмотреть файл

@ -23,7 +23,7 @@ InputQueue::InputQueue()
}
InputQueue::~InputQueue() {
mTouchBlockQueue.Clear();
mInputBlockQueue.Clear();
}
nsEventStatus
@ -33,70 +33,62 @@ InputQueue::ReceiveInputEvent(const nsRefPtr<AsyncPanZoomController>& aTarget,
uint64_t* aOutInputBlockId) {
AsyncPanZoomController::AssertOnControllerThread();
if (aEvent.mInputType != MULTITOUCH_INPUT) {
// 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.
// TODO (bug 1098430): we will eventually need to have smarter handling for
// non-touch events as well.
return aTarget->HandleInputEvent(aEvent);
}
switch (aEvent.mInputType) {
case MULTITOUCH_INPUT: {
const MultiTouchInput& event = aEvent.AsMultiTouchInput();
return ReceiveTouchInput(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.
// TODO (bug 1098430): we will eventually need to have smarter handling for
// non-touch events as well.
return aTarget->HandleInputEvent(aEvent);
}
}
bool
InputQueue::MaybeHandleCurrentBlock(const nsRefPtr<AsyncPanZoomController>& aTarget,
CancelableBlockState *block,
const InputData& aEvent) {
if (block == CurrentBlock() && block->IsReadyForHandling()) {
INPQ_LOG("current block is ready with target %p preventdefault %d\n",
aTarget.get(), block->IsDefaultPrevented());
if (!aTarget || block->IsDefaultPrevented()) {
return true;
}
aTarget->HandleInputEvent(aEvent);
return true;
}
return false;
}
nsEventStatus
InputQueue::ReceiveTouchInput(const nsRefPtr<AsyncPanZoomController>& aTarget,
bool aTargetConfirmed,
const MultiTouchInput& aEvent,
uint64_t* aOutInputBlockId) {
TouchBlockState* block = nullptr;
if (aEvent.AsMultiTouchInput().mType == MultiTouchInput::MULTITOUCH_START) {
if (aEvent.mType == MultiTouchInput::MULTITOUCH_START) {
block = StartNewTouchBlock(aTarget, aTargetConfirmed, false);
INPQ_LOG("started new touch block %p for target %p\n", 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()) {
// XXX using the chain from |block| here may be wrong in cases where the
// target isn't confirmed and the real target turns out to be something
// else. For now assume this is rare enough that it's not an issue.
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->SetDuringFastMotion();
INPQ_LOG("block %p tagged as fast-motion\n", block);
}
block->GetOverscrollHandoffChain()->CancelAnimations();
CancelAnimationsForNewBlock(block);
MaybeRequestContentResponse(aTarget, block);
} else {
if (!mInputBlockQueue.IsEmpty()) {
block = mInputBlockQueue.LastElement().get()->AsTouchBlock();
}
bool waitForMainThread = !aTargetConfirmed;
if (!gfxPrefs::LayoutEventRegionsEnabled()) {
waitForMainThread |= aTarget->NeedToWaitForContent();
if (!block) {
NS_WARNING("Received a non-start touch event while no touch blocks active!");
return nsEventStatus_eIgnore;
}
if (block->IsDuringFastMotion()) {
block->SetConfirmedTargetApzc(aTarget);
waitForMainThread = false;
}
if (waitForMainThread) {
// We either don't know for sure if aTarget is the right APZC, or we may
// need to wait to give content the opportunity to prevent-default the
// touch events. Either way we schedule a timeout so the main thread stuff
// can run.
ScheduleMainThreadTimeout(aTarget, block->GetBlockId());
} 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("not waiting for content response on block %p\n", block);
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("received new event in block %p\n", block);
}
if (!block) {
return nsEventStatus_eIgnore;
}
if (aOutInputBlockId) {
*aOutInputBlockId = block->GetBlockId();
}
@ -108,6 +100,7 @@ InputQueue::ReceiveInputEvent(const nsRefPtr<AsyncPanZoomController>& aTarget,
nsRefPtr<AsyncPanZoomController> target = block->GetTargetApzc();
nsEventStatus result = nsEventStatus_eIgnore;
// XXX calling ArePointerEventsConsumable on |target| may be wrong here if
// the target isn't confirmed and the real target turns out to be something
// else. For now assume this is rare enough that it's not an issue.
@ -116,20 +109,58 @@ InputQueue::ReceiveInputEvent(const nsRefPtr<AsyncPanZoomController>& aTarget,
} else if (target && target->ArePointerEventsConsumable(block, aEvent.AsMultiTouchInput().mTouches.Length())) {
result = nsEventStatus_eConsumeDoDefault;
}
if (!MaybeHandleCurrentBlock(target, block, aEvent)) {
block->AddEvent(aEvent.AsMultiTouchInput());
}
return result;
}
if (block == CurrentTouchBlock() && block->IsReadyForHandling()) {
INPQ_LOG("current touch block is ready with target %p preventdefault %d\n",
target.get(), block->IsDefaultPrevented());
if (!target || block->IsDefaultPrevented()) {
return result;
void
InputQueue::CancelAnimationsForNewBlock(CancelableBlockState* aBlock)
{
// 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 (aBlock == CurrentBlock()) {
// XXX using the chain from |block| here may be wrong in cases where the
// target isn't confirmed and the real target turns out to be something
// else. For now assume this is rare enough that it's not an issue.
if (aBlock->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.
if (TouchBlockState* touch = aBlock->AsTouchBlock()) {
touch->SetDuringFastMotion();
}
}
target->HandleInputEvent(aEvent);
return result;
aBlock->GetOverscrollHandoffChain()->CancelAnimations();
}
}
void
InputQueue::MaybeRequestContentResponse(const nsRefPtr<AsyncPanZoomController>& aTarget,
CancelableBlockState* aBlock)
{
bool waitForMainThread = !aBlock->IsTargetConfirmed();
if (!gfxPrefs::LayoutEventRegionsEnabled()) {
waitForMainThread |= aTarget->NeedToWaitForContent();
}
// Otherwise, add it to the queue for the touch block
block->AddEvent(aEvent.AsMultiTouchInput());
return result;
if (waitForMainThread) {
// We either don't know for sure if aTarget is the right APZC, or we may
// need to wait to give content the opportunity to prevent-default the
// touch events. Either way we schedule a timeout so the main thread stuff
// can run.
ScheduleMainThreadTimeout(aTarget, aBlock->GetBlockId());
} 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("not waiting for content response on block %p\n", block);
aBlock->TimeoutContentResponse();
}
}
uint64_t
@ -144,6 +175,22 @@ InputQueue::InjectNewTouchBlock(AsyncPanZoomController* aTarget)
return block->GetBlockId();
}
void
InputQueue::SweepDepletedBlocks()
{
// We're going to start a new block, so clear out any depleted blocks at the head of the queue.
// See corresponding comment in ProcessInputBlocks.
while (!mInputBlockQueue.IsEmpty()) {
CancelableBlockState* block = mInputBlockQueue[0].get();
if (!block->IsReadyForHandling() || block->HasEvents()) {
break;
}
INPQ_LOG("discarding depleted %s block %p\n", block->Type(), block);
mInputBlockQueue.RemoveElementAt(0);
}
}
TouchBlockState*
InputQueue::StartNewTouchBlock(const nsRefPtr<AsyncPanZoomController>& aTarget,
bool aTargetConfirmed,
@ -154,35 +201,36 @@ InputQueue::StartNewTouchBlock(const nsRefPtr<AsyncPanZoomController>& aTarget,
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("discarding depleted touch block %p\n", mTouchBlockQueue[0].get());
mTouchBlockQueue.RemoveElementAt(0);
} else {
break;
}
}
SweepDepletedBlocks();
// Add the new block to the queue.
mTouchBlockQueue.AppendElement(newBlock);
mInputBlockQueue.AppendElement(newBlock);
return newBlock;
}
CancelableBlockState*
InputQueue::CurrentBlock() const
{
AsyncPanZoomController::AssertOnControllerThread();
MOZ_ASSERT(!mInputBlockQueue.IsEmpty());
return mInputBlockQueue[0].get();
}
TouchBlockState*
InputQueue::CurrentTouchBlock() const
{
AsyncPanZoomController::AssertOnControllerThread();
MOZ_ASSERT(!mTouchBlockQueue.IsEmpty());
return mTouchBlockQueue[0].get();
TouchBlockState *block = CurrentBlock()->AsTouchBlock();
MOZ_ASSERT(block);
return block;
}
bool
InputQueue::HasReadyTouchBlock() const
{
return !mTouchBlockQueue.IsEmpty() && mTouchBlockQueue[0]->IsReadyForHandling();
return !mInputBlockQueue.IsEmpty() &&
mInputBlockQueue[0]->AsTouchBlock() &&
mInputBlockQueue[0]->IsReadyForHandling();
}
void
@ -199,18 +247,18 @@ InputQueue::MainThreadTimeout(const uint64_t& aInputBlockId) {
INPQ_LOG("got a main thread timeout; block=%" PRIu64 "\n", aInputBlockId);
bool success = false;
for (size_t i = 0; i < mTouchBlockQueue.Length(); i++) {
if (mTouchBlockQueue[i]->GetBlockId() == aInputBlockId) {
for (size_t i = 0; i < mInputBlockQueue.Length(); i++) {
if (mInputBlockQueue[i]->GetBlockId() == aInputBlockId) {
// time out the touch-listener response and also confirm the existing
// target apzc in the case where the main thread doesn't get back to us
// fast enough.
success = mTouchBlockQueue[i]->TimeoutContentResponse();
success |= mTouchBlockQueue[i]->SetConfirmedTargetApzc(mTouchBlockQueue[i]->GetTargetApzc());
success = mInputBlockQueue[i]->TimeoutContentResponse();
success |= mInputBlockQueue[i]->SetConfirmedTargetApzc(mInputBlockQueue[i]->GetTargetApzc());
break;
}
}
if (success) {
ProcessPendingInputBlocks();
ProcessInputBlocks();
}
}
@ -220,14 +268,15 @@ InputQueue::ContentReceivedTouch(uint64_t aInputBlockId, bool aPreventDefault) {
INPQ_LOG("got a content response; block=%" PRIu64 "\n", aInputBlockId);
bool success = false;
for (size_t i = 0; i < mTouchBlockQueue.Length(); i++) {
if (mTouchBlockQueue[i]->GetBlockId() == aInputBlockId) {
success = mTouchBlockQueue[i]->SetContentResponse(aPreventDefault);
for (size_t i = 0; i < mInputBlockQueue.Length(); i++) {
if (mInputBlockQueue[i]->GetBlockId() == aInputBlockId) {
CancelableBlockState* block = mInputBlockQueue[i].get();
success = block->SetContentResponse(aPreventDefault);
break;
}
}
if (success) {
ProcessPendingInputBlocks();
ProcessInputBlocks();
}
}
@ -238,14 +287,14 @@ InputQueue::SetConfirmedTargetApzc(uint64_t aInputBlockId, const nsRefPtr<AsyncP
INPQ_LOG("got a target apzc; block=%" PRIu64 " guid=%s\n",
aInputBlockId, aTargetApzc ? Stringify(aTargetApzc->GetGuid()).c_str() : "");
bool success = false;
for (size_t i = 0; i < mTouchBlockQueue.Length(); i++) {
if (mTouchBlockQueue[i]->GetBlockId() == aInputBlockId) {
success = mTouchBlockQueue[i]->SetConfirmedTargetApzc(aTargetApzc);
for (size_t i = 0; i < mInputBlockQueue.Length(); i++) {
if (mInputBlockQueue[i]->GetBlockId() == aInputBlockId) {
success = mInputBlockQueue[i]->SetConfirmedTargetApzc(aTargetApzc);
break;
}
}
if (success) {
ProcessPendingInputBlocks();
ProcessInputBlocks();
} else {
NS_WARNING("INPQ received useless SetConfirmedTargetApzc");
}
@ -257,25 +306,30 @@ InputQueue::SetAllowedTouchBehavior(uint64_t aInputBlockId, const nsTArray<Touch
INPQ_LOG("got allowed touch behaviours; block=%" PRIu64 "\n", aInputBlockId);
bool success = false;
for (size_t i = 0; i < mTouchBlockQueue.Length(); i++) {
if (mTouchBlockQueue[i]->GetBlockId() == aInputBlockId) {
success = mTouchBlockQueue[i]->SetAllowedTouchBehaviors(aBehaviors);
for (size_t i = 0; i < mInputBlockQueue.Length(); i++) {
if (mInputBlockQueue[i]->GetBlockId() == aInputBlockId) {
TouchBlockState *block = mInputBlockQueue[i]->AsTouchBlock();
if (block) {
success = block->SetAllowedTouchBehaviors(aBehaviors);
} else {
NS_WARNING("input block is not a touch block");
}
break;
}
}
if (success) {
ProcessPendingInputBlocks();
ProcessInputBlocks();
} else {
NS_WARNING("INPQ received useless SetAllowedTouchBehavior");
}
}
void
InputQueue::ProcessPendingInputBlocks() {
InputQueue::ProcessInputBlocks() {
AsyncPanZoomController::AssertOnControllerThread();
while (true) {
TouchBlockState* curBlock = CurrentTouchBlock();
do {
CancelableBlockState* curBlock = CurrentBlock();
if (!curBlock->IsReadyForHandling()) {
break;
}
@ -292,25 +346,23 @@ InputQueue::ProcessPendingInputBlocks() {
curBlock->DropEvents();
target->ResetInputState();
} else {
while (curBlock->HasEvents()) {
target->HandleInputEvent(curBlock->RemoveFirstEvent());
}
curBlock->HandleEvents(target);
}
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.
if (mInputBlockQueue.Length() == 1 && curBlock->MustStayActive()) {
// Some types of blocks (e.g. touch blocks) accumulate events until the
// next input block is started. Therefore we cannot remove the block from
// the queue until we have started another block. This block will be
// removed by SweepDeletedBlocks() whenever a new block is 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("discarding depleted touch block %p\n", curBlock);
mTouchBlockQueue.RemoveElementAt(0);
}
mInputBlockQueue.RemoveElementAt(0);
} while (!mInputBlockQueue.IsEmpty());
}
} // namespace layers

Просмотреть файл

@ -14,11 +14,13 @@
namespace mozilla {
class InputData;
class MultiTouchInput;
namespace layers {
class AsyncPanZoomController;
class OverscrollHandoffChain;
class CancelableBlockState;
class TouchBlockState;
/**
@ -76,28 +78,64 @@ public:
*/
uint64_t InjectNewTouchBlock(AsyncPanZoomController* aTarget);
/**
* Returns the touch block at the head of the queue.
* Returns the pending input block at the head of the queue.
*/
CancelableBlockState* CurrentBlock() const;
/**
* Returns the current pending input block as a touch block. It must only
* called if the current pending block is a touch block.
*/
TouchBlockState* CurrentTouchBlock() const;
/**
* Returns true iff the touch block at the head of the queue is ready for
* Returns true iff the pending block at the head of the queue is ready for
* handling.
*/
bool HasReadyTouchBlock() const;
private:
~InputQueue();
TouchBlockState* StartNewTouchBlock(const nsRefPtr<AsyncPanZoomController>& aTarget,
bool aTargetConfirmed,
bool aCopyAllowedTouchBehaviorFromCurrent);
/**
* If animations are present for the current pending input block, cancel
* them as soon as possible.
*/
void CancelAnimationsForNewBlock(CancelableBlockState* aBlock);
/**
* If we need to wait for a content response, schedule that now.
*/
void MaybeRequestContentResponse(const nsRefPtr<AsyncPanZoomController>& aTarget,
CancelableBlockState* aBlock);
nsEventStatus ReceiveTouchInput(const nsRefPtr<AsyncPanZoomController>& aTarget,
bool aTargetConfirmed,
const MultiTouchInput& aEvent,
uint64_t* aOutInputBlockId);
/**
* Remove any blocks that are inactive - not ready, and having no events.
*/
void SweepDepletedBlocks();
/**
* Processes the current block if it's ready for handling.
*/
bool MaybeHandleCurrentBlock(const nsRefPtr<AsyncPanZoomController>& aTarget,
CancelableBlockState* block,
const InputData& aEvent);
void ScheduleMainThreadTimeout(const nsRefPtr<AsyncPanZoomController>& aTarget, uint64_t aInputBlockId);
void MainThreadTimeout(const uint64_t& aInputBlockId);
void ProcessPendingInputBlocks();
void ProcessInputBlocks();
private:
// The queue of touch blocks that have not yet been processed.
// The queue of touch blocks that have not yet been fully processed.
// This member must only be accessed on the controller/UI thread.
nsTArray<UniquePtr<TouchBlockState>> mTouchBlockQueue;
nsTArray<UniquePtr<CancelableBlockState>> mInputBlockQueue;
};
}