Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 : /* This Source Code Form is subject to the terms of the Mozilla Public
4 : * License, v. 2.0. If a copy of the MPL was not distributed with this
5 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 :
7 : #include "AsyncPanZoomController.h" // for AsyncPanZoomController, etc
8 :
9 : #include <math.h> // for fabsf, fabs, atan2
10 : #include <stdint.h> // for uint32_t, uint64_t
11 : #include <sys/types.h> // for int32_t
12 : #include <algorithm> // for max, min
13 :
14 : #include "APZCTreeManager.h" // for APZCTreeManager
15 : #include "AsyncPanZoomAnimation.h" // for AsyncPanZoomAnimation
16 : #include "AutoDirWheelDeltaAdjuster.h" // for APZAutoDirWheelDeltaAdjuster
17 : #include "AutoscrollAnimation.h" // for AutoscrollAnimation
18 : #include "Axis.h" // for AxisX, AxisY, Axis, etc
19 : #include "CheckerboardEvent.h" // for CheckerboardEvent
20 : #include "Compositor.h" // for Compositor
21 : #include "DesktopFlingPhysics.h" // for DesktopFlingPhysics
22 : #include "FrameMetrics.h" // for FrameMetrics, etc
23 : #include "GenericFlingAnimation.h" // for GenericFlingAnimation
24 : #include "GestureEventListener.h" // for GestureEventListener
25 : #include "HitTestingTreeNode.h" // for HitTestingTreeNode
26 : #include "InputData.h" // for MultiTouchInput, etc
27 : #include "InputBlockState.h" // for InputBlockState, TouchBlockState
28 : #include "InputQueue.h" // for InputQueue
29 : #include "Overscroll.h" // for OverscrollAnimation
30 : #include "OverscrollHandoffState.h" // for OverscrollHandoffState
31 : #include "Units.h" // for CSSRect, CSSPoint, etc
32 : #include "UnitTransforms.h" // for TransformTo
33 : #include "base/message_loop.h" // for MessageLoop
34 : #include "base/task.h" // for NewRunnableMethod, etc
35 : #include "gfxPrefs.h" // for gfxPrefs
36 : #include "gfxTypes.h" // for gfxFloat
37 : #include "LayersLogging.h" // for print_stderr
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/ComputedTimingFunction.h" // for ComputedTimingFunction
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/StaticPtr.h" // for StaticAutoPtr
49 : #include "mozilla/Telemetry.h" // for Telemetry
50 : #include "mozilla/TimeStamp.h" // for TimeDuration, TimeStamp
51 : #include "mozilla/dom/CheckerboardReportService.h" // for CheckerboardEventStorage
52 : // note: CheckerboardReportService.h actually lives in gfx/layers/apz/util/
53 : #include "mozilla/dom/Touch.h" // for Touch
54 : #include "mozilla/gfx/BasePoint.h" // for BasePoint
55 : #include "mozilla/gfx/BaseRect.h" // for BaseRect
56 : #include "mozilla/gfx/Point.h" // for Point, RoundedToInt, etc
57 : #include "mozilla/gfx/Rect.h" // for RoundedIn
58 : #include "mozilla/gfx/ScaleFactor.h" // for ScaleFactor
59 : #include "mozilla/layers/APZThreadUtils.h" // for AssertOnControllerThread, etc
60 : #include "mozilla/layers/AsyncCompositionManager.h" // for ViewTransform
61 : #include "mozilla/layers/AxisPhysicsModel.h" // for AxisPhysicsModel
62 : #include "mozilla/layers/AxisPhysicsMSDModel.h" // for AxisPhysicsMSDModel
63 : #include "mozilla/layers/CompositorController.h" // for CompositorController
64 : #include "mozilla/layers/DirectionUtils.h" // for GetAxis{Start,End,Length,Scale}
65 : #include "mozilla/layers/LayerTransactionParent.h" // for LayerTransactionParent
66 : #include "mozilla/layers/MetricsSharingController.h" // for MetricsSharingController
67 : #include "mozilla/mozalloc.h" // for operator new, etc
68 : #include "mozilla/Unused.h" // for unused
69 : #include "mozilla/FloatingPoint.h" // for FuzzyEquals*
70 : #include "nsAlgorithm.h" // for clamped
71 : #include "nsCOMPtr.h" // for already_AddRefed
72 : #include "nsDebug.h" // for NS_WARNING
73 : #include "nsIDOMWindowUtils.h" // for nsIDOMWindowUtils
74 : #include "nsMathUtils.h" // for NS_hypot
75 : #include "nsPoint.h" // for nsIntPoint
76 : #include "nsStyleConsts.h"
77 : #include "nsTimingFunction.h"
78 : #include "nsTArray.h" // for nsTArray, nsTArray_Impl, etc
79 : #include "nsThreadUtils.h" // for NS_IsMainThread
80 : #include "nsViewportInfo.h" // for kViewportMinScale, kViewportMaxScale
81 : #include "prsystem.h" // for PR_GetPhysicalMemorySize
82 : #include "SharedMemoryBasic.h" // for SharedMemoryBasic
83 : #include "ScrollSnap.h" // for ScrollSnapUtils
84 : #include "ScrollAnimationPhysics.h" // for ComputeAcceleratedWheelDelta
85 : #include "WheelScrollAnimation.h"
86 : #include "KeyboardScrollAnimation.h"
87 : #if defined(MOZ_WIDGET_ANDROID)
88 : #include "AndroidAPZ.h"
89 : #include "mozilla/layers/AndroidDynamicToolbarAnimator.h"
90 : #endif // defined(MOZ_WIDGET_ANDROID)
91 :
92 : #define ENABLE_APZC_LOGGING 0
93 : // #define ENABLE_APZC_LOGGING 1
94 :
95 : #if ENABLE_APZC_LOGGING
96 : # define APZC_LOG(...) printf_stderr("APZC: " __VA_ARGS__)
97 : # define APZC_LOG_FM(fm, prefix, ...) \
98 : { std::stringstream ss; \
99 : ss << nsPrintfCString(prefix, __VA_ARGS__).get(); \
100 : AppendToString(ss, fm, ":", "", true); \
101 : APZC_LOG("%s\n", ss.str().c_str()); \
102 : }
103 : #else
104 : # define APZC_LOG(...)
105 : # define APZC_LOG_FM(fm, prefix, ...)
106 : #endif
107 :
108 : namespace mozilla {
109 : namespace layers {
110 :
111 : typedef mozilla::layers::AllowedTouchBehavior AllowedTouchBehavior;
112 : typedef GeckoContentController::APZStateChange APZStateChange;
113 : typedef GeckoContentController::TapType TapType;
114 : typedef mozilla::gfx::Point Point;
115 : typedef mozilla::gfx::Matrix4x4 Matrix4x4;
116 : using mozilla::gfx::PointTyped;
117 :
118 : // Choose between platform-specific implementations.
119 : #ifdef MOZ_WIDGET_ANDROID
120 : typedef WidgetOverscrollEffect OverscrollEffect;
121 : typedef AndroidSpecificState PlatformSpecificState;
122 : #else
123 : typedef GenericOverscrollEffect OverscrollEffect;
124 : typedef PlatformSpecificStateBase PlatformSpecificState; // no extra state, just use the base class
125 : #endif
126 :
127 : /**
128 : * \page APZCPrefs APZ preferences
129 : *
130 : * The following prefs are used to control the behaviour of the APZC.
131 : * The default values are provided in gfxPrefs.h.
132 : *
133 : * \li\b apz.allow_checkerboarding
134 : * Pref that allows or disallows checkerboarding
135 : *
136 : * \li\b apz.allow_immediate_handoff
137 : * If set to true, scroll can be handed off from one APZC to another within
138 : * a single input block. If set to false, a single input block can only
139 : * scroll one APZC.
140 : *
141 : * \li\b apz.android.chrome_fling_physics.enabled
142 : * If set to true, APZ uses a fling physical model similar to Chrome's
143 : * on Android, rather than Android's StackScroller.
144 : *
145 : * \li\b apz.android.chrome_fling_physics.friction
146 : * A tunable parameter for Chrome fling physics on Android that governs
147 : * how quickly a fling animation slows down due to friction (and therefore
148 : * also how far it reaches). Should be in the range [0-1].
149 : *
150 : * \li\b apz.android.chrome_fling_physics.inflexion
151 : * A tunable parameter for Chrome fling physics on Android that governs
152 : * the shape of the fling curve. Should be in the range [0-1].
153 : *
154 : * \li\b apz.android.chrome_fling_physics.stop_threshold
155 : * A tunable parameter for Chrome fling physics on Android that governs
156 : * how close the fling animation has to get to its target destination
157 : * before it stops.
158 : * Units: ParentLayer pixels
159 : *
160 : * \li\b apz.autoscroll.enabled
161 : * If set to true, autoscrolling is driven by APZ rather than the content
162 : * process main thread.
163 : *
164 : * \li\b apz.axis_lock.mode
165 : * The preferred axis locking style. See AxisLockMode for possible values.
166 : *
167 : * \li\b apz.axis_lock.lock_angle
168 : * Angle from axis within which we stay axis-locked.\n
169 : * Units: radians
170 : *
171 : * \li\b apz.axis_lock.breakout_threshold
172 : * Distance in inches the user must pan before axis lock can be broken.\n
173 : * Units: (real-world, i.e. screen) inches
174 : *
175 : * \li\b apz.axis_lock.breakout_angle
176 : * Angle at which axis lock can be broken.\n
177 : * Units: radians
178 : *
179 : * \li\b apz.axis_lock.direct_pan_angle
180 : * If the angle from an axis to the line drawn by a pan move is less than
181 : * this value, we can assume that panning can be done in the allowed direction
182 : * (horizontal or vertical).\n
183 : * Currently used only for touch-action css property stuff and was addded to
184 : * keep behaviour consistent with IE.\n
185 : * Units: radians
186 : *
187 : * \li\b apz.content_response_timeout
188 : * Amount of time before we timeout response from content. For example, if
189 : * content is being unruly/slow and we don't get a response back within this
190 : * time, we will just pretend that content did not preventDefault any touch
191 : * events we dispatched to it.\n
192 : * Units: milliseconds
193 : *
194 : * \li\b apz.danger_zone_x
195 : * \li\b apz.danger_zone_y
196 : * When drawing high-res tiles, we drop down to drawing low-res tiles
197 : * when we know we can't keep up with the scrolling. The way we determine
198 : * this is by checking if we are entering the "danger zone", which is the
199 : * boundary of the painted content. For example, if the painted content
200 : * goes from y=0...1000 and the visible portion is y=250...750 then
201 : * we're far from checkerboarding. If we get to y=490...990 though then we're
202 : * only 10 pixels away from showing checkerboarding so we are probably in
203 : * a state where we can't keep up with scrolling. The danger zone prefs specify
204 : * how wide this margin is; in the above example a y-axis danger zone of 10
205 : * pixels would make us drop to low-res at y=490...990.\n
206 : * This value is in layer pixels.
207 : *
208 : * \li\b apz.disable_for_scroll_linked_effects
209 : * Setting this pref to true will disable APZ scrolling on documents where
210 : * scroll-linked effects are detected. A scroll linked effect is detected if
211 : * positioning or transform properties are updated inside a scroll event
212 : * dispatch; we assume that such an update is in response to the scroll event
213 : * and is therefore a scroll-linked effect which will be laggy with APZ
214 : * scrolling.
215 : *
216 : * \li\b apz.displayport_expiry_ms
217 : * While a scrollable frame is scrolling async, we set a displayport on it
218 : * to make sure it is layerized. However this takes up memory, so once the
219 : * scrolling stops we want to remove the displayport. This pref controls how
220 : * long after scrolling stops the displayport is removed. A value of 0 will
221 : * disable the expiry behavior entirely.
222 : * Units: milliseconds
223 : *
224 : * \li\b apz.drag.enabled
225 : * Setting this pref to true will cause APZ to handle mouse-dragging of
226 : * scrollbar thumbs.
227 : *
228 : * \li\b apz.drag.initial.enabled
229 : * Setting this pref to true will cause APZ to try to handle mouse-dragging
230 : * of scrollbar thumbs without an initial round-trip to content to start it
231 : * if possible. Only has an effect if apz.drag.enabled is also true.
232 : *
233 : * \li\b apz.drag.touch.enabled
234 : * Setting this pref to true will cause APZ to handle touch-dragging of
235 : * scrollbar thumbs. Only has an effect if apz.drag.enabled is also true.
236 : *
237 : * \li\b apz.enlarge_displayport_when_clipped
238 : * Pref that enables enlarging of the displayport along one axis when the
239 : * generated displayport's size is beyond that of the scrollable rect on the
240 : * opposite axis.
241 : *
242 : * \li\b apz.fling_accel_interval_ms
243 : * The time that determines whether a second fling will be treated as
244 : * accelerated. If two flings are started within this interval, the second one
245 : * will be accelerated. Setting an interval of 0 means that acceleration will
246 : * be disabled.\n
247 : * Units: milliseconds
248 : *
249 : * \li\b apz.fling_accel_min_velocity
250 : * The minimum velocity of the second fling for it to be considered for fling
251 : * acceleration.
252 : * Units: screen pixels per milliseconds
253 : *
254 : * \li\b apz.fling_accel_base_mult
255 : * \li\b apz.fling_accel_supplemental_mult
256 : * When applying an acceleration on a fling, the new computed velocity is
257 : * (new_fling_velocity * base_mult) + (old_velocity * supplemental_mult).
258 : * The base_mult and supplemental_mult multiplier values are controlled by
259 : * these prefs. Note that "old_velocity" here is the initial velocity of the
260 : * previous fling _after_ acceleration was applied to it (if applicable).
261 : *
262 : * \li\b apz.fling_curve_function_x1
263 : * \li\b apz.fling_curve_function_y1
264 : * \li\b apz.fling_curve_function_x2
265 : * \li\b apz.fling_curve_function_y2
266 : * \li\b apz.fling_curve_threshold_inches_per_ms
267 : * These five parameters define a Bezier curve function and threshold used to
268 : * increase the actual velocity relative to the user's finger velocity. When the
269 : * finger velocity is below the threshold (or if the threshold is not positive),
270 : * the velocity is used as-is. If the finger velocity exceeds the threshold
271 : * velocity, then the function defined by the curve is applied on the part of
272 : * the velocity that exceeds the threshold. Note that the upper bound of the
273 : * velocity is still specified by the \b apz.max_velocity_inches_per_ms pref, and
274 : * the function will smoothly curve the velocity from the threshold to the
275 : * max. In general the function parameters chosen should define an ease-out
276 : * curve in order to increase the velocity in this range, or an ease-in curve to
277 : * decrease the velocity. A straight-line curve is equivalent to disabling the
278 : * curve entirely by setting the threshold to -1. The max velocity pref must
279 : * also be set in order for the curving to take effect, as it defines the upper
280 : * bound of the velocity curve.\n
281 : * The points (x1, y1) and (x2, y2) used as the two intermediate control points
282 : * in the cubic bezier curve; the first and last points are (0,0) and (1,1).\n
283 : * Some example values for these prefs can be found at\n
284 : * https://dxr.mozilla.org/mozilla-central/rev/70e05c6832e831374604ac3ce7433971368dffe0/layout/style/nsStyleStruct.cpp#2729
285 : *
286 : * \li\b apz.fling_friction
287 : * Amount of friction applied during flings. This is used in the following
288 : * formula: v(t1) = v(t0) * (1 - f)^(t1 - t0), where v(t1) is the velocity
289 : * for a new sample, v(t0) is the velocity at the previous sample, f is the
290 : * value of this pref, and (t1 - t0) is the amount of time, in milliseconds,
291 : * that has elapsed between the two samples.\n
292 : * NOTE: Not currently used in Android fling calculations.
293 : *
294 : * \li\b apz.fling_min_velocity_threshold
295 : * Minimum velocity for a fling to actually kick off. If the user pans and lifts
296 : * their finger such that the velocity is smaller than this amount, no fling
297 : * is initiated.\n
298 : * Units: screen pixels per millisecond
299 : *
300 : * \li\b apz.fling_stop_on_tap_threshold
301 : * When flinging, if the velocity is above this number, then a tap on the
302 : * screen will stop the fling without dispatching a tap to content. If the
303 : * velocity is below this threshold a tap will also be dispatched.
304 : * Note: when modifying this pref be sure to run the APZC gtests as some of
305 : * them depend on the value of this pref.\n
306 : * Units: screen pixels per millisecond
307 : *
308 : * \li\b apz.fling_stopped_threshold
309 : * When flinging, if the velocity goes below this number, we just stop the
310 : * animation completely. This is to prevent asymptotically approaching 0
311 : * velocity and rerendering unnecessarily.\n
312 : * Units: screen pixels per millisecond.\n
313 : * NOTE: Should not be set to anything
314 : * other than 0.0 for Android except for tests to disable flings.
315 : *
316 : * \li\b apz.frame_delay.enabled
317 : * If this is set to true, changes to the async scroll offset and async zoom
318 : * will not be immediately reflected in GetCurrentAsyncTransform() when called
319 : * with |AsyncTransformConsumer::eForCompositing|. Rather, the transform will
320 : * reflect the value of the async scroll offset and async zoom at the last time
321 : * SampleCompositedAsyncTransform() was called.
322 : *
323 : * \li\b apz.keyboard.enabled
324 : * Determines whether scrolling with the keyboard will be allowed to be handled
325 : * by APZ.
326 : *
327 : * \li\b apz.keyboard.passive-listeners
328 : * When enabled, APZ will interpret the passive event listener flag to mean
329 : * that the event listener won't change the focused element or selection of
330 : * the page. With this, web content can use passive key listeners and not have
331 : * keyboard APZ disabled.
332 : *
333 : * \li\b apz.max_tap_time
334 : * Maximum time for a touch on the screen and corresponding lift of the finger
335 : * to be considered a tap. This also applies to double taps, except that it is
336 : * used both for the interval between the first touchdown and first touchup,
337 : * and for the interval between the first touchup and the second touchdown.\n
338 : * Units: milliseconds.
339 : *
340 : * \li\b apz.max_velocity_inches_per_ms
341 : * Maximum velocity. Velocity will be capped at this value if a faster fling
342 : * occurs. Negative values indicate unlimited velocity.\n
343 : * Units: (real-world, i.e. screen) inches per millisecond
344 : *
345 : * \li\b apz.max_velocity_queue_size
346 : * Maximum size of velocity queue. The queue contains last N velocity records.
347 : * On touch end we calculate the average velocity in order to compensate
348 : * touch/mouse drivers misbehaviour.
349 : *
350 : * \li\b apz.min_skate_speed
351 : * Minimum amount of speed along an axis before we switch to "skate" multipliers
352 : * rather than using the "stationary" multipliers.\n
353 : * Units: CSS pixels per millisecond
354 : *
355 : * \li\b apz.one_touch_pinch.enabled
356 : * Whether or not the "one-touch-pinch" gesture (for zooming with one finger)
357 : * is enabled or not.
358 : *
359 : * \li\b apz.overscroll.enabled
360 : * Pref that enables overscrolling. If this is disabled, excess scroll that
361 : * cannot be handed off is discarded.
362 : *
363 : * \li\b apz.overscroll.min_pan_distance_ratio
364 : * The minimum ratio of the pan distance along one axis to the pan distance
365 : * along the other axis needed to initiate overscroll along the first axis
366 : * during panning.
367 : *
368 : * \li\b apz.overscroll.stretch_factor
369 : * How much overscrolling can stretch content along an axis.
370 : * The maximum stretch along an axis is a factor of (1 + kStretchFactor).
371 : * (So if kStretchFactor is 0, you can't stretch at all; if kStretchFactor
372 : * is 1, you can stretch at most by a factor of 2).
373 : *
374 : * \li\b apz.overscroll.spring_stiffness
375 : * The stiffness of the spring used in the physics model for the overscroll
376 : * animation.
377 : *
378 : * \li\b apz.overscroll.spring_friction
379 : * The friction of the spring used in the physics model for the overscroll
380 : * animation.
381 : * Even though a realistic physics model would dictate that this be the same
382 : * as \b apz.fling_friction, we allow it to be set to be something different,
383 : * because in practice we want flings to skate smoothly (low friction), while
384 : * we want the overscroll bounce-back to oscillate few times (high friction).
385 : *
386 : * \li\b apz.overscroll.stop_distance_threshold
387 : * \li\b apz.overscroll.stop_velocity_threshold
388 : * Thresholds for stopping the overscroll animation. When both the distance
389 : * and the velocity fall below their thresholds, we stop oscillating.\n
390 : * Units: screen pixels (for distance)
391 : * screen pixels per millisecond (for velocity)
392 : *
393 : * \li\b apz.paint_skipping.enabled
394 : * When APZ is scrolling and sending repaint requests to the main thread, often
395 : * the main thread doesn't actually need to do a repaint. This pref allows the
396 : * main thread to skip doing those repaints in cases where it doesn't need to.
397 : *
398 : * \li\b apz.pinch_lock.mode
399 : * The preferred pinch locking style. See PinchLockMode for possible values.
400 : *
401 : * \li\b apz.pinch_lock.scroll_lock_threshold
402 : * Pinch locking is triggered if the user scrolls more than this distance
403 : * and pinches less than apz.pinch_lock.span_lock_threshold.\n
404 : * Units: (real-world, i.e. screen) inches
405 : *
406 : * \li\b apz.pinch_lock.span_breakout_threshold
407 : * Distance in inches the user must pinch before lock can be broken.\n
408 : * Units: (real-world, i.e. screen) inches measured between two touch points
409 : *
410 : * \li\b apz.pinch_lock.span_lock_threshold
411 : * Pinch locking is triggered if the user pinches less than this distance
412 : * and scrolls more than apz.pinch_lock.scroll_lock_threshold.\n
413 : * Units: (real-world, i.e. screen) inches measured between two touch points
414 : *
415 : * \li\b apz.popups.enabled
416 : * Determines whether APZ is used for XUL popup widgets with remote content.
417 : * Ideally, this should always be true, but it is currently not well tested, and
418 : * has known issues, so needs to be prefable.
419 : *
420 : * \li\b apz.record_checkerboarding
421 : * Whether or not to record detailed info on checkerboarding events.
422 : *
423 : * \li\b apz.second_tap_tolerance
424 : * Constant describing the tolerance in distance we use, multiplied by the
425 : * device DPI, within which a second tap is counted as part of a gesture
426 : * continuing from the first tap. Making this larger allows the user more
427 : * distance between the first and second taps in a "double tap" or "one touch
428 : * pinch" gesture.\n
429 : * Units: (real-world, i.e. screen) inches
430 : *
431 : * \li\b apz.test.logging_enabled
432 : * Enable logging of APZ test data (see bug 961289).
433 : *
434 : * \li\b apz.touch_move_tolerance
435 : * See the description for apz.touch_start_tolerance below. This is a similar
436 : * threshold, except it is used to suppress touchmove events from being delivered
437 : * to content for NON-scrollable frames (or more precisely, for APZCs where
438 : * ArePointerEventsConsumable returns false).\n
439 : * Units: (real-world, i.e. screen) inches
440 : *
441 : * \li\b apz.touch_start_tolerance
442 : * Constant describing the tolerance in distance we use, multiplied by the
443 : * device DPI, before we start panning the screen. This is to prevent us from
444 : * accidentally processing taps as touch moves, and from very short/accidental
445 : * touches moving the screen. touchmove events are also not delivered to content
446 : * within this distance on scrollable frames.\n
447 : * Units: (real-world, i.e. screen) inches
448 : *
449 : * \li\b apz.velocity_bias
450 : * How much to adjust the displayport in the direction of scrolling. This value
451 : * is multiplied by the velocity and added to the displayport offset.
452 : *
453 : * \li\b apz.velocity_relevance_time_ms
454 : * When computing a fling velocity from the most recently stored velocity
455 : * information, only velocities within the most X milliseconds are used.
456 : * This pref controls the value of X.\n
457 : * Units: ms
458 : *
459 : * \li\b apz.x_skate_size_multiplier
460 : * \li\b apz.y_skate_size_multiplier
461 : * The multiplier we apply to the displayport size if it is skating (current
462 : * velocity is above \b apz.min_skate_speed). We prefer to increase the size of
463 : * the Y axis because it is more natural in the case that a user is reading a
464 : * page page that scrolls up/down. Note that one, both or neither of these may be
465 : * used at any instant.\n
466 : * In general we want \b apz.[xy]_skate_size_multiplier to be smaller than the corresponding
467 : * stationary size multiplier because when panning fast we would like to paint
468 : * less and get faster, more predictable paint times. When panning slowly we
469 : * can afford to paint more even though it's slower.
470 : *
471 : * \li\b apz.x_stationary_size_multiplier
472 : * \li\b apz.y_stationary_size_multiplier
473 : * The multiplier we apply to the displayport size if it is not skating (see
474 : * documentation for the skate size multipliers above).
475 : *
476 : * \li\b apz.x_skate_highmem_adjust
477 : * \li\b apz.y_skate_highmem_adjust
478 : * On high memory systems, we adjust the displayport during skating
479 : * to be larger so we can reduce checkerboarding.
480 : *
481 : * \li\b apz.zoom_animation_duration_ms
482 : * This controls how long the zoom-to-rect animation takes.\n
483 : * Units: ms
484 : *
485 : * \li\b apz.scale_repaint_delay_ms
486 : * How long to delay between repaint requests during a scale.
487 : * A negative number prevents repaint requests during a scale.\n
488 : * Units: ms
489 : *
490 : */
491 :
492 : /**
493 : * Computed time function used for sampling frames of a zoom to animation.
494 : */
495 0 : StaticAutoPtr<ComputedTimingFunction> gZoomAnimationFunction;
496 :
497 : /**
498 : * Computed time function used for curving up velocity when it gets high.
499 : */
500 : StaticAutoPtr<ComputedTimingFunction> gVelocityCurveFunction;
501 :
502 : /**
503 : * The estimated duration of a paint for the purposes of calculating a new
504 : * displayport, in milliseconds.
505 : */
506 : static const double kDefaultEstimatedPaintDurationMs = 50;
507 :
508 : /**
509 : * Returns true if this is a high memory system and we can use
510 : * extra memory for a larger displayport to reduce checkerboarding.
511 : */
512 : static bool gIsHighMemSystem = false;
513 : static bool IsHighMemSystem()
514 : {
515 0 : return gIsHighMemSystem;
516 : }
517 :
518 : /**
519 : * Is aAngle within the given threshold of the horizontal axis?
520 : * @param aAngle an angle in radians in the range [0, pi]
521 : * @param aThreshold an angle in radians in the range [0, pi/2]
522 : */
523 0 : static bool IsCloseToHorizontal(float aAngle, float aThreshold)
524 : {
525 0 : return (aAngle < aThreshold || aAngle > (M_PI - aThreshold));
526 : }
527 :
528 : // As above, but for the vertical axis.
529 : static bool IsCloseToVertical(float aAngle, float aThreshold)
530 : {
531 0 : return (fabs(aAngle - (M_PI / 2)) < aThreshold);
532 : }
533 :
534 : // Counter used to give each APZC a unique id
535 : static uint32_t sAsyncPanZoomControllerCount = 0;
536 :
537 : AsyncPanZoomAnimation*
538 0 : PlatformSpecificStateBase::CreateFlingAnimation(AsyncPanZoomController& aApzc,
539 : const FlingHandoffState& aHandoffState,
540 : float aPLPPI)
541 : {
542 : return new GenericFlingAnimation<DesktopFlingPhysics>(aApzc,
543 : aHandoffState.mChain,
544 0 : aHandoffState.mIsHandoff,
545 : aHandoffState.mScrolledApzc,
546 0 : aPLPPI);
547 : }
548 :
549 : TimeStamp
550 0 : AsyncPanZoomController::GetFrameTime() const
551 : {
552 0 : APZCTreeManager* treeManagerLocal = GetApzcTreeManager();
553 0 : return treeManagerLocal ? treeManagerLocal->GetFrameTime() : TimeStamp::Now();
554 : }
555 :
556 : class MOZ_STACK_CLASS StateChangeNotificationBlocker {
557 : public:
558 0 : explicit StateChangeNotificationBlocker(AsyncPanZoomController* aApzc)
559 0 : : mApzc(aApzc)
560 : {
561 0 : RecursiveMutexAutoLock lock(mApzc->mRecursiveMutex);
562 0 : mInitialState = mApzc->mState;
563 0 : mApzc->mNotificationBlockers++;
564 0 : }
565 :
566 0 : ~StateChangeNotificationBlocker()
567 0 : {
568 : AsyncPanZoomController::PanZoomState newState;
569 : {
570 0 : RecursiveMutexAutoLock lock(mApzc->mRecursiveMutex);
571 0 : mApzc->mNotificationBlockers--;
572 0 : newState = mApzc->mState;
573 : }
574 0 : mApzc->DispatchStateChangeNotification(mInitialState, newState);
575 0 : }
576 :
577 : private:
578 : AsyncPanZoomController* mApzc;
579 : AsyncPanZoomController::PanZoomState mInitialState;
580 : };
581 :
582 0 : class ZoomAnimation: public AsyncPanZoomAnimation {
583 : public:
584 0 : ZoomAnimation(const CSSPoint& aStartOffset,
585 : const CSSToParentLayerScale2D& aStartZoom,
586 : const CSSPoint& aEndOffset,
587 : const CSSToParentLayerScale2D& aEndZoom)
588 0 : : mTotalDuration(
589 0 : TimeDuration::FromMilliseconds(gfxPrefs::APZZoomAnimationDuration()))
590 : , mStartOffset(aStartOffset)
591 : , mStartZoom(aStartZoom)
592 : , mEndOffset(aEndOffset)
593 0 : , mEndZoom(aEndZoom)
594 : {
595 0 : }
596 :
597 0 : virtual bool DoSample(FrameMetrics& aFrameMetrics,
598 : const TimeDuration& aDelta) override
599 : {
600 0 : mDuration += aDelta;
601 0 : double animPosition = mDuration / mTotalDuration;
602 :
603 0 : if (animPosition >= 1.0) {
604 0 : aFrameMetrics.SetZoom(mEndZoom);
605 0 : aFrameMetrics.SetScrollOffset(mEndOffset);
606 0 : return false;
607 : }
608 :
609 : // Sample the zoom at the current time point. The sampled zoom
610 : // will affect the final computed resolution.
611 : float sampledPosition =
612 0 : gZoomAnimationFunction->GetValue(animPosition,
613 0 : ComputedTimingFunction::BeforeFlag::Unset);
614 :
615 : // We scale the scrollOffset linearly with sampledPosition, so the zoom
616 : // needs to scale inversely to match.
617 0 : aFrameMetrics.SetZoom(CSSToParentLayerScale2D(
618 0 : 1 / (sampledPosition / mEndZoom.xScale + (1 - sampledPosition) / mStartZoom.xScale),
619 0 : 1 / (sampledPosition / mEndZoom.yScale + (1 - sampledPosition) / mStartZoom.yScale)));
620 :
621 0 : aFrameMetrics.SetScrollOffset(CSSPoint::FromUnknownPoint(gfx::Point(
622 0 : mEndOffset.x * sampledPosition + mStartOffset.x * (1 - sampledPosition),
623 0 : mEndOffset.y * sampledPosition + mStartOffset.y * (1 - sampledPosition)
624 0 : )));
625 :
626 0 : return true;
627 : }
628 :
629 0 : virtual bool WantsRepaints() override
630 : {
631 0 : return false;
632 : }
633 :
634 : private:
635 : TimeDuration mDuration;
636 : const TimeDuration mTotalDuration;
637 :
638 : // Old metrics from before we started a zoom animation. This is only valid
639 : // when we are in the "ANIMATED_ZOOM" state. This is used so that we can
640 : // interpolate between the start and end frames. We only use the
641 : // |mViewportScrollOffset| and |mResolution| fields on this.
642 : CSSPoint mStartOffset;
643 : CSSToParentLayerScale2D mStartZoom;
644 :
645 : // Target metrics for a zoom to animation. This is only valid when we are in
646 : // the "ANIMATED_ZOOM" state. We only use the |mViewportScrollOffset| and
647 : // |mResolution| fields on this.
648 : CSSPoint mEndOffset;
649 : CSSToParentLayerScale2D mEndZoom;
650 : };
651 :
652 :
653 0 : class SmoothScrollAnimation : public AsyncPanZoomAnimation {
654 : public:
655 0 : SmoothScrollAnimation(AsyncPanZoomController& aApzc,
656 : const nsPoint &aInitialPosition,
657 : const nsPoint &aInitialVelocity,
658 : const nsPoint& aDestination, double aSpringConstant,
659 : double aDampingRatio)
660 0 : : mApzc(aApzc)
661 0 : , mXAxisModel(aInitialPosition.x, aDestination.x, aInitialVelocity.x,
662 : aSpringConstant, aDampingRatio)
663 0 : , mYAxisModel(aInitialPosition.y, aDestination.y, aInitialVelocity.y,
664 0 : aSpringConstant, aDampingRatio)
665 : {
666 0 : }
667 :
668 : /**
669 : * Advances a smooth scroll simulation based on the time passed in |aDelta|.
670 : * This should be called whenever sampling the content transform for this
671 : * frame. Returns true if the smooth scroll should be advanced by one frame,
672 : * or false if the smooth scroll has ended.
673 : */
674 0 : bool DoSample(FrameMetrics& aFrameMetrics, const TimeDuration& aDelta) override {
675 : nsPoint oneParentLayerPixel =
676 0 : CSSPoint::ToAppUnits(ParentLayerPoint(1, 1) / aFrameMetrics.GetZoom());
677 0 : if (mXAxisModel.IsFinished(oneParentLayerPixel.x) &&
678 0 : mYAxisModel.IsFinished(oneParentLayerPixel.y)) {
679 : // Set the scroll offset to the exact destination. If we allow the scroll
680 : // offset to end up being a bit off from the destination, we can get
681 : // artefacts like "scroll to the next snap point in this direction"
682 : // scrolling to the snap point we're already supposed to be at.
683 : aFrameMetrics.ClampAndSetScrollOffset(
684 0 : CSSPoint::FromAppUnits(nsPoint(mXAxisModel.GetDestination(),
685 0 : mYAxisModel.GetDestination())));
686 0 : return false;
687 : }
688 :
689 0 : mXAxisModel.Simulate(aDelta);
690 0 : mYAxisModel.Simulate(aDelta);
691 :
692 0 : CSSPoint position = CSSPoint::FromAppUnits(nsPoint(mXAxisModel.GetPosition(),
693 0 : mYAxisModel.GetPosition()));
694 0 : CSSPoint css_velocity = CSSPoint::FromAppUnits(nsPoint(mXAxisModel.GetVelocity(),
695 0 : mYAxisModel.GetVelocity()));
696 :
697 : // Convert from points/second to points/ms
698 0 : ParentLayerPoint velocity = ParentLayerPoint(css_velocity.x, css_velocity.y) / 1000.0f;
699 :
700 : // Keep the velocity updated for the Axis class so that any animations
701 : // chained off of the smooth scroll will inherit it.
702 0 : if (mXAxisModel.IsFinished(oneParentLayerPixel.x)) {
703 0 : mApzc.mX.SetVelocity(0);
704 : } else {
705 0 : mApzc.mX.SetVelocity(velocity.x);
706 : }
707 0 : if (mYAxisModel.IsFinished(oneParentLayerPixel.y)) {
708 0 : mApzc.mY.SetVelocity(0);
709 : } else {
710 0 : mApzc.mY.SetVelocity(velocity.y);
711 : }
712 : // If we overscroll, hand off to a fling animation that will complete the
713 : // spring back.
714 0 : CSSToParentLayerScale2D zoom = aFrameMetrics.GetZoom();
715 0 : ParentLayerPoint displacement = (position - aFrameMetrics.GetScrollOffset()) * zoom;
716 :
717 0 : ParentLayerPoint overscroll;
718 0 : ParentLayerPoint adjustedOffset;
719 0 : mApzc.mX.AdjustDisplacement(displacement.x, adjustedOffset.x, overscroll.x);
720 0 : mApzc.mY.AdjustDisplacement(displacement.y, adjustedOffset.y, overscroll.y);
721 :
722 0 : aFrameMetrics.ScrollBy(adjustedOffset / zoom);
723 :
724 : // The smooth scroll may have caused us to reach the end of our scroll range.
725 : // This can happen if either the layout.css.scroll-behavior.damping-ratio
726 : // preference is set to less than 1 (underdamped) or if a smooth scroll
727 : // inherits velocity from a fling gesture.
728 0 : if (!IsZero(overscroll)) {
729 : // Hand off a fling with the remaining momentum to the next APZC in the
730 : // overscroll handoff chain.
731 :
732 : // We may have reached the end of the scroll range along one axis but
733 : // not the other. In such a case we only want to hand off the relevant
734 : // component of the fling.
735 0 : if (FuzzyEqualsAdditive(overscroll.x, 0.0f, COORDINATE_EPSILON)) {
736 0 : velocity.x = 0;
737 0 : } else if (FuzzyEqualsAdditive(overscroll.y, 0.0f, COORDINATE_EPSILON)) {
738 0 : velocity.y = 0;
739 : }
740 :
741 : // To hand off the fling, we attempt to find a target APZC and start a new
742 : // fling with the same velocity on that APZC. For simplicity, the actual
743 : // overscroll of the current sample is discarded rather than being handed
744 : // off. The compositor should sample animations sufficiently frequently
745 : // that this is not noticeable. The target APZC is chosen by seeing if
746 : // there is an APZC further in the handoff chain which is pannable; if
747 : // there isn't, we take the new fling ourselves, entering an overscrolled
748 : // state.
749 : // Note: APZC is holding mRecursiveMutex, so directly calling
750 : // HandleSmoothScrollOverscroll() (which acquires the tree lock) would violate
751 : // the lock ordering. Instead we schedule HandleSmoothScrollOverscroll() to be
752 : // called after mRecursiveMutex is released.
753 0 : mDeferredTasks.AppendElement(NewRunnableMethod<ParentLayerPoint>(
754 : "layers::AsyncPanZoomController::HandleSmoothScrollOverscroll",
755 0 : &mApzc,
756 : &AsyncPanZoomController::HandleSmoothScrollOverscroll,
757 0 : velocity));
758 0 : return false;
759 : }
760 :
761 : return true;
762 : }
763 :
764 0 : void SetDestination(const nsPoint& aNewDestination) {
765 0 : mXAxisModel.SetDestination(static_cast<int32_t>(aNewDestination.x));
766 0 : mYAxisModel.SetDestination(static_cast<int32_t>(aNewDestination.y));
767 0 : }
768 :
769 0 : CSSPoint GetDestination() const {
770 : return CSSPoint::FromAppUnits(
771 0 : nsPoint(mXAxisModel.GetDestination(), mYAxisModel.GetDestination()));
772 : }
773 :
774 0 : SmoothScrollAnimation* AsSmoothScrollAnimation() override {
775 0 : return this;
776 : }
777 :
778 : private:
779 : AsyncPanZoomController& mApzc;
780 : AxisPhysicsMSDModel mXAxisModel, mYAxisModel;
781 : };
782 :
783 : /*static*/ void
784 0 : AsyncPanZoomController::InitializeGlobalState()
785 : {
786 : static bool sInitialized = false;
787 0 : if (sInitialized)
788 : return;
789 0 : sInitialized = true;
790 :
791 0 : MOZ_ASSERT(NS_IsMainThread());
792 :
793 : gZoomAnimationFunction = new ComputedTimingFunction(
794 0 : nsTimingFunction(NS_STYLE_TRANSITION_TIMING_FUNCTION_EASE));
795 0 : ClearOnShutdown(&gZoomAnimationFunction);
796 : gVelocityCurveFunction = new ComputedTimingFunction(
797 0 : nsTimingFunction(gfxPrefs::APZCurveFunctionX1(),
798 : gfxPrefs::APZCurveFunctionY1(),
799 : gfxPrefs::APZCurveFunctionX2(),
800 0 : gfxPrefs::APZCurveFunctionY2()));
801 0 : ClearOnShutdown(&gVelocityCurveFunction);
802 :
803 0 : uint64_t sysmem = PR_GetPhysicalMemorySize();
804 0 : uint64_t threshold = 1LL << 32; // 4 GB in bytes
805 0 : gIsHighMemSystem = sysmem >= threshold;
806 :
807 : PlatformSpecificState::InitializeGlobalState();
808 : }
809 :
810 0 : AsyncPanZoomController::AsyncPanZoomController(LayersId aLayersId,
811 : APZCTreeManager* aTreeManager,
812 : const RefPtr<InputQueue>& aInputQueue,
813 : GeckoContentController* aGeckoContentController,
814 0 : GestureBehavior aGestures)
815 : : mLayersId(aLayersId),
816 : mGeckoContentController(aGeckoContentController),
817 : mRefPtrMonitor("RefPtrMonitor"),
818 : // mTreeManager must be initialized before GetFrameTime() is called
819 : mTreeManager(aTreeManager),
820 0 : mFrameMetrics(mScrollMetadata.GetMetrics()),
821 : mRecursiveMutex("AsyncPanZoomController"),
822 0 : mLastContentPaintMetrics(mLastContentPaintMetadata.GetMetrics()),
823 : mX(this),
824 : mY(this),
825 : mPanDirRestricted(false),
826 : mPinchLocked(false),
827 : mZoomConstraints(false, false,
828 0 : mFrameMetrics.GetDevPixelsPerCSSPixel() * kViewportMinScale / ParentLayerToScreenScale(1),
829 0 : mFrameMetrics.GetDevPixelsPerCSSPixel() * kViewportMaxScale / ParentLayerToScreenScale(1)),
830 : mLastSampleTime(GetFrameTime()),
831 : mLastCheckerboardReport(GetFrameTime()),
832 0 : mOverscrollEffect(MakeUnique<OverscrollEffect>(*this)),
833 : mState(NOTHING),
834 : mNotificationBlockers(0),
835 : mInputQueue(aInputQueue),
836 : mPinchPaintTimerSet(false),
837 0 : mAPZCId(sAsyncPanZoomControllerCount++),
838 : mSharedLock(nullptr),
839 : mAsyncTransformAppliedToContent(false),
840 : mTestHasAsyncKeyScrolled(false),
841 0 : mCheckerboardEventLock("APZCBELock")
842 : {
843 0 : if (aGestures == USE_GESTURE_DETECTOR) {
844 0 : mGestureEventListener = new GestureEventListener(this);
845 : }
846 0 : }
847 :
848 0 : AsyncPanZoomController::~AsyncPanZoomController()
849 : {
850 0 : MOZ_ASSERT(IsDestroyed());
851 0 : }
852 :
853 : PlatformSpecificStateBase*
854 0 : AsyncPanZoomController::GetPlatformSpecificState()
855 : {
856 0 : if (!mPlatformSpecificState) {
857 0 : mPlatformSpecificState = MakeUnique<PlatformSpecificState>();
858 : }
859 0 : return mPlatformSpecificState.get();
860 : }
861 :
862 : already_AddRefed<GeckoContentController>
863 0 : AsyncPanZoomController::GetGeckoContentController() const {
864 0 : MonitorAutoLock lock(mRefPtrMonitor);
865 0 : RefPtr<GeckoContentController> controller = mGeckoContentController;
866 0 : return controller.forget();
867 : }
868 :
869 : already_AddRefed<GestureEventListener>
870 0 : AsyncPanZoomController::GetGestureEventListener() const {
871 0 : MonitorAutoLock lock(mRefPtrMonitor);
872 0 : RefPtr<GestureEventListener> listener = mGestureEventListener;
873 0 : return listener.forget();
874 : }
875 :
876 : const RefPtr<InputQueue>&
877 0 : AsyncPanZoomController::GetInputQueue() const {
878 0 : return mInputQueue;
879 : }
880 :
881 : void
882 0 : AsyncPanZoomController::Destroy()
883 : {
884 0 : AssertOnUpdaterThread();
885 :
886 0 : CancelAnimation(CancelAnimationFlags::ScrollSnap);
887 :
888 : { // scope the lock
889 0 : MonitorAutoLock lock(mRefPtrMonitor);
890 0 : mGeckoContentController = nullptr;
891 0 : mGestureEventListener = nullptr;
892 : }
893 0 : mParent = nullptr;
894 0 : mTreeManager = nullptr;
895 :
896 : // Only send the release message if the SharedFrameMetrics has been created.
897 0 : if (mMetricsSharingController && mSharedFrameMetricsBuffer) {
898 0 : Unused << mMetricsSharingController->StopSharingMetrics(mFrameMetrics.GetScrollId(), mAPZCId);
899 : }
900 :
901 : { // scope the lock
902 0 : RecursiveMutexAutoLock lock(mRecursiveMutex);
903 0 : mSharedFrameMetricsBuffer = nullptr;
904 0 : delete mSharedLock;
905 0 : mSharedLock = nullptr;
906 : }
907 0 : }
908 :
909 : bool
910 0 : AsyncPanZoomController::IsDestroyed() const
911 : {
912 0 : return mTreeManager == nullptr;
913 : }
914 :
915 : float
916 0 : AsyncPanZoomController::GetDPI() const
917 : {
918 0 : if (APZCTreeManager* localPtr = mTreeManager) {
919 0 : return localPtr->GetDPI();
920 : }
921 : // If this APZC has been destroyed then this value is not going to be
922 : // used for anything that the user will end up seeing, so we can just
923 : // return 0.
924 : return 0.0;
925 : }
926 :
927 : ScreenCoord
928 0 : AsyncPanZoomController::GetTouchStartTolerance() const
929 : {
930 0 : return (gfxPrefs::APZTouchStartTolerance() * GetDPI());
931 : }
932 :
933 : ScreenCoord
934 0 : AsyncPanZoomController::GetTouchMoveTolerance() const
935 : {
936 0 : return (gfxPrefs::APZTouchMoveTolerance() * GetDPI());
937 : }
938 :
939 : ScreenCoord
940 0 : AsyncPanZoomController::GetSecondTapTolerance() const
941 : {
942 0 : return (gfxPrefs::APZSecondTapTolerance() * GetDPI());
943 : }
944 :
945 0 : /* static */AsyncPanZoomController::AxisLockMode AsyncPanZoomController::GetAxisLockMode()
946 : {
947 0 : return static_cast<AxisLockMode>(gfxPrefs::APZAxisLockMode());
948 : }
949 :
950 0 : /* static */AsyncPanZoomController::PinchLockMode AsyncPanZoomController::GetPinchLockMode()
951 : {
952 0 : return static_cast<PinchLockMode>(gfxPrefs::APZPinchLockMode());
953 : }
954 :
955 : bool
956 0 : AsyncPanZoomController::ArePointerEventsConsumable(TouchBlockState* aBlock, uint32_t aTouchPoints) {
957 0 : if (aTouchPoints == 0) {
958 : // Cant' do anything with zero touch points
959 : return false;
960 : }
961 :
962 : // This logic is simplified, erring on the side of returning true
963 : // if we're not sure. It's safer to pretend that we can consume the
964 : // event and then not be able to than vice-versa.
965 : // We could probably enhance this logic to determine things like "we're
966 : // not pannable, so we can only zoom in, and the zoom is already maxed
967 : // out, so we're not zoomable either" but no need for that at this point.
968 :
969 0 : bool pannable = aBlock->GetOverscrollHandoffChain()->CanBePanned(this);
970 0 : bool zoomable = mZoomConstraints.mAllowZoom;
971 :
972 0 : pannable &= (aBlock->TouchActionAllowsPanningX() || aBlock->TouchActionAllowsPanningY());
973 0 : zoomable &= (aBlock->TouchActionAllowsPinchZoom());
974 :
975 : // XXX once we fix bug 1031443, consumable should be assigned
976 : // pannable || zoomable if aTouchPoints > 1.
977 0 : bool consumable = (aTouchPoints == 1 ? pannable : zoomable);
978 0 : if (!consumable) {
979 : return false;
980 : }
981 :
982 0 : return true;
983 : }
984 :
985 0 : nsEventStatus AsyncPanZoomController::HandleDragEvent(const MouseInput& aEvent,
986 : const AsyncDragMetrics& aDragMetrics,
987 : CSSCoord aInitialThumbPos)
988 : {
989 0 : if (!gfxPrefs::APZDragEnabled()) {
990 : return nsEventStatus_eIgnore;
991 : }
992 :
993 0 : if (!GetApzcTreeManager()) {
994 : return nsEventStatus_eConsumeNoDefault;
995 : }
996 :
997 0 : if (aEvent.mType == MouseInput::MouseType::MOUSE_UP) {
998 0 : APZC_LOG("%p ending drag\n", this);
999 0 : SetState(NOTHING);
1000 0 : ScrollSnap();
1001 : return nsEventStatus_eConsumeNoDefault;
1002 : }
1003 0 :
1004 0 : HitTestingTreeNodeAutoLock node;
1005 0 : GetApzcTreeManager()->FindScrollThumbNode(aDragMetrics, node);
1006 : if (!node) {
1007 : APZC_LOG("%p unable to find scrollthumb node with viewid %" PRIu64 "\n", this, aDragMetrics.mViewId);
1008 : return nsEventStatus_eConsumeNoDefault;
1009 0 : }
1010 0 :
1011 : if (aEvent.mType == MouseInput::MouseType::MOUSE_DOWN) {
1012 : APZC_LOG("%p starting scrollbar drag\n", this);
1013 0 : SetState(SCROLLBAR_DRAG);
1014 : }
1015 :
1016 : if (aEvent.mType != MouseInput::MouseType::MOUSE_MOVE) {
1017 0 : APZC_LOG("%p discarding event of type %d\n", this, aEvent.mType);
1018 0 : return nsEventStatus_eConsumeNoDefault;
1019 0 : }
1020 0 :
1021 : const ScrollbarData& scrollbarData = node->GetScrollbarData();
1022 0 : MOZ_ASSERT(scrollbarData.mScrollbarLayerType == layers::ScrollbarLayerType::Thumb);
1023 0 : MOZ_ASSERT(scrollbarData.mDirection.isSome());
1024 : ScrollDirection direction = *scrollbarData.mDirection;
1025 :
1026 : bool isMouseAwayFromThumb = false;
1027 : if (int snapMultiplier = gfxPrefs::SliderSnapMultiplier()) {
1028 0 : // It's fine to ignore the async component of the thumb's transform,
1029 0 : // because any async transform of the thumb will be in the direction of
1030 0 : // scrolling, but here we're interested in the other direction.
1031 : ParentLayerRect thumbRect =
1032 0 : (node->GetTransform() * AsyncTransformMatrix()).TransformBounds(
1033 0 : LayerRect(node->GetVisibleRegion().GetBounds()));
1034 : ScrollDirection otherDirection = GetPerpendicularDirection(direction);
1035 : ParentLayerCoord distance = GetAxisStart(otherDirection,
1036 0 : thumbRect.DistanceTo(aEvent.mLocalOrigin));
1037 0 : ParentLayerCoord thumbWidth = GetAxisLength(otherDirection, thumbRect);
1038 : // Avoid triggering this condition spuriously when the thumb is
1039 : // offscreen and its visible region is therefore empty.
1040 : if (thumbWidth > 0 && thumbWidth * snapMultiplier < distance) {
1041 0 : isMouseAwayFromThumb = true;
1042 0 : APZC_LOG("%p determined mouse is away from thumb, will snap\n", this);
1043 0 : }
1044 0 : }
1045 :
1046 : RecursiveMutexAutoLock lock(mRecursiveMutex);
1047 0 : CSSCoord thumbPosition;
1048 : if (isMouseAwayFromThumb) {
1049 : thumbPosition = aInitialThumbPos;
1050 0 : } else {
1051 0 : thumbPosition = ConvertScrollbarPoint(aEvent.mLocalOrigin, scrollbarData) -
1052 : aDragMetrics.mScrollbarDragOffset;
1053 0 : }
1054 :
1055 : CSSCoord maxThumbPos = scrollbarData.mScrollTrackLength;
1056 0 : maxThumbPos -= scrollbarData.mThumbLength;
1057 :
1058 0 : float scrollPercent = thumbPosition / maxThumbPos;
1059 0 : APZC_LOG("%p scrollbar dragged to %f percent\n", this, scrollPercent);
1060 0 :
1061 : CSSCoord minScrollPosition =
1062 0 : GetAxisStart(direction, mFrameMetrics.GetScrollableRect().TopLeft());
1063 0 : CSSCoord maxScrollPosition =
1064 : GetAxisStart(direction, mFrameMetrics.GetScrollableRect().BottomRight()) -
1065 0 : GetAxisLength(direction, mFrameMetrics.CalculateCompositionBoundsInCssPixelsOfSurroundingContent());
1066 0 : CSSCoord scrollPosition = minScrollPosition + (scrollPercent * (maxScrollPosition - minScrollPosition));
1067 0 :
1068 : scrollPosition = std::max(scrollPosition, minScrollPosition);
1069 0 : scrollPosition = std::min(scrollPosition, maxScrollPosition);
1070 :
1071 0 : CSSPoint scrollOffset = mFrameMetrics.GetScrollOffset();
1072 0 : if (direction == ScrollDirection::eHorizontal) {
1073 0 : scrollOffset.x = scrollPosition;
1074 : } else {
1075 : scrollOffset.y = scrollPosition;
1076 : }
1077 : APZC_LOG("%p set scroll offset to %s from scrollbar drag\n", this, Stringify(scrollOffset).c_str());
1078 0 : mFrameMetrics.SetScrollOffset(scrollOffset);
1079 : ScheduleCompositeAndMaybeRepaint();
1080 0 : UpdateSharedCompositorFrameMetrics();
1081 :
1082 0 : return nsEventStatus_eConsumeNoDefault;
1083 : }
1084 0 :
1085 : nsEventStatus AsyncPanZoomController::HandleInputEvent(const InputData& aEvent,
1086 0 : const ScreenToParentLayerMatrix4x4& aTransformToApzc) {
1087 0 : APZThreadUtils::AssertOnControllerThread();
1088 0 :
1089 : nsEventStatus rv = nsEventStatus_eIgnore;
1090 :
1091 0 : switch (aEvent.mInputType) {
1092 0 : case MULTITOUCH_INPUT: {
1093 0 : MultiTouchInput multiTouchInput = aEvent.AsMultiTouchInput();
1094 0 : if (!multiTouchInput.TransformToLocal(aTransformToApzc)) {
1095 0 : return rv;
1096 : }
1097 :
1098 : RefPtr<GestureEventListener> listener = GetGestureEventListener();
1099 0 : if (listener) {
1100 0 : rv = listener->HandleInputEvent(multiTouchInput);
1101 0 : if (rv == nsEventStatus_eConsumeNoDefault) {
1102 0 : return rv;
1103 0 : }
1104 : }
1105 0 :
1106 : switch (multiTouchInput.mType) {
1107 : case MultiTouchInput::MULTITOUCH_START: rv = OnTouchStart(multiTouchInput); break;
1108 0 : case MultiTouchInput::MULTITOUCH_MOVE: rv = OnTouchMove(multiTouchInput); break;
1109 0 : case MultiTouchInput::MULTITOUCH_END: rv = OnTouchEnd(multiTouchInput); break;
1110 0 : case MultiTouchInput::MULTITOUCH_CANCEL: rv = OnTouchCancel(multiTouchInput); break;
1111 : }
1112 : break;
1113 0 : }
1114 0 : case PANGESTURE_INPUT: {
1115 0 : PanGestureInput panGestureInput = aEvent.AsPanGestureInput();
1116 0 : if (!panGestureInput.TransformToLocal(aTransformToApzc)) {
1117 0 : return rv;
1118 0 : }
1119 0 :
1120 0 : switch (panGestureInput.mType) {
1121 0 : case PanGestureInput::PANGESTURE_MAYSTART: rv = OnPanMayBegin(panGestureInput); break;
1122 : case PanGestureInput::PANGESTURE_CANCELLED: rv = OnPanCancelled(panGestureInput); break;
1123 0 : case PanGestureInput::PANGESTURE_START: rv = OnPanBegin(panGestureInput); break;
1124 : case PanGestureInput::PANGESTURE_PAN: rv = OnPan(panGestureInput, true); break;
1125 : case PanGestureInput::PANGESTURE_END: rv = OnPanEnd(panGestureInput); break;
1126 0 : case PanGestureInput::PANGESTURE_MOMENTUMSTART: rv = OnPanMomentumStart(panGestureInput); break;
1127 0 : case PanGestureInput::PANGESTURE_MOMENTUMPAN: rv = OnPan(panGestureInput, false); break;
1128 0 : case PanGestureInput::PANGESTURE_MOMENTUMEND: rv = OnPanMomentumEnd(panGestureInput); break;
1129 : }
1130 0 : break;
1131 : }
1132 : case MOUSE_INPUT: {
1133 0 : MouseInput mouseInput = aEvent.AsMouseInput();
1134 0 : if (!mouseInput.TransformToLocal(aTransformToApzc)) {
1135 0 : return rv;
1136 : }
1137 : break;
1138 0 : }
1139 0 : case SCROLLWHEEL_INPUT: {
1140 : ScrollWheelInput scrollInput = aEvent.AsScrollWheelInput();
1141 : if (!scrollInput.TransformToLocal(aTransformToApzc)) {
1142 0 : return rv;
1143 0 : }
1144 0 :
1145 : rv = OnScrollWheel(scrollInput);
1146 : break;
1147 0 : }
1148 0 : case PINCHGESTURE_INPUT: {
1149 : PinchGestureInput pinchInput = aEvent.AsPinchGestureInput();
1150 : if (!pinchInput.TransformToLocal(aTransformToApzc)) {
1151 0 : return rv;
1152 0 : }
1153 0 :
1154 : rv = HandleGestureEvent(pinchInput);
1155 : break;
1156 0 : }
1157 0 : case TAPGESTURE_INPUT: {
1158 : TapGestureInput tapInput = aEvent.AsTapGestureInput();
1159 : if (!tapInput.TransformToLocal(aTransformToApzc)) {
1160 0 : return rv;
1161 0 : }
1162 0 :
1163 : rv = HandleGestureEvent(tapInput);
1164 : break;
1165 : }
1166 : case KEYBOARD_INPUT: {
1167 : const KeyboardInput& keyInput = aEvent.AsKeyboardInput();
1168 : rv = OnKeyboard(keyInput);
1169 0 : break;
1170 : }
1171 0 : }
1172 :
1173 0 : return rv;
1174 : }
1175 0 :
1176 : nsEventStatus AsyncPanZoomController::HandleGestureEvent(const InputData& aEvent)
1177 0 : {
1178 0 : APZThreadUtils::AssertOnControllerThread();
1179 0 :
1180 0 : nsEventStatus rv = nsEventStatus_eIgnore;
1181 0 :
1182 : switch (aEvent.mInputType) {
1183 : case PINCHGESTURE_INPUT: {
1184 : const PinchGestureInput& pinchGestureInput = aEvent.AsPinchGestureInput();
1185 : switch (pinchGestureInput.mType) {
1186 0 : case PinchGestureInput::PINCHGESTURE_START: rv = OnScaleBegin(pinchGestureInput); break;
1187 0 : case PinchGestureInput::PINCHGESTURE_SCALE: rv = OnScale(pinchGestureInput); break;
1188 0 : case PinchGestureInput::PINCHGESTURE_END: rv = OnScaleEnd(pinchGestureInput); break;
1189 0 : }
1190 0 : break;
1191 0 : }
1192 0 : case TAPGESTURE_INPUT: {
1193 0 : const TapGestureInput& tapGestureInput = aEvent.AsTapGestureInput();
1194 : switch (tapGestureInput.mType) {
1195 : case TapGestureInput::TAPGESTURE_LONG: rv = OnLongPress(tapGestureInput); break;
1196 : case TapGestureInput::TAPGESTURE_LONG_UP: rv = OnLongPressUp(tapGestureInput); break;
1197 : case TapGestureInput::TAPGESTURE_UP: rv = OnSingleTapUp(tapGestureInput); break;
1198 0 : case TapGestureInput::TAPGESTURE_CONFIRMED: rv = OnSingleTapConfirmed(tapGestureInput); break;
1199 : case TapGestureInput::TAPGESTURE_DOUBLE: rv = OnDoubleTap(tapGestureInput); break;
1200 : case TapGestureInput::TAPGESTURE_SECOND: rv = OnSecondTap(tapGestureInput); break;
1201 0 : case TapGestureInput::TAPGESTURE_CANCEL: rv = OnCancelTap(tapGestureInput); break;
1202 : }
1203 : break;
1204 0 : }
1205 : default: MOZ_ASSERT_UNREACHABLE("Unhandled input event"); break;
1206 0 : }
1207 0 :
1208 : return rv;
1209 0 : }
1210 :
1211 : void AsyncPanZoomController::HandleTouchVelocity(uint32_t aTimesampMs, float aSpeedY)
1212 0 : {
1213 : mY.HandleTouchVelocity(aTimesampMs, aSpeedY);
1214 0 : }
1215 0 :
1216 0 : void AsyncPanZoomController::StartAutoscroll(const ScreenPoint& aPoint)
1217 : {
1218 0 : // Cancel any existing animation.
1219 : CancelAnimation();
1220 0 :
1221 0 : SetState(AUTOSCROLL);
1222 : StartAnimation(new AutoscrollAnimation(*this, aPoint));
1223 0 : }
1224 :
1225 0 : void AsyncPanZoomController::StopAutoscroll()
1226 : {
1227 0 : if (mState == AUTOSCROLL) {
1228 0 : CancelAnimation(TriggeredExternally);
1229 : }
1230 0 : }
1231 :
1232 : nsEventStatus AsyncPanZoomController::OnTouchStart(const MultiTouchInput& aEvent) {
1233 : APZC_LOG("%p got a touch-start in state %d\n", this, mState);
1234 : mPanDirRestricted = false;
1235 : ParentLayerPoint point = GetFirstTouchPoint(aEvent);
1236 :
1237 : switch (mState) {
1238 : case FLING:
1239 0 : case ANIMATING_ZOOM:
1240 0 : case SMOOTH_SCROLL:
1241 : case OVERSCROLL_ANIMATION:
1242 : case WHEEL_SCROLL:
1243 : case KEYBOARD_SCROLL:
1244 0 : case PAN_MOMENTUM:
1245 0 : case AUTOSCROLL:
1246 0 : MOZ_ASSERT(GetCurrentTouchBlock());
1247 0 : GetCurrentTouchBlock()->GetOverscrollHandoffChain()->CancelAnimations(ExcludeOverscroll);
1248 0 : MOZ_FALLTHROUGH;
1249 0 : case SCROLLBAR_DRAG:
1250 0 : case NOTHING: {
1251 : mX.StartTouch(point.x, aEvent.mTime);
1252 0 : mY.StartTouch(point.y, aEvent.mTime);
1253 0 : if (RefPtr<GeckoContentController> controller = GetGeckoContentController()) {
1254 : MOZ_ASSERT(GetCurrentTouchBlock());
1255 : controller->NotifyAPZStateChange(
1256 : GetGuid(), APZStateChange::eStartTouch,
1257 : GetCurrentTouchBlock()->GetOverscrollHandoffChain()->CanBePanned(this));
1258 : }
1259 : SetState(TOUCHING);
1260 0 : break;
1261 0 : }
1262 : case TOUCHING:
1263 : case PANNING:
1264 0 : case PANNING_LOCKED_X:
1265 : case PANNING_LOCKED_Y:
1266 : case PINCHING:
1267 0 : NS_WARNING("Received impossible touch in OnTouchStart");
1268 : break;
1269 0 : }
1270 :
1271 : return nsEventStatus_eConsumeNoDefault;
1272 : }
1273 :
1274 : nsEventStatus AsyncPanZoomController::OnTouchMove(const MultiTouchInput& aEvent) {
1275 : APZC_LOG("%p got a touch-move in state %d\n", this, mState);
1276 : switch (mState) {
1277 : case FLING:
1278 : case SMOOTH_SCROLL:
1279 0 : case NOTHING:
1280 0 : case ANIMATING_ZOOM:
1281 : // May happen if the user double-taps and drags without lifting after the
1282 0 : // second tap. Ignore the move if this happens.
1283 : return nsEventStatus_eIgnore;
1284 :
1285 : case TOUCHING: {
1286 0 : ScreenCoord panThreshold = GetTouchStartTolerance();
1287 : UpdateWithTouchAtDevicePoint(aEvent);
1288 0 :
1289 0 : if (PanDistance() < panThreshold) {
1290 : return nsEventStatus_eIgnore;
1291 : }
1292 :
1293 : ParentLayerPoint touchPoint = GetFirstTouchPoint(aEvent);
1294 0 :
1295 0 : MOZ_ASSERT(GetCurrentTouchBlock());
1296 : if (gfxPrefs::TouchActionEnabled() && GetCurrentTouchBlock()->TouchActionAllowsPanningXY()) {
1297 : // User tries to trigger a touch behavior. If allowed touch behavior is vertical pan
1298 0 : // + horizontal pan (touch-action value is equal to AUTO) we can return ConsumeNoDefault
1299 : // status immediately to trigger cancel event further. It should happen independent of
1300 : // the parent type (whether it is scrolling or not).
1301 : StartPanning(touchPoint);
1302 : return nsEventStatus_eConsumeNoDefault;
1303 : }
1304 :
1305 0 : return StartPanning(touchPoint);
1306 0 : }
1307 :
1308 : case PANNING:
1309 : case PANNING_LOCKED_X:
1310 0 : case PANNING_LOCKED_Y:
1311 0 : case PAN_MOMENTUM:
1312 : TrackTouch(aEvent);
1313 : return nsEventStatus_eConsumeNoDefault;
1314 :
1315 : case PINCHING:
1316 : // The scale gesture listener should have handled this.
1317 : NS_WARNING("Gesture listener should have handled pinching in OnTouchMove.");
1318 : return nsEventStatus_eIgnore;
1319 :
1320 : case WHEEL_SCROLL:
1321 0 : case KEYBOARD_SCROLL:
1322 0 : case OVERSCROLL_ANIMATION:
1323 : case AUTOSCROLL:
1324 : case SCROLLBAR_DRAG:
1325 : // Should not receive a touch-move in the OVERSCROLL_ANIMATION state
1326 : // as touch blocks that begin in an overscrolled state cancel the
1327 : // animation. The same is true for wheel scroll animations.
1328 0 : NS_WARNING("Received impossible touch in OnTouchMove");
1329 : break;
1330 0 : }
1331 :
1332 : return nsEventStatus_eConsumeNoDefault;
1333 : }
1334 :
1335 : nsEventStatus AsyncPanZoomController::OnTouchEnd(const MultiTouchInput& aEvent) {
1336 : APZC_LOG("%p got a touch-end in state %d\n", this, mState);
1337 0 : OnTouchEndOrCancel();
1338 0 :
1339 : // In case no touch behavior triggered previously we can avoid sending
1340 : // scroll events or requesting content repaint. This condition is added
1341 0 : // to make tests consistent - in case touch-action is NONE (and therefore
1342 : // no pans/zooms can be performed) we expected neither scroll or repaint
1343 : // events.
1344 0 : if (mState != NOTHING) {
1345 : RecursiveMutexAutoLock lock(mRecursiveMutex);
1346 : }
1347 :
1348 : switch (mState) {
1349 : case FLING:
1350 : // Should never happen.
1351 : NS_WARNING("Received impossible touch end in OnTouchEnd.");
1352 : MOZ_FALLTHROUGH;
1353 : case ANIMATING_ZOOM:
1354 : case SMOOTH_SCROLL:
1355 : case NOTHING:
1356 0 : // May happen if the user double-taps and drags without lifting after the
1357 0 : // second tap. Ignore if this happens.
1358 0 : return nsEventStatus_eIgnore;
1359 :
1360 : case TOUCHING:
1361 : // We may have some velocity stored on the axis from move events
1362 : // that were not big enough to trigger scrolling. Clear that out.
1363 : mX.SetVelocity(0);
1364 : mY.SetVelocity(0);
1365 0 : MOZ_ASSERT(GetCurrentTouchBlock());
1366 : APZC_LOG("%p still has %u touch points active\n", this,
1367 : GetCurrentTouchBlock()->GetActiveTouchCount());
1368 : // In cases where the user is panning, then taps the second finger without
1369 : // entering a pinch, we will arrive here when the second finger is lifted.
1370 0 : // However the first finger is still down so we want to remain in state
1371 : // TOUCHING.
1372 : if (GetCurrentTouchBlock()->GetActiveTouchCount() == 0) {
1373 : // It's possible we may be overscrolled if the user tapped during a
1374 0 : // previous overscroll pan. Make sure to snap back in this situation.
1375 0 : // An ancestor APZC could be overscrolled instead of this APZC, so
1376 : // walk the handoff chain as well.
1377 : GetCurrentTouchBlock()->GetOverscrollHandoffChain()->SnapBackOverscrolledApzc(this);
1378 : // SnapBackOverscrolledApzc() will put any APZC it causes to snap back
1379 : // into the OVERSCROLL_ANIMATION state. If that's not us, since we're
1380 : // done TOUCHING enter the NOTHING state.
1381 : if (mState != OVERSCROLL_ANIMATION) {
1382 : SetState(NOTHING);
1383 : }
1384 : }
1385 0 : return nsEventStatus_eIgnore;
1386 0 :
1387 0 : case PANNING:
1388 0 : case PANNING_LOCKED_X:
1389 : case PANNING_LOCKED_Y:
1390 : case PAN_MOMENTUM:
1391 0 : {
1392 : MOZ_ASSERT(GetCurrentTouchBlock());
1393 0 : mX.EndTouch(aEvent.mTime);
1394 0 : mY.EndTouch(aEvent.mTime);
1395 : return HandleEndOfPan();
1396 : }
1397 : case PINCHING:
1398 : SetState(NOTHING);
1399 : // Scale gesture listener should have handled this.
1400 : NS_WARNING("Gesture listener should have handled pinching in OnTouchEnd.");
1401 : return nsEventStatus_eIgnore;
1402 :
1403 : case WHEEL_SCROLL:
1404 0 : case KEYBOARD_SCROLL:
1405 0 : case OVERSCROLL_ANIMATION:
1406 : case AUTOSCROLL:
1407 : case SCROLLBAR_DRAG:
1408 : // Should not receive a touch-end in the OVERSCROLL_ANIMATION state
1409 : // as touch blocks that begin in an overscrolled state cancel the
1410 : // animation. The same is true for WHEEL_SCROLL.
1411 0 : NS_WARNING("Received impossible touch in OnTouchEnd");
1412 : break;
1413 0 : }
1414 0 :
1415 0 : return nsEventStatus_eConsumeNoDefault;
1416 : }
1417 :
1418 0 : nsEventStatus AsyncPanZoomController::OnTouchCancel(const MultiTouchInput& aEvent) {
1419 : APZC_LOG("%p got a touch-cancel in state %d\n", this, mState);
1420 : OnTouchEndOrCancel();
1421 0 : CancelAnimationAndGestureState();
1422 0 : return nsEventStatus_eConsumeNoDefault;
1423 : }
1424 :
1425 0 : nsEventStatus AsyncPanZoomController::OnScaleBegin(const PinchGestureInput& aEvent) {
1426 : APZC_LOG("%p got a scale-begin in state %d\n", this, mState);
1427 :
1428 : mPinchLocked = false;
1429 : mPinchPaintTimerSet = false;
1430 : // Note that there may not be a touch block at this point, if we received the
1431 0 : // PinchGestureEvent directly from widget code without any touch events.
1432 0 : if (HasReadyTouchBlock() && !GetCurrentTouchBlock()->TouchActionAllowsPinchZoom()) {
1433 0 : return nsEventStatus_eIgnore;
1434 : }
1435 :
1436 : // If zooming is not allowed, this is a two-finger pan.
1437 : // Start tracking panning distance and velocity.
1438 0 : if (!mZoomConstraints.mAllowZoom) {
1439 0 : mX.StartTouch(aEvent.mLocalFocusPoint.x, aEvent.mTime);
1440 0 : mY.StartTouch(aEvent.mLocalFocusPoint.y, aEvent.mTime);
1441 : }
1442 :
1443 : // For platforms that don't support APZ zooming, dispatch a message to the
1444 0 : // content controller, it may want to do something else with this gesture.
1445 0 : if (!gfxPrefs::APZAllowZooming()) {
1446 0 : if (RefPtr<GeckoContentController> controller = GetGeckoContentController()) {
1447 0 : controller->NotifyPinchGesture(aEvent.mType, GetGuid(), 0, aEvent.modifiers);
1448 : }
1449 0 : }
1450 :
1451 : SetState(PINCHING);
1452 0 : mX.SetVelocity(0);
1453 : mY.SetVelocity(0);
1454 : mLastZoomFocus = aEvent.mLocalFocusPoint - mFrameMetrics.GetCompositionBounds().TopLeft();
1455 0 :
1456 : return nsEventStatus_eConsumeNoDefault;
1457 : }
1458 :
1459 0 : nsEventStatus AsyncPanZoomController::OnScale(const PinchGestureInput& aEvent) {
1460 : APZC_LOG("%p got a scale in state %d\n", this, mState);
1461 :
1462 : if (HasReadyTouchBlock() && !GetCurrentTouchBlock()->TouchActionAllowsPinchZoom()) {
1463 0 : return nsEventStatus_eIgnore;
1464 0 : }
1465 :
1466 0 : if (mState != PINCHING) {
1467 : return nsEventStatus_eConsumeNoDefault;
1468 0 : }
1469 0 :
1470 0 : ParentLayerCoord spanDistance = fabsf(aEvent.mPreviousSpan - aEvent.mCurrentSpan);
1471 : ParentLayerPoint focusPoint, focusChange;
1472 : {
1473 0 : RecursiveMutexAutoLock lock(mRecursiveMutex);
1474 0 :
1475 0 : focusPoint = aEvent.mLocalFocusPoint - mFrameMetrics.GetCompositionBounds().TopLeft();
1476 0 : focusChange = mLastZoomFocus - focusPoint;
1477 : mLastZoomFocus = focusPoint;
1478 : }
1479 :
1480 : HandlePinchLocking(
1481 : ToScreenCoordinates(ParentLayerPoint(0, spanDistance), focusPoint).Length(),
1482 0 : ToScreenCoordinates(focusChange, focusPoint));
1483 0 : bool allowZoom = mZoomConstraints.mAllowZoom && !mPinchLocked;
1484 0 :
1485 : // If zooming is not allowed, this is a two-finger pan.
1486 : // Tracking panning distance and velocity.
1487 0 : // UpdateWithTouchAtDevicePoint() acquires the tree lock, so
1488 0 : // it cannot be called while the mRecursiveMutex lock is held.
1489 0 : if (!allowZoom) {
1490 : mX.UpdateWithTouchAtDevicePoint(aEvent.mLocalFocusPoint.x, 0, aEvent.mTime);
1491 : mY.UpdateWithTouchAtDevicePoint(aEvent.mLocalFocusPoint.y, 0, aEvent.mTime);
1492 0 : }
1493 :
1494 : if (!gfxPrefs::APZAllowZooming()) {
1495 : if (RefPtr<GeckoContentController> controller = GetGeckoContentController()) {
1496 : controller->NotifyPinchGesture(aEvent.mType, GetGuid(),
1497 : ViewAs<LayoutDevicePixel>(aEvent.mCurrentSpan - aEvent.mPreviousSpan,
1498 : PixelCastJustification::LayoutDeviceIsParentLayerForRCDRSF),
1499 : aEvent.modifiers);
1500 : }
1501 0 : }
1502 0 :
1503 : // Only the root APZC is zoomable, and the root APZC is not allowed to have
1504 : // different x and y scales. If it did, the calculations in this function
1505 0 : // would have to be adjusted (as e.g. it would no longer be valid to take
1506 : // the minimum or maximum of the ratios of the widths and heights of the
1507 0 : // page rect and the composition bounds).
1508 0 : MOZ_ASSERT(mFrameMetrics.IsRootContent());
1509 : MOZ_ASSERT(mFrameMetrics.GetZoom().AreScalesSame());
1510 :
1511 : {
1512 0 : RecursiveMutexAutoLock lock(mRecursiveMutex);
1513 0 :
1514 0 : CSSToParentLayerScale userZoom = mFrameMetrics.GetZoom().ToScaleFactor();
1515 : CSSPoint cssFocusPoint = focusPoint / mFrameMetrics.GetZoom();
1516 :
1517 : // If displacing by the change in focus point will take us off page bounds,
1518 : // then reduce the displacement such that it doesn't.
1519 : focusChange.x -= mX.DisplacementWillOverscrollAmount(focusChange.x);
1520 : focusChange.y -= mY.DisplacementWillOverscrollAmount(focusChange.y);
1521 : ScrollBy(focusChange / userZoom);
1522 0 :
1523 0 : // If the span is zero or close to it, we don't want to process this zoom
1524 : // change because we're going to get wonky numbers for the spanRatio. So
1525 : // let's bail out here. Note that we do this after the focus-change-scroll
1526 0 : // above, so that if we have a pinch with zero span but changing focus,
1527 0 : // such as generated by some Synaptics touchpads on Windows, we still
1528 0 : // scroll properly.
1529 : float prevSpan = aEvent.mPreviousSpan;
1530 0 : if (fabsf(prevSpan) <= EPSILON || fabsf(aEvent.mCurrentSpan) <= EPSILON) {
1531 : // We might have done a nonzero ScrollBy above, so update metrics and
1532 : // repaint/recomposite
1533 : ScheduleCompositeAndMaybeRepaint();
1534 : UpdateSharedCompositorFrameMetrics();
1535 0 : return nsEventStatus_eConsumeNoDefault;
1536 : }
1537 0 : float spanRatio = aEvent.mCurrentSpan / aEvent.mPreviousSpan;
1538 0 :
1539 0 : // When we zoom in with focus, we can zoom too much towards the boundaries
1540 0 : // that we actually go over them. These are the needed displacements along
1541 0 : // either axis such that we don't overscroll the boundaries when zooming.
1542 0 : CSSPoint neededDisplacement;
1543 0 :
1544 0 : CSSToParentLayerScale realMinZoom = mZoomConstraints.mMinZoom;
1545 : CSSToParentLayerScale realMaxZoom = mZoomConstraints.mMaxZoom;
1546 : realMinZoom.scale = std::max(realMinZoom.scale,
1547 0 : mFrameMetrics.GetCompositionBounds().Width() / mFrameMetrics.GetScrollableRect().Width());
1548 0 : realMinZoom.scale = std::max(realMinZoom.scale,
1549 0 : mFrameMetrics.GetCompositionBounds().Height() / mFrameMetrics.GetScrollableRect().Height());
1550 : if (realMaxZoom < realMinZoom) {
1551 0 : realMaxZoom = realMinZoom;
1552 0 : }
1553 0 :
1554 0 : bool doScale = allowZoom && (
1555 : (spanRatio > 1.0 && userZoom < realMaxZoom) ||
1556 : (spanRatio < 1.0 && userZoom > realMinZoom));
1557 :
1558 0 : if (doScale) {
1559 0 : spanRatio = clamped(spanRatio,
1560 : realMinZoom.scale / userZoom.scale,
1561 0 : realMaxZoom.scale / userZoom.scale);
1562 :
1563 0 : // Note that the spanRatio here should never put us into OVERSCROLL_BOTH because
1564 0 : // up above we clamped it.
1565 : neededDisplacement.x = -mX.ScaleWillOverscrollAmount(spanRatio, cssFocusPoint.x);
1566 : neededDisplacement.y = -mY.ScaleWillOverscrollAmount(spanRatio, cssFocusPoint.y);
1567 :
1568 0 : ScaleWithFocus(spanRatio, cssFocusPoint);
1569 0 :
1570 0 : if (neededDisplacement != CSSPoint()) {
1571 0 : ScrollBy(neededDisplacement);
1572 0 : }
1573 0 :
1574 0 : // We don't want to redraw on every scale, so throttle it.
1575 : if (!mPinchPaintTimerSet) {
1576 : const int delay = gfxPrefs::APZScaleRepaintDelay();
1577 : if (delay >= 0) {
1578 : if (RefPtr<GeckoContentController> controller = GetGeckoContentController()) {
1579 0 : mPinchPaintTimerSet = true;
1580 : controller->PostDelayedTask(
1581 : NewRunnableMethod(
1582 : "layers::AsyncPanZoomController::"
1583 : "DoDelayedRequestContentRepaint",
1584 0 : this,
1585 : &AsyncPanZoomController::DoDelayedRequestContentRepaint),
1586 : delay);
1587 : }
1588 0 : }
1589 : }
1590 :
1591 : UpdateSharedCompositorFrameMetrics();
1592 :
1593 0 : } else {
1594 : // Trigger a repaint request after scrolling.
1595 : RequestContentRepaint();
1596 0 : }
1597 :
1598 : // We did a ScrollBy call above even if we didn't do a scale, so we
1599 0 : // should composite for that.
1600 : ScheduleComposite();
1601 : }
1602 0 :
1603 : return nsEventStatus_eConsumeNoDefault;
1604 0 : }
1605 :
1606 : nsEventStatus AsyncPanZoomController::OnScaleEnd(const PinchGestureInput& aEvent) {
1607 : APZC_LOG("%p got a scale-end in state %d\n", this, mState);
1608 0 :
1609 0 : mPinchPaintTimerSet = false;
1610 0 :
1611 : if (HasReadyTouchBlock() && !GetCurrentTouchBlock()->TouchActionAllowsPinchZoom()) {
1612 : return nsEventStatus_eIgnore;
1613 : }
1614 :
1615 0 : if (!gfxPrefs::APZAllowZooming()) {
1616 0 : if (RefPtr<GeckoContentController> controller = GetGeckoContentController()) {
1617 0 : controller->NotifyPinchGesture(aEvent.mType, GetGuid(), 0, aEvent.modifiers);
1618 0 : }
1619 : }
1620 :
1621 : {
1622 0 : RecursiveMutexAutoLock lock(mRecursiveMutex);
1623 0 : ScheduleComposite();
1624 0 : RequestContentRepaint();
1625 0 : UpdateSharedCompositorFrameMetrics();
1626 0 : }
1627 0 :
1628 : // Non-negative focus point would indicate that one finger is still down
1629 : if (aEvent.mLocalFocusPoint != PinchGestureInput::BothFingersLifted()) {
1630 : if (mZoomConstraints.mAllowZoom) {
1631 0 : mPanDirRestricted = false;
1632 : mX.StartTouch(aEvent.mLocalFocusPoint.x, aEvent.mTime);
1633 : mY.StartTouch(aEvent.mLocalFocusPoint.y, aEvent.mTime);
1634 : SetState(TOUCHING);
1635 : } else {
1636 : // If zooming isn't allowed, StartTouch() was already called
1637 : // in OnScaleBegin().
1638 : StartPanning(aEvent.mLocalFocusPoint);
1639 : }
1640 0 : } else {
1641 0 : // Otherwise, handle the fingers being lifted.
1642 0 :
1643 : // Some of the code paths below, like ScrollSnap() or HandleEndOfPan(),
1644 0 : // may start an animation, but otherwise we want to end up in the NOTHING
1645 0 : // state. To avoid state change notification churn, we use a
1646 : // notification blocker.
1647 : bool stateWasPinching = (mState == PINCHING);
1648 : StateChangeNotificationBlocker blocker(this);
1649 : SetState(NOTHING);
1650 :
1651 : if (mZoomConstraints.mAllowZoom) {
1652 : RecursiveMutexAutoLock lock(mRecursiveMutex);
1653 :
1654 : // We can get into a situation where we are overscrolled at the end of a
1655 0 : // pinch if we go into overscroll with a two-finger pan, and then turn
1656 0 : // that into a pinch by increasing the span sufficiently. In such a case,
1657 : // there is no snap-back animation to get us out of overscroll, so we need
1658 0 : // to get out of it somehow.
1659 : // Moreover, in cases of scroll handoff, the overscroll can be on an APZC
1660 : // further up in the handoff chain rather than on the current APZC, so
1661 : // we need to clear overscroll along the entire handoff chain.
1662 0 : if (HasReadyTouchBlock()) {
1663 : GetCurrentTouchBlock()->GetOverscrollHandoffChain()->ClearOverscroll();
1664 : } else {
1665 0 : ClearOverscroll();
1666 0 : }
1667 0 : // Along with clearing the overscroll, we also want to snap to the nearest
1668 : // snap point as appropriate.
1669 0 : ScrollSnap();
1670 0 : } else {
1671 : // when zoom is not allowed
1672 : mX.EndTouch(aEvent.mTime);
1673 : mY.EndTouch(aEvent.mTime);
1674 : if (stateWasPinching) {
1675 : // still pinching
1676 : if (HasReadyTouchBlock()) {
1677 : return HandleEndOfPan();
1678 0 : }
1679 : }
1680 0 : }
1681 0 : }
1682 0 : return nsEventStatus_eConsumeNoDefault;
1683 : }
1684 :
1685 : nsEventStatus AsyncPanZoomController::HandleEndOfPan()
1686 : {
1687 0 : MOZ_ASSERT(GetCurrentTouchBlock());
1688 0 : GetCurrentTouchBlock()->GetOverscrollHandoffChain()->FlushRepaints();
1689 : ParentLayerPoint flingVelocity = GetVelocityVector();
1690 :
1691 : // Clear our velocities; if DispatchFling() gives the fling to us,
1692 : // the fling velocity gets *added* to our existing velocity in
1693 0 : // AcceptFling().
1694 0 : mX.SetVelocity(0);
1695 : mY.SetVelocity(0);
1696 : // Clear our state so that we don't stay in the PANNING state
1697 : // if DispatchFling() gives the fling to somone else. However,
1698 : // don't send the state change notification until we've determined
1699 0 : // what our final state is to avoid notification churn.
1700 : StateChangeNotificationBlocker blocker(this);
1701 : SetState(NOTHING);
1702 0 :
1703 0 : APZC_LOG("%p starting a fling animation if %f >= %f\n", this,
1704 : flingVelocity.Length().value, gfxPrefs::APZFlingMinVelocityThreshold());
1705 :
1706 : if (flingVelocity.Length() < gfxPrefs::APZFlingMinVelocityThreshold()) {
1707 : // Relieve overscroll now if needed, since we will not transition to a fling
1708 : // animation and then an overscroll animation, and relieve it then.
1709 0 : GetCurrentTouchBlock()->GetOverscrollHandoffChain()->SnapBackOverscrolledApzc(this);
1710 : return nsEventStatus_eConsumeNoDefault;
1711 0 : }
1712 :
1713 0 : // Make a local copy of the tree manager pointer and check that it's not
1714 0 : // null before calling DispatchFling(). This is necessary because Destroy(),
1715 : // which nulls out mTreeManager, could be called concurrently.
1716 : if (APZCTreeManager* treeManagerLocal = GetApzcTreeManager()) {
1717 : const FlingHandoffState handoffState{flingVelocity,
1718 : GetCurrentTouchBlock()->GetOverscrollHandoffChain(),
1719 : false /* not handoff */,
1720 0 : GetCurrentTouchBlock()->GetScrolledApzc()};
1721 : treeManagerLocal->DispatchFling(this, handoffState);
1722 0 : }
1723 : return nsEventStatus_eConsumeNoDefault;
1724 0 : }
1725 0 :
1726 : bool
1727 : AsyncPanZoomController::ConvertToGecko(const ScreenIntPoint& aPoint, LayoutDevicePoint* aOut)
1728 0 : {
1729 0 : if (APZCTreeManager* treeManagerLocal = GetApzcTreeManager()) {
1730 : ScreenToScreenMatrix4x4 transformScreenToGecko =
1731 : treeManagerLocal->GetScreenToApzcTransform(this)
1732 : * treeManagerLocal->GetApzcToGeckoTransform(this);
1733 0 :
1734 : Maybe<ScreenIntPoint> layoutPoint = UntransformBy(
1735 0 : transformScreenToGecko, aPoint);
1736 : if (!layoutPoint) {
1737 : return false;
1738 : }
1739 :
1740 : *aOut = LayoutDevicePoint(ViewAs<LayoutDevicePixel>(*layoutPoint,
1741 0 : PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent));
1742 : return true;
1743 : }
1744 0 : return false;
1745 : }
1746 :
1747 0 : CSSCoord
1748 : AsyncPanZoomController::ConvertScrollbarPoint(const ParentLayerPoint& aScrollbarPoint,
1749 : const ScrollbarData& aThumbData) const
1750 0 : {
1751 : RecursiveMutexAutoLock lock(mRecursiveMutex);
1752 :
1753 0 : // First, get it into the right coordinate space.
1754 0 : CSSPoint scrollbarPoint = aScrollbarPoint / mFrameMetrics.GetZoom();
1755 0 : // The scrollbar can be transformed with the frame but the pres shell
1756 0 : // resolution is only applied to the scroll frame.
1757 : scrollbarPoint = scrollbarPoint * mFrameMetrics.GetPresShellResolution();
1758 :
1759 : // Now, get it to be relative to the beginning of the scroll track.
1760 : CSSRect cssCompositionBound = mFrameMetrics.CalculateCompositionBoundsInCssPixelsOfSurroundingContent();
1761 : return GetAxisStart(*aThumbData.mDirection, scrollbarPoint)
1762 : - GetAxisStart(*aThumbData.mDirection, cssCompositionBound)
1763 0 : - aThumbData.mScrollTrackStart;
1764 0 : }
1765 :
1766 : static bool
1767 : AllowsScrollingMoreThanOnePage(double aMultiplier)
1768 0 : {
1769 : const int32_t kMinAllowPageScroll =
1770 : EventStateManager::MIN_MULTIPLIER_VALUE_ALLOWING_OVER_ONE_PAGE_SCROLL;
1771 0 : return Abs(aMultiplier) >= kMinAllowPageScroll;
1772 0 : }
1773 0 :
1774 : ParentLayerPoint
1775 : AsyncPanZoomController::GetScrollWheelDelta(const ScrollWheelInput& aEvent) const
1776 : {
1777 0 : return GetScrollWheelDelta(aEvent,
1778 : aEvent.mDeltaX, aEvent.mDeltaY,
1779 : aEvent.mUserDeltaMultiplierX,
1780 : aEvent.mUserDeltaMultiplierY);
1781 : }
1782 :
1783 0 : ParentLayerPoint
1784 0 : AsyncPanZoomController::GetScrollWheelDelta(const ScrollWheelInput& aEvent,
1785 : double aDeltaX,
1786 : double aDeltaY,
1787 : double aMultiplierX,
1788 0 : double aMultiplierY) const
1789 0 : {
1790 0 : ParentLayerSize scrollAmount;
1791 0 : ParentLayerSize pageScrollSize;
1792 0 :
1793 0 : {
1794 0 : // Grab the lock to access the frame metrics.
1795 : RecursiveMutexAutoLock lock(mRecursiveMutex);
1796 : LayoutDeviceIntSize scrollAmountLD = mScrollMetadata.GetLineScrollAmount();
1797 0 : LayoutDeviceIntSize pageScrollSizeLD = mScrollMetadata.GetPageScrollAmount();
1798 0 : scrollAmount = scrollAmountLD /
1799 : mFrameMetrics.GetDevPixelsPerCSSPixel() * mFrameMetrics.GetZoom();
1800 0 : pageScrollSize = pageScrollSizeLD /
1801 0 : mFrameMetrics.GetDevPixelsPerCSSPixel() * mFrameMetrics.GetZoom();
1802 0 : }
1803 :
1804 : ParentLayerPoint delta;
1805 0 : switch (aEvent.mDeltaType) {
1806 0 : case ScrollWheelInput::SCROLLDELTA_LINE: {
1807 0 : delta.x = aDeltaX * scrollAmount.width;
1808 : delta.y = aDeltaY * scrollAmount.height;
1809 : break;
1810 0 : }
1811 0 : case ScrollWheelInput::SCROLLDELTA_PAGE: {
1812 0 : delta.x = aDeltaX * pageScrollSize.width;
1813 : delta.y = aDeltaY * pageScrollSize.height;
1814 : break;
1815 : }
1816 : case ScrollWheelInput::SCROLLDELTA_PIXEL: {
1817 0 : delta = ToParentLayerCoordinates(ScreenPoint(aDeltaX, aDeltaY),
1818 0 : aEvent.mOrigin);
1819 : break;
1820 : }
1821 : }
1822 :
1823 : // Apply user-set multipliers.
1824 0 : delta.x *= aMultiplierX;
1825 0 : delta.y *= aMultiplierY;
1826 0 :
1827 0 : // For the conditions under which we allow system scroll overrides, see
1828 0 : // EventStateManager::DeltaAccumulator::ComputeScrollAmountForDefaultAction
1829 0 : // and WheelTransaction::OverrideSystemScrollSpeed. Note that we do *not*
1830 : // restrict this to the root content, see bug 1217715 for discussion on this.
1831 : if (gfxPrefs::MouseWheelHasRootScrollDeltaOverride() &&
1832 : !aEvent.IsCustomizedByUserPrefs() &&
1833 : aEvent.mDeltaType == ScrollWheelInput::SCROLLDELTA_LINE &&
1834 0 : aEvent.mAllowToOverrideSystemScrollSpeed) {
1835 0 : delta.x = WidgetWheelEvent::ComputeOverriddenDelta(delta.x, false);
1836 : delta.y = WidgetWheelEvent::ComputeOverriddenDelta(delta.y, true);
1837 0 : }
1838 0 :
1839 0 : // If this is a line scroll, and this event was part of a scroll series, then
1840 0 : // it might need extra acceleration. See WheelHandlingHelper.cpp.
1841 0 : if (aEvent.mDeltaType == ScrollWheelInput::SCROLLDELTA_LINE &&
1842 0 : aEvent.mScrollSeriesNumber > 0)
1843 : {
1844 : int32_t start = gfxPrefs::MouseWheelAccelerationStart();
1845 : if (start >= 0 && aEvent.mScrollSeriesNumber >= uint32_t(start)) {
1846 : int32_t factor = gfxPrefs::MouseWheelAccelerationFactor();
1847 : if (factor > 0) {
1848 : delta.x = ComputeAcceleratedWheelDelta(delta.x, aEvent.mScrollSeriesNumber, factor);
1849 0 : delta.y = ComputeAcceleratedWheelDelta(delta.y, aEvent.mScrollSeriesNumber, factor);
1850 0 : }
1851 : }
1852 0 : }
1853 :
1854 : // We shouldn't scroll more than one page at once except when the
1855 0 : // user preference is large.
1856 0 : if (!AllowsScrollingMoreThanOnePage(aMultiplierX) &&
1857 : Abs(delta.x) > pageScrollSize.width) {
1858 0 : delta.x = (delta.x >= 0)
1859 : ? pageScrollSize.width
1860 : : -pageScrollSize.width;
1861 : }
1862 0 : if (!AllowsScrollingMoreThanOnePage(aMultiplierY) &&
1863 : Abs(delta.y) > pageScrollSize.height) {
1864 : delta.y = (delta.y >= 0)
1865 : ? pageScrollSize.height
1866 0 : : -pageScrollSize.height;
1867 : }
1868 :
1869 0 : return delta;
1870 : }
1871 :
1872 0 : nsEventStatus
1873 0 : AsyncPanZoomController::OnKeyboard(const KeyboardInput& aEvent)
1874 : {
1875 : // Mark that this APZC has async key scrolled
1876 0 : mTestHasAsyncKeyScrolled = true;
1877 0 :
1878 : // Calculate the destination for this keyboard scroll action
1879 : CSSPoint destination = GetKeyboardDestination(aEvent.mAction);
1880 : bool scrollSnapped = MaybeAdjustDestinationForScrollSnapping(aEvent, destination);
1881 0 :
1882 0 : // If smooth scrolling is disabled, then scroll immediately to the destination
1883 0 : if (!gfxPrefs::SmoothScrollEnabled()) {
1884 : CancelAnimation();
1885 :
1886 0 : // CallDispatchScroll interprets the start and end points as the start and
1887 : // end of a touch scroll so they need to be reversed.
1888 : ParentLayerPoint startPoint = destination * mFrameMetrics.GetZoom();
1889 0 : ParentLayerPoint endPoint = mFrameMetrics.GetScrollOffset() * mFrameMetrics.GetZoom();
1890 : ParentLayerPoint delta = endPoint - startPoint;
1891 0 :
1892 : ScreenPoint distance = ToScreenCoordinates(
1893 0 : ParentLayerPoint(fabs(delta.x), fabs(delta.y)), startPoint);
1894 :
1895 0 : OverscrollHandoffState handoffState(
1896 : *mInputQueue->GetCurrentKeyboardBlock()->GetOverscrollHandoffChain(),
1897 : distance,
1898 : ScrollSource::Keyboard);
1899 :
1900 : CallDispatchScroll(startPoint, endPoint, handoffState);
1901 :
1902 : SetState(NOTHING);
1903 0 :
1904 : return nsEventStatus_eConsumeDoDefault;
1905 0 : }
1906 :
1907 : // The lock must be held across the entire update operation, so the
1908 : // compositor doesn't end the animation before we get a chance to
1909 : // update it.
1910 0 : RecursiveMutexAutoLock lock(mRecursiveMutex);
1911 0 :
1912 : if (scrollSnapped) {
1913 : // If we're scroll snapping, use a smooth scroll animation to get
1914 : // the desired physics. Note that SmoothScrollTo() will re-use an
1915 0 : // existing smooth scroll animation if there is one.
1916 0 : APZC_LOG("%p keyboard scrolling to snap point %s\n", this, Stringify(destination).c_str());
1917 0 : SmoothScrollTo(destination);
1918 : return nsEventStatus_eConsumeDoDefault;
1919 0 : }
1920 0 :
1921 : // Use a keyboard scroll animation to scroll, reusing an existing one if it exists
1922 : if (mState != KEYBOARD_SCROLL) {
1923 : CancelAnimation();
1924 : SetState(KEYBOARD_SCROLL);
1925 :
1926 : nsPoint initialPosition = CSSPoint::ToAppUnits(mFrameMetrics.GetScrollOffset());
1927 0 : StartAnimation(new KeyboardScrollAnimation(*this, initialPosition, aEvent.mAction.mType));
1928 0 : }
1929 :
1930 0 : // Convert velocity from ParentLayerPoints/ms to ParentLayerPoints/s and then
1931 0 : // to appunits/second.
1932 : nsPoint velocity =
1933 0 : CSSPoint::ToAppUnits(
1934 0 : ParentLayerPoint(mX.GetVelocity() * 1000.0f, mY.GetVelocity() * 1000.0f) /
1935 0 : mFrameMetrics.GetZoom());
1936 :
1937 0 : KeyboardScrollAnimation* animation = mAnimation->AsKeyboardScrollAnimation();
1938 : MOZ_ASSERT(animation);
1939 :
1940 : animation->UpdateDestination(aEvent.mTimeStamp,
1941 0 : CSSPixel::ToAppUnits(destination),
1942 : nsSize(velocity.x, velocity.y));
1943 0 :
1944 0 : return nsEventStatus_eConsumeDoDefault;
1945 0 : }
1946 0 :
1947 : CSSPoint
1948 : AsyncPanZoomController::GetKeyboardDestination(const KeyboardScrollAction& aAction) const
1949 : {
1950 0 : CSSSize lineScrollSize;
1951 : CSSSize pageScrollSize;
1952 0 : CSSPoint scrollOffset;
1953 0 : CSSRect scrollRect;
1954 0 :
1955 0 : {
1956 : // Grab the lock to access the frame metrics.
1957 0 : RecursiveMutexAutoLock lock(mRecursiveMutex);
1958 0 :
1959 0 : lineScrollSize = mScrollMetadata.GetLineScrollAmount() /
1960 0 : mFrameMetrics.GetDevPixelsPerCSSPixel();
1961 0 : pageScrollSize = mScrollMetadata.GetPageScrollAmount() /
1962 0 : mFrameMetrics.GetDevPixelsPerCSSPixel();
1963 :
1964 0 : if (mState == WHEEL_SCROLL) {
1965 : scrollOffset = mAnimation->AsWheelScrollAnimation()->GetDestination();
1966 : } else if (mState == SMOOTH_SCROLL) {
1967 0 : scrollOffset = mAnimation->AsSmoothScrollAnimation()->GetDestination();
1968 : } else if (mState == KEYBOARD_SCROLL) {
1969 : scrollOffset = mAnimation->AsKeyboardScrollAnimation()->GetDestination();
1970 : } else {
1971 0 : scrollOffset = mFrameMetrics.GetScrollOffset();
1972 : }
1973 0 :
1974 : scrollRect = mFrameMetrics.GetScrollableRect();
1975 0 : }
1976 :
1977 0 : // Calculate the scroll destination based off of the scroll type and direction
1978 0 : CSSPoint scrollDestination = scrollOffset;
1979 :
1980 0 : switch (aAction.mType) {
1981 : case KeyboardScrollAction::eScrollCharacter: {
1982 : int32_t scrollDistance = gfxPrefs::ToolkitHorizontalScrollDistance();
1983 :
1984 : if (aAction.mForward) {
1985 0 : scrollDestination.x += scrollDistance * lineScrollSize.width;
1986 : } else {
1987 0 : scrollDestination.x -= scrollDistance * lineScrollSize.width;
1988 0 : }
1989 : break;
1990 0 : }
1991 : case KeyboardScrollAction::eScrollLine: {
1992 : int32_t scrollDistance = gfxPrefs::ToolkitVerticalScrollDistance();
1993 :
1994 : if (aAction.mForward) {
1995 0 : scrollDestination.y += scrollDistance * lineScrollSize.height;
1996 0 : } else {
1997 : scrollDestination.y -= scrollDistance * lineScrollSize.height;
1998 0 : }
1999 : break;
2000 : }
2001 : case KeyboardScrollAction::eScrollPage: {
2002 : if (aAction.mForward) {
2003 0 : scrollDestination.y += pageScrollSize.height;
2004 0 : } else {
2005 : scrollDestination.y -= pageScrollSize.height;
2006 : }
2007 : break;
2008 : }
2009 : case KeyboardScrollAction::eScrollComplete: {
2010 : if (aAction.mForward) {
2011 : scrollDestination.y = scrollRect.YMost();
2012 0 : } else {
2013 : scrollDestination.y = scrollRect.Y();
2014 : }
2015 : break;
2016 0 : }
2017 : }
2018 0 :
2019 0 : return scrollDestination;
2020 0 : }
2021 0 :
2022 0 : ParentLayerPoint
2023 0 : AsyncPanZoomController::GetDeltaForEvent(const InputData& aEvent) const
2024 : {
2025 0 : ParentLayerPoint delta;
2026 : if (aEvent.mInputType == SCROLLWHEEL_INPUT) {
2027 : delta = GetScrollWheelDelta(aEvent.AsScrollWheelInput());
2028 : } else if (aEvent.mInputType == PANGESTURE_INPUT) {
2029 : const PanGestureInput& panInput = aEvent.AsPanGestureInput();
2030 0 : delta = ToParentLayerCoordinates(panInput.UserMultipliedPanDisplacement(), panInput.mPanStartPoint);
2031 : }
2032 0 : return delta;
2033 0 : }
2034 :
2035 : // Return whether or not the underlying layer can be scrolled on either axis.
2036 : bool
2037 0 : AsyncPanZoomController::CanScroll(const InputData& aEvent) const
2038 0 : {
2039 : ParentLayerPoint delta = GetDeltaForEvent(aEvent);
2040 : if (!delta.x && !delta.y) {
2041 : return false;
2042 : }
2043 :
2044 : if (SCROLLWHEEL_INPUT == aEvent.mInputType) {
2045 0 : const ScrollWheelInput& scrollWheelInput = aEvent.AsScrollWheelInput();
2046 0 : // If it's a wheel scroll, we first check if it is an auto-dir scroll.
2047 0 : // 1. For an auto-dir scroll, check if it's delta should be adjusted, if it
2048 0 : // is, then we can conclude it must be scrollable; otherwise, fall back
2049 0 : // to checking if it is scrollable without adjusting its delta.
2050 0 : // 2. For a non-auto-dir scroll, simply check if it is scrollable without
2051 0 : // adjusting its delta.
2052 0 : if (scrollWheelInput.IsAutoDir()) {
2053 : RecursiveMutexAutoLock lock(mRecursiveMutex);
2054 : auto deltaX = scrollWheelInput.mDeltaX;
2055 0 : auto deltaY = scrollWheelInput.mDeltaY;
2056 : bool isRTL = IsContentOfHonouredTargetRightToLeft(
2057 : scrollWheelInput.HonoursRoot());
2058 0 : APZAutoDirWheelDeltaAdjuster adjuster(deltaX, deltaY, mX, mY, isRTL);
2059 : if (adjuster.ShouldBeAdjusted()) {
2060 0 : // If we detect that the delta values should be adjusted for an auto-dir
2061 : // wheel scroll, then it is impossible to be an unscrollable scroll.
2062 : return true;
2063 : }
2064 0 : }
2065 : return CanScrollWithWheel(delta);
2066 0 : }
2067 0 : return CanScroll(delta);
2068 0 : }
2069 0 :
2070 : ScrollDirections
2071 0 : AsyncPanZoomController::GetAllowedHandoffDirections() const
2072 0 : {
2073 : ScrollDirections result;
2074 0 : RecursiveMutexAutoLock lock(mRecursiveMutex);
2075 : if (mX.OverscrollBehaviorAllowsHandoff()) {
2076 : result += ScrollDirection::eHorizontal;
2077 : }
2078 0 : if (mY.OverscrollBehaviorAllowsHandoff()) {
2079 : result += ScrollDirection::eVertical;
2080 0 : }
2081 0 : return result;
2082 : }
2083 :
2084 : bool
2085 0 : AsyncPanZoomController::CanScroll(const ParentLayerPoint& aDelta) const
2086 : {
2087 0 : RecursiveMutexAutoLock lock(mRecursiveMutex);
2088 : return mX.CanScroll(aDelta.x) || mY.CanScroll(aDelta.y);
2089 : }
2090 :
2091 : bool
2092 0 : AsyncPanZoomController::CanScrollWithWheel(const ParentLayerPoint& aDelta) const
2093 0 : {
2094 0 : RecursiveMutexAutoLock lock(mRecursiveMutex);
2095 :
2096 : // For more details about the concept of a disregarded direction, refer to the
2097 0 : // code in struct ScrollMetadata which defines mDisregardedDirection.
2098 0 : Maybe<ScrollDirection> disregardedDirection =
2099 : mScrollMetadata.GetDisregardedDirection();
2100 : if (mX.CanScroll(aDelta.x) &&
2101 0 : disregardedDirection != Some(ScrollDirection::eHorizontal)) {
2102 : return true;
2103 : }
2104 : if (mY.CanScroll(aDelta.y) &&
2105 0 : disregardedDirection != Some(ScrollDirection::eVertical)) {
2106 : return true;
2107 0 : }
2108 0 : return false;
2109 0 : }
2110 0 :
2111 : bool
2112 0 : AsyncPanZoomController::CanScroll(ScrollDirection aDirection) const
2113 : {
2114 : RecursiveMutexAutoLock lock(mRecursiveMutex);
2115 : switch (aDirection) {
2116 : case ScrollDirection::eHorizontal: return mX.CanScroll();
2117 0 : case ScrollDirection::eVertical: return mY.CanScroll();
2118 : }
2119 : MOZ_ASSERT_UNREACHABLE("Invalid value");
2120 0 : return false;
2121 0 : }
2122 :
2123 0 : bool
2124 0 : AsyncPanZoomController::IsContentOfHonouredTargetRightToLeft(
2125 : bool aHonoursRoot) const
2126 : {
2127 : if (aHonoursRoot) {
2128 0 : return mScrollMetadata.IsAutoDirRootContentRTL();
2129 : }
2130 0 : RecursiveMutexAutoLock lock(mRecursiveMutex);
2131 0 : return mFrameMetrics.IsHorizontalContentRightToLeft();
2132 0 : }
2133 :
2134 0 : bool
2135 0 : AsyncPanZoomController::AllowScrollHandoffInCurrentBlock() const
2136 : {
2137 : bool result = mInputQueue->AllowScrollHandoff();
2138 : if (!gfxPrefs::APZAllowImmediateHandoff()) {
2139 0 : if (InputBlockState* currentBlock = GetCurrentInputBlock()) {
2140 : // Do not allow handoff beyond the first APZC to scroll.
2141 : if (currentBlock->GetScrolledApzc() == this) {
2142 0 : result = false;
2143 : }
2144 0 : }
2145 0 : }
2146 0 : return result;
2147 : }
2148 0 :
2149 0 : void AsyncPanZoomController::DoDelayedRequestContentRepaint()
2150 : {
2151 : if (!IsDestroyed() && mPinchPaintTimerSet) {
2152 0 : RecursiveMutexAutoLock lock(mRecursiveMutex);
2153 : RequestContentRepaint();
2154 : }
2155 : mPinchPaintTimerSet = false;
2156 0 : }
2157 0 :
2158 : static void
2159 0 : AdjustDeltaForAllowedScrollDirections(
2160 0 : ParentLayerPoint& aDelta,
2161 : const ScrollDirections& aAllowedScrollDirections)
2162 0 : {
2163 : if (!aAllowedScrollDirections.contains(ScrollDirection::eHorizontal)) {
2164 0 : aDelta.x = 0;
2165 : }
2166 : if (!aAllowedScrollDirections.contains(ScrollDirection::eVertical)) {
2167 : aDelta.y = 0;
2168 : }
2169 : }
2170 :
2171 0 : nsEventStatus AsyncPanZoomController::OnScrollWheel(const ScrollWheelInput& aEvent)
2172 0 : {
2173 0 : // Get the scroll wheel's delta values in parent-layer pixels. But before
2174 0 : // getting the values, we need to check if it is an auto-dir scroll and if it
2175 0 : // should be adjusted, if both answers are yes, let's adjust X and Y values
2176 : // first, and then get the delta values in parent-layer pixels based on the
2177 : // adjusted values.
2178 0 : bool adjustedByAutoDir = false;
2179 0 : auto deltaX = aEvent.mDeltaX;
2180 0 : auto deltaY = aEvent.mDeltaY;
2181 0 : ParentLayerPoint delta;
2182 0 : if (aEvent.IsAutoDir()) {
2183 0 : // It's an auto-dir scroll, so check if its delta should be adjusted, if so,
2184 : // adjust it.
2185 : RecursiveMutexAutoLock lock(mRecursiveMutex);
2186 : bool isRTL = IsContentOfHonouredTargetRightToLeft(aEvent.HonoursRoot());
2187 : APZAutoDirWheelDeltaAdjuster adjuster(deltaX, deltaY, mX, mY, isRTL);
2188 : if (adjuster.ShouldBeAdjusted()) {
2189 0 : adjuster.Adjust();
2190 : adjustedByAutoDir = true;
2191 : }
2192 : }
2193 : // Ensure the calls to GetScrollWheelDelta are outside the mRecursiveMutex
2194 : // lock since these calls may acquire the APZ tree lock. Holding mRecursiveMutex
2195 : // while acquiring the APZ tree lock is lock ordering violation.
2196 : if (adjustedByAutoDir) {
2197 : // If the original delta values have been adjusted, we pass them to
2198 0 : // replace the original delta values in |aEvent| so that the delta values
2199 0 : // in parent-layer pixels are caculated based on the adjusted values, not
2200 : // the original ones.
2201 : // Pay special attention to the last two parameters. They are in a swaped
2202 : // order so that they still correspond to their delta after adjustment.
2203 : delta = GetScrollWheelDelta(aEvent,
2204 0 : deltaX, deltaY,
2205 : aEvent.mUserDeltaMultiplierY,
2206 : aEvent.mUserDeltaMultiplierX);
2207 : } else {
2208 : // If the original delta values haven't been adjusted by auto-dir, just pass
2209 : // the |aEvent| and caculate the delta values in parent-layer pixels based
2210 0 : // on the original delta values from |aEvent|.
2211 0 : delta = GetScrollWheelDelta(aEvent);
2212 : }
2213 :
2214 : APZC_LOG("%p got a scroll-wheel with delta in parent-layer pixels: %s\n",
2215 0 : this, Stringify(delta).c_str());
2216 :
2217 0 : if (adjustedByAutoDir) {
2218 0 : MOZ_ASSERT(delta.x || delta.y,
2219 0 : "Adjusted auto-dir delta values can never be all-zero.");
2220 0 : APZC_LOG("%p got a scroll-wheel with adjusted auto-dir delta values\n",
2221 0 : this);
2222 0 : } else if ((delta.x || delta.y) && !CanScrollWithWheel(delta)) {
2223 : // We can't scroll this apz anymore, so we simply drop the event.
2224 : if (mInputQueue->GetActiveWheelTransaction() &&
2225 : gfxPrefs::MouseScrollTestingEnabled()) {
2226 : if (RefPtr<GeckoContentController> controller = GetGeckoContentController()) {
2227 : controller->NotifyMozMouseScrollEvent(
2228 0 : mFrameMetrics.GetScrollId(),
2229 : NS_LITERAL_STRING("MozMouseScrollFailed"));
2230 0 : }
2231 : }
2232 0 : return nsEventStatus_eConsumeNoDefault;
2233 : }
2234 :
2235 : MOZ_ASSERT(mInputQueue->GetCurrentWheelBlock());
2236 : AdjustDeltaForAllowedScrollDirections(delta,
2237 0 : mInputQueue->GetCurrentWheelBlock()->GetAllowedScrollDirections());
2238 :
2239 : if (delta.x == 0 && delta.y == 0) {
2240 : // Avoid spurious state changes and unnecessary work
2241 : return nsEventStatus_eIgnore;
2242 : }
2243 0 :
2244 0 : switch (aEvent.mScrollMode) {
2245 : case ScrollWheelInput::SCROLLMODE_INSTANT: {
2246 :
2247 0 : // Wheel events from "clicky" mouse wheels trigger scroll snapping to the
2248 : // next snap point. Check for this, and adjust the delta to take into
2249 0 : // account the snap point.
2250 : CSSPoint startPosition = mFrameMetrics.GetScrollOffset();
2251 : MaybeAdjustDeltaForScrollSnapping(aEvent, delta, startPosition);
2252 0 :
2253 : ScreenPoint distance = ToScreenCoordinates(
2254 0 : ParentLayerPoint(fabs(delta.x), fabs(delta.y)), aEvent.mLocalOrigin);
2255 0 :
2256 0 : CancelAnimation();
2257 0 :
2258 : OverscrollHandoffState handoffState(
2259 0 : *mInputQueue->GetCurrentWheelBlock()->GetOverscrollHandoffChain(),
2260 : distance,
2261 : ScrollSource::Wheel);
2262 : ParentLayerPoint startPoint = aEvent.mLocalOrigin;
2263 0 : ParentLayerPoint endPoint = aEvent.mLocalOrigin - delta;
2264 0 : CallDispatchScroll(startPoint, endPoint, handoffState);
2265 :
2266 : SetState(NOTHING);
2267 :
2268 : // The calls above handle their own locking; moreover,
2269 : // ToScreenCoordinates() and CallDispatchScroll() can grab the tree lock.
2270 : RecursiveMutexAutoLock lock(mRecursiveMutex);
2271 : RequestContentRepaint();
2272 :
2273 0 : break;
2274 : }
2275 :
2276 0 : case ScrollWheelInput::SCROLLMODE_SMOOTH: {
2277 : // The lock must be held across the entire update operation, so the
2278 : // compositor doesn't end the animation before we get a chance to
2279 : // update it.
2280 0 : RecursiveMutexAutoLock lock(mRecursiveMutex);
2281 0 :
2282 0 : // Perform scroll snapping if appropriate.
2283 0 : CSSPoint startPosition = mFrameMetrics.GetScrollOffset();
2284 0 : // If we're already in a wheel scroll or smooth scroll animation,
2285 0 : // the delta is applied to its destination, not to the current
2286 : // scroll position. Take this into account when finding a snap point.
2287 0 : if (mState == WHEEL_SCROLL) {
2288 : startPosition = mAnimation->AsWheelScrollAnimation()->GetDestination();
2289 : } else if (mState == SMOOTH_SCROLL) {
2290 : startPosition = mAnimation->AsSmoothScrollAnimation()->GetDestination();
2291 : } else if (mState == KEYBOARD_SCROLL) {
2292 0 : startPosition = mAnimation->AsKeyboardScrollAnimation()->GetDestination();
2293 0 : }
2294 : if (MaybeAdjustDeltaForScrollSnapping(aEvent, delta, startPosition)) {
2295 : // If we're scroll snapping, use a smooth scroll animation to get
2296 : // the desired physics. Note that SmoothScrollTo() will re-use an
2297 0 : // existing smooth scroll animation if there is one.
2298 0 : APZC_LOG("%p wheel scrolling to snap point %s\n", this, Stringify(startPosition).c_str());
2299 0 : SmoothScrollTo(startPosition);
2300 : break;
2301 0 : }
2302 :
2303 0 : // Otherwise, use a wheel scroll animation, also reusing one if possible.
2304 : if (mState != WHEEL_SCROLL) {
2305 : CancelAnimation();
2306 : SetState(WHEEL_SCROLL);
2307 0 :
2308 : nsPoint initialPosition = CSSPoint::ToAppUnits(mFrameMetrics.GetScrollOffset());
2309 : StartAnimation(new WheelScrollAnimation(
2310 : *this, initialPosition, aEvent.mDeltaType));
2311 : }
2312 :
2313 0 : nsPoint deltaInAppUnits =
2314 0 : CSSPoint::ToAppUnits(delta / mFrameMetrics.GetZoom());
2315 0 :
2316 : // Convert velocity from ParentLayerPoints/ms to ParentLayerPoints/s and
2317 0 : // then to appunits/second.
2318 0 : nsPoint velocity =
2319 0 : CSSPoint::ToAppUnits(
2320 : ParentLayerPoint(mX.GetVelocity() * 1000.0f,
2321 : mY.GetVelocity() * 1000.0f) /
2322 : mFrameMetrics.GetZoom());
2323 :
2324 : WheelScrollAnimation* animation = mAnimation->AsWheelScrollAnimation();
2325 : animation->UpdateDelta(aEvent.mTimeStamp, deltaInAppUnits, nsSize(velocity.x, velocity.y));
2326 : break;
2327 0 : }
2328 : }
2329 0 :
2330 0 : return nsEventStatus_eConsumeNoDefault;
2331 0 : }
2332 :
2333 : void
2334 0 : AsyncPanZoomController::NotifyMozMouseScrollEvent(const nsString& aString) const
2335 : {
2336 : RefPtr<GeckoContentController> controller = GetGeckoContentController();
2337 0 : if (!controller) {
2338 : return;
2339 : }
2340 0 :
2341 0 : controller->NotifyMozMouseScrollEvent(mFrameMetrics.GetScrollId(), aString);
2342 0 : }
2343 0 :
2344 : nsEventStatus AsyncPanZoomController::OnPanMayBegin(const PanGestureInput& aEvent) {
2345 0 : APZC_LOG("%p got a pan-maybegin in state %d\n", this, mState);
2346 :
2347 : mX.StartTouch(aEvent.mLocalPanStartPoint.x, aEvent.mTime);
2348 0 : mY.StartTouch(aEvent.mLocalPanStartPoint.y, aEvent.mTime);
2349 : MOZ_ASSERT(GetCurrentPanGestureBlock());
2350 : GetCurrentPanGestureBlock()->GetOverscrollHandoffChain()->CancelAnimations();
2351 0 :
2352 0 : return nsEventStatus_eConsumeNoDefault;
2353 : }
2354 0 :
2355 : nsEventStatus AsyncPanZoomController::OnPanCancelled(const PanGestureInput& aEvent) {
2356 : APZC_LOG("%p got a pan-cancelled in state %d\n", this, mState);
2357 :
2358 0 : mX.CancelGesture();
2359 : mY.CancelGesture();
2360 :
2361 0 : return nsEventStatus_eConsumeNoDefault;
2362 : }
2363 0 :
2364 :
2365 : nsEventStatus AsyncPanZoomController::OnPanBegin(const PanGestureInput& aEvent) {
2366 0 : APZC_LOG("%p got a pan-begin in state %d\n", this, mState);
2367 0 :
2368 : if (mState == SMOOTH_SCROLL) {
2369 0 : // SMOOTH_SCROLL scrolls are cancelled by pan gestures.
2370 0 : CancelAnimation();
2371 0 : }
2372 :
2373 : mX.StartTouch(aEvent.mLocalPanStartPoint.x, aEvent.mTime);
2374 0 : mY.StartTouch(aEvent.mLocalPanStartPoint.y, aEvent.mTime);
2375 :
2376 0 : if (GetAxisLockMode() == FREE) {
2377 0 : SetState(PANNING);
2378 0 : return nsEventStatus_eConsumeNoDefault;
2379 0 : }
2380 :
2381 0 : float dx = aEvent.mPanDisplacement.x, dy = aEvent.mPanDisplacement.y;
2382 :
2383 : if (dx || dy) {
2384 : double angle = atan2(dy, dx); // range [-pi, pi]
2385 0 : angle = fabs(angle); // range [0, pi]
2386 : HandlePanning(angle);
2387 0 : } else {
2388 : SetState(PANNING);
2389 : }
2390 0 :
2391 : // Call into OnPan in order to process any delta included in this event.
2392 : OnPan(aEvent, true);
2393 0 :
2394 0 : return nsEventStatus_eConsumeNoDefault;
2395 : }
2396 :
2397 : nsEventStatus AsyncPanZoomController::OnPan(const PanGestureInput& aEvent, bool aFingersOnTouchpad) {
2398 : APZC_LOG("%p got a pan-pan in state %d\n", this, mState);
2399 :
2400 : if (mState == SMOOTH_SCROLL) {
2401 : if (!aFingersOnTouchpad) {
2402 : // When a SMOOTH_SCROLL scroll is being processed on a frame, mouse
2403 : // wheel and trackpad momentum scroll position updates will not cancel the
2404 0 : // SMOOTH_SCROLL scroll animations, enabling scripts that depend on
2405 : // them to be responsive without forcing the user to wait for the momentum
2406 : // scrolling to completely stop.
2407 0 : return nsEventStatus_eConsumeNoDefault;
2408 : }
2409 :
2410 : // SMOOTH_SCROLL scrolls are cancelled by pan gestures.
2411 0 : CancelAnimation();
2412 : }
2413 :
2414 : if (mState == NOTHING) {
2415 : // This event block was interrupted by something else. If the user's fingers
2416 0 : // are still on on the touchpad we want to resume scrolling, otherwise we
2417 : // ignore the rest of the scroll gesture.
2418 : if (!aFingersOnTouchpad) {
2419 : return nsEventStatus_eConsumeNoDefault;
2420 : }
2421 : // Resume / restart the pan.
2422 : // PanBegin will call back into this function with mState == PANNING.
2423 : return OnPanBegin(aEvent);
2424 0 : }
2425 0 :
2426 : // Note that there is a multiplier that applies onto the "physical" pan
2427 0 : // displacement (how much the user's fingers moved) that produces the "logical"
2428 : // pan displacement (how much the page should move). For some of the code
2429 0 : // below it makes more sense to use the physical displacement rather than
2430 : // the logical displacement, and vice-versa.
2431 : ScreenPoint physicalPanDisplacement = aEvent.mPanDisplacement;
2432 : ParentLayerPoint logicalPanDisplacement = aEvent.UserMultipliedLocalPanDisplacement();
2433 :
2434 : MOZ_ASSERT(GetCurrentPanGestureBlock());
2435 0 : AdjustDeltaForAllowedScrollDirections(logicalPanDisplacement,
2436 0 : GetCurrentPanGestureBlock()->GetAllowedScrollDirections());
2437 :
2438 0 : // We need to update the axis velocity in order to get a useful display port
2439 : // size and position. We need to do so even if this is a momentum pan (i.e.
2440 0 : // aFingersOnTouchpad == false); in that case the "with touch" part is not
2441 : // really appropriate, so we may want to rethink this at some point.
2442 0 : mX.UpdateWithTouchAtDevicePoint(aEvent.mLocalPanStartPoint.x, logicalPanDisplacement.x, aEvent.mTime);
2443 : mY.UpdateWithTouchAtDevicePoint(aEvent.mLocalPanStartPoint.y, logicalPanDisplacement.y, aEvent.mTime);
2444 0 :
2445 : HandlePanningUpdate(physicalPanDisplacement);
2446 :
2447 : ScreenPoint panDistance(fabs(physicalPanDisplacement.x), fabs(physicalPanDisplacement.y));
2448 : OverscrollHandoffState handoffState(
2449 : *GetCurrentPanGestureBlock()->GetOverscrollHandoffChain(),
2450 : panDistance,
2451 : ScrollSource::Wheel);
2452 :
2453 0 : // Create fake "touch" positions that will result in the desired scroll motion.
2454 0 : // Note that the pan displacement describes the change in scroll position:
2455 0 : // positive displacement values mean that the scroll position increases.
2456 : // However, an increase in scroll position means that the scrolled contents
2457 0 : // are moved to the left / upwards. Since our simulated "touches" determine
2458 : // the motion of the scrolled contents, not of the scroll position, they need
2459 : // to move in the opposite direction of the pan displacement.
2460 0 : ParentLayerPoint startPoint = aEvent.mLocalPanStartPoint;
2461 : ParentLayerPoint endPoint = aEvent.mLocalPanStartPoint - logicalPanDisplacement;
2462 : CallDispatchScroll(startPoint, endPoint, handoffState);
2463 :
2464 0 : return nsEventStatus_eConsumeNoDefault;
2465 : }
2466 0 :
2467 0 : nsEventStatus AsyncPanZoomController::OnPanEnd(const PanGestureInput& aEvent) {
2468 : APZC_LOG("%p got a pan-end in state %d\n", this, mState);
2469 :
2470 : // Call into OnPan in order to process any delta included in this event.
2471 : OnPan(aEvent, true);
2472 0 :
2473 : mX.EndTouch(aEvent.mTime);
2474 0 : mY.EndTouch(aEvent.mTime);
2475 0 :
2476 0 : // Drop any velocity on axes where we don't have room to scroll anyways
2477 : // (in this APZC, or an APZC further in the handoff chain).
2478 0 : // This ensures that we don't enlarge the display port unnecessarily.
2479 0 : MOZ_ASSERT(GetCurrentPanGestureBlock());
2480 : RefPtr<const OverscrollHandoffChain> overscrollHandoffChain =
2481 : GetCurrentPanGestureBlock()->GetOverscrollHandoffChain();
2482 0 : if (!overscrollHandoffChain->CanScrollInDirection(this, ScrollDirection::eHorizontal)) {
2483 0 : mX.SetVelocity(0);
2484 : }
2485 0 : if (!overscrollHandoffChain->CanScrollInDirection(this, ScrollDirection::eVertical)) {
2486 0 : mY.SetVelocity(0);
2487 : }
2488 :
2489 0 : SetState(NOTHING);
2490 : RequestContentRepaint();
2491 :
2492 0 : if (!aEvent.mFollowedByMomentum) {
2493 : ScrollSnap();
2494 : }
2495 0 :
2496 : return nsEventStatus_eConsumeNoDefault;
2497 0 : }
2498 :
2499 : nsEventStatus AsyncPanZoomController::OnPanMomentumStart(const PanGestureInput& aEvent) {
2500 0 : APZC_LOG("%p got a pan-momentumstart in state %d\n", this, mState);
2501 0 :
2502 : if (mState == SMOOTH_SCROLL) {
2503 : // SMOOTH_SCROLL scrolls are cancelled by pan gestures.
2504 0 : CancelAnimation();
2505 : }
2506 0 :
2507 : SetState(PAN_MOMENTUM);
2508 : ScrollSnapToDestination();
2509 0 :
2510 : // Call into OnPan in order to process any delta included in this event.
2511 : OnPan(aEvent, false);
2512 :
2513 0 : return nsEventStatus_eConsumeNoDefault;
2514 : }
2515 :
2516 : nsEventStatus AsyncPanZoomController::OnPanMomentumEnd(const PanGestureInput& aEvent) {
2517 : APZC_LOG("%p got a pan-momentumend in state %d\n", this, mState);
2518 0 :
2519 0 : // Call into OnPan in order to process any delta included in this event.
2520 0 : OnPan(aEvent, false);
2521 :
2522 0 : // We need to reset the velocity to zero. We don't really have a "touch"
2523 : // here because the touch has already ended long before the momentum
2524 0 : // animation started, but I guess it doesn't really matter for now.
2525 : mX.CancelGesture();
2526 : mY.CancelGesture();
2527 0 : SetState(NOTHING);
2528 :
2529 0 : RequestContentRepaint();
2530 0 :
2531 0 : return nsEventStatus_eConsumeNoDefault;
2532 0 : }
2533 0 :
2534 0 : nsEventStatus AsyncPanZoomController::OnLongPress(const TapGestureInput& aEvent) {
2535 : APZC_LOG("%p got a long-press in state %d\n", this, mState);
2536 0 : RefPtr<GeckoContentController> controller = GetGeckoContentController();
2537 : if (controller) {
2538 0 : LayoutDevicePoint geckoScreenPoint;
2539 : if (ConvertToGecko(aEvent.mPoint, &geckoScreenPoint)) {
2540 : TouchBlockState* touch = GetCurrentTouchBlock();
2541 : if (!touch) {
2542 0 : APZC_LOG("%p dropping long-press because some non-touch block interrupted it\n", this);
2543 0 : return nsEventStatus_eIgnore;
2544 0 : }
2545 : if (touch->IsDuringFastFling()) {
2546 : APZC_LOG("%p dropping long-press because of fast fling\n", this);
2547 : return nsEventStatus_eIgnore;
2548 : }
2549 : uint64_t blockId = GetInputQueue()->InjectNewTouchBlock(this);
2550 0 : controller->HandleTap(TapType::eLongTap, geckoScreenPoint, aEvent.modifiers, GetGuid(), blockId);
2551 : return nsEventStatus_eConsumeNoDefault;
2552 0 : }
2553 : }
2554 : return nsEventStatus_eIgnore;
2555 0 : }
2556 :
2557 0 : nsEventStatus AsyncPanZoomController::OnLongPressUp(const TapGestureInput& aEvent) {
2558 0 : APZC_LOG("%p got a long-tap-up in state %d\n", this, mState);
2559 0 : return GenerateSingleTap(TapType::eLongTapUp, aEvent.mPoint, aEvent.modifiers);
2560 0 : }
2561 0 :
2562 : nsEventStatus AsyncPanZoomController::GenerateSingleTap(TapType aType,
2563 : const ScreenIntPoint& aPoint, mozilla::Modifiers aModifiers) {
2564 : RefPtr<GeckoContentController> controller = GetGeckoContentController();
2565 : if (controller) {
2566 : LayoutDevicePoint geckoScreenPoint;
2567 : if (ConvertToGecko(aPoint, &geckoScreenPoint)) {
2568 0 : TouchBlockState* touch = GetCurrentTouchBlock();
2569 0 : // |touch| may be null in the case where this function is
2570 : // invoked by GestureEventListener on a timeout. In that case we already
2571 : // verified that the single tap is allowed so we let it through.
2572 : // XXX there is a bug here that in such a case the touch block that
2573 0 : // generated this tap will not get its mSingleTapOccurred flag set.
2574 : // See https://bugzilla.mozilla.org/show_bug.cgi?id=1256344#c6
2575 : if (touch) {
2576 : if (touch->IsDuringFastFling()) {
2577 : APZC_LOG("%p dropping single-tap because it was during a fast-fling\n", this);
2578 : return nsEventStatus_eIgnore;
2579 : }
2580 : touch->SetSingleTapOccurred();
2581 : }
2582 : // Because this may be being running as part of APZCTreeManager::ReceiveInputEvent,
2583 : // calling controller->HandleTap directly might mean that content receives
2584 : // the single tap message before the corresponding touch-up. To avoid that we
2585 0 : // schedule the singletap message to run on the next spin of the event loop.
2586 : // See bug 965381 for the issue this was causing.
2587 : RefPtr<Runnable> runnable =
2588 : NewRunnableMethod<TapType,
2589 : LayoutDevicePoint,
2590 : mozilla::Modifiers,
2591 0 : ScrollableLayerGuid,
2592 0 : uint64_t>("layers::GeckoContentController::HandleTap",
2593 : controller,
2594 0 : &GeckoContentController::HandleTap,
2595 : aType,
2596 : geckoScreenPoint,
2597 : aModifiers,
2598 : GetGuid(),
2599 : touch ? touch->GetBlockId() : 0);
2600 :
2601 0 : controller->PostDelayedTask(runnable.forget(), 0);
2602 0 : return nsEventStatus_eConsumeNoDefault;
2603 0 : }
2604 0 : }
2605 0 : return nsEventStatus_eIgnore;
2606 : }
2607 0 :
2608 : void AsyncPanZoomController::OnTouchEndOrCancel() {
2609 0 : if (RefPtr<GeckoContentController> controller = GetGeckoContentController()) {
2610 : MOZ_ASSERT(GetCurrentTouchBlock());
2611 : controller->NotifyAPZStateChange(
2612 : GetGuid(), APZStateChange::eEndTouch, GetCurrentTouchBlock()->SingleTapOccurred());
2613 0 : }
2614 0 : }
2615 0 :
2616 : nsEventStatus AsyncPanZoomController::OnSingleTapUp(const TapGestureInput& aEvent) {
2617 : APZC_LOG("%p got a single-tap-up in state %d\n", this, mState);
2618 : // If mZoomConstraints.mAllowDoubleTapZoom is true we wait for a call to OnSingleTapConfirmed before
2619 : // sending event to content
2620 0 : MOZ_ASSERT(GetCurrentTouchBlock());
2621 : if (!(mZoomConstraints.mAllowDoubleTapZoom && GetCurrentTouchBlock()->TouchActionAllowsDoubleTapZoom())) {
2622 0 : return GenerateSingleTap(TapType::eSingleTap, aEvent.mPoint, aEvent.modifiers);
2623 : }
2624 : return nsEventStatus_eIgnore;
2625 0 : }
2626 :
2627 0 : nsEventStatus AsyncPanZoomController::OnSingleTapConfirmed(const TapGestureInput& aEvent) {
2628 0 : APZC_LOG("%p got a single-tap-confirmed in state %d\n", this, mState);
2629 0 : return GenerateSingleTap(TapType::eSingleTap, aEvent.mPoint, aEvent.modifiers);
2630 0 : }
2631 0 :
2632 0 : nsEventStatus AsyncPanZoomController::OnDoubleTap(const TapGestureInput& aEvent) {
2633 0 : APZC_LOG("%p got a double-tap in state %d\n", this, mState);
2634 0 : RefPtr<GeckoContentController> controller = GetGeckoContentController();
2635 : if (controller) {
2636 : MOZ_ASSERT(GetCurrentTouchBlock());
2637 : if (mZoomConstraints.mAllowDoubleTapZoom && GetCurrentTouchBlock()->TouchActionAllowsDoubleTapZoom()) {
2638 : LayoutDevicePoint geckoScreenPoint;
2639 : if (ConvertToGecko(aEvent.mPoint, &geckoScreenPoint)) {
2640 : controller->HandleTap(TapType::eDoubleTap, geckoScreenPoint,
2641 : aEvent.modifiers, GetGuid(), GetCurrentTouchBlock()->GetBlockId());
2642 0 : }
2643 : }
2644 : return nsEventStatus_eConsumeNoDefault;
2645 0 : }
2646 : return nsEventStatus_eIgnore;
2647 : }
2648 0 :
2649 : nsEventStatus AsyncPanZoomController::OnSecondTap(const TapGestureInput& aEvent)
2650 : {
2651 0 : APZC_LOG("%p got a second-tap in state %d\n", this, mState);
2652 : return GenerateSingleTap(TapType::eSecondTap, aEvent.mPoint, aEvent.modifiers);
2653 : }
2654 :
2655 0 : nsEventStatus AsyncPanZoomController::OnCancelTap(const TapGestureInput& aEvent) {
2656 0 : APZC_LOG("%p got a cancel-tap in state %d\n", this, mState);
2657 0 : // XXX: Implement this.
2658 : return nsEventStatus_eIgnore;
2659 0 : }
2660 :
2661 :
2662 0 : ScreenToParentLayerMatrix4x4 AsyncPanZoomController::GetTransformToThis() const {
2663 : if (APZCTreeManager* treeManagerLocal = GetApzcTreeManager()) {
2664 0 : return treeManagerLocal->GetScreenToApzcTransform(this);
2665 : }
2666 : return ScreenToParentLayerMatrix4x4();
2667 : }
2668 0 :
2669 : ScreenPoint AsyncPanZoomController::ToScreenCoordinates(const ParentLayerPoint& aVector,
2670 0 : const ParentLayerPoint& aAnchor) const {
2671 : return TransformVector(GetTransformToThis().Inverse(), aVector, aAnchor);
2672 : }
2673 0 :
2674 : // TODO: figure out a good way to check the w-coordinate is positive and return the result
2675 0 : ParentLayerPoint AsyncPanZoomController::ToParentLayerCoordinates(const ScreenPoint& aVector,
2676 0 : const ScreenPoint& aAnchor) const {
2677 0 : return TransformVector(GetTransformToThis(), aVector, aAnchor);
2678 : }
2679 :
2680 : bool AsyncPanZoomController::Contains(const ScreenIntPoint& aPoint) const
2681 0 : {
2682 : ScreenToParentLayerMatrix4x4 transformToThis = GetTransformToThis();
2683 0 : Maybe<ParentLayerIntPoint> point = UntransformBy(transformToThis, aPoint);
2684 0 : if (!point) {
2685 : return false;
2686 0 : }
2687 :
2688 : ParentLayerIntRect cb;
2689 0 : {
2690 0 : RecursiveMutexAutoLock lock(mRecursiveMutex);
2691 0 : GetFrameMetrics().GetCompositionBounds().ToIntRect(&cb);
2692 : }
2693 0 : return cb.Contains(*point);
2694 0 : }
2695 0 :
2696 : ScreenCoord AsyncPanZoomController::PanDistance() const {
2697 0 : ParentLayerPoint panVector;
2698 : ParentLayerPoint panStart;
2699 : {
2700 0 : RecursiveMutexAutoLock lock(mRecursiveMutex);
2701 0 : panVector = ParentLayerPoint(mX.PanDistance(), mY.PanDistance());
2702 : panStart = PanStart();
2703 : }
2704 0 : return ToScreenCoordinates(panVector, panStart).Length();
2705 0 : }
2706 :
2707 : ParentLayerPoint AsyncPanZoomController::PanStart() const {
2708 0 : return ParentLayerPoint(mX.PanStart(), mY.PanStart());
2709 0 : }
2710 0 :
2711 0 : const ParentLayerPoint AsyncPanZoomController::GetVelocityVector() const {
2712 : return ParentLayerPoint(mX.GetVelocity(), mY.GetVelocity());
2713 0 : }
2714 :
2715 : void AsyncPanZoomController::SetVelocityVector(const ParentLayerPoint& aVelocityVector) {
2716 0 : mX.SetVelocity(aVelocityVector.x);
2717 : mY.SetVelocity(aVelocityVector.y);
2718 0 : }
2719 0 :
2720 0 : void AsyncPanZoomController::HandlePanningWithTouchAction(double aAngle) {
2721 0 : // Handling of cross sliding will need to be added in this method after touch-action released
2722 0 : // enabled by default.
2723 0 : MOZ_ASSERT(GetCurrentTouchBlock());
2724 0 : RefPtr<const OverscrollHandoffChain> overscrollHandoffChain =
2725 0 : GetCurrentInputBlock()->GetOverscrollHandoffChain();
2726 0 : bool canScrollHorizontal = !mX.IsAxisLocked() &&
2727 0 : overscrollHandoffChain->CanScrollInDirection(this, ScrollDirection::eHorizontal);
2728 0 : bool canScrollVertical = !mY.IsAxisLocked() &&
2729 0 : overscrollHandoffChain->CanScrollInDirection(this, ScrollDirection::eVertical);
2730 0 : if (GetCurrentTouchBlock()->TouchActionAllowsPanningXY()) {
2731 : if (canScrollHorizontal && canScrollVertical) {
2732 0 : if (IsCloseToHorizontal(aAngle, gfxPrefs::APZAxisLockAngle())) {
2733 : mY.SetAxisLocked(true);
2734 0 : SetState(PANNING_LOCKED_X);
2735 0 : } else if (IsCloseToVertical(aAngle, gfxPrefs::APZAxisLockAngle())) {
2736 : mX.SetAxisLocked(true);
2737 0 : SetState(PANNING_LOCKED_Y);
2738 : } else {
2739 0 : SetState(PANNING);
2740 : }
2741 : } else if (canScrollHorizontal || canScrollVertical) {
2742 0 : SetState(PANNING);
2743 0 : } else {
2744 0 : SetState(NOTHING);
2745 0 : }
2746 : } else if (GetCurrentTouchBlock()->TouchActionAllowsPanningX()) {
2747 : // Using bigger angle for panning to keep behavior consistent
2748 : // with IE.
2749 0 : if (IsCloseToHorizontal(aAngle, gfxPrefs::APZAllowedDirectPanAngle())) {
2750 : mY.SetAxisLocked(true);
2751 0 : SetState(PANNING_LOCKED_X);
2752 0 : mPanDirRestricted = true;
2753 0 : } else {
2754 0 : // Don't treat these touches as pan/zoom movements since 'touch-action' value
2755 0 : // requires it.
2756 : SetState(NOTHING);
2757 0 : }
2758 : } else if (GetCurrentTouchBlock()->TouchActionAllowsPanningY()) {
2759 : if (IsCloseToVertical(aAngle, gfxPrefs::APZAllowedDirectPanAngle())) {
2760 0 : mX.SetAxisLocked(true);
2761 : SetState(PANNING_LOCKED_Y);
2762 0 : mPanDirRestricted = true;
2763 : } else {
2764 : SetState(NOTHING);
2765 : }
2766 0 : } else {
2767 0 : SetState(NOTHING);
2768 : }
2769 0 : if (!IsInPanningState()) {
2770 : // If we didn't enter a panning state because touch-action disallowed it,
2771 0 : // make sure to clear any leftover velocity from the pre-threshold
2772 0 : // touchmoves.
2773 0 : mX.SetVelocity(0);
2774 : mY.SetVelocity(0);
2775 0 : }
2776 0 : }
2777 0 :
2778 0 : void AsyncPanZoomController::HandlePanning(double aAngle) {
2779 0 : RecursiveMutexAutoLock lock(mRecursiveMutex);
2780 : MOZ_ASSERT(GetCurrentInputBlock());
2781 0 : RefPtr<const OverscrollHandoffChain> overscrollHandoffChain =
2782 0 : GetCurrentInputBlock()->GetOverscrollHandoffChain();
2783 0 : bool canScrollHorizontal = !mX.IsAxisLocked() &&
2784 0 : overscrollHandoffChain->CanScrollInDirection(this, ScrollDirection::eHorizontal);
2785 0 : bool canScrollVertical = !mY.IsAxisLocked() &&
2786 0 : overscrollHandoffChain->CanScrollInDirection(this, ScrollDirection::eVertical);
2787 :
2788 0 : if (!canScrollHorizontal || !canScrollVertical) {
2789 0 : SetState(PANNING);
2790 0 : } else if (IsCloseToHorizontal(aAngle, gfxPrefs::APZAxisLockAngle())) {
2791 0 : mY.SetAxisLocked(true);
2792 : if (canScrollHorizontal) {
2793 : SetState(PANNING_LOCKED_X);
2794 0 : }
2795 : } else if (IsCloseToVertical(aAngle, gfxPrefs::APZAxisLockAngle())) {
2796 0 : mX.SetAxisLocked(true);
2797 : if (canScrollVertical) {
2798 0 : SetState(PANNING_LOCKED_Y);
2799 : }
2800 0 : } else {
2801 : SetState(PANNING);
2802 0 : }
2803 0 : }
2804 :
2805 0 : void AsyncPanZoomController::HandlePanningUpdate(const ScreenPoint& aPanDistance) {
2806 : // If we're axis-locked, check if the user is trying to break the lock
2807 0 : if (GetAxisLockMode() == STICKY && !mPanDirRestricted) {
2808 0 :
2809 0 : double angle = atan2(aPanDistance.y, aPanDistance.x); // range [-pi, pi]
2810 0 : angle = fabs(angle); // range [0, pi]
2811 0 :
2812 : float breakThreshold = gfxPrefs::APZAxisBreakoutThreshold() * GetDPI();
2813 0 :
2814 0 : if (fabs(aPanDistance.x) > breakThreshold || fabs(aPanDistance.y) > breakThreshold) {
2815 0 : if (mState == PANNING_LOCKED_X) {
2816 0 : if (!IsCloseToHorizontal(angle, gfxPrefs::APZAxisBreakoutAngle())) {
2817 : mY.SetAxisLocked(false);
2818 : SetState(PANNING);
2819 : }
2820 : } else if (mState == PANNING_LOCKED_Y) {
2821 0 : if (!IsCloseToVertical(angle, gfxPrefs::APZAxisBreakoutAngle())) {
2822 : mX.SetAxisLocked(false);
2823 0 : SetState(PANNING);
2824 0 : }
2825 0 : }
2826 0 : }
2827 0 : }
2828 : }
2829 :
2830 0 : void AsyncPanZoomController::HandlePinchLocking(ScreenCoord spanDistance, ScreenPoint focusChange) {
2831 0 : if (mPinchLocked) {
2832 0 : if (GetPinchLockMode() == PINCH_STICKY) {
2833 : ScreenCoord spanBreakoutThreshold = gfxPrefs::APZPinchLockSpanBreakoutThreshold() * GetDPI();
2834 0 : mPinchLocked = !(spanDistance > spanBreakoutThreshold);
2835 0 : }
2836 : } else {
2837 : if (GetPinchLockMode() != PINCH_FREE) {
2838 : ScreenCoord spanLockThreshold = gfxPrefs::APZPinchLockSpanLockThreshold() * GetDPI();
2839 0 : ScreenCoord scrollLockThreshold = gfxPrefs::APZPinchLockScrollLockThreshold() * GetDPI();
2840 :
2841 : if (spanDistance < spanLockThreshold && focusChange.Length() > scrollLockThreshold) {
2842 0 : mPinchLocked = true;
2843 0 : }
2844 : }
2845 0 : }
2846 0 : }
2847 :
2848 0 : nsEventStatus
2849 0 : AsyncPanZoomController::StartPanning(const ParentLayerPoint& aStartPoint) {
2850 : RecursiveMutexAutoLock lock(mRecursiveMutex);
2851 0 :
2852 0 : float dx = mX.PanDistance(aStartPoint.x);
2853 : float dy = mY.PanDistance(aStartPoint.y);
2854 0 :
2855 0 : double angle = atan2(dy, dx); // range [-pi, pi]
2856 : angle = fabs(angle); // range [0, pi]
2857 0 :
2858 : if (gfxPrefs::TouchActionEnabled()) {
2859 : HandlePanningWithTouchAction(angle);
2860 : } else {
2861 0 : if (GetAxisLockMode() == FREE) {
2862 0 : SetState(PANNING);
2863 0 : } else {
2864 : HandlePanning(angle);
2865 0 : }
2866 : }
2867 :
2868 : if (IsInPanningState()) {
2869 : if (RefPtr<GeckoContentController> controller = GetGeckoContentController()) {
2870 : controller->NotifyAPZStateChange(GetGuid(), APZStateChange::eStartPanning);
2871 0 : }
2872 0 : return nsEventStatus_eConsumeNoDefault;
2873 0 : }
2874 0 : // Don't consume an event that didn't trigger a panning.
2875 0 : return nsEventStatus_eIgnore;
2876 : }
2877 0 :
2878 : void AsyncPanZoomController::UpdateWithTouchAtDevicePoint(const MultiTouchInput& aEvent) {
2879 : ParentLayerPoint point = GetFirstTouchPoint(aEvent);
2880 : mX.UpdateWithTouchAtDevicePoint(point.x, 0, aEvent.mTime);
2881 : mY.UpdateWithTouchAtDevicePoint(point.y, 0, aEvent.mTime);
2882 : }
2883 :
2884 0 : bool AsyncPanZoomController::AttemptScroll(ParentLayerPoint& aStartPoint,
2885 : ParentLayerPoint& aEndPoint,
2886 0 : OverscrollHandoffState& aOverscrollHandoffState) {
2887 :
2888 : // "start - end" rather than "end - start" because e.g. moving your finger
2889 : // down (*positive* direction along y axis) causes the vertical scroll offset
2890 : // to *decrease* as the page follows your finger.
2891 : ParentLayerPoint displacement = aStartPoint - aEndPoint;
2892 :
2893 0 : ParentLayerPoint overscroll; // will be used outside monitor block
2894 0 :
2895 0 : // If the direction of panning is reversed within the same input block,
2896 : // a later event in the block could potentially scroll an APZC earlier
2897 : // in the handoff chain, than an earlier event in the block (because
2898 0 : // the earlier APZC was scrolled to its extent in the original direction).
2899 0 : // We want to disallow this.
2900 : bool scrollThisApzc = false;
2901 0 : if (InputBlockState* block = GetCurrentInputBlock()) {
2902 0 : scrollThisApzc = !block->GetScrolledApzc() || block->IsDownchainOfScrolledApzc(this);
2903 : }
2904 0 :
2905 0 : if (scrollThisApzc) {
2906 : RecursiveMutexAutoLock lock(mRecursiveMutex);
2907 0 : bool forcesVerticalOverscroll =
2908 0 : ScrollSource::Wheel == aOverscrollHandoffState.mScrollSource &&
2909 : mScrollMetadata.GetDisregardedDirection() == Some(ScrollDirection::eVertical);
2910 0 : bool forcesHorizontalOverscroll =
2911 0 : ScrollSource::Wheel == aOverscrollHandoffState.mScrollSource &&
2912 : mScrollMetadata.GetDisregardedDirection() == Some(ScrollDirection::eHorizontal);
2913 0 :
2914 0 : ParentLayerPoint adjustedDisplacement;
2915 0 : bool yChanged = mY.AdjustDisplacement(displacement.y, adjustedDisplacement.y,
2916 : overscroll.y,
2917 : forcesVerticalOverscroll);
2918 0 : bool xChanged = mX.AdjustDisplacement(displacement.x, adjustedDisplacement.x,
2919 0 : overscroll.x,
2920 0 : forcesHorizontalOverscroll);
2921 : if (xChanged || yChanged) {
2922 : ScheduleComposite();
2923 : }
2924 :
2925 : if (!IsZero(adjustedDisplacement)) {
2926 : ScrollBy(adjustedDisplacement / mFrameMetrics.GetZoom());
2927 : if (InputBlockState* block = GetCurrentInputBlock()) {
2928 : #if defined(MOZ_WIDGET_ANDROID)
2929 : if (block->AsTouchBlock() && (block->GetScrolledApzc() != this) && IsRootContent()) {
2930 0 : if (APZCTreeManager* manager = GetApzcTreeManager()) {
2931 : AndroidDynamicToolbarAnimator* animator = manager->GetAndroidDynamicToolbarAnimator();
2932 0 : MOZ_ASSERT(animator);
2933 0 : animator->SetScrollingRootContent();
2934 : }
2935 : }
2936 : #endif
2937 0 : block->SetScrolledApzc(this);
2938 : }
2939 0 : ScheduleCompositeAndMaybeRepaint();
2940 : UpdateSharedCompositorFrameMetrics();
2941 : }
2942 :
2943 0 : // Adjust the start point to reflect the consumed portion of the scroll.
2944 : aStartPoint = aEndPoint + overscroll;
2945 : } else {
2946 : overscroll = displacement;
2947 0 : }
2948 :
2949 : // If we consumed the entire displacement as a normal scroll, great.
2950 : if (IsZero(overscroll)) {
2951 : return true;
2952 : }
2953 0 :
2954 0 : if (AllowScrollHandoffInCurrentBlock()) {
2955 : // If there is overscroll, first try to hand it off to an APZC later
2956 0 : // in the handoff chain to consume (either as a normal scroll or as
2957 0 : // overscroll).
2958 : // Note: "+ overscroll" rather than "- overscroll" because "overscroll"
2959 : // is what's left of "displacement", and "displacement" is "start - end".
2960 : ++aOverscrollHandoffState.mChainIndex;
2961 : CallDispatchScroll(aStartPoint, aEndPoint, aOverscrollHandoffState);
2962 :
2963 : overscroll = aStartPoint - aEndPoint;
2964 : if (IsZero(overscroll)) {
2965 : return true;
2966 0 : }
2967 0 : }
2968 :
2969 0 : // If there is no APZC later in the handoff chain that accepted the
2970 : // overscroll, try to accept it ourselves. We only accept it if we
2971 : // are pannable.
2972 0 : APZC_LOG("%p taking overscroll during panning\n", this);
2973 : OverscrollForPanning(overscroll, aOverscrollHandoffState.mPanDistance);
2974 : aStartPoint = aEndPoint + overscroll;
2975 :
2976 : return IsZero(overscroll);
2977 0 : }
2978 0 :
2979 0 : void AsyncPanZoomController::OverscrollForPanning(ParentLayerPoint& aOverscroll,
2980 : const ScreenPoint& aPanDistance) {
2981 0 : // Only allow entering overscroll along an axis if the pan distance along
2982 0 : // that axis is greater than the pan distance along the other axis by a
2983 : // configurable factor. If we are already overscrolled, don't check this.
2984 : if (!IsOverscrolled()) {
2985 : if (aPanDistance.x < gfxPrefs::APZMinPanDistanceRatio() * aPanDistance.y) {
2986 0 : aOverscroll.x = 0;
2987 0 : }
2988 : if (aPanDistance.y < gfxPrefs::APZMinPanDistanceRatio() * aPanDistance.x) {
2989 0 : aOverscroll.y = 0;
2990 0 : }
2991 0 : }
2992 :
2993 : OverscrollBy(aOverscroll);
2994 0 : }
2995 :
2996 : void AsyncPanZoomController::OverscrollBy(ParentLayerPoint& aOverscroll) {
2997 0 : if (!gfxPrefs::APZOverscrollEnabled()) {
2998 0 : return;
2999 0 : }
3000 0 :
3001 : RecursiveMutexAutoLock lock(mRecursiveMutex);
3002 0 : // Do not go into overscroll in a direction in which we have no room to
3003 0 : // scroll to begin with.
3004 : bool xCanScroll = mX.CanScroll();
3005 0 : bool yCanScroll = mY.CanScroll();
3006 : bool xConsumed = FuzzyEqualsAdditive(aOverscroll.x, 0.0f, COORDINATE_EPSILON);
3007 : bool yConsumed = FuzzyEqualsAdditive(aOverscroll.y, 0.0f, COORDINATE_EPSILON);
3008 0 :
3009 0 : bool shouldOverscrollX = xCanScroll && !xConsumed && mX.OverscrollBehaviorAllowsOverscrollEffect();
3010 0 : bool shouldOverscrollY = yCanScroll && !yConsumed && mY.OverscrollBehaviorAllowsOverscrollEffect();
3011 :
3012 : mOverscrollEffect->ConsumeOverscroll(aOverscroll, shouldOverscrollX, shouldOverscrollY);
3013 : }
3014 :
3015 0 : RefPtr<const OverscrollHandoffChain> AsyncPanZoomController::BuildOverscrollHandoffChain() {
3016 0 : if (APZCTreeManager* treeManagerLocal = GetApzcTreeManager()) {
3017 0 : return treeManagerLocal->BuildOverscrollHandoffChain(this);
3018 : }
3019 :
3020 0 : // This APZC IsDestroyed(). To avoid callers having to special-case this
3021 : // scenario, just build a 1-element chain containing ourselves.
3022 : OverscrollHandoffChain* result = new OverscrollHandoffChain;
3023 0 : result->Add(this);
3024 0 : return result;
3025 : }
3026 0 :
3027 : ParentLayerPoint AsyncPanZoomController::AttemptFling(const FlingHandoffState& aHandoffState) {
3028 0 : // The PLPPI computation acquires the tree lock, so it needs to be performed
3029 0 : // on the controller thread, and before the APZC lock is acquired.
3030 : APZThreadUtils::AssertOnControllerThread();
3031 : float PLPPI = ComputePLPPI(PanStart(), aHandoffState.mVelocity);
3032 :
3033 : RecursiveMutexAutoLock lock(mRecursiveMutex);
3034 :
3035 : if (!IsPannable()) {
3036 0 : return aHandoffState.mVelocity;
3037 0 : }
3038 0 :
3039 0 : // We may have a pre-existing velocity for whatever reason (for example,
3040 : // a previously handed off fling). We don't want to clobber that.
3041 0 : APZC_LOG("%p accepting fling with velocity %s\n", this,
3042 0 : Stringify(aHandoffState.mVelocity).c_str());
3043 0 : ParentLayerPoint residualVelocity = aHandoffState.mVelocity;
3044 : if (mX.CanScroll()) {
3045 : mX.SetVelocity(mX.GetVelocity() + aHandoffState.mVelocity.x);
3046 : residualVelocity.x = 0;
3047 : }
3048 : if (mY.CanScroll()) {
3049 0 : mY.SetVelocity(mY.GetVelocity() + aHandoffState.mVelocity.y);
3050 0 : residualVelocity.y = 0;
3051 0 : }
3052 0 :
3053 0 : // If there's a scroll snap point near the predicted fling destination,
3054 0 : // scroll there using a smooth scroll animation. Otherwise, start a
3055 : // fling animation.
3056 : ScrollSnapToDestination();
3057 0 : if (mState != SMOOTH_SCROLL) {
3058 : SetState(FLING);
3059 : AsyncPanZoomAnimation* fling = GetPlatformSpecificState()->CreateFlingAnimation(
3060 0 : *this, aHandoffState, PLPPI);
3061 : StartAnimation(fling);
3062 : }
3063 0 :
3064 : return residualVelocity;
3065 : }
3066 :
3067 : float AsyncPanZoomController::ComputePLPPI(ParentLayerPoint aPoint, ParentLayerPoint aDirection) const
3068 0 : {
3069 : // Convert |aDirection| into a unit vector.
3070 : aDirection = aDirection / aDirection.Length();
3071 0 :
3072 : // Place the vector at |aPoint| and convert to screen coordinates.
3073 : // The length of the resulting vector is the number of Screen coordinates
3074 : // that equal 1 ParentLayer coordinate in the given direction.
3075 0 : float screenPerParent = ToScreenCoordinates(aDirection, aPoint).Length();
3076 :
3077 0 : // Finally, factor in the DPI scale.
3078 0 : return GetDPI() / screenPerParent;
3079 0 : }
3080 0 :
3081 0 :
3082 : ParentLayerPoint AsyncPanZoomController::AdjustHandoffVelocityForOverscrollBehavior(ParentLayerPoint& aHandoffVelocity) const
3083 0 : {
3084 0 : RecursiveMutexAutoLock lock(mRecursiveMutex);
3085 0 : ParentLayerPoint residualVelocity;
3086 : if (!mX.OverscrollBehaviorAllowsHandoff()) {
3087 0 : residualVelocity.x = aHandoffVelocity.x;
3088 : aHandoffVelocity.x = 0;
3089 : }
3090 0 : if (!mY.OverscrollBehaviorAllowsHandoff()) {
3091 : residualVelocity.y = aHandoffVelocity.y;
3092 0 : aHandoffVelocity.y = 0;
3093 : }
3094 0 : return residualVelocity;
3095 : }
3096 :
3097 0 : bool AsyncPanZoomController::OverscrollBehaviorAllowsSwipe() const
3098 : {
3099 : RecursiveMutexAutoLock lock(mRecursiveMutex);
3100 0 : // Swipe navigation is a "non-local" overscroll behavior like handoff.
3101 0 : return mX.OverscrollBehaviorAllowsHandoff();
3102 : }
3103 :
3104 : void AsyncPanZoomController::HandleFlingOverscroll(const ParentLayerPoint& aVelocity,
3105 0 : const RefPtr<const OverscrollHandoffChain>& aOverscrollHandoffChain,
3106 0 : const RefPtr<const AsyncPanZoomController>& aScrolledApzc) {
3107 0 : APZCTreeManager* treeManagerLocal = GetApzcTreeManager();
3108 : if (treeManagerLocal) {
3109 0 : const FlingHandoffState handoffState{aVelocity,
3110 0 : aOverscrollHandoffChain,
3111 0 : true /* handoff */,
3112 : aScrolledApzc};
3113 0 : ParentLayerPoint residualVelocity = treeManagerLocal->DispatchFling(this, handoffState);
3114 0 : if (!IsZero(residualVelocity) && IsPannable() && gfxPrefs::APZOverscrollEnabled()) {
3115 : // Obey overscroll-behavior.
3116 : RecursiveMutexAutoLock lock(mRecursiveMutex);
3117 0 : if (!mX.OverscrollBehaviorAllowsOverscrollEffect()) {
3118 0 : residualVelocity.x = 0;
3119 : }
3120 : if (!mY.OverscrollBehaviorAllowsOverscrollEffect()) {
3121 : residualVelocity.y = 0;
3122 0 : }
3123 :
3124 0 : if (!IsZero(residualVelocity)) {
3125 : mOverscrollEffect->HandleFlingOverscroll(residualVelocity);
3126 : }
3127 0 : }
3128 0 : }
3129 : }
3130 0 :
3131 0 : void AsyncPanZoomController::HandleSmoothScrollOverscroll(const ParentLayerPoint& aVelocity) {
3132 : // We must call BuildOverscrollHandoffChain from this deferred callback
3133 : // function in order to avoid a deadlock when acquiring the tree lock.
3134 0 : HandleFlingOverscroll(aVelocity, BuildOverscrollHandoffChain(), nullptr);
3135 0 : }
3136 :
3137 0 : void AsyncPanZoomController::SmoothScrollTo(const CSSPoint& aDestination) {
3138 0 : if (mState == SMOOTH_SCROLL && mAnimation) {
3139 0 : APZC_LOG("%p updating destination on existing animation\n", this);
3140 : RefPtr<SmoothScrollAnimation> animation(
3141 : static_cast<SmoothScrollAnimation*>(mAnimation.get()));
3142 : animation->SetDestination(CSSPoint::ToAppUnits(aDestination));
3143 : } else {
3144 0 : CancelAnimation();
3145 0 : SetState(SMOOTH_SCROLL);
3146 0 : nsPoint initialPosition = CSSPoint::ToAppUnits(mFrameMetrics.GetScrollOffset());
3147 : // Convert velocity from ParentLayerPoints/ms to ParentLayerPoints/s and
3148 0 : // then to appunits/second.
3149 : nsPoint initialVelocity =
3150 : CSSPoint::ToAppUnits(
3151 : ParentLayerPoint(mX.GetVelocity() * 1000.0f,
3152 : mY.GetVelocity() * 1000.0f) /
3153 0 : mFrameMetrics.GetZoom());
3154 0 :
3155 : nsPoint destination = CSSPoint::ToAppUnits(aDestination);
3156 0 :
3157 : StartAnimation(new SmoothScrollAnimation(*this,
3158 0 : initialPosition, initialVelocity,
3159 0 : destination,
3160 0 : gfxPrefs::ScrollBehaviorSpringConstant(),
3161 0 : gfxPrefs::ScrollBehaviorDampingRatio()));
3162 : }
3163 0 : }
3164 :
3165 : void AsyncPanZoomController::StartOverscrollAnimation(const ParentLayerPoint& aVelocity) {
3166 : SetState(OVERSCROLL_ANIMATION);
3167 : StartAnimation(new OverscrollAnimation(*this, aVelocity));
3168 : }
3169 0 :
3170 0 : void AsyncPanZoomController::CallDispatchScroll(ParentLayerPoint& aStartPoint,
3171 0 : ParentLayerPoint& aEndPoint,
3172 : OverscrollHandoffState& aOverscrollHandoffState) {
3173 : // Make a local copy of the tree manager pointer and check if it's not
3174 : // null before calling DispatchScroll(). This is necessary because
3175 0 : // Destroy(), which nulls out mTreeManager, could be called concurrently.
3176 0 : APZCTreeManager* treeManagerLocal = GetApzcTreeManager();
3177 0 : if (!treeManagerLocal) {
3178 0 : return;
3179 0 : }
3180 :
3181 0 : // Obey overscroll-behavior.
3182 0 : ParentLayerPoint endPoint = aEndPoint;
3183 : if (aOverscrollHandoffState.mChainIndex > 0) {
3184 0 : RecursiveMutexAutoLock lock(mRecursiveMutex);
3185 : if (!mX.OverscrollBehaviorAllowsHandoff()) {
3186 0 : endPoint.x = aStartPoint.x;
3187 : }
3188 : if (!mY.OverscrollBehaviorAllowsHandoff()) {
3189 : endPoint.y = aStartPoint.y;
3190 : }
3191 : if (aStartPoint == endPoint) {
3192 0 : // Handoff not allowed in either direction - don't even bother.
3193 : return;
3194 : }
3195 0 : }
3196 0 :
3197 0 : treeManagerLocal->DispatchScroll(this,
3198 : aStartPoint, endPoint,
3199 : aOverscrollHandoffState);
3200 0 : }
3201 :
3202 0 : void AsyncPanZoomController::TrackTouch(const MultiTouchInput& aEvent) {
3203 0 : ParentLayerPoint prevTouchPoint(mX.GetPos(), mY.GetPos());
3204 : ParentLayerPoint touchPoint = GetFirstTouchPoint(aEvent);
3205 0 :
3206 : ScreenPoint panDistance = ToScreenCoordinates(
3207 0 : ParentLayerPoint(mX.PanDistance(touchPoint.x),
3208 0 : mY.PanDistance(touchPoint.y)),
3209 : PanStart());
3210 0 : HandlePanningUpdate(panDistance);
3211 :
3212 0 : UpdateWithTouchAtDevicePoint(aEvent);
3213 0 :
3214 : if (prevTouchPoint != touchPoint) {
3215 0 : MOZ_ASSERT(GetCurrentTouchBlock());
3216 : OverscrollHandoffState handoffState(
3217 0 : *GetCurrentTouchBlock()->GetOverscrollHandoffChain(),
3218 0 : panDistance,
3219 : ScrollSource::Touch);
3220 : CallDispatchScroll(prevTouchPoint, touchPoint, handoffState);
3221 0 : }
3222 : }
3223 0 :
3224 0 : ParentLayerPoint AsyncPanZoomController::GetFirstTouchPoint(const MultiTouchInput& aEvent) {
3225 0 : return ((SingleTouchData&)aEvent.mTouches[0]).mLocalScreenPoint;
3226 0 : }
3227 0 :
3228 : void AsyncPanZoomController::StartAnimation(AsyncPanZoomAnimation* aAnimation)
3229 0 : {
3230 0 : RecursiveMutexAutoLock lock(mRecursiveMutex);
3231 : mAnimation = aAnimation;
3232 : mLastSampleTime = GetFrameTime();
3233 0 : ScheduleComposite();
3234 0 : }
3235 :
3236 : void AsyncPanZoomController::CancelAnimation(CancelAnimationFlags aFlags) {
3237 0 : RecursiveMutexAutoLock lock(mRecursiveMutex);
3238 0 : APZC_LOG("%p running CancelAnimation(0x%x) in state %d\n", this, aFlags, mState);
3239 :
3240 : if ((aFlags & ExcludeWheel) && mState == WHEEL_SCROLL) {
3241 0 : return;
3242 0 : }
3243 :
3244 : if (mAnimation) {
3245 : mAnimation->Cancel(aFlags);
3246 : }
3247 :
3248 0 : SetState(NOTHING);
3249 0 : mAnimation = nullptr;
3250 0 : // Since there is no animation in progress now the axes should
3251 0 : // have no velocity either. If we are dropping the velocity from a non-zero
3252 0 : // value we should trigger a repaint as the displayport margins are dependent
3253 : // on the velocity and the last repaint request might not have good margins
3254 : // any more.
3255 : bool repaint = !IsZero(GetVelocityVector());
3256 0 : mX.SetVelocity(0);
3257 0 : mY.SetVelocity(0);
3258 0 : mX.SetAxisLocked(false);
3259 : mY.SetAxisLocked(false);
3260 : // Setting the state to nothing and cancelling the animation can
3261 : // preempt normal mechanisms for relieving overscroll, so we need to clear
3262 0 : // overscroll here.
3263 0 : if (!(aFlags & ExcludeOverscroll) && IsOverscrolled()) {
3264 : ClearOverscroll();
3265 0 : repaint = true;
3266 0 : }
3267 0 : // Similar to relieving overscroll, we also need to snap to any snap points
3268 0 : // if appropriate.
3269 : if (aFlags & CancelAnimationFlags::ScrollSnap) {
3270 : ScrollSnap();
3271 : }
3272 0 : if (repaint) {
3273 0 : RequestContentRepaint();
3274 0 : ScheduleComposite();
3275 0 : UpdateSharedCompositorFrameMetrics();
3276 0 : }
3277 : }
3278 0 :
3279 : void AsyncPanZoomController::ClearOverscroll() {
3280 0 : RecursiveMutexAutoLock lock(mRecursiveMutex);
3281 0 : mX.ClearOverscroll();
3282 : mY.ClearOverscroll();
3283 0 : }
3284 :
3285 0 : void AsyncPanZoomController::SetCompositorController(CompositorController* aCompositorController)
3286 0 : {
3287 : mCompositorController = aCompositorController;
3288 0 : }
3289 :
3290 0 : void AsyncPanZoomController::SetMetricsSharingController(MetricsSharingController* aMetricsSharingController)
3291 : {
3292 0 : mMetricsSharingController = aMetricsSharingController;
3293 0 : }
3294 :
3295 : void AsyncPanZoomController::AdjustScrollForSurfaceShift(const ScreenPoint& aShift)
3296 0 : {
3297 : RecursiveMutexAutoLock lock(mRecursiveMutex);
3298 0 : CSSPoint adjustment =
3299 0 : ViewAs<ParentLayerPixel>(aShift, PixelCastJustification::ScreenIsParentLayerForRoot)
3300 : / mFrameMetrics.GetZoom();
3301 : APZC_LOG("%p adjusting scroll position by %s for surface shift\n",
3302 : this, Stringify(adjustment).c_str());
3303 0 : CSSRect scrollRange = mFrameMetrics.CalculateScrollRange();
3304 0 : // Apply shift to mFrameMetrics.mScrollOffset.
3305 0 : mFrameMetrics.SetScrollOffset(scrollRange.ClampPoint(
3306 0 : mFrameMetrics.GetScrollOffset() + adjustment));
3307 : // Apply shift to mCompositedScrollOffset, since the dynamic toolbar expects
3308 0 : // the shift to take effect right away, without the usual frame delay.
3309 0 : mCompositedScrollOffset = scrollRange.ClampPoint(
3310 0 : mCompositedScrollOffset + adjustment);
3311 : RequestContentRepaint();
3312 0 : UpdateSharedCompositorFrameMetrics();
3313 0 : }
3314 0 :
3315 : void AsyncPanZoomController::ScrollBy(const CSSPoint& aOffset) {
3316 0 : mFrameMetrics.ScrollBy(aOffset);
3317 : }
3318 0 :
3319 : void AsyncPanZoomController::ScrollByAndClamp(const CSSPoint& aOffset) {
3320 : mFrameMetrics.ClampAndSetScrollOffset(mFrameMetrics.GetScrollOffset() + aOffset);
3321 : }
3322 :
3323 0 : void AsyncPanZoomController::ScaleWithFocus(float aScale,
3324 0 : const CSSPoint& aFocus) {
3325 : mFrameMetrics.ZoomBy(aScale);
3326 : // We want to adjust the scroll offset such that the CSS point represented by aFocus remains
3327 : // at the same position on the screen before and after the change in zoom. The below code
3328 : // accomplishes this; see https://bugzilla.mozilla.org/show_bug.cgi?id=923431#c6 for an
3329 : // in-depth explanation of how.
3330 0 : mFrameMetrics.SetScrollOffset((mFrameMetrics.GetScrollOffset() + aFocus) - (aFocus / aScale));
3331 : }
3332 :
3333 0 : /**
3334 0 : * Enlarges the displayport along both axes based on the velocity.
3335 : */
3336 0 : static CSSSize
3337 0 : CalculateDisplayPortSize(const CSSSize& aCompositionSize,
3338 : const CSSPoint& aVelocity)
3339 0 : {
3340 0 : bool xIsStationarySpeed = fabsf(aVelocity.x) < gfxPrefs::APZMinSkateSpeed();
3341 : bool yIsStationarySpeed = fabsf(aVelocity.y) < gfxPrefs::APZMinSkateSpeed();
3342 0 : float xMultiplier = xIsStationarySpeed
3343 0 : ? gfxPrefs::APZXStationarySizeMultiplier()
3344 : : gfxPrefs::APZXSkateSizeMultiplier();
3345 : float yMultiplier = yIsStationarySpeed
3346 0 : ? gfxPrefs::APZYStationarySizeMultiplier()
3347 0 : : gfxPrefs::APZYSkateSizeMultiplier();
3348 :
3349 : if (IsHighMemSystem() && !xIsStationarySpeed) {
3350 0 : xMultiplier += gfxPrefs::APZXSkateHighMemAdjust();
3351 : }
3352 :
3353 : if (IsHighMemSystem() && !yIsStationarySpeed) {
3354 : yMultiplier += gfxPrefs::APZYSkateHighMemAdjust();
3355 : }
3356 :
3357 : return aCompositionSize * CSSSize(xMultiplier, yMultiplier);
3358 : }
3359 :
3360 0 : /**
3361 : * Ensures that the displayport is at least as large as the visible area
3362 : * inflated by the danger zone. If this is not the case then the
3363 0 : * "AboutToCheckerboard" function in TiledContentClient.cpp will return true
3364 0 : * even in the stable state.
3365 0 : */
3366 0 : static CSSSize
3367 0 : ExpandDisplayPortToDangerZone(const CSSSize& aDisplayPortSize,
3368 0 : const FrameMetrics& aFrameMetrics)
3369 : {
3370 0 : CSSSize dangerZone(0.0f, 0.0f);
3371 : if (aFrameMetrics.LayersPixelsPerCSSPixel().xScale != 0 &&
3372 : aFrameMetrics.LayersPixelsPerCSSPixel().yScale != 0) {
3373 0 : dangerZone = LayerSize(
3374 : gfxPrefs::APZDangerZoneX(),
3375 : gfxPrefs::APZDangerZoneY()) / aFrameMetrics.LayersPixelsPerCSSPixel();
3376 0 : }
3377 : const CSSSize compositionSize = aFrameMetrics.CalculateBoundedCompositedSizeInCssPixels();
3378 0 :
3379 : const float xSize = std::max(aDisplayPortSize.width,
3380 : compositionSize.width + (2 * dangerZone.width));
3381 :
3382 : const float ySize = std::max(aDisplayPortSize.height,
3383 : compositionSize.height + (2 * dangerZone.height));
3384 :
3385 : return CSSSize(xSize, ySize);
3386 : }
3387 0 :
3388 : /**
3389 : * Attempts to redistribute any area in the displayport that would get clipped
3390 : * by the scrollable rect, or be inaccessible due to disabled scrolling, to the
3391 : * other axis, while maintaining total displayport area.
3392 0 : */
3393 0 : static void
3394 0 : RedistributeDisplayPortExcess(CSSSize& aDisplayPortSize,
3395 0 : const CSSRect& aScrollableRect)
3396 0 : {
3397 0 : // As aDisplayPortSize.height * aDisplayPortSize.width does not change,
3398 : // we are just scaling by the ratio and its inverse.
3399 0 : if (aDisplayPortSize.height > aScrollableRect.Height()) {
3400 : aDisplayPortSize.width *= (aDisplayPortSize.height / aScrollableRect.Height());
3401 : aDisplayPortSize.height = aScrollableRect.Height();
3402 0 : } else if (aDisplayPortSize.width > aScrollableRect.Width()) {
3403 : aDisplayPortSize.height *= (aDisplayPortSize.width / aScrollableRect.Width());
3404 : aDisplayPortSize.width = aScrollableRect.Width();
3405 : }
3406 0 : }
3407 :
3408 : /* static */
3409 0 : const ScreenMargin AsyncPanZoomController::CalculatePendingDisplayPort(
3410 : const FrameMetrics& aFrameMetrics,
3411 : const ParentLayerPoint& aVelocity)
3412 0 : {
3413 0 : if (aFrameMetrics.IsScrollInfoLayer()) {
3414 0 : // Don't compute margins. Since we can't asynchronously scroll this frame,
3415 0 : // we don't want to paint anything more than the composition bounds.
3416 : return ScreenMargin();
3417 0 : }
3418 :
3419 : CSSSize compositionSize = aFrameMetrics.CalculateBoundedCompositedSizeInCssPixels();
3420 0 : CSSPoint velocity;
3421 : if (aFrameMetrics.GetZoom() != CSSToParentLayerScale2D(0, 0)) {
3422 0 : velocity = aVelocity / aFrameMetrics.GetZoom(); // avoid division by zero
3423 : }
3424 0 : CSSRect scrollableRect = aFrameMetrics.GetExpandedScrollableRect();
3425 0 :
3426 : // Calculate the displayport size based on how fast we're moving along each axis.
3427 : CSSSize displayPortSize = CalculateDisplayPortSize(compositionSize, velocity);
3428 :
3429 : displayPortSize = ExpandDisplayPortToDangerZone(displayPortSize, aFrameMetrics);
3430 :
3431 : if (gfxPrefs::APZEnlargeDisplayPortWhenClipped()) {
3432 : RedistributeDisplayPortExcess(displayPortSize, scrollableRect);
3433 : }
3434 :
3435 : // We calculate a "displayport" here which is relative to the scroll offset.
3436 0 : // Note that the scroll offset we have here in the APZ code may not be the
3437 0 : // same as the base rect that gets used on the layout side when the displayport
3438 0 : // margins are actually applied, so it is important to only consider the
3439 : // displayport as margins relative to a scroll offset rather than relative to
3440 : // something more unchanging like the scrollable rect origin.
3441 :
3442 0 : // Center the displayport based on its expansion over the composition size.
3443 0 : CSSRect displayPort((compositionSize.width - displayPortSize.width) / 2.0f,
3444 : (compositionSize.height - displayPortSize.height) / 2.0f,
3445 : displayPortSize.width, displayPortSize.height);
3446 :
3447 : // Offset the displayport, depending on how fast we're moving and the
3448 : // estimated time it takes to paint, to try to minimise checkerboarding.
3449 : float paintFactor = kDefaultEstimatedPaintDurationMs;
3450 0 : displayPort.MoveBy(velocity * paintFactor * gfxPrefs::APZVelocityBias());
3451 0 :
3452 0 : APZC_LOG_FM(aFrameMetrics,
3453 0 : "Calculated displayport as (%f %f %f %f) from velocity %s paint time %f metrics",
3454 0 : displayPort.x, displayPort.y, displayPort.Width(), displayPort.Height(),
3455 : ToString(aVelocity).c_str(), paintFactor);
3456 0 :
3457 : CSSMargin cssMargins;
3458 : cssMargins.left = -displayPort.X();
3459 0 : cssMargins.top = -displayPort.Y();
3460 0 : cssMargins.right = displayPort.Width() - compositionSize.width - cssMargins.left;
3461 0 : cssMargins.bottom = displayPort.Height() - compositionSize.height - cssMargins.top;
3462 :
3463 0 : return cssMargins * aFrameMetrics.DisplayportPixelsPerCSSPixel();
3464 : }
3465 0 :
3466 0 : void AsyncPanZoomController::ScheduleComposite() {
3467 0 : if (mCompositorController) {
3468 0 : mCompositorController->ScheduleRenderOnCompositorThread();
3469 : }
3470 0 : }
3471 0 :
3472 0 : void AsyncPanZoomController::ScheduleCompositeAndMaybeRepaint() {
3473 0 : ScheduleComposite();
3474 0 : RequestContentRepaint();
3475 : }
3476 0 :
3477 : void AsyncPanZoomController::FlushRepaintForOverscrollHandoff() {
3478 : RecursiveMutexAutoLock lock(mRecursiveMutex);
3479 0 : RequestContentRepaint();
3480 0 : UpdateSharedCompositorFrameMetrics();
3481 0 : }
3482 0 :
3483 : void AsyncPanZoomController::FlushRepaintForNewInputBlock() {
3484 0 : APZC_LOG("%p flushing repaint for new input block\n", this);
3485 0 :
3486 : RecursiveMutexAutoLock lock(mRecursiveMutex);
3487 : RequestContentRepaint();
3488 0 : UpdateSharedCompositorFrameMetrics();
3489 : }
3490 0 :
3491 0 : bool AsyncPanZoomController::SnapBackIfOverscrolled() {
3492 : RecursiveMutexAutoLock lock(mRecursiveMutex);
3493 : // It's possible that we're already in the middle of an overscroll
3494 : // animation - if so, don't start a new one.
3495 : if (IsOverscrolled() && mState != OVERSCROLL_ANIMATION) {
3496 0 : APZC_LOG("%p is overscrolled, starting snap-back\n", this);
3497 0 : StartOverscrollAnimation(ParentLayerPoint(0, 0));
3498 : return true;
3499 : }
3500 : // If we don't kick off an overscroll animation, we still need to ask the
3501 : // main thread to snap to any nearby snap points, assuming we haven't already
3502 0 : // done so when we started this fling
3503 0 : if (mState != FLING) {
3504 0 : ScrollSnap();
3505 0 : }
3506 : return false;
3507 : }
3508 :
3509 0 : bool AsyncPanZoomController::IsFlingingFast() const {
3510 : RecursiveMutexAutoLock lock(mRecursiveMutex);
3511 : if (mState == FLING &&
3512 0 : GetVelocityVector().Length() > gfxPrefs::APZFlingStopOnTapThreshold()) {
3513 0 : APZC_LOG("%p is moving fast\n", this);
3514 0 : return true;
3515 : }
3516 : return false;
3517 0 : }
3518 0 :
3519 0 : bool AsyncPanZoomController::IsPannable() const {
3520 : RecursiveMutexAutoLock lock(mRecursiveMutex);
3521 : return mX.CanScroll() || mY.CanScroll();
3522 0 : }
3523 0 :
3524 0 : bool AsyncPanZoomController::IsScrollInfoLayer() const {
3525 : RecursiveMutexAutoLock lock(mRecursiveMutex);
3526 : return mFrameMetrics.IsScrollInfoLayer();
3527 0 : }
3528 :
3529 : int32_t AsyncPanZoomController::GetLastTouchIdentifier() const {
3530 : RefPtr<GestureEventListener> listener = GetGestureEventListener();
3531 : return listener ? listener->GetLastTouchIdentifier() : -1;
3532 0 : }
3533 0 :
3534 0 : void AsyncPanZoomController::RequestContentRepaint(bool aUserAction) {
3535 : // Reinvoke this method on the repaint thread if it's not there already. It's
3536 0 : // important to do this before the call to CalculatePendingDisplayPort, so
3537 : // that CalculatePendingDisplayPort uses the most recent available version of
3538 : // mFrameMetrics, just before the paint request is dispatched to content.
3539 0 : RefPtr<GeckoContentController> controller = GetGeckoContentController();
3540 0 : if (!controller) {
3541 : return;
3542 : }
3543 : if (!controller->IsRepaintThread()) {
3544 0 : // use the local variable to resolve the function overload.
3545 : auto func = static_cast<void (AsyncPanZoomController::*)(bool)>
3546 : (&AsyncPanZoomController::RequestContentRepaint);
3547 : controller->DispatchToRepaintThread(NewRunnableMethod<bool>(
3548 0 : "layers::AsyncPanZoomController::RequestContentRepaint",
3549 : this,
3550 0 : func,
3551 0 : aUserAction));
3552 0 : return;
3553 0 : }
3554 0 :
3555 0 : MOZ_ASSERT(controller->IsRepaintThread());
3556 0 :
3557 : RecursiveMutexAutoLock lock(mRecursiveMutex);
3558 : ParentLayerPoint velocity = GetVelocityVector();
3559 : mFrameMetrics.SetDisplayPortMargins(CalculatePendingDisplayPort(mFrameMetrics, velocity));
3560 0 : mFrameMetrics.SetUseDisplayPortMargins(true);
3561 : mFrameMetrics.SetPaintRequestTime(TimeStamp::Now());
3562 : mFrameMetrics.SetRepaintDrivenByUserAction(aUserAction);
3563 : RequestContentRepaint(mFrameMetrics, velocity);
3564 0 : }
3565 0 :
3566 0 : /*static*/ CSSRect
3567 0 : GetDisplayPortRect(const FrameMetrics& aFrameMetrics)
3568 : {
3569 : // This computation is based on what happens in CalculatePendingDisplayPort. If that
3570 : // changes then this might need to change too
3571 0 : CSSRect baseRect(aFrameMetrics.GetScrollOffset(),
3572 : aFrameMetrics.CalculateBoundedCompositedSizeInCssPixels());
3573 : baseRect.Inflate(aFrameMetrics.GetDisplayPortMargins() / aFrameMetrics.DisplayportPixelsPerCSSPixel());
3574 0 : return baseRect;
3575 0 : }
3576 0 :
3577 : void
3578 0 : AsyncPanZoomController::RequestContentRepaint(const FrameMetrics& aFrameMetrics,
3579 : const ParentLayerPoint& aVelocity)
3580 : {
3581 : RefPtr<GeckoContentController> controller = GetGeckoContentController();
3582 0 : if (!controller) {
3583 0 : return;
3584 0 : }
3585 0 : MOZ_ASSERT(controller->IsRepaintThread());
3586 0 :
3587 0 : // If we're trying to paint what we already think is painted, discard this
3588 0 : // request since it's a pointless paint.
3589 0 : ScreenMargin marginDelta = (mLastPaintRequestMetrics.GetDisplayPortMargins()
3590 0 : - aFrameMetrics.GetDisplayPortMargins());
3591 0 : if (fabsf(marginDelta.left) < EPSILON &&
3592 0 : fabsf(marginDelta.top) < EPSILON &&
3593 0 : fabsf(marginDelta.right) < EPSILON &&
3594 0 : fabsf(marginDelta.bottom) < EPSILON &&
3595 0 : fabsf(mLastPaintRequestMetrics.GetScrollOffset().x -
3596 0 : aFrameMetrics.GetScrollOffset().x) < EPSILON &&
3597 0 : fabsf(mLastPaintRequestMetrics.GetScrollOffset().y -
3598 0 : aFrameMetrics.GetScrollOffset().y) < EPSILON &&
3599 0 : aFrameMetrics.GetPresShellResolution() == mLastPaintRequestMetrics.GetPresShellResolution() &&
3600 0 : aFrameMetrics.GetZoom() == mLastPaintRequestMetrics.GetZoom() &&
3601 0 : fabsf(aFrameMetrics.GetViewport().Width() -
3602 : mLastPaintRequestMetrics.GetViewport().Width()) < EPSILON &&
3603 : fabsf(aFrameMetrics.GetViewport().Height() -
3604 : mLastPaintRequestMetrics.GetViewport().Height()) < EPSILON &&
3605 : aFrameMetrics.GetScrollGeneration() ==
3606 : mLastPaintRequestMetrics.GetScrollGeneration() &&
3607 0 : aFrameMetrics.GetScrollUpdateType() ==
3608 0 : mLastPaintRequestMetrics.GetScrollUpdateType()) {
3609 0 : return;
3610 0 : }
3611 0 :
3612 0 : APZC_LOG_FM(aFrameMetrics, "%p requesting content repaint", this);
3613 0 : { // scope lock
3614 0 : MutexAutoLock lock(mCheckerboardEventLock);
3615 : if (mCheckerboardEvent && mCheckerboardEvent->IsRecordingTrace()) {
3616 : std::stringstream info;
3617 : info << " velocity " << aVelocity;
3618 0 : std::string str = info.str();
3619 : mCheckerboardEvent->UpdateRendertraceProperty(
3620 0 : CheckerboardEvent::RequestedDisplayPort, GetDisplayPortRect(aFrameMetrics),
3621 0 : str);
3622 0 : }
3623 : }
3624 :
3625 0 : MOZ_ASSERT(aFrameMetrics.GetScrollUpdateType() == FrameMetrics::eNone ||
3626 : aFrameMetrics.GetScrollUpdateType() == FrameMetrics::eUserAction);
3627 : controller->RequestContentRepaint(aFrameMetrics);
3628 0 : mExpectedGeckoMetrics = aFrameMetrics;
3629 : mLastPaintRequestMetrics = aFrameMetrics;
3630 : }
3631 :
3632 : bool AsyncPanZoomController::UpdateAnimation(const TimeStamp& aSampleTime,
3633 : nsTArray<RefPtr<Runnable>>* aOutDeferredTasks)
3634 : {
3635 : AssertOnSamplerThread();
3636 0 :
3637 0 : // This function may get called multiple with the same sample time, for two
3638 : // reasons: (1) there may be multiple layers with this APZC, and each layer
3639 : // invokes this function during composition, and (2) we might composite
3640 : // multiple times at the same timestamp.
3641 : // However we only want to do one animation step per composition so we need
3642 : // to deduplicate these calls first.
3643 0 : if (mLastSampleTime == aSampleTime) {
3644 : return (mAnimation != nullptr);
3645 0 : }
3646 0 :
3647 : // Sample the composited async transform once per composite. Note that we
3648 0 : // call this after the |mLastSampleTime == aSampleTime| check, to ensure
3649 0 : // it's only called once per APZC on each composite.
3650 0 : bool needComposite = SampleCompositedAsyncTransform();
3651 0 :
3652 0 : TimeDuration sampleTimeDelta = aSampleTime - mLastSampleTime;
3653 0 : mLastSampleTime = aSampleTime;
3654 0 :
3655 : if (mAnimation) {
3656 : bool continueAnimation = mAnimation->Sample(mFrameMetrics, sampleTimeDelta);
3657 : bool wantsRepaints = mAnimation->WantsRepaints();
3658 : *aOutDeferredTasks = mAnimation->TakeDeferredTasks();
3659 0 : if (!continueAnimation) {
3660 0 : mAnimation = nullptr;
3661 : SetState(NOTHING);
3662 0 : }
3663 0 : // Request a repaint at the end of the animation in case something such as a
3664 : // call to NotifyLayersUpdated was invoked during the animation and Gecko's
3665 : // current state is some intermediate point of the animation.
3666 : if (!continueAnimation || wantsRepaints) {
3667 : RequestContentRepaint();
3668 : }
3669 0 : UpdateSharedCompositorFrameMetrics();
3670 : needComposite = true;
3671 0 : }
3672 : return needComposite;
3673 0 : }
3674 0 :
3675 : AsyncTransformComponentMatrix
3676 : AsyncPanZoomController::GetOverscrollTransform(AsyncTransformConsumer aMode) const
3677 0 : {
3678 0 : RecursiveMutexAutoLock lock(mRecursiveMutex);
3679 :
3680 : if (aMode == eForCompositing && mScrollMetadata.IsApzForceDisabled()) {
3681 : return AsyncTransformComponentMatrix();
3682 0 : }
3683 0 :
3684 0 : if (!IsOverscrolled()) {
3685 : return AsyncTransformComponentMatrix();
3686 : }
3687 0 :
3688 : // The overscroll effect is a simple translation by the overscroll offset.
3689 0 : ParentLayerPoint overscrollOffset(-mX.GetOverscroll(), -mY.GetOverscroll());
3690 : return AsyncTransformComponentMatrix()
3691 : .PostTranslate(overscrollOffset.x, overscrollOffset.y, 0);
3692 : }
3693 :
3694 0 : bool AsyncPanZoomController::AdvanceAnimations(const TimeStamp& aSampleTime)
3695 : {
3696 : AssertOnSamplerThread();
3697 :
3698 : // Don't send any state-change notifications until the end of the function,
3699 : // because we may go through some intermediate states while we finish
3700 : // animations and start new ones.
3701 0 : StateChangeNotificationBlocker blocker(this);
3702 0 :
3703 0 : // The eventual return value of this function. The compositor needs to know
3704 : // whether or not to advance by a frame as soon as it can. For example, if a
3705 : // fling is happening, it has to keep compositing so that the animation is
3706 0 : // smooth. If an animation frame is requested, it is the compositor's
3707 : // responsibility to schedule a composite.
3708 0 : mAsyncTransformAppliedToContent = false;
3709 : bool requestAnimationFrame = false;
3710 : nsTArray<RefPtr<Runnable>> deferredTasks;
3711 0 :
3712 0 : {
3713 0 : RecursiveMutexAutoLock lock(mRecursiveMutex);
3714 :
3715 0 : requestAnimationFrame = UpdateAnimation(aSampleTime, &deferredTasks);
3716 0 :
3717 : { // scope lock
3718 : MutexAutoLock lock(mCheckerboardEventLock);
3719 : if (mCheckerboardEvent) {
3720 : mCheckerboardEvent->UpdateRendertraceProperty(
3721 : CheckerboardEvent::UserVisible,
3722 : CSSRect(mFrameMetrics.GetScrollOffset(),
3723 : mFrameMetrics.CalculateCompositedSizeInCssPixels()));
3724 : }
3725 0 : }
3726 0 : }
3727 :
3728 : // Execute any deferred tasks queued up by mAnimation's Sample() (called by
3729 : // UpdateAnimation()). This needs to be done after the monitor is released
3730 : // since the tasks are allowed to call APZCTreeManager methods which can grab
3731 0 : // the tree lock.
3732 : for (uint32_t i = 0; i < deferredTasks.Length(); ++i) {
3733 : APZThreadUtils::RunOnControllerThread(deferredTasks[i].forget());
3734 : }
3735 0 :
3736 0 : // If any of the deferred tasks starts a new animation, it will request a
3737 0 : // new composite directly, so we can just return requestAnimationFrame here.
3738 : return requestAnimationFrame;
3739 0 : }
3740 0 :
3741 : CSSRect
3742 0 : AsyncPanZoomController::GetCurrentAsyncLayoutViewport(AsyncTransformConsumer aMode) const {
3743 : RecursiveMutexAutoLock lock(mRecursiveMutex);
3744 : MOZ_ASSERT(mFrameMetrics.IsRootContent(),
3745 : "Only the root content APZC has a layout viewport");
3746 1 : if (aMode == eForCompositing && mScrollMetadata.IsApzForceDisabled()) {
3747 : return mLastContentPaintMetrics.GetViewport();
3748 1 : }
3749 : return GetEffectiveLayoutViewport(aMode);
3750 1 : }
3751 0 :
3752 : ParentLayerPoint
3753 : AsyncPanZoomController::GetCurrentAsyncScrollOffset(AsyncTransformConsumer aMode) const
3754 2 : {
3755 1 : RecursiveMutexAutoLock lock(mRecursiveMutex);
3756 :
3757 : if (aMode == eForCompositing && mScrollMetadata.IsApzForceDisabled()) {
3758 : return mLastContentPaintMetrics.GetScrollOffset() * mLastContentPaintMetrics.GetZoom();
3759 0 : }
3760 0 :
3761 : return (GetEffectiveScrollOffset(aMode) + mTestAsyncScrollOffset)
3762 0 : * GetEffectiveZoom(aMode) * mTestAsyncZoom.scale;
3763 0 : }
3764 :
3765 : CSSPoint
3766 0 : AsyncPanZoomController::GetCurrentAsyncScrollOffsetInCssPixels(AsyncTransformConsumer aMode) const {
3767 : RecursiveMutexAutoLock lock(mRecursiveMutex);
3768 :
3769 : if (aMode == eForCompositing && mScrollMetadata.IsApzForceDisabled()) {
3770 8 : return mLastContentPaintMetrics.GetScrollOffset();
3771 : }
3772 0 :
3773 : return GetEffectiveScrollOffset(aMode) + mTestAsyncScrollOffset;
3774 16 : }
3775 0 :
3776 : AsyncTransform
3777 : AsyncPanZoomController::GetCurrentAsyncTransform(AsyncTransformConsumer aMode) const
3778 1 : {
3779 1 : RecursiveMutexAutoLock lock(mRecursiveMutex);
3780 8 :
3781 : if (aMode == eForCompositing && mScrollMetadata.IsApzForceDisabled()) {
3782 : return AsyncTransform();
3783 1 : }
3784 16 :
3785 : CSSPoint lastPaintScrollOffset;
3786 : if (mLastContentPaintMetrics.IsScrollable()) {
3787 : lastPaintScrollOffset = mLastContentPaintMetrics.GetScrollOffset();
3788 1 : }
3789 0 :
3790 0 : CSSPoint currentScrollOffset = GetEffectiveScrollOffset(aMode) +
3791 : mTestAsyncScrollOffset;
3792 0 :
3793 0 : // If checkerboarding has been disallowed, clamp the scroll position to stay
3794 0 : // within rendered content.
3795 : if (!gfxPrefs::APZAllowCheckerboarding() &&
3796 0 : !mLastContentPaintMetrics.GetDisplayPort().IsEmpty()) {
3797 0 : CSSSize compositedSize = mLastContentPaintMetrics.CalculateCompositedSizeInCssPixels();
3798 : CSSPoint maxScrollOffset = lastPaintScrollOffset +
3799 0 : CSSPoint(mLastContentPaintMetrics.GetDisplayPort().XMost() - compositedSize.width,
3800 0 : mLastContentPaintMetrics.GetDisplayPort().YMost() - compositedSize.height);
3801 : CSSPoint minScrollOffset = lastPaintScrollOffset + mLastContentPaintMetrics.GetDisplayPort().TopLeft();
3802 :
3803 : if (minScrollOffset.x < maxScrollOffset.x) {
3804 8 : currentScrollOffset.x = clamped(currentScrollOffset.x, minScrollOffset.x, maxScrollOffset.x);
3805 : }
3806 0 : if (minScrollOffset.y < maxScrollOffset.y) {
3807 16 : currentScrollOffset.y = clamped(currentScrollOffset.y, minScrollOffset.y, maxScrollOffset.y);
3808 : }
3809 : }
3810 8 :
3811 : CSSToParentLayerScale2D effectiveZoom = GetEffectiveZoom(aMode);
3812 16 :
3813 16 : ParentLayerPoint translation = (currentScrollOffset - lastPaintScrollOffset)
3814 : * effectiveZoom * mTestAsyncZoom.scale;
3815 :
3816 : LayerToParentLayerScale compositedAsyncZoom =
3817 0 : (effectiveZoom / mFrameMetrics.LayersPixelsPerCSSPixel()).ToScaleFactor();
3818 : return AsyncTransform(
3819 0 : LayerToParentLayerScale(compositedAsyncZoom.scale * mTestAsyncZoom.scale),
3820 0 : -translation);
3821 : }
3822 0 :
3823 : CSSRect
3824 : AsyncPanZoomController::GetEffectiveLayoutViewport(AsyncTransformConsumer aMode) const
3825 : {
3826 0 : if (gfxPrefs::APZFrameDelayEnabled() && aMode == eForCompositing) {
3827 : return mCompositedLayoutViewport;
3828 0 : }
3829 0 : return mFrameMetrics.GetViewport();
3830 : }
3831 0 :
3832 : CSSPoint
3833 : AsyncPanZoomController::GetEffectiveScrollOffset(AsyncTransformConsumer aMode) const
3834 : {
3835 9 : if (gfxPrefs::APZFrameDelayEnabled() && aMode == eForCompositing) {
3836 : return mCompositedScrollOffset;
3837 0 : }
3838 8 : return mFrameMetrics.GetScrollOffset();
3839 : }
3840 0 :
3841 : CSSToParentLayerScale2D
3842 : AsyncPanZoomController::GetEffectiveZoom(AsyncTransformConsumer aMode) const
3843 : {
3844 0 : if (gfxPrefs::APZFrameDelayEnabled() && aMode == eForCompositing) {
3845 : return mCompositedZoom;
3846 16 : }
3847 0 : return mFrameMetrics.GetZoom();
3848 0 : }
3849 0 :
3850 0 : bool
3851 0 : AsyncPanZoomController::SampleCompositedAsyncTransform()
3852 0 : {
3853 : RecursiveMutexAutoLock lock(mRecursiveMutex);
3854 : if (mCompositedScrollOffset != mFrameMetrics.GetScrollOffset() ||
3855 : mCompositedZoom != mFrameMetrics.GetZoom()) {
3856 : mCompositedLayoutViewport = mFrameMetrics.GetViewport();
3857 : mCompositedScrollOffset = mFrameMetrics.GetScrollOffset();
3858 0 : mCompositedZoom = mFrameMetrics.GetZoom();
3859 : return true;
3860 0 : }
3861 0 : return false;
3862 : }
3863 :
3864 0 : AsyncTransformComponentMatrix
3865 0 : AsyncPanZoomController::GetCurrentAsyncTransformWithOverscroll(AsyncTransformConsumer aMode) const
3866 : {
3867 : return AsyncTransformComponentMatrix(GetCurrentAsyncTransform(aMode))
3868 0 : * GetOverscrollTransform(aMode);
3869 0 : }
3870 0 :
3871 : Matrix4x4 AsyncPanZoomController::GetTransformToLastDispatchedPaint() const {
3872 : RecursiveMutexAutoLock lock(mRecursiveMutex);
3873 :
3874 : LayerPoint scrollChange =
3875 : (mLastContentPaintMetrics.GetScrollOffset() - mExpectedGeckoMetrics.GetScrollOffset())
3876 0 : * mLastContentPaintMetrics.GetDevPixelsPerCSSPixel()
3877 : * mLastContentPaintMetrics.GetCumulativeResolution();
3878 0 :
3879 0 : // We're interested in the async zoom change. Factor out the content scale
3880 : // that may change when dragging the window to a monitor with a different
3881 0 : // content scale.
3882 0 : LayoutDeviceToParentLayerScale2D lastContentZoom =
3883 : mLastContentPaintMetrics.GetZoom() / mLastContentPaintMetrics.GetDevPixelsPerCSSPixel();
3884 : LayoutDeviceToParentLayerScale2D lastDispatchedZoom =
3885 : mExpectedGeckoMetrics.GetZoom() / mExpectedGeckoMetrics.GetDevPixelsPerCSSPixel();
3886 8 : gfxSize zoomChange = lastContentZoom / lastDispatchedZoom;
3887 :
3888 16 : return Matrix4x4::Translation(scrollChange.x, scrollChange.y, 0).
3889 : PostScale(zoomChange.width, zoomChange.height, 1);
3890 8 : }
3891 0 :
3892 0 : uint32_t
3893 : AsyncPanZoomController::GetCheckerboardMagnitude() const
3894 16 : {
3895 : RecursiveMutexAutoLock lock(mRecursiveMutex);
3896 :
3897 0 : CSSPoint currentScrollOffset = mFrameMetrics.GetScrollOffset() + mTestAsyncScrollOffset;
3898 16 : CSSRect painted = mLastContentPaintMetrics.GetDisplayPort() + mLastContentPaintMetrics.GetScrollOffset();
3899 : CSSRect visible = CSSRect(currentScrollOffset, mFrameMetrics.CalculateCompositedSizeInCssPixels());
3900 :
3901 : CSSIntRegion checkerboard;
3902 8 : // Round so as to minimize checkerboarding; if we're only showing fractional
3903 : // pixels of checkerboarding it's not really worth counting
3904 8 : checkerboard.Sub(RoundedIn(visible), RoundedOut(painted));
3905 : return checkerboard.Area();
3906 : }
3907 :
3908 0 : void
3909 : AsyncPanZoomController::ReportCheckerboard(const TimeStamp& aSampleTime)
3910 0 : {
3911 : if (mLastCheckerboardReport == aSampleTime) {
3912 0 : // This function will get called multiple times for each APZC on a single
3913 0 : // composite (once for each layer it is attached to). Only report the
3914 0 : // checkerboard once per composite though.
3915 : return;
3916 0 : }
3917 0 : mLastCheckerboardReport = aSampleTime;
3918 1 :
3919 : bool recordTrace = gfxPrefs::APZRecordCheckerboarding();
3920 16 : bool forTelemetry = Telemetry::CanRecordExtended();
3921 0 : uint32_t magnitude = GetCheckerboardMagnitude();
3922 0 :
3923 : MutexAutoLock lock(mCheckerboardEventLock);
3924 8 : if (!mCheckerboardEvent && (recordTrace || forTelemetry)) {
3925 : mCheckerboardEvent = MakeUnique<CheckerboardEvent>(recordTrace);
3926 : }
3927 : mPotentialCheckerboardTracker.InTransform(IsTransformingState(mState));
3928 8 : if (magnitude) {
3929 : mPotentialCheckerboardTracker.CheckerboardSeen();
3930 : }
3931 0 : UpdateCheckerboardEvent(lock, magnitude);
3932 : }
3933 0 :
3934 0 : void
3935 0 : AsyncPanZoomController::UpdateCheckerboardEvent(const MutexAutoLock& aProofOfLock,
3936 0 : uint32_t aMagnitude)
3937 0 : {
3938 0 : if (mCheckerboardEvent && mCheckerboardEvent->RecordFrameInfo(aMagnitude)) {
3939 : // This checkerboard event is done. Report some metrics to telemetry.
3940 0 : mozilla::Telemetry::Accumulate(mozilla::Telemetry::CHECKERBOARD_SEVERITY,
3941 : mCheckerboardEvent->GetSeverity());
3942 0 : mozilla::Telemetry::Accumulate(mozilla::Telemetry::CHECKERBOARD_PEAK,
3943 : mCheckerboardEvent->GetPeak());
3944 : mozilla::Telemetry::Accumulate(mozilla::Telemetry::CHECKERBOARD_DURATION,
3945 : (uint32_t)mCheckerboardEvent->GetDuration().ToMilliseconds());
3946 0 :
3947 0 : mPotentialCheckerboardTracker.CheckerboardDone();
3948 0 :
3949 : if (gfxPrefs::APZRecordCheckerboarding()) {
3950 0 : // if the pref is enabled, also send it to the storage class. it may be
3951 : // chosen for public display on about:checkerboard, the hall of fame for
3952 1 : // checkerboard events.
3953 : uint32_t severity = mCheckerboardEvent->GetSeverity();
3954 : std::string log = mCheckerboardEvent->GetLog();
3955 0 : CheckerboardEventStorage::Report(severity, log);
3956 : }
3957 0 : mCheckerboardEvent = nullptr;
3958 : }
3959 : }
3960 0 :
3961 0 : void
3962 : AsyncPanZoomController::FlushActiveCheckerboardReport()
3963 0 : {
3964 0 : MutexAutoLock lock(mCheckerboardEventLock);
3965 : // Pretend like we got a frame with 0 pixels checkerboarded. This will
3966 0 : // terminate the checkerboard event and flush it out
3967 : UpdateCheckerboardEvent(lock, 0);
3968 : }
3969 :
3970 0 : bool AsyncPanZoomController::IsCurrentlyCheckerboarding() const {
3971 0 : RecursiveMutexAutoLock lock(mRecursiveMutex);
3972 0 :
3973 0 : if (!gfxPrefs::APZAllowCheckerboarding() || mScrollMetadata.IsApzForceDisabled()) {
3974 0 : return false;
3975 : }
3976 :
3977 : CSSPoint currentScrollOffset = mFrameMetrics.GetScrollOffset() + mTestAsyncScrollOffset;
3978 : CSSRect painted = mLastContentPaintMetrics.GetDisplayPort() + mLastContentPaintMetrics.GetScrollOffset();
3979 0 : painted.Inflate(CSSMargin::FromAppUnits(nsMargin(1, 1, 1, 1))); // fuzz for rounding error
3980 : CSSRect visible = CSSRect(currentScrollOffset, mFrameMetrics.CalculateCompositedSizeInCssPixels());
3981 : if (painted.Contains(visible)) {
3982 2 : return false;
3983 : }
3984 : APZC_LOG_FM(mFrameMetrics, "%p is currently checkerboarding (painted %s visble %s)",
3985 : this, Stringify(painted).c_str(), Stringify(visible).c_str());
3986 0 : return true;
3987 : }
3988 0 :
3989 0 : void AsyncPanZoomController::NotifyLayersUpdated(const ScrollMetadata& aScrollMetadata,
3990 : bool aIsFirstPaint,
3991 0 : bool aThisLayerTreeUpdated)
3992 : {
3993 2 : AssertOnUpdaterThread();
3994 :
3995 : RecursiveMutexAutoLock lock(mRecursiveMutex);
3996 1 : bool isDefault = mScrollMetadata.IsDefault();
3997 :
3998 : const FrameMetrics& aLayerMetrics = aScrollMetadata.GetMetrics();
3999 :
4000 : if ((aScrollMetadata == mLastContentPaintMetadata) && !isDefault) {
4001 : // No new information here, skip it.
4002 : APZC_LOG("%p NotifyLayersUpdated short-circuit\n", this);
4003 : return;
4004 : }
4005 :
4006 : // If the mFrameMetrics scroll offset is different from the last scroll offset
4007 1 : // that the main-thread sent us, then we know that the user has been doing
4008 : // something that triggers a scroll. This check is the APZ equivalent of the
4009 3 : // check on the main-thread at
4010 3 : // https://hg.mozilla.org/mozilla-central/file/97a52326b06a/layout/generic/nsGfxScrollFrame.cpp#l4050
4011 : // There is code below (the use site of userScrolled) that prevents a restored-
4012 0 : // scroll-position update from overwriting a user scroll, again equivalent to
4013 0 : // how the main thread code does the same thing.
4014 : CSSPoint lastScrollOffset = mLastContentPaintMetadata.GetMetrics().GetScrollOffset();
4015 : bool userScrolled =
4016 2 : !FuzzyEqualsAdditive(mFrameMetrics.GetScrollOffset().x, lastScrollOffset.x) ||
4017 : !FuzzyEqualsAdditive(mFrameMetrics.GetScrollOffset().y, lastScrollOffset.y);
4018 :
4019 : if (aLayerMetrics.GetScrollUpdateType() != FrameMetrics::ScrollOffsetUpdateType::ePending) {
4020 : mLastContentPaintMetadata = aScrollMetadata;
4021 0 : }
4022 0 :
4023 0 : mScrollMetadata.SetScrollParentId(aScrollMetadata.GetScrollParentId());
4024 0 : APZC_LOG_FM(aLayerMetrics, "%p got a NotifyLayersUpdated with aIsFirstPaint=%d, aThisLayerTreeUpdated=%d",
4025 0 : this, aIsFirstPaint, aThisLayerTreeUpdated);
4026 :
4027 : { // scope lock
4028 : MutexAutoLock lock(mCheckerboardEventLock);
4029 : if (mCheckerboardEvent && mCheckerboardEvent->IsRecordingTrace()) {
4030 : std::string str;
4031 : if (aThisLayerTreeUpdated) {
4032 0 : if (!aLayerMetrics.GetPaintRequestTime().IsNull()) {
4033 0 : // Note that we might get the paint request time as non-null, but with
4034 0 : // aThisLayerTreeUpdated false. That can happen if we get a layer transaction
4035 0 : // from a different process right after we get the layer transaction with
4036 : // aThisLayerTreeUpdated == true. In this case we want to ignore the
4037 : // paint request time because it was already dumped in the previous layer
4038 : // transaction.
4039 0 : TimeDuration paintTime = TimeStamp::Now() - aLayerMetrics.GetPaintRequestTime();
4040 : std::stringstream info;
4041 : info << " painttime " << paintTime.ToMilliseconds();
4042 0 : str = info.str();
4043 0 : } else {
4044 0 : // This might be indicative of a wasted paint particularly if it happens
4045 : // during a checkerboard event.
4046 0 : str = " (this layertree updated)";
4047 0 : }
4048 0 : }
4049 0 : mCheckerboardEvent->UpdateRendertraceProperty(
4050 : CheckerboardEvent::Page, aLayerMetrics.GetScrollableRect());
4051 0 : mCheckerboardEvent->UpdateRendertraceProperty(
4052 : CheckerboardEvent::PaintedDisplayPort,
4053 : aLayerMetrics.GetDisplayPort() + aLayerMetrics.GetScrollOffset(),
4054 : str);
4055 : if (!aLayerMetrics.GetCriticalDisplayPort().IsEmpty()) {
4056 0 : mCheckerboardEvent->UpdateRendertraceProperty(
4057 1 : CheckerboardEvent::PaintedCriticalDisplayPort,
4058 2 : aLayerMetrics.GetCriticalDisplayPort() + aLayerMetrics.GetScrollOffset());
4059 0 : }
4060 : }
4061 : }
4062 0 :
4063 0 : bool needContentRepaint = false;
4064 0 : bool viewportUpdated = false;
4065 0 : if (FuzzyEqualsAdditive(aLayerMetrics.GetCompositionBounds().Width(), mFrameMetrics.GetCompositionBounds().Width()) &&
4066 : FuzzyEqualsAdditive(aLayerMetrics.GetCompositionBounds().Height(), mFrameMetrics.GetCompositionBounds().Height())) {
4067 0 : // Remote content has sync'd up to the composition geometry
4068 : // change, so we can accept the viewport it's calculated.
4069 : if (mFrameMetrics.GetViewport().Width() != aLayerMetrics.GetViewport().Width() ||
4070 : mFrameMetrics.GetViewport().Height() != aLayerMetrics.GetViewport().Height()) {
4071 : needContentRepaint = true;
4072 : viewportUpdated = true;
4073 : }
4074 : mFrameMetrics.SetViewport(aLayerMetrics.GetViewport());
4075 1 : }
4076 :
4077 1 : // If the layers update was not triggered by our own repaint request, then
4078 : // we want to take the new scroll offset. Check the scroll generation as well
4079 : // to filter duplicate calls to NotifyLayersUpdated with the same scroll offset
4080 0 : // update message.
4081 : bool scrollOffsetUpdated = aLayerMetrics.GetScrollOffsetUpdated()
4082 : && (aLayerMetrics.GetScrollGeneration() != mFrameMetrics.GetScrollGeneration());
4083 0 :
4084 0 : if (scrollOffsetUpdated && userScrolled &&
4085 : aLayerMetrics.GetScrollUpdateType() == FrameMetrics::ScrollOffsetUpdateType::eRestore) {
4086 : APZC_LOG("%p dropping scroll update of type eRestore because of user scroll\n", this);
4087 : scrollOffsetUpdated = false;
4088 : }
4089 :
4090 : bool smoothScrollRequested = aLayerMetrics.GetDoSmoothScroll()
4091 : && (aLayerMetrics.GetScrollGeneration() != mFrameMetrics.GetScrollGeneration());
4092 :
4093 : // TODO if we're in a drag and scrollOffsetUpdated is set then we want to
4094 : // ignore it
4095 :
4096 : #if defined(MOZ_WIDGET_ANDROID)
4097 : if (aLayerMetrics.IsRootContent()) {
4098 : if (APZCTreeManager* manager = GetApzcTreeManager()) {
4099 1 : AndroidDynamicToolbarAnimator* animator = manager->GetAndroidDynamicToolbarAnimator();
4100 : MOZ_ASSERT(animator);
4101 : animator->MaybeUpdateCompositionSizeAndRootFrameMetrics(aLayerMetrics);
4102 1 : }
4103 : }
4104 0 : #endif
4105 1 :
4106 1 : if ((aIsFirstPaint && aThisLayerTreeUpdated) || isDefault) {
4107 : // Initialize our internal state to something sane when the content
4108 1 : // that was just painted is something we knew nothing about previously
4109 1 : CancelAnimation();
4110 1 :
4111 : mScrollMetadata = aScrollMetadata;
4112 1 : mExpectedGeckoMetrics = aLayerMetrics;
4113 : ShareCompositorFrameMetrics();
4114 :
4115 : mCompositedLayoutViewport = mFrameMetrics.GetViewport();
4116 : mCompositedScrollOffset = mFrameMetrics.GetScrollOffset();
4117 : mCompositedZoom = mFrameMetrics.GetZoom();
4118 0 :
4119 : if (mFrameMetrics.GetDisplayPortMargins() != ScreenMargin()) {
4120 : // A non-zero display port margin here indicates a displayport has
4121 : // been set by a previous APZC for the content at this guid. The
4122 : // scrollable rect may have changed since then, making the margins
4123 : // wrong, so we need to calculate a new display port.
4124 : APZC_LOG("%p detected non-empty margins which probably need updating\n", this);
4125 0 : needContentRepaint = true;
4126 0 : }
4127 : } else {
4128 : // If we're not taking the aLayerMetrics wholesale we still need to pull
4129 : // in some things into our local mFrameMetrics because these things are
4130 : // determined by Gecko and our copy in mFrameMetrics may be stale.
4131 :
4132 : if (FuzzyEqualsAdditive(mFrameMetrics.GetCompositionBounds().Width(), aLayerMetrics.GetCompositionBounds().Width()) &&
4133 : mFrameMetrics.GetDevPixelsPerCSSPixel() == aLayerMetrics.GetDevPixelsPerCSSPixel() &&
4134 : !viewportUpdated) {
4135 0 : // Any change to the pres shell resolution was requested by APZ and is
4136 0 : // already included in our zoom; however, other components of the
4137 : // cumulative resolution (a parent document's pres-shell resolution, or
4138 0 : // the css-driven resolution) may have changed, and we need to update
4139 0 : // our zoom to reflect that. Note that we can't just take
4140 0 : // aLayerMetrics.mZoom because the APZ may have additional async zoom
4141 : // since the repaint request.
4142 0 : gfxSize totalResolutionChange = aLayerMetrics.GetCumulativeResolution()
4143 0 : / mFrameMetrics.GetCumulativeResolution();
4144 0 : float presShellResolutionChange = aLayerMetrics.GetPresShellResolution()
4145 : / mFrameMetrics.GetPresShellResolution();
4146 : if (presShellResolutionChange != 1.0f) {
4147 : needContentRepaint = true;
4148 : }
4149 0 : mFrameMetrics.ZoomBy(totalResolutionChange / presShellResolutionChange);
4150 0 : mCompositedZoom.xScale *= (totalResolutionChange / presShellResolutionChange).width;
4151 0 : mCompositedZoom.yScale *= (totalResolutionChange / presShellResolutionChange).height;
4152 : } else {
4153 0 : // Take the new zoom as either device scale or composition width or
4154 0 : // viewport size got changed (e.g. due to orientation change, or content
4155 0 : // changing the meta-viewport tag).
4156 0 : mFrameMetrics.SetZoom(aLayerMetrics.GetZoom());
4157 0 : mCompositedZoom = aLayerMetrics.GetZoom();
4158 : mFrameMetrics.SetDevPixelsPerCSSPixel(aLayerMetrics.GetDevPixelsPerCSSPixel());
4159 0 : }
4160 0 : bool scrollableRectChanged = false;
4161 0 : if (!mFrameMetrics.GetScrollableRect().IsEqualEdges(aLayerMetrics.GetScrollableRect())) {
4162 0 : mFrameMetrics.SetScrollableRect(aLayerMetrics.GetScrollableRect());
4163 0 : needContentRepaint = true;
4164 0 : scrollableRectChanged = true;
4165 0 : }
4166 0 : mFrameMetrics.SetCompositionBounds(aLayerMetrics.GetCompositionBounds());
4167 : mFrameMetrics.SetRootCompositionSize(aLayerMetrics.GetRootCompositionSize());
4168 : mFrameMetrics.SetPresShellResolution(aLayerMetrics.GetPresShellResolution());
4169 : mFrameMetrics.SetCumulativeResolution(aLayerMetrics.GetCumulativeResolution());
4170 0 : mScrollMetadata.SetHasScrollgrab(aScrollMetadata.GetHasScrollgrab());
4171 0 : mScrollMetadata.SetLineScrollAmount(aScrollMetadata.GetLineScrollAmount());
4172 0 : mScrollMetadata.SetPageScrollAmount(aScrollMetadata.GetPageScrollAmount());
4173 0 : mScrollMetadata.SetSnapInfo(ScrollSnapInfo(aScrollMetadata.GetSnapInfo()));
4174 0 : // The scroll clip can differ between layers associated a given scroll frame,
4175 0 : // so APZC (which keeps a single copy of ScrollMetadata per scroll frame)
4176 0 : // has no business using it.
4177 0 : mScrollMetadata.SetScrollClip(Nothing());
4178 0 : mScrollMetadata.SetIsLayersIdRoot(aScrollMetadata.IsLayersIdRoot());
4179 : mScrollMetadata.SetIsAutoDirRootContentRTL(
4180 0 : aScrollMetadata.IsAutoDirRootContentRTL());
4181 : mScrollMetadata.SetUsesContainerScrolling(aScrollMetadata.UsesContainerScrolling());
4182 : mFrameMetrics.SetIsScrollInfoLayer(aLayerMetrics.IsScrollInfoLayer());
4183 : mScrollMetadata.SetForceDisableApz(aScrollMetadata.IsApzForceDisabled());
4184 : mScrollMetadata.SetDisregardedDirection(aScrollMetadata.GetDisregardedDirection());
4185 : mScrollMetadata.SetOverscrollBehavior(aScrollMetadata.GetOverscrollBehavior());
4186 :
4187 : if (scrollOffsetUpdated) {
4188 : APZC_LOG("%p updating scroll offset from %s to %s\n", this,
4189 : ToString(mFrameMetrics.GetScrollOffset()).c_str(),
4190 : ToString(aLayerMetrics.GetScrollOffset()).c_str());
4191 :
4192 0 : // Send an acknowledgement with the new scroll generation so that any
4193 0 : // repaint requests later in this function go through.
4194 0 : // Because of the scroll generation update, any inflight paint requests are
4195 0 : // going to be ignored by layout, and so mExpectedGeckoMetrics
4196 : // becomes incorrect for the purposes of calculating the LD transform. To
4197 : // correct this we need to update mExpectedGeckoMetrics to be the
4198 : // last thing we know was painted by Gecko.
4199 : mFrameMetrics.CopyScrollInfoFrom(aLayerMetrics);
4200 0 : mCompositedLayoutViewport = mFrameMetrics.GetViewport();
4201 : mCompositedScrollOffset = mFrameMetrics.GetScrollOffset();
4202 : mExpectedGeckoMetrics = aLayerMetrics;
4203 :
4204 : // Cancel the animation (which might also trigger a repaint request)
4205 : // after we update the scroll offset above. Otherwise we can be left
4206 : // in a state where things are out of sync.
4207 : CancelAnimation();
4208 :
4209 0 : // Since the scroll offset has changed, we need to recompute the
4210 : // displayport margins and send them to layout. Otherwise there might be
4211 : // scenarios where for example we scroll from the top of a page (where the
4212 0 : // top displayport margin is zero) to the bottom of a page, which will
4213 0 : // result in a displayport that doesn't extend upwards at all.
4214 : // Note that even if the CancelAnimation call above requested a repaint
4215 : // this is fine because we already have repaint request deduplication.
4216 : needContentRepaint = true;
4217 0 : // Since the main-thread scroll offset changed we should trigger a
4218 : // recomposite to make sure it becomes user-visible.
4219 : ScheduleComposite();
4220 : } else if (scrollableRectChanged) {
4221 0 : // Even if we didn't accept a new scroll offset from content, the
4222 : // scrollable rect may have changed in a way that makes our local
4223 : // scroll offset out of bounds, so re-clamp it.
4224 : mFrameMetrics.ClampAndSetScrollOffset(mFrameMetrics.GetScrollOffset());
4225 : }
4226 : }
4227 :
4228 : if (smoothScrollRequested) {
4229 : // A smooth scroll has been requested for animation on the compositor
4230 : // thread. This flag will be reset by the main thread when it receives
4231 : // the scroll update acknowledgement.
4232 :
4233 0 : APZC_LOG("%p smooth scrolling from %s to %s in state %d\n", this,
4234 0 : Stringify(mFrameMetrics.GetScrollOffset()).c_str(),
4235 0 : Stringify(aLayerMetrics.GetSmoothScrollOffset()).c_str(),
4236 : mState);
4237 0 :
4238 : // See comment on the similar code in the |if (scrollOffsetUpdated)| block
4239 : // above.
4240 0 : mFrameMetrics.CopySmoothScrollInfoFrom(aLayerMetrics);
4241 : needContentRepaint = true;
4242 0 : mExpectedGeckoMetrics = aLayerMetrics;
4243 :
4244 1 : SmoothScrollTo(mFrameMetrics.GetSmoothScrollOffset());
4245 : }
4246 :
4247 0 : if (needContentRepaint) {
4248 0 : // This repaint request is not driven by a user action on the APZ side
4249 0 : RequestContentRepaint(false);
4250 : }
4251 : UpdateSharedCompositorFrameMetrics();
4252 0 : }
4253 0 :
4254 0 : const FrameMetrics& AsyncPanZoomController::GetFrameMetrics() const {
4255 : mRecursiveMutex.AssertCurrentThreadIn();
4256 : return mFrameMetrics;
4257 : }
4258 1 :
4259 : const ScrollMetadata& AsyncPanZoomController::GetScrollMetadata() const {
4260 16 : mRecursiveMutex.AssertCurrentThreadIn();
4261 16 : return mScrollMetadata;
4262 : }
4263 1 :
4264 : void
4265 : AsyncPanZoomController::AssertOnSamplerThread() const
4266 3 : {
4267 : if (APZCTreeManager* treeManagerLocal = GetApzcTreeManager()) {
4268 3 : treeManagerLocal->AssertOnSamplerThread();
4269 3 : }
4270 : }
4271 3 :
4272 : void
4273 0 : AsyncPanZoomController::AssertOnUpdaterThread() const
4274 22 : {
4275 1 : if (APZCTreeManager* treeManagerLocal = GetApzcTreeManager()) {
4276 : treeManagerLocal->AssertOnUpdaterThread();
4277 : }
4278 0 : }
4279 0 :
4280 0 : APZCTreeManager* AsyncPanZoomController::GetApzcTreeManager() const {
4281 0 : mRecursiveMutex.AssertNotCurrentThreadIn();
4282 0 : return mTreeManager;
4283 : }
4284 :
4285 : void AsyncPanZoomController::ZoomToRect(CSSRect aRect, const uint32_t aFlags) {
4286 0 : if (!aRect.IsFinite()) {
4287 0 : NS_WARNING("ZoomToRect got called with a non-finite rect; ignoring...");
4288 : return;
4289 : } else if (aRect.IsEmpty() && (aFlags & DISABLE_ZOOM_OUT)) {
4290 : // Double-tap-to-zooming uses an empty rect to mean "zoom out".
4291 : // If zooming out is disabled, an empty rect is nonsensical
4292 : // and will produce undesirable scrolling.
4293 : NS_WARNING("ZoomToRect got called with an empty rect and zoom out disabled; ignoring...");
4294 : return;
4295 0 : }
4296 0 :
4297 : // Only the root APZC is zoomable, and the root APZC is not allowed to have
4298 0 : // different x and y scales. If it did, the calculations in this function
4299 : // would have to be adjusted (as e.g. it would no longer be valid to take
4300 : // the minimum or maximum of the ratios of the widths and heights of the
4301 0 : // page rect and the composition bounds).
4302 : MOZ_ASSERT(mFrameMetrics.IsRootContent());
4303 0 : MOZ_ASSERT(mFrameMetrics.GetZoom().AreScalesSame());
4304 0 :
4305 0 : SetState(ANIMATING_ZOOM);
4306 0 :
4307 0 : {
4308 : RecursiveMutexAutoLock lock(mRecursiveMutex);
4309 :
4310 : ParentLayerRect compositionBounds = mFrameMetrics.GetCompositionBounds();
4311 : CSSRect cssPageRect = mFrameMetrics.GetScrollableRect();
4312 : CSSPoint scrollOffset = mFrameMetrics.GetScrollOffset();
4313 : CSSToParentLayerScale currentZoom = mFrameMetrics.GetZoom().ToScaleFactor();
4314 : CSSToParentLayerScale targetZoom;
4315 0 :
4316 0 : // The minimum zoom to prevent over-zoom-out.
4317 0 : // If the zoom factor is lower than this (i.e. we are zoomed more into the page),
4318 : // then the CSS content rect, in layers pixels, will be smaller than the
4319 0 : // composition bounds. If this happens, we can't fill the target composited
4320 : // area with this frame.
4321 0 : CSSToParentLayerScale localMinZoom(std::max(mZoomConstraints.mMinZoom.scale,
4322 0 : std::max(compositionBounds.Width() / cssPageRect.Width(),
4323 0 : compositionBounds.Height() / cssPageRect.Height())));
4324 : CSSToParentLayerScale localMaxZoom = mZoomConstraints.mMaxZoom;
4325 :
4326 : if (!aRect.IsEmpty()) {
4327 : // Intersect the zoom-to-rect to the CSS rect to make sure it fits.
4328 : aRect = aRect.Intersect(cssPageRect);
4329 : targetZoom = CSSToParentLayerScale(std::min(compositionBounds.Width() / aRect.Width(),
4330 : compositionBounds.Height() / aRect.Height()));
4331 : }
4332 0 :
4333 : // 1. If the rect is empty, the content-side logic for handling a double-tap
4334 : // requested that we zoom out.
4335 0 : // 2. currentZoom is equal to mZoomConstraints.mMaxZoom and user still double-tapping it
4336 0 : // 3. currentZoom is equal to localMinZoom and user still double-tapping it
4337 0 : // Treat these three cases as a request to zoom out as much as possible.
4338 : bool zoomOut;
4339 : if (aFlags & DISABLE_ZOOM_OUT) {
4340 0 : zoomOut = false;
4341 0 : } else {
4342 0 : zoomOut = aRect.IsEmpty() ||
4343 : (currentZoom == localMaxZoom && targetZoom >= localMaxZoom) ||
4344 0 : (currentZoom == localMinZoom && targetZoom <= localMinZoom);
4345 0 : }
4346 :
4347 0 : if (zoomOut) {
4348 0 : CSSSize compositedSize = mFrameMetrics.CalculateCompositedSizeInCssPixels();
4349 : float y = scrollOffset.y;
4350 : float newHeight =
4351 0 : cssPageRect.Width() * (compositedSize.height / compositedSize.width);
4352 0 : float dh = compositedSize.height - newHeight;
4353 0 :
4354 : aRect = CSSRect(0.0f,
4355 : y + dh/2,
4356 0 : cssPageRect.Width(),
4357 0 : newHeight);
4358 0 : aRect = aRect.Intersect(cssPageRect);
4359 0 : targetZoom = CSSToParentLayerScale(std::min(compositionBounds.Width() / aRect.Width(),
4360 0 : compositionBounds.Height() / aRect.Height()));
4361 : }
4362 0 :
4363 0 : targetZoom.scale = clamped(targetZoom.scale, localMinZoom.scale, localMaxZoom.scale);
4364 : FrameMetrics endZoomToMetrics = mFrameMetrics;
4365 0 : if (aFlags & PAN_INTO_VIEW_ONLY) {
4366 0 : targetZoom = currentZoom;
4367 : } else if(aFlags & ONLY_ZOOM_TO_DEFAULT_SCALE) {
4368 0 : CSSToParentLayerScale zoomAtDefaultScale =
4369 : mFrameMetrics.GetDevPixelsPerCSSPixel() * LayoutDeviceToParentLayerScale(1.0);
4370 : if (targetZoom.scale > zoomAtDefaultScale.scale) {
4371 : // Only change the zoom if we are less than the default zoom
4372 0 : if (currentZoom.scale < zoomAtDefaultScale.scale) {
4373 : targetZoom = zoomAtDefaultScale;
4374 : } else {
4375 0 : targetZoom = currentZoom;
4376 : }
4377 : }
4378 0 : }
4379 0 : endZoomToMetrics.SetZoom(CSSToParentLayerScale2D(targetZoom));
4380 0 :
4381 0 : // Adjust the zoomToRect to a sensible position to prevent overscrolling.
4382 : CSSSize sizeAfterZoom = endZoomToMetrics.CalculateCompositedSizeInCssPixels();
4383 :
4384 : // Vertically center the zoomed element in the screen.
4385 : if (!zoomOut && (sizeAfterZoom.height > aRect.Height())) {
4386 : aRect.MoveByY(-(sizeAfterZoom.height - aRect.Height()) * 0.5f);
4387 0 : if (aRect.Y() < 0.0f) {
4388 0 : aRect.MoveToY(0.0f);
4389 : }
4390 0 : }
4391 0 :
4392 : // If either of these conditions are met, the page will be
4393 : // overscrolled after zoomed
4394 0 : if (aRect.Y() + sizeAfterZoom.height > cssPageRect.Height()) {
4395 : aRect.MoveToY(std::max(0.f, cssPageRect.Height() - sizeAfterZoom.height));
4396 0 : }
4397 0 : if (aRect.X() + sizeAfterZoom.width > cssPageRect.Width()) {
4398 0 : aRect.MoveToX(std::max(0.f, cssPageRect.Width() - sizeAfterZoom.width));
4399 : }
4400 0 :
4401 : endZoomToMetrics.SetScrollOffset(aRect.TopLeft());
4402 :
4403 : StartAnimation(new ZoomAnimation(
4404 0 : mFrameMetrics.GetScrollOffset(),
4405 : mFrameMetrics.GetZoom(),
4406 0 : endZoomToMetrics.GetScrollOffset(),
4407 0 : endZoomToMetrics.GetZoom()));
4408 0 :
4409 0 : // Schedule a repaint now, so the new displayport will be painted before the
4410 : // animation finishes.
4411 0 : ParentLayerPoint velocity(0, 0);
4412 0 : endZoomToMetrics.SetDisplayPortMargins(
4413 0 : CalculatePendingDisplayPort(endZoomToMetrics, velocity));
4414 : endZoomToMetrics.SetUseDisplayPortMargins(true);
4415 0 : endZoomToMetrics.SetPaintRequestTime(TimeStamp::Now());
4416 0 : endZoomToMetrics.SetRepaintDrivenByUserAction(true);
4417 :
4418 : RefPtr<GeckoContentController> controller = GetGeckoContentController();
4419 : if (!controller) {
4420 0 : return;
4421 0 : }
4422 0 : if (controller->IsRepaintThread()) {
4423 : RequestContentRepaint(endZoomToMetrics, velocity);
4424 : } else {
4425 : // use a local var to resolve the function overload
4426 : auto func = static_cast<void (AsyncPanZoomController::*)(const FrameMetrics&, const ParentLayerPoint&)>
4427 0 : (&AsyncPanZoomController::RequestContentRepaint);
4428 : controller->DispatchToRepaintThread(
4429 : NewRunnableMethod<FrameMetrics, ParentLayerPoint>(
4430 : "layers::AsyncPanZoomController::ZoomToRect",
4431 : this,
4432 : func,
4433 0 : endZoomToMetrics,
4434 : velocity));
4435 0 : }
4436 : }
4437 : }
4438 :
4439 0 : InputBlockState*
4440 : AsyncPanZoomController::GetCurrentInputBlock() const
4441 0 : {
4442 : return GetInputQueue()->GetCurrentBlock();
4443 : }
4444 :
4445 0 : TouchBlockState*
4446 : AsyncPanZoomController::GetCurrentTouchBlock() const
4447 0 : {
4448 : return GetInputQueue()->GetCurrentTouchBlock();
4449 : }
4450 :
4451 0 : PanGestureBlockState*
4452 : AsyncPanZoomController::GetCurrentPanGestureBlock() const
4453 0 : {
4454 0 : return GetInputQueue()->GetCurrentPanGestureBlock();
4455 0 : }
4456 0 :
4457 : void
4458 0 : AsyncPanZoomController::ResetTouchInputState()
4459 : {
4460 : MultiTouchInput cancel(MultiTouchInput::MULTITOUCH_CANCEL, 0, TimeStamp::Now(), 0);
4461 0 : RefPtr<GestureEventListener> listener = GetGestureEventListener();
4462 0 : if (listener) {
4463 : listener->HandleInputEvent(cancel);
4464 0 : }
4465 : CancelAnimationAndGestureState();
4466 : // Clear overscroll along the entire handoff chain, in case an APZC
4467 0 : // later in the chain is overscrolled.
4468 : if (TouchBlockState* block = GetCurrentTouchBlock()) {
4469 0 : block->GetOverscrollHandoffChain()->ClearOverscroll();
4470 0 : }
4471 0 : }
4472 0 :
4473 : void
4474 : AsyncPanZoomController::CancelAnimationAndGestureState()
4475 0 : {
4476 : mX.CancelGesture();
4477 0 : mY.CancelGesture();
4478 : CancelAnimation(CancelAnimationFlags::ScrollSnap);
4479 : }
4480 1 :
4481 : bool
4482 : AsyncPanZoomController::HasReadyTouchBlock() const
4483 : {
4484 : return GetInputQueue()->HasReadyTouchBlock();
4485 : }
4486 2 :
4487 : void AsyncPanZoomController::SetState(PanZoomState aNewState)
4488 1 : {
4489 0 : PanZoomState oldState;
4490 :
4491 : // Intentional scoping for mutex
4492 1 : {
4493 0 : RecursiveMutexAutoLock lock(mRecursiveMutex);
4494 : APZC_LOG("%p changing from state %d to %d\n", this, mState, aNewState);
4495 9 : oldState = mState;
4496 : mState = aNewState;
4497 : }
4498 :
4499 18 : DispatchStateChangeNotification(oldState, aNewState);
4500 9 : }
4501 0 :
4502 : void AsyncPanZoomController::DispatchStateChangeNotification(PanZoomState aOldState,
4503 : PanZoomState aNewState)
4504 : {
4505 0 : { // scope the lock
4506 0 : RecursiveMutexAutoLock lock(mRecursiveMutex);
4507 0 : if (mNotificationBlockers > 0) {
4508 0 : return;
4509 : }
4510 : }
4511 :
4512 0 : if (RefPtr<GeckoContentController> controller = GetGeckoContentController()) {
4513 0 : if (!IsTransformingState(aOldState) && IsTransformingState(aNewState)) {
4514 : controller->NotifyAPZStateChange(
4515 : GetGuid(), APZStateChange::eTransformBegin);
4516 1 : #if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
4517 : // Let the compositor know about scroll state changes so it can manage
4518 : // windowed plugins.
4519 : if (gfxPrefs::HidePluginsForScroll() && mCompositorController) {
4520 : mCompositorController->ScheduleHideAllPluginWindows();
4521 : }
4522 : #endif
4523 : } else if (IsTransformingState(aOldState) && !IsTransformingState(aNewState)) {
4524 : #if defined(MOZ_WIDGET_ANDROID)
4525 : // The Android UI thread only shows overlay UI elements when the content is not being
4526 : // panned or zoomed and it is in a steady state. So the FrameMetrics only need to be
4527 : // updated when the transform ends.
4528 0 : if (APZCTreeManager* manager = GetApzcTreeManager()) {
4529 0 : AndroidDynamicToolbarAnimator* animator = manager->GetAndroidDynamicToolbarAnimator();
4530 : MOZ_ASSERT(animator);
4531 0 : animator->UpdateRootFrameMetrics(mFrameMetrics);
4532 0 : }
4533 : #endif
4534 :
4535 : controller->NotifyAPZStateChange(
4536 : GetGuid(), APZStateChange::eTransformEnd);
4537 : #if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
4538 : if (gfxPrefs::HidePluginsForScroll() && mCompositorController) {
4539 0 : mCompositorController->ScheduleShowAllPluginWindows();
4540 0 : }
4541 : #endif
4542 : }
4543 0 : }
4544 0 : }
4545 :
4546 : bool AsyncPanZoomController::IsTransformingState(PanZoomState aState) {
4547 0 : return !(aState == NOTHING || aState == TOUCHING);
4548 : }
4549 :
4550 0 : bool AsyncPanZoomController::IsInPanningState() const {
4551 0 : return (mState == PANNING || mState == PANNING_LOCKED_X || mState == PANNING_LOCKED_Y);
4552 0 : }
4553 :
4554 : void AsyncPanZoomController::UpdateZoomConstraints(const ZoomConstraints& aConstraints) {
4555 0 : APZC_LOG("%p updating zoom constraints to %d %d %f %f\n", this, aConstraints.mAllowZoom,
4556 0 : aConstraints.mAllowDoubleTapZoom, aConstraints.mMinZoom.scale, aConstraints.mMaxZoom.scale);
4557 0 : if (IsNaN(aConstraints.mMinZoom.scale) || IsNaN(aConstraints.mMaxZoom.scale)) {
4558 0 : NS_WARNING("APZC received zoom constraints with NaN values; dropping...");
4559 : return;
4560 : }
4561 0 :
4562 0 : CSSToParentLayerScale min = mFrameMetrics.GetDevPixelsPerCSSPixel()
4563 0 : * kViewportMinScale / ParentLayerToScreenScale(1);
4564 0 : CSSToParentLayerScale max = mFrameMetrics.GetDevPixelsPerCSSPixel()
4565 0 : * kViewportMaxScale / ParentLayerToScreenScale(1);
4566 0 :
4567 : // inf float values and other bad cases should be sanitized by the code below.
4568 : mZoomConstraints.mAllowZoom = aConstraints.mAllowZoom;
4569 : mZoomConstraints.mAllowDoubleTapZoom = aConstraints.mAllowDoubleTapZoom;
4570 : mZoomConstraints.mMinZoom = (min > aConstraints.mMinZoom ? min : aConstraints.mMinZoom);
4571 0 : mZoomConstraints.mMaxZoom = (max > aConstraints.mMaxZoom ? aConstraints.mMaxZoom : max);
4572 : if (mZoomConstraints.mMaxZoom < mZoomConstraints.mMinZoom) {
4573 0 : mZoomConstraints.mMaxZoom = mZoomConstraints.mMinZoom;
4574 : }
4575 : }
4576 :
4577 0 : ZoomConstraints
4578 0 : AsyncPanZoomController::GetZoomConstraints() const
4579 0 : {
4580 0 : return mZoomConstraints;
4581 0 : }
4582 0 :
4583 :
4584 : void AsyncPanZoomController::PostDelayedTask(already_AddRefed<Runnable> aTask, int aDelayMs) {
4585 : APZThreadUtils::AssertOnControllerThread();
4586 : RefPtr<Runnable> task = aTask;
4587 0 : RefPtr<GeckoContentController> controller = GetGeckoContentController();
4588 : if (controller) {
4589 5 : controller->PostDelayedTask(task.forget(), aDelayMs);
4590 : }
4591 5 : // If there is no controller, that means this APZC has been destroyed, and
4592 : // we probably don't need to run the task. It will get destroyed when the
4593 : // RefPtr goes out of scope.
4594 0 : }
4595 :
4596 1 : bool AsyncPanZoomController::Matches(const ScrollableLayerGuid& aGuid)
4597 : {
4598 : return aGuid == GetGuid();
4599 0 : }
4600 :
4601 0 : bool AsyncPanZoomController::HasTreeManager(const APZCTreeManager* aTreeManager) const
4602 0 : {
4603 : return GetApzcTreeManager() == aTreeManager;
4604 0 : }
4605 :
4606 0 : void AsyncPanZoomController::GetGuid(ScrollableLayerGuid* aGuidOut) const
4607 : {
4608 18 : if (aGuidOut) {
4609 : *aGuidOut = GetGuid();
4610 : }
4611 1 : }
4612 :
4613 1 : ScrollableLayerGuid AsyncPanZoomController::GetGuid() const
4614 : {
4615 1 : return ScrollableLayerGuid(mLayersId, mFrameMetrics);
4616 1 : }
4617 :
4618 1 : void AsyncPanZoomController::UpdateSharedCompositorFrameMetrics()
4619 0 : {
4620 0 : mRecursiveMutex.AssertCurrentThreadIn();
4621 0 :
4622 : FrameMetrics* frame = mSharedFrameMetricsBuffer ?
4623 1 : static_cast<FrameMetrics*>(mSharedFrameMetricsBuffer->memory()) : nullptr;
4624 :
4625 1 : if (frame && mSharedLock && gfxPrefs::ProgressivePaint()) {
4626 : mSharedLock->Lock();
4627 1 : *frame = mFrameMetrics;
4628 : mSharedLock->Unlock();
4629 : }
4630 : }
4631 :
4632 3 : void AsyncPanZoomController::ShareCompositorFrameMetrics()
4633 : {
4634 : AssertOnUpdaterThread();
4635 0 :
4636 0 : // Only create the shared memory buffer if it hasn't already been created,
4637 0 : // we are using progressive tile painting, and we have a
4638 0 : // controller to pass the shared memory back to the content process/thread.
4639 0 : if (!mSharedFrameMetricsBuffer && mMetricsSharingController && gfxPrefs::ProgressivePaint()) {
4640 :
4641 0 : // Create shared memory and initialize it with the current FrameMetrics value
4642 : mSharedFrameMetricsBuffer = new ipc::SharedMemoryBasic;
4643 : FrameMetrics* frame = nullptr;
4644 0 : mSharedFrameMetricsBuffer->Create(sizeof(FrameMetrics));
4645 0 : mSharedFrameMetricsBuffer->Map(sizeof(FrameMetrics));
4646 : frame = static_cast<FrameMetrics*>(mSharedFrameMetricsBuffer->memory());
4647 :
4648 : if (frame) {
4649 0 :
4650 0 : { // scope the monitor, only needed to copy the FrameMetrics.
4651 : RecursiveMutexAutoLock lock(mRecursiveMutex);
4652 : *frame = mFrameMetrics;
4653 0 : }
4654 :
4655 : // Get the process id of the content process
4656 0 : base::ProcessId otherPid = mMetricsSharingController->RemotePid();
4657 0 : ipc::SharedMemoryBasic::Handle mem = ipc::SharedMemoryBasic::NULLHandle();
4658 :
4659 : // Get the shared memory handle to share with the content process
4660 : mSharedFrameMetricsBuffer->ShareToProcess(otherPid, &mem);
4661 :
4662 0 : // Get the cross process mutex handle to share with the content process
4663 : mSharedLock = new CrossProcessMutex("AsyncPanZoomControlLock");
4664 : CrossProcessMutexHandle handle = mSharedLock->ShareToProcess(otherPid);
4665 :
4666 : // Send the shared memory handle and cross process handle to the content
4667 1 : // process by an asynchronous ipc call. Include the APZC unique ID
4668 : // so the content process know which APZC sent this shared FrameMetrics.
4669 : if (!mMetricsSharingController->StartSharingMetrics(mem, handle, mLayersId, mAPZCId)) {
4670 0 : APZC_LOG("%p failed to share FrameMetrics with content process.", this);
4671 : }
4672 0 : }
4673 0 : }
4674 0 : }
4675 :
4676 : void
4677 0 : AsyncPanZoomController::SetTestAsyncScrollOffset(const CSSPoint& aPoint)
4678 : {
4679 0 : mTestAsyncScrollOffset = aPoint;
4680 0 : ScheduleComposite();
4681 0 : }
4682 :
4683 0 : void
4684 : AsyncPanZoomController::SetTestAsyncZoom(const LayerToParentLayerScale& aZoom)
4685 0 : {
4686 : mTestAsyncZoom = aZoom;
4687 0 : ScheduleComposite();
4688 0 : }
4689 :
4690 : Maybe<CSSPoint> AsyncPanZoomController::FindSnapPointNear(
4691 0 : const CSSPoint& aDestination, nsIScrollableFrame::ScrollUnit aUnit) {
4692 0 : mRecursiveMutex.AssertCurrentThreadIn();
4693 0 : APZC_LOG("%p scroll snapping near %s\n", this, Stringify(aDestination).c_str());
4694 0 : CSSRect scrollRange = mFrameMetrics.CalculateScrollRange();
4695 0 : if (Maybe<nsPoint> snapPoint = ScrollSnapUtils::GetSnapPointForDestination(
4696 : mScrollMetadata.GetSnapInfo(),
4697 : aUnit,
4698 : CSSSize::ToAppUnits(mFrameMetrics.CalculateCompositedSizeInCssPixels()),
4699 : CSSRect::ToAppUnits(scrollRange),
4700 0 : CSSPoint::ToAppUnits(mFrameMetrics.GetScrollOffset()),
4701 : CSSPoint::ToAppUnits(aDestination))) {
4702 : CSSPoint cssSnapPoint = CSSPoint::FromAppUnits(snapPoint.ref());
4703 : // GetSnapPointForDestination() can produce a destination that's outside
4704 : // of the scroll frame's scroll range. Clamp it here (this matches the
4705 0 : // behaviour of the main-thread code path, which clamps it in
4706 0 : // nsGfxScrollFrame::ScrollTo()).
4707 0 : return Some(scrollRange.ClampPoint(cssSnapPoint));
4708 0 : }
4709 : return Nothing();
4710 0 : }
4711 :
4712 : void AsyncPanZoomController::ScrollSnapNear(const CSSPoint& aDestination) {
4713 0 : if (Maybe<CSSPoint> snapPoint =
4714 : FindSnapPointNear(aDestination, nsIScrollableFrame::DEVICE_PIXELS)) {
4715 0 : if (*snapPoint != mFrameMetrics.GetScrollOffset()) {
4716 0 : APZC_LOG("%p smooth scrolling to snap point %s\n", this, Stringify(*snapPoint).c_str());
4717 0 : SmoothScrollTo(*snapPoint);
4718 0 : }
4719 : }
4720 0 : }
4721 0 :
4722 : void AsyncPanZoomController::ScrollSnap() {
4723 0 : RecursiveMutexAutoLock lock(mRecursiveMutex);
4724 0 : ScrollSnapNear(mFrameMetrics.GetScrollOffset());
4725 0 : }
4726 :
4727 : void AsyncPanZoomController::ScrollSnapToDestination() {
4728 0 : RecursiveMutexAutoLock lock(mRecursiveMutex);
4729 0 :
4730 : float friction = gfxPrefs::APZFlingFriction();
4731 0 : ParentLayerPoint velocity(mX.GetVelocity(), mY.GetVelocity());
4732 0 : ParentLayerPoint predictedDelta;
4733 : // "-velocity / log(1.0 - friction)" is the integral of the deceleration
4734 0 : // curve modeled for flings in the "Axis" class.
4735 : if (velocity.x != 0.0f) {
4736 : predictedDelta.x = -velocity.x / log(1.0 - friction);
4737 : }
4738 0 : if (velocity.y != 0.0f) {
4739 0 : predictedDelta.y = -velocity.y / log(1.0 - friction);
4740 0 : }
4741 : CSSPoint predictedDestination = mFrameMetrics.GetScrollOffset() + predictedDelta / mFrameMetrics.GetZoom();
4742 :
4743 : // If the fling will overscroll, don't scroll snap, because then the user
4744 : // user would not see any overscroll animation.
4745 : bool flingWillOverscroll = IsOverscrolled() && ((velocity.x * mX.GetOverscroll() >= 0) ||
4746 : (velocity.y * mY.GetOverscroll() >= 0));
4747 : if (!flingWillOverscroll) {
4748 : APZC_LOG("%p fling snapping. friction: %f velocity: %f, %f "
4749 0 : "predictedDelta: %f, %f position: %f, %f "
4750 : "predictedDestination: %f, %f\n",
4751 0 : this, friction, velocity.x, velocity.y, (float)predictedDelta.x,
4752 : (float)predictedDelta.y, (float)mFrameMetrics.GetScrollOffset().x,
4753 0 : (float)mFrameMetrics.GetScrollOffset().y,
4754 : (float)predictedDestination.x, (float)predictedDestination.y);
4755 :
4756 : ScrollSnapNear(predictedDestination);
4757 : }
4758 : }
4759 :
4760 0 : bool AsyncPanZoomController::MaybeAdjustDeltaForScrollSnapping(
4761 : const ScrollWheelInput& aEvent,
4762 : ParentLayerPoint& aDelta,
4763 : CSSPoint& aStartPosition)
4764 0 : {
4765 0 : // Don't scroll snap for pixel scrolls. This matches the main thread
4766 0 : // behaviour in EventStateManager::DoScrollText().
4767 0 : if (aEvent.mDeltaType == ScrollWheelInput::SCROLLDELTA_PIXEL) {
4768 : return false;
4769 : }
4770 :
4771 : RecursiveMutexAutoLock lock(mRecursiveMutex);
4772 : CSSToParentLayerScale2D zoom = mFrameMetrics.GetZoom();
4773 : CSSPoint destination = mFrameMetrics.CalculateScrollRange().ClampPoint(
4774 : aStartPosition + (aDelta / zoom));
4775 : nsIScrollableFrame::ScrollUnit unit =
4776 : ScrollWheelInput::ScrollUnitForDeltaType(aEvent.mDeltaType);
4777 :
4778 : if (Maybe<CSSPoint> snapPoint = FindSnapPointNear(destination, unit)) {
4779 : aDelta = (*snapPoint - aStartPosition) * zoom;
4780 : aStartPosition = *snapPoint;
4781 : return true;
4782 : }
4783 : return false;
4784 : }
4785 :
4786 : bool AsyncPanZoomController::MaybeAdjustDestinationForScrollSnapping(
4787 : const KeyboardInput& aEvent,
4788 : CSSPoint& aDestination)
4789 : {
4790 : RecursiveMutexAutoLock lock(mRecursiveMutex);
4791 : nsIScrollableFrame::ScrollUnit unit =
4792 : KeyboardScrollAction::GetScrollUnit(aEvent.mAction.mType);
4793 :
4794 : if (Maybe<CSSPoint> snapPoint = FindSnapPointNear(aDestination, unit)) {
4795 : aDestination = *snapPoint;
4796 : return true;
4797 : }
4798 : return false;
4799 : }
4800 :
4801 : } // namespace layers
4802 : } // namespace mozilla
|