tor-browser

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

ScrollThumbUtils.cpp (15533B)


      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 "ScrollThumbUtils.h"
      8 #include "AsyncPanZoomController.h"
      9 #include "FrameMetrics.h"
     10 #include "UnitTransforms.h"
     11 #include "Units.h"
     12 #include "gfxPlatform.h"
     13 #include "mozilla/gfx/Matrix.h"
     14 #include "mozilla/StaticPrefs_toolkit.h"
     15 
     16 namespace mozilla {
     17 namespace layers {
     18 namespace apz {
     19 
     20 struct AsyncScrollThumbTransformer {
     21  // Inputs
     22  const LayerToParentLayerMatrix4x4& mCurrentTransform;
     23  const gfx::Matrix4x4& mScrollableContentTransform;
     24  AsyncPanZoomController* mApzc;
     25  const FrameMetrics& mMetrics;
     26  const ScrollbarData& mScrollbarData;
     27  bool mScrollbarIsDescendant;
     28 
     29  // Intermediate results
     30  AsyncTransformComponentMatrix mAsyncTransform;
     31  AsyncTransformComponentMatrix mScrollbarTransform;
     32 
     33  LayerToParentLayerMatrix4x4 ComputeTransform();
     34 
     35 private:
     36  // Helper functions for ComputeTransform().
     37 
     38  // If the thumb's orientation is along |aAxis|, add transformations
     39  // of the thumb into |mScrollbarTransform|.
     40  void ApplyTransformForAxis(const Axis& aAxis);
     41 
     42  enum class ScrollThumbExtent { Start, End };
     43 
     44  // Scale the thumb by |aScale| along |aAxis|, while keeping constant the
     45  // position of the top denoted by |aExtent|.
     46  void ScaleThumbBy(const Axis& aAxis, float aScale, ScrollThumbExtent aExtent);
     47 
     48  // Translate the thumb along |aAxis| by |aTranslation| in "scrollbar space"
     49  // (CSS pixels along the scrollbar track, similar to e.g.
     50  // |mScrollbarData.mThumbStart|).
     51  void TranslateThumb(const Axis& aAxis, OuterCSSCoord aTranslation);
     52 };
     53 
     54 void AsyncScrollThumbTransformer::TranslateThumb(const Axis& aAxis,
     55                                                 OuterCSSCoord aTranslation) {
     56  aAxis.PostTranslate(
     57      mScrollbarTransform,
     58      ViewAs<CSSPixel>(aTranslation,
     59                       PixelCastJustification::CSSPixelsOfSurroundingContent) *
     60          mMetrics.GetDevPixelsPerCSSPixel() *
     61          LayoutDeviceToParentLayerScale(1.0));
     62 }
     63 
     64 void AsyncScrollThumbTransformer::ScaleThumbBy(const Axis& aAxis, float aScale,
     65                                               ScrollThumbExtent aExtent) {
     66  // To keep the position of the top of the thumb constant, the thumb needs to
     67  // translated to compensate for the scale applied. The origin with respect to
     68  // which the scale is applied is the origin of the layer tree, rather than
     69  // the origin of the scroll thumb. This means that the space between the
     70  // origin and the top of thumb (including the part of the scrollbar track
     71  // above the thumb, the part of the scrollbar above the track (i.e. a
     72  // scrollbar button if present), plus whatever content is above the scroll
     73  // frame) is scaled too, effectively translating the thumb. We undo that
     74  // translation here. (One can think of the adjustment being done to the
     75  // translation here as a change of basis. We have a method to help with that,
     76  // Matrix4x4::ChangeBasis(), but it wouldn't necessarily make the code cleaner
     77  // in this case).
     78  const OuterCSSCoord scrollTrackOrigin =
     79      aAxis.GetPointOffset(
     80          mMetrics.CalculateCompositionBoundsInOuterCssPixels().TopLeft()) +
     81      mScrollbarData.mScrollTrackStart;
     82  OuterCSSCoord thumbExtent = scrollTrackOrigin + mScrollbarData.mThumbStart;
     83  if (aExtent == ScrollThumbExtent::End) {
     84    thumbExtent += mScrollbarData.mThumbLength;
     85  }
     86  const OuterCSSCoord thumbExtentScaled = thumbExtent * aScale;
     87  const OuterCSSCoord thumbExtentDelta = thumbExtentScaled - thumbExtent;
     88 
     89  aAxis.PostScale(mScrollbarTransform, aScale);
     90  TranslateThumb(aAxis, -thumbExtentDelta);
     91 }
     92 
     93 void AsyncScrollThumbTransformer::ApplyTransformForAxis(const Axis& aAxis) {
     94  ParentLayerCoord asyncScroll = aAxis.GetTransformTranslation(mAsyncTransform);
     95  const float asyncZoom = aAxis.GetTransformScale(mAsyncTransform);
     96  const ParentLayerCoord overscroll =
     97      aAxis.GetPointOffset(mApzc->GetOverscrollAmount());
     98 
     99  bool haveAsyncZoom = !FuzzyEqualsAdditive(asyncZoom, 1.f);
    100  if (!haveAsyncZoom && mApzc->IsZero(asyncScroll) &&
    101      mApzc->IsZero(overscroll)) {
    102    return;
    103  }
    104 
    105  OuterCSSCoord translation;
    106  float scale = 1.0;
    107 
    108  bool recalcMode = StaticPrefs::apz_scrollthumb_recalc();
    109  if (recalcMode) {
    110    // In this branch (taken when apz.scrollthumb.recalc=true), |translation|
    111    // and |scale| are computed using the approach implemented in bug 1554795
    112    // of fully recalculating the desired position and size using the logic
    113    // that attempts to closely match the main-thread calculation.
    114 
    115    const CSSRect visualViewportRect = mApzc->GetCurrentAsyncVisualViewport(
    116        AsyncPanZoomController::eForCompositing);
    117    const CSSCoord visualViewportLength =
    118        aAxis.GetRectLength(visualViewportRect);
    119 
    120    const CSSCoord maxMinPosDifference =
    121        CSSCoord(
    122            aAxis.GetRectLength(mMetrics.GetScrollableRect()).Truncated()) -
    123        visualViewportLength;
    124 
    125    OuterCSSCoord effectiveThumbLength = mScrollbarData.mThumbLength;
    126 
    127    if (haveAsyncZoom) {
    128      // The calculations here closely follow the main thread calculations at
    129      // https://searchfox.org/mozilla-central/rev/0bf957f909ae1f3d19b43fd4edfc277342554836/layout/generic/nsGfxScrollFrame.cpp#6902-6927
    130      // and
    131      // https://searchfox.org/mozilla-central/rev/0bf957f909ae1f3d19b43fd4edfc277342554836/layout/xul/nsSliderFrame.cpp#587-614
    132      // Any modifications there should be reflected here as well.
    133      const CSSCoord pageIncrementMin =
    134          static_cast<int>(visualViewportLength * 0.8);
    135      CSSCoord pageIncrement;
    136 
    137      CSSToLayoutDeviceScale deviceScale = mMetrics.GetDevPixelsPerCSSPixel();
    138      if (*mScrollbarData.mDirection == ScrollDirection::eVertical) {
    139        const CSSCoord lineScrollAmount =
    140            (mApzc->GetScrollMetadata().GetLineScrollAmount() / deviceScale)
    141                .height;
    142        const double kScrollMultiplier =
    143            StaticPrefs::toolkit_scrollbox_verticalScrollDistance();
    144        CSSCoord increment = lineScrollAmount * kScrollMultiplier;
    145 
    146        pageIncrement =
    147            std::max(visualViewportLength - increment, pageIncrementMin);
    148      } else {
    149        pageIncrement = pageIncrementMin;
    150      }
    151 
    152      float ratio = pageIncrement / (maxMinPosDifference + pageIncrement);
    153 
    154      OuterCSSCoord desiredThumbLength{
    155          std::max(mScrollbarData.mThumbMinLength,
    156                   mScrollbarData.mScrollTrackLength * ratio)};
    157 
    158      // Round the thumb length to an integer number of LayoutDevice pixels, to
    159      // match the main-thread behaviour.
    160      auto outerDeviceScale = ViewAs<OuterCSSToLayoutDeviceScale>(
    161          deviceScale, PixelCastJustification::CSSPixelsOfSurroundingContent);
    162      desiredThumbLength =
    163          LayoutDeviceCoord((desiredThumbLength * outerDeviceScale).Rounded()) /
    164          outerDeviceScale;
    165 
    166      effectiveThumbLength = desiredThumbLength;
    167 
    168      scale = desiredThumbLength / mScrollbarData.mThumbLength;
    169    }
    170 
    171    // Subtracting the offset of the scrollable rect is needed for right-to-left
    172    // pages.
    173    const CSSCoord curPos = aAxis.GetRectOffset(visualViewportRect) -
    174                            aAxis.GetRectOffset(mMetrics.GetScrollableRect());
    175 
    176    const CSSToOuterCSSScale thumbPosRatio(
    177        (maxMinPosDifference != 0)
    178            ? float((mScrollbarData.mScrollTrackLength - effectiveThumbLength) /
    179                    maxMinPosDifference)
    180            : 1.f);
    181 
    182    const OuterCSSCoord desiredThumbPos = curPos * thumbPosRatio;
    183 
    184    translation = desiredThumbPos - mScrollbarData.mThumbStart;
    185  } else {
    186    // In this branch (taken when apz.scrollthumb.recalc=false), |translation|
    187    // and |scale| are computed using the pre-bug1554795 approach of turning
    188    // the async scroll and zoom deltas into transforms to apply to the
    189    // main-thread thumb position and size.
    190 
    191    // The scroll thumb needs to be scaled in the direction of scrolling by the
    192    // inverse of the async zoom. This is because zooming in decreases the
    193    // fraction of the whole srollable rect that is in view.
    194    scale = 1.f / asyncZoom;
    195 
    196    // Note: |metrics.GetZoom()| doesn't yet include the async zoom.
    197    CSSToParentLayerScale effectiveZoom =
    198        CSSToParentLayerScale(mMetrics.GetZoom().scale * asyncZoom);
    199 
    200    if (gfxPlatform::UseDesktopZoomingScrollbars()) {
    201      // As computed by GetCurrentAsyncTransform, asyncScrollY is
    202      //   asyncScrollY = -(GetEffectiveScrollOffset -
    203      //   mLastContentPaintMetrics.GetLayoutScrollOffset()) *
    204      //   effectiveZoom
    205      // where GetEffectiveScrollOffset includes the visual viewport offset that
    206      // the main thread knows about plus any async scrolling to the visual
    207      // viewport offset that the main thread does not (yet) know about. We want
    208      // asyncScrollY to be
    209      //   asyncScrollY = -(GetEffectiveScrollOffset -
    210      //   mLastContentPaintMetrics.GetVisualScrollOffset()) * effectiveZoom
    211      // because the main thread positions the scrollbars at the visual viewport
    212      // offset that it knows about. (aMetrics is mLastContentPaintMetrics)
    213 
    214      asyncScroll -= aAxis.GetPointOffset((mMetrics.GetLayoutScrollOffset() -
    215                                           mMetrics.GetVisualScrollOffset()) *
    216                                          effectiveZoom);
    217    }
    218 
    219    // Here we convert the scrollbar thumb ratio into a true unitless ratio by
    220    // dividing out the conversion factor from the scrollframe's parent's space
    221    // to the scrollframe's space.
    222    float unitlessThumbRatio = mScrollbarData.mThumbRatio /
    223                               (mMetrics.GetPresShellResolution() * asyncZoom);
    224 
    225    // The scroll thumb needs to be translated in opposite direction of the
    226    // async scroll. This is because scrolling down, which translates the layer
    227    // content up, should result in moving the scroll thumb down.
    228    ParentLayerCoord translationPL = -asyncScroll * unitlessThumbRatio;
    229 
    230    // The translation we computed is in the scroll frame's ParentLayer space.
    231    // This includes the full cumulative resolution, even if we are a subframe.
    232    // However, the resulting transform is used in a context where the scrollbar
    233    // is already subject to the resolutions of enclosing scroll frames. To
    234    // avoid double application of these enclosing resolutions, divide them out,
    235    // leaving only the local resolution if any.
    236    translationPL /= (mMetrics.GetCumulativeResolution().scale /
    237                      mMetrics.GetPresShellResolution());
    238 
    239    // Convert translation to CSS pixels as this is what TranslateThumb expects.
    240    translation = ViewAs<OuterCSSPixel>(
    241        translationPL / (mMetrics.GetDevPixelsPerCSSPixel() *
    242                         LayoutDeviceToParentLayerScale(1.0)),
    243        PixelCastJustification::CSSPixelsOfSurroundingContent);
    244  }
    245 
    246  // When scaling the thumb to account for the async zoom, keep the position
    247  // of the start of the thumb (which corresponds to the scroll offset)
    248  // constant.
    249  if (haveAsyncZoom) {
    250    ScaleThumbBy(aAxis, scale, ScrollThumbExtent::Start);
    251  }
    252 
    253  // If the page is overscrolled, additionally squish the thumb in accordance
    254  // with the overscroll amount.
    255  if (overscroll != 0) {
    256    ParentLayerCoord compBoundsLength =
    257        aAxis.GetRectLength(mMetrics.GetCompositionBounds());
    258 
    259    // It's possible for the overscroll amount to be larger than the length
    260    // of the composition bounds, if an overscroll animation was started with
    261    // a very large velocity.
    262    float overscrollProportion =
    263        std::min(std::abs(overscroll.value), compBoundsLength.value) /
    264        compBoundsLength.value;
    265 
    266    float overscrollScale = 1.0f - overscrollProportion;
    267    MOZ_ASSERT(overscrollScale >= 0.0f && overscrollScale <= 1.0f);
    268    // If we're overscrolled at the top, keep the top of the thumb in place
    269    // as we squish it. If we're overscrolled at the bottom, keep the bottom of
    270    // the thumb in place.
    271    ScaleThumbBy(
    272        aAxis, overscrollScale,
    273        overscroll < 0 ? ScrollThumbExtent::Start : ScrollThumbExtent::End);
    274  }
    275 
    276  TranslateThumb(aAxis, translation);
    277 }
    278 
    279 LayerToParentLayerMatrix4x4 AsyncScrollThumbTransformer::ComputeTransform() {
    280  // We only apply the transform if the scroll-target layer has non-container
    281  // children (i.e. when it has some possibly-visible content). This is to
    282  // avoid moving scroll-bars in the situation that only a scroll information
    283  // layer has been built for a scroll frame, as this would result in a
    284  // disparity between scrollbars and visible content.
    285  if (mMetrics.IsScrollInfoLayer()) {
    286    return LayerToParentLayerMatrix4x4{};
    287  }
    288 
    289  MOZ_RELEASE_ASSERT(mApzc);
    290 
    291  mAsyncTransform =
    292      mApzc->GetCurrentAsyncTransform(AsyncPanZoomController::eForCompositing);
    293 
    294  // |mAsyncTransform| represents the amount by which we have scrolled and
    295  // zoomed since the last paint. Because the scrollbar was sized and positioned
    296  // based on the painted content, we need to adjust it based on asyncTransform
    297  // so that it reflects what the user is actually seeing now.
    298  if (*mScrollbarData.mDirection == ScrollDirection::eVertical) {
    299    ApplyTransformForAxis(mApzc->mY);
    300  }
    301  if (*mScrollbarData.mDirection == ScrollDirection::eHorizontal) {
    302    ApplyTransformForAxis(mApzc->mX);
    303  }
    304 
    305  LayerToParentLayerMatrix4x4 transform =
    306      mCurrentTransform * mScrollbarTransform;
    307 
    308  AsyncTransformComponentMatrix compensation;
    309  // If the scrollbar layer is a child of the content it is a scrollbar for,
    310  // then we need to adjust for any async transform (including an overscroll
    311  // transform) on the content. This needs to be cancelled out because layout
    312  // positions and sizes the scrollbar on the assumption that there is no async
    313  // transform, and without this adjustment the scrollbar will end up in the
    314  // wrong place.
    315  //
    316  // Note that since the async transform is applied on top of the content's
    317  // regular transform, we need to make sure to unapply the async transform in
    318  // the same coordinate space. This requires applying the content transform
    319  // and then unapplying it after unapplying the async transform.
    320  if (mScrollbarIsDescendant) {
    321    AsyncTransformComponentMatrix overscroll =
    322        mApzc->GetOverscrollTransform(AsyncPanZoomController::eForCompositing);
    323    gfx::Matrix4x4 asyncUntransform =
    324        (mAsyncTransform * overscroll).Inverse().ToUnknownMatrix();
    325    const gfx::Matrix4x4& contentTransform = mScrollableContentTransform;
    326    gfx::Matrix4x4 contentUntransform = contentTransform.Inverse();
    327 
    328    compensation *= ViewAs<AsyncTransformComponentMatrix>(
    329        contentTransform * asyncUntransform * contentUntransform);
    330  }
    331  transform = transform * compensation;
    332 
    333  return transform;
    334 }
    335 
    336 LayerToParentLayerMatrix4x4 ComputeTransformForScrollThumb(
    337    const LayerToParentLayerMatrix4x4& aCurrentTransform,
    338    const gfx::Matrix4x4& aScrollableContentTransform,
    339    AsyncPanZoomController* aApzc, const FrameMetrics& aMetrics,
    340    const ScrollbarData& aScrollbarData, bool aScrollbarIsDescendant) {
    341  return AsyncScrollThumbTransformer{
    342      aCurrentTransform, aScrollableContentTransform, aApzc, aMetrics,
    343      aScrollbarData,    aScrollbarIsDescendant}
    344      .ComputeTransform();
    345 }
    346 
    347 }  // namespace apz
    348 }  // namespace layers
    349 }  // namespace mozilla