Don't use (Details) when exposing SPI
[WebKit-https.git] / Source / WebCore / platform / mac / ThemeMac.mm
1 /*
2  * Copyright (C) 2008, 2010, 2011, 2012 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 #import "config.h"
27 #import "ThemeMac.h"
28
29 #import "AXObjectCache.h"
30 #import "BlockExceptions.h"
31 #import "GraphicsContext.h"
32 #import "ImageBuffer.h"
33 #import "LocalCurrentGraphicsContext.h"
34 #import "NSButtonCellSPI.h"
35 #import "ScrollView.h"
36 #import "WebCoreSystemInterface.h"
37 #import <Carbon/Carbon.h>
38 #import <wtf/StdLibExtras.h>
39
40 static NSRect focusRingClipRect;
41 static BOOL themeWindowHasKeyAppearance;
42
43 @interface WebCoreThemeWindow : NSWindow
44 @end
45
46 @implementation WebCoreThemeWindow
47
48 - (BOOL)hasKeyAppearance
49 {
50     return themeWindowHasKeyAppearance;
51 }
52
53 - (BOOL)isKeyWindow
54 {
55     return themeWindowHasKeyAppearance;
56 }
57
58 @end
59
60 @interface WebCoreThemeView : NSControl
61 @end
62
63 @implementation WebCoreThemeView
64
65 - (NSWindow *)window
66 {
67     // Using defer:YES prevents us from wasting any window server resources for this window, since we're not actually
68     // going to draw into it. The other arguments match what you get when calling -[NSWindow init].
69     static WebCoreThemeWindow *window = [[WebCoreThemeWindow alloc] initWithContentRect:NSMakeRect(100, 100, 100, 100)
70         styleMask:NSTitledWindowMask backing:NSBackingStoreBuffered defer:YES];
71     return window;
72 }
73
74 - (BOOL)isFlipped
75 {
76     return YES;
77 }
78
79 - (NSText *)currentEditor
80 {
81     return nil;
82 }
83
84 - (BOOL)_automaticFocusRingDisabled
85 {
86     return YES;
87 }
88
89 - (NSRect)_focusRingVisibleRect
90 {
91     if (NSIsEmptyRect(focusRingClipRect))
92         return [self visibleRect];
93     return focusRingClipRect;
94 }
95
96 - (NSView *)_focusRingClipAncestor
97 {
98     return self;
99 }
100
101 - (void)addSubview:(NSView*)subview
102 {
103     // By doing nothing in this method we forbid controls from adding subviews.
104     // This tells AppKit to not use Layer-backed animation for control rendering.
105     UNUSED_PARAM(subview);
106 }
107
108 @end
109
110 @implementation NSFont (WebCoreTheme)
111
112 - (NSString*)webCoreFamilyName
113 {
114     if ([[self familyName] hasPrefix:@"."])
115         return [self fontName];
116
117     return [self familyName];
118 }
119
120 @end
121
122 // FIXME: Default buttons really should be more like push buttons and not like buttons.
123
124 namespace WebCore {
125
126 enum {
127     topMargin,
128     rightMargin,
129     bottomMargin,
130     leftMargin
131 };
132
133 Theme* platformTheme()
134 {
135     DEPRECATED_DEFINE_STATIC_LOCAL(ThemeMac, themeMac, ());
136     return &themeMac;
137 }
138
139 // Helper functions used by a bunch of different control parts.
140
141 static NSControlSize controlSizeForFont(const FontCascade& font)
142 {
143     int fontSize = font.pixelSize();
144     if (fontSize >= 16)
145         return NSRegularControlSize;
146     if (fontSize >= 11)
147         return NSSmallControlSize;
148     return NSMiniControlSize;
149 }
150
151 static LengthSize sizeFromNSControlSize(NSControlSize nsControlSize, const LengthSize& zoomedSize, float zoomFactor, const std::array<IntSize, 3>& sizes)
152 {
153     IntSize controlSize = sizes[nsControlSize];
154     if (zoomFactor != 1.0f)
155         controlSize = IntSize(controlSize.width() * zoomFactor, controlSize.height() * zoomFactor);
156     LengthSize result = zoomedSize;
157     if (zoomedSize.width().isIntrinsicOrAuto() && controlSize.width() > 0)
158         result.setWidth(Length(controlSize.width(), Fixed));
159     if (zoomedSize.height().isIntrinsicOrAuto() && controlSize.height() > 0)
160         result.setHeight(Length(controlSize.height(), Fixed));
161     return result;
162 }
163
164 static LengthSize sizeFromFont(const FontCascade& font, const LengthSize& zoomedSize, float zoomFactor, const std::array<IntSize, 3>& sizes)
165 {
166     return sizeFromNSControlSize(controlSizeForFont(font), zoomedSize, zoomFactor, sizes);
167 }
168
169 static ControlSize controlSizeFromPixelSize(const std::array<IntSize, 3>& sizes, const IntSize& minZoomedSize, float zoomFactor)
170 {
171     if (minZoomedSize.width() >= static_cast<int>(sizes[NSRegularControlSize].width() * zoomFactor) &&
172         minZoomedSize.height() >= static_cast<int>(sizes[NSRegularControlSize].height() * zoomFactor))
173         return NSRegularControlSize;
174     if (minZoomedSize.width() >= static_cast<int>(sizes[NSSmallControlSize].width() * zoomFactor) &&
175         minZoomedSize.height() >= static_cast<int>(sizes[NSSmallControlSize].height() * zoomFactor))
176         return NSSmallControlSize;
177     return NSMiniControlSize;
178 }
179
180 static void setControlSize(NSCell* cell, const std::array<IntSize, 3>& sizes, const IntSize& minZoomedSize, float zoomFactor)
181 {
182     ControlSize size = controlSizeFromPixelSize(sizes, minZoomedSize, zoomFactor);
183     if (size != [cell controlSize]) // Only update if we have to, since AppKit does work even if the size is the same.
184         [cell setControlSize:(NSControlSize)size];
185 }
186
187 static void updateStates(NSCell* cell, const ControlStates* controlStates, bool useAnimation = false)
188 {
189 #if __MAC_OS_X_VERSION_MIN_REQUIRED < 101000
190     UNUSED_PARAM(useAnimation);
191 #endif
192
193     // The animated state cause this thread to start and stop repeatedly on CoreAnimation synchronize calls.
194     // This short burts of activity in between are not long enough for VoiceOver to retrieve accessibility attributes and makes the process appear unresponsive.
195     if (AXObjectCache::accessibilityEnhancedUserInterfaceEnabled())
196         useAnimation = false;
197     
198     ControlStates::States states = controlStates->states();
199
200     // Hover state is not supported by Aqua.
201     
202     // Pressed state
203     bool oldPressed = [cell isHighlighted];
204     bool pressed = states & ControlStates::PressedState;
205     if (pressed != oldPressed) {
206 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
207         [(NSButtonCell*)cell _setHighlighted:pressed animated:useAnimation];
208 #else
209         [cell setHighlighted:pressed];
210 #endif
211     }
212     
213     // Enabled state
214     bool oldEnabled = [cell isEnabled];
215     bool enabled = states & ControlStates::EnabledState;
216     if (enabled != oldEnabled)
217         [cell setEnabled:enabled];
218
219     // Checked and Indeterminate
220     bool oldIndeterminate = [cell state] == NSMixedState;
221     bool indeterminate = (states & ControlStates::IndeterminateState);
222     bool checked = states & ControlStates::CheckedState;
223     bool oldChecked = [cell state] == NSOnState;
224     if (oldIndeterminate != indeterminate || checked != oldChecked) {
225         NSCellStateValue newState = indeterminate ? NSMixedState : (checked ? NSOnState : NSOffState);
226 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
227         [(NSButtonCell*)cell _setState:newState animated:useAnimation];
228 #else
229         [cell setState:newState];
230 #endif
231     }
232
233     // Window inactive state does not need to be checked explicitly, since we paint parented to 
234     // a view in a window whose key state can be detected.
235 }
236
237 static ThemeDrawState convertControlStatesToThemeDrawState(ThemeButtonKind kind, const ControlStates* controlStates)
238 {
239     ControlStates::States states = controlStates->states();
240
241     if (!(states & ControlStates::EnabledState))
242         return kThemeStateUnavailableInactive;
243
244     // Do not process PressedState if !EnabledState.
245     if (states & ControlStates::PressedState) {
246         if (kind == kThemeIncDecButton || kind == kThemeIncDecButtonSmall || kind == kThemeIncDecButtonMini)
247             return states & ControlStates::SpinUpState ? kThemeStatePressedUp : kThemeStatePressedDown;
248         return kThemeStatePressed;
249     }
250     return kThemeStateActive;
251 }
252
253 static FloatRect inflateRect(const FloatRect& zoomedRect, const IntSize& zoomedSize, const int* margins, float zoomFactor)
254 {
255     // Only do the inflation if the available width/height are too small.  Otherwise try to
256     // fit the glow/check space into the available box's width/height.
257     int widthDelta = zoomedRect.width() - (zoomedSize.width() + margins[leftMargin] * zoomFactor + margins[rightMargin] * zoomFactor);
258     int heightDelta = zoomedRect.height() - (zoomedSize.height() + margins[topMargin] * zoomFactor + margins[bottomMargin] * zoomFactor);
259     FloatRect result(zoomedRect);
260     if (widthDelta < 0) {
261         result.setX(result.x() - margins[leftMargin] * zoomFactor);
262         result.setWidth(result.width() - widthDelta);
263     }
264     if (heightDelta < 0) {
265         result.setY(result.y() - margins[topMargin] * zoomFactor);
266         result.setHeight(result.height() - heightDelta);
267     }
268     return result;
269 }
270
271 // Checkboxes and radio buttons
272
273 static const std::array<IntSize, 3>& checkboxSizes()
274 {
275     static const std::array<IntSize, 3> sizes = { { IntSize(14, 14), IntSize(12, 12), IntSize(10, 10) } };
276     return sizes;
277 }
278
279 static const int* checkboxMargins(NSControlSize controlSize)
280 {
281     static const int margins[3][4] =
282     {
283         // top right bottom left
284         { 2, 2, 2, 2 },
285         { 2, 1, 2, 1 },
286         { 0, 0, 1, 0 },
287     };
288     return margins[controlSize];
289 }
290
291 static LengthSize checkboxSize(const FontCascade& font, const LengthSize& zoomedSize, float zoomFactor)
292 {
293     // If the width and height are both specified, then we have nothing to do.
294     if (!zoomedSize.width().isIntrinsicOrAuto() && !zoomedSize.height().isIntrinsicOrAuto())
295         return zoomedSize;
296
297     // Use the font size to determine the intrinsic width of the control.
298     return sizeFromFont(font, zoomedSize, zoomFactor, checkboxSizes());
299 }
300
301 // Radio Buttons
302
303 static const std::array<IntSize, 3>& radioSizes()
304 {
305     static const std::array<IntSize, 3> sizes = { { IntSize(16, 16), IntSize(12, 12), IntSize(10, 10) } };
306     return sizes;
307 }
308
309 static const int* radioMargins(NSControlSize controlSize)
310 {
311     static const int margins[3][4] =
312     {
313         // top right bottom left
314         { 1, 0, 1, 2 },
315         { 1, 1, 2, 1 },
316         { 0, 0, 1, 1 },
317     };
318     return margins[controlSize];
319 }
320
321 static LengthSize radioSize(const FontCascade& font, const LengthSize& zoomedSize, float zoomFactor)
322 {
323     // If the width and height are both specified, then we have nothing to do.
324     if (!zoomedSize.width().isIntrinsicOrAuto() && !zoomedSize.height().isIntrinsicOrAuto())
325         return zoomedSize;
326
327     // Use the font size to determine the intrinsic width of the control.
328     return sizeFromFont(font, zoomedSize, zoomFactor, radioSizes());
329 }
330     
331 static void configureToggleButton(NSCell* cell, ControlPart buttonType, const ControlStates* states, const IntSize& zoomedSize, float zoomFactor, bool isStateChange)
332 {
333     // Set the control size based off the rectangle we're painting into.
334     setControlSize(cell, buttonType == CheckboxPart ? checkboxSizes() : radioSizes(), zoomedSize, zoomFactor);
335
336     // Update the various states we respond to.
337     updateStates(cell, states, isStateChange);
338 }
339     
340 static RetainPtr<NSButtonCell> createToggleButtonCell(ControlPart buttonType)
341 {
342     RetainPtr<NSButtonCell> toggleButtonCell = adoptNS([[NSButtonCell alloc] init]);
343     
344     if (buttonType == CheckboxPart) {
345         [toggleButtonCell setButtonType:NSSwitchButton];
346         [toggleButtonCell setAllowsMixedState:YES];
347     } else {
348         ASSERT(buttonType == RadioPart);
349         [toggleButtonCell setButtonType:NSRadioButton];
350     }
351     
352     [toggleButtonCell setTitle:nil];
353     [toggleButtonCell setFocusRingType:NSFocusRingTypeExterior];
354     return toggleButtonCell;
355 }
356     
357 static NSButtonCell *sharedRadioCell(const ControlStates* states, const IntSize& zoomedSize, float zoomFactor)
358 {
359     static NSButtonCell *radioCell = createToggleButtonCell(RadioPart).leakRef();
360
361     configureToggleButton(radioCell, RadioPart, states, zoomedSize, zoomFactor, false);
362     return radioCell;
363 }
364     
365 static NSButtonCell *sharedCheckboxCell(const ControlStates* states, const IntSize& zoomedSize, float zoomFactor)
366 {
367     static NSButtonCell *checkboxCell = createToggleButtonCell(CheckboxPart).leakRef();
368
369     configureToggleButton(checkboxCell, CheckboxPart, states, zoomedSize, zoomFactor, false);
370     return checkboxCell;
371 }
372
373 static bool drawCellFocusRing(NSCell *cell, NSRect cellFrame, NSView *controlView)
374 {
375     wkDrawCellFocusRingWithFrameAtTime(cell, cellFrame, controlView, std::numeric_limits<double>::max());
376     return false;
377 }
378
379 static void paintToggleButton(ControlPart buttonType, ControlStates* controlStates, GraphicsContext* context, const FloatRect& zoomedRect, float zoomFactor, ScrollView* scrollView)
380 {
381     BEGIN_BLOCK_OBJC_EXCEPTIONS
382
383     RetainPtr<NSButtonCell> toggleButtonCell = static_cast<NSButtonCell *>(controlStates->platformControl());
384     IntSize zoomedRectSize = IntSize(zoomedRect.size());
385
386     if (controlStates->isDirty()) {
387         if (!toggleButtonCell)
388             toggleButtonCell = createToggleButtonCell(buttonType);
389         configureToggleButton(toggleButtonCell.get(), buttonType, controlStates, zoomedRectSize, zoomFactor, true);
390     } else {
391         if (!toggleButtonCell) {
392             if (buttonType == CheckboxPart)
393                 toggleButtonCell = sharedCheckboxCell(controlStates, zoomedRectSize, zoomFactor);
394             else {
395                 ASSERT(buttonType == RadioPart);
396                 toggleButtonCell = sharedRadioCell(controlStates, zoomedRectSize, zoomFactor);
397             }
398         }
399         configureToggleButton(toggleButtonCell.get(), buttonType, controlStates, zoomedRectSize, zoomFactor, false);
400     }
401     controlStates->setDirty(false);
402
403     GraphicsContextStateSaver stateSaver(*context);
404
405     NSControlSize controlSize = [toggleButtonCell controlSize];
406     IntSize zoomedSize = buttonType == CheckboxPart ? checkboxSizes()[controlSize] : radioSizes()[controlSize];
407     zoomedSize.setWidth(zoomedSize.width() * zoomFactor);
408     zoomedSize.setHeight(zoomedSize.height() * zoomFactor);
409     const int* controlMargins = buttonType == CheckboxPart ? checkboxMargins(controlSize) : radioMargins(controlSize);
410     FloatRect inflatedRect = inflateRect(zoomedRect, zoomedSize, controlMargins, zoomFactor);
411
412     if (zoomFactor != 1.0f) {
413         inflatedRect.setWidth(inflatedRect.width() / zoomFactor);
414         inflatedRect.setHeight(inflatedRect.height() / zoomFactor);
415         context->translate(inflatedRect.x(), inflatedRect.y());
416         context->scale(FloatSize(zoomFactor, zoomFactor));
417         context->translate(-inflatedRect.x(), -inflatedRect.y());
418     }
419
420     LocalCurrentGraphicsContext localContext(context);
421
422     bool useUnparentedView = false;
423 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
424     useUnparentedView = true;
425 #endif
426     NSView *view = ThemeMac::ensuredView(scrollView, controlStates, useUnparentedView);
427
428     bool isAnimating = false;
429     bool needsRepaint = false;
430
431 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
432     if ([toggleButtonCell _stateAnimationRunning]) {
433         context->translate(inflatedRect.x(), inflatedRect.y());
434         context->scale(FloatSize(1, -1));
435         context->translate(0, -inflatedRect.height());
436
437         [toggleButtonCell _renderCurrentAnimationFrameInContext:context->platformContext() atLocation:NSMakePoint(0, 0)];
438         isAnimating = [toggleButtonCell _stateAnimationRunning];
439     } else
440         [toggleButtonCell drawWithFrame:NSRect(inflatedRect) inView:view];
441 #else
442     [toggleButtonCell drawWithFrame:NSRect(inflatedRect) inView:view];
443 #endif
444
445     if (!isAnimating && (controlStates->states() & ControlStates::FocusState))
446         needsRepaint = drawCellFocusRing(toggleButtonCell.get(), inflatedRect, view);
447     [toggleButtonCell setControlView:nil];
448
449 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
450     needsRepaint |= [toggleButtonCell _stateAnimationRunning];
451 #endif
452     controlStates->setNeedsRepaint(needsRepaint);
453     if (needsRepaint)
454         controlStates->setPlatformControl(toggleButtonCell.get());
455
456     END_BLOCK_OBJC_EXCEPTIONS
457 }
458
459 // Buttons
460
461 // Buttons really only constrain height. They respect width.
462 static const std::array<IntSize, 3>& buttonSizes()
463 {
464     static const std::array<IntSize, 3> sizes = { { IntSize(0, 21), IntSize(0, 18), IntSize(0, 15) } };
465     return sizes;
466 }
467
468 static const int* buttonMargins(NSControlSize controlSize)
469 {
470     static const int margins[3][4] =
471     {
472         { 4, 6, 7, 6 },
473         { 4, 5, 6, 5 },
474         { 0, 1, 1, 1 },
475     };
476     return margins[controlSize];
477 }
478
479 enum ButtonCellType { NormalButtonCell, DefaultButtonCell };
480
481 static NSButtonCell *leakButtonCell(ButtonCellType type)
482 {
483     NSButtonCell *cell = [[NSButtonCell alloc] init];
484     [cell setTitle:nil];
485     [cell setButtonType:NSMomentaryPushInButton];
486     if (type == DefaultButtonCell)
487         [cell setKeyEquivalent:@"\r"];
488     return cell;
489 }
490
491 static void setUpButtonCell(NSButtonCell *cell, ControlPart part, const ControlStates* states, const IntSize& zoomedSize, float zoomFactor)
492 {
493     // Set the control size based off the rectangle we're painting into.
494     const std::array<IntSize, 3>& sizes = buttonSizes();
495     if (part == SquareButtonPart || zoomedSize.height() > buttonSizes()[NSRegularControlSize].height() * zoomFactor) {
496         // Use the square button
497         if ([cell bezelStyle] != NSShadowlessSquareBezelStyle)
498             [cell setBezelStyle:NSShadowlessSquareBezelStyle];
499     } else if ([cell bezelStyle] != NSRoundedBezelStyle)
500         [cell setBezelStyle:NSRoundedBezelStyle];
501
502     setControlSize(cell, sizes, zoomedSize, zoomFactor);
503
504     // Update the various states we respond to.
505     updateStates(cell, states);
506 }
507
508 static NSButtonCell *button(ControlPart part, const ControlStates* controlStates, const IntSize& zoomedSize, float zoomFactor)
509 {
510     ControlStates::States states = controlStates->states();
511     NSButtonCell *cell;
512     if (states & ControlStates::DefaultState) {
513         static NSButtonCell *defaultCell = leakButtonCell(DefaultButtonCell);
514         cell = defaultCell;
515     } else {
516         static NSButtonCell *normalCell = leakButtonCell(NormalButtonCell);
517         cell = normalCell;
518     }
519     setUpButtonCell(cell, part, controlStates, zoomedSize, zoomFactor);
520     return cell;
521 }
522
523 static float buttonFocusRectOutlineWidth()
524 {
525     return 3.0f;
526 }
527     
528 static void paintButton(ControlPart part, ControlStates* controlStates, GraphicsContext* context, const FloatRect& zoomedRect, float zoomFactor, ScrollView* scrollView, float deviceScaleFactor, float pageScaleFactor)
529 {
530     BEGIN_BLOCK_OBJC_EXCEPTIONS
531     
532     // Determine the width and height needed for the control and prepare the cell for painting.
533     ControlStates::States states = controlStates->states();
534     NSButtonCell *buttonCell = button(part, controlStates, IntSize(zoomedRect.size()), zoomFactor);
535     GraphicsContextStateSaver stateSaver(*context);
536
537     NSControlSize controlSize = [buttonCell controlSize];
538     IntSize zoomedSize = buttonSizes()[controlSize];
539     zoomedSize.setWidth(zoomedRect.width()); // Buttons don't ever constrain width, so the zoomed width can just be honored.
540     zoomedSize.setHeight(zoomedSize.height() * zoomFactor);
541     FloatRect inflatedRect = zoomedRect;
542     if ([buttonCell bezelStyle] == NSRoundedBezelStyle) {
543         // Center the button within the available space.
544         if (inflatedRect.height() > zoomedSize.height()) {
545             inflatedRect.setY(inflatedRect.y() + (inflatedRect.height() - zoomedSize.height()) / 2);
546             inflatedRect.setHeight(zoomedSize.height());
547         }
548
549         // Now inflate it to account for the shadow.
550         inflatedRect = inflateRect(inflatedRect, zoomedSize, buttonMargins(controlSize), zoomFactor);
551
552         if (zoomFactor != 1.0f) {
553             inflatedRect.setWidth(inflatedRect.width() / zoomFactor);
554             inflatedRect.setHeight(inflatedRect.height() / zoomFactor);
555             context->translate(inflatedRect.x(), inflatedRect.y());
556             context->scale(FloatSize(zoomFactor, zoomFactor));
557             context->translate(-inflatedRect.x(), -inflatedRect.y());
558         }
559     }
560     
561     bool shouldUseImageBuffer = pageScaleFactor != 1.0f || zoomFactor != 1.0f;
562     float focusThickness = buttonFocusRectOutlineWidth();
563     
564     std::unique_ptr<ImageBuffer> imageBuffer;
565     GraphicsContext* drawButtonContext = context;
566     if (shouldUseImageBuffer) {
567         imageBuffer = ImageBuffer::createCompatibleBuffer(inflatedRect.size() + 2 * FloatSize(focusThickness, focusThickness), deviceScaleFactor, ColorSpaceDeviceRGB, context, false);
568         drawButtonContext = imageBuffer->context();
569     }
570     LocalCurrentGraphicsContext localContext(drawButtonContext);
571     
572     NSView *view = ThemeMac::ensuredView(scrollView, controlStates);
573     NSWindow *window = [view window];
574     NSButtonCell *previousDefaultButtonCell = [window defaultButtonCell];
575
576     if (states & ControlStates::DefaultState) {
577         [window setDefaultButtonCell:buttonCell];
578 #if __MAC_OS_X_VERSION_MIN_REQUIRED < 101000
579         wkAdvanceDefaultButtonPulseAnimation(buttonCell);
580 #endif
581     } else if ([previousDefaultButtonCell isEqual:buttonCell])
582         [window setDefaultButtonCell:nil];
583
584     FloatRect drawFromImageBufferRect(focusThickness, focusThickness, inflatedRect.width(), inflatedRect.height());
585     if (shouldUseImageBuffer) {
586         [buttonCell drawWithFrame:NSRect(drawFromImageBufferRect) inView:view];
587         if (!(states & ControlStates::FocusState))
588             context->drawImageBuffer(imageBuffer.get(), ColorSpaceDeviceRGB, inflatedRect.location() - FloatSize(focusThickness, focusThickness));
589     } else
590         [buttonCell drawWithFrame:NSRect(inflatedRect) inView:view];
591     
592     if (states & ControlStates::FocusState) {
593         if (shouldUseImageBuffer) {
594             drawCellFocusRing(buttonCell, NSRect(drawFromImageBufferRect), view);
595             context->drawImageBuffer(imageBuffer.get(), ColorSpaceDeviceRGB, inflatedRect.location() - FloatSize(focusThickness, focusThickness));
596         } else
597             drawCellFocusRing(buttonCell, NSRect(inflatedRect), view);
598     }
599     
600     controlStates->setNeedsRepaint(false);
601
602     [buttonCell setControlView:nil];
603
604     if (![previousDefaultButtonCell isEqual:buttonCell])
605         [window setDefaultButtonCell:previousDefaultButtonCell];
606
607     END_BLOCK_OBJC_EXCEPTIONS
608 }
609
610 // Stepper
611
612 static const std::array<IntSize, 3>& stepperSizes()
613 {
614     static const std::array<IntSize, 3> sizes = { { IntSize(19, 27), IntSize(15, 22), IntSize(13, 15) } };
615     return sizes;
616 }
617
618 // We don't use controlSizeForFont() for steppers because the stepper height
619 // should be equal to or less than the corresponding text field height,
620 static NSControlSize stepperControlSizeForFont(const FontCascade& font)
621 {
622     int fontSize = font.pixelSize();
623     if (fontSize >= 18)
624         return NSRegularControlSize;
625     if (fontSize >= 13)
626         return NSSmallControlSize;
627     return NSMiniControlSize;
628 }
629
630 static void paintStepper(ControlStates* states, GraphicsContext* context, const FloatRect& zoomedRect, float zoomFactor, ScrollView*)
631 {
632     // We don't use NSStepperCell because there are no ways to draw an
633     // NSStepperCell with the up button highlighted.
634
635     HIThemeButtonDrawInfo drawInfo;
636     drawInfo.version = 0;
637     drawInfo.state = convertControlStatesToThemeDrawState(kThemeIncDecButton, states);
638     drawInfo.adornment = kThemeAdornmentDefault;
639     ControlSize controlSize = controlSizeFromPixelSize(stepperSizes(), IntSize(zoomedRect.size()), zoomFactor);
640     if (controlSize == NSSmallControlSize)
641         drawInfo.kind = kThemeIncDecButtonSmall;
642     else if (controlSize == NSMiniControlSize)
643         drawInfo.kind = kThemeIncDecButtonMini;
644     else
645         drawInfo.kind = kThemeIncDecButton;
646
647     IntRect rect(zoomedRect);
648     GraphicsContextStateSaver stateSaver(*context);
649     if (zoomFactor != 1.0f) {
650         rect.setWidth(rect.width() / zoomFactor);
651         rect.setHeight(rect.height() / zoomFactor);
652         context->translate(rect.x(), rect.y());
653         context->scale(FloatSize(zoomFactor, zoomFactor));
654         context->translate(-rect.x(), -rect.y());
655     }
656     CGRect bounds(rect);
657     CGRect backgroundBounds;
658     HIThemeGetButtonBackgroundBounds(&bounds, &drawInfo, &backgroundBounds);
659     // Center the stepper rectangle in the specified area.
660     backgroundBounds.origin.x = bounds.origin.x + (bounds.size.width - backgroundBounds.size.width) / 2;
661     if (backgroundBounds.size.height < bounds.size.height) {
662         int heightDiff = clampToInteger(bounds.size.height - backgroundBounds.size.height);
663         backgroundBounds.origin.y = bounds.origin.y + (heightDiff / 2) + 1;
664     }
665
666     LocalCurrentGraphicsContext localContext(context);
667     HIThemeDrawButton(&backgroundBounds, &drawInfo, localContext.cgContext(), kHIThemeOrientationNormal, 0);
668 }
669
670 // This will ensure that we always return a valid NSView, even if ScrollView doesn't have an associated document NSView.
671 // If the ScrollView doesn't have an NSView, we will return a fake NSView set up in the way AppKit expects.
672 NSView *ThemeMac::ensuredView(ScrollView* scrollView, const ControlStates* controlStates, bool useUnparentedView)
673 {
674     if (!useUnparentedView) {
675         if (NSView *documentView = scrollView->documentView())
676             return documentView;
677     }
678
679     // Use a fake view.
680     static WebCoreThemeView *themeView = [[WebCoreThemeView alloc] init];
681     [themeView setFrameSize:NSSizeFromCGSize(scrollView->totalContentsSize())];
682
683     themeWindowHasKeyAppearance = !(controlStates->states() & ControlStates::WindowInactiveState);
684
685     return themeView;
686 }
687
688 void ThemeMac::setFocusRingClipRect(const FloatRect& rect)
689 {
690     focusRingClipRect = rect;
691 }
692
693 // Theme overrides
694
695 int ThemeMac::baselinePositionAdjustment(ControlPart part) const
696 {
697     if (part == CheckboxPart || part == RadioPart)
698         return -2;
699     return Theme::baselinePositionAdjustment(part);
700 }
701
702 Optional<FontDescription> ThemeMac::controlFont(ControlPart part, const FontCascade& font, float zoomFactor) const
703 {
704     switch (part) {
705         case PushButtonPart: {
706             FontDescription fontDescription;
707             fontDescription.setIsAbsoluteSize(true);
708
709             NSFont* nsFont = [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:controlSizeForFont(font)]];
710             fontDescription.setOneFamily(AtomicString("-apple-system", AtomicString::ConstructFromLiteral));
711             fontDescription.setComputedSize([nsFont pointSize] * zoomFactor);
712             fontDescription.setSpecifiedSize([nsFont pointSize] * zoomFactor);
713             return fontDescription;
714         }
715         default:
716             return Theme::controlFont(part, font, zoomFactor);
717     }
718 }
719
720 LengthSize ThemeMac::controlSize(ControlPart part, const FontCascade& font, const LengthSize& zoomedSize, float zoomFactor) const
721 {
722     switch (part) {
723         case CheckboxPart:
724             return checkboxSize(font, zoomedSize, zoomFactor);
725         case RadioPart:
726             return radioSize(font, zoomedSize, zoomFactor);
727         case PushButtonPart:
728             // Height is reset to auto so that specified heights can be ignored.
729             return sizeFromFont(font, LengthSize(zoomedSize.width(), Length()), zoomFactor, buttonSizes());
730         case InnerSpinButtonPart:
731             if (!zoomedSize.width().isIntrinsicOrAuto() && !zoomedSize.height().isIntrinsicOrAuto())
732                 return zoomedSize;
733             return sizeFromNSControlSize(stepperControlSizeForFont(font), zoomedSize, zoomFactor, stepperSizes());
734         default:
735             return zoomedSize;
736     }
737 }
738
739 LengthSize ThemeMac::minimumControlSize(ControlPart part, const FontCascade& font, float zoomFactor) const
740 {
741     switch (part) {
742         case SquareButtonPart:
743         case DefaultButtonPart:
744         case ButtonPart:
745             return LengthSize(Length(0, Fixed), Length(static_cast<int>(15 * zoomFactor), Fixed));
746         case InnerSpinButtonPart:{
747             IntSize base = stepperSizes()[NSMiniControlSize];
748             return LengthSize(Length(static_cast<int>(base.width() * zoomFactor), Fixed),
749                               Length(static_cast<int>(base.height() * zoomFactor), Fixed));
750         }
751         default:
752             return Theme::minimumControlSize(part, font, zoomFactor);
753     }
754 }
755
756 LengthBox ThemeMac::controlBorder(ControlPart part, const FontCascade& font, const LengthBox& zoomedBox, float zoomFactor) const
757 {
758     switch (part) {
759         case SquareButtonPart:
760         case DefaultButtonPart:
761         case ButtonPart:
762             return LengthBox(0, zoomedBox.right().value(), 0, zoomedBox.left().value());
763         default:
764             return Theme::controlBorder(part, font, zoomedBox, zoomFactor);
765     }
766 }
767
768 LengthBox ThemeMac::controlPadding(ControlPart part, const FontCascade& font, const LengthBox& zoomedBox, float zoomFactor) const
769 {
770     switch (part) {
771         case PushButtonPart: {
772             // Just use 8px.  AppKit wants to use 11px for mini buttons, but that padding is just too large
773             // for real-world Web sites (creating a huge necessary minimum width for buttons whose space is
774             // by definition constrained, since we select mini only for small cramped environments.
775             // This also guarantees the HTML <button> will match our rendering by default, since we're using a consistent
776             // padding.
777             const int padding = 8 * zoomFactor;
778             return LengthBox(2, padding, 3, padding);
779         }
780         default:
781             return Theme::controlPadding(part, font, zoomedBox, zoomFactor);
782     }
783 }
784
785 void ThemeMac::inflateControlPaintRect(ControlPart part, const ControlStates* states, FloatRect& zoomedRect, float zoomFactor) const
786 {
787     BEGIN_BLOCK_OBJC_EXCEPTIONS
788     IntSize zoomRectSize = IntSize(zoomedRect.size());
789     switch (part) {
790         case CheckboxPart: {
791             // We inflate the rect as needed to account for padding included in the cell to accommodate the checkbox
792             // shadow" and the check.  We don't consider this part of the bounds of the control in WebKit.
793             NSCell *cell = sharedCheckboxCell(states, zoomRectSize, zoomFactor);
794             NSControlSize controlSize = [cell controlSize];
795             IntSize zoomedSize = checkboxSizes()[controlSize];
796             zoomedSize.setHeight(zoomedSize.height() * zoomFactor);
797             zoomedSize.setWidth(zoomedSize.width() * zoomFactor);
798             zoomedRect = inflateRect(zoomedRect, zoomedSize, checkboxMargins(controlSize), zoomFactor);
799             break;
800         }
801         case RadioPart: {
802             // We inflate the rect as needed to account for padding included in the cell to accommodate the radio button
803             // shadow".  We don't consider this part of the bounds of the control in WebKit.
804             NSCell *cell = sharedRadioCell(states, zoomRectSize, zoomFactor);
805             NSControlSize controlSize = [cell controlSize];
806             IntSize zoomedSize = radioSizes()[controlSize];
807             zoomedSize.setHeight(zoomedSize.height() * zoomFactor);
808             zoomedSize.setWidth(zoomedSize.width() * zoomFactor);
809             zoomedRect = inflateRect(zoomedRect, zoomedSize, radioMargins(controlSize), zoomFactor);
810             break;
811         }
812         case PushButtonPart:
813         case DefaultButtonPart:
814         case ButtonPart: {
815             NSButtonCell *cell = button(part, states, zoomRectSize, zoomFactor);
816             NSControlSize controlSize = [cell controlSize];
817
818             // We inflate the rect as needed to account for the Aqua button's shadow.
819             if ([cell bezelStyle] == NSRoundedBezelStyle) {
820                 IntSize zoomedSize = buttonSizes()[controlSize];
821                 zoomedSize.setHeight(zoomedSize.height() * zoomFactor);
822                 zoomedSize.setWidth(zoomedRect.width()); // Buttons don't ever constrain width, so the zoomed width can just be honored.
823                 zoomedRect = inflateRect(zoomedRect, zoomedSize, buttonMargins(controlSize), zoomFactor);
824             }
825             break;
826         }
827         case InnerSpinButtonPart: {
828             static const int stepperMargin[4] = { 0, 0, 0, 0 };
829             ControlSize controlSize = controlSizeFromPixelSize(stepperSizes(), zoomRectSize, zoomFactor);
830             IntSize zoomedSize = stepperSizes()[controlSize];
831             zoomedSize.setHeight(zoomedSize.height() * zoomFactor);
832             zoomedSize.setWidth(zoomedSize.width() * zoomFactor);
833             zoomedRect = inflateRect(zoomedRect, zoomedSize, stepperMargin, zoomFactor);
834             break;
835         }
836         default:
837             break;
838     }
839     END_BLOCK_OBJC_EXCEPTIONS
840 }
841
842 void ThemeMac::paint(ControlPart part, ControlStates* states, GraphicsContext* context, const FloatRect& zoomedRect, float zoomFactor, ScrollView* scrollView, float deviceScaleFactor, float pageScaleFactor)
843 {
844     switch (part) {
845         case CheckboxPart:
846             paintToggleButton(part, states, context, zoomedRect, zoomFactor, scrollView);
847             break;
848         case RadioPart:
849             paintToggleButton(part, states, context, zoomedRect, zoomFactor, scrollView);
850             break;
851         case PushButtonPart:
852         case DefaultButtonPart:
853         case ButtonPart:
854         case SquareButtonPart:
855             paintButton(part, states, context, zoomedRect, zoomFactor, scrollView, deviceScaleFactor, pageScaleFactor);
856             break;
857         case InnerSpinButtonPart:
858             paintStepper(states, context, zoomedRect, zoomFactor, scrollView);
859             break;
860         default:
861             break;
862     }
863 }
864
865 }