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