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