Bug 1646817 - Support DocumentChannel process switching in sidebars and popups r...
[gecko.git] / widget / cocoa / nsNativeThemeCocoa.mm
blobae58a744fe761741286fe2d2dc216e349f882472
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"
8 #include "mozilla/gfx/2D.h"
9 #include "mozilla/gfx/Helpers.h"
10 #include "nsChildView.h"
11 #include "nsDeviceContext.h"
12 #include "nsLayoutUtils.h"
13 #include "nsObjCExceptions.h"
14 #include "nsNumberControlFrame.h"
15 #include "nsRangeFrame.h"
16 #include "nsRect.h"
17 #include "nsSize.h"
18 #include "nsStyleConsts.h"
19 #include "nsPresContext.h"
20 #include "nsIContent.h"
21 #include "mozilla/dom/Document.h"
22 #include "nsIFrame.h"
23 #include "nsAtom.h"
24 #include "nsNameSpaceManager.h"
25 #include "nsPresContext.h"
26 #include "nsGkAtoms.h"
27 #include "nsCocoaFeatures.h"
28 #include "nsCocoaWindow.h"
29 #include "nsNativeBasicTheme.h"
30 #include "nsNativeThemeColors.h"
31 #include "nsIScrollableFrame.h"
32 #include "mozilla/ClearOnShutdown.h"
33 #include "mozilla/EventStates.h"
34 #include "mozilla/Range.h"
35 #include "mozilla/dom/Element.h"
36 #include "mozilla/dom/HTMLMeterElement.h"
37 #include "mozilla/layers/StackingContextHelper.h"
38 #include "mozilla/StaticPrefs_layout.h"
39 #include "mozilla/StaticPrefs_widget.h"
40 #include "nsLookAndFeel.h"
41 #include "VibrancyManager.h"
43 #include "gfxContext.h"
44 #include "gfxQuartzSurface.h"
45 #include "gfxQuartzNativeDrawing.h"
46 #include "gfxUtils.h"  // for ToDeviceColor
47 #include <algorithm>
49 using namespace mozilla;
50 using namespace mozilla::gfx;
51 using mozilla::dom::HTMLMeterElement;
53 #define DRAW_IN_FRAME_DEBUG 0
54 #define SCROLLBARS_VISUAL_DEBUG 0
56 // private Quartz routines needed here
57 extern "C" {
58 CG_EXTERN void CGContextSetCTM(CGContextRef, CGAffineTransform);
59 CG_EXTERN void CGContextSetBaseCTM(CGContextRef, CGAffineTransform);
60 typedef CFTypeRef CUIRendererRef;
61 void CUIDraw(CUIRendererRef r, CGRect rect, CGContextRef ctx, CFDictionaryRef options,
62              CFDictionaryRef* result);
65 // Workaround for NSCell control tint drawing
66 // Without this workaround, NSCells are always drawn with the clear control tint
67 // as long as they're not attached to an NSControl which is a subview of an active window.
68 // XXXmstange Why doesn't Webkit need this?
69 @implementation NSCell (ControlTintWorkaround)
70 - (int)_realControlTint {
71   return [self controlTint];
73 @end
75 // The purpose of this class is to provide objects that can be used when drawing
76 // NSCells using drawWithFrame:inView: without causing any harm. The only
77 // messages that will be sent to such an object are "isFlipped" and
78 // "currentEditor": isFlipped needs to return YES in order to avoid drawing bugs
79 // on 10.4 (see bug 465069); currentEditor (which isn't even a method of
80 // NSView) will be called when drawing search fields, and we only provide it in
81 // order to prevent "unrecognized selector" exceptions.
82 // There's no need to pass the actual NSView that we're drawing into to
83 // drawWithFrame:inView:. What's more, doing so even causes unnecessary
84 // invalidations as soon as we draw a focusring!
85 @interface CellDrawView : NSView
87 @end
89 @implementation CellDrawView
91 - (BOOL)isFlipped {
92   return YES;
95 - (NSText*)currentEditor {
96   return nil;
99 @end
101 // These two classes don't actually add any behavior over NSButtonCell. Their
102 // purpose is to make it easy to distinguish NSCell objects that are used for
103 // drawing radio buttons / checkboxes from other cell types.
104 // The class names are made up, there are no classes with these names in AppKit.
105 // The reason we need them is that calling [cell setButtonType:NSRadioButton]
106 // doesn't leave an easy-to-check "marker" on the cell object - there is no
107 // -[NSButtonCell buttonType] method.
108 @interface RadioButtonCell : NSButtonCell
109 @end
111 @implementation RadioButtonCell
112 @end
114 @interface CheckboxCell : NSButtonCell
115 @end
117 @implementation CheckboxCell
118 @end
120 static void DrawFocusRingForCellIfNeeded(NSCell* aCell, NSRect aWithFrame, NSView* aInView) {
121   if ([aCell showsFirstResponder]) {
122     CGContextRef cgContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
123     CGContextSaveGState(cgContext);
125     // It's important to set the focus ring style before we enter the
126     // transparency layer so that the transparency layer only contains
127     // the normal button mask without the focus ring, and the conversion
128     // to the focus ring shape happens only when the transparency layer is
129     // ended.
130     NSSetFocusRingStyle(NSFocusRingOnly);
132     // We need to draw the whole button into a transparency layer because
133     // many button types are composed of multiple parts, and if these parts
134     // were drawn while the focus ring style was active, each individual part
135     // would produce a focus ring for itself. But we only want one focus ring
136     // for the whole button. The transparency layer is a way to merge the
137     // individual button parts together before the focus ring shape is
138     // calculated.
139     CGContextBeginTransparencyLayerWithRect(cgContext, NSRectToCGRect(aWithFrame), 0);
140     [aCell drawFocusRingMaskWithFrame:aWithFrame inView:aInView];
141     CGContextEndTransparencyLayer(cgContext);
143     CGContextRestoreGState(cgContext);
144   }
147 static bool FocusIsDrawnByDrawWithFrame(NSCell* aCell) {
148 #if defined(MAC_OS_X_VERSION_10_8) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8
149   // When building with the 10.8 SDK or higher, focus rings don't draw as part
150   // of -[NSCell drawWithFrame:inView:] and must be drawn by a separate call
151   // to -[NSCell drawFocusRingMaskWithFrame:inView:]; .
152   // See the NSButtonCell section under
153   // https://developer.apple.com/library/mac/releasenotes/AppKit/RN-AppKitOlderNotes/#X10_8Notes
154   return false;
155 #else
156   // On 10.10, whether the focus ring is drawn as part of
157   // -[NSCell drawWithFrame:inView:] depends on the cell type.
158   // Radio buttons and checkboxes draw their own focus rings, other cell
159   // types need -[NSCell drawFocusRingMaskWithFrame:inView:].
160   return
161       [aCell isKindOfClass:[RadioButtonCell class]] || [aCell isKindOfClass:[CheckboxCell class]];
162 #endif
165 static void DrawCellIncludingFocusRing(NSCell* aCell, NSRect aWithFrame, NSView* aInView) {
166   [aCell drawWithFrame:aWithFrame inView:aInView];
168   if (!FocusIsDrawnByDrawWithFrame(aCell)) {
169     DrawFocusRingForCellIfNeeded(aCell, aWithFrame, aInView);
170   }
174  * NSProgressBarCell is used to draw progress bars of any size.
175  */
176 @interface NSProgressBarCell : NSCell {
177   /*All instance variables are private*/
178   double mValue;
179   double mMax;
180   bool mIsIndeterminate;
181   bool mIsHorizontal;
184 - (void)setValue:(double)value;
185 - (double)value;
186 - (void)setMax:(double)max;
187 - (double)max;
188 - (void)setIndeterminate:(bool)aIndeterminate;
189 - (bool)isIndeterminate;
190 - (void)setHorizontal:(bool)aIsHorizontal;
191 - (bool)isHorizontal;
192 - (void)drawWithFrame:(NSRect)cellFrame inView:(NSView*)controlView;
193 @end
195 @implementation NSProgressBarCell
197 - (void)setMax:(double)aMax {
198   mMax = aMax;
201 - (double)max {
202   return mMax;
205 - (void)setValue:(double)aValue {
206   mValue = aValue;
209 - (double)value {
210   return mValue;
213 - (void)setIndeterminate:(bool)aIndeterminate {
214   mIsIndeterminate = aIndeterminate;
217 - (bool)isIndeterminate {
218   return mIsIndeterminate;
221 - (void)setHorizontal:(bool)aIsHorizontal {
222   mIsHorizontal = aIsHorizontal;
225 - (bool)isHorizontal {
226   return mIsHorizontal;
229 - (void)drawWithFrame:(NSRect)cellFrame inView:(NSView*)controlView {
230   CGContext* cgContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
232   HIThemeTrackDrawInfo tdi;
234   tdi.version = 0;
235   tdi.min = 0;
237   tdi.value = INT32_MAX * (mValue / mMax);
238   tdi.max = INT32_MAX;
239   tdi.bounds = NSRectToCGRect(cellFrame);
240   tdi.attributes = mIsHorizontal ? kThemeTrackHorizontal : 0;
241   tdi.enableState =
242       [self controlTint] == NSClearControlTint ? kThemeTrackInactive : kThemeTrackActive;
244   NSControlSize size = [self controlSize];
245   if (size == NSControlSizeRegular) {
246     tdi.kind = mIsIndeterminate ? kThemeLargeIndeterminateBar : kThemeLargeProgressBar;
247   } else {
248     NS_ASSERTION(size == NSControlSizeSmall,
249                  "We shouldn't have another size than small and regular for the moment");
250     tdi.kind = mIsIndeterminate ? kThemeMediumIndeterminateBar : kThemeMediumProgressBar;
251   }
253   int32_t stepsPerSecond = mIsIndeterminate ? 60 : 30;
254   int32_t milliSecondsPerStep = 1000 / stepsPerSecond;
255   tdi.trackInfo.progress.phase =
256       uint8_t(PR_IntervalToMilliseconds(PR_IntervalNow()) / milliSecondsPerStep);
258   HIThemeDrawTrack(&tdi, NULL, cgContext, kHIThemeOrientationNormal);
261 @end
263 @interface SearchFieldCellWithFocusRing : NSSearchFieldCell {
265 @end
267 // Workaround for Bug 542048
268 // On 64-bit, NSSearchFieldCells don't draw focus rings.
269 @implementation SearchFieldCellWithFocusRing
271 - (void)drawWithFrame:(NSRect)rect inView:(NSView*)controlView {
272   [super drawWithFrame:rect inView:controlView];
274   if (FocusIsDrawnByDrawWithFrame(self)) {
275     // For some reason, -[NSSearchFieldCell drawWithFrame:inView] doesn't draw a
276     // focus ring in 64 bit mode, no matter what SDK is used or what OS X version
277     // we're running on. But if FocusIsDrawnByDrawWithFrame(self), then our
278     // caller expects us to draw a focus ring. So we just do that here.
279     DrawFocusRingForCellIfNeeded(self, rect, controlView);
280   }
283 - (void)drawFocusRingMaskWithFrame:(NSRect)rect inView:(NSView*)controlView {
284   // By default this draws nothing. I don't know why.
285   // We just draw the search field again. It's a great mask shape for its own
286   // focus ring.
287   [super drawWithFrame:rect inView:controlView];
290 @end
292 @interface ToolbarSearchFieldCellWithFocusRing : SearchFieldCellWithFocusRing
293 @end
295 @implementation ToolbarSearchFieldCellWithFocusRing
297 - (BOOL)_isToolbarMode {
298   // This function is called during -[NSSearchFieldCell drawWithFrame:inView:].
299   // On earlier macOS versions, returning YES from it selects the style
300   // that's appropriate for search fields inside toolbars. On Big Sur,
301   // returning YES causes the search field to be drawn incorrectly, with
302   // the toolbar gradient appearing as the field background.
303   if (nsCocoaFeatures::OnBigSurOrLater()) {
304     return NO;
305   }
306   return YES;
309 @end
311 #define HITHEME_ORIENTATION kHIThemeOrientationNormal
313 static CGFloat kMaxFocusRingWidth = 0;  // initialized by the nsNativeThemeCocoa constructor
315 // These enums are for indexing into the margin array.
316 enum {
317   leopardOSorlater = 0,  // 10.6 - 10.9
318   yosemiteOSorlater = 1  // 10.10+
321 enum { miniControlSize, smallControlSize, regularControlSize };
323 enum { leftMargin, topMargin, rightMargin, bottomMargin };
325 static size_t EnumSizeForCocoaSize(NSControlSize cocoaControlSize) {
326   if (cocoaControlSize == NSControlSizeMini)
327     return miniControlSize;
328   else if (cocoaControlSize == NSControlSizeSmall)
329     return smallControlSize;
330   else
331     return regularControlSize;
334 static NSControlSize CocoaSizeForEnum(int32_t enumControlSize) {
335   if (enumControlSize == miniControlSize)
336     return NSControlSizeMini;
337   else if (enumControlSize == smallControlSize)
338     return NSControlSizeSmall;
339   else
340     return NSControlSizeRegular;
343 static NSString* CUIControlSizeForCocoaSize(NSControlSize aControlSize) {
344   if (aControlSize == NSControlSizeRegular)
345     return @"regular";
346   else if (aControlSize == NSControlSizeSmall)
347     return @"small";
348   else
349     return @"mini";
352 static void InflateControlRect(NSRect* rect, NSControlSize cocoaControlSize,
353                                const float marginSet[][3][4]) {
354   if (!marginSet) return;
356   static int osIndex = yosemiteOSorlater;
357   size_t controlSize = EnumSizeForCocoaSize(cocoaControlSize);
358   const float* buttonMargins = marginSet[osIndex][controlSize];
359   rect->origin.x -= buttonMargins[leftMargin];
360   rect->origin.y -= buttonMargins[bottomMargin];
361   rect->size.width += buttonMargins[leftMargin] + buttonMargins[rightMargin];
362   rect->size.height += buttonMargins[bottomMargin] + buttonMargins[topMargin];
365 static NSWindow* NativeWindowForFrame(nsIFrame* aFrame, nsIWidget** aTopLevelWidget = NULL) {
366   if (!aFrame) return nil;
368   nsIWidget* widget = aFrame->GetNearestWidget();
369   if (!widget) return nil;
371   nsIWidget* topLevelWidget = widget->GetTopLevelWidget();
372   if (aTopLevelWidget) *aTopLevelWidget = topLevelWidget;
374   return (NSWindow*)topLevelWidget->GetNativeData(NS_NATIVE_WINDOW);
377 static NSSize WindowButtonsSize(nsIFrame* aFrame) {
378   NSWindow* window = NativeWindowForFrame(aFrame);
379   if (!window) {
380     // Return fallback values.
381     return NSMakeSize(54, 16);
382   }
384   NSRect buttonBox = NSZeroRect;
385   NSButton* closeButton = [window standardWindowButton:NSWindowCloseButton];
386   if (closeButton) {
387     buttonBox = NSUnionRect(buttonBox, [closeButton frame]);
388   }
389   NSButton* minimizeButton = [window standardWindowButton:NSWindowMiniaturizeButton];
390   if (minimizeButton) {
391     buttonBox = NSUnionRect(buttonBox, [minimizeButton frame]);
392   }
393   NSButton* zoomButton = [window standardWindowButton:NSWindowZoomButton];
394   if (zoomButton) {
395     buttonBox = NSUnionRect(buttonBox, [zoomButton frame]);
396   }
397   return buttonBox.size;
400 static BOOL FrameIsInActiveWindow(nsIFrame* aFrame) {
401   nsIWidget* topLevelWidget = NULL;
402   NSWindow* win = NativeWindowForFrame(aFrame, &topLevelWidget);
403   if (!topLevelWidget || !win) return YES;
405   // XUL popups, e.g. the toolbar customization popup, can't become key windows,
406   // but controls in these windows should still get the active look.
407   if (topLevelWidget->WindowType() == eWindowType_popup) return YES;
408   if ([win isSheet]) return [win isKeyWindow];
409   return [win isMainWindow] && ![win attachedSheet];
412 // Toolbar controls and content controls respond to different window
413 // activeness states.
414 static BOOL IsActive(nsIFrame* aFrame, BOOL aIsToolbarControl) {
415   if (aIsToolbarControl) return [NativeWindowForFrame(aFrame) isMainWindow];
416   return FrameIsInActiveWindow(aFrame);
419 static bool IsInSourceList(nsIFrame* aFrame) {
420   for (nsIFrame* frame = aFrame->GetParent(); frame;
421        frame = nsLayoutUtils::GetCrossDocParentFrame(frame)) {
422     if (frame->StyleDisplay()->EffectiveAppearance() == StyleAppearance::MozMacSourceList) {
423       return true;
424     }
425   }
426   return false;
429 NS_IMPL_ISUPPORTS_INHERITED(nsNativeThemeCocoa, nsNativeTheme, nsITheme)
431 nsNativeThemeCocoa::nsNativeThemeCocoa() {
432   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
434   kMaxFocusRingWidth = 7;
436   // provide a local autorelease pool, as this is called during startup
437   // before the main event-loop pool is in place
438   nsAutoreleasePool pool;
440   mDisclosureButtonCell = [[NSButtonCell alloc] initTextCell:@""];
441   [mDisclosureButtonCell setBezelStyle:NSRoundedDisclosureBezelStyle];
442   [mDisclosureButtonCell setButtonType:NSPushOnPushOffButton];
443   [mDisclosureButtonCell setHighlightsBy:NSPushInCellMask];
445   mHelpButtonCell = [[NSButtonCell alloc] initTextCell:@""];
446   [mHelpButtonCell setBezelStyle:NSHelpButtonBezelStyle];
447   [mHelpButtonCell setButtonType:NSMomentaryPushInButton];
448   [mHelpButtonCell setHighlightsBy:NSPushInCellMask];
450   mPushButtonCell = [[NSButtonCell alloc] initTextCell:@""];
451   [mPushButtonCell setButtonType:NSMomentaryPushInButton];
452   [mPushButtonCell setHighlightsBy:NSPushInCellMask];
454   mRadioButtonCell = [[RadioButtonCell alloc] initTextCell:@""];
455   [mRadioButtonCell setButtonType:NSRadioButton];
457   mCheckboxCell = [[CheckboxCell alloc] initTextCell:@""];
458   [mCheckboxCell setButtonType:NSSwitchButton];
459   [mCheckboxCell setAllowsMixedState:YES];
461   mSearchFieldCell = [[SearchFieldCellWithFocusRing alloc] initTextCell:@""];
462   [mSearchFieldCell setBezelStyle:NSTextFieldRoundedBezel];
463   [mSearchFieldCell setBezeled:YES];
464   [mSearchFieldCell setEditable:YES];
465   [mSearchFieldCell setFocusRingType:NSFocusRingTypeExterior];
467   mToolbarSearchFieldCell = [[ToolbarSearchFieldCellWithFocusRing alloc] initTextCell:@""];
468   [mToolbarSearchFieldCell setBezelStyle:NSTextFieldRoundedBezel];
469   [mToolbarSearchFieldCell setBezeled:YES];
470   [mToolbarSearchFieldCell setEditable:YES];
471   [mToolbarSearchFieldCell setFocusRingType:NSFocusRingTypeExterior];
473   mDropdownCell = [[NSPopUpButtonCell alloc] initTextCell:@"" pullsDown:NO];
475   mComboBoxCell = [[NSComboBoxCell alloc] initTextCell:@""];
476   [mComboBoxCell setBezeled:YES];
477   [mComboBoxCell setEditable:YES];
478   [mComboBoxCell setFocusRingType:NSFocusRingTypeExterior];
480   mProgressBarCell = [[NSProgressBarCell alloc] init];
482   mMeterBarCell = [[NSLevelIndicatorCell alloc]
483       initWithLevelIndicatorStyle:NSContinuousCapacityLevelIndicatorStyle];
485   mCellDrawView = [[CellDrawView alloc] init];
487   NS_OBJC_END_TRY_ABORT_BLOCK;
490 nsNativeThemeCocoa::~nsNativeThemeCocoa() {
491   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
493   [mMeterBarCell release];
494   [mProgressBarCell release];
495   [mDisclosureButtonCell release];
496   [mHelpButtonCell release];
497   [mPushButtonCell release];
498   [mRadioButtonCell release];
499   [mCheckboxCell release];
500   [mSearchFieldCell release];
501   [mToolbarSearchFieldCell release];
502   [mDropdownCell release];
503   [mComboBoxCell release];
504   [mCellDrawView release];
506   NS_OBJC_END_TRY_ABORT_BLOCK;
509 // Limit on the area of the target rect (in pixels^2) in
510 // DrawCellWithScaling() and DrawButton() and above which we
511 // don't draw the object into a bitmap buffer.  This is to avoid crashes in
512 // [NSGraphicsContext graphicsContextWithGraphicsPort:flipped:] and
513 // CGContextDrawImage(), and also to avoid very poor drawing performance in
514 // CGContextDrawImage() when it scales the bitmap (particularly if xscale or
515 // yscale is less than but near 1 -- e.g. 0.9).  This value was determined
516 // by trial and error, on OS X 10.4.11 and 10.5.4, and on systems with
517 // different amounts of RAM.
518 #define BITMAP_MAX_AREA 500000
520 static int GetBackingScaleFactorForRendering(CGContextRef cgContext) {
521   CGAffineTransform ctm = CGContextGetUserSpaceToDeviceSpaceTransform(cgContext);
522   CGRect transformedUserSpacePixel = CGRectApplyAffineTransform(CGRectMake(0, 0, 1, 1), ctm);
523   float maxScale = std::max(fabs(transformedUserSpacePixel.size.width),
524                             fabs(transformedUserSpacePixel.size.height));
525   return maxScale > 1.0 ? 2 : 1;
529  * Draw the given NSCell into the given cgContext.
531  * destRect - the size and position of the resulting control rectangle
532  * controlSize - the NSControlSize which will be given to the NSCell before
533  *  asking it to render
534  * naturalSize - The natural dimensions of this control.
535  *  If the control rect size is not equal to either of these, a scale
536  *  will be applied to the context so that rendering the control at the
537  *  natural size will result in it filling the destRect space.
538  *  If a control has no natural dimensions in either/both axes, pass 0.0f.
539  * minimumSize - The minimum dimensions of this control.
540  *  If the control rect size is less than the minimum for a given axis,
541  *  a scale will be applied to the context so that the minimum is used
542  *  for drawing.  If a control has no minimum dimensions in either/both
543  *  axes, pass 0.0f.
544  * marginSet - an array of margins; a multidimensional array of [2][3][4],
545  *  with the first dimension being the OS version (Tiger or Leopard),
546  *  the second being the control size (mini, small, regular), and the third
547  *  being the 4 margin values (left, top, right, bottom).
548  * view - The NSView that we're drawing into. As far as I can tell, it doesn't
549  *  matter if this is really the right view; it just has to return YES when
550  *  asked for isFlipped. Otherwise we'll get drawing bugs on 10.4.
551  * mirrorHorizontal - whether to mirror the cell horizontally
552  */
553 static void DrawCellWithScaling(NSCell* cell, CGContextRef cgContext, const HIRect& destRect,
554                                 NSControlSize controlSize, NSSize naturalSize, NSSize minimumSize,
555                                 const float marginSet[][3][4], NSView* view,
556                                 BOOL mirrorHorizontal) {
557   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
559   NSRect drawRect =
560       NSMakeRect(destRect.origin.x, destRect.origin.y, destRect.size.width, destRect.size.height);
562   if (naturalSize.width != 0.0f) drawRect.size.width = naturalSize.width;
563   if (naturalSize.height != 0.0f) drawRect.size.height = naturalSize.height;
565   // Keep aspect ratio when scaling if one dimension is free.
566   if (naturalSize.width == 0.0f && naturalSize.height != 0.0f)
567     drawRect.size.width = destRect.size.width * naturalSize.height / destRect.size.height;
568   if (naturalSize.height == 0.0f && naturalSize.width != 0.0f)
569     drawRect.size.height = destRect.size.height * naturalSize.width / destRect.size.width;
571   // Honor minimum sizes.
572   if (drawRect.size.width < minimumSize.width) drawRect.size.width = minimumSize.width;
573   if (drawRect.size.height < minimumSize.height) drawRect.size.height = minimumSize.height;
575   [NSGraphicsContext saveGraphicsState];
577   // Only skip the buffer if the area of our cell (in pixels^2) is too large.
578   if (drawRect.size.width * drawRect.size.height > BITMAP_MAX_AREA) {
579     // Inflate the rect Gecko gave us by the margin for the control.
580     InflateControlRect(&drawRect, controlSize, marginSet);
582     NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
583     [NSGraphicsContext
584         setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:cgContext
585                                                                      flipped:YES]];
587     DrawCellIncludingFocusRing(cell, drawRect, view);
589     [NSGraphicsContext setCurrentContext:savedContext];
590   } else {
591     float w = ceil(drawRect.size.width);
592     float h = ceil(drawRect.size.height);
593     NSRect tmpRect = NSMakeRect(kMaxFocusRingWidth, kMaxFocusRingWidth, w, h);
595     // inflate to figure out the frame we need to tell NSCell to draw in, to get something that's
596     // 0,0,w,h
597     InflateControlRect(&tmpRect, controlSize, marginSet);
599     // and then, expand by kMaxFocusRingWidth size to make sure we can capture any focus ring
600     w += kMaxFocusRingWidth * 2.0;
601     h += kMaxFocusRingWidth * 2.0;
603     int backingScaleFactor = GetBackingScaleFactorForRendering(cgContext);
604     CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
605     CGContextRef ctx = CGBitmapContextCreate(
606         NULL, (int)w * backingScaleFactor, (int)h * backingScaleFactor, 8,
607         (int)w * backingScaleFactor * 4, rgb, kCGImageAlphaPremultipliedFirst);
608     CGColorSpaceRelease(rgb);
610     // We need to flip the image twice in order to avoid drawing bugs on 10.4, see bug 465069.
611     // This is the first flip transform, applied to cgContext.
612     CGContextScaleCTM(cgContext, 1.0f, -1.0f);
613     CGContextTranslateCTM(cgContext, 0.0f, -(2.0 * destRect.origin.y + destRect.size.height));
614     if (mirrorHorizontal) {
615       CGContextScaleCTM(cgContext, -1.0f, 1.0f);
616       CGContextTranslateCTM(cgContext, -(2.0 * destRect.origin.x + destRect.size.width), 0.0f);
617     }
619     NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
620     [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:ctx
621                                                                                     flipped:YES]];
623     CGContextScaleCTM(ctx, backingScaleFactor, backingScaleFactor);
625     // Set the context's "base transform" to in order to get correctly-sized focus rings.
626     CGContextSetBaseCTM(ctx, CGAffineTransformMakeScale(backingScaleFactor, backingScaleFactor));
628     // This is the second flip transform, applied to ctx.
629     CGContextScaleCTM(ctx, 1.0f, -1.0f);
630     CGContextTranslateCTM(ctx, 0.0f, -(2.0 * tmpRect.origin.y + tmpRect.size.height));
632     DrawCellIncludingFocusRing(cell, tmpRect, view);
634     [NSGraphicsContext setCurrentContext:savedContext];
636     CGImageRef img = CGBitmapContextCreateImage(ctx);
638     // Drop the image into the original destination rectangle, scaling to fit
639     // Only scale kMaxFocusRingWidth by xscale/yscale when the resulting rect
640     // doesn't extend beyond the overflow rect
641     float xscale = destRect.size.width / drawRect.size.width;
642     float yscale = destRect.size.height / drawRect.size.height;
643     float scaledFocusRingX = xscale < 1.0f ? kMaxFocusRingWidth * xscale : kMaxFocusRingWidth;
644     float scaledFocusRingY = yscale < 1.0f ? kMaxFocusRingWidth * yscale : kMaxFocusRingWidth;
645     CGContextDrawImage(
646         cgContext,
647         CGRectMake(destRect.origin.x - scaledFocusRingX, destRect.origin.y - scaledFocusRingY,
648                    destRect.size.width + scaledFocusRingX * 2,
649                    destRect.size.height + scaledFocusRingY * 2),
650         img);
652     CGImageRelease(img);
653     CGContextRelease(ctx);
654   }
656   [NSGraphicsContext restoreGraphicsState];
658 #if DRAW_IN_FRAME_DEBUG
659   CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
660   CGContextFillRect(cgContext, destRect);
661 #endif
663   NS_OBJC_END_TRY_ABORT_BLOCK;
666 struct CellRenderSettings {
667   // The natural dimensions of the control.
668   // If a control has no natural dimensions in either/both axes, set to 0.0f.
669   NSSize naturalSizes[3];
671   // The minimum dimensions of the control.
672   // If a control has no minimum dimensions in either/both axes, set to 0.0f.
673   NSSize minimumSizes[3];
675   // A three-dimensional array,
676   // with the first dimension being the OS version ([0] 10.6-10.9, [1] 10.10 and above),
677   // the second being the control size (mini, small, regular), and the third
678   // being the 4 margin values (left, top, right, bottom).
679   float margins[2][3][4];
683  * This is a helper method that returns the required NSControlSize given a size
684  * and the size of the three controls plus a tolerance.
685  * size - The width or the height of the element to draw.
686  * sizes - An array with the all the width/height of the element for its
687  *         different sizes.
688  * tolerance - The tolerance as passed to DrawCellWithSnapping.
689  * NOTE: returns NSControlSizeRegular if all values in 'sizes' are zero.
690  */
691 static NSControlSize FindControlSize(CGFloat size, const CGFloat* sizes, CGFloat tolerance) {
692   for (uint32_t i = miniControlSize; i <= regularControlSize; ++i) {
693     if (sizes[i] == 0) {
694       continue;
695     }
697     CGFloat next = 0;
698     // Find next value.
699     for (uint32_t j = i + 1; j <= regularControlSize; ++j) {
700       if (sizes[j] != 0) {
701         next = sizes[j];
702         break;
703       }
704     }
706     // If it's the latest value, we pick it.
707     if (next == 0) {
708       return CocoaSizeForEnum(i);
709     }
711     if (size <= sizes[i] + tolerance && size < next) {
712       return CocoaSizeForEnum(i);
713     }
714   }
716   // If we are here, that means sizes[] was an array with only empty values
717   // or the algorithm above is wrong.
718   // The former can happen but the later would be wrong.
719   NS_ASSERTION(sizes[0] == 0 && sizes[1] == 0 && sizes[2] == 0,
720                "We found no control! We shouldn't be there!");
721   return CocoaSizeForEnum(regularControlSize);
725  * Draw the given NSCell into the given cgContext with a nice control size.
727  * This function is similar to DrawCellWithScaling, but it decides what
728  * control size to use based on the destRect's size.
729  * Scaling is only applied when the difference between the destRect's size
730  * and the next smaller natural size is greater than snapTolerance. Otherwise
731  * it snaps to the next smaller control size without scaling because unscaled
732  * controls look nicer.
733  */
734 static void DrawCellWithSnapping(NSCell* cell, CGContextRef cgContext, const HIRect& destRect,
735                                  const CellRenderSettings settings, float verticalAlignFactor,
736                                  NSView* view, BOOL mirrorHorizontal, float snapTolerance = 2.0f) {
737   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
739   const float rectWidth = destRect.size.width, rectHeight = destRect.size.height;
740   const NSSize* sizes = settings.naturalSizes;
741   const NSSize miniSize = sizes[EnumSizeForCocoaSize(NSControlSizeMini)];
742   const NSSize smallSize = sizes[EnumSizeForCocoaSize(NSControlSizeSmall)];
743   const NSSize regularSize = sizes[EnumSizeForCocoaSize(NSControlSizeRegular)];
745   HIRect drawRect = destRect;
747   CGFloat controlWidths[3] = {miniSize.width, smallSize.width, regularSize.width};
748   NSControlSize controlSizeX = FindControlSize(rectWidth, controlWidths, snapTolerance);
749   CGFloat controlHeights[3] = {miniSize.height, smallSize.height, regularSize.height};
750   NSControlSize controlSizeY = FindControlSize(rectHeight, controlHeights, snapTolerance);
752   NSControlSize controlSize = NSControlSizeRegular;
753   size_t sizeIndex = 0;
755   // At some sizes, don't scale but snap.
756   const NSControlSize smallerControlSize =
757       EnumSizeForCocoaSize(controlSizeX) < EnumSizeForCocoaSize(controlSizeY) ? controlSizeX
758                                                                               : controlSizeY;
759   const size_t smallerControlSizeIndex = EnumSizeForCocoaSize(smallerControlSize);
760   const NSSize size = sizes[smallerControlSizeIndex];
761   float diffWidth = size.width ? rectWidth - size.width : 0.0f;
762   float diffHeight = size.height ? rectHeight - size.height : 0.0f;
763   if (diffWidth >= 0.0f && diffHeight >= 0.0f && diffWidth <= snapTolerance &&
764       diffHeight <= snapTolerance) {
765     // Snap to the smaller control size.
766     controlSize = smallerControlSize;
767     sizeIndex = smallerControlSizeIndex;
768     MOZ_ASSERT(sizeIndex < ArrayLength(settings.naturalSizes));
770     // Resize and center the drawRect.
771     if (sizes[sizeIndex].width) {
772       drawRect.origin.x += ceil((destRect.size.width - sizes[sizeIndex].width) / 2);
773       drawRect.size.width = sizes[sizeIndex].width;
774     }
775     if (sizes[sizeIndex].height) {
776       drawRect.origin.y +=
777           floor((destRect.size.height - sizes[sizeIndex].height) * verticalAlignFactor);
778       drawRect.size.height = sizes[sizeIndex].height;
779     }
780   } else {
781     // Use the larger control size.
782     controlSize = EnumSizeForCocoaSize(controlSizeX) > EnumSizeForCocoaSize(controlSizeY)
783                       ? controlSizeX
784                       : controlSizeY;
785     sizeIndex = EnumSizeForCocoaSize(controlSize);
786   }
788   [cell setControlSize:controlSize];
790   MOZ_ASSERT(sizeIndex < ArrayLength(settings.minimumSizes));
791   const NSSize minimumSize = settings.minimumSizes[sizeIndex];
792   DrawCellWithScaling(cell, cgContext, drawRect, controlSize, sizes[sizeIndex], minimumSize,
793                       settings.margins, view, mirrorHorizontal);
795   NS_OBJC_END_TRY_ABORT_BLOCK;
798 @interface NSWindow (CoreUIRendererPrivate)
799 + (CUIRendererRef)coreUIRenderer;
800 @end
802 static id GetAquaAppearance() {
803   Class NSAppearanceClass = NSClassFromString(@"NSAppearance");
804   if (NSAppearanceClass && [NSAppearanceClass respondsToSelector:@selector(appearanceNamed:)]) {
805     return [NSAppearanceClass performSelector:@selector(appearanceNamed:)
806                                    withObject:@"NSAppearanceNameAqua"];
807   }
808   return nil;
811 @interface NSObject (NSAppearanceCoreUIRendering)
812 - (void)_drawInRect:(CGRect)rect context:(CGContextRef)cgContext options:(id)options;
813 @end
815 static void RenderWithCoreUI(CGRect aRect, CGContextRef cgContext, NSDictionary* aOptions,
816                              bool aSkipAreaCheck = false) {
817   id appearance = GetAquaAppearance();
819   if (!aSkipAreaCheck && aRect.size.width * aRect.size.height > BITMAP_MAX_AREA) {
820     return;
821   }
823   if (appearance && [appearance respondsToSelector:@selector(_drawInRect:context:options:)]) {
824     // Render through NSAppearance on Mac OS 10.10 and up. This will call
825     // CUIDraw with a CoreUI renderer that will give us the correct 10.10
826     // style. Calling CUIDraw directly with [NSWindow coreUIRenderer] still
827     // renders 10.9-style widgets on 10.10.
828     [appearance _drawInRect:aRect context:cgContext options:aOptions];
829   } else {
830     // 10.9 and below
831     CUIRendererRef renderer =
832         [NSWindow respondsToSelector:@selector(coreUIRenderer)] ? [NSWindow coreUIRenderer] : nil;
833     CUIDraw(renderer, aRect, cgContext, (CFDictionaryRef)aOptions, NULL);
834   }
837 static float VerticalAlignFactor(nsIFrame* aFrame) {
838   if (!aFrame) return 0.5f;  // default: center
840   const auto& va = aFrame->StyleDisplay()->mVerticalAlign;
841   auto kw = va.IsKeyword() ? va.AsKeyword() : StyleVerticalAlignKeyword::Middle;
842   switch (kw) {
843     case StyleVerticalAlignKeyword::Top:
844     case StyleVerticalAlignKeyword::TextTop:
845       return 0.0f;
847     case StyleVerticalAlignKeyword::Sub:
848     case StyleVerticalAlignKeyword::Super:
849     case StyleVerticalAlignKeyword::Middle:
850     case StyleVerticalAlignKeyword::MozMiddleWithBaseline:
851       return 0.5f;
853     case StyleVerticalAlignKeyword::Baseline:
854     case StyleVerticalAlignKeyword::Bottom:
855     case StyleVerticalAlignKeyword::TextBottom:
856       return 1.0f;
858     default:
859       MOZ_ASSERT_UNREACHABLE("invalid vertical-align");
860       return 0.5f;
861   }
864 static void ApplyControlParamsToNSCell(nsNativeThemeCocoa::ControlParams aControlParams,
865                                        NSCell* aCell) {
866   [aCell setEnabled:!aControlParams.disabled];
867   [aCell setShowsFirstResponder:(aControlParams.focused && !aControlParams.disabled &&
868                                  aControlParams.insideActiveWindow)];
869   [aCell setHighlighted:aControlParams.pressed];
872 // These are the sizes that Gecko needs to request to draw if it wants
873 // to get a standard-sized Aqua radio button drawn. Note that the rects
874 // that draw these are actually a little bigger.
875 static const CellRenderSettings radioSettings = {{
876                                                      NSMakeSize(11, 11),  // mini
877                                                      NSMakeSize(13, 13),  // small
878                                                      NSMakeSize(16, 16)   // regular
879                                                  },
880                                                  {NSZeroSize, NSZeroSize, NSZeroSize},
881                                                  {{
882                                                       // Leopard
883                                                       {0, 0, 0, 0},  // mini
884                                                       {0, 1, 1, 1},  // small
885                                                       {0, 0, 0, 0}   // regular
886                                                   },
887                                                   {
888                                                       // Yosemite
889                                                       {0, 0, 0, 0},  // mini
890                                                       {1, 1, 1, 2},  // small
891                                                       {0, 0, 0, 0}   // regular
892                                                   }}};
894 static const CellRenderSettings checkboxSettings = {{
895                                                         NSMakeSize(11, 11),  // mini
896                                                         NSMakeSize(13, 13),  // small
897                                                         NSMakeSize(16, 16)   // regular
898                                                     },
899                                                     {NSZeroSize, NSZeroSize, NSZeroSize},
900                                                     {{
901                                                          // Leopard
902                                                          {0, 1, 0, 0},  // mini
903                                                          {0, 1, 0, 1},  // small
904                                                          {0, 1, 0, 1}   // regular
905                                                      },
906                                                      {
907                                                          // Yosemite
908                                                          {0, 1, 0, 0},  // mini
909                                                          {0, 1, 0, 1},  // small
910                                                          {0, 1, 0, 1}   // regular
911                                                      }}};
913 static NSCellStateValue CellStateForCheckboxOrRadioState(
914     nsNativeThemeCocoa::CheckboxOrRadioState aState) {
915   switch (aState) {
916     case nsNativeThemeCocoa::CheckboxOrRadioState::eOff:
917       return NSOffState;
918     case nsNativeThemeCocoa::CheckboxOrRadioState::eOn:
919       return NSOnState;
920     case nsNativeThemeCocoa::CheckboxOrRadioState::eIndeterminate:
921       return NSMixedState;
922   }
925 void nsNativeThemeCocoa::DrawCheckboxOrRadio(CGContextRef cgContext, bool inCheckbox,
926                                              const HIRect& inBoxRect,
927                                              const CheckboxOrRadioParams& aParams) {
928   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
930   NSButtonCell* cell = inCheckbox ? mCheckboxCell : mRadioButtonCell;
931   ApplyControlParamsToNSCell(aParams.controlParams, cell);
933   [cell setState:CellStateForCheckboxOrRadioState(aParams.state)];
934   [cell setControlTint:(aParams.controlParams.insideActiveWindow ? [NSColor currentControlTint]
935                                                                  : NSClearControlTint)];
937   // Ensure that the control is square.
938   float length = std::min(inBoxRect.size.width, inBoxRect.size.height);
939   HIRect drawRect = CGRectMake(inBoxRect.origin.x + (int)((inBoxRect.size.width - length) / 2.0f),
940                                inBoxRect.origin.y + (int)((inBoxRect.size.height - length) / 2.0f),
941                                length, length);
943   DrawCellWithSnapping(cell, cgContext, drawRect, inCheckbox ? checkboxSettings : radioSettings,
944                        aParams.verticalAlignFactor, mCellDrawView, NO);
946   NS_OBJC_END_TRY_ABORT_BLOCK;
949 static const CellRenderSettings searchFieldSettings = {{
950                                                            NSMakeSize(0, 16),  // mini
951                                                            NSMakeSize(0, 19),  // small
952                                                            NSMakeSize(0, 22)   // regular
953                                                        },
954                                                        {
955                                                            NSMakeSize(32, 0),  // mini
956                                                            NSMakeSize(38, 0),  // small
957                                                            NSMakeSize(44, 0)   // regular
958                                                        },
959                                                        {{
960                                                             // Leopard
961                                                             {0, 0, 0, 0},  // mini
962                                                             {0, 0, 0, 0},  // small
963                                                             {0, 0, 0, 0}   // regular
964                                                         },
965                                                         {
966                                                             // Yosemite
967                                                             {0, 0, 0, 0},  // mini
968                                                             {0, 0, 0, 0},  // small
969                                                             {0, 0, 0, 0}   // regular
970                                                         }}};
972 static bool IsToolbarStyleContainer(nsIFrame* aFrame) {
973   nsIContent* content = aFrame->GetContent();
974   if (!content) {
975     return false;
976   }
978   if (content->IsAnyOfXULElements(nsGkAtoms::toolbar, nsGkAtoms::toolbox, nsGkAtoms::statusbar)) {
979     return true;
980   }
982   switch (aFrame->StyleDisplay()->EffectiveAppearance()) {
983     case StyleAppearance::Toolbar:
984     case StyleAppearance::Statusbar:
985       return true;
986     default:
987       return false;
988   }
991 static bool IsInsideToolbar(nsIFrame* aFrame) {
992   for (nsIFrame* frame = aFrame; frame; frame = frame->GetParent()) {
993     if (IsToolbarStyleContainer(frame)) {
994       return true;
995     }
996   }
997   return false;
1000 nsNativeThemeCocoa::SearchFieldParams nsNativeThemeCocoa::ComputeSearchFieldParams(
1001     nsIFrame* aFrame, EventStates aEventState) {
1002   SearchFieldParams params;
1003   params.insideToolbar = IsInsideToolbar(aFrame);
1004   params.disabled = IsDisabled(aFrame, aEventState);
1005   params.focused = IsFocused(aFrame);
1006   params.rtl = IsFrameRTL(aFrame);
1007   params.verticalAlignFactor = VerticalAlignFactor(aFrame);
1008   return params;
1011 void nsNativeThemeCocoa::DrawSearchField(CGContextRef cgContext, const HIRect& inBoxRect,
1012                                          const SearchFieldParams& aParams) {
1013   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1015   NSSearchFieldCell* cell = aParams.insideToolbar ? mToolbarSearchFieldCell : mSearchFieldCell;
1016   [cell setEnabled:!aParams.disabled];
1017   [cell setShowsFirstResponder:aParams.focused];
1019   // When using the 10.11 SDK, the default string will be shown if we don't
1020   // set the placeholder string.
1021   [cell setPlaceholderString:@""];
1023   DrawCellWithSnapping(cell, cgContext, inBoxRect, searchFieldSettings, aParams.verticalAlignFactor,
1024                        mCellDrawView, aParams.rtl);
1026   NS_OBJC_END_TRY_ABORT_BLOCK;
1029 static const NSSize kCheckmarkSize = NSMakeSize(11, 11);
1030 static const NSSize kMenuarrowSize = NSMakeSize(9, 10);
1031 static const NSSize kMenuScrollArrowSize = NSMakeSize(10, 8);
1032 static NSString* kCheckmarkImage = @"MenuOnState";
1033 static NSString* kMenuarrowRightImage = @"MenuSubmenu";
1034 static NSString* kMenuarrowLeftImage = @"MenuSubmenuLeft";
1035 static NSString* kMenuDownScrollArrowImage = @"MenuScrollDown";
1036 static NSString* kMenuUpScrollArrowImage = @"MenuScrollUp";
1037 static const CGFloat kMenuIconIndent = 6.0f;
1039 NSString* nsNativeThemeCocoa::GetMenuIconName(const MenuIconParams& aParams) {
1040   switch (aParams.icon) {
1041     case MenuIcon::eCheckmark:
1042       return kCheckmarkImage;
1043     case MenuIcon::eMenuArrow:
1044       return aParams.rtl ? kMenuarrowLeftImage : kMenuarrowRightImage;
1045     case MenuIcon::eMenuDownScrollArrow:
1046       return kMenuDownScrollArrowImage;
1047     case MenuIcon::eMenuUpScrollArrow:
1048       return kMenuUpScrollArrowImage;
1049   }
1052 NSSize nsNativeThemeCocoa::GetMenuIconSize(MenuIcon aIcon) {
1053   switch (aIcon) {
1054     case MenuIcon::eCheckmark:
1055       return kCheckmarkSize;
1056     case MenuIcon::eMenuArrow:
1057       return kMenuarrowSize;
1058     case MenuIcon::eMenuDownScrollArrow:
1059     case MenuIcon::eMenuUpScrollArrow:
1060       return kMenuScrollArrowSize;
1061   }
1064 nsNativeThemeCocoa::MenuIconParams nsNativeThemeCocoa::ComputeMenuIconParams(
1065     nsIFrame* aFrame, EventStates aEventState, MenuIcon aIcon) {
1066   bool isDisabled = IsDisabled(aFrame, aEventState);
1068   MenuIconParams params;
1069   params.icon = aIcon;
1070   params.disabled = isDisabled;
1071   params.insideActiveMenuItem = !isDisabled && CheckBooleanAttr(aFrame, nsGkAtoms::menuactive);
1072   params.centerHorizontally = true;
1073   params.rtl = IsFrameRTL(aFrame);
1074   return params;
1077 void nsNativeThemeCocoa::DrawMenuIcon(CGContextRef cgContext, const CGRect& aRect,
1078                                       const MenuIconParams& aParams) {
1079   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1081   NSSize size = GetMenuIconSize(aParams.icon);
1083   // Adjust size and position of our drawRect.
1084   CGFloat paddingX = std::max(CGFloat(0.0), aRect.size.width - size.width);
1085   CGFloat paddingY = std::max(CGFloat(0.0), aRect.size.height - size.height);
1086   CGFloat paddingStartX = std::min(paddingX, kMenuIconIndent);
1087   CGFloat paddingEndX = std::max(CGFloat(0.0), paddingX - kMenuIconIndent);
1088   CGRect drawRect = CGRectMake(aRect.origin.x + (aParams.centerHorizontally ? ceil(paddingX / 2)
1089                                                  : aParams.rtl              ? paddingEndX
1090                                                                             : paddingStartX),
1091                                aRect.origin.y + ceil(paddingY / 2), size.width, size.height);
1093   NSString* state =
1094       aParams.disabled ? @"disabled" : (aParams.insideActiveMenuItem ? @"pressed" : @"normal");
1096   NSString* imageName = GetMenuIconName(aParams);
1098   RenderWithCoreUI(
1099       drawRect, cgContext,
1100       [NSDictionary dictionaryWithObjectsAndKeys:@"kCUIBackgroundTypeMenu", @"backgroundTypeKey",
1101                                                  imageName, @"imageNameKey", state, @"state",
1102                                                  @"image", @"widget", [NSNumber numberWithBool:YES],
1103                                                  @"is.flipped", nil]);
1105 #if DRAW_IN_FRAME_DEBUG
1106   CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
1107   CGContextFillRect(cgContext, drawRect);
1108 #endif
1110   NS_OBJC_END_TRY_ABORT_BLOCK;
1113 nsNativeThemeCocoa::MenuItemParams nsNativeThemeCocoa::ComputeMenuItemParams(
1114     nsIFrame* aFrame, EventStates aEventState, bool aIsChecked) {
1115   bool isDisabled = IsDisabled(aFrame, aEventState);
1117   MenuItemParams params;
1118   params.checked = aIsChecked;
1119   params.disabled = isDisabled;
1120   params.selected = !isDisabled && CheckBooleanAttr(aFrame, nsGkAtoms::menuactive);
1121   params.rtl = IsFrameRTL(aFrame);
1122   return params;
1125 static void SetCGContextFillColor(CGContextRef cgContext, const sRGBColor& aColor) {
1126   DeviceColor color = ToDeviceColor(aColor);
1127   CGContextSetRGBFillColor(cgContext, color.r, color.g, color.b, color.a);
1130 void nsNativeThemeCocoa::DrawMenuItem(CGContextRef cgContext, const CGRect& inBoxRect,
1131                                       const MenuItemParams& aParams) {
1132   if (aParams.checked) {
1133     MenuIconParams params;
1134     params.disabled = aParams.disabled;
1135     params.insideActiveMenuItem = aParams.selected;
1136     params.rtl = aParams.rtl;
1137     params.icon = MenuIcon::eCheckmark;
1138     DrawMenuIcon(cgContext, inBoxRect, params);
1139   }
1142 void nsNativeThemeCocoa::DrawMenuSeparator(CGContextRef cgContext, const CGRect& inBoxRect,
1143                                            const MenuItemParams& aParams) {
1144   // Workaround for visual artifacts issues with
1145   // HIThemeDrawMenuSeparator on macOS Big Sur.
1146   if (nsCocoaFeatures::OnBigSurOrLater()) {
1147     CGRect separatorRect = inBoxRect;
1148     separatorRect.size.height = 1;
1149     separatorRect.size.width -= 42;
1150     separatorRect.origin.x += 21;
1151     // Use transparent black with an alpha similar to the native separator.
1152     // The values 231 (menu background) and 205 (separator color) have been
1153     // sampled from a window screenshot of a native context menu.
1154     CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.0, (231 - 205) / 231.0);
1155     CGContextFillRect(cgContext, separatorRect);
1156     return;
1157   }
1159   ThemeMenuState menuState;
1160   if (aParams.disabled) {
1161     menuState = kThemeMenuDisabled;
1162   } else {
1163     menuState = aParams.selected ? kThemeMenuSelected : kThemeMenuActive;
1164   }
1166   HIThemeMenuItemDrawInfo midi = {0, kThemeMenuItemPlain, menuState};
1167   HIThemeDrawMenuSeparator(&inBoxRect, &inBoxRect, &midi, cgContext, HITHEME_ORIENTATION);
1170 static bool ShouldUnconditionallyDrawFocusRingIfFocused(nsIFrame* aFrame) {
1171   // Mac always draws focus rings for textboxes and lists.
1172   switch (aFrame->StyleDisplay()->EffectiveAppearance()) {
1173     case StyleAppearance::NumberInput:
1174     case StyleAppearance::Textfield:
1175     case StyleAppearance::Textarea:
1176     case StyleAppearance::Searchfield:
1177     case StyleAppearance::Listbox:
1178       return true;
1179     default:
1180       return false;
1181   }
1184 nsNativeThemeCocoa::ControlParams nsNativeThemeCocoa::ComputeControlParams(
1185     nsIFrame* aFrame, EventStates aEventState) {
1186   ControlParams params;
1187   params.disabled = IsDisabled(aFrame, aEventState);
1188   params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
1189   params.pressed = aEventState.HasAllStates(NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_HOVER);
1190   params.focused = aEventState.HasState(NS_EVENT_STATE_FOCUS) &&
1191                    (aEventState.HasState(NS_EVENT_STATE_FOCUSRING) ||
1192                     ShouldUnconditionallyDrawFocusRingIfFocused(aFrame));
1193   params.rtl = IsFrameRTL(aFrame);
1194   return params;
1197 static const NSSize kHelpButtonSize = NSMakeSize(20, 20);
1198 static const NSSize kDisclosureButtonSize = NSMakeSize(21, 21);
1200 static const CellRenderSettings pushButtonSettings = {{
1201                                                           NSMakeSize(0, 16),  // mini
1202                                                           NSMakeSize(0, 19),  // small
1203                                                           NSMakeSize(0, 22)   // regular
1204                                                       },
1205                                                       {
1206                                                           NSMakeSize(18, 0),  // mini
1207                                                           NSMakeSize(26, 0),  // small
1208                                                           NSMakeSize(30, 0)   // regular
1209                                                       },
1210                                                       {{
1211                                                            // Leopard
1212                                                            {0, 0, 0, 0},  // mini
1213                                                            {4, 0, 4, 1},  // small
1214                                                            {5, 0, 5, 2}   // regular
1215                                                        },
1216                                                        {
1217                                                            // Yosemite
1218                                                            {0, 0, 0, 0},  // mini
1219                                                            {4, 0, 4, 1},  // small
1220                                                            {5, 0, 5, 2}   // regular
1221                                                        }}};
1223 // The height at which we start doing square buttons instead of rounded buttons
1224 // Rounded buttons look bad if drawn at a height greater than 26, so at that point
1225 // we switch over to doing square buttons which looks fine at any size.
1226 #define DO_SQUARE_BUTTON_HEIGHT 26
1228 void nsNativeThemeCocoa::DrawRoundedBezelPushButton(CGContextRef cgContext, const HIRect& inBoxRect,
1229                                                     ControlParams aControlParams) {
1230   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1232   ApplyControlParamsToNSCell(aControlParams, mPushButtonCell);
1233   [mPushButtonCell setBezelStyle:NSRoundedBezelStyle];
1234   DrawCellWithSnapping(mPushButtonCell, cgContext, inBoxRect, pushButtonSettings, 0.5f,
1235                        mCellDrawView, aControlParams.rtl, 1.0f);
1237   NS_OBJC_END_TRY_ABORT_BLOCK;
1240 void nsNativeThemeCocoa::DrawSquareBezelPushButton(CGContextRef cgContext, const HIRect& inBoxRect,
1241                                                    ControlParams aControlParams) {
1242   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1244   ApplyControlParamsToNSCell(aControlParams, mPushButtonCell);
1245   [mPushButtonCell setBezelStyle:NSShadowlessSquareBezelStyle];
1246   DrawCellWithScaling(mPushButtonCell, cgContext, inBoxRect, NSControlSizeRegular, NSZeroSize,
1247                       NSMakeSize(14, 0), NULL, mCellDrawView, aControlParams.rtl);
1249   NS_OBJC_END_TRY_ABORT_BLOCK;
1252 void nsNativeThemeCocoa::DrawHelpButton(CGContextRef cgContext, const HIRect& inBoxRect,
1253                                         ControlParams aControlParams) {
1254   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1256   ApplyControlParamsToNSCell(aControlParams, mHelpButtonCell);
1257   DrawCellWithScaling(mHelpButtonCell, cgContext, inBoxRect, NSControlSizeRegular, NSZeroSize,
1258                       kHelpButtonSize, NULL, mCellDrawView,
1259                       false);  // Don't mirror icon in RTL.
1261   NS_OBJC_END_TRY_ABORT_BLOCK;
1264 void nsNativeThemeCocoa::DrawDisclosureButton(CGContextRef cgContext, const HIRect& inBoxRect,
1265                                               ControlParams aControlParams,
1266                                               NSCellStateValue aCellState) {
1267   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1269   ApplyControlParamsToNSCell(aControlParams, mDisclosureButtonCell);
1270   [mDisclosureButtonCell setState:aCellState];
1271   DrawCellWithScaling(mDisclosureButtonCell, cgContext, inBoxRect, NSControlSizeRegular, NSZeroSize,
1272                       kDisclosureButtonSize, NULL, mCellDrawView,
1273                       false);  // Don't mirror icon in RTL.
1275   NS_OBJC_END_TRY_ABORT_BLOCK;
1278 void nsNativeThemeCocoa::DrawFocusOutline(CGContextRef cgContext, const HIRect& inBoxRect) {
1279   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1280   NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
1281   [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:cgContext
1282                                                                                   flipped:YES]];
1283   CGContextSaveGState(cgContext);
1284   NSSetFocusRingStyle(NSFocusRingOnly);
1285   NSRectFill(NSRectFromCGRect(inBoxRect));
1286   CGContextRestoreGState(cgContext);
1287   [NSGraphicsContext setCurrentContext:savedContext];
1289   NS_OBJC_END_TRY_ABORT_BLOCK;
1292 typedef void (*RenderHIThemeControlFunction)(CGContextRef cgContext, const HIRect& aRenderRect,
1293                                              void* aData);
1295 static void RenderTransformedHIThemeControl(CGContextRef aCGContext, const HIRect& aRect,
1296                                             RenderHIThemeControlFunction aFunc, void* aData,
1297                                             BOOL mirrorHorizontally = NO) {
1298   CGAffineTransform savedCTM = CGContextGetCTM(aCGContext);
1299   CGContextTranslateCTM(aCGContext, aRect.origin.x, aRect.origin.y);
1301   bool drawDirect;
1302   HIRect drawRect = aRect;
1303   drawRect.origin = CGPointZero;
1305   if (!mirrorHorizontally && savedCTM.a == 1.0f && savedCTM.b == 0.0f && savedCTM.c == 0.0f &&
1306       (savedCTM.d == 1.0f || savedCTM.d == -1.0f)) {
1307     drawDirect = TRUE;
1308   } else {
1309     drawDirect = FALSE;
1310   }
1312   // Fall back to no bitmap buffer if the area of our control (in pixels^2)
1313   // is too large.
1314   if (drawDirect || (aRect.size.width * aRect.size.height > BITMAP_MAX_AREA)) {
1315     aFunc(aCGContext, drawRect, aData);
1316   } else {
1317     // Inflate the buffer to capture focus rings.
1318     int w = ceil(drawRect.size.width) + 2 * kMaxFocusRingWidth;
1319     int h = ceil(drawRect.size.height) + 2 * kMaxFocusRingWidth;
1321     int backingScaleFactor = GetBackingScaleFactorForRendering(aCGContext);
1322     CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
1323     CGContextRef bitmapctx = CGBitmapContextCreate(
1324         NULL, w * backingScaleFactor, h * backingScaleFactor, 8, w * backingScaleFactor * 4,
1325         colorSpace, kCGImageAlphaPremultipliedFirst);
1326     CGColorSpaceRelease(colorSpace);
1328     CGContextScaleCTM(bitmapctx, backingScaleFactor, backingScaleFactor);
1329     CGContextTranslateCTM(bitmapctx, kMaxFocusRingWidth, kMaxFocusRingWidth);
1331     // Set the context's "base transform" to in order to get correctly-sized focus rings.
1332     CGContextSetBaseCTM(bitmapctx,
1333                         CGAffineTransformMakeScale(backingScaleFactor, backingScaleFactor));
1335     // HITheme always wants to draw into a flipped context, or things
1336     // get confused.
1337     CGContextTranslateCTM(bitmapctx, 0.0f, aRect.size.height);
1338     CGContextScaleCTM(bitmapctx, 1.0f, -1.0f);
1340     aFunc(bitmapctx, drawRect, aData);
1342     CGImageRef bitmap = CGBitmapContextCreateImage(bitmapctx);
1344     CGAffineTransform ctm = CGContextGetCTM(aCGContext);
1346     // We need to unflip, so that we can do a DrawImage without getting a flipped image.
1347     CGContextTranslateCTM(aCGContext, 0.0f, aRect.size.height);
1348     CGContextScaleCTM(aCGContext, 1.0f, -1.0f);
1350     if (mirrorHorizontally) {
1351       CGContextTranslateCTM(aCGContext, aRect.size.width, 0);
1352       CGContextScaleCTM(aCGContext, -1.0f, 1.0f);
1353     }
1355     HIRect inflatedDrawRect = CGRectMake(-kMaxFocusRingWidth, -kMaxFocusRingWidth, w, h);
1356     CGContextDrawImage(aCGContext, inflatedDrawRect, bitmap);
1358     CGContextSetCTM(aCGContext, ctm);
1360     CGImageRelease(bitmap);
1361     CGContextRelease(bitmapctx);
1362   }
1364   CGContextSetCTM(aCGContext, savedCTM);
1367 static void RenderButton(CGContextRef cgContext, const HIRect& aRenderRect, void* aData) {
1368   HIThemeButtonDrawInfo* bdi = (HIThemeButtonDrawInfo*)aData;
1369   HIThemeDrawButton(&aRenderRect, bdi, cgContext, kHIThemeOrientationNormal, NULL);
1372 static ThemeDrawState ToThemeDrawState(const nsNativeThemeCocoa::ControlParams& aParams) {
1373   if (aParams.disabled) {
1374     return kThemeStateUnavailable;
1375   }
1376   if (aParams.pressed) {
1377     return kThemeStatePressed;
1378   }
1379   return kThemeStateActive;
1382 void nsNativeThemeCocoa::DrawHIThemeButton(CGContextRef cgContext, const HIRect& aRect,
1383                                            ThemeButtonKind aKind, ThemeButtonValue aValue,
1384                                            ThemeDrawState aState, ThemeButtonAdornment aAdornment,
1385                                            const ControlParams& aParams) {
1386   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1388   HIThemeButtonDrawInfo bdi;
1389   bdi.version = 0;
1390   bdi.kind = aKind;
1391   bdi.value = aValue;
1392   bdi.state = aState;
1393   bdi.adornment = aAdornment;
1395   if (aParams.focused && aParams.insideActiveWindow) {
1396     bdi.adornment |= kThemeAdornmentFocus;
1397   }
1399   if ((aAdornment & kThemeAdornmentDefault) && !aParams.disabled) {
1400     bdi.animation.time.start = 0;
1401     bdi.animation.time.current = CFAbsoluteTimeGetCurrent();
1402   }
1404   RenderTransformedHIThemeControl(cgContext, aRect, RenderButton, &bdi, aParams.rtl);
1406 #if DRAW_IN_FRAME_DEBUG
1407   CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
1408   CGContextFillRect(cgContext, inBoxRect);
1409 #endif
1411   NS_OBJC_END_TRY_ABORT_BLOCK;
1414 void nsNativeThemeCocoa::DrawButton(CGContextRef cgContext, const HIRect& inBoxRect,
1415                                     const ButtonParams& aParams) {
1416   ControlParams controlParams = aParams.controlParams;
1418   switch (aParams.button) {
1419     case ButtonType::eRegularPushButton:
1420     case ButtonType::eDefaultPushButton: {
1421       ThemeButtonAdornment adornment = aParams.button == ButtonType::eDefaultPushButton
1422                                            ? kThemeAdornmentDefault
1423                                            : kThemeAdornmentNone;
1424       HIRect drawFrame = inBoxRect;
1425       drawFrame.size.height -= 2;
1426       if (inBoxRect.size.height >= pushButtonSettings.naturalSizes[regularControlSize].height) {
1427         DrawHIThemeButton(cgContext, drawFrame, kThemePushButton, kThemeButtonOff,
1428                           ToThemeDrawState(controlParams), adornment, controlParams);
1429         return;
1430       }
1431       if (inBoxRect.size.height >= pushButtonSettings.naturalSizes[smallControlSize].height) {
1432         drawFrame.origin.y -= 1;
1433         drawFrame.origin.x += 1;
1434         drawFrame.size.width -= 2;
1435         DrawHIThemeButton(cgContext, drawFrame, kThemePushButtonSmall, kThemeButtonOff,
1436                           ToThemeDrawState(controlParams), adornment, controlParams);
1437         return;
1438       }
1439       DrawHIThemeButton(cgContext, drawFrame, kThemePushButtonMini, kThemeButtonOff,
1440                         ToThemeDrawState(controlParams), adornment, controlParams);
1441       return;
1442     }
1443     case ButtonType::eRegularBevelButton:
1444     case ButtonType::eDefaultBevelButton: {
1445       ThemeButtonAdornment adornment = aParams.button == ButtonType::eDefaultBevelButton
1446                                            ? kThemeAdornmentDefault
1447                                            : kThemeAdornmentNone;
1448       DrawHIThemeButton(cgContext, inBoxRect, kThemeMediumBevelButton, kThemeButtonOff,
1449                         ToThemeDrawState(controlParams), adornment, controlParams);
1450       return;
1451     }
1452     case ButtonType::eRoundedBezelPushButton:
1453       DrawRoundedBezelPushButton(cgContext, inBoxRect, controlParams);
1454       return;
1455     case ButtonType::eSquareBezelPushButton:
1456       DrawSquareBezelPushButton(cgContext, inBoxRect, controlParams);
1457       return;
1458     case ButtonType::eArrowButton:
1459       DrawHIThemeButton(cgContext, inBoxRect, kThemeArrowButton, kThemeButtonOn,
1460                         kThemeStateUnavailable, kThemeAdornmentArrowDownArrow, controlParams);
1461       return;
1462     case ButtonType::eHelpButton:
1463       DrawHelpButton(cgContext, inBoxRect, controlParams);
1464       return;
1465     case ButtonType::eTreeTwistyPointingRight:
1466       DrawHIThemeButton(cgContext, inBoxRect, kThemeDisclosureButton, kThemeDisclosureRight,
1467                         ToThemeDrawState(controlParams), kThemeAdornmentNone, controlParams);
1468       return;
1469     case ButtonType::eTreeTwistyPointingDown:
1470       DrawHIThemeButton(cgContext, inBoxRect, kThemeDisclosureButton, kThemeDisclosureDown,
1471                         ToThemeDrawState(controlParams), kThemeAdornmentNone, controlParams);
1472       return;
1473     case ButtonType::eDisclosureButtonClosed:
1474       DrawDisclosureButton(cgContext, inBoxRect, controlParams, NSOffState);
1475       return;
1476     case ButtonType::eDisclosureButtonOpen:
1477       DrawDisclosureButton(cgContext, inBoxRect, controlParams, NSOnState);
1478       return;
1479   }
1482 nsNativeThemeCocoa::TreeHeaderCellParams nsNativeThemeCocoa::ComputeTreeHeaderCellParams(
1483     nsIFrame* aFrame, EventStates aEventState) {
1484   TreeHeaderCellParams params;
1485   params.controlParams = ComputeControlParams(aFrame, aEventState);
1486   params.sortDirection = GetTreeSortDirection(aFrame);
1487   params.lastTreeHeaderCell = IsLastTreeHeaderCell(aFrame);
1488   return params;
1491 void nsNativeThemeCocoa::DrawTreeHeaderCell(CGContextRef cgContext, const HIRect& inBoxRect,
1492                                             const TreeHeaderCellParams& aParams) {
1493   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1495   HIThemeButtonDrawInfo bdi;
1496   bdi.version = 0;
1497   bdi.kind = kThemeListHeaderButton;
1498   bdi.value = kThemeButtonOff;
1499   bdi.adornment = kThemeAdornmentNone;
1501   switch (aParams.sortDirection) {
1502     case eTreeSortDirection_Natural:
1503       break;
1504     case eTreeSortDirection_Ascending:
1505       bdi.value = kThemeButtonOn;
1506       bdi.adornment = kThemeAdornmentHeaderButtonSortUp;
1507       break;
1508     case eTreeSortDirection_Descending:
1509       bdi.value = kThemeButtonOn;
1510       break;
1511   }
1513   if (aParams.controlParams.disabled) {
1514     bdi.state = kThemeStateUnavailable;
1515   } else if (aParams.controlParams.pressed) {
1516     bdi.state = kThemeStatePressed;
1517   } else if (!aParams.controlParams.insideActiveWindow) {
1518     bdi.state = kThemeStateInactive;
1519   } else {
1520     bdi.state = kThemeStateActive;
1521   }
1523   CGContextClipToRect(cgContext, inBoxRect);
1525   HIRect drawFrame = inBoxRect;
1526   // Always remove the top border.
1527   drawFrame.origin.y -= 1;
1528   drawFrame.size.height += 1;
1529   // Remove the left border in LTR mode and the right border in RTL mode.
1530   drawFrame.size.width += 1;
1531   if (aParams.lastTreeHeaderCell) {
1532     drawFrame.size.width += 1;  // Also remove the other border.
1533   }
1534   if (!aParams.controlParams.rtl || aParams.lastTreeHeaderCell) {
1535     drawFrame.origin.x -= 1;
1536   }
1538   RenderTransformedHIThemeControl(cgContext, drawFrame, RenderButton, &bdi,
1539                                   aParams.controlParams.rtl);
1541 #if DRAW_IN_FRAME_DEBUG
1542   CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
1543   CGContextFillRect(cgContext, inBoxRect);
1544 #endif
1546   NS_OBJC_END_TRY_ABORT_BLOCK;
1549 static const CellRenderSettings dropdownSettings = {{
1550                                                         NSMakeSize(0, 16),  // mini
1551                                                         NSMakeSize(0, 19),  // small
1552                                                         NSMakeSize(0, 22)   // regular
1553                                                     },
1554                                                     {
1555                                                         NSMakeSize(18, 0),  // mini
1556                                                         NSMakeSize(38, 0),  // small
1557                                                         NSMakeSize(44, 0)   // regular
1558                                                     },
1559                                                     {{
1560                                                          // Leopard
1561                                                          {1, 1, 2, 1},  // mini
1562                                                          {3, 0, 3, 1},  // small
1563                                                          {3, 0, 3, 0}   // regular
1564                                                      },
1565                                                      {
1566                                                          // Yosemite
1567                                                          {1, 1, 2, 1},  // mini
1568                                                          {3, 0, 3, 1},  // small
1569                                                          {3, 0, 3, 0}   // regular
1570                                                      }}};
1572 static const CellRenderSettings editableMenulistSettings = {{
1573                                                                 NSMakeSize(0, 15),  // mini
1574                                                                 NSMakeSize(0, 18),  // small
1575                                                                 NSMakeSize(0, 21)   // regular
1576                                                             },
1577                                                             {
1578                                                                 NSMakeSize(18, 0),  // mini
1579                                                                 NSMakeSize(38, 0),  // small
1580                                                                 NSMakeSize(44, 0)   // regular
1581                                                             },
1582                                                             {{
1583                                                                  // Leopard
1584                                                                  {0, 0, 2, 2},  // mini
1585                                                                  {0, 0, 3, 2},  // small
1586                                                                  {0, 1, 3, 3}   // regular
1587                                                              },
1588                                                              {
1589                                                                  // Yosemite
1590                                                                  {0, 0, 2, 2},  // mini
1591                                                                  {0, 0, 3, 2},  // small
1592                                                                  {0, 1, 3, 3}   // regular
1593                                                              }}};
1595 void nsNativeThemeCocoa::DrawDropdown(CGContextRef cgContext, const HIRect& inBoxRect,
1596                                       const DropdownParams& aParams) {
1597   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1599   [mDropdownCell setPullsDown:aParams.pullsDown];
1600   NSCell* cell = aParams.editable ? (NSCell*)mComboBoxCell : (NSCell*)mDropdownCell;
1602   ApplyControlParamsToNSCell(aParams.controlParams, cell);
1604   if (aParams.controlParams.insideActiveWindow) {
1605     [cell setControlTint:[NSColor currentControlTint]];
1606   } else {
1607     [cell setControlTint:NSClearControlTint];
1608   }
1610   const CellRenderSettings& settings =
1611       aParams.editable ? editableMenulistSettings : dropdownSettings;
1612   DrawCellWithSnapping(cell, cgContext, inBoxRect, settings, 0.5f, mCellDrawView,
1613                        aParams.controlParams.rtl);
1615   NS_OBJC_END_TRY_ABORT_BLOCK;
1618 static const CellRenderSettings spinnerSettings = {
1619     {
1620         NSMakeSize(11, 16),  // mini (width trimmed by 2px to reduce blank border)
1621         NSMakeSize(15, 22),  // small
1622         NSMakeSize(19, 27)   // regular
1623     },
1624     {
1625         NSMakeSize(11, 16),  // mini (width trimmed by 2px to reduce blank border)
1626         NSMakeSize(15, 22),  // small
1627         NSMakeSize(19, 27)   // regular
1628     },
1629     {{
1630          // Leopard
1631          {0, 0, 0, 0},  // mini
1632          {0, 0, 0, 0},  // small
1633          {0, 0, 0, 0}   // regular
1634      },
1635      {
1636          // Yosemite
1637          {0, 0, 0, 0},  // mini
1638          {0, 0, 0, 0},  // small
1639          {0, 0, 0, 0}   // regular
1640      }}};
1642 HIThemeButtonDrawInfo nsNativeThemeCocoa::SpinButtonDrawInfo(ThemeButtonKind aKind,
1643                                                              const SpinButtonParams& aParams) {
1644   HIThemeButtonDrawInfo bdi;
1645   bdi.version = 0;
1646   bdi.kind = aKind;
1647   bdi.value = kThemeButtonOff;
1648   bdi.adornment = kThemeAdornmentNone;
1650   if (aParams.disabled) {
1651     bdi.state = kThemeStateUnavailable;
1652   } else if (aParams.insideActiveWindow && aParams.pressedButton) {
1653     if (*aParams.pressedButton == SpinButton::eUp) {
1654       bdi.state = kThemeStatePressedUp;
1655     } else {
1656       bdi.state = kThemeStatePressedDown;
1657     }
1658   } else {
1659     bdi.state = kThemeStateActive;
1660   }
1662   return bdi;
1665 void nsNativeThemeCocoa::DrawSpinButtons(CGContextRef cgContext, const HIRect& inBoxRect,
1666                                          const SpinButtonParams& aParams) {
1667   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1669   HIThemeButtonDrawInfo bdi = SpinButtonDrawInfo(kThemeIncDecButton, aParams);
1670   HIThemeDrawButton(&inBoxRect, &bdi, cgContext, HITHEME_ORIENTATION, NULL);
1672   NS_OBJC_END_TRY_ABORT_BLOCK;
1675 void nsNativeThemeCocoa::DrawSpinButton(CGContextRef cgContext, const HIRect& inBoxRect,
1676                                         SpinButton aDrawnButton, const SpinButtonParams& aParams) {
1677   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1679   HIThemeButtonDrawInfo bdi = SpinButtonDrawInfo(kThemeIncDecButtonMini, aParams);
1681   // Cocoa only allows kThemeIncDecButton to paint the up and down spin buttons
1682   // together as a single unit (presumably because when one button is active,
1683   // the appearance of both changes (in different ways)). Here we have to paint
1684   // both buttons, using clip to hide the one we don't want to paint.
1685   HIRect drawRect = inBoxRect;
1686   drawRect.size.height *= 2;
1687   if (aDrawnButton == SpinButton::eDown) {
1688     drawRect.origin.y -= inBoxRect.size.height;
1689   }
1691   // Shift the drawing a little to the left, since cocoa paints with more
1692   // blank space around the visual buttons than we'd like:
1693   drawRect.origin.x -= 1;
1695   CGContextSaveGState(cgContext);
1696   CGContextClipToRect(cgContext, inBoxRect);
1698   HIThemeDrawButton(&drawRect, &bdi, cgContext, HITHEME_ORIENTATION, NULL);
1700   CGContextRestoreGState(cgContext);
1702   NS_OBJC_END_TRY_ABORT_BLOCK;
1705 void nsNativeThemeCocoa::DrawTextBox(CGContextRef cgContext, const HIRect& inBoxRect,
1706                                      TextBoxParams aParams) {
1707   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1709   SetCGContextFillColor(cgContext, sRGBColor(1.0, 1.0, 1.0, 1.0));
1710   CGContextFillRect(cgContext, inBoxRect);
1712 #if DRAW_IN_FRAME_DEBUG
1713   CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
1714   CGContextFillRect(cgContext, inBoxRect);
1715 #endif
1717   if (aParams.borderless) {
1718     return;
1719   }
1721   HIThemeFrameDrawInfo fdi;
1722   fdi.version = 0;
1723   fdi.kind = kHIThemeFrameTextFieldSquare;
1725   // We don't ever set an inactive state for this because it doesn't
1726   // look right (see other apps).
1727   fdi.state = aParams.disabled ? kThemeStateUnavailable : kThemeStateActive;
1728   fdi.isFocused = aParams.focused;
1730   // HIThemeDrawFrame takes the rect for the content area of the frame, not
1731   // the bounding rect for the frame. Here we reduce the size of the rect we
1732   // will pass to make it the size of the content.
1733   HIRect drawRect = inBoxRect;
1734   SInt32 frameOutset = 0;
1735   ::GetThemeMetric(kThemeMetricEditTextFrameOutset, &frameOutset);
1736   drawRect.origin.x += frameOutset;
1737   drawRect.origin.y += frameOutset;
1738   drawRect.size.width -= frameOutset * 2;
1739   drawRect.size.height -= frameOutset * 2;
1741   HIThemeDrawFrame(&drawRect, &fdi, cgContext, HITHEME_ORIENTATION);
1743   NS_OBJC_END_TRY_ABORT_BLOCK;
1746 static const CellRenderSettings progressSettings[2][2] = {
1747     // Vertical progress bar.
1748     {// Determined settings.
1749      {{
1750           NSZeroSize,         // mini
1751           NSMakeSize(10, 0),  // small
1752           NSMakeSize(16, 0)   // regular
1753       },
1754       {NSZeroSize, NSZeroSize, NSZeroSize},
1755       {{
1756           // Leopard
1757           {0, 0, 0, 0},  // mini
1758           {1, 1, 1, 1},  // small
1759           {1, 1, 1, 1}   // regular
1760       }}},
1761      // There is no horizontal margin in regular undetermined size.
1762      {{
1763           NSZeroSize,         // mini
1764           NSMakeSize(10, 0),  // small
1765           NSMakeSize(16, 0)   // regular
1766       },
1767       {NSZeroSize, NSZeroSize, NSZeroSize},
1768       {{
1769            // Leopard
1770            {0, 0, 0, 0},  // mini
1771            {1, 1, 1, 1},  // small
1772            {1, 0, 1, 0}   // regular
1773        },
1774        {
1775            // Yosemite
1776            {0, 0, 0, 0},  // mini
1777            {1, 1, 1, 1},  // small
1778            {1, 0, 1, 0}   // regular
1779        }}}},
1780     // Horizontal progress bar.
1781     {// Determined settings.
1782      {{
1783           NSZeroSize,         // mini
1784           NSMakeSize(0, 10),  // small
1785           NSMakeSize(0, 16)   // regular
1786       },
1787       {NSZeroSize, NSZeroSize, NSZeroSize},
1788       {{
1789            // Leopard
1790            {0, 0, 0, 0},  // mini
1791            {1, 1, 1, 1},  // small
1792            {1, 1, 1, 1}   // regular
1793        },
1794        {
1795            // Yosemite
1796            {0, 0, 0, 0},  // mini
1797            {1, 1, 1, 1},  // small
1798            {1, 1, 1, 1}   // regular
1799        }}},
1800      // There is no horizontal margin in regular undetermined size.
1801      {{
1802           NSZeroSize,         // mini
1803           NSMakeSize(0, 10),  // small
1804           NSMakeSize(0, 16)   // regular
1805       },
1806       {NSZeroSize, NSZeroSize, NSZeroSize},
1807       {{
1808            // Leopard
1809            {0, 0, 0, 0},  // mini
1810            {1, 1, 1, 1},  // small
1811            {0, 1, 0, 1}   // regular
1812        },
1813        {
1814            // Yosemite
1815            {0, 0, 0, 0},  // mini
1816            {1, 1, 1, 1},  // small
1817            {0, 1, 0, 1}   // regular
1818        }}}}};
1820 nsNativeThemeCocoa::ProgressParams nsNativeThemeCocoa::ComputeProgressParams(
1821     nsIFrame* aFrame, EventStates aEventState, bool aIsHorizontal) {
1822   ProgressParams params;
1823   params.value = GetProgressValue(aFrame);
1824   params.max = GetProgressMaxValue(aFrame);
1825   params.verticalAlignFactor = VerticalAlignFactor(aFrame);
1826   params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
1827   params.indeterminate = IsIndeterminateProgress(aFrame, aEventState);
1828   params.horizontal = aIsHorizontal;
1829   params.rtl = IsFrameRTL(aFrame);
1830   return params;
1833 void nsNativeThemeCocoa::DrawProgress(CGContextRef cgContext, const HIRect& inBoxRect,
1834                                       const ProgressParams& aParams) {
1835   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1837   NSProgressBarCell* cell = mProgressBarCell;
1839   [cell setValue:aParams.value];
1840   [cell setMax:aParams.max];
1841   [cell setIndeterminate:aParams.indeterminate];
1842   [cell setHorizontal:aParams.horizontal];
1843   [cell setControlTint:(aParams.insideActiveWindow ? [NSColor currentControlTint]
1844                                                    : NSClearControlTint)];
1846   DrawCellWithSnapping(cell, cgContext, inBoxRect,
1847                        progressSettings[aParams.horizontal][aParams.indeterminate],
1848                        aParams.verticalAlignFactor, mCellDrawView, aParams.rtl);
1850   NS_OBJC_END_TRY_ABORT_BLOCK;
1853 static const CellRenderSettings meterSetting = {{
1854                                                     NSMakeSize(0, 16),  // mini
1855                                                     NSMakeSize(0, 16),  // small
1856                                                     NSMakeSize(0, 16)   // regular
1857                                                 },
1858                                                 {NSZeroSize, NSZeroSize, NSZeroSize},
1859                                                 {{
1860                                                      // Leopard
1861                                                      {1, 1, 1, 1},  // mini
1862                                                      {1, 1, 1, 1},  // small
1863                                                      {1, 1, 1, 1}   // regular
1864                                                  },
1865                                                  {
1866                                                      // Yosemite
1867                                                      {1, 1, 1, 1},  // mini
1868                                                      {1, 1, 1, 1},  // small
1869                                                      {1, 1, 1, 1}   // regular
1870                                                  }}};
1872 nsNativeThemeCocoa::MeterParams nsNativeThemeCocoa::ComputeMeterParams(nsIFrame* aFrame) {
1873   nsIContent* content = aFrame->GetContent();
1874   if (!(content && content->IsHTMLElement(nsGkAtoms::meter))) {
1875     return MeterParams();
1876   }
1878   HTMLMeterElement* meterElement = static_cast<HTMLMeterElement*>(content);
1879   MeterParams params;
1880   params.value = meterElement->Value();
1881   params.min = meterElement->Min();
1882   params.max = meterElement->Max();
1883   EventStates states = meterElement->State();
1884   if (states.HasState(NS_EVENT_STATE_SUB_OPTIMUM)) {
1885     params.optimumState = OptimumState::eSubOptimum;
1886   } else if (states.HasState(NS_EVENT_STATE_SUB_SUB_OPTIMUM)) {
1887     params.optimumState = OptimumState::eSubSubOptimum;
1888   }
1889   params.horizontal = !IsVerticalMeter(aFrame);
1890   params.verticalAlignFactor = VerticalAlignFactor(aFrame);
1891   params.rtl = IsFrameRTL(aFrame);
1893   return params;
1896 void nsNativeThemeCocoa::DrawMeter(CGContextRef cgContext, const HIRect& inBoxRect,
1897                                    const MeterParams& aParams) {
1898   NS_OBJC_BEGIN_TRY_ABORT_BLOCK
1900   NSLevelIndicatorCell* cell = mMeterBarCell;
1902   [cell setMinValue:aParams.min];
1903   [cell setMaxValue:aParams.max];
1904   [cell setDoubleValue:aParams.value];
1906   /**
1907    * The way HTML and Cocoa defines the meter/indicator widget are different.
1908    * So, we are going to use a trick to get the Cocoa widget showing what we
1909    * are expecting: we set the warningValue or criticalValue to the current
1910    * value when we want to have the widget to be in the warning or critical
1911    * state.
1912    */
1913   switch (aParams.optimumState) {
1914     case OptimumState::eOptimum:
1915       [cell setWarningValue:aParams.max + 1];
1916       [cell setCriticalValue:aParams.max + 1];
1917       break;
1918     case OptimumState::eSubOptimum:
1919       [cell setWarningValue:aParams.value];
1920       [cell setCriticalValue:aParams.max + 1];
1921       break;
1922     case OptimumState::eSubSubOptimum:
1923       [cell setWarningValue:aParams.max + 1];
1924       [cell setCriticalValue:aParams.value];
1925       break;
1926   }
1928   HIRect rect = CGRectStandardize(inBoxRect);
1929   BOOL vertical = !aParams.horizontal;
1931   CGContextSaveGState(cgContext);
1933   if (vertical) {
1934     /**
1935      * Cocoa doesn't provide a vertical meter bar so to show one, we have to
1936      * show a rotated horizontal meter bar.
1937      * Given that we want to show a vertical meter bar, we assume that the rect
1938      * has vertical dimensions but we can't correctly draw a meter widget inside
1939      * such a rectangle so we need to inverse width and height (and re-position)
1940      * to get a rectangle with horizontal dimensions.
1941      * Finally, we want to show a vertical meter so we want to rotate the result
1942      * so it is vertical. We do that by changing the context.
1943      */
1944     CGFloat tmp = rect.size.width;
1945     rect.size.width = rect.size.height;
1946     rect.size.height = tmp;
1947     rect.origin.x += rect.size.height / 2.f - rect.size.width / 2.f;
1948     rect.origin.y += rect.size.width / 2.f - rect.size.height / 2.f;
1950     CGContextTranslateCTM(cgContext, CGRectGetMidX(rect), CGRectGetMidY(rect));
1951     CGContextRotateCTM(cgContext, -M_PI / 2.f);
1952     CGContextTranslateCTM(cgContext, -CGRectGetMidX(rect), -CGRectGetMidY(rect));
1953   }
1955   DrawCellWithSnapping(cell, cgContext, rect, meterSetting, aParams.verticalAlignFactor,
1956                        mCellDrawView, !vertical && aParams.rtl);
1958   CGContextRestoreGState(cgContext);
1960   NS_OBJC_END_TRY_ABORT_BLOCK
1963 void nsNativeThemeCocoa::DrawTabPanel(CGContextRef cgContext, const HIRect& inBoxRect,
1964                                       bool aIsInsideActiveWindow) {
1965   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1967   HIThemeTabPaneDrawInfo tpdi;
1969   tpdi.version = 1;
1970   tpdi.state = aIsInsideActiveWindow ? kThemeStateActive : kThemeStateInactive;
1971   tpdi.direction = kThemeTabNorth;
1972   tpdi.size = kHIThemeTabSizeNormal;
1973   tpdi.kind = kHIThemeTabKindNormal;
1975   HIThemeDrawTabPane(&inBoxRect, &tpdi, cgContext, HITHEME_ORIENTATION);
1977   NS_OBJC_END_TRY_ABORT_BLOCK;
1980 Maybe<nsNativeThemeCocoa::ScaleParams> nsNativeThemeCocoa::ComputeHTMLScaleParams(
1981     nsIFrame* aFrame, EventStates aEventState) {
1982   nsRangeFrame* rangeFrame = do_QueryFrame(aFrame);
1983   if (!rangeFrame) {
1984     return Nothing();
1985   }
1987   bool isHorizontal = IsRangeHorizontal(aFrame);
1989   // ScaleParams requires integer min, max and value. This is purely for
1990   // drawing, so we normalize to a range 0-1000 here.
1991   ScaleParams params;
1992   params.value = int32_t(rangeFrame->GetValueAsFractionOfRange() * 1000);
1993   params.min = 0;
1994   params.max = 1000;
1995   params.reverse = !isHorizontal || rangeFrame->IsRightToLeft();
1996   params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
1997   params.focused = aEventState.HasState(NS_EVENT_STATE_FOCUSRING);
1998   params.disabled = IsDisabled(aFrame, aEventState);
1999   params.horizontal = isHorizontal;
2000   return Some(params);
2003 void nsNativeThemeCocoa::DrawScale(CGContextRef cgContext, const HIRect& inBoxRect,
2004                                    const ScaleParams& aParams) {
2005   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2007   HIThemeTrackDrawInfo tdi;
2009   tdi.version = 0;
2010   tdi.kind = kThemeMediumSlider;
2011   tdi.bounds = inBoxRect;
2012   tdi.min = aParams.min;
2013   tdi.max = aParams.max;
2014   tdi.value = aParams.value;
2015   tdi.attributes = kThemeTrackShowThumb;
2016   if (aParams.horizontal) {
2017     tdi.attributes |= kThemeTrackHorizontal;
2018   }
2019   if (aParams.reverse) {
2020     tdi.attributes |= kThemeTrackRightToLeft;
2021   }
2022   if (aParams.focused) {
2023     tdi.attributes |= kThemeTrackHasFocus;
2024   }
2025   if (aParams.disabled) {
2026     tdi.enableState = kThemeTrackDisabled;
2027   } else {
2028     tdi.enableState = aParams.insideActiveWindow ? kThemeTrackActive : kThemeTrackInactive;
2029   }
2030   tdi.trackInfo.slider.thumbDir = kThemeThumbPlain;
2031   tdi.trackInfo.slider.pressState = 0;
2033   HIThemeDrawTrack(&tdi, NULL, cgContext, HITHEME_ORIENTATION);
2035   NS_OBJC_END_TRY_ABORT_BLOCK;
2038 nsIFrame* nsNativeThemeCocoa::SeparatorResponsibility(nsIFrame* aBefore, nsIFrame* aAfter) {
2039   // Usually a separator is drawn by the segment to the right of the
2040   // separator, but pressed and selected segments have higher priority.
2041   if (!aBefore || !aAfter) return nullptr;
2042   if (IsSelectedButton(aAfter)) return aAfter;
2043   if (IsSelectedButton(aBefore) || IsPressedButton(aBefore)) return aBefore;
2044   return aAfter;
2047 static CGRect SeparatorAdjustedRect(CGRect aRect, nsNativeThemeCocoa::SegmentParams aParams) {
2048   // A separator between two segments should always be located in the leftmost
2049   // pixel column of the segment to the right of the separator, regardless of
2050   // who ends up drawing it.
2051   // CoreUI draws the separators inside the drawing rect.
2052   if (!aParams.atLeftEnd && !aParams.drawsLeftSeparator) {
2053     // The segment to the left of us draws the separator, so we need to make
2054     // room for it.
2055     aRect.origin.x += 1;
2056     aRect.size.width -= 1;
2057   }
2058   if (aParams.drawsRightSeparator) {
2059     // We draw the right separator, so we need to extend the draw rect into the
2060     // segment to our right.
2061     aRect.size.width += 1;
2062   }
2063   return aRect;
2066 static NSString* ToolbarButtonPosition(BOOL aIsFirst, BOOL aIsLast) {
2067   if (aIsFirst) {
2068     if (aIsLast) return @"kCUISegmentPositionOnly";
2069     return @"kCUISegmentPositionFirst";
2070   }
2071   if (aIsLast) return @"kCUISegmentPositionLast";
2072   return @"kCUISegmentPositionMiddle";
2075 struct SegmentedControlRenderSettings {
2076   const CGFloat* heights;
2077   const NSString* widgetName;
2080 static const CGFloat tabHeights[3] = {17, 20, 23};
2082 static const SegmentedControlRenderSettings tabRenderSettings = {tabHeights, @"tab"};
2084 static const CGFloat toolbarButtonHeights[3] = {15, 18, 22};
2086 static const SegmentedControlRenderSettings toolbarButtonRenderSettings = {
2087     toolbarButtonHeights, @"kCUIWidgetButtonSegmentedSCurve"};
2089 nsNativeThemeCocoa::SegmentParams nsNativeThemeCocoa::ComputeSegmentParams(
2090     nsIFrame* aFrame, EventStates aEventState, SegmentType aSegmentType) {
2091   SegmentParams params;
2092   params.segmentType = aSegmentType;
2093   params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
2094   params.pressed = IsPressedButton(aFrame);
2095   params.selected = IsSelectedButton(aFrame);
2096   params.focused = aEventState.HasState(NS_EVENT_STATE_FOCUSRING);
2097   bool isRTL = IsFrameRTL(aFrame);
2098   nsIFrame* left = GetAdjacentSiblingFrameWithSameAppearance(aFrame, isRTL);
2099   nsIFrame* right = GetAdjacentSiblingFrameWithSameAppearance(aFrame, !isRTL);
2100   params.atLeftEnd = !left;
2101   params.atRightEnd = !right;
2102   params.drawsLeftSeparator = SeparatorResponsibility(left, aFrame) == aFrame;
2103   params.drawsRightSeparator = SeparatorResponsibility(aFrame, right) == aFrame;
2104   params.rtl = isRTL;
2105   return params;
2108 static SegmentedControlRenderSettings RenderSettingsForSegmentType(
2109     nsNativeThemeCocoa::SegmentType aSegmentType) {
2110   switch (aSegmentType) {
2111     case nsNativeThemeCocoa::SegmentType::eToolbarButton:
2112       return toolbarButtonRenderSettings;
2113     case nsNativeThemeCocoa::SegmentType::eTab:
2114       return tabRenderSettings;
2115   }
2118 void nsNativeThemeCocoa::DrawSegmentBackground(CGContextRef cgContext, const HIRect& inBoxRect,
2119                                                const SegmentParams& aParams) {
2120   // On earlier macOS versions, the segment background is automatically
2121   // drawn correctly and this method should not be used. ASSERT here
2122   // to catch unnecessary usage, but the method implementation is not
2123   // dependent on Big Sur in any way.
2124   MOZ_ASSERT(nsCocoaFeatures::OnBigSurOrLater());
2126   // Use colors resembling 10.15.
2127   if (aParams.selected) {
2128     DeviceColor color = ToDeviceColor(mozilla::gfx::sRGBColor::FromU8(93, 93, 93, 255));
2129     CGContextSetRGBFillColor(cgContext, color.r, color.g, color.b, color.a);
2130   } else {
2131     DeviceColor color = ToDeviceColor(mozilla::gfx::sRGBColor::FromU8(247, 247, 247, 255));
2132     CGContextSetRGBFillColor(cgContext, color.r, color.g, color.b, color.a);
2133   }
2135   // Create a rect for the background fill.
2136   CGRect bgRect = inBoxRect;
2137   bgRect.size.height -= 3.0;
2138   bgRect.size.width -= 4.0;
2139   bgRect.origin.x += 2.0;
2140   bgRect.origin.y += 1.0;
2142   // Round the corners unless the button is a middle button. Buttons in
2143   // a grouping but on the edge will have the inner edge filled below.
2144   if (aParams.atLeftEnd || aParams.atRightEnd) {
2145     CGPathRef path = CGPathCreateWithRoundedRect(bgRect, 5, 4, nullptr);
2146     CGContextAddPath(cgContext, path);
2147     CGPathRelease(path);
2148     CGContextClosePath(cgContext);
2149     CGContextFillPath(cgContext);
2150   }
2152   // Handle buttons grouped together where either or both of
2153   // the side edges do not have curved corners.
2154   if (!aParams.atLeftEnd && aParams.atRightEnd) {
2155     // Shift the rect left to draw the left side of the
2156     // rect with right angle corners leaving the right side
2157     // to have rounded corners drawn with the curve above.
2158     // For example, the left side of the forward button in
2159     // the Library window.
2160     CGRect leftRectEdge = bgRect;
2161     leftRectEdge.size.width -= 10;
2162     leftRectEdge.origin.x -= 2;
2163     CGContextFillRect(cgContext, leftRectEdge);
2164   } else if (aParams.atLeftEnd && !aParams.atRightEnd) {
2165     // Shift the rect right to draw the right side of the
2166     // rect with right angle corners leaving the left side
2167     // to have rounded corners drawn with the curve above.
2168     // For example, the right side of the back button in
2169     // the Library window.
2170     CGRect rightRectEdge = bgRect;
2171     rightRectEdge.size.width -= 10;
2172     rightRectEdge.origin.x += 12;
2173     CGContextFillRect(cgContext, rightRectEdge);
2174   } else if (!aParams.atLeftEnd && !aParams.atRightEnd) {
2175     // The middle button in a group of buttons. Widen the
2176     // background rect to meet adjacent buttons seamlessly.
2177     CGRect middleRect = bgRect;
2178     middleRect.size.width += 4;
2179     middleRect.origin.x -= 2;
2180     CGContextFillRect(cgContext, middleRect);
2181   }
2184 void nsNativeThemeCocoa::DrawSegment(CGContextRef cgContext, const HIRect& inBoxRect,
2185                                      const SegmentParams& aParams) {
2186   SegmentedControlRenderSettings renderSettings = RenderSettingsForSegmentType(aParams.segmentType);
2188   // On Big Sur, manually draw the background of the buttons to workaround a
2189   // change in Big Sur where the backround is filled with the toolbar gradient.
2190   if (nsCocoaFeatures::OnBigSurOrLater() &&
2191       (aParams.segmentType == nsNativeThemeCocoa::SegmentType::eToolbarButton)) {
2192     DrawSegmentBackground(cgContext, inBoxRect, aParams);
2193   }
2195   NSControlSize controlSize = FindControlSize(inBoxRect.size.height, renderSettings.heights, 4.0f);
2196   CGRect drawRect = SeparatorAdjustedRect(inBoxRect, aParams);
2198   NSDictionary* dict = @{
2199     @"widget" : renderSettings.widgetName,
2200     @"kCUIPresentationStateKey" : (aParams.insideActiveWindow ? @"kCUIPresentationStateActiveKey"
2201                                                               : @"kCUIPresentationStateInactive"),
2202     @"kCUIPositionKey" : ToolbarButtonPosition(aParams.atLeftEnd, aParams.atRightEnd),
2203     @"kCUISegmentLeadingSeparatorKey" : [NSNumber numberWithBool:aParams.drawsLeftSeparator],
2204     @"kCUISegmentTrailingSeparatorKey" : [NSNumber numberWithBool:aParams.drawsRightSeparator],
2205     @"value" : [NSNumber numberWithBool:aParams.selected],
2206     @"state" :
2207         (aParams.pressed ? @"pressed" : (aParams.insideActiveWindow ? @"normal" : @"inactive")),
2208     @"focus" : [NSNumber numberWithBool:aParams.focused],
2209     @"size" : CUIControlSizeForCocoaSize(controlSize),
2210     @"is.flipped" : [NSNumber numberWithBool:YES],
2211     @"direction" : @"up"
2212   };
2214   RenderWithCoreUI(drawRect, cgContext, dict);
2217 void nsNativeThemeCocoa::DrawToolbar(CGContextRef cgContext, const CGRect& inBoxRect,
2218                                      bool aIsMain) {
2219   CGRect drawRect = inBoxRect;
2221   // top border
2222   drawRect.size.height = 1.0f;
2223   DrawNativeGreyColorInRect(cgContext, toolbarTopBorderGrey, drawRect, aIsMain);
2225   // background
2226   drawRect.origin.y += drawRect.size.height;
2227   drawRect.size.height = inBoxRect.size.height - 2.0f;
2228   DrawNativeGreyColorInRect(cgContext, toolbarFillGrey, drawRect, aIsMain);
2230   // bottom border
2231   drawRect.origin.y += drawRect.size.height;
2232   drawRect.size.height = 1.0f;
2233   DrawNativeGreyColorInRect(cgContext, toolbarBottomBorderGrey, drawRect, aIsMain);
2236 static bool ToolbarCanBeUnified(const gfx::Rect& aRect, NSWindow* aWindow) {
2237   if (![aWindow isKindOfClass:[ToolbarWindow class]]) return false;
2239   ToolbarWindow* win = (ToolbarWindow*)aWindow;
2240   float unifiedToolbarHeight = [win unifiedToolbarHeight];
2241   return aRect.X() == 0 && aRect.Width() >= [win frame].size.width &&
2242          aRect.YMost() <= unifiedToolbarHeight;
2245 // By default, kCUIWidgetWindowFrame drawing draws rounded corners in the
2246 // upper corners. Depending on the context type, it fills the background in
2247 // the corners with black or leaves it transparent. Unfortunately, this corner
2248 // rounding interacts poorly with the window corner masking we apply during
2249 // titlebar drawing and results in small remnants of the corner background
2250 // appearing at the rounded edge.
2251 // So we draw square corners.
2252 static void DrawNativeTitlebarToolbarWithSquareCorners(CGContextRef aContext, const CGRect& aRect,
2253                                                        CGFloat aUnifiedHeight, BOOL aIsMain,
2254                                                        BOOL aIsFlipped) {
2255   // We extend the draw rect horizontally and clip away the rounded corners.
2256   const CGFloat extendHorizontal = 10;
2257   CGRect drawRect = CGRectInset(aRect, -extendHorizontal, 0);
2258   CGContextSaveGState(aContext);
2259   CGContextClipToRect(aContext, aRect);
2261   RenderWithCoreUI(
2262       drawRect, aContext,
2263       [NSDictionary
2264           dictionaryWithObjectsAndKeys:@"kCUIWidgetWindowFrame", @"widget", @"regularwin",
2265                                        @"windowtype", (aIsMain ? @"normal" : @"inactive"), @"state",
2266                                        [NSNumber numberWithDouble:aUnifiedHeight],
2267                                        @"kCUIWindowFrameUnifiedTitleBarHeightKey",
2268                                        [NSNumber numberWithBool:YES],
2269                                        @"kCUIWindowFrameDrawTitleSeparatorKey",
2270                                        [NSNumber numberWithBool:aIsFlipped], @"is.flipped", nil]);
2272   CGContextRestoreGState(aContext);
2275 void nsNativeThemeCocoa::DrawUnifiedToolbar(CGContextRef cgContext, const HIRect& inBoxRect,
2276                                             const UnifiedToolbarParams& aParams) {
2277   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2279   CGContextSaveGState(cgContext);
2280   CGContextClipToRect(cgContext, inBoxRect);
2282   CGFloat titlebarHeight = aParams.unifiedHeight - inBoxRect.size.height;
2283   CGRect drawRect = CGRectMake(inBoxRect.origin.x, inBoxRect.origin.y - titlebarHeight,
2284                                inBoxRect.size.width, inBoxRect.size.height + titlebarHeight);
2285   DrawNativeTitlebarToolbarWithSquareCorners(cgContext, drawRect, aParams.unifiedHeight,
2286                                              aParams.isMain, YES);
2288   CGContextRestoreGState(cgContext);
2290   NS_OBJC_END_TRY_ABORT_BLOCK;
2293 void nsNativeThemeCocoa::DrawStatusBar(CGContextRef cgContext, const HIRect& inBoxRect,
2294                                        bool aIsMain) {
2295   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2297   if (inBoxRect.size.height < 2.0f) return;
2299   CGContextSaveGState(cgContext);
2300   CGContextClipToRect(cgContext, inBoxRect);
2302   // kCUIWidgetWindowFrame draws a complete window frame with both title bar
2303   // and bottom bar. We only want the bottom bar, so we extend the draw rect
2304   // upwards to make space for the title bar, and then we clip it away.
2305   CGRect drawRect = inBoxRect;
2306   const int extendUpwards = 40;
2307   drawRect.origin.y -= extendUpwards;
2308   drawRect.size.height += extendUpwards;
2309   RenderWithCoreUI(
2310       drawRect, cgContext,
2311       [NSDictionary
2312           dictionaryWithObjectsAndKeys:@"kCUIWidgetWindowFrame", @"widget", @"regularwin",
2313                                        @"windowtype", (aIsMain ? @"normal" : @"inactive"), @"state",
2314                                        [NSNumber numberWithInt:inBoxRect.size.height],
2315                                        @"kCUIWindowFrameBottomBarHeightKey",
2316                                        [NSNumber numberWithBool:YES],
2317                                        @"kCUIWindowFrameDrawBottomBarSeparatorKey",
2318                                        [NSNumber numberWithBool:YES], @"is.flipped", nil]);
2320   CGContextRestoreGState(cgContext);
2322   NS_OBJC_END_TRY_ABORT_BLOCK;
2325 void nsNativeThemeCocoa::DrawNativeTitlebar(CGContextRef aContext, CGRect aTitlebarRect,
2326                                             CGFloat aUnifiedHeight, BOOL aIsMain, BOOL aIsFlipped) {
2327   CGFloat unifiedHeight = std::max(aUnifiedHeight, aTitlebarRect.size.height);
2328   DrawNativeTitlebarToolbarWithSquareCorners(aContext, aTitlebarRect, unifiedHeight, aIsMain,
2329                                              aIsFlipped);
2332 void nsNativeThemeCocoa::DrawNativeTitlebar(CGContextRef aContext, CGRect aTitlebarRect,
2333                                             const UnifiedToolbarParams& aParams) {
2334   DrawNativeTitlebar(aContext, aTitlebarRect, aParams.unifiedHeight, aParams.isMain, YES);
2337 static void RenderResizer(CGContextRef cgContext, const HIRect& aRenderRect, void* aData) {
2338   HIThemeGrowBoxDrawInfo* drawInfo = (HIThemeGrowBoxDrawInfo*)aData;
2339   HIThemeDrawGrowBox(&CGPointZero, drawInfo, cgContext, kHIThemeOrientationNormal);
2342 void nsNativeThemeCocoa::DrawResizer(CGContextRef cgContext, const HIRect& aRect, bool aIsRTL) {
2343   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2345   HIThemeGrowBoxDrawInfo drawInfo;
2346   drawInfo.version = 0;
2347   drawInfo.state = kThemeStateActive;
2348   drawInfo.kind = kHIThemeGrowBoxKindNormal;
2349   drawInfo.direction = kThemeGrowRight | kThemeGrowDown;
2350   drawInfo.size = kHIThemeGrowBoxSizeNormal;
2352   RenderTransformedHIThemeControl(cgContext, aRect, RenderResizer, &drawInfo, aIsRTL);
2354   NS_OBJC_END_TRY_ABORT_BLOCK;
2357 static const sRGBColor kMultilineTextFieldTopBorderColor(0.4510, 0.4510, 0.4510, 1.0);
2358 static const sRGBColor kMultilineTextFieldSidesAndBottomBorderColor(0.6, 0.6, 0.6, 1.0);
2359 static const sRGBColor kListboxTopBorderColor(0.557, 0.557, 0.557, 1.0);
2360 static const sRGBColor kListBoxSidesAndBottomBorderColor(0.745, 0.745, 0.745, 1.0);
2362 void nsNativeThemeCocoa::DrawMultilineTextField(CGContextRef cgContext, const CGRect& inBoxRect,
2363                                                 bool aIsFocused) {
2364   SetCGContextFillColor(cgContext, sRGBColor(1.0, 1.0, 1.0, 1.0));
2366   CGContextFillRect(cgContext, inBoxRect);
2368   float x = inBoxRect.origin.x, y = inBoxRect.origin.y;
2369   float w = inBoxRect.size.width, h = inBoxRect.size.height;
2370   SetCGContextFillColor(cgContext, kMultilineTextFieldTopBorderColor);
2371   CGContextFillRect(cgContext, CGRectMake(x, y, w, 1));
2372   SetCGContextFillColor(cgContext, kMultilineTextFieldSidesAndBottomBorderColor);
2373   CGContextFillRect(cgContext, CGRectMake(x, y + 1, 1, h - 1));
2374   CGContextFillRect(cgContext, CGRectMake(x + w - 1, y + 1, 1, h - 1));
2375   CGContextFillRect(cgContext, CGRectMake(x + 1, y + h - 1, w - 2, 1));
2377   if (aIsFocused) {
2378     NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
2379     [NSGraphicsContext
2380         setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:cgContext
2381                                                                      flipped:YES]];
2382     CGContextSaveGState(cgContext);
2383     NSSetFocusRingStyle(NSFocusRingOnly);
2384     NSRectFill(NSRectFromCGRect(inBoxRect));
2385     CGContextRestoreGState(cgContext);
2386     [NSGraphicsContext setCurrentContext:savedContext];
2387   }
2390 void nsNativeThemeCocoa::DrawSourceListSelection(CGContextRef aContext, const CGRect& aRect,
2391                                                  bool aWindowIsActive, bool aSelectionIsActive) {
2392   NSColor* fillColor;
2393   if (aSelectionIsActive) {
2394     // Active selection, blue or graphite.
2395     fillColor = ControlAccentColor();
2396   } else {
2397     // Inactive selection, gray.
2398     if (aWindowIsActive) {
2399       fillColor = [NSColor colorWithWhite:0.871 alpha:1.0];
2400     } else {
2401       fillColor = [NSColor colorWithWhite:0.808 alpha:1.0];
2402     }
2403   }
2404   CGContextSetFillColorWithColor(aContext, [fillColor CGColor]);
2405   CGContextFillRect(aContext, aRect);
2408 static bool IsHiDPIContext(nsDeviceContext* aContext) {
2409   return AppUnitsPerCSSPixel() >= 2 * aContext->AppUnitsPerDevPixelAtUnitFullZoom();
2412 Maybe<nsNativeThemeCocoa::WidgetInfo> nsNativeThemeCocoa::ComputeWidgetInfo(
2413     nsIFrame* aFrame, StyleAppearance aAppearance, const nsRect& aRect) {
2414   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
2416   // setup to draw into the correct port
2417   int32_t p2a = aFrame->PresContext()->AppUnitsPerDevPixel();
2419   gfx::Rect nativeWidgetRect(aRect.x, aRect.y, aRect.width, aRect.height);
2420   nativeWidgetRect.Scale(1.0 / gfxFloat(p2a));
2421   float originalHeight = nativeWidgetRect.Height();
2422   nativeWidgetRect.Round();
2423   if (nativeWidgetRect.IsEmpty()) {
2424     return Nothing();  // Don't attempt to draw invisible widgets.
2425   }
2427   bool hidpi = IsHiDPIContext(aFrame->PresContext()->DeviceContext());
2428   if (hidpi) {
2429     // Use high-resolution drawing.
2430     nativeWidgetRect.Scale(0.5f);
2431     originalHeight *= 0.5f;
2432   }
2434   EventStates eventState = GetContentState(aFrame, aAppearance);
2436   switch (aAppearance) {
2437     case StyleAppearance::Menupopup:
2438       return Nothing();
2440     case StyleAppearance::Menuarrow:
2441       return Some(
2442           WidgetInfo::MenuIcon(ComputeMenuIconParams(aFrame, eventState, MenuIcon::eMenuArrow)));
2444     case StyleAppearance::Menuitem:
2445     case StyleAppearance::Checkmenuitem:
2446       return Some(WidgetInfo::MenuItem(ComputeMenuItemParams(
2447           aFrame, eventState, aAppearance == StyleAppearance::Checkmenuitem)));
2449     case StyleAppearance::Menuseparator:
2450       return Some(WidgetInfo::MenuSeparator(ComputeMenuItemParams(aFrame, eventState, false)));
2452     case StyleAppearance::ButtonArrowUp:
2453     case StyleAppearance::ButtonArrowDown: {
2454       MenuIcon icon = aAppearance == StyleAppearance::ButtonArrowUp
2455                           ? MenuIcon::eMenuUpScrollArrow
2456                           : MenuIcon::eMenuDownScrollArrow;
2457       return Some(WidgetInfo::MenuIcon(ComputeMenuIconParams(aFrame, eventState, icon)));
2458     }
2460     case StyleAppearance::Tooltip:
2461       return Nothing();
2463     case StyleAppearance::Checkbox:
2464     case StyleAppearance::Radio: {
2465       bool isCheckbox = (aAppearance == StyleAppearance::Checkbox);
2467       CheckboxOrRadioParams params;
2468       params.state = CheckboxOrRadioState::eOff;
2469       if (isCheckbox && GetIndeterminate(aFrame)) {
2470         params.state = CheckboxOrRadioState::eIndeterminate;
2471       } else if (GetCheckedOrSelected(aFrame, !isCheckbox)) {
2472         params.state = CheckboxOrRadioState::eOn;
2473       }
2474       params.controlParams = ComputeControlParams(aFrame, eventState);
2475       params.verticalAlignFactor = VerticalAlignFactor(aFrame);
2476       if (isCheckbox) {
2477         return Some(WidgetInfo::Checkbox(params));
2478       }
2479       return Some(WidgetInfo::Radio(params));
2480     }
2482     case StyleAppearance::Button:
2483       if (IsDefaultButton(aFrame)) {
2484         // Check whether the default button is in a document that does not
2485         // match the :-moz-window-inactive pseudoclass. This activeness check
2486         // is different from the other "active window" checks in this file
2487         // because we absolutely need the button's default button appearance to
2488         // be in sync with its text color, and the text color is changed by
2489         // such a :-moz-window-inactive rule. (That's because on 10.10 and up,
2490         // default buttons in active windows have blue background and white
2491         // text, and default buttons in inactive windows have white background
2492         // and black text.)
2493         EventStates docState = aFrame->GetContent()->OwnerDoc()->GetDocumentState();
2494         bool isInActiveWindow = !docState.HasState(NS_DOCUMENT_STATE_WINDOW_INACTIVE);
2495         bool hasDefaultButtonLook = isInActiveWindow && !eventState.HasState(NS_EVENT_STATE_ACTIVE);
2496         ButtonType buttonType =
2497             hasDefaultButtonLook ? ButtonType::eDefaultPushButton : ButtonType::eRegularPushButton;
2498         ControlParams params = ComputeControlParams(aFrame, eventState);
2499         params.insideActiveWindow = isInActiveWindow;
2500         return Some(WidgetInfo::Button(ButtonParams{params, buttonType}));
2501       }
2502       if (IsButtonTypeMenu(aFrame)) {
2503         ControlParams controlParams = ComputeControlParams(aFrame, eventState);
2504         controlParams.focused = controlParams.focused || IsFocused(aFrame);
2505         controlParams.pressed = IsOpenButton(aFrame);
2506         DropdownParams params;
2507         params.controlParams = controlParams;
2508         params.pullsDown = true;
2509         params.editable = false;
2510         return Some(WidgetInfo::Dropdown(params));
2511       }
2512       if (originalHeight > DO_SQUARE_BUTTON_HEIGHT) {
2513         // If the button is tall enough, draw the square button style so that
2514         // buttons with non-standard content look good. Otherwise draw normal
2515         // rounded aqua buttons.
2516         // This comparison is done based on the height that is calculated without
2517         // the top, because the snapped height can be affected by the top of the
2518         // rect and that may result in different height depending on the top value.
2519         return Some(WidgetInfo::Button(ButtonParams{ComputeControlParams(aFrame, eventState),
2520                                                     ButtonType::eSquareBezelPushButton}));
2521       }
2522       return Some(WidgetInfo::Button(ButtonParams{ComputeControlParams(aFrame, eventState),
2523                                                   ButtonType::eRoundedBezelPushButton}));
2525     case StyleAppearance::FocusOutline:
2526       return Some(WidgetInfo::FocusOutline());
2528     case StyleAppearance::MozMacHelpButton:
2529       return Some(WidgetInfo::Button(
2530           ButtonParams{ComputeControlParams(aFrame, eventState), ButtonType::eHelpButton}));
2532     case StyleAppearance::MozMacDisclosureButtonOpen:
2533     case StyleAppearance::MozMacDisclosureButtonClosed: {
2534       ButtonType buttonType = (aAppearance == StyleAppearance::MozMacDisclosureButtonClosed)
2535                                   ? ButtonType::eDisclosureButtonClosed
2536                                   : ButtonType::eDisclosureButtonOpen;
2537       return Some(
2538           WidgetInfo::Button(ButtonParams{ComputeControlParams(aFrame, eventState), buttonType}));
2539     }
2541     case StyleAppearance::Spinner: {
2542       bool isSpinner = (aAppearance == StyleAppearance::Spinner);
2543       nsIContent* content = aFrame->GetContent();
2544       if (isSpinner && content->IsHTMLElement()) {
2545         // In HTML the theming for the spin buttons is drawn individually into
2546         // their own backgrounds instead of being drawn into the background of
2547         // their spinner parent as it is for XUL.
2548         break;
2549       }
2550       SpinButtonParams params;
2551       if (content->IsElement()) {
2552         if (content->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::state, u"up"_ns,
2553                                               eCaseMatters)) {
2554           params.pressedButton = Some(SpinButton::eUp);
2555         } else if (content->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::state,
2556                                                      u"down"_ns, eCaseMatters)) {
2557           params.pressedButton = Some(SpinButton::eDown);
2558         }
2559       }
2560       params.disabled = IsDisabled(aFrame, eventState);
2561       params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
2563       return Some(WidgetInfo::SpinButtons(params));
2564     }
2566     case StyleAppearance::SpinnerUpbutton:
2567     case StyleAppearance::SpinnerDownbutton: {
2568       nsNumberControlFrame* numberControlFrame =
2569           nsNumberControlFrame::GetNumberControlFrameForSpinButton(aFrame);
2570       if (numberControlFrame) {
2571         SpinButtonParams params;
2572         if (numberControlFrame->SpinnerUpButtonIsDepressed()) {
2573           params.pressedButton = Some(SpinButton::eUp);
2574         } else if (numberControlFrame->SpinnerDownButtonIsDepressed()) {
2575           params.pressedButton = Some(SpinButton::eDown);
2576         }
2577         params.disabled = IsDisabled(aFrame, eventState);
2578         params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
2579         if (aAppearance == StyleAppearance::SpinnerUpbutton) {
2580           return Some(WidgetInfo::SpinButtonUp(params));
2581         }
2582         return Some(WidgetInfo::SpinButtonDown(params));
2583       }
2584     } break;
2586     case StyleAppearance::Toolbarbutton: {
2587       SegmentParams params = ComputeSegmentParams(aFrame, eventState, SegmentType::eToolbarButton);
2588       params.insideActiveWindow = [NativeWindowForFrame(aFrame) isMainWindow];
2589       return Some(WidgetInfo::Segment(params));
2590     }
2592     case StyleAppearance::Separator:
2593       return Some(WidgetInfo::Separator());
2595     case StyleAppearance::Toolbar: {
2596       NSWindow* win = NativeWindowForFrame(aFrame);
2597       bool isMain = [win isMainWindow];
2598       if (ToolbarCanBeUnified(nativeWidgetRect, win)) {
2599         float unifiedHeight =
2600             std::max(float([(ToolbarWindow*)win unifiedToolbarHeight]), nativeWidgetRect.Height());
2601         return Some(WidgetInfo::UnifiedToolbar(UnifiedToolbarParams{unifiedHeight, isMain}));
2602       }
2603       return Some(WidgetInfo::Toolbar(isMain));
2604     }
2606     case StyleAppearance::MozWindowTitlebar: {
2607       NSWindow* win = NativeWindowForFrame(aFrame);
2608       bool isMain = [win isMainWindow];
2609       float unifiedToolbarHeight = [win isKindOfClass:[ToolbarWindow class]]
2610                                        ? [(ToolbarWindow*)win unifiedToolbarHeight]
2611                                        : nativeWidgetRect.Height();
2612       return Some(WidgetInfo::NativeTitlebar(UnifiedToolbarParams{unifiedToolbarHeight, isMain}));
2613     }
2615     case StyleAppearance::Statusbar:
2616       return Some(WidgetInfo::StatusBar(IsActive(aFrame, YES)));
2618     case StyleAppearance::MenulistButton:
2619     case StyleAppearance::Menulist: {
2620       ControlParams controlParams = ComputeControlParams(aFrame, eventState);
2621       controlParams.focused = controlParams.focused || IsFocused(aFrame);
2622       controlParams.pressed = IsOpenButton(aFrame);
2623       DropdownParams params;
2624       params.controlParams = controlParams;
2625       params.pullsDown = false;
2626       params.editable = false;
2627       return Some(WidgetInfo::Dropdown(params));
2628     }
2630     case StyleAppearance::MozMenulistArrowButton:
2631       return Some(WidgetInfo::Button(
2632           ButtonParams{ComputeControlParams(aFrame, eventState), ButtonType::eArrowButton}));
2634     case StyleAppearance::Groupbox:
2635       return Some(WidgetInfo::GroupBox());
2637     case StyleAppearance::Textfield:
2638     case StyleAppearance::NumberInput: {
2639       // See ShouldUnconditionallyDrawFocusRingIfFocused.
2640       bool isFocused = eventState.HasState(NS_EVENT_STATE_FOCUS);
2641       // XUL textboxes set the native appearance on the containing box, while
2642       // concrete focus is set on the html:input element within it. We can
2643       // though, check the focused attribute of xul textboxes in this case.
2644       // On Mac, focus rings are always shown for textboxes, so we do not need
2645       // to check the window's focus ring state here
2646       if (aFrame->GetContent()->IsXULElement() && IsFocused(aFrame)) {
2647         isFocused = true;
2648       }
2650       bool isDisabled = IsDisabled(aFrame, eventState) || IsReadOnly(aFrame);
2651       return Some(
2652           WidgetInfo::TextBox(TextBoxParams{isDisabled, isFocused, /* borderless = */ false}));
2653     }
2655     case StyleAppearance::Searchfield:
2656       return Some(WidgetInfo::SearchField(ComputeSearchFieldParams(aFrame, eventState)));
2658     case StyleAppearance::ProgressBar: {
2659       if (IsIndeterminateProgress(aFrame, eventState)) {
2660         if (!QueueAnimatedContentForRefresh(aFrame->GetContent(), 30)) {
2661           NS_WARNING("Unable to animate progressbar!");
2662         }
2663       }
2664       return Some(WidgetInfo::ProgressBar(
2665           ComputeProgressParams(aFrame, eventState, !IsVerticalProgress(aFrame))));
2666     }
2668     case StyleAppearance::Meter:
2669       return Some(WidgetInfo::Meter(ComputeMeterParams(aFrame)));
2671     case StyleAppearance::Progresschunk:
2672     case StyleAppearance::Meterchunk:
2673       // Do nothing: progress and meter bars cases will draw chunks.
2674       break;
2676     case StyleAppearance::Treetwisty:
2677       return Some(WidgetInfo::Button(ButtonParams{ComputeControlParams(aFrame, eventState),
2678                                                   ButtonType::eTreeTwistyPointingRight}));
2680     case StyleAppearance::Treetwistyopen:
2681       return Some(WidgetInfo::Button(ButtonParams{ComputeControlParams(aFrame, eventState),
2682                                                   ButtonType::eTreeTwistyPointingDown}));
2684     case StyleAppearance::Treeheadercell:
2685       return Some(WidgetInfo::TreeHeaderCell(ComputeTreeHeaderCellParams(aFrame, eventState)));
2687     case StyleAppearance::Treeitem:
2688     case StyleAppearance::Treeview:
2689       return Some(WidgetInfo::ColorFill(sRGBColor(1.0, 1.0, 1.0, 1.0)));
2691     case StyleAppearance::Treeheader:
2692       // do nothing, taken care of by individual header cells
2693     case StyleAppearance::Treeheadersortarrow:
2694       // do nothing, taken care of by treeview header
2695     case StyleAppearance::Treeline:
2696       // do nothing, these lines don't exist on macos
2697       break;
2699     case StyleAppearance::Range: {
2700       Maybe<ScaleParams> params = ComputeHTMLScaleParams(aFrame, eventState);
2701       if (params) {
2702         return Some(WidgetInfo::Scale(*params));
2703       }
2704       break;
2705     }
2707     case StyleAppearance::ScrollbarHorizontal:
2708     case StyleAppearance::ScrollbarVertical:
2709     case StyleAppearance::ScrollbarbuttonUp:
2710     case StyleAppearance::ScrollbarbuttonLeft:
2711     case StyleAppearance::ScrollbarbuttonDown:
2712     case StyleAppearance::ScrollbarbuttonRight:
2713       break;
2715     case StyleAppearance::ScrollbarthumbVertical:
2716     case StyleAppearance::ScrollbarthumbHorizontal:
2717     case StyleAppearance::ScrollbartrackHorizontal:
2718     case StyleAppearance::ScrollbartrackVertical:
2719     case StyleAppearance::Scrollcorner: {
2720       bool isHorizontal = aAppearance == StyleAppearance::ScrollbarthumbHorizontal ||
2721                           aAppearance == StyleAppearance::ScrollbartrackHorizontal;
2722       ScrollbarParams params = ScrollbarDrawingMac::ComputeScrollbarParams(
2723           aFrame, *nsLayoutUtils::StyleForScrollbar(aFrame), isHorizontal);
2724       switch (aAppearance) {
2725         case StyleAppearance::ScrollbarthumbVertical:
2726         case StyleAppearance::ScrollbarthumbHorizontal:
2727           return Some(WidgetInfo::ScrollbarThumb(params));
2728         case StyleAppearance::ScrollbartrackHorizontal:
2729         case StyleAppearance::ScrollbartrackVertical:
2730           return Some(WidgetInfo::ScrollbarTrack(params));
2731         case StyleAppearance::Scrollcorner:
2732           return Some(WidgetInfo::ScrollCorner(params));
2733         default:
2734           MOZ_CRASH("unexpected aAppearance");
2735       }
2736       break;
2737     }
2739     case StyleAppearance::Textarea:
2740       return Some(WidgetInfo::MultilineTextField(eventState.HasState(NS_EVENT_STATE_FOCUS)));
2742     case StyleAppearance::Listbox:
2743       return Some(WidgetInfo::ListBox());
2745     case StyleAppearance::MozMacSourceList: {
2746       return Nothing();
2747     }
2749     case StyleAppearance::MozMacSourceListSelection:
2750     case StyleAppearance::MozMacActiveSourceListSelection: {
2751       // We only support vibrancy for source list selections if we're inside
2752       // a source list, because we need the background to be transparent.
2753       if (IsInSourceList(aFrame)) {
2754         return Nothing();
2755       }
2756       bool isInActiveWindow = FrameIsInActiveWindow(aFrame);
2757       if (aAppearance == StyleAppearance::MozMacActiveSourceListSelection) {
2758         return Some(WidgetInfo::ActiveSourceListSelection(isInActiveWindow));
2759       }
2760       return Some(WidgetInfo::InactiveSourceListSelection(isInActiveWindow));
2761     }
2763     case StyleAppearance::Tab: {
2764       SegmentParams params = ComputeSegmentParams(aFrame, eventState, SegmentType::eTab);
2765       params.pressed = params.pressed && !params.selected;
2766       return Some(WidgetInfo::Segment(params));
2767     }
2769     case StyleAppearance::Tabpanels:
2770       return Some(WidgetInfo::TabPanel(FrameIsInActiveWindow(aFrame)));
2772     case StyleAppearance::Resizer:
2773       return Some(WidgetInfo::Resizer(IsFrameRTL(aFrame)));
2775     default:
2776       break;
2777   }
2779   return Nothing();
2781   NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(Nothing());
2784 NS_IMETHODIMP
2785 nsNativeThemeCocoa::DrawWidgetBackground(gfxContext* aContext, nsIFrame* aFrame,
2786                                          StyleAppearance aAppearance, const nsRect& aRect,
2787                                          const nsRect& aDirtyRect) {
2788   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
2790   Maybe<WidgetInfo> widgetInfo = ComputeWidgetInfo(aFrame, aAppearance, aRect);
2792   if (!widgetInfo) {
2793     return NS_OK;
2794   }
2796   int32_t p2a = aFrame->PresContext()->AppUnitsPerDevPixel();
2798   gfx::Rect nativeWidgetRect = NSRectToRect(aRect, p2a);
2799   nativeWidgetRect.Round();
2801   bool hidpi = IsHiDPIContext(aFrame->PresContext()->DeviceContext());
2803   RenderWidget(*widgetInfo, *aContext->GetDrawTarget(), nativeWidgetRect,
2804                NSRectToRect(aDirtyRect, p2a), hidpi ? 2.0f : 1.0f);
2806   return NS_OK;
2808   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
2811 void nsNativeThemeCocoa::RenderWidget(const WidgetInfo& aWidgetInfo, DrawTarget& aDrawTarget,
2812                                       const gfx::Rect& aWidgetRect, const gfx::Rect& aDirtyRect,
2813                                       float aScale) {
2814   AutoRestoreTransform autoRestoreTransform(&aDrawTarget);
2816   gfx::Rect dirtyRect = aDirtyRect;
2817   gfx::Rect widgetRect = aWidgetRect;
2818   dirtyRect.Scale(1.0f / aScale);
2819   widgetRect.Scale(1.0f / aScale);
2820   aDrawTarget.SetTransform(aDrawTarget.GetTransform().PreScale(aScale, aScale));
2822   const Widget widget = aWidgetInfo.Widget();
2824   // Some widgets render using DrawTarget, and some using CGContext.
2825   switch (widget) {
2826     case Widget::eColorFill: {
2827       sRGBColor color = aWidgetInfo.Params<sRGBColor>();
2828       aDrawTarget.FillRect(widgetRect, ColorPattern(ToDeviceColor(color)));
2829       break;
2830     }
2831     case Widget::eScrollbarThumb: {
2832       ScrollbarParams params = aWidgetInfo.Params<ScrollbarParams>();
2833       ScrollbarDrawingMac::DrawScrollbarThumb(aDrawTarget, widgetRect, params);
2834       break;
2835     }
2836     case Widget::eScrollbarTrack: {
2837       ScrollbarParams params = aWidgetInfo.Params<ScrollbarParams>();
2838       ScrollbarDrawingMac::DrawScrollbarTrack(aDrawTarget, widgetRect, params);
2839       break;
2840     }
2841     case Widget::eScrollCorner: {
2842       ScrollbarParams params = aWidgetInfo.Params<ScrollbarParams>();
2843       ScrollbarDrawingMac::DrawScrollCorner(aDrawTarget, widgetRect, params);
2844       break;
2845     }
2846     default: {
2847       // The remaining widgets require a CGContext.
2848       CGRect macRect =
2849           CGRectMake(widgetRect.X(), widgetRect.Y(), widgetRect.Width(), widgetRect.Height());
2851       gfxQuartzNativeDrawing nativeDrawing(aDrawTarget, dirtyRect);
2853       CGContextRef cgContext = nativeDrawing.BeginNativeDrawing();
2854       if (cgContext == nullptr) {
2855         // The Quartz surface handles 0x0 surfaces by internally
2856         // making all operations no-ops; there's no cgcontext created for them.
2857         // Unfortunately, this means that callers that want to render
2858         // directly to the CGContext need to be aware of this quirk.
2859         return;
2860       }
2862       // Set the context's "base transform" to in order to get correctly-sized focus rings.
2863       CGContextSetBaseCTM(cgContext, CGAffineTransformMakeScale(aScale, aScale));
2865       switch (widget) {
2866         case Widget::eColorFill:
2867         case Widget::eScrollbarThumb:
2868         case Widget::eScrollbarTrack:
2869         case Widget::eScrollCorner: {
2870           MOZ_CRASH("already handled in outer switch");
2871           break;
2872         }
2873         case Widget::eMenuIcon: {
2874           MenuIconParams params = aWidgetInfo.Params<MenuIconParams>();
2875           DrawMenuIcon(cgContext, macRect, params);
2876           break;
2877         }
2878         case Widget::eMenuItem: {
2879           MenuItemParams params = aWidgetInfo.Params<MenuItemParams>();
2880           DrawMenuItem(cgContext, macRect, params);
2881           break;
2882         }
2883         case Widget::eMenuSeparator: {
2884           MenuItemParams params = aWidgetInfo.Params<MenuItemParams>();
2885           DrawMenuSeparator(cgContext, macRect, params);
2886           break;
2887         }
2888         case Widget::eCheckbox: {
2889           CheckboxOrRadioParams params = aWidgetInfo.Params<CheckboxOrRadioParams>();
2890           DrawCheckboxOrRadio(cgContext, true, macRect, params);
2891           break;
2892         }
2893         case Widget::eRadio: {
2894           CheckboxOrRadioParams params = aWidgetInfo.Params<CheckboxOrRadioParams>();
2895           DrawCheckboxOrRadio(cgContext, false, macRect, params);
2896           break;
2897         }
2898         case Widget::eButton: {
2899           ButtonParams params = aWidgetInfo.Params<ButtonParams>();
2900           DrawButton(cgContext, macRect, params);
2901           break;
2902         }
2903         case Widget::eDropdown: {
2904           DropdownParams params = aWidgetInfo.Params<DropdownParams>();
2905           DrawDropdown(cgContext, macRect, params);
2906           break;
2907         }
2908         case Widget::eFocusOutline: {
2909           DrawFocusOutline(cgContext, macRect);
2910           break;
2911         }
2912         case Widget::eSpinButtons: {
2913           SpinButtonParams params = aWidgetInfo.Params<SpinButtonParams>();
2914           DrawSpinButtons(cgContext, macRect, params);
2915           break;
2916         }
2917         case Widget::eSpinButtonUp: {
2918           SpinButtonParams params = aWidgetInfo.Params<SpinButtonParams>();
2919           DrawSpinButton(cgContext, macRect, SpinButton::eUp, params);
2920           break;
2921         }
2922         case Widget::eSpinButtonDown: {
2923           SpinButtonParams params = aWidgetInfo.Params<SpinButtonParams>();
2924           DrawSpinButton(cgContext, macRect, SpinButton::eDown, params);
2925           break;
2926         }
2927         case Widget::eSegment: {
2928           SegmentParams params = aWidgetInfo.Params<SegmentParams>();
2929           DrawSegment(cgContext, macRect, params);
2930           break;
2931         }
2932         case Widget::eSeparator: {
2933           HIThemeSeparatorDrawInfo sdi = {0, kThemeStateActive};
2934           HIThemeDrawSeparator(&macRect, &sdi, cgContext, HITHEME_ORIENTATION);
2935           break;
2936         }
2937         case Widget::eUnifiedToolbar: {
2938           UnifiedToolbarParams params = aWidgetInfo.Params<UnifiedToolbarParams>();
2939           DrawUnifiedToolbar(cgContext, macRect, params);
2940           break;
2941         }
2942         case Widget::eToolbar: {
2943           bool isMain = aWidgetInfo.Params<bool>();
2944           DrawToolbar(cgContext, macRect, isMain);
2945           break;
2946         }
2947         case Widget::eNativeTitlebar: {
2948           UnifiedToolbarParams params = aWidgetInfo.Params<UnifiedToolbarParams>();
2949           DrawNativeTitlebar(cgContext, macRect, params);
2950           break;
2951         }
2952         case Widget::eStatusBar: {
2953           bool isMain = aWidgetInfo.Params<bool>();
2954           DrawStatusBar(cgContext, macRect, isMain);
2955           break;
2956         }
2957         case Widget::eGroupBox: {
2958           HIThemeGroupBoxDrawInfo gdi = {0, kThemeStateActive, kHIThemeGroupBoxKindPrimary};
2959           HIThemeDrawGroupBox(&macRect, &gdi, cgContext, HITHEME_ORIENTATION);
2960           break;
2961         }
2962         case Widget::eTextBox: {
2963           TextBoxParams params = aWidgetInfo.Params<TextBoxParams>();
2964           DrawTextBox(cgContext, macRect, params);
2965           break;
2966         }
2967         case Widget::eSearchField: {
2968           SearchFieldParams params = aWidgetInfo.Params<SearchFieldParams>();
2969           DrawSearchField(cgContext, macRect, params);
2970           break;
2971         }
2972         case Widget::eProgressBar: {
2973           ProgressParams params = aWidgetInfo.Params<ProgressParams>();
2974           DrawProgress(cgContext, macRect, params);
2975           break;
2976         }
2977         case Widget::eMeter: {
2978           MeterParams params = aWidgetInfo.Params<MeterParams>();
2979           DrawMeter(cgContext, macRect, params);
2980           break;
2981         }
2982         case Widget::eTreeHeaderCell: {
2983           TreeHeaderCellParams params = aWidgetInfo.Params<TreeHeaderCellParams>();
2984           DrawTreeHeaderCell(cgContext, macRect, params);
2985           break;
2986         }
2987         case Widget::eScale: {
2988           ScaleParams params = aWidgetInfo.Params<ScaleParams>();
2989           DrawScale(cgContext, macRect, params);
2990           break;
2991         }
2992         case Widget::eMultilineTextField: {
2993           bool isFocused = aWidgetInfo.Params<bool>();
2994           DrawMultilineTextField(cgContext, macRect, isFocused);
2995           break;
2996         }
2997         case Widget::eListBox: {
2998           // We have to draw this by hand because kHIThemeFrameListBox drawing
2999           // is buggy on 10.5, see bug 579259.
3000           SetCGContextFillColor(cgContext, sRGBColor(1.0, 1.0, 1.0, 1.0));
3001           CGContextFillRect(cgContext, macRect);
3003           float x = macRect.origin.x, y = macRect.origin.y;
3004           float w = macRect.size.width, h = macRect.size.height;
3005           SetCGContextFillColor(cgContext, kListboxTopBorderColor);
3006           CGContextFillRect(cgContext, CGRectMake(x, y, w, 1));
3007           SetCGContextFillColor(cgContext, kListBoxSidesAndBottomBorderColor);
3008           CGContextFillRect(cgContext, CGRectMake(x, y + 1, 1, h - 1));
3009           CGContextFillRect(cgContext, CGRectMake(x + w - 1, y + 1, 1, h - 1));
3010           CGContextFillRect(cgContext, CGRectMake(x + 1, y + h - 1, w - 2, 1));
3011           break;
3012         }
3013         case Widget::eActiveSourceListSelection:
3014         case Widget::eInactiveSourceListSelection: {
3015           bool isInActiveWindow = aWidgetInfo.Params<bool>();
3016           bool isActiveSelection = aWidgetInfo.Widget() == Widget::eActiveSourceListSelection;
3017           DrawSourceListSelection(cgContext, macRect, isInActiveWindow, isActiveSelection);
3018           break;
3019         }
3020         case Widget::eTabPanel: {
3021           bool isInsideActiveWindow = aWidgetInfo.Params<bool>();
3022           DrawTabPanel(cgContext, macRect, isInsideActiveWindow);
3023           break;
3024         }
3025         case Widget::eResizer: {
3026           bool isRTL = aWidgetInfo.Params<bool>();
3027           DrawResizer(cgContext, macRect, isRTL);
3028           break;
3029         }
3030       }
3032       // Reset the base CTM.
3033       CGContextSetBaseCTM(cgContext, CGAffineTransformIdentity);
3035       nativeDrawing.EndNativeDrawing();
3036     }
3037   }
3040 bool nsNativeThemeCocoa::CreateWebRenderCommandsForWidget(
3041     mozilla::wr::DisplayListBuilder& aBuilder, mozilla::wr::IpcResourceUpdateQueue& aResources,
3042     const mozilla::layers::StackingContextHelper& aSc,
3043     mozilla::layers::RenderRootStateManager* aManager, nsIFrame* aFrame,
3044     StyleAppearance aAppearance, const nsRect& aRect) {
3045   nsPresContext* presContext = aFrame->PresContext();
3046   wr::LayoutRect bounds =
3047       wr::ToLayoutRect(LayoutDeviceRect::FromAppUnits(aRect, presContext->AppUnitsPerDevPixel()));
3049   EventStates eventState = GetContentState(aFrame, aAppearance);
3051   // This list needs to stay consistent with the list in DrawWidgetBackground.
3052   // For every switch case in DrawWidgetBackground, there are three choices:
3053   //  - If the case in DrawWidgetBackground draws nothing for the given widget
3054   //    type, then don't list it here. We will hit the "default: return true;"
3055   //    case.
3056   //  - If the case in DrawWidgetBackground draws something simple for the given
3057   //    widget type, imitate that drawing using WebRender commands.
3058   //  - If the case in DrawWidgetBackground draws something complicated for the
3059   //    given widget type, return false here.
3060   switch (aAppearance) {
3061     case StyleAppearance::Menuarrow:
3062     case StyleAppearance::Menuitem:
3063     case StyleAppearance::Checkmenuitem:
3064     case StyleAppearance::Menuseparator:
3065     case StyleAppearance::ButtonArrowUp:
3066     case StyleAppearance::ButtonArrowDown:
3067     case StyleAppearance::Checkbox:
3068     case StyleAppearance::Radio:
3069     case StyleAppearance::Button:
3070     case StyleAppearance::FocusOutline:
3071     case StyleAppearance::MozMacHelpButton:
3072     case StyleAppearance::MozMacDisclosureButtonOpen:
3073     case StyleAppearance::MozMacDisclosureButtonClosed:
3074     case StyleAppearance::Spinner:
3075     case StyleAppearance::SpinnerUpbutton:
3076     case StyleAppearance::SpinnerDownbutton:
3077     case StyleAppearance::Toolbarbutton:
3078     case StyleAppearance::Separator:
3079     case StyleAppearance::Toolbar:
3080     case StyleAppearance::MozWindowTitlebar:
3081     case StyleAppearance::Statusbar:
3082     case StyleAppearance::Menulist:
3083     case StyleAppearance::MenulistButton:
3084     case StyleAppearance::MozMenulistArrowButton:
3085     case StyleAppearance::Groupbox:
3086     case StyleAppearance::Textfield:
3087     case StyleAppearance::NumberInput:
3088     case StyleAppearance::Searchfield:
3089     case StyleAppearance::ProgressBar:
3090     case StyleAppearance::Meter:
3091     case StyleAppearance::Treetwisty:
3092     case StyleAppearance::Treetwistyopen:
3093     case StyleAppearance::Treeheadercell:
3094     case StyleAppearance::Treeitem:
3095     case StyleAppearance::Treeview:
3096     case StyleAppearance::Range:
3097     case StyleAppearance::ScrollbarthumbVertical:
3098     case StyleAppearance::ScrollbarthumbHorizontal:
3099       return false;
3101     case StyleAppearance::Scrollcorner:
3102     case StyleAppearance::ScrollbartrackHorizontal:
3103     case StyleAppearance::ScrollbartrackVertical: {
3104       const ComputedStyle& style = *nsLayoutUtils::StyleForScrollbar(aFrame);
3105       ScrollbarParams params = ScrollbarDrawingMac::ComputeScrollbarParams(
3106           aFrame, style, aAppearance == StyleAppearance::ScrollbartrackHorizontal);
3107       if (params.overlay && !params.rolledOver) {
3108         // There is no scrollbar track, draw nothing and return true.
3109         return true;
3110       }
3111       // There is a scrollbar track and it needs to be drawn using fallback.
3112       return false;
3113     }
3115     case StyleAppearance::Textarea: {
3116       if (eventState.HasState(NS_EVENT_STATE_FOCUS)) {
3117         // We can't draw the focus ring using webrender, so fall back to regular
3118         // drawing if we're focused.
3119         return false;
3120       }
3122       // White background
3123       aBuilder.PushRect(bounds, bounds, true,
3124                         wr::ToColorF(ToDeviceColor(sRGBColor::OpaqueWhite())));
3126       wr::BorderSide side[4] = {
3127           wr::ToBorderSide(ToDeviceColor(kMultilineTextFieldTopBorderColor),
3128                            StyleBorderStyle::Solid),
3129           wr::ToBorderSide(ToDeviceColor(kMultilineTextFieldSidesAndBottomBorderColor),
3130                            StyleBorderStyle::Solid),
3131           wr::ToBorderSide(ToDeviceColor(kMultilineTextFieldSidesAndBottomBorderColor),
3132                            StyleBorderStyle::Solid),
3133           wr::ToBorderSide(ToDeviceColor(kMultilineTextFieldSidesAndBottomBorderColor),
3134                            StyleBorderStyle::Solid),
3135       };
3137       wr::BorderRadius borderRadius = wr::EmptyBorderRadius();
3138       float borderWidth = presContext->CSSPixelsToDevPixels(1.0f);
3139       wr::LayoutSideOffsets borderWidths =
3140           wr::ToBorderWidths(borderWidth, borderWidth, borderWidth, borderWidth);
3142       mozilla::Range<const wr::BorderSide> wrsides(side, 4);
3143       aBuilder.PushBorder(bounds, bounds, true, borderWidths, wrsides, borderRadius);
3145       return true;
3146     }
3148     case StyleAppearance::Listbox: {
3149       // White background
3150       aBuilder.PushRect(bounds, bounds, true,
3151                         wr::ToColorF(ToDeviceColor(sRGBColor::OpaqueWhite())));
3153       wr::BorderSide side[4] = {
3154           wr::ToBorderSide(ToDeviceColor(kListboxTopBorderColor), StyleBorderStyle::Solid),
3155           wr::ToBorderSide(ToDeviceColor(kListBoxSidesAndBottomBorderColor),
3156                            StyleBorderStyle::Solid),
3157           wr::ToBorderSide(ToDeviceColor(kListBoxSidesAndBottomBorderColor),
3158                            StyleBorderStyle::Solid),
3159           wr::ToBorderSide(ToDeviceColor(kListBoxSidesAndBottomBorderColor),
3160                            StyleBorderStyle::Solid),
3161       };
3163       wr::BorderRadius borderRadius = wr::EmptyBorderRadius();
3164       float borderWidth = presContext->CSSPixelsToDevPixels(1.0f);
3165       wr::LayoutSideOffsets borderWidths =
3166           wr::ToBorderWidths(borderWidth, borderWidth, borderWidth, borderWidth);
3168       mozilla::Range<const wr::BorderSide> wrsides(side, 4);
3169       aBuilder.PushBorder(bounds, bounds, true, borderWidths, wrsides, borderRadius);
3170       return true;
3171     }
3173     case StyleAppearance::Tab:
3174     case StyleAppearance::Tabpanels:
3175     case StyleAppearance::Resizer:
3176       return false;
3178     default:
3179       return true;
3180   }
3183 LayoutDeviceIntMargin nsNativeThemeCocoa::DirectionAwareMargin(const LayoutDeviceIntMargin& aMargin,
3184                                                                nsIFrame* aFrame) {
3185   // Assuming aMargin was originally specified for a horizontal LTR context,
3186   // reinterpret the values as logical, and then map to physical coords
3187   // according to aFrame's actual writing mode.
3188   WritingMode wm = aFrame->GetWritingMode();
3189   nsMargin m = LogicalMargin(wm, aMargin.top, aMargin.right, aMargin.bottom, aMargin.left)
3190                    .GetPhysicalMargin(wm);
3191   return LayoutDeviceIntMargin(m.top, m.right, m.bottom, m.left);
3194 static const LayoutDeviceIntMargin kAquaDropdownBorder(1, 22, 2, 5);
3195 static const LayoutDeviceIntMargin kAquaComboboxBorder(3, 20, 3, 4);
3196 static const LayoutDeviceIntMargin kAquaSearchfieldBorder(3, 5, 2, 19);
3197 static const LayoutDeviceIntMargin kAquaSearchfieldBorderBigSur(5, 5, 4, 26);
3199 LayoutDeviceIntMargin nsNativeThemeCocoa::GetWidgetBorder(nsDeviceContext* aContext,
3200                                                           nsIFrame* aFrame,
3201                                                           StyleAppearance aAppearance) {
3202   LayoutDeviceIntMargin result;
3204   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
3206   switch (aAppearance) {
3207     case StyleAppearance::Button: {
3208       if (IsButtonTypeMenu(aFrame)) {
3209         result = DirectionAwareMargin(kAquaDropdownBorder, aFrame);
3210       } else {
3211         result = DirectionAwareMargin(LayoutDeviceIntMargin(1, 7, 3, 7), aFrame);
3212       }
3213       break;
3214     }
3216     case StyleAppearance::Toolbarbutton: {
3217       result = DirectionAwareMargin(LayoutDeviceIntMargin(1, 4, 1, 4), aFrame);
3218       break;
3219     }
3221     case StyleAppearance::Checkbox:
3222     case StyleAppearance::Radio: {
3223       // nsCheckboxRadioFrame::GetIntrinsicWidth and nsCheckboxRadioFrame::GetIntrinsicHeight
3224       // assume a border width of 2px.
3225       result.SizeTo(2, 2, 2, 2);
3226       break;
3227     }
3229     case StyleAppearance::Menulist:
3230     case StyleAppearance::MenulistButton:
3231     case StyleAppearance::MozMenulistArrowButton:
3232       result = DirectionAwareMargin(kAquaDropdownBorder, aFrame);
3233       break;
3235     case StyleAppearance::Menuarrow:
3236       if (nsCocoaFeatures::OnBigSurOrLater()) {
3237         result.SizeTo(0, 0, 0, 28);
3238       }
3239       break;
3241     case StyleAppearance::NumberInput:
3242     case StyleAppearance::Textfield: {
3243       SInt32 frameOutset = 0;
3244       ::GetThemeMetric(kThemeMetricEditTextFrameOutset, &frameOutset);
3246       SInt32 textPadding = 0;
3247       ::GetThemeMetric(kThemeMetricEditTextWhitespace, &textPadding);
3249       frameOutset += textPadding;
3251       result.SizeTo(frameOutset, frameOutset, frameOutset, frameOutset);
3252       break;
3253     }
3255     case StyleAppearance::Textarea:
3256       result.SizeTo(1, 1, 1, 1);
3257       break;
3259     case StyleAppearance::Searchfield: {
3260       auto border = nsCocoaFeatures::OnBigSurOrLater() ? kAquaSearchfieldBorderBigSur
3261                                                        : kAquaSearchfieldBorder;
3262       result = DirectionAwareMargin(border, aFrame);
3263       break;
3264     }
3266     case StyleAppearance::Listbox: {
3267       SInt32 frameOutset = 0;
3268       ::GetThemeMetric(kThemeMetricListBoxFrameOutset, &frameOutset);
3269       result.SizeTo(frameOutset, frameOutset, frameOutset, frameOutset);
3270       break;
3271     }
3273     case StyleAppearance::ScrollbartrackHorizontal:
3274     case StyleAppearance::ScrollbartrackVertical: {
3275       bool isHorizontal = (aAppearance == StyleAppearance::ScrollbartrackHorizontal);
3276       if (nsLookAndFeel::UseOverlayScrollbars()) {
3277         // Leave a bit of space at the start and the end on all OS X versions.
3278         if (isHorizontal) {
3279           result.left = 1;
3280           result.right = 1;
3281         } else {
3282           result.top = 1;
3283           result.bottom = 1;
3284         }
3285       }
3287       break;
3288     }
3290     case StyleAppearance::Statusbar:
3291       result.SizeTo(1, 0, 0, 0);
3292       break;
3294     default:
3295       break;
3296   }
3298   if (IsHiDPIContext(aContext)) {
3299     result = result + result;  // doubled
3300   }
3302   NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(result);
3305 // Return false here to indicate that CSS padding values should be used. There is
3306 // no reason to make a distinction between padding and border values, just specify
3307 // whatever values you want in GetWidgetBorder and only use this to return true
3308 // if you want to override CSS padding values.
3309 bool nsNativeThemeCocoa::GetWidgetPadding(nsDeviceContext* aContext, nsIFrame* aFrame,
3310                                           StyleAppearance aAppearance,
3311                                           LayoutDeviceIntMargin* aResult) {
3312   // We don't want CSS padding being used for certain widgets.
3313   // See bug 381639 for an example of why.
3314   switch (aAppearance) {
3315     // Radios and checkboxes return a fixed size in GetMinimumWidgetSize
3316     // and have a meaningful baseline, so they can't have
3317     // author-specified padding.
3318     case StyleAppearance::Checkbox:
3319     case StyleAppearance::Radio:
3320       aResult->SizeTo(0, 0, 0, 0);
3321       return true;
3323     case StyleAppearance::Menuarrow:
3324     case StyleAppearance::Searchfield:
3325       if (nsCocoaFeatures::OnBigSurOrLater()) {
3326         return true;
3327       }
3328       break;
3330     default:
3331       break;
3332   }
3333   return false;
3336 bool nsNativeThemeCocoa::GetWidgetOverflow(nsDeviceContext* aContext, nsIFrame* aFrame,
3337                                            StyleAppearance aAppearance, nsRect* aOverflowRect) {
3338   nsIntMargin overflow;
3339   switch (aAppearance) {
3340     case StyleAppearance::Button:
3341     case StyleAppearance::MozMacDisclosureButtonOpen:
3342     case StyleAppearance::MozMacDisclosureButtonClosed:
3343     case StyleAppearance::MozMacHelpButton:
3344     case StyleAppearance::Toolbarbutton:
3345     case StyleAppearance::NumberInput:
3346     case StyleAppearance::Textfield:
3347     case StyleAppearance::Textarea:
3348     case StyleAppearance::Searchfield:
3349     case StyleAppearance::Listbox:
3350     case StyleAppearance::Menulist:
3351     case StyleAppearance::MenulistButton:
3352     case StyleAppearance::MozMenulistArrowButton:
3353     case StyleAppearance::Checkbox:
3354     case StyleAppearance::Radio:
3355     case StyleAppearance::Tab:
3356     case StyleAppearance::FocusOutline: {
3357       overflow.SizeTo(kMaxFocusRingWidth, kMaxFocusRingWidth, kMaxFocusRingWidth,
3358                       kMaxFocusRingWidth);
3359       break;
3360     }
3361     case StyleAppearance::ProgressBar: {
3362       // Progress bars draw a 2 pixel white shadow under their progress indicators.
3363       overflow.bottom = 2;
3364       break;
3365     }
3366     case StyleAppearance::Meter: {
3367       // Meter bars overflow their boxes by about 2 pixels.
3368       overflow.SizeTo(2, 2, 2, 2);
3369       break;
3370     }
3371     default:
3372       break;
3373   }
3375   if (IsHiDPIContext(aContext)) {
3376     // Double the number of device pixels.
3377     overflow += overflow;
3378   }
3380   if (overflow != nsIntMargin()) {
3381     int32_t p2a = aFrame->PresContext()->AppUnitsPerDevPixel();
3382     aOverflowRect->Inflate(nsMargin(
3383         NSIntPixelsToAppUnits(overflow.top, p2a), NSIntPixelsToAppUnits(overflow.right, p2a),
3384         NSIntPixelsToAppUnits(overflow.bottom, p2a), NSIntPixelsToAppUnits(overflow.left, p2a)));
3385     return true;
3386   }
3388   return false;
3391 NS_IMETHODIMP
3392 nsNativeThemeCocoa::GetMinimumWidgetSize(nsPresContext* aPresContext, nsIFrame* aFrame,
3393                                          StyleAppearance aAppearance, LayoutDeviceIntSize* aResult,
3394                                          bool* aIsOverridable) {
3395   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
3397   aResult->SizeTo(0, 0);
3398   *aIsOverridable = true;
3400   switch (aAppearance) {
3401     case StyleAppearance::Button: {
3402       aResult->SizeTo(pushButtonSettings.minimumSizes[miniControlSize].width,
3403                       pushButtonSettings.naturalSizes[miniControlSize].height);
3404       break;
3405     }
3407     case StyleAppearance::ButtonArrowUp:
3408     case StyleAppearance::ButtonArrowDown: {
3409       aResult->SizeTo(kMenuScrollArrowSize.width, kMenuScrollArrowSize.height);
3410       *aIsOverridable = false;
3411       break;
3412     }
3414     case StyleAppearance::Menuarrow: {
3415       aResult->SizeTo(kMenuarrowSize.width, kMenuarrowSize.height);
3416       *aIsOverridable = false;
3417       break;
3418     }
3420     case StyleAppearance::MozMacDisclosureButtonOpen:
3421     case StyleAppearance::MozMacDisclosureButtonClosed: {
3422       aResult->SizeTo(kDisclosureButtonSize.width, kDisclosureButtonSize.height);
3423       *aIsOverridable = false;
3424       break;
3425     }
3427     case StyleAppearance::MozMacHelpButton: {
3428       aResult->SizeTo(kHelpButtonSize.width, kHelpButtonSize.height);
3429       *aIsOverridable = false;
3430       break;
3431     }
3433     case StyleAppearance::Toolbarbutton: {
3434       aResult->SizeTo(0, toolbarButtonHeights[miniControlSize]);
3435       break;
3436     }
3438     case StyleAppearance::Spinner:
3439     case StyleAppearance::SpinnerUpbutton:
3440     case StyleAppearance::SpinnerDownbutton: {
3441       SInt32 buttonHeight = 0, buttonWidth = 0;
3442       if (aFrame->GetContent()->IsXULElement()) {
3443         ::GetThemeMetric(kThemeMetricLittleArrowsWidth, &buttonWidth);
3444         ::GetThemeMetric(kThemeMetricLittleArrowsHeight, &buttonHeight);
3445       } else {
3446         NSSize size = spinnerSettings.minimumSizes[EnumSizeForCocoaSize(NSControlSizeMini)];
3447         buttonWidth = size.width;
3448         buttonHeight = size.height;
3449         if (aAppearance != StyleAppearance::Spinner) {
3450           // the buttons are half the height of the spinner
3451           buttonHeight /= 2;
3452         }
3453       }
3454       aResult->SizeTo(buttonWidth, buttonHeight);
3455       *aIsOverridable = true;
3456       break;
3457     }
3459     case StyleAppearance::Menulist:
3460     case StyleAppearance::MenulistButton: {
3461       SInt32 popupHeight = 0;
3462       ::GetThemeMetric(kThemeMetricPopupButtonHeight, &popupHeight);
3463       aResult->SizeTo(0, popupHeight);
3464       break;
3465     }
3467     case StyleAppearance::NumberInput:
3468     case StyleAppearance::Textfield:
3469     case StyleAppearance::Textarea:
3470     case StyleAppearance::Searchfield: {
3471       // at minimum, we should be tall enough for 9pt text.
3472       // I'm using hardcoded values here because the appearance manager
3473       // values for the frame size are incorrect.
3474       aResult->SizeTo(0, (2 + 2) /* top */ + 9 + (1 + 1) /* bottom */);
3475       break;
3476     }
3478     case StyleAppearance::MozWindowButtonBox: {
3479       NSSize size = WindowButtonsSize(aFrame);
3480       aResult->SizeTo(size.width, size.height);
3481       *aIsOverridable = false;
3482       break;
3483     }
3485     case StyleAppearance::MozMacFullscreenButton: {
3486       *aIsOverridable = false;
3487       break;
3488     }
3490     case StyleAppearance::ProgressBar: {
3491       SInt32 barHeight = 0;
3492       ::GetThemeMetric(kThemeMetricNormalProgressBarThickness, &barHeight);
3493       aResult->SizeTo(0, barHeight);
3494       break;
3495     }
3497     case StyleAppearance::Separator: {
3498       aResult->SizeTo(1, 1);
3499       break;
3500     }
3502     case StyleAppearance::Treetwisty:
3503     case StyleAppearance::Treetwistyopen: {
3504       SInt32 twistyHeight = 0, twistyWidth = 0;
3505       ::GetThemeMetric(kThemeMetricDisclosureButtonWidth, &twistyWidth);
3506       ::GetThemeMetric(kThemeMetricDisclosureButtonHeight, &twistyHeight);
3507       aResult->SizeTo(twistyWidth, twistyHeight);
3508       *aIsOverridable = false;
3509       break;
3510     }
3512     case StyleAppearance::Treeheader:
3513     case StyleAppearance::Treeheadercell: {
3514       SInt32 headerHeight = 0;
3515       ::GetThemeMetric(kThemeMetricListHeaderHeight, &headerHeight);
3516       aResult->SizeTo(0, headerHeight - 1);  // We don't need the top border.
3517       break;
3518     }
3520     case StyleAppearance::Tab: {
3521       aResult->SizeTo(0, tabHeights[miniControlSize]);
3522       break;
3523     }
3525     case StyleAppearance::RangeThumb: {
3526       SInt32 width = 0;
3527       SInt32 height = 0;
3528       ::GetThemeMetric(kThemeMetricSliderMinThumbWidth, &width);
3529       ::GetThemeMetric(kThemeMetricSliderMinThumbHeight, &height);
3530       aResult->SizeTo(width, height);
3531       *aIsOverridable = false;
3532       break;
3533     }
3535     case StyleAppearance::ScrollbarthumbHorizontal:
3536     case StyleAppearance::ScrollbarthumbVertical:
3537     case StyleAppearance::ScrollbarHorizontal:
3538     case StyleAppearance::ScrollbarVertical:
3539     case StyleAppearance::ScrollbartrackVertical:
3540     case StyleAppearance::ScrollbartrackHorizontal:
3541     case StyleAppearance::ScrollbarbuttonUp:
3542     case StyleAppearance::ScrollbarbuttonDown:
3543     case StyleAppearance::ScrollbarbuttonLeft:
3544     case StyleAppearance::ScrollbarbuttonRight: {
3545       *aIsOverridable = false;
3546       *aResult = ScrollbarDrawingMac::GetMinimumWidgetSize(aAppearance, aFrame, 1.0f);
3547       break;
3548     }
3550     case StyleAppearance::MozMenulistArrowButton:
3551     case StyleAppearance::ScrollbarNonDisappearing: {
3552       *aResult = ScrollbarDrawingMac::GetMinimumWidgetSize(aAppearance, aFrame, 1.0f);
3553       break;
3554     }
3556     case StyleAppearance::Resizer: {
3557       HIThemeGrowBoxDrawInfo drawInfo;
3558       drawInfo.version = 0;
3559       drawInfo.state = kThemeStateActive;
3560       drawInfo.kind = kHIThemeGrowBoxKindNormal;
3561       drawInfo.direction = kThemeGrowRight | kThemeGrowDown;
3562       drawInfo.size = kHIThemeGrowBoxSizeNormal;
3563       HIPoint pnt = {0, 0};
3564       HIRect bounds;
3565       HIThemeGetGrowBoxBounds(&pnt, &drawInfo, &bounds);
3566       aResult->SizeTo(bounds.size.width, bounds.size.height);
3567       *aIsOverridable = false;
3568     }
3569     default:
3570       break;
3571   }
3573   if (IsHiDPIContext(aPresContext->DeviceContext())) {
3574     *aResult = *aResult * 2;
3575   }
3577   return NS_OK;
3579   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
3582 NS_IMETHODIMP
3583 nsNativeThemeCocoa::WidgetStateChanged(nsIFrame* aFrame, StyleAppearance aAppearance,
3584                                        nsAtom* aAttribute, bool* aShouldRepaint,
3585                                        const nsAttrValue* aOldValue) {
3586   // Some widget types just never change state.
3587   switch (aAppearance) {
3588     case StyleAppearance::MozWindowTitlebar:
3589     case StyleAppearance::Toolbox:
3590     case StyleAppearance::Toolbar:
3591     case StyleAppearance::Statusbar:
3592     case StyleAppearance::Statusbarpanel:
3593     case StyleAppearance::Resizerpanel:
3594     case StyleAppearance::Tooltip:
3595     case StyleAppearance::Tabpanels:
3596     case StyleAppearance::Tabpanel:
3597     case StyleAppearance::Dialog:
3598     case StyleAppearance::Menupopup:
3599     case StyleAppearance::Groupbox:
3600     case StyleAppearance::Progresschunk:
3601     case StyleAppearance::ProgressBar:
3602     case StyleAppearance::Meter:
3603     case StyleAppearance::Meterchunk:
3604     case StyleAppearance::MozMacVibrancyLight:
3605     case StyleAppearance::MozMacVibrancyDark:
3606     case StyleAppearance::MozMacVibrantTitlebarLight:
3607     case StyleAppearance::MozMacVibrantTitlebarDark:
3608       *aShouldRepaint = false;
3609       return NS_OK;
3610     default:
3611       break;
3612   }
3614   // XXXdwh Not sure what can really be done here.  Can at least guess for
3615   // specific widgets that they're highly unlikely to have certain states.
3616   // For example, a toolbar doesn't care about any states.
3617   if (!aAttribute) {
3618     // Hover/focus/active changed.  Always repaint.
3619     *aShouldRepaint = true;
3620   } else {
3621     // Check the attribute to see if it's relevant.
3622     // disabled, checked, dlgtype, default, etc.
3623     *aShouldRepaint = false;
3624     if (aAttribute == nsGkAtoms::disabled || aAttribute == nsGkAtoms::checked ||
3625         aAttribute == nsGkAtoms::selected || aAttribute == nsGkAtoms::visuallyselected ||
3626         aAttribute == nsGkAtoms::menuactive || aAttribute == nsGkAtoms::sortDirection ||
3627         aAttribute == nsGkAtoms::focused || aAttribute == nsGkAtoms::_default ||
3628         aAttribute == nsGkAtoms::open || aAttribute == nsGkAtoms::hover)
3629       *aShouldRepaint = true;
3630   }
3632   return NS_OK;
3635 NS_IMETHODIMP
3636 nsNativeThemeCocoa::ThemeChanged() {
3637   // This is unimplemented because we don't care if gecko changes its theme
3638   // and macOS system appearance changes are handled by
3639   // nsLookAndFeel::SystemWantsDarkTheme.
3640   return NS_OK;
3643 bool nsNativeThemeCocoa::ThemeSupportsWidget(nsPresContext* aPresContext, nsIFrame* aFrame,
3644                                              StyleAppearance aAppearance) {
3645   // if this is a dropdown button in a combobox the answer is always no
3646   if (aAppearance == StyleAppearance::MozMenulistArrowButton) {
3647     nsIFrame* parentFrame = aFrame->GetParent();
3648     if (parentFrame && parentFrame->IsComboboxControlFrame()) return false;
3649   }
3651   switch (aAppearance) {
3652     // Combobox dropdowns don't support native theming in vertical mode.
3653     case StyleAppearance::Menulist:
3654     case StyleAppearance::MenulistButton:
3655     case StyleAppearance::MozMenulistArrowButton:
3656     case StyleAppearance::MenulistText:
3657       if (aFrame && aFrame->GetWritingMode().IsVertical()) {
3658         return false;
3659       }
3660       [[fallthrough]];
3662     case StyleAppearance::Listbox:
3663     case StyleAppearance::Dialog:
3664     case StyleAppearance::Window:
3665     case StyleAppearance::MozWindowButtonBox:
3666     case StyleAppearance::MozWindowTitlebar:
3667     case StyleAppearance::Checkmenuitem:
3668     case StyleAppearance::Menupopup:
3669     case StyleAppearance::Menuarrow:
3670     case StyleAppearance::Menuitem:
3671     case StyleAppearance::Menuseparator:
3672     case StyleAppearance::MozMacFullscreenButton:
3673     case StyleAppearance::Tooltip:
3675     case StyleAppearance::Checkbox:
3676     case StyleAppearance::CheckboxContainer:
3677     case StyleAppearance::Radio:
3678     case StyleAppearance::RadioContainer:
3679     case StyleAppearance::Groupbox:
3680     case StyleAppearance::MozMacHelpButton:
3681     case StyleAppearance::MozMacDisclosureButtonOpen:
3682     case StyleAppearance::MozMacDisclosureButtonClosed:
3683     case StyleAppearance::Button:
3684     case StyleAppearance::ButtonArrowUp:
3685     case StyleAppearance::ButtonArrowDown:
3686     case StyleAppearance::Toolbarbutton:
3687     case StyleAppearance::Spinner:
3688     case StyleAppearance::SpinnerUpbutton:
3689     case StyleAppearance::SpinnerDownbutton:
3690     case StyleAppearance::Toolbar:
3691     case StyleAppearance::Statusbar:
3692     case StyleAppearance::NumberInput:
3693     case StyleAppearance::Textfield:
3694     case StyleAppearance::Textarea:
3695     case StyleAppearance::Searchfield:
3696     case StyleAppearance::Toolbox:
3697     // case StyleAppearance::Toolbarbutton:
3698     case StyleAppearance::ProgressBar:
3699     case StyleAppearance::Progresschunk:
3700     case StyleAppearance::Meter:
3701     case StyleAppearance::Meterchunk:
3702     case StyleAppearance::Separator:
3704     case StyleAppearance::Tabpanels:
3705     case StyleAppearance::Tab:
3707     case StyleAppearance::Treetwisty:
3708     case StyleAppearance::Treetwistyopen:
3709     case StyleAppearance::Treeview:
3710     case StyleAppearance::Treeheader:
3711     case StyleAppearance::Treeheadercell:
3712     case StyleAppearance::Treeheadersortarrow:
3713     case StyleAppearance::Treeitem:
3714     case StyleAppearance::Treeline:
3715     case StyleAppearance::MozMacSourceList:
3716     case StyleAppearance::MozMacSourceListSelection:
3717     case StyleAppearance::MozMacActiveSourceListSelection:
3719     case StyleAppearance::Range:
3721     case StyleAppearance::ScrollbarHorizontal:
3722     case StyleAppearance::ScrollbarVertical:
3723     case StyleAppearance::ScrollbarbuttonUp:
3724     case StyleAppearance::ScrollbarbuttonDown:
3725     case StyleAppearance::ScrollbarbuttonLeft:
3726     case StyleAppearance::ScrollbarbuttonRight:
3727     case StyleAppearance::ScrollbarthumbHorizontal:
3728     case StyleAppearance::ScrollbarthumbVertical:
3729     case StyleAppearance::ScrollbartrackVertical:
3730     case StyleAppearance::ScrollbartrackHorizontal:
3731     case StyleAppearance::ScrollbarNonDisappearing:
3732       return !IsWidgetStyled(aPresContext, aFrame, aAppearance);
3734     case StyleAppearance::Scrollcorner:
3735       return true;
3737     case StyleAppearance::Resizer: {
3738       nsIFrame* parentFrame = aFrame->GetParent();
3739       if (!parentFrame || !parentFrame->IsScrollFrame()) return true;
3741       // Note that IsWidgetStyled is not called for resizers on Mac. This is
3742       // because for scrollable containers, the native resizer looks better
3743       // when (non-overlay) scrollbars are present even when the style is
3744       // overriden, and the custom transparent resizer looks better when
3745       // scrollbars are not present.
3746       nsIScrollableFrame* scrollFrame = do_QueryFrame(parentFrame);
3747       return (!nsLookAndFeel::UseOverlayScrollbars() && scrollFrame &&
3748               scrollFrame->GetScrollbarVisibility());
3749     }
3751     case StyleAppearance::FocusOutline:
3752       return true;
3754     case StyleAppearance::MozMacVibrancyLight:
3755     case StyleAppearance::MozMacVibrancyDark:
3756     case StyleAppearance::MozMacVibrantTitlebarLight:
3757     case StyleAppearance::MozMacVibrantTitlebarDark:
3758       return true;
3759     default:
3760       break;
3761   }
3763   return false;
3766 bool nsNativeThemeCocoa::WidgetIsContainer(StyleAppearance aAppearance) {
3767   // flesh this out at some point
3768   switch (aAppearance) {
3769     case StyleAppearance::MozMenulistArrowButton:
3770     case StyleAppearance::Radio:
3771     case StyleAppearance::Checkbox:
3772     case StyleAppearance::ProgressBar:
3773     case StyleAppearance::Meter:
3774     case StyleAppearance::Range:
3775     case StyleAppearance::MozMacHelpButton:
3776     case StyleAppearance::MozMacDisclosureButtonOpen:
3777     case StyleAppearance::MozMacDisclosureButtonClosed:
3778       return false;
3779     default:
3780       break;
3781   }
3782   return true;
3785 bool nsNativeThemeCocoa::ThemeDrawsFocusForWidget(StyleAppearance aAppearance) {
3786   switch (aAppearance) {
3787     case StyleAppearance::Textarea:
3788     case StyleAppearance::Textfield:
3789     case StyleAppearance::Searchfield:
3790     case StyleAppearance::NumberInput:
3791     case StyleAppearance::Menulist:
3792     case StyleAppearance::MenulistButton:
3793     case StyleAppearance::Button:
3794     case StyleAppearance::MozMacHelpButton:
3795     case StyleAppearance::MozMacDisclosureButtonOpen:
3796     case StyleAppearance::MozMacDisclosureButtonClosed:
3797     case StyleAppearance::Radio:
3798     case StyleAppearance::Range:
3799     case StyleAppearance::Checkbox:
3800       return true;
3801     default:
3802       return false;
3803   }
3806 bool nsNativeThemeCocoa::ThemeNeedsComboboxDropmarker() { return false; }
3808 bool nsNativeThemeCocoa::WidgetAppearanceDependsOnWindowFocus(StyleAppearance aAppearance) {
3809   switch (aAppearance) {
3810     case StyleAppearance::Dialog:
3811     case StyleAppearance::Groupbox:
3812     case StyleAppearance::Tabpanels:
3813     case StyleAppearance::ButtonArrowUp:
3814     case StyleAppearance::ButtonArrowDown:
3815     case StyleAppearance::Checkmenuitem:
3816     case StyleAppearance::Menupopup:
3817     case StyleAppearance::Menuarrow:
3818     case StyleAppearance::Menuitem:
3819     case StyleAppearance::Menuseparator:
3820     case StyleAppearance::Tooltip:
3821     case StyleAppearance::Spinner:
3822     case StyleAppearance::SpinnerUpbutton:
3823     case StyleAppearance::SpinnerDownbutton:
3824     case StyleAppearance::Separator:
3825     case StyleAppearance::Toolbox:
3826     case StyleAppearance::NumberInput:
3827     case StyleAppearance::Textfield:
3828     case StyleAppearance::Treeview:
3829     case StyleAppearance::Treeline:
3830     case StyleAppearance::Textarea:
3831     case StyleAppearance::Listbox:
3832     case StyleAppearance::Resizer:
3833       return false;
3834     default:
3835       return true;
3836   }
3839 bool nsNativeThemeCocoa::IsWindowSheet(nsIFrame* aFrame) {
3840   NSWindow* win = NativeWindowForFrame(aFrame);
3841   id winDelegate = [win delegate];
3842   nsIWidget* widget = [(WindowDelegate*)winDelegate geckoWidget];
3843   if (!widget) {
3844     return false;
3845   }
3846   return (widget->WindowType() == eWindowType_sheet);
3849 nsITheme::ThemeGeometryType nsNativeThemeCocoa::ThemeGeometryTypeForWidget(
3850     nsIFrame* aFrame, StyleAppearance aAppearance) {
3851   switch (aAppearance) {
3852     case StyleAppearance::MozWindowTitlebar:
3853       return eThemeGeometryTypeTitlebar;
3854     case StyleAppearance::Toolbar:
3855       return eThemeGeometryTypeToolbar;
3856     case StyleAppearance::Toolbox:
3857       return eThemeGeometryTypeToolbox;
3858     case StyleAppearance::MozWindowButtonBox:
3859       return eThemeGeometryTypeWindowButtons;
3860     case StyleAppearance::MozMacFullscreenButton:
3861       return eThemeGeometryTypeFullscreenButton;
3862     case StyleAppearance::MozMacVibrancyLight:
3863       return eThemeGeometryTypeVibrancyLight;
3864     case StyleAppearance::MozMacVibrancyDark:
3865       return eThemeGeometryTypeVibrancyDark;
3866     case StyleAppearance::MozMacVibrantTitlebarLight:
3867       return eThemeGeometryTypeVibrantTitlebarLight;
3868     case StyleAppearance::MozMacVibrantTitlebarDark:
3869       return eThemeGeometryTypeVibrantTitlebarDark;
3870     case StyleAppearance::Tooltip:
3871       return eThemeGeometryTypeTooltip;
3872     case StyleAppearance::Menupopup:
3873       return eThemeGeometryTypeMenu;
3874     case StyleAppearance::Menuitem:
3875     case StyleAppearance::Checkmenuitem: {
3876       EventStates eventState = GetContentState(aFrame, aAppearance);
3877       bool isDisabled = IsDisabled(aFrame, eventState);
3878       bool isSelected = !isDisabled && CheckBooleanAttr(aFrame, nsGkAtoms::menuactive);
3879       return isSelected ? eThemeGeometryTypeHighlightedMenuItem : eThemeGeometryTypeMenu;
3880     }
3881     case StyleAppearance::Dialog:
3882       return IsWindowSheet(aFrame) ? eThemeGeometryTypeSheet : eThemeGeometryTypeUnknown;
3883     case StyleAppearance::MozMacSourceList:
3884       return eThemeGeometryTypeSourceList;
3885     case StyleAppearance::MozMacSourceListSelection:
3886       return IsInSourceList(aFrame) ? eThemeGeometryTypeSourceListSelection
3887                                     : eThemeGeometryTypeUnknown;
3888     case StyleAppearance::MozMacActiveSourceListSelection:
3889       return IsInSourceList(aFrame) ? eThemeGeometryTypeActiveSourceListSelection
3890                                     : eThemeGeometryTypeUnknown;
3891     default:
3892       return eThemeGeometryTypeUnknown;
3893   }
3896 nsITheme::Transparency nsNativeThemeCocoa::GetWidgetTransparency(nsIFrame* aFrame,
3897                                                                  StyleAppearance aAppearance) {
3898   switch (aAppearance) {
3899     case StyleAppearance::Menupopup:
3900     case StyleAppearance::Tooltip:
3901     case StyleAppearance::Dialog:
3902       return eTransparent;
3904     case StyleAppearance::ScrollbarHorizontal:
3905     case StyleAppearance::ScrollbarVertical:
3906     case StyleAppearance::Scrollcorner: {
3907       // We don't use custom scrollbars when using overlay scrollbars.
3908       if (nsLookAndFeel::UseOverlayScrollbars()) {
3909         return eTransparent;
3910       }
3911       const nsStyleUI* ui = nsLayoutUtils::StyleForScrollbar(aFrame)->StyleUI();
3912       if (!ui->mScrollbarColor.IsAuto() &&
3913           ui->mScrollbarColor.AsColors().track.MaybeTransparent()) {
3914         return eTransparent;
3915       }
3916       return eOpaque;
3917     }
3919     case StyleAppearance::Statusbar:
3920       // Knowing that scrollbars and statusbars are opaque improves
3921       // performance, because we create layers for them.
3922       return eOpaque;
3924     case StyleAppearance::Toolbar:
3925       return eOpaque;
3927     default:
3928       return eUnknownTransparency;
3929   }
3932 already_AddRefed<nsITheme> do_GetNativeThemeDoNotUseDirectly() {
3933   static nsCOMPtr<nsITheme> inst;
3935   if (!inst) {
3936     inst = new nsNativeThemeCocoa();
3937     ClearOnShutdown(&inst);
3938   }
3940   return do_AddRef(inst);