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