1/*
2 * Copyright (c) 2011, Google 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 are
6 * met:
7 *
8 *     * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 *     * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 *     * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31#include "config.h"
32
33#if ENABLE(SMOOTH_SCROLLING)
34
35#include "ScrollAnimatorNone.h"
36
37#include "FloatPoint.h"
38#include "NotImplemented.h"
39#include <wtf/OwnArrayPtr.h>
40#include "ScrollableArea.h"
41#include "ScrollbarTheme.h"
42#include <algorithm>
43#include <wtf/CurrentTime.h>
44#include <wtf/PassOwnPtr.h>
45
46#if ENABLE(GESTURE_EVENTS)
47#include "PlatformGestureEvent.h"
48#endif
49
50using namespace std;
51
52namespace WebCore {
53
54const double kFrameRate = 60;
55const double kTickTime = 1 / kFrameRate;
56const double kMinimumTimerInterval = .001;
57const double kZoomTicks = 11;
58
59#if !(PLATFORM(BLACKBERRY))
60PassOwnPtr<ScrollAnimator> ScrollAnimator::create(ScrollableArea* scrollableArea)
61{
62    if (scrollableArea && scrollableArea->scrollAnimatorEnabled())
63        return adoptPtr(new ScrollAnimatorNone(scrollableArea));
64    return adoptPtr(new ScrollAnimator(scrollableArea));
65}
66#endif
67
68ScrollAnimatorNone::Parameters::Parameters()
69    : m_isEnabled(false)
70{
71}
72
73ScrollAnimatorNone::Parameters::Parameters(bool isEnabled, double animationTime, double repeatMinimumSustainTime, Curve attackCurve, double attackTime, Curve releaseCurve, double releaseTime, Curve coastTimeCurve, double maximumCoastTime)
74    : m_isEnabled(isEnabled)
75    , m_animationTime(animationTime)
76    , m_repeatMinimumSustainTime(repeatMinimumSustainTime)
77    , m_attackCurve(attackCurve)
78    , m_attackTime(attackTime)
79    , m_releaseCurve(releaseCurve)
80    , m_releaseTime(releaseTime)
81    , m_coastTimeCurve(coastTimeCurve)
82    , m_maximumCoastTime(maximumCoastTime)
83{
84}
85
86double ScrollAnimatorNone::PerAxisData::curveAt(Curve curve, double t)
87{
88    switch (curve) {
89    case Linear:
90        return t;
91    case Quadratic:
92        return t * t;
93    case Cubic:
94        return t * t * t;
95    case Quartic:
96        return t * t * t * t;
97    case Bounce:
98        // Time base is chosen to keep the bounce points simpler:
99        // 1 (half bounce coming in) + 1 + .5 + .25
100        const double kTimeBase = 2.75;
101        const double kTimeBaseSquared = kTimeBase * kTimeBase;
102        if (t < 1 / kTimeBase)
103            return kTimeBaseSquared * t * t;
104        if (t < 2 / kTimeBase) {
105            // Invert a [-.5,.5] quadratic parabola, center it in [1,2].
106            double t1 = t - 1.5 / kTimeBase;
107            const double kParabolaAtEdge = 1 - .5 * .5;
108            return kTimeBaseSquared * t1 * t1 + kParabolaAtEdge;
109        }
110        if (t < 2.5 / kTimeBase) {
111            // Invert a [-.25,.25] quadratic parabola, center it in [2,2.5].
112            double t2 = t - 2.25 / kTimeBase;
113            const double kParabolaAtEdge = 1 - .25 * .25;
114            return kTimeBaseSquared * t2 * t2 + kParabolaAtEdge;
115        }
116            // Invert a [-.125,.125] quadratic parabola, center it in [2.5,2.75].
117        const double kParabolaAtEdge = 1 - .125 * .125;
118        t -= 2.625 / kTimeBase;
119        return kTimeBaseSquared * t * t + kParabolaAtEdge;
120    }
121    ASSERT_NOT_REACHED();
122    return 0;
123}
124
125double ScrollAnimatorNone::PerAxisData::attackCurve(Curve curve, double deltaTime, double curveT, double startPosition, double attackPosition)
126{
127    double t = deltaTime / curveT;
128    double positionFactor = curveAt(curve, t);
129    return startPosition + positionFactor * (attackPosition - startPosition);
130}
131
132double ScrollAnimatorNone::PerAxisData::releaseCurve(Curve curve, double deltaTime, double curveT, double releasePosition, double desiredPosition)
133{
134    double t = deltaTime / curveT;
135    double positionFactor = 1 - curveAt(curve, 1 - t);
136    return releasePosition + (positionFactor * (desiredPosition - releasePosition));
137}
138
139double ScrollAnimatorNone::PerAxisData::coastCurve(Curve curve, double factor)
140{
141    return 1 - curveAt(curve, 1 - factor);
142}
143
144double ScrollAnimatorNone::PerAxisData::curveIntegralAt(Curve curve, double t)
145{
146    switch (curve) {
147    case Linear:
148        return t * t / 2;
149    case Quadratic:
150        return t * t * t / 3;
151    case Cubic:
152        return t * t * t * t / 4;
153    case Quartic:
154        return t * t * t * t * t / 5;
155    case Bounce:
156        const double kTimeBase = 2.75;
157        const double kTimeBaseSquared = kTimeBase * kTimeBase;
158        const double kTimeBaseSquaredOverThree = kTimeBaseSquared / 3;
159        double area;
160        double t1 = min(t, 1 / kTimeBase);
161        area = kTimeBaseSquaredOverThree * t1 * t1 * t1;
162        if (t < 1 / kTimeBase)
163            return area;
164
165        t1 = min(t - 1 / kTimeBase, 1 / kTimeBase);
166        // The integral of kTimeBaseSquared * (t1 - .5 / kTimeBase) * (t1 - .5 / kTimeBase) + kParabolaAtEdge
167        const double kSecondInnerOffset = kTimeBaseSquared * .5 / kTimeBase;
168        double bounceArea = t1 * (t1 * (kTimeBaseSquaredOverThree * t1 - kSecondInnerOffset) + 1);
169        area += bounceArea;
170        if (t < 2 / kTimeBase)
171            return area;
172
173        t1 = min(t - 2 / kTimeBase, 0.5 / kTimeBase);
174        // The integral of kTimeBaseSquared * (t1 - .25 / kTimeBase) * (t1 - .25 / kTimeBase) + kParabolaAtEdge
175        const double kThirdInnerOffset = kTimeBaseSquared * .25 / kTimeBase;
176        bounceArea =  t1 * (t1 * (kTimeBaseSquaredOverThree * t1 - kThirdInnerOffset) + 1);
177        area += bounceArea;
178        if (t < 2.5 / kTimeBase)
179            return area;
180
181        t1 = t - 2.5 / kTimeBase;
182        // The integral of kTimeBaseSquared * (t1 - .125 / kTimeBase) * (t1 - .125 / kTimeBase) + kParabolaAtEdge
183        const double kFourthInnerOffset = kTimeBaseSquared * .125 / kTimeBase;
184        bounceArea = t1 * (t1 * (kTimeBaseSquaredOverThree * t1 - kFourthInnerOffset) + 1);
185        area += bounceArea;
186        return area;
187    }
188    ASSERT_NOT_REACHED();
189    return 0;
190}
191
192double ScrollAnimatorNone::PerAxisData::attackArea(Curve curve, double startT, double endT)
193{
194    double startValue = curveIntegralAt(curve, startT);
195    double endValue = curveIntegralAt(curve, endT);
196    return endValue - startValue;
197}
198
199double ScrollAnimatorNone::PerAxisData::releaseArea(Curve curve, double startT, double endT)
200{
201    double startValue = curveIntegralAt(curve, 1 - endT);
202    double endValue = curveIntegralAt(curve, 1 - startT);
203    return endValue - startValue;
204}
205
206ScrollAnimatorNone::PerAxisData::PerAxisData(ScrollAnimatorNone* parent, float* currentPosition, int visibleLength)
207    : m_currentPosition(currentPosition)
208    , m_visibleLength(visibleLength)
209{
210    reset();
211}
212
213void ScrollAnimatorNone::PerAxisData::reset()
214{
215    m_currentVelocity = 0;
216
217    m_desiredPosition = 0;
218    m_desiredVelocity = 0;
219
220    m_startPosition = 0;
221    m_startTime = 0;
222    m_startVelocity = 0;
223
224    m_animationTime = 0;
225    m_lastAnimationTime = 0;
226
227    m_attackPosition = 0;
228    m_attackTime = 0;
229    m_attackCurve = Quadratic;
230
231    m_releasePosition = 0;
232    m_releaseTime = 0;
233    m_releaseCurve = Quadratic;
234}
235
236
237bool ScrollAnimatorNone::PerAxisData::updateDataFromParameters(float step, float multiplier, float scrollableSize, double currentTime, Parameters* parameters)
238{
239    float delta = step * multiplier;
240    if (!m_startTime || !delta || (delta < 0) != (m_desiredPosition - *m_currentPosition < 0)) {
241        m_desiredPosition = *m_currentPosition;
242        m_startTime = 0;
243    }
244    float newPosition = m_desiredPosition + delta;
245
246    if (newPosition < 0 || newPosition > scrollableSize)
247        newPosition = max(min(newPosition, scrollableSize), 0.0f);
248
249    if (newPosition == m_desiredPosition)
250        return false;
251
252    m_desiredPosition = newPosition;
253
254    if (!m_startTime) {
255        m_attackTime = parameters->m_attackTime;
256        m_attackCurve = parameters->m_attackCurve;
257    }
258    m_animationTime = parameters->m_animationTime;
259    m_releaseTime = parameters->m_releaseTime;
260    m_releaseCurve = parameters->m_releaseCurve;
261
262    // Prioritize our way out of over constraint.
263    if (m_attackTime + m_releaseTime > m_animationTime) {
264        if (m_releaseTime > m_animationTime)
265            m_releaseTime = m_animationTime;
266        m_attackTime = m_animationTime - m_releaseTime;
267    }
268
269    if (!m_startTime) {
270        // FIXME: This should be the time from the event that got us here.
271        m_startTime = currentTime - kTickTime / 2;
272        m_startPosition = *m_currentPosition;
273        m_lastAnimationTime = m_startTime;
274    }
275    m_startVelocity = m_currentVelocity;
276
277    double remainingDelta = m_desiredPosition - *m_currentPosition;
278
279    double attackAreaLeft = 0;
280
281    double deltaTime = m_lastAnimationTime - m_startTime;
282    double attackTimeLeft = max(0., m_attackTime - deltaTime);
283    double timeLeft = m_animationTime - deltaTime;
284    double minTimeLeft = m_releaseTime + min(parameters->m_repeatMinimumSustainTime, m_animationTime - m_releaseTime - attackTimeLeft);
285    if (timeLeft < minTimeLeft) {
286        m_animationTime = deltaTime + minTimeLeft;
287        timeLeft = minTimeLeft;
288    }
289
290    if (parameters->m_maximumCoastTime > (parameters->m_repeatMinimumSustainTime + parameters->m_releaseTime)) {
291        double targetMaxCoastVelocity = m_visibleLength * .25 * kFrameRate;
292        // This needs to be as minimal as possible while not being intrusive to page up/down.
293        double minCoastDelta = m_visibleLength;
294
295        if (fabs(remainingDelta) > minCoastDelta) {
296            double maxCoastDelta = parameters->m_maximumCoastTime * targetMaxCoastVelocity;
297            double coastFactor = min(1., (fabs(remainingDelta) - minCoastDelta) / (maxCoastDelta - minCoastDelta));
298
299            // We could play with the curve here - linear seems a little soft. Initial testing makes me want to feed into the sustain time more aggressively.
300            double coastMinTimeLeft = min(parameters->m_maximumCoastTime, minTimeLeft + coastCurve(parameters->m_coastTimeCurve, coastFactor) * (parameters->m_maximumCoastTime - minTimeLeft));
301
302            double additionalTime = max(0., coastMinTimeLeft - minTimeLeft);
303            if (additionalTime) {
304                double additionalReleaseTime = min(additionalTime, parameters->m_releaseTime / (parameters->m_releaseTime + parameters->m_repeatMinimumSustainTime) * additionalTime);
305                m_releaseTime = parameters->m_releaseTime + additionalReleaseTime;
306                m_animationTime = deltaTime + coastMinTimeLeft;
307                timeLeft = coastMinTimeLeft;
308            }
309        }
310    }
311
312    double releaseTimeLeft = min(timeLeft, m_releaseTime);
313    double sustainTimeLeft = max(0., timeLeft - releaseTimeLeft - attackTimeLeft);
314
315    if (attackTimeLeft) {
316        double attackSpot = deltaTime / m_attackTime;
317        attackAreaLeft = attackArea(m_attackCurve, attackSpot, 1) * m_attackTime;
318    }
319
320    double releaseSpot = (m_releaseTime - releaseTimeLeft) / m_releaseTime;
321    double releaseAreaLeft  = releaseArea(m_releaseCurve, releaseSpot, 1) * m_releaseTime;
322
323    m_desiredVelocity = remainingDelta / (attackAreaLeft + sustainTimeLeft + releaseAreaLeft);
324    m_releasePosition = m_desiredPosition - m_desiredVelocity * releaseAreaLeft;
325    if (attackAreaLeft)
326        m_attackPosition = m_startPosition + m_desiredVelocity * attackAreaLeft;
327    else
328        m_attackPosition = m_releasePosition - (m_animationTime - m_releaseTime - m_attackTime) * m_desiredVelocity;
329
330    if (sustainTimeLeft) {
331        double roundOff = m_releasePosition - ((attackAreaLeft ? m_attackPosition : *m_currentPosition) + m_desiredVelocity * sustainTimeLeft);
332        m_desiredVelocity += roundOff / sustainTimeLeft;
333    }
334
335    return true;
336}
337
338// FIXME: Add in jank detection trace events into this function.
339bool ScrollAnimatorNone::PerAxisData::animateScroll(double currentTime)
340{
341    double lastScrollInterval = currentTime - m_lastAnimationTime;
342    if (lastScrollInterval < kMinimumTimerInterval)
343        return true;
344
345    m_lastAnimationTime = currentTime;
346
347    double deltaTime = currentTime - m_startTime;
348    double newPosition = *m_currentPosition;
349
350    if (deltaTime > m_animationTime) {
351        *m_currentPosition = m_desiredPosition;
352        reset();
353        return false;
354    }
355    if (deltaTime < m_attackTime)
356        newPosition = attackCurve(m_attackCurve, deltaTime, m_attackTime, m_startPosition, m_attackPosition);
357    else if (deltaTime < (m_animationTime - m_releaseTime))
358        newPosition = m_attackPosition + (deltaTime - m_attackTime) * m_desiredVelocity;
359    else {
360        // release is based on targeting the exact final position.
361        double releaseDeltaT = deltaTime - (m_animationTime - m_releaseTime);
362        newPosition = releaseCurve(m_releaseCurve, releaseDeltaT, m_releaseTime, m_releasePosition, m_desiredPosition);
363    }
364
365    // Normalize velocity to a per second amount. Could be used to check for jank.
366    if (lastScrollInterval > 0)
367        m_currentVelocity = (newPosition - *m_currentPosition) / lastScrollInterval;
368    *m_currentPosition = newPosition;
369
370    return true;
371}
372
373void ScrollAnimatorNone::PerAxisData::updateVisibleLength(int visibleLength)
374{
375    m_visibleLength = visibleLength;
376}
377
378ScrollAnimatorNone::ScrollAnimatorNone(ScrollableArea* scrollableArea)
379    : ScrollAnimator(scrollableArea)
380    , m_horizontalData(this, &m_currentPosX, scrollableArea->visibleWidth())
381    , m_verticalData(this, &m_currentPosY, scrollableArea->visibleHeight())
382    , m_startTime(0)
383#if USE(REQUEST_ANIMATION_FRAME_TIMER)
384    , m_animationTimer(this, &ScrollAnimatorNone::animationTimerFired)
385#else
386    , m_animationActive(false)
387#endif
388{
389}
390
391ScrollAnimatorNone::~ScrollAnimatorNone()
392{
393    stopAnimationTimerIfNeeded();
394}
395
396ScrollAnimatorNone::Parameters ScrollAnimatorNone::parametersForScrollGranularity(ScrollGranularity granularity) const
397{
398#if !PLATFORM(QT)
399    switch (granularity) {
400    case ScrollByDocument:
401        return Parameters(true, 20 * kTickTime, 10 * kTickTime, Cubic, 10 * kTickTime, Cubic, 10 * kTickTime, Linear, 1);
402    case ScrollByLine:
403        return Parameters(true, 10 * kTickTime, 7 * kTickTime, Cubic, 3 * kTickTime, Cubic, 3 * kTickTime, Linear, 1);
404    case ScrollByPage:
405        return Parameters(true, 15 * kTickTime, 10 * kTickTime, Cubic, 5 * kTickTime, Cubic, 5 * kTickTime, Linear, 1);
406    case ScrollByPixel:
407        return Parameters(true, 11 * kTickTime, 2 * kTickTime, Cubic, 3 * kTickTime, Cubic, 3 * kTickTime, Quadratic, 1.25);
408    default:
409        ASSERT_NOT_REACHED();
410    }
411#else
412    // This is a slightly different strategy for the animation with a steep attack curve and natural release curve.
413    // The fast acceleration makes the animation look more responsive to user input.
414    switch (granularity) {
415    case ScrollByDocument:
416        return Parameters(true, 20 * kTickTime, 10 * kTickTime, Cubic, 6 * kTickTime, Quadratic, 10 * kTickTime, Quadratic, 22 * kTickTime);
417    case ScrollByLine:
418        return Parameters(true, 6 * kTickTime, 5 * kTickTime, Cubic, 1 * kTickTime, Quadratic, 4 * kTickTime, Linear, 1);
419    case ScrollByPage:
420        return Parameters(true, 12 * kTickTime, 10 * kTickTime, Cubic, 3 * kTickTime, Quadratic, 6 * kTickTime, Linear, 1);
421    case ScrollByPixel:
422        return Parameters(true, 8 * kTickTime, 3 * kTickTime, Cubic, 2 * kTickTime, Quadratic, 5 * kTickTime, Quadratic, 1.25);
423    default:
424        ASSERT_NOT_REACHED();
425    }
426#endif
427    return Parameters();
428}
429
430bool ScrollAnimatorNone::scroll(ScrollbarOrientation orientation, ScrollGranularity granularity, float step, float multiplier)
431{
432    if (!m_scrollableArea->scrollAnimatorEnabled())
433        return ScrollAnimator::scroll(orientation, granularity, step, multiplier);
434
435    // FIXME: get the type passed in. MouseWheel could also be by line, but should still have different
436    // animation parameters than the keyboard.
437    Parameters parameters;
438    switch (granularity) {
439    case ScrollByDocument:
440    case ScrollByLine:
441    case ScrollByPage:
442    case ScrollByPixel:
443        parameters = parametersForScrollGranularity(granularity);
444        break;
445    case ScrollByPrecisePixel:
446        return ScrollAnimator::scroll(orientation, granularity, step, multiplier);
447    }
448
449    // If the individual input setting is disabled, bail.
450    if (!parameters.m_isEnabled)
451        return ScrollAnimator::scroll(orientation, granularity, step, multiplier);
452
453    // This is an animatable scroll. Set the animation in motion using the appropriate parameters.
454    float scrollableSize = static_cast<float>(m_scrollableArea->scrollSize(orientation));
455
456    PerAxisData& data = (orientation == VerticalScrollbar) ? m_verticalData : m_horizontalData;
457    bool needToScroll = data.updateDataFromParameters(step, multiplier, scrollableSize, WTF::monotonicallyIncreasingTime(), &parameters);
458    if (needToScroll && !animationTimerActive()) {
459        m_startTime = data.m_startTime;
460        animationWillStart();
461        animationTimerFired();
462    }
463    return needToScroll;
464}
465
466void ScrollAnimatorNone::scrollToOffsetWithoutAnimation(const FloatPoint& offset)
467{
468    stopAnimationTimerIfNeeded();
469
470    FloatSize delta = FloatSize(offset.x() - *m_horizontalData.m_currentPosition, offset.y() - *m_verticalData.m_currentPosition);
471
472    m_horizontalData.reset();
473    *m_horizontalData.m_currentPosition = offset.x();
474    m_horizontalData.m_desiredPosition = offset.x();
475
476    m_verticalData.reset();
477    *m_verticalData.m_currentPosition = offset.y();
478    m_verticalData.m_desiredPosition = offset.y();
479
480    notifyPositionChanged(delta);
481}
482
483#if !USE(REQUEST_ANIMATION_FRAME_TIMER)
484void ScrollAnimatorNone::cancelAnimations()
485{
486    m_animationActive = false;
487}
488
489void ScrollAnimatorNone::serviceScrollAnimations()
490{
491    if (m_animationActive)
492        animationTimerFired();
493}
494#endif
495
496void ScrollAnimatorNone::willEndLiveResize()
497{
498    updateVisibleLengths();
499}
500
501void ScrollAnimatorNone::didAddVerticalScrollbar(Scrollbar*)
502{
503    updateVisibleLengths();
504}
505
506void ScrollAnimatorNone::didAddHorizontalScrollbar(Scrollbar*)
507{
508    updateVisibleLengths();
509}
510
511void ScrollAnimatorNone::updateVisibleLengths()
512{
513    m_horizontalData.updateVisibleLength(scrollableArea()->visibleWidth());
514    m_verticalData.updateVisibleLength(scrollableArea()->visibleHeight());
515}
516
517#if USE(REQUEST_ANIMATION_FRAME_TIMER)
518void ScrollAnimatorNone::animationTimerFired(Timer<ScrollAnimatorNone>* timer)
519{
520    animationTimerFired();
521}
522#endif
523
524void ScrollAnimatorNone::animationTimerFired()
525{
526    double currentTime = WTF::monotonicallyIncreasingTime();
527    double deltaToNextFrame = ceil((currentTime - m_startTime) * kFrameRate) / kFrameRate - (currentTime - m_startTime);
528    currentTime += deltaToNextFrame;
529
530    bool continueAnimation = false;
531    if (m_horizontalData.m_startTime && m_horizontalData.animateScroll(currentTime))
532        continueAnimation = true;
533    if (m_verticalData.m_startTime && m_verticalData.animateScroll(currentTime))
534        continueAnimation = true;
535
536    if (continueAnimation)
537#if USE(REQUEST_ANIMATION_FRAME_TIMER)
538        startNextTimer(max(kMinimumTimerInterval, deltaToNextFrame));
539#else
540        startNextTimer();
541    else
542        m_animationActive = false;
543#endif
544
545    notifyPositionChanged(FloatSize());
546
547    if (!continueAnimation)
548        animationDidFinish();
549}
550
551#if USE(REQUEST_ANIMATION_FRAME_TIMER)
552void ScrollAnimatorNone::startNextTimer(double delay)
553{
554    m_animationTimer.startOneShot(delay);
555}
556#else
557void ScrollAnimatorNone::startNextTimer()
558{
559    if (scrollableArea()->scheduleAnimation())
560        m_animationActive = true;
561}
562#endif
563
564bool ScrollAnimatorNone::animationTimerActive()
565{
566#if USE(REQUEST_ANIMATION_FRAME_TIMER)
567    return m_animationTimer.isActive();
568#else
569    return m_animationActive;
570#endif
571}
572
573void ScrollAnimatorNone::stopAnimationTimerIfNeeded()
574{
575    if (animationTimerActive())
576#if USE(REQUEST_ANIMATION_FRAME_TIMER)
577        m_animationTimer.stop();
578#else
579        m_animationActive = false;
580#endif
581}
582
583} // namespace WebCore
584
585#endif // ENABLE(SMOOTH_SCROLLING)
586