no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD CLOSED TREE
[gecko.git] / widget / gtk / nsLookAndFeel.cpp
blob2dad456cf2740247883eb09ded176f3213c1127b
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:expandtab:shiftwidth=2:tabstop=2:
3 */
4 /* This Source Code Form is subject to the terms of the Mozilla Public
5 * License, v. 2.0. If a copy of the MPL was not distributed with this
6 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 // for strtod()
9 #include <stdlib.h>
10 #include <dlfcn.h>
12 #include "nsLookAndFeel.h"
14 #include <gtk/gtk.h>
15 #include <gdk/gdk.h>
17 #include <pango/pango.h>
18 #include <pango/pango-fontmap.h>
19 #include <fontconfig/fontconfig.h>
21 #include "GRefPtr.h"
22 #include "GUniquePtr.h"
23 #include "nsGtkUtils.h"
24 #include "gfxPlatformGtk.h"
25 #include "mozilla/FontPropertyTypes.h"
26 #include "mozilla/Preferences.h"
27 #include "mozilla/RelativeLuminanceUtils.h"
28 #include "mozilla/StaticPrefs_layout.h"
29 #include "mozilla/StaticPrefs_widget.h"
30 #include "mozilla/StaticPrefs_browser.h"
31 #include "mozilla/AutoRestore.h"
32 #include "mozilla/Telemetry.h"
33 #include "mozilla/ScopeExit.h"
34 #include "mozilla/WidgetUtilsGtk.h"
35 #include "ScreenHelperGTK.h"
36 #include "ScrollbarDrawing.h"
38 #include "gtkdrawing.h"
39 #include "nsString.h"
40 #include "nsStyleConsts.h"
41 #include "gfxFontConstants.h"
42 #include "WidgetUtils.h"
43 #include "nsWindow.h"
45 #include "mozilla/gfx/2D.h"
47 #include <cairo-gobject.h>
48 #include <dlfcn.h>
49 #include "WidgetStyleCache.h"
50 #include "prenv.h"
51 #include "nsCSSColorUtils.h"
52 #include "mozilla/Preferences.h"
54 using namespace mozilla;
55 using namespace mozilla::widget;
57 #ifdef MOZ_LOGGING
58 # include "mozilla/Logging.h"
59 # include "nsTArray.h"
60 # include "Units.h"
61 static LazyLogModule gLnfLog("LookAndFeel");
62 # define LOGLNF(...) MOZ_LOG(gLnfLog, LogLevel::Debug, (__VA_ARGS__))
63 # define LOGLNF_ENABLED() MOZ_LOG_TEST(gLnfLog, LogLevel::Debug)
64 #else
65 # define LOGLNF(args)
66 # define LOGLNF_ENABLED() false
67 #endif /* MOZ_LOGGING */
69 #define GDK_COLOR_TO_NS_RGB(c) \
70 ((nscolor)NS_RGB(c.red >> 8, c.green >> 8, c.blue >> 8))
71 #define GDK_RGBA_TO_NS_RGBA(c) \
72 ((nscolor)NS_RGBA((int)((c).red * 255), (int)((c).green * 255), \
73 (int)((c).blue * 255), (int)((c).alpha * 255)))
75 static bool sIgnoreChangedSettings = false;
77 static void OnSettingsChange() {
78 if (sIgnoreChangedSettings) {
79 return;
81 // TODO: We could be more granular here, but for now assume everything
82 // changed.
83 LookAndFeel::NotifyChangedAllWindows(widget::ThemeChangeKind::StyleAndLayout);
84 widget::IMContextWrapper::OnThemeChanged();
87 static void settings_changed_cb(GtkSettings*, GParamSpec*, void*) {
88 OnSettingsChange();
91 static bool sCSDAvailable;
93 static nsCString GVariantToString(GVariant* aVariant) {
94 nsCString ret;
95 gchar* s = g_variant_print(aVariant, TRUE);
96 if (s) {
97 ret.Assign(s);
98 g_free(s);
100 return ret;
103 static nsDependentCString GVariantGetString(GVariant* aVariant) {
104 gsize len = 0;
105 const gchar* v = g_variant_get_string(aVariant, &len);
106 return nsDependentCString(v, len);
109 static void settings_changed_signal_cb(GDBusProxy* proxy, gchar* sender_name,
110 gchar* signal_name, GVariant* parameters,
111 gpointer user_data) {
112 LOGLNF("Settings Change sender=%s signal=%s params=%s\n", sender_name,
113 signal_name, GVariantToString(parameters).get());
114 if (strcmp(signal_name, "SettingChanged")) {
115 NS_WARNING("Unknown change signal for settings");
116 return;
118 RefPtr<GVariant> ns = dont_AddRef(g_variant_get_child_value(parameters, 0));
119 RefPtr<GVariant> key = dont_AddRef(g_variant_get_child_value(parameters, 1));
120 // Third parameter is the value, but we don't care about it.
121 if (!ns || !key || !g_variant_is_of_type(ns, G_VARIANT_TYPE_STRING) ||
122 !g_variant_is_of_type(key, G_VARIANT_TYPE_STRING)) {
123 MOZ_ASSERT(false, "Unexpected setting change signal parameters");
124 return;
127 auto* lnf = static_cast<nsLookAndFeel*>(user_data);
129 auto nsStr = GVariantGetString(ns);
130 auto keyStr = GVariantGetString(key);
131 if (nsStr.Equals("org.freedesktop.appearance"_ns) &&
132 keyStr.Equals("color-scheme"_ns)) {
133 lnf->OnColorSchemeSettingChanged();
137 void nsLookAndFeel::WatchDBus() {
138 GUniquePtr<GError> error;
139 mDBusSettingsProxy = dont_AddRef(g_dbus_proxy_new_for_bus_sync(
140 G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE, nullptr,
141 "org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop",
142 "org.freedesktop.portal.Settings", nullptr, getter_Transfers(error)));
143 if (mDBusSettingsProxy) {
144 g_signal_connect(mDBusSettingsProxy, "g-signal",
145 G_CALLBACK(settings_changed_signal_cb), this);
146 } else {
147 LOGLNF("Can't create DBus proxy for settings: %s\n", error->message);
148 return;
151 // DBus interface was started after L&F init so we need to load
152 // our settings from DBus explicitly.
153 if (!sIgnoreChangedSettings) {
154 OnColorSchemeSettingChanged();
158 void nsLookAndFeel::UnwatchDBus() {
159 if (mDBusSettingsProxy) {
160 g_signal_handlers_disconnect_by_func(
161 mDBusSettingsProxy, FuncToGpointer(settings_changed_signal_cb), this);
162 mDBusSettingsProxy = nullptr;
166 nsLookAndFeel::nsLookAndFeel() {
167 static constexpr nsLiteralCString kObservedSettings[] = {
168 // Affects system font sizes.
169 "notify::gtk-xft-dpi"_ns,
170 // Affects mSystemTheme and mAltTheme as expected.
171 "notify::gtk-theme-name"_ns,
172 // System fonts?
173 "notify::gtk-font-name"_ns,
174 // prefers-reduced-motion
175 "notify::gtk-enable-animations"_ns,
176 // CSD media queries, etc.
177 "notify::gtk-decoration-layout"_ns,
178 // Text resolution affects system font and widget sizes.
179 "notify::resolution"_ns,
180 // These three Affect mCaretBlinkTime
181 "notify::gtk-cursor-blink"_ns,
182 "notify::gtk-cursor-blink-time"_ns,
183 "notify::gtk-cursor-blink-timeout"_ns,
184 // Affects SelectTextfieldsOnKeyFocus
185 "notify::gtk-entry-select-on-focus"_ns,
186 // Affects ScrollToClick
187 "notify::gtk-primary-button-warps-slider"_ns,
188 // Affects SubmenuDelay
189 "notify::gtk-menu-popup-delay"_ns,
190 // Affects DragThresholdX/Y
191 "notify::gtk-dnd-drag-threshold"_ns,
194 GtkSettings* settings = gtk_settings_get_default();
195 for (const auto& setting : kObservedSettings) {
196 g_signal_connect_after(settings, setting.get(),
197 G_CALLBACK(settings_changed_cb), nullptr);
200 sCSDAvailable =
201 nsWindow::GetSystemGtkWindowDecoration() != nsWindow::GTK_DECORATION_NONE;
203 if (ShouldUsePortal(PortalKind::Settings)) {
204 mDBusID = g_bus_watch_name(
205 G_BUS_TYPE_SESSION, "org.freedesktop.portal.Desktop",
206 G_BUS_NAME_WATCHER_FLAGS_AUTO_START,
207 [](GDBusConnection*, const gchar*, const gchar*,
208 gpointer data) -> void {
209 auto* lnf = static_cast<nsLookAndFeel*>(data);
210 lnf->WatchDBus();
212 [](GDBusConnection*, const gchar*, gpointer data) -> void {
213 auto* lnf = static_cast<nsLookAndFeel*>(data);
214 lnf->UnwatchDBus();
216 this, nullptr);
220 nsLookAndFeel::~nsLookAndFeel() {
221 ClearRoundedCornerProvider();
222 if (mDBusID) {
223 g_bus_unwatch_name(mDBusID);
224 mDBusID = 0;
226 UnwatchDBus();
227 g_signal_handlers_disconnect_by_func(
228 gtk_settings_get_default(), FuncToGpointer(settings_changed_cb), nullptr);
231 #if 0
232 static void DumpStyleContext(GtkStyleContext* aStyle) {
233 static auto sGtkStyleContextToString =
234 reinterpret_cast<char* (*)(GtkStyleContext*, gint)>(
235 dlsym(RTLD_DEFAULT, "gtk_style_context_to_string"));
236 char* str = sGtkStyleContextToString(aStyle, ~0);
237 printf("%s\n", str);
238 g_free(str);
239 str = gtk_widget_path_to_string(gtk_style_context_get_path(aStyle));
240 printf("%s\n", str);
241 g_free(str);
243 #endif
245 // Modifies color |*aDest| as if a pattern of color |aSource| was painted with
246 // CAIRO_OPERATOR_OVER to a surface with color |*aDest|.
247 static void ApplyColorOver(const GdkRGBA& aSource, GdkRGBA* aDest) {
248 gdouble sourceCoef = aSource.alpha;
249 gdouble destCoef = aDest->alpha * (1.0 - sourceCoef);
250 gdouble resultAlpha = sourceCoef + destCoef;
251 if (resultAlpha != 0.0) { // don't divide by zero
252 destCoef /= resultAlpha;
253 sourceCoef /= resultAlpha;
254 aDest->red = sourceCoef * aSource.red + destCoef * aDest->red;
255 aDest->green = sourceCoef * aSource.green + destCoef * aDest->green;
256 aDest->blue = sourceCoef * aSource.blue + destCoef * aDest->blue;
257 aDest->alpha = resultAlpha;
261 static void GetLightAndDarkness(const GdkRGBA& aColor, double* aLightness,
262 double* aDarkness) {
263 double sum = aColor.red + aColor.green + aColor.blue;
264 *aLightness = sum * aColor.alpha;
265 *aDarkness = (3.0 - sum) * aColor.alpha;
268 static bool GetGradientColors(const GValue* aValue, GdkRGBA* aLightColor,
269 GdkRGBA* aDarkColor) {
270 if (!G_TYPE_CHECK_VALUE_TYPE(aValue, CAIRO_GOBJECT_TYPE_PATTERN)) {
271 return false;
274 auto pattern = static_cast<cairo_pattern_t*>(g_value_get_boxed(aValue));
275 if (!pattern) {
276 return false;
279 // Just picking the lightest and darkest colors as simple samples rather
280 // than trying to blend, which could get messy if there are many stops.
281 if (CAIRO_STATUS_SUCCESS !=
282 cairo_pattern_get_color_stop_rgba(pattern, 0, nullptr, &aDarkColor->red,
283 &aDarkColor->green, &aDarkColor->blue,
284 &aDarkColor->alpha)) {
285 return false;
288 double maxLightness, maxDarkness;
289 GetLightAndDarkness(*aDarkColor, &maxLightness, &maxDarkness);
290 *aLightColor = *aDarkColor;
292 GdkRGBA stop;
293 for (int index = 1;
294 CAIRO_STATUS_SUCCESS ==
295 cairo_pattern_get_color_stop_rgba(pattern, index, nullptr, &stop.red,
296 &stop.green, &stop.blue, &stop.alpha);
297 ++index) {
298 double lightness, darkness;
299 GetLightAndDarkness(stop, &lightness, &darkness);
300 if (lightness > maxLightness) {
301 maxLightness = lightness;
302 *aLightColor = stop;
304 if (darkness > maxDarkness) {
305 maxDarkness = darkness;
306 *aDarkColor = stop;
310 return true;
313 static bool GetColorFromImagePattern(const GValue* aValue, nscolor* aColor) {
314 if (!G_TYPE_CHECK_VALUE_TYPE(aValue, CAIRO_GOBJECT_TYPE_PATTERN)) {
315 return false;
318 auto* pattern = static_cast<cairo_pattern_t*>(g_value_get_boxed(aValue));
319 if (!pattern) {
320 return false;
323 cairo_surface_t* surface;
324 if (cairo_pattern_get_surface(pattern, &surface) != CAIRO_STATUS_SUCCESS) {
325 return false;
328 cairo_format_t format = cairo_image_surface_get_format(surface);
329 if (format == CAIRO_FORMAT_INVALID) {
330 return false;
332 int width = cairo_image_surface_get_width(surface);
333 int height = cairo_image_surface_get_height(surface);
334 int stride = cairo_image_surface_get_stride(surface);
335 if (!width || !height) {
336 return false;
339 // Guesstimate the central pixel would have a sensible color.
340 int x = width / 2;
341 int y = height / 2;
343 unsigned char* data = cairo_image_surface_get_data(surface);
344 switch (format) {
345 // Most (all?) GTK images / patterns / etc use ARGB32.
346 case CAIRO_FORMAT_ARGB32: {
347 size_t offset = x * 4 + y * stride;
348 uint32_t* pixel = reinterpret_cast<uint32_t*>(data + offset);
349 *aColor = gfx::sRGBColor::UnusualFromARGB(*pixel).ToABGR();
350 return true;
352 default:
353 break;
356 return false;
359 static bool GetUnicoBorderGradientColors(GtkStyleContext* aContext,
360 GdkRGBA* aLightColor,
361 GdkRGBA* aDarkColor) {
362 // Ubuntu 12.04 has GTK engine Unico-1.0.2, which overrides render_frame,
363 // providing its own border code. Ubuntu 14.04 has
364 // Unico-1.0.3+14.04.20140109, which does not override render_frame, and
365 // so does not need special attention. The earlier Unico can be detected
366 // by the -unico-border-gradient style property it registers.
367 // gtk_style_properties_lookup_property() is checked first to avoid the
368 // warning from gtk_style_context_get_property() when the property does
369 // not exist. (gtk_render_frame() of GTK+ 3.16 no longer uses the
370 // engine.)
371 const char* propertyName = "-unico-border-gradient";
372 if (!gtk_style_properties_lookup_property(propertyName, nullptr, nullptr))
373 return false;
375 // -unico-border-gradient is used only when the CSS node's engine is Unico.
376 GtkThemingEngine* engine;
377 GtkStateFlags state = gtk_style_context_get_state(aContext);
378 gtk_style_context_get(aContext, state, "engine", &engine, nullptr);
379 if (strcmp(g_type_name(G_TYPE_FROM_INSTANCE(engine)), "UnicoEngine") != 0)
380 return false;
382 // draw_border() of Unico engine uses -unico-border-gradient
383 // in preference to border-color.
384 GValue value = G_VALUE_INIT;
385 gtk_style_context_get_property(aContext, propertyName, state, &value);
387 bool result = GetGradientColors(&value, aLightColor, aDarkColor);
389 g_value_unset(&value);
390 return result;
393 // Sets |aLightColor| and |aDarkColor| to colors from |aContext|. Returns
394 // true if |aContext| uses these colors to render a visible border.
395 // If returning false, then the colors returned are a fallback from the
396 // border-color value even though |aContext| does not use these colors to
397 // render a border.
398 static bool GetBorderColors(GtkStyleContext* aContext, GdkRGBA* aLightColor,
399 GdkRGBA* aDarkColor) {
400 // Determine whether the border on this style context is visible.
401 GtkStateFlags state = gtk_style_context_get_state(aContext);
402 GtkBorderStyle borderStyle;
403 gtk_style_context_get(aContext, state, GTK_STYLE_PROPERTY_BORDER_STYLE,
404 &borderStyle, nullptr);
405 bool visible = borderStyle != GTK_BORDER_STYLE_NONE &&
406 borderStyle != GTK_BORDER_STYLE_HIDDEN;
407 if (visible) {
408 // GTK has an initial value of zero for border-widths, and so themes
409 // need to explicitly set border-widths to make borders visible.
410 GtkBorder border;
411 gtk_style_context_get_border(aContext, state, &border);
412 visible = border.top != 0 || border.right != 0 || border.bottom != 0 ||
413 border.left != 0;
416 if (visible &&
417 GetUnicoBorderGradientColors(aContext, aLightColor, aDarkColor))
418 return true;
420 // The initial value for the border-color is the foreground color, and so
421 // this will usually return a color distinct from the background even if
422 // there is no visible border detected.
423 gtk_style_context_get_border_color(aContext, state, aDarkColor);
424 // TODO GTK3 - update aLightColor
425 // for GTK_BORDER_STYLE_INSET/OUTSET/GROVE/RIDGE border styles.
426 // https://bugzilla.mozilla.org/show_bug.cgi?id=978172#c25
427 *aLightColor = *aDarkColor;
428 return visible;
431 static bool GetBorderColors(GtkStyleContext* aContext, nscolor* aLightColor,
432 nscolor* aDarkColor) {
433 GdkRGBA lightColor, darkColor;
434 bool ret = GetBorderColors(aContext, &lightColor, &darkColor);
435 *aLightColor = GDK_RGBA_TO_NS_RGBA(lightColor);
436 *aDarkColor = GDK_RGBA_TO_NS_RGBA(darkColor);
437 return ret;
440 // Finds ideal cell highlight colors used for unfocused+selected cells distinct
441 // from both Highlight, used as focused+selected background, and the listbox
442 // background which is assumed to be similar to -moz-field
443 void nsLookAndFeel::PerThemeData::InitCellHighlightColors() {
444 int32_t minLuminosityDifference = NS_SUFFICIENT_LUMINOSITY_DIFFERENCE_BG;
445 int32_t backLuminosityDifference =
446 NS_LUMINOSITY_DIFFERENCE(mWindow.mBg, mField.mBg);
447 if (backLuminosityDifference >= minLuminosityDifference) {
448 mCellHighlight = mWindow;
449 return;
452 uint16_t hue, sat, luminance;
453 uint8_t alpha;
454 mCellHighlight = mField;
456 NS_RGB2HSV(mCellHighlight.mBg, hue, sat, luminance, alpha);
458 uint16_t step = 30;
459 // Lighten the color if the color is very dark
460 if (luminance <= step) {
461 luminance += step;
463 // Darken it if it is very light
464 else if (luminance >= 255 - step) {
465 luminance -= step;
467 // Otherwise, compute what works best depending on the text luminance.
468 else {
469 uint16_t textHue, textSat, textLuminance;
470 uint8_t textAlpha;
471 NS_RGB2HSV(mCellHighlight.mFg, textHue, textSat, textLuminance, textAlpha);
472 // Text is darker than background, use a lighter shade
473 if (textLuminance < luminance) {
474 luminance += step;
476 // Otherwise, use a darker shade
477 else {
478 luminance -= step;
481 NS_HSV2RGB(mCellHighlight.mBg, hue, sat, luminance, alpha);
484 void nsLookAndFeel::NativeInit() { EnsureInit(); }
486 void nsLookAndFeel::RefreshImpl() {
487 mInitialized = false;
488 moz_gtk_refresh();
490 nsXPLookAndFeel::RefreshImpl();
493 nsresult nsLookAndFeel::NativeGetColor(ColorID aID, ColorScheme aScheme,
494 nscolor& aColor) {
495 EnsureInit();
497 const auto& theme =
498 aScheme == ColorScheme::Light ? LightTheme() : DarkTheme();
499 return theme.GetColor(aID, aColor);
502 static bool ShouldUseColorForActiveDarkScrollbarThumb(nscolor aColor) {
503 auto IsDifferentEnough = [](int32_t aChannel, int32_t aOtherChannel) {
504 return std::abs(aChannel - aOtherChannel) > 10;
506 return IsDifferentEnough(NS_GET_R(aColor), NS_GET_G(aColor)) ||
507 IsDifferentEnough(NS_GET_R(aColor), NS_GET_B(aColor));
510 static bool ShouldUseThemedScrollbarColor(StyleSystemColor aID, nscolor aColor,
511 bool aIsDark) {
512 if (!aIsDark) {
513 return true;
515 if (StaticPrefs::widget_non_native_theme_scrollbar_dark_themed()) {
516 return true;
518 return aID == StyleSystemColor::ThemedScrollbarThumbActive &&
519 StaticPrefs::widget_non_native_theme_scrollbar_active_always_themed();
522 nsresult nsLookAndFeel::PerThemeData::GetColor(ColorID aID,
523 nscolor& aColor) const {
524 nsresult res = NS_OK;
526 switch (aID) {
527 // These colors don't seem to be used for anything anymore in Mozilla
528 // The CSS2 colors below are used.
529 case ColorID::Appworkspace: // MDI background color
530 case ColorID::Background: // desktop background
531 case ColorID::Window:
532 case ColorID::Windowframe:
533 case ColorID::MozDialog:
534 case ColorID::MozCombobox:
535 aColor = mWindow.mBg;
536 break;
537 case ColorID::Windowtext:
538 case ColorID::MozDialogtext:
539 aColor = mWindow.mFg;
540 break;
541 case ColorID::IMESelectedRawTextBackground:
542 case ColorID::IMESelectedConvertedTextBackground:
543 case ColorID::Highlight: // preference selected item,
544 aColor = mSelectedText.mBg;
545 break;
546 case ColorID::Highlighttext:
547 if (NS_GET_A(mSelectedText.mBg) < 155) {
548 aColor = NS_SAME_AS_FOREGROUND_COLOR;
549 break;
551 [[fallthrough]];
552 case ColorID::IMESelectedRawTextForeground:
553 case ColorID::IMESelectedConvertedTextForeground:
554 aColor = mSelectedText.mFg;
555 break;
556 case ColorID::Selecteditem:
557 aColor = mSelectedItem.mBg;
558 break;
559 case ColorID::Selecteditemtext:
560 aColor = mSelectedItem.mFg;
561 break;
562 case ColorID::Accentcolor:
563 aColor = mAccent.mBg;
564 break;
565 case ColorID::Accentcolortext:
566 aColor = mAccent.mFg;
567 break;
568 case ColorID::MozCellhighlight:
569 aColor = mCellHighlight.mBg;
570 break;
571 case ColorID::MozCellhighlighttext:
572 aColor = mCellHighlight.mFg;
573 break;
574 case ColorID::IMERawInputBackground:
575 case ColorID::IMEConvertedTextBackground:
576 aColor = NS_TRANSPARENT;
577 break;
578 case ColorID::IMERawInputForeground:
579 case ColorID::IMEConvertedTextForeground:
580 aColor = NS_SAME_AS_FOREGROUND_COLOR;
581 break;
582 case ColorID::IMERawInputUnderline:
583 case ColorID::IMEConvertedTextUnderline:
584 aColor = NS_SAME_AS_FOREGROUND_COLOR;
585 break;
586 case ColorID::IMESelectedRawTextUnderline:
587 case ColorID::IMESelectedConvertedTextUnderline:
588 aColor = NS_TRANSPARENT;
589 break;
590 case ColorID::Scrollbar:
591 aColor = mThemedScrollbar;
592 break;
593 case ColorID::ThemedScrollbar:
594 aColor = mThemedScrollbar;
595 if (!ShouldUseThemedScrollbarColor(aID, aColor, mIsDark)) {
596 return NS_ERROR_FAILURE;
598 break;
599 case ColorID::ThemedScrollbarInactive:
600 aColor = mThemedScrollbarInactive;
601 if (!ShouldUseThemedScrollbarColor(aID, aColor, mIsDark)) {
602 return NS_ERROR_FAILURE;
604 break;
605 case ColorID::ThemedScrollbarThumb:
606 aColor = mThemedScrollbarThumb;
607 if (!ShouldUseThemedScrollbarColor(aID, aColor, mIsDark)) {
608 return NS_ERROR_FAILURE;
610 break;
611 case ColorID::ThemedScrollbarThumbHover:
612 aColor = mThemedScrollbarThumbHover;
613 if (!ShouldUseThemedScrollbarColor(aID, aColor, mIsDark)) {
614 return NS_ERROR_FAILURE;
616 break;
617 case ColorID::ThemedScrollbarThumbActive:
618 aColor = mThemedScrollbarThumbActive;
619 if (!ShouldUseThemedScrollbarColor(aID, aColor, mIsDark)) {
620 return NS_ERROR_FAILURE;
622 break;
623 case ColorID::ThemedScrollbarThumbInactive:
624 aColor = mThemedScrollbarThumbInactive;
625 if (!ShouldUseThemedScrollbarColor(aID, aColor, mIsDark)) {
626 return NS_ERROR_FAILURE;
628 break;
630 // css2 http://www.w3.org/TR/REC-CSS2/ui.html#system-colors
631 case ColorID::Activeborder:
632 // active window border
633 aColor = mMozWindowActiveBorder;
634 break;
635 case ColorID::Inactiveborder:
636 // inactive window border
637 aColor = mMozWindowInactiveBorder;
638 break;
639 case ColorID::Graytext: // disabled text in windows, menus, etc.
640 aColor = mGrayText;
641 break;
642 case ColorID::Activecaption:
643 aColor = mTitlebar.mBg;
644 break;
645 case ColorID::Captiontext: // text in active window caption (titlebar)
646 aColor = mTitlebar.mFg;
647 break;
648 case ColorID::Inactivecaption:
649 // inactive window caption
650 aColor = mTitlebarInactive.mBg;
651 break;
652 case ColorID::Inactivecaptiontext:
653 aColor = mTitlebarInactive.mFg;
654 break;
655 case ColorID::Infobackground:
656 aColor = mInfo.mBg;
657 break;
658 case ColorID::Infotext:
659 aColor = mInfo.mFg;
660 break;
661 case ColorID::Menu:
662 aColor = mMenu.mBg;
663 break;
664 case ColorID::Menutext:
665 aColor = mMenu.mFg;
666 break;
667 case ColorID::MozHeaderbar:
668 aColor = mHeaderBar.mBg;
669 break;
670 case ColorID::MozHeaderbartext:
671 aColor = mHeaderBar.mFg;
672 break;
673 case ColorID::MozHeaderbarinactive:
674 aColor = mHeaderBarInactive.mBg;
675 break;
676 case ColorID::MozHeaderbarinactivetext:
677 aColor = mHeaderBarInactive.mFg;
678 break;
679 case ColorID::Threedface:
680 case ColorID::Buttonface:
681 case ColorID::MozButtondisabledface:
682 // 3-D face color
683 aColor = mWindow.mBg;
684 break;
686 case ColorID::Buttontext:
687 // text on push buttons
688 aColor = mButton.mFg;
689 break;
691 case ColorID::Buttonhighlight:
692 // 3-D highlighted edge color
693 case ColorID::Threedhighlight:
694 // 3-D highlighted outer edge color
695 aColor = mFrameOuterLightBorder;
696 break;
698 case ColorID::Buttonshadow:
699 // 3-D shadow edge color
700 case ColorID::Threedshadow:
701 // 3-D shadow inner edge color
702 aColor = mFrameInnerDarkBorder;
703 break;
705 case ColorID::Threedlightshadow:
706 case ColorID::Buttonborder:
707 case ColorID::MozDisabledfield:
708 aColor = mIsDark ? *GenericDarkColor(aID) : NS_RGB(0xE0, 0xE0, 0xE0);
709 break;
710 case ColorID::Threeddarkshadow:
711 aColor = mIsDark ? *GenericDarkColor(aID) : NS_RGB(0xDC, 0xDC, 0xDC);
712 break;
714 case ColorID::MozEventreerow:
715 case ColorID::Field:
716 aColor = mField.mBg;
717 break;
718 case ColorID::Fieldtext:
719 aColor = mField.mFg;
720 break;
721 case ColorID::MozButtonhoverface:
722 case ColorID::MozButtonactiveface:
723 aColor = mButtonHover.mBg;
724 break;
725 case ColorID::MozButtonhovertext:
726 aColor = mButtonHover.mFg;
727 break;
728 case ColorID::MozButtonactivetext:
729 aColor = mButtonActiveText;
730 break;
731 case ColorID::MozMenuhover:
732 aColor = mMenuHover.mBg;
733 break;
734 case ColorID::MozMenuhovertext:
735 aColor = mMenuHover.mFg;
736 break;
737 case ColorID::MozMenuhoverdisabled:
738 aColor = NS_TRANSPARENT;
739 break;
740 case ColorID::MozOddtreerow:
741 aColor = mOddCellBackground;
742 break;
743 case ColorID::MozNativehyperlinktext:
744 aColor = mNativeHyperLinkText;
745 break;
746 case ColorID::MozNativevisitedhyperlinktext:
747 aColor = mNativeVisitedHyperLinkText;
748 break;
749 case ColorID::MozComboboxtext:
750 aColor = mComboBoxText;
751 break;
752 case ColorID::MozColheadertext:
753 aColor = mMozColHeaderText;
754 break;
755 case ColorID::MozColheaderhovertext:
756 aColor = mMozColHeaderHoverText;
757 break;
758 case ColorID::SpellCheckerUnderline:
759 case ColorID::Mark:
760 case ColorID::Marktext:
761 aColor = GetStandinForNativeColor(
762 aID, mIsDark ? ColorScheme::Dark : ColorScheme::Light);
763 break;
764 default:
765 /* default color is BLACK */
766 aColor = 0;
767 res = NS_ERROR_FAILURE;
768 break;
771 return res;
774 static int32_t CheckWidgetStyle(GtkWidget* aWidget, const char* aStyle,
775 int32_t aResult) {
776 gboolean value = FALSE;
777 gtk_widget_style_get(aWidget, aStyle, &value, nullptr);
778 return value ? aResult : 0;
781 static int32_t ConvertGTKStepperStyleToMozillaScrollArrowStyle(
782 GtkWidget* aWidget) {
783 if (!aWidget) return mozilla::LookAndFeel::eScrollArrowStyle_Single;
785 return CheckWidgetStyle(aWidget, "has-backward-stepper",
786 mozilla::LookAndFeel::eScrollArrow_StartBackward) |
787 CheckWidgetStyle(aWidget, "has-forward-stepper",
788 mozilla::LookAndFeel::eScrollArrow_EndForward) |
789 CheckWidgetStyle(aWidget, "has-secondary-backward-stepper",
790 mozilla::LookAndFeel::eScrollArrow_EndBackward) |
791 CheckWidgetStyle(aWidget, "has-secondary-forward-stepper",
792 mozilla::LookAndFeel::eScrollArrow_StartForward);
795 nsresult nsLookAndFeel::NativeGetInt(IntID aID, int32_t& aResult) {
796 nsresult res = NS_OK;
798 // We use delayed initialization by EnsureInit() here
799 // to make sure mozilla::Preferences is available (Bug 115807).
800 // IntID::UseAccessibilityTheme is requested before user preferences
801 // are read, and so EnsureInit(), which depends on preference values,
802 // is deliberately delayed until required.
803 switch (aID) {
804 case IntID::ScrollButtonLeftMouseButtonAction:
805 aResult = 0;
806 break;
807 case IntID::ScrollButtonMiddleMouseButtonAction:
808 aResult = 1;
809 break;
810 case IntID::ScrollButtonRightMouseButtonAction:
811 aResult = 2;
812 break;
813 case IntID::CaretBlinkTime:
814 EnsureInit();
815 aResult = mCaretBlinkTime;
816 break;
817 case IntID::CaretBlinkCount:
818 EnsureInit();
819 aResult = mCaretBlinkCount;
820 break;
821 case IntID::CaretWidth:
822 aResult = 1;
823 break;
824 case IntID::ShowCaretDuringSelection:
825 aResult = 0;
826 break;
827 case IntID::SelectTextfieldsOnKeyFocus: {
828 GtkSettings* settings;
829 gboolean select_on_focus;
831 settings = gtk_settings_get_default();
832 g_object_get(settings, "gtk-entry-select-on-focus", &select_on_focus,
833 nullptr);
835 if (select_on_focus)
836 aResult = 1;
837 else
838 aResult = 0;
840 } break;
841 case IntID::ScrollToClick: {
842 GtkSettings* settings;
843 gboolean warps_slider = FALSE;
845 settings = gtk_settings_get_default();
846 if (g_object_class_find_property(G_OBJECT_GET_CLASS(settings),
847 "gtk-primary-button-warps-slider")) {
848 g_object_get(settings, "gtk-primary-button-warps-slider", &warps_slider,
849 nullptr);
852 if (warps_slider)
853 aResult = 1;
854 else
855 aResult = 0;
856 } break;
857 case IntID::SubmenuDelay: {
858 GtkSettings* settings;
859 gint delay;
861 settings = gtk_settings_get_default();
862 g_object_get(settings, "gtk-menu-popup-delay", &delay, nullptr);
863 aResult = (int32_t)delay;
864 break;
866 case IntID::TooltipDelay: {
867 aResult = 500;
868 break;
870 case IntID::MenusCanOverlapOSBar:
871 // we want XUL popups to be able to overlap the task bar.
872 aResult = 1;
873 break;
874 case IntID::SkipNavigatingDisabledMenuItem:
875 aResult = 1;
876 break;
877 case IntID::DragThresholdX:
878 case IntID::DragThresholdY: {
879 gint threshold = 0;
880 g_object_get(gtk_settings_get_default(), "gtk-dnd-drag-threshold",
881 &threshold, nullptr);
883 aResult = threshold;
884 } break;
885 case IntID::ScrollArrowStyle: {
886 GtkWidget* scrollbar = GetWidget(MOZ_GTK_SCROLLBAR_VERTICAL);
887 aResult = ConvertGTKStepperStyleToMozillaScrollArrowStyle(scrollbar);
888 break;
890 case IntID::TreeOpenDelay:
891 aResult = 1000;
892 break;
893 case IntID::TreeCloseDelay:
894 aResult = 1000;
895 break;
896 case IntID::TreeLazyScrollDelay:
897 aResult = 150;
898 break;
899 case IntID::TreeScrollDelay:
900 aResult = 100;
901 break;
902 case IntID::TreeScrollLinesMax:
903 aResult = 3;
904 break;
905 case IntID::AlertNotificationOrigin:
906 aResult = NS_ALERT_TOP;
907 break;
908 case IntID::IMERawInputUnderlineStyle:
909 case IntID::IMEConvertedTextUnderlineStyle:
910 aResult = static_cast<int32_t>(StyleTextDecorationStyle::Solid);
911 break;
912 case IntID::IMESelectedRawTextUnderlineStyle:
913 case IntID::IMESelectedConvertedTextUnderline:
914 aResult = static_cast<int32_t>(StyleTextDecorationStyle::None);
915 break;
916 case IntID::SpellCheckerUnderlineStyle:
917 aResult = static_cast<int32_t>(StyleTextDecorationStyle::Wavy);
918 break;
919 case IntID::MenuBarDrag:
920 EnsureInit();
921 aResult = mSystemTheme.mMenuSupportsDrag;
922 break;
923 case IntID::ScrollbarButtonAutoRepeatBehavior:
924 aResult = 1;
925 break;
926 case IntID::SwipeAnimationEnabled:
927 aResult = 1;
928 break;
929 case IntID::ContextMenuOffsetVertical:
930 case IntID::ContextMenuOffsetHorizontal:
931 aResult = 2;
932 break;
933 case IntID::GTKCSDAvailable:
934 aResult = sCSDAvailable;
935 break;
936 case IntID::GTKCSDMaximizeButton:
937 EnsureInit();
938 aResult = mCSDMaximizeButton;
939 break;
940 case IntID::GTKCSDMinimizeButton:
941 EnsureInit();
942 aResult = mCSDMinimizeButton;
943 break;
944 case IntID::GTKCSDCloseButton:
945 EnsureInit();
946 aResult = mCSDCloseButton;
947 break;
948 case IntID::GTKCSDReversedPlacement:
949 EnsureInit();
950 aResult = mCSDReversedPlacement;
951 break;
952 case IntID::PrefersReducedMotion: {
953 EnsureInit();
954 aResult = mPrefersReducedMotion;
955 break;
957 case IntID::SystemUsesDarkTheme: {
958 EnsureInit();
959 if (mColorSchemePreference) {
960 aResult = *mColorSchemePreference == ColorScheme::Dark;
961 } else {
962 aResult = mSystemTheme.mIsDark;
964 break;
966 case IntID::GTKCSDMaximizeButtonPosition:
967 aResult = mCSDMaximizeButtonPosition;
968 break;
969 case IntID::GTKCSDMinimizeButtonPosition:
970 aResult = mCSDMinimizeButtonPosition;
971 break;
972 case IntID::GTKCSDCloseButtonPosition:
973 aResult = mCSDCloseButtonPosition;
974 break;
975 case IntID::UseAccessibilityTheme:
976 // If high contrast is enabled, enable prefers-reduced-transparency media
977 // query as well as there is no dedicated option.
978 case IntID::PrefersReducedTransparency:
979 EnsureInit();
980 aResult = mSystemTheme.mHighContrast;
981 break;
982 case IntID::InvertedColors:
983 // No GTK API for checking if inverted colors is enabled
984 aResult = 0;
985 break;
986 case IntID::TitlebarRadius: {
987 EnsureInit();
988 aResult = EffectiveTheme().mTitlebarRadius;
989 break;
991 case IntID::AllowOverlayScrollbarsOverlap: {
992 aResult = 1;
993 break;
995 case IntID::ScrollbarFadeBeginDelay: {
996 aResult = 1000;
997 break;
999 case IntID::ScrollbarFadeDuration: {
1000 aResult = 400;
1001 break;
1003 case IntID::ScrollbarDisplayOnMouseMove: {
1004 aResult = 1;
1005 break;
1007 case IntID::PanelAnimations:
1008 aResult = [&]() -> bool {
1009 if (!sCSDAvailable) {
1010 // Disabled on systems without CSD, see bug 1385079.
1011 return false;
1013 if (GdkIsWaylandDisplay()) {
1014 // Disabled on wayland, see bug 1800442 and bug 1800368.
1015 return false;
1017 return true;
1018 }();
1019 break;
1020 case IntID::UseOverlayScrollbars: {
1021 aResult = StaticPrefs::widget_gtk_overlay_scrollbars_enabled();
1022 break;
1024 case IntID::HideCursorWhileTyping: {
1025 aResult = StaticPrefs::widget_gtk_hide_pointer_while_typing_enabled();
1026 break;
1028 case IntID::TouchDeviceSupportPresent:
1029 aResult = widget::WidgetUtilsGTK::IsTouchDeviceSupportPresent();
1030 break;
1031 default:
1032 aResult = 0;
1033 res = NS_ERROR_FAILURE;
1036 return res;
1039 nsresult nsLookAndFeel::NativeGetFloat(FloatID aID, float& aResult) {
1040 nsresult rv = NS_OK;
1041 switch (aID) {
1042 case FloatID::IMEUnderlineRelativeSize:
1043 aResult = 1.0f;
1044 break;
1045 case FloatID::SpellCheckerUnderlineRelativeSize:
1046 aResult = 1.0f;
1047 break;
1048 case FloatID::CaretAspectRatio:
1049 EnsureInit();
1050 aResult = mSystemTheme.mCaretRatio;
1051 break;
1052 case FloatID::TextScaleFactor:
1053 aResult = gfxPlatformGtk::GetFontScaleFactor();
1054 break;
1055 default:
1056 aResult = -1.0;
1057 rv = NS_ERROR_FAILURE;
1059 return rv;
1062 static void GetSystemFontInfo(GtkStyleContext* aStyle, nsString* aFontName,
1063 gfxFontStyle* aFontStyle) {
1064 aFontStyle->style = FontSlantStyle::NORMAL;
1066 // As in
1067 // https://git.gnome.org/browse/gtk+/tree/gtk/gtkwidget.c?h=3.22.19#n10333
1068 PangoFontDescription* desc;
1069 gtk_style_context_get(aStyle, gtk_style_context_get_state(aStyle), "font",
1070 &desc, nullptr);
1072 aFontStyle->systemFont = true;
1074 constexpr auto quote = u"\""_ns;
1075 NS_ConvertUTF8toUTF16 family(pango_font_description_get_family(desc));
1076 *aFontName = quote + family + quote;
1078 aFontStyle->weight =
1079 FontWeight::FromInt(pango_font_description_get_weight(desc));
1081 // FIXME: Set aFontStyle->stretch correctly!
1082 aFontStyle->stretch = FontStretch::NORMAL;
1084 float size = float(pango_font_description_get_size(desc)) / PANGO_SCALE;
1086 // |size| is now either pixels or pango-points (not Mozilla-points!)
1088 if (!pango_font_description_get_size_is_absolute(desc)) {
1089 // |size| is in pango-points, so convert to pixels.
1090 size *= float(gfxPlatformGtk::GetFontScaleDPI()) / POINTS_PER_INCH_FLOAT;
1093 // |size| is now pixels but not scaled for the hidpi displays,
1094 aFontStyle->size = size;
1096 pango_font_description_free(desc);
1099 bool nsLookAndFeel::NativeGetFont(FontID aID, nsString& aFontName,
1100 gfxFontStyle& aFontStyle) {
1101 return mSystemTheme.GetFont(aID, aFontName, aFontStyle);
1104 bool nsLookAndFeel::PerThemeData::GetFont(FontID aID, nsString& aFontName,
1105 gfxFontStyle& aFontStyle) const {
1106 switch (aID) {
1107 case FontID::Menu: // css2
1108 case FontID::MozPullDownMenu: // css3
1109 aFontName = mMenuFontName;
1110 aFontStyle = mMenuFontStyle;
1111 break;
1113 case FontID::MozField: // css3
1114 case FontID::MozList: // css3
1115 aFontName = mFieldFontName;
1116 aFontStyle = mFieldFontStyle;
1117 break;
1119 case FontID::MozButton: // css3
1120 aFontName = mButtonFontName;
1121 aFontStyle = mButtonFontStyle;
1122 break;
1124 case FontID::Caption: // css2
1125 case FontID::Icon: // css2
1126 case FontID::MessageBox: // css2
1127 case FontID::SmallCaption: // css2
1128 case FontID::StatusBar: // css2
1129 default:
1130 aFontName = mDefaultFontName;
1131 aFontStyle = mDefaultFontStyle;
1132 break;
1135 // Convert GDK pixels to CSS pixels.
1136 // When "layout.css.devPixelsPerPx" > 0, this is not a direct conversion.
1137 // The difference produces a scaling of system fonts in proportion with
1138 // other scaling from the change in CSS pixel sizes.
1139 aFontStyle.size /= LookAndFeel::GetTextScaleFactor();
1140 return true;
1143 // Check color contrast according to
1144 // https://www.w3.org/TR/AERT/#color-contrast
1145 static bool HasGoodContrastVisibility(GdkRGBA& aColor1, GdkRGBA& aColor2) {
1146 int32_t luminosityDifference = NS_LUMINOSITY_DIFFERENCE(
1147 GDK_RGBA_TO_NS_RGBA(aColor1), GDK_RGBA_TO_NS_RGBA(aColor2));
1148 if (luminosityDifference < NS_SUFFICIENT_LUMINOSITY_DIFFERENCE) {
1149 return false;
1152 double colorDifference = std::abs(aColor1.red - aColor2.red) +
1153 std::abs(aColor1.green - aColor2.green) +
1154 std::abs(aColor1.blue - aColor2.blue);
1155 return (colorDifference * 255.0 > 500.0);
1158 // Check if the foreground/background colors match with default white/black
1159 // html page colors.
1160 static bool IsGtkThemeCompatibleWithHTMLColors() {
1161 GdkRGBA white = {1.0, 1.0, 1.0};
1162 GdkRGBA black = {0.0, 0.0, 0.0};
1164 GtkStyleContext* style = GetStyleContext(MOZ_GTK_WINDOW);
1166 GdkRGBA textColor;
1167 gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &textColor);
1169 // Theme text color and default white html page background
1170 if (!HasGoodContrastVisibility(textColor, white)) {
1171 return false;
1174 GdkRGBA backgroundColor;
1175 gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL,
1176 &backgroundColor);
1178 // Theme background color and default white html page background
1179 if (HasGoodContrastVisibility(backgroundColor, white)) {
1180 return false;
1183 // Theme background color and default black text color
1184 return HasGoodContrastVisibility(backgroundColor, black);
1187 static nsCString GetGtkSettingsStringKey(const char* aKey) {
1188 MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
1189 nsCString ret;
1190 GtkSettings* settings = gtk_settings_get_default();
1191 char* value = nullptr;
1192 g_object_get(settings, aKey, &value, nullptr);
1193 if (value) {
1194 ret.Assign(value);
1195 g_free(value);
1197 return ret;
1200 static nsCString GetGtkTheme() {
1201 return GetGtkSettingsStringKey("gtk-theme-name");
1204 static bool GetPreferDarkTheme() {
1205 GtkSettings* settings = gtk_settings_get_default();
1206 gboolean preferDarkTheme = FALSE;
1207 g_object_get(settings, "gtk-application-prefer-dark-theme", &preferDarkTheme,
1208 nullptr);
1209 return preferDarkTheme == TRUE;
1212 // It seems GTK doesn't have an API to query if the current theme is "light" or
1213 // "dark", so we synthesize it from the CSS2 Window/WindowText colors instead,
1214 // by comparing their luminosity.
1215 static bool GetThemeIsDark() {
1216 GdkRGBA bg, fg;
1217 GtkStyleContext* style = GetStyleContext(MOZ_GTK_WINDOW);
1218 gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &bg);
1219 gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &fg);
1220 return RelativeLuminanceUtils::Compute(GDK_RGBA_TO_NS_RGBA(bg)) <
1221 RelativeLuminanceUtils::Compute(GDK_RGBA_TO_NS_RGBA(fg));
1224 void nsLookAndFeel::RestoreSystemTheme() {
1225 LOGLNF("RestoreSystemTheme(%s, %d, %d)\n", mSystemTheme.mName.get(),
1226 mSystemTheme.mPreferDarkTheme, mSystemThemeOverridden);
1228 if (!mSystemThemeOverridden) {
1229 return;
1232 // Available on Gtk 3.20+.
1233 static auto sGtkSettingsResetProperty =
1234 (void (*)(GtkSettings*, const gchar*))dlsym(
1235 RTLD_DEFAULT, "gtk_settings_reset_property");
1237 GtkSettings* settings = gtk_settings_get_default();
1238 if (sGtkSettingsResetProperty) {
1239 sGtkSettingsResetProperty(settings, "gtk-theme-name");
1240 sGtkSettingsResetProperty(settings, "gtk-application-prefer-dark-theme");
1241 } else {
1242 g_object_set(settings, "gtk-theme-name", mSystemTheme.mName.get(),
1243 "gtk-application-prefer-dark-theme",
1244 mSystemTheme.mPreferDarkTheme, nullptr);
1246 mSystemThemeOverridden = false;
1247 UpdateRoundedBottomCornerStyles();
1248 moz_gtk_refresh();
1251 static bool AnyColorChannelIsDifferent(nscolor aColor) {
1252 return NS_GET_R(aColor) != NS_GET_G(aColor) ||
1253 NS_GET_R(aColor) != NS_GET_B(aColor);
1256 bool nsLookAndFeel::ConfigureAltTheme() {
1257 GtkSettings* settings = gtk_settings_get_default();
1258 // Toggling gtk-application-prefer-dark-theme is not enough generally to
1259 // switch from dark to light theme. If the theme didn't change, and we have
1260 // a dark theme, try to first remove -Dark{,er,est} from the theme name to
1261 // find the light variant.
1262 if (mSystemTheme.mIsDark) {
1263 nsCString potentialLightThemeName = mSystemTheme.mName;
1264 // clang-format off
1265 constexpr nsLiteralCString kSubstringsToRemove[] = {
1266 "-darkest"_ns, "-darker"_ns, "-dark"_ns,
1267 "-Darkest"_ns, "-Darker"_ns, "-Dark"_ns,
1268 "_darkest"_ns, "_darker"_ns, "_dark"_ns,
1269 "_Darkest"_ns, "_Darker"_ns, "_Dark"_ns,
1271 // clang-format on
1272 bool found = false;
1273 for (const auto& s : kSubstringsToRemove) {
1274 potentialLightThemeName = mSystemTheme.mName;
1275 potentialLightThemeName.ReplaceSubstring(s, ""_ns);
1276 if (potentialLightThemeName.Length() != mSystemTheme.mName.Length()) {
1277 found = true;
1278 break;
1281 if (found) {
1282 LOGLNF(" found potential light variant of %s: %s",
1283 mSystemTheme.mName.get(), potentialLightThemeName.get());
1284 g_object_set(settings, "gtk-theme-name", potentialLightThemeName.get(),
1285 "gtk-application-prefer-dark-theme", !mSystemTheme.mIsDark,
1286 nullptr);
1287 moz_gtk_refresh();
1289 if (!GetThemeIsDark()) {
1290 return true; // Success!
1295 LOGLNF(" toggling gtk-application-prefer-dark-theme");
1296 g_object_set(settings, "gtk-application-prefer-dark-theme",
1297 !mSystemTheme.mIsDark, nullptr);
1298 moz_gtk_refresh();
1299 if (mSystemTheme.mIsDark != GetThemeIsDark()) {
1300 return true; // Success!
1303 LOGLNF(" didn't work, falling back to default theme");
1304 // If the theme still didn't change enough, fall back to Adwaita with the
1305 // appropriate color preference.
1306 g_object_set(settings, "gtk-theme-name", "Adwaita",
1307 "gtk-application-prefer-dark-theme", !mSystemTheme.mIsDark,
1308 nullptr);
1309 moz_gtk_refresh();
1311 // If it _still_ didn't change enough, and we're looking for a dark theme,
1312 // try to set Adwaita-dark as a theme name. This might be needed in older GTK
1313 // versions.
1314 if (!mSystemTheme.mIsDark && !GetThemeIsDark()) {
1315 LOGLNF(" last resort Adwaita-dark fallback");
1316 g_object_set(settings, "gtk-theme-name", "Adwaita-dark", nullptr);
1317 moz_gtk_refresh();
1320 return false;
1323 void nsLookAndFeel::ConfigureAndInitializeAltTheme() {
1324 const bool fellBackToDefaultTheme = !ConfigureAltTheme();
1326 mAltTheme.Init();
1328 // Some of the alt theme colors we can grab from the system theme, if we fell
1329 // back to the default light / dark themes.
1330 if (fellBackToDefaultTheme) {
1331 if (StaticPrefs::widget_gtk_alt_theme_selection()) {
1332 mAltTheme.mSelectedText = mSystemTheme.mSelectedText;
1335 if (StaticPrefs::widget_gtk_alt_theme_scrollbar_active() &&
1336 (!mAltTheme.mIsDark || ShouldUseColorForActiveDarkScrollbarThumb(
1337 mSystemTheme.mThemedScrollbarThumbActive))) {
1338 mAltTheme.mThemedScrollbarThumbActive =
1339 mSystemTheme.mThemedScrollbarThumbActive;
1342 if (StaticPrefs::widget_gtk_alt_theme_accent()) {
1343 mAltTheme.mAccent = mSystemTheme.mAccent;
1347 // Special case for Adwaita: In GTK3 we don't have more proper accent colors,
1348 // so we use the selected background colors. Those colors, however, don't have
1349 // much contrast in dark mode (see bug 1741293). We know, however, that GTK4
1350 // uses the light accent color for the dark theme, see:
1352 // https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/named-colors.html#accent-colors
1354 // So we manually do that.
1355 if (mSystemTheme.mFamily == ThemeFamily::Adwaita) {
1356 auto& dark = mSystemTheme.mIsDark ? mSystemTheme : mAltTheme;
1357 auto& light = mSystemTheme.mIsDark ? mAltTheme : mSystemTheme;
1358 dark.mAccent = light.mAccent;
1361 // Right now we're using the opposite color-scheme theme, make sure to record
1362 // it.
1363 mSystemThemeOverridden = true;
1364 UpdateRoundedBottomCornerStyles();
1367 void nsLookAndFeel::ClearRoundedCornerProvider() {
1368 if (mRoundedCornerProvider) {
1369 gtk_style_context_remove_provider_for_screen(
1370 gdk_screen_get_default(),
1371 GTK_STYLE_PROVIDER(mRoundedCornerProvider.get()));
1372 mRoundedCornerProvider = nullptr;
1376 void nsLookAndFeel::UpdateRoundedBottomCornerStyles() {
1377 ClearRoundedCornerProvider();
1378 if (!StaticPrefs::widget_gtk_rounded_bottom_corners_enabled()) {
1379 return;
1381 int32_t radius = EffectiveTheme().mTitlebarRadius;
1382 if (!radius) {
1383 return;
1385 mRoundedCornerProvider = dont_AddRef(gtk_css_provider_new());
1386 nsPrintfCString string(
1387 "window.csd decoration {"
1388 "border-bottom-right-radius: %dpx;"
1389 "border-bottom-left-radius: %dpx;"
1390 "}\n",
1391 radius, radius);
1392 GUniquePtr<GError> error;
1393 if (!gtk_css_provider_load_from_data(mRoundedCornerProvider.get(),
1394 string.get(), string.Length(),
1395 getter_Transfers(error))) {
1396 NS_WARNING(nsPrintfCString("Failed to load provider: %s - %s\n",
1397 string.get(), error ? error->message : nullptr)
1398 .get());
1400 gtk_style_context_add_provider_for_screen(
1401 gdk_screen_get_default(),
1402 GTK_STYLE_PROVIDER(mRoundedCornerProvider.get()),
1403 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
1406 Maybe<ColorScheme> nsLookAndFeel::ComputeColorSchemeSetting() {
1408 // Check the pref explicitly here. Usually this shouldn't be needed, but
1409 // since we can only load one GTK theme at a time, and the pref will
1410 // override the effective value that the rest of gecko assumes for the
1411 // "system" color scheme, we need to factor it in our GTK theme decisions.
1412 int32_t pref = 0;
1413 if (NS_SUCCEEDED(Preferences::GetInt("ui.systemUsesDarkTheme", &pref))) {
1414 return Some(pref ? ColorScheme::Dark : ColorScheme::Light);
1418 if (!mDBusSettingsProxy) {
1419 return Nothing();
1421 GUniquePtr<GError> error;
1422 RefPtr<GVariant> variant = dont_AddRef(g_dbus_proxy_call_sync(
1423 mDBusSettingsProxy, "Read",
1424 g_variant_new("(ss)", "org.freedesktop.appearance", "color-scheme"),
1425 G_DBUS_CALL_FLAGS_NONE,
1426 StaticPrefs::widget_gtk_settings_portal_timeout_ms(), nullptr,
1427 getter_Transfers(error)));
1428 if (!variant) {
1429 LOGLNF("color-scheme query error: %s\n", error->message);
1430 return Nothing();
1432 LOGLNF("color-scheme query result: %s\n", GVariantToString(variant).get());
1433 variant = dont_AddRef(g_variant_get_child_value(variant, 0));
1434 while (variant && g_variant_is_of_type(variant, G_VARIANT_TYPE_VARIANT)) {
1435 // Unbox the return value.
1436 variant = dont_AddRef(g_variant_get_variant(variant));
1438 if (!variant || !g_variant_is_of_type(variant, G_VARIANT_TYPE_UINT32)) {
1439 MOZ_ASSERT(false, "Unexpected color-scheme query return value");
1440 return Nothing();
1442 switch (g_variant_get_uint32(variant)) {
1443 default:
1444 MOZ_FALLTHROUGH_ASSERT("Unexpected color-scheme query return value");
1445 case 0:
1446 break;
1447 case 1:
1448 return Some(ColorScheme::Dark);
1449 case 2:
1450 return Some(ColorScheme::Light);
1452 return Nothing();
1455 void nsLookAndFeel::Initialize() {
1456 LOGLNF("nsLookAndFeel::Initialize");
1457 MOZ_DIAGNOSTIC_ASSERT(!mInitialized);
1458 MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread(),
1459 "LookAndFeel init should be done on the main thread");
1461 mInitialized = true;
1463 GtkSettings* settings = gtk_settings_get_default();
1464 if (MOZ_UNLIKELY(!settings)) {
1465 NS_WARNING("EnsureInit: No settings");
1466 return;
1469 AutoRestore<bool> restoreIgnoreSettings(sIgnoreChangedSettings);
1470 sIgnoreChangedSettings = true;
1472 // Our current theme may be different from the system theme if we're matching
1473 // the Firefox theme or using the alt theme intentionally due to the
1474 // color-scheme preference. Make sure to restore the original system theme.
1475 RestoreSystemTheme();
1477 // First initialize global settings.
1478 InitializeGlobalSettings();
1480 // Record our system theme settings now.
1481 mSystemTheme.Init();
1483 // Find the alternative-scheme theme (light if the system theme is dark, or
1484 // vice versa), configure it and initialize it.
1485 ConfigureAndInitializeAltTheme();
1487 LOGLNF("System Theme: %s. Alt Theme: %s\n", mSystemTheme.mName.get(),
1488 mAltTheme.mName.get());
1490 // Go back to the system theme or keep the alt theme configured, depending on
1491 // Firefox theme or user color-scheme preference.
1492 ConfigureFinalEffectiveTheme();
1494 RecordTelemetry();
1497 void nsLookAndFeel::OnColorSchemeSettingChanged() {
1498 if (NS_WARN_IF(mColorSchemePreference == ComputeColorSchemeSetting())) {
1499 // We sometimes get duplicate color-scheme changes from dbus, avoid doing
1500 // extra work if not needed.
1501 return;
1503 OnSettingsChange();
1506 void nsLookAndFeel::InitializeGlobalSettings() {
1507 GtkSettings* settings = gtk_settings_get_default();
1509 mColorSchemePreference = ComputeColorSchemeSetting();
1511 gboolean enableAnimations = false;
1512 g_object_get(settings, "gtk-enable-animations", &enableAnimations, nullptr);
1513 mPrefersReducedMotion = !enableAnimations;
1515 gint blink_time = 0; // In milliseconds
1516 gint blink_timeout = 0; // in seconds
1517 gboolean blink;
1518 g_object_get(settings, "gtk-cursor-blink-time", &blink_time,
1519 "gtk-cursor-blink-timeout", &blink_timeout, "gtk-cursor-blink",
1520 &blink, nullptr);
1521 // From
1522 // https://docs.gtk.org/gtk3/property.Settings.gtk-cursor-blink-timeout.html:
1524 // Setting this to zero has the same effect as setting
1525 // GtkSettings:gtk-cursor-blink to FALSE.
1527 mCaretBlinkTime = blink && blink_timeout ? (int32_t)blink_time : 0;
1529 if (mCaretBlinkTime) {
1530 // blink_time * 2 because blink count is a full blink cycle.
1531 mCaretBlinkCount =
1532 std::max(1, int32_t(std::ceil(float(blink_timeout * 1000) /
1533 (float(blink_time) * 2.0f))));
1534 } else {
1535 mCaretBlinkCount = -1;
1538 mCSDCloseButton = false;
1539 mCSDMinimizeButton = false;
1540 mCSDMaximizeButton = false;
1541 mCSDCloseButtonPosition = 0;
1542 mCSDMinimizeButtonPosition = 0;
1543 mCSDMaximizeButtonPosition = 0;
1545 // We need to initialize whole CSD config explicitly because it's queried
1546 // as -moz-gtk* media features.
1547 ButtonLayout buttonLayout[TOOLBAR_BUTTONS];
1549 size_t activeButtons =
1550 GetGtkHeaderBarButtonLayout(Span(buttonLayout), &mCSDReversedPlacement);
1551 for (size_t i = 0; i < activeButtons; i++) {
1552 // We check if a button is represented on the right side of the tabbar.
1553 // Then we assign it a value from 3 to 5, instead of 0 to 2 when it is on
1554 // the left side.
1555 const ButtonLayout& layout = buttonLayout[i];
1556 int32_t* pos = nullptr;
1557 switch (layout.mType) {
1558 case MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE:
1559 mCSDMinimizeButton = true;
1560 pos = &mCSDMinimizeButtonPosition;
1561 break;
1562 case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE:
1563 mCSDMaximizeButton = true;
1564 pos = &mCSDMaximizeButtonPosition;
1565 break;
1566 case MOZ_GTK_HEADER_BAR_BUTTON_CLOSE:
1567 mCSDCloseButton = true;
1568 pos = &mCSDCloseButtonPosition;
1569 break;
1570 default:
1571 break;
1574 if (pos) {
1575 *pos = i;
1580 void nsLookAndFeel::ConfigureFinalEffectiveTheme() {
1581 MOZ_ASSERT(mSystemThemeOverridden,
1582 "By this point, the alt theme should be configured");
1583 const bool shouldUseSystemTheme = [&] {
1584 using ChromeSetting = PreferenceSheet::ChromeColorSchemeSetting;
1585 // NOTE: We can't call ColorSchemeForChrome directly because this might run
1586 // while we're computing it.
1587 switch (PreferenceSheet::ColorSchemeSettingForChrome()) {
1588 case ChromeSetting::Light:
1589 return !mSystemTheme.mIsDark;
1590 case ChromeSetting::Dark:
1591 return mSystemTheme.mIsDark;
1592 case ChromeSetting::System:
1593 break;
1595 if (!mColorSchemePreference) {
1596 return true;
1598 const bool preferenceIsDark = *mColorSchemePreference == ColorScheme::Dark;
1599 return preferenceIsDark == mSystemTheme.mIsDark;
1600 }();
1602 const bool usingSystem = !mSystemThemeOverridden;
1603 LOGLNF("OverrideSystemThemeIfNeeded(matchesSystem=%d, usingSystem=%d)\n",
1604 shouldUseSystemTheme, usingSystem);
1606 if (shouldUseSystemTheme) {
1607 RestoreSystemTheme();
1608 } else if (usingSystem) {
1609 LOGLNF("Setting theme %s, %d\n", mAltTheme.mName.get(),
1610 mAltTheme.mPreferDarkTheme);
1612 GtkSettings* settings = gtk_settings_get_default();
1613 if (mSystemTheme.mName == mAltTheme.mName) {
1614 // Prefer setting only gtk-application-prefer-dark-theme, so we can still
1615 // get notified from notify::gtk-theme-name if the user changes the theme.
1616 g_object_set(settings, "gtk-application-prefer-dark-theme",
1617 mAltTheme.mPreferDarkTheme, nullptr);
1618 } else {
1619 g_object_set(settings, "gtk-theme-name", mAltTheme.mName.get(),
1620 "gtk-application-prefer-dark-theme",
1621 mAltTheme.mPreferDarkTheme, nullptr);
1623 mSystemThemeOverridden = true;
1624 UpdateRoundedBottomCornerStyles();
1625 moz_gtk_refresh();
1629 static bool GetColorFromBackgroundImage(GtkStyleContext* aStyle,
1630 nscolor aForForegroundColor,
1631 GtkStateFlags aState, nscolor* aColor) {
1632 GValue value = G_VALUE_INIT;
1633 gtk_style_context_get_property(aStyle, "background-image", aState, &value);
1634 auto cleanup = MakeScopeExit([&] { g_value_unset(&value); });
1635 if (GetColorFromImagePattern(&value, aColor)) {
1636 return true;
1640 GdkRGBA light, dark;
1641 if (GetGradientColors(&value, &light, &dark)) {
1642 nscolor l = GDK_RGBA_TO_NS_RGBA(light);
1643 nscolor d = GDK_RGBA_TO_NS_RGBA(dark);
1644 // Return the one with more contrast.
1645 // TODO(emilio): This could do interpolation or what not but seems
1646 // overkill.
1647 if (NS_LUMINOSITY_DIFFERENCE(l, aForForegroundColor) >
1648 NS_LUMINOSITY_DIFFERENCE(d, aForForegroundColor)) {
1649 *aColor = l;
1650 } else {
1651 *aColor = d;
1653 return true;
1657 return false;
1660 static nscolor GetBackgroundColor(
1661 GtkStyleContext* aStyle, nscolor aForForegroundColor,
1662 GtkStateFlags aState = GTK_STATE_FLAG_NORMAL,
1663 nscolor aOverBackgroundColor = NS_TRANSPARENT) {
1664 // Try to synthesize a color from a background-image.
1665 nscolor imageColor = NS_TRANSPARENT;
1666 if (GetColorFromBackgroundImage(aStyle, aForForegroundColor, aState,
1667 &imageColor)) {
1668 if (NS_GET_A(imageColor) == 255) {
1669 return imageColor;
1673 GdkRGBA gdkColor;
1674 gtk_style_context_get_background_color(aStyle, aState, &gdkColor);
1675 nscolor bgColor = GDK_RGBA_TO_NS_RGBA(gdkColor);
1676 // background-image paints over background-color.
1677 const nscolor finalColor = NS_ComposeColors(bgColor, imageColor);
1678 if (finalColor != aOverBackgroundColor) {
1679 return finalColor;
1681 return NS_TRANSPARENT;
1684 static nscolor GetTextColor(GtkStyleContext* aStyle,
1685 GtkStateFlags aState = GTK_STATE_FLAG_NORMAL) {
1686 GdkRGBA color;
1687 gtk_style_context_get_color(aStyle, aState, &color);
1688 return GDK_RGBA_TO_NS_RGBA(color);
1691 using ColorPair = nsLookAndFeel::ColorPair;
1692 static ColorPair GetColorPair(GtkStyleContext* aStyle,
1693 GtkStateFlags aState = GTK_STATE_FLAG_NORMAL) {
1694 ColorPair result;
1695 result.mFg = GetTextColor(aStyle, aState);
1696 result.mBg = GetBackgroundColor(aStyle, result.mFg, aState);
1697 return result;
1700 static bool GetNamedColorPair(GtkStyleContext* aStyle, const char* aBgName,
1701 const char* aFgName, ColorPair* aPair) {
1702 GdkRGBA bg, fg;
1703 if (!gtk_style_context_lookup_color(aStyle, aBgName, &bg) ||
1704 !gtk_style_context_lookup_color(aStyle, aFgName, &fg)) {
1705 return false;
1708 aPair->mBg = GDK_RGBA_TO_NS_RGBA(bg);
1709 aPair->mFg = GDK_RGBA_TO_NS_RGBA(fg);
1711 // If the colors are semi-transparent and the theme provides a
1712 // background color, blend with them to get the "final" color, see
1713 // bug 1717077.
1714 if (NS_GET_A(aPair->mBg) != 255 &&
1715 (gtk_style_context_lookup_color(aStyle, "bg_color", &bg) ||
1716 gtk_style_context_lookup_color(aStyle, "theme_bg_color", &bg))) {
1717 aPair->mBg = NS_ComposeColors(GDK_RGBA_TO_NS_RGBA(bg), aPair->mBg);
1720 // A semi-transparent foreground color would be kinda silly, but is done
1721 // for symmetry.
1722 if (NS_GET_A(aPair->mFg) != 255) {
1723 aPair->mFg = NS_ComposeColors(aPair->mBg, aPair->mFg);
1726 return true;
1729 static void EnsureColorPairIsOpaque(ColorPair& aPair) {
1730 // Blend with white, ensuring the color is opaque, so that the UI doesn't have
1731 // to care about alpha.
1732 aPair.mBg = NS_ComposeColors(NS_RGB(0xff, 0xff, 0xff), aPair.mBg);
1733 aPair.mFg = NS_ComposeColors(aPair.mBg, aPair.mFg);
1736 static void PreferDarkerBackground(ColorPair& aPair) {
1737 // We use the darker one unless the foreground isn't really a color (is all
1738 // white / black / gray) and the background is, in which case we stick to what
1739 // we have.
1740 if (RelativeLuminanceUtils::Compute(aPair.mBg) >
1741 RelativeLuminanceUtils::Compute(aPair.mFg) &&
1742 (AnyColorChannelIsDifferent(aPair.mFg) ||
1743 !AnyColorChannelIsDifferent(aPair.mBg))) {
1744 std::swap(aPair.mBg, aPair.mFg);
1748 void nsLookAndFeel::PerThemeData::Init() {
1749 mName = GetGtkTheme();
1751 mFamily = [&] {
1752 if (mName.EqualsLiteral("Adwaita") || mName.EqualsLiteral("Adwaita-dark")) {
1753 return ThemeFamily::Adwaita;
1755 if (mName.EqualsLiteral("Breeze") || mName.EqualsLiteral("Breeze-Dark")) {
1756 return ThemeFamily::Breeze;
1758 if (StringBeginsWith(mName, "Yaru"_ns)) {
1759 return ThemeFamily::Yaru;
1761 return ThemeFamily::Other;
1762 }();
1764 GtkStyleContext* style;
1766 mHighContrast = StaticPrefs::widget_content_gtk_high_contrast_enabled() &&
1767 mName.Find("HighContrast"_ns) >= 0;
1769 mPreferDarkTheme = GetPreferDarkTheme();
1771 mIsDark = GetThemeIsDark();
1773 mCompatibleWithHTMLLightColors =
1774 !mIsDark && IsGtkThemeCompatibleWithHTMLColors();
1776 GdkRGBA color;
1777 // Some themes style the <trough>, while others style the <scrollbar>
1778 // itself, so we look at both and compose the colors.
1779 style = GetStyleContext(MOZ_GTK_SCROLLBAR_VERTICAL);
1780 gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color);
1781 mThemedScrollbar = GDK_RGBA_TO_NS_RGBA(color);
1782 gtk_style_context_get_background_color(style, GTK_STATE_FLAG_BACKDROP,
1783 &color);
1784 mThemedScrollbarInactive = GDK_RGBA_TO_NS_RGBA(color);
1786 style = GetStyleContext(MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL);
1787 gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color);
1788 mThemedScrollbar =
1789 NS_ComposeColors(mThemedScrollbar, GDK_RGBA_TO_NS_RGBA(color));
1790 gtk_style_context_get_background_color(style, GTK_STATE_FLAG_BACKDROP,
1791 &color);
1792 mThemedScrollbarInactive =
1793 NS_ComposeColors(mThemedScrollbarInactive, GDK_RGBA_TO_NS_RGBA(color));
1795 style = GetStyleContext(MOZ_GTK_SCROLLBAR_THUMB_VERTICAL);
1796 gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color);
1797 mThemedScrollbarThumb = GDK_RGBA_TO_NS_RGBA(color);
1798 gtk_style_context_get_background_color(style, GTK_STATE_FLAG_PRELIGHT,
1799 &color);
1800 mThemedScrollbarThumbHover = GDK_RGBA_TO_NS_RGBA(color);
1801 gtk_style_context_get_background_color(
1802 style, GtkStateFlags(GTK_STATE_FLAG_PRELIGHT | GTK_STATE_FLAG_ACTIVE),
1803 &color);
1804 mThemedScrollbarThumbActive = GDK_RGBA_TO_NS_RGBA(color);
1805 gtk_style_context_get_background_color(style, GTK_STATE_FLAG_BACKDROP,
1806 &color);
1807 mThemedScrollbarThumbInactive = GDK_RGBA_TO_NS_RGBA(color);
1809 // Make sure that the thumb is visible, at least.
1810 const bool fallbackToUnthemedColors = [&] {
1811 if (!StaticPrefs::widget_gtk_theme_scrollbar_colors_enabled()) {
1812 return true;
1815 if (!ShouldHonorThemeScrollbarColors()) {
1816 return true;
1818 // If any of the scrollbar thumb colors are fully transparent, fall back to
1819 // non-native ones.
1820 if (!NS_GET_A(mThemedScrollbarThumb) ||
1821 !NS_GET_A(mThemedScrollbarThumbHover) ||
1822 !NS_GET_A(mThemedScrollbarThumbActive)) {
1823 return true;
1825 // If the thumb and track are the same color and opaque, fall back to
1826 // non-native colors as well.
1827 if (mThemedScrollbar == mThemedScrollbarThumb &&
1828 NS_GET_A(mThemedScrollbar) == 0xff) {
1829 return true;
1831 return false;
1832 }();
1834 if (fallbackToUnthemedColors) {
1835 if (mIsDark) {
1836 // Taken from Adwaita-dark.
1837 mThemedScrollbar = NS_RGB(0x31, 0x31, 0x31);
1838 mThemedScrollbarInactive = NS_RGB(0x2d, 0x2d, 0x2d);
1839 mThemedScrollbarThumb = NS_RGB(0xa3, 0xa4, 0xa4);
1840 mThemedScrollbarThumbInactive = NS_RGB(0x59, 0x5a, 0x5a);
1841 } else {
1842 // Taken from Adwaita.
1843 mThemedScrollbar = NS_RGB(0xce, 0xce, 0xce);
1844 mThemedScrollbarInactive = NS_RGB(0xec, 0xed, 0xef);
1845 mThemedScrollbarThumb = NS_RGB(0x82, 0x81, 0x7e);
1846 mThemedScrollbarThumbInactive = NS_RGB(0xce, 0xcf, 0xce);
1849 mThemedScrollbarThumbHover = ThemeColors::AdjustUnthemedScrollbarThumbColor(
1850 mThemedScrollbarThumb, dom::ElementState::HOVER);
1851 mThemedScrollbarThumbActive =
1852 ThemeColors::AdjustUnthemedScrollbarThumbColor(
1853 mThemedScrollbarThumb, dom::ElementState::ACTIVE);
1856 // The label is not added to a parent widget, but shared for constructing
1857 // different style contexts. The node hierarchy is constructed only on
1858 // the label style context.
1859 GtkWidget* labelWidget = gtk_label_new("M");
1860 g_object_ref_sink(labelWidget);
1862 // Window colors
1863 style = GetStyleContext(MOZ_GTK_WINDOW);
1865 mWindow.mFg = GetTextColor(style);
1866 mWindow.mBg = GetBackgroundColor(style, mWindow.mFg);
1868 gtk_style_context_get_border_color(style, GTK_STATE_FLAG_NORMAL, &color);
1869 mMozWindowActiveBorder = GDK_RGBA_TO_NS_RGBA(color);
1871 gtk_style_context_get_border_color(style, GTK_STATE_FLAG_INSENSITIVE, &color);
1872 mMozWindowInactiveBorder = GDK_RGBA_TO_NS_RGBA(color);
1874 style = GetStyleContext(MOZ_GTK_WINDOW_CONTAINER);
1876 GtkStyleContext* labelStyle = CreateStyleForWidget(labelWidget, style);
1877 GetSystemFontInfo(labelStyle, &mDefaultFontName, &mDefaultFontStyle);
1878 g_object_unref(labelStyle);
1881 // tooltip foreground and background
1882 style = GetStyleContext(MOZ_GTK_TOOLTIP_BOX_LABEL);
1883 mInfo.mFg = GetTextColor(style);
1884 style = GetStyleContext(MOZ_GTK_TOOLTIP);
1885 mInfo.mBg = GetBackgroundColor(style, mInfo.mFg);
1887 style = GetStyleContext(MOZ_GTK_MENUITEM);
1889 GtkStyleContext* accelStyle =
1890 CreateStyleForWidget(gtk_accel_label_new("M"), style);
1892 GetSystemFontInfo(accelStyle, &mMenuFontName, &mMenuFontStyle);
1894 gtk_style_context_get_color(accelStyle, GTK_STATE_FLAG_NORMAL, &color);
1895 mMenu.mFg = GetTextColor(accelStyle);
1896 mGrayText = GetTextColor(accelStyle, GTK_STATE_FLAG_INSENSITIVE);
1897 g_object_unref(accelStyle);
1900 const auto effectiveTitlebarStyle =
1901 HeaderBarShouldDrawContainer(MOZ_GTK_HEADER_BAR) ? MOZ_GTK_HEADERBAR_FIXED
1902 : MOZ_GTK_HEADER_BAR;
1903 style = GetStyleContext(effectiveTitlebarStyle);
1905 mTitlebar = GetColorPair(style, GTK_STATE_FLAG_NORMAL);
1906 mTitlebarInactive = GetColorPair(style, GTK_STATE_FLAG_BACKDROP);
1907 mTitlebarRadius = IsSolidCSDStyleUsed() ? 0 : GetBorderRadius(style);
1910 // We special-case the header bar color in Adwaita, Yaru and Breeze to be the
1911 // titlebar color, because it looks better and matches what apps do by
1912 // default, see bug 1838460.
1914 // We only do this in the relevant desktop environments, however, since in
1915 // other cases we don't really know if the DE's titlebars are going to match.
1917 // For breeze, additionally we read the KDE colors directly, if available,
1918 // since these are user-configurable.
1920 // For most other themes or those in unknown DEs, we use the menubar colors.
1922 // FIXME(emilio): Can we do something a bit less special-case-y?
1923 const bool shouldUseTitlebarColorsForHeaderBar = [&] {
1924 if (mFamily == ThemeFamily::Adwaita || mFamily == ThemeFamily::Yaru) {
1925 return IsGnomeDesktopEnvironment();
1927 if (mFamily == ThemeFamily::Breeze) {
1928 return IsKdeDesktopEnvironment();
1930 return false;
1931 }();
1933 if (shouldUseTitlebarColorsForHeaderBar) {
1934 mHeaderBar = mTitlebar;
1935 mHeaderBarInactive = mTitlebarInactive;
1936 if (mFamily == ThemeFamily::Breeze) {
1937 GetNamedColorPair(style, "theme_header_background_breeze",
1938 "theme_header_foreground_breeze", &mHeaderBar);
1939 GetNamedColorPair(style, "theme_header_background_backdrop_breeze",
1940 "theme_header_foreground_backdrop_breeze",
1941 &mHeaderBarInactive);
1943 } else {
1944 style = GetStyleContext(MOZ_GTK_MENUBARITEM);
1945 mHeaderBar.mFg = GetTextColor(style);
1946 mHeaderBarInactive.mFg = GetTextColor(style, GTK_STATE_FLAG_BACKDROP);
1948 style = GetStyleContext(MOZ_GTK_MENUBAR);
1949 mHeaderBar.mBg = GetBackgroundColor(style, mHeaderBar.mFg);
1950 mHeaderBarInactive.mBg = GetBackgroundColor(style, mHeaderBarInactive.mFg,
1951 GTK_STATE_FLAG_BACKDROP);
1954 style = GetStyleContext(MOZ_GTK_MENUPOPUP);
1955 mMenu.mBg = [&] {
1956 nscolor color = GetBackgroundColor(style, mMenu.mFg);
1957 if (NS_GET_A(color)) {
1958 return color;
1960 // Some themes only style menupopups with the backdrop pseudo-class. Since a
1961 // context / popup menu always seems to match that, try that before giving
1962 // up.
1963 color = GetBackgroundColor(style, mMenu.mFg, GTK_STATE_FLAG_BACKDROP);
1964 if (NS_GET_A(color)) {
1965 return color;
1967 // If we get here we couldn't figure out the right color to use. Rather than
1968 // falling back to transparent, fall back to the window background.
1969 NS_WARNING(
1970 "Couldn't find menu background color, falling back to window "
1971 "background");
1972 return mWindow.mBg;
1973 }();
1975 style = GetStyleContext(MOZ_GTK_MENUITEM);
1976 gtk_style_context_get_color(style, GTK_STATE_FLAG_PRELIGHT, &color);
1977 mMenuHover.mFg = GDK_RGBA_TO_NS_RGBA(color);
1978 mMenuHover.mBg = NS_ComposeColors(
1979 mMenu.mBg,
1980 GetBackgroundColor(style, mMenu.mFg, GTK_STATE_FLAG_PRELIGHT, mMenu.mBg));
1982 GtkWidget* parent = gtk_fixed_new();
1983 GtkWidget* window = gtk_window_new(GTK_WINDOW_POPUP);
1984 GtkWidget* treeView = gtk_tree_view_new();
1985 GtkWidget* linkButton = gtk_link_button_new("http://example.com/");
1986 GtkWidget* menuBar = gtk_menu_bar_new();
1987 GtkWidget* menuBarItem = gtk_menu_item_new();
1988 GtkWidget* entry = gtk_entry_new();
1989 GtkWidget* textView = gtk_text_view_new();
1991 gtk_container_add(GTK_CONTAINER(parent), treeView);
1992 gtk_container_add(GTK_CONTAINER(parent), linkButton);
1993 gtk_container_add(GTK_CONTAINER(parent), menuBar);
1994 gtk_menu_shell_append(GTK_MENU_SHELL(menuBar), menuBarItem);
1995 gtk_container_add(GTK_CONTAINER(window), parent);
1996 gtk_container_add(GTK_CONTAINER(parent), entry);
1997 gtk_container_add(GTK_CONTAINER(parent), textView);
1999 // Text colors
2000 GdkRGBA bgColor;
2001 // If the text window background is translucent, then the background of
2002 // the textview root node is visible.
2003 style = GetStyleContext(MOZ_GTK_TEXT_VIEW);
2004 gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL,
2005 &bgColor);
2007 style = GetStyleContext(MOZ_GTK_TEXT_VIEW_TEXT);
2008 gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color);
2009 ApplyColorOver(color, &bgColor);
2010 mField.mBg = GDK_RGBA_TO_NS_RGBA(bgColor);
2011 gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
2012 mField.mFg = GDK_RGBA_TO_NS_RGBA(color);
2014 // Selected text and background
2016 GtkStyleContext* selectionStyle =
2017 GetStyleContext(MOZ_GTK_TEXT_VIEW_TEXT_SELECTION);
2018 auto GrabSelectionColors = [&](GtkStyleContext* style) {
2019 gtk_style_context_get_background_color(
2020 style,
2021 static_cast<GtkStateFlags>(GTK_STATE_FLAG_FOCUSED |
2022 GTK_STATE_FLAG_SELECTED),
2023 &color);
2024 mSelectedText.mBg = GDK_RGBA_TO_NS_RGBA(color);
2025 gtk_style_context_get_color(
2026 style,
2027 static_cast<GtkStateFlags>(GTK_STATE_FLAG_FOCUSED |
2028 GTK_STATE_FLAG_SELECTED),
2029 &color);
2030 mSelectedText.mFg = GDK_RGBA_TO_NS_RGBA(color);
2032 GrabSelectionColors(selectionStyle);
2033 if (mSelectedText.mBg == mSelectedText.mFg) {
2034 // Some old distros/themes don't properly use the .selection style, so
2035 // fall back to the regular text view style.
2036 GrabSelectionColors(style);
2039 // Default selected item color is the selection background / foreground
2040 // colors, but we prefer named colors, as those are more general purpose
2041 // than the actual selection style, which might e.g. be too-transparent.
2043 // NOTE(emilio): It's unclear which one of the theme_selected_* or the
2044 // selected_* pairs should we prefer, in all themes that define both that
2045 // I've found, they're always the same.
2046 if (!GetNamedColorPair(style, "selected_bg_color", "selected_fg_color",
2047 &mSelectedItem) &&
2048 !GetNamedColorPair(style, "theme_selected_bg_color",
2049 "theme_selected_fg_color", &mSelectedItem)) {
2050 mSelectedItem = mSelectedText;
2053 EnsureColorPairIsOpaque(mSelectedItem);
2055 // In a similar fashion, default accent color is the selected item/text
2056 // pair, but we also prefer named colors, if available.
2058 // accent_{bg,fg}_color is not _really_ a gtk3 thing (it's a gtk4 thing),
2059 // but if gtk 3 themes want to specify these we let them, see:
2061 // https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/named-colors.html#accent-colors
2062 if (!GetNamedColorPair(style, "accent_bg_color", "accent_fg_color",
2063 &mAccent)) {
2064 mAccent = mSelectedItem;
2067 EnsureColorPairIsOpaque(mAccent);
2068 PreferDarkerBackground(mAccent);
2071 // Button text color
2072 style = GetStyleContext(MOZ_GTK_BUTTON);
2074 GtkStyleContext* labelStyle = CreateStyleForWidget(labelWidget, style);
2075 GetSystemFontInfo(labelStyle, &mButtonFontName, &mButtonFontStyle);
2076 g_object_unref(labelStyle);
2079 gtk_style_context_get_border_color(style, GTK_STATE_FLAG_NORMAL, &color);
2080 mButton.mBg = GDK_RGBA_TO_NS_RGBA(color);
2081 gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
2082 mButton.mFg = GDK_RGBA_TO_NS_RGBA(color);
2083 gtk_style_context_get_color(style, GTK_STATE_FLAG_PRELIGHT, &color);
2084 mButtonHover.mFg = GDK_RGBA_TO_NS_RGBA(color);
2085 gtk_style_context_get_color(style, GTK_STATE_FLAG_ACTIVE, &color);
2086 mButtonActiveText = GDK_RGBA_TO_NS_RGBA(color);
2087 gtk_style_context_get_background_color(style, GTK_STATE_FLAG_PRELIGHT,
2088 &color);
2089 mButtonHover.mBg = GDK_RGBA_TO_NS_RGBA(color);
2090 if (!NS_GET_A(mButtonHover.mBg)) {
2091 mButtonHover.mBg = mWindow.mBg;
2094 // Combobox text color
2095 style = GetStyleContext(MOZ_GTK_COMBOBOX_ENTRY_TEXTAREA);
2096 gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
2097 mComboBoxText = GDK_RGBA_TO_NS_RGBA(color);
2099 // GTK's guide to fancy odd row background colors:
2100 // 1) Check if a theme explicitly defines an odd row color
2101 // 2) If not, check if it defines an even row color, and darken it
2102 // slightly by a hardcoded value (gtkstyle.c)
2103 // 3) If neither are defined, take the base background color and
2104 // darken that by a hardcoded value
2105 style = GetStyleContext(MOZ_GTK_TREEVIEW);
2107 // Get odd row background color
2108 gtk_style_context_save(style);
2109 gtk_style_context_add_region(style, GTK_STYLE_REGION_ROW, GTK_REGION_ODD);
2110 gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color);
2111 mOddCellBackground = GDK_RGBA_TO_NS_RGBA(color);
2112 gtk_style_context_restore(style);
2114 // Column header colors
2115 style = GetStyleContext(MOZ_GTK_TREE_HEADER_CELL);
2116 gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
2117 mMozColHeaderText = GDK_RGBA_TO_NS_RGBA(color);
2118 gtk_style_context_get_color(style, GTK_STATE_FLAG_PRELIGHT, &color);
2119 mMozColHeaderHoverText = GDK_RGBA_TO_NS_RGBA(color);
2121 // Compute cell highlight colors
2122 InitCellHighlightColors();
2124 // GtkFrame has a "border" subnode on which Adwaita draws the border.
2125 // Some themes do not draw on this node but draw a border on the widget
2126 // root node, so check the root node if no border is found on the border
2127 // node.
2128 style = GetStyleContext(MOZ_GTK_FRAME_BORDER);
2129 bool themeUsesColors =
2130 GetBorderColors(style, &mFrameOuterLightBorder, &mFrameInnerDarkBorder);
2131 if (!themeUsesColors) {
2132 style = GetStyleContext(MOZ_GTK_FRAME);
2133 GetBorderColors(style, &mFrameOuterLightBorder, &mFrameInnerDarkBorder);
2136 // Some themes have a unified menu bar, and support window dragging on it
2137 gboolean supports_menubar_drag = FALSE;
2138 GParamSpec* param_spec = gtk_widget_class_find_style_property(
2139 GTK_WIDGET_GET_CLASS(menuBar), "window-dragging");
2140 if (param_spec) {
2141 if (g_type_is_a(G_PARAM_SPEC_VALUE_TYPE(param_spec), G_TYPE_BOOLEAN)) {
2142 gtk_widget_style_get(menuBar, "window-dragging", &supports_menubar_drag,
2143 nullptr);
2146 mMenuSupportsDrag = supports_menubar_drag;
2148 // TODO: It returns wrong color for themes which
2149 // sets link color for GtkLabel only as we query
2150 // GtkLinkButton style here.
2151 style = gtk_widget_get_style_context(linkButton);
2152 gtk_style_context_get_color(style, GTK_STATE_FLAG_LINK, &color);
2153 mNativeHyperLinkText = GDK_RGBA_TO_NS_RGBA(color);
2155 gtk_style_context_get_color(style, GTK_STATE_FLAG_VISITED, &color);
2156 mNativeVisitedHyperLinkText = GDK_RGBA_TO_NS_RGBA(color);
2158 // invisible character styles
2159 guint value;
2160 g_object_get(entry, "invisible-char", &value, nullptr);
2161 mInvisibleCharacter = char16_t(value);
2163 // caret styles
2164 gtk_widget_style_get(entry, "cursor-aspect-ratio", &mCaretRatio, nullptr);
2166 GetSystemFontInfo(gtk_widget_get_style_context(entry), &mFieldFontName,
2167 &mFieldFontStyle);
2169 gtk_widget_destroy(window);
2170 g_object_unref(labelWidget);
2172 if (LOGLNF_ENABLED()) {
2173 LOGLNF("Initialized theme %s (%d)\n", mName.get(), mPreferDarkTheme);
2174 for (auto id : MakeEnumeratedRange(ColorID::End)) {
2175 nscolor color;
2176 nsresult rv = GetColor(id, color);
2177 LOGLNF(" * color %d: pref=%s success=%d value=%x\n", int(id),
2178 GetColorPrefName(id), NS_SUCCEEDED(rv),
2179 NS_SUCCEEDED(rv) ? color : 0);
2181 LOGLNF(" * titlebar-radius: %d\n", mTitlebarRadius);
2185 // virtual
2186 char16_t nsLookAndFeel::GetPasswordCharacterImpl() {
2187 EnsureInit();
2188 return mSystemTheme.mInvisibleCharacter;
2191 bool nsLookAndFeel::GetEchoPasswordImpl() { return false; }
2193 bool nsLookAndFeel::GetDefaultDrawInTitlebar() { return sCSDAvailable; }
2195 void nsLookAndFeel::GetThemeInfo(nsACString& aInfo) {
2196 aInfo.Append(mSystemTheme.mName);
2197 aInfo.Append(" / ");
2198 aInfo.Append(mAltTheme.mName);
2201 bool nsLookAndFeel::WidgetUsesImage(WidgetNodeType aNodeType) {
2202 static constexpr GtkStateFlags sFlagsToCheck[]{
2203 GTK_STATE_FLAG_NORMAL, GTK_STATE_FLAG_PRELIGHT,
2204 GtkStateFlags(GTK_STATE_FLAG_PRELIGHT | GTK_STATE_FLAG_ACTIVE),
2205 GTK_STATE_FLAG_BACKDROP, GTK_STATE_FLAG_INSENSITIVE};
2207 GtkStyleContext* style = GetStyleContext(aNodeType);
2209 GValue value = G_VALUE_INIT;
2210 for (GtkStateFlags state : sFlagsToCheck) {
2211 gtk_style_context_get_property(style, "background-image", state, &value);
2212 bool hasPattern = G_VALUE_TYPE(&value) == CAIRO_GOBJECT_TYPE_PATTERN &&
2213 g_value_get_boxed(&value);
2214 g_value_unset(&value);
2215 if (hasPattern) {
2216 return true;
2219 return false;
2222 void nsLookAndFeel::RecordLookAndFeelSpecificTelemetry() {
2223 // Gtk version we're on.
2224 nsString version;
2225 version.AppendPrintf("%d.%d", gtk_major_version, gtk_minor_version);
2226 Telemetry::ScalarSet(Telemetry::ScalarID::WIDGET_GTK_VERSION, version);
2228 // Whether the current Gtk theme has scrollbar buttons.
2229 bool hasScrollbarButtons =
2230 GetInt(LookAndFeel::IntID::ScrollArrowStyle) != eScrollArrow_None;
2231 mozilla::Telemetry::ScalarSet(
2232 mozilla::Telemetry::ScalarID::WIDGET_GTK_THEME_HAS_SCROLLBAR_BUTTONS,
2233 hasScrollbarButtons);
2235 // Whether the current Gtk theme uses something other than a solid color
2236 // background for scrollbar parts.
2237 bool scrollbarUsesImage = !ShouldHonorThemeScrollbarColors();
2238 mozilla::Telemetry::ScalarSet(
2239 mozilla::Telemetry::ScalarID::WIDGET_GTK_THEME_SCROLLBAR_USES_IMAGES,
2240 scrollbarUsesImage);
2243 bool nsLookAndFeel::ShouldHonorThemeScrollbarColors() {
2244 // If the Gtk theme uses anything other than solid color backgrounds for Gtk
2245 // scrollbar parts, this is a good indication that painting XUL scrollbar part
2246 // elements using colors extracted from the theme won't provide good results.
2247 return !WidgetUsesImage(MOZ_GTK_SCROLLBAR_VERTICAL) &&
2248 !WidgetUsesImage(MOZ_GTK_SCROLLBAR_CONTENTS_VERTICAL) &&
2249 !WidgetUsesImage(MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL) &&
2250 !WidgetUsesImage(MOZ_GTK_SCROLLBAR_THUMB_VERTICAL);
2253 #undef LOGLNF
2254 #undef LOGLNF_ENABLED