ScrollbarThemes should be returned by reference.
[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 const bool globalSupportsUIStateTransitionProgress = [NSClassFromString(@"NSScrollerImp") instancesRespondToSelector:@selector(mouseEnteredScroller)];
49     return globalSupportsUIStateTransitionProgress;
50 }
51
52 static bool supportsExpansionTransitionProgress()
53 {
54     static const bool globalSupportsExpansionTransitionProgress = [NSClassFromString(@"NSScrollerImp") instancesRespondToSelector:@selector(expansionTransitionProgress)];
55     return globalSupportsExpansionTransitionProgress;
56 }
57
58 static bool supportsContentAreaScrolledInDirection()
59 {
60     static const 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) : nullptr;
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     updateActiveScrollSnapIndexForOffset();
762 }
763
764 bool ScrollAnimatorMac::isRubberBandInProgress() const
765 {
766 #if !ENABLE(RUBBER_BANDING)
767     return false;
768 #else
769     return m_scrollController.isRubberBandInProgress();
770 #endif
771 }
772
773 bool ScrollAnimatorMac::isScrollSnapInProgress() const
774 {
775 #if ENABLE(CSS_SCROLL_SNAP)
776     return m_scrollController.isScrollSnapInProgress();
777 #else
778     return false;
779 #endif
780 }
781
782 void ScrollAnimatorMac::immediateScrollToPointForScrollAnimation(const FloatPoint& newPosition)
783 {
784     ASSERT(m_scrollAnimationHelper);
785     immediateScrollTo(newPosition);
786 }
787
788 void ScrollAnimatorMac::notifyPositionChanged(const FloatSize& delta)
789 {
790     notifyContentAreaScrolled(delta);
791     ScrollAnimator::notifyPositionChanged(delta);
792 }
793
794 void ScrollAnimatorMac::contentAreaWillPaint() const
795 {
796     if ([m_scrollbarPainterController overlayScrollerStateIsLocked])
797         return;
798
799     [m_scrollbarPainterController contentAreaWillDraw];
800 }
801
802 void ScrollAnimatorMac::mouseEnteredContentArea() const
803 {
804     if ([m_scrollbarPainterController overlayScrollerStateIsLocked])
805         return;
806
807     [m_scrollbarPainterController mouseEnteredContentArea];
808 }
809
810 void ScrollAnimatorMac::mouseExitedContentArea() const
811 {
812     if ([m_scrollbarPainterController overlayScrollerStateIsLocked])
813         return;
814
815     [m_scrollbarPainterController mouseExitedContentArea];
816 }
817
818 void ScrollAnimatorMac::mouseMovedInContentArea() const
819 {
820     if ([m_scrollbarPainterController overlayScrollerStateIsLocked])
821         return;
822
823     [m_scrollbarPainterController mouseMovedInContentArea];
824 }
825
826 void ScrollAnimatorMac::mouseEnteredScrollbar(Scrollbar* scrollbar) const
827 {
828     // At this time, only legacy scrollbars needs to send notifications here.
829     if (recommendedScrollerStyle() != NSScrollerStyleLegacy)
830         return;
831
832     if ([m_scrollbarPainterController overlayScrollerStateIsLocked])
833         return;
834
835     if (!supportsUIStateTransitionProgress())
836         return;
837     if (ScrollbarPainter painter = scrollbarPainterForScrollbar(*scrollbar))
838         [painter mouseEnteredScroller];
839 }
840
841 void ScrollAnimatorMac::mouseExitedScrollbar(Scrollbar* scrollbar) const
842 {
843     // At this time, only legacy scrollbars needs to send notifications here.
844     if (recommendedScrollerStyle() != NSScrollerStyleLegacy)
845         return;
846
847     if ([m_scrollbarPainterController overlayScrollerStateIsLocked])
848         return;
849
850     if (!supportsUIStateTransitionProgress())
851         return;
852     if (ScrollbarPainter painter = scrollbarPainterForScrollbar(*scrollbar))
853         [painter mouseExitedScroller];
854 }
855
856 void ScrollAnimatorMac::willStartLiveResize()
857 {
858     if ([m_scrollbarPainterController overlayScrollerStateIsLocked])
859         return;
860
861     [m_scrollbarPainterController startLiveResize];
862 }
863
864 void ScrollAnimatorMac::contentsResized() const
865 {
866     if ([m_scrollbarPainterController overlayScrollerStateIsLocked])
867         return;
868
869     [m_scrollbarPainterController contentAreaDidResize];
870 }
871
872 void ScrollAnimatorMac::willEndLiveResize()
873 {
874     if ([m_scrollbarPainterController overlayScrollerStateIsLocked])
875         return;
876
877     [m_scrollbarPainterController endLiveResize];
878 }
879
880 void ScrollAnimatorMac::contentAreaDidShow() const
881 {
882     if ([m_scrollbarPainterController overlayScrollerStateIsLocked])
883         return;
884
885     [m_scrollbarPainterController windowOrderedIn];
886 }
887
888 void ScrollAnimatorMac::contentAreaDidHide() const
889 {
890     if ([m_scrollbarPainterController overlayScrollerStateIsLocked])
891         return;
892
893     [m_scrollbarPainterController windowOrderedOut];
894 }
895
896 void ScrollAnimatorMac::didBeginScrollGesture() const
897 {
898     if ([m_scrollbarPainterController overlayScrollerStateIsLocked])
899         return;
900
901     [m_scrollbarPainterController beginScrollGesture];
902
903 #if ENABLE(CSS_SCROLL_SNAP) || ENABLE(RUBBER_BANDING)
904     if (m_wheelEventTestTrigger)
905         m_wheelEventTestTrigger->deferTestsForReason(reinterpret_cast<WheelEventTestTrigger::ScrollableAreaIdentifier>(this), WheelEventTestTrigger::ContentScrollInProgress);
906 #endif
907 }
908
909 void ScrollAnimatorMac::didEndScrollGesture() const
910 {
911     if ([m_scrollbarPainterController overlayScrollerStateIsLocked])
912         return;
913
914     [m_scrollbarPainterController endScrollGesture];
915
916 #if ENABLE(CSS_SCROLL_SNAP) || ENABLE(RUBBER_BANDING)
917     if (m_wheelEventTestTrigger)
918         m_wheelEventTestTrigger->removeTestDeferralForReason(reinterpret_cast<WheelEventTestTrigger::ScrollableAreaIdentifier>(this), WheelEventTestTrigger::ContentScrollInProgress);
919 #endif
920 }
921
922 void ScrollAnimatorMac::mayBeginScrollGesture() const
923 {
924     if ([m_scrollbarPainterController overlayScrollerStateIsLocked])
925         return;
926
927     [m_scrollbarPainterController beginScrollGesture];
928     [m_scrollbarPainterController contentAreaScrolled];
929 }
930
931 void ScrollAnimatorMac::lockOverlayScrollbarStateToHidden(bool shouldLockState)
932 {
933     if (shouldLockState)
934         [m_scrollbarPainterController lockOverlayScrollerState:ScrollbarOverlayStateHidden];
935     else {
936         [m_scrollbarPainterController unlockOverlayScrollerState];
937
938         // We never update scroller style for PainterControllers that are locked. If we have a pending
939         // need to update the style, do it once we've unlocked the scroller state.
940         if (m_needsScrollerStyleUpdate)
941             updateScrollerStyle();
942     }
943 }
944
945 bool ScrollAnimatorMac::scrollbarsCanBeActive() const
946 {
947     return ![m_scrollbarPainterController overlayScrollerStateIsLocked];
948 }
949
950 void ScrollAnimatorMac::didAddVerticalScrollbar(Scrollbar* scrollbar)
951 {
952     ScrollbarPainter painter = scrollbarPainterForScrollbar(*scrollbar);
953     if (!painter)
954         return;
955
956     ASSERT(!m_verticalScrollbarPainterDelegate);
957     m_verticalScrollbarPainterDelegate = adoptNS([[WebScrollbarPainterDelegate alloc] initWithScrollbar:scrollbar]);
958
959     [painter setDelegate:(id)m_verticalScrollbarPainterDelegate.get()];
960     if (GraphicsLayer* layer = scrollbar->scrollableArea().layerForVerticalScrollbar())
961         [painter setLayer:layer->platformLayer()];
962
963     [m_scrollbarPainterController setVerticalScrollerImp:painter];
964     if (scrollableArea().inLiveResize())
965         [painter setKnobAlpha:1];
966 }
967
968 void ScrollAnimatorMac::willRemoveVerticalScrollbar(Scrollbar* scrollbar)
969 {
970     ScrollbarPainter painter = scrollbarPainterForScrollbar(*scrollbar);
971     if (!painter)
972         return;
973
974     ASSERT(m_verticalScrollbarPainterDelegate);
975     [m_verticalScrollbarPainterDelegate invalidate];
976     m_verticalScrollbarPainterDelegate = nullptr;
977
978     [painter setDelegate:nil];
979     [m_scrollbarPainterController setVerticalScrollerImp:nil];
980 }
981
982 void ScrollAnimatorMac::didAddHorizontalScrollbar(Scrollbar* scrollbar)
983 {
984     ScrollbarPainter painter = scrollbarPainterForScrollbar(*scrollbar);
985     if (!painter)
986         return;
987
988     ASSERT(!m_horizontalScrollbarPainterDelegate);
989     m_horizontalScrollbarPainterDelegate = adoptNS([[WebScrollbarPainterDelegate alloc] initWithScrollbar:scrollbar]);
990
991     [painter setDelegate:(id)m_horizontalScrollbarPainterDelegate.get()];
992     if (GraphicsLayer* layer = scrollbar->scrollableArea().layerForHorizontalScrollbar())
993         [painter setLayer:layer->platformLayer()];
994
995     [m_scrollbarPainterController setHorizontalScrollerImp:painter];
996     if (scrollableArea().inLiveResize())
997         [painter setKnobAlpha:1];
998 }
999
1000 void ScrollAnimatorMac::willRemoveHorizontalScrollbar(Scrollbar* scrollbar)
1001 {
1002     ScrollbarPainter painter = scrollbarPainterForScrollbar(*scrollbar);
1003     if (!painter)
1004         return;
1005
1006     ASSERT(m_horizontalScrollbarPainterDelegate);
1007     [m_horizontalScrollbarPainterDelegate invalidate];
1008     m_horizontalScrollbarPainterDelegate = nullptr;
1009
1010     [painter setDelegate:nil];
1011     [m_scrollbarPainterController setHorizontalScrollerImp:nil];
1012 }
1013
1014 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
1015 void ScrollAnimatorMac::invalidateScrollbarPartLayers(Scrollbar* scrollbar)
1016 {
1017     ScrollbarPainter painter = scrollbarPainterForScrollbar(*scrollbar);
1018     [painter setNeedsDisplay:YES];
1019 }
1020 #else
1021 void ScrollAnimatorMac::invalidateScrollbarPartLayers(Scrollbar*)
1022 {
1023 }
1024 #endif
1025
1026 void ScrollAnimatorMac::verticalScrollbarLayerDidChange()
1027 {
1028     GraphicsLayer* layer = m_scrollableArea.layerForVerticalScrollbar();
1029     Scrollbar* scrollbar = m_scrollableArea.verticalScrollbar();
1030     if (!scrollbar)
1031         return;
1032
1033     ScrollbarPainter painter = scrollbarPainterForScrollbar(*scrollbar);
1034     if (!painter)
1035         return;
1036
1037     [painter setLayer:layer ? layer->platformLayer() : nil];
1038 }
1039
1040 void ScrollAnimatorMac::horizontalScrollbarLayerDidChange()
1041 {
1042     GraphicsLayer* layer = m_scrollableArea.layerForHorizontalScrollbar();
1043     Scrollbar* scrollbar = m_scrollableArea.horizontalScrollbar();
1044     if (!scrollbar)
1045         return;
1046
1047     ScrollbarPainter painter = scrollbarPainterForScrollbar(*scrollbar);
1048     if (!painter)
1049         return;
1050
1051     [painter setLayer:layer ? layer->platformLayer() : nil];
1052 }
1053
1054 bool ScrollAnimatorMac::shouldScrollbarParticipateInHitTesting(Scrollbar* scrollbar)
1055 {
1056     // Non-overlay scrollbars should always participate in hit testing.
1057     if (recommendedScrollerStyle() != NSScrollerStyleOverlay)
1058         return true;
1059
1060     if (scrollbar->isAlphaLocked())
1061         return true;
1062
1063     // Overlay scrollbars should participate in hit testing whenever they are at all visible.
1064     ScrollbarPainter painter = scrollbarPainterForScrollbar(*scrollbar);
1065     if (!painter)
1066         return false;
1067     return [painter knobAlpha] > 0;
1068 }
1069
1070 void ScrollAnimatorMac::notifyContentAreaScrolled(const FloatSize& delta)
1071 {
1072     // This function is called when a page is going into the page cache, but the page
1073     // isn't really scrolling in that case. We should only pass the message on to the
1074     // ScrollbarPainterController when we're really scrolling on an active page.
1075     if ([m_scrollbarPainterController overlayScrollerStateIsLocked])
1076         return;
1077
1078     if (m_scrollableArea.isHandlingWheelEvent())
1079         sendContentAreaScrolled(delta);
1080     else
1081         sendContentAreaScrolledSoon(delta);
1082 }
1083
1084 void ScrollAnimatorMac::cancelAnimations()
1085 {
1086     m_haveScrolledSincePageLoad = false;
1087
1088     if (scrollbarPaintTimerIsActive())
1089         stopScrollbarPaintTimer();
1090     [m_horizontalScrollbarPainterDelegate cancelAnimations];
1091     [m_verticalScrollbarPainterDelegate cancelAnimations];
1092 }
1093
1094 void ScrollAnimatorMac::handleWheelEventPhase(PlatformWheelEventPhase phase)
1095 {
1096     // This may not have been set to true yet if the wheel event was handled by the ScrollingTree,
1097     // So set it to true here.
1098     m_haveScrolledSincePageLoad = true;
1099
1100     if (phase == PlatformWheelEventPhaseBegan)
1101         didBeginScrollGesture();
1102     else if (phase == PlatformWheelEventPhaseEnded || phase == PlatformWheelEventPhaseCancelled)
1103         didEndScrollGesture();
1104     else if (phase == PlatformWheelEventPhaseMayBegin)
1105         mayBeginScrollGesture();
1106 }
1107
1108 #if ENABLE(RUBBER_BANDING)
1109
1110 bool ScrollAnimatorMac::shouldForwardWheelEventsToParent(const PlatformWheelEvent& wheelEvent)
1111 {
1112     if (std::abs(wheelEvent.deltaY()) >= std::abs(wheelEvent.deltaX()))
1113         return !allowsVerticalStretching(wheelEvent);
1114
1115     return !allowsHorizontalStretching(wheelEvent);
1116 }
1117     
1118 bool ScrollAnimatorMac::handleWheelEvent(const PlatformWheelEvent& wheelEvent)
1119 {
1120     m_haveScrolledSincePageLoad = true;
1121
1122     if (!wheelEvent.hasPreciseScrollingDeltas() || !rubberBandingEnabledForSystem())
1123         return ScrollAnimator::handleWheelEvent(wheelEvent);
1124
1125     // FIXME: This is somewhat roundabout hack to allow forwarding wheel events
1126     // up to the parent scrollable area. It takes advantage of the fact that
1127     // the base class implementation of handleWheelEvent will not accept the
1128     // wheel event if there is nowhere to scroll.
1129     if (shouldForwardWheelEventsToParent(wheelEvent)) {
1130         bool didHandleEvent = ScrollAnimator::handleWheelEvent(wheelEvent);
1131         if (didHandleEvent || (!wheelEvent.deltaX() && !wheelEvent.deltaY()))
1132             handleWheelEventPhase(wheelEvent.phase());
1133         return didHandleEvent;
1134     }
1135
1136     bool didHandleEvent = m_scrollController.handleWheelEvent(wheelEvent);
1137
1138     if (didHandleEvent)
1139         handleWheelEventPhase(wheelEvent.phase());
1140
1141     return didHandleEvent;
1142 }
1143
1144 bool ScrollAnimatorMac::pinnedInDirection(const FloatSize& direction)
1145 {
1146     FloatSize limitDelta;
1147     if (fabsf(direction.height()) >= fabsf(direction.width())) {
1148         if (direction.height() < 0) {
1149             // We are trying to scroll up.  Make sure we are not pinned to the top
1150             limitDelta.setHeight(m_scrollableArea.visibleContentRect().y() + m_scrollableArea.scrollOrigin().y());
1151         } else {
1152             // We are trying to scroll down.  Make sure we are not pinned to the bottom
1153             limitDelta.setHeight(m_scrollableArea.totalContentsSize().height() - (m_scrollableArea.visibleContentRect().maxY() + m_scrollableArea.scrollOrigin().y()));
1154         }
1155     } else if (direction.width()) {
1156         if (direction.width() < 0) {
1157             // We are trying to scroll left.  Make sure we are not pinned to the left
1158             limitDelta.setWidth(m_scrollableArea.visibleContentRect().x() + m_scrollableArea.scrollOrigin().x());
1159         } else {
1160             // We are trying to scroll right.  Make sure we are not pinned to the right
1161             limitDelta.setWidth(m_scrollableArea.totalContentsSize().width() - (m_scrollableArea.visibleContentRect().maxX() + m_scrollableArea.scrollOrigin().x()));
1162         }
1163     }
1164     
1165     if ((direction.width() || direction.height()) && (limitDelta.width() < 1 && limitDelta.height() < 1))
1166         return true;
1167     return false;
1168 }
1169
1170 // FIXME: We should find a way to share some of the code from newGestureIsStarting(), isAlreadyPinnedInDirectionOfGesture(),
1171 // allowsVerticalStretching(), and allowsHorizontalStretching() with the implementation in ScrollingTreeFrameScrollingNodeMac.
1172 static bool newGestureIsStarting(const PlatformWheelEvent& wheelEvent)
1173 {
1174     return wheelEvent.phase() == PlatformWheelEventPhaseMayBegin || wheelEvent.phase() == PlatformWheelEventPhaseBegan;
1175 }
1176
1177 bool ScrollAnimatorMac::isAlreadyPinnedInDirectionOfGesture(const PlatformWheelEvent& wheelEvent, ScrollEventAxis axis)
1178 {
1179     switch (axis) {
1180     case ScrollEventAxis::Vertical:
1181         return (wheelEvent.deltaY() > 0 && m_scrollableArea.scrolledToTop()) || (wheelEvent.deltaY() < 0 && m_scrollableArea.scrolledToBottom());
1182     case ScrollEventAxis::Horizontal:
1183         return (wheelEvent.deltaX() > 0 && m_scrollableArea.scrolledToLeft()) || (wheelEvent.deltaX() < 0 && m_scrollableArea.scrolledToRight());
1184     }
1185
1186     ASSERT_NOT_REACHED();
1187     return false;
1188 }
1189
1190 #if ENABLE(CSS_SCROLL_SNAP)
1191 static bool gestureShouldBeginSnap(const PlatformWheelEvent& wheelEvent, const Vector<LayoutUnit>* snapOffsets)
1192 {
1193     if (!snapOffsets)
1194         return false;
1195     
1196     if (wheelEvent.phase() != PlatformWheelEventPhaseEnded && !wheelEvent.isEndGesture())
1197         return false;
1198
1199     return true;
1200 }
1201 #endif
1202
1203 bool ScrollAnimatorMac::allowsVerticalStretching(const PlatformWheelEvent& wheelEvent)
1204 {
1205     switch (m_scrollableArea.verticalScrollElasticity()) {
1206     case ScrollElasticityAutomatic: {
1207         Scrollbar* hScroller = m_scrollableArea.horizontalScrollbar();
1208         Scrollbar* vScroller = m_scrollableArea.verticalScrollbar();
1209         bool scrollbarsAllowStretching = ((vScroller && vScroller->enabled()) || (!hScroller || !hScroller->enabled()));
1210         bool eventPreventsStretching = m_scrollableArea.hasScrollableOrRubberbandableAncestor() && newGestureIsStarting(wheelEvent) && isAlreadyPinnedInDirectionOfGesture(wheelEvent, ScrollEventAxis::Vertical);
1211 #if ENABLE(CSS_SCROLL_SNAP)
1212         if (!eventPreventsStretching)
1213             eventPreventsStretching = gestureShouldBeginSnap(wheelEvent, m_scrollableArea.verticalSnapOffsets());
1214 #endif
1215         return scrollbarsAllowStretching && !eventPreventsStretching;
1216     }
1217     case ScrollElasticityNone:
1218         return false;
1219     case ScrollElasticityAllowed:
1220         return true;
1221     }
1222
1223     ASSERT_NOT_REACHED();
1224     return false;
1225 }
1226
1227 bool ScrollAnimatorMac::allowsHorizontalStretching(const PlatformWheelEvent& wheelEvent)
1228 {
1229     switch (m_scrollableArea.horizontalScrollElasticity()) {
1230     case ScrollElasticityAutomatic: {
1231         Scrollbar* hScroller = m_scrollableArea.horizontalScrollbar();
1232         Scrollbar* vScroller = m_scrollableArea.verticalScrollbar();
1233         bool scrollbarsAllowStretching = ((hScroller && hScroller->enabled()) || (!vScroller || !vScroller->enabled()));
1234         bool eventPreventsStretching = m_scrollableArea.hasScrollableOrRubberbandableAncestor() && newGestureIsStarting(wheelEvent) && isAlreadyPinnedInDirectionOfGesture(wheelEvent, ScrollEventAxis::Horizontal);
1235 #if ENABLE(CSS_SCROLL_SNAP)
1236         if (!eventPreventsStretching)
1237             eventPreventsStretching = gestureShouldBeginSnap(wheelEvent, m_scrollableArea.horizontalSnapOffsets());
1238 #endif
1239         return scrollbarsAllowStretching && !eventPreventsStretching;
1240     }
1241     case ScrollElasticityNone:
1242         return false;
1243     case ScrollElasticityAllowed:
1244         return true;
1245     }
1246
1247     ASSERT_NOT_REACHED();
1248     return false;
1249 }
1250
1251 IntSize ScrollAnimatorMac::stretchAmount()
1252 {
1253     return m_scrollableArea.overhangAmount();
1254 }
1255
1256 bool ScrollAnimatorMac::canScrollHorizontally()
1257 {
1258     Scrollbar* scrollbar = m_scrollableArea.horizontalScrollbar();
1259     if (!scrollbar)
1260         return false;
1261     return scrollbar->enabled();
1262 }
1263
1264 bool ScrollAnimatorMac::canScrollVertically()
1265 {
1266     Scrollbar* scrollbar = m_scrollableArea.verticalScrollbar();
1267     if (!scrollbar)
1268         return false;
1269     return scrollbar->enabled();
1270 }
1271
1272 bool ScrollAnimatorMac::shouldRubberBandInDirection(ScrollDirection)
1273 {
1274     return false;
1275 }
1276
1277 IntPoint ScrollAnimatorMac::absoluteScrollPosition()
1278 {
1279     return m_scrollableArea.visibleContentRect().location() + m_scrollableArea.scrollOrigin();
1280 }
1281
1282 void ScrollAnimatorMac::immediateScrollByWithoutContentEdgeConstraints(const FloatSize& delta)
1283 {
1284     m_scrollableArea.setConstrainsScrollingToContentEdge(false);
1285     immediateScrollBy(delta);
1286     m_scrollableArea.setConstrainsScrollingToContentEdge(true);
1287 }
1288
1289 void ScrollAnimatorMac::immediateScrollBy(const FloatSize& delta)
1290 {
1291     FloatPoint newPos = adjustScrollPositionIfNecessary(FloatPoint(m_currentPosX, m_currentPosY) + delta);
1292     if (newPos.x() == m_currentPosX && newPos.y() == m_currentPosY)
1293         return;
1294
1295     FloatSize adjustedDelta = FloatSize(newPos.x() - m_currentPosX, newPos.y() - m_currentPosY);
1296
1297     m_currentPosX = newPos.x();
1298     m_currentPosY = newPos.y();
1299     notifyPositionChanged(adjustedDelta);
1300     updateActiveScrollSnapIndexForOffset();
1301 }
1302 #endif
1303
1304 void ScrollAnimatorMac::updateScrollerStyle()
1305 {
1306     if ([m_scrollbarPainterController overlayScrollerStateIsLocked]) {
1307         m_needsScrollerStyleUpdate = true;
1308         return;
1309     }
1310
1311     ScrollbarThemeMac* macTheme = macScrollbarTheme();
1312     if (!macTheme) {
1313         m_needsScrollerStyleUpdate = false;
1314         return;
1315     }
1316     
1317     macTheme->usesOverlayScrollbarsChanged();
1318
1319     NSScrollerStyle newStyle = [m_scrollbarPainterController scrollerStyle];
1320
1321     if (Scrollbar* verticalScrollbar = scrollableArea().verticalScrollbar()) {
1322         verticalScrollbar->invalidate();
1323
1324         ScrollbarPainter oldVerticalPainter = [m_scrollbarPainterController verticalScrollerImp];
1325         ScrollbarPainter newVerticalPainter = [NSClassFromString(@"NSScrollerImp") scrollerImpWithStyle:newStyle 
1326                                                                                     controlSize:(NSControlSize)verticalScrollbar->controlSize() 
1327                                                                                     horizontal:NO 
1328                                                                                     replacingScrollerImp:oldVerticalPainter];
1329         [m_scrollbarPainterController setVerticalScrollerImp:newVerticalPainter];
1330         macTheme->setNewPainterForScrollbar(*verticalScrollbar, newVerticalPainter);
1331
1332         // The different scrollbar styles have different thicknesses, so we must re-set the 
1333         // frameRect to the new thickness, and the re-layout below will ensure the position
1334         // and length are properly updated.
1335         int thickness = macTheme->scrollbarThickness(verticalScrollbar->controlSize());
1336         verticalScrollbar->setFrameRect(IntRect(0, 0, thickness, thickness));
1337     }
1338
1339     if (Scrollbar* horizontalScrollbar = scrollableArea().horizontalScrollbar()) {
1340         horizontalScrollbar->invalidate();
1341
1342         ScrollbarPainter oldHorizontalPainter = [m_scrollbarPainterController horizontalScrollerImp];
1343         ScrollbarPainter newHorizontalPainter = [NSClassFromString(@"NSScrollerImp") scrollerImpWithStyle:newStyle 
1344                                                                                     controlSize:(NSControlSize)horizontalScrollbar->controlSize() 
1345                                                                                     horizontal:YES 
1346                                                                                     replacingScrollerImp:oldHorizontalPainter];
1347         [m_scrollbarPainterController setHorizontalScrollerImp:newHorizontalPainter];
1348         macTheme->setNewPainterForScrollbar(*horizontalScrollbar, newHorizontalPainter);
1349
1350         // The different scrollbar styles have different thicknesses, so we must re-set the 
1351         // frameRect to the new thickness, and the re-layout below will ensure the position
1352         // and length are properly updated.
1353         int thickness = macTheme->scrollbarThickness(horizontalScrollbar->controlSize());
1354         horizontalScrollbar->setFrameRect(IntRect(0, 0, thickness, thickness));
1355     }
1356
1357     // If m_needsScrollerStyleUpdate is true, then the page is restoring from the page cache, and 
1358     // a relayout will happen on its own. Otherwise, we must initiate a re-layout ourselves.
1359     scrollableArea().scrollbarStyleChanged(newStyle == NSScrollerStyleOverlay ? ScrollbarStyle::Overlay : ScrollbarStyle::AlwaysVisible, !m_needsScrollerStyleUpdate);
1360
1361     m_needsScrollerStyleUpdate = false;
1362 }
1363
1364 void ScrollAnimatorMac::startScrollbarPaintTimer()
1365 {
1366     m_initialScrollbarPaintTimer.startOneShot(0.1);
1367 }
1368
1369 bool ScrollAnimatorMac::scrollbarPaintTimerIsActive() const
1370 {
1371     return m_initialScrollbarPaintTimer.isActive();
1372 }
1373
1374 void ScrollAnimatorMac::stopScrollbarPaintTimer()
1375 {
1376     m_initialScrollbarPaintTimer.stop();
1377 }
1378
1379 void ScrollAnimatorMac::initialScrollbarPaintTimerFired()
1380 {
1381     // To force the scrollbars to flash, we have to call hide first. Otherwise, the ScrollbarPainterController
1382     // might think that the scrollbars are already showing and bail early.
1383     [m_scrollbarPainterController hideOverlayScrollers];
1384     [m_scrollbarPainterController flashScrollers];
1385 }
1386
1387 void ScrollAnimatorMac::sendContentAreaScrolledSoon(const FloatSize& delta)
1388 {
1389     m_contentAreaScrolledTimerScrollDelta = delta;
1390
1391     if (!m_sendContentAreaScrolledTimer.isActive())
1392         m_sendContentAreaScrolledTimer.startOneShot(0);
1393
1394     if (m_wheelEventTestTrigger)
1395         m_wheelEventTestTrigger->deferTestsForReason(reinterpret_cast<WheelEventTestTrigger::ScrollableAreaIdentifier>(this), WheelEventTestTrigger::ContentScrollInProgress);
1396 }
1397
1398 void ScrollAnimatorMac::sendContentAreaScrolled(const FloatSize& delta)
1399 {
1400     if (supportsContentAreaScrolledInDirection())
1401         [m_scrollbarPainterController contentAreaScrolledInDirection:NSMakePoint(delta.width(), delta.height())];
1402     else
1403         [m_scrollbarPainterController contentAreaScrolled];
1404 }
1405
1406 void ScrollAnimatorMac::sendContentAreaScrolledTimerFired()
1407 {
1408     sendContentAreaScrolled(m_contentAreaScrolledTimerScrollDelta);
1409     m_contentAreaScrolledTimerScrollDelta = FloatSize();
1410
1411     if (m_wheelEventTestTrigger)
1412         m_wheelEventTestTrigger->removeTestDeferralForReason(reinterpret_cast<WheelEventTestTrigger::ScrollableAreaIdentifier>(this), WheelEventTestTrigger::ContentScrollInProgress);
1413 }
1414
1415 void ScrollAnimatorMac::setVisibleScrollerThumbRect(const IntRect& scrollerThumb)
1416 {
1417     IntRect rectInViewCoordinates = scrollerThumb;
1418     if (Scrollbar* verticalScrollbar = m_scrollableArea.verticalScrollbar())
1419         rectInViewCoordinates = verticalScrollbar->convertToContainingView(scrollerThumb);
1420
1421     if (rectInViewCoordinates == m_visibleScrollerThumbRect)
1422         return;
1423
1424     m_scrollableArea.setVisibleScrollerThumbRect(rectInViewCoordinates);
1425     m_visibleScrollerThumbRect = rectInViewCoordinates;
1426 }
1427
1428 } // namespace WebCore
1429
1430 #endif // ENABLE(SMOOTH_SCROLLING)