mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-11-04 10:18:41 +02:00 
			
		
		
		
	Convert `nsIScrollableFrame` to `ScrollContainerFrame` for all the APIs in nsLayoutUtils, and then adapt other callers until everything compiles. In `nsLayoutUtils::CalculateBasicFrameMetrics()`'s documentation, s/ComputeFrameMetrics/ComputeScrollMetadata/ because the method was renamed in https://hg.mozilla.org/mozilla-central/rev/cb2023f50288 Differential Revision: https://phabricator.services.mozilla.com/D211493
		
			
				
	
	
		
			788 lines
		
	
	
	
		
			34 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			788 lines
		
	
	
	
		
			34 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/* -*- 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 "ScrollSnap.h"
 | 
						|
 | 
						|
#include "FrameMetrics.h"
 | 
						|
 | 
						|
#include "mozilla/ScrollContainerFrame.h"
 | 
						|
#include "mozilla/ScrollSnapInfo.h"
 | 
						|
#include "mozilla/ServoStyleConsts.h"
 | 
						|
#include "nsIFrame.h"
 | 
						|
#include "nsLayoutUtils.h"
 | 
						|
#include "nsPresContext.h"
 | 
						|
#include "nsTArray.h"
 | 
						|
#include "mozilla/StaticPrefs_layout.h"
 | 
						|
 | 
						|
namespace mozilla {
 | 
						|
 | 
						|
/**
 | 
						|
 * Keeps track of the current best edge to snap to. The criteria for
 | 
						|
 * adding an edge depends on the scrolling unit.
 | 
						|
 */
 | 
						|
class CalcSnapPoints final {
 | 
						|
  using SnapTarget = ScrollSnapInfo::SnapTarget;
 | 
						|
 | 
						|
 public:
 | 
						|
  CalcSnapPoints(ScrollUnit aUnit, ScrollSnapFlags aSnapFlags,
 | 
						|
                 const nsPoint& aDestination, const nsPoint& aStartPos);
 | 
						|
  struct SnapPosition : public SnapTarget {
 | 
						|
    SnapPosition(const SnapTarget& aSnapTarget, nscoord aPosition,
 | 
						|
                 nscoord aDistanceOnOtherAxis)
 | 
						|
        : SnapTarget(aSnapTarget),
 | 
						|
          mPosition(aPosition),
 | 
						|
          mDistanceOnOtherAxis(aDistanceOnOtherAxis) {}
 | 
						|
 | 
						|
    nscoord mPosition;
 | 
						|
    // The distance from the scroll destination to this snap position on the
 | 
						|
    // other axis. This value is used if there are multiple SnapPositions on
 | 
						|
    // this axis, but the positions on the other axis are different.
 | 
						|
    nscoord mDistanceOnOtherAxis;
 | 
						|
  };
 | 
						|
 | 
						|
  void AddHorizontalEdge(const SnapTarget& aTarget);
 | 
						|
  void AddVerticalEdge(const SnapTarget& aTarget);
 | 
						|
 | 
						|
  struct CandidateTracker {
 | 
						|
    // keeps track of the position of the current second best edge on the
 | 
						|
    // opposite side of the best edge on this axis.
 | 
						|
    // We use NSCoordSaturatingSubtract to calculate the distance between a
 | 
						|
    // given position and this second best edge position so that it can be an
 | 
						|
    // uninitialized value as the maximum possible value, because the first
 | 
						|
    // distance calculation would always be nscoord_MAX.
 | 
						|
    nscoord mSecondBestEdge = nscoord_MAX;
 | 
						|
 | 
						|
    // Assuming in most cases there's no multiple coincide snap points.
 | 
						|
    AutoTArray<ScrollSnapTargetId, 1> mTargetIds;
 | 
						|
    // keeps track of the positions of the current best edge on this axis.
 | 
						|
    // NOTE: Each SnapPosition.mPosition points the same snap position on this
 | 
						|
    // axis but other member variables of SnapPosition may have different
 | 
						|
    // values.
 | 
						|
    AutoTArray<SnapPosition, 1> mBestEdges;
 | 
						|
    bool EdgeFound() const { return !mBestEdges.IsEmpty(); }
 | 
						|
  };
 | 
						|
  void AddEdge(const SnapPosition& aEdge, nscoord aDestination,
 | 
						|
               nscoord aStartPos, nscoord aScrollingDirection,
 | 
						|
               CandidateTracker* aCandidateTracker);
 | 
						|
  SnapDestination GetBestEdge(const nsSize& aSnapportSize) const;
 | 
						|
  nscoord XDistanceBetweenBestAndSecondEdge() const {
 | 
						|
    return std::abs(NSCoordSaturatingSubtract(
 | 
						|
        mTrackerOnX.mSecondBestEdge,
 | 
						|
        mTrackerOnX.EdgeFound() ? mTrackerOnX.mBestEdges[0].mPosition
 | 
						|
                                : mDestination.x,
 | 
						|
        nscoord_MAX));
 | 
						|
  }
 | 
						|
  nscoord YDistanceBetweenBestAndSecondEdge() const {
 | 
						|
    return std::abs(NSCoordSaturatingSubtract(
 | 
						|
        mTrackerOnY.mSecondBestEdge,
 | 
						|
        mTrackerOnY.EdgeFound() ? mTrackerOnY.mBestEdges[0].mPosition
 | 
						|
                                : mDestination.y,
 | 
						|
        nscoord_MAX));
 | 
						|
  }
 | 
						|
  const nsPoint& Destination() const { return mDestination; }
 | 
						|
 | 
						|
 protected:
 | 
						|
  ScrollUnit mUnit;
 | 
						|
  ScrollSnapFlags mSnapFlags;
 | 
						|
  nsPoint mDestination;  // gives the position after scrolling but before
 | 
						|
                         // snapping
 | 
						|
  nsPoint mStartPos;     // gives the position before scrolling
 | 
						|
  nsIntPoint mScrollingDirection;  // always -1, 0, or 1
 | 
						|
  CandidateTracker mTrackerOnX;
 | 
						|
  CandidateTracker mTrackerOnY;
 | 
						|
};
 | 
						|
 | 
						|
CalcSnapPoints::CalcSnapPoints(ScrollUnit aUnit, ScrollSnapFlags aSnapFlags,
 | 
						|
                               const nsPoint& aDestination,
 | 
						|
                               const nsPoint& aStartPos)
 | 
						|
    : mUnit(aUnit),
 | 
						|
      mSnapFlags(aSnapFlags),
 | 
						|
      mDestination(aDestination),
 | 
						|
      mStartPos(aStartPos) {
 | 
						|
  MOZ_ASSERT(aSnapFlags != ScrollSnapFlags::Disabled);
 | 
						|
 | 
						|
  nsPoint direction = aDestination - aStartPos;
 | 
						|
  mScrollingDirection = nsIntPoint(0, 0);
 | 
						|
  if (direction.x < 0) {
 | 
						|
    mScrollingDirection.x = -1;
 | 
						|
  }
 | 
						|
  if (direction.x > 0) {
 | 
						|
    mScrollingDirection.x = 1;
 | 
						|
  }
 | 
						|
  if (direction.y < 0) {
 | 
						|
    mScrollingDirection.y = -1;
 | 
						|
  }
 | 
						|
  if (direction.y > 0) {
 | 
						|
    mScrollingDirection.y = 1;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
SnapDestination CalcSnapPoints::GetBestEdge(const nsSize& aSnapportSize) const {
 | 
						|
  if (mTrackerOnX.EdgeFound() && mTrackerOnY.EdgeFound()) {
 | 
						|
    nsPoint bestCandidate(mTrackerOnX.mBestEdges[0].mPosition,
 | 
						|
                          mTrackerOnY.mBestEdges[0].mPosition);
 | 
						|
    nsRect snappedPort = nsRect(bestCandidate, aSnapportSize);
 | 
						|
 | 
						|
    // If we've found the candidates on both axes, it's possible some of
 | 
						|
    // candidates will be outside of the snapport if we snap to the point
 | 
						|
    // (mTrackerOnX.mBestEdges[0].mPosition,
 | 
						|
    // mTrackerOnY.mBestEdges[0].mPosition). So we need to get the intersection
 | 
						|
    // of the snap area of each snap target element on each axis and the
 | 
						|
    // snapport to tell whether it's outside of the snapport or not.
 | 
						|
    //
 | 
						|
    // Also if at least either one of the elements will be outside of the
 | 
						|
    // snapport if we snap to (mTrackerOnX.mBestEdges[0].mPosition,
 | 
						|
    // mTrackerOnY.mBestEdges[0].mPosition). We need to choose one of
 | 
						|
    // combinations of the candidates which is closest to the destination.
 | 
						|
    //
 | 
						|
    // So here we iterate over mTrackerOnX and mTrackerOnY just once
 | 
						|
    // respectively for both purposes to avoid iterating over them again and
 | 
						|
    // again.
 | 
						|
    //
 | 
						|
    // NOTE: Ideally we have to iterate over every possible combinations of
 | 
						|
    // (mTrackerOnX.mBestEdges[i].mSnapPoint.mY,
 | 
						|
    //  mTrackerOnY.mBestEdges[j].mSnapPoint.mX) and tell whether the given
 | 
						|
    // combination will be visible in the snapport or not (maybe we should
 | 
						|
    // choose the one that the visible area, i.e., the intersection area of
 | 
						|
    // the snap target elements and the snapport, is the largest one rather than
 | 
						|
    // the closest one?). But it will be inefficient, so here we will not
 | 
						|
    // iterate all the combinations, we just iterate all the snap target
 | 
						|
    // elements in each axis respectively.
 | 
						|
 | 
						|
    AutoTArray<ScrollSnapTargetId, 1> visibleTargetIdsOnX;
 | 
						|
    nscoord minimumDistanceOnY = nscoord_MAX;
 | 
						|
    size_t minimumXIndex = 0;
 | 
						|
    AutoTArray<ScrollSnapTargetId, 1> minimumDistanceTargetIdsOnX;
 | 
						|
    for (size_t i = 0; i < mTrackerOnX.mBestEdges.Length(); i++) {
 | 
						|
      const auto& targetX = mTrackerOnX.mBestEdges[i];
 | 
						|
      if (targetX.mSnapArea.Intersects(snappedPort)) {
 | 
						|
        visibleTargetIdsOnX.AppendElement(targetX.mTargetId);
 | 
						|
      }
 | 
						|
 | 
						|
      if (targetX.mDistanceOnOtherAxis < minimumDistanceOnY) {
 | 
						|
        minimumDistanceOnY = targetX.mDistanceOnOtherAxis;
 | 
						|
        minimumXIndex = i;
 | 
						|
        minimumDistanceTargetIdsOnX =
 | 
						|
            AutoTArray<ScrollSnapTargetId, 1>{targetX.mTargetId};
 | 
						|
      } else if (minimumDistanceOnY != nscoord_MAX &&
 | 
						|
                 targetX.mDistanceOnOtherAxis == minimumDistanceOnY) {
 | 
						|
        minimumDistanceTargetIdsOnX.AppendElement(targetX.mTargetId);
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    AutoTArray<ScrollSnapTargetId, 1> visibleTargetIdsOnY;
 | 
						|
    nscoord minimumDistanceOnX = nscoord_MAX;
 | 
						|
    size_t minimumYIndex = 0;
 | 
						|
    AutoTArray<ScrollSnapTargetId, 1> minimumDistanceTargetIdsOnY;
 | 
						|
    for (size_t i = 0; i < mTrackerOnY.mBestEdges.Length(); i++) {
 | 
						|
      const auto& targetY = mTrackerOnY.mBestEdges[i];
 | 
						|
      if (targetY.mSnapArea.Intersects(snappedPort)) {
 | 
						|
        visibleTargetIdsOnY.AppendElement(targetY.mTargetId);
 | 
						|
      }
 | 
						|
 | 
						|
      if (targetY.mDistanceOnOtherAxis < minimumDistanceOnX) {
 | 
						|
        minimumDistanceOnX = targetY.mDistanceOnOtherAxis;
 | 
						|
        minimumYIndex = i;
 | 
						|
        minimumDistanceTargetIdsOnY =
 | 
						|
            AutoTArray<ScrollSnapTargetId, 1>{targetY.mTargetId};
 | 
						|
      } else if (minimumDistanceOnX != nscoord_MAX &&
 | 
						|
                 targetY.mDistanceOnOtherAxis == minimumDistanceOnX) {
 | 
						|
        minimumDistanceTargetIdsOnY.AppendElement(targetY.mTargetId);
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    // If we have the target ids on both axes, it means the target elements
 | 
						|
    // (ids) specifying the best edge on X axis and the target elements
 | 
						|
    // specifying the best edge on Y axis are visible if we snap to the best
 | 
						|
    // edge. Thus they are valid snap positions.
 | 
						|
    if (!visibleTargetIdsOnX.IsEmpty() && !visibleTargetIdsOnY.IsEmpty()) {
 | 
						|
      return SnapDestination{
 | 
						|
          bestCandidate,
 | 
						|
          ScrollSnapTargetIds{visibleTargetIdsOnX, visibleTargetIdsOnY}};
 | 
						|
    }
 | 
						|
 | 
						|
    // Now we've already known that snapping to
 | 
						|
    // (mTrackerOnX.mBestEdges[0].mPosition,
 | 
						|
    // mTrackerOnY.mBestEdges[0].mPosition) will make all candidates of
 | 
						|
    // mTrackerX or mTrackerY (or both) outside of the snapport. We need to
 | 
						|
    // choose another combination where candidates of both mTrackerX/Y are
 | 
						|
    // inside the snapport.
 | 
						|
 | 
						|
    // There are three possibilities;
 | 
						|
    // 1) There's no candidate on X axis in mTrackerOnY (that means
 | 
						|
    //    each candidate's scroll-snap-align is `none` on X axis), but there's
 | 
						|
    //    any candidate in mTrackerOnX, the closest candidates of mTrackerOnX
 | 
						|
    //    should be used.
 | 
						|
    // 2) There's no candidate on Y axis in mTrackerOnX (that means
 | 
						|
    //    each candidate's scroll-snap-align is `none` on Y axis), but there's
 | 
						|
    //    any candidate in mTrackerOnY, the closest candidates of mTrackerOnY
 | 
						|
    //    should be used.
 | 
						|
    // 3) There are candidates on both axes. Choosing a combination such as
 | 
						|
    //    (mTrackerOnX.mBestEdges[i].mSnapPoint.mX,
 | 
						|
    //     mTrackerOnY.mBestEdges[i].mSnapPoint.mY)
 | 
						|
    //    would require us to iterate over the candidates again if the
 | 
						|
    //    combination position is outside the snapport, which we don't want to
 | 
						|
    //    do. Instead, we choose either one of the axis' candidates.
 | 
						|
    if ((minimumDistanceOnX == nscoord_MAX) &&
 | 
						|
        minimumDistanceOnY != nscoord_MAX) {
 | 
						|
      bestCandidate.y = *mTrackerOnX.mBestEdges[minimumXIndex].mSnapPoint.mY;
 | 
						|
      return SnapDestination{bestCandidate,
 | 
						|
                             ScrollSnapTargetIds{minimumDistanceTargetIdsOnX,
 | 
						|
                                                 minimumDistanceTargetIdsOnX}};
 | 
						|
    }
 | 
						|
 | 
						|
    if (minimumDistanceOnX != nscoord_MAX &&
 | 
						|
        minimumDistanceOnY == nscoord_MAX) {
 | 
						|
      bestCandidate.x = *mTrackerOnY.mBestEdges[minimumYIndex].mSnapPoint.mX;
 | 
						|
      return SnapDestination{bestCandidate,
 | 
						|
                             ScrollSnapTargetIds{minimumDistanceTargetIdsOnY,
 | 
						|
                                                 minimumDistanceTargetIdsOnY}};
 | 
						|
    }
 | 
						|
 | 
						|
    if (minimumDistanceOnX != nscoord_MAX &&
 | 
						|
        minimumDistanceOnY != nscoord_MAX) {
 | 
						|
      // If we've found candidates on both axes, choose the closest point either
 | 
						|
      // on X axis or Y axis from the scroll destination. I.e. choose
 | 
						|
      // `minimumXIndex` one or `minimumYIndex` one to make at least one of
 | 
						|
      // snap target elements visible inside the snapport.
 | 
						|
      //
 | 
						|
      // For example,
 | 
						|
      // [bestCandidate.x, mTrackerOnX.mBestEdges[minimumXIndex].mSnapPoint.mY]
 | 
						|
      // is a candidate generated from a single element, thus snapping to the
 | 
						|
      // point would definitely make the element visible inside the snapport.
 | 
						|
      if (hypotf(NSCoordToFloat(mDestination.x -
 | 
						|
                                mTrackerOnX.mBestEdges[0].mPosition),
 | 
						|
                 NSCoordToFloat(minimumDistanceOnY)) <
 | 
						|
          hypotf(NSCoordToFloat(minimumDistanceOnX),
 | 
						|
                 NSCoordToFloat(mDestination.y -
 | 
						|
                                mTrackerOnY.mBestEdges[0].mPosition))) {
 | 
						|
        bestCandidate.y = *mTrackerOnX.mBestEdges[minimumXIndex].mSnapPoint.mY;
 | 
						|
      } else {
 | 
						|
        bestCandidate.x = *mTrackerOnY.mBestEdges[minimumYIndex].mSnapPoint.mX;
 | 
						|
      }
 | 
						|
      return SnapDestination{bestCandidate,
 | 
						|
                             ScrollSnapTargetIds{minimumDistanceTargetIdsOnX,
 | 
						|
                                                 minimumDistanceTargetIdsOnY}};
 | 
						|
    }
 | 
						|
    MOZ_ASSERT_UNREACHABLE("There's at least one candidate on either axis");
 | 
						|
    // `minimumDistanceOnX == nscoord_MAX && minimumDistanceOnY == nscoord_MAX`
 | 
						|
    // should not happen but we fall back for safety.
 | 
						|
  }
 | 
						|
 | 
						|
  return SnapDestination{
 | 
						|
      nsPoint(
 | 
						|
          mTrackerOnX.EdgeFound() ? mTrackerOnX.mBestEdges[0].mPosition
 | 
						|
          // In the case of IntendedEndPosition (i.e. the destination point is
 | 
						|
          // explicitely specied, e.g. scrollTo) use the destination point if we
 | 
						|
          // didn't find any candidates.
 | 
						|
          : !(mSnapFlags & ScrollSnapFlags::IntendedDirection) ? mDestination.x
 | 
						|
                                                               : mStartPos.x,
 | 
						|
          mTrackerOnY.EdgeFound() ? mTrackerOnY.mBestEdges[0].mPosition
 | 
						|
          // Same as above X axis case, use the destination point if we didn't
 | 
						|
          // find any candidates.
 | 
						|
          : !(mSnapFlags & ScrollSnapFlags::IntendedDirection) ? mDestination.y
 | 
						|
                                                               : mStartPos.y),
 | 
						|
      ScrollSnapTargetIds{mTrackerOnX.mTargetIds, mTrackerOnY.mTargetIds}};
 | 
						|
}
 | 
						|
 | 
						|
void CalcSnapPoints::AddHorizontalEdge(const SnapTarget& aTarget) {
 | 
						|
  MOZ_ASSERT(aTarget.mSnapPoint.mY);
 | 
						|
  AddEdge(SnapPosition{aTarget, *aTarget.mSnapPoint.mY,
 | 
						|
                       aTarget.mSnapPoint.mX
 | 
						|
                           ? std::abs(mDestination.x - *aTarget.mSnapPoint.mX)
 | 
						|
                           : nscoord_MAX},
 | 
						|
          mDestination.y, mStartPos.y, mScrollingDirection.y, &mTrackerOnY);
 | 
						|
}
 | 
						|
 | 
						|
void CalcSnapPoints::AddVerticalEdge(const SnapTarget& aTarget) {
 | 
						|
  MOZ_ASSERT(aTarget.mSnapPoint.mX);
 | 
						|
  AddEdge(SnapPosition{aTarget, *aTarget.mSnapPoint.mX,
 | 
						|
                       aTarget.mSnapPoint.mY
 | 
						|
                           ? std::abs(mDestination.y - *aTarget.mSnapPoint.mY)
 | 
						|
                           : nscoord_MAX},
 | 
						|
          mDestination.x, mStartPos.x, mScrollingDirection.x, &mTrackerOnX);
 | 
						|
}
 | 
						|
 | 
						|
void CalcSnapPoints::AddEdge(const SnapPosition& aEdge, nscoord aDestination,
 | 
						|
                             nscoord aStartPos, nscoord aScrollingDirection,
 | 
						|
                             CandidateTracker* aCandidateTracker) {
 | 
						|
  if (mSnapFlags & ScrollSnapFlags::IntendedDirection) {
 | 
						|
    // In the case of intended direction, we only want to snap to points ahead
 | 
						|
    // of the direction we are scrolling.
 | 
						|
    if (aScrollingDirection == 0 ||
 | 
						|
        (aEdge.mPosition - aStartPos) * aScrollingDirection <= 0) {
 | 
						|
      // The scroll direction is neutral - will not hit a snap point, or the
 | 
						|
      // edge is not in the direction we are scrolling, skip it.
 | 
						|
      return;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  if (!aCandidateTracker->EdgeFound()) {
 | 
						|
    aCandidateTracker->mBestEdges = AutoTArray<SnapPosition, 1>{aEdge};
 | 
						|
    aCandidateTracker->mTargetIds =
 | 
						|
        AutoTArray<ScrollSnapTargetId, 1>{aEdge.mTargetId};
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  auto isPreferredStopAlways = [&](const SnapPosition& aSnapPosition) -> bool {
 | 
						|
    MOZ_ASSERT(mSnapFlags & ScrollSnapFlags::IntendedDirection);
 | 
						|
    // In the case of intended direction scroll operations, `scroll-snap-stop:
 | 
						|
    // always` snap points in between the start point and the scroll destination
 | 
						|
    // are preferable preferable. In other words any `scroll-snap-stop: always`
 | 
						|
    // snap points can be handled as if it's `scroll-snap-stop: normal`.
 | 
						|
    return aSnapPosition.mScrollSnapStop == StyleScrollSnapStop::Always &&
 | 
						|
           std::abs(aSnapPosition.mPosition - aStartPos) <
 | 
						|
               std::abs(aDestination - aStartPos);
 | 
						|
  };
 | 
						|
 | 
						|
  const bool isOnOppositeSide =
 | 
						|
      ((aEdge.mPosition - aDestination) > 0) !=
 | 
						|
      ((aCandidateTracker->mBestEdges[0].mPosition - aDestination) > 0);
 | 
						|
  const nscoord distanceFromStart = aEdge.mPosition - aStartPos;
 | 
						|
  // A utility function to update the best and the second best edges in the
 | 
						|
  // given conditions.
 | 
						|
  // |aIsCloserThanBest| True if the current candidate is closer than the best
 | 
						|
  // edge.
 | 
						|
  // |aIsCloserThanSecond| True if the current candidate is closer than
 | 
						|
  // the second best edge.
 | 
						|
  const nscoord distanceFromDestination = aEdge.mPosition - aDestination;
 | 
						|
  auto updateBestEdges = [&](bool aIsCloserThanBest, bool aIsCloserThanSecond) {
 | 
						|
    if (aIsCloserThanBest) {
 | 
						|
      if (mSnapFlags & ScrollSnapFlags::IntendedDirection &&
 | 
						|
          isPreferredStopAlways(aEdge)) {
 | 
						|
        // In the case of intended direction scroll operations and the new best
 | 
						|
        // candidate is `scroll-snap-stop: always` and if it's closer to the
 | 
						|
        // start position than the destination, thus we won't use the second
 | 
						|
        // best edge since even if the snap port of the best edge covers entire
 | 
						|
        // snapport, the `scroll-snap-stop: always` snap point is preferred than
 | 
						|
        // any points.
 | 
						|
        // NOTE: We've already ignored snap points behind start points so that
 | 
						|
        // we can use std::abs here in the comparison.
 | 
						|
        //
 | 
						|
        // For example, if there's a `scroll-snap-stop: always` in between the
 | 
						|
        // start point and destination, no `snap-overflow` mechanism should
 | 
						|
        // happen, if there's `scroll-snap-stop: always` further than the
 | 
						|
        // destination, `snap-overflow` might happen something like below
 | 
						|
        // diagram.
 | 
						|
        // start        always    dest   other always
 | 
						|
        //   |------------|---------|------|
 | 
						|
        aCandidateTracker->mSecondBestEdge = aEdge.mPosition;
 | 
						|
      } else if (isOnOppositeSide) {
 | 
						|
        // Replace the second best edge with the current best edge only if the
 | 
						|
        // new best edge (aEdge) is on the opposite side of the current best
 | 
						|
        // edge.
 | 
						|
        aCandidateTracker->mSecondBestEdge =
 | 
						|
            aCandidateTracker->mBestEdges[0].mPosition;
 | 
						|
      }
 | 
						|
      aCandidateTracker->mBestEdges = AutoTArray<SnapPosition, 1>{aEdge};
 | 
						|
      aCandidateTracker->mTargetIds =
 | 
						|
          AutoTArray<ScrollSnapTargetId, 1>{aEdge.mTargetId};
 | 
						|
    } else {
 | 
						|
      if (aEdge.mPosition == aCandidateTracker->mBestEdges[0].mPosition) {
 | 
						|
        aCandidateTracker->mTargetIds.AppendElement(aEdge.mTargetId);
 | 
						|
        aCandidateTracker->mBestEdges.AppendElement(aEdge);
 | 
						|
      }
 | 
						|
      if (aIsCloserThanSecond && isOnOppositeSide) {
 | 
						|
        aCandidateTracker->mSecondBestEdge = aEdge.mPosition;
 | 
						|
      }
 | 
						|
    }
 | 
						|
  };
 | 
						|
 | 
						|
  bool isCandidateOfBest = false;
 | 
						|
  bool isCandidateOfSecondBest = false;
 | 
						|
  switch (mUnit) {
 | 
						|
    case ScrollUnit::DEVICE_PIXELS:
 | 
						|
    case ScrollUnit::LINES:
 | 
						|
    case ScrollUnit::WHOLE: {
 | 
						|
      isCandidateOfBest =
 | 
						|
          std::abs(distanceFromDestination) <
 | 
						|
          std::abs(aCandidateTracker->mBestEdges[0].mPosition - aDestination);
 | 
						|
      isCandidateOfSecondBest =
 | 
						|
          std::abs(distanceFromDestination) <
 | 
						|
          std::abs(NSCoordSaturatingSubtract(aCandidateTracker->mSecondBestEdge,
 | 
						|
                                             aDestination, nscoord_MAX));
 | 
						|
      break;
 | 
						|
    }
 | 
						|
    case ScrollUnit::PAGES: {
 | 
						|
      // distance to the edge from the scrolling destination in the direction of
 | 
						|
      // scrolling
 | 
						|
      nscoord overshoot = distanceFromDestination * aScrollingDirection;
 | 
						|
      // distance to the current best edge from the scrolling destination in the
 | 
						|
      // direction of scrolling
 | 
						|
      nscoord curOvershoot =
 | 
						|
          (aCandidateTracker->mBestEdges[0].mPosition - aDestination) *
 | 
						|
          aScrollingDirection;
 | 
						|
 | 
						|
      nscoord secondOvershoot =
 | 
						|
          NSCoordSaturatingSubtract(aCandidateTracker->mSecondBestEdge,
 | 
						|
                                    aDestination, nscoord_MAX) *
 | 
						|
          aScrollingDirection;
 | 
						|
 | 
						|
      // edges between the current position and the scrolling destination are
 | 
						|
      // favoured to preserve context
 | 
						|
      if (overshoot < 0) {
 | 
						|
        isCandidateOfBest = overshoot > curOvershoot || curOvershoot >= 0;
 | 
						|
        isCandidateOfSecondBest =
 | 
						|
            overshoot > secondOvershoot || secondOvershoot >= 0;
 | 
						|
      }
 | 
						|
      // if there are no edges between the current position and the scrolling
 | 
						|
      // destination the closest edge beyond the destination is used
 | 
						|
      if (overshoot > 0) {
 | 
						|
        isCandidateOfBest = overshoot < curOvershoot;
 | 
						|
        isCandidateOfSecondBest = overshoot < secondOvershoot;
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  if (mSnapFlags & ScrollSnapFlags::IntendedDirection) {
 | 
						|
    if (isPreferredStopAlways(aEdge)) {
 | 
						|
      // If the given position is `scroll-snap-stop: always` and if the position
 | 
						|
      // is in between the start and the destination positions, update the best
 | 
						|
      // position based on the distance from the __start__ point.
 | 
						|
      isCandidateOfBest =
 | 
						|
          std::abs(distanceFromStart) <
 | 
						|
          std::abs(aCandidateTracker->mBestEdges[0].mPosition - aStartPos);
 | 
						|
    } else if (isPreferredStopAlways(aCandidateTracker->mBestEdges[0])) {
 | 
						|
      // If we've found a preferable `scroll-snap-stop:always` position as the
 | 
						|
      // best, do not update it unless the given position is also
 | 
						|
      // `scroll-snap-stop: always`.
 | 
						|
      isCandidateOfBest = false;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  updateBestEdges(isCandidateOfBest, isCandidateOfSecondBest);
 | 
						|
}
 | 
						|
 | 
						|
static void ProcessSnapPositions(CalcSnapPoints& aCalcSnapPoints,
 | 
						|
                                 const ScrollSnapInfo& aSnapInfo) {
 | 
						|
  aSnapInfo.ForEachValidTargetFor(
 | 
						|
      aCalcSnapPoints.Destination(), [&](const auto& aTarget) -> bool {
 | 
						|
        if (aTarget.mSnapPoint.mX && aSnapInfo.mScrollSnapStrictnessX !=
 | 
						|
                                         StyleScrollSnapStrictness::None) {
 | 
						|
          aCalcSnapPoints.AddVerticalEdge(aTarget);
 | 
						|
        }
 | 
						|
        if (aTarget.mSnapPoint.mY && aSnapInfo.mScrollSnapStrictnessY !=
 | 
						|
                                         StyleScrollSnapStrictness::None) {
 | 
						|
          aCalcSnapPoints.AddHorizontalEdge(aTarget);
 | 
						|
        }
 | 
						|
        return true;
 | 
						|
      });
 | 
						|
}
 | 
						|
 | 
						|
Maybe<SnapDestination> ScrollSnapUtils::GetSnapPointForDestination(
 | 
						|
    const ScrollSnapInfo& aSnapInfo, ScrollUnit aUnit,
 | 
						|
    ScrollSnapFlags aSnapFlags, const nsRect& aScrollRange,
 | 
						|
    const nsPoint& aStartPos, const nsPoint& aDestination) {
 | 
						|
  if (aSnapInfo.mScrollSnapStrictnessY == StyleScrollSnapStrictness::None &&
 | 
						|
      aSnapInfo.mScrollSnapStrictnessX == StyleScrollSnapStrictness::None) {
 | 
						|
    return Nothing();
 | 
						|
  }
 | 
						|
 | 
						|
  if (!aSnapInfo.HasSnapPositions()) {
 | 
						|
    return Nothing();
 | 
						|
  }
 | 
						|
 | 
						|
  CalcSnapPoints calcSnapPoints(aUnit, aSnapFlags, aDestination, aStartPos);
 | 
						|
 | 
						|
  ProcessSnapPositions(calcSnapPoints, aSnapInfo);
 | 
						|
 | 
						|
  // If the distance between the first and the second candidate snap points
 | 
						|
  // is larger than the snapport size and the snapport is covered by larger
 | 
						|
  // elements, any points inside the covering area should be valid snap
 | 
						|
  // points.
 | 
						|
  // https://drafts.csswg.org/css-scroll-snap-1/#snap-overflow
 | 
						|
  // NOTE: |aDestination| sometimes points outside of the scroll range, e.g.
 | 
						|
  // by the APZC fling, so for the overflow checks we need to clamp it.
 | 
						|
  nsPoint clampedDestination = aScrollRange.ClampPoint(aDestination);
 | 
						|
  for (auto range : aSnapInfo.mXRangeWiderThanSnapport) {
 | 
						|
    if (range.IsValid(clampedDestination.x, aSnapInfo.mSnapportSize.width) &&
 | 
						|
        calcSnapPoints.XDistanceBetweenBestAndSecondEdge() >
 | 
						|
            aSnapInfo.mSnapportSize.width) {
 | 
						|
      calcSnapPoints.AddVerticalEdge(ScrollSnapInfo::SnapTarget{
 | 
						|
          Some(clampedDestination.x), Nothing(), range.mSnapArea,
 | 
						|
          StyleScrollSnapStop::Normal, range.mTargetId});
 | 
						|
      break;
 | 
						|
    }
 | 
						|
  }
 | 
						|
  for (auto range : aSnapInfo.mYRangeWiderThanSnapport) {
 | 
						|
    if (range.IsValid(clampedDestination.y, aSnapInfo.mSnapportSize.height) &&
 | 
						|
        calcSnapPoints.YDistanceBetweenBestAndSecondEdge() >
 | 
						|
            aSnapInfo.mSnapportSize.height) {
 | 
						|
      calcSnapPoints.AddHorizontalEdge(ScrollSnapInfo::SnapTarget{
 | 
						|
          Nothing(), Some(clampedDestination.y), range.mSnapArea,
 | 
						|
          StyleScrollSnapStop::Normal, range.mTargetId});
 | 
						|
      break;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  bool snapped = false;
 | 
						|
  auto finalPos = calcSnapPoints.GetBestEdge(aSnapInfo.mSnapportSize);
 | 
						|
  constexpr float proximityRatio = 0.3;
 | 
						|
  if (aSnapInfo.mScrollSnapStrictnessY ==
 | 
						|
          StyleScrollSnapStrictness::Proximity &&
 | 
						|
      std::abs(aDestination.y - finalPos.mPosition.y) >
 | 
						|
          aSnapInfo.mSnapportSize.height * proximityRatio) {
 | 
						|
    finalPos.mPosition.y = aDestination.y;
 | 
						|
  } else if (aSnapInfo.mScrollSnapStrictnessY !=
 | 
						|
                 StyleScrollSnapStrictness::None &&
 | 
						|
             aDestination.y != finalPos.mPosition.y) {
 | 
						|
    snapped = true;
 | 
						|
  }
 | 
						|
  if (aSnapInfo.mScrollSnapStrictnessX ==
 | 
						|
          StyleScrollSnapStrictness::Proximity &&
 | 
						|
      std::abs(aDestination.x - finalPos.mPosition.x) >
 | 
						|
          aSnapInfo.mSnapportSize.width * proximityRatio) {
 | 
						|
    finalPos.mPosition.x = aDestination.x;
 | 
						|
  } else if (aSnapInfo.mScrollSnapStrictnessX !=
 | 
						|
                 StyleScrollSnapStrictness::None &&
 | 
						|
             aDestination.x != finalPos.mPosition.x) {
 | 
						|
    snapped = true;
 | 
						|
  }
 | 
						|
  return snapped ? Some(finalPos) : Nothing();
 | 
						|
}
 | 
						|
 | 
						|
ScrollSnapTargetId ScrollSnapUtils::GetTargetIdFor(const nsIFrame* aFrame) {
 | 
						|
  MOZ_ASSERT(aFrame && aFrame->GetContent());
 | 
						|
  return ScrollSnapTargetId{reinterpret_cast<uintptr_t>(aFrame->GetContent())};
 | 
						|
}
 | 
						|
 | 
						|
static std::pair<Maybe<nscoord>, Maybe<nscoord>> GetCandidateInLastTargets(
 | 
						|
    const ScrollSnapInfo& aSnapInfo, const nsPoint& aCurrentPosition,
 | 
						|
    const UniquePtr<ScrollSnapTargetIds>& aLastSnapTargetIds,
 | 
						|
    const nsIContent* aFocusedContent) {
 | 
						|
  ScrollSnapTargetId targetIdForFocusedContent = ScrollSnapTargetId::None;
 | 
						|
  if (aFocusedContent && aFocusedContent->GetPrimaryFrame()) {
 | 
						|
    targetIdForFocusedContent =
 | 
						|
        ScrollSnapUtils::GetTargetIdFor(aFocusedContent->GetPrimaryFrame());
 | 
						|
  }
 | 
						|
 | 
						|
  // Note: Below algorithm doesn't care about cases where the last snap point
 | 
						|
  // was on an element larger than the snapport since it's not clear to us
 | 
						|
  // what we should do for now.
 | 
						|
  // https://github.com/w3c/csswg-drafts/issues/7438
 | 
						|
  const ScrollSnapInfo::SnapTarget* focusedTarget = nullptr;
 | 
						|
  Maybe<nscoord> x, y;
 | 
						|
  aSnapInfo.ForEachValidTargetFor(
 | 
						|
      aCurrentPosition, [&](const auto& aTarget) -> bool {
 | 
						|
        if (aTarget.mSnapPoint.mX && aSnapInfo.mScrollSnapStrictnessX !=
 | 
						|
                                         StyleScrollSnapStrictness::None) {
 | 
						|
          if (aLastSnapTargetIds->mIdsOnX.Contains(aTarget.mTargetId)) {
 | 
						|
            if (targetIdForFocusedContent == aTarget.mTargetId) {
 | 
						|
              // If we've already found the candidate on Y axis, but if snapping
 | 
						|
              // to the point results this target is scrolled out, we can't use
 | 
						|
              // it.
 | 
						|
              if ((y && !aTarget.mSnapArea.Intersects(
 | 
						|
                            nsRect(nsPoint(*aTarget.mSnapPoint.mX, *y),
 | 
						|
                                   aSnapInfo.mSnapportSize)))) {
 | 
						|
                y.reset();
 | 
						|
              }
 | 
						|
 | 
						|
              focusedTarget = &aTarget;
 | 
						|
              // If the focused one is valid, then it's the candidate.
 | 
						|
              x = aTarget.mSnapPoint.mX;
 | 
						|
            }
 | 
						|
 | 
						|
            if (!x) {
 | 
						|
              // Update the candidate on X axis only if
 | 
						|
              // 1) we haven't yet found the candidate on Y axis
 | 
						|
              // 2) or if we've found the candiate on Y axis and if snapping to
 | 
						|
              // the
 | 
						|
              //    candidate position result the target element is visible
 | 
						|
              //    inside the snapport.
 | 
						|
              if (!y || (y && aTarget.mSnapArea.Intersects(
 | 
						|
                                  nsRect(nsPoint(*aTarget.mSnapPoint.mX, *y),
 | 
						|
                                         aSnapInfo.mSnapportSize)))) {
 | 
						|
                x = aTarget.mSnapPoint.mX;
 | 
						|
              }
 | 
						|
            }
 | 
						|
          }
 | 
						|
        }
 | 
						|
        if (aTarget.mSnapPoint.mY && aSnapInfo.mScrollSnapStrictnessY !=
 | 
						|
                                         StyleScrollSnapStrictness::None) {
 | 
						|
          if (aLastSnapTargetIds->mIdsOnY.Contains(aTarget.mTargetId)) {
 | 
						|
            if (targetIdForFocusedContent == aTarget.mTargetId) {
 | 
						|
              NS_ASSERTION(
 | 
						|
                  !focusedTarget || focusedTarget == &aTarget,
 | 
						|
                  "If the focused target has been found on X axis, the "
 | 
						|
                  "target should be same");
 | 
						|
              // If we've already found the candidate on X axis other than the
 | 
						|
              // focused one, but if snapping to the point results this target
 | 
						|
              // is scrolled out, we can't use it.
 | 
						|
              if (!focusedTarget &&
 | 
						|
                  (x && !aTarget.mSnapArea.Intersects(
 | 
						|
                            nsRect(nsPoint(*x, *aTarget.mSnapPoint.mY),
 | 
						|
                                   aSnapInfo.mSnapportSize)))) {
 | 
						|
                x.reset();
 | 
						|
              }
 | 
						|
 | 
						|
              focusedTarget = &aTarget;
 | 
						|
              y = aTarget.mSnapPoint.mY;
 | 
						|
            }
 | 
						|
 | 
						|
            if (!y) {
 | 
						|
              if (!x || (x && aTarget.mSnapArea.Intersects(
 | 
						|
                                  nsRect(nsPoint(*x, *aTarget.mSnapPoint.mY),
 | 
						|
                                         aSnapInfo.mSnapportSize)))) {
 | 
						|
                y = aTarget.mSnapPoint.mY;
 | 
						|
              }
 | 
						|
            }
 | 
						|
          }
 | 
						|
        }
 | 
						|
 | 
						|
        // If we found candidates on both axes, it's the one we need.
 | 
						|
        if (x && y &&
 | 
						|
            // If we haven't found the focused target, it's possible that we
 | 
						|
            // haven't iterated it, don't break in such case.
 | 
						|
            (targetIdForFocusedContent == ScrollSnapTargetId::None ||
 | 
						|
             focusedTarget)) {
 | 
						|
          return false;
 | 
						|
        }
 | 
						|
        return true;
 | 
						|
      });
 | 
						|
 | 
						|
  return {x, y};
 | 
						|
}
 | 
						|
 | 
						|
Maybe<SnapDestination> ScrollSnapUtils::GetSnapPointForResnap(
 | 
						|
    const ScrollSnapInfo& aSnapInfo, const nsRect& aScrollRange,
 | 
						|
    const nsPoint& aCurrentPosition,
 | 
						|
    const UniquePtr<ScrollSnapTargetIds>& aLastSnapTargetIds,
 | 
						|
    const nsIContent* aFocusedContent) {
 | 
						|
  if (!aLastSnapTargetIds) {
 | 
						|
    return GetSnapPointForDestination(aSnapInfo, ScrollUnit::DEVICE_PIXELS,
 | 
						|
                                      ScrollSnapFlags::IntendedEndPosition,
 | 
						|
                                      aScrollRange, aCurrentPosition,
 | 
						|
                                      aCurrentPosition);
 | 
						|
  }
 | 
						|
 | 
						|
  auto [x, y] = GetCandidateInLastTargets(aSnapInfo, aCurrentPosition,
 | 
						|
                                          aLastSnapTargetIds, aFocusedContent);
 | 
						|
  if (!x && !y) {
 | 
						|
    // In the worst case there's no longer valid snap points previously snapped,
 | 
						|
    // try to find new valid snap points.
 | 
						|
    return GetSnapPointForDestination(aSnapInfo, ScrollUnit::DEVICE_PIXELS,
 | 
						|
                                      ScrollSnapFlags::IntendedEndPosition,
 | 
						|
                                      aScrollRange, aCurrentPosition,
 | 
						|
                                      aCurrentPosition);
 | 
						|
  }
 | 
						|
 | 
						|
  // If there's no candidate on one of the axes in the last snap points, try
 | 
						|
  // to find a new candidate.
 | 
						|
  if (!x || !y) {
 | 
						|
    nsPoint newPosition =
 | 
						|
        nsPoint(x ? *x : aCurrentPosition.x, y ? *y : aCurrentPosition.y);
 | 
						|
    CalcSnapPoints calcSnapPoints(ScrollUnit::DEVICE_PIXELS,
 | 
						|
                                  ScrollSnapFlags::IntendedEndPosition,
 | 
						|
                                  newPosition, newPosition);
 | 
						|
 | 
						|
    aSnapInfo.ForEachValidTargetFor(
 | 
						|
        newPosition, [&, &x = x, &y = y](const auto& aTarget) -> bool {
 | 
						|
          if (!x && aTarget.mSnapPoint.mX &&
 | 
						|
              aSnapInfo.mScrollSnapStrictnessX !=
 | 
						|
                  StyleScrollSnapStrictness::None) {
 | 
						|
            calcSnapPoints.AddVerticalEdge(aTarget);
 | 
						|
          }
 | 
						|
          if (!y && aTarget.mSnapPoint.mY &&
 | 
						|
              aSnapInfo.mScrollSnapStrictnessY !=
 | 
						|
                  StyleScrollSnapStrictness::None) {
 | 
						|
            calcSnapPoints.AddHorizontalEdge(aTarget);
 | 
						|
          }
 | 
						|
          return true;
 | 
						|
        });
 | 
						|
 | 
						|
    auto finalPos = calcSnapPoints.GetBestEdge(aSnapInfo.mSnapportSize);
 | 
						|
    if (!x) {
 | 
						|
      x = Some(finalPos.mPosition.x);
 | 
						|
    }
 | 
						|
    if (!y) {
 | 
						|
      y = Some(finalPos.mPosition.y);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  SnapDestination snapTarget{nsPoint(*x, *y)};
 | 
						|
  // Collect snap points where the position is still same as the new snap
 | 
						|
  // position.
 | 
						|
  aSnapInfo.ForEachValidTargetFor(
 | 
						|
      snapTarget.mPosition, [&, &x = x, &y = y](const auto& aTarget) -> bool {
 | 
						|
        if (aTarget.mSnapPoint.mX &&
 | 
						|
            aSnapInfo.mScrollSnapStrictnessX !=
 | 
						|
                StyleScrollSnapStrictness::None &&
 | 
						|
            aTarget.mSnapPoint.mX == x) {
 | 
						|
          snapTarget.mTargetIds.mIdsOnX.AppendElement(aTarget.mTargetId);
 | 
						|
        }
 | 
						|
 | 
						|
        if (aTarget.mSnapPoint.mY &&
 | 
						|
            aSnapInfo.mScrollSnapStrictnessY !=
 | 
						|
                StyleScrollSnapStrictness::None &&
 | 
						|
            aTarget.mSnapPoint.mY == y) {
 | 
						|
          snapTarget.mTargetIds.mIdsOnY.AppendElement(aTarget.mTargetId);
 | 
						|
        }
 | 
						|
        return true;
 | 
						|
      });
 | 
						|
  return Some(snapTarget);
 | 
						|
}
 | 
						|
 | 
						|
void ScrollSnapUtils::PostPendingResnapIfNeededFor(nsIFrame* aFrame) {
 | 
						|
  ScrollSnapTargetId id = GetTargetIdFor(aFrame);
 | 
						|
  if (id == ScrollSnapTargetId::None) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  if (ScrollContainerFrame* sf = nsLayoutUtils::GetNearestScrollContainerFrame(
 | 
						|
          aFrame, nsLayoutUtils::SCROLLABLE_SAME_DOC |
 | 
						|
                      nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN)) {
 | 
						|
    sf->PostPendingResnapIfNeeded(aFrame);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void ScrollSnapUtils::PostPendingResnapFor(nsIFrame* aFrame) {
 | 
						|
  if (ScrollContainerFrame* sf = nsLayoutUtils::GetNearestScrollContainerFrame(
 | 
						|
          aFrame, nsLayoutUtils::SCROLLABLE_SAME_DOC |
 | 
						|
                      nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN)) {
 | 
						|
    sf->PostPendingResnap();
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
bool ScrollSnapUtils::NeedsToRespectTargetWritingMode(
 | 
						|
    const nsSize& aSnapAreaSize, const nsSize& aSnapportSize) {
 | 
						|
  // Use the writing-mode on the target element if the snap area is larger than
 | 
						|
  // the snapport.
 | 
						|
  // https://drafts.csswg.org/css-scroll-snap/#snap-scope
 | 
						|
  //
 | 
						|
  // It's unclear `larger` means that the size is larger than only on the target
 | 
						|
  // axis. If it doesn't, it will pick the same axis in the case where only one
 | 
						|
  // axis is larger. For example, if an element size is (200 x 10) and the
 | 
						|
  // snapport size is (100 x 100) and if the element's writing mode is different
 | 
						|
  // from the scroller's writing mode, then `scroll-snap-align: start start`
 | 
						|
  // will be conflict.
 | 
						|
  return aSnapAreaSize.width > aSnapportSize.width ||
 | 
						|
         aSnapAreaSize.height > aSnapportSize.height;
 | 
						|
}
 | 
						|
 | 
						|
static nsRect InflateByScrollMargin(const nsRect& aTargetRect,
 | 
						|
                                    const nsMargin& aScrollMargin,
 | 
						|
                                    const nsRect& aScrolledRect) {
 | 
						|
  // Inflate the rect by scroll-margin.
 | 
						|
  nsRect result = aTargetRect;
 | 
						|
  result.Inflate(aScrollMargin);
 | 
						|
 | 
						|
  // But don't be beyond the limit boundary.
 | 
						|
  return result.Intersect(aScrolledRect);
 | 
						|
}
 | 
						|
 | 
						|
nsRect ScrollSnapUtils::GetSnapAreaFor(const nsIFrame* aFrame,
 | 
						|
                                       const nsIFrame* aScrolledFrame,
 | 
						|
                                       const nsRect& aScrolledRect) {
 | 
						|
  nsRect targetRect = nsLayoutUtils::TransformFrameRectToAncestor(
 | 
						|
      aFrame, aFrame->GetRectRelativeToSelf(), aScrolledFrame);
 | 
						|
 | 
						|
  // The snap area contains scroll-margin values.
 | 
						|
  // https://drafts.csswg.org/css-scroll-snap-1/#scroll-snap-area
 | 
						|
  nsMargin scrollMargin = aFrame->StyleMargin()->GetScrollMargin();
 | 
						|
  return InflateByScrollMargin(targetRect, scrollMargin, aScrolledRect);
 | 
						|
}
 | 
						|
 | 
						|
}  // namespace mozilla
 |