Simplify ScrollAnimatorMac scrollByDelta functions
[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::immediateScrollBy(const FloatSize& delta)
694 {
695     float newPosX = adjustScrollXPositionIfNecessary(m_currentPosX + delta.width());
696     float newPosY = adjustScrollYPositionIfNecessary(m_currentPosY + delta.height());
697
698     if (newPosX == m_currentPosX && newPosY == m_currentPosY)
699         return;
700
701     m_currentPosX = newPosX;
702     m_currentPosY = newPosY;
703     notifyPositionChanged();
704 }
705
706 void ScrollAnimatorMac::immediateScrollToPointForScrollAnimation(const FloatPoint& newPosition)
707 {
708     ASSERT(m_scrollAnimationHelper);
709     immediateScrollTo(newPosition);
710 }
711
712 void ScrollAnimatorMac::notifyPositionChanged()
713 {
714     if (isScrollbarOverlayAPIAvailable()) {
715         // This function is called when a page is going into the page cache, but the page 
716         // isn't really scrolling in that case. We should only pass the message on to the
717         // ScrollbarPainterController when we're really scrolling on an active page.
718         if (scrollableArea()->isOnActivePage())
719             [m_scrollbarPainterController.get() contentAreaScrolled];
720     }
721     ScrollAnimator::notifyPositionChanged();
722 }
723
724 void ScrollAnimatorMac::contentAreaWillPaint() const
725 {
726     if (!scrollableArea()->isOnActivePage())
727         return;
728     if (isScrollbarOverlayAPIAvailable())
729         [m_scrollbarPainterController.get() contentAreaWillDraw];
730 }
731
732 void ScrollAnimatorMac::mouseEnteredContentArea() const
733 {
734     if (!scrollableArea()->isOnActivePage())
735         return;
736     if (isScrollbarOverlayAPIAvailable())
737         [m_scrollbarPainterController.get() mouseEnteredContentArea];
738 }
739
740 void ScrollAnimatorMac::mouseExitedContentArea() const
741 {
742     if (!scrollableArea()->isOnActivePage())
743         return;
744     if (isScrollbarOverlayAPIAvailable())
745         [m_scrollbarPainterController.get() mouseExitedContentArea];
746 }
747
748 void ScrollAnimatorMac::mouseMovedInContentArea() const
749 {
750     if (!scrollableArea()->isOnActivePage())
751         return;
752     if (isScrollbarOverlayAPIAvailable())
753         [m_scrollbarPainterController.get() mouseMovedInContentArea];
754 }
755
756 void ScrollAnimatorMac::mouseEnteredScrollbar(Scrollbar* scrollbar) const
757 {
758     if (!scrollableArea()->isOnActivePage())
759         return;
760     if (isScrollbarOverlayAPIAvailable()) {
761         if (!supportsUIStateTransitionProgress())
762             return;
763         if (ScrollbarPainter painter = scrollbarPainterForScrollbar(scrollbar))
764             [painter mouseEnteredScroller];
765     }
766 }
767
768 void ScrollAnimatorMac::mouseExitedScrollbar(Scrollbar* scrollbar) const
769 {
770     if (!scrollableArea()->isOnActivePage())
771         return;
772     if (isScrollbarOverlayAPIAvailable()) {
773         if (!supportsUIStateTransitionProgress())
774             return;
775         if (ScrollbarPainter painter = scrollbarPainterForScrollbar(scrollbar))
776             [painter mouseExitedScroller];
777     }
778 }
779
780 void ScrollAnimatorMac::willStartLiveResize()
781 {
782     if (!scrollableArea()->isOnActivePage())
783         return;
784     if (isScrollbarOverlayAPIAvailable())
785         [m_scrollbarPainterController.get() startLiveResize];
786 }
787
788 void ScrollAnimatorMac::contentsResized() const
789 {
790     if (!scrollableArea()->isOnActivePage())
791         return;
792     if (isScrollbarOverlayAPIAvailable())
793         [m_scrollbarPainterController.get() contentAreaDidResize];
794 }
795
796 void ScrollAnimatorMac::willEndLiveResize()
797 {
798     if (!scrollableArea()->isOnActivePage())
799         return;
800     if (isScrollbarOverlayAPIAvailable())
801         [m_scrollbarPainterController.get() endLiveResize];
802 }
803
804 void ScrollAnimatorMac::contentAreaDidShow() const
805 {
806     if (!scrollableArea()->isOnActivePage())
807         return;
808     if (isScrollbarOverlayAPIAvailable())
809         [m_scrollbarPainterController.get() windowOrderedIn];
810 }
811
812 void ScrollAnimatorMac::contentAreaDidHide() const
813 {
814     if (!scrollableArea()->isOnActivePage())
815         return;
816     if (isScrollbarOverlayAPIAvailable())
817         [m_scrollbarPainterController.get() windowOrderedOut];
818 }
819
820 void ScrollAnimatorMac::didBeginScrollGesture() const
821 {
822     if (!scrollableArea()->isOnActivePage())
823         return;
824     if (isScrollbarOverlayAPIAvailable())
825         [m_scrollbarPainterController.get() beginScrollGesture];
826 }
827
828 void ScrollAnimatorMac::didEndScrollGesture() const
829 {
830     if (!scrollableArea()->isOnActivePage())
831         return;
832     if (isScrollbarOverlayAPIAvailable())
833         [m_scrollbarPainterController.get() endScrollGesture];
834 }
835
836 void ScrollAnimatorMac::didAddVerticalScrollbar(Scrollbar* scrollbar)
837 {
838     if (isScrollbarOverlayAPIAvailable()) {
839         ScrollbarPainter painter = scrollbarPainterForScrollbar(scrollbar);
840         if (!painter)
841             return;
842
843         ASSERT(!m_verticalScrollbarPainterDelegate);
844         m_verticalScrollbarPainterDelegate.adoptNS([[WebScrollbarPainterDelegate alloc] initWithScrollbar:scrollbar]);
845
846         [painter setDelegate:m_verticalScrollbarPainterDelegate.get()];
847         [m_scrollbarPainterController.get() setVerticalScrollerImp:painter];
848         if (scrollableArea()->inLiveResize())
849             [painter setKnobAlpha:1];
850     }
851 }
852
853 void ScrollAnimatorMac::willRemoveVerticalScrollbar(Scrollbar* scrollbar)
854 {
855     if (isScrollbarOverlayAPIAvailable()) {
856         ScrollbarPainter painter = scrollbarPainterForScrollbar(scrollbar);
857         if (!painter)
858             return;
859
860         ASSERT(m_verticalScrollbarPainterDelegate);
861         [m_verticalScrollbarPainterDelegate.get() invalidate];
862         m_verticalScrollbarPainterDelegate = nullptr;
863
864         [painter setDelegate:nil];
865         [m_scrollbarPainterController.get() setVerticalScrollerImp:nil];
866     }
867 }
868
869 void ScrollAnimatorMac::didAddHorizontalScrollbar(Scrollbar* scrollbar)
870 {
871     if (isScrollbarOverlayAPIAvailable()) {
872         ScrollbarPainter painter = scrollbarPainterForScrollbar(scrollbar);
873         if (!painter)
874             return;
875
876         ASSERT(!m_horizontalScrollbarPainterDelegate);
877         m_horizontalScrollbarPainterDelegate.adoptNS([[WebScrollbarPainterDelegate alloc] initWithScrollbar:scrollbar]);
878
879         [painter setDelegate:m_horizontalScrollbarPainterDelegate.get()];
880         [m_scrollbarPainterController.get() setHorizontalScrollerImp:painter];
881         if (scrollableArea()->inLiveResize())
882             [painter setKnobAlpha:1];
883     }
884 }
885
886 void ScrollAnimatorMac::willRemoveHorizontalScrollbar(Scrollbar* scrollbar)
887 {
888     if (isScrollbarOverlayAPIAvailable()) {
889         ScrollbarPainter painter = scrollbarPainterForScrollbar(scrollbar);
890         if (!painter)
891             return;
892
893         ASSERT(m_horizontalScrollbarPainterDelegate);
894         [m_horizontalScrollbarPainterDelegate.get() invalidate];
895         m_horizontalScrollbarPainterDelegate = nullptr;
896
897         [painter setDelegate:nil];
898         [m_scrollbarPainterController.get() setHorizontalScrollerImp:nil];
899     }
900 }
901
902 void ScrollAnimatorMac::cancelAnimations()
903 {
904     m_haveScrolledSincePageLoad = false;
905
906     if (isScrollbarOverlayAPIAvailable()) {
907         if (scrollbarPaintTimerIsActive())
908             stopScrollbarPaintTimer();
909         [m_horizontalScrollbarPainterDelegate.get() cancelAnimations];
910         [m_verticalScrollbarPainterDelegate.get() cancelAnimations];
911     }
912 }
913
914 #if ENABLE(RUBBER_BANDING)
915
916 static const float scrollVelocityZeroingTimeout = 0.10f;
917 static const float rubberbandStiffness = 20;
918 static const float rubberbandDirectionLockStretchRatio = 1;
919 static const float rubberbandMinimumRequiredDeltaBeforeStretch = 10;
920 static const float rubberbandAmplitude = 0.31f;
921 static const float rubberbandPeriod = 1.6f;
922
923 static float elasticDeltaForTimeDelta(float initialPosition, float initialVelocity, float elapsedTime)
924 {
925     float amplitude = rubberbandAmplitude;
926     float period = rubberbandPeriod;
927     float criticalDampeningFactor = expf((-elapsedTime * rubberbandStiffness) / period);
928              
929     return (initialPosition + (-initialVelocity * elapsedTime * amplitude)) * criticalDampeningFactor;
930 }
931
932 static float elasticDeltaForReboundDelta(float delta)
933 {
934     float stiffness = std::max(rubberbandStiffness, 1.0f);
935     return delta / stiffness;
936 }
937
938 static float reboundDeltaForElasticDelta(float delta)
939 {
940     return delta * rubberbandStiffness;
941 }
942
943 static float scrollWheelMultiplier()
944 {
945     static float multiplier = -1;
946     if (multiplier < 0) {
947         multiplier = [[NSUserDefaults standardUserDefaults] floatForKey:@"NSScrollWheelMultiplier"];
948         if (multiplier <= 0)
949             multiplier = 1;
950     }
951     return multiplier;
952 }
953
954 static inline bool isScrollingLeftAndShouldNotRubberBand(const PlatformWheelEvent& wheelEvent, ScrollableArea* scrollableArea)
955 {
956     return wheelEvent.deltaX() > 0 && !scrollableArea->shouldRubberBandInDirection(ScrollLeft);
957 }
958
959 static inline bool isScrollingRightAndShouldNotRubberBand(const PlatformWheelEvent& wheelEvent, ScrollableArea* scrollableArea)
960 {
961     return wheelEvent.deltaX() < 0 && !scrollableArea->shouldRubberBandInDirection(ScrollRight);
962 }
963
964 bool ScrollAnimatorMac::handleWheelEvent(const PlatformWheelEvent& wheelEvent)
965 {
966     m_haveScrolledSincePageLoad = true;
967
968     if (!wheelEvent.hasPreciseScrollingDeltas())
969         return ScrollAnimator::handleWheelEvent(wheelEvent);
970
971     // FIXME: This is somewhat roundabout hack to allow forwarding wheel events
972     // up to the parent scrollable area. It takes advantage of the fact that
973     // the base class implementation of handleWheelEvent will not accept the
974     // wheel event if there is nowhere to scroll.
975     if (fabsf(wheelEvent.deltaY()) >= fabsf(wheelEvent.deltaX())) {
976         if (!allowsVerticalStretching())
977             return ScrollAnimator::handleWheelEvent(wheelEvent);
978     } else {
979         if (!allowsHorizontalStretching())
980             return ScrollAnimator::handleWheelEvent(wheelEvent);
981         
982         if (m_scrollableArea->horizontalScrollbar()) {
983             // If there is a scrollbar, we aggregate the wheel events to get an
984             // overall trend of the scroll. If the direction of the scroll is ever
985             // in the opposite direction of the pin location, then we switch the
986             // boolean, and rubber band. That is, if we were pinned to the left,
987             // and we ended up scrolling to the right, we rubber band.
988             m_cumulativeHorizontalScroll += wheelEvent.deltaX();
989             if (m_scrollerInitiallyPinnedOnLeft && m_cumulativeHorizontalScroll < 0)
990                 m_didCumulativeHorizontalScrollEverSwitchToOppositeDirectionOfPin = true;
991             if (m_scrollerInitiallyPinnedOnRight && m_cumulativeHorizontalScroll > 0)
992                 m_didCumulativeHorizontalScrollEverSwitchToOppositeDirectionOfPin = true;
993         }
994
995         // After a gesture begins, we go through:
996         // 1+ PlatformWheelEventPhaseNone
997         // 0+ PlatformWheelEventPhaseChanged
998         // 1 PlatformWheelEventPhaseEnded if there was at least one changed event
999         if (wheelEvent.momentumPhase() == PlatformWheelEventPhaseNone && !m_didCumulativeHorizontalScrollEverSwitchToOppositeDirectionOfPin) {
1000             if ((isScrollingLeftAndShouldNotRubberBand(wheelEvent, m_scrollableArea) &&
1001                 m_scrollerInitiallyPinnedOnLeft &&
1002                 m_scrollableArea->isHorizontalScrollerPinnedToMinimumPosition()) ||
1003                 (isScrollingRightAndShouldNotRubberBand(wheelEvent, m_scrollableArea) &&
1004                 m_scrollerInitiallyPinnedOnRight &&
1005                 m_scrollableArea->isHorizontalScrollerPinnedToMaximumPosition())) {
1006                 return ScrollAnimator::handleWheelEvent(wheelEvent);
1007             }
1008         }
1009     }
1010
1011     bool isMomentumScrollEvent = (wheelEvent.momentumPhase() != PlatformWheelEventPhaseNone);
1012     if (m_scrollElasticityController.m_ignoreMomentumScrolls && (isMomentumScrollEvent || m_scrollElasticityController.m_snapRubberbandTimerIsActive)) {
1013         if (wheelEvent.momentumPhase() == PlatformWheelEventPhaseEnded) {
1014             m_scrollElasticityController.m_ignoreMomentumScrolls = false;
1015             return true;
1016         }
1017         return false;
1018     }
1019
1020     smoothScrollWithEvent(wheelEvent);
1021     return true;
1022 }
1023
1024 void ScrollAnimatorMac::handleGestureEvent(const PlatformGestureEvent& gestureEvent)
1025 {
1026     if (gestureEvent.type() == PlatformEvent::GestureScrollBegin)
1027         beginScrollGesture();
1028     else if (gestureEvent.type() == PlatformEvent::GestureScrollEnd)
1029         endScrollGesture();
1030 }
1031
1032 bool ScrollAnimatorMac::pinnedInDirection(float deltaX, float deltaY)
1033 {
1034     FloatSize limitDelta;
1035     if (fabsf(deltaY) >= fabsf(deltaX)) {
1036         if (deltaY < 0) {
1037             // We are trying to scroll up.  Make sure we are not pinned to the top
1038             limitDelta.setHeight(m_scrollableArea->visibleContentRect().y() + + m_scrollableArea->scrollOrigin().y());
1039         } else {
1040             // We are trying to scroll down.  Make sure we are not pinned to the bottom
1041             limitDelta.setHeight(m_scrollableArea->contentsSize().height() - (m_scrollableArea->visibleContentRect().maxY() + m_scrollableArea->scrollOrigin().y()));
1042         }
1043     } else if (deltaX != 0) {
1044         if (deltaX < 0) {
1045             // We are trying to scroll left.  Make sure we are not pinned to the left
1046             limitDelta.setWidth(m_scrollableArea->visibleContentRect().x() + m_scrollableArea->scrollOrigin().x());
1047         } else {
1048             // We are trying to scroll right.  Make sure we are not pinned to the right
1049             limitDelta.setWidth(m_scrollableArea->contentsSize().width() - (m_scrollableArea->visibleContentRect().maxX() + m_scrollableArea->scrollOrigin().x()));
1050         }
1051     }
1052     
1053     if ((deltaX != 0 || deltaY != 0) && (limitDelta.width() < 1 && limitDelta.height() < 1))
1054         return true;
1055     return false;
1056 }
1057
1058 IntSize ScrollAnimatorMac::stretchAmount()
1059 {
1060     return m_scrollableArea->overhangAmount();
1061 }
1062
1063 void ScrollAnimatorMac::startSnapRubberbandTimer()
1064 {
1065     m_snapRubberBandTimer.startRepeating(1.0 / 60.0);
1066 }
1067
1068 void ScrollAnimatorMac::stopSnapRubberbandTimer()
1069 {
1070     m_snapRubberBandTimer.stop();
1071 }
1072
1073 bool ScrollAnimatorMac::allowsVerticalStretching() const
1074 {
1075     switch (m_scrollableArea->verticalScrollElasticity()) {
1076     case ScrollElasticityAutomatic: {
1077         Scrollbar* hScroller = m_scrollableArea->horizontalScrollbar();
1078         Scrollbar* vScroller = m_scrollableArea->verticalScrollbar();
1079         return (((vScroller && vScroller->enabled()) || (!hScroller || !hScroller->enabled())));
1080     }
1081     case ScrollElasticityNone:
1082         return false;
1083     case ScrollElasticityAllowed:
1084         return true;
1085     }
1086
1087     ASSERT_NOT_REACHED();
1088     return false;
1089 }
1090
1091 bool ScrollAnimatorMac::allowsHorizontalStretching() const
1092 {
1093     switch (m_scrollableArea->horizontalScrollElasticity()) {
1094     case ScrollElasticityAutomatic: {
1095         Scrollbar* hScroller = m_scrollableArea->horizontalScrollbar();
1096         Scrollbar* vScroller = m_scrollableArea->verticalScrollbar();
1097         return (((hScroller && hScroller->enabled()) || (!vScroller || !vScroller->enabled())));
1098     }
1099     case ScrollElasticityNone:
1100         return false;
1101     case ScrollElasticityAllowed:
1102         return true;
1103     }
1104
1105     ASSERT_NOT_REACHED();
1106     return false;
1107 }
1108
1109 void ScrollAnimatorMac::smoothScrollWithEvent(const PlatformWheelEvent& wheelEvent)
1110 {
1111     m_haveScrolledSincePageLoad = true;
1112
1113     float deltaX = m_scrollElasticityController.m_overflowScrollDelta.width();
1114     float deltaY = m_scrollElasticityController.m_overflowScrollDelta.height();
1115
1116     // Reset overflow values because we may decide to remove delta at various points and put it into overflow.
1117     m_scrollElasticityController.m_overflowScrollDelta = FloatSize();
1118
1119     float eventCoalescedDeltaX = -wheelEvent.deltaX();
1120     float eventCoalescedDeltaY = -wheelEvent.deltaY();
1121
1122     deltaX += eventCoalescedDeltaX;
1123     deltaY += eventCoalescedDeltaY;
1124
1125     // Slightly prefer scrolling vertically by applying the = case to deltaY
1126     if (fabsf(deltaY) >= fabsf(deltaX))
1127         deltaX = 0;
1128     else
1129         deltaY = 0;
1130
1131     bool isVerticallyStretched = false;
1132     bool isHorizontallyStretched = false;
1133     bool shouldStretch = false;
1134     
1135     IntSize stretchAmount = m_scrollableArea->overhangAmount();
1136
1137     isHorizontallyStretched = stretchAmount.width();
1138     isVerticallyStretched = stretchAmount.height();
1139
1140     PlatformWheelEventPhase phase = wheelEvent.momentumPhase();
1141
1142     // If we are starting momentum scrolling then do some setup.
1143     if (!m_scrollElasticityController.m_momentumScrollInProgress && (phase == PlatformWheelEventPhaseBegan || phase == PlatformWheelEventPhaseChanged))
1144         m_scrollElasticityController.m_momentumScrollInProgress = true;
1145
1146     CFTimeInterval timeDelta = wheelEvent.timestamp() - m_scrollElasticityController.m_lastMomentumScrollTimestamp;
1147     if (m_scrollElasticityController.m_inScrollGesture || m_scrollElasticityController.m_momentumScrollInProgress) {
1148         if (m_scrollElasticityController.m_lastMomentumScrollTimestamp && timeDelta > 0 && timeDelta < scrollVelocityZeroingTimeout) {
1149             m_scrollElasticityController.m_momentumVelocity.setWidth(eventCoalescedDeltaX / (float)timeDelta);
1150             m_scrollElasticityController.m_momentumVelocity.setHeight(eventCoalescedDeltaY / (float)timeDelta);
1151             m_scrollElasticityController.m_lastMomentumScrollTimestamp = wheelEvent.timestamp();
1152         } else {
1153             m_scrollElasticityController.m_lastMomentumScrollTimestamp = wheelEvent.timestamp();
1154             m_scrollElasticityController.m_momentumVelocity = FloatSize();
1155         }
1156
1157         if (isVerticallyStretched) {
1158             if (!isHorizontallyStretched && pinnedInDirection(deltaX, 0)) {                
1159                 // Stretching only in the vertical.
1160                 if (deltaY != 0 && (fabsf(deltaX / deltaY) < rubberbandDirectionLockStretchRatio))
1161                     deltaX = 0;
1162                 else if (fabsf(deltaX) < rubberbandMinimumRequiredDeltaBeforeStretch) {
1163                     m_scrollElasticityController.m_overflowScrollDelta.setWidth(m_scrollElasticityController.m_overflowScrollDelta.width() + deltaX);
1164                     deltaX = 0;
1165                 } else
1166                     m_scrollElasticityController.m_overflowScrollDelta.setWidth(m_scrollElasticityController.m_overflowScrollDelta.width() + deltaX);
1167             }
1168         } else if (isHorizontallyStretched) {
1169             // Stretching only in the horizontal.
1170             if (pinnedInDirection(0, deltaY)) {
1171                 if (deltaX != 0 && (fabsf(deltaY / deltaX) < rubberbandDirectionLockStretchRatio))
1172                     deltaY = 0;
1173                 else if (fabsf(deltaY) < rubberbandMinimumRequiredDeltaBeforeStretch) {
1174                     m_scrollElasticityController.m_overflowScrollDelta.setHeight(m_scrollElasticityController.m_overflowScrollDelta.height() + deltaY);
1175                     deltaY = 0;
1176                 } else
1177                     m_scrollElasticityController.m_overflowScrollDelta.setHeight(m_scrollElasticityController.m_overflowScrollDelta.height() + deltaY);
1178             }
1179         } else {
1180             // Not stretching at all yet.
1181             if (pinnedInDirection(deltaX, deltaY)) {
1182                 if (fabsf(deltaY) >= fabsf(deltaX)) {
1183                     if (fabsf(deltaX) < rubberbandMinimumRequiredDeltaBeforeStretch) {
1184                         m_scrollElasticityController.m_overflowScrollDelta.setWidth(m_scrollElasticityController.m_overflowScrollDelta.width() + deltaX);
1185                         deltaX = 0;
1186                     } else
1187                         m_scrollElasticityController.m_overflowScrollDelta.setWidth(m_scrollElasticityController.m_overflowScrollDelta.width() + deltaX);
1188                 }
1189                 shouldStretch = true;
1190             }
1191         }
1192     }
1193
1194     if (deltaX != 0 || deltaY != 0) {
1195         if (!(shouldStretch || isVerticallyStretched || isHorizontallyStretched)) {
1196             if (deltaY != 0) {
1197                 deltaY *= scrollWheelMultiplier();
1198                 immediateScrollBy(FloatSize(0, deltaY));
1199             }
1200             if (deltaX != 0) {
1201                 deltaX *= scrollWheelMultiplier();
1202                 immediateScrollBy(FloatSize(deltaX, 0));
1203             }
1204         } else {
1205             if (!allowsHorizontalStretching()) {
1206                 deltaX = 0;
1207                 eventCoalescedDeltaX = 0;
1208             } else if ((deltaX != 0) && !isHorizontallyStretched && !pinnedInDirection(deltaX, 0)) {
1209                 deltaX *= scrollWheelMultiplier();
1210
1211                 m_scrollableArea->setConstrainsScrollingToContentEdge(false);
1212                 immediateScrollBy(FloatSize(deltaX, 0));
1213                 m_scrollableArea->setConstrainsScrollingToContentEdge(true);
1214
1215                 deltaX = 0;
1216             }
1217             
1218             if (!allowsVerticalStretching()) {
1219                 deltaY = 0;
1220                 eventCoalescedDeltaY = 0;
1221             } else if ((deltaY != 0) && !isVerticallyStretched && !pinnedInDirection(0, deltaY)) {
1222                 deltaY *= scrollWheelMultiplier();
1223
1224                 m_scrollableArea->setConstrainsScrollingToContentEdge(false);
1225                 immediateScrollBy(FloatSize(0, deltaY));
1226                 m_scrollableArea->setConstrainsScrollingToContentEdge(true);
1227
1228                 deltaY = 0;
1229             }
1230             
1231             IntSize stretchAmount = m_scrollableArea->overhangAmount();
1232         
1233             if (m_scrollElasticityController.m_momentumScrollInProgress) {
1234                 if ((pinnedInDirection(eventCoalescedDeltaX, eventCoalescedDeltaY) || (fabsf(eventCoalescedDeltaX) + fabsf(eventCoalescedDeltaY) <= 0)) && m_scrollElasticityController.m_lastMomentumScrollTimestamp) {
1235                     m_scrollElasticityController.m_ignoreMomentumScrolls = true;
1236                     m_scrollElasticityController.m_momentumScrollInProgress = false;
1237                     snapRubberBand();
1238                 }
1239             }
1240
1241             m_scrollElasticityController.m_stretchScrollForce.setWidth(m_scrollElasticityController.m_stretchScrollForce.width() + deltaX);
1242             m_scrollElasticityController.m_stretchScrollForce.setHeight(m_scrollElasticityController.m_stretchScrollForce.height() + deltaY);
1243
1244             FloatSize dampedDelta(ceilf(elasticDeltaForReboundDelta(m_scrollElasticityController.m_stretchScrollForce.width())), ceilf(elasticDeltaForReboundDelta(m_scrollElasticityController.m_stretchScrollForce.height())));
1245             FloatPoint origOrigin = (m_scrollableArea->visibleContentRect().location() + m_scrollableArea->scrollOrigin()) - stretchAmount;
1246             FloatPoint newOrigin = origOrigin + dampedDelta;
1247
1248             if (origOrigin != newOrigin) {
1249                 m_scrollableArea->setConstrainsScrollingToContentEdge(false);
1250                 immediateScrollTo(newOrigin);
1251                 m_scrollableArea->setConstrainsScrollingToContentEdge(true);
1252             }
1253         }
1254     }
1255
1256     if (m_scrollElasticityController.m_momentumScrollInProgress && phase == PlatformWheelEventPhaseEnded) {
1257         m_scrollElasticityController.m_momentumScrollInProgress = false;
1258         m_scrollElasticityController.m_ignoreMomentumScrolls = false;
1259         m_scrollElasticityController.m_lastMomentumScrollTimestamp = 0;
1260     }
1261 }
1262
1263 void ScrollAnimatorMac::beginScrollGesture()
1264 {
1265     didBeginScrollGesture();
1266
1267     m_scrollerInitiallyPinnedOnLeft = m_scrollableArea->isHorizontalScrollerPinnedToMinimumPosition();
1268     m_scrollerInitiallyPinnedOnRight = m_scrollableArea->isHorizontalScrollerPinnedToMaximumPosition();
1269     m_haveScrolledSincePageLoad = true;
1270     m_cumulativeHorizontalScroll = 0;
1271     m_didCumulativeHorizontalScrollEverSwitchToOppositeDirectionOfPin = false;
1272
1273     m_scrollElasticityController.beginScrollGesture();
1274 }
1275
1276 void ScrollAnimatorMac::endScrollGesture()
1277 {
1278     didEndScrollGesture();
1279
1280     snapRubberBand();
1281 }
1282
1283 void ScrollAnimatorMac::snapRubberBand()
1284 {
1285     CFTimeInterval timeDelta = systemUptime() - m_scrollElasticityController.m_lastMomentumScrollTimestamp;
1286     if (m_scrollElasticityController.m_lastMomentumScrollTimestamp && timeDelta >= scrollVelocityZeroingTimeout)
1287         m_scrollElasticityController.m_momentumVelocity = FloatSize();
1288
1289     m_scrollElasticityController.m_inScrollGesture = false;
1290
1291     if (m_scrollElasticityController.m_snapRubberbandTimerIsActive)
1292         return;
1293
1294     m_scrollElasticityController.m_startTime = [NSDate timeIntervalSinceReferenceDate];
1295     m_scrollElasticityController.m_startStretch = FloatSize();
1296     m_scrollElasticityController.m_origOrigin = FloatPoint();
1297     m_scrollElasticityController.m_origVelocity = FloatSize();
1298
1299     m_snapRubberBandTimer.startRepeating(1.0/60.0);
1300     m_scrollElasticityController.m_snapRubberbandTimerIsActive = true;
1301 }
1302
1303 static inline float roundTowardZero(float num)
1304 {
1305     return num > 0 ? ceilf(num - 0.5f) : floorf(num + 0.5f);
1306 }
1307
1308 static inline float roundToDevicePixelTowardZero(float num)
1309 {
1310     float roundedNum = roundf(num);
1311     if (fabs(num - roundedNum) < 0.125)
1312         num = roundedNum;
1313
1314     return roundTowardZero(num);
1315 }
1316
1317 void ScrollAnimatorMac::snapRubberBandTimerFired(Timer<ScrollAnimatorMac>*)
1318 {
1319     if (!m_scrollElasticityController.m_momentumScrollInProgress || m_scrollElasticityController.m_ignoreMomentumScrolls) {
1320         CFTimeInterval timeDelta = [NSDate timeIntervalSinceReferenceDate] - m_scrollElasticityController.m_startTime;
1321
1322         if (m_scrollElasticityController.m_startStretch == FloatSize()) {
1323             m_scrollElasticityController.m_startStretch = m_scrollableArea->overhangAmount();
1324             if (m_scrollElasticityController.m_startStretch == FloatSize()) {    
1325                 m_snapRubberBandTimer.stop();
1326
1327                 m_scrollElasticityController.m_stretchScrollForce = FloatSize();
1328                 m_scrollElasticityController.m_startTime = 0;
1329                 m_scrollElasticityController.m_startStretch = FloatSize();
1330                 m_scrollElasticityController.m_origOrigin = FloatPoint();
1331                 m_scrollElasticityController.m_origVelocity = FloatSize();
1332                 m_scrollElasticityController.m_snapRubberbandTimerIsActive = false;
1333
1334                 return;
1335             }
1336
1337             m_scrollElasticityController.m_origOrigin = (m_scrollableArea->visibleContentRect().location() + m_scrollableArea->scrollOrigin()) - m_scrollElasticityController.m_startStretch;
1338             m_scrollElasticityController.m_origVelocity = m_scrollElasticityController.m_momentumVelocity;
1339
1340             // Just like normal scrolling, prefer vertical rubberbanding
1341             if (fabsf(m_scrollElasticityController.m_origVelocity.height()) >= fabsf(m_scrollElasticityController.m_origVelocity.width()))
1342                 m_scrollElasticityController.m_origVelocity.setWidth(0);
1343             
1344             // Don't rubber-band horizontally if it's not possible to scroll horizontally
1345             Scrollbar* hScroller = m_scrollableArea->horizontalScrollbar();
1346             if (!hScroller || !hScroller->enabled())
1347                 m_scrollElasticityController.m_origVelocity.setWidth(0);
1348             
1349             // Don't rubber-band vertically if it's not possible to scroll horizontally
1350             Scrollbar* vScroller = m_scrollableArea->verticalScrollbar();
1351             if (!vScroller || !vScroller->enabled())
1352                 m_scrollElasticityController.m_origVelocity.setHeight(0);
1353         }
1354
1355         FloatPoint delta(roundToDevicePixelTowardZero(elasticDeltaForTimeDelta(m_scrollElasticityController.m_startStretch.width(), -m_scrollElasticityController.m_origVelocity.width(), (float)timeDelta)),
1356                          roundToDevicePixelTowardZero(elasticDeltaForTimeDelta(m_scrollElasticityController.m_startStretch.height(), -m_scrollElasticityController.m_origVelocity.height(), (float)timeDelta)));
1357
1358         if (fabs(delta.x()) >= 1 || fabs(delta.y()) >= 1) {
1359             FloatPoint newOrigin = m_scrollElasticityController.m_origOrigin + delta;
1360
1361             m_scrollableArea->setConstrainsScrollingToContentEdge(false);
1362             immediateScrollTo(newOrigin);
1363             m_scrollableArea->setConstrainsScrollingToContentEdge(true);
1364
1365             FloatSize newStretch = m_scrollableArea->overhangAmount();
1366             
1367             m_scrollElasticityController.m_stretchScrollForce.setWidth(reboundDeltaForElasticDelta(newStretch.width()));
1368             m_scrollElasticityController.m_stretchScrollForce.setHeight(reboundDeltaForElasticDelta(newStretch.height()));
1369         } else {
1370             immediateScrollTo(m_scrollElasticityController.m_origOrigin);
1371
1372             m_snapRubberBandTimer.stop();
1373
1374             m_scrollElasticityController.m_stretchScrollForce = FloatSize();
1375             m_scrollElasticityController.m_startTime = 0;
1376             m_scrollElasticityController.m_startStretch = FloatSize();
1377             m_scrollElasticityController.m_origOrigin = FloatPoint();
1378             m_scrollElasticityController.m_origVelocity = FloatSize();
1379             m_scrollElasticityController.m_snapRubberbandTimerIsActive = false;
1380         }
1381     } else {
1382         m_scrollElasticityController.m_startTime = [NSDate timeIntervalSinceReferenceDate];
1383         m_scrollElasticityController.m_startStretch = FloatSize();
1384     }
1385 }
1386 #endif
1387
1388 void ScrollAnimatorMac::setIsActive()
1389 {
1390     if (isScrollbarOverlayAPIAvailable()) {
1391         if (needsScrollerStyleUpdate())
1392             updateScrollerStyle();
1393     }
1394 }
1395
1396 void ScrollAnimatorMac::updateScrollerStyle()
1397 {
1398     if (!isScrollbarOverlayAPIAvailable())
1399         return;
1400
1401     if (!scrollableArea()->isOnActivePage()) {
1402         setNeedsScrollerStyleUpdate(true);
1403         return;
1404     }
1405
1406     ScrollbarThemeMac* macTheme = macScrollbarTheme();
1407     if (!macTheme) {
1408         setNeedsScrollerStyleUpdate(false);
1409         return;
1410     }
1411
1412     NSScrollerStyle newStyle = [m_scrollbarPainterController.get() scrollerStyle];
1413
1414     if (Scrollbar* verticalScrollbar = scrollableArea()->verticalScrollbar()) {
1415         verticalScrollbar->invalidate();
1416
1417         ScrollbarPainter oldVerticalPainter = [m_scrollbarPainterController.get() verticalScrollerImp];
1418         ScrollbarPainter newVerticalPainter = [NSClassFromString(@"NSScrollerImp") scrollerImpWithStyle:newStyle 
1419                                                                                     controlSize:(NSControlSize)verticalScrollbar->controlSize() 
1420                                                                                     horizontal:NO 
1421                                                                                     replacingScrollerImp:oldVerticalPainter];
1422         macTheme->setNewPainterForScrollbar(verticalScrollbar, newVerticalPainter);
1423         [m_scrollbarPainterController.get() setVerticalScrollerImp:newVerticalPainter];
1424
1425         // The different scrollbar styles have different thicknesses, so we must re-set the 
1426         // frameRect to the new thickness, and the re-layout below will ensure the position
1427         // and length are properly updated.
1428         int thickness = macTheme->scrollbarThickness(verticalScrollbar->controlSize());
1429         verticalScrollbar->setFrameRect(IntRect(0, 0, thickness, thickness));
1430     }
1431
1432     if (Scrollbar* horizontalScrollbar = scrollableArea()->horizontalScrollbar()) {
1433         horizontalScrollbar->invalidate();
1434
1435         ScrollbarPainter oldHorizontalPainter = [m_scrollbarPainterController.get() horizontalScrollerImp];
1436         ScrollbarPainter newHorizontalPainter = [NSClassFromString(@"NSScrollerImp") scrollerImpWithStyle:newStyle 
1437                                                                                     controlSize:(NSControlSize)horizontalScrollbar->controlSize() 
1438                                                                                     horizontal:YES 
1439                                                                                     replacingScrollerImp:oldHorizontalPainter];
1440         macTheme->setNewPainterForScrollbar(horizontalScrollbar, newHorizontalPainter);
1441         [m_scrollbarPainterController.get() setHorizontalScrollerImp:newHorizontalPainter];
1442
1443         // The different scrollbar styles have different thicknesses, so we must re-set the 
1444         // frameRect to the new thickness, and the re-layout below will ensure the position
1445         // and length are properly updated.
1446         int thickness = macTheme->scrollbarThickness(horizontalScrollbar->controlSize());
1447         horizontalScrollbar->setFrameRect(IntRect(0, 0, thickness, thickness));
1448     }
1449
1450     // If needsScrollerStyleUpdate() is true, then the page is restoring from the page cache, and 
1451     // a relayout will happen on its own. Otherwise, we must initiate a re-layout ourselves.
1452     scrollableArea()->scrollbarStyleChanged(newStyle, !needsScrollerStyleUpdate());
1453
1454     setNeedsScrollerStyleUpdate(false);
1455 }
1456
1457 void ScrollAnimatorMac::startScrollbarPaintTimer()
1458 {
1459     m_initialScrollbarPaintTimer.startOneShot(0.1);
1460 }
1461
1462 bool ScrollAnimatorMac::scrollbarPaintTimerIsActive() const
1463 {
1464     return m_initialScrollbarPaintTimer.isActive();
1465 }
1466
1467 void ScrollAnimatorMac::stopScrollbarPaintTimer()
1468 {
1469     m_initialScrollbarPaintTimer.stop();
1470 }
1471
1472 void ScrollAnimatorMac::initialScrollbarPaintTimerFired(Timer<ScrollAnimatorMac>*)
1473 {
1474     if (isScrollbarOverlayAPIAvailable()) {
1475         // To force the scrollbars to flash, we have to call hide first. Otherwise, the ScrollbarPainterController
1476         // might think that the scrollbars are already showing and bail early.
1477         [m_scrollbarPainterController.get() hideOverlayScrollers];
1478         [m_scrollbarPainterController.get() flashScrollers];
1479     }
1480 }
1481
1482 void ScrollAnimatorMac::setVisibleScrollerThumbRect(const IntRect& scrollerThumb)
1483 {
1484     IntRect rectInViewCoordinates = scrollerThumb;
1485     if (Scrollbar* verticalScrollbar = m_scrollableArea->verticalScrollbar())
1486         rectInViewCoordinates = verticalScrollbar->convertToContainingView(scrollerThumb);
1487
1488     if (rectInViewCoordinates == m_visibleScrollerThumbRect)
1489         return;
1490
1491     m_scrollableArea->setVisibleScrollerThumbRect(rectInViewCoordinates);
1492     m_visibleScrollerThumbRect = rectInViewCoordinates;
1493 }
1494
1495 } // namespace WebCore
1496
1497 #endif // ENABLE(SMOOTH_SCROLLING)