Scroll offset jumps after a programmatic scroll in an overflow container with scroll...
[WebKit-https.git] / Source / WebCore / platform / cocoa / ScrollController.mm
1 /*
2  * Copyright (C) 2011, 2014-2015 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "ScrollController.h"
28
29 #include "LayoutSize.h"
30 #include "PlatformWheelEvent.h"
31 #include "WebCoreSystemInterface.h"
32 #include "WheelEventTestTrigger.h"
33 #include <sys/sysctl.h>
34 #include <sys/time.h>
35 #include <wtf/CurrentTime.h>
36
37 #if ENABLE(CSS_SCROLL_SNAP)
38 #include "ScrollSnapAnimatorState.h"
39 #include "ScrollableArea.h"
40 #endif
41
42 #if ENABLE(RUBBER_BANDING) || ENABLE(CSS_SCROLL_SNAP)
43
44 namespace WebCore {
45
46 #if ENABLE(RUBBER_BANDING)
47 static const float scrollVelocityZeroingTimeout = 0.10f;
48 static const float rubberbandDirectionLockStretchRatio = 1;
49 static const float rubberbandMinimumRequiredDeltaBeforeStretch = 10;
50 #endif
51
52 #if PLATFORM(MAC)
53 static float elasticDeltaForTimeDelta(float initialPosition, float initialVelocity, float elapsedTime)
54 {
55     return wkNSElasticDeltaForTimeDelta(initialPosition, initialVelocity, elapsedTime);
56 }
57
58 static float elasticDeltaForReboundDelta(float delta)
59 {
60     return wkNSElasticDeltaForReboundDelta(delta);
61 }
62
63 static float reboundDeltaForElasticDelta(float delta)
64 {
65     return wkNSReboundDeltaForElasticDelta(delta);
66 }
67
68 static float scrollWheelMultiplier()
69 {
70     static float multiplier = -1;
71     if (multiplier < 0) {
72         multiplier = [[NSUserDefaults standardUserDefaults] floatForKey:@"NSScrollWheelMultiplier"];
73         if (multiplier <= 0)
74             multiplier = 1;
75     }
76     return multiplier;
77 }
78 #endif
79
80 static ScrollEventAxis otherScrollEventAxis(ScrollEventAxis axis)
81 {
82     return axis == ScrollEventAxis::Horizontal ? ScrollEventAxis::Vertical : ScrollEventAxis::Horizontal;
83 }
84
85 ScrollController::ScrollController(ScrollControllerClient& client)
86     : m_client(client)
87 #if ENABLE(RUBBER_BANDING)
88     , m_snapRubberbandTimer(RunLoop::current(), this, &ScrollController::snapRubberBandTimerFired)
89 #endif
90 #if ENABLE(CSS_SCROLL_SNAP) && PLATFORM(MAC)
91     , m_statelessSnapTransitionTimer(RunLoop::current(), this, &ScrollController::statelessSnapTransitionTimerFired)
92     , m_scrollSnapTimer(RunLoop::current(), this, &ScrollController::scrollSnapTimerFired)
93 #endif
94 {
95 }
96
97 #if PLATFORM(MAC)
98 bool ScrollController::handleWheelEvent(const PlatformWheelEvent& wheelEvent)
99 {
100 #if ENABLE(CSS_SCROLL_SNAP)
101     if (!processWheelEventForScrollSnap(wheelEvent))
102         return false;
103 #endif
104     if (wheelEvent.phase() == PlatformWheelEventPhaseBegan) {
105         // First, check if we should rubber-band at all.
106         if (m_client.pinnedInDirection(FloatSize(-wheelEvent.deltaX(), 0))
107             && !shouldRubberBandInHorizontalDirection(wheelEvent))
108             return false;
109
110         m_inScrollGesture = true;
111         m_momentumScrollInProgress = false;
112         m_ignoreMomentumScrolls = false;
113         m_lastMomentumScrollTimestamp = 0;
114         m_momentumVelocity = FloatSize();
115
116         IntSize stretchAmount = m_client.stretchAmount();
117         m_stretchScrollForce.setWidth(reboundDeltaForElasticDelta(stretchAmount.width()));
118         m_stretchScrollForce.setHeight(reboundDeltaForElasticDelta(stretchAmount.height()));
119         m_overflowScrollDelta = FloatSize();
120
121         stopSnapRubberbandTimer();
122
123         return true;
124     }
125
126     if (wheelEvent.phase() == PlatformWheelEventPhaseEnded) {
127         snapRubberBand();
128         return true;
129     }
130
131     bool isMomentumScrollEvent = (wheelEvent.momentumPhase() != PlatformWheelEventPhaseNone);
132     if (m_ignoreMomentumScrolls && (isMomentumScrollEvent || m_snapRubberbandTimerIsActive)) {
133         if (wheelEvent.momentumPhase() == PlatformWheelEventPhaseEnded) {
134             m_ignoreMomentumScrolls = false;
135             return true;
136         }
137         return false;
138     }
139
140     float deltaX = m_overflowScrollDelta.width();
141     float deltaY = m_overflowScrollDelta.height();
142
143     // Reset overflow values because we may decide to remove delta at various points and put it into overflow.
144     m_overflowScrollDelta = FloatSize();
145
146     IntSize stretchAmount = m_client.stretchAmount();
147     bool isVerticallyStretched = stretchAmount.height();
148     bool isHorizontallyStretched = stretchAmount.width();
149
150     float eventCoalescedDeltaX;
151     float eventCoalescedDeltaY;
152
153     if (isVerticallyStretched || isHorizontallyStretched) {
154         eventCoalescedDeltaX = -wheelEvent.unacceleratedScrollingDeltaX();
155         eventCoalescedDeltaY = -wheelEvent.unacceleratedScrollingDeltaY();
156     } else {
157         eventCoalescedDeltaX = -wheelEvent.deltaX();
158         eventCoalescedDeltaY = -wheelEvent.deltaY();
159     }
160
161     deltaX += eventCoalescedDeltaX;
162     deltaY += eventCoalescedDeltaY;
163
164     // Slightly prefer scrolling vertically by applying the = case to deltaY
165     if (fabsf(deltaY) >= fabsf(deltaX))
166         deltaX = 0;
167     else
168         deltaY = 0;
169
170     bool shouldStretch = false;
171
172     PlatformWheelEventPhase momentumPhase = wheelEvent.momentumPhase();
173
174     // If we are starting momentum scrolling then do some setup.
175     if (!m_momentumScrollInProgress && (momentumPhase == PlatformWheelEventPhaseBegan || momentumPhase == PlatformWheelEventPhaseChanged))
176         m_momentumScrollInProgress = true;
177
178     CFTimeInterval timeDelta = wheelEvent.timestamp() - m_lastMomentumScrollTimestamp;
179     if (m_inScrollGesture || m_momentumScrollInProgress) {
180         if (m_lastMomentumScrollTimestamp && timeDelta > 0 && timeDelta < scrollVelocityZeroingTimeout) {
181             m_momentumVelocity.setWidth(eventCoalescedDeltaX / (float)timeDelta);
182             m_momentumVelocity.setHeight(eventCoalescedDeltaY / (float)timeDelta);
183             m_lastMomentumScrollTimestamp = wheelEvent.timestamp();
184         } else {
185             m_lastMomentumScrollTimestamp = wheelEvent.timestamp();
186             m_momentumVelocity = FloatSize();
187         }
188
189         if (isVerticallyStretched) {
190             if (!isHorizontallyStretched && m_client.pinnedInDirection(FloatSize(deltaX, 0))) {
191                 // Stretching only in the vertical.
192                 if (deltaY && (fabsf(deltaX / deltaY) < rubberbandDirectionLockStretchRatio))
193                     deltaX = 0;
194                 else if (fabsf(deltaX) < rubberbandMinimumRequiredDeltaBeforeStretch) {
195                     m_overflowScrollDelta.setWidth(m_overflowScrollDelta.width() + deltaX);
196                     deltaX = 0;
197                 } else
198                     m_overflowScrollDelta.setWidth(m_overflowScrollDelta.width() + deltaX);
199             }
200         } else if (isHorizontallyStretched) {
201             // Stretching only in the horizontal.
202             if (m_client.pinnedInDirection(FloatSize(0, deltaY))) {
203                 if (deltaX && (fabsf(deltaY / deltaX) < rubberbandDirectionLockStretchRatio))
204                     deltaY = 0;
205                 else if (fabsf(deltaY) < rubberbandMinimumRequiredDeltaBeforeStretch) {
206                     m_overflowScrollDelta.setHeight(m_overflowScrollDelta.height() + deltaY);
207                     deltaY = 0;
208                 } else
209                     m_overflowScrollDelta.setHeight(m_overflowScrollDelta.height() + deltaY);
210             }
211         } else {
212             // Not stretching at all yet.
213             if (m_client.pinnedInDirection(FloatSize(deltaX, deltaY))) {
214                 if (fabsf(deltaY) >= fabsf(deltaX)) {
215                     if (fabsf(deltaX) < rubberbandMinimumRequiredDeltaBeforeStretch) {
216                         m_overflowScrollDelta.setWidth(m_overflowScrollDelta.width() + deltaX);
217                         deltaX = 0;
218                     } else
219                         m_overflowScrollDelta.setWidth(m_overflowScrollDelta.width() + deltaX);
220                 }
221                 shouldStretch = true;
222             }
223         }
224     }
225
226     if (deltaX || deltaY) {
227         if (!(shouldStretch || isVerticallyStretched || isHorizontallyStretched)) {
228             if (deltaY) {
229                 deltaY *= scrollWheelMultiplier();
230                 m_client.immediateScrollBy(FloatSize(0, deltaY));
231             }
232             if (deltaX) {
233                 deltaX *= scrollWheelMultiplier();
234                 m_client.immediateScrollBy(FloatSize(deltaX, 0));
235             }
236         } else {
237             if (!m_client.allowsHorizontalStretching(wheelEvent)) {
238                 deltaX = 0;
239                 eventCoalescedDeltaX = 0;
240             } else if (deltaX && !isHorizontallyStretched && !m_client.pinnedInDirection(FloatSize(deltaX, 0))) {
241                 deltaX *= scrollWheelMultiplier();
242
243                 m_client.immediateScrollByWithoutContentEdgeConstraints(FloatSize(deltaX, 0));
244                 deltaX = 0;
245             }
246
247             if (!m_client.allowsVerticalStretching(wheelEvent)) {
248                 deltaY = 0;
249                 eventCoalescedDeltaY = 0;
250             } else if (deltaY && !isVerticallyStretched && !m_client.pinnedInDirection(FloatSize(0, deltaY))) {
251                 deltaY *= scrollWheelMultiplier();
252
253                 m_client.immediateScrollByWithoutContentEdgeConstraints(FloatSize(0, deltaY));
254                 deltaY = 0;
255             }
256
257             IntSize stretchAmount = m_client.stretchAmount();
258
259             if (m_momentumScrollInProgress) {
260                 if ((m_client.pinnedInDirection(FloatSize(eventCoalescedDeltaX, eventCoalescedDeltaY)) || (fabsf(eventCoalescedDeltaX) + fabsf(eventCoalescedDeltaY) <= 0)) && m_lastMomentumScrollTimestamp) {
261                     m_ignoreMomentumScrolls = true;
262                     m_momentumScrollInProgress = false;
263                     snapRubberBand();
264                 }
265             }
266
267             m_stretchScrollForce.setWidth(m_stretchScrollForce.width() + deltaX);
268             m_stretchScrollForce.setHeight(m_stretchScrollForce.height() + deltaY);
269
270             FloatSize dampedDelta(ceilf(elasticDeltaForReboundDelta(m_stretchScrollForce.width())), ceilf(elasticDeltaForReboundDelta(m_stretchScrollForce.height())));
271
272             m_client.immediateScrollByWithoutContentEdgeConstraints(dampedDelta - stretchAmount);
273         }
274     }
275
276     if (m_momentumScrollInProgress && momentumPhase == PlatformWheelEventPhaseEnded) {
277         m_momentumScrollInProgress = false;
278         m_ignoreMomentumScrolls = false;
279         m_lastMomentumScrollTimestamp = 0;
280     }
281
282     return true;
283 }
284 #endif
285
286 #if ENABLE(RUBBER_BANDING)
287 static inline float roundTowardZero(float num)
288 {
289     return num > 0 ? ceilf(num - 0.5f) : floorf(num + 0.5f);
290 }
291
292 static inline float roundToDevicePixelTowardZero(float num)
293 {
294     float roundedNum = roundf(num);
295     if (fabs(num - roundedNum) < 0.125)
296         num = roundedNum;
297
298     return roundTowardZero(num);
299 }
300
301 void ScrollController::snapRubberBandTimerFired()
302 {
303     if (isScrollSnapInProgress())
304         return;
305     
306     if (!m_momentumScrollInProgress || m_ignoreMomentumScrolls) {
307         CFTimeInterval timeDelta = [NSDate timeIntervalSinceReferenceDate] - m_startTime;
308
309         if (m_startStretch == FloatSize()) {
310             m_startStretch = m_client.stretchAmount();
311             if (m_startStretch == FloatSize()) {
312                 stopSnapRubberbandTimer();
313
314                 m_stretchScrollForce = FloatSize();
315                 m_startTime = 0;
316                 m_startStretch = FloatSize();
317                 m_origVelocity = FloatSize();
318                 return;
319             }
320
321             m_origVelocity = m_momentumVelocity;
322
323             // Just like normal scrolling, prefer vertical rubberbanding
324             if (fabsf(m_origVelocity.height()) >= fabsf(m_origVelocity.width()))
325                 m_origVelocity.setWidth(0);
326
327             // Don't rubber-band horizontally if it's not possible to scroll horizontally
328             if (!m_client.canScrollHorizontally())
329                 m_origVelocity.setWidth(0);
330
331             // Don't rubber-band vertically if it's not possible to scroll vertically
332             if (!m_client.canScrollVertically())
333                 m_origVelocity.setHeight(0);
334         }
335
336         FloatPoint delta(roundToDevicePixelTowardZero(elasticDeltaForTimeDelta(m_startStretch.width(), -m_origVelocity.width(), (float)timeDelta)),
337             roundToDevicePixelTowardZero(elasticDeltaForTimeDelta(m_startStretch.height(), -m_origVelocity.height(), (float)timeDelta)));
338
339         if (fabs(delta.x()) >= 1 || fabs(delta.y()) >= 1) {
340             m_client.immediateScrollByWithoutContentEdgeConstraints(FloatSize(delta.x(), delta.y()) - m_client.stretchAmount());
341
342             FloatSize newStretch = m_client.stretchAmount();
343
344             m_stretchScrollForce.setWidth(reboundDeltaForElasticDelta(newStretch.width()));
345             m_stretchScrollForce.setHeight(reboundDeltaForElasticDelta(newStretch.height()));
346         } else {
347             m_client.adjustScrollPositionToBoundsIfNecessary();
348
349             stopSnapRubberbandTimer();
350             m_stretchScrollForce = FloatSize();
351             m_startTime = 0;
352             m_startStretch = FloatSize();
353             m_origVelocity = FloatSize();
354         }
355     } else {
356         m_startTime = [NSDate timeIntervalSinceReferenceDate];
357         m_startStretch = FloatSize();
358         if (!isRubberBandInProgress())
359             stopSnapRubberbandTimer();
360     }
361 }
362 #endif
363
364 bool ScrollController::isRubberBandInProgress() const
365 {
366 #if ENABLE(RUBBER_BANDING) && PLATFORM(MAC)
367     if (!m_inScrollGesture && !m_momentumScrollInProgress && !m_snapRubberbandTimerIsActive)
368         return false;
369
370     return !m_client.stretchAmount().isZero();
371 #else
372     return false;
373 #endif
374 }
375
376 bool ScrollController::isScrollSnapInProgress() const
377 {
378 #if ENABLE(CSS_SCROLL_SNAP) && PLATFORM(MAC)
379     if (m_inScrollGesture || m_momentumScrollInProgress || m_scrollSnapTimer.isActive())
380         return true;
381 #endif
382     return false;
383 }
384
385 #if ENABLE(RUBBER_BANDING)
386 void ScrollController::startSnapRubberbandTimer()
387 {
388     m_client.startSnapRubberbandTimer();
389     m_snapRubberbandTimer.startRepeating(1.0 / 60.0);
390
391     m_client.deferTestsForReason(reinterpret_cast<WheelEventTestTrigger::ScrollableAreaIdentifier>(this), WheelEventTestTrigger::RubberbandInProgress);
392 }
393
394 void ScrollController::stopSnapRubberbandTimer()
395 {
396     m_client.stopSnapRubberbandTimer();
397     m_snapRubberbandTimer.stop();
398     m_snapRubberbandTimerIsActive = false;
399     
400     m_client.removeTestDeferralForReason(reinterpret_cast<WheelEventTestTrigger::ScrollableAreaIdentifier>(this), WheelEventTestTrigger::RubberbandInProgress);
401 }
402
403 void ScrollController::snapRubberBand()
404 {
405     CFTimeInterval timeDelta = [NSProcessInfo processInfo].systemUptime - m_lastMomentumScrollTimestamp;
406     if (m_lastMomentumScrollTimestamp && timeDelta >= scrollVelocityZeroingTimeout)
407         m_momentumVelocity = FloatSize();
408
409     m_inScrollGesture = false;
410
411     if (m_snapRubberbandTimerIsActive)
412         return;
413
414     m_startTime = [NSDate timeIntervalSinceReferenceDate];
415     m_startStretch = FloatSize();
416     m_origVelocity = FloatSize();
417
418     startSnapRubberbandTimer();
419     m_snapRubberbandTimerIsActive = true;
420 }
421
422 bool ScrollController::shouldRubberBandInHorizontalDirection(const PlatformWheelEvent& wheelEvent)
423 {
424     if (wheelEvent.deltaX() > 0)
425         return m_client.shouldRubberBandInDirection(ScrollLeft);
426     if (wheelEvent.deltaX() < 0)
427         return m_client.shouldRubberBandInDirection(ScrollRight);
428
429     return true;
430 }
431 #endif
432
433 #if ENABLE(CSS_SCROLL_SNAP)
434
435 #if PLATFORM(MAC)
436 static inline WheelEventStatus toWheelEventStatus(PlatformWheelEventPhase phase, PlatformWheelEventPhase momentumPhase)
437 {
438     if (phase == PlatformWheelEventPhaseNone) {
439         switch (momentumPhase) {
440         case PlatformWheelEventPhaseBegan:
441             return WheelEventStatus::InertialScrollBegin;
442                 
443         case PlatformWheelEventPhaseChanged:
444             return WheelEventStatus::InertialScrolling;
445                 
446         case PlatformWheelEventPhaseEnded:
447             return WheelEventStatus::InertialScrollEnd;
448
449         case PlatformWheelEventPhaseNone:
450             return WheelEventStatus::StatelessScrollEvent;
451
452         default:
453             return WheelEventStatus::Unknown;
454         }
455     }
456     if (momentumPhase == PlatformWheelEventPhaseNone) {
457         switch (phase) {
458         case PlatformWheelEventPhaseBegan:
459         case PlatformWheelEventPhaseMayBegin:
460             return WheelEventStatus::UserScrollBegin;
461                 
462         case PlatformWheelEventPhaseChanged:
463             return WheelEventStatus::UserScrolling;
464                 
465         case PlatformWheelEventPhaseEnded:
466         case PlatformWheelEventPhaseCancelled:
467             return WheelEventStatus::UserScrollEnd;
468                 
469         default:
470             return WheelEventStatus::Unknown;
471         }
472     }
473     return WheelEventStatus::Unknown;
474 }
475
476 bool ScrollController::shouldOverrideInertialScrolling() const
477 {
478     if (!m_scrollSnapState)
479         return false;
480
481     ScrollSnapState scrollSnapState = m_scrollSnapState->currentState();
482     return scrollSnapState == ScrollSnapState::Gliding || scrollSnapState == ScrollSnapState::DestinationReached;
483 }
484
485 void ScrollController::scheduleStatelessScrollSnap()
486 {
487     stopScrollSnapTimer();
488     m_statelessSnapTransitionTimer.stop();
489     if (!m_scrollSnapState)
490         return;
491
492     static const Seconds statelessScrollSnapDelay = 750_ms;
493     m_statelessSnapTransitionTimer.startOneShot(statelessScrollSnapDelay.value());
494     startDeferringTestsDueToScrollSnapping();
495 }
496
497 void ScrollController::statelessSnapTransitionTimerFired()
498 {
499     if (!m_scrollSnapState)
500         return;
501
502     m_scrollSnapState->transitionToSnapAnimationState(m_client.scrollExtent(), m_client.viewportSize(), m_client.pageScaleFactor(), m_client.scrollOffset());
503     startScrollSnapTimer();
504 }
505
506 void ScrollController::startDeferringTestsDueToScrollSnapping()
507 {
508     m_client.deferTestsForReason(reinterpret_cast<WheelEventTestTrigger::ScrollableAreaIdentifier>(this), WheelEventTestTrigger::ScrollSnapInProgress);
509 }
510
511 void ScrollController::stopDeferringTestsDueToScrollSnapping()
512 {
513     m_client.removeTestDeferralForReason(reinterpret_cast<WheelEventTestTrigger::ScrollableAreaIdentifier>(this), WheelEventTestTrigger::ScrollSnapInProgress);
514 }
515
516 bool ScrollController::processWheelEventForScrollSnap(const PlatformWheelEvent& wheelEvent)
517 {
518     if (!m_scrollSnapState)
519         return true;
520
521     if (m_scrollSnapState->snapOffsetsForAxis(ScrollEventAxis::Horizontal).isEmpty() && m_scrollSnapState->snapOffsetsForAxis(ScrollEventAxis::Vertical).isEmpty())
522         return true;
523
524     WheelEventStatus status = toWheelEventStatus(wheelEvent.phase(), wheelEvent.momentumPhase());
525     bool isInertialScrolling = false;
526     switch (status) {
527     case WheelEventStatus::UserScrollBegin:
528     case WheelEventStatus::UserScrolling:
529         stopScrollSnapTimer();
530         m_scrollSnapState->transitionToUserInteractionState();
531         m_dragEndedScrollingVelocity = -wheelEvent.scrollingVelocity();
532         break;
533     case WheelEventStatus::UserScrollEnd:
534         m_scrollSnapState->transitionToSnapAnimationState(m_client.scrollExtent(), m_client.viewportSize(), m_client.pageScaleFactor(), m_client.scrollOffset());
535         startScrollSnapTimer();
536         break;
537     case WheelEventStatus::InertialScrollBegin:
538         m_scrollSnapState->transitionToGlideAnimationState(m_client.scrollExtent(), m_client.viewportSize(), m_client.pageScaleFactor(), m_client.scrollOffset(), m_dragEndedScrollingVelocity, FloatSize(-wheelEvent.deltaX(), -wheelEvent.deltaY()));
539         m_dragEndedScrollingVelocity = { };
540         isInertialScrolling = true;
541         break;
542     case WheelEventStatus::InertialScrolling:
543     case WheelEventStatus::InertialScrollEnd:
544         isInertialScrolling = true;
545         break;
546     case WheelEventStatus::StatelessScrollEvent:
547         m_scrollSnapState->transitionToUserInteractionState();
548         scheduleStatelessScrollSnap();
549         break;
550     case WheelEventStatus::Unknown:
551         ASSERT_NOT_REACHED();
552         break;
553     }
554
555     return !(isInertialScrolling && shouldOverrideInertialScrolling());
556 }
557
558 void ScrollController::startScrollSnapTimer()
559 {
560     if (m_scrollSnapTimer.isActive())
561         return;
562
563     startDeferringTestsDueToScrollSnapping();
564     m_client.startScrollSnapTimer();
565     m_scrollSnapTimer.startRepeating(1.0 / 60.0);
566 }
567
568 void ScrollController::stopScrollSnapTimer()
569 {
570     if (!m_scrollSnapTimer.isActive())
571         return;
572
573     stopDeferringTestsDueToScrollSnapping();
574     m_client.stopScrollSnapTimer();
575     m_scrollSnapTimer.stop();
576 }
577
578 void ScrollController::scrollSnapTimerFired()
579 {
580     if (!m_scrollSnapState) {
581         ASSERT_NOT_REACHED();
582         return;
583     }
584
585     bool isAnimationComplete;
586     auto animationOffset = m_scrollSnapState->currentAnimatedScrollOffset(isAnimationComplete);
587     auto currentOffset = m_client.scrollOffset();
588     m_client.immediateScrollByWithoutContentEdgeConstraints(FloatSize(animationOffset.x() - currentOffset.x(), animationOffset.y() - currentOffset.y()));
589     if (isAnimationComplete) {
590         m_scrollSnapState->transitionToDestinationReachedState();
591         stopScrollSnapTimer();
592     }
593 }
594 #endif // PLATFORM(MAC)
595
596 void ScrollController::updateScrollSnapState(const ScrollableArea& scrollableArea)
597 {
598     if (auto* snapOffsets = scrollableArea.horizontalSnapOffsets()) {
599         if (auto* snapOffsetRanges = scrollableArea.horizontalSnapOffsetRanges())
600             updateScrollSnapPoints(ScrollEventAxis::Horizontal, *snapOffsets, *snapOffsetRanges);
601         else
602             updateScrollSnapPoints(ScrollEventAxis::Horizontal, *snapOffsets, { });
603     } else
604         updateScrollSnapPoints(ScrollEventAxis::Horizontal, { }, { });
605
606     if (auto* snapOffsets = scrollableArea.verticalSnapOffsets()) {
607         if (auto* snapOffsetRanges = scrollableArea.verticalSnapOffsetRanges())
608             updateScrollSnapPoints(ScrollEventAxis::Vertical, *snapOffsets, *snapOffsetRanges);
609         else
610             updateScrollSnapPoints(ScrollEventAxis::Vertical, *snapOffsets, { });
611     } else
612         updateScrollSnapPoints(ScrollEventAxis::Vertical, { }, { });
613 }
614
615 void ScrollController::updateScrollSnapPoints(ScrollEventAxis axis, const Vector<LayoutUnit>& snapPoints, const Vector<ScrollOffsetRange<LayoutUnit>>& snapRanges)
616 {
617     if (!m_scrollSnapState) {
618         if (snapPoints.isEmpty())
619             return;
620
621         m_scrollSnapState = std::make_unique<ScrollSnapAnimatorState>();
622     }
623
624     if (snapPoints.isEmpty() && m_scrollSnapState->snapOffsetsForAxis(otherScrollEventAxis(axis)).isEmpty())
625         m_scrollSnapState = nullptr;
626     else
627         m_scrollSnapState->setSnapOffsetsAndPositionRangesForAxis(axis, snapPoints, snapRanges);
628 }
629
630 unsigned ScrollController::activeScrollSnapIndexForAxis(ScrollEventAxis axis) const
631 {
632     if (!m_scrollSnapState)
633         return 0;
634
635     return m_scrollSnapState->activeSnapIndexForAxis(axis);
636 }
637
638 void ScrollController::setActiveScrollSnapIndexForAxis(ScrollEventAxis axis, unsigned index)
639 {
640     if (!m_scrollSnapState)
641         return;
642
643     m_scrollSnapState->setActiveSnapIndexForAxis(axis, index);
644 }
645
646 void ScrollController::setNearestScrollSnapIndexForAxisAndOffset(ScrollEventAxis axis, int offset)
647 {
648     if (!m_scrollSnapState)
649         return;
650
651     float scaleFactor = m_client.pageScaleFactor();
652     ScrollSnapAnimatorState& snapState = *m_scrollSnapState;
653
654     auto snapOffsets = snapState.snapOffsetsForAxis(axis);
655     if (!snapOffsets.size())
656         return;
657
658     LayoutUnit clampedOffset = std::min(std::max(LayoutUnit(offset / scaleFactor), snapOffsets.first()), snapOffsets.last());
659
660     unsigned activeIndex = 0;
661     closestSnapOffset(snapState.snapOffsetsForAxis(axis), snapState.snapOffsetRangesForAxis(axis), clampedOffset, 0, activeIndex);
662
663     if (activeIndex == activeScrollSnapIndexForAxis(axis))
664         return;
665
666     m_activeScrollSnapIndexDidChange = true;
667     setActiveScrollSnapIndexForAxis(axis, activeIndex);
668 }
669
670 void ScrollController::setActiveScrollSnapIndicesForOffset(int x, int y)
671 {
672     if (!m_scrollSnapState)
673         return;
674
675     setNearestScrollSnapIndexForAxisAndOffset(ScrollEventAxis::Horizontal, x);
676     setNearestScrollSnapIndexForAxisAndOffset(ScrollEventAxis::Vertical, y);
677 }
678 #endif
679
680 } // namespace WebCore
681
682 #endif // ENABLE(RUBBER_BANDING)