diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /widget/gtk/nsLookAndFeel.cpp | |
parent | Initial commit. (diff) | |
download | firefox-upstream.tar.xz firefox-upstream.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | widget/gtk/nsLookAndFeel.cpp | 1536 |
1 files changed, 1536 insertions, 0 deletions
diff --git a/widget/gtk/nsLookAndFeel.cpp b/widget/gtk/nsLookAndFeel.cpp new file mode 100644 index 0000000000..0fdc7748ce --- /dev/null +++ b/widget/gtk/nsLookAndFeel.cpp @@ -0,0 +1,1536 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=2: + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// for strtod() +#include <stdlib.h> + +#include "nsLookAndFeel.h" + +#include <gtk/gtk.h> +#include <gdk/gdk.h> + +#include <pango/pango.h> +#include <pango/pango-fontmap.h> + +#include <fontconfig/fontconfig.h> +#include "gfxPlatformGtk.h" +#include "mozilla/FontPropertyTypes.h" +#include "mozilla/RelativeLuminanceUtils.h" +#include "mozilla/StaticPrefs_layout.h" +#include "mozilla/StaticPrefs_widget.h" +#include "mozilla/Telemetry.h" +#include "ScreenHelperGTK.h" +#include "nsNativeBasicThemeGTK.h" + +#include "gtkdrawing.h" +#include "nsStyleConsts.h" +#include "gfxFontConstants.h" +#include "WidgetUtils.h" +#include "nsWindow.h" + +#include "mozilla/gfx/2D.h" + +#include <cairo-gobject.h> +#include "WidgetStyleCache.h" +#include "prenv.h" +#include "nsCSSColorUtils.h" + +using namespace mozilla; +using mozilla::LookAndFeel; + +#undef LOG +#ifdef MOZ_LOGGING +# include "mozilla/Logging.h" +# include "nsTArray.h" +# include "Units.h" +extern mozilla::LazyLogModule gWidgetLog; +# define LOG(args) MOZ_LOG(gWidgetLog, mozilla::LogLevel::Debug, args) +#else +# define LOG(args) +#endif /* MOZ_LOGGING */ + +#define GDK_COLOR_TO_NS_RGB(c) \ + ((nscolor)NS_RGB(c.red >> 8, c.green >> 8, c.blue >> 8)) +#define GDK_RGBA_TO_NS_RGBA(c) \ + ((nscolor)NS_RGBA((int)((c).red * 255), (int)((c).green * 255), \ + (int)((c).blue * 255), (int)((c).alpha * 255))) + +nsLookAndFeel::nsLookAndFeel(const LookAndFeelCache* aCache) { + if (aCache) { + DoSetCache(*aCache); + } +} + +nsLookAndFeel::~nsLookAndFeel() = default; + +// Modifies color |*aDest| as if a pattern of color |aSource| was painted with +// CAIRO_OPERATOR_OVER to a surface with color |*aDest|. +static void ApplyColorOver(const GdkRGBA& aSource, GdkRGBA* aDest) { + gdouble sourceCoef = aSource.alpha; + gdouble destCoef = aDest->alpha * (1.0 - sourceCoef); + gdouble resultAlpha = sourceCoef + destCoef; + if (resultAlpha != 0.0) { // don't divide by zero + destCoef /= resultAlpha; + sourceCoef /= resultAlpha; + aDest->red = sourceCoef * aSource.red + destCoef * aDest->red; + aDest->green = sourceCoef * aSource.green + destCoef * aDest->green; + aDest->blue = sourceCoef * aSource.blue + destCoef * aDest->blue; + aDest->alpha = resultAlpha; + } +} + +static void GetLightAndDarkness(const GdkRGBA& aColor, double* aLightness, + double* aDarkness) { + double sum = aColor.red + aColor.green + aColor.blue; + *aLightness = sum * aColor.alpha; + *aDarkness = (3.0 - sum) * aColor.alpha; +} + +static bool GetGradientColors(const GValue* aValue, GdkRGBA* aLightColor, + GdkRGBA* aDarkColor) { + if (!G_TYPE_CHECK_VALUE_TYPE(aValue, CAIRO_GOBJECT_TYPE_PATTERN)) + return false; + + auto pattern = static_cast<cairo_pattern_t*>(g_value_get_boxed(aValue)); + if (!pattern) return false; + + // Just picking the lightest and darkest colors as simple samples rather + // than trying to blend, which could get messy if there are many stops. + if (CAIRO_STATUS_SUCCESS != + cairo_pattern_get_color_stop_rgba(pattern, 0, nullptr, &aDarkColor->red, + &aDarkColor->green, &aDarkColor->blue, + &aDarkColor->alpha)) + return false; + + double maxLightness, maxDarkness; + GetLightAndDarkness(*aDarkColor, &maxLightness, &maxDarkness); + *aLightColor = *aDarkColor; + + GdkRGBA stop; + for (int index = 1; + CAIRO_STATUS_SUCCESS == + cairo_pattern_get_color_stop_rgba(pattern, index, nullptr, &stop.red, + &stop.green, &stop.blue, &stop.alpha); + ++index) { + double lightness, darkness; + GetLightAndDarkness(stop, &lightness, &darkness); + if (lightness > maxLightness) { + maxLightness = lightness; + *aLightColor = stop; + } + if (darkness > maxDarkness) { + maxDarkness = darkness; + *aDarkColor = stop; + } + } + + return true; +} + +static bool GetUnicoBorderGradientColors(GtkStyleContext* aContext, + GdkRGBA* aLightColor, + GdkRGBA* aDarkColor) { + // Ubuntu 12.04 has GTK engine Unico-1.0.2, which overrides render_frame, + // providing its own border code. Ubuntu 14.04 has + // Unico-1.0.3+14.04.20140109, which does not override render_frame, and + // so does not need special attention. The earlier Unico can be detected + // by the -unico-border-gradient style property it registers. + // gtk_style_properties_lookup_property() is checked first to avoid the + // warning from gtk_style_context_get_property() when the property does + // not exist. (gtk_render_frame() of GTK+ 3.16 no longer uses the + // engine.) + const char* propertyName = "-unico-border-gradient"; + if (!gtk_style_properties_lookup_property(propertyName, nullptr, nullptr)) + return false; + + // -unico-border-gradient is used only when the CSS node's engine is Unico. + GtkThemingEngine* engine; + GtkStateFlags state = gtk_style_context_get_state(aContext); + gtk_style_context_get(aContext, state, "engine", &engine, nullptr); + if (strcmp(g_type_name(G_TYPE_FROM_INSTANCE(engine)), "UnicoEngine") != 0) + return false; + + // draw_border() of Unico engine uses -unico-border-gradient + // in preference to border-color. + GValue value = G_VALUE_INIT; + gtk_style_context_get_property(aContext, propertyName, state, &value); + + bool result = GetGradientColors(&value, aLightColor, aDarkColor); + + g_value_unset(&value); + return result; +} + +// Sets |aLightColor| and |aDarkColor| to colors from |aContext|. Returns +// true if |aContext| uses these colors to render a visible border. +// If returning false, then the colors returned are a fallback from the +// border-color value even though |aContext| does not use these colors to +// render a border. +static bool GetBorderColors(GtkStyleContext* aContext, GdkRGBA* aLightColor, + GdkRGBA* aDarkColor) { + // Determine whether the border on this style context is visible. + GtkStateFlags state = gtk_style_context_get_state(aContext); + GtkBorderStyle borderStyle; + gtk_style_context_get(aContext, state, GTK_STYLE_PROPERTY_BORDER_STYLE, + &borderStyle, nullptr); + bool visible = borderStyle != GTK_BORDER_STYLE_NONE && + borderStyle != GTK_BORDER_STYLE_HIDDEN; + if (visible) { + // GTK has an initial value of zero for border-widths, and so themes + // need to explicitly set border-widths to make borders visible. + GtkBorder border; + gtk_style_context_get_border(aContext, state, &border); + visible = border.top != 0 || border.right != 0 || border.bottom != 0 || + border.left != 0; + } + + if (visible && + GetUnicoBorderGradientColors(aContext, aLightColor, aDarkColor)) + return true; + + // The initial value for the border-color is the foreground color, and so + // this will usually return a color distinct from the background even if + // there is no visible border detected. + gtk_style_context_get_border_color(aContext, state, aDarkColor); + // TODO GTK3 - update aLightColor + // for GTK_BORDER_STYLE_INSET/OUTSET/GROVE/RIDGE border styles. + // https://bugzilla.mozilla.org/show_bug.cgi?id=978172#c25 + *aLightColor = *aDarkColor; + return visible; +} + +static bool GetBorderColors(GtkStyleContext* aContext, nscolor* aLightColor, + nscolor* aDarkColor) { + GdkRGBA lightColor, darkColor; + bool ret = GetBorderColors(aContext, &lightColor, &darkColor); + *aLightColor = GDK_RGBA_TO_NS_RGBA(lightColor); + *aDarkColor = GDK_RGBA_TO_NS_RGBA(darkColor); + return ret; +} + +// Finds ideal cell highlight colors used for unfocused+selected cells distinct +// from both Highlight, used as focused+selected background, and the listbox +// background which is assumed to be similar to -moz-field +nsresult nsLookAndFeel::InitCellHighlightColors() { + // NS_SUFFICIENT_LUMINOSITY_DIFFERENCE is the a11y standard for text + // on a background. Use 20% of that standard since we have a background + // on top of another background + int32_t minLuminosityDifference = NS_SUFFICIENT_LUMINOSITY_DIFFERENCE / 5; + int32_t backLuminosityDifference = + NS_LUMINOSITY_DIFFERENCE(mMozWindowBackground, mFieldBackground); + if (backLuminosityDifference >= minLuminosityDifference) { + mMozCellHighlightBackground = mMozWindowBackground; + mMozCellHighlightText = mMozWindowText; + return NS_OK; + } + + uint16_t hue, sat, luminance; + uint8_t alpha; + mMozCellHighlightBackground = mFieldBackground; + mMozCellHighlightText = mFieldText; + + NS_RGB2HSV(mMozCellHighlightBackground, hue, sat, luminance, alpha); + + uint16_t step = 30; + // Lighten the color if the color is very dark + if (luminance <= step) { + luminance += step; + } + // Darken it if it is very light + else if (luminance >= 255 - step) { + luminance -= step; + } + // Otherwise, compute what works best depending on the text luminance. + else { + uint16_t textHue, textSat, textLuminance; + uint8_t textAlpha; + NS_RGB2HSV(mMozCellHighlightText, textHue, textSat, textLuminance, + textAlpha); + // Text is darker than background, use a lighter shade + if (textLuminance < luminance) { + luminance += step; + } + // Otherwise, use a darker shade + else { + luminance -= step; + } + } + NS_HSV2RGB(mMozCellHighlightBackground, hue, sat, luminance, alpha); + return NS_OK; +} + +void nsLookAndFeel::NativeInit() { EnsureInit(); } + +void nsLookAndFeel::RefreshImpl() { + nsXPLookAndFeel::RefreshImpl(); + moz_gtk_refresh(); + + mInitialized = false; +} + +widget::LookAndFeelCache nsLookAndFeel::GetCacheImpl() { + LookAndFeelCache cache = nsXPLookAndFeel::GetCacheImpl(); + + constexpr IntID kIntIdsToCache[] = {IntID::SystemUsesDarkTheme, + IntID::PrefersReducedMotion, + IntID::UseAccessibilityTheme}; + + constexpr ColorID kColorIdsToCache[] = { + ColorID::ThemedScrollbar, + ColorID::ThemedScrollbarInactive, + ColorID::ThemedScrollbarThumb, + ColorID::ThemedScrollbarThumbHover, + ColorID::ThemedScrollbarThumbActive, + ColorID::ThemedScrollbarThumbInactive}; + + for (IntID id : kIntIdsToCache) { + cache.mInts().AppendElement(LookAndFeelInt(id, GetInt(id))); + } + + for (ColorID id : kColorIdsToCache) { + cache.mColors().AppendElement(LookAndFeelColor(id, GetColor(id))); + } + + return cache; +} + +void nsLookAndFeel::SetCacheImpl(const LookAndFeelCache& aCache) { + DoSetCache(aCache); +} + +void nsLookAndFeel::DoSetCache(const LookAndFeelCache& aCache) { + for (const auto& entry : aCache.mInts()) { + switch (entry.id()) { + case IntID::SystemUsesDarkTheme: + mSystemUsesDarkTheme = entry.value(); + break; + case IntID::PrefersReducedMotion: + mPrefersReducedMotion = entry.value(); + break; + case IntID::UseAccessibilityTheme: + mHighContrast = entry.value(); + break; + default: + MOZ_ASSERT_UNREACHABLE("Bogus Int ID in cache"); + break; + } + } + for (const auto& entry : aCache.mColors()) { + switch (entry.id()) { + case ColorID::ThemedScrollbar: + mThemedScrollbar = entry.color(); + break; + case ColorID::ThemedScrollbarInactive: + mThemedScrollbarInactive = entry.color(); + break; + case ColorID::ThemedScrollbarThumb: + mThemedScrollbarThumb = entry.color(); + break; + case ColorID::ThemedScrollbarThumbHover: + mThemedScrollbarThumbHover = entry.color(); + break; + case ColorID::ThemedScrollbarThumbActive: + mThemedScrollbarThumbActive = entry.color(); + break; + case ColorID::ThemedScrollbarThumbInactive: + mThemedScrollbarThumbInactive = entry.color(); + break; + default: + MOZ_ASSERT_UNREACHABLE("Bogus Color ID in cache"); + break; + } + } +} + +nsresult nsLookAndFeel::NativeGetColor(ColorID aID, nscolor& aColor) { + EnsureInit(); + + nsresult res = NS_OK; + + switch (aID) { + // These colors don't seem to be used for anything anymore in Mozilla + // (except here at least TextSelectBackground and TextSelectForeground) + // The CSS2 colors below are used. + case ColorID::WindowBackground: + case ColorID::WidgetBackground: + case ColorID::TextBackground: + case ColorID::Activecaption: // active window caption background + case ColorID::Appworkspace: // MDI background color + case ColorID::Background: // desktop background + case ColorID::Window: + case ColorID::Windowframe: + case ColorID::MozDialog: + case ColorID::MozCombobox: + aColor = mMozWindowBackground; + break; + case ColorID::WindowForeground: + case ColorID::WidgetForeground: + case ColorID::TextForeground: + case ColorID::Captiontext: // text in active window caption, size box, and + // scrollbar arrow box (!) + case ColorID::Windowtext: + case ColorID::MozDialogtext: + aColor = mMozWindowText; + break; + case ColorID::WidgetSelectBackground: + case ColorID::TextSelectBackground: + case ColorID::IMESelectedRawTextBackground: + case ColorID::IMESelectedConvertedTextBackground: + case ColorID::MozDragtargetzone: + case ColorID::MozHtmlCellhighlight: + case ColorID::Highlight: // preference selected item, + aColor = mTextSelectedBackground; + break; + case ColorID::WidgetSelectForeground: + case ColorID::TextSelectForeground: + case ColorID::IMESelectedRawTextForeground: + case ColorID::IMESelectedConvertedTextForeground: + case ColorID::Highlighttext: + case ColorID::MozHtmlCellhighlighttext: + aColor = mTextSelectedText; + break; + case ColorID::MozCellhighlight: + aColor = mMozCellHighlightBackground; + break; + case ColorID::MozCellhighlighttext: + aColor = mMozCellHighlightText; + break; + case ColorID::Widget3DHighlight: + aColor = NS_RGB(0xa0, 0xa0, 0xa0); + break; + case ColorID::Widget3DShadow: + aColor = NS_RGB(0x40, 0x40, 0x40); + break; + case ColorID::IMERawInputBackground: + case ColorID::IMEConvertedTextBackground: + aColor = NS_TRANSPARENT; + break; + case ColorID::IMERawInputForeground: + case ColorID::IMEConvertedTextForeground: + aColor = NS_SAME_AS_FOREGROUND_COLOR; + break; + case ColorID::IMERawInputUnderline: + case ColorID::IMEConvertedTextUnderline: + aColor = NS_SAME_AS_FOREGROUND_COLOR; + break; + case ColorID::IMESelectedRawTextUnderline: + case ColorID::IMESelectedConvertedTextUnderline: + aColor = NS_TRANSPARENT; + break; + case ColorID::SpellCheckerUnderline: + aColor = NS_RGB(0xff, 0, 0); + break; + case ColorID::ThemedScrollbar: + aColor = mThemedScrollbar; + break; + case ColorID::ThemedScrollbarInactive: + aColor = mThemedScrollbarInactive; + break; + case ColorID::ThemedScrollbarThumb: + aColor = mThemedScrollbarThumb; + break; + case ColorID::ThemedScrollbarThumbHover: + aColor = mThemedScrollbarThumbHover; + break; + case ColorID::ThemedScrollbarThumbActive: + aColor = mThemedScrollbarThumbActive; + break; + case ColorID::ThemedScrollbarThumbInactive: + aColor = mThemedScrollbarThumbInactive; + break; + + // css2 http://www.w3.org/TR/REC-CSS2/ui.html#system-colors + case ColorID::Activeborder: + // active window border + aColor = mMozWindowActiveBorder; + break; + case ColorID::Inactiveborder: + // inactive window border + aColor = mMozWindowInactiveBorder; + break; + case ColorID::Graytext: // disabled text in windows, menus, etc. + case ColorID::Inactivecaptiontext: // text in inactive window caption + aColor = mMenuTextInactive; + break; + case ColorID::Inactivecaption: + // inactive window caption + aColor = mMozWindowInactiveCaption; + break; + case ColorID::Infobackground: + // tooltip background color + aColor = mInfoBackground; + break; + case ColorID::Infotext: + // tooltip text color + aColor = mInfoText; + break; + case ColorID::Menu: + // menu background + aColor = mMenuBackground; + break; + case ColorID::Menutext: + // menu text + aColor = mMenuText; + break; + case ColorID::Scrollbar: + // scrollbar gray area + aColor = mMozScrollbar; + break; + + case ColorID::Threedface: + case ColorID::Buttonface: + // 3-D face color + aColor = mMozWindowBackground; + break; + + case ColorID::Buttontext: + // text on push buttons + aColor = mButtonText; + break; + + case ColorID::Buttonhighlight: + // 3-D highlighted edge color + case ColorID::Threedhighlight: + // 3-D highlighted outer edge color + aColor = mFrameOuterLightBorder; + break; + + case ColorID::Buttonshadow: + // 3-D shadow edge color + case ColorID::Threedshadow: + // 3-D shadow inner edge color + aColor = mFrameInnerDarkBorder; + break; + + case ColorID::Threedlightshadow: + aColor = NS_RGB(0xE0, 0xE0, 0xE0); + break; + case ColorID::Threeddarkshadow: + aColor = NS_RGB(0xDC, 0xDC, 0xDC); + break; + + case ColorID::MozEventreerow: + case ColorID::Field: + aColor = mFieldBackground; + break; + case ColorID::Fieldtext: + aColor = mFieldText; + break; + case ColorID::MozButtondefault: + // default button border color + aColor = mButtonDefault; + break; + case ColorID::MozButtonhoverface: + aColor = mButtonHoverFace; + break; + case ColorID::MozButtonhovertext: + aColor = mButtonHoverText; + break; + case ColorID::MozGtkButtonactivetext: + aColor = mButtonActiveText; + break; + case ColorID::MozMenuhover: + aColor = mMenuHover; + break; + case ColorID::MozMenuhovertext: + aColor = mMenuHoverText; + break; + case ColorID::MozOddtreerow: + aColor = mOddCellBackground; + break; + case ColorID::MozNativehyperlinktext: + aColor = mNativeHyperLinkText; + break; + case ColorID::MozComboboxtext: + aColor = mComboBoxText; + break; + case ColorID::MozMenubartext: + aColor = mMenuBarText; + break; + case ColorID::MozMenubarhovertext: + aColor = mMenuBarHoverText; + break; + case ColorID::MozGtkInfoBarText: + aColor = mInfoBarText; + break; + case ColorID::MozColheadertext: + aColor = mMozColHeaderText; + break; + case ColorID::MozColheaderhovertext: + aColor = mMozColHeaderHoverText; + break; + default: + /* default color is BLACK */ + aColor = 0; + res = NS_ERROR_FAILURE; + break; + } + + return res; +} + +static int32_t CheckWidgetStyle(GtkWidget* aWidget, const char* aStyle, + int32_t aResult) { + gboolean value = FALSE; + gtk_widget_style_get(aWidget, aStyle, &value, nullptr); + return value ? aResult : 0; +} + +static int32_t ConvertGTKStepperStyleToMozillaScrollArrowStyle( + GtkWidget* aWidget) { + if (!aWidget) return mozilla::LookAndFeel::eScrollArrowStyle_Single; + + return CheckWidgetStyle(aWidget, "has-backward-stepper", + mozilla::LookAndFeel::eScrollArrow_StartBackward) | + CheckWidgetStyle(aWidget, "has-forward-stepper", + mozilla::LookAndFeel::eScrollArrow_EndForward) | + CheckWidgetStyle(aWidget, "has-secondary-backward-stepper", + mozilla::LookAndFeel::eScrollArrow_EndBackward) | + CheckWidgetStyle(aWidget, "has-secondary-forward-stepper", + mozilla::LookAndFeel::eScrollArrow_StartForward); +} + +nsresult nsLookAndFeel::NativeGetInt(IntID aID, int32_t& aResult) { + nsresult res = NS_OK; + + // We use delayed initialization by EnsureInit() here + // to make sure mozilla::Preferences is available (Bug 115807). + // IntID::UseAccessibilityTheme is requested before user preferences + // are read, and so EnsureInit(), which depends on preference values, + // is deliberately delayed until required. + switch (aID) { + case IntID::ScrollButtonLeftMouseButtonAction: + aResult = 0; + break; + case IntID::ScrollButtonMiddleMouseButtonAction: + aResult = 1; + break; + case IntID::ScrollButtonRightMouseButtonAction: + aResult = 2; + break; + case IntID::CaretBlinkTime: + EnsureInit(); + aResult = mCaretBlinkTime; + break; + case IntID::CaretWidth: + aResult = 1; + break; + case IntID::ShowCaretDuringSelection: + aResult = 0; + break; + case IntID::SelectTextfieldsOnKeyFocus: { + GtkSettings* settings; + gboolean select_on_focus; + + settings = gtk_settings_get_default(); + g_object_get(settings, "gtk-entry-select-on-focus", &select_on_focus, + nullptr); + + if (select_on_focus) + aResult = 1; + else + aResult = 0; + + } break; + case IntID::ScrollToClick: { + GtkSettings* settings; + gboolean warps_slider = FALSE; + + settings = gtk_settings_get_default(); + if (g_object_class_find_property(G_OBJECT_GET_CLASS(settings), + "gtk-primary-button-warps-slider")) { + g_object_get(settings, "gtk-primary-button-warps-slider", &warps_slider, + nullptr); + } + + if (warps_slider) + aResult = 1; + else + aResult = 0; + } break; + case IntID::SubmenuDelay: { + GtkSettings* settings; + gint delay; + + settings = gtk_settings_get_default(); + g_object_get(settings, "gtk-menu-popup-delay", &delay, nullptr); + aResult = (int32_t)delay; + break; + } + case IntID::TooltipDelay: { + aResult = 500; + break; + } + case IntID::MenusCanOverlapOSBar: + // we want XUL popups to be able to overlap the task bar. + aResult = 1; + break; + case IntID::SkipNavigatingDisabledMenuItem: + aResult = 1; + break; + case IntID::DragThresholdX: + case IntID::DragThresholdY: { + gint threshold = 0; + g_object_get(gtk_settings_get_default(), "gtk-dnd-drag-threshold", + &threshold, nullptr); + + aResult = threshold; + } break; + case IntID::ScrollArrowStyle: { + GtkWidget* scrollbar = GetWidget(MOZ_GTK_SCROLLBAR_HORIZONTAL); + aResult = ConvertGTKStepperStyleToMozillaScrollArrowStyle(scrollbar); + break; + } + case IntID::ScrollSliderStyle: + aResult = eScrollThumbStyle_Proportional; + break; + case IntID::TreeOpenDelay: + aResult = 1000; + break; + case IntID::TreeCloseDelay: + aResult = 1000; + break; + case IntID::TreeLazyScrollDelay: + aResult = 150; + break; + case IntID::TreeScrollDelay: + aResult = 100; + break; + case IntID::TreeScrollLinesMax: + aResult = 3; + break; + case IntID::DWMCompositor: + case IntID::WindowsClassic: + case IntID::WindowsDefaultTheme: + case IntID::WindowsThemeIdentifier: + case IntID::OperatingSystemVersionIdentifier: + aResult = 0; + res = NS_ERROR_NOT_IMPLEMENTED; + break; + case IntID::TouchEnabled: + aResult = mozilla::widget::WidgetUtils::IsTouchDeviceSupportPresent(); + break; + case IntID::MacGraphiteTheme: + aResult = 0; + res = NS_ERROR_NOT_IMPLEMENTED; + break; + case IntID::AlertNotificationOrigin: + aResult = NS_ALERT_TOP; + break; + case IntID::IMERawInputUnderlineStyle: + case IntID::IMEConvertedTextUnderlineStyle: + aResult = NS_STYLE_TEXT_DECORATION_STYLE_SOLID; + break; + case IntID::IMESelectedRawTextUnderlineStyle: + case IntID::IMESelectedConvertedTextUnderline: + aResult = NS_STYLE_TEXT_DECORATION_STYLE_NONE; + break; + case IntID::SpellCheckerUnderlineStyle: + aResult = NS_STYLE_TEXT_DECORATION_STYLE_WAVY; + break; + case IntID::MenuBarDrag: + EnsureInit(); + aResult = mMenuSupportsDrag; + break; + case IntID::ScrollbarButtonAutoRepeatBehavior: + aResult = 1; + break; + case IntID::SwipeAnimationEnabled: + aResult = 0; + break; + case IntID::ContextMenuOffsetVertical: + case IntID::ContextMenuOffsetHorizontal: + aResult = 2; + break; + case IntID::GTKCSDAvailable: + EnsureInit(); + aResult = mCSDAvailable; + break; + case IntID::GTKCSDHideTitlebarByDefault: + EnsureInit(); + aResult = mCSDHideTitlebarByDefault; + break; + case IntID::GTKCSDMaximizeButton: + EnsureInit(); + aResult = mCSDMaximizeButton; + break; + case IntID::GTKCSDMinimizeButton: + EnsureInit(); + aResult = mCSDMinimizeButton; + break; + case IntID::GTKCSDCloseButton: + EnsureInit(); + aResult = mCSDCloseButton; + break; + case IntID::GTKCSDTransparentBackground: { + // Enable transparent titlebar corners for titlebar mode. + GdkScreen* screen = gdk_screen_get_default(); + aResult = gdk_screen_is_composited(screen) + ? (nsWindow::GetSystemCSDSupportLevel() != + nsWindow::CSD_SUPPORT_NONE) + : false; + break; + } + case IntID::GTKCSDReversedPlacement: + EnsureInit(); + aResult = mCSDReversedPlacement; + break; + case IntID::PrefersReducedMotion: { + aResult = mPrefersReducedMotion; + break; + } + case IntID::SystemUsesDarkTheme: { + EnsureInit(); + aResult = mSystemUsesDarkTheme; + break; + } + case IntID::GTKCSDMaximizeButtonPosition: + aResult = mCSDMaximizeButtonPosition; + break; + case IntID::GTKCSDMinimizeButtonPosition: + aResult = mCSDMinimizeButtonPosition; + break; + case IntID::GTKCSDCloseButtonPosition: + aResult = mCSDCloseButtonPosition; + break; + case IntID::UseAccessibilityTheme: { + EnsureInit(); + aResult = mHighContrast; + break; + } + default: + aResult = 0; + res = NS_ERROR_FAILURE; + } + + return res; +} + +nsresult nsLookAndFeel::NativeGetFloat(FloatID aID, float& aResult) { + nsresult rv = NS_OK; + switch (aID) { + case FloatID::IMEUnderlineRelativeSize: + aResult = 1.0f; + break; + case FloatID::SpellCheckerUnderlineRelativeSize: + aResult = 1.0f; + break; + case FloatID::CaretAspectRatio: + EnsureInit(); + aResult = mCaretRatio; + break; + default: + aResult = -1.0; + rv = NS_ERROR_FAILURE; + } + return rv; +} + +static void GetSystemFontInfo(GtkStyleContext* aStyle, nsString* aFontName, + gfxFontStyle* aFontStyle) { + aFontStyle->style = FontSlantStyle::Normal(); + + // As in + // https://git.gnome.org/browse/gtk+/tree/gtk/gtkwidget.c?h=3.22.19#n10333 + PangoFontDescription* desc; + gtk_style_context_get(aStyle, gtk_style_context_get_state(aStyle), "font", + &desc, nullptr); + + aFontStyle->systemFont = true; + + constexpr auto quote = u"\""_ns; + NS_ConvertUTF8toUTF16 family(pango_font_description_get_family(desc)); + *aFontName = quote + family + quote; + + aFontStyle->weight = FontWeight(pango_font_description_get_weight(desc)); + + // FIXME: Set aFontStyle->stretch correctly! + aFontStyle->stretch = FontStretch::Normal(); + + float size = float(pango_font_description_get_size(desc)) / PANGO_SCALE; + + // |size| is now either pixels or pango-points (not Mozilla-points!) + + if (!pango_font_description_get_size_is_absolute(desc)) { + // |size| is in pango-points, so convert to pixels. + size *= float(gfxPlatformGtk::GetFontScaleDPI()) / POINTS_PER_INCH_FLOAT; + } + + // |size| is now pixels but not scaled for the hidpi displays, + aFontStyle->size = size; + + pango_font_description_free(desc); +} + +bool nsLookAndFeel::NativeGetFont(FontID aID, nsString& aFontName, + gfxFontStyle& aFontStyle) { + switch (aID) { + case FontID::Menu: // css2 + case FontID::PullDownMenu: // css3 + aFontName = mMenuFontName; + aFontStyle = mMenuFontStyle; + break; + + case FontID::Field: // css3 + case FontID::List: // css3 + aFontName = mFieldFontName; + aFontStyle = mFieldFontStyle; + break; + + case FontID::Button: // css3 + aFontName = mButtonFontName; + aFontStyle = mButtonFontStyle; + break; + + case FontID::Caption: // css2 + case FontID::Icon: // css2 + case FontID::MessageBox: // css2 + case FontID::SmallCaption: // css2 + case FontID::StatusBar: // css2 + case FontID::Window: // css3 + case FontID::Document: // css3 + case FontID::Workspace: // css3 + case FontID::Desktop: // css3 + case FontID::Info: // css3 + case FontID::Dialog: // css3 + case FontID::Tooltips: // moz + case FontID::Widget: // moz + default: + aFontName = mDefaultFontName; + aFontStyle = mDefaultFontStyle; + break; + } + + // Scale the font for the current monitor + double scaleFactor = StaticPrefs::layout_css_devPixelsPerPx(); + if (scaleFactor > 0) { + aFontStyle.size *= + widget::ScreenHelperGTK::GetGTKMonitorScaleFactor() / scaleFactor; + } else { + // Convert gdk pixels to CSS pixels. + aFontStyle.size /= gfxPlatformGtk::GetFontScaleFactor(); + } + + return true; +} + +// Check color contrast according to +// https://www.w3.org/TR/AERT/#color-contrast +static bool HasGoodContrastVisibility(GdkRGBA& aColor1, GdkRGBA& aColor2) { + int32_t luminosityDifference = NS_LUMINOSITY_DIFFERENCE( + GDK_RGBA_TO_NS_RGBA(aColor1), GDK_RGBA_TO_NS_RGBA(aColor2)); + if (luminosityDifference < NS_SUFFICIENT_LUMINOSITY_DIFFERENCE) { + return false; + } + + double colorDifference = std::abs(aColor1.red - aColor2.red) + + std::abs(aColor1.green - aColor2.green) + + std::abs(aColor1.blue - aColor2.blue); + return (colorDifference * 255.0 > 500.0); +} + +// Check if the foreground/background colors match with default white/black +// html page colors. +static bool IsGtkThemeCompatibleWithHTMLColors() { + GdkRGBA white = {1.0, 1.0, 1.0}; + GdkRGBA black = {0.0, 0.0, 0.0}; + + GtkStyleContext* style = GetStyleContext(MOZ_GTK_WINDOW); + + GdkRGBA textColor; + gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &textColor); + + // Theme text color and default white html page background + if (!HasGoodContrastVisibility(textColor, white)) { + return false; + } + + GdkRGBA backgroundColor; + gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, + &backgroundColor); + + // Theme background color and default white html page background + if (HasGoodContrastVisibility(backgroundColor, white)) { + return false; + } + + // Theme background color and default black text color + return HasGoodContrastVisibility(backgroundColor, black); +} + +static nsCString GetGtkTheme() { + MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread()); + nsCString ret; + GtkSettings* settings = gtk_settings_get_default(); + char* themeName = nullptr; + g_object_get(settings, "gtk-theme-name", &themeName, nullptr); + if (themeName) { + ret.Assign(themeName); + g_free(themeName); + } + return ret; +} + +static bool GetPreferDarkTheme() { + GtkSettings* settings = gtk_settings_get_default(); + gboolean preferDarkTheme = FALSE; + g_object_get(settings, "gtk-application-prefer-dark-theme", &preferDarkTheme, + nullptr); + return preferDarkTheme == TRUE; +} + +void nsLookAndFeel::ConfigureTheme(const LookAndFeelTheme& aTheme) { + MOZ_ASSERT(XRE_IsContentProcess()); + GtkSettings* settings = gtk_settings_get_default(); + g_object_set(settings, "gtk-theme-name", aTheme.themeName().get(), + "gtk-application-prefer-dark-theme", + aTheme.preferDarkTheme() ? TRUE : FALSE, nullptr); +} + +void nsLookAndFeel::WithThemeConfiguredForContent( + const std::function<void(const LookAndFeelTheme& aTheme)>& aFn) { + nsWindow::WithSettingsChangesIgnored([&]() { + // Available on Gtk 3.20+. + static auto sGtkSettingsResetProperty = + (void (*)(GtkSettings*, const gchar*))dlsym( + RTLD_DEFAULT, "gtk_settings_reset_property"); + + nsCString themeName; + bool preferDarkTheme = false; + + if (!sGtkSettingsResetProperty) { + // When gtk_settings_reset_property is not available, we instead + // record the current theme name and variant and explicitly restore + // them afterwards. This means we won't respond to any subsequent + // theme settings changes, which is unfortunate. (It's possible we + // could listen to xsettings changes and update the GtkSettings object + // ourselves in response, if we wanted to fix this.) + themeName = GetGtkTheme(); + preferDarkTheme = GetPreferDarkTheme(); + } + + bool changed = ConfigureContentGtkTheme(); + if (changed) { + RefreshImpl(); + } + + LookAndFeelTheme theme; + theme.themeName() = GetGtkTheme(); + theme.preferDarkTheme() = GetPreferDarkTheme(); + + aFn(theme); + + if (changed) { + GtkSettings* settings = gtk_settings_get_default(); + if (sGtkSettingsResetProperty) { + sGtkSettingsResetProperty(settings, "gtk-theme-name"); + sGtkSettingsResetProperty(settings, + "gtk-application-prefer-dark-theme"); + } else { + g_object_set(settings, "gtk-theme-name", themeName.get(), + "gtk-application-prefer-dark-theme", + preferDarkTheme ? TRUE : FALSE, nullptr); + } + RefreshImpl(); + } + }); +} + +bool nsLookAndFeel::ConfigureContentGtkTheme() { + bool changed = false; + + GtkSettings* settings = gtk_settings_get_default(); + + nsAutoCString themeOverride; + mozilla::Preferences::GetCString("widget.content.gtk-theme-override", + themeOverride); + if (!themeOverride.IsEmpty()) { + g_object_set(settings, "gtk-theme-name", themeOverride.get(), nullptr); + changed = true; + LOG(("ConfigureContentGtkTheme(%s)\n", themeOverride.get())); + } else { + LOG(("ConfigureContentGtkTheme(%s)\n", GetGtkTheme().get())); + } + + // Dark theme is active but user explicitly enables it, or we're on + // high-contrast (in which case we prevent content to mess up with the colors + // of the page), so we're done now. + if (!themeOverride.IsEmpty() || mHighContrast || + StaticPrefs::widget_content_allow_gtk_dark_theme()) { + return changed; + } + + // Try to select the light variant of the current theme first... + if (GetPreferDarkTheme()) { + LOG((" disabling gtk-application-prefer-dark-theme\n")); + g_object_set(settings, "gtk-application-prefer-dark-theme", FALSE, nullptr); + changed = true; + } + + // ...and use a default Gtk theme as a fallback. + if (!IsGtkThemeCompatibleWithHTMLColors()) { + LOG((" Non-compatible dark theme, default to Adwaita\n")); + g_object_set(settings, "gtk-theme-name", "Adwaita", nullptr); + changed = true; + } + + return changed; +} + +void nsLookAndFeel::EnsureInit() { + if (mInitialized) { + return; + } + + // Gtk manages a screen's CSS in the settings object so we + // ask Gtk to create it explicitly. Otherwise we may end up + // with wrong color theme, see Bug 972382 + GtkSettings* settings = gtk_settings_get_default(); + if (MOZ_UNLIKELY(!settings)) { + NS_WARNING("EnsureInit: No settings"); + return; + } + + mInitialized = true; + + // gtk does non threadsafe refcounting + MOZ_ASSERT(NS_IsMainThread()); + + GtkStyleContext* style; + GdkRGBA color; + + if (XRE_IsContentProcess()) { + LOG(("nsLookAndFeel::EnsureInit() [%p] Content process\n", (void*)this)); + // Dark themes interacts poorly with widget styling (see bug 1216658). + // We disable dark themes by default for web content + // but allow user to overide it by prefs. + ConfigureContentGtkTheme(); + } else { + // It seems GTK doesn't have an API to query if the current theme is + // "light" or "dark", so we synthesize it from the CSS2 Window/WindowText + // colors instead, by comparing their luminosity. + GdkRGBA bg, fg; + style = GetStyleContext(MOZ_GTK_WINDOW); + gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &bg); + gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &fg); + LOG(("nsLookAndFeel::EnsureInit() [%p] Chrome process\n", (void*)this)); + // Update mSystemUsesDarkTheme only in the parent process since in the child + // processes we forcibly set gtk-theme-name so that we can't get correct + // results. Instead mSystemUsesDarkTheme in the child processes is updated + // via our caching machinery. + mSystemUsesDarkTheme = + (RelativeLuminanceUtils::Compute(GDK_RGBA_TO_NS_RGBA(bg)) < + RelativeLuminanceUtils::Compute(GDK_RGBA_TO_NS_RGBA(fg))); + + mHighContrast = StaticPrefs::widget_content_gtk_high_contrast_enabled() && + GetGtkTheme().Find("HighContrast"_ns) >= 0; + + gboolean enableAnimations = false; + g_object_get(settings, "gtk-enable-animations", &enableAnimations, nullptr); + mPrefersReducedMotion = !enableAnimations; + + // Colors that we pass to content processes through the LookAndFeelCache. + if (ShouldHonorThemeScrollbarColors()) { + // Some themes style the <trough>, while others style the <scrollbar> + // itself, so we look at both and compose the colors. + style = GetStyleContext(MOZ_GTK_SCROLLBAR_VERTICAL); + gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, + &color); + mThemedScrollbar = GDK_RGBA_TO_NS_RGBA(color); + gtk_style_context_get_background_color(style, GTK_STATE_FLAG_BACKDROP, + &color); + mThemedScrollbarInactive = GDK_RGBA_TO_NS_RGBA(color); + + style = GetStyleContext(MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL); + gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, + &color); + mThemedScrollbar = + NS_ComposeColors(mThemedScrollbar, GDK_RGBA_TO_NS_RGBA(color)); + gtk_style_context_get_background_color(style, GTK_STATE_FLAG_BACKDROP, + &color); + mThemedScrollbarInactive = NS_ComposeColors(mThemedScrollbarInactive, + GDK_RGBA_TO_NS_RGBA(color)); + + mMozScrollbar = mThemedScrollbar; + + style = GetStyleContext(MOZ_GTK_SCROLLBAR_THUMB_VERTICAL); + gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, + &color); + mThemedScrollbarThumb = GDK_RGBA_TO_NS_RGBA(color); + gtk_style_context_get_background_color(style, GTK_STATE_FLAG_PRELIGHT, + &color); + mThemedScrollbarThumbHover = GDK_RGBA_TO_NS_RGBA(color); + gtk_style_context_get_background_color( + style, GtkStateFlags(GTK_STATE_FLAG_PRELIGHT | GTK_STATE_FLAG_ACTIVE), + &color); + mThemedScrollbarThumbActive = GDK_RGBA_TO_NS_RGBA(color); + gtk_style_context_get_background_color(style, GTK_STATE_FLAG_BACKDROP, + &color); + mThemedScrollbarThumbInactive = GDK_RGBA_TO_NS_RGBA(color); + } else { + mMozScrollbar = mThemedScrollbar = widget::sScrollbarColor.ToABGR(); + mThemedScrollbarInactive = widget::sScrollbarColor.ToABGR(); + mThemedScrollbarThumb = widget::sScrollbarThumbColor.ToABGR(); + mThemedScrollbarThumbHover = widget::sScrollbarThumbColorHover.ToABGR(); + mThemedScrollbarThumbActive = widget::sScrollbarThumbColorActive.ToABGR(); + mThemedScrollbarThumbInactive = widget::sScrollbarThumbColor.ToABGR(); + } + } + + // The label is not added to a parent widget, but shared for constructing + // different style contexts. The node hierarchy is constructed only on + // the label style context. + GtkWidget* labelWidget = gtk_label_new("M"); + g_object_ref_sink(labelWidget); + + // Window colors + style = GetStyleContext(MOZ_GTK_WINDOW); + gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color); + mMozWindowBackground = GDK_RGBA_TO_NS_RGBA(color); + gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color); + mMozWindowText = GDK_RGBA_TO_NS_RGBA(color); + gtk_style_context_get_border_color(style, GTK_STATE_FLAG_NORMAL, &color); + mMozWindowActiveBorder = GDK_RGBA_TO_NS_RGBA(color); + gtk_style_context_get_border_color(style, GTK_STATE_FLAG_INSENSITIVE, &color); + mMozWindowInactiveBorder = GDK_RGBA_TO_NS_RGBA(color); + gtk_style_context_get_background_color(style, GTK_STATE_FLAG_INSENSITIVE, + &color); + mMozWindowInactiveCaption = GDK_RGBA_TO_NS_RGBA(color); + + style = GetStyleContext(MOZ_GTK_WINDOW_CONTAINER); + { + GtkStyleContext* labelStyle = CreateStyleForWidget(labelWidget, style); + GetSystemFontInfo(labelStyle, &mDefaultFontName, &mDefaultFontStyle); + g_object_unref(labelStyle); + } + + // tooltip foreground and background + style = GetStyleContext(MOZ_GTK_TOOLTIP); + gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color); + mInfoBackground = GDK_RGBA_TO_NS_RGBA(color); + + style = GetStyleContext(MOZ_GTK_TOOLTIP_BOX_LABEL); + gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color); + mInfoText = GDK_RGBA_TO_NS_RGBA(color); + + style = GetStyleContext(MOZ_GTK_MENUITEM); + { + GtkStyleContext* accelStyle = + CreateStyleForWidget(gtk_accel_label_new("M"), style); + + GetSystemFontInfo(accelStyle, &mMenuFontName, &mMenuFontStyle); + + gtk_style_context_get_color(accelStyle, GTK_STATE_FLAG_NORMAL, &color); + mMenuText = GDK_RGBA_TO_NS_RGBA(color); + gtk_style_context_get_color(accelStyle, GTK_STATE_FLAG_INSENSITIVE, &color); + mMenuTextInactive = GDK_RGBA_TO_NS_RGBA(color); + g_object_unref(accelStyle); + } + + style = GetStyleContext(MOZ_GTK_MENUPOPUP); + gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color); + mMenuBackground = GDK_RGBA_TO_NS_RGBA(color); + + style = GetStyleContext(MOZ_GTK_MENUITEM); + gtk_style_context_get_background_color(style, GTK_STATE_FLAG_PRELIGHT, + &color); + mMenuHover = GDK_RGBA_TO_NS_RGBA(color); + gtk_style_context_get_color(style, GTK_STATE_FLAG_PRELIGHT, &color); + mMenuHoverText = GDK_RGBA_TO_NS_RGBA(color); + + GtkWidget* parent = gtk_fixed_new(); + GtkWidget* window = gtk_window_new(GTK_WINDOW_POPUP); + GtkWidget* treeView = gtk_tree_view_new(); + GtkWidget* linkButton = gtk_link_button_new("http://example.com/"); + GtkWidget* menuBar = gtk_menu_bar_new(); + GtkWidget* menuBarItem = gtk_menu_item_new(); + GtkWidget* entry = gtk_entry_new(); + GtkWidget* textView = gtk_text_view_new(); + + gtk_container_add(GTK_CONTAINER(parent), treeView); + gtk_container_add(GTK_CONTAINER(parent), linkButton); + gtk_container_add(GTK_CONTAINER(parent), menuBar); + gtk_menu_shell_append(GTK_MENU_SHELL(menuBar), menuBarItem); + gtk_container_add(GTK_CONTAINER(window), parent); + gtk_container_add(GTK_CONTAINER(parent), entry); + gtk_container_add(GTK_CONTAINER(parent), textView); + + // Text colors + GdkRGBA bgColor; + // If the text window background is translucent, then the background of + // the textview root node is visible. + style = GetStyleContext(MOZ_GTK_TEXT_VIEW); + gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, + &bgColor); + + style = GetStyleContext(MOZ_GTK_TEXT_VIEW_TEXT); + gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color); + ApplyColorOver(color, &bgColor); + mFieldBackground = GDK_RGBA_TO_NS_RGBA(bgColor); + gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color); + mFieldText = GDK_RGBA_TO_NS_RGBA(color); + + // Selected text and background + { + GtkStyleContext* selectionStyle = + GetStyleContext(MOZ_GTK_TEXT_VIEW_TEXT_SELECTION); + auto GrabSelectionColors = [&](GtkStyleContext* style) { + gtk_style_context_get_background_color( + style, + static_cast<GtkStateFlags>(GTK_STATE_FLAG_FOCUSED | + GTK_STATE_FLAG_SELECTED), + &color); + mTextSelectedBackground = GDK_RGBA_TO_NS_RGBA(color); + gtk_style_context_get_color( + style, + static_cast<GtkStateFlags>(GTK_STATE_FLAG_FOCUSED | + GTK_STATE_FLAG_SELECTED), + &color); + mTextSelectedText = GDK_RGBA_TO_NS_RGBA(color); + }; + GrabSelectionColors(selectionStyle); + if (mTextSelectedBackground == mTextSelectedText) { + // Some old distros/themes don't properly use the .selection style, so + // fall back to the regular text view style. + GrabSelectionColors(style); + } + } + + // Button text color + style = GetStyleContext(MOZ_GTK_BUTTON); + { + GtkStyleContext* labelStyle = CreateStyleForWidget(labelWidget, style); + GetSystemFontInfo(labelStyle, &mButtonFontName, &mButtonFontStyle); + g_object_unref(labelStyle); + } + + gtk_style_context_get_border_color(style, GTK_STATE_FLAG_NORMAL, &color); + mButtonDefault = GDK_RGBA_TO_NS_RGBA(color); + gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color); + mButtonText = GDK_RGBA_TO_NS_RGBA(color); + gtk_style_context_get_color(style, GTK_STATE_FLAG_PRELIGHT, &color); + mButtonHoverText = GDK_RGBA_TO_NS_RGBA(color); + gtk_style_context_get_color(style, GTK_STATE_FLAG_ACTIVE, &color); + mButtonActiveText = GDK_RGBA_TO_NS_RGBA(color); + gtk_style_context_get_background_color(style, GTK_STATE_FLAG_PRELIGHT, + &color); + mButtonHoverFace = GDK_RGBA_TO_NS_RGBA(color); + + // Combobox text color + style = GetStyleContext(MOZ_GTK_COMBOBOX_ENTRY_TEXTAREA); + gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color); + mComboBoxText = GDK_RGBA_TO_NS_RGBA(color); + + // Menubar text and hover text colors + style = GetStyleContext(MOZ_GTK_MENUBARITEM); + gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color); + mMenuBarText = GDK_RGBA_TO_NS_RGBA(color); + gtk_style_context_get_color(style, GTK_STATE_FLAG_PRELIGHT, &color); + mMenuBarHoverText = GDK_RGBA_TO_NS_RGBA(color); + + // GTK's guide to fancy odd row background colors: + // 1) Check if a theme explicitly defines an odd row color + // 2) If not, check if it defines an even row color, and darken it + // slightly by a hardcoded value (gtkstyle.c) + // 3) If neither are defined, take the base background color and + // darken that by a hardcoded value + style = GetStyleContext(MOZ_GTK_TREEVIEW); + + // Get odd row background color + gtk_style_context_save(style); + gtk_style_context_add_region(style, GTK_STYLE_REGION_ROW, GTK_REGION_ODD); + gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color); + mOddCellBackground = GDK_RGBA_TO_NS_RGBA(color); + gtk_style_context_restore(style); + + // Column header colors + style = GetStyleContext(MOZ_GTK_TREE_HEADER_CELL); + gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color); + mMozColHeaderText = GDK_RGBA_TO_NS_RGBA(color); + gtk_style_context_get_color(style, GTK_STATE_FLAG_PRELIGHT, &color); + mMozColHeaderHoverText = GDK_RGBA_TO_NS_RGBA(color); + + // Compute cell highlight colors + InitCellHighlightColors(); + + // GtkFrame has a "border" subnode on which Adwaita draws the border. + // Some themes do not draw on this node but draw a border on the widget + // root node, so check the root node if no border is found on the border + // node. + style = GetStyleContext(MOZ_GTK_FRAME_BORDER); + bool themeUsesColors = + GetBorderColors(style, &mFrameOuterLightBorder, &mFrameInnerDarkBorder); + if (!themeUsesColors) { + style = GetStyleContext(MOZ_GTK_FRAME); + GetBorderColors(style, &mFrameOuterLightBorder, &mFrameInnerDarkBorder); + } + + // GtkInfoBar + // TODO - Use WidgetCache for it? + GtkWidget* infoBar = gtk_info_bar_new(); + GtkWidget* infoBarContent = + gtk_info_bar_get_content_area(GTK_INFO_BAR(infoBar)); + GtkWidget* infoBarLabel = gtk_label_new(nullptr); + gtk_container_add(GTK_CONTAINER(parent), infoBar); + gtk_container_add(GTK_CONTAINER(infoBarContent), infoBarLabel); + style = gtk_widget_get_style_context(infoBarLabel); + gtk_style_context_add_class(style, GTK_STYLE_CLASS_INFO); + gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color); + mInfoBarText = GDK_RGBA_TO_NS_RGBA(color); + // Some themes have a unified menu bar, and support window dragging on it + gboolean supports_menubar_drag = FALSE; + GParamSpec* param_spec = gtk_widget_class_find_style_property( + GTK_WIDGET_GET_CLASS(menuBar), "window-dragging"); + if (param_spec) { + if (g_type_is_a(G_PARAM_SPEC_VALUE_TYPE(param_spec), G_TYPE_BOOLEAN)) { + gtk_widget_style_get(menuBar, "window-dragging", &supports_menubar_drag, + nullptr); + } + } + mMenuSupportsDrag = supports_menubar_drag; + + // TODO: It returns wrong color for themes which + // sets link color for GtkLabel only as we query + // GtkLinkButton style here. + style = gtk_widget_get_style_context(linkButton); + gtk_style_context_get_color(style, GTK_STATE_FLAG_LINK, &color); + mNativeHyperLinkText = GDK_RGBA_TO_NS_RGBA(color); + + // invisible character styles + guint value; + g_object_get(entry, "invisible-char", &value, nullptr); + mInvisibleCharacter = char16_t(value); + + // caret styles + gtk_widget_style_get(entry, "cursor-aspect-ratio", &mCaretRatio, nullptr); + + gint blink_time; + gboolean blink; + g_object_get(settings, "gtk-cursor-blink-time", &blink_time, + "gtk-cursor-blink", &blink, nullptr); + mCaretBlinkTime = blink ? (int32_t)blink_time : 0; + + GetSystemFontInfo(gtk_widget_get_style_context(entry), &mFieldFontName, + &mFieldFontStyle); + + gtk_widget_destroy(window); + g_object_unref(labelWidget); + + mCSDAvailable = + nsWindow::GetSystemCSDSupportLevel() != nsWindow::CSD_SUPPORT_NONE; + mCSDHideTitlebarByDefault = nsWindow::HideTitlebarByDefault(); + + mCSDCloseButton = false; + mCSDMinimizeButton = false; + mCSDMaximizeButton = false; + mCSDCloseButtonPosition = 0; + mCSDMinimizeButtonPosition = 0; + mCSDMaximizeButtonPosition = 0; + + // We need to initialize whole CSD config explicitly because it's queried + // as -moz-gtk* media features. + ButtonLayout buttonLayout[TOOLBAR_BUTTONS]; + + size_t activeButtons = + GetGtkHeaderBarButtonLayout(Span(buttonLayout), &mCSDReversedPlacement); + for (size_t i = 0; i < activeButtons; i++) { + // We check if a button is represented on the right side of the tabbar. + // Then we assign it a value from 3 to 5, instead of 0 to 2 when it is on + // the left side. + const ButtonLayout& layout = buttonLayout[i]; + int32_t* pos = nullptr; + switch (layout.mType) { + case MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE: + mCSDMinimizeButton = true; + pos = &mCSDMinimizeButtonPosition; + break; + case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE: + mCSDMaximizeButton = true; + pos = &mCSDMaximizeButtonPosition; + break; + case MOZ_GTK_HEADER_BAR_BUTTON_CLOSE: + mCSDCloseButton = true; + pos = &mCSDCloseButtonPosition; + break; + default: + break; + } + + if (pos) { + *pos = i; + if (layout.mAtRight) { + *pos += TOOLBAR_BUTTONS; + } + } + } + + RecordTelemetry(); +} + +// virtual +char16_t nsLookAndFeel::GetPasswordCharacterImpl() { + EnsureInit(); + return mInvisibleCharacter; +} + +bool nsLookAndFeel::GetEchoPasswordImpl() { return false; } + +bool nsLookAndFeel::WidgetUsesImage(WidgetNodeType aNodeType) { + static constexpr GtkStateFlags sFlagsToCheck[]{ + GTK_STATE_FLAG_NORMAL, GTK_STATE_FLAG_PRELIGHT, + GtkStateFlags(GTK_STATE_FLAG_PRELIGHT | GTK_STATE_FLAG_ACTIVE), + GTK_STATE_FLAG_BACKDROP, GTK_STATE_FLAG_INSENSITIVE}; + + GtkStyleContext* style = GetStyleContext(aNodeType); + + GValue value = G_VALUE_INIT; + for (GtkStateFlags state : sFlagsToCheck) { + gtk_style_context_get_property(style, "background-image", state, &value); + bool hasPattern = G_VALUE_TYPE(&value) == CAIRO_GOBJECT_TYPE_PATTERN && + g_value_get_boxed(&value); + g_value_unset(&value); + if (hasPattern) { + return true; + } + } + return false; +} + +void nsLookAndFeel::RecordLookAndFeelSpecificTelemetry() { + // Gtk version we're on. + nsString version; + version.AppendPrintf("%d.%d", gtk_major_version, gtk_minor_version); + Telemetry::ScalarSet(Telemetry::ScalarID::WIDGET_GTK_VERSION, version); + + // Whether the current Gtk theme has scrollbar buttons. + bool hasScrollbarButtons = + GetInt(LookAndFeel::IntID::ScrollArrowStyle) != eScrollArrow_None; + mozilla::Telemetry::ScalarSet( + mozilla::Telemetry::ScalarID::WIDGET_GTK_THEME_HAS_SCROLLBAR_BUTTONS, + hasScrollbarButtons); + + // Whether the current Gtk theme uses something other than a solid color + // background for scrollbar parts. + bool scrollbarUsesImage = + WidgetUsesImage(MOZ_GTK_SCROLLBAR_VERTICAL) || + WidgetUsesImage(MOZ_GTK_SCROLLBAR_CONTENTS_VERTICAL) || + WidgetUsesImage(MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL) || + WidgetUsesImage(MOZ_GTK_SCROLLBAR_THUMB_VERTICAL); + mozilla::Telemetry::ScalarSet( + mozilla::Telemetry::ScalarID::WIDGET_GTK_THEME_SCROLLBAR_USES_IMAGES, + scrollbarUsesImage); +} + +bool nsLookAndFeel::ShouldHonorThemeScrollbarColors() { + // If the Gtk theme uses anything other than solid color backgrounds for Gtk + // scrollbar parts, this is a good indication that painting XUL scrollbar part + // elements using colors extracted from the theme won't provide good results. + return !WidgetUsesImage(MOZ_GTK_SCROLLBAR_VERTICAL) && + !WidgetUsesImage(MOZ_GTK_SCROLLBAR_CONTENTS_VERTICAL) && + !WidgetUsesImage(MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL) && + !WidgetUsesImage(MOZ_GTK_SCROLLBAR_THUMB_VERTICAL); +} |