author | Kartikaya Gupta <kgupta@mozilla.com> |
Fri, 24 Oct 2014 13:29:03 -0400 | |
changeset 212293 | e194c411165c0835a0fbfb68767d9a2e3032b62f |
parent 212292 | 00fdb9ef3be69c800433e2b28c8bb601642d6cf3 |
child 212294 | 6efc9dfeae46e54dc706f2f42d36153e8c67fc0f |
push id | 27704 |
push user | kwierso@gmail.com |
push date | Sat, 25 Oct 2014 01:25:30 +0000 |
treeherder | mozilla-central@e37231060eb4 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | botond |
bugs | 1083395 |
milestone | 36.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp +++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp @@ -11,16 +11,17 @@ #include "AsyncPanZoomController.h" // for AsyncPanZoomController, etc #include "Axis.h" // for AxisX, AxisY, Axis, etc #include "Compositor.h" // for Compositor #include "CompositorParent.h" // for CompositorParent #include "FrameMetrics.h" // for FrameMetrics, etc #include "GestureEventListener.h" // for GestureEventListener #include "InputData.h" // for MultiTouchInput, etc #include "InputBlockState.h" // for InputBlockState, TouchBlockState +#include "InputQueue.h" // for InputQueue #include "OverscrollHandoffState.h" // for OverscrollHandoffState #include "TaskThrottler.h" // for TaskThrottler #include "Units.h" // for CSSRect, CSSPoint, etc #include "UnitTransforms.h" // for TransformTo #include "base/message_loop.h" // for MessageLoop #include "base/task.h" // for NewRunnableMethod, etc #include "base/tracked.h" // for FROM_HERE #include "gfxPrefs.h" // for gfxPrefs @@ -880,17 +881,17 @@ AsyncPanZoomController::AsyncPanZoomCont mZoomConstraints(false, false, MIN_ZOOM, MAX_ZOOM), mLastSampleTime(GetFrameTime()), mLastAsyncScrollTime(GetFrameTime()), mLastAsyncScrollOffset(0, 0), mCurrentAsyncScrollOffset(0, 0), mAsyncScrollTimeoutTask(nullptr), mState(NOTHING), mNotificationBlockers(0), - mTouchBlockBalance(0), + mInputQueue(new InputQueue()), mTreeManager(aTreeManager), mAPZCId(sAsyncPanZoomControllerCount++), mSharedLock(nullptr), mAsyncTransformAppliedToContent(false) { if (aGestures == USE_GESTURE_DETECTOR) { mGestureEventListener = new GestureEventListener(this); } @@ -921,29 +922,35 @@ AsyncPanZoomController::GetGeckoContentC already_AddRefed<GestureEventListener> AsyncPanZoomController::GetGestureEventListener() const { MonitorAutoLock lock(mRefPtrMonitor); nsRefPtr<GestureEventListener> listener = mGestureEventListener; return listener.forget(); } +nsRefPtr<InputQueue> +AsyncPanZoomController::GetInputQueue() const { + MonitorAutoLock lock(mRefPtrMonitor); + MOZ_ASSERT(mInputQueue); + return mInputQueue; +} + void AsyncPanZoomController::Destroy() { AssertOnCompositorThread(); CancelAnimation(); - mTouchBlockQueue.Clear(); - { // scope the lock MonitorAutoLock lock(mRefPtrMonitor); mGeckoContentController = nullptr; mGestureEventListener = nullptr; + mInputQueue = nullptr; // XXX this is temporary and will be removed in a future patch } mPrevSibling = nullptr; mLastChild = nullptr; mParent = nullptr; mTreeManager = nullptr; PCompositorParent* compositor = GetSharedFrameMetricsCompositor(); // Only send the release message if the SharedFrameMetrics has been created. @@ -1002,86 +1009,17 @@ AsyncPanZoomController::ArePointerEvents if (!consumable) { return false; } return true; } nsEventStatus AsyncPanZoomController::ReceiveInputEvent(const InputData& aEvent) { - AssertOnControllerThread(); - - 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; + return GetInputQueue()->ReceiveInputEvent(this, aEvent); } nsEventStatus AsyncPanZoomController::HandleInputEvent(const InputData& aEvent) { AssertOnControllerThread(); nsEventStatus rv = nsEventStatus_eIgnore; switch (aEvent.mInputType) { @@ -1529,17 +1467,17 @@ nsEventStatus AsyncPanZoomController::On nsEventStatus AsyncPanZoomController::OnPanBegin(const PanGestureInput& aEvent) { APZC_LOG("%p got a pan-begin in state %d\n", this, mState); if (mState == SMOOTH_SCROLL) { // SMOOTH_SCROLL scrolls are cancelled by pan gestures. CancelAnimation(); } - mPanGestureState = MakeUnique<InputBlockState>(BuildOverscrollHandoffChain()); + mPanGestureState = MakeUnique<InputBlockState>(this); mX.StartTouch(aEvent.mPanStartPoint.x, aEvent.mTime); mY.StartTouch(aEvent.mPanStartPoint.y, aEvent.mTime); if (GetAxisLockMode() == FREE) { SetState(PANNING); return nsEventStatus_eConsumeNoDefault; } @@ -1608,17 +1546,17 @@ nsEventStatus AsyncPanZoomController::On nsEventStatus AsyncPanZoomController::OnPanMomentumStart(const PanGestureInput& aEvent) { APZC_LOG("%p got a pan-momentumstart in state %d\n", this, mState); if (mState == SMOOTH_SCROLL) { // SMOOTH_SCROLL scrolls are cancelled by pan gestures. CancelAnimation(); } - mPanGestureState = MakeUnique<InputBlockState>(BuildOverscrollHandoffChain()); + mPanGestureState = MakeUnique<InputBlockState>(this); return nsEventStatus_eConsumeNoDefault; } nsEventStatus AsyncPanZoomController::OnPanMomentumEnd(const PanGestureInput& aEvent) { APZC_LOG("%p got a pan-momentumend in state %d\n", this, mState); mPanGestureState = nullptr; @@ -1636,18 +1574,17 @@ nsEventStatus AsyncPanZoomController::On nsEventStatus AsyncPanZoomController::OnLongPress(const TapGestureInput& aEvent) { APZC_LOG("%p got a long-press in state %d\n", this, mState); nsRefPtr<GeckoContentController> controller = GetGeckoContentController(); if (controller) { int32_t modifiers = WidgetModifiersToDOMModifiers(aEvent.modifiers); CSSPoint geckoScreenPoint; if (ConvertToGecko(aEvent.mPoint, &geckoScreenPoint)) { - StartNewTouchBlock(true); - ScheduleContentResponseTimeout(); + GetInputQueue()->InjectNewTouchBlock(this); controller->HandleLongTap(geckoScreenPoint, modifiers, GetGuid()); return nsEventStatus_eConsumeNoDefault; } } return nsEventStatus_eIgnore; } nsEventStatus AsyncPanZoomController::OnLongPressUp(const TapGestureInput& aEvent) { @@ -2913,181 +2850,53 @@ void AsyncPanZoomController::ZoomToRect( // Schedule a repaint now, so the new displayport will be painted before the // animation finishes. RequestContentRepaint(endZoomToMetrics); } } 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 AsyncPanZoomController::ContentReceivedTouch(bool aPreventDefault) { - AssertOnControllerThread(); - - 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"); - } - } + GetInputQueue()->ContentReceivedTouch(aPreventDefault); } void AsyncPanZoomController::SetAllowedTouchBehavior(const nsTArray<TouchBehaviorFlags>& aBehaviors) { - 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("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); - } + GetInputQueue()->SetAllowedTouchBehavior(aBehaviors); } bool AsyncPanZoomController::NeedToWaitForContent() const { return (mFrameMetrics.GetMayHaveTouchListeners() || mFrameMetrics.GetMayHaveTouchCaret()); } +TouchBlockState* +AsyncPanZoomController::CurrentTouchBlock() +{ + return GetInputQueue()->CurrentTouchBlock(); +} + void AsyncPanZoomController::ResetInputState() { SetState(NOTHING); // Also clear the state in the gesture event listener nsRefPtr<GestureEventListener> listener = GetGestureEventListener(); if (listener) { MultiTouchInput cancel(MultiTouchInput::MULTITOUCH_CANCEL, 0, TimeStamp::Now(), 0); listener->HandleInputEvent(cancel); } } -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 AsyncPanZoomController::HasReadyTouchBlock() { - return !mTouchBlockQueue.IsEmpty() && mTouchBlockQueue[0]->IsReadyForHandling(); + return GetInputQueue()->HasReadyTouchBlock(); } AsyncPanZoomController::TouchBehaviorFlags AsyncPanZoomController::GetAllowedTouchBehavior(ScreenIntPoint& aPoint) { // Here we need to perform a hit testing over the touch-action regions attached to the // layer associated with current apzc. // Currently they are in progress, for more info see bug 928833. return AllowedTouchBehavior::UNKNOWN;
--- a/gfx/layers/apz/src/AsyncPanZoomController.h +++ b/gfx/layers/apz/src/AsyncPanZoomController.h @@ -14,16 +14,17 @@ #include "mozilla/EventForwards.h" #include "mozilla/Monitor.h" #include "mozilla/ReentrantMonitor.h" #include "mozilla/RefPtr.h" #include "mozilla/UniquePtr.h" #include "mozilla/Atomics.h" #include "InputData.h" #include "Axis.h" +#include "InputQueue.h" #include "TaskThrottler.h" #include "mozilla/gfx/Matrix.h" #include "nsRegion.h" #include "base/message_loop.h" namespace mozilla { @@ -578,25 +579,16 @@ protected: /** * Timeout function for mozbrowserasyncscroll event. Because we throttle * mozbrowserasyncscroll events in some conditions, this function ensures * that the last mozbrowserasyncscroll event will be fired after a period of * time. */ 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 * to the parent document. This excludes the transient compositor transform. * NOTE: This must be converted to CSSPoint relative to the child * document before sending over IPC. */ bool ConvertToGecko(const ScreenPoint& aPoint, CSSPoint* aOut); @@ -633,16 +625,17 @@ private: compositor thread. */ nsRefPtr<GeckoContentController> mGeckoContentController; nsRefPtr<GestureEventListener> mGestureEventListener; mutable Monitor mRefPtrMonitor; /* Utility functions that return a addrefed pointer to the corresponding fields. */ already_AddRefed<GeckoContentController> GetGeckoContentController() const; already_AddRefed<GestureEventListener> GetGestureEventListener() const; + nsRefPtr<InputQueue> GetInputQueue() const; // If we are sharing our frame metrics with content across processes bool mSharingFrameMetricsAcrossProcesses; /* Utility function to get the Compositor with which we share the FrameMetrics. This function is only callable from the compositor thread. */ PCompositorParent* GetSharedFrameMetricsCompositor(); protected: @@ -776,107 +769,54 @@ private: /* =================================================================== * The functions and members in this section are used to manage * blocks of touch events and the state needed to deal with content * listeners. */ public: /** - * This function is invoked by the APZCTreeManager which in turn is invoked - * 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. + * See InputQueue::ContentReceivedTouch */ void ContentReceivedTouch(bool aPreventDefault); /** - * Sets allowed touch behavior for current touch session. - * 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. + * See InputQueue::SetAllowedTouchBehavior */ void SetAllowedTouchBehavior(const nsTArray<TouchBehaviorFlags>& aBehaviors); /** * Flush a repaint request if one is needed, without throttling it with the * paint throttler. */ void FlushRepaintForNewInputBlock(); -private: - void ScheduleContentResponseTimeout(); - void ContentResponseTimeout(); + /** + * 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); + /** - * Processes any pending input blocks that are ready for processing. There - * must be at least one input block in the queue when this function is called. + * 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; + + /** + * Clear internal state relating to input handling. + */ void ResetInputState(); private: - // The queue of touch blocks that have not yet been processed by this APZC. - // 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; + nsRefPtr<InputQueue> mInputQueue; + TouchBlockState* CurrentTouchBlock(); + bool HasReadyTouchBlock(); /* =================================================================== * The functions and members in this section are used to manage * pan gestures. */ private: @@ -1025,16 +965,37 @@ public: void FlushRepaintForOverscrollHandoff(); /** * If overscrolled, start a snap-back animation and return true. * Otherwise return false. */ 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: /** * A helper function for calling APZCTreeManager::DispatchScroll(). * Guards against the case where the APZC is being concurrently destroyed * (and thus mTreeManager is being nulled out). */ bool CallDispatchScroll(const ScreenPoint& aStartPoint, const ScreenPoint& aEndPoint, @@ -1051,36 +1012,16 @@ private: /** * Try to overscroll by 'aOverscroll'. * If we are pannable, 'aOverscroll' is added to any existing overscroll, * and the function returns true. * Otherwise, nothing happens and the function return false. */ 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 * area that this APZC instance is responsible for. This is used when * hit-testing to see which APZC instance should handle touch events. */ public: void SetLayerHitTestData(const nsIntRegion& aRegion, const Matrix4x4& aTransformToLayer) {
--- a/gfx/layers/apz/src/InputBlockState.cpp +++ b/gfx/layers/apz/src/InputBlockState.cpp @@ -10,31 +10,38 @@ #include "OverscrollHandoffState.h" #define TBS_LOG(...) // #define TBS_LOG(...) printf_stderr("TBS: " __VA_ARGS__) namespace mozilla { namespace layers { -InputBlockState::InputBlockState(const nsRefPtr<const OverscrollHandoffChain>& aOverscrollHandoffChain) - : mOverscrollHandoffChain(aOverscrollHandoffChain) +InputBlockState::InputBlockState(const nsRefPtr<AsyncPanZoomController>& aTargetApzc) + : mTargetApzc(aTargetApzc) { - // We should never be constructed with a nullptr handoff chain. - MOZ_ASSERT(mOverscrollHandoffChain); + // We should never be constructed with a nullptr target. + MOZ_ASSERT(mTargetApzc); + mOverscrollHandoffChain = mTargetApzc->BuildOverscrollHandoffChain(); +} + +const nsRefPtr<AsyncPanZoomController>& +InputBlockState::GetTargetApzc() const +{ + return mTargetApzc; } const nsRefPtr<const OverscrollHandoffChain>& InputBlockState::GetOverscrollHandoffChain() const { return mOverscrollHandoffChain; } -TouchBlockState::TouchBlockState(const nsRefPtr<const OverscrollHandoffChain>& aOverscrollHandoffChain) - : InputBlockState(aOverscrollHandoffChain) +TouchBlockState::TouchBlockState(const nsRefPtr<AsyncPanZoomController>& aTargetApzc) + : InputBlockState(aTargetApzc) , mAllowedTouchBehaviorSet(false) , mPreventDefault(false) , mContentResponded(false) , mContentResponseTimerExpired(false) , mSingleTapDisallowed(false) , mSingleTapOccurred(false) { TBS_LOG("Creating %p\n", this);
--- a/gfx/layers/apz/src/InputBlockState.h +++ b/gfx/layers/apz/src/InputBlockState.h @@ -18,20 +18,23 @@ class OverscrollHandoffChain; /** * A base class that stores state common to various input blocks. * Currently, it just stores the overscroll handoff chain. */ class InputBlockState { 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; + private: + nsRefPtr<AsyncPanZoomController> mTargetApzc; nsRefPtr<const OverscrollHandoffChain> mOverscrollHandoffChain; }; /** * This class represents a single touch block. A touch block is * a set of touch events that can be cancelled by web content via * touch event listeners. * @@ -61,17 +64,17 @@ private: * be populated with some latency. The mAllowedTouchBehaviorSet and * mAllowedTouchBehaviors variables track this information. */ class TouchBlockState : public InputBlockState { public: 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. * @param aPreventDefault true iff the block is cancelled. * @return false if this block has already received a response from * web content, true if not. */ bool SetContentResponse(bool aPreventDefault);
new file mode 100644 --- /dev/null +++ b/gfx/layers/apz/src/InputQueue.cpp @@ -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
new file mode 100644 --- /dev/null +++ b/gfx/layers/apz/src/InputQueue.h @@ -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
--- a/gfx/layers/moz.build +++ b/gfx/layers/moz.build @@ -235,16 +235,17 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk ] UNIFIED_SOURCES += [ 'apz/src/APZCTreeManager.cpp', 'apz/src/AsyncPanZoomController.cpp', 'apz/src/Axis.cpp', 'apz/src/GestureEventListener.cpp', 'apz/src/InputBlockState.cpp', + 'apz/src/InputQueue.cpp', 'apz/src/OverscrollHandoffState.cpp', 'apz/src/TaskThrottler.cpp', 'apz/testutil/APZTestData.cpp', 'apz/util/ActiveElementManager.cpp', 'apz/util/APZCCallbackHelper.cpp', 'AxisPhysicsModel.cpp', 'AxisPhysicsMSDModel.cpp', 'basic/BasicCanvasLayer.cpp',