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