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