diff options
Diffstat (limited to 'gfx/layers/apz/src/AsyncPanZoomController.h')
-rw-r--r-- | gfx/layers/apz/src/AsyncPanZoomController.h | 1927 |
1 files changed, 1927 insertions, 0 deletions
diff --git a/gfx/layers/apz/src/AsyncPanZoomController.h b/gfx/layers/apz/src/AsyncPanZoomController.h new file mode 100644 index 0000000000..c116c12e88 --- /dev/null +++ b/gfx/layers/apz/src/AsyncPanZoomController.h @@ -0,0 +1,1927 @@ +/* -*- 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_AsyncPanZoomController_h +#define mozilla_layers_AsyncPanZoomController_h + +#include "mozilla/layers/GeckoContentController.h" +#include "mozilla/layers/RepaintRequest.h" +#include "mozilla/layers/SampleTime.h" +#include "mozilla/layers/ScrollbarData.h" +#include "mozilla/layers/ZoomConstraints.h" +#include "mozilla/Atomics.h" +#include "mozilla/Attributes.h" +#include "mozilla/EventForwards.h" +#include "mozilla/Monitor.h" +#include "mozilla/RecursiveMutex.h" +#include "mozilla/RefPtr.h" +#include "mozilla/ScrollTypes.h" +#include "mozilla/StaticPrefs_apz.h" +#include "mozilla/UniquePtr.h" +#include "InputData.h" +#include "Axis.h" // for Axis, Side, etc. +#include "ExpectedGeckoMetrics.h" +#include "FlingAccelerator.h" +#include "InputQueue.h" +#include "APZUtils.h" +#include "LayersTypes.h" +#include "mozilla/gfx/Matrix.h" +#include "nsRegion.h" +#include "nsTArray.h" +#include "PotentialCheckerboardDurationTracker.h" +#include "RecentEventsBuffer.h" // for RecentEventsBuffer +#include "SampledAPZCState.h" + +namespace mozilla { + +namespace ipc { + +class SharedMemoryBasic; + +} // namespace ipc + +namespace wr { +struct SampledScrollOffset; +} // namespace wr + +namespace layers { + +class AsyncDragMetrics; +class APZCTreeManager; +struct ScrollableLayerGuid; +class CompositorController; +class GestureEventListener; +struct AsyncTransform; +class AsyncPanZoomAnimation; +class StackScrollerFlingAnimation; +template <typename FlingPhysics> +class GenericFlingAnimation; +class AndroidFlingPhysics; +class DesktopFlingPhysics; +class InputBlockState; +struct FlingHandoffState; +class TouchBlockState; +class PanGestureBlockState; +class OverscrollHandoffChain; +struct OverscrollHandoffState; +class StateChangeNotificationBlocker; +class CheckerboardEvent; +class OverscrollEffectBase; +class WidgetOverscrollEffect; +class GenericOverscrollEffect; +class AndroidSpecificState; +struct KeyboardScrollAction; +struct ZoomTarget; + +namespace apz { +struct AsyncScrollThumbTransformer; +} + +// Base class for grouping platform-specific APZC state variables. +class PlatformSpecificStateBase { + public: + virtual ~PlatformSpecificStateBase() = default; + virtual AndroidSpecificState* AsAndroidSpecificState() { return nullptr; } + // PLPPI = "ParentLayer pixels per (Screen) inch" + virtual AsyncPanZoomAnimation* CreateFlingAnimation( + AsyncPanZoomController& aApzc, const FlingHandoffState& aHandoffState, + float aPLPPI); + virtual UniquePtr<VelocityTracker> CreateVelocityTracker(Axis* aAxis); + + static void InitializeGlobalState() {} +}; + +/* + * Represents a transform from the ParentLayer coordinate space of an APZC + * to the ParentLayer coordinate space of its parent APZC. + * Each layer along the way contributes to the transform. We track + * contributions that are perspective transforms separately, as sometimes + * these require special handling. + */ +struct AncestorTransform { + gfx::Matrix4x4 mTransform; + gfx::Matrix4x4 mPerspectiveTransform; + + AncestorTransform() = default; + + AncestorTransform(const gfx::Matrix4x4& aTransform, + bool aTransformIsPerspective) { + (aTransformIsPerspective ? mPerspectiveTransform : mTransform) = aTransform; + } + + AncestorTransform(const gfx::Matrix4x4& aTransform, + const gfx::Matrix4x4& aPerspectiveTransform) + : mTransform(aTransform), mPerspectiveTransform(aPerspectiveTransform) {} + + gfx::Matrix4x4 CombinedTransform() const { + return mTransform * mPerspectiveTransform; + } + + bool ContainsPerspectiveTransform() const { + return !mPerspectiveTransform.IsIdentity(); + } + + gfx::Matrix4x4 GetPerspectiveTransform() const { + return mPerspectiveTransform; + } + + friend AncestorTransform operator*(const AncestorTransform& aA, + const AncestorTransform& aB) { + return AncestorTransform{ + aA.mTransform * aB.mTransform, + aA.mPerspectiveTransform * aB.mPerspectiveTransform}; + } +}; + +// Flags returned by AsyncPanZoomController::ArePointerEventsConsumable(). +// See the function for more details. +struct PointerEventsConsumableFlags { + // The APZC has room to pan or zoom in response to the touch event. + bool mHasRoom = false; + + // The panning or zooming is allowed by the touch-action property. + bool mAllowedByTouchAction = false; + + bool IsConsumable() const { return mHasRoom && mAllowedByTouchAction; } +}; + +/** + * Controller for all panning and zooming logic. Any time a user input is + * detected and it must be processed in some way to affect what the user sees, + * it goes through here. Listens for any input event from InputData and can + * optionally handle WidgetGUIEvent-derived touch events, but this must be done + * on the main thread. Note that this class completely cross-platform. + * + * Input events originate on the UI thread of the platform that this runs on, + * and are then sent to this class. This class processes the event in some way; + * for example, a touch move will usually lead to a panning of content (though + * of course there are exceptions, such as if content preventDefaults the event, + * or if the target frame is not scrollable). The compositor interacts with this + * class by locking it and querying it for the current transform matrix based on + * the panning and zooming logic that was invoked on the UI thread. + * + * Currently, each outer DOM window (i.e. a website in a tab, but not any + * subframes) has its own AsyncPanZoomController. In the future, to support + * asynchronously scrolled subframes, we want to have one AsyncPanZoomController + * per frame. + */ +class AsyncPanZoomController { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AsyncPanZoomController) + + typedef mozilla::MonitorAutoLock MonitorAutoLock; + typedef mozilla::gfx::Matrix4x4 Matrix4x4; + typedef mozilla::layers::RepaintRequest::ScrollOffsetUpdateType + RepaintUpdateType; + + public: + enum GestureBehavior { + // The platform code is responsible for forwarding gesture events here. We + // will not attempt to generate gesture events from MultiTouchInputs. + DEFAULT_GESTURES, + // An instance of GestureEventListener is used to detect gestures. This is + // handled completely internally within this class. + USE_GESTURE_DETECTOR + }; + + /** + * Gets the DPI from the tree manager. + */ + float GetDPI() const; + + /** + * Constant describing the tolerance in distance we use, multiplied by the + * device DPI, before we start panning the screen. This is to prevent us from + * accidentally processing taps as touch moves, and from very short/accidental + * touches moving the screen. + * Note: It's an abuse of the 'Coord' class to use it to represent a 2D + * distance, but it's the closest thing we currently have. + */ + ScreenCoord GetTouchStartTolerance() const; + /** + * Same as GetTouchStartTolerance, but the tolerance for how far the touch + * has to move before it starts allowing touchmove events to be dispatched + * to content, for non-scrollable content. + */ + ScreenCoord GetTouchMoveTolerance() const; + /** + * Same as GetTouchStartTolerance, but the tolerance for how close the second + * tap has to be to the first tap in order to be counted as part of a + * multi-tap gesture (double-tap or one-touch-pinch). + */ + ScreenCoord GetSecondTapTolerance() const; + + AsyncPanZoomController(LayersId aLayersId, APZCTreeManager* aTreeManager, + const RefPtr<InputQueue>& aInputQueue, + GeckoContentController* aController, + GestureBehavior aGestures = DEFAULT_GESTURES); + + // -------------------------------------------------------------------------- + // These methods must only be called on the gecko thread. + // + + /** + * Read the various prefs and do any global initialization for all APZC + * instances. This must be run on the gecko thread before any APZC instances + * are actually used for anything meaningful. + */ + static void InitializeGlobalState(); + + // -------------------------------------------------------------------------- + // These methods must only be called on the controller/UI thread. + // + + /** + * Kicks an animation to zoom to a rect. This may be either a zoom out or zoom + * in. The actual animation is done on the sampler thread after being set + * up. + */ + void ZoomToRect(const ZoomTarget& aZoomTarget, const uint32_t aFlags); + + /** + * Updates any zoom constraints contained in the <meta name="viewport"> tag. + */ + void UpdateZoomConstraints(const ZoomConstraints& aConstraints); + + /** + * Schedules a runnable to run on the controller/UI thread at some time + * in the future. + */ + void PostDelayedTask(already_AddRefed<Runnable> aTask, int aDelayMs); + + // -------------------------------------------------------------------------- + // These methods must only be called on the sampler thread. + // + + /** + * Advances any animations currently running to the given timestamp. + * This may be called multiple times with the same timestamp. + * + * The return value indicates whether or not any currently running animation + * should continue. If true, the compositor should schedule another composite. + */ + bool AdvanceAnimations(const SampleTime& aSampleTime); + + bool UpdateAnimation(const RecursiveMutexAutoLock& aProofOfLock, + const SampleTime& aSampleTime, + nsTArray<RefPtr<Runnable>>* aOutDeferredTasks); + + // -------------------------------------------------------------------------- + // These methods must only be called on the updater thread. + // + + /** + * A shadow layer update has arrived. |aScrollMetdata| is the new + * ScrollMetadata for the container layer corresponding to this APZC. + * |aIsFirstPaint| is a flag passed from the shadow + * layers code indicating that the scroll metadata being sent with this call + * are the initial metadata and the initial paint of the frame has just + * happened. + */ + void NotifyLayersUpdated(const ScrollMetadata& aScrollMetadata, + bool aIsFirstPaint, bool aThisLayerTreeUpdated); + + /** + * The platform implementation must set the compositor controller so that we + * can request composites. + */ + void SetCompositorController(CompositorController* aCompositorController); + + // -------------------------------------------------------------------------- + // These methods can be called from any thread. + // + + /** + * Shut down the controller/UI thread state and prepare to be + * deleted (which may happen from any thread). + */ + void Destroy(); + + /** + * Returns true if Destroy() has already been called on this APZC instance. + */ + bool IsDestroyed() const; + + /** + * Returns the transform to take something from the coordinate space of the + * last thing we know gecko painted, to the coordinate space of the last thing + * we asked gecko to paint. In cases where that last request has not yet been + * processed, this is needed to transform input events properly into a space + * gecko will understand. + */ + Matrix4x4 GetTransformToLastDispatchedPaint( + const AsyncTransformComponents& aComponents = LayoutAndVisual) const; + + /** + * Returns the number of CSS pixels of checkerboard according to the metrics + * in this APZC. The argument provided by the caller is the composition bounds + * of this APZC, additionally clipped by the composition bounds of any + * ancestor APZCs, accounting for all the async transforms. + */ + uint32_t GetCheckerboardMagnitude( + const ParentLayerRect& aClippedCompositionBounds) const; + + /** + * Report the number of CSSPixel-milliseconds of checkerboard to telemetry. + * See GetCheckerboardMagnitude for documentation of the + * aClippedCompositionBounds argument that needs to be provided by the caller. + */ + void ReportCheckerboard(const SampleTime& aSampleTime, + const ParentLayerRect& aClippedCompositionBounds); + + /** + * Flush any active checkerboard report that's in progress. This basically + * pretends like any in-progress checkerboard event has terminated, and pushes + * out the report to the checkerboard reporting service and telemetry. If the + * checkerboard event has not really finished, it will start a new event + * on the next composite. + */ + void FlushActiveCheckerboardReport(); + + /** + * See documentation on corresponding method in APZPublicUtils.h + */ + static gfx::IntSize GetDisplayportAlignmentMultiplier( + const ScreenSize& aBaseSize); + + enum class ZoomInProgress { + No, + Yes, + }; + + /** + * Recalculates the displayport. Ideally, this should paint an area bigger + * than the composite-to dimensions so that when you scroll down, you don't + * checkerboard immediately. This includes a bunch of logic, including + * algorithms to bias painting in the direction of the velocity and other + * such things. + */ + static const ScreenMargin CalculatePendingDisplayPort( + const FrameMetrics& aFrameMetrics, const ParentLayerPoint& aVelocity, + ZoomInProgress aZoomInProgress); + + nsEventStatus HandleDragEvent(const MouseInput& aEvent, + const AsyncDragMetrics& aDragMetrics, + CSSCoord aInitialThumbPos); + + /** + * Handler for events which should not be intercepted by the touch listener. + */ + nsEventStatus HandleInputEvent( + const InputData& aEvent, + const ScreenToParentLayerMatrix4x4& aTransformToApzc); + + /** + * Handler for gesture events. + * Currently some gestures are detected in GestureEventListener that calls + * APZC back through this handler in order to avoid recursive calls to + * APZC::HandleInputEvent() which is supposed to do the work for + * ReceiveInputEvent(). + */ + nsEventStatus HandleGestureEvent(const InputData& aEvent); + + /** + * Start autoscrolling this APZC, anchored at the provided location. + */ + void StartAutoscroll(const ScreenPoint& aAnchorLocation); + + /** + * Stop autoscrolling this APZC. + */ + void StopAutoscroll(); + + /** + * Populates the provided object (if non-null) with the scrollable guid of + * this apzc. + */ + void GetGuid(ScrollableLayerGuid* aGuidOut) const; + + /** + * Returns the scrollable guid of this apzc. + */ + ScrollableLayerGuid GetGuid() const; + + /** + * Returns true if this APZC instance is for the layer identified by the guid. + */ + bool Matches(const ScrollableLayerGuid& aGuid); + + /** + * Returns true if the tree manager of this APZC is the same as the one + * passed in. + */ + bool HasTreeManager(const APZCTreeManager* aTreeManager) const; + + void StartAnimation(AsyncPanZoomAnimation* aAnimation); + + /** + * Cancels any currently running animation. + * aFlags is a bit-field to provide specifics of how to cancel the animation. + * See CancelAnimationFlags. + */ + void CancelAnimation(CancelAnimationFlags aFlags = Default); + + /** + * Clear any overscroll on this APZC. + */ + void ClearOverscroll(); + void ClearPhysicalOverscroll(); + + /** + * Returns whether this APZC is for an element marked with the 'scrollgrab' + * attribute. + */ + bool HasScrollgrab() const { return mScrollMetadata.GetHasScrollgrab(); } + + /** + * Returns whether this APZC has scroll snap points. + */ + bool HasScrollSnapping() const { + return mScrollMetadata.GetSnapInfo().HasScrollSnapping(); + } + + /** + * Returns whether this APZC has room to be panned (in any direction). + */ + bool IsPannable() const; + + /** + * Returns whether this APZC represents a scroll info layer. + */ + bool IsScrollInfoLayer() const; + + /** + * Returns true if the APZC has been flung with a velocity greater than the + * stop-on-tap fling velocity threshold (which is pref-controlled). + */ + bool IsFlingingFast() const; + + /** + * Returns whether this APZC is currently autoscrolling. + */ + bool IsAutoscroll() const { + RecursiveMutexAutoLock lock(mRecursiveMutex); + return mState == AUTOSCROLL; + } + + /** + * Returns the identifier of the touch in the last touch event processed by + * this APZC. This should only be called when the last touch event contained + * only one touch. + */ + int32_t GetLastTouchIdentifier() const; + + /** + * Returns the matrix that transforms points from global screen space into + * this APZC's ParentLayer space. + * To respect the lock ordering, mRecursiveMutex must NOT be held when calling + * this function (since this function acquires the tree lock). + */ + ScreenToParentLayerMatrix4x4 GetTransformToThis() const; + + /** + * Convert the vector |aVector|, rooted at the point |aAnchor|, from + * this APZC's ParentLayer coordinates into screen coordinates. + * The anchor is necessary because with 3D tranforms, the location of the + * vector can affect the result of the transform. + * To respect the lock ordering, mRecursiveMutex must NOT be held when calling + * this function (since this function acquires the tree lock). + */ + ScreenPoint ToScreenCoordinates(const ParentLayerPoint& aVector, + const ParentLayerPoint& aAnchor) const; + + /** + * Convert the vector |aVector|, rooted at the point |aAnchor|, from + * screen coordinates into this APZC's ParentLayer coordinates. + * The anchor is necessary because with 3D tranforms, the location of the + * vector can affect the result of the transform. + * To respect the lock ordering, mRecursiveMutex must NOT be held when calling + * this function (since this function acquires the tree lock). + */ + ParentLayerPoint ToParentLayerCoordinates(const ScreenPoint& aVector, + const ScreenPoint& aAnchor) const; + + /** + * Same as above, but uses an ExternalPoint as the anchor. + */ + ParentLayerPoint ToParentLayerCoordinates(const ScreenPoint& aVector, + const ExternalPoint& aAnchor) const; + + /** + * Combines an offset defined as an external point, with a window-relative + * offset to give an absolute external point. + */ + static ExternalPoint ToExternalPoint(const ExternalPoint& aScreenOffset, + const ScreenPoint& aScreenPoint); + + /** + * Gets a vector where the head is the given point, and the tail is + * the touch start position. + */ + ScreenPoint PanVector(const ExternalPoint& aPos) const; + + // Return whether or not a wheel event will be able to scroll in either + // direction. + bool CanScroll(const InputData& aEvent) const; + + // Return the directions in which this APZC allows handoff (as governed by + // overscroll-behavior). + ScrollDirections GetAllowedHandoffDirections() const; + + // Return the directions in which this APZC allows overscrolling. + ScrollDirections GetOverscrollableDirections() const; + + // Return whether or not a scroll delta will be able to scroll in either + // direction. + bool CanScroll(const ParentLayerPoint& aDelta) const; + + // Return whether or not a scroll delta will be able to scroll in either + // direction with wheel. + bool CanScrollWithWheel(const ParentLayerPoint& aDelta) const; + + // Return whether or not there is room to scroll this APZC + // in the given direction. + bool CanScroll(ScrollDirection aDirection) const; + + // Return the directions in which this APZC is able to scroll. + SideBits ScrollableDirections() const; + + // Return true if there is room to scroll along with moving the dynamic + // toolbar. + // + // NOTE: This function should be used only for the root content APZC. + bool CanVerticalScrollWithDynamicToolbar() const; + + // Return true if there is room to scroll downwards. + bool CanScrollDownwards() const; + + /** + * Convert a point on the scrollbar from this APZC's ParentLayer coordinates + * to CSS coordinates relative to the beginning of the scroll track. + * Only the component in the direction of scrolling is returned. + */ + CSSCoord ConvertScrollbarPoint(const ParentLayerPoint& aScrollbarPoint, + const ScrollbarData& aThumbData) const; + + void NotifyMozMouseScrollEvent(const nsString& aString) const; + + bool OverscrollBehaviorAllowsSwipe() const; + + //|Metrics()| and |Metrics() const| are getter functions that both return + // mScrollMetadata.mMetrics + + const FrameMetrics& Metrics() const; + FrameMetrics& Metrics(); + + /** + * Get the GeckoViewMetrics to be sent to Gecko for the current composite. + */ + GeckoViewMetrics GetGeckoViewMetrics() const; + + // Helper function to compare root frame metrics and update them + // Returns true when the metrics have changed and were updated. + bool UpdateRootFrameMetricsIfChanged(GeckoViewMetrics& aMetrics); + + // Returns the cached current frame time. + SampleTime GetFrameTime() const; + + bool IsZero(const ParentLayerPoint& aPoint) const; + bool IsZero(ParentLayerCoord aCoord) const; + + bool FuzzyGreater(ParentLayerCoord aCoord1, ParentLayerCoord aCoord2) const; + + private: + // Get whether the horizontal content of the honoured target of auto-dir + // scrolling starts from right to left. If you don't know of auto-dir + // scrolling or what a honoured target means, + // @see mozilla::WheelDeltaAdjustmentStrategy + bool IsContentOfHonouredTargetRightToLeft(bool aHonoursRoot) const; + + protected: + // Protected destructor, to discourage deletion outside of Release(): + virtual ~AsyncPanZoomController(); + + /** + * Helper method for touches beginning. Sets everything up for panning and any + * multitouch gestures. + */ + nsEventStatus OnTouchStart(const MultiTouchInput& aEvent); + + /** + * Helper method for touches moving. Does any transforms needed when panning. + */ + nsEventStatus OnTouchMove(const MultiTouchInput& aEvent); + + /** + * Helper method for touches ending. Redraws the screen if necessary and does + * any cleanup after a touch has ended. + */ + nsEventStatus OnTouchEnd(const MultiTouchInput& aEvent); + + /** + * Helper method for touches being cancelled. Treated roughly the same as a + * touch ending (OnTouchEnd()). + */ + nsEventStatus OnTouchCancel(const MultiTouchInput& aEvent); + + /** + * Helper method for scales beginning. Distinct from the OnTouch* handlers in + * that this implies some outside implementation has determined that the user + * is pinching. + */ + nsEventStatus OnScaleBegin(const PinchGestureInput& aEvent); + + /** + * Helper method for scaling. As the user moves their fingers when pinching, + * this changes the scale of the page. + */ + nsEventStatus OnScale(const PinchGestureInput& aEvent); + + /** + * Helper method for scales ending. Redraws the screen if necessary and does + * any cleanup after a scale has ended. + */ + nsEventStatus OnScaleEnd(const PinchGestureInput& aEvent); + + /** + * Helper methods for handling pan events. + */ + nsEventStatus OnPanMayBegin(const PanGestureInput& aEvent); + nsEventStatus OnPanCancelled(const PanGestureInput& aEvent); + nsEventStatus OnPanBegin(const PanGestureInput& aEvent); + enum class FingersOnTouchpad { + Yes, + No, + }; + nsEventStatus OnPan(const PanGestureInput& aEvent, + FingersOnTouchpad aFingersOnTouchpad); + nsEventStatus OnPanEnd(const PanGestureInput& aEvent); + nsEventStatus OnPanMomentumStart(const PanGestureInput& aEvent); + nsEventStatus OnPanMomentumEnd(const PanGestureInput& aEvent); + nsEventStatus HandleEndOfPan(); + nsEventStatus OnPanInterrupted(const PanGestureInput& aEvent); + + /** + * Helper methods for handling scroll wheel events. + */ + nsEventStatus OnScrollWheel(const ScrollWheelInput& aEvent); + + /** + * Gets the scroll wheel delta's values in parent-layer pixels from the + * original delta's values of a wheel input. + */ + ParentLayerPoint GetScrollWheelDelta(const ScrollWheelInput& aEvent) const; + + /** + * This function is like GetScrollWheelDelta(aEvent). + * The difference is the four added parameters provide values as alternatives + * to the original wheel input's delta values, so |aEvent|'s delta values are + * ignored in this function, we only use some other member variables and + * functions of |aEvent|. + */ + ParentLayerPoint GetScrollWheelDelta(const ScrollWheelInput& aEvent, + double aDeltaX, double aDeltaY, + double aMultiplierX, + double aMultiplierY) const; + + /** + * This deleted function is used for: + * 1. avoiding accidental implicit value type conversions of input delta + * values when callers intend to call the above function; + * 2. decoupling the manual relationship between the delta value type and the + * above function. If by any chance the defined delta value type in + * ScrollWheelInput has changed, this will automatically result in build + * time failure, so we can learn of it the first time and accordingly + * redefine those parameters' value types in the above function. + */ + template <typename T> + ParentLayerPoint GetScrollWheelDelta(ScrollWheelInput&, T, T, T, T) = delete; + + /** + * Helper methods for handling keyboard events. + */ + nsEventStatus OnKeyboard(const KeyboardInput& aEvent); + + CSSPoint GetKeyboardDestination(const KeyboardScrollAction& aAction) const; + + // Returns the corresponding ScrollSnapFlags for the given |aAction|. + // See https://drafts.csswg.org/css-scroll-snap/#scroll-types + ScrollSnapFlags GetScrollSnapFlagsForKeyboardAction( + const KeyboardScrollAction& aAction) const; + + /** + * Helper methods for long press gestures. + */ + MOZ_CAN_RUN_SCRIPT_BOUNDARY + nsEventStatus OnLongPress(const TapGestureInput& aEvent); + nsEventStatus OnLongPressUp(const TapGestureInput& aEvent); + + /** + * Helper method for single tap gestures. + */ + nsEventStatus OnSingleTapUp(const TapGestureInput& aEvent); + + /** + * Helper method for a single tap confirmed. + */ + nsEventStatus OnSingleTapConfirmed(const TapGestureInput& aEvent); + + /** + * Helper method for double taps. + */ + MOZ_CAN_RUN_SCRIPT_BOUNDARY + nsEventStatus OnDoubleTap(const TapGestureInput& aEvent); + + /** + * Helper method for double taps where the double-tap gesture is disabled. + */ + nsEventStatus OnSecondTap(const TapGestureInput& aEvent); + + /** + * Helper method to cancel any gesture currently going to Gecko. Used + * primarily when a user taps the screen over some clickable content but then + * pans down instead of letting go (i.e. to cancel a previous touch so that a + * new one can properly take effect. + */ + nsEventStatus OnCancelTap(const TapGestureInput& aEvent); + + /** + * The following five methods modify the scroll offset. For the APZC + * representing the RCD-RSF, they also recalculate the offset of the layout + * viewport. + */ + + /** + * Scroll the scroll frame to an X,Y offset. + */ + void SetVisualScrollOffset(const CSSPoint& aOffset); + + /** + * Scroll the scroll frame to an X,Y offset, clamping the resulting scroll + * offset to the scroll range. + */ + void ClampAndSetVisualScrollOffset(const CSSPoint& aOffset); + + /** + * Scroll the scroll frame by an X,Y offset. + * The resulting scroll offset is not clamped to the scrollable rect; + * the caller must ensure it stays within range. + */ + void ScrollBy(const CSSPoint& aOffset); + + /** + * Scroll the scroll frame by an X,Y offset, clamping the resulting + * scroll offset to the scroll range. + */ + void ScrollByAndClamp(const CSSPoint& aOffset); + + /** + * Scales the viewport by an amount (note that it multiplies this scale in to + * the current scale, it doesn't set it to |aScale|). Also considers a focus + * point so that the page zooms inward/outward from that point. + */ + void ScaleWithFocus(float aScale, const CSSPoint& aFocus); + + /** + * Schedules a composite on the compositor thread. + */ + void ScheduleComposite(); + + /** + * Schedules a composite, and if enough time has elapsed since the last + * paint, a paint. + */ + void ScheduleCompositeAndMaybeRepaint(); + + /** + * Gets the start point of the current touch. + * This only makes sense if a touch is currently happening and OnTouchMove() + * or the equivalent for pan gestures is being invoked. + */ + ParentLayerPoint PanStart() const; + + /** + * Gets a vector of the velocities of each axis. + */ + const ParentLayerPoint GetVelocityVector() const; + + /** + * Sets the velocities of each axis. + */ + void SetVelocityVector(const ParentLayerPoint& aVelocityVector); + + /** + * Gets the first touch point from a MultiTouchInput. This gets only + * the first one and assumes the rest are either missing or not relevant. + */ + ParentLayerPoint GetFirstTouchPoint(const MultiTouchInput& aEvent); + + /** + * Gets the relevant point in the event + * (eg. first touch, or pinch focus point) of the given InputData. + */ + ExternalPoint GetExternalPoint(const InputData& aEvent); + + /** + * Gets the relevant point in the event, in external screen coordinates. + */ + ExternalPoint GetFirstExternalTouchPoint(const MultiTouchInput& aEvent); + + /** + * Gets the amount by which this APZC is overscrolled along both axes. + */ + ParentLayerPoint GetOverscrollAmount() const; + + private: + // Internal version of GetOverscrollAmount() which does not set + // the test async properties. + ParentLayerPoint GetOverscrollAmountInternal() const; + + protected: + /** + * Returns SideBits where this APZC is overscrolled. + */ + SideBits GetOverscrollSideBits() const; + + /** + * Restore the amount by which this APZC is overscrolled along both axes + * to the specified amount. This is for test-related use; overscrolling + * as a result of user input should happen via OverscrollBy(). + */ + void RestoreOverscrollAmount(const ParentLayerPoint& aOverscroll); + + /** + * Sets the panning state basing on the pan direction angle and current + * touch-action value. + */ + void HandlePanningWithTouchAction(double angle); + + /** + * Sets the panning state ignoring the touch action value. + */ + void HandlePanning(double angle); + + /** + * Update the panning state and axis locks. + */ + void HandlePanningUpdate(const ScreenPoint& aDelta); + + /** + * Set and update the pinch lock + */ + void HandlePinchLocking(const PinchGestureInput& aEvent); + + /** + * Sets up anything needed for panning. This takes us out of the "TOUCHING" + * state and starts actually panning us. We provide the physical pixel + * position of the start point so that the pan gesture is calculated + * regardless of if the window/GeckoView moved during the pan. + */ + nsEventStatus StartPanning(const ExternalPoint& aStartPoint, + const TimeStamp& aEventTime); + + /** + * Wrapper for Axis::UpdateWithTouchAtDevicePoint(). Calls this function for + * both axes and factors in the time delta from the last update. + */ + void UpdateWithTouchAtDevicePoint(const MultiTouchInput& aEvent); + + /** + * Does any panning required due to a new touch event. + */ + void TrackTouch(const MultiTouchInput& aEvent); + + /** + * Register the start of a touch or pan gesture at the given position and + * time. + */ + void StartTouch(const ParentLayerPoint& aPoint, TimeStamp aTimestamp); + + /** + * Register the end of a touch or pan gesture at the given time. + */ + void EndTouch(TimeStamp aTimestamp, Axis::ClearAxisLock aClearAxisLock); + + /** + * Utility function to send updated FrameMetrics to Gecko so that it can paint + * the displayport area. Calls into GeckoContentController to do the actual + * work. This call will use the current metrics. If this function is called + * from a non-main thread, it will redispatch itself to the main thread, and + * use the latest metrics during the redispatch. + */ + void RequestContentRepaint( + RepaintUpdateType aUpdateType = RepaintUpdateType::eUserAction); + + /** + * Send Metrics() to Gecko to trigger a repaint. This function may filter + * duplicate calls with the same metrics. This function must be called on the + * main thread. + */ + void RequestContentRepaint(const ParentLayerPoint& aVelocity, + const ScreenMargin& aDisplayportMargins, + RepaintUpdateType aUpdateType); + + /** + * Gets the current frame metrics. This is *not* the Gecko copy stored in the + * layers code. + */ + const FrameMetrics& GetFrameMetrics() const; + + /** + * Gets the current scroll metadata. This is *not* the Gecko copy stored in + * the layers code/ + */ + const ScrollMetadata& GetScrollMetadata() const; + + /** + * Gets the pointer to the apzc tree manager. All the access to tree manager + * should be made via this method and not via private variable since this + * method ensures that no lock is set. + */ + APZCTreeManager* GetApzcTreeManager() const; + + void AssertOnSamplerThread() const; + void AssertOnUpdaterThread() const; + + /** + * Convert ScreenPoint relative to the screen to LayoutDevicePoint relative + * to the parent document. This excludes the transient compositor transform. + * NOTE: This must be converted to LayoutDevicePoint relative to the child + * document before sending over IPC to a child process. + */ + Maybe<LayoutDevicePoint> ConvertToGecko(const ScreenIntPoint& aPoint); + + enum AxisLockMode { + FREE, /* No locking at all */ + STANDARD, /* Default axis locking mode that remains locked until pan ends */ + STICKY, /* Allow lock to be broken, with hysteresis */ + DOMINANT_AXIS, /* Only allow movement on one axis */ + }; + + static AxisLockMode GetAxisLockMode(); + + bool UsingStatefulAxisLock() const; + + enum PinchLockMode { + PINCH_FREE, /* No locking at all */ + PINCH_STANDARD, /* Default pinch locking mode that remains locked until + pinch gesture ends*/ + PINCH_STICKY, /* Allow lock to be broken, with hysteresis */ + }; + + static PinchLockMode GetPinchLockMode(); + + // Helper function for OnSingleTapUp(), OnSingleTapConfirmed(), and + // OnLongPressUp(). + nsEventStatus GenerateSingleTap(GeckoContentController::TapType aType, + const ScreenIntPoint& aPoint, + mozilla::Modifiers aModifiers); + + // Common processing at the end of a touch block. + void OnTouchEndOrCancel(); + + LayersId mLayersId; + RefPtr<CompositorController> mCompositorController; + + /* Access to the following two fields is protected by the mRefPtrMonitor, + since they are accessed on the UI thread but can be cleared on the + updater thread. */ + RefPtr<GeckoContentController> mGeckoContentController; + RefPtr<GestureEventListener> mGestureEventListener; + mutable Monitor mRefPtrMonitor MOZ_UNANNOTATED; + + // This is a raw pointer to avoid introducing a reference cycle between + // AsyncPanZoomController and APZCTreeManager. Since these objects don't + // live on the main thread, we can't use the cycle collector with them. + // The APZCTreeManager owns the lifetime of the APZCs, so nulling this + // pointer out in Destroy() will prevent accessing deleted memory. + Atomic<APZCTreeManager*> mTreeManager; + + /* Utility functions that return a addrefed pointer to the corresponding + * fields. */ + already_AddRefed<GeckoContentController> GetGeckoContentController() const; + already_AddRefed<GestureEventListener> GetGestureEventListener() const; + + PlatformSpecificStateBase* GetPlatformSpecificState(); + + /** + * Convenience functions to get the corresponding fields of mZoomContraints + * while holding mRecursiveMutex. + */ + bool ZoomConstraintsAllowZoom() const; + bool ZoomConstraintsAllowDoubleTapZoom() const; + + protected: + // Both |mScrollMetadata| and |mLastContentPaintMetrics| are protected by the + // monitor. Do not read from or modify them without locking. + ScrollMetadata mScrollMetadata; + + // Protects |mScrollMetadata|, |mLastContentPaintMetrics|, |mState| and + // |mLastSnapTargetIds|. Before manipulating |mScrollMetadata|, + // |mLastContentPaintMetrics| or |mLastSnapTargetIds| the monitor should be + // held. When setting |mState|, either the SetState() function can be used, or + // the monitor can be held and then |mState| updated. + // IMPORTANT: See the note about lock ordering at the top of + // APZCTreeManager.h. This is mutable to allow entering it from 'const' + // methods; doing otherwise would significantly limit what methods could be + // 'const'. + // FIXME: Please keep in mind that due to some existing coupled relationships + // among the class members, we should be aware of indirect usage of the + // monitor-protected members. That is, although this monitor isn't required to + // be held before manipulating non-protected class members, some functions on + // those members might indirectly manipulate the protected members; in such + // cases, the monitor should still be held. Let's take mX.CanScroll for + // example: + // Axis::CanScroll(ParentLayerCoord) calls Axis::CanScroll() which calls + // Axis::GetPageLength() which calls Axis::GetFrameMetrics() which calls + // AsyncPanZoomController::GetFrameMetrics(), therefore, this monitor should + // be held before calling the CanScroll function of |mX| and |mY|. These + // coupled relationships bring us the burden of taking care of when the + // monitor should be held, so they should be decoupled in the future. + mutable RecursiveMutex mRecursiveMutex MOZ_UNANNOTATED; + + private: + // Metadata of the container layer corresponding to this APZC. This is + // stored here so that it is accessible from the UI/controller thread. + // These are the metrics at last content paint, the most recent + // values we were notified of in NotifyLayersUpdate(). Since it represents + // the Gecko state, it should be used as a basis for untransformation when + // sending messages back to Gecko. + ScrollMetadata mLastContentPaintMetadata; + FrameMetrics& mLastContentPaintMetrics; // for convenience, refers to + // mLastContentPaintMetadata.mMetrics + // The last content repaint request. + RepaintRequest mLastPaintRequestMetrics; + // The metrics that we expect content to have. This is updated when we + // request a content repaint, and when we receive a shadow layers update. + // This allows us to transform events into Gecko's coordinate space. + ExpectedGeckoMetrics mExpectedGeckoMetrics; + + // This holds important state from the Metrics() at previous times + // SampleCompositedAsyncTransform() was called. This will always have at least + // one item. mRecursiveMutex must be held when using or modifying this member. + // Samples should be inserted to the "back" of the deque and extracted from + // the "front". + std::deque<SampledAPZCState> mSampledState; + + // Groups state variables that are specific to a platform. + // Initialized on first use. + UniquePtr<PlatformSpecificStateBase> mPlatformSpecificState; + + // This flag is set to true when we are in a axis-locked pan as a result of + // the touch-action CSS property. + bool mPanDirRestricted; + + // This flag is set to true when we are in a pinch-locked state. ie: user + // is performing a two-finger pan rather than a pinch gesture + bool mPinchLocked; + + // Stores the pinch events that occured within a given timeframe. Used to + // calculate the focusChange and spanDistance within a fixed timeframe. + // RecentEventsBuffer is not threadsafe. Should only be accessed on the + // controller thread. + RecentEventsBuffer<PinchGestureInput> mPinchEventBuffer; + + // Most up-to-date constraints on zooming. These should always be reasonable + // values; for example, allowing a min zoom of 0.0 can cause very bad things + // to happen. Hold mRecursiveMutex when accessing this. + ZoomConstraints mZoomConstraints; + + // The last time the compositor has sampled the content transform for this + // frame. + SampleTime mLastSampleTime; + + // The last sample time at which we submitted a checkerboarding report. + SampleTime mLastCheckerboardReport; + + // Stores the previous focus point if there is a pinch gesture happening. Used + // to allow panning by moving multiple fingers (thus moving the focus point). + ParentLayerPoint mLastZoomFocus; + + // Stores the previous zoom level at which we last sent a ScaleGestureComplete + // notification. + CSSToParentLayerScale mLastNotifiedZoom; + + RefPtr<AsyncPanZoomAnimation> mAnimation; + + UniquePtr<OverscrollEffectBase> mOverscrollEffect; + + // Zoom animation id, used for zooming in WebRender. This should only be + // set on the APZC instance for the root content document (i.e. the one we + // support zooming on), and is only used if WebRender is enabled. The + // animation id itself refers to the transform animation id that was set on + // the stacking context in the WR display list. By changing the transform + // associated with this id, we can adjust the scaling that WebRender applies, + // thereby controlling the zoom. + Maybe<uint64_t> mZoomAnimationId; + + // Position on screen where user first put their finger down. + ExternalPoint mStartTouch; + + // Accessing mScrollPayload needs to be protected by mRecursiveMutex + Maybe<CompositionPayload> mScrollPayload; + + // Representing sampled scroll offset generation, this value is bumped up + // every time this APZC sampled new scroll offset. + APZScrollGeneration mScrollGeneration; + + friend class Axis; + + public: + Maybe<CompositionPayload> NotifyScrollSampling(); + + /** + * Invoke |callable|, passing |mLastContentPaintMetrics| as argument, + * while holding the APZC lock required to access |mLastContentPaintMetrics|. + * This allows code outside of an AsyncPanZoomController method implementation + * to access |mLastContentPaintMetrics| without having to make a copy of it. + * Passes through the return value of |callable|. + */ + template <typename Callable> + auto CallWithLastContentPaintMetrics(const Callable& callable) const + -> decltype(callable(mLastContentPaintMetrics)) { + RecursiveMutexAutoLock lock(mRecursiveMutex); + return callable(mLastContentPaintMetrics); + } + + void SetZoomAnimationId(const Maybe<uint64_t>& aZoomAnimationId); + Maybe<uint64_t> GetZoomAnimationId() const; + + /* =================================================================== + * The functions and members in this section are used to expose + * the current async transform state to callers. + */ + public: + /** + * Allows consumers of async transforms to specify for what purpose they are + * using the async transform: + * + * |eForHitTesting| is intended for hit-testing and other uses that need + * the most up-to-date transform, reflecting all events + * that have been processed so far, even if the transform + * is not yet reflected visually. + * |eForCompositing| is intended for the transform that should be reflected + * visually. + * + * For example, if an APZC has metrics with the mForceDisableApz flag set, + * then the |eForCompositing| async transform will be empty, while the + * |eForHitTesting| async transform will reflect processed input events + * regardless of mForceDisableApz. + */ + enum AsyncTransformConsumer { + eForHitTesting, + eForCompositing, + }; + + /** + * Get the current scroll offset of the scrollable frame corresponding + * to this APZC, including the effects of any asynchronous panning and + * zooming, in ParentLayer pixels. + */ + ParentLayerPoint GetCurrentAsyncScrollOffset( + AsyncTransformConsumer aMode) const; + + /** + * Get the current scroll offset of the scrollable frame corresponding + * to this APZC, including the effects of any asynchronous panning, in + * CSS pixels. + */ + CSSPoint GetCurrentAsyncScrollOffsetInCssPixels( + AsyncTransformConsumer aMode) const; + + /** + * Return a visual effect that reflects this apzc's + * overscrolled state, if any. + */ + AsyncTransformComponentMatrix GetOverscrollTransform( + AsyncTransformConsumer aMode) const; + + /** + * Returns the incremental transformation corresponding to the async pan/zoom + * in progress. That is, when this transform is multiplied with the layer's + * existing transform, it will make the layer appear with the desired pan/zoom + * amount. + * The transform can have both scroll and zoom components; the caller can + * request just one or the other, or both, via the |aComponents| parameter. + * When only the eLayout component is requested, the returned translation + * should really be a LayerPoint, rather than a ParentLayerPoint, as it will + * not be scaled by the asynchronous zoom. + * |aMode| specifies whether the async transform is queried for the purpose of + * hit testing (eHitTesting) in which case the latest values from |Metrics()| + * are used, or for compositing (eCompositing) in which case a sampled value + * from |mSampledState| is used. + * |aSampleIndex| specifies which sample in |mSampledState| to use. + */ + AsyncTransform GetCurrentAsyncTransform( + AsyncTransformConsumer aMode, + AsyncTransformComponents aComponents = LayoutAndVisual, + std::size_t aSampleIndex = 0) const; + + /** + * Returns the same transform as GetCurrentAsyncTransform(), but includes + * any transform due to axis over-scroll. + */ + AsyncTransformComponentMatrix GetCurrentAsyncTransformWithOverscroll( + AsyncTransformConsumer aMode, + AsyncTransformComponents aComponents = LayoutAndVisual, + std::size_t aSampleIndex = 0) const; + + AutoTArray<wr::SampledScrollOffset, 2> GetSampledScrollOffsets() const; + + /** + * Returns the "zoom" bits of the transform. This includes both the rasterized + * (layout device to layer scale) and async (layer scale to parent layer + * scale) components of the zoom. + */ + LayoutDeviceToParentLayerScale GetCurrentPinchZoomScale( + AsyncTransformConsumer aMode) const; + + ParentLayerRect GetCompositionBounds() const { + RecursiveMutexAutoLock lock(mRecursiveMutex); + return mScrollMetadata.GetMetrics().GetCompositionBounds(); + } + + LayoutDeviceToLayerScale GetCumulativeResolution() const { + RecursiveMutexAutoLock lock(mRecursiveMutex); + return mScrollMetadata.GetMetrics().GetCumulativeResolution(); + } + + // Returns the delta for the given InputData. + ParentLayerPoint GetDeltaForEvent(const InputData& aEvent) const; + + /** + * Get the current scroll range of the scrollable frame coreesponding to this + * APZC. + */ + CSSRect GetCurrentScrollRangeInCssPixels() const; + + private: + /** + * Advances to the next sample, if there is one, the list of sampled states + * stored in mSampledState. This will make the result of + * |GetCurrentAsyncTransform(eForCompositing)| and similar functions reflect + * the async scroll offset and zoom of the next sample. See also + * SampleCompositedAsyncTransform which creates the samples. + */ + void AdvanceToNextSample(); + + /** + * Samples the composited async transform, storing the result into + * mSampledState. This will make the result of + * |GetCurrentAsyncTransform(eForCompositing)| and similar functions reflect + * the async scroll offset and zoom stored in |Metrics()| when the sample + * is activated via some future call to |AdvanceToNextSample|. + * + * Returns true if the newly sampled value is different from the last + * sampled value. + */ + bool SampleCompositedAsyncTransform( + const RecursiveMutexAutoLock& aProofOfLock); + + /** + * Updates the sample at the front of mSampledState with the latest + * metrics. This makes the result of + * |GetCurrentAsyncTransform(eForCompositing)| reflect the current Metrics(). + */ + void ResampleCompositedAsyncTransform( + const RecursiveMutexAutoLock& aProofOfLock); + + /* + * Helper functions to query the async layout viewport, scroll offset, and + * zoom either directly from |Metrics()|, or from cached variables that + * store the required value from the last time it was sampled by calling + * SampleCompositedAsyncTransform(), depending on who is asking. + */ + CSSRect GetEffectiveLayoutViewport(AsyncTransformConsumer aMode, + const RecursiveMutexAutoLock& aProofOfLock, + std::size_t aSampleIndex = 0) const; + CSSPoint GetEffectiveScrollOffset(AsyncTransformConsumer aMode, + const RecursiveMutexAutoLock& aProofOfLock, + std::size_t aSampleIndex = 0) const; + CSSToParentLayerScale GetEffectiveZoom( + AsyncTransformConsumer aMode, const RecursiveMutexAutoLock& aProofOfLock, + std::size_t aSampleIndex = 0) const; + + /** + * Returns the visible portion of the content scrolled by this APZC, in + * CSS pixels. The caller must have acquired the mRecursiveMutex lock. + */ + CSSRect GetVisibleRect(const RecursiveMutexAutoLock& aProofOfLock) const; + + /** + * Returns a pair of displacements both in logical/physical units for + * |aEvent|. + */ + std::tuple<ParentLayerPoint, ScreenPoint> GetDisplacementsForPanGesture( + const PanGestureInput& aEvent); + + private: + friend class AutoApplyAsyncTestAttributes; + + bool SuppressAsyncScrollOffset() const; + + /** + * Applies |mTestAsyncScrollOffset| and |mTestAsyncZoom| to this + * AsyncPanZoomController. Calls |SampleCompositedAsyncTransform| to ensure + * that the GetCurrentAsync* functions consider the test offset and zoom in + * their computations. + */ + void ApplyAsyncTestAttributes(const RecursiveMutexAutoLock& aProofOfLock); + + /** + * Sets this AsyncPanZoomController's FrameMetrics to |aPrevFrameMetrics| and + * calls |SampleCompositedAsyncTransform| to unapply any test values applied + * by |ApplyAsyncTestAttributes|. + */ + void UnapplyAsyncTestAttributes(const RecursiveMutexAutoLock& aProofOfLock, + const FrameMetrics& aPrevFrameMetrics, + const ParentLayerPoint& aPrevOverscroll); + + /* =================================================================== + * The functions and members in this section are used to manage + * the state that tracks what this APZC is doing with the input events. + */ + protected: + enum PanZoomState { + NOTHING, /* no touch-start events received */ + FLING, /* all touches removed, but we're still scrolling page */ + TOUCHING, /* one touch-start event received */ + + PANNING, /* panning the frame */ + PANNING_LOCKED_X, /* touch-start followed by move (i.e. panning with axis + lock) X axis */ + PANNING_LOCKED_Y, /* as above for Y axis */ + + PAN_MOMENTUM, /* like PANNING, but controlled by momentum PanGestureInput + events */ + + PINCHING, /* nth touch-start, where n > 1. this mode allows pan and zoom */ + ANIMATING_ZOOM, /* animated zoom to a new rect */ + OVERSCROLL_ANIMATION, /* Spring-based animation used to relieve overscroll + once the finger is lifted. */ + SMOOTH_SCROLL, /* Smooth scrolling to destination, with physics + controlled by prefs specific to the scroll origin. */ + SMOOTHMSD_SCROLL, /* SmoothMSD scrolling to destination. Used by + CSSOM-View smooth scroll-behavior */ + WHEEL_SCROLL, /* Smooth scrolling to a destination for a wheel event. */ + KEYBOARD_SCROLL, /* Smooth scrolling to a destination for a keyboard event. + */ + AUTOSCROLL, /* Autoscroll animation. */ + SCROLLBAR_DRAG /* Async scrollbar drag. */ + }; + // This is in theory protected by |mRecursiveMutex|; that is, it should be + // held whenever this is updated. In practice though... see bug 897017. + PanZoomState mState; + + AxisX mX; + AxisY mY; + + /** + * Returns wheter the given input state is a user pan-gesture. + * + * Note: momentum pan gesture states are not considered a panning state. + */ + static bool IsPanningState(PanZoomState aState); + + /** + * Returns wheter a delayed transform end is queued. + */ + bool IsDelayedTransformEndSet(); + + /** + * Returns wheter a delayed transform end is queued. + */ + void SetDelayedTransformEnd(bool aDelayedTransformEnd); + + /** + * Returns whether the specified PanZoomState does not need to be reset when + * a scroll offset update is processed. + */ + static bool CanHandleScrollOffsetUpdate(PanZoomState aState); + + /** + * Determine whether a main-thread scroll offset update should result in + * a call to CancelAnimation() (which interrupts in-progress animations and + * gestures). + * + * If the update is a relative update, |aRelativeDelta| contains its amount. + * If the update is not a relative update, GetMetrics() should already reflect + * the new offset at the time of the call. + */ + bool ShouldCancelAnimationForScrollUpdate( + const Maybe<CSSPoint>& aRelativeDelta); + + private: + friend class StateChangeNotificationBlocker; + /** + * A counter of how many StateChangeNotificationBlockers are active. + * A non-zero count will prevent state change notifications from + * being dispatched. Only code that holds mRecursiveMutex should touch this. + */ + int mNotificationBlockers; + + /** + * Helper to set the current state, without content controller events + * for the state change. This is useful in cases where the content + * controller events may need to be delayed. + */ + PanZoomState SetStateNoContentControllerDispatch(PanZoomState aNewState); + + /** + * Helper to set the current state. Holds the monitor before actually setting + * it and fires content controller events based on state changes. Always set + * the state using this call, do not set it directly. + */ + void SetState(PanZoomState aNewState); + /** + * Fire content controller notifications about state changes, assuming no + * StateChangeNotificationBlocker has been activated. + */ + void DispatchStateChangeNotification(PanZoomState aOldState, + PanZoomState aNewState); + /** + * Internal helpers for checking general state of this apzc. + */ + bool IsInTransformingState() const; + static bool IsTransformingState(PanZoomState aState); + + /* =================================================================== + * 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: + /** + * Flush a repaint request if one is needed, without throttling it with the + * paint throttler. + */ + void FlushRepaintForNewInputBlock(); + + /** + * Given an input event and the touch block it belongs to, check if the + * event can lead to a panning/zooming behavior. + * This is used for logic related to the pointer events spec (figuring out + * when to dispatch the pointercancel event), as well as an input to the + * computation of the APZHandledResult for the event (used on Android to + * govern dynamic toolbar and pull-to-refresh behaviour). + */ + PointerEventsConsumableFlags ArePointerEventsConsumable( + TouchBlockState* aBlock, const MultiTouchInput& aInput); + + /** + * Clear internal state relating to touch input handling. + */ + void ResetTouchInputState(); + + /** + Clear internal state relating to pan gesture input handling. + */ + void ResetPanGestureInputState(); + + /** + * Gets a ref to the input queue that is shared across the entire tree + * manager. + */ + const RefPtr<InputQueue>& GetInputQueue() const; + + private: + void CancelAnimationAndGestureState(); + + RefPtr<InputQueue> mInputQueue; + InputBlockState* GetCurrentInputBlock() const; + TouchBlockState* GetCurrentTouchBlock() const; + bool HasReadyTouchBlock() const; + + PanGestureBlockState* GetCurrentPanGestureBlock() const; + PinchGestureBlockState* GetCurrentPinchGestureBlock() const; + + private: + /* =================================================================== + * The functions and members in this section are used to manage + * fling animations, smooth scroll animations, and overscroll + * during a fling or smooth scroll. + */ + public: + /** + * Attempt a fling with the velocity specified in |aHandoffState|. + * |aHandoffState.mIsHandoff| should be true iff. the fling was handed off + * from a previous APZC, and determines whether acceleration is applied + * to the fling. + * We only accept the fling in the direction(s) in which we are pannable. + * Returns the "residual velocity", i.e. the portion of + * |aHandoffState.mVelocity| that this APZC did not consume. + */ + ParentLayerPoint AttemptFling(const FlingHandoffState& aHandoffState); + + ParentLayerPoint AdjustHandoffVelocityForOverscrollBehavior( + ParentLayerPoint& aHandoffVelocity) const; + + private: + friend class StackScrollerFlingAnimation; + friend class AutoscrollAnimation; + template <typename FlingPhysics> + friend class GenericFlingAnimation; + friend class AndroidFlingPhysics; + friend class DesktopFlingPhysics; + friend class OverscrollAnimation; + friend class SmoothMsdScrollAnimation; + friend class GenericScrollAnimation; + friend class WheelScrollAnimation; + friend class ZoomAnimation; + + friend class GenericOverscrollEffect; + friend class WidgetOverscrollEffect; + friend struct apz::AsyncScrollThumbTransformer; + + FlingAccelerator mFlingAccelerator; + + // Indicates if the repaint-during-pinch timer is currently set + bool mPinchPaintTimerSet; + + // Indicates a delayed transform end notification is queued, and the + // transform-end timer is currently set. mRecursiveMutex must be held + // when using or modifying this member. + bool mDelayedTransformEnd; + + // Deal with overscroll resulting from a fling animation. This is only ever + // called on APZC instances that were actually performing a fling. + // The overscroll is handled by trying to hand the fling off to an APZC + // later in the handoff chain, or if there are no takers, continuing the + // fling and entering an overscrolled state. + void HandleFlingOverscroll( + const ParentLayerPoint& aVelocity, SideBits aOverscrollSideBits, + const RefPtr<const OverscrollHandoffChain>& aOverscrollHandoffChain, + const RefPtr<const AsyncPanZoomController>& aScrolledApzc); + + void HandleSmoothScrollOverscroll(const ParentLayerPoint& aVelocity, + SideBits aOverscrollSideBits); + + // Start an overscroll animation with the given initial velocity. + void StartOverscrollAnimation(const ParentLayerPoint& aVelocity, + SideBits aOverscrollSideBits); + + // Start a smooth-scrolling animation to the given destination, with physics + // based on the prefs for the indicated origin. + void SmoothScrollTo(const CSSPoint& aDestination, + const ScrollOrigin& aOrigin); + + // Start a smooth-scrolling animation to the given destination, with MSD + // physics that is suited for scroll-snapping. + void SmoothMsdScrollTo(CSSSnapTarget&& aDestination, + ScrollTriggeredByScript aTriggeredByScript); + + // Returns whether overscroll is allowed during an event. + bool AllowScrollHandoffInCurrentBlock() const; + + // Invoked by the pinch repaint timer. + void DoDelayedRequestContentRepaint(); + + // Invoked by the on pan-end handler to ensure that scrollend is only + // fired once when a momentum pan or scroll snap is triggered as a + // result of the pan gesture. + void DoDelayedTransformEndNotification(PanZoomState aOldState); + + // Compute the number of ParentLayer pixels per (Screen) inch at the given + // point and in the given direction. + float ComputePLPPI(ParentLayerPoint aPoint, + ParentLayerPoint aDirection) const; + + Maybe<CSSPoint> GetCurrentAnimationDestination( + const RecursiveMutexAutoLock& aProofOfLock) const; + + /* =================================================================== + * The functions and members in this section are used to make ancestor chains + * out of APZC instances. These chains can only be walked or manipulated + * while holding the lock in the associated APZCTreeManager instance. + */ + public: + void SetParent(AsyncPanZoomController* aParent) { mParent = aParent; } + + AsyncPanZoomController* GetParent() const { return mParent; } + + /* Returns true if there is no APZC higher in the tree with the same + * layers id. + */ + bool HasNoParentWithSameLayersId() const { + return !mParent || (mParent->mLayersId != mLayersId); + } + + bool IsRootForLayersId() const { + RecursiveMutexAutoLock lock(mRecursiveMutex); + return mScrollMetadata.IsLayersIdRoot(); + } + + bool IsRootContent() const { + RecursiveMutexAutoLock lock(mRecursiveMutex); + return Metrics().IsRootContent(); + } + + private: + // |mTreeManager| belongs in this section but it's declaration is a bit + // further above due to initialization-order constraints. + + RefPtr<AsyncPanZoomController> mParent; + + /* =================================================================== + * The functions and members in this section are used for scrolling, + * including handing off scroll to another APZC, and overscrolling. + */ + + ScrollableLayerGuid::ViewID GetScrollId() const { + RecursiveMutexAutoLock lock(mRecursiveMutex); + return Metrics().GetScrollId(); + } + + public: + ScrollableLayerGuid::ViewID GetScrollHandoffParentId() const { + return mScrollMetadata.GetScrollParentId(); + } + + /** + * Attempt to scroll in response to a touch-move from |aStartPoint| to + * |aEndPoint|, which are in our (transformed) screen coordinates. + * Due to overscroll handling, there may not actually have been a touch-move + * at these points, but this function will scroll as if there had been. + * If this attempt causes overscroll (i.e. the layer cannot be scrolled + * by the entire amount requested), the overscroll is passed back to the + * tree manager via APZCTreeManager::DispatchScroll(). If the tree manager + * does not find an APZC further in the handoff chain to accept the + * overscroll, and this APZC is pannable, this APZC enters an overscrolled + * state. + * |aOverscrollHandoffChain| and |aOverscrollHandoffChainIndex| are used by + * the tree manager to keep track of which APZC to hand off the overscroll + * to; this function increments the chain and the index and passes it on to + * APZCTreeManager::DispatchScroll() in the event of overscroll. + * Returns true iff. this APZC, or an APZC further down the + * handoff chain, accepted the scroll (possibly entering an overscrolled + * state). If this returns false, the caller APZC knows that it should enter + * an overscrolled state itself if it can. + * aStartPoint and aEndPoint are modified depending on how much of the + * scroll gesture was consumed by APZCs in the handoff chain. + */ + bool AttemptScroll(ParentLayerPoint& aStartPoint, ParentLayerPoint& aEndPoint, + OverscrollHandoffState& aOverscrollHandoffState); + + void FlushRepaintForOverscrollHandoff(); + + /** + * If overscrolled, start a snap-back animation and return true. Even if not + * overscrolled, this function tries to snap back to if there's an applicable + * scroll snap point. + * Otherwise return false. + */ + bool SnapBackIfOverscrolled(); + + /** + * NOTE: Similar to above but this function doesn't snap back to the scroll + * snap point. + */ + bool SnapBackIfOverscrolledForMomentum(const ParentLayerPoint& aVelocity); + + /** + * 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 + * |RefPtr<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 RefPtr<const OverscrollHandoffChain>&|. This allows the + * function to copy it into the |RefPtr<const OverscrollHandoffChain>| + * that will store it, while avoiding an unnecessary copy (and thus + * AddRef() and Release()) when passing it. + */ + RefPtr<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(ParentLayerPoint& aStartPoint, + ParentLayerPoint& aEndPoint, + OverscrollHandoffState& aOverscrollHandoffState); + + void RecordScrollPayload(const TimeStamp& aTimeStamp); + + /** + * A helper function for overscrolling during panning. This is a wrapper + * around OverscrollBy() that also implements restrictions on entering + * overscroll based on the pan angle. + */ + void OverscrollForPanning(ParentLayerPoint& aOverscroll, + const ScreenPoint& aPanDistance); + + /** + * Try to overscroll by 'aOverscroll'. + * If we are pannable on a particular axis, that component of 'aOverscroll' + * is transferred to any existing overscroll. + */ + void OverscrollBy(ParentLayerPoint& aOverscroll); + + /* =================================================================== + * 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 SetAncestorTransform(const AncestorTransform& aAncestorTransform) { + mAncestorTransform = aAncestorTransform; + } + + Matrix4x4 GetAncestorTransform() const { + return mAncestorTransform.CombinedTransform(); + } + + bool AncestorTransformContainsPerspective() const { + return mAncestorTransform.ContainsPerspectiveTransform(); + } + + // Return the perspective transform component of the ancestor transform. + Matrix4x4 GetAncestorTransformPerspective() const { + return mAncestorTransform.GetPerspectiveTransform(); + } + + // Returns whether or not this apzc contains the given screen point within + // its composition bounds. + bool Contains(const ScreenIntPoint& aPoint) const; + + bool IsInOverscrollGutter(const ScreenPoint& aHitTestPoint) const; + bool IsInOverscrollGutter(const ParentLayerPoint& aHitTestPoint) const; + + bool IsOverscrolled() const; + + // IsPhysicallyOverscrolled() checks whether the APZC is overscrolled + // by an overscroll effect which applies a transform to the APZC's contents. + bool IsPhysicallyOverscrolled() const; + + private: + bool IsInInvalidOverscroll() const; + + public: + bool IsInPanningState() const; + + // Returns whether being in the middle of a gesture. E.g., this APZC has + // started handling a pan gesture but hasn't yet received pan-end, etc. + bool IsInScrollingGesture() const; + + private: + /* This is the cumulative CSS transform for all the layers from (and + * including) the parent APZC down to (but excluding) this one, and excluding + * any perspective transforms. */ + AncestorTransform mAncestorTransform; + + /* =================================================================== + * The functions and members in this section are used for testing + * and assertion purposes only. + */ + public: + /** + * Gets whether this APZC has performed async key scrolling. + */ + bool TestHasAsyncKeyScrolled() const { return mTestHasAsyncKeyScrolled; } + + /** + * Set an extra offset for testing async scrolling. + */ + void SetTestAsyncScrollOffset(const CSSPoint& aPoint); + /** + * Set an extra offset for testing async scrolling. + */ + void SetTestAsyncZoom(const LayerToParentLayerScale& aZoom); + + LayersId GetLayersId() const { return mLayersId; } + + bool IsAsyncZooming() const { + RecursiveMutexAutoLock lock(mRecursiveMutex); + return mState == PINCHING || mState == ANIMATING_ZOOM; + } + + private: + // The timestamp of the latest touch start event. + TimeStamp mTouchStartTime; + // Used for interpolating touch events that cross the touch-start + // tolerance threshold. + struct TouchSample { + ExternalPoint mPosition; + TimeStamp mTimeStamp; + }; + // Information about the latest touch event. + // This is only populated when we're in the TOUCHING state + // (and thus the last touch event has only one touch point). + TouchSample mLastTouch; + // The time duration between mTouchStartTime and the touchmove event that + // started the pan (the touchmove event that transitioned this APZC from the + // TOUCHING state to one of the PANNING* states). Only valid while this APZC + // is in a panning state. + TimeDuration mTouchStartRestingTimeBeforePan; + Maybe<ParentLayerCoord> mMinimumVelocityDuringPan; + // This variable needs to be protected by |mRecursiveMutex|. + ScrollSnapTargetIds mLastSnapTargetIds; + // Extra offset to add to the async scroll position for testing + CSSPoint mTestAsyncScrollOffset; + // Extra zoom to include in the aync zoom for testing + LayerToParentLayerScale mTestAsyncZoom; + uint8_t mTestAttributeAppliers; + // Flag to track whether or not this APZC has ever async key scrolled. + bool mTestHasAsyncKeyScrolled; + + /* =================================================================== + * The functions and members in this section are used for checkerboard + * recording. + */ + private: + // Helper function to update the in-progress checkerboard event, if any. + void UpdateCheckerboardEvent(const MutexAutoLock& aProofOfLock, + uint32_t aMagnitude); + + // Mutex protecting mCheckerboardEvent + Mutex mCheckerboardEventLock MOZ_UNANNOTATED; + // This is created when this APZC instance is first included as part of a + // composite. If a checkerboard event takes place, this is destroyed at the + // end of the event, and a new one is created on the next composite. + UniquePtr<CheckerboardEvent> mCheckerboardEvent; + // This is used to track the total amount of time that we could reasonably + // be checkerboarding. Combined with other info, this allows us to + // meaningfully say how frequently users actually encounter checkerboarding. + PotentialCheckerboardDurationTracker mPotentialCheckerboardTracker; + + /* =================================================================== + * The functions in this section are used for CSS scroll snapping. + */ + + // If moving |aStartPosition| by |aDelta| should trigger scroll snapping, + // adjust |aDelta| to reflect the snapping (that is, make it a delta that will + // take us to the desired snap point). The delta is interpreted as being + // relative to |aStartPosition|, and if a target snap point is found, + // |aStartPosition| is also updated, to the value of the snap point. + // |aUnit| affects the snapping behaviour (see ScrollSnapUtils:: + // GetSnapPointForDestination). + // Returns true iff. a target snap point was found. + Maybe<CSSSnapTarget> MaybeAdjustDeltaForScrollSnapping( + ScrollUnit aUnit, ScrollSnapFlags aSnapFlags, ParentLayerPoint& aDelta, + CSSPoint& aStartPosition); + + // A wrapper function of MaybeAdjustDeltaForScrollSnapping for + // ScrollWheelInput. + Maybe<CSSSnapTarget> MaybeAdjustDeltaForScrollSnappingOnWheelInput( + const ScrollWheelInput& aEvent, ParentLayerPoint& aDelta, + CSSPoint& aStartPosition); + + Maybe<CSSSnapTarget> MaybeAdjustDestinationForScrollSnapping( + const KeyboardInput& aEvent, CSSPoint& aDestination, + ScrollSnapFlags aSnapFlags); + + // Snap to a snap position nearby the current scroll position, if appropriate. + void ScrollSnap(ScrollSnapFlags aSnapFlags); + + // Snap to a snap position nearby the destination predicted based on the + // current velocity, if appropriate. + void ScrollSnapToDestination(); + + // Snap to a snap position nearby the provided destination, if appropriate. + void ScrollSnapNear(const CSSPoint& aDestination, ScrollSnapFlags aSnapFlags); + + // Find a snap point near |aDestination| that we should snap to. + // Returns the snap point if one was found, or an empty Maybe otherwise. + // |aUnit| affects the snapping behaviour (see ScrollSnapUtils:: + // GetSnapPointForDestination). It should generally be determined by the + // type of event that's triggering the scroll. + Maybe<CSSSnapTarget> FindSnapPointNear(const CSSPoint& aDestination, + ScrollUnit aUnit, + ScrollSnapFlags aSnapFlags); + + // If |aOriginalEvent| crosses the touch-start tolerance threshold, split it + // into two events: one that just reaches the threshold, and the remainder. + // + // |aPanThreshold| is the touch-start tolerance, and |aVectorLength| is + // the length of the vector from the touch-start position to |aOriginalEvent|. + // These values could be computed from |aOriginalEvent| but they are + // passed in for convenience since the caller also needs to compute them. + // + // |aExtPoint| is the position of |aOriginalEvent| in External coordinates, + // and in case of a split is modified by the function to reflect the position + // of of the first event. This is a workaround for the fact that recomputing + // the External position from the returned event would require a round-trip + // through |mScreenPoint| which is an integer. + Maybe<std::pair<MultiTouchInput, MultiTouchInput>> MaybeSplitTouchMoveEvent( + const MultiTouchInput& aOriginalEvent, ScreenCoord aPanThreshold, + float aVectorLength, ExternalPoint& aExtPoint); + + friend std::ostream& operator<<( + std::ostream& aOut, const AsyncPanZoomController::PanZoomState& aState); +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_PanZoomController_h |