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