tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

AsyncPanZoomController.cpp (292965B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "AsyncPanZoomController.h"  // for AsyncPanZoomController, etc
      8 
      9 #include <math.h>       // for fabsf, fabs, atan2
     10 #include <stdint.h>     // for uint32_t, uint64_t
     11 #include <sys/types.h>  // for int32_t
     12 #include <algorithm>    // for max, min
     13 #include <utility>      // for std::make_pair
     14 
     15 #include "APZCTreeManager.h"            // for APZCTreeManager
     16 #include "AsyncPanZoomAnimation.h"      // for AsyncPanZoomAnimation
     17 #include "AutoDirWheelDeltaAdjuster.h"  // for APZAutoDirWheelDeltaAdjuster
     18 #include "AutoscrollAnimation.h"        // for AutoscrollAnimation
     19 #include "Axis.h"                       // for AxisX, AxisY, Axis, etc
     20 #include "CheckerboardEvent.h"          // for CheckerboardEvent
     21 #include "Compositor.h"                 // for Compositor
     22 #include "DesktopFlingPhysics.h"        // for DesktopFlingPhysics
     23 #include "FrameMetrics.h"               // for FrameMetrics, etc
     24 #include "GenericFlingAnimation.h"      // for GenericFlingAnimation
     25 #include "GestureEventListener.h"       // for GestureEventListener
     26 #include "HitTestingTreeNode.h"         // for HitTestingTreeNode
     27 #include "InputData.h"                  // for MultiTouchInput, etc
     28 #include "InputBlockState.h"            // for InputBlockState, TouchBlockState
     29 #include "InputQueue.h"                 // for InputQueue
     30 #include "Overscroll.h"                 // for OverscrollAnimation
     31 #include "OverscrollHandoffState.h"     // for OverscrollHandoffState
     32 #include "SimpleVelocityTracker.h"      // for SimpleVelocityTracker
     33 #include "Units.h"                      // for CSSRect, CSSPoint, etc
     34 #include "UnitTransforms.h"             // for TransformTo
     35 #include "apz/public/CompositorScrollUpdate.h"
     36 #include "base/message_loop.h"          // for MessageLoop
     37 #include "base/task.h"                  // for NewRunnableMethod, etc
     38 #include "gfxTypes.h"                   // for gfxFloat
     39 #include "mozilla/Assertions.h"         // for MOZ_ASSERT, etc
     40 #include "mozilla/BasicEvents.h"        // for Modifiers, MODIFIER_*
     41 #include "mozilla/ClearOnShutdown.h"    // for ClearOnShutdown
     42 #include "mozilla/ServoStyleConsts.h"   // for StyleComputedTimingFunction
     43 #include "mozilla/EventForwards.h"      // for nsEventStatus_*
     44 #include "mozilla/EventStateManager.h"  // for EventStateManager
     45 #include "mozilla/glean/GfxMetrics.h"
     46 #include "mozilla/MouseEvents.h"     // for WidgetWheelEvent
     47 #include "mozilla/Preferences.h"     // for Preferences
     48 #include "mozilla/RecursiveMutex.h"  // for RecursiveMutexAutoLock, etc
     49 #include "mozilla/RefPtr.h"          // for RefPtr
     50 #include "mozilla/ScrollTypes.h"
     51 #include "mozilla/StaticPrefs_apz.h"
     52 #include "mozilla/StaticPrefs_general.h"
     53 #include "mozilla/StaticPrefs_gfx.h"
     54 #include "mozilla/StaticPrefs_mousewheel.h"
     55 #include "mozilla/StaticPrefs_layers.h"
     56 #include "mozilla/StaticPrefs_layout.h"
     57 #include "mozilla/StaticPrefs_slider.h"
     58 #include "mozilla/StaticPrefs_test.h"
     59 #include "mozilla/StaticPrefs_toolkit.h"
     60 #include "mozilla/Telemetry.h"  // for Telemetry
     61 #include "mozilla/TimeStamp.h"  // for TimeDuration, TimeStamp
     62 #include "mozilla/dom/CheckerboardReportService.h"  // for CheckerboardEventStorage
     63 // note: CheckerboardReportService.h actually lives in gfx/layers/apz/util/
     64 #include "mozilla/dom/Touch.h"              // for Touch
     65 #include "mozilla/gfx/gfxVars.h"            // for gfxVars
     66 #include "mozilla/gfx/BasePoint.h"          // for BasePoint
     67 #include "mozilla/gfx/BaseRect.h"           // for BaseRect
     68 #include "mozilla/gfx/Point.h"              // for Point, RoundedToInt, etc
     69 #include "mozilla/gfx/Rect.h"               // for RoundedIn
     70 #include "mozilla/gfx/ScaleFactor.h"        // for ScaleFactor
     71 #include "mozilla/layers/APZThreadUtils.h"  // for AssertOnControllerThread, etc
     72 #include "mozilla/layers/APZUtils.h"        // for AsyncTransform
     73 #include "mozilla/layers/CompositorController.h"  // for CompositorController
     74 #include "mozilla/layers/DirectionUtils.h"  // for GetAxis{Start,End,Length,Scale}
     75 #include "mozilla/layers/DoubleTapToZoom.h"  // for ZoomTarget
     76 #include "mozilla/layers/APZPublicUtils.h"   // for GetScrollMode
     77 #include "mozilla/webrender/WebRenderAPI.h"  // for MinimapData
     78 #include "mozilla/mozalloc.h"                // for operator new, etc
     79 #include "mozilla/webrender/WebRenderTypes.h"
     80 #include "nsCOMPtr.h"  // for already_AddRefed
     81 #include "nsDebug.h"   // for NS_WARNING
     82 #include "nsLayoutUtils.h"
     83 #include "nsMathUtils.h"  // for NS_hypot
     84 #include "nsPoint.h"      // for nsIntPoint
     85 #include "nsStyleConsts.h"
     86 #include "nsTArray.h"        // for nsTArray, nsTArray_Impl, etc
     87 #include "nsThreadUtils.h"   // for NS_IsMainThread
     88 #include "nsViewportInfo.h"  // for ViewportMinScale(), ViewportMaxScale()
     89 #include "prsystem.h"        // for PR_GetPhysicalMemorySize
     90 #include "ScrollSnap.h"      // for ScrollSnapUtils
     91 #include "ScrollAnimationPhysics.h"  // for ComputeAcceleratedWheelDelta
     92 #include "SmoothScrollAnimation.h"
     93 #if defined(MOZ_WIDGET_ANDROID)
     94 #  include "AndroidAPZ.h"
     95 #endif  // defined(MOZ_WIDGET_ANDROID)
     96 
     97 static mozilla::LazyLogModule sApzCtlLog("apz.controller");
     98 #define APZC_LOG(...) MOZ_LOG(sApzCtlLog, LogLevel::Debug, (__VA_ARGS__))
     99 #define APZC_LOGV(...) MOZ_LOG(sApzCtlLog, LogLevel::Verbose, (__VA_ARGS__))
    100 
    101 // Log to the apz.controller log with additional info from the APZC
    102 #define APZC_LOG_DETAIL(fmt, apzc, ...)                   \
    103  APZC_LOG("%p(%s scrollId=%" PRIu64 "): " fmt, (apzc),   \
    104           (apzc)->IsRootContent() ? "root" : "subframe", \
    105           (apzc)->GetScrollId(), ##__VA_ARGS__)
    106 #define APZC_LOGV_DETAIL(fmt, apzc, ...)                   \
    107  APZC_LOGV("%p(%s scrollId=%" PRIu64 "): " fmt, (apzc),   \
    108            (apzc)->IsRootContent() ? "root" : "subframe", \
    109            (apzc)->GetScrollId(), ##__VA_ARGS__)
    110 
    111 #define APZC_LOG_FM_COMMON(fm, prefix, level, ...)                 \
    112  if (MOZ_LOG_TEST(sApzCtlLog, level)) {                           \
    113    std::stringstream ss;                                          \
    114    ss << nsPrintfCString(prefix, __VA_ARGS__).get() << ":" << fm; \
    115    MOZ_LOG(sApzCtlLog, level, ("%s\n", ss.str().c_str()));        \
    116  }
    117 #define APZC_LOG_FM(fm, prefix, ...) \
    118  APZC_LOG_FM_COMMON(fm, prefix, LogLevel::Debug, __VA_ARGS__)
    119 #define APZC_LOGV_FM(fm, prefix, ...) \
    120  APZC_LOG_FM_COMMON(fm, prefix, LogLevel::Verbose, __VA_ARGS__)
    121 
    122 namespace mozilla {
    123 namespace layers {
    124 
    125 typedef mozilla::layers::AllowedTouchBehavior AllowedTouchBehavior;
    126 typedef GeckoContentController::APZStateChange APZStateChange;
    127 typedef GeckoContentController::TapType TapType;
    128 typedef mozilla::gfx::Point Point;
    129 typedef mozilla::gfx::Matrix4x4 Matrix4x4;
    130 
    131 // Choose between platform-specific implementations.
    132 #ifdef MOZ_WIDGET_ANDROID
    133 typedef WidgetOverscrollEffect OverscrollEffect;
    134 typedef AndroidSpecificState PlatformSpecificState;
    135 #else
    136 typedef GenericOverscrollEffect OverscrollEffect;
    137 typedef PlatformSpecificStateBase
    138    PlatformSpecificState;  // no extra state, just use the base class
    139 #endif
    140 
    141 /**
    142 * \page APZCPrefs APZ preferences
    143 *
    144 * The following prefs are used to control the behaviour of the APZC.
    145 * The default values are provided in StaticPrefList.yaml.
    146 *
    147 * \li\b apz.allow_double_tap_zooming
    148 * Pref that allows or disallows double tap to zoom
    149 *
    150 * \li\b apz.allow_immediate_handoff
    151 * If set to true, scroll can be handed off from one APZC to another within
    152 * a single input block. If set to false, a single input block can only
    153 * scroll one APZC.
    154 *
    155 * \li\b apz.allow_zooming_out
    156 * If set to true, APZ will allow zooming out past the initial scale on
    157 * desktop. This is false by default to match Chrome's behaviour.
    158 *
    159 * \li\b apz.android.chrome_fling_physics.friction
    160 * A tunable parameter for Chrome fling physics on Android that governs
    161 * how quickly a fling animation slows down due to friction (and therefore
    162 * also how far it reaches). Should be in the range [0-1].
    163 *
    164 * \li\b apz.android.chrome_fling_physics.inflexion
    165 * A tunable parameter for Chrome fling physics on Android that governs
    166 * the shape of the fling curve. Should be in the range [0-1].
    167 *
    168 * \li\b apz.android.chrome_fling_physics.stop_threshold
    169 * A tunable parameter for Chrome fling physics on Android that governs
    170 * how close the fling animation has to get to its target destination
    171 * before it stops.
    172 * Units: ParentLayer pixels
    173 *
    174 * \li\b apz.autoscroll.enabled
    175 * If set to true, autoscrolling is driven by APZ rather than the content
    176 * process main thread.
    177 *
    178 * \li\b apz.axis_lock.mode
    179 * The preferred axis locking style. See AxisLockMode for possible values.
    180 *
    181 * \li\b apz.axis_lock.lock_angle
    182 * Angle from axis within which we stay axis-locked.\n
    183 * Units: radians
    184 *
    185 * \li\b apz.axis_lock.breakout_threshold
    186 * Distance in inches the user must pan before axis lock can be broken.\n
    187 * Units: (real-world, i.e. screen) inches
    188 *
    189 * \li\b apz.axis_lock.breakout_angle
    190 * Angle at which axis lock can be broken.\n
    191 * Units: radians
    192 *
    193 * \li\b apz.axis_lock.direct_pan_angle
    194 * If the angle from an axis to the line drawn by a pan move is less than
    195 * this value, we can assume that panning can be done in the allowed direction
    196 * (horizontal or vertical).\n
    197 * Currently used only for touch-action css property stuff and was addded to
    198 * keep behaviour consistent with IE.\n
    199 * Units: radians
    200 *
    201 * \li\b apz.content_response_timeout
    202 * Amount of time before we timeout response from content. For example, if
    203 * content is being unruly/slow and we don't get a response back within this
    204 * time, we will just pretend that content did not preventDefault any touch
    205 * events we dispatched to it.\n
    206 * Units: milliseconds
    207 *
    208 * \li\b apz.danger_zone_x
    209 * \li\b apz.danger_zone_y
    210 * When drawing high-res tiles, we drop down to drawing low-res tiles
    211 * when we know we can't keep up with the scrolling. The way we determine
    212 * this is by checking if we are entering the "danger zone", which is the
    213 * boundary of the painted content. For example, if the painted content
    214 * goes from y=0...1000 and the visible portion is y=250...750 then
    215 * we're far from checkerboarding. If we get to y=490...990 though then we're
    216 * only 10 pixels away from showing checkerboarding so we are probably in
    217 * a state where we can't keep up with scrolling. The danger zone prefs specify
    218 * how wide this margin is; in the above example a y-axis danger zone of 10
    219 * pixels would make us drop to low-res at y=490...990.\n
    220 * This value is in screen pixels.
    221 *
    222 * \li\b apz.disable_for_scroll_linked_effects
    223 * Setting this pref to true will disable APZ scrolling on documents where
    224 * scroll-linked effects are detected. A scroll linked effect is detected if
    225 * positioning or transform properties are updated inside a scroll event
    226 * dispatch; we assume that such an update is in response to the scroll event
    227 * and is therefore a scroll-linked effect which will be laggy with APZ
    228 * scrolling.
    229 *
    230 * \li\b apz.displayport_expiry_ms
    231 * While a scrollable frame is scrolling async, we set a displayport on it
    232 * to make sure it is layerized. However this takes up memory, so once the
    233 * scrolling stops we want to remove the displayport. This pref controls how
    234 * long after scrolling stops the displayport is removed. A value of 0 will
    235 * disable the expiry behavior entirely.
    236 * Units: milliseconds
    237 *
    238 * \li\b apz.drag.enabled
    239 * Setting this pref to true will cause APZ to handle mouse-dragging of
    240 * scrollbar thumbs.
    241 *
    242 * \li\b apz.drag.touch.enabled
    243 * Setting this pref to true will cause APZ to handle touch-dragging of
    244 * scrollbar thumbs. Only has an effect if apz.drag.enabled is also true.
    245 *
    246 * \li\b apz.enlarge_displayport_when_clipped
    247 * Pref that enables enlarging of the displayport along one axis when the
    248 * generated displayport's size is beyond that of the scrollable rect on the
    249 * opposite axis.
    250 *
    251 * \li\b apz.fling_accel_min_fling_velocity
    252 * The minimum velocity of the second fling, and the minimum velocity of the
    253 * previous fling animation at the point of interruption, for the new fling to
    254 * be considered for fling acceleration.
    255 * Units: screen pixels per milliseconds
    256 *
    257 * \li\b apz.fling_accel_min_pan_velocity
    258 * The minimum velocity during the pan gesture that causes a fling for that
    259 * fling to be considered for fling acceleration.
    260 * Units: screen pixels per milliseconds
    261 *
    262 * \li\b apz.fling_accel_max_pause_interval_ms
    263 * The maximum time that is allowed to elapse between the touch start event that
    264 * interrupts the previous fling, and the touch move that initiates panning for
    265 * the current fling, for that fling to be considered for fling acceleration.
    266 * Units: milliseconds
    267 *
    268 * \li\b apz.fling_accel_base_mult
    269 * \li\b apz.fling_accel_supplemental_mult
    270 * When applying an acceleration on a fling, the new computed velocity is
    271 * (new_fling_velocity * base_mult) + (old_velocity * supplemental_mult).
    272 * The base_mult and supplemental_mult multiplier values are controlled by
    273 * these prefs. Note that "old_velocity" here is the initial velocity of the
    274 * previous fling _after_ acceleration was applied to it (if applicable).
    275 *
    276 * \li\b apz.fling_curve_function_x1
    277 * \li\b apz.fling_curve_function_y1
    278 * \li\b apz.fling_curve_function_x2
    279 * \li\b apz.fling_curve_function_y2
    280 * \li\b apz.fling_curve_threshold_inches_per_ms
    281 * These five parameters define a Bezier curve function and threshold used to
    282 * increase the actual velocity relative to the user's finger velocity. When the
    283 * finger velocity is below the threshold (or if the threshold is not positive),
    284 * the velocity is used as-is. If the finger velocity exceeds the threshold
    285 * velocity, then the function defined by the curve is applied on the part of
    286 * the velocity that exceeds the threshold. Note that the upper bound of the
    287 * velocity is still specified by the \b apz.max_velocity_inches_per_ms pref,
    288 * and the function will smoothly curve the velocity from the threshold to the
    289 * max. In general the function parameters chosen should define an ease-out
    290 * curve in order to increase the velocity in this range, or an ease-in curve to
    291 * decrease the velocity. A straight-line curve is equivalent to disabling the
    292 * curve entirely by setting the threshold to -1. The max velocity pref must
    293 * also be set in order for the curving to take effect, as it defines the upper
    294 * bound of the velocity curve.\n
    295 * The points (x1, y1) and (x2, y2) used as the two intermediate control points
    296 * in the cubic bezier curve; the first and last points are (0,0) and (1,1).\n
    297 * Some example values for these prefs can be found at\n
    298 * https://searchfox.org/mozilla-central/rev/f82d5c549f046cb64ce5602bfd894b7ae807c8f8/dom/animation/ComputedTimingFunction.cpp#27-33
    299 *
    300 * \li\b apz.fling_friction
    301 * Amount of friction applied during flings. This is used in the following
    302 * formula: v(t1) = v(t0) * (1 - f)^(t1 - t0), where v(t1) is the velocity
    303 * for a new sample, v(t0) is the velocity at the previous sample, f is the
    304 * value of this pref, and (t1 - t0) is the amount of time, in milliseconds,
    305 * that has elapsed between the two samples.\n
    306 * NOTE: Not currently used in Android fling calculations.
    307 *
    308 * \li\b apz.fling_min_velocity_threshold
    309 * Minimum velocity for a fling to actually kick off. If the user pans and lifts
    310 * their finger such that the velocity is smaller than or equal to this amount,
    311 * no fling is initiated.\n
    312 * Units: screen pixels per millisecond
    313 *
    314 * \li\b apz.fling_stop_on_tap_threshold
    315 * When flinging, if the velocity is above this number, then a tap on the
    316 * screen will stop the fling without dispatching a tap to content. If the
    317 * velocity is below this threshold a tap will also be dispatched.
    318 * Note: when modifying this pref be sure to run the APZC gtests as some of
    319 * them depend on the value of this pref.\n
    320 * Units: screen pixels per millisecond
    321 *
    322 * \li\b apz.fling_stopped_threshold
    323 * When flinging, if the velocity goes below this number, we just stop the
    324 * animation completely. This is to prevent asymptotically approaching 0
    325 * velocity and rerendering unnecessarily.\n
    326 * Units: screen pixels per millisecond.\n
    327 * NOTE: Should not be set to anything
    328 * other than 0.0 for Android except for tests to disable flings.
    329 *
    330 * \li\b apz.keyboard.enabled
    331 * Determines whether scrolling with the keyboard will be allowed to be handled
    332 * by APZ.
    333 *
    334 * \li\b apz.keyboard.passive-listeners
    335 * When enabled, APZ will interpret the passive event listener flag to mean
    336 * that the event listener won't change the focused element or selection of
    337 * the page. With this, web content can use passive key listeners and not have
    338 * keyboard APZ disabled.
    339 *
    340 * \li\b apz.max_tap_time
    341 * Maximum time for a touch on the screen and corresponding lift of the finger
    342 * to be considered a tap. This also applies to double taps, except that it is
    343 * used both for the interval between the first touchdown and first touchup,
    344 * and for the interval between the first touchup and the second touchdown.\n
    345 * Units: milliseconds.
    346 *
    347 * \li\b apz.max_velocity_inches_per_ms
    348 * Maximum velocity.  Velocity will be capped at this value if a faster fling
    349 * occurs.  Negative values indicate unlimited velocity.\n
    350 * Units: (real-world, i.e. screen) inches per millisecond
    351 *
    352 * \li\b apz.max_velocity_queue_size
    353 * Maximum size of velocity queue. The queue contains last N velocity records.
    354 * On touch end we calculate the average velocity in order to compensate
    355 * touch/mouse drivers misbehaviour.
    356 *
    357 * \li\b apz.min_skate_speed
    358 * Minimum amount of speed along an axis before we switch to "skate" multipliers
    359 * rather than using the "stationary" multipliers.\n
    360 * Units: CSS pixels per millisecond
    361 *
    362 * \li\b apz.one_touch_pinch.enabled
    363 * Whether or not the "one-touch-pinch" gesture (for zooming with one finger)
    364 * is enabled or not.
    365 *
    366 * \li\b apz.overscroll.enabled
    367 * Pref that enables overscrolling. If this is disabled, excess scroll that
    368 * cannot be handed off is discarded.
    369 *
    370 * \li\b apz.overscroll.min_pan_distance_ratio
    371 * The minimum ratio of the pan distance along one axis to the pan distance
    372 * along the other axis needed to initiate overscroll along the first axis
    373 * during panning.
    374 *
    375 * \li\b apz.overscroll.stretch_factor
    376 * How much overscrolling can stretch content along an axis.
    377 * The maximum stretch along an axis is a factor of (1 + kStretchFactor).
    378 * (So if kStretchFactor is 0, you can't stretch at all; if kStretchFactor
    379 * is 1, you can stretch at most by a factor of 2).
    380 *
    381 * \li\b apz.overscroll.stop_distance_threshold
    382 * \li\b apz.overscroll.stop_velocity_threshold
    383 * Thresholds for stopping the overscroll animation. When both the distance
    384 * and the velocity fall below their thresholds, we stop oscillating.\n
    385 * Units: screen pixels (for distance)
    386 *        screen pixels per millisecond (for velocity)
    387 *
    388 * \li\b apz.overscroll.spring_stiffness
    389 * The spring stiffness constant for the overscroll mass-spring-damper model.
    390 *
    391 * \li\b apz.overscroll.damping
    392 * The damping constant for the overscroll mass-spring-damper model.
    393 *
    394 * \li\b apz.overscroll.max_velocity
    395 * The maximum velocity (in ParentLayerPixels per millisecond) allowed when
    396 * initiating the overscroll snap-back animation.
    397 *
    398 * \li\b apz.paint_skipping.enabled
    399 * When APZ is scrolling and sending repaint requests to the main thread, often
    400 * the main thread doesn't actually need to do a repaint. This pref allows the
    401 * main thread to skip doing those repaints in cases where it doesn't need to.
    402 *
    403 * \li\b apz.pinch_lock.mode
    404 * The preferred pinch locking style. See PinchLockMode for possible values.
    405 *
    406 * \li\b apz.pinch_lock.scroll_lock_threshold
    407 * Pinch locking is triggered if the user scrolls more than this distance
    408 * and pinches less than apz.pinch_lock.span_lock_threshold.\n
    409 * Units: (real-world, i.e. screen) inches
    410 *
    411 * \li\b apz.pinch_lock.span_breakout_threshold
    412 * Distance in inches the user must pinch before lock can be broken.\n
    413 * Units: (real-world, i.e. screen) inches measured between two touch points
    414 *
    415 * \li\b apz.pinch_lock.span_lock_threshold
    416 * Pinch locking is triggered if the user pinches less than this distance
    417 * and scrolls more than apz.pinch_lock.scroll_lock_threshold.\n
    418 * Units: (real-world, i.e. screen) inches measured between two touch points
    419 *
    420 * \li\b apz.pinch_lock.buffer_max_age
    421 * To ensure that pinch locking threshold calculations are not affected by
    422 * variations in touch screen sensitivity, calculations draw from a buffer of
    423 * recent events. This preference specifies the maximum time that events are
    424 * held in this buffer.
    425 * Units: milliseconds
    426 *
    427 * \li\b apz.popups.enabled
    428 * Determines whether APZ is used for XUL popup widgets with remote content.
    429 * Ideally, this should always be true, but it is currently not well tested, and
    430 * has known issues, so needs to be prefable.
    431 *
    432 * \li\b apz.record_checkerboarding
    433 * Whether or not to record detailed info on checkerboarding events.
    434 *
    435 * \li\b apz.second_tap_tolerance
    436 * Constant describing the tolerance in distance we use, multiplied by the
    437 * device DPI, within which a second tap is counted as part of a gesture
    438 * continuing from the first tap. Making this larger allows the user more
    439 * distance between the first and second taps in a "double tap" or "one touch
    440 * pinch" gesture.\n
    441 * Units: (real-world, i.e. screen) inches
    442 *
    443 * \li\b apz.test.logging_enabled
    444 * Enable logging of APZ test data (see bug 961289).
    445 *
    446 * \li\b apz.touch_move_tolerance
    447 * See the description for apz.touch_start_tolerance below. This is a similar
    448 * threshold, except it is used to suppress touchmove events from being
    449 * delivered to content for NON-scrollable frames (or more precisely, for APZCs
    450 * where ArePointerEventsConsumable returns false).\n Units: (real-world, i.e.
    451 * screen) inches
    452 *
    453 * \li\b apz.touch_start_tolerance
    454 * Constant describing the tolerance in distance we use, multiplied by the
    455 * device DPI, before we start panning the screen. This is to prevent us from
    456 * accidentally processing taps as touch moves, and from very short/accidental
    457 * touches moving the screen. touchmove events are also not delivered to content
    458 * within this distance on scrollable frames.\n
    459 * Units: (real-world, i.e. screen) inches
    460 *
    461 * \li\b apz.velocity_bias
    462 * How much to adjust the displayport in the direction of scrolling. This value
    463 * is multiplied by the velocity and added to the displayport offset.
    464 *
    465 * \li\b apz.velocity_relevance_time_ms
    466 * When computing a fling velocity from the most recently stored velocity
    467 * information, only velocities within the most X milliseconds are used.
    468 * This pref controls the value of X.\n
    469 * Units: ms
    470 *
    471 * \li\b apz.x_skate_size_multiplier
    472 * \li\b apz.y_skate_size_multiplier
    473 * The multiplier we apply to the displayport size if it is skating (current
    474 * velocity is above \b apz.min_skate_speed). We prefer to increase the size of
    475 * the Y axis because it is more natural in the case that a user is reading a
    476 * page page that scrolls up/down. Note that one, both or neither of these may
    477 * be used at any instant.\n In general we want \b
    478 * apz.[xy]_skate_size_multiplier to be smaller than the corresponding
    479 * stationary size multiplier because when panning fast we would like to paint
    480 * less and get faster, more predictable paint times. When panning slowly we
    481 * can afford to paint more even though it's slower.
    482 *
    483 * \li\b apz.x_stationary_size_multiplier
    484 * \li\b apz.y_stationary_size_multiplier
    485 * The multiplier we apply to the displayport size if it is not skating (see
    486 * documentation for the skate size multipliers above).
    487 *
    488 * \li\b apz.x_skate_highmem_adjust
    489 * \li\b apz.y_skate_highmem_adjust
    490 * On high memory systems, we adjust the displayport during skating
    491 * to be larger so we can reduce checkerboarding.
    492 *
    493 * \li\b apz.zoom_animation_duration_ms
    494 * This controls how long the zoom-to-rect animation takes.\n
    495 * Units: ms
    496 *
    497 * \li\b apz.scale_repaint_delay_ms
    498 * How long to delay between repaint requests during a scale.
    499 * A negative number prevents repaint requests during a scale.\n
    500 * Units: ms
    501 */
    502 
    503 /**
    504 * Computed time function used for sampling frames of a zoom to animation.
    505 */
    506 StaticAutoPtr<StyleComputedTimingFunction> gZoomAnimationFunction;
    507 
    508 /**
    509 * Computed time function used for curving up velocity when it gets high.
    510 */
    511 StaticAutoPtr<StyleComputedTimingFunction> gVelocityCurveFunction;
    512 
    513 /**
    514 * The estimated duration of a paint for the purposes of calculating a new
    515 * displayport, in milliseconds.
    516 */
    517 static const double kDefaultEstimatedPaintDurationMs = 50;
    518 
    519 /**
    520 * Returns true if this is a high memory system and we can use
    521 * extra memory for a larger displayport to reduce checkerboarding.
    522 */
    523 static bool gIsHighMemSystem = false;
    524 static bool IsHighMemSystem() { return gIsHighMemSystem; }
    525 
    526 // An RAII class to hide the dynamic toolbar on Android.
    527 class MOZ_RAII AutoDynamicToolbarHider final {
    528 public:
    529  explicit AutoDynamicToolbarHider(AsyncPanZoomController* aApzc)
    530      : mApzc(aApzc) {
    531    MOZ_ASSERT(mApzc);
    532  }
    533  ~AutoDynamicToolbarHider() {
    534    if (mHideDynamicToolbar) {
    535      RefPtr<GeckoContentController> controller =
    536          mApzc->GetGeckoContentController();
    537      controller->HideDynamicToolbar(mApzc->GetGuid());
    538    }
    539  }
    540 
    541  void Hide() { mHideDynamicToolbar = true; }
    542 
    543  friend class AsyncPanZoomController;
    544 
    545 private:
    546  AsyncPanZoomController* mApzc;
    547  bool mHideDynamicToolbar = false;
    548 };
    549 
    550 AsyncPanZoomAnimation* PlatformSpecificStateBase::CreateFlingAnimation(
    551    AsyncPanZoomController& aApzc, const FlingHandoffState& aHandoffState,
    552    float aPLPPI) {
    553  return new GenericFlingAnimation<DesktopFlingPhysics>(aApzc, aHandoffState,
    554                                                        aPLPPI);
    555 }
    556 
    557 UniquePtr<VelocityTracker> PlatformSpecificStateBase::CreateVelocityTracker(
    558    Axis* aAxis) {
    559  return MakeUnique<SimpleVelocityTracker>(aAxis);
    560 }
    561 
    562 // This is a helper class for populating
    563 // AsyncPanZoomController::mUpdatesSinceLastSample when the visual scroll offset
    564 // or zoom level changes.
    565 //
    566 // The class records the current offset and zoom level in its constructor, and
    567 // again in the destructor, and if they have changed, records a compositor
    568 // scroll update with the Source provided in the constructor.
    569 //
    570 // This allows tracking the source of compositor scroll updates in higher-level
    571 // functions such as AttemptScroll or NotifyLayersUpdated, rather than having
    572 // to propagate the source into lower-level functions such as
    573 // SetVisualScrollOffset.
    574 //
    575 // Note however that there is a limit to how far up the call stack this class
    576 // can be used: mRecursiveMutex must be held for the duration of the object's
    577 // lifetime (and to ensure this, the constructor takes a proof-of-lock
    578 // parameter). This is necessary because otherwise, the class could record
    579 // a change to the scroll offset or zoom made by another thread in between
    580 // construction and destruction, for which the source would be incorrect.
    581 class MOZ_STACK_CLASS AsyncPanZoomController::AutoRecordCompositorScrollUpdate
    582    final {
    583 public:
    584  AutoRecordCompositorScrollUpdate(
    585      AsyncPanZoomController* aApzc, CompositorScrollUpdate::Source aSource,
    586      const RecursiveMutexAutoLock& aProofOfApzcLock)
    587      : mApzc(aApzc),
    588        mProofOfApzcLock(aProofOfApzcLock),
    589        mSource(aSource),
    590        mPreviousMetrics(aApzc->GetCurrentMetricsForCompositorScrollUpdate(
    591            aProofOfApzcLock)) {}
    592  ~AutoRecordCompositorScrollUpdate() {
    593    if (!mApzc->IsRootContent()) {
    594      // Compositor scroll updates are only recorded for the root content APZC.
    595      // This check may need to be relaxed in bug 1861329, if we start to allow
    596      // some subframes to move the dynamic toolbar.
    597      return;
    598    }
    599    CompositorScrollUpdate::Metrics newMetrics =
    600        mApzc->GetCurrentMetricsForCompositorScrollUpdate(mProofOfApzcLock);
    601    if (newMetrics != mPreviousMetrics) {
    602      mApzc->mUpdatesSinceLastSample.push_back({newMetrics, mSource});
    603    }
    604  }
    605 
    606 private:
    607  AsyncPanZoomController* mApzc;
    608  const RecursiveMutexAutoLock& mProofOfApzcLock;
    609  CompositorScrollUpdate::Source mSource;
    610  CompositorScrollUpdate::Metrics mPreviousMetrics;
    611 };
    612 
    613 SampleTime AsyncPanZoomController::GetFrameTime() const {
    614  APZCTreeManager* treeManagerLocal = GetApzcTreeManager();
    615  return treeManagerLocal ? treeManagerLocal->GetFrameTime()
    616                          : SampleTime::FromNow();
    617 }
    618 
    619 bool AsyncPanZoomController::IsZero(const ParentLayerPoint& aPoint) const {
    620  RecursiveMutexAutoLock lock(mRecursiveMutex);
    621 
    622  return layers::IsZero(ToCSSPixels(aPoint));
    623 }
    624 
    625 bool AsyncPanZoomController::IsZero(ParentLayerCoord aCoord) const {
    626  RecursiveMutexAutoLock lock(mRecursiveMutex);
    627 
    628  return FuzzyEqualsAdditive(ToCSSPixels(aCoord), CSSCoord(),
    629                             COORDINATE_EPSILON);
    630 }
    631 
    632 bool AsyncPanZoomController::FuzzyGreater(ParentLayerCoord aCoord1,
    633                                          ParentLayerCoord aCoord2) const {
    634  RecursiveMutexAutoLock lock(mRecursiveMutex);
    635  return ToCSSPixels(aCoord1 - aCoord2) > COORDINATE_EPSILON;
    636 }
    637 
    638 class StateChangeNotificationBlocker final {
    639 public:
    640  explicit StateChangeNotificationBlocker(AsyncPanZoomController* aApzc)
    641      : mApzc(aApzc) {
    642    RecursiveMutexAutoLock lock(mApzc->mRecursiveMutex);
    643    mInitialState = mApzc->mState;
    644    mApzc->mNotificationBlockers++;
    645  }
    646 
    647  StateChangeNotificationBlocker(const StateChangeNotificationBlocker&) =
    648      delete;
    649  StateChangeNotificationBlocker(StateChangeNotificationBlocker&& aOther)
    650      : mApzc(aOther.mApzc), mInitialState(aOther.mInitialState) {
    651    aOther.mApzc = nullptr;
    652  }
    653 
    654  ~StateChangeNotificationBlocker() {
    655    if (!mApzc) {  // moved-from
    656      return;
    657    }
    658    AsyncPanZoomController::PanZoomState newState;
    659    {
    660      RecursiveMutexAutoLock lock(mApzc->mRecursiveMutex);
    661      mApzc->mNotificationBlockers--;
    662      newState = mApzc->mState;
    663    }
    664    mApzc->DispatchStateChangeNotification(mInitialState, newState);
    665  }
    666 
    667 private:
    668  AsyncPanZoomController* mApzc;
    669  AsyncPanZoomController::PanZoomState mInitialState;
    670 };
    671 
    672 class ThreadSafeStateChangeNotificationBlocker final {
    673 public:
    674  explicit ThreadSafeStateChangeNotificationBlocker(
    675      AsyncPanZoomController* aApzc) {
    676    RecursiveMutexAutoLock lock(aApzc->mRecursiveMutex);
    677    mApzcPtr = RefPtr(aApzc);
    678    mApzcPtr->mNotificationBlockers++;
    679    mInitialState = mApzcPtr->mState;
    680  }
    681 
    682  ThreadSafeStateChangeNotificationBlocker(
    683      const StateChangeNotificationBlocker&) = delete;
    684  ThreadSafeStateChangeNotificationBlocker(
    685      ThreadSafeStateChangeNotificationBlocker&& aOther)
    686      : mApzcPtr(std::move(aOther.mApzcPtr)),
    687        mInitialState(aOther.mInitialState) {
    688    aOther.mApzcPtr = nullptr;
    689  }
    690 
    691  ~ThreadSafeStateChangeNotificationBlocker() {
    692    // The point of the ThreadSafeStateChangeNotificationBlocker is to keep a
    693    // live reference to an APZC. If this reference doesn't exist, then it must
    694    // have been moved from, and the other state in the object isn't valid, so
    695    // we early out
    696    if (mApzcPtr == nullptr) {
    697      return;
    698    }
    699    AsyncPanZoomController::PanZoomState newState;
    700    {
    701      RecursiveMutexAutoLock lock(mApzcPtr->mRecursiveMutex);
    702      mApzcPtr->mNotificationBlockers--;
    703      newState = mApzcPtr->mState;
    704    }
    705    mApzcPtr->DispatchStateChangeNotification(mInitialState, newState);
    706  }
    707 
    708 private:
    709  RefPtr<AsyncPanZoomController> mApzcPtr;
    710  AsyncPanZoomController::PanZoomState mInitialState;
    711 };
    712 
    713 /**
    714 * An RAII class to temporarily apply async test attributes to the provided
    715 * AsyncPanZoomController.
    716 *
    717 * This class should be used in the implementation of any AsyncPanZoomController
    718 * method that queries the async scroll offset or async zoom (this includes
    719 * the async layout viewport offset, since modifying the async scroll offset
    720 * may result in the layout viewport moving as well).
    721 */
    722 class MOZ_RAII AutoApplyAsyncTestAttributes final {
    723 public:
    724  explicit AutoApplyAsyncTestAttributes(
    725      const AsyncPanZoomController*,
    726      const RecursiveMutexAutoLock& aProofOfLock);
    727  ~AutoApplyAsyncTestAttributes();
    728 
    729 private:
    730  AsyncPanZoomController* mApzc;
    731  FrameMetrics mPrevFrameMetrics;
    732  ParentLayerPoint mPrevOverscroll;
    733  const RecursiveMutexAutoLock& mProofOfLock;
    734 };
    735 
    736 AutoApplyAsyncTestAttributes::AutoApplyAsyncTestAttributes(
    737    const AsyncPanZoomController* aApzc,
    738    const RecursiveMutexAutoLock& aProofOfLock)
    739    // Having to use const_cast here seems less ugly than the alternatives
    740    // of making several members of AsyncPanZoomController that
    741    // ApplyAsyncTestAttributes() modifies |mutable|, or several methods that
    742    // query the async transforms non-const.
    743    : mApzc(const_cast<AsyncPanZoomController*>(aApzc)),
    744      mPrevFrameMetrics(aApzc->Metrics()),
    745      mPrevOverscroll(aApzc->GetOverscrollAmountInternal()),
    746      mProofOfLock(aProofOfLock) {
    747  mApzc->ApplyAsyncTestAttributes(aProofOfLock);
    748 }
    749 
    750 AutoApplyAsyncTestAttributes::~AutoApplyAsyncTestAttributes() {
    751  mApzc->UnapplyAsyncTestAttributes(mProofOfLock, mPrevFrameMetrics,
    752                                    mPrevOverscroll);
    753 }
    754 
    755 class ZoomAnimation : public AsyncPanZoomAnimation {
    756 public:
    757  ZoomAnimation(AsyncPanZoomController& aApzc, const CSSPoint& aStartOffset,
    758                const CSSToParentLayerScale& aStartZoom,
    759                const CSSPoint& aEndOffset,
    760                const CSSToParentLayerScale& aEndZoom)
    761      : mApzc(aApzc),
    762        mTotalDuration(TimeDuration::FromMilliseconds(
    763            StaticPrefs::apz_zoom_animation_duration_ms())),
    764        mStartOffset(aStartOffset),
    765        mStartZoom(aStartZoom),
    766        mEndOffset(aEndOffset),
    767        mEndZoom(aEndZoom) {}
    768 
    769  virtual bool DoSample(FrameMetrics& aFrameMetrics,
    770                        const TimeDuration& aDelta) override {
    771    mDuration += aDelta;
    772    double animPosition = mDuration / mTotalDuration;
    773 
    774    if (animPosition >= 1.0) {
    775      aFrameMetrics.SetZoom(mEndZoom);
    776      mApzc.SetVisualScrollOffset(mEndOffset);
    777      return false;
    778    }
    779 
    780    // Sample the zoom at the current time point.  The sampled zoom
    781    // will affect the final computed resolution.
    782    float sampledPosition =
    783        gZoomAnimationFunction->At(animPosition, /* aBeforeFlag = */ false);
    784 
    785    // We scale the scrollOffset linearly with sampledPosition, so the zoom
    786    // needs to scale inversely to match.
    787    if (mStartZoom == CSSToParentLayerScale(0) ||
    788        mEndZoom == CSSToParentLayerScale(0)) {
    789      return false;
    790    }
    791 
    792    aFrameMetrics.SetZoom(
    793        CSSToParentLayerScale(1 / (sampledPosition / mEndZoom.scale +
    794                                   (1 - sampledPosition) / mStartZoom.scale)));
    795 
    796    mApzc.SetVisualScrollOffset(CSSPoint::FromUnknownPoint(gfx::Point(
    797        mEndOffset.x * sampledPosition + mStartOffset.x * (1 - sampledPosition),
    798        mEndOffset.y * sampledPosition +
    799            mStartOffset.y * (1 - sampledPosition))));
    800    return true;
    801  }
    802 
    803  virtual bool WantsRepaints() override { return true; }
    804 
    805 private:
    806  AsyncPanZoomController& mApzc;
    807 
    808  TimeDuration mDuration;
    809  const TimeDuration mTotalDuration;
    810 
    811  // Old metrics from before we started a zoom animation. This is only valid
    812  // when we are in the "ANIMATED_ZOOM" state. This is used so that we can
    813  // interpolate between the start and end frames. We only use the
    814  // |mViewportScrollOffset| and |mResolution| fields on this.
    815  CSSPoint mStartOffset;
    816  CSSToParentLayerScale mStartZoom;
    817 
    818  // Target metrics for a zoom to animation. This is only valid when we are in
    819  // the "ANIMATED_ZOOM" state. We only use the |mViewportScrollOffset| and
    820  // |mResolution| fields on this.
    821  CSSPoint mEndOffset;
    822  CSSToParentLayerScale mEndZoom;
    823 };
    824 
    825 /*static*/
    826 void AsyncPanZoomController::InitializeGlobalState() {
    827  static bool sInitialized = false;
    828  if (sInitialized) return;
    829  sInitialized = true;
    830 
    831  MOZ_ASSERT(NS_IsMainThread());
    832 
    833  gZoomAnimationFunction = new StyleComputedTimingFunction(
    834      StyleComputedTimingFunction::Keyword(StyleTimingKeyword::Ease));
    835  ClearOnShutdown(&gZoomAnimationFunction);
    836  gVelocityCurveFunction =
    837      new StyleComputedTimingFunction(StyleComputedTimingFunction::CubicBezier(
    838          StaticPrefs::apz_fling_curve_function_x1_AtStartup(),
    839          StaticPrefs::apz_fling_curve_function_y1_AtStartup(),
    840          StaticPrefs::apz_fling_curve_function_x2_AtStartup(),
    841          StaticPrefs::apz_fling_curve_function_y2_AtStartup()));
    842  ClearOnShutdown(&gVelocityCurveFunction);
    843 
    844  uint64_t sysmem = PR_GetPhysicalMemorySize();
    845  uint64_t threshold = 1LL << 32;  // 4 GB in bytes
    846  gIsHighMemSystem = sysmem >= threshold;
    847 
    848  PlatformSpecificState::InitializeGlobalState();
    849 }
    850 
    851 AsyncPanZoomController::AsyncPanZoomController(
    852    LayersId aLayersId, APZCTreeManager* aTreeManager,
    853    const RefPtr<InputQueue>& aInputQueue,
    854    GeckoContentController* aGeckoContentController, GestureBehavior aGestures)
    855    : mLayersId(aLayersId),
    856      mGeckoContentController(aGeckoContentController),
    857      mRefPtrMonitor("RefPtrMonitor"),
    858      // mTreeManager must be initialized before GetFrameTime() is called
    859      mTreeManager(aTreeManager),
    860      mRecursiveMutex("AsyncPanZoomController"),
    861      mLastContentPaintMetrics(mLastContentPaintMetadata.GetMetrics()),
    862      mPanDirRestricted(false),
    863      mPinchLocked(false),
    864      mPinchEventBuffer(TimeDuration::FromMilliseconds(
    865          StaticPrefs::apz_pinch_lock_buffer_max_age_AtStartup())),
    866      mTouchScrollEventBuffer(
    867          TimeDuration::FromMilliseconds(
    868              StaticPrefs::apz_touch_scroll_buffer_max_age_AtStartup()),
    869          2),
    870      mZoomConstraints(false, false,
    871                       mScrollMetadata.GetMetrics().GetDevPixelsPerCSSPixel() *
    872                           ViewportMinScale() / ParentLayerToScreenScale(1),
    873                       mScrollMetadata.GetMetrics().GetDevPixelsPerCSSPixel() *
    874                           ViewportMaxScale() / ParentLayerToScreenScale(1)),
    875      mLastSampleTime(GetFrameTime()),
    876      mLastCheckerboardReport(GetFrameTime()),
    877      mOverscrollEffect(MakeUnique<OverscrollEffect>(*this)),
    878      mState(NOTHING),
    879      mX(this),
    880      mY(this),
    881      mNotificationBlockers(0),
    882      mInputQueue(aInputQueue),
    883      mPinchPaintTimerSet(false),
    884      mDelayedTransformEnd(false),
    885      mTestAttributeAppliers(0),
    886      mTestHasAsyncKeyScrolled(false),
    887      mCheckerboardEventLock("APZCBELock") {
    888  if (aGestures == USE_GESTURE_DETECTOR) {
    889    mGestureEventListener = new GestureEventListener(this);
    890  }
    891  // Put one default-constructed sampled state in the queue.
    892  RecursiveMutexAutoLock lock(mRecursiveMutex);
    893  mSampledState.emplace_back();
    894 }
    895 
    896 AsyncPanZoomController::~AsyncPanZoomController() { MOZ_ASSERT(IsDestroyed()); }
    897 
    898 PlatformSpecificStateBase* AsyncPanZoomController::GetPlatformSpecificState() {
    899  if (!mPlatformSpecificState) {
    900    mPlatformSpecificState = MakeUnique<PlatformSpecificState>();
    901  }
    902  return mPlatformSpecificState.get();
    903 }
    904 
    905 already_AddRefed<GeckoContentController>
    906 AsyncPanZoomController::GetGeckoContentController() const {
    907  MonitorAutoLock lock(mRefPtrMonitor);
    908  RefPtr<GeckoContentController> controller = mGeckoContentController;
    909  return controller.forget();
    910 }
    911 
    912 already_AddRefed<GestureEventListener>
    913 AsyncPanZoomController::GetGestureEventListener() const {
    914  MonitorAutoLock lock(mRefPtrMonitor);
    915  RefPtr<GestureEventListener> listener = mGestureEventListener;
    916  return listener.forget();
    917 }
    918 
    919 const RefPtr<InputQueue>& AsyncPanZoomController::GetInputQueue() const {
    920  return mInputQueue;
    921 }
    922 
    923 void AsyncPanZoomController::Destroy() {
    924  AssertOnUpdaterThread();
    925 
    926  CancelAnimation(CancelAnimationFlags::ScrollSnap);
    927 
    928  {  // scope the lock
    929    MonitorAutoLock lock(mRefPtrMonitor);
    930    mGeckoContentController = nullptr;
    931    if (mGestureEventListener) {
    932      APZThreadUtils::RunOnControllerThread(NS_NewRunnableFunction(
    933          "AsyncPanZoomController: destroying mGestureEventListener",
    934          [listener = std::move(mGestureEventListener)]() {
    935            listener->Destroy();
    936          }));
    937 
    938      mGestureEventListener = nullptr;
    939    }
    940  }
    941  mParent = nullptr;
    942  mTreeManager = nullptr;
    943 }
    944 
    945 bool AsyncPanZoomController::IsDestroyed() const {
    946  return mTreeManager == nullptr;
    947 }
    948 
    949 float AsyncPanZoomController::GetDPI() const {
    950  if (APZCTreeManager* localPtr = mTreeManager) {
    951    return localPtr->GetDPI();
    952  }
    953  // If this APZC has been destroyed then this value is not going to be
    954  // used for anything that the user will end up seeing, so we can just
    955  // return 0.
    956  return 0.0;
    957 }
    958 
    959 ScreenCoord AsyncPanZoomController::GetTouchStartTolerance() const {
    960  return (StaticPrefs::apz_touch_start_tolerance() * GetDPI());
    961 }
    962 
    963 ScreenCoord AsyncPanZoomController::GetTouchMoveTolerance() const {
    964  return (StaticPrefs::apz_touch_move_tolerance() * GetDPI());
    965 }
    966 
    967 ScreenCoord AsyncPanZoomController::GetSecondTapTolerance() const {
    968  return (StaticPrefs::apz_second_tap_tolerance() * GetDPI());
    969 }
    970 
    971 /* static */ AsyncPanZoomController::AxisLockMode
    972 AsyncPanZoomController::GetAxisLockMode() {
    973  return static_cast<AxisLockMode>(StaticPrefs::apz_axis_lock_mode());
    974 }
    975 
    976 bool AsyncPanZoomController::UsingStatefulAxisLock() const {
    977  return (GetAxisLockMode() == AxisLockMode::STANDARD ||
    978          GetAxisLockMode() == AxisLockMode::STICKY ||
    979          GetAxisLockMode() == AxisLockMode::BREAKABLE);
    980 }
    981 
    982 /* static */ AsyncPanZoomController::PinchLockMode
    983 AsyncPanZoomController::GetPinchLockMode() {
    984  return static_cast<PinchLockMode>(StaticPrefs::apz_pinch_lock_mode());
    985 }
    986 
    987 PointerEventsConsumableFlags AsyncPanZoomController::ArePointerEventsConsumable(
    988    const TouchBlockState* aBlock, const MultiTouchInput& aInput) const {
    989  uint32_t touchPoints = aInput.mTouches.Length();
    990  if (touchPoints == 0) {
    991    // Cant' do anything with zero touch points
    992    return {false, false};
    993  }
    994 
    995  // This logic is simplified, erring on the side of returning true if we're
    996  // not sure. It's safer to pretend that we can consume the event and then
    997  // not be able to than vice-versa. But at the same time, we should try hard
    998  // to return an accurate result, because returning true can trigger a
    999  // pointercancel event to web content, which can break certain features
   1000  // that are using touch-action and handling the pointermove events.
   1001  //
   1002  // Note that in particular this function can return true if APZ is waiting on
   1003  // the main thread for touch-action information. In this scenario, the
   1004  // APZEventState::MainThreadAgreesEventsAreConsumableByAPZ() function tries
   1005  // to use the main-thread touch-action information to filter out false
   1006  // positives.
   1007  //
   1008  // We could probably enhance this logic to determine things like "we're
   1009  // not pannable, so we can only zoom in, and the zoom is already maxed
   1010  // out, so we're not zoomable either" but no need for that at this point.
   1011 
   1012  bool pannableX = aBlock->GetOverscrollHandoffChain()->CanScrollInDirection(
   1013      this, ScrollDirection::eHorizontal);
   1014  bool touchActionAllowsX = aBlock->TouchActionAllowsPanningX();
   1015  bool pannableY = (aBlock->GetOverscrollHandoffChain()->CanScrollInDirection(
   1016                        this, ScrollDirection::eVertical) ||
   1017                    // In the case of the root APZC with any dynamic toolbar, it
   1018                    // shoule be pannable if there is room moving the dynamic
   1019                    // toolbar.
   1020                    (IsRootContent() && CanVerticalScrollWithDynamicToolbar()));
   1021  bool touchActionAllowsY = aBlock->TouchActionAllowsPanningY();
   1022 
   1023  bool pannable;
   1024  bool touchActionAllowsPanning;
   1025 
   1026  Maybe<ScrollDirection> panDirection =
   1027      aBlock->GetBestGuessPanDirection(aInput);
   1028  if (panDirection == Some(ScrollDirection::eVertical)) {
   1029    pannable = pannableY;
   1030    touchActionAllowsPanning = touchActionAllowsY;
   1031  } else if (panDirection == Some(ScrollDirection::eHorizontal)) {
   1032    pannable = pannableX;
   1033    touchActionAllowsPanning = touchActionAllowsX;
   1034  } else {
   1035    // If we don't have a guessed pan direction, err on the side of returning
   1036    // true.
   1037    pannable = pannableX || pannableY;
   1038    touchActionAllowsPanning = touchActionAllowsX || touchActionAllowsY;
   1039  }
   1040 
   1041  if (touchPoints == 1) {
   1042    return {pannable, touchActionAllowsPanning};
   1043  }
   1044 
   1045  bool zoomable = ZoomConstraintsAllowZoom();
   1046  bool touchActionAllowsZoom = aBlock->TouchActionAllowsPinchZoom();
   1047 
   1048  return {pannable || zoomable,
   1049          touchActionAllowsPanning || touchActionAllowsZoom};
   1050 }
   1051 
   1052 nsEventStatus AsyncPanZoomController::HandleDragEvent(
   1053    const MouseInput& aEvent, const AsyncDragMetrics& aDragMetrics,
   1054    OuterCSSCoord aInitialThumbPos, const CSSRect& aInitialScrollableRect) {
   1055  // RDM is a special case where touch events will be synthesized in response
   1056  // to mouse events, and APZ will receive both even though RDM prevent-defaults
   1057  // the mouse events. This is because mouse events don't opt into APZ waiting
   1058  // to check if the event has been prevent-defaulted and are still processed
   1059  // as a result. To handle this, have APZ ignore mouse events when RDM and
   1060  // touch simulation are active.
   1061  bool isRDMTouchSimulationActive = false;
   1062  {
   1063    RecursiveMutexAutoLock lock(mRecursiveMutex);
   1064    isRDMTouchSimulationActive =
   1065        mScrollMetadata.GetIsRDMTouchSimulationActive();
   1066  }
   1067 
   1068  if (!StaticPrefs::apz_drag_enabled() || isRDMTouchSimulationActive) {
   1069    return nsEventStatus_eIgnore;
   1070  }
   1071 
   1072  if (!GetApzcTreeManager()) {
   1073    return nsEventStatus_eConsumeNoDefault;
   1074  }
   1075 
   1076  {
   1077    RecursiveMutexAutoLock lock(mRecursiveMutex);
   1078 
   1079    if (aEvent.mType == MouseInput::MouseType::MOUSE_UP) {
   1080      if (mState == SCROLLBAR_DRAG) {
   1081        APZC_LOG("%p ending drag\n", this);
   1082        SetState(NOTHING);
   1083      }
   1084 
   1085      SnapBackIfOverscrolled();
   1086 
   1087      return nsEventStatus_eConsumeNoDefault;
   1088    }
   1089  }
   1090 
   1091  HitTestingTreeNodeAutoLock node;
   1092  GetApzcTreeManager()->FindScrollThumbNode(aDragMetrics, mLayersId, node);
   1093  if (!node) {
   1094    APZC_LOG("%p unable to find scrollthumb node with viewid %" PRIu64 "\n",
   1095             this, aDragMetrics.mViewId);
   1096    return nsEventStatus_eConsumeNoDefault;
   1097  }
   1098 
   1099  if (aEvent.mType == MouseInput::MouseType::MOUSE_DOWN) {
   1100    APZC_LOG("%p starting scrollbar drag\n", this);
   1101    SetState(SCROLLBAR_DRAG);
   1102  }
   1103 
   1104  if (aEvent.mType != MouseInput::MouseType::MOUSE_MOVE) {
   1105    APZC_LOG("%p discarding event of type %d\n", this, aEvent.mType);
   1106    return nsEventStatus_eConsumeNoDefault;
   1107  }
   1108 
   1109  const ScrollbarData& scrollbarData = node->GetScrollbarData();
   1110  MOZ_ASSERT(scrollbarData.mScrollbarLayerType ==
   1111             layers::ScrollbarLayerType::Thumb);
   1112  MOZ_ASSERT(scrollbarData.mDirection.isSome());
   1113  ScrollDirection direction = *scrollbarData.mDirection;
   1114 
   1115  bool isMouseAwayFromThumb = false;
   1116  if (int snapMultiplier = StaticPrefs::slider_snapMultiplier()) {
   1117    // It's fine to ignore the async component of the thumb's transform,
   1118    // because any async transform of the thumb will be in the direction of
   1119    // scrolling, but here we're interested in the other direction.
   1120    ParentLayerRect thumbRect =
   1121        (node->GetTransform() * AsyncTransformMatrix())
   1122            .TransformBounds(LayerRect(node->GetVisibleRect()));
   1123    ScrollDirection otherDirection = GetPerpendicularDirection(direction);
   1124    ParentLayerCoord distance =
   1125        GetAxisStart(otherDirection, thumbRect.DistanceTo(aEvent.mLocalOrigin));
   1126    ParentLayerCoord thumbWidth = GetAxisLength(otherDirection, thumbRect);
   1127    // Avoid triggering this condition spuriously when the thumb is
   1128    // offscreen and its visible region is therefore empty.
   1129    if (thumbWidth > 0 && thumbWidth * snapMultiplier < distance) {
   1130      isMouseAwayFromThumb = true;
   1131      APZC_LOG("%p determined mouse is away from thumb, will snap\n", this);
   1132    }
   1133  }
   1134 
   1135  RecursiveMutexAutoLock lock(mRecursiveMutex);
   1136  OuterCSSCoord thumbPosition;
   1137  if (isMouseAwayFromThumb) {
   1138    thumbPosition = aInitialThumbPos;
   1139  } else {
   1140    thumbPosition = ConvertScrollbarPoint(aEvent.mLocalOrigin, scrollbarData) -
   1141                    aDragMetrics.mScrollbarDragOffset;
   1142  }
   1143 
   1144  OuterCSSCoord maxThumbPos = scrollbarData.mScrollTrackLength;
   1145  maxThumbPos -= scrollbarData.mThumbLength;
   1146 
   1147  float scrollPercent =
   1148      maxThumbPos.value == 0.0f ? 0.0f : (float)(thumbPosition / maxThumbPos);
   1149  APZC_LOG("%p scrollbar dragged to %f percent\n", this, scrollPercent);
   1150 
   1151  CSSCoord minScrollPosition =
   1152      GetAxisStart(direction, aInitialScrollableRect.TopLeft());
   1153  CSSCoord maxScrollPosition =
   1154      GetAxisStart(direction, aInitialScrollableRect.BottomRight()) -
   1155      GetAxisLength(direction, Metrics().CalculateCompositedSizeInCssPixels());
   1156  CSSCoord scrollPosition =
   1157      minScrollPosition +
   1158      (scrollPercent * (maxScrollPosition - minScrollPosition));
   1159 
   1160  scrollPosition = std::max(scrollPosition, minScrollPosition);
   1161  scrollPosition = std::min(scrollPosition, maxScrollPosition);
   1162 
   1163  CSSPoint scrollOffset = Metrics().GetVisualScrollOffset();
   1164  if (direction == ScrollDirection::eHorizontal) {
   1165    scrollOffset.x = scrollPosition;
   1166  } else {
   1167    scrollOffset.y = scrollPosition;
   1168  }
   1169  APZC_LOG("%p set scroll offset to %s from scrollbar drag\n", this,
   1170           ToString(scrollOffset).c_str());
   1171  // Since the scroll position was calculated based on the scrollable rect at
   1172  // the start of the drag, we need to clamp the scroll position in case the
   1173  // scrollable rect has since shrunk.
   1174  ClampAndSetVisualScrollOffset(scrollOffset);
   1175  ScheduleCompositeAndMaybeRepaint();
   1176 
   1177  return nsEventStatus_eConsumeNoDefault;
   1178 }
   1179 
   1180 nsEventStatus AsyncPanZoomController::HandleInputEvent(
   1181    const InputData& aEvent,
   1182    const ScreenToParentLayerMatrix4x4& aTransformToApzc) {
   1183  APZThreadUtils::AssertOnControllerThread();
   1184 
   1185  nsEventStatus rv = nsEventStatus_eIgnore;
   1186 
   1187  switch (aEvent.mInputType) {
   1188    case MULTITOUCH_INPUT: {
   1189      MultiTouchInput multiTouchInput = aEvent.AsMultiTouchInput();
   1190      RefPtr<GestureEventListener> listener = GetGestureEventListener();
   1191      if (listener) {
   1192        // We only care about screen coordinates in the gesture listener,
   1193        // so we don't bother transforming the event to parent layer coordinates
   1194        rv = listener->HandleInputEvent(multiTouchInput);
   1195        if (rv == nsEventStatus_eConsumeNoDefault) {
   1196          return rv;
   1197        }
   1198      }
   1199 
   1200      if (!multiTouchInput.TransformToLocal(aTransformToApzc)) {
   1201        return rv;
   1202      }
   1203 
   1204      switch (multiTouchInput.mType) {
   1205        case MultiTouchInput::MULTITOUCH_START:
   1206          rv = OnTouchStart(multiTouchInput);
   1207          break;
   1208        case MultiTouchInput::MULTITOUCH_MOVE:
   1209          rv = OnTouchMove(multiTouchInput);
   1210          break;
   1211        case MultiTouchInput::MULTITOUCH_END:
   1212          rv = OnTouchEnd(multiTouchInput);
   1213          break;
   1214        case MultiTouchInput::MULTITOUCH_CANCEL:
   1215          rv = OnTouchCancel(multiTouchInput);
   1216          break;
   1217      }
   1218      break;
   1219    }
   1220    case PANGESTURE_INPUT: {
   1221      PanGestureInput panGestureInput = aEvent.AsPanGestureInput();
   1222      if (!panGestureInput.TransformToLocal(aTransformToApzc)) {
   1223        return rv;
   1224      }
   1225 
   1226      switch (panGestureInput.mType) {
   1227        case PanGestureInput::PANGESTURE_MAYSTART:
   1228          rv = OnPanMayBegin(panGestureInput);
   1229          break;
   1230        case PanGestureInput::PANGESTURE_CANCELLED:
   1231          rv = OnPanCancelled(panGestureInput);
   1232          break;
   1233        case PanGestureInput::PANGESTURE_START:
   1234          rv = OnPanBegin(panGestureInput);
   1235          break;
   1236        case PanGestureInput::PANGESTURE_PAN:
   1237          rv = OnPan(panGestureInput, FingersOnTouchpad::Yes);
   1238          break;
   1239        case PanGestureInput::PANGESTURE_END:
   1240          rv = OnPanEnd(panGestureInput);
   1241          break;
   1242        case PanGestureInput::PANGESTURE_MOMENTUMSTART:
   1243          rv = OnPanMomentumStart(panGestureInput);
   1244          break;
   1245        case PanGestureInput::PANGESTURE_MOMENTUMPAN:
   1246          rv = OnPan(panGestureInput, FingersOnTouchpad::No);
   1247          break;
   1248        case PanGestureInput::PANGESTURE_MOMENTUMEND:
   1249          rv = OnPanMomentumEnd(panGestureInput);
   1250          break;
   1251        case PanGestureInput::PANGESTURE_INTERRUPTED:
   1252          rv = OnPanInterrupted(panGestureInput);
   1253          break;
   1254      }
   1255      break;
   1256    }
   1257    case MOUSE_INPUT: {
   1258      MouseInput mouseInput = aEvent.AsMouseInput();
   1259      if (!mouseInput.TransformToLocal(aTransformToApzc)) {
   1260        return rv;
   1261      }
   1262      break;
   1263    }
   1264    case SCROLLWHEEL_INPUT: {
   1265      ScrollWheelInput scrollInput = aEvent.AsScrollWheelInput();
   1266      if (!scrollInput.TransformToLocal(aTransformToApzc)) {
   1267        return rv;
   1268      }
   1269 
   1270      rv = OnScrollWheel(scrollInput);
   1271      break;
   1272    }
   1273    case PINCHGESTURE_INPUT: {
   1274      // The APZCTreeManager should take care of ensuring that only root-content
   1275      // APZCs get pinch inputs.
   1276      MOZ_ASSERT(IsRootContent());
   1277      PinchGestureInput pinchInput = aEvent.AsPinchGestureInput();
   1278      if (!pinchInput.TransformToLocal(aTransformToApzc)) {
   1279        return rv;
   1280      }
   1281 
   1282      rv = HandleGestureEvent(pinchInput);
   1283      break;
   1284    }
   1285    case TAPGESTURE_INPUT: {
   1286      TapGestureInput tapInput = aEvent.AsTapGestureInput();
   1287      if (!tapInput.TransformToLocal(aTransformToApzc)) {
   1288        return rv;
   1289      }
   1290 
   1291      rv = HandleGestureEvent(tapInput);
   1292      break;
   1293    }
   1294    case KEYBOARD_INPUT: {
   1295      const KeyboardInput& keyInput = aEvent.AsKeyboardInput();
   1296      rv = OnKeyboard(keyInput);
   1297      break;
   1298    }
   1299  }
   1300 
   1301  return rv;
   1302 }
   1303 
   1304 nsEventStatus AsyncPanZoomController::HandleGestureEvent(
   1305    const InputData& aEvent) {
   1306  APZThreadUtils::AssertOnControllerThread();
   1307 
   1308  nsEventStatus rv = nsEventStatus_eIgnore;
   1309 
   1310  switch (aEvent.mInputType) {
   1311    case PINCHGESTURE_INPUT: {
   1312      // This may be invoked via a one-touch-pinch gesture from
   1313      // GestureEventListener. In that case we want redirect it to the enclosing
   1314      // root-content APZC.
   1315      if (!IsRootContent()) {
   1316        if (APZCTreeManager* treeManagerLocal = GetApzcTreeManager()) {
   1317          if (RefPtr<AsyncPanZoomController> root =
   1318                  treeManagerLocal->FindZoomableApzc(this)) {
   1319            rv = root->HandleGestureEvent(aEvent);
   1320          }
   1321        }
   1322        break;
   1323      }
   1324      PinchGestureInput pinchGestureInput = aEvent.AsPinchGestureInput();
   1325      pinchGestureInput.TransformToLocal(GetTransformToThis());
   1326      switch (pinchGestureInput.mType) {
   1327        case PinchGestureInput::PINCHGESTURE_START:
   1328          rv = OnScaleBegin(pinchGestureInput);
   1329          break;
   1330        case PinchGestureInput::PINCHGESTURE_SCALE:
   1331          rv = OnScale(pinchGestureInput);
   1332          break;
   1333        case PinchGestureInput::PINCHGESTURE_FINGERLIFTED:
   1334        case PinchGestureInput::PINCHGESTURE_END:
   1335          rv = OnScaleEnd(pinchGestureInput);
   1336          break;
   1337      }
   1338      break;
   1339    }
   1340    case TAPGESTURE_INPUT: {
   1341      TapGestureInput tapGestureInput = aEvent.AsTapGestureInput();
   1342      tapGestureInput.TransformToLocal(GetTransformToThis());
   1343      switch (tapGestureInput.mType) {
   1344        case TapGestureInput::TAPGESTURE_LONG:
   1345          rv = OnLongPress(tapGestureInput);
   1346          break;
   1347        case TapGestureInput::TAPGESTURE_LONG_UP:
   1348          rv = OnLongPressUp(tapGestureInput);
   1349          break;
   1350        case TapGestureInput::TAPGESTURE_UP:
   1351          rv = OnSingleTapUp(tapGestureInput);
   1352          break;
   1353        case TapGestureInput::TAPGESTURE_CONFIRMED:
   1354          rv = OnSingleTapConfirmed(tapGestureInput);
   1355          break;
   1356        case TapGestureInput::TAPGESTURE_DOUBLE:
   1357          if (!IsRootContent()) {
   1358            if (APZCTreeManager* treeManagerLocal = GetApzcTreeManager()) {
   1359              if (AsyncPanZoomController* apzc =
   1360                      treeManagerLocal->FindRootApzcFor(GetLayersId())) {
   1361                rv = apzc->OnDoubleTap(tapGestureInput);
   1362              }
   1363            }
   1364            break;
   1365          }
   1366          rv = OnDoubleTap(tapGestureInput);
   1367          break;
   1368        case TapGestureInput::TAPGESTURE_SECOND:
   1369          rv = OnSecondTap(tapGestureInput);
   1370          break;
   1371        case TapGestureInput::TAPGESTURE_CANCEL:
   1372          rv = OnCancelTap(tapGestureInput);
   1373          break;
   1374      }
   1375      break;
   1376    }
   1377    default:
   1378      MOZ_ASSERT_UNREACHABLE("Unhandled input event");
   1379      break;
   1380  }
   1381 
   1382  return rv;
   1383 }
   1384 
   1385 void AsyncPanZoomController::StartAutoscroll(const ScreenPoint& aPoint) {
   1386  // Cancel any existing animation.
   1387  CancelAnimation();
   1388 
   1389  SetState(AUTOSCROLL);
   1390  StartAnimation(do_AddRef(new AutoscrollAnimation(*this, aPoint)));
   1391 }
   1392 
   1393 void AsyncPanZoomController::StopAutoscroll() {
   1394  if (mState == AUTOSCROLL) {
   1395    CancelAnimation(TriggeredExternally);
   1396  }
   1397 }
   1398 
   1399 nsEventStatus AsyncPanZoomController::OnTouchStart(
   1400    const MultiTouchInput& aEvent) {
   1401  APZC_LOG_DETAIL("got a touch-start in state %s\n", this,
   1402                  ToString(mState).c_str());
   1403  mPanDirRestricted = false;
   1404 
   1405  switch (mState) {
   1406    case FLING:
   1407    case ANIMATING_ZOOM:
   1408    case SMOOTH_SCROLL:
   1409    case OVERSCROLL_ANIMATION:
   1410    case PAN_MOMENTUM:
   1411    case AUTOSCROLL:
   1412      MOZ_ASSERT(GetCurrentTouchBlock());
   1413      GetCurrentTouchBlock()->GetOverscrollHandoffChain()->CancelAnimations(
   1414          ExcludeOverscroll);
   1415      [[fallthrough]];
   1416    case SCROLLBAR_DRAG:
   1417    case NOTHING: {
   1418      ParentLayerPoint point = GetFirstTouchPoint(aEvent);
   1419      mLastTouch.mPosition = mStartTouch = GetFirstExternalTouchPoint(aEvent);
   1420      StartTouch(point, aEvent.mTimeStamp);
   1421      if (RefPtr<GeckoContentController> controller =
   1422              GetGeckoContentController()) {
   1423        MOZ_ASSERT(GetCurrentTouchBlock());
   1424        const bool canBePanOrZoom =
   1425            GetCurrentTouchBlock()->GetOverscrollHandoffChain()->CanBePanned(
   1426                this) ||
   1427            (ZoomConstraintsAllowDoubleTapZoom() &&
   1428             GetCurrentTouchBlock()->TouchActionAllowsDoubleTapZoom());
   1429        controller->NotifyAPZStateChange(
   1430            GetGuid(), APZStateChange::eStartTouch, canBePanOrZoom,
   1431            Some(GetCurrentTouchBlock()->GetBlockId()));
   1432      }
   1433      mLastTouch.mTimeStamp = mTouchStartTime = aEvent.mTimeStamp;
   1434      SetState(TOUCHING);
   1435      mTouchScrollEventBuffer.push(aEvent);
   1436      break;
   1437    }
   1438    case TOUCHING:
   1439    case PANNING:
   1440    case PANNING_LOCKED_X:
   1441    case PANNING_LOCKED_Y:
   1442    case PINCHING:
   1443      NS_WARNING("Received impossible touch in OnTouchStart");
   1444      break;
   1445  }
   1446 
   1447  return nsEventStatus_eConsumeNoDefault;
   1448 }
   1449 
   1450 nsEventStatus AsyncPanZoomController::OnTouchMove(
   1451    const MultiTouchInput& aEvent) {
   1452  APZC_LOG_DETAIL("got a touch-move in state %s\n", this,
   1453                  ToString(mState).c_str());
   1454 
   1455  if (InScrollAnimationTriggeredByScript()) {
   1456    // Cancel smooth animation triggered by script.
   1457    CancelAnimation();
   1458    // Restart the touch event series.
   1459    return OnTouchStart(aEvent);
   1460  }
   1461 
   1462  switch (mState) {
   1463    case FLING:
   1464    case SMOOTH_SCROLL:
   1465    case NOTHING:
   1466    case ANIMATING_ZOOM:
   1467      // May happen if the user double-taps and drags without lifting after the
   1468      // second tap. Ignore the move if this happens.
   1469      return nsEventStatus_eIgnore;
   1470 
   1471    case TOUCHING: {
   1472      ScreenCoord panThreshold = GetTouchStartTolerance();
   1473      ExternalPoint extPoint = GetFirstExternalTouchPoint(aEvent);
   1474      Maybe<std::pair<MultiTouchInput, MultiTouchInput>> splitEvent;
   1475 
   1476      // We intentionally skip the UpdateWithTouchAtDevicePoint call when the
   1477      // panThreshold is zero. This ensures more deterministic behaviour during
   1478      // testing. If we call that, Axis::mPos gets updated to the point of this
   1479      // touchmove event, but we "consume" the move to overcome the
   1480      // panThreshold, so it's hard to pan a specific amount reliably from a
   1481      // mochitest.
   1482      if (panThreshold > 0.0f) {
   1483        const float vectorLength = PanVector(extPoint).Length();
   1484 
   1485        if (vectorLength < panThreshold) {
   1486          UpdateWithTouchAtDevicePoint(aEvent);
   1487          mLastTouch = {extPoint, aEvent.mTimeStamp};
   1488 
   1489          return nsEventStatus_eIgnore;
   1490        }
   1491 
   1492        splitEvent = MaybeSplitTouchMoveEvent(aEvent, panThreshold,
   1493                                              vectorLength, extPoint);
   1494 
   1495        UpdateWithTouchAtDevicePoint(splitEvent ? splitEvent->first : aEvent);
   1496      }
   1497 
   1498      nsEventStatus result;
   1499      const MultiTouchInput& firstEvent =
   1500          splitEvent ? splitEvent->first : aEvent;
   1501      mTouchScrollEventBuffer.push(firstEvent);
   1502 
   1503      MOZ_ASSERT(GetCurrentTouchBlock());
   1504      if (GetCurrentTouchBlock()->TouchActionAllowsPanningXY()) {
   1505        // In the calls to StartPanning() below, the first argument needs to be
   1506        // the External position of |firstEvent|.
   1507        // However, instead of computing that using
   1508        // GetFirstExternalTouchPoint(firstEvent), we pass |extPoint| which
   1509        // has been modified by MaybeSplitTouchMoveEvent() to the desired
   1510        // value. This is a workaround for the fact that recomputing the
   1511        // External point would require a round-trip through |mScreenPoint|
   1512        // which is an integer.
   1513 
   1514        // User tries to trigger a touch behavior. If allowed touch behavior is
   1515        // vertical pan + horizontal pan (touch-action value is equal to AUTO)
   1516        // we can return ConsumeNoDefault status immediately to trigger cancel
   1517        // event further.
   1518        // It should happen independent of the parent type (whether it is
   1519        // scrolling or not).
   1520        StartPanning(extPoint, firstEvent.mTimeStamp);
   1521        result = nsEventStatus_eConsumeNoDefault;
   1522      } else {
   1523        result = StartPanning(extPoint, firstEvent.mTimeStamp);
   1524      }
   1525 
   1526      if (splitEvent && IsInPanningState()) {
   1527        TrackTouch(splitEvent->second);
   1528        return nsEventStatus_eConsumeNoDefault;
   1529      }
   1530 
   1531      return result;
   1532    }
   1533 
   1534    case PANNING:
   1535    case PANNING_LOCKED_X:
   1536    case PANNING_LOCKED_Y:
   1537    case PAN_MOMENTUM:
   1538      TrackTouch(aEvent);
   1539      return nsEventStatus_eConsumeNoDefault;
   1540 
   1541    case PINCHING:
   1542      // The scale gesture listener should have handled this.
   1543      NS_WARNING(
   1544          "Gesture listener should have handled pinching in OnTouchMove.");
   1545      return nsEventStatus_eIgnore;
   1546 
   1547    case OVERSCROLL_ANIMATION:
   1548    case AUTOSCROLL:
   1549    case SCROLLBAR_DRAG:
   1550      // Should not receive a touch-move in the OVERSCROLL_ANIMATION state
   1551      // as touch blocks that begin in an overscrolled state cancel the
   1552      // animation. The same is true for wheel scroll animations.
   1553      NS_WARNING("Received impossible touch in OnTouchMove");
   1554      break;
   1555  }
   1556 
   1557  return nsEventStatus_eConsumeNoDefault;
   1558 }
   1559 
   1560 nsEventStatus AsyncPanZoomController::OnTouchEnd(
   1561    const MultiTouchInput& aEvent) {
   1562  APZC_LOG_DETAIL("got a touch-end in state %s\n", this,
   1563                  ToString(mState).c_str());
   1564  OnTouchEndOrCancel();
   1565 
   1566  // In case no touch behavior triggered previously we can avoid sending
   1567  // scroll events or requesting content repaint. This condition is added
   1568  // to make tests consistent - in case touch-action is NONE (and therefore
   1569  // no pans/zooms can be performed) we expected neither scroll or repaint
   1570  // events.
   1571  if (mState != NOTHING) {
   1572    RecursiveMutexAutoLock lock(mRecursiveMutex);
   1573  }
   1574 
   1575  switch (mState) {
   1576    case FLING:
   1577      // Should never happen.
   1578      NS_WARNING("Received impossible touch end in OnTouchEnd.");
   1579      [[fallthrough]];
   1580    case ANIMATING_ZOOM:
   1581    case SMOOTH_SCROLL:
   1582    case NOTHING:
   1583      // May happen if the user double-taps and drags without lifting after the
   1584      // second tap. Ignore if this happens.
   1585      return nsEventStatus_eIgnore;
   1586 
   1587    case TOUCHING:
   1588      // We may have some velocity stored on the axis from move events
   1589      // that were not big enough to trigger scrolling. Clear that out.
   1590      SetVelocityVector(ParentLayerPoint(0, 0));
   1591      MOZ_ASSERT(GetCurrentTouchBlock());
   1592      APZC_LOG("%p still has %u touch points active\n", this,
   1593               GetCurrentTouchBlock()->GetActiveTouchCount());
   1594      // In cases where the user is panning, then taps the second finger without
   1595      // entering a pinch, we will arrive here when the second finger is lifted.
   1596      // However the first finger is still down so we want to remain in state
   1597      // TOUCHING.
   1598      if (GetCurrentTouchBlock()->GetActiveTouchCount() == 0) {
   1599        // It's possible we may be overscrolled if the user tapped during a
   1600        // previous overscroll pan. Make sure to snap back in this situation.
   1601        // An ancestor APZC could be overscrolled instead of this APZC, so
   1602        // walk the handoff chain as well.
   1603        GetCurrentTouchBlock()
   1604            ->GetOverscrollHandoffChain()
   1605            ->SnapBackOverscrolledApzc(this);
   1606        mFlingAccelerator.Reset();
   1607        // SnapBackOverscrolledApzc() will put any APZC it causes to snap back
   1608        // into the OVERSCROLL_ANIMATION state. If that's not us, since we're
   1609        // done TOUCHING enter the NOTHING state.
   1610        if (mState != OVERSCROLL_ANIMATION) {
   1611          SetState(NOTHING);
   1612        }
   1613      }
   1614      return nsEventStatus_eIgnore;
   1615 
   1616    case PANNING:
   1617    case PANNING_LOCKED_X:
   1618    case PANNING_LOCKED_Y:
   1619    case PAN_MOMENTUM: {
   1620      MOZ_ASSERT(GetCurrentTouchBlock());
   1621      EndTouch(aEvent.mTimeStamp, Axis::ClearAxisLock::Yes);
   1622      return HandleEndOfPan();
   1623    }
   1624    case PINCHING:
   1625      SetState(NOTHING);
   1626      // Scale gesture listener should have handled this.
   1627      NS_WARNING(
   1628          "Gesture listener should have handled pinching in OnTouchEnd.");
   1629      return nsEventStatus_eIgnore;
   1630 
   1631    case OVERSCROLL_ANIMATION:
   1632    case AUTOSCROLL:
   1633    case SCROLLBAR_DRAG:
   1634      // Should not receive a touch-end in the OVERSCROLL_ANIMATION state
   1635      // as touch blocks that begin in an overscrolled state cancel the
   1636      // animation. The same is true for WHEEL_SCROLL.
   1637      NS_WARNING("Received impossible touch in OnTouchEnd");
   1638      break;
   1639  }
   1640 
   1641  return nsEventStatus_eConsumeNoDefault;
   1642 }
   1643 
   1644 nsEventStatus AsyncPanZoomController::OnTouchCancel(
   1645    const MultiTouchInput& aEvent) {
   1646  APZC_LOG_DETAIL("got a touch-cancel in state %s\n", this,
   1647                  ToString(mState).c_str());
   1648  OnTouchEndOrCancel();
   1649  CancelAnimationAndGestureState();
   1650  return nsEventStatus_eConsumeNoDefault;
   1651 }
   1652 
   1653 nsEventStatus AsyncPanZoomController::OnScaleBegin(
   1654    const PinchGestureInput& aEvent) {
   1655  APZC_LOG_DETAIL("got a scale-begin in state %s\n", this,
   1656                  ToString(mState).c_str());
   1657 
   1658  mPinchLocked = false;
   1659  mPinchPaintTimerSet = false;
   1660  // Note that there may not be a touch block at this point, if we received the
   1661  // PinchGestureEvent directly from widget code without any touch events.
   1662  if (HasReadyTouchBlock() &&
   1663      !GetCurrentTouchBlock()->TouchActionAllowsPinchZoom()) {
   1664    return nsEventStatus_eIgnore;
   1665  }
   1666 
   1667  // For platforms that don't support APZ zooming, dispatch a message to the
   1668  // content controller, it may want to do something else with this gesture.
   1669  // FIXME: bug 1525793 -- this may need to handle zooming or not on a
   1670  // per-document basis.
   1671  if (!StaticPrefs::apz_allow_zooming()) {
   1672    if (RefPtr<GeckoContentController> controller =
   1673            GetGeckoContentController()) {
   1674      APZC_LOG("%p notifying controller of pinch gesture start\n", this);
   1675      controller->NotifyPinchGesture(
   1676          aEvent.mType, GetGuid(),
   1677          ViewAs<LayoutDevicePixel>(
   1678              aEvent.mFocusPoint,
   1679              PixelCastJustification::
   1680                  LayoutDeviceIsScreenForUntransformedEvent),
   1681          0, aEvent.modifiers);
   1682    }
   1683  }
   1684 
   1685  SetState(PINCHING);
   1686  glean::apz_zoom::pinchsource.AccumulateSingleSample((int)aEvent.mSource);
   1687  SetVelocityVector(ParentLayerPoint(0, 0));
   1688  RecursiveMutexAutoLock lock(mRecursiveMutex);
   1689  mLastZoomFocus =
   1690      aEvent.mLocalFocusPoint - Metrics().GetCompositionBounds().TopLeft();
   1691 
   1692  mPinchEventBuffer.push(aEvent);
   1693 
   1694  return nsEventStatus_eConsumeNoDefault;
   1695 }
   1696 
   1697 nsEventStatus AsyncPanZoomController::OnScale(const PinchGestureInput& aEvent) {
   1698  APZC_LOG_DETAIL("got a scale in state %s\n", this, ToString(mState).c_str());
   1699 
   1700  if (HasReadyTouchBlock() &&
   1701      !GetCurrentTouchBlock()->TouchActionAllowsPinchZoom()) {
   1702    return nsEventStatus_eIgnore;
   1703  }
   1704 
   1705  if (mState != PINCHING) {
   1706    return nsEventStatus_eConsumeNoDefault;
   1707  }
   1708 
   1709  mPinchEventBuffer.push(aEvent);
   1710  HandlePinchLocking(aEvent);
   1711  bool allowZoom = ZoomConstraintsAllowZoom() && !mPinchLocked;
   1712 
   1713  // If we are pinch-locked, this is a two-finger pan.
   1714  // Tracking panning distance and velocity.
   1715  // UpdateWithTouchAtDevicePoint() acquires the tree lock, so
   1716  // it cannot be called while the mRecursiveMutex lock is held.
   1717  if (mPinchLocked) {
   1718    mX.UpdateWithTouchAtDevicePoint(aEvent.mLocalFocusPoint.x,
   1719                                    aEvent.mTimeStamp);
   1720    mY.UpdateWithTouchAtDevicePoint(aEvent.mLocalFocusPoint.y,
   1721                                    aEvent.mTimeStamp);
   1722  }
   1723 
   1724  // FIXME: bug 1525793 -- this may need to handle zooming or not on a
   1725  // per-document basis.
   1726  if (!StaticPrefs::apz_allow_zooming()) {
   1727    if (RefPtr<GeckoContentController> controller =
   1728            GetGeckoContentController()) {
   1729      APZC_LOG("%p notifying controller of pinch gesture\n", this);
   1730      controller->NotifyPinchGesture(
   1731          aEvent.mType, GetGuid(),
   1732          ViewAs<LayoutDevicePixel>(
   1733              aEvent.mFocusPoint,
   1734              PixelCastJustification::
   1735                  LayoutDeviceIsScreenForUntransformedEvent),
   1736          ViewAs<LayoutDevicePixel>(
   1737              aEvent.mCurrentSpan - aEvent.mPreviousSpan,
   1738              PixelCastJustification::
   1739                  LayoutDeviceIsScreenForUntransformedEvent),
   1740          aEvent.modifiers);
   1741    }
   1742  }
   1743 
   1744  {
   1745    RecursiveMutexAutoLock lock(mRecursiveMutex);
   1746 
   1747    AutoRecordCompositorScrollUpdate csu(
   1748        this, CompositorScrollUpdate::Source::UserInteraction, lock);
   1749 
   1750    // Only the root APZC is zoomable, and the root APZC is not allowed to have
   1751    // different x and y scales. If it did, the calculations in this function
   1752    // would have to be adjusted (as e.g. it would no longer be valid to take
   1753    // the minimum or maximum of the ratios of the widths and heights of the
   1754    // page rect and the composition bounds).
   1755    MOZ_ASSERT(Metrics().IsRootContent());
   1756 
   1757    CSSToParentLayerScale userZoom = Metrics().GetZoom();
   1758    ParentLayerPoint focusPoint =
   1759        aEvent.mLocalFocusPoint - Metrics().GetCompositionBounds().TopLeft();
   1760    CSSPoint cssFocusPoint;
   1761    if (Metrics().GetZoom() != CSSToParentLayerScale(0)) {
   1762      cssFocusPoint = focusPoint / Metrics().GetZoom();
   1763    }
   1764 
   1765    ParentLayerPoint focusChange = mLastZoomFocus - focusPoint;
   1766    mLastZoomFocus = focusPoint;
   1767    // If displacing by the change in focus point will take us off page bounds,
   1768    // then reduce the displacement such that it doesn't.
   1769    focusChange.x -= mX.DisplacementWillOverscrollAmount(focusChange.x);
   1770    focusChange.y -= mY.DisplacementWillOverscrollAmount(focusChange.y);
   1771    if (userZoom != CSSToParentLayerScale(0)) {
   1772      ScrollBy(focusChange / userZoom);
   1773    }
   1774 
   1775    // If the span is zero or close to it, we don't want to process this zoom
   1776    // change because we're going to get wonky numbers for the spanRatio. So
   1777    // let's bail out here. Note that we do this after the focus-change-scroll
   1778    // above, so that if we have a pinch with zero span but changing focus,
   1779    // such as generated by some Synaptics touchpads on Windows, we still
   1780    // scroll properly.
   1781    float prevSpan = aEvent.mPreviousSpan;
   1782    if (fabsf(prevSpan) <= EPSILON || fabsf(aEvent.mCurrentSpan) <= EPSILON) {
   1783      // We might have done a nonzero ScrollBy above, so update metrics and
   1784      // repaint/recomposite
   1785      ScheduleCompositeAndMaybeRepaint();
   1786      return nsEventStatus_eConsumeNoDefault;
   1787    }
   1788    float spanRatio = aEvent.mCurrentSpan / aEvent.mPreviousSpan;
   1789 
   1790    // When we zoom in with focus, we can zoom too much towards the boundaries
   1791    // that we actually go over them. These are the needed displacements along
   1792    // either axis such that we don't overscroll the boundaries when zooming.
   1793    CSSPoint neededDisplacement;
   1794 
   1795    CSSToParentLayerScale realMinZoom = mZoomConstraints.mMinZoom;
   1796    CSSToParentLayerScale realMaxZoom = mZoomConstraints.mMaxZoom;
   1797    realMinZoom.scale =
   1798        std::max(realMinZoom.scale, Metrics().GetCompositionBounds().Width() /
   1799                                        Metrics().GetScrollableRect().Width());
   1800    realMinZoom.scale =
   1801        std::max(realMinZoom.scale, Metrics().GetCompositionBounds().Height() /
   1802                                        Metrics().GetScrollableRect().Height());
   1803    if (realMaxZoom < realMinZoom) {
   1804      realMaxZoom = realMinZoom;
   1805    }
   1806 
   1807    bool doScale = allowZoom && ((spanRatio > 1.0 && userZoom < realMaxZoom) ||
   1808                                 (spanRatio < 1.0 && userZoom > realMinZoom));
   1809 
   1810    if (doScale) {
   1811      spanRatio = std::clamp(spanRatio, realMinZoom.scale / userZoom.scale,
   1812                             realMaxZoom.scale / userZoom.scale);
   1813 
   1814      // Note that the spanRatio here should never put us into OVERSCROLL_BOTH
   1815      // because up above we clamped it.
   1816      neededDisplacement.x =
   1817          -mX.ScaleWillOverscrollAmount(spanRatio, cssFocusPoint.x);
   1818      neededDisplacement.y =
   1819          -mY.ScaleWillOverscrollAmount(spanRatio, cssFocusPoint.y);
   1820 
   1821      ScaleWithFocus(spanRatio, cssFocusPoint);
   1822 
   1823      if (neededDisplacement != CSSPoint()) {
   1824        ScrollBy(neededDisplacement);
   1825      }
   1826 
   1827      // We don't want to redraw on every scale, so throttle it.
   1828      if (!mPinchPaintTimerSet) {
   1829        const int delay = StaticPrefs::apz_scale_repaint_delay_ms();
   1830        if (delay >= 0) {
   1831          if (RefPtr<GeckoContentController> controller =
   1832                  GetGeckoContentController()) {
   1833            mPinchPaintTimerSet = true;
   1834            controller->PostDelayedTask(
   1835                NewRunnableMethod(
   1836                    "layers::AsyncPanZoomController::"
   1837                    "DoDelayedRequestContentRepaint",
   1838                    this,
   1839                    &AsyncPanZoomController::DoDelayedRequestContentRepaint),
   1840                delay);
   1841          }
   1842        }
   1843      } else if (apz::AboutToCheckerboard(mLastContentPaintMetrics,
   1844                                          Metrics())) {
   1845        // If we already scheduled a throttled repaint request but are also
   1846        // in danger of checkerboarding soon, trigger the repaint request to
   1847        // go out immediately. This should reduce the amount of time we spend
   1848        // checkerboarding.
   1849        //
   1850        // Note that if we remain in this "about to
   1851        // checkerboard" state over a period of time with multiple pinch input
   1852        // events (which is quite likely), then we will flip-flop between taking
   1853        // the above branch (!mPinchPaintTimerSet) and this branch (which will
   1854        // flush the repaint request and reset mPinchPaintTimerSet to false).
   1855        // This is sort of desirable because it halves the number of repaint
   1856        // requests we send, and therefore reduces IPC traffic.
   1857        // Keep in mind that many of these repaint requests will be ignored on
   1858        // the main-thread anyway due to the resolution mismatch - the first
   1859        // repaint request will be honored because APZ's notion of the painted
   1860        // resolution matches the actual main thread resolution, but that first
   1861        // repaint request will change the resolution on the main thread.
   1862        // Subsequent repaint requests will be ignored in APZCCallbackHelper, at
   1863        // https://searchfox.org/mozilla-central/rev/e0eb861a187f0bb6d994228f2e0e49b2c9ee455e/gfx/layers/apz/util/APZCCallbackHelper.cpp#331-338,
   1864        // until we receive a NotifyLayersUpdated call that re-syncs APZ's
   1865        // notion of the painted resolution to the main thread. These ignored
   1866        // repaint requests are contributing to IPC traffic needlessly, and so
   1867        // halving the number of repaint requests (as mentioned above) seems
   1868        // desirable.
   1869        DoDelayedRequestContentRepaint();
   1870      }
   1871    } else {
   1872      // Trigger a repaint request after scrolling.
   1873      RequestContentRepaint();
   1874    }
   1875 
   1876    // We did a ScrollBy call above even if we didn't do a scale, so we
   1877    // should composite for that.
   1878    ScheduleComposite();
   1879  }
   1880 
   1881  return nsEventStatus_eConsumeNoDefault;
   1882 }
   1883 
   1884 nsEventStatus AsyncPanZoomController::OnScaleEnd(
   1885    const PinchGestureInput& aEvent) {
   1886  APZC_LOG_DETAIL("got a scale-end in state %s\n", this,
   1887                  ToString(mState).c_str());
   1888 
   1889  mPinchPaintTimerSet = false;
   1890 
   1891  if (HasReadyTouchBlock() &&
   1892      !GetCurrentTouchBlock()->TouchActionAllowsPinchZoom()) {
   1893    return nsEventStatus_eIgnore;
   1894  }
   1895 
   1896  // FIXME: bug 1525793 -- this may need to handle zooming or not on a
   1897  // per-document basis.
   1898  if (!StaticPrefs::apz_allow_zooming()) {
   1899    if (RefPtr<GeckoContentController> controller =
   1900            GetGeckoContentController()) {
   1901      controller->NotifyPinchGesture(
   1902          aEvent.mType, GetGuid(),
   1903          ViewAs<LayoutDevicePixel>(
   1904              aEvent.mFocusPoint,
   1905              PixelCastJustification::
   1906                  LayoutDeviceIsScreenForUntransformedEvent),
   1907          0, aEvent.modifiers);
   1908    }
   1909  }
   1910 
   1911  {
   1912    RecursiveMutexAutoLock lock(mRecursiveMutex);
   1913    ScheduleComposite();
   1914    RequestContentRepaint();
   1915  }
   1916 
   1917  mPinchEventBuffer.clear();
   1918 
   1919  if (aEvent.mType == PinchGestureInput::PINCHGESTURE_FINGERLIFTED) {
   1920    // One finger is still down, so transition to a TOUCHING state
   1921    if (!mPinchLocked) {
   1922      mPanDirRestricted = false;
   1923      mLastTouch.mPosition = mStartTouch =
   1924          ToExternalPoint(aEvent.mScreenOffset, aEvent.mFocusPoint);
   1925      mLastTouch.mTimeStamp = mTouchStartTime = aEvent.mTimeStamp;
   1926      StartTouch(aEvent.mLocalFocusPoint, aEvent.mTimeStamp);
   1927      SetState(TOUCHING);
   1928    } else {
   1929      // If we are pinch locked, StartTouch() was already called
   1930      // when we entered the pinch lock.
   1931      StartPanning(ToExternalPoint(aEvent.mScreenOffset, aEvent.mFocusPoint),
   1932                   aEvent.mTimeStamp);
   1933    }
   1934  } else {
   1935    // Otherwise, handle the gesture being completely done.
   1936 
   1937    // Some of the code paths below, like ScrollSnap() or HandleEndOfPan(),
   1938    // may start an animation, but otherwise we want to end up in the NOTHING
   1939    // state. To avoid state change notification churn, we use a
   1940    // notification blocker.
   1941    bool stateWasPinching = (mState == PINCHING);
   1942    StateChangeNotificationBlocker blocker(this);
   1943    SetState(NOTHING);
   1944 
   1945    if (ZoomConstraintsAllowZoom()) {
   1946      RecursiveMutexAutoLock lock(mRecursiveMutex);
   1947 
   1948      // We can get into a situation where we are overscrolled at the end of a
   1949      // pinch if we go into overscroll with a two-finger pan, and then turn
   1950      // that into a pinch by increasing the span sufficiently. In such a case,
   1951      // there is no snap-back animation to get us out of overscroll, so we need
   1952      // to get out of it somehow.
   1953      // Moreover, in cases of scroll handoff, the overscroll can be on an APZC
   1954      // further up in the handoff chain rather than on the current APZC, so
   1955      // we need to clear overscroll along the entire handoff chain.
   1956      if (HasReadyTouchBlock()) {
   1957        GetCurrentTouchBlock()->GetOverscrollHandoffChain()->ClearOverscroll();
   1958      } else {
   1959        ClearOverscroll();
   1960      }
   1961      // Along with clearing the overscroll, we also want to snap to the nearest
   1962      // snap point as appropriate.
   1963      ScrollSnap(ScrollSnapFlags::IntendedEndPosition);
   1964    } else {
   1965      // when zoom is not allowed
   1966      EndTouch(aEvent.mTimeStamp, Axis::ClearAxisLock::Yes);
   1967      if (stateWasPinching) {
   1968        // still pinching
   1969        if (HasReadyTouchBlock()) {
   1970          return HandleEndOfPan();
   1971        }
   1972      }
   1973    }
   1974  }
   1975  return nsEventStatus_eConsumeNoDefault;
   1976 }
   1977 
   1978 nsEventStatus AsyncPanZoomController::HandleEndOfPan() {
   1979  MOZ_ASSERT(!mAnimation);
   1980  MOZ_ASSERT(GetCurrentTouchBlock() || GetCurrentPanGestureBlock());
   1981  GetCurrentInputBlock()->GetOverscrollHandoffChain()->FlushRepaints();
   1982  ParentLayerPoint flingVelocity = GetVelocityVector();
   1983 
   1984  // Clear our velocities; if DispatchFling() gives the fling to us,
   1985  // the fling velocity gets *added* to our existing velocity in
   1986  // AcceptFling().
   1987  SetVelocityVector(ParentLayerPoint(0, 0));
   1988  // Clear our state so that we don't stay in the PANNING state
   1989  // if DispatchFling() gives the fling to somone else. However,
   1990  // don't send the state change notification until we've determined
   1991  // what our final state is to avoid notification churn.
   1992  StateChangeNotificationBlocker blocker(this);
   1993  SetState(NOTHING);
   1994 
   1995  APZC_LOG("%p starting a fling animation if %f > %f\n", this,
   1996           flingVelocity.Length().value,
   1997           StaticPrefs::apz_fling_min_velocity_threshold());
   1998 
   1999  if (flingVelocity.Length() <=
   2000      StaticPrefs::apz_fling_min_velocity_threshold()) {
   2001    // Relieve overscroll now if needed, since we will not transition to a fling
   2002    // animation and then an overscroll animation, and relieve it then.
   2003    GetCurrentInputBlock()
   2004        ->GetOverscrollHandoffChain()
   2005        ->SnapBackOverscrolledApzc(this);
   2006    mFlingAccelerator.Reset();
   2007    return nsEventStatus_eConsumeNoDefault;
   2008  }
   2009 
   2010  // Make a local copy of the tree manager pointer and check that it's not
   2011  // null before calling DispatchFling(). This is necessary because Destroy(),
   2012  // which nulls out mTreeManager, could be called concurrently.
   2013  if (APZCTreeManager* treeManagerLocal = GetApzcTreeManager()) {
   2014    const FlingHandoffState handoffState{
   2015        flingVelocity,
   2016        GetCurrentInputBlock()->GetOverscrollHandoffChain(),
   2017        Some(mTouchStartRestingTimeBeforePan),
   2018        mMinimumVelocityDuringPan.valueOr(0),
   2019        false /* not handoff */,
   2020        GetCurrentInputBlock()->GetScrolledApzc()};
   2021    treeManagerLocal->DispatchFling(this, handoffState);
   2022  }
   2023  return nsEventStatus_eConsumeNoDefault;
   2024 }
   2025 
   2026 Maybe<LayoutDevicePoint> AsyncPanZoomController::ConvertToGecko(
   2027    const ScreenIntPoint& aPoint) {
   2028  if (APZCTreeManager* treeManagerLocal = GetApzcTreeManager()) {
   2029    if (Maybe<ScreenIntPoint> layoutPoint =
   2030            treeManagerLocal->ConvertToGecko(aPoint, this)) {
   2031      return Some(LayoutDevicePoint(ViewAs<LayoutDevicePixel>(
   2032          *layoutPoint,
   2033          PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent)));
   2034    }
   2035  }
   2036  return Nothing();
   2037 }
   2038 
   2039 OuterCSSCoord AsyncPanZoomController::ConvertScrollbarPoint(
   2040    const ParentLayerPoint& aScrollbarPoint,
   2041    const ScrollbarData& aThumbData) const {
   2042  RecursiveMutexAutoLock lock(mRecursiveMutex);
   2043 
   2044  CSSPoint scrollbarPoint;
   2045  if (Metrics().GetZoom() != CSSToParentLayerScale(0)) {
   2046    // First, get it into the right coordinate space.
   2047    scrollbarPoint = aScrollbarPoint / Metrics().GetZoom();
   2048  }
   2049 
   2050  // The scrollbar can be transformed with the frame but the pres shell
   2051  // resolution is only applied to the scroll frame.
   2052  OuterCSSPoint outerScrollbarPoint =
   2053      scrollbarPoint * Metrics().GetCSSToOuterCSSScale();
   2054 
   2055  // Now, get it to be relative to the beginning of the scroll track.
   2056  OuterCSSRect cssCompositionBound =
   2057      Metrics().CalculateCompositionBoundsInOuterCssPixels();
   2058  return GetAxisStart(*aThumbData.mDirection, outerScrollbarPoint) -
   2059         GetAxisStart(*aThumbData.mDirection, cssCompositionBound) -
   2060         aThumbData.mScrollTrackStart;
   2061 }
   2062 
   2063 static bool AllowsScrollingMoreThanOnePage(double aMultiplier) {
   2064  return Abs(aMultiplier) >=
   2065         EventStateManager::MIN_MULTIPLIER_VALUE_ALLOWING_OVER_ONE_PAGE_SCROLL;
   2066 }
   2067 
   2068 ParentLayerPoint AsyncPanZoomController::GetScrollWheelDelta(
   2069    const ScrollWheelInput& aEvent) const {
   2070  return GetScrollWheelDelta(aEvent, aEvent.mDeltaX, aEvent.mDeltaY,
   2071                             aEvent.mUserDeltaMultiplierX,
   2072                             aEvent.mUserDeltaMultiplierY);
   2073 }
   2074 
   2075 ParentLayerPoint AsyncPanZoomController::GetScrollWheelDelta(
   2076    const ScrollWheelInput& aEvent, double aDeltaX, double aDeltaY,
   2077    double aMultiplierX, double aMultiplierY) const {
   2078  ParentLayerSize scrollAmount;
   2079  ParentLayerSize pageScrollSize;
   2080 
   2081  {
   2082    // Grab the lock to access the frame metrics.
   2083    RecursiveMutexAutoLock lock(mRecursiveMutex);
   2084    LayoutDeviceIntSize scrollAmountLD = mScrollMetadata.GetLineScrollAmount();
   2085    LayoutDeviceIntSize pageScrollSizeLD =
   2086        mScrollMetadata.GetPageScrollAmount();
   2087    scrollAmount = scrollAmountLD / Metrics().GetDevPixelsPerCSSPixel() *
   2088                   Metrics().GetZoom();
   2089    pageScrollSize = pageScrollSizeLD / Metrics().GetDevPixelsPerCSSPixel() *
   2090                     Metrics().GetZoom();
   2091  }
   2092 
   2093  ParentLayerPoint delta;
   2094  switch (aEvent.mDeltaType) {
   2095    case ScrollWheelInput::SCROLLDELTA_LINE: {
   2096      delta.x = aDeltaX * scrollAmount.width;
   2097      delta.y = aDeltaY * scrollAmount.height;
   2098      break;
   2099    }
   2100    case ScrollWheelInput::SCROLLDELTA_PAGE: {
   2101      delta.x = aDeltaX * pageScrollSize.width;
   2102      delta.y = aDeltaY * pageScrollSize.height;
   2103      break;
   2104    }
   2105    case ScrollWheelInput::SCROLLDELTA_PIXEL: {
   2106      delta = ToParentLayerCoordinates(ScreenPoint(aDeltaX, aDeltaY),
   2107                                       aEvent.mOrigin);
   2108      break;
   2109    }
   2110  }
   2111 
   2112  // Apply user-set multipliers.
   2113  delta.x *= aMultiplierX;
   2114  delta.y *= aMultiplierY;
   2115  APZC_LOGV(
   2116      "user-multiplied delta is %s (deltaType %d, line size %s, page size %s)",
   2117      ToString(delta).c_str(), (int)aEvent.mDeltaType,
   2118      ToString(scrollAmount).c_str(), ToString(pageScrollSize).c_str());
   2119 
   2120  // For the conditions under which we allow system scroll overrides, see
   2121  // WidgetWheelEvent::OverriddenDelta{X,Y}.
   2122  // Note that we do *not* restrict this to the root content, see bug 1217715
   2123  // for discussion on this.
   2124  if (StaticPrefs::mousewheel_system_scroll_override_enabled() &&
   2125      !aEvent.IsCustomizedByUserPrefs() &&
   2126      aEvent.mDeltaType == ScrollWheelInput::SCROLLDELTA_LINE &&
   2127      aEvent.mAllowToOverrideSystemScrollSpeed) {
   2128    delta.x = WidgetWheelEvent::ComputeOverriddenDelta(delta.x, false);
   2129    delta.y = WidgetWheelEvent::ComputeOverriddenDelta(delta.y, true);
   2130    APZC_LOGV("overridden delta is %s", ToString(delta).c_str());
   2131  }
   2132 
   2133  // If this is a line scroll, and this event was part of a scroll series, then
   2134  // it might need extra acceleration. See WheelHandlingHelper.cpp.
   2135  if (aEvent.mDeltaType == ScrollWheelInput::SCROLLDELTA_LINE &&
   2136      aEvent.mScrollSeriesNumber > 0) {
   2137    int32_t start = StaticPrefs::mousewheel_acceleration_start();
   2138    if (start >= 0 && aEvent.mScrollSeriesNumber >= uint32_t(start)) {
   2139      int32_t factor = StaticPrefs::mousewheel_acceleration_factor();
   2140      if (factor > 0) {
   2141        delta.x = ComputeAcceleratedWheelDelta(
   2142            delta.x, aEvent.mScrollSeriesNumber, factor);
   2143        delta.y = ComputeAcceleratedWheelDelta(
   2144            delta.y, aEvent.mScrollSeriesNumber, factor);
   2145      }
   2146    }
   2147  }
   2148 
   2149  // We shouldn't scroll more than one page at once except when the
   2150  // user preference is large.
   2151  if (!AllowsScrollingMoreThanOnePage(aMultiplierX) &&
   2152      Abs(delta.x) > pageScrollSize.width) {
   2153    delta.x = (delta.x >= 0) ? pageScrollSize.width : -pageScrollSize.width;
   2154  }
   2155  if (!AllowsScrollingMoreThanOnePage(aMultiplierY) &&
   2156      Abs(delta.y) > pageScrollSize.height) {
   2157    delta.y = (delta.y >= 0) ? pageScrollSize.height : -pageScrollSize.height;
   2158  }
   2159 
   2160  return delta;
   2161 }
   2162 
   2163 nsEventStatus AsyncPanZoomController::OnKeyboard(const KeyboardInput& aEvent) {
   2164  // Mark that this APZC has async key scrolled
   2165  mTestHasAsyncKeyScrolled = true;
   2166 
   2167  // Calculate the destination for this keyboard scroll action
   2168  CSSPoint destination = GetKeyboardDestination(aEvent.mAction);
   2169  ScrollOrigin scrollOrigin =
   2170      SmoothScrollAnimation::GetScrollOriginForAction(aEvent.mAction.mType);
   2171  Maybe<CSSSnapDestination> snapDestination =
   2172      MaybeAdjustDestinationForScrollSnapping(
   2173          aEvent, destination,
   2174          GetScrollSnapFlagsForKeyboardAction(aEvent.mAction));
   2175  ScrollMode scrollMode = apz::GetScrollModeForOrigin(scrollOrigin);
   2176 
   2177  RecordScrollPayload(aEvent.mTimeStamp);
   2178  // If the scrolling is instant, then scroll immediately to the destination
   2179  if (scrollMode == ScrollMode::Instant) {
   2180    CancelAnimation();
   2181 
   2182    ParentLayerPoint startPoint, endPoint;
   2183 
   2184    {
   2185      RecursiveMutexAutoLock lock(mRecursiveMutex);
   2186 
   2187      // CallDispatchScroll interprets the start and end points as the start and
   2188      // end of a touch scroll so they need to be reversed.
   2189      startPoint = destination * Metrics().GetZoom();
   2190      endPoint = Metrics().GetVisualScrollOffset() * Metrics().GetZoom();
   2191    }
   2192 
   2193    ParentLayerPoint delta = endPoint - startPoint;
   2194 
   2195    ScreenPoint distance = ToScreenCoordinates(
   2196        ParentLayerPoint(fabs(delta.x), fabs(delta.y)), startPoint);
   2197 
   2198    OverscrollHandoffState handoffState(
   2199        *mInputQueue->GetCurrentKeyboardBlock()->GetOverscrollHandoffChain(),
   2200        distance, ScrollSource::Keyboard);
   2201 
   2202    CallDispatchScroll(startPoint, endPoint, handoffState);
   2203    ParentLayerPoint remainingDelta = endPoint - startPoint;
   2204    if (remainingDelta != delta) {
   2205      // If any scrolling happened, set a transforming state explicitly so that
   2206      // it will trigger a TransformEnd notification.
   2207      SetState(SMOOTH_SCROLL);
   2208    }
   2209 
   2210    if (snapDestination) {
   2211      {
   2212        RecursiveMutexAutoLock lock(mRecursiveMutex);
   2213        mLastSnapTargetIds = std::move(snapDestination->mTargetIds);
   2214      }
   2215    }
   2216    SetState(NOTHING);
   2217 
   2218    return nsEventStatus_eConsumeDoDefault;
   2219  }
   2220 
   2221  // The lock must be held across the entire update operation, so the
   2222  // compositor doesn't end the animation before we get a chance to
   2223  // update it.
   2224  RecursiveMutexAutoLock lock(mRecursiveMutex);
   2225 
   2226  if (snapDestination) {
   2227    // If we're scroll snapping, use a smooth scroll animation to get
   2228    // the desired physics. Note that SmoothMsdScrollTo() will re-use an
   2229    // existing smooth scroll animation if there is one.
   2230    APZC_LOG("%p keyboard scrolling to snap point %s\n", this,
   2231             ToString(destination).c_str());
   2232    SmoothScrollTo(std::move(*snapDestination), ScrollTriggeredByScript::No,
   2233                   ScrollAnimationKind::SmoothMsd, ViewportType::Visual,
   2234                   ScrollOrigin::NotSpecified, GetFrameTime().Time());
   2235    return nsEventStatus_eConsumeDoDefault;
   2236  }
   2237 
   2238  // Use a keyboard scroll animation to scroll, reusing an existing one if it
   2239  // exists
   2240  if (!InScrollAnimation(ScrollAnimationKind::Keyboard)) {
   2241    CancelAnimation();
   2242 
   2243    // Keyboard input that does not change the scroll position should not
   2244    // cause a TransformBegin state change, in order to avoid firing a
   2245    // scrollend event when no scrolling occurred.
   2246    if (!CanScroll(ConvertDestinationToDelta(destination))) {
   2247      return nsEventStatus_eConsumeDoDefault;
   2248    }
   2249    SetState(SMOOTH_SCROLL);
   2250 
   2251    StartAnimation(
   2252        SmoothScrollAnimation::CreateForKeyboard(*this, scrollOrigin));
   2253  }
   2254 
   2255  // Convert velocity from ParentLayerPoints/ms to ParentLayerPoints/s and then
   2256  // to appunits/second.
   2257  nsPoint velocity;
   2258  if (Metrics().GetZoom() != CSSToParentLayerScale(0)) {
   2259    velocity =
   2260        CSSPoint::ToAppUnits(ParentLayerPoint(mX.GetVelocity() * 1000.0f,
   2261                                              mY.GetVelocity() * 1000.0f) /
   2262                             Metrics().GetZoom());
   2263  }
   2264 
   2265  SmoothScrollAnimation* animation = mAnimation->AsSmoothScrollAnimation();
   2266  MOZ_ASSERT(animation);
   2267 
   2268  animation->UpdateDestination(GetFrameTime().Time(),
   2269                               CSSPixel::ToAppUnits(destination),
   2270                               nsSize(velocity.x, velocity.y));
   2271 
   2272  return nsEventStatus_eConsumeDoDefault;
   2273 }
   2274 
   2275 CSSPoint AsyncPanZoomController::GetKeyboardDestination(
   2276    const KeyboardScrollAction& aAction) const {
   2277  CSSSize lineScrollSize;
   2278  CSSSize pageScrollSize;
   2279  CSSPoint scrollOffset;
   2280  CSSRect scrollRect;
   2281  ParentLayerRect compositionBounds;
   2282 
   2283  {
   2284    // Grab the lock to access the frame metrics.
   2285    RecursiveMutexAutoLock lock(mRecursiveMutex);
   2286 
   2287    lineScrollSize = mScrollMetadata.GetLineScrollAmount() /
   2288                     Metrics().GetDevPixelsPerCSSPixel();
   2289    pageScrollSize = mScrollMetadata.GetPageScrollAmount() /
   2290                     Metrics().GetDevPixelsPerCSSPixel();
   2291 
   2292    scrollOffset = GetCurrentAnimationDestination(lock).valueOr(
   2293        Metrics().GetVisualScrollOffset());
   2294 
   2295    scrollRect = Metrics().GetScrollableRect();
   2296    compositionBounds = Metrics().GetCompositionBounds();
   2297  }
   2298 
   2299  // Calculate the scroll destination based off of the scroll type and direction
   2300  CSSPoint scrollDestination = scrollOffset;
   2301 
   2302  switch (aAction.mType) {
   2303    case KeyboardScrollAction::eScrollCharacter: {
   2304      int32_t scrollDistance =
   2305          StaticPrefs::toolkit_scrollbox_horizontalScrollDistance();
   2306 
   2307      if (aAction.mForward) {
   2308        scrollDestination.x += scrollDistance * lineScrollSize.width;
   2309      } else {
   2310        scrollDestination.x -= scrollDistance * lineScrollSize.width;
   2311      }
   2312      break;
   2313    }
   2314    case KeyboardScrollAction::eScrollLine: {
   2315      int32_t scrollDistance =
   2316          StaticPrefs::toolkit_scrollbox_verticalScrollDistance();
   2317      if (scrollDistance * lineScrollSize.height <=
   2318          compositionBounds.Height()) {
   2319        if (aAction.mForward) {
   2320          scrollDestination.y += scrollDistance * lineScrollSize.height;
   2321        } else {
   2322          scrollDestination.y -= scrollDistance * lineScrollSize.height;
   2323        }
   2324        break;
   2325      }
   2326      [[fallthrough]];
   2327    }
   2328    case KeyboardScrollAction::eScrollPage: {
   2329      if (aAction.mForward) {
   2330        scrollDestination.y += pageScrollSize.height;
   2331      } else {
   2332        scrollDestination.y -= pageScrollSize.height;
   2333      }
   2334      break;
   2335    }
   2336    case KeyboardScrollAction::eScrollComplete: {
   2337      if (aAction.mForward) {
   2338        scrollDestination.y = scrollRect.YMost();
   2339      } else {
   2340        scrollDestination.y = scrollRect.Y();
   2341      }
   2342      break;
   2343    }
   2344  }
   2345 
   2346  return scrollDestination;
   2347 }
   2348 
   2349 ScrollSnapFlags AsyncPanZoomController::GetScrollSnapFlagsForKeyboardAction(
   2350    const KeyboardScrollAction& aAction) const {
   2351  switch (aAction.mType) {
   2352    case KeyboardScrollAction::eScrollCharacter:
   2353    case KeyboardScrollAction::eScrollLine:
   2354      return ScrollSnapFlags::IntendedDirection;
   2355    case KeyboardScrollAction::eScrollPage:
   2356      return ScrollSnapFlags::IntendedDirection |
   2357             ScrollSnapFlags::IntendedEndPosition;
   2358    case KeyboardScrollAction::eScrollComplete:
   2359      return ScrollSnapFlags::IntendedEndPosition;
   2360  }
   2361  return ScrollSnapFlags::Disabled;
   2362 }
   2363 
   2364 ParentLayerPoint AsyncPanZoomController::GetDeltaForEvent(
   2365    const InputData& aEvent) const {
   2366  ParentLayerPoint delta;
   2367  if (aEvent.mInputType == SCROLLWHEEL_INPUT) {
   2368    delta = GetScrollWheelDelta(aEvent.AsScrollWheelInput());
   2369  } else if (aEvent.mInputType == PANGESTURE_INPUT) {
   2370    const PanGestureInput& panInput = aEvent.AsPanGestureInput();
   2371    delta = ToParentLayerCoordinates(panInput.UserMultipliedPanDisplacement(),
   2372                                     panInput.mPanStartPoint);
   2373  }
   2374  return delta;
   2375 }
   2376 
   2377 CSSRect AsyncPanZoomController::GetCurrentScrollRangeInCssPixels() const {
   2378  RecursiveMutexAutoLock lock(mRecursiveMutex);
   2379  return Metrics().CalculateScrollRange();
   2380 }
   2381 
   2382 bool AsyncPanZoomController::AllowOneTouchPinch() const {
   2383  return StaticPrefs::apz_one_touch_pinch_enabled() &&
   2384         ZoomConstraintsAllowZoom();
   2385 }
   2386 
   2387 // Return whether or not the underlying layer can be scrolled on either axis.
   2388 bool AsyncPanZoomController::CanScroll(const InputData& aEvent) const {
   2389  ParentLayerPoint delta = GetDeltaForEvent(aEvent);
   2390  APZC_LOGV_DETAIL("CanScroll: event delta is %s", this,
   2391                   ToString(delta).c_str());
   2392  if (!delta.x && !delta.y) {
   2393    return false;
   2394  }
   2395 
   2396  if (SCROLLWHEEL_INPUT == aEvent.mInputType) {
   2397    const ScrollWheelInput& scrollWheelInput = aEvent.AsScrollWheelInput();
   2398    // If it's a wheel scroll, we first check if it is an auto-dir scroll.
   2399    // 1. For an auto-dir scroll, check if it's delta should be adjusted, if it
   2400    //    is, then we can conclude it must be scrollable; otherwise, fall back
   2401    //    to checking if it is scrollable without adjusting its delta.
   2402    // 2. For a non-auto-dir scroll, simply check if it is scrollable without
   2403    //    adjusting its delta.
   2404    RecursiveMutexAutoLock lock(mRecursiveMutex);
   2405    if (scrollWheelInput.IsAutoDir(mScrollMetadata.ForceMousewheelAutodir())) {
   2406      auto deltaX = scrollWheelInput.mDeltaX;
   2407      auto deltaY = scrollWheelInput.mDeltaY;
   2408      bool isRTL =
   2409          IsContentOfHonouredTargetRightToLeft(scrollWheelInput.HonoursRoot(
   2410              mScrollMetadata.ForceMousewheelAutodirHonourRoot()));
   2411      APZAutoDirWheelDeltaAdjuster adjuster(deltaX, deltaY, mX, mY, isRTL);
   2412      if (adjuster.ShouldBeAdjusted()) {
   2413        // If we detect that the delta values should be adjusted for an auto-dir
   2414        // wheel scroll, then it is impossible to be an unscrollable scroll.
   2415        return true;
   2416      }
   2417    }
   2418    return CanScrollWithWheel(delta);
   2419  }
   2420  return CanScroll(delta);
   2421 }
   2422 
   2423 bool AsyncPanZoomController::BlocksPullToRefreshForOverflowHidden() const {
   2424  return IsRootContent() &&
   2425         GetScrollMetadata().GetOverflow().mOverflowY == StyleOverflow::Hidden;
   2426 }
   2427 
   2428 ScrollDirections AsyncPanZoomController::GetAllowedHandoffDirections(
   2429    HandoffConsumer aConsumer) const {
   2430  ScrollDirections result;
   2431  RecursiveMutexAutoLock lock(mRecursiveMutex);
   2432 
   2433  // In Fission there can be non-scrollable APZCs. It's unclear whether
   2434  // overscroll-behavior should be respected for these
   2435  // (see https://github.com/w3c/csswg-drafts/issues/6523) but
   2436  // we currently don't, to match existing practice.
   2437  const bool isScrollable = mX.CanScroll() || mY.CanScroll();
   2438  const bool isRoot = IsRootContent();
   2439  if ((!isScrollable && !isRoot) || mX.OverscrollBehaviorAllowsHandoff()) {
   2440    result += ScrollDirection::eHorizontal;
   2441  }
   2442  if ((!isScrollable && !isRoot) || mY.OverscrollBehaviorAllowsHandoff()) {
   2443    // Bug 1902313: Block pull-to-refresh on pages with overflow-y:hidden
   2444    // to match Chrome behaviour.
   2445    bool blockPullToRefreshForOverflowHidden =
   2446        aConsumer == HandoffConsumer::PullToRefresh &&
   2447        BlocksPullToRefreshForOverflowHidden();
   2448    if (!blockPullToRefreshForOverflowHidden) {
   2449      result += ScrollDirection::eVertical;
   2450    }
   2451  }
   2452  return result;
   2453 }
   2454 
   2455 bool AsyncPanZoomController::CanScroll(const ParentLayerPoint& aDelta) const {
   2456  RecursiveMutexAutoLock lock(mRecursiveMutex);
   2457  return mX.CanScroll(ParentLayerCoord(aDelta.x)) ||
   2458         mY.CanScroll(ParentLayerCoord(aDelta.y));
   2459 }
   2460 
   2461 bool AsyncPanZoomController::CanScrollWithWheel(
   2462    const ParentLayerPoint& aDelta) const {
   2463  RecursiveMutexAutoLock lock(mRecursiveMutex);
   2464 
   2465  // For more details about the concept of a disregarded direction, refer to the
   2466  // code in struct ScrollMetadata which defines mDisregardedDirection.
   2467  Maybe<ScrollDirection> disregardedDirection =
   2468      mScrollMetadata.GetDisregardedDirection();
   2469  if (mX.CanScroll(ParentLayerCoord(aDelta.x)) &&
   2470      disregardedDirection != Some(ScrollDirection::eHorizontal)) {
   2471    return true;
   2472  }
   2473  if (mY.CanScroll(ParentLayerCoord(aDelta.y)) &&
   2474      disregardedDirection != Some(ScrollDirection::eVertical)) {
   2475    return true;
   2476  }
   2477  APZC_LOGV_FM(Metrics(),
   2478               "cannot scroll with wheel (disregarded direction is %s)",
   2479               ToString(disregardedDirection).c_str());
   2480  return false;
   2481 }
   2482 
   2483 bool AsyncPanZoomController::CanScroll(ScrollDirection aDirection) const {
   2484  RecursiveMutexAutoLock lock(mRecursiveMutex);
   2485  switch (aDirection) {
   2486    case ScrollDirection::eHorizontal:
   2487      return mX.CanScroll();
   2488    case ScrollDirection::eVertical:
   2489      return mY.CanScroll();
   2490  }
   2491  MOZ_ASSERT_UNREACHABLE("Invalid value");
   2492  return false;
   2493 }
   2494 
   2495 bool AsyncPanZoomController::CanVerticalScrollWithDynamicToolbar() const {
   2496  MOZ_ASSERT(IsRootContent());
   2497 
   2498  RecursiveMutexAutoLock lock(mRecursiveMutex);
   2499  return mY.CanVerticalScrollWithDynamicToolbar();
   2500 }
   2501 
   2502 bool AsyncPanZoomController::CanOverscrollUpwards(
   2503    HandoffConsumer aConsumer) const {
   2504  RecursiveMutexAutoLock lock(mRecursiveMutex);
   2505 
   2506  return !(aConsumer == HandoffConsumer::PullToRefresh &&
   2507           BlocksPullToRefreshForOverflowHidden()) &&
   2508         !mY.CanScrollTo(eSideTop) && mY.OverscrollBehaviorAllowsHandoff();
   2509 }
   2510 
   2511 bool AsyncPanZoomController::CanScrollDownwards() const {
   2512  RecursiveMutexAutoLock lock(mRecursiveMutex);
   2513  return mY.CanScrollTo(eSideBottom);
   2514 }
   2515 
   2516 SideBits AsyncPanZoomController::ScrollableDirections() const {
   2517  SideBits result;
   2518  {  // scope lock to respect lock ordering with APZCTreeManager::mTreeLock
   2519    // which will be acquired in the `GetCompositorFixedLayerMargins` below.
   2520    RecursiveMutexAutoLock lock(mRecursiveMutex);
   2521    result = mX.ScrollableDirections() | mY.ScrollableDirections();
   2522  }
   2523 
   2524  if (IsRootContent()) {
   2525    if (APZCTreeManager* treeManagerLocal = GetApzcTreeManager()) {
   2526      ScreenMargin fixedLayerMargins =
   2527          treeManagerLocal->GetCompositorFixedLayerMargins();
   2528      {
   2529        RecursiveMutexAutoLock lock(mRecursiveMutex);
   2530        result |= mY.ScrollableDirectionsWithDynamicToolbar(fixedLayerMargins);
   2531      }
   2532    }
   2533  }
   2534 
   2535  return result;
   2536 }
   2537 
   2538 bool AsyncPanZoomController::IsContentOfHonouredTargetRightToLeft(
   2539    bool aHonoursRoot) const {
   2540  if (aHonoursRoot) {
   2541    return mScrollMetadata.IsAutoDirRootContentRTL();
   2542  }
   2543  RecursiveMutexAutoLock lock(mRecursiveMutex);
   2544  return Metrics().IsHorizontalContentRightToLeft();
   2545 }
   2546 
   2547 bool AsyncPanZoomController::AllowScrollHandoffInCurrentBlock() const {
   2548  bool result = mInputQueue->AllowScrollHandoff();
   2549  if (!StaticPrefs::apz_allow_immediate_handoff()) {
   2550    if (InputBlockState* currentBlock = GetCurrentInputBlock()) {
   2551      // Do not allow handoff beyond the first APZC to scroll.
   2552      if (currentBlock->GetScrolledApzc() == this) {
   2553        result = false;
   2554        APZC_LOG("%p dropping handoff; AllowImmediateHandoff=false\n", this);
   2555      }
   2556    }
   2557  }
   2558  return result;
   2559 }
   2560 
   2561 void AsyncPanZoomController::DoDelayedRequestContentRepaint() {
   2562  if (!IsDestroyed() && mPinchPaintTimerSet) {
   2563    RecursiveMutexAutoLock lock(mRecursiveMutex);
   2564    RequestContentRepaint();
   2565  }
   2566  mPinchPaintTimerSet = false;
   2567 }
   2568 
   2569 void AsyncPanZoomController::DoDelayedTransformEndNotification(
   2570    PanZoomState aOldState) {
   2571  if (!IsDestroyed() && IsDelayedTransformEndSet()) {
   2572    DispatchStateChangeNotification(aOldState, NOTHING);
   2573  }
   2574  SetDelayedTransformEnd(false);
   2575 }
   2576 
   2577 static void AdjustDeltaForAllowedScrollDirections(
   2578    ParentLayerPoint& aDelta,
   2579    const ScrollDirections& aAllowedScrollDirections) {
   2580  if (!aAllowedScrollDirections.contains(ScrollDirection::eHorizontal)) {
   2581    aDelta.x = 0;
   2582  }
   2583  if (!aAllowedScrollDirections.contains(ScrollDirection::eVertical)) {
   2584    aDelta.y = 0;
   2585  }
   2586 }
   2587 
   2588 nsEventStatus AsyncPanZoomController::OnScrollWheel(
   2589    const ScrollWheelInput& aEvent) {
   2590  // Get the scroll wheel's delta values in parent-layer pixels. But before
   2591  // getting the values, we need to check if it is an auto-dir scroll and if it
   2592  // should be adjusted, if both answers are yes, let's adjust X and Y values
   2593  // first, and then get the delta values in parent-layer pixels based on the
   2594  // adjusted values.
   2595  bool adjustedByAutoDir = false;
   2596  auto deltaX = aEvent.mDeltaX;
   2597  auto deltaY = aEvent.mDeltaY;
   2598  ParentLayerPoint delta;
   2599  {
   2600    RecursiveMutexAutoLock lock(mRecursiveMutex);
   2601    if (aEvent.IsAutoDir(mScrollMetadata.ForceMousewheelAutodir())) {
   2602      // It's an auto-dir scroll, so check if its delta should be adjusted, if
   2603      // so, adjust it.
   2604      bool isRTL = IsContentOfHonouredTargetRightToLeft(aEvent.HonoursRoot(
   2605          mScrollMetadata.ForceMousewheelAutodirHonourRoot()));
   2606      APZAutoDirWheelDeltaAdjuster adjuster(deltaX, deltaY, mX, mY, isRTL);
   2607      if (adjuster.ShouldBeAdjusted()) {
   2608        adjuster.Adjust();
   2609        adjustedByAutoDir = true;
   2610      }
   2611    }
   2612  }
   2613  // Ensure the calls to GetScrollWheelDelta are outside the mRecursiveMutex
   2614  // lock since these calls may acquire the APZ tree lock. Holding
   2615  // mRecursiveMutex while acquiring the APZ tree lock is lock ordering
   2616  // violation.
   2617  if (adjustedByAutoDir) {
   2618    // If the original delta values have been adjusted, we pass them to
   2619    // replace the original delta values in |aEvent| so that the delta values
   2620    // in parent-layer pixels are caculated based on the adjusted values, not
   2621    // the original ones.
   2622    // Pay special attention to the last two parameters. They are in a swaped
   2623    // order so that they still correspond to their delta after adjustment.
   2624    delta = GetScrollWheelDelta(aEvent, deltaX, deltaY,
   2625                                aEvent.mUserDeltaMultiplierY,
   2626                                aEvent.mUserDeltaMultiplierX);
   2627  } else {
   2628    // If the original delta values haven't been adjusted by auto-dir, just pass
   2629    // the |aEvent| and caculate the delta values in parent-layer pixels based
   2630    // on the original delta values from |aEvent|.
   2631    delta = GetScrollWheelDelta(aEvent);
   2632  }
   2633 
   2634  APZC_LOG("%p got a scroll-wheel with delta in parent-layer pixels: %s\n",
   2635           this, ToString(delta).c_str());
   2636 
   2637  if (adjustedByAutoDir) {
   2638    MOZ_ASSERT(delta.x || delta.y,
   2639               "Adjusted auto-dir delta values can never be all-zero.");
   2640    APZC_LOG("%p got a scroll-wheel with adjusted auto-dir delta values\n",
   2641             this);
   2642  } else if ((delta.x || delta.y) && !CanScrollWithWheel(delta)) {
   2643    // We can't scroll this apz anymore, so we simply drop the event.
   2644    if (mInputQueue->GetActiveWheelTransaction() &&
   2645        StaticPrefs::test_mousescroll()) {
   2646      if (RefPtr<GeckoContentController> controller =
   2647              GetGeckoContentController()) {
   2648        controller->NotifyMozMouseScrollEvent(GetScrollId(),
   2649                                              u"MozMouseScrollFailed"_ns);
   2650      }
   2651    }
   2652    return nsEventStatus_eConsumeNoDefault;
   2653  }
   2654 
   2655  MOZ_ASSERT(mInputQueue->GetCurrentWheelBlock());
   2656  AdjustDeltaForAllowedScrollDirections(
   2657      delta, mInputQueue->GetCurrentWheelBlock()->GetAllowedScrollDirections());
   2658 
   2659  if (delta.x == 0 && delta.y == 0) {
   2660    // Avoid spurious state changes and unnecessary work
   2661    return nsEventStatus_eIgnore;
   2662  }
   2663 
   2664  switch (aEvent.mScrollMode) {
   2665    case ScrollWheelInput::SCROLLMODE_INSTANT: {
   2666      // Wheel events from "clicky" mouse wheels trigger scroll snapping to the
   2667      // next snap point. Check for this, and adjust the delta to take into
   2668      // account the snap point.
   2669      CSSPoint startPosition;
   2670      {
   2671        RecursiveMutexAutoLock lock(mRecursiveMutex);
   2672        startPosition = Metrics().GetVisualScrollOffset();
   2673      }
   2674      Maybe<CSSSnapDestination> snapDestination =
   2675          MaybeAdjustDeltaForScrollSnappingOnWheelInput(aEvent, delta,
   2676                                                        startPosition);
   2677 
   2678      ScreenPoint distance = ToScreenCoordinates(
   2679          ParentLayerPoint(fabs(delta.x), fabs(delta.y)), aEvent.mLocalOrigin);
   2680 
   2681      CancelAnimation();
   2682 
   2683      OverscrollHandoffState handoffState(
   2684          *mInputQueue->GetCurrentWheelBlock()->GetOverscrollHandoffChain(),
   2685          distance, ScrollSource::Wheel);
   2686      ParentLayerPoint startPoint = aEvent.mLocalOrigin;
   2687      ParentLayerPoint endPoint = aEvent.mLocalOrigin - delta;
   2688      RecordScrollPayload(aEvent.mTimeStamp);
   2689 
   2690      CallDispatchScroll(startPoint, endPoint, handoffState);
   2691      ParentLayerPoint remainingDelta = endPoint - startPoint;
   2692      if (remainingDelta != delta) {
   2693        // If any scrolling happened, set a transforming state explicitly so
   2694        // that it will trigger a TransformEnd notification.
   2695        SetState(SMOOTH_SCROLL);
   2696      }
   2697 
   2698      if (snapDestination) {
   2699        {
   2700          RecursiveMutexAutoLock lock(mRecursiveMutex);
   2701          mLastSnapTargetIds = std::move(snapDestination->mTargetIds);
   2702        }
   2703      }
   2704      SetState(NOTHING);
   2705 
   2706      // The calls above handle their own locking; moreover,
   2707      // ToScreenCoordinates() and CallDispatchScroll() can grab the tree lock.
   2708      RecursiveMutexAutoLock lock(mRecursiveMutex);
   2709      RequestContentRepaint();
   2710 
   2711      break;
   2712    }
   2713 
   2714    case ScrollWheelInput::SCROLLMODE_SMOOTH: {
   2715      // The lock must be held across the entire update operation, so the
   2716      // compositor doesn't end the animation before we get a chance to
   2717      // update it.
   2718      RecursiveMutexAutoLock lock(mRecursiveMutex);
   2719 
   2720      RecordScrollPayload(aEvent.mTimeStamp);
   2721      // Perform scroll snapping if appropriate.
   2722      // If we're already in a wheel scroll or smooth scroll animation,
   2723      // the delta is applied to its destination, not to the current
   2724      // scroll position. Take this into account when finding a snap point.
   2725      CSSPoint startPosition = GetCurrentAnimationDestination(lock).valueOr(
   2726          Metrics().GetVisualScrollOffset());
   2727 
   2728      if (Maybe<CSSSnapDestination> snapDestination =
   2729              MaybeAdjustDeltaForScrollSnappingOnWheelInput(aEvent, delta,
   2730                                                            startPosition)) {
   2731        // If we're scroll snapping, use a smooth scroll animation to get
   2732        // the desired physics. Note that SmoothMsdScrollTo() will re-use an
   2733        // existing smooth scroll animation if there is one.
   2734        APZC_LOG("%p wheel scrolling to snap point %s\n", this,
   2735                 ToString(startPosition).c_str());
   2736        SmoothScrollTo(std::move(*snapDestination), ScrollTriggeredByScript::No,
   2737                       ScrollAnimationKind::SmoothMsd, ViewportType::Visual,
   2738                       ScrollOrigin::NotSpecified, GetFrameTime().Time());
   2739        break;
   2740      }
   2741 
   2742      // Otherwise, use a wheel scroll animation, also reusing one if possible.
   2743      if (!InScrollAnimation(ScrollAnimationKind::Wheel)) {
   2744        CancelAnimation();
   2745        SetState(SMOOTH_SCROLL);
   2746 
   2747        StartAnimation(
   2748            SmoothScrollAnimation::CreateForWheel(*this, aEvent.mDeltaType));
   2749      }
   2750      // Convert velocity from ParentLayerPoints/ms to ParentLayerPoints/s and
   2751      // then to appunits/second.
   2752 
   2753      nsPoint deltaInAppUnits;
   2754      nsPoint velocity;
   2755      if (Metrics().GetZoom() != CSSToParentLayerScale(0)) {
   2756        deltaInAppUnits = CSSPoint::ToAppUnits(delta / Metrics().GetZoom());
   2757        velocity =
   2758            CSSPoint::ToAppUnits(ParentLayerPoint(mX.GetVelocity() * 1000.0f,
   2759                                                  mY.GetVelocity() * 1000.0f) /
   2760                                 Metrics().GetZoom());
   2761      }
   2762 
   2763      SmoothScrollAnimation* animation = mAnimation->AsSmoothScrollAnimation();
   2764      animation->UpdateDelta(GetFrameTime().Time(), deltaInAppUnits,
   2765                             nsSize(velocity.x, velocity.y));
   2766      break;
   2767    }
   2768  }
   2769 
   2770  return nsEventStatus_eConsumeNoDefault;
   2771 }
   2772 
   2773 void AsyncPanZoomController::NotifyMozMouseScrollEvent(
   2774    const nsString& aString) const {
   2775  RefPtr<GeckoContentController> controller = GetGeckoContentController();
   2776  if (!controller) {
   2777    return;
   2778  }
   2779  controller->NotifyMozMouseScrollEvent(GetScrollId(), aString);
   2780 }
   2781 
   2782 nsEventStatus AsyncPanZoomController::OnPanMayBegin(
   2783    const PanGestureInput& aEvent) {
   2784  APZC_LOG_DETAIL("got a pan-maybegin in state %s\n", this,
   2785                  ToString(mState).c_str());
   2786 
   2787  StartTouch(aEvent.mLocalPanStartPoint, aEvent.mTimeStamp);
   2788  MOZ_ASSERT(GetCurrentPanGestureBlock());
   2789  GetCurrentPanGestureBlock()->GetOverscrollHandoffChain()->CancelAnimations(
   2790      ExcludeOverscroll | ExcludeAutoscroll);
   2791 
   2792  return nsEventStatus_eConsumeNoDefault;
   2793 }
   2794 
   2795 nsEventStatus AsyncPanZoomController::OnPanCancelled(
   2796    const PanGestureInput& aEvent) {
   2797  APZC_LOG_DETAIL("got a pan-cancelled in state %s\n", this,
   2798                  ToString(mState).c_str());
   2799 
   2800  mX.CancelGesture();
   2801  mY.CancelGesture();
   2802 
   2803  MOZ_ASSERT(GetCurrentPanGestureBlock());
   2804  GetCurrentPanGestureBlock()
   2805      ->GetOverscrollHandoffChain()
   2806      ->SnapBackOverscrolledApzc(this);
   2807 
   2808  return nsEventStatus_eConsumeNoDefault;
   2809 }
   2810 
   2811 nsEventStatus AsyncPanZoomController::OnPanBegin(
   2812    const PanGestureInput& aEvent) {
   2813  APZC_LOG_DETAIL("got a pan-begin in state %s\n", this,
   2814                  ToString(mState).c_str());
   2815 
   2816  MOZ_ASSERT(GetCurrentPanGestureBlock());
   2817  GetCurrentPanGestureBlock()->GetOverscrollHandoffChain()->CancelAnimations(
   2818      ExcludeOverscroll);
   2819 
   2820  StartTouch(aEvent.mLocalPanStartPoint, aEvent.mTimeStamp);
   2821 
   2822  if (!UsingStatefulAxisLock()) {
   2823    SetState(PANNING);
   2824  } else {
   2825    float dx = aEvent.mPanDisplacement.x, dy = aEvent.mPanDisplacement.y;
   2826 
   2827    if (dx != 0.0f || dy != 0.0f) {
   2828      double angle = atan2(dy, dx);  // range [-pi, pi]
   2829      angle = fabs(angle);           // range [0, pi]
   2830      HandlePanning(angle);
   2831    } else {
   2832      SetState(PANNING);
   2833    }
   2834  }
   2835 
   2836  // Call into OnPan in order to process any delta included in this event.
   2837  OnPan(aEvent, FingersOnTouchpad::Yes);
   2838 
   2839  return nsEventStatus_eConsumeNoDefault;
   2840 }
   2841 
   2842 std::tuple<ParentLayerPoint, ScreenPoint>
   2843 AsyncPanZoomController::GetDisplacementsForPanGesture(
   2844    const PanGestureInput& aEvent) {
   2845  // Note that there is a multiplier that applies onto the "physical" pan
   2846  // displacement (how much the user's fingers moved) that produces the
   2847  // "logical" pan displacement (how much the page should move). For some of the
   2848  // code below it makes more sense to use the physical displacement rather than
   2849  // the logical displacement, and vice-versa.
   2850  ScreenPoint physicalPanDisplacement = aEvent.mPanDisplacement;
   2851  ParentLayerPoint logicalPanDisplacement =
   2852      aEvent.UserMultipliedLocalPanDisplacement();
   2853  if (aEvent.mDeltaType == PanGestureInput::PANDELTA_PAGE) {
   2854    // Pan events with page units are used by Gtk, so this replicates Gtk:
   2855    // https://gitlab.gnome.org/GNOME/gtk/blob/c734c7e9188b56f56c3a504abee05fa40c5475ac/gtk/gtkrange.c#L3065-3073
   2856    CSSSize pageScrollSize;
   2857    CSSToParentLayerScale zoom;
   2858    {
   2859      // Grab the lock to access the frame metrics.
   2860      RecursiveMutexAutoLock lock(mRecursiveMutex);
   2861      pageScrollSize = mScrollMetadata.GetPageScrollAmount() /
   2862                       Metrics().GetDevPixelsPerCSSPixel();
   2863      zoom = Metrics().GetZoom();
   2864    }
   2865    // scrollUnit* is in units of "ParentLayer pixels per page proportion"...
   2866    auto scrollUnitWidth = std::min(std::pow(pageScrollSize.width, 2.0 / 3.0),
   2867                                    pageScrollSize.width / 2.0) *
   2868                           zoom.scale;
   2869    auto scrollUnitHeight = std::min(std::pow(pageScrollSize.height, 2.0 / 3.0),
   2870                                     pageScrollSize.height / 2.0) *
   2871                            zoom.scale;
   2872    // ... and pan displacements are in units of "page proportion count"
   2873    // here, so the products of them and scrollUnit* are in ParentLayer pixels
   2874    ParentLayerPoint physicalPanDisplacementPL(
   2875        physicalPanDisplacement.x * scrollUnitWidth,
   2876        physicalPanDisplacement.y * scrollUnitHeight);
   2877    physicalPanDisplacement = ToScreenCoordinates(physicalPanDisplacementPL,
   2878                                                  aEvent.mLocalPanStartPoint);
   2879    logicalPanDisplacement.x *= scrollUnitWidth;
   2880    logicalPanDisplacement.y *= scrollUnitHeight;
   2881 
   2882    // Accelerate (decelerate) any pans by raising it to a user configurable
   2883    // power (apz.touch_acceleration_factor_x, apz.touch_acceleration_factor_y)
   2884    //
   2885    // Confine input for pow() to greater than or equal to 0 to avoid domain
   2886    // errors with non-integer exponents
   2887    if (mX.GetVelocity() != 0) {
   2888      float absVelocity = std::abs(mX.GetVelocity());
   2889      logicalPanDisplacement.x *=
   2890          std::pow(absVelocity,
   2891                   StaticPrefs::apz_touch_acceleration_factor_x()) /
   2892          absVelocity;
   2893    }
   2894 
   2895    if (mY.GetVelocity() != 0) {
   2896      float absVelocity = std::abs(mY.GetVelocity());
   2897      logicalPanDisplacement.y *=
   2898          std::pow(absVelocity,
   2899                   StaticPrefs::apz_touch_acceleration_factor_y()) /
   2900          absVelocity;
   2901    }
   2902  }
   2903 
   2904  MOZ_ASSERT(GetCurrentPanGestureBlock());
   2905  AdjustDeltaForAllowedScrollDirections(
   2906      logicalPanDisplacement,
   2907      GetCurrentPanGestureBlock()->GetAllowedScrollDirections());
   2908 
   2909  if (GetAxisLockMode() == AxisLockMode::DOMINANT_AXIS) {
   2910    // Given a pan gesture and both directions have a delta, implement
   2911    // dominant axis scrolling and only use the delta for the larger
   2912    // axis.
   2913    if (logicalPanDisplacement.y != 0 && logicalPanDisplacement.x != 0) {
   2914      if (fabs(logicalPanDisplacement.y) >= fabs(logicalPanDisplacement.x)) {
   2915        logicalPanDisplacement.x = 0;
   2916        physicalPanDisplacement.x = 0;
   2917      } else {
   2918        logicalPanDisplacement.y = 0;
   2919        physicalPanDisplacement.y = 0;
   2920      }
   2921    }
   2922  }
   2923 
   2924  return {logicalPanDisplacement, physicalPanDisplacement};
   2925 }
   2926 
   2927 CSSPoint AsyncPanZoomController::ToCSSPixels(ParentLayerPoint value) const {
   2928  if (this->Metrics().GetZoom() == CSSToParentLayerScale(0)) {
   2929    return CSSPoint{0, 0};
   2930  }
   2931  return (value / this->Metrics().GetZoom());
   2932 }
   2933 
   2934 CSSCoord AsyncPanZoomController::ToCSSPixels(ParentLayerCoord value) const {
   2935  if (this->Metrics().GetZoom() == CSSToParentLayerScale(0)) {
   2936    return CSSCoord{0};
   2937  }
   2938  return (value / this->Metrics().GetZoom());
   2939 }
   2940 
   2941 nsEventStatus AsyncPanZoomController::OnPan(
   2942    const PanGestureInput& aEvent, FingersOnTouchpad aFingersOnTouchpad) {
   2943  APZC_LOG_DETAIL("got a pan-pan in state %s\n", this,
   2944                  ToString(GetState()).c_str());
   2945 
   2946  if (InScrollAnimation(ScrollAnimationKind::SmoothMsd)) {
   2947    if (aFingersOnTouchpad == FingersOnTouchpad::No) {
   2948      // When a SmoothMsd scroll is being processed on a frame, mouse
   2949      // wheel and trackpad momentum scroll position updates will not cancel the
   2950      // SmoothMsd scroll animations, enabling scripts that depend on
   2951      // them to be responsive without forcing the user to wait for the momentum
   2952      // scrolling to completely stop.
   2953      return nsEventStatus_eConsumeNoDefault;
   2954    }
   2955 
   2956    // SMOOTHMSD_SCROLL scrolls are cancelled by pan gestures.
   2957    CancelAnimation();
   2958  }
   2959 
   2960  if (GetState() == NOTHING) {
   2961    // This event block was interrupted by something else. If the user's fingers
   2962    // are still on on the touchpad we want to resume scrolling, otherwise we
   2963    // ignore the rest of the scroll gesture.
   2964    if (aFingersOnTouchpad == FingersOnTouchpad::No) {
   2965      return nsEventStatus_eConsumeNoDefault;
   2966    }
   2967    // Resume / restart the pan.
   2968    // PanBegin will call back into this function with mState == PANNING.
   2969    return OnPanBegin(aEvent);
   2970  }
   2971 
   2972  auto [logicalPanDisplacement, physicalPanDisplacement] =
   2973      GetDisplacementsForPanGesture(aEvent);
   2974 
   2975  {
   2976    // Grab the lock to protect the animation from being canceled on the updater
   2977    // thread.
   2978    RecursiveMutexAutoLock lock(mRecursiveMutex);
   2979    MOZ_ASSERT_IF(GetState() == OVERSCROLL_ANIMATION, mAnimation);
   2980 
   2981    if (GetState() == OVERSCROLL_ANIMATION && mAnimation &&
   2982        aFingersOnTouchpad == FingersOnTouchpad::No) {
   2983      // If there is an on-going overscroll animation, we tell the animation
   2984      // whether the displacements should be handled by the animation or not.
   2985      MOZ_ASSERT(mAnimation->AsOverscrollAnimation());
   2986      if (RefPtr<OverscrollAnimation> overscrollAnimation =
   2987              mAnimation->AsOverscrollAnimation()) {
   2988        overscrollAnimation->HandlePanMomentum(logicalPanDisplacement);
   2989        // And then as a result of the above call, if the animation is currently
   2990        // affecting on the axis, drop the displacement value on the axis so
   2991        // that we stop further oversrolling on the axis.
   2992        if (overscrollAnimation->IsManagingXAxis()) {
   2993          logicalPanDisplacement.x = 0;
   2994          physicalPanDisplacement.x = 0;
   2995        }
   2996        if (overscrollAnimation->IsManagingYAxis()) {
   2997          logicalPanDisplacement.y = 0;
   2998          physicalPanDisplacement.y = 0;
   2999        }
   3000      }
   3001    }
   3002  }
   3003 
   3004  HandlePanningUpdate(physicalPanDisplacement);
   3005 
   3006  MOZ_ASSERT(GetCurrentPanGestureBlock());
   3007  ScreenPoint panDistance(fabs(physicalPanDisplacement.x),
   3008                          fabs(physicalPanDisplacement.y));
   3009  OverscrollHandoffState handoffState(
   3010      *GetCurrentPanGestureBlock()->GetOverscrollHandoffChain(), panDistance,
   3011      ScrollSource::Touchpad);
   3012 
   3013  // Create fake "touch" positions that will result in the desired scroll
   3014  // motion. Note that the pan displacement describes the change in scroll
   3015  // position: positive displacement values mean that the scroll position
   3016  // increases. However, an increase in scroll position means that the scrolled
   3017  // contents are moved to the left / upwards. Since our simulated "touches"
   3018  // determine the motion of the scrolled contents, not of the scroll position,
   3019  // they need to move in the opposite direction of the pan displacement.
   3020  ParentLayerPoint startPoint = aEvent.mLocalPanStartPoint;
   3021  ParentLayerPoint endPoint =
   3022      aEvent.mLocalPanStartPoint - logicalPanDisplacement;
   3023  if (logicalPanDisplacement != ParentLayerPoint()) {
   3024    // Don't expect a composite to be triggered if the displacement is zero
   3025    RecordScrollPayload(aEvent.mTimeStamp);
   3026  }
   3027 
   3028  const ParentLayerPoint velocity = GetVelocityVector();
   3029  bool consumed = CallDispatchScroll(startPoint, endPoint, handoffState);
   3030 
   3031  const ParentLayerPoint visualDisplacement = ToParentLayerCoordinates(
   3032      handoffState.mTotalMovement, aEvent.mPanStartPoint);
   3033  // We need to update the axis velocity in order to get a useful display port
   3034  // size and position. We need to do so even if this is a momentum pan (i.e.
   3035  // aFingersOnTouchpad == No); in that case the "with touch" part is not
   3036  // really appropriate, so we may want to rethink this at some point.
   3037  // Note that we have to make all simulated positions relative to
   3038  // Axis::GetPos(), because the current position is an invented position, and
   3039  // because resetting the position to the mouse position (e.g.
   3040  // aEvent.mLocalStartPoint) would mess up velocity calculation. (This is
   3041  // the only caller of UpdateWithTouchAtDevicePoint() for pan events, so
   3042  // there is no risk of other calls resetting the position.)
   3043  // Also note that if there is an on-going overscroll animation in the axis,
   3044  // we shouldn't call UpdateWithTouchAtDevicePoint because the call changes
   3045  // the velocity which should be managed by the overscroll animation.
   3046  // Finally, note that we do this *after* CallDispatchScroll(), so that the
   3047  // position we use reflects the actual amount of movement that occurred
   3048  // (in particular, if we're in overscroll, if reflects the amount of movement
   3049  // *after* applying resistance). This is important because we want the axis
   3050  // velocity to track the visual movement speed of the page.
   3051  if (visualDisplacement.x != 0) {
   3052    mX.UpdateWithTouchAtDevicePoint(mX.GetPos() - visualDisplacement.x,
   3053                                    aEvent.mTimeStamp);
   3054  }
   3055  if (visualDisplacement.y != 0) {
   3056    mY.UpdateWithTouchAtDevicePoint(mY.GetPos() - visualDisplacement.y,
   3057                                    aEvent.mTimeStamp);
   3058  }
   3059 
   3060  if (aFingersOnTouchpad == FingersOnTouchpad::No) {
   3061    if (IsOverscrolled() && GetState() != OVERSCROLL_ANIMATION) {
   3062      StartOverscrollAnimation(velocity, GetOverscrollSideBits());
   3063    } else if (!consumed) {
   3064      // If there is unconsumed scroll and we're in the momentum part of the
   3065      // pan gesture, terminate the momentum scroll. This prevents momentum
   3066      // scroll events from unexpectedly causing scrolling later if somehow
   3067      // the APZC becomes scrollable again in this direction (e.g. if the user
   3068      // uses some other input method to scroll in the opposite direction).
   3069      SetState(NOTHING);
   3070    }
   3071  }
   3072 
   3073  return nsEventStatus_eConsumeNoDefault;
   3074 }
   3075 
   3076 nsEventStatus AsyncPanZoomController::OnPanEnd(const PanGestureInput& aEvent) {
   3077  APZC_LOG_DETAIL("got a pan-end in state %s\n", this,
   3078                  ToString(mState).c_str());
   3079 
   3080  // This can happen if the OS sends a second pan-end event after the first one
   3081  // has already started an overscroll animation or entered a fling state.
   3082  // This has been observed on some Wayland versions.
   3083  PanZoomState currentState = GetState();
   3084  if (currentState == OVERSCROLL_ANIMATION || currentState == NOTHING ||
   3085      currentState == FLING) {
   3086    return nsEventStatus_eIgnore;
   3087  }
   3088 
   3089  if (aEvent.mPanDisplacement != ScreenPoint{}) {
   3090    // Call into OnPan in order to process the delta included in this event.
   3091    OnPan(aEvent, FingersOnTouchpad::Yes);
   3092  }
   3093 
   3094  // Do not unlock the axis lock at the end of a pan gesture. The axis lock
   3095  // should extend into the momentum scroll.
   3096  EndTouch(aEvent.mTimeStamp, Axis::ClearAxisLock::No);
   3097 
   3098  // Use HandleEndOfPan for fling on platforms that don't
   3099  // emit momentum events (Gtk).
   3100  if (aEvent.mSimulateMomentum) {
   3101    return HandleEndOfPan();
   3102  }
   3103 
   3104  MOZ_ASSERT(GetCurrentPanGestureBlock());
   3105  RefPtr<const OverscrollHandoffChain> overscrollHandoffChain =
   3106      GetCurrentPanGestureBlock()->GetOverscrollHandoffChain();
   3107 
   3108  // Call SnapBackOverscrolledApzcForMomentum regardless whether this APZC is
   3109  // overscrolled or not since overscroll animations for ancestor APZCs in this
   3110  // overscroll handoff chain might have been cancelled by the current pan
   3111  // gesture block.
   3112  overscrollHandoffChain->SnapBackOverscrolledApzcForMomentum(
   3113      this, GetVelocityVector());
   3114  // If this APZC is overscrolled, the above SnapBackOverscrolledApzcForMomentum
   3115  // triggers an overscroll animation. When we're finished with the overscroll
   3116  // animation, the state will be reset and a TransformEnd will be sent to the
   3117  // main thread.
   3118  currentState = GetState();
   3119  if (currentState != OVERSCROLL_ANIMATION) {
   3120    // Do not send a state change notification to the content controller here.
   3121    // Instead queue a delayed task to dispatch the notification if no
   3122    // momentum pan or scroll snap follows the pan-end.
   3123    RefPtr<GeckoContentController> controller = GetGeckoContentController();
   3124    if (controller) {
   3125      SetDelayedTransformEnd(true);
   3126      controller->PostDelayedTask(
   3127          NewRunnableMethod<PanZoomState>(
   3128              "layers::AsyncPanZoomController::"
   3129              "DoDelayedTransformEndNotification",
   3130              this, &AsyncPanZoomController::DoDelayedTransformEndNotification,
   3131              currentState),
   3132          StaticPrefs::apz_scrollend_event_content_delay_ms());
   3133      SetStateNoContentControllerDispatch(NOTHING);
   3134    } else {
   3135      SetState(NOTHING);
   3136    }
   3137  }
   3138 
   3139  // Drop any velocity on axes where we don't have room to scroll anyways
   3140  // (in this APZC, or an APZC further in the handoff chain).
   3141  // This ensures that we don't enlarge the display port unnecessarily.
   3142  {
   3143    RecursiveMutexAutoLock lock(mRecursiveMutex);
   3144    if (!overscrollHandoffChain->CanScrollInDirection(
   3145            this, ScrollDirection::eHorizontal)) {
   3146      mX.SetVelocity(0);
   3147    }
   3148    if (!overscrollHandoffChain->CanScrollInDirection(
   3149            this, ScrollDirection::eVertical)) {
   3150      mY.SetVelocity(0);
   3151    }
   3152  }
   3153 
   3154  RequestContentRepaint();
   3155  ScrollSnapToDestination();
   3156 
   3157  return nsEventStatus_eConsumeNoDefault;
   3158 }
   3159 
   3160 nsEventStatus AsyncPanZoomController::OnPanMomentumStart(
   3161    const PanGestureInput& aEvent) {
   3162  APZC_LOG_DETAIL("got a pan-momentumstart in state %s\n", this,
   3163                  ToString(mState).c_str());
   3164 
   3165  if (InScrollAnimation(ScrollAnimationKind::SmoothMsd) ||
   3166      mState == OVERSCROLL_ANIMATION) {
   3167    return nsEventStatus_eConsumeNoDefault;
   3168  }
   3169 
   3170  if (IsDelayedTransformEndSet()) {
   3171    // Do not send another TransformBegin notification if we have not
   3172    // delivered a corresponding TransformEnd. Also ensure that any
   3173    // queued transform-end due to a pan-end is not sent. Instead rely
   3174    // on the transform-end sent due to the momentum pan.
   3175    SetDelayedTransformEnd(false);
   3176    SetStateNoContentControllerDispatch(PAN_MOMENTUM);
   3177  } else {
   3178    SetState(PAN_MOMENTUM);
   3179  }
   3180 
   3181  // Call into OnPan in order to process any delta included in this event.
   3182  OnPan(aEvent, FingersOnTouchpad::No);
   3183 
   3184  return nsEventStatus_eConsumeNoDefault;
   3185 }
   3186 
   3187 nsEventStatus AsyncPanZoomController::OnPanMomentumEnd(
   3188    const PanGestureInput& aEvent) {
   3189  APZC_LOG_DETAIL("got a pan-momentumend in state %s\n", this,
   3190                  ToString(mState).c_str());
   3191 
   3192  if (mState == OVERSCROLL_ANIMATION) {
   3193    return nsEventStatus_eConsumeNoDefault;
   3194  }
   3195 
   3196  // Call into OnPan in order to process any delta included in this event.
   3197  OnPan(aEvent, FingersOnTouchpad::No);
   3198 
   3199  // We need to reset the velocity to zero. We don't really have a "touch"
   3200  // here because the touch has already ended long before the momentum
   3201  // animation started, but I guess it doesn't really matter for now.
   3202  mX.CancelGesture();
   3203  mY.CancelGesture();
   3204  SetState(NOTHING);
   3205 
   3206  RequestContentRepaint();
   3207 
   3208  return nsEventStatus_eConsumeNoDefault;
   3209 }
   3210 
   3211 nsEventStatus AsyncPanZoomController::OnPanInterrupted(
   3212    const PanGestureInput& aEvent) {
   3213  APZC_LOG_DETAIL("got a pan-interrupted in state %s\n", this,
   3214                  ToString(mState).c_str());
   3215 
   3216  CancelAnimation();
   3217 
   3218  return nsEventStatus_eIgnore;
   3219 }
   3220 
   3221 nsEventStatus AsyncPanZoomController::OnLongPress(
   3222    const TapGestureInput& aEvent) {
   3223  APZC_LOG_DETAIL("got a long-press in state %s\n", this,
   3224                  ToString(mState).c_str());
   3225  RefPtr<GeckoContentController> controller = GetGeckoContentController();
   3226  if (controller) {
   3227    if (Maybe<LayoutDevicePoint> geckoScreenPoint =
   3228            ConvertToGecko(aEvent.mPoint)) {
   3229      TouchBlockState* touch = GetCurrentTouchBlock();
   3230      if (!touch) {
   3231        APZC_LOG(
   3232            "%p dropping long-press because some non-touch block interrupted "
   3233            "it\n",
   3234            this);
   3235        return nsEventStatus_eIgnore;
   3236      }
   3237      if (touch->IsDuringFastFling()) {
   3238        APZC_LOG("%p dropping long-press because of fast fling\n", this);
   3239        return nsEventStatus_eIgnore;
   3240      }
   3241      uint64_t blockId = GetInputQueue()->InjectNewTouchBlock(this);
   3242      controller->HandleTap(TapType::eLongTap, *geckoScreenPoint,
   3243                            aEvent.modifiers, GetGuid(), blockId, Nothing());
   3244      return nsEventStatus_eConsumeNoDefault;
   3245    }
   3246  }
   3247  return nsEventStatus_eIgnore;
   3248 }
   3249 
   3250 nsEventStatus AsyncPanZoomController::OnLongPressUp(
   3251    const TapGestureInput& aEvent) {
   3252  APZC_LOG_DETAIL("got a long-tap-up in state %s\n", this,
   3253                  ToString(mState).c_str());
   3254  return GenerateSingleTap(TapType::eLongTapUp, aEvent.mPoint,
   3255                           aEvent.modifiers);
   3256 }
   3257 
   3258 nsEventStatus AsyncPanZoomController::GenerateSingleTap(
   3259    TapType aType, const ScreenIntPoint& aPoint,
   3260    mozilla::Modifiers aModifiers) {
   3261  RefPtr<GeckoContentController> controller = GetGeckoContentController();
   3262  if (controller) {
   3263    if (Maybe<LayoutDevicePoint> geckoScreenPoint = ConvertToGecko(aPoint)) {
   3264      TouchBlockState* touch = GetCurrentTouchBlock();
   3265      // |touch| may be null in the case where this function is
   3266      // invoked by GestureEventListener on a timeout. In that case we already
   3267      // verified that the single tap is allowed so we let it through.
   3268      // XXX there is a bug here that in such a case the touch block that
   3269      // generated this tap will not get its mSingleTapOccurred flag set.
   3270      // See https://bugzilla.mozilla.org/show_bug.cgi?id=1256344#c6
   3271      if (touch) {
   3272        if (touch->IsDuringFastFling()) {
   3273          APZC_LOG(
   3274              "%p dropping single-tap because it was during a fast-fling\n",
   3275              this);
   3276          return nsEventStatus_eIgnore;
   3277        }
   3278 
   3279        // The below `single-tap-occurred` flag is only used to tell whether the
   3280        // touch block caused a `click` event or not, thus for long-tap events,
   3281        // it's not necessary.
   3282        if (aType != TapType::eLongTapUp) {
   3283          touch->SetSingleTapState(apz::SingleTapState::WasClick);
   3284        }
   3285      }
   3286      // Because this may be being running as part of
   3287      // APZCTreeManager::ReceiveInputEvent, calling controller->HandleTap
   3288      // directly might mean that content receives the single tap message before
   3289      // the corresponding touch-up. To avoid that we schedule the singletap
   3290      // message to run on the next spin of the event loop. See bug 965381 for
   3291      // the issue this was causing.
   3292      APZC_LOG("posting runnable for HandleTap from GenerateSingleTap");
   3293      RefPtr<Runnable> runnable =
   3294          NewRunnableMethod<TapType, LayoutDevicePoint, mozilla::Modifiers,
   3295                            ScrollableLayerGuid, uint64_t,
   3296                            Maybe<DoubleTapToZoomMetrics>>(
   3297              "layers::GeckoContentController::HandleTap", controller,
   3298              &GeckoContentController::HandleTap, aType, *geckoScreenPoint,
   3299              aModifiers, GetGuid(), touch ? touch->GetBlockId() : 0,
   3300              Nothing());
   3301 
   3302      controller->PostDelayedTask(runnable.forget(), 0);
   3303      return nsEventStatus_eConsumeNoDefault;
   3304    }
   3305  }
   3306  return nsEventStatus_eIgnore;
   3307 }
   3308 
   3309 void AsyncPanZoomController::OnTouchEndOrCancel() {
   3310  mTouchScrollEventBuffer.clear();
   3311  if (RefPtr<GeckoContentController> controller = GetGeckoContentController()) {
   3312    MOZ_ASSERT(GetCurrentTouchBlock());
   3313    controller->NotifyAPZStateChange(
   3314        GetGuid(), APZStateChange::eEndTouch,
   3315        static_cast<int>(GetCurrentTouchBlock()->SingleTapState()),
   3316        Some(GetCurrentTouchBlock()->GetBlockId()));
   3317  }
   3318 }
   3319 
   3320 nsEventStatus AsyncPanZoomController::OnSingleTapUp(
   3321    const TapGestureInput& aEvent) {
   3322  APZC_LOG_DETAIL("got a single-tap-up in state %s\n", this,
   3323                  ToString(mState).c_str());
   3324  // If mZoomConstraints.mAllowDoubleTapZoom is true we wait for a call to
   3325  // OnSingleTapConfirmed before sending event to content
   3326  MOZ_ASSERT(GetCurrentTouchBlock());
   3327  if (!(ZoomConstraintsAllowDoubleTapZoom() &&
   3328        GetCurrentTouchBlock()->TouchActionAllowsDoubleTapZoom())) {
   3329    return GenerateSingleTap(TapType::eSingleTap, aEvent.mPoint,
   3330                             aEvent.modifiers);
   3331  }
   3332 
   3333  // Ignore the event if it does not have valid local coordinates.
   3334  // GenerateSingleTap will not send a tap in this case.
   3335  if (!ConvertToGecko(aEvent.mPoint)) {
   3336    return nsEventStatus_eIgnore;
   3337  }
   3338 
   3339  // Here we need to wait for the call to OnSingleTapConfirmed, we need to tell
   3340  // it to ElementStateManager so that we can do element activation once
   3341  // ElementStateManager got a single tap event later.
   3342  if (TouchBlockState* touch = GetCurrentTouchBlock()) {
   3343    if (!touch->IsDuringFastFling()) {
   3344      touch->SetSingleTapState(apz::SingleTapState::NotYetDetermined);
   3345    }
   3346  }
   3347  return nsEventStatus_eIgnore;
   3348 }
   3349 
   3350 nsEventStatus AsyncPanZoomController::OnSingleTapConfirmed(
   3351    const TapGestureInput& aEvent) {
   3352  APZC_LOG_DETAIL("got a single-tap-confirmed in state %s\n", this,
   3353                  ToString(mState).c_str());
   3354  return GenerateSingleTap(TapType::eSingleTap, aEvent.mPoint,
   3355                           aEvent.modifiers);
   3356 }
   3357 
   3358 nsEventStatus AsyncPanZoomController::OnDoubleTap(
   3359    const TapGestureInput& aEvent) {
   3360  APZC_LOG_DETAIL("got a double-tap in state %s\n", this,
   3361                  ToString(mState).c_str());
   3362 
   3363  MOZ_ASSERT(IsRootForLayersId(),
   3364             "This function should be called for the root content APZC or "
   3365             "OOPIF root APZC");
   3366 
   3367  CSSToCSSMatrix4x4 transformToRootContentApzc;
   3368  RefPtr<AsyncPanZoomController> rootContentApzc;
   3369  if (IsRootContent()) {
   3370    rootContentApzc = RefPtr{this};
   3371  } else {
   3372    if (APZCTreeManager* treeManagerLocal = GetApzcTreeManager()) {
   3373      rootContentApzc = treeManagerLocal->FindZoomableApzc(this);
   3374      if (rootContentApzc) {
   3375        MOZ_ASSERT(rootContentApzc->GetLayersId() != GetLayersId());
   3376        MOZ_ASSERT(this == treeManagerLocal->FindRootApzcFor(GetLayersId()));
   3377        transformToRootContentApzc =
   3378            treeManagerLocal->GetOopifToRootContentTransform(this);
   3379      }
   3380    }
   3381  }
   3382 
   3383  if (!rootContentApzc) {
   3384    return nsEventStatus_eIgnore;
   3385  }
   3386 
   3387  RefPtr<GeckoContentController> controller = GetGeckoContentController();
   3388  if (controller) {
   3389    if (rootContentApzc->ZoomConstraintsAllowDoubleTapZoom() &&
   3390        (!GetCurrentTouchBlock() ||
   3391         GetCurrentTouchBlock()->TouchActionAllowsDoubleTapZoom())) {
   3392      if (Maybe<LayoutDevicePoint> geckoScreenPoint =
   3393              ConvertToGecko(aEvent.mPoint)) {
   3394        controller->HandleTap(
   3395            TapType::eDoubleTap, *geckoScreenPoint, aEvent.modifiers, GetGuid(),
   3396            GetCurrentTouchBlock() ? GetCurrentTouchBlock()->GetBlockId() : 0,
   3397            Some(DoubleTapToZoomMetrics{rootContentApzc->GetVisualViewport(),
   3398                                        rootContentApzc->GetScrollableRect(),
   3399                                        transformToRootContentApzc}));
   3400      }
   3401    }
   3402    return nsEventStatus_eConsumeNoDefault;
   3403  }
   3404  return nsEventStatus_eIgnore;
   3405 }
   3406 
   3407 nsEventStatus AsyncPanZoomController::OnSecondTap(
   3408    const TapGestureInput& aEvent) {
   3409  APZC_LOG_DETAIL("got a second-tap in state %s\n", this,
   3410                  ToString(mState).c_str());
   3411  return GenerateSingleTap(TapType::eSecondTap, aEvent.mPoint,
   3412                           aEvent.modifiers);
   3413 }
   3414 
   3415 nsEventStatus AsyncPanZoomController::OnCancelTap(
   3416    const TapGestureInput& aEvent) {
   3417  APZC_LOG_DETAIL("got a cancel-tap in state %s\n", this,
   3418                  ToString(mState).c_str());
   3419  // XXX: Implement this.
   3420  return nsEventStatus_eIgnore;
   3421 }
   3422 
   3423 ScreenToParentLayerMatrix4x4 AsyncPanZoomController::GetTransformToThis()
   3424    const {
   3425  if (APZCTreeManager* treeManagerLocal = GetApzcTreeManager()) {
   3426    return treeManagerLocal->GetScreenToApzcTransform(this);
   3427  }
   3428  return ScreenToParentLayerMatrix4x4();
   3429 }
   3430 
   3431 ScreenPoint AsyncPanZoomController::ToScreenCoordinates(
   3432    const ParentLayerPoint& aVector, const ParentLayerPoint& aAnchor) const {
   3433  return TransformVector(GetTransformToThis().Inverse(), aVector, aAnchor);
   3434 }
   3435 
   3436 // TODO: figure out a good way to check the w-coordinate is positive and return
   3437 // the result
   3438 ParentLayerPoint AsyncPanZoomController::ToParentLayerCoordinates(
   3439    const ScreenPoint& aVector, const ScreenPoint& aAnchor) const {
   3440  return TransformVector(GetTransformToThis(), aVector, aAnchor);
   3441 }
   3442 
   3443 ParentLayerPoint AsyncPanZoomController::ToParentLayerCoordinates(
   3444    const ScreenPoint& aVector, const ExternalPoint& aAnchor) const {
   3445  return ToParentLayerCoordinates(
   3446      aVector,
   3447      ViewAs<ScreenPixel>(aAnchor, PixelCastJustification::ExternalIsScreen));
   3448 }
   3449 
   3450 ExternalPoint AsyncPanZoomController::ToExternalPoint(
   3451    const ExternalPoint& aScreenOffset, const ScreenPoint& aScreenPoint) {
   3452  return aScreenOffset +
   3453         ViewAs<ExternalPixel>(aScreenPoint,
   3454                               PixelCastJustification::ExternalIsScreen);
   3455 }
   3456 
   3457 ScreenPoint AsyncPanZoomController::PanVector(const ExternalPoint& aPos) const {
   3458  return ScreenPoint(fabs(aPos.x - mStartTouch.x),
   3459                     fabs(aPos.y - mStartTouch.y));
   3460 }
   3461 
   3462 bool AsyncPanZoomController::Contains(const ScreenIntPoint& aPoint) const {
   3463  ScreenToParentLayerMatrix4x4 transformToThis = GetTransformToThis();
   3464  Maybe<ParentLayerIntPoint> point = UntransformBy(transformToThis, aPoint);
   3465  if (!point) {
   3466    return false;
   3467  }
   3468 
   3469  ParentLayerIntRect cb;
   3470  {
   3471    RecursiveMutexAutoLock lock(mRecursiveMutex);
   3472    GetFrameMetrics().GetCompositionBounds().ToIntRect(&cb);
   3473  }
   3474  return cb.Contains(*point);
   3475 }
   3476 
   3477 bool AsyncPanZoomController::IsInOverscrollGutter(
   3478    const ScreenPoint& aHitTestPoint) const {
   3479  if (!IsPhysicallyOverscrolled()) {
   3480    return false;
   3481  }
   3482 
   3483  Maybe<ParentLayerPoint> apzcPoint =
   3484      UntransformBy(GetTransformToThis(), aHitTestPoint);
   3485  if (!apzcPoint) return false;
   3486  return IsInOverscrollGutter(*apzcPoint);
   3487 }
   3488 
   3489 bool AsyncPanZoomController::IsInOverscrollGutter(
   3490    const ParentLayerPoint& aHitTestPoint) const {
   3491  ParentLayerRect compositionBounds;
   3492  {
   3493    RecursiveMutexAutoLock lock(mRecursiveMutex);
   3494    compositionBounds = GetFrameMetrics().GetCompositionBounds();
   3495  }
   3496  if (!compositionBounds.Contains(aHitTestPoint)) {
   3497    // Point is outside of scrollable element's bounds altogether.
   3498    return false;
   3499  }
   3500  auto overscrollTransform = GetOverscrollTransform(eForEventHandling);
   3501  ParentLayerPoint overscrollUntransformed =
   3502      overscrollTransform.Inverse().TransformPoint(aHitTestPoint);
   3503 
   3504  if (compositionBounds.Contains(overscrollUntransformed)) {
   3505    // Point is over scrollable content.
   3506    return false;
   3507  }
   3508 
   3509  // Point is in gutter.
   3510  return true;
   3511 }
   3512 
   3513 bool AsyncPanZoomController::IsOverscrolled() const {
   3514  return mOverscrollEffect->IsOverscrolled();
   3515 }
   3516 
   3517 bool AsyncPanZoomController::IsPhysicallyOverscrolled() const {
   3518  // As an optimization, avoid calling Apply/UnapplyAsyncTestAttributes
   3519  // unless we're in a test environment where we need it.
   3520  if (StaticPrefs::apz_overscroll_test_async_scroll_offset_enabled()) {
   3521    RecursiveMutexAutoLock lock(mRecursiveMutex);
   3522    AutoApplyAsyncTestAttributes testAttributeApplier(this, lock);
   3523    return mX.IsOverscrolled() || mY.IsOverscrolled();
   3524  }
   3525  RecursiveMutexAutoLock lock(mRecursiveMutex);
   3526  return mX.IsOverscrolled() || mY.IsOverscrolled();
   3527 }
   3528 
   3529 bool AsyncPanZoomController::IsInInvalidOverscroll() const {
   3530  return mX.IsInInvalidOverscroll() || mY.IsInInvalidOverscroll();
   3531 }
   3532 
   3533 ParentLayerPoint AsyncPanZoomController::PanStart() const {
   3534  return ParentLayerPoint(mX.PanStart(), mY.PanStart());
   3535 }
   3536 
   3537 const ParentLayerPoint AsyncPanZoomController::GetVelocityVector() const {
   3538  RecursiveMutexAutoLock lock(mRecursiveMutex);
   3539  return ParentLayerPoint(mX.GetVelocity(), mY.GetVelocity());
   3540 }
   3541 
   3542 void AsyncPanZoomController::SetVelocityVector(
   3543    const ParentLayerPoint& aVelocityVector) {
   3544  RecursiveMutexAutoLock lock(mRecursiveMutex);
   3545  mX.SetVelocity(aVelocityVector.x);
   3546  mY.SetVelocity(aVelocityVector.y);
   3547 }
   3548 
   3549 void AsyncPanZoomController::HandlePanningWithTouchAction(double aAngle) {
   3550  // Handling of cross sliding will need to be added in this method after
   3551  // touch-action released enabled by default.
   3552  MOZ_ASSERT(GetCurrentTouchBlock());
   3553  RefPtr<const OverscrollHandoffChain> overscrollHandoffChain =
   3554      GetCurrentInputBlock()->GetOverscrollHandoffChain();
   3555  bool canScrollHorizontal =
   3556      !mX.IsAxisLocked() && overscrollHandoffChain->CanScrollInDirection(
   3557                                this, ScrollDirection::eHorizontal);
   3558  bool canScrollVertical =
   3559      !mY.IsAxisLocked() && overscrollHandoffChain->CanScrollInDirection(
   3560                                this, ScrollDirection::eVertical);
   3561  if (GetCurrentTouchBlock()->TouchActionAllowsPanningXY()) {
   3562    if (canScrollHorizontal && canScrollVertical) {
   3563      if (apz::IsCloseToHorizontal(aAngle,
   3564                                   StaticPrefs::apz_axis_lock_lock_angle())) {
   3565        mY.SetAxisLocked(true);
   3566        SetState(PANNING_LOCKED_X);
   3567      } else if (apz::IsCloseToVertical(
   3568                     aAngle, StaticPrefs::apz_axis_lock_lock_angle())) {
   3569        mX.SetAxisLocked(true);
   3570        SetState(PANNING_LOCKED_Y);
   3571      } else {
   3572        SetState(PANNING);
   3573      }
   3574    } else if (canScrollHorizontal || canScrollVertical) {
   3575      SetState(PANNING);
   3576    } else {
   3577      SetState(NOTHING);
   3578    }
   3579  } else if (GetCurrentTouchBlock()->TouchActionAllowsPanningX()) {
   3580    // Using bigger angle for panning to keep behavior consistent
   3581    // with IE.
   3582    if (apz::IsCloseToHorizontal(
   3583            aAngle, StaticPrefs::apz_axis_lock_direct_pan_angle())) {
   3584      mY.SetAxisLocked(true);
   3585      SetState(PANNING_LOCKED_X);
   3586      mPanDirRestricted = true;
   3587    } else {
   3588      // Don't treat these touches as pan/zoom movements since 'touch-action'
   3589      // value requires it.
   3590      SetState(NOTHING);
   3591    }
   3592  } else if (GetCurrentTouchBlock()->TouchActionAllowsPanningY()) {
   3593    if (apz::IsCloseToVertical(aAngle,
   3594                               StaticPrefs::apz_axis_lock_direct_pan_angle())) {
   3595      mX.SetAxisLocked(true);
   3596      SetState(PANNING_LOCKED_Y);
   3597      mPanDirRestricted = true;
   3598    } else {
   3599      SetState(NOTHING);
   3600    }
   3601  } else {
   3602    SetState(NOTHING);
   3603  }
   3604  if (!IsInPanningState()) {
   3605    // If we didn't enter a panning state because touch-action disallowed it,
   3606    // make sure to clear any leftover velocity from the pre-threshold
   3607    // touchmoves.
   3608    mX.SetVelocity(0);
   3609    mY.SetVelocity(0);
   3610  }
   3611 }
   3612 
   3613 void AsyncPanZoomController::HandlePanning(double aAngle) {
   3614  RecursiveMutexAutoLock lock(mRecursiveMutex);
   3615  MOZ_ASSERT(GetCurrentInputBlock());
   3616  RefPtr<const OverscrollHandoffChain> overscrollHandoffChain =
   3617      GetCurrentInputBlock()->GetOverscrollHandoffChain();
   3618  bool canScrollHorizontal =
   3619      !mX.IsAxisLocked() && overscrollHandoffChain->CanScrollInDirection(
   3620                                this, ScrollDirection::eHorizontal);
   3621  bool canScrollVertical =
   3622      !mY.IsAxisLocked() && overscrollHandoffChain->CanScrollInDirection(
   3623                                this, ScrollDirection::eVertical);
   3624 
   3625  MOZ_ASSERT(UsingStatefulAxisLock());
   3626 
   3627  if (!canScrollHorizontal || !canScrollVertical) {
   3628    SetState(PANNING);
   3629  } else if (apz::IsCloseToHorizontal(
   3630                 aAngle, StaticPrefs::apz_axis_lock_lock_angle())) {
   3631    mY.SetAxisLocked(true);
   3632    if (canScrollHorizontal) {
   3633      SetState(PANNING_LOCKED_X);
   3634    }
   3635  } else if (apz::IsCloseToVertical(aAngle,
   3636                                    StaticPrefs::apz_axis_lock_lock_angle())) {
   3637    mX.SetAxisLocked(true);
   3638    if (canScrollVertical) {
   3639      SetState(PANNING_LOCKED_Y);
   3640    }
   3641  } else {
   3642    SetState(PANNING);
   3643  }
   3644 }
   3645 
   3646 void AsyncPanZoomController::HandlePanningUpdate(
   3647    const ScreenPoint& aPanDistance) {
   3648  // If we're axis-locked, check if the user is trying to break the lock
   3649  if ((GetAxisLockMode() == AxisLockMode::STICKY ||
   3650       GetAxisLockMode() == AxisLockMode::BREAKABLE) &&
   3651      !mPanDirRestricted) {
   3652    ParentLayerPoint vector =
   3653        ToParentLayerCoordinates(aPanDistance, mStartTouch);
   3654 
   3655    float angle = atan2f(vector.y, vector.x);  // range [-pi, pi]
   3656    angle = fabsf(angle);                      // range [0, pi]
   3657 
   3658    float breakThreshold =
   3659        StaticPrefs::apz_axis_lock_breakout_threshold() * GetDPI();
   3660 
   3661    if (fabs(aPanDistance.x) > breakThreshold ||
   3662        fabs(aPanDistance.y) > breakThreshold) {
   3663      switch (mState) {
   3664        case PANNING_LOCKED_X:
   3665          if (!apz::IsCloseToHorizontal(
   3666                  angle, StaticPrefs::apz_axis_lock_breakout_angle())) {
   3667            mY.SetAxisLocked(false);
   3668            // If we are within the lock angle from the Y axis and STICKY,
   3669            // lock onto the Y axis. BREAKABLE should not re-acquire the lock.
   3670            if (apz::IsCloseToVertical(
   3671                    angle, StaticPrefs::apz_axis_lock_lock_angle()) &&
   3672                GetAxisLockMode() != AxisLockMode::BREAKABLE) {
   3673              mX.SetAxisLocked(true);
   3674              SetState(PANNING_LOCKED_Y);
   3675            } else {
   3676              SetState(PANNING);
   3677            }
   3678          }
   3679          break;
   3680 
   3681        case PANNING_LOCKED_Y:
   3682          if (!apz::IsCloseToVertical(
   3683                  angle, StaticPrefs::apz_axis_lock_breakout_angle())) {
   3684            mX.SetAxisLocked(false);
   3685            // If we are within the lock angle from the X axis and STICKY,
   3686            // lock onto the X axis. BREAKABLE should not re-acquire the lock.
   3687            if (apz::IsCloseToHorizontal(
   3688                    angle, StaticPrefs::apz_axis_lock_lock_angle()) &&
   3689                GetAxisLockMode() != AxisLockMode::BREAKABLE) {
   3690              mY.SetAxisLocked(true);
   3691              SetState(PANNING_LOCKED_X);
   3692            } else {
   3693              SetState(PANNING);
   3694            }
   3695          }
   3696          break;
   3697 
   3698        case PANNING:
   3699          // `HandlePanning` can re-acquire the axis lock, which we don't want
   3700          // to do if the lock is BREAKABLE
   3701          if (GetAxisLockMode() != AxisLockMode::BREAKABLE) {
   3702            HandlePanning(angle);
   3703          }
   3704          break;
   3705 
   3706        default:
   3707          break;
   3708      }
   3709    }
   3710  }
   3711 }
   3712 
   3713 void AsyncPanZoomController::HandlePinchLocking(
   3714    const PinchGestureInput& aEvent) {
   3715  // Focus change and span distance calculated from an event buffer
   3716  // Used to handle pinch locking irrespective of touch screen sensitivity
   3717  // Note: both values fall back to the same value as
   3718  //       their un-buffered counterparts if there is only one (the latest)
   3719  //       event in the buffer. ie: when the touch screen is dispatching
   3720  //       events slower than the lifetime of the buffer
   3721  ParentLayerCoord bufferedSpanDistance;
   3722  ParentLayerPoint focusPoint, bufferedFocusChange;
   3723  {
   3724    RecursiveMutexAutoLock lock(mRecursiveMutex);
   3725 
   3726    focusPoint = mPinchEventBuffer.back().mLocalFocusPoint -
   3727                 Metrics().GetCompositionBounds().TopLeft();
   3728    ParentLayerPoint bufferedLastZoomFocus =
   3729        (mPinchEventBuffer.size() > 1)
   3730            ? mPinchEventBuffer.front().mLocalFocusPoint -
   3731                  Metrics().GetCompositionBounds().TopLeft()
   3732            : mLastZoomFocus;
   3733 
   3734    bufferedFocusChange = bufferedLastZoomFocus - focusPoint;
   3735    bufferedSpanDistance = fabsf(mPinchEventBuffer.front().mPreviousSpan -
   3736                                 mPinchEventBuffer.back().mCurrentSpan);
   3737  }
   3738 
   3739  // Convert to screen coordinates
   3740  ScreenCoord spanDistance =
   3741      ToScreenCoordinates(ParentLayerPoint(0, bufferedSpanDistance), focusPoint)
   3742          .Length();
   3743  ScreenPoint focusChange =
   3744      ToScreenCoordinates(bufferedFocusChange, focusPoint);
   3745 
   3746  if (mPinchLocked) {
   3747    if (GetPinchLockMode() == PINCH_STICKY) {
   3748      ScreenCoord spanBreakoutThreshold =
   3749          StaticPrefs::apz_pinch_lock_span_breakout_threshold() * GetDPI();
   3750      mPinchLocked = !(spanDistance > spanBreakoutThreshold);
   3751    }
   3752  } else {
   3753    if (GetPinchLockMode() != PINCH_FREE) {
   3754      ScreenCoord spanLockThreshold =
   3755          StaticPrefs::apz_pinch_lock_span_lock_threshold() * GetDPI();
   3756      ScreenCoord scrollLockThreshold =
   3757          StaticPrefs::apz_pinch_lock_scroll_lock_threshold() * GetDPI();
   3758 
   3759      if (spanDistance < spanLockThreshold &&
   3760          focusChange.Length() > scrollLockThreshold) {
   3761        mPinchLocked = true;
   3762 
   3763        // We are transitioning to a two-finger pan that could trigger
   3764        // a fling at its end, so start tracking velocity.
   3765        StartTouch(aEvent.mLocalFocusPoint, aEvent.mTimeStamp);
   3766      }
   3767    }
   3768  }
   3769 }
   3770 
   3771 nsEventStatus AsyncPanZoomController::StartPanning(
   3772    const ExternalPoint& aStartPoint, const TimeStamp& aEventTime) {
   3773  ParentLayerPoint vector =
   3774      ToParentLayerCoordinates(PanVector(aStartPoint), mStartTouch);
   3775  double angle = atan2(vector.y, vector.x);  // range [-pi, pi]
   3776  angle = fabs(angle);                       // range [0, pi]
   3777 
   3778  RecursiveMutexAutoLock lock(mRecursiveMutex);
   3779  HandlePanningWithTouchAction(angle);
   3780 
   3781  if (IsInPanningState()) {
   3782    mTouchStartRestingTimeBeforePan = aEventTime - mTouchStartTime;
   3783    mMinimumVelocityDuringPan = Nothing();
   3784 
   3785    if (RefPtr<GeckoContentController> controller =
   3786            GetGeckoContentController()) {
   3787      controller->NotifyAPZStateChange(GetGuid(),
   3788                                       APZStateChange::eStartPanning);
   3789    }
   3790    return nsEventStatus_eConsumeNoDefault;
   3791  }
   3792  // Don't consume an event that didn't trigger a panning.
   3793  return nsEventStatus_eIgnore;
   3794 }
   3795 
   3796 void AsyncPanZoomController::UpdateWithTouchAtDevicePoint(
   3797    const MultiTouchInput& aEvent) {
   3798  const SingleTouchData& touchData = aEvent.mTouches[0];
   3799  // Take historical touch data into account in order to improve the accuracy
   3800  // of the velocity estimate. On many Android devices, the touch screen samples
   3801  // at a higher rate than vsync (e.g. 100Hz vs 60Hz), and the historical data
   3802  // lets us take advantage of those high-rate samples.
   3803  for (const auto& historicalData : touchData.mHistoricalData) {
   3804    ParentLayerPoint historicalPoint = historicalData.mLocalScreenPoint;
   3805    mX.UpdateWithTouchAtDevicePoint(historicalPoint.x,
   3806                                    historicalData.mTimeStamp);
   3807    mY.UpdateWithTouchAtDevicePoint(historicalPoint.y,
   3808                                    historicalData.mTimeStamp);
   3809  }
   3810  ParentLayerPoint point = touchData.mLocalScreenPoint;
   3811  mX.UpdateWithTouchAtDevicePoint(point.x, aEvent.mTimeStamp);
   3812  mY.UpdateWithTouchAtDevicePoint(point.y, aEvent.mTimeStamp);
   3813 }
   3814 
   3815 Maybe<CompositionPayload> AsyncPanZoomController::NotifyScrollSampling() {
   3816  RecursiveMutexAutoLock lock(mRecursiveMutex);
   3817  return mSampledState.front().TakeScrollPayload();
   3818 }
   3819 
   3820 bool AsyncPanZoomController::AttemptScroll(
   3821    ParentLayerPoint& aStartPoint, ParentLayerPoint& aEndPoint,
   3822    OverscrollHandoffState& aOverscrollHandoffState) {
   3823  // "start - end" rather than "end - start" because e.g. moving your finger
   3824  // down (*positive* direction along y axis) causes the vertical scroll offset
   3825  // to *decrease* as the page follows your finger.
   3826  ParentLayerPoint displacement = aStartPoint - aEndPoint;
   3827 
   3828  ParentLayerPoint overscroll;  // will be used outside monitor block
   3829 
   3830  // If the direction of panning is reversed within the same input block,
   3831  // a later event in the block could potentially scroll an APZC earlier
   3832  // in the handoff chain, than an earlier event in the block (because
   3833  // the earlier APZC was scrolled to its extent in the original direction).
   3834  // We want to disallow this.
   3835  bool scrollThisApzc = false;
   3836  if (InputBlockState* block = GetCurrentInputBlock()) {
   3837    scrollThisApzc =
   3838        !block->GetScrolledApzc() || block->IsDownchainOfScrolledApzc(this);
   3839  }
   3840 
   3841  ParentLayerPoint adjustedDisplacement;
   3842  if (scrollThisApzc) {
   3843    RecursiveMutexAutoLock lock(mRecursiveMutex);
   3844    AutoRecordCompositorScrollUpdate csu(
   3845        this, CompositorScrollUpdate::Source::UserInteraction, lock);
   3846 
   3847    bool respectDisregardedDirections =
   3848        ScrollSourceRespectsDisregardedDirections(
   3849            aOverscrollHandoffState.mScrollSource);
   3850    bool forcesVerticalOverscroll = respectDisregardedDirections &&
   3851                                    mScrollMetadata.GetDisregardedDirection() ==
   3852                                        Some(ScrollDirection::eVertical);
   3853    bool forcesHorizontalOverscroll =
   3854        respectDisregardedDirections &&
   3855        mScrollMetadata.GetDisregardedDirection() ==
   3856            Some(ScrollDirection::eHorizontal);
   3857 
   3858    bool yChanged =
   3859        mY.AdjustDisplacement(displacement.y, adjustedDisplacement.y,
   3860                              overscroll.y, forcesVerticalOverscroll);
   3861    bool xChanged =
   3862        mX.AdjustDisplacement(displacement.x, adjustedDisplacement.x,
   3863                              overscroll.x, forcesHorizontalOverscroll);
   3864    if (xChanged || yChanged) {
   3865      ScheduleComposite();
   3866    }
   3867 
   3868    if (!IsZero(adjustedDisplacement) &&
   3869        Metrics().GetZoom() != CSSToParentLayerScale(0)) {
   3870      ScrollBy(adjustedDisplacement / Metrics().GetZoom());
   3871      if (InputBlockState* block = GetCurrentInputBlock()) {
   3872        bool displacementIsUserVisible = true;
   3873 
   3874        {  // Release the APZC lock before calling ToScreenCoordinates which
   3875          // acquires the APZ tree lock. Note that this just unlocks the mutex
   3876          // once, so if we're locking it multiple times on the callstack then
   3877          // this will be insufficient.
   3878          RecursiveMutexAutoUnlock unlock(mRecursiveMutex);
   3879 
   3880          ScreenIntPoint screenDisplacement = RoundedToInt(
   3881              ToScreenCoordinates(adjustedDisplacement, aStartPoint));
   3882          // If the displacement we just applied rounds to zero in screen space,
   3883          // then it's probably not going to be visible to the user. In that
   3884          // case let's not mark this APZC as scrolled, so that even if the
   3885          // immediate handoff pref is disabled, we'll allow doing the handoff
   3886          // to the next APZC.
   3887          if (screenDisplacement == ScreenIntPoint()) {
   3888            displacementIsUserVisible = false;
   3889          }
   3890        }
   3891        if (displacementIsUserVisible) {
   3892          block->SetScrolledApzc(this);
   3893        }
   3894      }
   3895      // Note that in the case of instant scrolling, the last snap target ids
   3896      // will be set after AttemptScroll call so that we can clobber them
   3897      // unconditionally here.
   3898      mLastSnapTargetIds = ScrollSnapTargetIds{};
   3899      ScheduleCompositeAndMaybeRepaint();
   3900    }
   3901 
   3902    // Adjust the start point to reflect the consumed portion of the scroll.
   3903    aStartPoint = aEndPoint + overscroll;
   3904  } else {
   3905    overscroll = displacement;
   3906  }
   3907 
   3908  // Accumulate the amount of actual scrolling that occurred into the handoff
   3909  // state. Note that ToScreenCoordinates() needs to be called outside the
   3910  // mutex.
   3911  if (!IsZero(adjustedDisplacement)) {
   3912    aOverscrollHandoffState.mTotalMovement +=
   3913        ToScreenCoordinates(adjustedDisplacement, aEndPoint);
   3914  }
   3915 
   3916  // If we consumed the entire displacement as a normal scroll, great.
   3917  if (IsZero(overscroll)) {
   3918    return true;
   3919  }
   3920 
   3921  if (AllowScrollHandoffInCurrentBlock()) {
   3922    // If there is overscroll, first try to hand it off to an APZC later
   3923    // in the handoff chain to consume (either as a normal scroll or as
   3924    // overscroll).
   3925    // Note: "+ overscroll" rather than "- overscroll" because "overscroll"
   3926    // is what's left of "displacement", and "displacement" is "start - end".
   3927    ++aOverscrollHandoffState.mChainIndex;
   3928    bool consumed =
   3929        CallDispatchScroll(aStartPoint, aEndPoint, aOverscrollHandoffState);
   3930    if (consumed) {
   3931      return true;
   3932    }
   3933 
   3934    overscroll = aStartPoint - aEndPoint;
   3935    MOZ_ASSERT(!IsZero(overscroll));
   3936  }
   3937 
   3938  // If there is no APZC later in the handoff chain that accepted the
   3939  // overscroll, try to accept it ourselves. We only accept it if we
   3940  // are pannable.
   3941  if (ScrollSourceAllowsOverscroll(aOverscrollHandoffState.mScrollSource)) {
   3942    APZC_LOG("%p taking overscroll during panning\n", this);
   3943 
   3944    ParentLayerPoint prevVisualOverscroll = GetOverscrollAmount();
   3945 
   3946    OverscrollForPanning(overscroll, aOverscrollHandoffState.mPanDistance);
   3947 
   3948    // Accumulate the amount of change to the overscroll that occurred into the
   3949    // handoff state. Note that the input amount, |overscroll|, is turned into
   3950    // some smaller visual overscroll amount (queried via GetOverscrollAmount())
   3951    // by applying resistance (Axis::ApplyResistance()), and it's the latter we
   3952    // want to count towards OverscrollHandoffState::mTotalMovement.
   3953    ParentLayerPoint visualOverscrollChange =
   3954        GetOverscrollAmount() - prevVisualOverscroll;
   3955    if (!IsZero(visualOverscrollChange)) {
   3956      aOverscrollHandoffState.mTotalMovement +=
   3957          ToScreenCoordinates(visualOverscrollChange, aEndPoint);
   3958    }
   3959  }
   3960 
   3961  aStartPoint = aEndPoint + overscroll;
   3962 
   3963  return IsZero(overscroll);
   3964 }
   3965 
   3966 void AsyncPanZoomController::OverscrollForPanning(
   3967    ParentLayerPoint& aOverscroll, const ScreenPoint& aPanDistance) {
   3968  // Only allow entering overscroll along an axis if the pan distance along
   3969  // that axis is greater than the pan distance along the other axis by a
   3970  // configurable factor. If we are already overscrolled, don't check this.
   3971  if (!IsOverscrolled()) {
   3972    if (aPanDistance.x <
   3973        StaticPrefs::apz_overscroll_min_pan_distance_ratio() * aPanDistance.y) {
   3974      aOverscroll.x = 0;
   3975    }
   3976    if (aPanDistance.y <
   3977        StaticPrefs::apz_overscroll_min_pan_distance_ratio() * aPanDistance.x) {
   3978      aOverscroll.y = 0;
   3979    }
   3980  }
   3981 
   3982  OverscrollBy(aOverscroll);
   3983 }
   3984 
   3985 ScrollDirections AsyncPanZoomController::GetOverscrollableDirections() const {
   3986  ScrollDirections result;
   3987 
   3988  RecursiveMutexAutoLock lock(mRecursiveMutex);
   3989 
   3990  // If the target has the disregarded direction, it means it's single line
   3991  // text control, thus we don't want to overscroll in both directions.
   3992  if (mScrollMetadata.GetDisregardedDirection()) {
   3993    return result;
   3994  }
   3995 
   3996  if (mX.CanScroll() && mX.OverscrollBehaviorAllowsOverscrollEffect()) {
   3997    result += ScrollDirection::eHorizontal;
   3998  }
   3999 
   4000  if (mY.CanScroll() && mY.OverscrollBehaviorAllowsOverscrollEffect()) {
   4001    result += ScrollDirection::eVertical;
   4002  }
   4003 
   4004  return result;
   4005 }
   4006 
   4007 void AsyncPanZoomController::OverscrollBy(ParentLayerPoint& aOverscroll) {
   4008  if (!StaticPrefs::apz_overscroll_enabled()) {
   4009    return;
   4010  }
   4011 
   4012  RecursiveMutexAutoLock lock(mRecursiveMutex);
   4013  // Do not go into overscroll in a direction in which we have no room to
   4014  // scroll to begin with.
   4015  ScrollDirections overscrollableDirections = GetOverscrollableDirections();
   4016  if (IsZero(aOverscroll.x)) {
   4017    overscrollableDirections -= ScrollDirection::eHorizontal;
   4018  }
   4019  if (IsZero(aOverscroll.y)) {
   4020    overscrollableDirections -= ScrollDirection::eVertical;
   4021  }
   4022 
   4023  mOverscrollEffect->ConsumeOverscroll(aOverscroll, overscrollableDirections);
   4024 }
   4025 
   4026 RefPtr<const OverscrollHandoffChain>
   4027 AsyncPanZoomController::BuildOverscrollHandoffChain() {
   4028  if (APZCTreeManager* treeManagerLocal = GetApzcTreeManager()) {
   4029    return treeManagerLocal->BuildOverscrollHandoffChain(this);
   4030  }
   4031 
   4032  // This APZC IsDestroyed(). To avoid callers having to special-case this
   4033  // scenario, just build a 1-element chain containing ourselves.
   4034  OverscrollHandoffChain* result = new OverscrollHandoffChain;
   4035  result->Add(this);
   4036  return result;
   4037 }
   4038 
   4039 ParentLayerPoint AsyncPanZoomController::AttemptFling(
   4040    const FlingHandoffState& aHandoffState) {
   4041  // The PLPPI computation acquires the tree lock, so it needs to be performed
   4042  // on the controller thread, and before the APZC lock is acquired.
   4043  APZThreadUtils::AssertOnControllerThread();
   4044  float PLPPI = ComputePLPPI(PanStart(), aHandoffState.mVelocity);
   4045 
   4046  RecursiveMutexAutoLock lock(mRecursiveMutex);
   4047 
   4048  if (!IsPannable()) {
   4049    return aHandoffState.mVelocity;
   4050  }
   4051 
   4052  // We may have a pre-existing velocity for whatever reason (for example,
   4053  // a previously handed off fling). We don't want to clobber that.
   4054  APZC_LOG_DETAIL("accepting fling with velocity %s\n", this,
   4055                  ToString(aHandoffState.mVelocity).c_str());
   4056  ParentLayerPoint residualVelocity = aHandoffState.mVelocity;
   4057  if (mX.CanScroll()) {
   4058    mX.SetVelocity(mX.GetVelocity() + aHandoffState.mVelocity.x);
   4059    residualVelocity.x = 0;
   4060  }
   4061  if (mY.CanScroll()) {
   4062    mY.SetVelocity(mY.GetVelocity() + aHandoffState.mVelocity.y);
   4063    residualVelocity.y = 0;
   4064  }
   4065 
   4066  if (!aHandoffState.mIsHandoff && aHandoffState.mScrolledApzc == this) {
   4067    residualVelocity.x = 0;
   4068    residualVelocity.y = 0;
   4069  }
   4070 
   4071  // If we're not scrollable in at least one of the directions in which we
   4072  // were handed velocity, don't start a fling animation.
   4073  // The |IsFinite()| condition should only fail when running some tests
   4074  // that generate events faster than the clock resolution.
   4075  ParentLayerPoint velocity = GetVelocityVector();
   4076  if (!velocity.IsFinite() ||
   4077      velocity.Length() <= StaticPrefs::apz_fling_min_velocity_threshold()) {
   4078    // Relieve overscroll now if needed, since we will not transition to a fling
   4079    // animation and then an overscroll animation, and relieve it then.
   4080    aHandoffState.mChain->SnapBackOverscrolledApzc(this);
   4081    return residualVelocity;
   4082  }
   4083 
   4084  // If there's a scroll snap point near the predicted fling destination,
   4085  // scroll there using a smooth scroll animation. Otherwise, start a
   4086  // fling animation.
   4087  ScrollSnapToDestination();
   4088  if (!InScrollAnimation(ScrollAnimationKind::SmoothMsd)) {
   4089    SetState(FLING);
   4090    RefPtr<AsyncPanZoomAnimation> fling =
   4091        GetPlatformSpecificState()->CreateFlingAnimation(*this, aHandoffState,
   4092                                                         PLPPI);
   4093    StartAnimation(fling.forget());
   4094  }
   4095 
   4096  return residualVelocity;
   4097 }
   4098 
   4099 float AsyncPanZoomController::ComputePLPPI(ParentLayerPoint aPoint,
   4100                                           ParentLayerPoint aDirection) const {
   4101  // Avoid division-by-zero.
   4102  if (aDirection == ParentLayerPoint()) {
   4103    return GetDPI();
   4104  }
   4105 
   4106  // Convert |aDirection| into a unit vector.
   4107  aDirection = aDirection / aDirection.Length();
   4108 
   4109  // Place the vector at |aPoint| and convert to screen coordinates.
   4110  // The length of the resulting vector is the number of Screen coordinates
   4111  // that equal 1 ParentLayer coordinate in the given direction.
   4112  float screenPerParent = ToScreenCoordinates(aDirection, aPoint).Length();
   4113 
   4114  // Finally, factor in the DPI scale.
   4115  return GetDPI() / screenPerParent;
   4116 }
   4117 
   4118 Maybe<CSSPoint> AsyncPanZoomController::GetCurrentAnimationDestination(
   4119    const RecursiveMutexAutoLock& aProofOfLock) const {
   4120  if (mState == SMOOTH_SCROLL) {
   4121    return Some(mAnimation->AsSmoothScrollAnimation()->GetDestination());
   4122  }
   4123  return Nothing();
   4124 }
   4125 
   4126 ParentLayerPoint
   4127 AsyncPanZoomController::AdjustHandoffVelocityForOverscrollBehavior(
   4128    ParentLayerPoint& aHandoffVelocity) const {
   4129  ParentLayerPoint residualVelocity;
   4130  ScrollDirections handoffDirections = GetAllowedHandoffDirections();
   4131  if (!handoffDirections.contains(ScrollDirection::eHorizontal)) {
   4132    residualVelocity.x = aHandoffVelocity.x;
   4133    aHandoffVelocity.x = 0;
   4134  }
   4135  if (!handoffDirections.contains(ScrollDirection::eVertical)) {
   4136    residualVelocity.y = aHandoffVelocity.y;
   4137    aHandoffVelocity.y = 0;
   4138  }
   4139  return residualVelocity;
   4140 }
   4141 
   4142 bool AsyncPanZoomController::OverscrollBehaviorAllowsSwipe() const {
   4143  // Swipe navigation is a "non-local" overscroll behavior like handoff.
   4144  return GetAllowedHandoffDirections().contains(ScrollDirection::eHorizontal);
   4145 }
   4146 
   4147 void AsyncPanZoomController::HandleFlingOverscroll(
   4148    const ParentLayerPoint& aVelocity, SideBits aOverscrollSideBits,
   4149    const RefPtr<const OverscrollHandoffChain>& aOverscrollHandoffChain,
   4150    const RefPtr<const AsyncPanZoomController>& aScrolledApzc) {
   4151  APZCTreeManager* treeManagerLocal = GetApzcTreeManager();
   4152  if (treeManagerLocal) {
   4153    const FlingHandoffState handoffState{
   4154        aVelocity, aOverscrollHandoffChain, Nothing(),
   4155        0,         true /* handoff */,      aScrolledApzc};
   4156    ParentLayerPoint residualVelocity =
   4157        treeManagerLocal->DispatchFling(this, handoffState);
   4158    FLING_LOG("APZC %p left with residual velocity %s\n", this,
   4159              ToString(residualVelocity).c_str());
   4160    if (!IsZero(residualVelocity) && IsPannable() &&
   4161        StaticPrefs::apz_overscroll_enabled()) {
   4162      // Obey overscroll-behavior.
   4163      RecursiveMutexAutoLock lock(mRecursiveMutex);
   4164      if (!mX.OverscrollBehaviorAllowsOverscrollEffect()) {
   4165        residualVelocity.x = 0;
   4166      }
   4167      if (!mY.OverscrollBehaviorAllowsOverscrollEffect()) {
   4168        residualVelocity.y = 0;
   4169      }
   4170 
   4171      // If there is velocity left over from the fling which could not
   4172      // be handed off to another other APZC in the handoff chain,
   4173      // start an overscroll animation which will enter overscroll
   4174      // and then relieve it.
   4175      if (!IsZero(residualVelocity)) {
   4176        mOverscrollEffect->RelieveOverscroll(residualVelocity,
   4177                                             aOverscrollSideBits);
   4178      }
   4179 
   4180      // Additionally snap back any other APZC in the handoff chain
   4181      // which may be overscrolled (e.g. an ancestor whose overscroll
   4182      // animation may have been interrupted by the input gesture which
   4183      // triggered the fling).
   4184      aOverscrollHandoffChain->SnapBackOverscrolledApzcForMomentum(
   4185          this, residualVelocity);
   4186    }
   4187  }
   4188 }
   4189 
   4190 ParentLayerPoint AsyncPanZoomController::ConvertDestinationToDelta(
   4191    CSSPoint& aDestination) const {
   4192  ParentLayerPoint startPoint, endPoint;
   4193 
   4194  {
   4195    RecursiveMutexAutoLock lock(mRecursiveMutex);
   4196 
   4197    startPoint = aDestination * Metrics().GetZoom();
   4198    endPoint = Metrics().GetVisualScrollOffset() * Metrics().GetZoom();
   4199  }
   4200 
   4201  return startPoint - endPoint;
   4202 }
   4203 
   4204 void AsyncPanZoomController::SmoothScrollTo(
   4205    CSSSnapDestination&& aDestination,
   4206    ScrollTriggeredByScript aTriggeredByScript,
   4207    ScrollAnimationKind aAnimationKind, ViewportType aViewportToScroll,
   4208    ScrollOrigin aOrigin, TimeStamp aStartTime) {
   4209  MOZ_ASSERT(aAnimationKind == ScrollAnimationKind::Smooth ||
   4210             aAnimationKind == ScrollAnimationKind::SmoothMsd);
   4211  MOZ_ASSERT_IF(aAnimationKind == ScrollAnimationKind::Smooth,
   4212                aOrigin != ScrollOrigin::NotSpecified);
   4213 
   4214  // Convert velocity from ParentLayerPoints/ms to ParentLayerPoints/s and then
   4215  // to appunits/second.
   4216  nsPoint destination = CSSPoint::ToAppUnits(aDestination.mPosition);
   4217  nsSize velocity;
   4218  if (Metrics().GetZoom() != CSSToParentLayerScale(0)) {
   4219    velocity = CSSSize::ToAppUnits(ParentLayerSize(mX.GetVelocity() * 1000.0f,
   4220                                                   mY.GetVelocity() * 1000.0f) /
   4221                                   Metrics().GetZoom());
   4222  }
   4223 
   4224  if (InScrollAnimation(aAnimationKind)) {
   4225    RefPtr<SmoothScrollAnimation> animation(
   4226        mAnimation->AsSmoothScrollAnimation());
   4227    if (animation->CanExtend(aViewportToScroll, aOrigin)) {
   4228      APZC_LOG("%p updating destination on existing animation\n", this);
   4229      animation->UpdateDestinationAndSnapTargets(
   4230          aStartTime, destination, velocity, std::move(aDestination.mTargetIds),
   4231          aTriggeredByScript);
   4232      return;
   4233    }
   4234  }
   4235 
   4236  // If no scroll is required, we should exit early to avoid triggering
   4237  // a scrollend event when no scrolling occurred.
   4238  if (ConvertDestinationToDelta(aDestination.mPosition) == ParentLayerPoint()) {
   4239    return;
   4240  }
   4241  CancelAnimation();
   4242  SetState(SMOOTH_SCROLL);
   4243 
   4244  RefPtr<SmoothScrollAnimation> animation = SmoothScrollAnimation::Create(
   4245      *this, aAnimationKind, aViewportToScroll, aOrigin);
   4246  animation->UpdateDestinationAndSnapTargets(aStartTime, destination, velocity,
   4247                                             std::move(aDestination.mTargetIds),
   4248                                             aTriggeredByScript);
   4249  StartAnimation(animation.forget());
   4250 }
   4251 
   4252 void AsyncPanZoomController::StartOverscrollAnimation(
   4253    const ParentLayerPoint& aVelocity, SideBits aOverscrollSideBits) {
   4254  MOZ_ASSERT(mState != OVERSCROLL_ANIMATION);
   4255 
   4256  SetState(OVERSCROLL_ANIMATION);
   4257 
   4258  ParentLayerPoint velocity = aVelocity;
   4259  AdjustDeltaForAllowedScrollDirections(velocity,
   4260                                        GetOverscrollableDirections());
   4261  StartAnimation(
   4262      do_AddRef(new OverscrollAnimation(*this, velocity, aOverscrollSideBits)));
   4263 }
   4264 
   4265 bool AsyncPanZoomController::CallDispatchScroll(
   4266    ParentLayerPoint& aStartPoint, ParentLayerPoint& aEndPoint,
   4267    OverscrollHandoffState& aOverscrollHandoffState) {
   4268  // Make a local copy of the tree manager pointer and check if it's not
   4269  // null before calling DispatchScroll(). This is necessary because
   4270  // Destroy(), which nulls out mTreeManager, could be called concurrently.
   4271  APZCTreeManager* treeManagerLocal = GetApzcTreeManager();
   4272  if (!treeManagerLocal) {
   4273    return false;
   4274  }
   4275 
   4276  // Obey overscroll-behavior.
   4277  ParentLayerPoint endPoint = aEndPoint;
   4278  if (aOverscrollHandoffState.mChainIndex > 0) {
   4279    ScrollDirections handoffDirections = GetAllowedHandoffDirections();
   4280    if (!handoffDirections.contains(ScrollDirection::eHorizontal)) {
   4281      endPoint.x = aStartPoint.x;
   4282    }
   4283    if (!handoffDirections.contains(ScrollDirection::eVertical)) {
   4284      endPoint.y = aStartPoint.y;
   4285    }
   4286    if (aStartPoint == endPoint) {
   4287      // Handoff not allowed in either direction - don't even bother.
   4288      return false;
   4289    }
   4290  }
   4291 
   4292  return treeManagerLocal->DispatchScroll(this, aStartPoint, endPoint,
   4293                                          aOverscrollHandoffState);
   4294 }
   4295 
   4296 void AsyncPanZoomController::RecordScrollPayload(const TimeStamp& aTimeStamp) {
   4297  RecursiveMutexAutoLock lock(mRecursiveMutex);
   4298  if (!mScrollPayload) {
   4299    mScrollPayload = Some(
   4300        CompositionPayload{CompositionPayloadType::eAPZScroll, aTimeStamp});
   4301  }
   4302 }
   4303 
   4304 void AsyncPanZoomController::StartTouch(const ParentLayerPoint& aPoint,
   4305                                        TimeStamp aTimestamp) {
   4306  RecursiveMutexAutoLock lock(mRecursiveMutex);
   4307  mX.StartTouch(aPoint.x, aTimestamp);
   4308  mY.StartTouch(aPoint.y, aTimestamp);
   4309 }
   4310 
   4311 void AsyncPanZoomController::EndTouch(TimeStamp aTimestamp,
   4312                                      Axis::ClearAxisLock aClearAxisLock) {
   4313  RecursiveMutexAutoLock lock(mRecursiveMutex);
   4314  mX.EndTouch(aTimestamp, aClearAxisLock);
   4315  mY.EndTouch(aTimestamp, aClearAxisLock);
   4316 }
   4317 
   4318 void AsyncPanZoomController::TrackTouch(const MultiTouchInput& aEvent) {
   4319  mTouchScrollEventBuffer.push(aEvent);
   4320  ExternalPoint extPoint = GetFirstExternalTouchPoint(aEvent);
   4321  ExternalPoint refPoint;
   4322  if (mTouchScrollEventBuffer.size() > 1) {
   4323    refPoint = GetFirstExternalTouchPoint(mTouchScrollEventBuffer.front());
   4324  } else {
   4325    refPoint = mStartTouch;
   4326  }
   4327 
   4328  ScreenPoint panVector = ViewAs<ScreenPixel>(
   4329      extPoint - refPoint, PixelCastJustification::ExternalIsScreen);
   4330 
   4331  HandlePanningUpdate(panVector);
   4332 
   4333  ParentLayerPoint prevTouchPoint(mX.GetPos(), mY.GetPos());
   4334  ParentLayerPoint touchPoint = GetFirstTouchPoint(aEvent);
   4335 
   4336  UpdateWithTouchAtDevicePoint(aEvent);
   4337 
   4338  auto velocity = GetVelocityVector().Length();
   4339  if (mMinimumVelocityDuringPan) {
   4340    mMinimumVelocityDuringPan =
   4341        Some(std::min(*mMinimumVelocityDuringPan, velocity));
   4342  } else {
   4343    mMinimumVelocityDuringPan = Some(velocity);
   4344  }
   4345 
   4346  if (prevTouchPoint != touchPoint) {
   4347    MOZ_ASSERT(GetCurrentTouchBlock());
   4348    OverscrollHandoffState handoffState(
   4349        *GetCurrentTouchBlock()->GetOverscrollHandoffChain(),
   4350        PanVector(extPoint), ScrollSource::Touchscreen);
   4351    RecordScrollPayload(aEvent.mTimeStamp);
   4352    CallDispatchScroll(prevTouchPoint, touchPoint, handoffState);
   4353  }
   4354 }
   4355 
   4356 ParentLayerPoint AsyncPanZoomController::GetFirstTouchPoint(
   4357    const MultiTouchInput& aEvent) {
   4358  return ((SingleTouchData&)aEvent.mTouches[0]).mLocalScreenPoint;
   4359 }
   4360 
   4361 ExternalPoint AsyncPanZoomController::GetFirstExternalTouchPoint(
   4362    const MultiTouchInput& aEvent) {
   4363  return ToExternalPoint(aEvent.mScreenOffset,
   4364                         ((SingleTouchData&)aEvent.mTouches[0]).mScreenPoint);
   4365 }
   4366 
   4367 ParentLayerPoint AsyncPanZoomController::GetOverscrollAmount() const {
   4368  if (StaticPrefs::apz_overscroll_test_async_scroll_offset_enabled()) {
   4369    RecursiveMutexAutoLock lock(mRecursiveMutex);
   4370    AutoApplyAsyncTestAttributes testAttributeApplier(this, lock);
   4371    return GetOverscrollAmountInternal();
   4372  }
   4373  RecursiveMutexAutoLock lock(mRecursiveMutex);
   4374  return GetOverscrollAmountInternal();
   4375 }
   4376 
   4377 ParentLayerPoint AsyncPanZoomController::GetOverscrollAmountInternal() const {
   4378  return {mX.GetOverscroll(), mY.GetOverscroll()};
   4379 }
   4380 
   4381 SideBits AsyncPanZoomController::GetOverscrollSideBits() const {
   4382  return apz::GetOverscrollSideBits({mX.GetOverscroll(), mY.GetOverscroll()});
   4383 }
   4384 
   4385 void AsyncPanZoomController::RestoreOverscrollAmount(
   4386    const ParentLayerPoint& aOverscroll) {
   4387  mX.RestoreOverscroll(aOverscroll.x);
   4388  mY.RestoreOverscroll(aOverscroll.y);
   4389 }
   4390 
   4391 void AsyncPanZoomController::StartAnimation(
   4392    already_AddRefed<AsyncPanZoomAnimation> aAnimation) {
   4393  RecursiveMutexAutoLock lock(mRecursiveMutex);
   4394  mAnimation = aAnimation;
   4395  mLastSampleTime = GetFrameTime();
   4396  ScheduleComposite();
   4397 }
   4398 
   4399 void AsyncPanZoomController::CancelAnimation(CancelAnimationFlags aFlags) {
   4400  RecursiveMutexAutoLock lock(mRecursiveMutex);
   4401  APZC_LOG_DETAIL("running CancelAnimation(0x%x) in state %s\n", this, aFlags,
   4402                  ToString(mState).c_str());
   4403 
   4404  if ((aFlags & ExcludeAutoscroll) && mState == AUTOSCROLL) {
   4405    return;
   4406  }
   4407 
   4408  if (mAnimation) {
   4409    mAnimation->Cancel(aFlags);
   4410  }
   4411 
   4412  SetState(NOTHING);
   4413  mLastSnapTargetIds = ScrollSnapTargetIds{};
   4414  mAnimation = nullptr;
   4415  // Since there is no animation in progress now the axes should
   4416  // have no velocity either. If we are dropping the velocity from a non-zero
   4417  // value we should trigger a repaint as the displayport margins are dependent
   4418  // on the velocity and the last repaint request might not have good margins
   4419  // any more.
   4420  bool repaint = !IsZero(GetVelocityVector());
   4421  mX.SetVelocity(0);
   4422  mY.SetVelocity(0);
   4423  mX.SetAxisLocked(false);
   4424  mY.SetAxisLocked(false);
   4425  // Setting the state to nothing and cancelling the animation can
   4426  // preempt normal mechanisms for relieving overscroll, so we need to clear
   4427  // overscroll here.
   4428  if (!(aFlags & ExcludeOverscroll) && IsOverscrolled()) {
   4429    ClearOverscroll();
   4430    repaint = true;
   4431  }
   4432  // Similar to relieving overscroll, we also need to snap to any snap points
   4433  // if appropriate.
   4434  if (aFlags & CancelAnimationFlags::ScrollSnap) {
   4435    ScrollSnap(ScrollSnapFlags::IntendedEndPosition);
   4436  }
   4437  if (repaint) {
   4438    RequestContentRepaint();
   4439    ScheduleComposite();
   4440  }
   4441 }
   4442 
   4443 void AsyncPanZoomController::ClearOverscroll() {
   4444  mOverscrollEffect->ClearOverscroll();
   4445 }
   4446 
   4447 void AsyncPanZoomController::ClearPhysicalOverscroll() {
   4448  RecursiveMutexAutoLock lock(mRecursiveMutex);
   4449  mX.ClearOverscroll();
   4450  mY.ClearOverscroll();
   4451 }
   4452 
   4453 void AsyncPanZoomController::SetCompositorController(
   4454    CompositorController* aCompositorController) {
   4455  mCompositorController = aCompositorController;
   4456 }
   4457 
   4458 void AsyncPanZoomController::SetVisualScrollOffset(const CSSPoint& aOffset) {
   4459  Metrics().SetVisualScrollOffset(aOffset);
   4460  Metrics().RecalculateLayoutViewportOffset();
   4461 }
   4462 
   4463 void AsyncPanZoomController::ClampAndSetVisualScrollOffset(
   4464    const CSSPoint& aOffset) {
   4465  Metrics().ClampAndSetVisualScrollOffset(aOffset);
   4466  Metrics().RecalculateLayoutViewportOffset();
   4467 }
   4468 
   4469 void AsyncPanZoomController::ScrollToAndClamp(ViewportType aViewportToScroll,
   4470                                              const CSSPoint& aDestination) {
   4471  if (aViewportToScroll == ViewportType::Visual) {
   4472    ClampAndSetVisualScrollOffset(aDestination);
   4473  } else {
   4474    Metrics().ScrollLayoutViewportTo(aDestination);
   4475    Metrics().RecalculateLayoutViewportOffset();
   4476  }
   4477 }
   4478 
   4479 void AsyncPanZoomController::ScrollBy(const CSSPoint& aOffset) {
   4480  SetVisualScrollOffset(Metrics().GetVisualScrollOffset() + aOffset);
   4481 }
   4482 
   4483 void AsyncPanZoomController::ScrollByAndClamp(const CSSPoint& aOffset) {
   4484  ClampAndSetVisualScrollOffset(Metrics().GetVisualScrollOffset() + aOffset);
   4485 }
   4486 
   4487 void AsyncPanZoomController::ScrollByAndClamp(ViewportType aViewportToScroll,
   4488                                              const CSSPoint& aOffset) {
   4489  ScrollToAndClamp(aViewportToScroll,
   4490                   (aViewportToScroll == ViewportType::Visual
   4491                        ? Metrics().GetVisualScrollOffset()
   4492                        : Metrics().GetLayoutScrollOffset()) +
   4493                       aOffset);
   4494 }
   4495 
   4496 void AsyncPanZoomController::ScaleWithFocus(float aScale,
   4497                                            const CSSPoint& aFocus) {
   4498  Metrics().ZoomBy(aScale);
   4499  // We want to adjust the scroll offset such that the CSS point represented by
   4500  // aFocus remains at the same position on the screen before and after the
   4501  // change in zoom. The below code accomplishes this; see
   4502  // https://bugzilla.mozilla.org/show_bug.cgi?id=923431#c6 for an in-depth
   4503  // explanation of how.
   4504  SetVisualScrollOffset((Metrics().GetVisualScrollOffset() + aFocus) -
   4505                        (aFocus / aScale));
   4506 }
   4507 
   4508 /*static*/
   4509 gfx::Size AsyncPanZoomController::GetDisplayportAlignmentMultiplier(
   4510    const ScreenSize& aBaseSize) {
   4511  return gfx::Size(
   4512      std::min(std::max(double(aBaseSize.width) / 250.0, 1.0), 8.0),
   4513      std::min(std::max(double(aBaseSize.height) / 250.0, 1.0), 8.0));
   4514 }
   4515 
   4516 /*static*/
   4517 CSSSize AsyncPanZoomController::CalculateDisplayPortSize(
   4518    const CSSSize& aCompositionSize, const CSSPoint& aVelocity,
   4519    AsyncPanZoomController::ZoomInProgress aZoomInProgress,
   4520    const CSSToScreenScale2D& aDpPerCSS) {
   4521  bool xIsStationarySpeed =
   4522      fabsf(aVelocity.x) < StaticPrefs::apz_min_skate_speed();
   4523  bool yIsStationarySpeed =
   4524      fabsf(aVelocity.y) < StaticPrefs::apz_min_skate_speed();
   4525  float xMultiplier = xIsStationarySpeed
   4526                          ? StaticPrefs::apz_x_stationary_size_multiplier()
   4527                          : StaticPrefs::apz_x_skate_size_multiplier();
   4528  float yMultiplier = yIsStationarySpeed
   4529                          ? StaticPrefs::apz_y_stationary_size_multiplier()
   4530                          : StaticPrefs::apz_y_skate_size_multiplier();
   4531 
   4532  if (IsHighMemSystem() && !xIsStationarySpeed) {
   4533    xMultiplier += StaticPrefs::apz_x_skate_highmem_adjust();
   4534  }
   4535 
   4536  if (IsHighMemSystem() && !yIsStationarySpeed) {
   4537    yMultiplier += StaticPrefs::apz_y_skate_highmem_adjust();
   4538  }
   4539 
   4540  if (aZoomInProgress == AsyncPanZoomController::ZoomInProgress::Yes) {
   4541    // If a zoom is in progress, we will be making content visible on the
   4542    // x and y axes in equal proportion, because the zoom operation scales
   4543    // equally on the x and y axes. The default multipliers computed above are
   4544    // biased towards the y-axis since that's where most scrolling occurs, but
   4545    // in the case of zooming, we should really use equal multipliers on both
   4546    // axes. This does that while preserving the total displayport area
   4547    // quantity (aCompositionSize.Area() * xMultiplier * yMultiplier).
   4548    // Note that normally changing the shape of the displayport is expensive
   4549    // and should be avoided, but if a zoom is in progress the displayport
   4550    // is likely going to be fully repainted anyway due to changes in resolution
   4551    // so there should be no marginal cost to also changing the shape of it.
   4552    float areaMultiplier = xMultiplier * yMultiplier;
   4553    xMultiplier = sqrt(areaMultiplier);
   4554    yMultiplier = xMultiplier;
   4555  }
   4556 
   4557  // Scale down the margin multipliers by the alignment multiplier because
   4558  // the alignment code will expand the displayport outward to the multiplied
   4559  // alignment. This is not necessary for correctness, but for performance;
   4560  // if we don't do this the displayport can end up much larger. The math here
   4561  // is actually just scaling the part of the multipler that is > 1, so that
   4562  // we never end up with xMultiplier or yMultiplier being less than 1 (that
   4563  // would result in a guaranteed checkerboarding situation). Note that the
   4564  // calculation doesn't cancel exactly the increased margin from applying
   4565  // the alignment multiplier, but this is simple and should provide
   4566  // reasonable behaviour in most cases.
   4567  gfx::Size alignmentMultipler =
   4568      AsyncPanZoomController::GetDisplayportAlignmentMultiplier(
   4569          aCompositionSize * aDpPerCSS);
   4570  if (xMultiplier > 1) {
   4571    xMultiplier = ((xMultiplier - 1) / alignmentMultipler.width) + 1;
   4572  }
   4573  if (yMultiplier > 1) {
   4574    yMultiplier = ((yMultiplier - 1) / alignmentMultipler.height) + 1;
   4575  }
   4576 
   4577  return aCompositionSize * CSSSize(xMultiplier, yMultiplier);
   4578 }
   4579 
   4580 /**
   4581 * Ensures that the displayport is at least as large as the visible area
   4582 * inflated by the danger zone. If this is not the case then the
   4583 * "AboutToCheckerboard" function in TiledContentClient.cpp will return true
   4584 * even in the stable state.
   4585 */
   4586 static CSSSize ExpandDisplayPortToDangerZone(
   4587    const CSSSize& aDisplayPortSize, const FrameMetrics& aFrameMetrics) {
   4588  CSSSize dangerZone(0.0f, 0.0f);
   4589  if (aFrameMetrics.DisplayportPixelsPerCSSPixel().xScale != 0 &&
   4590      aFrameMetrics.DisplayportPixelsPerCSSPixel().yScale != 0) {
   4591    dangerZone = ScreenSize(StaticPrefs::apz_danger_zone_x(),
   4592                            StaticPrefs::apz_danger_zone_y()) /
   4593                 aFrameMetrics.DisplayportPixelsPerCSSPixel();
   4594  }
   4595  const CSSSize compositionSize =
   4596      aFrameMetrics.CalculateBoundedCompositedSizeInCssPixels();
   4597 
   4598  const float xSize = std::max(aDisplayPortSize.width,
   4599                               compositionSize.width + (2 * dangerZone.width));
   4600 
   4601  const float ySize =
   4602      std::max(aDisplayPortSize.height,
   4603               compositionSize.height + (2 * dangerZone.height));
   4604 
   4605  return CSSSize(xSize, ySize);
   4606 }
   4607 
   4608 /**
   4609 * Attempts to redistribute any area in the displayport that would get clipped
   4610 * by the scrollable rect, or be inaccessible due to disabled scrolling, to the
   4611 * other axis, while maintaining total displayport area.
   4612 */
   4613 static void RedistributeDisplayPortExcess(CSSSize& aDisplayPortSize,
   4614                                          const CSSRect& aScrollableRect) {
   4615  // As aDisplayPortSize.height * aDisplayPortSize.width does not change,
   4616  // we are just scaling by the ratio and its inverse.
   4617  if (aDisplayPortSize.height > aScrollableRect.Height()) {
   4618    aDisplayPortSize.width *=
   4619        (aDisplayPortSize.height / aScrollableRect.Height());
   4620    aDisplayPortSize.height = aScrollableRect.Height();
   4621  } else if (aDisplayPortSize.width > aScrollableRect.Width()) {
   4622    aDisplayPortSize.height *=
   4623        (aDisplayPortSize.width / aScrollableRect.Width());
   4624    aDisplayPortSize.width = aScrollableRect.Width();
   4625  }
   4626 }
   4627 
   4628 /* static */
   4629 const ScreenMargin AsyncPanZoomController::CalculatePendingDisplayPort(
   4630    const FrameMetrics& aFrameMetrics, const ParentLayerPoint& aVelocity,
   4631    ZoomInProgress aZoomInProgress) {
   4632  if (aFrameMetrics.IsScrollInfoLayer()) {
   4633    // Don't compute margins. Since we can't asynchronously scroll this frame,
   4634    // we don't want to paint anything more than the composition bounds.
   4635    return ScreenMargin();
   4636  }
   4637 
   4638  CSSSize compositionSize =
   4639      aFrameMetrics.CalculateBoundedCompositedSizeInCssPixels();
   4640  CSSPoint velocity;
   4641  if (aFrameMetrics.GetZoom() != CSSToParentLayerScale(0)) {
   4642    velocity = aVelocity / aFrameMetrics.GetZoom();  // avoid division by zero
   4643  }
   4644  CSSRect scrollableRect = aFrameMetrics.GetExpandedScrollableRect();
   4645 
   4646  // Calculate the displayport size based on how fast we're moving along each
   4647  // axis.
   4648  CSSSize displayPortSize =
   4649      CalculateDisplayPortSize(compositionSize, velocity, aZoomInProgress,
   4650                               aFrameMetrics.DisplayportPixelsPerCSSPixel());
   4651 
   4652  displayPortSize =
   4653      ExpandDisplayPortToDangerZone(displayPortSize, aFrameMetrics);
   4654 
   4655  if (StaticPrefs::apz_enlarge_displayport_when_clipped()) {
   4656    RedistributeDisplayPortExcess(displayPortSize, scrollableRect);
   4657  }
   4658 
   4659  // We calculate a "displayport" here which is relative to the scroll offset.
   4660  // Note that the scroll offset we have here in the APZ code may not be the
   4661  // same as the base rect that gets used on the layout side when the
   4662  // displayport margins are actually applied, so it is important to only
   4663  // consider the displayport as margins relative to a scroll offset rather than
   4664  // relative to something more unchanging like the scrollable rect origin.
   4665 
   4666  // Center the displayport based on its expansion over the composition size.
   4667  CSSRect displayPort((compositionSize.width - displayPortSize.width) / 2.0f,
   4668                      (compositionSize.height - displayPortSize.height) / 2.0f,
   4669                      displayPortSize.width, displayPortSize.height);
   4670 
   4671  // Offset the displayport, depending on how fast we're moving and the
   4672  // estimated time it takes to paint, to try to minimise checkerboarding.
   4673  float paintFactor = kDefaultEstimatedPaintDurationMs;
   4674  displayPort.MoveBy(velocity * paintFactor * StaticPrefs::apz_velocity_bias());
   4675 
   4676  APZC_LOGV_FM(aFrameMetrics,
   4677               "Calculated displayport as %s from velocity %s zooming %d paint "
   4678               "time %f metrics",
   4679               ToString(displayPort).c_str(), ToString(aVelocity).c_str(),
   4680               (int)aZoomInProgress, paintFactor);
   4681 
   4682  CSSMargin cssMargins;
   4683  cssMargins.left = -displayPort.X();
   4684  cssMargins.top = -displayPort.Y();
   4685  cssMargins.right =
   4686      displayPort.Width() - compositionSize.width - cssMargins.left;
   4687  cssMargins.bottom =
   4688      displayPort.Height() - compositionSize.height - cssMargins.top;
   4689 
   4690  return cssMargins * aFrameMetrics.DisplayportPixelsPerCSSPixel();
   4691 }
   4692 
   4693 void AsyncPanZoomController::ScheduleComposite() {
   4694  if (mCompositorController) {
   4695    mCompositorController->ScheduleRenderOnCompositorThread(
   4696        wr::RenderReasons::APZ);
   4697  }
   4698 }
   4699 
   4700 void AsyncPanZoomController::ScheduleCompositeAndMaybeRepaint() {
   4701  ScheduleComposite();
   4702  RequestContentRepaint();
   4703 }
   4704 
   4705 void AsyncPanZoomController::FlushRepaintForOverscrollHandoff() {
   4706  RecursiveMutexAutoLock lock(mRecursiveMutex);
   4707  RequestContentRepaint();
   4708 }
   4709 
   4710 void AsyncPanZoomController::FlushRepaintForNewInputBlock() {
   4711  APZC_LOG("%p flushing repaint for new input block\n", this);
   4712 
   4713  RecursiveMutexAutoLock lock(mRecursiveMutex);
   4714  RequestContentRepaint();
   4715 }
   4716 
   4717 bool AsyncPanZoomController::SnapBackIfOverscrolled() {
   4718  RecursiveMutexAutoLock lock(mRecursiveMutex);
   4719  if (SnapBackIfOverscrolledForMomentum(ParentLayerPoint(0, 0))) {
   4720    return true;
   4721  }
   4722  // If we don't kick off an overscroll animation, we still need to snap to any
   4723  // nearby snap points, assuming we haven't already done so when we started
   4724  // this fling
   4725  if (mState != FLING) {
   4726    ScrollSnap(ScrollSnapFlags::IntendedEndPosition);
   4727  }
   4728  return false;
   4729 }
   4730 
   4731 bool AsyncPanZoomController::SnapBackIfOverscrolledForMomentum(
   4732    const ParentLayerPoint& aVelocity) {
   4733  RecursiveMutexAutoLock lock(mRecursiveMutex);
   4734  // It's possible that we're already in the middle of an overscroll
   4735  // animation - if so, don't start a new one.
   4736  if (IsOverscrolled() && mState != OVERSCROLL_ANIMATION) {
   4737    APZC_LOG("%p is overscrolled, starting snap-back\n", this);
   4738    mOverscrollEffect->RelieveOverscroll(aVelocity, GetOverscrollSideBits());
   4739    return true;
   4740  }
   4741  return false;
   4742 }
   4743 
   4744 bool AsyncPanZoomController::IsFlingingFast() const {
   4745  RecursiveMutexAutoLock lock(mRecursiveMutex);
   4746  if (mState == FLING && GetVelocityVector().Length() >
   4747                             StaticPrefs::apz_fling_stop_on_tap_threshold()) {
   4748    APZC_LOG("%p is moving fast\n", this);
   4749    return true;
   4750  }
   4751  return false;
   4752 }
   4753 
   4754 bool AsyncPanZoomController::IsPannable() const {
   4755  RecursiveMutexAutoLock lock(mRecursiveMutex);
   4756  return mX.CanScroll() || mY.CanScroll();
   4757 }
   4758 
   4759 bool AsyncPanZoomController::IsScrollInfoLayer() const {
   4760  RecursiveMutexAutoLock lock(mRecursiveMutex);
   4761  return Metrics().IsScrollInfoLayer();
   4762 }
   4763 
   4764 int32_t AsyncPanZoomController::GetLastTouchIdentifier() const {
   4765  RefPtr<GestureEventListener> listener = GetGestureEventListener();
   4766  return listener ? listener->GetLastTouchIdentifier() : -1;
   4767 }
   4768 
   4769 void AsyncPanZoomController::RequestContentRepaint(
   4770    RepaintUpdateType aUpdateType) {
   4771  // Reinvoke this method on the repaint thread if it's not there already. It's
   4772  // important to do this before the call to CalculatePendingDisplayPort, so
   4773  // that CalculatePendingDisplayPort uses the most recent available version of
   4774  // Metrics(). just before the paint request is dispatched to content.
   4775  RefPtr<GeckoContentController> controller = GetGeckoContentController();
   4776  if (!controller) {
   4777    return;
   4778  }
   4779  if (!controller->IsRepaintThread()) {
   4780    // Even though we want to do the actual repaint request on the repaint
   4781    // thread, we want to update the expected gecko metrics synchronously.
   4782    // Otherwise we introduce a race condition where we might read from the
   4783    // expected gecko metrics on the controller thread before or after it gets
   4784    // updated on the repaint thread, when in fact we always want the updated
   4785    // version when reading.
   4786    {  // scope lock
   4787      RecursiveMutexAutoLock lock(mRecursiveMutex);
   4788      mExpectedGeckoMetrics.UpdateFrom(Metrics());
   4789    }
   4790 
   4791    // use the local variable to resolve the function overload.
   4792    auto func =
   4793        static_cast<void (AsyncPanZoomController::*)(RepaintUpdateType)>(
   4794            &AsyncPanZoomController::RequestContentRepaint);
   4795    controller->DispatchToRepaintThread(NewRunnableMethod<RepaintUpdateType>(
   4796        "layers::AsyncPanZoomController::RequestContentRepaint", this, func,
   4797        aUpdateType));
   4798    return;
   4799  }
   4800 
   4801  MOZ_ASSERT(controller->IsRepaintThread());
   4802 
   4803  RecursiveMutexAutoLock lock(mRecursiveMutex);
   4804  ParentLayerPoint velocity = GetVelocityVector();
   4805  ScreenMargin displayportMargins = CalculatePendingDisplayPort(
   4806      Metrics(), velocity,
   4807      (mState == PINCHING || mState == ANIMATING_ZOOM) ? ZoomInProgress::Yes
   4808                                                       : ZoomInProgress::No);
   4809  Metrics().SetPaintRequestTime(TimeStamp::Now());
   4810  RequestContentRepaint(velocity, displayportMargins, aUpdateType);
   4811 }
   4812 
   4813 static CSSRect GetDisplayPortRect(const FrameMetrics& aFrameMetrics,
   4814                                  const ScreenMargin& aDisplayportMargins) {
   4815  // This computation is based on what happens in CalculatePendingDisplayPort.
   4816  // If that changes then this might need to change too.
   4817  // Note that the display port rect APZ computes is relative to the visual
   4818  // scroll offset. It's adjusted to be relative to the layout scroll offset
   4819  // when the main thread processes a repaint request (in
   4820  // APZCCallbackHelper::AdjustDisplayPortForScrollDelta()) and ultimately
   4821  // applied (in DisplayPortUtils::GetDisplayPort()) in this adjusted form.
   4822  CSSRect baseRect(aFrameMetrics.GetVisualScrollOffset(),
   4823                   aFrameMetrics.CalculateBoundedCompositedSizeInCssPixels());
   4824  baseRect.Inflate(aDisplayportMargins /
   4825                   aFrameMetrics.DisplayportPixelsPerCSSPixel());
   4826  return baseRect;
   4827 }
   4828 
   4829 void AsyncPanZoomController::RequestContentRepaint(
   4830    const ParentLayerPoint& aVelocity, const ScreenMargin& aDisplayportMargins,
   4831    RepaintUpdateType aUpdateType) {
   4832  mRecursiveMutex.AssertCurrentThreadIn();
   4833 
   4834  RefPtr<GeckoContentController> controller = GetGeckoContentController();
   4835  if (!controller) {
   4836    return;
   4837  }
   4838  MOZ_ASSERT(controller->IsRepaintThread());
   4839 
   4840  APZScrollAnimationType animationType = APZScrollAnimationType::No;
   4841  if (mAnimation) {
   4842    animationType = mAnimation->WasTriggeredByScript()
   4843                        ? APZScrollAnimationType::TriggeredByScript
   4844                        : APZScrollAnimationType::TriggeredByUserInput;
   4845  }
   4846  RepaintRequest request(Metrics(), aDisplayportMargins, aUpdateType,
   4847                         animationType, mScrollGeneration, mLastSnapTargetIds,
   4848                         IsInScrollingGesture());
   4849 
   4850  if (request.IsRootContent() && request.GetZoom() != mLastNotifiedZoom &&
   4851      mState != PINCHING && mState != ANIMATING_ZOOM) {
   4852    controller->NotifyScaleGestureComplete(
   4853        GetGuid(),
   4854        (request.GetZoom() / request.GetDevPixelsPerCSSPixel()).scale);
   4855    mLastNotifiedZoom = request.GetZoom();
   4856  }
   4857 
   4858  // If we're trying to paint what we already think is painted, discard this
   4859  // request since it's a pointless paint.
   4860  if (request.GetDisplayPortMargins().WithinEpsilonOf(
   4861          mLastPaintRequestMetrics.GetDisplayPortMargins(), EPSILON) &&
   4862      request.GetVisualScrollOffset().WithinEpsilonOf(
   4863          mLastPaintRequestMetrics.GetVisualScrollOffset(), EPSILON) &&
   4864      request.GetPresShellResolution() ==
   4865          mLastPaintRequestMetrics.GetPresShellResolution() &&
   4866      request.GetZoom() == mLastPaintRequestMetrics.GetZoom() &&
   4867      request.GetLayoutViewport().WithinEpsilonOf(
   4868          mLastPaintRequestMetrics.GetLayoutViewport(), EPSILON) &&
   4869      request.GetScrollGeneration() ==
   4870          mLastPaintRequestMetrics.GetScrollGeneration() &&
   4871      request.GetScrollUpdateType() ==
   4872          mLastPaintRequestMetrics.GetScrollUpdateType() &&
   4873      request.GetScrollAnimationType() ==
   4874          mLastPaintRequestMetrics.GetScrollAnimationType() &&
   4875      request.GetLastSnapTargetIds() ==
   4876          mLastPaintRequestMetrics.GetLastSnapTargetIds()) {
   4877    return;
   4878  }
   4879 
   4880  APZC_LOGV("%p requesting content repaint %s", this,
   4881            ToString(request).c_str());
   4882  {  // scope lock
   4883    MutexAutoLock lock(mCheckerboardEventLock);
   4884    if (mCheckerboardEvent && mCheckerboardEvent->IsRecordingTrace()) {
   4885      std::stringstream info;
   4886      info << " velocity " << aVelocity;
   4887      std::string str = info.str();
   4888      mCheckerboardEvent->UpdateRendertraceProperty(
   4889          CheckerboardEvent::RequestedDisplayPort,
   4890          GetDisplayPortRect(Metrics(), aDisplayportMargins), str);
   4891    }
   4892  }
   4893 
   4894  controller->RequestContentRepaint(request);
   4895  mExpectedGeckoMetrics.UpdateFrom(Metrics());
   4896  mLastPaintRequestMetrics = request;
   4897 
   4898  // We're holding the APZC lock here, so redispatch this so we can get
   4899  // the tree lock without the APZC lock.
   4900  controller->DispatchToRepaintThread(
   4901      NewRunnableMethod<AsyncPanZoomController*>(
   4902          "layers::APZCTreeManager::SendSubtreeTransformsToChromeMainThread",
   4903          GetApzcTreeManager(),
   4904          &APZCTreeManager::SendSubtreeTransformsToChromeMainThread, this));
   4905 }
   4906 
   4907 bool AsyncPanZoomController::UpdateAnimation(
   4908    const RecursiveMutexAutoLock& aProofOfLock, const SampleTime& aSampleTime,
   4909    nsTArray<RefPtr<Runnable>>* aOutDeferredTasks) {
   4910  AssertOnSamplerThread();
   4911 
   4912  // This function may get called multiple with the same sample time, if we
   4913  // composite multiple times at the same timestamp.
   4914  // However we only want to do one animation step per composition so we need
   4915  // to deduplicate these calls first.
   4916  // Even if there's no animation, if we have a scroll offset change pending due
   4917  // to the frame delay, we need to keep compositing.
   4918  if (mLastSampleTime == aSampleTime) {
   4919    APZC_LOGV_DETAIL(
   4920        "UpdateAnimation short-circuit, animation=%p, pending frame-delayed "
   4921        "offset=%d\n",
   4922        this, mAnimation.get(), HavePendingFrameDelayedOffset());
   4923    return !!mAnimation || HavePendingFrameDelayedOffset();
   4924  }
   4925 
   4926  // We're at a new timestamp, so advance to the next sample in the deque, if
   4927  // there is one. That one will be used for all the code that reads the
   4928  // eForCompositing transforms in this vsync interval.
   4929  AdvanceToNextSample();
   4930 
   4931  // And then create a new sample, which will be used in the *next* vsync
   4932  // interval. We do the sample at this point and not later in order to try
   4933  // and enforce one frame delay between computing the async transform and
   4934  // compositing it to the screen. This one-frame delay gives code running on
   4935  // the main thread a chance to try and respond to the scroll position change,
   4936  // so that e.g. a main-thread animation can stay in sync with user-driven
   4937  // scrolling or a compositor animation.
   4938  bool needComposite = SampleCompositedAsyncTransform(aProofOfLock);
   4939  APZC_LOGV_DETAIL("UpdateAnimation needComposite=%d mAnimation=%p\n", this,
   4940                   needComposite, mAnimation.get());
   4941 
   4942  TimeDuration sampleTimeDelta = aSampleTime - mLastSampleTime;
   4943  mLastSampleTime = aSampleTime;
   4944 
   4945  if (needComposite || mAnimation) {
   4946    // Bump the scroll generation before we call RequestContentRepaint below
   4947    // so that the RequestContentRepaint call will surely use the new
   4948    // generation.
   4949    if (APZCTreeManager* treeManagerLocal = GetApzcTreeManager()) {
   4950      mScrollGeneration = treeManagerLocal->NewAPZScrollGeneration();
   4951    }
   4952  }
   4953 
   4954  if (mAnimation) {
   4955    AutoRecordCompositorScrollUpdate csu(
   4956        this,
   4957        mAnimation->WasTriggeredByScript()
   4958            ? CompositorScrollUpdate::Source::Other
   4959            : CompositorScrollUpdate::Source::UserInteraction,
   4960        aProofOfLock);
   4961    bool continueAnimation = mAnimation->Sample(Metrics(), sampleTimeDelta);
   4962    bool wantsRepaints = mAnimation->WantsRepaints();
   4963    *aOutDeferredTasks = mAnimation->TakeDeferredTasks();
   4964    if (!continueAnimation) {
   4965      SetState(NOTHING);
   4966      if (SmoothScrollAnimation* anim = mAnimation->AsSmoothScrollAnimation();
   4967          anim && (anim->Kind() == ScrollAnimationKind::Smooth ||
   4968                   anim->Kind() == ScrollAnimationKind::SmoothMsd)) {
   4969        RecursiveMutexAutoLock lock(mRecursiveMutex);
   4970        mLastSnapTargetIds =
   4971            mAnimation->AsSmoothScrollAnimation()->TakeSnapTargetIds();
   4972      }
   4973      mAnimation = nullptr;
   4974    }
   4975    // Request a repaint at the end of the animation in case something such as a
   4976    // call to NotifyLayersUpdated was invoked during the animation and Gecko's
   4977    // current state is some intermediate point of the animation.
   4978    if (!continueAnimation || wantsRepaints) {
   4979      RequestContentRepaint();
   4980    }
   4981    needComposite = true;
   4982  }
   4983  return needComposite;
   4984 }
   4985 
   4986 AsyncTransformComponentMatrix AsyncPanZoomController::GetOverscrollTransform(
   4987    AsyncTransformConsumer aMode) const {
   4988  RecursiveMutexAutoLock lock(mRecursiveMutex);
   4989  AutoApplyAsyncTestAttributes testAttributeApplier(this, lock);
   4990 
   4991  if (aMode == eForCompositing && mScrollMetadata.IsApzForceDisabled()) {
   4992    return AsyncTransformComponentMatrix();
   4993  }
   4994 
   4995  if (!IsPhysicallyOverscrolled()) {
   4996    return AsyncTransformComponentMatrix();
   4997  }
   4998 
   4999  // The overscroll effect is a simple translation by the overscroll offset.
   5000  ParentLayerPoint overscrollOffset(-mX.GetOverscroll(), -mY.GetOverscroll());
   5001  return AsyncTransformComponentMatrix().PostTranslate(overscrollOffset.x,
   5002                                                       overscrollOffset.y, 0);
   5003 }
   5004 
   5005 bool AsyncPanZoomController::AdvanceAnimations(const SampleTime& aSampleTime) {
   5006  AssertOnSamplerThread();
   5007 
   5008  // Don't send any state-change notifications until the end of the function,
   5009  // because we may go through some intermediate states while we finish
   5010  // animations and start new ones.
   5011  ThreadSafeStateChangeNotificationBlocker blocker(this);
   5012 
   5013  // The eventual return value of this function. The compositor needs to know
   5014  // whether or not to advance by a frame as soon as it can. For example, if a
   5015  // fling is happening, it has to keep compositing so that the animation is
   5016  // smooth. If an animation frame is requested, it is the compositor's
   5017  // responsibility to schedule a composite.
   5018  bool requestAnimationFrame = false;
   5019  nsTArray<RefPtr<Runnable>> deferredTasks;
   5020 
   5021  {
   5022    RecursiveMutexAutoLock lock(mRecursiveMutex);
   5023    {  // scope lock
   5024      CSSRect visibleRect = GetVisibleRect(lock);
   5025      MutexAutoLock lock2(mCheckerboardEventLock);
   5026      // Update RendertraceProperty before UpdateAnimation() call, since
   5027      // the UpdateAnimation() updates effective ScrollOffset for next frame
   5028      // if APZFrameDelay is enabled.
   5029      if (mCheckerboardEvent) {
   5030        mCheckerboardEvent->UpdateRendertraceProperty(
   5031            CheckerboardEvent::UserVisible, visibleRect);
   5032      }
   5033    }
   5034 
   5035    requestAnimationFrame = UpdateAnimation(lock, aSampleTime, &deferredTasks);
   5036  }
   5037  // Execute any deferred tasks queued up by mAnimation's Sample() (called by
   5038  // UpdateAnimation()). This needs to be done after the monitor is released
   5039  // since the tasks are allowed to call APZCTreeManager methods which can grab
   5040  // the tree lock.
   5041  // Move the ThreadSafeStateChangeNotificationBlocker into the task so that
   5042  // notifications continue to be blocked until the deferred tasks have run.
   5043  // Must be the ThreadSafe variant of StateChangeNotificationBlocker to
   5044  // guarantee that the APZ is alive until the deferred tasks are done
   5045  if (!deferredTasks.IsEmpty()) {
   5046    APZThreadUtils::RunOnControllerThread(NS_NewRunnableFunction(
   5047        "AsyncPanZoomController::AdvanceAnimations deferred tasks",
   5048        [blocker = std::move(blocker),
   5049         deferredTasks = std::move(deferredTasks)]() {
   5050          for (uint32_t i = 0; i < deferredTasks.Length(); ++i) {
   5051            deferredTasks[i]->Run();
   5052          }
   5053        }));
   5054  }
   5055 
   5056  // If any of the deferred tasks starts a new animation, it will request a
   5057  // new composite directly, so we can just return requestAnimationFrame here.
   5058  return requestAnimationFrame;
   5059 }
   5060 
   5061 ParentLayerPoint AsyncPanZoomController::GetCurrentAsyncScrollOffset(
   5062    AsyncTransformConsumer aMode) const {
   5063  RecursiveMutexAutoLock lock(mRecursiveMutex);
   5064  AutoApplyAsyncTestAttributes testAttributeApplier(this, lock);
   5065 
   5066  return GetEffectiveScrollOffset(aMode, lock) * GetEffectiveZoom(aMode, lock);
   5067 }
   5068 
   5069 CSSRect AsyncPanZoomController::GetCurrentAsyncVisualViewport(
   5070    AsyncTransformConsumer aMode) const {
   5071  RecursiveMutexAutoLock lock(mRecursiveMutex);
   5072  AutoApplyAsyncTestAttributes testAttributeApplier(this, lock);
   5073 
   5074  return CSSRect(
   5075      GetEffectiveScrollOffset(aMode, lock),
   5076      FrameMetrics::CalculateCompositedSizeInCssPixels(
   5077          Metrics().GetCompositionBounds(), GetEffectiveZoom(aMode, lock)));
   5078 }
   5079 
   5080 AsyncTransform AsyncPanZoomController::GetCurrentAsyncTransform(
   5081    AsyncTransformConsumer aMode, AsyncTransformComponents aComponents,
   5082    std::size_t aSampleIndex) const {
   5083  RecursiveMutexAutoLock lock(mRecursiveMutex);
   5084  AutoApplyAsyncTestAttributes testAttributeApplier(this, lock);
   5085 
   5086  CSSToParentLayerScale effectiveZoom;
   5087  if (aComponents.contains(AsyncTransformComponent::eVisual)) {
   5088    effectiveZoom = GetEffectiveZoom(aMode, lock, aSampleIndex);
   5089  } else {
   5090    effectiveZoom =
   5091        Metrics().LayersPixelsPerCSSPixel() * LayerToParentLayerScale(1.0f);
   5092  }
   5093 
   5094  LayerToParentLayerScale compositedAsyncZoom =
   5095      effectiveZoom / Metrics().LayersPixelsPerCSSPixel();
   5096 
   5097  ParentLayerPoint translation;
   5098  if (aComponents.contains(AsyncTransformComponent::eVisual)) {
   5099    // There is no "lastPaintVisualOffset" to subtract here; the visual offset
   5100    // is entirely async.
   5101 
   5102    CSSPoint currentVisualOffset =
   5103        GetEffectiveScrollOffset(aMode, lock, aSampleIndex) -
   5104        GetEffectiveLayoutViewport(aMode, lock, aSampleIndex).TopLeft();
   5105 
   5106    translation += currentVisualOffset * effectiveZoom;
   5107  }
   5108  if (aComponents.contains(AsyncTransformComponent::eLayout)) {
   5109    CSSPoint lastPaintLayoutOffset;
   5110    if (mLastContentPaintMetrics.IsScrollable()) {
   5111      lastPaintLayoutOffset = mLastContentPaintMetrics.GetLayoutScrollOffset();
   5112    }
   5113 
   5114    CSSPoint currentLayoutOffset =
   5115        GetEffectiveLayoutViewport(aMode, lock, aSampleIndex).TopLeft();
   5116 
   5117    translation +=
   5118        (currentLayoutOffset - lastPaintLayoutOffset) * effectiveZoom;
   5119  }
   5120 
   5121  return AsyncTransform(compositedAsyncZoom, -translation);
   5122 }
   5123 
   5124 AsyncTransformComponentMatrix
   5125 AsyncPanZoomController::GetAsyncTransformForInputTransformation(
   5126    AsyncTransformComponents aComponents, LayersId aForLayersId) const {
   5127  AsyncTransformComponentMatrix result;
   5128  // If we are the root, and |aForLayersId| is different from our LayersId,
   5129  // |aForLayersId| must be in a remote subdocument.
   5130  if (IsRootContent() && aForLayersId != GetLayersId()) {
   5131    result =
   5132        ViewAs<AsyncTransformComponentMatrix>(GetPaintedResolutionTransform());
   5133  }
   5134  // Order of transforms: the painted resolution (if any) applies first, and
   5135  // any async transform on top of that.
   5136  result = result * AsyncTransformComponentMatrix(GetCurrentAsyncTransform(
   5137                        eForEventHandling, aComponents));
   5138  // The overscroll transform is considered part of the layout component of
   5139  // the async transform, because it should not apply to fixed content.
   5140  if (aComponents.contains(AsyncTransformComponent::eLayout)) {
   5141    result = result * GetOverscrollTransform(eForEventHandling);
   5142  }
   5143  return result;
   5144 }
   5145 
   5146 Matrix4x4 AsyncPanZoomController::GetPaintedResolutionTransform() const {
   5147  RecursiveMutexAutoLock lock(mRecursiveMutex);
   5148  MOZ_ASSERT(IsRootContent());
   5149  float resolution = mLastContentPaintMetrics.GetPresShellResolution();
   5150  return Matrix4x4::Scaling(resolution, resolution, 1.f);
   5151 }
   5152 
   5153 LayoutDeviceToParentLayerScale AsyncPanZoomController::GetCurrentPinchZoomScale(
   5154    AsyncTransformConsumer aMode) const {
   5155  RecursiveMutexAutoLock lock(mRecursiveMutex);
   5156  AutoApplyAsyncTestAttributes testAttributeApplier(this, lock);
   5157  CSSToParentLayerScale scale = GetEffectiveZoom(aMode, lock);
   5158  return scale / Metrics().GetDevPixelsPerCSSPixel();
   5159 }
   5160 
   5161 AutoTArray<wr::SampledScrollOffset, 2>
   5162 AsyncPanZoomController::GetSampledScrollOffsets() const {
   5163  AssertOnSamplerThread();
   5164 
   5165  RecursiveMutexAutoLock lock(mRecursiveMutex);
   5166 
   5167  const AsyncTransformComponents asyncTransformComponents =
   5168      GetZoomAnimationId()
   5169          ? AsyncTransformComponents{AsyncTransformComponent::eLayout}
   5170          : LayoutAndVisual;
   5171 
   5172  // If layerTranslation includes only the layout component of the async
   5173  // transform then it has not been scaled by the async zoom, so we want to
   5174  // divide it by the resolution. If layerTranslation includes the visual
   5175  // component, then we should use the pinch zoom scale, which includes the
   5176  // async zoom. However, we only use LayoutAndVisual for non-zoomable APZCs,
   5177  // so it makes no difference.
   5178  LayoutDeviceToParentLayerScale resolution =
   5179      GetCumulativeResolution() * LayerToParentLayerScale(1.0f);
   5180 
   5181  AutoTArray<wr::SampledScrollOffset, 2> sampledOffsets;
   5182 
   5183  for (std::deque<SampledAPZCState>::size_type index = 0;
   5184       index < mSampledState.size(); index++) {
   5185    ParentLayerPoint layerTranslation =
   5186        GetCurrentAsyncTransform(AsyncPanZoomController::eForCompositing,
   5187                                 asyncTransformComponents, index)
   5188            .mTranslation;
   5189 
   5190    // Include the overscroll transform here in scroll offsets transform
   5191    // to ensure that we do not overscroll fixed content.
   5192    layerTranslation =
   5193        GetOverscrollTransform(AsyncPanZoomController::eForCompositing)
   5194            .TransformPoint(layerTranslation);
   5195    // The positive translation means the painted content is supposed to
   5196    // move down (or to the right), and that corresponds to a reduction in
   5197    // the scroll offset. Since we are effectively giving WR the async
   5198    // scroll delta here, we want to negate the translation.
   5199    LayoutDevicePoint asyncScrollDelta = -layerTranslation / resolution;
   5200    sampledOffsets.AppendElement(wr::SampledScrollOffset{
   5201        wr::ToLayoutVector2D(asyncScrollDelta),
   5202        wr::ToWrAPZScrollGeneration(mSampledState[index].Generation())});
   5203  }
   5204 
   5205  return sampledOffsets;
   5206 }
   5207 
   5208 bool AsyncPanZoomController::SuppressAsyncScrollOffset() const {
   5209  return mScrollMetadata.IsApzForceDisabled() ||
   5210         (Metrics().IsMinimalDisplayPort() &&
   5211          StaticPrefs::apz_prefer_jank_minimal_displayports());
   5212 }
   5213 
   5214 CSSRect AsyncPanZoomController::GetEffectiveLayoutViewport(
   5215    AsyncTransformConsumer aMode, const RecursiveMutexAutoLock& aProofOfLock,
   5216    std::size_t aSampleIndex) const {
   5217  if (aMode == eForCompositing && SuppressAsyncScrollOffset()) {
   5218    return mLastContentPaintMetrics.GetLayoutViewport();
   5219  }
   5220  if (aMode == eForCompositing) {
   5221    return mSampledState[aSampleIndex].GetLayoutViewport();
   5222  }
   5223  return Metrics().GetLayoutViewport();
   5224 }
   5225 
   5226 CSSPoint AsyncPanZoomController::GetEffectiveScrollOffset(
   5227    AsyncTransformConsumer aMode, const RecursiveMutexAutoLock& aProofOfLock,
   5228    std::size_t aSampleIndex) const {
   5229  if (aMode == eForCompositing && SuppressAsyncScrollOffset()) {
   5230    return mLastContentPaintMetrics.GetVisualScrollOffset();
   5231  }
   5232  if (aMode == eForCompositing) {
   5233    return mSampledState[aSampleIndex].GetVisualScrollOffset();
   5234  }
   5235  return Metrics().GetVisualScrollOffset();
   5236 }
   5237 
   5238 CSSToParentLayerScale AsyncPanZoomController::GetEffectiveZoom(
   5239    AsyncTransformConsumer aMode, const RecursiveMutexAutoLock& aProofOfLock,
   5240    std::size_t aSampleIndex) const {
   5241  if (aMode == eForCompositing && SuppressAsyncScrollOffset()) {
   5242    return mLastContentPaintMetrics.GetZoom();
   5243  }
   5244  if (aMode == eForCompositing) {
   5245    return mSampledState[aSampleIndex].GetZoom();
   5246  }
   5247  return Metrics().GetZoom();
   5248 }
   5249 
   5250 void AsyncPanZoomController::AdvanceToNextSample() {
   5251  AssertOnSamplerThread();
   5252  RecursiveMutexAutoLock lock(mRecursiveMutex);
   5253  // Always keep at least one state in mSampledState.
   5254  if (mSampledState.size() > 1) {
   5255    mSampledState.pop_front();
   5256  }
   5257 }
   5258 
   5259 bool AsyncPanZoomController::HavePendingFrameDelayedOffset() const {
   5260  AssertOnSamplerThread();
   5261  RecursiveMutexAutoLock lock(mRecursiveMutex);
   5262 
   5263  const bool nextFrameWillChange =
   5264      mSampledState.size() >= 2 && mSampledState[0] != mSampledState[1];
   5265  const bool frameAfterThatWillChange =
   5266      mSampledState.back() != SampledAPZCState(Metrics());
   5267  return nextFrameWillChange || frameAfterThatWillChange;
   5268 }
   5269 
   5270 bool AsyncPanZoomController::SampleCompositedAsyncTransform(
   5271    const RecursiveMutexAutoLock& aProofOfLock) {
   5272  MOZ_ASSERT(mSampledState.size() <= 2);
   5273  bool sampleChanged = (mSampledState.back() != SampledAPZCState(Metrics()));
   5274  mSampledState.emplace_back(
   5275      Metrics(), std::move(mScrollPayload), mScrollGeneration,
   5276      // Will consume mUpdatesSinceLastSample and leave it empty
   5277      std::move(mUpdatesSinceLastSample));
   5278  return sampleChanged;
   5279 }
   5280 
   5281 void AsyncPanZoomController::ResampleCompositedAsyncTransform(
   5282    const RecursiveMutexAutoLock& aProofOfLock) {
   5283  // This only gets called during testing situations, so the fact that this
   5284  // drops the scroll payload from mSampledState.front() is not really a
   5285  // problem.
   5286  if (APZCTreeManager* treeManagerLocal = GetApzcTreeManager()) {
   5287    mScrollGeneration = treeManagerLocal->NewAPZScrollGeneration();
   5288  }
   5289  mSampledState.front() = SampledAPZCState(
   5290      Metrics(), {}, mScrollGeneration,
   5291      // Will consume mUpdatesSinceLastSample and leave it empty
   5292      std::move(mUpdatesSinceLastSample));
   5293 }
   5294 
   5295 void AsyncPanZoomController::ApplyAsyncTestAttributes(
   5296    const RecursiveMutexAutoLock& aProofOfLock) {
   5297  if (mTestAttributeAppliers == 0) {
   5298    if (mTestAsyncScrollOffset != CSSPoint() ||
   5299        mTestAsyncZoom != LayerToParentLayerScale()) {
   5300      // TODO Currently we update Metrics() and resample, which will cause
   5301      // the very latest user input to get immediately captured in the sample,
   5302      // and may defeat our attempt at "frame delay" (i.e. delaying the user
   5303      // input from affecting composition by one frame).
   5304      // Instead, maybe we should just apply the mTest* stuff directly to
   5305      // mSampledState.front(). We can even save/restore that SampledAPZCState
   5306      // instance in the AutoApplyAsyncTestAttributes instead of Metrics().
   5307      Metrics().ZoomBy(mTestAsyncZoom.scale);
   5308      CSSPoint asyncScrollPosition = Metrics().GetVisualScrollOffset();
   5309      CSSPoint requestedPoint =
   5310          asyncScrollPosition + this->mTestAsyncScrollOffset;
   5311      CSSPoint clampedPoint =
   5312          Metrics().CalculateScrollRange().ClampPoint(requestedPoint);
   5313      CSSPoint difference = mTestAsyncScrollOffset - clampedPoint;
   5314 
   5315      ScrollByAndClamp(mTestAsyncScrollOffset);
   5316 
   5317      if (StaticPrefs::apz_overscroll_test_async_scroll_offset_enabled()) {
   5318        ParentLayerPoint overscroll = difference * Metrics().GetZoom();
   5319        OverscrollBy(overscroll);
   5320      }
   5321      ResampleCompositedAsyncTransform(aProofOfLock);
   5322    }
   5323  }
   5324  ++mTestAttributeAppliers;
   5325 }
   5326 
   5327 void AsyncPanZoomController::UnapplyAsyncTestAttributes(
   5328    const RecursiveMutexAutoLock& aProofOfLock,
   5329    const FrameMetrics& aPrevFrameMetrics,
   5330    const ParentLayerPoint& aPrevOverscroll) {
   5331  MOZ_ASSERT(mTestAttributeAppliers >= 1);
   5332  --mTestAttributeAppliers;
   5333  if (mTestAttributeAppliers == 0) {
   5334    if (mTestAsyncScrollOffset != CSSPoint() ||
   5335        mTestAsyncZoom != LayerToParentLayerScale()) {
   5336      Metrics() = aPrevFrameMetrics;
   5337      RestoreOverscrollAmount(aPrevOverscroll);
   5338      ResampleCompositedAsyncTransform(aProofOfLock);
   5339    }
   5340  }
   5341 }
   5342 
   5343 Matrix4x4 AsyncPanZoomController::GetTransformToLastDispatchedPaint(
   5344    const AsyncTransformComponents& aComponents, LayersId aForLayersId) const {
   5345  RecursiveMutexAutoLock lock(mRecursiveMutex);
   5346  CSSPoint componentOffset;
   5347 
   5348  // The computation of the componentOffset should roughly be the negation
   5349  // of the translation in GetCurrentAsyncTransform() with the expected
   5350  // gecko metrics substituted for the effective scroll offsets.
   5351  if (aComponents.contains(AsyncTransformComponent::eVisual)) {
   5352    componentOffset += mExpectedGeckoMetrics.GetLayoutScrollOffset() -
   5353                       mExpectedGeckoMetrics.GetVisualScrollOffset();
   5354  }
   5355 
   5356  if (aComponents.contains(AsyncTransformComponent::eLayout)) {
   5357    CSSPoint lastPaintLayoutOffset;
   5358 
   5359    if (mLastContentPaintMetrics.IsScrollable()) {
   5360      lastPaintLayoutOffset = mLastContentPaintMetrics.GetLayoutScrollOffset();
   5361    }
   5362 
   5363    componentOffset +=
   5364        lastPaintLayoutOffset - mExpectedGeckoMetrics.GetLayoutScrollOffset();
   5365  }
   5366 
   5367  LayerPoint scrollChange = componentOffset *
   5368                            mLastContentPaintMetrics.GetDevPixelsPerCSSPixel() *
   5369                            mLastContentPaintMetrics.GetCumulativeResolution();
   5370 
   5371  // We're interested in the async zoom change. Factor out the content scale
   5372  // that may change when dragging the window to a monitor with a different
   5373  // content scale.
   5374  LayoutDeviceToParentLayerScale lastContentZoom =
   5375      mLastContentPaintMetrics.GetZoom() /
   5376      mLastContentPaintMetrics.GetDevPixelsPerCSSPixel();
   5377  LayoutDeviceToParentLayerScale lastDispatchedZoom =
   5378      mExpectedGeckoMetrics.GetZoom() /
   5379      mExpectedGeckoMetrics.GetDevPixelsPerCSSPixel();
   5380  float zoomChange = 1.0;
   5381  if (aComponents.contains(AsyncTransformComponent::eVisual) &&
   5382      lastDispatchedZoom != LayoutDeviceToParentLayerScale(0)) {
   5383    zoomChange = lastContentZoom.scale / lastDispatchedZoom.scale;
   5384  }
   5385  Matrix4x4 result;
   5386  // If we are the root, and |aForLayersId| is different from our LayersId,
   5387  // |aForLayersId| must be in a remote subdocument.
   5388  if (IsRootContent() && aForLayersId != GetLayersId()) {
   5389    result = GetPaintedResolutionTransform();
   5390  }
   5391  // Order of transforms: the painted resolution (if any) applies first, and
   5392  // any async transform on top of that.
   5393  return result * Matrix4x4::Translation(scrollChange.x, scrollChange.y, 0)
   5394                      .PostScale(zoomChange, zoomChange, 1);
   5395 }
   5396 
   5397 CSSRect AsyncPanZoomController::GetVisibleRect(
   5398    const RecursiveMutexAutoLock& aProofOfLock) const {
   5399  AutoApplyAsyncTestAttributes testAttributeApplier(this, aProofOfLock);
   5400  CSSPoint currentScrollOffset = GetEffectiveScrollOffset(
   5401      AsyncPanZoomController::eForCompositing, aProofOfLock);
   5402  CSSRect visible = CSSRect(currentScrollOffset,
   5403                            Metrics().CalculateCompositedSizeInCssPixels());
   5404  return visible;
   5405 }
   5406 
   5407 static CSSRect GetPaintedRect(const FrameMetrics& aFrameMetrics) {
   5408  CSSRect displayPort = aFrameMetrics.GetDisplayPort();
   5409  if (displayPort.IsEmpty()) {
   5410    // Fallback to use the viewport if the diplayport hasn't been set.
   5411    // This situation often happens non-scrollable iframe's root scroller in
   5412    // Fission.
   5413    return aFrameMetrics.GetVisualViewport();
   5414  }
   5415 
   5416  return displayPort + aFrameMetrics.GetLayoutScrollOffset();
   5417 }
   5418 
   5419 uint32_t AsyncPanZoomController::GetCheckerboardMagnitude(
   5420    const ParentLayerRect& aClippedCompositionBounds) const {
   5421  RecursiveMutexAutoLock lock(mRecursiveMutex);
   5422 
   5423  CSSRect painted = GetPaintedRect(mLastContentPaintMetrics);
   5424  painted.Inflate(CSSMargin::FromAppUnits(
   5425      nsMargin(1, 1, 1, 1)));  // fuzz for rounding error
   5426 
   5427  CSSRect visible = GetVisibleRect(lock);  // relative to scrolled frame origin
   5428  if (visible.IsEmpty() || painted.Contains(visible)) {
   5429    // early-exit if we're definitely not checkerboarding
   5430    return 0;
   5431  }
   5432 
   5433  // aClippedCompositionBounds and Metrics().GetCompositionBounds() are both
   5434  // relative to the layer tree origin.
   5435  // The "*RelativeToItself*" variables are relative to the comp bounds origin
   5436  ParentLayerRect visiblePartOfCompBoundsRelativeToItself =
   5437      aClippedCompositionBounds - Metrics().GetCompositionBounds().TopLeft();
   5438  CSSRect visiblePartOfCompBoundsRelativeToItselfInCssSpace;
   5439  if (Metrics().GetZoom() != CSSToParentLayerScale(0)) {
   5440    visiblePartOfCompBoundsRelativeToItselfInCssSpace =
   5441        (visiblePartOfCompBoundsRelativeToItself / Metrics().GetZoom());
   5442  }
   5443 
   5444  // This one is relative to the scrolled frame origin, same as `visible`
   5445  CSSRect visiblePartOfCompBoundsInCssSpace =
   5446      visiblePartOfCompBoundsRelativeToItselfInCssSpace + visible.TopLeft();
   5447 
   5448  visible = visible.Intersect(visiblePartOfCompBoundsInCssSpace);
   5449 
   5450  CSSIntRegion checkerboard;
   5451  // Round so as to minimize checkerboarding; if we're only showing fractional
   5452  // pixels of checkerboarding it's not really worth counting
   5453  checkerboard.Sub(RoundedIn(visible), RoundedOut(painted));
   5454  uint32_t area = checkerboard.Area();
   5455  if (area) {
   5456    APZC_LOG_FM(Metrics(),
   5457                "%p is currently checkerboarding (painted %s visible %s)", this,
   5458                ToString(painted).c_str(), ToString(visible).c_str());
   5459  }
   5460  return area;
   5461 }
   5462 
   5463 void AsyncPanZoomController::ReportCheckerboard(
   5464    const SampleTime& aSampleTime,
   5465    const ParentLayerRect& aClippedCompositionBounds) {
   5466  if (mLastCheckerboardReport == aSampleTime) {
   5467    // This function will get called multiple times for each APZC on a single
   5468    // composite (once for each layer it is attached to). Only report the
   5469    // checkerboard once per composite though.
   5470    return;
   5471  }
   5472  mLastCheckerboardReport = aSampleTime;
   5473 
   5474  bool recordTrace = StaticPrefs::apz_record_checkerboarding();
   5475  bool forTelemetry = Telemetry::CanRecordBase();
   5476  uint32_t magnitude = GetCheckerboardMagnitude(aClippedCompositionBounds);
   5477 
   5478  // IsInTransformingState() acquires the APZC lock and thus needs to
   5479  // be called before acquiring mCheckerboardEventLock.
   5480  bool inTransformingState = IsInTransformingState();
   5481 
   5482  MutexAutoLock lock(mCheckerboardEventLock);
   5483  if (!mCheckerboardEvent && (recordTrace || forTelemetry)) {
   5484    mCheckerboardEvent = MakeUnique<CheckerboardEvent>(recordTrace);
   5485  }
   5486  mPotentialCheckerboardTracker.InTransform(inTransformingState,
   5487                                            recordTrace || forTelemetry);
   5488  if (magnitude) {
   5489    mPotentialCheckerboardTracker.CheckerboardSeen();
   5490  }
   5491  UpdateCheckerboardEvent(lock, magnitude);
   5492 }
   5493 
   5494 void AsyncPanZoomController::UpdateCheckerboardEvent(
   5495    const MutexAutoLock& aProofOfLock, uint32_t aMagnitude) {
   5496  if (mCheckerboardEvent && mCheckerboardEvent->RecordFrameInfo(aMagnitude)) {
   5497    // This checkerboard event is done. Report some metrics to telemetry.
   5498    mozilla::glean::gfx_checkerboard::severity.AccumulateSingleSample(
   5499        mCheckerboardEvent->GetSeverity());
   5500    mozilla::glean::gfx_checkerboard::peak_pixel_count.AccumulateSingleSample(
   5501        mCheckerboardEvent->GetPeak());
   5502    mozilla::glean::gfx_checkerboard::duration.AccumulateRawDuration(
   5503        mCheckerboardEvent->GetDuration());
   5504 
   5505    // mCheckerboardEvent only gets created if we are supposed to record
   5506    // telemetry so we always pass true for aRecordTelemetry.
   5507    mPotentialCheckerboardTracker.CheckerboardDone(
   5508        /* aRecordTelemetry = */ true);
   5509 
   5510    if (StaticPrefs::apz_record_checkerboarding()) {
   5511      // if the pref is enabled, also send it to the storage class. it may be
   5512      // chosen for public display on about:checkerboard, the hall of fame for
   5513      // checkerboard events.
   5514      uint32_t severity = mCheckerboardEvent->GetSeverity();
   5515      std::string log = mCheckerboardEvent->GetLog();
   5516      CheckerboardEventStorage::Report(severity, log);
   5517    }
   5518    mCheckerboardEvent = nullptr;
   5519  }
   5520 }
   5521 
   5522 void AsyncPanZoomController::FlushActiveCheckerboardReport() {
   5523  MutexAutoLock lock(mCheckerboardEventLock);
   5524  // Pretend like we got a frame with 0 pixels checkerboarded. This will
   5525  // terminate the checkerboard event and flush it out
   5526  UpdateCheckerboardEvent(lock, 0);
   5527 }
   5528 
   5529 void AsyncPanZoomController::NotifyLayersUpdated(
   5530    const ScrollMetadata& aScrollMetadata,
   5531    LayersUpdateFlags aLayersUpdateFlags) {
   5532  AssertOnUpdaterThread();
   5533 
   5534  RecursiveMutexAutoLock lock(mRecursiveMutex);
   5535  bool isDefault = mScrollMetadata.IsDefault();
   5536 
   5537  const FrameMetrics& aLayerMetrics = aScrollMetadata.GetMetrics();
   5538 
   5539  if ((aScrollMetadata == mLastContentPaintMetadata) && !isDefault) {
   5540    // No new information here, skip it.
   5541    APZC_LOGV("%p NotifyLayersUpdated short-circuit\n", this);
   5542    return;
   5543  }
   5544 
   5545  // FIXME: CompositorScrollUpdate::Source::Other is not accurate for every
   5546  //        change made by NotifyLayersUpdated. We may need to track different
   5547  //        sources for different ScrollPositionUpdates.
   5548  AutoRecordCompositorScrollUpdate updater(
   5549      this, CompositorScrollUpdate::Source::Other, lock);
   5550 
   5551  // If the Metrics scroll offset is different from the last scroll offset
   5552  // that the main-thread sent us, then we know that the user has been doing
   5553  // something that triggers a scroll. This check is the APZ equivalent of the
   5554  // check on the main-thread at
   5555  // https://hg.mozilla.org/mozilla-central/file/97a52326b06a/layout/generic/nsGfxScrollFrame.cpp#l4050
   5556  // There is code below (the use site of userScrolled) that prevents a
   5557  // restored- scroll-position update from overwriting a user scroll, again
   5558  // equivalent to how the main thread code does the same thing.
   5559  // XXX Suspicious comparison between layout and visual scroll offsets.
   5560  // This may not do the right thing when we're zoomed in.
   5561  CSSPoint lastScrollOffset = mLastContentPaintMetrics.GetLayoutScrollOffset();
   5562  bool userScrolled =
   5563      !FuzzyEqualsCoordinate(Metrics().GetVisualScrollOffset().x,
   5564                             lastScrollOffset.x) ||
   5565      !FuzzyEqualsCoordinate(Metrics().GetVisualScrollOffset().y,
   5566                             lastScrollOffset.y);
   5567 
   5568  if (aScrollMetadata.DidContentGetPainted()) {
   5569    mLastContentPaintMetadata = aScrollMetadata;
   5570  }
   5571 
   5572  mScrollMetadata.SetScrollParentId(aScrollMetadata.GetScrollParentId());
   5573  APZC_LOGV_FM(aLayerMetrics,
   5574               "%p got a NotifyLayersUpdated with mIsFirstPaint=%d, "
   5575               "mThisLayerTreeUpdated=%d",
   5576               this, aLayersUpdateFlags.mIsFirstPaint,
   5577               aLayersUpdateFlags.mThisLayerTreeUpdated);
   5578 
   5579  {  // scope lock
   5580    MutexAutoLock lock(mCheckerboardEventLock);
   5581    if (mCheckerboardEvent && mCheckerboardEvent->IsRecordingTrace()) {
   5582      std::string str;
   5583      if (aLayersUpdateFlags.mThisLayerTreeUpdated) {
   5584        if (!aLayerMetrics.GetPaintRequestTime().IsNull()) {
   5585          // Note that we might get the paint request time as non-null, but with
   5586          // mThisLayerTreeUpdated false. That can happen if we get a layer
   5587          // transaction from a different process right after we get the layer
   5588          // transaction with mThisLayerTreeUpdated == true. In this case we
   5589          // want to ignore the paint request time because it was already dumped
   5590          // in the previous layer transaction.
   5591          TimeDuration paintTime =
   5592              TimeStamp::Now() - aLayerMetrics.GetPaintRequestTime();
   5593          std::stringstream info;
   5594          info << " painttime " << paintTime.ToMilliseconds();
   5595          str = info.str();
   5596        } else {
   5597          // This might be indicative of a wasted paint particularly if it
   5598          // happens during a checkerboard event.
   5599          str = " (this layertree updated)";
   5600        }
   5601      }
   5602      mCheckerboardEvent->UpdateRendertraceProperty(
   5603          CheckerboardEvent::Page, aLayerMetrics.GetScrollableRect());
   5604      mCheckerboardEvent->UpdateRendertraceProperty(
   5605          CheckerboardEvent::PaintedDisplayPort, GetPaintedRect(aLayerMetrics),
   5606          str);
   5607    }
   5608  }
   5609 
   5610  // The main thread may send us a visual scroll offset update. This is
   5611  // different from a layout viewport offset update in that the layout viewport
   5612  // offset is limited to the layout scroll range, while the visual viewport
   5613  // offset is not.
   5614  // However, there are some conditions in which the layout update will clobber
   5615  // the visual update, and we want to ignore the visual update in those cases.
   5616  // This variable tracks that.
   5617  bool ignoreVisualUpdate = false;
   5618 
   5619  // TODO if we're in a drag and scrollOffsetUpdated is set then we want to
   5620  // ignore it
   5621 
   5622  bool needContentRepaint = false;
   5623  RepaintUpdateType contentRepaintType = RepaintUpdateType::eNone;
   5624  bool viewportSizeUpdated = false;
   5625  bool needToReclampScroll = false;
   5626 
   5627  if ((aLayersUpdateFlags.mIsFirstPaint &&
   5628       aLayersUpdateFlags.mThisLayerTreeUpdated) ||
   5629      isDefault || Metrics().IsRootContent() != aLayerMetrics.IsRootContent()) {
   5630    if (Metrics().IsRootContent() && !aLayerMetrics.IsRootContent()) {
   5631      // We only support zooming on root content APZCs
   5632      SetZoomAnimationId(Nothing());
   5633    }
   5634 
   5635    // Initialize our internal state to something sane when the content
   5636    // that was just painted is something we knew nothing about previously
   5637    CancelAnimation();
   5638 
   5639    // Keep our existing scroll generation, if there are scroll updates. In this
   5640    // case we'll update our scroll generation. If there are no scroll updates,
   5641    // take the generation from the incoming metrics. Bug 1662019 will simplify
   5642    // this later.
   5643    ScrollGeneration oldScrollGeneration = Metrics().GetScrollGeneration();
   5644    CSSPoint oldLayoutScrollOffset = Metrics().GetLayoutScrollOffset();
   5645    CSSPoint oldVisualScrollOffset = Metrics().GetVisualScrollOffset();
   5646    mScrollMetadata = aScrollMetadata;
   5647    if (!aScrollMetadata.GetScrollUpdates().IsEmpty()) {
   5648      Metrics().SetScrollGeneration(oldScrollGeneration);
   5649      // Keep existing scroll offsets only if it's not default metrics.
   5650      //
   5651      // NOTE: The above scroll generation is used to tell whether we need to
   5652      // apply the scroll updates or not so that the old generation needs to be
   5653      // preserved. Whereas the old scroll offsets don't need to be preserved in
   5654      // the case of default since the new metrics have valid scroll offsets on
   5655      // the main-thread.
   5656      //
   5657      // Bug 1978682: In the case of default metrics, the original layout/visual
   5658      // scroll offsets on the main-thread (e.g the
   5659      // ScrollPositionUpdate::mSource in the case of relative update) need to
   5660      // be reflected to this new APZC because the first ScrollPositionUpdate is
   5661      // supposed to be applied upon the original offsets.
   5662      if (!isDefault) {
   5663        Metrics().SetLayoutScrollOffset(oldLayoutScrollOffset);
   5664        Metrics().SetVisualScrollOffset(oldVisualScrollOffset);
   5665      }
   5666    }
   5667 
   5668    mExpectedGeckoMetrics.UpdateFrom(aLayerMetrics);
   5669 
   5670    for (auto& sampledState : mSampledState) {
   5671      sampledState.UpdateScrollProperties(Metrics());
   5672      sampledState.UpdateZoomProperties(Metrics());
   5673    }
   5674 
   5675    if (aLayerMetrics.HasNonZeroDisplayPortMargins()) {
   5676      // A non-zero display port margin here indicates a displayport has
   5677      // been set by a previous APZC for the content at this guid. The
   5678      // scrollable rect may have changed since then, making the margins
   5679      // wrong, so we need to calculate a new display port.
   5680      // It is important that we request a repaint here only when we need to
   5681      // otherwise we will end up setting a display port on every frame that
   5682      // gets a view id.
   5683      APZC_LOG("%p detected non-empty margins which probably need updating\n",
   5684               this);
   5685      needContentRepaint = true;
   5686    }
   5687 
   5688    APZC_LOG("%p first-paint at scroll position %s\n", this,
   5689             ToString(Metrics().GetVisualScrollOffset()).c_str());
   5690 
   5691  } else {
   5692    // If we're not taking the aLayerMetrics wholesale we still need to pull
   5693    // in some things into our local Metrics() because these things are
   5694    // determined by Gecko and our copy in Metrics() may be stale.
   5695 
   5696    if (Metrics().GetLayoutViewport().Size() !=
   5697        aLayerMetrics.GetLayoutViewport().Size()) {
   5698      CSSRect layoutViewport = Metrics().GetLayoutViewport();
   5699      // The offset will be updated if necessary via
   5700      // RecalculateLayoutViewportOffset().
   5701      layoutViewport.SizeTo(aLayerMetrics.GetLayoutViewport().Size());
   5702      Metrics().SetLayoutViewport(layoutViewport);
   5703 
   5704      needContentRepaint = true;
   5705      viewportSizeUpdated = true;
   5706    }
   5707 
   5708    // TODO: Rely entirely on |aScrollMetadata.IsResolutionUpdated()| to
   5709    //       determine which branch to take, and drop the other conditions.
   5710    CSSToParentLayerScale oldZoom = Metrics().GetZoom();
   5711    if (FuzzyEqualsAdditive(
   5712            Metrics().GetCompositionBoundsWidthIgnoringScrollbars(),
   5713            aLayerMetrics.GetCompositionBoundsWidthIgnoringScrollbars()) &&
   5714        Metrics().GetDevPixelsPerCSSPixel() ==
   5715            aLayerMetrics.GetDevPixelsPerCSSPixel() &&
   5716        !viewportSizeUpdated && !aScrollMetadata.IsResolutionUpdated()) {
   5717      // Any change to the pres shell resolution was requested by APZ and is
   5718      // already included in our zoom; however, other components of the
   5719      // cumulative resolution (a parent document's pres-shell resolution, or
   5720      // the css-driven resolution) may have changed, and we need to update
   5721      // our zoom to reflect that. Note that we can't just take
   5722      // aLayerMetrics.mZoom because the APZ may have additional async zoom
   5723      // since the repaint request.
   5724      float totalResolutionChange = 1.0;
   5725 
   5726      if (Metrics().GetCumulativeResolution() != LayoutDeviceToLayerScale(0)) {
   5727        totalResolutionChange = aLayerMetrics.GetCumulativeResolution().scale /
   5728                                Metrics().GetCumulativeResolution().scale;
   5729      }
   5730 
   5731      float presShellResolutionChange = aLayerMetrics.GetPresShellResolution() /
   5732                                        Metrics().GetPresShellResolution();
   5733      if (presShellResolutionChange != 1.0f) {
   5734        needContentRepaint = true;
   5735      }
   5736      Metrics().ZoomBy(totalResolutionChange / presShellResolutionChange);
   5737      for (auto& sampledState : mSampledState) {
   5738        sampledState.ZoomBy(totalResolutionChange / presShellResolutionChange);
   5739      }
   5740    } else {
   5741      // Take the new zoom as either device scale or composition width or
   5742      // viewport size got changed (e.g. due to orientation change, or content
   5743      // changing the meta-viewport tag), or the main thread originated a
   5744      // resolution change for another reason (e.g. Ctrl+0 was pressed to
   5745      // reset the zoom).
   5746      Metrics().SetZoom(aLayerMetrics.GetZoom());
   5747      for (auto& sampledState : mSampledState) {
   5748        sampledState.UpdateZoomProperties(aLayerMetrics);
   5749      }
   5750      Metrics().SetDevPixelsPerCSSPixel(
   5751          aLayerMetrics.GetDevPixelsPerCSSPixel());
   5752    }
   5753 
   5754    if (Metrics().GetZoom() != oldZoom) {
   5755      // If the zoom changed, the scroll range in CSS pixels may have changed
   5756      // even if the composition bounds didn't.
   5757      needToReclampScroll = true;
   5758    }
   5759 
   5760    mExpectedGeckoMetrics.UpdateZoomFrom(aLayerMetrics);
   5761 
   5762    if (!Metrics().GetScrollableRect().IsEqualEdges(
   5763            aLayerMetrics.GetScrollableRect())) {
   5764      Metrics().SetScrollableRect(aLayerMetrics.GetScrollableRect());
   5765      needContentRepaint = true;
   5766      needToReclampScroll = true;
   5767    }
   5768    if (!Metrics().GetCompositionBounds().IsEqualEdges(
   5769            aLayerMetrics.GetCompositionBounds())) {
   5770      Metrics().SetCompositionBounds(aLayerMetrics.GetCompositionBounds());
   5771      needToReclampScroll = true;
   5772    }
   5773    Metrics().SetCompositionBoundsWidthIgnoringScrollbars(
   5774        aLayerMetrics.GetCompositionBoundsWidthIgnoringScrollbars());
   5775 
   5776    if (Metrics().IsRootContent() &&
   5777        Metrics().GetCompositionSizeWithoutDynamicToolbar() !=
   5778            aLayerMetrics.GetCompositionSizeWithoutDynamicToolbar()) {
   5779      Metrics().SetCompositionSizeWithoutDynamicToolbar(
   5780          aLayerMetrics.GetCompositionSizeWithoutDynamicToolbar());
   5781      needToReclampScroll = true;
   5782    }
   5783    if (Metrics().IsRootContent()) {
   5784      // If the composition size changed, the compositor's layout viewport
   5785      // offset may have changed (to keep the layout viewport enclosing the
   5786      // visual viewport) in a way the main thread doesn't know about until it
   5787      // gets a repaint request. An example scenario where this can occur is if
   5788      // the software keyboard is hidden. In such cases we need to trigger a
   5789      // content repaint request with `eVisualUpdate` otherwise any visual
   5790      // scroll offset changes triggered on the main-thread will never reflect
   5791      // to APZ.
   5792      if (Metrics().GetBoundingCompositionSize() !=
   5793          aLayerMetrics.GetBoundingCompositionSize()) {
   5794        needContentRepaint = true;
   5795        contentRepaintType = RepaintUpdateType::eVisualUpdate;
   5796      }
   5797    }
   5798    Metrics().SetBoundingCompositionSize(
   5799        aLayerMetrics.GetBoundingCompositionSize());
   5800    Metrics().SetPresShellResolution(aLayerMetrics.GetPresShellResolution());
   5801    Metrics().SetCumulativeResolution(aLayerMetrics.GetCumulativeResolution());
   5802    Metrics().SetTransformToAncestorScale(
   5803        aLayerMetrics.GetTransformToAncestorScale());
   5804    mScrollMetadata.SetLineScrollAmount(aScrollMetadata.GetLineScrollAmount());
   5805    mScrollMetadata.SetPageScrollAmount(aScrollMetadata.GetPageScrollAmount());
   5806    mScrollMetadata.SetSnapInfo(ScrollSnapInfo(aScrollMetadata.GetSnapInfo()));
   5807    mScrollMetadata.SetIsLayersIdRoot(aScrollMetadata.IsLayersIdRoot());
   5808    mScrollMetadata.SetIsAutoDirRootContentRTL(
   5809        aScrollMetadata.IsAutoDirRootContentRTL());
   5810    Metrics().SetIsScrollInfoLayer(aLayerMetrics.IsScrollInfoLayer());
   5811    Metrics().SetHasNonZeroDisplayPortMargins(
   5812        aLayerMetrics.HasNonZeroDisplayPortMargins());
   5813    Metrics().SetMinimalDisplayPort(aLayerMetrics.IsMinimalDisplayPort());
   5814    Metrics().SetInteractiveWidget(aLayerMetrics.GetInteractiveWidget());
   5815    Metrics().SetIsSoftwareKeyboardVisible(
   5816        aLayerMetrics.IsSoftwareKeyboardVisible());
   5817    mScrollMetadata.SetForceDisableApz(aScrollMetadata.IsApzForceDisabled());
   5818    mScrollMetadata.SetIsRDMTouchSimulationActive(
   5819        aScrollMetadata.GetIsRDMTouchSimulationActive());
   5820    mScrollMetadata.SetForceMousewheelAutodir(
   5821        aScrollMetadata.ForceMousewheelAutodir());
   5822    mScrollMetadata.SetForceMousewheelAutodirHonourRoot(
   5823        aScrollMetadata.ForceMousewheelAutodirHonourRoot());
   5824    mScrollMetadata.SetIsPaginatedPresentation(
   5825        aScrollMetadata.IsPaginatedPresentation());
   5826    mScrollMetadata.SetDisregardedDirection(
   5827        aScrollMetadata.GetDisregardedDirection());
   5828    mScrollMetadata.SetOverscrollBehavior(
   5829        aScrollMetadata.GetOverscrollBehavior());
   5830    mScrollMetadata.SetOverflow(aScrollMetadata.GetOverflow());
   5831  }
   5832 
   5833  bool instantScrollMayTriggerTransform = false;
   5834  bool scrollOffsetUpdated = false;
   5835  bool smoothScrollRequested = false;
   5836  bool didCancelAnimation = false;
   5837  Maybe<CSSPoint> cumulativeRelativeDelta;
   5838  // Sample the current times once, to ensure different scroll updates don't see
   5839  // different times.
   5840  TimeStamp transactionTime = GetFrameTime().Time();
   5841  for (const auto& scrollUpdate : aScrollMetadata.GetScrollUpdates()) {
   5842    APZC_LOG("%p processing scroll update %s\n", this,
   5843             ToString(scrollUpdate).c_str());
   5844    if (!(Metrics().GetScrollGeneration() < scrollUpdate.GetGeneration())) {
   5845      // This is stale, let's ignore it
   5846      APZC_LOG("%p scrollupdate generation stale, dropping\n", this);
   5847      continue;
   5848    }
   5849    Metrics().SetScrollGeneration(scrollUpdate.GetGeneration());
   5850 
   5851    MOZ_ASSERT(scrollUpdate.GetOrigin() != ScrollOrigin::Apz);
   5852    if (userScrolled &&
   5853        !nsLayoutUtils::CanScrollOriginClobberApz(scrollUpdate.GetOrigin())) {
   5854      APZC_LOG("%p scrollupdate cannot clobber APZ userScrolled\n", this);
   5855      continue;
   5856    }
   5857    // XXX: if we get here, |scrollUpdate| is clobbering APZ, so we may want
   5858    // to reset |userScrolled| back to false so that subsequent scrollUpdates
   5859    // in this loop don't get dropped by the check above. Need to add a test
   5860    // that exercises this scenario, as we don't currently have one.
   5861 
   5862    if (scrollUpdate.GetMode() == ScrollMode::Smooth ||
   5863        scrollUpdate.GetMode() == ScrollMode::SmoothMsd) {
   5864      smoothScrollRequested = true;
   5865 
   5866      // Requests to animate the visual scroll position override requests to
   5867      // simply update the visual scroll offset to a particular point. Since
   5868      // we have an animation request, we set ignoreVisualUpdate to true to
   5869      // indicate we don't need to apply the visual scroll update in
   5870      // aLayerMetrics.
   5871      ignoreVisualUpdate = true;
   5872 
   5873      // For relative updates we want to add the relative offset to any existing
   5874      // destination, or the current visual offset if there is no existing
   5875      // destination.
   5876      CSSPoint base = GetCurrentAnimationDestination(lock).valueOr(
   5877          Metrics().GetVisualScrollOffset());
   5878 
   5879      CSSPoint destination;
   5880      if (scrollUpdate.GetType() == ScrollUpdateType::Relative) {
   5881        CSSPoint delta =
   5882            scrollUpdate.GetDestination() - scrollUpdate.GetSource();
   5883        APZC_LOG("%p relative smooth scrolling from %s by %s\n", this,
   5884                 ToString(base).c_str(), ToString(delta).c_str());
   5885        destination = Metrics().CalculateScrollRange().ClampPoint(base + delta);
   5886      } else if (scrollUpdate.GetType() == ScrollUpdateType::PureRelative) {
   5887        CSSPoint delta = scrollUpdate.GetDelta();
   5888        APZC_LOG("%p pure-relative smooth scrolling from %s by %s\n", this,
   5889                 ToString(base).c_str(), ToString(delta).c_str());
   5890        destination = Metrics().CalculateScrollRange().ClampPoint(base + delta);
   5891      } else {
   5892        APZC_LOG("%p smooth scrolling to %s\n", this,
   5893                 ToString(scrollUpdate.GetDestination()).c_str());
   5894        destination = scrollUpdate.GetDestination();
   5895      }
   5896 
   5897      ScrollAnimationKind animationKind =
   5898          scrollUpdate.GetMode() == ScrollMode::SmoothMsd
   5899              ? ScrollAnimationKind::SmoothMsd
   5900              : ScrollAnimationKind::Smooth;
   5901      SmoothScrollTo(
   5902          CSSSnapDestination{destination, scrollUpdate.GetSnapTargetIds()},
   5903          scrollUpdate.GetScrollTriggeredByScript(), animationKind,
   5904          scrollUpdate.GetViewportType(), scrollUpdate.GetOrigin(),
   5905          transactionTime);
   5906      continue;
   5907    }
   5908 
   5909    MOZ_ASSERT(scrollUpdate.GetMode() == ScrollMode::Instant ||
   5910               scrollUpdate.GetMode() == ScrollMode::Normal);
   5911 
   5912    instantScrollMayTriggerTransform =
   5913        scrollUpdate.GetMode() == ScrollMode::Instant &&
   5914        scrollUpdate.GetScrollTriggeredByScript() ==
   5915            ScrollTriggeredByScript::No;
   5916 
   5917    // If the layout update is of a higher priority than the visual update, then
   5918    // we don't want to apply the visual update.
   5919    // If the layout update is of a clobbering type (or a smooth scroll request,
   5920    // which is handled above) then it takes precedence over an eRestore visual
   5921    // update. But we also allow the possibility for the main thread to ask us
   5922    // to scroll both the layout and visual viewports to distinct (but
   5923    // compatible) locations (via e.g. both updates being of a non-clobbering/
   5924    // eRestore type).
   5925    if (nsLayoutUtils::CanScrollOriginClobberApz(scrollUpdate.GetOrigin()) &&
   5926        aLayerMetrics.GetVisualScrollUpdateType() !=
   5927            FrameMetrics::eMainThread) {
   5928      ignoreVisualUpdate = true;
   5929    }
   5930 
   5931    Maybe<CSSPoint> relativeDelta;
   5932    if (scrollUpdate.GetType() == ScrollUpdateType::Relative) {
   5933      APZC_LOG(
   5934          "%p relative updating scroll offset from %s by %s, isDefault(%d)\n",
   5935          this, ToString(Metrics().GetVisualScrollOffset()).c_str(),
   5936          ToString(scrollUpdate.GetDestination() - scrollUpdate.GetSource())
   5937              .c_str(),
   5938          isDefault);
   5939 
   5940      scrollOffsetUpdated = true;
   5941 
   5942      // It's possible that the main thread has ignored an APZ scroll offset
   5943      // update for the pending relative scroll that we have just received.
   5944      // When this happens, we need to send a new scroll offset update with
   5945      // the combined scroll offset or else the main thread may have an
   5946      // incorrect scroll offset for a period of time.
   5947      if (Metrics().HasPendingScroll(aLayerMetrics)) {
   5948        needContentRepaint = true;
   5949        contentRepaintType = RepaintUpdateType::eUserAction;
   5950      }
   5951 
   5952      relativeDelta = Some(Metrics().ApplyRelativeScrollUpdateFrom(
   5953          scrollUpdate, FrameMetrics::IsDefaultApzc{isDefault}));
   5954      Metrics().RecalculateLayoutViewportOffset();
   5955    } else if (scrollUpdate.GetType() == ScrollUpdateType::PureRelative) {
   5956      APZC_LOG("%p pure-relative updating scroll offset from %s by %s\n", this,
   5957               ToString(Metrics().GetVisualScrollOffset()).c_str(),
   5958               ToString(scrollUpdate.GetDelta()).c_str());
   5959 
   5960      scrollOffsetUpdated = true;
   5961 
   5962      // Always need a repaint request with a repaint type for pure relative
   5963      // scrolls because apz is doing the scroll at the main thread's request.
   5964      // The main thread has not updated it's scroll offset yet, it is depending
   5965      // on apz to tell it where to scroll.
   5966      needContentRepaint = true;
   5967      contentRepaintType = RepaintUpdateType::eVisualUpdate;
   5968 
   5969      // We have to ignore a visual scroll offset update otherwise it will
   5970      // clobber the relative scrolling we are about to do. We perform
   5971      // visualScrollOffset = visualScrollOffset + delta. Then the
   5972      // visualScrollOffsetUpdated block below will do visualScrollOffset =
   5973      // aLayerMetrics.GetVisualDestination(). We need visual scroll offset
   5974      // updates to be incorporated into this scroll update loop to properly fix
   5975      // this.
   5976      ignoreVisualUpdate = true;
   5977 
   5978      relativeDelta =
   5979          Some(Metrics().ApplyPureRelativeScrollUpdateFrom(scrollUpdate));
   5980      Metrics().RecalculateLayoutViewportOffset();
   5981    } else {
   5982      APZC_LOG("%p updating scroll offset from %s to %s\n", this,
   5983               ToString(Metrics().GetVisualScrollOffset()).c_str(),
   5984               ToString(scrollUpdate.GetDestination()).c_str());
   5985      bool offsetChanged = Metrics().ApplyScrollUpdateFrom(scrollUpdate);
   5986      Metrics().RecalculateLayoutViewportOffset();
   5987 
   5988      if (offsetChanged || scrollUpdate.GetMode() != ScrollMode::Instant ||
   5989          scrollUpdate.GetType() != ScrollUpdateType::Absolute ||
   5990          scrollUpdate.GetOrigin() != ScrollOrigin::None) {
   5991        // We get a NewScrollFrame update for newly created scroll frames. Only
   5992        // if this was not a NewScrollFrame update or the offset changed do we
   5993        // request repaint. This is important so that we don't request repaint
   5994        // for every new content and set a full display port on it.
   5995        scrollOffsetUpdated = true;
   5996      }
   5997    }
   5998 
   5999    if (relativeDelta) {
   6000      cumulativeRelativeDelta =
   6001          !cumulativeRelativeDelta
   6002              ? relativeDelta
   6003              : Some(*cumulativeRelativeDelta + *relativeDelta);
   6004    } else {
   6005      // If the scroll update is not relative, clobber the cumulative delta,
   6006      // i.e. later updates win.
   6007      cumulativeRelativeDelta.reset();
   6008    }
   6009 
   6010    // If an animation is underway, tell it about the scroll offset update.
   6011    // Some animations can handle some scroll offset updates and continue
   6012    // running. Those that can't will return false, and we cancel them.
   6013    if (ShouldCancelAnimationForScrollUpdate(relativeDelta)) {
   6014      // Cancel the animation (which might also trigger a repaint request)
   6015      // after we update the scroll offset above. Otherwise we can be left
   6016      // in a state where things are out of sync.
   6017      CancelAnimation();
   6018      didCancelAnimation = true;
   6019    }
   6020  }
   6021 
   6022  if (aLayersUpdateFlags.mIsFirstPaint || needToReclampScroll) {
   6023    // The scrollable rect or composition bounds may have changed in a way that
   6024    // makes our local scroll offset out of bounds, so clamp it.
   6025    ClampAndSetVisualScrollOffset(Metrics().GetVisualScrollOffset());
   6026  }
   6027 
   6028  // If our scroll range changed (for example, because the page dynamically
   6029  // loaded new content, thereby increasing the size of the scrollable rect),
   6030  // and we're overscrolled, being overscrolled may no longer be a valid
   6031  // state (for example, we may no longer be at the edge of our scroll range),
   6032  // then try to fill it out with the new content if the overscroll amount is
   6033  // inside the new scroll range.
   6034  if (needToReclampScroll && IsInInvalidOverscroll()) {
   6035    if (!cumulativeRelativeDelta) {
   6036      // TODO: If we have a cumulative delta, can we combine the overscroll
   6037      //  change with it?
   6038      CSSPoint scrollPositionChange = MaybeFillOutOverscrollGutter(lock);
   6039      if (scrollPositionChange != CSSPoint()) {
   6040        cumulativeRelativeDelta = Some(scrollPositionChange);
   6041      }
   6042    }
   6043    if (mState == OVERSCROLL_ANIMATION) {
   6044      CancelAnimation();
   6045      didCancelAnimation = true;
   6046    } else if (IsOverscrolled()) {
   6047      ClearOverscroll();
   6048    }
   6049  }
   6050 
   6051  if (scrollOffsetUpdated) {
   6052    // Because of the scroll generation update, any inflight paint requests
   6053    // are going to be ignored by layout, and so mExpectedGeckoMetrics becomes
   6054    // incorrect for the purposes of calculating the LD transform. To correct
   6055    // this we need to update mExpectedGeckoMetrics to be the last thing we
   6056    // know was painted by Gecko.
   6057    mExpectedGeckoMetrics.UpdateFrom(aLayerMetrics);
   6058 
   6059    // Since the scroll offset has changed, we need to recompute the
   6060    // displayport margins and send them to layout. Otherwise there might be
   6061    // scenarios where for example we scroll from the top of a page (where the
   6062    // top displayport margin is zero) to the bottom of a page, which will
   6063    // result in a displayport that doesn't extend upwards at all.
   6064    // Note that even if the CancelAnimation call above requested a repaint
   6065    // this is fine because we already have repaint request deduplication.
   6066    needContentRepaint = true;
   6067    // Since the main-thread scroll offset changed we should trigger a
   6068    // recomposite to make sure it becomes user-visible.
   6069    ScheduleComposite();
   6070 
   6071    // If the scroll offset was updated, we're not in a transforming state,
   6072    // and we are scrolling by a non-zero delta, we should ensure
   6073    // TransformBegin and TransformEnd notifications are sent.
   6074    if (!IsTransformingState(mState) && instantScrollMayTriggerTransform &&
   6075        cumulativeRelativeDelta && *cumulativeRelativeDelta != CSSPoint() &&
   6076        (!didCancelAnimation || mState == NOTHING)) {
   6077      SendTransformBeginAndEnd();
   6078    }
   6079  }
   6080 
   6081  if (smoothScrollRequested && !scrollOffsetUpdated) {
   6082    mExpectedGeckoMetrics.UpdateFrom(aLayerMetrics);
   6083    // Need to acknowledge the request.
   6084    needContentRepaint = true;
   6085  }
   6086 
   6087  // If `isDefault` is true, this APZC is a "new" one (this is the first time
   6088  // it's getting a NotifyLayersUpdated call). In this case we want to apply the
   6089  // visual scroll offset from the main thread to our scroll offset.
   6090  // The main thread may also ask us to scroll the visual viewport to a
   6091  // particular location. However, in all cases, we want to ignore the visual
   6092  // offset update if ignoreVisualUpdate is true, because we're clobbering
   6093  // the visual update with a layout update.
   6094  bool visualScrollOffsetUpdated =
   6095      !ignoreVisualUpdate &&
   6096      (isDefault ||
   6097       aLayerMetrics.GetVisualScrollUpdateType() != FrameMetrics::eNone);
   6098 
   6099  if (visualScrollOffsetUpdated) {
   6100    APZC_LOG("%p updating visual scroll offset from %s to %s (updateType %d)\n",
   6101             this, ToString(Metrics().GetVisualScrollOffset()).c_str(),
   6102             ToString(aLayerMetrics.GetVisualDestination()).c_str(),
   6103             (int)aLayerMetrics.GetVisualScrollUpdateType());
   6104    bool offsetChanged = Metrics().ClampAndSetVisualScrollOffset(
   6105        aLayerMetrics.GetVisualDestination());
   6106 
   6107    // If this is the first time we got metrics for this content (isDefault) and
   6108    // the update type was none and the offset didn't change then we don't have
   6109    // to do anything. This is important because we don't want to request
   6110    // repaint on the initial NotifyLayersUpdated for every content and thus set
   6111    // a full display port.
   6112    if (aLayerMetrics.GetVisualScrollUpdateType() == FrameMetrics::eNone &&
   6113        !offsetChanged) {
   6114      visualScrollOffsetUpdated = false;
   6115    }
   6116  }
   6117  if (visualScrollOffsetUpdated) {
   6118    // The rest of this branch largely follows the code in the
   6119    // |if (scrollOffsetUpdated)| branch above. Eventually it should get
   6120    // merged into that branch.
   6121    //
   6122    // RecalculateLayoutViewportOffset tries to adjust the layout scroll offset
   6123    // if the updated visual scroll offset overflows the visual viewport from
   6124    // the layout viewport. Unfortunately the visual viewport calculated in APZ
   6125    // is basically including the dynamic toolbar area (because position:fixed
   6126    // (or sticky) elements are directly composited on the compositor in
   6127    // response to the dynamic toolbar movement), thus with the slightly larger
   6128    // visual viewport RecalculateLayoutViewportOffset unintentionally moves the
   6129    // layout scroll offset even if the dynamic toolbar is not collapsed at all.
   6130    // So we pass the compositor fixed layers margins which is representing the
   6131    // dynamic toolbar state to RecalculateLayoutViewportOffset to avoid such
   6132    // unintentional layout offset changes.
   6133    Metrics().RecalculateLayoutViewportOffset(
   6134        GetFixedLayerMargins(lock).bottom);
   6135    mExpectedGeckoMetrics.UpdateFrom(aLayerMetrics);
   6136    if (ShouldCancelAnimationForScrollUpdate(Nothing())) {
   6137      CancelAnimation();
   6138    }
   6139    // The main thread did not actually paint a displayport at the target
   6140    // visual offset, so we need to ask it to repaint. We need to set the
   6141    // contentRepaintType to something other than eNone, otherwise the main
   6142    // thread will short-circuit the repaint request.
   6143    // Don't do this for eRestore visual updates as a repaint coming from APZ
   6144    // breaks the scroll offset restoration mechanism.
   6145    needContentRepaint = true;
   6146    if (aLayerMetrics.GetVisualScrollUpdateType() ==
   6147        FrameMetrics::eMainThread) {
   6148      contentRepaintType = RepaintUpdateType::eVisualUpdate;
   6149    }
   6150    ScheduleComposite();
   6151  }
   6152 
   6153  if (viewportSizeUpdated) {
   6154    // While we want to accept the main thread's layout viewport _size_,
   6155    // its position may be out of date in light of async scrolling, to
   6156    // adjust it if necessary to make sure it continues to enclose the
   6157    // visual viewport.
   6158    // Note: it's important to do this _after_ we've accepted any
   6159    // updated composition bounds.
   6160    Metrics().RecalculateLayoutViewportOffset();
   6161  }
   6162 
   6163  // Modify sampled state lastly.
   6164  if (scrollOffsetUpdated || visualScrollOffsetUpdated) {
   6165    for (auto& sampledState : mSampledState) {
   6166      if (!didCancelAnimation && cumulativeRelativeDelta.isSome()) {
   6167        sampledState.UpdateScrollPropertiesWithRelativeDelta(
   6168            Metrics(), *cumulativeRelativeDelta);
   6169      } else {
   6170        sampledState.UpdateScrollProperties(Metrics());
   6171      }
   6172    }
   6173  }
   6174  if (aLayersUpdateFlags.mIsFirstPaint || needToReclampScroll) {
   6175    for (auto& sampledState : mSampledState) {
   6176      sampledState.ClampVisualScrollOffset(Metrics());
   6177    }
   6178  }
   6179 
   6180  if (needContentRepaint) {
   6181    // This repaint request could be driven by a user action if we accept a
   6182    // relative scroll offset update
   6183    RequestContentRepaint(contentRepaintType);
   6184  }
   6185 }
   6186 
   6187 FrameMetrics& AsyncPanZoomController::Metrics() {
   6188  mRecursiveMutex.AssertCurrentThreadIn();
   6189  return mScrollMetadata.GetMetrics();
   6190 }
   6191 
   6192 const FrameMetrics& AsyncPanZoomController::Metrics() const {
   6193  mRecursiveMutex.AssertCurrentThreadIn();
   6194  return mScrollMetadata.GetMetrics();
   6195 }
   6196 
   6197 bool CompositorScrollUpdate::Metrics::operator==(const Metrics& aOther) const {
   6198  // Consider two metrcs to be the same if the scroll offsets are the same
   6199  // when rounded to the nearest screen pixel. This avoids spurious updates
   6200  // due to small rounding errors, which consumers do not care about because
   6201  // if the scroll offset does not change in screen pixels, what is composited
   6202  // should not change either.
   6203  return RoundedToInt(mVisualScrollOffset * mZoom) ==
   6204             RoundedToInt(aOther.mVisualScrollOffset * aOther.mZoom) &&
   6205         mZoom == aOther.mZoom;
   6206 }
   6207 
   6208 bool CompositorScrollUpdate::operator==(
   6209    const CompositorScrollUpdate& aOther) const {
   6210  return mMetrics == aOther.mMetrics && mSource == aOther.mSource;
   6211 }
   6212 
   6213 std::vector<CompositorScrollUpdate>
   6214 AsyncPanZoomController::GetCompositorScrollUpdates() {
   6215  RecursiveMutexAutoLock lock(mRecursiveMutex);
   6216  MOZ_ASSERT(Metrics().IsRootContent());
   6217  return mSampledState[0].Updates();
   6218 }
   6219 
   6220 CompositorScrollUpdate::Metrics
   6221 AsyncPanZoomController::GetCurrentMetricsForCompositorScrollUpdate(
   6222    const RecursiveMutexAutoLock& aProofOfApzcLock) const {
   6223  return {Metrics().GetVisualScrollOffset(), Metrics().GetZoom()};
   6224 }
   6225 
   6226 wr::MinimapData AsyncPanZoomController::GetMinimapData() const {
   6227  RecursiveMutexAutoLock lock(mRecursiveMutex);
   6228  wr::MinimapData result;
   6229  result.is_root_content = IsRootContent();
   6230  // We want the minimap to reflect the scroll offset actually composited,
   6231  // which could be older than the latest one in Metrics() due to the frame
   6232  // delay.
   6233  CSSRect visualViewport = GetCurrentAsyncVisualViewport(eForCompositing);
   6234  result.visual_viewport = wr::ToLayoutRect(visualViewport.ToUnknownRect());
   6235  CSSRect layoutViewport = GetEffectiveLayoutViewport(eForCompositing, lock);
   6236  result.layout_viewport = wr::ToLayoutRect(layoutViewport.ToUnknownRect());
   6237  result.scrollable_rect =
   6238      wr::ToLayoutRect(Metrics().GetScrollableRect().ToUnknownRect());
   6239  // The display port is stored relative to the layout viewport origin.
   6240  // Translate it to be relative to the document origin, like the other rects.
   6241  CSSRect displayPort = mLastContentPaintMetrics.GetDisplayPort() +
   6242                        mLastContentPaintMetrics.GetLayoutScrollOffset();
   6243  result.displayport = wr::ToLayoutRect(displayPort.ToUnknownRect());
   6244  // Remaining fields (zoom_transform, root_content_scroll_id,
   6245  // root_content_pipeline_id) will be populated by the caller, since they
   6246  // require information from other APZCs to compute.
   6247  return result;
   6248 }
   6249 
   6250 const FrameMetrics& AsyncPanZoomController::GetFrameMetrics() const {
   6251  return Metrics();
   6252 }
   6253 
   6254 const ScrollMetadata& AsyncPanZoomController::GetScrollMetadata() const {
   6255  mRecursiveMutex.AssertCurrentThreadIn();
   6256  return mScrollMetadata;
   6257 }
   6258 
   6259 void AsyncPanZoomController::AssertOnSamplerThread() const {
   6260  if (APZCTreeManager* treeManagerLocal = GetApzcTreeManager()) {
   6261    treeManagerLocal->AssertOnSamplerThread();
   6262  }
   6263 }
   6264 
   6265 void AsyncPanZoomController::AssertOnUpdaterThread() const {
   6266  if (APZCTreeManager* treeManagerLocal = GetApzcTreeManager()) {
   6267    treeManagerLocal->AssertOnUpdaterThread();
   6268  }
   6269 }
   6270 
   6271 APZCTreeManager* AsyncPanZoomController::GetApzcTreeManager() const {
   6272  mRecursiveMutex.AssertNotCurrentThreadIn();
   6273  return mTreeManager;
   6274 }
   6275 
   6276 void AsyncPanZoomController::ZoomToRect(const ZoomTarget& aZoomTarget,
   6277                                        const uint32_t aFlags) {
   6278  CSSRect rect = aZoomTarget.targetRect;
   6279  if (!rect.IsFinite()) {
   6280    NS_WARNING("ZoomToRect got called with a non-finite rect; ignoring...");
   6281    return;
   6282  }
   6283 
   6284  if (rect.IsEmpty() && (aFlags & DISABLE_ZOOM_OUT)) {
   6285    // Double-tap-to-zooming uses an empty rect to mean "zoom out".
   6286    // If zooming out is disabled, an empty rect is nonsensical
   6287    // and will produce undesirable scrolling.
   6288    NS_WARNING(
   6289        "ZoomToRect got called with an empty rect and zoom out disabled; "
   6290        "ignoring...");
   6291    return;
   6292  }
   6293 
   6294  AutoDynamicToolbarHider dynamicToolbarHider(this);
   6295 
   6296  {
   6297    RecursiveMutexAutoLock lock(mRecursiveMutex);
   6298 
   6299    // If we are zooming to focus an input element near the bottom of the
   6300    // scrollable rect, it may be covered up by the dynamic toolbar and we may
   6301    // not have room to scroll it into view. In such cases, trigger hiding of
   6302    // the dynamic toolbar to ensure the input element is visible.
   6303    if (aFlags & ZOOM_TO_FOCUSED_INPUT) {
   6304      // Long and short viewport heights, corresponding to CSS length values of
   6305      // 100lvh and 100svh.
   6306      const CSSCoord lvh = ToCSSPixels(Metrics().GetCompositionBounds().height);
   6307      const CSSCoord svh = ToCSSPixels(
   6308          Metrics().GetCompositionSizeWithoutDynamicToolbar().height);
   6309      const CSSCoord scrollableRectHeight =
   6310          Metrics().GetScrollableRect().height;
   6311 
   6312      auto mightNeedToHideToolbar = [&]() -> bool {
   6313        // While the software keyboard is visible on resizes-visual mode,
   6314        // if the target rect is underneath of the toolbar, we will have to
   6315        // hide the toolbar.
   6316        if (aFlags & ZOOM_TO_FOCUSED_INPUT_ON_RESIZES_VISUAL) {
   6317          return true;
   6318        }
   6319        // FIXME: This condition is too strict even in resizes-content mode,
   6320        // it's possible for the toolbar to cover up an element at the bottom
   6321        // of the scrollable rect even if `scrollableRectHeight > lvh`.
   6322        // We need to either relax the condition, or find a different solution
   6323        // such as bug 1920019 comment 8.
   6324        return scrollableRectHeight > svh && scrollableRectHeight < lvh;
   6325      };
   6326 
   6327      if (mightNeedToHideToolbar()) {
   6328        const CSSCoord targetDistanceFromBottom =
   6329            (Metrics().GetScrollableRect().YMost() -
   6330             aZoomTarget.targetRect.YMost());
   6331        const CSSCoord dynamicToolbarHeight = (lvh - svh);
   6332        if (targetDistanceFromBottom < dynamicToolbarHeight) {
   6333          dynamicToolbarHider.Hide();
   6334        }
   6335      }
   6336    }
   6337 
   6338    MOZ_ASSERT(Metrics().IsRootContent());
   6339 
   6340    const float defaultZoomInAmount =
   6341        StaticPrefs::apz_doubletapzoom_defaultzoomin();
   6342 
   6343    ParentLayerRect compositionBounds = Metrics().GetCompositionBounds();
   6344    CSSRect cssPageRect = Metrics().GetScrollableRect();
   6345    CSSPoint scrollOffset = Metrics().GetVisualScrollOffset();
   6346    CSSSize sizeBeforeZoom = Metrics().CalculateCompositedSizeInCssPixels();
   6347    CSSToParentLayerScale currentZoom = Metrics().GetZoom();
   6348    CSSToParentLayerScale targetZoom;
   6349 
   6350    // The minimum zoom to prevent over-zoom-out.
   6351    // If the zoom factor is lower than this (i.e. we are zoomed more into the
   6352    // page), then the CSS content rect, in layers pixels, will be smaller than
   6353    // the composition bounds. If this happens, we can't fill the target
   6354    // composited area with this frame.
   6355    const CSSRect cssExpandedPageRect = Metrics().GetExpandedScrollableRect();
   6356    CSSToParentLayerScale localMinZoom(
   6357        std::max(compositionBounds.Width() / cssExpandedPageRect.Width(),
   6358                 compositionBounds.Height() / cssExpandedPageRect.Height()));
   6359 
   6360    localMinZoom.scale =
   6361        std::clamp(localMinZoom.scale, mZoomConstraints.mMinZoom.scale,
   6362                   mZoomConstraints.mMaxZoom.scale);
   6363 
   6364    localMinZoom = std::max(mZoomConstraints.mMinZoom, localMinZoom);
   6365    CSSToParentLayerScale localMaxZoom =
   6366        std::max(localMinZoom, mZoomConstraints.mMaxZoom);
   6367 
   6368    if (!rect.IsEmpty()) {
   6369      // Intersect the zoom-to-rect to the CSS rect to make sure it fits.
   6370      rect = rect.Intersect(cssPageRect);
   6371      targetZoom = CSSToParentLayerScale(
   6372          std::min(compositionBounds.Width() / rect.Width(),
   6373                   compositionBounds.Height() / rect.Height()));
   6374      if (aFlags & DISABLE_ZOOM_OUT) {
   6375        targetZoom = std::max(targetZoom, currentZoom);
   6376      }
   6377    }
   6378 
   6379    // 1. If the rect is empty, the content-side logic for handling a double-tap
   6380    //    requested that we zoom out.
   6381    // 2. currentZoom is equal to mZoomConstraints.mMaxZoom and user still
   6382    // double-tapping it
   6383    // Treat these cases as a request to zoom out as much as possible
   6384    // unless cantZoomOutBehavior == ZoomIn and currentZoom
   6385    // is equal to localMinZoom and user still double-tapping it, then try to
   6386    // zoom in a small amount to provide feedback to the user.
   6387    bool zoomOut = false;
   6388    // True if we are already zoomed out and we are asked to either stay there
   6389    // or zoom out more and cantZoomOutBehavior == ZoomIn.
   6390    bool zoomInDefaultAmount = false;
   6391    if (aFlags & DISABLE_ZOOM_OUT) {
   6392      zoomOut = false;
   6393    } else {
   6394      if (rect.IsEmpty()) {
   6395        if (currentZoom == localMinZoom &&
   6396            aZoomTarget.cantZoomOutBehavior == CantZoomOutBehavior::ZoomIn &&
   6397            (defaultZoomInAmount != 1.f)) {
   6398          zoomInDefaultAmount = true;
   6399        } else {
   6400          zoomOut = true;
   6401        }
   6402      } else if (currentZoom == localMaxZoom && targetZoom >= localMaxZoom) {
   6403        zoomOut = true;
   6404      }
   6405    }
   6406 
   6407    // already at min zoom and asked to zoom out further
   6408    if (!zoomOut && currentZoom == localMinZoom && targetZoom <= localMinZoom &&
   6409        aZoomTarget.cantZoomOutBehavior == CantZoomOutBehavior::ZoomIn &&
   6410        (defaultZoomInAmount != 1.f)) {
   6411      zoomInDefaultAmount = true;
   6412    }
   6413    MOZ_ASSERT(!(zoomInDefaultAmount && zoomOut));
   6414 
   6415    if (zoomInDefaultAmount) {
   6416      targetZoom =
   6417          CSSToParentLayerScale(currentZoom.scale * defaultZoomInAmount);
   6418    }
   6419 
   6420    if (zoomOut) {
   6421      targetZoom = localMinZoom;
   6422    }
   6423 
   6424    if (aFlags & PAN_INTO_VIEW_ONLY) {
   6425      targetZoom = currentZoom;
   6426    } else if (aFlags & ONLY_ZOOM_TO_DEFAULT_SCALE) {
   6427      CSSToParentLayerScale zoomAtDefaultScale =
   6428          Metrics().GetDevPixelsPerCSSPixel() *
   6429          LayoutDeviceToParentLayerScale(1.0);
   6430      if (targetZoom.scale > zoomAtDefaultScale.scale) {
   6431        // Only change the zoom if we are less than the default zoom
   6432        if (currentZoom.scale < zoomAtDefaultScale.scale) {
   6433          targetZoom = zoomAtDefaultScale;
   6434        } else {
   6435          targetZoom = currentZoom;
   6436        }
   6437      }
   6438    }
   6439 
   6440    targetZoom.scale =
   6441        std::clamp(targetZoom.scale, localMinZoom.scale, localMaxZoom.scale);
   6442 
   6443    // For zoom-to-focused-input, we've already centered the given focused
   6444    // element in nsDOMWindowUtils::ZoomToFocusedInput() so that if the target
   6445    // zoom scale would be same we don't need to trigger a ZoomAnimation.
   6446    if ((aFlags & ZOOM_TO_FOCUSED_INPUT) && targetZoom == currentZoom) {
   6447      return;
   6448    }
   6449 
   6450    FrameMetrics endZoomToMetrics = Metrics();
   6451    endZoomToMetrics.SetZoom(CSSToParentLayerScale(targetZoom));
   6452    CSSSize sizeAfterZoom =
   6453        endZoomToMetrics.CalculateCompositedSizeInCssPixels();
   6454 
   6455    if (zoomInDefaultAmount || zoomOut) {
   6456      // For the zoom out case we should always center what was visible
   6457      // otherwise it feels like we are scrolling as well as zooming out. For
   6458      // the non-zoomOut case, if we've been provided a pointer location, zoom
   6459      // around that, otherwise just zoom in to the center of what's currently
   6460      // visible.
   6461      if (!zoomOut && aZoomTarget.documentRelativePointerPosition.isSome()) {
   6462        rect = CSSRect(aZoomTarget.documentRelativePointerPosition->x -
   6463                           sizeAfterZoom.width / 2,
   6464                       aZoomTarget.documentRelativePointerPosition->y -
   6465                           sizeAfterZoom.height / 2,
   6466                       sizeAfterZoom.Width(), sizeAfterZoom.Height());
   6467      } else {
   6468        rect = CSSRect(
   6469            scrollOffset.x + (sizeBeforeZoom.width - sizeAfterZoom.width) / 2,
   6470            scrollOffset.y + (sizeBeforeZoom.height - sizeAfterZoom.height) / 2,
   6471            sizeAfterZoom.Width(), sizeAfterZoom.Height());
   6472      }
   6473 
   6474      rect = rect.Intersect(cssPageRect);
   6475    }
   6476 
   6477    // Check if we can fit the full elementBoundingRect.
   6478    if (!aZoomTarget.targetRect.IsEmpty() && !zoomOut &&
   6479        aZoomTarget.elementBoundingRect.isSome()) {
   6480      MOZ_ASSERT(aZoomTarget.elementBoundingRect->Contains(rect));
   6481      CSSRect elementBoundingRect =
   6482          aZoomTarget.elementBoundingRect->Intersect(cssPageRect);
   6483      if (elementBoundingRect.width <= sizeAfterZoom.width &&
   6484          elementBoundingRect.height <= sizeAfterZoom.height) {
   6485        rect = elementBoundingRect;
   6486      }
   6487    }
   6488 
   6489    // Vertically center the zoomed element in the screen.
   6490    if (!zoomOut &&
   6491        (sizeAfterZoom.height - rect.Height() > COORDINATE_EPSILON)) {
   6492      rect.MoveByY(-(sizeAfterZoom.height - rect.Height()) * 0.5f);
   6493      if (rect.Y() < 0.0f) {
   6494        rect.MoveToY(0.0f);
   6495      }
   6496    }
   6497 
   6498    // Horizontally center the zoomed element in the screen.
   6499    if (!zoomOut && (sizeAfterZoom.width - rect.Width() > COORDINATE_EPSILON)) {
   6500      rect.MoveByX(-(sizeAfterZoom.width - rect.Width()) * 0.5f);
   6501      if (rect.X() < 0.0f) {
   6502        rect.MoveToX(0.0f);
   6503      }
   6504    }
   6505 
   6506    bool intersectRectAgain = false;
   6507    // If we can't zoom out enough to show the full rect then shift the rect we
   6508    // are able to show to center what was visible.
   6509    // Note that this calculation works no matter the relation of sizeBeforeZoom
   6510    // to sizeAfterZoom, ie whether we are increasing or decreasing zoom.
   6511    if (!zoomOut &&
   6512        (rect.Height() - sizeAfterZoom.height > COORDINATE_EPSILON)) {
   6513      rect.y =
   6514          scrollOffset.y + (sizeBeforeZoom.height - sizeAfterZoom.height) / 2;
   6515      rect.height = sizeAfterZoom.Height();
   6516 
   6517      intersectRectAgain = true;
   6518    }
   6519 
   6520    if (!zoomOut && (rect.Width() - sizeAfterZoom.width > COORDINATE_EPSILON)) {
   6521      rect.x =
   6522          scrollOffset.x + (sizeBeforeZoom.width - sizeAfterZoom.width) / 2;
   6523      rect.width = sizeAfterZoom.Width();
   6524 
   6525      intersectRectAgain = true;
   6526    }
   6527    if (intersectRectAgain) {
   6528      rect = rect.Intersect(cssPageRect);
   6529    }
   6530 
   6531    // If any of these conditions are met, the page will be overscrolled after
   6532    // zoomed. Attempting to scroll outside of the valid scroll range will cause
   6533    // problems.
   6534    if (rect.Y() + sizeAfterZoom.height > cssPageRect.YMost()) {
   6535      rect.MoveToY(std::max(cssPageRect.Y(),
   6536                            cssPageRect.YMost() - sizeAfterZoom.height));
   6537    }
   6538    if (rect.Y() < cssPageRect.Y()) {
   6539      rect.MoveToY(cssPageRect.Y());
   6540    }
   6541    if (rect.X() + sizeAfterZoom.width > cssPageRect.XMost()) {
   6542      rect.MoveToX(
   6543          std::max(cssPageRect.X(), cssPageRect.XMost() - sizeAfterZoom.width));
   6544    }
   6545    if (rect.X() < cssPageRect.X()) {
   6546      rect.MoveToX(cssPageRect.X());
   6547    }
   6548 
   6549    endZoomToMetrics.SetVisualScrollOffset(rect.TopLeft());
   6550    endZoomToMetrics.RecalculateLayoutViewportOffset();
   6551 
   6552    SetState(ANIMATING_ZOOM);
   6553    StartAnimation(do_AddRef(new ZoomAnimation(
   6554        *this, Metrics().GetVisualScrollOffset(), Metrics().GetZoom(),
   6555        endZoomToMetrics.GetVisualScrollOffset(), endZoomToMetrics.GetZoom())));
   6556 
   6557    RequestContentRepaint();
   6558  }
   6559 }
   6560 
   6561 InputBlockState* AsyncPanZoomController::GetCurrentInputBlock() const {
   6562  return GetInputQueue()->GetCurrentBlock();
   6563 }
   6564 
   6565 TouchBlockState* AsyncPanZoomController::GetCurrentTouchBlock() const {
   6566  return GetInputQueue()->GetCurrentTouchBlock();
   6567 }
   6568 
   6569 PanGestureBlockState* AsyncPanZoomController::GetCurrentPanGestureBlock()
   6570    const {
   6571  return GetInputQueue()->GetCurrentPanGestureBlock();
   6572 }
   6573 
   6574 PinchGestureBlockState* AsyncPanZoomController::GetCurrentPinchGestureBlock()
   6575    const {
   6576  return GetInputQueue()->GetCurrentPinchGestureBlock();
   6577 }
   6578 
   6579 void AsyncPanZoomController::ResetTouchInputState() {
   6580  TouchBlockState* block = GetCurrentTouchBlock();
   6581  if (block && block->HasStateBeenReset()) {
   6582    // Bail out only if there's a touch block that the state of the touch block
   6583    // has been reset.
   6584    return;
   6585  }
   6586 
   6587  MultiTouchInput cancel(MultiTouchInput::MULTITOUCH_CANCEL, 0,
   6588                         TimeStamp::Now(), 0);
   6589  RefPtr<GestureEventListener> listener = GetGestureEventListener();
   6590  if (listener) {
   6591    listener->HandleInputEvent(cancel);
   6592  }
   6593  CancelAnimationAndGestureState();
   6594  // Clear overscroll along the entire handoff chain, in case an APZC
   6595  // later in the chain is overscrolled.
   6596  if (block) {
   6597    block->GetOverscrollHandoffChain()->ClearOverscroll();
   6598    block->ResetState();
   6599  }
   6600 }
   6601 
   6602 void AsyncPanZoomController::ResetPanGestureInputState() {
   6603  PanGestureBlockState* block = GetCurrentPanGestureBlock();
   6604  if (block && block->HasStateBeenReset()) {
   6605    // Bail out only if there's a pan gesture block that the state of the pan
   6606    // gesture block has been reset.
   6607    return;
   6608  }
   6609 
   6610  // Unlike in ResetTouchInputState(), do not cancel animations unconditionally.
   6611  // Doing so would break scenarios where content handled `wheel` events
   6612  // triggered by pan gesture input by calling preventDefault() and doing its
   6613  // own smooth (animated) scrolling. However, we do need to call
   6614  // CancelAnimation for its state-resetting effect if there isn't an animation
   6615  // running, otherwise we could e.g. get stuck in a PANNING state if content
   6616  // preventDefault()s an event in the middle of a pan gesture.
   6617  if (!mAnimation) {
   6618    CancelAnimationAndGestureState();
   6619  }
   6620 
   6621  // Clear overscroll along the entire handoff chain, in case an APZC
   6622  // later in the chain is overscrolled.
   6623  if (block) {
   6624    block->GetOverscrollHandoffChain()->ClearOverscroll();
   6625    block->ResetState();
   6626  }
   6627 }
   6628 
   6629 void AsyncPanZoomController::CancelAnimationAndGestureState() {
   6630  mX.CancelGesture();
   6631  mY.CancelGesture();
   6632  CancelAnimation(CancelAnimationFlags::ScrollSnap);
   6633 }
   6634 
   6635 bool AsyncPanZoomController::HasReadyTouchBlock() const {
   6636  return GetInputQueue()->HasReadyTouchBlock();
   6637 }
   6638 
   6639 bool AsyncPanZoomController::CanHandleScrollOffsetUpdate(PanZoomState aState) {
   6640  return aState == PAN_MOMENTUM || aState == TOUCHING || IsPanningState(aState);
   6641 }
   6642 
   6643 bool AsyncPanZoomController::ShouldCancelAnimationForScrollUpdate(
   6644    const Maybe<CSSPoint>& aRelativeDelta) {
   6645  // Never call CancelAnimation() for a no-op relative update.
   6646  if (aRelativeDelta == Some(CSSPoint())) {
   6647    return false;
   6648  }
   6649 
   6650  if (mAnimation) {
   6651    return !mAnimation->HandleScrollOffsetUpdate(aRelativeDelta);
   6652  }
   6653 
   6654  return !CanHandleScrollOffsetUpdate(mState);
   6655 }
   6656 
   6657 AsyncPanZoomController::PanZoomState
   6658 AsyncPanZoomController::SetStateNoContentControllerDispatch(
   6659    PanZoomState aNewState) {
   6660  RecursiveMutexAutoLock lock(mRecursiveMutex);
   6661  APZC_LOG_DETAIL("changing from state %s to %s\n", this,
   6662                  ToString(mState).c_str(), ToString(aNewState).c_str());
   6663  PanZoomState oldState = mState;
   6664  mState = aNewState;
   6665  return oldState;
   6666 }
   6667 
   6668 void AsyncPanZoomController::SetState(PanZoomState aNewState) {
   6669  // When a state transition to a transforming state is occuring and a delayed
   6670  // transform end notification exists, send the TransformEnd notification
   6671  // before the TransformBegin notification is sent for the input state change.
   6672  if (IsTransformingState(aNewState) && IsDelayedTransformEndSet()) {
   6673    MOZ_ASSERT(!IsTransformingState(mState));
   6674    SetDelayedTransformEnd(false);
   6675    DispatchStateChangeNotification(PANNING, NOTHING);
   6676  }
   6677 
   6678  PanZoomState oldState = SetStateNoContentControllerDispatch(aNewState);
   6679 
   6680  DispatchStateChangeNotification(oldState, aNewState);
   6681 }
   6682 
   6683 auto AsyncPanZoomController::GetState() const -> PanZoomState {
   6684  RecursiveMutexAutoLock lock(mRecursiveMutex);
   6685  return mState;
   6686 }
   6687 
   6688 void AsyncPanZoomController::DispatchStateChangeNotification(
   6689    PanZoomState aOldState, PanZoomState aNewState) {
   6690  {  // scope the lock
   6691    RecursiveMutexAutoLock lock(mRecursiveMutex);
   6692    if (mNotificationBlockers > 0) {
   6693      return;
   6694    }
   6695  }
   6696 
   6697  if (RefPtr<GeckoContentController> controller = GetGeckoContentController()) {
   6698    if (!IsTransformingState(aOldState) && IsTransformingState(aNewState)) {
   6699      controller->NotifyAPZStateChange(GetGuid(),
   6700                                       APZStateChange::eTransformBegin);
   6701    } else if (IsTransformingState(aOldState) &&
   6702               !IsTransformingState(aNewState)) {
   6703      controller->NotifyAPZStateChange(GetGuid(),
   6704                                       APZStateChange::eTransformEnd);
   6705    }
   6706  }
   6707 }
   6708 void AsyncPanZoomController::SendTransformBeginAndEnd() {
   6709  RefPtr<GeckoContentController> controller = GetGeckoContentController();
   6710  if (controller) {
   6711    controller->NotifyAPZStateChange(GetGuid(),
   6712                                     APZStateChange::eTransformBegin);
   6713    controller->NotifyAPZStateChange(GetGuid(), APZStateChange::eTransformEnd);
   6714  }
   6715 }
   6716 
   6717 bool AsyncPanZoomController::IsInTransformingState() const {
   6718  RecursiveMutexAutoLock lock(mRecursiveMutex);
   6719  return IsTransformingState(mState);
   6720 }
   6721 
   6722 bool AsyncPanZoomController::IsTransformingState(PanZoomState aState) {
   6723  return !(aState == NOTHING || aState == TOUCHING);
   6724 }
   6725 
   6726 bool AsyncPanZoomController::IsPanningState(PanZoomState aState) {
   6727  return (aState == PANNING || aState == PANNING_LOCKED_X ||
   6728          aState == PANNING_LOCKED_Y);
   6729 }
   6730 
   6731 bool AsyncPanZoomController::IsInPanningState() const {
   6732  return IsPanningState(mState);
   6733 }
   6734 
   6735 bool AsyncPanZoomController::IsInScrollingGesture() const {
   6736  return IsPanningState(mState) || mState == SCROLLBAR_DRAG ||
   6737         mState == TOUCHING || mState == PINCHING;
   6738 }
   6739 
   6740 bool AsyncPanZoomController::IsDelayedTransformEndSet() {
   6741  RecursiveMutexAutoLock lock(mRecursiveMutex);
   6742  return mDelayedTransformEnd;
   6743 }
   6744 
   6745 void AsyncPanZoomController::SetDelayedTransformEnd(bool aDelayedTransformEnd) {
   6746  RecursiveMutexAutoLock lock(mRecursiveMutex);
   6747  mDelayedTransformEnd = aDelayedTransformEnd;
   6748 }
   6749 
   6750 bool AsyncPanZoomController::InScrollAnimation(
   6751    ScrollAnimationKind aKind) const {
   6752  RecursiveMutexAutoLock lock(mRecursiveMutex);
   6753  if (!mAnimation) {
   6754    return false;
   6755  }
   6756  RefPtr<SmoothScrollAnimation> smoothScroll =
   6757      mAnimation->AsSmoothScrollAnimation();
   6758  return smoothScroll && smoothScroll->Kind() == aKind;
   6759 }
   6760 
   6761 bool AsyncPanZoomController::InScrollAnimationTriggeredByScript() const {
   6762  RecursiveMutexAutoLock lock(mRecursiveMutex);
   6763  if (!mAnimation) {
   6764    return false;
   6765  }
   6766  RefPtr<SmoothScrollAnimation> smoothScroll =
   6767      mAnimation->AsSmoothScrollAnimation();
   6768  return smoothScroll && smoothScroll->WasTriggeredByScript();
   6769 }
   6770 
   6771 void AsyncPanZoomController::UpdateZoomConstraints(
   6772    const ZoomConstraints& aConstraints) {
   6773  if ((MOZ_LOG_TEST(sApzCtlLog, LogLevel::Debug) &&
   6774       (aConstraints != mZoomConstraints)) ||
   6775      MOZ_LOG_TEST(sApzCtlLog, LogLevel::Verbose)) {
   6776    APZC_LOG("%p updating zoom constraints to %d %d %f %f\n", this,
   6777             aConstraints.mAllowZoom, aConstraints.mAllowDoubleTapZoom,
   6778             aConstraints.mMinZoom.scale, aConstraints.mMaxZoom.scale);
   6779  }
   6780 
   6781  if (std::isnan(aConstraints.mMinZoom.scale) ||
   6782      std::isnan(aConstraints.mMaxZoom.scale)) {
   6783    NS_WARNING("APZC received zoom constraints with NaN values; dropping...");
   6784    return;
   6785  }
   6786 
   6787  RecursiveMutexAutoLock lock(mRecursiveMutex);
   6788  CSSToParentLayerScale min = Metrics().GetDevPixelsPerCSSPixel() *
   6789                              ViewportMinScale() / ParentLayerToScreenScale(1);
   6790  CSSToParentLayerScale max = Metrics().GetDevPixelsPerCSSPixel() *
   6791                              ViewportMaxScale() / ParentLayerToScreenScale(1);
   6792 
   6793  // inf float values and other bad cases should be sanitized by the code below.
   6794  mZoomConstraints.mAllowZoom = aConstraints.mAllowZoom;
   6795  mZoomConstraints.mAllowDoubleTapZoom = aConstraints.mAllowDoubleTapZoom;
   6796  mZoomConstraints.mMinZoom =
   6797      (min > aConstraints.mMinZoom ? min : aConstraints.mMinZoom);
   6798  mZoomConstraints.mMaxZoom =
   6799      (max > aConstraints.mMaxZoom ? aConstraints.mMaxZoom : max);
   6800  if (mZoomConstraints.mMaxZoom < mZoomConstraints.mMinZoom) {
   6801    mZoomConstraints.mMaxZoom = mZoomConstraints.mMinZoom;
   6802  }
   6803 }
   6804 
   6805 bool AsyncPanZoomController::ZoomConstraintsAllowZoom() const {
   6806  RecursiveMutexAutoLock lock(mRecursiveMutex);
   6807  return mZoomConstraints.mAllowZoom;
   6808 }
   6809 
   6810 bool AsyncPanZoomController::ZoomConstraintsAllowDoubleTapZoom() const {
   6811  RecursiveMutexAutoLock lock(mRecursiveMutex);
   6812  return mZoomConstraints.mAllowDoubleTapZoom;
   6813 }
   6814 
   6815 void AsyncPanZoomController::PostDelayedTask(already_AddRefed<Runnable> aTask,
   6816                                             int aDelayMs) {
   6817  APZThreadUtils::AssertOnControllerThread();
   6818  RefPtr<Runnable> task = aTask;
   6819  RefPtr<GeckoContentController> controller = GetGeckoContentController();
   6820  if (controller) {
   6821    controller->PostDelayedTask(task.forget(), aDelayMs);
   6822  }
   6823  // If there is no controller, that means this APZC has been destroyed, and
   6824  // we probably don't need to run the task. It will get destroyed when the
   6825  // RefPtr goes out of scope.
   6826 }
   6827 
   6828 bool AsyncPanZoomController::Matches(const ScrollableLayerGuid& aGuid) {
   6829  return aGuid == GetGuid();
   6830 }
   6831 
   6832 bool AsyncPanZoomController::HasTreeManager(
   6833    const APZCTreeManager* aTreeManager) const {
   6834  return GetApzcTreeManager() == aTreeManager;
   6835 }
   6836 
   6837 void AsyncPanZoomController::GetGuid(ScrollableLayerGuid* aGuidOut) const {
   6838  if (aGuidOut) {
   6839    *aGuidOut = GetGuid();
   6840  }
   6841 }
   6842 
   6843 ScrollableLayerGuid AsyncPanZoomController::GetGuid() const {
   6844  RecursiveMutexAutoLock lock(mRecursiveMutex);
   6845  return ScrollableLayerGuid(mLayersId, Metrics().GetPresShellId(),
   6846                             Metrics().GetScrollId());
   6847 }
   6848 
   6849 void AsyncPanZoomController::SetTestAsyncScrollOffset(const CSSPoint& aPoint) {
   6850  RecursiveMutexAutoLock lock(mRecursiveMutex);
   6851  mTestAsyncScrollOffset = aPoint;
   6852  ScheduleComposite();
   6853 }
   6854 
   6855 void AsyncPanZoomController::SetTestAsyncZoom(
   6856    const LayerToParentLayerScale& aZoom) {
   6857  RecursiveMutexAutoLock lock(mRecursiveMutex);
   6858  mTestAsyncZoom = aZoom;
   6859  ScheduleComposite();
   6860 }
   6861 
   6862 Maybe<CSSSnapDestination> AsyncPanZoomController::FindSnapPointNear(
   6863    const CSSPoint& aDestination, ScrollUnit aUnit,
   6864    ScrollSnapFlags aSnapFlags) {
   6865  mRecursiveMutex.AssertCurrentThreadIn();
   6866  APZC_LOG("%p scroll snapping near %s\n", this,
   6867           ToString(aDestination).c_str());
   6868  CSSRect scrollRange = Metrics().CalculateScrollRange();
   6869  if (auto snapDestination = ScrollSnapUtils::GetSnapPointForDestination(
   6870          mScrollMetadata.GetSnapInfo(), aUnit, aSnapFlags,
   6871          CSSRect::ToAppUnits(scrollRange),
   6872          CSSPoint::ToAppUnits(Metrics().GetVisualScrollOffset()),
   6873          CSSPoint::ToAppUnits(aDestination))) {
   6874    CSSPoint cssSnapPoint = CSSPoint::FromAppUnits(snapDestination->mPosition);
   6875    // GetSnapPointForDestination() can produce a destination that's outside
   6876    // of the scroll frame's scroll range. Clamp it here (this matches the
   6877    // behaviour of the main-thread code path, which clamps it in
   6878    // ScrollContainerFrame::ScrollTo()).
   6879    return Some(CSSSnapDestination{scrollRange.ClampPoint(cssSnapPoint),
   6880                                   snapDestination->mTargetIds});
   6881  }
   6882  return Nothing();
   6883 }
   6884 
   6885 Maybe<std::pair<MultiTouchInput, MultiTouchInput>>
   6886 AsyncPanZoomController::MaybeSplitTouchMoveEvent(
   6887    const MultiTouchInput& aOriginalEvent, ScreenCoord aPanThreshold,
   6888    float aVectorLength, ExternalPoint& aExtPoint) {
   6889  if (aVectorLength <= aPanThreshold) {
   6890    return Nothing();
   6891  }
   6892 
   6893  auto splitEvent = std::make_pair(aOriginalEvent, aOriginalEvent);
   6894 
   6895  SingleTouchData& firstTouchData = splitEvent.first.mTouches[0];
   6896  SingleTouchData& secondTouchData = splitEvent.second.mTouches[0];
   6897 
   6898  firstTouchData.mHistoricalData.Clear();
   6899  secondTouchData.mHistoricalData.Clear();
   6900 
   6901  ExternalPoint destination = aExtPoint;
   6902  ExternalPoint thresholdPosition;
   6903 
   6904  const float ratio = aPanThreshold / aVectorLength;
   6905  thresholdPosition.x = mStartTouch.x + ratio * (destination.x - mStartTouch.x);
   6906  thresholdPosition.y = mStartTouch.y + ratio * (destination.y - mStartTouch.y);
   6907 
   6908  TouchSample start{mLastTouch};
   6909  // To compute the timestamp of the first event (which is at the threshold),
   6910  // use linear interpolation with the starting point |start| being the last
   6911  // event that's before the threshold, and the end point |end| being the first
   6912  // event after the threshold.
   6913 
   6914  // The initial choice for |start| is the last touch event before
   6915  // |aOriginalEvent|, and the initial choice for |end| is |aOriginalEvent|.
   6916 
   6917  // However, the historical data points stored in |aOriginalEvent| may contain
   6918  // intermediate positions that can serve as tighter bounds for the
   6919  // interpolation.
   6920  TouchSample end{destination, aOriginalEvent.mTimeStamp};
   6921 
   6922  for (const auto& historicalData :
   6923       aOriginalEvent.mTouches[0].mHistoricalData) {
   6924    ExternalPoint histExtPoint = ToExternalPoint(aOriginalEvent.mScreenOffset,
   6925                                                 historicalData.mScreenPoint);
   6926 
   6927    if (PanVector(histExtPoint).Length() <
   6928        PanVector(thresholdPosition).Length()) {
   6929      start = {histExtPoint, historicalData.mTimeStamp};
   6930    } else {
   6931      break;
   6932    }
   6933  }
   6934 
   6935  for (const SingleTouchData::HistoricalTouchData& histData :
   6936       Reversed(aOriginalEvent.mTouches[0].mHistoricalData)) {
   6937    ExternalPoint histExtPoint =
   6938        ToExternalPoint(aOriginalEvent.mScreenOffset, histData.mScreenPoint);
   6939 
   6940    if (PanVector(histExtPoint).Length() >
   6941        PanVector(thresholdPosition).Length()) {
   6942      end = {histExtPoint, histData.mTimeStamp};
   6943    } else {
   6944      break;
   6945    }
   6946  }
   6947 
   6948  const float totalLength =
   6949      ScreenPoint(fabs(end.mPosition.x - start.mPosition.x),
   6950                  fabs(end.mPosition.y - start.mPosition.y))
   6951          .Length();
   6952  const float thresholdLength =
   6953      ScreenPoint(fabs(thresholdPosition.x - start.mPosition.x),
   6954                  fabs(thresholdPosition.y - start.mPosition.y))
   6955          .Length();
   6956  const float splitRatio = thresholdLength / totalLength;
   6957 
   6958  splitEvent.first.mTimeStamp =
   6959      start.mTimeStamp +
   6960      (end.mTimeStamp - start.mTimeStamp).MultDouble(splitRatio);
   6961 
   6962  for (const auto& historicalData :
   6963       aOriginalEvent.mTouches[0].mHistoricalData) {
   6964    if (historicalData.mTimeStamp > splitEvent.first.mTimeStamp) {
   6965      secondTouchData.mHistoricalData.AppendElement(historicalData);
   6966    } else {
   6967      firstTouchData.mHistoricalData.AppendElement(historicalData);
   6968    }
   6969  }
   6970 
   6971  firstTouchData.mScreenPoint = RoundedToInt(
   6972      ViewAs<ScreenPixel>(thresholdPosition - splitEvent.first.mScreenOffset,
   6973                          PixelCastJustification::ExternalIsScreen));
   6974 
   6975  // Recompute firstTouchData.mLocalScreenPoint.
   6976  splitEvent.first.TransformToLocal(
   6977      GetCurrentTouchBlock()->GetTransformToApzc());
   6978 
   6979  // Pass |thresholdPosition| back out to the caller via |aExtPoint|
   6980  aExtPoint = thresholdPosition;
   6981 
   6982  return Some(splitEvent);
   6983 }
   6984 
   6985 void AsyncPanZoomController::ScrollSnapNear(const CSSPoint& aDestination,
   6986                                            ScrollSnapFlags aSnapFlags) {
   6987  if (Maybe<CSSSnapDestination> snapDestination = FindSnapPointNear(
   6988          aDestination, ScrollUnit::DEVICE_PIXELS, aSnapFlags)) {
   6989    if (snapDestination->mPosition != Metrics().GetVisualScrollOffset()) {
   6990      APZC_LOG("%p smooth scrolling to snap point %s\n", this,
   6991               ToString(snapDestination->mPosition).c_str());
   6992      SmoothScrollTo(std::move(*snapDestination), ScrollTriggeredByScript::No,
   6993                     ScrollAnimationKind::SmoothMsd, ViewportType::Visual,
   6994                     ScrollOrigin::NotSpecified, GetFrameTime().Time());
   6995    }
   6996  }
   6997 }
   6998 
   6999 void AsyncPanZoomController::ScrollSnap(ScrollSnapFlags aSnapFlags) {
   7000  RecursiveMutexAutoLock lock(mRecursiveMutex);
   7001  ScrollSnapNear(Metrics().GetVisualScrollOffset(), aSnapFlags);
   7002 }
   7003 
   7004 void AsyncPanZoomController::ScrollSnapToDestination() {
   7005  RecursiveMutexAutoLock lock(mRecursiveMutex);
   7006 
   7007  float friction = StaticPrefs::apz_fling_friction();
   7008  ParentLayerPoint velocity(mX.GetVelocity(), mY.GetVelocity());
   7009  ParentLayerPoint predictedDelta;
   7010  // "-velocity / log(1.0 - friction)" is the integral of the deceleration
   7011  // curve modeled for flings in the "Axis" class.
   7012  if (velocity.x != 0.0f && friction != 0.0f) {
   7013    predictedDelta.x = -velocity.x / log(1.0 - friction);
   7014  }
   7015  if (velocity.y != 0.0f && friction != 0.0f) {
   7016    predictedDelta.y = -velocity.y / log(1.0 - friction);
   7017  }
   7018 
   7019  // If the fling will overscroll, don't scroll snap, because then the user
   7020  // user would not see any overscroll animation.
   7021  bool flingWillOverscroll =
   7022      IsOverscrolled() && ((velocity.x.value * mX.GetOverscroll() >= 0) ||
   7023                           (velocity.y.value * mY.GetOverscroll() >= 0));
   7024  if (flingWillOverscroll) {
   7025    return;
   7026  }
   7027 
   7028  CSSPoint startPosition = Metrics().GetVisualScrollOffset();
   7029  ScrollSnapFlags snapFlags = ScrollSnapFlags::IntendedEndPosition;
   7030  if (predictedDelta != ParentLayerPoint()) {
   7031    snapFlags |= ScrollSnapFlags::IntendedDirection;
   7032  }
   7033  if (Maybe<CSSSnapDestination> snapDestination =
   7034          MaybeAdjustDeltaForScrollSnapping(ScrollUnit::DEVICE_PIXELS,
   7035                                            snapFlags, predictedDelta,
   7036                                            startPosition)) {
   7037    APZC_LOG(
   7038        "%p fling snapping.  friction: %f velocity: %f, %f "
   7039        "predictedDelta: %f, %f position: %f, %f "
   7040        "snapDestination: %f, %f\n",
   7041        this, friction, velocity.x.value, velocity.y.value,
   7042        predictedDelta.x.value, predictedDelta.y.value,
   7043        Metrics().GetVisualScrollOffset().x.value,
   7044        Metrics().GetVisualScrollOffset().y.value, startPosition.x.value,
   7045        startPosition.y.value);
   7046 
   7047    // Ensure that any queued transform-end due to a pan-end is not
   7048    // sent. Instead rely on the transform-end sent due to the
   7049    // scroll snap animation.
   7050    SetDelayedTransformEnd(false);
   7051 
   7052    SmoothScrollTo(std::move(*snapDestination), ScrollTriggeredByScript::No,
   7053                   ScrollAnimationKind::SmoothMsd, ViewportType::Visual,
   7054                   ScrollOrigin::NotSpecified, GetFrameTime().Time());
   7055  }
   7056 }
   7057 
   7058 Maybe<CSSSnapDestination>
   7059 AsyncPanZoomController::MaybeAdjustDeltaForScrollSnapping(
   7060    ScrollUnit aUnit, ScrollSnapFlags aSnapFlags, ParentLayerPoint& aDelta,
   7061    CSSPoint& aStartPosition) {
   7062  RecursiveMutexAutoLock lock(mRecursiveMutex);
   7063  CSSToParentLayerScale zoom = Metrics().GetZoom();
   7064  if (zoom == CSSToParentLayerScale(0)) {
   7065    return Nothing();
   7066  }
   7067  CSSPoint destination = Metrics().CalculateScrollRange().ClampPoint(
   7068      aStartPosition + ToCSSPixels(aDelta));
   7069 
   7070  if (Maybe<CSSSnapDestination> snapDestination =
   7071          FindSnapPointNear(destination, aUnit, aSnapFlags)) {
   7072    aDelta = (snapDestination->mPosition - aStartPosition) * zoom;
   7073    aStartPosition = snapDestination->mPosition;
   7074    return snapDestination;
   7075  }
   7076  return Nothing();
   7077 }
   7078 
   7079 Maybe<CSSSnapDestination>
   7080 AsyncPanZoomController::MaybeAdjustDeltaForScrollSnappingOnWheelInput(
   7081    const ScrollWheelInput& aEvent, ParentLayerPoint& aDelta,
   7082    CSSPoint& aStartPosition) {
   7083  // Don't scroll snap for pixel scrolls. This matches the main thread
   7084  // behaviour in EventStateManager::DoScrollText().
   7085  if (aEvent.mDeltaType == ScrollWheelInput::SCROLLDELTA_PIXEL) {
   7086    return Nothing();
   7087  }
   7088 
   7089  // Note that this MaybeAdjustDeltaForScrollSnappingOnWheelInput also gets
   7090  // called for pan gestures at least on older Mac and Windows. In such cases
   7091  // `aEvent.mDeltaType` is `SCROLLDELTA_PIXEL` which should be filtered out by
   7092  // the above `if` block, so we assume all incoming `aEvent` are purely wheel
   7093  // events, thus we basically use `IntendedDirection` here.
   7094  // If we want to change the behavior, i.e. we want to do scroll snap for
   7095  // such cases as well, we need to use `IntendedEndPoint`.
   7096  ScrollSnapFlags snapFlags = ScrollSnapFlags::IntendedDirection;
   7097  if (aEvent.mDeltaType == ScrollWheelInput::SCROLLDELTA_PAGE) {
   7098    // On Windows there are a couple of cases where scroll events happen with
   7099    // SCROLLDELTA_PAGE, in such case we consider it's a page scroll.
   7100    snapFlags |= ScrollSnapFlags::IntendedEndPosition;
   7101  }
   7102  return MaybeAdjustDeltaForScrollSnapping(
   7103      ScrollWheelInput::ScrollUnitForDeltaType(aEvent.mDeltaType),
   7104      ScrollSnapFlags::IntendedDirection, aDelta, aStartPosition);
   7105 }
   7106 
   7107 Maybe<CSSSnapDestination>
   7108 AsyncPanZoomController::MaybeAdjustDestinationForScrollSnapping(
   7109    const KeyboardInput& aEvent, CSSPoint& aDestination,
   7110    ScrollSnapFlags aSnapFlags) {
   7111  RecursiveMutexAutoLock lock(mRecursiveMutex);
   7112  ScrollUnit unit = KeyboardScrollAction::GetScrollUnit(aEvent.mAction.mType);
   7113 
   7114  if (Maybe<CSSSnapDestination> snapPoint =
   7115          FindSnapPointNear(aDestination, unit, aSnapFlags)) {
   7116    aDestination = snapPoint->mPosition;
   7117    return snapPoint;
   7118  }
   7119  return Nothing();
   7120 }
   7121 
   7122 void AsyncPanZoomController::SetZoomAnimationId(
   7123    const Maybe<uint64_t>& aZoomAnimationId) {
   7124  RecursiveMutexAutoLock lock(mRecursiveMutex);
   7125  mZoomAnimationId = aZoomAnimationId;
   7126 }
   7127 
   7128 Maybe<uint64_t> AsyncPanZoomController::GetZoomAnimationId() const {
   7129  RecursiveMutexAutoLock lock(mRecursiveMutex);
   7130  return mZoomAnimationId;
   7131 }
   7132 
   7133 CSSPoint AsyncPanZoomController::MaybeFillOutOverscrollGutter(
   7134    const RecursiveMutexAutoLock& aProofOfLock) {
   7135  CSSPoint delta = ToCSSPixels(GetOverscrollAmount());
   7136  CSSPoint origin = Metrics().GetVisualScrollOffset();
   7137  CSSRect scrollRange = Metrics().CalculateScrollRange();
   7138  if (!scrollRange.ContainsInclusively(origin + delta)) {
   7139    return CSSPoint();
   7140  }
   7141  SetVisualScrollOffset(origin + delta);
   7142  Metrics().RecalculateLayoutViewportOffset();
   7143  return Metrics().GetVisualScrollOffset() - origin;
   7144 }
   7145 
   7146 ScreenMargin AsyncPanZoomController::GetFixedLayerMargins(
   7147    const RecursiveMutexAutoLock& aProofOfLock) const {
   7148  return mCompositorFixedLayerMargins;
   7149 }
   7150 
   7151 void AsyncPanZoomController::SetFixedLayerMargins(const ScreenMargin& aMargin) {
   7152  RecursiveMutexAutoLock lock(mRecursiveMutex);
   7153  mCompositorFixedLayerMargins = aMargin;
   7154 }
   7155 
   7156 std::ostream& operator<<(std::ostream& aOut,
   7157                         const AsyncPanZoomController::PanZoomState& aState) {
   7158  switch (aState) {
   7159    case AsyncPanZoomController::PanZoomState::NOTHING:
   7160      aOut << "NOTHING";
   7161      break;
   7162    case AsyncPanZoomController::PanZoomState::FLING:
   7163      aOut << "FLING";
   7164      break;
   7165    case AsyncPanZoomController::PanZoomState::TOUCHING:
   7166      aOut << "TOUCHING";
   7167      break;
   7168    case AsyncPanZoomController::PanZoomState::PANNING:
   7169      aOut << "PANNING";
   7170      break;
   7171    case AsyncPanZoomController::PanZoomState::PANNING_LOCKED_X:
   7172      aOut << "PANNING_LOCKED_X";
   7173      break;
   7174    case AsyncPanZoomController::PanZoomState::PANNING_LOCKED_Y:
   7175      aOut << "PANNING_LOCKED_Y";
   7176      break;
   7177    case AsyncPanZoomController::PanZoomState::PAN_MOMENTUM:
   7178      aOut << "PAN_MOMENTUM";
   7179      break;
   7180    case AsyncPanZoomController::PanZoomState::PINCHING:
   7181      aOut << "PINCHING";
   7182      break;
   7183    case AsyncPanZoomController::PanZoomState::ANIMATING_ZOOM:
   7184      aOut << "ANIMATING_ZOOM";
   7185      break;
   7186    case AsyncPanZoomController::PanZoomState::OVERSCROLL_ANIMATION:
   7187      aOut << "OVERSCROLL_ANIMATION";
   7188      break;
   7189    case AsyncPanZoomController::PanZoomState::SMOOTH_SCROLL:
   7190      aOut << "SMOOTH_SCROLL";
   7191      break;
   7192    case AsyncPanZoomController::PanZoomState::AUTOSCROLL:
   7193      aOut << "AUTOSCROLL";
   7194      break;
   7195    case AsyncPanZoomController::PanZoomState::SCROLLBAR_DRAG:
   7196      aOut << "SCROLLBAR_DRAG";
   7197      break;
   7198    default:
   7199      aOut << "UNKNOWN_STATE";
   7200      break;
   7201  }
   7202  return aOut;
   7203 }
   7204 
   7205 bool operator==(const PointerEventsConsumableFlags& aLhs,
   7206                const PointerEventsConsumableFlags& aRhs) {
   7207  return (aLhs.mHasRoom == aRhs.mHasRoom) &&
   7208         (aLhs.mAllowedByTouchAction == aRhs.mAllowedByTouchAction);
   7209 }
   7210 
   7211 std::ostream& operator<<(std::ostream& aOut,
   7212                         const PointerEventsConsumableFlags& aFlags) {
   7213  aOut << std::boolalpha << "{ hasRoom: " << aFlags.mHasRoom
   7214       << ", allowedByTouchAction: " << aFlags.mAllowedByTouchAction << "}";
   7215  return aOut;
   7216 }
   7217 
   7218 }  // namespace layers
   7219 }  // namespace mozilla