ce0f160f1a1ebf6178c0ba888284f6af3d11ca8e
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / platform / mac / ScrollAnimatorMac.mm
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