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