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