initial import
[vuplus_webkit] / Source / WebCore / platform / mac / ScrollAnimatorMac.mm
1 /*
2  * Copyright (C) 2010, 2011 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
28 #if ENABLE(SMOOTH_SCROLLING)
29
30 #include "ScrollAnimatorMac.h"
31
32 #include "FloatPoint.h"
33 #include "NSScrollerImpDetails.h"
34 #include "PlatformGestureEvent.h"
35 #include "PlatformWheelEvent.h"
36 #include "ScrollView.h"
37 #include "ScrollableArea.h"
38 #include "ScrollbarTheme.h"
39 #include "ScrollbarThemeMac.h"
40 #include "WebCoreSystemInterface.h"
41 #include <wtf/PassOwnPtr.h>
42 #include <wtf/UnusedParam.h>
43
44 using namespace WebCore;
45 using namespace std;
46
47 @interface NSObject (ScrollAnimationHelperDetails)
48 - (id)initWithDelegate:(id)delegate;
49 - (void)_stopRun;
50 - (BOOL)_isAnimating;
51 - (NSPoint)targetOrigin;
52 - (CGFloat)_progress;
53 @end
54
55 @interface ScrollAnimationHelperDelegate : NSObject
56 {
57     WebCore::ScrollAnimatorMac* _animator;
58 }
59 - (id)initWithScrollAnimator:(WebCore::ScrollAnimatorMac*)scrollAnimator;
60 @end
61
62 static NSSize abs(NSSize size)
63 {
64     NSSize finalSize = size;
65     if (finalSize.width < 0)
66         finalSize.width = -finalSize.width;
67     if (finalSize.height < 0)
68         finalSize.height = -finalSize.height;
69     return finalSize;    
70 }
71
72 @implementation ScrollAnimationHelperDelegate
73
74 - (id)initWithScrollAnimator:(WebCore::ScrollAnimatorMac*)scrollAnimator
75 {
76     self = [super init];
77     if (!self)
78         return nil;
79
80     _animator = scrollAnimator;
81     return self;
82 }
83
84 - (void)scrollAnimatorDestroyed
85 {
86     _animator = 0;
87 }
88
89 - (NSRect)bounds
90 {
91     if (!_animator)
92         return NSZeroRect;
93
94     WebCore::FloatPoint currentPosition = _animator->currentPosition();
95     return NSMakeRect(currentPosition.x(), currentPosition.y(), 0, 0);
96 }
97
98 - (void)_immediateScrollToPoint:(NSPoint)newPosition
99 {
100     if (!_animator)
101         return;
102     _animator->immediateScrollToPointForScrollAnimation(newPosition);
103 }
104
105 - (NSPoint)_pixelAlignProposedScrollPosition:(NSPoint)newOrigin
106 {
107     return newOrigin;
108 }
109
110 - (NSSize)convertSizeToBase:(NSSize)size
111 {
112     return abs(size);
113 }
114
115 - (NSSize)convertSizeFromBase:(NSSize)size
116 {
117     return abs(size);
118 }
119
120 - (NSSize)convertSizeToBacking:(NSSize)size
121 {
122     return abs(size);
123 }
124
125 - (NSSize)convertSizeFromBacking:(NSSize)size
126 {
127     return abs(size);
128 }
129
130 - (id)superview
131 {
132     return nil;
133 }
134
135 - (id)documentView
136 {
137     return nil;
138 }
139
140 - (id)window
141 {
142     return nil;
143 }
144
145 - (void)_recursiveRecomputeToolTips
146 {
147 }
148
149 @end
150
151 #if USE(SCROLLBAR_PAINTER)
152
153 @interface ScrollbarPainterControllerDelegate : NSObject
154 {
155     WebCore::ScrollAnimatorMac* _animator;
156 }
157 - (id)initWithScrollAnimator:(WebCore::ScrollAnimatorMac*)scrollAnimator;
158 @end
159
160 @implementation ScrollbarPainterControllerDelegate
161
162 - (id)initWithScrollAnimator:(WebCore::ScrollAnimatorMac*)scrollAnimator
163 {
164     self = [super init];
165     if (!self)
166         return nil;
167     
168     _animator = scrollAnimator;
169     return self;
170 }
171
172 - (void)scrollAnimatorDestroyed
173 {
174     _animator = 0;
175 }
176
177 - (NSRect)contentAreaRectForScrollerImpPair:(id)scrollerImpPair
178 {
179     UNUSED_PARAM(scrollerImpPair);
180     if (!_animator)
181         return NSZeroRect;
182
183     WebCore::IntSize contentsSize = _animator->scrollableArea()->contentsSize();
184     return NSMakeRect(0, 0, contentsSize.width(), contentsSize.height());
185 }
186
187 - (BOOL)inLiveResizeForScrollerImpPair:(id)scrollerImpPair
188 {
189     UNUSED_PARAM(scrollerImpPair);
190     if (!_animator)
191         return NO;
192
193     return _animator->scrollableArea()->inLiveResize();
194 }
195
196 - (NSPoint)mouseLocationInContentAreaForScrollerImpPair:(id)scrollerImpPair
197 {
198     UNUSED_PARAM(scrollerImpPair);
199     if (!_animator)
200         return NSZeroPoint;
201
202     return _animator->scrollableArea()->currentMousePosition();
203 }
204
205 - (NSPoint)scrollerImpPair:(id)scrollerImpPair convertContentPoint:(NSPoint)pointInContentArea toScrollerImp:(id)scrollerImp
206 {
207     UNUSED_PARAM(scrollerImpPair);
208     if (!_animator)
209         return NSZeroPoint;
210
211     WebCore::Scrollbar* scrollbar = 0;
212     if ([scrollerImp isHorizontal])
213         scrollbar = _animator->scrollableArea()->horizontalScrollbar();
214     else 
215         scrollbar = _animator->scrollableArea()->verticalScrollbar();
216
217     // It is possible to have a null scrollbar here since it is possible for this delegate
218     // method to be called between the moment when a scrollbar has been set to 0 and the
219     // moment when its destructor has been called. We should probably de-couple some
220     // of the clean-up work in ScrollbarThemeMac::unregisterScrollbar() to avoid this
221     // issue.
222     if (!scrollbar)
223         return WebCore::IntPoint();
224     
225     return scrollbar->convertFromContainingView(WebCore::IntPoint(pointInContentArea));
226 }
227
228 - (void)scrollerImpPair:(id)scrollerImpPair setContentAreaNeedsDisplayInRect:(NSRect)rect
229 {
230     UNUSED_PARAM(scrollerImpPair);
231     UNUSED_PARAM(rect);
232 }
233
234 - (void)scrollerImpPair:(id)scrollerImpPair updateScrollerStyleForNewRecommendedScrollerStyle:(NSScrollerStyle)newRecommendedScrollerStyle
235 {
236     if (!_animator)
237         return;
238
239     [scrollerImpPair setScrollerStyle:newRecommendedScrollerStyle];
240     _animator->updateScrollerStyle();
241 }
242
243 @end
244
245 @interface ScrollbarPartAnimation : NSAnimation
246 {
247     RetainPtr<ScrollbarPainter> _scrollerPainter;
248     WebCore::ScrollbarPart _part;
249     WebCore::ScrollAnimatorMac* _animator;
250     CGFloat _initialAlpha;
251     CGFloat _newAlpha;
252 }
253 - (id)initWithScrollbarPainter:(ScrollbarPainter)scrollerPainter part:(WebCore::ScrollbarPart)part scrollAnimator:(WebCore::ScrollAnimatorMac*)scrollAnimator animateAlphaTo:(CGFloat)newAlpha duration:(NSTimeInterval)duration;
254 @end
255
256 @implementation ScrollbarPartAnimation
257
258 - (id)initWithScrollbarPainter:(ScrollbarPainter)scrollerPainter part:(WebCore::ScrollbarPart)part scrollAnimator:(WebCore::ScrollAnimatorMac*)scrollAnimator animateAlphaTo:(CGFloat)newAlpha duration:(NSTimeInterval)duration
259 {
260     self = [super initWithDuration:duration animationCurve:NSAnimationEaseInOut];
261     if (!self)
262         return nil;
263     
264     _scrollerPainter = scrollerPainter;
265     _part = part;
266     _animator = scrollAnimator;
267     _initialAlpha = _part == WebCore::ThumbPart ? [_scrollerPainter.get() knobAlpha] : [_scrollerPainter.get() trackAlpha];
268     _newAlpha = newAlpha;
269     
270     return self;    
271 }
272
273 - (void)setCurrentProgress:(NSAnimationProgress)progress
274 {
275     [super setCurrentProgress:progress];
276
277     if (!_animator)
278         return;
279
280     CGFloat currentAlpha;
281     if (_initialAlpha > _newAlpha)
282         currentAlpha = 1 - progress;
283     else
284         currentAlpha = progress;
285     
286     if (_part == WebCore::ThumbPart)
287         [_scrollerPainter.get() setKnobAlpha:currentAlpha];
288     else
289         [_scrollerPainter.get() setTrackAlpha:currentAlpha];
290
291     // Invalidate the scrollbars so that they paint the animation
292     if (WebCore::Scrollbar* verticalScrollbar = _animator->scrollableArea()->verticalScrollbar())
293         verticalScrollbar->invalidateRect(WebCore::IntRect(0, 0, verticalScrollbar->width(), verticalScrollbar->height()));
294     if (WebCore::Scrollbar* horizontalScrollbar = _animator->scrollableArea()->horizontalScrollbar())
295         horizontalScrollbar->invalidateRect(WebCore::IntRect(0, 0, horizontalScrollbar->width(), horizontalScrollbar->height()));
296 }
297
298 - (void)scrollAnimatorDestroyed
299 {
300     [self stopAnimation];
301     _animator = 0;
302 }
303
304 @end
305
306 @interface ScrollbarPainterDelegate : NSObject<NSAnimationDelegate>
307 {
308     WebCore::ScrollAnimatorMac* _animator;
309
310     RetainPtr<ScrollbarPartAnimation> _verticalKnobAnimation;
311     RetainPtr<ScrollbarPartAnimation> _horizontalKnobAnimation;
312
313     RetainPtr<ScrollbarPartAnimation> _verticalTrackAnimation;
314     RetainPtr<ScrollbarPartAnimation> _horizontalTrackAnimation;
315 }
316 - (id)initWithScrollAnimator:(WebCore::ScrollAnimatorMac*)scrollAnimator;
317 - (void)cancelAnimations;
318 @end
319
320 @implementation ScrollbarPainterDelegate
321
322 - (id)initWithScrollAnimator:(WebCore::ScrollAnimatorMac*)scrollAnimator
323 {
324     self = [super init];
325     if (!self)
326         return nil;
327     
328     _animator = scrollAnimator;
329     return self;
330 }
331
332 - (void)cancelAnimations
333 {
334     [_verticalKnobAnimation.get() stopAnimation];
335     [_horizontalKnobAnimation.get() stopAnimation];
336     [_verticalTrackAnimation.get() stopAnimation];
337     [_horizontalTrackAnimation.get() stopAnimation];
338 }
339
340 - (NSRect)convertRectToBacking:(NSRect)aRect
341 {
342     return aRect;
343 }
344
345 - (NSRect)convertRectFromBacking:(NSRect)aRect
346 {
347     return aRect;
348 }
349
350 - (CALayer *)layer
351 {
352     if (!_animator)
353         return nil;
354     if (!_animator->isDrawingIntoLayer())
355         return nil;
356
357     // FIXME: This should attempt to return an actual layer.
358     static CALayer *dummyLayer = [[CALayer alloc] init];
359     return dummyLayer;
360 }
361
362 - (void)setUpAnimation:(RetainPtr<ScrollbarPartAnimation>&)scrollbarPartAnimation scrollerPainter:(ScrollbarPainter)scrollerPainter part:(WebCore::ScrollbarPart)part animateAlphaTo:(CGFloat)newAlpha duration:(NSTimeInterval)duration
363 {
364     // If the user has scrolled the page, then the scrollbars must be animated here. 
365     // This overrides the early returns.
366     bool mustAnimate = _animator->haveScrolledSincePageLoad();
367
368     if (_animator->scrollbarPaintTimerIsActive() && !mustAnimate)
369         return;
370
371     if (_animator->scrollableArea()->shouldSuspendScrollAnimations() && !mustAnimate) {
372         _animator->startScrollbarPaintTimer();
373         return;
374     }
375
376     // At this point, we are definitely going to animate now, so stop the timer.
377     _animator->stopScrollbarPaintTimer();
378
379     // If we are currently animating, stop
380     if (scrollbarPartAnimation) {
381         [scrollbarPartAnimation.get() stopAnimation];
382         scrollbarPartAnimation = nil;
383     }
384
385     if (part == WebCore::ThumbPart && ![scrollerPainter isHorizontal]) {
386         if (newAlpha == 1) {
387             IntRect thumbRect = IntRect([scrollerPainter rectForPart:NSScrollerKnob]);
388             _animator->setVisibleScrollerThumbRect(thumbRect);
389         } else
390             _animator->setVisibleScrollerThumbRect(IntRect());
391     }
392
393     [NSAnimationContext beginGrouping];
394     [[NSAnimationContext currentContext] setDuration:duration];
395     scrollbarPartAnimation.adoptNS([[ScrollbarPartAnimation alloc] initWithScrollbarPainter:scrollerPainter 
396                                                                     part:part
397                                                                     scrollAnimator:_animator 
398                                                                     animateAlphaTo:newAlpha 
399                                                                     duration:duration]);
400     [scrollbarPartAnimation.get() setAnimationBlockingMode:NSAnimationNonblocking];
401     [scrollbarPartAnimation.get() startAnimation];
402     [NSAnimationContext endGrouping];
403 }
404
405 - (void)scrollerImp:(id)scrollerImp animateKnobAlphaTo:(CGFloat)newKnobAlpha duration:(NSTimeInterval)duration
406 {
407     if (!_animator)
408         return;
409
410     ScrollbarPainter scrollerPainter = (ScrollbarPainter)scrollerImp;
411     if ([scrollerImp isHorizontal])
412         [self setUpAnimation:_horizontalKnobAnimation scrollerPainter:scrollerPainter part:WebCore::ThumbPart animateAlphaTo:newKnobAlpha duration:duration];
413     else
414         [self setUpAnimation:_verticalKnobAnimation scrollerPainter:scrollerPainter part:WebCore::ThumbPart animateAlphaTo:newKnobAlpha duration:duration];
415 }
416
417 - (void)scrollerImp:(id)scrollerImp animateTrackAlphaTo:(CGFloat)newTrackAlpha duration:(NSTimeInterval)duration
418 {
419     if (!_animator)
420         return;
421
422     ScrollbarPainter scrollerPainter = (ScrollbarPainter)scrollerImp;
423     if ([scrollerImp isHorizontal])
424         [self setUpAnimation:_horizontalTrackAnimation scrollerPainter:scrollerPainter part:WebCore::BackTrackPart animateAlphaTo:newTrackAlpha duration:duration];
425     else
426         [self setUpAnimation:_verticalTrackAnimation scrollerPainter:scrollerPainter part:WebCore::BackTrackPart animateAlphaTo:newTrackAlpha duration:duration];
427 }
428
429 - (void)scrollerImp:(id)scrollerImp overlayScrollerStateChangedTo:(NSUInteger)newOverlayScrollerState
430 {
431     UNUSED_PARAM(scrollerImp);
432     UNUSED_PARAM(newOverlayScrollerState);
433 }
434
435 - (void)scrollAnimatorDestroyed
436 {
437     _animator = 0;
438     [_verticalKnobAnimation.get() scrollAnimatorDestroyed];
439     [_horizontalKnobAnimation.get() scrollAnimatorDestroyed];
440     [_verticalTrackAnimation.get() scrollAnimatorDestroyed];
441     [_horizontalTrackAnimation.get() scrollAnimatorDestroyed];
442 }
443
444 @end
445
446 #endif // USE(SCROLLBAR_PAINTER)
447
448 namespace WebCore {
449
450 PassOwnPtr<ScrollAnimator> ScrollAnimator::create(ScrollableArea* scrollableArea)
451 {
452     return adoptPtr(new ScrollAnimatorMac(scrollableArea));
453 }
454
455 ScrollAnimatorMac::ScrollAnimatorMac(ScrollableArea* scrollableArea)
456     : ScrollAnimator(scrollableArea)
457 #if USE(SCROLLBAR_PAINTER)
458     , m_initialScrollbarPaintTimer(this, &ScrollAnimatorMac::initialScrollbarPaintTimerFired)
459 #endif
460 #if ENABLE(RUBBER_BANDING)
461     , m_inScrollGesture(false)
462     , m_momentumScrollInProgress(false)
463     , m_ignoreMomentumScrolls(false)
464     , m_lastMomentumScrollTimestamp(0)
465     , m_startTime(0)
466     , m_snapRubberBandTimer(this, &ScrollAnimatorMac::snapRubberBandTimerFired)
467 #endif
468     , m_drawingIntoLayer(false)
469     , m_haveScrolledSincePageLoad(false)
470     , m_needsScrollerStyleUpdate(false)
471 {
472     m_scrollAnimationHelperDelegate.adoptNS([[ScrollAnimationHelperDelegate alloc] initWithScrollAnimator:this]);
473     m_scrollAnimationHelper.adoptNS([[NSClassFromString(@"NSScrollAnimationHelper") alloc] initWithDelegate:m_scrollAnimationHelperDelegate.get()]);
474
475 #if USE(SCROLLBAR_PAINTER)
476     m_scrollbarPainterControllerDelegate.adoptNS([[ScrollbarPainterControllerDelegate alloc] initWithScrollAnimator:this]);
477     m_scrollbarPainterController = [[[NSClassFromString(@"NSScrollerImpPair") alloc] init] autorelease];
478     [m_scrollbarPainterController.get() setDelegate:m_scrollbarPainterControllerDelegate.get()];
479     [m_scrollbarPainterController.get() setScrollerStyle:wkRecommendedScrollerStyle()];
480
481     m_scrollbarPainterDelegate.adoptNS([[ScrollbarPainterDelegate alloc] initWithScrollAnimator:this]);
482 #endif
483 }
484
485 ScrollAnimatorMac::~ScrollAnimatorMac()
486 {
487 #if USE(SCROLLBAR_PAINTER)
488     [m_scrollbarPainterControllerDelegate.get() scrollAnimatorDestroyed];
489     [m_scrollbarPainterController.get() setDelegate:nil];
490     [m_scrollbarPainterDelegate.get() scrollAnimatorDestroyed];
491     [m_scrollAnimationHelperDelegate.get() scrollAnimatorDestroyed];
492 #endif
493 }
494
495 bool ScrollAnimatorMac::scroll(ScrollbarOrientation orientation, ScrollGranularity granularity, float step, float multiplier)
496 {
497     m_haveScrolledSincePageLoad = true;
498
499     if (![[NSUserDefaults standardUserDefaults] boolForKey:@"AppleScrollAnimationEnabled"] || !m_scrollableArea->scrollAnimatorEnabled())
500         return ScrollAnimator::scroll(orientation, granularity, step, multiplier);
501
502     if (granularity == ScrollByPixel)
503         return ScrollAnimator::scroll(orientation, granularity, step, multiplier);
504
505     float currentPos = orientation == HorizontalScrollbar ? m_currentPosX : m_currentPosY;
506     float newPos = std::max<float>(std::min<float>(currentPos + (step * multiplier), static_cast<float>(m_scrollableArea->scrollSize(orientation))), 0);
507     if (currentPos == newPos)
508         return false;
509
510     NSPoint newPoint;
511     if ([m_scrollAnimationHelper.get() _isAnimating]) {
512         NSPoint targetOrigin = [m_scrollAnimationHelper.get() targetOrigin];
513         newPoint = orientation == HorizontalScrollbar ? NSMakePoint(newPos, targetOrigin.y) : NSMakePoint(targetOrigin.x, newPos);
514     } else {
515         newPoint = orientation == HorizontalScrollbar ? NSMakePoint(newPos, m_currentPosY) : NSMakePoint(m_currentPosX, newPos);
516         m_scrollableArea->didStartAnimatedScroll();
517     }
518
519     [m_scrollAnimationHelper.get() scrollToPoint:newPoint];
520     return true;
521 }
522
523 void ScrollAnimatorMac::scrollToOffsetWithoutAnimation(const FloatPoint& offset)
524 {
525     [m_scrollAnimationHelper.get() _stopRun];
526     immediateScrollToPoint(offset);
527 }
528
529 float ScrollAnimatorMac::adjustScrollXPositionIfNecessary(float position) const
530 {
531     if (!m_scrollableArea->constrainsScrollingToContentEdge())
532         return position;
533
534     return max<float>(min<float>(position, m_scrollableArea->contentsSize().width() - m_scrollableArea->visibleWidth()), 0);
535 }
536
537 float ScrollAnimatorMac::adjustScrollYPositionIfNecessary(float position) const
538 {
539     if (!m_scrollableArea->constrainsScrollingToContentEdge())
540         return position;
541
542     return max<float>(min<float>(position, m_scrollableArea->contentsSize().height() - m_scrollableArea->visibleHeight()), 0);
543 }
544
545 FloatPoint ScrollAnimatorMac::adjustScrollPositionIfNecessary(const FloatPoint& position) const
546 {
547     if (!m_scrollableArea->constrainsScrollingToContentEdge())
548         return position;
549
550     float newX = max<float>(min<float>(position.x(), m_scrollableArea->contentsSize().width() - m_scrollableArea->visibleWidth()), 0);
551     float newY = max<float>(min<float>(position.y(), m_scrollableArea->contentsSize().height() - m_scrollableArea->visibleHeight()), 0);
552
553     return FloatPoint(newX, newY);
554 }
555
556 void ScrollAnimatorMac::immediateScrollToPoint(const FloatPoint& newPosition)
557 {
558     FloatPoint adjustedPosition = adjustScrollPositionIfNecessary(newPosition);
559  
560     if (adjustedPosition.x() == m_currentPosX && adjustedPosition.y() == m_currentPosY)
561         return;
562     
563     m_currentPosX = adjustedPosition.x();
564     m_currentPosY = adjustedPosition.y();
565     notifyPositionChanged();
566 }
567
568 void ScrollAnimatorMac::immediateScrollByDeltaX(float deltaX)
569 {
570     float newPosX = adjustScrollXPositionIfNecessary(m_currentPosX + deltaX);
571     
572     if (newPosX == m_currentPosX)
573         return;
574     
575     m_currentPosX = newPosX;
576     notifyPositionChanged();
577 }
578
579 void ScrollAnimatorMac::immediateScrollByDeltaY(float deltaY)
580 {
581     float newPosY = adjustScrollYPositionIfNecessary(m_currentPosY + deltaY);
582     
583     if (newPosY == m_currentPosY)
584         return;
585     
586     m_currentPosY = newPosY;
587     notifyPositionChanged();
588 }
589
590 void ScrollAnimatorMac::immediateScrollToPointForScrollAnimation(const FloatPoint& newPosition)
591 {
592     ASSERT(m_scrollAnimationHelper);
593     CGFloat progress = [m_scrollAnimationHelper.get() _progress];
594     
595     immediateScrollToPoint(newPosition);
596
597     if (progress >= 1.0)
598         m_scrollableArea->didCompleteAnimatedScroll();
599 }
600
601 void ScrollAnimatorMac::notifyPositionChanged()
602 {
603 #if USE(SCROLLBAR_PAINTER)
604     [m_scrollbarPainterController.get() contentAreaScrolled];
605 #endif
606     ScrollAnimator::notifyPositionChanged();
607 }
608
609 void ScrollAnimatorMac::contentAreaWillPaint() const
610 {
611 #if USE(SCROLLBAR_PAINTER)
612     [m_scrollbarPainterController.get() contentAreaWillDraw];
613 #endif
614 }
615
616 void ScrollAnimatorMac::mouseEnteredContentArea() const
617 {
618 #if USE(SCROLLBAR_PAINTER)
619     [m_scrollbarPainterController.get() mouseEnteredContentArea];
620 #endif
621 }
622
623 void ScrollAnimatorMac::mouseExitedContentArea() const
624 {
625 #if USE(SCROLLBAR_PAINTER)
626     [m_scrollbarPainterController.get() mouseExitedContentArea];
627 #endif
628 }
629
630 void ScrollAnimatorMac::mouseMovedInContentArea() const
631 {
632 #if USE(SCROLLBAR_PAINTER)
633     [m_scrollbarPainterController.get() mouseMovedInContentArea];
634 #endif
635 }
636
637 void ScrollAnimatorMac::willStartLiveResize()
638 {
639 #if USE(SCROLLBAR_PAINTER)
640     [m_scrollbarPainterController.get() startLiveResize];
641 #endif
642 }
643
644 void ScrollAnimatorMac::contentsResized() const
645 {
646 #if USE(SCROLLBAR_PAINTER)
647     [m_scrollbarPainterController.get() contentAreaDidResize];
648 #endif
649 }
650
651 void ScrollAnimatorMac::willEndLiveResize()
652 {
653 #if USE(SCROLLBAR_PAINTER)
654     [m_scrollbarPainterController.get() endLiveResize];
655 #endif
656 }
657
658 void ScrollAnimatorMac::contentAreaDidShow() const
659 {
660 #if USE(SCROLLBAR_PAINTER)
661     [m_scrollbarPainterController.get() windowOrderedIn];
662 #endif
663 }
664
665 void ScrollAnimatorMac::contentAreaDidHide() const
666 {
667 #if USE(SCROLLBAR_PAINTER)
668     [m_scrollbarPainterController.get() windowOrderedOut];
669 #endif
670 }
671
672 void ScrollAnimatorMac::didBeginScrollGesture() const
673 {
674 #if USE(SCROLLBAR_PAINTER)
675     [m_scrollbarPainterController.get() beginScrollGesture];
676 #endif
677 }
678
679 void ScrollAnimatorMac::didEndScrollGesture() const
680 {
681 #if USE(SCROLLBAR_PAINTER)
682     [m_scrollbarPainterController.get() endScrollGesture];
683 #endif
684 }
685
686 void ScrollAnimatorMac::didAddVerticalScrollbar(Scrollbar* scrollbar)
687 {
688 #if USE(SCROLLBAR_PAINTER)
689     ScrollbarPainter painter = static_cast<WebCore::ScrollbarThemeMac*>(WebCore::ScrollbarTheme::nativeTheme())->painterForScrollbar(scrollbar);
690     [painter setDelegate:m_scrollbarPainterDelegate.get()];
691     [m_scrollbarPainterController.get() setVerticalScrollerImp:painter];
692     if (scrollableArea()->inLiveResize())
693         [painter setKnobAlpha:1];
694 #else
695     UNUSED_PARAM(scrollbar);
696 #endif
697 }
698
699 void ScrollAnimatorMac::willRemoveVerticalScrollbar(Scrollbar* scrollbar)
700 {
701 #if USE(SCROLLBAR_PAINTER)
702     ScrollbarPainter painter = static_cast<WebCore::ScrollbarThemeMac*>(WebCore::ScrollbarTheme::nativeTheme())->painterForScrollbar(scrollbar);
703     [painter setDelegate:nil];
704     [m_scrollbarPainterController.get() setVerticalScrollerImp:nil];
705 #else
706     UNUSED_PARAM(scrollbar);
707 #endif
708 }
709
710 void ScrollAnimatorMac::didAddHorizontalScrollbar(Scrollbar* scrollbar)
711 {
712 #if USE(SCROLLBAR_PAINTER)
713     ScrollbarPainter painter = static_cast<WebCore::ScrollbarThemeMac*>(WebCore::ScrollbarTheme::nativeTheme())->painterForScrollbar(scrollbar);
714     [painter setDelegate:m_scrollbarPainterDelegate.get()];
715     [m_scrollbarPainterController.get() setHorizontalScrollerImp:painter];
716     if (scrollableArea()->inLiveResize())
717         [painter setKnobAlpha:1];
718 #else
719     UNUSED_PARAM(scrollbar);
720 #endif
721 }
722
723 void ScrollAnimatorMac::willRemoveHorizontalScrollbar(Scrollbar* scrollbar)
724 {
725 #if USE(SCROLLBAR_PAINTER)
726     ScrollbarPainter painter = static_cast<WebCore::ScrollbarThemeMac*>(WebCore::ScrollbarTheme::nativeTheme())->painterForScrollbar(scrollbar);
727     [painter setDelegate:nil];
728     [m_scrollbarPainterController.get() setHorizontalScrollerImp:nil];
729 #else
730     UNUSED_PARAM(scrollbar);
731 #endif
732 }
733
734 void ScrollAnimatorMac::cancelAnimations()
735 {
736     m_haveScrolledSincePageLoad = false;
737
738 #if USE(SCROLLBAR_PAINTER)
739     if (scrollbarPaintTimerIsActive())
740         stopScrollbarPaintTimer();
741     [m_scrollbarPainterDelegate.get() cancelAnimations];
742 #endif
743 }
744
745 #if ENABLE(RUBBER_BANDING)
746
747 static const float scrollVelocityZeroingTimeout = 0.10f;
748 static const float rubberbandStiffness = 20;
749 static const float rubberbandDirectionLockStretchRatio = 1;
750 static const float rubberbandMinimumRequiredDeltaBeforeStretch = 10;
751 static const float rubberbandAmplitude = 0.31f;
752 static const float rubberbandPeriod = 1.6f;
753
754 static float elasticDeltaForTimeDelta(float initialPosition, float initialVelocity, float elapsedTime)
755 {
756     float amplitude = rubberbandAmplitude;
757     float period = rubberbandPeriod;
758     float criticalDampeningFactor = expf((-elapsedTime * rubberbandStiffness) / period);
759              
760     return (initialPosition + (-initialVelocity * elapsedTime * amplitude)) * criticalDampeningFactor;
761 }
762
763 static float elasticDeltaForReboundDelta(float delta)
764 {
765     float stiffness = std::max(rubberbandStiffness, 1.0f);
766     return delta / stiffness;
767 }
768
769 static float reboundDeltaForElasticDelta(float delta)
770 {
771     return delta * rubberbandStiffness;
772 }
773
774 static float scrollWheelMultiplier()
775 {
776     static float multiplier = -1;
777     if (multiplier < 0) {
778         multiplier = [[NSUserDefaults standardUserDefaults] floatForKey:@"NSScrollWheelMultiplier"];
779         if (multiplier <= 0)
780             multiplier = 1;
781     }
782     return multiplier;
783 }
784
785 static inline bool isScrollingLeftAndShouldNotRubberBand(PlatformWheelEvent& wheelEvent, ScrollableArea* scrollableArea)
786 {
787     return wheelEvent.deltaX() > 0 && !scrollableArea->shouldRubberBandInDirection(ScrollLeft);
788 }
789
790 static inline bool isScrollingRightAndShouldNotRubberBand(PlatformWheelEvent& wheelEvent, ScrollableArea* scrollableArea)
791 {
792     return wheelEvent.deltaX() < 0 && !scrollableArea->shouldRubberBandInDirection(ScrollRight);
793 }
794
795 void ScrollAnimatorMac::handleWheelEvent(PlatformWheelEvent& wheelEvent)
796 {
797     m_haveScrolledSincePageLoad = true;
798
799     if (!wheelEvent.hasPreciseScrollingDeltas()) {
800         ScrollAnimator::handleWheelEvent(wheelEvent);
801         return;
802     }
803
804     // FIXME: This is somewhat roundabout hack to allow forwarding wheel events
805     // up to the parent scrollable area. It takes advantage of the fact that
806     // the base class implemenatation of handleWheelEvent will not accept the
807     // wheel event if there is nowhere to scroll.
808     if (fabsf(wheelEvent.deltaY()) >= fabsf(wheelEvent.deltaX())) {
809         if (!allowsVerticalStretching()) {
810             ScrollAnimator::handleWheelEvent(wheelEvent);
811             return;
812         }
813     } else {
814         if (!allowsHorizontalStretching()) {
815             ScrollAnimator::handleWheelEvent(wheelEvent);
816             return;
817         }
818         
819         if (m_scrollableArea->horizontalScrollbar()) {
820             // If there is a scrollbar, we aggregate the wheel events to get an
821             // overall trend of the scroll. If the direction of the scroll is ever
822             // in the opposite direction of the pin location, then we switch the
823             // boolean, and rubber band. That is, if we were pinned to the left,
824             // and we ended up scrolling to the right, we rubber band.
825             m_cumulativeHorizontalScroll += wheelEvent.deltaX();
826             if (m_scrollerInitiallyPinnedOnLeft && m_cumulativeHorizontalScroll < 0)
827                 m_didCumulativeHorizontalScrollEverSwitchToOppositeDirectionOfPin = true;
828             if (m_scrollerInitiallyPinnedOnRight && m_cumulativeHorizontalScroll > 0)
829                 m_didCumulativeHorizontalScrollEverSwitchToOppositeDirectionOfPin = true;
830         }
831
832         // After a gesture begins, we go through:
833         // 1+ PlatformWheelEventPhaseNone
834         // 0+ PlatformWheelEventPhaseChanged
835         // 1 PlatformWheelEventPhaseEnded if there was at least one changed event
836         if (wheelEvent.momentumPhase() == PlatformWheelEventPhaseNone && !m_didCumulativeHorizontalScrollEverSwitchToOppositeDirectionOfPin) {
837             if ((isScrollingLeftAndShouldNotRubberBand(wheelEvent, m_scrollableArea) &&
838                 m_scrollerInitiallyPinnedOnLeft &&
839                 m_scrollableArea->isHorizontalScrollerPinnedToMinimumPosition()) ||
840                 (isScrollingRightAndShouldNotRubberBand(wheelEvent, m_scrollableArea) &&
841                 m_scrollerInitiallyPinnedOnRight &&
842                 m_scrollableArea->isHorizontalScrollerPinnedToMaximumPosition())) {
843                 ScrollAnimator::handleWheelEvent(wheelEvent);
844                 return;
845             }
846         }
847     }
848
849     bool isMomentumScrollEvent = (wheelEvent.momentumPhase() != PlatformWheelEventPhaseNone);
850     if (m_ignoreMomentumScrolls && (isMomentumScrollEvent || m_snapRubberBandTimer.isActive())) {
851         if (wheelEvent.momentumPhase() == PlatformWheelEventPhaseEnded) {
852             m_ignoreMomentumScrolls = false;
853             wheelEvent.accept();
854         }
855         return;
856     }
857
858     wheelEvent.accept();
859     smoothScrollWithEvent(wheelEvent);
860 }
861
862 void ScrollAnimatorMac::handleGestureEvent(const PlatformGestureEvent& gestureEvent)
863 {
864     if (gestureEvent.type() == PlatformGestureEvent::ScrollBeginType)
865         beginScrollGesture();
866     else if (gestureEvent.type() == PlatformGestureEvent::ScrollEndType)
867         endScrollGesture();
868 }
869
870 bool ScrollAnimatorMac::pinnedInDirection(float deltaX, float deltaY)
871 {
872     FloatSize limitDelta;
873     if (fabsf(deltaY) >= fabsf(deltaX)) {
874         if (deltaY < 0) {
875             // We are trying to scroll up.  Make sure we are not pinned to the top
876             limitDelta.setHeight(m_scrollableArea->visibleContentRect().y() + + m_scrollableArea->scrollOrigin().y());
877         } else {
878             // We are trying to scroll down.  Make sure we are not pinned to the bottom
879             limitDelta.setHeight(m_scrollableArea->contentsSize().height() - (m_scrollableArea->visibleContentRect().maxY() + m_scrollableArea->scrollOrigin().y()));
880         }
881     } else if (deltaX != 0) {
882         if (deltaX < 0) {
883             // We are trying to scroll left.  Make sure we are not pinned to the left
884             limitDelta.setWidth(m_scrollableArea->visibleContentRect().x() + m_scrollableArea->scrollOrigin().x());
885         } else {
886             // We are trying to scroll right.  Make sure we are not pinned to the right
887             limitDelta.setWidth(m_scrollableArea->contentsSize().width() - (m_scrollableArea->visibleContentRect().maxX() + m_scrollableArea->scrollOrigin().x()));
888         }
889     }
890     
891     if ((deltaX != 0 || deltaY != 0) && (limitDelta.width() < 1 && limitDelta.height() < 1))
892         return true;
893     return false;
894 }
895
896 bool ScrollAnimatorMac::allowsVerticalStretching() const
897 {
898     switch (m_scrollableArea->verticalScrollElasticity()) {
899     case ScrollElasticityAutomatic: {
900         Scrollbar* hScroller = m_scrollableArea->horizontalScrollbar();
901         Scrollbar* vScroller = m_scrollableArea->verticalScrollbar();
902         return (((vScroller && vScroller->enabled()) || (!hScroller || !hScroller->enabled())));
903     }
904     case ScrollElasticityNone:
905         return false;
906     case ScrollElasticityAllowed:
907         return true;
908     }
909
910     ASSERT_NOT_REACHED();
911     return false;
912 }
913
914 bool ScrollAnimatorMac::allowsHorizontalStretching() const
915 {
916     switch (m_scrollableArea->horizontalScrollElasticity()) {
917     case ScrollElasticityAutomatic: {
918         Scrollbar* hScroller = m_scrollableArea->horizontalScrollbar();
919         Scrollbar* vScroller = m_scrollableArea->verticalScrollbar();
920         return (((hScroller && hScroller->enabled()) || (!vScroller || !vScroller->enabled())));
921     }
922     case ScrollElasticityNone:
923         return false;
924     case ScrollElasticityAllowed:
925         return true;
926     }
927
928     ASSERT_NOT_REACHED();
929     return false;
930 }
931
932 void ScrollAnimatorMac::smoothScrollWithEvent(PlatformWheelEvent& wheelEvent)
933 {
934     m_haveScrolledSincePageLoad = true;
935
936     float deltaX = m_overflowScrollDelta.width();
937     float deltaY = m_overflowScrollDelta.height();
938
939     // Reset overflow values because we may decide to remove delta at various points and put it into overflow.
940     m_overflowScrollDelta = FloatSize();
941
942     float eventCoalescedDeltaX = -wheelEvent.deltaX();
943     float eventCoalescedDeltaY = -wheelEvent.deltaY();
944
945     deltaX += eventCoalescedDeltaX;
946     deltaY += eventCoalescedDeltaY;
947
948     // Slightly prefer scrolling vertically by applying the = case to deltaY
949     if (fabsf(deltaY) >= fabsf(deltaX))
950         deltaX = 0;
951     else
952         deltaY = 0;
953
954     bool isVerticallyStretched = false;
955     bool isHorizontallyStretched = false;
956     bool shouldStretch = false;
957     
958     IntSize stretchAmount = m_scrollableArea->overhangAmount();
959
960     isHorizontallyStretched = stretchAmount.width();
961     isVerticallyStretched = stretchAmount.height();
962
963     PlatformWheelEventPhase phase = wheelEvent.momentumPhase();
964
965     // If we are starting momentum scrolling then do some setup.
966     if (!m_momentumScrollInProgress && (phase == PlatformWheelEventPhaseBegan || phase == PlatformWheelEventPhaseChanged))
967         m_momentumScrollInProgress = true;
968
969     CFTimeInterval timeDelta = wheelEvent.timestamp() - m_lastMomentumScrollTimestamp;
970     if (m_inScrollGesture || m_momentumScrollInProgress) {
971         if (m_lastMomentumScrollTimestamp && timeDelta > 0 && timeDelta < scrollVelocityZeroingTimeout) {
972             m_momentumVelocity.setWidth(eventCoalescedDeltaX / (float)timeDelta);
973             m_momentumVelocity.setHeight(eventCoalescedDeltaY / (float)timeDelta);
974             m_lastMomentumScrollTimestamp = wheelEvent.timestamp();
975         } else {
976             m_lastMomentumScrollTimestamp = wheelEvent.timestamp();
977             m_momentumVelocity = FloatSize();
978         }
979
980         if (isVerticallyStretched) {
981             if (!isHorizontallyStretched && pinnedInDirection(deltaX, 0)) {                
982                 // Stretching only in the vertical.
983                 if (deltaY != 0 && (fabsf(deltaX / deltaY) < rubberbandDirectionLockStretchRatio))
984                     deltaX = 0;
985                 else if (fabsf(deltaX) < rubberbandMinimumRequiredDeltaBeforeStretch) {
986                     m_overflowScrollDelta.setWidth(m_overflowScrollDelta.width() + deltaX);
987                     deltaX = 0;
988                 } else
989                     m_overflowScrollDelta.setWidth(m_overflowScrollDelta.width() + deltaX);
990             }
991         } else if (isHorizontallyStretched) {
992             // Stretching only in the horizontal.
993             if (pinnedInDirection(0, deltaY)) {
994                 if (deltaX != 0 && (fabsf(deltaY / deltaX) < rubberbandDirectionLockStretchRatio))
995                     deltaY = 0;
996                 else if (fabsf(deltaY) < rubberbandMinimumRequiredDeltaBeforeStretch) {
997                     m_overflowScrollDelta.setHeight(m_overflowScrollDelta.height() + deltaY);
998                     deltaY = 0;
999                 } else
1000                     m_overflowScrollDelta.setHeight(m_overflowScrollDelta.height() + deltaY);
1001             }
1002         } else {
1003             // Not stretching at all yet.
1004             if (pinnedInDirection(deltaX, deltaY)) {
1005                 if (fabsf(deltaY) >= fabsf(deltaX)) {
1006                     if (fabsf(deltaX) < rubberbandMinimumRequiredDeltaBeforeStretch) {
1007                         m_overflowScrollDelta.setWidth(m_overflowScrollDelta.width() + deltaX);
1008                         deltaX = 0;
1009                     } else
1010                         m_overflowScrollDelta.setWidth(m_overflowScrollDelta.width() + deltaX);
1011                 }
1012                 shouldStretch = true;
1013             }
1014         }
1015     }
1016
1017     if (deltaX != 0 || deltaY != 0) {
1018         if (!(shouldStretch || isVerticallyStretched || isHorizontallyStretched)) {
1019             if (deltaY != 0) {
1020                 deltaY *= scrollWheelMultiplier();
1021                 immediateScrollByDeltaY(deltaY);
1022             }
1023             if (deltaX != 0) {
1024                 deltaX *= scrollWheelMultiplier();
1025                 immediateScrollByDeltaX(deltaX);
1026             }
1027         } else {
1028             if (!allowsHorizontalStretching()) {
1029                 deltaX = 0;
1030                 eventCoalescedDeltaX = 0;
1031             } else if ((deltaX != 0) && !isHorizontallyStretched && !pinnedInDirection(deltaX, 0)) {
1032                 deltaX *= scrollWheelMultiplier();
1033
1034                 m_scrollableArea->setConstrainsScrollingToContentEdge(false);
1035                 immediateScrollByDeltaX(deltaX);
1036                 m_scrollableArea->setConstrainsScrollingToContentEdge(true);
1037
1038                 deltaX = 0;
1039             }
1040             
1041             if (!allowsVerticalStretching()) {
1042                 deltaY = 0;
1043                 eventCoalescedDeltaY = 0;
1044             } else if ((deltaY != 0) && !isVerticallyStretched && !pinnedInDirection(0, deltaY)) {
1045                 deltaY *= scrollWheelMultiplier();
1046
1047                 m_scrollableArea->setConstrainsScrollingToContentEdge(false);
1048                 immediateScrollByDeltaY(deltaY);
1049                 m_scrollableArea->setConstrainsScrollingToContentEdge(true);
1050
1051                 deltaY = 0;
1052             }
1053             
1054             IntSize stretchAmount = m_scrollableArea->overhangAmount();
1055         
1056             if (m_momentumScrollInProgress) {
1057                 if ((pinnedInDirection(eventCoalescedDeltaX, eventCoalescedDeltaY) || (fabsf(eventCoalescedDeltaX) + fabsf(eventCoalescedDeltaY) <= 0)) && m_lastMomentumScrollTimestamp) {
1058                     m_ignoreMomentumScrolls = true;
1059                     m_momentumScrollInProgress = false;
1060                     snapRubberBand();
1061                 }
1062             }
1063
1064             m_stretchScrollForce.setWidth(m_stretchScrollForce.width() + deltaX);
1065             m_stretchScrollForce.setHeight(m_stretchScrollForce.height() + deltaY);
1066
1067             FloatSize dampedDelta(ceilf(elasticDeltaForReboundDelta(m_stretchScrollForce.width())), ceilf(elasticDeltaForReboundDelta(m_stretchScrollForce.height())));
1068             FloatPoint origOrigin = (m_scrollableArea->visibleContentRect().location() + m_scrollableArea->scrollOrigin()) - stretchAmount;
1069             FloatPoint newOrigin = origOrigin + dampedDelta;
1070
1071             if (origOrigin != newOrigin) {
1072                 m_scrollableArea->setConstrainsScrollingToContentEdge(false);
1073                 immediateScrollToPoint(newOrigin);
1074                 m_scrollableArea->setConstrainsScrollingToContentEdge(true);
1075             }
1076         }
1077     }
1078
1079     if (m_momentumScrollInProgress && phase == PlatformWheelEventPhaseEnded) {
1080         m_momentumScrollInProgress = false;
1081         m_ignoreMomentumScrolls = false;
1082         m_lastMomentumScrollTimestamp = 0;
1083     }
1084 }
1085
1086 void ScrollAnimatorMac::beginScrollGesture()
1087 {
1088     didBeginScrollGesture();
1089
1090     m_haveScrolledSincePageLoad = true;
1091     m_inScrollGesture = true;
1092     m_momentumScrollInProgress = false;
1093     m_ignoreMomentumScrolls = false;
1094     m_lastMomentumScrollTimestamp = 0;
1095     m_momentumVelocity = FloatSize();
1096     m_scrollerInitiallyPinnedOnLeft = m_scrollableArea->isHorizontalScrollerPinnedToMinimumPosition();
1097     m_scrollerInitiallyPinnedOnRight = m_scrollableArea->isHorizontalScrollerPinnedToMaximumPosition();
1098     m_cumulativeHorizontalScroll = 0;
1099     m_didCumulativeHorizontalScrollEverSwitchToOppositeDirectionOfPin = false;
1100     
1101     IntSize stretchAmount = m_scrollableArea->overhangAmount();
1102     m_stretchScrollForce.setWidth(reboundDeltaForElasticDelta(stretchAmount.width()));
1103     m_stretchScrollForce.setHeight(reboundDeltaForElasticDelta(stretchAmount.height()));
1104
1105     m_overflowScrollDelta = FloatSize();
1106     
1107     if (m_snapRubberBandTimer.isActive())
1108         m_snapRubberBandTimer.stop();
1109 }
1110
1111 void ScrollAnimatorMac::endScrollGesture()
1112 {
1113     didEndScrollGesture();
1114
1115     snapRubberBand();
1116 }
1117
1118 void ScrollAnimatorMac::snapRubberBand()
1119 {
1120     CFTimeInterval timeDelta = [[NSProcessInfo processInfo] systemUptime] - m_lastMomentumScrollTimestamp;
1121     if (m_lastMomentumScrollTimestamp && timeDelta >= scrollVelocityZeroingTimeout)
1122         m_momentumVelocity = FloatSize();
1123
1124     m_inScrollGesture = false;
1125
1126     if (m_snapRubberBandTimer.isActive())
1127         return;
1128
1129     m_startTime = [NSDate timeIntervalSinceReferenceDate];
1130     m_startStretch = FloatSize();
1131     m_origOrigin = FloatPoint();
1132     m_origVelocity = FloatSize();
1133
1134     m_snapRubberBandTimer.startRepeating(1.0/60.0);
1135 }
1136
1137 static inline float roundTowardZero(float num)
1138 {
1139     return num > 0 ? ceilf(num - 0.5f) : floorf(num + 0.5f);
1140 }
1141
1142 static inline float roundToDevicePixelTowardZero(float num)
1143 {
1144     float roundedNum = roundf(num);
1145     if (fabs(num - roundedNum) < 0.125)
1146         num = roundedNum;
1147
1148     return roundTowardZero(num);
1149 }
1150
1151 void ScrollAnimatorMac::snapRubberBandTimerFired(Timer<ScrollAnimatorMac>*)
1152 {
1153     if (!m_momentumScrollInProgress || m_ignoreMomentumScrolls) {
1154         CFTimeInterval timeDelta = [NSDate timeIntervalSinceReferenceDate] - m_startTime;
1155
1156         if (m_startStretch == FloatSize()) {
1157             m_startStretch = m_scrollableArea->overhangAmount();
1158             if (m_startStretch == FloatSize()) {    
1159                 m_snapRubberBandTimer.stop();
1160                 m_stretchScrollForce = FloatSize();
1161                 m_startTime = 0;
1162                 m_startStretch = FloatSize();
1163                 m_origOrigin = FloatPoint();
1164                 m_origVelocity = FloatSize();
1165
1166                 return;
1167             }
1168
1169             m_scrollableArea->didStartRubberBand(roundedIntSize(m_startStretch));
1170             
1171             m_origOrigin = (m_scrollableArea->visibleContentRect().location() + m_scrollableArea->scrollOrigin()) - m_startStretch;
1172             m_origVelocity = m_momentumVelocity;
1173
1174             // Just like normal scrolling, prefer vertical rubberbanding
1175             if (fabsf(m_origVelocity.height()) >= fabsf(m_origVelocity.width()))
1176                 m_origVelocity.setWidth(0);
1177             
1178             // Don't rubber-band horizontally if it's not possible to scroll horizontally
1179             Scrollbar* hScroller = m_scrollableArea->horizontalScrollbar();
1180             if (!hScroller || !hScroller->enabled())
1181                 m_origVelocity.setWidth(0);
1182             
1183             // Don't rubber-band vertically if it's not possible to scroll horizontally
1184             Scrollbar* vScroller = m_scrollableArea->verticalScrollbar();
1185             if (!vScroller || !vScroller->enabled())
1186                 m_origVelocity.setHeight(0);
1187         }
1188
1189         FloatPoint delta(roundToDevicePixelTowardZero(elasticDeltaForTimeDelta(m_startStretch.width(), -m_origVelocity.width(), (float)timeDelta)),
1190                          roundToDevicePixelTowardZero(elasticDeltaForTimeDelta(m_startStretch.height(), -m_origVelocity.height(), (float)timeDelta)));
1191
1192         if (fabs(delta.x()) >= 1 || fabs(delta.y()) >= 1) {
1193             FloatPoint newOrigin = m_origOrigin + delta;
1194
1195             m_scrollableArea->setConstrainsScrollingToContentEdge(false);
1196             immediateScrollToPoint(newOrigin);
1197             m_scrollableArea->setConstrainsScrollingToContentEdge(true);
1198
1199             FloatSize newStretch = m_scrollableArea->overhangAmount();
1200             
1201             m_stretchScrollForce.setWidth(reboundDeltaForElasticDelta(newStretch.width()));
1202             m_stretchScrollForce.setHeight(reboundDeltaForElasticDelta(newStretch.height()));
1203         } else {
1204             immediateScrollToPoint(m_origOrigin);
1205
1206             m_scrollableArea->didCompleteRubberBand(roundedIntSize(m_startStretch));
1207
1208             m_snapRubberBandTimer.stop();
1209             m_stretchScrollForce = FloatSize();
1210             
1211             m_startTime = 0;
1212             m_startStretch = FloatSize();
1213             m_origOrigin = FloatPoint();
1214             m_origVelocity = FloatSize();
1215         }
1216     } else {
1217         m_startTime = [NSDate timeIntervalSinceReferenceDate];
1218         m_startStretch = FloatSize();
1219     }
1220 }
1221 #endif
1222
1223 void ScrollAnimatorMac::setIsActive()
1224 {
1225 #if USE(SCROLLBAR_PAINTER)
1226     if (needsScrollerStyleUpdate())
1227         updateScrollerStyle();
1228 #endif
1229 }
1230
1231 #if USE(SCROLLBAR_PAINTER)
1232 void ScrollAnimatorMac::updateScrollerStyle()
1233 {
1234     if (!scrollableArea()->isOnActivePage()) {
1235         setNeedsScrollerStyleUpdate(true);
1236         return;
1237     }
1238
1239     ScrollbarThemeMac* macTheme = (ScrollbarThemeMac*)ScrollbarTheme::nativeTheme();
1240     NSScrollerStyle newStyle = [m_scrollbarPainterController.get() scrollerStyle];
1241
1242     if (Scrollbar* verticalScrollbar = scrollableArea()->verticalScrollbar()) {
1243         verticalScrollbar->invalidate();
1244
1245         ScrollbarPainter oldVerticalPainter = [m_scrollbarPainterController.get() verticalScrollerImp];
1246         ScrollbarPainter newVerticalPainter = [NSClassFromString(@"NSScrollerImp") scrollerImpWithStyle:newStyle 
1247                                                                                     controlSize:(NSControlSize)verticalScrollbar->controlSize() 
1248                                                                                     horizontal:NO 
1249                                                                                     replacingScrollerImp:oldVerticalPainter];
1250         macTheme->setNewPainterForScrollbar(verticalScrollbar, newVerticalPainter);
1251         [m_scrollbarPainterController.get() setVerticalScrollerImp:newVerticalPainter];
1252
1253         // The different scrollbar styles have different thicknesses, so we must re-set the 
1254         // frameRect to the new thickness, and the re-layout below will ensure the position
1255         // and length are properly updated.
1256         int thickness = macTheme->scrollbarThickness(verticalScrollbar->controlSize());
1257         verticalScrollbar->setFrameRect(IntRect(0, 0, thickness, thickness));
1258     }
1259
1260     if (Scrollbar* horizontalScrollbar = scrollableArea()->horizontalScrollbar()) {
1261         horizontalScrollbar->invalidate();
1262
1263         ScrollbarPainter oldHorizontalPainter = [m_scrollbarPainterController.get() horizontalScrollerImp];
1264         ScrollbarPainter newHorizontalPainter = [NSClassFromString(@"NSScrollerImp") scrollerImpWithStyle:newStyle 
1265                                                                                     controlSize:(NSControlSize)horizontalScrollbar->controlSize() 
1266                                                                                     horizontal:YES 
1267                                                                                     replacingScrollerImp:oldHorizontalPainter];
1268         macTheme->setNewPainterForScrollbar(horizontalScrollbar, newHorizontalPainter);
1269         [m_scrollbarPainterController.get() setVerticalScrollerImp:newHorizontalPainter];
1270
1271         // The different scrollbar styles have different thicknesses, so we must re-set the 
1272         // frameRect to the new thickness, and the re-layout below will ensure the position
1273         // and length are properly updated.
1274         int thickness = macTheme->scrollbarThickness(horizontalScrollbar->controlSize());
1275         horizontalScrollbar->setFrameRect(IntRect(0, 0, thickness, thickness));
1276     }
1277
1278     // If needsScrollerStyleUpdate() is true, then the page is restoring from the page cache, and 
1279     // a relayout will happen on its own. Otherwise, we must initiate a re-layout ourselves.
1280     if (!needsScrollerStyleUpdate())
1281         scrollableArea()->scrollbarStyleChanged();
1282
1283     setNeedsScrollerStyleUpdate(false);
1284 }
1285
1286 void ScrollAnimatorMac::startScrollbarPaintTimer()
1287 {
1288     m_initialScrollbarPaintTimer.startOneShot(0.1);
1289 }
1290
1291 bool ScrollAnimatorMac::scrollbarPaintTimerIsActive() const
1292 {
1293     return m_initialScrollbarPaintTimer.isActive();
1294 }
1295
1296 void ScrollAnimatorMac::stopScrollbarPaintTimer()
1297 {
1298     m_initialScrollbarPaintTimer.stop();
1299 }
1300
1301 void ScrollAnimatorMac::initialScrollbarPaintTimerFired(Timer<ScrollAnimatorMac>*)
1302 {
1303     // To force the scrollbars to flash, we have to call hide first. Otherwise, the ScrollbarPainterController
1304     // might think that the scrollbars are already showing and bail early.
1305     [m_scrollbarPainterController.get() hideOverlayScrollers];
1306     [m_scrollbarPainterController.get() flashScrollers];
1307 }
1308 #endif
1309
1310 void ScrollAnimatorMac::setVisibleScrollerThumbRect(const IntRect& scrollerThumb)
1311 {
1312     IntRect rectInViewCoordinates = scrollerThumb;
1313     if (Scrollbar* verticalScrollbar = m_scrollableArea->verticalScrollbar())
1314         rectInViewCoordinates = verticalScrollbar->convertToContainingView(scrollerThumb);
1315
1316     if (rectInViewCoordinates == m_visibleScrollerThumbRect)
1317         return;
1318
1319     m_scrollableArea->setVisibleScrollerThumbRect(rectInViewCoordinates);
1320     m_visibleScrollerThumbRect = rectInViewCoordinates;
1321 }
1322
1323 } // namespace WebCore
1324
1325 #endif // ENABLE(SMOOTH_SCROLLING)