blob: edb3195e8739f2ff5065312f780f5f744acf98ca [file] [log] [blame]
/*
* Copyright (C) 2010, 2011 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "platform/mac/ScrollAnimatorMac.h"
#include "platform/PlatformGestureEvent.h"
#include "platform/PlatformWheelEvent.h"
#include "platform/Timer.h"
#include "platform/animation/TimingFunction.h"
#include "platform/geometry/FloatRect.h"
#include "platform/geometry/IntRect.h"
#include "platform/mac/BlockExceptions.h"
#include "platform/mac/NSScrollerImpDetails.h"
#include "platform/scroll/ScrollableArea.h"
#include "platform/scroll/ScrollbarTheme.h"
#include "platform/scroll/ScrollbarThemeMac.h"
#include "public/platform/Platform.h"
#include "public/platform/WebScheduler.h"
#include "wtf/MathExtras.h"
#include "wtf/PtrUtil.h"
#include <memory>
using namespace blink;
static bool supportsUIStateTransitionProgress() {
// FIXME: This is temporary until all platforms that support ScrollbarPainter
// support this part of the API.
static bool globalSupportsUIStateTransitionProgress =
[NSClassFromString(@"NSScrollerImp")
instancesRespondToSelector:@selector(mouseEnteredScroller)];
return globalSupportsUIStateTransitionProgress;
}
static bool supportsExpansionTransitionProgress() {
static bool globalSupportsExpansionTransitionProgress =
[NSClassFromString(@"NSScrollerImp")
instancesRespondToSelector:@selector(expansionTransitionProgress)];
return globalSupportsExpansionTransitionProgress;
}
static bool supportsContentAreaScrolledInDirection() {
static bool globalSupportsContentAreaScrolledInDirection = [NSClassFromString(
@"NSScrollerImpPair")
instancesRespondToSelector:@selector(contentAreaScrolledInDirection:)];
return globalSupportsContentAreaScrolledInDirection;
}
static ScrollbarThemeMac* macOverlayScrollbarTheme() {
ScrollbarTheme& scrollbarTheme = ScrollbarTheme::theme();
return !scrollbarTheme.isMockTheme()
? static_cast<ScrollbarThemeMac*>(&scrollbarTheme)
: nil;
}
static ScrollbarPainter scrollbarPainterForScrollbar(Scrollbar& scrollbar) {
if (ScrollbarThemeMac* scrollbarTheme = macOverlayScrollbarTheme())
return scrollbarTheme->painterForScrollbar(scrollbar);
return nil;
}
@interface NSObject (ScrollAnimationHelperDetails)
- (id)initWithDelegate:(id)delegate;
- (void)_stopRun;
- (BOOL)_isAnimating;
- (NSPoint)targetOrigin;
- (CGFloat)_progress;
@end
@interface BlinkScrollAnimationHelperDelegate : NSObject {
blink::ScrollAnimatorMac* _animator;
}
- (id)initWithScrollAnimator:(blink::ScrollAnimatorMac*)scrollAnimator;
@end
static NSSize abs(NSSize size) {
NSSize finalSize = size;
if (finalSize.width < 0)
finalSize.width = -finalSize.width;
if (finalSize.height < 0)
finalSize.height = -finalSize.height;
return finalSize;
}
@implementation BlinkScrollAnimationHelperDelegate
- (id)initWithScrollAnimator:(blink::ScrollAnimatorMac*)scrollAnimator {
self = [super init];
if (!self)
return nil;
_animator = scrollAnimator;
return self;
}
- (void)invalidate {
_animator = 0;
}
- (NSRect)bounds {
if (!_animator)
return NSZeroRect;
blink::ScrollOffset currentOffset = _animator->currentOffset();
return NSMakeRect(currentOffset.width(), currentOffset.height(), 0, 0);
}
- (void)_immediateScrollToPoint:(NSPoint)newPosition {
if (!_animator)
return;
_animator->immediateScrollToOffsetForScrollAnimation(
toScrollOffset(newPosition));
}
- (NSPoint)_pixelAlignProposedScrollPosition:(NSPoint)newOrigin {
return newOrigin;
}
- (NSSize)convertSizeToBase:(NSSize)size {
return abs(size);
}
- (NSSize)convertSizeFromBase:(NSSize)size {
return abs(size);
}
- (NSSize)convertSizeToBacking:(NSSize)size {
return abs(size);
}
- (NSSize)convertSizeFromBacking:(NSSize)size {
return abs(size);
}
- (id)superview {
return nil;
}
- (id)documentView {
return nil;
}
- (id)window {
return nil;
}
- (void)_recursiveRecomputeToolTips {
}
@end
@interface BlinkScrollbarPainterControllerDelegate : NSObject {
ScrollableArea* _scrollableArea;
}
- (id)initWithScrollableArea:(ScrollableArea*)scrollableArea;
@end
@implementation BlinkScrollbarPainterControllerDelegate
- (id)initWithScrollableArea:(ScrollableArea*)scrollableArea {
self = [super init];
if (!self)
return nil;
_scrollableArea = scrollableArea;
return self;
}
- (void)invalidate {
_scrollableArea = 0;
}
- (NSRect)contentAreaRectForScrollerImpPair:(id)scrollerImpPair {
if (!_scrollableArea)
return NSZeroRect;
blink::IntSize contentsSize = _scrollableArea->contentsSize();
return NSMakeRect(0, 0, contentsSize.width(), contentsSize.height());
}
- (BOOL)inLiveResizeForScrollerImpPair:(id)scrollerImpPair {
return NO;
}
- (NSPoint)mouseLocationInContentAreaForScrollerImpPair:(id)scrollerImpPair {
if (!_scrollableArea)
return NSZeroPoint;
return _scrollableArea->lastKnownMousePosition();
}
- (NSPoint)scrollerImpPair:(id)scrollerImpPair
convertContentPoint:(NSPoint)pointInContentArea
toScrollerImp:(id)scrollerImp {
if (!_scrollableArea || !scrollerImp)
return NSZeroPoint;
blink::Scrollbar* scrollbar = nil;
if ([scrollerImp isHorizontal])
scrollbar = _scrollableArea->horizontalScrollbar();
else
scrollbar = _scrollableArea->verticalScrollbar();
// It is possible to have a null scrollbar here since it is possible for this
// delegate
// method to be called between the moment when a scrollbar has been set to 0
// and the
// moment when its destructor has been called. We should probably de-couple
// some
// of the clean-up work in ScrollbarThemeMac::unregisterScrollbar() to avoid
// this
// issue.
if (!scrollbar)
return NSZeroPoint;
ASSERT(scrollerImp == scrollbarPainterForScrollbar(*scrollbar));
return scrollbar->convertFromContainingWidget(
blink::IntPoint(pointInContentArea));
}
- (void)scrollerImpPair:(id)scrollerImpPair
setContentAreaNeedsDisplayInRect:(NSRect)rect {
if (!_scrollableArea)
return;
if (!_scrollableArea->scrollbarsCanBeActive())
return;
_scrollableArea->scrollAnimator().contentAreaWillPaint();
}
- (void)scrollerImpPair:(id)scrollerImpPair
updateScrollerStyleForNewRecommendedScrollerStyle:
(NSScrollerStyle)newRecommendedScrollerStyle {
// Chrome has a single process mode which is used for testing on Mac. In that
// mode, WebKit runs on a thread in the
// browser process. This notification is called by the OS on the main thread
// in the browser process, and not on the
// the WebKit thread. Better to not update the style than crash.
// http://crbug.com/126514
if (!isMainThread())
return;
if (!_scrollableArea)
return;
[scrollerImpPair setScrollerStyle:newRecommendedScrollerStyle];
static_cast<ScrollAnimatorMac&>(_scrollableArea->scrollAnimator())
.updateScrollerStyle();
}
@end
enum FeatureToAnimate {
ThumbAlpha,
TrackAlpha,
UIStateTransition,
ExpansionTransition
};
@class BlinkScrollbarPartAnimation;
namespace blink {
// This class is used to drive the animation timer for
// BlinkScrollbarPartAnimation
// objects. This is used instead of NSAnimation because CoreAnimation
// establishes connections to the WindowServer, which should not be done in a
// sandboxed renderer process.
class BlinkScrollbarPartAnimationTimer {
public:
BlinkScrollbarPartAnimationTimer(BlinkScrollbarPartAnimation* animation,
CFTimeInterval duration)
: m_timer(this, &BlinkScrollbarPartAnimationTimer::timerFired),
m_startTime(0.0),
m_duration(duration),
m_animation(animation),
m_timingFunction(CubicBezierTimingFunction::preset(
CubicBezierTimingFunction::EaseType::EASE_IN_OUT)) {}
~BlinkScrollbarPartAnimationTimer() {}
void start() {
m_startTime = WTF::currentTime();
// Set the framerate of the animation. NSAnimation uses a default
// framerate of 60 Hz, so use that here.
m_timer.startRepeating(1.0 / 60.0, BLINK_FROM_HERE);
}
void stop() { m_timer.stop(); }
void setDuration(CFTimeInterval duration) { m_duration = duration; }
private:
void timerFired(TimerBase*) {
double currentTime = WTF::currentTime();
double delta = currentTime - m_startTime;
if (delta >= m_duration)
m_timer.stop();
double fraction = delta / m_duration;
fraction = clampTo(fraction, 0.0, 1.0);
double progress = m_timingFunction->evaluate(fraction, 0.001);
[m_animation setCurrentProgress:progress];
}
Timer<BlinkScrollbarPartAnimationTimer> m_timer;
double m_startTime; // In seconds.
double m_duration; // In seconds.
BlinkScrollbarPartAnimation* m_animation; // Weak, owns this.
RefPtr<CubicBezierTimingFunction> m_timingFunction;
};
} // namespace blink
@interface BlinkScrollbarPartAnimation : NSObject {
Scrollbar* _scrollbar;
std::unique_ptr<BlinkScrollbarPartAnimationTimer> _timer;
RetainPtr<ScrollbarPainter> _scrollbarPainter;
FeatureToAnimate _featureToAnimate;
CGFloat _startValue;
CGFloat _endValue;
}
- (id)initWithScrollbar:(Scrollbar*)scrollbar
featureToAnimate:(FeatureToAnimate)featureToAnimate
animateFrom:(CGFloat)startValue
animateTo:(CGFloat)endValue
duration:(NSTimeInterval)duration;
@end
@implementation BlinkScrollbarPartAnimation
- (id)initWithScrollbar:(Scrollbar*)scrollbar
featureToAnimate:(FeatureToAnimate)featureToAnimate
animateFrom:(CGFloat)startValue
animateTo:(CGFloat)endValue
duration:(NSTimeInterval)duration {
self = [super init];
if (!self)
return nil;
_timer = wrapUnique(new BlinkScrollbarPartAnimationTimer(self, duration));
_scrollbar = scrollbar;
_featureToAnimate = featureToAnimate;
_startValue = startValue;
_endValue = endValue;
return self;
}
- (void)startAnimation {
ASSERT(_scrollbar);
_scrollbarPainter = scrollbarPainterForScrollbar(*_scrollbar);
_timer->start();
}
- (void)stopAnimation {
_timer->stop();
}
- (void)setDuration:(CFTimeInterval)duration {
_timer->setDuration(duration);
}
- (void)setStartValue:(CGFloat)startValue {
_startValue = startValue;
}
- (void)setEndValue:(CGFloat)endValue {
_endValue = endValue;
}
- (void)setCurrentProgress:(NSAnimationProgress)progress {
ASSERT(_scrollbar);
CGFloat currentValue;
if (_startValue > _endValue)
currentValue = 1 - progress;
else
currentValue = progress;
ScrollbarPart invalidParts = NoPart;
switch (_featureToAnimate) {
case ThumbAlpha:
[_scrollbarPainter.get() setKnobAlpha:currentValue];
break;
case TrackAlpha:
[_scrollbarPainter.get() setTrackAlpha:currentValue];
invalidParts = static_cast<ScrollbarPart>(~ThumbPart);
break;
case UIStateTransition:
[_scrollbarPainter.get() setUiStateTransitionProgress:currentValue];
invalidParts = AllParts;
break;
case ExpansionTransition:
[_scrollbarPainter.get() setExpansionTransitionProgress:currentValue];
invalidParts = ThumbPart;
break;
}
_scrollbar->setNeedsPaintInvalidation(invalidParts);
}
- (void)invalidate {
BEGIN_BLOCK_OBJC_EXCEPTIONS;
[self stopAnimation];
END_BLOCK_OBJC_EXCEPTIONS;
_scrollbar = 0;
}
@end
@interface BlinkScrollbarPainterDelegate : NSObject<NSAnimationDelegate> {
blink::Scrollbar* _scrollbar;
RetainPtr<BlinkScrollbarPartAnimation> _knobAlphaAnimation;
RetainPtr<BlinkScrollbarPartAnimation> _trackAlphaAnimation;
RetainPtr<BlinkScrollbarPartAnimation> _uiStateTransitionAnimation;
RetainPtr<BlinkScrollbarPartAnimation> _expansionTransitionAnimation;
BOOL _hasExpandedSinceInvisible;
}
- (id)initWithScrollbar:(blink::Scrollbar*)scrollbar;
- (void)updateVisibilityImmediately:(bool)show;
- (void)cancelAnimations;
@end
@implementation BlinkScrollbarPainterDelegate
- (id)initWithScrollbar:(blink::Scrollbar*)scrollbar {
self = [super init];
if (!self)
return nil;
_scrollbar = scrollbar;
return self;
}
- (void)updateVisibilityImmediately:(bool)show {
[self cancelAnimations];
[scrollbarPainterForScrollbar(*_scrollbar) setKnobAlpha:(show ? 1.0 : 0.0)];
}
- (void)cancelAnimations {
BEGIN_BLOCK_OBJC_EXCEPTIONS;
[_knobAlphaAnimation.get() stopAnimation];
[_trackAlphaAnimation.get() stopAnimation];
[_uiStateTransitionAnimation.get() stopAnimation];
[_expansionTransitionAnimation.get() stopAnimation];
END_BLOCK_OBJC_EXCEPTIONS;
}
- (ScrollAnimatorMac&)scrollAnimator {
return static_cast<ScrollAnimatorMac&>(
_scrollbar->getScrollableArea()->scrollAnimator());
}
- (NSRect)convertRectToBacking:(NSRect)aRect {
return aRect;
}
- (NSRect)convertRectFromBacking:(NSRect)aRect {
return aRect;
}
- (NSPoint)mouseLocationInScrollerForScrollerImp:(id)scrollerImp {
if (!_scrollbar)
return NSZeroPoint;
DCHECK_EQ(scrollerImp, scrollbarPainterForScrollbar(*_scrollbar));
return _scrollbar->convertFromContainingWidget(
_scrollbar->getScrollableArea()->lastKnownMousePosition());
}
- (void)setUpAlphaAnimation:
(RetainPtr<BlinkScrollbarPartAnimation>&)scrollbarPartAnimation
scrollerPainter:(ScrollbarPainter)scrollerPainter
part:(blink::ScrollbarPart)part
animateAlphaTo:(CGFloat)newAlpha
duration:(NSTimeInterval)duration {
// If the user has scrolled the page, then the scrollbars must be animated
// here.
// This overrides the early returns.
bool mustAnimate = [self scrollAnimator].haveScrolledSincePageLoad();
if ([self scrollAnimator].scrollbarPaintTimerIsActive() && !mustAnimate)
return;
if (_scrollbar->getScrollableArea()->shouldSuspendScrollAnimations() &&
!mustAnimate) {
[self scrollAnimator].startScrollbarPaintTimer();
return;
}
// At this point, we are definitely going to animate now, so stop the timer.
[self scrollAnimator].stopScrollbarPaintTimer();
// If we are currently animating, stop
if (scrollbarPartAnimation) {
[scrollbarPartAnimation.get() stopAnimation];
scrollbarPartAnimation = nullptr;
}
if (part == blink::ThumbPart &&
_scrollbar->orientation() == VerticalScrollbar) {
if (newAlpha == 1) {
IntRect thumbRect = IntRect([scrollerPainter rectForPart:NSScrollerKnob]);
[self scrollAnimator].setVisibleScrollerThumbRect(thumbRect);
} else
[self scrollAnimator].setVisibleScrollerThumbRect(IntRect());
}
scrollbarPartAnimation.adoptNS([[BlinkScrollbarPartAnimation alloc]
initWithScrollbar:_scrollbar
featureToAnimate:part == ThumbPart ? ThumbAlpha : TrackAlpha
animateFrom:part == ThumbPart ? [scrollerPainter knobAlpha]
: [scrollerPainter trackAlpha]
animateTo:newAlpha
duration:duration]);
[scrollbarPartAnimation.get() startAnimation];
}
- (void)scrollerImp:(id)scrollerImp
animateKnobAlphaTo:(CGFloat)newKnobAlpha
duration:(NSTimeInterval)duration {
if (!_scrollbar)
return;
ASSERT(scrollerImp == scrollbarPainterForScrollbar(*_scrollbar));
ScrollbarPainter scrollerPainter = (ScrollbarPainter)scrollerImp;
[self setUpAlphaAnimation:_knobAlphaAnimation
scrollerPainter:scrollerPainter
part:blink::ThumbPart
animateAlphaTo:newKnobAlpha
duration:duration];
}
- (void)scrollerImp:(id)scrollerImp
animateTrackAlphaTo:(CGFloat)newTrackAlpha
duration:(NSTimeInterval)duration {
if (!_scrollbar)
return;
ASSERT(scrollerImp == scrollbarPainterForScrollbar(*_scrollbar));
ScrollbarPainter scrollerPainter = (ScrollbarPainter)scrollerImp;
[self setUpAlphaAnimation:_trackAlphaAnimation
scrollerPainter:scrollerPainter
part:blink::BackTrackPart
animateAlphaTo:newTrackAlpha
duration:duration];
}
- (void)scrollerImp:(id)scrollerImp
animateUIStateTransitionWithDuration:(NSTimeInterval)duration {
if (!_scrollbar)
return;
if (!supportsUIStateTransitionProgress())
return;
ASSERT(scrollerImp == scrollbarPainterForScrollbar(*_scrollbar));
ScrollbarPainter scrollbarPainter = (ScrollbarPainter)scrollerImp;
// UIStateTransition always animates to 1. In case an animation is in progress
// this avoids a hard transition.
[scrollbarPainter
setUiStateTransitionProgress:1 - [scrollerImp uiStateTransitionProgress]];
if (!_uiStateTransitionAnimation)
_uiStateTransitionAnimation.adoptNS([[BlinkScrollbarPartAnimation alloc]
initWithScrollbar:_scrollbar
featureToAnimate:UIStateTransition
animateFrom:[scrollbarPainter uiStateTransitionProgress]
animateTo:1.0
duration:duration]);
else {
// If we don't need to initialize the animation, just reset the values in
// case they have changed.
[_uiStateTransitionAnimation.get()
setStartValue:[scrollbarPainter uiStateTransitionProgress]];
[_uiStateTransitionAnimation.get() setEndValue:1.0];
[_uiStateTransitionAnimation.get() setDuration:duration];
}
[_uiStateTransitionAnimation.get() startAnimation];
}
- (void)scrollerImp:(id)scrollerImp
animateExpansionTransitionWithDuration:(NSTimeInterval)duration {
if (!_scrollbar)
return;
if (!supportsExpansionTransitionProgress())
return;
ASSERT(scrollerImp == scrollbarPainterForScrollbar(*_scrollbar));
ScrollbarPainter scrollbarPainter = (ScrollbarPainter)scrollerImp;
// ExpansionTransition always animates to 1. In case an animation is in
// progress this avoids a hard transition.
[scrollbarPainter
setExpansionTransitionProgress:1 -
[scrollerImp expansionTransitionProgress]];
if (!_expansionTransitionAnimation) {
_expansionTransitionAnimation.adoptNS([[BlinkScrollbarPartAnimation alloc]
initWithScrollbar:_scrollbar
featureToAnimate:ExpansionTransition
animateFrom:[scrollbarPainter expansionTransitionProgress]
animateTo:1.0
duration:duration]);
} else {
// If we don't need to initialize the animation, just reset the values in
// case they have changed.
[_expansionTransitionAnimation.get()
setStartValue:[scrollbarPainter uiStateTransitionProgress]];
[_expansionTransitionAnimation.get() setEndValue:1.0];
[_expansionTransitionAnimation.get() setDuration:duration];
}
[_expansionTransitionAnimation.get() startAnimation];
}
- (void)scrollerImp:(id)scrollerImp
overlayScrollerStateChangedTo:(NSUInteger)newOverlayScrollerState {
// The names of these states are based on their observed behavior, and are not
// based on documentation.
enum {
NSScrollerStateInvisible = 0,
NSScrollerStateKnob = 1,
NSScrollerStateExpanded = 2
};
// We do not receive notifications about the thumb un-expanding when the
// scrollbar fades away. Ensure
// that we re-paint the thumb the next time that we transition away from being
// invisible, so that
// the thumb doesn't stick in an expanded state.
if (newOverlayScrollerState == NSScrollerStateExpanded) {
_hasExpandedSinceInvisible = YES;
} else if (newOverlayScrollerState != NSScrollerStateInvisible &&
_hasExpandedSinceInvisible) {
_scrollbar->setNeedsPaintInvalidation(ThumbPart);
_hasExpandedSinceInvisible = NO;
}
}
- (void)invalidate {
_scrollbar = 0;
BEGIN_BLOCK_OBJC_EXCEPTIONS;
[_knobAlphaAnimation.get() invalidate];
[_trackAlphaAnimation.get() invalidate];
[_uiStateTransitionAnimation.get() invalidate];
[_expansionTransitionAnimation.get() invalidate];
END_BLOCK_OBJC_EXCEPTIONS;
}
@end
namespace blink {
ScrollAnimatorBase* ScrollAnimatorBase::create(ScrollableArea* scrollableArea) {
return new ScrollAnimatorMac(scrollableArea);
}
ScrollAnimatorMac::ScrollAnimatorMac(ScrollableArea* scrollableArea)
: ScrollAnimatorBase(scrollableArea),
m_initialScrollbarPaintTaskFactory(CancellableTaskFactory::create(
this,
&ScrollAnimatorMac::initialScrollbarPaintTask)),
m_sendContentAreaScrolledTaskFactory(CancellableTaskFactory::create(
this,
&ScrollAnimatorMac::sendContentAreaScrolledTask)),
m_taskRunner(Platform::current()
->currentThread()
->scheduler()
->timerTaskRunner()
->clone()),
m_haveScrolledSincePageLoad(false),
m_needsScrollerStyleUpdate(false) {
ThreadState::current()->registerPreFinalizer(this);
m_scrollAnimationHelperDelegate.adoptNS(
[[BlinkScrollAnimationHelperDelegate alloc] initWithScrollAnimator:this]);
m_scrollAnimationHelper.adoptNS(
[[NSClassFromString(@"NSScrollAnimationHelper") alloc]
initWithDelegate:m_scrollAnimationHelperDelegate.get()]);
m_scrollbarPainterControllerDelegate.adoptNS(
[[BlinkScrollbarPainterControllerDelegate alloc]
initWithScrollableArea:scrollableArea]);
m_scrollbarPainterController =
[[[NSClassFromString(@"NSScrollerImpPair") alloc] init] autorelease];
[m_scrollbarPainterController.get()
performSelector:@selector(setDelegate:)
withObject:m_scrollbarPainterControllerDelegate.get()];
[m_scrollbarPainterController.get()
setScrollerStyle:ScrollbarThemeMac::recommendedScrollerStyle()];
}
ScrollAnimatorMac::~ScrollAnimatorMac() {}
void ScrollAnimatorMac::dispose() {
BEGIN_BLOCK_OBJC_EXCEPTIONS;
[m_scrollbarPainterControllerDelegate.get() invalidate];
[m_scrollbarPainterController.get() setDelegate:nil];
[m_horizontalScrollbarPainterDelegate.get() invalidate];
[m_verticalScrollbarPainterDelegate.get() invalidate];
[m_scrollAnimationHelperDelegate.get() invalidate];
END_BLOCK_OBJC_EXCEPTIONS;
m_initialScrollbarPaintTaskFactory->cancel();
m_sendContentAreaScrolledTaskFactory->cancel();
}
ScrollResult ScrollAnimatorMac::userScroll(ScrollGranularity granularity,
const ScrollOffset& delta) {
m_haveScrolledSincePageLoad = true;
if (!m_scrollableArea->scrollAnimatorEnabled())
return ScrollAnimatorBase::userScroll(granularity, delta);
if (granularity == ScrollByPixel || granularity == ScrollByPrecisePixel)
return ScrollAnimatorBase::userScroll(granularity, delta);
ScrollOffset consumedDelta = computeDeltaToConsume(delta);
ScrollOffset newOffset = m_currentOffset + consumedDelta;
if (m_currentOffset == newOffset)
return ScrollResult();
// Prevent clobbering an existing animation on an unscrolled axis.
if ([m_scrollAnimationHelper.get() _isAnimating]) {
NSPoint targetOrigin = [m_scrollAnimationHelper.get() targetOrigin];
if (!delta.width())
newOffset.setWidth(targetOrigin.x);
if (!delta.height())
newOffset.setHeight(targetOrigin.y);
}
NSPoint newPoint = NSMakePoint(newOffset.width(), newOffset.height());
[m_scrollAnimationHelper.get() scrollToPoint:newPoint];
// TODO(bokan): This has different semantics on ScrollResult than
// ScrollAnimator,
// which only returns unused delta if there's no animation and we don't start
// one.
return ScrollResult(consumedDelta.width(), consumedDelta.height(),
delta.width() - consumedDelta.width(),
delta.height() - consumedDelta.height());
}
void ScrollAnimatorMac::scrollToOffsetWithoutAnimation(
const ScrollOffset& offset) {
[m_scrollAnimationHelper.get() _stopRun];
immediateScrollTo(offset);
}
ScrollOffset ScrollAnimatorMac::adjustScrollOffsetIfNecessary(
const ScrollOffset& offset) const {
ScrollOffset minOffset = m_scrollableArea->minimumScrollOffset();
ScrollOffset maxOffset = m_scrollableArea->maximumScrollOffset();
float newX = clampTo<float, float>(offset.width(), minOffset.width(),
maxOffset.width());
float newY = clampTo<float, float>(offset.height(), minOffset.height(),
maxOffset.height());
return ScrollOffset(newX, newY);
}
void ScrollAnimatorMac::immediateScrollTo(const ScrollOffset& newOffset) {
ScrollOffset adjustedOffset = adjustScrollOffsetIfNecessary(newOffset);
bool offsetChanged = adjustedOffset != m_currentOffset;
if (!offsetChanged && !getScrollableArea()->scrollOriginChanged())
return;
ScrollOffset delta = adjustedOffset - m_currentOffset;
m_currentOffset = adjustedOffset;
notifyContentAreaScrolled(delta);
notifyOffsetChanged();
}
void ScrollAnimatorMac::immediateScrollToOffsetForScrollAnimation(
const ScrollOffset& newOffset) {
ASSERT(m_scrollAnimationHelper);
immediateScrollTo(newOffset);
}
void ScrollAnimatorMac::contentAreaWillPaint() const {
if (!getScrollableArea()->scrollbarsCanBeActive())
return;
[m_scrollbarPainterController.get() contentAreaWillDraw];
}
void ScrollAnimatorMac::mouseEnteredContentArea() const {
if (!getScrollableArea()->scrollbarsCanBeActive())
return;
[m_scrollbarPainterController.get() mouseEnteredContentArea];
}
void ScrollAnimatorMac::mouseExitedContentArea() const {
if (!getScrollableArea()->scrollbarsCanBeActive())
return;
[m_scrollbarPainterController.get() mouseExitedContentArea];
}
void ScrollAnimatorMac::mouseMovedInContentArea() const {
if (!getScrollableArea()->scrollbarsCanBeActive())
return;
[m_scrollbarPainterController.get() mouseMovedInContentArea];
}
void ScrollAnimatorMac::mouseEnteredScrollbar(Scrollbar& scrollbar) const {
if (!getScrollableArea()->scrollbarsCanBeActive())
return;
if (!supportsUIStateTransitionProgress())
return;
if (ScrollbarPainter painter = scrollbarPainterForScrollbar(scrollbar))
[painter mouseEnteredScroller];
}
void ScrollAnimatorMac::mouseExitedScrollbar(Scrollbar& scrollbar) const {
if (!getScrollableArea()->scrollbarsCanBeActive())
return;
if (!supportsUIStateTransitionProgress())
return;
if (ScrollbarPainter painter = scrollbarPainterForScrollbar(scrollbar))
[painter mouseExitedScroller];
}
void ScrollAnimatorMac::contentsResized() const {
if (!getScrollableArea()->scrollbarsCanBeActive())
return;
[m_scrollbarPainterController.get() contentAreaDidResize];
}
void ScrollAnimatorMac::contentAreaDidShow() const {
if (!getScrollableArea()->scrollbarsCanBeActive())
return;
[m_scrollbarPainterController.get() windowOrderedIn];
}
void ScrollAnimatorMac::contentAreaDidHide() const {
if (!getScrollableArea()->scrollbarsCanBeActive())
return;
[m_scrollbarPainterController.get() windowOrderedOut];
}
void ScrollAnimatorMac::didBeginScrollGesture() const {
if (!getScrollableArea()->scrollbarsCanBeActive())
return;
[m_scrollbarPainterController.get() beginScrollGesture];
}
void ScrollAnimatorMac::didEndScrollGesture() const {
if (!getScrollableArea()->scrollbarsCanBeActive())
return;
[m_scrollbarPainterController.get() endScrollGesture];
}
void ScrollAnimatorMac::mayBeginScrollGesture() const {
if (!getScrollableArea()->scrollbarsCanBeActive())
return;
[m_scrollbarPainterController.get() beginScrollGesture];
[m_scrollbarPainterController.get() contentAreaScrolled];
}
void ScrollAnimatorMac::finishCurrentScrollAnimations() {
[m_scrollbarPainterController.get() hideOverlayScrollers];
}
void ScrollAnimatorMac::didAddVerticalScrollbar(Scrollbar& scrollbar) {
ScrollbarPainter painter = scrollbarPainterForScrollbar(scrollbar);
if (!painter)
return;
ASSERT(!m_verticalScrollbarPainterDelegate);
m_verticalScrollbarPainterDelegate.adoptNS(
[[BlinkScrollbarPainterDelegate alloc] initWithScrollbar:&scrollbar]);
[painter setDelegate:m_verticalScrollbarPainterDelegate.get()];
[m_scrollbarPainterController.get() setVerticalScrollerImp:painter];
}
void ScrollAnimatorMac::willRemoveVerticalScrollbar(Scrollbar& scrollbar) {
ScrollbarPainter painter = scrollbarPainterForScrollbar(scrollbar);
if (!painter)
return;
ASSERT(m_verticalScrollbarPainterDelegate);
[m_verticalScrollbarPainterDelegate.get() invalidate];
m_verticalScrollbarPainterDelegate = nullptr;
[painter setDelegate:nil];
[m_scrollbarPainterController.get() setVerticalScrollerImp:nil];
}
void ScrollAnimatorMac::didAddHorizontalScrollbar(Scrollbar& scrollbar) {
ScrollbarPainter painter = scrollbarPainterForScrollbar(scrollbar);
if (!painter)
return;
ASSERT(!m_horizontalScrollbarPainterDelegate);
m_horizontalScrollbarPainterDelegate.adoptNS(
[[BlinkScrollbarPainterDelegate alloc] initWithScrollbar:&scrollbar]);
[painter setDelegate:m_horizontalScrollbarPainterDelegate.get()];
[m_scrollbarPainterController.get() setHorizontalScrollerImp:painter];
}
void ScrollAnimatorMac::willRemoveHorizontalScrollbar(Scrollbar& scrollbar) {
ScrollbarPainter painter = scrollbarPainterForScrollbar(scrollbar);
if (!painter)
return;
ASSERT(m_horizontalScrollbarPainterDelegate);
[m_horizontalScrollbarPainterDelegate.get() invalidate];
m_horizontalScrollbarPainterDelegate = nullptr;
[painter setDelegate:nil];
[m_scrollbarPainterController.get() setHorizontalScrollerImp:nil];
}
void ScrollAnimatorMac::notifyContentAreaScrolled(const ScrollOffset& delta) {
// This function is called when a page is going into the page cache, but the
// page
// isn't really scrolling in that case. We should only pass the message on to
// the
// ScrollbarPainterController when we're really scrolling on an active page.
if (getScrollableArea()->scrollbarsCanBeActive())
sendContentAreaScrolledSoon(delta);
}
bool ScrollAnimatorMac::setScrollbarsVisibleForTesting(bool show) {
if (show)
[m_scrollbarPainterController.get() flashScrollers];
else
[m_scrollbarPainterController.get() hideOverlayScrollers];
[m_verticalScrollbarPainterDelegate.get() updateVisibilityImmediately:show];
[m_horizontalScrollbarPainterDelegate.get() updateVisibilityImmediately:show];
return true;
}
void ScrollAnimatorMac::cancelAnimation() {
[m_scrollAnimationHelper.get() _stopRun];
m_haveScrolledSincePageLoad = false;
}
void ScrollAnimatorMac::handleWheelEventPhase(PlatformWheelEventPhase phase) {
// This may not have been set to true yet if the wheel event was handled by
// the ScrollingTree,
// So set it to true here.
m_haveScrolledSincePageLoad = true;
if (phase == PlatformWheelEventPhaseBegan)
didBeginScrollGesture();
else if (phase == PlatformWheelEventPhaseEnded ||
phase == PlatformWheelEventPhaseCancelled)
didEndScrollGesture();
else if (phase == PlatformWheelEventPhaseMayBegin)
mayBeginScrollGesture();
}
void ScrollAnimatorMac::updateScrollerStyle() {
if (!getScrollableArea()->scrollbarsCanBeActive()) {
m_needsScrollerStyleUpdate = true;
return;
}
ScrollbarThemeMac* macTheme = macOverlayScrollbarTheme();
if (!macTheme) {
m_needsScrollerStyleUpdate = false;
return;
}
NSScrollerStyle newStyle = [m_scrollbarPainterController.get() scrollerStyle];
if (Scrollbar* verticalScrollbar = getScrollableArea()->verticalScrollbar()) {
verticalScrollbar->setNeedsPaintInvalidation(AllParts);
ScrollbarPainter oldVerticalPainter =
[m_scrollbarPainterController.get() verticalScrollerImp];
ScrollbarPainter newVerticalPainter = [NSClassFromString(@"NSScrollerImp")
scrollerImpWithStyle:newStyle
controlSize:(NSControlSize)verticalScrollbar->controlSize()
horizontal:NO
replacingScrollerImp:oldVerticalPainter];
[m_scrollbarPainterController.get()
setVerticalScrollerImp:newVerticalPainter];
macTheme->setNewPainterForScrollbar(*verticalScrollbar, newVerticalPainter);
// The different scrollbar styles have different thicknesses, so we must
// re-set the
// frameRect to the new thickness, and the re-layout below will ensure the
// offset
// and length are properly updated.
int thickness =
macTheme->scrollbarThickness(verticalScrollbar->controlSize());
verticalScrollbar->setFrameRect(IntRect(0, 0, thickness, thickness));
}
if (Scrollbar* horizontalScrollbar =
getScrollableArea()->horizontalScrollbar()) {
horizontalScrollbar->setNeedsPaintInvalidation(AllParts);
ScrollbarPainter oldHorizontalPainter =
[m_scrollbarPainterController.get() horizontalScrollerImp];
ScrollbarPainter newHorizontalPainter = [NSClassFromString(@"NSScrollerImp")
scrollerImpWithStyle:newStyle
controlSize:(NSControlSize)horizontalScrollbar->controlSize()
horizontal:YES
replacingScrollerImp:oldHorizontalPainter];
[m_scrollbarPainterController.get()
setHorizontalScrollerImp:newHorizontalPainter];
macTheme->setNewPainterForScrollbar(*horizontalScrollbar,
newHorizontalPainter);
// The different scrollbar styles have different thicknesses, so we must
// re-set the
// frameRect to the new thickness, and the re-layout below will ensure the
// offset
// and length are properly updated.
int thickness =
macTheme->scrollbarThickness(horizontalScrollbar->controlSize());
horizontalScrollbar->setFrameRect(IntRect(0, 0, thickness, thickness));
}
// If m_needsScrollerStyleUpdate is true, then the page is restoring from the
// page cache, and
// a relayout will happen on its own. Otherwise, we must initiate a re-layout
// ourselves.
if (!m_needsScrollerStyleUpdate)
getScrollableArea()->scrollbarStyleChanged();
m_needsScrollerStyleUpdate = false;
}
void ScrollAnimatorMac::startScrollbarPaintTimer() {
m_taskRunner->postDelayedTask(
BLINK_FROM_HERE, m_initialScrollbarPaintTaskFactory->cancelAndCreate(),
0.1);
}
bool ScrollAnimatorMac::scrollbarPaintTimerIsActive() const {
return m_initialScrollbarPaintTaskFactory->isPending();
}
void ScrollAnimatorMac::stopScrollbarPaintTimer() {
m_initialScrollbarPaintTaskFactory->cancel();
}
void ScrollAnimatorMac::initialScrollbarPaintTask() {
// To force the scrollbars to flash, we have to call hide first. Otherwise,
// the ScrollbarPainterController
// might think that the scrollbars are already showing and bail early.
[m_scrollbarPainterController.get() hideOverlayScrollers];
[m_scrollbarPainterController.get() flashScrollers];
}
void ScrollAnimatorMac::sendContentAreaScrolledSoon(const ScrollOffset& delta) {
m_contentAreaScrolledTimerScrollDelta = delta;
if (!m_sendContentAreaScrolledTaskFactory->isPending())
m_taskRunner->postTask(
BLINK_FROM_HERE,
m_sendContentAreaScrolledTaskFactory->cancelAndCreate());
}
void ScrollAnimatorMac::sendContentAreaScrolledTask() {
if (supportsContentAreaScrolledInDirection()) {
[m_scrollbarPainterController.get()
contentAreaScrolledInDirection:NSMakePoint(
m_contentAreaScrolledTimerScrollDelta
.width(),
m_contentAreaScrolledTimerScrollDelta
.height())];
m_contentAreaScrolledTimerScrollDelta = ScrollOffset();
} else
[m_scrollbarPainterController.get() contentAreaScrolled];
}
void ScrollAnimatorMac::setVisibleScrollerThumbRect(
const IntRect& scrollerThumb) {
IntRect rectInViewCoordinates = scrollerThumb;
if (Scrollbar* verticalScrollbar = m_scrollableArea->verticalScrollbar())
rectInViewCoordinates =
verticalScrollbar->convertToContainingWidget(scrollerThumb);
if (rectInViewCoordinates == m_visibleScrollerThumbRect)
return;
m_visibleScrollerThumbRect = rectInViewCoordinates;
}
} // namespace blink