summaryrefslogtreecommitdiffstats
path: root/gfx/layers/apz/src/ScrollThumbUtils.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 11:44:51 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 11:44:51 +0000
commit9e3c08db40b8916968b9f30096c7be3f00ce9647 (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /gfx/layers/apz/src/ScrollThumbUtils.cpp
parentInitial commit. (diff)
downloadthunderbird-9e3c08db40b8916968b9f30096c7be3f00ce9647.tar.xz
thunderbird-9e3c08db40b8916968b9f30096c7be3f00ce9647.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'gfx/layers/apz/src/ScrollThumbUtils.cpp')
-rw-r--r--gfx/layers/apz/src/ScrollThumbUtils.cpp341
1 files changed, 341 insertions, 0 deletions
diff --git a/gfx/layers/apz/src/ScrollThumbUtils.cpp b/gfx/layers/apz/src/ScrollThumbUtils.cpp
new file mode 100644
index 0000000000..814fa59759
--- /dev/null
+++ b/gfx/layers/apz/src/ScrollThumbUtils.cpp
@@ -0,0 +1,341 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "ScrollThumbUtils.h"
+#include "AsyncPanZoomController.h"
+#include "FrameMetrics.h"
+#include "UnitTransforms.h"
+#include "Units.h"
+#include "gfxPlatform.h"
+#include "mozilla/gfx/Matrix.h"
+#include "mozilla/StaticPrefs_toolkit.h"
+
+namespace mozilla {
+namespace layers {
+namespace apz {
+
+struct AsyncScrollThumbTransformer {
+ // Inputs
+ const LayerToParentLayerMatrix4x4& mCurrentTransform;
+ const gfx::Matrix4x4& mScrollableContentTransform;
+ AsyncPanZoomController* mApzc;
+ const FrameMetrics& mMetrics;
+ const ScrollbarData& mScrollbarData;
+ bool mScrollbarIsDescendant;
+
+ // Intermediate results
+ AsyncTransformComponentMatrix mAsyncTransform;
+ AsyncTransformComponentMatrix mScrollbarTransform;
+
+ LayerToParentLayerMatrix4x4 ComputeTransform();
+
+ private:
+ // Helper functions for ComputeTransform().
+
+ // If the thumb's orientation is along |aAxis|, add transformations
+ // of the thumb into |mScrollbarTransform|.
+ void ApplyTransformForAxis(const Axis& aAxis);
+
+ enum class ScrollThumbExtent { Start, End };
+
+ // Scale the thumb by |aScale| along |aAxis|, while keeping constant the
+ // position of the top denoted by |aExtent|.
+ void ScaleThumbBy(const Axis& aAxis, float aScale, ScrollThumbExtent aExtent);
+
+ // Translate the thumb along |aAxis| by |aTranslation| in "scrollbar space"
+ // (CSS pixels along the scrollbar track, similar to e.g.
+ // |mScrollbarData.mThumbStart|).
+ void TranslateThumb(const Axis& aAxis, OuterCSSCoord aTranslation);
+};
+
+void AsyncScrollThumbTransformer::TranslateThumb(const Axis& aAxis,
+ OuterCSSCoord aTranslation) {
+ aAxis.PostTranslate(
+ mScrollbarTransform,
+ ViewAs<CSSPixel>(aTranslation,
+ PixelCastJustification::CSSPixelsOfSurroundingContent) *
+ mMetrics.GetDevPixelsPerCSSPixel() *
+ LayoutDeviceToParentLayerScale(1.0));
+}
+
+void AsyncScrollThumbTransformer::ScaleThumbBy(const Axis& aAxis, float aScale,
+ ScrollThumbExtent aExtent) {
+ // To keep the position of the top of the thumb constant, the thumb needs to
+ // translated to compensate for the scale applied. The origin with respect to
+ // which the scale is applied is the origin of the layer tree, rather than
+ // the origin of the scroll thumb. This means that the space between the
+ // origin and the top of thumb (including the part of the scrollbar track
+ // above the thumb, the part of the scrollbar above the track (i.e. a
+ // scrollbar button if present), plus whatever content is above the scroll
+ // frame) is scaled too, effectively translating the thumb. We undo that
+ // translation here. (One can think of the adjustment being done to the
+ // translation here as a change of basis. We have a method to help with that,
+ // Matrix4x4::ChangeBasis(), but it wouldn't necessarily make the code cleaner
+ // in this case).
+ const OuterCSSCoord scrollTrackOrigin =
+ aAxis.GetPointOffset(
+ mMetrics.CalculateCompositionBoundsInOuterCssPixels().TopLeft()) +
+ mScrollbarData.mScrollTrackStart;
+ OuterCSSCoord thumbExtent = scrollTrackOrigin + mScrollbarData.mThumbStart;
+ if (aExtent == ScrollThumbExtent::End) {
+ thumbExtent += mScrollbarData.mThumbLength;
+ }
+ const OuterCSSCoord thumbExtentScaled = thumbExtent * aScale;
+ const OuterCSSCoord thumbExtentDelta = thumbExtentScaled - thumbExtent;
+
+ aAxis.PostScale(mScrollbarTransform, aScale);
+ TranslateThumb(aAxis, -thumbExtentDelta);
+}
+
+void AsyncScrollThumbTransformer::ApplyTransformForAxis(const Axis& aAxis) {
+ ParentLayerCoord asyncScroll = aAxis.GetTransformTranslation(mAsyncTransform);
+ const float asyncZoom = aAxis.GetTransformScale(mAsyncTransform);
+ const ParentLayerCoord overscroll =
+ aAxis.GetPointOffset(mApzc->GetOverscrollAmount());
+
+ bool haveAsyncZoom = !FuzzyEqualsAdditive(asyncZoom, 1.f);
+ if (!haveAsyncZoom && mApzc->IsZero(asyncScroll) &&
+ mApzc->IsZero(overscroll)) {
+ return;
+ }
+
+ OuterCSSCoord translation;
+ float scale = 1.0;
+
+ bool recalcMode = StaticPrefs::apz_scrollthumb_recalc();
+ if (recalcMode) {
+ // In this branch (taken when apz.scrollthumb.recalc=true), |translation|
+ // and |scale| are computed using the approach implemented in bug 1554795
+ // of fully recalculating the desired position and size using the logic
+ // that attempts to closely match the main-thread calculation.
+
+ const CSSRect visualViewportRect = mApzc->GetCurrentAsyncVisualViewport(
+ AsyncPanZoomController::eForCompositing);
+ const CSSCoord visualViewportLength =
+ aAxis.GetRectLength(visualViewportRect);
+
+ const CSSCoord maxMinPosDifference =
+ CSSCoord(
+ aAxis.GetRectLength(mMetrics.GetScrollableRect()).Truncated()) -
+ visualViewportLength;
+
+ OuterCSSCoord effectiveThumbLength = mScrollbarData.mThumbLength;
+
+ if (haveAsyncZoom) {
+ // The calculations here closely follow the main thread calculations at
+ // https://searchfox.org/mozilla-central/rev/0bf957f909ae1f3d19b43fd4edfc277342554836/layout/generic/nsGfxScrollFrame.cpp#6902-6927
+ // and
+ // https://searchfox.org/mozilla-central/rev/0bf957f909ae1f3d19b43fd4edfc277342554836/layout/xul/nsSliderFrame.cpp#587-614
+ // Any modifications there should be reflected here as well.
+ const CSSCoord pageIncrementMin =
+ static_cast<int>(visualViewportLength * 0.8);
+ CSSCoord pageIncrement;
+
+ CSSToLayoutDeviceScale deviceScale = mMetrics.GetDevPixelsPerCSSPixel();
+ if (*mScrollbarData.mDirection == ScrollDirection::eVertical) {
+ const CSSCoord lineScrollAmount =
+ (mApzc->GetScrollMetadata().GetLineScrollAmount() / deviceScale)
+ .height;
+ const double kScrollMultiplier =
+ StaticPrefs::toolkit_scrollbox_verticalScrollDistance();
+ CSSCoord increment = lineScrollAmount * kScrollMultiplier;
+
+ pageIncrement =
+ std::max(visualViewportLength - increment, pageIncrementMin);
+ } else {
+ pageIncrement = pageIncrementMin;
+ }
+
+ float ratio = pageIncrement / (maxMinPosDifference + pageIncrement);
+
+ OuterCSSCoord desiredThumbLength{
+ std::max(mScrollbarData.mThumbMinLength,
+ mScrollbarData.mScrollTrackLength * ratio)};
+
+ // Round the thumb length to an integer number of LayoutDevice pixels, to
+ // match the main-thread behaviour.
+ auto outerDeviceScale = ViewAs<OuterCSSToLayoutDeviceScale>(
+ deviceScale, PixelCastJustification::CSSPixelsOfSurroundingContent);
+ desiredThumbLength =
+ LayoutDeviceCoord((desiredThumbLength * outerDeviceScale).Rounded()) /
+ outerDeviceScale;
+
+ effectiveThumbLength = desiredThumbLength;
+
+ scale = desiredThumbLength / mScrollbarData.mThumbLength;
+ }
+
+ // Subtracting the offset of the scrollable rect is needed for right-to-left
+ // pages.
+ const CSSCoord curPos = aAxis.GetRectOffset(visualViewportRect) -
+ aAxis.GetRectOffset(mMetrics.GetScrollableRect());
+
+ const CSSToOuterCSSScale thumbPosRatio(
+ (maxMinPosDifference != 0)
+ ? float((mScrollbarData.mScrollTrackLength - effectiveThumbLength) /
+ maxMinPosDifference)
+ : 1.f);
+
+ const OuterCSSCoord desiredThumbPos = curPos * thumbPosRatio;
+
+ translation = desiredThumbPos - mScrollbarData.mThumbStart;
+ } else {
+ // In this branch (taken when apz.scrollthumb.recalc=false), |translation|
+ // and |scale| are computed using the pre-bug1554795 approach of turning
+ // the async scroll and zoom deltas into transforms to apply to the
+ // main-thread thumb position and size.
+
+ // The scroll thumb needs to be scaled in the direction of scrolling by the
+ // inverse of the async zoom. This is because zooming in decreases the
+ // fraction of the whole srollable rect that is in view.
+ scale = 1.f / asyncZoom;
+
+ // Note: |metrics.GetZoom()| doesn't yet include the async zoom.
+ CSSToParentLayerScale effectiveZoom =
+ CSSToParentLayerScale(mMetrics.GetZoom().scale * asyncZoom);
+
+ if (gfxPlatform::UseDesktopZoomingScrollbars()) {
+ // As computed by GetCurrentAsyncTransform, asyncScrollY is
+ // asyncScrollY = -(GetEffectiveScrollOffset -
+ // mLastContentPaintMetrics.GetLayoutScrollOffset()) *
+ // effectiveZoom
+ // where GetEffectiveScrollOffset includes the visual viewport offset that
+ // the main thread knows about plus any async scrolling to the visual
+ // viewport offset that the main thread does not (yet) know about. We want
+ // asyncScrollY to be
+ // asyncScrollY = -(GetEffectiveScrollOffset -
+ // mLastContentPaintMetrics.GetVisualScrollOffset()) * effectiveZoom
+ // because the main thread positions the scrollbars at the visual viewport
+ // offset that it knows about. (aMetrics is mLastContentPaintMetrics)
+
+ asyncScroll -= aAxis.GetPointOffset((mMetrics.GetLayoutScrollOffset() -
+ mMetrics.GetVisualScrollOffset()) *
+ effectiveZoom);
+ }
+
+ // Here we convert the scrollbar thumb ratio into a true unitless ratio by
+ // dividing out the conversion factor from the scrollframe's parent's space
+ // to the scrollframe's space.
+ float unitlessThumbRatio = mScrollbarData.mThumbRatio /
+ (mMetrics.GetPresShellResolution() * asyncZoom);
+
+ // The scroll thumb needs to be translated in opposite direction of the
+ // async scroll. This is because scrolling down, which translates the layer
+ // content up, should result in moving the scroll thumb down.
+ ParentLayerCoord translationPL = -asyncScroll * unitlessThumbRatio;
+
+ // The translation we computed is in the scroll frame's ParentLayer space.
+ // This includes the full cumulative resolution, even if we are a subframe.
+ // However, the resulting transform is used in a context where the scrollbar
+ // is already subject to the resolutions of enclosing scroll frames. To
+ // avoid double application of these enclosing resolutions, divide them out,
+ // leaving only the local resolution if any.
+ translationPL /= (mMetrics.GetCumulativeResolution().scale /
+ mMetrics.GetPresShellResolution());
+
+ // Convert translation to CSS pixels as this is what TranslateThumb expects.
+ translation = ViewAs<OuterCSSPixel>(
+ translationPL / (mMetrics.GetDevPixelsPerCSSPixel() *
+ LayoutDeviceToParentLayerScale(1.0)),
+ PixelCastJustification::CSSPixelsOfSurroundingContent);
+ }
+
+ // When scaling the thumb to account for the async zoom, keep the position
+ // of the start of the thumb (which corresponds to the scroll offset)
+ // constant.
+ if (haveAsyncZoom) {
+ ScaleThumbBy(aAxis, scale, ScrollThumbExtent::Start);
+ }
+
+ // If the page is overscrolled, additionally squish the thumb in accordance
+ // with the overscroll amount.
+ if (overscroll != 0) {
+ float overscrollScale =
+ 1.0f - (std::abs(overscroll.value) /
+ aAxis.GetRectLength(mMetrics.GetCompositionBounds()));
+ MOZ_ASSERT(overscrollScale > 0.0f && overscrollScale <= 1.0f);
+ // If we're overscrolled at the top, keep the top of the thumb in place
+ // as we squish it. If we're overscrolled at the bottom, keep the bottom of
+ // the thumb in place.
+ ScaleThumbBy(
+ aAxis, overscrollScale,
+ overscroll < 0 ? ScrollThumbExtent::Start : ScrollThumbExtent::End);
+ }
+
+ TranslateThumb(aAxis, translation);
+}
+
+LayerToParentLayerMatrix4x4 AsyncScrollThumbTransformer::ComputeTransform() {
+ // We only apply the transform if the scroll-target layer has non-container
+ // children (i.e. when it has some possibly-visible content). This is to
+ // avoid moving scroll-bars in the situation that only a scroll information
+ // layer has been built for a scroll frame, as this would result in a
+ // disparity between scrollbars and visible content.
+ if (mMetrics.IsScrollInfoLayer()) {
+ return LayerToParentLayerMatrix4x4{};
+ }
+
+ MOZ_RELEASE_ASSERT(mApzc);
+
+ mAsyncTransform =
+ mApzc->GetCurrentAsyncTransform(AsyncPanZoomController::eForCompositing);
+
+ // |mAsyncTransform| represents the amount by which we have scrolled and
+ // zoomed since the last paint. Because the scrollbar was sized and positioned
+ // based on the painted content, we need to adjust it based on asyncTransform
+ // so that it reflects what the user is actually seeing now.
+ if (*mScrollbarData.mDirection == ScrollDirection::eVertical) {
+ ApplyTransformForAxis(mApzc->mY);
+ }
+ if (*mScrollbarData.mDirection == ScrollDirection::eHorizontal) {
+ ApplyTransformForAxis(mApzc->mX);
+ }
+
+ LayerToParentLayerMatrix4x4 transform =
+ mCurrentTransform * mScrollbarTransform;
+
+ AsyncTransformComponentMatrix compensation;
+ // If the scrollbar layer is a child of the content it is a scrollbar for,
+ // then we need to adjust for any async transform (including an overscroll
+ // transform) on the content. This needs to be cancelled out because layout
+ // positions and sizes the scrollbar on the assumption that there is no async
+ // transform, and without this adjustment the scrollbar will end up in the
+ // wrong place.
+ //
+ // Note that since the async transform is applied on top of the content's
+ // regular transform, we need to make sure to unapply the async transform in
+ // the same coordinate space. This requires applying the content transform
+ // and then unapplying it after unapplying the async transform.
+ if (mScrollbarIsDescendant) {
+ AsyncTransformComponentMatrix overscroll =
+ mApzc->GetOverscrollTransform(AsyncPanZoomController::eForCompositing);
+ gfx::Matrix4x4 asyncUntransform =
+ (mAsyncTransform * overscroll).Inverse().ToUnknownMatrix();
+ const gfx::Matrix4x4& contentTransform = mScrollableContentTransform;
+ gfx::Matrix4x4 contentUntransform = contentTransform.Inverse();
+
+ compensation *= ViewAs<AsyncTransformComponentMatrix>(
+ contentTransform * asyncUntransform * contentUntransform);
+ }
+ transform = transform * compensation;
+
+ return transform;
+}
+
+LayerToParentLayerMatrix4x4 ComputeTransformForScrollThumb(
+ const LayerToParentLayerMatrix4x4& aCurrentTransform,
+ const gfx::Matrix4x4& aScrollableContentTransform,
+ AsyncPanZoomController* aApzc, const FrameMetrics& aMetrics,
+ const ScrollbarData& aScrollbarData, bool aScrollbarIsDescendant) {
+ return AsyncScrollThumbTransformer{
+ aCurrentTransform, aScrollableContentTransform, aApzc, aMetrics,
+ aScrollbarData, aScrollbarIsDescendant}
+ .ComputeTransform();
+}
+
+} // namespace apz
+} // namespace layers
+} // namespace mozilla