Bug 1755924 [wpt PR 32876] - Handle resumed blocks that get sliced by floats correctl...
[gecko.git] / widget / cocoa / nsNativeThemeCocoa.mm
blob881d7e89e18f03ac8741f7b54722c8d349cab776
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/EventStates.h"
35 #include "mozilla/Range.h"
36 #include "mozilla/dom/Element.h"
37 #include "mozilla/dom/HTMLMeterElement.h"
38 #include "mozilla/layers/StackingContextHelper.h"
39 #include "mozilla/StaticPrefs_layout.h"
40 #include "mozilla/StaticPrefs_widget.h"
41 #include "nsLookAndFeel.h"
42 #include "MacThemeGeometryType.h"
43 #include "SDKDeclarations.h"
44 #include "VibrancyManager.h"
46 #include "gfxContext.h"
47 #include "gfxQuartzSurface.h"
48 #include "gfxQuartzNativeDrawing.h"
49 #include "gfxUtils.h"  // for ToDeviceColor
50 #include <algorithm>
52 using namespace mozilla;
53 using namespace mozilla::gfx;
54 using mozilla::dom::HTMLMeterElement;
56 #define DRAW_IN_FRAME_DEBUG 0
57 #define SCROLLBARS_VISUAL_DEBUG 0
59 // private Quartz routines needed here
60 extern "C" {
61 CG_EXTERN void CGContextSetCTM(CGContextRef, CGAffineTransform);
62 CG_EXTERN void CGContextSetBaseCTM(CGContextRef, CGAffineTransform);
63 typedef CFTypeRef CUIRendererRef;
64 void CUIDraw(CUIRendererRef r, CGRect rect, CGContextRef ctx, CFDictionaryRef options,
65              CFDictionaryRef* result);
68 // Workaround for NSCell control tint drawing
69 // Without this workaround, NSCells are always drawn with the clear control tint
70 // as long as they're not attached to an NSControl which is a subview of an active window.
71 // XXXmstange Why doesn't Webkit need this?
72 @implementation NSCell (ControlTintWorkaround)
73 - (int)_realControlTint {
74   return [self controlTint];
76 @end
78 // This is the window for our MOZCellDrawView. When an NSCell is drawn, some NSCell implementations
79 // look at the draw view's window to determine whether 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 could call any one
87 // 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 ring.
112 @interface MOZCellDrawView : NSControl
113 // Called by NSTreeHeaderCell during drawing.
114 @property BOOL _drawingEndSeparator;
115 @end
117 @implementation MOZCellDrawView
119 - (BOOL)isFlipped {
120   return YES;
123 - (NSText*)currentEditor {
124   return nil;
127 @end
129 static void DrawFocusRingForCellIfNeeded(NSCell* aCell, NSRect aWithFrame, NSView* aInView) {
130   if ([aCell showsFirstResponder]) {
131     CGContextRef cgContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
132     CGContextSaveGState(cgContext);
134     // It's important to set the focus ring style before we enter the
135     // transparency layer so that the transparency layer only contains
136     // the normal button mask without the focus ring, and the conversion
137     // to the focus ring shape happens only when the transparency layer is
138     // ended.
139     NSSetFocusRingStyle(NSFocusRingOnly);
141     // We need to draw the whole button into a transparency layer because
142     // many button types are composed of multiple parts, and if these parts
143     // were drawn while the focus ring style was active, each individual part
144     // would produce a focus ring for itself. But we only want one focus ring
145     // for the whole button. The transparency layer is a way to merge the
146     // individual button parts together before the focus ring shape is
147     // calculated.
148     CGContextBeginTransparencyLayerWithRect(cgContext, NSRectToCGRect(aWithFrame), 0);
149     [aCell drawFocusRingMaskWithFrame:aWithFrame inView:aInView];
150     CGContextEndTransparencyLayer(cgContext);
152     CGContextRestoreGState(cgContext);
153   }
156 static void DrawCellIncludingFocusRing(NSCell* aCell, NSRect aWithFrame, NSView* aInView) {
157   [aCell drawWithFrame:aWithFrame inView:aInView];
158   DrawFocusRingForCellIfNeeded(aCell, aWithFrame, aInView);
162  * NSProgressBarCell is used to draw progress bars of any size.
163  */
164 @interface NSProgressBarCell : NSCell {
165   /*All instance variables are private*/
166   double mValue;
167   double mMax;
168   bool mIsIndeterminate;
169   bool mIsHorizontal;
172 - (void)setValue:(double)value;
173 - (double)value;
174 - (void)setMax:(double)max;
175 - (double)max;
176 - (void)setIndeterminate:(bool)aIndeterminate;
177 - (bool)isIndeterminate;
178 - (void)setHorizontal:(bool)aIsHorizontal;
179 - (bool)isHorizontal;
180 - (void)drawWithFrame:(NSRect)cellFrame inView:(NSView*)controlView;
181 @end
183 @implementation NSProgressBarCell
185 - (void)setMax:(double)aMax {
186   mMax = aMax;
189 - (double)max {
190   return mMax;
193 - (void)setValue:(double)aValue {
194   mValue = aValue;
197 - (double)value {
198   return mValue;
201 - (void)setIndeterminate:(bool)aIndeterminate {
202   mIsIndeterminate = aIndeterminate;
205 - (bool)isIndeterminate {
206   return mIsIndeterminate;
209 - (void)setHorizontal:(bool)aIsHorizontal {
210   mIsHorizontal = aIsHorizontal;
213 - (bool)isHorizontal {
214   return mIsHorizontal;
217 - (void)drawWithFrame:(NSRect)cellFrame inView:(NSView*)controlView {
218   CGContext* cgContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
220   HIThemeTrackDrawInfo tdi;
222   tdi.version = 0;
223   tdi.min = 0;
225   tdi.value = INT32_MAX * (mValue / mMax);
226   tdi.max = INT32_MAX;
227   tdi.bounds = NSRectToCGRect(cellFrame);
228   tdi.attributes = mIsHorizontal ? kThemeTrackHorizontal : 0;
229   tdi.enableState =
230       [self controlTint] == NSClearControlTint ? kThemeTrackInactive : kThemeTrackActive;
232   NSControlSize size = [self controlSize];
233   if (size == NSControlSizeRegular) {
234     tdi.kind = mIsIndeterminate ? kThemeLargeIndeterminateBar : kThemeLargeProgressBar;
235   } else {
236     NS_ASSERTION(size == NSControlSizeSmall,
237                  "We shouldn't have another size than small and regular for the moment");
238     tdi.kind = mIsIndeterminate ? kThemeMediumIndeterminateBar : kThemeMediumProgressBar;
239   }
241   int32_t stepsPerSecond = mIsIndeterminate ? 60 : 30;
242   int32_t milliSecondsPerStep = 1000 / stepsPerSecond;
243   tdi.trackInfo.progress.phase =
244       uint8_t(PR_IntervalToMilliseconds(PR_IntervalNow()) / milliSecondsPerStep);
246   HIThemeDrawTrack(&tdi, NULL, cgContext, kHIThemeOrientationNormal);
249 @end
251 @interface MOZSearchFieldCell : NSSearchFieldCell
252 @property BOOL shouldUseToolbarStyle;
253 @end
255 @implementation MOZSearchFieldCell
257 - (instancetype)init {
258   // We would like to render a search field which has the magnifying glass icon at the start of the
259   // search field, and no cancel button.
260   // On 10.12 and 10.13, empty search fields render the magnifying glass icon in the middle of the
261   // field. So in order to get the icon to show at the start of the field, we need to give the field
262   // some content. We achieve this with a single space character.
263   self = [super initTextCell:@" "];
265   // However, because the field is now non-empty, by default it shows a cancel button. To hide the
266   // cancel button, override it with a custom NSButtonCell which renders nothing.
267   NSButtonCell* invisibleCell = [[NSButtonCell alloc] initImageCell:nil];
268   invisibleCell.bezeled = NO;
269   invisibleCell.bordered = NO;
270   self.cancelButtonCell = invisibleCell;
271   [invisibleCell release];
273   return self;
276 - (BOOL)_isToolbarMode {
277   return self.shouldUseToolbarStyle;
280 @end
282 #define HITHEME_ORIENTATION kHIThemeOrientationNormal
284 static CGFloat kMaxFocusRingWidth = 0;  // initialized by the nsNativeThemeCocoa constructor
286 // These enums are for indexing into the margin array.
287 enum {
288   leopardOSorlater = 0,  // 10.6 - 10.9
289   yosemiteOSorlater = 1  // 10.10+
292 enum { miniControlSize, smallControlSize, regularControlSize };
294 enum { leftMargin, topMargin, rightMargin, bottomMargin };
296 static size_t EnumSizeForCocoaSize(NSControlSize cocoaControlSize) {
297   if (cocoaControlSize == NSControlSizeMini)
298     return miniControlSize;
299   else if (cocoaControlSize == NSControlSizeSmall)
300     return smallControlSize;
301   else
302     return regularControlSize;
305 static NSControlSize CocoaSizeForEnum(int32_t enumControlSize) {
306   if (enumControlSize == miniControlSize)
307     return NSControlSizeMini;
308   else if (enumControlSize == smallControlSize)
309     return NSControlSizeSmall;
310   else
311     return NSControlSizeRegular;
314 static NSString* CUIControlSizeForCocoaSize(NSControlSize aControlSize) {
315   if (aControlSize == NSControlSizeRegular)
316     return @"regular";
317   else if (aControlSize == NSControlSizeSmall)
318     return @"small";
319   else
320     return @"mini";
323 static void InflateControlRect(NSRect* rect, NSControlSize cocoaControlSize,
324                                const float marginSet[][3][4]) {
325   if (!marginSet) return;
327   static int osIndex = yosemiteOSorlater;
328   size_t controlSize = EnumSizeForCocoaSize(cocoaControlSize);
329   const float* buttonMargins = marginSet[osIndex][controlSize];
330   rect->origin.x -= buttonMargins[leftMargin];
331   rect->origin.y -= buttonMargins[bottomMargin];
332   rect->size.width += buttonMargins[leftMargin] + buttonMargins[rightMargin];
333   rect->size.height += buttonMargins[bottomMargin] + buttonMargins[topMargin];
336 static NSWindow* NativeWindowForFrame(nsIFrame* aFrame, nsIWidget** aTopLevelWidget = NULL) {
337   if (!aFrame) return nil;
339   nsIWidget* widget = aFrame->GetNearestWidget();
340   if (!widget) return nil;
342   nsIWidget* topLevelWidget = widget->GetTopLevelWidget();
343   if (aTopLevelWidget) *aTopLevelWidget = topLevelWidget;
345   return (NSWindow*)topLevelWidget->GetNativeData(NS_NATIVE_WINDOW);
348 static NSSize WindowButtonsSize(nsIFrame* aFrame) {
349   NSWindow* window = NativeWindowForFrame(aFrame);
350   if (!window) {
351     // Return fallback values.
352     return NSMakeSize(54, 16);
353   }
355   NSRect buttonBox = NSZeroRect;
356   NSButton* closeButton = [window standardWindowButton:NSWindowCloseButton];
357   if (closeButton) {
358     buttonBox = NSUnionRect(buttonBox, [closeButton frame]);
359   }
360   NSButton* minimizeButton = [window standardWindowButton:NSWindowMiniaturizeButton];
361   if (minimizeButton) {
362     buttonBox = NSUnionRect(buttonBox, [minimizeButton frame]);
363   }
364   NSButton* zoomButton = [window standardWindowButton:NSWindowZoomButton];
365   if (zoomButton) {
366     buttonBox = NSUnionRect(buttonBox, [zoomButton frame]);
367   }
368   return buttonBox.size;
371 static BOOL FrameIsInActiveWindow(nsIFrame* aFrame) {
372   nsIWidget* topLevelWidget = NULL;
373   NSWindow* win = NativeWindowForFrame(aFrame, &topLevelWidget);
374   if (!topLevelWidget || !win) return YES;
376   // XUL popups, e.g. the toolbar customization popup, can't become key windows,
377   // but controls in these windows should still get the active look.
378   if (topLevelWidget->WindowType() == eWindowType_popup) return YES;
379   if ([win isSheet]) return [win isKeyWindow];
380   return [win isMainWindow] && ![win attachedSheet];
383 // Toolbar controls and content controls respond to different window
384 // activeness states.
385 static BOOL IsActive(nsIFrame* aFrame, BOOL aIsToolbarControl) {
386   if (aIsToolbarControl) return [NativeWindowForFrame(aFrame) isMainWindow];
387   return FrameIsInActiveWindow(aFrame);
390 static bool IsInSourceList(nsIFrame* aFrame) {
391   for (nsIFrame* frame = aFrame->GetParent(); frame;
392        frame = nsLayoutUtils::GetCrossDocParentFrameInProcess(frame)) {
393     if (frame->StyleDisplay()->EffectiveAppearance() == StyleAppearance::MozMacSourceList) {
394       return true;
395     }
396   }
397   return false;
400 NS_IMPL_ISUPPORTS_INHERITED(nsNativeThemeCocoa, nsNativeTheme, nsITheme)
402 nsNativeThemeCocoa::nsNativeThemeCocoa() : ThemeCocoa(ScrollbarStyle()) {
403   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
405   kMaxFocusRingWidth = 7;
407   // provide a local autorelease pool, as this is called during startup
408   // before the main event-loop pool is in place
409   nsAutoreleasePool pool;
411   mDisclosureButtonCell = [[NSButtonCell alloc] initTextCell:@""];
412   [mDisclosureButtonCell setBezelStyle:NSRoundedDisclosureBezelStyle];
413   [mDisclosureButtonCell setButtonType:NSPushOnPushOffButton];
414   [mDisclosureButtonCell setHighlightsBy:NSPushInCellMask];
416   mHelpButtonCell = [[NSButtonCell alloc] initTextCell:@""];
417   [mHelpButtonCell setBezelStyle:NSHelpButtonBezelStyle];
418   [mHelpButtonCell setButtonType:NSMomentaryPushInButton];
419   [mHelpButtonCell setHighlightsBy:NSPushInCellMask];
421   mPushButtonCell = [[NSButtonCell alloc] initTextCell:@""];
422   [mPushButtonCell setButtonType:NSMomentaryPushInButton];
423   [mPushButtonCell setHighlightsBy:NSPushInCellMask];
425   mRadioButtonCell = [[NSButtonCell alloc] initTextCell:@""];
426   [mRadioButtonCell setButtonType:NSRadioButton];
428   mCheckboxCell = [[NSButtonCell alloc] initTextCell:@""];
429   [mCheckboxCell setButtonType:NSSwitchButton];
430   [mCheckboxCell setAllowsMixedState:YES];
432   mTextFieldCell = [[NSTextFieldCell alloc] initTextCell:@""];
433   [mTextFieldCell setBezeled:YES];
434   [mTextFieldCell setEditable:YES];
435   [mTextFieldCell setFocusRingType:NSFocusRingTypeExterior];
437   mSearchFieldCell = [[MOZSearchFieldCell alloc] init];
438   [mSearchFieldCell setBezelStyle:NSTextFieldRoundedBezel];
439   [mSearchFieldCell setBezeled:YES];
440   [mSearchFieldCell setEditable:YES];
441   [mSearchFieldCell setFocusRingType:NSFocusRingTypeExterior];
443   mDropdownCell = [[NSPopUpButtonCell alloc] initTextCell:@"" pullsDown:NO];
445   mComboBoxCell = [[NSComboBoxCell alloc] initTextCell:@""];
446   [mComboBoxCell setBezeled:YES];
447   [mComboBoxCell setEditable:YES];
448   [mComboBoxCell setFocusRingType:NSFocusRingTypeExterior];
450   mProgressBarCell = [[NSProgressBarCell alloc] init];
452   mMeterBarCell = [[NSLevelIndicatorCell alloc]
453       initWithLevelIndicatorStyle:NSContinuousCapacityLevelIndicatorStyle];
455   mTreeHeaderCell = [[NSTableHeaderCell alloc] init];
457   mCellDrawView = [[MOZCellDrawView alloc] init];
459   if (XRE_IsParentProcess()) {
460     // Put the cell draw view into a window that is never shown.
461     // This allows us to convince some NSCell implementations (such as NSButtonCell for default
462     // buttons) to draw with the active appearance. Another benefit of putting the draw view in a
463     // window is the fact that it lets NSTextFieldCell (and its subclass NSSearchFieldCell) inherit
464     // the current NSApplication effectiveAppearance automatically, so the field adapts to Dark Mode
465     // correctly.
466     // We don't create this window when the native theme is used in the content process because
467     // NSWindow creation runs into the sandbox and because we never run default buttons in content
468     // processes anyway.
469     mCellDrawWindow = [[MOZCellDrawWindow alloc] initWithContentRect:NSZeroRect
470                                                            styleMask:NSWindowStyleMaskBorderless
471                                                              backing:NSBackingStoreBuffered
472                                                                defer:NO];
473     [mCellDrawWindow.contentView addSubview:mCellDrawView];
474   }
476   NS_OBJC_END_TRY_IGNORE_BLOCK;
479 nsNativeThemeCocoa::~nsNativeThemeCocoa() {
480   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
482   [mMeterBarCell release];
483   [mProgressBarCell release];
484   [mDisclosureButtonCell release];
485   [mHelpButtonCell release];
486   [mPushButtonCell release];
487   [mRadioButtonCell release];
488   [mCheckboxCell release];
489   [mTextFieldCell release];
490   [mSearchFieldCell release];
491   [mDropdownCell release];
492   [mComboBoxCell release];
493   [mTreeHeaderCell release];
494   [mCellDrawWindow release];
495   [mCellDrawView release];
497   NS_OBJC_END_TRY_IGNORE_BLOCK;
500 // Limit on the area of the target rect (in pixels^2) in
501 // DrawCellWithScaling() and DrawButton() and above which we
502 // don't draw the object into a bitmap buffer.  This is to avoid crashes in
503 // [NSGraphicsContext graphicsContextWithGraphicsPort:flipped:] and
504 // CGContextDrawImage(), and also to avoid very poor drawing performance in
505 // CGContextDrawImage() when it scales the bitmap (particularly if xscale or
506 // yscale is less than but near 1 -- e.g. 0.9).  This value was determined
507 // by trial and error, on OS X 10.4.11 and 10.5.4, and on systems with
508 // different amounts of RAM.
509 #define BITMAP_MAX_AREA 500000
511 static int GetBackingScaleFactorForRendering(CGContextRef cgContext) {
512   CGAffineTransform ctm = CGContextGetUserSpaceToDeviceSpaceTransform(cgContext);
513   CGRect transformedUserSpacePixel = CGRectApplyAffineTransform(CGRectMake(0, 0, 1, 1), ctm);
514   float maxScale = std::max(fabs(transformedUserSpacePixel.size.width),
515                             fabs(transformedUserSpacePixel.size.height));
516   return maxScale > 1.0 ? 2 : 1;
520  * Draw the given NSCell into the given cgContext.
522  * destRect - the size and position of the resulting control rectangle
523  * controlSize - the NSControlSize which will be given to the NSCell before
524  *  asking it to render
525  * naturalSize - The natural dimensions of this control.
526  *  If the control rect size is not equal to either of these, a scale
527  *  will be applied to the context so that rendering the control at the
528  *  natural size will result in it filling the destRect space.
529  *  If a control has no natural dimensions in either/both axes, pass 0.0f.
530  * minimumSize - The minimum dimensions of this control.
531  *  If the control rect size is less than the minimum for a given axis,
532  *  a scale will be applied to the context so that the minimum is used
533  *  for drawing.  If a control has no minimum dimensions in either/both
534  *  axes, pass 0.0f.
535  * marginSet - an array of margins; a multidimensional array of [2][3][4],
536  *  with the first dimension being the OS version (Tiger or Leopard),
537  *  the second being the control size (mini, small, regular), and the third
538  *  being the 4 margin values (left, top, right, bottom).
539  * view - The NSView that we're drawing into. As far as I can tell, it doesn't
540  *  matter if this is really the right view; it just has to return YES when
541  *  asked for isFlipped. Otherwise we'll get drawing bugs on 10.4.
542  * mirrorHorizontal - whether to mirror the cell horizontally
543  */
544 static void DrawCellWithScaling(NSCell* cell, CGContextRef cgContext, const HIRect& destRect,
545                                 NSControlSize controlSize, NSSize naturalSize, NSSize minimumSize,
546                                 const float marginSet[][3][4], NSView* view,
547                                 BOOL mirrorHorizontal) {
548   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
550   NSRect drawRect =
551       NSMakeRect(destRect.origin.x, destRect.origin.y, destRect.size.width, destRect.size.height);
553   if (naturalSize.width != 0.0f) drawRect.size.width = naturalSize.width;
554   if (naturalSize.height != 0.0f) drawRect.size.height = naturalSize.height;
556   // Keep aspect ratio when scaling if one dimension is free.
557   if (naturalSize.width == 0.0f && naturalSize.height != 0.0f)
558     drawRect.size.width = destRect.size.width * naturalSize.height / destRect.size.height;
559   if (naturalSize.height == 0.0f && naturalSize.width != 0.0f)
560     drawRect.size.height = destRect.size.height * naturalSize.width / destRect.size.width;
562   // Honor minimum sizes.
563   if (drawRect.size.width < minimumSize.width) drawRect.size.width = minimumSize.width;
564   if (drawRect.size.height < minimumSize.height) drawRect.size.height = minimumSize.height;
566   [NSGraphicsContext saveGraphicsState];
568   // Only skip the buffer if the area of our cell (in pixels^2) is too large.
569   if (drawRect.size.width * drawRect.size.height > BITMAP_MAX_AREA) {
570     // Inflate the rect Gecko gave us by the margin for the control.
571     InflateControlRect(&drawRect, controlSize, marginSet);
573     NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
574     [NSGraphicsContext
575         setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:cgContext
576                                                                      flipped:YES]];
578     DrawCellIncludingFocusRing(cell, drawRect, view);
580     [NSGraphicsContext setCurrentContext:savedContext];
581   } else {
582     float w = ceil(drawRect.size.width);
583     float h = ceil(drawRect.size.height);
584     NSRect tmpRect = NSMakeRect(kMaxFocusRingWidth, kMaxFocusRingWidth, w, h);
586     // inflate to figure out the frame we need to tell NSCell to draw in, to get something that's
587     // 0,0,w,h
588     InflateControlRect(&tmpRect, controlSize, marginSet);
590     // and then, expand by kMaxFocusRingWidth size to make sure we can capture any focus ring
591     w += kMaxFocusRingWidth * 2.0;
592     h += kMaxFocusRingWidth * 2.0;
594     int backingScaleFactor = GetBackingScaleFactorForRendering(cgContext);
595     CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
596     CGContextRef ctx = CGBitmapContextCreate(
597         NULL, (int)w * backingScaleFactor, (int)h * backingScaleFactor, 8,
598         (int)w * backingScaleFactor * 4, rgb, kCGImageAlphaPremultipliedFirst);
599     CGColorSpaceRelease(rgb);
601     // We need to flip the image twice in order to avoid drawing bugs on 10.4, see bug 465069.
602     // This is the first flip transform, applied to cgContext.
603     CGContextScaleCTM(cgContext, 1.0f, -1.0f);
604     CGContextTranslateCTM(cgContext, 0.0f, -(2.0 * destRect.origin.y + destRect.size.height));
605     if (mirrorHorizontal) {
606       CGContextScaleCTM(cgContext, -1.0f, 1.0f);
607       CGContextTranslateCTM(cgContext, -(2.0 * destRect.origin.x + destRect.size.width), 0.0f);
608     }
610     NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
611     [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:ctx
612                                                                                     flipped:YES]];
614     CGContextScaleCTM(ctx, backingScaleFactor, backingScaleFactor);
616     // Set the context's "base transform" to in order to get correctly-sized focus rings.
617     CGContextSetBaseCTM(ctx, CGAffineTransformMakeScale(backingScaleFactor, backingScaleFactor));
619     // This is the second flip transform, applied to ctx.
620     CGContextScaleCTM(ctx, 1.0f, -1.0f);
621     CGContextTranslateCTM(ctx, 0.0f, -(2.0 * tmpRect.origin.y + tmpRect.size.height));
623     DrawCellIncludingFocusRing(cell, tmpRect, view);
625     [NSGraphicsContext setCurrentContext:savedContext];
627     CGImageRef img = CGBitmapContextCreateImage(ctx);
629     // Drop the image into the original destination rectangle, scaling to fit
630     // Only scale kMaxFocusRingWidth by xscale/yscale when the resulting rect
631     // doesn't extend beyond the overflow rect
632     float xscale = destRect.size.width / drawRect.size.width;
633     float yscale = destRect.size.height / drawRect.size.height;
634     float scaledFocusRingX = xscale < 1.0f ? kMaxFocusRingWidth * xscale : kMaxFocusRingWidth;
635     float scaledFocusRingY = yscale < 1.0f ? kMaxFocusRingWidth * yscale : kMaxFocusRingWidth;
636     CGContextDrawImage(
637         cgContext,
638         CGRectMake(destRect.origin.x - scaledFocusRingX, destRect.origin.y - scaledFocusRingY,
639                    destRect.size.width + scaledFocusRingX * 2,
640                    destRect.size.height + scaledFocusRingY * 2),
641         img);
643     CGImageRelease(img);
644     CGContextRelease(ctx);
645   }
647   [NSGraphicsContext restoreGraphicsState];
649 #if DRAW_IN_FRAME_DEBUG
650   CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
651   CGContextFillRect(cgContext, destRect);
652 #endif
654   NS_OBJC_END_TRY_IGNORE_BLOCK;
657 struct CellRenderSettings {
658   // The natural dimensions of the control.
659   // If a control has no natural dimensions in either/both axes, set to 0.0f.
660   NSSize naturalSizes[3];
662   // The minimum dimensions of the control.
663   // If a control has no minimum dimensions in either/both axes, set to 0.0f.
664   NSSize minimumSizes[3];
666   // A three-dimensional array,
667   // with the first dimension being the OS version ([0] 10.6-10.9, [1] 10.10 and above),
668   // the second being the control size (mini, small, regular), and the third
669   // being the 4 margin values (left, top, right, bottom).
670   float margins[2][3][4];
674  * This is a helper method that returns the required NSControlSize given a size
675  * and the size of the three controls plus a tolerance.
676  * size - The width or the height of the element to draw.
677  * sizes - An array with the all the width/height of the element for its
678  *         different sizes.
679  * tolerance - The tolerance as passed to DrawCellWithSnapping.
680  * NOTE: returns NSControlSizeRegular if all values in 'sizes' are zero.
681  */
682 static NSControlSize FindControlSize(CGFloat size, const CGFloat* sizes, CGFloat tolerance) {
683   for (uint32_t i = miniControlSize; i <= regularControlSize; ++i) {
684     if (sizes[i] == 0) {
685       continue;
686     }
688     CGFloat next = 0;
689     // Find next value.
690     for (uint32_t j = i + 1; j <= regularControlSize; ++j) {
691       if (sizes[j] != 0) {
692         next = sizes[j];
693         break;
694       }
695     }
697     // If it's the latest value, we pick it.
698     if (next == 0) {
699       return CocoaSizeForEnum(i);
700     }
702     if (size <= sizes[i] + tolerance && size < next) {
703       return CocoaSizeForEnum(i);
704     }
705   }
707   // If we are here, that means sizes[] was an array with only empty values
708   // or the algorithm above is wrong.
709   // The former can happen but the later would be wrong.
710   NS_ASSERTION(sizes[0] == 0 && sizes[1] == 0 && sizes[2] == 0,
711                "We found no control! We shouldn't be there!");
712   return CocoaSizeForEnum(regularControlSize);
716  * Draw the given NSCell into the given cgContext with a nice control size.
718  * This function is similar to DrawCellWithScaling, but it decides what
719  * control size to use based on the destRect's size.
720  * Scaling is only applied when the difference between the destRect's size
721  * and the next smaller natural size is greater than snapTolerance. Otherwise
722  * it snaps to the next smaller control size without scaling because unscaled
723  * controls look nicer.
724  */
725 static void DrawCellWithSnapping(NSCell* cell, CGContextRef cgContext, const HIRect& destRect,
726                                  const CellRenderSettings settings, float verticalAlignFactor,
727                                  NSView* view, BOOL mirrorHorizontal, float snapTolerance = 2.0f) {
728   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
730   const float rectWidth = destRect.size.width, rectHeight = destRect.size.height;
731   const NSSize* sizes = settings.naturalSizes;
732   const NSSize miniSize = sizes[EnumSizeForCocoaSize(NSControlSizeMini)];
733   const NSSize smallSize = sizes[EnumSizeForCocoaSize(NSControlSizeSmall)];
734   const NSSize regularSize = sizes[EnumSizeForCocoaSize(NSControlSizeRegular)];
736   HIRect drawRect = destRect;
738   CGFloat controlWidths[3] = {miniSize.width, smallSize.width, regularSize.width};
739   NSControlSize controlSizeX = FindControlSize(rectWidth, controlWidths, snapTolerance);
740   CGFloat controlHeights[3] = {miniSize.height, smallSize.height, regularSize.height};
741   NSControlSize controlSizeY = FindControlSize(rectHeight, controlHeights, snapTolerance);
743   NSControlSize controlSize = NSControlSizeRegular;
744   size_t sizeIndex = 0;
746   // At some sizes, don't scale but snap.
747   const NSControlSize smallerControlSize =
748       EnumSizeForCocoaSize(controlSizeX) < EnumSizeForCocoaSize(controlSizeY) ? controlSizeX
749                                                                               : controlSizeY;
750   const size_t smallerControlSizeIndex = EnumSizeForCocoaSize(smallerControlSize);
751   const NSSize size = sizes[smallerControlSizeIndex];
752   float diffWidth = size.width ? rectWidth - size.width : 0.0f;
753   float diffHeight = size.height ? rectHeight - size.height : 0.0f;
754   if (diffWidth >= 0.0f && diffHeight >= 0.0f && diffWidth <= snapTolerance &&
755       diffHeight <= snapTolerance) {
756     // Snap to the smaller control size.
757     controlSize = smallerControlSize;
758     sizeIndex = smallerControlSizeIndex;
759     MOZ_ASSERT(sizeIndex < ArrayLength(settings.naturalSizes));
761     // Resize and center the drawRect.
762     if (sizes[sizeIndex].width) {
763       drawRect.origin.x += ceil((destRect.size.width - sizes[sizeIndex].width) / 2);
764       drawRect.size.width = sizes[sizeIndex].width;
765     }
766     if (sizes[sizeIndex].height) {
767       drawRect.origin.y +=
768           floor((destRect.size.height - sizes[sizeIndex].height) * verticalAlignFactor);
769       drawRect.size.height = sizes[sizeIndex].height;
770     }
771   } else {
772     // Use the larger control size.
773     controlSize = EnumSizeForCocoaSize(controlSizeX) > EnumSizeForCocoaSize(controlSizeY)
774                       ? controlSizeX
775                       : controlSizeY;
776     sizeIndex = EnumSizeForCocoaSize(controlSize);
777   }
779   [cell setControlSize:controlSize];
781   MOZ_ASSERT(sizeIndex < ArrayLength(settings.minimumSizes));
782   const NSSize minimumSize = settings.minimumSizes[sizeIndex];
783   DrawCellWithScaling(cell, cgContext, drawRect, controlSize, sizes[sizeIndex], minimumSize,
784                       settings.margins, view, mirrorHorizontal);
786   NS_OBJC_END_TRY_IGNORE_BLOCK;
789 @interface NSWindow (CoreUIRendererPrivate)
790 + (CUIRendererRef)coreUIRenderer;
791 @end
793 @interface NSObject (NSAppearanceCoreUIRendering)
794 - (void)_drawInRect:(CGRect)rect context:(CGContextRef)cgContext options:(id)options;
795 @end
797 static void RenderWithCoreUI(CGRect aRect, CGContextRef cgContext, NSDictionary* aOptions,
798                              bool aSkipAreaCheck = false) {
799   if (!aSkipAreaCheck && aRect.size.width * aRect.size.height > BITMAP_MAX_AREA) {
800     return;
801   }
803   NSAppearance* appearance = NSAppearance.currentAppearance;
804   if (appearance && [appearance respondsToSelector:@selector(_drawInRect:context:options:)]) {
805     // Render through NSAppearance on Mac OS 10.10 and up. This will call
806     // CUIDraw with a CoreUI renderer that will give us the correct 10.10
807     // style. Calling CUIDraw directly with [NSWindow coreUIRenderer] still
808     // renders 10.9-style widgets on 10.10.
809     [appearance _drawInRect:aRect context:cgContext options:aOptions];
810   } else {
811     // 10.9 and below
812     CUIRendererRef renderer =
813         [NSWindow respondsToSelector:@selector(coreUIRenderer)] ? [NSWindow coreUIRenderer] : nil;
814     CUIDraw(renderer, aRect, cgContext, (CFDictionaryRef)aOptions, NULL);
815   }
818 static float VerticalAlignFactor(nsIFrame* aFrame) {
819   if (!aFrame) return 0.5f;  // default: center
821   const auto& va = aFrame->StyleDisplay()->mVerticalAlign;
822   auto kw = va.IsKeyword() ? va.AsKeyword() : StyleVerticalAlignKeyword::Middle;
823   switch (kw) {
824     case StyleVerticalAlignKeyword::Top:
825     case StyleVerticalAlignKeyword::TextTop:
826       return 0.0f;
828     case StyleVerticalAlignKeyword::Sub:
829     case StyleVerticalAlignKeyword::Super:
830     case StyleVerticalAlignKeyword::Middle:
831     case StyleVerticalAlignKeyword::MozMiddleWithBaseline:
832       return 0.5f;
834     case StyleVerticalAlignKeyword::Baseline:
835     case StyleVerticalAlignKeyword::Bottom:
836     case StyleVerticalAlignKeyword::TextBottom:
837       return 1.0f;
839     default:
840       MOZ_ASSERT_UNREACHABLE("invalid vertical-align");
841       return 0.5f;
842   }
845 static void ApplyControlParamsToNSCell(nsNativeThemeCocoa::ControlParams aControlParams,
846                                        NSCell* aCell) {
847   [aCell setEnabled:!aControlParams.disabled];
848   [aCell setShowsFirstResponder:(aControlParams.focused && !aControlParams.disabled &&
849                                  aControlParams.insideActiveWindow)];
850   [aCell setHighlighted:aControlParams.pressed];
853 // These are the sizes that Gecko needs to request to draw if it wants
854 // to get a standard-sized Aqua radio button drawn. Note that the rects
855 // that draw these are actually a little bigger.
856 static const CellRenderSettings radioSettings = {{
857                                                      NSMakeSize(11, 11),  // mini
858                                                      NSMakeSize(13, 13),  // small
859                                                      NSMakeSize(16, 16)   // regular
860                                                  },
861                                                  {NSZeroSize, NSZeroSize, NSZeroSize},
862                                                  {{
863                                                       // Leopard
864                                                       {0, 0, 0, 0},  // mini
865                                                       {0, 1, 1, 1},  // small
866                                                       {0, 0, 0, 0}   // regular
867                                                   },
868                                                   {
869                                                       // Yosemite
870                                                       {0, 0, 0, 0},  // mini
871                                                       {1, 1, 1, 2},  // small
872                                                       {0, 0, 0, 0}   // regular
873                                                   }}};
875 static const CellRenderSettings checkboxSettings = {{
876                                                         NSMakeSize(11, 11),  // mini
877                                                         NSMakeSize(13, 13),  // small
878                                                         NSMakeSize(16, 16)   // regular
879                                                     },
880                                                     {NSZeroSize, NSZeroSize, NSZeroSize},
881                                                     {{
882                                                          // Leopard
883                                                          {0, 1, 0, 0},  // mini
884                                                          {0, 1, 0, 1},  // small
885                                                          {0, 1, 0, 1}   // regular
886                                                      },
887                                                      {
888                                                          // Yosemite
889                                                          {0, 1, 0, 0},  // mini
890                                                          {0, 1, 0, 1},  // small
891                                                          {0, 1, 0, 1}   // regular
892                                                      }}};
894 static NSCellStateValue CellStateForCheckboxOrRadioState(
895     nsNativeThemeCocoa::CheckboxOrRadioState aState) {
896   switch (aState) {
897     case nsNativeThemeCocoa::CheckboxOrRadioState::eOff:
898       return NSOffState;
899     case nsNativeThemeCocoa::CheckboxOrRadioState::eOn:
900       return NSOnState;
901     case nsNativeThemeCocoa::CheckboxOrRadioState::eIndeterminate:
902       return NSMixedState;
903   }
906 void nsNativeThemeCocoa::DrawCheckboxOrRadio(CGContextRef cgContext, bool inCheckbox,
907                                              const HIRect& inBoxRect,
908                                              const CheckboxOrRadioParams& aParams) {
909   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
911   NSButtonCell* cell = inCheckbox ? mCheckboxCell : mRadioButtonCell;
912   ApplyControlParamsToNSCell(aParams.controlParams, cell);
914   [cell setState:CellStateForCheckboxOrRadioState(aParams.state)];
915   [cell setControlTint:(aParams.controlParams.insideActiveWindow ? [NSColor currentControlTint]
916                                                                  : NSClearControlTint)];
918   // Ensure that the control is square.
919   float length = std::min(inBoxRect.size.width, inBoxRect.size.height);
920   HIRect drawRect = CGRectMake(inBoxRect.origin.x + (int)((inBoxRect.size.width - length) / 2.0f),
921                                inBoxRect.origin.y + (int)((inBoxRect.size.height - length) / 2.0f),
922                                length, length);
924   if (mCellDrawWindow) {
925     mCellDrawWindow.cellsShouldLookActive = aParams.controlParams.insideActiveWindow;
926   }
927   DrawCellWithSnapping(cell, cgContext, drawRect, inCheckbox ? checkboxSettings : radioSettings,
928                        aParams.verticalAlignFactor, mCellDrawView, NO);
930   NS_OBJC_END_TRY_IGNORE_BLOCK;
933 static const CellRenderSettings searchFieldSettings = {{
934                                                            NSMakeSize(0, 16),  // mini
935                                                            NSMakeSize(0, 19),  // small
936                                                            NSMakeSize(0, 22)   // regular
937                                                        },
938                                                        {
939                                                            NSMakeSize(32, 0),  // mini
940                                                            NSMakeSize(38, 0),  // small
941                                                            NSMakeSize(44, 0)   // regular
942                                                        },
943                                                        {{
944                                                             // Leopard
945                                                             {0, 0, 0, 0},  // mini
946                                                             {0, 0, 0, 0},  // small
947                                                             {0, 0, 0, 0}   // regular
948                                                         },
949                                                         {
950                                                             // Yosemite
951                                                             {0, 0, 0, 0},  // mini
952                                                             {0, 0, 0, 0},  // small
953                                                             {0, 0, 0, 0}   // regular
954                                                         }}};
956 static bool IsToolbarStyleContainer(nsIFrame* aFrame) {
957   nsIContent* content = aFrame->GetContent();
958   if (!content) {
959     return false;
960   }
962   if (content->IsAnyOfXULElements(nsGkAtoms::toolbar, nsGkAtoms::toolbox, nsGkAtoms::statusbar)) {
963     return true;
964   }
966   switch (aFrame->StyleDisplay()->EffectiveAppearance()) {
967     case StyleAppearance::Toolbar:
968     case StyleAppearance::Statusbar:
969       return true;
970     default:
971       return false;
972   }
975 static bool IsInsideToolbar(nsIFrame* aFrame) {
976   for (nsIFrame* frame = aFrame; frame; frame = frame->GetParent()) {
977     if (IsToolbarStyleContainer(frame)) {
978       return true;
979     }
980   }
981   return false;
984 nsNativeThemeCocoa::TextFieldParams nsNativeThemeCocoa::ComputeTextFieldParams(
985     nsIFrame* aFrame, EventStates aEventState) {
986   TextFieldParams params;
987   params.insideToolbar = IsInsideToolbar(aFrame);
988   params.disabled = aEventState.HasState(NS_EVENT_STATE_DISABLED);
990   // See ShouldUnconditionallyDrawFocusRingIfFocused.
991   params.focused = aEventState.HasState(NS_EVENT_STATE_FOCUS);
993   params.rtl = IsFrameRTL(aFrame);
994   params.verticalAlignFactor = VerticalAlignFactor(aFrame);
995   return params;
998 void nsNativeThemeCocoa::DrawTextField(CGContextRef cgContext, const HIRect& inBoxRect,
999                                        const TextFieldParams& aParams) {
1000   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1002   NSTextFieldCell* cell = mTextFieldCell;
1003   [cell setEnabled:!aParams.disabled];
1004   [cell setShowsFirstResponder:aParams.focused];
1006   if (mCellDrawWindow) {
1007     mCellDrawWindow.cellsShouldLookActive = YES;  // TODO: propagate correct activeness state
1008   }
1009   DrawCellWithSnapping(cell, cgContext, inBoxRect, searchFieldSettings, aParams.verticalAlignFactor,
1010                        mCellDrawView, aParams.rtl);
1012   NS_OBJC_END_TRY_IGNORE_BLOCK;
1015 void nsNativeThemeCocoa::DrawSearchField(CGContextRef cgContext, const HIRect& inBoxRect,
1016                                          const TextFieldParams& aParams) {
1017   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1019   mSearchFieldCell.enabled = !aParams.disabled;
1020   mSearchFieldCell.showsFirstResponder = aParams.focused;
1021   mSearchFieldCell.placeholderString = @"";
1022   mSearchFieldCell.shouldUseToolbarStyle = aParams.insideToolbar;
1024   if (mCellDrawWindow) {
1025     mCellDrawWindow.cellsShouldLookActive = YES;  // TODO: propagate correct activeness state
1026   }
1027   DrawCellWithSnapping(mSearchFieldCell, cgContext, inBoxRect, searchFieldSettings,
1028                        aParams.verticalAlignFactor, mCellDrawView, aParams.rtl);
1030   NS_OBJC_END_TRY_IGNORE_BLOCK;
1033 static const NSSize kCheckmarkSize = NSMakeSize(11, 11);
1034 static const NSSize kMenuarrowSize = NSMakeSize(9, 10);
1035 static const NSSize kMenuScrollArrowSize = NSMakeSize(10, 8);
1036 static NSString* kCheckmarkImage = @"MenuOnState";
1037 static NSString* kMenuarrowRightImage = @"MenuSubmenu";
1038 static NSString* kMenuarrowLeftImage = @"MenuSubmenuLeft";
1039 static NSString* kMenuDownScrollArrowImage = @"MenuScrollDown";
1040 static NSString* kMenuUpScrollArrowImage = @"MenuScrollUp";
1041 static const CGFloat kMenuIconIndent = 6.0f;
1043 NSString* nsNativeThemeCocoa::GetMenuIconName(const MenuIconParams& aParams) {
1044   switch (aParams.icon) {
1045     case MenuIcon::eCheckmark:
1046       return kCheckmarkImage;
1047     case MenuIcon::eMenuArrow:
1048       return aParams.rtl ? kMenuarrowLeftImage : kMenuarrowRightImage;
1049     case MenuIcon::eMenuDownScrollArrow:
1050       return kMenuDownScrollArrowImage;
1051     case MenuIcon::eMenuUpScrollArrow:
1052       return kMenuUpScrollArrowImage;
1053   }
1056 NSSize nsNativeThemeCocoa::GetMenuIconSize(MenuIcon aIcon) {
1057   switch (aIcon) {
1058     case MenuIcon::eCheckmark:
1059       return kCheckmarkSize;
1060     case MenuIcon::eMenuArrow:
1061       return kMenuarrowSize;
1062     case MenuIcon::eMenuDownScrollArrow:
1063     case MenuIcon::eMenuUpScrollArrow:
1064       return kMenuScrollArrowSize;
1065   }
1068 nsNativeThemeCocoa::MenuIconParams nsNativeThemeCocoa::ComputeMenuIconParams(
1069     nsIFrame* aFrame, EventStates aEventState, MenuIcon aIcon) {
1070   bool isDisabled = aEventState.HasState(NS_EVENT_STATE_DISABLED);
1072   MenuIconParams params;
1073   params.icon = aIcon;
1074   params.disabled = isDisabled;
1075   params.insideActiveMenuItem = !isDisabled && CheckBooleanAttr(aFrame, nsGkAtoms::menuactive);
1076   params.centerHorizontally = true;
1077   params.rtl = IsFrameRTL(aFrame);
1078   return params;
1081 void nsNativeThemeCocoa::DrawMenuIcon(CGContextRef cgContext, const CGRect& aRect,
1082                                       const MenuIconParams& aParams) {
1083   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1085   NSSize size = GetMenuIconSize(aParams.icon);
1087   // Adjust size and position of our drawRect.
1088   CGFloat paddingX = std::max(CGFloat(0.0), aRect.size.width - size.width);
1089   CGFloat paddingY = std::max(CGFloat(0.0), aRect.size.height - size.height);
1090   CGFloat paddingStartX = std::min(paddingX, kMenuIconIndent);
1091   CGFloat paddingEndX = std::max(CGFloat(0.0), paddingX - kMenuIconIndent);
1092   CGRect drawRect = CGRectMake(aRect.origin.x + (aParams.centerHorizontally ? ceil(paddingX / 2)
1093                                                  : aParams.rtl              ? paddingEndX
1094                                                                             : paddingStartX),
1095                                aRect.origin.y + ceil(paddingY / 2), size.width, size.height);
1097   NSString* state =
1098       aParams.disabled ? @"disabled" : (aParams.insideActiveMenuItem ? @"pressed" : @"normal");
1100   NSString* imageName = GetMenuIconName(aParams);
1102   RenderWithCoreUI(
1103       drawRect, cgContext,
1104       [NSDictionary dictionaryWithObjectsAndKeys:@"kCUIBackgroundTypeMenu", @"backgroundTypeKey",
1105                                                  imageName, @"imageNameKey", state, @"state",
1106                                                  @"image", @"widget", [NSNumber numberWithBool:YES],
1107                                                  @"is.flipped", nil]);
1109 #if DRAW_IN_FRAME_DEBUG
1110   CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
1111   CGContextFillRect(cgContext, drawRect);
1112 #endif
1114   NS_OBJC_END_TRY_IGNORE_BLOCK;
1117 nsNativeThemeCocoa::MenuItemParams nsNativeThemeCocoa::ComputeMenuItemParams(
1118     nsIFrame* aFrame, EventStates aEventState, bool aIsChecked) {
1119   bool isDisabled = aEventState.HasState(NS_EVENT_STATE_DISABLED);
1121   MenuItemParams params;
1122   params.checked = aIsChecked;
1123   params.disabled = isDisabled;
1124   params.selected = !isDisabled && CheckBooleanAttr(aFrame, nsGkAtoms::menuactive);
1125   params.rtl = IsFrameRTL(aFrame);
1126   return params;
1129 void nsNativeThemeCocoa::DrawMenuItem(CGContextRef cgContext, const CGRect& inBoxRect,
1130                                       const MenuItemParams& aParams) {
1131   if (aParams.checked) {
1132     MenuIconParams params;
1133     params.disabled = aParams.disabled;
1134     params.insideActiveMenuItem = aParams.selected;
1135     params.rtl = aParams.rtl;
1136     params.icon = MenuIcon::eCheckmark;
1137     DrawMenuIcon(cgContext, inBoxRect, params);
1138   }
1141 void nsNativeThemeCocoa::DrawMenuSeparator(CGContextRef cgContext, const CGRect& inBoxRect,
1142                                            const MenuItemParams& aParams) {
1143   // Workaround for visual artifacts issues with
1144   // HIThemeDrawMenuSeparator on macOS Big Sur.
1145   if (nsCocoaFeatures::OnBigSurOrLater()) {
1146     CGRect separatorRect = inBoxRect;
1147     separatorRect.size.height = 1;
1148     separatorRect.size.width -= 42;
1149     separatorRect.origin.x += 21;
1150     // Use transparent black with an alpha similar to the native separator.
1151     // The values 231 (menu background) and 205 (separator color) have been
1152     // sampled from a window screenshot of a native context menu.
1153     CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.0, (231 - 205) / 231.0);
1154     CGContextFillRect(cgContext, separatorRect);
1155     return;
1156   }
1158   ThemeMenuState menuState;
1159   if (aParams.disabled) {
1160     menuState = kThemeMenuDisabled;
1161   } else {
1162     menuState = aParams.selected ? kThemeMenuSelected : kThemeMenuActive;
1163   }
1165   HIThemeMenuItemDrawInfo midi = {0, kThemeMenuItemPlain, menuState};
1166   HIThemeDrawMenuSeparator(&inBoxRect, &inBoxRect, &midi, cgContext, HITHEME_ORIENTATION);
1169 static bool ShouldUnconditionallyDrawFocusRingIfFocused(nsIFrame* aFrame) {
1170   // Mac always draws focus rings for textboxes and lists.
1171   switch (aFrame->StyleDisplay()->EffectiveAppearance()) {
1172     case StyleAppearance::NumberInput:
1173     case StyleAppearance::Textfield:
1174     case StyleAppearance::Textarea:
1175     case StyleAppearance::Searchfield:
1176     case StyleAppearance::Listbox:
1177       return true;
1178     default:
1179       return false;
1180   }
1183 nsNativeThemeCocoa::ControlParams nsNativeThemeCocoa::ComputeControlParams(
1184     nsIFrame* aFrame, EventStates aEventState) {
1185   ControlParams params;
1186   params.disabled = aEventState.HasState(NS_EVENT_STATE_DISABLED);
1187   params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
1188   params.pressed = aEventState.HasAllStates(NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_HOVER);
1189   params.focused = aEventState.HasState(NS_EVENT_STATE_FOCUS) &&
1190                    (aEventState.HasState(NS_EVENT_STATE_FOCUSRING) ||
1191                     ShouldUnconditionallyDrawFocusRingIfFocused(aFrame));
1192   params.rtl = IsFrameRTL(aFrame);
1193   return params;
1196 static const NSSize kHelpButtonSize = NSMakeSize(20, 20);
1197 static const NSSize kDisclosureButtonSize = NSMakeSize(21, 21);
1199 static const CellRenderSettings pushButtonSettings = {{
1200                                                           NSMakeSize(0, 16),  // mini
1201                                                           NSMakeSize(0, 19),  // small
1202                                                           NSMakeSize(0, 22)   // regular
1203                                                       },
1204                                                       {
1205                                                           NSMakeSize(18, 0),  // mini
1206                                                           NSMakeSize(26, 0),  // small
1207                                                           NSMakeSize(30, 0)   // regular
1208                                                       },
1209                                                       {{
1210                                                            // Leopard
1211                                                            {0, 0, 0, 0},  // mini
1212                                                            {4, 0, 4, 1},  // small
1213                                                            {5, 0, 5, 2}   // regular
1214                                                        },
1215                                                        {
1216                                                            // Yosemite
1217                                                            {0, 0, 0, 0},  // mini
1218                                                            {4, 0, 4, 1},  // small
1219                                                            {5, 0, 5, 2}   // regular
1220                                                        }}};
1222 // The height at which we start doing square buttons instead of rounded buttons
1223 // Rounded buttons look bad if drawn at a height greater than 26, so at that point
1224 // we switch over to doing square buttons which looks fine at any size.
1225 #define DO_SQUARE_BUTTON_HEIGHT 26
1227 void nsNativeThemeCocoa::DrawPushButton(CGContextRef cgContext, const HIRect& inBoxRect,
1228                                         ButtonType aButtonType, ControlParams aControlParams) {
1229   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1231   ApplyControlParamsToNSCell(aControlParams, mPushButtonCell);
1232   [mPushButtonCell setBezelStyle:NSRoundedBezelStyle];
1233   mPushButtonCell.keyEquivalent = aButtonType == ButtonType::eDefaultPushButton ? @"\r" : @"";
1235   if (mCellDrawWindow) {
1236     mCellDrawWindow.cellsShouldLookActive = aControlParams.insideActiveWindow;
1237   }
1238   DrawCellWithSnapping(mPushButtonCell, cgContext, inBoxRect, pushButtonSettings, 0.5f,
1239                        mCellDrawView, aControlParams.rtl, 1.0f);
1241   NS_OBJC_END_TRY_IGNORE_BLOCK;
1244 void nsNativeThemeCocoa::DrawSquareBezelPushButton(CGContextRef cgContext, const HIRect& inBoxRect,
1245                                                    ControlParams aControlParams) {
1246   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1248   ApplyControlParamsToNSCell(aControlParams, mPushButtonCell);
1249   [mPushButtonCell setBezelStyle:NSShadowlessSquareBezelStyle];
1251   if (mCellDrawWindow) {
1252     mCellDrawWindow.cellsShouldLookActive = aControlParams.insideActiveWindow;
1253   }
1254   DrawCellWithScaling(mPushButtonCell, cgContext, inBoxRect, NSControlSizeRegular, NSZeroSize,
1255                       NSMakeSize(14, 0), NULL, mCellDrawView, aControlParams.rtl);
1257   NS_OBJC_END_TRY_IGNORE_BLOCK;
1260 void nsNativeThemeCocoa::DrawHelpButton(CGContextRef cgContext, const HIRect& inBoxRect,
1261                                         ControlParams aControlParams) {
1262   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1264   ApplyControlParamsToNSCell(aControlParams, mHelpButtonCell);
1266   if (mCellDrawWindow) {
1267     mCellDrawWindow.cellsShouldLookActive = aControlParams.insideActiveWindow;
1268   }
1269   DrawCellWithScaling(mHelpButtonCell, cgContext, inBoxRect, NSControlSizeRegular, NSZeroSize,
1270                       kHelpButtonSize, NULL, mCellDrawView,
1271                       false);  // Don't mirror icon in RTL.
1273   NS_OBJC_END_TRY_IGNORE_BLOCK;
1276 void nsNativeThemeCocoa::DrawDisclosureButton(CGContextRef cgContext, const HIRect& inBoxRect,
1277                                               ControlParams aControlParams,
1278                                               NSCellStateValue aCellState) {
1279   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1281   ApplyControlParamsToNSCell(aControlParams, mDisclosureButtonCell);
1282   [mDisclosureButtonCell setState:aCellState];
1284   if (mCellDrawWindow) {
1285     mCellDrawWindow.cellsShouldLookActive = aControlParams.insideActiveWindow;
1286   }
1287   DrawCellWithScaling(mDisclosureButtonCell, cgContext, inBoxRect, NSControlSizeRegular, NSZeroSize,
1288                       kDisclosureButtonSize, NULL, mCellDrawView,
1289                       false);  // Don't mirror icon in RTL.
1291   NS_OBJC_END_TRY_IGNORE_BLOCK;
1294 void nsNativeThemeCocoa::DrawFocusOutline(CGContextRef cgContext, const HIRect& inBoxRect) {
1295   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1296   NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
1297   [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:cgContext
1298                                                                                   flipped:YES]];
1299   CGContextSaveGState(cgContext);
1300   NSSetFocusRingStyle(NSFocusRingOnly);
1301   NSRectFill(NSRectFromCGRect(inBoxRect));
1302   CGContextRestoreGState(cgContext);
1303   [NSGraphicsContext setCurrentContext:savedContext];
1305   NS_OBJC_END_TRY_IGNORE_BLOCK;
1308 typedef void (*RenderHIThemeControlFunction)(CGContextRef cgContext, const HIRect& aRenderRect,
1309                                              void* aData);
1311 static void RenderTransformedHIThemeControl(CGContextRef aCGContext, const HIRect& aRect,
1312                                             RenderHIThemeControlFunction aFunc, void* aData,
1313                                             BOOL mirrorHorizontally = NO) {
1314   CGAffineTransform savedCTM = CGContextGetCTM(aCGContext);
1315   CGContextTranslateCTM(aCGContext, aRect.origin.x, aRect.origin.y);
1317   bool drawDirect;
1318   HIRect drawRect = aRect;
1319   drawRect.origin = CGPointZero;
1321   if (!mirrorHorizontally && savedCTM.a == 1.0f && savedCTM.b == 0.0f && savedCTM.c == 0.0f &&
1322       (savedCTM.d == 1.0f || savedCTM.d == -1.0f)) {
1323     drawDirect = TRUE;
1324   } else {
1325     drawDirect = FALSE;
1326   }
1328   // Fall back to no bitmap buffer if the area of our control (in pixels^2)
1329   // is too large.
1330   if (drawDirect || (aRect.size.width * aRect.size.height > BITMAP_MAX_AREA)) {
1331     aFunc(aCGContext, drawRect, aData);
1332   } else {
1333     // Inflate the buffer to capture focus rings.
1334     int w = ceil(drawRect.size.width) + 2 * kMaxFocusRingWidth;
1335     int h = ceil(drawRect.size.height) + 2 * kMaxFocusRingWidth;
1337     int backingScaleFactor = GetBackingScaleFactorForRendering(aCGContext);
1338     CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
1339     CGContextRef bitmapctx = CGBitmapContextCreate(
1340         NULL, w * backingScaleFactor, h * backingScaleFactor, 8, w * backingScaleFactor * 4,
1341         colorSpace, kCGImageAlphaPremultipliedFirst);
1342     CGColorSpaceRelease(colorSpace);
1344     CGContextScaleCTM(bitmapctx, backingScaleFactor, backingScaleFactor);
1345     CGContextTranslateCTM(bitmapctx, kMaxFocusRingWidth, kMaxFocusRingWidth);
1347     // Set the context's "base transform" to in order to get correctly-sized focus rings.
1348     CGContextSetBaseCTM(bitmapctx,
1349                         CGAffineTransformMakeScale(backingScaleFactor, backingScaleFactor));
1351     // HITheme always wants to draw into a flipped context, or things
1352     // get confused.
1353     CGContextTranslateCTM(bitmapctx, 0.0f, aRect.size.height);
1354     CGContextScaleCTM(bitmapctx, 1.0f, -1.0f);
1356     aFunc(bitmapctx, drawRect, aData);
1358     CGImageRef bitmap = CGBitmapContextCreateImage(bitmapctx);
1360     CGAffineTransform ctm = CGContextGetCTM(aCGContext);
1362     // We need to unflip, so that we can do a DrawImage without getting a flipped image.
1363     CGContextTranslateCTM(aCGContext, 0.0f, aRect.size.height);
1364     CGContextScaleCTM(aCGContext, 1.0f, -1.0f);
1366     if (mirrorHorizontally) {
1367       CGContextTranslateCTM(aCGContext, aRect.size.width, 0);
1368       CGContextScaleCTM(aCGContext, -1.0f, 1.0f);
1369     }
1371     HIRect inflatedDrawRect = CGRectMake(-kMaxFocusRingWidth, -kMaxFocusRingWidth, w, h);
1372     CGContextDrawImage(aCGContext, inflatedDrawRect, bitmap);
1374     CGContextSetCTM(aCGContext, ctm);
1376     CGImageRelease(bitmap);
1377     CGContextRelease(bitmapctx);
1378   }
1380   CGContextSetCTM(aCGContext, savedCTM);
1383 static void RenderButton(CGContextRef cgContext, const HIRect& aRenderRect, void* aData) {
1384   HIThemeButtonDrawInfo* bdi = (HIThemeButtonDrawInfo*)aData;
1385   HIThemeDrawButton(&aRenderRect, bdi, cgContext, kHIThemeOrientationNormal, NULL);
1388 static ThemeDrawState ToThemeDrawState(const nsNativeThemeCocoa::ControlParams& aParams) {
1389   if (aParams.disabled) {
1390     return kThemeStateUnavailable;
1391   }
1392   if (aParams.pressed) {
1393     return kThemeStatePressed;
1394   }
1395   return kThemeStateActive;
1398 void nsNativeThemeCocoa::DrawHIThemeButton(CGContextRef cgContext, const HIRect& aRect,
1399                                            ThemeButtonKind aKind, ThemeButtonValue aValue,
1400                                            ThemeDrawState aState, ThemeButtonAdornment aAdornment,
1401                                            const ControlParams& aParams) {
1402   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1404   HIThemeButtonDrawInfo bdi;
1405   bdi.version = 0;
1406   bdi.kind = aKind;
1407   bdi.value = aValue;
1408   bdi.state = aState;
1409   bdi.adornment = aAdornment;
1411   if (aParams.focused && aParams.insideActiveWindow) {
1412     bdi.adornment |= kThemeAdornmentFocus;
1413   }
1415   RenderTransformedHIThemeControl(cgContext, aRect, RenderButton, &bdi, aParams.rtl);
1417 #if DRAW_IN_FRAME_DEBUG
1418   CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
1419   CGContextFillRect(cgContext, inBoxRect);
1420 #endif
1422   NS_OBJC_END_TRY_IGNORE_BLOCK;
1425 void nsNativeThemeCocoa::DrawButton(CGContextRef cgContext, const HIRect& inBoxRect,
1426                                     const ButtonParams& aParams) {
1427   ControlParams controlParams = aParams.controlParams;
1429   switch (aParams.button) {
1430     case ButtonType::eRegularPushButton:
1431     case ButtonType::eDefaultPushButton:
1432       DrawPushButton(cgContext, inBoxRect, aParams.button, controlParams);
1433       return;
1434     case ButtonType::eSquareBezelPushButton:
1435       DrawSquareBezelPushButton(cgContext, inBoxRect, controlParams);
1436       return;
1437     case ButtonType::eArrowButton:
1438       DrawHIThemeButton(cgContext, inBoxRect, kThemeArrowButton, kThemeButtonOn,
1439                         kThemeStateUnavailable, kThemeAdornmentArrowDownArrow, controlParams);
1440       return;
1441     case ButtonType::eHelpButton:
1442       DrawHelpButton(cgContext, inBoxRect, controlParams);
1443       return;
1444     case ButtonType::eTreeTwistyPointingRight:
1445       DrawHIThemeButton(cgContext, inBoxRect, kThemeDisclosureButton, kThemeDisclosureRight,
1446                         ToThemeDrawState(controlParams), kThemeAdornmentNone, controlParams);
1447       return;
1448     case ButtonType::eTreeTwistyPointingDown:
1449       DrawHIThemeButton(cgContext, inBoxRect, kThemeDisclosureButton, kThemeDisclosureDown,
1450                         ToThemeDrawState(controlParams), kThemeAdornmentNone, controlParams);
1451       return;
1452     case ButtonType::eDisclosureButtonClosed:
1453       DrawDisclosureButton(cgContext, inBoxRect, controlParams, NSOffState);
1454       return;
1455     case ButtonType::eDisclosureButtonOpen:
1456       DrawDisclosureButton(cgContext, inBoxRect, controlParams, NSOnState);
1457       return;
1458   }
1461 nsNativeThemeCocoa::TreeHeaderCellParams nsNativeThemeCocoa::ComputeTreeHeaderCellParams(
1462     nsIFrame* aFrame, EventStates aEventState) {
1463   TreeHeaderCellParams params;
1464   params.controlParams = ComputeControlParams(aFrame, aEventState);
1465   params.sortDirection = GetTreeSortDirection(aFrame);
1466   params.lastTreeHeaderCell = IsLastTreeHeaderCell(aFrame);
1467   return params;
1470 @interface NSTableHeaderCell (NSTableHeaderCell_setSortable)
1471 // This method has been present in the same form since at least macOS 10.4.
1472 - (void)_setSortable:(BOOL)arg1
1473     showSortIndicator:(BOOL)arg2
1474             ascending:(BOOL)arg3
1475              priority:(NSInteger)arg4
1476      highlightForSort:(BOOL)arg5;
1477 @end
1479 void nsNativeThemeCocoa::DrawTreeHeaderCell(CGContextRef cgContext, const HIRect& inBoxRect,
1480                                             const TreeHeaderCellParams& aParams) {
1481   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1483   // Without clearing the cell's title, it takes on a default value of "Field",
1484   // which is displayed underneath the title set in the front-end.
1485   NSCell* cell = (NSCell*)mTreeHeaderCell;
1486   cell.title = @"";
1488   if ([mTreeHeaderCell respondsToSelector:@selector
1489                        (_setSortable:showSortIndicator:ascending:priority:highlightForSort:)]) {
1490     switch (aParams.sortDirection) {
1491       case eTreeSortDirection_Ascending:
1492         [mTreeHeaderCell _setSortable:YES
1493                     showSortIndicator:YES
1494                             ascending:YES
1495                              priority:0
1496                      highlightForSort:YES];
1497         break;
1498       case eTreeSortDirection_Descending:
1499         [mTreeHeaderCell _setSortable:YES
1500                     showSortIndicator:YES
1501                             ascending:NO
1502                              priority:0
1503                      highlightForSort:YES];
1504         break;
1505       default:
1506         // eTreeSortDirection_Natural
1507         [mTreeHeaderCell _setSortable:YES
1508                     showSortIndicator:NO
1509                             ascending:YES
1510                              priority:0
1511                      highlightForSort:NO];
1512         break;
1513     }
1514   }
1516   mTreeHeaderCell.enabled = !aParams.controlParams.disabled;
1517   mTreeHeaderCell.state =
1518       (mTreeHeaderCell.enabled && aParams.controlParams.pressed) ? NSOnState : NSOffState;
1520   mCellDrawView._drawingEndSeparator = !aParams.lastTreeHeaderCell;
1522   NSGraphicsContext* savedContext = NSGraphicsContext.currentContext;
1523   NSGraphicsContext.currentContext = [NSGraphicsContext graphicsContextWithCGContext:cgContext
1524                                                                              flipped:YES];
1525   DrawCellIncludingFocusRing(mTreeHeaderCell, inBoxRect, mCellDrawView);
1526   NSGraphicsContext.currentContext = savedContext;
1528 #if DRAW_IN_FRAME_DEBUG
1529   CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
1530   CGContextFillRect(cgContext, inBoxRect);
1531 #endif
1533   NS_OBJC_END_TRY_IGNORE_BLOCK;
1536 static const CellRenderSettings dropdownSettings = {{
1537                                                         NSMakeSize(0, 16),  // mini
1538                                                         NSMakeSize(0, 19),  // small
1539                                                         NSMakeSize(0, 22)   // regular
1540                                                     },
1541                                                     {
1542                                                         NSMakeSize(18, 0),  // mini
1543                                                         NSMakeSize(38, 0),  // small
1544                                                         NSMakeSize(44, 0)   // regular
1545                                                     },
1546                                                     {{
1547                                                          // Leopard
1548                                                          {1, 1, 2, 1},  // mini
1549                                                          {3, 0, 3, 1},  // small
1550                                                          {3, 0, 3, 0}   // regular
1551                                                      },
1552                                                      {
1553                                                          // Yosemite
1554                                                          {1, 1, 2, 1},  // mini
1555                                                          {3, 0, 3, 1},  // small
1556                                                          {3, 0, 3, 0}   // regular
1557                                                      }}};
1559 static const CellRenderSettings editableMenulistSettings = {{
1560                                                                 NSMakeSize(0, 15),  // mini
1561                                                                 NSMakeSize(0, 18),  // small
1562                                                                 NSMakeSize(0, 21)   // regular
1563                                                             },
1564                                                             {
1565                                                                 NSMakeSize(18, 0),  // mini
1566                                                                 NSMakeSize(38, 0),  // small
1567                                                                 NSMakeSize(44, 0)   // regular
1568                                                             },
1569                                                             {{
1570                                                                  // Leopard
1571                                                                  {0, 0, 2, 2},  // mini
1572                                                                  {0, 0, 3, 2},  // small
1573                                                                  {0, 1, 3, 3}   // regular
1574                                                              },
1575                                                              {
1576                                                                  // Yosemite
1577                                                                  {0, 0, 2, 2},  // mini
1578                                                                  {0, 0, 3, 2},  // small
1579                                                                  {0, 1, 3, 3}   // regular
1580                                                              }}};
1582 void nsNativeThemeCocoa::DrawDropdown(CGContextRef cgContext, const HIRect& inBoxRect,
1583                                       const DropdownParams& aParams) {
1584   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1586   [mDropdownCell setPullsDown:aParams.pullsDown];
1587   NSCell* cell = aParams.editable ? (NSCell*)mComboBoxCell : (NSCell*)mDropdownCell;
1589   ApplyControlParamsToNSCell(aParams.controlParams, cell);
1591   if (aParams.controlParams.insideActiveWindow) {
1592     [cell setControlTint:[NSColor currentControlTint]];
1593   } else {
1594     [cell setControlTint:NSClearControlTint];
1595   }
1597   const CellRenderSettings& settings =
1598       aParams.editable ? editableMenulistSettings : dropdownSettings;
1600   if (mCellDrawWindow) {
1601     mCellDrawWindow.cellsShouldLookActive = aParams.controlParams.insideActiveWindow;
1602   }
1603   DrawCellWithSnapping(cell, cgContext, inBoxRect, settings, 0.5f, mCellDrawView,
1604                        aParams.controlParams.rtl);
1606   NS_OBJC_END_TRY_IGNORE_BLOCK;
1609 static const CellRenderSettings spinnerSettings = {
1610     {
1611         NSMakeSize(11, 16),  // mini (width trimmed by 2px to reduce blank border)
1612         NSMakeSize(15, 22),  // small
1613         NSMakeSize(19, 27)   // regular
1614     },
1615     {
1616         NSMakeSize(11, 16),  // mini (width trimmed by 2px to reduce blank border)
1617         NSMakeSize(15, 22),  // small
1618         NSMakeSize(19, 27)   // regular
1619     },
1620     {{
1621          // Leopard
1622          {0, 0, 0, 0},  // mini
1623          {0, 0, 0, 0},  // small
1624          {0, 0, 0, 0}   // regular
1625      },
1626      {
1627          // Yosemite
1628          {0, 0, 0, 0},  // mini
1629          {0, 0, 0, 0},  // small
1630          {0, 0, 0, 0}   // regular
1631      }}};
1633 HIThemeButtonDrawInfo nsNativeThemeCocoa::SpinButtonDrawInfo(ThemeButtonKind aKind,
1634                                                              const SpinButtonParams& aParams) {
1635   HIThemeButtonDrawInfo bdi;
1636   bdi.version = 0;
1637   bdi.kind = aKind;
1638   bdi.value = kThemeButtonOff;
1639   bdi.adornment = kThemeAdornmentNone;
1641   if (aParams.disabled) {
1642     bdi.state = kThemeStateUnavailable;
1643   } else if (aParams.insideActiveWindow && aParams.pressedButton) {
1644     if (*aParams.pressedButton == SpinButton::eUp) {
1645       bdi.state = kThemeStatePressedUp;
1646     } else {
1647       bdi.state = kThemeStatePressedDown;
1648     }
1649   } else {
1650     bdi.state = kThemeStateActive;
1651   }
1653   return bdi;
1656 void nsNativeThemeCocoa::DrawSpinButtons(CGContextRef cgContext, const HIRect& inBoxRect,
1657                                          const SpinButtonParams& aParams) {
1658   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1660   HIThemeButtonDrawInfo bdi = SpinButtonDrawInfo(kThemeIncDecButton, aParams);
1661   HIThemeDrawButton(&inBoxRect, &bdi, cgContext, HITHEME_ORIENTATION, NULL);
1663   NS_OBJC_END_TRY_IGNORE_BLOCK;
1666 void nsNativeThemeCocoa::DrawSpinButton(CGContextRef cgContext, const HIRect& inBoxRect,
1667                                         SpinButton aDrawnButton, const SpinButtonParams& aParams) {
1668   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1670   HIThemeButtonDrawInfo bdi = SpinButtonDrawInfo(kThemeIncDecButtonMini, aParams);
1672   // Cocoa only allows kThemeIncDecButton to paint the up and down spin buttons
1673   // together as a single unit (presumably because when one button is active,
1674   // the appearance of both changes (in different ways)). Here we have to paint
1675   // both buttons, using clip to hide the one we don't want to paint.
1676   HIRect drawRect = inBoxRect;
1677   drawRect.size.height *= 2;
1678   if (aDrawnButton == SpinButton::eDown) {
1679     drawRect.origin.y -= inBoxRect.size.height;
1680   }
1682   // Shift the drawing a little to the left, since cocoa paints with more
1683   // blank space around the visual buttons than we'd like:
1684   drawRect.origin.x -= 1;
1686   CGContextSaveGState(cgContext);
1687   CGContextClipToRect(cgContext, inBoxRect);
1689   HIThemeDrawButton(&drawRect, &bdi, cgContext, HITHEME_ORIENTATION, NULL);
1691   CGContextRestoreGState(cgContext);
1693   NS_OBJC_END_TRY_IGNORE_BLOCK;
1696 static const CellRenderSettings progressSettings[2][2] = {
1697     // Vertical progress bar.
1698     {// Determined settings.
1699      {{
1700           NSZeroSize,         // mini
1701           NSMakeSize(10, 0),  // small
1702           NSMakeSize(16, 0)   // regular
1703       },
1704       {NSZeroSize, NSZeroSize, NSZeroSize},
1705       {{
1706           // Leopard
1707           {0, 0, 0, 0},  // mini
1708           {1, 1, 1, 1},  // small
1709           {1, 1, 1, 1}   // regular
1710       }}},
1711      // There is no horizontal margin in regular undetermined size.
1712      {{
1713           NSZeroSize,         // mini
1714           NSMakeSize(10, 0),  // small
1715           NSMakeSize(16, 0)   // regular
1716       },
1717       {NSZeroSize, NSZeroSize, NSZeroSize},
1718       {{
1719            // Leopard
1720            {0, 0, 0, 0},  // mini
1721            {1, 1, 1, 1},  // small
1722            {1, 0, 1, 0}   // regular
1723        },
1724        {
1725            // Yosemite
1726            {0, 0, 0, 0},  // mini
1727            {1, 1, 1, 1},  // small
1728            {1, 0, 1, 0}   // regular
1729        }}}},
1730     // Horizontal progress bar.
1731     {// Determined settings.
1732      {{
1733           NSZeroSize,         // mini
1734           NSMakeSize(0, 10),  // small
1735           NSMakeSize(0, 16)   // regular
1736       },
1737       {NSZeroSize, NSZeroSize, NSZeroSize},
1738       {{
1739            // Leopard
1740            {0, 0, 0, 0},  // mini
1741            {1, 1, 1, 1},  // small
1742            {1, 1, 1, 1}   // regular
1743        },
1744        {
1745            // Yosemite
1746            {0, 0, 0, 0},  // mini
1747            {1, 1, 1, 1},  // small
1748            {1, 1, 1, 1}   // regular
1749        }}},
1750      // There is no horizontal margin in regular undetermined size.
1751      {{
1752           NSZeroSize,         // mini
1753           NSMakeSize(0, 10),  // small
1754           NSMakeSize(0, 16)   // regular
1755       },
1756       {NSZeroSize, NSZeroSize, NSZeroSize},
1757       {{
1758            // Leopard
1759            {0, 0, 0, 0},  // mini
1760            {1, 1, 1, 1},  // small
1761            {0, 1, 0, 1}   // regular
1762        },
1763        {
1764            // Yosemite
1765            {0, 0, 0, 0},  // mini
1766            {1, 1, 1, 1},  // small
1767            {0, 1, 0, 1}   // regular
1768        }}}}};
1770 nsNativeThemeCocoa::ProgressParams nsNativeThemeCocoa::ComputeProgressParams(
1771     nsIFrame* aFrame, EventStates aEventState, bool aIsHorizontal) {
1772   ProgressParams params;
1773   params.value = GetProgressValue(aFrame);
1774   params.max = GetProgressMaxValue(aFrame);
1775   params.verticalAlignFactor = VerticalAlignFactor(aFrame);
1776   params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
1777   params.indeterminate = aEventState.HasState(NS_EVENT_STATE_INDETERMINATE);
1778   params.horizontal = aIsHorizontal;
1779   params.rtl = IsFrameRTL(aFrame);
1780   return params;
1783 void nsNativeThemeCocoa::DrawProgress(CGContextRef cgContext, const HIRect& inBoxRect,
1784                                       const ProgressParams& aParams) {
1785   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1787   NSProgressBarCell* cell = mProgressBarCell;
1789   [cell setValue:aParams.value];
1790   [cell setMax:aParams.max];
1791   [cell setIndeterminate:aParams.indeterminate];
1792   [cell setHorizontal:aParams.horizontal];
1793   [cell setControlTint:(aParams.insideActiveWindow ? [NSColor currentControlTint]
1794                                                    : NSClearControlTint)];
1796   if (mCellDrawWindow) {
1797     mCellDrawWindow.cellsShouldLookActive = aParams.insideActiveWindow;
1798   }
1799   DrawCellWithSnapping(cell, cgContext, inBoxRect,
1800                        progressSettings[aParams.horizontal][aParams.indeterminate],
1801                        aParams.verticalAlignFactor, mCellDrawView, aParams.rtl);
1803   NS_OBJC_END_TRY_IGNORE_BLOCK;
1806 static const CellRenderSettings meterSetting = {{
1807                                                     NSMakeSize(0, 16),  // mini
1808                                                     NSMakeSize(0, 16),  // small
1809                                                     NSMakeSize(0, 16)   // regular
1810                                                 },
1811                                                 {NSZeroSize, NSZeroSize, NSZeroSize},
1812                                                 {{
1813                                                      // Leopard
1814                                                      {1, 1, 1, 1},  // mini
1815                                                      {1, 1, 1, 1},  // small
1816                                                      {1, 1, 1, 1}   // regular
1817                                                  },
1818                                                  {
1819                                                      // Yosemite
1820                                                      {1, 1, 1, 1},  // mini
1821                                                      {1, 1, 1, 1},  // small
1822                                                      {1, 1, 1, 1}   // regular
1823                                                  }}};
1825 nsNativeThemeCocoa::MeterParams nsNativeThemeCocoa::ComputeMeterParams(nsIFrame* aFrame) {
1826   nsIContent* content = aFrame->GetContent();
1827   if (!(content && content->IsHTMLElement(nsGkAtoms::meter))) {
1828     return MeterParams();
1829   }
1831   HTMLMeterElement* meterElement = static_cast<HTMLMeterElement*>(content);
1832   MeterParams params;
1833   params.value = meterElement->Value();
1834   params.min = meterElement->Min();
1835   params.max = meterElement->Max();
1836   EventStates states = meterElement->State();
1837   if (states.HasState(NS_EVENT_STATE_SUB_OPTIMUM)) {
1838     params.optimumState = OptimumState::eSubOptimum;
1839   } else if (states.HasState(NS_EVENT_STATE_SUB_SUB_OPTIMUM)) {
1840     params.optimumState = OptimumState::eSubSubOptimum;
1841   }
1842   params.horizontal = !IsVerticalMeter(aFrame);
1843   params.verticalAlignFactor = VerticalAlignFactor(aFrame);
1844   params.rtl = IsFrameRTL(aFrame);
1846   return params;
1849 void nsNativeThemeCocoa::DrawMeter(CGContextRef cgContext, const HIRect& inBoxRect,
1850                                    const MeterParams& aParams) {
1851   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK
1853   NSLevelIndicatorCell* cell = mMeterBarCell;
1855   [cell setMinValue:aParams.min];
1856   [cell setMaxValue:aParams.max];
1857   [cell setDoubleValue:aParams.value];
1859   /**
1860    * The way HTML and Cocoa defines the meter/indicator widget are different.
1861    * So, we are going to use a trick to get the Cocoa widget showing what we
1862    * are expecting: we set the warningValue or criticalValue to the current
1863    * value when we want to have the widget to be in the warning or critical
1864    * state.
1865    */
1866   switch (aParams.optimumState) {
1867     case OptimumState::eOptimum:
1868       [cell setWarningValue:aParams.max + 1];
1869       [cell setCriticalValue:aParams.max + 1];
1870       break;
1871     case OptimumState::eSubOptimum:
1872       [cell setWarningValue:aParams.value];
1873       [cell setCriticalValue:aParams.max + 1];
1874       break;
1875     case OptimumState::eSubSubOptimum:
1876       [cell setWarningValue:aParams.max + 1];
1877       [cell setCriticalValue:aParams.value];
1878       break;
1879   }
1881   HIRect rect = CGRectStandardize(inBoxRect);
1882   BOOL vertical = !aParams.horizontal;
1884   CGContextSaveGState(cgContext);
1886   if (vertical) {
1887     /**
1888      * Cocoa doesn't provide a vertical meter bar so to show one, we have to
1889      * show a rotated horizontal meter bar.
1890      * Given that we want to show a vertical meter bar, we assume that the rect
1891      * has vertical dimensions but we can't correctly draw a meter widget inside
1892      * such a rectangle so we need to inverse width and height (and re-position)
1893      * to get a rectangle with horizontal dimensions.
1894      * Finally, we want to show a vertical meter so we want to rotate the result
1895      * so it is vertical. We do that by changing the context.
1896      */
1897     CGFloat tmp = rect.size.width;
1898     rect.size.width = rect.size.height;
1899     rect.size.height = tmp;
1900     rect.origin.x += rect.size.height / 2.f - rect.size.width / 2.f;
1901     rect.origin.y += rect.size.width / 2.f - rect.size.height / 2.f;
1903     CGContextTranslateCTM(cgContext, CGRectGetMidX(rect), CGRectGetMidY(rect));
1904     CGContextRotateCTM(cgContext, -M_PI / 2.f);
1905     CGContextTranslateCTM(cgContext, -CGRectGetMidX(rect), -CGRectGetMidY(rect));
1906   }
1908   if (mCellDrawWindow) {
1909     mCellDrawWindow.cellsShouldLookActive = YES;  // TODO: propagate correct activeness state
1910   }
1911   DrawCellWithSnapping(cell, cgContext, rect, meterSetting, aParams.verticalAlignFactor,
1912                        mCellDrawView, !vertical && aParams.rtl);
1914   CGContextRestoreGState(cgContext);
1916   NS_OBJC_END_TRY_IGNORE_BLOCK
1919 void nsNativeThemeCocoa::DrawTabPanel(CGContextRef cgContext, const HIRect& inBoxRect,
1920                                       bool aIsInsideActiveWindow) {
1921   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1923   HIThemeTabPaneDrawInfo tpdi;
1925   tpdi.version = 1;
1926   tpdi.state = aIsInsideActiveWindow ? kThemeStateActive : kThemeStateInactive;
1927   tpdi.direction = kThemeTabNorth;
1928   tpdi.size = kHIThemeTabSizeNormal;
1929   tpdi.kind = kHIThemeTabKindNormal;
1931   HIThemeDrawTabPane(&inBoxRect, &tpdi, cgContext, HITHEME_ORIENTATION);
1933   NS_OBJC_END_TRY_IGNORE_BLOCK;
1936 Maybe<nsNativeThemeCocoa::ScaleParams> nsNativeThemeCocoa::ComputeHTMLScaleParams(
1937     nsIFrame* aFrame, EventStates aEventState) {
1938   nsRangeFrame* rangeFrame = do_QueryFrame(aFrame);
1939   if (!rangeFrame) {
1940     return Nothing();
1941   }
1943   bool isHorizontal = IsRangeHorizontal(aFrame);
1945   // ScaleParams requires integer min, max and value. This is purely for
1946   // drawing, so we normalize to a range 0-1000 here.
1947   ScaleParams params;
1948   params.value = int32_t(rangeFrame->GetValueAsFractionOfRange() * 1000);
1949   params.min = 0;
1950   params.max = 1000;
1951   params.reverse = !isHorizontal || rangeFrame->IsRightToLeft();
1952   params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
1953   params.focused = aEventState.HasState(NS_EVENT_STATE_FOCUSRING);
1954   params.disabled = aEventState.HasState(NS_EVENT_STATE_DISABLED);
1955   params.horizontal = isHorizontal;
1956   return Some(params);
1959 void nsNativeThemeCocoa::DrawScale(CGContextRef cgContext, const HIRect& inBoxRect,
1960                                    const ScaleParams& aParams) {
1961   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1963   HIThemeTrackDrawInfo tdi;
1965   tdi.version = 0;
1966   tdi.kind = kThemeMediumSlider;
1967   tdi.bounds = inBoxRect;
1968   tdi.min = aParams.min;
1969   tdi.max = aParams.max;
1970   tdi.value = aParams.value;
1971   tdi.attributes = kThemeTrackShowThumb;
1972   if (aParams.horizontal) {
1973     tdi.attributes |= kThemeTrackHorizontal;
1974   }
1975   if (aParams.reverse) {
1976     tdi.attributes |= kThemeTrackRightToLeft;
1977   }
1978   if (aParams.focused) {
1979     tdi.attributes |= kThemeTrackHasFocus;
1980   }
1981   if (aParams.disabled) {
1982     tdi.enableState = kThemeTrackDisabled;
1983   } else {
1984     tdi.enableState = aParams.insideActiveWindow ? kThemeTrackActive : kThemeTrackInactive;
1985   }
1986   tdi.trackInfo.slider.thumbDir = kThemeThumbPlain;
1987   tdi.trackInfo.slider.pressState = 0;
1989   HIThemeDrawTrack(&tdi, NULL, cgContext, HITHEME_ORIENTATION);
1991   NS_OBJC_END_TRY_IGNORE_BLOCK;
1994 nsIFrame* nsNativeThemeCocoa::SeparatorResponsibility(nsIFrame* aBefore, nsIFrame* aAfter) {
1995   // Usually a separator is drawn by the segment to the right of the
1996   // separator, but pressed and selected segments have higher priority.
1997   if (!aBefore || !aAfter) return nullptr;
1998   if (IsSelectedButton(aAfter)) return aAfter;
1999   if (IsSelectedButton(aBefore) || IsPressedButton(aBefore)) return aBefore;
2000   return aAfter;
2003 static CGRect SeparatorAdjustedRect(CGRect aRect, nsNativeThemeCocoa::SegmentParams aParams) {
2004   // A separator between two segments should always be located in the leftmost
2005   // pixel column of the segment to the right of the separator, regardless of
2006   // who ends up drawing it.
2007   // CoreUI draws the separators inside the drawing rect.
2008   if (!aParams.atLeftEnd && !aParams.drawsLeftSeparator) {
2009     // The segment to the left of us draws the separator, so we need to make
2010     // room for it.
2011     aRect.origin.x += 1;
2012     aRect.size.width -= 1;
2013   }
2014   if (aParams.drawsRightSeparator) {
2015     // We draw the right separator, so we need to extend the draw rect into the
2016     // segment to our right.
2017     aRect.size.width += 1;
2018   }
2019   return aRect;
2022 static NSString* ToolbarButtonPosition(BOOL aIsFirst, BOOL aIsLast) {
2023   if (aIsFirst) {
2024     if (aIsLast) return @"kCUISegmentPositionOnly";
2025     return @"kCUISegmentPositionFirst";
2026   }
2027   if (aIsLast) return @"kCUISegmentPositionLast";
2028   return @"kCUISegmentPositionMiddle";
2031 struct SegmentedControlRenderSettings {
2032   const CGFloat* heights;
2033   const NSString* widgetName;
2036 static const CGFloat tabHeights[3] = {17, 20, 23};
2038 static const SegmentedControlRenderSettings tabRenderSettings = {tabHeights, @"tab"};
2040 static const CGFloat toolbarButtonHeights[3] = {15, 18, 22};
2042 static const SegmentedControlRenderSettings toolbarButtonRenderSettings = {
2043     toolbarButtonHeights, @"kCUIWidgetButtonSegmentedSCurve"};
2045 nsNativeThemeCocoa::SegmentParams nsNativeThemeCocoa::ComputeSegmentParams(
2046     nsIFrame* aFrame, EventStates aEventState, SegmentType aSegmentType) {
2047   SegmentParams params;
2048   params.segmentType = aSegmentType;
2049   params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
2050   params.pressed = IsPressedButton(aFrame);
2051   params.selected = IsSelectedButton(aFrame);
2052   params.focused = aEventState.HasState(NS_EVENT_STATE_FOCUSRING);
2053   bool isRTL = IsFrameRTL(aFrame);
2054   nsIFrame* left = GetAdjacentSiblingFrameWithSameAppearance(aFrame, isRTL);
2055   nsIFrame* right = GetAdjacentSiblingFrameWithSameAppearance(aFrame, !isRTL);
2056   params.atLeftEnd = !left;
2057   params.atRightEnd = !right;
2058   params.drawsLeftSeparator = SeparatorResponsibility(left, aFrame) == aFrame;
2059   params.drawsRightSeparator = SeparatorResponsibility(aFrame, right) == aFrame;
2060   params.rtl = isRTL;
2061   return params;
2064 static SegmentedControlRenderSettings RenderSettingsForSegmentType(
2065     nsNativeThemeCocoa::SegmentType aSegmentType) {
2066   switch (aSegmentType) {
2067     case nsNativeThemeCocoa::SegmentType::eToolbarButton:
2068       return toolbarButtonRenderSettings;
2069     case nsNativeThemeCocoa::SegmentType::eTab:
2070       return tabRenderSettings;
2071   }
2074 void nsNativeThemeCocoa::DrawSegment(CGContextRef cgContext, const HIRect& inBoxRect,
2075                                      const SegmentParams& aParams) {
2076   SegmentedControlRenderSettings renderSettings = RenderSettingsForSegmentType(aParams.segmentType);
2077   NSControlSize controlSize = FindControlSize(inBoxRect.size.height, renderSettings.heights, 4.0f);
2078   CGRect drawRect = SeparatorAdjustedRect(inBoxRect, aParams);
2080   NSDictionary* dict = @{
2081     @"widget" : renderSettings.widgetName,
2082     @"kCUIPresentationStateKey" : (aParams.insideActiveWindow ? @"kCUIPresentationStateActiveKey"
2083                                                               : @"kCUIPresentationStateInactive"),
2084     @"kCUIPositionKey" : ToolbarButtonPosition(aParams.atLeftEnd, aParams.atRightEnd),
2085     @"kCUISegmentLeadingSeparatorKey" : [NSNumber numberWithBool:aParams.drawsLeftSeparator],
2086     @"kCUISegmentTrailingSeparatorKey" : [NSNumber numberWithBool:aParams.drawsRightSeparator],
2087     @"value" : [NSNumber numberWithBool:aParams.selected],
2088     @"state" :
2089         (aParams.pressed ? @"pressed" : (aParams.insideActiveWindow ? @"normal" : @"inactive")),
2090     @"focus" : [NSNumber numberWithBool:aParams.focused],
2091     @"size" : CUIControlSizeForCocoaSize(controlSize),
2092     @"is.flipped" : [NSNumber numberWithBool:YES],
2093     @"direction" : @"up"
2094   };
2096   RenderWithCoreUI(drawRect, cgContext, dict);
2099 void nsNativeThemeCocoa::DrawToolbar(CGContextRef cgContext, const CGRect& inBoxRect,
2100                                      bool aIsMain) {
2101   CGRect drawRect = inBoxRect;
2103   // top border
2104   drawRect.size.height = 1.0f;
2105   DrawNativeGreyColorInRect(cgContext, toolbarTopBorderGrey, drawRect, aIsMain);
2107   // background
2108   drawRect.origin.y += drawRect.size.height;
2109   drawRect.size.height = inBoxRect.size.height - 2.0f;
2110   DrawNativeGreyColorInRect(cgContext, toolbarFillGrey, drawRect, aIsMain);
2112   // bottom border
2113   drawRect.origin.y += drawRect.size.height;
2114   drawRect.size.height = 1.0f;
2115   DrawNativeGreyColorInRect(cgContext, toolbarBottomBorderGrey, drawRect, aIsMain);
2118 static bool ToolbarCanBeUnified(const gfx::Rect& aRect, NSWindow* aWindow) {
2119   if (![aWindow isKindOfClass:[ToolbarWindow class]]) return false;
2121   ToolbarWindow* win = (ToolbarWindow*)aWindow;
2122   float unifiedToolbarHeight = [win unifiedToolbarHeight];
2123   return aRect.X() == 0 && aRect.Width() >= [win frame].size.width &&
2124          aRect.YMost() <= unifiedToolbarHeight;
2127 void nsNativeThemeCocoa::DrawStatusBar(CGContextRef cgContext, const HIRect& inBoxRect,
2128                                        bool aIsMain) {
2129   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2131   if (inBoxRect.size.height < 2.0f) return;
2133   CGContextSaveGState(cgContext);
2134   CGContextClipToRect(cgContext, inBoxRect);
2136   // kCUIWidgetWindowFrame draws a complete window frame with both title bar
2137   // and bottom bar. We only want the bottom bar, so we extend the draw rect
2138   // upwards to make space for the title bar, and then we clip it away.
2139   CGRect drawRect = inBoxRect;
2140   const int extendUpwards = 40;
2141   drawRect.origin.y -= extendUpwards;
2142   drawRect.size.height += extendUpwards;
2143   RenderWithCoreUI(
2144       drawRect, cgContext,
2145       [NSDictionary
2146           dictionaryWithObjectsAndKeys:@"kCUIWidgetWindowFrame", @"widget", @"regularwin",
2147                                        @"windowtype", (aIsMain ? @"normal" : @"inactive"), @"state",
2148                                        [NSNumber numberWithInt:inBoxRect.size.height],
2149                                        @"kCUIWindowFrameBottomBarHeightKey",
2150                                        [NSNumber numberWithBool:YES],
2151                                        @"kCUIWindowFrameDrawBottomBarSeparatorKey",
2152                                        [NSNumber numberWithBool:YES], @"is.flipped", nil]);
2154   CGContextRestoreGState(cgContext);
2156   NS_OBJC_END_TRY_IGNORE_BLOCK;
2159 static void RenderResizer(CGContextRef cgContext, const HIRect& aRenderRect, void* aData) {
2160   HIThemeGrowBoxDrawInfo* drawInfo = (HIThemeGrowBoxDrawInfo*)aData;
2161   HIThemeDrawGrowBox(&CGPointZero, drawInfo, cgContext, kHIThemeOrientationNormal);
2164 void nsNativeThemeCocoa::DrawResizer(CGContextRef cgContext, const HIRect& aRect, bool aIsRTL) {
2165   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2167   HIThemeGrowBoxDrawInfo drawInfo;
2168   drawInfo.version = 0;
2169   drawInfo.state = kThemeStateActive;
2170   drawInfo.kind = kHIThemeGrowBoxKindNormal;
2171   drawInfo.direction = kThemeGrowRight | kThemeGrowDown;
2172   drawInfo.size = kHIThemeGrowBoxSizeNormal;
2174   RenderTransformedHIThemeControl(cgContext, aRect, RenderResizer, &drawInfo, aIsRTL);
2176   NS_OBJC_END_TRY_IGNORE_BLOCK;
2179 void nsNativeThemeCocoa::DrawMultilineTextField(CGContextRef cgContext, const CGRect& inBoxRect,
2180                                                 bool aIsFocused) {
2181   mTextFieldCell.enabled = YES;
2182   mTextFieldCell.showsFirstResponder = aIsFocused;
2184   if (mCellDrawWindow) {
2185     mCellDrawWindow.cellsShouldLookActive = YES;
2186   }
2188   // DrawCellIncludingFocusRing draws into the current NSGraphicsContext, so do the usual
2189   // save+restore dance.
2190   NSGraphicsContext* savedContext = NSGraphicsContext.currentContext;
2191   NSGraphicsContext.currentContext = [NSGraphicsContext graphicsContextWithCGContext:cgContext
2192                                                                              flipped:YES];
2193   DrawCellIncludingFocusRing(mTextFieldCell, inBoxRect, mCellDrawView);
2194   NSGraphicsContext.currentContext = savedContext;
2197 void nsNativeThemeCocoa::DrawSourceListSelection(CGContextRef aContext, const CGRect& aRect,
2198                                                  bool aWindowIsActive, bool aSelectionIsActive) {
2199   NSColor* fillColor;
2200   if (aSelectionIsActive) {
2201     // Active selection, blue or graphite.
2202     fillColor = ControlAccentColor();
2203   } else {
2204     // Inactive selection, gray.
2205     if (aWindowIsActive) {
2206       fillColor = [NSColor colorWithWhite:0.871 alpha:1.0];
2207     } else {
2208       fillColor = [NSColor colorWithWhite:0.808 alpha:1.0];
2209     }
2210   }
2211   CGContextSetFillColorWithColor(aContext, [fillColor CGColor]);
2212   CGContextFillRect(aContext, aRect);
2215 static bool IsHiDPIContext(nsDeviceContext* aContext) {
2216   return AppUnitsPerCSSPixel() >= 2 * aContext->AppUnitsPerDevPixelAtUnitFullZoom();
2219 Maybe<nsNativeThemeCocoa::WidgetInfo> nsNativeThemeCocoa::ComputeWidgetInfo(
2220     nsIFrame* aFrame, StyleAppearance aAppearance, const nsRect& aRect) {
2221   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
2223   // setup to draw into the correct port
2224   int32_t p2a = aFrame->PresContext()->AppUnitsPerDevPixel();
2226   gfx::Rect nativeWidgetRect(aRect.x, aRect.y, aRect.width, aRect.height);
2227   nativeWidgetRect.Scale(1.0 / gfxFloat(p2a));
2228   float originalHeight = nativeWidgetRect.Height();
2229   nativeWidgetRect.Round();
2230   if (nativeWidgetRect.IsEmpty()) {
2231     return Nothing();  // Don't attempt to draw invisible widgets.
2232   }
2234   bool hidpi = IsHiDPIContext(aFrame->PresContext()->DeviceContext());
2235   if (hidpi) {
2236     // Use high-resolution drawing.
2237     nativeWidgetRect.Scale(0.5f);
2238     originalHeight *= 0.5f;
2239   }
2241   EventStates eventState = GetContentState(aFrame, aAppearance);
2243   switch (aAppearance) {
2244     case StyleAppearance::Menupopup:
2245       return Nothing();
2247     case StyleAppearance::Menuarrow:
2248       return Some(
2249           WidgetInfo::MenuIcon(ComputeMenuIconParams(aFrame, eventState, MenuIcon::eMenuArrow)));
2251     case StyleAppearance::Menuitem:
2252     case StyleAppearance::Checkmenuitem:
2253       return Some(WidgetInfo::MenuItem(ComputeMenuItemParams(
2254           aFrame, eventState, aAppearance == StyleAppearance::Checkmenuitem)));
2256     case StyleAppearance::Menuseparator:
2257       return Some(WidgetInfo::MenuSeparator(ComputeMenuItemParams(aFrame, eventState, false)));
2259     case StyleAppearance::ButtonArrowUp:
2260     case StyleAppearance::ButtonArrowDown: {
2261       MenuIcon icon = aAppearance == StyleAppearance::ButtonArrowUp
2262                           ? MenuIcon::eMenuUpScrollArrow
2263                           : MenuIcon::eMenuDownScrollArrow;
2264       return Some(WidgetInfo::MenuIcon(ComputeMenuIconParams(aFrame, eventState, icon)));
2265     }
2267     case StyleAppearance::Tooltip:
2268       return Nothing();
2270     case StyleAppearance::Checkbox:
2271     case StyleAppearance::Radio: {
2272       bool isCheckbox = (aAppearance == StyleAppearance::Checkbox);
2274       CheckboxOrRadioParams params;
2275       params.state = CheckboxOrRadioState::eOff;
2276       if (eventState.HasState(NS_EVENT_STATE_INDETERMINATE)) {
2277         params.state = CheckboxOrRadioState::eIndeterminate;
2278       } else if (eventState.HasState(NS_EVENT_STATE_CHECKED)) {
2279         params.state = CheckboxOrRadioState::eOn;
2280       }
2281       params.controlParams = ComputeControlParams(aFrame, eventState);
2282       params.verticalAlignFactor = VerticalAlignFactor(aFrame);
2283       if (isCheckbox) {
2284         return Some(WidgetInfo::Checkbox(params));
2285       }
2286       return Some(WidgetInfo::Radio(params));
2287     }
2289     case StyleAppearance::Button:
2290       if (IsDefaultButton(aFrame)) {
2291         // Check whether the default button is in a document that does not
2292         // match the :-moz-window-inactive pseudoclass. This activeness check
2293         // is different from the other "active window" checks in this file
2294         // because we absolutely need the button's default button appearance to
2295         // be in sync with its text color, and the text color is changed by
2296         // such a :-moz-window-inactive rule. (That's because on 10.10 and up,
2297         // default buttons in active windows have blue background and white
2298         // text, and default buttons in inactive windows have white background
2299         // and black text.)
2300         EventStates docState = aFrame->GetContent()->OwnerDoc()->GetDocumentState();
2301         ControlParams params = ComputeControlParams(aFrame, eventState);
2302         params.insideActiveWindow = !docState.HasState(NS_DOCUMENT_STATE_WINDOW_INACTIVE);
2303         return Some(WidgetInfo::Button(ButtonParams{params, ButtonType::eDefaultPushButton}));
2304       }
2305       if (IsButtonTypeMenu(aFrame)) {
2306         ControlParams controlParams = ComputeControlParams(aFrame, eventState);
2307         controlParams.pressed = IsOpenButton(aFrame);
2308         DropdownParams params;
2309         params.controlParams = controlParams;
2310         params.pullsDown = true;
2311         params.editable = false;
2312         return Some(WidgetInfo::Dropdown(params));
2313       }
2314       if (originalHeight > DO_SQUARE_BUTTON_HEIGHT) {
2315         // If the button is tall enough, draw the square button style so that
2316         // buttons with non-standard content look good. Otherwise draw normal
2317         // rounded aqua buttons.
2318         // This comparison is done based on the height that is calculated without
2319         // the top, because the snapped height can be affected by the top of the
2320         // rect and that may result in different height depending on the top value.
2321         return Some(WidgetInfo::Button(ButtonParams{ComputeControlParams(aFrame, eventState),
2322                                                     ButtonType::eSquareBezelPushButton}));
2323       }
2324       return Some(WidgetInfo::Button(
2325           ButtonParams{ComputeControlParams(aFrame, eventState), ButtonType::eRegularPushButton}));
2327     case StyleAppearance::FocusOutline:
2328       return Some(WidgetInfo::FocusOutline());
2330     case StyleAppearance::MozMacHelpButton:
2331       return Some(WidgetInfo::Button(
2332           ButtonParams{ComputeControlParams(aFrame, eventState), ButtonType::eHelpButton}));
2334     case StyleAppearance::MozMacDisclosureButtonOpen:
2335     case StyleAppearance::MozMacDisclosureButtonClosed: {
2336       ButtonType buttonType = (aAppearance == StyleAppearance::MozMacDisclosureButtonClosed)
2337                                   ? ButtonType::eDisclosureButtonClosed
2338                                   : ButtonType::eDisclosureButtonOpen;
2339       return Some(
2340           WidgetInfo::Button(ButtonParams{ComputeControlParams(aFrame, eventState), buttonType}));
2341     }
2343     case StyleAppearance::Spinner: {
2344       bool isSpinner = (aAppearance == StyleAppearance::Spinner);
2345       nsIContent* content = aFrame->GetContent();
2346       if (isSpinner && content->IsHTMLElement()) {
2347         // In HTML the theming for the spin buttons is drawn individually into
2348         // their own backgrounds instead of being drawn into the background of
2349         // their spinner parent as it is for XUL.
2350         break;
2351       }
2352       SpinButtonParams params;
2353       if (content->IsElement()) {
2354         if (content->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::state, u"up"_ns,
2355                                               eCaseMatters)) {
2356           params.pressedButton = Some(SpinButton::eUp);
2357         } else if (content->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::state,
2358                                                      u"down"_ns, eCaseMatters)) {
2359           params.pressedButton = Some(SpinButton::eDown);
2360         }
2361       }
2362       params.disabled = eventState.HasState(NS_EVENT_STATE_DISABLED);
2363       params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
2365       return Some(WidgetInfo::SpinButtons(params));
2366     }
2368     case StyleAppearance::SpinnerUpbutton:
2369     case StyleAppearance::SpinnerDownbutton: {
2370       nsNumberControlFrame* numberControlFrame =
2371           nsNumberControlFrame::GetNumberControlFrameForSpinButton(aFrame);
2372       if (numberControlFrame) {
2373         SpinButtonParams params;
2374         if (numberControlFrame->SpinnerUpButtonIsDepressed()) {
2375           params.pressedButton = Some(SpinButton::eUp);
2376         } else if (numberControlFrame->SpinnerDownButtonIsDepressed()) {
2377           params.pressedButton = Some(SpinButton::eDown);
2378         }
2379         params.disabled = eventState.HasState(NS_EVENT_STATE_DISABLED);
2380         params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
2381         if (aAppearance == StyleAppearance::SpinnerUpbutton) {
2382           return Some(WidgetInfo::SpinButtonUp(params));
2383         }
2384         return Some(WidgetInfo::SpinButtonDown(params));
2385       }
2386     } break;
2388     case StyleAppearance::Toolbarbutton: {
2389       SegmentParams params = ComputeSegmentParams(aFrame, eventState, SegmentType::eToolbarButton);
2390       params.insideActiveWindow = [NativeWindowForFrame(aFrame) isMainWindow];
2391       return Some(WidgetInfo::Segment(params));
2392     }
2394     case StyleAppearance::Separator:
2395       return Some(WidgetInfo::Separator());
2397     case StyleAppearance::Toolbar: {
2398       NSWindow* win = NativeWindowForFrame(aFrame);
2399       bool isMain = [win isMainWindow];
2400       if (ToolbarCanBeUnified(nativeWidgetRect, win)) {
2401         // Unified toolbars are drawn similar to vibrancy; we communicate their extents via the
2402         // theme geometry mechanism and then place native views under Gecko's rendering. So Gecko
2403         // just needs to be transparent in the place where the toolbar should be visible.
2404         return Nothing();
2405       }
2406       return Some(WidgetInfo::Toolbar(isMain));
2407     }
2409     case StyleAppearance::MozWindowTitlebar: {
2410       return Nothing();
2411     }
2413     case StyleAppearance::Statusbar:
2414       return Some(WidgetInfo::StatusBar(IsActive(aFrame, YES)));
2416     case StyleAppearance::MenulistButton:
2417     case StyleAppearance::Menulist: {
2418       ControlParams controlParams = ComputeControlParams(aFrame, eventState);
2419       controlParams.pressed = IsOpenButton(aFrame);
2420       DropdownParams params;
2421       params.controlParams = controlParams;
2422       params.pullsDown = false;
2423       params.editable = false;
2424       return Some(WidgetInfo::Dropdown(params));
2425     }
2427     case StyleAppearance::MozMenulistArrowButton:
2428       return Some(WidgetInfo::Button(
2429           ButtonParams{ComputeControlParams(aFrame, eventState), ButtonType::eArrowButton}));
2431     case StyleAppearance::Groupbox:
2432       return Some(WidgetInfo::GroupBox());
2434     case StyleAppearance::Textfield:
2435     case StyleAppearance::NumberInput:
2436       return Some(WidgetInfo::TextField(ComputeTextFieldParams(aFrame, eventState)));
2438     case StyleAppearance::Searchfield:
2439       return Some(WidgetInfo::SearchField(ComputeTextFieldParams(aFrame, eventState)));
2441     case StyleAppearance::ProgressBar: {
2442       if (eventState.HasState(NS_EVENT_STATE_INDETERMINATE)) {
2443         if (!QueueAnimatedContentForRefresh(aFrame->GetContent(), 30)) {
2444           NS_WARNING("Unable to animate progressbar!");
2445         }
2446       }
2447       return Some(WidgetInfo::ProgressBar(
2448           ComputeProgressParams(aFrame, eventState, !IsVerticalProgress(aFrame))));
2449     }
2451     case StyleAppearance::Meter:
2452       return Some(WidgetInfo::Meter(ComputeMeterParams(aFrame)));
2454     case StyleAppearance::Progresschunk:
2455     case StyleAppearance::Meterchunk:
2456       // Do nothing: progress and meter bars cases will draw chunks.
2457       break;
2459     case StyleAppearance::Treetwisty:
2460       return Some(WidgetInfo::Button(ButtonParams{ComputeControlParams(aFrame, eventState),
2461                                                   ButtonType::eTreeTwistyPointingRight}));
2463     case StyleAppearance::Treetwistyopen:
2464       return Some(WidgetInfo::Button(ButtonParams{ComputeControlParams(aFrame, eventState),
2465                                                   ButtonType::eTreeTwistyPointingDown}));
2467     case StyleAppearance::Treeheadercell:
2468       return Some(WidgetInfo::TreeHeaderCell(ComputeTreeHeaderCellParams(aFrame, eventState)));
2470     case StyleAppearance::Treeitem:
2471     case StyleAppearance::Treeview:
2472       return Some(WidgetInfo::ColorFill(sRGBColor(1.0, 1.0, 1.0, 1.0)));
2474     case StyleAppearance::Treeheader:
2475       // do nothing, taken care of by individual header cells
2476     case StyleAppearance::Treeheadersortarrow:
2477       // do nothing, taken care of by treeview header
2478     case StyleAppearance::Treeline:
2479       // do nothing, these lines don't exist on macos
2480       break;
2482     case StyleAppearance::Range: {
2483       Maybe<ScaleParams> params = ComputeHTMLScaleParams(aFrame, eventState);
2484       if (params) {
2485         return Some(WidgetInfo::Scale(*params));
2486       }
2487       break;
2488     }
2490     case StyleAppearance::Textarea:
2491       return Some(WidgetInfo::MultilineTextField(eventState.HasState(NS_EVENT_STATE_FOCUS)));
2493     case StyleAppearance::Listbox:
2494       return Some(WidgetInfo::ListBox());
2496     case StyleAppearance::MozMacSourceList: {
2497       return Nothing();
2498     }
2500     case StyleAppearance::MozMacSourceListSelection:
2501     case StyleAppearance::MozMacActiveSourceListSelection: {
2502       // We only support vibrancy for source list selections if we're inside
2503       // a source list, because we need the background to be transparent.
2504       if (IsInSourceList(aFrame)) {
2505         return Nothing();
2506       }
2507       bool isInActiveWindow = FrameIsInActiveWindow(aFrame);
2508       if (aAppearance == StyleAppearance::MozMacActiveSourceListSelection) {
2509         return Some(WidgetInfo::ActiveSourceListSelection(isInActiveWindow));
2510       }
2511       return Some(WidgetInfo::InactiveSourceListSelection(isInActiveWindow));
2512     }
2514     case StyleAppearance::Tab: {
2515       SegmentParams params = ComputeSegmentParams(aFrame, eventState, SegmentType::eTab);
2516       params.pressed = params.pressed && !params.selected;
2517       return Some(WidgetInfo::Segment(params));
2518     }
2520     case StyleAppearance::Tabpanels:
2521       return Some(WidgetInfo::TabPanel(FrameIsInActiveWindow(aFrame)));
2523     case StyleAppearance::Resizer:
2524       return Some(WidgetInfo::Resizer(IsFrameRTL(aFrame)));
2526     default:
2527       break;
2528   }
2530   return Nothing();
2532   NS_OBJC_END_TRY_BLOCK_RETURN(Nothing());
2535 NS_IMETHODIMP
2536 nsNativeThemeCocoa::DrawWidgetBackground(gfxContext* aContext, nsIFrame* aFrame,
2537                                          StyleAppearance aAppearance, const nsRect& aRect,
2538                                          const nsRect& aDirtyRect, DrawOverflow aDrawOverflow) {
2539   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
2541   if (IsWidgetScrollbarPart(aAppearance)) {
2542     return ThemeCocoa::DrawWidgetBackground(aContext, aFrame, aAppearance, aRect, aDirtyRect,
2543                                             aDrawOverflow);
2544   }
2546   Maybe<WidgetInfo> widgetInfo = ComputeWidgetInfo(aFrame, aAppearance, aRect);
2548   if (!widgetInfo) {
2549     return NS_OK;
2550   }
2552   int32_t p2a = aFrame->PresContext()->AppUnitsPerDevPixel();
2554   gfx::Rect nativeWidgetRect = NSRectToRect(aRect, p2a);
2555   nativeWidgetRect.Round();
2557   bool hidpi = IsHiDPIContext(aFrame->PresContext()->DeviceContext());
2559   auto colorScheme = LookAndFeel::ColorSchemeForFrame(aFrame);
2561   RenderWidget(*widgetInfo, colorScheme, *aContext->GetDrawTarget(), nativeWidgetRect,
2562                NSRectToRect(aDirtyRect, p2a), hidpi ? 2.0f : 1.0f);
2564   return NS_OK;
2566   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
2569 void nsNativeThemeCocoa::RenderWidget(const WidgetInfo& aWidgetInfo,
2570                                       LookAndFeel::ColorScheme aScheme, DrawTarget& aDrawTarget,
2571                                       const gfx::Rect& aWidgetRect, const gfx::Rect& aDirtyRect,
2572                                       float aScale) {
2573   // Some of the drawing below uses NSAppearance.currentAppearance behind the scenes.
2574   // Set it to the appearance we want, the same way as nsLookAndFeel::NativeGetColor.
2575   NSAppearance.currentAppearance = NSAppearanceForColorScheme(aScheme);
2577   // Also set the cell draw window's appearance; this is respected by NSTextFieldCell (and its
2578   // subclass NSSearchFieldCell).
2579   if (mCellDrawWindow) {
2580     mCellDrawWindow.appearance = NSAppearance.currentAppearance;
2581   }
2583   const Widget widget = aWidgetInfo.Widget();
2585   // Some widgets render using DrawTarget, and some using CGContext.
2586   switch (widget) {
2587     case Widget::eColorFill: {
2588       sRGBColor color = aWidgetInfo.Params<sRGBColor>();
2589       aDrawTarget.FillRect(aWidgetRect, ColorPattern(ToDeviceColor(color)));
2590       break;
2591     }
2592     default: {
2593       AutoRestoreTransform autoRestoreTransform(&aDrawTarget);
2594       gfx::Rect widgetRect = aWidgetRect;
2595       gfx::Rect dirtyRect = aDirtyRect;
2597       dirtyRect.Scale(1.0f / aScale);
2598       widgetRect.Scale(1.0f / aScale);
2599       aDrawTarget.SetTransform(aDrawTarget.GetTransform().PreScale(aScale, aScale));
2601       // The remaining widgets require a CGContext.
2602       CGRect macRect =
2603           CGRectMake(widgetRect.X(), widgetRect.Y(), widgetRect.Width(), widgetRect.Height());
2605       gfxQuartzNativeDrawing nativeDrawing(aDrawTarget, dirtyRect);
2607       CGContextRef cgContext = nativeDrawing.BeginNativeDrawing();
2608       if (cgContext == nullptr) {
2609         // The Quartz surface handles 0x0 surfaces by internally
2610         // making all operations no-ops; there's no cgcontext created for them.
2611         // Unfortunately, this means that callers that want to render
2612         // directly to the CGContext need to be aware of this quirk.
2613         return;
2614       }
2616       // Set the context's "base transform" to in order to get correctly-sized focus rings.
2617       CGContextSetBaseCTM(cgContext, CGAffineTransformMakeScale(aScale, aScale));
2619       switch (widget) {
2620         case Widget::eColorFill:
2621           MOZ_CRASH("already handled in outer switch");
2622           break;
2623         case Widget::eMenuIcon: {
2624           MenuIconParams params = aWidgetInfo.Params<MenuIconParams>();
2625           DrawMenuIcon(cgContext, macRect, params);
2626           break;
2627         }
2628         case Widget::eMenuItem: {
2629           MenuItemParams params = aWidgetInfo.Params<MenuItemParams>();
2630           DrawMenuItem(cgContext, macRect, params);
2631           break;
2632         }
2633         case Widget::eMenuSeparator: {
2634           MenuItemParams params = aWidgetInfo.Params<MenuItemParams>();
2635           DrawMenuSeparator(cgContext, macRect, params);
2636           break;
2637         }
2638         case Widget::eCheckbox: {
2639           CheckboxOrRadioParams params = aWidgetInfo.Params<CheckboxOrRadioParams>();
2640           DrawCheckboxOrRadio(cgContext, true, macRect, params);
2641           break;
2642         }
2643         case Widget::eRadio: {
2644           CheckboxOrRadioParams params = aWidgetInfo.Params<CheckboxOrRadioParams>();
2645           DrawCheckboxOrRadio(cgContext, false, macRect, params);
2646           break;
2647         }
2648         case Widget::eButton: {
2649           ButtonParams params = aWidgetInfo.Params<ButtonParams>();
2650           DrawButton(cgContext, macRect, params);
2651           break;
2652         }
2653         case Widget::eDropdown: {
2654           DropdownParams params = aWidgetInfo.Params<DropdownParams>();
2655           DrawDropdown(cgContext, macRect, params);
2656           break;
2657         }
2658         case Widget::eFocusOutline: {
2659           DrawFocusOutline(cgContext, macRect);
2660           break;
2661         }
2662         case Widget::eSpinButtons: {
2663           SpinButtonParams params = aWidgetInfo.Params<SpinButtonParams>();
2664           DrawSpinButtons(cgContext, macRect, params);
2665           break;
2666         }
2667         case Widget::eSpinButtonUp: {
2668           SpinButtonParams params = aWidgetInfo.Params<SpinButtonParams>();
2669           DrawSpinButton(cgContext, macRect, SpinButton::eUp, params);
2670           break;
2671         }
2672         case Widget::eSpinButtonDown: {
2673           SpinButtonParams params = aWidgetInfo.Params<SpinButtonParams>();
2674           DrawSpinButton(cgContext, macRect, SpinButton::eDown, params);
2675           break;
2676         }
2677         case Widget::eSegment: {
2678           SegmentParams params = aWidgetInfo.Params<SegmentParams>();
2679           DrawSegment(cgContext, macRect, params);
2680           break;
2681         }
2682         case Widget::eSeparator: {
2683           HIThemeSeparatorDrawInfo sdi = {0, kThemeStateActive};
2684           HIThemeDrawSeparator(&macRect, &sdi, cgContext, HITHEME_ORIENTATION);
2685           break;
2686         }
2687         case Widget::eToolbar: {
2688           bool isMain = aWidgetInfo.Params<bool>();
2689           DrawToolbar(cgContext, macRect, isMain);
2690           break;
2691         }
2692         case Widget::eStatusBar: {
2693           bool isMain = aWidgetInfo.Params<bool>();
2694           DrawStatusBar(cgContext, macRect, isMain);
2695           break;
2696         }
2697         case Widget::eGroupBox: {
2698           HIThemeGroupBoxDrawInfo gdi = {0, kThemeStateActive, kHIThemeGroupBoxKindPrimary};
2699           HIThemeDrawGroupBox(&macRect, &gdi, cgContext, HITHEME_ORIENTATION);
2700           break;
2701         }
2702         case Widget::eTextField: {
2703           TextFieldParams params = aWidgetInfo.Params<TextFieldParams>();
2704           DrawTextField(cgContext, macRect, params);
2705           break;
2706         }
2707         case Widget::eSearchField: {
2708           TextFieldParams params = aWidgetInfo.Params<TextFieldParams>();
2709           DrawSearchField(cgContext, macRect, params);
2710           break;
2711         }
2712         case Widget::eProgressBar: {
2713           ProgressParams params = aWidgetInfo.Params<ProgressParams>();
2714           DrawProgress(cgContext, macRect, params);
2715           break;
2716         }
2717         case Widget::eMeter: {
2718           MeterParams params = aWidgetInfo.Params<MeterParams>();
2719           DrawMeter(cgContext, macRect, params);
2720           break;
2721         }
2722         case Widget::eTreeHeaderCell: {
2723           TreeHeaderCellParams params = aWidgetInfo.Params<TreeHeaderCellParams>();
2724           DrawTreeHeaderCell(cgContext, macRect, params);
2725           break;
2726         }
2727         case Widget::eScale: {
2728           ScaleParams params = aWidgetInfo.Params<ScaleParams>();
2729           DrawScale(cgContext, macRect, params);
2730           break;
2731         }
2732         case Widget::eMultilineTextField: {
2733           bool isFocused = aWidgetInfo.Params<bool>();
2734           DrawMultilineTextField(cgContext, macRect, isFocused);
2735           break;
2736         }
2737         case Widget::eListBox: {
2738           // Fill the content with the control background color.
2739           CGContextSetFillColorWithColor(cgContext, [NSColor.controlBackgroundColor CGColor]);
2740           CGContextFillRect(cgContext, macRect);
2741           // Draw the frame using kCUIWidgetScrollViewFrame. This is what NSScrollView uses in
2742           // -[NSScrollView drawRect:] if you give it a borderType of NSBezelBorder.
2743           RenderWithCoreUI(
2744               macRect, cgContext, @{
2745                 @"widget" : @"kCUIWidgetScrollViewFrame",
2746                 @"kCUIIsFlippedKey" : @YES,
2747                 @"kCUIVariantMetal" : @NO,
2748               });
2749           break;
2750         }
2751         case Widget::eActiveSourceListSelection:
2752         case Widget::eInactiveSourceListSelection: {
2753           bool isInActiveWindow = aWidgetInfo.Params<bool>();
2754           bool isActiveSelection = aWidgetInfo.Widget() == Widget::eActiveSourceListSelection;
2755           DrawSourceListSelection(cgContext, macRect, isInActiveWindow, isActiveSelection);
2756           break;
2757         }
2758         case Widget::eTabPanel: {
2759           bool isInsideActiveWindow = aWidgetInfo.Params<bool>();
2760           DrawTabPanel(cgContext, macRect, isInsideActiveWindow);
2761           break;
2762         }
2763         case Widget::eResizer: {
2764           bool isRTL = aWidgetInfo.Params<bool>();
2765           DrawResizer(cgContext, macRect, isRTL);
2766           break;
2767         }
2768       }
2770       // Reset the base CTM.
2771       CGContextSetBaseCTM(cgContext, CGAffineTransformIdentity);
2773       nativeDrawing.EndNativeDrawing();
2774     }
2775   }
2778 bool nsNativeThemeCocoa::CreateWebRenderCommandsForWidget(
2779     mozilla::wr::DisplayListBuilder& aBuilder, mozilla::wr::IpcResourceUpdateQueue& aResources,
2780     const mozilla::layers::StackingContextHelper& aSc,
2781     mozilla::layers::RenderRootStateManager* aManager, nsIFrame* aFrame,
2782     StyleAppearance aAppearance, const nsRect& aRect) {
2783   if (IsWidgetScrollbarPart(aAppearance)) {
2784     return ThemeCocoa::CreateWebRenderCommandsForWidget(aBuilder, aResources, aSc, aManager, aFrame,
2785                                                         aAppearance, aRect);
2786   }
2788   // This list needs to stay consistent with the list in DrawWidgetBackground.
2789   // For every switch case in DrawWidgetBackground, there are three choices:
2790   //  - If the case in DrawWidgetBackground draws nothing for the given widget
2791   //    type, then don't list it here. We will hit the "default: return true;"
2792   //    case.
2793   //  - If the case in DrawWidgetBackground draws something simple for the given
2794   //    widget type, imitate that drawing using WebRender commands.
2795   //  - If the case in DrawWidgetBackground draws something complicated for the
2796   //    given widget type, return false here.
2797   switch (aAppearance) {
2798     case StyleAppearance::Menuarrow:
2799     case StyleAppearance::Menuitem:
2800     case StyleAppearance::Checkmenuitem:
2801     case StyleAppearance::Menuseparator:
2802     case StyleAppearance::ButtonArrowUp:
2803     case StyleAppearance::ButtonArrowDown:
2804     case StyleAppearance::Checkbox:
2805     case StyleAppearance::Radio:
2806     case StyleAppearance::Button:
2807     case StyleAppearance::FocusOutline:
2808     case StyleAppearance::MozMacHelpButton:
2809     case StyleAppearance::MozMacDisclosureButtonOpen:
2810     case StyleAppearance::MozMacDisclosureButtonClosed:
2811     case StyleAppearance::Spinner:
2812     case StyleAppearance::SpinnerUpbutton:
2813     case StyleAppearance::SpinnerDownbutton:
2814     case StyleAppearance::Toolbarbutton:
2815     case StyleAppearance::Separator:
2816     case StyleAppearance::Toolbar:
2817     case StyleAppearance::MozWindowTitlebar:
2818     case StyleAppearance::Statusbar:
2819     case StyleAppearance::Menulist:
2820     case StyleAppearance::MenulistButton:
2821     case StyleAppearance::MozMenulistArrowButton:
2822     case StyleAppearance::Groupbox:
2823     case StyleAppearance::Textfield:
2824     case StyleAppearance::NumberInput:
2825     case StyleAppearance::Searchfield:
2826     case StyleAppearance::ProgressBar:
2827     case StyleAppearance::Meter:
2828     case StyleAppearance::Treeheadercell:
2829     case StyleAppearance::Treetwisty:
2830     case StyleAppearance::Treetwistyopen:
2831     case StyleAppearance::Treeitem:
2832     case StyleAppearance::Treeview:
2833     case StyleAppearance::Range:
2834       return false;
2836     case StyleAppearance::Textarea:
2837     case StyleAppearance::Listbox:
2838     case StyleAppearance::Tab:
2839     case StyleAppearance::Tabpanels:
2840     case StyleAppearance::Resizer:
2841       return false;
2843     default:
2844       return true;
2845   }
2848 LayoutDeviceIntMargin nsNativeThemeCocoa::DirectionAwareMargin(const LayoutDeviceIntMargin& aMargin,
2849                                                                nsIFrame* aFrame) {
2850   // Assuming aMargin was originally specified for a horizontal LTR context,
2851   // reinterpret the values as logical, and then map to physical coords
2852   // according to aFrame's actual writing mode.
2853   WritingMode wm = aFrame->GetWritingMode();
2854   nsMargin m = LogicalMargin(wm, aMargin.top, aMargin.right, aMargin.bottom, aMargin.left)
2855                    .GetPhysicalMargin(wm);
2856   return LayoutDeviceIntMargin(m.top, m.right, m.bottom, m.left);
2859 static const LayoutDeviceIntMargin kAquaDropdownBorder(1, 22, 2, 5);
2860 static const LayoutDeviceIntMargin kAquaComboboxBorder(3, 20, 3, 4);
2861 static const LayoutDeviceIntMargin kAquaSearchfieldBorder(3, 5, 2, 19);
2862 static const LayoutDeviceIntMargin kAquaSearchfieldBorderBigSur(5, 5, 4, 26);
2864 LayoutDeviceIntMargin nsNativeThemeCocoa::GetWidgetBorder(nsDeviceContext* aContext,
2865                                                           nsIFrame* aFrame,
2866                                                           StyleAppearance aAppearance) {
2867   LayoutDeviceIntMargin result;
2869   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
2870   switch (aAppearance) {
2871     case StyleAppearance::Button: {
2872       if (IsButtonTypeMenu(aFrame)) {
2873         result = DirectionAwareMargin(kAquaDropdownBorder, aFrame);
2874       } else {
2875         result = DirectionAwareMargin(LayoutDeviceIntMargin(1, 7, 3, 7), aFrame);
2876       }
2877       break;
2878     }
2880     case StyleAppearance::Toolbarbutton: {
2881       result = DirectionAwareMargin(LayoutDeviceIntMargin(1, 4, 1, 4), aFrame);
2882       break;
2883     }
2885     case StyleAppearance::Checkbox:
2886     case StyleAppearance::Radio: {
2887       // nsCheckboxRadioFrame::GetIntrinsicWidth and nsCheckboxRadioFrame::GetIntrinsicHeight
2888       // assume a border width of 2px.
2889       result.SizeTo(2, 2, 2, 2);
2890       break;
2891     }
2893     case StyleAppearance::Menulist:
2894     case StyleAppearance::MenulistButton:
2895     case StyleAppearance::MozMenulistArrowButton:
2896       result = DirectionAwareMargin(kAquaDropdownBorder, aFrame);
2897       break;
2899     case StyleAppearance::Menuarrow:
2900       if (nsCocoaFeatures::OnBigSurOrLater()) {
2901         result.SizeTo(0, 0, 0, 28);
2902       }
2903       break;
2905     case StyleAppearance::NumberInput:
2906     case StyleAppearance::Textfield: {
2907       SInt32 frameOutset = 0;
2908       ::GetThemeMetric(kThemeMetricEditTextFrameOutset, &frameOutset);
2910       SInt32 textPadding = 0;
2911       ::GetThemeMetric(kThemeMetricEditTextWhitespace, &textPadding);
2913       frameOutset += textPadding;
2915       result.SizeTo(frameOutset, frameOutset, frameOutset, frameOutset);
2916       break;
2917     }
2919     case StyleAppearance::Textarea:
2920       result.SizeTo(1, 1, 1, 1);
2921       break;
2923     case StyleAppearance::Searchfield: {
2924       auto border = nsCocoaFeatures::OnBigSurOrLater() ? kAquaSearchfieldBorderBigSur
2925                                                        : kAquaSearchfieldBorder;
2926       result = DirectionAwareMargin(border, aFrame);
2927       break;
2928     }
2930     case StyleAppearance::Listbox: {
2931       SInt32 frameOutset = 0;
2932       ::GetThemeMetric(kThemeMetricListBoxFrameOutset, &frameOutset);
2933       result.SizeTo(frameOutset, frameOutset, frameOutset, frameOutset);
2934       break;
2935     }
2937     case StyleAppearance::Statusbar:
2938       result.SizeTo(1, 0, 0, 0);
2939       break;
2941     default:
2942       break;
2943   }
2945   if (IsHiDPIContext(aContext)) {
2946     result = result + result;  // doubled
2947   }
2949   NS_OBJC_END_TRY_BLOCK_RETURN(result);
2952 // Return false here to indicate that CSS padding values should be used. There is
2953 // no reason to make a distinction between padding and border values, just specify
2954 // whatever values you want in GetWidgetBorder and only use this to return true
2955 // if you want to override CSS padding values.
2956 bool nsNativeThemeCocoa::GetWidgetPadding(nsDeviceContext* aContext, nsIFrame* aFrame,
2957                                           StyleAppearance aAppearance,
2958                                           LayoutDeviceIntMargin* aResult) {
2959   // We don't want CSS padding being used for certain widgets.
2960   // See bug 381639 for an example of why.
2961   switch (aAppearance) {
2962     // Radios and checkboxes return a fixed size in GetMinimumWidgetSize
2963     // and have a meaningful baseline, so they can't have
2964     // author-specified padding.
2965     case StyleAppearance::Checkbox:
2966     case StyleAppearance::Radio:
2967       aResult->SizeTo(0, 0, 0, 0);
2968       return true;
2970     case StyleAppearance::Menuarrow:
2971     case StyleAppearance::Searchfield:
2972       if (nsCocoaFeatures::OnBigSurOrLater()) {
2973         return true;
2974       }
2975       break;
2977     default:
2978       break;
2979   }
2980   return false;
2983 bool nsNativeThemeCocoa::GetWidgetOverflow(nsDeviceContext* aContext, nsIFrame* aFrame,
2984                                            StyleAppearance aAppearance, nsRect* aOverflowRect) {
2985   nsIntMargin overflow;
2986   switch (aAppearance) {
2987     case StyleAppearance::Button:
2988     case StyleAppearance::MozMacDisclosureButtonOpen:
2989     case StyleAppearance::MozMacDisclosureButtonClosed:
2990     case StyleAppearance::MozMacHelpButton:
2991     case StyleAppearance::Toolbarbutton:
2992     case StyleAppearance::NumberInput:
2993     case StyleAppearance::Textfield:
2994     case StyleAppearance::Textarea:
2995     case StyleAppearance::Searchfield:
2996     case StyleAppearance::Listbox:
2997     case StyleAppearance::Menulist:
2998     case StyleAppearance::MenulistButton:
2999     case StyleAppearance::MozMenulistArrowButton:
3000     case StyleAppearance::Checkbox:
3001     case StyleAppearance::Radio:
3002     case StyleAppearance::Tab:
3003     case StyleAppearance::FocusOutline: {
3004       overflow.SizeTo(kMaxFocusRingWidth, kMaxFocusRingWidth, kMaxFocusRingWidth,
3005                       kMaxFocusRingWidth);
3006       break;
3007     }
3008     case StyleAppearance::ProgressBar: {
3009       // Progress bars draw a 2 pixel white shadow under their progress indicators.
3010       overflow.bottom = 2;
3011       break;
3012     }
3013     case StyleAppearance::Meter: {
3014       // Meter bars overflow their boxes by about 2 pixels.
3015       overflow.SizeTo(2, 2, 2, 2);
3016       break;
3017     }
3018     default:
3019       break;
3020   }
3022   if (IsHiDPIContext(aContext)) {
3023     // Double the number of device pixels.
3024     overflow += overflow;
3025   }
3027   if (overflow != nsIntMargin()) {
3028     int32_t p2a = aFrame->PresContext()->AppUnitsPerDevPixel();
3029     aOverflowRect->Inflate(nsMargin(
3030         NSIntPixelsToAppUnits(overflow.top, p2a), NSIntPixelsToAppUnits(overflow.right, p2a),
3031         NSIntPixelsToAppUnits(overflow.bottom, p2a), NSIntPixelsToAppUnits(overflow.left, p2a)));
3032     return true;
3033   }
3035   return false;
3038 NS_IMETHODIMP
3039 nsNativeThemeCocoa::GetMinimumWidgetSize(nsPresContext* aPresContext, nsIFrame* aFrame,
3040                                          StyleAppearance aAppearance, LayoutDeviceIntSize* aResult,
3041                                          bool* aIsOverridable) {
3042   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
3044   aResult->SizeTo(0, 0);
3045   *aIsOverridable = true;
3047   if (IsWidgetScrollbarPart(aAppearance)) {
3048     return ThemeCocoa::GetMinimumWidgetSize(aPresContext, aFrame, aAppearance, aResult,
3049                                             aIsOverridable);
3050   }
3052   switch (aAppearance) {
3053     case StyleAppearance::Button: {
3054       aResult->SizeTo(pushButtonSettings.minimumSizes[miniControlSize].width,
3055                       pushButtonSettings.naturalSizes[miniControlSize].height);
3056       break;
3057     }
3059     case StyleAppearance::ButtonArrowUp:
3060     case StyleAppearance::ButtonArrowDown: {
3061       aResult->SizeTo(kMenuScrollArrowSize.width, kMenuScrollArrowSize.height);
3062       *aIsOverridable = false;
3063       break;
3064     }
3066     case StyleAppearance::Menuarrow: {
3067       aResult->SizeTo(kMenuarrowSize.width, kMenuarrowSize.height);
3068       *aIsOverridable = false;
3069       break;
3070     }
3072     case StyleAppearance::MozMacDisclosureButtonOpen:
3073     case StyleAppearance::MozMacDisclosureButtonClosed: {
3074       aResult->SizeTo(kDisclosureButtonSize.width, kDisclosureButtonSize.height);
3075       *aIsOverridable = false;
3076       break;
3077     }
3079     case StyleAppearance::MozMacHelpButton: {
3080       aResult->SizeTo(kHelpButtonSize.width, kHelpButtonSize.height);
3081       *aIsOverridable = false;
3082       break;
3083     }
3085     case StyleAppearance::Toolbarbutton: {
3086       aResult->SizeTo(0, toolbarButtonHeights[miniControlSize]);
3087       break;
3088     }
3090     case StyleAppearance::Spinner:
3091     case StyleAppearance::SpinnerUpbutton:
3092     case StyleAppearance::SpinnerDownbutton: {
3093       SInt32 buttonHeight = 0, buttonWidth = 0;
3094       if (aFrame->GetContent()->IsXULElement()) {
3095         ::GetThemeMetric(kThemeMetricLittleArrowsWidth, &buttonWidth);
3096         ::GetThemeMetric(kThemeMetricLittleArrowsHeight, &buttonHeight);
3097       } else {
3098         NSSize size = spinnerSettings.minimumSizes[EnumSizeForCocoaSize(NSControlSizeMini)];
3099         buttonWidth = size.width;
3100         buttonHeight = size.height;
3101         if (aAppearance != StyleAppearance::Spinner) {
3102           // the buttons are half the height of the spinner
3103           buttonHeight /= 2;
3104         }
3105       }
3106       aResult->SizeTo(buttonWidth, buttonHeight);
3107       *aIsOverridable = true;
3108       break;
3109     }
3111     case StyleAppearance::Menulist:
3112     case StyleAppearance::MenulistButton: {
3113       SInt32 popupHeight = 0;
3114       ::GetThemeMetric(kThemeMetricPopupButtonHeight, &popupHeight);
3115       aResult->SizeTo(0, popupHeight);
3116       break;
3117     }
3119     case StyleAppearance::NumberInput:
3120     case StyleAppearance::Textfield:
3121     case StyleAppearance::Textarea:
3122     case StyleAppearance::Searchfield: {
3123       // at minimum, we should be tall enough for 9pt text.
3124       // I'm using hardcoded values here because the appearance manager
3125       // values for the frame size are incorrect.
3126       aResult->SizeTo(0, (2 + 2) /* top */ + 9 + (1 + 1) /* bottom */);
3127       break;
3128     }
3130     case StyleAppearance::MozWindowButtonBox: {
3131       NSSize size = WindowButtonsSize(aFrame);
3132       aResult->SizeTo(size.width, size.height);
3133       *aIsOverridable = false;
3134       break;
3135     }
3137     case StyleAppearance::ProgressBar: {
3138       SInt32 barHeight = 0;
3139       ::GetThemeMetric(kThemeMetricNormalProgressBarThickness, &barHeight);
3140       aResult->SizeTo(0, barHeight);
3141       break;
3142     }
3144     case StyleAppearance::Separator: {
3145       aResult->SizeTo(1, 1);
3146       break;
3147     }
3149     case StyleAppearance::Treetwisty:
3150     case StyleAppearance::Treetwistyopen: {
3151       SInt32 twistyHeight = 0, twistyWidth = 0;
3152       ::GetThemeMetric(kThemeMetricDisclosureButtonWidth, &twistyWidth);
3153       ::GetThemeMetric(kThemeMetricDisclosureButtonHeight, &twistyHeight);
3154       aResult->SizeTo(twistyWidth, twistyHeight);
3155       *aIsOverridable = false;
3156       break;
3157     }
3159     case StyleAppearance::Treeheader:
3160     case StyleAppearance::Treeheadercell: {
3161       SInt32 headerHeight = 0;
3162       ::GetThemeMetric(kThemeMetricListHeaderHeight, &headerHeight);
3163       aResult->SizeTo(0, headerHeight);
3164       break;
3165     }
3167     case StyleAppearance::Tab: {
3168       aResult->SizeTo(0, tabHeights[miniControlSize]);
3169       break;
3170     }
3172     case StyleAppearance::RangeThumb: {
3173       SInt32 width = 0;
3174       SInt32 height = 0;
3175       ::GetThemeMetric(kThemeMetricSliderMinThumbWidth, &width);
3176       ::GetThemeMetric(kThemeMetricSliderMinThumbHeight, &height);
3177       aResult->SizeTo(width, height);
3178       *aIsOverridable = false;
3179       break;
3180     }
3182     case StyleAppearance::MozMenulistArrowButton:
3183       return ThemeCocoa::GetMinimumWidgetSize(aPresContext, aFrame, aAppearance, aResult,
3184                                               aIsOverridable);
3186     case StyleAppearance::Resizer: {
3187       HIThemeGrowBoxDrawInfo drawInfo;
3188       drawInfo.version = 0;
3189       drawInfo.state = kThemeStateActive;
3190       drawInfo.kind = kHIThemeGrowBoxKindNormal;
3191       drawInfo.direction = kThemeGrowRight | kThemeGrowDown;
3192       drawInfo.size = kHIThemeGrowBoxSizeNormal;
3193       HIPoint pnt = {0, 0};
3194       HIRect bounds;
3195       HIThemeGetGrowBoxBounds(&pnt, &drawInfo, &bounds);
3196       aResult->SizeTo(bounds.size.width, bounds.size.height);
3197       *aIsOverridable = false;
3198       break;
3199     }
3200     default:
3201       break;
3202   }
3204   if (IsHiDPIContext(aPresContext->DeviceContext())) {
3205     *aResult = *aResult * 2;
3206   }
3208   return NS_OK;
3210   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
3213 NS_IMETHODIMP
3214 nsNativeThemeCocoa::WidgetStateChanged(nsIFrame* aFrame, StyleAppearance aAppearance,
3215                                        nsAtom* aAttribute, bool* aShouldRepaint,
3216                                        const nsAttrValue* aOldValue) {
3217   // Some widget types just never change state.
3218   switch (aAppearance) {
3219     case StyleAppearance::MozWindowTitlebar:
3220     case StyleAppearance::Toolbox:
3221     case StyleAppearance::Toolbar:
3222     case StyleAppearance::Statusbar:
3223     case StyleAppearance::Statusbarpanel:
3224     case StyleAppearance::Resizerpanel:
3225     case StyleAppearance::Tooltip:
3226     case StyleAppearance::Tabpanels:
3227     case StyleAppearance::Tabpanel:
3228     case StyleAppearance::Dialog:
3229     case StyleAppearance::Menupopup:
3230     case StyleAppearance::Groupbox:
3231     case StyleAppearance::Progresschunk:
3232     case StyleAppearance::ProgressBar:
3233     case StyleAppearance::Meter:
3234     case StyleAppearance::Meterchunk:
3235     case StyleAppearance::MozMacVibrantTitlebarLight:
3236     case StyleAppearance::MozMacVibrantTitlebarDark:
3237       *aShouldRepaint = false;
3238       return NS_OK;
3239     default:
3240       break;
3241   }
3243   // XXXdwh Not sure what can really be done here.  Can at least guess for
3244   // specific widgets that they're highly unlikely to have certain states.
3245   // For example, a toolbar doesn't care about any states.
3246   if (!aAttribute) {
3247     // Hover/focus/active changed.  Always repaint.
3248     *aShouldRepaint = true;
3249   } else {
3250     // Check the attribute to see if it's relevant.
3251     // disabled, checked, dlgtype, default, etc.
3252     *aShouldRepaint = false;
3253     if (aAttribute == nsGkAtoms::disabled || aAttribute == nsGkAtoms::checked ||
3254         aAttribute == nsGkAtoms::selected || aAttribute == nsGkAtoms::visuallyselected ||
3255         aAttribute == nsGkAtoms::menuactive || aAttribute == nsGkAtoms::sortDirection ||
3256         aAttribute == nsGkAtoms::focused || aAttribute == nsGkAtoms::_default ||
3257         aAttribute == nsGkAtoms::open || aAttribute == nsGkAtoms::hover)
3258       *aShouldRepaint = true;
3259   }
3261   return NS_OK;
3264 NS_IMETHODIMP
3265 nsNativeThemeCocoa::ThemeChanged() {
3266   // This is unimplemented because we don't care if gecko changes its theme
3267   // and macOS system appearance changes are handled by
3268   // nsLookAndFeel::SystemWantsDarkTheme.
3269   return NS_OK;
3272 bool nsNativeThemeCocoa::ThemeSupportsWidget(nsPresContext* aPresContext, nsIFrame* aFrame,
3273                                              StyleAppearance aAppearance) {
3274   if (IsWidgetScrollbarPart(aAppearance)) {
3275     return ThemeCocoa::ThemeSupportsWidget(aPresContext, aFrame, aAppearance);
3276   }
3277   // if this is a dropdown button in a combobox the answer is always no
3278   if (aAppearance == StyleAppearance::MozMenulistArrowButton) {
3279     nsIFrame* parentFrame = aFrame->GetParent();
3280     if (parentFrame && parentFrame->IsComboboxControlFrame()) return false;
3281   }
3283   switch (aAppearance) {
3284     // Combobox dropdowns don't support native theming in vertical mode.
3285     case StyleAppearance::Menulist:
3286     case StyleAppearance::MenulistButton:
3287     case StyleAppearance::MozMenulistArrowButton:
3288     case StyleAppearance::MenulistText:
3289       if (aFrame && aFrame->GetWritingMode().IsVertical()) {
3290         return false;
3291       }
3292       [[fallthrough]];
3294     case StyleAppearance::Listbox:
3295     case StyleAppearance::Dialog:
3296     case StyleAppearance::Window:
3297     case StyleAppearance::MozWindowButtonBox:
3298     case StyleAppearance::MozWindowTitlebar:
3299     case StyleAppearance::Checkmenuitem:
3300     case StyleAppearance::Menupopup:
3301     case StyleAppearance::Menuarrow:
3302     case StyleAppearance::Menuitem:
3303     case StyleAppearance::Menuseparator:
3304     case StyleAppearance::Tooltip:
3306     case StyleAppearance::Checkbox:
3307     case StyleAppearance::CheckboxContainer:
3308     case StyleAppearance::Radio:
3309     case StyleAppearance::RadioContainer:
3310     case StyleAppearance::Groupbox:
3311     case StyleAppearance::MozMacHelpButton:
3312     case StyleAppearance::MozMacDisclosureButtonOpen:
3313     case StyleAppearance::MozMacDisclosureButtonClosed:
3314     case StyleAppearance::Button:
3315     case StyleAppearance::ButtonArrowUp:
3316     case StyleAppearance::ButtonArrowDown:
3317     case StyleAppearance::Toolbarbutton:
3318     case StyleAppearance::Spinner:
3319     case StyleAppearance::SpinnerUpbutton:
3320     case StyleAppearance::SpinnerDownbutton:
3321     case StyleAppearance::Toolbar:
3322     case StyleAppearance::Statusbar:
3323     case StyleAppearance::NumberInput:
3324     case StyleAppearance::Textfield:
3325     case StyleAppearance::Textarea:
3326     case StyleAppearance::Searchfield:
3327     case StyleAppearance::Toolbox:
3328     case StyleAppearance::ProgressBar:
3329     case StyleAppearance::Progresschunk:
3330     case StyleAppearance::Meter:
3331     case StyleAppearance::Meterchunk:
3332     case StyleAppearance::Separator:
3334     case StyleAppearance::Tabpanels:
3335     case StyleAppearance::Tab:
3337     case StyleAppearance::Treetwisty:
3338     case StyleAppearance::Treetwistyopen:
3339     case StyleAppearance::Treeview:
3340     case StyleAppearance::Treeheader:
3341     case StyleAppearance::Treeheadercell:
3342     case StyleAppearance::Treeheadersortarrow:
3343     case StyleAppearance::Treeitem:
3344     case StyleAppearance::Treeline:
3345     case StyleAppearance::MozMacSourceList:
3346     case StyleAppearance::MozMacSourceListSelection:
3347     case StyleAppearance::MozMacActiveSourceListSelection:
3349     case StyleAppearance::Range:
3350       return !IsWidgetStyled(aPresContext, aFrame, aAppearance);
3352     case StyleAppearance::Resizer: {
3353       nsIFrame* parentFrame = aFrame->GetParent();
3354       if (!parentFrame || !parentFrame->IsScrollFrame()) return true;
3356       // Note that IsWidgetStyled is not called for resizers on Mac. This is
3357       // because for scrollable containers, the native resizer looks better
3358       // when (non-overlay) scrollbars are present even when the style is
3359       // overriden, and the custom transparent resizer looks better when
3360       // scrollbars are not present.
3361       nsIScrollableFrame* scrollFrame = do_QueryFrame(parentFrame);
3362       return (!LookAndFeel::UseOverlayScrollbars() && scrollFrame &&
3363               (!scrollFrame->GetScrollbarVisibility().isEmpty()));
3364     }
3366     case StyleAppearance::FocusOutline:
3367       return true;
3369     case StyleAppearance::MozMacVibrantTitlebarLight:
3370     case StyleAppearance::MozMacVibrantTitlebarDark:
3371       return true;
3372     default:
3373       break;
3374   }
3376   return false;
3379 bool nsNativeThemeCocoa::WidgetIsContainer(StyleAppearance aAppearance) {
3380   // flesh this out at some point
3381   switch (aAppearance) {
3382     case StyleAppearance::MozMenulistArrowButton:
3383     case StyleAppearance::Radio:
3384     case StyleAppearance::Checkbox:
3385     case StyleAppearance::ProgressBar:
3386     case StyleAppearance::Meter:
3387     case StyleAppearance::Range:
3388     case StyleAppearance::MozMacHelpButton:
3389     case StyleAppearance::MozMacDisclosureButtonOpen:
3390     case StyleAppearance::MozMacDisclosureButtonClosed:
3391       return false;
3392     default:
3393       break;
3394   }
3395   return true;
3398 bool nsNativeThemeCocoa::ThemeDrawsFocusForWidget(nsIFrame*, StyleAppearance aAppearance) {
3399   switch (aAppearance) {
3400     case StyleAppearance::Textarea:
3401     case StyleAppearance::Textfield:
3402     case StyleAppearance::Searchfield:
3403     case StyleAppearance::NumberInput:
3404     case StyleAppearance::Menulist:
3405     case StyleAppearance::MenulistButton:
3406     case StyleAppearance::Button:
3407     case StyleAppearance::MozMacHelpButton:
3408     case StyleAppearance::MozMacDisclosureButtonOpen:
3409     case StyleAppearance::MozMacDisclosureButtonClosed:
3410     case StyleAppearance::Radio:
3411     case StyleAppearance::Range:
3412     case StyleAppearance::Checkbox:
3413       return true;
3414     default:
3415       return false;
3416   }
3419 bool nsNativeThemeCocoa::ThemeNeedsComboboxDropmarker() { return false; }
3421 bool nsNativeThemeCocoa::WidgetAppearanceDependsOnWindowFocus(StyleAppearance aAppearance) {
3422   switch (aAppearance) {
3423     case StyleAppearance::Dialog:
3424     case StyleAppearance::Groupbox:
3425     case StyleAppearance::Tabpanels:
3426     case StyleAppearance::ButtonArrowUp:
3427     case StyleAppearance::ButtonArrowDown:
3428     case StyleAppearance::Checkmenuitem:
3429     case StyleAppearance::Menupopup:
3430     case StyleAppearance::Menuarrow:
3431     case StyleAppearance::Menuitem:
3432     case StyleAppearance::Menuseparator:
3433     case StyleAppearance::Tooltip:
3434     case StyleAppearance::Spinner:
3435     case StyleAppearance::SpinnerUpbutton:
3436     case StyleAppearance::SpinnerDownbutton:
3437     case StyleAppearance::Separator:
3438     case StyleAppearance::Toolbox:
3439     case StyleAppearance::NumberInput:
3440     case StyleAppearance::Textfield:
3441     case StyleAppearance::Treeview:
3442     case StyleAppearance::Treeline:
3443     case StyleAppearance::Textarea:
3444     case StyleAppearance::Listbox:
3445     case StyleAppearance::Resizer:
3446       return false;
3447     default:
3448       return true;
3449   }
3452 nsITheme::ThemeGeometryType nsNativeThemeCocoa::ThemeGeometryTypeForWidget(
3453     nsIFrame* aFrame, StyleAppearance aAppearance) {
3454   switch (aAppearance) {
3455     case StyleAppearance::MozWindowTitlebar:
3456       return eThemeGeometryTypeTitlebar;
3457     case StyleAppearance::Toolbar:
3458       return eThemeGeometryTypeToolbar;
3459     case StyleAppearance::Toolbox:
3460       return eThemeGeometryTypeToolbox;
3461     case StyleAppearance::MozWindowButtonBox:
3462       return eThemeGeometryTypeWindowButtons;
3463     case StyleAppearance::MozMacVibrantTitlebarLight:
3464       return eThemeGeometryTypeVibrantTitlebarLight;
3465     case StyleAppearance::MozMacVibrantTitlebarDark:
3466       return eThemeGeometryTypeVibrantTitlebarDark;
3467     case StyleAppearance::Tooltip:
3468       return eThemeGeometryTypeTooltip;
3469     case StyleAppearance::Menupopup:
3470       return eThemeGeometryTypeMenu;
3471     case StyleAppearance::Menuitem:
3472     case StyleAppearance::Checkmenuitem: {
3473       EventStates eventState = GetContentState(aFrame, aAppearance);
3474       bool isDisabled = eventState.HasState(NS_EVENT_STATE_DISABLED);
3475       bool isSelected = !isDisabled && CheckBooleanAttr(aFrame, nsGkAtoms::menuactive);
3476       return isSelected ? eThemeGeometryTypeHighlightedMenuItem : eThemeGeometryTypeMenu;
3477     }
3478     case StyleAppearance::MozMacSourceList:
3479       return eThemeGeometryTypeSourceList;
3480     case StyleAppearance::MozMacSourceListSelection:
3481       return IsInSourceList(aFrame) ? eThemeGeometryTypeSourceListSelection
3482                                     : eThemeGeometryTypeUnknown;
3483     case StyleAppearance::MozMacActiveSourceListSelection:
3484       return IsInSourceList(aFrame) ? eThemeGeometryTypeActiveSourceListSelection
3485                                     : eThemeGeometryTypeUnknown;
3486     default:
3487       return eThemeGeometryTypeUnknown;
3488   }
3491 nsITheme::Transparency nsNativeThemeCocoa::GetWidgetTransparency(nsIFrame* aFrame,
3492                                                                  StyleAppearance aAppearance) {
3493   if (IsWidgetScrollbarPart(aAppearance)) {
3494     return ThemeCocoa::GetWidgetTransparency(aFrame, aAppearance);
3495   }
3497   switch (aAppearance) {
3498     case StyleAppearance::Menupopup:
3499     case StyleAppearance::Tooltip:
3500     case StyleAppearance::Dialog:
3501     case StyleAppearance::Toolbar:
3502       return eTransparent;
3504     case StyleAppearance::Statusbar:
3505       // Knowing that scrollbars and statusbars are opaque improves
3506       // performance, because we create layers for them.
3507       return eOpaque;
3509     default:
3510       return eUnknownTransparency;
3511   }
3514 already_AddRefed<widget::Theme> do_CreateNativeThemeDoNotUseDirectly() {
3515   return do_AddRef(new nsNativeThemeCocoa());