1/* 2 * Copyright (C) 2010, 2011 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 "third_party/blink/renderer/core/scroll/scroll_animator_mac.h" 27 28#import <AppKit/AppKit.h> 29 30#include <memory> 31#include "base/mac/scoped_cftyperef.h" 32#include "base/memory/scoped_policy.h" 33#include "third_party/blink/public/platform/platform.h" 34#include "third_party/blink/renderer/core/scroll/ns_scroller_imp_details.h" 35#include "third_party/blink/renderer/core/scroll/scrollable_area.h" 36#include "third_party/blink/renderer/core/scroll/scrollbar_theme.h" 37#include "third_party/blink/renderer/core/scroll/scrollbar_theme_mac.h" 38#include "third_party/blink/renderer/platform/animation/timing_function.h" 39#include "third_party/blink/renderer/platform/geometry/float_rect.h" 40#include "third_party/blink/renderer/platform/geometry/int_rect.h" 41#include "third_party/blink/renderer/platform/mac/block_exceptions.h" 42#include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h" 43#include "third_party/blink/renderer/platform/timer.h" 44#include "third_party/blink/renderer/platform/wtf/math_extras.h" 45 46namespace { 47 48bool SupportsUIStateTransitionProgress() { 49 // FIXME: This is temporary until all platforms that support ScrollbarPainter 50 // support this part of the API. 51 static bool global_supports_ui_state_transition_progress = 52 [NSClassFromString(@"NSScrollerImp") 53 instancesRespondToSelector:@selector(mouseEnteredScroller)]; 54 return global_supports_ui_state_transition_progress; 55} 56 57bool SupportsExpansionTransitionProgress() { 58 static bool global_supports_expansion_transition_progress = 59 [NSClassFromString(@"NSScrollerImp") 60 instancesRespondToSelector:@selector(expansionTransitionProgress)]; 61 return global_supports_expansion_transition_progress; 62} 63 64bool SupportsContentAreaScrolledInDirection() { 65 static bool global_supports_content_area_scrolled_in_direction = 66 [NSClassFromString(@"NSScrollerImpPair") 67 instancesRespondToSelector:@selector 68 (contentAreaScrolledInDirection:)]; 69 return global_supports_content_area_scrolled_in_direction; 70} 71 72blink::ScrollbarThemeMac* MacOverlayScrollbarTheme( 73 blink::ScrollbarTheme& scrollbar_theme) { 74 return !scrollbar_theme.IsMockTheme() 75 ? static_cast<blink::ScrollbarThemeMac*>(&scrollbar_theme) 76 : nil; 77} 78 79ScrollbarPainter ScrollbarPainterForScrollbar(blink::Scrollbar& scrollbar) { 80 if (blink::ScrollbarThemeMac* scrollbar_theme = 81 MacOverlayScrollbarTheme(scrollbar.GetTheme())) 82 return scrollbar_theme->PainterForScrollbar(scrollbar); 83 84 return nil; 85} 86 87} // namespace 88 89@interface NSObject (ScrollAnimationHelperDetails) 90- (id)initWithDelegate:(id)delegate; 91- (void)_stopRun; 92- (BOOL)_isAnimating; 93- (NSPoint)targetOrigin; 94- (CGFloat)_progress; 95@end 96 97@interface BlinkScrollAnimationHelperDelegate : NSObject { 98 blink::ScrollAnimatorMac* _animator; 99} 100- (id)initWithScrollAnimator:(blink::ScrollAnimatorMac*)scrollAnimator; 101@end 102 103static NSSize abs(NSSize size) { 104 NSSize finalSize = size; 105 if (finalSize.width < 0) 106 finalSize.width = -finalSize.width; 107 if (finalSize.height < 0) 108 finalSize.height = -finalSize.height; 109 return finalSize; 110} 111 112@implementation BlinkScrollAnimationHelperDelegate 113 114- (id)initWithScrollAnimator:(blink::ScrollAnimatorMac*)scrollAnimator { 115 self = [super init]; 116 if (!self) 117 return nil; 118 119 _animator = scrollAnimator; 120 return self; 121} 122 123- (void)invalidate { 124 _animator = 0; 125} 126 127- (NSRect)bounds { 128 if (!_animator) 129 return NSZeroRect; 130 131 blink::ScrollOffset currentOffset = _animator->CurrentOffset(); 132 return NSMakeRect(currentOffset.Width(), currentOffset.Height(), 0, 0); 133} 134 135- (void)_immediateScrollToPoint:(NSPoint)newPosition { 136 if (!_animator) 137 return; 138 _animator->ImmediateScrollToOffsetForScrollAnimation( 139 blink::ToScrollOffset(newPosition)); 140} 141 142- (NSPoint)_pixelAlignProposedScrollPosition:(NSPoint)newOrigin { 143 return newOrigin; 144} 145 146- (NSSize)convertSizeToBase:(NSSize)size { 147 return abs(size); 148} 149 150- (NSSize)convertSizeFromBase:(NSSize)size { 151 return abs(size); 152} 153 154- (NSSize)convertSizeToBacking:(NSSize)size { 155 return abs(size); 156} 157 158- (NSSize)convertSizeFromBacking:(NSSize)size { 159 return abs(size); 160} 161 162- (id)superview { 163 return nil; 164} 165 166- (id)documentView { 167 return nil; 168} 169 170- (id)window { 171 return nil; 172} 173 174- (void)_recursiveRecomputeToolTips { 175} 176 177@end 178 179@interface BlinkScrollbarPainterControllerDelegate : NSObject { 180 blink::ScrollableArea* _scrollableArea; 181} 182- (id)initWithScrollableArea:(blink::ScrollableArea*)scrollableArea; 183@end 184 185@implementation BlinkScrollbarPainterControllerDelegate 186 187- (id)initWithScrollableArea:(blink::ScrollableArea*)scrollableArea { 188 self = [super init]; 189 if (!self) 190 return nil; 191 192 _scrollableArea = scrollableArea; 193 return self; 194} 195 196- (void)invalidate { 197 _scrollableArea = 0; 198} 199 200- (NSRect)contentAreaRectForScrollerImpPair:(id)scrollerImpPair { 201 if (!_scrollableArea) 202 return NSZeroRect; 203 204 blink::IntSize contentsSize = _scrollableArea->ContentsSize(); 205 return NSMakeRect(0, 0, contentsSize.Width(), contentsSize.Height()); 206} 207 208- (BOOL)inLiveResizeForScrollerImpPair:(id)scrollerImpPair { 209 return NO; 210} 211 212- (NSPoint)mouseLocationInContentAreaForScrollerImpPair:(id)scrollerImpPair { 213 if (!_scrollableArea) 214 return NSZeroPoint; 215 216 return _scrollableArea->LastKnownMousePosition(); 217} 218 219- (NSPoint)scrollerImpPair:(id)scrollerImpPair 220 convertContentPoint:(NSPoint)pointInContentArea 221 toScrollerImp:(id)scrollerImp { 222 if (!_scrollableArea || !scrollerImp) 223 return NSZeroPoint; 224 225 blink::Scrollbar* scrollbar = nil; 226 if ([scrollerImp isHorizontal]) 227 scrollbar = _scrollableArea->HorizontalScrollbar(); 228 else 229 scrollbar = _scrollableArea->VerticalScrollbar(); 230 231 // It is possible to have a null scrollbar here since it is possible for this 232 // delegate method to be called between the moment when a scrollbar has been 233 // set to 0 and the moment when its destructor has been called. 234 if (!scrollbar) 235 return NSZeroPoint; 236 237 DCHECK_EQ(scrollerImp, ScrollbarPainterForScrollbar(*scrollbar)); 238 239 return scrollbar->ConvertFromContainingEmbeddedContentView( 240 blink::IntPoint(pointInContentArea)); 241} 242 243- (void)scrollerImpPair:(id)scrollerImpPair 244 setContentAreaNeedsDisplayInRect:(NSRect)rect { 245 if (!_scrollableArea) 246 return; 247 248 if (!_scrollableArea->ScrollbarsCanBeActive()) 249 return; 250 251 _scrollableArea->GetScrollAnimator().ContentAreaWillPaint(); 252} 253 254- (void)scrollerImpPair:(id)scrollerImpPair 255 updateScrollerStyleForNewRecommendedScrollerStyle: 256 (NSScrollerStyle)newRecommendedScrollerStyle { 257 // Chrome has a single process mode which is used for testing on Mac. In that 258 // mode, WebKit runs on a thread in the 259 // browser process. This notification is called by the OS on the main thread 260 // in the browser process, and not on the 261 // the WebKit thread. Better to not update the style than crash. 262 // http://crbug.com/126514 263 if (!IsMainThread()) 264 return; 265 266 if (!_scrollableArea) 267 return; 268 269 [scrollerImpPair setScrollerStyle:newRecommendedScrollerStyle]; 270 271 static_cast<blink::ScrollAnimatorMac&>(_scrollableArea->GetScrollAnimator()) 272 .UpdateScrollerStyle(); 273} 274 275@end 276 277enum FeatureToAnimate { 278 ThumbAlpha, 279 TrackAlpha, 280 UIStateTransition, 281 ExpansionTransition 282}; 283 284@class BlinkScrollbarPartAnimation; 285 286namespace blink { 287 288// This class is used to drive the animation timer for 289// BlinkScrollbarPartAnimation 290// objects. This is used instead of NSAnimation because CoreAnimation 291// establishes connections to the WindowServer, which should not be done in a 292// sandboxed renderer process. 293class BlinkScrollbarPartAnimationTimer { 294 public: 295 BlinkScrollbarPartAnimationTimer(BlinkScrollbarPartAnimation* animation, 296 CFTimeInterval duration) 297 : timer_(ThreadScheduler::Current()->CompositorTaskRunner(), 298 this, 299 &BlinkScrollbarPartAnimationTimer::TimerFired), 300 start_time_(0.0), 301 duration_(duration), 302 animation_(animation), 303 timing_function_(CubicBezierTimingFunction::Preset( 304 CubicBezierTimingFunction::EaseType::EASE_IN_OUT)) {} 305 306 ~BlinkScrollbarPartAnimationTimer() {} 307 308 void Start() { 309 start_time_ = base::Time::Now().ToDoubleT(); 310 // Set the framerate of the animation. NSAnimation uses a default 311 // framerate of 60 Hz, so use that here. 312 timer_.StartRepeating(base::TimeDelta::FromSecondsD(1.0 / 60.0), FROM_HERE); 313 } 314 315 void Stop() { timer_.Stop(); } 316 317 void SetDuration(CFTimeInterval duration) { duration_ = duration; } 318 319 private: 320 void TimerFired(TimerBase*) { 321 double current_time = base::Time::Now().ToDoubleT(); 322 double delta = current_time - start_time_; 323 324 if (delta >= duration_) 325 timer_.Stop(); 326 327 double fraction = delta / duration_; 328 fraction = clampTo(fraction, 0.0, 1.0); 329 double progress = timing_function_->Evaluate(fraction); 330 [animation_ setCurrentProgress:progress]; 331 } 332 333 TaskRunnerTimer<BlinkScrollbarPartAnimationTimer> timer_; 334 double start_time_; // In seconds. 335 double duration_; // In seconds. 336 BlinkScrollbarPartAnimation* animation_; // Weak, owns this. 337 scoped_refptr<CubicBezierTimingFunction> timing_function_; 338}; 339 340} // namespace blink 341 342@interface BlinkScrollbarPartAnimation : NSObject { 343 blink::Scrollbar* _scrollbar; 344 std::unique_ptr<blink::BlinkScrollbarPartAnimationTimer> _timer; 345 base::scoped_nsobject<ScrollbarPainter> _scrollbarPainter; 346 FeatureToAnimate _featureToAnimate; 347 CGFloat _startValue; 348 CGFloat _endValue; 349} 350- (id)initWithScrollbar:(blink::Scrollbar*)scrollbar 351 featureToAnimate:(FeatureToAnimate)featureToAnimate 352 animateFrom:(CGFloat)startValue 353 animateTo:(CGFloat)endValue 354 duration:(NSTimeInterval)duration; 355@end 356 357@implementation BlinkScrollbarPartAnimation 358 359- (id)initWithScrollbar:(blink::Scrollbar*)scrollbar 360 featureToAnimate:(FeatureToAnimate)featureToAnimate 361 animateFrom:(CGFloat)startValue 362 animateTo:(CGFloat)endValue 363 duration:(NSTimeInterval)duration { 364 self = [super init]; 365 if (!self) 366 return nil; 367 368 _timer = 369 std::make_unique<blink::BlinkScrollbarPartAnimationTimer>(self, duration); 370 _scrollbar = scrollbar; 371 _featureToAnimate = featureToAnimate; 372 _startValue = startValue; 373 _endValue = endValue; 374 375 return self; 376} 377 378- (void)startAnimation { 379 DCHECK(_scrollbar); 380 381 _scrollbarPainter.reset(ScrollbarPainterForScrollbar(*_scrollbar), 382 base::scoped_policy::RETAIN); 383 _timer->Start(); 384} 385 386- (void)stopAnimation { 387 _timer->Stop(); 388} 389 390- (void)setDuration:(CFTimeInterval)duration { 391 _timer->SetDuration(duration); 392} 393 394- (void)setStartValue:(CGFloat)startValue { 395 _startValue = startValue; 396} 397 398- (void)setEndValue:(CGFloat)endValue { 399 _endValue = endValue; 400} 401 402- (void)setCurrentProgress:(NSAnimationProgress)progress { 403 DCHECK(_scrollbar); 404 405 CGFloat currentValue; 406 if (_startValue > _endValue) 407 currentValue = 1 - progress; 408 else 409 currentValue = progress; 410 411 blink::ScrollbarPart invalidParts = blink::kNoPart; 412 switch (_featureToAnimate) { 413 case ThumbAlpha: 414 [_scrollbarPainter setKnobAlpha:currentValue]; 415 break; 416 case TrackAlpha: 417 [_scrollbarPainter setTrackAlpha:currentValue]; 418 invalidParts = static_cast<blink::ScrollbarPart>(~blink::kThumbPart); 419 break; 420 case UIStateTransition: 421 [_scrollbarPainter setUiStateTransitionProgress:currentValue]; 422 invalidParts = blink::kAllParts; 423 break; 424 case ExpansionTransition: 425 [_scrollbarPainter setExpansionTransitionProgress:currentValue]; 426 invalidParts = blink::kThumbPart; 427 break; 428 } 429 430 _scrollbar->SetNeedsPaintInvalidation(invalidParts); 431} 432 433- (void)invalidate { 434 BEGIN_BLOCK_OBJC_EXCEPTIONS; 435 [self stopAnimation]; 436 END_BLOCK_OBJC_EXCEPTIONS; 437 _scrollbar = 0; 438} 439 440@end 441 442@interface BlinkScrollbarPainterDelegate : NSObject<NSAnimationDelegate> { 443 blink::Scrollbar* _scrollbar; 444 445 base::scoped_nsobject<BlinkScrollbarPartAnimation> _knobAlphaAnimation; 446 base::scoped_nsobject<BlinkScrollbarPartAnimation> _trackAlphaAnimation; 447 base::scoped_nsobject<BlinkScrollbarPartAnimation> 448 _uiStateTransitionAnimation; 449 base::scoped_nsobject<BlinkScrollbarPartAnimation> 450 _expansionTransitionAnimation; 451 BOOL _hasExpandedSinceInvisible; 452} 453- (id)initWithScrollbar:(blink::Scrollbar*)scrollbar; 454- (void)updateVisibilityImmediately:(bool)show; 455- (void)cancelAnimations; 456@end 457 458@implementation BlinkScrollbarPainterDelegate 459 460- (id)initWithScrollbar:(blink::Scrollbar*)scrollbar { 461 self = [super init]; 462 if (!self) 463 return nil; 464 465 _scrollbar = scrollbar; 466 return self; 467} 468 469- (void)updateVisibilityImmediately:(bool)show { 470 [self cancelAnimations]; 471 [ScrollbarPainterForScrollbar(*_scrollbar) setKnobAlpha:(show ? 1.0 : 0.0)]; 472} 473 474- (void)cancelAnimations { 475 BEGIN_BLOCK_OBJC_EXCEPTIONS; 476 [_knobAlphaAnimation stopAnimation]; 477 [_trackAlphaAnimation stopAnimation]; 478 [_uiStateTransitionAnimation stopAnimation]; 479 [_expansionTransitionAnimation stopAnimation]; 480 END_BLOCK_OBJC_EXCEPTIONS; 481} 482 483- (blink::ScrollAnimatorMac&)scrollAnimator { 484 return static_cast<blink::ScrollAnimatorMac&>( 485 _scrollbar->GetScrollableArea()->GetScrollAnimator()); 486} 487 488- (NSRect)convertRectToBacking:(NSRect)aRect { 489 return aRect; 490} 491 492- (NSRect)convertRectFromBacking:(NSRect)aRect { 493 return aRect; 494} 495 496- (NSPoint)mouseLocationInScrollerForScrollerImp:(id)scrollerImp { 497 if (!_scrollbar) 498 return NSZeroPoint; 499 500 DCHECK_EQ(scrollerImp, ScrollbarPainterForScrollbar(*_scrollbar)); 501 502 return _scrollbar->ConvertFromContainingEmbeddedContentView( 503 _scrollbar->GetScrollableArea()->LastKnownMousePosition()); 504} 505 506- (void)setUpAlphaAnimation: 507 (base::scoped_nsobject<BlinkScrollbarPartAnimation>&) 508 scrollbarPartAnimation 509 scrollerPainter:(ScrollbarPainter)scrollerPainter 510 part:(blink::ScrollbarPart)part 511 animateAlphaTo:(CGFloat)newAlpha 512 duration:(NSTimeInterval)duration { 513 // If the user has scrolled the page, then the scrollbars must be animated 514 // here. 515 // This overrides the early returns. 516 bool mustAnimate = [self scrollAnimator].HaveScrolledSincePageLoad(); 517 518 if ([self scrollAnimator].ScrollbarPaintTimerIsActive() && !mustAnimate) 519 return; 520 521 if (_scrollbar->GetScrollableArea()->ShouldSuspendScrollAnimations() && 522 !mustAnimate) { 523 [self scrollAnimator].StartScrollbarPaintTimer(); 524 return; 525 } 526 527 // At this point, we are definitely going to animate now, so stop the timer. 528 [self scrollAnimator].StopScrollbarPaintTimer(); 529 530 // If we are currently animating, stop 531 if (scrollbarPartAnimation) { 532 [scrollbarPartAnimation stopAnimation]; 533 scrollbarPartAnimation.reset(); 534 } 535 536 scrollbarPartAnimation.reset([[BlinkScrollbarPartAnimation alloc] 537 initWithScrollbar:_scrollbar 538 featureToAnimate:part == blink::kThumbPart ? ThumbAlpha : TrackAlpha 539 animateFrom:part == blink::kThumbPart ? [scrollerPainter knobAlpha] 540 : [scrollerPainter trackAlpha] 541 animateTo:newAlpha 542 duration:duration]); 543 [scrollbarPartAnimation startAnimation]; 544} 545 546- (void)scrollerImp:(id)scrollerImp 547 animateKnobAlphaTo:(CGFloat)newKnobAlpha 548 duration:(NSTimeInterval)duration { 549 if (!_scrollbar) 550 return; 551 552 DCHECK_EQ(scrollerImp, ScrollbarPainterForScrollbar(*_scrollbar)); 553 554 ScrollbarPainter scrollerPainter = (ScrollbarPainter)scrollerImp; 555 [self setUpAlphaAnimation:_knobAlphaAnimation 556 scrollerPainter:scrollerPainter 557 part:blink::kThumbPart 558 animateAlphaTo:newKnobAlpha 559 duration:duration]; 560} 561 562- (void)scrollerImp:(id)scrollerImp 563 animateTrackAlphaTo:(CGFloat)newTrackAlpha 564 duration:(NSTimeInterval)duration { 565 if (!_scrollbar) 566 return; 567 568 DCHECK_EQ(scrollerImp, ScrollbarPainterForScrollbar(*_scrollbar)); 569 570 ScrollbarPainter scrollerPainter = (ScrollbarPainter)scrollerImp; 571 [self setUpAlphaAnimation:_trackAlphaAnimation 572 scrollerPainter:scrollerPainter 573 part:blink::kBackTrackPart 574 animateAlphaTo:newTrackAlpha 575 duration:duration]; 576} 577 578- (void)scrollerImp:(id)scrollerImp 579 animateUIStateTransitionWithDuration:(NSTimeInterval)duration { 580 if (!_scrollbar) 581 return; 582 583 if (!SupportsUIStateTransitionProgress()) 584 return; 585 586 DCHECK_EQ(scrollerImp, ScrollbarPainterForScrollbar(*_scrollbar)); 587 588 ScrollbarPainter scrollbarPainter = (ScrollbarPainter)scrollerImp; 589 590 // UIStateTransition always animates to 1. In case an animation is in progress 591 // this avoids a hard transition. 592 [scrollbarPainter 593 setUiStateTransitionProgress:1 - [scrollerImp uiStateTransitionProgress]]; 594 595 if (!_uiStateTransitionAnimation) 596 _uiStateTransitionAnimation.reset([[BlinkScrollbarPartAnimation alloc] 597 initWithScrollbar:_scrollbar 598 featureToAnimate:UIStateTransition 599 animateFrom:[scrollbarPainter uiStateTransitionProgress] 600 animateTo:1.0 601 duration:duration]); 602 else { 603 // If we don't need to initialize the animation, just reset the values in 604 // case they have changed. 605 [_uiStateTransitionAnimation 606 setStartValue:[scrollbarPainter uiStateTransitionProgress]]; 607 [_uiStateTransitionAnimation setEndValue:1.0]; 608 [_uiStateTransitionAnimation setDuration:duration]; 609 } 610 [_uiStateTransitionAnimation startAnimation]; 611} 612 613- (void)scrollerImp:(id)scrollerImp 614 animateExpansionTransitionWithDuration:(NSTimeInterval)duration { 615 if (!_scrollbar) 616 return; 617 618 if (!SupportsExpansionTransitionProgress()) 619 return; 620 621 DCHECK_EQ(scrollerImp, ScrollbarPainterForScrollbar(*_scrollbar)); 622 623 ScrollbarPainter scrollbarPainter = (ScrollbarPainter)scrollerImp; 624 625 // ExpansionTransition always animates to 1. In case an animation is in 626 // progress this avoids a hard transition. 627 [scrollbarPainter 628 setExpansionTransitionProgress:1 - 629 [scrollerImp expansionTransitionProgress]]; 630 631 if (!_expansionTransitionAnimation) { 632 _expansionTransitionAnimation.reset([[BlinkScrollbarPartAnimation alloc] 633 initWithScrollbar:_scrollbar 634 featureToAnimate:ExpansionTransition 635 animateFrom:[scrollbarPainter expansionTransitionProgress] 636 animateTo:1.0 637 duration:duration]); 638 } else { 639 // If we don't need to initialize the animation, just reset the values in 640 // case they have changed. 641 [_expansionTransitionAnimation 642 setStartValue:[scrollbarPainter uiStateTransitionProgress]]; 643 [_expansionTransitionAnimation setEndValue:1.0]; 644 [_expansionTransitionAnimation setDuration:duration]; 645 } 646 [_expansionTransitionAnimation startAnimation]; 647} 648 649- (void)scrollerImp:(id)scrollerImp 650 overlayScrollerStateChangedTo:(NSUInteger)newOverlayScrollerState { 651 // The names of these states are based on their observed behavior, and are not 652 // based on documentation. 653 enum { 654 NSScrollerStateInvisible = 0, 655 NSScrollerStateKnob = 1, 656 NSScrollerStateExpanded = 2 657 }; 658 // We do not receive notifications about the thumb un-expanding when the 659 // scrollbar fades away. Ensure 660 // that we re-paint the thumb the next time that we transition away from being 661 // invisible, so that 662 // the thumb doesn't stick in an expanded state. 663 if (newOverlayScrollerState == NSScrollerStateExpanded) { 664 _hasExpandedSinceInvisible = YES; 665 } else if (newOverlayScrollerState != NSScrollerStateInvisible && 666 _hasExpandedSinceInvisible) { 667 _scrollbar->SetNeedsPaintInvalidation(blink::kThumbPart); 668 _hasExpandedSinceInvisible = NO; 669 } 670} 671 672- (void)invalidate { 673 _scrollbar = 0; 674 BEGIN_BLOCK_OBJC_EXCEPTIONS; 675 [_knobAlphaAnimation invalidate]; 676 [_trackAlphaAnimation invalidate]; 677 [_uiStateTransitionAnimation invalidate]; 678 [_expansionTransitionAnimation invalidate]; 679 END_BLOCK_OBJC_EXCEPTIONS; 680} 681 682@end 683 684namespace blink { 685 686ScrollAnimatorBase* ScrollAnimatorBase::Create( 687 blink::ScrollableArea* scrollable_area) { 688 return MakeGarbageCollected<ScrollAnimatorMac>(scrollable_area); 689} 690 691ScrollAnimatorMac::ScrollAnimatorMac(blink::ScrollableArea* scrollable_area) 692 : ScrollAnimatorBase(scrollable_area), 693 task_runner_(ThreadScheduler::Current()->CompositorTaskRunner()), 694 have_scrolled_since_page_load_(false), 695 needs_scroller_style_update_(false) { 696 scroll_animation_helper_delegate_.reset( 697 [[BlinkScrollAnimationHelperDelegate alloc] initWithScrollAnimator:this]); 698 scroll_animation_helper_.reset([[NSClassFromString(@"NSScrollAnimationHelper") 699 alloc] initWithDelegate:scroll_animation_helper_delegate_]); 700 701 scrollbar_painter_controller_delegate_.reset( 702 [[BlinkScrollbarPainterControllerDelegate alloc] 703 initWithScrollableArea:scrollable_area]); 704 scrollbar_painter_controller_.reset( 705 [[[NSClassFromString(@"NSScrollerImpPair") alloc] init] autorelease], 706 base::scoped_policy::RETAIN); 707 [scrollbar_painter_controller_ 708 performSelector:@selector(setDelegate:) 709 withObject:scrollbar_painter_controller_delegate_]; 710 [scrollbar_painter_controller_ 711 setScrollerStyle:ScrollbarThemeMac::RecommendedScrollerStyle()]; 712} 713 714ScrollAnimatorMac::~ScrollAnimatorMac() {} 715 716void ScrollAnimatorMac::Dispose() { 717 BEGIN_BLOCK_OBJC_EXCEPTIONS; 718 ScrollbarPainter horizontal_scrollbar_painter = 719 [scrollbar_painter_controller_ horizontalScrollerImp]; 720 [horizontal_scrollbar_painter setDelegate:nil]; 721 722 ScrollbarPainter vertical_scrollbar_painter = 723 [scrollbar_painter_controller_ verticalScrollerImp]; 724 [vertical_scrollbar_painter setDelegate:nil]; 725 726 [scrollbar_painter_controller_delegate_ invalidate]; 727 [scrollbar_painter_controller_ setDelegate:nil]; 728 [horizontal_scrollbar_painter_delegate_ invalidate]; 729 [vertical_scrollbar_painter_delegate_ invalidate]; 730 [scroll_animation_helper_delegate_ invalidate]; 731 END_BLOCK_OBJC_EXCEPTIONS; 732 733 initial_scrollbar_paint_task_handle_.Cancel(); 734 send_content_area_scrolled_task_handle_.Cancel(); 735} 736 737ScrollResult ScrollAnimatorMac::UserScroll( 738 ScrollGranularity granularity, 739 const ScrollOffset& delta, 740 ScrollableArea::ScrollCallback on_finish) { 741 have_scrolled_since_page_load_ = true; 742 743 if (!scrollable_area_->ScrollAnimatorEnabled() || 744 granularity == ScrollGranularity::kScrollByPixel || 745 granularity == ScrollGranularity::kScrollByPrecisePixel) { 746 return ScrollAnimatorBase::UserScroll(granularity, delta, 747 std::move(on_finish)); 748 } 749 750 // TODO(lanwei): we should find when the animation finishes and run the 751 // callback after the animation finishes, see https://crbug.com/967842. 752 if (on_finish) 753 std::move(on_finish).Run(); 754 755 ScrollOffset consumed_delta = ComputeDeltaToConsume(delta); 756 ScrollOffset new_offset = current_offset_ + consumed_delta; 757 if (current_offset_ == new_offset) 758 return ScrollResult(); 759 760 // Prevent clobbering an existing animation on an unscrolled axis. 761 if ([scroll_animation_helper_ _isAnimating]) { 762 NSPoint target_origin = [scroll_animation_helper_ targetOrigin]; 763 if (!delta.Width()) 764 new_offset.SetWidth(target_origin.x); 765 if (!delta.Height()) 766 new_offset.SetHeight(target_origin.y); 767 } 768 769 NSPoint new_point = NSMakePoint(new_offset.Width(), new_offset.Height()); 770 [scroll_animation_helper_ scrollToPoint:new_point]; 771 772 // TODO(bokan): This has different semantics on ScrollResult than 773 // ScrollAnimator, 774 // which only returns unused delta if there's no animation and we don't start 775 // one. 776 return ScrollResult(consumed_delta.Width(), consumed_delta.Height(), 777 delta.Width() - consumed_delta.Width(), 778 delta.Height() - consumed_delta.Height()); 779} 780 781void ScrollAnimatorMac::ScrollToOffsetWithoutAnimation( 782 const ScrollOffset& offset) { 783 [scroll_animation_helper_ _stopRun]; 784 ImmediateScrollTo(offset); 785} 786 787ScrollOffset ScrollAnimatorMac::AdjustScrollOffsetIfNecessary( 788 const ScrollOffset& offset) const { 789 ScrollOffset min_offset = scrollable_area_->MinimumScrollOffset(); 790 ScrollOffset max_offset = scrollable_area_->MaximumScrollOffset(); 791 792 float new_x = clampTo<float, float>(offset.Width(), min_offset.Width(), 793 max_offset.Width()); 794 float new_y = clampTo<float, float>(offset.Height(), min_offset.Height(), 795 max_offset.Height()); 796 797 return ScrollOffset(new_x, new_y); 798} 799 800void ScrollAnimatorMac::ImmediateScrollTo(const ScrollOffset& new_offset) { 801 ScrollOffset adjusted_offset = AdjustScrollOffsetIfNecessary(new_offset); 802 803 bool offset_changed = adjusted_offset != current_offset_; 804 if (!offset_changed) 805 return; 806 807 ScrollOffset delta = adjusted_offset - current_offset_; 808 809 current_offset_ = adjusted_offset; 810 NotifyContentAreaScrolled(delta, mojom::blink::ScrollType::kUser); 811 NotifyOffsetChanged(); 812} 813 814void ScrollAnimatorMac::ImmediateScrollToOffsetForScrollAnimation( 815 const ScrollOffset& new_offset) { 816 DCHECK(scroll_animation_helper_); 817 ImmediateScrollTo(new_offset); 818} 819 820void ScrollAnimatorMac::ContentAreaWillPaint() const { 821 if (!GetScrollableArea()->ScrollbarsCanBeActive()) 822 return; 823 [scrollbar_painter_controller_ contentAreaWillDraw]; 824} 825 826void ScrollAnimatorMac::MouseEnteredContentArea() const { 827 if (!GetScrollableArea()->ScrollbarsCanBeActive()) 828 return; 829 [scrollbar_painter_controller_ mouseEnteredContentArea]; 830} 831 832void ScrollAnimatorMac::MouseExitedContentArea() const { 833 if (!GetScrollableArea()->ScrollbarsCanBeActive()) 834 return; 835 [scrollbar_painter_controller_ mouseExitedContentArea]; 836} 837 838void ScrollAnimatorMac::MouseMovedInContentArea() const { 839 if (!GetScrollableArea()->ScrollbarsCanBeActive()) 840 return; 841 [scrollbar_painter_controller_ mouseMovedInContentArea]; 842} 843 844void ScrollAnimatorMac::MouseEnteredScrollbar(Scrollbar& scrollbar) const { 845 if (!GetScrollableArea()->ScrollbarsCanBeActive()) 846 return; 847 848 if (!SupportsUIStateTransitionProgress()) 849 return; 850 if (ScrollbarPainter painter = ScrollbarPainterForScrollbar(scrollbar)) 851 [painter mouseEnteredScroller]; 852} 853 854void ScrollAnimatorMac::MouseExitedScrollbar(Scrollbar& scrollbar) const { 855 if (!GetScrollableArea()->ScrollbarsCanBeActive()) 856 return; 857 858 if (!SupportsUIStateTransitionProgress()) 859 return; 860 if (ScrollbarPainter painter = ScrollbarPainterForScrollbar(scrollbar)) 861 [painter mouseExitedScroller]; 862} 863 864void ScrollAnimatorMac::ContentsResized() const { 865 if (!GetScrollableArea()->ScrollbarsCanBeActive()) 866 return; 867 [scrollbar_painter_controller_ contentAreaDidResize]; 868} 869 870void ScrollAnimatorMac::ContentAreaDidShow() const { 871 if (!GetScrollableArea()->ScrollbarsCanBeActive()) 872 return; 873 [scrollbar_painter_controller_ windowOrderedIn]; 874} 875 876void ScrollAnimatorMac::ContentAreaDidHide() const { 877 if (!GetScrollableArea()->ScrollbarsCanBeActive()) 878 return; 879 [scrollbar_painter_controller_ windowOrderedOut]; 880} 881 882void ScrollAnimatorMac::FinishCurrentScrollAnimations() { 883 [scrollbar_painter_controller_ hideOverlayScrollers]; 884} 885 886void ScrollAnimatorMac::DidAddVerticalScrollbar(Scrollbar& scrollbar) { 887 ScrollbarPainter painter = ScrollbarPainterForScrollbar(scrollbar); 888 if (!painter) 889 return; 890 891 DCHECK(!vertical_scrollbar_painter_delegate_); 892 vertical_scrollbar_painter_delegate_.reset( 893 [[BlinkScrollbarPainterDelegate alloc] initWithScrollbar:&scrollbar]); 894 895 [painter setDelegate:vertical_scrollbar_painter_delegate_]; 896 [scrollbar_painter_controller_ setVerticalScrollerImp:painter]; 897} 898 899void ScrollAnimatorMac::WillRemoveVerticalScrollbar(Scrollbar& scrollbar) { 900 ScrollbarPainter painter = ScrollbarPainterForScrollbar(scrollbar); 901 DCHECK_EQ([scrollbar_painter_controller_ verticalScrollerImp], painter); 902 903 if (!painter) 904 DCHECK(!vertical_scrollbar_painter_delegate_); 905 906 [painter setDelegate:nil]; 907 [vertical_scrollbar_painter_delegate_ invalidate]; 908 vertical_scrollbar_painter_delegate_.reset(); 909 [scrollbar_painter_controller_ setVerticalScrollerImp:nil]; 910} 911 912void ScrollAnimatorMac::DidAddHorizontalScrollbar(Scrollbar& scrollbar) { 913 ScrollbarPainter painter = ScrollbarPainterForScrollbar(scrollbar); 914 if (!painter) 915 return; 916 917 DCHECK(!horizontal_scrollbar_painter_delegate_); 918 horizontal_scrollbar_painter_delegate_.reset( 919 [[BlinkScrollbarPainterDelegate alloc] initWithScrollbar:&scrollbar]); 920 921 [painter setDelegate:horizontal_scrollbar_painter_delegate_]; 922 [scrollbar_painter_controller_ setHorizontalScrollerImp:painter]; 923} 924 925void ScrollAnimatorMac::WillRemoveHorizontalScrollbar(Scrollbar& scrollbar) { 926 ScrollbarPainter painter = ScrollbarPainterForScrollbar(scrollbar); 927 DCHECK_EQ([scrollbar_painter_controller_ horizontalScrollerImp], painter); 928 929 if (!painter) 930 DCHECK(!horizontal_scrollbar_painter_delegate_); 931 932 [painter setDelegate:nil]; 933 [horizontal_scrollbar_painter_delegate_ invalidate]; 934 horizontal_scrollbar_painter_delegate_.reset(); 935 [scrollbar_painter_controller_ setHorizontalScrollerImp:nil]; 936} 937 938void ScrollAnimatorMac::NotifyContentAreaScrolled( 939 const ScrollOffset& delta, 940 mojom::blink::ScrollType scrollType) { 941 // This function is called when a page is going into the page cache, but the 942 // page 943 // isn't really scrolling in that case. We should only pass the message on to 944 // the 945 // ScrollbarPainterController when we're really scrolling on an active page. 946 if (IsExplicitScrollType(scrollType) && 947 GetScrollableArea()->ScrollbarsCanBeActive()) 948 SendContentAreaScrolledSoon(delta); 949} 950 951bool ScrollAnimatorMac::SetScrollbarsVisibleForTesting(bool show) { 952 if (show) 953 [scrollbar_painter_controller_ flashScrollers]; 954 else 955 [scrollbar_painter_controller_ hideOverlayScrollers]; 956 957 [vertical_scrollbar_painter_delegate_ updateVisibilityImmediately:show]; 958 [horizontal_scrollbar_painter_delegate_ updateVisibilityImmediately:show]; 959 return true; 960} 961 962void ScrollAnimatorMac::CancelAnimation() { 963 [scroll_animation_helper_ _stopRun]; 964 have_scrolled_since_page_load_ = false; 965} 966 967void ScrollAnimatorMac::UpdateScrollerStyle() { 968 if (!GetScrollableArea()->ScrollbarsCanBeActive()) { 969 needs_scroller_style_update_ = true; 970 return; 971 } 972 973 blink::ScrollbarThemeMac* mac_theme = 974 MacOverlayScrollbarTheme(scrollable_area_->GetPageScrollbarTheme()); 975 if (!mac_theme) { 976 needs_scroller_style_update_ = false; 977 return; 978 } 979 980 NSScrollerStyle new_style = [scrollbar_painter_controller_ scrollerStyle]; 981 982 if (Scrollbar* vertical_scrollbar = 983 GetScrollableArea()->VerticalScrollbar()) { 984 vertical_scrollbar->SetNeedsPaintInvalidation(kAllParts); 985 986 ScrollbarPainter old_vertical_painter = 987 [scrollbar_painter_controller_ verticalScrollerImp]; 988 ScrollbarPainter new_vertical_painter = [NSClassFromString(@"NSScrollerImp") 989 scrollerImpWithStyle:new_style 990 controlSize:NSRegularControlSize 991 horizontal:NO 992 replacingScrollerImp:old_vertical_painter]; 993 [old_vertical_painter setDelegate:nil]; 994 [new_vertical_painter setDelegate:vertical_scrollbar_painter_delegate_]; 995 [scrollbar_painter_controller_ setVerticalScrollerImp:new_vertical_painter]; 996 mac_theme->SetNewPainterForScrollbar(*vertical_scrollbar, 997 new_vertical_painter); 998 999 // The different scrollbar styles have different thicknesses, so we must 1000 // re-set the 1001 // frameRect to the new thickness, and the re-layout below will ensure the 1002 // offset 1003 // and length are properly updated. 1004 int thickness = 1005 mac_theme->ScrollbarThickness(vertical_scrollbar->ScaleFromDIP()); 1006 vertical_scrollbar->SetFrameRect(IntRect(0, 0, thickness, thickness)); 1007 } 1008 1009 if (Scrollbar* horizontal_scrollbar = 1010 GetScrollableArea()->HorizontalScrollbar()) { 1011 horizontal_scrollbar->SetNeedsPaintInvalidation(kAllParts); 1012 1013 ScrollbarPainter old_horizontal_painter = 1014 [scrollbar_painter_controller_ horizontalScrollerImp]; 1015 ScrollbarPainter new_horizontal_painter = 1016 [NSClassFromString(@"NSScrollerImp") 1017 scrollerImpWithStyle:new_style 1018 controlSize:NSRegularControlSize 1019 horizontal:YES 1020 replacingScrollerImp:old_horizontal_painter]; 1021 [old_horizontal_painter setDelegate:nil]; 1022 [new_horizontal_painter setDelegate:horizontal_scrollbar_painter_delegate_]; 1023 [scrollbar_painter_controller_ 1024 setHorizontalScrollerImp:new_horizontal_painter]; 1025 mac_theme->SetNewPainterForScrollbar(*horizontal_scrollbar, 1026 new_horizontal_painter); 1027 1028 // The different scrollbar styles have different thicknesses, so we must 1029 // re-set the 1030 // frameRect to the new thickness, and the re-layout below will ensure the 1031 // offset 1032 // and length are properly updated. 1033 int thickness = 1034 mac_theme->ScrollbarThickness(horizontal_scrollbar->ScaleFromDIP()); 1035 horizontal_scrollbar->SetFrameRect(IntRect(0, 0, thickness, thickness)); 1036 } 1037 1038 // If m_needsScrollerStyleUpdate is true, then the page is restoring from the 1039 // page cache, and 1040 // a relayout will happen on its own. Otherwise, we must initiate a re-layout 1041 // ourselves. 1042 if (!needs_scroller_style_update_) 1043 GetScrollableArea()->ScrollbarStyleChanged(); 1044 1045 needs_scroller_style_update_ = false; 1046} 1047 1048void ScrollAnimatorMac::StartScrollbarPaintTimer() { 1049 // Post a task with 1 ms delay to give a chance to run other immediate tasks 1050 // that may cancel this. 1051 initial_scrollbar_paint_task_handle_ = PostDelayedCancellableTask( 1052 *task_runner_, FROM_HERE, 1053 WTF::Bind(&ScrollAnimatorMac::InitialScrollbarPaintTask, 1054 WrapWeakPersistent(this)), 1055 base::TimeDelta::FromMilliseconds(1)); 1056} 1057 1058bool ScrollAnimatorMac::ScrollbarPaintTimerIsActive() const { 1059 return initial_scrollbar_paint_task_handle_.IsActive(); 1060} 1061 1062void ScrollAnimatorMac::StopScrollbarPaintTimer() { 1063 initial_scrollbar_paint_task_handle_.Cancel(); 1064} 1065 1066void ScrollAnimatorMac::InitialScrollbarPaintTask() { 1067 // To force the scrollbars to flash, we have to call hide first. Otherwise, 1068 // the ScrollbarPainterController 1069 // might think that the scrollbars are already showing and bail early. 1070 [scrollbar_painter_controller_ hideOverlayScrollers]; 1071 [scrollbar_painter_controller_ flashScrollers]; 1072} 1073 1074void ScrollAnimatorMac::SendContentAreaScrolledSoon(const ScrollOffset& delta) { 1075 content_area_scrolled_timer_scroll_delta_ = delta; 1076 1077 if (send_content_area_scrolled_task_handle_.IsActive()) 1078 return; 1079 send_content_area_scrolled_task_handle_ = PostCancellableTask( 1080 *task_runner_, FROM_HERE, 1081 WTF::Bind(&ScrollAnimatorMac::SendContentAreaScrolledTask, 1082 WrapWeakPersistent(this))); 1083} 1084 1085void ScrollAnimatorMac::SendContentAreaScrolledTask() { 1086 if (SupportsContentAreaScrolledInDirection()) { 1087 [scrollbar_painter_controller_ 1088 contentAreaScrolledInDirection: 1089 NSMakePoint(content_area_scrolled_timer_scroll_delta_.Width(), 1090 content_area_scrolled_timer_scroll_delta_.Height())]; 1091 content_area_scrolled_timer_scroll_delta_ = ScrollOffset(); 1092 } else 1093 [scrollbar_painter_controller_ contentAreaScrolled]; 1094} 1095 1096} // namespace blink 1097