tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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