ThemeChromiumMac.mm   [plain text]


/*
 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
 * Copyright (C) 2009 Google Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#import "config.h"
#import "ThemeChromiumMac.h"

#import "BlockExceptions.h"
#import "GraphicsContext.h"
#import "LocalCurrentGraphicsContext.h"
#import "ScrollView.h"
#import "WebCoreSystemInterface.h"
#import <Carbon/Carbon.h>
#include <wtf/StdLibExtras.h>
#import <objc/runtime.h>

using namespace std;

// This file (and its associated .h file) is a clone of ThemeMac.mm.
// Because the original file is designed to run in-process inside a Cocoa view,
// we must maintain a fork. Please maintain this file by performing parallel
// changes to it.
//
// The only changes from ThemeMac should be:
// - The classname change from ThemeMac to ThemeChromiumMac.
// - The import of FlippedView() and its use as the parent view for cell
//   rendering.
// - In updateStates() the code to update the cells' inactive state.
// - In paintButton() the code to save/restore the window's default button cell.
// - The Snow Leopard focus ring bug fix and its use around every call to
//   -[NSButtonCell drawWithFrame:inView:].
//
// For all other differences, if it was introduced in this file, then the
// maintainer forgot to include it in the list; otherwise it is an update that
// should have been applied to this file but was not.

// FIXME: Default buttons really should be more like push buttons and not like buttons.

// --- START fix for Snow Leopard focus ring bug ---

// There is a bug in the Cocoa focus ring drawing code. The code calls +[NSView
// focusView] (to get the currently focused view) and then calls an NSRect-
// returning method on that view to obtain a clipping rect. However, if there is
// no focused view (as there won't be if the destination is a context), the rect
// returned from the method invocation on nil is garbage.
//
// The garbage fortunately does not clip the focus ring on Leopard, but
// unfortunately does so on Snow Leopard. Therefore, if a runtime test shows
// that focus ring drawing fails, we swizzle NSView to ensure it returns a valid
// view with a valid clipping rectangle.
//
// FIXME: After the referenced bug is fixed on all supported platforms, remove
// this code.
//
// References:
//  <http://crbug.com/27493>
//  <rdar://problem/7604051> (<http://openradar.appspot.com/7604051>)

@interface TCMVisibleView : NSView

@end

@implementation TCMVisibleView

- (struct CGRect)_focusRingVisibleRect
{
    return CGRectZero;
}

- (id)_focusRingClipAncestor
{
    return self;
}

@end

@interface NSView (TCMInterposing)
+ (NSView *)TCMInterposing_focusView;
@end

namespace FocusIndicationFix {

bool currentOSHasSetFocusRingStyleInBitmapBug()
{
    UInt32 pixel = 0;
    UInt32* pixelPlane = &pixel;
    UInt32** pixelPlanes = &pixelPlane;
    NSBitmapImageRep *bitmap = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:(UInt8**)pixelPlanes
                                                                       pixelsWide:1
                                                                       pixelsHigh:1
                                                                    bitsPerSample:8
                                                                  samplesPerPixel:4
                                                                         hasAlpha:YES
                                                                         isPlanar:NO
                                                                   colorSpaceName:NSCalibratedRGBColorSpace
                                                                     bitmapFormat:NSAlphaFirstBitmapFormat
                                                                      bytesPerRow:4
                                                                     bitsPerPixel:32];
    [NSGraphicsContext saveGraphicsState];
    [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithBitmapImageRep:bitmap]];
    NSSetFocusRingStyle(NSFocusRingOnly);
    NSRectFill(NSMakeRect(0, 0, 1, 1));
    [NSGraphicsContext restoreGraphicsState];
    [bitmap release];

    return !pixel;
}

bool swizzleFocusView()
{
    if (!currentOSHasSetFocusRingStyleInBitmapBug())
        return false;

    Class nsview = [NSView class];
    Method m1 = class_getClassMethod(nsview, @selector(focusView));
    Method m2 = class_getClassMethod(nsview, @selector(TCMInterposing_focusView));
    if (m1 && m2) {
        method_exchangeImplementations(m1, m2);
        return true;
    }

    return false;
}

static bool interpose = false;

// A class to restrict the amount of time spent messing with interposing. It
// only stacks one-deep.
class ScopedFixer {
public:
    ScopedFixer()
    {
        static bool swizzled = swizzleFocusView();
        interpose = swizzled;
    }

    ~ScopedFixer()
    {
        interpose = false;
    }
};

}  // namespace FocusIndicationFix

@implementation NSView (TCMInterposing)

+ (NSView *)TCMInterposing_focusView
{
    NSView *view = [self TCMInterposing_focusView];  // call original (was swizzled)
    if (!view && FocusIndicationFix::interpose) {
        static TCMVisibleView* fixedView = [[TCMVisibleView alloc] init];
        view = fixedView;
    }

    return view;
}

@end

// --- END fix for Snow Leopard focus ring bug ---

namespace WebCore {

// Pick up utility function from RenderThemeChromiumMac.
extern NSView* FlippedView();

enum {
    topMargin,
    rightMargin,
    bottomMargin,
    leftMargin
};

Theme* platformTheme()
{
    DEFINE_STATIC_LOCAL(ThemeChromiumMac, themeMac, ());
    return &themeMac;
}

// Helper functions used by a bunch of different control parts.

static NSControlSize controlSizeForFont(const Font& font)
{
    int fontSize = font.pixelSize();
    if (fontSize >= 16)
        return NSRegularControlSize;
    if (fontSize >= 11)
        return NSSmallControlSize;
    return NSMiniControlSize;
}

static LengthSize sizeFromNSControlSize(NSControlSize nsControlSize, const LengthSize& zoomedSize, float zoomFactor, const IntSize* sizes)
{
    IntSize controlSize = sizes[nsControlSize];
    if (zoomFactor != 1.0f)
        controlSize = IntSize(controlSize.width() * zoomFactor, controlSize.height() * zoomFactor);
    LengthSize result = zoomedSize;
    if (zoomedSize.width().isIntrinsicOrAuto() && controlSize.width() > 0)
        result.setWidth(Length(controlSize.width(), Fixed));
    if (zoomedSize.height().isIntrinsicOrAuto() && controlSize.height() > 0)
        result.setHeight(Length(controlSize.height(), Fixed));
    return result;
}

static LengthSize sizeFromFont(const Font& font, const LengthSize& zoomedSize, float zoomFactor, const IntSize* sizes)
{
    return sizeFromNSControlSize(controlSizeForFont(font), zoomedSize, zoomFactor, sizes);
}

static ControlSize controlSizeFromPixelSize(const IntSize* sizes, const IntSize& minZoomedSize, float zoomFactor)
{
    if (minZoomedSize.width() >= static_cast<int>(sizes[NSRegularControlSize].width() * zoomFactor) &&
        minZoomedSize.height() >= static_cast<int>(sizes[NSRegularControlSize].height() * zoomFactor))
        return NSRegularControlSize;
    if (minZoomedSize.width() >= static_cast<int>(sizes[NSSmallControlSize].width() * zoomFactor) &&
        minZoomedSize.height() >= static_cast<int>(sizes[NSSmallControlSize].height() * zoomFactor))
        return NSSmallControlSize;
    return NSMiniControlSize;
}

static void setControlSize(NSCell* cell, const IntSize* sizes, const IntSize& minZoomedSize, float zoomFactor)
{
    ControlSize size = controlSizeFromPixelSize(sizes, minZoomedSize, zoomFactor);
    if (size != [cell controlSize]) // Only update if we have to, since AppKit does work even if the size is the same.
        [cell setControlSize:(NSControlSize)size];
}

static void updateStates(NSCell* cell, ControlStates states)
{
    // Hover state is not supported by Aqua.

    // Pressed state
    bool oldPressed = [cell isHighlighted];
    bool pressed = states & PressedState;
    if (pressed != oldPressed)
        [cell setHighlighted:pressed];

    // Enabled state
    bool oldEnabled = [cell isEnabled];
    bool enabled = states & EnabledState;
    if (enabled != oldEnabled)
        [cell setEnabled:enabled];

    // Focused state
    bool oldFocused = [cell showsFirstResponder];
    bool focused = states & FocusState;
    if (focused != oldFocused)
        [cell setShowsFirstResponder:focused];

    // Checked and Indeterminate
    bool oldIndeterminate = [cell state] == NSMixedState;
    bool indeterminate = (states & IndeterminateState);
    bool checked = states & CheckedState;
    bool oldChecked = [cell state] == NSOnState;
    if (oldIndeterminate != indeterminate || checked != oldChecked)
        [cell setState:indeterminate ? NSMixedState : (checked ? NSOnState : NSOffState)];

    // Window Inactive state
    NSControlTint oldTint = [cell controlTint];
    bool windowInactive = (states & WindowInactiveState);
    NSControlTint tint = windowInactive ? static_cast<NSControlTint>(NSClearControlTint)
                                        : [NSColor currentControlTint];
    if (tint != oldTint)
        [cell setControlTint:tint];
}

static ThemeDrawState convertControlStatesToThemeDrawState(ThemeButtonKind kind, ControlStates states)
{
    if (states & ReadOnlyState)
        return kThemeStateUnavailableInactive;
    if (!(states & EnabledState))
        return kThemeStateUnavailableInactive;

    // Do not process PressedState if !EnabledState or ReadOnlyState.
    if (states & PressedState) {
        if (kind == kThemeIncDecButton || kind == kThemeIncDecButtonSmall || kind == kThemeIncDecButtonMini)
            return states & SpinUpState ? kThemeStatePressedUp : kThemeStatePressedDown;
        return kThemeStatePressed;
    }
    return kThemeStateActive;
}

static IntRect inflateRect(const IntRect& zoomedRect, const IntSize& zoomedSize, const int* margins, float zoomFactor)
{
    // Only do the inflation if the available width/height are too small.  Otherwise try to
    // fit the glow/check space into the available box's width/height.
    int widthDelta = zoomedRect.width() - (zoomedSize.width() + margins[leftMargin] * zoomFactor + margins[rightMargin] * zoomFactor);
    int heightDelta = zoomedRect.height() - (zoomedSize.height() + margins[topMargin] * zoomFactor + margins[bottomMargin] * zoomFactor);
    IntRect result(zoomedRect);
    if (widthDelta < 0) {
        result.setX(result.x() - margins[leftMargin] * zoomFactor);
        result.setWidth(result.width() - widthDelta);
    }
    if (heightDelta < 0) {
        result.setY(result.y() - margins[topMargin] * zoomFactor);
        result.setHeight(result.height() - heightDelta);
    }
    return result;
}

// Checkboxes

static const IntSize* checkboxSizes()
{
    static const IntSize sizes[3] = { IntSize(14, 14), IntSize(12, 12), IntSize(10, 10) };
    return sizes;
}

static const int* checkboxMargins(NSControlSize controlSize)
{
    static const int margins[3][4] =
    {
        { 3, 4, 4, 2 },
        { 4, 3, 3, 3 },
        { 4, 3, 3, 3 },
    };
    return margins[controlSize];
}

static LengthSize checkboxSize(const Font& font, const LengthSize& zoomedSize, float zoomFactor)
{
    // If the width and height are both specified, then we have nothing to do.
    if (!zoomedSize.width().isIntrinsicOrAuto() && !zoomedSize.height().isIntrinsicOrAuto())
        return zoomedSize;

    // Use the font size to determine the intrinsic width of the control.
    return sizeFromFont(font, zoomedSize, zoomFactor, checkboxSizes());
}

static NSButtonCell *checkbox(ControlStates states, const IntRect& zoomedRect, float zoomFactor)
{
    static NSButtonCell *checkboxCell;
    if (!checkboxCell) {
        checkboxCell = [[NSButtonCell alloc] init];
        [checkboxCell setButtonType:NSSwitchButton];
        [checkboxCell setTitle:nil];
        [checkboxCell setAllowsMixedState:YES];
        [checkboxCell setFocusRingType:NSFocusRingTypeExterior];
    }

    // Set the control size based off the rectangle we're painting into.
    setControlSize(checkboxCell, checkboxSizes(), zoomedRect.size(), zoomFactor);

    // Update the various states we respond to.
    updateStates(checkboxCell, states);

    return checkboxCell;
}

// FIXME: Share more code with radio buttons.
static void paintCheckbox(ControlStates states, GraphicsContext* context, const IntRect& zoomedRect, float zoomFactor, ScrollView* scrollView)
{
    BEGIN_BLOCK_OBJC_EXCEPTIONS

    // Determine the width and height needed for the control and prepare the cell for painting.
    NSButtonCell *checkboxCell = checkbox(states, zoomedRect, zoomFactor);
    LocalCurrentGraphicsContext localContext(context);

    context->save();

    NSControlSize controlSize = [checkboxCell controlSize];
    IntSize zoomedSize = checkboxSizes()[controlSize];
    zoomedSize.setWidth(zoomedSize.width() * zoomFactor);
    zoomedSize.setHeight(zoomedSize.height() * zoomFactor);
    IntRect inflatedRect = inflateRect(zoomedRect, zoomedSize, checkboxMargins(controlSize), zoomFactor);

    if (zoomFactor != 1.0f) {
        inflatedRect.setWidth(inflatedRect.width() / zoomFactor);
        inflatedRect.setHeight(inflatedRect.height() / zoomFactor);
        context->translate(inflatedRect.x(), inflatedRect.y());
        context->scale(FloatSize(zoomFactor, zoomFactor));
        context->translate(-inflatedRect.x(), -inflatedRect.y());
    }

    {
        FocusIndicationFix::ScopedFixer fix;
        [checkboxCell drawWithFrame:NSRect(inflatedRect) inView:FlippedView()];
    }
    [checkboxCell setControlView:nil];

    context->restore();

    END_BLOCK_OBJC_EXCEPTIONS
}

// Radio Buttons

static const IntSize* radioSizes()
{
    static const IntSize sizes[3] = { IntSize(14, 15), IntSize(12, 13), IntSize(10, 10) };
    return sizes;
}

static const int* radioMargins(NSControlSize controlSize)
{
    static const int margins[3][4] =
    {
        { 2, 2, 4, 2 },
        { 3, 2, 3, 2 },
        { 1, 0, 2, 0 },
    };
    return margins[controlSize];
}

static LengthSize radioSize(const Font& font, const LengthSize& zoomedSize, float zoomFactor)
{
    // If the width and height are both specified, then we have nothing to do.
    if (!zoomedSize.width().isIntrinsicOrAuto() && !zoomedSize.height().isIntrinsicOrAuto())
        return zoomedSize;

    // Use the font size to determine the intrinsic width of the control.
    return sizeFromFont(font, zoomedSize, zoomFactor, radioSizes());
}

static NSButtonCell *radio(ControlStates states, const IntRect& zoomedRect, float zoomFactor)
{
    static NSButtonCell *radioCell;
    if (!radioCell) {
        radioCell = [[NSButtonCell alloc] init];
        [radioCell setButtonType:NSRadioButton];
        [radioCell setTitle:nil];
        [radioCell setFocusRingType:NSFocusRingTypeExterior];
    }

    // Set the control size based off the rectangle we're painting into.
    setControlSize(radioCell, radioSizes(), zoomedRect.size(), zoomFactor);

    // Update the various states we respond to.
    updateStates(radioCell, states);

    return radioCell;
}

static void paintRadio(ControlStates states, GraphicsContext* context, const IntRect& zoomedRect, float zoomFactor, ScrollView* scrollView)
{
    // Determine the width and height needed for the control and prepare the cell for painting.
    NSButtonCell *radioCell = radio(states, zoomedRect, zoomFactor);
    LocalCurrentGraphicsContext localContext(context);

    context->save();

    NSControlSize controlSize = [radioCell controlSize];
    IntSize zoomedSize = radioSizes()[controlSize];
    zoomedSize.setWidth(zoomedSize.width() * zoomFactor);
    zoomedSize.setHeight(zoomedSize.height() * zoomFactor);
    IntRect inflatedRect = inflateRect(zoomedRect, zoomedSize, radioMargins(controlSize), zoomFactor);

    if (zoomFactor != 1.0f) {
        inflatedRect.setWidth(inflatedRect.width() / zoomFactor);
        inflatedRect.setHeight(inflatedRect.height() / zoomFactor);
        context->translate(inflatedRect.x(), inflatedRect.y());
        context->scale(FloatSize(zoomFactor, zoomFactor));
        context->translate(-inflatedRect.x(), -inflatedRect.y());
    }

    BEGIN_BLOCK_OBJC_EXCEPTIONS
    {
        FocusIndicationFix::ScopedFixer fix;
        [radioCell drawWithFrame:NSRect(inflatedRect) inView:FlippedView()];
    }
    [radioCell setControlView:nil];
    END_BLOCK_OBJC_EXCEPTIONS

    context->restore();
}

// Buttons

// Buttons really only constrain height. They respect width.
static const IntSize* buttonSizes()
{
    static const IntSize sizes[3] = { IntSize(0, 21), IntSize(0, 18), IntSize(0, 15) };
    return sizes;
}

#if ENABLE(DATALIST)
static const IntSize* listButtonSizes()
{
    static const IntSize sizes[3] = { IntSize(21, 21), IntSize(19, 18), IntSize(17, 16) };
    return sizes;
}
#endif

static const int* buttonMargins(NSControlSize controlSize)
{
    static const int margins[3][4] =
    {
        { 4, 6, 7, 6 },
        { 4, 5, 6, 5 },
        { 0, 1, 1, 1 },
    };
    return margins[controlSize];
}

static void setupButtonCell(NSButtonCell *&buttonCell, ControlPart part, ControlStates states, const IntRect& zoomedRect, float zoomFactor)
{
    if (!buttonCell) {
        buttonCell = [[NSButtonCell alloc] init];
        [buttonCell setTitle:nil];
        [buttonCell setButtonType:NSMomentaryPushInButton];
        if (states & DefaultState)
            [buttonCell setKeyEquivalent:@"\r"];
    }

    // Set the control size based off the rectangle we're painting into.
    const IntSize* sizes = buttonSizes();
#if ENABLE(DATALIST)
    if (part == ListButtonPart) {
        [buttonCell setBezelStyle:NSRoundedDisclosureBezelStyle];
        sizes = listButtonSizes();
    } else
#endif
    if (part == SquareButtonPart || zoomedRect.height() > buttonSizes()[NSRegularControlSize].height() * zoomFactor) {
        // Use the square button
        if ([buttonCell bezelStyle] != NSShadowlessSquareBezelStyle)
            [buttonCell setBezelStyle:NSShadowlessSquareBezelStyle];
    } else if ([buttonCell bezelStyle] != NSRoundedBezelStyle)
        [buttonCell setBezelStyle:NSRoundedBezelStyle];

    setControlSize(buttonCell, sizes, zoomedRect.size(), zoomFactor);

    // Update the various states we respond to.
    updateStates(buttonCell, states);
}

static NSButtonCell *button(ControlPart part, ControlStates states, const IntRect& zoomedRect, float zoomFactor)
{
    bool isDefault = states & DefaultState;
    static NSButtonCell *cells[2];
    setupButtonCell(cells[isDefault], part, states, zoomedRect, zoomFactor);
    return cells[isDefault];
}

static void paintButton(ControlPart part, ControlStates states, GraphicsContext* context, const IntRect& zoomedRect, float zoomFactor, ScrollView* scrollView)
{
    BEGIN_BLOCK_OBJC_EXCEPTIONS

    // Determine the width and height needed for the control and prepare the cell for painting.
    NSButtonCell *buttonCell = button(part, states, zoomedRect, zoomFactor);
    LocalCurrentGraphicsContext localContext(context);

    NSControlSize controlSize = [buttonCell controlSize];
#if ENABLE(DATALIST)
    IntSize zoomedSize = (part == ListButtonPart ? listButtonSizes() : buttonSizes())[controlSize];
#else
    IntSize zoomedSize = buttonSizes()[controlSize];
#endif
    zoomedSize.setWidth(zoomedRect.width()); // Buttons don't ever constrain width, so the zoomed width can just be honored.
    zoomedSize.setHeight(zoomedSize.height() * zoomFactor);
    IntRect inflatedRect = zoomedRect;
    if ([buttonCell bezelStyle] == NSRoundedBezelStyle) {
        // Center the button within the available space.
        if (inflatedRect.height() > zoomedSize.height()) {
            inflatedRect.setY(inflatedRect.y() + (inflatedRect.height() - zoomedSize.height()) / 2);
            inflatedRect.setHeight(zoomedSize.height());
        }

        // Now inflate it to account for the shadow.
        inflatedRect = inflateRect(inflatedRect, zoomedSize, buttonMargins(controlSize), zoomFactor);

        if (zoomFactor != 1.0f) {
            inflatedRect.setWidth(inflatedRect.width() / zoomFactor);
            inflatedRect.setHeight(inflatedRect.height() / zoomFactor);
            context->translate(inflatedRect.x(), inflatedRect.y());
            context->scale(FloatSize(zoomFactor, zoomFactor));
            context->translate(-inflatedRect.x(), -inflatedRect.y());
        }
    }

    {
        FocusIndicationFix::ScopedFixer fix;
        [buttonCell drawWithFrame:NSRect(inflatedRect) inView:FlippedView()];
    }
    [buttonCell setControlView:nil];

    END_BLOCK_OBJC_EXCEPTIONS
}

// Stepper

static const IntSize* stepperSizes()
{
    static const IntSize sizes[3] = { IntSize(19, 27), IntSize(15, 22), IntSize(13, 15) };
    return sizes;
}

// We don't use controlSizeForFont() for steppers because the stepper height
// should be equal to or less than the corresponding text field height,
static NSControlSize stepperControlSizeForFont(const Font& font)
{
    int fontSize = font.pixelSize();
    if (fontSize >= 18)
        return NSRegularControlSize;
    if (fontSize >= 13)
        return NSSmallControlSize;
    return NSMiniControlSize;
}

static void paintStepper(ControlStates states, GraphicsContext* context, const IntRect& zoomedRect, float zoomFactor, ScrollView*)
{
    // We don't use NSStepperCell because there are no ways to draw an
    // NSStepperCell with the up button highlighted.

    HIThemeButtonDrawInfo drawInfo;
    drawInfo.version = 0;
    drawInfo.state = convertControlStatesToThemeDrawState(kThemeIncDecButton, states);
    drawInfo.adornment = kThemeAdornmentDefault;
    ControlSize controlSize = controlSizeFromPixelSize(stepperSizes(), zoomedRect.size(), zoomFactor);
    if (controlSize == NSSmallControlSize)
        drawInfo.kind = kThemeIncDecButtonSmall;
    else if (controlSize == NSMiniControlSize)
        drawInfo.kind = kThemeIncDecButtonMini;
    else
        drawInfo.kind = kThemeIncDecButton;

    IntRect rect(zoomedRect);
    context->save();
    if (zoomFactor != 1.0f) {
        rect.setWidth(rect.width() / zoomFactor);
        rect.setHeight(rect.height() / zoomFactor);
        context->translate(rect.x(), rect.y());
        context->scale(FloatSize(zoomFactor, zoomFactor));
        context->translate(-rect.x(), -rect.y());
    }
    CGRect bounds(rect);
    // Adjust 'bounds' so that HIThemeDrawButton(bounds,...) draws exactly on 'rect'.
    CGRect backgroundBounds;
    HIThemeGetButtonBackgroundBounds(&bounds, &drawInfo, &backgroundBounds);
    if (bounds.origin.x != backgroundBounds.origin.x)
        bounds.origin.x += bounds.origin.x - backgroundBounds.origin.x;
    if (bounds.origin.y != backgroundBounds.origin.y)
        bounds.origin.y += bounds.origin.y - backgroundBounds.origin.y;
    HIThemeDrawButton(&bounds, &drawInfo, context->platformContext(), kHIThemeOrientationNormal, 0);
    context->restore();
}

// Theme overrides

int ThemeChromiumMac::baselinePositionAdjustment(ControlPart part) const
{
    if (part == CheckboxPart || part == RadioPart)
        return -2;
    return Theme::baselinePositionAdjustment(part);
}

FontDescription ThemeChromiumMac::controlFont(ControlPart part, const Font& font, float zoomFactor) const
{
    switch (part) {
        case PushButtonPart: {
            FontDescription fontDescription;
            fontDescription.setIsAbsoluteSize(true);
            fontDescription.setGenericFamily(FontDescription::SerifFamily);

            NSFont* nsFont = [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:controlSizeForFont(font)]];
            fontDescription.firstFamily().setFamily([nsFont familyName]);
            fontDescription.setComputedSize([nsFont pointSize] * zoomFactor);
            fontDescription.setSpecifiedSize([nsFont pointSize] * zoomFactor);
            return fontDescription;
        }
        default:
            return Theme::controlFont(part, font, zoomFactor);
    }
}

LengthSize ThemeChromiumMac::controlSize(ControlPart part, const Font& font, const LengthSize& zoomedSize, float zoomFactor) const
{
    switch (part) {
        case CheckboxPart:
            return checkboxSize(font, zoomedSize, zoomFactor);
        case RadioPart:
            return radioSize(font, zoomedSize, zoomFactor);
        case PushButtonPart:
            // Height is reset to auto so that specified heights can be ignored.
            return sizeFromFont(font, LengthSize(zoomedSize.width(), Length()), zoomFactor, buttonSizes());
#if ENABLE(DATALIST)
        case ListButtonPart:
            return sizeFromFont(font, LengthSize(zoomedSize.width(), Length()), zoomFactor, listButtonSizes());
#endif
        case InnerSpinButtonPart:
            // We don't use inner spin buttons on Mac.
            return LengthSize(Length(Fixed), Length(Fixed));
        case OuterSpinButtonPart:
            if (!zoomedSize.width().isIntrinsicOrAuto() && !zoomedSize.height().isIntrinsicOrAuto())
                return zoomedSize;
            return sizeFromNSControlSize(stepperControlSizeForFont(font), zoomedSize, zoomFactor, stepperSizes());
        default:
            return zoomedSize;
    }
}

LengthSize ThemeChromiumMac::minimumControlSize(ControlPart part, const Font& font, float zoomFactor) const
{
    switch (part) {
        case SquareButtonPart:
        case DefaultButtonPart:
        case ButtonPart:
        case ListButtonPart:
            return LengthSize(Length(0, Fixed), Length(static_cast<int>(15 * zoomFactor), Fixed));
        case InnerSpinButtonPart:
            // We don't use inner spin buttons on Mac.
            return LengthSize(Length(Fixed), Length(Fixed));
        case OuterSpinButtonPart: {
            IntSize base = stepperSizes()[NSMiniControlSize];
            return LengthSize(Length(static_cast<int>(base.width() * zoomFactor), Fixed),
                              Length(static_cast<int>(base.height() * zoomFactor), Fixed));
        }
        default:
            return Theme::minimumControlSize(part, font, zoomFactor);
    }
}

LengthBox ThemeChromiumMac::controlBorder(ControlPart part, const Font& font, const LengthBox& zoomedBox, float zoomFactor) const
{
    switch (part) {
        case SquareButtonPart:
        case DefaultButtonPart:
        case ButtonPart:
        case ListButtonPart:
            return LengthBox(0, zoomedBox.right().value(), 0, zoomedBox.left().value());
        default:
            return Theme::controlBorder(part, font, zoomedBox, zoomFactor);
    }
}

LengthBox ThemeChromiumMac::controlPadding(ControlPart part, const Font& font, const LengthBox& zoomedBox, float zoomFactor) const
{
    switch (part) {
        case PushButtonPart: {
            // Just use 8px.  AppKit wants to use 11px for mini buttons, but that padding is just too large
            // for real-world Web sites (creating a huge necessary minimum width for buttons whose space is
            // by definition constrained, since we select mini only for small cramped environments.
            // This also guarantees the HTML <button> will match our rendering by default, since we're using a consistent
            // padding.
            const int padding = 8 * zoomFactor;
            return LengthBox(0, padding, 0, padding);
        }
        default:
            return Theme::controlPadding(part, font, zoomedBox, zoomFactor);
    }
}

void ThemeChromiumMac::inflateControlPaintRect(ControlPart part, ControlStates states, IntRect& zoomedRect, float zoomFactor) const
{
    BEGIN_BLOCK_OBJC_EXCEPTIONS
    switch (part) {
        case CheckboxPart: {
            // We inflate the rect as needed to account for padding included in the cell to accommodate the checkbox
            // shadow" and the check.  We don't consider this part of the bounds of the control in WebKit.
            NSCell *cell = checkbox(states, zoomedRect, zoomFactor);
            NSControlSize controlSize = [cell controlSize];
            IntSize zoomedSize = checkboxSizes()[controlSize];
            zoomedSize.setHeight(zoomedSize.height() * zoomFactor);
            zoomedSize.setWidth(zoomedSize.width() * zoomFactor);
            zoomedRect = inflateRect(zoomedRect, zoomedSize, checkboxMargins(controlSize), zoomFactor);
            break;
        }
        case RadioPart: {
            // We inflate the rect as needed to account for padding included in the cell to accommodate the radio button
            // shadow".  We don't consider this part of the bounds of the control in WebKit.
            NSCell *cell = radio(states, zoomedRect, zoomFactor);
            NSControlSize controlSize = [cell controlSize];
            IntSize zoomedSize = radioSizes()[controlSize];
            zoomedSize.setHeight(zoomedSize.height() * zoomFactor);
            zoomedSize.setWidth(zoomedSize.width() * zoomFactor);
            zoomedRect = inflateRect(zoomedRect, zoomedSize, radioMargins(controlSize), zoomFactor);
            break;
        }
        case PushButtonPart:
        case DefaultButtonPart:
        case ButtonPart: {
            NSButtonCell *cell = button(part, states, zoomedRect, zoomFactor);
            NSControlSize controlSize = [cell controlSize];

            // We inflate the rect as needed to account for the Aqua button's shadow.
            if ([cell bezelStyle] == NSRoundedBezelStyle) {
                IntSize zoomedSize = buttonSizes()[controlSize];
                zoomedSize.setHeight(zoomedSize.height() * zoomFactor);
                zoomedSize.setWidth(zoomedRect.width()); // Buttons don't ever constrain width, so the zoomed width can just be honored.
                zoomedRect = inflateRect(zoomedRect, zoomedSize, buttonMargins(controlSize), zoomFactor);
            }
            break;
        }
        case OuterSpinButtonPart: {
            static const int stepperMargin[4] = { 0, 0, 0, 0 };
            ControlSize controlSize = controlSizeFromPixelSize(stepperSizes(), zoomedRect.size(), zoomFactor);
            IntSize zoomedSize = stepperSizes()[controlSize];
            zoomedSize.setHeight(zoomedSize.height() * zoomFactor);
            zoomedSize.setWidth(zoomedSize.width() * zoomFactor);
            zoomedRect = inflateRect(zoomedRect, zoomedSize, stepperMargin, zoomFactor);
            break;
        }
        default:
            break;
    }
    END_BLOCK_OBJC_EXCEPTIONS
}

void ThemeChromiumMac::paint(ControlPart part, ControlStates states, GraphicsContext* context, const IntRect& zoomedRect, float zoomFactor, ScrollView* scrollView) const
{
    switch (part) {
        case CheckboxPart:
            paintCheckbox(states, context, zoomedRect, zoomFactor, scrollView);
            break;
        case RadioPart:
            paintRadio(states, context, zoomedRect, zoomFactor, scrollView);
            break;
        case PushButtonPart:
        case DefaultButtonPart:
        case ButtonPart:
        case SquareButtonPart:
        case ListButtonPart:
            paintButton(part, states, context, zoomedRect, zoomFactor, scrollView);
            break;
        case OuterSpinButtonPart:
            paintStepper(states, context, zoomedRect, zoomFactor, scrollView);
            break;
        default:
            break;
    }
}

}