Bumping gaia.json for 2 gaia revision(s) a=gaia-bump
[gecko.git] / widget / cocoa / nsNativeThemeCocoa.mm
bloba518d66a2b185ae4438928ba9792d0605f7c308c
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsNativeThemeCocoa.h"
7 #include "nsObjCExceptions.h"
8 #include "nsNumberControlFrame.h"
9 #include "nsRangeFrame.h"
10 #include "nsRenderingContext.h"
11 #include "nsRect.h"
12 #include "nsSize.h"
13 #include "nsThemeConstants.h"
14 #include "nsIPresShell.h"
15 #include "nsPresContext.h"
16 #include "nsIContent.h"
17 #include "nsIDocument.h"
18 #include "nsIFrame.h"
19 #include "nsIAtom.h"
20 #include "nsNameSpaceManager.h"
21 #include "nsPresContext.h"
22 #include "nsGkAtoms.h"
23 #include "nsCocoaFeatures.h"
24 #include "nsCocoaWindow.h"
25 #include "nsNativeThemeColors.h"
26 #include "nsIScrollableFrame.h"
27 #include "mozilla/EventStates.h"
28 #include "mozilla/dom/Element.h"
29 #include "mozilla/dom/HTMLMeterElement.h"
30 #include "nsLookAndFeel.h"
31 #include "VibrancyManager.h"
33 #include "gfxContext.h"
34 #include "gfxQuartzSurface.h"
35 #include "gfxQuartzNativeDrawing.h"
36 #include <algorithm>
38 using namespace mozilla;
39 using namespace mozilla::gfx;
40 using mozilla::dom::HTMLMeterElement;
42 #define DRAW_IN_FRAME_DEBUG 0
43 #define SCROLLBARS_VISUAL_DEBUG 0
45 // private Quartz routines needed here
46 extern "C" {
47   CG_EXTERN void CGContextSetCTM(CGContextRef, CGAffineTransform);
48   typedef CFTypeRef CUIRendererRef;
49   void CUIDraw(CUIRendererRef r, CGRect rect, CGContextRef ctx, CFDictionaryRef options, CFDictionaryRef* result);
52 // Workaround for NSCell control tint drawing
53 // Without this workaround, NSCells are always drawn with the clear control tint
54 // as long as they're not attached to an NSControl which is a subview of an active window.
55 // XXXmstange Why doesn't Webkit need this?
56 @implementation NSCell (ControlTintWorkaround)
57 - (int)_realControlTint { return [self controlTint]; }
58 @end
60 // The purpose of this class is to provide objects that can be used when drawing
61 // NSCells using drawWithFrame:inView: without causing any harm. The only
62 // messages that will be sent to such an object are "isFlipped" and
63 // "currentEditor": isFlipped needs to return YES in order to avoid drawing bugs
64 // on 10.4 (see bug 465069); currentEditor (which isn't even a method of
65 // NSView) will be called when drawing search fields, and we only provide it in
66 // order to prevent "unrecognized selector" exceptions.
67 // There's no need to pass the actual NSView that we're drawing into to
68 // drawWithFrame:inView:. What's more, doing so even causes unnecessary
69 // invalidations as soon as we draw a focusring!
70 @interface CellDrawView : NSView
72 @end;
74 @implementation CellDrawView
76 - (BOOL)isFlipped
78   return YES;
81 - (NSText*)currentEditor
83   return nil;
86 @end
88 static void
89 DrawFocusRingForCellIfNeeded(NSCell* aCell, NSRect aWithFrame, NSView* aInView)
91   if ([aCell showsFirstResponder]) {
92     CGContextRef cgContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
93     CGContextSaveGState(cgContext);
95     // It's important to set the focus ring style before we enter the
96     // transparency layer so that the transparency layer only contains
97     // the normal button mask without the focus ring, and the conversion
98     // to the focus ring shape happens only when the transparency layer is
99     // ended.
100     NSSetFocusRingStyle(NSFocusRingOnly);
102     // We need to draw the whole button into a transparency layer because
103     // many button types are composed of multiple parts, and if these parts
104     // were drawn while the focus ring style was active, each individual part
105     // would produce a focus ring for itself. But we only want one focus ring
106     // for the whole button. The transparency layer is a way to merge the
107     // individual button parts together before the focus ring shape is
108     // calculated.
109     CGContextBeginTransparencyLayerWithRect(cgContext, NSRectToCGRect(aWithFrame), 0);
110     [aCell drawFocusRingMaskWithFrame:aWithFrame inView:aInView];
111     CGContextEndTransparencyLayer(cgContext);
113     CGContextRestoreGState(cgContext);
114   }
117 static bool
118 FocusIsDrawnByDrawWithFrame()
120 #if defined(MAC_OS_X_VERSION_10_8) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8
121   // When building with the 10.8 SDK or higher, focus rings don't draw as part
122   // of -[NSCell drawWithFrame:inView:] and must be drawn by a separate call
123   // to -[NSCell drawFocusRingMaskWithFrame:inView:]; .
124   // See the NSButtonCell section under
125   // https://developer.apple.com/library/mac/releasenotes/AppKit/RN-AppKitOlderNotes/#X10_8Notes
126   return false;
127 #else
128   // On 10.10 and up, this is the case even when building against the 10.7 SDK
129   // or lower.
130   return !nsCocoaFeatures::OnYosemiteOrLater();
131 #endif
134 static void
135 DrawCellIncludingFocusRing(NSCell* aCell, NSRect aWithFrame, NSView* aInView)
137   [aCell drawWithFrame:aWithFrame inView:aInView];
139   if (!FocusIsDrawnByDrawWithFrame()) {
140     DrawFocusRingForCellIfNeeded(aCell, aWithFrame, aInView);
141   }
145  * NSProgressBarCell is used to draw progress bars of any size.
146  */
147 @interface NSProgressBarCell : NSCell
149     /*All instance variables are private*/
150     double mValue;
151     double mMax;
152     bool   mIsIndeterminate;
153     bool   mIsHorizontal;
156 - (void)setValue:(double)value;
157 - (double)value;
158 - (void)setMax:(double)max;
159 - (double)max;
160 - (void)setIndeterminate:(bool)aIndeterminate;
161 - (bool)isIndeterminate;
162 - (void)setHorizontal:(bool)aIsHorizontal;
163 - (bool)isHorizontal;
164 - (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView;
165 @end
167 @implementation NSProgressBarCell
169 - (void)setMax:(double)aMax
171   mMax = aMax;
174 - (double)max
176   return mMax;
179 - (void)setValue:(double)aValue
181   mValue = aValue;
184 - (double)value
186   return mValue;
189 - (void)setIndeterminate:(bool)aIndeterminate
191   mIsIndeterminate = aIndeterminate;
194 - (bool)isIndeterminate
196   return mIsIndeterminate;
199 - (void)setHorizontal:(bool)aIsHorizontal
201   mIsHorizontal = aIsHorizontal;
204 - (bool)isHorizontal
206   return mIsHorizontal;
209 - (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
211   CGContext* cgContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
213   HIThemeTrackDrawInfo tdi;
215   tdi.version = 0;
216   tdi.min = 0;
218   tdi.value = INT32_MAX * (mValue / mMax);
219   tdi.max = INT32_MAX;
220   tdi.bounds = NSRectToCGRect(cellFrame);
221   tdi.attributes = mIsHorizontal ? kThemeTrackHorizontal : 0;
222   tdi.enableState = [self controlTint] == NSClearControlTint ? kThemeTrackInactive
223                                                              : kThemeTrackActive;
225   NSControlSize size = [self controlSize];
226   if (size == NSRegularControlSize) {
227     tdi.kind = mIsIndeterminate ? kThemeLargeIndeterminateBar
228                                 : kThemeLargeProgressBar;
229   } else {
230     NS_ASSERTION(size == NSSmallControlSize,
231                  "We shouldn't have another size than small and regular for the moment");
232     tdi.kind = mIsIndeterminate ? kThemeMediumIndeterminateBar
233                                 : kThemeMediumProgressBar;
234   }
236   int32_t stepsPerSecond = mIsIndeterminate ? 60 : 30;
237   int32_t milliSecondsPerStep = 1000 / stepsPerSecond;
238   tdi.trackInfo.progress.phase = uint8_t(PR_IntervalToMilliseconds(PR_IntervalNow()) /
239                                          milliSecondsPerStep);
241   HIThemeDrawTrack(&tdi, NULL, cgContext, kHIThemeOrientationNormal);
244 @end
246 @interface ContextAwareSearchFieldCell : NSSearchFieldCell
248   nsIFrame* mContext;
251 // setContext: stores the searchfield nsIFrame so that it can be consulted
252 // during painting. Please reset this by calling setContext:nullptr as soon as
253 // you're done with painting because we don't want to keep a dangling pointer.
254 - (void)setContext:(nsIFrame*)aContext;
255 @end
257 @implementation ContextAwareSearchFieldCell
259 - (id)initTextCell:(NSString*)aString
261   if ((self = [super initTextCell:aString])) {
262     mContext = nullptr;
263   }
264   return self;
267 - (void)setContext:(nsIFrame*)aContext
269   mContext = aContext;
272 static BOOL IsToolbarStyleContainer(nsIFrame* aFrame)
274   nsIContent* content = aFrame->GetContent();
275   if (!content)
276     return NO;
278   if (content->Tag() == nsGkAtoms::toolbar ||
279       content->Tag() == nsGkAtoms::toolbox ||
280       content->Tag() == nsGkAtoms::statusbar)
281     return YES;
283   switch (aFrame->StyleDisplay()->mAppearance) {
284     case NS_THEME_TOOLBAR:
285     case NS_THEME_MOZ_MAC_UNIFIED_TOOLBAR:
286     case NS_THEME_STATUSBAR:
287       return YES;
288     default:
289       return NO;
290   }
293 - (BOOL)_isToolbarMode
295   // On 10.7, searchfields have two different styles, depending on whether
296   // the searchfield is on top of of window chrome. This function is called on
297   // 10.7 during drawing in order to determine which style to use.
298   for (nsIFrame* frame = mContext; frame; frame = frame->GetParent()) {
299     if (IsToolbarStyleContainer(frame)) {
300       return YES;
301     }
302   }
303   return NO;
306 @end
308 // Workaround for Bug 542048
309 // On 64-bit, NSSearchFieldCells don't draw focus rings.
310 #if defined(__x86_64__)
312 @interface SearchFieldCellWithFocusRing : ContextAwareSearchFieldCell {} @end
314 @implementation SearchFieldCellWithFocusRing
316 - (void)drawWithFrame:(NSRect)rect inView:(NSView*)controlView
318   [super drawWithFrame:rect inView:controlView];
320   if (FocusIsDrawnByDrawWithFrame()) {
321     DrawFocusRingForCellIfNeeded(self, rect, controlView);
322   }
325 - (void)drawFocusRingMaskWithFrame:(NSRect)rect inView:(NSView*)controlView
327   // By default this draws nothing. I don't know why.
328   // We just draw the search field again. It's a great mask shape for its own
329   // focus ring.
330   [super drawWithFrame:rect inView:controlView];
333 @end
335 #endif
337 #define HITHEME_ORIENTATION kHIThemeOrientationNormal
338 #define MAX_FOCUS_RING_WIDTH 4
340 // These enums are for indexing into the margin array.
341 enum {
342   leopardOS = 0
345 enum {
346   miniControlSize,
347   smallControlSize,
348   regularControlSize
351 enum {
352   leftMargin,
353   topMargin,
354   rightMargin,
355   bottomMargin
358 static int EnumSizeForCocoaSize(NSControlSize cocoaControlSize) {
359   if (cocoaControlSize == NSMiniControlSize)
360     return miniControlSize;
361   else if (cocoaControlSize == NSSmallControlSize)
362     return smallControlSize;
363   else
364     return regularControlSize;
367 static NSControlSize CocoaSizeForEnum(int32_t enumControlSize) {
368   if (enumControlSize == miniControlSize)
369     return NSMiniControlSize;
370   else if (enumControlSize == smallControlSize)
371     return NSSmallControlSize;
372   else
373     return NSRegularControlSize;
376 static NSString* CUIControlSizeForCocoaSize(NSControlSize aControlSize)
378   if (aControlSize == NSRegularControlSize)
379     return @"regular";
380   else if (aControlSize == NSSmallControlSize)
381     return @"small";
382   else
383     return @"mini";
386 static void InflateControlRect(NSRect* rect, NSControlSize cocoaControlSize, const float marginSet[][3][4])
388   if (!marginSet)
389     return;
391   static int osIndex = leopardOS;
392   int controlSize = EnumSizeForCocoaSize(cocoaControlSize);
393   const float* buttonMargins = marginSet[osIndex][controlSize];
394   rect->origin.x -= buttonMargins[leftMargin];
395   rect->origin.y -= buttonMargins[bottomMargin];
396   rect->size.width += buttonMargins[leftMargin] + buttonMargins[rightMargin];
397   rect->size.height += buttonMargins[bottomMargin] + buttonMargins[topMargin];
400 static NSWindow* NativeWindowForFrame(nsIFrame* aFrame,
401                                       nsIWidget** aTopLevelWidget = NULL)
403   if (!aFrame)
404     return nil;  
406   nsIWidget* widget = aFrame->GetNearestWidget();
407   if (!widget)
408     return nil;
410   nsIWidget* topLevelWidget = widget->GetTopLevelWidget();
411   if (aTopLevelWidget)
412     *aTopLevelWidget = topLevelWidget;
414   return (NSWindow*)topLevelWidget->GetNativeData(NS_NATIVE_WINDOW);
417 static NSSize
418 WindowButtonsSize(nsIFrame* aFrame)
420   NSWindow* window = NativeWindowForFrame(aFrame);
421   if (!window) {
422     // Return fallback values.
423     if (!nsCocoaFeatures::OnLionOrLater())
424       return NSMakeSize(57, 16);
425     return NSMakeSize(54, 16);
426   }
428   NSRect buttonBox = NSZeroRect;
429   NSButton* closeButton = [window standardWindowButton:NSWindowCloseButton];
430   if (closeButton) {
431     buttonBox = NSUnionRect(buttonBox, [closeButton frame]);
432   }
433   NSButton* minimizeButton = [window standardWindowButton:NSWindowMiniaturizeButton];
434   if (minimizeButton) {
435     buttonBox = NSUnionRect(buttonBox, [minimizeButton frame]);
436   }
437   NSButton* zoomButton = [window standardWindowButton:NSWindowZoomButton];
438   if (zoomButton) {
439     buttonBox = NSUnionRect(buttonBox, [zoomButton frame]);
440   }
441   return buttonBox.size;
444 static BOOL FrameIsInActiveWindow(nsIFrame* aFrame)
446   nsIWidget* topLevelWidget = NULL;
447   NSWindow* win = NativeWindowForFrame(aFrame, &topLevelWidget);
448   if (!topLevelWidget || !win)
449     return YES;
451   // XUL popups, e.g. the toolbar customization popup, can't become key windows,
452   // but controls in these windows should still get the active look.
453   if (topLevelWidget->WindowType() == eWindowType_popup)
454     return YES;
455   if ([win isSheet])
456     return [win isKeyWindow];
457   return [win isMainWindow] && ![win attachedSheet];
460 // Toolbar controls and content controls respond to different window
461 // activeness states.
462 static BOOL IsActive(nsIFrame* aFrame, BOOL aIsToolbarControl)
464   if (aIsToolbarControl)
465     return [NativeWindowForFrame(aFrame) isMainWindow];
466   return FrameIsInActiveWindow(aFrame);
469 NS_IMPL_ISUPPORTS_INHERITED(nsNativeThemeCocoa, nsNativeTheme, nsITheme)
472 nsNativeThemeCocoa::nsNativeThemeCocoa()
474   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
476   // provide a local autorelease pool, as this is called during startup
477   // before the main event-loop pool is in place
478   nsAutoreleasePool pool;
480   mHelpButtonCell = [[NSButtonCell alloc] initTextCell:nil];
481   [mHelpButtonCell setBezelStyle:NSHelpButtonBezelStyle];
482   [mHelpButtonCell setButtonType:NSMomentaryPushInButton];
483   [mHelpButtonCell setHighlightsBy:NSPushInCellMask];
485   mPushButtonCell = [[NSButtonCell alloc] initTextCell:nil];
486   [mPushButtonCell setButtonType:NSMomentaryPushInButton];
487   [mPushButtonCell setHighlightsBy:NSPushInCellMask];
489   mRadioButtonCell = [[NSButtonCell alloc] initTextCell:nil];
490   [mRadioButtonCell setButtonType:NSRadioButton];
492   mCheckboxCell = [[NSButtonCell alloc] initTextCell:nil];
493   [mCheckboxCell setButtonType:NSSwitchButton];
494   [mCheckboxCell setAllowsMixedState:YES];
496 #if defined(__x86_64__)
497   mSearchFieldCell = [[SearchFieldCellWithFocusRing alloc] initTextCell:@""];
498 #else
499   mSearchFieldCell = [[ContextAwareSearchFieldCell alloc] initTextCell:@""];
500 #endif
501   [mSearchFieldCell setBezelStyle:NSTextFieldRoundedBezel];
502   [mSearchFieldCell setBezeled:YES];
503   [mSearchFieldCell setEditable:YES];
504   [mSearchFieldCell setFocusRingType:NSFocusRingTypeExterior];
506   mDropdownCell = [[NSPopUpButtonCell alloc] initTextCell:@"" pullsDown:NO];
508   mComboBoxCell = [[NSComboBoxCell alloc] initTextCell:@""];
509   [mComboBoxCell setBezeled:YES];
510   [mComboBoxCell setEditable:YES];
511   [mComboBoxCell setFocusRingType:NSFocusRingTypeExterior];
513   mProgressBarCell = [[NSProgressBarCell alloc] init];
515   mMeterBarCell = [[NSLevelIndicatorCell alloc]
516                     initWithLevelIndicatorStyle:NSContinuousCapacityLevelIndicatorStyle];
518   mCellDrawView = [[CellDrawView alloc] init];
520   NS_OBJC_END_TRY_ABORT_BLOCK;
523 nsNativeThemeCocoa::~nsNativeThemeCocoa()
525   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
527   [mMeterBarCell release];
528   [mProgressBarCell release];
529   [mHelpButtonCell release];
530   [mPushButtonCell release];
531   [mRadioButtonCell release];
532   [mCheckboxCell release];
533   [mSearchFieldCell release];
534   [mDropdownCell release];
535   [mComboBoxCell release];
536   [mCellDrawView release];
538   NS_OBJC_END_TRY_ABORT_BLOCK;
541 // Limit on the area of the target rect (in pixels^2) in
542 // DrawCellWithScaling(), DrawButton() and DrawScrollbar(), above which we
543 // don't draw the object into a bitmap buffer.  This is to avoid crashes in
544 // [NSGraphicsContext graphicsContextWithGraphicsPort:flipped:] and
545 // CGContextDrawImage(), and also to avoid very poor drawing performance in
546 // CGContextDrawImage() when it scales the bitmap (particularly if xscale or
547 // yscale is less than but near 1 -- e.g. 0.9).  This value was determined
548 // by trial and error, on OS X 10.4.11 and 10.5.4, and on systems with
549 // different amounts of RAM.
550 #define BITMAP_MAX_AREA 500000
552 static int
553 GetBackingScaleFactorForRendering(CGContextRef cgContext)
555   CGAffineTransform ctm = CGContextGetUserSpaceToDeviceSpaceTransform(cgContext);
556   CGRect transformedUserSpacePixel = CGRectApplyAffineTransform(CGRectMake(0, 0, 1, 1), ctm);
557   float maxScale = std::max(fabs(transformedUserSpacePixel.size.width),
558                           fabs(transformedUserSpacePixel.size.height));
559   return maxScale > 1.0 ? 2 : 1;  
563  * Draw the given NSCell into the given cgContext.
565  * destRect - the size and position of the resulting control rectangle
566  * controlSize - the NSControlSize which will be given to the NSCell before
567  *  asking it to render
568  * naturalSize - The natural dimensions of this control.
569  *  If the control rect size is not equal to either of these, a scale
570  *  will be applied to the context so that rendering the control at the
571  *  natural size will result in it filling the destRect space.
572  *  If a control has no natural dimensions in either/both axes, pass 0.0f.
573  * minimumSize - The minimum dimensions of this control.
574  *  If the control rect size is less than the minimum for a given axis,
575  *  a scale will be applied to the context so that the minimum is used
576  *  for drawing.  If a control has no minimum dimensions in either/both
577  *  axes, pass 0.0f.
578  * marginSet - an array of margins; a multidimensional array of [2][3][4],
579  *  with the first dimension being the OS version (Tiger or Leopard),
580  *  the second being the control size (mini, small, regular), and the third
581  *  being the 4 margin values (left, top, right, bottom).
582  * view - The NSView that we're drawing into. As far as I can tell, it doesn't
583  *  matter if this is really the right view; it just has to return YES when
584  *  asked for isFlipped. Otherwise we'll get drawing bugs on 10.4.
585  * mirrorHorizontal - whether to mirror the cell horizontally
586  */
587 static void DrawCellWithScaling(NSCell *cell,
588                                 CGContextRef cgContext,
589                                 const HIRect& destRect,
590                                 NSControlSize controlSize,
591                                 NSSize naturalSize,
592                                 NSSize minimumSize,
593                                 const float marginSet[][3][4],
594                                 NSView* view,
595                                 BOOL mirrorHorizontal)
597   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
599   NSRect drawRect = NSMakeRect(destRect.origin.x, destRect.origin.y, destRect.size.width, destRect.size.height);
601   if (naturalSize.width != 0.0f)
602     drawRect.size.width = naturalSize.width;
603   if (naturalSize.height != 0.0f)
604     drawRect.size.height = naturalSize.height;
606   // Keep aspect ratio when scaling if one dimension is free.
607   if (naturalSize.width == 0.0f && naturalSize.height != 0.0f)
608     drawRect.size.width = destRect.size.width * naturalSize.height / destRect.size.height;
609   if (naturalSize.height == 0.0f && naturalSize.width != 0.0f)
610     drawRect.size.height = destRect.size.height * naturalSize.width / destRect.size.width;
612   // Honor minimum sizes.
613   if (drawRect.size.width < minimumSize.width)
614     drawRect.size.width = minimumSize.width;
615   if (drawRect.size.height < minimumSize.height)
616     drawRect.size.height = minimumSize.height;
618   [NSGraphicsContext saveGraphicsState];
620   // Only skip the buffer if the area of our cell (in pixels^2) is too large.
621   if (drawRect.size.width * drawRect.size.height > BITMAP_MAX_AREA) {
622     // Inflate the rect Gecko gave us by the margin for the control.
623     InflateControlRect(&drawRect, controlSize, marginSet);
625     NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
626     [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:cgContext flipped:YES]];
628     DrawCellIncludingFocusRing(cell, drawRect, view);
630     [NSGraphicsContext setCurrentContext:savedContext];
631   }
632   else {
633     float w = ceil(drawRect.size.width);
634     float h = ceil(drawRect.size.height);
635     NSRect tmpRect = NSMakeRect(MAX_FOCUS_RING_WIDTH, MAX_FOCUS_RING_WIDTH, w, h);
637     // inflate to figure out the frame we need to tell NSCell to draw in, to get something that's 0,0,w,h
638     InflateControlRect(&tmpRect, controlSize, marginSet);
640     // and then, expand by MAX_FOCUS_RING_WIDTH size to make sure we can capture any focus ring
641     w += MAX_FOCUS_RING_WIDTH * 2.0;
642     h += MAX_FOCUS_RING_WIDTH * 2.0;
644     int backingScaleFactor = GetBackingScaleFactorForRendering(cgContext);
645     CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
646     CGContextRef ctx = CGBitmapContextCreate(NULL,
647                                              (int) w * backingScaleFactor, (int) h * backingScaleFactor,
648                                              8, (int) w * backingScaleFactor * 4,
649                                              rgb, kCGImageAlphaPremultipliedFirst);
650     CGColorSpaceRelease(rgb);
652     // We need to flip the image twice in order to avoid drawing bugs on 10.4, see bug 465069.
653     // This is the first flip transform, applied to cgContext.
654     CGContextScaleCTM(cgContext, 1.0f, -1.0f);
655     CGContextTranslateCTM(cgContext, 0.0f, -(2.0 * destRect.origin.y + destRect.size.height));
656     if (mirrorHorizontal) {
657       CGContextScaleCTM(cgContext, -1.0f, 1.0f);
658       CGContextTranslateCTM(cgContext, -(2.0 * destRect.origin.x + destRect.size.width), 0.0f);
659     }
661     NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
662     [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:ctx flipped:YES]];
664     CGContextScaleCTM(ctx, backingScaleFactor, backingScaleFactor);
666     // This is the second flip transform, applied to ctx.
667     CGContextScaleCTM(ctx, 1.0f, -1.0f);
668     CGContextTranslateCTM(ctx, 0.0f, -(2.0 * tmpRect.origin.y + tmpRect.size.height));
670     DrawCellIncludingFocusRing(cell, tmpRect, view);
672     [NSGraphicsContext setCurrentContext:savedContext];
674     CGImageRef img = CGBitmapContextCreateImage(ctx);
676     // Drop the image into the original destination rectangle, scaling to fit
677     // Only scale MAX_FOCUS_RING_WIDTH by xscale/yscale when the resulting rect
678     // doesn't extend beyond the overflow rect
679     float xscale = destRect.size.width / drawRect.size.width;
680     float yscale = destRect.size.height / drawRect.size.height;
681     float scaledFocusRingX = xscale < 1.0f ? MAX_FOCUS_RING_WIDTH * xscale : MAX_FOCUS_RING_WIDTH;
682     float scaledFocusRingY = yscale < 1.0f ? MAX_FOCUS_RING_WIDTH * yscale : MAX_FOCUS_RING_WIDTH;
683     CGContextDrawImage(cgContext, CGRectMake(destRect.origin.x - scaledFocusRingX,
684                                              destRect.origin.y - scaledFocusRingY,
685                                              destRect.size.width + scaledFocusRingX * 2,
686                                              destRect.size.height + scaledFocusRingY * 2),
687                        img);
689     CGImageRelease(img);
690     CGContextRelease(ctx);
691   }
693   [NSGraphicsContext restoreGraphicsState];
695 #if DRAW_IN_FRAME_DEBUG
696   CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
697   CGContextFillRect(cgContext, destRect);
698 #endif
700   NS_OBJC_END_TRY_ABORT_BLOCK;
703 struct CellRenderSettings {
704   // The natural dimensions of the control.
705   // If a control has no natural dimensions in either/both axes, set to 0.0f.
706   NSSize naturalSizes[3];
708   // The minimum dimensions of the control.
709   // If a control has no minimum dimensions in either/both axes, set to 0.0f.
710   NSSize minimumSizes[3];
712   // A three-dimensional array,
713   // with the first dimension being the OS version (only Leopard for the moment),
714   // the second being the control size (mini, small, regular), and the third
715   // being the 4 margin values (left, top, right, bottom).
716   float margins[1][3][4];
720  * This is a helper method that returns the required NSControlSize given a size
721  * and the size of the three controls plus a tolerance.
722  * size - The width or the height of the element to draw.
723  * sizes - An array with the all the width/height of the element for its
724  *         different sizes.
725  * tolerance - The tolerance as passed to DrawCellWithSnapping.
726  * NOTE: returns NSRegularControlSize if all values in 'sizes' are zero.
727  */
728 static NSControlSize FindControlSize(CGFloat size, const CGFloat* sizes, CGFloat tolerance)
730   for (uint32_t i = miniControlSize; i <= regularControlSize; ++i) {
731     if (sizes[i] == 0) {
732       continue;
733     }
735     CGFloat next = 0;
736     // Find next value.
737     for (uint32_t j = i+1; j <= regularControlSize; ++j) {
738       if (sizes[j] != 0) {
739         next = sizes[j];
740         break;
741       }
742     }
744     // If it's the latest value, we pick it.
745     if (next == 0) {
746       return CocoaSizeForEnum(i);
747     }
749     if (size <= sizes[i] + tolerance && size < next) {
750       return CocoaSizeForEnum(i);
751     }
752   }
754   // If we are here, that means sizes[] was an array with only empty values
755   // or the algorithm above is wrong.
756   // The former can happen but the later would be wrong.
757   NS_ASSERTION(sizes[0] == 0 && sizes[1] == 0 && sizes[2] == 0,
758                "We found no control! We shouldn't be there!");
759   return CocoaSizeForEnum(regularControlSize);
763  * Draw the given NSCell into the given cgContext with a nice control size.
765  * This function is similar to DrawCellWithScaling, but it decides what
766  * control size to use based on the destRect's size.
767  * Scaling is only applied when the difference between the destRect's size
768  * and the next smaller natural size is greater than snapTolerance. Otherwise
769  * it snaps to the next smaller control size without scaling because unscaled
770  * controls look nicer.
771  */
772 static void DrawCellWithSnapping(NSCell *cell,
773                                  CGContextRef cgContext,
774                                  const HIRect& destRect,
775                                  const CellRenderSettings settings,
776                                  float verticalAlignFactor,
777                                  NSView* view,
778                                  BOOL mirrorHorizontal,
779                                  float snapTolerance = 2.0f)
781   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
783   const float rectWidth = destRect.size.width, rectHeight = destRect.size.height;
784   const NSSize *sizes = settings.naturalSizes;
785   const NSSize miniSize = sizes[EnumSizeForCocoaSize(NSMiniControlSize)];
786   const NSSize smallSize = sizes[EnumSizeForCocoaSize(NSSmallControlSize)];
787   const NSSize regularSize = sizes[EnumSizeForCocoaSize(NSRegularControlSize)];
789   HIRect drawRect = destRect;
791   CGFloat controlWidths[3] = { miniSize.width, smallSize.width, regularSize.width };
792   NSControlSize controlSizeX = FindControlSize(rectWidth, controlWidths, snapTolerance);
793   CGFloat controlHeights[3] = { miniSize.height, smallSize.height, regularSize.height };
794   NSControlSize controlSizeY = FindControlSize(rectHeight, controlHeights, snapTolerance);
796   NSControlSize controlSize = NSRegularControlSize;
797   int sizeIndex = 0;
799   // At some sizes, don't scale but snap.
800   const NSControlSize smallerControlSize =
801     EnumSizeForCocoaSize(controlSizeX) < EnumSizeForCocoaSize(controlSizeY) ?
802     controlSizeX : controlSizeY;
803   const int smallerControlSizeIndex = EnumSizeForCocoaSize(smallerControlSize);
804   const NSSize size = sizes[smallerControlSizeIndex];
805   float diffWidth = size.width ? rectWidth - size.width : 0.0f;
806   float diffHeight = size.height ? rectHeight - size.height : 0.0f;
807   if (diffWidth >= 0.0f && diffHeight >= 0.0f &&
808       diffWidth <= snapTolerance && diffHeight <= snapTolerance) {
809     // Snap to the smaller control size.
810     controlSize = smallerControlSize;
811     sizeIndex = smallerControlSizeIndex;
812     // Resize and center the drawRect.
813     if (sizes[sizeIndex].width) {
814       drawRect.origin.x += ceil((destRect.size.width - sizes[sizeIndex].width) / 2);
815       drawRect.size.width = sizes[sizeIndex].width;
816     }
817     if (sizes[sizeIndex].height) {
818       drawRect.origin.y += floor((destRect.size.height - sizes[sizeIndex].height) * verticalAlignFactor);
819       drawRect.size.height = sizes[sizeIndex].height;
820     }
821   } else {
822     // Use the larger control size.
823     controlSize = EnumSizeForCocoaSize(controlSizeX) > EnumSizeForCocoaSize(controlSizeY) ?
824                   controlSizeX : controlSizeY;
825     sizeIndex = EnumSizeForCocoaSize(controlSize);
826    }
828   [cell setControlSize:controlSize];
830   NSSize minimumSize = settings.minimumSizes ? settings.minimumSizes[sizeIndex] : NSZeroSize;
831   DrawCellWithScaling(cell, cgContext, drawRect, controlSize, sizes[sizeIndex],
832                       minimumSize, settings.margins, view, mirrorHorizontal);
834   NS_OBJC_END_TRY_ABORT_BLOCK;
837 @interface NSWindow(CoreUIRendererPrivate)
838 + (CUIRendererRef)coreUIRenderer;
839 @end
841 static void
842 RenderWithCoreUILegacy(CGRect aRect, CGContextRef cgContext, NSDictionary* aOptions)
844   if (aRect.size.width * aRect.size.height <= BITMAP_MAX_AREA) {
845     CUIRendererRef renderer = [NSWindow respondsToSelector:@selector(coreUIRenderer)]
846       ? [NSWindow coreUIRenderer] : nil;
847     CUIDraw(renderer, aRect, cgContext, (CFDictionaryRef)aOptions, NULL);
848   }
851 static id
852 GetAquaAppearance()
854   // We only need NSAppearance on 10.10 and up.
855   if (nsCocoaFeatures::OnYosemiteOrLater()) {
856     Class NSAppearanceClass = NSClassFromString(@"NSAppearance");
857     if (NSAppearanceClass &&
858         [NSAppearanceClass respondsToSelector:@selector(appearanceNamed:)]) {
859       return [NSAppearanceClass performSelector:@selector(appearanceNamed:)
860                                      withObject:@"NSAppearanceNameAqua"];
861     }
862   }
863   return nil;
866 @interface NSObject(NSAppearanceCoreUIRendering)
867 - (void)_drawInRect:(CGRect)rect context:(CGContextRef)cgContext options:(id)options;
868 @end
870 static void
871 RenderWithCoreUI(CGRect aRect, CGContextRef cgContext, NSDictionary* aOptions)
873   id appearance = GetAquaAppearance();
875   if (aRect.size.width * aRect.size.height > BITMAP_MAX_AREA) {
876     return;
877   }
879   if (appearance && [appearance respondsToSelector:@selector(_drawInRect:context:options:)]) {
880     // Render through NSAppearance on Mac OS 10.10 and up. This will call
881     // CUIDraw with a CoreUI renderer that will give us the correct 10.10
882     // style. Calling CUIDraw directly with [NSWindow coreUIRenderer] still
883     // renders 10.9-style widgets on 10.10.
884     [appearance _drawInRect:aRect context:cgContext options:aOptions];
885   } else {
886     // 10.9 and below
887     RenderWithCoreUILegacy(aRect, cgContext, aOptions);
888   }
891 static float VerticalAlignFactor(nsIFrame *aFrame)
893   if (!aFrame)
894     return 0.5f; // default: center
896   const nsStyleCoord& va = aFrame->StyleTextReset()->mVerticalAlign;
897   uint8_t intval = (va.GetUnit() == eStyleUnit_Enumerated)
898                      ? va.GetIntValue()
899                      : NS_STYLE_VERTICAL_ALIGN_MIDDLE;
900   switch (intval) {
901     case NS_STYLE_VERTICAL_ALIGN_TOP:
902     case NS_STYLE_VERTICAL_ALIGN_TEXT_TOP:
903       return 0.0f;
905     case NS_STYLE_VERTICAL_ALIGN_SUB:
906     case NS_STYLE_VERTICAL_ALIGN_SUPER:
907     case NS_STYLE_VERTICAL_ALIGN_MIDDLE:
908     case NS_STYLE_VERTICAL_ALIGN_MIDDLE_WITH_BASELINE:
909       return 0.5f;
911     case NS_STYLE_VERTICAL_ALIGN_BASELINE:
912     case NS_STYLE_VERTICAL_ALIGN_TEXT_BOTTOM:
913     case NS_STYLE_VERTICAL_ALIGN_BOTTOM:
914       return 1.0f;
916     default:
917       NS_NOTREACHED("invalid vertical-align");
918       return 0.5f;
919   }
922 // These are the sizes that Gecko needs to request to draw if it wants
923 // to get a standard-sized Aqua radio button drawn. Note that the rects
924 // that draw these are actually a little bigger.
925 static const CellRenderSettings radioSettings = {
926   {
927     NSMakeSize(11, 11), // mini
928     NSMakeSize(13, 13), // small
929     NSMakeSize(16, 16)  // regular
930   },
931   {
932     NSZeroSize, NSZeroSize, NSZeroSize
933   },
934   {
935     { // Leopard
936       {0, 0, 0, 0},     // mini
937       {0, 1, 1, 1},     // small
938       {0, 0, 0, 0}      // regular
939     }
940   }
943 static const CellRenderSettings checkboxSettings = {
944   {
945     NSMakeSize(11, 11), // mini
946     NSMakeSize(13, 13), // small
947     NSMakeSize(16, 16)  // regular
948   },
949   {
950     NSZeroSize, NSZeroSize, NSZeroSize
951   },
952   {
953     { // Leopard
954       {0, 1, 0, 0},     // mini
955       {0, 1, 0, 1},     // small
956       {0, 1, 0, 1}      // regular
957     }
958   }
961 void
962 nsNativeThemeCocoa::DrawCheckboxOrRadio(CGContextRef cgContext, bool inCheckbox,
963                                         const HIRect& inBoxRect, bool inSelected,
964                                         EventStates inState, nsIFrame* aFrame)
966   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
968   NSButtonCell *cell = inCheckbox ? mCheckboxCell : mRadioButtonCell;
969   NSCellStateValue state = inSelected ? NSOnState : NSOffState;
971   // Check if we have an indeterminate checkbox
972   if (inCheckbox && GetIndeterminate(aFrame))
973     state = NSMixedState;
975   [cell setEnabled:!IsDisabled(aFrame, inState)];
976   [cell setShowsFirstResponder:inState.HasState(NS_EVENT_STATE_FOCUS)];
977   [cell setState:state];
978   [cell setHighlighted:inState.HasAllStates(NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_HOVER)];
979   [cell setControlTint:(FrameIsInActiveWindow(aFrame) ? [NSColor currentControlTint] : NSClearControlTint)];
981   // Ensure that the control is square.
982   float length = std::min(inBoxRect.size.width, inBoxRect.size.height);
983   HIRect drawRect = CGRectMake(inBoxRect.origin.x + (int)((inBoxRect.size.width - length) / 2.0f),
984                                inBoxRect.origin.y + (int)((inBoxRect.size.height - length) / 2.0f),
985                                length, length);
987   DrawCellWithSnapping(cell, cgContext, drawRect,
988                        inCheckbox ? checkboxSettings : radioSettings,
989                        VerticalAlignFactor(aFrame), mCellDrawView, NO);
991   NS_OBJC_END_TRY_ABORT_BLOCK;
994 static const CellRenderSettings searchFieldSettings = {
995   {
996     NSMakeSize(0, 16), // mini
997     NSMakeSize(0, 19), // small
998     NSMakeSize(0, 22)  // regular
999   },
1000   {
1001     NSMakeSize(32, 0), // mini
1002     NSMakeSize(38, 0), // small
1003     NSMakeSize(44, 0)  // regular
1004   },
1005   {
1006     { // Leopard
1007       {0, 0, 0, 0},     // mini
1008       {0, 0, 0, 0},     // small
1009       {0, 0, 0, 0}      // regular
1010     }
1011   }
1014 void
1015 nsNativeThemeCocoa::DrawSearchField(CGContextRef cgContext, const HIRect& inBoxRect,
1016                                     nsIFrame* aFrame, EventStates inState)
1018   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1020   ContextAwareSearchFieldCell* cell = mSearchFieldCell;
1021   [cell setContext:aFrame];
1022   [cell setEnabled:!IsDisabled(aFrame, inState)];
1023   // NOTE: this could probably use inState
1024   [cell setShowsFirstResponder:IsFocused(aFrame)];
1026   DrawCellWithSnapping(cell, cgContext, inBoxRect, searchFieldSettings,
1027                        VerticalAlignFactor(aFrame), mCellDrawView,
1028                        IsFrameRTL(aFrame));
1030   [cell setContext:nullptr];
1032   NS_OBJC_END_TRY_ABORT_BLOCK;
1035 static const NSSize kCheckmarkSize = NSMakeSize(11, 11);
1036 static const NSString* kCheckmarkImage = @"image.MenuOnState";
1037 static const CGFloat kMenuIconIndent = 6.0f;
1039 void
1040 nsNativeThemeCocoa::DrawMenuIcon(CGContextRef cgContext, const CGRect& aRect,
1041                                  EventStates inState, nsIFrame* aFrame,
1042                                  const NSSize& aIconSize, const NSString* aImageName)
1044   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1046   // Adjust size and position of our drawRect.
1047   CGFloat paddingX = std::max(CGFloat(0.0), aRect.size.width - aIconSize.width);
1048   CGFloat paddingY = std::max(CGFloat(0.0), aRect.size.height - aIconSize.height);
1049   CGFloat paddingStartX = std::min(paddingX, kMenuIconIndent);
1050   CGFloat paddingEndX = std::max(CGFloat(0.0), paddingX - kMenuIconIndent);
1051   CGRect drawRect = CGRectMake(
1052     aRect.origin.x + (IsFrameRTL(aFrame) ? paddingEndX : paddingStartX),
1053     aRect.origin.y + ceil(paddingY / 2),
1054     aIconSize.width, aIconSize.height);
1056   BOOL isDisabled = IsDisabled(aFrame, inState);
1057   BOOL isActive = CheckBooleanAttr(aFrame, nsGkAtoms::menuactive);
1059   // On 10.6 and at least on 10.7.0, Apple doesn’t seem to have implemented all
1060   // keys and values used on 10.7.5 and later. We can however draw menu icons
1061   // on earlier OS versions by using different keys/values.
1062   BOOL otherKeysAndValues = !nsCocoaFeatures::OnLionOrLater() ||
1063                             (nsCocoaFeatures::OSXVersionMajor() == 10 &&
1064                              nsCocoaFeatures::OSXVersionMinor() == 7 &&
1065                              nsCocoaFeatures::OSXVersionBugFix() < 5);
1067   // 2 states combined with 2 different backgroundTypeKeys on earlier versions.
1068   NSString* state = isDisabled ? @"disabled" :
1069     (isActive && !otherKeysAndValues) ? @"pressed" : @"normal";
1070   NSString* backgroundTypeKey = !otherKeysAndValues ? @"kCUIBackgroundTypeMenu" :
1071     !isDisabled && isActive ? @"backgroundTypeDark" : @"backgroundTypeLight";
1073   NSMutableArray* keys = [NSMutableArray arrayWithObjects:@"backgroundTypeKey",
1074     @"imageNameKey", @"state", @"widget", @"is.flipped", nil];
1075   NSMutableArray* values = [NSMutableArray arrayWithObjects: backgroundTypeKey,
1076     aImageName, state, @"image", [NSNumber numberWithBool:YES], nil];
1078   if (otherKeysAndValues) { // Earlier versions used one more key-value pair.
1079     [keys insertObject:@"imageIsGrayscaleKey" atIndex:1];
1080     [values insertObject:[NSNumber numberWithBool:YES] atIndex:1];
1081   }
1083   RenderWithCoreUI(drawRect, cgContext,
1084                   [NSDictionary dictionaryWithObjects:values
1085                                               forKeys:keys]);
1087 #if DRAW_IN_FRAME_DEBUG
1088   CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
1089   CGContextFillRect(cgContext, drawRect);
1090 #endif
1092   NS_OBJC_END_TRY_ABORT_BLOCK;
1095 static const NSSize kHelpButtonSize = NSMakeSize(20, 20);
1097 static const CellRenderSettings pushButtonSettings = {
1098   {
1099     NSMakeSize(0, 16), // mini
1100     NSMakeSize(0, 19), // small
1101     NSMakeSize(0, 22)  // regular
1102   },
1103   {
1104     NSMakeSize(18, 0), // mini
1105     NSMakeSize(26, 0), // small
1106     NSMakeSize(30, 0)  // regular
1107   },
1108   {
1109     { // Leopard
1110       {0, 0, 0, 0},    // mini
1111       {4, 0, 4, 1},    // small
1112       {5, 0, 5, 2}     // regular
1113     }
1114   }
1117 // The height at which we start doing square buttons instead of rounded buttons
1118 // Rounded buttons look bad if drawn at a height greater than 26, so at that point
1119 // we switch over to doing square buttons which looks fine at any size.
1120 #define DO_SQUARE_BUTTON_HEIGHT 26
1122 void
1123 nsNativeThemeCocoa::DrawPushButton(CGContextRef cgContext, const HIRect& inBoxRect,
1124                                    EventStates inState, uint8_t aWidgetType,
1125                                    nsIFrame* aFrame)
1127   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1129   BOOL isActive = FrameIsInActiveWindow(aFrame);
1130   BOOL isDisabled = IsDisabled(aFrame, inState);
1132   NSButtonCell* cell = (aWidgetType == NS_THEME_MOZ_MAC_HELP_BUTTON) ? mHelpButtonCell : mPushButtonCell;
1133   [cell setEnabled:!isDisabled];
1134   [cell setHighlighted:isActive &&
1135                        inState.HasAllStates(NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_HOVER)];
1136   [cell setShowsFirstResponder:inState.HasState(NS_EVENT_STATE_FOCUS) && !isDisabled && isActive];
1138   if (aWidgetType == NS_THEME_MOZ_MAC_HELP_BUTTON) {
1139     DrawCellWithScaling(cell, cgContext, inBoxRect, NSRegularControlSize,
1140                         NSZeroSize, kHelpButtonSize, NULL, mCellDrawView,
1141                         false); // Don't mirror icon in RTL.
1142   } else {
1143     // If the button is tall enough, draw the square button style so that
1144     // buttons with non-standard content look good. Otherwise draw normal
1145     // rounded aqua buttons.
1146     if (inBoxRect.size.height > DO_SQUARE_BUTTON_HEIGHT) {
1147       [cell setBezelStyle:NSShadowlessSquareBezelStyle];
1148       DrawCellWithScaling(cell, cgContext, inBoxRect, NSRegularControlSize,
1149                           NSZeroSize, NSMakeSize(14, 0), NULL, mCellDrawView,
1150                           IsFrameRTL(aFrame));
1151     } else {
1152       [cell setBezelStyle:NSRoundedBezelStyle];
1153       DrawCellWithSnapping(cell, cgContext, inBoxRect, pushButtonSettings, 0.5f,
1154                            mCellDrawView, IsFrameRTL(aFrame), 1.0f);
1155     }
1156   }
1158 #if DRAW_IN_FRAME_DEBUG
1159   CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
1160   CGContextFillRect(cgContext, inBoxRect);
1161 #endif
1163   NS_OBJC_END_TRY_ABORT_BLOCK;
1166 void
1167 nsNativeThemeCocoa::DrawFocusOutline(CGContextRef cgContext, const HIRect& inBoxRect,
1168                                      EventStates inState, uint8_t aWidgetType,
1169                                      nsIFrame* aFrame)
1171   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1173   HIThemeFrameDrawInfo fdi;
1174   fdi.version = 0;
1175   fdi.kind = kHIThemeFrameTextFieldSquare;
1176   fdi.state = kThemeStateActive;
1177   fdi.isFocused = TRUE;
1179 #if DRAW_IN_FRAME_DEBUG
1180   CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
1181   CGContextFillRect(cgContext, inBoxRect);
1182 #endif
1184   HIThemeDrawFrame(&inBoxRect, &fdi, cgContext, HITHEME_ORIENTATION);
1186   NS_OBJC_END_TRY_ABORT_BLOCK;
1189 typedef void (*RenderHIThemeControlFunction)(CGContextRef cgContext, const HIRect& aRenderRect, void* aData);
1191 static void
1192 RenderTransformedHIThemeControl(CGContextRef aCGContext, const HIRect& aRect,
1193                                 RenderHIThemeControlFunction aFunc, void* aData,
1194                                 BOOL mirrorHorizontally = NO)
1196   CGAffineTransform savedCTM = CGContextGetCTM(aCGContext);
1197   CGContextTranslateCTM(aCGContext, aRect.origin.x, aRect.origin.y);
1199   bool drawDirect;
1200   HIRect drawRect = aRect;
1201   drawRect.origin = CGPointZero;
1203   if (!mirrorHorizontally && savedCTM.a == 1.0f && savedCTM.b == 0.0f &&
1204       savedCTM.c == 0.0f && (savedCTM.d == 1.0f || savedCTM.d == -1.0f)) {
1205     drawDirect = TRUE;
1206   } else {
1207     drawDirect = FALSE;
1208   }
1210   // Fall back to no bitmap buffer if the area of our control (in pixels^2)
1211   // is too large.
1212   if (drawDirect || (aRect.size.width * aRect.size.height > BITMAP_MAX_AREA)) {
1213     aFunc(aCGContext, drawRect, aData);
1214   } else {
1215     // Inflate the buffer to capture focus rings.
1216     int w = ceil(drawRect.size.width) + 2 * MAX_FOCUS_RING_WIDTH;
1217     int h = ceil(drawRect.size.height) + 2 * MAX_FOCUS_RING_WIDTH;
1219     int backingScaleFactor = GetBackingScaleFactorForRendering(aCGContext);
1220     CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
1221     CGContextRef bitmapctx = CGBitmapContextCreate(NULL,
1222                                                    w * backingScaleFactor,
1223                                                    h * backingScaleFactor,
1224                                                    8,
1225                                                    w * backingScaleFactor * 4,
1226                                                    colorSpace,
1227                                                    kCGImageAlphaPremultipliedFirst);
1228     CGColorSpaceRelease(colorSpace);
1230     CGContextScaleCTM(bitmapctx, backingScaleFactor, backingScaleFactor);
1231     CGContextTranslateCTM(bitmapctx, MAX_FOCUS_RING_WIDTH, MAX_FOCUS_RING_WIDTH);
1233     // HITheme always wants to draw into a flipped context, or things
1234     // get confused.
1235     CGContextTranslateCTM(bitmapctx, 0.0f, aRect.size.height);
1236     CGContextScaleCTM(bitmapctx, 1.0f, -1.0f);
1238     aFunc(bitmapctx, drawRect, aData);
1240     CGImageRef bitmap = CGBitmapContextCreateImage(bitmapctx);
1242     CGAffineTransform ctm = CGContextGetCTM(aCGContext);
1244     // We need to unflip, so that we can do a DrawImage without getting a flipped image.
1245     CGContextTranslateCTM(aCGContext, 0.0f, aRect.size.height);
1246     CGContextScaleCTM(aCGContext, 1.0f, -1.0f);
1248     if (mirrorHorizontally) {
1249       CGContextTranslateCTM(aCGContext, aRect.size.width, 0);
1250       CGContextScaleCTM(aCGContext, -1.0f, 1.0f);
1251     }
1253     HIRect inflatedDrawRect = CGRectMake(-MAX_FOCUS_RING_WIDTH, -MAX_FOCUS_RING_WIDTH, w, h);
1254     CGContextDrawImage(aCGContext, inflatedDrawRect, bitmap);
1256     CGContextSetCTM(aCGContext, ctm);
1258     CGImageRelease(bitmap);
1259     CGContextRelease(bitmapctx);
1260   }
1262   CGContextSetCTM(aCGContext, savedCTM);
1265 static void
1266 RenderButton(CGContextRef cgContext, const HIRect& aRenderRect, void* aData)
1268   HIThemeButtonDrawInfo* bdi = (HIThemeButtonDrawInfo*)aData;
1269   HIThemeDrawButton(&aRenderRect, bdi, cgContext, kHIThemeOrientationNormal, NULL);
1272 void
1273 nsNativeThemeCocoa::DrawButton(CGContextRef cgContext, ThemeButtonKind inKind,
1274                                const HIRect& inBoxRect, bool inIsDefault,
1275                                ThemeButtonValue inValue, ThemeButtonAdornment inAdornment,
1276                                EventStates inState, nsIFrame* aFrame)
1278   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1280   BOOL isActive = FrameIsInActiveWindow(aFrame);
1281   BOOL isDisabled = IsDisabled(aFrame, inState);
1283   HIThemeButtonDrawInfo bdi;
1284   bdi.version = 0;
1285   bdi.kind = inKind;
1286   bdi.value = inValue;
1287   bdi.adornment = inAdornment;
1289   if (isDisabled) {
1290     bdi.state = kThemeStateUnavailable;
1291   }
1292   else if (inState.HasAllStates(NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_HOVER)) {
1293     bdi.state = kThemeStatePressed;
1294   }
1295   else {
1296     if (inKind == kThemeArrowButton)
1297       bdi.state = kThemeStateUnavailable; // these are always drawn as unavailable
1298     else if (!isActive && inKind == kThemeListHeaderButton)
1299       bdi.state = kThemeStateInactive;
1300     else
1301       bdi.state = kThemeStateActive;
1302   }
1304   if (inState.HasState(NS_EVENT_STATE_FOCUS) && isActive)
1305     bdi.adornment |= kThemeAdornmentFocus;
1307   if (inIsDefault && !isDisabled && isActive &&
1308       !inState.HasState(NS_EVENT_STATE_ACTIVE)) {
1309     bdi.adornment |= kThemeAdornmentDefault;
1310     bdi.animation.time.start = 0;
1311     bdi.animation.time.current = CFAbsoluteTimeGetCurrent();
1312   }
1314   HIRect drawFrame = inBoxRect;
1316   if (inKind == kThemePushButton) {
1317     drawFrame.size.height -= 2;
1318     if (inBoxRect.size.height < pushButtonSettings.naturalSizes[smallControlSize].height) {
1319       bdi.kind = kThemePushButtonMini;
1320     }
1321     else if (inBoxRect.size.height < pushButtonSettings.naturalSizes[regularControlSize].height) {
1322       bdi.kind = kThemePushButtonSmall;
1323       drawFrame.origin.y -= 1;
1324       drawFrame.origin.x += 1;
1325       drawFrame.size.width -= 2;
1326     }
1327   }
1328   else if (inKind == kThemeListHeaderButton) {
1329     CGContextClipToRect(cgContext, inBoxRect);
1330     // Always remove the top border.
1331     drawFrame.origin.y -= 1;
1332     drawFrame.size.height += 1;
1333     // Remove the left border in LTR mode and the right border in RTL mode.
1334     drawFrame.size.width += 1;
1335     bool isLast = IsLastTreeHeaderCell(aFrame);
1336     if (isLast)
1337       drawFrame.size.width += 1; // Also remove the other border.
1338     if (!IsFrameRTL(aFrame) || isLast)
1339       drawFrame.origin.x -= 1;
1340   }
1342   RenderTransformedHIThemeControl(cgContext, drawFrame, RenderButton, &bdi,
1343                                   IsFrameRTL(aFrame));
1345 #if DRAW_IN_FRAME_DEBUG
1346   CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
1347   CGContextFillRect(cgContext, inBoxRect);
1348 #endif
1350   NS_OBJC_END_TRY_ABORT_BLOCK;
1353 static const CellRenderSettings dropdownSettings = {
1354   {
1355     NSMakeSize(0, 16), // mini
1356     NSMakeSize(0, 19), // small
1357     NSMakeSize(0, 22)  // regular
1358   },
1359   {
1360     NSMakeSize(18, 0), // mini
1361     NSMakeSize(38, 0), // small
1362     NSMakeSize(44, 0)  // regular
1363   },
1364   {
1365     { // Leopard
1366       {1, 1, 2, 1},    // mini
1367       {3, 0, 3, 1},    // small
1368       {3, 0, 3, 0}     // regular
1369     }
1370   }
1373 static const CellRenderSettings editableMenulistSettings = {
1374   {
1375     NSMakeSize(0, 15), // mini
1376     NSMakeSize(0, 18), // small
1377     NSMakeSize(0, 21)  // regular
1378   },
1379   {
1380     NSMakeSize(18, 0), // mini
1381     NSMakeSize(38, 0), // small
1382     NSMakeSize(44, 0)  // regular
1383   },
1384   {
1385     { // Leopard
1386       {0, 0, 2, 2},    // mini
1387       {0, 0, 3, 2},    // small
1388       {0, 1, 3, 3}     // regular
1389     }
1390   }
1393 void
1394 nsNativeThemeCocoa::DrawDropdown(CGContextRef cgContext, const HIRect& inBoxRect,
1395                                  EventStates inState, uint8_t aWidgetType,
1396                                  nsIFrame* aFrame)
1398   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1400   [mDropdownCell setPullsDown:(aWidgetType == NS_THEME_BUTTON)];
1402   BOOL isEditable = (aWidgetType == NS_THEME_DROPDOWN_TEXTFIELD);
1403   NSCell* cell = isEditable ? (NSCell*)mComboBoxCell : (NSCell*)mDropdownCell;
1405   [cell setEnabled:!IsDisabled(aFrame, inState)];
1406   [cell setShowsFirstResponder:(IsFocused(aFrame) || inState.HasState(NS_EVENT_STATE_FOCUS))];
1407   [cell setHighlighted:IsOpenButton(aFrame)];
1408   [cell setControlTint:(FrameIsInActiveWindow(aFrame) ? [NSColor currentControlTint] : NSClearControlTint)];
1410   const CellRenderSettings& settings = isEditable ? editableMenulistSettings : dropdownSettings;
1411   DrawCellWithSnapping(cell, cgContext, inBoxRect, settings,
1412                        0.5f, mCellDrawView, IsFrameRTL(aFrame));
1414   NS_OBJC_END_TRY_ABORT_BLOCK;
1417 static const CellRenderSettings spinnerSettings = {
1418   {
1419     NSMakeSize(11, 16), // mini (width trimmed by 2px to reduce blank border)
1420     NSMakeSize(15, 22), // small
1421     NSMakeSize(19, 27)  // regular
1422   },
1423   {
1424     NSMakeSize(11, 16), // mini (width trimmed by 2px to reduce blank border)
1425     NSMakeSize(15, 22), // small
1426     NSMakeSize(19, 27)  // regular
1427   },
1428   {
1429     { // Leopard
1430       {0, 0, 0, 0},    // mini
1431       {0, 0, 0, 0},    // small
1432       {0, 0, 0, 0}     // regular
1433     }
1434   }
1437 void
1438 nsNativeThemeCocoa::DrawSpinButtons(CGContextRef cgContext, ThemeButtonKind inKind,
1439                                     const HIRect& inBoxRect, ThemeDrawState inDrawState,
1440                                     ThemeButtonAdornment inAdornment,
1441                                     EventStates inState, nsIFrame* aFrame)
1443   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1445   HIThemeButtonDrawInfo bdi;
1446   bdi.version = 0;
1447   bdi.kind = inKind;
1448   bdi.value = kThemeButtonOff;
1449   bdi.adornment = inAdornment;
1451   if (IsDisabled(aFrame, inState))
1452     bdi.state = kThemeStateUnavailable;
1453   else
1454     bdi.state = FrameIsInActiveWindow(aFrame) ? inDrawState : kThemeStateActive;
1456   HIThemeDrawButton(&inBoxRect, &bdi, cgContext, HITHEME_ORIENTATION, NULL);
1458   NS_OBJC_END_TRY_ABORT_BLOCK;
1461 void
1462 nsNativeThemeCocoa::DrawSpinButton(CGContextRef cgContext,
1463                                    ThemeButtonKind inKind,
1464                                    const HIRect& inBoxRect,
1465                                    ThemeDrawState inDrawState,
1466                                    ThemeButtonAdornment inAdornment,
1467                                    EventStates inState,
1468                                    nsIFrame* aFrame,
1469                                    uint8_t aWidgetType)
1471   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1473   MOZ_ASSERT(aWidgetType == NS_THEME_SPINNER_UP_BUTTON ||
1474              aWidgetType == NS_THEME_SPINNER_DOWN_BUTTON);
1476   HIThemeButtonDrawInfo bdi;
1477   bdi.version = 0;
1478   bdi.kind = inKind;
1479   bdi.value = kThemeButtonOff;
1480   bdi.adornment = inAdornment;
1482   if (IsDisabled(aFrame, inState))
1483     bdi.state = kThemeStateUnavailable;
1484   else
1485     bdi.state = FrameIsInActiveWindow(aFrame) ? inDrawState : kThemeStateActive;
1487   // Cocoa only allows kThemeIncDecButton to paint the up and down spin buttons
1488   // together as a single unit (presumably because when one button is active,
1489   // the appearance of both changes (in different ways)). Here we have to paint
1490   // both buttons, using clip to hide the one we don't want to paint.
1491   HIRect drawRect = inBoxRect;
1492   drawRect.size.height *= 2;
1493   if (aWidgetType == NS_THEME_SPINNER_DOWN_BUTTON) {
1494     drawRect.origin.y -= inBoxRect.size.height;
1495   }
1497   // Shift the drawing a little to the left, since cocoa paints with more
1498   // blank space around the visual buttons than we'd like:
1499   drawRect.origin.x -= 1;
1501   CGContextSaveGState(cgContext);
1502   CGContextClipToRect(cgContext, inBoxRect);
1504   HIThemeDrawButton(&drawRect, &bdi, cgContext, HITHEME_ORIENTATION, NULL);
1506   CGContextRestoreGState(cgContext);
1508   NS_OBJC_END_TRY_ABORT_BLOCK;
1511 void
1512 nsNativeThemeCocoa::DrawFrame(CGContextRef cgContext, HIThemeFrameKind inKind,
1513                               const HIRect& inBoxRect, bool inDisabled,
1514                               EventStates inState)
1516   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1518   HIThemeFrameDrawInfo fdi;
1519   fdi.version = 0;
1520   fdi.kind = inKind;
1522   // We don't ever set an inactive state for this because it doesn't
1523   // look right (see other apps).
1524   fdi.state = inDisabled ? kThemeStateUnavailable : kThemeStateActive;
1526   // for some reason focus rings on listboxes draw incorrectly
1527   if (inKind == kHIThemeFrameListBox)
1528     fdi.isFocused = 0;
1529   else
1530     fdi.isFocused = inState.HasState(NS_EVENT_STATE_FOCUS);
1532   // HIThemeDrawFrame takes the rect for the content area of the frame, not
1533   // the bounding rect for the frame. Here we reduce the size of the rect we
1534   // will pass to make it the size of the content.
1535   HIRect drawRect = inBoxRect;
1536   if (inKind == kHIThemeFrameTextFieldSquare) {
1537     SInt32 frameOutset = 0;
1538     ::GetThemeMetric(kThemeMetricEditTextFrameOutset, &frameOutset);
1539     drawRect.origin.x += frameOutset;
1540     drawRect.origin.y += frameOutset;
1541     drawRect.size.width -= frameOutset * 2;
1542     drawRect.size.height -= frameOutset * 2;
1543   }
1544   else if (inKind == kHIThemeFrameListBox) {
1545     SInt32 frameOutset = 0;
1546     ::GetThemeMetric(kThemeMetricListBoxFrameOutset, &frameOutset);
1547     drawRect.origin.x += frameOutset;
1548     drawRect.origin.y += frameOutset;
1549     drawRect.size.width -= frameOutset * 2;
1550     drawRect.size.height -= frameOutset * 2;
1551   }
1553 #if DRAW_IN_FRAME_DEBUG
1554   CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
1555   CGContextFillRect(cgContext, inBoxRect);
1556 #endif
1558   HIThemeDrawFrame(&drawRect, &fdi, cgContext, HITHEME_ORIENTATION);
1560   NS_OBJC_END_TRY_ABORT_BLOCK;
1563 static const CellRenderSettings progressSettings[2][2] = {
1564   // Vertical progress bar.
1565   {
1566     // Determined settings.
1567     {
1568       {
1569         NSZeroSize, // mini
1570         NSMakeSize(10, 0), // small
1571         NSMakeSize(16, 0)  // regular
1572       },
1573       {
1574         NSZeroSize, NSZeroSize, NSZeroSize
1575       },
1576       {
1577         { // Leopard
1578           {0, 0, 0, 0},     // mini
1579           {1, 1, 1, 1},     // small
1580           {1, 1, 1, 1}      // regular
1581         }
1582       }
1583     },
1584     // There is no horizontal margin in regular undetermined size.
1585     {
1586       {
1587         NSZeroSize, // mini
1588         NSMakeSize(10, 0), // small
1589         NSMakeSize(16, 0)  // regular
1590       },
1591       {
1592         NSZeroSize, NSZeroSize, NSZeroSize
1593       },
1594       {
1595         { // Leopard
1596           {0, 0, 0, 0},     // mini
1597           {1, 1, 1, 1},     // small
1598           {1, 0, 1, 0}      // regular
1599         }
1600       }
1601     }
1602   },
1603   // Horizontal progress bar.
1604   {
1605     // Determined settings.
1606     {
1607       {
1608         NSZeroSize, // mini
1609         NSMakeSize(0, 10), // small
1610         NSMakeSize(0, 16)  // regular
1611       },
1612       {
1613         NSZeroSize, NSZeroSize, NSZeroSize
1614       },
1615       {
1616         { // Leopard
1617           {0, 0, 0, 0},     // mini
1618           {1, 1, 1, 1},     // small
1619           {1, 1, 1, 1}      // regular
1620         }
1621       }
1622     },
1623     // There is no horizontal margin in regular undetermined size.
1624     {
1625       {
1626         NSZeroSize, // mini
1627         NSMakeSize(0, 10), // small
1628         NSMakeSize(0, 16)  // regular
1629       },
1630       {
1631         NSZeroSize, NSZeroSize, NSZeroSize
1632       },
1633       {
1634         { // Leopard
1635           {0, 0, 0, 0},     // mini
1636           {1, 1, 1, 1},     // small
1637           {0, 1, 0, 1}      // regular
1638         }
1639       }
1640     }
1641   }
1644 void
1645 nsNativeThemeCocoa::DrawProgress(CGContextRef cgContext, const HIRect& inBoxRect,
1646                                  bool inIsIndeterminate, bool inIsHorizontal,
1647                                  double inValue, double inMaxValue,
1648                                  nsIFrame* aFrame)
1650   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1652   NSProgressBarCell* cell = mProgressBarCell;
1654   [cell setValue:inValue];
1655   [cell setMax:inMaxValue];
1656   [cell setIndeterminate:inIsIndeterminate];
1657   [cell setHorizontal:inIsHorizontal];
1658   [cell setControlTint:(FrameIsInActiveWindow(aFrame) ? [NSColor currentControlTint]
1659                                                       : NSClearControlTint)];
1661   DrawCellWithSnapping(cell, cgContext, inBoxRect,
1662                        progressSettings[inIsHorizontal][inIsIndeterminate],
1663                        VerticalAlignFactor(aFrame), mCellDrawView,
1664                        IsFrameRTL(aFrame));
1666   NS_OBJC_END_TRY_ABORT_BLOCK;
1669 static const CellRenderSettings meterSetting = {
1670   {
1671     NSMakeSize(0, 16), // mini
1672     NSMakeSize(0, 16), // small
1673     NSMakeSize(0, 16)  // regular
1674   },
1675   {
1676     NSZeroSize, NSZeroSize, NSZeroSize
1677   },
1678   {
1679     { // Leopard
1680       {1, 1, 1, 1},     // mini
1681       {1, 1, 1, 1},     // small
1682       {1, 1, 1, 1}      // regular
1683     }
1684   }
1687 void
1688 nsNativeThemeCocoa::DrawMeter(CGContextRef cgContext, const HIRect& inBoxRect,
1689                               nsIFrame* aFrame)
1691   NS_OBJC_BEGIN_TRY_ABORT_BLOCK
1693   NS_PRECONDITION(aFrame, "aFrame should not be null here!");
1695   // When using -moz-meterbar on an non meter element, we will not be able to
1696   // get all the needed information so we just draw an empty meter.
1697   nsIContent* content = aFrame->GetContent();
1698   if (!(content && content->IsHTML(nsGkAtoms::meter))) {
1699     DrawCellWithSnapping(mMeterBarCell, cgContext, inBoxRect,
1700                          meterSetting, VerticalAlignFactor(aFrame),
1701                          mCellDrawView, IsFrameRTL(aFrame));
1702     return;
1703   }
1705   HTMLMeterElement* meterElement = static_cast<HTMLMeterElement*>(content);
1706   double value = meterElement->Value();
1707   double min = meterElement->Min();
1708   double max = meterElement->Max();
1710   NSLevelIndicatorCell* cell = mMeterBarCell;
1712   [cell setMinValue:min];
1713   [cell setMaxValue:max];
1714   [cell setDoubleValue:value];
1716   /**
1717    * The way HTML and Cocoa defines the meter/indicator widget are different.
1718    * So, we are going to use a trick to get the Cocoa widget showing what we
1719    * are expecting: we set the warningValue or criticalValue to the current
1720    * value when we want to have the widget to be in the warning or critical
1721    * state.
1722    */
1723   EventStates states = aFrame->GetContent()->AsElement()->State();
1725   // Reset previously set warning and critical values.
1726   [cell setWarningValue:max+1];
1727   [cell setCriticalValue:max+1];
1729   if (states.HasState(NS_EVENT_STATE_SUB_OPTIMUM)) {
1730     [cell setWarningValue:value];
1731   } else if (states.HasState(NS_EVENT_STATE_SUB_SUB_OPTIMUM)) {
1732     [cell setCriticalValue:value];
1733   }
1735   HIRect rect = CGRectStandardize(inBoxRect);
1736   BOOL vertical = IsVerticalMeter(aFrame);
1738   CGContextSaveGState(cgContext);
1740   if (vertical) {
1741     /**
1742      * Cocoa doesn't provide a vertical meter bar so to show one, we have to
1743      * show a rotated horizontal meter bar.
1744      * Given that we want to show a vertical meter bar, we assume that the rect
1745      * has vertical dimensions but we can't correctly draw a meter widget inside
1746      * such a rectangle so we need to inverse width and height (and re-position)
1747      * to get a rectangle with horizontal dimensions.
1748      * Finally, we want to show a vertical meter so we want to rotate the result
1749      * so it is vertical. We do that by changing the context.
1750      */
1751     CGFloat tmp = rect.size.width;
1752     rect.size.width = rect.size.height;
1753     rect.size.height = tmp;
1754     rect.origin.x += rect.size.height / 2.f - rect.size.width / 2.f;
1755     rect.origin.y += rect.size.width / 2.f - rect.size.height / 2.f;
1757     CGContextTranslateCTM(cgContext, CGRectGetMidX(rect), CGRectGetMidY(rect));
1758     CGContextRotateCTM(cgContext, -M_PI / 2.f);
1759     CGContextTranslateCTM(cgContext, -CGRectGetMidX(rect), -CGRectGetMidY(rect));
1760   }
1762   DrawCellWithSnapping(cell, cgContext, rect,
1763                        meterSetting, VerticalAlignFactor(aFrame),
1764                        mCellDrawView, !vertical && IsFrameRTL(aFrame));
1766   CGContextRestoreGState(cgContext);
1768   NS_OBJC_END_TRY_ABORT_BLOCK
1771 void
1772 nsNativeThemeCocoa::DrawTabPanel(CGContextRef cgContext, const HIRect& inBoxRect,
1773                                  nsIFrame* aFrame)
1775   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1777   HIThemeTabPaneDrawInfo tpdi;
1779   tpdi.version = 1;
1780   tpdi.state = FrameIsInActiveWindow(aFrame) ? kThemeStateActive : kThemeStateInactive;
1781   tpdi.direction = kThemeTabNorth;
1782   tpdi.size = kHIThemeTabSizeNormal;
1783   tpdi.kind = kHIThemeTabKindNormal;
1785   HIThemeDrawTabPane(&inBoxRect, &tpdi, cgContext, HITHEME_ORIENTATION);
1787   NS_OBJC_END_TRY_ABORT_BLOCK;
1790 void
1791 nsNativeThemeCocoa::DrawScale(CGContextRef cgContext, const HIRect& inBoxRect,
1792                               EventStates inState, bool inIsVertical,
1793                               bool inIsReverse, int32_t inCurrentValue,
1794                               int32_t inMinValue, int32_t inMaxValue,
1795                               nsIFrame* aFrame)
1797   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1799   HIThemeTrackDrawInfo tdi;
1801   tdi.version = 0;
1802   tdi.kind = kThemeMediumSlider;
1803   tdi.bounds = inBoxRect;
1804   tdi.min = inMinValue;
1805   tdi.max = inMaxValue;
1806   tdi.value = inCurrentValue;
1807   tdi.attributes = kThemeTrackShowThumb;
1808   if (!inIsVertical)
1809     tdi.attributes |= kThemeTrackHorizontal;
1810   if (inIsReverse)
1811     tdi.attributes |= kThemeTrackRightToLeft;
1812   if (inState.HasState(NS_EVENT_STATE_FOCUS))
1813     tdi.attributes |= kThemeTrackHasFocus;
1814   if (IsDisabled(aFrame, inState))
1815     tdi.enableState = kThemeTrackDisabled;
1816   else
1817     tdi.enableState = FrameIsInActiveWindow(aFrame) ? kThemeTrackActive : kThemeTrackInactive;
1818   tdi.trackInfo.slider.thumbDir = kThemeThumbPlain;
1819   tdi.trackInfo.slider.pressState = 0;
1821   HIThemeDrawTrack(&tdi, NULL, cgContext, HITHEME_ORIENTATION);
1823   NS_OBJC_END_TRY_ABORT_BLOCK;
1826 nsIFrame*
1827 nsNativeThemeCocoa::SeparatorResponsibility(nsIFrame* aBefore, nsIFrame* aAfter)
1829   // Usually a separator is drawn by the segment to the right of the
1830   // separator, but pressed and selected segments have higher priority.
1831   if (!aBefore || !aAfter)
1832     return nullptr;
1833   if (IsSelectedButton(aAfter))
1834     return aAfter;
1835   if (IsSelectedButton(aBefore) || IsPressedButton(aBefore))
1836     return aBefore;
1837   return aAfter;
1840 CGRect
1841 nsNativeThemeCocoa::SeparatorAdjustedRect(CGRect aRect, nsIFrame* aLeft,
1842                                           nsIFrame* aCurrent, nsIFrame* aRight)
1844   // A separator between two segments should always be located in the leftmost
1845   // pixel column of the segment to the right of the separator, regardless of
1846   // who ends up drawing it.
1847   // CoreUI draws the separators inside the drawing rect.
1848   if (aLeft && SeparatorResponsibility(aLeft, aCurrent) == aLeft) {
1849     // The left button draws the separator, so we need to make room for it.
1850     aRect.origin.x += 1;
1851     aRect.size.width -= 1;
1852   }
1853   if (SeparatorResponsibility(aCurrent, aRight) == aCurrent) {
1854     // We draw the right separator, so we need to extend the draw rect into the
1855     // segment to our right.
1856     aRect.size.width += 1;
1857   }
1858   return aRect;
1861 static NSString* ToolbarButtonPosition(BOOL aIsFirst, BOOL aIsLast)
1863   if (aIsFirst) {
1864     if (aIsLast)
1865       return @"kCUISegmentPositionOnly";
1866     return @"kCUISegmentPositionFirst";
1867   }
1868   if (aIsLast)
1869     return @"kCUISegmentPositionLast";
1870   return @"kCUISegmentPositionMiddle";
1873 struct SegmentedControlRenderSettings {
1874   const CGFloat* heights;
1875   const NSString* widgetName;
1876   const BOOL ignoresPressedWhenSelected;
1877   const BOOL isToolbarControl;
1880 static const CGFloat tabHeights[3] = { 17, 20, 23 };
1882 static const SegmentedControlRenderSettings tabRenderSettings = {
1883   tabHeights, @"tab", YES, NO
1886 static const CGFloat toolbarButtonHeights[3] = { 15, 18, 22 };
1888 static const SegmentedControlRenderSettings toolbarButtonRenderSettings = {
1889   toolbarButtonHeights, @"kCUIWidgetButtonSegmentedSCurve", NO, YES
1892 void
1893 nsNativeThemeCocoa::DrawSegment(CGContextRef cgContext, const HIRect& inBoxRect,
1894                                 EventStates inState, nsIFrame* aFrame,
1895                                 const SegmentedControlRenderSettings& aSettings)
1897   BOOL isActive = IsActive(aFrame, aSettings.isToolbarControl);
1898   BOOL isFocused = inState.HasState(NS_EVENT_STATE_FOCUS);
1899   BOOL isSelected = IsSelectedButton(aFrame);
1900   BOOL isPressed = IsPressedButton(aFrame);
1901   if (isSelected && aSettings.ignoresPressedWhenSelected) {
1902     isPressed = NO;
1903   }
1905   BOOL isRTL = IsFrameRTL(aFrame);
1906   nsIFrame* left = GetAdjacentSiblingFrameWithSameAppearance(aFrame, isRTL);
1907   nsIFrame* right = GetAdjacentSiblingFrameWithSameAppearance(aFrame, !isRTL);
1908   CGRect drawRect = SeparatorAdjustedRect(inBoxRect, left, aFrame, right);
1909   BOOL drawLeftSeparator = SeparatorResponsibility(left, aFrame) == aFrame;
1910   BOOL drawRightSeparator = SeparatorResponsibility(aFrame, right) == aFrame;
1911   NSControlSize controlSize = FindControlSize(drawRect.size.height, aSettings.heights, 4.0f);
1913   RenderWithCoreUI(drawRect, cgContext, [NSDictionary dictionaryWithObjectsAndKeys:
1914             aSettings.widgetName, @"widget",
1915             (isActive ? @"kCUIPresentationStateActiveKey" : @"kCUIPresentationStateInactive"), @"kCUIPresentationStateKey",
1916             ToolbarButtonPosition(!left, !right), @"kCUIPositionKey",
1917             [NSNumber numberWithBool:drawLeftSeparator], @"kCUISegmentLeadingSeparatorKey",
1918             [NSNumber numberWithBool:drawRightSeparator], @"kCUISegmentTrailingSeparatorKey",
1919             [NSNumber numberWithBool:isSelected], @"value",
1920             (isPressed ? @"pressed" : (isActive ? @"normal" : @"inactive")), @"state",
1921             [NSNumber numberWithBool:isFocused], @"focus",
1922             CUIControlSizeForCocoaSize(controlSize), @"size",
1923             [NSNumber numberWithBool:YES], @"is.flipped",
1924             @"up", @"direction",
1925             nil]);
1928 static inline UInt8
1929 ConvertToPressState(EventStates aButtonState, UInt8 aPressState)
1931   // If the button is pressed, return the press state passed in. Otherwise, return 0.
1932   return aButtonState.HasAllStates(NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_HOVER) ? aPressState : 0;
1935 void 
1936 nsNativeThemeCocoa::GetScrollbarPressStates(nsIFrame* aFrame,
1937                                             EventStates aButtonStates[])
1939   static nsIContent::AttrValuesArray attributeValues[] = {
1940     &nsGkAtoms::scrollbarUpTop,
1941     &nsGkAtoms::scrollbarDownTop,
1942     &nsGkAtoms::scrollbarUpBottom,
1943     &nsGkAtoms::scrollbarDownBottom,
1944     nullptr
1945   };
1947   // Get the state of any scrollbar buttons in our child frames
1948   for (nsIFrame *childFrame = aFrame->GetFirstPrincipalChild(); 
1949        childFrame;
1950        childFrame = childFrame->GetNextSibling()) {
1952     nsIContent *childContent = childFrame->GetContent();
1953     if (!childContent) continue;
1954     int32_t attrIndex = childContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::sbattr, 
1955                                                       attributeValues, eCaseMatters);
1956     if (attrIndex < 0) continue;
1958     aButtonStates[attrIndex] = GetContentState(childFrame, NS_THEME_BUTTON);
1959   }
1962 // Both of the following sets of numbers were derived by loading the testcase in
1963 // bmo bug 380185 in Safari and observing its behavior for various heights of scrollbar.
1964 // These magic numbers are the minimum sizes we can draw a scrollbar and still 
1965 // have room for everything to display, including the thumb
1966 #define MIN_SCROLLBAR_SIZE_WITH_THUMB 61
1967 #define MIN_SMALL_SCROLLBAR_SIZE_WITH_THUMB 49
1968 // And these are the minimum sizes if we don't draw the thumb
1969 #define MIN_SCROLLBAR_SIZE 56
1970 #define MIN_SMALL_SCROLLBAR_SIZE 46
1972 void
1973 nsNativeThemeCocoa::GetScrollbarDrawInfo(HIThemeTrackDrawInfo& aTdi, nsIFrame *aFrame, 
1974                                          const CGSize& aSize, bool aShouldGetButtonStates)
1976   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1978   int32_t curpos = CheckIntAttr(aFrame, nsGkAtoms::curpos, 0);
1979   int32_t minpos = CheckIntAttr(aFrame, nsGkAtoms::minpos, 0);
1980   int32_t maxpos = CheckIntAttr(aFrame, nsGkAtoms::maxpos, 100);
1981   int32_t thumbSize = CheckIntAttr(aFrame, nsGkAtoms::pageincrement, 10);
1983   bool isHorizontal = aFrame->GetContent()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::orient, 
1984                                                           nsGkAtoms::horizontal, eCaseMatters);
1985   bool isSmall = aFrame->StyleDisplay()->mAppearance == NS_THEME_SCROLLBAR_SMALL;
1987   aTdi.version = 0;
1988   aTdi.kind = isSmall ? kThemeSmallScrollBar : kThemeMediumScrollBar;
1989   aTdi.bounds.origin = CGPointZero;
1990   aTdi.bounds.size = aSize;
1991   aTdi.min = minpos;
1992   aTdi.max = maxpos;
1993   aTdi.value = curpos;
1994   aTdi.attributes = 0;
1995   aTdi.enableState = kThemeTrackActive;
1996   if (isHorizontal)
1997     aTdi.attributes |= kThemeTrackHorizontal;
1999   aTdi.trackInfo.scrollbar.viewsize = (SInt32)thumbSize;
2001   // This should be done early on so things like "kThemeTrackNothingToScroll" can
2002   // override the active enable state.
2003   aTdi.enableState = FrameIsInActiveWindow(aFrame) ? kThemeTrackActive : kThemeTrackInactive;
2005   /* Only display features if we have enough room for them.
2006    * Gecko still maintains the scrollbar info; this is just a visual issue (bug 380185).
2007    */
2008   int32_t longSideLength = (int32_t)(isHorizontal ? (aSize.width) : (aSize.height));
2009   if (longSideLength >= (isSmall ? MIN_SMALL_SCROLLBAR_SIZE_WITH_THUMB : MIN_SCROLLBAR_SIZE_WITH_THUMB)) {
2010     aTdi.attributes |= kThemeTrackShowThumb;
2011   }
2012   else if (longSideLength < (isSmall ? MIN_SMALL_SCROLLBAR_SIZE : MIN_SCROLLBAR_SIZE)) {
2013     aTdi.enableState = kThemeTrackNothingToScroll;
2014     return;
2015   }
2017   aTdi.trackInfo.scrollbar.pressState = 0;
2019   // Only go get these scrollbar button states if we need it. For example,
2020   // there's no reason to look up scrollbar button states when we're only
2021   // creating a TrackDrawInfo to determine the size of the thumb. There's
2022   // also no reason to do this on Lion or later, whose scrollbars have no
2023   // arrow buttons.
2024   if (aShouldGetButtonStates && !nsCocoaFeatures::OnLionOrLater()) {
2025     EventStates buttonStates[4];
2026     GetScrollbarPressStates(aFrame, buttonStates);
2027     NSString *buttonPlacement = [[NSUserDefaults standardUserDefaults] objectForKey:@"AppleScrollBarVariant"];
2028     // It seems that unless all four buttons are showing, kThemeTopOutsideArrowPressed is the correct constant for
2029     // the up scrollbar button.
2030     if ([buttonPlacement isEqualToString:@"DoubleBoth"]) {
2031       aTdi.trackInfo.scrollbar.pressState = ConvertToPressState(buttonStates[0], kThemeTopOutsideArrowPressed) |
2032                                             ConvertToPressState(buttonStates[1], kThemeTopInsideArrowPressed) |
2033                                             ConvertToPressState(buttonStates[2], kThemeBottomInsideArrowPressed) |
2034                                             ConvertToPressState(buttonStates[3], kThemeBottomOutsideArrowPressed);
2035     } else {
2036       aTdi.trackInfo.scrollbar.pressState = ConvertToPressState(buttonStates[0], kThemeTopOutsideArrowPressed) |
2037                                             ConvertToPressState(buttonStates[1], kThemeBottomOutsideArrowPressed) |
2038                                             ConvertToPressState(buttonStates[2], kThemeTopOutsideArrowPressed) |
2039                                             ConvertToPressState(buttonStates[3], kThemeBottomOutsideArrowPressed);
2040     }
2041   }
2043   NS_OBJC_END_TRY_ABORT_BLOCK;
2046 static void
2047 RenderScrollbar(CGContextRef cgContext, const HIRect& aRenderRect, void* aData)
2049   HIThemeTrackDrawInfo* tdi = (HIThemeTrackDrawInfo*)aData;
2050   HIThemeDrawTrack(tdi, NULL, cgContext, HITHEME_ORIENTATION);
2053 void
2054 nsNativeThemeCocoa::DrawScrollbar(CGContextRef aCGContext, const HIRect& aBoxRect, nsIFrame *aFrame)
2056   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2058   HIThemeTrackDrawInfo tdi;
2059   GetScrollbarDrawInfo(tdi, aFrame, aBoxRect.size, true); // True means we want the press states
2060   RenderTransformedHIThemeControl(aCGContext, aBoxRect, RenderScrollbar, &tdi);
2062   NS_OBJC_END_TRY_ABORT_BLOCK;
2065 nsIFrame*
2066 nsNativeThemeCocoa::GetParentScrollbarFrame(nsIFrame *aFrame)
2068   // Walk our parents to find a scrollbar frame
2069   nsIFrame *scrollbarFrame = aFrame;
2070   do {
2071     if (scrollbarFrame->GetType() == nsGkAtoms::scrollbarFrame) break;
2072   } while ((scrollbarFrame = scrollbarFrame->GetParent()));
2074   // We return null if we can't find a parent scrollbar frame
2075   return scrollbarFrame;
2078 static bool
2079 ToolbarCanBeUnified(CGContextRef cgContext, const HIRect& inBoxRect, NSWindow* aWindow)
2081   if (![aWindow isKindOfClass:[ToolbarWindow class]])
2082     return false;
2084   ToolbarWindow* win = (ToolbarWindow*)aWindow;
2085   float unifiedToolbarHeight = [win unifiedToolbarHeight];
2086   return inBoxRect.origin.x == 0 &&
2087          inBoxRect.size.width >= [win frame].size.width &&
2088          CGRectGetMaxY(inBoxRect) <= unifiedToolbarHeight;
2091 // By default, kCUIWidgetWindowFrame drawing draws rounded corners in the
2092 // upper corners. Depending on the context type, it fills the background in
2093 // the corners with black or leaves it transparent. Unfortunately, this corner
2094 // rounding interacts poorly with the window corner masking we apply during
2095 // titlebar drawing and results in small remnants of the corner background
2096 // appearing at the rounded edge.
2097 // So we draw square corners.
2098 static void
2099 DrawNativeTitlebarToolbarWithSquareCorners(CGContextRef aContext, const CGRect& aRect,
2100                                            CGFloat aUnifiedHeight, BOOL aIsMain, BOOL aIsFlipped)
2102   // We extend the draw rect horizontally and clip away the rounded corners.
2103   const CGFloat extendHorizontal = 10;
2104   CGRect drawRect = CGRectInset(aRect, -extendHorizontal, 0);
2105   CGContextSaveGState(aContext);
2106   CGContextClipToRect(aContext, aRect);
2108   RenderWithCoreUI(drawRect, aContext,
2109           [NSDictionary dictionaryWithObjectsAndKeys:
2110             @"kCUIWidgetWindowFrame", @"widget",
2111             @"regularwin", @"windowtype",
2112             (aIsMain ? @"normal" : @"inactive"), @"state",
2113             [NSNumber numberWithDouble:aUnifiedHeight], @"kCUIWindowFrameUnifiedTitleBarHeightKey",
2114             [NSNumber numberWithBool:YES], @"kCUIWindowFrameDrawTitleSeparatorKey",
2115             [NSNumber numberWithBool:aIsFlipped], @"is.flipped",
2116             nil]);
2118   CGContextRestoreGState(aContext);
2121 void
2122 nsNativeThemeCocoa::DrawUnifiedToolbar(CGContextRef cgContext, const HIRect& inBoxRect,
2123                                        NSWindow* aWindow)
2125   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2127   CGContextSaveGState(cgContext);
2128   CGContextClipToRect(cgContext, inBoxRect);
2130   CGFloat unifiedHeight = std::max([(ToolbarWindow*)aWindow unifiedToolbarHeight],
2131                                    inBoxRect.size.height);
2132   BOOL isMain = [aWindow isMainWindow];
2133   CGFloat titlebarHeight = unifiedHeight - inBoxRect.size.height;
2134   CGRect drawRect = CGRectMake(inBoxRect.origin.x, inBoxRect.origin.y - titlebarHeight,
2135                                inBoxRect.size.width, inBoxRect.size.height + titlebarHeight);
2136   DrawNativeTitlebarToolbarWithSquareCorners(cgContext, drawRect, unifiedHeight, isMain, YES);
2138   CGContextRestoreGState(cgContext);
2140   NS_OBJC_END_TRY_ABORT_BLOCK;
2143 void
2144 nsNativeThemeCocoa::DrawStatusBar(CGContextRef cgContext, const HIRect& inBoxRect,
2145                                   nsIFrame *aFrame)
2147   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2149   if (inBoxRect.size.height < 2.0f)
2150     return;
2152   CGContextSaveGState(cgContext);
2153   CGContextClipToRect(cgContext, inBoxRect);
2155   // kCUIWidgetWindowFrame draws a complete window frame with both title bar
2156   // and bottom bar. We only want the bottom bar, so we extend the draw rect
2157   // upwards to make space for the title bar, and then we clip it away.
2158   CGRect drawRect = inBoxRect;
2159   const int extendUpwards = 40;
2160   drawRect.origin.y -= extendUpwards;
2161   drawRect.size.height += extendUpwards;
2162   RenderWithCoreUI(drawRect, cgContext,
2163           [NSDictionary dictionaryWithObjectsAndKeys:
2164             @"kCUIWidgetWindowFrame", @"widget",
2165             @"regularwin", @"windowtype",
2166             (IsActive(aFrame, YES) ? @"normal" : @"inactive"), @"state",
2167             [NSNumber numberWithInt:inBoxRect.size.height], @"kCUIWindowFrameBottomBarHeightKey",
2168             [NSNumber numberWithBool:YES], @"kCUIWindowFrameDrawBottomBarSeparatorKey",
2169             [NSNumber numberWithBool:YES], @"is.flipped",
2170             nil]);
2172   CGContextRestoreGState(cgContext);
2174   NS_OBJC_END_TRY_ABORT_BLOCK;
2177 void
2178 nsNativeThemeCocoa::DrawNativeTitlebar(CGContextRef aContext, CGRect aTitlebarRect,
2179                                        CGFloat aUnifiedHeight, BOOL aIsMain, BOOL aIsFlipped)
2181   CGFloat unifiedHeight = std::max(aUnifiedHeight, aTitlebarRect.size.height);
2182   DrawNativeTitlebarToolbarWithSquareCorners(aContext, aTitlebarRect, unifiedHeight, aIsMain, aIsFlipped);
2185 static void
2186 RenderResizer(CGContextRef cgContext, const HIRect& aRenderRect, void* aData)
2188   HIThemeGrowBoxDrawInfo* drawInfo = (HIThemeGrowBoxDrawInfo*)aData;
2189   HIThemeDrawGrowBox(&CGPointZero, drawInfo, cgContext, kHIThemeOrientationNormal);
2192 void
2193 nsNativeThemeCocoa::DrawResizer(CGContextRef cgContext, const HIRect& aRect,
2194                                 nsIFrame *aFrame)
2196   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2198   HIThemeGrowBoxDrawInfo drawInfo;
2199   drawInfo.version = 0;
2200   drawInfo.state = kThemeStateActive;
2201   drawInfo.kind = kHIThemeGrowBoxKindNormal;
2202   drawInfo.direction = kThemeGrowRight | kThemeGrowDown;
2203   drawInfo.size = kHIThemeGrowBoxSizeNormal;
2205   RenderTransformedHIThemeControl(cgContext, aRect, RenderResizer, &drawInfo,
2206                                   IsFrameRTL(aFrame));
2208   NS_OBJC_END_TRY_ABORT_BLOCK;
2211 static bool
2212 ScrollbarTrackAndThumbDrawSeparately()
2214   return nsLookAndFeel::UseOverlayScrollbars() || nsCocoaFeatures::OnLionOrLater();
2217 bool
2218 nsNativeThemeCocoa::IsParentScrollbarRolledOver(nsIFrame* aFrame)
2220   nsIFrame* scrollbarFrame = GetParentScrollbarFrame(aFrame);
2221   return nsLookAndFeel::UseOverlayScrollbars()
2222     ? CheckBooleanAttr(scrollbarFrame, nsGkAtoms::hover)
2223     : GetContentState(scrollbarFrame, NS_THEME_NONE).HasState(NS_EVENT_STATE_HOVER);
2226 static bool
2227 IsHiDPIContext(nsPresContext* aContext)
2229   return nsPresContext::AppUnitsPerCSSPixel() >=
2230     2 * aContext->DeviceContext()->UnscaledAppUnitsPerDevPixel();
2233 NS_IMETHODIMP
2234 nsNativeThemeCocoa::DrawWidgetBackground(nsRenderingContext* aContext,
2235                                          nsIFrame* aFrame,
2236                                          uint8_t aWidgetType,
2237                                          const nsRect& aRect,
2238                                          const nsRect& aDirtyRect)
2240   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
2242   // setup to draw into the correct port
2243   int32_t p2a = aFrame->PresContext()->AppUnitsPerDevPixel();
2245   gfxRect nativeDirtyRect(aDirtyRect.x, aDirtyRect.y,
2246                           aDirtyRect.width, aDirtyRect.height);
2247   gfxRect nativeWidgetRect(aRect.x, aRect.y, aRect.width, aRect.height);
2248   nativeWidgetRect.ScaleInverse(gfxFloat(p2a));
2249   nativeDirtyRect.ScaleInverse(gfxFloat(p2a));
2250   nativeWidgetRect.Round();
2251   if (nativeWidgetRect.IsEmpty())
2252     return NS_OK; // Don't attempt to draw invisible widgets.
2254   gfxContext* thebesCtx = aContext->ThebesContext();
2255   if (!thebesCtx)
2256     return NS_ERROR_FAILURE;
2258   gfxContextMatrixAutoSaveRestore save(thebesCtx);
2260   bool hidpi = IsHiDPIContext(aFrame->PresContext());
2261   if (hidpi) {
2262     // Use high-resolution drawing.
2263     nativeWidgetRect.ScaleInverse(2.0f);
2264     nativeDirtyRect.ScaleInverse(2.0f);
2265     thebesCtx->Scale(2.0f, 2.0f);
2266   }
2268   gfxQuartzNativeDrawing nativeDrawing(thebesCtx, nativeDirtyRect);
2270   CGContextRef cgContext = nativeDrawing.BeginNativeDrawing();
2271   if (cgContext == nullptr) {
2272     // The Quartz surface handles 0x0 surfaces by internally
2273     // making all operations no-ops; there's no cgcontext created for them.
2274     // Unfortunately, this means that callers that want to render
2275     // directly to the CGContext need to be aware of this quirk.
2276     return NS_OK;
2277   }
2279 #if 0
2280   if (1 /*aWidgetType == NS_THEME_TEXTFIELD*/) {
2281     fprintf(stderr, "Native theme drawing widget %d [%p] dis:%d in rect [%d %d %d %d]\n",
2282             aWidgetType, aFrame, IsDisabled(aFrame), aRect.x, aRect.y, aRect.width, aRect.height);
2283     fprintf(stderr, "Cairo matrix: [%f %f %f %f %f %f]\n",
2284             mat._11, mat._12, mat._21, mat._22, mat._31, mat._32);
2285     fprintf(stderr, "Native theme xform[0]: [%f %f %f %f %f %f]\n",
2286             mm0.a, mm0.b, mm0.c, mm0.d, mm0.tx, mm0.ty);
2287     CGAffineTransform mm = CGContextGetCTM(cgContext);
2288     fprintf(stderr, "Native theme xform[1]: [%f %f %f %f %f %f]\n",
2289             mm.a, mm.b, mm.c, mm.d, mm.tx, mm.ty);
2290   }
2291 #endif
2293   CGRect macRect = CGRectMake(nativeWidgetRect.X(), nativeWidgetRect.Y(),
2294                               nativeWidgetRect.Width(), nativeWidgetRect.Height());
2296 #if 0
2297   fprintf(stderr, "    --> macRect %f %f %f %f\n",
2298           macRect.origin.x, macRect.origin.y, macRect.size.width, macRect.size.height);
2299   CGRect bounds = CGContextGetClipBoundingBox(cgContext);
2300   fprintf(stderr, "    --> clip bounds: %f %f %f %f\n",
2301           bounds.origin.x, bounds.origin.y, bounds.size.width, bounds.size.height);
2303   //CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 1.0, 0.1);
2304   //CGContextFillRect(cgContext, bounds);
2305 #endif
2307   EventStates eventState = GetContentState(aFrame, aWidgetType);
2309   switch (aWidgetType) {
2310     case NS_THEME_DIALOG: {
2311       HIThemeSetFill(kThemeBrushDialogBackgroundActive, NULL, cgContext, HITHEME_ORIENTATION);
2312       CGContextFillRect(cgContext, macRect);
2313     }
2314       break;
2316     case NS_THEME_MENUPOPUP: {
2317       HIThemeMenuDrawInfo mdi;
2318       memset(&mdi, 0, sizeof(mdi));
2319       mdi.version = 0;
2320       mdi.menuType = IsDisabled(aFrame, eventState) ?
2321                        static_cast<ThemeMenuType>(kThemeMenuTypeInactive) :
2322                        static_cast<ThemeMenuType>(kThemeMenuTypePopUp);
2324       bool isLeftOfParent = false;
2325       if (IsSubmenu(aFrame, &isLeftOfParent) && !isLeftOfParent) {
2326         mdi.menuType = kThemeMenuTypeHierarchical;
2327       }
2328       
2329       // The rounded corners draw outside the frame.
2330       CGRect deflatedRect = CGRectMake(macRect.origin.x, macRect.origin.y + 4,
2331                                        macRect.size.width, macRect.size.height - 8);
2332       HIThemeDrawMenuBackground(&deflatedRect, &mdi, cgContext, HITHEME_ORIENTATION);
2333     }
2334       break;
2336     case NS_THEME_MENUITEM:
2337     case NS_THEME_CHECKMENUITEM: {
2338       SurfaceFormat format  = thebesCtx->GetDrawTarget()->GetFormat();
2339       bool isTransparent = (format == SurfaceFormat::R8G8B8A8) ||
2340                       (format == SurfaceFormat::B8G8R8A8);
2341       if (isTransparent) {
2342         // Clear the background to get correct transparency.
2343         CGContextClearRect(cgContext, macRect);
2344       }
2346       // maybe use kThemeMenuItemHierBackground or PopUpBackground instead of just Plain?
2347       HIThemeMenuItemDrawInfo drawInfo;
2348       memset(&drawInfo, 0, sizeof(drawInfo));
2349       drawInfo.version = 0;
2350       drawInfo.itemType = kThemeMenuItemPlain;
2351       drawInfo.state = (IsDisabled(aFrame, eventState) ?
2352                          static_cast<ThemeMenuState>(kThemeMenuDisabled) :
2353                          CheckBooleanAttr(aFrame, nsGkAtoms::menuactive) ?
2354                            static_cast<ThemeMenuState>(kThemeMenuSelected) :
2355                            static_cast<ThemeMenuState>(kThemeMenuActive));
2357       // XXX pass in the menu rect instead of always using the item rect
2358       HIRect ignored;
2359       HIThemeDrawMenuItem(&macRect, &macRect, &drawInfo, cgContext, HITHEME_ORIENTATION, &ignored);
2361       if (aWidgetType == NS_THEME_CHECKMENUITEM) {
2362         DrawMenuIcon(cgContext, macRect, eventState, aFrame, kCheckmarkSize, kCheckmarkImage);
2363       }
2364     }
2365       break;
2367     case NS_THEME_MENUSEPARATOR: {
2368       ThemeMenuState menuState;
2369       if (IsDisabled(aFrame, eventState)) {
2370         menuState = kThemeMenuDisabled;
2371       }
2372       else {
2373         menuState = CheckBooleanAttr(aFrame, nsGkAtoms::menuactive) ?
2374                     kThemeMenuSelected : kThemeMenuActive;
2375       }
2377       HIThemeMenuItemDrawInfo midi = { 0, kThemeMenuItemPlain, menuState };
2378       HIThemeDrawMenuSeparator(&macRect, &macRect, &midi, cgContext, HITHEME_ORIENTATION);
2379     }
2380       break;
2382     case NS_THEME_TOOLTIP:
2383       if (nsCocoaFeatures::OnYosemiteOrLater()) {
2384         CGContextSetRGBFillColor(cgContext, 0.945, 0.942, 0.945, 0.950);
2385       } else {
2386         CGContextSetRGBFillColor(cgContext, 0.996, 1.000, 0.792, 0.950);
2387       }
2388       CGContextFillRect(cgContext, macRect);
2389       break;
2391     case NS_THEME_CHECKBOX:
2392     case NS_THEME_RADIO: {
2393       bool isCheckbox = (aWidgetType == NS_THEME_CHECKBOX);
2394       DrawCheckboxOrRadio(cgContext, isCheckbox, macRect, GetCheckedOrSelected(aFrame, !isCheckbox),
2395                           eventState, aFrame);
2396     }
2397       break;
2399     case NS_THEME_BUTTON:
2400       if (IsDefaultButton(aFrame)) {
2401         if (!IsDisabled(aFrame, eventState) && FrameIsInActiveWindow(aFrame) &&
2402             !QueueAnimatedContentForRefresh(aFrame->GetContent(), 10)) {
2403           NS_WARNING("Unable to animate button!");
2404         }
2405         DrawButton(cgContext, kThemePushButton, macRect, true,
2406                    kThemeButtonOff, kThemeAdornmentNone, eventState, aFrame);
2407       } else if (IsButtonTypeMenu(aFrame)) {
2408         DrawDropdown(cgContext, macRect, eventState, aWidgetType, aFrame);
2409       } else {
2410         DrawPushButton(cgContext, macRect, eventState, aWidgetType, aFrame);
2411       }
2412       break;
2414     case NS_THEME_FOCUS_OUTLINE:
2415       DrawFocusOutline(cgContext, macRect, eventState, aWidgetType, aFrame);
2416       break;
2418     case NS_THEME_MOZ_MAC_HELP_BUTTON:
2419       DrawPushButton(cgContext, macRect, eventState, aWidgetType, aFrame);
2420       break;
2422     case NS_THEME_BUTTON_BEVEL:
2423       DrawButton(cgContext, kThemeMediumBevelButton, macRect,
2424                  IsDefaultButton(aFrame), kThemeButtonOff, kThemeAdornmentNone,
2425                  eventState, aFrame);
2426       break;
2428     case NS_THEME_SPINNER: {
2429       nsIContent* content = aFrame->GetContent();
2430       if (content->IsHTML()) {
2431         // In HTML the theming for the spin buttons is drawn individually into
2432         // their own backgrounds instead of being drawn into the background of
2433         // their spinner parent as it is for XUL.
2434         break;
2435       }
2436       ThemeDrawState state = kThemeStateActive;
2437       if (content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::state,
2438                                NS_LITERAL_STRING("up"), eCaseMatters)) {
2439         state = kThemeStatePressedUp;
2440       }
2441       else if (content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::state,
2442                                     NS_LITERAL_STRING("down"), eCaseMatters)) {
2443         state = kThemeStatePressedDown;
2444       }
2446       DrawSpinButtons(cgContext, kThemeIncDecButton, macRect, state,
2447                       kThemeAdornmentNone, eventState, aFrame);
2448     }
2449       break;
2451     case NS_THEME_SPINNER_UP_BUTTON:
2452     case NS_THEME_SPINNER_DOWN_BUTTON: {
2453       nsNumberControlFrame* numberControlFrame =
2454         nsNumberControlFrame::GetNumberControlFrameForSpinButton(aFrame);
2455       if (numberControlFrame) {
2456         ThemeDrawState state = kThemeStateActive;
2457         if (numberControlFrame->SpinnerUpButtonIsDepressed()) {
2458           state = kThemeStatePressedUp;
2459         } else if (numberControlFrame->SpinnerDownButtonIsDepressed()) {
2460           state = kThemeStatePressedDown;
2461         }
2462         DrawSpinButton(cgContext, kThemeIncDecButtonMini, macRect, state,
2463                        kThemeAdornmentNone, eventState, aFrame, aWidgetType);
2464       }
2465     }
2466       break;
2468     case NS_THEME_TOOLBAR_BUTTON:
2469       DrawSegment(cgContext, macRect, eventState, aFrame, toolbarButtonRenderSettings);
2470       break;
2472     case NS_THEME_TOOLBAR_SEPARATOR: {
2473       HIThemeSeparatorDrawInfo sdi = { 0, kThemeStateActive };
2474       HIThemeDrawSeparator(&macRect, &sdi, cgContext, HITHEME_ORIENTATION);
2475     }
2476       break;
2478     case NS_THEME_MOZ_MAC_UNIFIED_TOOLBAR:
2479     case NS_THEME_TOOLBAR: {
2480       NSWindow* win = NativeWindowForFrame(aFrame);
2481       if (ToolbarCanBeUnified(cgContext, macRect, win)) {
2482         DrawUnifiedToolbar(cgContext, macRect, win);
2483         break;
2484       }
2485       BOOL isMain = [win isMainWindow];
2486       CGRect drawRect = macRect;
2488       // top border
2489       drawRect.size.height = 1.0f;
2490       DrawNativeGreyColorInRect(cgContext, toolbarTopBorderGrey, drawRect, isMain);
2492       // background
2493       drawRect.origin.y += drawRect.size.height;
2494       drawRect.size.height = macRect.size.height - 2.0f;
2495       DrawNativeGreyColorInRect(cgContext, toolbarFillGrey, drawRect, isMain);
2497       // bottom border
2498       drawRect.origin.y += drawRect.size.height;
2499       drawRect.size.height = 1.0f;
2500       DrawNativeGreyColorInRect(cgContext, toolbarBottomBorderGrey, drawRect, isMain);
2501     }
2502       break;
2504     case NS_THEME_WINDOW_TITLEBAR: {
2505       NSWindow* win = NativeWindowForFrame(aFrame);
2506       BOOL isMain = [win isMainWindow];
2507       float unifiedToolbarHeight = [win isKindOfClass:[ToolbarWindow class]] ?
2508         [(ToolbarWindow*)win unifiedToolbarHeight] : macRect.size.height;
2509       DrawNativeTitlebar(cgContext, macRect, unifiedToolbarHeight, isMain, YES);
2510     }
2511       break;
2513     case NS_THEME_TOOLBOX: {
2514       HIThemeHeaderDrawInfo hdi = { 0, kThemeStateActive, kHIThemeHeaderKindWindow };
2515       HIThemeDrawHeader(&macRect, &hdi, cgContext, HITHEME_ORIENTATION);
2516     }
2517       break;
2519     case NS_THEME_STATUSBAR: 
2520       DrawStatusBar(cgContext, macRect, aFrame);
2521       break;
2523     case NS_THEME_DROPDOWN:
2524     case NS_THEME_DROPDOWN_TEXTFIELD:
2525       DrawDropdown(cgContext, macRect, eventState, aWidgetType, aFrame);
2526       break;
2528     case NS_THEME_DROPDOWN_BUTTON:
2529       DrawButton(cgContext, kThemeArrowButton, macRect, false, kThemeButtonOn,
2530                  kThemeAdornmentArrowDownArrow, eventState, aFrame);
2531       break;
2533     case NS_THEME_GROUPBOX: {
2534       HIThemeGroupBoxDrawInfo gdi = { 0, kThemeStateActive, kHIThemeGroupBoxKindPrimary };
2535       HIThemeDrawGroupBox(&macRect, &gdi, cgContext, HITHEME_ORIENTATION);
2536       break;
2537     }
2539     case NS_THEME_TEXTFIELD:
2540     case NS_THEME_NUMBER_INPUT:
2541       // HIThemeSetFill is not available on 10.3
2542       CGContextSetRGBFillColor(cgContext, 1.0, 1.0, 1.0, 1.0);
2543       CGContextFillRect(cgContext, macRect);
2545       // XUL textboxes set the native appearance on the containing box, while
2546       // concrete focus is set on the html:input element within it. We can
2547       // though, check the focused attribute of xul textboxes in this case.
2548       // On Mac, focus rings are always shown for textboxes, so we do not need
2549       // to check the window's focus ring state here
2550       if (aFrame->GetContent()->IsXUL() && IsFocused(aFrame)) {
2551         eventState |= NS_EVENT_STATE_FOCUS;
2552       }
2554       DrawFrame(cgContext, kHIThemeFrameTextFieldSquare, macRect,
2555                 IsDisabled(aFrame, eventState) || IsReadOnly(aFrame), eventState);
2556       break;
2557       
2558     case NS_THEME_SEARCHFIELD:
2559       DrawSearchField(cgContext, macRect, aFrame, eventState);
2560       break;
2562     case NS_THEME_PROGRESSBAR:
2563     {
2564       double value = GetProgressValue(aFrame);
2565       double maxValue = GetProgressMaxValue(aFrame);
2566       // Don't request repaints for scrollbars at 100% because those don't animate.
2567       if (value < maxValue) {
2568         if (!QueueAnimatedContentForRefresh(aFrame->GetContent(), 30)) {
2569           NS_WARNING("Unable to animate progressbar!");
2570         }
2571       }
2572       DrawProgress(cgContext, macRect, IsIndeterminateProgress(aFrame, eventState),
2573                    aFrame->StyleDisplay()->mOrient != NS_STYLE_ORIENT_VERTICAL,
2574                    value, maxValue, aFrame);
2575       break;
2576     }
2578     case NS_THEME_PROGRESSBAR_VERTICAL:
2579       DrawProgress(cgContext, macRect, IsIndeterminateProgress(aFrame, eventState),
2580                    false, GetProgressValue(aFrame),
2581                    GetProgressMaxValue(aFrame), aFrame);
2582       break;
2584     case NS_THEME_METERBAR:
2585       DrawMeter(cgContext, macRect, aFrame);
2586       break;
2588     case NS_THEME_PROGRESSBAR_CHUNK:
2589     case NS_THEME_PROGRESSBAR_CHUNK_VERTICAL:
2590     case NS_THEME_METERBAR_CHUNK:
2591       // Do nothing: progress and meter bars cases will draw chunks.
2592       break;
2594     case NS_THEME_TREEVIEW_TWISTY:
2595       DrawButton(cgContext, kThemeDisclosureButton, macRect, false,
2596                  kThemeDisclosureRight, kThemeAdornmentNone, eventState, aFrame);
2597       break;
2599     case NS_THEME_TREEVIEW_TWISTY_OPEN:
2600       DrawButton(cgContext, kThemeDisclosureButton, macRect, false,
2601                  kThemeDisclosureDown, kThemeAdornmentNone, eventState, aFrame);
2602       break;
2604     case NS_THEME_TREEVIEW_HEADER_CELL: {
2605       TreeSortDirection sortDirection = GetTreeSortDirection(aFrame);
2606       DrawButton(cgContext, kThemeListHeaderButton, macRect, false,
2607                  sortDirection == eTreeSortDirection_Natural ? kThemeButtonOff : kThemeButtonOn,
2608                  sortDirection == eTreeSortDirection_Ascending ?
2609                  kThemeAdornmentHeaderButtonSortUp : kThemeAdornmentNone, eventState, aFrame);
2610     }
2611       break;
2613     case NS_THEME_TREEVIEW_TREEITEM:
2614     case NS_THEME_TREEVIEW:
2615       // HIThemeSetFill is not available on 10.3
2616       // HIThemeSetFill(kThemeBrushWhite, NULL, cgContext, HITHEME_ORIENTATION);
2617       CGContextSetRGBFillColor(cgContext, 1.0, 1.0, 1.0, 1.0);
2618       CGContextFillRect(cgContext, macRect);
2619       break;
2621     case NS_THEME_TREEVIEW_HEADER:
2622       // do nothing, taken care of by individual header cells
2623     case NS_THEME_TREEVIEW_HEADER_SORTARROW:
2624       // do nothing, taken care of by treeview header
2625     case NS_THEME_TREEVIEW_LINE:
2626       // do nothing, these lines don't exist on macos
2627       break;
2629     case NS_THEME_SCALE_HORIZONTAL:
2630     case NS_THEME_SCALE_VERTICAL: {
2631       int32_t curpos = CheckIntAttr(aFrame, nsGkAtoms::curpos, 0);
2632       int32_t minpos = CheckIntAttr(aFrame, nsGkAtoms::minpos, 0);
2633       int32_t maxpos = CheckIntAttr(aFrame, nsGkAtoms::maxpos, 100);
2634       if (!maxpos)
2635         maxpos = 100;
2637       bool reverse = aFrame->GetContent()->
2638         AttrValueIs(kNameSpaceID_None, nsGkAtoms::dir,
2639                     NS_LITERAL_STRING("reverse"), eCaseMatters);
2640       DrawScale(cgContext, macRect, eventState,
2641                 (aWidgetType == NS_THEME_SCALE_VERTICAL), reverse,
2642                 curpos, minpos, maxpos, aFrame);
2643     }
2644       break;
2646     case NS_THEME_SCALE_THUMB_HORIZONTAL:
2647     case NS_THEME_SCALE_THUMB_VERTICAL:
2648       // do nothing, drawn by scale
2649       break;
2651     case NS_THEME_RANGE: {
2652       nsRangeFrame *rangeFrame = do_QueryFrame(aFrame);
2653       if (!rangeFrame) {
2654         break;
2655       }
2656       // DrawScale requires integer min, max and value. This is purely for
2657       // drawing, so we normalize to a range 0-1000 here.
2658       int32_t value = int32_t(rangeFrame->GetValueAsFractionOfRange() * 1000);
2659       int32_t min = 0;
2660       int32_t max = 1000;
2661       bool isVertical = !IsRangeHorizontal(aFrame);
2662       bool reverseDir =
2663         isVertical ||
2664         rangeFrame->StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL;
2665       DrawScale(cgContext, macRect, eventState, isVertical, reverseDir,
2666                 value, min, max, aFrame);
2667       break;
2668     }
2670     case NS_THEME_SCROLLBAR_SMALL:
2671     case NS_THEME_SCROLLBAR:
2672       if (!ScrollbarTrackAndThumbDrawSeparately()) {
2673         DrawScrollbar(cgContext, macRect, aFrame);
2674       }
2675       break;
2676     case NS_THEME_SCROLLBAR_THUMB_VERTICAL:
2677     case NS_THEME_SCROLLBAR_THUMB_HORIZONTAL:
2678       if (ScrollbarTrackAndThumbDrawSeparately()) {
2679         BOOL isOverlay = nsLookAndFeel::UseOverlayScrollbars();
2680         BOOL isHorizontal = (aWidgetType == NS_THEME_SCROLLBAR_THUMB_HORIZONTAL);
2681         BOOL isRolledOver = IsParentScrollbarRolledOver(aFrame);
2682         if (isOverlay && (!nsCocoaFeatures::OnMountainLionOrLater() || !isRolledOver)) {
2683           if (isHorizontal) {
2684             macRect.origin.y += 4;
2685             macRect.size.height -= 4;
2686           } else {
2687             if (aFrame->StyleVisibility()->mDirection !=
2688                 NS_STYLE_DIRECTION_RTL) {
2689               macRect.origin.x += 4;
2690             }
2691             macRect.size.width -= 4;
2692           }
2693         }
2694         const BOOL isOnTopOfDarkBackground = IsDarkBackground(aFrame);
2695         // Scrollbar thumbs have a too high minimum width when rendered through
2696         // NSAppearance on 10.10, so we call RenderWithCoreUILegacy here.
2697         RenderWithCoreUILegacy(macRect, cgContext,
2698                 [NSDictionary dictionaryWithObjectsAndKeys:
2699                   (isOverlay ? @"kCUIWidgetOverlayScrollBar" : @"scrollbar"), @"widget",
2700                   @"regular", @"size",
2701                   (isRolledOver ? @"rollover" : @"normal"), @"state",
2702                   (isHorizontal ? @"kCUIOrientHorizontal" : @"kCUIOrientVertical"), @"kCUIOrientationKey",
2703                   (isOnTopOfDarkBackground ? @"kCUIVariantWhite" : @""), @"kCUIVariantKey",
2704                   [NSNumber numberWithBool:YES], @"indiconly",
2705                   [NSNumber numberWithBool:YES], @"kCUIThumbProportionKey",
2706                   [NSNumber numberWithBool:YES], @"is.flipped",
2707                   nil]);
2708       }
2709       break;
2710     case NS_THEME_SCROLLBAR_BUTTON_UP:
2711     case NS_THEME_SCROLLBAR_BUTTON_LEFT:
2712 #if SCROLLBARS_VISUAL_DEBUG
2713       CGContextSetRGBFillColor(cgContext, 1.0, 0, 0, 0.6);
2714       CGContextFillRect(cgContext, macRect);
2715 #endif
2716     break;
2717     case NS_THEME_SCROLLBAR_BUTTON_DOWN:
2718     case NS_THEME_SCROLLBAR_BUTTON_RIGHT:
2719 #if SCROLLBARS_VISUAL_DEBUG
2720       CGContextSetRGBFillColor(cgContext, 0, 1.0, 0, 0.6);
2721       CGContextFillRect(cgContext, macRect);
2722 #endif
2723     break;
2724     case NS_THEME_SCROLLBAR_TRACK_HORIZONTAL:
2725     case NS_THEME_SCROLLBAR_TRACK_VERTICAL:
2726       if (ScrollbarTrackAndThumbDrawSeparately()) {
2727         BOOL isOverlay = nsLookAndFeel::UseOverlayScrollbars();
2728         if (!isOverlay || IsParentScrollbarRolledOver(aFrame)) {
2729           BOOL isHorizontal = (aWidgetType == NS_THEME_SCROLLBAR_TRACK_HORIZONTAL);
2730           if (isOverlay && !nsCocoaFeatures::OnMountainLionOrLater()) {
2731             // On OSX 10.7, scrollbars don't grow when hovered.
2732             // The adjustments below were obtained by trial and error.
2733             if (isHorizontal) {
2734               macRect.origin.y += 2.0;
2735             } else {
2736               if (aFrame->StyleVisibility()->mDirection !=
2737                     NS_STYLE_DIRECTION_RTL) {
2738                 macRect.origin.x += 3.0;
2739               } else {
2740                 macRect.origin.x -= 1.0;
2741               }
2742             }
2743           }
2744           const BOOL isOnTopOfDarkBackground = IsDarkBackground(aFrame);
2745           RenderWithCoreUILegacy(macRect, cgContext,
2746                   [NSDictionary dictionaryWithObjectsAndKeys:
2747                     (isOverlay ? @"kCUIWidgetOverlayScrollBar" : @"scrollbar"), @"widget",
2748                     @"regular", @"size",
2749                     (isHorizontal ? @"kCUIOrientHorizontal" : @"kCUIOrientVertical"), @"kCUIOrientationKey",
2750                     (isOnTopOfDarkBackground ? @"kCUIVariantWhite" : @""), @"kCUIVariantKey",
2751                     [NSNumber numberWithBool:YES], @"noindicator",
2752                     [NSNumber numberWithBool:YES], @"kCUIThumbProportionKey",
2753                     [NSNumber numberWithBool:YES], @"is.flipped",
2754                     nil]);
2755         }
2756       }
2757       break;
2759     case NS_THEME_TEXTFIELD_MULTILINE: {
2760       // we have to draw this by hand because there is no HITheme value for it
2761       CGContextSetRGBFillColor(cgContext, 1.0, 1.0, 1.0, 1.0);
2762       
2763       CGContextFillRect(cgContext, macRect);
2765       CGContextSetLineWidth(cgContext, 1.0);
2766       CGContextSetShouldAntialias(cgContext, false);
2768       // stroke everything but the top line of the text area
2769       CGContextSetRGBStrokeColor(cgContext, 0.6, 0.6, 0.6, 1.0);
2770       CGContextBeginPath(cgContext);
2771       CGContextMoveToPoint(cgContext, macRect.origin.x, macRect.origin.y + 1);
2772       CGContextAddLineToPoint(cgContext, macRect.origin.x, macRect.origin.y + macRect.size.height);
2773       CGContextAddLineToPoint(cgContext, macRect.origin.x + macRect.size.width - 1, macRect.origin.y + macRect.size.height);
2774       CGContextAddLineToPoint(cgContext, macRect.origin.x + macRect.size.width - 1, macRect.origin.y + 1);
2775       CGContextStrokePath(cgContext);
2777       // stroke the line across the top of the text area
2778       CGContextSetRGBStrokeColor(cgContext, 0.4510, 0.4510, 0.4510, 1.0);
2779       CGContextBeginPath(cgContext);
2780       CGContextMoveToPoint(cgContext, macRect.origin.x, macRect.origin.y + 1);
2781       CGContextAddLineToPoint(cgContext, macRect.origin.x + macRect.size.width - 1, macRect.origin.y + 1);
2782       CGContextStrokePath(cgContext);
2784       // draw a focus ring
2785       if (eventState.HasState(NS_EVENT_STATE_FOCUS)) {
2786         // We need to bring the rectangle in by 1 pixel on each side.
2787         CGRect cgr = CGRectMake(macRect.origin.x + 1,
2788                                 macRect.origin.y + 1,
2789                                 macRect.size.width - 2,
2790                                 macRect.size.height - 2);
2791         HIThemeDrawFocusRect(&cgr, true, cgContext, kHIThemeOrientationNormal);
2792       }
2793     }
2794       break;
2796     case NS_THEME_LISTBOX: {
2797       // We have to draw this by hand because kHIThemeFrameListBox drawing
2798       // is buggy on 10.5, see bug 579259.
2799       CGContextSetRGBFillColor(cgContext, 1.0, 1.0, 1.0, 1.0);
2800       CGContextFillRect(cgContext, macRect);
2802       // #8E8E8E for the top border, #BEBEBE for the rest.
2803       float x = macRect.origin.x, y = macRect.origin.y;
2804       float w = macRect.size.width, h = macRect.size.height;
2805       CGContextSetRGBFillColor(cgContext, 0.557, 0.557, 0.557, 1.0);
2806       CGContextFillRect(cgContext, CGRectMake(x, y, w, 1));
2807       CGContextSetRGBFillColor(cgContext, 0.745, 0.745, 0.745, 1.0);
2808       CGContextFillRect(cgContext, CGRectMake(x, y + 1, 1, h - 1));
2809       CGContextFillRect(cgContext, CGRectMake(x + w - 1, y + 1, 1, h - 1));
2810       CGContextFillRect(cgContext, CGRectMake(x + 1, y + h - 1, w - 2, 1));
2811     }
2812       break;
2813     
2814     case NS_THEME_TAB:
2815       DrawSegment(cgContext, macRect, eventState, aFrame, tabRenderSettings);
2816       break;
2818     case NS_THEME_TAB_PANELS:
2819       DrawTabPanel(cgContext, macRect, aFrame);
2820       break;
2822     case NS_THEME_RESIZER:
2823       DrawResizer(cgContext, macRect, aFrame);
2824       break;
2826     case NS_THEME_MAC_VIBRANCY_LIGHT:
2827     case NS_THEME_MAC_VIBRANCY_DARK:
2828     {
2829       NSWindow* win = NativeWindowForFrame(aFrame);
2830       if ([win isKindOfClass:[ToolbarWindow class]]) {
2831         NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
2832         [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:cgContext flipped:YES]];
2834         ChildView* childView = [(ToolbarWindow*)win mainChildView];
2835         [[childView vibrancyFillColorForWidgetType:aWidgetType] set];
2836         NSRectFill(NSRectFromCGRect(macRect));
2838         [NSGraphicsContext setCurrentContext:savedContext];
2839       }
2840       break;
2841     }
2842   }
2844   nativeDrawing.EndNativeDrawing();
2846   return NS_OK;
2848   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
2851 nsIntMargin
2852 nsNativeThemeCocoa::RTLAwareMargin(const nsIntMargin& aMargin, nsIFrame* aFrame)
2854   if (IsFrameRTL(aFrame)) {
2855     // Return a copy of aMargin w/ right & left reversed:
2856     return nsIntMargin(aMargin.top, aMargin.left,
2857                        aMargin.bottom, aMargin.right);
2858   }
2860   return aMargin;
2863 static const nsIntMargin kAquaDropdownBorder(1, 22, 2, 5);
2864 static const nsIntMargin kAquaComboboxBorder(3, 20, 3, 4);
2865 static const nsIntMargin kAquaSearchfieldBorder(3, 5, 2, 19);
2867 NS_IMETHODIMP
2868 nsNativeThemeCocoa::GetWidgetBorder(nsDeviceContext* aContext, 
2869                                     nsIFrame* aFrame,
2870                                     uint8_t aWidgetType,
2871                                     nsIntMargin* aResult)
2873   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
2875   aResult->SizeTo(0, 0, 0, 0);
2877   switch (aWidgetType) {
2878     case NS_THEME_BUTTON:
2879     {
2880       if (IsButtonTypeMenu(aFrame)) {
2881         *aResult = RTLAwareMargin(kAquaDropdownBorder, aFrame);
2882       } else {
2883         aResult->SizeTo(1, 7, 3, 7);
2884       }
2885       break;
2886     }
2888     case NS_THEME_TOOLBAR_BUTTON:
2889     {
2890       aResult->SizeTo(1, 4, 1, 4);
2891       break;
2892     }
2894     case NS_THEME_CHECKBOX:
2895     case NS_THEME_RADIO:
2896     {
2897       // nsFormControlFrame::GetIntrinsicWidth and nsFormControlFrame::GetIntrinsicHeight
2898       // assume a border width of 2px.
2899       aResult->SizeTo(2, 2, 2, 2);
2900       break;
2901     }
2903     case NS_THEME_DROPDOWN:
2904     case NS_THEME_DROPDOWN_BUTTON:
2905       *aResult = RTLAwareMargin(kAquaDropdownBorder, aFrame);
2906       break;
2908     case NS_THEME_DROPDOWN_TEXTFIELD:
2909       *aResult = RTLAwareMargin(kAquaComboboxBorder, aFrame);
2910       break;
2912     case NS_THEME_NUMBER_INPUT:
2913     case NS_THEME_TEXTFIELD:
2914     {
2915       SInt32 frameOutset = 0;
2916       ::GetThemeMetric(kThemeMetricEditTextFrameOutset, &frameOutset);
2918       SInt32 textPadding = 0;
2919       ::GetThemeMetric(kThemeMetricEditTextWhitespace, &textPadding);
2921       frameOutset += textPadding;
2923       aResult->SizeTo(frameOutset, frameOutset, frameOutset, frameOutset);
2924       break;
2925     }
2927     case NS_THEME_TEXTFIELD_MULTILINE:
2928       aResult->SizeTo(1, 1, 1, 1);
2929       break;
2931     case NS_THEME_SEARCHFIELD:
2932       *aResult = RTLAwareMargin(kAquaSearchfieldBorder, aFrame);
2933       break;
2935     case NS_THEME_LISTBOX:
2936     {
2937       SInt32 frameOutset = 0;
2938       ::GetThemeMetric(kThemeMetricListBoxFrameOutset, &frameOutset);
2939       aResult->SizeTo(frameOutset, frameOutset, frameOutset, frameOutset);
2940       break;
2941     }
2943     case NS_THEME_SCROLLBAR_TRACK_HORIZONTAL:
2944     case NS_THEME_SCROLLBAR_TRACK_VERTICAL:
2945     {
2946       bool isHorizontal = (aWidgetType == NS_THEME_SCROLLBAR_TRACK_HORIZONTAL);
2948       // On Lion and later, scrollbars have no arrows.
2949       if (!nsCocoaFeatures::OnLionOrLater()) {
2950         // There's only an endcap to worry about when both arrows are on the bottom
2951         NSString *buttonPlacement = [[NSUserDefaults standardUserDefaults] objectForKey:@"AppleScrollBarVariant"];
2952         if (!buttonPlacement || [buttonPlacement isEqualToString:@"DoubleMax"]) {
2953           nsIFrame *scrollbarFrame = GetParentScrollbarFrame(aFrame);
2954           if (!scrollbarFrame) return NS_ERROR_FAILURE;
2955           bool isSmall = (scrollbarFrame->StyleDisplay()->mAppearance == NS_THEME_SCROLLBAR_SMALL);
2957           // There isn't a metric for this, so just hardcode a best guess at the value.
2958           // This value is even less exact due to the fact that the endcap is partially concave.
2959           int32_t endcapSize = isSmall ? 5 : 6;
2961           if (isHorizontal)
2962             aResult->SizeTo(0, 0, 0, endcapSize);
2963           else
2964             aResult->SizeTo(endcapSize, 0, 0, 0);
2965         }
2966       }
2968       if (nsLookAndFeel::UseOverlayScrollbars()) {
2969         if (isHorizontal) {
2970           aResult->SizeTo(2, 1, 1, 1);
2971         } else {
2972           aResult->SizeTo(1, 1, 1, 2);
2973         }
2974       }
2976       break;
2977     }
2979     case NS_THEME_STATUSBAR:
2980       aResult->SizeTo(1, 0, 0, 0);
2981       break;
2982   }
2984   if (IsHiDPIContext(aFrame->PresContext())) {
2985     *aResult = *aResult + *aResult; // doubled
2986   }
2988   return NS_OK;
2990   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
2993 // Return false here to indicate that CSS padding values should be used. There is
2994 // no reason to make a distinction between padding and border values, just specify
2995 // whatever values you want in GetWidgetBorder and only use this to return true
2996 // if you want to override CSS padding values.
2997 bool
2998 nsNativeThemeCocoa::GetWidgetPadding(nsDeviceContext* aContext, 
2999                                      nsIFrame* aFrame,
3000                                      uint8_t aWidgetType,
3001                                      nsIntMargin* aResult)
3003   // We don't want CSS padding being used for certain widgets.
3004   // See bug 381639 for an example of why.
3005   switch (aWidgetType) {
3006     case NS_THEME_BUTTON:
3007     // Radios and checkboxes return a fixed size in GetMinimumWidgetSize
3008     // and have a meaningful baseline, so they can't have
3009     // author-specified padding.
3010     case NS_THEME_CHECKBOX:
3011     case NS_THEME_RADIO:
3012       aResult->SizeTo(0, 0, 0, 0);
3013       return true;
3014   }
3015   return false;
3018 bool
3019 nsNativeThemeCocoa::GetWidgetOverflow(nsDeviceContext* aContext, nsIFrame* aFrame,
3020                                       uint8_t aWidgetType, nsRect* aOverflowRect)
3022   int32_t p2a = aFrame->PresContext()->AppUnitsPerDevPixel();
3023   switch (aWidgetType) {
3024     case NS_THEME_BUTTON:
3025     case NS_THEME_MOZ_MAC_HELP_BUTTON:
3026     case NS_THEME_TOOLBAR_BUTTON:
3027     case NS_THEME_NUMBER_INPUT:
3028     case NS_THEME_TEXTFIELD:
3029     case NS_THEME_TEXTFIELD_MULTILINE:
3030     case NS_THEME_SEARCHFIELD:
3031     case NS_THEME_LISTBOX:
3032     case NS_THEME_DROPDOWN:
3033     case NS_THEME_DROPDOWN_BUTTON:
3034     case NS_THEME_DROPDOWN_TEXTFIELD:
3035     case NS_THEME_CHECKBOX:
3036     case NS_THEME_RADIO:
3037     case NS_THEME_TAB:
3038     {
3039       // We assume that the above widgets can draw a focus ring that will be less than
3040       // or equal to 4 pixels thick.
3041       nsIntMargin extraSize = nsIntMargin(MAX_FOCUS_RING_WIDTH,
3042                                           MAX_FOCUS_RING_WIDTH,
3043                                           MAX_FOCUS_RING_WIDTH,
3044                                           MAX_FOCUS_RING_WIDTH);
3045       nsMargin m(NSIntPixelsToAppUnits(extraSize.top, p2a),
3046                  NSIntPixelsToAppUnits(extraSize.right, p2a),
3047                  NSIntPixelsToAppUnits(extraSize.bottom, p2a),
3048                  NSIntPixelsToAppUnits(extraSize.left, p2a));
3049       aOverflowRect->Inflate(m);
3050       return true;
3051     }
3052     case NS_THEME_PROGRESSBAR:
3053     {
3054       // Progress bars draw a 2 pixel white shadow under their progress indicators
3055       nsMargin m(0, 0, NSIntPixelsToAppUnits(2, p2a), 0);
3056       aOverflowRect->Inflate(m);
3057       return true;
3058     }
3059     case NS_THEME_FOCUS_OUTLINE:
3060     {
3061       aOverflowRect->Inflate(NSIntPixelsToAppUnits(2, p2a));
3062       return true;
3063     }
3064   }
3066   return false;
3069 static const int32_t kRegularScrollbarThumbMinSize = 26;
3070 static const int32_t kSmallScrollbarThumbMinSize = 26;
3072 NS_IMETHODIMP
3073 nsNativeThemeCocoa::GetMinimumWidgetSize(nsPresContext* aPresContext,
3074                                          nsIFrame* aFrame,
3075                                          uint8_t aWidgetType,
3076                                          nsIntSize* aResult,
3077                                          bool* aIsOverridable)
3079   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
3081   aResult->SizeTo(0,0);
3082   *aIsOverridable = true;
3084   switch (aWidgetType) {
3085     case NS_THEME_BUTTON:
3086     {
3087       aResult->SizeTo(pushButtonSettings.minimumSizes[miniControlSize].width,
3088                       pushButtonSettings.naturalSizes[miniControlSize].height);
3089       break;
3090     }
3092     case NS_THEME_MOZ_MAC_HELP_BUTTON:
3093     {
3094       aResult->SizeTo(kHelpButtonSize.width, kHelpButtonSize.height);
3095       *aIsOverridable = false;
3096       break;
3097     }
3099     case NS_THEME_TOOLBAR_BUTTON:
3100     {
3101       aResult->SizeTo(0, toolbarButtonHeights[miniControlSize]);
3102       break;
3103     }
3105     case NS_THEME_SPINNER:
3106     case NS_THEME_SPINNER_UP_BUTTON:
3107     case NS_THEME_SPINNER_DOWN_BUTTON:
3108     {
3109       SInt32 buttonHeight = 0, buttonWidth = 0;
3110       if (aFrame->GetContent()->IsXUL()) {
3111         ::GetThemeMetric(kThemeMetricLittleArrowsWidth, &buttonWidth);
3112         ::GetThemeMetric(kThemeMetricLittleArrowsHeight, &buttonHeight);
3113       } else {
3114         NSSize size =
3115           spinnerSettings.minimumSizes[EnumSizeForCocoaSize(NSMiniControlSize)];
3116         buttonWidth = size.width;
3117         buttonHeight = size.height;
3118         if (aWidgetType != NS_THEME_SPINNER) {
3119           // the buttons are half the height of the spinner
3120           buttonHeight /= 2;
3121         }
3122       }
3123       aResult->SizeTo(buttonWidth, buttonHeight);
3124       *aIsOverridable = true;
3125       break;
3126     }
3128     case NS_THEME_DROPDOWN:
3129     case NS_THEME_DROPDOWN_BUTTON:
3130     {
3131       SInt32 popupHeight = 0;
3132       ::GetThemeMetric(kThemeMetricPopupButtonHeight, &popupHeight);
3133       aResult->SizeTo(0, popupHeight);
3134       break;
3135     }
3137     case NS_THEME_NUMBER_INPUT:
3138     case NS_THEME_TEXTFIELD:
3139     case NS_THEME_TEXTFIELD_MULTILINE:
3140     case NS_THEME_SEARCHFIELD:
3141     {
3142       // at minimum, we should be tall enough for 9pt text.
3143       // I'm using hardcoded values here because the appearance manager
3144       // values for the frame size are incorrect.
3145       aResult->SizeTo(0, (2 + 2) /* top */ + 9 + (1 + 1) /* bottom */);
3146       break;
3147     }
3149     case NS_THEME_WINDOW_BUTTON_BOX: {
3150       NSSize size = WindowButtonsSize(aFrame);
3151       aResult->SizeTo(size.width, size.height);
3152       *aIsOverridable = false;
3153       break;
3154     }
3156     case NS_THEME_MOZ_MAC_FULLSCREEN_BUTTON: {
3157       if ([NativeWindowForFrame(aFrame) respondsToSelector:@selector(toggleFullScreen:)] &&
3158           !nsCocoaFeatures::OnYosemiteOrLater()) {
3159         // This value is hardcoded because it's needed before we can measure the
3160         // position and size of the fullscreen button.
3161         aResult->SizeTo(16, 17);
3162       }
3163       *aIsOverridable = false;
3164       break;
3165     }
3167     case NS_THEME_PROGRESSBAR:
3168     {
3169       SInt32 barHeight = 0;
3170       ::GetThemeMetric(kThemeMetricNormalProgressBarThickness, &barHeight);
3171       aResult->SizeTo(0, barHeight);
3172       break;
3173     }
3175     case NS_THEME_TREEVIEW_TWISTY:
3176     case NS_THEME_TREEVIEW_TWISTY_OPEN:   
3177     {
3178       SInt32 twistyHeight = 0, twistyWidth = 0;
3179       ::GetThemeMetric(kThemeMetricDisclosureButtonWidth, &twistyWidth);
3180       ::GetThemeMetric(kThemeMetricDisclosureButtonHeight, &twistyHeight);
3181       aResult->SizeTo(twistyWidth, twistyHeight);
3182       *aIsOverridable = false;
3183       break;
3184     }
3185     
3186     case NS_THEME_TREEVIEW_HEADER:
3187     case NS_THEME_TREEVIEW_HEADER_CELL:
3188     {
3189       SInt32 headerHeight = 0;
3190       ::GetThemeMetric(kThemeMetricListHeaderHeight, &headerHeight);
3191       aResult->SizeTo(0, headerHeight - 1); // We don't need the top border.
3192       break;
3193     }
3195     case NS_THEME_TAB:
3196     {
3197       aResult->SizeTo(0, tabHeights[miniControlSize]);
3198       break;
3199     }
3201     case NS_THEME_RANGE:
3202     {
3203       // The Mac Appearance Manager API (the old API we're currently using)
3204       // doesn't define constants to obtain a minimum size for sliders. We use
3205       // the "thickness" of a slider that has default dimensions for both the
3206       // minimum width and height to get something sane and so that paint
3207       // invalidation works.
3208       SInt32 size = 0;
3209       if (IsRangeHorizontal(aFrame)) {
3210         ::GetThemeMetric(kThemeMetricHSliderHeight, &size);
3211       } else {
3212         ::GetThemeMetric(kThemeMetricVSliderWidth, &size);
3213       }
3214       aResult->SizeTo(size, size);
3215       *aIsOverridable = true;
3216       break;
3217     }
3219     case NS_THEME_RANGE_THUMB:
3220     {
3221       SInt32 width = 0;
3222       SInt32 height = 0;
3223       ::GetThemeMetric(kThemeMetricSliderMinThumbWidth, &width);
3224       ::GetThemeMetric(kThemeMetricSliderMinThumbHeight, &height);
3225       aResult->SizeTo(width, height);
3226       *aIsOverridable = false;
3227       break;
3228     }
3230     case NS_THEME_SCALE_HORIZONTAL:
3231     {
3232       SInt32 scaleHeight = 0;
3233       ::GetThemeMetric(kThemeMetricHSliderHeight, &scaleHeight);
3234       aResult->SizeTo(scaleHeight, scaleHeight);
3235       *aIsOverridable = false;
3236       break;
3237     }
3239     case NS_THEME_SCALE_VERTICAL:
3240     {
3241       SInt32 scaleWidth = 0;
3242       ::GetThemeMetric(kThemeMetricVSliderWidth, &scaleWidth);
3243       aResult->SizeTo(scaleWidth, scaleWidth);
3244       *aIsOverridable = false;
3245       break;
3246     }
3248     case NS_THEME_SCROLLBAR_THUMB_HORIZONTAL:
3249     case NS_THEME_SCROLLBAR_THUMB_VERTICAL:
3250     {
3251       // Find our parent scrollbar frame in order to find out whether we're in
3252       // a small or a large scrollbar.
3253       nsIFrame *scrollbarFrame = GetParentScrollbarFrame(aFrame);
3254       if (!scrollbarFrame)
3255         return NS_ERROR_FAILURE;
3257       bool isSmall = (scrollbarFrame->StyleDisplay()->mAppearance == NS_THEME_SCROLLBAR_SMALL);
3258       bool isHorizontal = (aWidgetType == NS_THEME_SCROLLBAR_THUMB_HORIZONTAL);
3259       int32_t& minSize = isHorizontal ? aResult->width : aResult->height;
3260       minSize = isSmall ? kSmallScrollbarThumbMinSize : kRegularScrollbarThumbMinSize;
3261       break;
3262     }
3264     case NS_THEME_SCROLLBAR:
3265     case NS_THEME_SCROLLBAR_SMALL:
3266     case NS_THEME_SCROLLBAR_TRACK_VERTICAL:
3267     case NS_THEME_SCROLLBAR_TRACK_HORIZONTAL:
3268     {
3269       *aIsOverridable = false;
3271       if (nsLookAndFeel::UseOverlayScrollbars()) {
3272         nsIFrame* scrollbarFrame = GetParentScrollbarFrame(aFrame);
3273         if (scrollbarFrame &&
3274             scrollbarFrame->StyleDisplay()->mAppearance ==
3275               NS_THEME_SCROLLBAR_SMALL) {
3276           aResult->SizeTo(14, 14);
3277         }
3278         else {
3279           aResult->SizeTo(16, 16);
3280         }
3281         break;
3282       }
3284       // yeah, i know i'm cheating a little here, but i figure that it
3285       // really doesn't matter if the scrollbar is vertical or horizontal
3286       // and the width metric is a really good metric for every piece
3287       // of the scrollbar.
3289       nsIFrame *scrollbarFrame = GetParentScrollbarFrame(aFrame);
3290       if (!scrollbarFrame) return NS_ERROR_FAILURE;
3292       int32_t themeMetric = (scrollbarFrame->StyleDisplay()->mAppearance == NS_THEME_SCROLLBAR_SMALL) ?
3293                             kThemeMetricSmallScrollBarWidth :
3294                             kThemeMetricScrollBarWidth;
3295       SInt32 scrollbarWidth = 0;
3296       ::GetThemeMetric(themeMetric, &scrollbarWidth);
3297       aResult->SizeTo(scrollbarWidth, scrollbarWidth);
3298       break;
3299     }
3301     case NS_THEME_SCROLLBAR_NON_DISAPPEARING:
3302     {
3303       int32_t themeMetric = kThemeMetricScrollBarWidth;
3305       if (aFrame) {
3306         nsIFrame* scrollbarFrame = GetParentScrollbarFrame(aFrame);
3307         if (scrollbarFrame &&
3308             scrollbarFrame->StyleDisplay()->mAppearance ==
3309             NS_THEME_SCROLLBAR_SMALL) {
3310           // XXX We're interested in the width of non-disappearing scrollbars
3311           // to leave enough space for a dropmarker in non-native styled
3312           // comboboxes (bug 869314). It isn't clear to me if comboboxes can
3313           // ever have small scrollbars.
3314           themeMetric = kThemeMetricSmallScrollBarWidth;
3315         }
3316       }
3318       SInt32 scrollbarWidth = 0;
3319       ::GetThemeMetric(themeMetric, &scrollbarWidth);
3320       aResult->SizeTo(scrollbarWidth, scrollbarWidth);
3321       break;
3322     }
3324     case NS_THEME_SCROLLBAR_BUTTON_UP:
3325     case NS_THEME_SCROLLBAR_BUTTON_DOWN:
3326     case NS_THEME_SCROLLBAR_BUTTON_LEFT:
3327     case NS_THEME_SCROLLBAR_BUTTON_RIGHT:
3328     {
3329       nsIFrame *scrollbarFrame = GetParentScrollbarFrame(aFrame);
3330       if (!scrollbarFrame) return NS_ERROR_FAILURE;
3332       // Since there is no NS_THEME_SCROLLBAR_BUTTON_UP_SMALL we need to ask the parent what appearance style it has.
3333       int32_t themeMetric = (scrollbarFrame->StyleDisplay()->mAppearance == NS_THEME_SCROLLBAR_SMALL) ?
3334                             kThemeMetricSmallScrollBarWidth :
3335                             kThemeMetricScrollBarWidth;
3336       SInt32 scrollbarWidth = 0;
3337       ::GetThemeMetric(themeMetric, &scrollbarWidth);
3339       // It seems that for both sizes of scrollbar, the buttons are one pixel "longer".
3340       if (aWidgetType == NS_THEME_SCROLLBAR_BUTTON_LEFT || aWidgetType == NS_THEME_SCROLLBAR_BUTTON_RIGHT)
3341         aResult->SizeTo(scrollbarWidth+1, scrollbarWidth);
3342       else
3343         aResult->SizeTo(scrollbarWidth, scrollbarWidth+1);
3345       *aIsOverridable = false;
3346       break;
3347     }
3348     case NS_THEME_RESIZER:
3349     {
3350       HIThemeGrowBoxDrawInfo drawInfo;
3351       drawInfo.version = 0;
3352       drawInfo.state = kThemeStateActive;
3353       drawInfo.kind = kHIThemeGrowBoxKindNormal;
3354       drawInfo.direction = kThemeGrowRight | kThemeGrowDown;
3355       drawInfo.size = kHIThemeGrowBoxSizeNormal;
3356       HIPoint pnt = { 0, 0 };
3357       HIRect bounds;
3358       HIThemeGetGrowBoxBounds(&pnt, &drawInfo, &bounds);
3359       aResult->SizeTo(bounds.size.width, bounds.size.height);
3360       *aIsOverridable = false;
3361     }
3362   }
3364   if (IsHiDPIContext(aPresContext)) {
3365     *aResult = *aResult * 2;
3366   }
3368   return NS_OK;
3370   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
3373 NS_IMETHODIMP
3374 nsNativeThemeCocoa::WidgetStateChanged(nsIFrame* aFrame, uint8_t aWidgetType, 
3375                                      nsIAtom* aAttribute, bool* aShouldRepaint)
3377   // Some widget types just never change state.
3378   switch (aWidgetType) {
3379     case NS_THEME_WINDOW_TITLEBAR:
3380     case NS_THEME_TOOLBOX:
3381     case NS_THEME_TOOLBAR:
3382     case NS_THEME_MOZ_MAC_UNIFIED_TOOLBAR:
3383     case NS_THEME_STATUSBAR:
3384     case NS_THEME_STATUSBAR_PANEL:
3385     case NS_THEME_STATUSBAR_RESIZER_PANEL:
3386     case NS_THEME_TOOLTIP:
3387     case NS_THEME_TAB_PANELS:
3388     case NS_THEME_TAB_PANEL:
3389     case NS_THEME_DIALOG:
3390     case NS_THEME_MENUPOPUP:
3391     case NS_THEME_GROUPBOX:
3392     case NS_THEME_PROGRESSBAR_CHUNK:
3393     case NS_THEME_PROGRESSBAR_CHUNK_VERTICAL:
3394     case NS_THEME_PROGRESSBAR:
3395     case NS_THEME_PROGRESSBAR_VERTICAL:
3396     case NS_THEME_METERBAR:
3397     case NS_THEME_METERBAR_CHUNK:
3398       *aShouldRepaint = false;
3399       return NS_OK;
3400   }
3402   // XXXdwh Not sure what can really be done here.  Can at least guess for
3403   // specific widgets that they're highly unlikely to have certain states.
3404   // For example, a toolbar doesn't care about any states.
3405   if (!aAttribute) {
3406     // Hover/focus/active changed.  Always repaint.
3407     *aShouldRepaint = true;
3408   } else {
3409     // Check the attribute to see if it's relevant.  
3410     // disabled, checked, dlgtype, default, etc.
3411     *aShouldRepaint = false;
3412     if (aAttribute == nsGkAtoms::disabled ||
3413         aAttribute == nsGkAtoms::checked ||
3414         aAttribute == nsGkAtoms::selected ||
3415         aAttribute == nsGkAtoms::menuactive ||
3416         aAttribute == nsGkAtoms::sortDirection ||
3417         aAttribute == nsGkAtoms::focused ||
3418         aAttribute == nsGkAtoms::_default ||
3419         aAttribute == nsGkAtoms::open ||
3420         aAttribute == nsGkAtoms::hover)
3421       *aShouldRepaint = true;
3423     if ((aWidgetType == NS_THEME_SCROLLBAR ||
3424          aWidgetType == NS_THEME_SCROLLBAR_SMALL) &&
3425         !ScrollbarTrackAndThumbDrawSeparately() &&
3426         (aAttribute == nsGkAtoms::curpos ||
3427          aAttribute == nsGkAtoms::minpos ||
3428          aAttribute == nsGkAtoms::maxpos ||
3429          aAttribute == nsGkAtoms::pageincrement)) {
3430       // 10.6-style scrollbars paint the thumb as part of the scrollbar,
3431       // so we need to invalidate the scrollbar when the thumb moves.
3432       *aShouldRepaint = true;
3433     }
3434   }
3436   return NS_OK;
3439 NS_IMETHODIMP
3440 nsNativeThemeCocoa::ThemeChanged()
3442   // This is unimplemented because we don't care if gecko changes its theme
3443   // and Mac OS X doesn't have themes.
3444   return NS_OK;
3447 bool 
3448 nsNativeThemeCocoa::ThemeSupportsWidget(nsPresContext* aPresContext, nsIFrame* aFrame,
3449                                       uint8_t aWidgetType)
3451   // We don't have CSS set up to render non-native scrollbars on Mac OS X so we
3452   // render natively even if native theme support is disabled.
3453   if (aWidgetType != NS_THEME_SCROLLBAR &&
3454       aPresContext && !aPresContext->PresShell()->IsThemeSupportEnabled())
3455     return false;
3457   // if this is a dropdown button in a combobox the answer is always no
3458   if (aWidgetType == NS_THEME_DROPDOWN_BUTTON) {
3459     nsIFrame* parentFrame = aFrame->GetParent();
3460     if (parentFrame && (parentFrame->GetType() == nsGkAtoms::comboboxControlFrame))
3461       return false;
3462   }
3464   switch (aWidgetType) {
3465     case NS_THEME_LISTBOX:
3467     case NS_THEME_DIALOG:
3468     case NS_THEME_WINDOW:
3469     case NS_THEME_WINDOW_BUTTON_BOX:
3470     case NS_THEME_WINDOW_TITLEBAR:
3471     case NS_THEME_CHECKMENUITEM:
3472     case NS_THEME_MENUPOPUP:
3473     case NS_THEME_MENUITEM:
3474     case NS_THEME_MENUSEPARATOR:
3475     case NS_THEME_MOZ_MAC_FULLSCREEN_BUTTON:
3476     case NS_THEME_TOOLTIP:
3477     
3478     case NS_THEME_CHECKBOX:
3479     case NS_THEME_CHECKBOX_CONTAINER:
3480     case NS_THEME_RADIO:
3481     case NS_THEME_RADIO_CONTAINER:
3482     case NS_THEME_GROUPBOX:
3483     case NS_THEME_MOZ_MAC_HELP_BUTTON:
3484     case NS_THEME_BUTTON:
3485     case NS_THEME_BUTTON_BEVEL:
3486     case NS_THEME_TOOLBAR_BUTTON:
3487     case NS_THEME_SPINNER:
3488     case NS_THEME_SPINNER_UP_BUTTON:
3489     case NS_THEME_SPINNER_DOWN_BUTTON:
3490     case NS_THEME_TOOLBAR:
3491     case NS_THEME_MOZ_MAC_UNIFIED_TOOLBAR:
3492     case NS_THEME_STATUSBAR:
3493     case NS_THEME_NUMBER_INPUT:
3494     case NS_THEME_TEXTFIELD:
3495     case NS_THEME_TEXTFIELD_MULTILINE:
3496     case NS_THEME_SEARCHFIELD:
3497     //case NS_THEME_TOOLBOX:
3498     //case NS_THEME_TOOLBAR_BUTTON:
3499     case NS_THEME_PROGRESSBAR:
3500     case NS_THEME_PROGRESSBAR_VERTICAL:
3501     case NS_THEME_PROGRESSBAR_CHUNK:
3502     case NS_THEME_PROGRESSBAR_CHUNK_VERTICAL:
3503     case NS_THEME_METERBAR:
3504     case NS_THEME_METERBAR_CHUNK:
3505     case NS_THEME_TOOLBAR_SEPARATOR:
3506     
3507     case NS_THEME_TAB_PANELS:
3508     case NS_THEME_TAB:
3509     
3510     case NS_THEME_TREEVIEW_TWISTY:
3511     case NS_THEME_TREEVIEW_TWISTY_OPEN:
3512     case NS_THEME_TREEVIEW:
3513     case NS_THEME_TREEVIEW_HEADER:
3514     case NS_THEME_TREEVIEW_HEADER_CELL:
3515     case NS_THEME_TREEVIEW_HEADER_SORTARROW:
3516     case NS_THEME_TREEVIEW_TREEITEM:
3517     case NS_THEME_TREEVIEW_LINE:
3519     case NS_THEME_RANGE:
3521     case NS_THEME_SCALE_HORIZONTAL:
3522     case NS_THEME_SCALE_THUMB_HORIZONTAL:
3523     case NS_THEME_SCALE_VERTICAL:
3524     case NS_THEME_SCALE_THUMB_VERTICAL:
3526     case NS_THEME_SCROLLBAR:
3527     case NS_THEME_SCROLLBAR_SMALL:
3528     case NS_THEME_SCROLLBAR_BUTTON_UP:
3529     case NS_THEME_SCROLLBAR_BUTTON_DOWN:
3530     case NS_THEME_SCROLLBAR_BUTTON_LEFT:
3531     case NS_THEME_SCROLLBAR_BUTTON_RIGHT:
3532     case NS_THEME_SCROLLBAR_THUMB_HORIZONTAL:
3533     case NS_THEME_SCROLLBAR_THUMB_VERTICAL:
3534     case NS_THEME_SCROLLBAR_TRACK_VERTICAL:
3535     case NS_THEME_SCROLLBAR_TRACK_HORIZONTAL:
3536     case NS_THEME_SCROLLBAR_NON_DISAPPEARING:
3538     case NS_THEME_DROPDOWN:
3539     case NS_THEME_DROPDOWN_BUTTON:
3540     case NS_THEME_DROPDOWN_TEXT:
3541     case NS_THEME_DROPDOWN_TEXTFIELD:
3542       return !IsWidgetStyled(aPresContext, aFrame, aWidgetType);
3543       break;
3545     case NS_THEME_RESIZER:
3546     {
3547       nsIFrame* parentFrame = aFrame->GetParent();
3548       if (!parentFrame || parentFrame->GetType() != nsGkAtoms::scrollFrame)
3549         return true;
3551       // Note that IsWidgetStyled is not called for resizers on Mac. This is
3552       // because for scrollable containers, the native resizer looks better
3553       // when (non-overlay) scrollbars are present even when the style is
3554       // overriden, and the custom transparent resizer looks better when
3555       // scrollbars are not present.
3556       nsIScrollableFrame* scrollFrame = do_QueryFrame(parentFrame);
3557       return (!nsLookAndFeel::UseOverlayScrollbars() &&
3558               scrollFrame && scrollFrame->GetScrollbarVisibility());
3559       break;
3560     }
3561     case NS_THEME_FOCUS_OUTLINE:
3562       return true;
3564     case NS_THEME_MAC_VIBRANCY_LIGHT:
3565     case NS_THEME_MAC_VIBRANCY_DARK:
3566       return VibrancyManager::SystemSupportsVibrancy();
3567   }
3569   return false;
3572 bool
3573 nsNativeThemeCocoa::WidgetIsContainer(uint8_t aWidgetType)
3575   // flesh this out at some point
3576   switch (aWidgetType) {
3577    case NS_THEME_DROPDOWN_BUTTON:
3578    case NS_THEME_RADIO:
3579    case NS_THEME_CHECKBOX:
3580    case NS_THEME_PROGRESSBAR:
3581    case NS_THEME_METERBAR:
3582    case NS_THEME_RANGE:
3583    case NS_THEME_MOZ_MAC_HELP_BUTTON:
3584     return false;
3585     break;
3586   }
3587   return true;
3590 bool
3591 nsNativeThemeCocoa::ThemeDrawsFocusForWidget(uint8_t aWidgetType)
3593   if (aWidgetType == NS_THEME_DROPDOWN ||
3594       aWidgetType == NS_THEME_DROPDOWN_TEXTFIELD ||
3595       aWidgetType == NS_THEME_BUTTON ||
3596       aWidgetType == NS_THEME_MOZ_MAC_HELP_BUTTON ||
3597       aWidgetType == NS_THEME_RADIO ||
3598       aWidgetType == NS_THEME_RANGE ||
3599       aWidgetType == NS_THEME_CHECKBOX)
3600     return true;
3602   return false;
3605 bool
3606 nsNativeThemeCocoa::ThemeNeedsComboboxDropmarker()
3608   return false;
3611 bool
3612 nsNativeThemeCocoa::WidgetAppearanceDependsOnWindowFocus(uint8_t aWidgetType)
3614   switch (aWidgetType) {
3615     case NS_THEME_DIALOG:
3616     case NS_THEME_GROUPBOX:
3617     case NS_THEME_TAB_PANELS:
3618     case NS_THEME_CHECKMENUITEM:
3619     case NS_THEME_MENUPOPUP:
3620     case NS_THEME_MENUITEM:
3621     case NS_THEME_MENUSEPARATOR:
3622     case NS_THEME_TOOLTIP:
3623     case NS_THEME_SPINNER:
3624     case NS_THEME_SPINNER_UP_BUTTON:
3625     case NS_THEME_SPINNER_DOWN_BUTTON:
3626     case NS_THEME_TOOLBAR_SEPARATOR:
3627     case NS_THEME_TOOLBOX:
3628     case NS_THEME_NUMBER_INPUT:
3629     case NS_THEME_TEXTFIELD:
3630     case NS_THEME_TREEVIEW:
3631     case NS_THEME_TREEVIEW_LINE:
3632     case NS_THEME_TEXTFIELD_MULTILINE:
3633     case NS_THEME_LISTBOX:
3634     case NS_THEME_RESIZER:
3635       return false;
3636     default:
3637       return true;
3638   }
3641 bool
3642 nsNativeThemeCocoa::NeedToClearBackgroundBehindWidget(uint8_t aWidgetType)
3644   switch (aWidgetType) {
3645     case NS_THEME_MAC_VIBRANCY_LIGHT:
3646     case NS_THEME_MAC_VIBRANCY_DARK:
3647       return true;
3648     default:
3649       return false;
3650   }
3653 nsITheme::Transparency
3654 nsNativeThemeCocoa::GetWidgetTransparency(nsIFrame* aFrame, uint8_t aWidgetType)
3656   switch (aWidgetType) {
3657   case NS_THEME_MENUPOPUP:
3658   case NS_THEME_TOOLTIP:
3659     return eTransparent;
3661   case NS_THEME_SCROLLBAR_SMALL:
3662   case NS_THEME_SCROLLBAR:
3663     return nsLookAndFeel::UseOverlayScrollbars() ? eTransparent : eOpaque;
3665   case NS_THEME_STATUSBAR:
3666     // Knowing that scrollbars and statusbars are opaque improves
3667     // performance, because we create layers for them.
3668     return eOpaque;
3670   case NS_THEME_TOOLBAR:
3671   case NS_THEME_MOZ_MAC_UNIFIED_TOOLBAR:
3672     return eOpaque;
3674   default:
3675     return eUnknownTransparency;
3676   }