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