Bug 1769547 - Do not MOZ_CRASH() on missing process r=nika
[gecko.git] / widget / ScrollbarDrawing.cpp
blobaf131c4850022588d485f00d6addd8d4da9d05b6
1 /* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 2; -*- */
2 /* vim: set sw=2 ts=8 et tw=80 : */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "ScrollbarDrawing.h"
9 #include "mozilla/RelativeLuminanceUtils.h"
10 #include "mozilla/StaticPrefs_widget.h"
11 #include "nsContainerFrame.h"
12 #include "nsDeviceContext.h"
13 #include "nsIFrame.h"
14 #include "nsLayoutUtils.h"
15 #include "nsLookAndFeel.h"
16 #include "nsNativeTheme.h"
18 using namespace mozilla::gfx;
20 namespace mozilla::widget {
22 using mozilla::RelativeLuminanceUtils;
24 /* static */
25 auto ScrollbarDrawing::GetDPIRatioForScrollbarPart(nsPresContext* aPc)
26 -> DPIRatio {
27 if (auto* rootPc = aPc->GetRootPresContext()) {
28 if (nsCOMPtr<nsIWidget> widget = rootPc->GetRootWidget()) {
29 return widget->GetDefaultScale();
32 return DPIRatio(
33 float(AppUnitsPerCSSPixel()) /
34 float(aPc->DeviceContext()->AppUnitsPerDevPixelAtUnitFullZoom()));
37 /*static*/
38 nsIFrame* ScrollbarDrawing::GetParentScrollbarFrame(nsIFrame* aFrame) {
39 // Walk our parents to find a scrollbar frame
40 nsIFrame* scrollbarFrame = aFrame;
41 do {
42 if (scrollbarFrame->IsScrollbarFrame()) {
43 break;
45 } while ((scrollbarFrame = scrollbarFrame->GetParent()));
47 // We return null if we can't find a parent scrollbar frame
48 return scrollbarFrame;
51 /*static*/
52 bool ScrollbarDrawing::IsParentScrollbarRolledOver(nsIFrame* aFrame) {
53 nsIFrame* scrollbarFrame = GetParentScrollbarFrame(aFrame);
54 return aFrame->PresContext()->UseOverlayScrollbars()
55 ? nsNativeTheme::CheckBooleanAttr(scrollbarFrame, nsGkAtoms::hover)
56 : nsNativeTheme::GetContentState(scrollbarFrame,
57 StyleAppearance::None)
58 .HasState(NS_EVENT_STATE_HOVER);
61 /*static*/
62 bool ScrollbarDrawing::IsParentScrollbarHoveredOrActive(nsIFrame* aFrame) {
63 nsIFrame* scrollbarFrame = GetParentScrollbarFrame(aFrame);
64 return scrollbarFrame && scrollbarFrame->GetContent()
65 ->AsElement()
66 ->State()
67 .HasAtLeastOneOfStates(NS_EVENT_STATE_HOVER |
68 NS_EVENT_STATE_ACTIVE);
71 /*static*/
72 bool ScrollbarDrawing::IsScrollbarWidthThin(const ComputedStyle& aStyle) {
73 auto scrollbarWidth = aStyle.StyleUIReset()->ScrollbarWidth();
74 return scrollbarWidth == StyleScrollbarWidth::Thin;
77 /*static*/
78 bool ScrollbarDrawing::IsScrollbarWidthThin(nsIFrame* aFrame) {
79 ComputedStyle* style = nsLayoutUtils::StyleForScrollbar(aFrame);
80 return IsScrollbarWidthThin(*style);
83 auto ScrollbarDrawing::GetScrollbarSizes(nsPresContext* aPresContext,
84 StyleScrollbarWidth aWidth, Overlay)
85 -> ScrollbarSizes {
86 uint32_t h = GetHorizontalScrollbarHeight();
87 uint32_t w = GetVerticalScrollbarWidth();
88 if (aWidth == StyleScrollbarWidth::Thin) {
89 h /= 2;
90 w /= 2;
92 auto dpi = GetDPIRatioForScrollbarPart(aPresContext);
93 return {(CSSCoord(w) * dpi).Rounded(), (CSSCoord(h) * dpi).Rounded()};
96 auto ScrollbarDrawing::GetScrollbarSizes(nsPresContext* aPresContext,
97 nsIFrame* aFrame) -> ScrollbarSizes {
98 auto* style = nsLayoutUtils::StyleForScrollbar(aFrame);
99 auto width = style->StyleUIReset()->ScrollbarWidth();
100 auto overlay =
101 aPresContext->UseOverlayScrollbars() ? Overlay::Yes : Overlay::No;
102 return GetScrollbarSizes(aPresContext, width, overlay);
105 bool ScrollbarDrawing::IsScrollbarTrackOpaque(nsIFrame* aFrame) {
106 auto trackColor = ComputeScrollbarTrackColor(
107 aFrame, *nsLayoutUtils::StyleForScrollbar(aFrame),
108 aFrame->PresContext()->Document()->GetDocumentState(),
109 Colors(aFrame, StyleAppearance::ScrollbartrackVertical));
110 return trackColor.a == 1.0f;
113 sRGBColor ScrollbarDrawing::ComputeScrollbarTrackColor(
114 nsIFrame* aFrame, const ComputedStyle& aStyle,
115 const EventStates& aDocumentState, const Colors& aColors) {
116 if (aColors.HighContrast()) {
117 return aColors.System(StyleSystemColor::Window);
119 const nsStyleUI* ui = aStyle.StyleUI();
120 if (ui->mScrollbarColor.IsColors()) {
121 return sRGBColor::FromABGR(
122 ui->mScrollbarColor.AsColors().track.CalcColor(aStyle));
124 static constexpr sRGBColor sDefaultDarkTrackColor =
125 sRGBColor::FromU8(20, 20, 25, 77);
126 static constexpr sRGBColor sDefaultTrackColor(
127 gfx::sRGBColor::UnusualFromARGB(0xfff0f0f0));
129 auto systemColor =
130 aDocumentState.HasAllStates(NS_DOCUMENT_STATE_WINDOW_INACTIVE)
131 ? StyleSystemColor::ThemedScrollbarInactive
132 : StyleSystemColor::ThemedScrollbar;
133 return aColors.SystemOrElse(systemColor, [&] {
134 return aColors.IsDark() ? sDefaultDarkTrackColor : sDefaultTrackColor;
138 // Don't use the theme color for dark scrollbars if it's not a color (if it's
139 // grey-ish), as that'd either lack enough contrast, or be close to what we'd do
140 // by default anyways.
141 sRGBColor ScrollbarDrawing::ComputeScrollbarThumbColor(
142 nsIFrame* aFrame, const ComputedStyle& aStyle,
143 const EventStates& aElementState, const EventStates& aDocumentState,
144 const Colors& aColors) {
145 const nsStyleUI* ui = aStyle.StyleUI();
146 if (ui->mScrollbarColor.IsColors()) {
147 return sRGBColor::FromABGR(ThemeColors::AdjustUnthemedScrollbarThumbColor(
148 ui->mScrollbarColor.AsColors().thumb.CalcColor(aStyle), aElementState));
151 auto systemColor = [&] {
152 if (aDocumentState.HasState(NS_DOCUMENT_STATE_WINDOW_INACTIVE)) {
153 return StyleSystemColor::ThemedScrollbarThumbInactive;
155 if (aElementState.HasState(NS_EVENT_STATE_ACTIVE)) {
156 if (aColors.HighContrast()) {
157 return StyleSystemColor::Selecteditem;
159 return StyleSystemColor::ThemedScrollbarThumbActive;
161 if (aElementState.HasState(NS_EVENT_STATE_HOVER)) {
162 if (aColors.HighContrast()) {
163 return StyleSystemColor::Selecteditem;
165 return StyleSystemColor::ThemedScrollbarThumbHover;
167 if (aColors.HighContrast()) {
168 return StyleSystemColor::Windowtext;
170 return StyleSystemColor::ThemedScrollbarThumb;
171 }();
173 return aColors.SystemOrElse(systemColor, [&] {
174 const nscolor unthemedColor = aColors.IsDark() ? NS_RGBA(249, 249, 250, 102)
175 : NS_RGB(0xcd, 0xcd, 0xcd);
177 return sRGBColor::FromABGR(ThemeColors::AdjustUnthemedScrollbarThumbColor(
178 unthemedColor, aElementState));
182 template <typename PaintBackendData>
183 bool ScrollbarDrawing::DoPaintDefaultScrollbar(
184 PaintBackendData& aPaintData, const LayoutDeviceRect& aRect,
185 ScrollbarKind aScrollbarKind, nsIFrame* aFrame, const ComputedStyle& aStyle,
186 const EventStates& aElementState, const EventStates& aDocumentState,
187 const Colors& aColors, const DPIRatio& aDpiRatio) {
188 const bool overlay = aFrame->PresContext()->UseOverlayScrollbars();
189 if (overlay && !aElementState.HasAtLeastOneOfStates(NS_EVENT_STATE_HOVER |
190 NS_EVENT_STATE_ACTIVE)) {
191 return true;
193 const auto color =
194 ComputeScrollbarTrackColor(aFrame, aStyle, aDocumentState, aColors);
195 if (overlay && mKind == Kind::Win11) {
196 LayoutDeviceCoord radius =
197 (aScrollbarKind == ScrollbarKind::Horizontal ? aRect.height
198 : aRect.width) /
199 2.0f;
200 ThemeDrawing::PaintRoundedRectWithRadius(aPaintData, aRect, color,
201 sRGBColor(), 0, radius / aDpiRatio,
202 aDpiRatio);
203 } else {
204 ThemeDrawing::FillRect(aPaintData, aRect, color);
206 return true;
209 bool ScrollbarDrawing::PaintScrollbar(
210 DrawTarget& aDrawTarget, const LayoutDeviceRect& aRect,
211 ScrollbarKind aScrollbarKind, nsIFrame* aFrame, const ComputedStyle& aStyle,
212 const EventStates& aElementState, const EventStates& aDocumentState,
213 const Colors& aColors, const DPIRatio& aDpiRatio) {
214 return DoPaintDefaultScrollbar(aDrawTarget, aRect, aScrollbarKind, aFrame,
215 aStyle, aElementState, aDocumentState, aColors,
216 aDpiRatio);
219 bool ScrollbarDrawing::PaintScrollbar(
220 WebRenderBackendData& aWrData, const LayoutDeviceRect& aRect,
221 ScrollbarKind aScrollbarKind, nsIFrame* aFrame, const ComputedStyle& aStyle,
222 const EventStates& aElementState, const EventStates& aDocumentState,
223 const Colors& aColors, const DPIRatio& aDpiRatio) {
224 return DoPaintDefaultScrollbar(aWrData, aRect, aScrollbarKind, aFrame, aStyle,
225 aElementState, aDocumentState, aColors,
226 aDpiRatio);
229 template <typename PaintBackendData>
230 bool ScrollbarDrawing::DoPaintDefaultScrollCorner(
231 PaintBackendData& aPaintData, const LayoutDeviceRect& aRect,
232 ScrollbarKind aScrollbarKind, nsIFrame* aFrame, const ComputedStyle& aStyle,
233 const EventStates& aDocumentState, const Colors& aColors,
234 const DPIRatio& aDpiRatio) {
235 auto scrollbarColor =
236 ComputeScrollbarTrackColor(aFrame, aStyle, aDocumentState, aColors);
237 ThemeDrawing::FillRect(aPaintData, aRect, scrollbarColor);
238 return true;
241 bool ScrollbarDrawing::PaintScrollCorner(
242 DrawTarget& aDrawTarget, const LayoutDeviceRect& aRect,
243 ScrollbarKind aScrollbarKind, nsIFrame* aFrame, const ComputedStyle& aStyle,
244 const EventStates& aDocumentState, const Colors& aColors,
245 const DPIRatio& aDpiRatio) {
246 return DoPaintDefaultScrollCorner(aDrawTarget, aRect, aScrollbarKind, aFrame,
247 aStyle, aDocumentState, aColors, aDpiRatio);
250 bool ScrollbarDrawing::PaintScrollCorner(
251 WebRenderBackendData& aWrData, const LayoutDeviceRect& aRect,
252 ScrollbarKind aScrollbarKind, nsIFrame* aFrame, const ComputedStyle& aStyle,
253 const EventStates& aDocumentState, const Colors& aColors,
254 const DPIRatio& aDpiRatio) {
255 return DoPaintDefaultScrollCorner(aWrData, aRect, aScrollbarKind, aFrame,
256 aStyle, aDocumentState, aColors, aDpiRatio);
259 nscolor ScrollbarDrawing::GetScrollbarButtonColor(nscolor aTrackColor,
260 EventStates aStates) {
261 // See numbers in GetScrollbarArrowColor.
262 // This function is written based on ratios between values listed there.
264 bool isActive = aStates.HasState(NS_EVENT_STATE_ACTIVE);
265 bool isHover = aStates.HasState(NS_EVENT_STATE_HOVER);
266 if (!isActive && !isHover) {
267 return aTrackColor;
269 float luminance = RelativeLuminanceUtils::Compute(aTrackColor);
270 if (isActive) {
271 if (luminance >= 0.18f) {
272 luminance *= 0.134f;
273 } else {
274 luminance /= 0.134f;
275 luminance = std::min(luminance, 1.0f);
277 } else {
278 if (luminance >= 0.18f) {
279 luminance *= 0.805f;
280 } else {
281 luminance /= 0.805f;
284 return RelativeLuminanceUtils::Adjust(aTrackColor, luminance);
287 Maybe<nscolor> ScrollbarDrawing::GetScrollbarArrowColor(nscolor aButtonColor) {
288 // In Windows 10 scrollbar, there are several gray colors used:
290 // State | Background (lum) | Arrow | Contrast
291 // -------+------------------+---------+---------
292 // Normal | Gray 240 (87.1%) | Gray 96 | 5.5
293 // Hover | Gray 218 (70.1%) | Black | 15.0
294 // Active | Gray 96 (11.7%) | White | 6.3
296 // Contrast value is computed based on the definition in
297 // https://www.w3.org/TR/WCAG20/#contrast-ratiodef
299 // This function is written based on these values.
301 if (NS_GET_A(aButtonColor) == 0) {
302 // If the button color is transparent, because of e.g.
303 // scrollbar-color: <something> transparent, then use
304 // the thumb color, which is expected to have enough
305 // contrast.
306 return Nothing();
309 float luminance = RelativeLuminanceUtils::Compute(aButtonColor);
310 // Color with luminance larger than 0.72 has contrast ratio over 4.6
311 // to color with luminance of gray 96, so this value is chosen for
312 // this range. It is the luminance of gray 221.
313 if (luminance >= 0.72) {
314 // ComputeRelativeLuminanceFromComponents(96). That function cannot
315 // be constexpr because of std::pow.
316 const float GRAY96_LUMINANCE = 0.117f;
317 return Some(RelativeLuminanceUtils::Adjust(aButtonColor, GRAY96_LUMINANCE));
319 // The contrast ratio of a color to black equals that to white when its
320 // luminance is around 0.18, with a contrast ratio ~4.6 to both sides,
321 // thus the value below. It's the lumanince of gray 118.
323 // TODO(emilio): Maybe the button alpha is not the best thing to use here and
324 // we should use the thumb alpha? It seems weird that the color of the arrow
325 // depends on the opacity of the scrollbar thumb...
326 if (luminance >= 0.18) {
327 return Some(NS_RGBA(0, 0, 0, NS_GET_A(aButtonColor)));
329 return Some(NS_RGBA(255, 255, 255, NS_GET_A(aButtonColor)));
332 std::pair<sRGBColor, sRGBColor> ScrollbarDrawing::ComputeScrollbarButtonColors(
333 nsIFrame* aFrame, StyleAppearance aAppearance, const ComputedStyle& aStyle,
334 const EventStates& aElementState, const EventStates& aDocumentState,
335 const Colors& aColors) {
336 if (aColors.HighContrast()) {
337 if (aElementState.HasAtLeastOneOfStates(NS_EVENT_STATE_ACTIVE |
338 NS_EVENT_STATE_HOVER)) {
339 return aColors.SystemPair(StyleSystemColor::Selecteditem,
340 StyleSystemColor::Buttonface);
342 return aColors.SystemPair(StyleSystemColor::Window,
343 StyleSystemColor::Windowtext);
346 auto trackColor =
347 ComputeScrollbarTrackColor(aFrame, aStyle, aDocumentState, aColors);
348 nscolor buttonColor =
349 GetScrollbarButtonColor(trackColor.ToABGR(), aElementState);
350 auto arrowColor =
351 GetScrollbarArrowColor(buttonColor)
352 .map(sRGBColor::FromABGR)
353 .valueOrFrom([&] {
354 return ComputeScrollbarThumbColor(aFrame, aStyle, aElementState,
355 aDocumentState, aColors);
357 return {sRGBColor::FromABGR(buttonColor), arrowColor};
360 bool ScrollbarDrawing::PaintScrollbarButton(
361 DrawTarget& aDrawTarget, StyleAppearance aAppearance,
362 const LayoutDeviceRect& aRect, ScrollbarKind aScrollbarKind,
363 nsIFrame* aFrame, const ComputedStyle& aStyle,
364 const EventStates& aElementState, const EventStates& aDocumentState,
365 const Colors& aColors, const DPIRatio&) {
366 auto [buttonColor, arrowColor] = ComputeScrollbarButtonColors(
367 aFrame, aAppearance, aStyle, aElementState, aDocumentState, aColors);
368 aDrawTarget.FillRect(aRect.ToUnknownRect(),
369 ColorPattern(ToDeviceColor(buttonColor)));
371 // Start with Up arrow.
372 float arrowPolygonX[] = {-4.0f, 0.0f, 4.0f, 4.0f, 0.0f, -4.0f};
373 float arrowPolygonY[] = {0.0f, -4.0f, 0.0f, 3.0f, -1.0f, 3.0f};
375 const float kPolygonSize = 17;
377 const int32_t arrowNumPoints = ArrayLength(arrowPolygonX);
378 switch (aAppearance) {
379 case StyleAppearance::ScrollbarbuttonUp:
380 break;
381 case StyleAppearance::ScrollbarbuttonDown:
382 for (int32_t i = 0; i < arrowNumPoints; i++) {
383 arrowPolygonY[i] *= -1;
385 break;
386 case StyleAppearance::ScrollbarbuttonLeft:
387 for (int32_t i = 0; i < arrowNumPoints; i++) {
388 float temp = arrowPolygonX[i];
389 arrowPolygonX[i] = arrowPolygonY[i];
390 arrowPolygonY[i] = temp;
392 break;
393 case StyleAppearance::ScrollbarbuttonRight:
394 for (int32_t i = 0; i < arrowNumPoints; i++) {
395 float temp = arrowPolygonX[i];
396 arrowPolygonX[i] = arrowPolygonY[i] * -1;
397 arrowPolygonY[i] = temp;
399 break;
400 default:
401 return false;
403 ThemeDrawing::PaintArrow(aDrawTarget, aRect, arrowPolygonX, arrowPolygonY,
404 kPolygonSize, arrowNumPoints, arrowColor);
405 return true;
408 } // namespace mozilla::widget