Home | History | Annotate | Download | only in chromium
      1 /*
      2  * Copyright (C) 2008 Apple Inc. All Rights Reserved.
      3  * Copyright (C) 2009 Google Inc. All Rights Reserved.
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions
      7  * are met:
      8  * 1. Redistributions of source code must retain the above copyright
      9  *    notice, this list of conditions and the following disclaimer.
     10  * 2. Redistributions in binary form must reproduce the above copyright
     11  *    notice, this list of conditions and the following disclaimer in the
     12  *    documentation and/or other materials provided with the distribution.
     13  *
     14  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
     15  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     17  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
     18  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     19  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     20  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     21  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
     22  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     24  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     25  */
     26 
     27 #import "config.h"
     28 #import "ThemeChromiumMac.h"
     29 
     30 #import "BlockExceptions.h"
     31 #import "GraphicsContext.h"
     32 #import "LocalCurrentGraphicsContext.h"
     33 #import "ScrollView.h"
     34 #import "WebCoreSystemInterface.h"
     35 #import <Carbon/Carbon.h>
     36 #include <wtf/StdLibExtras.h>
     37 #import <objc/runtime.h>
     38 
     39 using namespace std;
     40 
     41 // This file (and its associated .h file) is a clone of ThemeMac.mm.
     42 // Because the original file is designed to run in-process inside a Cocoa view,
     43 // we must maintain a fork. Please maintain this file by performing parallel
     44 // changes to it.
     45 //
     46 // The only changes from ThemeMac should be:
     47 // - The classname change from ThemeMac to ThemeChromiumMac.
     48 // - The import of FlippedView() and its use as the parent view for cell
     49 //   rendering.
     50 // - In updateStates() the code to update the cells' inactive state.
     51 // - In paintButton() the code to save/restore the window's default button cell.
     52 // - The Snow Leopard focus ring bug fix and its use around every call to
     53 //   -[NSButtonCell drawWithFrame:inView:].
     54 //
     55 // For all other differences, if it was introduced in this file, then the
     56 // maintainer forgot to include it in the list; otherwise it is an update that
     57 // should have been applied to this file but was not.
     58 
     59 // FIXME: Default buttons really should be more like push buttons and not like buttons.
     60 
     61 // --- START fix for Snow Leopard focus ring bug ---
     62 
     63 // There is a bug in the Cocoa focus ring drawing code. The code calls +[NSView
     64 // focusView] (to get the currently focused view) and then calls an NSRect-
     65 // returning method on that view to obtain a clipping rect. However, if there is
     66 // no focused view (as there won't be if the destination is a context), the rect
     67 // returned from the method invocation on nil is garbage.
     68 //
     69 // The garbage fortunately does not clip the focus ring on Leopard, but
     70 // unfortunately does so on Snow Leopard. Therefore, if a runtime test shows
     71 // that focus ring drawing fails, we swizzle NSView to ensure it returns a valid
     72 // view with a valid clipping rectangle.
     73 //
     74 // FIXME: After the referenced bug is fixed on all supported platforms, remove
     75 // this code.
     76 //
     77 // References:
     78 //  <http://crbug.com/27493>
     79 //  <rdar://problem/7604051> (<http://openradar.appspot.com/7604051>)
     80 
     81 @interface TCMVisibleView : NSView
     82 
     83 @end
     84 
     85 @implementation TCMVisibleView
     86 
     87 - (struct CGRect)_focusRingVisibleRect
     88 {
     89     return CGRectZero;
     90 }
     91 
     92 - (id)_focusRingClipAncestor
     93 {
     94     return self;
     95 }
     96 
     97 @end
     98 
     99 @interface NSView (TCMInterposing)
    100 + (NSView *)TCMInterposing_focusView;
    101 @end
    102 
    103 namespace FocusIndicationFix {
    104 
    105 bool currentOSHasSetFocusRingStyleInBitmapBug()
    106 {
    107     UInt32 pixel = 0;
    108     UInt32* pixelPlane = &pixel;
    109     UInt32** pixelPlanes = &pixelPlane;
    110     NSBitmapImageRep *bitmap = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:(UInt8**)pixelPlanes
    111                                                                        pixelsWide:1
    112                                                                        pixelsHigh:1
    113                                                                     bitsPerSample:8
    114                                                                   samplesPerPixel:4
    115                                                                          hasAlpha:YES
    116                                                                          isPlanar:NO
    117                                                                    colorSpaceName:NSCalibratedRGBColorSpace
    118                                                                      bitmapFormat:NSAlphaFirstBitmapFormat
    119                                                                       bytesPerRow:4
    120                                                                      bitsPerPixel:32];
    121     [NSGraphicsContext saveGraphicsState];
    122     [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithBitmapImageRep:bitmap]];
    123     NSSetFocusRingStyle(NSFocusRingOnly);
    124     NSRectFill(NSMakeRect(0, 0, 1, 1));
    125     [NSGraphicsContext restoreGraphicsState];
    126     [bitmap release];
    127 
    128     return !pixel;
    129 }
    130 
    131 bool swizzleFocusView()
    132 {
    133     if (!currentOSHasSetFocusRingStyleInBitmapBug())
    134         return false;
    135 
    136     Class nsview = [NSView class];
    137     Method m1 = class_getClassMethod(nsview, @selector(focusView));
    138     Method m2 = class_getClassMethod(nsview, @selector(TCMInterposing_focusView));
    139     if (m1 && m2) {
    140         method_exchangeImplementations(m1, m2);
    141         return true;
    142     }
    143 
    144     return false;
    145 }
    146 
    147 static bool interpose = false;
    148 
    149 // A class to restrict the amount of time spent messing with interposing. It
    150 // only stacks one-deep.
    151 class ScopedFixer {
    152 public:
    153     ScopedFixer()
    154     {
    155         static bool swizzled = swizzleFocusView();
    156         interpose = swizzled;
    157     }
    158 
    159     ~ScopedFixer()
    160     {
    161         interpose = false;
    162     }
    163 };
    164 
    165 }  // namespace FocusIndicationFix
    166 
    167 @implementation NSView (TCMInterposing)
    168 
    169 + (NSView *)TCMInterposing_focusView
    170 {
    171     NSView *view = [self TCMInterposing_focusView];  // call original (was swizzled)
    172     if (!view && FocusIndicationFix::interpose) {
    173         static TCMVisibleView* fixedView = [[TCMVisibleView alloc] init];
    174         view = fixedView;
    175     }
    176 
    177     return view;
    178 }
    179 
    180 @end
    181 
    182 // --- END fix for Snow Leopard focus ring bug ---
    183 
    184 namespace WebCore {
    185 
    186 // Pick up utility function from RenderThemeChromiumMac.
    187 extern NSView* FlippedView();
    188 
    189 enum {
    190     topMargin,
    191     rightMargin,
    192     bottomMargin,
    193     leftMargin
    194 };
    195 
    196 Theme* platformTheme()
    197 {
    198     DEFINE_STATIC_LOCAL(ThemeChromiumMac, themeMac, ());
    199     return &themeMac;
    200 }
    201 
    202 // Helper functions used by a bunch of different control parts.
    203 
    204 static NSControlSize controlSizeForFont(const Font& font)
    205 {
    206     int fontSize = font.pixelSize();
    207     if (fontSize >= 16)
    208         return NSRegularControlSize;
    209     if (fontSize >= 11)
    210         return NSSmallControlSize;
    211     return NSMiniControlSize;
    212 }
    213 
    214 static LengthSize sizeFromNSControlSize(NSControlSize nsControlSize, const LengthSize& zoomedSize, float zoomFactor, const IntSize* sizes)
    215 {
    216     IntSize controlSize = sizes[nsControlSize];
    217     if (zoomFactor != 1.0f)
    218         controlSize = IntSize(controlSize.width() * zoomFactor, controlSize.height() * zoomFactor);
    219     LengthSize result = zoomedSize;
    220     if (zoomedSize.width().isIntrinsicOrAuto() && controlSize.width() > 0)
    221         result.setWidth(Length(controlSize.width(), Fixed));
    222     if (zoomedSize.height().isIntrinsicOrAuto() && controlSize.height() > 0)
    223         result.setHeight(Length(controlSize.height(), Fixed));
    224     return result;
    225 }
    226 
    227 static LengthSize sizeFromFont(const Font& font, const LengthSize& zoomedSize, float zoomFactor, const IntSize* sizes)
    228 {
    229     return sizeFromNSControlSize(controlSizeForFont(font), zoomedSize, zoomFactor, sizes);
    230 }
    231 
    232 static ControlSize controlSizeFromPixelSize(const IntSize* sizes, const IntSize& minZoomedSize, float zoomFactor)
    233 {
    234     if (minZoomedSize.width() >= static_cast<int>(sizes[NSRegularControlSize].width() * zoomFactor) &&
    235         minZoomedSize.height() >= static_cast<int>(sizes[NSRegularControlSize].height() * zoomFactor))
    236         return NSRegularControlSize;
    237     if (minZoomedSize.width() >= static_cast<int>(sizes[NSSmallControlSize].width() * zoomFactor) &&
    238         minZoomedSize.height() >= static_cast<int>(sizes[NSSmallControlSize].height() * zoomFactor))
    239         return NSSmallControlSize;
    240     return NSMiniControlSize;
    241 }
    242 
    243 static void setControlSize(NSCell* cell, const IntSize* sizes, const IntSize& minZoomedSize, float zoomFactor)
    244 {
    245     ControlSize size = controlSizeFromPixelSize(sizes, minZoomedSize, zoomFactor);
    246     if (size != [cell controlSize]) // Only update if we have to, since AppKit does work even if the size is the same.
    247         [cell setControlSize:(NSControlSize)size];
    248 }
    249 
    250 static void updateStates(NSCell* cell, ControlStates states)
    251 {
    252     // Hover state is not supported by Aqua.
    253 
    254     // Pressed state
    255     bool oldPressed = [cell isHighlighted];
    256     bool pressed = states & PressedState;
    257     if (pressed != oldPressed)
    258         [cell setHighlighted:pressed];
    259 
    260     // Enabled state
    261     bool oldEnabled = [cell isEnabled];
    262     bool enabled = states & EnabledState;
    263     if (enabled != oldEnabled)
    264         [cell setEnabled:enabled];
    265 
    266     // Focused state
    267     bool oldFocused = [cell showsFirstResponder];
    268     bool focused = states & FocusState;
    269     if (focused != oldFocused)
    270         [cell setShowsFirstResponder:focused];
    271 
    272     // Checked and Indeterminate
    273     bool oldIndeterminate = [cell state] == NSMixedState;
    274     bool indeterminate = (states & IndeterminateState);
    275     bool checked = states & CheckedState;
    276     bool oldChecked = [cell state] == NSOnState;
    277     if (oldIndeterminate != indeterminate || checked != oldChecked)
    278         [cell setState:indeterminate ? NSMixedState : (checked ? NSOnState : NSOffState)];
    279 
    280     // Window Inactive state
    281     NSControlTint oldTint = [cell controlTint];
    282     bool windowInactive = (states & WindowInactiveState);
    283     NSControlTint tint = windowInactive ? static_cast<NSControlTint>(NSClearControlTint)
    284                                         : [NSColor currentControlTint];
    285     if (tint != oldTint)
    286         [cell setControlTint:tint];
    287 }
    288 
    289 static ThemeDrawState convertControlStatesToThemeDrawState(ThemeButtonKind kind, ControlStates states)
    290 {
    291     if (states & ReadOnlyState)
    292         return kThemeStateUnavailableInactive;
    293     if (!(states & EnabledState))
    294         return kThemeStateUnavailableInactive;
    295 
    296     // Do not process PressedState if !EnabledState or ReadOnlyState.
    297     if (states & PressedState) {
    298         if (kind == kThemeIncDecButton || kind == kThemeIncDecButtonSmall || kind == kThemeIncDecButtonMini)
    299             return states & SpinUpState ? kThemeStatePressedUp : kThemeStatePressedDown;
    300         return kThemeStatePressed;
    301     }
    302     return kThemeStateActive;
    303 }
    304 
    305 static IntRect inflateRect(const IntRect& zoomedRect, const IntSize& zoomedSize, const int* margins, float zoomFactor)
    306 {
    307     // Only do the inflation if the available width/height are too small.  Otherwise try to
    308     // fit the glow/check space into the available box's width/height.
    309     int widthDelta = zoomedRect.width() - (zoomedSize.width() + margins[leftMargin] * zoomFactor + margins[rightMargin] * zoomFactor);
    310     int heightDelta = zoomedRect.height() - (zoomedSize.height() + margins[topMargin] * zoomFactor + margins[bottomMargin] * zoomFactor);
    311     IntRect result(zoomedRect);
    312     if (widthDelta < 0) {
    313         result.setX(result.x() - margins[leftMargin] * zoomFactor);
    314         result.setWidth(result.width() - widthDelta);
    315     }
    316     if (heightDelta < 0) {
    317         result.setY(result.y() - margins[topMargin] * zoomFactor);
    318         result.setHeight(result.height() - heightDelta);
    319     }
    320     return result;
    321 }
    322 
    323 // Checkboxes
    324 
    325 static const IntSize* checkboxSizes()
    326 {
    327     static const IntSize sizes[3] = { IntSize(14, 14), IntSize(12, 12), IntSize(10, 10) };
    328     return sizes;
    329 }
    330 
    331 static const int* checkboxMargins(NSControlSize controlSize)
    332 {
    333     static const int margins[3][4] =
    334     {
    335         { 3, 4, 4, 2 },
    336         { 4, 3, 3, 3 },
    337         { 4, 3, 3, 3 },
    338     };
    339     return margins[controlSize];
    340 }
    341 
    342 static LengthSize checkboxSize(const Font& font, const LengthSize& zoomedSize, float zoomFactor)
    343 {
    344     // If the width and height are both specified, then we have nothing to do.
    345     if (!zoomedSize.width().isIntrinsicOrAuto() && !zoomedSize.height().isIntrinsicOrAuto())
    346         return zoomedSize;
    347 
    348     // Use the font size to determine the intrinsic width of the control.
    349     return sizeFromFont(font, zoomedSize, zoomFactor, checkboxSizes());
    350 }
    351 
    352 static NSButtonCell *checkbox(ControlStates states, const IntRect& zoomedRect, float zoomFactor)
    353 {
    354     static NSButtonCell *checkboxCell;
    355     if (!checkboxCell) {
    356         checkboxCell = [[NSButtonCell alloc] init];
    357         [checkboxCell setButtonType:NSSwitchButton];
    358         [checkboxCell setTitle:nil];
    359         [checkboxCell setAllowsMixedState:YES];
    360         [checkboxCell setFocusRingType:NSFocusRingTypeExterior];
    361     }
    362 
    363     // Set the control size based off the rectangle we're painting into.
    364     setControlSize(checkboxCell, checkboxSizes(), zoomedRect.size(), zoomFactor);
    365 
    366     // Update the various states we respond to.
    367     updateStates(checkboxCell, states);
    368 
    369     return checkboxCell;
    370 }
    371 
    372 // FIXME: Share more code with radio buttons.
    373 static void paintCheckbox(ControlStates states, GraphicsContext* context, const IntRect& zoomedRect, float zoomFactor, ScrollView* scrollView)
    374 {
    375     BEGIN_BLOCK_OBJC_EXCEPTIONS
    376 
    377     // Determine the width and height needed for the control and prepare the cell for painting.
    378     NSButtonCell *checkboxCell = checkbox(states, zoomedRect, zoomFactor);
    379     LocalCurrentGraphicsContext localContext(context);
    380 
    381     context->save();
    382 
    383     NSControlSize controlSize = [checkboxCell controlSize];
    384     IntSize zoomedSize = checkboxSizes()[controlSize];
    385     zoomedSize.setWidth(zoomedSize.width() * zoomFactor);
    386     zoomedSize.setHeight(zoomedSize.height() * zoomFactor);
    387     IntRect inflatedRect = inflateRect(zoomedRect, zoomedSize, checkboxMargins(controlSize), zoomFactor);
    388 
    389     if (zoomFactor != 1.0f) {
    390         inflatedRect.setWidth(inflatedRect.width() / zoomFactor);
    391         inflatedRect.setHeight(inflatedRect.height() / zoomFactor);
    392         context->translate(inflatedRect.x(), inflatedRect.y());
    393         context->scale(FloatSize(zoomFactor, zoomFactor));
    394         context->translate(-inflatedRect.x(), -inflatedRect.y());
    395     }
    396 
    397     {
    398         FocusIndicationFix::ScopedFixer fix;
    399         [checkboxCell drawWithFrame:NSRect(inflatedRect) inView:FlippedView()];
    400     }
    401     [checkboxCell setControlView:nil];
    402 
    403     context->restore();
    404 
    405     END_BLOCK_OBJC_EXCEPTIONS
    406 }
    407 
    408 // Radio Buttons
    409 
    410 static const IntSize* radioSizes()
    411 {
    412     static const IntSize sizes[3] = { IntSize(14, 15), IntSize(12, 13), IntSize(10, 10) };
    413     return sizes;
    414 }
    415 
    416 static const int* radioMargins(NSControlSize controlSize)
    417 {
    418     static const int margins[3][4] =
    419     {
    420         { 2, 2, 4, 2 },
    421         { 3, 2, 3, 2 },
    422         { 1, 0, 2, 0 },
    423     };
    424     return margins[controlSize];
    425 }
    426 
    427 static LengthSize radioSize(const Font& font, const LengthSize& zoomedSize, float zoomFactor)
    428 {
    429     // If the width and height are both specified, then we have nothing to do.
    430     if (!zoomedSize.width().isIntrinsicOrAuto() && !zoomedSize.height().isIntrinsicOrAuto())
    431         return zoomedSize;
    432 
    433     // Use the font size to determine the intrinsic width of the control.
    434     return sizeFromFont(font, zoomedSize, zoomFactor, radioSizes());
    435 }
    436 
    437 static NSButtonCell *radio(ControlStates states, const IntRect& zoomedRect, float zoomFactor)
    438 {
    439     static NSButtonCell *radioCell;
    440     if (!radioCell) {
    441         radioCell = [[NSButtonCell alloc] init];
    442         [radioCell setButtonType:NSRadioButton];
    443         [radioCell setTitle:nil];
    444         [radioCell setFocusRingType:NSFocusRingTypeExterior];
    445     }
    446 
    447     // Set the control size based off the rectangle we're painting into.
    448     setControlSize(radioCell, radioSizes(), zoomedRect.size(), zoomFactor);
    449 
    450     // Update the various states we respond to.
    451     updateStates(radioCell, states);
    452 
    453     return radioCell;
    454 }
    455 
    456 static void paintRadio(ControlStates states, GraphicsContext* context, const IntRect& zoomedRect, float zoomFactor, ScrollView* scrollView)
    457 {
    458     // Determine the width and height needed for the control and prepare the cell for painting.
    459     NSButtonCell *radioCell = radio(states, zoomedRect, zoomFactor);
    460     LocalCurrentGraphicsContext localContext(context);
    461 
    462     context->save();
    463 
    464     NSControlSize controlSize = [radioCell controlSize];
    465     IntSize zoomedSize = radioSizes()[controlSize];
    466     zoomedSize.setWidth(zoomedSize.width() * zoomFactor);
    467     zoomedSize.setHeight(zoomedSize.height() * zoomFactor);
    468     IntRect inflatedRect = inflateRect(zoomedRect, zoomedSize, radioMargins(controlSize), zoomFactor);
    469 
    470     if (zoomFactor != 1.0f) {
    471         inflatedRect.setWidth(inflatedRect.width() / zoomFactor);
    472         inflatedRect.setHeight(inflatedRect.height() / zoomFactor);
    473         context->translate(inflatedRect.x(), inflatedRect.y());
    474         context->scale(FloatSize(zoomFactor, zoomFactor));
    475         context->translate(-inflatedRect.x(), -inflatedRect.y());
    476     }
    477 
    478     BEGIN_BLOCK_OBJC_EXCEPTIONS
    479     {
    480         FocusIndicationFix::ScopedFixer fix;
    481         [radioCell drawWithFrame:NSRect(inflatedRect) inView:FlippedView()];
    482     }
    483     [radioCell setControlView:nil];
    484     END_BLOCK_OBJC_EXCEPTIONS
    485 
    486     context->restore();
    487 }
    488 
    489 // Buttons
    490 
    491 // Buttons really only constrain height. They respect width.
    492 static const IntSize* buttonSizes()
    493 {
    494     static const IntSize sizes[3] = { IntSize(0, 21), IntSize(0, 18), IntSize(0, 15) };
    495     return sizes;
    496 }
    497 
    498 #if ENABLE(DATALIST)
    499 static const IntSize* listButtonSizes()
    500 {
    501     static const IntSize sizes[3] = { IntSize(21, 21), IntSize(19, 18), IntSize(17, 16) };
    502     return sizes;
    503 }
    504 #endif
    505 
    506 static const int* buttonMargins(NSControlSize controlSize)
    507 {
    508     static const int margins[3][4] =
    509     {
    510         { 4, 6, 7, 6 },
    511         { 4, 5, 6, 5 },
    512         { 0, 1, 1, 1 },
    513     };
    514     return margins[controlSize];
    515 }
    516 
    517 static void setupButtonCell(NSButtonCell *&buttonCell, ControlPart part, ControlStates states, const IntRect& zoomedRect, float zoomFactor)
    518 {
    519     if (!buttonCell) {
    520         buttonCell = [[NSButtonCell alloc] init];
    521         [buttonCell setTitle:nil];
    522         [buttonCell setButtonType:NSMomentaryPushInButton];
    523         if (states & DefaultState)
    524             [buttonCell setKeyEquivalent:@"\r"];
    525     }
    526 
    527     // Set the control size based off the rectangle we're painting into.
    528     const IntSize* sizes = buttonSizes();
    529 #if ENABLE(DATALIST)
    530     if (part == ListButtonPart) {
    531         [buttonCell setBezelStyle:NSRoundedDisclosureBezelStyle];
    532         sizes = listButtonSizes();
    533     } else
    534 #endif
    535     if (part == SquareButtonPart || zoomedRect.height() > buttonSizes()[NSRegularControlSize].height() * zoomFactor) {
    536         // Use the square button
    537         if ([buttonCell bezelStyle] != NSShadowlessSquareBezelStyle)
    538             [buttonCell setBezelStyle:NSShadowlessSquareBezelStyle];
    539     } else if ([buttonCell bezelStyle] != NSRoundedBezelStyle)
    540         [buttonCell setBezelStyle:NSRoundedBezelStyle];
    541 
    542     setControlSize(buttonCell, sizes, zoomedRect.size(), zoomFactor);
    543 
    544     // Update the various states we respond to.
    545     updateStates(buttonCell, states);
    546 }
    547 
    548 static NSButtonCell *button(ControlPart part, ControlStates states, const IntRect& zoomedRect, float zoomFactor)
    549 {
    550     bool isDefault = states & DefaultState;
    551     static NSButtonCell *cells[2];
    552     setupButtonCell(cells[isDefault], part, states, zoomedRect, zoomFactor);
    553     return cells[isDefault];
    554 }
    555 
    556 static void paintButton(ControlPart part, ControlStates states, GraphicsContext* context, const IntRect& zoomedRect, float zoomFactor, ScrollView* scrollView)
    557 {
    558     BEGIN_BLOCK_OBJC_EXCEPTIONS
    559 
    560     // Determine the width and height needed for the control and prepare the cell for painting.
    561     NSButtonCell *buttonCell = button(part, states, zoomedRect, zoomFactor);
    562     LocalCurrentGraphicsContext localContext(context);
    563 
    564     NSControlSize controlSize = [buttonCell controlSize];
    565 #if ENABLE(DATALIST)
    566     IntSize zoomedSize = (part == ListButtonPart ? listButtonSizes() : buttonSizes())[controlSize];
    567 #else
    568     IntSize zoomedSize = buttonSizes()[controlSize];
    569 #endif
    570     zoomedSize.setWidth(zoomedRect.width()); // Buttons don't ever constrain width, so the zoomed width can just be honored.
    571     zoomedSize.setHeight(zoomedSize.height() * zoomFactor);
    572     IntRect inflatedRect = zoomedRect;
    573     if ([buttonCell bezelStyle] == NSRoundedBezelStyle) {
    574         // Center the button within the available space.
    575         if (inflatedRect.height() > zoomedSize.height()) {
    576             inflatedRect.setY(inflatedRect.y() + (inflatedRect.height() - zoomedSize.height()) / 2);
    577             inflatedRect.setHeight(zoomedSize.height());
    578         }
    579 
    580         // Now inflate it to account for the shadow.
    581         inflatedRect = inflateRect(inflatedRect, zoomedSize, buttonMargins(controlSize), zoomFactor);
    582 
    583         if (zoomFactor != 1.0f) {
    584             inflatedRect.setWidth(inflatedRect.width() / zoomFactor);
    585             inflatedRect.setHeight(inflatedRect.height() / zoomFactor);
    586             context->translate(inflatedRect.x(), inflatedRect.y());
    587             context->scale(FloatSize(zoomFactor, zoomFactor));
    588             context->translate(-inflatedRect.x(), -inflatedRect.y());
    589         }
    590     }
    591 
    592     {
    593         FocusIndicationFix::ScopedFixer fix;
    594         [buttonCell drawWithFrame:NSRect(inflatedRect) inView:FlippedView()];
    595     }
    596     [buttonCell setControlView:nil];
    597 
    598     END_BLOCK_OBJC_EXCEPTIONS
    599 }
    600 
    601 // Stepper
    602 
    603 static const IntSize* stepperSizes()
    604 {
    605     static const IntSize sizes[3] = { IntSize(19, 27), IntSize(15, 22), IntSize(13, 15) };
    606     return sizes;
    607 }
    608 
    609 // We don't use controlSizeForFont() for steppers because the stepper height
    610 // should be equal to or less than the corresponding text field height,
    611 static NSControlSize stepperControlSizeForFont(const Font& font)
    612 {
    613     int fontSize = font.pixelSize();
    614     if (fontSize >= 18)
    615         return NSRegularControlSize;
    616     if (fontSize >= 13)
    617         return NSSmallControlSize;
    618     return NSMiniControlSize;
    619 }
    620 
    621 static void paintStepper(ControlStates states, GraphicsContext* context, const IntRect& zoomedRect, float zoomFactor, ScrollView*)
    622 {
    623     // We don't use NSStepperCell because there are no ways to draw an
    624     // NSStepperCell with the up button highlighted.
    625 
    626     HIThemeButtonDrawInfo drawInfo;
    627     drawInfo.version = 0;
    628     drawInfo.state = convertControlStatesToThemeDrawState(kThemeIncDecButton, states);
    629     drawInfo.adornment = kThemeAdornmentDefault;
    630     ControlSize controlSize = controlSizeFromPixelSize(stepperSizes(), zoomedRect.size(), zoomFactor);
    631     if (controlSize == NSSmallControlSize)
    632         drawInfo.kind = kThemeIncDecButtonSmall;
    633     else if (controlSize == NSMiniControlSize)
    634         drawInfo.kind = kThemeIncDecButtonMini;
    635     else
    636         drawInfo.kind = kThemeIncDecButton;
    637 
    638     IntRect rect(zoomedRect);
    639     context->save();
    640     if (zoomFactor != 1.0f) {
    641         rect.setWidth(rect.width() / zoomFactor);
    642         rect.setHeight(rect.height() / zoomFactor);
    643         context->translate(rect.x(), rect.y());
    644         context->scale(FloatSize(zoomFactor, zoomFactor));
    645         context->translate(-rect.x(), -rect.y());
    646     }
    647     CGRect bounds(rect);
    648     // Adjust 'bounds' so that HIThemeDrawButton(bounds,...) draws exactly on 'rect'.
    649     CGRect backgroundBounds;
    650     HIThemeGetButtonBackgroundBounds(&bounds, &drawInfo, &backgroundBounds);
    651     if (bounds.origin.x != backgroundBounds.origin.x)
    652         bounds.origin.x += bounds.origin.x - backgroundBounds.origin.x;
    653     if (bounds.origin.y != backgroundBounds.origin.y)
    654         bounds.origin.y += bounds.origin.y - backgroundBounds.origin.y;
    655     HIThemeDrawButton(&bounds, &drawInfo, context->platformContext(), kHIThemeOrientationNormal, 0);
    656     context->restore();
    657 }
    658 
    659 // Theme overrides
    660 
    661 int ThemeChromiumMac::baselinePositionAdjustment(ControlPart part) const
    662 {
    663     if (part == CheckboxPart || part == RadioPart)
    664         return -2;
    665     return Theme::baselinePositionAdjustment(part);
    666 }
    667 
    668 FontDescription ThemeChromiumMac::controlFont(ControlPart part, const Font& font, float zoomFactor) const
    669 {
    670     switch (part) {
    671         case PushButtonPart: {
    672             FontDescription fontDescription;
    673             fontDescription.setIsAbsoluteSize(true);
    674             fontDescription.setGenericFamily(FontDescription::SerifFamily);
    675 
    676             NSFont* nsFont = [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:controlSizeForFont(font)]];
    677             fontDescription.firstFamily().setFamily([nsFont familyName]);
    678             fontDescription.setComputedSize([nsFont pointSize] * zoomFactor);
    679             fontDescription.setSpecifiedSize([nsFont pointSize] * zoomFactor);
    680             return fontDescription;
    681         }
    682         default:
    683             return Theme::controlFont(part, font, zoomFactor);
    684     }
    685 }
    686 
    687 LengthSize ThemeChromiumMac::controlSize(ControlPart part, const Font& font, const LengthSize& zoomedSize, float zoomFactor) const
    688 {
    689     switch (part) {
    690         case CheckboxPart:
    691             return checkboxSize(font, zoomedSize, zoomFactor);
    692         case RadioPart:
    693             return radioSize(font, zoomedSize, zoomFactor);
    694         case PushButtonPart:
    695             // Height is reset to auto so that specified heights can be ignored.
    696             return sizeFromFont(font, LengthSize(zoomedSize.width(), Length()), zoomFactor, buttonSizes());
    697 #if ENABLE(DATALIST)
    698         case ListButtonPart:
    699             return sizeFromFont(font, LengthSize(zoomedSize.width(), Length()), zoomFactor, listButtonSizes());
    700 #endif
    701         case InnerSpinButtonPart:
    702             // We don't use inner spin buttons on Mac.
    703             return LengthSize(Length(Fixed), Length(Fixed));
    704         case OuterSpinButtonPart:
    705             if (!zoomedSize.width().isIntrinsicOrAuto() && !zoomedSize.height().isIntrinsicOrAuto())
    706                 return zoomedSize;
    707             return sizeFromNSControlSize(stepperControlSizeForFont(font), zoomedSize, zoomFactor, stepperSizes());
    708         default:
    709             return zoomedSize;
    710     }
    711 }
    712 
    713 LengthSize ThemeChromiumMac::minimumControlSize(ControlPart part, const Font& font, float zoomFactor) const
    714 {
    715     switch (part) {
    716         case SquareButtonPart:
    717         case DefaultButtonPart:
    718         case ButtonPart:
    719         case ListButtonPart:
    720             return LengthSize(Length(0, Fixed), Length(static_cast<int>(15 * zoomFactor), Fixed));
    721         case InnerSpinButtonPart:
    722             // We don't use inner spin buttons on Mac.
    723             return LengthSize(Length(Fixed), Length(Fixed));
    724         case OuterSpinButtonPart: {
    725             IntSize base = stepperSizes()[NSMiniControlSize];
    726             return LengthSize(Length(static_cast<int>(base.width() * zoomFactor), Fixed),
    727                               Length(static_cast<int>(base.height() * zoomFactor), Fixed));
    728         }
    729         default:
    730             return Theme::minimumControlSize(part, font, zoomFactor);
    731     }
    732 }
    733 
    734 LengthBox ThemeChromiumMac::controlBorder(ControlPart part, const Font& font, const LengthBox& zoomedBox, float zoomFactor) const
    735 {
    736     switch (part) {
    737         case SquareButtonPart:
    738         case DefaultButtonPart:
    739         case ButtonPart:
    740         case ListButtonPart:
    741             return LengthBox(0, zoomedBox.right().value(), 0, zoomedBox.left().value());
    742         default:
    743             return Theme::controlBorder(part, font, zoomedBox, zoomFactor);
    744     }
    745 }
    746 
    747 LengthBox ThemeChromiumMac::controlPadding(ControlPart part, const Font& font, const LengthBox& zoomedBox, float zoomFactor) const
    748 {
    749     switch (part) {
    750         case PushButtonPart: {
    751             // Just use 8px.  AppKit wants to use 11px for mini buttons, but that padding is just too large
    752             // for real-world Web sites (creating a huge necessary minimum width for buttons whose space is
    753             // by definition constrained, since we select mini only for small cramped environments.
    754             // This also guarantees the HTML <button> will match our rendering by default, since we're using a consistent
    755             // padding.
    756             const int padding = 8 * zoomFactor;
    757             return LengthBox(0, padding, 0, padding);
    758         }
    759         default:
    760             return Theme::controlPadding(part, font, zoomedBox, zoomFactor);
    761     }
    762 }
    763 
    764 void ThemeChromiumMac::inflateControlPaintRect(ControlPart part, ControlStates states, IntRect& zoomedRect, float zoomFactor) const
    765 {
    766     BEGIN_BLOCK_OBJC_EXCEPTIONS
    767     switch (part) {
    768         case CheckboxPart: {
    769             // We inflate the rect as needed to account for padding included in the cell to accommodate the checkbox
    770             // shadow" and the check.  We don't consider this part of the bounds of the control in WebKit.
    771             NSCell *cell = checkbox(states, zoomedRect, zoomFactor);
    772             NSControlSize controlSize = [cell controlSize];
    773             IntSize zoomedSize = checkboxSizes()[controlSize];
    774             zoomedSize.setHeight(zoomedSize.height() * zoomFactor);
    775             zoomedSize.setWidth(zoomedSize.width() * zoomFactor);
    776             zoomedRect = inflateRect(zoomedRect, zoomedSize, checkboxMargins(controlSize), zoomFactor);
    777             break;
    778         }
    779         case RadioPart: {
    780             // We inflate the rect as needed to account for padding included in the cell to accommodate the radio button
    781             // shadow".  We don't consider this part of the bounds of the control in WebKit.
    782             NSCell *cell = radio(states, zoomedRect, zoomFactor);
    783             NSControlSize controlSize = [cell controlSize];
    784             IntSize zoomedSize = radioSizes()[controlSize];
    785             zoomedSize.setHeight(zoomedSize.height() * zoomFactor);
    786             zoomedSize.setWidth(zoomedSize.width() * zoomFactor);
    787             zoomedRect = inflateRect(zoomedRect, zoomedSize, radioMargins(controlSize), zoomFactor);
    788             break;
    789         }
    790         case PushButtonPart:
    791         case DefaultButtonPart:
    792         case ButtonPart: {
    793             NSButtonCell *cell = button(part, states, zoomedRect, zoomFactor);
    794             NSControlSize controlSize = [cell controlSize];
    795 
    796             // We inflate the rect as needed to account for the Aqua button's shadow.
    797             if ([cell bezelStyle] == NSRoundedBezelStyle) {
    798                 IntSize zoomedSize = buttonSizes()[controlSize];
    799                 zoomedSize.setHeight(zoomedSize.height() * zoomFactor);
    800                 zoomedSize.setWidth(zoomedRect.width()); // Buttons don't ever constrain width, so the zoomed width can just be honored.
    801                 zoomedRect = inflateRect(zoomedRect, zoomedSize, buttonMargins(controlSize), zoomFactor);
    802             }
    803             break;
    804         }
    805         case OuterSpinButtonPart: {
    806             static const int stepperMargin[4] = { 0, 0, 0, 0 };
    807             ControlSize controlSize = controlSizeFromPixelSize(stepperSizes(), zoomedRect.size(), zoomFactor);
    808             IntSize zoomedSize = stepperSizes()[controlSize];
    809             zoomedSize.setHeight(zoomedSize.height() * zoomFactor);
    810             zoomedSize.setWidth(zoomedSize.width() * zoomFactor);
    811             zoomedRect = inflateRect(zoomedRect, zoomedSize, stepperMargin, zoomFactor);
    812             break;
    813         }
    814         default:
    815             break;
    816     }
    817     END_BLOCK_OBJC_EXCEPTIONS
    818 }
    819 
    820 void ThemeChromiumMac::paint(ControlPart part, ControlStates states, GraphicsContext* context, const IntRect& zoomedRect, float zoomFactor, ScrollView* scrollView) const
    821 {
    822     switch (part) {
    823         case CheckboxPart:
    824             paintCheckbox(states, context, zoomedRect, zoomFactor, scrollView);
    825             break;
    826         case RadioPart:
    827             paintRadio(states, context, zoomedRect, zoomFactor, scrollView);
    828             break;
    829         case PushButtonPart:
    830         case DefaultButtonPart:
    831         case ButtonPart:
    832         case SquareButtonPart:
    833         case ListButtonPart:
    834             paintButton(part, states, context, zoomedRect, zoomFactor, scrollView);
    835             break;
    836         case OuterSpinButtonPart:
    837             paintStepper(states, context, zoomedRect, zoomFactor, scrollView);
    838             break;
    839         default:
    840             break;
    841     }
    842 }
    843 
    844 }
    845