Bug 1869043 assert that graph set access is main thread only r=padenot
[gecko.git] / widget / cocoa / nsNativeThemeCocoa.mm
blobff715ac57c37babae69c29fd38c2de64a4ecb9a2
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsNativeThemeCocoa.h"
7 #include <objc/NSObjCRuntime.h>
9 #include "mozilla/gfx/2D.h"
10 #include "mozilla/gfx/Helpers.h"
11 #include "mozilla/gfx/PathHelpers.h"
12 #include "nsChildView.h"
13 #include "nsDeviceContext.h"
14 #include "nsLayoutUtils.h"
15 #include "nsObjCExceptions.h"
16 #include "nsNumberControlFrame.h"
17 #include "nsRangeFrame.h"
18 #include "nsRect.h"
19 #include "nsSize.h"
20 #include "nsStyleConsts.h"
21 #include "nsPresContext.h"
22 #include "nsIContent.h"
23 #include "mozilla/dom/Document.h"
24 #include "nsIFrame.h"
25 #include "nsAtom.h"
26 #include "nsNameSpaceManager.h"
27 #include "nsPresContext.h"
28 #include "nsGkAtoms.h"
29 #include "nsCocoaFeatures.h"
30 #include "nsCocoaWindow.h"
31 #include "nsNativeThemeColors.h"
32 #include "nsIScrollableFrame.h"
33 #include "mozilla/ClearOnShutdown.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 "MacThemeGeometryType.h"
42 #include "VibrancyManager.h"
44 #include "gfxContext.h"
45 #include "gfxQuartzSurface.h"
46 #include "gfxQuartzNativeDrawing.h"
47 #include "gfxUtils.h"  // for ToDeviceColor
48 #include <algorithm>
50 using namespace mozilla;
51 using namespace mozilla::gfx;
52 using mozilla::dom::HTMLMeterElement;
54 #define DRAW_IN_FRAME_DEBUG 0
55 #define SCROLLBARS_VISUAL_DEBUG 0
57 // private Quartz routines needed here
58 extern "C" {
59 CG_EXTERN void CGContextSetCTM(CGContextRef, CGAffineTransform);
60 CG_EXTERN void CGContextSetBaseCTM(CGContextRef, CGAffineTransform);
61 typedef CFTypeRef CUIRendererRef;
62 void CUIDraw(CUIRendererRef r, CGRect rect, CGContextRef ctx,
63              CFDictionaryRef options, CFDictionaryRef* result);
66 // Workaround for NSCell control tint drawing
67 // Without this workaround, NSCells are always drawn with the clear control tint
68 // as long as they're not attached to an NSControl which is a subview of an
69 // active window.
70 // XXXmstange Why doesn't Webkit need this?
71 @implementation NSCell (ControlTintWorkaround)
72 - (int)_realControlTint {
73   return [self controlTint];
75 @end
77 // This is the window for our MOZCellDrawView. When an NSCell is drawn, some
78 // NSCell implementations look at the draw view's window to determine whether
79 // the cell should draw with the active look.
80 @interface MOZCellDrawWindow : NSWindow
81 @property BOOL cellsShouldLookActive;
82 @end
84 @implementation MOZCellDrawWindow
86 // Override three different methods, for good measure. The NSCell implementation
87 // could call any one of them.
88 - (BOOL)_hasActiveAppearance {
89   return self.cellsShouldLookActive;
91 - (BOOL)hasKeyAppearance {
92   return self.cellsShouldLookActive;
94 - (BOOL)_hasKeyAppearance {
95   return self.cellsShouldLookActive;
98 @end
100 // The purpose of this class is to provide objects that can be used when drawing
101 // NSCells using drawWithFrame:inView: without causing any harm. Only a small
102 // number of methods are called on the draw view, among those "isFlipped" and
103 // "currentEditor": isFlipped needs to return YES in order to avoid drawing bugs
104 // on 10.4 (see bug 465069); currentEditor (which isn't even a method of
105 // NSView) will be called when drawing search fields, and we only provide it in
106 // order to prevent "unrecognized selector" exceptions.
107 // There's no need to pass the actual NSView that we're drawing into to
108 // drawWithFrame:inView:. What's more, doing so even causes unnecessary
109 // invalidations as soon as we draw a focusring!
110 // This class needs to be an NSControl so that NSTextFieldCell (and
111 // NSSearchFieldCell, which is a subclass of NSTextFieldCell) draws a focus
112 // ring.
113 @interface MOZCellDrawView : NSControl
114 // Called by NSTreeHeaderCell during drawing.
115 @property BOOL _drawingEndSeparator;
116 @end
118 @implementation MOZCellDrawView
120 - (BOOL)isFlipped {
121   return YES;
124 - (NSText*)currentEditor {
125   return nil;
128 @end
130 static void DrawFocusRingForCellIfNeeded(NSCell* aCell, NSRect aWithFrame,
131                                          NSView* aInView) {
132   if ([aCell showsFirstResponder]) {
133     CGContextRef cgContext = [[NSGraphicsContext currentContext] CGContext];
134     CGContextSaveGState(cgContext);
136     // It's important to set the focus ring style before we enter the
137     // transparency layer so that the transparency layer only contains
138     // the normal button mask without the focus ring, and the conversion
139     // to the focus ring shape happens only when the transparency layer is
140     // ended.
141     NSSetFocusRingStyle(NSFocusRingOnly);
143     // We need to draw the whole button into a transparency layer because
144     // many button types are composed of multiple parts, and if these parts
145     // were drawn while the focus ring style was active, each individual part
146     // would produce a focus ring for itself. But we only want one focus ring
147     // for the whole button. The transparency layer is a way to merge the
148     // individual button parts together before the focus ring shape is
149     // calculated.
150     CGContextBeginTransparencyLayerWithRect(cgContext,
151                                             NSRectToCGRect(aWithFrame), 0);
152     [aCell drawFocusRingMaskWithFrame:aWithFrame inView:aInView];
153     CGContextEndTransparencyLayer(cgContext);
155     CGContextRestoreGState(cgContext);
156   }
159 static void DrawCellIncludingFocusRing(NSCell* aCell, NSRect aWithFrame,
160                                        NSView* aInView) {
161   [aCell drawWithFrame:aWithFrame inView:aInView];
162   DrawFocusRingForCellIfNeeded(aCell, aWithFrame, aInView);
166  * NSProgressBarCell is used to draw progress bars of any size.
167  */
168 @interface NSProgressBarCell : NSCell {
169   /*All instance variables are private*/
170   double mValue;
171   double mMax;
172   bool mIsIndeterminate;
173   bool mIsHorizontal;
176 - (void)setValue:(double)value;
177 - (double)value;
178 - (void)setMax:(double)max;
179 - (double)max;
180 - (void)setIndeterminate:(bool)aIndeterminate;
181 - (bool)isIndeterminate;
182 - (void)setHorizontal:(bool)aIsHorizontal;
183 - (bool)isHorizontal;
184 - (void)drawWithFrame:(NSRect)cellFrame inView:(NSView*)controlView;
185 @end
187 @implementation NSProgressBarCell
189 - (void)setMax:(double)aMax {
190   mMax = aMax;
193 - (double)max {
194   return mMax;
197 - (void)setValue:(double)aValue {
198   mValue = aValue;
201 - (double)value {
202   return mValue;
205 - (void)setIndeterminate:(bool)aIndeterminate {
206   mIsIndeterminate = aIndeterminate;
209 - (bool)isIndeterminate {
210   return mIsIndeterminate;
213 - (void)setHorizontal:(bool)aIsHorizontal {
214   mIsHorizontal = aIsHorizontal;
217 - (bool)isHorizontal {
218   return mIsHorizontal;
221 - (void)drawWithFrame:(NSRect)cellFrame inView:(NSView*)controlView {
222   CGContext* cgContext = [[NSGraphicsContext currentContext] CGContext];
224   HIThemeTrackDrawInfo tdi;
226   tdi.version = 0;
227   tdi.min = 0;
229   tdi.value = INT32_MAX * (mValue / mMax);
230   tdi.max = INT32_MAX;
231   tdi.bounds = NSRectToCGRect(cellFrame);
232   tdi.attributes = mIsHorizontal ? kThemeTrackHorizontal : 0;
233   tdi.enableState = [self controlTint] == NSClearControlTint
234                         ? kThemeTrackInactive
235                         : kThemeTrackActive;
237   NSControlSize size = [self controlSize];
238   if (size == NSControlSizeRegular) {
239     tdi.kind =
240         mIsIndeterminate ? kThemeLargeIndeterminateBar : kThemeLargeProgressBar;
241   } else {
242     NS_ASSERTION(
243         size == NSControlSizeSmall,
244         "We shouldn't have another size than small and regular for the moment");
245     tdi.kind = mIsIndeterminate ? kThemeMediumIndeterminateBar
246                                 : kThemeMediumProgressBar;
247   }
249   int32_t stepsPerSecond = mIsIndeterminate ? 60 : 30;
250   int32_t milliSecondsPerStep = 1000 / stepsPerSecond;
251   tdi.trackInfo.progress.phase = uint8_t(
252       PR_IntervalToMilliseconds(PR_IntervalNow()) / milliSecondsPerStep);
254   HIThemeDrawTrack(&tdi, NULL, cgContext, kHIThemeOrientationNormal);
257 @end
259 @interface MOZSearchFieldCell : NSSearchFieldCell
260 @property BOOL shouldUseToolbarStyle;
261 @end
263 @implementation MOZSearchFieldCell
265 - (instancetype)init {
266   // We would like to render a search field which has the magnifying glass icon
267   // at the start of the search field, and no cancel button. On 10.12 and 10.13,
268   // empty search fields render the magnifying glass icon in the middle of the
269   // field. So in order to get the icon to show at the start of the field, we
270   // need to give the field some content. We achieve this with a single space
271   // character.
272   self = [super initTextCell:@" "];
274   // However, because the field is now non-empty, by default it shows a cancel
275   // button. To hide the cancel button, override it with a custom NSButtonCell
276   // which renders nothing.
277   NSButtonCell* invisibleCell = [[NSButtonCell alloc] initImageCell:nil];
278   invisibleCell.bezeled = NO;
279   invisibleCell.bordered = NO;
280   self.cancelButtonCell = invisibleCell;
281   [invisibleCell release];
283   return self;
286 - (BOOL)_isToolbarMode {
287   return self.shouldUseToolbarStyle;
290 @end
292 #define HITHEME_ORIENTATION kHIThemeOrientationNormal
294 static CGFloat kMaxFocusRingWidth =
295     0;  // initialized by the nsNativeThemeCocoa constructor
297 // These enums are for indexing into the margin array.
298 enum {
299   leopardOSorlater = 0,  // 10.6 - 10.9
300   yosemiteOSorlater = 1  // 10.10+
303 enum { miniControlSize, smallControlSize, regularControlSize };
305 enum { leftMargin, topMargin, rightMargin, bottomMargin };
307 static size_t EnumSizeForCocoaSize(NSControlSize cocoaControlSize) {
308   if (cocoaControlSize == NSControlSizeMini)
309     return miniControlSize;
310   else if (cocoaControlSize == NSControlSizeSmall)
311     return smallControlSize;
312   else
313     return regularControlSize;
316 static NSControlSize CocoaSizeForEnum(int32_t enumControlSize) {
317   if (enumControlSize == miniControlSize)
318     return NSControlSizeMini;
319   else if (enumControlSize == smallControlSize)
320     return NSControlSizeSmall;
321   else
322     return NSControlSizeRegular;
325 static NSString* CUIControlSizeForCocoaSize(NSControlSize aControlSize) {
326   if (aControlSize == NSControlSizeRegular)
327     return @"regular";
328   else if (aControlSize == NSControlSizeSmall)
329     return @"small";
330   else
331     return @"mini";
334 static void InflateControlRect(NSRect* rect, NSControlSize cocoaControlSize,
335                                const float marginSet[][3][4]) {
336   if (!marginSet) return;
338   static int osIndex = yosemiteOSorlater;
339   size_t controlSize = EnumSizeForCocoaSize(cocoaControlSize);
340   const float* buttonMargins = marginSet[osIndex][controlSize];
341   rect->origin.x -= buttonMargins[leftMargin];
342   rect->origin.y -= buttonMargins[bottomMargin];
343   rect->size.width += buttonMargins[leftMargin] + buttonMargins[rightMargin];
344   rect->size.height += buttonMargins[bottomMargin] + buttonMargins[topMargin];
347 static NSWindow* NativeWindowForFrame(nsIFrame* aFrame,
348                                       nsIWidget** aTopLevelWidget = NULL) {
349   if (!aFrame) return nil;
351   nsIWidget* widget = aFrame->GetNearestWidget();
352   if (!widget) return nil;
354   nsIWidget* topLevelWidget = widget->GetTopLevelWidget();
355   if (aTopLevelWidget) *aTopLevelWidget = topLevelWidget;
357   return (NSWindow*)topLevelWidget->GetNativeData(NS_NATIVE_WINDOW);
360 static NSSize WindowButtonsSize(nsIFrame* aFrame) {
361   NSWindow* window = NativeWindowForFrame(aFrame);
362   if (!window) {
363     // Return fallback values.
364     return NSMakeSize(54, 16);
365   }
367   NSRect buttonBox = NSZeroRect;
368   NSButton* closeButton = [window standardWindowButton:NSWindowCloseButton];
369   if (closeButton) {
370     buttonBox = NSUnionRect(buttonBox, [closeButton frame]);
371   }
372   NSButton* minimizeButton =
373       [window standardWindowButton:NSWindowMiniaturizeButton];
374   if (minimizeButton) {
375     buttonBox = NSUnionRect(buttonBox, [minimizeButton frame]);
376   }
377   NSButton* zoomButton = [window standardWindowButton:NSWindowZoomButton];
378   if (zoomButton) {
379     buttonBox = NSUnionRect(buttonBox, [zoomButton frame]);
380   }
381   return buttonBox.size;
384 static BOOL FrameIsInActiveWindow(nsIFrame* aFrame) {
385   nsIWidget* topLevelWidget = NULL;
386   NSWindow* win = NativeWindowForFrame(aFrame, &topLevelWidget);
387   if (!topLevelWidget || !win) return YES;
389   // XUL popups, e.g. the toolbar customization popup, can't become key windows,
390   // but controls in these windows should still get the active look.
391   if (topLevelWidget->GetWindowType() == widget::WindowType::Popup) {
392     return YES;
393   }
394   if ([win isSheet]) {
395     return [win isKeyWindow];
396   }
397   return [win isMainWindow] && ![win attachedSheet];
400 // Toolbar controls and content controls respond to different window
401 // activeness states.
402 static BOOL IsActive(nsIFrame* aFrame, BOOL aIsToolbarControl) {
403   if (aIsToolbarControl) return [NativeWindowForFrame(aFrame) isMainWindow];
404   return FrameIsInActiveWindow(aFrame);
407 NS_IMPL_ISUPPORTS_INHERITED(nsNativeThemeCocoa, nsNativeTheme, nsITheme)
409 nsNativeThemeCocoa::nsNativeThemeCocoa() : ThemeCocoa(ScrollbarStyle()) {
410   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
412   kMaxFocusRingWidth = 7;
414   // provide a local autorelease pool, as this is called during startup
415   // before the main event-loop pool is in place
416   nsAutoreleasePool pool;
418   mDisclosureButtonCell = [[NSButtonCell alloc] initTextCell:@""];
419   [mDisclosureButtonCell setBezelStyle:NSBezelStyleRoundedDisclosure];
420   [mDisclosureButtonCell setButtonType:NSButtonTypePushOnPushOff];
421   [mDisclosureButtonCell setHighlightsBy:NSPushInCellMask];
423   mHelpButtonCell = [[NSButtonCell alloc] initTextCell:@""];
424   [mHelpButtonCell setBezelStyle:NSBezelStyleHelpButton];
425   [mHelpButtonCell setButtonType:NSButtonTypeMomentaryPushIn];
426   [mHelpButtonCell setHighlightsBy:NSPushInCellMask];
428   mPushButtonCell = [[NSButtonCell alloc] initTextCell:@""];
429   [mPushButtonCell setButtonType:NSButtonTypeMomentaryPushIn];
430   [mPushButtonCell setHighlightsBy:NSPushInCellMask];
432   mRadioButtonCell = [[NSButtonCell alloc] initTextCell:@""];
433   [mRadioButtonCell setButtonType:NSButtonTypeRadio];
435   mCheckboxCell = [[NSButtonCell alloc] initTextCell:@""];
436   [mCheckboxCell setButtonType:NSButtonTypeSwitch];
437   [mCheckboxCell setAllowsMixedState:YES];
439   mTextFieldCell = [[NSTextFieldCell alloc] initTextCell:@""];
440   [mTextFieldCell setBezeled:YES];
441   [mTextFieldCell setEditable:YES];
442   [mTextFieldCell setFocusRingType:NSFocusRingTypeExterior];
444   mSearchFieldCell = [[MOZSearchFieldCell alloc] init];
445   [mSearchFieldCell setBezelStyle:NSTextFieldRoundedBezel];
446   [mSearchFieldCell setBezeled:YES];
447   [mSearchFieldCell setEditable:YES];
448   [mSearchFieldCell setFocusRingType:NSFocusRingTypeExterior];
450   mDropdownCell = [[NSPopUpButtonCell alloc] initTextCell:@"" pullsDown:NO];
452   mComboBoxCell = [[NSComboBoxCell alloc] initTextCell:@""];
453   [mComboBoxCell setBezeled:YES];
454   [mComboBoxCell setEditable:YES];
455   [mComboBoxCell setFocusRingType:NSFocusRingTypeExterior];
457   mProgressBarCell = [[NSProgressBarCell alloc] init];
459   mMeterBarCell = [[NSLevelIndicatorCell alloc]
460       initWithLevelIndicatorStyle:NSLevelIndicatorStyleContinuousCapacity];
462   mTreeHeaderCell = [[NSTableHeaderCell alloc] init];
464   mCellDrawView = [[MOZCellDrawView alloc] init];
466   if (XRE_IsParentProcess()) {
467     // Put the cell draw view into a window that is never shown.
468     // This allows us to convince some NSCell implementations (such as
469     // NSButtonCell for default buttons) to draw with the active appearance.
470     // Another benefit of putting the draw view in a window is the fact that it
471     // lets NSTextFieldCell (and its subclass NSSearchFieldCell) inherit the
472     // current NSApplication effectiveAppearance automatically, so the field
473     // adapts to Dark Mode correctly. We don't create this window when the
474     // native theme is used in the content process because NSWindow creation
475     // runs into the sandbox and because we never run default buttons in content
476     // processes anyway.
477     mCellDrawWindow = [[MOZCellDrawWindow alloc]
478         initWithContentRect:NSZeroRect
479                   styleMask:NSWindowStyleMaskBorderless
480                     backing:NSBackingStoreBuffered
481                       defer:NO];
482     [mCellDrawWindow.contentView addSubview:mCellDrawView];
483   }
485   NS_OBJC_END_TRY_IGNORE_BLOCK;
488 nsNativeThemeCocoa::~nsNativeThemeCocoa() {
489   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
491   [mMeterBarCell release];
492   [mProgressBarCell release];
493   [mDisclosureButtonCell release];
494   [mHelpButtonCell release];
495   [mPushButtonCell release];
496   [mRadioButtonCell release];
497   [mCheckboxCell release];
498   [mTextFieldCell release];
499   [mSearchFieldCell release];
500   [mDropdownCell release];
501   [mComboBoxCell release];
502   [mTreeHeaderCell release];
503   [mCellDrawWindow release];
504   [mCellDrawView release];
506   NS_OBJC_END_TRY_IGNORE_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 graphicsContextWithCGContext: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 =
522       CGContextGetUserSpaceToDeviceSpaceTransform(cgContext);
523   CGRect transformedUserSpacePixel =
524       CGRectApplyAffineTransform(CGRectMake(0, 0, 1, 1), ctm);
525   float maxScale = std::max(fabs(transformedUserSpacePixel.size.width),
526                             fabs(transformedUserSpacePixel.size.height));
527   return maxScale > 1.0 ? 2 : 1;
531  * Draw the given NSCell into the given cgContext.
533  * destRect - the size and position of the resulting control rectangle
534  * controlSize - the NSControlSize which will be given to the NSCell before
535  *  asking it to render
536  * naturalSize - The natural dimensions of this control.
537  *  If the control rect size is not equal to either of these, a scale
538  *  will be applied to the context so that rendering the control at the
539  *  natural size will result in it filling the destRect space.
540  *  If a control has no natural dimensions in either/both axes, pass 0.0f.
541  * minimumSize - The minimum dimensions of this control.
542  *  If the control rect size is less than the minimum for a given axis,
543  *  a scale will be applied to the context so that the minimum is used
544  *  for drawing.  If a control has no minimum dimensions in either/both
545  *  axes, pass 0.0f.
546  * marginSet - an array of margins; a multidimensional array of [2][3][4],
547  *  with the first dimension being the OS version (Tiger or Leopard),
548  *  the second being the control size (mini, small, regular), and the third
549  *  being the 4 margin values (left, top, right, bottom).
550  * view - The NSView that we're drawing into. As far as I can tell, it doesn't
551  *  matter if this is really the right view; it just has to return YES when
552  *  asked for isFlipped. Otherwise we'll get drawing bugs on 10.4.
553  * mirrorHorizontal - whether to mirror the cell horizontally
554  */
555 static void DrawCellWithScaling(NSCell* cell, CGContextRef cgContext,
556                                 const HIRect& destRect,
557                                 NSControlSize controlSize, NSSize naturalSize,
558                                 NSSize minimumSize,
559                                 const float marginSet[][3][4], NSView* view,
560                                 BOOL mirrorHorizontal) {
561   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
563   NSRect drawRect = NSMakeRect(destRect.origin.x, destRect.origin.y,
564                                destRect.size.width, destRect.size.height);
566   if (naturalSize.width != 0.0f) drawRect.size.width = naturalSize.width;
567   if (naturalSize.height != 0.0f) drawRect.size.height = naturalSize.height;
569   // Keep aspect ratio when scaling if one dimension is free.
570   if (naturalSize.width == 0.0f && naturalSize.height != 0.0f)
571     drawRect.size.width =
572         destRect.size.width * naturalSize.height / destRect.size.height;
573   if (naturalSize.height == 0.0f && naturalSize.width != 0.0f)
574     drawRect.size.height =
575         destRect.size.height * naturalSize.width / destRect.size.width;
577   // Honor minimum sizes.
578   if (drawRect.size.width < minimumSize.width)
579     drawRect.size.width = minimumSize.width;
580   if (drawRect.size.height < minimumSize.height)
581     drawRect.size.height = minimumSize.height;
583   [NSGraphicsContext saveGraphicsState];
585   // Only skip the buffer if the area of our cell (in pixels^2) is too large.
586   if (drawRect.size.width * drawRect.size.height > BITMAP_MAX_AREA) {
587     // Inflate the rect Gecko gave us by the margin for the control.
588     InflateControlRect(&drawRect, controlSize, marginSet);
590     NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
591     [NSGraphicsContext
592         setCurrentContext:[NSGraphicsContext
593                               graphicsContextWithCGContext:cgContext
594                                                    flipped:YES]];
596     DrawCellIncludingFocusRing(cell, drawRect, view);
598     [NSGraphicsContext setCurrentContext:savedContext];
599   } else {
600     float w = ceil(drawRect.size.width);
601     float h = ceil(drawRect.size.height);
602     NSRect tmpRect = NSMakeRect(kMaxFocusRingWidth, kMaxFocusRingWidth, w, h);
604     // inflate to figure out the frame we need to tell NSCell to draw in, to get
605     // something that's 0,0,w,h
606     InflateControlRect(&tmpRect, controlSize, marginSet);
608     // and then, expand by kMaxFocusRingWidth size to make sure we can capture
609     // any focus ring
610     w += kMaxFocusRingWidth * 2.0;
611     h += kMaxFocusRingWidth * 2.0;
613     int backingScaleFactor = GetBackingScaleFactorForRendering(cgContext);
614     CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
615     CGContextRef ctx = CGBitmapContextCreate(
616         NULL, (int)w * backingScaleFactor, (int)h * backingScaleFactor, 8,
617         (int)w * backingScaleFactor * 4, rgb, kCGImageAlphaPremultipliedFirst);
618     CGColorSpaceRelease(rgb);
620     // We need to flip the image twice in order to avoid drawing bugs on 10.4,
621     // see bug 465069. This is the first flip transform, applied to cgContext.
622     CGContextScaleCTM(cgContext, 1.0f, -1.0f);
623     CGContextTranslateCTM(cgContext, 0.0f,
624                           -(2.0 * destRect.origin.y + destRect.size.height));
625     if (mirrorHorizontal) {
626       CGContextScaleCTM(cgContext, -1.0f, 1.0f);
627       CGContextTranslateCTM(
628           cgContext, -(2.0 * destRect.origin.x + destRect.size.width), 0.0f);
629     }
631     NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
632     [NSGraphicsContext
633         setCurrentContext:[NSGraphicsContext graphicsContextWithCGContext:ctx
634                                                                   flipped:YES]];
636     CGContextScaleCTM(ctx, backingScaleFactor, backingScaleFactor);
638     // Set the context's "base transform" to in order to get correctly-sized
639     // focus rings.
640     CGContextSetBaseCTM(ctx, CGAffineTransformMakeScale(backingScaleFactor,
641                                                         backingScaleFactor));
643     // This is the second flip transform, applied to ctx.
644     CGContextScaleCTM(ctx, 1.0f, -1.0f);
645     CGContextTranslateCTM(ctx, 0.0f,
646                           -(2.0 * tmpRect.origin.y + tmpRect.size.height));
648     DrawCellIncludingFocusRing(cell, tmpRect, view);
650     [NSGraphicsContext setCurrentContext:savedContext];
652     CGImageRef img = CGBitmapContextCreateImage(ctx);
654     // Drop the image into the original destination rectangle, scaling to fit
655     // Only scale kMaxFocusRingWidth by xscale/yscale when the resulting rect
656     // doesn't extend beyond the overflow rect
657     float xscale = destRect.size.width / drawRect.size.width;
658     float yscale = destRect.size.height / drawRect.size.height;
659     float scaledFocusRingX =
660         xscale < 1.0f ? kMaxFocusRingWidth * xscale : kMaxFocusRingWidth;
661     float scaledFocusRingY =
662         yscale < 1.0f ? kMaxFocusRingWidth * yscale : kMaxFocusRingWidth;
663     CGContextDrawImage(cgContext,
664                        CGRectMake(destRect.origin.x - scaledFocusRingX,
665                                   destRect.origin.y - scaledFocusRingY,
666                                   destRect.size.width + scaledFocusRingX * 2,
667                                   destRect.size.height + scaledFocusRingY * 2),
668                        img);
670     CGImageRelease(img);
671     CGContextRelease(ctx);
672   }
674   [NSGraphicsContext restoreGraphicsState];
676 #if DRAW_IN_FRAME_DEBUG
677   CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
678   CGContextFillRect(cgContext, destRect);
679 #endif
681   NS_OBJC_END_TRY_IGNORE_BLOCK;
684 struct CellRenderSettings {
685   // The natural dimensions of the control.
686   // If a control has no natural dimensions in either/both axes, set to 0.0f.
687   NSSize naturalSizes[3];
689   // The minimum dimensions of the control.
690   // If a control has no minimum dimensions in either/both axes, set to 0.0f.
691   NSSize minimumSizes[3];
693   // A three-dimensional array,
694   // with the first dimension being the OS version ([0] 10.6-10.9, [1] 10.10 and
695   // above), the second being the control size (mini, small, regular), and the
696   // third being the 4 margin values (left, top, right, bottom).
697   float margins[2][3][4];
701  * This is a helper method that returns the required NSControlSize given a size
702  * and the size of the three controls plus a tolerance.
703  * size - The width or the height of the element to draw.
704  * sizes - An array with the all the width/height of the element for its
705  *         different sizes.
706  * tolerance - The tolerance as passed to DrawCellWithSnapping.
707  * NOTE: returns NSControlSizeRegular if all values in 'sizes' are zero.
708  */
709 static NSControlSize FindControlSize(CGFloat size, const CGFloat* sizes,
710                                      CGFloat tolerance) {
711   for (uint32_t i = miniControlSize; i <= regularControlSize; ++i) {
712     if (sizes[i] == 0) {
713       continue;
714     }
716     CGFloat next = 0;
717     // Find next value.
718     for (uint32_t j = i + 1; j <= regularControlSize; ++j) {
719       if (sizes[j] != 0) {
720         next = sizes[j];
721         break;
722       }
723     }
725     // If it's the latest value, we pick it.
726     if (next == 0) {
727       return CocoaSizeForEnum(i);
728     }
730     if (size <= sizes[i] + tolerance && size < next) {
731       return CocoaSizeForEnum(i);
732     }
733   }
735   // If we are here, that means sizes[] was an array with only empty values
736   // or the algorithm above is wrong.
737   // The former can happen but the later would be wrong.
738   NS_ASSERTION(sizes[0] == 0 && sizes[1] == 0 && sizes[2] == 0,
739                "We found no control! We shouldn't be there!");
740   return CocoaSizeForEnum(regularControlSize);
744  * Draw the given NSCell into the given cgContext with a nice control size.
746  * This function is similar to DrawCellWithScaling, but it decides what
747  * control size to use based on the destRect's size.
748  * Scaling is only applied when the difference between the destRect's size
749  * and the next smaller natural size is greater than snapTolerance. Otherwise
750  * it snaps to the next smaller control size without scaling because unscaled
751  * controls look nicer.
752  */
753 static void DrawCellWithSnapping(NSCell* cell, CGContextRef cgContext,
754                                  const HIRect& destRect,
755                                  const CellRenderSettings settings,
756                                  float verticalAlignFactor, NSView* view,
757                                  BOOL mirrorHorizontal,
758                                  float snapTolerance = 2.0f) {
759   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
761   const float rectWidth = destRect.size.width,
762               rectHeight = destRect.size.height;
763   const NSSize* sizes = settings.naturalSizes;
764   const NSSize miniSize = sizes[EnumSizeForCocoaSize(NSControlSizeMini)];
765   const NSSize smallSize = sizes[EnumSizeForCocoaSize(NSControlSizeSmall)];
766   const NSSize regularSize = sizes[EnumSizeForCocoaSize(NSControlSizeRegular)];
768   HIRect drawRect = destRect;
770   CGFloat controlWidths[3] = {miniSize.width, smallSize.width,
771                               regularSize.width};
772   NSControlSize controlSizeX =
773       FindControlSize(rectWidth, controlWidths, snapTolerance);
774   CGFloat controlHeights[3] = {miniSize.height, smallSize.height,
775                                regularSize.height};
776   NSControlSize controlSizeY =
777       FindControlSize(rectHeight, controlHeights, snapTolerance);
779   NSControlSize controlSize = NSControlSizeRegular;
780   size_t sizeIndex = 0;
782   // At some sizes, don't scale but snap.
783   const NSControlSize smallerControlSize =
784       EnumSizeForCocoaSize(controlSizeX) < EnumSizeForCocoaSize(controlSizeY)
785           ? controlSizeX
786           : controlSizeY;
787   const size_t smallerControlSizeIndex =
788       EnumSizeForCocoaSize(smallerControlSize);
789   const NSSize size = sizes[smallerControlSizeIndex];
790   float diffWidth = size.width ? rectWidth - size.width : 0.0f;
791   float diffHeight = size.height ? rectHeight - size.height : 0.0f;
792   if (diffWidth >= 0.0f && diffHeight >= 0.0f && diffWidth <= snapTolerance &&
793       diffHeight <= snapTolerance) {
794     // Snap to the smaller control size.
795     controlSize = smallerControlSize;
796     sizeIndex = smallerControlSizeIndex;
797     MOZ_ASSERT(sizeIndex < ArrayLength(settings.naturalSizes));
799     // Resize and center the drawRect.
800     if (sizes[sizeIndex].width) {
801       drawRect.origin.x +=
802           ceil((destRect.size.width - sizes[sizeIndex].width) / 2);
803       drawRect.size.width = sizes[sizeIndex].width;
804     }
805     if (sizes[sizeIndex].height) {
806       drawRect.origin.y +=
807           floor((destRect.size.height - sizes[sizeIndex].height) *
808                 verticalAlignFactor);
809       drawRect.size.height = sizes[sizeIndex].height;
810     }
811   } else {
812     // Use the larger control size.
813     controlSize =
814         EnumSizeForCocoaSize(controlSizeX) > EnumSizeForCocoaSize(controlSizeY)
815             ? controlSizeX
816             : controlSizeY;
817     sizeIndex = EnumSizeForCocoaSize(controlSize);
818   }
820   [cell setControlSize:controlSize];
822   MOZ_ASSERT(sizeIndex < ArrayLength(settings.minimumSizes));
823   const NSSize minimumSize = settings.minimumSizes[sizeIndex];
824   DrawCellWithScaling(cell, cgContext, drawRect, controlSize, sizes[sizeIndex],
825                       minimumSize, settings.margins, view, mirrorHorizontal);
827   NS_OBJC_END_TRY_IGNORE_BLOCK;
830 @interface NSWindow (CoreUIRendererPrivate)
831 + (CUIRendererRef)coreUIRenderer;
832 @end
834 @interface NSObject (NSAppearanceCoreUIRendering)
835 - (void)_drawInRect:(CGRect)rect
836             context:(CGContextRef)cgContext
837             options:(id)options;
838 @end
840 static void RenderWithCoreUI(CGRect aRect, CGContextRef cgContext,
841                              NSDictionary* aOptions,
842                              bool aSkipAreaCheck = false) {
843   if (!aSkipAreaCheck &&
844       aRect.size.width * aRect.size.height > BITMAP_MAX_AREA) {
845     return;
846   }
848   NSAppearance* appearance = NSAppearance.currentAppearance;
849   if (appearance &&
850       [appearance respondsToSelector:@selector(_drawInRect:context:options:)]) {
851     // Render through NSAppearance on Mac OS 10.10 and up. This will call
852     // CUIDraw with a CoreUI renderer that will give us the correct 10.10
853     // style. Calling CUIDraw directly with [NSWindow coreUIRenderer] still
854     // renders 10.9-style widgets on 10.10.
855     [appearance _drawInRect:aRect context:cgContext options:aOptions];
856   } else {
857     // 10.9 and below
858     CUIRendererRef renderer =
859         [NSWindow respondsToSelector:@selector(coreUIRenderer)]
860             ? [NSWindow coreUIRenderer]
861             : nil;
862     CUIDraw(renderer, aRect, cgContext, (CFDictionaryRef)aOptions, NULL);
863   }
866 static float VerticalAlignFactor(nsIFrame* aFrame) {
867   if (!aFrame) return 0.5f;  // default: center
869   const auto& va = aFrame->StyleDisplay()->mVerticalAlign;
870   auto kw = va.IsKeyword() ? va.AsKeyword() : StyleVerticalAlignKeyword::Middle;
871   switch (kw) {
872     case StyleVerticalAlignKeyword::Top:
873     case StyleVerticalAlignKeyword::TextTop:
874       return 0.0f;
876     case StyleVerticalAlignKeyword::Sub:
877     case StyleVerticalAlignKeyword::Super:
878     case StyleVerticalAlignKeyword::Middle:
879     case StyleVerticalAlignKeyword::MozMiddleWithBaseline:
880       return 0.5f;
882     case StyleVerticalAlignKeyword::Baseline:
883     case StyleVerticalAlignKeyword::Bottom:
884     case StyleVerticalAlignKeyword::TextBottom:
885       return 1.0f;
887     default:
888       MOZ_ASSERT_UNREACHABLE("invalid vertical-align");
889       return 0.5f;
890   }
893 static void ApplyControlParamsToNSCell(
894     nsNativeThemeCocoa::ControlParams aControlParams, NSCell* aCell) {
895   [aCell setEnabled:!aControlParams.disabled];
896   [aCell setShowsFirstResponder:(aControlParams.focused &&
897                                  !aControlParams.disabled &&
898                                  aControlParams.insideActiveWindow)];
899   [aCell setHighlighted:aControlParams.pressed];
902 // These are the sizes that Gecko needs to request to draw if it wants
903 // to get a standard-sized Aqua radio button drawn. Note that the rects
904 // that draw these are actually a little bigger.
905 static const CellRenderSettings radioSettings = {
906     {
907         NSMakeSize(11, 11),  // mini
908         NSMakeSize(13, 13),  // small
909         NSMakeSize(16, 16)   // regular
910     },
911     {NSZeroSize, NSZeroSize, NSZeroSize},
912     {{
913          // Leopard
914          {0, 0, 0, 0},  // mini
915          {0, 1, 1, 1},  // small
916          {0, 0, 0, 0}   // regular
917      },
918      {
919          // Yosemite
920          {0, 0, 0, 0},  // mini
921          {1, 1, 1, 2},  // small
922          {0, 0, 0, 0}   // regular
923      }}};
925 static const CellRenderSettings checkboxSettings = {
926     {
927         NSMakeSize(11, 11),  // mini
928         NSMakeSize(13, 13),  // small
929         NSMakeSize(16, 16)   // regular
930     },
931     {NSZeroSize, NSZeroSize, NSZeroSize},
932     {{
933          // Leopard
934          {0, 1, 0, 0},  // mini
935          {0, 1, 0, 1},  // small
936          {0, 1, 0, 1}   // regular
937      },
938      {
939          // Yosemite
940          {0, 1, 0, 0},  // mini
941          {0, 1, 0, 1},  // small
942          {0, 1, 0, 1}   // regular
943      }}};
945 static NSControlStateValue CellStateForCheckboxOrRadioState(
946     nsNativeThemeCocoa::CheckboxOrRadioState aState) {
947   switch (aState) {
948     case nsNativeThemeCocoa::CheckboxOrRadioState::eOff:
949       return NSControlStateValueOff;
950     case nsNativeThemeCocoa::CheckboxOrRadioState::eOn:
951       return NSControlStateValueOn;
952     case nsNativeThemeCocoa::CheckboxOrRadioState::eIndeterminate:
953       return NSControlStateValueMixed;
954   }
957 void nsNativeThemeCocoa::DrawCheckboxOrRadio(
958     CGContextRef cgContext, bool inCheckbox, const HIRect& inBoxRect,
959     const CheckboxOrRadioParams& aParams) {
960   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
962   NSButtonCell* cell = inCheckbox ? mCheckboxCell : mRadioButtonCell;
963   ApplyControlParamsToNSCell(aParams.controlParams, cell);
965   [cell setState:CellStateForCheckboxOrRadioState(aParams.state)];
966   [cell setControlTint:(aParams.controlParams.insideActiveWindow
967                             ? [NSColor currentControlTint]
968                             : NSClearControlTint)];
970   // Ensure that the control is square.
971   float length = std::min(inBoxRect.size.width, inBoxRect.size.height);
972   HIRect drawRect = CGRectMake(
973       inBoxRect.origin.x + (int)((inBoxRect.size.width - length) / 2.0f),
974       inBoxRect.origin.y + (int)((inBoxRect.size.height - length) / 2.0f),
975       length, length);
977   if (mCellDrawWindow) {
978     mCellDrawWindow.cellsShouldLookActive =
979         aParams.controlParams.insideActiveWindow;
980   }
981   DrawCellWithSnapping(cell, cgContext, drawRect,
982                        inCheckbox ? checkboxSettings : radioSettings,
983                        aParams.verticalAlignFactor, mCellDrawView, NO);
985   NS_OBJC_END_TRY_IGNORE_BLOCK;
988 static const CellRenderSettings searchFieldSettings = {
989     {
990         NSMakeSize(0, 16),  // mini
991         NSMakeSize(0, 19),  // small
992         NSMakeSize(0, 22)   // regular
993     },
994     {
995         NSMakeSize(32, 0),  // mini
996         NSMakeSize(38, 0),  // small
997         NSMakeSize(44, 0)   // regular
998     },
999     {{
1000          // Leopard
1001          {0, 0, 0, 0},  // mini
1002          {0, 0, 0, 0},  // small
1003          {0, 0, 0, 0}   // regular
1004      },
1005      {
1006          // Yosemite
1007          {0, 0, 0, 0},  // mini
1008          {0, 0, 0, 0},  // small
1009          {0, 0, 0, 0}   // regular
1010      }}};
1012 static bool IsToolbarStyleContainer(nsIFrame* aFrame) {
1013   nsIContent* content = aFrame->GetContent();
1014   if (!content) {
1015     return false;
1016   }
1018   if (content->IsAnyOfXULElements(nsGkAtoms::toolbar, nsGkAtoms::toolbox,
1019                                   nsGkAtoms::statusbar)) {
1020     return true;
1021   }
1023   switch (aFrame->StyleDisplay()->EffectiveAppearance()) {
1024     case StyleAppearance::Toolbar:
1025     case StyleAppearance::Statusbar:
1026       return true;
1027     default:
1028       return false;
1029   }
1032 static bool IsInsideToolbar(nsIFrame* aFrame) {
1033   for (nsIFrame* frame = aFrame; frame; frame = frame->GetParent()) {
1034     if (IsToolbarStyleContainer(frame)) {
1035       return true;
1036     }
1037   }
1038   return false;
1041 nsNativeThemeCocoa::TextFieldParams nsNativeThemeCocoa::ComputeTextFieldParams(
1042     nsIFrame* aFrame, ElementState aEventState) {
1043   TextFieldParams params;
1044   params.insideToolbar = IsInsideToolbar(aFrame);
1045   params.disabled = aEventState.HasState(ElementState::DISABLED);
1047   // See ShouldUnconditionallyDrawFocusRingIfFocused.
1048   params.focused = aEventState.HasState(ElementState::FOCUS);
1050   params.rtl = IsFrameRTL(aFrame);
1051   params.verticalAlignFactor = VerticalAlignFactor(aFrame);
1052   return params;
1055 void nsNativeThemeCocoa::DrawTextField(CGContextRef cgContext,
1056                                        const HIRect& inBoxRect,
1057                                        const TextFieldParams& aParams) {
1058   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1060   NSTextFieldCell* cell = mTextFieldCell;
1061   [cell setEnabled:!aParams.disabled];
1062   [cell setShowsFirstResponder:aParams.focused];
1064   if (mCellDrawWindow) {
1065     mCellDrawWindow.cellsShouldLookActive =
1066         YES;  // TODO: propagate correct activeness state
1067   }
1068   DrawCellWithSnapping(cell, cgContext, inBoxRect, searchFieldSettings,
1069                        aParams.verticalAlignFactor, mCellDrawView, aParams.rtl);
1071   NS_OBJC_END_TRY_IGNORE_BLOCK;
1074 void nsNativeThemeCocoa::DrawSearchField(CGContextRef cgContext,
1075                                          const HIRect& inBoxRect,
1076                                          const TextFieldParams& aParams) {
1077   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1079   mSearchFieldCell.enabled = !aParams.disabled;
1080   mSearchFieldCell.showsFirstResponder = aParams.focused;
1081   mSearchFieldCell.placeholderString = @"";
1082   mSearchFieldCell.shouldUseToolbarStyle = aParams.insideToolbar;
1084   if (mCellDrawWindow) {
1085     mCellDrawWindow.cellsShouldLookActive =
1086         YES;  // TODO: propagate correct activeness state
1087   }
1088   DrawCellWithSnapping(mSearchFieldCell, cgContext, inBoxRect,
1089                        searchFieldSettings, aParams.verticalAlignFactor,
1090                        mCellDrawView, aParams.rtl);
1092   NS_OBJC_END_TRY_IGNORE_BLOCK;
1095 static bool ShouldUnconditionallyDrawFocusRingIfFocused(nsIFrame* aFrame) {
1096   // Mac always draws focus rings for textboxes and lists.
1097   switch (aFrame->StyleDisplay()->EffectiveAppearance()) {
1098     case StyleAppearance::NumberInput:
1099     case StyleAppearance::Textfield:
1100     case StyleAppearance::Textarea:
1101     case StyleAppearance::Searchfield:
1102     case StyleAppearance::Listbox:
1103       return true;
1104     default:
1105       return false;
1106   }
1109 nsNativeThemeCocoa::ControlParams nsNativeThemeCocoa::ComputeControlParams(
1110     nsIFrame* aFrame, ElementState aEventState) {
1111   ControlParams params;
1112   params.disabled = aEventState.HasState(ElementState::DISABLED);
1113   params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
1114   params.pressed =
1115       aEventState.HasAllStates(ElementState::ACTIVE | ElementState::HOVER);
1116   params.focused = aEventState.HasState(ElementState::FOCUS) &&
1117                    (aEventState.HasState(ElementState::FOCUSRING) ||
1118                     ShouldUnconditionallyDrawFocusRingIfFocused(aFrame));
1119   params.rtl = IsFrameRTL(aFrame);
1120   return params;
1123 static const NSSize kHelpButtonSize = NSMakeSize(20, 20);
1124 static const NSSize kDisclosureButtonSize = NSMakeSize(21, 21);
1126 static const CellRenderSettings pushButtonSettings = {
1127     {
1128         NSMakeSize(0, 16),  // mini
1129         NSMakeSize(0, 19),  // small
1130         NSMakeSize(0, 22)   // regular
1131     },
1132     {
1133         NSMakeSize(18, 0),  // mini
1134         NSMakeSize(26, 0),  // small
1135         NSMakeSize(30, 0)   // regular
1136     },
1137     {{
1138          // Leopard
1139          {0, 0, 0, 0},  // mini
1140          {4, 0, 4, 1},  // small
1141          {5, 0, 5, 2}   // regular
1142      },
1143      {
1144          // Yosemite
1145          {0, 0, 0, 0},  // mini
1146          {4, 0, 4, 1},  // small
1147          {5, 0, 5, 2}   // regular
1148      }}};
1150 // The height at which we start doing square buttons instead of rounded buttons
1151 // Rounded buttons look bad if drawn at a height greater than 26, so at that
1152 // point we switch over to doing square buttons which looks fine at any size.
1153 #define DO_SQUARE_BUTTON_HEIGHT 26
1155 void nsNativeThemeCocoa::DrawPushButton(CGContextRef cgContext,
1156                                         const HIRect& inBoxRect,
1157                                         ButtonType aButtonType,
1158                                         ControlParams aControlParams) {
1159   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1161   ApplyControlParamsToNSCell(aControlParams, mPushButtonCell);
1162   [mPushButtonCell setBezelStyle:NSBezelStyleRounded];
1163   mPushButtonCell.keyEquivalent =
1164       aButtonType == ButtonType::eDefaultPushButton ? @"\r" : @"";
1166   if (mCellDrawWindow) {
1167     mCellDrawWindow.cellsShouldLookActive = aControlParams.insideActiveWindow;
1168   }
1169   DrawCellWithSnapping(mPushButtonCell, cgContext, inBoxRect,
1170                        pushButtonSettings, 0.5f, mCellDrawView,
1171                        aControlParams.rtl, 1.0f);
1173   NS_OBJC_END_TRY_IGNORE_BLOCK;
1176 void nsNativeThemeCocoa::DrawSquareBezelPushButton(
1177     CGContextRef cgContext, const HIRect& inBoxRect,
1178     ControlParams aControlParams) {
1179   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1181   ApplyControlParamsToNSCell(aControlParams, mPushButtonCell);
1182   [mPushButtonCell setBezelStyle:NSBezelStyleShadowlessSquare];
1184   if (mCellDrawWindow) {
1185     mCellDrawWindow.cellsShouldLookActive = aControlParams.insideActiveWindow;
1186   }
1187   DrawCellWithScaling(mPushButtonCell, cgContext, inBoxRect,
1188                       NSControlSizeRegular, NSZeroSize, NSMakeSize(14, 0), NULL,
1189                       mCellDrawView, aControlParams.rtl);
1191   NS_OBJC_END_TRY_IGNORE_BLOCK;
1194 void nsNativeThemeCocoa::DrawHelpButton(CGContextRef cgContext,
1195                                         const HIRect& inBoxRect,
1196                                         ControlParams aControlParams) {
1197   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1199   ApplyControlParamsToNSCell(aControlParams, mHelpButtonCell);
1201   if (mCellDrawWindow) {
1202     mCellDrawWindow.cellsShouldLookActive = aControlParams.insideActiveWindow;
1203   }
1204   DrawCellWithScaling(mHelpButtonCell, cgContext, inBoxRect,
1205                       NSControlSizeRegular, NSZeroSize, kHelpButtonSize, NULL,
1206                       mCellDrawView,
1207                       false);  // Don't mirror icon in RTL.
1209   NS_OBJC_END_TRY_IGNORE_BLOCK;
1212 void nsNativeThemeCocoa::DrawDisclosureButton(CGContextRef cgContext,
1213                                               const HIRect& inBoxRect,
1214                                               ControlParams aControlParams,
1215                                               NSControlStateValue aCellState) {
1216   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1218   ApplyControlParamsToNSCell(aControlParams, mDisclosureButtonCell);
1219   [mDisclosureButtonCell setState:aCellState];
1221   if (mCellDrawWindow) {
1222     mCellDrawWindow.cellsShouldLookActive = aControlParams.insideActiveWindow;
1223   }
1224   DrawCellWithScaling(mDisclosureButtonCell, cgContext, inBoxRect,
1225                       NSControlSizeRegular, NSZeroSize, kDisclosureButtonSize,
1226                       NULL, mCellDrawView,
1227                       false);  // Don't mirror icon in RTL.
1229   NS_OBJC_END_TRY_IGNORE_BLOCK;
1232 typedef void (*RenderHIThemeControlFunction)(CGContextRef cgContext,
1233                                              const HIRect& aRenderRect,
1234                                              void* aData);
1236 static void RenderTransformedHIThemeControl(CGContextRef aCGContext,
1237                                             const HIRect& aRect,
1238                                             RenderHIThemeControlFunction aFunc,
1239                                             void* aData,
1240                                             BOOL mirrorHorizontally = NO) {
1241   CGAffineTransform savedCTM = CGContextGetCTM(aCGContext);
1242   CGContextTranslateCTM(aCGContext, aRect.origin.x, aRect.origin.y);
1244   bool drawDirect;
1245   HIRect drawRect = aRect;
1246   drawRect.origin = CGPointZero;
1248   if (!mirrorHorizontally && savedCTM.a == 1.0f && savedCTM.b == 0.0f &&
1249       savedCTM.c == 0.0f && (savedCTM.d == 1.0f || savedCTM.d == -1.0f)) {
1250     drawDirect = TRUE;
1251   } else {
1252     drawDirect = FALSE;
1253   }
1255   // Fall back to no bitmap buffer if the area of our control (in pixels^2)
1256   // is too large.
1257   if (drawDirect || (aRect.size.width * aRect.size.height > BITMAP_MAX_AREA)) {
1258     aFunc(aCGContext, drawRect, aData);
1259   } else {
1260     // Inflate the buffer to capture focus rings.
1261     int w = ceil(drawRect.size.width) + 2 * kMaxFocusRingWidth;
1262     int h = ceil(drawRect.size.height) + 2 * kMaxFocusRingWidth;
1264     int backingScaleFactor = GetBackingScaleFactorForRendering(aCGContext);
1265     CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
1266     CGContextRef bitmapctx = CGBitmapContextCreate(
1267         NULL, w * backingScaleFactor, h * backingScaleFactor, 8,
1268         w * backingScaleFactor * 4, colorSpace,
1269         kCGImageAlphaPremultipliedFirst);
1270     CGColorSpaceRelease(colorSpace);
1272     CGContextScaleCTM(bitmapctx, backingScaleFactor, backingScaleFactor);
1273     CGContextTranslateCTM(bitmapctx, kMaxFocusRingWidth, kMaxFocusRingWidth);
1275     // Set the context's "base transform" to in order to get correctly-sized
1276     // focus rings.
1277     CGContextSetBaseCTM(bitmapctx, CGAffineTransformMakeScale(
1278                                        backingScaleFactor, backingScaleFactor));
1280     // HITheme always wants to draw into a flipped context, or things
1281     // get confused.
1282     CGContextTranslateCTM(bitmapctx, 0.0f, aRect.size.height);
1283     CGContextScaleCTM(bitmapctx, 1.0f, -1.0f);
1285     aFunc(bitmapctx, drawRect, aData);
1287     CGImageRef bitmap = CGBitmapContextCreateImage(bitmapctx);
1289     CGAffineTransform ctm = CGContextGetCTM(aCGContext);
1291     // We need to unflip, so that we can do a DrawImage without getting a
1292     // flipped image.
1293     CGContextTranslateCTM(aCGContext, 0.0f, aRect.size.height);
1294     CGContextScaleCTM(aCGContext, 1.0f, -1.0f);
1296     if (mirrorHorizontally) {
1297       CGContextTranslateCTM(aCGContext, aRect.size.width, 0);
1298       CGContextScaleCTM(aCGContext, -1.0f, 1.0f);
1299     }
1301     HIRect inflatedDrawRect =
1302         CGRectMake(-kMaxFocusRingWidth, -kMaxFocusRingWidth, w, h);
1303     CGContextDrawImage(aCGContext, inflatedDrawRect, bitmap);
1305     CGContextSetCTM(aCGContext, ctm);
1307     CGImageRelease(bitmap);
1308     CGContextRelease(bitmapctx);
1309   }
1311   CGContextSetCTM(aCGContext, savedCTM);
1314 static void RenderButton(CGContextRef cgContext, const HIRect& aRenderRect,
1315                          void* aData) {
1316   HIThemeButtonDrawInfo* bdi = (HIThemeButtonDrawInfo*)aData;
1317   HIThemeDrawButton(&aRenderRect, bdi, cgContext, kHIThemeOrientationNormal,
1318                     NULL);
1321 static ThemeDrawState ToThemeDrawState(
1322     const nsNativeThemeCocoa::ControlParams& aParams) {
1323   if (aParams.disabled) {
1324     return kThemeStateUnavailable;
1325   }
1326   if (aParams.pressed) {
1327     return kThemeStatePressed;
1328   }
1329   return kThemeStateActive;
1332 void nsNativeThemeCocoa::DrawHIThemeButton(
1333     CGContextRef cgContext, const HIRect& aRect, ThemeButtonKind aKind,
1334     ThemeButtonValue aValue, ThemeDrawState aState,
1335     ThemeButtonAdornment aAdornment, const ControlParams& aParams) {
1336   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1338   HIThemeButtonDrawInfo bdi;
1339   bdi.version = 0;
1340   bdi.kind = aKind;
1341   bdi.value = aValue;
1342   bdi.state = aState;
1343   bdi.adornment = aAdornment;
1345   if (aParams.focused && aParams.insideActiveWindow) {
1346     bdi.adornment |= kThemeAdornmentFocus;
1347   }
1349   RenderTransformedHIThemeControl(cgContext, aRect, RenderButton, &bdi,
1350                                   aParams.rtl);
1352 #if DRAW_IN_FRAME_DEBUG
1353   CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
1354   CGContextFillRect(cgContext, inBoxRect);
1355 #endif
1357   NS_OBJC_END_TRY_IGNORE_BLOCK;
1360 void nsNativeThemeCocoa::DrawButton(CGContextRef cgContext,
1361                                     const HIRect& inBoxRect,
1362                                     const ButtonParams& aParams) {
1363   ControlParams controlParams = aParams.controlParams;
1365   switch (aParams.button) {
1366     case ButtonType::eRegularPushButton:
1367     case ButtonType::eDefaultPushButton:
1368       DrawPushButton(cgContext, inBoxRect, aParams.button, controlParams);
1369       return;
1370     case ButtonType::eSquareBezelPushButton:
1371       DrawSquareBezelPushButton(cgContext, inBoxRect, controlParams);
1372       return;
1373     case ButtonType::eArrowButton:
1374       DrawHIThemeButton(cgContext, inBoxRect, kThemeArrowButton, kThemeButtonOn,
1375                         kThemeStateUnavailable, kThemeAdornmentArrowDownArrow,
1376                         controlParams);
1377       return;
1378     case ButtonType::eHelpButton:
1379       DrawHelpButton(cgContext, inBoxRect, controlParams);
1380       return;
1381     case ButtonType::eTreeTwistyPointingRight:
1382       DrawHIThemeButton(cgContext, inBoxRect, kThemeDisclosureButton,
1383                         kThemeDisclosureRight, ToThemeDrawState(controlParams),
1384                         kThemeAdornmentNone, controlParams);
1385       return;
1386     case ButtonType::eTreeTwistyPointingDown:
1387       DrawHIThemeButton(cgContext, inBoxRect, kThemeDisclosureButton,
1388                         kThemeDisclosureDown, ToThemeDrawState(controlParams),
1389                         kThemeAdornmentNone, controlParams);
1390       return;
1391     case ButtonType::eDisclosureButtonClosed:
1392       DrawDisclosureButton(cgContext, inBoxRect, controlParams,
1393                            NSControlStateValueOff);
1394       return;
1395     case ButtonType::eDisclosureButtonOpen:
1396       DrawDisclosureButton(cgContext, inBoxRect, controlParams,
1397                            NSControlStateValueOn);
1398       return;
1399   }
1402 nsNativeThemeCocoa::TreeHeaderCellParams
1403 nsNativeThemeCocoa::ComputeTreeHeaderCellParams(nsIFrame* aFrame,
1404                                                 ElementState aEventState) {
1405   TreeHeaderCellParams params;
1406   params.controlParams = ComputeControlParams(aFrame, aEventState);
1407   params.sortDirection = GetTreeSortDirection(aFrame);
1408   params.lastTreeHeaderCell = IsLastTreeHeaderCell(aFrame);
1409   return params;
1412 @interface NSTableHeaderCell (NSTableHeaderCell_setSortable)
1413 // This method has been present in the same form since at least macOS 10.4.
1414 - (void)_setSortable:(BOOL)arg1
1415     showSortIndicator:(BOOL)arg2
1416             ascending:(BOOL)arg3
1417              priority:(NSInteger)arg4
1418      highlightForSort:(BOOL)arg5;
1419 @end
1421 void nsNativeThemeCocoa::DrawTreeHeaderCell(
1422     CGContextRef cgContext, const HIRect& inBoxRect,
1423     const TreeHeaderCellParams& aParams) {
1424   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1426   // Without clearing the cell's title, it takes on a default value of "Field",
1427   // which is displayed underneath the title set in the front-end.
1428   NSCell* cell = (NSCell*)mTreeHeaderCell;
1429   cell.title = @"";
1431   if ([mTreeHeaderCell
1432           respondsToSelector:@selector
1433           (_setSortable:
1434               showSortIndicator:ascending:priority:highlightForSort:)]) {
1435     switch (aParams.sortDirection) {
1436       case eTreeSortDirection_Ascending:
1437         [mTreeHeaderCell _setSortable:YES
1438                     showSortIndicator:YES
1439                             ascending:YES
1440                              priority:0
1441                      highlightForSort:YES];
1442         break;
1443       case eTreeSortDirection_Descending:
1444         [mTreeHeaderCell _setSortable:YES
1445                     showSortIndicator:YES
1446                             ascending:NO
1447                              priority:0
1448                      highlightForSort:YES];
1449         break;
1450       default:
1451         // eTreeSortDirection_Natural
1452         [mTreeHeaderCell _setSortable:YES
1453                     showSortIndicator:NO
1454                             ascending:YES
1455                              priority:0
1456                      highlightForSort:NO];
1457         break;
1458     }
1459   }
1461   mTreeHeaderCell.enabled = !aParams.controlParams.disabled;
1462   mTreeHeaderCell.state =
1463       (mTreeHeaderCell.enabled && aParams.controlParams.pressed)
1464           ? NSControlStateValueOn
1465           : NSControlStateValueOff;
1467   mCellDrawView._drawingEndSeparator = !aParams.lastTreeHeaderCell;
1469   NSGraphicsContext* savedContext = NSGraphicsContext.currentContext;
1470   NSGraphicsContext.currentContext =
1471       [NSGraphicsContext graphicsContextWithCGContext:cgContext flipped:YES];
1472   DrawCellIncludingFocusRing(mTreeHeaderCell, inBoxRect, mCellDrawView);
1473   NSGraphicsContext.currentContext = savedContext;
1475 #if DRAW_IN_FRAME_DEBUG
1476   CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
1477   CGContextFillRect(cgContext, inBoxRect);
1478 #endif
1480   NS_OBJC_END_TRY_IGNORE_BLOCK;
1483 static const CellRenderSettings dropdownSettings = {
1484     {
1485         NSMakeSize(0, 16),  // mini
1486         NSMakeSize(0, 19),  // small
1487         NSMakeSize(0, 22)   // regular
1488     },
1489     {
1490         NSMakeSize(18, 0),  // mini
1491         NSMakeSize(38, 0),  // small
1492         NSMakeSize(44, 0)   // regular
1493     },
1494     {{
1495          // Leopard
1496          {1, 1, 2, 1},  // mini
1497          {3, 0, 3, 1},  // small
1498          {3, 0, 3, 0}   // regular
1499      },
1500      {
1501          // Yosemite
1502          {1, 1, 2, 1},  // mini
1503          {3, 0, 3, 1},  // small
1504          {3, 0, 3, 0}   // regular
1505      }}};
1507 static const CellRenderSettings editableMenulistSettings = {
1508     {
1509         NSMakeSize(0, 15),  // mini
1510         NSMakeSize(0, 18),  // small
1511         NSMakeSize(0, 21)   // regular
1512     },
1513     {
1514         NSMakeSize(18, 0),  // mini
1515         NSMakeSize(38, 0),  // small
1516         NSMakeSize(44, 0)   // regular
1517     },
1518     {{
1519          // Leopard
1520          {0, 0, 2, 2},  // mini
1521          {0, 0, 3, 2},  // small
1522          {0, 1, 3, 3}   // regular
1523      },
1524      {
1525          // Yosemite
1526          {0, 0, 2, 2},  // mini
1527          {0, 0, 3, 2},  // small
1528          {0, 1, 3, 3}   // regular
1529      }}};
1531 void nsNativeThemeCocoa::DrawDropdown(CGContextRef cgContext,
1532                                       const HIRect& inBoxRect,
1533                                       const DropdownParams& aParams) {
1534   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1536   [mDropdownCell setPullsDown:aParams.pullsDown];
1537   NSCell* cell =
1538       aParams.editable ? (NSCell*)mComboBoxCell : (NSCell*)mDropdownCell;
1540   ApplyControlParamsToNSCell(aParams.controlParams, cell);
1542   if (aParams.controlParams.insideActiveWindow) {
1543     [cell setControlTint:[NSColor currentControlTint]];
1544   } else {
1545     [cell setControlTint:NSClearControlTint];
1546   }
1548   const CellRenderSettings& settings =
1549       aParams.editable ? editableMenulistSettings : dropdownSettings;
1551   if (mCellDrawWindow) {
1552     mCellDrawWindow.cellsShouldLookActive =
1553         aParams.controlParams.insideActiveWindow;
1554   }
1555   DrawCellWithSnapping(cell, cgContext, inBoxRect, settings, 0.5f,
1556                        mCellDrawView, aParams.controlParams.rtl);
1558   NS_OBJC_END_TRY_IGNORE_BLOCK;
1561 static const CellRenderSettings spinnerSettings = {
1562     {
1563         NSMakeSize(11,
1564                    16),  // mini (width trimmed by 2px to reduce blank border)
1565         NSMakeSize(15, 22),  // small
1566         NSMakeSize(19, 27)   // regular
1567     },
1568     {
1569         NSMakeSize(11,
1570                    16),  // mini (width trimmed by 2px to reduce blank border)
1571         NSMakeSize(15, 22),  // small
1572         NSMakeSize(19, 27)   // regular
1573     },
1574     {{
1575          // Leopard
1576          {0, 0, 0, 0},  // mini
1577          {0, 0, 0, 0},  // small
1578          {0, 0, 0, 0}   // regular
1579      },
1580      {
1581          // Yosemite
1582          {0, 0, 0, 0},  // mini
1583          {0, 0, 0, 0},  // small
1584          {0, 0, 0, 0}   // regular
1585      }}};
1587 HIThemeButtonDrawInfo nsNativeThemeCocoa::SpinButtonDrawInfo(
1588     ThemeButtonKind aKind, const SpinButtonParams& aParams) {
1589   HIThemeButtonDrawInfo bdi;
1590   bdi.version = 0;
1591   bdi.kind = aKind;
1592   bdi.value = kThemeButtonOff;
1593   bdi.adornment = kThemeAdornmentNone;
1595   if (aParams.disabled) {
1596     bdi.state = kThemeStateUnavailable;
1597   } else if (aParams.insideActiveWindow && aParams.pressedButton) {
1598     if (*aParams.pressedButton == SpinButton::eUp) {
1599       bdi.state = kThemeStatePressedUp;
1600     } else {
1601       bdi.state = kThemeStatePressedDown;
1602     }
1603   } else {
1604     bdi.state = kThemeStateActive;
1605   }
1607   return bdi;
1610 void nsNativeThemeCocoa::DrawSpinButtons(CGContextRef cgContext,
1611                                          const HIRect& inBoxRect,
1612                                          const SpinButtonParams& aParams) {
1613   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1615   HIThemeButtonDrawInfo bdi = SpinButtonDrawInfo(kThemeIncDecButton, aParams);
1616   HIThemeDrawButton(&inBoxRect, &bdi, cgContext, HITHEME_ORIENTATION, NULL);
1618   NS_OBJC_END_TRY_IGNORE_BLOCK;
1621 void nsNativeThemeCocoa::DrawSpinButton(CGContextRef cgContext,
1622                                         const HIRect& inBoxRect,
1623                                         SpinButton aDrawnButton,
1624                                         const SpinButtonParams& aParams) {
1625   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1627   HIThemeButtonDrawInfo bdi =
1628       SpinButtonDrawInfo(kThemeIncDecButtonMini, aParams);
1630   // Cocoa only allows kThemeIncDecButton to paint the up and down spin buttons
1631   // together as a single unit (presumably because when one button is active,
1632   // the appearance of both changes (in different ways)). Here we have to paint
1633   // both buttons, using clip to hide the one we don't want to paint.
1634   HIRect drawRect = inBoxRect;
1635   drawRect.size.height *= 2;
1636   if (aDrawnButton == SpinButton::eDown) {
1637     drawRect.origin.y -= inBoxRect.size.height;
1638   }
1640   // Shift the drawing a little to the left, since cocoa paints with more
1641   // blank space around the visual buttons than we'd like:
1642   drawRect.origin.x -= 1;
1644   CGContextSaveGState(cgContext);
1645   CGContextClipToRect(cgContext, inBoxRect);
1647   HIThemeDrawButton(&drawRect, &bdi, cgContext, HITHEME_ORIENTATION, NULL);
1649   CGContextRestoreGState(cgContext);
1651   NS_OBJC_END_TRY_IGNORE_BLOCK;
1654 static const CellRenderSettings progressSettings[2][2] = {
1655     // Vertical progress bar.
1656     {// Determined settings.
1657      {{
1658           NSZeroSize,         // mini
1659           NSMakeSize(10, 0),  // small
1660           NSMakeSize(16, 0)   // regular
1661       },
1662       {NSZeroSize, NSZeroSize, NSZeroSize},
1663       {{
1664           // Leopard
1665           {0, 0, 0, 0},  // mini
1666           {1, 1, 1, 1},  // small
1667           {1, 1, 1, 1}   // regular
1668       }}},
1669      // There is no horizontal margin in regular undetermined size.
1670      {{
1671           NSZeroSize,         // mini
1672           NSMakeSize(10, 0),  // small
1673           NSMakeSize(16, 0)   // regular
1674       },
1675       {NSZeroSize, NSZeroSize, NSZeroSize},
1676       {{
1677            // Leopard
1678            {0, 0, 0, 0},  // mini
1679            {1, 1, 1, 1},  // small
1680            {1, 0, 1, 0}   // regular
1681        },
1682        {
1683            // Yosemite
1684            {0, 0, 0, 0},  // mini
1685            {1, 1, 1, 1},  // small
1686            {1, 0, 1, 0}   // regular
1687        }}}},
1688     // Horizontal progress bar.
1689     {// Determined settings.
1690      {{
1691           NSZeroSize,         // mini
1692           NSMakeSize(0, 10),  // small
1693           NSMakeSize(0, 16)   // regular
1694       },
1695       {NSZeroSize, NSZeroSize, NSZeroSize},
1696       {{
1697            // Leopard
1698            {0, 0, 0, 0},  // mini
1699            {1, 1, 1, 1},  // small
1700            {1, 1, 1, 1}   // regular
1701        },
1702        {
1703            // Yosemite
1704            {0, 0, 0, 0},  // mini
1705            {1, 1, 1, 1},  // small
1706            {1, 1, 1, 1}   // regular
1707        }}},
1708      // There is no horizontal margin in regular undetermined size.
1709      {{
1710           NSZeroSize,         // mini
1711           NSMakeSize(0, 10),  // small
1712           NSMakeSize(0, 16)   // regular
1713       },
1714       {NSZeroSize, NSZeroSize, NSZeroSize},
1715       {{
1716            // Leopard
1717            {0, 0, 0, 0},  // mini
1718            {1, 1, 1, 1},  // small
1719            {0, 1, 0, 1}   // regular
1720        },
1721        {
1722            // Yosemite
1723            {0, 0, 0, 0},  // mini
1724            {1, 1, 1, 1},  // small
1725            {0, 1, 0, 1}   // regular
1726        }}}}};
1728 nsNativeThemeCocoa::ProgressParams nsNativeThemeCocoa::ComputeProgressParams(
1729     nsIFrame* aFrame, ElementState aEventState, bool aIsHorizontal) {
1730   ProgressParams params;
1731   params.value = GetProgressValue(aFrame);
1732   params.max = GetProgressMaxValue(aFrame);
1733   params.verticalAlignFactor = VerticalAlignFactor(aFrame);
1734   params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
1735   params.indeterminate = aEventState.HasState(ElementState::INDETERMINATE);
1736   params.horizontal = aIsHorizontal;
1737   params.rtl = IsFrameRTL(aFrame);
1738   return params;
1741 void nsNativeThemeCocoa::DrawProgress(CGContextRef cgContext,
1742                                       const HIRect& inBoxRect,
1743                                       const ProgressParams& aParams) {
1744   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1746   NSProgressBarCell* cell = mProgressBarCell;
1748   [cell setValue:aParams.value];
1749   [cell setMax:aParams.max];
1750   [cell setIndeterminate:aParams.indeterminate];
1751   [cell setHorizontal:aParams.horizontal];
1752   [cell
1753       setControlTint:(aParams.insideActiveWindow ? [NSColor currentControlTint]
1754                                                  : NSClearControlTint)];
1756   if (mCellDrawWindow) {
1757     mCellDrawWindow.cellsShouldLookActive = aParams.insideActiveWindow;
1758   }
1759   DrawCellWithSnapping(
1760       cell, cgContext, inBoxRect,
1761       progressSettings[aParams.horizontal][aParams.indeterminate],
1762       aParams.verticalAlignFactor, mCellDrawView, aParams.rtl);
1764   NS_OBJC_END_TRY_IGNORE_BLOCK;
1767 static const CellRenderSettings meterSetting = {
1768     {
1769         NSMakeSize(0, 16),  // mini
1770         NSMakeSize(0, 16),  // small
1771         NSMakeSize(0, 16)   // regular
1772     },
1773     {NSZeroSize, NSZeroSize, NSZeroSize},
1774     {{
1775          // Leopard
1776          {1, 1, 1, 1},  // mini
1777          {1, 1, 1, 1},  // small
1778          {1, 1, 1, 1}   // regular
1779      },
1780      {
1781          // Yosemite
1782          {1, 1, 1, 1},  // mini
1783          {1, 1, 1, 1},  // small
1784          {1, 1, 1, 1}   // regular
1785      }}};
1787 nsNativeThemeCocoa::MeterParams nsNativeThemeCocoa::ComputeMeterParams(
1788     nsIFrame* aFrame) {
1789   nsIContent* content = aFrame->GetContent();
1790   if (!(content && content->IsHTMLElement(nsGkAtoms::meter))) {
1791     return MeterParams();
1792   }
1794   HTMLMeterElement* meterElement = static_cast<HTMLMeterElement*>(content);
1795   MeterParams params;
1796   params.value = meterElement->Value();
1797   params.min = meterElement->Min();
1798   params.max = meterElement->Max();
1799   ElementState states = meterElement->State();
1800   if (states.HasState(ElementState::SUB_OPTIMUM)) {
1801     params.optimumState = OptimumState::eSubOptimum;
1802   } else if (states.HasState(ElementState::SUB_SUB_OPTIMUM)) {
1803     params.optimumState = OptimumState::eSubSubOptimum;
1804   }
1805   params.horizontal = !IsVerticalMeter(aFrame);
1806   params.verticalAlignFactor = VerticalAlignFactor(aFrame);
1807   params.rtl = IsFrameRTL(aFrame);
1809   return params;
1812 void nsNativeThemeCocoa::DrawMeter(CGContextRef cgContext,
1813                                    const HIRect& inBoxRect,
1814                                    const MeterParams& aParams) {
1815   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK
1817   NSLevelIndicatorCell* cell = mMeterBarCell;
1819   [cell setMinValue:aParams.min];
1820   [cell setMaxValue:aParams.max];
1821   [cell setDoubleValue:aParams.value];
1823   /**
1824    * The way HTML and Cocoa defines the meter/indicator widget are different.
1825    * So, we are going to use a trick to get the Cocoa widget showing what we
1826    * are expecting: we set the warningValue or criticalValue to the current
1827    * value when we want to have the widget to be in the warning or critical
1828    * state.
1829    */
1830   switch (aParams.optimumState) {
1831     case OptimumState::eOptimum:
1832       [cell setWarningValue:aParams.max + 1];
1833       [cell setCriticalValue:aParams.max + 1];
1834       break;
1835     case OptimumState::eSubOptimum:
1836       [cell setWarningValue:aParams.value];
1837       [cell setCriticalValue:aParams.max + 1];
1838       break;
1839     case OptimumState::eSubSubOptimum:
1840       [cell setWarningValue:aParams.max + 1];
1841       [cell setCriticalValue:aParams.value];
1842       break;
1843   }
1845   HIRect rect = CGRectStandardize(inBoxRect);
1846   BOOL vertical = !aParams.horizontal;
1848   CGContextSaveGState(cgContext);
1850   if (vertical) {
1851     /**
1852      * Cocoa doesn't provide a vertical meter bar so to show one, we have to
1853      * show a rotated horizontal meter bar.
1854      * Given that we want to show a vertical meter bar, we assume that the rect
1855      * has vertical dimensions but we can't correctly draw a meter widget inside
1856      * such a rectangle so we need to inverse width and height (and re-position)
1857      * to get a rectangle with horizontal dimensions.
1858      * Finally, we want to show a vertical meter so we want to rotate the result
1859      * so it is vertical. We do that by changing the context.
1860      */
1861     CGFloat tmp = rect.size.width;
1862     rect.size.width = rect.size.height;
1863     rect.size.height = tmp;
1864     rect.origin.x += rect.size.height / 2.f - rect.size.width / 2.f;
1865     rect.origin.y += rect.size.width / 2.f - rect.size.height / 2.f;
1867     CGContextTranslateCTM(cgContext, CGRectGetMidX(rect), CGRectGetMidY(rect));
1868     CGContextRotateCTM(cgContext, -M_PI / 2.f);
1869     CGContextTranslateCTM(cgContext, -CGRectGetMidX(rect),
1870                           -CGRectGetMidY(rect));
1871   }
1873   if (mCellDrawWindow) {
1874     mCellDrawWindow.cellsShouldLookActive =
1875         YES;  // TODO: propagate correct activeness state
1876   }
1877   DrawCellWithSnapping(cell, cgContext, rect, meterSetting,
1878                        aParams.verticalAlignFactor, mCellDrawView,
1879                        !vertical && aParams.rtl);
1881   CGContextRestoreGState(cgContext);
1883   NS_OBJC_END_TRY_IGNORE_BLOCK
1886 void nsNativeThemeCocoa::DrawTabPanel(CGContextRef cgContext,
1887                                       const HIRect& inBoxRect,
1888                                       bool aIsInsideActiveWindow) {
1889   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1891   HIThemeTabPaneDrawInfo tpdi;
1893   tpdi.version = 1;
1894   tpdi.state = aIsInsideActiveWindow ? kThemeStateActive : kThemeStateInactive;
1895   tpdi.direction = kThemeTabNorth;
1896   tpdi.size = kHIThemeTabSizeNormal;
1897   tpdi.kind = kHIThemeTabKindNormal;
1899   HIThemeDrawTabPane(&inBoxRect, &tpdi, cgContext, HITHEME_ORIENTATION);
1901   NS_OBJC_END_TRY_IGNORE_BLOCK;
1904 Maybe<nsNativeThemeCocoa::ScaleParams>
1905 nsNativeThemeCocoa::ComputeHTMLScaleParams(nsIFrame* aFrame,
1906                                            ElementState aEventState) {
1907   nsRangeFrame* rangeFrame = do_QueryFrame(aFrame);
1908   if (!rangeFrame) {
1909     return Nothing();
1910   }
1912   bool isHorizontal = IsRangeHorizontal(aFrame);
1914   // ScaleParams requires integer min, max and value. This is purely for
1915   // drawing, so we normalize to a range 0-1000 here.
1916   ScaleParams params;
1917   params.value = int32_t(rangeFrame->GetValueAsFractionOfRange() * 1000);
1918   params.min = 0;
1919   params.max = 1000;
1920   params.reverse = !isHorizontal || rangeFrame->IsRightToLeft();
1921   params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
1922   params.focused = aEventState.HasState(ElementState::FOCUSRING);
1923   params.disabled = aEventState.HasState(ElementState::DISABLED);
1924   params.horizontal = isHorizontal;
1925   return Some(params);
1928 void nsNativeThemeCocoa::DrawScale(CGContextRef cgContext,
1929                                    const HIRect& inBoxRect,
1930                                    const ScaleParams& aParams) {
1931   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1933   HIThemeTrackDrawInfo tdi;
1935   tdi.version = 0;
1936   tdi.kind = kThemeMediumSlider;
1937   tdi.bounds = inBoxRect;
1938   tdi.min = aParams.min;
1939   tdi.max = aParams.max;
1940   tdi.value = aParams.value;
1941   tdi.attributes = kThemeTrackShowThumb;
1942   if (aParams.horizontal) {
1943     tdi.attributes |= kThemeTrackHorizontal;
1944   }
1945   if (aParams.reverse) {
1946     tdi.attributes |= kThemeTrackRightToLeft;
1947   }
1948   if (aParams.focused) {
1949     tdi.attributes |= kThemeTrackHasFocus;
1950   }
1951   if (aParams.disabled) {
1952     tdi.enableState = kThemeTrackDisabled;
1953   } else {
1954     tdi.enableState =
1955         aParams.insideActiveWindow ? kThemeTrackActive : kThemeTrackInactive;
1956   }
1957   tdi.trackInfo.slider.thumbDir = kThemeThumbPlain;
1958   tdi.trackInfo.slider.pressState = 0;
1960   HIThemeDrawTrack(&tdi, NULL, cgContext, HITHEME_ORIENTATION);
1962   NS_OBJC_END_TRY_IGNORE_BLOCK;
1965 nsIFrame* nsNativeThemeCocoa::SeparatorResponsibility(nsIFrame* aBefore,
1966                                                       nsIFrame* aAfter) {
1967   // Usually a separator is drawn by the segment to the right of the
1968   // separator, but pressed and selected segments have higher priority.
1969   if (!aBefore || !aAfter) return nullptr;
1970   if (IsSelectedButton(aAfter)) return aAfter;
1971   if (IsSelectedButton(aBefore) || IsPressedButton(aBefore)) return aBefore;
1972   return aAfter;
1975 static CGRect SeparatorAdjustedRect(CGRect aRect,
1976                                     nsNativeThemeCocoa::SegmentParams aParams) {
1977   // A separator between two segments should always be located in the leftmost
1978   // pixel column of the segment to the right of the separator, regardless of
1979   // who ends up drawing it.
1980   // CoreUI draws the separators inside the drawing rect.
1981   if (!aParams.atLeftEnd && !aParams.drawsLeftSeparator) {
1982     // The segment to the left of us draws the separator, so we need to make
1983     // room for it.
1984     aRect.origin.x += 1;
1985     aRect.size.width -= 1;
1986   }
1987   if (aParams.drawsRightSeparator) {
1988     // We draw the right separator, so we need to extend the draw rect into the
1989     // segment to our right.
1990     aRect.size.width += 1;
1991   }
1992   return aRect;
1995 static NSString* ToolbarButtonPosition(BOOL aIsFirst, BOOL aIsLast) {
1996   if (aIsFirst) {
1997     if (aIsLast) return @"kCUISegmentPositionOnly";
1998     return @"kCUISegmentPositionFirst";
1999   }
2000   if (aIsLast) return @"kCUISegmentPositionLast";
2001   return @"kCUISegmentPositionMiddle";
2004 struct SegmentedControlRenderSettings {
2005   const CGFloat* heights;
2006   const NSString* widgetName;
2009 static const CGFloat tabHeights[3] = {17, 20, 23};
2011 static const SegmentedControlRenderSettings tabRenderSettings = {tabHeights,
2012                                                                  @"tab"};
2014 static const CGFloat toolbarButtonHeights[3] = {15, 18, 22};
2016 static const SegmentedControlRenderSettings toolbarButtonRenderSettings = {
2017     toolbarButtonHeights, @"kCUIWidgetButtonSegmentedSCurve"};
2019 nsNativeThemeCocoa::SegmentParams nsNativeThemeCocoa::ComputeSegmentParams(
2020     nsIFrame* aFrame, ElementState aEventState, SegmentType aSegmentType) {
2021   SegmentParams params;
2022   params.segmentType = aSegmentType;
2023   params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
2024   params.pressed = IsPressedButton(aFrame);
2025   params.selected = IsSelectedButton(aFrame);
2026   params.focused = aEventState.HasState(ElementState::FOCUSRING);
2027   bool isRTL = IsFrameRTL(aFrame);
2028   nsIFrame* left = GetAdjacentSiblingFrameWithSameAppearance(aFrame, isRTL);
2029   nsIFrame* right = GetAdjacentSiblingFrameWithSameAppearance(aFrame, !isRTL);
2030   params.atLeftEnd = !left;
2031   params.atRightEnd = !right;
2032   params.drawsLeftSeparator = SeparatorResponsibility(left, aFrame) == aFrame;
2033   params.drawsRightSeparator = SeparatorResponsibility(aFrame, right) == aFrame;
2034   params.rtl = isRTL;
2035   return params;
2038 static SegmentedControlRenderSettings RenderSettingsForSegmentType(
2039     nsNativeThemeCocoa::SegmentType aSegmentType) {
2040   switch (aSegmentType) {
2041     case nsNativeThemeCocoa::SegmentType::eToolbarButton:
2042       return toolbarButtonRenderSettings;
2043     case nsNativeThemeCocoa::SegmentType::eTab:
2044       return tabRenderSettings;
2045   }
2048 void nsNativeThemeCocoa::DrawSegment(CGContextRef cgContext,
2049                                      const HIRect& inBoxRect,
2050                                      const SegmentParams& aParams) {
2051   SegmentedControlRenderSettings renderSettings =
2052       RenderSettingsForSegmentType(aParams.segmentType);
2053   NSControlSize controlSize =
2054       FindControlSize(inBoxRect.size.height, renderSettings.heights, 4.0f);
2055   CGRect drawRect = SeparatorAdjustedRect(inBoxRect, aParams);
2057   NSDictionary* dict = @{
2058     @"widget" : renderSettings.widgetName,
2059     @"kCUIPresentationStateKey" :
2060         (aParams.insideActiveWindow ? @"kCUIPresentationStateActiveKey"
2061                                     : @"kCUIPresentationStateInactive"),
2062     @"kCUIPositionKey" :
2063         ToolbarButtonPosition(aParams.atLeftEnd, aParams.atRightEnd),
2064     @"kCUISegmentLeadingSeparatorKey" :
2065         [NSNumber numberWithBool:aParams.drawsLeftSeparator],
2066     @"kCUISegmentTrailingSeparatorKey" :
2067         [NSNumber numberWithBool:aParams.drawsRightSeparator],
2068     @"value" : [NSNumber numberWithBool:aParams.selected],
2069     @"state" : (aParams.pressed
2070                     ? @"pressed"
2071                     : (aParams.insideActiveWindow ? @"normal" : @"inactive")),
2072     @"focus" : [NSNumber numberWithBool:aParams.focused],
2073     @"size" : CUIControlSizeForCocoaSize(controlSize),
2074     @"is.flipped" : [NSNumber numberWithBool:YES],
2075     @"direction" : @"up"
2076   };
2078   RenderWithCoreUI(drawRect, cgContext, dict);
2081 void nsNativeThemeCocoa::DrawToolbar(CGContextRef cgContext,
2082                                      const CGRect& inBoxRect, bool aIsMain) {
2083   CGRect drawRect = inBoxRect;
2085   // top border
2086   drawRect.size.height = 1.0f;
2087   DrawNativeGreyColorInRect(cgContext, toolbarTopBorderGrey, drawRect, aIsMain);
2089   // background
2090   drawRect.origin.y += drawRect.size.height;
2091   drawRect.size.height = inBoxRect.size.height - 2.0f;
2092   DrawNativeGreyColorInRect(cgContext, toolbarFillGrey, drawRect, aIsMain);
2094   // bottom border
2095   drawRect.origin.y += drawRect.size.height;
2096   drawRect.size.height = 1.0f;
2097   DrawNativeGreyColorInRect(cgContext, toolbarBottomBorderGrey, drawRect,
2098                             aIsMain);
2101 static bool ToolbarCanBeUnified(const gfx::Rect& aRect, NSWindow* aWindow) {
2102   if (![aWindow isKindOfClass:[ToolbarWindow class]]) return false;
2104   ToolbarWindow* win = (ToolbarWindow*)aWindow;
2105   float unifiedToolbarHeight = [win unifiedToolbarHeight];
2106   return aRect.X() == 0 && aRect.Width() >= [win frame].size.width &&
2107          aRect.YMost() <= unifiedToolbarHeight;
2110 void nsNativeThemeCocoa::DrawStatusBar(CGContextRef cgContext,
2111                                        const HIRect& inBoxRect, bool aIsMain) {
2112   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2114   if (inBoxRect.size.height < 2.0f) return;
2116   CGContextSaveGState(cgContext);
2117   CGContextClipToRect(cgContext, inBoxRect);
2119   // kCUIWidgetWindowFrame draws a complete window frame with both title bar
2120   // and bottom bar. We only want the bottom bar, so we extend the draw rect
2121   // upwards to make space for the title bar, and then we clip it away.
2122   CGRect drawRect = inBoxRect;
2123   const int extendUpwards = 40;
2124   drawRect.origin.y -= extendUpwards;
2125   drawRect.size.height += extendUpwards;
2126   RenderWithCoreUI(
2127       drawRect, cgContext,
2128       [NSDictionary dictionaryWithObjectsAndKeys:
2129                         @"kCUIWidgetWindowFrame", @"widget", @"regularwin",
2130                         @"windowtype", (aIsMain ? @"normal" : @"inactive"),
2131                         @"state",
2132                         [NSNumber numberWithInt:inBoxRect.size.height],
2133                         @"kCUIWindowFrameBottomBarHeightKey",
2134                         [NSNumber numberWithBool:YES],
2135                         @"kCUIWindowFrameDrawBottomBarSeparatorKey",
2136                         [NSNumber numberWithBool:YES], @"is.flipped", nil]);
2138   CGContextRestoreGState(cgContext);
2140   NS_OBJC_END_TRY_IGNORE_BLOCK;
2143 void nsNativeThemeCocoa::DrawMultilineTextField(CGContextRef cgContext,
2144                                                 const CGRect& inBoxRect,
2145                                                 bool aIsFocused) {
2146   mTextFieldCell.enabled = YES;
2147   mTextFieldCell.showsFirstResponder = aIsFocused;
2149   if (mCellDrawWindow) {
2150     mCellDrawWindow.cellsShouldLookActive = YES;
2151   }
2153   // DrawCellIncludingFocusRing draws into the current NSGraphicsContext, so do
2154   // the usual save+restore dance.
2155   NSGraphicsContext* savedContext = NSGraphicsContext.currentContext;
2156   NSGraphicsContext.currentContext =
2157       [NSGraphicsContext graphicsContextWithCGContext:cgContext flipped:YES];
2158   DrawCellIncludingFocusRing(mTextFieldCell, inBoxRect, mCellDrawView);
2159   NSGraphicsContext.currentContext = savedContext;
2162 static bool IsHiDPIContext(nsDeviceContext* aContext) {
2163   return AppUnitsPerCSSPixel() >=
2164          2 * aContext->AppUnitsPerDevPixelAtUnitFullZoom();
2167 Maybe<nsNativeThemeCocoa::WidgetInfo> nsNativeThemeCocoa::ComputeWidgetInfo(
2168     nsIFrame* aFrame, StyleAppearance aAppearance, const nsRect& aRect) {
2169   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
2171   // setup to draw into the correct port
2172   int32_t p2a = aFrame->PresContext()->AppUnitsPerDevPixel();
2174   gfx::Rect nativeWidgetRect(aRect.x, aRect.y, aRect.width, aRect.height);
2175   nativeWidgetRect.Scale(1.0 / gfxFloat(p2a));
2176   float originalHeight = nativeWidgetRect.Height();
2177   nativeWidgetRect.Round();
2178   if (nativeWidgetRect.IsEmpty()) {
2179     return Nothing();  // Don't attempt to draw invisible widgets.
2180   }
2182   bool hidpi = IsHiDPIContext(aFrame->PresContext()->DeviceContext());
2183   if (hidpi) {
2184     // Use high-resolution drawing.
2185     nativeWidgetRect.Scale(0.5f);
2186     originalHeight *= 0.5f;
2187   }
2189   ElementState elementState = GetContentState(aFrame, aAppearance);
2191   switch (aAppearance) {
2192     case StyleAppearance::Menupopup:
2193       return Nothing();
2195     case StyleAppearance::Tooltip:
2196       return Nothing();
2198     case StyleAppearance::Checkbox:
2199     case StyleAppearance::Radio: {
2200       bool isCheckbox = aAppearance == StyleAppearance::Checkbox;
2202       CheckboxOrRadioParams params;
2203       params.state = CheckboxOrRadioState::eOff;
2204       if (isCheckbox && elementState.HasState(ElementState::INDETERMINATE)) {
2205         params.state = CheckboxOrRadioState::eIndeterminate;
2206       } else if (elementState.HasState(ElementState::CHECKED)) {
2207         params.state = CheckboxOrRadioState::eOn;
2208       }
2209       params.controlParams = ComputeControlParams(aFrame, elementState);
2210       params.verticalAlignFactor = VerticalAlignFactor(aFrame);
2211       if (isCheckbox) {
2212         return Some(WidgetInfo::Checkbox(params));
2213       }
2214       return Some(WidgetInfo::Radio(params));
2215     }
2217     case StyleAppearance::Button:
2218       if (IsDefaultButton(aFrame)) {
2219         // Check whether the default button is in a document that does not
2220         // match the :-moz-window-inactive pseudoclass. This activeness check
2221         // is different from the other "active window" checks in this file
2222         // because we absolutely need the button's default button appearance to
2223         // be in sync with its text color, and the text color is changed by
2224         // such a :-moz-window-inactive rule. (That's because on 10.10 and up,
2225         // default buttons in active windows have blue background and white
2226         // text, and default buttons in inactive windows have white background
2227         // and black text.)
2228         DocumentState docState = aFrame->PresContext()->Document()->State();
2229         ControlParams params = ComputeControlParams(aFrame, elementState);
2230         params.insideActiveWindow =
2231             !docState.HasState(DocumentState::WINDOW_INACTIVE);
2232         return Some(WidgetInfo::Button(
2233             ButtonParams{params, ButtonType::eDefaultPushButton}));
2234       }
2235       if (IsButtonTypeMenu(aFrame)) {
2236         ControlParams controlParams =
2237             ComputeControlParams(aFrame, elementState);
2238         controlParams.pressed = IsOpenButton(aFrame);
2239         DropdownParams params;
2240         params.controlParams = controlParams;
2241         params.pullsDown = true;
2242         params.editable = false;
2243         return Some(WidgetInfo::Dropdown(params));
2244       }
2245       if (originalHeight > DO_SQUARE_BUTTON_HEIGHT) {
2246         // If the button is tall enough, draw the square button style so that
2247         // buttons with non-standard content look good. Otherwise draw normal
2248         // rounded aqua buttons.
2249         // This comparison is done based on the height that is calculated
2250         // without the top, because the snapped height can be affected by the
2251         // top of the rect and that may result in different height depending on
2252         // the top value.
2253         return Some(WidgetInfo::Button(
2254             ButtonParams{ComputeControlParams(aFrame, elementState),
2255                          ButtonType::eSquareBezelPushButton}));
2256       }
2257       return Some(WidgetInfo::Button(
2258           ButtonParams{ComputeControlParams(aFrame, elementState),
2259                        ButtonType::eRegularPushButton}));
2261     case StyleAppearance::MozMacHelpButton:
2262       return Some(WidgetInfo::Button(
2263           ButtonParams{ComputeControlParams(aFrame, elementState),
2264                        ButtonType::eHelpButton}));
2266     case StyleAppearance::MozMacDisclosureButtonOpen:
2267     case StyleAppearance::MozMacDisclosureButtonClosed: {
2268       ButtonType buttonType =
2269           (aAppearance == StyleAppearance::MozMacDisclosureButtonClosed)
2270               ? ButtonType::eDisclosureButtonClosed
2271               : ButtonType::eDisclosureButtonOpen;
2272       return Some(WidgetInfo::Button(ButtonParams{
2273           ComputeControlParams(aFrame, elementState), buttonType}));
2274     }
2276     case StyleAppearance::Spinner: {
2277       bool isSpinner = (aAppearance == StyleAppearance::Spinner);
2278       nsIContent* content = aFrame->GetContent();
2279       if (isSpinner && content->IsHTMLElement()) {
2280         // In HTML the theming for the spin buttons is drawn individually into
2281         // their own backgrounds instead of being drawn into the background of
2282         // their spinner parent as it is for XUL.
2283         break;
2284       }
2285       SpinButtonParams params;
2286       if (content->IsElement()) {
2287         if (content->AsElement()->AttrValueIs(
2288                 kNameSpaceID_None, nsGkAtoms::state, u"up"_ns, eCaseMatters)) {
2289           params.pressedButton = Some(SpinButton::eUp);
2290         } else if (content->AsElement()->AttrValueIs(
2291                        kNameSpaceID_None, nsGkAtoms::state, u"down"_ns,
2292                        eCaseMatters)) {
2293           params.pressedButton = Some(SpinButton::eDown);
2294         }
2295       }
2296       params.disabled = elementState.HasState(ElementState::DISABLED);
2297       params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
2299       return Some(WidgetInfo::SpinButtons(params));
2300     }
2302     case StyleAppearance::SpinnerUpbutton:
2303     case StyleAppearance::SpinnerDownbutton: {
2304       nsNumberControlFrame* numberControlFrame =
2305           nsNumberControlFrame::GetNumberControlFrameForSpinButton(aFrame);
2306       if (numberControlFrame) {
2307         SpinButtonParams params;
2308         if (numberControlFrame->SpinnerUpButtonIsDepressed()) {
2309           params.pressedButton = Some(SpinButton::eUp);
2310         } else if (numberControlFrame->SpinnerDownButtonIsDepressed()) {
2311           params.pressedButton = Some(SpinButton::eDown);
2312         }
2313         params.disabled = elementState.HasState(ElementState::DISABLED);
2314         params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
2315         if (aAppearance == StyleAppearance::SpinnerUpbutton) {
2316           return Some(WidgetInfo::SpinButtonUp(params));
2317         }
2318         return Some(WidgetInfo::SpinButtonDown(params));
2319       }
2320     } break;
2322     case StyleAppearance::Toolbarbutton: {
2323       SegmentParams params = ComputeSegmentParams(aFrame, elementState,
2324                                                   SegmentType::eToolbarButton);
2325       params.insideActiveWindow = [NativeWindowForFrame(aFrame) isMainWindow];
2326       return Some(WidgetInfo::Segment(params));
2327     }
2329     case StyleAppearance::Separator:
2330       return Some(WidgetInfo::Separator());
2332     case StyleAppearance::Toolbar: {
2333       NSWindow* win = NativeWindowForFrame(aFrame);
2334       bool isMain = [win isMainWindow];
2335       if (ToolbarCanBeUnified(nativeWidgetRect, win)) {
2336         // Unified toolbars are drawn similar to vibrancy; we communicate their
2337         // extents via the theme geometry mechanism and then place native views
2338         // under Gecko's rendering. So Gecko just needs to be transparent in the
2339         // place where the toolbar should be visible.
2340         return Nothing();
2341       }
2342       return Some(WidgetInfo::Toolbar(isMain));
2343     }
2345     case StyleAppearance::MozWindowTitlebar: {
2346       return Nothing();
2347     }
2349     case StyleAppearance::Statusbar:
2350       return Some(WidgetInfo::StatusBar(IsActive(aFrame, YES)));
2352     case StyleAppearance::MenulistButton:
2353     case StyleAppearance::Menulist: {
2354       ControlParams controlParams = ComputeControlParams(aFrame, elementState);
2355       controlParams.pressed = IsOpenButton(aFrame);
2356       DropdownParams params;
2357       params.controlParams = controlParams;
2358       params.pullsDown = false;
2359       params.editable = false;
2360       return Some(WidgetInfo::Dropdown(params));
2361     }
2363     case StyleAppearance::MozMenulistArrowButton:
2364       return Some(WidgetInfo::Button(
2365           ButtonParams{ComputeControlParams(aFrame, elementState),
2366                        ButtonType::eArrowButton}));
2368     case StyleAppearance::Textfield:
2369     case StyleAppearance::NumberInput:
2370       return Some(
2371           WidgetInfo::TextField(ComputeTextFieldParams(aFrame, elementState)));
2373     case StyleAppearance::Searchfield:
2374       return Some(WidgetInfo::SearchField(
2375           ComputeTextFieldParams(aFrame, elementState)));
2377     case StyleAppearance::ProgressBar: {
2378       if (elementState.HasState(ElementState::INDETERMINATE)) {
2379         if (!QueueAnimatedContentForRefresh(aFrame->GetContent(), 30)) {
2380           NS_WARNING("Unable to animate progressbar!");
2381         }
2382       }
2383       return Some(WidgetInfo::ProgressBar(ComputeProgressParams(
2384           aFrame, elementState, !IsVerticalProgress(aFrame))));
2385     }
2387     case StyleAppearance::Meter:
2388       return Some(WidgetInfo::Meter(ComputeMeterParams(aFrame)));
2390     case StyleAppearance::Progresschunk:
2391     case StyleAppearance::Meterchunk:
2392       // Do nothing: progress and meter bars cases will draw chunks.
2393       break;
2395     case StyleAppearance::Treetwisty:
2396       return Some(WidgetInfo::Button(
2397           ButtonParams{ComputeControlParams(aFrame, elementState),
2398                        ButtonType::eTreeTwistyPointingRight}));
2400     case StyleAppearance::Treetwistyopen:
2401       return Some(WidgetInfo::Button(
2402           ButtonParams{ComputeControlParams(aFrame, elementState),
2403                        ButtonType::eTreeTwistyPointingDown}));
2405     case StyleAppearance::Treeheadercell:
2406       return Some(WidgetInfo::TreeHeaderCell(
2407           ComputeTreeHeaderCellParams(aFrame, elementState)));
2409     case StyleAppearance::Treeitem:
2410     case StyleAppearance::Treeview:
2411       return Some(WidgetInfo::ColorFill(sRGBColor(1.0, 1.0, 1.0, 1.0)));
2413     case StyleAppearance::Treeheader:
2414       // do nothing, taken care of by individual header cells
2415     case StyleAppearance::Treeline:
2416       // do nothing, these lines don't exist on macos
2417       break;
2419     case StyleAppearance::Range: {
2420       Maybe<ScaleParams> params = ComputeHTMLScaleParams(aFrame, elementState);
2421       if (params) {
2422         return Some(WidgetInfo::Scale(*params));
2423       }
2424       break;
2425     }
2427     case StyleAppearance::Textarea:
2428       return Some(WidgetInfo::MultilineTextField(
2429           elementState.HasState(ElementState::FOCUS)));
2431     case StyleAppearance::Listbox:
2432       return Some(WidgetInfo::ListBox());
2434     case StyleAppearance::Tab: {
2435       SegmentParams params =
2436           ComputeSegmentParams(aFrame, elementState, SegmentType::eTab);
2437       params.pressed = params.pressed && !params.selected;
2438       return Some(WidgetInfo::Segment(params));
2439     }
2441     case StyleAppearance::Tabpanels:
2442       return Some(WidgetInfo::TabPanel(FrameIsInActiveWindow(aFrame)));
2444     default:
2445       break;
2446   }
2448   return Nothing();
2450   NS_OBJC_END_TRY_BLOCK_RETURN(Nothing());
2453 NS_IMETHODIMP
2454 nsNativeThemeCocoa::DrawWidgetBackground(gfxContext* aContext, nsIFrame* aFrame,
2455                                          StyleAppearance aAppearance,
2456                                          const nsRect& aRect,
2457                                          const nsRect& aDirtyRect,
2458                                          DrawOverflow aDrawOverflow) {
2459   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
2461   if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) {
2462     return ThemeCocoa::DrawWidgetBackground(aContext, aFrame, aAppearance,
2463                                             aRect, aDirtyRect, aDrawOverflow);
2464   }
2466   Maybe<WidgetInfo> widgetInfo = ComputeWidgetInfo(aFrame, aAppearance, aRect);
2468   if (!widgetInfo) {
2469     return NS_OK;
2470   }
2472   int32_t p2a = aFrame->PresContext()->AppUnitsPerDevPixel();
2474   gfx::Rect nativeWidgetRect = NSRectToRect(aRect, p2a);
2475   nativeWidgetRect.Round();
2477   bool hidpi = IsHiDPIContext(aFrame->PresContext()->DeviceContext());
2479   auto colorScheme = LookAndFeel::ColorSchemeForFrame(aFrame);
2481   RenderWidget(*widgetInfo, colorScheme, *aContext->GetDrawTarget(),
2482                nativeWidgetRect, NSRectToRect(aDirtyRect, p2a),
2483                hidpi ? 2.0f : 1.0f);
2485   return NS_OK;
2487   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
2490 void nsNativeThemeCocoa::RenderWidget(const WidgetInfo& aWidgetInfo,
2491                                       LookAndFeel::ColorScheme aScheme,
2492                                       DrawTarget& aDrawTarget,
2493                                       const gfx::Rect& aWidgetRect,
2494                                       const gfx::Rect& aDirtyRect,
2495                                       float aScale) {
2496   // Some of the drawing below uses NSAppearance.currentAppearance behind the
2497   // scenes. Set it to the appearance we want, the same way as
2498   // nsLookAndFeel::NativeGetColor.
2499   NSAppearance.currentAppearance = NSAppearanceForColorScheme(aScheme);
2501   // Also set the cell draw window's appearance; this is respected by
2502   // NSTextFieldCell (and its subclass NSSearchFieldCell).
2503   if (mCellDrawWindow) {
2504     mCellDrawWindow.appearance = NSAppearance.currentAppearance;
2505   }
2507   const Widget widget = aWidgetInfo.Widget();
2509   // Some widgets render using DrawTarget, and some using CGContext.
2510   switch (widget) {
2511     case Widget::eColorFill: {
2512       sRGBColor color = aWidgetInfo.Params<sRGBColor>();
2513       aDrawTarget.FillRect(aWidgetRect, ColorPattern(ToDeviceColor(color)));
2514       break;
2515     }
2516     default: {
2517       AutoRestoreTransform autoRestoreTransform(&aDrawTarget);
2518       gfx::Rect widgetRect = aWidgetRect;
2519       gfx::Rect dirtyRect = aDirtyRect;
2521       dirtyRect.Scale(1.0f / aScale);
2522       widgetRect.Scale(1.0f / aScale);
2523       aDrawTarget.SetTransform(
2524           aDrawTarget.GetTransform().PreScale(aScale, aScale));
2526       // The remaining widgets require a CGContext.
2527       CGRect macRect = CGRectMake(widgetRect.X(), widgetRect.Y(),
2528                                   widgetRect.Width(), widgetRect.Height());
2530       gfxQuartzNativeDrawing nativeDrawing(aDrawTarget, dirtyRect);
2532       CGContextRef cgContext = nativeDrawing.BeginNativeDrawing();
2533       if (cgContext == nullptr) {
2534         // The Quartz surface handles 0x0 surfaces by internally
2535         // making all operations no-ops; there's no cgcontext created for them.
2536         // Unfortunately, this means that callers that want to render
2537         // directly to the CGContext need to be aware of this quirk.
2538         return;
2539       }
2541       // Set the context's "base transform" to in order to get correctly-sized
2542       // focus rings.
2543       CGContextSetBaseCTM(cgContext,
2544                           CGAffineTransformMakeScale(aScale, aScale));
2546       switch (widget) {
2547         case Widget::eColorFill:
2548           MOZ_CRASH("already handled in outer switch");
2549           break;
2550         case Widget::eCheckbox: {
2551           CheckboxOrRadioParams params =
2552               aWidgetInfo.Params<CheckboxOrRadioParams>();
2553           DrawCheckboxOrRadio(cgContext, true, macRect, params);
2554           break;
2555         }
2556         case Widget::eRadio: {
2557           CheckboxOrRadioParams params =
2558               aWidgetInfo.Params<CheckboxOrRadioParams>();
2559           DrawCheckboxOrRadio(cgContext, false, macRect, params);
2560           break;
2561         }
2562         case Widget::eButton: {
2563           ButtonParams params = aWidgetInfo.Params<ButtonParams>();
2564           DrawButton(cgContext, macRect, params);
2565           break;
2566         }
2567         case Widget::eDropdown: {
2568           DropdownParams params = aWidgetInfo.Params<DropdownParams>();
2569           DrawDropdown(cgContext, macRect, params);
2570           break;
2571         }
2572         case Widget::eSpinButtons: {
2573           SpinButtonParams params = aWidgetInfo.Params<SpinButtonParams>();
2574           DrawSpinButtons(cgContext, macRect, params);
2575           break;
2576         }
2577         case Widget::eSpinButtonUp: {
2578           SpinButtonParams params = aWidgetInfo.Params<SpinButtonParams>();
2579           DrawSpinButton(cgContext, macRect, SpinButton::eUp, params);
2580           break;
2581         }
2582         case Widget::eSpinButtonDown: {
2583           SpinButtonParams params = aWidgetInfo.Params<SpinButtonParams>();
2584           DrawSpinButton(cgContext, macRect, SpinButton::eDown, params);
2585           break;
2586         }
2587         case Widget::eSegment: {
2588           SegmentParams params = aWidgetInfo.Params<SegmentParams>();
2589           DrawSegment(cgContext, macRect, params);
2590           break;
2591         }
2592         case Widget::eSeparator: {
2593           HIThemeSeparatorDrawInfo sdi = {0, kThemeStateActive};
2594           HIThemeDrawSeparator(&macRect, &sdi, cgContext, HITHEME_ORIENTATION);
2595           break;
2596         }
2597         case Widget::eToolbar: {
2598           bool isMain = aWidgetInfo.Params<bool>();
2599           DrawToolbar(cgContext, macRect, isMain);
2600           break;
2601         }
2602         case Widget::eStatusBar: {
2603           bool isMain = aWidgetInfo.Params<bool>();
2604           DrawStatusBar(cgContext, macRect, isMain);
2605           break;
2606         }
2607         case Widget::eGroupBox: {
2608           HIThemeGroupBoxDrawInfo gdi = {0, kThemeStateActive,
2609                                          kHIThemeGroupBoxKindPrimary};
2610           HIThemeDrawGroupBox(&macRect, &gdi, cgContext, HITHEME_ORIENTATION);
2611           break;
2612         }
2613         case Widget::eTextField: {
2614           TextFieldParams params = aWidgetInfo.Params<TextFieldParams>();
2615           DrawTextField(cgContext, macRect, params);
2616           break;
2617         }
2618         case Widget::eSearchField: {
2619           TextFieldParams params = aWidgetInfo.Params<TextFieldParams>();
2620           DrawSearchField(cgContext, macRect, params);
2621           break;
2622         }
2623         case Widget::eProgressBar: {
2624           ProgressParams params = aWidgetInfo.Params<ProgressParams>();
2625           DrawProgress(cgContext, macRect, params);
2626           break;
2627         }
2628         case Widget::eMeter: {
2629           MeterParams params = aWidgetInfo.Params<MeterParams>();
2630           DrawMeter(cgContext, macRect, params);
2631           break;
2632         }
2633         case Widget::eTreeHeaderCell: {
2634           TreeHeaderCellParams params =
2635               aWidgetInfo.Params<TreeHeaderCellParams>();
2636           DrawTreeHeaderCell(cgContext, macRect, params);
2637           break;
2638         }
2639         case Widget::eScale: {
2640           ScaleParams params = aWidgetInfo.Params<ScaleParams>();
2641           DrawScale(cgContext, macRect, params);
2642           break;
2643         }
2644         case Widget::eMultilineTextField: {
2645           bool isFocused = aWidgetInfo.Params<bool>();
2646           DrawMultilineTextField(cgContext, macRect, isFocused);
2647           break;
2648         }
2649         case Widget::eListBox: {
2650           // Fill the content with the control background color.
2651           CGContextSetFillColorWithColor(
2652               cgContext, [NSColor.controlBackgroundColor CGColor]);
2653           CGContextFillRect(cgContext, macRect);
2654           // Draw the frame using kCUIWidgetScrollViewFrame. This is what
2655           // NSScrollView uses in
2656           // -[NSScrollView drawRect:] if you give it a borderType of
2657           // NSBezelBorder.
2658           RenderWithCoreUI(
2659               macRect, cgContext, @{
2660                 @"widget" : @"kCUIWidgetScrollViewFrame",
2661                 @"kCUIIsFlippedKey" : @YES,
2662                 @"kCUIVariantMetal" : @NO,
2663               });
2664           break;
2665         }
2666         case Widget::eTabPanel: {
2667           bool isInsideActiveWindow = aWidgetInfo.Params<bool>();
2668           DrawTabPanel(cgContext, macRect, isInsideActiveWindow);
2669           break;
2670         }
2671       }
2673       // Reset the base CTM.
2674       CGContextSetBaseCTM(cgContext, CGAffineTransformIdentity);
2676       nativeDrawing.EndNativeDrawing();
2677     }
2678   }
2681 bool nsNativeThemeCocoa::CreateWebRenderCommandsForWidget(
2682     mozilla::wr::DisplayListBuilder& aBuilder,
2683     mozilla::wr::IpcResourceUpdateQueue& aResources,
2684     const mozilla::layers::StackingContextHelper& aSc,
2685     mozilla::layers::RenderRootStateManager* aManager, nsIFrame* aFrame,
2686     StyleAppearance aAppearance, const nsRect& aRect) {
2687   if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) {
2688     return ThemeCocoa::CreateWebRenderCommandsForWidget(
2689         aBuilder, aResources, aSc, aManager, aFrame, aAppearance, aRect);
2690   }
2692   // This list needs to stay consistent with the list in DrawWidgetBackground.
2693   // For every switch case in DrawWidgetBackground, there are three choices:
2694   //  - If the case in DrawWidgetBackground draws nothing for the given widget
2695   //    type, then don't list it here. We will hit the "default: return true;"
2696   //    case.
2697   //  - If the case in DrawWidgetBackground draws something simple for the given
2698   //    widget type, imitate that drawing using WebRender commands.
2699   //  - If the case in DrawWidgetBackground draws something complicated for the
2700   //    given widget type, return false here.
2701   switch (aAppearance) {
2702     case StyleAppearance::Checkbox:
2703     case StyleAppearance::Radio:
2704     case StyleAppearance::Button:
2705     case StyleAppearance::MozMacHelpButton:
2706     case StyleAppearance::MozMacDisclosureButtonOpen:
2707     case StyleAppearance::MozMacDisclosureButtonClosed:
2708     case StyleAppearance::Spinner:
2709     case StyleAppearance::SpinnerUpbutton:
2710     case StyleAppearance::SpinnerDownbutton:
2711     case StyleAppearance::Toolbarbutton:
2712     case StyleAppearance::Separator:
2713     case StyleAppearance::Toolbar:
2714     case StyleAppearance::MozWindowTitlebar:
2715     case StyleAppearance::Statusbar:
2716     case StyleAppearance::Menulist:
2717     case StyleAppearance::MenulistButton:
2718     case StyleAppearance::MozMenulistArrowButton:
2719     case StyleAppearance::Textfield:
2720     case StyleAppearance::NumberInput:
2721     case StyleAppearance::Searchfield:
2722     case StyleAppearance::ProgressBar:
2723     case StyleAppearance::Meter:
2724     case StyleAppearance::Treeheadercell:
2725     case StyleAppearance::Treetwisty:
2726     case StyleAppearance::Treetwistyopen:
2727     case StyleAppearance::Treeitem:
2728     case StyleAppearance::Treeview:
2729     case StyleAppearance::Range:
2730       return false;
2732     case StyleAppearance::Textarea:
2733     case StyleAppearance::Listbox:
2734     case StyleAppearance::Tab:
2735     case StyleAppearance::Tabpanels:
2736       return false;
2738     default:
2739       return true;
2740   }
2743 LayoutDeviceIntMargin nsNativeThemeCocoa::DirectionAwareMargin(
2744     const LayoutDeviceIntMargin& aMargin, nsIFrame* aFrame) {
2745   // Assuming aMargin was originally specified for a horizontal LTR context,
2746   // reinterpret the values as logical, and then map to physical coords
2747   // according to aFrame's actual writing mode.
2748   WritingMode wm = aFrame->GetWritingMode();
2749   nsMargin m = LogicalMargin(wm, aMargin.top, aMargin.right, aMargin.bottom,
2750                              aMargin.left)
2751                    .GetPhysicalMargin(wm);
2752   return LayoutDeviceIntMargin(m.top, m.right, m.bottom, m.left);
2755 static const LayoutDeviceIntMargin kAquaDropdownBorder(1, 22, 2, 5);
2756 static const LayoutDeviceIntMargin kAquaComboboxBorder(3, 20, 3, 4);
2757 static const LayoutDeviceIntMargin kAquaSearchfieldBorder(3, 5, 2, 19);
2758 static const LayoutDeviceIntMargin kAquaSearchfieldBorderBigSur(5, 5, 4, 26);
2760 LayoutDeviceIntMargin nsNativeThemeCocoa::GetWidgetBorder(
2761     nsDeviceContext* aContext, nsIFrame* aFrame, StyleAppearance aAppearance) {
2762   if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) {
2763     return Theme::GetWidgetBorder(aContext, aFrame, aAppearance);
2764   }
2766   LayoutDeviceIntMargin result;
2768   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
2769   switch (aAppearance) {
2770     case StyleAppearance::Button: {
2771       if (IsButtonTypeMenu(aFrame)) {
2772         result = DirectionAwareMargin(kAquaDropdownBorder, aFrame);
2773       } else {
2774         result =
2775             DirectionAwareMargin(LayoutDeviceIntMargin(1, 7, 3, 7), aFrame);
2776       }
2777       break;
2778     }
2780     case StyleAppearance::Toolbarbutton: {
2781       result = DirectionAwareMargin(LayoutDeviceIntMargin(1, 4, 1, 4), aFrame);
2782       break;
2783     }
2785     case StyleAppearance::Checkbox:
2786     case StyleAppearance::Radio: {
2787       // nsCheckboxRadioFrame::GetIntrinsicWidth and
2788       // nsCheckboxRadioFrame::GetIntrinsicHeight assume a border width of 2px.
2789       result.SizeTo(2, 2, 2, 2);
2790       break;
2791     }
2793     case StyleAppearance::Menulist:
2794     case StyleAppearance::MenulistButton:
2795     case StyleAppearance::MozMenulistArrowButton:
2796       result = DirectionAwareMargin(kAquaDropdownBorder, aFrame);
2797       break;
2799     case StyleAppearance::NumberInput:
2800     case StyleAppearance::Textfield: {
2801       SInt32 frameOutset = 0;
2802       ::GetThemeMetric(kThemeMetricEditTextFrameOutset, &frameOutset);
2804       SInt32 textPadding = 0;
2805       ::GetThemeMetric(kThemeMetricEditTextWhitespace, &textPadding);
2807       frameOutset += textPadding;
2809       result.SizeTo(frameOutset, frameOutset, frameOutset, frameOutset);
2810       break;
2811     }
2813     case StyleAppearance::Textarea:
2814       result.SizeTo(1, 1, 1, 1);
2815       break;
2817     case StyleAppearance::Searchfield: {
2818       auto border = nsCocoaFeatures::OnBigSurOrLater()
2819                         ? kAquaSearchfieldBorderBigSur
2820                         : kAquaSearchfieldBorder;
2821       result = DirectionAwareMargin(border, aFrame);
2822       break;
2823     }
2825     case StyleAppearance::Listbox: {
2826       SInt32 frameOutset = 0;
2827       ::GetThemeMetric(kThemeMetricListBoxFrameOutset, &frameOutset);
2828       result.SizeTo(frameOutset, frameOutset, frameOutset, frameOutset);
2829       break;
2830     }
2832     case StyleAppearance::Statusbar:
2833       result.SizeTo(1, 0, 0, 0);
2834       break;
2836     default:
2837       break;
2838   }
2840   if (IsHiDPIContext(aContext)) {
2841     result = result + result;  // doubled
2842   }
2844   NS_OBJC_END_TRY_BLOCK_RETURN(result);
2847 // Return false here to indicate that CSS padding values should be used. There
2848 // is no reason to make a distinction between padding and border values, just
2849 // specify whatever values you want in GetWidgetBorder and only use this to
2850 // return true if you want to override CSS padding values.
2851 bool nsNativeThemeCocoa::GetWidgetPadding(nsDeviceContext* aContext,
2852                                           nsIFrame* aFrame,
2853                                           StyleAppearance aAppearance,
2854                                           LayoutDeviceIntMargin* aResult) {
2855   if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) {
2856     return Theme::GetWidgetPadding(aContext, aFrame, aAppearance, aResult);
2857   }
2859   // We don't want CSS padding being used for certain widgets.
2860   // See bug 381639 for an example of why.
2861   switch (aAppearance) {
2862     // Radios and checkboxes return a fixed size in GetMinimumWidgetSize
2863     // and have a meaningful baseline, so they can't have
2864     // author-specified padding.
2865     case StyleAppearance::Checkbox:
2866     case StyleAppearance::Radio:
2867       aResult->SizeTo(0, 0, 0, 0);
2868       return true;
2870     case StyleAppearance::Searchfield:
2871       if (nsCocoaFeatures::OnBigSurOrLater()) {
2872         return true;
2873       }
2874       break;
2876     default:
2877       break;
2878   }
2879   return false;
2882 bool nsNativeThemeCocoa::GetWidgetOverflow(nsDeviceContext* aContext,
2883                                            nsIFrame* aFrame,
2884                                            StyleAppearance aAppearance,
2885                                            nsRect* aOverflowRect) {
2886   if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) {
2887     return ThemeCocoa::GetWidgetOverflow(aContext, aFrame, aAppearance,
2888                                          aOverflowRect);
2889   }
2890   nsIntMargin overflow;
2891   switch (aAppearance) {
2892     case StyleAppearance::Button:
2893     case StyleAppearance::MozMacDisclosureButtonOpen:
2894     case StyleAppearance::MozMacDisclosureButtonClosed:
2895     case StyleAppearance::MozMacHelpButton:
2896     case StyleAppearance::Toolbarbutton:
2897     case StyleAppearance::NumberInput:
2898     case StyleAppearance::Textfield:
2899     case StyleAppearance::Textarea:
2900     case StyleAppearance::Searchfield:
2901     case StyleAppearance::Listbox:
2902     case StyleAppearance::Menulist:
2903     case StyleAppearance::MenulistButton:
2904     case StyleAppearance::MozMenulistArrowButton:
2905     case StyleAppearance::Checkbox:
2906     case StyleAppearance::Radio:
2907     case StyleAppearance::Tab: {
2908       overflow.SizeTo(static_cast<int32_t>(kMaxFocusRingWidth),
2909                       static_cast<int32_t>(kMaxFocusRingWidth),
2910                       static_cast<int32_t>(kMaxFocusRingWidth),
2911                       static_cast<int32_t>(kMaxFocusRingWidth));
2912       break;
2913     }
2914     case StyleAppearance::ProgressBar: {
2915       // Progress bars draw a 2 pixel white shadow under their progress
2916       // indicators.
2917       overflow.bottom = 2;
2918       break;
2919     }
2920     case StyleAppearance::Meter: {
2921       // Meter bars overflow their boxes by about 2 pixels.
2922       overflow.SizeTo(2, 2, 2, 2);
2923       break;
2924     }
2925     default:
2926       break;
2927   }
2929   if (IsHiDPIContext(aContext)) {
2930     // Double the number of device pixels.
2931     overflow += overflow;
2932   }
2934   if (overflow != nsIntMargin()) {
2935     int32_t p2a = aFrame->PresContext()->AppUnitsPerDevPixel();
2936     aOverflowRect->Inflate(nsMargin(NSIntPixelsToAppUnits(overflow.top, p2a),
2937                                     NSIntPixelsToAppUnits(overflow.right, p2a),
2938                                     NSIntPixelsToAppUnits(overflow.bottom, p2a),
2939                                     NSIntPixelsToAppUnits(overflow.left, p2a)));
2940     return true;
2941   }
2943   return false;
2946 LayoutDeviceIntSize nsNativeThemeCocoa::GetMinimumWidgetSize(
2947     nsPresContext* aPresContext, nsIFrame* aFrame,
2948     StyleAppearance aAppearance) {
2949   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
2951   if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) {
2952     return ThemeCocoa::GetMinimumWidgetSize(aPresContext, aFrame, aAppearance);
2953   }
2955   LayoutDeviceIntSize result;
2956   switch (aAppearance) {
2957     case StyleAppearance::Button: {
2958       result.SizeTo(pushButtonSettings.minimumSizes[miniControlSize].width,
2959                     pushButtonSettings.naturalSizes[miniControlSize].height);
2960       break;
2961     }
2963     case StyleAppearance::MozMacDisclosureButtonOpen:
2964     case StyleAppearance::MozMacDisclosureButtonClosed: {
2965       result.SizeTo(kDisclosureButtonSize.width, kDisclosureButtonSize.height);
2966       break;
2967     }
2969     case StyleAppearance::MozMacHelpButton: {
2970       result.SizeTo(kHelpButtonSize.width, kHelpButtonSize.height);
2971       break;
2972     }
2974     case StyleAppearance::Toolbarbutton: {
2975       result.SizeTo(0, toolbarButtonHeights[miniControlSize]);
2976       break;
2977     }
2979     case StyleAppearance::Spinner:
2980     case StyleAppearance::SpinnerUpbutton:
2981     case StyleAppearance::SpinnerDownbutton: {
2982       SInt32 buttonHeight = 0, buttonWidth = 0;
2983       if (aFrame->GetContent()->IsXULElement()) {
2984         ::GetThemeMetric(kThemeMetricLittleArrowsWidth, &buttonWidth);
2985         ::GetThemeMetric(kThemeMetricLittleArrowsHeight, &buttonHeight);
2986       } else {
2987         NSSize size =
2988             spinnerSettings
2989                 .minimumSizes[EnumSizeForCocoaSize(NSControlSizeMini)];
2990         buttonWidth = size.width;
2991         buttonHeight = size.height;
2992         if (aAppearance != StyleAppearance::Spinner) {
2993           // the buttons are half the height of the spinner
2994           buttonHeight /= 2;
2995         }
2996       }
2997       result.SizeTo(buttonWidth, buttonHeight);
2998       break;
2999     }
3001     case StyleAppearance::Menulist:
3002     case StyleAppearance::MenulistButton: {
3003       SInt32 popupHeight = 0;
3004       ::GetThemeMetric(kThemeMetricPopupButtonHeight, &popupHeight);
3005       result.SizeTo(0, popupHeight);
3006       break;
3007     }
3009     case StyleAppearance::NumberInput:
3010     case StyleAppearance::Textfield:
3011     case StyleAppearance::Textarea:
3012     case StyleAppearance::Searchfield: {
3013       // at minimum, we should be tall enough for 9pt text.
3014       // I'm using hardcoded values here because the appearance manager
3015       // values for the frame size are incorrect.
3016       result.SizeTo(0, (2 + 2) /* top */ + 9 + (1 + 1) /* bottom */);
3017       break;
3018     }
3020     case StyleAppearance::MozWindowButtonBox: {
3021       NSSize size = WindowButtonsSize(aFrame);
3022       result.SizeTo(size.width, size.height);
3023       break;
3024     }
3026     case StyleAppearance::ProgressBar: {
3027       SInt32 barHeight = 0;
3028       ::GetThemeMetric(kThemeMetricNormalProgressBarThickness, &barHeight);
3029       result.SizeTo(0, barHeight);
3030       break;
3031     }
3033     case StyleAppearance::Separator: {
3034       result.SizeTo(1, 1);
3035       break;
3036     }
3038     case StyleAppearance::Treetwisty:
3039     case StyleAppearance::Treetwistyopen: {
3040       SInt32 twistyHeight = 0, twistyWidth = 0;
3041       ::GetThemeMetric(kThemeMetricDisclosureButtonWidth, &twistyWidth);
3042       ::GetThemeMetric(kThemeMetricDisclosureButtonHeight, &twistyHeight);
3043       result.SizeTo(twistyWidth, twistyHeight);
3044       break;
3045     }
3047     case StyleAppearance::Treeheader:
3048     case StyleAppearance::Treeheadercell: {
3049       SInt32 headerHeight = 0;
3050       ::GetThemeMetric(kThemeMetricListHeaderHeight, &headerHeight);
3051       result.SizeTo(0, headerHeight);
3052       break;
3053     }
3055     case StyleAppearance::Tab: {
3056       result.SizeTo(0, tabHeights[miniControlSize]);
3057       break;
3058     }
3060     case StyleAppearance::RangeThumb: {
3061       SInt32 width = 0;
3062       SInt32 height = 0;
3063       ::GetThemeMetric(kThemeMetricSliderMinThumbWidth, &width);
3064       ::GetThemeMetric(kThemeMetricSliderMinThumbHeight, &height);
3065       result.SizeTo(width, height);
3066       break;
3067     }
3069     case StyleAppearance::MozMenulistArrowButton:
3070       return ThemeCocoa::GetMinimumWidgetSize(aPresContext, aFrame,
3071                                               aAppearance);
3073     default:
3074       break;
3075   }
3077   if (IsHiDPIContext(aPresContext->DeviceContext())) {
3078     result = result * 2;
3079   }
3081   return result;
3083   NS_OBJC_END_TRY_BLOCK_RETURN(LayoutDeviceIntSize());
3086 NS_IMETHODIMP
3087 nsNativeThemeCocoa::WidgetStateChanged(nsIFrame* aFrame,
3088                                        StyleAppearance aAppearance,
3089                                        nsAtom* aAttribute, bool* aShouldRepaint,
3090                                        const nsAttrValue* aOldValue) {
3091   // Some widget types just never change state.
3092   switch (aAppearance) {
3093     case StyleAppearance::MozWindowTitlebar:
3094     case StyleAppearance::Toolbox:
3095     case StyleAppearance::Toolbar:
3096     case StyleAppearance::Statusbar:
3097     case StyleAppearance::Tooltip:
3098     case StyleAppearance::Tabpanels:
3099     case StyleAppearance::Tabpanel:
3100     case StyleAppearance::Menupopup:
3101     case StyleAppearance::Progresschunk:
3102     case StyleAppearance::ProgressBar:
3103     case StyleAppearance::Meter:
3104     case StyleAppearance::Meterchunk:
3105       *aShouldRepaint = false;
3106       return NS_OK;
3107     default:
3108       break;
3109   }
3111   // XXXdwh Not sure what can really be done here.  Can at least guess for
3112   // specific widgets that they're highly unlikely to have certain states.
3113   // For example, a toolbar doesn't care about any states.
3114   if (!aAttribute) {
3115     // Hover/focus/active changed.  Always repaint.
3116     *aShouldRepaint = true;
3117   } else {
3118     // Check the attribute to see if it's relevant.
3119     // disabled, checked, dlgtype, default, etc.
3120     *aShouldRepaint = false;
3121     if (aAttribute == nsGkAtoms::disabled || aAttribute == nsGkAtoms::checked ||
3122         aAttribute == nsGkAtoms::selected ||
3123         aAttribute == nsGkAtoms::visuallyselected ||
3124         aAttribute == nsGkAtoms::menuactive ||
3125         aAttribute == nsGkAtoms::sortDirection ||
3126         aAttribute == nsGkAtoms::focused || aAttribute == nsGkAtoms::_default ||
3127         aAttribute == nsGkAtoms::open || aAttribute == nsGkAtoms::hover)
3128       *aShouldRepaint = true;
3129   }
3131   return NS_OK;
3134 NS_IMETHODIMP
3135 nsNativeThemeCocoa::ThemeChanged() {
3136   // This is unimplemented because we don't care if gecko changes its theme
3137   // and macOS system appearance changes are handled by
3138   // nsLookAndFeel::SystemWantsDarkTheme.
3139   return NS_OK;
3142 bool nsNativeThemeCocoa::ThemeSupportsWidget(nsPresContext* aPresContext,
3143                                              nsIFrame* aFrame,
3144                                              StyleAppearance aAppearance) {
3145   if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) {
3146     return ThemeCocoa::ThemeSupportsWidget(aPresContext, aFrame, aAppearance);
3147   }
3148   // if this is a dropdown button in a combobox the answer is always no
3149   if (aAppearance == StyleAppearance::MozMenulistArrowButton) {
3150     nsIFrame* parentFrame = aFrame->GetParent();
3151     if (parentFrame && parentFrame->IsComboboxControlFrame()) return false;
3152   }
3154   switch (aAppearance) {
3155     // Combobox dropdowns don't support native theming in vertical mode.
3156     case StyleAppearance::Menulist:
3157     case StyleAppearance::MenulistButton:
3158     case StyleAppearance::MozMenulistArrowButton:
3159       if (aFrame && aFrame->GetWritingMode().IsVertical()) {
3160         return false;
3161       }
3162       [[fallthrough]];
3164     case StyleAppearance::Listbox:
3165     case StyleAppearance::MozWindowButtonBox:
3166     case StyleAppearance::MozWindowTitlebar:
3167     case StyleAppearance::Menupopup:
3168     case StyleAppearance::Tooltip:
3170     case StyleAppearance::Checkbox:
3171     case StyleAppearance::Radio:
3172     case StyleAppearance::MozMacHelpButton:
3173     case StyleAppearance::MozMacDisclosureButtonOpen:
3174     case StyleAppearance::MozMacDisclosureButtonClosed:
3175     case StyleAppearance::MozMacUnifiedToolbarWindow:
3176     case StyleAppearance::Button:
3177     case StyleAppearance::Toolbarbutton:
3178     case StyleAppearance::Spinner:
3179     case StyleAppearance::SpinnerUpbutton:
3180     case StyleAppearance::SpinnerDownbutton:
3181     case StyleAppearance::Toolbar:
3182     case StyleAppearance::Statusbar:
3183     case StyleAppearance::NumberInput:
3184     case StyleAppearance::Textfield:
3185     case StyleAppearance::Textarea:
3186     case StyleAppearance::Searchfield:
3187     case StyleAppearance::Toolbox:
3188     case StyleAppearance::ProgressBar:
3189     case StyleAppearance::Progresschunk:
3190     case StyleAppearance::Meter:
3191     case StyleAppearance::Meterchunk:
3192     case StyleAppearance::Separator:
3194     case StyleAppearance::Tabpanels:
3195     case StyleAppearance::Tab:
3197     case StyleAppearance::Treetwisty:
3198     case StyleAppearance::Treetwistyopen:
3199     case StyleAppearance::Treeview:
3200     case StyleAppearance::Treeheader:
3201     case StyleAppearance::Treeheadercell:
3202     case StyleAppearance::Treeitem:
3203     case StyleAppearance::Treeline:
3205     case StyleAppearance::Range:
3206       return !IsWidgetStyled(aPresContext, aFrame, aAppearance);
3208     default:
3209       break;
3210   }
3212   return false;
3215 bool nsNativeThemeCocoa::WidgetIsContainer(StyleAppearance aAppearance) {
3216   // flesh this out at some point
3217   switch (aAppearance) {
3218     case StyleAppearance::MozMenulistArrowButton:
3219     case StyleAppearance::Radio:
3220     case StyleAppearance::Checkbox:
3221     case StyleAppearance::ProgressBar:
3222     case StyleAppearance::Meter:
3223     case StyleAppearance::Range:
3224     case StyleAppearance::MozMacHelpButton:
3225     case StyleAppearance::MozMacDisclosureButtonOpen:
3226     case StyleAppearance::MozMacDisclosureButtonClosed:
3227       return false;
3228     default:
3229       break;
3230   }
3231   return true;
3234 bool nsNativeThemeCocoa::ThemeDrawsFocusForWidget(nsIFrame*,
3235                                                   StyleAppearance aAppearance) {
3236   switch (aAppearance) {
3237     case StyleAppearance::Textarea:
3238     case StyleAppearance::Textfield:
3239     case StyleAppearance::Searchfield:
3240     case StyleAppearance::NumberInput:
3241     case StyleAppearance::Menulist:
3242     case StyleAppearance::MenulistButton:
3243     case StyleAppearance::Button:
3244     case StyleAppearance::MozMacHelpButton:
3245     case StyleAppearance::MozMacDisclosureButtonOpen:
3246     case StyleAppearance::MozMacDisclosureButtonClosed:
3247     case StyleAppearance::Radio:
3248     case StyleAppearance::Range:
3249     case StyleAppearance::Checkbox:
3250       return true;
3251     default:
3252       return false;
3253   }
3256 bool nsNativeThemeCocoa::ThemeNeedsComboboxDropmarker() { return false; }
3258 bool nsNativeThemeCocoa::WidgetAppearanceDependsOnWindowFocus(
3259     StyleAppearance aAppearance) {
3260   switch (aAppearance) {
3261     case StyleAppearance::Tabpanels:
3262     case StyleAppearance::Menupopup:
3263     case StyleAppearance::Tooltip:
3264     case StyleAppearance::Spinner:
3265     case StyleAppearance::SpinnerUpbutton:
3266     case StyleAppearance::SpinnerDownbutton:
3267     case StyleAppearance::Separator:
3268     case StyleAppearance::Toolbox:
3269     case StyleAppearance::NumberInput:
3270     case StyleAppearance::Textfield:
3271     case StyleAppearance::Treeview:
3272     case StyleAppearance::Treeline:
3273     case StyleAppearance::Textarea:
3274     case StyleAppearance::Listbox:
3275       return false;
3276     default:
3277       return true;
3278   }
3281 nsITheme::ThemeGeometryType nsNativeThemeCocoa::ThemeGeometryTypeForWidget(
3282     nsIFrame* aFrame, StyleAppearance aAppearance) {
3283   switch (aAppearance) {
3284     case StyleAppearance::MozWindowTitlebar:
3285       return eThemeGeometryTypeTitlebar;
3286     case StyleAppearance::Toolbar:
3287       return eThemeGeometryTypeToolbar;
3288     case StyleAppearance::Toolbox:
3289       return eThemeGeometryTypeToolbox;
3290     case StyleAppearance::MozWindowButtonBox:
3291       return eThemeGeometryTypeWindowButtons;
3292     case StyleAppearance::Tooltip:
3293       return eThemeGeometryTypeTooltip;
3294     case StyleAppearance::Menupopup:
3295       return eThemeGeometryTypeMenu;
3296     default:
3297       return eThemeGeometryTypeUnknown;
3298   }
3301 nsITheme::Transparency nsNativeThemeCocoa::GetWidgetTransparency(
3302     nsIFrame* aFrame, StyleAppearance aAppearance) {
3303   if (IsWidgetScrollbarPart(aAppearance)) {
3304     return ThemeCocoa::GetWidgetTransparency(aFrame, aAppearance);
3305   }
3307   switch (aAppearance) {
3308     case StyleAppearance::Menupopup:
3309     case StyleAppearance::Tooltip:
3310     case StyleAppearance::Toolbar:
3311       return eTransparent;
3312     case StyleAppearance::MozMacUnifiedToolbarWindow:
3313       // We want these to be treated as opaque by Gecko. We ensure there's an
3314       // appropriate OS-level clear color to make sure that's the case.
3315       return eOpaque;
3316     case StyleAppearance::Statusbar:
3317       // Knowing that scrollbars and statusbars are opaque improves
3318       // performance, because we create layers for them.
3319       return eOpaque;
3321     default:
3322       return eUnknownTransparency;
3323   }
3326 already_AddRefed<widget::Theme> do_CreateNativeThemeDoNotUseDirectly() {
3327   return do_AddRef(new nsNativeThemeCocoa());