Source code

Revision control

Other Tools

/* -*- 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/. */
#include "AsyncPanZoomController.h" // for AsyncPanZoomController, etc
#include <math.h> // for fabsf, fabs, atan2
#include <stdint.h> // for uint32_t, uint64_t
#include <sys/types.h> // for int32_t
#include <algorithm> // for max, min
#include "APZCTreeManager.h" // for APZCTreeManager
#include "AsyncPanZoomAnimation.h" // for AsyncPanZoomAnimation
#include "AutoDirWheelDeltaAdjuster.h" // for APZAutoDirWheelDeltaAdjuster
#include "AutoscrollAnimation.h" // for AutoscrollAnimation
#include "Axis.h" // for AxisX, AxisY, Axis, etc
#include "CheckerboardEvent.h" // for CheckerboardEvent
#include "Compositor.h" // for Compositor
#include "DesktopFlingPhysics.h" // for DesktopFlingPhysics
#include "FrameMetrics.h" // for FrameMetrics, etc
#include "GenericFlingAnimation.h" // for GenericFlingAnimation
#include "GestureEventListener.h" // for GestureEventListener
#include "HitTestingTreeNode.h" // for HitTestingTreeNode
#include "InputData.h" // for MultiTouchInput, etc
#include "InputBlockState.h" // for InputBlockState, TouchBlockState
#include "InputQueue.h" // for InputQueue
#include "Overscroll.h" // for OverscrollAnimation
#include "OverscrollHandoffState.h" // for OverscrollHandoffState
#include "SimpleVelocityTracker.h" // for SimpleVelocityTracker
#include "Units.h" // for CSSRect, CSSPoint, etc
#include "UnitTransforms.h" // for TransformTo
#include "base/message_loop.h" // for MessageLoop
#include "base/task.h" // for NewRunnableMethod, etc
#include "gfxTypes.h" // for gfxFloat
#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc
#include "mozilla/BasicEvents.h" // for Modifiers, MODIFIER_*
#include "mozilla/ClearOnShutdown.h" // for ClearOnShutdown
#include "mozilla/ComputedTimingFunction.h" // for ComputedTimingFunction
#include "mozilla/EventForwards.h" // for nsEventStatus_*
#include "mozilla/EventStateManager.h" // for EventStateManager
#include "mozilla/MouseEvents.h" // for WidgetWheelEvent
#include "mozilla/Preferences.h" // for Preferences
#include "mozilla/RecursiveMutex.h" // for RecursiveMutexAutoLock, etc
#include "mozilla/RefPtr.h" // for RefPtr
#include "mozilla/ScrollTypes.h"
#include "mozilla/StaticPrefs_apz.h"
#include "mozilla/StaticPrefs_general.h"
#include "mozilla/StaticPrefs_gfx.h"
#include "mozilla/StaticPrefs_mousewheel.h"
#include "mozilla/StaticPrefs_layers.h"
#include "mozilla/StaticPrefs_layout.h"
#include "mozilla/StaticPrefs_slider.h"
#include "mozilla/StaticPrefs_test.h"
#include "mozilla/StaticPrefs_toolkit.h"
#include "mozilla/Telemetry.h" // for Telemetry
#include "mozilla/TimeStamp.h" // for TimeDuration, TimeStamp
#include "mozilla/dom/CheckerboardReportService.h" // for CheckerboardEventStorage
// note: CheckerboardReportService.h actually lives in gfx/layers/apz/util/
#include "mozilla/dom/Touch.h" // for Touch
#include "mozilla/gfx/gfxVars.h" // for gfxVars
#include "mozilla/gfx/BasePoint.h" // for BasePoint
#include "mozilla/gfx/BaseRect.h" // for BaseRect
#include "mozilla/gfx/Point.h" // for Point, RoundedToInt, etc
#include "mozilla/gfx/Rect.h" // for RoundedIn
#include "mozilla/gfx/ScaleFactor.h" // for ScaleFactor
#include "mozilla/layers/APZThreadUtils.h" // for AssertOnControllerThread, etc
#include "mozilla/layers/APZUtils.h" // for AsyncTransform
#include "mozilla/layers/CompositorController.h" // for CompositorController
#include "mozilla/layers/DirectionUtils.h" // for GetAxis{Start,End,Length,Scale}
#include "mozilla/mozalloc.h" // for operator new, etc
#include "mozilla/Unused.h" // for unused
#include "mozilla/FloatingPoint.h" // for FuzzyEquals*
#include "nsAlgorithm.h" // for clamped
#include "nsCOMPtr.h" // for already_AddRefed
#include "nsDebug.h" // for NS_WARNING
#include "nsLayoutUtils.h"
#include "nsMathUtils.h" // for NS_hypot
#include "nsPoint.h" // for nsIntPoint
#include "nsStyleConsts.h"
#include "nsTimingFunction.h"
#include "nsTArray.h" // for nsTArray, nsTArray_Impl, etc
#include "nsThreadUtils.h" // for NS_IsMainThread
#include "nsViewportInfo.h" // for kViewportMinScale, kViewportMaxScale
#include "prsystem.h" // for PR_GetPhysicalMemorySize
#include "mozilla/ipc/SharedMemoryBasic.h" // for SharedMemoryBasic
#include "ScrollSnap.h" // for ScrollSnapUtils
#include "ScrollAnimationPhysics.h" // for ComputeAcceleratedWheelDelta
#include "SmoothMsdScrollAnimation.h"
#include "SmoothScrollAnimation.h"
#include "WheelScrollAnimation.h"
#if defined(MOZ_WIDGET_ANDROID)
# include "AndroidAPZ.h"
#endif // defined(MOZ_WIDGET_ANDROID)
static mozilla::LazyLogModule sApzCtlLog("apz.controller");
#define APZC_LOG(...) MOZ_LOG(sApzCtlLog, LogLevel::Debug, (__VA_ARGS__))
#define APZC_LOGV(...) MOZ_LOG(sApzCtlLog, LogLevel::Verbose, (__VA_ARGS__))
#define APZC_LOG_FM_COMMON(fm, prefix, level, ...) \
if (MOZ_LOG_TEST(sApzCtlLog, level)) { \
std::stringstream ss; \
ss << nsPrintfCString(prefix, __VA_ARGS__).get() << ":" << fm; \
MOZ_LOG(sApzCtlLog, level, ("%s\n", ss.str().c_str())); \
}
#define APZC_LOG_FM(fm, prefix, ...) \
APZC_LOG_FM_COMMON(fm, prefix, LogLevel::Debug, __VA_ARGS__)
#define APZC_LOGV_FM(fm, prefix, ...) \
APZC_LOG_FM_COMMON(fm, prefix, LogLevel::Verbose, __VA_ARGS__)
namespace mozilla {
namespace layers {
typedef mozilla::layers::AllowedTouchBehavior AllowedTouchBehavior;
typedef GeckoContentController::APZStateChange APZStateChange;
typedef GeckoContentController::TapType TapType;
typedef mozilla::gfx::Point Point;
typedef mozilla::gfx::Matrix4x4 Matrix4x4;
// Choose between platform-specific implementations.
#ifdef MOZ_WIDGET_ANDROID
typedef WidgetOverscrollEffect OverscrollEffect;
typedef AndroidSpecificState PlatformSpecificState;
#else
typedef GenericOverscrollEffect OverscrollEffect;
typedef PlatformSpecificStateBase
PlatformSpecificState; // no extra state, just use the base class
#endif
/**
* \page APZCPrefs APZ preferences
*
* The following prefs are used to control the behaviour of the APZC.
* The default values are provided in StaticPrefList.yaml.
*
* \li\b apz.allow_double_tap_zooming
* Pref that allows or disallows double tap to zoom
*
* \li\b apz.allow_immediate_handoff
* If set to true, scroll can be handed off from one APZC to another within
* a single input block. If set to false, a single input block can only
* scroll one APZC.
*
* \li\b apz.allow_zooming_out
* If set to true, APZ will allow zooming out past the initial scale on
* desktop. This is false by default to match Chrome's behaviour.
*
* \li\b apz.android.chrome_fling_physics.friction
* A tunable parameter for Chrome fling physics on Android that governs
* how quickly a fling animation slows down due to friction (and therefore
* also how far it reaches). Should be in the range [0-1].
*
* \li\b apz.android.chrome_fling_physics.inflexion
* A tunable parameter for Chrome fling physics on Android that governs
* the shape of the fling curve. Should be in the range [0-1].
*
* \li\b apz.android.chrome_fling_physics.stop_threshold
* A tunable parameter for Chrome fling physics on Android that governs
* how close the fling animation has to get to its target destination
* before it stops.
* Units: ParentLayer pixels
*
* \li\b apz.autoscroll.enabled
* If set to true, autoscrolling is driven by APZ rather than the content
* process main thread.
*
* \li\b apz.axis_lock.mode
* The preferred axis locking style. See AxisLockMode for possible values.
*
* \li\b apz.axis_lock.lock_angle
* Angle from axis within which we stay axis-locked.\n
* Units: radians
*
* \li\b apz.axis_lock.breakout_threshold
* Distance in inches the user must pan before axis lock can be broken.\n
* Units: (real-world, i.e. screen) inches
*
* \li\b apz.axis_lock.breakout_angle
* Angle at which axis lock can be broken.\n
* Units: radians
*
* \li\b apz.axis_lock.direct_pan_angle
* If the angle from an axis to the line drawn by a pan move is less than
* this value, we can assume that panning can be done in the allowed direction
* (horizontal or vertical).\n
* Currently used only for touch-action css property stuff and was addded to
* keep behaviour consistent with IE.\n
* Units: radians
*
* \li\b apz.content_response_timeout
* Amount of time before we timeout response from content. For example, if
* content is being unruly/slow and we don't get a response back within this
* time, we will just pretend that content did not preventDefault any touch
* events we dispatched to it.\n
* Units: milliseconds
*
* \li\b apz.danger_zone_x
* \li\b apz.danger_zone_y
* When drawing high-res tiles, we drop down to drawing low-res tiles
* when we know we can't keep up with the scrolling. The way we determine
* this is by checking if we are entering the "danger zone", which is the
* boundary of the painted content. For example, if the painted content
* goes from y=0...1000 and the visible portion is y=250...750 then
* we're far from checkerboarding. If we get to y=490...990 though then we're
* only 10 pixels away from showing checkerboarding so we are probably in
* a state where we can't keep up with scrolling. The danger zone prefs specify
* how wide this margin is; in the above example a y-axis danger zone of 10
* pixels would make us drop to low-res at y=490...990.\n
* This value is in layer pixels.
*
* \li\b apz.disable_for_scroll_linked_effects
* Setting this pref to true will disable APZ scrolling on documents where
* scroll-linked effects are detected. A scroll linked effect is detected if
* positioning or transform properties are updated inside a scroll event
* dispatch; we assume that such an update is in response to the scroll event
* and is therefore a scroll-linked effect which will be laggy with APZ
* scrolling.
*
* \li\b apz.displayport_expiry_ms
* While a scrollable frame is scrolling async, we set a displayport on it
* to make sure it is layerized. However this takes up memory, so once the
* scrolling stops we want to remove the displayport. This pref controls how
* long after scrolling stops the displayport is removed. A value of 0 will
* disable the expiry behavior entirely.
* Units: milliseconds
*
* \li\b apz.drag.enabled
* Setting this pref to true will cause APZ to handle mouse-dragging of
* scrollbar thumbs.
*
* \li\b apz.drag.initial.enabled
* Setting this pref to true will cause APZ to try to handle mouse-dragging
* of scrollbar thumbs without an initial round-trip to content to start it
* if possible. Only has an effect if apz.drag.enabled is also true.
*
* \li\b apz.drag.touch.enabled
* Setting this pref to true will cause APZ to handle touch-dragging of
* scrollbar thumbs. Only has an effect if apz.drag.enabled is also true.
*
* \li\b apz.enlarge_displayport_when_clipped
* Pref that enables enlarging of the displayport along one axis when the
* generated displayport's size is beyond that of the scrollable rect on the
* opposite axis.
*
* \li\b apz.fling_accel_min_fling_velocity
* The minimum velocity of the second fling, and the minimum velocity of the
* previous fling animation at the point of interruption, for the new fling to
* be considered for fling acceleration.
* Units: screen pixels per milliseconds
*
* \li\b apz.fling_accel_min_pan_velocity
* The minimum velocity during the pan gesture that causes a fling for that
* fling to be considered for fling acceleration.
* Units: screen pixels per milliseconds
*
* \li\b apz.fling_accel_max_pause_interval_ms
* The maximum time that is allowed to elapse between the touch start event that
* interrupts the previous fling, and the touch move that initiates panning for
* the current fling, for that fling to be considered for fling acceleration.
* Units: milliseconds
*
* \li\b apz.fling_accel_base_mult
* \li\b apz.fling_accel_supplemental_mult
* When applying an acceleration on a fling, the new computed velocity is
* (new_fling_velocity * base_mult) + (old_velocity * supplemental_mult).
* The base_mult and supplemental_mult multiplier values are controlled by
* these prefs. Note that "old_velocity" here is the initial velocity of the
* previous fling _after_ acceleration was applied to it (if applicable).
*
* \li\b apz.fling_curve_function_x1
* \li\b apz.fling_curve_function_y1
* \li\b apz.fling_curve_function_x2
* \li\b apz.fling_curve_function_y2
* \li\b apz.fling_curve_threshold_inches_per_ms
* These five parameters define a Bezier curve function and threshold used to
* increase the actual velocity relative to the user's finger velocity. When the
* finger velocity is below the threshold (or if the threshold is not positive),
* the velocity is used as-is. If the finger velocity exceeds the threshold
* velocity, then the function defined by the curve is applied on the part of
* the velocity that exceeds the threshold. Note that the upper bound of the
* velocity is still specified by the \b apz.max_velocity_inches_per_ms pref,
* and the function will smoothly curve the velocity from the threshold to the
* max. In general the function parameters chosen should define an ease-out
* curve in order to increase the velocity in this range, or an ease-in curve to
* decrease the velocity. A straight-line curve is equivalent to disabling the
* curve entirely by setting the threshold to -1. The max velocity pref must
* also be set in order for the curving to take effect, as it defines the upper
* bound of the velocity curve.\n
* The points (x1, y1) and (x2, y2) used as the two intermediate control points
* in the cubic bezier curve; the first and last points are (0,0) and (1,1).\n
* Some example values for these prefs can be found at\n
*
* \li\b apz.fling_friction
* Amount of friction applied during flings. This is used in the following
* formula: v(t1) = v(t0) * (1 - f)^(t1 - t0), where v(t1) is the velocity
* for a new sample, v(t0) is the velocity at the previous sample, f is the
* value of this pref, and (t1 - t0) is the amount of time, in milliseconds,
* that has elapsed between the two samples.\n
* NOTE: Not currently used in Android fling calculations.
*
* \li\b apz.fling_min_velocity_threshold
* Minimum velocity for a fling to actually kick off. If the user pans and lifts
* their finger such that the velocity is smaller than or equal to this amount,
* no fling is initiated.\n
* Units: screen pixels per millisecond
*
* \li\b apz.fling_stop_on_tap_threshold
* When flinging, if the velocity is above this number, then a tap on the
* screen will stop the fling without dispatching a tap to content. If the
* velocity is below this threshold a tap will also be dispatched.
* Note: when modifying this pref be sure to run the APZC gtests as some of
* them depend on the value of this pref.\n
* Units: screen pixels per millisecond
*
* \li\b apz.fling_stopped_threshold
* When flinging, if the velocity goes below this number, we just stop the
* animation completely. This is to prevent asymptotically approaching 0
* velocity and rerendering unnecessarily.\n
* Units: screen pixels per millisecond.\n
* NOTE: Should not be set to anything
* other than 0.0 for Android except for tests to disable flings.
*
* \li\b apz.keyboard.enabled
* Determines whether scrolling with the keyboard will be allowed to be handled
* by APZ.
*
* \li\b apz.keyboard.passive-listeners
* When enabled, APZ will interpret the passive event listener flag to mean
* that the event listener won't change the focused element or selection of
* the page. With this, web content can use passive key listeners and not have
* keyboard APZ disabled.
*
* \li\b apz.max_tap_time
* Maximum time for a touch on the screen and corresponding lift of the finger
* to be considered a tap. This also applies to double taps, except that it is
* used both for the interval between the first touchdown and first touchup,
* and for the interval between the first touchup and the second touchdown.\n
* Units: milliseconds.
*
* \li\b apz.max_velocity_inches_per_ms
* Maximum velocity. Velocity will be capped at this value if a faster fling
* occurs. Negative values indicate unlimited velocity.\n
* Units: (real-world, i.e. screen) inches per millisecond
*
* \li\b apz.max_velocity_queue_size
* Maximum size of velocity queue. The queue contains last N velocity records.
* On touch end we calculate the average velocity in order to compensate
* touch/mouse drivers misbehaviour.
*
* \li\b apz.min_skate_speed
* Minimum amount of speed along an axis before we switch to "skate" multipliers
* rather than using the "stationary" multipliers.\n
* Units: CSS pixels per millisecond
*
* \li\b apz.one_touch_pinch.enabled
* Whether or not the "one-touch-pinch" gesture (for zooming with one finger)
* is enabled or not.
*
* \li\b apz.overscroll.enabled
* Pref that enables overscrolling. If this is disabled, excess scroll that
* cannot be handed off is discarded.
*
* \li\b apz.overscroll.min_pan_distance_ratio
* The minimum ratio of the pan distance along one axis to the pan distance
* along the other axis needed to initiate overscroll along the first axis
* during panning.
*
* \li\b apz.overscroll.stretch_factor
* How much overscrolling can stretch content along an axis.
* The maximum stretch along an axis is a factor of (1 + kStretchFactor).
* (So if kStretchFactor is 0, you can't stretch at all; if kStretchFactor
* is 1, you can stretch at most by a factor of 2).
*
* \li\b apz.overscroll.stop_distance_threshold
* \li\b apz.overscroll.stop_velocity_threshold
* Thresholds for stopping the overscroll animation. When both the distance
* and the velocity fall below their thresholds, we stop oscillating.\n
* Units: screen pixels (for distance)
* screen pixels per millisecond (for velocity)
*
* \li\b apz.overscroll.spring_stiffness
* The spring stiffness constant for the overscroll mass-spring-damper model.
*
* \li\b apz.overscroll.damping
* The damping constant for the overscroll mass-spring-damper model.
*
* \li\b apz.overscroll.max_velocity
* The maximum velocity (in ParentLayerPixels per millisecond) allowed when
* initiating the overscroll snap-back animation.
*
* \li\b apz.paint_skipping.enabled
* When APZ is scrolling and sending repaint requests to the main thread, often
* the main thread doesn't actually need to do a repaint. This pref allows the
* main thread to skip doing those repaints in cases where it doesn't need to.
*
* \li\b apz.pinch_lock.mode
* The preferred pinch locking style. See PinchLockMode for possible values.
*
* \li\b apz.pinch_lock.scroll_lock_threshold
* Pinch locking is triggered if the user scrolls more than this distance
* and pinches less than apz.pinch_lock.span_lock_threshold.\n
* Units: (real-world, i.e. screen) inches
*
* \li\b apz.pinch_lock.span_breakout_threshold
* Distance in inches the user must pinch before lock can be broken.\n
* Units: (real-world, i.e. screen) inches measured between two touch points
*
* \li\b apz.pinch_lock.span_lock_threshold
* Pinch locking is triggered if the user pinches less than this distance
* and scrolls more than apz.pinch_lock.scroll_lock_threshold.\n
* Units: (real-world, i.e. screen) inches measured between two touch points
*
* \li\b apz.pinch_lock.buffer_max_age
* To ensure that pinch locking threshold calculations are not affected by
* variations in touch screen sensitivity, calculations draw from a buffer of
* recent events. This preference specifies the maximum time that events are
* held in this buffer.
* Units: milliseconds
*
* \li\b apz.popups.enabled
* Determines whether APZ is used for XUL popup widgets with remote content.
* Ideally, this should always be true, but it is currently not well tested, and
* has known issues, so needs to be prefable.
*
* \li\b apz.record_checkerboarding
* Whether or not to record detailed info on checkerboarding events.
*
* \li\b apz.second_tap_tolerance
* Constant describing the tolerance in distance we use, multiplied by the
* device DPI, within which a second tap is counted as part of a gesture
* continuing from the first tap. Making this larger allows the user more
* distance between the first and second taps in a "double tap" or "one touch
* pinch" gesture.\n
* Units: (real-world, i.e. screen) inches
*
* \li\b apz.test.logging_enabled
* Enable logging of APZ test data (see bug 961289).
*
* \li\b apz.touch_move_tolerance
* See the description for apz.touch_start_tolerance below. This is a similar
* threshold, except it is used to suppress touchmove events from being
* delivered to content for NON-scrollable frames (or more precisely, for APZCs
* where ArePointerEventsConsumable returns false).\n Units: (real-world, i.e.
* screen) inches
*
* \li\b apz.touch_start_tolerance
* 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. touchmove events are also not delivered to content
* within this distance on scrollable frames.\n
* Units: (real-world, i.e. screen) inches
*
* \li\b apz.velocity_bias
* How much to adjust the displayport in the direction of scrolling. This value
* is multiplied by the velocity and added to the displayport offset.
*
* \li\b apz.velocity_relevance_time_ms
* When computing a fling velocity from the most recently stored velocity
* information, only velocities within the most X milliseconds are used.
* This pref controls the value of X.\n
* Units: ms
*
* \li\b apz.x_skate_size_multiplier
* \li\b apz.y_skate_size_multiplier
* The multiplier we apply to the displayport size if it is skating (current
* velocity is above \b apz.min_skate_speed). We prefer to increase the size of
* the Y axis because it is more natural in the case that a user is reading a
* page page that scrolls up/down. Note that one, both or neither of these may
* be used at any instant.\n In general we want \b
* apz.[xy]_skate_size_multiplier to be smaller than the corresponding
* stationary size multiplier because when panning fast we would like to paint
* less and get faster, more predictable paint times. When panning slowly we
* can afford to paint more even though it's slower.
*
* \li\b apz.x_stationary_size_multiplier
* \li\b apz.y_stationary_size_multiplier
* The multiplier we apply to the displayport size if it is not skating (see
* documentation for the skate size multipliers above).
*
* \li\b apz.x_skate_highmem_adjust
* \li\b apz.y_skate_highmem_adjust
* On high memory systems, we adjust the displayport during skating
* to be larger so we can reduce checkerboarding.
*
* \li\b apz.zoom_animation_duration_ms
* This controls how long the zoom-to-rect animation takes.\n
* Units: ms
*
* \li\b apz.scale_repaint_delay_ms
* How long to delay between repaint requests during a scale.
* A negative number prevents repaint requests during a scale.\n
* Units: ms
*/
/**
* Computed time function used for sampling frames of a zoom to animation.
*/
StaticAutoPtr<ComputedTimingFunction> gZoomAnimationFunction;
/**
* Computed time function used for curving up velocity when it gets high.
*/
StaticAutoPtr<ComputedTimingFunction> gVelocityCurveFunction;
/**
* The estimated duration of a paint for the purposes of calculating a new
* displayport, in milliseconds.
*/
static const double kDefaultEstimatedPaintDurationMs = 50;
/**
* Returns true if this is a high memory system and we can use
* extra memory for a larger displayport to reduce checkerboarding.
*/
static bool gIsHighMemSystem = false;
static bool IsHighMemSystem() { return gIsHighMemSystem; }
AsyncPanZoomAnimation* PlatformSpecificStateBase::CreateFlingAnimation(
AsyncPanZoomController& aApzc, const FlingHandoffState& aHandoffState,
float aPLPPI) {
return new GenericFlingAnimation<DesktopFlingPhysics>(aApzc, aHandoffState,
aPLPPI);
}
UniquePtr<VelocityTracker> PlatformSpecificStateBase::CreateVelocityTracker(
Axis* aAxis) {
return MakeUnique<SimpleVelocityTracker>(aAxis);
}
SampleTime AsyncPanZoomController::GetFrameTime() const {
APZCTreeManager* treeManagerLocal = GetApzcTreeManager();
return treeManagerLocal ? treeManagerLocal->GetFrameTime()
: SampleTime::FromNow();
}
class MOZ_STACK_CLASS StateChangeNotificationBlocker final {
public:
explicit StateChangeNotificationBlocker(AsyncPanZoomController* aApzc)
: mApzc(aApzc) {
RecursiveMutexAutoLock lock(mApzc->mRecursiveMutex);
mInitialState = mApzc->mState;
mApzc->mNotificationBlockers++;
}
~StateChangeNotificationBlocker() {
AsyncPanZoomController::PanZoomState newState;
{
RecursiveMutexAutoLock lock(mApzc->mRecursiveMutex);
mApzc->mNotificationBlockers--;
newState = mApzc->mState;
}
mApzc->DispatchStateChangeNotification(mInitialState, newState);
}
private:
AsyncPanZoomController* mApzc;
AsyncPanZoomController::PanZoomState mInitialState;
};
/**
* An RAII class to temporarily apply async test attributes to the provided
* AsyncPanZoomController.
*
* This class should be used in the implementation of any AsyncPanZoomController
* method that queries the async scroll offset or async zoom (this includes
* the async layout viewport offset, since modifying the async scroll offset
* may result in the layout viewport moving as well).
*/
class MOZ_RAII AutoApplyAsyncTestAttributes final {
public:
explicit AutoApplyAsyncTestAttributes(
const AsyncPanZoomController*,
const RecursiveMutexAutoLock& aProofOfLock);
~AutoApplyAsyncTestAttributes();
private:
AsyncPanZoomController* mApzc;
FrameMetrics mPrevFrameMetrics;
ParentLayerPoint mPrevOverscroll;
const RecursiveMutexAutoLock& mProofOfLock;
};
AutoApplyAsyncTestAttributes::AutoApplyAsyncTestAttributes(
const AsyncPanZoomController* aApzc,
const RecursiveMutexAutoLock& aProofOfLock)
// Having to use const_cast here seems less ugly than the alternatives
// of making several members of AsyncPanZoomController that
// ApplyAsyncTestAttributes() modifies |mutable|, or several methods that
// query the async transforms non-const.
: mApzc(const_cast<AsyncPanZoomController*>(aApzc)),
mPrevFrameMetrics(aApzc->Metrics()),
mPrevOverscroll(aApzc->GetOverscrollAmountInternal()),
mProofOfLock(aProofOfLock) {
mApzc->ApplyAsyncTestAttributes(aProofOfLock);
}
AutoApplyAsyncTestAttributes::~AutoApplyAsyncTestAttributes() {
mApzc->UnapplyAsyncTestAttributes(mProofOfLock, mPrevFrameMetrics,
mPrevOverscroll);
}
class ZoomAnimation : public AsyncPanZoomAnimation {
public:
ZoomAnimation(AsyncPanZoomController& aApzc, const CSSPoint& aStartOffset,
const CSSToParentLayerScale2D& aStartZoom,
const CSSPoint& aEndOffset,
const CSSToParentLayerScale2D& aEndZoom)
: mApzc(aApzc),
mTotalDuration(TimeDuration::FromMilliseconds(
StaticPrefs::apz_zoom_animation_duration_ms())),
mStartOffset(aStartOffset),
mStartZoom(aStartZoom),
mEndOffset(aEndOffset),
mEndZoom(aEndZoom) {}
virtual bool DoSample(FrameMetrics& aFrameMetrics,
const TimeDuration& aDelta) override {
mDuration += aDelta;
double animPosition = mDuration / mTotalDuration;
if (animPosition >= 1.0) {
aFrameMetrics.SetZoom(mEndZoom);
mApzc.SetVisualScrollOffset(mEndOffset);
return false;
}
// Sample the zoom at the current time point. The sampled zoom
// will affect the final computed resolution.
float sampledPosition = gZoomAnimationFunction->GetValue(
animPosition, ComputedTimingFunction::BeforeFlag::Unset);
// We scale the scrollOffset linearly with sampledPosition, so the zoom
// needs to scale inversely to match.
if (mStartZoom == CSSToParentLayerScale2D(0, 0) ||
mEndZoom == CSSToParentLayerScale2D(0, 0)) {
return false;
}
aFrameMetrics.SetZoom(CSSToParentLayerScale2D(
1 / (sampledPosition / mEndZoom.xScale +
(1 - sampledPosition) / mStartZoom.xScale),
1 / (sampledPosition / mEndZoom.yScale +
(1 - sampledPosition) / mStartZoom.yScale)));
mApzc.SetVisualScrollOffset(CSSPoint::FromUnknownPoint(gfx::Point(
mEndOffset.x * sampledPosition + mStartOffset.x * (1 - sampledPosition),
mEndOffset.y * sampledPosition +
mStartOffset.y * (1 - sampledPosition))));
return true;
}
virtual bool WantsRepaints() override { return true; }
private:
AsyncPanZoomController& mApzc;
TimeDuration mDuration;
const TimeDuration mTotalDuration;
// Old metrics from before we started a zoom animation. This is only valid
// when we are in the "ANIMATED_ZOOM" state. This is used so that we can
// interpolate between the start and end frames. We only use the
// |mViewportScrollOffset| and |mResolution| fields on this.
CSSPoint mStartOffset;
CSSToParentLayerScale2D mStartZoom;
// Target metrics for a zoom to animation. This is only valid when we are in
// the "ANIMATED_ZOOM" state. We only use the |mViewportScrollOffset| and
// |mResolution| fields on this.
CSSPoint mEndOffset;
CSSToParentLayerScale2D mEndZoom;
};
/*static*/
void AsyncPanZoomController::InitializeGlobalState() {
static bool sInitialized = false;
if (sInitialized) return;
sInitialized = true;
MOZ_ASSERT(NS_IsMainThread());
gZoomAnimationFunction =
new ComputedTimingFunction(nsTimingFunction(StyleTimingKeyword::Ease));
ClearOnShutdown(&gZoomAnimationFunction);
gVelocityCurveFunction = new ComputedTimingFunction(
nsTimingFunction(StaticPrefs::apz_fling_curve_function_x1_AtStartup(),
StaticPrefs::apz_fling_curve_function_y1_AtStartup(),
StaticPrefs::apz_fling_curve_function_x2_AtStartup(),
StaticPrefs::apz_fling_curve_function_y2_AtStartup()));
ClearOnShutdown(&gVelocityCurveFunction);
uint64_t sysmem = PR_GetPhysicalMemorySize();
uint64_t threshold = 1LL << 32; // 4 GB in bytes
gIsHighMemSystem = sysmem >= threshold;
PlatformSpecificState::InitializeGlobalState();
}
AsyncPanZoomController::AsyncPanZoomController(
LayersId aLayersId, APZCTreeManager* aTreeManager,
const RefPtr<InputQueue>& aInputQueue,
GeckoContentController* aGeckoContentController, GestureBehavior aGestures)
: mLayersId(aLayersId),
mGeckoContentController(aGeckoContentController),
mRefPtrMonitor("RefPtrMonitor"),
// mTreeManager must be initialized before GetFrameTime() is called
mTreeManager(aTreeManager),
mRecursiveMutex("AsyncPanZoomController"),
mLastContentPaintMetrics(mLastContentPaintMetadata.GetMetrics()),
mX(this),
mY(this),
mPanDirRestricted(false),
mPinchLocked(false),
mPinchEventBuffer(TimeDuration::FromMilliseconds(
StaticPrefs::apz_pinch_lock_buffer_max_age_AtStartup())),
mZoomConstraints(false, false,
mScrollMetadata.GetMetrics().GetDevPixelsPerCSSPixel() *
kViewportMinScale / ParentLayerToScreenScale(1),
mScrollMetadata.GetMetrics().GetDevPixelsPerCSSPixel() *
kViewportMaxScale / ParentLayerToScreenScale(1)),
mLastSampleTime(GetFrameTime()),
mLastCheckerboardReport(GetFrameTime()),
mOverscrollEffect(MakeUnique<OverscrollEffect>(*this)),
mState(NOTHING),
mNotificationBlockers(0),
mInputQueue(aInputQueue),
mPinchPaintTimerSet(false),
mTestAttributeAppliers(0),
mTestHasAsyncKeyScrolled(false),
mCheckerboardEventLock("APZCBELock") {
if (aGestures == USE_GESTURE_DETECTOR) {
mGestureEventListener = new GestureEventListener(this);
}
// Put one default-constructed sampled state in the queue.
RecursiveMutexAutoLock lock(mRecursiveMutex);
mSampledState.emplace_back();
}
AsyncPanZoomController::~AsyncPanZoomController() { MOZ_ASSERT(IsDestroyed()); }
PlatformSpecificStateBase* AsyncPanZoomController::GetPlatformSpecificState() {
if (!mPlatformSpecificState) {
mPlatformSpecificState = MakeUnique<PlatformSpecificState>();
}
return mPlatformSpecificState.get();
}
already_AddRefed<GeckoContentController>
AsyncPanZoomController::GetGeckoContentController() const {
MonitorAutoLock lock(mRefPtrMonitor);
RefPtr<GeckoContentController> controller = mGeckoContentController;
return controller.forget();
}
already_AddRefed<GestureEventListener>
AsyncPanZoomController::GetGestureEventListener() const {
MonitorAutoLock lock(mRefPtrMonitor);
RefPtr<GestureEventListener> listener = mGestureEventListener;
return listener.forget();
}
const RefPtr<InputQueue>& AsyncPanZoomController::GetInputQueue() const {
return mInputQueue;
}
void AsyncPanZoomController::Destroy() {
AssertOnUpdaterThread();
CancelAnimation(CancelAnimationFlags::ScrollSnap);
{ // scope the lock
MonitorAutoLock lock(mRefPtrMonitor);
mGeckoContentController = nullptr;
mGestureEventListener = nullptr;
}
mParent = nullptr;
mTreeManager = nullptr;
}
bool AsyncPanZoomController::IsDestroyed() const {
return mTreeManager == nullptr;
}
float AsyncPanZoomController::GetDPI() const {
if (APZCTreeManager* localPtr = mTreeManager) {
return localPtr->GetDPI();
}
// If this APZC has been destroyed then this value is not going to be
// used for anything that the user will end up seeing, so we can just
// return 0.
return 0.0;
}
ScreenCoord AsyncPanZoomController::GetTouchStartTolerance() const {
return (StaticPrefs::apz_touch_start_tolerance() * GetDPI());
}
ScreenCoord AsyncPanZoomController::GetTouchMoveTolerance() const {
return (StaticPrefs::apz_touch_move_tolerance() * GetDPI());
}
ScreenCoord AsyncPanZoomController::GetSecondTapTolerance() const {
return (StaticPrefs::apz_second_tap_tolerance() * GetDPI());
}
/* static */ AsyncPanZoomController::AxisLockMode
AsyncPanZoomController::GetAxisLockMode() {
return static_cast<AxisLockMode>(StaticPrefs::apz_axis_lock_mode());
}
/* static */ AsyncPanZoomController::PinchLockMode
AsyncPanZoomController::GetPinchLockMode() {
return static_cast<PinchLockMode>(StaticPrefs::apz_pinch_lock_mode());
}
bool AsyncPanZoomController::ArePointerEventsConsumable(
TouchBlockState* aBlock, const MultiTouchInput& aInput) {
uint32_t touchPoints = aInput.mTouches.Length();
if (touchPoints == 0) {
// Cant' do anything with zero touch points
return false;
}
// This logic is simplified, erring on the side of returning true if we're
// not sure. It's safer to pretend that we can consume the event and then
// not be able to than vice-versa. But at the same time, we should try hard
// to return an accurate result, because returning true can trigger a
// pointercancel event to web content, which can break certain features
// that are using touch-action and handling the pointermove events.
//
// Note that in particular this function can return true if APZ is waiting on
// the main thread for touch-action information. In this scenario, the
// APZEventState::MainThreadAgreesEventsAreConsumableByAPZ() function tries
// to use the main-thread touch-action information to filter out false
// positives.
//
// We could probably enhance this logic to determine things like "we're
// not pannable, so we can only zoom in, and the zoom is already maxed
// out, so we're not zoomable either" but no need for that at this point.
bool pannableX = aBlock->TouchActionAllowsPanningX() &&
aBlock->GetOverscrollHandoffChain()->CanScrollInDirection(
this, ScrollDirection::eHorizontal);
bool pannableY =
(aBlock->TouchActionAllowsPanningY() &&
(aBlock->GetOverscrollHandoffChain()->CanScrollInDirection(
this, ScrollDirection::eVertical) ||
// In the case of the root APZC with any dynamic toolbar, it
// shoule be pannable if there is room moving the dynamic
// toolbar.
(IsRootContent() && CanVerticalScrollWithDynamicToolbar())));
bool pannable;
Maybe<ScrollDirection> panDirection =
aBlock->GetBestGuessPanDirection(aInput);
if (panDirection == Some(ScrollDirection::eVertical)) {
pannable = pannableY;
} else if (panDirection == Some(ScrollDirection::eHorizontal)) {
pannable = pannableX;
} else {
// If we don't have a guessed pan direction, err on the side of returning
// true.
pannable = pannableX || pannableY;
}
if (touchPoints == 1) {
return pannable;
}
bool zoomable = ZoomConstraintsAllowZoom();
zoomable &= (aBlock->TouchActionAllowsPinchZoom());
return pannable || zoomable;
}
nsEventStatus AsyncPanZoomController::HandleDragEvent(
const MouseInput& aEvent, const AsyncDragMetrics& aDragMetrics,
CSSCoord aInitialThumbPos) {
// RDM is a special case where touch events will be synthesized in response
// to mouse events, and APZ will receive both even though RDM prevent-defaults
// the mouse events. This is because mouse events don't opt into APZ waiting
// to check if the event has been prevent-defaulted and are still processed
// as a result. To handle this, have APZ ignore mouse events when RDM and
// touch simulation are active.
bool isRDMTouchSimulationActive = false;
{
RecursiveMutexAutoLock lock(mRecursiveMutex);
isRDMTouchSimulationActive =
mScrollMetadata.GetIsRDMTouchSimulationActive();
}
if (!StaticPrefs::apz_drag_enabled() || isRDMTouchSimulationActive) {
return nsEventStatus_eIgnore;
}
if (!GetApzcTreeManager()) {
return nsEventStatus_eConsumeNoDefault;
}
{
RecursiveMutexAutoLock lock(mRecursiveMutex);
if (aEvent.mType == MouseInput::MouseType::MOUSE_UP) {
if (mState == SCROLLBAR_DRAG) {
APZC_LOG("%p ending drag\n", this);
SetState(NOTHING);
}
SnapBackIfOverscrolled();
return nsEventStatus_eConsumeNoDefault;
}
}
HitTestingTreeNodeAutoLock node;
GetApzcTreeManager()->FindScrollThumbNode(aDragMetrics, mLayersId, node);
if (!node) {
APZC_LOG("%p unable to find scrollthumb node with viewid %" PRIu64 "\n",
this, aDragMetrics.mViewId);
return nsEventStatus_eConsumeNoDefault;
}
if (aEvent.mType == MouseInput::MouseType::MOUSE_DOWN) {
APZC_LOG("%p starting scrollbar drag\n", this);
SetState(SCROLLBAR_DRAG);
}
if (aEvent.mType != MouseInput::MouseType::MOUSE_MOVE) {
APZC_LOG("%p discarding event of type %d\n", this, aEvent.mType);
return nsEventStatus_eConsumeNoDefault;
}
const ScrollbarData& scrollbarData = node->GetScrollbarData();
MOZ_ASSERT(scrollbarData.mScrollbarLayerType ==
layers::ScrollbarLayerType::Thumb);
MOZ_ASSERT(scrollbarData.mDirection.isSome());
ScrollDirection direction = *scrollbarData.mDirection;
bool isMouseAwayFromThumb = false;
if (int snapMultiplier = StaticPrefs::slider_snapMultiplier_AtStartup()) {
// It's fine to ignore the async component of the thumb's transform,
// because any async transform of the thumb will be in the direction of
// scrolling, but here we're interested in the other direction.
ParentLayerRect thumbRect =
(node->GetTransform() * AsyncTransformMatrix())
.TransformBounds(LayerRect(node->GetVisibleRegion().GetBounds()));
ScrollDirection otherDirection = GetPerpendicularDirection(direction);
ParentLayerCoord distance =
GetAxisStart(otherDirection, thumbRect.DistanceTo(aEvent.mLocalOrigin));
ParentLayerCoord thumbWidth = GetAxisLength(otherDirection, thumbRect);
// Avoid triggering this condition spuriously when the thumb is
// offscreen and its visible region is therefore empty.
if (thumbWidth > 0 && thumbWidth * snapMultiplier < distance) {
isMouseAwayFromThumb = true;
APZC_LOG("%p determined mouse is away from thumb, will snap\n", this);
}
}
RecursiveMutexAutoLock lock(mRecursiveMutex);
CSSCoord thumbPosition;
if (isMouseAwayFromThumb) {
thumbPosition = aInitialThumbPos;
} else {
thumbPosition = ConvertScrollbarPoint(aEvent.mLocalOrigin, scrollbarData) -
aDragMetrics.mScrollbarDragOffset;
}
CSSCoord maxThumbPos = scrollbarData.mScrollTrackLength;
maxThumbPos -= scrollbarData.mThumbLength;
float scrollPercent =
maxThumbPos.value == 0.0f ? 0.0f : (float)(thumbPosition / maxThumbPos);
APZC_LOG("%p scrollbar dragged to %f percent\n", this, scrollPercent);
CSSCoord minScrollPosition =
GetAxisStart(direction, Metrics().GetScrollableRect().TopLeft());
CSSCoord maxScrollPosition =
GetAxisStart(direction, Metrics().GetScrollableRect().BottomRight()) -
GetAxisLength(direction, Metrics().CalculateCompositedSizeInCssPixels());
CSSCoord scrollPosition =
minScrollPosition +
(scrollPercent * (maxScrollPosition - minScrollPosition));
scrollPosition = std::max(scrollPosition, minScrollPosition);
scrollPosition = std::min(scrollPosition, maxScrollPosition);
CSSPoint scrollOffset = Metrics().GetVisualScrollOffset();
if (direction == ScrollDirection::eHorizontal) {
scrollOffset.x = scrollPosition;
} else {
scrollOffset.y = scrollPosition;
}
APZC_LOG("%p set scroll offset to %s from scrollbar drag\n", this,
ToString(scrollOffset).c_str());
SetVisualScrollOffset(scrollOffset);
ScheduleCompositeAndMaybeRepaint();
return nsEventStatus_eConsumeNoDefault;
}
nsEventStatus AsyncPanZoomController::HandleInputEvent(
const InputData& aEvent,
const ScreenToParentLayerMatrix4x4& aTransformToApzc) {
APZThreadUtils::AssertOnControllerThread();
nsEventStatus rv = nsEventStatus_eIgnore;
switch (aEvent.mInputType) {
case MULTITOUCH_INPUT: {
MultiTouchInput multiTouchInput = aEvent.AsMultiTouchInput();
RefPtr<GestureEventListener> listener = GetGestureEventListener();
if (listener) {
// We only care about screen coordinates in the gesture listener,
// so we don't bother transforming the event to parent layer coordinates
rv = listener->HandleInputEvent(multiTouchInput);
if (rv == nsEventStatus_eConsumeNoDefault) {
return rv;
}
}
if (!multiTouchInput.TransformToLocal(aTransformToApzc)) {
return rv;
}
switch (multiTouchInput.mType) {
case MultiTouchInput::MULTITOUCH_START:
rv = OnTouchStart(multiTouchInput);
break;
case MultiTouchInput::MULTITOUCH_MOVE:
rv = OnTouchMove(multiTouchInput);
break;
case MultiTouchInput::MULTITOUCH_END:
rv = OnTouchEnd(multiTouchInput);
break;
case MultiTouchInput::MULTITOUCH_CANCEL:
rv = OnTouchCancel(multiTouchInput);
break;
}
break;
}
case PANGESTURE_INPUT: {
PanGestureInput panGestureInput = aEvent.AsPanGestureInput();
if (!panGestureInput.TransformToLocal(aTransformToApzc)) {
return rv;
}
switch (panGestureInput.mType) {
case PanGestureInput::PANGESTURE_MAYSTART:
rv = OnPanMayBegin(panGestureInput);
break;
case PanGestureInput::PANGESTURE_CANCELLED:
rv = OnPanCancelled(panGestureInput);
break;
case PanGestureInput::PANGESTURE_START:
rv = OnPanBegin(panGestureInput);
break;
case PanGestureInput::PANGESTURE_PAN:
rv = OnPan(panGestureInput, FingersOnTouchpad::Yes);
break;
case PanGestureInput::PANGESTURE_END:
rv = OnPanEnd(panGestureInput);
break;
case PanGestureInput::PANGESTURE_MOMENTUMSTART:
rv = OnPanMomentumStart(panGestureInput);
break;
case PanGestureInput::PANGESTURE_MOMENTUMPAN:
rv = OnPan(panGestureInput, FingersOnTouchpad::No);
break;
case PanGestureInput::PANGESTURE_MOMENTUMEND:
rv = OnPanMomentumEnd(panGestureInput);
break;
case PanGestureInput::PANGESTURE_INTERRUPTED:
rv = OnPanInterrupted(panGestureInput);
break;
}
break;
}
case MOUSE_INPUT: {
MouseInput mouseInput = aEvent.AsMouseInput();
if (!mouseInput.TransformToLocal(aTransformToApzc)) {
return rv;
}
break;
}
case SCROLLWHEEL_INPUT: {
ScrollWheelInput scrollInput = aEvent.AsScrollWheelInput();
if (!scrollInput.TransformToLocal(aTransformToApzc)) {
return rv;
}
rv = OnScrollWheel(scrollInput);
break;
}
case PINCHGESTURE_INPUT: {
// The APZCTreeManager should take care of ensuring that only root-content
// APZCs get pinch inputs.
MOZ_ASSERT(IsRootContent());
PinchGestureInput pinchInput = aEvent.AsPinchGestureInput();
if (!pinchInput.TransformToLocal(aTransformToApzc)) {
return rv;
}
rv = HandleGestureEvent(pinchInput);
break;
}
case TAPGESTURE_INPUT: {
TapGestureInput tapInput = aEvent.AsTapGestureInput();
if (!tapInput.TransformToLocal(aTransformToApzc)) {
return rv;
}
rv = HandleGestureEvent(tapInput);
break;
}
case KEYBOARD_INPUT: {
const KeyboardInput& keyInput = aEvent.AsKeyboardInput();
rv = OnKeyboard(keyInput);
break;
}
}
return rv;
}
nsEventStatus AsyncPanZoomController::HandleGestureEvent(
const InputData& aEvent) {
APZThreadUtils::AssertOnControllerThread();
nsEventStatus rv = nsEventStatus_eIgnore;
switch (aEvent.mInputType) {
case PINCHGESTURE_INPUT: {
// This may be invoked via a one-touch-pinch gesture from
// GestureEventListener. In that case we want redirect it to the enclosing
// root-content APZC.
if (!IsRootContent()) {
if (APZCTreeManager* treeManagerLocal = GetApzcTreeManager()) {
if (RefPtr<AsyncPanZoomController> root =
treeManagerLocal->FindZoomableApzc(this)) {
rv = root->HandleGestureEvent(aEvent);
}
}
break;
}
PinchGestureInput pinchGestureInput = aEvent.AsPinchGestureInput();
pinchGestureInput.TransformToLocal(GetTransformToThis());
switch (pinchGestureInput.mType) {
case PinchGestureInput::PINCHGESTURE_START:
rv = OnScaleBegin(pinchGestureInput);
break;
case PinchGestureInput::PINCHGESTURE_SCALE:
rv = OnScale(pinchGestureInput);
break;
case PinchGestureInput::PINCHGESTURE_FINGERLIFTED:
case PinchGestureInput::PINCHGESTURE_END:
rv = OnScaleEnd(pinchGestureInput);
break;
}
break;
}
case TAPGESTURE_INPUT: {
TapGestureInput tapGestureInput = aEvent.AsTapGestureInput();
tapGestureInput.TransformToLocal(GetTransformToThis());
switch (tapGestureInput.mType) {
case TapGestureInput::TAPGESTURE_LONG:
rv = OnLongPress(tapGestureInput);
break;
case TapGestureInput::TAPGESTURE_LONG_UP:
rv = OnLongPressUp(tapGestureInput);
break;
case TapGestureInput::TAPGESTURE_UP:
rv = OnSingleTapUp(tapGestureInput);
break;
case TapGestureInput::TAPGESTURE_CONFIRMED:
rv = OnSingleTapConfirmed(tapGestureInput);
break;
case TapGestureInput::TAPGESTURE_DOUBLE:
// This means that double tapping on an oop iframe "works" in that we
// don't try (and fail) to zoom the oop iframe. But it also means it
// is impossible to zoom to some content inside that oop iframe.
// Instead the best we can do is zoom to the oop iframe itself. This
// is consistent with what Chrome and Safari currently do. Allowing
// zooming to content inside an oop iframe would be decently
// complicated and it doesn't seem worth it. Bug 1715179 is on file
// for this.
if (!IsRootContent()) {
if (APZCTreeManager* treeManagerLocal = GetApzcTreeManager()) {
if (RefPtr<AsyncPanZoomController> root =
treeManagerLocal->FindZoomableApzc(this)) {
rv = root->OnDoubleTap(tapGestureInput);
}
}
break;
}
rv = OnDoubleTap(tapGestureInput);
break;
case TapGestureInput::TAPGESTURE_SECOND:
rv = OnSecondTap(tapGestureInput);
break;
case TapGestureInput::TAPGESTURE_CANCEL:
rv = OnCancelTap(tapGestureInput);
break;
}
break;
}
default:
MOZ_ASSERT_UNREACHABLE("Unhandled input event");
break;
}
return rv;
}
void AsyncPanZoomController::StartAutoscroll(const ScreenPoint& aPoint) {
// Cancel any existing animation.
CancelAnimation();
SetState(AUTOSCROLL);
StartAnimation(new AutoscrollAnimation(*this, aPoint));
}
void AsyncPanZoomController::StopAutoscroll() {
if (mState == AUTOSCROLL) {
CancelAnimation(TriggeredExternally);
}
}
nsEventStatus AsyncPanZoomController::OnTouchStart(
const MultiTouchInput& aEvent) {
APZC_LOG("%p got a touch-start in state %d\n", this, mState);
mPanDirRestricted = false;
switch (mState) {
case FLING:
case ANIMATING_ZOOM:
case SMOOTH_SCROLL:
case SMOOTHMSD_SCROLL:
case OVERSCROLL_ANIMATION:
case WHEEL_SCROLL:
case KEYBOARD_SCROLL:
case PAN_MOMENTUM:
case AUTOSCROLL:
MOZ_ASSERT(GetCurrentTouchBlock());
GetCurrentTouchBlock()->GetOverscrollHandoffChain()->CancelAnimations(
ExcludeOverscroll);
[[fallthrough]];
case SCROLLBAR_DRAG:
case NOTHING: {
ParentLayerPoint point = GetFirstTouchPoint(aEvent);
mStartTouch = GetFirstExternalTouchPoint(aEvent);
StartTouch(point, aEvent.mTimeStamp);
if (RefPtr<GeckoContentController> controller =
GetGeckoContentController()) {
MOZ_ASSERT(GetCurrentTouchBlock());
controller->NotifyAPZStateChange(
GetGuid(), APZStateChange::eStartTouch,
GetCurrentTouchBlock()->GetOverscrollHandoffChain()->CanBePanned(
this));
}
mTouchStartTime = aEvent.mTimeStamp;
SetState(TOUCHING);
break;
}
case TOUCHING:
case PANNING:
case PANNING_LOCKED_X:
case PANNING_LOCKED_Y:
case PINCHING:
NS_WARNING("Received impossible touch in OnTouchStart");
break;
}
return nsEventStatus_eConsumeNoDefault;
}
nsEventStatus AsyncPanZoomController::OnTouchMove(
const MultiTouchInput& aEvent) {
APZC_LOG("%p got a touch-move in state %d\n", this, mState);
switch (mState) {
case FLING:
case SMOOTHMSD_SCROLL:
case NOTHING:
case ANIMATING_ZOOM:
// May happen if the user double-taps and drags without lifting after the
// second tap. Ignore the move if this happens.
return nsEventStatus_eIgnore;
case TOUCHING: {
ScreenCoord panThreshold = GetTouchStartTolerance();
ExternalPoint extPoint = GetFirstExternalTouchPoint(aEvent);
// We intentionally skip the UpdateWithTouchAtDevicePoint call when the
// panThreshold is zero. This ensures more deterministic behaviour during
// testing. If we call that, Axis::mPos gets updated to the point of this
// touchmove event, but we "consume" the move to overcome the
// panThreshold, so it's hard to pan a specific amount reliably from a
// mochitest.
if (panThreshold > 0.0f) {
UpdateWithTouchAtDevicePoint(aEvent);
if (PanVector(extPoint).Length() < panThreshold) {
return nsEventStatus_eIgnore;
}
}
MOZ_ASSERT(GetCurrentTouchBlock());
if (StaticPrefs::layout_css_touch_action_enabled() &&
GetCurrentTouchBlock()->TouchActionAllowsPanningXY()) {
// User tries to trigger a touch behavior. If allowed touch behavior is
// vertical pan
// + horizontal pan (touch-action value is equal to AUTO) we can return
// ConsumeNoDefault status immediately to trigger cancel event further.
// It should happen independent of the parent type (whether it is
// scrolling or not).
StartPanning(extPoint, aEvent.mTimeStamp);
return nsEventStatus_eConsumeNoDefault;
}
return StartPanning(extPoint, aEvent.mTimeStamp);
}
case PANNING:
case PANNING_LOCKED_X:
case PANNING_LOCKED_Y:
case PAN_MOMENTUM:
TrackTouch(aEvent);
return nsEventStatus_eConsumeNoDefault;
case PINCHING:
// The scale gesture listener should have handled this.
NS_WARNING(
"Gesture listener should have handled pinching in OnTouchMove.");
return nsEventStatus_eIgnore;
case SMOOTH_SCROLL:
case WHEEL_SCROLL:
case KEYBOARD_SCROLL:
case OVERSCROLL_ANIMATION:
case AUTOSCROLL:
case SCROLLBAR_DRAG:
// Should not receive a touch-move in the OVERSCROLL_ANIMATION state
// as touch blocks that begin in an overscrolled state cancel the
// animation. The same is true for wheel scroll animations.
NS_WARNING("Received impossible touch in OnTouchMove");
break;
}
return nsEventStatus_eConsumeNoDefault;
}
nsEventStatus AsyncPanZoomController::OnTouchEnd(
const MultiTouchInput& aEvent) {
APZC_LOG("%p got a touch-end in state %d\n", this, mState);
OnTouchEndOrCancel();
// In case no touch behavior triggered previously we can avoid sending
// scroll events or requesting content repaint. This condition is added
// to make tests consistent - in case touch-action is NONE (and therefore
// no pans/zooms can be performed) we expected neither scroll or repaint
// events.
if (mState != NOTHING) {
RecursiveMutexAutoLock lock(mRecursiveMutex);
}
switch (mState) {
case FLING:
// Should never happen.
NS_WARNING("Received impossible touch end in OnTouchEnd.");
[[fallthrough]];
case ANIMATING_ZOOM:
case SMOOTHMSD_SCROLL:
case NOTHING:
// May happen if the user double-taps and drags without lifting after the
// second tap. Ignore if this happens.
return nsEventStatus_eIgnore;
case TOUCHING:
// We may have some velocity stored on the axis from move events
// that were not big enough to trigger scrolling. Clear that out.
SetVelocityVector(ParentLayerPoint(0, 0));
MOZ_ASSERT(GetCurrentTouchBlock());
APZC_LOG("%p still has %u touch points active\n", this,
GetCurrentTouchBlock()->GetActiveTouchCount());
// In cases where the user is panning, then taps the second finger without
// entering a pinch, we will arrive here when the second finger is lifted.
// However the first finger is still down so we want to remain in state
// TOUCHING.
if (GetCurrentTouchBlock()->GetActiveTouchCount() == 0) {
// It's possible we may be overscrolled if the user tapped during a
// previous overscroll pan. Make sure to snap back in this situation.
// An ancestor APZC could be overscrolled instead of this APZC, so
// walk the handoff chain as well.
GetCurrentTouchBlock()
->GetOverscrollHandoffChain()
->SnapBackOverscrolledApzc(this);
mFlingAccelerator.Reset();
// SnapBackOverscrolledApzc() will put any APZC it causes to snap back
// into the OVERSCROLL_ANIMATION state. If that's not us, since we're
// done TOUCHING enter the NOTHING state.
if (mState != OVERSCROLL_ANIMATION) {
SetState(NOTHING);
}
}
return nsEventStatus_eIgnore;
case PANNING:
case PANNING_LOCKED_X:
case PANNING_LOCKED_Y:
case PAN_MOMENTUM: {
MOZ_ASSERT(GetCurrentTouchBlock());
EndTouch(aEvent.mTimeStamp);
return HandleEndOfPan();
}
case PINCHING:
SetState(NOTHING);
// Scale gesture listener should have handled this.
NS_WARNING(
"Gesture listener should have handled pinching in OnTouchEnd.");
return nsEventStatus_eIgnore;
case SMOOTH_SCROLL:
case WHEEL_SCROLL:
case KEYBOARD_SCROLL:
case OVERSCROLL_ANIMATION:
case AUTOSCROLL:
case SCROLLBAR_DRAG:
// Should not receive a touch-end in the OVERSCROLL_ANIMATION state
// as touch blocks that begin in an overscrolled state cancel the
// animation. The same is true for WHEEL_SCROLL.
NS_WARNING("Received impossible touch in OnTouchEnd");
break;
}
return nsEventStatus_eConsumeNoDefault;
}
nsEventStatus AsyncPanZoomController::OnTouchCancel(
const MultiTouchInput& aEvent) {
APZC_LOG("%p got a touch-cancel in state %d\n", this, mState);
OnTouchEndOrCancel();
CancelAnimationAndGestureState();
return nsEventStatus_eConsumeNoDefault;
}
nsEventStatus AsyncPanZoomController::OnScaleBegin(
const PinchGestureInput& aEvent) {
APZC_LOG("%p got a scale-begin in state %d\n", this, mState);
mPinchLocked = false;
mPinchPaintTimerSet = false;
// Note that there may not be a touch block at this point, if we received the
// PinchGestureEvent directly from widget code without any touch events.
if (HasReadyTouchBlock() &&
!GetCurrentTouchBlock()->TouchActionAllowsPinchZoom()) {
return nsEventStatus_eIgnore;
}
// If zooming is not allowed, this is a two-finger pan.
// Start tracking panning distance and velocity.
if (!ZoomConstraintsAllowZoom()) {
StartTouch(aEvent.mLocalFocusPoint, aEvent.mTimeStamp);
}
// For platforms that don't support APZ zooming, dispatch a message to the
// content controller, it may want to do something else with this gesture.
// FIXME: bug 1525793 -- this may need to handle zooming or not on a
// per-document basis.
if (!StaticPrefs::apz_allow_zooming()) {
if (RefPtr<GeckoContentController> controller =
GetGeckoContentController()) {
APZC_LOG("%p notifying controller of pinch gesture start\n", this);
controller->NotifyPinchGesture(
aEvent.mType, GetGuid(),
ViewAs<LayoutDevicePixel>(
aEvent.mFocusPoint,
PixelCastJustification::
LayoutDeviceIsScreenForUntransformedEvent),
0, aEvent.modifiers);
}
}
SetState(PINCHING);
Telemetry::Accumulate(Telemetry::APZ_ZOOM_PINCHSOURCE, (int)aEvent.mSource);
SetVelocityVector(ParentLayerPoint(0, 0));
RecursiveMutexAutoLock lock(mRecursiveMutex);
mLastZoomFocus =
aEvent.mLocalFocusPoint - Metrics().GetCompositionBounds().TopLeft();
mPinchEventBuffer.push(aEvent);
return nsEventStatus_eConsumeNoDefault;
}
nsEventStatus AsyncPanZoomController::OnScale(const PinchGestureInput& aEvent) {
APZC_LOG("%p got a scale in state %d\n", this, mState);
if (HasReadyTouchBlock() &&
!GetCurrentTouchBlock()->TouchActionAllowsPinchZoom()) {
return nsEventStatus_eIgnore;
}
if (mState != PINCHING) {
return nsEventStatus_eConsumeNoDefault;
}
mPinchEventBuffer.push(aEvent);
HandlePinchLocking(aEvent);
bool allowZoom = ZoomConstraintsAllowZoom() && !mPinchLocked;
// If zooming is not allowed, this is a two-finger pan.
// Tracking panning distance and velocity.
// UpdateWithTouchAtDevicePoint() acquires the tree lock, so
// it cannot be called while the mRecursiveMutex lock is held.
if (!allowZoom) {
mX.UpdateWithTouchAtDevicePoint(aEvent.mLocalFocusPoint.x,
aEvent.mTimeStamp);
mY.UpdateWithTouchAtDevicePoint(aEvent.mLocalFocusPoint.y,
aEvent.mTimeStamp);
}
// FIXME: bug 1525793 -- this may need to handle zooming or not on a
// per-document basis.
if (!StaticPrefs::apz_allow_zooming()) {
if (RefPtr<GeckoContentController> controller =
GetGeckoContentController()) {
APZC_LOG("%p notifying controller of pinch gesture\n", this);
controller->NotifyPinchGesture(
aEvent.mType, GetGuid(),
ViewAs<LayoutDevicePixel>(
aEvent.mFocusPoint,
PixelCastJustification::
LayoutDeviceIsScreenForUntransformedEvent),
ViewAs<LayoutDevicePixel>(
aEvent.mCurrentSpan - aEvent.mPreviousSpan,
PixelCastJustification::
LayoutDeviceIsScreenForUntransformedEvent),
aEvent.modifiers);
}
}
{