1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:expandtab:shiftwidth=2:tabstop=2:
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/. */
12 #include "nsLookAndFeel.h"
17 #include <pango/pango.h>
18 #include <pango/pango-fontmap.h>
19 #include <fontconfig/fontconfig.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"
44 #include "mozilla/gfx/2D.h"
46 #include <cairo-gobject.h>
47 #include "WidgetStyleCache.h"
49 #include "nsCSSColorUtils.h"
51 using namespace mozilla
;
52 using namespace mozilla::widget
;
55 # include "mozilla/Logging.h"
56 # include "nsTArray.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)
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
) {
78 // TODO: We could be more granular here, but for now assume everything
80 LookAndFeel::NotifyChangedAllWindows(widget::ThemeChangeKind::StyleAndLayout
);
81 widget::IMContextWrapper::OnThemeChanged();
84 static void settings_changed_cb(GtkSettings
*, GParamSpec
*, void*) {
88 static bool sCSDAvailable
;
90 static nsCString
GVariantToString(GVariant
* aVariant
) {
92 gchar
* s
= g_variant_print(aVariant
, TRUE
);
100 static nsDependentCString
GVariantGetString(GVariant
* aVariant
) {
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");
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");
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
)) {
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
,
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);
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);
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
),
199 mDBusSettingsProxy
= nullptr;
201 g_signal_handlers_disconnect_by_func(
202 gtk_settings_get_default(), FuncToGpointer(settings_changed_cb
), nullptr);
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);
213 str
= gtk_widget_path_to_string(gtk_style_context_get_path(aStyle
));
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
,
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
)) {
248 auto pattern
= static_cast<cairo_pattern_t
*>(g_value_get_boxed(aValue
));
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
)) {
262 double maxLightness
, maxDarkness
;
263 GetLightAndDarkness(*aDarkColor
, &maxLightness
, &maxDarkness
);
264 *aLightColor
= *aDarkColor
;
268 CAIRO_STATUS_SUCCESS
==
269 cairo_pattern_get_color_stop_rgba(pattern
, index
, nullptr, &stop
.red
,
270 &stop
.green
, &stop
.blue
, &stop
.alpha
);
272 double lightness
, darkness
;
273 GetLightAndDarkness(stop
, &lightness
, &darkness
);
274 if (lightness
> maxLightness
) {
275 maxLightness
= lightness
;
278 if (darkness
> maxDarkness
) {
279 maxDarkness
= darkness
;
287 static bool GetColorFromImagePattern(const GValue
* aValue
, nscolor
* aColor
) {
288 if (!G_TYPE_CHECK_VALUE_TYPE(aValue
, CAIRO_GOBJECT_TYPE_PATTERN
)) {
292 auto pattern
= static_cast<cairo_pattern_t
*>(g_value_get_boxed(aValue
));
297 cairo_surface_t
* surface
;
298 if (cairo_pattern_get_surface(pattern
, &surface
) != CAIRO_STATUS_SUCCESS
) {
302 cairo_format_t format
= cairo_image_surface_get_format(surface
);
303 if (format
== CAIRO_FORMAT_INVALID
) {
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
) {
313 // Guesstimate the central pixel would have a sensible color.
317 unsigned char* data
= cairo_image_surface_get_data(surface
);
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();
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
345 const char* propertyName
= "-unico-border-gradient";
346 if (!gtk_style_properties_lookup_property(propertyName
, nullptr, nullptr))
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)
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
);
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
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
;
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.
385 gtk_style_context_get_border(aContext
, state
, &border
);
386 visible
= border
.top
!= 0 || border
.right
!= 0 || border
.bottom
!= 0 ||
391 GetUnicoBorderGradientColors(aContext
, aLightColor
, aDarkColor
))
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
;
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
);
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
;
427 uint16_t hue
, sat
, luminance
;
429 mMozCellHighlightBackground
= mFieldBackground
;
430 mMozCellHighlightText
= mFieldText
;
432 NS_RGB2HSV(mMozCellHighlightBackground
, hue
, sat
, luminance
, alpha
);
435 // Lighten the color if the color is very dark
436 if (luminance
<= step
) {
439 // Darken it if it is very light
440 else if (luminance
>= 255 - step
) {
443 // Otherwise, compute what works best depending on the text luminance.
445 uint16_t textHue
, textSat
, textLuminance
;
447 NS_RGB2HSV(mMozCellHighlightText
, textHue
, textSat
, textLuminance
,
449 // Text is darker than background, use a lighter shade
450 if (textLuminance
< luminance
) {
453 // Otherwise, use a darker shade
458 NS_HSV2RGB(mMozCellHighlightBackground
, hue
, sat
, luminance
, alpha
);
461 void nsLookAndFeel::NativeInit() { EnsureInit(); }
463 void nsLookAndFeel::RefreshImpl() {
464 mInitialized
= false;
467 nsXPLookAndFeel::RefreshImpl();
470 nsresult
nsLookAndFeel::NativeGetColor(ColorID aID
, ColorScheme aScheme
,
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
,
491 if (StaticPrefs::widget_non_native_theme_scrollbar_dark_themed()) {
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
;
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
;
513 case ColorID::Windowtext
:
514 case ColorID::MozDialogtext
:
515 aColor
= mMozWindowText
;
517 case ColorID::IMESelectedRawTextBackground
:
518 case ColorID::IMESelectedConvertedTextBackground
:
519 case ColorID::MozDragtargetzone
:
520 case ColorID::Highlight
: // preference selected item,
521 aColor
= mTextSelectedBackground
;
523 case ColorID::Highlighttext
:
524 if (NS_GET_A(mTextSelectedBackground
) < 155) {
525 aColor
= NS_SAME_AS_FOREGROUND_COLOR
;
529 case ColorID::IMESelectedRawTextForeground
:
530 case ColorID::IMESelectedConvertedTextForeground
:
531 aColor
= mTextSelectedText
;
533 case ColorID::Selecteditem
:
534 case ColorID::MozAccentColor
:
535 aColor
= mAccentColor
;
537 case ColorID::Selecteditemtext
:
538 case ColorID::MozAccentColorForeground
:
539 aColor
= mAccentColorForeground
;
541 case ColorID::MozCellhighlight
:
542 aColor
= mMozCellHighlightBackground
;
544 case ColorID::MozCellhighlighttext
:
545 aColor
= mMozCellHighlightText
;
547 case ColorID::IMERawInputBackground
:
548 case ColorID::IMEConvertedTextBackground
:
549 aColor
= NS_TRANSPARENT
;
551 case ColorID::IMERawInputForeground
:
552 case ColorID::IMEConvertedTextForeground
:
553 aColor
= NS_SAME_AS_FOREGROUND_COLOR
;
555 case ColorID::IMERawInputUnderline
:
556 case ColorID::IMEConvertedTextUnderline
:
557 aColor
= NS_SAME_AS_FOREGROUND_COLOR
;
559 case ColorID::IMESelectedRawTextUnderline
:
560 case ColorID::IMESelectedConvertedTextUnderline
:
561 aColor
= NS_TRANSPARENT
;
563 case ColorID::SpellCheckerUnderline
:
564 aColor
= NS_RGB(0xff, 0, 0);
566 case ColorID::Scrollbar
:
567 aColor
= mThemedScrollbar
;
569 case ColorID::ThemedScrollbar
:
570 aColor
= mThemedScrollbar
;
571 if (!ShouldUseThemedScrollbarColor(aID
, aColor
, mIsDark
)) {
572 return NS_ERROR_FAILURE
;
575 case ColorID::ThemedScrollbarInactive
:
576 aColor
= mThemedScrollbarInactive
;
577 if (!ShouldUseThemedScrollbarColor(aID
, aColor
, mIsDark
)) {
578 return NS_ERROR_FAILURE
;
581 case ColorID::ThemedScrollbarThumb
:
582 aColor
= mThemedScrollbarThumb
;
583 if (!ShouldUseThemedScrollbarColor(aID
, aColor
, mIsDark
)) {
584 return NS_ERROR_FAILURE
;
587 case ColorID::ThemedScrollbarThumbHover
:
588 aColor
= mThemedScrollbarThumbHover
;
589 if (!ShouldUseThemedScrollbarColor(aID
, aColor
, mIsDark
)) {
590 return NS_ERROR_FAILURE
;
593 case ColorID::ThemedScrollbarThumbActive
:
594 aColor
= mThemedScrollbarThumbActive
;
595 if (!ShouldUseThemedScrollbarColor(aID
, aColor
, mIsDark
)) {
596 return NS_ERROR_FAILURE
;
599 case ColorID::ThemedScrollbarThumbInactive
:
600 aColor
= mThemedScrollbarThumbInactive
;
601 if (!ShouldUseThemedScrollbarColor(aID
, aColor
, mIsDark
)) {
602 return NS_ERROR_FAILURE
;
606 // css2 http://www.w3.org/TR/REC-CSS2/ui.html#system-colors
607 case ColorID::Activeborder
:
608 // active window border
609 aColor
= mMozWindowActiveBorder
;
611 case ColorID::Inactiveborder
:
612 // inactive window border
613 aColor
= mMozWindowInactiveBorder
;
615 case ColorID::Graytext
: // disabled text in windows, menus, etc.
616 aColor
= mMenuTextInactive
;
618 case ColorID::Activecaption
:
619 aColor
= mTitlebarBackground
;
621 case ColorID::Captiontext
: // text in active window caption (titlebar)
622 aColor
= mTitlebarText
;
624 case ColorID::Inactivecaption
:
625 // inactive window caption
626 aColor
= mTitlebarInactiveBackground
;
628 case ColorID::Inactivecaptiontext
: // text in active window caption
630 aColor
= mTitlebarInactiveText
;
632 case ColorID::Infobackground
:
633 // tooltip background color
634 aColor
= mInfoBackground
;
636 case ColorID::Infotext
:
637 // tooltip text color
642 aColor
= mMenuBackground
;
644 case ColorID::Menutext
:
648 case ColorID::Threedface
:
649 case ColorID::Buttonface
:
650 case ColorID::MozButtondisabledface
:
652 aColor
= mMozWindowBackground
;
655 case ColorID::Buttontext
:
656 // text on push buttons
657 aColor
= mButtonText
;
660 case ColorID::Buttonhighlight
:
661 // 3-D highlighted edge color
662 case ColorID::Threedhighlight
:
663 // 3-D highlighted outer edge color
664 aColor
= mFrameOuterLightBorder
;
667 case ColorID::Buttonshadow
:
668 // 3-D shadow edge color
669 case ColorID::Threedshadow
:
670 // 3-D shadow inner edge color
671 aColor
= mFrameInnerDarkBorder
;
674 case ColorID::Threedlightshadow
:
675 case ColorID::MozDisabledfield
:
676 aColor
= NS_RGB(0xE0, 0xE0, 0xE0);
678 case ColorID::Threeddarkshadow
:
679 aColor
= NS_RGB(0xDC, 0xDC, 0xDC);
682 case ColorID::MozEventreerow
:
684 aColor
= mFieldBackground
;
686 case ColorID::Fieldtext
:
689 case ColorID::MozButtondefault
:
690 // default button border color
691 aColor
= mButtonDefault
;
693 case ColorID::MozButtonhoverface
:
694 case ColorID::MozButtonactiveface
:
695 aColor
= mButtonHoverFace
;
697 case ColorID::MozButtonhovertext
:
698 aColor
= mButtonHoverText
;
700 case ColorID::MozButtonactivetext
:
701 aColor
= mButtonActiveText
;
703 case ColorID::MozMenuhover
:
706 case ColorID::MozMenuhovertext
:
707 aColor
= mMenuHoverText
;
709 case ColorID::MozOddtreerow
:
710 aColor
= mOddCellBackground
;
712 case ColorID::MozNativehyperlinktext
:
713 aColor
= mNativeHyperLinkText
;
715 case ColorID::MozNativevisitedhyperlinktext
:
716 aColor
= mNativeVisitedHyperLinkText
;
718 case ColorID::MozComboboxtext
:
719 aColor
= mComboBoxText
;
721 case ColorID::MozMenubartext
:
722 aColor
= mMenuBarText
;
724 case ColorID::MozMenubarhovertext
:
725 aColor
= mMenuBarHoverText
;
727 case ColorID::MozColheadertext
:
728 aColor
= mMozColHeaderText
;
730 case ColorID::MozColheaderhovertext
:
731 aColor
= mMozColHeaderHoverText
;
734 /* default color is BLACK */
736 res
= NS_ERROR_FAILURE
;
743 static int32_t CheckWidgetStyle(GtkWidget
* aWidget
, const char* aStyle
,
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.
773 case IntID::ScrollButtonLeftMouseButtonAction
:
776 case IntID::ScrollButtonMiddleMouseButtonAction
:
779 case IntID::ScrollButtonRightMouseButtonAction
:
782 case IntID::CaretBlinkTime
:
784 aResult
= mCaretBlinkTime
;
786 case IntID::CaretBlinkCount
:
788 aResult
= mCaretBlinkCount
;
790 case IntID::CaretWidth
:
793 case IntID::ShowCaretDuringSelection
:
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
,
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
,
826 case IntID::SubmenuDelay
: {
827 GtkSettings
* settings
;
830 settings
= gtk_settings_get_default();
831 g_object_get(settings
, "gtk-menu-popup-delay", &delay
, nullptr);
832 aResult
= (int32_t)delay
;
835 case IntID::TooltipDelay
: {
839 case IntID::MenusCanOverlapOSBar
:
840 // we want XUL popups to be able to overlap the task bar.
843 case IntID::SkipNavigatingDisabledMenuItem
:
846 case IntID::DragThresholdX
:
847 case IntID::DragThresholdY
: {
849 g_object_get(gtk_settings_get_default(), "gtk-dnd-drag-threshold",
850 &threshold
, nullptr);
854 case IntID::ScrollArrowStyle
: {
855 GtkWidget
* scrollbar
= GetWidget(MOZ_GTK_SCROLLBAR_VERTICAL
);
856 aResult
= ConvertGTKStepperStyleToMozillaScrollArrowStyle(scrollbar
);
859 case IntID::ScrollSliderStyle
:
860 aResult
= eScrollThumbStyle_Proportional
;
862 case IntID::TreeOpenDelay
:
865 case IntID::TreeCloseDelay
:
868 case IntID::TreeLazyScrollDelay
:
871 case IntID::TreeScrollDelay
:
874 case IntID::TreeScrollLinesMax
:
877 case IntID::AlertNotificationOrigin
:
878 aResult
= NS_ALERT_TOP
;
880 case IntID::IMERawInputUnderlineStyle
:
881 case IntID::IMEConvertedTextUnderlineStyle
:
882 aResult
= NS_STYLE_TEXT_DECORATION_STYLE_SOLID
;
884 case IntID::IMESelectedRawTextUnderlineStyle
:
885 case IntID::IMESelectedConvertedTextUnderline
:
886 aResult
= NS_STYLE_TEXT_DECORATION_STYLE_NONE
;
888 case IntID::SpellCheckerUnderlineStyle
:
889 aResult
= NS_STYLE_TEXT_DECORATION_STYLE_WAVY
;
891 case IntID::MenuBarDrag
:
893 aResult
= mSystemTheme
.mMenuSupportsDrag
;
895 case IntID::ScrollbarButtonAutoRepeatBehavior
:
898 case IntID::SwipeAnimationEnabled
:
901 case IntID::ContextMenuOffsetVertical
:
902 case IntID::ContextMenuOffsetHorizontal
:
905 case IntID::GTKCSDAvailable
:
906 aResult
= sCSDAvailable
;
908 case IntID::GTKCSDMaximizeButton
:
910 aResult
= mCSDMaximizeButton
;
912 case IntID::GTKCSDMinimizeButton
:
914 aResult
= mCSDMinimizeButton
;
916 case IntID::GTKCSDCloseButton
:
918 aResult
= mCSDCloseButton
;
920 case IntID::GTKCSDReversedPlacement
:
922 aResult
= mCSDReversedPlacement
;
924 case IntID::PrefersReducedMotion
: {
925 aResult
= mPrefersReducedMotion
;
928 case IntID::SystemUsesDarkTheme
: {
930 if (mColorSchemePreference
) {
931 aResult
= *mColorSchemePreference
== ColorScheme::Dark
;
933 aResult
= mSystemTheme
.mIsDark
;
937 case IntID::GTKCSDMaximizeButtonPosition
:
938 aResult
= mCSDMaximizeButtonPosition
;
940 case IntID::GTKCSDMinimizeButtonPosition
:
941 aResult
= mCSDMinimizeButtonPosition
;
943 case IntID::GTKCSDCloseButtonPosition
:
944 aResult
= mCSDCloseButtonPosition
;
946 case IntID::UseAccessibilityTheme
: {
948 aResult
= mSystemTheme
.mHighContrast
;
951 case IntID::TitlebarRadius
: {
953 aResult
= EffectiveTheme().mTitlebarRadius
;
956 case IntID::GtkMenuRadius
: {
958 aResult
= EffectiveTheme().mMenuRadius
;
961 case IntID::AllowOverlayScrollbarsOverlap
: {
965 case IntID::ScrollbarFadeBeginDelay
: {
969 case IntID::ScrollbarFadeDuration
: {
973 case IntID::ScrollbarDisplayOnMouseMove
: {
977 case IntID::UseOverlayScrollbars
: {
978 aResult
= StaticPrefs::widget_gtk_overlay_scrollbars_enabled();
981 case IntID::TouchDeviceSupportPresent
:
982 aResult
= widget::WidgetUtilsGTK::IsTouchDeviceSupportPresent() ? 1 : 0;
986 res
= NS_ERROR_FAILURE
;
992 nsresult
nsLookAndFeel::NativeGetFloat(FloatID aID
, float& aResult
) {
995 case FloatID::IMEUnderlineRelativeSize
:
998 case FloatID::SpellCheckerUnderlineRelativeSize
:
1001 case FloatID::CaretAspectRatio
:
1003 aResult
= mSystemTheme
.mCaretRatio
;
1005 case FloatID::TextScaleFactor
:
1006 aResult
= gfxPlatformGtk::GetFontScaleFactor();
1010 rv
= NS_ERROR_FAILURE
;
1015 static void GetSystemFontInfo(GtkStyleContext
* aStyle
, nsString
* aFontName
,
1016 gfxFontStyle
* aFontStyle
) {
1017 aFontStyle
->style
= FontSlantStyle::Normal();
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",
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 {
1059 case FontID::Menu
: // css2
1060 case FontID::MozPullDownMenu
: // css3
1061 aFontName
= mMenuFontName
;
1062 aFontStyle
= mMenuFontStyle
;
1065 case FontID::MozField
: // css3
1066 case FontID::MozList
: // css3
1067 aFontName
= mFieldFontName
;
1068 aFontStyle
= mFieldFontStyle
;
1071 case FontID::MozButton
: // css3
1072 aFontName
= mButtonFontName
;
1073 aFontStyle
= mButtonFontStyle
;
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
1088 aFontName
= mDefaultFontName
;
1089 aFontStyle
= mDefaultFontStyle
;
1093 // Scale the font for the current monitor
1094 double scaleFactor
= StaticPrefs::layout_css_devPixelsPerPx();
1095 if (scaleFactor
> 0) {
1097 widget::ScreenHelperGTK::GetGTKMonitorScaleFactor() / scaleFactor
;
1099 // Convert gdk pixels to CSS pixels.
1100 aFontStyle
.size
/= gfxPlatformGtk::GetFontScaleFactor();
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
) {
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
);
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
)) {
1137 GdkRGBA backgroundColor
;
1138 gtk_style_context_get_background_color(style
, GTK_STATE_FLAG_NORMAL
,
1141 // Theme background color and default white html page background
1142 if (HasGoodContrastVisibility(backgroundColor
, white
)) {
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());
1153 GtkSettings
* settings
= gtk_settings_get_default();
1154 char* value
= nullptr;
1155 g_object_get(settings
, aKey
, &value
, nullptr);
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
,
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() {
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
) {
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");
1213 g_object_set(settings
, "gtk-theme-name", mSystemTheme
.mName
.get(),
1214 "gtk-application-prefer-dark-theme",
1215 mSystemTheme
.mPreferDarkTheme
, nullptr);
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);
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
;
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
,
1252 for (auto& s
: kSubstringsToRemove
) {
1253 potentialLightThemeName
= mSystemTheme
.mName
;
1254 potentialLightThemeName
.ReplaceSubstring(s
, ""_ns
);
1255 if (potentialLightThemeName
.Length() != mSystemTheme
.mName
.Length()) {
1261 g_object_set(settings
, "gtk-theme-name", potentialLightThemeName
.get(),
1267 if (mSystemTheme
.mIsDark
== GetThemeIsDark()) {
1268 // If the theme still didn't change enough, fall back to either Adwaita or
1270 g_object_set(settings
, "gtk-theme-name",
1271 mSystemTheme
.mIsDark
? "Adwaita" : "Adwaita-dark", nullptr);
1273 fellBackToDefaultTheme
= true;
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
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.
1311 if (NS_SUCCEEDED(Preferences::GetInt("ui.systemUsesDarkTheme", &pref
))) {
1312 return Some(pref
? ColorScheme::Dark
: ColorScheme::Light
);
1316 if (!mDBusSettingsProxy
) {
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
)));
1327 LOGLNF("color-scheme query error: %s\n", error
->message
);
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");
1340 switch (g_variant_get_uint32(variant
)) {
1342 MOZ_FALLTHROUGH_ASSERT("Unexpected color-scheme query return value");
1346 return Some(ColorScheme::Dark
);
1348 return Some(ColorScheme::Light
);
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");
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();
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
1407 g_object_get(settings
, "gtk-cursor-blink-time", &blink_time
,
1408 "gtk-cursor-blink-timeout", &blink_timeout
, "gtk-cursor-blink",
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.
1421 std::max(1, int32_t(std::ceil(float(blink_timeout
* 1000) /
1422 (float(blink_time
) * 2.0f
))));
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
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
;
1451 case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE
:
1452 mCSDMaximizeButton
= true;
1453 pos
= &mCSDMaximizeButtonPosition
;
1455 case MOZ_GTK_HEADER_BAR_BUTTON_CLOSE
:
1456 mCSDCloseButton
= true;
1457 pos
= &mCSDCloseButtonPosition
;
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
:
1484 if (!mColorSchemePreference
) {
1487 bool preferenceIsDark
= *mColorSchemePreference
== ColorScheme::Dark
;
1488 return preferenceIsDark
== mSystemTheme
.mIsDark
;
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);
1508 g_object_set(settings
, "gtk-theme-name", mAltTheme
.mName
.get(),
1509 "gtk-application-prefer-dark-theme",
1510 mAltTheme
.mPreferDarkTheme
, nullptr);
1513 mSystemThemeOverridden
= true;
1517 void nsLookAndFeel::GetGtkContentTheme(LookAndFeelTheme
& aTheme
) {
1518 if (NS_SUCCEEDED(Preferences::GetCString("widget.content.gtk-theme-override",
1519 aTheme
.themeName()))) {
1523 auto& theme
= StaticPrefs::widget_content_allow_gtk_dark_theme()
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
) {
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
)) {
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
)) {
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
1557 return NS_LUMINOSITY_DIFFERENCE(l
, aForForegroundColor
) >
1558 NS_LUMINOSITY_DIFFERENCE(d
, aForForegroundColor
)
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();
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
,
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
);
1595 NS_ComposeColors(mThemedScrollbar
, GDK_RGBA_TO_NS_RGBA(color
));
1596 gtk_style_context_get_background_color(style
, GTK_STATE_FLAG_BACKDROP
,
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
,
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
),
1610 mThemedScrollbarThumbActive
= GDK_RGBA_TO_NS_RGBA(color
);
1611 gtk_style_context_get_background_color(style
, GTK_STATE_FLAG_BACKDROP
,
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()) {
1621 if (!ShouldHonorThemeScrollbarColors()) {
1624 // If any of the scrollbar thumb colors are fully transparent, fall back to
1626 if (!NS_GET_A(mThemedScrollbarThumb
) ||
1627 !NS_GET_A(mThemedScrollbarThumbHover
) ||
1628 !NS_GET_A(mThemedScrollbarThumbActive
)) {
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) {
1640 if (fallbackToUnthemedColors
) {
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);
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
);
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
)) {
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
1736 color
= GetBackgroundColor(style
, mMenuText
, GTK_STATE_FLAG_BACKDROP
);
1737 if (NS_GET_A(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.
1743 "Couldn't find menu background color, falling back to window "
1745 return mMozWindowBackground
;
1748 if (!IsSolidCSDStyleUsed()) {
1749 mMenuRadius
= GetBorderRadius(style
);
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(
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
);
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
,
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(
1802 static_cast<GtkStateFlags
>(GTK_STATE_FLAG_FOCUSED
|
1803 GTK_STATE_FLAG_SELECTED
),
1805 mTextSelectedBackground
= GDK_RGBA_TO_NS_RGBA(color
);
1806 gtk_style_context_get_color(
1808 static_cast<GtkStateFlags
>(GTK_STATE_FLAG_FOCUSED
|
1809 GTK_STATE_FLAG_SELECTED
),
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.
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",
1837 gtk_style_context_lookup_color(style
, "theme_selected_fg_color",
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
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
))) {
1850 NS_ComposeColors(GDK_RGBA_TO_NS_RGBA(bg
), mAccentColor
);
1853 // A semi-transparent foreground color would be kinda silly, but is done
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
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
,
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
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");
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
,
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
1977 g_object_get(entry
, "invisible-char", &value
, nullptr);
1978 mInvisibleCharacter
= char16_t(value
);
1981 gtk_widget_style_get(entry
, "cursor-aspect-ratio", &mCaretRatio
, nullptr);
1983 GetSystemFontInfo(gtk_widget_get_style_context(entry
), &mFieldFontName
,
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
)) {
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
);
2004 char16_t
nsLookAndFeel::GetPasswordCharacterImpl() {
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
) {
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");
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
);
2060 void nsLookAndFeel::RecordLookAndFeelSpecificTelemetry() {
2061 // Gtk version we're on.
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
);
2092 #undef LOGLNF_ENABLED