Bug 1673120 - Extract out scrollbar drawing from nsNativeThemeCocoa so that it can later be used by nsNativeBasicTheme. r=spohl
authorMarkus Stange <mstange.moz@gmail.com>
Tue, 27 Oct 2020 15:16:49 +0000
changeset 554693 2113a9bdf470191c58e79f702f2fa28c5426bc9c
parent 554692 ab6218dd84f501adbdd38817c71ee6ffee87ee63
child 554694 782210c2ceaac280721976b9669df3680400be26
push id37898
push userabutkovits@mozilla.com
push dateWed, 28 Oct 2020 09:24:21 +0000
treeherdermozilla-central@83bf4fd3b1fb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersspohl
bugs1673120
milestone84.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1673120 - Extract out scrollbar drawing from nsNativeThemeCocoa so that it can later be used by nsNativeBasicTheme. r=spohl Differential Revision: https://phabricator.services.mozilla.com/D93844
widget/ScrollbarDrawingMac.cpp
widget/ScrollbarDrawingMac.h
widget/WidgetEventImpl.cpp
widget/cocoa/nsNativeThemeCocoa.h
widget/cocoa/nsNativeThemeCocoa.mm
widget/moz.build
widget/nsNativeTheme.cpp
widget/nsNativeTheme.h
new file mode 100644
--- /dev/null
+++ b/widget/ScrollbarDrawingMac.cpp
@@ -0,0 +1,337 @@
+/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 2; -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ScrollbarDrawingMac.h"
+#include "mozilla/gfx/Helpers.h"
+#include "mozilla/gfx/PathHelpers.h"
+#include "mozilla/RelativeLuminanceUtils.h"
+#include "nsLayoutUtils.h"
+#include "nsIFrame.h"
+#include "nsLookAndFeel.h"
+#include "nsContainerFrame.h"
+#include "nsNativeTheme.h"
+
+namespace mozilla {
+
+using namespace gfx;
+
+namespace widget {
+
+static nsIFrame* GetParentScrollbarFrame(nsIFrame* aFrame) {
+  // Walk our parents to find a scrollbar frame
+  nsIFrame* scrollbarFrame = aFrame;
+  do {
+    if (scrollbarFrame->IsScrollbarFrame()) {
+      break;
+    }
+  } while ((scrollbarFrame = scrollbarFrame->GetParent()));
+
+  // We return null if we can't find a parent scrollbar frame
+  return scrollbarFrame;
+}
+
+static bool IsSmallScrollbar(nsIFrame* aFrame) {
+  ComputedStyle* style = nsLayoutUtils::StyleForScrollbar(aFrame);
+  if (style->StyleUIReset()->mScrollbarWidth == StyleScrollbarWidth::Thin) {
+    return true;
+  }
+  nsIFrame* scrollbarFrame = GetParentScrollbarFrame(aFrame);
+  if (scrollbarFrame && scrollbarFrame->StyleDisplay()->EffectiveAppearance() ==
+                            StyleAppearance::ScrollbarSmall) {
+    return true;
+  }
+  return false;
+}
+
+static bool IsParentScrollbarRolledOver(nsIFrame* aFrame) {
+  nsIFrame* scrollbarFrame = GetParentScrollbarFrame(aFrame);
+  return nsLookAndFeel::GetInt(LookAndFeel::IntID::UseOverlayScrollbars) != 0
+             ? nsNativeTheme::CheckBooleanAttr(scrollbarFrame, nsGkAtoms::hover)
+             : nsNativeTheme::GetContentState(scrollbarFrame,
+                                              StyleAppearance::None)
+                   .HasState(NS_EVENT_STATE_HOVER);
+}
+
+LayoutDeviceIntSize ScrollbarDrawingMac::GetMinimumWidgetSize(
+    StyleAppearance aAppearance, nsIFrame* aFrame, float aDpiRatio) {
+  auto fn = [](StyleAppearance aAppearance, nsIFrame* aFrame) -> IntSize {
+    switch (aAppearance) {
+      case StyleAppearance::ScrollbarthumbHorizontal:
+        return IntSize{26, 0};
+      case StyleAppearance::ScrollbarthumbVertical:
+        return IntSize{0, 26};
+      case StyleAppearance::Scrollbar:
+      case StyleAppearance::ScrollbarSmall:
+      case StyleAppearance::ScrollbartrackVertical:
+      case StyleAppearance::ScrollbartrackHorizontal: {
+        if (nsLookAndFeel::GetInt(LookAndFeel::IntID::UseOverlayScrollbars) !=
+            0) {
+          if (IsSmallScrollbar(aFrame)) {
+            return IntSize{14, 14};
+          }
+          return IntSize{16, 16};
+        }
+        if (IsSmallScrollbar(aFrame)) {
+          return IntSize{11, 11};
+        }
+        return IntSize{15, 15};
+      }
+      case StyleAppearance::MozMenulistArrowButton:
+      case StyleAppearance::ScrollbarNonDisappearing:
+        return IntSize{15, 15};
+      case StyleAppearance::ScrollbarbuttonUp:
+      case StyleAppearance::ScrollbarbuttonDown:
+        return IntSize{15, 16};
+      case StyleAppearance::ScrollbarbuttonLeft:
+      case StyleAppearance::ScrollbarbuttonRight:
+        return IntSize{16, 15};
+      default:
+        return IntSize{};
+    }
+  };
+
+  IntSize minSize = fn(aAppearance, aFrame);
+  if (aDpiRatio >= 2.0f) {
+    return LayoutDeviceIntSize{minSize.width * 2, minSize.height * 2};
+  }
+  return LayoutDeviceIntSize{minSize.width, minSize.height};
+}
+
+ScrollbarParams ScrollbarDrawingMac::ComputeScrollbarParams(
+    nsIFrame* aFrame, bool aIsHorizontal) {
+  ScrollbarParams params;
+  params.overlay =
+      nsLookAndFeel::GetInt(LookAndFeel::IntID::UseOverlayScrollbars) != 0;
+  params.rolledOver = IsParentScrollbarRolledOver(aFrame);
+  params.small = IsSmallScrollbar(aFrame);
+  params.rtl = nsNativeTheme::IsFrameRTL(aFrame);
+  params.horizontal = aIsHorizontal;
+  params.onDarkBackground = nsNativeTheme::IsDarkBackground(aFrame);
+  // Don't use custom scrollbars for overlay scrollbars since they are
+  // generally good enough for use cases of custom scrollbars.
+  if (!params.overlay) {
+    ComputedStyle* style = nsLayoutUtils::StyleForScrollbar(aFrame);
+    const nsStyleUI* ui = style->StyleUI();
+    if (ui->HasCustomScrollbars()) {
+      const auto& colors = ui->mScrollbarColor.AsColors();
+      params.custom = true;
+      params.trackColor = colors.track.CalcColor(*style);
+      params.faceColor = colors.thumb.CalcColor(*style);
+    }
+  }
+
+  return params;
+}
+
+void ScrollbarDrawingMac::DrawScrollbarThumb(DrawTarget& aDT, const Rect& aRect,
+                                             const ScrollbarParams& aParams) {
+  // Compute the thumb thickness. This varies based on aParams.small,
+  // aParams.overlay and aParams.rolledOver. non-overlay: 6 / 8, overlay
+  // non-hovered: 5 / 7, overlay hovered: 9 / 11
+  float thickness = aParams.small ? 6.0f : 8.0f;
+  if (aParams.overlay) {
+    thickness -= 1.0f;
+    if (aParams.rolledOver) {
+      thickness += 4.0f;
+    }
+  }
+
+  // Compute the thumb rect.
+  float outerSpacing = (aParams.overlay || aParams.small) ? 1.0f : 2.0f;
+  Rect thumbRect = aRect;
+  thumbRect.Deflate(1.0f);
+  if (aParams.horizontal) {
+    float bottomEdge = thumbRect.YMost() - outerSpacing;
+    thumbRect.SetBoxY(bottomEdge - thickness, bottomEdge);
+  } else {
+    if (aParams.rtl) {
+      float leftEdge = thumbRect.X() + outerSpacing;
+      thumbRect.SetBoxX(leftEdge, leftEdge + thickness);
+    } else {
+      float rightEdge = thumbRect.XMost() - outerSpacing;
+      thumbRect.SetBoxX(rightEdge - thickness, rightEdge);
+    }
+  }
+
+  // Compute the thumb fill color.
+  nscolor faceColor;
+  if (aParams.custom) {
+    faceColor = aParams.faceColor;
+  } else {
+    if (aParams.overlay) {
+      faceColor = aParams.onDarkBackground ? NS_RGBA(255, 255, 255, 128)
+                                           : NS_RGBA(0, 0, 0, 128);
+    } else {
+      faceColor = aParams.rolledOver ? NS_RGBA(125, 125, 125, 255)
+                                     : NS_RGBA(194, 194, 194, 255);
+    }
+  }
+
+  // Fill the thumb shape with the color.
+  float cornerRadius =
+      (aParams.horizontal ? thumbRect.Height() : thumbRect.Width()) / 2.0f;
+  aDT.FillRoundedRect(RoundedRect(thumbRect, RectCornerRadii(cornerRadius)),
+                      ColorPattern(ToDeviceColor(faceColor)));
+
+  // Overlay scrollbars have an additional stroke around the fill.
+  if (aParams.overlay) {
+    float strokeOutset = aParams.onDarkBackground ? 0.3f : 0.5f;
+    float strokeWidth = aParams.onDarkBackground ? 0.6f : 0.8f;
+    nscolor strokeColor = aParams.onDarkBackground ? NS_RGBA(0, 0, 0, 48)
+                                                   : NS_RGBA(255, 255, 255, 48);
+    Rect thumbStrokeRect = thumbRect;
+    thumbStrokeRect.Inflate(strokeOutset);
+    float strokeRadius = (aParams.horizontal ? thumbStrokeRect.Height()
+                                             : thumbStrokeRect.Width()) /
+                         2.0f;
+
+    RefPtr<Path> path = MakePathForRoundedRect(aDT, thumbStrokeRect,
+                                               RectCornerRadii(strokeRadius));
+    aDT.Stroke(path, ColorPattern(ToDeviceColor(strokeColor)),
+               StrokeOptions(strokeWidth));
+  }
+}
+
+struct ScrollbarTrackDecorationColors {
+  nscolor mInnerColor = 0;
+  nscolor mShadowColor = 0;
+  nscolor mOuterColor = 0;
+};
+
+static ScrollbarTrackDecorationColors ComputeScrollbarTrackDecorationColors(
+    nscolor aTrackColor) {
+  ScrollbarTrackDecorationColors result;
+  float luminance = RelativeLuminanceUtils::Compute(aTrackColor);
+  if (luminance >= 0.5f) {
+    result.mInnerColor =
+        RelativeLuminanceUtils::Adjust(aTrackColor, luminance * 0.836f);
+    result.mShadowColor =
+        RelativeLuminanceUtils::Adjust(aTrackColor, luminance * 0.982f);
+    result.mOuterColor =
+        RelativeLuminanceUtils::Adjust(aTrackColor, luminance * 0.886f);
+  } else {
+    result.mInnerColor =
+        RelativeLuminanceUtils::Adjust(aTrackColor, luminance * 1.196f);
+    result.mShadowColor =
+        RelativeLuminanceUtils::Adjust(aTrackColor, luminance * 1.018f);
+    result.mOuterColor =
+        RelativeLuminanceUtils::Adjust(aTrackColor, luminance * 1.129f);
+  }
+  return result;
+}
+
+void ScrollbarDrawingMac::DrawScrollbarTrack(DrawTarget& aDT, const Rect& aRect,
+                                             const ScrollbarParams& aParams) {
+  if (aParams.overlay && !aParams.rolledOver) {
+    // Non-hovered overlay scrollbars don't have a track. Draw nothing.
+    return;
+  }
+
+  nscolor trackColor;
+  if (aParams.custom) {
+    trackColor = aParams.trackColor;
+  } else {
+    if (aParams.overlay) {
+      trackColor = aParams.onDarkBackground ? NS_RGBA(201, 201, 201, 38)
+                                            : NS_RGBA(250, 250, 250, 191);
+    } else {
+      trackColor = NS_RGBA(250, 250, 250, 255);
+    }
+  }
+
+  float thickness = aParams.horizontal ? aRect.height : aRect.width;
+
+  // The scrollbar track is drawn as multiple non-overlapping segments, which
+  // make up lines of different widths and with slightly different shading.
+  ScrollbarTrackDecorationColors colors =
+      ComputeScrollbarTrackDecorationColors(trackColor);
+  struct {
+    nscolor color;
+    float thickness;
+  } segments[] = {
+      {colors.mInnerColor, 1.0f},
+      {colors.mShadowColor, 1.0f},
+      {trackColor, thickness - 3.0f},
+      {colors.mOuterColor, 1.0f},
+  };
+
+  // Iterate over the segments "from inside to outside" and fill each segment.
+  // For horizontal scrollbars, iterate top to bottom.
+  // For vertical scrollbars, iterate left to right or right to left based on
+  // aParams.rtl.
+  float accumulatedThickness = 0.0f;
+  for (const auto& segment : segments) {
+    Rect segmentRect = aRect;
+    float startThickness = accumulatedThickness;
+    float endThickness = startThickness + segment.thickness;
+    if (aParams.horizontal) {
+      segmentRect.SetBoxY(aRect.Y() + startThickness, aRect.Y() + endThickness);
+    } else {
+      if (aParams.rtl) {
+        segmentRect.SetBoxX(aRect.XMost() - endThickness,
+                            aRect.XMost() - startThickness);
+      } else {
+        segmentRect.SetBoxX(aRect.X() + startThickness,
+                            aRect.X() + endThickness);
+      }
+    }
+    aDT.FillRect(segmentRect, ColorPattern(ToDeviceColor(segment.color)));
+    accumulatedThickness = endThickness;
+  }
+}
+
+void ScrollbarDrawingMac::DrawScrollCorner(DrawTarget& aDT, const Rect& aRect,
+                                           const ScrollbarParams& aParams) {
+  if (aParams.overlay && !aParams.rolledOver) {
+    // Non-hovered overlay scrollbars don't have a corner. Draw nothing.
+    return;
+  }
+
+  // Draw the following scroll corner.
+  //
+  //        Output:                      Rectangles:
+  // +---+---+----------+---+     +---+---+----------+---+
+  // | I | S | T ...  T | O |     | I | S | T ...  T | O |
+  // +---+   |          |   |     +---+---+          |   |
+  // | S   S | T ...  T |   |     | S   S | T ...  T | . |
+  // +-------+          | . |     +-------+----------+ . |
+  // | T      ...     T | . |     | T      ...     T | . |
+  // | .              . | . |     | .              . |   |
+  // | T      ...     T |   |     | T      ...     T | O |
+  // +------------------+   |     +------------------+---+
+  // | O       ...        O |     | O       ...        O |
+  // +----------------------+     +----------------------+
+
+  float width = aRect.width;
+  float height = aRect.height;
+  nscolor trackColor =
+      aParams.custom ? aParams.trackColor : NS_RGBA(250, 250, 250, 255);
+  ScrollbarTrackDecorationColors colors =
+      ComputeScrollbarTrackDecorationColors(trackColor);
+  struct {
+    nscolor color;
+    Rect relativeRect;
+  } pieces[] = {
+      {colors.mInnerColor, {0.0f, 0.0f, 1.0f, 1.0f}},
+      {colors.mShadowColor, {1.0f, 0.0f, 1.0f, 1.0f}},
+      {colors.mShadowColor, {0.0f, 1.0f, 2.0f, 1.0f}},
+      {trackColor, {2.0f, 0.0f, width - 3.0f, 2.0f}},
+      {trackColor, {0.0f, 2.0f, width - 1.0f, height - 3.0f}},
+      {colors.mOuterColor, {width - 1.0f, 0.0f, 1.0f, height - 1.0f}},
+      {colors.mOuterColor, {0.0f, height - 1.0f, width, 1.0f}},
+  };
+
+  for (const auto& piece : pieces) {
+    Rect pieceRect = piece.relativeRect + aRect.TopLeft();
+    if (aParams.rtl) {
+      pieceRect.x = aRect.XMost() - piece.relativeRect.XMost();
+    }
+    aDT.FillRect(pieceRect, ColorPattern(ToDeviceColor(piece.color)));
+  }
+}
+
+}  // namespace widget
+}  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/widget/ScrollbarDrawingMac.h
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_widget_ScrollbarDrawing_h
+#define mozilla_widget_ScrollbarDrawing_h
+
+#include "nsColor.h"
+#include "nsITheme.h"
+#include "Units.h"
+
+namespace mozilla {
+namespace widget {
+
+struct ScrollbarParams {
+  bool overlay = false;
+  bool rolledOver = false;
+  bool small = false;
+  bool horizontal = false;
+  bool rtl = false;
+  bool onDarkBackground = false;
+  bool custom = false;
+  // Two colors only used when custom is true.
+  nscolor trackColor = NS_RGBA(0, 0, 0, 0);
+  nscolor faceColor = NS_RGBA(0, 0, 0, 0);
+};
+
+class ScrollbarDrawingMac final {
+ public:
+  static LayoutDeviceIntSize GetMinimumWidgetSize(StyleAppearance aAppearance,
+                                                  nsIFrame* aFrame,
+                                                  float aDpiRatio);
+  static ScrollbarParams ComputeScrollbarParams(nsIFrame* aFrame,
+                                                bool aIsHorizontal);
+  static void DrawScrollbarThumb(gfx::DrawTarget& aDT, const gfx::Rect& aRect,
+                                 const ScrollbarParams& aParams);
+  static void DrawScrollbarTrack(gfx::DrawTarget& aDT, const gfx::Rect& aRect,
+                                 const ScrollbarParams& aParams);
+  static void DrawScrollCorner(gfx::DrawTarget& aDT, const gfx::Rect& aRect,
+                               const ScrollbarParams& aParams);
+};
+
+}  // namespace widget
+}  // namespace mozilla
+
+#endif
--- a/widget/WidgetEventImpl.cpp
+++ b/widget/WidgetEventImpl.cpp
@@ -18,16 +18,17 @@
 #include "nsCommandParams.h"
 #include "nsContentUtils.h"
 #include "nsIContent.h"
 #include "nsIDragSession.h"
 #include "nsPrintfCString.h"
 
 #if defined(XP_WIN)
 #  include "npapi.h"
+#  include "WinUtils.h"
 #endif
 
 namespace mozilla {
 
 /******************************************************************************
  * Global helper methods
  ******************************************************************************/
 
--- a/widget/cocoa/nsNativeThemeCocoa.h
+++ b/widget/cocoa/nsNativeThemeCocoa.h
@@ -10,16 +10,17 @@
 #import <Cocoa/Cocoa.h>
 
 #include "mozilla/Variant.h"
 
 #include "nsITheme.h"
 #include "nsCOMPtr.h"
 #include "nsAtom.h"
 #include "nsNativeTheme.h"
+#include "ScrollbarDrawingMac.h"
 
 @class CellDrawView;
 @class NSProgressBarCell;
 class nsDeviceContext;
 struct SegmentedControlRenderSettings;
 
 namespace mozilla {
 class EventStates;
@@ -198,37 +199,17 @@ class nsNativeThemeCocoa : private nsNat
     int32_t max = 0;
     bool insideActiveWindow = false;
     bool disabled = false;
     bool focused = false;
     bool horizontal = true;
     bool reverse = false;
   };
 
-  struct ScrollbarParams {
-    ScrollbarParams()
-        : overlay(false),
-          rolledOver(false),
-          small(false),
-          horizontal(false),
-          rtl(false),
-          onDarkBackground(false),
-          custom(false) {}
-
-    bool overlay : 1;
-    bool rolledOver : 1;
-    bool small : 1;
-    bool horizontal : 1;
-    bool rtl : 1;
-    bool onDarkBackground : 1;
-    bool custom : 1;
-    // Two colors only used when custom is true.
-    nscolor trackColor = NS_RGBA(0, 0, 0, 0);
-    nscolor faceColor = NS_RGBA(0, 0, 0, 0);
-  };
+  using ScrollbarParams = mozilla::widget::ScrollbarParams;
 
   enum Widget : uint8_t {
     eColorFill,       // mozilla::gfx::sRGBColor
     eMenuBackground,  // MenuBackgroundParams
     eMenuIcon,        // MenuIconParams
     eMenuItem,        // MenuItemParams
     eMenuSeparator,   // MenuItemParams
     eTooltip,
@@ -375,16 +356,18 @@ class nsNativeThemeCocoa : private nsNat
                      SegmentParams, UnifiedToolbarParams, TextBoxParams, SearchFieldParams,
                      ProgressParams, MeterParams, TreeHeaderCellParams, ScaleParams,
                      ScrollbarParams, bool>
         mVariant;
 
     enum Widget mWidget;
   };
 
+  using ScrollbarDrawingMac = mozilla::widget::ScrollbarDrawingMac;
+
   nsNativeThemeCocoa();
 
   NS_DECL_ISUPPORTS_INHERITED
 
   // The nsITheme interface.
   NS_IMETHOD DrawWidgetBackground(gfxContext* aContext, nsIFrame* aFrame,
                                   StyleAppearance aAppearance, const nsRect& aRect,
                                   const nsRect& aDirtyRect) override;
@@ -446,17 +429,16 @@ class nsNativeThemeCocoa : private nsNat
   SearchFieldParams ComputeSearchFieldParams(nsIFrame* aFrame, mozilla::EventStates aEventState);
   ProgressParams ComputeProgressParams(nsIFrame* aFrame, mozilla::EventStates aEventState,
                                        bool aIsHorizontal);
   MeterParams ComputeMeterParams(nsIFrame* aFrame);
   TreeHeaderCellParams ComputeTreeHeaderCellParams(nsIFrame* aFrame,
                                                    mozilla::EventStates aEventState);
   mozilla::Maybe<ScaleParams> ComputeHTMLScaleParams(nsIFrame* aFrame,
                                                      mozilla::EventStates aEventState);
-  ScrollbarParams ComputeScrollbarParams(nsIFrame* aFrame, bool aIsHorizontal);
 
   // HITheme drawing routines
   void DrawTextBox(CGContextRef context, const HIRect& inBoxRect, TextBoxParams aParams);
   void DrawMeter(CGContextRef context, const HIRect& inBoxRect, const MeterParams& aParams);
   void DrawSegment(CGContextRef cgContext, const HIRect& inBoxRect, const SegmentParams& aParams);
   void DrawSegmentBackground(CGContextRef cgContext, const HIRect& inBoxRect,
                              const SegmentParams& aParams);
   void DrawTabPanel(CGContextRef context, const HIRect& inBoxRect, bool aIsInsideActiveWindow);
@@ -496,30 +478,21 @@ class nsNativeThemeCocoa : private nsNat
                       const SpinButtonParams& aParams);
   void DrawToolbar(CGContextRef cgContext, const CGRect& inBoxRect, bool aIsMain);
   void DrawUnifiedToolbar(CGContextRef cgContext, const HIRect& inBoxRect,
                           const UnifiedToolbarParams& aParams);
   void DrawNativeTitlebar(CGContextRef aContext, CGRect aTitlebarRect,
                           const UnifiedToolbarParams& aParams);
   void DrawStatusBar(CGContextRef cgContext, const HIRect& inBoxRect, bool aIsMain);
   void DrawResizer(CGContextRef cgContext, const HIRect& aRect, bool aIsRTL);
-  void DrawScrollbarThumb(mozilla::gfx::DrawTarget& aDT, const mozilla::gfx::Rect& aRect,
-                          ScrollbarParams aParams);
-  void DrawScrollbarTrack(mozilla::gfx::DrawTarget& aDT, const mozilla::gfx::Rect& aRect,
-                          ScrollbarParams aParams);
-  void DrawScrollCorner(mozilla::gfx::DrawTarget& aDT, const mozilla::gfx::Rect& aRect,
-                        ScrollbarParams aParams);
   void DrawMultilineTextField(CGContextRef cgContext, const CGRect& inBoxRect, bool aIsFocused);
   void DrawSourceList(CGContextRef cgContext, const CGRect& inBoxRect, bool aIsActive);
   void DrawSourceListSelection(CGContextRef aContext, const CGRect& aRect, bool aWindowIsActive,
                                bool aSelectionIsActive);
 
-  // Scrollbars
-  bool IsParentScrollbarRolledOver(nsIFrame* aFrame);
-
   void RenderWidget(const WidgetInfo& aWidgetInfo, mozilla::gfx::DrawTarget& aDrawTarget,
                     const mozilla::gfx::Rect& aWidgetRect, const mozilla::gfx::Rect& aDirtyRect,
                     float aScale);
 
  private:
   NSButtonCell* mDisclosureButtonCell;
   NSButtonCell* mHelpButtonCell;
   NSButtonCell* mPushButtonCell;
--- a/widget/cocoa/nsNativeThemeCocoa.mm
+++ b/widget/cocoa/nsNativeThemeCocoa.mm
@@ -27,17 +27,16 @@
 #include "nsCocoaFeatures.h"
 #include "nsCocoaWindow.h"
 #include "nsNativeBasicTheme.h"
 #include "nsNativeThemeColors.h"
 #include "nsIScrollableFrame.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/EventStates.h"
 #include "mozilla/Range.h"
-#include "mozilla/RelativeLuminanceUtils.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/HTMLMeterElement.h"
 #include "mozilla/layers/StackingContextHelper.h"
 #include "mozilla/StaticPrefs_layout.h"
 #include "mozilla/StaticPrefs_widget.h"
 #include "nsLookAndFeel.h"
 #include "VibrancyManager.h"
 
@@ -2269,27 +2268,16 @@ void nsNativeThemeCocoa::DrawSegment(CGC
     @"size" : CUIControlSizeForCocoaSize(controlSize),
     @"is.flipped" : [NSNumber numberWithBool:YES],
     @"direction" : @"up"
   };
 
   RenderWithCoreUI(drawRect, cgContext, dict);
 }
 
-static nsIFrame* GetParentScrollbarFrame(nsIFrame* aFrame) {
-  // Walk our parents to find a scrollbar frame
-  nsIFrame* scrollbarFrame = aFrame;
-  do {
-    if (scrollbarFrame->IsScrollbarFrame()) break;
-  } while ((scrollbarFrame = scrollbarFrame->GetParent()));
-
-  // We return null if we can't find a parent scrollbar frame
-  return scrollbarFrame;
-}
-
 void nsNativeThemeCocoa::DrawToolbar(CGContextRef cgContext, const CGRect& inBoxRect,
                                      bool aIsMain) {
   CGRect drawRect = inBoxRect;
 
   // top border
   drawRect.size.height = 1.0f;
   DrawNativeGreyColorInRect(cgContext, toolbarTopBorderGrey, drawRect, aIsMain);
 
@@ -2420,241 +2408,16 @@ void nsNativeThemeCocoa::DrawResizer(CGC
   drawInfo.direction = kThemeGrowRight | kThemeGrowDown;
   drawInfo.size = kHIThemeGrowBoxSizeNormal;
 
   RenderTransformedHIThemeControl(cgContext, aRect, RenderResizer, &drawInfo, aIsRTL);
 
   NS_OBJC_END_TRY_ABORT_BLOCK;
 }
 
-static bool IsSmallScrollbar(nsIFrame* aFrame) {
-  ComputedStyle* style = nsLayoutUtils::StyleForScrollbar(aFrame);
-  if (style->StyleUIReset()->mScrollbarWidth == StyleScrollbarWidth::Thin) {
-    return true;
-  }
-  nsIFrame* scrollbarFrame = GetParentScrollbarFrame(aFrame);
-  if (scrollbarFrame &&
-      scrollbarFrame->StyleDisplay()->EffectiveAppearance() == StyleAppearance::ScrollbarSmall) {
-    return true;
-  }
-  return false;
-}
-
-nsNativeThemeCocoa::ScrollbarParams nsNativeThemeCocoa::ComputeScrollbarParams(nsIFrame* aFrame,
-                                                                               bool aIsHorizontal) {
-  ScrollbarParams params;
-  params.overlay = nsLookAndFeel::UseOverlayScrollbars();
-  params.rolledOver = IsParentScrollbarRolledOver(aFrame);
-  params.small = IsSmallScrollbar(aFrame);
-  params.rtl = IsFrameRTL(aFrame);
-  params.horizontal = aIsHorizontal;
-  params.onDarkBackground = IsDarkBackground(aFrame);
-  // Don't use custom scrollbars for overlay scrollbars since they are
-  // generally good enough for use cases of custom scrollbars.
-  if (!params.overlay) {
-    ComputedStyle* style = nsLayoutUtils::StyleForScrollbar(aFrame);
-    const nsStyleUI* ui = style->StyleUI();
-    if (ui->HasCustomScrollbars()) {
-      const auto& colors = ui->mScrollbarColor.AsColors();
-      params.custom = true;
-      params.trackColor = colors.track.CalcColor(*style);
-      params.faceColor = colors.thumb.CalcColor(*style);
-    }
-  }
-
-  return params;
-}
-
-void nsNativeThemeCocoa::DrawScrollbarThumb(DrawTarget& aDT, const gfx::Rect& aRect,
-                                            ScrollbarParams aParams) {
-  // Compute the thumb thickness. This varies based on aParams.small, aParams.overlay and
-  // aParams.rolledOver. non-overlay: 6 / 8, overlay non-hovered: 5 / 7, overlay hovered: 9 / 11
-  float thickness = aParams.small ? 6.0f : 8.0f;
-  if (aParams.overlay) {
-    thickness -= 1.0f;
-    if (aParams.rolledOver) {
-      thickness += 4.0f;
-    }
-  }
-
-  // Compute the thumb rect.
-  float outerSpacing = (aParams.overlay || aParams.small) ? 1.0f : 2.0f;
-  gfx::Rect thumbRect = aRect;
-  thumbRect.Deflate(1.0f);
-  if (aParams.horizontal) {
-    float bottomEdge = thumbRect.YMost() - outerSpacing;
-    thumbRect.SetBoxY(bottomEdge - thickness, bottomEdge);
-  } else {
-    if (aParams.rtl) {
-      float leftEdge = thumbRect.X() + outerSpacing;
-      thumbRect.SetBoxX(leftEdge, leftEdge + thickness);
-    } else {
-      float rightEdge = thumbRect.XMost() - outerSpacing;
-      thumbRect.SetBoxX(rightEdge - thickness, rightEdge);
-    }
-  }
-
-  // Compute the thumb fill color.
-  nscolor faceColor;
-  if (aParams.custom) {
-    faceColor = aParams.faceColor;
-  } else {
-    if (aParams.overlay) {
-      faceColor = aParams.onDarkBackground ? NS_RGBA(255, 255, 255, 128) : NS_RGBA(0, 0, 0, 128);
-    } else {
-      faceColor = aParams.rolledOver ? NS_RGBA(125, 125, 125, 255) : NS_RGBA(194, 194, 194, 255);
-    }
-  }
-
-  // Fill the thumb shape with the color.
-  float cornerRadius = (aParams.horizontal ? thumbRect.Height() : thumbRect.Width()) / 2.0f;
-  aDT.FillRoundedRect(RoundedRect(thumbRect, RectCornerRadii(cornerRadius)),
-                      ColorPattern(ToDeviceColor(faceColor)));
-
-  // Overlay scrollbars have an additional stroke around the fill.
-  if (aParams.overlay) {
-    float strokeOutset = aParams.onDarkBackground ? 0.3f : 0.5f;
-    float strokeWidth = aParams.onDarkBackground ? 0.6f : 0.8f;
-    nscolor strokeColor =
-        aParams.onDarkBackground ? NS_RGBA(0, 0, 0, 48) : NS_RGBA(255, 255, 255, 48);
-    gfx::Rect thumbStrokeRect = thumbRect;
-    thumbStrokeRect.Inflate(strokeOutset);
-    float strokeRadius =
-        (aParams.horizontal ? thumbStrokeRect.Height() : thumbStrokeRect.Width()) / 2.0f;
-
-    RefPtr<Path> path = MakePathForRoundedRect(aDT, thumbStrokeRect, RectCornerRadii(strokeRadius));
-    aDT.Stroke(path, ColorPattern(ToDeviceColor(strokeColor)), StrokeOptions(strokeWidth));
-  }
-}
-
-struct ScrollbarTrackDecorationColors {
-  nscolor mInnerColor;
-  nscolor mShadowColor;
-  nscolor mOuterColor;
-};
-
-static ScrollbarTrackDecorationColors ComputeScrollbarTrackDecorationColors(nscolor aTrackColor) {
-  ScrollbarTrackDecorationColors result;
-  float luminance = RelativeLuminanceUtils::Compute(aTrackColor);
-  if (luminance >= 0.5) {
-    result.mInnerColor = RelativeLuminanceUtils::Adjust(aTrackColor, luminance * 0.836);
-    result.mShadowColor = RelativeLuminanceUtils::Adjust(aTrackColor, luminance * 0.982);
-    result.mOuterColor = RelativeLuminanceUtils::Adjust(aTrackColor, luminance * 0.886);
-  } else {
-    result.mInnerColor = RelativeLuminanceUtils::Adjust(aTrackColor, luminance * 1.196);
-    result.mShadowColor = RelativeLuminanceUtils::Adjust(aTrackColor, luminance * 1.018);
-    result.mOuterColor = RelativeLuminanceUtils::Adjust(aTrackColor, luminance * 1.129);
-  }
-  return result;
-}
-
-void nsNativeThemeCocoa::DrawScrollbarTrack(DrawTarget& aDT, const gfx::Rect& aRect,
-                                            ScrollbarParams aParams) {
-  if (aParams.overlay && !aParams.rolledOver) {
-    // Non-hovered overlay scrollbars don't have a track. Draw nothing.
-    return;
-  }
-
-  nscolor trackColor;
-  if (aParams.custom) {
-    trackColor = aParams.trackColor;
-  } else {
-    if (aParams.overlay) {
-      trackColor =
-          aParams.onDarkBackground ? NS_RGBA(201, 201, 201, 38) : NS_RGBA(250, 250, 250, 191);
-    } else {
-      trackColor = NS_RGBA(250, 250, 250, 255);
-    }
-  }
-
-  float thickness = aParams.horizontal ? aRect.height : aRect.width;
-
-  // The scrollbar track is drawn as multiple non-overlapping segments, which make up lines of
-  // different widths and with slightly different shading.
-  ScrollbarTrackDecorationColors colors = ComputeScrollbarTrackDecorationColors(trackColor);
-  struct {
-    nscolor color;
-    float thickness;
-  } segments[] = {
-      {colors.mInnerColor, 1.0f},
-      {colors.mShadowColor, 1.0f},
-      {trackColor, thickness - 3.0f},
-      {colors.mOuterColor, 1.0f},
-  };
-
-  // Iterate over the segments "from inside to outside" and fill each segment.
-  // For horizontal scrollbars, iterate top to bottom.
-  // For vertical scrollbars, iterate left to right or right to left based on aParams.rtl.
-  float accumulatedThickness = 0.0f;
-  for (const auto& segment : segments) {
-    gfx::Rect segmentRect = aRect;
-    float startThickness = accumulatedThickness;
-    float endThickness = startThickness + segment.thickness;
-    if (aParams.horizontal) {
-      segmentRect.SetBoxY(aRect.Y() + startThickness, aRect.Y() + endThickness);
-    } else {
-      if (aParams.rtl) {
-        segmentRect.SetBoxX(aRect.XMost() - endThickness, aRect.XMost() - startThickness);
-      } else {
-        segmentRect.SetBoxX(aRect.X() + startThickness, aRect.X() + endThickness);
-      }
-    }
-    aDT.FillRect(segmentRect, ColorPattern(ToDeviceColor(segment.color)));
-    accumulatedThickness = endThickness;
-  }
-}
-
-void nsNativeThemeCocoa::DrawScrollCorner(DrawTarget& aDT, const gfx::Rect& aRect,
-                                          ScrollbarParams aParams) {
-  if (aParams.overlay && !aParams.rolledOver) {
-    // Non-hovered overlay scrollbars don't have a corner. Draw nothing.
-    return;
-  }
-
-  // Draw the following scroll corner.
-  //
-  //        Output:                      Rectangles:
-  // +---+---+----------+---+     +---+---+----------+---+
-  // | I | S | T ...  T | O |     | I | S | T ...  T | O |
-  // +---+   |          |   |     +---+---+          |   |
-  // | S   S | T ...  T |   |     | S   S | T ...  T | . |
-  // +-------+          | . |     +-------+----------+ . |
-  // | T      ...     T | . |     | T      ...     T | . |
-  // | .              . | . |     | .              . |   |
-  // | T      ...     T |   |     | T      ...     T | O |
-  // +------------------+   |     +------------------+---+
-  // | O       ...        O |     | O       ...        O |
-  // +----------------------+     +----------------------+
-
-  float width = aRect.width;
-  float height = aRect.height;
-  nscolor trackColor = aParams.custom ? aParams.trackColor : NS_RGBA(250, 250, 250, 255);
-  ScrollbarTrackDecorationColors colors = ComputeScrollbarTrackDecorationColors(trackColor);
-  struct {
-    nscolor color;
-    gfx::Rect relativeRect;
-  } pieces[] = {
-      {colors.mInnerColor, {0.0f, 0.0f, 1.0f, 1.0f}},
-      {colors.mShadowColor, {1.0f, 0.0f, 1.0f, 1.0f}},
-      {colors.mShadowColor, {0.0f, 1.0f, 2.0f, 1.0f}},
-      {trackColor, {2.0f, 0.0f, width - 3.0f, 2.0f}},
-      {trackColor, {0.0f, 2.0f, width - 1.0f, height - 3.0f}},
-      {colors.mOuterColor, {width - 1.0f, 0.0f, 1.0f, height - 1.0f}},
-      {colors.mOuterColor, {0.0f, height - 1.0f, width, 1.0f}},
-  };
-
-  for (const auto& piece : pieces) {
-    gfx::Rect pieceRect = piece.relativeRect + aRect.TopLeft();
-    if (aParams.rtl) {
-      pieceRect.x = aRect.XMost() - piece.relativeRect.XMost();
-    }
-    aDT.FillRect(pieceRect, ColorPattern(ToDeviceColor(piece.color)));
-  }
-}
-
 static const sRGBColor kTooltipBackgroundColor(0.996, 1.000, 0.792, 0.950);
 static const sRGBColor kMultilineTextFieldTopBorderColor(0.4510, 0.4510, 0.4510, 1.0);
 static const sRGBColor kMultilineTextFieldSidesAndBottomBorderColor(0.6, 0.6, 0.6, 1.0);
 static const sRGBColor kListboxTopBorderColor(0.557, 0.557, 0.557, 1.0);
 static const sRGBColor kListBoxSidesAndBottomBorderColor(0.745, 0.745, 0.745, 1.0);
 
 void nsNativeThemeCocoa::DrawMultilineTextField(CGContextRef cgContext, const CGRect& inBoxRect,
                                                 bool aIsFocused) {
@@ -2725,24 +2488,16 @@ void nsNativeThemeCocoa::DrawSourceListS
     } else {
       fillColor = [NSColor colorWithWhite:0.808 alpha:1.0];
     }
   }
   CGContextSetFillColorWithColor(aContext, [fillColor CGColor]);
   CGContextFillRect(aContext, aRect);
 }
 
-bool nsNativeThemeCocoa::IsParentScrollbarRolledOver(nsIFrame* aFrame) {
-  nsIFrame* scrollbarFrame = GetParentScrollbarFrame(aFrame);
-  return nsLookAndFeel::UseOverlayScrollbars()
-             ? CheckBooleanAttr(scrollbarFrame, nsGkAtoms::hover)
-             : GetContentState(scrollbarFrame, StyleAppearance::None)
-                   .HasState(NS_EVENT_STATE_HOVER);
-}
-
 static bool IsHiDPIContext(nsDeviceContext* aContext) {
   return AppUnitsPerCSSPixel() >= 2 * aContext->AppUnitsPerDevPixelAtUnitFullZoom();
 }
 
 Maybe<nsNativeThemeCocoa::WidgetInfo> nsNativeThemeCocoa::ComputeWidgetInfo(
     nsIFrame* aFrame, StyleAppearance aAppearance, const nsRect& aRect) {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
 
@@ -3043,32 +2798,33 @@ Maybe<nsNativeThemeCocoa::WidgetInfo> ns
       break;
     }
 
     case StyleAppearance::ScrollbarSmall:
     case StyleAppearance::Scrollbar:
       break;
     case StyleAppearance::ScrollbarthumbVertical:
     case StyleAppearance::ScrollbarthumbHorizontal:
-      return Some(WidgetInfo::ScrollbarThumb(ComputeScrollbarParams(
+      return Some(WidgetInfo::ScrollbarThumb(ScrollbarDrawingMac::ComputeScrollbarParams(
           aFrame, aAppearance == StyleAppearance::ScrollbarthumbHorizontal)));
 
     case StyleAppearance::ScrollbarbuttonUp:
     case StyleAppearance::ScrollbarbuttonLeft:
     case StyleAppearance::ScrollbarbuttonDown:
     case StyleAppearance::ScrollbarbuttonRight:
       break;
 
     case StyleAppearance::ScrollbartrackHorizontal:
     case StyleAppearance::ScrollbartrackVertical:
-      return Some(WidgetInfo::ScrollbarTrack(ComputeScrollbarParams(
+      return Some(WidgetInfo::ScrollbarTrack(ScrollbarDrawingMac::ComputeScrollbarParams(
           aFrame, aAppearance == StyleAppearance::ScrollbartrackHorizontal)));
 
     case StyleAppearance::Scrollcorner:
-      return Some(WidgetInfo::ScrollCorner(ComputeScrollbarParams(aFrame, false)));
+      return Some(
+          WidgetInfo::ScrollCorner(ScrollbarDrawingMac::ComputeScrollbarParams(aFrame, false)));
 
     case StyleAppearance::Textarea:
       return Some(WidgetInfo::MultilineTextField(eventState.HasState(NS_EVENT_STATE_FOCUS)));
 
     case StyleAppearance::Listbox:
       return Some(WidgetInfo::ListBox());
 
     case StyleAppearance::MozMacSourceList: {
@@ -3157,27 +2913,27 @@ void nsNativeThemeCocoa::RenderWidget(co
   switch (widget) {
     case Widget::eColorFill: {
       sRGBColor color = aWidgetInfo.Params<sRGBColor>();
       aDrawTarget.FillRect(widgetRect, ColorPattern(ToDeviceColor(color)));
       break;
     }
     case Widget::eScrollbarThumb: {
       ScrollbarParams params = aWidgetInfo.Params<ScrollbarParams>();
-      DrawScrollbarThumb(aDrawTarget, widgetRect, params);
+      ScrollbarDrawingMac::DrawScrollbarThumb(aDrawTarget, widgetRect, params);
       break;
     }
     case Widget::eScrollbarTrack: {
       ScrollbarParams params = aWidgetInfo.Params<ScrollbarParams>();
-      DrawScrollbarTrack(aDrawTarget, widgetRect, params);
+      ScrollbarDrawingMac::DrawScrollbarTrack(aDrawTarget, widgetRect, params);
       break;
     }
     case Widget::eScrollCorner: {
       ScrollbarParams params = aWidgetInfo.Params<ScrollbarParams>();
-      DrawScrollCorner(aDrawTarget, widgetRect, params);
+      ScrollbarDrawingMac::DrawScrollCorner(aDrawTarget, widgetRect, params);
       break;
     }
     default: {
       // The remaining widgets require a CGContext.
       CGRect macRect =
           CGRectMake(widgetRect.X(), widgetRect.Y(), widgetRect.Width(), widgetRect.Height());
 
       gfxQuartzNativeDrawing nativeDrawing(aDrawTarget, dirtyRect);
@@ -3460,18 +3216,19 @@ bool nsNativeThemeCocoa::CreateWebRender
     case StyleAppearance::Range:
     case StyleAppearance::ScrollbarthumbVertical:
     case StyleAppearance::ScrollbarthumbHorizontal:
       return false;
 
     case StyleAppearance::Scrollcorner:
     case StyleAppearance::ScrollbartrackHorizontal:
     case StyleAppearance::ScrollbartrackVertical: {
-      BOOL isOverlay = nsLookAndFeel::UseOverlayScrollbars();
-      if (isOverlay && !IsParentScrollbarRolledOver(aFrame)) {
+      ScrollbarParams params = ScrollbarDrawingMac::ComputeScrollbarParams(
+          aFrame, aAppearance == StyleAppearance::ScrollbartrackHorizontal);
+      if (params.overlay && !params.rolledOver) {
         // There is no scrollbar track, draw nothing and return true.
         return true;
       }
       // There is a scrollbar track and it needs to be drawn using fallback.
       return false;
     }
 
     case StyleAppearance::Textarea: {
@@ -3763,19 +3520,16 @@ bool nsNativeThemeCocoa::GetWidgetOverfl
         NSIntPixelsToAppUnits(overflow.top, p2a), NSIntPixelsToAppUnits(overflow.right, p2a),
         NSIntPixelsToAppUnits(overflow.bottom, p2a), NSIntPixelsToAppUnits(overflow.left, p2a)));
     return true;
   }
 
   return false;
 }
 
-static const int32_t kRegularScrollbarThumbMinSize = 26;
-static const int32_t kSmallScrollbarThumbMinSize = 26;
-
 NS_IMETHODIMP
 nsNativeThemeCocoa::GetMinimumWidgetSize(nsPresContext* aPresContext, nsIFrame* aFrame,
                                          StyleAppearance aAppearance, LayoutDeviceIntSize* aResult,
                                          bool* aIsOverridable) {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
 
   aResult->SizeTo(0, 0);
   *aIsOverridable = true;
@@ -3917,75 +3671,36 @@ nsNativeThemeCocoa::GetMinimumWidgetSize
       ::GetThemeMetric(kThemeMetricSliderMinThumbWidth, &width);
       ::GetThemeMetric(kThemeMetricSliderMinThumbHeight, &height);
       aResult->SizeTo(width, height);
       *aIsOverridable = false;
       break;
     }
 
     case StyleAppearance::ScrollbarthumbHorizontal:
-    case StyleAppearance::ScrollbarthumbVertical: {
-      bool isSmall = IsSmallScrollbar(aFrame);
-      bool isHorizontal = (aAppearance == StyleAppearance::ScrollbarthumbHorizontal);
-      int32_t& minSize = isHorizontal ? aResult->width : aResult->height;
-      minSize = isSmall ? kSmallScrollbarThumbMinSize : kRegularScrollbarThumbMinSize;
-      break;
-    }
-
+    case StyleAppearance::ScrollbarthumbVertical:
     case StyleAppearance::Scrollbar:
     case StyleAppearance::ScrollbarSmall:
     case StyleAppearance::ScrollbartrackVertical:
-    case StyleAppearance::ScrollbartrackHorizontal: {
+    case StyleAppearance::ScrollbartrackHorizontal:
+    case StyleAppearance::ScrollbarbuttonUp:
+    case StyleAppearance::ScrollbarbuttonDown:
+    case StyleAppearance::ScrollbarbuttonLeft:
+    case StyleAppearance::ScrollbarbuttonRight: {
       *aIsOverridable = false;
-
-      if (nsLookAndFeel::UseOverlayScrollbars()) {
-        if (IsSmallScrollbar(aFrame)) {
-          aResult->SizeTo(14, 14);
-        } else {
-          aResult->SizeTo(16, 16);
-        }
-      } else {
-        if (IsSmallScrollbar(aFrame)) {
-          aResult->SizeTo(11, 11);
-        } else {
-          aResult->SizeTo(15, 15);
-        }
-      }
-
+      *aResult = ScrollbarDrawingMac::GetMinimumWidgetSize(aAppearance, aFrame, 1.0f);
       break;
     }
 
     case StyleAppearance::MozMenulistArrowButton:
     case StyleAppearance::ScrollbarNonDisappearing: {
-      int32_t themeMetric = kThemeMetricScrollBarWidth;
-      SInt32 scrollbarWidth = 0;
-      ::GetThemeMetric(themeMetric, &scrollbarWidth);
-      aResult->SizeTo(scrollbarWidth, scrollbarWidth);
+      *aResult = ScrollbarDrawingMac::GetMinimumWidgetSize(aAppearance, aFrame, 1.0f);
       break;
     }
 
-    case StyleAppearance::ScrollbarbuttonUp:
-    case StyleAppearance::ScrollbarbuttonDown:
-    case StyleAppearance::ScrollbarbuttonLeft:
-    case StyleAppearance::ScrollbarbuttonRight: {
-      int32_t themeMetric =
-          IsSmallScrollbar(aFrame) ? kThemeMetricSmallScrollBarWidth : kThemeMetricScrollBarWidth;
-      SInt32 scrollbarWidth = 0;
-      ::GetThemeMetric(themeMetric, &scrollbarWidth);
-
-      // It seems that for both sizes of scrollbar, the buttons are one pixel "longer".
-      if (aAppearance == StyleAppearance::ScrollbarbuttonLeft ||
-          aAppearance == StyleAppearance::ScrollbarbuttonRight)
-        aResult->SizeTo(scrollbarWidth + 1, scrollbarWidth);
-      else
-        aResult->SizeTo(scrollbarWidth, scrollbarWidth + 1);
-
-      *aIsOverridable = false;
-      break;
-    }
     case StyleAppearance::Resizer: {
       HIThemeGrowBoxDrawInfo drawInfo;
       drawInfo.version = 0;
       drawInfo.state = kThemeStateActive;
       drawInfo.kind = kHIThemeGrowBoxKindNormal;
       drawInfo.direction = kThemeGrowRight | kThemeGrowDown;
       drawInfo.size = kHIThemeGrowBoxSizeNormal;
       HIPoint pnt = {0, 0};
--- a/widget/moz.build
+++ b/widget/moz.build
@@ -212,16 +212,17 @@ UNIFIED_SOURCES += [
     "nsPrintSettingsImpl.cpp",
     "nsSoundProxy.cpp",
     "nsTransferable.cpp",
     "nsUserIdleService.cpp",
     "nsXPLookAndFeel.cpp",
     "PuppetBidiKeyboard.cpp",
     "PuppetWidget.cpp",
     "Screen.cpp",
+    'ScrollbarDrawingMac.cpp',
     "SharedWidgetUtils.cpp",
     "TextEventDispatcher.cpp",
     "VsyncDispatcher.cpp",
     "WidgetEventImpl.cpp",
     "WidgetUtils.cpp",
 ]
 
 if CONFIG["OS_ARCH"] == "Linux":
--- a/widget/nsNativeTheme.cpp
+++ b/widget/nsNativeTheme.cpp
@@ -34,18 +34,18 @@
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 nsNativeTheme::nsNativeTheme() : mAnimatedContentTimeout(UINT32_MAX) {}
 
 NS_IMPL_ISUPPORTS(nsNativeTheme, nsITimerCallback, nsINamed)
 
-EventStates nsNativeTheme::GetContentState(nsIFrame* aFrame,
-                                           StyleAppearance aAppearance) {
+/* static */ EventStates nsNativeTheme::GetContentState(
+    nsIFrame* aFrame, StyleAppearance aAppearance) {
   if (!aFrame) return EventStates();
 
   bool isXULCheckboxRadio = (aAppearance == StyleAppearance::Checkbox ||
                              aAppearance == StyleAppearance::Radio) &&
                             aFrame->GetContent()->IsXULElement();
   if (isXULCheckboxRadio) aFrame = aFrame->GetParent();
 
   if (!aFrame->GetContent()) return EventStates();
--- a/widget/nsNativeTheme.h
+++ b/widget/nsNativeTheme.h
@@ -33,33 +33,33 @@ class EventStates;
 class nsNativeTheme : public nsITimerCallback, public nsINamed {
  protected:
   virtual ~nsNativeTheme() = default;
 
   NS_DECL_ISUPPORTS
   NS_DECL_NSITIMERCALLBACK
   NS_DECL_NSINAMED
 
+  nsNativeTheme();
+
+ public:
   enum ScrollbarButtonType {
     eScrollbarButton_UpTop = 0,
     eScrollbarButton_Down = 1 << 0,
     eScrollbarButton_Bottom = 1 << 1
   };
 
   enum TreeSortDirection {
     eTreeSortDirection_Descending,
     eTreeSortDirection_Natural,
     eTreeSortDirection_Ascending
   };
-
-  nsNativeTheme();
-
   // Returns the content state (hover, focus, etc), see EventStateManager.h
-  mozilla::EventStates GetContentState(nsIFrame* aFrame,
-                                       mozilla::StyleAppearance aAppearance);
+  static mozilla::EventStates GetContentState(
+      nsIFrame* aFrame, mozilla::StyleAppearance aAppearance);
 
   // Returns whether the widget is already styled by content
   // Normally called from ThemeSupportsWidget to turn off native theming
   // for elements that are already styled.
   bool IsWidgetStyled(nsPresContext* aPresContext, nsIFrame* aFrame,
                       mozilla::StyleAppearance aAppearance);
 
   // Accessors to widget-specific state information
@@ -83,17 +83,17 @@ class nsNativeTheme : public nsITimerCal
     return GetCheckedOrSelected(aFrame, false);
   }
 
   // radiobutton:
   bool IsSelected(nsIFrame* aFrame) {
     return GetCheckedOrSelected(aFrame, true);
   }
 
-  bool IsFocused(nsIFrame* aFrame) {
+  static bool IsFocused(nsIFrame* aFrame) {
     return CheckBooleanAttr(aFrame, nsGkAtoms::focused);
   }
 
   // scrollbar button:
   int32_t GetScrollbarButtonType(nsIFrame* aFrame);
 
   // tab:
   bool IsSelectedTab(nsIFrame* aFrame) {