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