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