summaryrefslogtreecommitdiffstats
path: root/gfx/layers/apz/src/InputBlockState.h
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--gfx/layers/apz/src/InputBlockState.h592
1 files changed, 592 insertions, 0 deletions
diff --git a/gfx/layers/apz/src/InputBlockState.h b/gfx/layers/apz/src/InputBlockState.h
new file mode 100644
index 0000000000..f20a4a5901
--- /dev/null
+++ b/gfx/layers/apz/src/InputBlockState.h
@@ -0,0 +1,592 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 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/. */
+
+#ifndef mozilla_layers_InputBlockState_h
+#define mozilla_layers_InputBlockState_h
+
+#include "InputData.h" // for MultiTouchInput
+#include "mozilla/RefCounted.h" // for RefCounted
+#include "mozilla/RefPtr.h" // for RefPtr
+#include "mozilla/StaticPrefs_apz.h"
+#include "mozilla/gfx/Matrix.h" // for Matrix4x4
+#include "mozilla/layers/APZUtils.h"
+#include "mozilla/layers/LayersTypes.h" // for TouchBehaviorFlags
+#include "mozilla/layers/AsyncDragMetrics.h"
+#include "mozilla/layers/TouchCounter.h"
+#include "mozilla/TimeStamp.h" // for TimeStamp
+#include "nsTArray.h" // for nsTArray
+
+namespace mozilla {
+namespace layers {
+
+class AsyncPanZoomController;
+class OverscrollHandoffChain;
+class CancelableBlockState;
+class TouchBlockState;
+class WheelBlockState;
+class DragBlockState;
+class PanGestureBlockState;
+class PinchGestureBlockState;
+class KeyboardBlockState;
+enum class BrowserGestureResponse : bool;
+
+/**
+ * A base class that stores state common to various input blocks.
+ * Note that the InputBlockState constructor acquires the tree lock, so callers
+ * from inside AsyncPanZoomController should ensure that the APZC lock is not
+ * held.
+ */
+class InputBlockState : public RefCounted<InputBlockState> {
+ public:
+ MOZ_DECLARE_REFCOUNTED_TYPENAME(InputBlockState)
+
+ static const uint64_t NO_BLOCK_ID = 0;
+
+ enum class TargetConfirmationState : uint8_t {
+ eUnconfirmed,
+ eTimedOut,
+ eConfirmed
+ };
+
+ InputBlockState(const RefPtr<AsyncPanZoomController>& aTargetApzc,
+ TargetConfirmationFlags aFlags);
+ virtual ~InputBlockState() = default;
+
+ virtual CancelableBlockState* AsCancelableBlock() { return nullptr; }
+ virtual TouchBlockState* AsTouchBlock() { return nullptr; }
+ virtual WheelBlockState* AsWheelBlock() { return nullptr; }
+ virtual DragBlockState* AsDragBlock() { return nullptr; }
+ virtual PanGestureBlockState* AsPanGestureBlock() { return nullptr; }
+ virtual PinchGestureBlockState* AsPinchGestureBlock() { return nullptr; }
+ virtual KeyboardBlockState* AsKeyboardBlock() { return nullptr; }
+
+ virtual bool SetConfirmedTargetApzc(
+ const RefPtr<AsyncPanZoomController>& aTargetApzc,
+ TargetConfirmationState aState, InputData* aFirstInput,
+ bool aForScrollbarDrag);
+ const RefPtr<AsyncPanZoomController>& GetTargetApzc() const;
+ const RefPtr<const OverscrollHandoffChain>& GetOverscrollHandoffChain() const;
+ uint64_t GetBlockId() const;
+
+ bool IsTargetConfirmed() const;
+
+ virtual bool ShouldDropEvents() const;
+
+ void SetScrolledApzc(AsyncPanZoomController* aApzc);
+ AsyncPanZoomController* GetScrolledApzc() const;
+ bool IsDownchainOfScrolledApzc(AsyncPanZoomController* aApzc) const;
+
+ /**
+ * Dispatch the event to the target APZC. Mostly this is a hook for
+ * subclasses to do any per-event processing they need to.
+ */
+ virtual void DispatchEvent(const InputData& aEvent) const;
+
+ /**
+ * 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;
+
+ protected:
+ virtual void UpdateTargetApzc(
+ const RefPtr<AsyncPanZoomController>& aTargetApzc);
+
+ private:
+ // Checks whether |aA| is an ancestor of |aB| (or the same as |aB|) in
+ // |mOverscrollHandoffChain|.
+ bool IsDownchainOf(AsyncPanZoomController* aA,
+ AsyncPanZoomController* aB) const;
+
+ private:
+ RefPtr<AsyncPanZoomController> mTargetApzc;
+ bool mRequiresTargetConfirmation;
+ const uint64_t mBlockId;
+
+ // The APZC that was actually scrolled by events in this input block.
+ // This is used in configurations where a single input block is only
+ // allowed to scroll a single APZC (configurations where
+ // StaticPrefs::apz_allow_immediate_handoff() is false). Set the first time an
+ // input event in this block scrolls an APZC.
+ RefPtr<AsyncPanZoomController> mScrolledApzc;
+
+ protected:
+ TargetConfirmationState mTargetConfirmed;
+ RefPtr<const OverscrollHandoffChain> mOverscrollHandoffChain;
+
+ // Used to transform events from global screen space to |mTargetApzc|'s
+ // screen space. It's cached at the beginning of the input block so that
+ // all events in the block are in the same coordinate space.
+ ScreenToParentLayerMatrix4x4 mTransformToApzc;
+};
+
+/**
+ * This class represents a set of events that can be cancelled by web content
+ * via event listeners.
+ *
+ * Each cancelable input block can be cancelled by web content, and
+ * this information is stored in the mPreventDefault flag. Because web
+ * content runs on the Gecko main thread, we cannot always wait for web
+ * content's response. Instead, there is a timeout that sets this flag in the
+ * case where web content doesn't respond in time. The mContentResponded and
+ * mContentResponseTimerExpired flags indicate which of these scenarios
+ * occurred.
+ */
+class CancelableBlockState : public InputBlockState {
+ public:
+ CancelableBlockState(const RefPtr<AsyncPanZoomController>& aTargetApzc,
+ TargetConfirmationFlags aFlags);
+
+ CancelableBlockState* AsCancelableBlock() override { return this; }
+
+ /**
+ * 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.
+ */
+ virtual bool SetContentResponse(bool aPreventDefault);
+
+ /**
+ * Record that content didn't respond in time.
+ * @return false if this block already timed out, true if not.
+ */
+ virtual bool TimeoutContentResponse();
+
+ /**
+ * Checks if the content response timer has already expired.
+ */
+ bool IsContentResponseTimerExpired() const;
+
+ /**
+ * @return true iff web content cancelled this block of events.
+ */
+ bool IsDefaultPrevented() const;
+
+ /**
+ * @return true iff this block has received all the information needed
+ * to properly dispatch the events in the block.
+ */
+ virtual bool IsReadyForHandling() const;
+
+ /**
+ * Return a descriptive name for the block kind.
+ */
+ virtual const char* Type() = 0;
+
+ bool ShouldDropEvents() const override;
+
+ void ResetContentResponseTimerExpired() {
+ mContentResponseTimerExpired = false;
+ mContentResponded = false;
+ }
+
+ private:
+ bool mPreventDefault;
+ bool mContentResponded;
+ bool mContentResponseTimerExpired;
+};
+
+/**
+ * A single block of wheel events.
+ */
+class WheelBlockState : public CancelableBlockState {
+ public:
+ WheelBlockState(const RefPtr<AsyncPanZoomController>& aTargetApzc,
+ TargetConfirmationFlags aFlags,
+ const ScrollWheelInput& aEvent);
+
+ bool SetContentResponse(bool aPreventDefault) override;
+ bool MustStayActive() override;
+ const char* Type() override;
+ bool SetConfirmedTargetApzc(const RefPtr<AsyncPanZoomController>& aTargetApzc,
+ TargetConfirmationState aState,
+ InputData* aFirstInput,
+ bool aForScrollbarDrag) override;
+
+ WheelBlockState* AsWheelBlock() override { return this; }
+
+ /**
+ * Determine whether this wheel block is accepting new events.
+ */
+ bool ShouldAcceptNewEvent() const;
+
+ /**
+ * Call to check whether a wheel event will cause the current transaction to
+ * timeout.
+ */
+ bool MaybeTimeout(const ScrollWheelInput& aEvent);
+
+ /**
+ * Called from APZCTM when a mouse move or drag+drop event occurs, before
+ * the event has been processed.
+ */
+ void OnMouseMove(const ScreenIntPoint& aPoint,
+ const Maybe<ScrollableLayerGuid>& aTargetGuid);
+
+ /**
+ * Returns whether or not the block is participating in a wheel transaction.
+ * This means that the block is the most recent input block to be created,
+ * and no events have occurred that would require scrolling a different
+ * frame.
+ *
+ * @return True if in a transaction, false otherwise.
+ */
+ bool InTransaction() const;
+
+ /**
+ * Mark the block as no longer participating in a wheel transaction. This
+ * will force future wheel events to begin a new input block.
+ */
+ void EndTransaction();
+
+ /**
+ * @return Whether or not overscrolling is prevented for this wheel block.
+ */
+ bool AllowScrollHandoff() const;
+
+ /**
+ * Called to check and possibly end the transaction due to a timeout.
+ *
+ * @return True if the transaction ended, false otherwise.
+ */
+ bool MaybeTimeout(const TimeStamp& aTimeStamp);
+
+ /**
+ * Update the wheel transaction state for a new event.
+ */
+ void Update(ScrollWheelInput& aEvent);
+
+ ScrollDirections GetAllowedScrollDirections() const {
+ return mAllowedScrollDirections;
+ }
+
+ protected:
+ void UpdateTargetApzc(
+ const RefPtr<AsyncPanZoomController>& aTargetApzc) override;
+
+ private:
+ TimeStamp mLastEventTime;
+ TimeStamp mLastMouseMove;
+ uint32_t mScrollSeriesCounter;
+ bool mTransactionEnded;
+ bool mIsScrollable = true;
+ ScrollDirections mAllowedScrollDirections;
+};
+
+/**
+ * A block of mouse events that are part of a drag
+ */
+class DragBlockState : public CancelableBlockState {
+ public:
+ DragBlockState(const RefPtr<AsyncPanZoomController>& aTargetApzc,
+ TargetConfirmationFlags aFlags, const MouseInput& aEvent);
+
+ bool MustStayActive() override;
+ const char* Type() override;
+
+ bool HasReceivedMouseUp();
+ void MarkMouseUpReceived();
+
+ DragBlockState* AsDragBlock() override { return this; }
+
+ void SetInitialThumbPos(OuterCSSCoord aThumbPos);
+ void SetDragMetrics(const AsyncDragMetrics& aDragMetrics,
+ const CSSRect& aScrollableRect);
+
+ void DispatchEvent(const InputData& aEvent) const override;
+
+ private:
+ AsyncDragMetrics mDragMetrics;
+ OuterCSSCoord mInitialThumbPos;
+ CSSRect mInitialScrollableRect;
+ bool mReceivedMouseUp;
+};
+
+/**
+ * A single block of pan gesture events.
+ */
+class PanGestureBlockState : public CancelableBlockState {
+ public:
+ PanGestureBlockState(const RefPtr<AsyncPanZoomController>& aTargetApzc,
+ TargetConfirmationFlags aFlags,
+ const PanGestureInput& aEvent);
+
+ bool SetContentResponse(bool aPreventDefault) override;
+ bool IsReadyForHandling() const override;
+ bool MustStayActive() override;
+ const char* Type() override;
+ bool SetConfirmedTargetApzc(const RefPtr<AsyncPanZoomController>& aTargetApzc,
+ TargetConfirmationState aState,
+ InputData* aFirstInput,
+ bool aForScrollbarDrag) override;
+
+ PanGestureBlockState* AsPanGestureBlock() override { return this; }
+
+ bool ShouldDropEvents() const override;
+
+ bool TimeoutContentResponse() override;
+
+ /**
+ * @return Whether or not overscrolling is prevented for this block.
+ */
+ bool AllowScrollHandoff() const;
+
+ bool WasInterrupted() const { return mInterrupted; }
+
+ void SetNeedsToWaitForContentResponse(bool aWaitForContentResponse);
+ void SetNeedsToWaitForBrowserGestureResponse(
+ bool aWaitForBrowserGestureResponse);
+ void SetBrowserGestureResponse(BrowserGestureResponse aResponse);
+
+ ScrollDirections GetAllowedScrollDirections() const {
+ return mAllowedScrollDirections;
+ }
+
+ private:
+ bool mInterrupted;
+ bool mWaitingForContentResponse;
+ // A pan gesture may be used for browser's swipe gestures so APZ needs to wait
+ // for the response from the browser whether the gesture has been used for
+ // swipe or not. This `mWaitingForBrowserGestureResponse` flag represents the
+ // waiting state. And below `mStartedBrowserGesture` represents the response
+ // from the browser.
+ bool mWaitingForBrowserGestureResponse;
+ bool mStartedBrowserGesture;
+ ScrollDirections mAllowedScrollDirections;
+};
+
+/**
+ * A single block of pinch gesture events.
+ */
+class PinchGestureBlockState : public CancelableBlockState {
+ public:
+ PinchGestureBlockState(const RefPtr<AsyncPanZoomController>& aTargetApzc,
+ TargetConfirmationFlags aFlags);
+
+ bool SetContentResponse(bool aPreventDefault) override;
+ bool IsReadyForHandling() const override;
+ bool MustStayActive() override;
+ const char* Type() override;
+
+ PinchGestureBlockState* AsPinchGestureBlock() override { return this; }
+
+ bool WasInterrupted() const { return mInterrupted; }
+
+ void SetNeedsToWaitForContentResponse(bool aWaitForContentResponse);
+
+ private:
+ bool mInterrupted;
+ bool mWaitingForContentResponse;
+};
+
+/**
+ * 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.
+ *
+ * Every touch-start event creates a new touch block. In this case, the
+ * touch block consists of the touch-start, followed by all touch events
+ * up to but not including the next touch-start (except in the case where
+ * a long-tap happens, see below). Note that in particular we cannot know
+ * when a touch block ends until the next one is started. Most touch
+ * blocks are created by receipt of a touch-start event.
+ *
+ * Every long-tap event also creates a new touch block, since it can also
+ * be consumed by web content. In this case, when the long-tap event is
+ * dispatched to web content, a new touch block is started to hold the remaining
+ * touch events, up to but not including the next touch start (or long-tap).
+ *
+ * Additionally, if touch-action is enabled, each touch block should
+ * have a set of allowed touch behavior flags; one for each touch point.
+ * This also requires running code on the Gecko main thread, and so may
+ * be populated with some latency. The mAllowedTouchBehaviorSet and
+ * mAllowedTouchBehaviors variables track this information.
+ */
+class TouchBlockState : public CancelableBlockState {
+ public:
+ explicit TouchBlockState(const RefPtr<AsyncPanZoomController>& aTargetApzc,
+ TargetConfirmationFlags aFlags,
+ TouchCounter& aTouchCounter);
+
+ TouchBlockState* AsTouchBlock() 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.
+ */
+ bool SetAllowedTouchBehaviors(const nsTArray<TouchBehaviorFlags>& aBehaviors);
+ /**
+ * If the allowed touch behaviors have been set, populate them into
+ * |aOutBehaviors| and return true. Else, return false.
+ */
+ bool GetAllowedTouchBehaviors(
+ nsTArray<TouchBehaviorFlags>& aOutBehaviors) const;
+
+ /**
+ * Returns true if the allowed touch behaviours have been set, or if touch
+ * action is disabled.
+ */
+ bool HasAllowedTouchBehaviors() const;
+
+ /**
+ * Copy various properties from another block.
+ */
+ void CopyPropertiesFrom(const TouchBlockState& aOther);
+
+ /**
+ * @return true iff this block has received all the information needed
+ * to properly dispatch the events in the block.
+ */
+ bool IsReadyForHandling() const override;
+
+ /**
+ * Sets a flag that indicates this input block occurred while the APZ was
+ * in a state of fast flinging. This affects gestures that may be produced
+ * from input events in this block.
+ */
+ void SetDuringFastFling();
+ /**
+ * @return true iff SetDuringFastFling was called on this block.
+ */
+ bool IsDuringFastFling() const;
+ /**
+ * Set the single-tap-occurred flag that indicates that this touch block
+ * triggered a single tap event.
+ */
+ void SetSingleTapOccurred();
+ /**
+ * @return true iff the single-tap-occurred flag is set on this block.
+ */
+ bool SingleTapOccurred() const;
+
+ /**
+ * @return false iff touch-action is enabled and the allowed touch behaviors
+ * for this touch block do not allow pinch-zooming.
+ */
+ bool TouchActionAllowsPinchZoom() const;
+ /**
+ * @return false iff touch-action is enabled and the allowed touch behaviors
+ * for this touch block do not allow double-tap zooming.
+ */
+ bool TouchActionAllowsDoubleTapZoom() const;
+ /**
+ * @return false iff touch-action is enabled and the allowed touch behaviors
+ * for the first touch point do not allow panning in the specified
+ * direction(s).
+ */
+ bool TouchActionAllowsPanningX() const;
+ bool TouchActionAllowsPanningY() const;
+ bool TouchActionAllowsPanningXY() const;
+
+ /**
+ * Notifies the input block of an incoming touch event so that the block can
+ * update its internal slop state. "Slop" refers to the area around the
+ * initial touchstart where we drop touchmove events so that content doesn't
+ * see them. The |aApzcCanConsumeEvents| parameter is factored into how large
+ * the slop area is - if this is true the slop area is larger.
+ * @return true iff the provided event is a touchmove in the slop area and
+ * so should not be sent to content.
+ */
+ bool UpdateSlopState(const MultiTouchInput& aInput,
+ bool aApzcCanConsumeEvents);
+ bool IsInSlop() const;
+ bool ForLongTap() const { return mForLongTap; }
+ void SetForLongTap() { mForLongTap = true; }
+ bool WasLongTapProcessed() const { return mLongTapWasProcessed; }
+ void SetLongTapProcessed() {
+ MOZ_ASSERT(!mForLongTap);
+ mLongTapWasProcessed = true;
+ mIsWaitingLongTapResult = false;
+ }
+
+ void SetWaitingLongTapResult() {
+ MOZ_ASSERT(!mForLongTap);
+ mIsWaitingLongTapResult = true;
+ }
+ bool IsWaitingLongTapResult() const { return mIsWaitingLongTapResult; }
+
+ void SetNeedsToWaitTouchMove(bool aNeedsWaitTouchMove) {
+ mNeedsWaitTouchMove = aNeedsWaitTouchMove;
+ }
+ bool IsReadyForCallback() const { return !mNeedsWaitTouchMove; };
+
+ /**
+ * Based on the slop origin and the given input event, return a best guess
+ * as to the pan direction of this touch block. Returns Nothing() if no guess
+ * can be made.
+ */
+ Maybe<ScrollDirection> GetBestGuessPanDirection(
+ const MultiTouchInput& aInput);
+
+ /**
+ * Returns the number of touch points currently active.
+ */
+ uint32_t GetActiveTouchCount() const;
+
+ void DispatchEvent(const InputData& aEvent) const override;
+ bool MustStayActive() override;
+ const char* Type() override;
+ TimeDuration GetTimeSinceBlockStart() const;
+ bool IsTargetOriginallyConfirmed() const;
+
+ private:
+ nsTArray<TouchBehaviorFlags> mAllowedTouchBehaviors;
+ bool mAllowedTouchBehaviorSet;
+ bool mDuringFastFling;
+ bool mSingleTapOccurred;
+ bool mInSlop;
+ // A long tap involves two touch blocks: the original touch
+ // block containing the `touchstart`, and a second one
+ // specifically for the long tap. `mForLongTap` is set on the
+ // second touch block. `mLongTapWasProcessed` is set
+ // on the first touch block after the long tap was processed.
+ bool mForLongTap;
+ bool mLongTapWasProcessed;
+
+ // A flag representing a state while we are waiting for a content response for
+ // the long tap.
+ // The reason why we have this flag separately from `mLongTapWasProcessed` is
+ // the block is not ready to be processed during the wait, and is ready once
+ // after `mLongTapWasProcessed` became true.
+ bool mIsWaitingLongTapResult;
+ // A flag representing a state that this block still needs to wait for a
+ // content response for a touch move event. It will be set just before
+ // triggering a long-press event.
+ bool mNeedsWaitTouchMove;
+ ScreenIntPoint mSlopOrigin;
+ // A reference to the InputQueue's touch counter
+ TouchCounter& mTouchCounter;
+ TimeStamp mStartTime;
+ // The original `mTargetConfirmed`. This is necessary to tell whether there's
+ // any APZ-aware event listener in the content after we've got a content
+ // response, because in the case of a long-tap event we need to wait a content
+ // response again.
+ TargetConfirmationState mOriginalTargetConfirmedState;
+};
+
+/**
+ * This class represents a set of keyboard inputs targeted at the same Apzc.
+ */
+class KeyboardBlockState : public InputBlockState {
+ public:
+ explicit KeyboardBlockState(
+ const RefPtr<AsyncPanZoomController>& aTargetApzc);
+
+ KeyboardBlockState* AsKeyboardBlock() override { return this; }
+
+ bool MustStayActive() override { return false; }
+
+ /**
+ * @return Whether or not overscrolling is prevented for this keyboard block.
+ */
+ bool AllowScrollHandoff() const { return false; }
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif // mozilla_layers_InputBlockState_h