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