712c449a0db6564df6ca807c0832d1de1a61f39f
[WebKit-https.git] / Source / WebCore / platform / mac / ScrollbarThemeMac.mm
1 /*
2  * Copyright (C) 2008-2017 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. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
24  */
25
26 #include "config.h"
27 #include "ScrollbarThemeMac.h"
28
29 #include "ColorMac.h"
30 #include "GraphicsLayer.h"
31 #include "ImageBuffer.h"
32 #include "LocalCurrentGraphicsContext.h"
33 #include "NSScrollerImpDetails.h"
34 #include "PlatformMouseEvent.h"
35 #include "ScrollAnimatorMac.h"
36 #include "ScrollView.h"
37 #include "WebCoreSystemInterface.h"
38 #include <Carbon/Carbon.h>
39 #include <pal/spi/cg/CoreGraphicsSPI.h>
40 #include <pal/spi/mac/NSScrollerImpSPI.h>
41 #include <wtf/BlockObjCExceptions.h>
42 #include <wtf/HashMap.h>
43 #include <wtf/NeverDestroyed.h>
44 #include <wtf/SetForScope.h>
45 #include <wtf/StdLibExtras.h>
46
47 // FIXME: There are repainting problems due to Aqua scroll bar buttons' visual overflow.
48
49 using namespace WebCore;
50
51 @interface NSColor (WebNSColorDetails)
52 + (NSImage *)_linenPatternImage;
53 @end
54
55 namespace WebCore {
56
57 typedef HashMap<Scrollbar*, RetainPtr<NSScrollerImp>> ScrollerImpMap;
58
59 static ScrollerImpMap* scrollbarMap()
60 {
61     static ScrollerImpMap* map = new ScrollerImpMap;
62     return map;
63 }
64
65 }
66
67 @interface WebScrollbarPrefsObserver : NSObject
68 {
69 }
70
71 + (void)registerAsObserver;
72 + (void)appearancePrefsChanged:(NSNotification *)theNotification;
73 + (void)behaviorPrefsChanged:(NSNotification *)theNotification;
74
75 @end
76
77 @implementation WebScrollbarPrefsObserver
78
79 + (void)appearancePrefsChanged:(NSNotification *)unusedNotification
80 {
81     UNUSED_PARAM(unusedNotification);
82
83     ScrollbarTheme& theme = ScrollbarTheme::theme();
84     if (theme.isMockTheme())
85         return;
86
87     static_cast<ScrollbarThemeMac&>(theme).preferencesChanged();
88     if (scrollbarMap()->isEmpty())
89         return;
90     ScrollerImpMap::iterator end = scrollbarMap()->end();
91     for (ScrollerImpMap::iterator it = scrollbarMap()->begin(); it != end; ++it) {
92         it->key->styleChanged();
93         it->key->invalidate();
94     }
95 }
96
97 + (void)behaviorPrefsChanged:(NSNotification *)unusedNotification
98 {
99     UNUSED_PARAM(unusedNotification);
100
101     ScrollbarTheme& theme = ScrollbarTheme::theme();
102     if (theme.isMockTheme())
103         return;
104
105     static_cast<ScrollbarThemeMac&>(theme).preferencesChanged();
106 }
107
108 + (void)registerAsObserver
109 {
110     [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(appearancePrefsChanged:) name:@"AppleAquaScrollBarVariantChanged" object:nil suspensionBehavior:NSNotificationSuspensionBehaviorDeliverImmediately];
111     [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(behaviorPrefsChanged:) name:@"AppleNoRedisplayAppearancePreferenceChanged" object:nil suspensionBehavior:NSNotificationSuspensionBehaviorCoalesce];
112     [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(behaviorPrefsChanged:) name:NSPreferredScrollerStyleDidChangeNotification object:nil];
113 }
114
115 @end
116
117 namespace WebCore {
118
119 ScrollbarTheme& ScrollbarTheme::nativeTheme()
120 {
121     static NeverDestroyed<ScrollbarThemeMac> theme;
122     return theme;
123 }
124
125 // FIXME: Get these numbers from CoreUI.
126 static const int cRealButtonLength[] = { 28, 21 };
127 static const int cButtonHitInset[] = { 3, 2 };
128 // cRealButtonLength - cButtonInset
129 static const int cButtonLength[] = { 14, 10 };
130
131 static const int cOuterButtonLength[] = { 16, 14 }; // The outer button in a double button pair is a bit bigger.
132 static const int cOuterButtonOverlap = 2;
133
134 static Seconds gInitialButtonDelay { 500_ms };
135 static Seconds gAutoscrollButtonDelay { 50_ms };
136 static bool gJumpOnTrackClick = false;
137 static bool gUsesOverlayScrollbars = false;
138
139 static ScrollbarButtonsPlacement gButtonPlacement = ScrollbarButtonsDoubleEnd;
140
141 static NSControlSize scrollbarControlSizeToNSControlSize(ScrollbarControlSize controlSize)
142 {
143     switch (controlSize) {
144     case RegularScrollbar:
145         return NSControlSizeRegular;
146     case SmallScrollbar:
147         return NSControlSizeSmall;
148     }
149
150     ASSERT_NOT_REACHED();
151     return NSControlSizeRegular;
152 }
153
154 void ScrollbarThemeMac::didCreateScrollerImp(Scrollbar& scrollbar)
155 {
156 #if PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101200
157     NSScrollerImp *scrollerImp = painterForScrollbar(scrollbar);
158     ASSERT(scrollerImp);
159     scrollerImp.userInterfaceLayoutDirection = scrollbar.scrollableArea().shouldPlaceBlockDirectionScrollbarOnLeft() ? NSUserInterfaceLayoutDirectionRightToLeft : NSUserInterfaceLayoutDirectionLeftToRight;
160 #else
161     UNUSED_PARAM(scrollbar);
162 #endif
163 }
164
165 void ScrollbarThemeMac::registerScrollbar(Scrollbar& scrollbar)
166 {
167     if (scrollbar.isCustomScrollbar())
168         return;
169
170     bool isHorizontal = scrollbar.orientation() == HorizontalScrollbar;
171     NSScrollerImp *scrollerImp = [NSScrollerImp scrollerImpWithStyle:recommendedScrollerStyle() controlSize:scrollbarControlSizeToNSControlSize(scrollbar.controlSize()) horizontal:isHorizontal replacingScrollerImp:nil];
172     scrollbarMap()->add(&scrollbar, scrollerImp);
173     didCreateScrollerImp(scrollbar);
174     updateEnabledState(scrollbar);
175     updateScrollbarOverlayStyle(scrollbar);
176 }
177
178 void ScrollbarThemeMac::unregisterScrollbar(Scrollbar& scrollbar)
179 {
180     scrollbarMap()->remove(&scrollbar);
181 }
182
183 void ScrollbarThemeMac::setNewPainterForScrollbar(Scrollbar& scrollbar, NSScrollerImp *newPainter)
184 {
185     scrollbarMap()->set(&scrollbar, newPainter);
186     updateEnabledState(scrollbar);
187     updateScrollbarOverlayStyle(scrollbar);
188 }
189
190 NSScrollerImp *ScrollbarThemeMac::painterForScrollbar(Scrollbar& scrollbar)
191 {
192     return scrollbarMap()->get(&scrollbar).get();
193 }
194
195 bool ScrollbarThemeMac::isLayoutDirectionRTL(Scrollbar& scrollbar)
196 {
197 #if PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101200
198     NSScrollerImp *scrollerImp = painterForScrollbar(scrollbar);
199     ASSERT(scrollerImp);
200     return scrollerImp.userInterfaceLayoutDirection == NSUserInterfaceLayoutDirectionRightToLeft;
201 #else
202     UNUSED_PARAM(scrollbar);
203     return false;
204 #endif
205 }
206
207 static bool g_isCurrentlyDrawingIntoLayer;
208     
209 bool ScrollbarThemeMac::isCurrentlyDrawingIntoLayer()
210 {
211     return g_isCurrentlyDrawingIntoLayer;
212 }
213
214 void ScrollbarThemeMac::setIsCurrentlyDrawingIntoLayer(bool b)
215 {
216     g_isCurrentlyDrawingIntoLayer = b;
217 }
218
219 ScrollbarThemeMac::ScrollbarThemeMac()
220 {
221     static bool initialized;
222     if (!initialized) {
223         initialized = true;
224         gButtonPlacement = ScrollbarButtonsNone;
225         [WebScrollbarPrefsObserver registerAsObserver];
226         preferencesChanged();
227     }
228 }
229
230 ScrollbarThemeMac::~ScrollbarThemeMac()
231 {
232 }
233
234 void ScrollbarThemeMac::preferencesChanged()
235 {
236     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
237     [defaults synchronize];
238     gInitialButtonDelay = Seconds { [defaults floatForKey:@"NSScrollerButtonDelay"] };
239     gAutoscrollButtonDelay = Seconds { [defaults floatForKey:@"NSScrollerButtonPeriod"] };
240     gJumpOnTrackClick = [defaults boolForKey:@"AppleScrollerPagingBehavior"];
241     usesOverlayScrollbarsChanged();
242 }
243
244 int ScrollbarThemeMac::scrollbarThickness(ScrollbarControlSize controlSize, ScrollbarExpansionState expansionState)
245 {
246     BEGIN_BLOCK_OBJC_EXCEPTIONS;
247     NSScrollerImp *scrollerImp = [NSScrollerImp scrollerImpWithStyle:recommendedScrollerStyle() controlSize:scrollbarControlSizeToNSControlSize(controlSize) horizontal:NO replacingScrollerImp:nil];
248     [scrollerImp setExpanded:(expansionState == ScrollbarExpansionState::Expanded)];
249     return [scrollerImp trackBoxWidth];
250     END_BLOCK_OBJC_EXCEPTIONS;
251 }
252
253 bool ScrollbarThemeMac::usesOverlayScrollbars() const
254 {
255     return gUsesOverlayScrollbars;
256 }
257
258 void ScrollbarThemeMac::usesOverlayScrollbarsChanged()
259 {
260     gUsesOverlayScrollbars = recommendedScrollerStyle() == NSScrollerStyleOverlay;
261 }
262
263 void ScrollbarThemeMac::updateScrollbarOverlayStyle(Scrollbar& scrollbar)
264 {
265     BEGIN_BLOCK_OBJC_EXCEPTIONS;
266     NSScrollerImp *painter = painterForScrollbar(scrollbar);
267     switch (scrollbar.scrollableArea().scrollbarOverlayStyle()) {
268     case ScrollbarOverlayStyleDefault:
269         [painter setKnobStyle:NSScrollerKnobStyleDefault];
270         break;
271     case ScrollbarOverlayStyleDark:
272         [painter setKnobStyle:NSScrollerKnobStyleDark];
273         break;
274     case ScrollbarOverlayStyleLight:
275         [painter setKnobStyle:NSScrollerKnobStyleLight];
276         break;
277     }
278     END_BLOCK_OBJC_EXCEPTIONS;
279 }
280
281 Seconds ScrollbarThemeMac::initialAutoscrollTimerDelay()
282 {
283     return gInitialButtonDelay;
284 }
285
286 Seconds ScrollbarThemeMac::autoscrollTimerDelay()
287 {
288     return gAutoscrollButtonDelay;
289 }
290     
291 ScrollbarButtonsPlacement ScrollbarThemeMac::buttonsPlacement() const
292 {
293     return gButtonPlacement;
294 }
295
296 bool ScrollbarThemeMac::hasButtons(Scrollbar& scrollbar)
297 {
298     return scrollbar.enabled() && buttonsPlacement() != ScrollbarButtonsNone
299              && (scrollbar.orientation() == HorizontalScrollbar
300              ? scrollbar.width()
301              : scrollbar.height()) >= 2 * (cRealButtonLength[scrollbar.controlSize()] - cButtonHitInset[scrollbar.controlSize()]);
302 }
303
304 bool ScrollbarThemeMac::hasThumb(Scrollbar& scrollbar)
305 {
306     int minLengthForThumb;
307
308     NSScrollerImp *painter = scrollbarMap()->get(&scrollbar).get();
309     minLengthForThumb = [painter knobMinLength] + [painter trackOverlapEndInset] + [painter knobOverlapEndInset]
310         + 2 * ([painter trackEndInset] + [painter knobEndInset]);
311
312     return scrollbar.enabled() && (scrollbar.orientation() == HorizontalScrollbar ?
313              scrollbar.width() :
314              scrollbar.height()) >= minLengthForThumb;
315 }
316
317 static IntRect buttonRepaintRect(const IntRect& buttonRect, ScrollbarOrientation orientation, ScrollbarControlSize controlSize, bool start)
318 {
319     ASSERT(gButtonPlacement != ScrollbarButtonsNone);
320
321     IntRect paintRect(buttonRect);
322     if (orientation == HorizontalScrollbar) {
323         paintRect.setWidth(cRealButtonLength[controlSize]);
324         if (!start)
325             paintRect.setX(buttonRect.x() - (cRealButtonLength[controlSize] - buttonRect.width()));
326     } else {
327         paintRect.setHeight(cRealButtonLength[controlSize]);
328         if (!start)
329             paintRect.setY(buttonRect.y() - (cRealButtonLength[controlSize] - buttonRect.height()));
330     }
331
332     return paintRect;
333 }
334
335 IntRect ScrollbarThemeMac::backButtonRect(Scrollbar& scrollbar, ScrollbarPart part, bool painting)
336 {
337     IntRect result;
338     
339     if (part == BackButtonStartPart && (buttonsPlacement() == ScrollbarButtonsNone || buttonsPlacement() == ScrollbarButtonsDoubleEnd))
340         return result;
341     
342     if (part == BackButtonEndPart && (buttonsPlacement() == ScrollbarButtonsNone || buttonsPlacement() == ScrollbarButtonsDoubleStart || buttonsPlacement() == ScrollbarButtonsSingle))
343         return result;
344         
345     int thickness = scrollbarThickness(scrollbar.controlSize());
346     bool outerButton = part == BackButtonStartPart && (buttonsPlacement() == ScrollbarButtonsDoubleStart || buttonsPlacement() == ScrollbarButtonsDoubleBoth);
347     if (outerButton) {
348         if (scrollbar.orientation() == HorizontalScrollbar)
349             result = IntRect(scrollbar.x(), scrollbar.y(), cOuterButtonLength[scrollbar.controlSize()] + (painting ? cOuterButtonOverlap : 0), thickness);
350         else
351             result = IntRect(scrollbar.x(), scrollbar.y(), thickness, cOuterButtonLength[scrollbar.controlSize()] + (painting ? cOuterButtonOverlap : 0));
352         return result;
353     }
354     
355     // Our repaint rect is slightly larger, since we are a button that is adjacent to the track.
356     if (scrollbar.orientation() == HorizontalScrollbar) {
357         int start = part == BackButtonStartPart ? scrollbar.x() : scrollbar.x() + scrollbar.width() - cOuterButtonLength[scrollbar.controlSize()] - cButtonLength[scrollbar.controlSize()];
358         result = IntRect(start, scrollbar.y(), cButtonLength[scrollbar.controlSize()], thickness);
359     } else {
360         int start = part == BackButtonStartPart ? scrollbar.y() : scrollbar.y() + scrollbar.height() - cOuterButtonLength[scrollbar.controlSize()] - cButtonLength[scrollbar.controlSize()];
361         result = IntRect(scrollbar.x(), start, thickness, cButtonLength[scrollbar.controlSize()]);
362     }
363     
364     if (painting)
365         return buttonRepaintRect(result, scrollbar.orientation(), scrollbar.controlSize(), part == BackButtonStartPart);
366     return result;
367 }
368
369 IntRect ScrollbarThemeMac::forwardButtonRect(Scrollbar& scrollbar, ScrollbarPart part, bool painting)
370 {
371     IntRect result;
372     
373     if (part == ForwardButtonEndPart && (buttonsPlacement() == ScrollbarButtonsNone || buttonsPlacement() == ScrollbarButtonsDoubleStart))
374         return result;
375     
376     if (part == ForwardButtonStartPart && (buttonsPlacement() == ScrollbarButtonsNone || buttonsPlacement() == ScrollbarButtonsDoubleEnd || buttonsPlacement() == ScrollbarButtonsSingle))
377         return result;
378         
379     int thickness = scrollbarThickness(scrollbar.controlSize());
380     int outerButtonLength = cOuterButtonLength[scrollbar.controlSize()];
381     int buttonLength = cButtonLength[scrollbar.controlSize()];
382     
383     bool outerButton = part == ForwardButtonEndPart && (buttonsPlacement() == ScrollbarButtonsDoubleEnd || buttonsPlacement() == ScrollbarButtonsDoubleBoth);
384     if (outerButton) {
385         if (scrollbar.orientation() == HorizontalScrollbar) {
386             result = IntRect(scrollbar.x() + scrollbar.width() - outerButtonLength, scrollbar.y(), outerButtonLength, thickness);
387             if (painting)
388                 result.inflateX(cOuterButtonOverlap);
389         } else {
390             result = IntRect(scrollbar.x(), scrollbar.y() + scrollbar.height() - outerButtonLength, thickness, outerButtonLength);
391             if (painting)
392                 result.inflateY(cOuterButtonOverlap);
393         }
394         return result;
395     }
396     
397     if (scrollbar.orientation() == HorizontalScrollbar) {
398         int start = part == ForwardButtonEndPart ? scrollbar.x() + scrollbar.width() - buttonLength : scrollbar.x() + outerButtonLength;
399         result = IntRect(start, scrollbar.y(), buttonLength, thickness);
400     } else {
401         int start = part == ForwardButtonEndPart ? scrollbar.y() + scrollbar.height() - buttonLength : scrollbar.y() + outerButtonLength;
402         result = IntRect(scrollbar.x(), start, thickness, buttonLength);
403     }
404     if (painting)
405         return buttonRepaintRect(result, scrollbar.orientation(), scrollbar.controlSize(), part == ForwardButtonStartPart);
406     return result;
407 }
408
409 IntRect ScrollbarThemeMac::trackRect(Scrollbar& scrollbar, bool painting)
410 {
411     if (painting || !hasButtons(scrollbar))
412         return scrollbar.frameRect();
413     
414     IntRect result;
415     int thickness = scrollbarThickness(scrollbar.controlSize());
416     int startWidth = 0;
417     int endWidth = 0;
418     int outerButtonLength = cOuterButtonLength[scrollbar.controlSize()];
419     int buttonLength = cButtonLength[scrollbar.controlSize()];
420     int doubleButtonLength = outerButtonLength + buttonLength;
421     switch (buttonsPlacement()) {
422         case ScrollbarButtonsSingle:
423             startWidth = buttonLength;
424             endWidth = buttonLength;
425             break;
426         case ScrollbarButtonsDoubleStart:
427             startWidth = doubleButtonLength;
428             break;
429         case ScrollbarButtonsDoubleEnd:
430             endWidth = doubleButtonLength;
431             break;
432         case ScrollbarButtonsDoubleBoth:
433             startWidth = doubleButtonLength;
434             endWidth = doubleButtonLength;
435             break;
436         default:
437             break;
438     }
439     
440     int totalWidth = startWidth + endWidth;
441     if (scrollbar.orientation() == HorizontalScrollbar)
442         return IntRect(scrollbar.x() + startWidth, scrollbar.y(), scrollbar.width() - totalWidth, thickness);
443     return IntRect(scrollbar.x(), scrollbar.y() + startWidth, thickness, scrollbar.height() - totalWidth);
444 }
445
446 int ScrollbarThemeMac::minimumThumbLength(Scrollbar& scrollbar)
447 {
448     BEGIN_BLOCK_OBJC_EXCEPTIONS;
449     return [scrollbarMap()->get(&scrollbar) knobMinLength];
450     END_BLOCK_OBJC_EXCEPTIONS;
451 }
452
453 static bool shouldCenterOnThumb(const PlatformMouseEvent& evt)
454 {
455     if (evt.button() != LeftButton)
456         return false;
457     if (gJumpOnTrackClick)
458         return !evt.altKey();
459     return evt.altKey();
460 }
461
462 ScrollbarButtonPressAction ScrollbarThemeMac::handleMousePressEvent(Scrollbar&, const PlatformMouseEvent& event, ScrollbarPart pressedPart)
463 {
464     if (event.button() == RightButton)
465         return ScrollbarButtonPressAction::None;
466
467     switch (pressedPart) {
468     case BackTrackPart:
469     case ForwardTrackPart:
470         if (shouldCenterOnThumb(event))
471             return ScrollbarButtonPressAction::CenterOnThumb;
472         break;
473     case ThumbPart:
474         return ScrollbarButtonPressAction::StartDrag;
475     default:
476         break;
477     }
478
479     return ScrollbarButtonPressAction::Scroll;
480 }
481
482 bool ScrollbarThemeMac::shouldDragDocumentInsteadOfThumb(Scrollbar&, const PlatformMouseEvent& event)
483 {
484     return event.altKey();
485 }
486
487 int ScrollbarThemeMac::scrollbarPartToHIPressedState(ScrollbarPart part)
488 {
489     switch (part) {
490         case BackButtonStartPart:
491             return kThemeTopOutsideArrowPressed;
492         case BackButtonEndPart:
493             return kThemeTopOutsideArrowPressed; // This does not make much sense.  For some reason the outside constant is required.
494         case ForwardButtonStartPart:
495             return kThemeTopInsideArrowPressed;
496         case ForwardButtonEndPart:
497             return kThemeBottomOutsideArrowPressed;
498         case ThumbPart:
499             return kThemeThumbPressed;
500         default:
501             return 0;
502     }
503 }
504
505 void ScrollbarThemeMac::updateEnabledState(Scrollbar& scrollbar)
506 {
507     BEGIN_BLOCK_OBJC_EXCEPTIONS;
508     [scrollbarMap()->get(&scrollbar) setEnabled:scrollbar.enabled()];
509     END_BLOCK_OBJC_EXCEPTIONS;
510 }
511
512 void ScrollbarThemeMac::setPaintCharacteristicsForScrollbar(Scrollbar& scrollbar)
513 {
514     BEGIN_BLOCK_OBJC_EXCEPTIONS;
515     NSScrollerImp *painter = painterForScrollbar(scrollbar);
516
517     float value;
518     float overhang;
519     ScrollableArea::computeScrollbarValueAndOverhang(scrollbar.currentPos(), scrollbar.totalSize(), scrollbar.visibleSize(), value, overhang);
520     float proportion = scrollbar.totalSize() > 0 ? (static_cast<CGFloat>(scrollbar.visibleSize()) - overhang) / scrollbar.totalSize() : 1;
521
522     [painter setEnabled:scrollbar.enabled()];
523     [painter setBoundsSize:scrollbar.frameRect().size()];
524     [painter setDoubleValue:value];
525 #if ENABLE(ASYNC_SCROLLING) && PLATFORM(MAC)
526     [painter setPresentationValue:value];
527 #endif
528     [painter setKnobProportion:proportion];
529     END_BLOCK_OBJC_EXCEPTIONS;
530 }
531
532 static void scrollerImpPaint(NSScrollerImp *scrollerImp, bool enabled)
533 {
534     BEGIN_BLOCK_OBJC_EXCEPTIONS;
535     // Use rectForPart: here; it will take the expansion transition progress into account.
536     NSRect trackRect = [scrollerImp rectForPart:NSScrollerKnobSlot];
537     [scrollerImp drawKnobSlotInRect:trackRect highlight:NO];
538
539     // If the scrollbar is not enabled, then there is nothing to scroll to, and we shouldn't
540     // call drawKnob.
541     if (enabled)
542         [scrollerImp drawKnob];
543     END_BLOCK_OBJC_EXCEPTIONS;
544 }
545
546 bool ScrollbarThemeMac::paint(Scrollbar& scrollbar, GraphicsContext& context, const IntRect& damageRect)
547 {
548     setPaintCharacteristicsForScrollbar(scrollbar);
549
550     if (scrollbar.supportsUpdateOnSecondaryThread())
551         return true;
552
553     SetForScope<bool> isCurrentlyDrawingIntoLayer(g_isCurrentlyDrawingIntoLayer, context.isCALayerContext());
554     
555     GraphicsContextStateSaver stateSaver(context);
556     context.clip(damageRect);
557     context.translate(scrollbar.frameRect().location());
558     LocalCurrentGraphicsContext localContext(context);
559     scrollerImpPaint(scrollbarMap()->get(&scrollbar).get(), scrollbar.enabled());
560
561     return true;
562 }
563
564 #if ENABLE(RUBBER_BANDING)
565 static RetainPtr<CGColorRef> linenBackgroundColor()
566 {
567     NSImage *image = nil;
568     CGImageRef cgImage = nullptr;
569     BEGIN_BLOCK_OBJC_EXCEPTIONS;
570     image = [NSColor _linenPatternImage];
571     cgImage = [image CGImageForProposedRect:NULL context:NULL hints:nil];
572     END_BLOCK_OBJC_EXCEPTIONS;
573     
574     if (!cgImage)
575         return nullptr;
576
577     RetainPtr<CGPatternRef> pattern = adoptCF(CGPatternCreateWithImage2(cgImage, CGAffineTransformIdentity, kCGPatternTilingNoDistortion));
578     RetainPtr<CGColorSpaceRef> colorSpace = adoptCF(CGColorSpaceCreatePattern(0));
579
580     const CGFloat alpha = 1.0;
581     return adoptCF(CGColorCreateWithPattern(colorSpace.get(), pattern.get(), &alpha));
582 }
583
584 void ScrollbarThemeMac::setUpOverhangAreaBackground(CALayer *layer, const Color& customBackgroundColor)
585 {
586     static CGColorRef cachedLinenBackgroundColor = linenBackgroundColor().leakRef();
587     // We operate on the CALayer directly here, since GraphicsLayer doesn't have the concept
588     // of pattern images, and we know that WebCore won't touch this layer.
589     layer.backgroundColor = customBackgroundColor.isValid() ? cachedCGColor(customBackgroundColor) : cachedLinenBackgroundColor;
590 }
591
592 void ScrollbarThemeMac::removeOverhangAreaBackground(CALayer *layer)
593 {
594     layer.backgroundColor = nil;
595 }
596
597 void ScrollbarThemeMac::setUpOverhangAreaShadow(CALayer *layer)
598 {
599     static const CGFloat shadowOpacity = 0.66;
600     static const CGFloat shadowRadius = 3;
601
602     // We only need to set these shadow properties once.
603     if (!layer.shadowOpacity) {
604         layer.shadowColor = CGColorGetConstantColor(kCGColorBlack);
605         layer.shadowOffset = CGSizeZero;
606         layer.shadowOpacity = shadowOpacity;
607         layer.shadowRadius = shadowRadius;
608     }
609
610     RetainPtr<CGPathRef> shadowPath = adoptCF(CGPathCreateWithRect(layer.bounds, NULL));
611     layer.shadowPath = shadowPath.get();
612 }
613
614 void ScrollbarThemeMac::removeOverhangAreaShadow(CALayer *layer)
615 {
616     layer.shadowPath = nil;
617     layer.shadowOpacity = 0;
618 }
619
620 void ScrollbarThemeMac::setUpOverhangAreasLayerContents(GraphicsLayer* graphicsLayer, const Color& customBackgroundColor)
621 {
622     ScrollbarThemeMac::setUpOverhangAreaBackground(graphicsLayer->platformLayer(), customBackgroundColor);
623 }
624
625 void ScrollbarThemeMac::setUpContentShadowLayer(GraphicsLayer* graphicsLayer)
626 {
627     // We operate on the CALayer directly here, since GraphicsLayer doesn't have the concept
628     // of shadows, and we know that WebCore won't touch this layer.
629     setUpOverhangAreaShadow(graphicsLayer->platformLayer());
630 }
631 #endif
632
633 } // namespace WebCore