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