5d42b6eeedb43bfff054f993a555a8e2f5f1e4fe
[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 "PlatformWheelEvent.h"
30 #include "WebCoreSystemInterface.h"
31 #include <sys/sysctl.h>
32 #include <sys/time.h>
33
34 #if ENABLE(CSS_SCROLL_SNAP)
35 #include "ScrollSnapAnimatorState.h"
36 #include "ScrollableArea.h"
37 #endif
38
39 #if ENABLE(RUBBER_BANDING)
40
41 static NSTimeInterval systemUptime()
42 {
43     if ([[NSProcessInfo processInfo] respondsToSelector:@selector(systemUptime)])
44         return [[NSProcessInfo processInfo] systemUptime];
45
46     // Get how long system has been up. Found by looking getting "boottime" from the kernel.
47     static struct timeval boottime = {0, 0};
48     if (!boottime.tv_sec) {
49         int mib[2] = {CTL_KERN, KERN_BOOTTIME};
50         size_t size = sizeof(boottime);
51         if (-1 == sysctl(mib, 2, &boottime, &size, 0, 0))
52             boottime.tv_sec = 0;
53     }
54     struct timeval now;
55     if (boottime.tv_sec && -1 != gettimeofday(&now, 0)) {
56         struct timeval uptime;
57         timersub(&now, &boottime, &uptime);
58         NSTimeInterval result = uptime.tv_sec + (uptime.tv_usec / 1E+6);
59         return result;
60     }
61     return 0;
62 }
63
64
65 namespace WebCore {
66
67 static const float scrollVelocityZeroingTimeout = 0.10f;
68 static const float rubberbandDirectionLockStretchRatio = 1;
69 static const float rubberbandMinimumRequiredDeltaBeforeStretch = 10;
70
71 #if ENABLE(CSS_SCROLL_SNAP) && PLATFORM(MAC)
72 static const float snapMagnitudeMax = 25;
73 static const float snapMagnitudeMin = 5;
74 static const float snapThresholdHigh = 1000;
75 static const float snapThresholdLow = 50;
76
77 static const float inertialScrollPredictionFactor = 16.7;
78 static const float initialToFinalMomentumFactor = 1.0 / 40.0;
79
80 static const float glideBoostMultiplier = 3.5;
81
82 static const float maxTargetWheelDelta = 7;
83 static const float minTargetWheelDelta = 3.5;
84 #endif
85
86 enum class WheelEventStatus {
87     UserScrollBegin,
88     UserScrolling,
89     UserScrollEnd,
90     InertialScrollBegin,
91     InertialScrolling,
92     InertialScrollEnd,
93     StatelessScrollEvent,
94     Unknown
95 };
96
97 static float elasticDeltaForTimeDelta(float initialPosition, float initialVelocity, float elapsedTime)
98 {
99     return wkNSElasticDeltaForTimeDelta(initialPosition, initialVelocity, elapsedTime);
100 }
101
102 static float elasticDeltaForReboundDelta(float delta)
103 {
104     return wkNSElasticDeltaForReboundDelta(delta);
105 }
106
107 static float reboundDeltaForElasticDelta(float delta)
108 {
109     return wkNSReboundDeltaForElasticDelta(delta);
110 }
111
112 static float scrollWheelMultiplier()
113 {
114     static float multiplier = -1;
115     if (multiplier < 0) {
116         multiplier = [[NSUserDefaults standardUserDefaults] floatForKey:@"NSScrollWheelMultiplier"];
117         if (multiplier <= 0)
118             multiplier = 1;
119     }
120     return multiplier;
121 }
122
123 ScrollController::ScrollController(ScrollControllerClient& client)
124     : m_client(client)
125     , m_lastMomentumScrollTimestamp(0)
126     , m_startTime(0)
127     , m_snapRubberbandTimer(RunLoop::current(), this, &ScrollController::snapRubberBandTimerFired)
128 #if ENABLE(CSS_SCROLL_SNAP) && PLATFORM(MAC)
129     , m_horizontalScrollSnapTimer(RunLoop::current(), this, &ScrollController::horizontalScrollSnapTimerFired)
130     , m_verticalScrollSnapTimer(RunLoop::current(), this, &ScrollController::verticalScrollSnapTimerFired)
131 #endif
132     , m_inScrollGesture(false)
133     , m_momentumScrollInProgress(false)
134     , m_ignoreMomentumScrolls(false)
135     , m_snapRubberbandTimerIsActive(false)
136 {
137 }
138
139 bool ScrollController::handleWheelEvent(const PlatformWheelEvent& wheelEvent)
140 {
141 #if ENABLE(CSS_SCROLL_SNAP) && PLATFORM(MAC)
142     if (!processWheelEventForScrollSnap(wheelEvent))
143         return false;
144 #endif
145     if (wheelEvent.phase() == PlatformWheelEventPhaseBegan) {
146         // First, check if we should rubber-band at all.
147         if (m_client.pinnedInDirection(FloatSize(-wheelEvent.deltaX(), 0))
148             && !shouldRubberBandInHorizontalDirection(wheelEvent))
149             return false;
150
151         m_inScrollGesture = true;
152         m_momentumScrollInProgress = false;
153         m_ignoreMomentumScrolls = false;
154         m_lastMomentumScrollTimestamp = 0;
155         m_momentumVelocity = FloatSize();
156
157         IntSize stretchAmount = m_client.stretchAmount();
158         m_stretchScrollForce.setWidth(reboundDeltaForElasticDelta(stretchAmount.width()));
159         m_stretchScrollForce.setHeight(reboundDeltaForElasticDelta(stretchAmount.height()));
160         m_overflowScrollDelta = FloatSize();
161
162         stopSnapRubberbandTimer();
163
164         return true;
165     }
166
167     if (wheelEvent.phase() == PlatformWheelEventPhaseEnded) {
168         snapRubberBand();
169         return true;
170     }
171
172     bool isMomentumScrollEvent = (wheelEvent.momentumPhase() != PlatformWheelEventPhaseNone);
173     if (m_ignoreMomentumScrolls && (isMomentumScrollEvent || m_snapRubberbandTimerIsActive)) {
174         if (wheelEvent.momentumPhase() == PlatformWheelEventPhaseEnded) {
175             m_ignoreMomentumScrolls = false;
176             return true;
177         }
178         return false;
179     }
180
181     float deltaX = m_overflowScrollDelta.width();
182     float deltaY = m_overflowScrollDelta.height();
183
184     // Reset overflow values because we may decide to remove delta at various points and put it into overflow.
185     m_overflowScrollDelta = FloatSize();
186
187     IntSize stretchAmount = m_client.stretchAmount();
188     bool isVerticallyStretched = stretchAmount.height();
189     bool isHorizontallyStretched = stretchAmount.width();
190
191     float eventCoalescedDeltaX;
192     float eventCoalescedDeltaY;
193
194     if (isVerticallyStretched || isHorizontallyStretched) {
195         eventCoalescedDeltaX = -wheelEvent.unacceleratedScrollingDeltaX();
196         eventCoalescedDeltaY = -wheelEvent.unacceleratedScrollingDeltaY();
197     } else {
198         eventCoalescedDeltaX = -wheelEvent.deltaX();
199         eventCoalescedDeltaY = -wheelEvent.deltaY();
200     }
201
202     deltaX += eventCoalescedDeltaX;
203     deltaY += eventCoalescedDeltaY;
204
205     // Slightly prefer scrolling vertically by applying the = case to deltaY
206     if (fabsf(deltaY) >= fabsf(deltaX))
207         deltaX = 0;
208     else
209         deltaY = 0;
210
211     bool shouldStretch = false;
212
213     PlatformWheelEventPhase momentumPhase = wheelEvent.momentumPhase();
214
215     // If we are starting momentum scrolling then do some setup.
216     if (!m_momentumScrollInProgress && (momentumPhase == PlatformWheelEventPhaseBegan || momentumPhase == PlatformWheelEventPhaseChanged))
217         m_momentumScrollInProgress = true;
218
219     CFTimeInterval timeDelta = wheelEvent.timestamp() - m_lastMomentumScrollTimestamp;
220     if (m_inScrollGesture || m_momentumScrollInProgress) {
221         if (m_lastMomentumScrollTimestamp && timeDelta > 0 && timeDelta < scrollVelocityZeroingTimeout) {
222             m_momentumVelocity.setWidth(eventCoalescedDeltaX / (float)timeDelta);
223             m_momentumVelocity.setHeight(eventCoalescedDeltaY / (float)timeDelta);
224             m_lastMomentumScrollTimestamp = wheelEvent.timestamp();
225         } else {
226             m_lastMomentumScrollTimestamp = wheelEvent.timestamp();
227             m_momentumVelocity = FloatSize();
228         }
229
230         if (isVerticallyStretched) {
231             if (!isHorizontallyStretched && m_client.pinnedInDirection(FloatSize(deltaX, 0))) {
232                 // Stretching only in the vertical.
233                 if (deltaY && (fabsf(deltaX / deltaY) < rubberbandDirectionLockStretchRatio))
234                     deltaX = 0;
235                 else if (fabsf(deltaX) < rubberbandMinimumRequiredDeltaBeforeStretch) {
236                     m_overflowScrollDelta.setWidth(m_overflowScrollDelta.width() + deltaX);
237                     deltaX = 0;
238                 } else
239                     m_overflowScrollDelta.setWidth(m_overflowScrollDelta.width() + deltaX);
240             }
241         } else if (isHorizontallyStretched) {
242             // Stretching only in the horizontal.
243             if (m_client.pinnedInDirection(FloatSize(0, deltaY))) {
244                 if (deltaX && (fabsf(deltaY / deltaX) < rubberbandDirectionLockStretchRatio))
245                     deltaY = 0;
246                 else if (fabsf(deltaY) < rubberbandMinimumRequiredDeltaBeforeStretch) {
247                     m_overflowScrollDelta.setHeight(m_overflowScrollDelta.height() + deltaY);
248                     deltaY = 0;
249                 } else
250                     m_overflowScrollDelta.setHeight(m_overflowScrollDelta.height() + deltaY);
251             }
252         } else {
253             // Not stretching at all yet.
254             if (m_client.pinnedInDirection(FloatSize(deltaX, deltaY))) {
255                 if (fabsf(deltaY) >= fabsf(deltaX)) {
256                     if (fabsf(deltaX) < rubberbandMinimumRequiredDeltaBeforeStretch) {
257                         m_overflowScrollDelta.setWidth(m_overflowScrollDelta.width() + deltaX);
258                         deltaX = 0;
259                     } else
260                         m_overflowScrollDelta.setWidth(m_overflowScrollDelta.width() + deltaX);
261                 }
262                 shouldStretch = true;
263             }
264         }
265     }
266
267     if (deltaX || deltaY) {
268         if (!(shouldStretch || isVerticallyStretched || isHorizontallyStretched)) {
269             if (deltaY) {
270                 deltaY *= scrollWheelMultiplier();
271                 m_client.immediateScrollBy(FloatSize(0, deltaY));
272             }
273             if (deltaX) {
274                 deltaX *= scrollWheelMultiplier();
275                 m_client.immediateScrollBy(FloatSize(deltaX, 0));
276             }
277         } else {
278             if (!m_client.allowsHorizontalStretching(wheelEvent)) {
279                 deltaX = 0;
280                 eventCoalescedDeltaX = 0;
281             } else if (deltaX && !isHorizontallyStretched && !m_client.pinnedInDirection(FloatSize(deltaX, 0))) {
282                 deltaX *= scrollWheelMultiplier();
283
284                 m_client.immediateScrollByWithoutContentEdgeConstraints(FloatSize(deltaX, 0));
285                 deltaX = 0;
286             }
287
288             if (!m_client.allowsVerticalStretching(wheelEvent)) {
289                 deltaY = 0;
290                 eventCoalescedDeltaY = 0;
291             } else if (deltaY && !isVerticallyStretched && !m_client.pinnedInDirection(FloatSize(0, deltaY))) {
292                 deltaY *= scrollWheelMultiplier();
293
294                 m_client.immediateScrollByWithoutContentEdgeConstraints(FloatSize(0, deltaY));
295                 deltaY = 0;
296             }
297
298             IntSize stretchAmount = m_client.stretchAmount();
299
300             if (m_momentumScrollInProgress) {
301                 if ((m_client.pinnedInDirection(FloatSize(eventCoalescedDeltaX, eventCoalescedDeltaY)) || (fabsf(eventCoalescedDeltaX) + fabsf(eventCoalescedDeltaY) <= 0)) && m_lastMomentumScrollTimestamp) {
302                     m_ignoreMomentumScrolls = true;
303                     m_momentumScrollInProgress = false;
304                     snapRubberBand();
305                 }
306             }
307
308             m_stretchScrollForce.setWidth(m_stretchScrollForce.width() + deltaX);
309             m_stretchScrollForce.setHeight(m_stretchScrollForce.height() + deltaY);
310
311             FloatSize dampedDelta(ceilf(elasticDeltaForReboundDelta(m_stretchScrollForce.width())), ceilf(elasticDeltaForReboundDelta(m_stretchScrollForce.height())));
312
313             m_client.immediateScrollByWithoutContentEdgeConstraints(dampedDelta - stretchAmount);
314         }
315     }
316
317     if (m_momentumScrollInProgress && momentumPhase == PlatformWheelEventPhaseEnded) {
318         m_momentumScrollInProgress = false;
319         m_ignoreMomentumScrolls = false;
320         m_lastMomentumScrollTimestamp = 0;
321     }
322
323     return true;
324 }
325
326 static inline float roundTowardZero(float num)
327 {
328     return num > 0 ? ceilf(num - 0.5f) : floorf(num + 0.5f);
329 }
330
331 static inline float roundToDevicePixelTowardZero(float num)
332 {
333     float roundedNum = roundf(num);
334     if (fabs(num - roundedNum) < 0.125)
335         num = roundedNum;
336
337     return roundTowardZero(num);
338 }
339
340 void ScrollController::snapRubberBandTimerFired()
341 {
342     if (!m_momentumScrollInProgress || m_ignoreMomentumScrolls) {
343         CFTimeInterval timeDelta = [NSDate timeIntervalSinceReferenceDate] - m_startTime;
344
345         if (m_startStretch == FloatSize()) {
346             m_startStretch = m_client.stretchAmount();
347             if (m_startStretch == FloatSize()) {
348                 stopSnapRubberbandTimer();
349
350                 m_stretchScrollForce = FloatSize();
351                 m_startTime = 0;
352                 m_startStretch = FloatSize();
353                 m_origOrigin = FloatPoint();
354                 m_origVelocity = FloatSize();
355                 return;
356             }
357
358             m_origOrigin = m_client.absoluteScrollPosition() - m_startStretch;
359             m_origVelocity = m_momentumVelocity;
360
361             // Just like normal scrolling, prefer vertical rubberbanding
362             if (fabsf(m_origVelocity.height()) >= fabsf(m_origVelocity.width()))
363                 m_origVelocity.setWidth(0);
364
365             // Don't rubber-band horizontally if it's not possible to scroll horizontally
366             if (!m_client.canScrollHorizontally())
367                 m_origVelocity.setWidth(0);
368
369             // Don't rubber-band vertically if it's not possible to scroll vertically
370             if (!m_client.canScrollVertically())
371                 m_origVelocity.setHeight(0);
372         }
373
374         FloatPoint delta(roundToDevicePixelTowardZero(elasticDeltaForTimeDelta(m_startStretch.width(), -m_origVelocity.width(), (float)timeDelta)),
375             roundToDevicePixelTowardZero(elasticDeltaForTimeDelta(m_startStretch.height(), -m_origVelocity.height(), (float)timeDelta)));
376
377         if (fabs(delta.x()) >= 1 || fabs(delta.y()) >= 1) {
378             m_client.immediateScrollByWithoutContentEdgeConstraints(FloatSize(delta.x(), delta.y()) - m_client.stretchAmount());
379
380             FloatSize newStretch = m_client.stretchAmount();
381
382             m_stretchScrollForce.setWidth(reboundDeltaForElasticDelta(newStretch.width()));
383             m_stretchScrollForce.setHeight(reboundDeltaForElasticDelta(newStretch.height()));
384         } else {
385             m_client.adjustScrollPositionToBoundsIfNecessary();
386
387             stopSnapRubberbandTimer();
388             m_stretchScrollForce = FloatSize();
389             m_startTime = 0;
390             m_startStretch = FloatSize();
391             m_origOrigin = FloatPoint();
392             m_origVelocity = FloatSize();
393         }
394     } else {
395         m_startTime = [NSDate timeIntervalSinceReferenceDate];
396         m_startStretch = FloatSize();
397         if (!isRubberBandInProgress())
398             stopSnapRubberbandTimer();
399     }
400 }
401
402 bool ScrollController::isRubberBandInProgress() const
403 {
404     if (!m_inScrollGesture && !m_momentumScrollInProgress && !m_snapRubberbandTimerIsActive)
405         return false;
406
407     return !m_client.stretchAmount().isZero();
408 }
409
410 void ScrollController::startSnapRubberbandTimer()
411 {
412     m_client.startSnapRubberbandTimer();
413     m_snapRubberbandTimer.startRepeating(1.0 / 60.0);
414 }
415
416 void ScrollController::stopSnapRubberbandTimer()
417 {
418     m_client.stopSnapRubberbandTimer();
419     m_snapRubberbandTimer.stop();
420     m_snapRubberbandTimerIsActive = false;
421 }
422
423 void ScrollController::snapRubberBand()
424 {
425     CFTimeInterval timeDelta = systemUptime() - m_lastMomentumScrollTimestamp;
426     if (m_lastMomentumScrollTimestamp && timeDelta >= scrollVelocityZeroingTimeout)
427         m_momentumVelocity = FloatSize();
428
429     m_inScrollGesture = false;
430
431     if (m_snapRubberbandTimerIsActive)
432         return;
433
434     m_startTime = [NSDate timeIntervalSinceReferenceDate];
435     m_startStretch = FloatSize();
436     m_origOrigin = FloatPoint();
437     m_origVelocity = FloatSize();
438
439     startSnapRubberbandTimer();
440     m_snapRubberbandTimerIsActive = true;
441 }
442
443 bool ScrollController::shouldRubberBandInHorizontalDirection(const PlatformWheelEvent& wheelEvent)
444 {
445     if (wheelEvent.deltaX() > 0)
446         return m_client.shouldRubberBandInDirection(ScrollLeft);
447     if (wheelEvent.deltaX() < 0)
448         return m_client.shouldRubberBandInDirection(ScrollRight);
449
450     return true;
451 }
452
453 #if ENABLE(CSS_SCROLL_SNAP) && PLATFORM(MAC)
454 ScrollSnapAnimatorState& ScrollController::scrollSnapPointState(ScrollEventAxis axis)
455 {
456     ASSERT(axis != ScrollEventAxis::Horizontal || m_horizontalScrollSnapState);
457     ASSERT(axis != ScrollEventAxis::Vertical || m_verticalScrollSnapState);
458
459     return (axis == ScrollEventAxis::Horizontal) ? *m_horizontalScrollSnapState : *m_verticalScrollSnapState;
460 }
461
462 const ScrollSnapAnimatorState& ScrollController::scrollSnapPointState(ScrollEventAxis axis) const
463 {
464     ASSERT(axis != ScrollEventAxis::Horizontal || m_horizontalScrollSnapState);
465     ASSERT(axis != ScrollEventAxis::Vertical || m_verticalScrollSnapState);
466     
467     return (axis == ScrollEventAxis::Horizontal) ? *m_horizontalScrollSnapState : *m_verticalScrollSnapState;
468 }
469
470 static inline WheelEventStatus toWheelEventStatus(PlatformWheelEventPhase phase, PlatformWheelEventPhase momentumPhase)
471 {
472     if (phase == PlatformWheelEventPhaseNone) {
473         switch (momentumPhase) {
474         case PlatformWheelEventPhaseBegan:
475             return WheelEventStatus::InertialScrollBegin;
476                 
477         case PlatformWheelEventPhaseChanged:
478             return WheelEventStatus::InertialScrolling;
479                 
480         case PlatformWheelEventPhaseEnded:
481             return WheelEventStatus::InertialScrollEnd;
482
483         case PlatformWheelEventPhaseNone:
484             return WheelEventStatus::StatelessScrollEvent;
485
486         default:
487             return WheelEventStatus::Unknown;
488         }
489     }
490     if (momentumPhase == PlatformWheelEventPhaseNone) {
491         switch (phase) {
492         case PlatformWheelEventPhaseBegan:
493         case PlatformWheelEventPhaseMayBegin:
494             return WheelEventStatus::UserScrollBegin;
495                 
496         case PlatformWheelEventPhaseChanged:
497             return WheelEventStatus::UserScrolling;
498                 
499         case PlatformWheelEventPhaseEnded:
500         case PlatformWheelEventPhaseCancelled:
501             return WheelEventStatus::UserScrollEnd;
502                 
503         default:
504             return WheelEventStatus::Unknown;
505         }
506     }
507     return WheelEventStatus::Unknown;
508 }
509
510 void ScrollController::processWheelEventForScrollSnapOnAxis(ScrollEventAxis axis, const PlatformWheelEvent& event)
511 {
512     ScrollSnapAnimatorState& snapState = scrollSnapPointState(axis);
513
514     float wheelDelta = axis == ScrollEventAxis::Horizontal ? -event.deltaX() : -event.deltaY();
515     WheelEventStatus wheelStatus = toWheelEventStatus(event.phase(), event.momentumPhase());
516     
517     switch (wheelStatus) {
518     case WheelEventStatus::UserScrollBegin:
519     case WheelEventStatus::UserScrolling:
520         endScrollSnapAnimation(axis, ScrollSnapState::UserInteraction);
521         break;
522             
523     case WheelEventStatus::UserScrollEnd:
524         beginScrollSnapAnimation(axis, ScrollSnapState::Snapping);
525         break;
526         
527     case WheelEventStatus::InertialScrollBegin:
528         // Begin tracking wheel deltas for glide prediction.
529         endScrollSnapAnimation(axis, ScrollSnapState::UserInteraction);
530         snapState.pushInitialWheelDelta(wheelDelta);
531         snapState.m_beginTrackingWheelDeltaOffset = m_client.scrollOffsetOnAxis(axis);
532         break;
533             
534     case WheelEventStatus::InertialScrolling:
535         // This check for DestinationReached ensures that we don't receive another set of momentum events after ending the last glide.
536         if (snapState.m_currentState != ScrollSnapState::Gliding && snapState.m_currentState != ScrollSnapState::DestinationReached) {
537             if (snapState.m_numWheelDeltasTracked < snapState.wheelDeltaWindowSize)
538                 snapState.pushInitialWheelDelta(wheelDelta);
539             
540             if (snapState.m_numWheelDeltasTracked == snapState.wheelDeltaWindowSize)
541                 beginScrollSnapAnimation(axis, ScrollSnapState::Gliding);
542         }
543         break;
544         
545     case WheelEventStatus::InertialScrollEnd:
546         snapState.clearInitialWheelDeltaWindow();
547         snapState.m_shouldOverrideWheelEvent = false;
548         break;
549
550     case WheelEventStatus::StatelessScrollEvent:
551         endScrollSnapAnimation(axis, ScrollSnapState::UserInteraction);
552         snapState.clearInitialWheelDeltaWindow();
553         snapState.m_shouldOverrideWheelEvent = false;
554         break;
555
556     case WheelEventStatus::Unknown:
557         ASSERT_NOT_REACHED();
558         break;
559     }
560 }
561
562 bool ScrollController::shouldOverrideWheelEvent(ScrollEventAxis axis, const PlatformWheelEvent& event) const
563 {
564     const ScrollSnapAnimatorState& snapState = scrollSnapPointState(axis);
565
566     return snapState.m_shouldOverrideWheelEvent && toWheelEventStatus(event.phase(), event.momentumPhase()) == WheelEventStatus::InertialScrolling;
567 }
568
569 bool ScrollController::processWheelEventForScrollSnap(const PlatformWheelEvent& wheelEvent)
570 {
571     if (m_verticalScrollSnapState) {
572         processWheelEventForScrollSnapOnAxis(ScrollEventAxis::Vertical, wheelEvent);
573         if (shouldOverrideWheelEvent(ScrollEventAxis::Vertical, wheelEvent))
574             return false;
575     }
576     if (m_horizontalScrollSnapState) {
577         processWheelEventForScrollSnapOnAxis(ScrollEventAxis::Horizontal, wheelEvent);
578         if (shouldOverrideWheelEvent(ScrollEventAxis::Horizontal, wheelEvent))
579             return false;
580     }
581
582     return true;
583 }
584
585 void ScrollController::updateScrollAnimatorsAndTimers(const ScrollableArea& scrollableArea)
586 {
587     // FIXME: Currently, scroll snap animators are recreated even though the snap offsets alone can be updated.
588     if (scrollableArea.horizontalSnapOffsets())
589         m_horizontalScrollSnapState = std::make_unique<ScrollSnapAnimatorState>(ScrollEventAxis::Horizontal, *scrollableArea.horizontalSnapOffsets());
590     else if (m_horizontalScrollSnapState)
591         m_horizontalScrollSnapState = nullptr;
592
593     if (scrollableArea.verticalSnapOffsets())
594         m_verticalScrollSnapState = std::make_unique<ScrollSnapAnimatorState>(ScrollEventAxis::Vertical, *scrollableArea.verticalSnapOffsets());
595     else if (m_verticalScrollSnapState)
596         m_verticalScrollSnapState = nullptr;
597 }
598
599 void ScrollController::updateScrollSnapPoints(ScrollEventAxis axis, const Vector<LayoutUnit>& snapPoints)
600 {
601     // FIXME: Currently, scroll snap animators are recreated even though the snap offsets alone can be updated.
602     if (axis == ScrollEventAxis::Horizontal)
603         m_horizontalScrollSnapState = !snapPoints.isEmpty() ? std::make_unique<ScrollSnapAnimatorState>(ScrollEventAxis::Horizontal, snapPoints) : nullptr;
604
605     if (axis == ScrollEventAxis::Vertical)
606         m_verticalScrollSnapState = !snapPoints.isEmpty() ? std::make_unique<ScrollSnapAnimatorState>(ScrollEventAxis::Vertical, snapPoints) : nullptr;
607 }
608
609 void ScrollController::startScrollSnapTimer(ScrollEventAxis axis)
610 {
611     RunLoop::Timer<ScrollController>& scrollSnapTimer = axis == ScrollEventAxis::Horizontal ? m_horizontalScrollSnapTimer : m_verticalScrollSnapTimer;
612     if (!scrollSnapTimer.isActive()) {
613         m_client.startScrollSnapTimer(axis);
614         scrollSnapTimer.startRepeating(1.0 / 60.0);
615     }
616 }
617
618 void ScrollController::stopScrollSnapTimer(ScrollEventAxis axis)
619 {
620     m_client.stopScrollSnapTimer(axis);
621     RunLoop::Timer<ScrollController>& scrollSnapTimer = axis == ScrollEventAxis::Horizontal ? m_horizontalScrollSnapTimer : m_verticalScrollSnapTimer;
622     scrollSnapTimer.stop();
623 }
624
625 void ScrollController::horizontalScrollSnapTimerFired()
626 {
627     scrollSnapAnimationUpdate(ScrollEventAxis::Horizontal);
628 }
629
630 void ScrollController::verticalScrollSnapTimerFired()
631 {
632     scrollSnapAnimationUpdate(ScrollEventAxis::Vertical);
633 }
634
635 void ScrollController::scrollSnapAnimationUpdate(ScrollEventAxis axis)
636 {
637     if (axis == ScrollEventAxis::Horizontal && !m_horizontalScrollSnapState)
638         return;
639
640     if (axis == ScrollEventAxis::Vertical && !m_verticalScrollSnapState)
641         return;
642
643     ScrollSnapAnimatorState& snapState = scrollSnapPointState(axis);
644     if (snapState.m_currentState == ScrollSnapState::DestinationReached)
645         return;
646     
647     ASSERT(snapState.m_currentState == ScrollSnapState::Gliding || snapState.m_currentState == ScrollSnapState::Snapping);
648     float delta = snapState.m_currentState == ScrollSnapState::Snapping ? computeSnapDelta(axis) : computeGlideDelta(axis);
649     if (delta)
650         m_client.immediateScrollOnAxis(axis, delta);
651     else
652         endScrollSnapAnimation(axis, ScrollSnapState::DestinationReached);
653 }
654
655 static inline float projectedInertialScrollDistance(float initialWheelDelta)
656 {
657     // FIXME: Experiments with inertial scrolling show a fairly consistent linear relationship between initial wheel delta and total distance scrolled.
658     // In the future, we'll want to find a more accurate way of inertial scroll prediction.
659     return inertialScrollPredictionFactor * initialWheelDelta;
660 }
661
662 void ScrollController::initializeGlideParameters(ScrollEventAxis axis, bool shouldIncreaseInitialWheelDelta)
663 {
664     ScrollSnapAnimatorState& snapState = scrollSnapPointState(axis);
665     
666     // FIXME: Glide boost is a hacky way to speed up natural scrolling velocity. We should find a better way to accomplish this.
667     if (shouldIncreaseInitialWheelDelta)
668         snapState.m_glideInitialWheelDelta *= glideBoostMultiplier;
669     
670     // FIXME: There must be a better way to determine a good target delta than multiplying by a factor and clamping to min/max values.
671     float targetFinalWheelDelta = initialToFinalMomentumFactor * (snapState.m_glideInitialWheelDelta < 0 ? -snapState.m_glideInitialWheelDelta : snapState.m_glideInitialWheelDelta);
672     targetFinalWheelDelta = (snapState.m_glideInitialWheelDelta > 0 ? 1 : -1) * std::min(std::max(targetFinalWheelDelta, minTargetWheelDelta), maxTargetWheelDelta);
673     snapState.m_glideMagnitude = (snapState.m_glideInitialWheelDelta + targetFinalWheelDelta) / 2;
674     snapState.m_glidePhaseShift = acos((snapState.m_glideInitialWheelDelta - targetFinalWheelDelta) / (snapState.m_glideInitialWheelDelta + targetFinalWheelDelta));
675 }
676
677 void ScrollController::beginScrollSnapAnimation(ScrollEventAxis axis, ScrollSnapState newState)
678 {
679     ASSERT(newState == ScrollSnapState::Gliding || newState == ScrollSnapState::Snapping);
680     
681     ScrollSnapAnimatorState& snapState = scrollSnapPointState(axis);
682
683     LayoutUnit offset = m_client.scrollOffsetOnAxis(axis);
684     float initialWheelDelta = newState == ScrollSnapState::Gliding ? snapState.averageInitialWheelDelta() : 0;
685     LayoutUnit projectedScrollDestination = newState == ScrollSnapState::Gliding ? snapState.m_beginTrackingWheelDeltaOffset + LayoutUnit(projectedInertialScrollDistance(initialWheelDelta)) : offset;
686     if (snapState.m_snapOffsets.isEmpty())
687         return;
688
689     float scaleFactor = m_client.pageScaleFactor();
690     
691     projectedScrollDestination = std::min(std::max(LayoutUnit(projectedScrollDestination / scaleFactor), snapState.m_snapOffsets.first()), snapState.m_snapOffsets.last());
692     snapState.m_initialOffset = offset;
693     snapState.m_targetOffset = scaleFactor * closestSnapOffset<LayoutUnit, float>(snapState.m_snapOffsets, projectedScrollDestination, initialWheelDelta);
694     if (snapState.m_initialOffset == snapState.m_targetOffset)
695         return;
696     
697     snapState.m_currentState = newState;
698     if (newState == ScrollSnapState::Gliding) {
699         snapState.m_shouldOverrideWheelEvent = true;
700         snapState.m_glideInitialWheelDelta = initialWheelDelta;
701         bool glideRequiresBoost;
702         if (initialWheelDelta > 0)
703             glideRequiresBoost = projectedScrollDestination - offset < snapState.m_targetOffset - projectedScrollDestination;
704         else
705             glideRequiresBoost = offset - projectedScrollDestination < projectedScrollDestination - snapState.m_targetOffset;
706         
707         initializeGlideParameters(axis, glideRequiresBoost);
708         snapState.clearInitialWheelDeltaWindow();
709     }
710     startScrollSnapTimer(axis);
711 }
712
713 void ScrollController::endScrollSnapAnimation(ScrollEventAxis axis, ScrollSnapState newState)
714 {
715     ASSERT(newState == ScrollSnapState::DestinationReached || newState == ScrollSnapState::UserInteraction);
716
717     ScrollSnapAnimatorState& snapState = scrollSnapPointState(axis);
718
719     if (snapState.m_currentState == ScrollSnapState::Gliding)
720         snapState.clearInitialWheelDeltaWindow();
721     
722     snapState.m_currentState = newState;
723     stopScrollSnapTimer(axis);
724 }
725
726 static inline float snapProgress(const LayoutUnit& offset, const ScrollSnapAnimatorState& snapState)
727 {
728     const float distanceTraveled = static_cast<float>(offset - snapState.m_initialOffset);
729     const float totalDistance = static_cast<float>(snapState.m_targetOffset - snapState.m_initialOffset);
730
731     return distanceTraveled / totalDistance;
732 }
733
734 static inline float clampedSnapMagnitude(float thresholdedDistance)
735 {
736     return snapMagnitudeMin + (snapMagnitudeMax - snapMagnitudeMin) * (thresholdedDistance - snapThresholdLow) / (snapThresholdHigh - snapThresholdLow);
737 }
738
739 // Computes the amount to scroll by when performing a "snap" operation, i.e. when a user releases the trackpad without flicking. The snap delta
740 // is a function of progress t, where t is equal to DISTANCE_TRAVELED / TOTAL_DISTANCE, DISTANCE_TRAVELED is the distance from the initialOffset
741 // to the current offset, and TOTAL_DISTANCE is the distance from initialOffset to targetOffset. The snapping equation is as follows:
742 // delta(t) = MAGNITUDE * sin(PI * t). MAGNITUDE indicates the top speed reached near the middle of the animation (t = 0.5), and is a linear
743 // relationship of the distance traveled, clamped by arbitrary min and max values.
744 float ScrollController::computeSnapDelta(ScrollEventAxis axis) const
745 {
746     const ScrollSnapAnimatorState& snapState = scrollSnapPointState(axis);
747
748     LayoutUnit offset = m_client.scrollOffsetOnAxis(axis);
749     bool canComputeSnap =  (snapState.m_initialOffset <= offset && offset < snapState.m_targetOffset) || (snapState.m_targetOffset < offset && offset <= snapState.m_initialOffset);
750     if (snapState.m_currentState != ScrollSnapState::Snapping || !canComputeSnap)
751         return 0;
752     
753     float progress = snapProgress(offset, snapState);
754
755     // Threshold the distance before computing magnitude, so only distances within a certain range are considered.
756     int sign = snapState.m_initialOffset < snapState.m_targetOffset ? 1 : -1;
757     float thresholdedDistance = std::min(std::max<float>((snapState.m_targetOffset - snapState.m_initialOffset) * sign, snapThresholdLow), snapThresholdHigh);
758
759     float magnitude = clampedSnapMagnitude(thresholdedDistance);
760
761     float rawSnapDelta = std::max<float>(1, magnitude * std::sin(piFloat * progress));
762     if ((snapState.m_targetOffset < offset && offset - rawSnapDelta < snapState.m_targetOffset) || (snapState.m_targetOffset > offset && offset + rawSnapDelta > snapState.m_targetOffset))
763         return snapState.m_targetOffset - offset;
764     
765     return sign * rawSnapDelta;
766 }
767
768 static inline float snapGlide(float progress, const ScrollSnapAnimatorState& snapState)
769 {
770     // FIXME: We might want to investigate why -m_glidePhaseShift results in the behavior we want.
771     return ceil(snapState.m_glideMagnitude * (1.0f + std::cos(piFloat * progress - snapState.m_glidePhaseShift)));
772 }
773
774 // Computes the amount to scroll by when performing a "glide" operation, i.e. when a user releases the trackpad with an initial velocity. Here,
775 // we want the scroll offset to animate directly to the snap point.
776 //
777 // The snap delta is a function of progress t, where: (1) t is equal to DISTANCE_TRAVELED / TOTAL_DISTANCE, (2) DISTANCE_TRAVELED is the distance
778 // from the initialOffset to the current offset, and (3) TOTAL_DISTANCE is the distance from initialOffset to targetOffset.
779 //
780 // The general model of our gliding equation is delta(t) = MAGNITUDE * (1 + cos(PI * t + PHASE_SHIFT)). This was determined after examining the
781 // momentum velocity curve as a function of progress. To compute MAGNITUDE and PHASE_SHIFT, we use initial velocity V0 and the final velocity VF,
782 // both as wheel deltas (pixels per timestep). VF should be a small value (< 10) chosen based on the initial velocity and TOTAL_DISTANCE.
783 // We also enforce the following constraints for the gliding equation:
784 //   1. delta(0) = V0, since we want the initial velocity of the gliding animation to match the user's scroll velocity. The exception to this is
785 //      when the glide velocity is not enough to naturally reach the next snap point, and thus requires a boost (see initializeGlideParameters)
786 //   2. delta(1) = VF, since at t=1, the animation has completed and we want the last wheel delta to match the final velocity VF. Note that this
787 //      doesn't guarantee that the final velocity will be exactly VF. However, assuming that the initial velocity is much less than TOTAL_DISTANCE,
788 //      the last wheel delta will be very close, if not the same, as VF.
789 // For MAGNITUDE = (V0 + VF) / 2 and PHASE_SHIFT = arccos((V0 - VF) / (V0 + VF)), observe that delta(0) and delta(1) evaluate respectively to V0
790 // and VF. Thus, we can express our gliding equation all in terms of V0, VF and t.
791 float ScrollController::computeGlideDelta(ScrollEventAxis axis) const
792 {
793     const ScrollSnapAnimatorState& snapState = scrollSnapPointState(axis);
794
795     LayoutUnit offset = m_client.scrollOffsetOnAxis(axis);
796     bool canComputeGlide = (snapState.m_initialOffset <= offset && offset < snapState.m_targetOffset) || (snapState.m_targetOffset < offset && offset <= snapState.m_initialOffset);
797     if (snapState.m_currentState != ScrollSnapState::Gliding || !canComputeGlide)
798         return 0;
799
800     const float progress = snapProgress(offset, snapState);
801     const float rawGlideDelta = snapGlide(progress, snapState);
802
803     float glideDelta = snapState.m_initialOffset < snapState.m_targetOffset ? std::max<float>(rawGlideDelta, 1) : std::min<float>(rawGlideDelta, -1);
804     if ((snapState.m_initialOffset < snapState.m_targetOffset && offset + glideDelta > snapState.m_targetOffset) || (snapState.m_initialOffset > snapState.m_targetOffset && offset + glideDelta < snapState.m_targetOffset))
805         return snapState.m_targetOffset - offset;
806     
807     return glideDelta;
808 }
809 #endif
810
811 } // namespace WebCore
812
813 #endif // ENABLE(RUBBER_BANDING)