Bug 1769547 - Do not MOZ_CRASH() on missing process r=nika
[gecko.git] / widget / ThemeColors.cpp
blob8f449a23e053f962e449c30506baf3d82efe810d
1 /* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "ThemeColors.h"
8 #include "mozilla/RelativeLuminanceUtils.h"
9 #include "mozilla/StaticPrefs_widget.h"
10 #include "ThemeDrawing.h"
11 #include "nsNativeTheme.h"
13 using namespace mozilla::gfx;
15 namespace mozilla::widget {
17 struct ColorPalette {
18 ColorPalette(nscolor aAccent, nscolor aForeground);
20 constexpr ColorPalette(sRGBColor aAccent, sRGBColor aForeground,
21 sRGBColor aLight, sRGBColor aDark, sRGBColor aDarker)
22 : mAccent(aAccent),
23 mForeground(aForeground),
24 mAccentLight(aLight),
25 mAccentDark(aDark),
26 mAccentDarker(aDarker) {}
28 constexpr static ColorPalette Default() {
29 return ColorPalette(
30 sDefaultAccent, sDefaultAccentForeground,
31 sRGBColor::UnusualFromARGB(0x4d008deb), // Luminance: 25.04791%
32 sRGBColor::UnusualFromARGB(0xff0250bb), // Luminance: 9.33808%
33 sRGBColor::UnusualFromARGB(0xff054096) // Luminance: 5.90106%
37 // Ensure accent color is opaque by blending with white. This serves two
38 // purposes: On one hand, it avoids surprises if we overdraw. On the other, it
39 // makes our math below make more sense, as we want to match the browser
40 // style, which has an opaque accent color.
41 static nscolor EnsureOpaque(nscolor aAccent) {
42 if (NS_GET_A(aAccent) != 0xff) {
43 return NS_ComposeColors(NS_RGB(0xff, 0xff, 0xff), aAccent);
45 return aAccent;
48 static nscolor GetLight(nscolor aAccent) {
49 // The luminance from the light color divided by the one of the accent color
50 // in the default palette.
51 constexpr float kLightLuminanceScale = 25.048f / 13.693f;
52 const float lightLuminanceAdjust = ThemeColors::ScaleLuminanceBy(
53 RelativeLuminanceUtils::Compute(aAccent), kLightLuminanceScale);
54 nscolor lightColor =
55 RelativeLuminanceUtils::Adjust(aAccent, lightLuminanceAdjust);
56 return NS_RGBA(NS_GET_R(lightColor), NS_GET_G(lightColor),
57 NS_GET_B(lightColor), 0x4d);
60 static nscolor GetDark(nscolor aAccent) {
61 // Same deal as above (but without the alpha).
62 constexpr float kDarkLuminanceScale = 9.338f / 13.693f;
63 const float darkLuminanceAdjust = ThemeColors::ScaleLuminanceBy(
64 RelativeLuminanceUtils::Compute(aAccent), kDarkLuminanceScale);
65 return RelativeLuminanceUtils::Adjust(aAccent, darkLuminanceAdjust);
68 static nscolor GetDarker(nscolor aAccent) {
69 // Same deal as above.
70 constexpr float kDarkerLuminanceScale = 5.901f / 13.693f;
71 const float darkerLuminanceAdjust = ThemeColors::ScaleLuminanceBy(
72 RelativeLuminanceUtils::Compute(aAccent), kDarkerLuminanceScale);
73 return RelativeLuminanceUtils::Adjust(aAccent, darkerLuminanceAdjust);
76 sRGBColor mAccent;
77 sRGBColor mForeground;
79 // Note that depending on the exact accent color, lighter/darker might really
80 // be inverted.
81 sRGBColor mAccentLight;
82 sRGBColor mAccentDark;
83 sRGBColor mAccentDarker;
86 static nscolor ThemedAccentColor(bool aBackground, ColorScheme aScheme) {
87 MOZ_ASSERT(StaticPrefs::widget_non_native_theme_use_theme_accent());
88 return ColorPalette::EnsureOpaque(LookAndFeel::Color(
89 aBackground ? LookAndFeel::ColorID::MozAccentColor
90 : LookAndFeel::ColorID::MozAccentColorForeground,
91 aScheme, LookAndFeel::UseStandins::No));
94 static ColorPalette sDefaultLightPalette = ColorPalette::Default();
95 static ColorPalette sDefaultDarkPalette = ColorPalette::Default();
97 ColorPalette::ColorPalette(nscolor aAccent, nscolor aForeground) {
98 mAccent = sRGBColor::FromABGR(aAccent);
99 mForeground = sRGBColor::FromABGR(aForeground);
100 mAccentLight = sRGBColor::FromABGR(GetLight(aAccent));
101 mAccentDark = sRGBColor::FromABGR(GetDark(aAccent));
102 mAccentDarker = sRGBColor::FromABGR(GetDarker(aAccent));
105 ThemeAccentColor::ThemeAccentColor(const ComputedStyle& aStyle,
106 ColorScheme aScheme) {
107 const auto& color = aStyle.StyleUI()->mAccentColor;
108 if (color.IsColor()) {
109 mAccentColor.emplace(
110 ColorPalette::EnsureOpaque(color.AsColor().CalcColor(aStyle)));
111 } else {
112 MOZ_ASSERT(color.IsAuto());
113 mDefaultPalette = aScheme == ColorScheme::Light ? &sDefaultLightPalette
114 : &sDefaultDarkPalette;
118 sRGBColor ThemeAccentColor::Get() const {
119 if (!mAccentColor) {
120 return mDefaultPalette->mAccent;
122 return sRGBColor::FromABGR(*mAccentColor);
125 sRGBColor ThemeAccentColor::GetForeground() const {
126 if (!mAccentColor) {
127 return mDefaultPalette->mForeground;
129 return sRGBColor::FromABGR(
130 ThemeColors::ComputeCustomAccentForeground(*mAccentColor));
133 sRGBColor ThemeAccentColor::GetLight() const {
134 if (!mAccentColor) {
135 return mDefaultPalette->mAccentLight;
137 return sRGBColor::FromABGR(ColorPalette::GetLight(*mAccentColor));
140 sRGBColor ThemeAccentColor::GetDark() const {
141 if (!mAccentColor) {
142 return mDefaultPalette->mAccentDark;
144 return sRGBColor::FromABGR(ColorPalette::GetDark(*mAccentColor));
147 sRGBColor ThemeAccentColor::GetDarker() const {
148 if (!mAccentColor) {
149 return mDefaultPalette->mAccentDarker;
151 return sRGBColor::FromABGR(ColorPalette::GetDarker(*mAccentColor));
154 bool ThemeColors::ShouldBeHighContrast(const nsPresContext& aPc) {
155 // We make sure that we're drawing backgrounds, since otherwise layout will
156 // darken our used text colors etc anyways, and that can cause contrast issues
157 // with dark high-contrast themes.
158 return aPc.GetBackgroundColorDraw() &&
159 PreferenceSheet::PrefsFor(*aPc.Document())
160 .NonNativeThemeShouldBeHighContrast();
163 ColorScheme ThemeColors::ColorSchemeForWidget(const nsIFrame* aFrame,
164 StyleAppearance aAppearance,
165 bool aHighContrast) {
166 if (!nsNativeTheme::IsWidgetScrollbarPart(aAppearance)) {
167 return LookAndFeel::ColorSchemeForFrame(aFrame);
169 // Scrollbars are a bit tricky. Their used color-scheme depends on whether the
170 // background they are on is light or dark.
172 // TODO(emilio): This heuristic effectively predates the color-scheme CSS
173 // property. Perhaps we should check whether the style or the document set
174 // `color-scheme` to something that isn't `normal`, and if so go through the
175 // code-path above.
176 if (aHighContrast) {
177 return ColorScheme::Light;
179 if (StaticPrefs::widget_disable_dark_scrollbar()) {
180 return ColorScheme::Light;
182 return nsNativeTheme::IsDarkBackground(const_cast<nsIFrame*>(aFrame))
183 ? ColorScheme::Dark
184 : ColorScheme::Light;
187 /*static*/
188 void ThemeColors::RecomputeAccentColors() {
189 MOZ_RELEASE_ASSERT(NS_IsMainThread());
191 if (!StaticPrefs::widget_non_native_theme_use_theme_accent()) {
192 sDefaultLightPalette = sDefaultDarkPalette = ColorPalette::Default();
193 return;
196 sDefaultLightPalette =
197 ColorPalette(ThemedAccentColor(true, ColorScheme::Light),
198 ThemedAccentColor(false, ColorScheme::Light));
200 sDefaultDarkPalette =
201 ColorPalette(ThemedAccentColor(true, ColorScheme::Dark),
202 ThemedAccentColor(false, ColorScheme::Dark));
205 /*static*/
206 nscolor ThemeColors::ComputeCustomAccentForeground(nscolor aColor) {
207 // Contrast ratio is defined in
208 // https://www.w3.org/TR/WCAG20/#contrast-ratiodef as:
210 // (L1 + 0.05) / (L2 + 0.05)
212 // Where L1 is the lighter color, and L2 is the darker one. So we determine
213 // whether we're dark or light and resolve the equation for the target ratio.
215 // So when lightening:
217 // L1 = k * (L2 + 0.05) - 0.05
219 // And when darkening:
221 // L2 = (L1 + 0.05) / k - 0.05
223 const float luminance = RelativeLuminanceUtils::Compute(aColor);
225 // We generally prefer white unless we can't because the color is really light
226 // and we can't provide reasonable contrast.
227 const float ratioWithWhite = 1.05f / (luminance + 0.05f);
228 const bool canBeWhite =
229 ratioWithWhite >=
230 StaticPrefs::layout_css_accent_color_min_contrast_ratio();
231 if (canBeWhite) {
232 return NS_RGB(0xff, 0xff, 0xff);
234 const float targetRatio =
235 StaticPrefs::layout_css_accent_color_darkening_target_contrast_ratio();
236 const float targetLuminance = (luminance + 0.05f) / targetRatio - 0.05f;
237 return RelativeLuminanceUtils::Adjust(aColor, targetLuminance);
240 nscolor ThemeColors::AdjustUnthemedScrollbarThumbColor(nscolor aFaceColor,
241 EventStates aStates) {
242 // In Windows 10, scrollbar thumb has the following colors:
244 // State | Color | Luminance
245 // -------+----------+----------
246 // Normal | Gray 205 | 61.0%
247 // Hover | Gray 166 | 38.1%
248 // Active | Gray 96 | 11.7%
250 // This function is written based on the ratios between the values.
251 bool isActive = aStates.HasState(NS_EVENT_STATE_ACTIVE);
252 bool isHover = aStates.HasState(NS_EVENT_STATE_HOVER);
253 if (!isActive && !isHover) {
254 return aFaceColor;
256 float luminance = RelativeLuminanceUtils::Compute(aFaceColor);
257 if (isActive) {
258 // 11.7 / 61.0
259 luminance = ScaleLuminanceBy(luminance, 0.192f);
260 } else {
261 // 38.1 / 61.0
262 luminance = ScaleLuminanceBy(luminance, 0.625f);
264 return RelativeLuminanceUtils::Adjust(aFaceColor, luminance);
267 } // namespace mozilla::widget