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