SmoothScrollAnimation.cpp (11828B)
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 "SmoothScrollAnimation.h" 8 #include "AsyncPanZoomController.h" 9 #include "ScrollAnimationBezierPhysics.h" 10 #include "ScrollAnimationMSDPhysics.h" 11 #include "ScrollPositionUpdate.h" 12 #include "Units.h" 13 #include "mozilla/RelativeTo.h" 14 #include "mozilla/StaticPrefs_general.h" 15 #include "nsLayoutUtils.h" 16 17 static mozilla::LazyLogModule sApzScrollAnimLog("apz.scrollanimation"); 18 #define SSA_LOG(...) MOZ_LOG(sApzScrollAnimLog, LogLevel::Debug, (__VA_ARGS__)) 19 20 namespace mozilla { 21 namespace layers { 22 23 /*static*/ 24 already_AddRefed<SmoothScrollAnimation> SmoothScrollAnimation::Create( 25 AsyncPanZoomController& aApzc, ScrollAnimationKind aKind, 26 ViewportType aViewportToScroll, ScrollOrigin aOrigin) { 27 MOZ_ASSERT(aKind == ScrollAnimationKind::Smooth || 28 aKind == ScrollAnimationKind::SmoothMsd); 29 RefPtr<SmoothScrollAnimation> result = 30 new SmoothScrollAnimation(aKind, aApzc, aViewportToScroll, aOrigin); 31 return result.forget(); 32 } 33 34 /*static*/ 35 already_AddRefed<SmoothScrollAnimation> 36 SmoothScrollAnimation::CreateForKeyboard(AsyncPanZoomController& aApzc, 37 ScrollOrigin aOrigin) { 38 RefPtr<SmoothScrollAnimation> result = new SmoothScrollAnimation( 39 ScrollAnimationKind::Keyboard, aApzc, ViewportType::Visual, aOrigin); 40 return result.forget(); 41 } 42 43 static ScrollOrigin OriginForDeltaType( 44 ScrollWheelInput::ScrollDeltaType aDeltaType) { 45 switch (aDeltaType) { 46 case ScrollWheelInput::SCROLLDELTA_PAGE: 47 return ScrollOrigin::Pages; 48 case ScrollWheelInput::SCROLLDELTA_PIXEL: 49 return ScrollOrigin::Pixels; 50 case ScrollWheelInput::SCROLLDELTA_LINE: 51 return ScrollOrigin::MouseWheel; 52 } 53 // Shouldn't happen, pick a default. 54 return ScrollOrigin::MouseWheel; 55 } 56 57 /*static*/ 58 already_AddRefed<SmoothScrollAnimation> SmoothScrollAnimation::CreateForWheel( 59 AsyncPanZoomController& aApzc, 60 ScrollWheelInput::ScrollDeltaType aDeltaType) { 61 RefPtr<SmoothScrollAnimation> result = new SmoothScrollAnimation( 62 ScrollAnimationKind::Wheel, aApzc, ViewportType::Visual, 63 OriginForDeltaType(aDeltaType)); 64 MOZ_ASSERT(nsLayoutUtils::IsSmoothScrollingEnabled(), 65 "We shouldn't be creating a WheelScrollAnimation if smooth " 66 "scrolling is disabled"); 67 result->mDirectionForcedToOverscroll = 68 aApzc.mScrollMetadata.GetDisregardedDirection(); 69 return result.forget(); 70 } 71 72 SmoothScrollAnimation::SmoothScrollAnimation(ScrollAnimationKind aKind, 73 AsyncPanZoomController& aApzc, 74 ViewportType aViewportToScroll, 75 ScrollOrigin aOrigin) 76 : mKind(aKind), 77 mViewportToScroll(aViewportToScroll), 78 mApzc(aApzc), 79 mFinalDestination( 80 CSSPoint::ToAppUnits(GetViewportOffset(aApzc.Metrics()))), 81 mOrigin(aOrigin), 82 mTriggeredByScript(ScrollTriggeredByScript::No) { 83 // ScrollAnimationBezierPhysics (despite its name) handles the case of 84 // general.smoothScroll being disabled whereas ScrollAnimationMSDPhysics does 85 // not (ie it scrolls smoothly). 86 if (mKind == ScrollAnimationKind::SmoothMsd || 87 (nsLayoutUtils::IsSmoothScrollingEnabled() && 88 StaticPrefs::general_smoothScroll_msdPhysics_enabled())) { 89 nscoord smallestVisibleIncrement = 1; 90 if (mKind == ScrollAnimationKind::SmoothMsd && 91 mApzc.GetFrameMetrics().GetZoom() != CSSToParentLayerScale(0)) { 92 // SmoothMsdScrollAnimation used 1 ParentLayer pixel as the "smallest 93 // visible increment". Note that we are passing quantities (such as the 94 // destination) to ScrollAnimationMSDPhysics in app units, so the 95 // increment needs to be converted to app units as well. 96 smallestVisibleIncrement = CSSPixel::ToAppUnits( 97 ParentLayerCoord(1) / mApzc.GetFrameMetrics().GetZoom()); 98 } 99 mAnimationPhysics = MakeUnique<ScrollAnimationMSDPhysics>( 100 mKind, mFinalDestination, smallestVisibleIncrement); 101 } else { 102 mAnimationPhysics = MakeUnique<ScrollAnimationBezierPhysics>( 103 mFinalDestination, 104 apz::ComputeBezierAnimationSettingsForOrigin(aOrigin)); 105 } 106 } 107 108 bool SmoothScrollAnimation::CanExtend(ViewportType aViewportToScroll, 109 ScrollOrigin aOrigin) const { 110 MOZ_ASSERT(mKind == ScrollAnimationKind::Smooth || 111 mKind == ScrollAnimationKind::SmoothMsd); 112 // The viewport type must always match. 113 if (aViewportToScroll != mViewportToScroll) { 114 return false; 115 } 116 if (mKind == ScrollAnimationKind::SmoothMsd) { 117 // We do not track the origin of SmoothMsd animations, so 118 // always allow extending. 119 return true; 120 } 121 // Otherwise, the origin must match. 122 return aOrigin == mOrigin; 123 } 124 125 SmoothScrollAnimation* SmoothScrollAnimation::AsSmoothScrollAnimation() { 126 return this; 127 } 128 129 void SmoothScrollAnimation::UpdateDestinationAndSnapTargets( 130 TimeStamp aTime, const nsPoint& aDestination, 131 const nsSize& aCurrentVelocity, ScrollSnapTargetIds&& aSnapTargetIds, 132 ScrollTriggeredByScript aTriggeredByScript) { 133 UpdateDestination(aTime, aDestination, aCurrentVelocity); 134 mSnapTargetIds = std::move(aSnapTargetIds); 135 mTriggeredByScript = aTriggeredByScript; 136 } 137 138 ScrollOrigin SmoothScrollAnimation::GetScrollOrigin() const { return mOrigin; } 139 140 ScrollOrigin SmoothScrollAnimation::GetScrollOriginForAction( 141 KeyboardScrollAction::KeyboardScrollActionType aAction) { 142 switch (aAction) { 143 case KeyboardScrollAction::eScrollCharacter: 144 case KeyboardScrollAction::eScrollLine: { 145 return ScrollOrigin::Lines; 146 } 147 case KeyboardScrollAction::eScrollPage: 148 return ScrollOrigin::Pages; 149 case KeyboardScrollAction::eScrollComplete: 150 return ScrollOrigin::Other; 151 default: 152 MOZ_ASSERT(false, "Unknown keyboard scroll action type"); 153 return ScrollOrigin::Other; 154 } 155 } 156 157 void SmoothScrollAnimation::UpdateDelta(TimeStamp aTime, const nsPoint& aDelta, 158 const nsSize& aCurrentVelocity) { 159 mFinalDestination += aDelta; 160 161 Update(aTime, aCurrentVelocity); 162 } 163 164 void SmoothScrollAnimation::UpdateDestination(TimeStamp aTime, 165 const nsPoint& aDestination, 166 const nsSize& aCurrentVelocity) { 167 mFinalDestination = aDestination; 168 169 Update(aTime, aCurrentVelocity); 170 } 171 172 void SmoothScrollAnimation::Update(TimeStamp aTime, 173 const nsSize& aCurrentVelocity) { 174 // Clamp the final destination to the scrollable area. 175 CSSPoint clamped = CSSPoint::FromAppUnits(mFinalDestination); 176 clamped.x = mApzc.mX.ClampOriginToScrollableRect(clamped.x); 177 clamped.y = mApzc.mY.ClampOriginToScrollableRect(clamped.y); 178 mFinalDestination = CSSPoint::ToAppUnits(clamped); 179 180 mAnimationPhysics->Update(aTime, mFinalDestination, aCurrentVelocity); 181 } 182 183 CSSPoint SmoothScrollAnimation::GetViewportOffset( 184 const FrameMetrics& aMetrics) const { 185 return mViewportToScroll == ViewportType::Visual 186 ? aMetrics.GetVisualScrollOffset() 187 : aMetrics.GetLayoutScrollOffset(); 188 } 189 190 bool SmoothScrollAnimation::DoSample(FrameMetrics& aFrameMetrics, 191 const TimeDuration& aDelta) { 192 TimeStamp now = mApzc.GetFrameTime().Time(); 193 CSSToParentLayerScale zoom(aFrameMetrics.GetZoom()); 194 if (zoom == CSSToParentLayerScale(0)) { 195 return false; 196 } 197 198 // If the animation is finished, make sure the final position is correct by 199 // using one last displacement. Otherwise, compute the delta via the timing 200 // function as normal. 201 bool finished = mAnimationPhysics->IsFinished(now); 202 nsPoint sampledDest = mAnimationPhysics->PositionAt(now); 203 const CSSPoint cssDisplacement = 204 CSSPoint::FromAppUnits(sampledDest) - GetViewportOffset(aFrameMetrics); 205 206 if (finished) { 207 mApzc.mX.SetVelocity(0); 208 mApzc.mY.SetVelocity(0); 209 } else if (!IsZero(cssDisplacement)) { 210 // Convert velocity from AppUnits/Seconds to ParentLayerCoords/Milliseconds 211 nsSize velocity = mAnimationPhysics->VelocityAt(now); 212 ParentLayerPoint velocityPL = 213 CSSPoint::FromAppUnits(nsPoint(velocity.width, velocity.height)) * zoom; 214 mApzc.mX.SetVelocity(velocityPL.x / 1000.0); 215 mApzc.mY.SetVelocity(velocityPL.y / 1000.0); 216 } 217 if (mViewportToScroll == ViewportType::Visual) { 218 // Note: we ignore overscroll for generic animations. 219 const ParentLayerPoint displacement = cssDisplacement * zoom; 220 ParentLayerPoint adjustedOffset, overscroll; 221 mApzc.mX.AdjustDisplacement( 222 displacement.x, adjustedOffset.x, overscroll.x, 223 mDirectionForcedToOverscroll == Some(ScrollDirection::eHorizontal)); 224 mApzc.mY.AdjustDisplacement( 225 displacement.y, adjustedOffset.y, overscroll.y, 226 mDirectionForcedToOverscroll == Some(ScrollDirection::eVertical)); 227 // If we expected to scroll, but there's no more scroll range on either 228 // axis, then end the animation early. Note that the initial displacement 229 // could be 0 if the compositor ran very quickly (<1ms) after the animation 230 // was created. When that happens we want to make sure the animation 231 // continues. 232 SSA_LOG( 233 "Sampling SmoothScrollAnimation (visual mode): time %f finished %d " 234 "sampledDest %s adjustedOffset %s overscroll %s", 235 (now - TimeStamp::ProcessCreation()).ToMilliseconds(), finished, 236 ToString(CSSPoint::FromAppUnits(sampledDest)).c_str(), 237 ToString(adjustedOffset).c_str(), ToString(overscroll).c_str()); 238 if (!IsZero(cssDisplacement) && IsZero(adjustedOffset / zoom)) { 239 // Nothing more to do - end the animation. 240 finished = true; 241 } else { 242 mApzc.ScrollBy(adjustedOffset / zoom); 243 } 244 } else { 245 // Use a slightly simplified implementation for ViewportType::Layout. 246 // For example, we don't need to handle mDirectionForcedToOverscroll in this 247 // case. 248 MOZ_ASSERT(mDirectionForcedToOverscroll.isNothing()); 249 MOZ_ASSERT(!mApzc.IsPhysicallyOverscrolled()); 250 CSSPoint offsetBefore = GetViewportOffset(aFrameMetrics); 251 mApzc.ScrollByAndClamp(mViewportToScroll, cssDisplacement); 252 CSSPoint offsetAfter = GetViewportOffset(aFrameMetrics); 253 CSSPoint amountScrolled = offsetAfter - offsetBefore; 254 if (!IsZero(cssDisplacement) && IsZero(amountScrolled)) { 255 finished = true; 256 } 257 SSA_LOG( 258 "Sampling SmoothScrollAnimation (layout mode): time %f finished %d " 259 "sampledDest %s offsetAfter %s\n", 260 (now - TimeStamp::ProcessCreation()).ToMilliseconds(), finished, 261 ToString(CSSPoint::FromAppUnits(sampledDest)).c_str(), 262 ToString(offsetAfter).c_str()); 263 } 264 if (finished) { 265 // Set the scroll offset to the exact destination. If we allow the scroll 266 // offset to end up being a bit off from the destination, we can get 267 // artefacts like "scroll to the next snap point in this direction" 268 // scrolling to the snap point we're already supposed to be at. 269 mApzc.ScrollToAndClamp(mViewportToScroll, 270 CSSPoint::FromAppUnits(mFinalDestination)); 271 } 272 return !finished; 273 } 274 275 bool SmoothScrollAnimation::HandleScrollOffsetUpdate( 276 const Maybe<CSSPoint>& aRelativeDelta) { 277 if (aRelativeDelta) { 278 mAnimationPhysics->ApplyContentShift(*aRelativeDelta); 279 mFinalDestination += CSSPoint::ToAppUnits(*aRelativeDelta); 280 return true; 281 } 282 return false; 283 } 284 285 } // namespace layers 286 } // namespace mozilla