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