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