/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "SmoothScrollAnimation.h" #include "AsyncPanZoomController.h" #include "ScrollAnimationBezierPhysics.h" #include "ScrollAnimationMSDPhysics.h" #include "ScrollPositionUpdate.h" #include "Units.h" #include "mozilla/RelativeTo.h" #include "mozilla/StaticPrefs_general.h" #include "nsLayoutUtils.h" static mozilla::LazyLogModule sApzScrollAnimLog("apz.scrollanimation"); #define SSA_LOG(...) MOZ_LOG(sApzScrollAnimLog, LogLevel::Debug, (__VA_ARGS__)) namespace mozilla { namespace layers { /*static*/ already_AddRefed SmoothScrollAnimation::Create( AsyncPanZoomController& aApzc, ScrollAnimationKind aKind, ViewportType aViewportToScroll, ScrollOrigin aOrigin) { MOZ_ASSERT(aKind == ScrollAnimationKind::Smooth || aKind == ScrollAnimationKind::SmoothMsd); RefPtr result = new SmoothScrollAnimation(aKind, aApzc, aViewportToScroll, aOrigin); return result.forget(); } /*static*/ already_AddRefed SmoothScrollAnimation::CreateForKeyboard(AsyncPanZoomController& aApzc, ScrollOrigin aOrigin) { RefPtr result = new SmoothScrollAnimation( ScrollAnimationKind::Keyboard, aApzc, ViewportType::Visual, aOrigin); return result.forget(); } static ScrollOrigin OriginForDeltaType( ScrollWheelInput::ScrollDeltaType aDeltaType) { switch (aDeltaType) { case ScrollWheelInput::SCROLLDELTA_PAGE: return ScrollOrigin::Pages; case ScrollWheelInput::SCROLLDELTA_PIXEL: return ScrollOrigin::Pixels; case ScrollWheelInput::SCROLLDELTA_LINE: return ScrollOrigin::MouseWheel; } // Shouldn't happen, pick a default. return ScrollOrigin::MouseWheel; } /*static*/ already_AddRefed SmoothScrollAnimation::CreateForWheel( AsyncPanZoomController& aApzc, ScrollWheelInput::ScrollDeltaType aDeltaType) { RefPtr result = new SmoothScrollAnimation( ScrollAnimationKind::Wheel, aApzc, ViewportType::Visual, OriginForDeltaType(aDeltaType)); MOZ_ASSERT(nsLayoutUtils::IsSmoothScrollingEnabled(), "We shouldn't be creating a WheelScrollAnimation if smooth " "scrolling is disabled"); result->mDirectionForcedToOverscroll = aApzc.mScrollMetadata.GetDisregardedDirection(); return result.forget(); } SmoothScrollAnimation::SmoothScrollAnimation(ScrollAnimationKind aKind, AsyncPanZoomController& aApzc, ViewportType aViewportToScroll, ScrollOrigin aOrigin) : mKind(aKind), mViewportToScroll(aViewportToScroll), mApzc(aApzc), mFinalDestination( CSSPoint::ToAppUnits(GetViewportOffset(aApzc.Metrics()))), mOrigin(aOrigin), mTriggeredByScript(ScrollTriggeredByScript::No) { // ScrollAnimationBezierPhysics (despite its name) handles the case of // general.smoothScroll being disabled whereas ScrollAnimationMSDPhysics does // not (ie it scrolls smoothly). if (mKind == ScrollAnimationKind::SmoothMsd || (nsLayoutUtils::IsSmoothScrollingEnabled() && StaticPrefs::general_smoothScroll_msdPhysics_enabled())) { nscoord smallestVisibleIncrement = 1; if (mKind == ScrollAnimationKind::SmoothMsd && mApzc.GetFrameMetrics().GetZoom() != CSSToParentLayerScale(0)) { // SmoothMsdScrollAnimation used 1 ParentLayer pixel as the "smallest // visible increment". Note that we are passing quantities (such as the // destination) to ScrollAnimationMSDPhysics in app units, so the // increment needs to be converted to app units as well. smallestVisibleIncrement = CSSPixel::ToAppUnits( ParentLayerCoord(1) / mApzc.GetFrameMetrics().GetZoom()); } mAnimationPhysics = MakeUnique( mKind, mFinalDestination, smallestVisibleIncrement); } else { mAnimationPhysics = MakeUnique( mFinalDestination, apz::ComputeBezierAnimationSettingsForOrigin(aOrigin)); } } bool SmoothScrollAnimation::CanExtend(ViewportType aViewportToScroll, ScrollOrigin aOrigin) const { MOZ_ASSERT(mKind == ScrollAnimationKind::Smooth || mKind == ScrollAnimationKind::SmoothMsd); // The viewport type must always match. if (aViewportToScroll != mViewportToScroll) { return false; } if (mKind == ScrollAnimationKind::SmoothMsd) { // We do not track the origin of SmoothMsd animations, so // always allow extending. return true; } // Otherwise, the origin must match. return aOrigin == mOrigin; } SmoothScrollAnimation* SmoothScrollAnimation::AsSmoothScrollAnimation() { return this; } void SmoothScrollAnimation::UpdateDestinationAndSnapTargets( TimeStamp aTime, const nsPoint& aDestination, const nsSize& aCurrentVelocity, ScrollSnapTargetIds&& aSnapTargetIds, ScrollTriggeredByScript aTriggeredByScript) { UpdateDestination(aTime, aDestination, aCurrentVelocity); mSnapTargetIds = std::move(aSnapTargetIds); mTriggeredByScript = aTriggeredByScript; } ScrollOrigin SmoothScrollAnimation::GetScrollOrigin() const { return mOrigin; } ScrollOrigin SmoothScrollAnimation::GetScrollOriginForAction( KeyboardScrollAction::KeyboardScrollActionType aAction) { switch (aAction) { case KeyboardScrollAction::eScrollCharacter: case KeyboardScrollAction::eScrollLine: { return ScrollOrigin::Lines; } case KeyboardScrollAction::eScrollPage: return ScrollOrigin::Pages; case KeyboardScrollAction::eScrollComplete: return ScrollOrigin::Other; default: MOZ_ASSERT(false, "Unknown keyboard scroll action type"); return ScrollOrigin::Other; } } void SmoothScrollAnimation::UpdateDelta(TimeStamp aTime, const nsPoint& aDelta, const nsSize& aCurrentVelocity) { mFinalDestination += aDelta; Update(aTime, aCurrentVelocity); } void SmoothScrollAnimation::UpdateDestination(TimeStamp aTime, const nsPoint& aDestination, const nsSize& aCurrentVelocity) { mFinalDestination = aDestination; Update(aTime, aCurrentVelocity); } void SmoothScrollAnimation::Update(TimeStamp aTime, const nsSize& aCurrentVelocity) { // Clamp the final destination to the scrollable area. CSSPoint clamped = CSSPoint::FromAppUnits(mFinalDestination); clamped.x = mApzc.mX.ClampOriginToScrollableRect(clamped.x); clamped.y = mApzc.mY.ClampOriginToScrollableRect(clamped.y); mFinalDestination = CSSPoint::ToAppUnits(clamped); mAnimationPhysics->Update(aTime, mFinalDestination, aCurrentVelocity); } CSSPoint SmoothScrollAnimation::GetViewportOffset( const FrameMetrics& aMetrics) const { return mViewportToScroll == ViewportType::Visual ? aMetrics.GetVisualScrollOffset() : aMetrics.GetLayoutScrollOffset(); } bool SmoothScrollAnimation::DoSample(FrameMetrics& aFrameMetrics, const TimeDuration& aDelta) { TimeStamp now = mApzc.GetFrameTime().Time(); CSSToParentLayerScale zoom(aFrameMetrics.GetZoom()); if (zoom == CSSToParentLayerScale(0)) { return false; } // If the animation is finished, make sure the final position is correct by // using one last displacement. Otherwise, compute the delta via the timing // function as normal. bool finished = mAnimationPhysics->IsFinished(now); nsPoint sampledDest = mAnimationPhysics->PositionAt(now); const CSSPoint cssDisplacement = CSSPoint::FromAppUnits(sampledDest) - GetViewportOffset(aFrameMetrics); if (finished) { mApzc.mX.SetVelocity(0); mApzc.mY.SetVelocity(0); } else if (!IsZero(cssDisplacement)) { // Convert velocity from AppUnits/Seconds to ParentLayerCoords/Milliseconds nsSize velocity = mAnimationPhysics->VelocityAt(now); ParentLayerPoint velocityPL = CSSPoint::FromAppUnits(nsPoint(velocity.width, velocity.height)) * zoom; mApzc.mX.SetVelocity(velocityPL.x / 1000.0); mApzc.mY.SetVelocity(velocityPL.y / 1000.0); } if (mViewportToScroll == ViewportType::Visual) { // Note: we ignore overscroll for generic animations. const ParentLayerPoint displacement = cssDisplacement * zoom; ParentLayerPoint adjustedOffset, overscroll; mApzc.mX.AdjustDisplacement( displacement.x, adjustedOffset.x, overscroll.x, mDirectionForcedToOverscroll == Some(ScrollDirection::eHorizontal)); mApzc.mY.AdjustDisplacement( displacement.y, adjustedOffset.y, overscroll.y, mDirectionForcedToOverscroll == Some(ScrollDirection::eVertical)); // If we expected to scroll, but there's no more scroll range on either // axis, then end the animation early. Note that the initial displacement // could be 0 if the compositor ran very quickly (<1ms) after the animation // was created. When that happens we want to make sure the animation // continues. SSA_LOG( "Sampling SmoothScrollAnimation (visual mode): time %f finished %d " "sampledDest %s adjustedOffset %s overscroll %s", (now - TimeStamp::ProcessCreation()).ToMilliseconds(), finished, ToString(CSSPoint::FromAppUnits(sampledDest)).c_str(), ToString(adjustedOffset).c_str(), ToString(overscroll).c_str()); if (!IsZero(cssDisplacement) && IsZero(adjustedOffset / zoom)) { // Nothing more to do - end the animation. finished = true; } else { mApzc.ScrollBy(adjustedOffset / zoom); } } else { // Use a slightly simplified implementation for ViewportType::Layout. // For example, we don't need to handle mDirectionForcedToOverscroll in this // case. MOZ_ASSERT(mDirectionForcedToOverscroll.isNothing()); MOZ_ASSERT(!mApzc.IsPhysicallyOverscrolled()); CSSPoint offsetBefore = GetViewportOffset(aFrameMetrics); mApzc.ScrollByAndClamp(mViewportToScroll, cssDisplacement); CSSPoint offsetAfter = GetViewportOffset(aFrameMetrics); CSSPoint amountScrolled = offsetAfter - offsetBefore; if (!IsZero(cssDisplacement) && IsZero(amountScrolled)) { finished = true; } SSA_LOG( "Sampling SmoothScrollAnimation (layout mode): time %f finished %d " "sampledDest %s offsetAfter %s\n", (now - TimeStamp::ProcessCreation()).ToMilliseconds(), finished, ToString(CSSPoint::FromAppUnits(sampledDest)).c_str(), ToString(offsetAfter).c_str()); } if (finished) { // Set the scroll offset to the exact destination. If we allow the scroll // offset to end up being a bit off from the destination, we can get // artefacts like "scroll to the next snap point in this direction" // scrolling to the snap point we're already supposed to be at. mApzc.ScrollToAndClamp(mViewportToScroll, CSSPoint::FromAppUnits(mFinalDestination)); } return !finished; } bool SmoothScrollAnimation::HandleScrollOffsetUpdate( const Maybe& aRelativeDelta) { if (aRelativeDelta) { mAnimationPhysics->ApplyContentShift(*aRelativeDelta); mFinalDestination += CSSPoint::ToAppUnits(*aRelativeDelta); return true; } return false; } } // namespace layers } // namespace mozilla