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