cd6fdbe2097bba5744d81fa88e47094aca26270c
[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 ENABLE(CSS_SCROLL_SNAP) && PLATFORM(MAC)
78 static const float inertialScrollPredictionFactor = 16.7;
79 static const double statelessScrollSnapDelay = 0.5;
80 #endif
81
82 #if PLATFORM(MAC)
83 enum class WheelEventStatus {
84     UserScrollBegin,
85     UserScrolling,
86     UserScrollEnd,
87     InertialScrollBegin,
88     InertialScrolling,
89     InertialScrollEnd,
90     StatelessScrollEvent,
91     Unknown
92 };
93
94 static float elasticDeltaForTimeDelta(float initialPosition, float initialVelocity, float elapsedTime)
95 {
96     return wkNSElasticDeltaForTimeDelta(initialPosition, initialVelocity, elapsedTime);
97 }
98
99 static float elasticDeltaForReboundDelta(float delta)
100 {
101     return wkNSElasticDeltaForReboundDelta(delta);
102 }
103
104 static float reboundDeltaForElasticDelta(float delta)
105 {
106     return wkNSReboundDeltaForElasticDelta(delta);
107 }
108
109 static float scrollWheelMultiplier()
110 {
111     static float multiplier = -1;
112     if (multiplier < 0) {
113         multiplier = [[NSUserDefaults standardUserDefaults] floatForKey:@"NSScrollWheelMultiplier"];
114         if (multiplier <= 0)
115             multiplier = 1;
116     }
117     return multiplier;
118 }
119 #endif
120
121 ScrollController::ScrollController(ScrollControllerClient& client)
122     : m_client(client)
123 #if ENABLE(RUBBER_BANDING)
124     , m_snapRubberbandTimer(RunLoop::current(), this, &ScrollController::snapRubberBandTimerFired)
125 #endif
126 #if ENABLE(CSS_SCROLL_SNAP) && PLATFORM(MAC)
127     , m_scrollSnapTimer(RunLoop::current(), this, &ScrollController::scrollSnapTimerFired)
128 #endif
129 {
130 }
131
132 #if PLATFORM(MAC)
133 bool ScrollController::handleWheelEvent(const PlatformWheelEvent& wheelEvent)
134 {
135 #if ENABLE(CSS_SCROLL_SNAP)
136     if (!processWheelEventForScrollSnap(wheelEvent))
137         return false;
138 #endif
139     if (wheelEvent.phase() == PlatformWheelEventPhaseBegan) {
140         // First, check if we should rubber-band at all.
141         if (m_client.pinnedInDirection(FloatSize(-wheelEvent.deltaX(), 0))
142             && !shouldRubberBandInHorizontalDirection(wheelEvent))
143             return false;
144
145         m_inScrollGesture = true;
146         m_momentumScrollInProgress = false;
147         m_ignoreMomentumScrolls = false;
148         m_lastMomentumScrollTimestamp = 0;
149         m_momentumVelocity = FloatSize();
150
151         IntSize stretchAmount = m_client.stretchAmount();
152         m_stretchScrollForce.setWidth(reboundDeltaForElasticDelta(stretchAmount.width()));
153         m_stretchScrollForce.setHeight(reboundDeltaForElasticDelta(stretchAmount.height()));
154         m_overflowScrollDelta = FloatSize();
155
156         stopSnapRubberbandTimer();
157
158         return true;
159     }
160
161     if (wheelEvent.phase() == PlatformWheelEventPhaseEnded) {
162         snapRubberBand();
163         return true;
164     }
165
166     bool isMomentumScrollEvent = (wheelEvent.momentumPhase() != PlatformWheelEventPhaseNone);
167     if (m_ignoreMomentumScrolls && (isMomentumScrollEvent || m_snapRubberbandTimerIsActive)) {
168         if (wheelEvent.momentumPhase() == PlatformWheelEventPhaseEnded) {
169             m_ignoreMomentumScrolls = false;
170             return true;
171         }
172         return false;
173     }
174
175     float deltaX = m_overflowScrollDelta.width();
176     float deltaY = m_overflowScrollDelta.height();
177
178     // Reset overflow values because we may decide to remove delta at various points and put it into overflow.
179     m_overflowScrollDelta = FloatSize();
180
181     IntSize stretchAmount = m_client.stretchAmount();
182     bool isVerticallyStretched = stretchAmount.height();
183     bool isHorizontallyStretched = stretchAmount.width();
184
185     float eventCoalescedDeltaX;
186     float eventCoalescedDeltaY;
187
188     if (isVerticallyStretched || isHorizontallyStretched) {
189         eventCoalescedDeltaX = -wheelEvent.unacceleratedScrollingDeltaX();
190         eventCoalescedDeltaY = -wheelEvent.unacceleratedScrollingDeltaY();
191     } else {
192         eventCoalescedDeltaX = -wheelEvent.deltaX();
193         eventCoalescedDeltaY = -wheelEvent.deltaY();
194     }
195
196     deltaX += eventCoalescedDeltaX;
197     deltaY += eventCoalescedDeltaY;
198
199     // Slightly prefer scrolling vertically by applying the = case to deltaY
200     if (fabsf(deltaY) >= fabsf(deltaX))
201         deltaX = 0;
202     else
203         deltaY = 0;
204
205     bool shouldStretch = false;
206
207     PlatformWheelEventPhase momentumPhase = wheelEvent.momentumPhase();
208
209     // If we are starting momentum scrolling then do some setup.
210     if (!m_momentumScrollInProgress && (momentumPhase == PlatformWheelEventPhaseBegan || momentumPhase == PlatformWheelEventPhaseChanged))
211         m_momentumScrollInProgress = true;
212
213     CFTimeInterval timeDelta = wheelEvent.timestamp() - m_lastMomentumScrollTimestamp;
214     if (m_inScrollGesture || m_momentumScrollInProgress) {
215         if (m_lastMomentumScrollTimestamp && timeDelta > 0 && timeDelta < scrollVelocityZeroingTimeout) {
216             m_momentumVelocity.setWidth(eventCoalescedDeltaX / (float)timeDelta);
217             m_momentumVelocity.setHeight(eventCoalescedDeltaY / (float)timeDelta);
218             m_lastMomentumScrollTimestamp = wheelEvent.timestamp();
219         } else {
220             m_lastMomentumScrollTimestamp = wheelEvent.timestamp();
221             m_momentumVelocity = FloatSize();
222         }
223
224         if (isVerticallyStretched) {
225             if (!isHorizontallyStretched && m_client.pinnedInDirection(FloatSize(deltaX, 0))) {
226                 // Stretching only in the vertical.
227                 if (deltaY && (fabsf(deltaX / deltaY) < rubberbandDirectionLockStretchRatio))
228                     deltaX = 0;
229                 else if (fabsf(deltaX) < rubberbandMinimumRequiredDeltaBeforeStretch) {
230                     m_overflowScrollDelta.setWidth(m_overflowScrollDelta.width() + deltaX);
231                     deltaX = 0;
232                 } else
233                     m_overflowScrollDelta.setWidth(m_overflowScrollDelta.width() + deltaX);
234             }
235         } else if (isHorizontallyStretched) {
236             // Stretching only in the horizontal.
237             if (m_client.pinnedInDirection(FloatSize(0, deltaY))) {
238                 if (deltaX && (fabsf(deltaY / deltaX) < rubberbandDirectionLockStretchRatio))
239                     deltaY = 0;
240                 else if (fabsf(deltaY) < rubberbandMinimumRequiredDeltaBeforeStretch) {
241                     m_overflowScrollDelta.setHeight(m_overflowScrollDelta.height() + deltaY);
242                     deltaY = 0;
243                 } else
244                     m_overflowScrollDelta.setHeight(m_overflowScrollDelta.height() + deltaY);
245             }
246         } else {
247             // Not stretching at all yet.
248             if (m_client.pinnedInDirection(FloatSize(deltaX, deltaY))) {
249                 if (fabsf(deltaY) >= fabsf(deltaX)) {
250                     if (fabsf(deltaX) < rubberbandMinimumRequiredDeltaBeforeStretch) {
251                         m_overflowScrollDelta.setWidth(m_overflowScrollDelta.width() + deltaX);
252                         deltaX = 0;
253                     } else
254                         m_overflowScrollDelta.setWidth(m_overflowScrollDelta.width() + deltaX);
255                 }
256                 shouldStretch = true;
257             }
258         }
259     }
260
261     if (deltaX || deltaY) {
262         if (!(shouldStretch || isVerticallyStretched || isHorizontallyStretched)) {
263             if (deltaY) {
264                 deltaY *= scrollWheelMultiplier();
265                 m_client.immediateScrollBy(FloatSize(0, deltaY));
266             }
267             if (deltaX) {
268                 deltaX *= scrollWheelMultiplier();
269                 m_client.immediateScrollBy(FloatSize(deltaX, 0));
270             }
271         } else {
272             if (!m_client.allowsHorizontalStretching(wheelEvent)) {
273                 deltaX = 0;
274                 eventCoalescedDeltaX = 0;
275             } else if (deltaX && !isHorizontallyStretched && !m_client.pinnedInDirection(FloatSize(deltaX, 0))) {
276                 deltaX *= scrollWheelMultiplier();
277
278                 m_client.immediateScrollByWithoutContentEdgeConstraints(FloatSize(deltaX, 0));
279                 deltaX = 0;
280             }
281
282             if (!m_client.allowsVerticalStretching(wheelEvent)) {
283                 deltaY = 0;
284                 eventCoalescedDeltaY = 0;
285             } else if (deltaY && !isVerticallyStretched && !m_client.pinnedInDirection(FloatSize(0, deltaY))) {
286                 deltaY *= scrollWheelMultiplier();
287
288                 m_client.immediateScrollByWithoutContentEdgeConstraints(FloatSize(0, deltaY));
289                 deltaY = 0;
290             }
291
292             IntSize stretchAmount = m_client.stretchAmount();
293
294             if (m_momentumScrollInProgress) {
295                 if ((m_client.pinnedInDirection(FloatSize(eventCoalescedDeltaX, eventCoalescedDeltaY)) || (fabsf(eventCoalescedDeltaX) + fabsf(eventCoalescedDeltaY) <= 0)) && m_lastMomentumScrollTimestamp) {
296                     m_ignoreMomentumScrolls = true;
297                     m_momentumScrollInProgress = false;
298                     snapRubberBand();
299                 }
300             }
301
302             m_stretchScrollForce.setWidth(m_stretchScrollForce.width() + deltaX);
303             m_stretchScrollForce.setHeight(m_stretchScrollForce.height() + deltaY);
304
305             FloatSize dampedDelta(ceilf(elasticDeltaForReboundDelta(m_stretchScrollForce.width())), ceilf(elasticDeltaForReboundDelta(m_stretchScrollForce.height())));
306
307             m_client.immediateScrollByWithoutContentEdgeConstraints(dampedDelta - stretchAmount);
308         }
309     }
310
311     if (m_momentumScrollInProgress && momentumPhase == PlatformWheelEventPhaseEnded) {
312         m_momentumScrollInProgress = false;
313         m_ignoreMomentumScrolls = false;
314         m_lastMomentumScrollTimestamp = 0;
315     }
316
317     return true;
318 }
319 #endif
320
321 #if ENABLE(RUBBER_BANDING)
322 static inline float roundTowardZero(float num)
323 {
324     return num > 0 ? ceilf(num - 0.5f) : floorf(num + 0.5f);
325 }
326
327 static inline float roundToDevicePixelTowardZero(float num)
328 {
329     float roundedNum = roundf(num);
330     if (fabs(num - roundedNum) < 0.125)
331         num = roundedNum;
332
333     return roundTowardZero(num);
334 }
335
336 void ScrollController::snapRubberBandTimerFired()
337 {
338     if (isScrollSnapInProgress())
339         return;
340     
341     if (!m_momentumScrollInProgress || m_ignoreMomentumScrolls) {
342         CFTimeInterval timeDelta = [NSDate timeIntervalSinceReferenceDate] - m_startTime;
343
344         if (m_startStretch == FloatSize()) {
345             m_startStretch = m_client.stretchAmount();
346             if (m_startStretch == FloatSize()) {
347                 stopSnapRubberbandTimer();
348
349                 m_stretchScrollForce = FloatSize();
350                 m_startTime = 0;
351                 m_startStretch = FloatSize();
352                 m_origVelocity = FloatSize();
353                 return;
354             }
355
356             m_origVelocity = m_momentumVelocity;
357
358             // Just like normal scrolling, prefer vertical rubberbanding
359             if (fabsf(m_origVelocity.height()) >= fabsf(m_origVelocity.width()))
360                 m_origVelocity.setWidth(0);
361
362             // Don't rubber-band horizontally if it's not possible to scroll horizontally
363             if (!m_client.canScrollHorizontally())
364                 m_origVelocity.setWidth(0);
365
366             // Don't rubber-band vertically if it's not possible to scroll vertically
367             if (!m_client.canScrollVertically())
368                 m_origVelocity.setHeight(0);
369         }
370
371         FloatPoint delta(roundToDevicePixelTowardZero(elasticDeltaForTimeDelta(m_startStretch.width(), -m_origVelocity.width(), (float)timeDelta)),
372             roundToDevicePixelTowardZero(elasticDeltaForTimeDelta(m_startStretch.height(), -m_origVelocity.height(), (float)timeDelta)));
373
374         if (fabs(delta.x()) >= 1 || fabs(delta.y()) >= 1) {
375             m_client.immediateScrollByWithoutContentEdgeConstraints(FloatSize(delta.x(), delta.y()) - m_client.stretchAmount());
376
377             FloatSize newStretch = m_client.stretchAmount();
378
379             m_stretchScrollForce.setWidth(reboundDeltaForElasticDelta(newStretch.width()));
380             m_stretchScrollForce.setHeight(reboundDeltaForElasticDelta(newStretch.height()));
381         } else {
382             m_client.adjustScrollPositionToBoundsIfNecessary();
383
384             stopSnapRubberbandTimer();
385             m_stretchScrollForce = FloatSize();
386             m_startTime = 0;
387             m_startStretch = FloatSize();
388             m_origVelocity = FloatSize();
389         }
390     } else {
391         m_startTime = [NSDate timeIntervalSinceReferenceDate];
392         m_startStretch = FloatSize();
393         if (!isRubberBandInProgress())
394             stopSnapRubberbandTimer();
395     }
396 }
397 #endif
398
399 bool ScrollController::isRubberBandInProgress() const
400 {
401 #if ENABLE(RUBBER_BANDING) && PLATFORM(MAC)
402     if (!m_inScrollGesture && !m_momentumScrollInProgress && !m_snapRubberbandTimerIsActive)
403         return false;
404
405     return !m_client.stretchAmount().isZero();
406 #else
407     return false;
408 #endif
409 }
410
411 bool ScrollController::isScrollSnapInProgress() const
412 {
413 #if ENABLE(CSS_SCROLL_SNAP) && PLATFORM(MAC)
414     if (m_inScrollGesture || m_momentumScrollInProgress || m_scrollSnapTimer.isActive())
415         return true;
416 #endif
417     return false;
418 }
419
420 #if ENABLE(RUBBER_BANDING)
421 void ScrollController::startSnapRubberbandTimer()
422 {
423     m_client.startSnapRubberbandTimer();
424     m_snapRubberbandTimer.startRepeating(1.0 / 60.0);
425
426     m_client.deferTestsForReason(reinterpret_cast<WheelEventTestTrigger::ScrollableAreaIdentifier>(this), WheelEventTestTrigger::RubberbandInProgress);
427 }
428
429 void ScrollController::stopSnapRubberbandTimer()
430 {
431     m_client.stopSnapRubberbandTimer();
432     m_snapRubberbandTimer.stop();
433     m_snapRubberbandTimerIsActive = false;
434     
435     m_client.removeTestDeferralForReason(reinterpret_cast<WheelEventTestTrigger::ScrollableAreaIdentifier>(this), WheelEventTestTrigger::RubberbandInProgress);
436 }
437
438 void ScrollController::snapRubberBand()
439 {
440     CFTimeInterval timeDelta = systemUptime() - m_lastMomentumScrollTimestamp;
441     if (m_lastMomentumScrollTimestamp && timeDelta >= scrollVelocityZeroingTimeout)
442         m_momentumVelocity = FloatSize();
443
444     m_inScrollGesture = false;
445
446     if (m_snapRubberbandTimerIsActive)
447         return;
448
449     m_startTime = [NSDate timeIntervalSinceReferenceDate];
450     m_startStretch = FloatSize();
451     m_origVelocity = FloatSize();
452
453     startSnapRubberbandTimer();
454     m_snapRubberbandTimerIsActive = true;
455 }
456
457 bool ScrollController::shouldRubberBandInHorizontalDirection(const PlatformWheelEvent& wheelEvent)
458 {
459     if (wheelEvent.deltaX() > 0)
460         return m_client.shouldRubberBandInDirection(ScrollLeft);
461     if (wheelEvent.deltaX() < 0)
462         return m_client.shouldRubberBandInDirection(ScrollRight);
463
464     return true;
465 }
466 #endif
467
468 #if ENABLE(CSS_SCROLL_SNAP)
469 ScrollSnapAnimatorState& ScrollController::scrollSnapPointState(ScrollEventAxis axis)
470 {
471     ASSERT(axis != ScrollEventAxis::Horizontal || m_horizontalScrollSnapState);
472     ASSERT(axis != ScrollEventAxis::Vertical || m_verticalScrollSnapState);
473
474     return (axis == ScrollEventAxis::Horizontal) ? *m_horizontalScrollSnapState : *m_verticalScrollSnapState;
475 }
476
477 const ScrollSnapAnimatorState& ScrollController::scrollSnapPointState(ScrollEventAxis axis) const
478 {
479     ASSERT(axis != ScrollEventAxis::Horizontal || m_horizontalScrollSnapState);
480     ASSERT(axis != ScrollEventAxis::Vertical || m_verticalScrollSnapState);
481     
482     return (axis == ScrollEventAxis::Horizontal) ? *m_horizontalScrollSnapState : *m_verticalScrollSnapState;
483 }
484
485 #if PLATFORM(MAC)
486 static inline WheelEventStatus toWheelEventStatus(PlatformWheelEventPhase phase, PlatformWheelEventPhase momentumPhase)
487 {
488     if (phase == PlatformWheelEventPhaseNone) {
489         switch (momentumPhase) {
490         case PlatformWheelEventPhaseBegan:
491             return WheelEventStatus::InertialScrollBegin;
492                 
493         case PlatformWheelEventPhaseChanged:
494             return WheelEventStatus::InertialScrolling;
495                 
496         case PlatformWheelEventPhaseEnded:
497             return WheelEventStatus::InertialScrollEnd;
498
499         case PlatformWheelEventPhaseNone:
500             return WheelEventStatus::StatelessScrollEvent;
501
502         default:
503             return WheelEventStatus::Unknown;
504         }
505     }
506     if (momentumPhase == PlatformWheelEventPhaseNone) {
507         switch (phase) {
508         case PlatformWheelEventPhaseBegan:
509         case PlatformWheelEventPhaseMayBegin:
510             return WheelEventStatus::UserScrollBegin;
511                 
512         case PlatformWheelEventPhaseChanged:
513             return WheelEventStatus::UserScrolling;
514                 
515         case PlatformWheelEventPhaseEnded:
516         case PlatformWheelEventPhaseCancelled:
517             return WheelEventStatus::UserScrollEnd;
518                 
519         default:
520             return WheelEventStatus::Unknown;
521         }
522     }
523     return WheelEventStatus::Unknown;
524 }
525
526 void ScrollController::processWheelEventForScrollSnapOnAxis(ScrollEventAxis axis, const PlatformWheelEvent& event)
527 {
528     ScrollSnapAnimatorState& snapState = scrollSnapPointState(axis);
529
530     float wheelDelta = axis == ScrollEventAxis::Horizontal ? -event.deltaX() : -event.deltaY();
531     WheelEventStatus wheelStatus = toWheelEventStatus(event.phase(), event.momentumPhase());
532     
533     switch (wheelStatus) {
534     case WheelEventStatus::UserScrollBegin:
535     case WheelEventStatus::UserScrolling:
536         endScrollSnapAnimation(ScrollSnapState::UserInteraction);
537         break;
538             
539     case WheelEventStatus::UserScrollEnd:
540         beginScrollSnapAnimation(axis, ScrollSnapState::Snapping);
541         break;
542         
543     case WheelEventStatus::InertialScrollBegin:
544         // Begin tracking wheel deltas for glide prediction.
545         endScrollSnapAnimation(ScrollSnapState::UserInteraction);
546         snapState.pushInitialWheelDelta(wheelDelta);
547         snapState.m_beginTrackingWheelDeltaOffset = m_client.scrollOffsetOnAxis(axis);
548         break;
549             
550     case WheelEventStatus::InertialScrolling:
551         // This check for DestinationReached ensures that we don't receive another set of momentum events after ending the last glide.
552         if (snapState.m_currentState != ScrollSnapState::Gliding && snapState.m_currentState != ScrollSnapState::DestinationReached) {
553             if (snapState.wheelDeltaTrackingIsInProgress() && wheelDelta)
554                 snapState.pushInitialWheelDelta(wheelDelta);
555             
556             if (snapState.hasFinishedTrackingWheelDeltas() && snapState.averageInitialWheelDelta())
557                 beginScrollSnapAnimation(axis, ScrollSnapState::Gliding);
558         }
559         break;
560         
561     case WheelEventStatus::InertialScrollEnd:
562         if (snapState.wheelDeltaTrackingIsInProgress() && snapState.averageInitialWheelDelta())
563             beginScrollSnapAnimation(axis, ScrollSnapState::Gliding);
564
565         snapState.clearInitialWheelDeltaWindow();
566         snapState.m_shouldOverrideWheelEvent = false;
567         break;
568
569     case WheelEventStatus::StatelessScrollEvent:
570         endScrollSnapAnimation(ScrollSnapState::UserInteraction);
571         snapState.clearInitialWheelDeltaWindow();
572         snapState.m_shouldOverrideWheelEvent = false;
573         m_scrollSnapTimer.startOneShot(statelessScrollSnapDelay);
574         if (axis == ScrollEventAxis::Horizontal)
575             m_expectingHorizontalStatelessScrollSnap = true;
576         else
577             m_expectingVerticalStatelessScrollSnap = true;
578         break;
579
580     case WheelEventStatus::Unknown:
581         ASSERT_NOT_REACHED();
582         break;
583     }
584 }
585
586 bool ScrollController::shouldOverrideWheelEvent(ScrollEventAxis axis, const PlatformWheelEvent& event) const
587 {
588     const ScrollSnapAnimatorState& snapState = scrollSnapPointState(axis);
589
590     return snapState.m_shouldOverrideWheelEvent && toWheelEventStatus(event.phase(), event.momentumPhase()) == WheelEventStatus::InertialScrolling;
591 }
592
593 bool ScrollController::processWheelEventForScrollSnap(const PlatformWheelEvent& wheelEvent)
594 {
595     bool shouldAllowWheelEventToPropagate = true;
596     if (m_verticalScrollSnapState) {
597         processWheelEventForScrollSnapOnAxis(ScrollEventAxis::Vertical, wheelEvent);
598         shouldAllowWheelEventToPropagate &= !shouldOverrideWheelEvent(ScrollEventAxis::Vertical, wheelEvent);
599     }
600     if (m_horizontalScrollSnapState) {
601         processWheelEventForScrollSnapOnAxis(ScrollEventAxis::Horizontal, wheelEvent);
602         shouldAllowWheelEventToPropagate &= !shouldOverrideWheelEvent(ScrollEventAxis::Horizontal, wheelEvent);
603     }
604     return shouldAllowWheelEventToPropagate;
605 }
606 #endif
607
608 void ScrollController::updateScrollSnapState(const ScrollableArea& scrollableArea)
609 {
610     // FIXME: Currently, scroll snap animators are recreated even though the snap offsets alone can be updated.
611     if (scrollableArea.horizontalSnapOffsets())
612         m_horizontalScrollSnapState = std::make_unique<ScrollSnapAnimatorState>(ScrollEventAxis::Horizontal, *scrollableArea.horizontalSnapOffsets());
613     else if (m_horizontalScrollSnapState)
614         m_horizontalScrollSnapState = nullptr;
615
616     if (scrollableArea.verticalSnapOffsets())
617         m_verticalScrollSnapState = std::make_unique<ScrollSnapAnimatorState>(ScrollEventAxis::Vertical, *scrollableArea.verticalSnapOffsets());
618     else if (m_verticalScrollSnapState)
619         m_verticalScrollSnapState = nullptr;
620 }
621
622 void ScrollController::updateScrollSnapPoints(ScrollEventAxis axis, const Vector<LayoutUnit>& snapPoints)
623 {
624     // FIXME: Currently, scroll snap animators are recreated even though the snap offsets alone can be updated.
625     if (axis == ScrollEventAxis::Horizontal)
626         m_horizontalScrollSnapState = !snapPoints.isEmpty() ? std::make_unique<ScrollSnapAnimatorState>(ScrollEventAxis::Horizontal, snapPoints) : nullptr;
627
628     if (axis == ScrollEventAxis::Vertical)
629         m_verticalScrollSnapState = !snapPoints.isEmpty() ? std::make_unique<ScrollSnapAnimatorState>(ScrollEventAxis::Vertical, snapPoints) : nullptr;
630 }
631
632 #if PLATFORM(MAC)
633 void ScrollController::startScrollSnapTimer()
634 {
635     if (!m_scrollSnapTimer.isActive()) {
636         m_client.startScrollSnapTimer();
637         m_scrollSnapTimer.startRepeating(1.0 / 60.0);
638     }
639
640     if (!m_scrollSnapTimer.isActive())
641         return;
642
643     m_client.deferTestsForReason(reinterpret_cast<WheelEventTestTrigger::ScrollableAreaIdentifier>(this), WheelEventTestTrigger::ScrollSnapInProgress);
644 }
645
646 void ScrollController::stopScrollSnapTimer()
647 {
648     m_client.stopScrollSnapTimer();
649     m_scrollSnapTimer.stop();
650     
651     if (m_scrollSnapTimer.isActive())
652         return;
653
654     m_client.removeTestDeferralForReason(reinterpret_cast<WheelEventTestTrigger::ScrollableAreaIdentifier>(this), WheelEventTestTrigger::ScrollSnapInProgress);
655 }
656
657 void ScrollController::scrollSnapTimerFired()
658 {
659     if (m_expectingHorizontalStatelessScrollSnap || m_expectingVerticalStatelessScrollSnap) {
660         if (m_expectingHorizontalStatelessScrollSnap)
661             beginScrollSnapAnimation(ScrollEventAxis::Horizontal, ScrollSnapState::Snapping);
662         if (m_expectingVerticalStatelessScrollSnap)
663             beginScrollSnapAnimation(ScrollEventAxis::Vertical, ScrollSnapState::Snapping);
664         return;
665     }
666         
667     bool snapOnHorizontalAxis = isSnappingOnAxis(ScrollEventAxis::Horizontal);
668     bool snapOnVerticalAxis = isSnappingOnAxis(ScrollEventAxis::Vertical);
669     if (snapOnHorizontalAxis && !m_horizontalScrollSnapState->canReachTargetWithCurrentInitialScrollDelta()) {
670         m_horizontalScrollSnapState->m_currentState = ScrollSnapState::DestinationReached;
671         snapOnHorizontalAxis = false;
672     }
673     if (snapOnVerticalAxis && !m_verticalScrollSnapState->canReachTargetWithCurrentInitialScrollDelta()) {
674         m_verticalScrollSnapState->m_currentState = ScrollSnapState::DestinationReached;
675         snapOnVerticalAxis = false;
676     }
677     if (!snapOnHorizontalAxis && !snapOnVerticalAxis) {
678         endScrollSnapAnimation(ScrollSnapState::DestinationReached);
679         return;
680     }
681     
682     double currentTime = monotonicallyIncreasingTime();
683     if (m_scrollSnapCurveState->shouldCompleteSnapAnimationImmediatelyAtTime(currentTime)) {
684         float finalHorizontalDelta = 0;
685         float finalVerticalDelta = 0;
686         if (snapOnHorizontalAxis)
687             finalHorizontalDelta = m_horizontalScrollSnapState->m_targetOffset - m_client.scrollOffsetOnAxis(ScrollEventAxis::Horizontal);
688         if (snapOnVerticalAxis)
689             finalVerticalDelta = m_verticalScrollSnapState->m_targetOffset - m_client.scrollOffsetOnAxis(ScrollEventAxis::Vertical);
690
691         if (finalHorizontalDelta || finalVerticalDelta)
692             m_client.immediateScrollBy(FloatSize(finalHorizontalDelta, finalVerticalDelta));
693
694         endScrollSnapAnimation(ScrollSnapState::DestinationReached);
695         return;
696     }
697     
698     float animationProgress = m_scrollSnapCurveState->animationProgressAtTime(currentTime);
699     float horizontalDelta = 0;
700     float verticalDelta = 0;
701     if (m_scrollSnapCurveState->shouldAnimateDirectlyToSnapPoint) {
702         if (snapOnHorizontalAxis)
703             horizontalDelta = m_horizontalScrollSnapState->interpolatedOffsetAtProgress(animationProgress) - m_client.scrollOffsetOnAxis(ScrollEventAxis::Horizontal);
704         if (snapOnVerticalAxis)
705             verticalDelta = m_verticalScrollSnapState->interpolatedOffsetAtProgress(animationProgress) - m_client.scrollOffsetOnAxis(ScrollEventAxis::Vertical);
706
707     } else {
708         FloatPoint interpolatedPoint = m_scrollSnapCurveState->interpolatedPositionAtProgress(animationProgress);
709         horizontalDelta = interpolatedPoint.x() - m_client.scrollOffsetOnAxis(ScrollEventAxis::Horizontal);
710         verticalDelta = interpolatedPoint.y() - m_client.scrollOffsetOnAxis(ScrollEventAxis::Vertical);
711     }
712     
713     if (horizontalDelta || verticalDelta)
714         m_client.immediateScrollBy(FloatSize(horizontalDelta, verticalDelta));
715 }
716
717 static inline float projectedInertialScrollDistance(float initialWheelDelta)
718 {
719     // FIXME: Experiments with inertial scrolling show a fairly consistent linear relationship between initial wheel delta and total distance scrolled.
720     // In the future, we'll want to find a more accurate way of inertial scroll prediction.
721     return inertialScrollPredictionFactor * initialWheelDelta;
722 }
723 #endif
724
725 unsigned ScrollController::activeScrollSnapIndexForAxis(ScrollEventAxis axis) const
726 {
727     if ((axis == ScrollEventAxis::Horizontal) && !m_horizontalScrollSnapState)
728         return 0;
729     if ((axis == ScrollEventAxis::Vertical) && !m_verticalScrollSnapState)
730         return 0;
731     
732     const ScrollSnapAnimatorState& snapState = scrollSnapPointState(axis);
733     return snapState.m_activeSnapIndex;
734 }
735
736 void ScrollController::setActiveScrollSnapIndexForAxis(ScrollEventAxis axis, unsigned index)
737 {
738     auto* snapState = (axis == ScrollEventAxis::Horizontal) ? m_horizontalScrollSnapState.get() : m_verticalScrollSnapState.get();
739     if (!snapState)
740         return;
741
742     snapState->m_activeSnapIndex = index;
743 }
744
745 void ScrollController::setNearestScrollSnapIndexForAxisAndOffset(ScrollEventAxis axis, int offset)
746 {
747     float scaleFactor = m_client.pageScaleFactor();
748     ScrollSnapAnimatorState& snapState = scrollSnapPointState(axis);
749     
750     LayoutUnit clampedOffset = std::min(std::max(LayoutUnit(offset / scaleFactor), snapState.m_snapOffsets.first()), snapState.m_snapOffsets.last());
751
752     unsigned activeIndex = 0;
753     (void)closestSnapOffset<LayoutUnit, float>(snapState.m_snapOffsets, clampedOffset, 0, activeIndex);
754
755     if (activeIndex == snapState.m_activeSnapIndex)
756         return;
757
758     m_activeScrollSnapIndexDidChange = true;
759     snapState.m_activeSnapIndex = activeIndex;
760 }
761
762 void ScrollController::setActiveScrollSnapIndicesForOffset(int x, int y)
763 {
764     if (m_horizontalScrollSnapState)
765         setNearestScrollSnapIndexForAxisAndOffset(ScrollEventAxis::Horizontal, x);
766     if (m_verticalScrollSnapState)
767         setNearestScrollSnapIndexForAxisAndOffset(ScrollEventAxis::Vertical, y);
768 }
769
770 #if PLATFORM(MAC)
771 void ScrollController::beginScrollSnapAnimation(ScrollEventAxis axis, ScrollSnapState newState)
772 {
773     ASSERT(newState == ScrollSnapState::Gliding || newState == ScrollSnapState::Snapping);
774     if (m_expectingHorizontalStatelessScrollSnap || m_expectingVerticalStatelessScrollSnap) {
775         m_expectingHorizontalStatelessScrollSnap = false;
776         m_expectingVerticalStatelessScrollSnap = false;
777         stopScrollSnapTimer();
778     }
779     ScrollSnapAnimatorState& snapState = scrollSnapPointState(axis);
780
781     LayoutUnit offset = m_client.scrollOffsetOnAxis(axis);
782     float initialWheelDelta = newState == ScrollSnapState::Gliding ? snapState.averageInitialWheelDelta() : 0;
783     LayoutUnit scaledProjectedScrollDestination = newState == ScrollSnapState::Gliding ? snapState.m_beginTrackingWheelDeltaOffset + LayoutUnit(projectedInertialScrollDistance(initialWheelDelta)) : offset;
784     if (snapState.m_snapOffsets.isEmpty())
785         return;
786
787     float scaleFactor = m_client.pageScaleFactor();
788     LayoutUnit originalProjectedScrollDestination = scaledProjectedScrollDestination / scaleFactor;
789     
790     LayoutUnit clampedScrollDestination = std::min(std::max(originalProjectedScrollDestination, snapState.m_snapOffsets.first()), snapState.m_snapOffsets.last());
791     snapState.m_initialOffset = offset;
792     m_activeScrollSnapIndexDidChange = false;
793     snapState.m_targetOffset = scaleFactor * closestSnapOffset<LayoutUnit, float>(snapState.m_snapOffsets, clampedScrollDestination, initialWheelDelta, snapState.m_activeSnapIndex);
794     if (snapState.m_initialOffset == snapState.m_targetOffset)
795         return;
796
797     LayoutUnit scrollExtent = (axis == ScrollEventAxis::Horizontal) ? m_client.scrollExtent().width() : m_client.scrollExtent().height();
798     LayoutUnit projectedScrollDestination = clampedScrollDestination;
799     if (originalProjectedScrollDestination < 0 || originalProjectedScrollDestination > scrollExtent)
800         projectedScrollDestination = originalProjectedScrollDestination;
801     
802     m_activeScrollSnapIndexDidChange = true;
803     snapState.m_currentState = newState;
804     if (newState == ScrollSnapState::Gliding) {
805         // Check if the other scroll axis needs to animate to the nearest snap point.
806         snapState.m_initialScrollDelta = initialWheelDelta;
807         snapState.m_shouldOverrideWheelEvent = true;
808         snapState.clearInitialWheelDeltaWindow();
809         ScrollEventAxis otherAxis = axis == ScrollEventAxis::Horizontal ? ScrollEventAxis::Vertical : ScrollEventAxis::Horizontal;
810         if ((otherAxis == ScrollEventAxis::Horizontal && m_horizontalScrollSnapState && m_horizontalScrollSnapState->m_currentState == ScrollSnapState::UserInteraction)
811             || (otherAxis == ScrollEventAxis::Vertical && m_verticalScrollSnapState && m_verticalScrollSnapState->m_currentState == ScrollSnapState::UserInteraction)) {
812             
813             ScrollSnapAnimatorState& otherState = scrollSnapPointState(otherAxis);
814             if (!otherState.averageInitialWheelDelta()) {
815                 float offsetOnOtherAxis = m_client.scrollOffsetOnAxis(otherAxis);
816                 float snapOffsetForOtherAxis = scaleFactor * closestSnapOffset<LayoutUnit, float>(otherState.m_snapOffsets, offsetOnOtherAxis, 0, otherState.m_activeSnapIndex);
817                 if (offsetOnOtherAxis != snapOffsetForOtherAxis) {
818                     otherState.m_initialOffset = offsetOnOtherAxis;
819                     otherState.m_targetOffset = snapOffsetForOtherAxis;
820                     otherState.m_initialScrollDelta = 0;
821                     otherState.m_currentState = ScrollSnapState::Gliding;
822                 }
823             }
824         }
825         
826     } else {
827         snapState.m_initialScrollDelta = initialWheelDelta;
828     }
829     initializeScrollSnapAnimationParameters();
830     startScrollSnapTimer();
831 }
832
833 void ScrollController::endScrollSnapAnimation(ScrollSnapState newState)
834 {
835     ASSERT(newState == ScrollSnapState::DestinationReached || newState == ScrollSnapState::UserInteraction);
836     if (m_horizontalScrollSnapState)
837         m_horizontalScrollSnapState->m_currentState = newState;
838
839     if (m_verticalScrollSnapState)
840         m_verticalScrollSnapState->m_currentState = newState;
841
842     stopScrollSnapTimer();
843 }
844
845 void ScrollController::initializeScrollSnapAnimationParameters()
846 {
847     if (!m_scrollSnapCurveState)
848         m_scrollSnapCurveState = std::make_unique<ScrollSnapAnimationCurveState>();
849     
850     bool isSnappingOnHorizontalAxis = isSnappingOnAxis(ScrollEventAxis::Horizontal);
851     bool isSnappingOnVerticalAxis = isSnappingOnAxis(ScrollEventAxis::Vertical);
852     FloatSize initialVector(isSnappingOnHorizontalAxis ? m_horizontalScrollSnapState->m_initialOffset : m_client.scrollOffsetOnAxis(ScrollEventAxis::Horizontal),
853         isSnappingOnVerticalAxis ? m_verticalScrollSnapState->m_initialOffset : m_client.scrollOffsetOnAxis(ScrollEventAxis::Vertical));
854     FloatSize targetVector(isSnappingOnHorizontalAxis ? m_horizontalScrollSnapState->m_targetOffset : m_client.scrollOffsetOnAxis(ScrollEventAxis::Horizontal),
855         isSnappingOnVerticalAxis ? m_verticalScrollSnapState->m_targetOffset : m_client.scrollOffsetOnAxis(ScrollEventAxis::Vertical));
856     FloatSize initialDelta(isSnappingOnHorizontalAxis ? m_horizontalScrollSnapState->m_initialScrollDelta : 0,
857         isSnappingOnVerticalAxis ? m_verticalScrollSnapState->m_initialScrollDelta : 0);
858
859     // Animate directly by default. This flag will be changed as necessary if interpolation is possible.
860     m_scrollSnapCurveState->shouldAnimateDirectlyToSnapPoint = true;
861     m_scrollSnapCurveState->initializeSnapProgressCurve(initialVector, targetVector, initialDelta);
862     if (isSnappingOnHorizontalAxis && isSnappingOnVerticalAxis)
863         m_scrollSnapCurveState->initializeInterpolationCoefficientsIfNecessary(initialVector, targetVector, initialDelta);
864 }
865     
866 bool ScrollController::isSnappingOnAxis(ScrollEventAxis axis) const
867 {
868     if (axis == ScrollEventAxis::Horizontal)
869         return m_horizontalScrollSnapState && m_horizontalScrollSnapState->isSnapping();
870
871     return m_verticalScrollSnapState && m_verticalScrollSnapState->isSnapping();
872 }
873     
874 #endif
875 #endif
876
877 } // namespace WebCore
878
879 #endif // ENABLE(RUBBER_BANDING)