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/. */
7 #include "AsyncPanZoomController.h" // for AsyncPanZoomController, etc
9 #include <math.h> // for fabsf, fabs, atan2
10 #include <stdint.h> // for uint32_t, uint64_t
11 #include <sys/types.h> // for int32_t
12 #include <algorithm> // for max, min
13 #include <utility> // for std::make_pair
15 #include "APZCTreeManager.h" // for APZCTreeManager
16 #include "AsyncPanZoomAnimation.h" // for AsyncPanZoomAnimation
17 #include "AutoDirWheelDeltaAdjuster.h" // for APZAutoDirWheelDeltaAdjuster
18 #include "AutoscrollAnimation.h" // for AutoscrollAnimation
19 #include "Axis.h" // for AxisX, AxisY, Axis, etc
20 #include "CheckerboardEvent.h" // for CheckerboardEvent
21 #include "Compositor.h" // for Compositor
22 #include "DesktopFlingPhysics.h" // for DesktopFlingPhysics
23 #include "FrameMetrics.h" // for FrameMetrics, etc
24 #include "GenericFlingAnimation.h" // for GenericFlingAnimation
25 #include "GestureEventListener.h" // for GestureEventListener
26 #include "HitTestingTreeNode.h" // for HitTestingTreeNode
27 #include "InputData.h" // for MultiTouchInput, etc
28 #include "InputBlockState.h" // for InputBlockState, TouchBlockState
29 #include "InputQueue.h" // for InputQueue
30 #include "Overscroll.h" // for OverscrollAnimation
31 #include "OverscrollHandoffState.h" // for OverscrollHandoffState
32 #include "SimpleVelocityTracker.h" // for SimpleVelocityTracker
33 #include "Units.h" // for CSSRect, CSSPoint, etc
34 #include "UnitTransforms.h" // for TransformTo
35 #include "base/message_loop.h" // for MessageLoop
36 #include "base/task.h" // for NewRunnableMethod, etc
37 #include "gfxTypes.h" // for gfxFloat
38 #include "mozilla/Assertions.h" // for MOZ_ASSERT, etc
39 #include "mozilla/BasicEvents.h" // for Modifiers, MODIFIER_*
40 #include "mozilla/ClearOnShutdown.h" // for ClearOnShutdown
41 #include "mozilla/ServoStyleConsts.h" // for StyleComputedTimingFunction
42 #include "mozilla/EventForwards.h" // for nsEventStatus_*
43 #include "mozilla/EventStateManager.h" // for EventStateManager
44 #include "mozilla/MouseEvents.h" // for WidgetWheelEvent
45 #include "mozilla/Preferences.h" // for Preferences
46 #include "mozilla/RecursiveMutex.h" // for RecursiveMutexAutoLock, etc
47 #include "mozilla/RefPtr.h" // for RefPtr
48 #include "mozilla/ScrollTypes.h"
49 #include "mozilla/StaticPrefs_apz.h"
50 #include "mozilla/StaticPrefs_general.h"
51 #include "mozilla/StaticPrefs_gfx.h"
52 #include "mozilla/StaticPrefs_mousewheel.h"
53 #include "mozilla/StaticPrefs_layers.h"
54 #include "mozilla/StaticPrefs_layout.h"
55 #include "mozilla/StaticPrefs_slider.h"
56 #include "mozilla/StaticPrefs_test.h"
57 #include "mozilla/StaticPrefs_toolkit.h"
58 #include "mozilla/Telemetry.h" // for Telemetry
59 #include "mozilla/TimeStamp.h" // for TimeDuration, TimeStamp
60 #include "mozilla/dom/CheckerboardReportService.h" // for CheckerboardEventStorage
61 // note: CheckerboardReportService.h actually lives in gfx/layers/apz/util/
62 #include "mozilla/dom/Touch.h" // for Touch
63 #include "mozilla/gfx/gfxVars.h" // for gfxVars
64 #include "mozilla/gfx/BasePoint.h" // for BasePoint
65 #include "mozilla/gfx/BaseRect.h" // for BaseRect
66 #include "mozilla/gfx/Point.h" // for Point, RoundedToInt, etc
67 #include "mozilla/gfx/Rect.h" // for RoundedIn
68 #include "mozilla/gfx/ScaleFactor.h" // for ScaleFactor
69 #include "mozilla/layers/APZThreadUtils.h" // for AssertOnControllerThread, etc
70 #include "mozilla/layers/APZUtils.h" // for AsyncTransform
71 #include "mozilla/layers/CompositorController.h" // for CompositorController
72 #include "mozilla/layers/DirectionUtils.h" // for GetAxis{Start,End,Length,Scale}
73 #include "mozilla/layers/APZPublicUtils.h" // for GetScrollMode
74 #include "mozilla/mozalloc.h" // for operator new, etc
75 #include "mozilla/Unused.h" // for unused
76 #include "nsAlgorithm.h" // for clamped
77 #include "nsCOMPtr.h" // for already_AddRefed
78 #include "nsDebug.h" // for NS_WARNING
79 #include "nsLayoutUtils.h"
80 #include "nsMathUtils.h" // for NS_hypot
81 #include "nsPoint.h" // for nsIntPoint
82 #include "nsStyleConsts.h"
83 #include "nsTArray.h" // for nsTArray, nsTArray_Impl, etc
84 #include "nsThreadUtils.h" // for NS_IsMainThread
85 #include "nsViewportInfo.h" // for ViewportMinScale(), ViewportMaxScale()
86 #include "prsystem.h" // for PR_GetPhysicalMemorySize
87 #include "mozilla/ipc/SharedMemoryBasic.h" // for SharedMemoryBasic
88 #include "ScrollSnap.h" // for ScrollSnapUtils
89 #include "ScrollAnimationPhysics.h" // for ComputeAcceleratedWheelDelta
90 #include "SmoothMsdScrollAnimation.h"
91 #include "SmoothScrollAnimation.h"
92 #include "WheelScrollAnimation.h"
93 #if defined(MOZ_WIDGET_ANDROID)
94 # include "AndroidAPZ.h"
95 #endif // defined(MOZ_WIDGET_ANDROID)
97 static mozilla::LazyLogModule
sApzCtlLog("apz.controller");
98 #define APZC_LOG(...) MOZ_LOG(sApzCtlLog, LogLevel::Debug, (__VA_ARGS__))
99 #define APZC_LOGV(...) MOZ_LOG(sApzCtlLog, LogLevel::Verbose, (__VA_ARGS__))
101 // Log to the apz.controller log with additional info from the APZC
102 #define APZC_LOG_DETAIL(fmt, apzc, ...) \
103 APZC_LOG("%p(%s scrollId=%" PRIu64 "): " fmt, (apzc), \
104 (apzc)->IsRootContent() ? "root" : "subframe", \
105 (apzc)->GetScrollId(), ##__VA_ARGS__)
107 #define APZC_LOG_FM_COMMON(fm, prefix, level, ...) \
108 if (MOZ_LOG_TEST(sApzCtlLog, level)) { \
109 std::stringstream ss; \
110 ss << nsPrintfCString(prefix, __VA_ARGS__).get() << ":" << fm; \
111 MOZ_LOG(sApzCtlLog, level, ("%s\n", ss.str().c_str())); \
113 #define APZC_LOG_FM(fm, prefix, ...) \
114 APZC_LOG_FM_COMMON(fm, prefix, LogLevel::Debug, __VA_ARGS__)
115 #define APZC_LOGV_FM(fm, prefix, ...) \
116 APZC_LOG_FM_COMMON(fm, prefix, LogLevel::Verbose, __VA_ARGS__)
121 typedef mozilla::layers::AllowedTouchBehavior AllowedTouchBehavior
;
122 typedef GeckoContentController::APZStateChange APZStateChange
;
123 typedef GeckoContentController::TapType TapType
;
124 typedef mozilla::gfx::Point Point
;
125 typedef mozilla::gfx::Matrix4x4 Matrix4x4
;
127 // Choose between platform-specific implementations.
128 #ifdef MOZ_WIDGET_ANDROID
129 typedef WidgetOverscrollEffect OverscrollEffect
;
130 typedef AndroidSpecificState PlatformSpecificState
;
132 typedef GenericOverscrollEffect OverscrollEffect
;
133 typedef PlatformSpecificStateBase
134 PlatformSpecificState
; // no extra state, just use the base class
138 * \page APZCPrefs APZ preferences
140 * The following prefs are used to control the behaviour of the APZC.
141 * The default values are provided in StaticPrefList.yaml.
143 * \li\b apz.allow_double_tap_zooming
144 * Pref that allows or disallows double tap to zoom
146 * \li\b apz.allow_immediate_handoff
147 * If set to true, scroll can be handed off from one APZC to another within
148 * a single input block. If set to false, a single input block can only
151 * \li\b apz.allow_zooming_out
152 * If set to true, APZ will allow zooming out past the initial scale on
153 * desktop. This is false by default to match Chrome's behaviour.
155 * \li\b apz.android.chrome_fling_physics.friction
156 * A tunable parameter for Chrome fling physics on Android that governs
157 * how quickly a fling animation slows down due to friction (and therefore
158 * also how far it reaches). Should be in the range [0-1].
160 * \li\b apz.android.chrome_fling_physics.inflexion
161 * A tunable parameter for Chrome fling physics on Android that governs
162 * the shape of the fling curve. Should be in the range [0-1].
164 * \li\b apz.android.chrome_fling_physics.stop_threshold
165 * A tunable parameter for Chrome fling physics on Android that governs
166 * how close the fling animation has to get to its target destination
168 * Units: ParentLayer pixels
170 * \li\b apz.autoscroll.enabled
171 * If set to true, autoscrolling is driven by APZ rather than the content
172 * process main thread.
174 * \li\b apz.axis_lock.mode
175 * The preferred axis locking style. See AxisLockMode for possible values.
177 * \li\b apz.axis_lock.lock_angle
178 * Angle from axis within which we stay axis-locked.\n
181 * \li\b apz.axis_lock.breakout_threshold
182 * Distance in inches the user must pan before axis lock can be broken.\n
183 * Units: (real-world, i.e. screen) inches
185 * \li\b apz.axis_lock.breakout_angle
186 * Angle at which axis lock can be broken.\n
189 * \li\b apz.axis_lock.direct_pan_angle
190 * If the angle from an axis to the line drawn by a pan move is less than
191 * this value, we can assume that panning can be done in the allowed direction
192 * (horizontal or vertical).\n
193 * Currently used only for touch-action css property stuff and was addded to
194 * keep behaviour consistent with IE.\n
197 * \li\b apz.content_response_timeout
198 * Amount of time before we timeout response from content. For example, if
199 * content is being unruly/slow and we don't get a response back within this
200 * time, we will just pretend that content did not preventDefault any touch
201 * events we dispatched to it.\n
202 * Units: milliseconds
204 * \li\b apz.danger_zone_x
205 * \li\b apz.danger_zone_y
206 * When drawing high-res tiles, we drop down to drawing low-res tiles
207 * when we know we can't keep up with the scrolling. The way we determine
208 * this is by checking if we are entering the "danger zone", which is the
209 * boundary of the painted content. For example, if the painted content
210 * goes from y=0...1000 and the visible portion is y=250...750 then
211 * we're far from checkerboarding. If we get to y=490...990 though then we're
212 * only 10 pixels away from showing checkerboarding so we are probably in
213 * a state where we can't keep up with scrolling. The danger zone prefs specify
214 * how wide this margin is; in the above example a y-axis danger zone of 10
215 * pixels would make us drop to low-res at y=490...990.\n
216 * This value is in screen pixels.
218 * \li\b apz.disable_for_scroll_linked_effects
219 * Setting this pref to true will disable APZ scrolling on documents where
220 * scroll-linked effects are detected. A scroll linked effect is detected if
221 * positioning or transform properties are updated inside a scroll event
222 * dispatch; we assume that such an update is in response to the scroll event
223 * and is therefore a scroll-linked effect which will be laggy with APZ
226 * \li\b apz.displayport_expiry_ms
227 * While a scrollable frame is scrolling async, we set a displayport on it
228 * to make sure it is layerized. However this takes up memory, so once the
229 * scrolling stops we want to remove the displayport. This pref controls how
230 * long after scrolling stops the displayport is removed. A value of 0 will
231 * disable the expiry behavior entirely.
232 * Units: milliseconds
234 * \li\b apz.drag.enabled
235 * Setting this pref to true will cause APZ to handle mouse-dragging of
238 * \li\b apz.drag.initial.enabled
239 * Setting this pref to true will cause APZ to try to handle mouse-dragging
240 * of scrollbar thumbs without an initial round-trip to content to start it
241 * if possible. Only has an effect if apz.drag.enabled is also true.
243 * \li\b apz.drag.touch.enabled
244 * Setting this pref to true will cause APZ to handle touch-dragging of
245 * scrollbar thumbs. Only has an effect if apz.drag.enabled is also true.
247 * \li\b apz.enlarge_displayport_when_clipped
248 * Pref that enables enlarging of the displayport along one axis when the
249 * generated displayport's size is beyond that of the scrollable rect on the
252 * \li\b apz.fling_accel_min_fling_velocity
253 * The minimum velocity of the second fling, and the minimum velocity of the
254 * previous fling animation at the point of interruption, for the new fling to
255 * be considered for fling acceleration.
256 * Units: screen pixels per milliseconds
258 * \li\b apz.fling_accel_min_pan_velocity
259 * The minimum velocity during the pan gesture that causes a fling for that
260 * fling to be considered for fling acceleration.
261 * Units: screen pixels per milliseconds
263 * \li\b apz.fling_accel_max_pause_interval_ms
264 * The maximum time that is allowed to elapse between the touch start event that
265 * interrupts the previous fling, and the touch move that initiates panning for
266 * the current fling, for that fling to be considered for fling acceleration.
267 * Units: milliseconds
269 * \li\b apz.fling_accel_base_mult
270 * \li\b apz.fling_accel_supplemental_mult
271 * When applying an acceleration on a fling, the new computed velocity is
272 * (new_fling_velocity * base_mult) + (old_velocity * supplemental_mult).
273 * The base_mult and supplemental_mult multiplier values are controlled by
274 * these prefs. Note that "old_velocity" here is the initial velocity of the
275 * previous fling _after_ acceleration was applied to it (if applicable).
277 * \li\b apz.fling_curve_function_x1
278 * \li\b apz.fling_curve_function_y1
279 * \li\b apz.fling_curve_function_x2
280 * \li\b apz.fling_curve_function_y2
281 * \li\b apz.fling_curve_threshold_inches_per_ms
282 * These five parameters define a Bezier curve function and threshold used to
283 * increase the actual velocity relative to the user's finger velocity. When the
284 * finger velocity is below the threshold (or if the threshold is not positive),
285 * the velocity is used as-is. If the finger velocity exceeds the threshold
286 * velocity, then the function defined by the curve is applied on the part of
287 * the velocity that exceeds the threshold. Note that the upper bound of the
288 * velocity is still specified by the \b apz.max_velocity_inches_per_ms pref,
289 * and the function will smoothly curve the velocity from the threshold to the
290 * max. In general the function parameters chosen should define an ease-out
291 * curve in order to increase the velocity in this range, or an ease-in curve to
292 * decrease the velocity. A straight-line curve is equivalent to disabling the
293 * curve entirely by setting the threshold to -1. The max velocity pref must
294 * also be set in order for the curving to take effect, as it defines the upper
295 * bound of the velocity curve.\n
296 * The points (x1, y1) and (x2, y2) used as the two intermediate control points
297 * in the cubic bezier curve; the first and last points are (0,0) and (1,1).\n
298 * Some example values for these prefs can be found at\n
299 * https://searchfox.org/mozilla-central/rev/f82d5c549f046cb64ce5602bfd894b7ae807c8f8/dom/animation/ComputedTimingFunction.cpp#27-33
301 * \li\b apz.fling_friction
302 * Amount of friction applied during flings. This is used in the following
303 * formula: v(t1) = v(t0) * (1 - f)^(t1 - t0), where v(t1) is the velocity
304 * for a new sample, v(t0) is the velocity at the previous sample, f is the
305 * value of this pref, and (t1 - t0) is the amount of time, in milliseconds,
306 * that has elapsed between the two samples.\n
307 * NOTE: Not currently used in Android fling calculations.
309 * \li\b apz.fling_min_velocity_threshold
310 * Minimum velocity for a fling to actually kick off. If the user pans and lifts
311 * their finger such that the velocity is smaller than or equal to this amount,
312 * no fling is initiated.\n
313 * Units: screen pixels per millisecond
315 * \li\b apz.fling_stop_on_tap_threshold
316 * When flinging, if the velocity is above this number, then a tap on the
317 * screen will stop the fling without dispatching a tap to content. If the
318 * velocity is below this threshold a tap will also be dispatched.
319 * Note: when modifying this pref be sure to run the APZC gtests as some of
320 * them depend on the value of this pref.\n
321 * Units: screen pixels per millisecond
323 * \li\b apz.fling_stopped_threshold
324 * When flinging, if the velocity goes below this number, we just stop the
325 * animation completely. This is to prevent asymptotically approaching 0
326 * velocity and rerendering unnecessarily.\n
327 * Units: screen pixels per millisecond.\n
328 * NOTE: Should not be set to anything
329 * other than 0.0 for Android except for tests to disable flings.
331 * \li\b apz.keyboard.enabled
332 * Determines whether scrolling with the keyboard will be allowed to be handled
335 * \li\b apz.keyboard.passive-listeners
336 * When enabled, APZ will interpret the passive event listener flag to mean
337 * that the event listener won't change the focused element or selection of
338 * the page. With this, web content can use passive key listeners and not have
339 * keyboard APZ disabled.
341 * \li\b apz.max_tap_time
342 * Maximum time for a touch on the screen and corresponding lift of the finger
343 * to be considered a tap. This also applies to double taps, except that it is
344 * used both for the interval between the first touchdown and first touchup,
345 * and for the interval between the first touchup and the second touchdown.\n
346 * Units: milliseconds.
348 * \li\b apz.max_velocity_inches_per_ms
349 * Maximum velocity. Velocity will be capped at this value if a faster fling
350 * occurs. Negative values indicate unlimited velocity.\n
351 * Units: (real-world, i.e. screen) inches per millisecond
353 * \li\b apz.max_velocity_queue_size
354 * Maximum size of velocity queue. The queue contains last N velocity records.
355 * On touch end we calculate the average velocity in order to compensate
356 * touch/mouse drivers misbehaviour.
358 * \li\b apz.min_skate_speed
359 * Minimum amount of speed along an axis before we switch to "skate" multipliers
360 * rather than using the "stationary" multipliers.\n
361 * Units: CSS pixels per millisecond
363 * \li\b apz.one_touch_pinch.enabled
364 * Whether or not the "one-touch-pinch" gesture (for zooming with one finger)
367 * \li\b apz.overscroll.enabled
368 * Pref that enables overscrolling. If this is disabled, excess scroll that
369 * cannot be handed off is discarded.
371 * \li\b apz.overscroll.min_pan_distance_ratio
372 * The minimum ratio of the pan distance along one axis to the pan distance
373 * along the other axis needed to initiate overscroll along the first axis
376 * \li\b apz.overscroll.stretch_factor
377 * How much overscrolling can stretch content along an axis.
378 * The maximum stretch along an axis is a factor of (1 + kStretchFactor).
379 * (So if kStretchFactor is 0, you can't stretch at all; if kStretchFactor
380 * is 1, you can stretch at most by a factor of 2).
382 * \li\b apz.overscroll.stop_distance_threshold
383 * \li\b apz.overscroll.stop_velocity_threshold
384 * Thresholds for stopping the overscroll animation. When both the distance
385 * and the velocity fall below their thresholds, we stop oscillating.\n
386 * Units: screen pixels (for distance)
387 * screen pixels per millisecond (for velocity)
389 * \li\b apz.overscroll.spring_stiffness
390 * The spring stiffness constant for the overscroll mass-spring-damper model.
392 * \li\b apz.overscroll.damping
393 * The damping constant for the overscroll mass-spring-damper model.
395 * \li\b apz.overscroll.max_velocity
396 * The maximum velocity (in ParentLayerPixels per millisecond) allowed when
397 * initiating the overscroll snap-back animation.
399 * \li\b apz.paint_skipping.enabled
400 * When APZ is scrolling and sending repaint requests to the main thread, often
401 * the main thread doesn't actually need to do a repaint. This pref allows the
402 * main thread to skip doing those repaints in cases where it doesn't need to.
404 * \li\b apz.pinch_lock.mode
405 * The preferred pinch locking style. See PinchLockMode for possible values.
407 * \li\b apz.pinch_lock.scroll_lock_threshold
408 * Pinch locking is triggered if the user scrolls more than this distance
409 * and pinches less than apz.pinch_lock.span_lock_threshold.\n
410 * Units: (real-world, i.e. screen) inches
412 * \li\b apz.pinch_lock.span_breakout_threshold
413 * Distance in inches the user must pinch before lock can be broken.\n
414 * Units: (real-world, i.e. screen) inches measured between two touch points
416 * \li\b apz.pinch_lock.span_lock_threshold
417 * Pinch locking is triggered if the user pinches less than this distance
418 * and scrolls more than apz.pinch_lock.scroll_lock_threshold.\n
419 * Units: (real-world, i.e. screen) inches measured between two touch points
421 * \li\b apz.pinch_lock.buffer_max_age
422 * To ensure that pinch locking threshold calculations are not affected by
423 * variations in touch screen sensitivity, calculations draw from a buffer of
424 * recent events. This preference specifies the maximum time that events are
425 * held in this buffer.
426 * Units: milliseconds
428 * \li\b apz.popups.enabled
429 * Determines whether APZ is used for XUL popup widgets with remote content.
430 * Ideally, this should always be true, but it is currently not well tested, and
431 * has known issues, so needs to be prefable.
433 * \li\b apz.record_checkerboarding
434 * Whether or not to record detailed info on checkerboarding events.
436 * \li\b apz.second_tap_tolerance
437 * Constant describing the tolerance in distance we use, multiplied by the
438 * device DPI, within which a second tap is counted as part of a gesture
439 * continuing from the first tap. Making this larger allows the user more
440 * distance between the first and second taps in a "double tap" or "one touch
442 * Units: (real-world, i.e. screen) inches
444 * \li\b apz.test.logging_enabled
445 * Enable logging of APZ test data (see bug 961289).
447 * \li\b apz.touch_move_tolerance
448 * See the description for apz.touch_start_tolerance below. This is a similar
449 * threshold, except it is used to suppress touchmove events from being
450 * delivered to content for NON-scrollable frames (or more precisely, for APZCs
451 * where ArePointerEventsConsumable returns false).\n Units: (real-world, i.e.
454 * \li\b apz.touch_start_tolerance
455 * Constant describing the tolerance in distance we use, multiplied by the
456 * device DPI, before we start panning the screen. This is to prevent us from
457 * accidentally processing taps as touch moves, and from very short/accidental
458 * touches moving the screen. touchmove events are also not delivered to content
459 * within this distance on scrollable frames.\n
460 * Units: (real-world, i.e. screen) inches
462 * \li\b apz.velocity_bias
463 * How much to adjust the displayport in the direction of scrolling. This value
464 * is multiplied by the velocity and added to the displayport offset.
466 * \li\b apz.velocity_relevance_time_ms
467 * When computing a fling velocity from the most recently stored velocity
468 * information, only velocities within the most X milliseconds are used.
469 * This pref controls the value of X.\n
472 * \li\b apz.x_skate_size_multiplier
473 * \li\b apz.y_skate_size_multiplier
474 * The multiplier we apply to the displayport size if it is skating (current
475 * velocity is above \b apz.min_skate_speed). We prefer to increase the size of
476 * the Y axis because it is more natural in the case that a user is reading a
477 * page page that scrolls up/down. Note that one, both or neither of these may
478 * be used at any instant.\n In general we want \b
479 * apz.[xy]_skate_size_multiplier to be smaller than the corresponding
480 * stationary size multiplier because when panning fast we would like to paint
481 * less and get faster, more predictable paint times. When panning slowly we
482 * can afford to paint more even though it's slower.
484 * \li\b apz.x_stationary_size_multiplier
485 * \li\b apz.y_stationary_size_multiplier
486 * The multiplier we apply to the displayport size if it is not skating (see
487 * documentation for the skate size multipliers above).
489 * \li\b apz.x_skate_highmem_adjust
490 * \li\b apz.y_skate_highmem_adjust
491 * On high memory systems, we adjust the displayport during skating
492 * to be larger so we can reduce checkerboarding.
494 * \li\b apz.zoom_animation_duration_ms
495 * This controls how long the zoom-to-rect animation takes.\n
498 * \li\b apz.scale_repaint_delay_ms
499 * How long to delay between repaint requests during a scale.
500 * A negative number prevents repaint requests during a scale.\n
505 * Computed time function used for sampling frames of a zoom to animation.
507 StaticAutoPtr
<StyleComputedTimingFunction
> gZoomAnimationFunction
;
510 * Computed time function used for curving up velocity when it gets high.
512 StaticAutoPtr
<StyleComputedTimingFunction
> gVelocityCurveFunction
;
515 * The estimated duration of a paint for the purposes of calculating a new
516 * displayport, in milliseconds.
518 static const double kDefaultEstimatedPaintDurationMs
= 50;
521 * Returns true if this is a high memory system and we can use
522 * extra memory for a larger displayport to reduce checkerboarding.
524 static bool gIsHighMemSystem
= false;
525 static bool IsHighMemSystem() { return gIsHighMemSystem
; }
527 AsyncPanZoomAnimation
* PlatformSpecificStateBase::CreateFlingAnimation(
528 AsyncPanZoomController
& aApzc
, const FlingHandoffState
& aHandoffState
,
530 return new GenericFlingAnimation
<DesktopFlingPhysics
>(aApzc
, aHandoffState
,
534 UniquePtr
<VelocityTracker
> PlatformSpecificStateBase::CreateVelocityTracker(
536 return MakeUnique
<SimpleVelocityTracker
>(aAxis
);
539 SampleTime
AsyncPanZoomController::GetFrameTime() const {
540 APZCTreeManager
* treeManagerLocal
= GetApzcTreeManager();
541 return treeManagerLocal
? treeManagerLocal
->GetFrameTime()
542 : SampleTime::FromNow();
545 bool AsyncPanZoomController::IsZero(const ParentLayerPoint
& aPoint
) const {
546 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
548 const auto zoom
= Metrics().GetZoom();
550 if (zoom
== CSSToParentLayerScale(0)) {
554 return layers::IsZero(aPoint
/ zoom
);
557 bool AsyncPanZoomController::IsZero(ParentLayerCoord aCoord
) const {
558 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
560 const auto zoom
= Metrics().GetZoom();
562 if (zoom
== CSSToParentLayerScale(0)) {
566 return FuzzyEqualsAdditive((aCoord
/ zoom
), CSSCoord(), COORDINATE_EPSILON
);
569 bool AsyncPanZoomController::FuzzyGreater(ParentLayerCoord aCoord1
,
570 ParentLayerCoord aCoord2
) const {
571 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
573 const auto zoom
= Metrics().GetZoom();
575 if (zoom
== CSSToParentLayerScale(0)) {
579 return (aCoord1
- aCoord2
) / zoom
> COORDINATE_EPSILON
;
582 class MOZ_STACK_CLASS StateChangeNotificationBlocker final
{
584 explicit StateChangeNotificationBlocker(AsyncPanZoomController
* aApzc
)
586 RecursiveMutexAutoLock
lock(mApzc
->mRecursiveMutex
);
587 mInitialState
= mApzc
->mState
;
588 mApzc
->mNotificationBlockers
++;
591 ~StateChangeNotificationBlocker() {
592 AsyncPanZoomController::PanZoomState newState
;
594 RecursiveMutexAutoLock
lock(mApzc
->mRecursiveMutex
);
595 mApzc
->mNotificationBlockers
--;
596 newState
= mApzc
->mState
;
598 mApzc
->DispatchStateChangeNotification(mInitialState
, newState
);
602 AsyncPanZoomController
* mApzc
;
603 AsyncPanZoomController::PanZoomState mInitialState
;
607 * An RAII class to temporarily apply async test attributes to the provided
608 * AsyncPanZoomController.
610 * This class should be used in the implementation of any AsyncPanZoomController
611 * method that queries the async scroll offset or async zoom (this includes
612 * the async layout viewport offset, since modifying the async scroll offset
613 * may result in the layout viewport moving as well).
615 class MOZ_RAII AutoApplyAsyncTestAttributes final
{
617 explicit AutoApplyAsyncTestAttributes(
618 const AsyncPanZoomController
*,
619 const RecursiveMutexAutoLock
& aProofOfLock
);
620 ~AutoApplyAsyncTestAttributes();
623 AsyncPanZoomController
* mApzc
;
624 FrameMetrics mPrevFrameMetrics
;
625 ParentLayerPoint mPrevOverscroll
;
626 const RecursiveMutexAutoLock
& mProofOfLock
;
629 AutoApplyAsyncTestAttributes::AutoApplyAsyncTestAttributes(
630 const AsyncPanZoomController
* aApzc
,
631 const RecursiveMutexAutoLock
& aProofOfLock
)
632 // Having to use const_cast here seems less ugly than the alternatives
633 // of making several members of AsyncPanZoomController that
634 // ApplyAsyncTestAttributes() modifies |mutable|, or several methods that
635 // query the async transforms non-const.
636 : mApzc(const_cast<AsyncPanZoomController
*>(aApzc
)),
637 mPrevFrameMetrics(aApzc
->Metrics()),
638 mPrevOverscroll(aApzc
->GetOverscrollAmountInternal()),
639 mProofOfLock(aProofOfLock
) {
640 mApzc
->ApplyAsyncTestAttributes(aProofOfLock
);
643 AutoApplyAsyncTestAttributes::~AutoApplyAsyncTestAttributes() {
644 mApzc
->UnapplyAsyncTestAttributes(mProofOfLock
, mPrevFrameMetrics
,
648 class ZoomAnimation
: public AsyncPanZoomAnimation
{
650 ZoomAnimation(AsyncPanZoomController
& aApzc
, const CSSPoint
& aStartOffset
,
651 const CSSToParentLayerScale
& aStartZoom
,
652 const CSSPoint
& aEndOffset
,
653 const CSSToParentLayerScale
& aEndZoom
)
655 mTotalDuration(TimeDuration::FromMilliseconds(
656 StaticPrefs::apz_zoom_animation_duration_ms())),
657 mStartOffset(aStartOffset
),
658 mStartZoom(aStartZoom
),
659 mEndOffset(aEndOffset
),
660 mEndZoom(aEndZoom
) {}
662 virtual bool DoSample(FrameMetrics
& aFrameMetrics
,
663 const TimeDuration
& aDelta
) override
{
665 double animPosition
= mDuration
/ mTotalDuration
;
667 if (animPosition
>= 1.0) {
668 aFrameMetrics
.SetZoom(mEndZoom
);
669 mApzc
.SetVisualScrollOffset(mEndOffset
);
673 // Sample the zoom at the current time point. The sampled zoom
674 // will affect the final computed resolution.
675 float sampledPosition
=
676 gZoomAnimationFunction
->At(animPosition
, /* aBeforeFlag = */ false);
678 // We scale the scrollOffset linearly with sampledPosition, so the zoom
679 // needs to scale inversely to match.
680 if (mStartZoom
== CSSToParentLayerScale(0) ||
681 mEndZoom
== CSSToParentLayerScale(0)) {
685 aFrameMetrics
.SetZoom(
686 CSSToParentLayerScale(1 / (sampledPosition
/ mEndZoom
.scale
+
687 (1 - sampledPosition
) / mStartZoom
.scale
)));
689 mApzc
.SetVisualScrollOffset(CSSPoint::FromUnknownPoint(gfx::Point(
690 mEndOffset
.x
* sampledPosition
+ mStartOffset
.x
* (1 - sampledPosition
),
691 mEndOffset
.y
* sampledPosition
+
692 mStartOffset
.y
* (1 - sampledPosition
))));
696 virtual bool WantsRepaints() override
{ return true; }
699 AsyncPanZoomController
& mApzc
;
701 TimeDuration mDuration
;
702 const TimeDuration mTotalDuration
;
704 // Old metrics from before we started a zoom animation. This is only valid
705 // when we are in the "ANIMATED_ZOOM" state. This is used so that we can
706 // interpolate between the start and end frames. We only use the
707 // |mViewportScrollOffset| and |mResolution| fields on this.
708 CSSPoint mStartOffset
;
709 CSSToParentLayerScale mStartZoom
;
711 // Target metrics for a zoom to animation. This is only valid when we are in
712 // the "ANIMATED_ZOOM" state. We only use the |mViewportScrollOffset| and
713 // |mResolution| fields on this.
715 CSSToParentLayerScale mEndZoom
;
719 void AsyncPanZoomController::InitializeGlobalState() {
720 static bool sInitialized
= false;
721 if (sInitialized
) return;
724 MOZ_ASSERT(NS_IsMainThread());
726 gZoomAnimationFunction
= new StyleComputedTimingFunction(
727 StyleComputedTimingFunction::Keyword(StyleTimingKeyword::Ease
));
728 ClearOnShutdown(&gZoomAnimationFunction
);
729 gVelocityCurveFunction
=
730 new StyleComputedTimingFunction(StyleComputedTimingFunction::CubicBezier(
731 StaticPrefs::apz_fling_curve_function_x1_AtStartup(),
732 StaticPrefs::apz_fling_curve_function_y1_AtStartup(),
733 StaticPrefs::apz_fling_curve_function_x2_AtStartup(),
734 StaticPrefs::apz_fling_curve_function_y2_AtStartup()));
735 ClearOnShutdown(&gVelocityCurveFunction
);
737 uint64_t sysmem
= PR_GetPhysicalMemorySize();
738 uint64_t threshold
= 1LL << 32; // 4 GB in bytes
739 gIsHighMemSystem
= sysmem
>= threshold
;
741 PlatformSpecificState::InitializeGlobalState();
744 AsyncPanZoomController::AsyncPanZoomController(
745 LayersId aLayersId
, APZCTreeManager
* aTreeManager
,
746 const RefPtr
<InputQueue
>& aInputQueue
,
747 GeckoContentController
* aGeckoContentController
, GestureBehavior aGestures
)
748 : mLayersId(aLayersId
),
749 mGeckoContentController(aGeckoContentController
),
750 mRefPtrMonitor("RefPtrMonitor"),
751 // mTreeManager must be initialized before GetFrameTime() is called
752 mTreeManager(aTreeManager
),
753 mRecursiveMutex("AsyncPanZoomController"),
754 mLastContentPaintMetrics(mLastContentPaintMetadata
.GetMetrics()),
755 mPanDirRestricted(false),
757 mPinchEventBuffer(TimeDuration::FromMilliseconds(
758 StaticPrefs::apz_pinch_lock_buffer_max_age_AtStartup())),
759 mZoomConstraints(false, false,
760 mScrollMetadata
.GetMetrics().GetDevPixelsPerCSSPixel() *
761 ViewportMinScale() / ParentLayerToScreenScale(1),
762 mScrollMetadata
.GetMetrics().GetDevPixelsPerCSSPixel() *
763 ViewportMaxScale() / ParentLayerToScreenScale(1)),
764 mLastSampleTime(GetFrameTime()),
765 mLastCheckerboardReport(GetFrameTime()),
766 mOverscrollEffect(MakeUnique
<OverscrollEffect
>(*this)),
770 mNotificationBlockers(0),
771 mInputQueue(aInputQueue
),
772 mPinchPaintTimerSet(false),
773 mDelayedTransformEnd(false),
774 mTestAttributeAppliers(0),
775 mTestHasAsyncKeyScrolled(false),
776 mCheckerboardEventLock("APZCBELock") {
777 if (aGestures
== USE_GESTURE_DETECTOR
) {
778 mGestureEventListener
= new GestureEventListener(this);
780 // Put one default-constructed sampled state in the queue.
781 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
782 mSampledState
.emplace_back();
785 AsyncPanZoomController::~AsyncPanZoomController() { MOZ_ASSERT(IsDestroyed()); }
787 PlatformSpecificStateBase
* AsyncPanZoomController::GetPlatformSpecificState() {
788 if (!mPlatformSpecificState
) {
789 mPlatformSpecificState
= MakeUnique
<PlatformSpecificState
>();
791 return mPlatformSpecificState
.get();
794 already_AddRefed
<GeckoContentController
>
795 AsyncPanZoomController::GetGeckoContentController() const {
796 MonitorAutoLock
lock(mRefPtrMonitor
);
797 RefPtr
<GeckoContentController
> controller
= mGeckoContentController
;
798 return controller
.forget();
801 already_AddRefed
<GestureEventListener
>
802 AsyncPanZoomController::GetGestureEventListener() const {
803 MonitorAutoLock
lock(mRefPtrMonitor
);
804 RefPtr
<GestureEventListener
> listener
= mGestureEventListener
;
805 return listener
.forget();
808 const RefPtr
<InputQueue
>& AsyncPanZoomController::GetInputQueue() const {
812 void AsyncPanZoomController::Destroy() {
813 AssertOnUpdaterThread();
815 CancelAnimation(CancelAnimationFlags::ScrollSnap
);
818 MonitorAutoLock
lock(mRefPtrMonitor
);
819 mGeckoContentController
= nullptr;
820 mGestureEventListener
= nullptr;
823 mTreeManager
= nullptr;
826 bool AsyncPanZoomController::IsDestroyed() const {
827 return mTreeManager
== nullptr;
830 float AsyncPanZoomController::GetDPI() const {
831 if (APZCTreeManager
* localPtr
= mTreeManager
) {
832 return localPtr
->GetDPI();
834 // If this APZC has been destroyed then this value is not going to be
835 // used for anything that the user will end up seeing, so we can just
840 ScreenCoord
AsyncPanZoomController::GetTouchStartTolerance() const {
841 return (StaticPrefs::apz_touch_start_tolerance() * GetDPI());
844 ScreenCoord
AsyncPanZoomController::GetTouchMoveTolerance() const {
845 return (StaticPrefs::apz_touch_move_tolerance() * GetDPI());
848 ScreenCoord
AsyncPanZoomController::GetSecondTapTolerance() const {
849 return (StaticPrefs::apz_second_tap_tolerance() * GetDPI());
852 /* static */ AsyncPanZoomController::AxisLockMode
853 AsyncPanZoomController::GetAxisLockMode() {
854 return static_cast<AxisLockMode
>(StaticPrefs::apz_axis_lock_mode());
857 bool AsyncPanZoomController::UsingStatefulAxisLock() const {
858 return (GetAxisLockMode() == STANDARD
|| GetAxisLockMode() == STICKY
);
861 /* static */ AsyncPanZoomController::PinchLockMode
862 AsyncPanZoomController::GetPinchLockMode() {
863 return static_cast<PinchLockMode
>(StaticPrefs::apz_pinch_lock_mode());
866 PointerEventsConsumableFlags
AsyncPanZoomController::ArePointerEventsConsumable(
867 TouchBlockState
* aBlock
, const MultiTouchInput
& aInput
) {
868 uint32_t touchPoints
= aInput
.mTouches
.Length();
869 if (touchPoints
== 0) {
870 // Cant' do anything with zero touch points
871 return {false, false};
874 // This logic is simplified, erring on the side of returning true if we're
875 // not sure. It's safer to pretend that we can consume the event and then
876 // not be able to than vice-versa. But at the same time, we should try hard
877 // to return an accurate result, because returning true can trigger a
878 // pointercancel event to web content, which can break certain features
879 // that are using touch-action and handling the pointermove events.
881 // Note that in particular this function can return true if APZ is waiting on
882 // the main thread for touch-action information. In this scenario, the
883 // APZEventState::MainThreadAgreesEventsAreConsumableByAPZ() function tries
884 // to use the main-thread touch-action information to filter out false
887 // We could probably enhance this logic to determine things like "we're
888 // not pannable, so we can only zoom in, and the zoom is already maxed
889 // out, so we're not zoomable either" but no need for that at this point.
891 bool pannableX
= aBlock
->GetOverscrollHandoffChain()->CanScrollInDirection(
892 this, ScrollDirection::eHorizontal
);
893 bool touchActionAllowsX
= aBlock
->TouchActionAllowsPanningX();
894 bool pannableY
= (aBlock
->GetOverscrollHandoffChain()->CanScrollInDirection(
895 this, ScrollDirection::eVertical
) ||
896 // In the case of the root APZC with any dynamic toolbar, it
897 // shoule be pannable if there is room moving the dynamic
899 (IsRootContent() && CanVerticalScrollWithDynamicToolbar()));
900 bool touchActionAllowsY
= aBlock
->TouchActionAllowsPanningY();
903 bool touchActionAllowsPanning
;
905 Maybe
<ScrollDirection
> panDirection
=
906 aBlock
->GetBestGuessPanDirection(aInput
);
907 if (panDirection
== Some(ScrollDirection::eVertical
)) {
908 pannable
= pannableY
;
909 touchActionAllowsPanning
= touchActionAllowsY
;
910 } else if (panDirection
== Some(ScrollDirection::eHorizontal
)) {
911 pannable
= pannableX
;
912 touchActionAllowsPanning
= touchActionAllowsX
;
914 // If we don't have a guessed pan direction, err on the side of returning
916 pannable
= pannableX
|| pannableY
;
917 touchActionAllowsPanning
= touchActionAllowsX
|| touchActionAllowsY
;
920 if (touchPoints
== 1) {
921 return {pannable
, touchActionAllowsPanning
};
924 bool zoomable
= ZoomConstraintsAllowZoom();
925 bool touchActionAllowsZoom
= aBlock
->TouchActionAllowsPinchZoom();
927 return {pannable
|| zoomable
,
928 touchActionAllowsPanning
|| touchActionAllowsZoom
};
931 nsEventStatus
AsyncPanZoomController::HandleDragEvent(
932 const MouseInput
& aEvent
, const AsyncDragMetrics
& aDragMetrics
,
933 OuterCSSCoord aInitialThumbPos
, const CSSRect
& aInitialScrollableRect
) {
934 // RDM is a special case where touch events will be synthesized in response
935 // to mouse events, and APZ will receive both even though RDM prevent-defaults
936 // the mouse events. This is because mouse events don't opt into APZ waiting
937 // to check if the event has been prevent-defaulted and are still processed
938 // as a result. To handle this, have APZ ignore mouse events when RDM and
939 // touch simulation are active.
940 bool isRDMTouchSimulationActive
= false;
942 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
943 isRDMTouchSimulationActive
=
944 mScrollMetadata
.GetIsRDMTouchSimulationActive();
947 if (!StaticPrefs::apz_drag_enabled() || isRDMTouchSimulationActive
) {
948 return nsEventStatus_eIgnore
;
951 if (!GetApzcTreeManager()) {
952 return nsEventStatus_eConsumeNoDefault
;
956 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
958 if (aEvent
.mType
== MouseInput::MouseType::MOUSE_UP
) {
959 if (mState
== SCROLLBAR_DRAG
) {
960 APZC_LOG("%p ending drag\n", this);
964 SnapBackIfOverscrolled();
966 return nsEventStatus_eConsumeNoDefault
;
970 HitTestingTreeNodeAutoLock node
;
971 GetApzcTreeManager()->FindScrollThumbNode(aDragMetrics
, mLayersId
, node
);
973 APZC_LOG("%p unable to find scrollthumb node with viewid %" PRIu64
"\n",
974 this, aDragMetrics
.mViewId
);
975 return nsEventStatus_eConsumeNoDefault
;
978 if (aEvent
.mType
== MouseInput::MouseType::MOUSE_DOWN
) {
979 APZC_LOG("%p starting scrollbar drag\n", this);
980 SetState(SCROLLBAR_DRAG
);
983 if (aEvent
.mType
!= MouseInput::MouseType::MOUSE_MOVE
) {
984 APZC_LOG("%p discarding event of type %d\n", this, aEvent
.mType
);
985 return nsEventStatus_eConsumeNoDefault
;
988 const ScrollbarData
& scrollbarData
= node
->GetScrollbarData();
989 MOZ_ASSERT(scrollbarData
.mScrollbarLayerType
==
990 layers::ScrollbarLayerType::Thumb
);
991 MOZ_ASSERT(scrollbarData
.mDirection
.isSome());
992 ScrollDirection direction
= *scrollbarData
.mDirection
;
994 bool isMouseAwayFromThumb
= false;
995 if (int snapMultiplier
= StaticPrefs::slider_snapMultiplier()) {
996 // It's fine to ignore the async component of the thumb's transform,
997 // because any async transform of the thumb will be in the direction of
998 // scrolling, but here we're interested in the other direction.
999 ParentLayerRect thumbRect
=
1000 (node
->GetTransform() * AsyncTransformMatrix())
1001 .TransformBounds(LayerRect(node
->GetVisibleRegion().GetBounds()));
1002 ScrollDirection otherDirection
= GetPerpendicularDirection(direction
);
1003 ParentLayerCoord distance
=
1004 GetAxisStart(otherDirection
, thumbRect
.DistanceTo(aEvent
.mLocalOrigin
));
1005 ParentLayerCoord thumbWidth
= GetAxisLength(otherDirection
, thumbRect
);
1006 // Avoid triggering this condition spuriously when the thumb is
1007 // offscreen and its visible region is therefore empty.
1008 if (thumbWidth
> 0 && thumbWidth
* snapMultiplier
< distance
) {
1009 isMouseAwayFromThumb
= true;
1010 APZC_LOG("%p determined mouse is away from thumb, will snap\n", this);
1014 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
1015 OuterCSSCoord thumbPosition
;
1016 if (isMouseAwayFromThumb
) {
1017 thumbPosition
= aInitialThumbPos
;
1019 thumbPosition
= ConvertScrollbarPoint(aEvent
.mLocalOrigin
, scrollbarData
) -
1020 aDragMetrics
.mScrollbarDragOffset
;
1023 OuterCSSCoord maxThumbPos
= scrollbarData
.mScrollTrackLength
;
1024 maxThumbPos
-= scrollbarData
.mThumbLength
;
1026 float scrollPercent
=
1027 maxThumbPos
.value
== 0.0f
? 0.0f
: (float)(thumbPosition
/ maxThumbPos
);
1028 APZC_LOG("%p scrollbar dragged to %f percent\n", this, scrollPercent
);
1030 CSSCoord minScrollPosition
=
1031 GetAxisStart(direction
, aInitialScrollableRect
.TopLeft());
1032 CSSCoord maxScrollPosition
=
1033 GetAxisStart(direction
, aInitialScrollableRect
.BottomRight()) -
1034 GetAxisLength(direction
, Metrics().CalculateCompositedSizeInCssPixels());
1035 CSSCoord scrollPosition
=
1037 (scrollPercent
* (maxScrollPosition
- minScrollPosition
));
1039 scrollPosition
= std::max(scrollPosition
, minScrollPosition
);
1040 scrollPosition
= std::min(scrollPosition
, maxScrollPosition
);
1042 CSSPoint scrollOffset
= Metrics().GetVisualScrollOffset();
1043 if (direction
== ScrollDirection::eHorizontal
) {
1044 scrollOffset
.x
= scrollPosition
;
1046 scrollOffset
.y
= scrollPosition
;
1048 APZC_LOG("%p set scroll offset to %s from scrollbar drag\n", this,
1049 ToString(scrollOffset
).c_str());
1050 SetVisualScrollOffset(scrollOffset
);
1051 ScheduleCompositeAndMaybeRepaint();
1053 return nsEventStatus_eConsumeNoDefault
;
1056 nsEventStatus
AsyncPanZoomController::HandleInputEvent(
1057 const InputData
& aEvent
,
1058 const ScreenToParentLayerMatrix4x4
& aTransformToApzc
) {
1059 APZThreadUtils::AssertOnControllerThread();
1061 nsEventStatus rv
= nsEventStatus_eIgnore
;
1063 switch (aEvent
.mInputType
) {
1064 case MULTITOUCH_INPUT
: {
1065 MultiTouchInput multiTouchInput
= aEvent
.AsMultiTouchInput();
1066 RefPtr
<GestureEventListener
> listener
= GetGestureEventListener();
1068 // We only care about screen coordinates in the gesture listener,
1069 // so we don't bother transforming the event to parent layer coordinates
1070 rv
= listener
->HandleInputEvent(multiTouchInput
);
1071 if (rv
== nsEventStatus_eConsumeNoDefault
) {
1076 if (!multiTouchInput
.TransformToLocal(aTransformToApzc
)) {
1080 switch (multiTouchInput
.mType
) {
1081 case MultiTouchInput::MULTITOUCH_START
:
1082 rv
= OnTouchStart(multiTouchInput
);
1084 case MultiTouchInput::MULTITOUCH_MOVE
:
1085 rv
= OnTouchMove(multiTouchInput
);
1087 case MultiTouchInput::MULTITOUCH_END
:
1088 rv
= OnTouchEnd(multiTouchInput
);
1090 case MultiTouchInput::MULTITOUCH_CANCEL
:
1091 rv
= OnTouchCancel(multiTouchInput
);
1096 case PANGESTURE_INPUT
: {
1097 PanGestureInput panGestureInput
= aEvent
.AsPanGestureInput();
1098 if (!panGestureInput
.TransformToLocal(aTransformToApzc
)) {
1102 switch (panGestureInput
.mType
) {
1103 case PanGestureInput::PANGESTURE_MAYSTART
:
1104 rv
= OnPanMayBegin(panGestureInput
);
1106 case PanGestureInput::PANGESTURE_CANCELLED
:
1107 rv
= OnPanCancelled(panGestureInput
);
1109 case PanGestureInput::PANGESTURE_START
:
1110 rv
= OnPanBegin(panGestureInput
);
1112 case PanGestureInput::PANGESTURE_PAN
:
1113 rv
= OnPan(panGestureInput
, FingersOnTouchpad::Yes
);
1115 case PanGestureInput::PANGESTURE_END
:
1116 rv
= OnPanEnd(panGestureInput
);
1118 case PanGestureInput::PANGESTURE_MOMENTUMSTART
:
1119 rv
= OnPanMomentumStart(panGestureInput
);
1121 case PanGestureInput::PANGESTURE_MOMENTUMPAN
:
1122 rv
= OnPan(panGestureInput
, FingersOnTouchpad::No
);
1124 case PanGestureInput::PANGESTURE_MOMENTUMEND
:
1125 rv
= OnPanMomentumEnd(panGestureInput
);
1127 case PanGestureInput::PANGESTURE_INTERRUPTED
:
1128 rv
= OnPanInterrupted(panGestureInput
);
1134 MouseInput mouseInput
= aEvent
.AsMouseInput();
1135 if (!mouseInput
.TransformToLocal(aTransformToApzc
)) {
1140 case SCROLLWHEEL_INPUT
: {
1141 ScrollWheelInput scrollInput
= aEvent
.AsScrollWheelInput();
1142 if (!scrollInput
.TransformToLocal(aTransformToApzc
)) {
1146 rv
= OnScrollWheel(scrollInput
);
1149 case PINCHGESTURE_INPUT
: {
1150 // The APZCTreeManager should take care of ensuring that only root-content
1151 // APZCs get pinch inputs.
1152 MOZ_ASSERT(IsRootContent());
1153 PinchGestureInput pinchInput
= aEvent
.AsPinchGestureInput();
1154 if (!pinchInput
.TransformToLocal(aTransformToApzc
)) {
1158 rv
= HandleGestureEvent(pinchInput
);
1161 case TAPGESTURE_INPUT
: {
1162 TapGestureInput tapInput
= aEvent
.AsTapGestureInput();
1163 if (!tapInput
.TransformToLocal(aTransformToApzc
)) {
1167 rv
= HandleGestureEvent(tapInput
);
1170 case KEYBOARD_INPUT
: {
1171 const KeyboardInput
& keyInput
= aEvent
.AsKeyboardInput();
1172 rv
= OnKeyboard(keyInput
);
1180 nsEventStatus
AsyncPanZoomController::HandleGestureEvent(
1181 const InputData
& aEvent
) {
1182 APZThreadUtils::AssertOnControllerThread();
1184 nsEventStatus rv
= nsEventStatus_eIgnore
;
1186 switch (aEvent
.mInputType
) {
1187 case PINCHGESTURE_INPUT
: {
1188 // This may be invoked via a one-touch-pinch gesture from
1189 // GestureEventListener. In that case we want redirect it to the enclosing
1190 // root-content APZC.
1191 if (!IsRootContent()) {
1192 if (APZCTreeManager
* treeManagerLocal
= GetApzcTreeManager()) {
1193 if (RefPtr
<AsyncPanZoomController
> root
=
1194 treeManagerLocal
->FindZoomableApzc(this)) {
1195 rv
= root
->HandleGestureEvent(aEvent
);
1200 PinchGestureInput pinchGestureInput
= aEvent
.AsPinchGestureInput();
1201 pinchGestureInput
.TransformToLocal(GetTransformToThis());
1202 switch (pinchGestureInput
.mType
) {
1203 case PinchGestureInput::PINCHGESTURE_START
:
1204 rv
= OnScaleBegin(pinchGestureInput
);
1206 case PinchGestureInput::PINCHGESTURE_SCALE
:
1207 rv
= OnScale(pinchGestureInput
);
1209 case PinchGestureInput::PINCHGESTURE_FINGERLIFTED
:
1210 case PinchGestureInput::PINCHGESTURE_END
:
1211 rv
= OnScaleEnd(pinchGestureInput
);
1216 case TAPGESTURE_INPUT
: {
1217 TapGestureInput tapGestureInput
= aEvent
.AsTapGestureInput();
1218 tapGestureInput
.TransformToLocal(GetTransformToThis());
1219 switch (tapGestureInput
.mType
) {
1220 case TapGestureInput::TAPGESTURE_LONG
:
1221 rv
= OnLongPress(tapGestureInput
);
1223 case TapGestureInput::TAPGESTURE_LONG_UP
:
1224 rv
= OnLongPressUp(tapGestureInput
);
1226 case TapGestureInput::TAPGESTURE_UP
:
1227 rv
= OnSingleTapUp(tapGestureInput
);
1229 case TapGestureInput::TAPGESTURE_CONFIRMED
:
1230 rv
= OnSingleTapConfirmed(tapGestureInput
);
1232 case TapGestureInput::TAPGESTURE_DOUBLE
:
1233 if (!IsRootContent()) {
1234 if (APZCTreeManager
* treeManagerLocal
= GetApzcTreeManager()) {
1235 if (AsyncPanZoomController
* apzc
=
1236 treeManagerLocal
->FindRootApzcFor(GetLayersId())) {
1237 rv
= apzc
->OnDoubleTap(tapGestureInput
);
1242 rv
= OnDoubleTap(tapGestureInput
);
1244 case TapGestureInput::TAPGESTURE_SECOND
:
1245 rv
= OnSecondTap(tapGestureInput
);
1247 case TapGestureInput::TAPGESTURE_CANCEL
:
1248 rv
= OnCancelTap(tapGestureInput
);
1254 MOZ_ASSERT_UNREACHABLE("Unhandled input event");
1261 void AsyncPanZoomController::StartAutoscroll(const ScreenPoint
& aPoint
) {
1262 // Cancel any existing animation.
1265 SetState(AUTOSCROLL
);
1266 StartAnimation(do_AddRef(new AutoscrollAnimation(*this, aPoint
)));
1269 void AsyncPanZoomController::StopAutoscroll() {
1270 if (mState
== AUTOSCROLL
) {
1271 CancelAnimation(TriggeredExternally
);
1275 nsEventStatus
AsyncPanZoomController::OnTouchStart(
1276 const MultiTouchInput
& aEvent
) {
1277 APZC_LOG_DETAIL("got a touch-start in state %s\n", this,
1278 ToString(mState
).c_str());
1279 mPanDirRestricted
= false;
1283 case ANIMATING_ZOOM
:
1285 case SMOOTHMSD_SCROLL
:
1286 case OVERSCROLL_ANIMATION
:
1288 case KEYBOARD_SCROLL
:
1291 MOZ_ASSERT(GetCurrentTouchBlock());
1292 GetCurrentTouchBlock()->GetOverscrollHandoffChain()->CancelAnimations(
1295 case SCROLLBAR_DRAG
:
1297 ParentLayerPoint point
= GetFirstTouchPoint(aEvent
);
1298 mLastTouch
.mPosition
= mStartTouch
= GetFirstExternalTouchPoint(aEvent
);
1299 StartTouch(point
, aEvent
.mTimeStamp
);
1300 if (RefPtr
<GeckoContentController
> controller
=
1301 GetGeckoContentController()) {
1302 MOZ_ASSERT(GetCurrentTouchBlock());
1303 controller
->NotifyAPZStateChange(
1304 GetGuid(), APZStateChange::eStartTouch
,
1305 GetCurrentTouchBlock()->GetOverscrollHandoffChain()->CanBePanned(
1307 Some(GetCurrentTouchBlock()->GetBlockId()));
1309 mLastTouch
.mTimeStamp
= mTouchStartTime
= aEvent
.mTimeStamp
;
1315 case PANNING_LOCKED_X
:
1316 case PANNING_LOCKED_Y
:
1318 NS_WARNING("Received impossible touch in OnTouchStart");
1322 return nsEventStatus_eConsumeNoDefault
;
1325 nsEventStatus
AsyncPanZoomController::OnTouchMove(
1326 const MultiTouchInput
& aEvent
) {
1327 APZC_LOG_DETAIL("got a touch-move in state %s\n", this,
1328 ToString(mState
).c_str());
1331 case SMOOTHMSD_SCROLL
:
1333 case ANIMATING_ZOOM
:
1334 // May happen if the user double-taps and drags without lifting after the
1335 // second tap. Ignore the move if this happens.
1336 return nsEventStatus_eIgnore
;
1339 ScreenCoord panThreshold
= GetTouchStartTolerance();
1340 ExternalPoint extPoint
= GetFirstExternalTouchPoint(aEvent
);
1341 Maybe
<std::pair
<MultiTouchInput
, MultiTouchInput
>> splitEvent
;
1343 // We intentionally skip the UpdateWithTouchAtDevicePoint call when the
1344 // panThreshold is zero. This ensures more deterministic behaviour during
1345 // testing. If we call that, Axis::mPos gets updated to the point of this
1346 // touchmove event, but we "consume" the move to overcome the
1347 // panThreshold, so it's hard to pan a specific amount reliably from a
1349 if (panThreshold
> 0.0f
) {
1350 const float vectorLength
= PanVector(extPoint
).Length();
1352 if (vectorLength
< panThreshold
) {
1353 UpdateWithTouchAtDevicePoint(aEvent
);
1354 mLastTouch
= {extPoint
, aEvent
.mTimeStamp
};
1356 return nsEventStatus_eIgnore
;
1359 splitEvent
= MaybeSplitTouchMoveEvent(aEvent
, panThreshold
,
1360 vectorLength
, extPoint
);
1362 UpdateWithTouchAtDevicePoint(splitEvent
? splitEvent
->first
: aEvent
);
1365 nsEventStatus result
;
1366 const MultiTouchInput
& firstEvent
=
1367 splitEvent
? splitEvent
->first
: aEvent
;
1369 MOZ_ASSERT(GetCurrentTouchBlock());
1370 if (GetCurrentTouchBlock()->TouchActionAllowsPanningXY()) {
1371 // In the calls to StartPanning() below, the first argument needs to be
1372 // the External position of |firstEvent|.
1373 // However, instead of computing that using
1374 // GetFirstExternalTouchPoint(firstEvent), we pass |extPoint| which
1375 // has been modified by MaybeSplitTouchMoveEvent() to the desired
1376 // value. This is a workaround for the fact that recomputing the
1377 // External point would require a round-trip through |mScreenPoint|
1378 // which is an integer.
1380 // User tries to trigger a touch behavior. If allowed touch behavior is
1381 // vertical pan + horizontal pan (touch-action value is equal to AUTO)
1382 // we can return ConsumeNoDefault status immediately to trigger cancel
1384 // It should happen independent of the parent type (whether it is
1385 // scrolling or not).
1386 StartPanning(extPoint
, firstEvent
.mTimeStamp
);
1387 result
= nsEventStatus_eConsumeNoDefault
;
1389 result
= StartPanning(extPoint
, firstEvent
.mTimeStamp
);
1392 if (splitEvent
&& IsInPanningState()) {
1393 TrackTouch(splitEvent
->second
);
1394 return nsEventStatus_eConsumeNoDefault
;
1401 case PANNING_LOCKED_X
:
1402 case PANNING_LOCKED_Y
:
1405 return nsEventStatus_eConsumeNoDefault
;
1408 // The scale gesture listener should have handled this.
1410 "Gesture listener should have handled pinching in OnTouchMove.");
1411 return nsEventStatus_eIgnore
;
1415 case KEYBOARD_SCROLL
:
1416 case OVERSCROLL_ANIMATION
:
1418 case SCROLLBAR_DRAG
:
1419 // Should not receive a touch-move in the OVERSCROLL_ANIMATION state
1420 // as touch blocks that begin in an overscrolled state cancel the
1421 // animation. The same is true for wheel scroll animations.
1422 NS_WARNING("Received impossible touch in OnTouchMove");
1426 return nsEventStatus_eConsumeNoDefault
;
1429 nsEventStatus
AsyncPanZoomController::OnTouchEnd(
1430 const MultiTouchInput
& aEvent
) {
1431 APZC_LOG_DETAIL("got a touch-end in state %s\n", this,
1432 ToString(mState
).c_str());
1433 OnTouchEndOrCancel();
1435 // In case no touch behavior triggered previously we can avoid sending
1436 // scroll events or requesting content repaint. This condition is added
1437 // to make tests consistent - in case touch-action is NONE (and therefore
1438 // no pans/zooms can be performed) we expected neither scroll or repaint
1440 if (mState
!= NOTHING
) {
1441 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
1446 // Should never happen.
1447 NS_WARNING("Received impossible touch end in OnTouchEnd.");
1449 case ANIMATING_ZOOM
:
1450 case SMOOTHMSD_SCROLL
:
1452 // May happen if the user double-taps and drags without lifting after the
1453 // second tap. Ignore if this happens.
1454 return nsEventStatus_eIgnore
;
1457 // We may have some velocity stored on the axis from move events
1458 // that were not big enough to trigger scrolling. Clear that out.
1459 SetVelocityVector(ParentLayerPoint(0, 0));
1460 MOZ_ASSERT(GetCurrentTouchBlock());
1461 APZC_LOG("%p still has %u touch points active\n", this,
1462 GetCurrentTouchBlock()->GetActiveTouchCount());
1463 // In cases where the user is panning, then taps the second finger without
1464 // entering a pinch, we will arrive here when the second finger is lifted.
1465 // However the first finger is still down so we want to remain in state
1467 if (GetCurrentTouchBlock()->GetActiveTouchCount() == 0) {
1468 // It's possible we may be overscrolled if the user tapped during a
1469 // previous overscroll pan. Make sure to snap back in this situation.
1470 // An ancestor APZC could be overscrolled instead of this APZC, so
1471 // walk the handoff chain as well.
1472 GetCurrentTouchBlock()
1473 ->GetOverscrollHandoffChain()
1474 ->SnapBackOverscrolledApzc(this);
1475 mFlingAccelerator
.Reset();
1476 // SnapBackOverscrolledApzc() will put any APZC it causes to snap back
1477 // into the OVERSCROLL_ANIMATION state. If that's not us, since we're
1478 // done TOUCHING enter the NOTHING state.
1479 if (mState
!= OVERSCROLL_ANIMATION
) {
1483 return nsEventStatus_eIgnore
;
1486 case PANNING_LOCKED_X
:
1487 case PANNING_LOCKED_Y
:
1488 case PAN_MOMENTUM
: {
1489 MOZ_ASSERT(GetCurrentTouchBlock());
1490 EndTouch(aEvent
.mTimeStamp
, Axis::ClearAxisLock::Yes
);
1491 return HandleEndOfPan();
1495 // Scale gesture listener should have handled this.
1497 "Gesture listener should have handled pinching in OnTouchEnd.");
1498 return nsEventStatus_eIgnore
;
1502 case KEYBOARD_SCROLL
:
1503 case OVERSCROLL_ANIMATION
:
1505 case SCROLLBAR_DRAG
:
1506 // Should not receive a touch-end in the OVERSCROLL_ANIMATION state
1507 // as touch blocks that begin in an overscrolled state cancel the
1508 // animation. The same is true for WHEEL_SCROLL.
1509 NS_WARNING("Received impossible touch in OnTouchEnd");
1513 return nsEventStatus_eConsumeNoDefault
;
1516 nsEventStatus
AsyncPanZoomController::OnTouchCancel(
1517 const MultiTouchInput
& aEvent
) {
1518 APZC_LOG_DETAIL("got a touch-cancel in state %s\n", this,
1519 ToString(mState
).c_str());
1520 OnTouchEndOrCancel();
1521 CancelAnimationAndGestureState();
1522 return nsEventStatus_eConsumeNoDefault
;
1525 nsEventStatus
AsyncPanZoomController::OnScaleBegin(
1526 const PinchGestureInput
& aEvent
) {
1527 APZC_LOG_DETAIL("got a scale-begin in state %s\n", this,
1528 ToString(mState
).c_str());
1530 mPinchLocked
= false;
1531 mPinchPaintTimerSet
= false;
1532 // Note that there may not be a touch block at this point, if we received the
1533 // PinchGestureEvent directly from widget code without any touch events.
1534 if (HasReadyTouchBlock() &&
1535 !GetCurrentTouchBlock()->TouchActionAllowsPinchZoom()) {
1536 return nsEventStatus_eIgnore
;
1539 // For platforms that don't support APZ zooming, dispatch a message to the
1540 // content controller, it may want to do something else with this gesture.
1541 // FIXME: bug 1525793 -- this may need to handle zooming or not on a
1542 // per-document basis.
1543 if (!StaticPrefs::apz_allow_zooming()) {
1544 if (RefPtr
<GeckoContentController
> controller
=
1545 GetGeckoContentController()) {
1546 APZC_LOG("%p notifying controller of pinch gesture start\n", this);
1547 controller
->NotifyPinchGesture(
1548 aEvent
.mType
, GetGuid(),
1549 ViewAs
<LayoutDevicePixel
>(
1551 PixelCastJustification::
1552 LayoutDeviceIsScreenForUntransformedEvent
),
1553 0, aEvent
.modifiers
);
1558 Telemetry::Accumulate(Telemetry::APZ_ZOOM_PINCHSOURCE
, (int)aEvent
.mSource
);
1559 SetVelocityVector(ParentLayerPoint(0, 0));
1560 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
1562 aEvent
.mLocalFocusPoint
- Metrics().GetCompositionBounds().TopLeft();
1564 mPinchEventBuffer
.push(aEvent
);
1566 return nsEventStatus_eConsumeNoDefault
;
1569 nsEventStatus
AsyncPanZoomController::OnScale(const PinchGestureInput
& aEvent
) {
1570 APZC_LOG_DETAIL("got a scale in state %s\n", this, ToString(mState
).c_str());
1572 if (HasReadyTouchBlock() &&
1573 !GetCurrentTouchBlock()->TouchActionAllowsPinchZoom()) {
1574 return nsEventStatus_eIgnore
;
1577 if (mState
!= PINCHING
) {
1578 return nsEventStatus_eConsumeNoDefault
;
1581 mPinchEventBuffer
.push(aEvent
);
1582 HandlePinchLocking(aEvent
);
1583 bool allowZoom
= ZoomConstraintsAllowZoom() && !mPinchLocked
;
1585 // If we are pinch-locked, this is a two-finger pan.
1586 // Tracking panning distance and velocity.
1587 // UpdateWithTouchAtDevicePoint() acquires the tree lock, so
1588 // it cannot be called while the mRecursiveMutex lock is held.
1590 mX
.UpdateWithTouchAtDevicePoint(aEvent
.mLocalFocusPoint
.x
,
1592 mY
.UpdateWithTouchAtDevicePoint(aEvent
.mLocalFocusPoint
.y
,
1596 // FIXME: bug 1525793 -- this may need to handle zooming or not on a
1597 // per-document basis.
1598 if (!StaticPrefs::apz_allow_zooming()) {
1599 if (RefPtr
<GeckoContentController
> controller
=
1600 GetGeckoContentController()) {
1601 APZC_LOG("%p notifying controller of pinch gesture\n", this);
1602 controller
->NotifyPinchGesture(
1603 aEvent
.mType
, GetGuid(),
1604 ViewAs
<LayoutDevicePixel
>(
1606 PixelCastJustification::
1607 LayoutDeviceIsScreenForUntransformedEvent
),
1608 ViewAs
<LayoutDevicePixel
>(
1609 aEvent
.mCurrentSpan
- aEvent
.mPreviousSpan
,
1610 PixelCastJustification::
1611 LayoutDeviceIsScreenForUntransformedEvent
),
1617 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
1618 // Only the root APZC is zoomable, and the root APZC is not allowed to have
1619 // different x and y scales. If it did, the calculations in this function
1620 // would have to be adjusted (as e.g. it would no longer be valid to take
1621 // the minimum or maximum of the ratios of the widths and heights of the
1622 // page rect and the composition bounds).
1623 MOZ_ASSERT(Metrics().IsRootContent());
1625 CSSToParentLayerScale userZoom
= Metrics().GetZoom();
1626 ParentLayerPoint focusPoint
=
1627 aEvent
.mLocalFocusPoint
- Metrics().GetCompositionBounds().TopLeft();
1628 CSSPoint cssFocusPoint
;
1629 if (Metrics().GetZoom() != CSSToParentLayerScale(0)) {
1630 cssFocusPoint
= focusPoint
/ Metrics().GetZoom();
1633 ParentLayerPoint focusChange
= mLastZoomFocus
- focusPoint
;
1634 mLastZoomFocus
= focusPoint
;
1635 // If displacing by the change in focus point will take us off page bounds,
1636 // then reduce the displacement such that it doesn't.
1637 focusChange
.x
-= mX
.DisplacementWillOverscrollAmount(focusChange
.x
);
1638 focusChange
.y
-= mY
.DisplacementWillOverscrollAmount(focusChange
.y
);
1639 if (userZoom
!= CSSToParentLayerScale(0)) {
1640 ScrollBy(focusChange
/ userZoom
);
1643 // If the span is zero or close to it, we don't want to process this zoom
1644 // change because we're going to get wonky numbers for the spanRatio. So
1645 // let's bail out here. Note that we do this after the focus-change-scroll
1646 // above, so that if we have a pinch with zero span but changing focus,
1647 // such as generated by some Synaptics touchpads on Windows, we still
1649 float prevSpan
= aEvent
.mPreviousSpan
;
1650 if (fabsf(prevSpan
) <= EPSILON
|| fabsf(aEvent
.mCurrentSpan
) <= EPSILON
) {
1651 // We might have done a nonzero ScrollBy above, so update metrics and
1652 // repaint/recomposite
1653 ScheduleCompositeAndMaybeRepaint();
1654 return nsEventStatus_eConsumeNoDefault
;
1656 float spanRatio
= aEvent
.mCurrentSpan
/ aEvent
.mPreviousSpan
;
1658 // When we zoom in with focus, we can zoom too much towards the boundaries
1659 // that we actually go over them. These are the needed displacements along
1660 // either axis such that we don't overscroll the boundaries when zooming.
1661 CSSPoint neededDisplacement
;
1663 CSSToParentLayerScale realMinZoom
= mZoomConstraints
.mMinZoom
;
1664 CSSToParentLayerScale realMaxZoom
= mZoomConstraints
.mMaxZoom
;
1666 std::max(realMinZoom
.scale
, Metrics().GetCompositionBounds().Width() /
1667 Metrics().GetScrollableRect().Width());
1669 std::max(realMinZoom
.scale
, Metrics().GetCompositionBounds().Height() /
1670 Metrics().GetScrollableRect().Height());
1671 if (realMaxZoom
< realMinZoom
) {
1672 realMaxZoom
= realMinZoom
;
1675 bool doScale
= allowZoom
&& ((spanRatio
> 1.0 && userZoom
< realMaxZoom
) ||
1676 (spanRatio
< 1.0 && userZoom
> realMinZoom
));
1679 spanRatio
= clamped(spanRatio
, realMinZoom
.scale
/ userZoom
.scale
,
1680 realMaxZoom
.scale
/ userZoom
.scale
);
1682 // Note that the spanRatio here should never put us into OVERSCROLL_BOTH
1683 // because up above we clamped it.
1684 neededDisplacement
.x
=
1685 -mX
.ScaleWillOverscrollAmount(spanRatio
, cssFocusPoint
.x
);
1686 neededDisplacement
.y
=
1687 -mY
.ScaleWillOverscrollAmount(spanRatio
, cssFocusPoint
.y
);
1689 ScaleWithFocus(spanRatio
, cssFocusPoint
);
1691 if (neededDisplacement
!= CSSPoint()) {
1692 ScrollBy(neededDisplacement
);
1695 // We don't want to redraw on every scale, so throttle it.
1696 if (!mPinchPaintTimerSet
) {
1697 const int delay
= StaticPrefs::apz_scale_repaint_delay_ms();
1699 if (RefPtr
<GeckoContentController
> controller
=
1700 GetGeckoContentController()) {
1701 mPinchPaintTimerSet
= true;
1702 controller
->PostDelayedTask(
1704 "layers::AsyncPanZoomController::"
1705 "DoDelayedRequestContentRepaint",
1707 &AsyncPanZoomController::DoDelayedRequestContentRepaint
),
1711 } else if (apz::AboutToCheckerboard(mLastContentPaintMetrics
,
1713 // If we already scheduled a throttled repaint request but are also
1714 // in danger of checkerboarding soon, trigger the repaint request to
1715 // go out immediately. This should reduce the amount of time we spend
1718 // Note that if we remain in this "about to
1719 // checkerboard" state over a period of time with multiple pinch input
1720 // events (which is quite likely), then we will flip-flop between taking
1721 // the above branch (!mPinchPaintTimerSet) and this branch (which will
1722 // flush the repaint request and reset mPinchPaintTimerSet to false).
1723 // This is sort of desirable because it halves the number of repaint
1724 // requests we send, and therefore reduces IPC traffic.
1725 // Keep in mind that many of these repaint requests will be ignored on
1726 // the main-thread anyway due to the resolution mismatch - the first
1727 // repaint request will be honored because APZ's notion of the painted
1728 // resolution matches the actual main thread resolution, but that first
1729 // repaint request will change the resolution on the main thread.
1730 // Subsequent repaint requests will be ignored in APZCCallbackHelper, at
1731 // https://searchfox.org/mozilla-central/rev/e0eb861a187f0bb6d994228f2e0e49b2c9ee455e/gfx/layers/apz/util/APZCCallbackHelper.cpp#331-338,
1732 // until we receive a NotifyLayersUpdated call that re-syncs APZ's
1733 // notion of the painted resolution to the main thread. These ignored
1734 // repaint requests are contributing to IPC traffic needlessly, and so
1735 // halving the number of repaint requests (as mentioned above) seems
1737 DoDelayedRequestContentRepaint();
1740 // Trigger a repaint request after scrolling.
1741 RequestContentRepaint();
1744 // We did a ScrollBy call above even if we didn't do a scale, so we
1745 // should composite for that.
1746 ScheduleComposite();
1749 return nsEventStatus_eConsumeNoDefault
;
1752 nsEventStatus
AsyncPanZoomController::OnScaleEnd(
1753 const PinchGestureInput
& aEvent
) {
1754 APZC_LOG_DETAIL("got a scale-end in state %s\n", this,
1755 ToString(mState
).c_str());
1757 mPinchPaintTimerSet
= false;
1759 if (HasReadyTouchBlock() &&
1760 !GetCurrentTouchBlock()->TouchActionAllowsPinchZoom()) {
1761 return nsEventStatus_eIgnore
;
1764 // FIXME: bug 1525793 -- this may need to handle zooming or not on a
1765 // per-document basis.
1766 if (!StaticPrefs::apz_allow_zooming()) {
1767 if (RefPtr
<GeckoContentController
> controller
=
1768 GetGeckoContentController()) {
1769 controller
->NotifyPinchGesture(
1770 aEvent
.mType
, GetGuid(),
1771 ViewAs
<LayoutDevicePixel
>(
1773 PixelCastJustification::
1774 LayoutDeviceIsScreenForUntransformedEvent
),
1775 0, aEvent
.modifiers
);
1780 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
1781 ScheduleComposite();
1782 RequestContentRepaint();
1785 mPinchEventBuffer
.clear();
1787 if (aEvent
.mType
== PinchGestureInput::PINCHGESTURE_FINGERLIFTED
) {
1788 // One finger is still down, so transition to a TOUCHING state
1789 if (!mPinchLocked
) {
1790 mPanDirRestricted
= false;
1791 mLastTouch
.mPosition
= mStartTouch
=
1792 ToExternalPoint(aEvent
.mScreenOffset
, aEvent
.mFocusPoint
);
1793 mLastTouch
.mTimeStamp
= mTouchStartTime
= aEvent
.mTimeStamp
;
1794 StartTouch(aEvent
.mLocalFocusPoint
, aEvent
.mTimeStamp
);
1797 // If we are pinch locked, StartTouch() was already called
1798 // when we entered the pinch lock.
1799 StartPanning(ToExternalPoint(aEvent
.mScreenOffset
, aEvent
.mFocusPoint
),
1803 // Otherwise, handle the gesture being completely done.
1805 // Some of the code paths below, like ScrollSnap() or HandleEndOfPan(),
1806 // may start an animation, but otherwise we want to end up in the NOTHING
1807 // state. To avoid state change notification churn, we use a
1808 // notification blocker.
1809 bool stateWasPinching
= (mState
== PINCHING
);
1810 StateChangeNotificationBlocker
blocker(this);
1813 if (ZoomConstraintsAllowZoom()) {
1814 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
1816 // We can get into a situation where we are overscrolled at the end of a
1817 // pinch if we go into overscroll with a two-finger pan, and then turn
1818 // that into a pinch by increasing the span sufficiently. In such a case,
1819 // there is no snap-back animation to get us out of overscroll, so we need
1820 // to get out of it somehow.
1821 // Moreover, in cases of scroll handoff, the overscroll can be on an APZC
1822 // further up in the handoff chain rather than on the current APZC, so
1823 // we need to clear overscroll along the entire handoff chain.
1824 if (HasReadyTouchBlock()) {
1825 GetCurrentTouchBlock()->GetOverscrollHandoffChain()->ClearOverscroll();
1829 // Along with clearing the overscroll, we also want to snap to the nearest
1830 // snap point as appropriate.
1831 ScrollSnap(ScrollSnapFlags::IntendedEndPosition
);
1833 // when zoom is not allowed
1834 EndTouch(aEvent
.mTimeStamp
, Axis::ClearAxisLock::Yes
);
1835 if (stateWasPinching
) {
1837 if (HasReadyTouchBlock()) {
1838 return HandleEndOfPan();
1843 return nsEventStatus_eConsumeNoDefault
;
1846 nsEventStatus
AsyncPanZoomController::HandleEndOfPan() {
1847 MOZ_ASSERT(!mAnimation
);
1848 MOZ_ASSERT(GetCurrentTouchBlock() || GetCurrentPanGestureBlock());
1849 GetCurrentInputBlock()->GetOverscrollHandoffChain()->FlushRepaints();
1850 ParentLayerPoint flingVelocity
= GetVelocityVector();
1852 // Clear our velocities; if DispatchFling() gives the fling to us,
1853 // the fling velocity gets *added* to our existing velocity in
1855 SetVelocityVector(ParentLayerPoint(0, 0));
1856 // Clear our state so that we don't stay in the PANNING state
1857 // if DispatchFling() gives the fling to somone else. However,
1858 // don't send the state change notification until we've determined
1859 // what our final state is to avoid notification churn.
1860 StateChangeNotificationBlocker
blocker(this);
1863 APZC_LOG("%p starting a fling animation if %f > %f\n", this,
1864 flingVelocity
.Length().value
,
1865 StaticPrefs::apz_fling_min_velocity_threshold());
1867 if (flingVelocity
.Length() <=
1868 StaticPrefs::apz_fling_min_velocity_threshold()) {
1869 // Relieve overscroll now if needed, since we will not transition to a fling
1870 // animation and then an overscroll animation, and relieve it then.
1871 GetCurrentInputBlock()
1872 ->GetOverscrollHandoffChain()
1873 ->SnapBackOverscrolledApzc(this);
1874 mFlingAccelerator
.Reset();
1875 return nsEventStatus_eConsumeNoDefault
;
1878 // Make a local copy of the tree manager pointer and check that it's not
1879 // null before calling DispatchFling(). This is necessary because Destroy(),
1880 // which nulls out mTreeManager, could be called concurrently.
1881 if (APZCTreeManager
* treeManagerLocal
= GetApzcTreeManager()) {
1882 const FlingHandoffState handoffState
{
1884 GetCurrentInputBlock()->GetOverscrollHandoffChain(),
1885 Some(mTouchStartRestingTimeBeforePan
),
1886 mMinimumVelocityDuringPan
.valueOr(0),
1887 false /* not handoff */,
1888 GetCurrentInputBlock()->GetScrolledApzc()};
1889 treeManagerLocal
->DispatchFling(this, handoffState
);
1891 return nsEventStatus_eConsumeNoDefault
;
1894 Maybe
<LayoutDevicePoint
> AsyncPanZoomController::ConvertToGecko(
1895 const ScreenIntPoint
& aPoint
) {
1896 if (APZCTreeManager
* treeManagerLocal
= GetApzcTreeManager()) {
1897 if (Maybe
<ScreenIntPoint
> layoutPoint
=
1898 treeManagerLocal
->ConvertToGecko(aPoint
, this)) {
1899 return Some(LayoutDevicePoint(ViewAs
<LayoutDevicePixel
>(
1901 PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent
)));
1907 OuterCSSCoord
AsyncPanZoomController::ConvertScrollbarPoint(
1908 const ParentLayerPoint
& aScrollbarPoint
,
1909 const ScrollbarData
& aThumbData
) const {
1910 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
1912 CSSPoint scrollbarPoint
;
1913 if (Metrics().GetZoom() != CSSToParentLayerScale(0)) {
1914 // First, get it into the right coordinate space.
1915 scrollbarPoint
= aScrollbarPoint
/ Metrics().GetZoom();
1918 // The scrollbar can be transformed with the frame but the pres shell
1919 // resolution is only applied to the scroll frame.
1920 OuterCSSPoint outerScrollbarPoint
=
1921 scrollbarPoint
* Metrics().GetCSSToOuterCSSScale();
1923 // Now, get it to be relative to the beginning of the scroll track.
1924 OuterCSSRect cssCompositionBound
=
1925 Metrics().CalculateCompositionBoundsInOuterCssPixels();
1926 return GetAxisStart(*aThumbData
.mDirection
, outerScrollbarPoint
) -
1927 GetAxisStart(*aThumbData
.mDirection
, cssCompositionBound
) -
1928 aThumbData
.mScrollTrackStart
;
1931 static bool AllowsScrollingMoreThanOnePage(double aMultiplier
) {
1932 return Abs(aMultiplier
) >=
1933 EventStateManager::MIN_MULTIPLIER_VALUE_ALLOWING_OVER_ONE_PAGE_SCROLL
;
1936 ParentLayerPoint
AsyncPanZoomController::GetScrollWheelDelta(
1937 const ScrollWheelInput
& aEvent
) const {
1938 return GetScrollWheelDelta(aEvent
, aEvent
.mDeltaX
, aEvent
.mDeltaY
,
1939 aEvent
.mUserDeltaMultiplierX
,
1940 aEvent
.mUserDeltaMultiplierY
);
1943 ParentLayerPoint
AsyncPanZoomController::GetScrollWheelDelta(
1944 const ScrollWheelInput
& aEvent
, double aDeltaX
, double aDeltaY
,
1945 double aMultiplierX
, double aMultiplierY
) const {
1946 ParentLayerSize scrollAmount
;
1947 ParentLayerSize pageScrollSize
;
1950 // Grab the lock to access the frame metrics.
1951 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
1952 LayoutDeviceIntSize scrollAmountLD
= mScrollMetadata
.GetLineScrollAmount();
1953 LayoutDeviceIntSize pageScrollSizeLD
=
1954 mScrollMetadata
.GetPageScrollAmount();
1955 scrollAmount
= scrollAmountLD
/ Metrics().GetDevPixelsPerCSSPixel() *
1956 Metrics().GetZoom();
1957 pageScrollSize
= pageScrollSizeLD
/ Metrics().GetDevPixelsPerCSSPixel() *
1958 Metrics().GetZoom();
1961 ParentLayerPoint delta
;
1962 switch (aEvent
.mDeltaType
) {
1963 case ScrollWheelInput::SCROLLDELTA_LINE
: {
1964 delta
.x
= aDeltaX
* scrollAmount
.width
;
1965 delta
.y
= aDeltaY
* scrollAmount
.height
;
1968 case ScrollWheelInput::SCROLLDELTA_PAGE
: {
1969 delta
.x
= aDeltaX
* pageScrollSize
.width
;
1970 delta
.y
= aDeltaY
* pageScrollSize
.height
;
1973 case ScrollWheelInput::SCROLLDELTA_PIXEL
: {
1974 delta
= ToParentLayerCoordinates(ScreenPoint(aDeltaX
, aDeltaY
),
1980 // Apply user-set multipliers.
1981 delta
.x
*= aMultiplierX
;
1982 delta
.y
*= aMultiplierY
;
1984 // For the conditions under which we allow system scroll overrides, see
1985 // WidgetWheelEvent::OverriddenDelta{X,Y}.
1986 // Note that we do *not* restrict this to the root content, see bug 1217715
1987 // for discussion on this.
1988 if (StaticPrefs::mousewheel_system_scroll_override_enabled() &&
1989 !aEvent
.IsCustomizedByUserPrefs() &&
1990 aEvent
.mDeltaType
== ScrollWheelInput::SCROLLDELTA_LINE
&&
1991 aEvent
.mAllowToOverrideSystemScrollSpeed
) {
1992 delta
.x
= WidgetWheelEvent::ComputeOverriddenDelta(delta
.x
, false);
1993 delta
.y
= WidgetWheelEvent::ComputeOverriddenDelta(delta
.y
, true);
1996 // If this is a line scroll, and this event was part of a scroll series, then
1997 // it might need extra acceleration. See WheelHandlingHelper.cpp.
1998 if (aEvent
.mDeltaType
== ScrollWheelInput::SCROLLDELTA_LINE
&&
1999 aEvent
.mScrollSeriesNumber
> 0) {
2000 int32_t start
= StaticPrefs::mousewheel_acceleration_start();
2001 if (start
>= 0 && aEvent
.mScrollSeriesNumber
>= uint32_t(start
)) {
2002 int32_t factor
= StaticPrefs::mousewheel_acceleration_factor();
2004 delta
.x
= ComputeAcceleratedWheelDelta(
2005 delta
.x
, aEvent
.mScrollSeriesNumber
, factor
);
2006 delta
.y
= ComputeAcceleratedWheelDelta(
2007 delta
.y
, aEvent
.mScrollSeriesNumber
, factor
);
2012 // We shouldn't scroll more than one page at once except when the
2013 // user preference is large.
2014 if (!AllowsScrollingMoreThanOnePage(aMultiplierX
) &&
2015 Abs(delta
.x
) > pageScrollSize
.width
) {
2016 delta
.x
= (delta
.x
>= 0) ? pageScrollSize
.width
: -pageScrollSize
.width
;
2018 if (!AllowsScrollingMoreThanOnePage(aMultiplierY
) &&
2019 Abs(delta
.y
) > pageScrollSize
.height
) {
2020 delta
.y
= (delta
.y
>= 0) ? pageScrollSize
.height
: -pageScrollSize
.height
;
2026 nsEventStatus
AsyncPanZoomController::OnKeyboard(const KeyboardInput
& aEvent
) {
2027 // Mark that this APZC has async key scrolled
2028 mTestHasAsyncKeyScrolled
= true;
2030 // Calculate the destination for this keyboard scroll action
2031 CSSPoint destination
= GetKeyboardDestination(aEvent
.mAction
);
2032 ScrollOrigin scrollOrigin
=
2033 SmoothScrollAnimation::GetScrollOriginForAction(aEvent
.mAction
.mType
);
2034 Maybe
<CSSSnapDestination
> snapDestination
=
2035 MaybeAdjustDestinationForScrollSnapping(
2036 aEvent
, destination
,
2037 GetScrollSnapFlagsForKeyboardAction(aEvent
.mAction
));
2038 ScrollMode scrollMode
= apz::GetScrollModeForOrigin(scrollOrigin
);
2040 RecordScrollPayload(aEvent
.mTimeStamp
);
2041 // If the scrolling is instant, then scroll immediately to the destination
2042 if (scrollMode
== ScrollMode::Instant
) {
2045 ParentLayerPoint startPoint
, endPoint
;
2048 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
2050 // CallDispatchScroll interprets the start and end points as the start and
2051 // end of a touch scroll so they need to be reversed.
2052 startPoint
= destination
* Metrics().GetZoom();
2053 endPoint
= Metrics().GetVisualScrollOffset() * Metrics().GetZoom();
2056 ParentLayerPoint delta
= endPoint
- startPoint
;
2058 ScreenPoint distance
= ToScreenCoordinates(
2059 ParentLayerPoint(fabs(delta
.x
), fabs(delta
.y
)), startPoint
);
2061 OverscrollHandoffState
handoffState(
2062 *mInputQueue
->GetCurrentKeyboardBlock()->GetOverscrollHandoffChain(),
2063 distance
, ScrollSource::Keyboard
);
2065 CallDispatchScroll(startPoint
, endPoint
, handoffState
);
2066 ParentLayerPoint remainingDelta
= endPoint
- startPoint
;
2067 if (remainingDelta
!= delta
) {
2068 // If any scrolling happened, set KEYBOARD_SCROLL explicitly so that it
2069 // will trigger a TransformEnd notification.
2070 SetState(KEYBOARD_SCROLL
);
2073 if (snapDestination
) {
2075 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
2076 mLastSnapTargetIds
= std::move(snapDestination
->mTargetIds
);
2081 return nsEventStatus_eConsumeDoDefault
;
2084 // The lock must be held across the entire update operation, so the
2085 // compositor doesn't end the animation before we get a chance to
2087 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
2089 if (snapDestination
) {
2090 // If we're scroll snapping, use a smooth scroll animation to get
2091 // the desired physics. Note that SmoothMsdScrollTo() will re-use an
2092 // existing smooth scroll animation if there is one.
2093 APZC_LOG("%p keyboard scrolling to snap point %s\n", this,
2094 ToString(destination
).c_str());
2095 SmoothMsdScrollTo(std::move(*snapDestination
), ScrollTriggeredByScript::No
);
2096 return nsEventStatus_eConsumeDoDefault
;
2099 // Use a keyboard scroll animation to scroll, reusing an existing one if it
2101 if (mState
!= KEYBOARD_SCROLL
) {
2104 // Keyboard input that does not change the scroll position should not
2105 // cause a TransformBegin state change, in order to avoid firing a
2106 // scrollend event when no scrolling occurred.
2107 if (!CanScroll(ConvertDestinationToDelta(destination
))) {
2108 return nsEventStatus_eConsumeDoDefault
;
2110 SetState(KEYBOARD_SCROLL
);
2112 nsPoint initialPosition
=
2113 CSSPoint::ToAppUnits(Metrics().GetVisualScrollOffset());
2114 StartAnimation(do_AddRef(
2115 new SmoothScrollAnimation(*this, initialPosition
, scrollOrigin
)));
2118 // Convert velocity from ParentLayerPoints/ms to ParentLayerPoints/s and then
2119 // to appunits/second.
2121 if (Metrics().GetZoom() != CSSToParentLayerScale(0)) {
2123 CSSPoint::ToAppUnits(ParentLayerPoint(mX
.GetVelocity() * 1000.0f
,
2124 mY
.GetVelocity() * 1000.0f
) /
2125 Metrics().GetZoom());
2128 SmoothScrollAnimation
* animation
= mAnimation
->AsSmoothScrollAnimation();
2129 MOZ_ASSERT(animation
);
2131 animation
->UpdateDestination(aEvent
.mTimeStamp
,
2132 CSSPixel::ToAppUnits(destination
),
2133 nsSize(velocity
.x
, velocity
.y
));
2135 return nsEventStatus_eConsumeDoDefault
;
2138 CSSPoint
AsyncPanZoomController::GetKeyboardDestination(
2139 const KeyboardScrollAction
& aAction
) const {
2140 CSSSize lineScrollSize
;
2141 CSSSize pageScrollSize
;
2142 CSSPoint scrollOffset
;
2144 ParentLayerRect compositionBounds
;
2147 // Grab the lock to access the frame metrics.
2148 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
2150 lineScrollSize
= mScrollMetadata
.GetLineScrollAmount() /
2151 Metrics().GetDevPixelsPerCSSPixel();
2152 pageScrollSize
= mScrollMetadata
.GetPageScrollAmount() /
2153 Metrics().GetDevPixelsPerCSSPixel();
2155 scrollOffset
= GetCurrentAnimationDestination(lock
).valueOr(
2156 Metrics().GetVisualScrollOffset());
2158 scrollRect
= Metrics().GetScrollableRect();
2159 compositionBounds
= Metrics().GetCompositionBounds();
2162 // Calculate the scroll destination based off of the scroll type and direction
2163 CSSPoint scrollDestination
= scrollOffset
;
2165 switch (aAction
.mType
) {
2166 case KeyboardScrollAction::eScrollCharacter
: {
2167 int32_t scrollDistance
=
2168 StaticPrefs::toolkit_scrollbox_horizontalScrollDistance();
2170 if (aAction
.mForward
) {
2171 scrollDestination
.x
+= scrollDistance
* lineScrollSize
.width
;
2173 scrollDestination
.x
-= scrollDistance
* lineScrollSize
.width
;
2177 case KeyboardScrollAction::eScrollLine
: {
2178 int32_t scrollDistance
=
2179 StaticPrefs::toolkit_scrollbox_verticalScrollDistance();
2180 if (scrollDistance
* lineScrollSize
.height
<=
2181 compositionBounds
.Height()) {
2182 if (aAction
.mForward
) {
2183 scrollDestination
.y
+= scrollDistance
* lineScrollSize
.height
;
2185 scrollDestination
.y
-= scrollDistance
* lineScrollSize
.height
;
2191 case KeyboardScrollAction::eScrollPage
: {
2192 if (aAction
.mForward
) {
2193 scrollDestination
.y
+= pageScrollSize
.height
;
2195 scrollDestination
.y
-= pageScrollSize
.height
;
2199 case KeyboardScrollAction::eScrollComplete
: {
2200 if (aAction
.mForward
) {
2201 scrollDestination
.y
= scrollRect
.YMost();
2203 scrollDestination
.y
= scrollRect
.Y();
2209 return scrollDestination
;
2212 ScrollSnapFlags
AsyncPanZoomController::GetScrollSnapFlagsForKeyboardAction(
2213 const KeyboardScrollAction
& aAction
) const {
2214 switch (aAction
.mType
) {
2215 case KeyboardScrollAction::eScrollCharacter
:
2216 case KeyboardScrollAction::eScrollLine
:
2217 return ScrollSnapFlags::IntendedDirection
;
2218 case KeyboardScrollAction::eScrollPage
:
2219 return ScrollSnapFlags::IntendedDirection
|
2220 ScrollSnapFlags::IntendedEndPosition
;
2221 case KeyboardScrollAction::eScrollComplete
:
2222 return ScrollSnapFlags::IntendedEndPosition
;
2224 return ScrollSnapFlags::Disabled
;
2227 ParentLayerPoint
AsyncPanZoomController::GetDeltaForEvent(
2228 const InputData
& aEvent
) const {
2229 ParentLayerPoint delta
;
2230 if (aEvent
.mInputType
== SCROLLWHEEL_INPUT
) {
2231 delta
= GetScrollWheelDelta(aEvent
.AsScrollWheelInput());
2232 } else if (aEvent
.mInputType
== PANGESTURE_INPUT
) {
2233 const PanGestureInput
& panInput
= aEvent
.AsPanGestureInput();
2234 delta
= ToParentLayerCoordinates(panInput
.UserMultipliedPanDisplacement(),
2235 panInput
.mPanStartPoint
);
2240 CSSRect
AsyncPanZoomController::GetCurrentScrollRangeInCssPixels() const {
2241 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
2242 return Metrics().CalculateScrollRange();
2245 // Return whether or not the underlying layer can be scrolled on either axis.
2246 bool AsyncPanZoomController::CanScroll(const InputData
& aEvent
) const {
2247 ParentLayerPoint delta
= GetDeltaForEvent(aEvent
);
2248 if (!delta
.x
&& !delta
.y
) {
2252 if (SCROLLWHEEL_INPUT
== aEvent
.mInputType
) {
2253 const ScrollWheelInput
& scrollWheelInput
= aEvent
.AsScrollWheelInput();
2254 // If it's a wheel scroll, we first check if it is an auto-dir scroll.
2255 // 1. For an auto-dir scroll, check if it's delta should be adjusted, if it
2256 // is, then we can conclude it must be scrollable; otherwise, fall back
2257 // to checking if it is scrollable without adjusting its delta.
2258 // 2. For a non-auto-dir scroll, simply check if it is scrollable without
2259 // adjusting its delta.
2260 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
2261 if (scrollWheelInput
.IsAutoDir(mScrollMetadata
.ForceMousewheelAutodir())) {
2262 auto deltaX
= scrollWheelInput
.mDeltaX
;
2263 auto deltaY
= scrollWheelInput
.mDeltaY
;
2265 IsContentOfHonouredTargetRightToLeft(scrollWheelInput
.HonoursRoot(
2266 mScrollMetadata
.ForceMousewheelAutodirHonourRoot()));
2267 APZAutoDirWheelDeltaAdjuster
adjuster(deltaX
, deltaY
, mX
, mY
, isRTL
);
2268 if (adjuster
.ShouldBeAdjusted()) {
2269 // If we detect that the delta values should be adjusted for an auto-dir
2270 // wheel scroll, then it is impossible to be an unscrollable scroll.
2274 return CanScrollWithWheel(delta
);
2276 return CanScroll(delta
);
2279 ScrollDirections
AsyncPanZoomController::GetAllowedHandoffDirections() const {
2280 ScrollDirections result
;
2281 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
2283 // In Fission there can be non-scrollable APZCs. It's unclear whether
2284 // overscroll-behavior should be respected for these
2285 // (see https://github.com/w3c/csswg-drafts/issues/6523) but
2286 // we currently don't, to match existing practice.
2287 const bool isScrollable
= mX
.CanScroll() || mY
.CanScroll();
2288 const bool isRoot
= IsRootContent();
2289 if ((!isScrollable
&& !isRoot
) || mX
.OverscrollBehaviorAllowsHandoff()) {
2290 result
+= ScrollDirection::eHorizontal
;
2292 if ((!isScrollable
&& !isRoot
) || mY
.OverscrollBehaviorAllowsHandoff()) {
2293 result
+= ScrollDirection::eVertical
;
2298 bool AsyncPanZoomController::CanScroll(const ParentLayerPoint
& aDelta
) const {
2299 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
2300 return mX
.CanScroll(ParentLayerCoord(aDelta
.x
)) ||
2301 mY
.CanScroll(ParentLayerCoord(aDelta
.y
));
2304 bool AsyncPanZoomController::CanScrollWithWheel(
2305 const ParentLayerPoint
& aDelta
) const {
2306 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
2308 // For more details about the concept of a disregarded direction, refer to the
2309 // code in struct ScrollMetadata which defines mDisregardedDirection.
2310 Maybe
<ScrollDirection
> disregardedDirection
=
2311 mScrollMetadata
.GetDisregardedDirection();
2312 if (mX
.CanScroll(ParentLayerCoord(aDelta
.x
)) &&
2313 disregardedDirection
!= Some(ScrollDirection::eHorizontal
)) {
2316 if (mY
.CanScroll(ParentLayerCoord(aDelta
.y
)) &&
2317 disregardedDirection
!= Some(ScrollDirection::eVertical
)) {
2323 bool AsyncPanZoomController::CanScroll(ScrollDirection aDirection
) const {
2324 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
2325 switch (aDirection
) {
2326 case ScrollDirection::eHorizontal
:
2327 return mX
.CanScroll();
2328 case ScrollDirection::eVertical
:
2329 return mY
.CanScroll();
2331 MOZ_ASSERT_UNREACHABLE("Invalid value");
2335 bool AsyncPanZoomController::CanVerticalScrollWithDynamicToolbar() const {
2336 MOZ_ASSERT(IsRootContent());
2338 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
2339 return mY
.CanVerticalScrollWithDynamicToolbar();
2342 bool AsyncPanZoomController::CanOverscrollUpwards() const {
2343 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
2344 return !mY
.CanScrollTo(eSideTop
) && mY
.OverscrollBehaviorAllowsHandoff();
2347 bool AsyncPanZoomController::CanScrollDownwards() const {
2348 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
2349 return mY
.CanScrollTo(eSideBottom
);
2352 SideBits
AsyncPanZoomController::ScrollableDirections() const {
2354 { // scope lock to respect lock ordering with APZCTreeManager::mTreeLock
2355 // which will be acquired in the `GetCompositorFixedLayerMargins` below.
2356 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
2357 result
= mX
.ScrollableDirections() | mY
.ScrollableDirections();
2360 if (IsRootContent()) {
2361 if (APZCTreeManager
* treeManagerLocal
= GetApzcTreeManager()) {
2362 ScreenMargin fixedLayerMargins
=
2363 treeManagerLocal
->GetCompositorFixedLayerMargins();
2365 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
2366 result
|= mY
.ScrollableDirectionsWithDynamicToolbar(fixedLayerMargins
);
2374 bool AsyncPanZoomController::IsContentOfHonouredTargetRightToLeft(
2375 bool aHonoursRoot
) const {
2377 return mScrollMetadata
.IsAutoDirRootContentRTL();
2379 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
2380 return Metrics().IsHorizontalContentRightToLeft();
2383 bool AsyncPanZoomController::AllowScrollHandoffInCurrentBlock() const {
2384 bool result
= mInputQueue
->AllowScrollHandoff();
2385 if (!StaticPrefs::apz_allow_immediate_handoff()) {
2386 if (InputBlockState
* currentBlock
= GetCurrentInputBlock()) {
2387 // Do not allow handoff beyond the first APZC to scroll.
2388 if (currentBlock
->GetScrolledApzc() == this) {
2390 APZC_LOG("%p dropping handoff; AllowImmediateHandoff=false\n", this);
2397 void AsyncPanZoomController::DoDelayedRequestContentRepaint() {
2398 if (!IsDestroyed() && mPinchPaintTimerSet
) {
2399 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
2400 RequestContentRepaint();
2402 mPinchPaintTimerSet
= false;
2405 void AsyncPanZoomController::DoDelayedTransformEndNotification(
2406 PanZoomState aOldState
) {
2407 if (!IsDestroyed() && IsDelayedTransformEndSet()) {
2408 DispatchStateChangeNotification(aOldState
, NOTHING
);
2410 SetDelayedTransformEnd(false);
2413 static void AdjustDeltaForAllowedScrollDirections(
2414 ParentLayerPoint
& aDelta
,
2415 const ScrollDirections
& aAllowedScrollDirections
) {
2416 if (!aAllowedScrollDirections
.contains(ScrollDirection::eHorizontal
)) {
2419 if (!aAllowedScrollDirections
.contains(ScrollDirection::eVertical
)) {
2424 nsEventStatus
AsyncPanZoomController::OnScrollWheel(
2425 const ScrollWheelInput
& aEvent
) {
2426 // Get the scroll wheel's delta values in parent-layer pixels. But before
2427 // getting the values, we need to check if it is an auto-dir scroll and if it
2428 // should be adjusted, if both answers are yes, let's adjust X and Y values
2429 // first, and then get the delta values in parent-layer pixels based on the
2431 bool adjustedByAutoDir
= false;
2432 auto deltaX
= aEvent
.mDeltaX
;
2433 auto deltaY
= aEvent
.mDeltaY
;
2434 ParentLayerPoint delta
;
2436 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
2437 if (aEvent
.IsAutoDir(mScrollMetadata
.ForceMousewheelAutodir())) {
2438 // It's an auto-dir scroll, so check if its delta should be adjusted, if
2440 bool isRTL
= IsContentOfHonouredTargetRightToLeft(aEvent
.HonoursRoot(
2441 mScrollMetadata
.ForceMousewheelAutodirHonourRoot()));
2442 APZAutoDirWheelDeltaAdjuster
adjuster(deltaX
, deltaY
, mX
, mY
, isRTL
);
2443 if (adjuster
.ShouldBeAdjusted()) {
2445 adjustedByAutoDir
= true;
2449 // Ensure the calls to GetScrollWheelDelta are outside the mRecursiveMutex
2450 // lock since these calls may acquire the APZ tree lock. Holding
2451 // mRecursiveMutex while acquiring the APZ tree lock is lock ordering
2453 if (adjustedByAutoDir
) {
2454 // If the original delta values have been adjusted, we pass them to
2455 // replace the original delta values in |aEvent| so that the delta values
2456 // in parent-layer pixels are caculated based on the adjusted values, not
2457 // the original ones.
2458 // Pay special attention to the last two parameters. They are in a swaped
2459 // order so that they still correspond to their delta after adjustment.
2460 delta
= GetScrollWheelDelta(aEvent
, deltaX
, deltaY
,
2461 aEvent
.mUserDeltaMultiplierY
,
2462 aEvent
.mUserDeltaMultiplierX
);
2464 // If the original delta values haven't been adjusted by auto-dir, just pass
2465 // the |aEvent| and caculate the delta values in parent-layer pixels based
2466 // on the original delta values from |aEvent|.
2467 delta
= GetScrollWheelDelta(aEvent
);
2470 APZC_LOG("%p got a scroll-wheel with delta in parent-layer pixels: %s\n",
2471 this, ToString(delta
).c_str());
2473 if (adjustedByAutoDir
) {
2474 MOZ_ASSERT(delta
.x
|| delta
.y
,
2475 "Adjusted auto-dir delta values can never be all-zero.");
2476 APZC_LOG("%p got a scroll-wheel with adjusted auto-dir delta values\n",
2478 } else if ((delta
.x
|| delta
.y
) && !CanScrollWithWheel(delta
)) {
2479 // We can't scroll this apz anymore, so we simply drop the event.
2480 if (mInputQueue
->GetActiveWheelTransaction() &&
2481 StaticPrefs::test_mousescroll()) {
2482 if (RefPtr
<GeckoContentController
> controller
=
2483 GetGeckoContentController()) {
2484 controller
->NotifyMozMouseScrollEvent(GetScrollId(),
2485 u
"MozMouseScrollFailed"_ns
);
2488 return nsEventStatus_eConsumeNoDefault
;
2491 MOZ_ASSERT(mInputQueue
->GetCurrentWheelBlock());
2492 AdjustDeltaForAllowedScrollDirections(
2493 delta
, mInputQueue
->GetCurrentWheelBlock()->GetAllowedScrollDirections());
2495 if (delta
.x
== 0 && delta
.y
== 0) {
2496 // Avoid spurious state changes and unnecessary work
2497 return nsEventStatus_eIgnore
;
2500 switch (aEvent
.mScrollMode
) {
2501 case ScrollWheelInput::SCROLLMODE_INSTANT
: {
2502 // Wheel events from "clicky" mouse wheels trigger scroll snapping to the
2503 // next snap point. Check for this, and adjust the delta to take into
2504 // account the snap point.
2505 CSSPoint startPosition
;
2507 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
2508 startPosition
= Metrics().GetVisualScrollOffset();
2510 Maybe
<CSSSnapDestination
> snapDestination
=
2511 MaybeAdjustDeltaForScrollSnappingOnWheelInput(aEvent
, delta
,
2514 ScreenPoint distance
= ToScreenCoordinates(
2515 ParentLayerPoint(fabs(delta
.x
), fabs(delta
.y
)), aEvent
.mLocalOrigin
);
2519 OverscrollHandoffState
handoffState(
2520 *mInputQueue
->GetCurrentWheelBlock()->GetOverscrollHandoffChain(),
2521 distance
, ScrollSource::Wheel
);
2522 ParentLayerPoint startPoint
= aEvent
.mLocalOrigin
;
2523 ParentLayerPoint endPoint
= aEvent
.mLocalOrigin
- delta
;
2524 RecordScrollPayload(aEvent
.mTimeStamp
);
2526 CallDispatchScroll(startPoint
, endPoint
, handoffState
);
2527 ParentLayerPoint remainingDelta
= endPoint
- startPoint
;
2528 if (remainingDelta
!= delta
) {
2529 // If any scrolling happened, set WHEEL_SCROLL explicitly so that it
2530 // will trigger a TransformEnd notification.
2531 SetState(WHEEL_SCROLL
);
2534 if (snapDestination
) {
2536 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
2537 mLastSnapTargetIds
= std::move(snapDestination
->mTargetIds
);
2542 // The calls above handle their own locking; moreover,
2543 // ToScreenCoordinates() and CallDispatchScroll() can grab the tree lock.
2544 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
2545 RequestContentRepaint();
2550 case ScrollWheelInput::SCROLLMODE_SMOOTH
: {
2551 // The lock must be held across the entire update operation, so the
2552 // compositor doesn't end the animation before we get a chance to
2554 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
2556 RecordScrollPayload(aEvent
.mTimeStamp
);
2557 // Perform scroll snapping if appropriate.
2558 // If we're already in a wheel scroll or smooth scroll animation,
2559 // the delta is applied to its destination, not to the current
2560 // scroll position. Take this into account when finding a snap point.
2561 CSSPoint startPosition
= GetCurrentAnimationDestination(lock
).valueOr(
2562 Metrics().GetVisualScrollOffset());
2564 if (Maybe
<CSSSnapDestination
> snapDestination
=
2565 MaybeAdjustDeltaForScrollSnappingOnWheelInput(aEvent
, delta
,
2567 // If we're scroll snapping, use a smooth scroll animation to get
2568 // the desired physics. Note that SmoothMsdScrollTo() will re-use an
2569 // existing smooth scroll animation if there is one.
2570 APZC_LOG("%p wheel scrolling to snap point %s\n", this,
2571 ToString(startPosition
).c_str());
2572 SmoothMsdScrollTo(std::move(*snapDestination
),
2573 ScrollTriggeredByScript::No
);
2577 // Otherwise, use a wheel scroll animation, also reusing one if possible.
2578 if (mState
!= WHEEL_SCROLL
) {
2580 SetState(WHEEL_SCROLL
);
2582 nsPoint initialPosition
=
2583 CSSPoint::ToAppUnits(Metrics().GetVisualScrollOffset());
2584 StartAnimation(do_AddRef(new WheelScrollAnimation(
2585 *this, initialPosition
, aEvent
.mDeltaType
)));
2587 // Convert velocity from ParentLayerPoints/ms to ParentLayerPoints/s and
2588 // then to appunits/second.
2590 nsPoint deltaInAppUnits
;
2592 if (Metrics().GetZoom() != CSSToParentLayerScale(0)) {
2593 deltaInAppUnits
= CSSPoint::ToAppUnits(delta
/ Metrics().GetZoom());
2595 CSSPoint::ToAppUnits(ParentLayerPoint(mX
.GetVelocity() * 1000.0f
,
2596 mY
.GetVelocity() * 1000.0f
) /
2597 Metrics().GetZoom());
2600 WheelScrollAnimation
* animation
= mAnimation
->AsWheelScrollAnimation();
2601 animation
->UpdateDelta(aEvent
.mTimeStamp
, deltaInAppUnits
,
2602 nsSize(velocity
.x
, velocity
.y
));
2607 return nsEventStatus_eConsumeNoDefault
;
2610 void AsyncPanZoomController::NotifyMozMouseScrollEvent(
2611 const nsString
& aString
) const {
2612 RefPtr
<GeckoContentController
> controller
= GetGeckoContentController();
2616 controller
->NotifyMozMouseScrollEvent(GetScrollId(), aString
);
2619 nsEventStatus
AsyncPanZoomController::OnPanMayBegin(
2620 const PanGestureInput
& aEvent
) {
2621 APZC_LOG_DETAIL("got a pan-maybegin in state %s\n", this,
2622 ToString(mState
).c_str());
2624 StartTouch(aEvent
.mLocalPanStartPoint
, aEvent
.mTimeStamp
);
2625 MOZ_ASSERT(GetCurrentPanGestureBlock());
2626 GetCurrentPanGestureBlock()->GetOverscrollHandoffChain()->CancelAnimations();
2628 return nsEventStatus_eConsumeNoDefault
;
2631 nsEventStatus
AsyncPanZoomController::OnPanCancelled(
2632 const PanGestureInput
& aEvent
) {
2633 APZC_LOG_DETAIL("got a pan-cancelled in state %s\n", this,
2634 ToString(mState
).c_str());
2639 return nsEventStatus_eConsumeNoDefault
;
2642 nsEventStatus
AsyncPanZoomController::OnPanBegin(
2643 const PanGestureInput
& aEvent
) {
2644 APZC_LOG_DETAIL("got a pan-begin in state %s\n", this,
2645 ToString(mState
).c_str());
2647 if (mState
== SMOOTHMSD_SCROLL
) {
2648 // SMOOTHMSD_SCROLL scrolls are cancelled by pan gestures.
2652 StartTouch(aEvent
.mLocalPanStartPoint
, aEvent
.mTimeStamp
);
2654 if (!UsingStatefulAxisLock()) {
2657 float dx
= aEvent
.mPanDisplacement
.x
, dy
= aEvent
.mPanDisplacement
.y
;
2659 if (dx
!= 0.0f
|| dy
!= 0.0f
) {
2660 double angle
= atan2(dy
, dx
); // range [-pi, pi]
2661 angle
= fabs(angle
); // range [0, pi]
2662 HandlePanning(angle
);
2668 // Call into OnPan in order to process any delta included in this event.
2669 OnPan(aEvent
, FingersOnTouchpad::Yes
);
2671 return nsEventStatus_eConsumeNoDefault
;
2674 std::tuple
<ParentLayerPoint
, ScreenPoint
>
2675 AsyncPanZoomController::GetDisplacementsForPanGesture(
2676 const PanGestureInput
& aEvent
) {
2677 // Note that there is a multiplier that applies onto the "physical" pan
2678 // displacement (how much the user's fingers moved) that produces the
2679 // "logical" pan displacement (how much the page should move). For some of the
2680 // code below it makes more sense to use the physical displacement rather than
2681 // the logical displacement, and vice-versa.
2682 ScreenPoint physicalPanDisplacement
= aEvent
.mPanDisplacement
;
2683 ParentLayerPoint logicalPanDisplacement
=
2684 aEvent
.UserMultipliedLocalPanDisplacement();
2685 if (aEvent
.mDeltaType
== PanGestureInput::PANDELTA_PAGE
) {
2686 // Pan events with page units are used by Gtk, so this replicates Gtk:
2687 // https://gitlab.gnome.org/GNOME/gtk/blob/c734c7e9188b56f56c3a504abee05fa40c5475ac/gtk/gtkrange.c#L3065-3073
2688 CSSSize pageScrollSize
;
2689 CSSToParentLayerScale zoom
;
2691 // Grab the lock to access the frame metrics.
2692 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
2693 pageScrollSize
= mScrollMetadata
.GetPageScrollAmount() /
2694 Metrics().GetDevPixelsPerCSSPixel();
2695 zoom
= Metrics().GetZoom();
2697 // scrollUnit* is in units of "ParentLayer pixels per page proportion"...
2698 auto scrollUnitWidth
= std::min(std::pow(pageScrollSize
.width
, 2.0 / 3.0),
2699 pageScrollSize
.width
/ 2.0) *
2701 auto scrollUnitHeight
= std::min(std::pow(pageScrollSize
.height
, 2.0 / 3.0),
2702 pageScrollSize
.height
/ 2.0) *
2704 // ... and pan displacements are in units of "page proportion count"
2705 // here, so the products of them and scrollUnit* are in ParentLayer pixels
2706 ParentLayerPoint
physicalPanDisplacementPL(
2707 physicalPanDisplacement
.x
* scrollUnitWidth
,
2708 physicalPanDisplacement
.y
* scrollUnitHeight
);
2709 physicalPanDisplacement
= ToScreenCoordinates(physicalPanDisplacementPL
,
2710 aEvent
.mLocalPanStartPoint
);
2711 logicalPanDisplacement
.x
*= scrollUnitWidth
;
2712 logicalPanDisplacement
.y
*= scrollUnitHeight
;
2714 // Accelerate (decelerate) any pans by raising it to a user configurable
2715 // power (apz.touch_acceleration_factor_x, apz.touch_acceleration_factor_y)
2717 // Confine input for pow() to greater than or equal to 0 to avoid domain
2718 // errors with non-integer exponents
2719 if (mX
.GetVelocity() != 0) {
2720 float absVelocity
= std::abs(mX
.GetVelocity());
2721 logicalPanDisplacement
.x
*=
2722 std::pow(absVelocity
,
2723 StaticPrefs::apz_touch_acceleration_factor_x()) /
2727 if (mY
.GetVelocity() != 0) {
2728 float absVelocity
= std::abs(mY
.GetVelocity());
2729 logicalPanDisplacement
.y
*=
2730 std::pow(absVelocity
,
2731 StaticPrefs::apz_touch_acceleration_factor_y()) /
2736 MOZ_ASSERT(GetCurrentPanGestureBlock());
2737 AdjustDeltaForAllowedScrollDirections(
2738 logicalPanDisplacement
,
2739 GetCurrentPanGestureBlock()->GetAllowedScrollDirections());
2741 if (GetAxisLockMode() == DOMINANT_AXIS
) {
2742 // Given a pan gesture and both directions have a delta, implement
2743 // dominant axis scrolling and only use the delta for the larger
2745 if (logicalPanDisplacement
.y
!= 0 && logicalPanDisplacement
.x
!= 0) {
2746 if (fabs(logicalPanDisplacement
.y
) >= fabs(logicalPanDisplacement
.x
)) {
2747 logicalPanDisplacement
.x
= 0;
2748 physicalPanDisplacement
.x
= 0;
2750 logicalPanDisplacement
.y
= 0;
2751 physicalPanDisplacement
.y
= 0;
2756 return {logicalPanDisplacement
, physicalPanDisplacement
};
2759 nsEventStatus
AsyncPanZoomController::OnPan(
2760 const PanGestureInput
& aEvent
, FingersOnTouchpad aFingersOnTouchpad
) {
2761 APZC_LOG_DETAIL("got a pan-pan in state %s\n", this,
2762 ToString(GetState()).c_str());
2764 if (GetState() == SMOOTHMSD_SCROLL
) {
2765 if (aFingersOnTouchpad
== FingersOnTouchpad::No
) {
2766 // When a SMOOTHMSD_SCROLL scroll is being processed on a frame, mouse
2767 // wheel and trackpad momentum scroll position updates will not cancel the
2768 // SMOOTHMSD_SCROLL scroll animations, enabling scripts that depend on
2769 // them to be responsive without forcing the user to wait for the momentum
2770 // scrolling to completely stop.
2771 return nsEventStatus_eConsumeNoDefault
;
2774 // SMOOTHMSD_SCROLL scrolls are cancelled by pan gestures.
2778 if (GetState() == NOTHING
) {
2779 // This event block was interrupted by something else. If the user's fingers
2780 // are still on on the touchpad we want to resume scrolling, otherwise we
2781 // ignore the rest of the scroll gesture.
2782 if (aFingersOnTouchpad
== FingersOnTouchpad::No
) {
2783 return nsEventStatus_eConsumeNoDefault
;
2785 // Resume / restart the pan.
2786 // PanBegin will call back into this function with mState == PANNING.
2787 return OnPanBegin(aEvent
);
2790 auto [logicalPanDisplacement
, physicalPanDisplacement
] =
2791 GetDisplacementsForPanGesture(aEvent
);
2794 // Grab the lock to protect the animation from being canceled on the updater
2796 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
2797 MOZ_ASSERT_IF(GetState() == OVERSCROLL_ANIMATION
, mAnimation
);
2799 if (GetState() == OVERSCROLL_ANIMATION
&& mAnimation
&&
2800 aFingersOnTouchpad
== FingersOnTouchpad::No
) {
2801 // If there is an on-going overscroll animation, we tell the animation
2802 // whether the displacements should be handled by the animation or not.
2803 MOZ_ASSERT(mAnimation
->AsOverscrollAnimation());
2804 if (RefPtr
<OverscrollAnimation
> overscrollAnimation
=
2805 mAnimation
->AsOverscrollAnimation()) {
2806 overscrollAnimation
->HandlePanMomentum(logicalPanDisplacement
);
2807 // And then as a result of the above call, if the animation is currently
2808 // affecting on the axis, drop the displacement value on the axis so
2809 // that we stop further oversrolling on the axis.
2810 if (overscrollAnimation
->IsManagingXAxis()) {
2811 logicalPanDisplacement
.x
= 0;
2812 physicalPanDisplacement
.x
= 0;
2814 if (overscrollAnimation
->IsManagingYAxis()) {
2815 logicalPanDisplacement
.y
= 0;
2816 physicalPanDisplacement
.y
= 0;
2822 HandlePanningUpdate(physicalPanDisplacement
);
2824 MOZ_ASSERT(GetCurrentPanGestureBlock());
2825 ScreenPoint
panDistance(fabs(physicalPanDisplacement
.x
),
2826 fabs(physicalPanDisplacement
.y
));
2827 OverscrollHandoffState
handoffState(
2828 *GetCurrentPanGestureBlock()->GetOverscrollHandoffChain(), panDistance
,
2829 ScrollSource::Touchpad
);
2831 // Create fake "touch" positions that will result in the desired scroll
2832 // motion. Note that the pan displacement describes the change in scroll
2833 // position: positive displacement values mean that the scroll position
2834 // increases. However, an increase in scroll position means that the scrolled
2835 // contents are moved to the left / upwards. Since our simulated "touches"
2836 // determine the motion of the scrolled contents, not of the scroll position,
2837 // they need to move in the opposite direction of the pan displacement.
2838 ParentLayerPoint startPoint
= aEvent
.mLocalPanStartPoint
;
2839 ParentLayerPoint endPoint
=
2840 aEvent
.mLocalPanStartPoint
- logicalPanDisplacement
;
2841 if (logicalPanDisplacement
!= ParentLayerPoint()) {
2842 // Don't expect a composite to be triggered if the displacement is zero
2843 RecordScrollPayload(aEvent
.mTimeStamp
);
2846 const ParentLayerPoint velocity
= GetVelocityVector();
2847 bool consumed
= CallDispatchScroll(startPoint
, endPoint
, handoffState
);
2849 const ParentLayerPoint visualDisplacement
= ToParentLayerCoordinates(
2850 handoffState
.mTotalMovement
, aEvent
.mPanStartPoint
);
2851 // We need to update the axis velocity in order to get a useful display port
2852 // size and position. We need to do so even if this is a momentum pan (i.e.
2853 // aFingersOnTouchpad == No); in that case the "with touch" part is not
2854 // really appropriate, so we may want to rethink this at some point.
2855 // Note that we have to make all simulated positions relative to
2856 // Axis::GetPos(), because the current position is an invented position, and
2857 // because resetting the position to the mouse position (e.g.
2858 // aEvent.mLocalStartPoint) would mess up velocity calculation. (This is
2859 // the only caller of UpdateWithTouchAtDevicePoint() for pan events, so
2860 // there is no risk of other calls resetting the position.)
2861 // Also note that if there is an on-going overscroll animation in the axis,
2862 // we shouldn't call UpdateWithTouchAtDevicePoint because the call changes
2863 // the velocity which should be managed by the overscroll animation.
2864 // Finally, note that we do this *after* CallDispatchScroll(), so that the
2865 // position we use reflects the actual amount of movement that occurred
2866 // (in particular, if we're in overscroll, if reflects the amount of movement
2867 // *after* applying resistance). This is important because we want the axis
2868 // velocity to track the visual movement speed of the page.
2869 if (visualDisplacement
.x
!= 0) {
2870 mX
.UpdateWithTouchAtDevicePoint(mX
.GetPos() - visualDisplacement
.x
,
2873 if (visualDisplacement
.y
!= 0) {
2874 mY
.UpdateWithTouchAtDevicePoint(mY
.GetPos() - visualDisplacement
.y
,
2878 if (aFingersOnTouchpad
== FingersOnTouchpad::No
) {
2879 if (IsOverscrolled() && GetState() != OVERSCROLL_ANIMATION
) {
2880 StartOverscrollAnimation(velocity
, GetOverscrollSideBits());
2881 } else if (!consumed
) {
2882 // If there is unconsumed scroll and we're in the momentum part of the
2883 // pan gesture, terminate the momentum scroll. This prevents momentum
2884 // scroll events from unexpectedly causing scrolling later if somehow
2885 // the APZC becomes scrollable again in this direction (e.g. if the user
2886 // uses some other input method to scroll in the opposite direction).
2891 return nsEventStatus_eConsumeNoDefault
;
2894 nsEventStatus
AsyncPanZoomController::OnPanEnd(const PanGestureInput
& aEvent
) {
2895 APZC_LOG_DETAIL("got a pan-end in state %s\n", this,
2896 ToString(mState
).c_str());
2898 // This can happen if the OS sends a second pan-end event after the first one
2899 // has already started an overscroll animation or entered a fling state.
2900 // This has been observed on some Wayland versions.
2901 PanZoomState currentState
= GetState();
2902 if (currentState
== OVERSCROLL_ANIMATION
|| currentState
== NOTHING
||
2903 currentState
== FLING
) {
2904 return nsEventStatus_eIgnore
;
2907 if (aEvent
.mPanDisplacement
!= ScreenPoint
{}) {
2908 // Call into OnPan in order to process the delta included in this event.
2909 OnPan(aEvent
, FingersOnTouchpad::Yes
);
2912 // Do not unlock the axis lock at the end of a pan gesture. The axis lock
2913 // should extend into the momentum scroll.
2914 EndTouch(aEvent
.mTimeStamp
, Axis::ClearAxisLock::No
);
2916 // Use HandleEndOfPan for fling on platforms that don't
2917 // emit momentum events (Gtk).
2918 if (aEvent
.mSimulateMomentum
) {
2919 return HandleEndOfPan();
2922 MOZ_ASSERT(GetCurrentPanGestureBlock());
2923 RefPtr
<const OverscrollHandoffChain
> overscrollHandoffChain
=
2924 GetCurrentPanGestureBlock()->GetOverscrollHandoffChain();
2926 // Call SnapBackOverscrolledApzcForMomentum regardless whether this APZC is
2927 // overscrolled or not since overscroll animations for ancestor APZCs in this
2928 // overscroll handoff chain might have been cancelled by the current pan
2930 overscrollHandoffChain
->SnapBackOverscrolledApzcForMomentum(
2931 this, GetVelocityVector());
2932 // If this APZC is overscrolled, the above SnapBackOverscrolledApzcForMomentum
2933 // triggers an overscroll animation. When we're finished with the overscroll
2934 // animation, the state will be reset and a TransformEnd will be sent to the
2936 currentState
= GetState();
2937 if (currentState
!= OVERSCROLL_ANIMATION
) {
2938 // Do not send a state change notification to the content controller here.
2939 // Instead queue a delayed task to dispatch the notification if no
2940 // momentum pan or scroll snap follows the pan-end.
2941 RefPtr
<GeckoContentController
> controller
= GetGeckoContentController();
2943 SetDelayedTransformEnd(true);
2944 controller
->PostDelayedTask(
2945 NewRunnableMethod
<PanZoomState
>(
2946 "layers::AsyncPanZoomController::"
2947 "DoDelayedTransformEndNotification",
2948 this, &AsyncPanZoomController::DoDelayedTransformEndNotification
,
2950 StaticPrefs::apz_scrollend_event_content_delay_ms());
2951 SetStateNoContentControllerDispatch(NOTHING
);
2957 // Drop any velocity on axes where we don't have room to scroll anyways
2958 // (in this APZC, or an APZC further in the handoff chain).
2959 // This ensures that we don't enlarge the display port unnecessarily.
2961 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
2962 if (!overscrollHandoffChain
->CanScrollInDirection(
2963 this, ScrollDirection::eHorizontal
)) {
2966 if (!overscrollHandoffChain
->CanScrollInDirection(
2967 this, ScrollDirection::eVertical
)) {
2972 RequestContentRepaint();
2973 ScrollSnapToDestination();
2975 return nsEventStatus_eConsumeNoDefault
;
2978 nsEventStatus
AsyncPanZoomController::OnPanMomentumStart(
2979 const PanGestureInput
& aEvent
) {
2980 APZC_LOG_DETAIL("got a pan-momentumstart in state %s\n", this,
2981 ToString(mState
).c_str());
2983 if (mState
== SMOOTHMSD_SCROLL
|| mState
== OVERSCROLL_ANIMATION
) {
2984 return nsEventStatus_eConsumeNoDefault
;
2987 if (IsDelayedTransformEndSet()) {
2988 // Do not send another TransformBegin notification if we have not
2989 // delivered a corresponding TransformEnd. Also ensure that any
2990 // queued transform-end due to a pan-end is not sent. Instead rely
2991 // on the transform-end sent due to the momentum pan.
2992 SetDelayedTransformEnd(false);
2993 SetStateNoContentControllerDispatch(PAN_MOMENTUM
);
2995 SetState(PAN_MOMENTUM
);
2998 // Call into OnPan in order to process any delta included in this event.
2999 OnPan(aEvent
, FingersOnTouchpad::No
);
3001 return nsEventStatus_eConsumeNoDefault
;
3004 nsEventStatus
AsyncPanZoomController::OnPanMomentumEnd(
3005 const PanGestureInput
& aEvent
) {
3006 APZC_LOG_DETAIL("got a pan-momentumend in state %s\n", this,
3007 ToString(mState
).c_str());
3009 if (mState
== OVERSCROLL_ANIMATION
) {
3010 return nsEventStatus_eConsumeNoDefault
;
3013 // Call into OnPan in order to process any delta included in this event.
3014 OnPan(aEvent
, FingersOnTouchpad::No
);
3016 // We need to reset the velocity to zero. We don't really have a "touch"
3017 // here because the touch has already ended long before the momentum
3018 // animation started, but I guess it doesn't really matter for now.
3023 RequestContentRepaint();
3025 return nsEventStatus_eConsumeNoDefault
;
3028 nsEventStatus
AsyncPanZoomController::OnPanInterrupted(
3029 const PanGestureInput
& aEvent
) {
3030 APZC_LOG_DETAIL("got a pan-interrupted in state %s\n", this,
3031 ToString(mState
).c_str());
3035 return nsEventStatus_eIgnore
;
3038 nsEventStatus
AsyncPanZoomController::OnLongPress(
3039 const TapGestureInput
& aEvent
) {
3040 APZC_LOG_DETAIL("got a long-press in state %s\n", this,
3041 ToString(mState
).c_str());
3042 RefPtr
<GeckoContentController
> controller
= GetGeckoContentController();
3044 if (Maybe
<LayoutDevicePoint
> geckoScreenPoint
=
3045 ConvertToGecko(aEvent
.mPoint
)) {
3046 TouchBlockState
* touch
= GetCurrentTouchBlock();
3049 "%p dropping long-press because some non-touch block interrupted "
3052 return nsEventStatus_eIgnore
;
3054 if (touch
->IsDuringFastFling()) {
3055 APZC_LOG("%p dropping long-press because of fast fling\n", this);
3056 return nsEventStatus_eIgnore
;
3058 uint64_t blockId
= GetInputQueue()->InjectNewTouchBlock(this);
3059 controller
->HandleTap(TapType::eLongTap
, *geckoScreenPoint
,
3060 aEvent
.modifiers
, GetGuid(), blockId
, Nothing());
3061 return nsEventStatus_eConsumeNoDefault
;
3064 return nsEventStatus_eIgnore
;
3067 nsEventStatus
AsyncPanZoomController::OnLongPressUp(
3068 const TapGestureInput
& aEvent
) {
3069 APZC_LOG_DETAIL("got a long-tap-up in state %s\n", this,
3070 ToString(mState
).c_str());
3071 return GenerateSingleTap(TapType::eLongTapUp
, aEvent
.mPoint
,
3075 nsEventStatus
AsyncPanZoomController::GenerateSingleTap(
3076 TapType aType
, const ScreenIntPoint
& aPoint
,
3077 mozilla::Modifiers aModifiers
) {
3078 RefPtr
<GeckoContentController
> controller
= GetGeckoContentController();
3080 if (Maybe
<LayoutDevicePoint
> geckoScreenPoint
= ConvertToGecko(aPoint
)) {
3081 TouchBlockState
* touch
= GetCurrentTouchBlock();
3082 // |touch| may be null in the case where this function is
3083 // invoked by GestureEventListener on a timeout. In that case we already
3084 // verified that the single tap is allowed so we let it through.
3085 // XXX there is a bug here that in such a case the touch block that
3086 // generated this tap will not get its mSingleTapOccurred flag set.
3087 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1256344#c6
3089 if (touch
->IsDuringFastFling()) {
3091 "%p dropping single-tap because it was during a fast-fling\n",
3093 return nsEventStatus_eIgnore
;
3095 touch
->SetSingleTapOccurred();
3097 // Because this may be being running as part of
3098 // APZCTreeManager::ReceiveInputEvent, calling controller->HandleTap
3099 // directly might mean that content receives the single tap message before
3100 // the corresponding touch-up. To avoid that we schedule the singletap
3101 // message to run on the next spin of the event loop. See bug 965381 for
3102 // the issue this was causing.
3103 APZC_LOG("posting runnable for HandleTap from GenerateSingleTap");
3104 RefPtr
<Runnable
> runnable
=
3105 NewRunnableMethod
<TapType
, LayoutDevicePoint
, mozilla::Modifiers
,
3106 ScrollableLayerGuid
, uint64_t,
3107 Maybe
<DoubleTapToZoomMetrics
>>(
3108 "layers::GeckoContentController::HandleTap", controller
,
3109 &GeckoContentController::HandleTap
, aType
, *geckoScreenPoint
,
3110 aModifiers
, GetGuid(), touch
? touch
->GetBlockId() : 0,
3113 controller
->PostDelayedTask(runnable
.forget(), 0);
3114 return nsEventStatus_eConsumeNoDefault
;
3117 return nsEventStatus_eIgnore
;
3120 void AsyncPanZoomController::OnTouchEndOrCancel() {
3121 if (RefPtr
<GeckoContentController
> controller
= GetGeckoContentController()) {
3122 MOZ_ASSERT(GetCurrentTouchBlock());
3123 controller
->NotifyAPZStateChange(
3124 GetGuid(), APZStateChange::eEndTouch
,
3125 GetCurrentTouchBlock()->SingleTapOccurred(),
3126 Some(GetCurrentTouchBlock()->GetBlockId()));
3130 nsEventStatus
AsyncPanZoomController::OnSingleTapUp(
3131 const TapGestureInput
& aEvent
) {
3132 APZC_LOG_DETAIL("got a single-tap-up in state %s\n", this,
3133 ToString(mState
).c_str());
3134 // If mZoomConstraints.mAllowDoubleTapZoom is true we wait for a call to
3135 // OnSingleTapConfirmed before sending event to content
3136 MOZ_ASSERT(GetCurrentTouchBlock());
3137 if (!(ZoomConstraintsAllowDoubleTapZoom() &&
3138 GetCurrentTouchBlock()->TouchActionAllowsDoubleTapZoom())) {
3139 return GenerateSingleTap(TapType::eSingleTap
, aEvent
.mPoint
,
3142 return nsEventStatus_eIgnore
;
3145 nsEventStatus
AsyncPanZoomController::OnSingleTapConfirmed(
3146 const TapGestureInput
& aEvent
) {
3147 APZC_LOG_DETAIL("got a single-tap-confirmed in state %s\n", this,
3148 ToString(mState
).c_str());
3149 return GenerateSingleTap(TapType::eSingleTap
, aEvent
.mPoint
,
3153 nsEventStatus
AsyncPanZoomController::OnDoubleTap(
3154 const TapGestureInput
& aEvent
) {
3155 APZC_LOG_DETAIL("got a double-tap in state %s\n", this,
3156 ToString(mState
).c_str());
3158 MOZ_ASSERT(IsRootForLayersId(),
3159 "This function should be called for the root content APZC or "
3162 CSSToCSSMatrix4x4 transformToRootContentApzc
;
3163 RefPtr
<AsyncPanZoomController
> rootContentApzc
;
3164 if (IsRootContent()) {
3165 rootContentApzc
= RefPtr
{this};
3167 if (APZCTreeManager
* treeManagerLocal
= GetApzcTreeManager()) {
3168 rootContentApzc
= treeManagerLocal
->FindZoomableApzc(this);
3169 if (rootContentApzc
) {
3170 MOZ_ASSERT(rootContentApzc
->GetLayersId() != GetLayersId());
3171 MOZ_ASSERT(this == treeManagerLocal
->FindRootApzcFor(GetLayersId()));
3172 transformToRootContentApzc
=
3173 treeManagerLocal
->GetOopifToRootContentTransform(this);
3175 CSSPoint rootScrollPosition
= rootContentApzc
->GetLayoutScrollOffset();
3176 transformToRootContentApzc
.PostTranslate(rootScrollPosition
.x
,
3177 rootScrollPosition
.y
, 0);
3182 if (!rootContentApzc
) {
3183 return nsEventStatus_eIgnore
;
3186 RefPtr
<GeckoContentController
> controller
= GetGeckoContentController();
3188 if (rootContentApzc
->ZoomConstraintsAllowDoubleTapZoom() &&
3189 (!GetCurrentTouchBlock() ||
3190 GetCurrentTouchBlock()->TouchActionAllowsDoubleTapZoom())) {
3191 if (Maybe
<LayoutDevicePoint
> geckoScreenPoint
=
3192 ConvertToGecko(aEvent
.mPoint
)) {
3193 controller
->HandleTap(
3194 TapType::eDoubleTap
, *geckoScreenPoint
, aEvent
.modifiers
, GetGuid(),
3195 GetCurrentTouchBlock() ? GetCurrentTouchBlock()->GetBlockId() : 0,
3196 Some(DoubleTapToZoomMetrics
{rootContentApzc
->GetVisualViewport(),
3197 rootContentApzc
->GetScrollableRect(),
3198 transformToRootContentApzc
}));
3201 return nsEventStatus_eConsumeNoDefault
;
3203 return nsEventStatus_eIgnore
;
3206 nsEventStatus
AsyncPanZoomController::OnSecondTap(
3207 const TapGestureInput
& aEvent
) {
3208 APZC_LOG_DETAIL("got a second-tap in state %s\n", this,
3209 ToString(mState
).c_str());
3210 return GenerateSingleTap(TapType::eSecondTap
, aEvent
.mPoint
,
3214 nsEventStatus
AsyncPanZoomController::OnCancelTap(
3215 const TapGestureInput
& aEvent
) {
3216 APZC_LOG_DETAIL("got a cancel-tap in state %s\n", this,
3217 ToString(mState
).c_str());
3218 // XXX: Implement this.
3219 return nsEventStatus_eIgnore
;
3222 ScreenToParentLayerMatrix4x4
AsyncPanZoomController::GetTransformToThis()
3224 if (APZCTreeManager
* treeManagerLocal
= GetApzcTreeManager()) {
3225 return treeManagerLocal
->GetScreenToApzcTransform(this);
3227 return ScreenToParentLayerMatrix4x4();
3230 ScreenPoint
AsyncPanZoomController::ToScreenCoordinates(
3231 const ParentLayerPoint
& aVector
, const ParentLayerPoint
& aAnchor
) const {
3232 return TransformVector(GetTransformToThis().Inverse(), aVector
, aAnchor
);
3235 // TODO: figure out a good way to check the w-coordinate is positive and return
3237 ParentLayerPoint
AsyncPanZoomController::ToParentLayerCoordinates(
3238 const ScreenPoint
& aVector
, const ScreenPoint
& aAnchor
) const {
3239 return TransformVector(GetTransformToThis(), aVector
, aAnchor
);
3242 ParentLayerPoint
AsyncPanZoomController::ToParentLayerCoordinates(
3243 const ScreenPoint
& aVector
, const ExternalPoint
& aAnchor
) const {
3244 return ToParentLayerCoordinates(
3246 ViewAs
<ScreenPixel
>(aAnchor
, PixelCastJustification::ExternalIsScreen
));
3249 ExternalPoint
AsyncPanZoomController::ToExternalPoint(
3250 const ExternalPoint
& aScreenOffset
, const ScreenPoint
& aScreenPoint
) {
3251 return aScreenOffset
+
3252 ViewAs
<ExternalPixel
>(aScreenPoint
,
3253 PixelCastJustification::ExternalIsScreen
);
3256 ScreenPoint
AsyncPanZoomController::PanVector(const ExternalPoint
& aPos
) const {
3257 return ScreenPoint(fabs(aPos
.x
- mStartTouch
.x
),
3258 fabs(aPos
.y
- mStartTouch
.y
));
3261 bool AsyncPanZoomController::Contains(const ScreenIntPoint
& aPoint
) const {
3262 ScreenToParentLayerMatrix4x4 transformToThis
= GetTransformToThis();
3263 Maybe
<ParentLayerIntPoint
> point
= UntransformBy(transformToThis
, aPoint
);
3268 ParentLayerIntRect cb
;
3270 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
3271 GetFrameMetrics().GetCompositionBounds().ToIntRect(&cb
);
3273 return cb
.Contains(*point
);
3276 bool AsyncPanZoomController::IsInOverscrollGutter(
3277 const ScreenPoint
& aHitTestPoint
) const {
3278 if (!IsPhysicallyOverscrolled()) {
3282 Maybe
<ParentLayerPoint
> apzcPoint
=
3283 UntransformBy(GetTransformToThis(), aHitTestPoint
);
3284 if (!apzcPoint
) return false;
3285 return IsInOverscrollGutter(*apzcPoint
);
3288 bool AsyncPanZoomController::IsInOverscrollGutter(
3289 const ParentLayerPoint
& aHitTestPoint
) const {
3290 ParentLayerRect compositionBounds
;
3292 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
3293 compositionBounds
= GetFrameMetrics().GetCompositionBounds();
3295 if (!compositionBounds
.Contains(aHitTestPoint
)) {
3296 // Point is outside of scrollable element's bounds altogether.
3299 auto overscrollTransform
= GetOverscrollTransform(eForEventHandling
);
3300 ParentLayerPoint overscrollUntransformed
=
3301 overscrollTransform
.Inverse().TransformPoint(aHitTestPoint
);
3303 if (compositionBounds
.Contains(overscrollUntransformed
)) {
3304 // Point is over scrollable content.
3308 // Point is in gutter.
3312 bool AsyncPanZoomController::IsOverscrolled() const {
3313 return mOverscrollEffect
->IsOverscrolled();
3316 bool AsyncPanZoomController::IsPhysicallyOverscrolled() const {
3317 // As an optimization, avoid calling Apply/UnapplyAsyncTestAttributes
3318 // unless we're in a test environment where we need it.
3319 if (StaticPrefs::apz_overscroll_test_async_scroll_offset_enabled()) {
3320 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
3321 AutoApplyAsyncTestAttributes
testAttributeApplier(this, lock
);
3322 return mX
.IsOverscrolled() || mY
.IsOverscrolled();
3324 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
3325 return mX
.IsOverscrolled() || mY
.IsOverscrolled();
3328 bool AsyncPanZoomController::IsInInvalidOverscroll() const {
3329 return mX
.IsInInvalidOverscroll() || mY
.IsInInvalidOverscroll();
3332 ParentLayerPoint
AsyncPanZoomController::PanStart() const {
3333 return ParentLayerPoint(mX
.PanStart(), mY
.PanStart());
3336 const ParentLayerPoint
AsyncPanZoomController::GetVelocityVector() const {
3337 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
3338 return ParentLayerPoint(mX
.GetVelocity(), mY
.GetVelocity());
3341 void AsyncPanZoomController::SetVelocityVector(
3342 const ParentLayerPoint
& aVelocityVector
) {
3343 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
3344 mX
.SetVelocity(aVelocityVector
.x
);
3345 mY
.SetVelocity(aVelocityVector
.y
);
3348 void AsyncPanZoomController::HandlePanningWithTouchAction(double aAngle
) {
3349 // Handling of cross sliding will need to be added in this method after
3350 // touch-action released enabled by default.
3351 MOZ_ASSERT(GetCurrentTouchBlock());
3352 RefPtr
<const OverscrollHandoffChain
> overscrollHandoffChain
=
3353 GetCurrentInputBlock()->GetOverscrollHandoffChain();
3354 bool canScrollHorizontal
=
3355 !mX
.IsAxisLocked() && overscrollHandoffChain
->CanScrollInDirection(
3356 this, ScrollDirection::eHorizontal
);
3357 bool canScrollVertical
=
3358 !mY
.IsAxisLocked() && overscrollHandoffChain
->CanScrollInDirection(
3359 this, ScrollDirection::eVertical
);
3360 if (GetCurrentTouchBlock()->TouchActionAllowsPanningXY()) {
3361 if (canScrollHorizontal
&& canScrollVertical
) {
3362 if (apz::IsCloseToHorizontal(aAngle
,
3363 StaticPrefs::apz_axis_lock_lock_angle())) {
3364 mY
.SetAxisLocked(true);
3365 SetState(PANNING_LOCKED_X
);
3366 } else if (apz::IsCloseToVertical(
3367 aAngle
, StaticPrefs::apz_axis_lock_lock_angle())) {
3368 mX
.SetAxisLocked(true);
3369 SetState(PANNING_LOCKED_Y
);
3373 } else if (canScrollHorizontal
|| canScrollVertical
) {
3378 } else if (GetCurrentTouchBlock()->TouchActionAllowsPanningX()) {
3379 // Using bigger angle for panning to keep behavior consistent
3381 if (apz::IsCloseToHorizontal(
3382 aAngle
, StaticPrefs::apz_axis_lock_direct_pan_angle())) {
3383 mY
.SetAxisLocked(true);
3384 SetState(PANNING_LOCKED_X
);
3385 mPanDirRestricted
= true;
3387 // Don't treat these touches as pan/zoom movements since 'touch-action'
3388 // value requires it.
3391 } else if (GetCurrentTouchBlock()->TouchActionAllowsPanningY()) {
3392 if (apz::IsCloseToVertical(aAngle
,
3393 StaticPrefs::apz_axis_lock_direct_pan_angle())) {
3394 mX
.SetAxisLocked(true);
3395 SetState(PANNING_LOCKED_Y
);
3396 mPanDirRestricted
= true;
3403 if (!IsInPanningState()) {
3404 // If we didn't enter a panning state because touch-action disallowed it,
3405 // make sure to clear any leftover velocity from the pre-threshold
3412 void AsyncPanZoomController::HandlePanning(double aAngle
) {
3413 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
3414 MOZ_ASSERT(GetCurrentInputBlock());
3415 RefPtr
<const OverscrollHandoffChain
> overscrollHandoffChain
=
3416 GetCurrentInputBlock()->GetOverscrollHandoffChain();
3417 bool canScrollHorizontal
=
3418 !mX
.IsAxisLocked() && overscrollHandoffChain
->CanScrollInDirection(
3419 this, ScrollDirection::eHorizontal
);
3420 bool canScrollVertical
=
3421 !mY
.IsAxisLocked() && overscrollHandoffChain
->CanScrollInDirection(
3422 this, ScrollDirection::eVertical
);
3424 MOZ_ASSERT(UsingStatefulAxisLock());
3426 if (!canScrollHorizontal
|| !canScrollVertical
) {
3428 } else if (apz::IsCloseToHorizontal(
3429 aAngle
, StaticPrefs::apz_axis_lock_lock_angle())) {
3430 mY
.SetAxisLocked(true);
3431 if (canScrollHorizontal
) {
3432 SetState(PANNING_LOCKED_X
);
3434 } else if (apz::IsCloseToVertical(aAngle
,
3435 StaticPrefs::apz_axis_lock_lock_angle())) {
3436 mX
.SetAxisLocked(true);
3437 if (canScrollVertical
) {
3438 SetState(PANNING_LOCKED_Y
);
3445 void AsyncPanZoomController::HandlePanningUpdate(
3446 const ScreenPoint
& aPanDistance
) {
3447 // If we're axis-locked, check if the user is trying to break the lock
3448 if (GetAxisLockMode() == STICKY
&& !mPanDirRestricted
) {
3449 ParentLayerPoint vector
=
3450 ToParentLayerCoordinates(aPanDistance
, mStartTouch
);
3452 float angle
= atan2f(vector
.y
, vector
.x
); // range [-pi, pi]
3453 angle
= fabsf(angle
); // range [0, pi]
3455 float breakThreshold
=
3456 StaticPrefs::apz_axis_lock_breakout_threshold() * GetDPI();
3458 if (fabs(aPanDistance
.x
) > breakThreshold
||
3459 fabs(aPanDistance
.y
) > breakThreshold
) {
3461 case PANNING_LOCKED_X
:
3462 if (!apz::IsCloseToHorizontal(
3463 angle
, StaticPrefs::apz_axis_lock_breakout_angle())) {
3464 mY
.SetAxisLocked(false);
3465 // If we are within the lock angle from the Y axis, lock
3467 if (apz::IsCloseToVertical(
3468 angle
, StaticPrefs::apz_axis_lock_lock_angle())) {
3469 mX
.SetAxisLocked(true);
3470 SetState(PANNING_LOCKED_Y
);
3477 case PANNING_LOCKED_Y
:
3478 if (!apz::IsCloseToVertical(
3479 angle
, StaticPrefs::apz_axis_lock_breakout_angle())) {
3480 mX
.SetAxisLocked(false);
3481 // If we are within the lock angle from the X axis, lock
3483 if (apz::IsCloseToHorizontal(
3484 angle
, StaticPrefs::apz_axis_lock_lock_angle())) {
3485 mY
.SetAxisLocked(true);
3486 SetState(PANNING_LOCKED_X
);
3494 HandlePanning(angle
);
3504 void AsyncPanZoomController::HandlePinchLocking(
3505 const PinchGestureInput
& aEvent
) {
3506 // Focus change and span distance calculated from an event buffer
3507 // Used to handle pinch locking irrespective of touch screen sensitivity
3508 // Note: both values fall back to the same value as
3509 // their un-buffered counterparts if there is only one (the latest)
3510 // event in the buffer. ie: when the touch screen is dispatching
3511 // events slower than the lifetime of the buffer
3512 ParentLayerCoord bufferedSpanDistance
;
3513 ParentLayerPoint focusPoint
, bufferedFocusChange
;
3515 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
3517 focusPoint
= mPinchEventBuffer
.back().mLocalFocusPoint
-
3518 Metrics().GetCompositionBounds().TopLeft();
3519 ParentLayerPoint bufferedLastZoomFocus
=
3520 (mPinchEventBuffer
.size() > 1)
3521 ? mPinchEventBuffer
.front().mLocalFocusPoint
-
3522 Metrics().GetCompositionBounds().TopLeft()
3525 bufferedFocusChange
= bufferedLastZoomFocus
- focusPoint
;
3526 bufferedSpanDistance
= fabsf(mPinchEventBuffer
.front().mPreviousSpan
-
3527 mPinchEventBuffer
.back().mCurrentSpan
);
3530 // Convert to screen coordinates
3531 ScreenCoord spanDistance
=
3532 ToScreenCoordinates(ParentLayerPoint(0, bufferedSpanDistance
), focusPoint
)
3534 ScreenPoint focusChange
=
3535 ToScreenCoordinates(bufferedFocusChange
, focusPoint
);
3538 if (GetPinchLockMode() == PINCH_STICKY
) {
3539 ScreenCoord spanBreakoutThreshold
=
3540 StaticPrefs::apz_pinch_lock_span_breakout_threshold() * GetDPI();
3541 mPinchLocked
= !(spanDistance
> spanBreakoutThreshold
);
3544 if (GetPinchLockMode() != PINCH_FREE
) {
3545 ScreenCoord spanLockThreshold
=
3546 StaticPrefs::apz_pinch_lock_span_lock_threshold() * GetDPI();
3547 ScreenCoord scrollLockThreshold
=
3548 StaticPrefs::apz_pinch_lock_scroll_lock_threshold() * GetDPI();
3550 if (spanDistance
< spanLockThreshold
&&
3551 focusChange
.Length() > scrollLockThreshold
) {
3552 mPinchLocked
= true;
3554 // We are transitioning to a two-finger pan that could trigger
3555 // a fling at its end, so start tracking velocity.
3556 StartTouch(aEvent
.mLocalFocusPoint
, aEvent
.mTimeStamp
);
3562 nsEventStatus
AsyncPanZoomController::StartPanning(
3563 const ExternalPoint
& aStartPoint
, const TimeStamp
& aEventTime
) {
3564 ParentLayerPoint vector
=
3565 ToParentLayerCoordinates(PanVector(aStartPoint
), mStartTouch
);
3566 double angle
= atan2(vector
.y
, vector
.x
); // range [-pi, pi]
3567 angle
= fabs(angle
); // range [0, pi]
3569 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
3570 HandlePanningWithTouchAction(angle
);
3572 if (IsInPanningState()) {
3573 mTouchStartRestingTimeBeforePan
= aEventTime
- mTouchStartTime
;
3574 mMinimumVelocityDuringPan
= Nothing();
3576 if (RefPtr
<GeckoContentController
> controller
=
3577 GetGeckoContentController()) {
3578 controller
->NotifyAPZStateChange(GetGuid(),
3579 APZStateChange::eStartPanning
);
3581 return nsEventStatus_eConsumeNoDefault
;
3583 // Don't consume an event that didn't trigger a panning.
3584 return nsEventStatus_eIgnore
;
3587 void AsyncPanZoomController::UpdateWithTouchAtDevicePoint(
3588 const MultiTouchInput
& aEvent
) {
3589 const SingleTouchData
& touchData
= aEvent
.mTouches
[0];
3590 // Take historical touch data into account in order to improve the accuracy
3591 // of the velocity estimate. On many Android devices, the touch screen samples
3592 // at a higher rate than vsync (e.g. 100Hz vs 60Hz), and the historical data
3593 // lets us take advantage of those high-rate samples.
3594 for (const auto& historicalData
: touchData
.mHistoricalData
) {
3595 ParentLayerPoint historicalPoint
= historicalData
.mLocalScreenPoint
;
3596 mX
.UpdateWithTouchAtDevicePoint(historicalPoint
.x
,
3597 historicalData
.mTimeStamp
);
3598 mY
.UpdateWithTouchAtDevicePoint(historicalPoint
.y
,
3599 historicalData
.mTimeStamp
);
3601 ParentLayerPoint point
= touchData
.mLocalScreenPoint
;
3602 mX
.UpdateWithTouchAtDevicePoint(point
.x
, aEvent
.mTimeStamp
);
3603 mY
.UpdateWithTouchAtDevicePoint(point
.y
, aEvent
.mTimeStamp
);
3606 Maybe
<CompositionPayload
> AsyncPanZoomController::NotifyScrollSampling() {
3607 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
3608 return mSampledState
.front().TakeScrollPayload();
3611 bool AsyncPanZoomController::AttemptScroll(
3612 ParentLayerPoint
& aStartPoint
, ParentLayerPoint
& aEndPoint
,
3613 OverscrollHandoffState
& aOverscrollHandoffState
) {
3614 // "start - end" rather than "end - start" because e.g. moving your finger
3615 // down (*positive* direction along y axis) causes the vertical scroll offset
3616 // to *decrease* as the page follows your finger.
3617 ParentLayerPoint displacement
= aStartPoint
- aEndPoint
;
3619 ParentLayerPoint overscroll
; // will be used outside monitor block
3621 // If the direction of panning is reversed within the same input block,
3622 // a later event in the block could potentially scroll an APZC earlier
3623 // in the handoff chain, than an earlier event in the block (because
3624 // the earlier APZC was scrolled to its extent in the original direction).
3625 // We want to disallow this.
3626 bool scrollThisApzc
= false;
3627 if (InputBlockState
* block
= GetCurrentInputBlock()) {
3629 !block
->GetScrolledApzc() || block
->IsDownchainOfScrolledApzc(this);
3632 ParentLayerPoint adjustedDisplacement
;
3633 if (scrollThisApzc
) {
3634 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
3635 bool respectDisregardedDirections
=
3636 ScrollSourceRespectsDisregardedDirections(
3637 aOverscrollHandoffState
.mScrollSource
);
3638 bool forcesVerticalOverscroll
= respectDisregardedDirections
&&
3639 mScrollMetadata
.GetDisregardedDirection() ==
3640 Some(ScrollDirection::eVertical
);
3641 bool forcesHorizontalOverscroll
=
3642 respectDisregardedDirections
&&
3643 mScrollMetadata
.GetDisregardedDirection() ==
3644 Some(ScrollDirection::eHorizontal
);
3647 mY
.AdjustDisplacement(displacement
.y
, adjustedDisplacement
.y
,
3648 overscroll
.y
, forcesVerticalOverscroll
);
3650 mX
.AdjustDisplacement(displacement
.x
, adjustedDisplacement
.x
,
3651 overscroll
.x
, forcesHorizontalOverscroll
);
3652 if (xChanged
|| yChanged
) {
3653 ScheduleComposite();
3656 if (!IsZero(adjustedDisplacement
) &&
3657 Metrics().GetZoom() != CSSToParentLayerScale(0)) {
3658 ScrollBy(adjustedDisplacement
/ Metrics().GetZoom());
3659 if (InputBlockState
* block
= GetCurrentInputBlock()) {
3660 bool displacementIsUserVisible
= true;
3662 { // Release the APZC lock before calling ToScreenCoordinates which
3663 // acquires the APZ tree lock. Note that this just unlocks the mutex
3664 // once, so if we're locking it multiple times on the callstack then
3665 // this will be insufficient.
3666 RecursiveMutexAutoUnlock
unlock(mRecursiveMutex
);
3668 ScreenIntPoint screenDisplacement
= RoundedToInt(
3669 ToScreenCoordinates(adjustedDisplacement
, aStartPoint
));
3670 // If the displacement we just applied rounds to zero in screen space,
3671 // then it's probably not going to be visible to the user. In that
3672 // case let's not mark this APZC as scrolled, so that even if the
3673 // immediate handoff pref is disabled, we'll allow doing the handoff
3674 // to the next APZC.
3675 if (screenDisplacement
== ScreenIntPoint()) {
3676 displacementIsUserVisible
= false;
3679 if (displacementIsUserVisible
) {
3680 block
->SetScrolledApzc(this);
3683 // Note that in the case of instant scrolling, the last snap target ids
3684 // will be set after AttemptScroll call so that we can clobber them
3685 // unconditionally here.
3686 mLastSnapTargetIds
= ScrollSnapTargetIds
{};
3687 ScheduleCompositeAndMaybeRepaint();
3690 // Adjust the start point to reflect the consumed portion of the scroll.
3691 aStartPoint
= aEndPoint
+ overscroll
;
3693 overscroll
= displacement
;
3696 // Accumulate the amount of actual scrolling that occurred into the handoff
3697 // state. Note that ToScreenCoordinates() needs to be called outside the
3699 if (!IsZero(adjustedDisplacement
)) {
3700 aOverscrollHandoffState
.mTotalMovement
+=
3701 ToScreenCoordinates(adjustedDisplacement
, aEndPoint
);
3704 // If we consumed the entire displacement as a normal scroll, great.
3705 if (IsZero(overscroll
)) {
3709 if (AllowScrollHandoffInCurrentBlock()) {
3710 // If there is overscroll, first try to hand it off to an APZC later
3711 // in the handoff chain to consume (either as a normal scroll or as
3713 // Note: "+ overscroll" rather than "- overscroll" because "overscroll"
3714 // is what's left of "displacement", and "displacement" is "start - end".
3715 ++aOverscrollHandoffState
.mChainIndex
;
3717 CallDispatchScroll(aStartPoint
, aEndPoint
, aOverscrollHandoffState
);
3722 overscroll
= aStartPoint
- aEndPoint
;
3723 MOZ_ASSERT(!IsZero(overscroll
));
3726 // If there is no APZC later in the handoff chain that accepted the
3727 // overscroll, try to accept it ourselves. We only accept it if we
3729 if (ScrollSourceAllowsOverscroll(aOverscrollHandoffState
.mScrollSource
)) {
3730 APZC_LOG("%p taking overscroll during panning\n", this);
3732 ParentLayerPoint prevVisualOverscroll
= GetOverscrollAmount();
3734 OverscrollForPanning(overscroll
, aOverscrollHandoffState
.mPanDistance
);
3736 // Accumulate the amount of change to the overscroll that occurred into the
3737 // handoff state. Note that the input amount, |overscroll|, is turned into
3738 // some smaller visual overscroll amount (queried via GetOverscrollAmount())
3739 // by applying resistance (Axis::ApplyResistance()), and it's the latter we
3740 // want to count towards OverscrollHandoffState::mTotalMovement.
3741 ParentLayerPoint visualOverscrollChange
=
3742 GetOverscrollAmount() - prevVisualOverscroll
;
3743 if (!IsZero(visualOverscrollChange
)) {
3744 aOverscrollHandoffState
.mTotalMovement
+=
3745 ToScreenCoordinates(visualOverscrollChange
, aEndPoint
);
3749 aStartPoint
= aEndPoint
+ overscroll
;
3751 return IsZero(overscroll
);
3754 void AsyncPanZoomController::OverscrollForPanning(
3755 ParentLayerPoint
& aOverscroll
, const ScreenPoint
& aPanDistance
) {
3756 // Only allow entering overscroll along an axis if the pan distance along
3757 // that axis is greater than the pan distance along the other axis by a
3758 // configurable factor. If we are already overscrolled, don't check this.
3759 if (!IsOverscrolled()) {
3760 if (aPanDistance
.x
<
3761 StaticPrefs::apz_overscroll_min_pan_distance_ratio() * aPanDistance
.y
) {
3764 if (aPanDistance
.y
<
3765 StaticPrefs::apz_overscroll_min_pan_distance_ratio() * aPanDistance
.x
) {
3770 OverscrollBy(aOverscroll
);
3773 ScrollDirections
AsyncPanZoomController::GetOverscrollableDirections() const {
3774 ScrollDirections result
;
3776 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
3778 // If the target has the disregarded direction, it means it's single line
3779 // text control, thus we don't want to overscroll in both directions.
3780 if (mScrollMetadata
.GetDisregardedDirection()) {
3784 if (mX
.CanScroll() && mX
.OverscrollBehaviorAllowsOverscrollEffect()) {
3785 result
+= ScrollDirection::eHorizontal
;
3788 if (mY
.CanScroll() && mY
.OverscrollBehaviorAllowsOverscrollEffect()) {
3789 result
+= ScrollDirection::eVertical
;
3795 void AsyncPanZoomController::OverscrollBy(ParentLayerPoint
& aOverscroll
) {
3796 if (!StaticPrefs::apz_overscroll_enabled()) {
3800 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
3801 // Do not go into overscroll in a direction in which we have no room to
3802 // scroll to begin with.
3803 ScrollDirections overscrollableDirections
= GetOverscrollableDirections();
3804 if (IsZero(aOverscroll
.x
)) {
3805 overscrollableDirections
-= ScrollDirection::eHorizontal
;
3807 if (IsZero(aOverscroll
.y
)) {
3808 overscrollableDirections
-= ScrollDirection::eVertical
;
3811 mOverscrollEffect
->ConsumeOverscroll(aOverscroll
, overscrollableDirections
);
3814 RefPtr
<const OverscrollHandoffChain
>
3815 AsyncPanZoomController::BuildOverscrollHandoffChain() {
3816 if (APZCTreeManager
* treeManagerLocal
= GetApzcTreeManager()) {
3817 return treeManagerLocal
->BuildOverscrollHandoffChain(this);
3820 // This APZC IsDestroyed(). To avoid callers having to special-case this
3821 // scenario, just build a 1-element chain containing ourselves.
3822 OverscrollHandoffChain
* result
= new OverscrollHandoffChain
;
3827 ParentLayerPoint
AsyncPanZoomController::AttemptFling(
3828 const FlingHandoffState
& aHandoffState
) {
3829 // The PLPPI computation acquires the tree lock, so it needs to be performed
3830 // on the controller thread, and before the APZC lock is acquired.
3831 APZThreadUtils::AssertOnControllerThread();
3832 float PLPPI
= ComputePLPPI(PanStart(), aHandoffState
.mVelocity
);
3834 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
3836 if (!IsPannable()) {
3837 return aHandoffState
.mVelocity
;
3840 // We may have a pre-existing velocity for whatever reason (for example,
3841 // a previously handed off fling). We don't want to clobber that.
3842 APZC_LOG("%p accepting fling with velocity %s\n", this,
3843 ToString(aHandoffState
.mVelocity
).c_str());
3844 ParentLayerPoint residualVelocity
= aHandoffState
.mVelocity
;
3845 if (mX
.CanScroll()) {
3846 mX
.SetVelocity(mX
.GetVelocity() + aHandoffState
.mVelocity
.x
);
3847 residualVelocity
.x
= 0;
3849 if (mY
.CanScroll()) {
3850 mY
.SetVelocity(mY
.GetVelocity() + aHandoffState
.mVelocity
.y
);
3851 residualVelocity
.y
= 0;
3854 // If we're not scrollable in at least one of the directions in which we
3855 // were handed velocity, don't start a fling animation.
3856 // The |IsFinite()| condition should only fail when running some tests
3857 // that generate events faster than the clock resolution.
3858 ParentLayerPoint velocity
= GetVelocityVector();
3859 if (!velocity
.IsFinite() ||
3860 velocity
.Length() <= StaticPrefs::apz_fling_min_velocity_threshold()) {
3861 // Relieve overscroll now if needed, since we will not transition to a fling
3862 // animation and then an overscroll animation, and relieve it then.
3863 aHandoffState
.mChain
->SnapBackOverscrolledApzc(this);
3864 return residualVelocity
;
3867 // If there's a scroll snap point near the predicted fling destination,
3868 // scroll there using a smooth scroll animation. Otherwise, start a
3870 ScrollSnapToDestination();
3871 if (mState
!= SMOOTHMSD_SCROLL
) {
3873 RefPtr
<AsyncPanZoomAnimation
> fling
=
3874 GetPlatformSpecificState()->CreateFlingAnimation(*this, aHandoffState
,
3876 StartAnimation(fling
.forget());
3879 return residualVelocity
;
3882 float AsyncPanZoomController::ComputePLPPI(ParentLayerPoint aPoint
,
3883 ParentLayerPoint aDirection
) const {
3884 // Avoid division-by-zero.
3885 if (aDirection
== ParentLayerPoint()) {
3889 // Convert |aDirection| into a unit vector.
3890 aDirection
= aDirection
/ aDirection
.Length();
3892 // Place the vector at |aPoint| and convert to screen coordinates.
3893 // The length of the resulting vector is the number of Screen coordinates
3894 // that equal 1 ParentLayer coordinate in the given direction.
3895 float screenPerParent
= ToScreenCoordinates(aDirection
, aPoint
).Length();
3897 // Finally, factor in the DPI scale.
3898 return GetDPI() / screenPerParent
;
3901 Maybe
<CSSPoint
> AsyncPanZoomController::GetCurrentAnimationDestination(
3902 const RecursiveMutexAutoLock
& aProofOfLock
) const {
3903 if (mState
== WHEEL_SCROLL
) {
3904 return Some(mAnimation
->AsWheelScrollAnimation()->GetDestination());
3906 if (mState
== SMOOTH_SCROLL
) {
3907 return Some(mAnimation
->AsSmoothScrollAnimation()->GetDestination());
3909 if (mState
== SMOOTHMSD_SCROLL
) {
3910 return Some(mAnimation
->AsSmoothMsdScrollAnimation()->GetDestination());
3912 if (mState
== KEYBOARD_SCROLL
) {
3913 return Some(mAnimation
->AsSmoothScrollAnimation()->GetDestination());
3920 AsyncPanZoomController::AdjustHandoffVelocityForOverscrollBehavior(
3921 ParentLayerPoint
& aHandoffVelocity
) const {
3922 ParentLayerPoint residualVelocity
;
3923 ScrollDirections handoffDirections
= GetAllowedHandoffDirections();
3924 if (!handoffDirections
.contains(ScrollDirection::eHorizontal
)) {
3925 residualVelocity
.x
= aHandoffVelocity
.x
;
3926 aHandoffVelocity
.x
= 0;
3928 if (!handoffDirections
.contains(ScrollDirection::eVertical
)) {
3929 residualVelocity
.y
= aHandoffVelocity
.y
;
3930 aHandoffVelocity
.y
= 0;
3932 return residualVelocity
;
3935 bool AsyncPanZoomController::OverscrollBehaviorAllowsSwipe() const {
3936 // Swipe navigation is a "non-local" overscroll behavior like handoff.
3937 return GetAllowedHandoffDirections().contains(ScrollDirection::eHorizontal
);
3940 void AsyncPanZoomController::HandleFlingOverscroll(
3941 const ParentLayerPoint
& aVelocity
, SideBits aOverscrollSideBits
,
3942 const RefPtr
<const OverscrollHandoffChain
>& aOverscrollHandoffChain
,
3943 const RefPtr
<const AsyncPanZoomController
>& aScrolledApzc
) {
3944 APZCTreeManager
* treeManagerLocal
= GetApzcTreeManager();
3945 if (treeManagerLocal
) {
3946 const FlingHandoffState handoffState
{
3947 aVelocity
, aOverscrollHandoffChain
, Nothing(),
3948 0, true /* handoff */, aScrolledApzc
};
3949 ParentLayerPoint residualVelocity
=
3950 treeManagerLocal
->DispatchFling(this, handoffState
);
3951 FLING_LOG("APZC %p left with residual velocity %s\n", this,
3952 ToString(residualVelocity
).c_str());
3953 if (!IsZero(residualVelocity
) && IsPannable() &&
3954 StaticPrefs::apz_overscroll_enabled()) {
3955 // Obey overscroll-behavior.
3956 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
3957 if (!mX
.OverscrollBehaviorAllowsOverscrollEffect()) {
3958 residualVelocity
.x
= 0;
3960 if (!mY
.OverscrollBehaviorAllowsOverscrollEffect()) {
3961 residualVelocity
.y
= 0;
3964 if (!IsZero(residualVelocity
)) {
3965 mOverscrollEffect
->RelieveOverscroll(residualVelocity
,
3966 aOverscrollSideBits
);
3972 void AsyncPanZoomController::HandleSmoothScrollOverscroll(
3973 const ParentLayerPoint
& aVelocity
, SideBits aOverscrollSideBits
) {
3974 // We must call BuildOverscrollHandoffChain from this deferred callback
3975 // function in order to avoid a deadlock when acquiring the tree lock.
3976 HandleFlingOverscroll(aVelocity
, aOverscrollSideBits
,
3977 BuildOverscrollHandoffChain(), nullptr);
3980 ParentLayerPoint
AsyncPanZoomController::ConvertDestinationToDelta(
3981 CSSPoint
& aDestination
) const {
3982 ParentLayerPoint startPoint
, endPoint
;
3985 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
3987 startPoint
= aDestination
* Metrics().GetZoom();
3988 endPoint
= Metrics().GetVisualScrollOffset() * Metrics().GetZoom();
3991 return startPoint
- endPoint
;
3994 void AsyncPanZoomController::SmoothScrollTo(
3995 CSSSnapDestination
&& aDestination
,
3996 ScrollTriggeredByScript aTriggeredByScript
, const ScrollOrigin
& aOrigin
) {
3997 // Convert velocity from ParentLayerPoints/ms to ParentLayerPoints/s and then
3998 // to appunits/second.
3999 nsPoint destination
= CSSPoint::ToAppUnits(aDestination
.mPosition
);
4001 if (Metrics().GetZoom() != CSSToParentLayerScale(0)) {
4002 velocity
= CSSSize::ToAppUnits(ParentLayerSize(mX
.GetVelocity() * 1000.0f
,
4003 mY
.GetVelocity() * 1000.0f
) /
4004 Metrics().GetZoom());
4007 if (mState
== SMOOTH_SCROLL
&& mAnimation
) {
4008 RefPtr
<SmoothScrollAnimation
> animation(
4009 mAnimation
->AsSmoothScrollAnimation());
4010 if (animation
->GetScrollOrigin() == aOrigin
) {
4011 APZC_LOG("%p updating destination on existing animation\n", this);
4012 animation
->UpdateDestinationAndSnapTargets(
4013 GetFrameTime().Time(), destination
, velocity
,
4014 std::move(aDestination
.mTargetIds
), aTriggeredByScript
);
4021 // If no scroll is required, we should exit early to avoid triggering
4022 // a scrollend event when no scrolling occurred.
4023 if (ConvertDestinationToDelta(aDestination
.mPosition
) == ParentLayerPoint()) {
4027 SetState(SMOOTH_SCROLL
);
4028 nsPoint initialPosition
=
4029 CSSPoint::ToAppUnits(Metrics().GetVisualScrollOffset());
4030 RefPtr
<SmoothScrollAnimation
> animation
=
4031 new SmoothScrollAnimation(*this, initialPosition
, aOrigin
);
4032 animation
->UpdateDestinationAndSnapTargets(
4033 GetFrameTime().Time(), destination
, velocity
,
4034 std::move(aDestination
.mTargetIds
), aTriggeredByScript
);
4035 StartAnimation(animation
.forget());
4038 void AsyncPanZoomController::SmoothMsdScrollTo(
4039 CSSSnapDestination
&& aDestination
,
4040 ScrollTriggeredByScript aTriggeredByScript
) {
4041 if (mState
== SMOOTHMSD_SCROLL
&& mAnimation
) {
4042 APZC_LOG("%p updating destination on existing animation\n", this);
4043 RefPtr
<SmoothMsdScrollAnimation
> animation(
4044 static_cast<SmoothMsdScrollAnimation
*>(mAnimation
.get()));
4045 animation
->SetDestination(aDestination
.mPosition
,
4046 std::move(aDestination
.mTargetIds
),
4047 aTriggeredByScript
);
4051 // If no scroll is required, we should exit early to avoid triggering
4052 // a scrollend event when no scrolling occurred.
4053 if (ConvertDestinationToDelta(aDestination
.mPosition
) == ParentLayerPoint()) {
4057 SetState(SMOOTHMSD_SCROLL
);
4058 // Convert velocity from ParentLayerPoints/ms to ParentLayerPoints/s.
4059 CSSPoint initialVelocity
;
4060 if (Metrics().GetZoom() != CSSToParentLayerScale(0)) {
4061 initialVelocity
= ParentLayerPoint(mX
.GetVelocity() * 1000.0f
,
4062 mY
.GetVelocity() * 1000.0f
) /
4063 Metrics().GetZoom();
4066 StartAnimation(do_AddRef(new SmoothMsdScrollAnimation(
4067 *this, Metrics().GetVisualScrollOffset(), initialVelocity
,
4068 aDestination
.mPosition
,
4069 StaticPrefs::layout_css_scroll_behavior_spring_constant(),
4070 StaticPrefs::layout_css_scroll_behavior_damping_ratio(),
4071 std::move(aDestination
.mTargetIds
), aTriggeredByScript
)));
4074 void AsyncPanZoomController::StartOverscrollAnimation(
4075 const ParentLayerPoint
& aVelocity
, SideBits aOverscrollSideBits
) {
4076 MOZ_ASSERT(mState
!= OVERSCROLL_ANIMATION
);
4078 SetState(OVERSCROLL_ANIMATION
);
4080 ParentLayerPoint velocity
= aVelocity
;
4081 AdjustDeltaForAllowedScrollDirections(velocity
,
4082 GetOverscrollableDirections());
4084 do_AddRef(new OverscrollAnimation(*this, velocity
, aOverscrollSideBits
)));
4087 bool AsyncPanZoomController::CallDispatchScroll(
4088 ParentLayerPoint
& aStartPoint
, ParentLayerPoint
& aEndPoint
,
4089 OverscrollHandoffState
& aOverscrollHandoffState
) {
4090 // Make a local copy of the tree manager pointer and check if it's not
4091 // null before calling DispatchScroll(). This is necessary because
4092 // Destroy(), which nulls out mTreeManager, could be called concurrently.
4093 APZCTreeManager
* treeManagerLocal
= GetApzcTreeManager();
4094 if (!treeManagerLocal
) {
4098 // Obey overscroll-behavior.
4099 ParentLayerPoint endPoint
= aEndPoint
;
4100 if (aOverscrollHandoffState
.mChainIndex
> 0) {
4101 ScrollDirections handoffDirections
= GetAllowedHandoffDirections();
4102 if (!handoffDirections
.contains(ScrollDirection::eHorizontal
)) {
4103 endPoint
.x
= aStartPoint
.x
;
4105 if (!handoffDirections
.contains(ScrollDirection::eVertical
)) {
4106 endPoint
.y
= aStartPoint
.y
;
4108 if (aStartPoint
== endPoint
) {
4109 // Handoff not allowed in either direction - don't even bother.
4114 return treeManagerLocal
->DispatchScroll(this, aStartPoint
, endPoint
,
4115 aOverscrollHandoffState
);
4118 void AsyncPanZoomController::RecordScrollPayload(const TimeStamp
& aTimeStamp
) {
4119 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
4120 if (!mScrollPayload
) {
4121 mScrollPayload
= Some(
4122 CompositionPayload
{CompositionPayloadType::eAPZScroll
, aTimeStamp
});
4126 void AsyncPanZoomController::StartTouch(const ParentLayerPoint
& aPoint
,
4127 TimeStamp aTimestamp
) {
4128 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
4129 mX
.StartTouch(aPoint
.x
, aTimestamp
);
4130 mY
.StartTouch(aPoint
.y
, aTimestamp
);
4133 void AsyncPanZoomController::EndTouch(TimeStamp aTimestamp
,
4134 Axis::ClearAxisLock aClearAxisLock
) {
4135 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
4136 mX
.EndTouch(aTimestamp
, aClearAxisLock
);
4137 mY
.EndTouch(aTimestamp
, aClearAxisLock
);
4140 void AsyncPanZoomController::TrackTouch(const MultiTouchInput
& aEvent
) {
4141 ExternalPoint extPoint
= GetFirstExternalTouchPoint(aEvent
);
4142 ScreenPoint panVector
= PanVector(extPoint
);
4143 HandlePanningUpdate(panVector
);
4145 ParentLayerPoint
prevTouchPoint(mX
.GetPos(), mY
.GetPos());
4146 ParentLayerPoint touchPoint
= GetFirstTouchPoint(aEvent
);
4148 UpdateWithTouchAtDevicePoint(aEvent
);
4150 auto velocity
= GetVelocityVector().Length();
4151 if (mMinimumVelocityDuringPan
) {
4152 mMinimumVelocityDuringPan
=
4153 Some(std::min(*mMinimumVelocityDuringPan
, velocity
));
4155 mMinimumVelocityDuringPan
= Some(velocity
);
4158 if (prevTouchPoint
!= touchPoint
) {
4159 MOZ_ASSERT(GetCurrentTouchBlock());
4160 OverscrollHandoffState
handoffState(
4161 *GetCurrentTouchBlock()->GetOverscrollHandoffChain(), panVector
,
4162 ScrollSource::Touchscreen
);
4163 RecordScrollPayload(aEvent
.mTimeStamp
);
4164 CallDispatchScroll(prevTouchPoint
, touchPoint
, handoffState
);
4168 ParentLayerPoint
AsyncPanZoomController::GetFirstTouchPoint(
4169 const MultiTouchInput
& aEvent
) {
4170 return ((SingleTouchData
&)aEvent
.mTouches
[0]).mLocalScreenPoint
;
4173 ExternalPoint
AsyncPanZoomController::GetFirstExternalTouchPoint(
4174 const MultiTouchInput
& aEvent
) {
4175 return ToExternalPoint(aEvent
.mScreenOffset
,
4176 ((SingleTouchData
&)aEvent
.mTouches
[0]).mScreenPoint
);
4179 ParentLayerPoint
AsyncPanZoomController::GetOverscrollAmount() const {
4180 if (StaticPrefs::apz_overscroll_test_async_scroll_offset_enabled()) {
4181 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
4182 AutoApplyAsyncTestAttributes
testAttributeApplier(this, lock
);
4183 return GetOverscrollAmountInternal();
4185 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
4186 return GetOverscrollAmountInternal();
4189 ParentLayerPoint
AsyncPanZoomController::GetOverscrollAmountInternal() const {
4190 return {mX
.GetOverscroll(), mY
.GetOverscroll()};
4193 SideBits
AsyncPanZoomController::GetOverscrollSideBits() const {
4194 return apz::GetOverscrollSideBits({mX
.GetOverscroll(), mY
.GetOverscroll()});
4197 void AsyncPanZoomController::RestoreOverscrollAmount(
4198 const ParentLayerPoint
& aOverscroll
) {
4199 mX
.RestoreOverscroll(aOverscroll
.x
);
4200 mY
.RestoreOverscroll(aOverscroll
.y
);
4203 void AsyncPanZoomController::StartAnimation(
4204 already_AddRefed
<AsyncPanZoomAnimation
> aAnimation
) {
4205 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
4206 mAnimation
= aAnimation
;
4207 mLastSampleTime
= GetFrameTime();
4208 ScheduleComposite();
4211 void AsyncPanZoomController::CancelAnimation(CancelAnimationFlags aFlags
) {
4212 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
4213 APZC_LOG_DETAIL("running CancelAnimation(0x%x) in state %s\n", this, aFlags
,
4214 ToString(mState
).c_str());
4216 if ((aFlags
& ExcludeWheel
) && mState
== WHEEL_SCROLL
) {
4221 mAnimation
->Cancel(aFlags
);
4225 mLastSnapTargetIds
= ScrollSnapTargetIds
{};
4226 mAnimation
= nullptr;
4227 // Since there is no animation in progress now the axes should
4228 // have no velocity either. If we are dropping the velocity from a non-zero
4229 // value we should trigger a repaint as the displayport margins are dependent
4230 // on the velocity and the last repaint request might not have good margins
4232 bool repaint
= !IsZero(GetVelocityVector());
4235 mX
.SetAxisLocked(false);
4236 mY
.SetAxisLocked(false);
4237 // Setting the state to nothing and cancelling the animation can
4238 // preempt normal mechanisms for relieving overscroll, so we need to clear
4240 if (!(aFlags
& ExcludeOverscroll
) && IsOverscrolled()) {
4244 // Similar to relieving overscroll, we also need to snap to any snap points
4246 if (aFlags
& CancelAnimationFlags::ScrollSnap
) {
4247 ScrollSnap(ScrollSnapFlags::IntendedEndPosition
);
4250 RequestContentRepaint();
4251 ScheduleComposite();
4255 void AsyncPanZoomController::ClearOverscroll() {
4256 mOverscrollEffect
->ClearOverscroll();
4259 void AsyncPanZoomController::ClearPhysicalOverscroll() {
4260 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
4261 mX
.ClearOverscroll();
4262 mY
.ClearOverscroll();
4265 void AsyncPanZoomController::SetCompositorController(
4266 CompositorController
* aCompositorController
) {
4267 mCompositorController
= aCompositorController
;
4270 void AsyncPanZoomController::SetVisualScrollOffset(const CSSPoint
& aOffset
) {
4271 Metrics().SetVisualScrollOffset(aOffset
);
4272 Metrics().RecalculateLayoutViewportOffset();
4275 void AsyncPanZoomController::ClampAndSetVisualScrollOffset(
4276 const CSSPoint
& aOffset
) {
4277 Metrics().ClampAndSetVisualScrollOffset(aOffset
);
4278 Metrics().RecalculateLayoutViewportOffset();
4281 void AsyncPanZoomController::ScrollBy(const CSSPoint
& aOffset
) {
4282 SetVisualScrollOffset(Metrics().GetVisualScrollOffset() + aOffset
);
4285 void AsyncPanZoomController::ScrollByAndClamp(const CSSPoint
& aOffset
) {
4286 ClampAndSetVisualScrollOffset(Metrics().GetVisualScrollOffset() + aOffset
);
4289 void AsyncPanZoomController::ScaleWithFocus(float aScale
,
4290 const CSSPoint
& aFocus
) {
4291 Metrics().ZoomBy(aScale
);
4292 // We want to adjust the scroll offset such that the CSS point represented by
4293 // aFocus remains at the same position on the screen before and after the
4294 // change in zoom. The below code accomplishes this; see
4295 // https://bugzilla.mozilla.org/show_bug.cgi?id=923431#c6 for an in-depth
4296 // explanation of how.
4297 SetVisualScrollOffset((Metrics().GetVisualScrollOffset() + aFocus
) -
4302 gfx::IntSize
AsyncPanZoomController::GetDisplayportAlignmentMultiplier(
4303 const ScreenSize
& aBaseSize
) {
4304 gfx::IntSize
multiplier(1, 1);
4305 float baseWidth
= aBaseSize
.width
;
4306 while (baseWidth
> 500) {
4308 multiplier
.width
*= 2;
4309 if (multiplier
.width
>= 8) {
4313 float baseHeight
= aBaseSize
.height
;
4314 while (baseHeight
> 500) {
4316 multiplier
.height
*= 2;
4317 if (multiplier
.height
>= 8) {
4325 * Enlarges the displayport along both axes based on the velocity.
4327 static CSSSize
CalculateDisplayPortSize(
4328 const CSSSize
& aCompositionSize
, const CSSPoint
& aVelocity
,
4329 AsyncPanZoomController::ZoomInProgress aZoomInProgress
,
4330 const CSSToScreenScale2D
& aDpPerCSS
) {
4331 bool xIsStationarySpeed
=
4332 fabsf(aVelocity
.x
) < StaticPrefs::apz_min_skate_speed();
4333 bool yIsStationarySpeed
=
4334 fabsf(aVelocity
.y
) < StaticPrefs::apz_min_skate_speed();
4335 float xMultiplier
= xIsStationarySpeed
4336 ? StaticPrefs::apz_x_stationary_size_multiplier()
4337 : StaticPrefs::apz_x_skate_size_multiplier();
4338 float yMultiplier
= yIsStationarySpeed
4339 ? StaticPrefs::apz_y_stationary_size_multiplier()
4340 : StaticPrefs::apz_y_skate_size_multiplier();
4342 if (IsHighMemSystem() && !xIsStationarySpeed
) {
4343 xMultiplier
+= StaticPrefs::apz_x_skate_highmem_adjust();
4346 if (IsHighMemSystem() && !yIsStationarySpeed
) {
4347 yMultiplier
+= StaticPrefs::apz_y_skate_highmem_adjust();
4350 if (aZoomInProgress
== AsyncPanZoomController::ZoomInProgress::Yes
) {
4351 // If a zoom is in progress, we will be making content visible on the
4352 // x and y axes in equal proportion, because the zoom operation scales
4353 // equally on the x and y axes. The default multipliers computed above are
4354 // biased towards the y-axis since that's where most scrolling occurs, but
4355 // in the case of zooming, we should really use equal multipliers on both
4356 // axes. This does that while preserving the total displayport area
4357 // quantity (aCompositionSize.Area() * xMultiplier * yMultiplier).
4358 // Note that normally changing the shape of the displayport is expensive
4359 // and should be avoided, but if a zoom is in progress the displayport
4360 // is likely going to be fully repainted anyway due to changes in resolution
4361 // so there should be no marginal cost to also changing the shape of it.
4362 float areaMultiplier
= xMultiplier
* yMultiplier
;
4363 xMultiplier
= sqrt(areaMultiplier
);
4364 yMultiplier
= xMultiplier
;
4367 // Scale down the margin multipliers by the alignment multiplier because
4368 // the alignment code will expand the displayport outward to the multiplied
4369 // alignment. This is not necessary for correctness, but for performance;
4370 // if we don't do this the displayport can end up much larger. The math here
4371 // is actually just scaling the part of the multipler that is > 1, so that
4372 // we never end up with xMultiplier or yMultiplier being less than 1 (that
4373 // would result in a guaranteed checkerboarding situation). Note that the
4374 // calculation doesn't cancel exactly the increased margin from applying
4375 // the alignment multiplier, but this is simple and should provide
4376 // reasonable behaviour in most cases.
4377 gfx::IntSize alignmentMultipler
=
4378 AsyncPanZoomController::GetDisplayportAlignmentMultiplier(
4379 aCompositionSize
* aDpPerCSS
);
4380 if (xMultiplier
> 1) {
4381 xMultiplier
= ((xMultiplier
- 1) / alignmentMultipler
.width
) + 1;
4383 if (yMultiplier
> 1) {
4384 yMultiplier
= ((yMultiplier
- 1) / alignmentMultipler
.height
) + 1;
4387 return aCompositionSize
* CSSSize(xMultiplier
, yMultiplier
);
4391 * Ensures that the displayport is at least as large as the visible area
4392 * inflated by the danger zone. If this is not the case then the
4393 * "AboutToCheckerboard" function in TiledContentClient.cpp will return true
4394 * even in the stable state.
4396 static CSSSize
ExpandDisplayPortToDangerZone(
4397 const CSSSize
& aDisplayPortSize
, const FrameMetrics
& aFrameMetrics
) {
4398 CSSSize
dangerZone(0.0f
, 0.0f
);
4399 if (aFrameMetrics
.DisplayportPixelsPerCSSPixel().xScale
!= 0 &&
4400 aFrameMetrics
.DisplayportPixelsPerCSSPixel().yScale
!= 0) {
4401 dangerZone
= ScreenSize(StaticPrefs::apz_danger_zone_x(),
4402 StaticPrefs::apz_danger_zone_y()) /
4403 aFrameMetrics
.DisplayportPixelsPerCSSPixel();
4405 const CSSSize compositionSize
=
4406 aFrameMetrics
.CalculateBoundedCompositedSizeInCssPixels();
4408 const float xSize
= std::max(aDisplayPortSize
.width
,
4409 compositionSize
.width
+ (2 * dangerZone
.width
));
4412 std::max(aDisplayPortSize
.height
,
4413 compositionSize
.height
+ (2 * dangerZone
.height
));
4415 return CSSSize(xSize
, ySize
);
4419 * Attempts to redistribute any area in the displayport that would get clipped
4420 * by the scrollable rect, or be inaccessible due to disabled scrolling, to the
4421 * other axis, while maintaining total displayport area.
4423 static void RedistributeDisplayPortExcess(CSSSize
& aDisplayPortSize
,
4424 const CSSRect
& aScrollableRect
) {
4425 // As aDisplayPortSize.height * aDisplayPortSize.width does not change,
4426 // we are just scaling by the ratio and its inverse.
4427 if (aDisplayPortSize
.height
> aScrollableRect
.Height()) {
4428 aDisplayPortSize
.width
*=
4429 (aDisplayPortSize
.height
/ aScrollableRect
.Height());
4430 aDisplayPortSize
.height
= aScrollableRect
.Height();
4431 } else if (aDisplayPortSize
.width
> aScrollableRect
.Width()) {
4432 aDisplayPortSize
.height
*=
4433 (aDisplayPortSize
.width
/ aScrollableRect
.Width());
4434 aDisplayPortSize
.width
= aScrollableRect
.Width();
4439 const ScreenMargin
AsyncPanZoomController::CalculatePendingDisplayPort(
4440 const FrameMetrics
& aFrameMetrics
, const ParentLayerPoint
& aVelocity
,
4441 ZoomInProgress aZoomInProgress
) {
4442 if (aFrameMetrics
.IsScrollInfoLayer()) {
4443 // Don't compute margins. Since we can't asynchronously scroll this frame,
4444 // we don't want to paint anything more than the composition bounds.
4445 return ScreenMargin();
4448 CSSSize compositionSize
=
4449 aFrameMetrics
.CalculateBoundedCompositedSizeInCssPixels();
4451 if (aFrameMetrics
.GetZoom() != CSSToParentLayerScale(0)) {
4452 velocity
= aVelocity
/ aFrameMetrics
.GetZoom(); // avoid division by zero
4454 CSSRect scrollableRect
= aFrameMetrics
.GetExpandedScrollableRect();
4456 // Calculate the displayport size based on how fast we're moving along each
4458 CSSSize displayPortSize
=
4459 CalculateDisplayPortSize(compositionSize
, velocity
, aZoomInProgress
,
4460 aFrameMetrics
.DisplayportPixelsPerCSSPixel());
4463 ExpandDisplayPortToDangerZone(displayPortSize
, aFrameMetrics
);
4465 if (StaticPrefs::apz_enlarge_displayport_when_clipped()) {
4466 RedistributeDisplayPortExcess(displayPortSize
, scrollableRect
);
4469 // We calculate a "displayport" here which is relative to the scroll offset.
4470 // Note that the scroll offset we have here in the APZ code may not be the
4471 // same as the base rect that gets used on the layout side when the
4472 // displayport margins are actually applied, so it is important to only
4473 // consider the displayport as margins relative to a scroll offset rather than
4474 // relative to something more unchanging like the scrollable rect origin.
4476 // Center the displayport based on its expansion over the composition size.
4477 CSSRect
displayPort((compositionSize
.width
- displayPortSize
.width
) / 2.0f
,
4478 (compositionSize
.height
- displayPortSize
.height
) / 2.0f
,
4479 displayPortSize
.width
, displayPortSize
.height
);
4481 // Offset the displayport, depending on how fast we're moving and the
4482 // estimated time it takes to paint, to try to minimise checkerboarding.
4483 float paintFactor
= kDefaultEstimatedPaintDurationMs
;
4484 displayPort
.MoveBy(velocity
* paintFactor
* StaticPrefs::apz_velocity_bias());
4486 APZC_LOGV_FM(aFrameMetrics
,
4487 "Calculated displayport as %s from velocity %s zooming %d paint "
4489 ToString(displayPort
).c_str(), ToString(aVelocity
).c_str(),
4490 (int)aZoomInProgress
, paintFactor
);
4492 CSSMargin cssMargins
;
4493 cssMargins
.left
= -displayPort
.X();
4494 cssMargins
.top
= -displayPort
.Y();
4496 displayPort
.Width() - compositionSize
.width
- cssMargins
.left
;
4498 displayPort
.Height() - compositionSize
.height
- cssMargins
.top
;
4500 return cssMargins
* aFrameMetrics
.DisplayportPixelsPerCSSPixel();
4503 void AsyncPanZoomController::ScheduleComposite() {
4504 if (mCompositorController
) {
4505 mCompositorController
->ScheduleRenderOnCompositorThread(
4506 wr::RenderReasons::APZ
);
4510 void AsyncPanZoomController::ScheduleCompositeAndMaybeRepaint() {
4511 ScheduleComposite();
4512 RequestContentRepaint();
4515 void AsyncPanZoomController::FlushRepaintForOverscrollHandoff() {
4516 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
4517 RequestContentRepaint();
4520 void AsyncPanZoomController::FlushRepaintForNewInputBlock() {
4521 APZC_LOG("%p flushing repaint for new input block\n", this);
4523 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
4524 RequestContentRepaint();
4527 bool AsyncPanZoomController::SnapBackIfOverscrolled() {
4528 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
4529 if (SnapBackIfOverscrolledForMomentum(ParentLayerPoint(0, 0))) {
4532 // If we don't kick off an overscroll animation, we still need to snap to any
4533 // nearby snap points, assuming we haven't already done so when we started
4535 if (mState
!= FLING
) {
4536 ScrollSnap(ScrollSnapFlags::IntendedEndPosition
);
4541 bool AsyncPanZoomController::SnapBackIfOverscrolledForMomentum(
4542 const ParentLayerPoint
& aVelocity
) {
4543 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
4544 // It's possible that we're already in the middle of an overscroll
4545 // animation - if so, don't start a new one.
4546 if (IsOverscrolled() && mState
!= OVERSCROLL_ANIMATION
) {
4547 APZC_LOG("%p is overscrolled, starting snap-back\n", this);
4548 mOverscrollEffect
->RelieveOverscroll(aVelocity
, GetOverscrollSideBits());
4554 bool AsyncPanZoomController::IsFlingingFast() const {
4555 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
4556 if (mState
== FLING
&& GetVelocityVector().Length() >
4557 StaticPrefs::apz_fling_stop_on_tap_threshold()) {
4558 APZC_LOG("%p is moving fast\n", this);
4564 bool AsyncPanZoomController::IsPannable() const {
4565 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
4566 return mX
.CanScroll() || mY
.CanScroll();
4569 bool AsyncPanZoomController::IsScrollInfoLayer() const {
4570 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
4571 return Metrics().IsScrollInfoLayer();
4574 int32_t AsyncPanZoomController::GetLastTouchIdentifier() const {
4575 RefPtr
<GestureEventListener
> listener
= GetGestureEventListener();
4576 return listener
? listener
->GetLastTouchIdentifier() : -1;
4579 void AsyncPanZoomController::RequestContentRepaint(
4580 RepaintUpdateType aUpdateType
) {
4581 // Reinvoke this method on the repaint thread if it's not there already. It's
4582 // important to do this before the call to CalculatePendingDisplayPort, so
4583 // that CalculatePendingDisplayPort uses the most recent available version of
4584 // Metrics(). just before the paint request is dispatched to content.
4585 RefPtr
<GeckoContentController
> controller
= GetGeckoContentController();
4589 if (!controller
->IsRepaintThread()) {
4590 // Even though we want to do the actual repaint request on the repaint
4591 // thread, we want to update the expected gecko metrics synchronously.
4592 // Otherwise we introduce a race condition where we might read from the
4593 // expected gecko metrics on the controller thread before or after it gets
4594 // updated on the repaint thread, when in fact we always want the updated
4595 // version when reading.
4597 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
4598 mExpectedGeckoMetrics
.UpdateFrom(Metrics());
4601 // use the local variable to resolve the function overload.
4603 static_cast<void (AsyncPanZoomController::*)(RepaintUpdateType
)>(
4604 &AsyncPanZoomController::RequestContentRepaint
);
4605 controller
->DispatchToRepaintThread(NewRunnableMethod
<RepaintUpdateType
>(
4606 "layers::AsyncPanZoomController::RequestContentRepaint", this, func
,
4611 MOZ_ASSERT(controller
->IsRepaintThread());
4613 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
4614 ParentLayerPoint velocity
= GetVelocityVector();
4615 ScreenMargin displayportMargins
= CalculatePendingDisplayPort(
4616 Metrics(), velocity
,
4617 (mState
== PINCHING
|| mState
== ANIMATING_ZOOM
) ? ZoomInProgress::Yes
4618 : ZoomInProgress::No
);
4619 Metrics().SetPaintRequestTime(TimeStamp::Now());
4620 RequestContentRepaint(velocity
, displayportMargins
, aUpdateType
);
4623 static CSSRect
GetDisplayPortRect(const FrameMetrics
& aFrameMetrics
,
4624 const ScreenMargin
& aDisplayportMargins
) {
4625 // This computation is based on what happens in CalculatePendingDisplayPort.
4626 // If that changes then this might need to change too.
4627 // Note that the display port rect APZ computes is relative to the visual
4628 // scroll offset. It's adjusted to be relative to the layout scroll offset
4629 // when the main thread processes a repaint request (in
4630 // APZCCallbackHelper::AdjustDisplayPortForScrollDelta()) and ultimately
4631 // applied (in DisplayPortUtils::GetDisplayPort()) in this adjusted form.
4632 CSSRect
baseRect(aFrameMetrics
.GetVisualScrollOffset(),
4633 aFrameMetrics
.CalculateBoundedCompositedSizeInCssPixels());
4634 baseRect
.Inflate(aDisplayportMargins
/
4635 aFrameMetrics
.DisplayportPixelsPerCSSPixel());
4639 void AsyncPanZoomController::RequestContentRepaint(
4640 const ParentLayerPoint
& aVelocity
, const ScreenMargin
& aDisplayportMargins
,
4641 RepaintUpdateType aUpdateType
) {
4642 mRecursiveMutex
.AssertCurrentThreadIn();
4644 RefPtr
<GeckoContentController
> controller
= GetGeckoContentController();
4648 MOZ_ASSERT(controller
->IsRepaintThread());
4650 APZScrollAnimationType animationType
= APZScrollAnimationType::No
;
4652 animationType
= mAnimation
->WasTriggeredByScript()
4653 ? APZScrollAnimationType::TriggeredByScript
4654 : APZScrollAnimationType::TriggeredByUserInput
;
4656 RepaintRequest
request(Metrics(), aDisplayportMargins
, aUpdateType
,
4657 animationType
, mScrollGeneration
, mLastSnapTargetIds
,
4658 IsInScrollingGesture());
4660 if (request
.IsRootContent() && request
.GetZoom() != mLastNotifiedZoom
&&
4661 mState
!= PINCHING
&& mState
!= ANIMATING_ZOOM
) {
4662 controller
->NotifyScaleGestureComplete(
4664 (request
.GetZoom() / request
.GetDevPixelsPerCSSPixel()).scale
);
4665 mLastNotifiedZoom
= request
.GetZoom();
4668 // If we're trying to paint what we already think is painted, discard this
4669 // request since it's a pointless paint.
4670 if (request
.GetDisplayPortMargins().WithinEpsilonOf(
4671 mLastPaintRequestMetrics
.GetDisplayPortMargins(), EPSILON
) &&
4672 request
.GetVisualScrollOffset().WithinEpsilonOf(
4673 mLastPaintRequestMetrics
.GetVisualScrollOffset(), EPSILON
) &&
4674 request
.GetPresShellResolution() ==
4675 mLastPaintRequestMetrics
.GetPresShellResolution() &&
4676 request
.GetZoom() == mLastPaintRequestMetrics
.GetZoom() &&
4677 request
.GetLayoutViewport().WithinEpsilonOf(
4678 mLastPaintRequestMetrics
.GetLayoutViewport(), EPSILON
) &&
4679 request
.GetScrollGeneration() ==
4680 mLastPaintRequestMetrics
.GetScrollGeneration() &&
4681 request
.GetScrollUpdateType() ==
4682 mLastPaintRequestMetrics
.GetScrollUpdateType() &&
4683 request
.GetScrollAnimationType() ==
4684 mLastPaintRequestMetrics
.GetScrollAnimationType() &&
4685 request
.GetLastSnapTargetIds() ==
4686 mLastPaintRequestMetrics
.GetLastSnapTargetIds()) {
4690 APZC_LOGV("%p requesting content repaint %s", this,
4691 ToString(request
).c_str());
4693 MutexAutoLock
lock(mCheckerboardEventLock
);
4694 if (mCheckerboardEvent
&& mCheckerboardEvent
->IsRecordingTrace()) {
4695 std::stringstream info
;
4696 info
<< " velocity " << aVelocity
;
4697 std::string str
= info
.str();
4698 mCheckerboardEvent
->UpdateRendertraceProperty(
4699 CheckerboardEvent::RequestedDisplayPort
,
4700 GetDisplayPortRect(Metrics(), aDisplayportMargins
), str
);
4704 controller
->RequestContentRepaint(request
);
4705 mExpectedGeckoMetrics
.UpdateFrom(Metrics());
4706 mLastPaintRequestMetrics
= request
;
4708 // We're holding the APZC lock here, so redispatch this so we can get
4709 // the tree lock without the APZC lock.
4710 controller
->DispatchToRepaintThread(
4711 NewRunnableMethod
<AsyncPanZoomController
*>(
4712 "layers::APZCTreeManager::SendSubtreeTransformsToChromeMainThread",
4713 GetApzcTreeManager(),
4714 &APZCTreeManager::SendSubtreeTransformsToChromeMainThread
, this));
4717 bool AsyncPanZoomController::UpdateAnimation(
4718 const RecursiveMutexAutoLock
& aProofOfLock
, const SampleTime
& aSampleTime
,
4719 nsTArray
<RefPtr
<Runnable
>>* aOutDeferredTasks
) {
4720 AssertOnSamplerThread();
4722 // This function may get called multiple with the same sample time, if we
4723 // composite multiple times at the same timestamp.
4724 // However we only want to do one animation step per composition so we need
4725 // to deduplicate these calls first.
4726 // Even if there's no animation, if we have a scroll offset change pending due
4727 // to the frame delay, we need to keep compositing.
4728 if (mLastSampleTime
== aSampleTime
) {
4729 return !!mAnimation
|| HavePendingFrameDelayedOffset();
4732 // We're at a new timestamp, so advance to the next sample in the deque, if
4733 // there is one. That one will be used for all the code that reads the
4734 // eForCompositing transforms in this vsync interval.
4735 AdvanceToNextSample();
4737 // And then create a new sample, which will be used in the *next* vsync
4738 // interval. We do the sample at this point and not later in order to try
4739 // and enforce one frame delay between computing the async transform and
4740 // compositing it to the screen. This one-frame delay gives code running on
4741 // the main thread a chance to try and respond to the scroll position change,
4742 // so that e.g. a main-thread animation can stay in sync with user-driven
4743 // scrolling or a compositor animation.
4744 bool needComposite
= SampleCompositedAsyncTransform(aProofOfLock
);
4746 TimeDuration sampleTimeDelta
= aSampleTime
- mLastSampleTime
;
4747 mLastSampleTime
= aSampleTime
;
4749 if (needComposite
|| mAnimation
) {
4750 // Bump the scroll generation before we call RequestContentRepaint below
4751 // so that the RequestContentRepaint call will surely use the new
4753 if (APZCTreeManager
* treeManagerLocal
= GetApzcTreeManager()) {
4754 mScrollGeneration
= treeManagerLocal
->NewAPZScrollGeneration();
4759 bool continueAnimation
= mAnimation
->Sample(Metrics(), sampleTimeDelta
);
4760 bool wantsRepaints
= mAnimation
->WantsRepaints();
4761 *aOutDeferredTasks
= mAnimation
->TakeDeferredTasks();
4762 if (!continueAnimation
) {
4764 if (mAnimation
->AsSmoothMsdScrollAnimation()) {
4766 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
4767 mLastSnapTargetIds
=
4768 mAnimation
->AsSmoothMsdScrollAnimation()->TakeSnapTargetIds();
4770 } else if (mAnimation
->AsSmoothScrollAnimation()) {
4771 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
4772 mLastSnapTargetIds
=
4773 mAnimation
->AsSmoothScrollAnimation()->TakeSnapTargetIds();
4775 mAnimation
= nullptr;
4777 // Request a repaint at the end of the animation in case something such as a
4778 // call to NotifyLayersUpdated was invoked during the animation and Gecko's
4779 // current state is some intermediate point of the animation.
4780 if (!continueAnimation
|| wantsRepaints
) {
4781 RequestContentRepaint();
4783 needComposite
= true;
4785 return needComposite
;
4788 AsyncTransformComponentMatrix
AsyncPanZoomController::GetOverscrollTransform(
4789 AsyncTransformConsumer aMode
) const {
4790 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
4791 AutoApplyAsyncTestAttributes
testAttributeApplier(this, lock
);
4793 if (aMode
== eForCompositing
&& mScrollMetadata
.IsApzForceDisabled()) {
4794 return AsyncTransformComponentMatrix();
4797 if (!IsPhysicallyOverscrolled()) {
4798 return AsyncTransformComponentMatrix();
4801 // The overscroll effect is a simple translation by the overscroll offset.
4802 ParentLayerPoint
overscrollOffset(-mX
.GetOverscroll(), -mY
.GetOverscroll());
4803 return AsyncTransformComponentMatrix().PostTranslate(overscrollOffset
.x
,
4804 overscrollOffset
.y
, 0);
4807 bool AsyncPanZoomController::AdvanceAnimations(const SampleTime
& aSampleTime
) {
4808 AssertOnSamplerThread();
4810 // Don't send any state-change notifications until the end of the function,
4811 // because we may go through some intermediate states while we finish
4812 // animations and start new ones.
4813 StateChangeNotificationBlocker
blocker(this);
4815 // The eventual return value of this function. The compositor needs to know
4816 // whether or not to advance by a frame as soon as it can. For example, if a
4817 // fling is happening, it has to keep compositing so that the animation is
4818 // smooth. If an animation frame is requested, it is the compositor's
4819 // responsibility to schedule a composite.
4820 bool requestAnimationFrame
= false;
4821 nsTArray
<RefPtr
<Runnable
>> deferredTasks
;
4824 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
4826 CSSRect visibleRect
= GetVisibleRect(lock
);
4827 MutexAutoLock
lock2(mCheckerboardEventLock
);
4828 // Update RendertraceProperty before UpdateAnimation() call, since
4829 // the UpdateAnimation() updates effective ScrollOffset for next frame
4830 // if APZFrameDelay is enabled.
4831 if (mCheckerboardEvent
) {
4832 mCheckerboardEvent
->UpdateRendertraceProperty(
4833 CheckerboardEvent::UserVisible
, visibleRect
);
4837 requestAnimationFrame
= UpdateAnimation(lock
, aSampleTime
, &deferredTasks
);
4839 // Execute any deferred tasks queued up by mAnimation's Sample() (called by
4840 // UpdateAnimation()). This needs to be done after the monitor is released
4841 // since the tasks are allowed to call APZCTreeManager methods which can grab
4843 for (uint32_t i
= 0; i
< deferredTasks
.Length(); ++i
) {
4844 APZThreadUtils::RunOnControllerThread(std::move(deferredTasks
[i
]));
4847 // If any of the deferred tasks starts a new animation, it will request a
4848 // new composite directly, so we can just return requestAnimationFrame here.
4849 return requestAnimationFrame
;
4852 ParentLayerPoint
AsyncPanZoomController::GetCurrentAsyncScrollOffset(
4853 AsyncTransformConsumer aMode
) const {
4854 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
4855 AutoApplyAsyncTestAttributes
testAttributeApplier(this, lock
);
4857 return GetEffectiveScrollOffset(aMode
, lock
) * GetEffectiveZoom(aMode
, lock
);
4860 CSSRect
AsyncPanZoomController::GetCurrentAsyncVisualViewport(
4861 AsyncTransformConsumer aMode
) const {
4862 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
4863 AutoApplyAsyncTestAttributes
testAttributeApplier(this, lock
);
4866 GetEffectiveScrollOffset(aMode
, lock
),
4867 FrameMetrics::CalculateCompositedSizeInCssPixels(
4868 Metrics().GetCompositionBounds(), GetEffectiveZoom(aMode
, lock
)));
4871 AsyncTransform
AsyncPanZoomController::GetCurrentAsyncTransform(
4872 AsyncTransformConsumer aMode
, AsyncTransformComponents aComponents
,
4873 std::size_t aSampleIndex
) const {
4874 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
4875 AutoApplyAsyncTestAttributes
testAttributeApplier(this, lock
);
4877 CSSToParentLayerScale effectiveZoom
;
4878 if (aComponents
.contains(AsyncTransformComponent::eVisual
)) {
4879 effectiveZoom
= GetEffectiveZoom(aMode
, lock
, aSampleIndex
);
4882 Metrics().LayersPixelsPerCSSPixel() * LayerToParentLayerScale(1.0f
);
4885 LayerToParentLayerScale compositedAsyncZoom
=
4886 effectiveZoom
/ Metrics().LayersPixelsPerCSSPixel();
4888 ParentLayerPoint translation
;
4889 if (aComponents
.contains(AsyncTransformComponent::eVisual
)) {
4890 // There is no "lastPaintVisualOffset" to subtract here; the visual offset
4891 // is entirely async.
4893 CSSPoint currentVisualOffset
=
4894 GetEffectiveScrollOffset(aMode
, lock
, aSampleIndex
) -
4895 GetEffectiveLayoutViewport(aMode
, lock
, aSampleIndex
).TopLeft();
4897 translation
+= currentVisualOffset
* effectiveZoom
;
4899 if (aComponents
.contains(AsyncTransformComponent::eLayout
)) {
4900 CSSPoint lastPaintLayoutOffset
;
4901 if (mLastContentPaintMetrics
.IsScrollable()) {
4902 lastPaintLayoutOffset
= mLastContentPaintMetrics
.GetLayoutScrollOffset();
4905 CSSPoint currentLayoutOffset
=
4906 GetEffectiveLayoutViewport(aMode
, lock
, aSampleIndex
).TopLeft();
4909 (currentLayoutOffset
- lastPaintLayoutOffset
) * effectiveZoom
;
4912 return AsyncTransform(compositedAsyncZoom
, -translation
);
4915 AsyncTransformComponentMatrix
4916 AsyncPanZoomController::GetAsyncTransformForInputTransformation(
4917 AsyncTransformComponents aComponents
, LayersId aForLayersId
) const {
4918 AsyncTransformComponentMatrix result
;
4919 // If we are the root, and |aForLayersId| is different from our LayersId,
4920 // |aForLayersId| must be in a remote subdocument.
4921 if (IsRootContent() && aForLayersId
!= GetLayersId()) {
4923 ViewAs
<AsyncTransformComponentMatrix
>(GetPaintedResolutionTransform());
4925 // Order of transforms: the painted resolution (if any) applies first, and
4926 // any async transform on top of that.
4927 result
= result
* AsyncTransformComponentMatrix(GetCurrentAsyncTransform(
4928 eForEventHandling
, aComponents
));
4929 // The overscroll transform is considered part of the layout component of
4930 // the async transform, because it should not apply to fixed content.
4931 if (aComponents
.contains(AsyncTransformComponent::eLayout
)) {
4932 result
= result
* GetOverscrollTransform(eForEventHandling
);
4937 Matrix4x4
AsyncPanZoomController::GetPaintedResolutionTransform() const {
4938 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
4939 MOZ_ASSERT(IsRootContent());
4940 float resolution
= mLastContentPaintMetrics
.GetPresShellResolution();
4941 return Matrix4x4::Scaling(resolution
, resolution
, 1.f
);
4944 LayoutDeviceToParentLayerScale
AsyncPanZoomController::GetCurrentPinchZoomScale(
4945 AsyncTransformConsumer aMode
) const {
4946 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
4947 AutoApplyAsyncTestAttributes
testAttributeApplier(this, lock
);
4948 CSSToParentLayerScale scale
= GetEffectiveZoom(aMode
, lock
);
4949 return scale
/ Metrics().GetDevPixelsPerCSSPixel();
4952 AutoTArray
<wr::SampledScrollOffset
, 2>
4953 AsyncPanZoomController::GetSampledScrollOffsets() const {
4954 AssertOnSamplerThread();
4956 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
4958 const AsyncTransformComponents asyncTransformComponents
=
4959 GetZoomAnimationId()
4960 ? AsyncTransformComponents
{AsyncTransformComponent::eLayout
}
4963 // If layerTranslation includes only the layout component of the async
4964 // transform then it has not been scaled by the async zoom, so we want to
4965 // divide it by the resolution. If layerTranslation includes the visual
4966 // component, then we should use the pinch zoom scale, which includes the
4967 // async zoom. However, we only use LayoutAndVisual for non-zoomable APZCs,
4968 // so it makes no difference.
4969 LayoutDeviceToParentLayerScale resolution
=
4970 GetCumulativeResolution() * LayerToParentLayerScale(1.0f
);
4972 AutoTArray
<wr::SampledScrollOffset
, 2> sampledOffsets
;
4974 for (std::deque
<SampledAPZCState
>::size_type index
= 0;
4975 index
< mSampledState
.size(); index
++) {
4976 ParentLayerPoint layerTranslation
=
4977 GetCurrentAsyncTransform(AsyncPanZoomController::eForCompositing
,
4978 asyncTransformComponents
, index
)
4981 // Include the overscroll transform here in scroll offsets transform
4982 // to ensure that we do not overscroll fixed content.
4984 GetOverscrollTransform(AsyncPanZoomController::eForCompositing
)
4985 .TransformPoint(layerTranslation
);
4986 // The positive translation means the painted content is supposed to
4987 // move down (or to the right), and that corresponds to a reduction in
4988 // the scroll offset. Since we are effectively giving WR the async
4989 // scroll delta here, we want to negate the translation.
4990 LayoutDevicePoint asyncScrollDelta
= -layerTranslation
/ resolution
;
4991 sampledOffsets
.AppendElement(wr::SampledScrollOffset
{
4992 wr::ToLayoutVector2D(asyncScrollDelta
),
4993 wr::ToWrAPZScrollGeneration(mSampledState
[index
].Generation())});
4996 return sampledOffsets
;
4999 bool AsyncPanZoomController::SuppressAsyncScrollOffset() const {
5000 return mScrollMetadata
.IsApzForceDisabled() ||
5001 (Metrics().IsMinimalDisplayPort() &&
5002 StaticPrefs::apz_prefer_jank_minimal_displayports());
5005 CSSRect
AsyncPanZoomController::GetEffectiveLayoutViewport(
5006 AsyncTransformConsumer aMode
, const RecursiveMutexAutoLock
& aProofOfLock
,
5007 std::size_t aSampleIndex
) const {
5008 if (aMode
== eForCompositing
&& SuppressAsyncScrollOffset()) {
5009 return mLastContentPaintMetrics
.GetLayoutViewport();
5011 if (aMode
== eForCompositing
) {
5012 return mSampledState
[aSampleIndex
].GetLayoutViewport();
5014 return Metrics().GetLayoutViewport();
5017 CSSPoint
AsyncPanZoomController::GetEffectiveScrollOffset(
5018 AsyncTransformConsumer aMode
, const RecursiveMutexAutoLock
& aProofOfLock
,
5019 std::size_t aSampleIndex
) const {
5020 if (aMode
== eForCompositing
&& SuppressAsyncScrollOffset()) {
5021 return mLastContentPaintMetrics
.GetVisualScrollOffset();
5023 if (aMode
== eForCompositing
) {
5024 return mSampledState
[aSampleIndex
].GetVisualScrollOffset();
5026 return Metrics().GetVisualScrollOffset();
5029 CSSToParentLayerScale
AsyncPanZoomController::GetEffectiveZoom(
5030 AsyncTransformConsumer aMode
, const RecursiveMutexAutoLock
& aProofOfLock
,
5031 std::size_t aSampleIndex
) const {
5032 if (aMode
== eForCompositing
&& SuppressAsyncScrollOffset()) {
5033 return mLastContentPaintMetrics
.GetZoom();
5035 if (aMode
== eForCompositing
) {
5036 return mSampledState
[aSampleIndex
].GetZoom();
5038 return Metrics().GetZoom();
5041 void AsyncPanZoomController::AdvanceToNextSample() {
5042 AssertOnSamplerThread();
5043 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
5044 // Always keep at least one state in mSampledState.
5045 if (mSampledState
.size() > 1) {
5046 mSampledState
.pop_front();
5050 bool AsyncPanZoomController::HavePendingFrameDelayedOffset() const {
5051 AssertOnSamplerThread();
5052 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
5054 const bool nextFrameWillChange
=
5055 mSampledState
.size() >= 2 && mSampledState
[0] != mSampledState
[1];
5056 const bool frameAfterThatWillChange
=
5057 mSampledState
.back() != SampledAPZCState(Metrics());
5058 return nextFrameWillChange
|| frameAfterThatWillChange
;
5061 bool AsyncPanZoomController::SampleCompositedAsyncTransform(
5062 const RecursiveMutexAutoLock
& aProofOfLock
) {
5063 MOZ_ASSERT(mSampledState
.size() <= 2);
5064 bool sampleChanged
= (mSampledState
.back() != SampledAPZCState(Metrics()));
5065 mSampledState
.emplace_back(Metrics(), std::move(mScrollPayload
),
5067 return sampleChanged
;
5070 void AsyncPanZoomController::ResampleCompositedAsyncTransform(
5071 const RecursiveMutexAutoLock
& aProofOfLock
) {
5072 // This only gets called during testing situations, so the fact that this
5073 // drops the scroll payload from mSampledState.front() is not really a
5075 if (APZCTreeManager
* treeManagerLocal
= GetApzcTreeManager()) {
5076 mScrollGeneration
= treeManagerLocal
->NewAPZScrollGeneration();
5078 mSampledState
.front() = SampledAPZCState(Metrics(), {}, mScrollGeneration
);
5081 void AsyncPanZoomController::ApplyAsyncTestAttributes(
5082 const RecursiveMutexAutoLock
& aProofOfLock
) {
5083 if (mTestAttributeAppliers
== 0) {
5084 if (mTestAsyncScrollOffset
!= CSSPoint() ||
5085 mTestAsyncZoom
!= LayerToParentLayerScale()) {
5086 // TODO Currently we update Metrics() and resample, which will cause
5087 // the very latest user input to get immediately captured in the sample,
5088 // and may defeat our attempt at "frame delay" (i.e. delaying the user
5089 // input from affecting composition by one frame).
5090 // Instead, maybe we should just apply the mTest* stuff directly to
5091 // mSampledState.front(). We can even save/restore that SampledAPZCState
5092 // instance in the AutoApplyAsyncTestAttributes instead of Metrics().
5093 Metrics().ZoomBy(mTestAsyncZoom
.scale
);
5094 CSSPoint asyncScrollPosition
= Metrics().GetVisualScrollOffset();
5095 CSSPoint requestedPoint
=
5096 asyncScrollPosition
+ this->mTestAsyncScrollOffset
;
5097 CSSPoint clampedPoint
=
5098 Metrics().CalculateScrollRange().ClampPoint(requestedPoint
);
5099 CSSPoint difference
= mTestAsyncScrollOffset
- clampedPoint
;
5101 ScrollByAndClamp(mTestAsyncScrollOffset
);
5103 if (StaticPrefs::apz_overscroll_test_async_scroll_offset_enabled()) {
5104 ParentLayerPoint overscroll
= difference
* Metrics().GetZoom();
5105 OverscrollBy(overscroll
);
5107 ResampleCompositedAsyncTransform(aProofOfLock
);
5110 ++mTestAttributeAppliers
;
5113 void AsyncPanZoomController::UnapplyAsyncTestAttributes(
5114 const RecursiveMutexAutoLock
& aProofOfLock
,
5115 const FrameMetrics
& aPrevFrameMetrics
,
5116 const ParentLayerPoint
& aPrevOverscroll
) {
5117 MOZ_ASSERT(mTestAttributeAppliers
>= 1);
5118 --mTestAttributeAppliers
;
5119 if (mTestAttributeAppliers
== 0) {
5120 if (mTestAsyncScrollOffset
!= CSSPoint() ||
5121 mTestAsyncZoom
!= LayerToParentLayerScale()) {
5122 Metrics() = aPrevFrameMetrics
;
5123 RestoreOverscrollAmount(aPrevOverscroll
);
5124 ResampleCompositedAsyncTransform(aProofOfLock
);
5129 Matrix4x4
AsyncPanZoomController::GetTransformToLastDispatchedPaint(
5130 const AsyncTransformComponents
& aComponents
, LayersId aForLayersId
) const {
5131 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
5132 CSSPoint componentOffset
;
5134 // The computation of the componentOffset should roughly be the negation
5135 // of the translation in GetCurrentAsyncTransform() with the expected
5136 // gecko metrics substituted for the effective scroll offsets.
5137 if (aComponents
.contains(AsyncTransformComponent::eVisual
)) {
5138 componentOffset
+= mExpectedGeckoMetrics
.GetLayoutScrollOffset() -
5139 mExpectedGeckoMetrics
.GetVisualScrollOffset();
5142 if (aComponents
.contains(AsyncTransformComponent::eLayout
)) {
5143 CSSPoint lastPaintLayoutOffset
;
5145 if (mLastContentPaintMetrics
.IsScrollable()) {
5146 lastPaintLayoutOffset
= mLastContentPaintMetrics
.GetLayoutScrollOffset();
5150 lastPaintLayoutOffset
- mExpectedGeckoMetrics
.GetLayoutScrollOffset();
5153 LayerPoint scrollChange
= componentOffset
*
5154 mLastContentPaintMetrics
.GetDevPixelsPerCSSPixel() *
5155 mLastContentPaintMetrics
.GetCumulativeResolution();
5157 // We're interested in the async zoom change. Factor out the content scale
5158 // that may change when dragging the window to a monitor with a different
5160 LayoutDeviceToParentLayerScale lastContentZoom
=
5161 mLastContentPaintMetrics
.GetZoom() /
5162 mLastContentPaintMetrics
.GetDevPixelsPerCSSPixel();
5163 LayoutDeviceToParentLayerScale lastDispatchedZoom
=
5164 mExpectedGeckoMetrics
.GetZoom() /
5165 mExpectedGeckoMetrics
.GetDevPixelsPerCSSPixel();
5166 float zoomChange
= 1.0;
5167 if (aComponents
.contains(AsyncTransformComponent::eVisual
) &&
5168 lastDispatchedZoom
!= LayoutDeviceToParentLayerScale(0)) {
5169 zoomChange
= lastContentZoom
.scale
/ lastDispatchedZoom
.scale
;
5172 // If we are the root, and |aForLayersId| is different from our LayersId,
5173 // |aForLayersId| must be in a remote subdocument.
5174 if (IsRootContent() && aForLayersId
!= GetLayersId()) {
5175 result
= GetPaintedResolutionTransform();
5177 // Order of transforms: the painted resolution (if any) applies first, and
5178 // any async transform on top of that.
5179 return result
* Matrix4x4::Translation(scrollChange
.x
, scrollChange
.y
, 0)
5180 .PostScale(zoomChange
, zoomChange
, 1);
5183 CSSRect
AsyncPanZoomController::GetVisibleRect(
5184 const RecursiveMutexAutoLock
& aProofOfLock
) const {
5185 AutoApplyAsyncTestAttributes
testAttributeApplier(this, aProofOfLock
);
5186 CSSPoint currentScrollOffset
= GetEffectiveScrollOffset(
5187 AsyncPanZoomController::eForCompositing
, aProofOfLock
);
5188 CSSRect visible
= CSSRect(currentScrollOffset
,
5189 Metrics().CalculateCompositedSizeInCssPixels());
5193 static CSSRect
GetPaintedRect(const FrameMetrics
& aFrameMetrics
) {
5194 CSSRect displayPort
= aFrameMetrics
.GetDisplayPort();
5195 if (displayPort
.IsEmpty()) {
5196 // Fallback to use the viewport if the diplayport hasn't been set.
5197 // This situation often happens non-scrollable iframe's root scroller in
5199 return aFrameMetrics
.GetVisualViewport();
5202 return displayPort
+ aFrameMetrics
.GetLayoutScrollOffset();
5205 uint32_t AsyncPanZoomController::GetCheckerboardMagnitude(
5206 const ParentLayerRect
& aClippedCompositionBounds
) const {
5207 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
5209 CSSRect painted
= GetPaintedRect(mLastContentPaintMetrics
);
5210 painted
.Inflate(CSSMargin::FromAppUnits(
5211 nsMargin(1, 1, 1, 1))); // fuzz for rounding error
5213 CSSRect visible
= GetVisibleRect(lock
); // relative to scrolled frame origin
5214 if (visible
.IsEmpty() || painted
.Contains(visible
)) {
5215 // early-exit if we're definitely not checkerboarding
5219 // aClippedCompositionBounds and Metrics().GetCompositionBounds() are both
5220 // relative to the layer tree origin.
5221 // The "*RelativeToItself*" variables are relative to the comp bounds origin
5222 ParentLayerRect visiblePartOfCompBoundsRelativeToItself
=
5223 aClippedCompositionBounds
- Metrics().GetCompositionBounds().TopLeft();
5224 CSSRect visiblePartOfCompBoundsRelativeToItselfInCssSpace
;
5225 if (Metrics().GetZoom() != CSSToParentLayerScale(0)) {
5226 visiblePartOfCompBoundsRelativeToItselfInCssSpace
=
5227 (visiblePartOfCompBoundsRelativeToItself
/ Metrics().GetZoom());
5230 // This one is relative to the scrolled frame origin, same as `visible`
5231 CSSRect visiblePartOfCompBoundsInCssSpace
=
5232 visiblePartOfCompBoundsRelativeToItselfInCssSpace
+ visible
.TopLeft();
5234 visible
= visible
.Intersect(visiblePartOfCompBoundsInCssSpace
);
5236 CSSIntRegion checkerboard
;
5237 // Round so as to minimize checkerboarding; if we're only showing fractional
5238 // pixels of checkerboarding it's not really worth counting
5239 checkerboard
.Sub(RoundedIn(visible
), RoundedOut(painted
));
5240 uint32_t area
= checkerboard
.Area();
5242 APZC_LOG_FM(Metrics(),
5243 "%p is currently checkerboarding (painted %s visible %s)", this,
5244 ToString(painted
).c_str(), ToString(visible
).c_str());
5249 void AsyncPanZoomController::ReportCheckerboard(
5250 const SampleTime
& aSampleTime
,
5251 const ParentLayerRect
& aClippedCompositionBounds
) {
5252 if (mLastCheckerboardReport
== aSampleTime
) {
5253 // This function will get called multiple times for each APZC on a single
5254 // composite (once for each layer it is attached to). Only report the
5255 // checkerboard once per composite though.
5258 mLastCheckerboardReport
= aSampleTime
;
5260 bool recordTrace
= StaticPrefs::apz_record_checkerboarding();
5261 bool forTelemetry
= Telemetry::CanRecordBase();
5262 uint32_t magnitude
= GetCheckerboardMagnitude(aClippedCompositionBounds
);
5264 // IsInTransformingState() acquires the APZC lock and thus needs to
5265 // be called before acquiring mCheckerboardEventLock.
5266 bool inTransformingState
= IsInTransformingState();
5268 MutexAutoLock
lock(mCheckerboardEventLock
);
5269 if (!mCheckerboardEvent
&& (recordTrace
|| forTelemetry
)) {
5270 mCheckerboardEvent
= MakeUnique
<CheckerboardEvent
>(recordTrace
);
5272 mPotentialCheckerboardTracker
.InTransform(inTransformingState
,
5273 recordTrace
|| forTelemetry
);
5275 mPotentialCheckerboardTracker
.CheckerboardSeen();
5277 UpdateCheckerboardEvent(lock
, magnitude
);
5280 void AsyncPanZoomController::UpdateCheckerboardEvent(
5281 const MutexAutoLock
& aProofOfLock
, uint32_t aMagnitude
) {
5282 if (mCheckerboardEvent
&& mCheckerboardEvent
->RecordFrameInfo(aMagnitude
)) {
5283 // This checkerboard event is done. Report some metrics to telemetry.
5284 mozilla::Telemetry::Accumulate(mozilla::Telemetry::CHECKERBOARD_SEVERITY
,
5285 mCheckerboardEvent
->GetSeverity());
5286 mozilla::Telemetry::Accumulate(mozilla::Telemetry::CHECKERBOARD_PEAK
,
5287 mCheckerboardEvent
->GetPeak());
5288 mozilla::Telemetry::Accumulate(
5289 mozilla::Telemetry::CHECKERBOARD_DURATION
,
5290 (uint32_t)mCheckerboardEvent
->GetDuration().ToMilliseconds());
5292 // mCheckerboardEvent only gets created if we are supposed to record
5293 // telemetry so we always pass true for aRecordTelemetry.
5294 mPotentialCheckerboardTracker
.CheckerboardDone(
5295 /* aRecordTelemetry = */ true);
5297 if (StaticPrefs::apz_record_checkerboarding()) {
5298 // if the pref is enabled, also send it to the storage class. it may be
5299 // chosen for public display on about:checkerboard, the hall of fame for
5300 // checkerboard events.
5301 uint32_t severity
= mCheckerboardEvent
->GetSeverity();
5302 std::string log
= mCheckerboardEvent
->GetLog();
5303 CheckerboardEventStorage::Report(severity
, log
);
5305 mCheckerboardEvent
= nullptr;
5309 void AsyncPanZoomController::FlushActiveCheckerboardReport() {
5310 MutexAutoLock
lock(mCheckerboardEventLock
);
5311 // Pretend like we got a frame with 0 pixels checkerboarded. This will
5312 // terminate the checkerboard event and flush it out
5313 UpdateCheckerboardEvent(lock
, 0);
5316 void AsyncPanZoomController::NotifyLayersUpdated(
5317 const ScrollMetadata
& aScrollMetadata
, bool aIsFirstPaint
,
5318 bool aThisLayerTreeUpdated
) {
5319 AssertOnUpdaterThread();
5321 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
5322 bool isDefault
= mScrollMetadata
.IsDefault();
5324 const FrameMetrics
& aLayerMetrics
= aScrollMetadata
.GetMetrics();
5326 if ((aScrollMetadata
== mLastContentPaintMetadata
) && !isDefault
) {
5327 // No new information here, skip it.
5328 APZC_LOGV("%p NotifyLayersUpdated short-circuit\n", this);
5332 // If the Metrics scroll offset is different from the last scroll offset
5333 // that the main-thread sent us, then we know that the user has been doing
5334 // something that triggers a scroll. This check is the APZ equivalent of the
5335 // check on the main-thread at
5336 // https://hg.mozilla.org/mozilla-central/file/97a52326b06a/layout/generic/nsGfxScrollFrame.cpp#l4050
5337 // There is code below (the use site of userScrolled) that prevents a
5338 // restored- scroll-position update from overwriting a user scroll, again
5339 // equivalent to how the main thread code does the same thing.
5340 // XXX Suspicious comparison between layout and visual scroll offsets.
5341 // This may not do the right thing when we're zoomed in.
5342 CSSPoint lastScrollOffset
= mLastContentPaintMetrics
.GetLayoutScrollOffset();
5344 !FuzzyEqualsCoordinate(Metrics().GetVisualScrollOffset().x
,
5345 lastScrollOffset
.x
) ||
5346 !FuzzyEqualsCoordinate(Metrics().GetVisualScrollOffset().y
,
5347 lastScrollOffset
.y
);
5349 if (aScrollMetadata
.DidContentGetPainted()) {
5350 mLastContentPaintMetadata
= aScrollMetadata
;
5353 mScrollMetadata
.SetScrollParentId(aScrollMetadata
.GetScrollParentId());
5354 APZC_LOGV_FM(aLayerMetrics
,
5355 "%p got a NotifyLayersUpdated with aIsFirstPaint=%d, "
5356 "aThisLayerTreeUpdated=%d",
5357 this, aIsFirstPaint
, aThisLayerTreeUpdated
);
5360 MutexAutoLock
lock(mCheckerboardEventLock
);
5361 if (mCheckerboardEvent
&& mCheckerboardEvent
->IsRecordingTrace()) {
5363 if (aThisLayerTreeUpdated
) {
5364 if (!aLayerMetrics
.GetPaintRequestTime().IsNull()) {
5365 // Note that we might get the paint request time as non-null, but with
5366 // aThisLayerTreeUpdated false. That can happen if we get a layer
5367 // transaction from a different process right after we get the layer
5368 // transaction with aThisLayerTreeUpdated == true. In this case we
5369 // want to ignore the paint request time because it was already dumped
5370 // in the previous layer transaction.
5371 TimeDuration paintTime
=
5372 TimeStamp::Now() - aLayerMetrics
.GetPaintRequestTime();
5373 std::stringstream info
;
5374 info
<< " painttime " << paintTime
.ToMilliseconds();
5377 // This might be indicative of a wasted paint particularly if it
5378 // happens during a checkerboard event.
5379 str
= " (this layertree updated)";
5382 mCheckerboardEvent
->UpdateRendertraceProperty(
5383 CheckerboardEvent::Page
, aLayerMetrics
.GetScrollableRect());
5384 mCheckerboardEvent
->UpdateRendertraceProperty(
5385 CheckerboardEvent::PaintedDisplayPort
, GetPaintedRect(aLayerMetrics
),
5390 // The main thread may send us a visual scroll offset update. This is
5391 // different from a layout viewport offset update in that the layout viewport
5392 // offset is limited to the layout scroll range, while the visual viewport
5394 // However, there are some conditions in which the layout update will clobber
5395 // the visual update, and we want to ignore the visual update in those cases.
5396 // This variable tracks that.
5397 bool ignoreVisualUpdate
= false;
5399 // TODO if we're in a drag and scrollOffsetUpdated is set then we want to
5402 bool needContentRepaint
= false;
5403 RepaintUpdateType contentRepaintType
= RepaintUpdateType::eNone
;
5404 bool viewportSizeUpdated
= false;
5405 bool needToReclampScroll
= false;
5407 if ((aIsFirstPaint
&& aThisLayerTreeUpdated
) || isDefault
||
5408 Metrics().IsRootContent() != aLayerMetrics
.IsRootContent()) {
5409 if (Metrics().IsRootContent() && !aLayerMetrics
.IsRootContent()) {
5410 // We only support zooming on root content APZCs
5411 SetZoomAnimationId(Nothing());
5414 // Initialize our internal state to something sane when the content
5415 // that was just painted is something we knew nothing about previously
5418 // Keep our existing scroll generation, if there are scroll updates. In this
5419 // case we'll update our scroll generation when processing the scroll update
5420 // array below. If there are no scroll updates, take the generation from the
5421 // incoming metrics. Bug 1662019 will simplify this later.
5422 ScrollGeneration oldScrollGeneration
= Metrics().GetScrollGeneration();
5423 mScrollMetadata
= aScrollMetadata
;
5424 if (!aScrollMetadata
.GetScrollUpdates().IsEmpty()) {
5425 Metrics().SetScrollGeneration(oldScrollGeneration
);
5428 mExpectedGeckoMetrics
.UpdateFrom(aLayerMetrics
);
5430 for (auto& sampledState
: mSampledState
) {
5431 sampledState
.UpdateScrollProperties(Metrics());
5432 sampledState
.UpdateZoomProperties(Metrics());
5435 if (aLayerMetrics
.HasNonZeroDisplayPortMargins()) {
5436 // A non-zero display port margin here indicates a displayport has
5437 // been set by a previous APZC for the content at this guid. The
5438 // scrollable rect may have changed since then, making the margins
5439 // wrong, so we need to calculate a new display port.
5440 // It is important that we request a repaint here only when we need to
5441 // otherwise we will end up setting a display port on every frame that
5443 APZC_LOG("%p detected non-empty margins which probably need updating\n",
5445 needContentRepaint
= true;
5448 // If we're not taking the aLayerMetrics wholesale we still need to pull
5449 // in some things into our local Metrics() because these things are
5450 // determined by Gecko and our copy in Metrics() may be stale.
5452 if (Metrics().GetLayoutViewport().Size() !=
5453 aLayerMetrics
.GetLayoutViewport().Size()) {
5454 CSSRect layoutViewport
= Metrics().GetLayoutViewport();
5455 // The offset will be updated if necessary via
5456 // RecalculateLayoutViewportOffset().
5457 layoutViewport
.SizeTo(aLayerMetrics
.GetLayoutViewport().Size());
5458 Metrics().SetLayoutViewport(layoutViewport
);
5460 needContentRepaint
= true;
5461 viewportSizeUpdated
= true;
5464 // TODO: Rely entirely on |aScrollMetadata.IsResolutionUpdated()| to
5465 // determine which branch to take, and drop the other conditions.
5466 CSSToParentLayerScale oldZoom
= Metrics().GetZoom();
5467 if (FuzzyEqualsAdditive(
5468 Metrics().GetCompositionBoundsWidthIgnoringScrollbars(),
5469 aLayerMetrics
.GetCompositionBoundsWidthIgnoringScrollbars()) &&
5470 Metrics().GetDevPixelsPerCSSPixel() ==
5471 aLayerMetrics
.GetDevPixelsPerCSSPixel() &&
5472 !viewportSizeUpdated
&& !aScrollMetadata
.IsResolutionUpdated()) {
5473 // Any change to the pres shell resolution was requested by APZ and is
5474 // already included in our zoom; however, other components of the
5475 // cumulative resolution (a parent document's pres-shell resolution, or
5476 // the css-driven resolution) may have changed, and we need to update
5477 // our zoom to reflect that. Note that we can't just take
5478 // aLayerMetrics.mZoom because the APZ may have additional async zoom
5479 // since the repaint request.
5480 float totalResolutionChange
= 1.0;
5482 if (Metrics().GetCumulativeResolution() != LayoutDeviceToLayerScale(0)) {
5483 totalResolutionChange
= aLayerMetrics
.GetCumulativeResolution().scale
/
5484 Metrics().GetCumulativeResolution().scale
;
5487 float presShellResolutionChange
= aLayerMetrics
.GetPresShellResolution() /
5488 Metrics().GetPresShellResolution();
5489 if (presShellResolutionChange
!= 1.0f
) {
5490 needContentRepaint
= true;
5492 Metrics().ZoomBy(totalResolutionChange
/ presShellResolutionChange
);
5493 for (auto& sampledState
: mSampledState
) {
5494 sampledState
.ZoomBy(totalResolutionChange
/ presShellResolutionChange
);
5497 // Take the new zoom as either device scale or composition width or
5498 // viewport size got changed (e.g. due to orientation change, or content
5499 // changing the meta-viewport tag), or the main thread originated a
5500 // resolution change for another reason (e.g. Ctrl+0 was pressed to
5502 Metrics().SetZoom(aLayerMetrics
.GetZoom());
5503 for (auto& sampledState
: mSampledState
) {
5504 sampledState
.UpdateZoomProperties(aLayerMetrics
);
5506 Metrics().SetDevPixelsPerCSSPixel(
5507 aLayerMetrics
.GetDevPixelsPerCSSPixel());
5510 if (Metrics().GetZoom() != oldZoom
) {
5511 // If the zoom changed, the scroll range in CSS pixels may have changed
5512 // even if the composition bounds didn't.
5513 needToReclampScroll
= true;
5516 mExpectedGeckoMetrics
.UpdateZoomFrom(aLayerMetrics
);
5518 if (!Metrics().GetScrollableRect().IsEqualEdges(
5519 aLayerMetrics
.GetScrollableRect())) {
5520 Metrics().SetScrollableRect(aLayerMetrics
.GetScrollableRect());
5521 needContentRepaint
= true;
5522 needToReclampScroll
= true;
5524 if (!Metrics().GetCompositionBounds().IsEqualEdges(
5525 aLayerMetrics
.GetCompositionBounds())) {
5526 Metrics().SetCompositionBounds(aLayerMetrics
.GetCompositionBounds());
5527 needToReclampScroll
= true;
5529 Metrics().SetCompositionBoundsWidthIgnoringScrollbars(
5530 aLayerMetrics
.GetCompositionBoundsWidthIgnoringScrollbars());
5532 if (Metrics().IsRootContent() &&
5533 Metrics().GetCompositionSizeWithoutDynamicToolbar() !=
5534 aLayerMetrics
.GetCompositionSizeWithoutDynamicToolbar()) {
5535 Metrics().SetCompositionSizeWithoutDynamicToolbar(
5536 aLayerMetrics
.GetCompositionSizeWithoutDynamicToolbar());
5537 needToReclampScroll
= true;
5539 Metrics().SetBoundingCompositionSize(
5540 aLayerMetrics
.GetBoundingCompositionSize());
5541 Metrics().SetPresShellResolution(aLayerMetrics
.GetPresShellResolution());
5542 Metrics().SetCumulativeResolution(aLayerMetrics
.GetCumulativeResolution());
5543 Metrics().SetTransformToAncestorScale(
5544 aLayerMetrics
.GetTransformToAncestorScale());
5545 mScrollMetadata
.SetHasScrollgrab(aScrollMetadata
.GetHasScrollgrab());
5546 mScrollMetadata
.SetLineScrollAmount(aScrollMetadata
.GetLineScrollAmount());
5547 mScrollMetadata
.SetPageScrollAmount(aScrollMetadata
.GetPageScrollAmount());
5548 mScrollMetadata
.SetSnapInfo(ScrollSnapInfo(aScrollMetadata
.GetSnapInfo()));
5549 mScrollMetadata
.SetIsLayersIdRoot(aScrollMetadata
.IsLayersIdRoot());
5550 mScrollMetadata
.SetIsAutoDirRootContentRTL(
5551 aScrollMetadata
.IsAutoDirRootContentRTL());
5552 Metrics().SetIsScrollInfoLayer(aLayerMetrics
.IsScrollInfoLayer());
5553 Metrics().SetHasNonZeroDisplayPortMargins(
5554 aLayerMetrics
.HasNonZeroDisplayPortMargins());
5555 Metrics().SetMinimalDisplayPort(aLayerMetrics
.IsMinimalDisplayPort());
5556 mScrollMetadata
.SetForceDisableApz(aScrollMetadata
.IsApzForceDisabled());
5557 mScrollMetadata
.SetIsRDMTouchSimulationActive(
5558 aScrollMetadata
.GetIsRDMTouchSimulationActive());
5559 mScrollMetadata
.SetForceMousewheelAutodir(
5560 aScrollMetadata
.ForceMousewheelAutodir());
5561 mScrollMetadata
.SetForceMousewheelAutodirHonourRoot(
5562 aScrollMetadata
.ForceMousewheelAutodirHonourRoot());
5563 mScrollMetadata
.SetIsPaginatedPresentation(
5564 aScrollMetadata
.IsPaginatedPresentation());
5565 mScrollMetadata
.SetDisregardedDirection(
5566 aScrollMetadata
.GetDisregardedDirection());
5567 mScrollMetadata
.SetOverscrollBehavior(
5568 aScrollMetadata
.GetOverscrollBehavior());
5571 if (needToReclampScroll
) {
5572 // Whenever scrollable rect or composition bounds has changed, we need to
5573 // re-clamp the scroll offset since it may be out of bounds. Also note that
5574 // we need to re-clamp before updating new scroll offsets from content since
5575 // we will use the last scroll offset to reflect the new offsets.
5576 ClampAndSetVisualScrollOffset(Metrics().GetVisualScrollOffset());
5577 for (auto& sampledState
: mSampledState
) {
5578 sampledState
.ClampVisualScrollOffset(Metrics());
5582 bool instantScrollMayTriggerTransform
= false;
5583 bool scrollOffsetUpdated
= false;
5584 bool smoothScrollRequested
= false;
5585 bool didCancelAnimation
= false;
5586 Maybe
<CSSPoint
> cumulativeRelativeDelta
;
5587 for (const auto& scrollUpdate
: aScrollMetadata
.GetScrollUpdates()) {
5588 APZC_LOG("%p processing scroll update %s\n", this,
5589 ToString(scrollUpdate
).c_str());
5590 if (!(Metrics().GetScrollGeneration() < scrollUpdate
.GetGeneration())) {
5591 // This is stale, let's ignore it
5592 APZC_LOG("%p scrollupdate generation stale, dropping\n", this);
5595 Metrics().SetScrollGeneration(scrollUpdate
.GetGeneration());
5597 MOZ_ASSERT(scrollUpdate
.GetOrigin() != ScrollOrigin::Apz
);
5599 !nsLayoutUtils::CanScrollOriginClobberApz(scrollUpdate
.GetOrigin())) {
5600 APZC_LOG("%p scrollupdate cannot clobber APZ userScrolled\n", this);
5603 // XXX: if we get here, |scrollUpdate| is clobbering APZ, so we may want
5604 // to reset |userScrolled| back to false so that subsequent scrollUpdates
5605 // in this loop don't get dropped by the check above. Need to add a test
5606 // that exercises this scenario, as we don't currently have one.
5608 if (scrollUpdate
.GetMode() == ScrollMode::Smooth
||
5609 scrollUpdate
.GetMode() == ScrollMode::SmoothMsd
) {
5610 smoothScrollRequested
= true;
5612 // Requests to animate the visual scroll position override requests to
5613 // simply update the visual scroll offset to a particular point. Since
5614 // we have an animation request, we set ignoreVisualUpdate to true to
5615 // indicate we don't need to apply the visual scroll update in
5617 ignoreVisualUpdate
= true;
5619 // For relative updates we want to add the relative offset to any existing
5620 // destination, or the current visual offset if there is no existing
5622 CSSPoint base
= GetCurrentAnimationDestination(lock
).valueOr(
5623 Metrics().GetVisualScrollOffset());
5625 CSSPoint destination
;
5626 if (scrollUpdate
.GetType() == ScrollUpdateType::Relative
) {
5628 scrollUpdate
.GetDestination() - scrollUpdate
.GetSource();
5629 APZC_LOG("%p relative smooth scrolling from %s by %s\n", this,
5630 ToString(base
).c_str(), ToString(delta
).c_str());
5631 destination
= Metrics().CalculateScrollRange().ClampPoint(base
+ delta
);
5632 } else if (scrollUpdate
.GetType() == ScrollUpdateType::PureRelative
) {
5633 CSSPoint delta
= scrollUpdate
.GetDelta();
5634 APZC_LOG("%p pure-relative smooth scrolling from %s by %s\n", this,
5635 ToString(base
).c_str(), ToString(delta
).c_str());
5636 destination
= Metrics().CalculateScrollRange().ClampPoint(base
+ delta
);
5638 APZC_LOG("%p smooth scrolling to %s\n", this,
5639 ToString(scrollUpdate
.GetDestination()).c_str());
5640 destination
= scrollUpdate
.GetDestination();
5643 if (scrollUpdate
.GetMode() == ScrollMode::SmoothMsd
) {
5645 CSSSnapDestination
{destination
, scrollUpdate
.GetSnapTargetIds()},
5646 scrollUpdate
.GetScrollTriggeredByScript());
5648 MOZ_ASSERT(scrollUpdate
.GetMode() == ScrollMode::Smooth
);
5650 CSSSnapDestination
{destination
, scrollUpdate
.GetSnapTargetIds()},
5651 scrollUpdate
.GetScrollTriggeredByScript(),
5652 scrollUpdate
.GetOrigin());
5657 MOZ_ASSERT(scrollUpdate
.GetMode() == ScrollMode::Instant
||
5658 scrollUpdate
.GetMode() == ScrollMode::Normal
);
5660 instantScrollMayTriggerTransform
=
5661 scrollUpdate
.GetMode() == ScrollMode::Instant
&&
5662 scrollUpdate
.GetScrollTriggeredByScript() ==
5663 ScrollTriggeredByScript::No
;
5665 // If the layout update is of a higher priority than the visual update, then
5666 // we don't want to apply the visual update.
5667 // If the layout update is of a clobbering type (or a smooth scroll request,
5668 // which is handled above) then it takes precedence over an eRestore visual
5669 // update. But we also allow the possibility for the main thread to ask us
5670 // to scroll both the layout and visual viewports to distinct (but
5671 // compatible) locations (via e.g. both updates being of a non-clobbering/
5673 if (nsLayoutUtils::CanScrollOriginClobberApz(scrollUpdate
.GetOrigin()) &&
5674 aLayerMetrics
.GetVisualScrollUpdateType() !=
5675 FrameMetrics::eMainThread
) {
5676 ignoreVisualUpdate
= true;
5679 Maybe
<CSSPoint
> relativeDelta
;
5680 if (scrollUpdate
.GetType() == ScrollUpdateType::Relative
) {
5682 "%p relative updating scroll offset from %s by %s\n", this,
5683 ToString(Metrics().GetVisualScrollOffset()).c_str(),
5684 ToString(scrollUpdate
.GetDestination() - scrollUpdate
.GetSource())
5687 scrollOffsetUpdated
= true;
5689 // It's possible that the main thread has ignored an APZ scroll offset
5690 // update for the pending relative scroll that we have just received.
5691 // When this happens, we need to send a new scroll offset update with
5692 // the combined scroll offset or else the main thread may have an
5693 // incorrect scroll offset for a period of time.
5694 if (Metrics().HasPendingScroll(aLayerMetrics
)) {
5695 needContentRepaint
= true;
5696 contentRepaintType
= RepaintUpdateType::eUserAction
;
5700 Some(Metrics().ApplyRelativeScrollUpdateFrom(scrollUpdate
));
5701 Metrics().RecalculateLayoutViewportOffset();
5702 } else if (scrollUpdate
.GetType() == ScrollUpdateType::PureRelative
) {
5703 APZC_LOG("%p pure-relative updating scroll offset from %s by %s\n", this,
5704 ToString(Metrics().GetVisualScrollOffset()).c_str(),
5705 ToString(scrollUpdate
.GetDelta()).c_str());
5707 scrollOffsetUpdated
= true;
5709 // Always need a repaint request with a repaint type for pure relative
5710 // scrolls because apz is doing the scroll at the main thread's request.
5711 // The main thread has not updated it's scroll offset yet, it is depending
5712 // on apz to tell it where to scroll.
5713 needContentRepaint
= true;
5714 contentRepaintType
= RepaintUpdateType::eVisualUpdate
;
5716 // We have to ignore a visual scroll offset update otherwise it will
5717 // clobber the relative scrolling we are about to do. We perform
5718 // visualScrollOffset = visualScrollOffset + delta. Then the
5719 // visualScrollOffsetUpdated block below will do visualScrollOffset =
5720 // aLayerMetrics.GetVisualDestination(). We need visual scroll offset
5721 // updates to be incorporated into this scroll update loop to properly fix
5723 ignoreVisualUpdate
= true;
5726 Some(Metrics().ApplyPureRelativeScrollUpdateFrom(scrollUpdate
));
5727 Metrics().RecalculateLayoutViewportOffset();
5728 } else if (scrollUpdate
.GetType() == ScrollUpdateType::MergeableAbsolute
) {
5729 APZC_LOG("%p mergeable updating scroll offset from %s to %s\n", this,
5730 ToString(Metrics().GetVisualScrollOffset()).c_str(),
5731 ToString(scrollUpdate
.GetDestination()).c_str());
5733 Some(Metrics().ApplyAbsoluteScrollUpdateFrom(scrollUpdate
).second
);
5734 Metrics().RecalculateLayoutViewportOffset();
5735 scrollOffsetUpdated
= true;
5737 APZC_LOG("%p updating scroll offset from %s to %s\n", this,
5738 ToString(Metrics().GetVisualScrollOffset()).c_str(),
5739 ToString(scrollUpdate
.GetDestination()).c_str());
5740 auto [offsetChanged
, _
] =
5741 Metrics().ApplyAbsoluteScrollUpdateFrom(scrollUpdate
);
5742 Metrics().RecalculateLayoutViewportOffset();
5744 if (offsetChanged
|| scrollUpdate
.GetMode() != ScrollMode::Instant
||
5745 scrollUpdate
.GetType() != ScrollUpdateType::Absolute
||
5746 scrollUpdate
.GetOrigin() != ScrollOrigin::None
) {
5747 // We get a NewScrollFrame update for newly created scroll frames. Only
5748 // if this was not a NewScrollFrame update or the offset changed do we
5749 // request repaint. This is important so that we don't request repaint
5750 // for every new content and set a full display port on it.
5751 scrollOffsetUpdated
= true;
5755 if (relativeDelta
) {
5756 cumulativeRelativeDelta
=
5757 !cumulativeRelativeDelta
5759 : Some(*cumulativeRelativeDelta
+ *relativeDelta
);
5761 // If the scroll update is not relative, clobber the cumulative delta,
5762 // i.e. later updates win.
5763 cumulativeRelativeDelta
.reset();
5766 // If an animation is underway, tell it about the scroll offset update.
5767 // Some animations can handle some scroll offset updates and continue
5768 // running. Those that can't will return false, and we cancel them.
5769 if (ShouldCancelAnimationForScrollUpdate(relativeDelta
)) {
5770 // Cancel the animation (which might also trigger a repaint request)
5771 // after we update the scroll offset above. Otherwise we can be left
5772 // in a state where things are out of sync.
5774 didCancelAnimation
= true;
5778 if (scrollOffsetUpdated
) {
5779 for (auto& sampledState
: mSampledState
) {
5780 if (!didCancelAnimation
&& cumulativeRelativeDelta
.isSome()) {
5781 sampledState
.UpdateScrollPropertiesWithRelativeDelta(
5782 Metrics(), *cumulativeRelativeDelta
);
5784 sampledState
.UpdateScrollProperties(Metrics());
5788 // Because of the scroll generation update, any inflight paint requests
5789 // are going to be ignored by layout, and so mExpectedGeckoMetrics becomes
5790 // incorrect for the purposes of calculating the LD transform. To correct
5791 // this we need to update mExpectedGeckoMetrics to be the last thing we
5792 // know was painted by Gecko.
5793 mExpectedGeckoMetrics
.UpdateFrom(aLayerMetrics
);
5795 // Since the scroll offset has changed, we need to recompute the
5796 // displayport margins and send them to layout. Otherwise there might be
5797 // scenarios where for example we scroll from the top of a page (where the
5798 // top displayport margin is zero) to the bottom of a page, which will
5799 // result in a displayport that doesn't extend upwards at all.
5800 // Note that even if the CancelAnimation call above requested a repaint
5801 // this is fine because we already have repaint request deduplication.
5802 needContentRepaint
= true;
5803 // Since the main-thread scroll offset changed we should trigger a
5804 // recomposite to make sure it becomes user-visible.
5805 ScheduleComposite();
5807 // If the scroll offset was updated, we're not in a transforming state,
5808 // and we are scrolling by a non-zero delta, we should ensure
5809 // TransformBegin and TransformEnd notifications are sent.
5810 if (!IsTransformingState(mState
) && instantScrollMayTriggerTransform
&&
5811 cumulativeRelativeDelta
&& *cumulativeRelativeDelta
!= CSSPoint() &&
5812 !didCancelAnimation
) {
5813 SendTransformBeginAndEnd();
5817 // If our scroll range changed (for example, because the page dynamically
5818 // loaded new content, thereby increasing the size of the scrollable rect),
5819 // and we're overscrolled, being overscrolled may no longer be a valid
5820 // state (for example, we may no longer be at the edge of our scroll range),
5821 // so clear overscroll and discontinue any overscroll animation.
5822 // Ideas for improvements here:
5823 // - Instead of collapsing the overscroll gutter, try to "fill it"
5824 // with newly loaded content. This would basically entail checking
5825 // if (GetVisualScrollOffset() + GetOverscrollAmount()) is a valid
5826 // visual scroll offset in our new scroll range, and if so, scrolling
5828 if (needToReclampScroll
) {
5829 if (IsInInvalidOverscroll()) {
5830 if (mState
== OVERSCROLL_ANIMATION
) {
5832 } else if (IsOverscrolled()) {
5838 if (smoothScrollRequested
&& !scrollOffsetUpdated
) {
5839 mExpectedGeckoMetrics
.UpdateFrom(aLayerMetrics
);
5840 // Need to acknowledge the request.
5841 needContentRepaint
= true;
5844 // If `isDefault` is true, this APZC is a "new" one (this is the first time
5845 // it's getting a NotifyLayersUpdated call). In this case we want to apply the
5846 // visual scroll offset from the main thread to our scroll offset.
5847 // The main thread may also ask us to scroll the visual viewport to a
5848 // particular location. However, in all cases, we want to ignore the visual
5849 // offset update if ignoreVisualUpdate is true, because we're clobbering
5850 // the visual update with a layout update.
5851 bool visualScrollOffsetUpdated
=
5852 !ignoreVisualUpdate
&&
5854 aLayerMetrics
.GetVisualScrollUpdateType() != FrameMetrics::eNone
);
5856 if (visualScrollOffsetUpdated
) {
5857 APZC_LOG("%p updating visual scroll offset from %s to %s (updateType %d)\n",
5858 this, ToString(Metrics().GetVisualScrollOffset()).c_str(),
5859 ToString(aLayerMetrics
.GetVisualDestination()).c_str(),
5860 (int)aLayerMetrics
.GetVisualScrollUpdateType());
5861 bool offsetChanged
= Metrics().ClampAndSetVisualScrollOffset(
5862 aLayerMetrics
.GetVisualDestination());
5864 // If this is the first time we got metrics for this content (isDefault) and
5865 // the update type was none and the offset didn't change then we don't have
5866 // to do anything. This is important because we don't want to request
5867 // repaint on the initial NotifyLayersUpdated for every content and thus set
5868 // a full display port.
5869 if (aLayerMetrics
.GetVisualScrollUpdateType() == FrameMetrics::eNone
&&
5871 visualScrollOffsetUpdated
= false;
5874 if (visualScrollOffsetUpdated
) {
5875 // The rest of this branch largely follows the code in the
5876 // |if (scrollOffsetUpdated)| branch above. Eventually it should get
5877 // merged into that branch.
5878 Metrics().RecalculateLayoutViewportOffset();
5879 for (auto& sampledState
: mSampledState
) {
5880 sampledState
.UpdateScrollProperties(Metrics());
5882 mExpectedGeckoMetrics
.UpdateFrom(aLayerMetrics
);
5883 if (ShouldCancelAnimationForScrollUpdate(Nothing())) {
5886 // The main thread did not actually paint a displayport at the target
5887 // visual offset, so we need to ask it to repaint. We need to set the
5888 // contentRepaintType to something other than eNone, otherwise the main
5889 // thread will short-circuit the repaint request.
5890 // Don't do this for eRestore visual updates as a repaint coming from APZ
5891 // breaks the scroll offset restoration mechanism.
5892 needContentRepaint
= true;
5893 if (aLayerMetrics
.GetVisualScrollUpdateType() ==
5894 FrameMetrics::eMainThread
) {
5895 contentRepaintType
= RepaintUpdateType::eVisualUpdate
;
5897 ScheduleComposite();
5900 if (viewportSizeUpdated
) {
5901 // While we want to accept the main thread's layout viewport _size_,
5902 // its position may be out of date in light of async scrolling, to
5903 // adjust it if necessary to make sure it continues to enclose the
5905 // Note: it's important to do this _after_ we've accepted any
5906 // updated composition bounds.
5907 Metrics().RecalculateLayoutViewportOffset();
5910 if (needContentRepaint
) {
5911 // This repaint request could be driven by a user action if we accept a
5912 // relative scroll offset update
5913 RequestContentRepaint(contentRepaintType
);
5917 FrameMetrics
& AsyncPanZoomController::Metrics() {
5918 mRecursiveMutex
.AssertCurrentThreadIn();
5919 return mScrollMetadata
.GetMetrics();
5922 const FrameMetrics
& AsyncPanZoomController::Metrics() const {
5923 mRecursiveMutex
.AssertCurrentThreadIn();
5924 return mScrollMetadata
.GetMetrics();
5927 GeckoViewMetrics
AsyncPanZoomController::GetGeckoViewMetrics() const {
5928 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
5929 return GeckoViewMetrics
{GetEffectiveScrollOffset(eForCompositing
, lock
),
5930 GetEffectiveZoom(eForCompositing
, lock
)};
5933 bool AsyncPanZoomController::UpdateRootFrameMetricsIfChanged(
5934 GeckoViewMetrics
& aMetrics
) {
5935 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
5937 if (!Metrics().IsRootContent()) {
5941 GeckoViewMetrics newMetrics
= GetGeckoViewMetrics();
5942 bool hasChanged
= RoundedToInt(aMetrics
.mVisualScrollOffset
) !=
5943 RoundedToInt(newMetrics
.mVisualScrollOffset
) ||
5944 aMetrics
.mZoom
!= newMetrics
.mZoom
;
5947 aMetrics
= newMetrics
;
5953 const FrameMetrics
& AsyncPanZoomController::GetFrameMetrics() const {
5957 const ScrollMetadata
& AsyncPanZoomController::GetScrollMetadata() const {
5958 mRecursiveMutex
.AssertCurrentThreadIn();
5959 return mScrollMetadata
;
5962 void AsyncPanZoomController::AssertOnSamplerThread() const {
5963 if (APZCTreeManager
* treeManagerLocal
= GetApzcTreeManager()) {
5964 treeManagerLocal
->AssertOnSamplerThread();
5968 void AsyncPanZoomController::AssertOnUpdaterThread() const {
5969 if (APZCTreeManager
* treeManagerLocal
= GetApzcTreeManager()) {
5970 treeManagerLocal
->AssertOnUpdaterThread();
5974 APZCTreeManager
* AsyncPanZoomController::GetApzcTreeManager() const {
5975 mRecursiveMutex
.AssertNotCurrentThreadIn();
5976 return mTreeManager
;
5979 void AsyncPanZoomController::ZoomToRect(const ZoomTarget
& aZoomTarget
,
5980 const uint32_t aFlags
) {
5981 CSSRect rect
= aZoomTarget
.targetRect
;
5982 if (!rect
.IsFinite()) {
5983 NS_WARNING("ZoomToRect got called with a non-finite rect; ignoring...");
5987 if (rect
.IsEmpty() && (aFlags
& DISABLE_ZOOM_OUT
)) {
5988 // Double-tap-to-zooming uses an empty rect to mean "zoom out".
5989 // If zooming out is disabled, an empty rect is nonsensical
5990 // and will produce undesirable scrolling.
5992 "ZoomToRect got called with an empty rect and zoom out disabled; "
5997 SetState(ANIMATING_ZOOM
);
6000 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
6002 MOZ_ASSERT(Metrics().IsRootContent());
6004 const float defaultZoomInAmount
=
6005 StaticPrefs::apz_doubletapzoom_defaultzoomin();
6007 ParentLayerRect compositionBounds
= Metrics().GetCompositionBounds();
6008 CSSRect cssPageRect
= Metrics().GetScrollableRect();
6009 CSSPoint scrollOffset
= Metrics().GetVisualScrollOffset();
6010 CSSSize sizeBeforeZoom
= Metrics().CalculateCompositedSizeInCssPixels();
6011 CSSToParentLayerScale currentZoom
= Metrics().GetZoom();
6012 CSSToParentLayerScale targetZoom
;
6014 // The minimum zoom to prevent over-zoom-out.
6015 // If the zoom factor is lower than this (i.e. we are zoomed more into the
6016 // page), then the CSS content rect, in layers pixels, will be smaller than
6017 // the composition bounds. If this happens, we can't fill the target
6018 // composited area with this frame.
6019 const CSSRect cssExpandedPageRect
= Metrics().GetExpandedScrollableRect();
6020 CSSToParentLayerScale
localMinZoom(
6021 std::max(compositionBounds
.Width() / cssExpandedPageRect
.Width(),
6022 compositionBounds
.Height() / cssExpandedPageRect
.Height()));
6024 localMinZoom
.scale
=
6025 clamped(localMinZoom
.scale
, mZoomConstraints
.mMinZoom
.scale
,
6026 mZoomConstraints
.mMaxZoom
.scale
);
6028 localMinZoom
= std::max(mZoomConstraints
.mMinZoom
, localMinZoom
);
6029 CSSToParentLayerScale localMaxZoom
=
6030 std::max(localMinZoom
, mZoomConstraints
.mMaxZoom
);
6032 if (!rect
.IsEmpty()) {
6033 // Intersect the zoom-to-rect to the CSS rect to make sure it fits.
6034 rect
= rect
.Intersect(cssPageRect
);
6035 targetZoom
= CSSToParentLayerScale(
6036 std::min(compositionBounds
.Width() / rect
.Width(),
6037 compositionBounds
.Height() / rect
.Height()));
6038 if (aFlags
& DISABLE_ZOOM_OUT
) {
6039 targetZoom
= std::max(targetZoom
, currentZoom
);
6043 // 1. If the rect is empty, the content-side logic for handling a double-tap
6044 // requested that we zoom out.
6045 // 2. currentZoom is equal to mZoomConstraints.mMaxZoom and user still
6046 // double-tapping it
6047 // Treat these cases as a request to zoom out as much as possible
6048 // unless cantZoomOutBehavior == ZoomIn and currentZoom
6049 // is equal to localMinZoom and user still double-tapping it, then try to
6050 // zoom in a small amount to provide feedback to the user.
6051 bool zoomOut
= false;
6052 // True if we are already zoomed out and we are asked to either stay there
6053 // or zoom out more and cantZoomOutBehavior == ZoomIn.
6054 bool zoomInDefaultAmount
= false;
6055 if (aFlags
& DISABLE_ZOOM_OUT
) {
6058 if (rect
.IsEmpty()) {
6059 if (currentZoom
== localMinZoom
&&
6060 aZoomTarget
.cantZoomOutBehavior
== CantZoomOutBehavior::ZoomIn
&&
6061 (defaultZoomInAmount
!= 1.f
)) {
6062 zoomInDefaultAmount
= true;
6066 } else if (currentZoom
== localMaxZoom
&& targetZoom
>= localMaxZoom
) {
6071 // already at min zoom and asked to zoom out further
6072 if (!zoomOut
&& currentZoom
== localMinZoom
&& targetZoom
<= localMinZoom
&&
6073 aZoomTarget
.cantZoomOutBehavior
== CantZoomOutBehavior::ZoomIn
&&
6074 (defaultZoomInAmount
!= 1.f
)) {
6075 zoomInDefaultAmount
= true;
6077 MOZ_ASSERT(!(zoomInDefaultAmount
&& zoomOut
));
6079 if (zoomInDefaultAmount
) {
6081 CSSToParentLayerScale(currentZoom
.scale
* defaultZoomInAmount
);
6085 targetZoom
= localMinZoom
;
6088 if (aFlags
& PAN_INTO_VIEW_ONLY
) {
6089 targetZoom
= currentZoom
;
6090 } else if (aFlags
& ONLY_ZOOM_TO_DEFAULT_SCALE
) {
6091 CSSToParentLayerScale zoomAtDefaultScale
=
6092 Metrics().GetDevPixelsPerCSSPixel() *
6093 LayoutDeviceToParentLayerScale(1.0);
6094 if (targetZoom
.scale
> zoomAtDefaultScale
.scale
) {
6095 // Only change the zoom if we are less than the default zoom
6096 if (currentZoom
.scale
< zoomAtDefaultScale
.scale
) {
6097 targetZoom
= zoomAtDefaultScale
;
6099 targetZoom
= currentZoom
;
6105 clamped(targetZoom
.scale
, localMinZoom
.scale
, localMaxZoom
.scale
);
6107 FrameMetrics endZoomToMetrics
= Metrics();
6108 endZoomToMetrics
.SetZoom(CSSToParentLayerScale(targetZoom
));
6109 CSSSize sizeAfterZoom
=
6110 endZoomToMetrics
.CalculateCompositedSizeInCssPixels();
6112 if (zoomInDefaultAmount
|| zoomOut
) {
6113 // For the zoom out case we should always center what was visible
6114 // otherwise it feels like we are scrolling as well as zooming out. For
6115 // the non-zoomOut case, if we've been provided a pointer location, zoom
6116 // around that, otherwise just zoom in to the center of what's currently
6118 if (!zoomOut
&& aZoomTarget
.documentRelativePointerPosition
.isSome()) {
6119 rect
= CSSRect(aZoomTarget
.documentRelativePointerPosition
->x
-
6120 sizeAfterZoom
.width
/ 2,
6121 aZoomTarget
.documentRelativePointerPosition
->y
-
6122 sizeAfterZoom
.height
/ 2,
6123 sizeAfterZoom
.Width(), sizeAfterZoom
.Height());
6126 scrollOffset
.x
+ (sizeBeforeZoom
.width
- sizeAfterZoom
.width
) / 2,
6127 scrollOffset
.y
+ (sizeBeforeZoom
.height
- sizeAfterZoom
.height
) / 2,
6128 sizeAfterZoom
.Width(), sizeAfterZoom
.Height());
6131 rect
= rect
.Intersect(cssPageRect
);
6134 // Check if we can fit the full elementBoundingRect.
6135 if (!aZoomTarget
.targetRect
.IsEmpty() && !zoomOut
&&
6136 aZoomTarget
.elementBoundingRect
.isSome()) {
6137 MOZ_ASSERT(aZoomTarget
.elementBoundingRect
->Contains(rect
));
6138 CSSRect elementBoundingRect
=
6139 aZoomTarget
.elementBoundingRect
->Intersect(cssPageRect
);
6140 if (elementBoundingRect
.width
<= sizeAfterZoom
.width
&&
6141 elementBoundingRect
.height
<= sizeAfterZoom
.height
) {
6142 rect
= elementBoundingRect
;
6146 // Vertically center the zoomed element in the screen.
6147 if (!zoomOut
&& (sizeAfterZoom
.height
> rect
.Height())) {
6148 rect
.MoveByY(-(sizeAfterZoom
.height
- rect
.Height()) * 0.5f
);
6149 if (rect
.Y() < 0.0f
) {
6154 // Horizontally center the zoomed element in the screen.
6155 if (!zoomOut
&& (sizeAfterZoom
.width
> rect
.Width())) {
6156 rect
.MoveByX(-(sizeAfterZoom
.width
- rect
.Width()) * 0.5f
);
6157 if (rect
.X() < 0.0f
) {
6162 bool intersectRectAgain
= false;
6163 // If we can't zoom out enough to show the full rect then shift the rect we
6164 // are able to show to center what was visible.
6165 // Note that this calculation works no matter the relation of sizeBeforeZoom
6166 // to sizeAfterZoom, ie whether we are increasing or decreasing zoom.
6167 if (!zoomOut
&& (sizeAfterZoom
.height
< rect
.Height())) {
6169 scrollOffset
.y
+ (sizeBeforeZoom
.height
- sizeAfterZoom
.height
) / 2;
6170 rect
.height
= sizeAfterZoom
.Height();
6172 intersectRectAgain
= true;
6175 if (!zoomOut
&& (sizeAfterZoom
.width
< rect
.Width())) {
6177 scrollOffset
.x
+ (sizeBeforeZoom
.width
- sizeAfterZoom
.width
) / 2;
6178 rect
.width
= sizeAfterZoom
.Width();
6180 intersectRectAgain
= true;
6182 if (intersectRectAgain
) {
6183 rect
= rect
.Intersect(cssPageRect
);
6186 // If any of these conditions are met, the page will be overscrolled after
6187 // zoomed. Attempting to scroll outside of the valid scroll range will cause
6189 if (rect
.Y() + sizeAfterZoom
.height
> cssPageRect
.YMost()) {
6190 rect
.MoveToY(std::max(cssPageRect
.Y(),
6191 cssPageRect
.YMost() - sizeAfterZoom
.height
));
6193 if (rect
.Y() < cssPageRect
.Y()) {
6194 rect
.MoveToY(cssPageRect
.Y());
6196 if (rect
.X() + sizeAfterZoom
.width
> cssPageRect
.XMost()) {
6198 std::max(cssPageRect
.X(), cssPageRect
.XMost() - sizeAfterZoom
.width
));
6200 if (rect
.X() < cssPageRect
.X()) {
6201 rect
.MoveToY(cssPageRect
.X());
6204 endZoomToMetrics
.SetVisualScrollOffset(rect
.TopLeft());
6205 endZoomToMetrics
.RecalculateLayoutViewportOffset();
6207 StartAnimation(do_AddRef(new ZoomAnimation(
6208 *this, Metrics().GetVisualScrollOffset(), Metrics().GetZoom(),
6209 endZoomToMetrics
.GetVisualScrollOffset(), endZoomToMetrics
.GetZoom())));
6211 RequestContentRepaint(RepaintUpdateType::eUserAction
);
6215 InputBlockState
* AsyncPanZoomController::GetCurrentInputBlock() const {
6216 return GetInputQueue()->GetCurrentBlock();
6219 TouchBlockState
* AsyncPanZoomController::GetCurrentTouchBlock() const {
6220 return GetInputQueue()->GetCurrentTouchBlock();
6223 PanGestureBlockState
* AsyncPanZoomController::GetCurrentPanGestureBlock()
6225 return GetInputQueue()->GetCurrentPanGestureBlock();
6228 PinchGestureBlockState
* AsyncPanZoomController::GetCurrentPinchGestureBlock()
6230 return GetInputQueue()->GetCurrentPinchGestureBlock();
6233 void AsyncPanZoomController::ResetTouchInputState() {
6234 MultiTouchInput
cancel(MultiTouchInput::MULTITOUCH_CANCEL
, 0,
6235 TimeStamp::Now(), 0);
6236 RefPtr
<GestureEventListener
> listener
= GetGestureEventListener();
6238 listener
->HandleInputEvent(cancel
);
6240 CancelAnimationAndGestureState();
6241 // Clear overscroll along the entire handoff chain, in case an APZC
6242 // later in the chain is overscrolled.
6243 if (TouchBlockState
* block
= GetCurrentTouchBlock()) {
6244 block
->GetOverscrollHandoffChain()->ClearOverscroll();
6248 void AsyncPanZoomController::ResetPanGestureInputState() {
6249 // No point sending a PANGESTURE_INTERRUPTED as all it does is
6250 // call CancelAnimation(), which we also do here.
6251 CancelAnimationAndGestureState();
6252 // Clear overscroll along the entire handoff chain, in case an APZC
6253 // later in the chain is overscrolled.
6254 if (PanGestureBlockState
* block
= GetCurrentPanGestureBlock()) {
6255 block
->GetOverscrollHandoffChain()->ClearOverscroll();
6259 void AsyncPanZoomController::CancelAnimationAndGestureState() {
6262 CancelAnimation(CancelAnimationFlags::ScrollSnap
);
6265 bool AsyncPanZoomController::HasReadyTouchBlock() const {
6266 return GetInputQueue()->HasReadyTouchBlock();
6269 bool AsyncPanZoomController::CanHandleScrollOffsetUpdate(PanZoomState aState
) {
6270 return aState
== NOTHING
|| aState
== PAN_MOMENTUM
|| aState
== TOUCHING
||
6271 IsPanningState(aState
);
6274 bool AsyncPanZoomController::ShouldCancelAnimationForScrollUpdate(
6275 const Maybe
<CSSPoint
>& aRelativeDelta
) {
6276 // Never call CancelAnimation() for a no-op relative update.
6277 if (aRelativeDelta
== Some(CSSPoint())) {
6282 return !mAnimation
->HandleScrollOffsetUpdate(aRelativeDelta
);
6285 return !CanHandleScrollOffsetUpdate(mState
);
6288 AsyncPanZoomController::PanZoomState
6289 AsyncPanZoomController::SetStateNoContentControllerDispatch(
6290 PanZoomState aNewState
) {
6291 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
6292 APZC_LOG_DETAIL("changing from state %s to %s\n", this,
6293 ToString(mState
).c_str(), ToString(aNewState
).c_str());
6294 PanZoomState oldState
= mState
;
6299 void AsyncPanZoomController::SetState(PanZoomState aNewState
) {
6300 // When a state transition to a transforming state is occuring and a delayed
6301 // transform end notification exists, send the TransformEnd notification
6302 // before the TransformBegin notification is sent for the input state change.
6303 if (IsTransformingState(aNewState
) && IsDelayedTransformEndSet()) {
6304 MOZ_ASSERT(!IsTransformingState(mState
));
6305 SetDelayedTransformEnd(false);
6306 DispatchStateChangeNotification(PANNING
, NOTHING
);
6309 PanZoomState oldState
= SetStateNoContentControllerDispatch(aNewState
);
6311 DispatchStateChangeNotification(oldState
, aNewState
);
6314 auto AsyncPanZoomController::GetState() const -> PanZoomState
{
6315 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
6319 void AsyncPanZoomController::DispatchStateChangeNotification(
6320 PanZoomState aOldState
, PanZoomState aNewState
) {
6322 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
6323 if (mNotificationBlockers
> 0) {
6328 if (RefPtr
<GeckoContentController
> controller
= GetGeckoContentController()) {
6329 if (!IsTransformingState(aOldState
) && IsTransformingState(aNewState
)) {
6330 controller
->NotifyAPZStateChange(GetGuid(),
6331 APZStateChange::eTransformBegin
);
6332 } else if (IsTransformingState(aOldState
) &&
6333 !IsTransformingState(aNewState
)) {
6334 controller
->NotifyAPZStateChange(GetGuid(),
6335 APZStateChange::eTransformEnd
);
6339 void AsyncPanZoomController::SendTransformBeginAndEnd() {
6340 RefPtr
<GeckoContentController
> controller
= GetGeckoContentController();
6342 controller
->NotifyAPZStateChange(GetGuid(),
6343 APZStateChange::eTransformBegin
);
6344 controller
->NotifyAPZStateChange(GetGuid(), APZStateChange::eTransformEnd
);
6348 bool AsyncPanZoomController::IsInTransformingState() const {
6349 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
6350 return IsTransformingState(mState
);
6353 bool AsyncPanZoomController::IsTransformingState(PanZoomState aState
) {
6354 return !(aState
== NOTHING
|| aState
== TOUCHING
);
6357 bool AsyncPanZoomController::IsPanningState(PanZoomState aState
) {
6358 return (aState
== PANNING
|| aState
== PANNING_LOCKED_X
||
6359 aState
== PANNING_LOCKED_Y
);
6362 bool AsyncPanZoomController::IsInPanningState() const {
6363 return IsPanningState(mState
);
6366 bool AsyncPanZoomController::IsInScrollingGesture() const {
6367 return IsPanningState(mState
) || mState
== SCROLLBAR_DRAG
||
6368 mState
== TOUCHING
|| mState
== PINCHING
;
6371 bool AsyncPanZoomController::IsDelayedTransformEndSet() {
6372 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
6373 return mDelayedTransformEnd
;
6376 void AsyncPanZoomController::SetDelayedTransformEnd(bool aDelayedTransformEnd
) {
6377 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
6378 mDelayedTransformEnd
= aDelayedTransformEnd
;
6381 void AsyncPanZoomController::UpdateZoomConstraints(
6382 const ZoomConstraints
& aConstraints
) {
6383 if ((MOZ_LOG_TEST(sApzCtlLog
, LogLevel::Debug
) &&
6384 (aConstraints
!= mZoomConstraints
)) ||
6385 MOZ_LOG_TEST(sApzCtlLog
, LogLevel::Verbose
)) {
6386 APZC_LOG("%p updating zoom constraints to %d %d %f %f\n", this,
6387 aConstraints
.mAllowZoom
, aConstraints
.mAllowDoubleTapZoom
,
6388 aConstraints
.mMinZoom
.scale
, aConstraints
.mMaxZoom
.scale
);
6391 if (std::isnan(aConstraints
.mMinZoom
.scale
) ||
6392 std::isnan(aConstraints
.mMaxZoom
.scale
)) {
6393 NS_WARNING("APZC received zoom constraints with NaN values; dropping...");
6397 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
6398 CSSToParentLayerScale min
= Metrics().GetDevPixelsPerCSSPixel() *
6399 ViewportMinScale() / ParentLayerToScreenScale(1);
6400 CSSToParentLayerScale max
= Metrics().GetDevPixelsPerCSSPixel() *
6401 ViewportMaxScale() / ParentLayerToScreenScale(1);
6403 // inf float values and other bad cases should be sanitized by the code below.
6404 mZoomConstraints
.mAllowZoom
= aConstraints
.mAllowZoom
;
6405 mZoomConstraints
.mAllowDoubleTapZoom
= aConstraints
.mAllowDoubleTapZoom
;
6406 mZoomConstraints
.mMinZoom
=
6407 (min
> aConstraints
.mMinZoom
? min
: aConstraints
.mMinZoom
);
6408 mZoomConstraints
.mMaxZoom
=
6409 (max
> aConstraints
.mMaxZoom
? aConstraints
.mMaxZoom
: max
);
6410 if (mZoomConstraints
.mMaxZoom
< mZoomConstraints
.mMinZoom
) {
6411 mZoomConstraints
.mMaxZoom
= mZoomConstraints
.mMinZoom
;
6415 bool AsyncPanZoomController::ZoomConstraintsAllowZoom() const {
6416 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
6417 return mZoomConstraints
.mAllowZoom
;
6420 bool AsyncPanZoomController::ZoomConstraintsAllowDoubleTapZoom() const {
6421 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
6422 return mZoomConstraints
.mAllowDoubleTapZoom
;
6425 void AsyncPanZoomController::PostDelayedTask(already_AddRefed
<Runnable
> aTask
,
6427 APZThreadUtils::AssertOnControllerThread();
6428 RefPtr
<Runnable
> task
= aTask
;
6429 RefPtr
<GeckoContentController
> controller
= GetGeckoContentController();
6431 controller
->PostDelayedTask(task
.forget(), aDelayMs
);
6433 // If there is no controller, that means this APZC has been destroyed, and
6434 // we probably don't need to run the task. It will get destroyed when the
6435 // RefPtr goes out of scope.
6438 bool AsyncPanZoomController::Matches(const ScrollableLayerGuid
& aGuid
) {
6439 return aGuid
== GetGuid();
6442 bool AsyncPanZoomController::HasTreeManager(
6443 const APZCTreeManager
* aTreeManager
) const {
6444 return GetApzcTreeManager() == aTreeManager
;
6447 void AsyncPanZoomController::GetGuid(ScrollableLayerGuid
* aGuidOut
) const {
6449 *aGuidOut
= GetGuid();
6453 ScrollableLayerGuid
AsyncPanZoomController::GetGuid() const {
6454 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
6455 return ScrollableLayerGuid(mLayersId
, Metrics().GetPresShellId(),
6456 Metrics().GetScrollId());
6459 void AsyncPanZoomController::SetTestAsyncScrollOffset(const CSSPoint
& aPoint
) {
6460 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
6461 mTestAsyncScrollOffset
= aPoint
;
6462 ScheduleComposite();
6465 void AsyncPanZoomController::SetTestAsyncZoom(
6466 const LayerToParentLayerScale
& aZoom
) {
6467 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
6468 mTestAsyncZoom
= aZoom
;
6469 ScheduleComposite();
6472 Maybe
<CSSSnapDestination
> AsyncPanZoomController::FindSnapPointNear(
6473 const CSSPoint
& aDestination
, ScrollUnit aUnit
,
6474 ScrollSnapFlags aSnapFlags
) {
6475 mRecursiveMutex
.AssertCurrentThreadIn();
6476 APZC_LOG("%p scroll snapping near %s\n", this,
6477 ToString(aDestination
).c_str());
6478 CSSRect scrollRange
= Metrics().CalculateScrollRange();
6479 if (auto snapDestination
= ScrollSnapUtils::GetSnapPointForDestination(
6480 mScrollMetadata
.GetSnapInfo(), aUnit
, aSnapFlags
,
6481 CSSRect::ToAppUnits(scrollRange
),
6482 CSSPoint::ToAppUnits(Metrics().GetVisualScrollOffset()),
6483 CSSPoint::ToAppUnits(aDestination
))) {
6484 CSSPoint cssSnapPoint
= CSSPoint::FromAppUnits(snapDestination
->mPosition
);
6485 // GetSnapPointForDestination() can produce a destination that's outside
6486 // of the scroll frame's scroll range. Clamp it here (this matches the
6487 // behaviour of the main-thread code path, which clamps it in
6488 // nsGfxScrollFrame::ScrollTo()).
6489 return Some(CSSSnapDestination
{scrollRange
.ClampPoint(cssSnapPoint
),
6490 snapDestination
->mTargetIds
});
6495 Maybe
<std::pair
<MultiTouchInput
, MultiTouchInput
>>
6496 AsyncPanZoomController::MaybeSplitTouchMoveEvent(
6497 const MultiTouchInput
& aOriginalEvent
, ScreenCoord aPanThreshold
,
6498 float aVectorLength
, ExternalPoint
& aExtPoint
) {
6499 if (aVectorLength
<= aPanThreshold
) {
6503 auto splitEvent
= std::make_pair(aOriginalEvent
, aOriginalEvent
);
6505 SingleTouchData
& firstTouchData
= splitEvent
.first
.mTouches
[0];
6506 SingleTouchData
& secondTouchData
= splitEvent
.second
.mTouches
[0];
6508 firstTouchData
.mHistoricalData
.Clear();
6509 secondTouchData
.mHistoricalData
.Clear();
6511 ExternalPoint destination
= aExtPoint
;
6512 ExternalPoint thresholdPosition
;
6514 const float ratio
= aPanThreshold
/ aVectorLength
;
6515 thresholdPosition
.x
= mStartTouch
.x
+ ratio
* (destination
.x
- mStartTouch
.x
);
6516 thresholdPosition
.y
= mStartTouch
.y
+ ratio
* (destination
.y
- mStartTouch
.y
);
6518 TouchSample start
{mLastTouch
};
6519 // To compute the timestamp of the first event (which is at the threshold),
6520 // use linear interpolation with the starting point |start| being the last
6521 // event that's before the threshold, and the end point |end| being the first
6522 // event after the threshold.
6524 // The initial choice for |start| is the last touch event before
6525 // |aOriginalEvent|, and the initial choice for |end| is |aOriginalEvent|.
6527 // However, the historical data points stored in |aOriginalEvent| may contain
6528 // intermediate positions that can serve as tighter bounds for the
6530 TouchSample end
{destination
, aOriginalEvent
.mTimeStamp
};
6532 for (const auto& historicalData
:
6533 aOriginalEvent
.mTouches
[0].mHistoricalData
) {
6534 ExternalPoint histExtPoint
= ToExternalPoint(aOriginalEvent
.mScreenOffset
,
6535 historicalData
.mScreenPoint
);
6537 if (PanVector(histExtPoint
).Length() <
6538 PanVector(thresholdPosition
).Length()) {
6539 start
= {histExtPoint
, historicalData
.mTimeStamp
};
6545 for (const SingleTouchData::HistoricalTouchData
& histData
:
6546 Reversed(aOriginalEvent
.mTouches
[0].mHistoricalData
)) {
6547 ExternalPoint histExtPoint
=
6548 ToExternalPoint(aOriginalEvent
.mScreenOffset
, histData
.mScreenPoint
);
6550 if (PanVector(histExtPoint
).Length() >
6551 PanVector(thresholdPosition
).Length()) {
6552 end
= {histExtPoint
, histData
.mTimeStamp
};
6558 const float totalLength
=
6559 ScreenPoint(fabs(end
.mPosition
.x
- start
.mPosition
.x
),
6560 fabs(end
.mPosition
.y
- start
.mPosition
.y
))
6562 const float thresholdLength
=
6563 ScreenPoint(fabs(thresholdPosition
.x
- start
.mPosition
.x
),
6564 fabs(thresholdPosition
.y
- start
.mPosition
.y
))
6566 const float splitRatio
= thresholdLength
/ totalLength
;
6568 splitEvent
.first
.mTimeStamp
=
6570 (end
.mTimeStamp
- start
.mTimeStamp
).MultDouble(splitRatio
);
6572 for (const auto& historicalData
:
6573 aOriginalEvent
.mTouches
[0].mHistoricalData
) {
6574 if (historicalData
.mTimeStamp
> splitEvent
.first
.mTimeStamp
) {
6575 secondTouchData
.mHistoricalData
.AppendElement(historicalData
);
6577 firstTouchData
.mHistoricalData
.AppendElement(historicalData
);
6581 firstTouchData
.mScreenPoint
= RoundedToInt(
6582 ViewAs
<ScreenPixel
>(thresholdPosition
- splitEvent
.first
.mScreenOffset
,
6583 PixelCastJustification::ExternalIsScreen
));
6585 // Recompute firstTouchData.mLocalScreenPoint.
6586 splitEvent
.first
.TransformToLocal(GetTransformToThis());
6588 // Pass |thresholdPosition| back out to the caller via |aExtPoint|
6589 aExtPoint
= thresholdPosition
;
6591 return Some(splitEvent
);
6594 void AsyncPanZoomController::ScrollSnapNear(const CSSPoint
& aDestination
,
6595 ScrollSnapFlags aSnapFlags
) {
6596 if (Maybe
<CSSSnapDestination
> snapDestination
= FindSnapPointNear(
6597 aDestination
, ScrollUnit::DEVICE_PIXELS
, aSnapFlags
)) {
6598 if (snapDestination
->mPosition
!= Metrics().GetVisualScrollOffset()) {
6599 APZC_LOG("%p smooth scrolling to snap point %s\n", this,
6600 ToString(snapDestination
->mPosition
).c_str());
6601 SmoothMsdScrollTo(std::move(*snapDestination
),
6602 ScrollTriggeredByScript::No
);
6607 void AsyncPanZoomController::ScrollSnap(ScrollSnapFlags aSnapFlags
) {
6608 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
6609 ScrollSnapNear(Metrics().GetVisualScrollOffset(), aSnapFlags
);
6612 void AsyncPanZoomController::ScrollSnapToDestination() {
6613 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
6615 float friction
= StaticPrefs::apz_fling_friction();
6616 ParentLayerPoint
velocity(mX
.GetVelocity(), mY
.GetVelocity());
6617 ParentLayerPoint predictedDelta
;
6618 // "-velocity / log(1.0 - friction)" is the integral of the deceleration
6619 // curve modeled for flings in the "Axis" class.
6620 if (velocity
.x
!= 0.0f
&& friction
!= 0.0f
) {
6621 predictedDelta
.x
= -velocity
.x
/ log(1.0 - friction
);
6623 if (velocity
.y
!= 0.0f
&& friction
!= 0.0f
) {
6624 predictedDelta
.y
= -velocity
.y
/ log(1.0 - friction
);
6627 // If the fling will overscroll, don't scroll snap, because then the user
6628 // user would not see any overscroll animation.
6629 bool flingWillOverscroll
=
6630 IsOverscrolled() && ((velocity
.x
.value
* mX
.GetOverscroll() >= 0) ||
6631 (velocity
.y
.value
* mY
.GetOverscroll() >= 0));
6632 if (flingWillOverscroll
) {
6636 CSSPoint startPosition
= Metrics().GetVisualScrollOffset();
6637 ScrollSnapFlags snapFlags
= ScrollSnapFlags::IntendedEndPosition
;
6638 if (predictedDelta
!= ParentLayerPoint()) {
6639 snapFlags
|= ScrollSnapFlags::IntendedDirection
;
6641 if (Maybe
<CSSSnapDestination
> snapDestination
=
6642 MaybeAdjustDeltaForScrollSnapping(ScrollUnit::DEVICE_PIXELS
,
6643 snapFlags
, predictedDelta
,
6646 "%p fling snapping. friction: %f velocity: %f, %f "
6647 "predictedDelta: %f, %f position: %f, %f "
6648 "snapDestination: %f, %f\n",
6649 this, friction
, velocity
.x
.value
, velocity
.y
.value
,
6650 predictedDelta
.x
.value
, predictedDelta
.y
.value
,
6651 Metrics().GetVisualScrollOffset().x
.value
,
6652 Metrics().GetVisualScrollOffset().y
.value
, startPosition
.x
.value
,
6653 startPosition
.y
.value
);
6655 // Ensure that any queued transform-end due to a pan-end is not
6656 // sent. Instead rely on the transform-end sent due to the
6657 // scroll snap animation.
6658 SetDelayedTransformEnd(false);
6660 SmoothMsdScrollTo(std::move(*snapDestination
), ScrollTriggeredByScript::No
);
6664 Maybe
<CSSSnapDestination
>
6665 AsyncPanZoomController::MaybeAdjustDeltaForScrollSnapping(
6666 ScrollUnit aUnit
, ScrollSnapFlags aSnapFlags
, ParentLayerPoint
& aDelta
,
6667 CSSPoint
& aStartPosition
) {
6668 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
6669 CSSToParentLayerScale zoom
= Metrics().GetZoom();
6670 if (zoom
== CSSToParentLayerScale(0)) {
6673 CSSPoint destination
= Metrics().CalculateScrollRange().ClampPoint(
6674 aStartPosition
+ (aDelta
/ zoom
));
6676 if (Maybe
<CSSSnapDestination
> snapDestination
=
6677 FindSnapPointNear(destination
, aUnit
, aSnapFlags
)) {
6678 aDelta
= (snapDestination
->mPosition
- aStartPosition
) * zoom
;
6679 aStartPosition
= snapDestination
->mPosition
;
6680 return snapDestination
;
6685 Maybe
<CSSSnapDestination
>
6686 AsyncPanZoomController::MaybeAdjustDeltaForScrollSnappingOnWheelInput(
6687 const ScrollWheelInput
& aEvent
, ParentLayerPoint
& aDelta
,
6688 CSSPoint
& aStartPosition
) {
6689 // Don't scroll snap for pixel scrolls. This matches the main thread
6690 // behaviour in EventStateManager::DoScrollText().
6691 if (aEvent
.mDeltaType
== ScrollWheelInput::SCROLLDELTA_PIXEL
) {
6695 // Note that this MaybeAdjustDeltaForScrollSnappingOnWheelInput also gets
6696 // called for pan gestures at least on older Mac and Windows. In such cases
6697 // `aEvent.mDeltaType` is `SCROLLDELTA_PIXEL` which should be filtered out by
6698 // the above `if` block, so we assume all incoming `aEvent` are purely wheel
6699 // events, thus we basically use `IntendedDirection` here.
6700 // If we want to change the behavior, i.e. we want to do scroll snap for
6701 // such cases as well, we need to use `IntendedEndPoint`.
6702 ScrollSnapFlags snapFlags
= ScrollSnapFlags::IntendedDirection
;
6703 if (aEvent
.mDeltaType
== ScrollWheelInput::SCROLLDELTA_PAGE
) {
6704 // On Windows there are a couple of cases where scroll events happen with
6705 // SCROLLDELTA_PAGE, in such case we consider it's a page scroll.
6706 snapFlags
|= ScrollSnapFlags::IntendedEndPosition
;
6708 return MaybeAdjustDeltaForScrollSnapping(
6709 ScrollWheelInput::ScrollUnitForDeltaType(aEvent
.mDeltaType
),
6710 ScrollSnapFlags::IntendedDirection
, aDelta
, aStartPosition
);
6713 Maybe
<CSSSnapDestination
>
6714 AsyncPanZoomController::MaybeAdjustDestinationForScrollSnapping(
6715 const KeyboardInput
& aEvent
, CSSPoint
& aDestination
,
6716 ScrollSnapFlags aSnapFlags
) {
6717 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
6718 ScrollUnit unit
= KeyboardScrollAction::GetScrollUnit(aEvent
.mAction
.mType
);
6720 if (Maybe
<CSSSnapDestination
> snapPoint
=
6721 FindSnapPointNear(aDestination
, unit
, aSnapFlags
)) {
6722 aDestination
= snapPoint
->mPosition
;
6728 void AsyncPanZoomController::SetZoomAnimationId(
6729 const Maybe
<uint64_t>& aZoomAnimationId
) {
6730 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
6731 mZoomAnimationId
= aZoomAnimationId
;
6734 Maybe
<uint64_t> AsyncPanZoomController::GetZoomAnimationId() const {
6735 RecursiveMutexAutoLock
lock(mRecursiveMutex
);
6736 return mZoomAnimationId
;
6739 std::ostream
& operator<<(std::ostream
& aOut
,
6740 const AsyncPanZoomController::PanZoomState
& aState
) {
6742 case AsyncPanZoomController::PanZoomState::NOTHING
:
6745 case AsyncPanZoomController::PanZoomState::FLING
:
6748 case AsyncPanZoomController::PanZoomState::TOUCHING
:
6751 case AsyncPanZoomController::PanZoomState::PANNING
:
6754 case AsyncPanZoomController::PanZoomState::PANNING_LOCKED_X
:
6755 aOut
<< "PANNING_LOCKED_X";
6757 case AsyncPanZoomController::PanZoomState::PANNING_LOCKED_Y
:
6758 aOut
<< "PANNING_LOCKED_Y";
6760 case AsyncPanZoomController::PanZoomState::PAN_MOMENTUM
:
6761 aOut
<< "PAN_MOMENTUM";
6763 case AsyncPanZoomController::PanZoomState::PINCHING
:
6766 case AsyncPanZoomController::PanZoomState::ANIMATING_ZOOM
:
6767 aOut
<< "ANIMATING_ZOOM";
6769 case AsyncPanZoomController::PanZoomState::OVERSCROLL_ANIMATION
:
6770 aOut
<< "OVERSCROLL_ANIMATION";
6772 case AsyncPanZoomController::PanZoomState::SMOOTH_SCROLL
:
6773 aOut
<< "SMOOTH_SCROLL";
6775 case AsyncPanZoomController::PanZoomState::SMOOTHMSD_SCROLL
:
6776 aOut
<< "SMOOTHMSD_SCROLL";
6778 case AsyncPanZoomController::PanZoomState::WHEEL_SCROLL
:
6779 aOut
<< "WHEEL_SCROLL";
6781 case AsyncPanZoomController::PanZoomState::KEYBOARD_SCROLL
:
6782 aOut
<< "KEYBOARD_SCROLL";
6784 case AsyncPanZoomController::PanZoomState::AUTOSCROLL
:
6785 aOut
<< "AUTOSCROLL";
6787 case AsyncPanZoomController::PanZoomState::SCROLLBAR_DRAG
:
6788 aOut
<< "SCROLLBAR_DRAG";
6791 aOut
<< "UNKNOWN_STATE";
6797 bool operator==(const PointerEventsConsumableFlags
& aLhs
,
6798 const PointerEventsConsumableFlags
& aRhs
) {
6799 return (aLhs
.mHasRoom
== aRhs
.mHasRoom
) &&
6800 (aLhs
.mAllowedByTouchAction
== aRhs
.mAllowedByTouchAction
);
6803 std::ostream
& operator<<(std::ostream
& aOut
,
6804 const PointerEventsConsumableFlags
& aFlags
) {
6805 aOut
<< std::boolalpha
<< "{ hasRoom: " << aFlags
.mHasRoom
6806 << ", allowedByTouchAction: " << aFlags
.mAllowedByTouchAction
<< "}";
6810 } // namespace layers
6811 } // namespace mozilla