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