Expand test infrastructure to support scrolling tests
[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 #if ENABLE(CSS_SCROLL_SNAP) || ENABLE(RUBBER_BANDING)
894     if (m_wheelEventTestTrigger)
895         m_wheelEventTestTrigger->deferTestsForReason(reinterpret_cast<WheelEventTestTrigger::ScrollableAreaIdentifier>(this), WheelEventTestTrigger::ContentScrollInProgress);
896 #endif
897 }
898
899 void ScrollAnimatorMac::didEndScrollGesture() const
900 {
901     if ([m_scrollbarPainterController overlayScrollerStateIsLocked])
902         return;
903
904     [m_scrollbarPainterController endScrollGesture];
905
906 #if ENABLE(CSS_SCROLL_SNAP) || ENABLE(RUBBER_BANDING)
907     if (m_wheelEventTestTrigger)
908         m_wheelEventTestTrigger->removeTestDeferralForReason(reinterpret_cast<WheelEventTestTrigger::ScrollableAreaIdentifier>(this), WheelEventTestTrigger::ContentScrollInProgress);
909 #endif
910 }
911
912 void ScrollAnimatorMac::mayBeginScrollGesture() const
913 {
914     if ([m_scrollbarPainterController overlayScrollerStateIsLocked])
915         return;
916
917     [m_scrollbarPainterController beginScrollGesture];
918     [m_scrollbarPainterController contentAreaScrolled];
919 }
920
921 void ScrollAnimatorMac::lockOverlayScrollbarStateToHidden(bool shouldLockState)
922 {
923     if (shouldLockState)
924         [m_scrollbarPainterController lockOverlayScrollerState:ScrollbarOverlayStateHidden];
925     else {
926         [m_scrollbarPainterController unlockOverlayScrollerState];
927
928         // We never update scroller style for PainterControllers that are locked. If we have a pending
929         // need to update the style, do it once we've unlocked the scroller state.
930         if (m_needsScrollerStyleUpdate)
931             updateScrollerStyle();
932     }
933 }
934
935 bool ScrollAnimatorMac::scrollbarsCanBeActive() const
936 {
937     return ![m_scrollbarPainterController overlayScrollerStateIsLocked];
938 }
939
940 void ScrollAnimatorMac::didAddVerticalScrollbar(Scrollbar* scrollbar)
941 {
942     ScrollbarPainter painter = scrollbarPainterForScrollbar(scrollbar);
943     if (!painter)
944         return;
945
946     ASSERT(!m_verticalScrollbarPainterDelegate);
947     m_verticalScrollbarPainterDelegate = adoptNS([[WebScrollbarPainterDelegate alloc] initWithScrollbar:scrollbar]);
948
949     [painter setDelegate:(id)m_verticalScrollbarPainterDelegate.get()];
950     if (GraphicsLayer* layer = scrollbar->scrollableArea().layerForVerticalScrollbar())
951         [painter setLayer:layer->platformLayer()];
952
953     [m_scrollbarPainterController setVerticalScrollerImp:painter];
954     if (scrollableArea().inLiveResize())
955         [painter setKnobAlpha:1];
956 }
957
958 void ScrollAnimatorMac::willRemoveVerticalScrollbar(Scrollbar* scrollbar)
959 {
960     ScrollbarPainter painter = scrollbarPainterForScrollbar(scrollbar);
961     if (!painter)
962         return;
963
964     ASSERT(m_verticalScrollbarPainterDelegate);
965     [m_verticalScrollbarPainterDelegate invalidate];
966     m_verticalScrollbarPainterDelegate = nullptr;
967
968     [painter setDelegate:nil];
969     [m_scrollbarPainterController setVerticalScrollerImp:nil];
970 }
971
972 void ScrollAnimatorMac::didAddHorizontalScrollbar(Scrollbar* scrollbar)
973 {
974     ScrollbarPainter painter = scrollbarPainterForScrollbar(scrollbar);
975     if (!painter)
976         return;
977
978     ASSERT(!m_horizontalScrollbarPainterDelegate);
979     m_horizontalScrollbarPainterDelegate = adoptNS([[WebScrollbarPainterDelegate alloc] initWithScrollbar:scrollbar]);
980
981     [painter setDelegate:(id)m_horizontalScrollbarPainterDelegate.get()];
982     if (GraphicsLayer* layer = scrollbar->scrollableArea().layerForHorizontalScrollbar())
983         [painter setLayer:layer->platformLayer()];
984
985     [m_scrollbarPainterController setHorizontalScrollerImp:painter];
986     if (scrollableArea().inLiveResize())
987         [painter setKnobAlpha:1];
988 }
989
990 void ScrollAnimatorMac::willRemoveHorizontalScrollbar(Scrollbar* scrollbar)
991 {
992     ScrollbarPainter painter = scrollbarPainterForScrollbar(scrollbar);
993     if (!painter)
994         return;
995
996     ASSERT(m_horizontalScrollbarPainterDelegate);
997     [m_horizontalScrollbarPainterDelegate invalidate];
998     m_horizontalScrollbarPainterDelegate = nullptr;
999
1000     [painter setDelegate:nil];
1001     [m_scrollbarPainterController setHorizontalScrollerImp:nil];
1002 }
1003
1004 void ScrollAnimatorMac::verticalScrollbarLayerDidChange()
1005 {
1006     GraphicsLayer* layer = m_scrollableArea.layerForVerticalScrollbar();
1007     Scrollbar* scrollbar = m_scrollableArea.verticalScrollbar();
1008     if (!scrollbar)
1009         return;
1010
1011     ScrollbarPainter painter = scrollbarPainterForScrollbar(scrollbar);
1012     if (!painter)
1013         return;
1014
1015     [painter setLayer:layer ? layer->platformLayer() : nil];
1016 }
1017
1018 void ScrollAnimatorMac::horizontalScrollbarLayerDidChange()
1019 {
1020     GraphicsLayer* layer = m_scrollableArea.layerForHorizontalScrollbar();
1021     Scrollbar* scrollbar = m_scrollableArea.horizontalScrollbar();
1022     if (!scrollbar)
1023         return;
1024
1025     ScrollbarPainter painter = scrollbarPainterForScrollbar(scrollbar);
1026     if (!painter)
1027         return;
1028
1029     [painter setLayer:layer ? layer->platformLayer() : nil];
1030 }
1031
1032 bool ScrollAnimatorMac::shouldScrollbarParticipateInHitTesting(Scrollbar* scrollbar)
1033 {
1034     // Non-overlay scrollbars should always participate in hit testing.
1035     if (recommendedScrollerStyle() != NSScrollerStyleOverlay)
1036         return true;
1037
1038     if (scrollbar->isAlphaLocked())
1039         return true;
1040
1041     // Overlay scrollbars should participate in hit testing whenever they are at all visible.
1042     ScrollbarPainter painter = scrollbarPainterForScrollbar(scrollbar);
1043     if (!painter)
1044         return false;
1045     return [painter knobAlpha] > 0;
1046 }
1047
1048 void ScrollAnimatorMac::notifyContentAreaScrolled(const FloatSize& delta)
1049 {
1050     // This function is called when a page is going into the page cache, but the page
1051     // isn't really scrolling in that case. We should only pass the message on to the
1052     // ScrollbarPainterController when we're really scrolling on an active page.
1053     if ([m_scrollbarPainterController overlayScrollerStateIsLocked])
1054         return;
1055
1056     if (m_scrollableArea.isHandlingWheelEvent())
1057         sendContentAreaScrolled(delta);
1058     else
1059         sendContentAreaScrolledSoon(delta);
1060 }
1061
1062 void ScrollAnimatorMac::cancelAnimations()
1063 {
1064     m_haveScrolledSincePageLoad = false;
1065
1066     if (scrollbarPaintTimerIsActive())
1067         stopScrollbarPaintTimer();
1068     [m_horizontalScrollbarPainterDelegate cancelAnimations];
1069     [m_verticalScrollbarPainterDelegate cancelAnimations];
1070 }
1071
1072 void ScrollAnimatorMac::handleWheelEventPhase(PlatformWheelEventPhase phase)
1073 {
1074     // This may not have been set to true yet if the wheel event was handled by the ScrollingTree,
1075     // So set it to true here.
1076     m_haveScrolledSincePageLoad = true;
1077
1078     if (phase == PlatformWheelEventPhaseBegan)
1079         didBeginScrollGesture();
1080     else if (phase == PlatformWheelEventPhaseEnded || phase == PlatformWheelEventPhaseCancelled)
1081         didEndScrollGesture();
1082     else if (phase == PlatformWheelEventPhaseMayBegin)
1083         mayBeginScrollGesture();
1084 }
1085
1086 #if ENABLE(RUBBER_BANDING)
1087 bool ScrollAnimatorMac::handleWheelEvent(const PlatformWheelEvent& wheelEvent)
1088 {
1089     m_haveScrolledSincePageLoad = true;
1090
1091     if (!wheelEvent.hasPreciseScrollingDeltas() || !rubberBandingEnabledForSystem())
1092         return ScrollAnimator::handleWheelEvent(wheelEvent);
1093
1094     // FIXME: This is somewhat roundabout hack to allow forwarding wheel events
1095     // up to the parent scrollable area. It takes advantage of the fact that
1096     // the base class implementation of handleWheelEvent will not accept the
1097     // wheel event if there is nowhere to scroll.
1098     if (fabsf(wheelEvent.deltaY()) >= fabsf(wheelEvent.deltaX())) {
1099         if (!allowsVerticalStretching(wheelEvent))
1100             return ScrollAnimator::handleWheelEvent(wheelEvent);
1101     } else {
1102         if (!allowsHorizontalStretching(wheelEvent))
1103             return ScrollAnimator::handleWheelEvent(wheelEvent);
1104     }
1105
1106     bool didHandleEvent = m_scrollController.handleWheelEvent(wheelEvent);
1107
1108     if (didHandleEvent)
1109         handleWheelEventPhase(wheelEvent.phase());
1110
1111     return didHandleEvent;
1112 }
1113
1114 bool ScrollAnimatorMac::pinnedInDirection(const FloatSize& direction)
1115 {
1116     FloatSize limitDelta;
1117     if (fabsf(direction.height()) >= fabsf(direction.width())) {
1118         if (direction.height() < 0) {
1119             // We are trying to scroll up.  Make sure we are not pinned to the top
1120             limitDelta.setHeight(m_scrollableArea.visibleContentRect().y() + m_scrollableArea.scrollOrigin().y());
1121         } else {
1122             // We are trying to scroll down.  Make sure we are not pinned to the bottom
1123             limitDelta.setHeight(m_scrollableArea.totalContentsSize().height() - (m_scrollableArea.visibleContentRect().maxY() + m_scrollableArea.scrollOrigin().y()));
1124         }
1125     } else if (direction.width()) {
1126         if (direction.width() < 0) {
1127             // We are trying to scroll left.  Make sure we are not pinned to the left
1128             limitDelta.setWidth(m_scrollableArea.visibleContentRect().x() + m_scrollableArea.scrollOrigin().x());
1129         } else {
1130             // We are trying to scroll right.  Make sure we are not pinned to the right
1131             limitDelta.setWidth(m_scrollableArea.totalContentsSize().width() - (m_scrollableArea.visibleContentRect().maxX() + m_scrollableArea.scrollOrigin().x()));
1132         }
1133     }
1134     
1135     if ((direction.width() || direction.height()) && (limitDelta.width() < 1 && limitDelta.height() < 1))
1136         return true;
1137     return false;
1138 }
1139
1140 // FIXME: We should find a way to share some of the code from newGestureIsStarting(), isAlreadyPinnedInDirectionOfGesture(),
1141 // allowsVerticalStretching(), and allowsHorizontalStretching() with the implementation in ScrollingTreeFrameScrollingNodeMac.
1142 static bool newGestureIsStarting(const PlatformWheelEvent& wheelEvent)
1143 {
1144     return wheelEvent.phase() == PlatformWheelEventPhaseMayBegin || wheelEvent.phase() == PlatformWheelEventPhaseBegan;
1145 }
1146
1147 bool ScrollAnimatorMac::isAlreadyPinnedInDirectionOfGesture(const PlatformWheelEvent& wheelEvent, ScrollEventAxis axis)
1148 {
1149     switch (axis) {
1150     case ScrollEventAxis::Vertical:
1151         return (wheelEvent.deltaY() > 0 && m_scrollableArea.scrolledToTop()) || (wheelEvent.deltaY() < 0 && m_scrollableArea.scrolledToBottom());
1152     case ScrollEventAxis::Horizontal:
1153         return (wheelEvent.deltaX() > 0 && m_scrollableArea.scrolledToLeft()) || (wheelEvent.deltaX() < 0 && m_scrollableArea.scrolledToRight());
1154     }
1155
1156     ASSERT_NOT_REACHED();
1157     return false;
1158 }
1159
1160 #if ENABLE(CSS_SCROLL_SNAP)
1161 static bool gestureShouldBeginSnap(const PlatformWheelEvent& wheelEvent, const Vector<LayoutUnit>* snapOffsets)
1162 {
1163     if (!snapOffsets)
1164         return false;
1165     
1166     if (wheelEvent.phase() != PlatformWheelEventPhaseEnded && !wheelEvent.isEndGesture())
1167         return false;
1168
1169     return true;
1170 }
1171 #endif
1172
1173 bool ScrollAnimatorMac::allowsVerticalStretching(const PlatformWheelEvent& wheelEvent)
1174 {
1175     switch (m_scrollableArea.verticalScrollElasticity()) {
1176     case ScrollElasticityAutomatic: {
1177         Scrollbar* hScroller = m_scrollableArea.horizontalScrollbar();
1178         Scrollbar* vScroller = m_scrollableArea.verticalScrollbar();
1179         bool scrollbarsAllowStretching = ((vScroller && vScroller->enabled()) || (!hScroller || !hScroller->enabled()));
1180         bool eventPreventsStretching = m_scrollableArea.hasScrollableOrRubberbandableAncestor() && newGestureIsStarting(wheelEvent) && isAlreadyPinnedInDirectionOfGesture(wheelEvent, ScrollEventAxis::Vertical);
1181 #if ENABLE(CSS_SCROLL_SNAP)
1182         if (!eventPreventsStretching)
1183             eventPreventsStretching = gestureShouldBeginSnap(wheelEvent, m_scrollableArea.verticalSnapOffsets());
1184 #endif
1185         return scrollbarsAllowStretching && !eventPreventsStretching;
1186     }
1187     case ScrollElasticityNone:
1188         return false;
1189     case ScrollElasticityAllowed:
1190         return true;
1191     }
1192
1193     ASSERT_NOT_REACHED();
1194     return false;
1195 }
1196
1197 bool ScrollAnimatorMac::allowsHorizontalStretching(const PlatformWheelEvent& wheelEvent)
1198 {
1199     switch (m_scrollableArea.horizontalScrollElasticity()) {
1200     case ScrollElasticityAutomatic: {
1201         Scrollbar* hScroller = m_scrollableArea.horizontalScrollbar();
1202         Scrollbar* vScroller = m_scrollableArea.verticalScrollbar();
1203         bool scrollbarsAllowStretching = ((hScroller && hScroller->enabled()) || (!vScroller || !vScroller->enabled()));
1204         bool eventPreventsStretching = m_scrollableArea.hasScrollableOrRubberbandableAncestor() && newGestureIsStarting(wheelEvent) && isAlreadyPinnedInDirectionOfGesture(wheelEvent, ScrollEventAxis::Horizontal);
1205 #if ENABLE(CSS_SCROLL_SNAP)
1206         if (!eventPreventsStretching)
1207             eventPreventsStretching = gestureShouldBeginSnap(wheelEvent, m_scrollableArea.horizontalSnapOffsets());
1208 #endif
1209         return scrollbarsAllowStretching && !eventPreventsStretching;
1210     }
1211     case ScrollElasticityNone:
1212         return false;
1213     case ScrollElasticityAllowed:
1214         return true;
1215     }
1216
1217     ASSERT_NOT_REACHED();
1218     return false;
1219 }
1220
1221 IntSize ScrollAnimatorMac::stretchAmount()
1222 {
1223     return m_scrollableArea.overhangAmount();
1224 }
1225
1226 bool ScrollAnimatorMac::canScrollHorizontally()
1227 {
1228     Scrollbar* scrollbar = m_scrollableArea.horizontalScrollbar();
1229     if (!scrollbar)
1230         return false;
1231     return scrollbar->enabled();
1232 }
1233
1234 bool ScrollAnimatorMac::canScrollVertically()
1235 {
1236     Scrollbar* scrollbar = m_scrollableArea.verticalScrollbar();
1237     if (!scrollbar)
1238         return false;
1239     return scrollbar->enabled();
1240 }
1241
1242 bool ScrollAnimatorMac::shouldRubberBandInDirection(ScrollDirection)
1243 {
1244     return false;
1245 }
1246
1247 IntPoint ScrollAnimatorMac::absoluteScrollPosition()
1248 {
1249     return m_scrollableArea.visibleContentRect().location() + m_scrollableArea.scrollOrigin();
1250 }
1251
1252 void ScrollAnimatorMac::immediateScrollByWithoutContentEdgeConstraints(const FloatSize& delta)
1253 {
1254     m_scrollableArea.setConstrainsScrollingToContentEdge(false);
1255     immediateScrollBy(delta);
1256     m_scrollableArea.setConstrainsScrollingToContentEdge(true);
1257 }
1258
1259 void ScrollAnimatorMac::immediateScrollBy(const FloatSize& delta)
1260 {
1261     FloatPoint newPos = adjustScrollPositionIfNecessary(FloatPoint(m_currentPosX, m_currentPosY) + delta);
1262     if (newPos.x() == m_currentPosX && newPos.y() == m_currentPosY)
1263         return;
1264
1265     FloatSize adjustedDelta = FloatSize(newPos.x() - m_currentPosX, newPos.y() - m_currentPosY);
1266
1267     m_currentPosX = newPos.x();
1268     m_currentPosY = newPos.y();
1269     notifyPositionChanged(adjustedDelta);
1270 }
1271 #endif
1272
1273 void ScrollAnimatorMac::updateScrollerStyle()
1274 {
1275     if ([m_scrollbarPainterController overlayScrollerStateIsLocked]) {
1276         m_needsScrollerStyleUpdate = true;
1277         return;
1278     }
1279
1280     ScrollbarThemeMac* macTheme = macScrollbarTheme();
1281     if (!macTheme) {
1282         m_needsScrollerStyleUpdate = false;
1283         return;
1284     }
1285     
1286     macTheme->usesOverlayScrollbarsChanged();
1287
1288     NSScrollerStyle newStyle = [m_scrollbarPainterController scrollerStyle];
1289
1290     if (Scrollbar* verticalScrollbar = scrollableArea().verticalScrollbar()) {
1291         verticalScrollbar->invalidate();
1292
1293         ScrollbarPainter oldVerticalPainter = [m_scrollbarPainterController verticalScrollerImp];
1294         ScrollbarPainter newVerticalPainter = [NSClassFromString(@"NSScrollerImp") scrollerImpWithStyle:newStyle 
1295                                                                                     controlSize:(NSControlSize)verticalScrollbar->controlSize() 
1296                                                                                     horizontal:NO 
1297                                                                                     replacingScrollerImp:oldVerticalPainter];
1298         [m_scrollbarPainterController setVerticalScrollerImp:newVerticalPainter];
1299         macTheme->setNewPainterForScrollbar(verticalScrollbar, newVerticalPainter);
1300
1301         // The different scrollbar styles have different thicknesses, so we must re-set the 
1302         // frameRect to the new thickness, and the re-layout below will ensure the position
1303         // and length are properly updated.
1304         int thickness = macTheme->scrollbarThickness(verticalScrollbar->controlSize());
1305         verticalScrollbar->setFrameRect(IntRect(0, 0, thickness, thickness));
1306     }
1307
1308     if (Scrollbar* horizontalScrollbar = scrollableArea().horizontalScrollbar()) {
1309         horizontalScrollbar->invalidate();
1310
1311         ScrollbarPainter oldHorizontalPainter = [m_scrollbarPainterController horizontalScrollerImp];
1312         ScrollbarPainter newHorizontalPainter = [NSClassFromString(@"NSScrollerImp") scrollerImpWithStyle:newStyle 
1313                                                                                     controlSize:(NSControlSize)horizontalScrollbar->controlSize() 
1314                                                                                     horizontal:YES 
1315                                                                                     replacingScrollerImp:oldHorizontalPainter];
1316         [m_scrollbarPainterController setHorizontalScrollerImp:newHorizontalPainter];
1317         macTheme->setNewPainterForScrollbar(horizontalScrollbar, newHorizontalPainter);
1318
1319         // The different scrollbar styles have different thicknesses, so we must re-set the 
1320         // frameRect to the new thickness, and the re-layout below will ensure the position
1321         // and length are properly updated.
1322         int thickness = macTheme->scrollbarThickness(horizontalScrollbar->controlSize());
1323         horizontalScrollbar->setFrameRect(IntRect(0, 0, thickness, thickness));
1324     }
1325
1326     // If m_needsScrollerStyleUpdate is true, then the page is restoring from the page cache, and 
1327     // a relayout will happen on its own. Otherwise, we must initiate a re-layout ourselves.
1328     scrollableArea().scrollbarStyleChanged(newStyle == NSScrollerStyleOverlay ? ScrollbarStyle::Overlay : ScrollbarStyle::AlwaysVisible, !m_needsScrollerStyleUpdate);
1329
1330     m_needsScrollerStyleUpdate = false;
1331 }
1332
1333 void ScrollAnimatorMac::startScrollbarPaintTimer()
1334 {
1335     m_initialScrollbarPaintTimer.startOneShot(0.1);
1336 }
1337
1338 bool ScrollAnimatorMac::scrollbarPaintTimerIsActive() const
1339 {
1340     return m_initialScrollbarPaintTimer.isActive();
1341 }
1342
1343 void ScrollAnimatorMac::stopScrollbarPaintTimer()
1344 {
1345     m_initialScrollbarPaintTimer.stop();
1346 }
1347
1348 void ScrollAnimatorMac::initialScrollbarPaintTimerFired()
1349 {
1350     // To force the scrollbars to flash, we have to call hide first. Otherwise, the ScrollbarPainterController
1351     // might think that the scrollbars are already showing and bail early.
1352     [m_scrollbarPainterController hideOverlayScrollers];
1353     [m_scrollbarPainterController flashScrollers];
1354 }
1355
1356 void ScrollAnimatorMac::sendContentAreaScrolledSoon(const FloatSize& delta)
1357 {
1358     m_contentAreaScrolledTimerScrollDelta = delta;
1359
1360     if (!m_sendContentAreaScrolledTimer.isActive())
1361         m_sendContentAreaScrolledTimer.startOneShot(0);
1362
1363     if (m_wheelEventTestTrigger)
1364         m_wheelEventTestTrigger->deferTestsForReason(reinterpret_cast<WheelEventTestTrigger::ScrollableAreaIdentifier>(this), WheelEventTestTrigger::ContentScrollInProgress);
1365 }
1366
1367 void ScrollAnimatorMac::sendContentAreaScrolled(const FloatSize& delta)
1368 {
1369     if (supportsContentAreaScrolledInDirection())
1370         [m_scrollbarPainterController contentAreaScrolledInDirection:NSMakePoint(delta.width(), delta.height())];
1371     else
1372         [m_scrollbarPainterController contentAreaScrolled];
1373 }
1374
1375 void ScrollAnimatorMac::sendContentAreaScrolledTimerFired()
1376 {
1377     sendContentAreaScrolled(m_contentAreaScrolledTimerScrollDelta);
1378     m_contentAreaScrolledTimerScrollDelta = FloatSize();
1379
1380     if (m_wheelEventTestTrigger)
1381         m_wheelEventTestTrigger->removeTestDeferralForReason(reinterpret_cast<WheelEventTestTrigger::ScrollableAreaIdentifier>(this), WheelEventTestTrigger::ContentScrollInProgress);
1382 }
1383
1384 void ScrollAnimatorMac::setVisibleScrollerThumbRect(const IntRect& scrollerThumb)
1385 {
1386     IntRect rectInViewCoordinates = scrollerThumb;
1387     if (Scrollbar* verticalScrollbar = m_scrollableArea.verticalScrollbar())
1388         rectInViewCoordinates = verticalScrollbar->convertToContainingView(scrollerThumb);
1389
1390     if (rectInViewCoordinates == m_visibleScrollerThumbRect)
1391         return;
1392
1393     m_scrollableArea.setVisibleScrollerThumbRect(rectInViewCoordinates);
1394     m_visibleScrollerThumbRect = rectInViewCoordinates;
1395 }
1396
1397 } // namespace WebCore
1398
1399 #endif // ENABLE(SMOOTH_SCROLLING)