Commit 098b8bc1 authored by Hiroyuki Ikezoe's avatar Hiroyuki Ikezoe
Browse files

Bug 1766805 - Introduce intended direction and intended end position concepts. r=botond

The scroll snap spec defines the concepts [1]. There are three type of scroll
operations. 1) intended end position, 2) intended direction and end position
and 3) intended direction.

Basically our existing ScrollUnits types correspond;

1) DEVICE_PIXELS, WHOLE => intended end position
2) PAGES => intended direction and end position
3) LINES => intended direction

There are two exceptions in the `intended direction and end position` case,
scrollBy() and fling gestures (on Linux). They were defined as scroll operations
with DEVICE_PIXELS unit, but the spec cleary says they are `intended direction
and end position` operations.

Note that we will also use this information for scroll-snap-stop property since
the properly will only have effects on both 2) and 3) cases.

[1] https://drafts.csswg.org/css-scroll-snap/#scroll-types

Depends on D145190

Differential Revision: https://phabricator.services.mozilla.com/D145191
parent fbbbe95e
Loading
Loading
Loading
Loading
+5 −5
Original line number Diff line number Diff line
@@ -2912,17 +2912,17 @@ void EventStateManager::DoScrollText(nsIScrollableFrame* aScrollableFrame,
    actualDevPixelScrollAmount.y = 0;
  }

  nsIScrollbarMediator::ScrollSnapMode snapMode =
      nsIScrollbarMediator::DISABLE_SNAP;
  ScrollSnapFlags snapFlags = ScrollSnapFlags::Disabled;
  mozilla::ScrollOrigin origin = mozilla::ScrollOrigin::NotSpecified;
  switch (aEvent->mDeltaMode) {
    case WheelEvent_Binding::DOM_DELTA_LINE:
      origin = mozilla::ScrollOrigin::MouseWheel;
      snapMode = nsIScrollableFrame::ENABLE_SNAP;
      snapFlags = ScrollSnapFlags::IntendedDirection;
      break;
    case WheelEvent_Binding::DOM_DELTA_PAGE:
      origin = mozilla::ScrollOrigin::Pages;
      snapMode = nsIScrollableFrame::ENABLE_SNAP;
      snapFlags = ScrollSnapFlags::IntendedDirection |
                  ScrollSnapFlags::IntendedEndPosition;
      break;
    case WheelEvent_Binding::DOM_DELTA_PIXEL:
      origin = mozilla::ScrollOrigin::Pixels;
@@ -2984,7 +2984,7 @@ void EventStateManager::DoScrollText(nsIScrollableFrame* aScrollableFrame,
  nsIntPoint overflow;
  aScrollableFrame->ScrollBy(actualDevPixelScrollAmount,
                             ScrollUnit::DEVICE_PIXELS, mode, &overflow, origin,
                             momentum, snapMode);
                             momentum, snapFlags);

  if (!scrollFrameWeak.IsAlive()) {
    // If the scroll causes changing the layout, we can think that the event
+57 −19
Original line number Diff line number Diff line
@@ -1773,7 +1773,7 @@ nsEventStatus AsyncPanZoomController::OnScaleEnd(
      }
      // Along with clearing the overscroll, we also want to snap to the nearest
      // snap point as appropriate.
      ScrollSnap();
      ScrollSnap(ScrollSnapFlags::IntendedEndPosition);
    } else {
      // when zoom is not allowed
      EndTouch(aEvent.mTimeStamp);
@@ -1975,8 +1975,8 @@ nsEventStatus AsyncPanZoomController::OnKeyboard(const KeyboardInput& aEvent) {
  CSSPoint destination = GetKeyboardDestination(aEvent.mAction);
  ScrollOrigin scrollOrigin =
      SmoothScrollAnimation::GetScrollOriginForAction(aEvent.mAction.mType);
  bool scrollSnapped =
      MaybeAdjustDestinationForScrollSnapping(aEvent, destination);
  bool scrollSnapped = MaybeAdjustDestinationForScrollSnapping(
      aEvent, destination, GetScrollSnapFlagsForKeyboardAction(aEvent.mAction));
  ScrollMode scrollMode = apz::GetScrollModeForOrigin(scrollOrigin);

  RecordScrollPayload(aEvent.mTimeStamp);
@@ -2127,6 +2127,20 @@ CSSPoint AsyncPanZoomController::GetKeyboardDestination(
  return scrollDestination;
}

ScrollSnapFlags AsyncPanZoomController::GetScrollSnapFlagsForKeyboardAction(
    const KeyboardScrollAction& aAction) const {
  switch (aAction.mType) {
    case KeyboardScrollAction::eScrollCharacter:
    case KeyboardScrollAction::eScrollLine:
      return ScrollSnapFlags::IntendedDirection;
    case KeyboardScrollAction::eScrollPage:
      return ScrollSnapFlags::IntendedDirection |
             ScrollSnapFlags::IntendedEndPosition;
    case KeyboardScrollAction::eScrollComplete:
      return ScrollSnapFlags::IntendedEndPosition;
  }
}

ParentLayerPoint AsyncPanZoomController::GetDeltaForEvent(
    const InputData& aEvent) const {
  ParentLayerPoint delta;
@@ -3945,7 +3959,7 @@ void AsyncPanZoomController::CancelAnimation(CancelAnimationFlags aFlags) {
  // Similar to relieving overscroll, we also need to snap to any snap points
  // if appropriate.
  if (aFlags & CancelAnimationFlags::ScrollSnap) {
    ScrollSnap();
    ScrollSnap(ScrollSnapFlags::IntendedEndPosition);
  }
  if (repaint) {
    RequestContentRepaint();
@@ -4230,7 +4244,8 @@ bool AsyncPanZoomController::SnapBackIfOverscrolled() {
  // main thread to snap to any nearby snap points, assuming we haven't already
  // done so when we started this fling
  if (mState != FLING) {
    ScrollSnap();
    ScrollSnap(ScrollSnapFlags::IntendedEndPosition |
               ScrollSnapFlags::IntendedDirection);
  }
  return false;
}
@@ -5993,13 +6008,14 @@ void AsyncPanZoomController::SetTestAsyncZoom(
}

Maybe<CSSPoint> AsyncPanZoomController::FindSnapPointNear(
    const CSSPoint& aDestination, ScrollUnit aUnit) {
    const CSSPoint& aDestination, ScrollUnit aUnit,
    ScrollSnapFlags aSnapFlags) {
  mRecursiveMutex.AssertCurrentThreadIn();
  APZC_LOG("%p scroll snapping near %s\n", this,
           ToString(aDestination).c_str());
  CSSRect scrollRange = Metrics().CalculateScrollRange();
  if (Maybe<nsPoint> snapPoint = ScrollSnapUtils::GetSnapPointForDestination(
          mScrollMetadata.GetSnapInfo(), aUnit,
          mScrollMetadata.GetSnapInfo(), aUnit, aSnapFlags,
          CSSRect::ToAppUnits(scrollRange),
          CSSPoint::ToAppUnits(Metrics().GetVisualScrollOffset()),
          CSSPoint::ToAppUnits(aDestination))) {
@@ -6013,9 +6029,10 @@ Maybe<CSSPoint> AsyncPanZoomController::FindSnapPointNear(
  return Nothing();
}

void AsyncPanZoomController::ScrollSnapNear(const CSSPoint& aDestination) {
  if (Maybe<CSSPoint> snapPoint =
          FindSnapPointNear(aDestination, ScrollUnit::DEVICE_PIXELS)) {
void AsyncPanZoomController::ScrollSnapNear(const CSSPoint& aDestination,
                                            ScrollSnapFlags aSnapFlags) {
  if (Maybe<CSSPoint> snapPoint = FindSnapPointNear(
          aDestination, ScrollUnit::DEVICE_PIXELS, aSnapFlags)) {
    if (*snapPoint != Metrics().GetVisualScrollOffset()) {
      APZC_LOG("%p smooth scrolling to snap point %s\n", this,
               ToString(*snapPoint).c_str());
@@ -6024,9 +6041,9 @@ void AsyncPanZoomController::ScrollSnapNear(const CSSPoint& aDestination) {
  }
}

void AsyncPanZoomController::ScrollSnap() {
void AsyncPanZoomController::ScrollSnap(ScrollSnapFlags aSnapFlags) {
  RecursiveMutexAutoLock lock(mRecursiveMutex);
  ScrollSnapNear(Metrics().GetVisualScrollOffset());
  ScrollSnapNear(Metrics().GetVisualScrollOffset(), aSnapFlags);
}

void AsyncPanZoomController::ScrollSnapToDestination() {
@@ -6054,7 +6071,11 @@ void AsyncPanZoomController::ScrollSnapToDestination() {
  }

  CSSPoint startPosition = Metrics().GetVisualScrollOffset();
  if (MaybeAdjustDeltaForScrollSnapping(ScrollUnit::DEVICE_PIXELS,
  ScrollSnapFlags snapFlags = ScrollSnapFlags::IntendedEndPosition;
  if (predictedDelta != ParentLayerPoint()) {
    snapFlags |= ScrollSnapFlags::IntendedDirection;
  }
  if (MaybeAdjustDeltaForScrollSnapping(ScrollUnit::DEVICE_PIXELS, snapFlags,
                                        predictedDelta, startPosition)) {
    APZC_LOG(
        "%p fling snapping.  friction: %f velocity: %f, %f "
@@ -6070,7 +6091,8 @@ void AsyncPanZoomController::ScrollSnapToDestination() {
}

bool AsyncPanZoomController::MaybeAdjustDeltaForScrollSnapping(
    ScrollUnit aUnit, ParentLayerPoint& aDelta, CSSPoint& aStartPosition) {
    ScrollUnit aUnit, ScrollSnapFlags aSnapFlags, ParentLayerPoint& aDelta,
    CSSPoint& aStartPosition) {
  RecursiveMutexAutoLock lock(mRecursiveMutex);
  CSSToParentLayerScale zoom = Metrics().GetZoom();
  if (zoom == CSSToParentLayerScale(0)) {
@@ -6079,7 +6101,8 @@ bool AsyncPanZoomController::MaybeAdjustDeltaForScrollSnapping(
  CSSPoint destination = Metrics().CalculateScrollRange().ClampPoint(
      aStartPosition + (aDelta / zoom));

  if (Maybe<CSSPoint> snapPoint = FindSnapPointNear(destination, aUnit)) {
  if (Maybe<CSSPoint> snapPoint =
          FindSnapPointNear(destination, aUnit, aSnapFlags)) {
    aDelta = (*snapPoint - aStartPosition) * zoom;
    aStartPosition = *snapPoint;
    return true;
@@ -6096,17 +6119,32 @@ bool AsyncPanZoomController::MaybeAdjustDeltaForScrollSnappingOnWheelInput(
    return false;
  }

  // Note that this MaybeAdjustDeltaForScrollSnappingOnWheelInput also gets
  // called for pan gestures at least on older Mac and Windows. In such cases
  // `aEvent.mDeltaType` is `SCROLLDELTA_PIXEL` which should be filtered out by
  // the above `if` block, so we assume all incoming `aEvent` are purely wheel
  // events, thus we basically use `IntendedDirection` here.
  // If we want to change the behavior, i.e. we want to do scroll snap for
  // such cases as well, we need to use `IntendedEndPoint`.
  ScrollSnapFlags snapFlags = ScrollSnapFlags::IntendedDirection;
  if (aEvent.mDeltaType == ScrollWheelInput::SCROLLDELTA_PAGE) {
    // On Windows there are a couple of cases where scroll events happen with
    // SCROLLDELTA_PAGE, in such case we consider it's a page scroll.
    snapFlags |= ScrollSnapFlags::IntendedEndPosition;
  }
  return MaybeAdjustDeltaForScrollSnapping(
      ScrollWheelInput::ScrollUnitForDeltaType(aEvent.mDeltaType), aDelta,
      aStartPosition);
      ScrollWheelInput::ScrollUnitForDeltaType(aEvent.mDeltaType),
      ScrollSnapFlags::IntendedDirection, aDelta, aStartPosition);
}

bool AsyncPanZoomController::MaybeAdjustDestinationForScrollSnapping(
    const KeyboardInput& aEvent, CSSPoint& aDestination) {
    const KeyboardInput& aEvent, CSSPoint& aDestination,
    ScrollSnapFlags aSnapFlags) {
  RecursiveMutexAutoLock lock(mRecursiveMutex);
  ScrollUnit unit = KeyboardScrollAction::GetScrollUnit(aEvent.mAction.mType);

  if (Maybe<CSSPoint> snapPoint = FindSnapPointNear(aDestination, unit)) {
  if (Maybe<CSSPoint> snapPoint =
          FindSnapPointNear(aDestination, unit, aSnapFlags)) {
    aDestination = *snapPoint;
    return true;
  }
+12 −4
Original line number Diff line number Diff line
@@ -689,6 +689,11 @@ class AsyncPanZoomController {

  CSSPoint GetKeyboardDestination(const KeyboardScrollAction& aAction) const;

  // Returns the corresponding ScrollSnapFlags for the given |aAction|.
  // See https://drafts.csswg.org/css-scroll-snap/#scroll-types
  ScrollSnapFlags GetScrollSnapFlagsForKeyboardAction(
      const KeyboardScrollAction& aAction) const;

  /**
   * Helper methods for long press gestures.
   */
@@ -1785,6 +1790,7 @@ class AsyncPanZoomController {
  // GetSnapPointForDestination).
  // Returns true iff. a target snap point was found.
  bool MaybeAdjustDeltaForScrollSnapping(ScrollUnit aUnit,
                                         ScrollSnapFlags aFlags,
                                         ParentLayerPoint& aDelta,
                                         CSSPoint& aStartPosition);

@@ -1795,17 +1801,18 @@ class AsyncPanZoomController {
      CSSPoint& aStartPosition);

  bool MaybeAdjustDestinationForScrollSnapping(const KeyboardInput& aEvent,
                                               CSSPoint& aDestination);
                                               CSSPoint& aDestination,
                                               ScrollSnapFlags aSnapFlags);

  // Snap to a snap position nearby the current scroll position, if appropriate.
  void ScrollSnap();
  void ScrollSnap(ScrollSnapFlags aSnapFlags);

  // Snap to a snap position nearby the destination predicted based on the
  // current velocity, if appropriate.
  void ScrollSnapToDestination();

  // Snap to a snap position nearby the provided destination, if appropriate.
  void ScrollSnapNear(const CSSPoint& aDestination);
  void ScrollSnapNear(const CSSPoint& aDestination, ScrollSnapFlags aSnapFlags);

  // Find a snap point near |aDestination| that we should snap to.
  // Returns the snap point if one was found, or an empty Maybe otherwise.
@@ -1813,7 +1820,8 @@ class AsyncPanZoomController {
  // GetSnapPointForDestination). It should generally be determined by the
  // type of event that's triggering the scroll.
  Maybe<CSSPoint> FindSnapPointNear(const CSSPoint& aDestination,
                                    ScrollUnit aUnit);
                                    ScrollUnit aUnit,
                                    ScrollSnapFlags aSnapFlags);

  friend std::ostream& operator<<(
      std::ostream& aOut, const AsyncPanZoomController::PanZoomState& aState);
+5 −3
Original line number Diff line number Diff line
@@ -59,9 +59,11 @@ class OverscrollAnimation : public AsyncPanZoomAnimation {
      // done in a deferred task, otherwise the state change to NOTHING caused
      // by the overscroll animation ending would clobber a possible state
      // change to SMOOTH_SCROLL in ScrollSnap().
      mDeferredTasks.AppendElement(
          NewRunnableMethod("layers::AsyncPanZoomController::ScrollSnap",
                            &mApzc, &AsyncPanZoomController::ScrollSnap));
      mDeferredTasks.AppendElement(NewRunnableMethod<ScrollSnapFlags>(
          "layers::AsyncPanZoomController::ScrollSnap", &mApzc,
          &AsyncPanZoomController::ScrollSnap,
          ScrollSnapFlags::IntendedDirection |
              ScrollSnapFlags::IntendedEndPosition));
      return false;
    }
    return true;
+11 −9
Original line number Diff line number Diff line
@@ -2387,10 +2387,12 @@ PresShell::ScrollPage(bool aForward) {
      GetScrollableFrameToScroll(VerticalScrollDirection);
  ScrollMode scrollMode = apz::GetScrollModeForOrigin(ScrollOrigin::Pages);
  if (scrollFrame) {
    scrollFrame->ScrollBy(
        nsIntPoint(0, aForward ? 1 : -1), ScrollUnit::PAGES, scrollMode,
        nullptr, mozilla::ScrollOrigin::NotSpecified,
        nsIScrollableFrame::NOT_MOMENTUM, nsIScrollableFrame::ENABLE_SNAP);
    scrollFrame->ScrollBy(nsIntPoint(0, aForward ? 1 : -1), ScrollUnit::PAGES,
                          scrollMode, nullptr,
                          mozilla::ScrollOrigin::NotSpecified,
                          nsIScrollableFrame::NOT_MOMENTUM,
                          ScrollSnapFlags::IntendedDirection |
                              ScrollSnapFlags::IntendedEndPosition);
  }
  return NS_OK;
}
@@ -2407,7 +2409,7 @@ PresShell::ScrollLine(bool aForward) {
    scrollFrame->ScrollBy(
        nsIntPoint(0, aForward ? lineCount : -lineCount), ScrollUnit::LINES,
        scrollMode, nullptr, mozilla::ScrollOrigin::NotSpecified,
        nsIScrollableFrame::NOT_MOMENTUM, nsIScrollableFrame::ENABLE_SNAP);
        nsIScrollableFrame::NOT_MOMENTUM, ScrollSnapFlags::IntendedDirection);
  }
  return NS_OK;
}
@@ -2424,7 +2426,7 @@ PresShell::ScrollCharacter(bool aRight) {
    scrollFrame->ScrollBy(
        nsIntPoint(aRight ? h : -h, 0), ScrollUnit::LINES, scrollMode, nullptr,
        mozilla::ScrollOrigin::NotSpecified, nsIScrollableFrame::NOT_MOMENTUM,
        nsIScrollableFrame::ENABLE_SNAP);
        ScrollSnapFlags::IntendedDirection);
  }
  return NS_OK;
}
@@ -2438,7 +2440,7 @@ PresShell::CompleteScroll(bool aForward) {
    scrollFrame->ScrollBy(
        nsIntPoint(0, aForward ? 1 : -1), ScrollUnit::WHOLE, scrollMode,
        nullptr, mozilla::ScrollOrigin::NotSpecified,
        nsIScrollableFrame::NOT_MOMENTUM, nsIScrollableFrame::ENABLE_SNAP);
        nsIScrollableFrame::NOT_MOMENTUM, ScrollSnapFlags::IntendedEndPosition);
  }
  return NS_OK;
}
@@ -3570,8 +3572,8 @@ static void ScrollToShowRect(nsIScrollableFrame* aFrameAsScrollable,
  AutoWeakFrame weakFrame(frame);
  aFrameAsScrollable->ScrollTo(scrollPt, scrollMode, &allowedRange,
                               aScrollFlags & ScrollFlags::ScrollSnap
                                   ? nsIScrollbarMediator::ENABLE_SNAP
                                   : nsIScrollbarMediator::DISABLE_SNAP,
                                   ? ScrollSnapFlags::IntendedEndPosition
                                   : ScrollSnapFlags::Disabled,
                               aScrollFlags & ScrollFlags::TriggeredByScript
                                   ? ScrollTriggeredByScript::Yes
                                   : ScrollTriggeredByScript::No);
Loading