bug 818009 - canActivate: only click-to-play-type plugins are valid r=jaws
[gecko.git] / widget / cocoa / nsNativeThemeCocoa.mm
blobf352c841493c8e53f80f0e5aadc0d7adc377fa10
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 "nsRenderingContext.h"
9 #include "nsRect.h"
10 #include "nsSize.h"
11 #include "nsThemeConstants.h"
12 #include "nsIPresShell.h"
13 #include "nsPresContext.h"
14 #include "nsIContent.h"
15 #include "nsIDocument.h"
16 #include "nsIFrame.h"
17 #include "nsIAtom.h"
18 #include "nsEventStates.h"
19 #include "nsINameSpaceManager.h"
20 #include "nsPresContext.h"
21 #include "nsGkAtoms.h"
22 #include "nsCocoaFeatures.h"
23 #include "nsCocoaWindow.h"
24 #include "nsNativeThemeColors.h"
25 #include "nsIScrollableFrame.h"
26 #include "nsIDOMHTMLProgressElement.h"
27 #include "nsIDOMHTMLMeterElement.h"
28 #include "mozilla/dom/Element.h"
30 #include "gfxContext.h"
31 #include "gfxQuartzSurface.h"
32 #include "gfxQuartzNativeDrawing.h"
34 #define DRAW_IN_FRAME_DEBUG 0
35 #define SCROLLBARS_VISUAL_DEBUG 0
37 // private Quartz routines needed here
38 extern "C" {
39   CG_EXTERN void CGContextSetCTM(CGContextRef, CGAffineTransform);
42 // Workaround for NSCell control tint drawing
43 // Without this workaround, NSCells are always drawn with the clear control tint
44 // as long as they're not attached to an NSControl which is a subview of an active window.
45 // XXXmstange Why doesn't Webkit need this?
46 @implementation NSCell (ControlTintWorkaround)
47 - (int)_realControlTint { return [self controlTint]; }
48 @end
50 // The purpose of this class is to provide objects that can be used when drawing
51 // NSCells using drawWithFrame:inView: without causing any harm. The only
52 // messages that will be sent to such an object are "isFlipped" and
53 // "currentEditor": isFlipped needs to return YES in order to avoid drawing bugs
54 // on 10.4 (see bug 465069); currentEditor (which isn't even a method of
55 // NSView) will be called when drawing search fields, and we only provide it in
56 // order to prevent "unrecognized selector" exceptions.
57 // There's no need to pass the actual NSView that we're drawing into to
58 // drawWithFrame:inView:. What's more, doing so even causes unnecessary
59 // invalidations as soon as we draw a focusring!
60 @interface CellDrawView : NSView
62 @end;
64 @implementation CellDrawView
66 - (BOOL)isFlipped
68   return YES;
71 - (NSText*)currentEditor
73   return nil;
76 @end
78 /**
79  * NSProgressBarCell is used to draw progress bars of any size.
80  */
81 @interface NSProgressBarCell : NSCell
83     /*All instance variables are private*/
84     double mValue;
85     double mMax;
86     bool   mIsIndeterminate;
87     bool   mIsHorizontal;
90 - (void)setValue:(double)value;
91 - (double)value;
92 - (void)setMax:(double)max;
93 - (double)max;
94 - (void)setIndeterminate:(bool)aIndeterminate;
95 - (bool)isIndeterminate;
96 - (void)setHorizontal:(bool)aIsHorizontal;
97 - (bool)isHorizontal;
98 - (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView;
99 @end
101 @implementation NSProgressBarCell
103 - (void)setMax:(double)aMax
105   mMax = aMax;
108 - (double)max
110   return mMax;
113 - (void)setValue:(double)aValue
115   mValue = aValue;
118 - (double)value
120   return mValue;
123 - (void)setIndeterminate:(bool)aIndeterminate
125   mIsIndeterminate = aIndeterminate;
128 - (bool)isIndeterminate
130   return mIsIndeterminate;
133 - (void)setHorizontal:(bool)aIsHorizontal
135   mIsHorizontal = aIsHorizontal;
138 - (bool)isHorizontal
140   return mIsHorizontal;
143 - (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
145   CGContext* cgContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
147   HIThemeTrackDrawInfo tdi;
149   tdi.version = 0;
150   tdi.min = 0;
152   tdi.value = INT32_MAX * (mValue / mMax);
153   tdi.max = INT32_MAX;
154   tdi.bounds = NSRectToCGRect(cellFrame);
155   tdi.attributes = mIsHorizontal ? kThemeTrackHorizontal : 0;
156   tdi.enableState = [self controlTint] == NSClearControlTint ? kThemeTrackInactive
157                                                              : kThemeTrackActive;
159   NSControlSize size = [self controlSize];
160   if (size == NSRegularControlSize) {
161     tdi.kind = mIsIndeterminate ? kThemeLargeIndeterminateBar
162                                 : kThemeLargeProgressBar;
163   } else {
164     NS_ASSERTION(size == NSSmallControlSize,
165                  "We shouldn't have another size than small and regular for the moment");
166     tdi.kind = mIsIndeterminate ? kThemeMediumIndeterminateBar
167                                 : kThemeMediumProgressBar;
168   }
170   int32_t stepsPerSecond = mIsIndeterminate ? 60 : 30;
171   int32_t milliSecondsPerStep = 1000 / stepsPerSecond;
172   tdi.trackInfo.progress.phase = PR_IntervalToMilliseconds(PR_IntervalNow()) /
173                                  milliSecondsPerStep % 32;
175   HIThemeDrawTrack(&tdi, NULL, cgContext, kHIThemeOrientationNormal);
178 @end
180 @interface ContextAwareSearchFieldCell : NSSearchFieldCell
182   nsIFrame* mContext;
185 // setContext: stores the searchfield nsIFrame so that it can be consulted
186 // during painting. Please reset this by calling setContext:nullptr as soon as
187 // you're done with painting because we don't want to keep a dangling pointer.
188 - (void)setContext:(nsIFrame*)aContext;
189 @end
191 @implementation ContextAwareSearchFieldCell
193 - (id)initTextCell:(NSString*)aString
195   if ((self = [super initTextCell:aString])) {
196     mContext = nullptr;
197   }
198   return self;
201 - (void)setContext:(nsIFrame*)aContext
203   mContext = aContext;
206 static BOOL IsToolbarStyleContainer(nsIFrame* aFrame)
208   nsIContent* content = aFrame->GetContent();
209   if (!content)
210     return NO;
212   if (content->Tag() == nsGkAtoms::toolbar ||
213       content->Tag() == nsGkAtoms::toolbox ||
214       content->Tag() == nsGkAtoms::statusbar)
215     return YES;
217   switch (aFrame->GetStyleDisplay()->mAppearance) {
218     case NS_THEME_TOOLBAR:
219     case NS_THEME_MOZ_MAC_UNIFIED_TOOLBAR:
220     case NS_THEME_STATUSBAR:
221       return YES;
222     default:
223       return NO;
224   }
227 - (BOOL)_isToolbarMode
229   // On 10.7, searchfields have two different styles, depending on whether
230   // the searchfield is on top of of window chrome. This function is called on
231   // 10.7 during drawing in order to determine which style to use.
232   for (nsIFrame* frame = mContext; frame; frame = frame->GetParent()) {
233     if (IsToolbarStyleContainer(frame)) {
234       return YES;
235     }
236   }
237   return NO;
240 @end
242 // Workaround for Bug 542048
243 // On 64-bit, NSSearchFieldCells don't draw focus rings.
244 #if defined(__x86_64__)
246 static void DrawFocusRing(NSRect rect, float radius)
248   NSSetFocusRingStyle(NSFocusRingOnly);
249   NSBezierPath* path = [NSBezierPath bezierPath];
250   rect = NSInsetRect(rect, radius, radius);
251   [path appendBezierPathWithArcWithCenter:NSMakePoint(NSMinX(rect), NSMinY(rect)) radius:radius startAngle:180.0 endAngle:270.0];
252   [path appendBezierPathWithArcWithCenter:NSMakePoint(NSMaxX(rect), NSMinY(rect)) radius:radius startAngle:270.0 endAngle:360.0];
253   [path appendBezierPathWithArcWithCenter:NSMakePoint(NSMaxX(rect), NSMaxY(rect)) radius:radius startAngle:  0.0 endAngle: 90.0];
254   [path appendBezierPathWithArcWithCenter:NSMakePoint(NSMinX(rect), NSMaxY(rect)) radius:radius startAngle: 90.0 endAngle:180.0];
255   [path closePath];
256   [path fill];
259 @interface SearchFieldCellWithFocusRing : ContextAwareSearchFieldCell {} @end
261 @implementation SearchFieldCellWithFocusRing
263 - (void)drawWithFrame:(NSRect)rect inView:(NSView*)controlView
265   [super drawWithFrame:rect inView:controlView];
266   if ([self showsFirstResponder]) {
267     DrawFocusRing(rect, NSHeight(rect) / 2);
268   }
271 @end
272   
273 #endif
275 // Copied from nsLookAndFeel.h
276 // Apple hasn't defined a constant for scollbars with two arrows on each end, so we'll use this one.
277 static const int kThemeScrollBarArrowsBoth = 2;
279 #define HITHEME_ORIENTATION kHIThemeOrientationNormal
280 #define MAX_FOCUS_RING_WIDTH 4
282 // These enums are for indexing into the margin array.
283 enum {
284   leopardOS = 0
287 enum {
288   miniControlSize,
289   smallControlSize,
290   regularControlSize
293 enum {
294   leftMargin,
295   topMargin,
296   rightMargin,
297   bottomMargin
300 static int EnumSizeForCocoaSize(NSControlSize cocoaControlSize) {
301   if (cocoaControlSize == NSMiniControlSize)
302     return miniControlSize;
303   else if (cocoaControlSize == NSSmallControlSize)
304     return smallControlSize;
305   else
306     return regularControlSize;
309 static NSControlSize CocoaSizeForEnum(int32_t enumControlSize) {
310   if (enumControlSize == miniControlSize)
311     return NSMiniControlSize;
312   else if (enumControlSize == smallControlSize)
313     return NSSmallControlSize;
314   else
315     return NSRegularControlSize;
318 static NSString* CUIControlSizeForCocoaSize(NSControlSize aControlSize)
320   if (aControlSize == NSRegularControlSize)
321     return @"regular";
322   else if (aControlSize == NSSmallControlSize)
323     return @"small";
324   else
325     return @"mini";
328 static void InflateControlRect(NSRect* rect, NSControlSize cocoaControlSize, const float marginSet[][3][4])
330   if (!marginSet)
331     return;
333   static int osIndex = leopardOS;
334   int controlSize = EnumSizeForCocoaSize(cocoaControlSize);
335   const float* buttonMargins = marginSet[osIndex][controlSize];
336   rect->origin.x -= buttonMargins[leftMargin];
337   rect->origin.y -= buttonMargins[bottomMargin];
338   rect->size.width += buttonMargins[leftMargin] + buttonMargins[rightMargin];
339   rect->size.height += buttonMargins[bottomMargin] + buttonMargins[topMargin];
342 static NSWindow* NativeWindowForFrame(nsIFrame* aFrame,
343                                       nsIWidget** aTopLevelWidget = NULL)
345   if (!aFrame)
346     return nil;  
348   nsIWidget* widget = aFrame->GetNearestWidget();
349   if (!widget)
350     return nil;
352   nsIWidget* topLevelWidget = widget->GetTopLevelWidget();
353   if (aTopLevelWidget)
354     *aTopLevelWidget = topLevelWidget;
356   return (NSWindow*)topLevelWidget->GetNativeData(NS_NATIVE_WINDOW);
359 static BOOL FrameIsInActiveWindow(nsIFrame* aFrame)
361   nsIWidget* topLevelWidget = NULL;
362   NSWindow* win = NativeWindowForFrame(aFrame, &topLevelWidget);
363   if (!topLevelWidget || !win)
364     return YES;
366   // XUL popups, e.g. the toolbar customization popup, can't become key windows,
367   // but controls in these windows should still get the active look.
368   nsWindowType windowType;
369   topLevelWidget->GetWindowType(windowType);
370   if (windowType == eWindowType_popup)
371     return YES;
372   if ([win isSheet])
373     return [win isKeyWindow];
374   return [win isMainWindow] && ![win attachedSheet];
377 // Toolbar controls and content controls respond to different window
378 // activeness states.
379 static BOOL IsActive(nsIFrame* aFrame, BOOL aIsToolbarControl)
381   if (aIsToolbarControl)
382     return [NativeWindowForFrame(aFrame) isMainWindow];
383   return FrameIsInActiveWindow(aFrame);
386 NS_IMPL_ISUPPORTS_INHERITED1(nsNativeThemeCocoa, nsNativeTheme, nsITheme)
389 nsNativeThemeCocoa::nsNativeThemeCocoa()
391   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
393   // provide a local autorelease pool, as this is called during startup
394   // before the main event-loop pool is in place
395   nsAutoreleasePool pool;
397   mPushButtonCell = [[NSButtonCell alloc] initTextCell:nil];
398   [mPushButtonCell setButtonType:NSMomentaryPushInButton];
399   [mPushButtonCell setHighlightsBy:NSPushInCellMask];
401   mRadioButtonCell = [[NSButtonCell alloc] initTextCell:nil];
402   [mRadioButtonCell setButtonType:NSRadioButton];
404   mCheckboxCell = [[NSButtonCell alloc] initTextCell:nil];
405   [mCheckboxCell setButtonType:NSSwitchButton];
406   [mCheckboxCell setAllowsMixedState:YES];
408 #if defined(__x86_64__)
409   mSearchFieldCell = [[SearchFieldCellWithFocusRing alloc] initTextCell:@""];
410 #else
411   mSearchFieldCell = [[ContextAwareSearchFieldCell alloc] initTextCell:@""];
412 #endif
413   [mSearchFieldCell setBezelStyle:NSTextFieldRoundedBezel];
414   [mSearchFieldCell setBezeled:YES];
415   [mSearchFieldCell setEditable:YES];
416   [mSearchFieldCell setFocusRingType:NSFocusRingTypeExterior];
418   mDropdownCell = [[NSPopUpButtonCell alloc] initTextCell:@"" pullsDown:NO];
420   mComboBoxCell = [[NSComboBoxCell alloc] initTextCell:@""];
421   [mComboBoxCell setBezeled:YES];
422   [mComboBoxCell setEditable:YES];
423   [mComboBoxCell setFocusRingType:NSFocusRingTypeExterior];
425   mProgressBarCell = [[NSProgressBarCell alloc] init];
427   mMeterBarCell = [[NSLevelIndicatorCell alloc]
428                     initWithLevelIndicatorStyle:NSContinuousCapacityLevelIndicatorStyle];
430   mCellDrawView = [[CellDrawView alloc] init];
432   NS_OBJC_END_TRY_ABORT_BLOCK;
435 nsNativeThemeCocoa::~nsNativeThemeCocoa()
437   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
439   [mMeterBarCell release];
440   [mProgressBarCell release];
441   [mPushButtonCell release];
442   [mRadioButtonCell release];
443   [mCheckboxCell release];
444   [mSearchFieldCell release];
445   [mDropdownCell release];
446   [mComboBoxCell release];
447   [mCellDrawView release];
449   NS_OBJC_END_TRY_ABORT_BLOCK;
452 // Limit on the area of the target rect (in pixels^2) in
453 // DrawCellWithScaling(), DrawButton() and DrawScrollbar(), above which we
454 // don't draw the object into a bitmap buffer.  This is to avoid crashes in
455 // [NSGraphicsContext graphicsContextWithGraphicsPort:flipped:] and
456 // CGContextDrawImage(), and also to avoid very poor drawing performance in
457 // CGContextDrawImage() when it scales the bitmap (particularly if xscale or
458 // yscale is less than but near 1 -- e.g. 0.9).  This value was determined
459 // by trial and error, on OS X 10.4.11 and 10.5.4, and on systems with
460 // different amounts of RAM.
461 #define BITMAP_MAX_AREA 500000
463 static int
464 GetBackingScaleFactorForRendering(CGContextRef cgContext)
466   CGAffineTransform ctm = CGContextGetUserSpaceToDeviceSpaceTransform(cgContext);
467   CGRect transformedUserSpacePixel = CGRectApplyAffineTransform(CGRectMake(0, 0, 1, 1), ctm);
468   float maxScale = NS_MAX(fabs(transformedUserSpacePixel.size.width),
469                           fabs(transformedUserSpacePixel.size.height));
470   return maxScale > 1.0 ? 2 : 1;  
474  * Draw the given NSCell into the given cgContext.
476  * destRect - the size and position of the resulting control rectangle
477  * controlSize - the NSControlSize which will be given to the NSCell before
478  *  asking it to render
479  * naturalSize - The natural dimensions of this control.
480  *  If the control rect size is not equal to either of these, a scale
481  *  will be applied to the context so that rendering the control at the
482  *  natural size will result in it filling the destRect space.
483  *  If a control has no natural dimensions in either/both axes, pass 0.0f.
484  * minimumSize - The minimum dimensions of this control.
485  *  If the control rect size is less than the minimum for a given axis,
486  *  a scale will be applied to the context so that the minimum is used
487  *  for drawing.  If a control has no minimum dimensions in either/both
488  *  axes, pass 0.0f.
489  * marginSet - an array of margins; a multidimensional array of [2][3][4],
490  *  with the first dimension being the OS version (Tiger or Leopard),
491  *  the second being the control size (mini, small, regular), and the third
492  *  being the 4 margin values (left, top, right, bottom).
493  * view - The NSView that we're drawing into. As far as I can tell, it doesn't
494  *  matter if this is really the right view; it just has to return YES when
495  *  asked for isFlipped. Otherwise we'll get drawing bugs on 10.4.
496  * mirrorHorizontal - whether to mirror the cell horizontally
497  */
498 static void DrawCellWithScaling(NSCell *cell,
499                                 CGContextRef cgContext,
500                                 const HIRect& destRect,
501                                 NSControlSize controlSize,
502                                 NSSize naturalSize,
503                                 NSSize minimumSize,
504                                 const float marginSet[][3][4],
505                                 NSView* view,
506                                 BOOL mirrorHorizontal)
508   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
510   NSRect drawRect = NSMakeRect(destRect.origin.x, destRect.origin.y, destRect.size.width, destRect.size.height);
512   if (naturalSize.width != 0.0f)
513     drawRect.size.width = naturalSize.width;
514   if (naturalSize.height != 0.0f)
515     drawRect.size.height = naturalSize.height;
517   // Keep aspect ratio when scaling if one dimension is free.
518   if (naturalSize.width == 0.0f && naturalSize.height != 0.0f)
519     drawRect.size.width = destRect.size.width * naturalSize.height / destRect.size.height;
520   if (naturalSize.height == 0.0f && naturalSize.width != 0.0f)
521     drawRect.size.height = destRect.size.height * naturalSize.width / destRect.size.width;
523   // Honor minimum sizes.
524   if (drawRect.size.width < minimumSize.width)
525     drawRect.size.width = minimumSize.width;
526   if (drawRect.size.height < minimumSize.height)
527     drawRect.size.height = minimumSize.height;
529   [NSGraphicsContext saveGraphicsState];
531   // Only skip the buffer if the area of our cell (in pixels^2) is too large.
532   if (drawRect.size.width * drawRect.size.height > BITMAP_MAX_AREA) {
533     // Inflate the rect Gecko gave us by the margin for the control.
534     InflateControlRect(&drawRect, controlSize, marginSet);
536     NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
537     [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:cgContext flipped:YES]];
539     [cell drawWithFrame:drawRect inView:view];
541     [NSGraphicsContext setCurrentContext:savedContext];
542   }
543   else {
544     float w = ceil(drawRect.size.width);
545     float h = ceil(drawRect.size.height);
546     NSRect tmpRect = NSMakeRect(MAX_FOCUS_RING_WIDTH, MAX_FOCUS_RING_WIDTH, w, h);
548     // inflate to figure out the frame we need to tell NSCell to draw in, to get something that's 0,0,w,h
549     InflateControlRect(&tmpRect, controlSize, marginSet);
551     // and then, expand by MAX_FOCUS_RING_WIDTH size to make sure we can capture any focus ring
552     w += MAX_FOCUS_RING_WIDTH * 2.0;
553     h += MAX_FOCUS_RING_WIDTH * 2.0;
555     int backingScaleFactor = GetBackingScaleFactorForRendering(cgContext);
556     CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
557     CGContextRef ctx = CGBitmapContextCreate(NULL,
558                                              (int) w * backingScaleFactor, (int) h * backingScaleFactor,
559                                              8, (int) w * backingScaleFactor * 4,
560                                              rgb, kCGImageAlphaPremultipliedFirst);
561     CGColorSpaceRelease(rgb);
563     // We need to flip the image twice in order to avoid drawing bugs on 10.4, see bug 465069.
564     // This is the first flip transform, applied to cgContext.
565     CGContextScaleCTM(cgContext, 1.0f, -1.0f);
566     CGContextTranslateCTM(cgContext, 0.0f, -(2.0 * destRect.origin.y + destRect.size.height));
567     if (mirrorHorizontal) {
568       CGContextScaleCTM(cgContext, -1.0f, 1.0f);
569       CGContextTranslateCTM(cgContext, -(2.0 * destRect.origin.x + destRect.size.width), 0.0f);
570     }
572     NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
573     [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:ctx flipped:YES]];
575     CGContextScaleCTM(ctx, backingScaleFactor, backingScaleFactor);
577     // This is the second flip transform, applied to ctx.
578     CGContextScaleCTM(ctx, 1.0f, -1.0f);
579     CGContextTranslateCTM(ctx, 0.0f, -(2.0 * tmpRect.origin.y + tmpRect.size.height));
581     [cell drawWithFrame:tmpRect inView:view];
583     [NSGraphicsContext setCurrentContext:savedContext];
585     CGImageRef img = CGBitmapContextCreateImage(ctx);
587     // Drop the image into the original destination rectangle, scaling to fit
588     // Only scale MAX_FOCUS_RING_WIDTH by xscale/yscale when the resulting rect
589     // doesn't extend beyond the overflow rect
590     float xscale = destRect.size.width / drawRect.size.width;
591     float yscale = destRect.size.height / drawRect.size.height;
592     float scaledFocusRingX = xscale < 1.0f ? MAX_FOCUS_RING_WIDTH * xscale : MAX_FOCUS_RING_WIDTH;
593     float scaledFocusRingY = yscale < 1.0f ? MAX_FOCUS_RING_WIDTH * yscale : MAX_FOCUS_RING_WIDTH;
594     CGContextDrawImage(cgContext, CGRectMake(destRect.origin.x - scaledFocusRingX,
595                                              destRect.origin.y - scaledFocusRingY,
596                                              destRect.size.width + scaledFocusRingX * 2,
597                                              destRect.size.height + scaledFocusRingY * 2),
598                        img);
600     CGImageRelease(img);
601     CGContextRelease(ctx);
602   }
604   [NSGraphicsContext restoreGraphicsState];
606 #if DRAW_IN_FRAME_DEBUG
607   CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
608   CGContextFillRect(cgContext, destRect);
609 #endif
611   NS_OBJC_END_TRY_ABORT_BLOCK;
614 struct CellRenderSettings {
615   // The natural dimensions of the control.
616   // If a control has no natural dimensions in either/both axes, set to 0.0f.
617   NSSize naturalSizes[3];
619   // The minimum dimensions of the control.
620   // If a control has no minimum dimensions in either/both axes, set to 0.0f.
621   NSSize minimumSizes[3];
623   // A three-dimensional array,
624   // with the first dimension being the OS version (only Leopard for the moment),
625   // the second being the control size (mini, small, regular), and the third
626   // being the 4 margin values (left, top, right, bottom).
627   float margins[1][3][4];
631  * This is a helper method that returns the required NSControlSize given a size
632  * and the size of the three controls plus a tolerance.
633  * size - The width or the height of the element to draw.
634  * sizes - An array with the all the width/height of the element for its
635  *         different sizes.
636  * tolerance - The tolerance as passed to DrawCellWithSnapping.
637  * NOTE: returns NSRegularControlSize if all values in 'sizes' are zero.
638  */
639 static NSControlSize FindControlSize(CGFloat size, const CGFloat* sizes, CGFloat tolerance)
641   for (uint32_t i = miniControlSize; i <= regularControlSize; ++i) {
642     if (sizes[i] == 0) {
643       continue;
644     }
646     CGFloat next = 0;
647     // Find next value.
648     for (uint32_t j = i+1; j <= regularControlSize; ++j) {
649       if (sizes[j] != 0) {
650         next = sizes[j];
651         break;
652       }
653     }
655     // If it's the latest value, we pick it.
656     if (next == 0) {
657       return CocoaSizeForEnum(i);
658     }
660     if (size <= sizes[i] + tolerance && size < next) {
661       return CocoaSizeForEnum(i);
662     }
663   }
665   // If we are here, that means sizes[] was an array with only empty values
666   // or the algorithm above is wrong.
667   // The former can happen but the later would be wrong.
668   NS_ASSERTION(sizes[0] == 0 && sizes[1] == 0 && sizes[2] == 0,
669                "We found no control! We shouldn't be there!");
670   return CocoaSizeForEnum(regularControlSize);
674  * Draw the given NSCell into the given cgContext with a nice control size.
676  * This function is similar to DrawCellWithScaling, but it decides what
677  * control size to use based on the destRect's size.
678  * Scaling is only applied when the difference between the destRect's size
679  * and the next smaller natural size is greater than snapTolerance. Otherwise
680  * it snaps to the next smaller control size without scaling because unscaled
681  * controls look nicer.
682  */
683 static void DrawCellWithSnapping(NSCell *cell,
684                                  CGContextRef cgContext,
685                                  const HIRect& destRect,
686                                  const CellRenderSettings settings,
687                                  float verticalAlignFactor,
688                                  NSView* view,
689                                  BOOL mirrorHorizontal,
690                                  float snapTolerance = 2.0f)
692   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
694   const float rectWidth = destRect.size.width, rectHeight = destRect.size.height;
695   const NSSize *sizes = settings.naturalSizes;
696   const NSSize miniSize = sizes[EnumSizeForCocoaSize(NSMiniControlSize)];
697   const NSSize smallSize = sizes[EnumSizeForCocoaSize(NSSmallControlSize)];
698   const NSSize regularSize = sizes[EnumSizeForCocoaSize(NSRegularControlSize)];
700   HIRect drawRect = destRect;
702   CGFloat controlWidths[3] = { miniSize.width, smallSize.width, regularSize.width };
703   NSControlSize controlSizeX = FindControlSize(rectWidth, controlWidths, snapTolerance);
704   CGFloat controlHeights[3] = { miniSize.height, smallSize.height, regularSize.height };
705   NSControlSize controlSizeY = FindControlSize(rectHeight, controlHeights, snapTolerance);
707   NSControlSize controlSize = NSRegularControlSize;
708   int sizeIndex = 0;
710   // At some sizes, don't scale but snap.
711   const NSControlSize smallerControlSize =
712     EnumSizeForCocoaSize(controlSizeX) < EnumSizeForCocoaSize(controlSizeY) ?
713     controlSizeX : controlSizeY;
714   const int smallerControlSizeIndex = EnumSizeForCocoaSize(smallerControlSize);
715   const NSSize size = sizes[smallerControlSizeIndex];
716   float diffWidth = size.width ? rectWidth - size.width : 0.0f;
717   float diffHeight = size.height ? rectHeight - size.height : 0.0f;
718   if (diffWidth >= 0.0f && diffHeight >= 0.0f &&
719       diffWidth <= snapTolerance && diffHeight <= snapTolerance) {
720     // Snap to the smaller control size.
721     controlSize = smallerControlSize;
722     sizeIndex = smallerControlSizeIndex;
723     // Resize and center the drawRect.
724     if (sizes[sizeIndex].width) {
725       drawRect.origin.x += ceil((destRect.size.width - sizes[sizeIndex].width) / 2);
726       drawRect.size.width = sizes[sizeIndex].width;
727     }
728     if (sizes[sizeIndex].height) {
729       drawRect.origin.y += floor((destRect.size.height - sizes[sizeIndex].height) * verticalAlignFactor);
730       drawRect.size.height = sizes[sizeIndex].height;
731     }
732   } else {
733     // Use the larger control size.
734     controlSize = EnumSizeForCocoaSize(controlSizeX) > EnumSizeForCocoaSize(controlSizeY) ?
735                   controlSizeX : controlSizeY;
736     sizeIndex = EnumSizeForCocoaSize(controlSize);
737    }
739   [cell setControlSize:controlSize];
741   NSSize minimumSize = settings.minimumSizes ? settings.minimumSizes[sizeIndex] : NSZeroSize;
742   DrawCellWithScaling(cell, cgContext, drawRect, controlSize, sizes[sizeIndex],
743                       minimumSize, settings.margins, view, mirrorHorizontal);
745   NS_OBJC_END_TRY_ABORT_BLOCK;
748 static float VerticalAlignFactor(nsIFrame *aFrame)
750   if (!aFrame)
751     return 0.5f; // default: center
753   const nsStyleCoord& va = aFrame->GetStyleTextReset()->mVerticalAlign;
754   uint8_t intval = (va.GetUnit() == eStyleUnit_Enumerated)
755                      ? va.GetIntValue()
756                      : NS_STYLE_VERTICAL_ALIGN_MIDDLE;
757   switch (intval) {
758     case NS_STYLE_VERTICAL_ALIGN_TOP:
759     case NS_STYLE_VERTICAL_ALIGN_TEXT_TOP:
760       return 0.0f;
762     case NS_STYLE_VERTICAL_ALIGN_SUB:
763     case NS_STYLE_VERTICAL_ALIGN_SUPER:
764     case NS_STYLE_VERTICAL_ALIGN_MIDDLE:
765     case NS_STYLE_VERTICAL_ALIGN_MIDDLE_WITH_BASELINE:
766       return 0.5f;
768     case NS_STYLE_VERTICAL_ALIGN_BASELINE:
769     case NS_STYLE_VERTICAL_ALIGN_TEXT_BOTTOM:
770     case NS_STYLE_VERTICAL_ALIGN_BOTTOM:
771       return 1.0f;
773     default:
774       NS_NOTREACHED("invalid vertical-align");
775       return 0.5f;
776   }
779 // These are the sizes that Gecko needs to request to draw if it wants
780 // to get a standard-sized Aqua radio button drawn. Note that the rects
781 // that draw these are actually a little bigger.
782 static const CellRenderSettings radioSettings = {
783   {
784     NSMakeSize(11, 11), // mini
785     NSMakeSize(13, 13), // small
786     NSMakeSize(16, 16)  // regular
787   },
788   {
789     NSZeroSize, NSZeroSize, NSZeroSize
790   },
791   {
792     { // Leopard
793       {0, 0, 0, 0},     // mini
794       {0, 1, 1, 1},     // small
795       {0, 0, 0, 0}      // regular
796     }
797   }
800 static const CellRenderSettings checkboxSettings = {
801   {
802     NSMakeSize(11, 11), // mini
803     NSMakeSize(13, 13), // small
804     NSMakeSize(16, 16)  // regular
805   },
806   {
807     NSZeroSize, NSZeroSize, NSZeroSize
808   },
809   {
810     { // Leopard
811       {0, 1, 0, 0},     // mini
812       {0, 1, 0, 1},     // small
813       {0, 1, 0, 1}      // regular
814     }
815   }
818 void
819 nsNativeThemeCocoa::DrawCheckboxOrRadio(CGContextRef cgContext, bool inCheckbox,
820                                         const HIRect& inBoxRect, bool inSelected,
821                                         nsEventStates inState, nsIFrame* aFrame)
823   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
825   NSButtonCell *cell = inCheckbox ? mCheckboxCell : mRadioButtonCell;
826   NSCellStateValue state = inSelected ? NSOnState : NSOffState;
828   // Check if we have an indeterminate checkbox
829   if (inCheckbox && GetIndeterminate(aFrame))
830     state = NSMixedState;
832   [cell setEnabled:!IsDisabled(aFrame, inState)];
833   [cell setShowsFirstResponder:inState.HasState(NS_EVENT_STATE_FOCUS)];
834   [cell setState:state];
835   [cell setHighlighted:inState.HasAllStates(NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_HOVER)];
836   [cell setControlTint:(FrameIsInActiveWindow(aFrame) ? [NSColor currentControlTint] : NSClearControlTint)];
838   // Ensure that the control is square.
839   float length = NS_MIN(inBoxRect.size.width, inBoxRect.size.height);
840   HIRect drawRect = CGRectMake(inBoxRect.origin.x + (int)((inBoxRect.size.width - length) / 2.0f),
841                                inBoxRect.origin.y + (int)((inBoxRect.size.height - length) / 2.0f),
842                                length, length);
844   DrawCellWithSnapping(cell, cgContext, drawRect,
845                        inCheckbox ? checkboxSettings : radioSettings,
846                        VerticalAlignFactor(aFrame), mCellDrawView, NO);
848   NS_OBJC_END_TRY_ABORT_BLOCK;
851 static const CellRenderSettings searchFieldSettings = {
852   {
853     NSMakeSize(0, 16), // mini
854     NSMakeSize(0, 19), // small
855     NSMakeSize(0, 22)  // regular
856   },
857   {
858     NSMakeSize(32, 0), // mini
859     NSMakeSize(38, 0), // small
860     NSMakeSize(44, 0)  // regular
861   },
862   {
863     { // Leopard
864       {0, 0, 0, 0},     // mini
865       {0, 0, 0, 0},     // small
866       {0, 0, 0, 0}      // regular
867     }
868   }
871 void
872 nsNativeThemeCocoa::DrawSearchField(CGContextRef cgContext, const HIRect& inBoxRect,
873                                     nsIFrame* aFrame, nsEventStates inState)
875   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
877   ContextAwareSearchFieldCell* cell = mSearchFieldCell;
878   [cell setContext:aFrame];
879   [cell setEnabled:!IsDisabled(aFrame, inState)];
880   // NOTE: this could probably use inState
881   [cell setShowsFirstResponder:IsFocused(aFrame)];
883   DrawCellWithSnapping(cell, cgContext, inBoxRect, searchFieldSettings,
884                        VerticalAlignFactor(aFrame), mCellDrawView,
885                        IsFrameRTL(aFrame));
887   [cell setContext:nullptr];
889   NS_OBJC_END_TRY_ABORT_BLOCK;
892 static const CellRenderSettings pushButtonSettings = {
893   {
894     NSMakeSize(0, 16), // mini
895     NSMakeSize(0, 19), // small
896     NSMakeSize(0, 22)  // regular
897   },
898   {
899     NSMakeSize(18, 0), // mini
900     NSMakeSize(26, 0), // small
901     NSMakeSize(30, 0)  // regular
902   },
903   {
904     { // Leopard
905       {0, 0, 0, 0},    // mini
906       {4, 0, 4, 1},    // small
907       {5, 0, 5, 2}     // regular
908     }
909   }
912 // The height at which we start doing square buttons instead of rounded buttons
913 // Rounded buttons look bad if drawn at a height greater than 26, so at that point
914 // we switch over to doing square buttons which looks fine at any size.
915 #define DO_SQUARE_BUTTON_HEIGHT 26
917 void
918 nsNativeThemeCocoa::DrawPushButton(CGContextRef cgContext, const HIRect& inBoxRect,
919                                    nsEventStates inState, nsIFrame* aFrame)
921   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
923   BOOL isActive = FrameIsInActiveWindow(aFrame);
924   BOOL isDisabled = IsDisabled(aFrame, inState);
926   [mPushButtonCell setEnabled:!isDisabled];
927   [mPushButtonCell setHighlighted:isActive &&
928                                   inState.HasAllStates(NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_HOVER)];
929   [mPushButtonCell setShowsFirstResponder:inState.HasState(NS_EVENT_STATE_FOCUS) && !isDisabled && isActive];
931   // If the button is tall enough, draw the square button style so that buttons with
932   // non-standard content look good. Otherwise draw normal rounded aqua buttons.
933   if (inBoxRect.size.height > DO_SQUARE_BUTTON_HEIGHT) {
934     [mPushButtonCell setBezelStyle:NSShadowlessSquareBezelStyle];
935     DrawCellWithScaling(mPushButtonCell, cgContext, inBoxRect, NSRegularControlSize,
936                         NSZeroSize, NSMakeSize(14, 0), NULL,
937                         mCellDrawView, IsFrameRTL(aFrame));
938   } else {
939     [mPushButtonCell setBezelStyle:NSRoundedBezelStyle];
941     DrawCellWithSnapping(mPushButtonCell, cgContext, inBoxRect, pushButtonSettings,
942                          0.5f, mCellDrawView, IsFrameRTL(aFrame), 1.0f);
943   }
945 #if DRAW_IN_FRAME_DEBUG
946   CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
947   CGContextFillRect(cgContext, inBoxRect);
948 #endif
950   NS_OBJC_END_TRY_ABORT_BLOCK;
953 typedef void (*RenderHIThemeControlFunction)(CGContextRef cgContext, const HIRect& aRenderRect, void* aData);
955 static void
956 RenderTransformedHIThemeControl(CGContextRef aCGContext, const HIRect& aRect,
957                                 RenderHIThemeControlFunction aFunc, void* aData,
958                                 BOOL mirrorHorizontally = NO)
960   CGAffineTransform savedCTM = CGContextGetCTM(aCGContext);
961   CGContextTranslateCTM(aCGContext, aRect.origin.x, aRect.origin.y);
963   bool drawDirect;
964   HIRect drawRect = aRect;
965   drawRect.origin = CGPointZero;
967   if (!mirrorHorizontally && savedCTM.a == 1.0f && savedCTM.b == 0.0f &&
968       savedCTM.c == 0.0f && (savedCTM.d == 1.0f || savedCTM.d == -1.0f)) {
969     drawDirect = TRUE;
970   } else {
971     drawDirect = FALSE;
972   }
974   // Fall back to no bitmap buffer if the area of our control (in pixels^2)
975   // is too large.
976   if (drawDirect || (aRect.size.width * aRect.size.height > BITMAP_MAX_AREA)) {
977     aFunc(aCGContext, drawRect, aData);
978   } else {
979     // Inflate the buffer to capture focus rings.
980     int w = ceil(drawRect.size.width) + 2 * MAX_FOCUS_RING_WIDTH;
981     int h = ceil(drawRect.size.height) + 2 * MAX_FOCUS_RING_WIDTH;
983     int backingScaleFactor = GetBackingScaleFactorForRendering(aCGContext);
984     CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
985     CGContextRef bitmapctx = CGBitmapContextCreate(NULL,
986                                                    w * backingScaleFactor,
987                                                    h * backingScaleFactor,
988                                                    8,
989                                                    w * backingScaleFactor * 4,
990                                                    colorSpace,
991                                                    kCGImageAlphaPremultipliedFirst);
992     CGColorSpaceRelease(colorSpace);
994     CGContextScaleCTM(bitmapctx, backingScaleFactor, backingScaleFactor);
995     CGContextTranslateCTM(bitmapctx, MAX_FOCUS_RING_WIDTH, MAX_FOCUS_RING_WIDTH);
997     // HITheme always wants to draw into a flipped context, or things
998     // get confused.
999     CGContextTranslateCTM(bitmapctx, 0.0f, aRect.size.height);
1000     CGContextScaleCTM(bitmapctx, 1.0f, -1.0f);
1002     aFunc(bitmapctx, drawRect, aData);
1004     CGImageRef bitmap = CGBitmapContextCreateImage(bitmapctx);
1006     CGAffineTransform ctm = CGContextGetCTM(aCGContext);
1008     // We need to unflip, so that we can do a DrawImage without getting a flipped image.
1009     CGContextTranslateCTM(aCGContext, 0.0f, aRect.size.height);
1010     CGContextScaleCTM(aCGContext, 1.0f, -1.0f);
1012     if (mirrorHorizontally) {
1013       CGContextTranslateCTM(aCGContext, aRect.size.width, 0);
1014       CGContextScaleCTM(aCGContext, -1.0f, 1.0f);
1015     }
1017     HIRect inflatedDrawRect = CGRectMake(-MAX_FOCUS_RING_WIDTH, -MAX_FOCUS_RING_WIDTH, w, h);
1018     CGContextDrawImage(aCGContext, inflatedDrawRect, bitmap);
1020     CGContextSetCTM(aCGContext, ctm);
1022     CGImageRelease(bitmap);
1023     CGContextRelease(bitmapctx);
1024   }
1026   CGContextSetCTM(aCGContext, savedCTM);
1029 static void
1030 RenderButton(CGContextRef cgContext, const HIRect& aRenderRect, void* aData)
1032   HIThemeButtonDrawInfo* bdi = (HIThemeButtonDrawInfo*)aData;
1033   HIThemeDrawButton(&aRenderRect, bdi, cgContext, kHIThemeOrientationNormal, NULL);
1036 void
1037 nsNativeThemeCocoa::DrawButton(CGContextRef cgContext, ThemeButtonKind inKind,
1038                                const HIRect& inBoxRect, bool inIsDefault,
1039                                ThemeButtonValue inValue, ThemeButtonAdornment inAdornment,
1040                                nsEventStates inState, nsIFrame* aFrame)
1042   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1044   BOOL isActive = FrameIsInActiveWindow(aFrame);
1045   BOOL isDisabled = IsDisabled(aFrame, inState);
1047   HIThemeButtonDrawInfo bdi;
1048   bdi.version = 0;
1049   bdi.kind = inKind;
1050   bdi.value = inValue;
1051   bdi.adornment = inAdornment;
1053   if (isDisabled) {
1054     bdi.state = kThemeStateUnavailable;
1055   }
1056   else if (inState.HasAllStates(NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_HOVER)) {
1057     bdi.state = kThemeStatePressed;
1058   }
1059   else {
1060     if (inKind == kThemeArrowButton)
1061       bdi.state = kThemeStateUnavailable; // these are always drawn as unavailable
1062     else if (!isActive && inKind == kThemeListHeaderButton)
1063       bdi.state = kThemeStateInactive;
1064     else
1065       bdi.state = kThemeStateActive;
1066   }
1068   if (inState.HasState(NS_EVENT_STATE_FOCUS) && isActive)
1069     bdi.adornment |= kThemeAdornmentFocus;
1071   if (inIsDefault && !isDisabled && isActive &&
1072       !inState.HasState(NS_EVENT_STATE_ACTIVE)) {
1073     bdi.adornment |= kThemeAdornmentDefault;
1074     bdi.animation.time.start = 0;
1075     bdi.animation.time.current = CFAbsoluteTimeGetCurrent();
1076   }
1078   HIRect drawFrame = inBoxRect;
1080   if (inKind == kThemePushButton) {
1081     drawFrame.size.height -= 2;
1082     if (inBoxRect.size.height < pushButtonSettings.naturalSizes[smallControlSize].height) {
1083       bdi.kind = kThemePushButtonMini;
1084     }
1085     else if (inBoxRect.size.height < pushButtonSettings.naturalSizes[regularControlSize].height) {
1086       bdi.kind = kThemePushButtonSmall;
1087       drawFrame.origin.y -= 1;
1088       drawFrame.origin.x += 1;
1089       drawFrame.size.width -= 2;
1090     }
1091   }
1092   else if (inKind == kThemeListHeaderButton) {
1093     CGContextClipToRect(cgContext, inBoxRect);
1094     // Always remove the top border.
1095     drawFrame.origin.y -= 1;
1096     drawFrame.size.height += 1;
1097     // Remove the left border in LTR mode and the right border in RTL mode.
1098     drawFrame.size.width += 1;
1099     bool isLast = IsLastTreeHeaderCell(aFrame);
1100     if (isLast)
1101       drawFrame.size.width += 1; // Also remove the other border.
1102     if (!IsFrameRTL(aFrame) || isLast)
1103       drawFrame.origin.x -= 1;
1104   }
1106   RenderTransformedHIThemeControl(cgContext, drawFrame, RenderButton, &bdi,
1107                                   IsFrameRTL(aFrame));
1109 #if DRAW_IN_FRAME_DEBUG
1110   CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
1111   CGContextFillRect(cgContext, inBoxRect);
1112 #endif
1114   NS_OBJC_END_TRY_ABORT_BLOCK;
1117 static const CellRenderSettings dropdownSettings = {
1118   {
1119     NSMakeSize(0, 16), // mini
1120     NSMakeSize(0, 19), // small
1121     NSMakeSize(0, 22)  // regular
1122   },
1123   {
1124     NSMakeSize(18, 0), // mini
1125     NSMakeSize(38, 0), // small
1126     NSMakeSize(44, 0)  // regular
1127   },
1128   {
1129     { // Leopard
1130       {1, 1, 2, 1},    // mini
1131       {3, 0, 3, 1},    // small
1132       {3, 0, 3, 0}     // regular
1133     }
1134   }
1137 static const CellRenderSettings editableMenulistSettings = {
1138   {
1139     NSMakeSize(0, 15), // mini
1140     NSMakeSize(0, 18), // small
1141     NSMakeSize(0, 21)  // regular
1142   },
1143   {
1144     NSMakeSize(18, 0), // mini
1145     NSMakeSize(38, 0), // small
1146     NSMakeSize(44, 0)  // regular
1147   },
1148   {
1149     { // Leopard
1150       {0, 0, 2, 2},    // mini
1151       {0, 0, 3, 2},    // small
1152       {0, 1, 3, 3}     // regular
1153     }
1154   }
1157 void
1158 nsNativeThemeCocoa::DrawDropdown(CGContextRef cgContext, const HIRect& inBoxRect,
1159                                  nsEventStates inState, uint8_t aWidgetType,
1160                                  nsIFrame* aFrame)
1162   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1164   [mDropdownCell setPullsDown:(aWidgetType == NS_THEME_BUTTON)];
1166   BOOL isEditable = (aWidgetType == NS_THEME_DROPDOWN_TEXTFIELD);
1167   NSCell* cell = isEditable ? (NSCell*)mComboBoxCell : (NSCell*)mDropdownCell;
1169   [cell setEnabled:!IsDisabled(aFrame, inState)];
1170   [cell setShowsFirstResponder:(IsFocused(aFrame) || inState.HasState(NS_EVENT_STATE_FOCUS))];
1171   [cell setHighlighted:IsOpenButton(aFrame)];
1172   [cell setControlTint:(FrameIsInActiveWindow(aFrame) ? [NSColor currentControlTint] : NSClearControlTint)];
1174   const CellRenderSettings& settings = isEditable ? editableMenulistSettings : dropdownSettings;
1175   DrawCellWithSnapping(cell, cgContext, inBoxRect, settings,
1176                        0.5f, mCellDrawView, IsFrameRTL(aFrame));
1178   NS_OBJC_END_TRY_ABORT_BLOCK;
1181 void
1182 nsNativeThemeCocoa::DrawSpinButtons(CGContextRef cgContext, ThemeButtonKind inKind,
1183                                     const HIRect& inBoxRect, ThemeDrawState inDrawState,
1184                                     ThemeButtonAdornment inAdornment,
1185                                     nsEventStates inState, nsIFrame* aFrame)
1187   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1189   HIThemeButtonDrawInfo bdi;
1190   bdi.version = 0;
1191   bdi.kind = inKind;
1192   bdi.value = kThemeButtonOff;
1193   bdi.adornment = inAdornment;
1195   if (IsDisabled(aFrame, inState))
1196     bdi.state = kThemeStateUnavailable;
1197   else
1198     bdi.state = FrameIsInActiveWindow(aFrame) ? inDrawState : kThemeStateActive;
1200   HIThemeDrawButton(&inBoxRect, &bdi, cgContext, HITHEME_ORIENTATION, NULL);
1202   NS_OBJC_END_TRY_ABORT_BLOCK;
1205 void
1206 nsNativeThemeCocoa::DrawFrame(CGContextRef cgContext, HIThemeFrameKind inKind,
1207                               const HIRect& inBoxRect, bool inDisabled,
1208                               nsEventStates inState)
1210   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1212   HIThemeFrameDrawInfo fdi;
1213   fdi.version = 0;
1214   fdi.kind = inKind;
1216   // We don't ever set an inactive state for this because it doesn't
1217   // look right (see other apps).
1218   fdi.state = inDisabled ? kThemeStateUnavailable : kThemeStateActive;
1220   // for some reason focus rings on listboxes draw incorrectly
1221   if (inKind == kHIThemeFrameListBox)
1222     fdi.isFocused = 0;
1223   else
1224     fdi.isFocused = inState.HasState(NS_EVENT_STATE_FOCUS);
1226   // HIThemeDrawFrame takes the rect for the content area of the frame, not
1227   // the bounding rect for the frame. Here we reduce the size of the rect we
1228   // will pass to make it the size of the content.
1229   HIRect drawRect = inBoxRect;
1230   if (inKind == kHIThemeFrameTextFieldSquare) {
1231     SInt32 frameOutset = 0;
1232     ::GetThemeMetric(kThemeMetricEditTextFrameOutset, &frameOutset);
1233     drawRect.origin.x += frameOutset;
1234     drawRect.origin.y += frameOutset;
1235     drawRect.size.width -= frameOutset * 2;
1236     drawRect.size.height -= frameOutset * 2;
1237   }
1238   else if (inKind == kHIThemeFrameListBox) {
1239     SInt32 frameOutset = 0;
1240     ::GetThemeMetric(kThemeMetricListBoxFrameOutset, &frameOutset);
1241     drawRect.origin.x += frameOutset;
1242     drawRect.origin.y += frameOutset;
1243     drawRect.size.width -= frameOutset * 2;
1244     drawRect.size.height -= frameOutset * 2;
1245   }
1247 #if DRAW_IN_FRAME_DEBUG
1248   CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
1249   CGContextFillRect(cgContext, inBoxRect);
1250 #endif
1252   HIThemeDrawFrame(&drawRect, &fdi, cgContext, HITHEME_ORIENTATION);
1254   NS_OBJC_END_TRY_ABORT_BLOCK;
1257 static const CellRenderSettings progressSettings[2][2] = {
1258   // Vertical progress bar.
1259   {
1260     // Determined settings.
1261     {
1262       {
1263         NSZeroSize, // mini
1264         NSMakeSize(10, 0), // small
1265         NSMakeSize(16, 0)  // regular
1266       },
1267       {
1268         NSZeroSize, NSZeroSize, NSZeroSize
1269       },
1270       {
1271         { // Leopard
1272           {0, 0, 0, 0},     // mini
1273           {1, 1, 1, 1},     // small
1274           {1, 1, 1, 1}      // regular
1275         }
1276       }
1277     },
1278     // There is no horizontal margin in regular undetermined size.
1279     {
1280       {
1281         NSZeroSize, // mini
1282         NSMakeSize(10, 0), // small
1283         NSMakeSize(16, 0)  // regular
1284       },
1285       {
1286         NSZeroSize, NSZeroSize, NSZeroSize
1287       },
1288       {
1289         { // Leopard
1290           {0, 0, 0, 0},     // mini
1291           {1, 1, 1, 1},     // small
1292           {1, 0, 1, 0}      // regular
1293         }
1294       }
1295     }
1296   },
1297   // Horizontal progress bar.
1298   {
1299     // Determined settings.
1300     {
1301       {
1302         NSZeroSize, // mini
1303         NSMakeSize(0, 10), // small
1304         NSMakeSize(0, 16)  // regular
1305       },
1306       {
1307         NSZeroSize, NSZeroSize, NSZeroSize
1308       },
1309       {
1310         { // Leopard
1311           {0, 0, 0, 0},     // mini
1312           {1, 1, 1, 1},     // small
1313           {1, 1, 1, 1}      // regular
1314         }
1315       }
1316     },
1317     // There is no horizontal margin in regular undetermined size.
1318     {
1319       {
1320         NSZeroSize, // mini
1321         NSMakeSize(0, 10), // small
1322         NSMakeSize(0, 16)  // regular
1323       },
1324       {
1325         NSZeroSize, NSZeroSize, NSZeroSize
1326       },
1327       {
1328         { // Leopard
1329           {0, 0, 0, 0},     // mini
1330           {1, 1, 1, 1},     // small
1331           {0, 1, 0, 1}      // regular
1332         }
1333       }
1334     }
1335   }
1338 void
1339 nsNativeThemeCocoa::DrawProgress(CGContextRef cgContext, const HIRect& inBoxRect,
1340                                  bool inIsIndeterminate, bool inIsHorizontal,
1341                                  double inValue, double inMaxValue,
1342                                  nsIFrame* aFrame)
1344   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1346   NSProgressBarCell* cell = mProgressBarCell;
1348   [cell setValue:inValue];
1349   [cell setMax:inMaxValue];
1350   [cell setIndeterminate:inIsIndeterminate];
1351   [cell setHorizontal:inIsHorizontal];
1352   [cell setControlTint:(FrameIsInActiveWindow(aFrame) ? [NSColor currentControlTint]
1353                                                       : NSClearControlTint)];
1355   DrawCellWithSnapping(cell, cgContext, inBoxRect,
1356                        progressSettings[inIsHorizontal][inIsIndeterminate],
1357                        VerticalAlignFactor(aFrame), mCellDrawView,
1358                        IsFrameRTL(aFrame));
1360   NS_OBJC_END_TRY_ABORT_BLOCK;
1363 static const CellRenderSettings meterSetting = {
1364   {
1365     NSMakeSize(0, 16), // mini
1366     NSMakeSize(0, 16), // small
1367     NSMakeSize(0, 16)  // regular
1368   },
1369   {
1370     NSZeroSize, NSZeroSize, NSZeroSize
1371   },
1372   {
1373     { // Leopard
1374       {1, 1, 1, 1},     // mini
1375       {1, 1, 1, 1},     // small
1376       {1, 1, 1, 1}      // regular
1377     }
1378   }
1381 void
1382 nsNativeThemeCocoa::DrawMeter(CGContextRef cgContext, const HIRect& inBoxRect,
1383                               nsIFrame* aFrame)
1385   NS_OBJC_BEGIN_TRY_ABORT_BLOCK
1387   NS_PRECONDITION(aFrame, "aFrame should not be null here!");
1389   nsCOMPtr<nsIDOMHTMLMeterElement> meterElement =
1390     do_QueryInterface(aFrame->GetContent());
1392   // When using -moz-meterbar on an non meter element, we will not be able to
1393   // get all the needed information so we just draw an empty meter.
1394   if (!meterElement) {
1395     DrawCellWithSnapping(mMeterBarCell, cgContext, inBoxRect,
1396                          meterSetting, VerticalAlignFactor(aFrame),
1397                          mCellDrawView, IsFrameRTL(aFrame));
1398     return;
1399   }
1401   double value;
1402   double min;
1403   double max;
1404   double low;
1405   double high;
1406   double optimum;
1408   // NOTE: if we were allowed to static_cast to nsHTMLMeterElement we would be
1409   // able to use nicer getters...
1410   meterElement->GetValue(&value);
1411   meterElement->GetMin(&min);
1412   meterElement->GetMax(&max);
1413   meterElement->GetLow(&low);
1414   meterElement->GetHigh(&high);
1415   meterElement->GetOptimum(&optimum);
1417   NSLevelIndicatorCell* cell = mMeterBarCell;
1419   [cell setMinValue:min];
1420   [cell setMaxValue:max];
1421   [cell setDoubleValue:value];
1423   /**
1424    * The way HTML and Cocoa defines the meter/indicator widget are different.
1425    * So, we are going to use a trick to get the Cocoa widget showing what we
1426    * are expecting: we set the warningValue or criticalValue to the current
1427    * value when we want to have the widget to be in the warning or critical
1428    * state.
1429    */
1430   nsEventStates states = aFrame->GetContent()->AsElement()->State();
1432   // Reset previously set warning and critical values.
1433   [cell setWarningValue:max+1];
1434   [cell setCriticalValue:max+1];
1436   if (states.HasState(NS_EVENT_STATE_SUB_OPTIMUM)) {
1437     [cell setWarningValue:value];
1438   } else if (states.HasState(NS_EVENT_STATE_SUB_SUB_OPTIMUM)) {
1439     [cell setCriticalValue:value];
1440   }
1442   HIRect rect = CGRectStandardize(inBoxRect);
1443   BOOL vertical = IsVerticalMeter(aFrame);
1445   CGContextSaveGState(cgContext);
1447   if (vertical) {
1448     /**
1449      * Cocoa doesn't provide a vertical meter bar so to show one, we have to
1450      * show a rotated horizontal meter bar.
1451      * Given that we want to show a vertical meter bar, we assume that the rect
1452      * has vertical dimensions but we can't correctly draw a meter widget inside
1453      * such a rectangle so we need to inverse width and height (and re-position)
1454      * to get a rectangle with horizontal dimensions.
1455      * Finally, we want to show a vertical meter so we want to rotate the result
1456      * so it is vertical. We do that by changing the context.
1457      */
1458     CGFloat tmp = rect.size.width;
1459     rect.size.width = rect.size.height;
1460     rect.size.height = tmp;
1461     rect.origin.x += rect.size.height / 2.f - rect.size.width / 2.f;
1462     rect.origin.y += rect.size.width / 2.f - rect.size.height / 2.f;
1464     CGContextTranslateCTM(cgContext, CGRectGetMidX(rect), CGRectGetMidY(rect));
1465     CGContextRotateCTM(cgContext, -M_PI / 2.f);
1466     CGContextTranslateCTM(cgContext, -CGRectGetMidX(rect), -CGRectGetMidY(rect));
1467   }
1469   DrawCellWithSnapping(cell, cgContext, rect,
1470                        meterSetting, VerticalAlignFactor(aFrame),
1471                        mCellDrawView, !vertical && IsFrameRTL(aFrame));
1473   CGContextRestoreGState(cgContext);
1475   NS_OBJC_END_TRY_ABORT_BLOCK
1478 void
1479 nsNativeThemeCocoa::DrawTabPanel(CGContextRef cgContext, const HIRect& inBoxRect,
1480                                  nsIFrame* aFrame)
1482   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1484   HIThemeTabPaneDrawInfo tpdi;
1486   tpdi.version = 1;
1487   tpdi.state = FrameIsInActiveWindow(aFrame) ? kThemeStateActive : kThemeStateInactive;
1488   tpdi.direction = kThemeTabNorth;
1489   tpdi.size = kHIThemeTabSizeNormal;
1490   tpdi.kind = kHIThemeTabKindNormal;
1492   HIThemeDrawTabPane(&inBoxRect, &tpdi, cgContext, HITHEME_ORIENTATION);
1494   NS_OBJC_END_TRY_ABORT_BLOCK;
1497 void
1498 nsNativeThemeCocoa::DrawScale(CGContextRef cgContext, const HIRect& inBoxRect,
1499                               nsEventStates inState, bool inIsVertical,
1500                               bool inIsReverse, int32_t inCurrentValue,
1501                               int32_t inMinValue, int32_t inMaxValue,
1502                               nsIFrame* aFrame)
1504   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1506   HIThemeTrackDrawInfo tdi;
1508   tdi.version = 0;
1509   tdi.kind = kThemeMediumSlider;
1510   tdi.bounds = inBoxRect;
1511   tdi.min = inMinValue;
1512   tdi.max = inMaxValue;
1513   tdi.value = inCurrentValue;
1514   tdi.attributes = kThemeTrackShowThumb;
1515   if (!inIsVertical)
1516     tdi.attributes |= kThemeTrackHorizontal;
1517   if (inIsReverse)
1518     tdi.attributes |= kThemeTrackRightToLeft;
1519   if (inState.HasState(NS_EVENT_STATE_FOCUS))
1520     tdi.attributes |= kThemeTrackHasFocus;
1521   if (IsDisabled(aFrame, inState))
1522     tdi.enableState = kThemeTrackDisabled;
1523   else
1524     tdi.enableState = FrameIsInActiveWindow(aFrame) ? kThemeTrackActive : kThemeTrackInactive;
1525   tdi.trackInfo.slider.thumbDir = kThemeThumbPlain;
1526   tdi.trackInfo.slider.pressState = 0;
1528   HIThemeDrawTrack(&tdi, NULL, cgContext, HITHEME_ORIENTATION);
1530   NS_OBJC_END_TRY_ABORT_BLOCK;
1533 nsIFrame*
1534 nsNativeThemeCocoa::SeparatorResponsibility(nsIFrame* aBefore, nsIFrame* aAfter)
1536   // Usually a separator is drawn by the segment to the right of the
1537   // separator, but pressed and selected segments have higher priority.
1538   if (!aBefore || !aAfter)
1539     return nullptr;
1540   if (IsSelectedButton(aAfter))
1541     return aAfter;
1542   if (IsSelectedButton(aBefore) || IsPressedButton(aBefore))
1543     return aBefore;
1544   return aAfter;
1547 CGRect
1548 nsNativeThemeCocoa::SeparatorAdjustedRect(CGRect aRect, nsIFrame* aLeft,
1549                                           nsIFrame* aCurrent, nsIFrame* aRight)
1551   // A separator between two segments should always be located in the leftmost
1552   // pixel column of the segment to the right of the separator, regardless of
1553   // who ends up drawing it.
1554   // CoreUI draws the separators inside the drawing rect.
1555   if (aLeft && SeparatorResponsibility(aLeft, aCurrent) == aLeft) {
1556     // The left button draws the separator, so we need to make room for it.
1557     aRect.origin.x += 1;
1558     aRect.size.width -= 1;
1559   }
1560   if (SeparatorResponsibility(aCurrent, aRight) == aCurrent) {
1561     // We draw the right separator, so we need to extend the draw rect into the
1562     // segment to our right.
1563     aRect.size.width += 1;
1564   }
1565   return aRect;
1568 static NSString* ToolbarButtonPosition(BOOL aIsFirst, BOOL aIsLast)
1570   if (aIsFirst) {
1571     if (aIsLast)
1572       return @"kCUISegmentPositionOnly";
1573     return @"kCUISegmentPositionFirst";
1574   }
1575   if (aIsLast)
1576     return @"kCUISegmentPositionLast";
1577   return @"kCUISegmentPositionMiddle";
1580 struct SegmentedControlRenderSettings {
1581   const CGFloat* heights;
1582   const NSString* widgetName;
1583   const BOOL ignoresPressedWhenSelected;
1584   const BOOL isToolbarControl;
1587 static const CGFloat tabHeights[3] = { 17, 20, 23 };
1589 static const SegmentedControlRenderSettings tabRenderSettings = {
1590   tabHeights, @"tab", YES, NO
1593 static const CGFloat toolbarButtonHeights[3] = { 15, 18, 22 };
1595 static const SegmentedControlRenderSettings toolbarButtonRenderSettings = {
1596   toolbarButtonHeights, @"kCUIWidgetButtonSegmentedSCurve", NO, YES
1599 void
1600 nsNativeThemeCocoa::DrawSegment(CGContextRef cgContext, const HIRect& inBoxRect,
1601                                 nsEventStates inState, nsIFrame* aFrame,
1602                                 const SegmentedControlRenderSettings& aSettings)
1604   BOOL isActive = IsActive(aFrame, aSettings.isToolbarControl);
1605   BOOL isFocused = inState.HasState(NS_EVENT_STATE_FOCUS);
1606   BOOL isSelected = IsSelectedButton(aFrame);
1607   BOOL isPressed = IsPressedButton(aFrame);
1608   if (isSelected && aSettings.ignoresPressedWhenSelected) {
1609     isPressed = NO;
1610   }
1612   BOOL isRTL = IsFrameRTL(aFrame);
1613   nsIFrame* left = GetAdjacentSiblingFrameWithSameAppearance(aFrame, isRTL);
1614   nsIFrame* right = GetAdjacentSiblingFrameWithSameAppearance(aFrame, !isRTL);
1615   CGRect drawRect = SeparatorAdjustedRect(inBoxRect, left, aFrame, right);
1616   if (drawRect.size.width * drawRect.size.height > CUIDRAW_MAX_AREA) {
1617     return;
1618   }
1619   BOOL drawLeftSeparator = SeparatorResponsibility(left, aFrame) == aFrame;
1620   BOOL drawRightSeparator = SeparatorResponsibility(aFrame, right) == aFrame;
1621   NSControlSize controlSize = FindControlSize(drawRect.size.height, aSettings.heights, 4.0f);
1623   CUIDraw([NSWindow coreUIRenderer], drawRect, cgContext,
1624           (CFDictionaryRef)[NSDictionary dictionaryWithObjectsAndKeys:
1625             aSettings.widgetName, @"widget",
1626             ToolbarButtonPosition(!left, !right), @"kCUIPositionKey",
1627             [NSNumber numberWithBool:drawLeftSeparator], @"kCUISegmentLeadingSeparatorKey",
1628             [NSNumber numberWithBool:drawRightSeparator], @"kCUISegmentTrailingSeparatorKey",
1629             [NSNumber numberWithBool:isSelected], @"value",
1630             (isPressed ? @"pressed" : (isActive ? @"normal" : @"inactive")), @"state",
1631             [NSNumber numberWithBool:isFocused], @"focus",
1632             CUIControlSizeForCocoaSize(controlSize), @"size",
1633             [NSNumber numberWithBool:YES], @"is.flipped",
1634             @"up", @"direction",
1635             nil],
1636           nil);
1639 static inline UInt8
1640 ConvertToPressState(nsEventStates aButtonState, UInt8 aPressState)
1642   // If the button is pressed, return the press state passed in. Otherwise, return 0.
1643   return aButtonState.HasAllStates(NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_HOVER) ? aPressState : 0;
1646 void 
1647 nsNativeThemeCocoa::GetScrollbarPressStates(nsIFrame *aFrame, nsEventStates aButtonStates[])
1649   static nsIContent::AttrValuesArray attributeValues[] = {
1650     &nsGkAtoms::scrollbarUpTop,
1651     &nsGkAtoms::scrollbarDownTop,
1652     &nsGkAtoms::scrollbarUpBottom,
1653     &nsGkAtoms::scrollbarDownBottom,
1654     nullptr
1655   };
1657   // Get the state of any scrollbar buttons in our child frames
1658   for (nsIFrame *childFrame = aFrame->GetFirstPrincipalChild(); 
1659        childFrame;
1660        childFrame = childFrame->GetNextSibling()) {
1662     nsIContent *childContent = childFrame->GetContent();
1663     if (!childContent) continue;
1664     int32_t attrIndex = childContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::sbattr, 
1665                                                       attributeValues, eCaseMatters);
1666     if (attrIndex < 0) continue;
1668     aButtonStates[attrIndex] = GetContentState(childFrame, NS_THEME_BUTTON);
1669   }
1672 // Both of the following sets of numbers were derived by loading the testcase in
1673 // bmo bug 380185 in Safari and observing its behavior for various heights of scrollbar.
1674 // These magic numbers are the minimum sizes we can draw a scrollbar and still 
1675 // have room for everything to display, including the thumb
1676 #define MIN_SCROLLBAR_SIZE_WITH_THUMB 61
1677 #define MIN_SMALL_SCROLLBAR_SIZE_WITH_THUMB 49
1678 // And these are the minimum sizes if we don't draw the thumb
1679 #define MIN_SCROLLBAR_SIZE 56
1680 #define MIN_SMALL_SCROLLBAR_SIZE 46
1682 void
1683 nsNativeThemeCocoa::GetScrollbarDrawInfo(HIThemeTrackDrawInfo& aTdi, nsIFrame *aFrame, 
1684                                          const CGSize& aSize, bool aShouldGetButtonStates)
1686   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1688   int32_t curpos = CheckIntAttr(aFrame, nsGkAtoms::curpos, 0);
1689   int32_t minpos = CheckIntAttr(aFrame, nsGkAtoms::minpos, 0);
1690   int32_t maxpos = CheckIntAttr(aFrame, nsGkAtoms::maxpos, 100);
1691   int32_t thumbSize = CheckIntAttr(aFrame, nsGkAtoms::pageincrement, 10);
1693   bool isHorizontal = aFrame->GetContent()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::orient, 
1694                                                           nsGkAtoms::horizontal, eCaseMatters);
1695   bool isSmall = aFrame->GetStyleDisplay()->mAppearance == NS_THEME_SCROLLBAR_SMALL;
1697   aTdi.version = 0;
1698   aTdi.kind = isSmall ? kThemeSmallScrollBar : kThemeMediumScrollBar;
1699   aTdi.bounds.origin = CGPointZero;
1700   aTdi.bounds.size = aSize;
1701   aTdi.min = minpos;
1702   aTdi.max = maxpos;
1703   aTdi.value = curpos;
1704   aTdi.attributes = 0;
1705   aTdi.enableState = kThemeTrackActive;
1706   if (isHorizontal)
1707     aTdi.attributes |= kThemeTrackHorizontal;
1709   aTdi.trackInfo.scrollbar.viewsize = (SInt32)thumbSize;
1711   // This should be done early on so things like "kThemeTrackNothingToScroll" can
1712   // override the active enable state.
1713   aTdi.enableState = FrameIsInActiveWindow(aFrame) ? kThemeTrackActive : kThemeTrackInactive;
1715   /* Only display features if we have enough room for them.
1716    * Gecko still maintains the scrollbar info; this is just a visual issue (bug 380185).
1717    */
1718   int32_t longSideLength = (int32_t)(isHorizontal ? (aSize.width) : (aSize.height));
1719   if (longSideLength >= (isSmall ? MIN_SMALL_SCROLLBAR_SIZE_WITH_THUMB : MIN_SCROLLBAR_SIZE_WITH_THUMB)) {
1720     aTdi.attributes |= kThemeTrackShowThumb;
1721   }
1722   else if (longSideLength < (isSmall ? MIN_SMALL_SCROLLBAR_SIZE : MIN_SCROLLBAR_SIZE)) {
1723     aTdi.enableState = kThemeTrackNothingToScroll;
1724     return;
1725   }
1727   aTdi.trackInfo.scrollbar.pressState = 0;
1729   // Only go get these scrollbar button states if we need it. For example,
1730   // there's no reason to look up scrollbar button states when we're only
1731   // creating a TrackDrawInfo to determine the size of the thumb. There's
1732   // also no reason to do this on Lion or later, whose scrollbars have no
1733   // arrow buttons.
1734   if (aShouldGetButtonStates && !nsCocoaFeatures::OnLionOrLater()) {
1735     nsEventStates buttonStates[4];
1736     GetScrollbarPressStates(aFrame, buttonStates);
1737     NSString *buttonPlacement = [[NSUserDefaults standardUserDefaults] objectForKey:@"AppleScrollBarVariant"];
1738     // It seems that unless all four buttons are showing, kThemeTopOutsideArrowPressed is the correct constant for
1739     // the up scrollbar button.
1740     if ([buttonPlacement isEqualToString:@"DoubleBoth"]) {
1741       aTdi.trackInfo.scrollbar.pressState = ConvertToPressState(buttonStates[0], kThemeTopOutsideArrowPressed) |
1742                                             ConvertToPressState(buttonStates[1], kThemeTopInsideArrowPressed) |
1743                                             ConvertToPressState(buttonStates[2], kThemeBottomInsideArrowPressed) |
1744                                             ConvertToPressState(buttonStates[3], kThemeBottomOutsideArrowPressed);
1745     } else {
1746       aTdi.trackInfo.scrollbar.pressState = ConvertToPressState(buttonStates[0], kThemeTopOutsideArrowPressed) |
1747                                             ConvertToPressState(buttonStates[1], kThemeBottomOutsideArrowPressed) |
1748                                             ConvertToPressState(buttonStates[2], kThemeTopOutsideArrowPressed) |
1749                                             ConvertToPressState(buttonStates[3], kThemeBottomOutsideArrowPressed);
1750     }
1751   }
1753   NS_OBJC_END_TRY_ABORT_BLOCK;
1756 static void
1757 RenderScrollbar(CGContextRef cgContext, const HIRect& aRenderRect, void* aData)
1759   HIThemeTrackDrawInfo* tdi = (HIThemeTrackDrawInfo*)aData;
1760   HIThemeDrawTrack(tdi, NULL, cgContext, HITHEME_ORIENTATION);
1763 void
1764 nsNativeThemeCocoa::DrawScrollbar(CGContextRef aCGContext, const HIRect& aBoxRect, nsIFrame *aFrame)
1766   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1768   HIThemeTrackDrawInfo tdi;
1769   GetScrollbarDrawInfo(tdi, aFrame, aBoxRect.size, true); // True means we want the press states
1770   RenderTransformedHIThemeControl(aCGContext, aBoxRect, RenderScrollbar, &tdi);
1772   NS_OBJC_END_TRY_ABORT_BLOCK;
1775 nsIFrame*
1776 nsNativeThemeCocoa::GetParentScrollbarFrame(nsIFrame *aFrame)
1778   // Walk our parents to find a scrollbar frame
1779   nsIFrame *scrollbarFrame = aFrame;
1780   do {
1781     if (scrollbarFrame->GetType() == nsGkAtoms::scrollbarFrame) break;
1782   } while ((scrollbarFrame = scrollbarFrame->GetParent()));
1783   
1784   // We return null if we can't find a parent scrollbar frame
1785   return scrollbarFrame;
1788 static bool
1789 ToolbarCanBeUnified(CGContextRef cgContext, const HIRect& inBoxRect, NSWindow* aWindow)
1791   if (![aWindow isKindOfClass:[ToolbarWindow class]] ||
1792       [(ToolbarWindow*)aWindow drawsContentsIntoWindowFrame])
1793     return false;
1795   float unifiedToolbarHeight = [(ToolbarWindow*)aWindow unifiedToolbarHeight];
1796   CGAffineTransform ctm = CGContextGetUserSpaceToDeviceSpaceTransform(cgContext);
1797   CGRect deviceRect = CGRectApplyAffineTransform(inBoxRect, ctm);
1798   return inBoxRect.origin.x == 0 &&
1799          deviceRect.size.width >= [aWindow frame].size.width &&
1800          inBoxRect.origin.y <= 0.0 &&
1801          floor(inBoxRect.origin.y + inBoxRect.size.height) <= unifiedToolbarHeight;
1804 void
1805 nsNativeThemeCocoa::DrawUnifiedToolbar(CGContextRef cgContext, const HIRect& inBoxRect,
1806                                        NSWindow* aWindow)
1808   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1810   float titlebarHeight = [(ToolbarWindow*)aWindow titlebarHeight];
1811   float unifiedHeight = titlebarHeight + inBoxRect.size.height;
1813   BOOL isMain = [aWindow isMainWindow];
1815   CGContextSaveGState(cgContext);
1816   CGContextClipToRect(cgContext, inBoxRect);
1818   CGRect drawRect = CGRectOffset(inBoxRect, 0, -titlebarHeight);
1819   if (drawRect.size.width * drawRect.size.height <= CUIDRAW_MAX_AREA) {
1820     CUIDraw([NSWindow coreUIRenderer], drawRect, cgContext,
1821             (CFDictionaryRef)[NSDictionary dictionaryWithObjectsAndKeys:
1822               @"kCUIWidgetWindowFrame", @"widget",
1823               @"regularwin", @"windowtype",
1824               (isMain ? @"normal" : @"inactive"), @"state",
1825               [NSNumber numberWithInt:unifiedHeight], @"kCUIWindowFrameUnifiedTitleBarHeightKey",
1826               [NSNumber numberWithBool:YES], @"kCUIWindowFrameDrawTitleSeparatorKey",
1827               [NSNumber numberWithBool:YES], @"is.flipped",
1828               nil],
1829             nil);
1830   }
1832   CGContextRestoreGState(cgContext);
1834   NS_OBJC_END_TRY_ABORT_BLOCK;
1837 void
1838 nsNativeThemeCocoa::DrawStatusBar(CGContextRef cgContext, const HIRect& inBoxRect,
1839                                   nsIFrame *aFrame)
1841   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1843   if (inBoxRect.size.height < 2.0f)
1844     return;
1846   CGContextSaveGState(cgContext);
1847   CGContextClipToRect(cgContext, inBoxRect);
1849   // kCUIWidgetWindowFrame draws a complete window frame with both title bar
1850   // and bottom bar. We only want the bottom bar, so we extend the draw rect
1851   // upwards to make space for the title bar, and then we clip it away.
1852   CGRect drawRect = inBoxRect;
1853   const int extendUpwards = 40;
1854   drawRect.origin.y -= extendUpwards;
1855   drawRect.size.height += extendUpwards;
1856   if (drawRect.size.width * drawRect.size.height <= CUIDRAW_MAX_AREA) {
1857     CUIDraw([NSWindow coreUIRenderer], drawRect, cgContext,
1858             (CFDictionaryRef)[NSDictionary dictionaryWithObjectsAndKeys:
1859               @"kCUIWidgetWindowFrame", @"widget",
1860               @"regularwin", @"windowtype",
1861               (IsActive(aFrame, YES) ? @"normal" : @"inactive"), @"state",
1862               [NSNumber numberWithInt:inBoxRect.size.height], @"kCUIWindowFrameBottomBarHeightKey",
1863               [NSNumber numberWithBool:YES], @"kCUIWindowFrameDrawBottomBarSeparatorKey",
1864               [NSNumber numberWithBool:YES], @"is.flipped",
1865               nil],
1866             nil);
1867   }
1869   CGContextRestoreGState(cgContext);
1871   NS_OBJC_END_TRY_ABORT_BLOCK;
1874 static void
1875 RenderResizer(CGContextRef cgContext, const HIRect& aRenderRect, void* aData)
1877   HIThemeGrowBoxDrawInfo* drawInfo = (HIThemeGrowBoxDrawInfo*)aData;
1878   HIThemeDrawGrowBox(&CGPointZero, drawInfo, cgContext, kHIThemeOrientationNormal);
1881 void
1882 nsNativeThemeCocoa::DrawResizer(CGContextRef cgContext, const HIRect& aRect,
1883                                 nsIFrame *aFrame)
1885   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1887   HIThemeGrowBoxDrawInfo drawInfo;
1888   drawInfo.version = 0;
1889   drawInfo.state = kThemeStateActive;
1890   drawInfo.kind = kHIThemeGrowBoxKindNormal;
1891   drawInfo.direction = kThemeGrowRight | kThemeGrowDown;
1892   drawInfo.size = kHIThemeGrowBoxSizeNormal;
1894   RenderTransformedHIThemeControl(cgContext, aRect, RenderResizer, &drawInfo,
1895                                   IsFrameRTL(aFrame));
1897   NS_OBJC_END_TRY_ABORT_BLOCK;
1900 static bool
1901 IsHiDPIContext(nsDeviceContext* aContext)
1903   return nsPresContext::AppUnitsPerCSSPixel() >=
1904     2 * aContext->UnscaledAppUnitsPerDevPixel();
1907 NS_IMETHODIMP
1908 nsNativeThemeCocoa::DrawWidgetBackground(nsRenderingContext* aContext,
1909                                          nsIFrame* aFrame,
1910                                          uint8_t aWidgetType,
1911                                          const nsRect& aRect,
1912                                          const nsRect& aDirtyRect)
1914   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
1916   // setup to draw into the correct port
1917   int32_t p2a = aContext->AppUnitsPerDevPixel();
1919   gfxRect nativeDirtyRect(aDirtyRect.x, aDirtyRect.y,
1920                           aDirtyRect.width, aDirtyRect.height);
1921   gfxRect nativeWidgetRect(aRect.x, aRect.y, aRect.width, aRect.height);
1922   nativeWidgetRect.ScaleInverse(gfxFloat(p2a));
1923   nativeDirtyRect.ScaleInverse(gfxFloat(p2a));
1924   nativeWidgetRect.Round();
1925   if (nativeWidgetRect.IsEmpty())
1926     return NS_OK; // Don't attempt to draw invisible widgets.
1928   gfxContext* thebesCtx = aContext->ThebesContext();
1929   if (!thebesCtx)
1930     return NS_ERROR_FAILURE;
1932   gfxContextMatrixAutoSaveRestore save(thebesCtx);
1934   bool hidpi = IsHiDPIContext(aContext->DeviceContext());
1935   if (hidpi) {
1936     // Use high-resolution drawing.
1937     nativeWidgetRect.ScaleInverse(2.0f);
1938     nativeDirtyRect.ScaleInverse(2.0f);
1939     thebesCtx->Scale(2.0f, 2.0f);
1940   }
1942   gfxQuartzNativeDrawing nativeDrawing(thebesCtx, nativeDirtyRect,
1943                                        hidpi ? 2.0f : 1.0f);
1945   CGContextRef cgContext = nativeDrawing.BeginNativeDrawing();
1946   if (cgContext == nullptr) {
1947     // The Quartz surface handles 0x0 surfaces by internally
1948     // making all operations no-ops; there's no cgcontext created for them.
1949     // Unfortunately, this means that callers that want to render
1950     // directly to the CGContext need to be aware of this quirk.
1951     return NS_OK;
1952   }
1954 #if 0
1955   if (1 /*aWidgetType == NS_THEME_TEXTFIELD*/) {
1956     fprintf(stderr, "Native theme drawing widget %d [%p] dis:%d in rect [%d %d %d %d]\n",
1957             aWidgetType, aFrame, IsDisabled(aFrame), aRect.x, aRect.y, aRect.width, aRect.height);
1958     fprintf(stderr, "Cairo matrix: [%f %f %f %f %f %f]\n",
1959             mat.xx, mat.yx, mat.xy, mat.yy, mat.x0, mat.y0);
1960     fprintf(stderr, "Native theme xform[0]: [%f %f %f %f %f %f]\n",
1961             mm0.a, mm0.b, mm0.c, mm0.d, mm0.tx, mm0.ty);
1962     CGAffineTransform mm = CGContextGetCTM(cgContext);
1963     fprintf(stderr, "Native theme xform[1]: [%f %f %f %f %f %f]\n",
1964             mm.a, mm.b, mm.c, mm.d, mm.tx, mm.ty);
1965   }
1966 #endif
1968   CGRect macRect = CGRectMake(nativeWidgetRect.X(), nativeWidgetRect.Y(),
1969                               nativeWidgetRect.Width(), nativeWidgetRect.Height());
1971 #if 0
1972   fprintf(stderr, "    --> macRect %f %f %f %f\n",
1973           macRect.origin.x, macRect.origin.y, macRect.size.width, macRect.size.height);
1974   CGRect bounds = CGContextGetClipBoundingBox(cgContext);
1975   fprintf(stderr, "    --> clip bounds: %f %f %f %f\n",
1976           bounds.origin.x, bounds.origin.y, bounds.size.width, bounds.size.height);
1978   //CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 1.0, 0.1);
1979   //CGContextFillRect(cgContext, bounds);
1980 #endif
1982   nsEventStates eventState = GetContentState(aFrame, aWidgetType);
1984   switch (aWidgetType) {
1985     case NS_THEME_DIALOG: {
1986       HIThemeSetFill(kThemeBrushDialogBackgroundActive, NULL, cgContext, HITHEME_ORIENTATION);
1987       CGContextFillRect(cgContext, macRect);
1988     }
1989       break;
1991     case NS_THEME_MENUPOPUP: {
1992       HIThemeMenuDrawInfo mdi;
1993       memset(&mdi, 0, sizeof(mdi));
1994       mdi.version = 0;
1995       mdi.menuType = IsDisabled(aFrame, eventState) ?
1996                        static_cast<ThemeMenuType>(kThemeMenuTypeInactive) :
1997                        static_cast<ThemeMenuType>(kThemeMenuTypePopUp);
1999       bool isLeftOfParent = false;
2000       if (IsSubmenu(aFrame, &isLeftOfParent) && !isLeftOfParent) {
2001         mdi.menuType = kThemeMenuTypeHierarchical;
2002       }
2003       
2004       // The rounded corners draw outside the frame.
2005       CGRect deflatedRect = CGRectMake(macRect.origin.x, macRect.origin.y + 4,
2006                                        macRect.size.width, macRect.size.height - 8);
2007       HIThemeDrawMenuBackground(&deflatedRect, &mdi, cgContext, HITHEME_ORIENTATION);
2008     }
2009       break;
2011     case NS_THEME_MENUITEM: {
2012       if (thebesCtx->OriginalSurface()->GetContentType() == gfxASurface::CONTENT_COLOR_ALPHA) {
2013         // Clear the background to get correct transparency.
2014         CGContextClearRect(cgContext, macRect);
2015       }
2017       // maybe use kThemeMenuItemHierBackground or PopUpBackground instead of just Plain?
2018       HIThemeMenuItemDrawInfo drawInfo;
2019       memset(&drawInfo, 0, sizeof(drawInfo));
2020       drawInfo.version = 0;
2021       drawInfo.itemType = kThemeMenuItemPlain;
2022       drawInfo.state = (IsDisabled(aFrame, eventState) ?
2023                          static_cast<ThemeMenuState>(kThemeMenuDisabled) :
2024                          CheckBooleanAttr(aFrame, nsGkAtoms::menuactive) ?
2025                            static_cast<ThemeMenuState>(kThemeMenuSelected) :
2026                            static_cast<ThemeMenuState>(kThemeMenuActive));
2028       // XXX pass in the menu rect instead of always using the item rect
2029       HIRect ignored;
2030       HIThemeDrawMenuItem(&macRect, &macRect, &drawInfo, cgContext, HITHEME_ORIENTATION, &ignored);
2031     }
2032       break;
2034     case NS_THEME_MENUSEPARATOR: {
2035       ThemeMenuState menuState;
2036       if (IsDisabled(aFrame, eventState)) {
2037         menuState = kThemeMenuDisabled;
2038       }
2039       else {
2040         menuState = CheckBooleanAttr(aFrame, nsGkAtoms::menuactive) ?
2041                     kThemeMenuSelected : kThemeMenuActive;
2042       }
2044       HIThemeMenuItemDrawInfo midi = { 0, kThemeMenuItemPlain, menuState };
2045       HIThemeDrawMenuSeparator(&macRect, &macRect, &midi, cgContext, HITHEME_ORIENTATION);
2046     }
2047       break;
2049     case NS_THEME_TOOLTIP:
2050       CGContextSetRGBFillColor(cgContext, 0.996, 1.000, 0.792, 0.950);
2051       CGContextFillRect(cgContext, macRect);
2052       break;
2054     case NS_THEME_CHECKBOX:
2055     case NS_THEME_RADIO: {
2056       bool isCheckbox = (aWidgetType == NS_THEME_CHECKBOX);
2057       DrawCheckboxOrRadio(cgContext, isCheckbox, macRect, GetCheckedOrSelected(aFrame, !isCheckbox),
2058                           eventState, aFrame);
2059     }
2060       break;
2062     case NS_THEME_BUTTON:
2063       if (IsDefaultButton(aFrame)) {
2064         if (!QueueAnimatedContentForRefresh(aFrame->GetContent(), 10)) {
2065           NS_WARNING("Unable to animate button!");
2066         }
2067         DrawButton(cgContext, kThemePushButton, macRect, true,
2068                    kThemeButtonOff, kThemeAdornmentNone, eventState, aFrame);
2069       } else if (IsButtonTypeMenu(aFrame)) {
2070         DrawDropdown(cgContext, macRect, eventState, aWidgetType, aFrame);
2071       } else {
2072         DrawPushButton(cgContext, macRect, eventState, aFrame);
2073       }
2074       break;
2076     case NS_THEME_BUTTON_BEVEL:
2077       DrawButton(cgContext, kThemeMediumBevelButton, macRect,
2078                  IsDefaultButton(aFrame), kThemeButtonOff, kThemeAdornmentNone,
2079                  eventState, aFrame);
2080       break;
2082     case NS_THEME_SPINNER: {
2083       ThemeDrawState state = kThemeStateActive;
2084       nsIContent* content = aFrame->GetContent();
2085       if (content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::state,
2086                                NS_LITERAL_STRING("up"), eCaseMatters)) {
2087         state = kThemeStatePressedUp;
2088       }
2089       else if (content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::state,
2090                                     NS_LITERAL_STRING("down"), eCaseMatters)) {
2091         state = kThemeStatePressedDown;
2092       }
2094       DrawSpinButtons(cgContext, kThemeIncDecButton, macRect, state,
2095                       kThemeAdornmentNone, eventState, aFrame);
2096     }
2097       break;
2099     case NS_THEME_TOOLBAR_BUTTON:
2100       DrawSegment(cgContext, macRect, eventState, aFrame, toolbarButtonRenderSettings);
2101       break;
2103     case NS_THEME_TOOLBAR_SEPARATOR: {
2104       HIThemeSeparatorDrawInfo sdi = { 0, kThemeStateActive };
2105       HIThemeDrawSeparator(&macRect, &sdi, cgContext, HITHEME_ORIENTATION);
2106     }
2107       break;
2109     case NS_THEME_MOZ_MAC_UNIFIED_TOOLBAR:
2110     case NS_THEME_TOOLBAR: {
2111       NSWindow* win = NativeWindowForFrame(aFrame);
2112       if (ToolbarCanBeUnified(cgContext, macRect, win)) {
2113         DrawUnifiedToolbar(cgContext, macRect, win);
2114         break;
2115       }
2116       BOOL isMain = [win isMainWindow];
2117       CGRect drawRect = macRect;
2119       // top border
2120       drawRect.size.height = 1.0f;
2121       DrawNativeGreyColorInRect(cgContext, toolbarTopBorderGrey, drawRect, isMain);
2123       // background
2124       drawRect.origin.y += drawRect.size.height;
2125       drawRect.size.height = macRect.size.height - 2.0f;
2126       DrawNativeGreyColorInRect(cgContext, toolbarFillGrey, drawRect, isMain);
2128       // bottom border
2129       drawRect.origin.y += drawRect.size.height;
2130       drawRect.size.height = 1.0f;
2131       DrawNativeGreyColorInRect(cgContext, toolbarBottomBorderGrey, drawRect, isMain);
2132     }
2133       break;
2135     case NS_THEME_TOOLBOX: {
2136       HIThemeHeaderDrawInfo hdi = { 0, kThemeStateActive, kHIThemeHeaderKindWindow };
2137       HIThemeDrawHeader(&macRect, &hdi, cgContext, HITHEME_ORIENTATION);
2138     }
2139       break;
2141     case NS_THEME_STATUSBAR: 
2142       DrawStatusBar(cgContext, macRect, aFrame);
2143       break;
2145     case NS_THEME_DROPDOWN:
2146     case NS_THEME_DROPDOWN_TEXTFIELD:
2147       DrawDropdown(cgContext, macRect, eventState, aWidgetType, aFrame);
2148       break;
2150     case NS_THEME_DROPDOWN_BUTTON:
2151       DrawButton(cgContext, kThemeArrowButton, macRect, false, kThemeButtonOn,
2152                  kThemeAdornmentArrowDownArrow, eventState, aFrame);
2153       break;
2155     case NS_THEME_GROUPBOX: {
2156       HIThemeGroupBoxDrawInfo gdi = { 0, kThemeStateActive, kHIThemeGroupBoxKindPrimary };
2157       HIThemeDrawGroupBox(&macRect, &gdi, cgContext, HITHEME_ORIENTATION);
2158       break;
2159     }
2161     case NS_THEME_TEXTFIELD:
2162       // HIThemeSetFill is not available on 10.3
2163       CGContextSetRGBFillColor(cgContext, 1.0, 1.0, 1.0, 1.0);
2164       CGContextFillRect(cgContext, macRect);
2166       // XUL textboxes set the native appearance on the containing box, while
2167       // concrete focus is set on the html:input element within it. We can
2168       // though, check the focused attribute of xul textboxes in this case.
2169       // On Mac, focus rings are always shown for textboxes, so we do not need
2170       // to check the window's focus ring state here
2171       if (aFrame->GetContent()->IsXUL() && IsFocused(aFrame)) {
2172         eventState |= NS_EVENT_STATE_FOCUS;
2173       }
2175       DrawFrame(cgContext, kHIThemeFrameTextFieldSquare, macRect,
2176                 IsDisabled(aFrame, eventState) || IsReadOnly(aFrame), eventState);
2177       break;
2178       
2179     case NS_THEME_SEARCHFIELD:
2180       DrawSearchField(cgContext, macRect, aFrame, eventState);
2181       break;
2183     case NS_THEME_PROGRESSBAR:
2184       if (!QueueAnimatedContentForRefresh(aFrame->GetContent(), 30)) {
2185         NS_WARNING("Unable to animate progressbar!");
2186       }
2187       DrawProgress(cgContext, macRect, IsIndeterminateProgress(aFrame, eventState),
2188                    aFrame->GetStyleDisplay()->mOrient != NS_STYLE_ORIENT_VERTICAL,
2189                    GetProgressValue(aFrame), GetProgressMaxValue(aFrame), aFrame);
2190       break;
2192     case NS_THEME_PROGRESSBAR_VERTICAL:
2193       DrawProgress(cgContext, macRect, IsIndeterminateProgress(aFrame, eventState),
2194                    false, GetProgressValue(aFrame),
2195                    GetProgressMaxValue(aFrame), aFrame);
2196       break;
2198     case NS_THEME_METERBAR:
2199       DrawMeter(cgContext, macRect, aFrame);
2200       break;
2202     case NS_THEME_PROGRESSBAR_CHUNK:
2203     case NS_THEME_PROGRESSBAR_CHUNK_VERTICAL:
2204     case NS_THEME_METERBAR_CHUNK:
2205       // Do nothing: progress and meter bars cases will draw chunks.
2206       break;
2208     case NS_THEME_TREEVIEW_TWISTY:
2209       DrawButton(cgContext, kThemeDisclosureButton, macRect, false,
2210                  kThemeDisclosureRight, kThemeAdornmentNone, eventState, aFrame);
2211       break;
2213     case NS_THEME_TREEVIEW_TWISTY_OPEN:
2214       DrawButton(cgContext, kThemeDisclosureButton, macRect, false,
2215                  kThemeDisclosureDown, kThemeAdornmentNone, eventState, aFrame);
2216       break;
2218     case NS_THEME_TREEVIEW_HEADER_CELL: {
2219       TreeSortDirection sortDirection = GetTreeSortDirection(aFrame);
2220       DrawButton(cgContext, kThemeListHeaderButton, macRect, false,
2221                  sortDirection == eTreeSortDirection_Natural ? kThemeButtonOff : kThemeButtonOn,
2222                  sortDirection == eTreeSortDirection_Ascending ?
2223                  kThemeAdornmentHeaderButtonSortUp : kThemeAdornmentNone, eventState, aFrame);
2224     }
2225       break;
2227     case NS_THEME_TREEVIEW_TREEITEM:
2228     case NS_THEME_TREEVIEW:
2229       // HIThemeSetFill is not available on 10.3
2230       // HIThemeSetFill(kThemeBrushWhite, NULL, cgContext, HITHEME_ORIENTATION);
2231       CGContextSetRGBFillColor(cgContext, 1.0, 1.0, 1.0, 1.0);
2232       CGContextFillRect(cgContext, macRect);
2233       break;
2235     case NS_THEME_TREEVIEW_HEADER:
2236       // do nothing, taken care of by individual header cells
2237     case NS_THEME_TREEVIEW_HEADER_SORTARROW:
2238       // do nothing, taken care of by treeview header
2239     case NS_THEME_TREEVIEW_LINE:
2240       // do nothing, these lines don't exist on macos
2241       break;
2243     case NS_THEME_SCALE_HORIZONTAL:
2244     case NS_THEME_SCALE_VERTICAL: {
2245       int32_t curpos = CheckIntAttr(aFrame, nsGkAtoms::curpos, 0);
2246       int32_t minpos = CheckIntAttr(aFrame, nsGkAtoms::minpos, 0);
2247       int32_t maxpos = CheckIntAttr(aFrame, nsGkAtoms::maxpos, 100);
2248       if (!maxpos)
2249         maxpos = 100;
2251       bool reverse = aFrame->GetContent()->
2252         AttrValueIs(kNameSpaceID_None, nsGkAtoms::dir,
2253                     NS_LITERAL_STRING("reverse"), eCaseMatters);
2254       DrawScale(cgContext, macRect, eventState,
2255                 (aWidgetType == NS_THEME_SCALE_VERTICAL), reverse,
2256                 curpos, minpos, maxpos, aFrame);
2257     }
2258       break;
2260     case NS_THEME_SCALE_THUMB_HORIZONTAL:
2261     case NS_THEME_SCALE_THUMB_VERTICAL:
2262       // do nothing, drawn by scale
2263       break;
2265     case NS_THEME_SCROLLBAR_SMALL:
2266     case NS_THEME_SCROLLBAR: {
2267       DrawScrollbar(cgContext, macRect, aFrame);
2268     }
2269       break;
2270     case NS_THEME_SCROLLBAR_THUMB_VERTICAL:
2271     case NS_THEME_SCROLLBAR_THUMB_HORIZONTAL:
2272 #if SCROLLBARS_VISUAL_DEBUG
2273       CGContextSetRGBFillColor(cgContext, 1.0, 1.0, 0, 0.6);
2274       CGContextFillRect(cgContext, macRect);
2275     break;
2276 #endif
2277     case NS_THEME_SCROLLBAR_BUTTON_UP:
2278     case NS_THEME_SCROLLBAR_BUTTON_LEFT:
2279 #if SCROLLBARS_VISUAL_DEBUG
2280       CGContextSetRGBFillColor(cgContext, 1.0, 0, 0, 0.6);
2281       CGContextFillRect(cgContext, macRect);
2282     break;
2283 #endif
2284     case NS_THEME_SCROLLBAR_BUTTON_DOWN:
2285     case NS_THEME_SCROLLBAR_BUTTON_RIGHT:
2286 #if SCROLLBARS_VISUAL_DEBUG
2287       CGContextSetRGBFillColor(cgContext, 0, 1.0, 0, 0.6);
2288       CGContextFillRect(cgContext, macRect);
2289     break;      
2290 #endif
2291     case NS_THEME_SCROLLBAR_TRACK_HORIZONTAL:
2292     case NS_THEME_SCROLLBAR_TRACK_VERTICAL:
2293       // do nothing, drawn by scrollbar
2294       break;
2296     case NS_THEME_TEXTFIELD_MULTILINE: {
2297       // we have to draw this by hand because there is no HITheme value for it
2298       CGContextSetRGBFillColor(cgContext, 1.0, 1.0, 1.0, 1.0);
2299       
2300       CGContextFillRect(cgContext, macRect);
2302       CGContextSetLineWidth(cgContext, 1.0);
2303       CGContextSetShouldAntialias(cgContext, false);
2305       // stroke everything but the top line of the text area
2306       CGContextSetRGBStrokeColor(cgContext, 0.6, 0.6, 0.6, 1.0);
2307       CGContextBeginPath(cgContext);
2308       CGContextMoveToPoint(cgContext, macRect.origin.x, macRect.origin.y + 1);
2309       CGContextAddLineToPoint(cgContext, macRect.origin.x, macRect.origin.y + macRect.size.height);
2310       CGContextAddLineToPoint(cgContext, macRect.origin.x + macRect.size.width - 1, macRect.origin.y + macRect.size.height);
2311       CGContextAddLineToPoint(cgContext, macRect.origin.x + macRect.size.width - 1, macRect.origin.y + 1);
2312       CGContextStrokePath(cgContext);
2314       // stroke the line across the top of the text area
2315       CGContextSetRGBStrokeColor(cgContext, 0.4510, 0.4510, 0.4510, 1.0);
2316       CGContextBeginPath(cgContext);
2317       CGContextMoveToPoint(cgContext, macRect.origin.x, macRect.origin.y + 1);
2318       CGContextAddLineToPoint(cgContext, macRect.origin.x + macRect.size.width - 1, macRect.origin.y + 1);
2319       CGContextStrokePath(cgContext);
2321       // draw a focus ring
2322       if (eventState.HasState(NS_EVENT_STATE_FOCUS)) {
2323         // We need to bring the rectangle in by 1 pixel on each side.
2324         CGRect cgr = CGRectMake(macRect.origin.x + 1,
2325                                 macRect.origin.y + 1,
2326                                 macRect.size.width - 2,
2327                                 macRect.size.height - 2);
2328         HIThemeDrawFocusRect(&cgr, true, cgContext, kHIThemeOrientationNormal);
2329       }
2330     }
2331       break;
2333     case NS_THEME_LISTBOX: {
2334       // We have to draw this by hand because kHIThemeFrameListBox drawing
2335       // is buggy on 10.5, see bug 579259.
2336       CGContextSetRGBFillColor(cgContext, 1.0, 1.0, 1.0, 1.0);
2337       CGContextFillRect(cgContext, macRect);
2339       // #8E8E8E for the top border, #BEBEBE for the rest.
2340       float x = macRect.origin.x, y = macRect.origin.y;
2341       float w = macRect.size.width, h = macRect.size.height;
2342       CGContextSetRGBFillColor(cgContext, 0.557, 0.557, 0.557, 1.0);
2343       CGContextFillRect(cgContext, CGRectMake(x, y, w, 1));
2344       CGContextSetRGBFillColor(cgContext, 0.745, 0.745, 0.745, 1.0);
2345       CGContextFillRect(cgContext, CGRectMake(x, y + 1, 1, h - 1));
2346       CGContextFillRect(cgContext, CGRectMake(x + w - 1, y + 1, 1, h - 1));
2347       CGContextFillRect(cgContext, CGRectMake(x + 1, y + h - 1, w - 2, 1));
2348     }
2349       break;
2350     
2351     case NS_THEME_TAB:
2352       DrawSegment(cgContext, macRect, eventState, aFrame, tabRenderSettings);
2353       break;
2355     case NS_THEME_TAB_PANELS:
2356       DrawTabPanel(cgContext, macRect, aFrame);
2357       break;
2359     case NS_THEME_RESIZER:
2360       DrawResizer(cgContext, macRect, aFrame);
2361       break;
2362   }
2364   nativeDrawing.EndNativeDrawing();
2366   return NS_OK;
2368   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
2371 nsIntMargin
2372 nsNativeThemeCocoa::RTLAwareMargin(const nsIntMargin& aMargin, nsIFrame* aFrame)
2374   if (IsFrameRTL(aFrame))
2375     return nsIntMargin(aMargin.right, aMargin.top, aMargin.left, aMargin.bottom);
2377   return aMargin;
2380 static const nsIntMargin kAquaDropdownBorder(5, 1, 22, 2);
2381 static const nsIntMargin kAquaComboboxBorder(4, 3, 20, 3);
2382 static const nsIntMargin kAquaSearchfieldBorder(19, 3, 5, 2);
2384 NS_IMETHODIMP
2385 nsNativeThemeCocoa::GetWidgetBorder(nsDeviceContext* aContext, 
2386                                     nsIFrame* aFrame,
2387                                     uint8_t aWidgetType,
2388                                     nsIntMargin* aResult)
2390   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
2392   aResult->SizeTo(0, 0, 0, 0);
2394   switch (aWidgetType) {
2395     case NS_THEME_BUTTON:
2396     {
2397       if (IsButtonTypeMenu(aFrame)) {
2398         *aResult = RTLAwareMargin(kAquaDropdownBorder, aFrame);
2399       } else {
2400         aResult->SizeTo(7, 1, 7, 3);
2401       }
2402       break;
2403     }
2405     case NS_THEME_TOOLBAR_BUTTON:
2406     {
2407       aResult->SizeTo(4, 1, 4, 1);
2408       break;
2409     }
2411     case NS_THEME_CHECKBOX:
2412     case NS_THEME_RADIO:
2413     {
2414       // nsFormControlFrame::GetIntrinsicWidth and nsFormControlFrame::GetIntrinsicHeight
2415       // assume a border width of 2px.
2416       aResult->SizeTo(2, 2, 2, 2);
2417       break;
2418     }
2420     case NS_THEME_DROPDOWN:
2421     case NS_THEME_DROPDOWN_BUTTON:
2422       *aResult = RTLAwareMargin(kAquaDropdownBorder, aFrame);
2423       break;
2425     case NS_THEME_DROPDOWN_TEXTFIELD:
2426       *aResult = RTLAwareMargin(kAquaComboboxBorder, aFrame);
2427       break;
2429     case NS_THEME_TEXTFIELD:
2430     {
2431       SInt32 frameOutset = 0;
2432       ::GetThemeMetric(kThemeMetricEditTextFrameOutset, &frameOutset);
2434       SInt32 textPadding = 0;
2435       ::GetThemeMetric(kThemeMetricEditTextWhitespace, &textPadding);
2437       frameOutset += textPadding;
2439       aResult->SizeTo(frameOutset, frameOutset, frameOutset, frameOutset);
2440       break;
2441     }
2443     case NS_THEME_TEXTFIELD_MULTILINE:
2444       aResult->SizeTo(1, 1, 1, 1);
2445       break;
2447     case NS_THEME_SEARCHFIELD:
2448       *aResult = RTLAwareMargin(kAquaSearchfieldBorder, aFrame);
2449       break;
2451     case NS_THEME_LISTBOX:
2452     {
2453       SInt32 frameOutset = 0;
2454       ::GetThemeMetric(kThemeMetricListBoxFrameOutset, &frameOutset);
2455       aResult->SizeTo(frameOutset, frameOutset, frameOutset, frameOutset);
2456       break;
2457     }
2459     case NS_THEME_SCROLLBAR_TRACK_HORIZONTAL:
2460     case NS_THEME_SCROLLBAR_TRACK_VERTICAL:
2461     {
2462       // On Lion and later, scrollbars have no arrows.
2463       if (!nsCocoaFeatures::OnLionOrLater()) {
2464         // There's only an endcap to worry about when both arrows are on the bottom
2465         NSString *buttonPlacement = [[NSUserDefaults standardUserDefaults] objectForKey:@"AppleScrollBarVariant"];
2466         if (!buttonPlacement || [buttonPlacement isEqualToString:@"DoubleMax"]) {
2467           bool isHorizontal = (aWidgetType == NS_THEME_SCROLLBAR_TRACK_HORIZONTAL);
2469           nsIFrame *scrollbarFrame = GetParentScrollbarFrame(aFrame);
2470           if (!scrollbarFrame) return NS_ERROR_FAILURE;
2471           bool isSmall = (scrollbarFrame->GetStyleDisplay()->mAppearance == NS_THEME_SCROLLBAR_SMALL);
2473           // There isn't a metric for this, so just hardcode a best guess at the value.
2474           // This value is even less exact due to the fact that the endcap is partially concave.
2475           int32_t endcapSize = isSmall ? 5 : 6;
2477           if (isHorizontal)
2478             aResult->SizeTo(endcapSize, 0, 0, 0);
2479           else
2480             aResult->SizeTo(0, endcapSize, 0, 0);
2481         }
2482       }
2483       break;
2484     }
2486     case NS_THEME_STATUSBAR:
2487       aResult->SizeTo(0, 1, 0, 0);
2488       break;
2489   }
2491   if (IsHiDPIContext(aContext)) {
2492     *aResult = *aResult + *aResult; // doubled
2493   }
2495   return NS_OK;
2497   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
2500 // Return false here to indicate that CSS padding values should be used. There is
2501 // no reason to make a distinction between padding and border values, just specify
2502 // whatever values you want in GetWidgetBorder and only use this to return true
2503 // if you want to override CSS padding values.
2504 bool
2505 nsNativeThemeCocoa::GetWidgetPadding(nsDeviceContext* aContext, 
2506                                      nsIFrame* aFrame,
2507                                      uint8_t aWidgetType,
2508                                      nsIntMargin* aResult)
2510   // We don't want CSS padding being used for certain widgets.
2511   // See bug 381639 for an example of why.
2512   switch (aWidgetType) {
2513     case NS_THEME_BUTTON:
2514     // Radios and checkboxes return a fixed size in GetMinimumWidgetSize
2515     // and have a meaningful baseline, so they can't have
2516     // author-specified padding.
2517     case NS_THEME_CHECKBOX:
2518     case NS_THEME_RADIO:
2519       aResult->SizeTo(0, 0, 0, 0);
2520       return true;
2521   }
2522   return false;
2525 bool
2526 nsNativeThemeCocoa::GetWidgetOverflow(nsDeviceContext* aContext, nsIFrame* aFrame,
2527                                       uint8_t aWidgetType, nsRect* aOverflowRect)
2529   int32_t p2a = aContext->AppUnitsPerDevPixel();
2530   switch (aWidgetType) {
2531     case NS_THEME_BUTTON:
2532     case NS_THEME_TOOLBAR_BUTTON:
2533     case NS_THEME_TEXTFIELD:
2534     case NS_THEME_TEXTFIELD_MULTILINE:
2535     case NS_THEME_SEARCHFIELD:
2536     case NS_THEME_LISTBOX:
2537     case NS_THEME_DROPDOWN:
2538     case NS_THEME_DROPDOWN_BUTTON:
2539     case NS_THEME_DROPDOWN_TEXTFIELD:
2540     case NS_THEME_CHECKBOX:
2541     case NS_THEME_RADIO:
2542     case NS_THEME_TAB:
2543     {
2544       // We assume that the above widgets can draw a focus ring that will be less than
2545       // or equal to 4 pixels thick.
2546       nsIntMargin extraSize = nsIntMargin(MAX_FOCUS_RING_WIDTH, MAX_FOCUS_RING_WIDTH, MAX_FOCUS_RING_WIDTH, MAX_FOCUS_RING_WIDTH);
2547       nsMargin m(NSIntPixelsToAppUnits(extraSize.left, p2a),
2548                  NSIntPixelsToAppUnits(extraSize.top, p2a),
2549                  NSIntPixelsToAppUnits(extraSize.right, p2a),
2550                  NSIntPixelsToAppUnits(extraSize.bottom, p2a));
2551       aOverflowRect->Inflate(m);
2552       return true;
2553     }
2554     case NS_THEME_PROGRESSBAR:
2555     {
2556       // Progress bars draw a 2 pixel white shadow under their progress indicators
2557       nsMargin m(0, 0, 0, NSIntPixelsToAppUnits(2, p2a));
2558       aOverflowRect->Inflate(m);
2559       return true;
2560     }
2561   }
2563   return false;
2566 static const int32_t kRegularScrollbarThumbMinSize = 22;
2567 static const int32_t kSmallScrollbarThumbMinSize = 19;
2569 NS_IMETHODIMP
2570 nsNativeThemeCocoa::GetMinimumWidgetSize(nsRenderingContext* aContext,
2571                                          nsIFrame* aFrame,
2572                                          uint8_t aWidgetType,
2573                                          nsIntSize* aResult,
2574                                          bool* aIsOverridable)
2576   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
2578   aResult->SizeTo(0,0);
2579   *aIsOverridable = true;
2581   switch (aWidgetType) {
2582     case NS_THEME_BUTTON:
2583     {
2584       aResult->SizeTo(pushButtonSettings.minimumSizes[miniControlSize].width,
2585                       pushButtonSettings.naturalSizes[miniControlSize].height);
2586       break;
2587     }
2589     case NS_THEME_TOOLBAR_BUTTON:
2590     {
2591       aResult->SizeTo(0, toolbarButtonHeights[miniControlSize]);
2592       break;
2593     }
2595     case NS_THEME_SPINNER:
2596     {
2597       SInt32 buttonHeight = 0, buttonWidth = 0;
2598       ::GetThemeMetric(kThemeMetricLittleArrowsWidth, &buttonWidth);
2599       ::GetThemeMetric(kThemeMetricLittleArrowsHeight, &buttonHeight);
2600       aResult->SizeTo(buttonWidth, buttonHeight);
2601       *aIsOverridable = false;
2602       break;
2603     }
2605     case NS_THEME_DROPDOWN:
2606     case NS_THEME_DROPDOWN_BUTTON:
2607     {
2608       SInt32 popupHeight = 0;
2609       ::GetThemeMetric(kThemeMetricPopupButtonHeight, &popupHeight);
2610       aResult->SizeTo(0, popupHeight);
2611       break;
2612     }
2614     case NS_THEME_TEXTFIELD:
2615     case NS_THEME_TEXTFIELD_MULTILINE:
2616     case NS_THEME_SEARCHFIELD:
2617     {
2618       // at minimum, we should be tall enough for 9pt text.
2619       // I'm using hardcoded values here because the appearance manager
2620       // values for the frame size are incorrect.
2621       aResult->SizeTo(0, (2 + 2) /* top */ + 9 + (1 + 1) /* bottom */);
2622       break;
2623     }
2624       
2625     case NS_THEME_PROGRESSBAR:
2626     {
2627       SInt32 barHeight = 0;
2628       ::GetThemeMetric(kThemeMetricNormalProgressBarThickness, &barHeight);
2629       aResult->SizeTo(0, barHeight);
2630       break;
2631     }
2633     case NS_THEME_TREEVIEW_TWISTY:
2634     case NS_THEME_TREEVIEW_TWISTY_OPEN:   
2635     {
2636       SInt32 twistyHeight = 0, twistyWidth = 0;
2637       ::GetThemeMetric(kThemeMetricDisclosureButtonWidth, &twistyWidth);
2638       ::GetThemeMetric(kThemeMetricDisclosureButtonHeight, &twistyHeight);
2639       aResult->SizeTo(twistyWidth, twistyHeight);
2640       *aIsOverridable = false;
2641       break;
2642     }
2643     
2644     case NS_THEME_TREEVIEW_HEADER:
2645     case NS_THEME_TREEVIEW_HEADER_CELL:
2646     {
2647       SInt32 headerHeight = 0;
2648       ::GetThemeMetric(kThemeMetricListHeaderHeight, &headerHeight);
2649       aResult->SizeTo(0, headerHeight - 1); // We don't need the top border.
2650       break;
2651     }
2653     case NS_THEME_TAB:
2654     {
2655       aResult->SizeTo(0, tabHeights[miniControlSize]);
2656       break;
2657     }
2659     case NS_THEME_SCALE_HORIZONTAL:
2660     {
2661       SInt32 scaleHeight = 0;
2662       ::GetThemeMetric(kThemeMetricHSliderHeight, &scaleHeight);
2663       aResult->SizeTo(scaleHeight, scaleHeight);
2664       *aIsOverridable = false;
2665       break;
2666     }
2668     case NS_THEME_SCALE_VERTICAL:
2669     {
2670       SInt32 scaleWidth = 0;
2671       ::GetThemeMetric(kThemeMetricVSliderWidth, &scaleWidth);
2672       aResult->SizeTo(scaleWidth, scaleWidth);
2673       *aIsOverridable = false;
2674       break;
2675     }
2676       
2677     case NS_THEME_SCROLLBAR_SMALL:
2678     {
2679       SInt32 scrollbarWidth = 0;
2680       ::GetThemeMetric(kThemeMetricSmallScrollBarWidth, &scrollbarWidth);
2681       aResult->SizeTo(scrollbarWidth, scrollbarWidth);
2682       *aIsOverridable = false;
2683       break;
2684     }
2686     case NS_THEME_SCROLLBAR_THUMB_HORIZONTAL:
2687     case NS_THEME_SCROLLBAR_THUMB_VERTICAL:
2688     {
2689       // Find our parent scrollbar frame in order to find out whether we're in
2690       // a small or a large scrollbar.
2691       nsIFrame *scrollbarFrame = GetParentScrollbarFrame(aFrame);
2692       if (!scrollbarFrame)
2693         return NS_ERROR_FAILURE;
2695       bool isSmall = (scrollbarFrame->GetStyleDisplay()->mAppearance == NS_THEME_SCROLLBAR_SMALL);
2696       bool isHorizontal = (aWidgetType == NS_THEME_SCROLLBAR_THUMB_HORIZONTAL);
2697       int32_t& minSize = isHorizontal ? aResult->width : aResult->height;
2698       minSize = isSmall ? kSmallScrollbarThumbMinSize : kRegularScrollbarThumbMinSize;
2699       break;
2700     }
2702     case NS_THEME_SCROLLBAR:
2703     case NS_THEME_SCROLLBAR_TRACK_VERTICAL:
2704     case NS_THEME_SCROLLBAR_TRACK_HORIZONTAL:
2705     {
2706       // yeah, i know i'm cheating a little here, but i figure that it
2707       // really doesn't matter if the scrollbar is vertical or horizontal
2708       // and the width metric is a really good metric for every piece
2709       // of the scrollbar.
2711       nsIFrame *scrollbarFrame = GetParentScrollbarFrame(aFrame);
2712       if (!scrollbarFrame) return NS_ERROR_FAILURE;
2714       int32_t themeMetric = (scrollbarFrame->GetStyleDisplay()->mAppearance == NS_THEME_SCROLLBAR_SMALL) ?
2715                             kThemeMetricSmallScrollBarWidth :
2716                             kThemeMetricScrollBarWidth;
2717       SInt32 scrollbarWidth = 0;
2718       ::GetThemeMetric(themeMetric, &scrollbarWidth);
2719       aResult->SizeTo(scrollbarWidth, scrollbarWidth);
2720       *aIsOverridable = false;
2721       break;
2722     }
2724     case NS_THEME_SCROLLBAR_BUTTON_UP:
2725     case NS_THEME_SCROLLBAR_BUTTON_DOWN:
2726     case NS_THEME_SCROLLBAR_BUTTON_LEFT:
2727     case NS_THEME_SCROLLBAR_BUTTON_RIGHT:
2728     {
2729       nsIFrame *scrollbarFrame = GetParentScrollbarFrame(aFrame);
2730       if (!scrollbarFrame) return NS_ERROR_FAILURE;
2732       // Since there is no NS_THEME_SCROLLBAR_BUTTON_UP_SMALL we need to ask the parent what appearance style it has.
2733       int32_t themeMetric = (scrollbarFrame->GetStyleDisplay()->mAppearance == NS_THEME_SCROLLBAR_SMALL) ?
2734                             kThemeMetricSmallScrollBarWidth :
2735                             kThemeMetricScrollBarWidth;
2736       SInt32 scrollbarWidth = 0;
2737       ::GetThemeMetric(themeMetric, &scrollbarWidth);
2739       // It seems that for both sizes of scrollbar, the buttons are one pixel "longer".
2740       if (aWidgetType == NS_THEME_SCROLLBAR_BUTTON_LEFT || aWidgetType == NS_THEME_SCROLLBAR_BUTTON_RIGHT)
2741         aResult->SizeTo(scrollbarWidth+1, scrollbarWidth);
2742       else
2743         aResult->SizeTo(scrollbarWidth, scrollbarWidth+1);
2745       *aIsOverridable = false;
2746       break;
2747     }
2748     case NS_THEME_RESIZER:
2749     {
2750       HIThemeGrowBoxDrawInfo drawInfo;
2751       drawInfo.version = 0;
2752       drawInfo.state = kThemeStateActive;
2753       drawInfo.kind = kHIThemeGrowBoxKindNormal;
2754       drawInfo.direction = kThemeGrowRight | kThemeGrowDown;
2755       drawInfo.size = kHIThemeGrowBoxSizeNormal;
2756       HIPoint pnt = { 0, 0 };
2757       HIRect bounds;
2758       HIThemeGetGrowBoxBounds(&pnt, &drawInfo, &bounds);
2759       aResult->SizeTo(bounds.size.width, bounds.size.height);
2760       *aIsOverridable = false;
2761     }
2762   }
2764   if (IsHiDPIContext(aContext->DeviceContext())) {
2765     *aResult = *aResult * 2;
2766   }
2768   return NS_OK;
2770   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
2773 NS_IMETHODIMP
2774 nsNativeThemeCocoa::WidgetStateChanged(nsIFrame* aFrame, uint8_t aWidgetType, 
2775                                      nsIAtom* aAttribute, bool* aShouldRepaint)
2777   // Some widget types just never change state.
2778   switch (aWidgetType) {
2779     case NS_THEME_TOOLBOX:
2780     case NS_THEME_TOOLBAR:
2781     case NS_THEME_MOZ_MAC_UNIFIED_TOOLBAR:
2782     case NS_THEME_SCROLLBAR_TRACK_VERTICAL: 
2783     case NS_THEME_SCROLLBAR_TRACK_HORIZONTAL:
2784     case NS_THEME_STATUSBAR:
2785     case NS_THEME_STATUSBAR_PANEL:
2786     case NS_THEME_STATUSBAR_RESIZER_PANEL:
2787     case NS_THEME_TOOLTIP:
2788     case NS_THEME_TAB_PANELS:
2789     case NS_THEME_TAB_PANEL:
2790     case NS_THEME_DIALOG:
2791     case NS_THEME_MENUPOPUP:
2792     case NS_THEME_GROUPBOX:
2793     case NS_THEME_PROGRESSBAR_CHUNK:
2794     case NS_THEME_PROGRESSBAR_CHUNK_VERTICAL:
2795     case NS_THEME_PROGRESSBAR:
2796     case NS_THEME_PROGRESSBAR_VERTICAL:
2797     case NS_THEME_METERBAR:
2798     case NS_THEME_METERBAR_CHUNK:
2799       *aShouldRepaint = false;
2800       return NS_OK;
2801   }
2803   // XXXdwh Not sure what can really be done here.  Can at least guess for
2804   // specific widgets that they're highly unlikely to have certain states.
2805   // For example, a toolbar doesn't care about any states.
2806   if (!aAttribute) {
2807     // Hover/focus/active changed.  Always repaint.
2808     *aShouldRepaint = true;
2809   } else {
2810     // Check the attribute to see if it's relevant.  
2811     // disabled, checked, dlgtype, default, etc.
2812     *aShouldRepaint = false;
2813     if (aAttribute == nsGkAtoms::disabled ||
2814         aAttribute == nsGkAtoms::checked ||
2815         aAttribute == nsGkAtoms::selected ||
2816         aAttribute == nsGkAtoms::menuactive ||
2817         aAttribute == nsGkAtoms::sortDirection ||
2818         aAttribute == nsGkAtoms::focused ||
2819         aAttribute == nsGkAtoms::_default ||
2820         aAttribute == nsGkAtoms::open)
2821       *aShouldRepaint = true;
2822   }
2824   return NS_OK;
2827 NS_IMETHODIMP
2828 nsNativeThemeCocoa::ThemeChanged()
2830   // This is unimplemented because we don't care if gecko changes its theme
2831   // and Mac OS X doesn't have themes.
2832   return NS_OK;
2835 bool 
2836 nsNativeThemeCocoa::ThemeSupportsWidget(nsPresContext* aPresContext, nsIFrame* aFrame,
2837                                       uint8_t aWidgetType)
2839   // We don't have CSS set up to render non-native scrollbars on Mac OS X so we
2840   // render natively even if native theme support is disabled.
2841   if (aWidgetType != NS_THEME_SCROLLBAR &&
2842       aPresContext && !aPresContext->PresShell()->IsThemeSupportEnabled())
2843     return false;
2845   // if this is a dropdown button in a combobox the answer is always no
2846   if (aWidgetType == NS_THEME_DROPDOWN_BUTTON) {
2847     nsIFrame* parentFrame = aFrame->GetParent();
2848     if (parentFrame && (parentFrame->GetType() == nsGkAtoms::comboboxControlFrame))
2849       return false;
2850   }
2852   switch (aWidgetType) {
2853     case NS_THEME_LISTBOX:
2855     case NS_THEME_DIALOG:
2856     case NS_THEME_WINDOW:
2857     case NS_THEME_MENUPOPUP:
2858     case NS_THEME_MENUITEM:
2859     case NS_THEME_MENUSEPARATOR:
2860     case NS_THEME_TOOLTIP:
2861     
2862     case NS_THEME_CHECKBOX:
2863     case NS_THEME_CHECKBOX_CONTAINER:
2864     case NS_THEME_RADIO:
2865     case NS_THEME_RADIO_CONTAINER:
2866     case NS_THEME_GROUPBOX:
2867     case NS_THEME_BUTTON:
2868     case NS_THEME_BUTTON_BEVEL:
2869     case NS_THEME_TOOLBAR_BUTTON:
2870     case NS_THEME_SPINNER:
2871     case NS_THEME_TOOLBAR:
2872     case NS_THEME_MOZ_MAC_UNIFIED_TOOLBAR:
2873     case NS_THEME_STATUSBAR:
2874     case NS_THEME_TEXTFIELD:
2875     case NS_THEME_TEXTFIELD_MULTILINE:
2876     case NS_THEME_SEARCHFIELD:
2877     //case NS_THEME_TOOLBOX:
2878     //case NS_THEME_TOOLBAR_BUTTON:
2879     case NS_THEME_PROGRESSBAR:
2880     case NS_THEME_PROGRESSBAR_VERTICAL:
2881     case NS_THEME_PROGRESSBAR_CHUNK:
2882     case NS_THEME_PROGRESSBAR_CHUNK_VERTICAL:
2883     case NS_THEME_METERBAR:
2884     case NS_THEME_METERBAR_CHUNK:
2885     case NS_THEME_TOOLBAR_SEPARATOR:
2886     
2887     case NS_THEME_TAB_PANELS:
2888     case NS_THEME_TAB:
2889     
2890     case NS_THEME_TREEVIEW_TWISTY:
2891     case NS_THEME_TREEVIEW_TWISTY_OPEN:
2892     case NS_THEME_TREEVIEW:
2893     case NS_THEME_TREEVIEW_HEADER:
2894     case NS_THEME_TREEVIEW_HEADER_CELL:
2895     case NS_THEME_TREEVIEW_HEADER_SORTARROW:
2896     case NS_THEME_TREEVIEW_TREEITEM:
2897     case NS_THEME_TREEVIEW_LINE:
2899     case NS_THEME_SCALE_HORIZONTAL:
2900     case NS_THEME_SCALE_THUMB_HORIZONTAL:
2901     case NS_THEME_SCALE_VERTICAL:
2902     case NS_THEME_SCALE_THUMB_VERTICAL:
2904     case NS_THEME_SCROLLBAR:
2905     case NS_THEME_SCROLLBAR_SMALL:
2906     case NS_THEME_SCROLLBAR_BUTTON_UP:
2907     case NS_THEME_SCROLLBAR_BUTTON_DOWN:
2908     case NS_THEME_SCROLLBAR_BUTTON_LEFT:
2909     case NS_THEME_SCROLLBAR_BUTTON_RIGHT:
2910     case NS_THEME_SCROLLBAR_THUMB_HORIZONTAL:
2911     case NS_THEME_SCROLLBAR_THUMB_VERTICAL:
2912     case NS_THEME_SCROLLBAR_TRACK_VERTICAL:
2913     case NS_THEME_SCROLLBAR_TRACK_HORIZONTAL:
2915     case NS_THEME_DROPDOWN:
2916     case NS_THEME_DROPDOWN_BUTTON:
2917     case NS_THEME_DROPDOWN_TEXT:
2918     case NS_THEME_DROPDOWN_TEXTFIELD:
2919       return !IsWidgetStyled(aPresContext, aFrame, aWidgetType);
2920       break;
2922     case NS_THEME_RESIZER:
2923     {
2924       nsIFrame* parentFrame = aFrame->GetParent();
2925       if (!parentFrame || parentFrame->GetType() != nsGkAtoms::scrollFrame)
2926         return true;
2928       // Note that IsWidgetStyled is not called for resizers on Mac. This is
2929       // because for scrollable containers, the native resizer looks better
2930       // when scrollbars are present even when the style is overriden, and the
2931       // custom transparent resizer looks better when scrollbars are not
2932       // present.
2933       nsIScrollableFrame* scrollFrame = do_QueryFrame(parentFrame);
2934       return (scrollFrame && scrollFrame->GetScrollbarVisibility());
2935       break;
2936     }
2937   }
2939   return false;
2942 bool
2943 nsNativeThemeCocoa::WidgetIsContainer(uint8_t aWidgetType)
2945   // flesh this out at some point
2946   switch (aWidgetType) {
2947    case NS_THEME_DROPDOWN_BUTTON:
2948    case NS_THEME_RADIO:
2949    case NS_THEME_CHECKBOX:
2950    case NS_THEME_PROGRESSBAR:
2951    case NS_THEME_METERBAR:
2952     return false;
2953     break;
2954   }
2955   return true;
2958 bool
2959 nsNativeThemeCocoa::ThemeDrawsFocusForWidget(nsPresContext* aPresContext, nsIFrame* aFrame, uint8_t aWidgetType)
2961   if (aWidgetType == NS_THEME_DROPDOWN ||
2962       aWidgetType == NS_THEME_DROPDOWN_TEXTFIELD ||
2963       aWidgetType == NS_THEME_BUTTON ||
2964       aWidgetType == NS_THEME_RADIO ||
2965       aWidgetType == NS_THEME_CHECKBOX)
2966     return true;
2968   return false;
2971 bool
2972 nsNativeThemeCocoa::ThemeNeedsComboboxDropmarker()
2974   return false;
2977 nsITheme::Transparency
2978 nsNativeThemeCocoa::GetWidgetTransparency(nsIFrame* aFrame, uint8_t aWidgetType)
2980   switch (aWidgetType) {
2981   case NS_THEME_MENUPOPUP:
2982   case NS_THEME_TOOLTIP:
2983     return eTransparent;
2985   case NS_THEME_SCROLLBAR_SMALL:
2986   case NS_THEME_SCROLLBAR:
2987   case NS_THEME_STATUSBAR:
2988     // Knowing that scrollbars and statusbars are opaque improves
2989     // performance, because we create layers for them.
2990     return eOpaque;
2992   default:
2993     return eUnknownTransparency;
2994   }
2997 double
2998 nsNativeThemeCocoa::GetProgressValue(nsIFrame* aFrame)
3000   // When we are using the HTML progress element,
3001   // we can get the value from the IDL property.
3002   if (aFrame) {
3003     nsCOMPtr<nsIDOMHTMLProgressElement> progress =
3004       do_QueryInterface(aFrame->GetContent());
3005     if (progress) {
3006       double value;
3007       progress->GetValue(&value);
3008       return value;
3009     }
3010   }
3012   return (double)CheckIntAttr(aFrame, nsGkAtoms::value, 0);
3015 double
3016 nsNativeThemeCocoa::GetProgressMaxValue(nsIFrame* aFrame)
3018   // When we are using the HTML progress element,
3019   // we can get the max from the IDL property.
3020   if (aFrame) {
3021     nsCOMPtr<nsIDOMHTMLProgressElement> progress =
3022       do_QueryInterface(aFrame->GetContent());
3023     if (progress) {
3024       double max;
3025       progress->GetMax(&max);
3026       return max;
3027     }
3028   }
3030   return (double)NS_MAX(CheckIntAttr(aFrame, nsGkAtoms::max, 100), 1);