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