Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/widget/cocoa/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 107 kB image not shown  

Quelle  nsNativeThemeCocoa.mm   Sprache: unbekannt

 
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "nsNativeThemeCocoa.h"
#include <objc/NSObjCRuntime.h>

#include "mozilla/gfx/2D.h"
#include "mozilla/gfx/Helpers.h"
#include "mozilla/gfx/PathHelpers.h"
#include "nsChildView.h"
#include "nsDeviceContext.h"
#include "nsLayoutUtils.h"
#include "nsObjCExceptions.h"
#include "nsNumberControlFrame.h"
#include "nsRangeFrame.h"
#include "nsRect.h"
#include "nsSize.h"
#include "nsStyleConsts.h"
#include "nsPresContext.h"
#include "nsIContent.h"
#include "mozilla/dom/Document.h"
#include "nsIFrame.h"
#include "nsAtom.h"
#include "nsNameSpaceManager.h"
#include "nsPresContext.h"
#include "nsGkAtoms.h"
#include "nsCocoaFeatures.h"
#include "nsCocoaWindow.h"
#include "nsNativeThemeColors.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/Range.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/HTMLMeterElement.h"
#include "mozilla/layers/StackingContextHelper.h"
#include "mozilla/StaticPrefs_layout.h"
#include "mozilla/StaticPrefs_widget.h"
#include "nsLookAndFeel.h"
#include "MacThemeGeometryType.h"
#include "VibrancyManager.h"

#include "gfxContext.h"
#include "gfxQuartzSurface.h"
#include "gfxQuartzNativeDrawing.h"
#include "gfxUtils.h"  // for ToDeviceColor
#include <algorithm>

using namespace mozilla;
using namespace mozilla::gfx;
using mozilla::dom::HTMLMeterElement;

#define DRAW_IN_FRAME_DEBUG 0
#define SCROLLBARS_VISUAL_DEBUG 0

// private Quartz routines needed here
extern "C" {
CG_EXTERN void CGContextSetCTM(CGContextRef, CGAffineTransform);
CG_EXTERN void CGContextSetBaseCTM(CGContextRef, CGAffineTransform);
typedef CFTypeRef CUIRendererRef;
void CUIDraw(CUIRendererRef r, CGRect rect, CGContextRef ctx,
             CFDictionaryRef options, CFDictionaryRef* result);
}

// Workaround for NSCell control tint drawing
// Without this workaround, NSCells are always drawn with the clear control tint
// as long as they're not attached to an NSControl which is a subview of an
// active window.
// XXXmstange Why doesn't Webkit need this?
@implementation NSCell (ControlTintWorkaround)
- (int)_realControlTint {
  return [self controlTint];
}
@end

// This is the window for our MOZCellDrawView. When an NSCell is drawn, some
// NSCell implementations look at the draw view's window to determine whether
// the cell should draw with the active look.
@interface MOZCellDrawWindow : NSWindow
@property BOOL cellsShouldLookActive;
@end

@implementation MOZCellDrawWindow

// Override three different methods, for good measure. The NSCell implementation
// could call any one of them.
- (BOOL)_hasActiveAppearance {
  return self.cellsShouldLookActive;
}
- (BOOL)hasKeyAppearance {
  return self.cellsShouldLookActive;
}
- (BOOL)_hasKeyAppearance {
  return self.cellsShouldLookActive;
}

@end

// The purpose of this class is to provide objects that can be used when drawing
// NSCells using drawWithFrame:inView: without causing any harm. Only a small
// number of methods are called on the draw view, among those "isFlipped" and
// "currentEditor": isFlipped needs to return YES in order to avoid drawing bugs
// on 10.4 (see bug 465069); currentEditor (which isn't even a method of
// NSView) will be called when drawing search fields, and we only provide it in
// order to prevent "unrecognized selector" exceptions.
// There's no need to pass the actual NSView that we're drawing into to
// drawWithFrame:inView:. What's more, doing so even causes unnecessary
// invalidations as soon as we draw a focusring!
// This class needs to be an NSControl so that NSTextFieldCell (and
// NSSearchFieldCell, which is a subclass of NSTextFieldCell) draws a focus
// ring.
@interface MOZCellDrawView : NSControl
// Called by NSTreeHeaderCell during drawing.
@property BOOL _drawingEndSeparator;
@end

@implementation MOZCellDrawView

- (BOOL)isFlipped {
  return YES;
}

- (NSText*)currentEditor {
  return nil;
}

@end

static void DrawFocusRingForCellIfNeeded(NSCell* aCell, NSRect aWithFrame,
                                         NSView* aInView) {
  if ([aCell showsFirstResponder]) {
    CGContextRef cgContext = [[NSGraphicsContext currentContext] CGContext];
    CGContextSaveGState(cgContext);

    // It's important to set the focus ring style before we enter the
    // transparency layer so that the transparency layer only contains
    // the normal button mask without the focus ring, and the conversion
    // to the focus ring shape happens only when the transparency layer is
    // ended.
    NSSetFocusRingStyle(NSFocusRingOnly);

    // We need to draw the whole button into a transparency layer because
    // many button types are composed of multiple parts, and if these parts
    // were drawn while the focus ring style was active, each individual part
    // would produce a focus ring for itself. But we only want one focus ring
    // for the whole button. The transparency layer is a way to merge the
    // individual button parts together before the focus ring shape is
    // calculated.
    CGContextBeginTransparencyLayerWithRect(cgContext,
                                            NSRectToCGRect(aWithFrame), 0);
    [aCell drawFocusRingMaskWithFrame:aWithFrame inView:aInView];
    CGContextEndTransparencyLayer(cgContext);

    CGContextRestoreGState(cgContext);
  }
}

static void DrawCellIncludingFocusRing(NSCell* aCell, NSRect aWithFrame,
                                       NSView* aInView) {
  [aCell drawWithFrame:aWithFrame inView:aInView];
  DrawFocusRingForCellIfNeeded(aCell, aWithFrame, aInView);
}

/**
 * NSProgressBarCell is used to draw progress bars of any size.
 */
@interface NSProgressBarCell : NSCell {
  /*All instance variables are private*/
  double mValue;
  double mMax;
  bool mIsIndeterminate;
  bool mIsHorizontal;
}

- (void)setValue:(double)value;
- (double)value;
- (void)setMax:(double)max;
- (double)max;
- (void)setIndeterminate:(bool)aIndeterminate;
- (bool)isIndeterminate;
- (void)setHorizontal:(bool)aIsHorizontal;
- (bool)isHorizontal;
- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView*)controlView;
@end

@implementation NSProgressBarCell

- (void)setMax:(double)aMax {
  mMax = aMax;
}

- (double)max {
  return mMax;
}

- (void)setValue:(double)aValue {
  mValue = aValue;
}

- (double)value {
  return mValue;
}

- (void)setIndeterminate:(bool)aIndeterminate {
  mIsIndeterminate = aIndeterminate;
}

- (bool)isIndeterminate {
  return mIsIndeterminate;
}

- (void)setHorizontal:(bool)aIsHorizontal {
  mIsHorizontal = aIsHorizontal;
}

- (bool)isHorizontal {
  return mIsHorizontal;
}

- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView*)controlView {
  CGContext* cgContext = [[NSGraphicsContext currentContext] CGContext];

  HIThemeTrackDrawInfo tdi;

  tdi.version = 0;
  tdi.min = 0;

  tdi.value = INT32_MAX * (mValue / mMax);
  tdi.max = INT32_MAX;
  tdi.bounds = NSRectToCGRect(cellFrame);
  tdi.attributes = mIsHorizontal ? kThemeTrackHorizontal : 0;
  tdi.enableState = [self controlTint] == NSClearControlTint
                        ? kThemeTrackInactive
                        : kThemeTrackActive;

  NSControlSize size = [self controlSize];
  if (size == NSControlSizeRegular) {
    tdi.kind =
        mIsIndeterminate ? kThemeLargeIndeterminateBar : kThemeLargeProgressBar;
  } else {
    NS_ASSERTION(
        size == NSControlSizeSmall,
        "We shouldn't have another size than small and regular for the moment");
    tdi.kind = mIsIndeterminate ? kThemeMediumIndeterminateBar
                                : kThemeMediumProgressBar;
  }

  int32_t stepsPerSecond = mIsIndeterminate ? 60 : 30;
  int32_t milliSecondsPerStep = 1000 / stepsPerSecond;
  tdi.trackInfo.progress.phase = uint8_t(
      PR_IntervalToMilliseconds(PR_IntervalNow()) / milliSecondsPerStep);

  HIThemeDrawTrack(&tdi, NULL, cgContext, kHIThemeOrientationNormal);
}

@end

@interface MOZSearchFieldCell : NSSearchFieldCell
@property BOOL shouldUseToolbarStyle;
@end

@implementation MOZSearchFieldCell

- (instancetype)init {
  // We would like to render a search field which has the magnifying glass icon
  // at the start of the search field, and no cancel button. On 10.12 and 10.13,
  // empty search fields render the magnifying glass icon in the middle of the
  // field. So in order to get the icon to show at the start of the field, we
  // need to give the field some content. We achieve this with a single space
  // character.
  self = [super initTextCell:@" "];

  // However, because the field is now non-empty, by default it shows a cancel
  // button. To hide the cancel button, override it with a custom NSButtonCell
  // which renders nothing.
  NSButtonCell* invisibleCell = [[NSButtonCell alloc] initImageCell:nil];
  invisibleCell.bezeled = NO;
  invisibleCell.bordered = NO;
  self.cancelButtonCell = invisibleCell;
  [invisibleCell release];

  return self;
}

- (BOOL)_isToolbarMode {
  return self.shouldUseToolbarStyle;
}

@end

#define HITHEME_ORIENTATION kHIThemeOrientationNormal

static CGFloat kMaxFocusRingWidth =
    0;  // initialized by the nsNativeThemeCocoa constructor

// These enums are for indexing into the margin array.
enum {
  leopardOSorlater = 0,  // 10.6 - 10.9
  yosemiteOSorlater = 1  // 10.10+
};

enum { miniControlSize, smallControlSize, regularControlSize };

enum { leftMargin, topMargin, rightMargin, bottomMargin };

static size_t EnumSizeForCocoaSize(NSControlSize cocoaControlSize) {
  if (cocoaControlSize == NSControlSizeMini)
    return miniControlSize;
  else if (cocoaControlSize == NSControlSizeSmall)
    return smallControlSize;
  else
    return regularControlSize;
}

static NSControlSize CocoaSizeForEnum(int32_t enumControlSize) {
  if (enumControlSize == miniControlSize)
    return NSControlSizeMini;
  else if (enumControlSize == smallControlSize)
    return NSControlSizeSmall;
  else
    return NSControlSizeRegular;
}

static NSString* CUIControlSizeForCocoaSize(NSControlSize aControlSize) {
  if (aControlSize == NSControlSizeRegular)
    return @"regular";
  else if (aControlSize == NSControlSizeSmall)
    return @"small";
  else
    return @"mini";
}

static void InflateControlRect(NSRect* rect, NSControlSize cocoaControlSize,
                               const float marginSet[][3][4]) {
  if (!marginSet) return;

  static int osIndex = yosemiteOSorlater;
  size_t controlSize = EnumSizeForCocoaSize(cocoaControlSize);
  const float* buttonMargins = marginSet[osIndex][controlSize];
  rect->origin.x -= buttonMargins[leftMargin];
  rect->origin.y -= buttonMargins[bottomMargin];
  rect->size.width += buttonMargins[leftMargin] + buttonMargins[rightMargin];
  rect->size.height += buttonMargins[bottomMargin] + buttonMargins[topMargin];
}

static NSWindow* NativeWindowForFrame(nsIFrame* aFrame,
                                      nsIWidget** aTopLevelWidget = NULL) {
  if (!aFrame) return nil;

  nsIWidget* widget = aFrame->GetNearestWidget();
  if (!widget) return nil;

  nsIWidget* topLevelWidget = widget->GetTopLevelWidget();
  if (aTopLevelWidget) *aTopLevelWidget = topLevelWidget;

  return (NSWindow*)topLevelWidget->GetNativeData(NS_NATIVE_WINDOW);
}

static NSSize WindowButtonsSize(nsIFrame* aFrame) {
  NSWindow* window = NativeWindowForFrame(aFrame);
  if (!window) {
    // Return fallback values.
    return NSMakeSize(54, 16);
  }

  NSRect buttonBox = NSZeroRect;
  NSButton* closeButton = [window standardWindowButton:NSWindowCloseButton];
  if (closeButton) {
    buttonBox = NSUnionRect(buttonBox, [closeButton frame]);
  }
  NSButton* minimizeButton =
      [window standardWindowButton:NSWindowMiniaturizeButton];
  if (minimizeButton) {
    buttonBox = NSUnionRect(buttonBox, [minimizeButton frame]);
  }
  NSButton* zoomButton = [window standardWindowButton:NSWindowZoomButton];
  if (zoomButton) {
    buttonBox = NSUnionRect(buttonBox, [zoomButton frame]);
  }
  return buttonBox.size;
}

static BOOL FrameIsInActiveWindow(nsIFrame* aFrame) {
  nsIWidget* topLevelWidget = NULL;
  NSWindow* win = NativeWindowForFrame(aFrame, &topLevelWidget);
  if (!topLevelWidget || !win) return YES;

  // XUL popups, e.g. the toolbar customization popup, can't become key windows,
  // but controls in these windows should still get the active look.
  if (topLevelWidget->GetWindowType() == widget::WindowType::Popup) {
    return YES;
  }
  if ([win isSheet]) {
    return [win isKeyWindow];
  }
  return [win isMainWindow] && ![win attachedSheet];
}

// Toolbar controls and content controls respond to different window
// activeness states.
static BOOL IsActiveToolbarControl(nsIFrame* aFrame) {
  return NativeWindowForFrame(aFrame).isMainWindow;
}

NS_IMPL_ISUPPORTS_INHERITED(nsNativeThemeCocoa, nsNativeTheme, nsITheme)

nsNativeThemeCocoa::nsNativeThemeCocoa() : ThemeCocoa(ScrollbarStyle()) {
  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;

  kMaxFocusRingWidth = 7;

  // provide a local autorelease pool, as this is called during startup
  // before the main event-loop pool is in place
  nsAutoreleasePool pool;

  mDisclosureButtonCell = [[NSButtonCell alloc] initTextCell:@""];
  [mDisclosureButtonCell setBezelStyle:NSBezelStyleRoundedDisclosure];
  [mDisclosureButtonCell setButtonType:NSButtonTypePushOnPushOff];
  [mDisclosureButtonCell setHighlightsBy:NSPushInCellMask];

  mHelpButtonCell = [[NSButtonCell alloc] initTextCell:@""];
  [mHelpButtonCell setBezelStyle:NSBezelStyleHelpButton];
  [mHelpButtonCell setButtonType:NSButtonTypeMomentaryPushIn];
  [mHelpButtonCell setHighlightsBy:NSPushInCellMask];

  mPushButtonCell = [[NSButtonCell alloc] initTextCell:@""];
  [mPushButtonCell setButtonType:NSButtonTypeMomentaryPushIn];
  [mPushButtonCell setHighlightsBy:NSPushInCellMask];

  mRadioButtonCell = [[NSButtonCell alloc] initTextCell:@""];
  [mRadioButtonCell setButtonType:NSButtonTypeRadio];

  mCheckboxCell = [[NSButtonCell alloc] initTextCell:@""];
  [mCheckboxCell setButtonType:NSButtonTypeSwitch];
  [mCheckboxCell setAllowsMixedState:YES];

  mTextFieldCell = [[NSTextFieldCell alloc] initTextCell:@""];
  [mTextFieldCell setBezeled:YES];
  [mTextFieldCell setEditable:YES];
  [mTextFieldCell setFocusRingType:NSFocusRingTypeExterior];

  mSearchFieldCell = [[MOZSearchFieldCell alloc] init];
  [mSearchFieldCell setBezelStyle:NSTextFieldRoundedBezel];
  [mSearchFieldCell setBezeled:YES];
  [mSearchFieldCell setEditable:YES];
  [mSearchFieldCell setFocusRingType:NSFocusRingTypeExterior];

  mDropdownCell = [[NSPopUpButtonCell alloc] initTextCell:@"" pullsDown:NO];

  mComboBoxCell = [[NSComboBoxCell alloc] initTextCell:@""];
  [mComboBoxCell setBezeled:YES];
  [mComboBoxCell setEditable:YES];
  [mComboBoxCell setFocusRingType:NSFocusRingTypeExterior];

  mProgressBarCell = [[NSProgressBarCell alloc] init];

  mMeterBarCell = [[NSLevelIndicatorCell alloc]
      initWithLevelIndicatorStyle:NSLevelIndicatorStyleContinuousCapacity];

  mCellDrawView = [[MOZCellDrawView alloc] init];

  if (XRE_IsParentProcess()) {
    // Put the cell draw view into a window that is never shown.
    // This allows us to convince some NSCell implementations (such as
    // NSButtonCell for default buttons) to draw with the active appearance.
    // Another benefit of putting the draw view in a window is the fact that it
    // lets NSTextFieldCell (and its subclass NSSearchFieldCell) inherit the
    // current NSApplication effectiveAppearance automatically, so the field
    // adapts to Dark Mode correctly. We don't create this window when the
    // native theme is used in the content process because NSWindow creation
    // runs into the sandbox and because we never run default buttons in content
    // processes anyway.
    mCellDrawWindow = [[MOZCellDrawWindow alloc]
        initWithContentRect:NSZeroRect
                  styleMask:NSWindowStyleMaskBorderless
                    backing:NSBackingStoreBuffered
                      defer:NO];
    [mCellDrawWindow.contentView addSubview:mCellDrawView];
  }

  NS_OBJC_END_TRY_IGNORE_BLOCK;
}

nsNativeThemeCocoa::~nsNativeThemeCocoa() {
  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;

  [mMeterBarCell release];
  [mProgressBarCell release];
  [mDisclosureButtonCell release];
  [mHelpButtonCell release];
  [mPushButtonCell release];
  [mRadioButtonCell release];
  [mCheckboxCell release];
  [mTextFieldCell release];
  [mSearchFieldCell release];
  [mDropdownCell release];
  [mComboBoxCell release];
  [mCellDrawWindow release];
  [mCellDrawView release];

  NS_OBJC_END_TRY_IGNORE_BLOCK;
}

// Limit on the area of the target rect (in pixels^2) in
// DrawCellWithScaling() and DrawButton() and above which we
// don't draw the object into a bitmap buffer.  This is to avoid crashes in
// [NSGraphicsContext graphicsContextWithCGContext:flipped:] and
// CGContextDrawImage(), and also to avoid very poor drawing performance in
// CGContextDrawImage() when it scales the bitmap (particularly if xscale or
// yscale is less than but near 1 -- e.g. 0.9).  This value was determined
// by trial and error, on OS X 10.4.11 and 10.5.4, and on systems with
// different amounts of RAM.
#define BITMAP_MAX_AREA 500000

static int GetBackingScaleFactorForRendering(CGContextRef cgContext) {
  CGAffineTransform ctm =
      CGContextGetUserSpaceToDeviceSpaceTransform(cgContext);
  CGRect transformedUserSpacePixel =
      CGRectApplyAffineTransform(CGRectMake(0, 0, 1, 1), ctm);
  float maxScale = std::max(fabs(transformedUserSpacePixel.size.width),
                            fabs(transformedUserSpacePixel.size.height));
  return maxScale > 1.0 ? 2 : 1;
}

/*
 * Draw the given NSCell into the given cgContext.
 *
 * destRect - the size and position of the resulting control rectangle
 * controlSize - the NSControlSize which will be given to the NSCell before
 *  asking it to render
 * naturalSize - The natural dimensions of this control.
 *  If the control rect size is not equal to either of these, a scale
 *  will be applied to the context so that rendering the control at the
 *  natural size will result in it filling the destRect space.
 *  If a control has no natural dimensions in either/both axes, pass 0.0f.
 * minimumSize - The minimum dimensions of this control.
 *  If the control rect size is less than the minimum for a given axis,
 *  a scale will be applied to the context so that the minimum is used
 *  for drawing.  If a control has no minimum dimensions in either/both
 *  axes, pass 0.0f.
 * marginSet - an array of margins; a multidimensional array of [2][3][4],
 *  with the first dimension being the OS version (Tiger or Leopard),
 *  the second being the control size (mini, small, regular), and the third
 *  being the 4 margin values (left, top, right, bottom).
 * view - The NSView that we're drawing into. As far as I can tell, it doesn't
 *  matter if this is really the right view; it just has to return YES when
 *  asked for isFlipped. Otherwise we'll get drawing bugs on 10.4.
 * mirrorHorizontal - whether to mirror the cell horizontally
 */
static void DrawCellWithScaling(NSCell* cell, CGContextRef cgContext,
                                const HIRect& destRect,
                                NSControlSize controlSize, NSSize naturalSize,
                                NSSize minimumSize,
                                const float marginSet[][3][4], NSView* view,
                                BOOL mirrorHorizontal) {
  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;

  NSRect drawRect = NSMakeRect(destRect.origin.x, destRect.origin.y,
                               destRect.size.width, destRect.size.height);

  if (naturalSize.width != 0.0f) drawRect.size.width = naturalSize.width;
  if (naturalSize.height != 0.0f) drawRect.size.height = naturalSize.height;

  // Keep aspect ratio when scaling if one dimension is free.
  if (naturalSize.width == 0.0f && naturalSize.height != 0.0f)
    drawRect.size.width =
        destRect.size.width * naturalSize.height / destRect.size.height;
  if (naturalSize.height == 0.0f && naturalSize.width != 0.0f)
    drawRect.size.height =
        destRect.size.height * naturalSize.width / destRect.size.width;

  // Honor minimum sizes.
  if (drawRect.size.width < minimumSize.width)
    drawRect.size.width = minimumSize.width;
  if (drawRect.size.height < minimumSize.height)
    drawRect.size.height = minimumSize.height;

  [NSGraphicsContext saveGraphicsState];

  // Only skip the buffer if the area of our cell (in pixels^2) is too large.
  if (drawRect.size.width * drawRect.size.height > BITMAP_MAX_AREA) {
    // Inflate the rect Gecko gave us by the margin for the control.
    InflateControlRect(&drawRect, controlSize, marginSet);

    NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
    [NSGraphicsContext
        setCurrentContext:[NSGraphicsContext
                              graphicsContextWithCGContext:cgContext
                                                   flipped:YES]];

    DrawCellIncludingFocusRing(cell, drawRect, view);

    [NSGraphicsContext setCurrentContext:savedContext];
  } else {
    float w = ceil(drawRect.size.width);
    float h = ceil(drawRect.size.height);
    NSRect tmpRect = NSMakeRect(kMaxFocusRingWidth, kMaxFocusRingWidth, w, h);

    // inflate to figure out the frame we need to tell NSCell to draw in, to get
    // something that's 0,0,w,h
    InflateControlRect(&tmpRect, controlSize, marginSet);

    // and then, expand by kMaxFocusRingWidth size to make sure we can capture
    // any focus ring
    w += kMaxFocusRingWidth * 2.0;
    h += kMaxFocusRingWidth * 2.0;

    int backingScaleFactor = GetBackingScaleFactorForRendering(cgContext);
    CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
    CGContextRef ctx = CGBitmapContextCreate(
        NULL, (int)w * backingScaleFactor, (int)h * backingScaleFactor, 8,
        (int)w * backingScaleFactor * 4, rgb, kCGImageAlphaPremultipliedFirst);
    CGColorSpaceRelease(rgb);

    // We need to flip the image twice in order to avoid drawing bugs on 10.4,
    // see bug 465069. This is the first flip transform, applied to cgContext.
    CGContextScaleCTM(cgContext, 1.0f, -1.0f);
    CGContextTranslateCTM(cgContext, 0.0f,
                          -(2.0 * destRect.origin.y + destRect.size.height));
    if (mirrorHorizontal) {
      CGContextScaleCTM(cgContext, -1.0f, 1.0f);
      CGContextTranslateCTM(
          cgContext, -(2.0 * destRect.origin.x + destRect.size.width), 0.0f);
    }

    NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
    [NSGraphicsContext
        setCurrentContext:[NSGraphicsContext graphicsContextWithCGContext:ctx
                                                                  flipped:YES]];

    CGContextScaleCTM(ctx, backingScaleFactor, backingScaleFactor);

    // Set the context's "base transform" to in order to get correctly-sized
    // focus rings.
    CGContextSetBaseCTM(ctx, CGAffineTransformMakeScale(backingScaleFactor,
                                                        backingScaleFactor));

    // This is the second flip transform, applied to ctx.
    CGContextScaleCTM(ctx, 1.0f, -1.0f);
    CGContextTranslateCTM(ctx, 0.0f,
                          -(2.0 * tmpRect.origin.y + tmpRect.size.height));

    DrawCellIncludingFocusRing(cell, tmpRect, view);

    [NSGraphicsContext setCurrentContext:savedContext];

    CGImageRef img = CGBitmapContextCreateImage(ctx);

    // Drop the image into the original destination rectangle, scaling to fit
    // Only scale kMaxFocusRingWidth by xscale/yscale when the resulting rect
    // doesn't extend beyond the overflow rect
    float xscale = destRect.size.width / drawRect.size.width;
    float yscale = destRect.size.height / drawRect.size.height;
    float scaledFocusRingX =
        xscale < 1.0f ? kMaxFocusRingWidth * xscale : kMaxFocusRingWidth;
    float scaledFocusRingY =
        yscale < 1.0f ? kMaxFocusRingWidth * yscale : kMaxFocusRingWidth;
    CGContextDrawImage(cgContext,
                       CGRectMake(destRect.origin.x - scaledFocusRingX,
                                  destRect.origin.y - scaledFocusRingY,
                                  destRect.size.width + scaledFocusRingX * 2,
                                  destRect.size.height + scaledFocusRingY * 2),
                       img);

    CGImageRelease(img);
    CGContextRelease(ctx);
  }

  [NSGraphicsContext restoreGraphicsState];

#if DRAW_IN_FRAME_DEBUG
  CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
  CGContextFillRect(cgContext, destRect);
#endif

  NS_OBJC_END_TRY_IGNORE_BLOCK;
}

struct CellRenderSettings {
  // The natural dimensions of the control.
  // If a control has no natural dimensions in either/both axes, set to 0.0f.
  NSSize naturalSizes[3];

  // The minimum dimensions of the control.
  // If a control has no minimum dimensions in either/both axes, set to 0.0f.
  NSSize minimumSizes[3];

  // A three-dimensional array,
  // with the first dimension being the OS version ([0] 10.6-10.9, [1] 10.10 and
  // above), the second being the control size (mini, small, regular), and the
  // third being the 4 margin values (left, top, right, bottom).
  float margins[2][3][4];
};

/*
 * This is a helper method that returns the required NSControlSize given a size
 * and the size of the three controls plus a tolerance.
 * size - The width or the height of the element to draw.
 * sizes - An array with the all the width/height of the element for its
 *         different sizes.
 * tolerance - The tolerance as passed to DrawCellWithSnapping.
 * NOTE: returns NSControlSizeRegular if all values in 'sizes' are zero.
 */
static NSControlSize FindControlSize(CGFloat size, const CGFloat* sizes,
                                     CGFloat tolerance) {
  for (uint32_t i = miniControlSize; i <= regularControlSize; ++i) {
    if (sizes[i] == 0) {
      continue;
    }

    CGFloat next = 0;
    // Find next value.
    for (uint32_t j = i + 1; j <= regularControlSize; ++j) {
      if (sizes[j] != 0) {
        next = sizes[j];
        break;
      }
    }

    // If it's the latest value, we pick it.
    if (next == 0) {
      return CocoaSizeForEnum(i);
    }

    if (size <= sizes[i] + tolerance && size < next) {
      return CocoaSizeForEnum(i);
    }
  }

  // If we are here, that means sizes[] was an array with only empty values
  // or the algorithm above is wrong.
  // The former can happen but the later would be wrong.
  NS_ASSERTION(sizes[0] == 0 && sizes[1] == 0 && sizes[2] == 0,
               "We found no control! We shouldn't be there!");
  return CocoaSizeForEnum(regularControlSize);
}

/*
 * Draw the given NSCell into the given cgContext with a nice control size.
 *
 * This function is similar to DrawCellWithScaling, but it decides what
 * control size to use based on the destRect's size.
 * Scaling is only applied when the difference between the destRect's size
 * and the next smaller natural size is greater than snapTolerance. Otherwise
 * it snaps to the next smaller control size without scaling because unscaled
 * controls look nicer.
 */
static void DrawCellWithSnapping(NSCell* cell, CGContextRef cgContext,
                                 const HIRect& destRect,
                                 const CellRenderSettings settings,
                                 float verticalAlignFactor, NSView* view,
                                 BOOL mirrorHorizontal,
                                 float snapTolerance = 2.0f) {
  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;

  const float rectWidth = destRect.size.width,
              rectHeight = destRect.size.height;
  const NSSize* sizes = settings.naturalSizes;
  const NSSize miniSize = sizes[EnumSizeForCocoaSize(NSControlSizeMini)];
  const NSSize smallSize = sizes[EnumSizeForCocoaSize(NSControlSizeSmall)];
  const NSSize regularSize = sizes[EnumSizeForCocoaSize(NSControlSizeRegular)];

  HIRect drawRect = destRect;

  CGFloat controlWidths[3] = {miniSize.width, smallSize.width,
                              regularSize.width};
  NSControlSize controlSizeX =
      FindControlSize(rectWidth, controlWidths, snapTolerance);
  CGFloat controlHeights[3] = {miniSize.height, smallSize.height,
                               regularSize.height};
  NSControlSize controlSizeY =
      FindControlSize(rectHeight, controlHeights, snapTolerance);

  NSControlSize controlSize = NSControlSizeRegular;
  size_t sizeIndex = 0;

  // At some sizes, don't scale but snap.
  const NSControlSize smallerControlSize =
      EnumSizeForCocoaSize(controlSizeX) < EnumSizeForCocoaSize(controlSizeY)
          ? controlSizeX
          : controlSizeY;
  const size_t smallerControlSizeIndex =
      EnumSizeForCocoaSize(smallerControlSize);
  const NSSize size = sizes[smallerControlSizeIndex];
  float diffWidth = size.width ? rectWidth - size.width : 0.0f;
  float diffHeight = size.height ? rectHeight - size.height : 0.0f;
  if (diffWidth >= 0.0f && diffHeight >= 0.0f && diffWidth <= snapTolerance &&
      diffHeight <= snapTolerance) {
    // Snap to the smaller control size.
    controlSize = smallerControlSize;
    sizeIndex = smallerControlSizeIndex;
    MOZ_ASSERT(sizeIndex < std::size(settings.naturalSizes));

    // Resize and center the drawRect.
    if (sizes[sizeIndex].width) {
      drawRect.origin.x +=
          ceil((destRect.size.width - sizes[sizeIndex].width) / 2);
      drawRect.size.width = sizes[sizeIndex].width;
    }
    if (sizes[sizeIndex].height) {
      drawRect.origin.y +=
          floor((destRect.size.height - sizes[sizeIndex].height) *
                verticalAlignFactor);
      drawRect.size.height = sizes[sizeIndex].height;
    }
  } else {
    // Use the larger control size.
    controlSize =
        EnumSizeForCocoaSize(controlSizeX) > EnumSizeForCocoaSize(controlSizeY)
            ? controlSizeX
            : controlSizeY;
    sizeIndex = EnumSizeForCocoaSize(controlSize);
  }

  [cell setControlSize:controlSize];

  MOZ_ASSERT(sizeIndex < std::size(settings.minimumSizes));
  const NSSize minimumSize = settings.minimumSizes[sizeIndex];
  DrawCellWithScaling(cell, cgContext, drawRect, controlSize, sizes[sizeIndex],
                      minimumSize, settings.margins, view, mirrorHorizontal);

  NS_OBJC_END_TRY_IGNORE_BLOCK;
}

@interface NSWindow (CoreUIRendererPrivate)
+ (CUIRendererRef)coreUIRenderer;
@end

@interface NSObject (NSAppearanceCoreUIRendering)
- (void)_drawInRect:(CGRect)rect
            context:(CGContextRef)cgContext
            options:(id)options;
@end

static void RenderWithCoreUI(CGRect aRect, CGContextRef cgContext,
                             NSDictionary* aOptions,
                             bool aSkipAreaCheck = false) {
  if (!aSkipAreaCheck &&
      aRect.size.width * aRect.size.height > BITMAP_MAX_AREA) {
    return;
  }

  NSAppearance* appearance = NSAppearance.currentAppearance;
  if (appearance &&
      [appearance respondsToSelector:@selector(_drawInRect:context:options:)]) {
    // Render through NSAppearance on Mac OS 10.10 and up. This will call
    // CUIDraw with a CoreUI renderer that will give us the correct 10.10
    // style. Calling CUIDraw directly with [NSWindow coreUIRenderer] still
    // renders 10.9-style widgets on 10.10.
    [appearance _drawInRect:aRect context:cgContext options:aOptions];
  } else {
    // 10.9 and below
    CUIRendererRef renderer =
        [NSWindow respondsToSelector:@selector(coreUIRenderer)]
            ? [NSWindow coreUIRenderer]
            : nil;
    CUIDraw(renderer, aRect, cgContext, (CFDictionaryRef)aOptions, NULL);
  }
}

static float VerticalAlignFactor(nsIFrame* aFrame) {
  if (!aFrame) return 0.5f;  // default: center

  const auto& va = aFrame->StyleDisplay()->mVerticalAlign;
  auto kw = va.IsKeyword() ? va.AsKeyword() : StyleVerticalAlignKeyword::Middle;
  switch (kw) {
    case StyleVerticalAlignKeyword::Top:
    case StyleVerticalAlignKeyword::TextTop:
      return 0.0f;

    case StyleVerticalAlignKeyword::Sub:
    case StyleVerticalAlignKeyword::Super:
    case StyleVerticalAlignKeyword::Middle:
    case StyleVerticalAlignKeyword::MozMiddleWithBaseline:
      return 0.5f;

    case StyleVerticalAlignKeyword::Baseline:
    case StyleVerticalAlignKeyword::Bottom:
    case StyleVerticalAlignKeyword::TextBottom:
      return 1.0f;

    default:
      MOZ_ASSERT_UNREACHABLE("invalid vertical-align");
      return 0.5f;
  }
}

static void ApplyControlParamsToNSCell(
    nsNativeThemeCocoa::ControlParams aControlParams, NSCell* aCell) {
  [aCell setEnabled:!aControlParams.disabled];
  [aCell setShowsFirstResponder:(aControlParams.focused &&
                                 !aControlParams.disabled &&
                                 aControlParams.insideActiveWindow)];
  [aCell setHighlighted:aControlParams.pressed];
}

// These are the sizes that Gecko needs to request to draw if it wants
// to get a standard-sized Aqua radio button drawn. Note that the rects
// that draw these are actually a little bigger.
MOZ_RUNINIT static const CellRenderSettings radioSettings = {
    {
        NSMakeSize(11, 11),  // mini
        NSMakeSize(13, 13),  // small
        NSMakeSize(16, 16)   // regular
    },
    {NSZeroSize, NSZeroSize, NSZeroSize},
    {{
         // Leopard
         {0, 0, 0, 0},  // mini
         {0, 1, 1, 1},  // small
         {0, 0, 0, 0}   // regular
     },
     {
         // Yosemite
         {0, 0, 0, 0},  // mini
         {1, 1, 1, 2},  // small
         {0, 0, 0, 0}   // regular
     }}};

MOZ_RUNINIT static const CellRenderSettings checkboxSettings = {
    {
        NSMakeSize(11, 11),  // mini
        NSMakeSize(13, 13),  // small
        NSMakeSize(16, 16)   // regular
    },
    {NSZeroSize, NSZeroSize, NSZeroSize},
    {{
         // Leopard
         {0, 1, 0, 0},  // mini
         {0, 1, 0, 1},  // small
         {0, 1, 0, 1}   // regular
     },
     {
         // Yosemite
         {0, 1, 0, 0},  // mini
         {0, 1, 0, 1},  // small
         {0, 1, 0, 1}   // regular
     }}};

static NSControlStateValue CellStateForCheckboxOrRadioState(
    nsNativeThemeCocoa::CheckboxOrRadioState aState) {
  switch (aState) {
    case nsNativeThemeCocoa::CheckboxOrRadioState::eOff:
      return NSControlStateValueOff;
    case nsNativeThemeCocoa::CheckboxOrRadioState::eOn:
      return NSControlStateValueOn;
    case nsNativeThemeCocoa::CheckboxOrRadioState::eIndeterminate:
      return NSControlStateValueMixed;
  }
}

void nsNativeThemeCocoa::DrawCheckboxOrRadio(
    CGContextRef cgContext, bool inCheckbox, const HIRect& inBoxRect,
    const CheckboxOrRadioParams& aParams) {
  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;

  NSButtonCell* cell = inCheckbox ? mCheckboxCell : mRadioButtonCell;
  ApplyControlParamsToNSCell(aParams.controlParams, cell);

  [cell setState:CellStateForCheckboxOrRadioState(aParams.state)];
  [cell setControlTint:(aParams.controlParams.insideActiveWindow
                            ? [NSColor currentControlTint]
                            : NSClearControlTint)];

  // Ensure that the control is square.
  float length = std::min(inBoxRect.size.width, inBoxRect.size.height);
  HIRect drawRect = CGRectMake(
      inBoxRect.origin.x + (int)((inBoxRect.size.width - length) / 2.0f),
      inBoxRect.origin.y + (int)((inBoxRect.size.height - length) / 2.0f),
      length, length);

  if (mCellDrawWindow) {
    mCellDrawWindow.cellsShouldLookActive =
        aParams.controlParams.insideActiveWindow;
  }
  DrawCellWithSnapping(cell, cgContext, drawRect,
                       inCheckbox ? checkboxSettings : radioSettings,
                       aParams.verticalAlignFactor, mCellDrawView, NO);

  NS_OBJC_END_TRY_IGNORE_BLOCK;
}

MOZ_RUNINIT static const CellRenderSettings searchFieldSettings = {
    {
        NSMakeSize(0, 16),  // mini
        NSMakeSize(0, 19),  // small
        NSMakeSize(0, 22)   // regular
    },
    {
        NSMakeSize(32, 0),  // mini
        NSMakeSize(38, 0),  // small
        NSMakeSize(44, 0)   // regular
    },
    {{
         // Leopard
         {0, 0, 0, 0},  // mini
         {0, 0, 0, 0},  // small
         {0, 0, 0, 0}   // regular
     },
     {
         // Yosemite
         {0, 0, 0, 0},  // mini
         {0, 0, 0, 0},  // small
         {0, 0, 0, 0}   // regular
     }}};

static bool IsToolbarStyleContainer(nsIFrame* aFrame) {
  nsIContent* content = aFrame->GetContent();
  if (!content) {
    return false;
  }

  if (content->IsAnyOfXULElements(nsGkAtoms::toolbar, nsGkAtoms::toolbox,
                                  nsGkAtoms::statusbar)) {
    return true;
  }

  switch (aFrame->StyleDisplay()->EffectiveAppearance()) {
    case StyleAppearance::Statusbar:
      return true;
    default:
      return false;
  }
}

static bool IsInsideToolbar(nsIFrame* aFrame) {
  for (nsIFrame* frame = aFrame; frame; frame = frame->GetParent()) {
    if (IsToolbarStyleContainer(frame)) {
      return true;
    }
  }
  return false;
}

nsNativeThemeCocoa::TextFieldParams nsNativeThemeCocoa::ComputeTextFieldParams(
    nsIFrame* aFrame, ElementState aEventState) {
  TextFieldParams params;
  params.insideToolbar = IsInsideToolbar(aFrame);
  params.disabled = aEventState.HasState(ElementState::DISABLED);

  // See ShouldUnconditionallyDrawFocusRingIfFocused.
  params.focused = aEventState.HasState(ElementState::FOCUS);

  params.rtl = IsFrameRTL(aFrame);
  params.verticalAlignFactor = VerticalAlignFactor(aFrame);
  return params;
}

void nsNativeThemeCocoa::DrawTextField(CGContextRef cgContext,
                                       const HIRect& inBoxRect,
                                       const TextFieldParams& aParams) {
  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;

  NSTextFieldCell* cell = mTextFieldCell;
  [cell setEnabled:!aParams.disabled];
  [cell setShowsFirstResponder:aParams.focused];

  if (mCellDrawWindow) {
    mCellDrawWindow.cellsShouldLookActive =
        YES;  // TODO: propagate correct activeness state
  }
  DrawCellWithSnapping(cell, cgContext, inBoxRect, searchFieldSettings,
                       aParams.verticalAlignFactor, mCellDrawView, aParams.rtl);

  NS_OBJC_END_TRY_IGNORE_BLOCK;
}

void nsNativeThemeCocoa::DrawSearchField(CGContextRef cgContext,
                                         const HIRect& inBoxRect,
                                         const TextFieldParams& aParams) {
  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;

  mSearchFieldCell.enabled = !aParams.disabled;
  mSearchFieldCell.showsFirstResponder = aParams.focused;
  mSearchFieldCell.placeholderString = @"";
  mSearchFieldCell.shouldUseToolbarStyle = aParams.insideToolbar;

  if (mCellDrawWindow) {
    mCellDrawWindow.cellsShouldLookActive =
        YES;  // TODO: propagate correct activeness state
  }
  DrawCellWithSnapping(mSearchFieldCell, cgContext, inBoxRect,
                       searchFieldSettings, aParams.verticalAlignFactor,
                       mCellDrawView, aParams.rtl);

  NS_OBJC_END_TRY_IGNORE_BLOCK;
}

static bool ShouldUnconditionallyDrawFocusRingIfFocused(nsIFrame* aFrame) {
  // Mac always draws focus rings for textboxes and lists.
  switch (aFrame->StyleDisplay()->EffectiveAppearance()) {
    case StyleAppearance::NumberInput:
    case StyleAppearance::PasswordInput:
    case StyleAppearance::Textfield:
    case StyleAppearance::Textarea:
    case StyleAppearance::Searchfield:
    case StyleAppearance::Listbox:
      return true;
    default:
      return false;
  }
}

nsNativeThemeCocoa::ControlParams nsNativeThemeCocoa::ComputeControlParams(
    nsIFrame* aFrame, ElementState aEventState) {
  ControlParams params;
  params.disabled = aEventState.HasState(ElementState::DISABLED);
  params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
  params.pressed =
      aEventState.HasAllStates(ElementState::ACTIVE | ElementState::HOVER);
  params.focused = aEventState.HasState(ElementState::FOCUS) &&
                   (aEventState.HasState(ElementState::FOCUSRING) ||
                    ShouldUnconditionallyDrawFocusRingIfFocused(aFrame));
  params.rtl = IsFrameRTL(aFrame);
  return params;
}

MOZ_RUNINIT static const NSSize kHelpButtonSize = NSMakeSize(20, 20);
MOZ_RUNINIT static const NSSize kDisclosureButtonSize = NSMakeSize(21, 21);

MOZ_RUNINIT static const CellRenderSettings pushButtonSettings = {
    {
        NSMakeSize(0, 16),  // mini
        NSMakeSize(0, 19),  // small
        NSMakeSize(0, 22)   // regular
    },
    {
        NSMakeSize(18, 0),  // mini
        NSMakeSize(26, 0),  // small
        NSMakeSize(30, 0)   // regular
    },
    {{
         // Leopard
         {0, 0, 0, 0},  // mini
         {4, 0, 4, 1},  // small
         {5, 0, 5, 2}   // regular
     },
     {
         // Yosemite
         {0, 0, 0, 0},  // mini
         {4, 0, 4, 1},  // small
         {5, 0, 5, 2}   // regular
     }}};

// The height at which we start doing square buttons instead of rounded buttons
// Rounded buttons look bad if drawn at a height greater than 26, so at that
// point we switch over to doing square buttons which looks fine at any size.
#define DO_SQUARE_BUTTON_HEIGHT 26

void nsNativeThemeCocoa::DrawPushButton(CGContextRef cgContext,
                                        const HIRect& inBoxRect,
                                        ButtonType aButtonType,
                                        ControlParams aControlParams) {
  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;

  ApplyControlParamsToNSCell(aControlParams, mPushButtonCell);
  [mPushButtonCell setBezelStyle:NSBezelStyleRounded];
  mPushButtonCell.keyEquivalent =
      aButtonType == ButtonType::eDefaultPushButton ? @"\r" : @"";

  if (mCellDrawWindow) {
    mCellDrawWindow.cellsShouldLookActive = aControlParams.insideActiveWindow;
  }
  DrawCellWithSnapping(mPushButtonCell, cgContext, inBoxRect,
                       pushButtonSettings, 0.5f, mCellDrawView,
                       aControlParams.rtl, 1.0f);

  NS_OBJC_END_TRY_IGNORE_BLOCK;
}

void nsNativeThemeCocoa::DrawSquareBezelPushButton(
    CGContextRef cgContext, const HIRect& inBoxRect,
    ControlParams aControlParams) {
  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;

  ApplyControlParamsToNSCell(aControlParams, mPushButtonCell);
  [mPushButtonCell setBezelStyle:NSBezelStyleShadowlessSquare];

  if (mCellDrawWindow) {
    mCellDrawWindow.cellsShouldLookActive = aControlParams.insideActiveWindow;
  }
  DrawCellWithScaling(mPushButtonCell, cgContext, inBoxRect,
                      NSControlSizeRegular, NSZeroSize, NSMakeSize(14, 0), NULL,
                      mCellDrawView, aControlParams.rtl);

  NS_OBJC_END_TRY_IGNORE_BLOCK;
}

void nsNativeThemeCocoa::DrawHelpButton(CGContextRef cgContext,
                                        const HIRect& inBoxRect,
                                        ControlParams aControlParams) {
  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;

  ApplyControlParamsToNSCell(aControlParams, mHelpButtonCell);

  if (mCellDrawWindow) {
    mCellDrawWindow.cellsShouldLookActive = aControlParams.insideActiveWindow;
  }
  DrawCellWithScaling(mHelpButtonCell, cgContext, inBoxRect,
                      NSControlSizeRegular, NSZeroSize, kHelpButtonSize, NULL,
                      mCellDrawView,
                      false);  // Don't mirror icon in RTL.

  NS_OBJC_END_TRY_IGNORE_BLOCK;
}

void nsNativeThemeCocoa::DrawDisclosureButton(CGContextRef cgContext,
                                              const HIRect& inBoxRect,
                                              ControlParams aControlParams,
                                              NSControlStateValue aCellState) {
  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;

  ApplyControlParamsToNSCell(aControlParams, mDisclosureButtonCell);
  [mDisclosureButtonCell setState:aCellState];

  if (mCellDrawWindow) {
    mCellDrawWindow.cellsShouldLookActive = aControlParams.insideActiveWindow;
  }
  DrawCellWithScaling(mDisclosureButtonCell, cgContext, inBoxRect,
                      NSControlSizeRegular, NSZeroSize, kDisclosureButtonSize,
                      NULL, mCellDrawView,
                      false);  // Don't mirror icon in RTL.

  NS_OBJC_END_TRY_IGNORE_BLOCK;
}

typedef void (*RenderHIThemeControlFunction)(CGContextRef cgContext,
                                             const HIRect& aRenderRect,
                                             void* aData);

static void RenderTransformedHIThemeControl(CGContextRef aCGContext,
                                            const HIRect& aRect,
                                            RenderHIThemeControlFunction aFunc,
                                            void* aData,
                                            BOOL mirrorHorizontally = NO) {
  CGAffineTransform savedCTM = CGContextGetCTM(aCGContext);
  CGContextTranslateCTM(aCGContext, aRect.origin.x, aRect.origin.y);

  bool drawDirect;
  HIRect drawRect = aRect;
  drawRect.origin = CGPointZero;

  if (!mirrorHorizontally && savedCTM.a == 1.0f && savedCTM.b == 0.0f &&
      savedCTM.c == 0.0f && (savedCTM.d == 1.0f || savedCTM.d == -1.0f)) {
    drawDirect = TRUE;
  } else {
    drawDirect = FALSE;
  }

  // Fall back to no bitmap buffer if the area of our control (in pixels^2)
  // is too large.
  if (drawDirect || (aRect.size.width * aRect.size.height > BITMAP_MAX_AREA)) {
    aFunc(aCGContext, drawRect, aData);
  } else {
    // Inflate the buffer to capture focus rings.
    int w = ceil(drawRect.size.width) + 2 * kMaxFocusRingWidth;
    int h = ceil(drawRect.size.height) + 2 * kMaxFocusRingWidth;

    int backingScaleFactor = GetBackingScaleFactorForRendering(aCGContext);
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGContextRef bitmapctx = CGBitmapContextCreate(
        NULL, w * backingScaleFactor, h * backingScaleFactor, 8,
        w * backingScaleFactor * 4, colorSpace,
        kCGImageAlphaPremultipliedFirst);
    CGColorSpaceRelease(colorSpace);

    CGContextScaleCTM(bitmapctx, backingScaleFactor, backingScaleFactor);
    CGContextTranslateCTM(bitmapctx, kMaxFocusRingWidth, kMaxFocusRingWidth);

    // Set the context's "base transform" to in order to get correctly-sized
    // focus rings.
    CGContextSetBaseCTM(bitmapctx, CGAffineTransformMakeScale(
                                       backingScaleFactor, backingScaleFactor));

    // HITheme always wants to draw into a flipped context, or things
    // get confused.
    CGContextTranslateCTM(bitmapctx, 0.0f, aRect.size.height);
    CGContextScaleCTM(bitmapctx, 1.0f, -1.0f);

    aFunc(bitmapctx, drawRect, aData);

    CGImageRef bitmap = CGBitmapContextCreateImage(bitmapctx);

    CGAffineTransform ctm = CGContextGetCTM(aCGContext);

    // We need to unflip, so that we can do a DrawImage without getting a
    // flipped image.
    CGContextTranslateCTM(aCGContext, 0.0f, aRect.size.height);
    CGContextScaleCTM(aCGContext, 1.0f, -1.0f);

    if (mirrorHorizontally) {
      CGContextTranslateCTM(aCGContext, aRect.size.width, 0);
      CGContextScaleCTM(aCGContext, -1.0f, 1.0f);
    }

    HIRect inflatedDrawRect =
        CGRectMake(-kMaxFocusRingWidth, -kMaxFocusRingWidth, w, h);
    CGContextDrawImage(aCGContext, inflatedDrawRect, bitmap);

    CGContextSetCTM(aCGContext, ctm);

    CGImageRelease(bitmap);
    CGContextRelease(bitmapctx);
  }

  CGContextSetCTM(aCGContext, savedCTM);
}

static void RenderButton(CGContextRef cgContext, const HIRect& aRenderRect,
                         void* aData) {
  HIThemeButtonDrawInfo* bdi = (HIThemeButtonDrawInfo*)aData;
  HIThemeDrawButton(&aRenderRect, bdi, cgContext, kHIThemeOrientationNormal,
                    NULL);
}

void nsNativeThemeCocoa::DrawHIThemeButton(
    CGContextRef cgContext, const HIRect& aRect, ThemeButtonKind aKind,
    ThemeButtonValue aValue, ThemeDrawState aState,
    ThemeButtonAdornment aAdornment, const ControlParams& aParams) {
  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;

  HIThemeButtonDrawInfo bdi;
  bdi.version = 0;
  bdi.kind = aKind;
  bdi.value = aValue;
  bdi.state = aState;
  bdi.adornment = aAdornment;

  if (aParams.focused && aParams.insideActiveWindow) {
    bdi.adornment |= kThemeAdornmentFocus;
  }

  RenderTransformedHIThemeControl(cgContext, aRect, RenderButton, &bdi,
                                  aParams.rtl);

#if DRAW_IN_FRAME_DEBUG
  CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
  CGContextFillRect(cgContext, inBoxRect);
#endif

  NS_OBJC_END_TRY_IGNORE_BLOCK;
}

void nsNativeThemeCocoa::DrawButton(CGContextRef cgContext,
                                    const HIRect& inBoxRect,
                                    const ButtonParams& aParams) {
  ControlParams controlParams = aParams.controlParams;

  switch (aParams.button) {
    case ButtonType::eRegularPushButton:
    case ButtonType::eDefaultPushButton:
      DrawPushButton(cgContext, inBoxRect, aParams.button, controlParams);
      return;
    case ButtonType::eSquareBezelPushButton:
      DrawSquareBezelPushButton(cgContext, inBoxRect, controlParams);
      return;
    case ButtonType::eArrowButton:
      DrawHIThemeButton(cgContext, inBoxRect, kThemeArrowButton, kThemeButtonOn,
                        kThemeStateUnavailable, kThemeAdornmentArrowDownArrow,
                        controlParams);
      return;
    case ButtonType::eHelpButton:
      DrawHelpButton(cgContext, inBoxRect, controlParams);
      return;
    case ButtonType::eDisclosureButtonClosed:
      DrawDisclosureButton(cgContext, inBoxRect, controlParams,
                           NSControlStateValueOff);
      return;
    case ButtonType::eDisclosureButtonOpen:
      DrawDisclosureButton(cgContext, inBoxRect, controlParams,
                           NSControlStateValueOn);
      return;
  }
}

MOZ_RUNINIT static const CellRenderSettings dropdownSettings = {
    {
        NSMakeSize(0, 16),  // mini
        NSMakeSize(0, 19),  // small
        NSMakeSize(0, 22)   // regular
    },
    {
        NSMakeSize(18, 0),  // mini
        NSMakeSize(38, 0),  // small
        NSMakeSize(44, 0)   // regular
    },
    {{
         // Leopard
         {1, 1, 2, 1},  // mini
         {3, 0, 3, 1},  // small
         {3, 0, 3, 0}   // regular
     },
     {
         // Yosemite
         {1, 1, 2, 1},  // mini
         {3, 0, 3, 1},  // small
         {3, 0, 3, 0}   // regular
     }}};

MOZ_RUNINIT static const CellRenderSettings editableMenulistSettings = {
    {
        NSMakeSize(0, 15),  // mini
        NSMakeSize(0, 18),  // small
        NSMakeSize(0, 21)   // regular
    },
    {
        NSMakeSize(18, 0),  // mini
        NSMakeSize(38, 0),  // small
        NSMakeSize(44, 0)   // regular
    },
    {{
         // Leopard
         {0, 0, 2, 2},  // mini
         {0, 0, 3, 2},  // small
         {0, 1, 3, 3}   // regular
     },
     {
         // Yosemite
         {0, 0, 2, 2},  // mini
         {0, 0, 3, 2},  // small
         {0, 1, 3, 3}   // regular
     }}};

void nsNativeThemeCocoa::DrawDropdown(CGContextRef cgContext,
                                      const HIRect& inBoxRect,
                                      const DropdownParams& aParams) {
  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;

  [mDropdownCell setPullsDown:aParams.pullsDown];
  NSCell* cell =
      aParams.editable ? (NSCell*)mComboBoxCell : (NSCell*)mDropdownCell;

  ApplyControlParamsToNSCell(aParams.controlParams, cell);

  if (aParams.controlParams.insideActiveWindow) {
    [cell setControlTint:[NSColor currentControlTint]];
  } else {
    [cell setControlTint:NSClearControlTint];
  }

  const CellRenderSettings& settings =
      aParams.editable ? editableMenulistSettings : dropdownSettings;

  if (mCellDrawWindow) {
    mCellDrawWindow.cellsShouldLookActive =
        aParams.controlParams.insideActiveWindow;
  }
  DrawCellWithSnapping(cell, cgContext, inBoxRect, settings, 0.5f,
                       mCellDrawView, aParams.controlParams.rtl);

  NS_OBJC_END_TRY_IGNORE_BLOCK;
}

MOZ_RUNINIT static const CellRenderSettings spinnerSettings = {
    {
        NSMakeSize(11,
                   16),  // mini (width trimmed by 2px to reduce blank border)
        NSMakeSize(15, 22),  // small
        NSMakeSize(19, 27)   // regular
    },
    {
        NSMakeSize(11,
                   16),  // mini (width trimmed by 2px to reduce blank border)
        NSMakeSize(15, 22),  // small
        NSMakeSize(19, 27)   // regular
    },
    {{
         // Leopard
         {0, 0, 0, 0},  // mini
         {0, 0, 0, 0},  // small
         {0, 0, 0, 0}   // regular
     },
     {
         // Yosemite
         {0, 0, 0, 0},  // mini
         {0, 0, 0, 0},  // small
         {0, 0, 0, 0}   // regular
     }}};

HIThemeButtonDrawInfo nsNativeThemeCocoa::SpinButtonDrawInfo(
    ThemeButtonKind aKind, const SpinButtonParams& aParams) {
  HIThemeButtonDrawInfo bdi;
  bdi.version = 0;
  bdi.kind = aKind;
  bdi.value = kThemeButtonOff;
  bdi.adornment = kThemeAdornmentNone;

  if (aParams.disabled) {
    bdi.state = kThemeStateUnavailable;
  } else if (aParams.insideActiveWindow && aParams.pressedButton) {
    if (*aParams.pressedButton == SpinButton::eUp) {
      bdi.state = kThemeStatePressedUp;
    } else {
      bdi.state = kThemeStatePressedDown;
    }
  } else {
    bdi.state = kThemeStateActive;
  }

  return bdi;
}

void nsNativeThemeCocoa::DrawSpinButtons(CGContextRef cgContext,
                                         const HIRect& inBoxRect,
                                         const SpinButtonParams& aParams) {
  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;

  HIThemeButtonDrawInfo bdi = SpinButtonDrawInfo(kThemeIncDecButton, aParams);
  HIThemeDrawButton(&inBoxRect, &bdi, cgContext, HITHEME_ORIENTATION, NULL);

  NS_OBJC_END_TRY_IGNORE_BLOCK;
}

void nsNativeThemeCocoa::DrawSpinButton(CGContextRef cgContext,
                                        const HIRect& inBoxRect,
                                        SpinButton aDrawnButton,
                                        const SpinButtonParams& aParams) {
  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;

  HIThemeButtonDrawInfo bdi =
      SpinButtonDrawInfo(kThemeIncDecButtonMini, aParams);

  // Cocoa only allows kThemeIncDecButton to paint the up and down spin buttons
  // together as a single unit (presumably because when one button is active,
  // the appearance of both changes (in different ways)). Here we have to paint
  // both buttons, using clip to hide the one we don't want to paint.
  HIRect drawRect = inBoxRect;
  drawRect.size.height *= 2;
  if (aDrawnButton == SpinButton::eDown) {
    drawRect.origin.y -= inBoxRect.size.height;
  }

  // Shift the drawing a little to the left, since cocoa paints with more
  // blank space around the visual buttons than we'd like:
  drawRect.origin.x -= 1;

  CGContextSaveGState(cgContext);
  CGContextClipToRect(cgContext, inBoxRect);

  HIThemeDrawButton(&drawRect, &bdi, cgContext, HITHEME_ORIENTATION, NULL);

  CGContextRestoreGState(cgContext);

  NS_OBJC_END_TRY_IGNORE_BLOCK;
}

MOZ_RUNINIT static const CellRenderSettings progressSettings[2][2] = {
    // Vertical progress bar.
    {// Determined settings.
     {{
          NSZeroSize,         // mini
          NSMakeSize(10, 0),  // small
          NSMakeSize(16, 0)   // regular
      },
      {NSZeroSize, NSZeroSize, NSZeroSize},
      {{
          // Leopard
          {0, 0, 0, 0},  // mini
          {1, 1, 1, 1},  // small
          {1, 1, 1, 1}   // regular
      }}},
     // There is no horizontal margin in regular undetermined size.
     {{
          NSZeroSize,         // mini
          NSMakeSize(10, 0),  // small
          NSMakeSize(16, 0)   // regular
      },
      {NSZeroSize, NSZeroSize, NSZeroSize},
      {{
           // Leopard
           {0, 0, 0, 0},  // mini
           {1, 1, 1, 1},  // small
           {1, 0, 1, 0}   // regular
       },
       {
           // Yosemite
           {0, 0, 0, 0},  // mini
           {1, 1, 1, 1},  // small
           {1, 0, 1, 0}   // regular
       }}}},
    // Horizontal progress bar.
    {// Determined settings.
     {{
          NSZeroSize,         // mini
          NSMakeSize(0, 10),  // small
          NSMakeSize(0, 16)   // regular
      },
      {NSZeroSize, NSZeroSize, NSZeroSize},
      {{
           // Leopard
           {0, 0, 0, 0},  // mini
           {1, 1, 1, 1},  // small
           {1, 1, 1, 1}   // regular
       },
       {
           // Yosemite
           {0, 0, 0, 0},  // mini
           {1, 1, 1, 1},  // small
           {1, 1, 1, 1}   // regular
       }}},
     // There is no horizontal margin in regular undetermined size.
     {{
          NSZeroSize,         // mini
          NSMakeSize(0, 10),  // small
          NSMakeSize(0, 16)   // regular
      },
      {NSZeroSize, NSZeroSize, NSZeroSize},
      {{
           // Leopard
           {0, 0, 0, 0},  // mini
           {1, 1, 1, 1},  // small
           {0, 1, 0, 1}   // regular
       },
       {
           // Yosemite
           {0, 0, 0, 0},  // mini
           {1, 1, 1, 1},  // small
           {0, 1, 0, 1}   // regular
       }}}}};

nsNativeThemeCocoa::ProgressParams nsNativeThemeCocoa::ComputeProgressParams(
    nsIFrame* aFrame, ElementState aEventState, bool aIsHorizontal) {
  ProgressParams params;
  params.value = GetProgressValue(aFrame);
  params.max = GetProgressMaxValue(aFrame);
  params.verticalAlignFactor = VerticalAlignFactor(aFrame);
  params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
  params.indeterminate = aEventState.HasState(ElementState::INDETERMINATE);
  params.horizontal = aIsHorizontal;
  params.rtl = IsFrameRTL(aFrame);
  return params;
}

void nsNativeThemeCocoa::DrawProgress(CGContextRef cgContext,
                                      const HIRect& inBoxRect,
                                      const ProgressParams& aParams) {
  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;

  NSProgressBarCell* cell = mProgressBarCell;

  [cell setValue:aParams.value];
  [cell setMax:aParams.max];
  [cell setIndeterminate:aParams.indeterminate];
  [cell setHorizontal:aParams.horizontal];
  [cell
      setControlTint:(aParams.insideActiveWindow ? [NSColor currentControlTint]
                                                 : NSClearControlTint)];

  if (mCellDrawWindow) {
    mCellDrawWindow.cellsShouldLookActive = aParams.insideActiveWindow;
  }
  DrawCellWithSnapping(
      cell, cgContext, inBoxRect,
      progressSettings[aParams.horizontal][aParams.indeterminate],
      aParams.verticalAlignFactor, mCellDrawView, aParams.rtl);

  NS_OBJC_END_TRY_IGNORE_BLOCK;
}

MOZ_RUNINIT static const CellRenderSettings meterSetting = {
    {
        NSMakeSize(0, 16),  // mini
        NSMakeSize(0, 16),  // small
        NSMakeSize(0, 16)   // regular
    },
    {NSZeroSize, NSZeroSize, NSZeroSize},
    {{
         // Leopard
         {1, 1, 1, 1},  // mini
         {1, 1, 1, 1},  // small
         {1, 1, 1, 1}   // regular
     },
     {
         // Yosemite
         {1, 1, 1, 1},  // mini
         {1, 1, 1, 1},  // small
         {1, 1, 1, 1}   // regular
     }}};

nsNativeThemeCocoa::MeterParams nsNativeThemeCocoa::ComputeMeterParams(
    nsIFrame* aFrame) {
  nsIContent* content = aFrame->GetContent();
  if (!(content && content->IsHTMLElement(nsGkAtoms::meter))) {
    return MeterParams();
  }

  HTMLMeterElement* meterElement = static_cast<HTMLMeterElement*>(content);
  MeterParams params;
  params.value = meterElement->Value();
  params.min = meterElement->Min();
  params.max = meterElement->Max();
  ElementState states = meterElement->State();
  if (states.HasState(ElementState::SUB_OPTIMUM)) {
    params.optimumState = OptimumState::eSubOptimum;
  } else if (states.HasState(ElementState::SUB_SUB_OPTIMUM)) {
    params.optimumState = OptimumState::eSubSubOptimum;
  }
  params.horizontal = !IsVerticalMeter(aFrame);
  params.verticalAlignFactor = VerticalAlignFactor(aFrame);
  params.rtl = IsFrameRTL(aFrame);

  return params;
}

void nsNativeThemeCocoa::DrawMeter(CGContextRef cgContext,
                                   const HIRect& inBoxRect,
                                   const MeterParams& aParams) {
  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK

  NSLevelIndicatorCell* cell = mMeterBarCell;

  [cell setMinValue:aParams.min];
  [cell setMaxValue:aParams.max];
  [cell setDoubleValue:aParams.value];

  /**
   * The way HTML and Cocoa defines the meter/indicator widget are different.
   * So, we are going to use a trick to get the Cocoa widget showing what we
   * are expecting: we set the warningValue or criticalValue to the current
   * value when we want to have the widget to be in the warning or critical
   * state.
   */
  switch (aParams.optimumState) {
    case OptimumState::eOptimum:
      [cell setWarningValue:aParams.max + 1];
      [cell setCriticalValue:aParams.max + 1];
      break;
    case OptimumState::eSubOptimum:
      [cell setWarningValue:aParams.value];
      [cell setCriticalValue:aParams.max + 1];
      break;
    case OptimumState::eSubSubOptimum:
      [cell setWarningValue:aParams.max + 1];
      [cell setCriticalValue:aParams.value];
      break;
  }

  HIRect rect = CGRectStandardize(inBoxRect);
  BOOL vertical = !aParams.horizontal;

  CGContextSaveGState(cgContext);

  if (vertical) {
    /**
     * Cocoa doesn't provide a vertical meter bar so to show one, we have to
     * show a rotated horizontal meter bar.
     * Given that we want to show a vertical meter bar, we assume that the rect
     * has vertical dimensions but we can't correctly draw a meter widget inside
     * such a rectangle so we need to inverse width and height (and re-position)
     * to get a rectangle with horizontal dimensions.
     * Finally, we want to show a vertical meter so we want to rotate the result
     * so it is vertical. We do that by changing the context.
     */
    CGFloat tmp = rect.size.width;
    rect.size.width = rect.size.height;
    rect.size.height = tmp;
    rect.origin.x += rect.size.height / 2.f - rect.size.width / 2.f;
    rect.origin.y += rect.size.width / 2.f - rect.size.height / 2.f;

    CGContextTranslateCTM(cgContext, CGRectGetMidX(rect), CGRectGetMidY(rect));
    CGContextRotateCTM(cgContext, -M_PI / 2.f);
    CGContextTranslateCTM(cgContext, -CGRectGetMidX(rect),
                          -CGRectGetMidY(rect));
  }

  if (mCellDrawWindow) {
    mCellDrawWindow.cellsShouldLookActive =
        YES;  // TODO: propagate correct activeness state
  }
  DrawCellWithSnapping(cell, cgContext, rect, meterSetting,
                       aParams.verticalAlignFactor, mCellDrawView,
                       !vertical && aParams.rtl);

  CGContextRestoreGState(cgContext);

  NS_OBJC_END_TRY_IGNORE_BLOCK
}

void nsNativeThemeCocoa::DrawTabPanel(CGContextRef cgContext,
                                      const HIRect& inBoxRect,
                                      bool aIsInsideActiveWindow) {
  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;

  HIThemeTabPaneDrawInfo tpdi;

  tpdi.version = 1;
  tpdi.state = aIsInsideActiveWindow ? kThemeStateActive : kThemeStateInactive;
  tpdi.direction = kThemeTabNorth;
  tpdi.size = kHIThemeTabSizeNormal;
  tpdi.kind = kHIThemeTabKindNormal;

  HIThemeDrawTabPane(&inBoxRect, &tpdi, cgContext, HITHEME_ORIENTATION);

  NS_OBJC_END_TRY_IGNORE_BLOCK;
}

Maybe<nsNativeThemeCocoa::ScaleParams>
nsNativeThemeCocoa::ComputeHTMLScaleParams(nsIFrame* aFrame,
                                           ElementState aEventState) {
  nsRangeFrame* rangeFrame = do_QueryFrame(aFrame);
  if (!rangeFrame) {
    return Nothing();
  }

  bool isHorizontal = IsRangeHorizontal(aFrame);

  // ScaleParams requires integer min, max and value. This is purely for
  // drawing, so we normalize to a range 0-1000 here.
  ScaleParams params;
  params.value = int32_t(rangeFrame->GetValueAsFractionOfRange() * 1000);
  params.min = 0;
  params.max = 1000;
  params.reverse = !isHorizontal || rangeFrame->IsRightToLeft();
  params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
  params.focused = aEventState.HasState(ElementState::FOCUSRING);
  params.disabled = aEventState.HasState(ElementState::DISABLED);
  params.horizontal = isHorizontal;
  return Some(params);
}

void nsNativeThemeCocoa::DrawScale(CGContextRef cgContext,
                                   const HIRect& inBoxRect,
                                   const ScaleParams& aParams) {
  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;

  HIThemeTrackDrawInfo tdi;

  tdi.version = 0;
  tdi.kind = kThemeMediumSlider;
  tdi.bounds = inBoxRect;
  tdi.min = aParams.min;
  tdi.max = aParams.max;
  tdi.value = aParams.value;
  tdi.attributes = kThemeTrackShowThumb;
  if (aParams.horizontal) {
    tdi.attributes |= kThemeTrackHorizontal;
  }
  if (aParams.reverse) {
    tdi.attributes |= kThemeTrackRightToLeft;
  }
  if (aParams.focused) {
    tdi.attributes |= kThemeTrackHasFocus;
  }
  if (aParams.disabled) {
    tdi.enableState = kThemeTrackDisabled;
  } else {
    tdi.enableState =
        aParams.insideActiveWindow ? kThemeTrackActive : kThemeTrackInactive;
  }
  tdi.trackInfo.slider.thumbDir = kThemeThumbPlain;
  tdi.trackInfo.slider.pressState = 0;

  HIThemeDrawTrack(&tdi, NULL, cgContext, HITHEME_ORIENTATION);

  NS_OBJC_END_TRY_IGNORE_BLOCK;
}

nsIFrame* nsNativeThemeCocoa::SeparatorResponsibility(nsIFrame* aBefore,
                                                      nsIFrame* aAfter) {
  // Usually a separator is drawn by the segment to the right of the
  // separator, but pressed and selected segments have higher priority.
--> --------------------

--> maximum size reached

--> --------------------

[ Dauer der Verarbeitung: 0.37 Sekunden  (vorverarbeitet)  ]