Bug 1751217 Part 3: Make HDR-capable macOS screens report 30 pixelDepth. r=mstange
[gecko.git] / widget / gtk / nsLookAndFeel.cpp
blobaca722ff0815590fcbae6dded50f1fdcb5184d0b
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 "nsStyleConsts.h"
40 #include "gfxFontConstants.h"
41 #include "WidgetUtils.h"
42 #include "nsWindow.h"
44 #include "mozilla/gfx/2D.h"
46 #include <cairo-gobject.h>
47 #include "WidgetStyleCache.h"
48 #include "prenv.h"
49 #include "nsCSSColorUtils.h"
51 using namespace mozilla;
52 using namespace mozilla::widget;
54 #ifdef MOZ_LOGGING
55 # include "mozilla/Logging.h"
56 # include "nsTArray.h"
57 # include "Units.h"
58 static LazyLogModule gLnfLog("LookAndFeel");
59 # define LOGLNF(...) MOZ_LOG(gLnfLog, LogLevel::Debug, (__VA_ARGS__))
60 # define LOGLNF_ENABLED() MOZ_LOG_TEST(gLnfLog, LogLevel::Debug)
61 #else
62 # define LOGLNF(args)
63 # define LOGLNF_ENABLED() false
64 #endif /* MOZ_LOGGING */
66 #define GDK_COLOR_TO_NS_RGB(c) \
67 ((nscolor)NS_RGB(c.red >> 8, c.green >> 8, c.blue >> 8))
68 #define GDK_RGBA_TO_NS_RGBA(c) \
69 ((nscolor)NS_RGBA((int)((c).red * 255), (int)((c).green * 255), \
70 (int)((c).blue * 255), (int)((c).alpha * 255)))
72 static bool sIgnoreChangedSettings = false;
74 static void OnSettingsChange() {
75 if (sIgnoreChangedSettings) {
76 return;
78 // TODO: We could be more granular here, but for now assume everything
79 // changed.
80 LookAndFeel::NotifyChangedAllWindows(widget::ThemeChangeKind::StyleAndLayout);
81 widget::IMContextWrapper::OnThemeChanged();
84 static void settings_changed_cb(GtkSettings*, GParamSpec*, void*) {
85 OnSettingsChange();
88 static bool sCSDAvailable;
90 static nsCString GVariantToString(GVariant* aVariant) {
91 nsCString ret;
92 gchar* s = g_variant_print(aVariant, TRUE);
93 if (s) {
94 ret.Assign(s);
95 g_free(s);
97 return ret;
100 static nsDependentCString GVariantGetString(GVariant* aVariant) {
101 gsize len = 0;
102 const gchar* v = g_variant_get_string(aVariant, &len);
103 return nsDependentCString(v, len);
106 // Observed settings for portal.
107 static constexpr struct {
108 nsLiteralCString mNamespace;
109 nsLiteralCString mKey;
110 } kObservedSettings[] = {
111 {"org.freedesktop.appearance"_ns, "color-scheme"_ns},
114 static void settings_changed_signal_cb(GDBusProxy* proxy, gchar* sender_name,
115 gchar* signal_name, GVariant* parameters,
116 gpointer user_data) {
117 LOGLNF("Settings Change sender=%s signal=%s params=%s\n", sender_name,
118 signal_name, GVariantToString(parameters).get());
119 if (strcmp(signal_name, "SettingChanged")) {
120 NS_WARNING("Unknown change signal for settings");
121 return;
123 RefPtr<GVariant> ns = dont_AddRef(g_variant_get_child_value(parameters, 0));
124 RefPtr<GVariant> key = dont_AddRef(g_variant_get_child_value(parameters, 1));
125 // Third parameter is the value, but we don't care about it.
126 if (!ns || !key || !g_variant_is_of_type(ns, G_VARIANT_TYPE_STRING) ||
127 !g_variant_is_of_type(key, G_VARIANT_TYPE_STRING)) {
128 MOZ_ASSERT(false, "Unexpected setting change signal parameters");
129 return;
132 auto nsStr = GVariantGetString(ns);
133 auto keyStr = GVariantGetString(key);
134 for (const auto& setting : kObservedSettings) {
135 if (setting.mNamespace.Equals(nsStr) && setting.mKey.Equals(keyStr)) {
136 OnSettingsChange();
137 return;
142 nsLookAndFeel::nsLookAndFeel() {
143 static constexpr nsLiteralCString kObservedSettings[] = {
144 // Affects system font sizes.
145 "notify::gtk-xft-dpi"_ns,
146 // Affects mSystemTheme and mAltTheme as expected.
147 "notify::gtk-theme-name"_ns,
148 // System fonts?
149 "notify::gtk-font-name"_ns,
150 // prefers-reduced-motion
151 "notify::gtk-enable-animations"_ns,
152 // CSD media queries, etc.
153 "notify::gtk-decoration-layout"_ns,
154 // Text resolution affects system font and widget sizes.
155 "notify::resolution"_ns,
156 // These three Affect mCaretBlinkTime
157 "notify::gtk-cursor-blink"_ns,
158 "notify::gtk-cursor-blink-time"_ns,
159 "notify::gtk-cursor-blink-timeout"_ns,
160 // Affects SelectTextfieldsOnKeyFocus
161 "notify::gtk-entry-select-on-focus"_ns,
162 // Affects ScrollToClick
163 "notify::gtk-primary-button-warps-slider"_ns,
164 // Affects SubmenuDelay
165 "notify::gtk-menu-popup-delay"_ns,
166 // Affects DragThresholdX/Y
167 "notify::gtk-dnd-drag-threshold"_ns,
170 GtkSettings* settings = gtk_settings_get_default();
171 for (const auto& setting : kObservedSettings) {
172 g_signal_connect_after(settings, setting.get(),
173 G_CALLBACK(settings_changed_cb), nullptr);
176 sCSDAvailable =
177 nsWindow::GetSystemGtkWindowDecoration() != nsWindow::GTK_DECORATION_NONE;
179 if (ShouldUsePortal(PortalKind::Settings)) {
180 GUniquePtr<GError> error;
181 mDBusSettingsProxy = dont_AddRef(g_dbus_proxy_new_for_bus_sync(
182 G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE, nullptr,
183 "org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop",
184 "org.freedesktop.portal.Settings", nullptr, getter_Transfers(error)));
185 if (mDBusSettingsProxy) {
186 g_signal_connect(mDBusSettingsProxy, "g-signal",
187 G_CALLBACK(settings_changed_signal_cb), nullptr);
188 } else {
189 LOGLNF("Can't create DBus proxy for settings: %s\n", error->message);
194 nsLookAndFeel::~nsLookAndFeel() {
195 if (mDBusSettingsProxy) {
196 g_signal_handlers_disconnect_by_func(
197 mDBusSettingsProxy, FuncToGpointer(settings_changed_signal_cb),
198 nullptr);
199 mDBusSettingsProxy = nullptr;
201 g_signal_handlers_disconnect_by_func(
202 gtk_settings_get_default(), FuncToGpointer(settings_changed_cb), nullptr);
205 #if 0
206 static void DumpStyleContext(GtkStyleContext* aStyle) {
207 static auto sGtkStyleContextToString =
208 reinterpret_cast<char* (*)(GtkStyleContext*, gint)>(
209 dlsym(RTLD_DEFAULT, "gtk_style_context_to_string"));
210 char* str = sGtkStyleContextToString(aStyle, ~0);
211 printf("%s\n", str);
212 g_free(str);
213 str = gtk_widget_path_to_string(gtk_style_context_get_path(aStyle));
214 printf("%s\n", str);
215 g_free(str);
217 #endif
219 // Modifies color |*aDest| as if a pattern of color |aSource| was painted with
220 // CAIRO_OPERATOR_OVER to a surface with color |*aDest|.
221 static void ApplyColorOver(const GdkRGBA& aSource, GdkRGBA* aDest) {
222 gdouble sourceCoef = aSource.alpha;
223 gdouble destCoef = aDest->alpha * (1.0 - sourceCoef);
224 gdouble resultAlpha = sourceCoef + destCoef;
225 if (resultAlpha != 0.0) { // don't divide by zero
226 destCoef /= resultAlpha;
227 sourceCoef /= resultAlpha;
228 aDest->red = sourceCoef * aSource.red + destCoef * aDest->red;
229 aDest->green = sourceCoef * aSource.green + destCoef * aDest->green;
230 aDest->blue = sourceCoef * aSource.blue + destCoef * aDest->blue;
231 aDest->alpha = resultAlpha;
235 static void GetLightAndDarkness(const GdkRGBA& aColor, double* aLightness,
236 double* aDarkness) {
237 double sum = aColor.red + aColor.green + aColor.blue;
238 *aLightness = sum * aColor.alpha;
239 *aDarkness = (3.0 - sum) * aColor.alpha;
242 static bool GetGradientColors(const GValue* aValue, GdkRGBA* aLightColor,
243 GdkRGBA* aDarkColor) {
244 if (!G_TYPE_CHECK_VALUE_TYPE(aValue, CAIRO_GOBJECT_TYPE_PATTERN)) {
245 return false;
248 auto pattern = static_cast<cairo_pattern_t*>(g_value_get_boxed(aValue));
249 if (!pattern) {
250 return false;
253 // Just picking the lightest and darkest colors as simple samples rather
254 // than trying to blend, which could get messy if there are many stops.
255 if (CAIRO_STATUS_SUCCESS !=
256 cairo_pattern_get_color_stop_rgba(pattern, 0, nullptr, &aDarkColor->red,
257 &aDarkColor->green, &aDarkColor->blue,
258 &aDarkColor->alpha)) {
259 return false;
262 double maxLightness, maxDarkness;
263 GetLightAndDarkness(*aDarkColor, &maxLightness, &maxDarkness);
264 *aLightColor = *aDarkColor;
266 GdkRGBA stop;
267 for (int index = 1;
268 CAIRO_STATUS_SUCCESS ==
269 cairo_pattern_get_color_stop_rgba(pattern, index, nullptr, &stop.red,
270 &stop.green, &stop.blue, &stop.alpha);
271 ++index) {
272 double lightness, darkness;
273 GetLightAndDarkness(stop, &lightness, &darkness);
274 if (lightness > maxLightness) {
275 maxLightness = lightness;
276 *aLightColor = stop;
278 if (darkness > maxDarkness) {
279 maxDarkness = darkness;
280 *aDarkColor = stop;
284 return true;
287 static bool GetColorFromImagePattern(const GValue* aValue, nscolor* aColor) {
288 if (!G_TYPE_CHECK_VALUE_TYPE(aValue, CAIRO_GOBJECT_TYPE_PATTERN)) {
289 return false;
292 auto pattern = static_cast<cairo_pattern_t*>(g_value_get_boxed(aValue));
293 if (!pattern) {
294 return false;
297 cairo_surface_t* surface;
298 if (cairo_pattern_get_surface(pattern, &surface) != CAIRO_STATUS_SUCCESS) {
299 return false;
302 cairo_format_t format = cairo_image_surface_get_format(surface);
303 if (format == CAIRO_FORMAT_INVALID) {
304 return false;
306 int width = cairo_image_surface_get_width(surface);
307 int height = cairo_image_surface_get_height(surface);
308 int stride = cairo_image_surface_get_stride(surface);
309 if (!width || !height) {
310 return false;
313 // Guesstimate the central pixel would have a sensible color.
314 int x = width / 2;
315 int y = height / 2;
317 unsigned char* data = cairo_image_surface_get_data(surface);
318 switch (format) {
319 // Most (all?) GTK images / patterns / etc use ARGB32.
320 case CAIRO_FORMAT_ARGB32: {
321 size_t offset = x * 4 + y * stride;
322 uint32_t* pixel = reinterpret_cast<uint32_t*>(data + offset);
323 *aColor = gfx::sRGBColor::UnusualFromARGB(*pixel).ToABGR();
324 return true;
326 default:
327 break;
330 return false;
333 static bool GetUnicoBorderGradientColors(GtkStyleContext* aContext,
334 GdkRGBA* aLightColor,
335 GdkRGBA* aDarkColor) {
336 // Ubuntu 12.04 has GTK engine Unico-1.0.2, which overrides render_frame,
337 // providing its own border code. Ubuntu 14.04 has
338 // Unico-1.0.3+14.04.20140109, which does not override render_frame, and
339 // so does not need special attention. The earlier Unico can be detected
340 // by the -unico-border-gradient style property it registers.
341 // gtk_style_properties_lookup_property() is checked first to avoid the
342 // warning from gtk_style_context_get_property() when the property does
343 // not exist. (gtk_render_frame() of GTK+ 3.16 no longer uses the
344 // engine.)
345 const char* propertyName = "-unico-border-gradient";
346 if (!gtk_style_properties_lookup_property(propertyName, nullptr, nullptr))
347 return false;
349 // -unico-border-gradient is used only when the CSS node's engine is Unico.
350 GtkThemingEngine* engine;
351 GtkStateFlags state = gtk_style_context_get_state(aContext);
352 gtk_style_context_get(aContext, state, "engine", &engine, nullptr);
353 if (strcmp(g_type_name(G_TYPE_FROM_INSTANCE(engine)), "UnicoEngine") != 0)
354 return false;
356 // draw_border() of Unico engine uses -unico-border-gradient
357 // in preference to border-color.
358 GValue value = G_VALUE_INIT;
359 gtk_style_context_get_property(aContext, propertyName, state, &value);
361 bool result = GetGradientColors(&value, aLightColor, aDarkColor);
363 g_value_unset(&value);
364 return result;
367 // Sets |aLightColor| and |aDarkColor| to colors from |aContext|. Returns
368 // true if |aContext| uses these colors to render a visible border.
369 // If returning false, then the colors returned are a fallback from the
370 // border-color value even though |aContext| does not use these colors to
371 // render a border.
372 static bool GetBorderColors(GtkStyleContext* aContext, GdkRGBA* aLightColor,
373 GdkRGBA* aDarkColor) {
374 // Determine whether the border on this style context is visible.
375 GtkStateFlags state = gtk_style_context_get_state(aContext);
376 GtkBorderStyle borderStyle;
377 gtk_style_context_get(aContext, state, GTK_STYLE_PROPERTY_BORDER_STYLE,
378 &borderStyle, nullptr);
379 bool visible = borderStyle != GTK_BORDER_STYLE_NONE &&
380 borderStyle != GTK_BORDER_STYLE_HIDDEN;
381 if (visible) {
382 // GTK has an initial value of zero for border-widths, and so themes
383 // need to explicitly set border-widths to make borders visible.
384 GtkBorder border;
385 gtk_style_context_get_border(aContext, state, &border);
386 visible = border.top != 0 || border.right != 0 || border.bottom != 0 ||
387 border.left != 0;
390 if (visible &&
391 GetUnicoBorderGradientColors(aContext, aLightColor, aDarkColor))
392 return true;
394 // The initial value for the border-color is the foreground color, and so
395 // this will usually return a color distinct from the background even if
396 // there is no visible border detected.
397 gtk_style_context_get_border_color(aContext, state, aDarkColor);
398 // TODO GTK3 - update aLightColor
399 // for GTK_BORDER_STYLE_INSET/OUTSET/GROVE/RIDGE border styles.
400 // https://bugzilla.mozilla.org/show_bug.cgi?id=978172#c25
401 *aLightColor = *aDarkColor;
402 return visible;
405 static bool GetBorderColors(GtkStyleContext* aContext, nscolor* aLightColor,
406 nscolor* aDarkColor) {
407 GdkRGBA lightColor, darkColor;
408 bool ret = GetBorderColors(aContext, &lightColor, &darkColor);
409 *aLightColor = GDK_RGBA_TO_NS_RGBA(lightColor);
410 *aDarkColor = GDK_RGBA_TO_NS_RGBA(darkColor);
411 return ret;
414 // Finds ideal cell highlight colors used for unfocused+selected cells distinct
415 // from both Highlight, used as focused+selected background, and the listbox
416 // background which is assumed to be similar to -moz-field
417 void nsLookAndFeel::PerThemeData::InitCellHighlightColors() {
418 int32_t minLuminosityDifference = NS_SUFFICIENT_LUMINOSITY_DIFFERENCE_BG;
419 int32_t backLuminosityDifference =
420 NS_LUMINOSITY_DIFFERENCE(mMozWindowBackground, mFieldBackground);
421 if (backLuminosityDifference >= minLuminosityDifference) {
422 mMozCellHighlightBackground = mMozWindowBackground;
423 mMozCellHighlightText = mMozWindowText;
424 return;
427 uint16_t hue, sat, luminance;
428 uint8_t alpha;
429 mMozCellHighlightBackground = mFieldBackground;
430 mMozCellHighlightText = mFieldText;
432 NS_RGB2HSV(mMozCellHighlightBackground, hue, sat, luminance, alpha);
434 uint16_t step = 30;
435 // Lighten the color if the color is very dark
436 if (luminance <= step) {
437 luminance += step;
439 // Darken it if it is very light
440 else if (luminance >= 255 - step) {
441 luminance -= step;
443 // Otherwise, compute what works best depending on the text luminance.
444 else {
445 uint16_t textHue, textSat, textLuminance;
446 uint8_t textAlpha;
447 NS_RGB2HSV(mMozCellHighlightText, textHue, textSat, textLuminance,
448 textAlpha);
449 // Text is darker than background, use a lighter shade
450 if (textLuminance < luminance) {
451 luminance += step;
453 // Otherwise, use a darker shade
454 else {
455 luminance -= step;
458 NS_HSV2RGB(mMozCellHighlightBackground, hue, sat, luminance, alpha);
461 void nsLookAndFeel::NativeInit() { EnsureInit(); }
463 void nsLookAndFeel::RefreshImpl() {
464 mInitialized = false;
465 moz_gtk_refresh();
467 nsXPLookAndFeel::RefreshImpl();
470 nsresult nsLookAndFeel::NativeGetColor(ColorID aID, ColorScheme aScheme,
471 nscolor& aColor) {
472 EnsureInit();
474 auto& theme = aScheme == ColorScheme::Light ? LightTheme() : DarkTheme();
475 return theme.GetColor(aID, aColor);
478 static bool ShouldUseColorForActiveDarkScrollbarThumb(nscolor aColor) {
479 auto IsDifferentEnough = [](int32_t aChannel, int32_t aOtherChannel) {
480 return std::abs(aChannel - aOtherChannel) > 10;
482 return IsDifferentEnough(NS_GET_R(aColor), NS_GET_G(aColor)) ||
483 IsDifferentEnough(NS_GET_R(aColor), NS_GET_B(aColor));
486 static bool ShouldUseThemedScrollbarColor(StyleSystemColor aID, nscolor aColor,
487 bool aIsDark) {
488 if (!aIsDark) {
489 return true;
491 if (StaticPrefs::widget_non_native_theme_scrollbar_dark_themed()) {
492 return true;
494 return aID == StyleSystemColor::ThemedScrollbarThumbActive &&
495 StaticPrefs::widget_non_native_theme_scrollbar_active_always_themed();
498 nsresult nsLookAndFeel::PerThemeData::GetColor(ColorID aID,
499 nscolor& aColor) const {
500 nsresult res = NS_OK;
502 switch (aID) {
503 // These colors don't seem to be used for anything anymore in Mozilla
504 // The CSS2 colors below are used.
505 case ColorID::Appworkspace: // MDI background color
506 case ColorID::Background: // desktop background
507 case ColorID::Window:
508 case ColorID::Windowframe:
509 case ColorID::MozDialog:
510 case ColorID::MozCombobox:
511 aColor = mMozWindowBackground;
512 break;
513 case ColorID::Windowtext:
514 case ColorID::MozDialogtext:
515 aColor = mMozWindowText;
516 break;
517 case ColorID::IMESelectedRawTextBackground:
518 case ColorID::IMESelectedConvertedTextBackground:
519 case ColorID::MozDragtargetzone:
520 case ColorID::Highlight: // preference selected item,
521 aColor = mTextSelectedBackground;
522 break;
523 case ColorID::Highlighttext:
524 if (NS_GET_A(mTextSelectedBackground) < 155) {
525 aColor = NS_SAME_AS_FOREGROUND_COLOR;
526 break;
528 [[fallthrough]];
529 case ColorID::IMESelectedRawTextForeground:
530 case ColorID::IMESelectedConvertedTextForeground:
531 aColor = mTextSelectedText;
532 break;
533 case ColorID::Selecteditem:
534 case ColorID::MozAccentColor:
535 aColor = mAccentColor;
536 break;
537 case ColorID::Selecteditemtext:
538 case ColorID::MozAccentColorForeground:
539 aColor = mAccentColorForeground;
540 break;
541 case ColorID::MozCellhighlight:
542 aColor = mMozCellHighlightBackground;
543 break;
544 case ColorID::MozCellhighlighttext:
545 aColor = mMozCellHighlightText;
546 break;
547 case ColorID::IMERawInputBackground:
548 case ColorID::IMEConvertedTextBackground:
549 aColor = NS_TRANSPARENT;
550 break;
551 case ColorID::IMERawInputForeground:
552 case ColorID::IMEConvertedTextForeground:
553 aColor = NS_SAME_AS_FOREGROUND_COLOR;
554 break;
555 case ColorID::IMERawInputUnderline:
556 case ColorID::IMEConvertedTextUnderline:
557 aColor = NS_SAME_AS_FOREGROUND_COLOR;
558 break;
559 case ColorID::IMESelectedRawTextUnderline:
560 case ColorID::IMESelectedConvertedTextUnderline:
561 aColor = NS_TRANSPARENT;
562 break;
563 case ColorID::SpellCheckerUnderline:
564 aColor = NS_RGB(0xff, 0, 0);
565 break;
566 case ColorID::Scrollbar:
567 aColor = mThemedScrollbar;
568 break;
569 case ColorID::ThemedScrollbar:
570 aColor = mThemedScrollbar;
571 if (!ShouldUseThemedScrollbarColor(aID, aColor, mIsDark)) {
572 return NS_ERROR_FAILURE;
574 break;
575 case ColorID::ThemedScrollbarInactive:
576 aColor = mThemedScrollbarInactive;
577 if (!ShouldUseThemedScrollbarColor(aID, aColor, mIsDark)) {
578 return NS_ERROR_FAILURE;
580 break;
581 case ColorID::ThemedScrollbarThumb:
582 aColor = mThemedScrollbarThumb;
583 if (!ShouldUseThemedScrollbarColor(aID, aColor, mIsDark)) {
584 return NS_ERROR_FAILURE;
586 break;
587 case ColorID::ThemedScrollbarThumbHover:
588 aColor = mThemedScrollbarThumbHover;
589 if (!ShouldUseThemedScrollbarColor(aID, aColor, mIsDark)) {
590 return NS_ERROR_FAILURE;
592 break;
593 case ColorID::ThemedScrollbarThumbActive:
594 aColor = mThemedScrollbarThumbActive;
595 if (!ShouldUseThemedScrollbarColor(aID, aColor, mIsDark)) {
596 return NS_ERROR_FAILURE;
598 break;
599 case ColorID::ThemedScrollbarThumbInactive:
600 aColor = mThemedScrollbarThumbInactive;
601 if (!ShouldUseThemedScrollbarColor(aID, aColor, mIsDark)) {
602 return NS_ERROR_FAILURE;
604 break;
606 // css2 http://www.w3.org/TR/REC-CSS2/ui.html#system-colors
607 case ColorID::Activeborder:
608 // active window border
609 aColor = mMozWindowActiveBorder;
610 break;
611 case ColorID::Inactiveborder:
612 // inactive window border
613 aColor = mMozWindowInactiveBorder;
614 break;
615 case ColorID::Graytext: // disabled text in windows, menus, etc.
616 aColor = mMenuTextInactive;
617 break;
618 case ColorID::Activecaption:
619 aColor = mTitlebarBackground;
620 break;
621 case ColorID::Captiontext: // text in active window caption (titlebar)
622 aColor = mTitlebarText;
623 break;
624 case ColorID::Inactivecaption:
625 // inactive window caption
626 aColor = mTitlebarInactiveBackground;
627 break;
628 case ColorID::Inactivecaptiontext: // text in active window caption
629 // (titlebar)
630 aColor = mTitlebarInactiveText;
631 break;
632 case ColorID::Infobackground:
633 // tooltip background color
634 aColor = mInfoBackground;
635 break;
636 case ColorID::Infotext:
637 // tooltip text color
638 aColor = mInfoText;
639 break;
640 case ColorID::Menu:
641 // menu background
642 aColor = mMenuBackground;
643 break;
644 case ColorID::Menutext:
645 // menu text
646 aColor = mMenuText;
647 break;
648 case ColorID::Threedface:
649 case ColorID::Buttonface:
650 case ColorID::MozButtondisabledface:
651 // 3-D face color
652 aColor = mMozWindowBackground;
653 break;
655 case ColorID::Buttontext:
656 // text on push buttons
657 aColor = mButtonText;
658 break;
660 case ColorID::Buttonhighlight:
661 // 3-D highlighted edge color
662 case ColorID::Threedhighlight:
663 // 3-D highlighted outer edge color
664 aColor = mFrameOuterLightBorder;
665 break;
667 case ColorID::Buttonshadow:
668 // 3-D shadow edge color
669 case ColorID::Threedshadow:
670 // 3-D shadow inner edge color
671 aColor = mFrameInnerDarkBorder;
672 break;
674 case ColorID::Threedlightshadow:
675 case ColorID::MozDisabledfield:
676 aColor = NS_RGB(0xE0, 0xE0, 0xE0);
677 break;
678 case ColorID::Threeddarkshadow:
679 aColor = NS_RGB(0xDC, 0xDC, 0xDC);
680 break;
682 case ColorID::MozEventreerow:
683 case ColorID::Field:
684 aColor = mFieldBackground;
685 break;
686 case ColorID::Fieldtext:
687 aColor = mFieldText;
688 break;
689 case ColorID::MozButtondefault:
690 // default button border color
691 aColor = mButtonDefault;
692 break;
693 case ColorID::MozButtonhoverface:
694 case ColorID::MozButtonactiveface:
695 aColor = mButtonHoverFace;
696 break;
697 case ColorID::MozButtonhovertext:
698 aColor = mButtonHoverText;
699 break;
700 case ColorID::MozButtonactivetext:
701 aColor = mButtonActiveText;
702 break;
703 case ColorID::MozMenuhover:
704 aColor = mMenuHover;
705 break;
706 case ColorID::MozMenuhovertext:
707 aColor = mMenuHoverText;
708 break;
709 case ColorID::MozOddtreerow:
710 aColor = mOddCellBackground;
711 break;
712 case ColorID::MozNativehyperlinktext:
713 aColor = mNativeHyperLinkText;
714 break;
715 case ColorID::MozNativevisitedhyperlinktext:
716 aColor = mNativeVisitedHyperLinkText;
717 break;
718 case ColorID::MozComboboxtext:
719 aColor = mComboBoxText;
720 break;
721 case ColorID::MozMenubartext:
722 aColor = mMenuBarText;
723 break;
724 case ColorID::MozMenubarhovertext:
725 aColor = mMenuBarHoverText;
726 break;
727 case ColorID::MozColheadertext:
728 aColor = mMozColHeaderText;
729 break;
730 case ColorID::MozColheaderhovertext:
731 aColor = mMozColHeaderHoverText;
732 break;
733 default:
734 /* default color is BLACK */
735 aColor = 0;
736 res = NS_ERROR_FAILURE;
737 break;
740 return res;
743 static int32_t CheckWidgetStyle(GtkWidget* aWidget, const char* aStyle,
744 int32_t aResult) {
745 gboolean value = FALSE;
746 gtk_widget_style_get(aWidget, aStyle, &value, nullptr);
747 return value ? aResult : 0;
750 static int32_t ConvertGTKStepperStyleToMozillaScrollArrowStyle(
751 GtkWidget* aWidget) {
752 if (!aWidget) return mozilla::LookAndFeel::eScrollArrowStyle_Single;
754 return CheckWidgetStyle(aWidget, "has-backward-stepper",
755 mozilla::LookAndFeel::eScrollArrow_StartBackward) |
756 CheckWidgetStyle(aWidget, "has-forward-stepper",
757 mozilla::LookAndFeel::eScrollArrow_EndForward) |
758 CheckWidgetStyle(aWidget, "has-secondary-backward-stepper",
759 mozilla::LookAndFeel::eScrollArrow_EndBackward) |
760 CheckWidgetStyle(aWidget, "has-secondary-forward-stepper",
761 mozilla::LookAndFeel::eScrollArrow_StartForward);
764 nsresult nsLookAndFeel::NativeGetInt(IntID aID, int32_t& aResult) {
765 nsresult res = NS_OK;
767 // We use delayed initialization by EnsureInit() here
768 // to make sure mozilla::Preferences is available (Bug 115807).
769 // IntID::UseAccessibilityTheme is requested before user preferences
770 // are read, and so EnsureInit(), which depends on preference values,
771 // is deliberately delayed until required.
772 switch (aID) {
773 case IntID::ScrollButtonLeftMouseButtonAction:
774 aResult = 0;
775 break;
776 case IntID::ScrollButtonMiddleMouseButtonAction:
777 aResult = 1;
778 break;
779 case IntID::ScrollButtonRightMouseButtonAction:
780 aResult = 2;
781 break;
782 case IntID::CaretBlinkTime:
783 EnsureInit();
784 aResult = mCaretBlinkTime;
785 break;
786 case IntID::CaretBlinkCount:
787 EnsureInit();
788 aResult = mCaretBlinkCount;
789 break;
790 case IntID::CaretWidth:
791 aResult = 1;
792 break;
793 case IntID::ShowCaretDuringSelection:
794 aResult = 0;
795 break;
796 case IntID::SelectTextfieldsOnKeyFocus: {
797 GtkSettings* settings;
798 gboolean select_on_focus;
800 settings = gtk_settings_get_default();
801 g_object_get(settings, "gtk-entry-select-on-focus", &select_on_focus,
802 nullptr);
804 if (select_on_focus)
805 aResult = 1;
806 else
807 aResult = 0;
809 } break;
810 case IntID::ScrollToClick: {
811 GtkSettings* settings;
812 gboolean warps_slider = FALSE;
814 settings = gtk_settings_get_default();
815 if (g_object_class_find_property(G_OBJECT_GET_CLASS(settings),
816 "gtk-primary-button-warps-slider")) {
817 g_object_get(settings, "gtk-primary-button-warps-slider", &warps_slider,
818 nullptr);
821 if (warps_slider)
822 aResult = 1;
823 else
824 aResult = 0;
825 } break;
826 case IntID::SubmenuDelay: {
827 GtkSettings* settings;
828 gint delay;
830 settings = gtk_settings_get_default();
831 g_object_get(settings, "gtk-menu-popup-delay", &delay, nullptr);
832 aResult = (int32_t)delay;
833 break;
835 case IntID::TooltipDelay: {
836 aResult = 500;
837 break;
839 case IntID::MenusCanOverlapOSBar:
840 // we want XUL popups to be able to overlap the task bar.
841 aResult = 1;
842 break;
843 case IntID::SkipNavigatingDisabledMenuItem:
844 aResult = 1;
845 break;
846 case IntID::DragThresholdX:
847 case IntID::DragThresholdY: {
848 gint threshold = 0;
849 g_object_get(gtk_settings_get_default(), "gtk-dnd-drag-threshold",
850 &threshold, nullptr);
852 aResult = threshold;
853 } break;
854 case IntID::ScrollArrowStyle: {
855 GtkWidget* scrollbar = GetWidget(MOZ_GTK_SCROLLBAR_VERTICAL);
856 aResult = ConvertGTKStepperStyleToMozillaScrollArrowStyle(scrollbar);
857 break;
859 case IntID::ScrollSliderStyle:
860 aResult = eScrollThumbStyle_Proportional;
861 break;
862 case IntID::TreeOpenDelay:
863 aResult = 1000;
864 break;
865 case IntID::TreeCloseDelay:
866 aResult = 1000;
867 break;
868 case IntID::TreeLazyScrollDelay:
869 aResult = 150;
870 break;
871 case IntID::TreeScrollDelay:
872 aResult = 100;
873 break;
874 case IntID::TreeScrollLinesMax:
875 aResult = 3;
876 break;
877 case IntID::AlertNotificationOrigin:
878 aResult = NS_ALERT_TOP;
879 break;
880 case IntID::IMERawInputUnderlineStyle:
881 case IntID::IMEConvertedTextUnderlineStyle:
882 aResult = NS_STYLE_TEXT_DECORATION_STYLE_SOLID;
883 break;
884 case IntID::IMESelectedRawTextUnderlineStyle:
885 case IntID::IMESelectedConvertedTextUnderline:
886 aResult = NS_STYLE_TEXT_DECORATION_STYLE_NONE;
887 break;
888 case IntID::SpellCheckerUnderlineStyle:
889 aResult = NS_STYLE_TEXT_DECORATION_STYLE_WAVY;
890 break;
891 case IntID::MenuBarDrag:
892 EnsureInit();
893 aResult = mSystemTheme.mMenuSupportsDrag;
894 break;
895 case IntID::ScrollbarButtonAutoRepeatBehavior:
896 aResult = 1;
897 break;
898 case IntID::SwipeAnimationEnabled:
899 aResult = 0;
900 break;
901 case IntID::ContextMenuOffsetVertical:
902 case IntID::ContextMenuOffsetHorizontal:
903 aResult = 2;
904 break;
905 case IntID::GTKCSDAvailable:
906 aResult = sCSDAvailable;
907 break;
908 case IntID::GTKCSDMaximizeButton:
909 EnsureInit();
910 aResult = mCSDMaximizeButton;
911 break;
912 case IntID::GTKCSDMinimizeButton:
913 EnsureInit();
914 aResult = mCSDMinimizeButton;
915 break;
916 case IntID::GTKCSDCloseButton:
917 EnsureInit();
918 aResult = mCSDCloseButton;
919 break;
920 case IntID::GTKCSDReversedPlacement:
921 EnsureInit();
922 aResult = mCSDReversedPlacement;
923 break;
924 case IntID::PrefersReducedMotion: {
925 aResult = mPrefersReducedMotion;
926 break;
928 case IntID::SystemUsesDarkTheme: {
929 EnsureInit();
930 if (mColorSchemePreference) {
931 aResult = *mColorSchemePreference == ColorScheme::Dark;
932 } else {
933 aResult = mSystemTheme.mIsDark;
935 break;
937 case IntID::GTKCSDMaximizeButtonPosition:
938 aResult = mCSDMaximizeButtonPosition;
939 break;
940 case IntID::GTKCSDMinimizeButtonPosition:
941 aResult = mCSDMinimizeButtonPosition;
942 break;
943 case IntID::GTKCSDCloseButtonPosition:
944 aResult = mCSDCloseButtonPosition;
945 break;
946 case IntID::UseAccessibilityTheme: {
947 EnsureInit();
948 aResult = mSystemTheme.mHighContrast;
949 break;
951 case IntID::TitlebarRadius: {
952 EnsureInit();
953 aResult = EffectiveTheme().mTitlebarRadius;
954 break;
956 case IntID::GtkMenuRadius: {
957 EnsureInit();
958 aResult = EffectiveTheme().mMenuRadius;
959 break;
961 case IntID::AllowOverlayScrollbarsOverlap: {
962 aResult = 1;
963 break;
965 case IntID::ScrollbarFadeBeginDelay: {
966 aResult = 1000;
967 break;
969 case IntID::ScrollbarFadeDuration: {
970 aResult = 400;
971 break;
973 case IntID::ScrollbarDisplayOnMouseMove: {
974 aResult = 1;
975 break;
977 case IntID::UseOverlayScrollbars: {
978 aResult = StaticPrefs::widget_gtk_overlay_scrollbars_enabled();
979 break;
981 case IntID::TouchDeviceSupportPresent:
982 aResult = widget::WidgetUtilsGTK::IsTouchDeviceSupportPresent() ? 1 : 0;
983 break;
984 default:
985 aResult = 0;
986 res = NS_ERROR_FAILURE;
989 return res;
992 nsresult nsLookAndFeel::NativeGetFloat(FloatID aID, float& aResult) {
993 nsresult rv = NS_OK;
994 switch (aID) {
995 case FloatID::IMEUnderlineRelativeSize:
996 aResult = 1.0f;
997 break;
998 case FloatID::SpellCheckerUnderlineRelativeSize:
999 aResult = 1.0f;
1000 break;
1001 case FloatID::CaretAspectRatio:
1002 EnsureInit();
1003 aResult = mSystemTheme.mCaretRatio;
1004 break;
1005 case FloatID::TextScaleFactor:
1006 aResult = gfxPlatformGtk::GetFontScaleFactor();
1007 break;
1008 default:
1009 aResult = -1.0;
1010 rv = NS_ERROR_FAILURE;
1012 return rv;
1015 static void GetSystemFontInfo(GtkStyleContext* aStyle, nsString* aFontName,
1016 gfxFontStyle* aFontStyle) {
1017 aFontStyle->style = FontSlantStyle::Normal();
1019 // As in
1020 // https://git.gnome.org/browse/gtk+/tree/gtk/gtkwidget.c?h=3.22.19#n10333
1021 PangoFontDescription* desc;
1022 gtk_style_context_get(aStyle, gtk_style_context_get_state(aStyle), "font",
1023 &desc, nullptr);
1025 aFontStyle->systemFont = true;
1027 constexpr auto quote = u"\""_ns;
1028 NS_ConvertUTF8toUTF16 family(pango_font_description_get_family(desc));
1029 *aFontName = quote + family + quote;
1031 aFontStyle->weight = FontWeight(pango_font_description_get_weight(desc));
1033 // FIXME: Set aFontStyle->stretch correctly!
1034 aFontStyle->stretch = FontStretch::Normal();
1036 float size = float(pango_font_description_get_size(desc)) / PANGO_SCALE;
1038 // |size| is now either pixels or pango-points (not Mozilla-points!)
1040 if (!pango_font_description_get_size_is_absolute(desc)) {
1041 // |size| is in pango-points, so convert to pixels.
1042 size *= float(gfxPlatformGtk::GetFontScaleDPI()) / POINTS_PER_INCH_FLOAT;
1045 // |size| is now pixels but not scaled for the hidpi displays,
1046 aFontStyle->size = size;
1048 pango_font_description_free(desc);
1051 bool nsLookAndFeel::NativeGetFont(FontID aID, nsString& aFontName,
1052 gfxFontStyle& aFontStyle) {
1053 return mSystemTheme.GetFont(aID, aFontName, aFontStyle);
1056 bool nsLookAndFeel::PerThemeData::GetFont(FontID aID, nsString& aFontName,
1057 gfxFontStyle& aFontStyle) const {
1058 switch (aID) {
1059 case FontID::Menu: // css2
1060 case FontID::MozPullDownMenu: // css3
1061 aFontName = mMenuFontName;
1062 aFontStyle = mMenuFontStyle;
1063 break;
1065 case FontID::MozField: // css3
1066 case FontID::MozList: // css3
1067 aFontName = mFieldFontName;
1068 aFontStyle = mFieldFontStyle;
1069 break;
1071 case FontID::MozButton: // css3
1072 aFontName = mButtonFontName;
1073 aFontStyle = mButtonFontStyle;
1074 break;
1076 case FontID::Caption: // css2
1077 case FontID::Icon: // css2
1078 case FontID::MessageBox: // css2
1079 case FontID::SmallCaption: // css2
1080 case FontID::StatusBar: // css2
1081 case FontID::MozWindow: // css3
1082 case FontID::MozDocument: // css3
1083 case FontID::MozWorkspace: // css3
1084 case FontID::MozDesktop: // css3
1085 case FontID::MozInfo: // css3
1086 case FontID::MozDialog: // css3
1087 default:
1088 aFontName = mDefaultFontName;
1089 aFontStyle = mDefaultFontStyle;
1090 break;
1093 // Scale the font for the current monitor
1094 double scaleFactor = StaticPrefs::layout_css_devPixelsPerPx();
1095 if (scaleFactor > 0) {
1096 aFontStyle.size *=
1097 widget::ScreenHelperGTK::GetGTKMonitorScaleFactor() / scaleFactor;
1098 } else {
1099 // Convert gdk pixels to CSS pixels.
1100 aFontStyle.size /= gfxPlatformGtk::GetFontScaleFactor();
1103 return true;
1106 // Check color contrast according to
1107 // https://www.w3.org/TR/AERT/#color-contrast
1108 static bool HasGoodContrastVisibility(GdkRGBA& aColor1, GdkRGBA& aColor2) {
1109 int32_t luminosityDifference = NS_LUMINOSITY_DIFFERENCE(
1110 GDK_RGBA_TO_NS_RGBA(aColor1), GDK_RGBA_TO_NS_RGBA(aColor2));
1111 if (luminosityDifference < NS_SUFFICIENT_LUMINOSITY_DIFFERENCE) {
1112 return false;
1115 double colorDifference = std::abs(aColor1.red - aColor2.red) +
1116 std::abs(aColor1.green - aColor2.green) +
1117 std::abs(aColor1.blue - aColor2.blue);
1118 return (colorDifference * 255.0 > 500.0);
1121 // Check if the foreground/background colors match with default white/black
1122 // html page colors.
1123 static bool IsGtkThemeCompatibleWithHTMLColors() {
1124 GdkRGBA white = {1.0, 1.0, 1.0};
1125 GdkRGBA black = {0.0, 0.0, 0.0};
1127 GtkStyleContext* style = GetStyleContext(MOZ_GTK_WINDOW);
1129 GdkRGBA textColor;
1130 gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &textColor);
1132 // Theme text color and default white html page background
1133 if (!HasGoodContrastVisibility(textColor, white)) {
1134 return false;
1137 GdkRGBA backgroundColor;
1138 gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL,
1139 &backgroundColor);
1141 // Theme background color and default white html page background
1142 if (HasGoodContrastVisibility(backgroundColor, white)) {
1143 return false;
1146 // Theme background color and default black text color
1147 return HasGoodContrastVisibility(backgroundColor, black);
1150 static nsCString GetGtkSettingsStringKey(const char* aKey) {
1151 MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
1152 nsCString ret;
1153 GtkSettings* settings = gtk_settings_get_default();
1154 char* value = nullptr;
1155 g_object_get(settings, aKey, &value, nullptr);
1156 if (value) {
1157 ret.Assign(value);
1158 g_free(value);
1160 return ret;
1163 static nsCString GetGtkTheme() {
1164 return GetGtkSettingsStringKey("gtk-theme-name");
1167 static bool GetPreferDarkTheme() {
1168 GtkSettings* settings = gtk_settings_get_default();
1169 gboolean preferDarkTheme = FALSE;
1170 g_object_get(settings, "gtk-application-prefer-dark-theme", &preferDarkTheme,
1171 nullptr);
1172 return preferDarkTheme == TRUE;
1175 // It seems GTK doesn't have an API to query if the current theme is "light" or
1176 // "dark", so we synthesize it from the CSS2 Window/WindowText colors instead,
1177 // by comparing their luminosity.
1178 static bool GetThemeIsDark() {
1179 GdkRGBA bg, fg;
1180 GtkStyleContext* style = GetStyleContext(MOZ_GTK_WINDOW);
1181 gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &bg);
1182 gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &fg);
1183 return RelativeLuminanceUtils::Compute(GDK_RGBA_TO_NS_RGBA(bg)) <
1184 RelativeLuminanceUtils::Compute(GDK_RGBA_TO_NS_RGBA(fg));
1187 void nsLookAndFeel::ConfigureTheme(const LookAndFeelTheme& aTheme) {
1188 MOZ_ASSERT(XRE_IsContentProcess());
1189 GtkSettings* settings = gtk_settings_get_default();
1190 g_object_set(settings, "gtk-theme-name", aTheme.themeName().get(),
1191 "gtk-application-prefer-dark-theme",
1192 aTheme.preferDarkTheme() ? TRUE : FALSE, nullptr);
1195 void nsLookAndFeel::RestoreSystemTheme() {
1196 LOGLNF("RestoreSystemTheme(%s, %d, %d)\n", mSystemTheme.mName.get(),
1197 mSystemTheme.mPreferDarkTheme, mSystemThemeOverridden);
1199 if (!mSystemThemeOverridden) {
1200 return;
1203 // Available on Gtk 3.20+.
1204 static auto sGtkSettingsResetProperty =
1205 (void (*)(GtkSettings*, const gchar*))dlsym(
1206 RTLD_DEFAULT, "gtk_settings_reset_property");
1208 GtkSettings* settings = gtk_settings_get_default();
1209 if (sGtkSettingsResetProperty) {
1210 sGtkSettingsResetProperty(settings, "gtk-theme-name");
1211 sGtkSettingsResetProperty(settings, "gtk-application-prefer-dark-theme");
1212 } else {
1213 g_object_set(settings, "gtk-theme-name", mSystemTheme.mName.get(),
1214 "gtk-application-prefer-dark-theme",
1215 mSystemTheme.mPreferDarkTheme, nullptr);
1217 moz_gtk_refresh();
1218 mSystemThemeOverridden = false;
1221 static bool AnyColorChannelIsDifferent(nscolor aColor) {
1222 return NS_GET_R(aColor) != NS_GET_G(aColor) ||
1223 NS_GET_R(aColor) != NS_GET_B(aColor);
1226 void nsLookAndFeel::ConfigureAndInitializeAltTheme() {
1227 GtkSettings* settings = gtk_settings_get_default();
1229 bool fellBackToDefaultTheme = false;
1231 // Try to select the opposite variant of the current theme first...
1232 LOGLNF(" toggling gtk-application-prefer-dark-theme\n");
1233 g_object_set(settings, "gtk-application-prefer-dark-theme",
1234 !mSystemTheme.mIsDark, nullptr);
1235 moz_gtk_refresh();
1237 // Toggling gtk-application-prefer-dark-theme is not enough generally to
1238 // switch from dark to light theme. If the theme didn't change, and we have
1239 // a dark theme, try to first remove -Dark{,er,est} from the theme name to
1240 // find the light variant.
1241 if (mSystemTheme.mIsDark && mSystemTheme.mIsDark == GetThemeIsDark()) {
1242 nsCString potentialLightThemeName = mSystemTheme.mName;
1243 // clang-format off
1244 constexpr nsLiteralCString kSubstringsToRemove[] = {
1245 "-darkest"_ns, "-darker"_ns, "-dark"_ns,
1246 "-Darkest"_ns, "-Darker"_ns, "-Dark"_ns,
1247 "_darkest"_ns, "_darker"_ns, "_dark"_ns,
1248 "_Darkest"_ns, "_Darker"_ns, "_Dark"_ns,
1250 // clang-format on
1251 bool found = false;
1252 for (auto& s : kSubstringsToRemove) {
1253 potentialLightThemeName = mSystemTheme.mName;
1254 potentialLightThemeName.ReplaceSubstring(s, ""_ns);
1255 if (potentialLightThemeName.Length() != mSystemTheme.mName.Length()) {
1256 found = true;
1257 break;
1260 if (found) {
1261 g_object_set(settings, "gtk-theme-name", potentialLightThemeName.get(),
1262 nullptr);
1263 moz_gtk_refresh();
1267 if (mSystemTheme.mIsDark == GetThemeIsDark()) {
1268 // If the theme still didn't change enough, fall back to either Adwaita or
1269 // Adwaita-dark.
1270 g_object_set(settings, "gtk-theme-name",
1271 mSystemTheme.mIsDark ? "Adwaita" : "Adwaita-dark", nullptr);
1272 moz_gtk_refresh();
1273 fellBackToDefaultTheme = true;
1276 mAltTheme.Init();
1278 // Some of the alt theme colors we can grab from the system theme, if we fell
1279 // back to the default light / dark themes.
1280 if (fellBackToDefaultTheme) {
1281 if (StaticPrefs::widget_gtk_alt_theme_selection()) {
1282 mAltTheme.mTextSelectedText = mSystemTheme.mTextSelectedText;
1283 mAltTheme.mTextSelectedBackground = mSystemTheme.mTextSelectedBackground;
1286 if (StaticPrefs::widget_gtk_alt_theme_scrollbar_active() &&
1287 (!mAltTheme.mIsDark || ShouldUseColorForActiveDarkScrollbarThumb(
1288 mSystemTheme.mThemedScrollbarThumbActive))) {
1289 mAltTheme.mThemedScrollbarThumbActive =
1290 mSystemTheme.mThemedScrollbarThumbActive;
1293 if (StaticPrefs::widget_gtk_alt_theme_accent()) {
1294 mAltTheme.mAccentColor = mSystemTheme.mAccentColor;
1295 mAltTheme.mAccentColorForeground = mSystemTheme.mAccentColorForeground;
1299 // Right now we're using the opposite color-scheme theme, make sure to record
1300 // it.
1301 mSystemThemeOverridden = true;
1304 Maybe<ColorScheme> nsLookAndFeel::ComputeColorSchemeSetting() {
1306 // Check the pref explicitly here. Usually this shouldn't be needed, but
1307 // since we can only load one GTK theme at a time, and the pref will
1308 // override the effective value that the rest of gecko assumes for the
1309 // "system" color scheme, we need to factor it in our GTK theme decisions.
1310 int32_t pref = 0;
1311 if (NS_SUCCEEDED(Preferences::GetInt("ui.systemUsesDarkTheme", &pref))) {
1312 return Some(pref ? ColorScheme::Dark : ColorScheme::Light);
1316 if (!mDBusSettingsProxy) {
1317 return Nothing();
1319 GUniquePtr<GError> error;
1320 RefPtr<GVariant> variant = dont_AddRef(g_dbus_proxy_call_sync(
1321 mDBusSettingsProxy, "Read",
1322 g_variant_new("(ss)", "org.freedesktop.appearance", "color-scheme"),
1323 G_DBUS_CALL_FLAGS_NONE,
1324 StaticPrefs::widget_gtk_settings_portal_timeout_ms(), nullptr,
1325 getter_Transfers(error)));
1326 if (!variant) {
1327 LOGLNF("color-scheme query error: %s\n", error->message);
1328 return Nothing();
1330 LOGLNF("color-scheme query result: %s\n", GVariantToString(variant).get());
1331 variant = dont_AddRef(g_variant_get_child_value(variant, 0));
1332 while (variant && g_variant_is_of_type(variant, G_VARIANT_TYPE_VARIANT)) {
1333 // Unbox the return value.
1334 variant = dont_AddRef(g_variant_get_variant(variant));
1336 if (!variant || !g_variant_is_of_type(variant, G_VARIANT_TYPE_UINT32)) {
1337 MOZ_ASSERT(false, "Unexpected color-scheme query return value");
1338 return Nothing();
1340 switch (g_variant_get_uint32(variant)) {
1341 default:
1342 MOZ_FALLTHROUGH_ASSERT("Unexpected color-scheme query return value");
1343 case 0:
1344 break;
1345 case 1:
1346 return Some(ColorScheme::Dark);
1347 case 2:
1348 return Some(ColorScheme::Light);
1350 return Nothing();
1353 void nsLookAndFeel::Initialize() {
1354 LOGLNF("nsLookAndFeel::Initialize");
1355 MOZ_DIAGNOSTIC_ASSERT(!mInitialized);
1356 MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread(),
1357 "LookAndFeel init should be done on the main thread");
1359 mInitialized = true;
1361 GtkSettings* settings = gtk_settings_get_default();
1362 if (MOZ_UNLIKELY(!settings)) {
1363 NS_WARNING("EnsureInit: No settings");
1364 return;
1367 AutoRestore<bool> restoreIgnoreSettings(sIgnoreChangedSettings);
1368 sIgnoreChangedSettings = true;
1370 // Our current theme may be different from the system theme if we're matching
1371 // the Firefox theme or using the alt theme intentionally due to the
1372 // color-scheme preference. Make sure to restore the original system theme.
1373 RestoreSystemTheme();
1375 // First initialize global settings.
1376 InitializeGlobalSettings();
1378 // Record our system theme settings now.
1379 mSystemTheme.Init();
1381 // Find the alternative-scheme theme (light if the system theme is dark, or
1382 // vice versa), configure it and initialize it.
1383 ConfigureAndInitializeAltTheme();
1385 LOGLNF("System Theme: %s. Alt Theme: %s\n", mSystemTheme.mName.get(),
1386 mAltTheme.mName.get());
1388 // Go back to the system theme or keep the alt theme configured, depending on
1389 // Firefox theme or user color-scheme preference.
1390 ConfigureFinalEffectiveTheme();
1392 RecordTelemetry();
1395 void nsLookAndFeel::InitializeGlobalSettings() {
1396 GtkSettings* settings = gtk_settings_get_default();
1398 mColorSchemePreference = ComputeColorSchemeSetting();
1400 gboolean enableAnimations = false;
1401 g_object_get(settings, "gtk-enable-animations", &enableAnimations, nullptr);
1402 mPrefersReducedMotion = !enableAnimations;
1404 gint blink_time = 0; // In milliseconds
1405 gint blink_timeout = 0; // in seconds
1406 gboolean blink;
1407 g_object_get(settings, "gtk-cursor-blink-time", &blink_time,
1408 "gtk-cursor-blink-timeout", &blink_timeout, "gtk-cursor-blink",
1409 &blink, nullptr);
1410 // From
1411 // https://docs.gtk.org/gtk3/property.Settings.gtk-cursor-blink-timeout.html:
1413 // Setting this to zero has the same effect as setting
1414 // GtkSettings:gtk-cursor-blink to FALSE.
1416 mCaretBlinkTime = blink && blink_timeout ? (int32_t)blink_time : 0;
1418 if (mCaretBlinkTime) {
1419 // blink_time * 2 because blink count is a full blink cycle.
1420 mCaretBlinkCount =
1421 std::max(1, int32_t(std::ceil(float(blink_timeout * 1000) /
1422 (float(blink_time) * 2.0f))));
1423 } else {
1424 mCaretBlinkCount = -1;
1427 mCSDCloseButton = false;
1428 mCSDMinimizeButton = false;
1429 mCSDMaximizeButton = false;
1430 mCSDCloseButtonPosition = 0;
1431 mCSDMinimizeButtonPosition = 0;
1432 mCSDMaximizeButtonPosition = 0;
1434 // We need to initialize whole CSD config explicitly because it's queried
1435 // as -moz-gtk* media features.
1436 ButtonLayout buttonLayout[TOOLBAR_BUTTONS];
1438 size_t activeButtons =
1439 GetGtkHeaderBarButtonLayout(Span(buttonLayout), &mCSDReversedPlacement);
1440 for (size_t i = 0; i < activeButtons; i++) {
1441 // We check if a button is represented on the right side of the tabbar.
1442 // Then we assign it a value from 3 to 5, instead of 0 to 2 when it is on
1443 // the left side.
1444 const ButtonLayout& layout = buttonLayout[i];
1445 int32_t* pos = nullptr;
1446 switch (layout.mType) {
1447 case MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE:
1448 mCSDMinimizeButton = true;
1449 pos = &mCSDMinimizeButtonPosition;
1450 break;
1451 case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE:
1452 mCSDMaximizeButton = true;
1453 pos = &mCSDMaximizeButtonPosition;
1454 break;
1455 case MOZ_GTK_HEADER_BAR_BUTTON_CLOSE:
1456 mCSDCloseButton = true;
1457 pos = &mCSDCloseButtonPosition;
1458 break;
1459 default:
1460 break;
1463 if (pos) {
1464 *pos = i;
1469 void nsLookAndFeel::ConfigureFinalEffectiveTheme() {
1470 MOZ_ASSERT(mSystemThemeOverridden,
1471 "By this point, the alt theme should be configured");
1473 const bool shouldUseSystemTheme = [&] {
1474 // NOTE: We can't call ColorSchemeForChrome directly because this might run
1475 // while we're computing it.
1476 switch (ColorSchemeSettingForChrome()) {
1477 case ChromeColorSchemeSetting::Light:
1478 return !mSystemTheme.mIsDark;
1479 case ChromeColorSchemeSetting::Dark:
1480 return mSystemTheme.mIsDark;
1481 case ChromeColorSchemeSetting::System:
1482 break;
1484 if (!mColorSchemePreference) {
1485 return true;
1487 bool preferenceIsDark = *mColorSchemePreference == ColorScheme::Dark;
1488 return preferenceIsDark == mSystemTheme.mIsDark;
1489 }();
1491 const bool usingSystem = !mSystemThemeOverridden;
1492 LOGLNF("OverrideSystemThemeIfNeeded(matchesSystem=%d, usingSystem=%d)\n",
1493 shouldUseSystemTheme, usingSystem);
1495 if (shouldUseSystemTheme) {
1496 RestoreSystemTheme();
1497 } else if (usingSystem) {
1498 LOGLNF("Setting theme %s, %d\n", mAltTheme.mName.get(),
1499 mAltTheme.mPreferDarkTheme);
1501 GtkSettings* settings = gtk_settings_get_default();
1502 if (mSystemTheme.mName == mAltTheme.mName) {
1503 // Prefer setting only gtk-application-prefer-dark-theme, so we can still
1504 // get notified from notify::gtk-theme-name if the user changes the theme.
1505 g_object_set(settings, "gtk-application-prefer-dark-theme",
1506 mAltTheme.mPreferDarkTheme, nullptr);
1507 } else {
1508 g_object_set(settings, "gtk-theme-name", mAltTheme.mName.get(),
1509 "gtk-application-prefer-dark-theme",
1510 mAltTheme.mPreferDarkTheme, nullptr);
1512 moz_gtk_refresh();
1513 mSystemThemeOverridden = true;
1517 void nsLookAndFeel::GetGtkContentTheme(LookAndFeelTheme& aTheme) {
1518 if (NS_SUCCEEDED(Preferences::GetCString("widget.content.gtk-theme-override",
1519 aTheme.themeName()))) {
1520 return;
1523 auto& theme = StaticPrefs::widget_content_allow_gtk_dark_theme()
1524 ? mSystemTheme
1525 : LightTheme();
1526 aTheme.preferDarkTheme() = theme.mPreferDarkTheme;
1527 aTheme.themeName() = theme.mName;
1530 static nscolor GetBackgroundColor(
1531 GtkStyleContext* aStyle, nscolor aForForegroundColor,
1532 GtkStateFlags aState = GTK_STATE_FLAG_NORMAL) {
1533 GdkRGBA gdkColor;
1534 gtk_style_context_get_background_color(aStyle, aState, &gdkColor);
1535 nscolor color = GDK_RGBA_TO_NS_RGBA(gdkColor);
1536 if (NS_GET_A(color)) {
1537 return color;
1540 // Try to synthesize a color from a background-image.
1541 GValue value = G_VALUE_INIT;
1542 gtk_style_context_get_property(aStyle, "background-image", aState, &value);
1543 auto cleanup = MakeScopeExit([&] { g_value_unset(&value); });
1545 if (GetColorFromImagePattern(&value, &color)) {
1546 return color;
1550 GdkRGBA light, dark;
1551 if (GetGradientColors(&value, &light, &dark)) {
1552 nscolor l = GDK_RGBA_TO_NS_RGBA(light);
1553 nscolor d = GDK_RGBA_TO_NS_RGBA(dark);
1554 // Return the one with more contrast.
1555 // TODO(emilio): This could do interpolation or what not but seems
1556 // overkill.
1557 return NS_LUMINOSITY_DIFFERENCE(l, aForForegroundColor) >
1558 NS_LUMINOSITY_DIFFERENCE(d, aForForegroundColor)
1560 : d;
1564 return NS_TRANSPARENT;
1567 void nsLookAndFeel::PerThemeData::Init() {
1568 mName = GetGtkTheme();
1570 GtkStyleContext* style;
1572 mHighContrast = StaticPrefs::widget_content_gtk_high_contrast_enabled() &&
1573 mName.Find("HighContrast"_ns) >= 0;
1575 mPreferDarkTheme = GetPreferDarkTheme();
1577 mIsDark = GetThemeIsDark();
1579 mCompatibleWithHTMLLightColors =
1580 !mIsDark && IsGtkThemeCompatibleWithHTMLColors();
1582 GdkRGBA color;
1583 // Some themes style the <trough>, while others style the <scrollbar>
1584 // itself, so we look at both and compose the colors.
1585 style = GetStyleContext(MOZ_GTK_SCROLLBAR_VERTICAL);
1586 gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color);
1587 mThemedScrollbar = GDK_RGBA_TO_NS_RGBA(color);
1588 gtk_style_context_get_background_color(style, GTK_STATE_FLAG_BACKDROP,
1589 &color);
1590 mThemedScrollbarInactive = GDK_RGBA_TO_NS_RGBA(color);
1592 style = GetStyleContext(MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL);
1593 gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color);
1594 mThemedScrollbar =
1595 NS_ComposeColors(mThemedScrollbar, GDK_RGBA_TO_NS_RGBA(color));
1596 gtk_style_context_get_background_color(style, GTK_STATE_FLAG_BACKDROP,
1597 &color);
1598 mThemedScrollbarInactive =
1599 NS_ComposeColors(mThemedScrollbarInactive, GDK_RGBA_TO_NS_RGBA(color));
1601 style = GetStyleContext(MOZ_GTK_SCROLLBAR_THUMB_VERTICAL);
1602 gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color);
1603 mThemedScrollbarThumb = GDK_RGBA_TO_NS_RGBA(color);
1604 gtk_style_context_get_background_color(style, GTK_STATE_FLAG_PRELIGHT,
1605 &color);
1606 mThemedScrollbarThumbHover = GDK_RGBA_TO_NS_RGBA(color);
1607 gtk_style_context_get_background_color(
1608 style, GtkStateFlags(GTK_STATE_FLAG_PRELIGHT | GTK_STATE_FLAG_ACTIVE),
1609 &color);
1610 mThemedScrollbarThumbActive = GDK_RGBA_TO_NS_RGBA(color);
1611 gtk_style_context_get_background_color(style, GTK_STATE_FLAG_BACKDROP,
1612 &color);
1613 mThemedScrollbarThumbInactive = GDK_RGBA_TO_NS_RGBA(color);
1615 // Make sure that the thumb is visible, at least.
1616 const bool fallbackToUnthemedColors = [&] {
1617 if (!StaticPrefs::widget_gtk_theme_scrollbar_colors_enabled()) {
1618 return true;
1621 if (!ShouldHonorThemeScrollbarColors()) {
1622 return true;
1624 // If any of the scrollbar thumb colors are fully transparent, fall back to
1625 // non-native ones.
1626 if (!NS_GET_A(mThemedScrollbarThumb) ||
1627 !NS_GET_A(mThemedScrollbarThumbHover) ||
1628 !NS_GET_A(mThemedScrollbarThumbActive)) {
1629 return true;
1631 // If the thumb and track are the same color and opaque, fall back to
1632 // non-native colors as well.
1633 if (mThemedScrollbar == mThemedScrollbarThumb &&
1634 NS_GET_A(mThemedScrollbar) == 0xff) {
1635 return true;
1637 return false;
1638 }();
1640 if (fallbackToUnthemedColors) {
1641 if (mIsDark) {
1642 // Taken from Adwaita-dark.
1643 mThemedScrollbar = NS_RGB(0x31, 0x31, 0x31);
1644 mThemedScrollbarInactive = NS_RGB(0x2d, 0x2d, 0x2d);
1645 mThemedScrollbarThumb = NS_RGB(0xa3, 0xa4, 0xa4);
1646 mThemedScrollbarThumbInactive = NS_RGB(0x59, 0x5a, 0x5a);
1647 } else {
1648 // Taken from Adwaita.
1649 mThemedScrollbar = NS_RGB(0xce, 0xce, 0xce);
1650 mThemedScrollbarInactive = NS_RGB(0xec, 0xed, 0xef);
1651 mThemedScrollbarThumb = NS_RGB(0x82, 0x81, 0x7e);
1652 mThemedScrollbarThumbInactive = NS_RGB(0xce, 0xcf, 0xce);
1655 mThemedScrollbarThumbHover = ThemeColors::AdjustUnthemedScrollbarThumbColor(
1656 mThemedScrollbarThumb, NS_EVENT_STATE_HOVER);
1657 mThemedScrollbarThumbActive =
1658 ThemeColors::AdjustUnthemedScrollbarThumbColor(mThemedScrollbarThumb,
1659 NS_EVENT_STATE_ACTIVE);
1662 // The label is not added to a parent widget, but shared for constructing
1663 // different style contexts. The node hierarchy is constructed only on
1664 // the label style context.
1665 GtkWidget* labelWidget = gtk_label_new("M");
1666 g_object_ref_sink(labelWidget);
1668 // Window colors
1669 style = GetStyleContext(MOZ_GTK_WINDOW);
1671 gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
1672 mMozWindowText = GDK_RGBA_TO_NS_RGBA(color);
1674 mMozWindowBackground = GetBackgroundColor(style, mMozWindowText);
1676 gtk_style_context_get_border_color(style, GTK_STATE_FLAG_NORMAL, &color);
1677 mMozWindowActiveBorder = GDK_RGBA_TO_NS_RGBA(color);
1679 gtk_style_context_get_border_color(style, GTK_STATE_FLAG_INSENSITIVE, &color);
1680 mMozWindowInactiveBorder = GDK_RGBA_TO_NS_RGBA(color);
1682 style = GetStyleContext(MOZ_GTK_WINDOW_CONTAINER);
1684 GtkStyleContext* labelStyle = CreateStyleForWidget(labelWidget, style);
1685 GetSystemFontInfo(labelStyle, &mDefaultFontName, &mDefaultFontStyle);
1686 g_object_unref(labelStyle);
1689 // tooltip foreground and background
1690 style = GetStyleContext(MOZ_GTK_TOOLTIP_BOX_LABEL);
1691 gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
1692 mInfoText = GDK_RGBA_TO_NS_RGBA(color);
1694 style = GetStyleContext(MOZ_GTK_TOOLTIP);
1695 mInfoBackground = GetBackgroundColor(style, mInfoText);
1697 style = GetStyleContext(MOZ_GTK_MENUITEM);
1699 GtkStyleContext* accelStyle =
1700 CreateStyleForWidget(gtk_accel_label_new("M"), style);
1702 GetSystemFontInfo(accelStyle, &mMenuFontName, &mMenuFontStyle);
1704 gtk_style_context_get_color(accelStyle, GTK_STATE_FLAG_NORMAL, &color);
1705 mMenuText = GDK_RGBA_TO_NS_RGBA(color);
1706 gtk_style_context_get_color(accelStyle, GTK_STATE_FLAG_INSENSITIVE, &color);
1707 mMenuTextInactive = GDK_RGBA_TO_NS_RGBA(color);
1708 g_object_unref(accelStyle);
1711 const auto effectiveHeaderBarStyle =
1712 HeaderBarShouldDrawContainer(MOZ_GTK_HEADER_BAR) ? MOZ_GTK_HEADERBAR_FIXED
1713 : MOZ_GTK_HEADER_BAR;
1714 style = GetStyleContext(effectiveHeaderBarStyle);
1716 gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
1717 mTitlebarText = GDK_RGBA_TO_NS_RGBA(color);
1718 mTitlebarBackground = GetBackgroundColor(style, mTitlebarText);
1720 gtk_style_context_get_color(style, GTK_STATE_FLAG_BACKDROP, &color);
1721 mTitlebarInactiveText = GDK_RGBA_TO_NS_RGBA(color);
1722 mTitlebarInactiveBackground =
1723 GetBackgroundColor(style, mTitlebarText, GTK_STATE_FLAG_BACKDROP);
1724 mTitlebarRadius = IsSolidCSDStyleUsed() ? 0 : GetBorderRadius(style);
1727 style = GetStyleContext(MOZ_GTK_MENUPOPUP);
1728 mMenuBackground = [&] {
1729 nscolor color = GetBackgroundColor(style, mMenuText);
1730 if (NS_GET_A(color)) {
1731 return color;
1733 // Some themes only style menupopups with the backdrop pseudo-class. Since a
1734 // context / popup menu always seems to match that, try that before giving
1735 // up.
1736 color = GetBackgroundColor(style, mMenuText, GTK_STATE_FLAG_BACKDROP);
1737 if (NS_GET_A(color)) {
1738 return color;
1740 // If we get here we couldn't figure out the right color to use. Rather than
1741 // falling back to transparent, fall back to the window background.
1742 NS_WARNING(
1743 "Couldn't find menu background color, falling back to window "
1744 "background");
1745 return mMozWindowBackground;
1746 }();
1747 mMenuRadius = 0;
1748 if (!IsSolidCSDStyleUsed()) {
1749 mMenuRadius = GetBorderRadius(style);
1750 if (!mMenuRadius) {
1751 mMenuRadius =
1752 GetBorderRadius(GetStyleContext(MOZ_GTK_MENUPOPUP_DECORATION));
1756 style = GetStyleContext(MOZ_GTK_MENUITEM);
1757 gtk_style_context_get_color(style, GTK_STATE_FLAG_PRELIGHT, &color);
1758 mMenuHoverText = GDK_RGBA_TO_NS_RGBA(color);
1759 mMenuHover = NS_ComposeColors(
1760 mMenuBackground,
1761 GetBackgroundColor(style, mMenuHoverText, GTK_STATE_FLAG_PRELIGHT));
1763 GtkWidget* parent = gtk_fixed_new();
1764 GtkWidget* window = gtk_window_new(GTK_WINDOW_POPUP);
1765 GtkWidget* treeView = gtk_tree_view_new();
1766 GtkWidget* linkButton = gtk_link_button_new("http://example.com/");
1767 GtkWidget* menuBar = gtk_menu_bar_new();
1768 GtkWidget* menuBarItem = gtk_menu_item_new();
1769 GtkWidget* entry = gtk_entry_new();
1770 GtkWidget* textView = gtk_text_view_new();
1772 gtk_container_add(GTK_CONTAINER(parent), treeView);
1773 gtk_container_add(GTK_CONTAINER(parent), linkButton);
1774 gtk_container_add(GTK_CONTAINER(parent), menuBar);
1775 gtk_menu_shell_append(GTK_MENU_SHELL(menuBar), menuBarItem);
1776 gtk_container_add(GTK_CONTAINER(window), parent);
1777 gtk_container_add(GTK_CONTAINER(parent), entry);
1778 gtk_container_add(GTK_CONTAINER(parent), textView);
1780 // Text colors
1781 GdkRGBA bgColor;
1782 // If the text window background is translucent, then the background of
1783 // the textview root node is visible.
1784 style = GetStyleContext(MOZ_GTK_TEXT_VIEW);
1785 gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL,
1786 &bgColor);
1788 style = GetStyleContext(MOZ_GTK_TEXT_VIEW_TEXT);
1789 gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color);
1790 ApplyColorOver(color, &bgColor);
1791 mFieldBackground = GDK_RGBA_TO_NS_RGBA(bgColor);
1792 gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
1793 mFieldText = GDK_RGBA_TO_NS_RGBA(color);
1795 // Selected text and background
1797 GtkStyleContext* selectionStyle =
1798 GetStyleContext(MOZ_GTK_TEXT_VIEW_TEXT_SELECTION);
1799 auto GrabSelectionColors = [&](GtkStyleContext* style) {
1800 gtk_style_context_get_background_color(
1801 style,
1802 static_cast<GtkStateFlags>(GTK_STATE_FLAG_FOCUSED |
1803 GTK_STATE_FLAG_SELECTED),
1804 &color);
1805 mTextSelectedBackground = GDK_RGBA_TO_NS_RGBA(color);
1806 gtk_style_context_get_color(
1807 style,
1808 static_cast<GtkStateFlags>(GTK_STATE_FLAG_FOCUSED |
1809 GTK_STATE_FLAG_SELECTED),
1810 &color);
1811 mTextSelectedText = GDK_RGBA_TO_NS_RGBA(color);
1813 GrabSelectionColors(selectionStyle);
1814 if (mTextSelectedBackground == mTextSelectedText) {
1815 // Some old distros/themes don't properly use the .selection style, so
1816 // fall back to the regular text view style.
1817 GrabSelectionColors(style);
1820 // Default accent color is the selection background / foreground colors.
1821 mAccentColor = mTextSelectedBackground;
1822 mAccentColorForeground = mTextSelectedText;
1824 // But prefer named colors, as those are more general purpose than the
1825 // actual selection style, which might e.g. be too-transparent.
1827 // NOTE(emilio): It's unclear which one of the theme_selected_* or the
1828 // selected_* pairs should we prefer, in all themes that define both that
1829 // I've found, they're always the same.
1831 GdkRGBA bg, fg;
1832 const bool found =
1833 (gtk_style_context_lookup_color(style, "selected_bg_color", &bg) &&
1834 gtk_style_context_lookup_color(style, "selected_fg_color", &fg)) ||
1835 (gtk_style_context_lookup_color(style, "theme_selected_bg_color",
1836 &bg) &&
1837 gtk_style_context_lookup_color(style, "theme_selected_fg_color",
1838 &fg));
1839 if (found) {
1840 mAccentColor = GDK_RGBA_TO_NS_RGBA(bg);
1841 mAccentColorForeground = GDK_RGBA_TO_NS_RGBA(fg);
1843 // If the accent colors are semi-transparent and the theme provides a
1844 // background color, blend with them to get the "final" color, see
1845 // bug 1717077.
1846 if (NS_GET_A(mAccentColor) != 255 &&
1847 (gtk_style_context_lookup_color(style, "bg_color", &bg) ||
1848 gtk_style_context_lookup_color(style, "theme_bg_color", &bg))) {
1849 mAccentColor =
1850 NS_ComposeColors(GDK_RGBA_TO_NS_RGBA(bg), mAccentColor);
1853 // A semi-transparent foreground color would be kinda silly, but is done
1854 // for symmetry.
1855 if (NS_GET_A(mAccentColorForeground) != 255 &&
1856 (gtk_style_context_lookup_color(style, "fg_color", &fg) ||
1857 gtk_style_context_lookup_color(style, "theme_fg_color", &fg))) {
1858 mAccentColorForeground =
1859 NS_ComposeColors(GDK_RGBA_TO_NS_RGBA(fg), mAccentColorForeground);
1864 // Accent is the darker one, unless the foreground isn't really a color (is
1865 // all white / black / gray) and the background is, in which case we stick
1866 // to what we have.
1867 if (RelativeLuminanceUtils::Compute(mAccentColor) >
1868 RelativeLuminanceUtils::Compute(mAccentColorForeground) &&
1869 (AnyColorChannelIsDifferent(mAccentColorForeground) ||
1870 !AnyColorChannelIsDifferent(mAccentColor))) {
1871 std::swap(mAccentColor, mAccentColorForeground);
1874 // Blend with white, ensuring the color is opaque, so that the UI doesn't
1875 // have to care about alpha.
1876 mAccentColorForeground =
1877 NS_ComposeColors(NS_RGB(0xff, 0xff, 0xff), mAccentColorForeground);
1878 mAccentColor = NS_ComposeColors(NS_RGB(0xff, 0xff, 0xff), mAccentColor);
1881 // Button text color
1882 style = GetStyleContext(MOZ_GTK_BUTTON);
1884 GtkStyleContext* labelStyle = CreateStyleForWidget(labelWidget, style);
1885 GetSystemFontInfo(labelStyle, &mButtonFontName, &mButtonFontStyle);
1886 g_object_unref(labelStyle);
1889 gtk_style_context_get_border_color(style, GTK_STATE_FLAG_NORMAL, &color);
1890 mButtonDefault = GDK_RGBA_TO_NS_RGBA(color);
1891 gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
1892 mButtonText = GDK_RGBA_TO_NS_RGBA(color);
1893 gtk_style_context_get_color(style, GTK_STATE_FLAG_PRELIGHT, &color);
1894 mButtonHoverText = GDK_RGBA_TO_NS_RGBA(color);
1895 gtk_style_context_get_color(style, GTK_STATE_FLAG_ACTIVE, &color);
1896 mButtonActiveText = GDK_RGBA_TO_NS_RGBA(color);
1897 gtk_style_context_get_background_color(style, GTK_STATE_FLAG_PRELIGHT,
1898 &color);
1899 mButtonHoverFace = GDK_RGBA_TO_NS_RGBA(color);
1900 if (!NS_GET_A(mButtonHoverFace)) {
1901 mButtonHoverFace = mMozWindowBackground;
1904 // Combobox text color
1905 style = GetStyleContext(MOZ_GTK_COMBOBOX_ENTRY_TEXTAREA);
1906 gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
1907 mComboBoxText = GDK_RGBA_TO_NS_RGBA(color);
1909 // Menubar text and hover text colors
1910 style = GetStyleContext(MOZ_GTK_MENUBARITEM);
1911 gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
1912 mMenuBarText = GDK_RGBA_TO_NS_RGBA(color);
1913 gtk_style_context_get_color(style, GTK_STATE_FLAG_PRELIGHT, &color);
1914 mMenuBarHoverText = GDK_RGBA_TO_NS_RGBA(color);
1916 // GTK's guide to fancy odd row background colors:
1917 // 1) Check if a theme explicitly defines an odd row color
1918 // 2) If not, check if it defines an even row color, and darken it
1919 // slightly by a hardcoded value (gtkstyle.c)
1920 // 3) If neither are defined, take the base background color and
1921 // darken that by a hardcoded value
1922 style = GetStyleContext(MOZ_GTK_TREEVIEW);
1924 // Get odd row background color
1925 gtk_style_context_save(style);
1926 gtk_style_context_add_region(style, GTK_STYLE_REGION_ROW, GTK_REGION_ODD);
1927 gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color);
1928 mOddCellBackground = GDK_RGBA_TO_NS_RGBA(color);
1929 gtk_style_context_restore(style);
1931 // Column header colors
1932 style = GetStyleContext(MOZ_GTK_TREE_HEADER_CELL);
1933 gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
1934 mMozColHeaderText = GDK_RGBA_TO_NS_RGBA(color);
1935 gtk_style_context_get_color(style, GTK_STATE_FLAG_PRELIGHT, &color);
1936 mMozColHeaderHoverText = GDK_RGBA_TO_NS_RGBA(color);
1938 // Compute cell highlight colors
1939 InitCellHighlightColors();
1941 // GtkFrame has a "border" subnode on which Adwaita draws the border.
1942 // Some themes do not draw on this node but draw a border on the widget
1943 // root node, so check the root node if no border is found on the border
1944 // node.
1945 style = GetStyleContext(MOZ_GTK_FRAME_BORDER);
1946 bool themeUsesColors =
1947 GetBorderColors(style, &mFrameOuterLightBorder, &mFrameInnerDarkBorder);
1948 if (!themeUsesColors) {
1949 style = GetStyleContext(MOZ_GTK_FRAME);
1950 GetBorderColors(style, &mFrameOuterLightBorder, &mFrameInnerDarkBorder);
1953 // Some themes have a unified menu bar, and support window dragging on it
1954 gboolean supports_menubar_drag = FALSE;
1955 GParamSpec* param_spec = gtk_widget_class_find_style_property(
1956 GTK_WIDGET_GET_CLASS(menuBar), "window-dragging");
1957 if (param_spec) {
1958 if (g_type_is_a(G_PARAM_SPEC_VALUE_TYPE(param_spec), G_TYPE_BOOLEAN)) {
1959 gtk_widget_style_get(menuBar, "window-dragging", &supports_menubar_drag,
1960 nullptr);
1963 mMenuSupportsDrag = supports_menubar_drag;
1965 // TODO: It returns wrong color for themes which
1966 // sets link color for GtkLabel only as we query
1967 // GtkLinkButton style here.
1968 style = gtk_widget_get_style_context(linkButton);
1969 gtk_style_context_get_color(style, GTK_STATE_FLAG_LINK, &color);
1970 mNativeHyperLinkText = GDK_RGBA_TO_NS_RGBA(color);
1972 gtk_style_context_get_color(style, GTK_STATE_FLAG_VISITED, &color);
1973 mNativeVisitedHyperLinkText = GDK_RGBA_TO_NS_RGBA(color);
1975 // invisible character styles
1976 guint value;
1977 g_object_get(entry, "invisible-char", &value, nullptr);
1978 mInvisibleCharacter = char16_t(value);
1980 // caret styles
1981 gtk_widget_style_get(entry, "cursor-aspect-ratio", &mCaretRatio, nullptr);
1983 GetSystemFontInfo(gtk_widget_get_style_context(entry), &mFieldFontName,
1984 &mFieldFontStyle);
1986 gtk_widget_destroy(window);
1987 g_object_unref(labelWidget);
1989 if (LOGLNF_ENABLED()) {
1990 LOGLNF("Initialized theme %s (%d)\n", mName.get(), mPreferDarkTheme);
1991 for (auto id : MakeEnumeratedRange(ColorID::End)) {
1992 nscolor color;
1993 nsresult rv = GetColor(id, color);
1994 LOGLNF(" * color %d: pref=%s success=%d value=%x\n", int(id),
1995 GetColorPrefName(id), NS_SUCCEEDED(rv),
1996 NS_SUCCEEDED(rv) ? color : 0);
1998 LOGLNF(" * titlebar-radius: %d\n", mTitlebarRadius);
1999 LOGLNF(" * menu-radius: %d\n", mMenuRadius);
2003 // virtual
2004 char16_t nsLookAndFeel::GetPasswordCharacterImpl() {
2005 EnsureInit();
2006 return mSystemTheme.mInvisibleCharacter;
2009 bool nsLookAndFeel::GetEchoPasswordImpl() { return false; }
2011 bool nsLookAndFeel::GetDefaultDrawInTitlebar() {
2012 static bool drawInTitlebar = []() {
2013 // When user defined widget.default-hidden-titlebar don't do any
2014 // heuristics and just follow it.
2015 if (Preferences::HasUserValue("widget.default-hidden-titlebar")) {
2016 return Preferences::GetBool("widget.default-hidden-titlebar", false);
2019 // Don't hide titlebar when it's disabled on current desktop.
2020 const char* currentDesktop = getenv("XDG_CURRENT_DESKTOP");
2021 if (!currentDesktop || !sCSDAvailable) {
2022 return false;
2025 // We hide system titlebar on Gnome/ElementaryOS without any restriction.
2026 return strstr(currentDesktop, "GNOME-Flashback:GNOME") ||
2027 strstr(currentDesktop, "GNOME") ||
2028 strstr(currentDesktop, "Pantheon");
2029 }();
2030 return drawInTitlebar;
2033 void nsLookAndFeel::GetThemeInfo(nsACString& aInfo) {
2034 aInfo.Append(mSystemTheme.mName);
2035 aInfo.Append(" / ");
2036 aInfo.Append(mAltTheme.mName);
2039 bool nsLookAndFeel::WidgetUsesImage(WidgetNodeType aNodeType) {
2040 static constexpr GtkStateFlags sFlagsToCheck[]{
2041 GTK_STATE_FLAG_NORMAL, GTK_STATE_FLAG_PRELIGHT,
2042 GtkStateFlags(GTK_STATE_FLAG_PRELIGHT | GTK_STATE_FLAG_ACTIVE),
2043 GTK_STATE_FLAG_BACKDROP, GTK_STATE_FLAG_INSENSITIVE};
2045 GtkStyleContext* style = GetStyleContext(aNodeType);
2047 GValue value = G_VALUE_INIT;
2048 for (GtkStateFlags state : sFlagsToCheck) {
2049 gtk_style_context_get_property(style, "background-image", state, &value);
2050 bool hasPattern = G_VALUE_TYPE(&value) == CAIRO_GOBJECT_TYPE_PATTERN &&
2051 g_value_get_boxed(&value);
2052 g_value_unset(&value);
2053 if (hasPattern) {
2054 return true;
2057 return false;
2060 void nsLookAndFeel::RecordLookAndFeelSpecificTelemetry() {
2061 // Gtk version we're on.
2062 nsString version;
2063 version.AppendPrintf("%d.%d", gtk_major_version, gtk_minor_version);
2064 Telemetry::ScalarSet(Telemetry::ScalarID::WIDGET_GTK_VERSION, version);
2066 // Whether the current Gtk theme has scrollbar buttons.
2067 bool hasScrollbarButtons =
2068 GetInt(LookAndFeel::IntID::ScrollArrowStyle) != eScrollArrow_None;
2069 mozilla::Telemetry::ScalarSet(
2070 mozilla::Telemetry::ScalarID::WIDGET_GTK_THEME_HAS_SCROLLBAR_BUTTONS,
2071 hasScrollbarButtons);
2073 // Whether the current Gtk theme uses something other than a solid color
2074 // background for scrollbar parts.
2075 bool scrollbarUsesImage = !ShouldHonorThemeScrollbarColors();
2076 mozilla::Telemetry::ScalarSet(
2077 mozilla::Telemetry::ScalarID::WIDGET_GTK_THEME_SCROLLBAR_USES_IMAGES,
2078 scrollbarUsesImage);
2081 bool nsLookAndFeel::ShouldHonorThemeScrollbarColors() {
2082 // If the Gtk theme uses anything other than solid color backgrounds for Gtk
2083 // scrollbar parts, this is a good indication that painting XUL scrollbar part
2084 // elements using colors extracted from the theme won't provide good results.
2085 return !WidgetUsesImage(MOZ_GTK_SCROLLBAR_VERTICAL) &&
2086 !WidgetUsesImage(MOZ_GTK_SCROLLBAR_CONTENTS_VERTICAL) &&
2087 !WidgetUsesImage(MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL) &&
2088 !WidgetUsesImage(MOZ_GTK_SCROLLBAR_THUMB_VERTICAL);
2091 #undef LOGLNF
2092 #undef LOGLNF_ENABLED