Bug 1890689 accumulate input in LargerReceiverBlockSizeThanDesiredBuffering GTest...
[gecko.git] / widget / cocoa / nsNativeThemeCocoa.mm
blobddb6f00ce2f1e27774b95aa775a6a21a0568cb64
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 IsActiveToolbarControl(nsIFrame* aFrame) {
403   return NativeWindowForFrame(aFrame).isMainWindow;
406 NS_IMPL_ISUPPORTS_INHERITED(nsNativeThemeCocoa, nsNativeTheme, nsITheme)
408 nsNativeThemeCocoa::nsNativeThemeCocoa() : ThemeCocoa(ScrollbarStyle()) {
409   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
411   kMaxFocusRingWidth = 7;
413   // provide a local autorelease pool, as this is called during startup
414   // before the main event-loop pool is in place
415   nsAutoreleasePool pool;
417   mDisclosureButtonCell = [[NSButtonCell alloc] initTextCell:@""];
418   [mDisclosureButtonCell setBezelStyle:NSBezelStyleRoundedDisclosure];
419   [mDisclosureButtonCell setButtonType:NSButtonTypePushOnPushOff];
420   [mDisclosureButtonCell setHighlightsBy:NSPushInCellMask];
422   mHelpButtonCell = [[NSButtonCell alloc] initTextCell:@""];
423   [mHelpButtonCell setBezelStyle:NSBezelStyleHelpButton];
424   [mHelpButtonCell setButtonType:NSButtonTypeMomentaryPushIn];
425   [mHelpButtonCell setHighlightsBy:NSPushInCellMask];
427   mPushButtonCell = [[NSButtonCell alloc] initTextCell:@""];
428   [mPushButtonCell setButtonType:NSButtonTypeMomentaryPushIn];
429   [mPushButtonCell setHighlightsBy:NSPushInCellMask];
431   mRadioButtonCell = [[NSButtonCell alloc] initTextCell:@""];
432   [mRadioButtonCell setButtonType:NSButtonTypeRadio];
434   mCheckboxCell = [[NSButtonCell alloc] initTextCell:@""];
435   [mCheckboxCell setButtonType:NSButtonTypeSwitch];
436   [mCheckboxCell setAllowsMixedState:YES];
438   mTextFieldCell = [[NSTextFieldCell alloc] initTextCell:@""];
439   [mTextFieldCell setBezeled:YES];
440   [mTextFieldCell setEditable:YES];
441   [mTextFieldCell setFocusRingType:NSFocusRingTypeExterior];
443   mSearchFieldCell = [[MOZSearchFieldCell alloc] init];
444   [mSearchFieldCell setBezelStyle:NSTextFieldRoundedBezel];
445   [mSearchFieldCell setBezeled:YES];
446   [mSearchFieldCell setEditable:YES];
447   [mSearchFieldCell setFocusRingType:NSFocusRingTypeExterior];
449   mDropdownCell = [[NSPopUpButtonCell alloc] initTextCell:@"" pullsDown:NO];
451   mComboBoxCell = [[NSComboBoxCell alloc] initTextCell:@""];
452   [mComboBoxCell setBezeled:YES];
453   [mComboBoxCell setEditable:YES];
454   [mComboBoxCell setFocusRingType:NSFocusRingTypeExterior];
456   mProgressBarCell = [[NSProgressBarCell alloc] init];
458   mMeterBarCell = [[NSLevelIndicatorCell alloc]
459       initWithLevelIndicatorStyle:NSLevelIndicatorStyleContinuousCapacity];
461   mTreeHeaderCell = [[NSTableHeaderCell alloc] init];
463   mCellDrawView = [[MOZCellDrawView alloc] init];
465   if (XRE_IsParentProcess()) {
466     // Put the cell draw view into a window that is never shown.
467     // This allows us to convince some NSCell implementations (such as
468     // NSButtonCell for default buttons) to draw with the active appearance.
469     // Another benefit of putting the draw view in a window is the fact that it
470     // lets NSTextFieldCell (and its subclass NSSearchFieldCell) inherit the
471     // current NSApplication effectiveAppearance automatically, so the field
472     // adapts to Dark Mode correctly. We don't create this window when the
473     // native theme is used in the content process because NSWindow creation
474     // runs into the sandbox and because we never run default buttons in content
475     // processes anyway.
476     mCellDrawWindow = [[MOZCellDrawWindow alloc]
477         initWithContentRect:NSZeroRect
478                   styleMask:NSWindowStyleMaskBorderless
479                     backing:NSBackingStoreBuffered
480                       defer:NO];
481     [mCellDrawWindow.contentView addSubview:mCellDrawView];
482   }
484   NS_OBJC_END_TRY_IGNORE_BLOCK;
487 nsNativeThemeCocoa::~nsNativeThemeCocoa() {
488   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
490   [mMeterBarCell release];
491   [mProgressBarCell release];
492   [mDisclosureButtonCell release];
493   [mHelpButtonCell release];
494   [mPushButtonCell release];
495   [mRadioButtonCell release];
496   [mCheckboxCell release];
497   [mTextFieldCell release];
498   [mSearchFieldCell release];
499   [mDropdownCell release];
500   [mComboBoxCell release];
501   [mTreeHeaderCell release];
502   [mCellDrawWindow release];
503   [mCellDrawView release];
505   NS_OBJC_END_TRY_IGNORE_BLOCK;
508 // Limit on the area of the target rect (in pixels^2) in
509 // DrawCellWithScaling() and DrawButton() and above which we
510 // don't draw the object into a bitmap buffer.  This is to avoid crashes in
511 // [NSGraphicsContext graphicsContextWithCGContext:flipped:] and
512 // CGContextDrawImage(), and also to avoid very poor drawing performance in
513 // CGContextDrawImage() when it scales the bitmap (particularly if xscale or
514 // yscale is less than but near 1 -- e.g. 0.9).  This value was determined
515 // by trial and error, on OS X 10.4.11 and 10.5.4, and on systems with
516 // different amounts of RAM.
517 #define BITMAP_MAX_AREA 500000
519 static int GetBackingScaleFactorForRendering(CGContextRef cgContext) {
520   CGAffineTransform ctm =
521       CGContextGetUserSpaceToDeviceSpaceTransform(cgContext);
522   CGRect transformedUserSpacePixel =
523       CGRectApplyAffineTransform(CGRectMake(0, 0, 1, 1), ctm);
524   float maxScale = std::max(fabs(transformedUserSpacePixel.size.width),
525                             fabs(transformedUserSpacePixel.size.height));
526   return maxScale > 1.0 ? 2 : 1;
530  * Draw the given NSCell into the given cgContext.
532  * destRect - the size and position of the resulting control rectangle
533  * controlSize - the NSControlSize which will be given to the NSCell before
534  *  asking it to render
535  * naturalSize - The natural dimensions of this control.
536  *  If the control rect size is not equal to either of these, a scale
537  *  will be applied to the context so that rendering the control at the
538  *  natural size will result in it filling the destRect space.
539  *  If a control has no natural dimensions in either/both axes, pass 0.0f.
540  * minimumSize - The minimum dimensions of this control.
541  *  If the control rect size is less than the minimum for a given axis,
542  *  a scale will be applied to the context so that the minimum is used
543  *  for drawing.  If a control has no minimum dimensions in either/both
544  *  axes, pass 0.0f.
545  * marginSet - an array of margins; a multidimensional array of [2][3][4],
546  *  with the first dimension being the OS version (Tiger or Leopard),
547  *  the second being the control size (mini, small, regular), and the third
548  *  being the 4 margin values (left, top, right, bottom).
549  * view - The NSView that we're drawing into. As far as I can tell, it doesn't
550  *  matter if this is really the right view; it just has to return YES when
551  *  asked for isFlipped. Otherwise we'll get drawing bugs on 10.4.
552  * mirrorHorizontal - whether to mirror the cell horizontally
553  */
554 static void DrawCellWithScaling(NSCell* cell, CGContextRef cgContext,
555                                 const HIRect& destRect,
556                                 NSControlSize controlSize, NSSize naturalSize,
557                                 NSSize minimumSize,
558                                 const float marginSet[][3][4], NSView* view,
559                                 BOOL mirrorHorizontal) {
560   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
562   NSRect drawRect = NSMakeRect(destRect.origin.x, destRect.origin.y,
563                                destRect.size.width, destRect.size.height);
565   if (naturalSize.width != 0.0f) drawRect.size.width = naturalSize.width;
566   if (naturalSize.height != 0.0f) drawRect.size.height = naturalSize.height;
568   // Keep aspect ratio when scaling if one dimension is free.
569   if (naturalSize.width == 0.0f && naturalSize.height != 0.0f)
570     drawRect.size.width =
571         destRect.size.width * naturalSize.height / destRect.size.height;
572   if (naturalSize.height == 0.0f && naturalSize.width != 0.0f)
573     drawRect.size.height =
574         destRect.size.height * naturalSize.width / destRect.size.width;
576   // Honor minimum sizes.
577   if (drawRect.size.width < minimumSize.width)
578     drawRect.size.width = minimumSize.width;
579   if (drawRect.size.height < minimumSize.height)
580     drawRect.size.height = minimumSize.height;
582   [NSGraphicsContext saveGraphicsState];
584   // Only skip the buffer if the area of our cell (in pixels^2) is too large.
585   if (drawRect.size.width * drawRect.size.height > BITMAP_MAX_AREA) {
586     // Inflate the rect Gecko gave us by the margin for the control.
587     InflateControlRect(&drawRect, controlSize, marginSet);
589     NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
590     [NSGraphicsContext
591         setCurrentContext:[NSGraphicsContext
592                               graphicsContextWithCGContext:cgContext
593                                                    flipped:YES]];
595     DrawCellIncludingFocusRing(cell, drawRect, view);
597     [NSGraphicsContext setCurrentContext:savedContext];
598   } else {
599     float w = ceil(drawRect.size.width);
600     float h = ceil(drawRect.size.height);
601     NSRect tmpRect = NSMakeRect(kMaxFocusRingWidth, kMaxFocusRingWidth, w, h);
603     // inflate to figure out the frame we need to tell NSCell to draw in, to get
604     // something that's 0,0,w,h
605     InflateControlRect(&tmpRect, controlSize, marginSet);
607     // and then, expand by kMaxFocusRingWidth size to make sure we can capture
608     // any focus ring
609     w += kMaxFocusRingWidth * 2.0;
610     h += kMaxFocusRingWidth * 2.0;
612     int backingScaleFactor = GetBackingScaleFactorForRendering(cgContext);
613     CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
614     CGContextRef ctx = CGBitmapContextCreate(
615         NULL, (int)w * backingScaleFactor, (int)h * backingScaleFactor, 8,
616         (int)w * backingScaleFactor * 4, rgb, kCGImageAlphaPremultipliedFirst);
617     CGColorSpaceRelease(rgb);
619     // We need to flip the image twice in order to avoid drawing bugs on 10.4,
620     // see bug 465069. This is the first flip transform, applied to cgContext.
621     CGContextScaleCTM(cgContext, 1.0f, -1.0f);
622     CGContextTranslateCTM(cgContext, 0.0f,
623                           -(2.0 * destRect.origin.y + destRect.size.height));
624     if (mirrorHorizontal) {
625       CGContextScaleCTM(cgContext, -1.0f, 1.0f);
626       CGContextTranslateCTM(
627           cgContext, -(2.0 * destRect.origin.x + destRect.size.width), 0.0f);
628     }
630     NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
631     [NSGraphicsContext
632         setCurrentContext:[NSGraphicsContext graphicsContextWithCGContext:ctx
633                                                                   flipped:YES]];
635     CGContextScaleCTM(ctx, backingScaleFactor, backingScaleFactor);
637     // Set the context's "base transform" to in order to get correctly-sized
638     // focus rings.
639     CGContextSetBaseCTM(ctx, CGAffineTransformMakeScale(backingScaleFactor,
640                                                         backingScaleFactor));
642     // This is the second flip transform, applied to ctx.
643     CGContextScaleCTM(ctx, 1.0f, -1.0f);
644     CGContextTranslateCTM(ctx, 0.0f,
645                           -(2.0 * tmpRect.origin.y + tmpRect.size.height));
647     DrawCellIncludingFocusRing(cell, tmpRect, view);
649     [NSGraphicsContext setCurrentContext:savedContext];
651     CGImageRef img = CGBitmapContextCreateImage(ctx);
653     // Drop the image into the original destination rectangle, scaling to fit
654     // Only scale kMaxFocusRingWidth by xscale/yscale when the resulting rect
655     // doesn't extend beyond the overflow rect
656     float xscale = destRect.size.width / drawRect.size.width;
657     float yscale = destRect.size.height / drawRect.size.height;
658     float scaledFocusRingX =
659         xscale < 1.0f ? kMaxFocusRingWidth * xscale : kMaxFocusRingWidth;
660     float scaledFocusRingY =
661         yscale < 1.0f ? kMaxFocusRingWidth * yscale : kMaxFocusRingWidth;
662     CGContextDrawImage(cgContext,
663                        CGRectMake(destRect.origin.x - scaledFocusRingX,
664                                   destRect.origin.y - scaledFocusRingY,
665                                   destRect.size.width + scaledFocusRingX * 2,
666                                   destRect.size.height + scaledFocusRingY * 2),
667                        img);
669     CGImageRelease(img);
670     CGContextRelease(ctx);
671   }
673   [NSGraphicsContext restoreGraphicsState];
675 #if DRAW_IN_FRAME_DEBUG
676   CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
677   CGContextFillRect(cgContext, destRect);
678 #endif
680   NS_OBJC_END_TRY_IGNORE_BLOCK;
683 struct CellRenderSettings {
684   // The natural dimensions of the control.
685   // If a control has no natural dimensions in either/both axes, set to 0.0f.
686   NSSize naturalSizes[3];
688   // The minimum dimensions of the control.
689   // If a control has no minimum dimensions in either/both axes, set to 0.0f.
690   NSSize minimumSizes[3];
692   // A three-dimensional array,
693   // with the first dimension being the OS version ([0] 10.6-10.9, [1] 10.10 and
694   // above), the second being the control size (mini, small, regular), and the
695   // third being the 4 margin values (left, top, right, bottom).
696   float margins[2][3][4];
700  * This is a helper method that returns the required NSControlSize given a size
701  * and the size of the three controls plus a tolerance.
702  * size - The width or the height of the element to draw.
703  * sizes - An array with the all the width/height of the element for its
704  *         different sizes.
705  * tolerance - The tolerance as passed to DrawCellWithSnapping.
706  * NOTE: returns NSControlSizeRegular if all values in 'sizes' are zero.
707  */
708 static NSControlSize FindControlSize(CGFloat size, const CGFloat* sizes,
709                                      CGFloat tolerance) {
710   for (uint32_t i = miniControlSize; i <= regularControlSize; ++i) {
711     if (sizes[i] == 0) {
712       continue;
713     }
715     CGFloat next = 0;
716     // Find next value.
717     for (uint32_t j = i + 1; j <= regularControlSize; ++j) {
718       if (sizes[j] != 0) {
719         next = sizes[j];
720         break;
721       }
722     }
724     // If it's the latest value, we pick it.
725     if (next == 0) {
726       return CocoaSizeForEnum(i);
727     }
729     if (size <= sizes[i] + tolerance && size < next) {
730       return CocoaSizeForEnum(i);
731     }
732   }
734   // If we are here, that means sizes[] was an array with only empty values
735   // or the algorithm above is wrong.
736   // The former can happen but the later would be wrong.
737   NS_ASSERTION(sizes[0] == 0 && sizes[1] == 0 && sizes[2] == 0,
738                "We found no control! We shouldn't be there!");
739   return CocoaSizeForEnum(regularControlSize);
743  * Draw the given NSCell into the given cgContext with a nice control size.
745  * This function is similar to DrawCellWithScaling, but it decides what
746  * control size to use based on the destRect's size.
747  * Scaling is only applied when the difference between the destRect's size
748  * and the next smaller natural size is greater than snapTolerance. Otherwise
749  * it snaps to the next smaller control size without scaling because unscaled
750  * controls look nicer.
751  */
752 static void DrawCellWithSnapping(NSCell* cell, CGContextRef cgContext,
753                                  const HIRect& destRect,
754                                  const CellRenderSettings settings,
755                                  float verticalAlignFactor, NSView* view,
756                                  BOOL mirrorHorizontal,
757                                  float snapTolerance = 2.0f) {
758   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
760   const float rectWidth = destRect.size.width,
761               rectHeight = destRect.size.height;
762   const NSSize* sizes = settings.naturalSizes;
763   const NSSize miniSize = sizes[EnumSizeForCocoaSize(NSControlSizeMini)];
764   const NSSize smallSize = sizes[EnumSizeForCocoaSize(NSControlSizeSmall)];
765   const NSSize regularSize = sizes[EnumSizeForCocoaSize(NSControlSizeRegular)];
767   HIRect drawRect = destRect;
769   CGFloat controlWidths[3] = {miniSize.width, smallSize.width,
770                               regularSize.width};
771   NSControlSize controlSizeX =
772       FindControlSize(rectWidth, controlWidths, snapTolerance);
773   CGFloat controlHeights[3] = {miniSize.height, smallSize.height,
774                                regularSize.height};
775   NSControlSize controlSizeY =
776       FindControlSize(rectHeight, controlHeights, snapTolerance);
778   NSControlSize controlSize = NSControlSizeRegular;
779   size_t sizeIndex = 0;
781   // At some sizes, don't scale but snap.
782   const NSControlSize smallerControlSize =
783       EnumSizeForCocoaSize(controlSizeX) < EnumSizeForCocoaSize(controlSizeY)
784           ? controlSizeX
785           : controlSizeY;
786   const size_t smallerControlSizeIndex =
787       EnumSizeForCocoaSize(smallerControlSize);
788   const NSSize size = sizes[smallerControlSizeIndex];
789   float diffWidth = size.width ? rectWidth - size.width : 0.0f;
790   float diffHeight = size.height ? rectHeight - size.height : 0.0f;
791   if (diffWidth >= 0.0f && diffHeight >= 0.0f && diffWidth <= snapTolerance &&
792       diffHeight <= snapTolerance) {
793     // Snap to the smaller control size.
794     controlSize = smallerControlSize;
795     sizeIndex = smallerControlSizeIndex;
796     MOZ_ASSERT(sizeIndex < ArrayLength(settings.naturalSizes));
798     // Resize and center the drawRect.
799     if (sizes[sizeIndex].width) {
800       drawRect.origin.x +=
801           ceil((destRect.size.width - sizes[sizeIndex].width) / 2);
802       drawRect.size.width = sizes[sizeIndex].width;
803     }
804     if (sizes[sizeIndex].height) {
805       drawRect.origin.y +=
806           floor((destRect.size.height - sizes[sizeIndex].height) *
807                 verticalAlignFactor);
808       drawRect.size.height = sizes[sizeIndex].height;
809     }
810   } else {
811     // Use the larger control size.
812     controlSize =
813         EnumSizeForCocoaSize(controlSizeX) > EnumSizeForCocoaSize(controlSizeY)
814             ? controlSizeX
815             : controlSizeY;
816     sizeIndex = EnumSizeForCocoaSize(controlSize);
817   }
819   [cell setControlSize:controlSize];
821   MOZ_ASSERT(sizeIndex < ArrayLength(settings.minimumSizes));
822   const NSSize minimumSize = settings.minimumSizes[sizeIndex];
823   DrawCellWithScaling(cell, cgContext, drawRect, controlSize, sizes[sizeIndex],
824                       minimumSize, settings.margins, view, mirrorHorizontal);
826   NS_OBJC_END_TRY_IGNORE_BLOCK;
829 @interface NSWindow (CoreUIRendererPrivate)
830 + (CUIRendererRef)coreUIRenderer;
831 @end
833 @interface NSObject (NSAppearanceCoreUIRendering)
834 - (void)_drawInRect:(CGRect)rect
835             context:(CGContextRef)cgContext
836             options:(id)options;
837 @end
839 static void RenderWithCoreUI(CGRect aRect, CGContextRef cgContext,
840                              NSDictionary* aOptions,
841                              bool aSkipAreaCheck = false) {
842   if (!aSkipAreaCheck &&
843       aRect.size.width * aRect.size.height > BITMAP_MAX_AREA) {
844     return;
845   }
847   NSAppearance* appearance = NSAppearance.currentAppearance;
848   if (appearance &&
849       [appearance respondsToSelector:@selector(_drawInRect:context:options:)]) {
850     // Render through NSAppearance on Mac OS 10.10 and up. This will call
851     // CUIDraw with a CoreUI renderer that will give us the correct 10.10
852     // style. Calling CUIDraw directly with [NSWindow coreUIRenderer] still
853     // renders 10.9-style widgets on 10.10.
854     [appearance _drawInRect:aRect context:cgContext options:aOptions];
855   } else {
856     // 10.9 and below
857     CUIRendererRef renderer =
858         [NSWindow respondsToSelector:@selector(coreUIRenderer)]
859             ? [NSWindow coreUIRenderer]
860             : nil;
861     CUIDraw(renderer, aRect, cgContext, (CFDictionaryRef)aOptions, NULL);
862   }
865 static float VerticalAlignFactor(nsIFrame* aFrame) {
866   if (!aFrame) return 0.5f;  // default: center
868   const auto& va = aFrame->StyleDisplay()->mVerticalAlign;
869   auto kw = va.IsKeyword() ? va.AsKeyword() : StyleVerticalAlignKeyword::Middle;
870   switch (kw) {
871     case StyleVerticalAlignKeyword::Top:
872     case StyleVerticalAlignKeyword::TextTop:
873       return 0.0f;
875     case StyleVerticalAlignKeyword::Sub:
876     case StyleVerticalAlignKeyword::Super:
877     case StyleVerticalAlignKeyword::Middle:
878     case StyleVerticalAlignKeyword::MozMiddleWithBaseline:
879       return 0.5f;
881     case StyleVerticalAlignKeyword::Baseline:
882     case StyleVerticalAlignKeyword::Bottom:
883     case StyleVerticalAlignKeyword::TextBottom:
884       return 1.0f;
886     default:
887       MOZ_ASSERT_UNREACHABLE("invalid vertical-align");
888       return 0.5f;
889   }
892 static void ApplyControlParamsToNSCell(
893     nsNativeThemeCocoa::ControlParams aControlParams, NSCell* aCell) {
894   [aCell setEnabled:!aControlParams.disabled];
895   [aCell setShowsFirstResponder:(aControlParams.focused &&
896                                  !aControlParams.disabled &&
897                                  aControlParams.insideActiveWindow)];
898   [aCell setHighlighted:aControlParams.pressed];
901 // These are the sizes that Gecko needs to request to draw if it wants
902 // to get a standard-sized Aqua radio button drawn. Note that the rects
903 // that draw these are actually a little bigger.
904 static const CellRenderSettings radioSettings = {
905     {
906         NSMakeSize(11, 11),  // mini
907         NSMakeSize(13, 13),  // small
908         NSMakeSize(16, 16)   // regular
909     },
910     {NSZeroSize, NSZeroSize, NSZeroSize},
911     {{
912          // Leopard
913          {0, 0, 0, 0},  // mini
914          {0, 1, 1, 1},  // small
915          {0, 0, 0, 0}   // regular
916      },
917      {
918          // Yosemite
919          {0, 0, 0, 0},  // mini
920          {1, 1, 1, 2},  // small
921          {0, 0, 0, 0}   // regular
922      }}};
924 static const CellRenderSettings checkboxSettings = {
925     {
926         NSMakeSize(11, 11),  // mini
927         NSMakeSize(13, 13),  // small
928         NSMakeSize(16, 16)   // regular
929     },
930     {NSZeroSize, NSZeroSize, NSZeroSize},
931     {{
932          // Leopard
933          {0, 1, 0, 0},  // mini
934          {0, 1, 0, 1},  // small
935          {0, 1, 0, 1}   // regular
936      },
937      {
938          // Yosemite
939          {0, 1, 0, 0},  // mini
940          {0, 1, 0, 1},  // small
941          {0, 1, 0, 1}   // regular
942      }}};
944 static NSControlStateValue CellStateForCheckboxOrRadioState(
945     nsNativeThemeCocoa::CheckboxOrRadioState aState) {
946   switch (aState) {
947     case nsNativeThemeCocoa::CheckboxOrRadioState::eOff:
948       return NSControlStateValueOff;
949     case nsNativeThemeCocoa::CheckboxOrRadioState::eOn:
950       return NSControlStateValueOn;
951     case nsNativeThemeCocoa::CheckboxOrRadioState::eIndeterminate:
952       return NSControlStateValueMixed;
953   }
956 void nsNativeThemeCocoa::DrawCheckboxOrRadio(
957     CGContextRef cgContext, bool inCheckbox, const HIRect& inBoxRect,
958     const CheckboxOrRadioParams& aParams) {
959   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
961   NSButtonCell* cell = inCheckbox ? mCheckboxCell : mRadioButtonCell;
962   ApplyControlParamsToNSCell(aParams.controlParams, cell);
964   [cell setState:CellStateForCheckboxOrRadioState(aParams.state)];
965   [cell setControlTint:(aParams.controlParams.insideActiveWindow
966                             ? [NSColor currentControlTint]
967                             : NSClearControlTint)];
969   // Ensure that the control is square.
970   float length = std::min(inBoxRect.size.width, inBoxRect.size.height);
971   HIRect drawRect = CGRectMake(
972       inBoxRect.origin.x + (int)((inBoxRect.size.width - length) / 2.0f),
973       inBoxRect.origin.y + (int)((inBoxRect.size.height - length) / 2.0f),
974       length, length);
976   if (mCellDrawWindow) {
977     mCellDrawWindow.cellsShouldLookActive =
978         aParams.controlParams.insideActiveWindow;
979   }
980   DrawCellWithSnapping(cell, cgContext, drawRect,
981                        inCheckbox ? checkboxSettings : radioSettings,
982                        aParams.verticalAlignFactor, mCellDrawView, NO);
984   NS_OBJC_END_TRY_IGNORE_BLOCK;
987 static const CellRenderSettings searchFieldSettings = {
988     {
989         NSMakeSize(0, 16),  // mini
990         NSMakeSize(0, 19),  // small
991         NSMakeSize(0, 22)   // regular
992     },
993     {
994         NSMakeSize(32, 0),  // mini
995         NSMakeSize(38, 0),  // small
996         NSMakeSize(44, 0)   // regular
997     },
998     {{
999          // Leopard
1000          {0, 0, 0, 0},  // mini
1001          {0, 0, 0, 0},  // small
1002          {0, 0, 0, 0}   // regular
1003      },
1004      {
1005          // Yosemite
1006          {0, 0, 0, 0},  // mini
1007          {0, 0, 0, 0},  // small
1008          {0, 0, 0, 0}   // regular
1009      }}};
1011 static bool IsToolbarStyleContainer(nsIFrame* aFrame) {
1012   nsIContent* content = aFrame->GetContent();
1013   if (!content) {
1014     return false;
1015   }
1017   if (content->IsAnyOfXULElements(nsGkAtoms::toolbar, nsGkAtoms::toolbox,
1018                                   nsGkAtoms::statusbar)) {
1019     return true;
1020   }
1022   switch (aFrame->StyleDisplay()->EffectiveAppearance()) {
1023     case StyleAppearance::Statusbar:
1024       return true;
1025     default:
1026       return false;
1027   }
1030 static bool IsInsideToolbar(nsIFrame* aFrame) {
1031   for (nsIFrame* frame = aFrame; frame; frame = frame->GetParent()) {
1032     if (IsToolbarStyleContainer(frame)) {
1033       return true;
1034     }
1035   }
1036   return false;
1039 nsNativeThemeCocoa::TextFieldParams nsNativeThemeCocoa::ComputeTextFieldParams(
1040     nsIFrame* aFrame, ElementState aEventState) {
1041   TextFieldParams params;
1042   params.insideToolbar = IsInsideToolbar(aFrame);
1043   params.disabled = aEventState.HasState(ElementState::DISABLED);
1045   // See ShouldUnconditionallyDrawFocusRingIfFocused.
1046   params.focused = aEventState.HasState(ElementState::FOCUS);
1048   params.rtl = IsFrameRTL(aFrame);
1049   params.verticalAlignFactor = VerticalAlignFactor(aFrame);
1050   return params;
1053 void nsNativeThemeCocoa::DrawTextField(CGContextRef cgContext,
1054                                        const HIRect& inBoxRect,
1055                                        const TextFieldParams& aParams) {
1056   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1058   NSTextFieldCell* cell = mTextFieldCell;
1059   [cell setEnabled:!aParams.disabled];
1060   [cell setShowsFirstResponder:aParams.focused];
1062   if (mCellDrawWindow) {
1063     mCellDrawWindow.cellsShouldLookActive =
1064         YES;  // TODO: propagate correct activeness state
1065   }
1066   DrawCellWithSnapping(cell, cgContext, inBoxRect, searchFieldSettings,
1067                        aParams.verticalAlignFactor, mCellDrawView, aParams.rtl);
1069   NS_OBJC_END_TRY_IGNORE_BLOCK;
1072 void nsNativeThemeCocoa::DrawSearchField(CGContextRef cgContext,
1073                                          const HIRect& inBoxRect,
1074                                          const TextFieldParams& aParams) {
1075   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1077   mSearchFieldCell.enabled = !aParams.disabled;
1078   mSearchFieldCell.showsFirstResponder = aParams.focused;
1079   mSearchFieldCell.placeholderString = @"";
1080   mSearchFieldCell.shouldUseToolbarStyle = aParams.insideToolbar;
1082   if (mCellDrawWindow) {
1083     mCellDrawWindow.cellsShouldLookActive =
1084         YES;  // TODO: propagate correct activeness state
1085   }
1086   DrawCellWithSnapping(mSearchFieldCell, cgContext, inBoxRect,
1087                        searchFieldSettings, aParams.verticalAlignFactor,
1088                        mCellDrawView, aParams.rtl);
1090   NS_OBJC_END_TRY_IGNORE_BLOCK;
1093 static bool ShouldUnconditionallyDrawFocusRingIfFocused(nsIFrame* aFrame) {
1094   // Mac always draws focus rings for textboxes and lists.
1095   switch (aFrame->StyleDisplay()->EffectiveAppearance()) {
1096     case StyleAppearance::NumberInput:
1097     case StyleAppearance::Textfield:
1098     case StyleAppearance::Textarea:
1099     case StyleAppearance::Searchfield:
1100     case StyleAppearance::Listbox:
1101       return true;
1102     default:
1103       return false;
1104   }
1107 nsNativeThemeCocoa::ControlParams nsNativeThemeCocoa::ComputeControlParams(
1108     nsIFrame* aFrame, ElementState aEventState) {
1109   ControlParams params;
1110   params.disabled = aEventState.HasState(ElementState::DISABLED);
1111   params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
1112   params.pressed =
1113       aEventState.HasAllStates(ElementState::ACTIVE | ElementState::HOVER);
1114   params.focused = aEventState.HasState(ElementState::FOCUS) &&
1115                    (aEventState.HasState(ElementState::FOCUSRING) ||
1116                     ShouldUnconditionallyDrawFocusRingIfFocused(aFrame));
1117   params.rtl = IsFrameRTL(aFrame);
1118   return params;
1121 static const NSSize kHelpButtonSize = NSMakeSize(20, 20);
1122 static const NSSize kDisclosureButtonSize = NSMakeSize(21, 21);
1124 static const CellRenderSettings pushButtonSettings = {
1125     {
1126         NSMakeSize(0, 16),  // mini
1127         NSMakeSize(0, 19),  // small
1128         NSMakeSize(0, 22)   // regular
1129     },
1130     {
1131         NSMakeSize(18, 0),  // mini
1132         NSMakeSize(26, 0),  // small
1133         NSMakeSize(30, 0)   // regular
1134     },
1135     {{
1136          // Leopard
1137          {0, 0, 0, 0},  // mini
1138          {4, 0, 4, 1},  // small
1139          {5, 0, 5, 2}   // regular
1140      },
1141      {
1142          // Yosemite
1143          {0, 0, 0, 0},  // mini
1144          {4, 0, 4, 1},  // small
1145          {5, 0, 5, 2}   // regular
1146      }}};
1148 // The height at which we start doing square buttons instead of rounded buttons
1149 // Rounded buttons look bad if drawn at a height greater than 26, so at that
1150 // point we switch over to doing square buttons which looks fine at any size.
1151 #define DO_SQUARE_BUTTON_HEIGHT 26
1153 void nsNativeThemeCocoa::DrawPushButton(CGContextRef cgContext,
1154                                         const HIRect& inBoxRect,
1155                                         ButtonType aButtonType,
1156                                         ControlParams aControlParams) {
1157   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1159   ApplyControlParamsToNSCell(aControlParams, mPushButtonCell);
1160   [mPushButtonCell setBezelStyle:NSBezelStyleRounded];
1161   mPushButtonCell.keyEquivalent =
1162       aButtonType == ButtonType::eDefaultPushButton ? @"\r" : @"";
1164   if (mCellDrawWindow) {
1165     mCellDrawWindow.cellsShouldLookActive = aControlParams.insideActiveWindow;
1166   }
1167   DrawCellWithSnapping(mPushButtonCell, cgContext, inBoxRect,
1168                        pushButtonSettings, 0.5f, mCellDrawView,
1169                        aControlParams.rtl, 1.0f);
1171   NS_OBJC_END_TRY_IGNORE_BLOCK;
1174 void nsNativeThemeCocoa::DrawSquareBezelPushButton(
1175     CGContextRef cgContext, const HIRect& inBoxRect,
1176     ControlParams aControlParams) {
1177   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1179   ApplyControlParamsToNSCell(aControlParams, mPushButtonCell);
1180   [mPushButtonCell setBezelStyle:NSBezelStyleShadowlessSquare];
1182   if (mCellDrawWindow) {
1183     mCellDrawWindow.cellsShouldLookActive = aControlParams.insideActiveWindow;
1184   }
1185   DrawCellWithScaling(mPushButtonCell, cgContext, inBoxRect,
1186                       NSControlSizeRegular, NSZeroSize, NSMakeSize(14, 0), NULL,
1187                       mCellDrawView, aControlParams.rtl);
1189   NS_OBJC_END_TRY_IGNORE_BLOCK;
1192 void nsNativeThemeCocoa::DrawHelpButton(CGContextRef cgContext,
1193                                         const HIRect& inBoxRect,
1194                                         ControlParams aControlParams) {
1195   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1197   ApplyControlParamsToNSCell(aControlParams, mHelpButtonCell);
1199   if (mCellDrawWindow) {
1200     mCellDrawWindow.cellsShouldLookActive = aControlParams.insideActiveWindow;
1201   }
1202   DrawCellWithScaling(mHelpButtonCell, cgContext, inBoxRect,
1203                       NSControlSizeRegular, NSZeroSize, kHelpButtonSize, NULL,
1204                       mCellDrawView,
1205                       false);  // Don't mirror icon in RTL.
1207   NS_OBJC_END_TRY_IGNORE_BLOCK;
1210 void nsNativeThemeCocoa::DrawDisclosureButton(CGContextRef cgContext,
1211                                               const HIRect& inBoxRect,
1212                                               ControlParams aControlParams,
1213                                               NSControlStateValue aCellState) {
1214   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1216   ApplyControlParamsToNSCell(aControlParams, mDisclosureButtonCell);
1217   [mDisclosureButtonCell setState:aCellState];
1219   if (mCellDrawWindow) {
1220     mCellDrawWindow.cellsShouldLookActive = aControlParams.insideActiveWindow;
1221   }
1222   DrawCellWithScaling(mDisclosureButtonCell, cgContext, inBoxRect,
1223                       NSControlSizeRegular, NSZeroSize, kDisclosureButtonSize,
1224                       NULL, mCellDrawView,
1225                       false);  // Don't mirror icon in RTL.
1227   NS_OBJC_END_TRY_IGNORE_BLOCK;
1230 typedef void (*RenderHIThemeControlFunction)(CGContextRef cgContext,
1231                                              const HIRect& aRenderRect,
1232                                              void* aData);
1234 static void RenderTransformedHIThemeControl(CGContextRef aCGContext,
1235                                             const HIRect& aRect,
1236                                             RenderHIThemeControlFunction aFunc,
1237                                             void* aData,
1238                                             BOOL mirrorHorizontally = NO) {
1239   CGAffineTransform savedCTM = CGContextGetCTM(aCGContext);
1240   CGContextTranslateCTM(aCGContext, aRect.origin.x, aRect.origin.y);
1242   bool drawDirect;
1243   HIRect drawRect = aRect;
1244   drawRect.origin = CGPointZero;
1246   if (!mirrorHorizontally && savedCTM.a == 1.0f && savedCTM.b == 0.0f &&
1247       savedCTM.c == 0.0f && (savedCTM.d == 1.0f || savedCTM.d == -1.0f)) {
1248     drawDirect = TRUE;
1249   } else {
1250     drawDirect = FALSE;
1251   }
1253   // Fall back to no bitmap buffer if the area of our control (in pixels^2)
1254   // is too large.
1255   if (drawDirect || (aRect.size.width * aRect.size.height > BITMAP_MAX_AREA)) {
1256     aFunc(aCGContext, drawRect, aData);
1257   } else {
1258     // Inflate the buffer to capture focus rings.
1259     int w = ceil(drawRect.size.width) + 2 * kMaxFocusRingWidth;
1260     int h = ceil(drawRect.size.height) + 2 * kMaxFocusRingWidth;
1262     int backingScaleFactor = GetBackingScaleFactorForRendering(aCGContext);
1263     CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
1264     CGContextRef bitmapctx = CGBitmapContextCreate(
1265         NULL, w * backingScaleFactor, h * backingScaleFactor, 8,
1266         w * backingScaleFactor * 4, colorSpace,
1267         kCGImageAlphaPremultipliedFirst);
1268     CGColorSpaceRelease(colorSpace);
1270     CGContextScaleCTM(bitmapctx, backingScaleFactor, backingScaleFactor);
1271     CGContextTranslateCTM(bitmapctx, kMaxFocusRingWidth, kMaxFocusRingWidth);
1273     // Set the context's "base transform" to in order to get correctly-sized
1274     // focus rings.
1275     CGContextSetBaseCTM(bitmapctx, CGAffineTransformMakeScale(
1276                                        backingScaleFactor, backingScaleFactor));
1278     // HITheme always wants to draw into a flipped context, or things
1279     // get confused.
1280     CGContextTranslateCTM(bitmapctx, 0.0f, aRect.size.height);
1281     CGContextScaleCTM(bitmapctx, 1.0f, -1.0f);
1283     aFunc(bitmapctx, drawRect, aData);
1285     CGImageRef bitmap = CGBitmapContextCreateImage(bitmapctx);
1287     CGAffineTransform ctm = CGContextGetCTM(aCGContext);
1289     // We need to unflip, so that we can do a DrawImage without getting a
1290     // flipped image.
1291     CGContextTranslateCTM(aCGContext, 0.0f, aRect.size.height);
1292     CGContextScaleCTM(aCGContext, 1.0f, -1.0f);
1294     if (mirrorHorizontally) {
1295       CGContextTranslateCTM(aCGContext, aRect.size.width, 0);
1296       CGContextScaleCTM(aCGContext, -1.0f, 1.0f);
1297     }
1299     HIRect inflatedDrawRect =
1300         CGRectMake(-kMaxFocusRingWidth, -kMaxFocusRingWidth, w, h);
1301     CGContextDrawImage(aCGContext, inflatedDrawRect, bitmap);
1303     CGContextSetCTM(aCGContext, ctm);
1305     CGImageRelease(bitmap);
1306     CGContextRelease(bitmapctx);
1307   }
1309   CGContextSetCTM(aCGContext, savedCTM);
1312 static void RenderButton(CGContextRef cgContext, const HIRect& aRenderRect,
1313                          void* aData) {
1314   HIThemeButtonDrawInfo* bdi = (HIThemeButtonDrawInfo*)aData;
1315   HIThemeDrawButton(&aRenderRect, bdi, cgContext, kHIThemeOrientationNormal,
1316                     NULL);
1319 static ThemeDrawState ToThemeDrawState(
1320     const nsNativeThemeCocoa::ControlParams& aParams) {
1321   if (aParams.disabled) {
1322     return kThemeStateUnavailable;
1323   }
1324   if (aParams.pressed) {
1325     return kThemeStatePressed;
1326   }
1327   return kThemeStateActive;
1330 void nsNativeThemeCocoa::DrawHIThemeButton(
1331     CGContextRef cgContext, const HIRect& aRect, ThemeButtonKind aKind,
1332     ThemeButtonValue aValue, ThemeDrawState aState,
1333     ThemeButtonAdornment aAdornment, const ControlParams& aParams) {
1334   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1336   HIThemeButtonDrawInfo bdi;
1337   bdi.version = 0;
1338   bdi.kind = aKind;
1339   bdi.value = aValue;
1340   bdi.state = aState;
1341   bdi.adornment = aAdornment;
1343   if (aParams.focused && aParams.insideActiveWindow) {
1344     bdi.adornment |= kThemeAdornmentFocus;
1345   }
1347   RenderTransformedHIThemeControl(cgContext, aRect, RenderButton, &bdi,
1348                                   aParams.rtl);
1350 #if DRAW_IN_FRAME_DEBUG
1351   CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
1352   CGContextFillRect(cgContext, inBoxRect);
1353 #endif
1355   NS_OBJC_END_TRY_IGNORE_BLOCK;
1358 void nsNativeThemeCocoa::DrawButton(CGContextRef cgContext,
1359                                     const HIRect& inBoxRect,
1360                                     const ButtonParams& aParams) {
1361   ControlParams controlParams = aParams.controlParams;
1363   switch (aParams.button) {
1364     case ButtonType::eRegularPushButton:
1365     case ButtonType::eDefaultPushButton:
1366       DrawPushButton(cgContext, inBoxRect, aParams.button, controlParams);
1367       return;
1368     case ButtonType::eSquareBezelPushButton:
1369       DrawSquareBezelPushButton(cgContext, inBoxRect, controlParams);
1370       return;
1371     case ButtonType::eArrowButton:
1372       DrawHIThemeButton(cgContext, inBoxRect, kThemeArrowButton, kThemeButtonOn,
1373                         kThemeStateUnavailable, kThemeAdornmentArrowDownArrow,
1374                         controlParams);
1375       return;
1376     case ButtonType::eHelpButton:
1377       DrawHelpButton(cgContext, inBoxRect, controlParams);
1378       return;
1379     case ButtonType::eTreeTwistyPointingRight:
1380       DrawHIThemeButton(cgContext, inBoxRect, kThemeDisclosureButton,
1381                         kThemeDisclosureRight, ToThemeDrawState(controlParams),
1382                         kThemeAdornmentNone, controlParams);
1383       return;
1384     case ButtonType::eTreeTwistyPointingDown:
1385       DrawHIThemeButton(cgContext, inBoxRect, kThemeDisclosureButton,
1386                         kThemeDisclosureDown, ToThemeDrawState(controlParams),
1387                         kThemeAdornmentNone, controlParams);
1388       return;
1389     case ButtonType::eDisclosureButtonClosed:
1390       DrawDisclosureButton(cgContext, inBoxRect, controlParams,
1391                            NSControlStateValueOff);
1392       return;
1393     case ButtonType::eDisclosureButtonOpen:
1394       DrawDisclosureButton(cgContext, inBoxRect, controlParams,
1395                            NSControlStateValueOn);
1396       return;
1397   }
1400 nsNativeThemeCocoa::TreeHeaderCellParams
1401 nsNativeThemeCocoa::ComputeTreeHeaderCellParams(nsIFrame* aFrame,
1402                                                 ElementState aEventState) {
1403   TreeHeaderCellParams params;
1404   params.controlParams = ComputeControlParams(aFrame, aEventState);
1405   params.sortDirection = GetTreeSortDirection(aFrame);
1406   params.lastTreeHeaderCell = IsLastTreeHeaderCell(aFrame);
1407   return params;
1410 @interface NSTableHeaderCell (NSTableHeaderCell_setSortable)
1411 // This method has been present in the same form since at least macOS 10.4.
1412 - (void)_setSortable:(BOOL)arg1
1413     showSortIndicator:(BOOL)arg2
1414             ascending:(BOOL)arg3
1415              priority:(NSInteger)arg4
1416      highlightForSort:(BOOL)arg5;
1417 @end
1419 void nsNativeThemeCocoa::DrawTreeHeaderCell(
1420     CGContextRef cgContext, const HIRect& inBoxRect,
1421     const TreeHeaderCellParams& aParams) {
1422   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1424   // Without clearing the cell's title, it takes on a default value of "Field",
1425   // which is displayed underneath the title set in the front-end.
1426   NSCell* cell = (NSCell*)mTreeHeaderCell;
1427   cell.title = @"";
1429   if ([mTreeHeaderCell
1430           respondsToSelector:@selector
1431           (_setSortable:
1432               showSortIndicator:ascending:priority:highlightForSort:)]) {
1433     switch (aParams.sortDirection) {
1434       case eTreeSortDirection_Ascending:
1435         [mTreeHeaderCell _setSortable:YES
1436                     showSortIndicator:YES
1437                             ascending:YES
1438                              priority:0
1439                      highlightForSort:YES];
1440         break;
1441       case eTreeSortDirection_Descending:
1442         [mTreeHeaderCell _setSortable:YES
1443                     showSortIndicator:YES
1444                             ascending:NO
1445                              priority:0
1446                      highlightForSort:YES];
1447         break;
1448       default:
1449         // eTreeSortDirection_Natural
1450         [mTreeHeaderCell _setSortable:YES
1451                     showSortIndicator:NO
1452                             ascending:YES
1453                              priority:0
1454                      highlightForSort:NO];
1455         break;
1456     }
1457   }
1459   mTreeHeaderCell.enabled = !aParams.controlParams.disabled;
1460   mTreeHeaderCell.state =
1461       (mTreeHeaderCell.enabled && aParams.controlParams.pressed)
1462           ? NSControlStateValueOn
1463           : NSControlStateValueOff;
1465   mCellDrawView._drawingEndSeparator = !aParams.lastTreeHeaderCell;
1467   NSGraphicsContext* savedContext = NSGraphicsContext.currentContext;
1468   NSGraphicsContext.currentContext =
1469       [NSGraphicsContext graphicsContextWithCGContext:cgContext flipped:YES];
1470   DrawCellIncludingFocusRing(mTreeHeaderCell, inBoxRect, mCellDrawView);
1471   NSGraphicsContext.currentContext = savedContext;
1473 #if DRAW_IN_FRAME_DEBUG
1474   CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
1475   CGContextFillRect(cgContext, inBoxRect);
1476 #endif
1478   NS_OBJC_END_TRY_IGNORE_BLOCK;
1481 static const CellRenderSettings dropdownSettings = {
1482     {
1483         NSMakeSize(0, 16),  // mini
1484         NSMakeSize(0, 19),  // small
1485         NSMakeSize(0, 22)   // regular
1486     },
1487     {
1488         NSMakeSize(18, 0),  // mini
1489         NSMakeSize(38, 0),  // small
1490         NSMakeSize(44, 0)   // regular
1491     },
1492     {{
1493          // Leopard
1494          {1, 1, 2, 1},  // mini
1495          {3, 0, 3, 1},  // small
1496          {3, 0, 3, 0}   // regular
1497      },
1498      {
1499          // Yosemite
1500          {1, 1, 2, 1},  // mini
1501          {3, 0, 3, 1},  // small
1502          {3, 0, 3, 0}   // regular
1503      }}};
1505 static const CellRenderSettings editableMenulistSettings = {
1506     {
1507         NSMakeSize(0, 15),  // mini
1508         NSMakeSize(0, 18),  // small
1509         NSMakeSize(0, 21)   // regular
1510     },
1511     {
1512         NSMakeSize(18, 0),  // mini
1513         NSMakeSize(38, 0),  // small
1514         NSMakeSize(44, 0)   // regular
1515     },
1516     {{
1517          // Leopard
1518          {0, 0, 2, 2},  // mini
1519          {0, 0, 3, 2},  // small
1520          {0, 1, 3, 3}   // regular
1521      },
1522      {
1523          // Yosemite
1524          {0, 0, 2, 2},  // mini
1525          {0, 0, 3, 2},  // small
1526          {0, 1, 3, 3}   // regular
1527      }}};
1529 void nsNativeThemeCocoa::DrawDropdown(CGContextRef cgContext,
1530                                       const HIRect& inBoxRect,
1531                                       const DropdownParams& aParams) {
1532   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1534   [mDropdownCell setPullsDown:aParams.pullsDown];
1535   NSCell* cell =
1536       aParams.editable ? (NSCell*)mComboBoxCell : (NSCell*)mDropdownCell;
1538   ApplyControlParamsToNSCell(aParams.controlParams, cell);
1540   if (aParams.controlParams.insideActiveWindow) {
1541     [cell setControlTint:[NSColor currentControlTint]];
1542   } else {
1543     [cell setControlTint:NSClearControlTint];
1544   }
1546   const CellRenderSettings& settings =
1547       aParams.editable ? editableMenulistSettings : dropdownSettings;
1549   if (mCellDrawWindow) {
1550     mCellDrawWindow.cellsShouldLookActive =
1551         aParams.controlParams.insideActiveWindow;
1552   }
1553   DrawCellWithSnapping(cell, cgContext, inBoxRect, settings, 0.5f,
1554                        mCellDrawView, aParams.controlParams.rtl);
1556   NS_OBJC_END_TRY_IGNORE_BLOCK;
1559 static const CellRenderSettings spinnerSettings = {
1560     {
1561         NSMakeSize(11,
1562                    16),  // mini (width trimmed by 2px to reduce blank border)
1563         NSMakeSize(15, 22),  // small
1564         NSMakeSize(19, 27)   // regular
1565     },
1566     {
1567         NSMakeSize(11,
1568                    16),  // mini (width trimmed by 2px to reduce blank border)
1569         NSMakeSize(15, 22),  // small
1570         NSMakeSize(19, 27)   // regular
1571     },
1572     {{
1573          // Leopard
1574          {0, 0, 0, 0},  // mini
1575          {0, 0, 0, 0},  // small
1576          {0, 0, 0, 0}   // regular
1577      },
1578      {
1579          // Yosemite
1580          {0, 0, 0, 0},  // mini
1581          {0, 0, 0, 0},  // small
1582          {0, 0, 0, 0}   // regular
1583      }}};
1585 HIThemeButtonDrawInfo nsNativeThemeCocoa::SpinButtonDrawInfo(
1586     ThemeButtonKind aKind, const SpinButtonParams& aParams) {
1587   HIThemeButtonDrawInfo bdi;
1588   bdi.version = 0;
1589   bdi.kind = aKind;
1590   bdi.value = kThemeButtonOff;
1591   bdi.adornment = kThemeAdornmentNone;
1593   if (aParams.disabled) {
1594     bdi.state = kThemeStateUnavailable;
1595   } else if (aParams.insideActiveWindow && aParams.pressedButton) {
1596     if (*aParams.pressedButton == SpinButton::eUp) {
1597       bdi.state = kThemeStatePressedUp;
1598     } else {
1599       bdi.state = kThemeStatePressedDown;
1600     }
1601   } else {
1602     bdi.state = kThemeStateActive;
1603   }
1605   return bdi;
1608 void nsNativeThemeCocoa::DrawSpinButtons(CGContextRef cgContext,
1609                                          const HIRect& inBoxRect,
1610                                          const SpinButtonParams& aParams) {
1611   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1613   HIThemeButtonDrawInfo bdi = SpinButtonDrawInfo(kThemeIncDecButton, aParams);
1614   HIThemeDrawButton(&inBoxRect, &bdi, cgContext, HITHEME_ORIENTATION, NULL);
1616   NS_OBJC_END_TRY_IGNORE_BLOCK;
1619 void nsNativeThemeCocoa::DrawSpinButton(CGContextRef cgContext,
1620                                         const HIRect& inBoxRect,
1621                                         SpinButton aDrawnButton,
1622                                         const SpinButtonParams& aParams) {
1623   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1625   HIThemeButtonDrawInfo bdi =
1626       SpinButtonDrawInfo(kThemeIncDecButtonMini, aParams);
1628   // Cocoa only allows kThemeIncDecButton to paint the up and down spin buttons
1629   // together as a single unit (presumably because when one button is active,
1630   // the appearance of both changes (in different ways)). Here we have to paint
1631   // both buttons, using clip to hide the one we don't want to paint.
1632   HIRect drawRect = inBoxRect;
1633   drawRect.size.height *= 2;
1634   if (aDrawnButton == SpinButton::eDown) {
1635     drawRect.origin.y -= inBoxRect.size.height;
1636   }
1638   // Shift the drawing a little to the left, since cocoa paints with more
1639   // blank space around the visual buttons than we'd like:
1640   drawRect.origin.x -= 1;
1642   CGContextSaveGState(cgContext);
1643   CGContextClipToRect(cgContext, inBoxRect);
1645   HIThemeDrawButton(&drawRect, &bdi, cgContext, HITHEME_ORIENTATION, NULL);
1647   CGContextRestoreGState(cgContext);
1649   NS_OBJC_END_TRY_IGNORE_BLOCK;
1652 static const CellRenderSettings progressSettings[2][2] = {
1653     // Vertical progress bar.
1654     {// Determined settings.
1655      {{
1656           NSZeroSize,         // mini
1657           NSMakeSize(10, 0),  // small
1658           NSMakeSize(16, 0)   // regular
1659       },
1660       {NSZeroSize, NSZeroSize, NSZeroSize},
1661       {{
1662           // Leopard
1663           {0, 0, 0, 0},  // mini
1664           {1, 1, 1, 1},  // small
1665           {1, 1, 1, 1}   // regular
1666       }}},
1667      // There is no horizontal margin in regular undetermined size.
1668      {{
1669           NSZeroSize,         // mini
1670           NSMakeSize(10, 0),  // small
1671           NSMakeSize(16, 0)   // regular
1672       },
1673       {NSZeroSize, NSZeroSize, NSZeroSize},
1674       {{
1675            // Leopard
1676            {0, 0, 0, 0},  // mini
1677            {1, 1, 1, 1},  // small
1678            {1, 0, 1, 0}   // regular
1679        },
1680        {
1681            // Yosemite
1682            {0, 0, 0, 0},  // mini
1683            {1, 1, 1, 1},  // small
1684            {1, 0, 1, 0}   // regular
1685        }}}},
1686     // Horizontal progress bar.
1687     {// Determined settings.
1688      {{
1689           NSZeroSize,         // mini
1690           NSMakeSize(0, 10),  // small
1691           NSMakeSize(0, 16)   // regular
1692       },
1693       {NSZeroSize, NSZeroSize, NSZeroSize},
1694       {{
1695            // Leopard
1696            {0, 0, 0, 0},  // mini
1697            {1, 1, 1, 1},  // small
1698            {1, 1, 1, 1}   // regular
1699        },
1700        {
1701            // Yosemite
1702            {0, 0, 0, 0},  // mini
1703            {1, 1, 1, 1},  // small
1704            {1, 1, 1, 1}   // regular
1705        }}},
1706      // There is no horizontal margin in regular undetermined size.
1707      {{
1708           NSZeroSize,         // mini
1709           NSMakeSize(0, 10),  // small
1710           NSMakeSize(0, 16)   // regular
1711       },
1712       {NSZeroSize, NSZeroSize, NSZeroSize},
1713       {{
1714            // Leopard
1715            {0, 0, 0, 0},  // mini
1716            {1, 1, 1, 1},  // small
1717            {0, 1, 0, 1}   // regular
1718        },
1719        {
1720            // Yosemite
1721            {0, 0, 0, 0},  // mini
1722            {1, 1, 1, 1},  // small
1723            {0, 1, 0, 1}   // regular
1724        }}}}};
1726 nsNativeThemeCocoa::ProgressParams nsNativeThemeCocoa::ComputeProgressParams(
1727     nsIFrame* aFrame, ElementState aEventState, bool aIsHorizontal) {
1728   ProgressParams params;
1729   params.value = GetProgressValue(aFrame);
1730   params.max = GetProgressMaxValue(aFrame);
1731   params.verticalAlignFactor = VerticalAlignFactor(aFrame);
1732   params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
1733   params.indeterminate = aEventState.HasState(ElementState::INDETERMINATE);
1734   params.horizontal = aIsHorizontal;
1735   params.rtl = IsFrameRTL(aFrame);
1736   return params;
1739 void nsNativeThemeCocoa::DrawProgress(CGContextRef cgContext,
1740                                       const HIRect& inBoxRect,
1741                                       const ProgressParams& aParams) {
1742   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1744   NSProgressBarCell* cell = mProgressBarCell;
1746   [cell setValue:aParams.value];
1747   [cell setMax:aParams.max];
1748   [cell setIndeterminate:aParams.indeterminate];
1749   [cell setHorizontal:aParams.horizontal];
1750   [cell
1751       setControlTint:(aParams.insideActiveWindow ? [NSColor currentControlTint]
1752                                                  : NSClearControlTint)];
1754   if (mCellDrawWindow) {
1755     mCellDrawWindow.cellsShouldLookActive = aParams.insideActiveWindow;
1756   }
1757   DrawCellWithSnapping(
1758       cell, cgContext, inBoxRect,
1759       progressSettings[aParams.horizontal][aParams.indeterminate],
1760       aParams.verticalAlignFactor, mCellDrawView, aParams.rtl);
1762   NS_OBJC_END_TRY_IGNORE_BLOCK;
1765 static const CellRenderSettings meterSetting = {
1766     {
1767         NSMakeSize(0, 16),  // mini
1768         NSMakeSize(0, 16),  // small
1769         NSMakeSize(0, 16)   // regular
1770     },
1771     {NSZeroSize, NSZeroSize, NSZeroSize},
1772     {{
1773          // Leopard
1774          {1, 1, 1, 1},  // mini
1775          {1, 1, 1, 1},  // small
1776          {1, 1, 1, 1}   // regular
1777      },
1778      {
1779          // Yosemite
1780          {1, 1, 1, 1},  // mini
1781          {1, 1, 1, 1},  // small
1782          {1, 1, 1, 1}   // regular
1783      }}};
1785 nsNativeThemeCocoa::MeterParams nsNativeThemeCocoa::ComputeMeterParams(
1786     nsIFrame* aFrame) {
1787   nsIContent* content = aFrame->GetContent();
1788   if (!(content && content->IsHTMLElement(nsGkAtoms::meter))) {
1789     return MeterParams();
1790   }
1792   HTMLMeterElement* meterElement = static_cast<HTMLMeterElement*>(content);
1793   MeterParams params;
1794   params.value = meterElement->Value();
1795   params.min = meterElement->Min();
1796   params.max = meterElement->Max();
1797   ElementState states = meterElement->State();
1798   if (states.HasState(ElementState::SUB_OPTIMUM)) {
1799     params.optimumState = OptimumState::eSubOptimum;
1800   } else if (states.HasState(ElementState::SUB_SUB_OPTIMUM)) {
1801     params.optimumState = OptimumState::eSubSubOptimum;
1802   }
1803   params.horizontal = !IsVerticalMeter(aFrame);
1804   params.verticalAlignFactor = VerticalAlignFactor(aFrame);
1805   params.rtl = IsFrameRTL(aFrame);
1807   return params;
1810 void nsNativeThemeCocoa::DrawMeter(CGContextRef cgContext,
1811                                    const HIRect& inBoxRect,
1812                                    const MeterParams& aParams) {
1813   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK
1815   NSLevelIndicatorCell* cell = mMeterBarCell;
1817   [cell setMinValue:aParams.min];
1818   [cell setMaxValue:aParams.max];
1819   [cell setDoubleValue:aParams.value];
1821   /**
1822    * The way HTML and Cocoa defines the meter/indicator widget are different.
1823    * So, we are going to use a trick to get the Cocoa widget showing what we
1824    * are expecting: we set the warningValue or criticalValue to the current
1825    * value when we want to have the widget to be in the warning or critical
1826    * state.
1827    */
1828   switch (aParams.optimumState) {
1829     case OptimumState::eOptimum:
1830       [cell setWarningValue:aParams.max + 1];
1831       [cell setCriticalValue:aParams.max + 1];
1832       break;
1833     case OptimumState::eSubOptimum:
1834       [cell setWarningValue:aParams.value];
1835       [cell setCriticalValue:aParams.max + 1];
1836       break;
1837     case OptimumState::eSubSubOptimum:
1838       [cell setWarningValue:aParams.max + 1];
1839       [cell setCriticalValue:aParams.value];
1840       break;
1841   }
1843   HIRect rect = CGRectStandardize(inBoxRect);
1844   BOOL vertical = !aParams.horizontal;
1846   CGContextSaveGState(cgContext);
1848   if (vertical) {
1849     /**
1850      * Cocoa doesn't provide a vertical meter bar so to show one, we have to
1851      * show a rotated horizontal meter bar.
1852      * Given that we want to show a vertical meter bar, we assume that the rect
1853      * has vertical dimensions but we can't correctly draw a meter widget inside
1854      * such a rectangle so we need to inverse width and height (and re-position)
1855      * to get a rectangle with horizontal dimensions.
1856      * Finally, we want to show a vertical meter so we want to rotate the result
1857      * so it is vertical. We do that by changing the context.
1858      */
1859     CGFloat tmp = rect.size.width;
1860     rect.size.width = rect.size.height;
1861     rect.size.height = tmp;
1862     rect.origin.x += rect.size.height / 2.f - rect.size.width / 2.f;
1863     rect.origin.y += rect.size.width / 2.f - rect.size.height / 2.f;
1865     CGContextTranslateCTM(cgContext, CGRectGetMidX(rect), CGRectGetMidY(rect));
1866     CGContextRotateCTM(cgContext, -M_PI / 2.f);
1867     CGContextTranslateCTM(cgContext, -CGRectGetMidX(rect),
1868                           -CGRectGetMidY(rect));
1869   }
1871   if (mCellDrawWindow) {
1872     mCellDrawWindow.cellsShouldLookActive =
1873         YES;  // TODO: propagate correct activeness state
1874   }
1875   DrawCellWithSnapping(cell, cgContext, rect, meterSetting,
1876                        aParams.verticalAlignFactor, mCellDrawView,
1877                        !vertical && aParams.rtl);
1879   CGContextRestoreGState(cgContext);
1881   NS_OBJC_END_TRY_IGNORE_BLOCK
1884 void nsNativeThemeCocoa::DrawTabPanel(CGContextRef cgContext,
1885                                       const HIRect& inBoxRect,
1886                                       bool aIsInsideActiveWindow) {
1887   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1889   HIThemeTabPaneDrawInfo tpdi;
1891   tpdi.version = 1;
1892   tpdi.state = aIsInsideActiveWindow ? kThemeStateActive : kThemeStateInactive;
1893   tpdi.direction = kThemeTabNorth;
1894   tpdi.size = kHIThemeTabSizeNormal;
1895   tpdi.kind = kHIThemeTabKindNormal;
1897   HIThemeDrawTabPane(&inBoxRect, &tpdi, cgContext, HITHEME_ORIENTATION);
1899   NS_OBJC_END_TRY_IGNORE_BLOCK;
1902 Maybe<nsNativeThemeCocoa::ScaleParams>
1903 nsNativeThemeCocoa::ComputeHTMLScaleParams(nsIFrame* aFrame,
1904                                            ElementState aEventState) {
1905   nsRangeFrame* rangeFrame = do_QueryFrame(aFrame);
1906   if (!rangeFrame) {
1907     return Nothing();
1908   }
1910   bool isHorizontal = IsRangeHorizontal(aFrame);
1912   // ScaleParams requires integer min, max and value. This is purely for
1913   // drawing, so we normalize to a range 0-1000 here.
1914   ScaleParams params;
1915   params.value = int32_t(rangeFrame->GetValueAsFractionOfRange() * 1000);
1916   params.min = 0;
1917   params.max = 1000;
1918   params.reverse = !isHorizontal || rangeFrame->IsRightToLeft();
1919   params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
1920   params.focused = aEventState.HasState(ElementState::FOCUSRING);
1921   params.disabled = aEventState.HasState(ElementState::DISABLED);
1922   params.horizontal = isHorizontal;
1923   return Some(params);
1926 void nsNativeThemeCocoa::DrawScale(CGContextRef cgContext,
1927                                    const HIRect& inBoxRect,
1928                                    const ScaleParams& aParams) {
1929   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1931   HIThemeTrackDrawInfo tdi;
1933   tdi.version = 0;
1934   tdi.kind = kThemeMediumSlider;
1935   tdi.bounds = inBoxRect;
1936   tdi.min = aParams.min;
1937   tdi.max = aParams.max;
1938   tdi.value = aParams.value;
1939   tdi.attributes = kThemeTrackShowThumb;
1940   if (aParams.horizontal) {
1941     tdi.attributes |= kThemeTrackHorizontal;
1942   }
1943   if (aParams.reverse) {
1944     tdi.attributes |= kThemeTrackRightToLeft;
1945   }
1946   if (aParams.focused) {
1947     tdi.attributes |= kThemeTrackHasFocus;
1948   }
1949   if (aParams.disabled) {
1950     tdi.enableState = kThemeTrackDisabled;
1951   } else {
1952     tdi.enableState =
1953         aParams.insideActiveWindow ? kThemeTrackActive : kThemeTrackInactive;
1954   }
1955   tdi.trackInfo.slider.thumbDir = kThemeThumbPlain;
1956   tdi.trackInfo.slider.pressState = 0;
1958   HIThemeDrawTrack(&tdi, NULL, cgContext, HITHEME_ORIENTATION);
1960   NS_OBJC_END_TRY_IGNORE_BLOCK;
1963 nsIFrame* nsNativeThemeCocoa::SeparatorResponsibility(nsIFrame* aBefore,
1964                                                       nsIFrame* aAfter) {
1965   // Usually a separator is drawn by the segment to the right of the
1966   // separator, but pressed and selected segments have higher priority.
1967   if (!aBefore || !aAfter) return nullptr;
1968   if (IsSelectedButton(aAfter)) return aAfter;
1969   if (IsSelectedButton(aBefore) || IsPressedButton(aBefore)) return aBefore;
1970   return aAfter;
1973 static CGRect SeparatorAdjustedRect(CGRect aRect,
1974                                     nsNativeThemeCocoa::SegmentParams aParams) {
1975   // A separator between two segments should always be located in the leftmost
1976   // pixel column of the segment to the right of the separator, regardless of
1977   // who ends up drawing it.
1978   // CoreUI draws the separators inside the drawing rect.
1979   if (!aParams.atLeftEnd && !aParams.drawsLeftSeparator) {
1980     // The segment to the left of us draws the separator, so we need to make
1981     // room for it.
1982     aRect.origin.x += 1;
1983     aRect.size.width -= 1;
1984   }
1985   if (aParams.drawsRightSeparator) {
1986     // We draw the right separator, so we need to extend the draw rect into the
1987     // segment to our right.
1988     aRect.size.width += 1;
1989   }
1990   return aRect;
1993 static NSString* ToolbarButtonPosition(BOOL aIsFirst, BOOL aIsLast) {
1994   if (aIsFirst) {
1995     if (aIsLast) return @"kCUISegmentPositionOnly";
1996     return @"kCUISegmentPositionFirst";
1997   }
1998   if (aIsLast) return @"kCUISegmentPositionLast";
1999   return @"kCUISegmentPositionMiddle";
2002 struct SegmentedControlRenderSettings {
2003   const CGFloat* heights;
2004   const NSString* widgetName;
2007 static const CGFloat tabHeights[3] = {17, 20, 23};
2009 static const SegmentedControlRenderSettings tabRenderSettings = {tabHeights,
2010                                                                  @"tab"};
2012 static const CGFloat toolbarButtonHeights[3] = {15, 18, 22};
2014 static const SegmentedControlRenderSettings toolbarButtonRenderSettings = {
2015     toolbarButtonHeights, @"kCUIWidgetButtonSegmentedSCurve"};
2017 nsNativeThemeCocoa::SegmentParams nsNativeThemeCocoa::ComputeSegmentParams(
2018     nsIFrame* aFrame, ElementState aEventState, SegmentType aSegmentType) {
2019   SegmentParams params;
2020   params.segmentType = aSegmentType;
2021   params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
2022   params.pressed = IsPressedButton(aFrame);
2023   params.selected = IsSelectedButton(aFrame);
2024   params.focused = aEventState.HasState(ElementState::FOCUSRING);
2025   bool isRTL = IsFrameRTL(aFrame);
2026   nsIFrame* left = GetAdjacentSiblingFrameWithSameAppearance(aFrame, isRTL);
2027   nsIFrame* right = GetAdjacentSiblingFrameWithSameAppearance(aFrame, !isRTL);
2028   params.atLeftEnd = !left;
2029   params.atRightEnd = !right;
2030   params.drawsLeftSeparator = SeparatorResponsibility(left, aFrame) == aFrame;
2031   params.drawsRightSeparator = SeparatorResponsibility(aFrame, right) == aFrame;
2032   params.rtl = isRTL;
2033   return params;
2036 static SegmentedControlRenderSettings RenderSettingsForSegmentType(
2037     nsNativeThemeCocoa::SegmentType aSegmentType) {
2038   switch (aSegmentType) {
2039     case nsNativeThemeCocoa::SegmentType::eToolbarButton:
2040       return toolbarButtonRenderSettings;
2041     case nsNativeThemeCocoa::SegmentType::eTab:
2042       return tabRenderSettings;
2043   }
2046 void nsNativeThemeCocoa::DrawSegment(CGContextRef cgContext,
2047                                      const HIRect& inBoxRect,
2048                                      const SegmentParams& aParams) {
2049   SegmentedControlRenderSettings renderSettings =
2050       RenderSettingsForSegmentType(aParams.segmentType);
2051   NSControlSize controlSize =
2052       FindControlSize(inBoxRect.size.height, renderSettings.heights, 4.0f);
2053   CGRect drawRect = SeparatorAdjustedRect(inBoxRect, aParams);
2055   NSDictionary* dict = @{
2056     @"widget" : renderSettings.widgetName,
2057     @"kCUIPresentationStateKey" :
2058         (aParams.insideActiveWindow ? @"kCUIPresentationStateActiveKey"
2059                                     : @"kCUIPresentationStateInactive"),
2060     @"kCUIPositionKey" :
2061         ToolbarButtonPosition(aParams.atLeftEnd, aParams.atRightEnd),
2062     @"kCUISegmentLeadingSeparatorKey" :
2063         [NSNumber numberWithBool:aParams.drawsLeftSeparator],
2064     @"kCUISegmentTrailingSeparatorKey" :
2065         [NSNumber numberWithBool:aParams.drawsRightSeparator],
2066     @"value" : [NSNumber numberWithBool:aParams.selected],
2067     @"state" : (aParams.pressed
2068                     ? @"pressed"
2069                     : (aParams.insideActiveWindow ? @"normal" : @"inactive")),
2070     @"focus" : [NSNumber numberWithBool:aParams.focused],
2071     @"size" : CUIControlSizeForCocoaSize(controlSize),
2072     @"is.flipped" : [NSNumber numberWithBool:YES],
2073     @"direction" : @"up"
2074   };
2076   RenderWithCoreUI(drawRect, cgContext, dict);
2079 void nsNativeThemeCocoa::DrawStatusBar(CGContextRef cgContext,
2080                                        const HIRect& inBoxRect, bool aIsMain) {
2081   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2083   if (inBoxRect.size.height < 2.0f) return;
2085   CGContextSaveGState(cgContext);
2086   CGContextClipToRect(cgContext, inBoxRect);
2088   // kCUIWidgetWindowFrame draws a complete window frame with both title bar
2089   // and bottom bar. We only want the bottom bar, so we extend the draw rect
2090   // upwards to make space for the title bar, and then we clip it away.
2091   CGRect drawRect = inBoxRect;
2092   const int extendUpwards = 40;
2093   drawRect.origin.y -= extendUpwards;
2094   drawRect.size.height += extendUpwards;
2095   RenderWithCoreUI(
2096       drawRect, cgContext,
2097       [NSDictionary dictionaryWithObjectsAndKeys:
2098                         @"kCUIWidgetWindowFrame", @"widget", @"regularwin",
2099                         @"windowtype", (aIsMain ? @"normal" : @"inactive"),
2100                         @"state",
2101                         [NSNumber numberWithInt:inBoxRect.size.height],
2102                         @"kCUIWindowFrameBottomBarHeightKey",
2103                         [NSNumber numberWithBool:YES],
2104                         @"kCUIWindowFrameDrawBottomBarSeparatorKey",
2105                         [NSNumber numberWithBool:YES], @"is.flipped", nil]);
2107   CGContextRestoreGState(cgContext);
2109   NS_OBJC_END_TRY_IGNORE_BLOCK;
2112 void nsNativeThemeCocoa::DrawMultilineTextField(CGContextRef cgContext,
2113                                                 const CGRect& inBoxRect,
2114                                                 bool aIsFocused) {
2115   mTextFieldCell.enabled = YES;
2116   mTextFieldCell.showsFirstResponder = aIsFocused;
2118   if (mCellDrawWindow) {
2119     mCellDrawWindow.cellsShouldLookActive = YES;
2120   }
2122   // DrawCellIncludingFocusRing draws into the current NSGraphicsContext, so do
2123   // the usual save+restore dance.
2124   NSGraphicsContext* savedContext = NSGraphicsContext.currentContext;
2125   NSGraphicsContext.currentContext =
2126       [NSGraphicsContext graphicsContextWithCGContext:cgContext flipped:YES];
2127   DrawCellIncludingFocusRing(mTextFieldCell, inBoxRect, mCellDrawView);
2128   NSGraphicsContext.currentContext = savedContext;
2131 static bool IsHiDPIContext(nsDeviceContext* aContext) {
2132   return AppUnitsPerCSSPixel() >=
2133          2 * aContext->AppUnitsPerDevPixelAtUnitFullZoom();
2136 Maybe<nsNativeThemeCocoa::WidgetInfo> nsNativeThemeCocoa::ComputeWidgetInfo(
2137     nsIFrame* aFrame, StyleAppearance aAppearance, const nsRect& aRect) {
2138   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
2140   // setup to draw into the correct port
2141   int32_t p2a = aFrame->PresContext()->AppUnitsPerDevPixel();
2143   gfx::Rect nativeWidgetRect(aRect.x, aRect.y, aRect.width, aRect.height);
2144   nativeWidgetRect.Scale(1.0 / gfxFloat(p2a));
2145   float originalHeight = nativeWidgetRect.Height();
2146   nativeWidgetRect.Round();
2147   if (nativeWidgetRect.IsEmpty()) {
2148     return Nothing();  // Don't attempt to draw invisible widgets.
2149   }
2151   bool hidpi = IsHiDPIContext(aFrame->PresContext()->DeviceContext());
2152   if (hidpi) {
2153     // Use high-resolution drawing.
2154     nativeWidgetRect.Scale(0.5f);
2155     originalHeight *= 0.5f;
2156   }
2158   ElementState elementState = GetContentState(aFrame, aAppearance);
2160   switch (aAppearance) {
2161     case StyleAppearance::Menupopup:
2162     case StyleAppearance::Tooltip:
2163       return Nothing();
2165     case StyleAppearance::Checkbox:
2166     case StyleAppearance::Radio: {
2167       bool isCheckbox = aAppearance == StyleAppearance::Checkbox;
2169       CheckboxOrRadioParams params;
2170       params.state = CheckboxOrRadioState::eOff;
2171       if (isCheckbox && elementState.HasState(ElementState::INDETERMINATE)) {
2172         params.state = CheckboxOrRadioState::eIndeterminate;
2173       } else if (elementState.HasState(ElementState::CHECKED)) {
2174         params.state = CheckboxOrRadioState::eOn;
2175       }
2176       params.controlParams = ComputeControlParams(aFrame, elementState);
2177       params.verticalAlignFactor = VerticalAlignFactor(aFrame);
2178       if (isCheckbox) {
2179         return Some(WidgetInfo::Checkbox(params));
2180       }
2181       return Some(WidgetInfo::Radio(params));
2182     }
2184     case StyleAppearance::Button:
2185       if (IsDefaultButton(aFrame)) {
2186         // Check whether the default button is in a document that does not
2187         // match the :-moz-window-inactive pseudoclass. This activeness check
2188         // is different from the other "active window" checks in this file
2189         // because we absolutely need the button's default button appearance to
2190         // be in sync with its text color, and the text color is changed by
2191         // such a :-moz-window-inactive rule. (That's because on 10.10 and up,
2192         // default buttons in active windows have blue background and white
2193         // text, and default buttons in inactive windows have white background
2194         // and black text.)
2195         DocumentState docState = aFrame->PresContext()->Document()->State();
2196         ControlParams params = ComputeControlParams(aFrame, elementState);
2197         params.insideActiveWindow =
2198             !docState.HasState(DocumentState::WINDOW_INACTIVE);
2199         return Some(WidgetInfo::Button(
2200             ButtonParams{params, ButtonType::eDefaultPushButton}));
2201       }
2202       if (IsButtonTypeMenu(aFrame)) {
2203         ControlParams controlParams =
2204             ComputeControlParams(aFrame, elementState);
2205         controlParams.pressed = IsOpenButton(aFrame);
2206         DropdownParams params;
2207         params.controlParams = controlParams;
2208         params.pullsDown = true;
2209         params.editable = false;
2210         return Some(WidgetInfo::Dropdown(params));
2211       }
2212       if (originalHeight > DO_SQUARE_BUTTON_HEIGHT) {
2213         // If the button is tall enough, draw the square button style so that
2214         // buttons with non-standard content look good. Otherwise draw normal
2215         // rounded aqua buttons.
2216         // This comparison is done based on the height that is calculated
2217         // without the top, because the snapped height can be affected by the
2218         // top of the rect and that may result in different height depending on
2219         // the top value.
2220         return Some(WidgetInfo::Button(
2221             ButtonParams{ComputeControlParams(aFrame, elementState),
2222                          ButtonType::eSquareBezelPushButton}));
2223       }
2224       return Some(WidgetInfo::Button(
2225           ButtonParams{ComputeControlParams(aFrame, elementState),
2226                        ButtonType::eRegularPushButton}));
2228     case StyleAppearance::MozMacHelpButton:
2229       return Some(WidgetInfo::Button(
2230           ButtonParams{ComputeControlParams(aFrame, elementState),
2231                        ButtonType::eHelpButton}));
2233     case StyleAppearance::MozMacDisclosureButtonOpen:
2234     case StyleAppearance::MozMacDisclosureButtonClosed: {
2235       ButtonType buttonType =
2236           (aAppearance == StyleAppearance::MozMacDisclosureButtonClosed)
2237               ? ButtonType::eDisclosureButtonClosed
2238               : ButtonType::eDisclosureButtonOpen;
2239       return Some(WidgetInfo::Button(ButtonParams{
2240           ComputeControlParams(aFrame, elementState), buttonType}));
2241     }
2243     case StyleAppearance::Spinner: {
2244       bool isSpinner = (aAppearance == StyleAppearance::Spinner);
2245       nsIContent* content = aFrame->GetContent();
2246       if (isSpinner && content->IsHTMLElement()) {
2247         // In HTML the theming for the spin buttons is drawn individually into
2248         // their own backgrounds instead of being drawn into the background of
2249         // their spinner parent as it is for XUL.
2250         break;
2251       }
2252       SpinButtonParams params;
2253       if (content->IsElement()) {
2254         if (content->AsElement()->AttrValueIs(
2255                 kNameSpaceID_None, nsGkAtoms::state, u"up"_ns, eCaseMatters)) {
2256           params.pressedButton = Some(SpinButton::eUp);
2257         } else if (content->AsElement()->AttrValueIs(
2258                        kNameSpaceID_None, nsGkAtoms::state, u"down"_ns,
2259                        eCaseMatters)) {
2260           params.pressedButton = Some(SpinButton::eDown);
2261         }
2262       }
2263       params.disabled = elementState.HasState(ElementState::DISABLED);
2264       params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
2266       return Some(WidgetInfo::SpinButtons(params));
2267     }
2269     case StyleAppearance::SpinnerUpbutton:
2270     case StyleAppearance::SpinnerDownbutton: {
2271       nsNumberControlFrame* numberControlFrame =
2272           nsNumberControlFrame::GetNumberControlFrameForSpinButton(aFrame);
2273       if (numberControlFrame) {
2274         SpinButtonParams params;
2275         if (numberControlFrame->SpinnerUpButtonIsDepressed()) {
2276           params.pressedButton = Some(SpinButton::eUp);
2277         } else if (numberControlFrame->SpinnerDownButtonIsDepressed()) {
2278           params.pressedButton = Some(SpinButton::eDown);
2279         }
2280         params.disabled = elementState.HasState(ElementState::DISABLED);
2281         params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
2282         if (aAppearance == StyleAppearance::SpinnerUpbutton) {
2283           return Some(WidgetInfo::SpinButtonUp(params));
2284         }
2285         return Some(WidgetInfo::SpinButtonDown(params));
2286       }
2287     } break;
2289     case StyleAppearance::Toolbarbutton: {
2290       SegmentParams params = ComputeSegmentParams(aFrame, elementState,
2291                                                   SegmentType::eToolbarButton);
2292       params.insideActiveWindow = [NativeWindowForFrame(aFrame) isMainWindow];
2293       return Some(WidgetInfo::Segment(params));
2294     }
2296     case StyleAppearance::Separator:
2297       return Some(WidgetInfo::Separator());
2299     case StyleAppearance::MozWindowTitlebar: {
2300       return Nothing();
2301     }
2303     case StyleAppearance::Statusbar:
2304       return Some(WidgetInfo::StatusBar(IsActiveToolbarControl(aFrame)));
2306     case StyleAppearance::MenulistButton:
2307     case StyleAppearance::Menulist: {
2308       ControlParams controlParams = ComputeControlParams(aFrame, elementState);
2309       controlParams.pressed = IsOpenButton(aFrame);
2310       DropdownParams params;
2311       params.controlParams = controlParams;
2312       params.pullsDown = false;
2313       params.editable = false;
2314       return Some(WidgetInfo::Dropdown(params));
2315     }
2317     case StyleAppearance::MozMenulistArrowButton:
2318       return Some(WidgetInfo::Button(
2319           ButtonParams{ComputeControlParams(aFrame, elementState),
2320                        ButtonType::eArrowButton}));
2322     case StyleAppearance::Textfield:
2323     case StyleAppearance::NumberInput:
2324       return Some(
2325           WidgetInfo::TextField(ComputeTextFieldParams(aFrame, elementState)));
2327     case StyleAppearance::Searchfield:
2328       return Some(WidgetInfo::SearchField(
2329           ComputeTextFieldParams(aFrame, elementState)));
2331     case StyleAppearance::ProgressBar: {
2332       if (elementState.HasState(ElementState::INDETERMINATE)) {
2333         if (!QueueAnimatedContentForRefresh(aFrame->GetContent(), 30)) {
2334           NS_WARNING("Unable to animate progressbar!");
2335         }
2336       }
2337       return Some(WidgetInfo::ProgressBar(ComputeProgressParams(
2338           aFrame, elementState, !IsVerticalProgress(aFrame))));
2339     }
2341     case StyleAppearance::Meter:
2342       return Some(WidgetInfo::Meter(ComputeMeterParams(aFrame)));
2344     case StyleAppearance::Progresschunk:
2345     case StyleAppearance::Meterchunk:
2346       // Do nothing: progress and meter bars cases will draw chunks.
2347       break;
2349     case StyleAppearance::Treetwisty:
2350       return Some(WidgetInfo::Button(
2351           ButtonParams{ComputeControlParams(aFrame, elementState),
2352                        ButtonType::eTreeTwistyPointingRight}));
2354     case StyleAppearance::Treetwistyopen:
2355       return Some(WidgetInfo::Button(
2356           ButtonParams{ComputeControlParams(aFrame, elementState),
2357                        ButtonType::eTreeTwistyPointingDown}));
2359     case StyleAppearance::Treeheadercell:
2360       return Some(WidgetInfo::TreeHeaderCell(
2361           ComputeTreeHeaderCellParams(aFrame, elementState)));
2363     case StyleAppearance::Treeitem:
2364     case StyleAppearance::Treeview:
2365       return Some(WidgetInfo::ColorFill(sRGBColor(1.0, 1.0, 1.0, 1.0)));
2367     case StyleAppearance::Treeheader:
2368       // do nothing, taken care of by individual header cells
2369     case StyleAppearance::Treeline:
2370       // do nothing, these lines don't exist on macos
2371       break;
2373     case StyleAppearance::Range: {
2374       Maybe<ScaleParams> params = ComputeHTMLScaleParams(aFrame, elementState);
2375       if (params) {
2376         return Some(WidgetInfo::Scale(*params));
2377       }
2378       break;
2379     }
2381     case StyleAppearance::Textarea:
2382       return Some(WidgetInfo::MultilineTextField(
2383           elementState.HasState(ElementState::FOCUS)));
2385     case StyleAppearance::Listbox:
2386       return Some(WidgetInfo::ListBox());
2388     case StyleAppearance::Tab: {
2389       SegmentParams params =
2390           ComputeSegmentParams(aFrame, elementState, SegmentType::eTab);
2391       params.pressed = params.pressed && !params.selected;
2392       return Some(WidgetInfo::Segment(params));
2393     }
2395     case StyleAppearance::Tabpanels:
2396       return Some(WidgetInfo::TabPanel(FrameIsInActiveWindow(aFrame)));
2398     default:
2399       break;
2400   }
2402   return Nothing();
2404   NS_OBJC_END_TRY_BLOCK_RETURN(Nothing());
2407 NS_IMETHODIMP
2408 nsNativeThemeCocoa::DrawWidgetBackground(gfxContext* aContext, nsIFrame* aFrame,
2409                                          StyleAppearance aAppearance,
2410                                          const nsRect& aRect,
2411                                          const nsRect& aDirtyRect,
2412                                          DrawOverflow aDrawOverflow) {
2413   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
2415   if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) {
2416     return ThemeCocoa::DrawWidgetBackground(aContext, aFrame, aAppearance,
2417                                             aRect, aDirtyRect, aDrawOverflow);
2418   }
2420   Maybe<WidgetInfo> widgetInfo = ComputeWidgetInfo(aFrame, aAppearance, aRect);
2422   if (!widgetInfo) {
2423     return NS_OK;
2424   }
2426   int32_t p2a = aFrame->PresContext()->AppUnitsPerDevPixel();
2428   gfx::Rect nativeWidgetRect = NSRectToRect(aRect, p2a);
2429   nativeWidgetRect.Round();
2431   bool hidpi = IsHiDPIContext(aFrame->PresContext()->DeviceContext());
2433   auto colorScheme = LookAndFeel::ColorSchemeForFrame(aFrame);
2435   RenderWidget(*widgetInfo, colorScheme, *aContext->GetDrawTarget(),
2436                nativeWidgetRect, NSRectToRect(aDirtyRect, p2a),
2437                hidpi ? 2.0f : 1.0f);
2439   return NS_OK;
2441   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
2444 void nsNativeThemeCocoa::RenderWidget(const WidgetInfo& aWidgetInfo,
2445                                       LookAndFeel::ColorScheme aScheme,
2446                                       DrawTarget& aDrawTarget,
2447                                       const gfx::Rect& aWidgetRect,
2448                                       const gfx::Rect& aDirtyRect,
2449                                       float aScale) {
2450   // Some of the drawing below uses NSAppearance.currentAppearance behind the
2451   // scenes. Set it to the appearance we want, the same way as
2452   // nsLookAndFeel::NativeGetColor.
2453   NSAppearance.currentAppearance = NSAppearanceForColorScheme(aScheme);
2455   // Also set the cell draw window's appearance; this is respected by
2456   // NSTextFieldCell (and its subclass NSSearchFieldCell).
2457   if (mCellDrawWindow) {
2458     mCellDrawWindow.appearance = NSAppearance.currentAppearance;
2459   }
2461   const Widget widget = aWidgetInfo.Widget();
2463   // Some widgets render using DrawTarget, and some using CGContext.
2464   switch (widget) {
2465     case Widget::eColorFill: {
2466       sRGBColor color = aWidgetInfo.Params<sRGBColor>();
2467       aDrawTarget.FillRect(aWidgetRect, ColorPattern(ToDeviceColor(color)));
2468       break;
2469     }
2470     default: {
2471       AutoRestoreTransform autoRestoreTransform(&aDrawTarget);
2472       gfx::Rect widgetRect = aWidgetRect;
2473       gfx::Rect dirtyRect = aDirtyRect;
2475       dirtyRect.Scale(1.0f / aScale);
2476       widgetRect.Scale(1.0f / aScale);
2477       aDrawTarget.SetTransform(
2478           aDrawTarget.GetTransform().PreScale(aScale, aScale));
2480       // The remaining widgets require a CGContext.
2481       CGRect macRect = CGRectMake(widgetRect.X(), widgetRect.Y(),
2482                                   widgetRect.Width(), widgetRect.Height());
2484       gfxQuartzNativeDrawing nativeDrawing(aDrawTarget, dirtyRect);
2486       CGContextRef cgContext = nativeDrawing.BeginNativeDrawing();
2487       if (cgContext == nullptr) {
2488         // The Quartz surface handles 0x0 surfaces by internally
2489         // making all operations no-ops; there's no cgcontext created for them.
2490         // Unfortunately, this means that callers that want to render
2491         // directly to the CGContext need to be aware of this quirk.
2492         return;
2493       }
2495       // Set the context's "base transform" to in order to get correctly-sized
2496       // focus rings.
2497       CGContextSetBaseCTM(cgContext,
2498                           CGAffineTransformMakeScale(aScale, aScale));
2500       switch (widget) {
2501         case Widget::eColorFill:
2502           MOZ_CRASH("already handled in outer switch");
2503           break;
2504         case Widget::eCheckbox: {
2505           CheckboxOrRadioParams params =
2506               aWidgetInfo.Params<CheckboxOrRadioParams>();
2507           DrawCheckboxOrRadio(cgContext, true, macRect, params);
2508           break;
2509         }
2510         case Widget::eRadio: {
2511           CheckboxOrRadioParams params =
2512               aWidgetInfo.Params<CheckboxOrRadioParams>();
2513           DrawCheckboxOrRadio(cgContext, false, macRect, params);
2514           break;
2515         }
2516         case Widget::eButton: {
2517           ButtonParams params = aWidgetInfo.Params<ButtonParams>();
2518           DrawButton(cgContext, macRect, params);
2519           break;
2520         }
2521         case Widget::eDropdown: {
2522           DropdownParams params = aWidgetInfo.Params<DropdownParams>();
2523           DrawDropdown(cgContext, macRect, params);
2524           break;
2525         }
2526         case Widget::eSpinButtons: {
2527           SpinButtonParams params = aWidgetInfo.Params<SpinButtonParams>();
2528           DrawSpinButtons(cgContext, macRect, params);
2529           break;
2530         }
2531         case Widget::eSpinButtonUp: {
2532           SpinButtonParams params = aWidgetInfo.Params<SpinButtonParams>();
2533           DrawSpinButton(cgContext, macRect, SpinButton::eUp, params);
2534           break;
2535         }
2536         case Widget::eSpinButtonDown: {
2537           SpinButtonParams params = aWidgetInfo.Params<SpinButtonParams>();
2538           DrawSpinButton(cgContext, macRect, SpinButton::eDown, params);
2539           break;
2540         }
2541         case Widget::eSegment: {
2542           SegmentParams params = aWidgetInfo.Params<SegmentParams>();
2543           DrawSegment(cgContext, macRect, params);
2544           break;
2545         }
2546         case Widget::eSeparator: {
2547           HIThemeSeparatorDrawInfo sdi = {0, kThemeStateActive};
2548           HIThemeDrawSeparator(&macRect, &sdi, cgContext, HITHEME_ORIENTATION);
2549           break;
2550         }
2551         case Widget::eStatusBar: {
2552           bool isMain = aWidgetInfo.Params<bool>();
2553           DrawStatusBar(cgContext, macRect, isMain);
2554           break;
2555         }
2556         case Widget::eGroupBox: {
2557           HIThemeGroupBoxDrawInfo gdi = {0, kThemeStateActive,
2558                                          kHIThemeGroupBoxKindPrimary};
2559           HIThemeDrawGroupBox(&macRect, &gdi, cgContext, HITHEME_ORIENTATION);
2560           break;
2561         }
2562         case Widget::eTextField: {
2563           TextFieldParams params = aWidgetInfo.Params<TextFieldParams>();
2564           DrawTextField(cgContext, macRect, params);
2565           break;
2566         }
2567         case Widget::eSearchField: {
2568           TextFieldParams params = aWidgetInfo.Params<TextFieldParams>();
2569           DrawSearchField(cgContext, macRect, params);
2570           break;
2571         }
2572         case Widget::eProgressBar: {
2573           ProgressParams params = aWidgetInfo.Params<ProgressParams>();
2574           DrawProgress(cgContext, macRect, params);
2575           break;
2576         }
2577         case Widget::eMeter: {
2578           MeterParams params = aWidgetInfo.Params<MeterParams>();
2579           DrawMeter(cgContext, macRect, params);
2580           break;
2581         }
2582         case Widget::eTreeHeaderCell: {
2583           TreeHeaderCellParams params =
2584               aWidgetInfo.Params<TreeHeaderCellParams>();
2585           DrawTreeHeaderCell(cgContext, macRect, params);
2586           break;
2587         }
2588         case Widget::eScale: {
2589           ScaleParams params = aWidgetInfo.Params<ScaleParams>();
2590           DrawScale(cgContext, macRect, params);
2591           break;
2592         }
2593         case Widget::eMultilineTextField: {
2594           bool isFocused = aWidgetInfo.Params<bool>();
2595           DrawMultilineTextField(cgContext, macRect, isFocused);
2596           break;
2597         }
2598         case Widget::eListBox: {
2599           // Fill the content with the control background color.
2600           CGContextSetFillColorWithColor(
2601               cgContext, [NSColor.controlBackgroundColor CGColor]);
2602           CGContextFillRect(cgContext, macRect);
2603           // Draw the frame using kCUIWidgetScrollViewFrame. This is what
2604           // NSScrollView uses in
2605           // -[NSScrollView drawRect:] if you give it a borderType of
2606           // NSBezelBorder.
2607           RenderWithCoreUI(
2608               macRect, cgContext, @{
2609                 @"widget" : @"kCUIWidgetScrollViewFrame",
2610                 @"kCUIIsFlippedKey" : @YES,
2611                 @"kCUIVariantMetal" : @NO,
2612               });
2613           break;
2614         }
2615         case Widget::eTabPanel: {
2616           bool isInsideActiveWindow = aWidgetInfo.Params<bool>();
2617           DrawTabPanel(cgContext, macRect, isInsideActiveWindow);
2618           break;
2619         }
2620       }
2622       // Reset the base CTM.
2623       CGContextSetBaseCTM(cgContext, CGAffineTransformIdentity);
2625       nativeDrawing.EndNativeDrawing();
2626     }
2627   }
2630 bool nsNativeThemeCocoa::CreateWebRenderCommandsForWidget(
2631     mozilla::wr::DisplayListBuilder& aBuilder,
2632     mozilla::wr::IpcResourceUpdateQueue& aResources,
2633     const mozilla::layers::StackingContextHelper& aSc,
2634     mozilla::layers::RenderRootStateManager* aManager, nsIFrame* aFrame,
2635     StyleAppearance aAppearance, const nsRect& aRect) {
2636   if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) {
2637     return ThemeCocoa::CreateWebRenderCommandsForWidget(
2638         aBuilder, aResources, aSc, aManager, aFrame, aAppearance, aRect);
2639   }
2641   // This list needs to stay consistent with the list in DrawWidgetBackground.
2642   // For every switch case in DrawWidgetBackground, there are three choices:
2643   //  - If the case in DrawWidgetBackground draws nothing for the given widget
2644   //    type, then don't list it here. We will hit the "default: return true;"
2645   //    case.
2646   //  - If the case in DrawWidgetBackground draws something simple for the given
2647   //    widget type, imitate that drawing using WebRender commands.
2648   //  - If the case in DrawWidgetBackground draws something complicated for the
2649   //    given widget type, return false here.
2650   switch (aAppearance) {
2651     case StyleAppearance::Checkbox:
2652     case StyleAppearance::Radio:
2653     case StyleAppearance::Button:
2654     case StyleAppearance::MozMacHelpButton:
2655     case StyleAppearance::MozMacDisclosureButtonOpen:
2656     case StyleAppearance::MozMacDisclosureButtonClosed:
2657     case StyleAppearance::Spinner:
2658     case StyleAppearance::SpinnerUpbutton:
2659     case StyleAppearance::SpinnerDownbutton:
2660     case StyleAppearance::Toolbarbutton:
2661     case StyleAppearance::Separator:
2662     case StyleAppearance::MozWindowTitlebar:
2663     case StyleAppearance::Statusbar:
2664     case StyleAppearance::Menulist:
2665     case StyleAppearance::MenulistButton:
2666     case StyleAppearance::MozMenulistArrowButton:
2667     case StyleAppearance::Textfield:
2668     case StyleAppearance::NumberInput:
2669     case StyleAppearance::Searchfield:
2670     case StyleAppearance::ProgressBar:
2671     case StyleAppearance::Meter:
2672     case StyleAppearance::Treeheadercell:
2673     case StyleAppearance::Treetwisty:
2674     case StyleAppearance::Treetwistyopen:
2675     case StyleAppearance::Treeitem:
2676     case StyleAppearance::Treeview:
2677     case StyleAppearance::Range:
2678       return false;
2680     case StyleAppearance::Textarea:
2681     case StyleAppearance::Listbox:
2682     case StyleAppearance::Tab:
2683     case StyleAppearance::Tabpanels:
2684       return false;
2686     default:
2687       return true;
2688   }
2691 LayoutDeviceIntMargin nsNativeThemeCocoa::DirectionAwareMargin(
2692     const LayoutDeviceIntMargin& aMargin, nsIFrame* aFrame) {
2693   // Assuming aMargin was originally specified for a horizontal LTR context,
2694   // reinterpret the values as logical, and then map to physical coords
2695   // according to aFrame's actual writing mode.
2696   WritingMode wm = aFrame->GetWritingMode();
2697   nsMargin m = LogicalMargin(wm, aMargin.top, aMargin.right, aMargin.bottom,
2698                              aMargin.left)
2699                    .GetPhysicalMargin(wm);
2700   return LayoutDeviceIntMargin(m.top, m.right, m.bottom, m.left);
2703 static const LayoutDeviceIntMargin kAquaDropdownBorder(1, 22, 2, 5);
2704 static const LayoutDeviceIntMargin kAquaComboboxBorder(3, 20, 3, 4);
2705 static const LayoutDeviceIntMargin kAquaSearchfieldBorder(3, 5, 2, 19);
2706 static const LayoutDeviceIntMargin kAquaSearchfieldBorderBigSur(5, 5, 4, 26);
2708 LayoutDeviceIntMargin nsNativeThemeCocoa::GetWidgetBorder(
2709     nsDeviceContext* aContext, nsIFrame* aFrame, StyleAppearance aAppearance) {
2710   if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) {
2711     return Theme::GetWidgetBorder(aContext, aFrame, aAppearance);
2712   }
2714   LayoutDeviceIntMargin result;
2716   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
2717   switch (aAppearance) {
2718     case StyleAppearance::Button: {
2719       if (IsButtonTypeMenu(aFrame)) {
2720         result = DirectionAwareMargin(kAquaDropdownBorder, aFrame);
2721       } else {
2722         result =
2723             DirectionAwareMargin(LayoutDeviceIntMargin(1, 7, 3, 7), aFrame);
2724       }
2725       break;
2726     }
2728     case StyleAppearance::Toolbarbutton: {
2729       result = DirectionAwareMargin(LayoutDeviceIntMargin(1, 4, 1, 4), aFrame);
2730       break;
2731     }
2733     case StyleAppearance::Checkbox:
2734     case StyleAppearance::Radio: {
2735       // nsCheckboxRadioFrame::GetIntrinsicWidth and
2736       // nsCheckboxRadioFrame::GetIntrinsicHeight assume a border width of 2px.
2737       result.SizeTo(2, 2, 2, 2);
2738       break;
2739     }
2741     case StyleAppearance::Menulist:
2742     case StyleAppearance::MenulistButton:
2743     case StyleAppearance::MozMenulistArrowButton:
2744       result = DirectionAwareMargin(kAquaDropdownBorder, aFrame);
2745       break;
2747     case StyleAppearance::NumberInput:
2748     case StyleAppearance::Textfield: {
2749       SInt32 frameOutset = 0;
2750       ::GetThemeMetric(kThemeMetricEditTextFrameOutset, &frameOutset);
2752       SInt32 textPadding = 0;
2753       ::GetThemeMetric(kThemeMetricEditTextWhitespace, &textPadding);
2755       frameOutset += textPadding;
2757       result.SizeTo(frameOutset, frameOutset, frameOutset, frameOutset);
2758       break;
2759     }
2761     case StyleAppearance::Textarea:
2762       result.SizeTo(1, 1, 1, 1);
2763       break;
2765     case StyleAppearance::Searchfield: {
2766       auto border = nsCocoaFeatures::OnBigSurOrLater()
2767                         ? kAquaSearchfieldBorderBigSur
2768                         : kAquaSearchfieldBorder;
2769       result = DirectionAwareMargin(border, aFrame);
2770       break;
2771     }
2773     case StyleAppearance::Listbox: {
2774       SInt32 frameOutset = 0;
2775       ::GetThemeMetric(kThemeMetricListBoxFrameOutset, &frameOutset);
2776       result.SizeTo(frameOutset, frameOutset, frameOutset, frameOutset);
2777       break;
2778     }
2780     case StyleAppearance::Statusbar:
2781       result.SizeTo(1, 0, 0, 0);
2782       break;
2784     default:
2785       break;
2786   }
2788   if (IsHiDPIContext(aContext)) {
2789     result = result + result;  // doubled
2790   }
2792   NS_OBJC_END_TRY_BLOCK_RETURN(result);
2795 // Return false here to indicate that CSS padding values should be used. There
2796 // is no reason to make a distinction between padding and border values, just
2797 // specify whatever values you want in GetWidgetBorder and only use this to
2798 // return true if you want to override CSS padding values.
2799 bool nsNativeThemeCocoa::GetWidgetPadding(nsDeviceContext* aContext,
2800                                           nsIFrame* aFrame,
2801                                           StyleAppearance aAppearance,
2802                                           LayoutDeviceIntMargin* aResult) {
2803   if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) {
2804     return Theme::GetWidgetPadding(aContext, aFrame, aAppearance, aResult);
2805   }
2807   // We don't want CSS padding being used for certain widgets.
2808   // See bug 381639 for an example of why.
2809   switch (aAppearance) {
2810     // Radios and checkboxes return a fixed size in GetMinimumWidgetSize
2811     // and have a meaningful baseline, so they can't have
2812     // author-specified padding.
2813     case StyleAppearance::Checkbox:
2814     case StyleAppearance::Radio:
2815       aResult->SizeTo(0, 0, 0, 0);
2816       return true;
2818     case StyleAppearance::Searchfield:
2819       if (nsCocoaFeatures::OnBigSurOrLater()) {
2820         return true;
2821       }
2822       break;
2824     default:
2825       break;
2826   }
2827   return false;
2830 bool nsNativeThemeCocoa::GetWidgetOverflow(nsDeviceContext* aContext,
2831                                            nsIFrame* aFrame,
2832                                            StyleAppearance aAppearance,
2833                                            nsRect* aOverflowRect) {
2834   if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) {
2835     return ThemeCocoa::GetWidgetOverflow(aContext, aFrame, aAppearance,
2836                                          aOverflowRect);
2837   }
2838   nsIntMargin overflow;
2839   switch (aAppearance) {
2840     case StyleAppearance::Button:
2841     case StyleAppearance::MozMacDisclosureButtonOpen:
2842     case StyleAppearance::MozMacDisclosureButtonClosed:
2843     case StyleAppearance::MozMacHelpButton:
2844     case StyleAppearance::Toolbarbutton:
2845     case StyleAppearance::NumberInput:
2846     case StyleAppearance::Textfield:
2847     case StyleAppearance::Textarea:
2848     case StyleAppearance::Searchfield:
2849     case StyleAppearance::Listbox:
2850     case StyleAppearance::Menulist:
2851     case StyleAppearance::MenulistButton:
2852     case StyleAppearance::MozMenulistArrowButton:
2853     case StyleAppearance::Checkbox:
2854     case StyleAppearance::Radio:
2855     case StyleAppearance::Tab: {
2856       overflow.SizeTo(static_cast<int32_t>(kMaxFocusRingWidth),
2857                       static_cast<int32_t>(kMaxFocusRingWidth),
2858                       static_cast<int32_t>(kMaxFocusRingWidth),
2859                       static_cast<int32_t>(kMaxFocusRingWidth));
2860       break;
2861     }
2862     case StyleAppearance::ProgressBar: {
2863       // Progress bars draw a 2 pixel white shadow under their progress
2864       // indicators.
2865       overflow.bottom = 2;
2866       break;
2867     }
2868     case StyleAppearance::Meter: {
2869       // Meter bars overflow their boxes by about 2 pixels.
2870       overflow.SizeTo(2, 2, 2, 2);
2871       break;
2872     }
2873     default:
2874       break;
2875   }
2877   if (IsHiDPIContext(aContext)) {
2878     // Double the number of device pixels.
2879     overflow += overflow;
2880   }
2882   if (overflow != nsIntMargin()) {
2883     int32_t p2a = aFrame->PresContext()->AppUnitsPerDevPixel();
2884     aOverflowRect->Inflate(nsMargin(NSIntPixelsToAppUnits(overflow.top, p2a),
2885                                     NSIntPixelsToAppUnits(overflow.right, p2a),
2886                                     NSIntPixelsToAppUnits(overflow.bottom, p2a),
2887                                     NSIntPixelsToAppUnits(overflow.left, p2a)));
2888     return true;
2889   }
2891   return false;
2894 LayoutDeviceIntSize nsNativeThemeCocoa::GetMinimumWidgetSize(
2895     nsPresContext* aPresContext, nsIFrame* aFrame,
2896     StyleAppearance aAppearance) {
2897   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
2899   if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) {
2900     return ThemeCocoa::GetMinimumWidgetSize(aPresContext, aFrame, aAppearance);
2901   }
2903   LayoutDeviceIntSize result;
2904   switch (aAppearance) {
2905     case StyleAppearance::Button: {
2906       result.SizeTo(pushButtonSettings.minimumSizes[miniControlSize].width,
2907                     pushButtonSettings.naturalSizes[miniControlSize].height);
2908       break;
2909     }
2911     case StyleAppearance::MozMacDisclosureButtonOpen:
2912     case StyleAppearance::MozMacDisclosureButtonClosed: {
2913       result.SizeTo(kDisclosureButtonSize.width, kDisclosureButtonSize.height);
2914       break;
2915     }
2917     case StyleAppearance::MozMacHelpButton: {
2918       result.SizeTo(kHelpButtonSize.width, kHelpButtonSize.height);
2919       break;
2920     }
2922     case StyleAppearance::Toolbarbutton: {
2923       result.SizeTo(0, toolbarButtonHeights[miniControlSize]);
2924       break;
2925     }
2927     case StyleAppearance::Spinner:
2928     case StyleAppearance::SpinnerUpbutton:
2929     case StyleAppearance::SpinnerDownbutton: {
2930       SInt32 buttonHeight = 0, buttonWidth = 0;
2931       if (aFrame->GetContent()->IsXULElement()) {
2932         ::GetThemeMetric(kThemeMetricLittleArrowsWidth, &buttonWidth);
2933         ::GetThemeMetric(kThemeMetricLittleArrowsHeight, &buttonHeight);
2934       } else {
2935         NSSize size =
2936             spinnerSettings
2937                 .minimumSizes[EnumSizeForCocoaSize(NSControlSizeMini)];
2938         buttonWidth = size.width;
2939         buttonHeight = size.height;
2940         if (aAppearance != StyleAppearance::Spinner) {
2941           // the buttons are half the height of the spinner
2942           buttonHeight /= 2;
2943         }
2944       }
2945       result.SizeTo(buttonWidth, buttonHeight);
2946       break;
2947     }
2949     case StyleAppearance::Menulist:
2950     case StyleAppearance::MenulistButton: {
2951       SInt32 popupHeight = 0;
2952       ::GetThemeMetric(kThemeMetricPopupButtonHeight, &popupHeight);
2953       result.SizeTo(0, popupHeight);
2954       break;
2955     }
2957     case StyleAppearance::NumberInput:
2958     case StyleAppearance::Textfield:
2959     case StyleAppearance::Textarea:
2960     case StyleAppearance::Searchfield: {
2961       // at minimum, we should be tall enough for 9pt text.
2962       // I'm using hardcoded values here because the appearance manager
2963       // values for the frame size are incorrect.
2964       result.SizeTo(0, (2 + 2) /* top */ + 9 + (1 + 1) /* bottom */);
2965       break;
2966     }
2968     case StyleAppearance::MozWindowButtonBox: {
2969       NSSize size = WindowButtonsSize(aFrame);
2970       result.SizeTo(size.width, size.height);
2971       break;
2972     }
2974     case StyleAppearance::ProgressBar: {
2975       SInt32 barHeight = 0;
2976       ::GetThemeMetric(kThemeMetricNormalProgressBarThickness, &barHeight);
2977       result.SizeTo(0, barHeight);
2978       break;
2979     }
2981     case StyleAppearance::Separator: {
2982       result.SizeTo(1, 1);
2983       break;
2984     }
2986     case StyleAppearance::Treetwisty:
2987     case StyleAppearance::Treetwistyopen: {
2988       SInt32 twistyHeight = 0, twistyWidth = 0;
2989       ::GetThemeMetric(kThemeMetricDisclosureButtonWidth, &twistyWidth);
2990       ::GetThemeMetric(kThemeMetricDisclosureButtonHeight, &twistyHeight);
2991       result.SizeTo(twistyWidth, twistyHeight);
2992       break;
2993     }
2995     case StyleAppearance::Treeheader:
2996     case StyleAppearance::Treeheadercell: {
2997       SInt32 headerHeight = 0;
2998       ::GetThemeMetric(kThemeMetricListHeaderHeight, &headerHeight);
2999       result.SizeTo(0, headerHeight);
3000       break;
3001     }
3003     case StyleAppearance::Tab: {
3004       result.SizeTo(0, tabHeights[miniControlSize]);
3005       break;
3006     }
3008     case StyleAppearance::RangeThumb: {
3009       SInt32 width = 0;
3010       SInt32 height = 0;
3011       ::GetThemeMetric(kThemeMetricSliderMinThumbWidth, &width);
3012       ::GetThemeMetric(kThemeMetricSliderMinThumbHeight, &height);
3013       result.SizeTo(width, height);
3014       break;
3015     }
3017     case StyleAppearance::MozMenulistArrowButton:
3018       return ThemeCocoa::GetMinimumWidgetSize(aPresContext, aFrame,
3019                                               aAppearance);
3021     default:
3022       break;
3023   }
3025   if (IsHiDPIContext(aPresContext->DeviceContext())) {
3026     result = result * 2;
3027   }
3029   return result;
3031   NS_OBJC_END_TRY_BLOCK_RETURN(LayoutDeviceIntSize());
3034 NS_IMETHODIMP
3035 nsNativeThemeCocoa::WidgetStateChanged(nsIFrame* aFrame,
3036                                        StyleAppearance aAppearance,
3037                                        nsAtom* aAttribute, bool* aShouldRepaint,
3038                                        const nsAttrValue* aOldValue) {
3039   // Some widget types just never change state.
3040   switch (aAppearance) {
3041     case StyleAppearance::MozWindowTitlebar:
3042     case StyleAppearance::Statusbar:
3043     case StyleAppearance::Tooltip:
3044     case StyleAppearance::Tabpanels:
3045     case StyleAppearance::Tabpanel:
3046     case StyleAppearance::Menupopup:
3047     case StyleAppearance::Progresschunk:
3048     case StyleAppearance::ProgressBar:
3049     case StyleAppearance::Meter:
3050     case StyleAppearance::Meterchunk:
3051       *aShouldRepaint = false;
3052       return NS_OK;
3053     default:
3054       break;
3055   }
3057   // XXXdwh Not sure what can really be done here.  Can at least guess for
3058   // specific widgets that they're highly unlikely to have certain states.
3059   // For example, a toolbar doesn't care about any states.
3060   if (!aAttribute) {
3061     // Hover/focus/active changed.  Always repaint.
3062     *aShouldRepaint = true;
3063   } else {
3064     // Check the attribute to see if it's relevant.
3065     // disabled, checked, dlgtype, default, etc.
3066     *aShouldRepaint = false;
3067     if (aAttribute == nsGkAtoms::disabled || aAttribute == nsGkAtoms::checked ||
3068         aAttribute == nsGkAtoms::selected ||
3069         aAttribute == nsGkAtoms::visuallyselected ||
3070         aAttribute == nsGkAtoms::menuactive ||
3071         aAttribute == nsGkAtoms::sortDirection ||
3072         aAttribute == nsGkAtoms::focused || aAttribute == nsGkAtoms::_default ||
3073         aAttribute == nsGkAtoms::open || aAttribute == nsGkAtoms::hover)
3074       *aShouldRepaint = true;
3075   }
3077   return NS_OK;
3080 NS_IMETHODIMP
3081 nsNativeThemeCocoa::ThemeChanged() {
3082   // This is unimplemented because we don't care if gecko changes its theme
3083   // and macOS system appearance changes are handled by
3084   // nsLookAndFeel::SystemWantsDarkTheme.
3085   return NS_OK;
3088 bool nsNativeThemeCocoa::ThemeSupportsWidget(nsPresContext* aPresContext,
3089                                              nsIFrame* aFrame,
3090                                              StyleAppearance aAppearance) {
3091   if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) {
3092     return ThemeCocoa::ThemeSupportsWidget(aPresContext, aFrame, aAppearance);
3093   }
3094   // if this is a dropdown button in a combobox the answer is always no
3095   if (aAppearance == StyleAppearance::MozMenulistArrowButton) {
3096     nsIFrame* parentFrame = aFrame->GetParent();
3097     if (parentFrame && parentFrame->IsComboboxControlFrame()) return false;
3098   }
3100   switch (aAppearance) {
3101     // Combobox dropdowns don't support native theming in vertical mode.
3102     case StyleAppearance::Menulist:
3103     case StyleAppearance::MenulistButton:
3104     case StyleAppearance::MozMenulistArrowButton:
3105       if (aFrame && aFrame->GetWritingMode().IsVertical()) {
3106         return false;
3107       }
3108       [[fallthrough]];
3110     case StyleAppearance::Listbox:
3111     case StyleAppearance::MozWindowButtonBox:
3112     case StyleAppearance::MozWindowTitlebar:
3113     case StyleAppearance::Menupopup:
3114     case StyleAppearance::Tooltip:
3116     case StyleAppearance::Checkbox:
3117     case StyleAppearance::Radio:
3118     case StyleAppearance::MozMacHelpButton:
3119     case StyleAppearance::MozMacDisclosureButtonOpen:
3120     case StyleAppearance::MozMacDisclosureButtonClosed:
3121     case StyleAppearance::MozMacUnifiedToolbarWindow:
3122     case StyleAppearance::Button:
3123     case StyleAppearance::Toolbarbutton:
3124     case StyleAppearance::Spinner:
3125     case StyleAppearance::SpinnerUpbutton:
3126     case StyleAppearance::SpinnerDownbutton:
3127     case StyleAppearance::Statusbar:
3128     case StyleAppearance::NumberInput:
3129     case StyleAppearance::Textfield:
3130     case StyleAppearance::Textarea:
3131     case StyleAppearance::Searchfield:
3132     case StyleAppearance::ProgressBar:
3133     case StyleAppearance::Progresschunk:
3134     case StyleAppearance::Meter:
3135     case StyleAppearance::Meterchunk:
3136     case StyleAppearance::Separator:
3138     case StyleAppearance::Tabpanels:
3139     case StyleAppearance::Tab:
3141     case StyleAppearance::Treetwisty:
3142     case StyleAppearance::Treetwistyopen:
3143     case StyleAppearance::Treeview:
3144     case StyleAppearance::Treeheader:
3145     case StyleAppearance::Treeheadercell:
3146     case StyleAppearance::Treeitem:
3147     case StyleAppearance::Treeline:
3149     case StyleAppearance::Range:
3150       return !IsWidgetStyled(aPresContext, aFrame, aAppearance);
3152     default:
3153       break;
3154   }
3156   return false;
3159 bool nsNativeThemeCocoa::WidgetIsContainer(StyleAppearance aAppearance) {
3160   // flesh this out at some point
3161   switch (aAppearance) {
3162     case StyleAppearance::MozMenulistArrowButton:
3163     case StyleAppearance::Radio:
3164     case StyleAppearance::Checkbox:
3165     case StyleAppearance::ProgressBar:
3166     case StyleAppearance::Meter:
3167     case StyleAppearance::Range:
3168     case StyleAppearance::MozMacHelpButton:
3169     case StyleAppearance::MozMacDisclosureButtonOpen:
3170     case StyleAppearance::MozMacDisclosureButtonClosed:
3171       return false;
3172     default:
3173       break;
3174   }
3175   return true;
3178 bool nsNativeThemeCocoa::ThemeDrawsFocusForWidget(nsIFrame*,
3179                                                   StyleAppearance aAppearance) {
3180   switch (aAppearance) {
3181     case StyleAppearance::Textarea:
3182     case StyleAppearance::Textfield:
3183     case StyleAppearance::Searchfield:
3184     case StyleAppearance::NumberInput:
3185     case StyleAppearance::Menulist:
3186     case StyleAppearance::MenulistButton:
3187     case StyleAppearance::Button:
3188     case StyleAppearance::MozMacHelpButton:
3189     case StyleAppearance::MozMacDisclosureButtonOpen:
3190     case StyleAppearance::MozMacDisclosureButtonClosed:
3191     case StyleAppearance::Radio:
3192     case StyleAppearance::Range:
3193     case StyleAppearance::Checkbox:
3194       return true;
3195     default:
3196       return false;
3197   }
3200 bool nsNativeThemeCocoa::ThemeNeedsComboboxDropmarker() { return false; }
3202 bool nsNativeThemeCocoa::WidgetAppearanceDependsOnWindowFocus(
3203     StyleAppearance aAppearance) {
3204   switch (aAppearance) {
3205     case StyleAppearance::Tabpanels:
3206     case StyleAppearance::Menupopup:
3207     case StyleAppearance::Tooltip:
3208     case StyleAppearance::Spinner:
3209     case StyleAppearance::SpinnerUpbutton:
3210     case StyleAppearance::SpinnerDownbutton:
3211     case StyleAppearance::Separator:
3212     case StyleAppearance::NumberInput:
3213     case StyleAppearance::Textfield:
3214     case StyleAppearance::Treeview:
3215     case StyleAppearance::Treeline:
3216     case StyleAppearance::Textarea:
3217     case StyleAppearance::Listbox:
3218       return false;
3219     default:
3220       return true;
3221   }
3224 nsITheme::ThemeGeometryType nsNativeThemeCocoa::ThemeGeometryTypeForWidget(
3225     nsIFrame* aFrame, StyleAppearance aAppearance) {
3226   switch (aAppearance) {
3227     case StyleAppearance::MozWindowTitlebar:
3228       return eThemeGeometryTypeTitlebar;
3229     case StyleAppearance::MozWindowButtonBox:
3230       return eThemeGeometryTypeWindowButtons;
3231     default:
3232       return eThemeGeometryTypeUnknown;
3233   }
3236 nsITheme::Transparency nsNativeThemeCocoa::GetWidgetTransparency(
3237     nsIFrame* aFrame, StyleAppearance aAppearance) {
3238   if (IsWidgetScrollbarPart(aAppearance)) {
3239     return ThemeCocoa::GetWidgetTransparency(aFrame, aAppearance);
3240   }
3242   switch (aAppearance) {
3243     case StyleAppearance::Menupopup:
3244     case StyleAppearance::Tooltip:
3245       return eTransparent;
3246     case StyleAppearance::MozMacUnifiedToolbarWindow:
3247       // We want these to be treated as opaque by Gecko. We ensure there's an
3248       // appropriate OS-level clear color to make sure that's the case.
3249       return eOpaque;
3250     case StyleAppearance::Statusbar:
3251       // Knowing that scrollbars and statusbars are opaque improves
3252       // performance, because we create layers for them.
3253       return eOpaque;
3255     default:
3256       return eUnknownTransparency;
3257   }
3260 already_AddRefed<widget::Theme> do_CreateNativeThemeDoNotUseDirectly() {
3261   return do_AddRef(new nsNativeThemeCocoa());