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