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