fune/widget/gtk/nsLookAndFeel.cpp
Cameron McCormack eefbb537b9 Bug 1683204 - Include content theme configuration details in FullLookAndFeel. r=spohl
And re-enable the RemoteLookAndFeel by default with Gtk.

When the RemoteLookAndFeel is enabled and the non-native theme is not
enabled, we still need to configure the Gtk theme in content processes,
since we're still using Gtk to paint widget backgrounds etc. Without
this, we can end up using LookAndFeel colors from a light theme but
painting widget backgrounds from a dark theme.

Other platforms don't configure themes for content processes
differently, so on those platforms LookAndFeelTheme is an empty struct
and we skip the ConfigureTheme call.

Differential Revision: https://phabricator.services.mozilla.com/D100223
2020-12-22 19:26:41 +00:00

1536 lines
53 KiB
C++

/* -*- 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);
}