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