1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:expandtab:shiftwidth=2:tabstop=2:
3  */
4 /* This Source Code Form is subject to the terms of the Mozilla Public
5  * License, v. 2.0. If a copy of the MPL was not distributed with this
6  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 
8 // for strtod()
9 #include <stdlib.h>
10 #include <dlfcn.h>
11 
12 #include "nsLookAndFeel.h"
13 
14 #include <gtk/gtk.h>
15 #include <gdk/gdk.h>
16 
17 #include <pango/pango.h>
18 #include <pango/pango-fontmap.h>
19 #include <fontconfig/fontconfig.h>
20 
21 #include "nsGtkUtils.h"
22 #include "gfxPlatformGtk.h"
23 #include "mozilla/FontPropertyTypes.h"
24 #include "mozilla/Preferences.h"
25 #include "mozilla/RelativeLuminanceUtils.h"
26 #include "mozilla/StaticPrefs_layout.h"
27 #include "mozilla/StaticPrefs_widget.h"
28 #include "mozilla/StaticPrefs_browser.h"
29 #include "mozilla/AutoRestore.h"
30 #include "mozilla/Telemetry.h"
31 #include "mozilla/ScopeExit.h"
32 #include "ScreenHelperGTK.h"
33 #include "nsNativeBasicThemeGTK.h"
34 
35 #include "gtkdrawing.h"
36 #include "nsStyleConsts.h"
37 #include "gfxFontConstants.h"
38 #include "WidgetUtils.h"
39 #include "nsWindow.h"
40 
41 #include "mozilla/gfx/2D.h"
42 
43 #include <cairo-gobject.h>
44 #include "WidgetStyleCache.h"
45 #include "prenv.h"
46 #include "nsCSSColorUtils.h"
47 
48 using namespace mozilla;
49 
50 #ifdef MOZ_LOGGING
51 #  include "mozilla/Logging.h"
52 #  include "nsTArray.h"
53 #  include "Units.h"
54 static LazyLogModule gLnfLog("LookAndFeel");
55 #  define LOGLNF(...) MOZ_LOG(gLnfLog, LogLevel::Debug, (__VA_ARGS__))
56 #  define LOGLNF_ENABLED() MOZ_LOG_TEST(gLnfLog, LogLevel::Debug)
57 #else
58 #  define LOGLNF(args)
59 #  define LOGLNF_ENABLED() false
60 #endif /* MOZ_LOGGING */
61 
62 #define GDK_COLOR_TO_NS_RGB(c) \
63   ((nscolor)NS_RGB(c.red >> 8, c.green >> 8, c.blue >> 8))
64 #define GDK_RGBA_TO_NS_RGBA(c)                                    \
65   ((nscolor)NS_RGBA((int)((c).red * 255), (int)((c).green * 255), \
66                     (int)((c).blue * 255), (int)((c).alpha * 255)))
67 
68 static bool sIgnoreChangedSettings = false;
settings_changed_cb(GtkSettings *,GParamSpec *,void *)69 static void settings_changed_cb(GtkSettings*, GParamSpec*, void*) {
70   if (sIgnoreChangedSettings) {
71     return;
72   }
73   // TODO: We could be more granular here, but for now assume everything
74   // changed.
75   LookAndFeel::NotifyChangedAllWindows(widget::ThemeChangeKind::StyleAndLayout);
76   widget::IMContextWrapper::OnThemeChanged();
77 }
78 
nsLookAndFeel()79 nsLookAndFeel::nsLookAndFeel() {
80   static constexpr nsLiteralCString kObservedSettings[] = {
81       // Affects system font sizes.
82       "notify::gtk-xft-dpi"_ns,
83       // Affects mSystemTheme and mAltTheme as expected.
84       "notify::gtk-theme-name"_ns,
85       // System fonts?
86       "notify::gtk-font-name"_ns,
87       // prefers-reduced-motion
88       "notify::gtk-enable-animations"_ns,
89       // CSD media queries, etc.
90       "notify::gtk-decoration-layout"_ns,
91       // Text resolution affects system font and widget sizes.
92       "notify::resolution"_ns,
93       // Affects mCaretBlinkTime
94       "notify::gtk-cursor-blink-time"_ns,
95       // Affects SelectTextfieldsOnKeyFocus
96       "notify::gtk-entry-select-on-focus"_ns,
97       // Affects ScrollToClick
98       "notify::gtk-primary-button-warps-slider"_ns,
99       // Affects SubmenuDelay
100       "notify::gtk-menu-popup-delay"_ns,
101       // Affects DragThresholdX/Y
102       "notify::gtk-dnd-drag-threshold"_ns,
103   };
104 
105   GtkSettings* settings = gtk_settings_get_default();
106   for (const auto& setting : kObservedSettings) {
107     g_signal_connect_after(settings, setting.get(),
108                            G_CALLBACK(settings_changed_cb), nullptr);
109   }
110 
111   Preferences::RegisterCallback(
112       FirefoxThemeChanged,
113       nsDependentCString(
114           StaticPrefs::GetPrefName_widget_gtk_follow_firefox_theme()),
115       this);
116 }
117 
~nsLookAndFeel()118 nsLookAndFeel::~nsLookAndFeel() {
119   g_signal_handlers_disconnect_by_func(
120       gtk_settings_get_default(), FuncToGpointer(settings_changed_cb), nullptr);
121   Preferences::UnregisterCallback(
122       FirefoxThemeChanged,
123       nsDependentCString(
124           StaticPrefs::GetPrefName_widget_gtk_follow_firefox_theme()),
125       this);
126 }
127 
128 // Modifies color |*aDest| as if a pattern of color |aSource| was painted with
129 // CAIRO_OPERATOR_OVER to a surface with color |*aDest|.
ApplyColorOver(const GdkRGBA & aSource,GdkRGBA * aDest)130 static void ApplyColorOver(const GdkRGBA& aSource, GdkRGBA* aDest) {
131   gdouble sourceCoef = aSource.alpha;
132   gdouble destCoef = aDest->alpha * (1.0 - sourceCoef);
133   gdouble resultAlpha = sourceCoef + destCoef;
134   if (resultAlpha != 0.0) {  // don't divide by zero
135     destCoef /= resultAlpha;
136     sourceCoef /= resultAlpha;
137     aDest->red = sourceCoef * aSource.red + destCoef * aDest->red;
138     aDest->green = sourceCoef * aSource.green + destCoef * aDest->green;
139     aDest->blue = sourceCoef * aSource.blue + destCoef * aDest->blue;
140     aDest->alpha = resultAlpha;
141   }
142 }
143 
GetLightAndDarkness(const GdkRGBA & aColor,double * aLightness,double * aDarkness)144 static void GetLightAndDarkness(const GdkRGBA& aColor, double* aLightness,
145                                 double* aDarkness) {
146   double sum = aColor.red + aColor.green + aColor.blue;
147   *aLightness = sum * aColor.alpha;
148   *aDarkness = (3.0 - sum) * aColor.alpha;
149 }
150 
GetGradientColors(const GValue * aValue,GdkRGBA * aLightColor,GdkRGBA * aDarkColor)151 static bool GetGradientColors(const GValue* aValue, GdkRGBA* aLightColor,
152                               GdkRGBA* aDarkColor) {
153   if (!G_TYPE_CHECK_VALUE_TYPE(aValue, CAIRO_GOBJECT_TYPE_PATTERN)) {
154     return false;
155   }
156 
157   auto pattern = static_cast<cairo_pattern_t*>(g_value_get_boxed(aValue));
158   if (!pattern) {
159     return false;
160   }
161 
162   // Just picking the lightest and darkest colors as simple samples rather
163   // than trying to blend, which could get messy if there are many stops.
164   if (CAIRO_STATUS_SUCCESS !=
165       cairo_pattern_get_color_stop_rgba(pattern, 0, nullptr, &aDarkColor->red,
166                                         &aDarkColor->green, &aDarkColor->blue,
167                                         &aDarkColor->alpha)) {
168     return false;
169   }
170 
171   double maxLightness, maxDarkness;
172   GetLightAndDarkness(*aDarkColor, &maxLightness, &maxDarkness);
173   *aLightColor = *aDarkColor;
174 
175   GdkRGBA stop;
176   for (int index = 1;
177        CAIRO_STATUS_SUCCESS ==
178        cairo_pattern_get_color_stop_rgba(pattern, index, nullptr, &stop.red,
179                                          &stop.green, &stop.blue, &stop.alpha);
180        ++index) {
181     double lightness, darkness;
182     GetLightAndDarkness(stop, &lightness, &darkness);
183     if (lightness > maxLightness) {
184       maxLightness = lightness;
185       *aLightColor = stop;
186     }
187     if (darkness > maxDarkness) {
188       maxDarkness = darkness;
189       *aDarkColor = stop;
190     }
191   }
192 
193   return true;
194 }
195 
GetColorFromImagePattern(const GValue * aValue,nscolor * aColor)196 static bool GetColorFromImagePattern(const GValue* aValue, nscolor* aColor) {
197   if (!G_TYPE_CHECK_VALUE_TYPE(aValue, CAIRO_GOBJECT_TYPE_PATTERN)) {
198     return false;
199   }
200 
201   auto pattern = static_cast<cairo_pattern_t*>(g_value_get_boxed(aValue));
202   if (!pattern) {
203     return false;
204   }
205 
206   cairo_surface_t* surface;
207   if (cairo_pattern_get_surface(pattern, &surface) != CAIRO_STATUS_SUCCESS) {
208     return false;
209   }
210 
211   cairo_format_t format = cairo_image_surface_get_format(surface);
212   if (format == CAIRO_FORMAT_INVALID) {
213     return false;
214   }
215   int width = cairo_image_surface_get_width(surface);
216   int height = cairo_image_surface_get_height(surface);
217   int stride = cairo_image_surface_get_stride(surface);
218   if (!width || !height) {
219     return false;
220   }
221 
222   // Guesstimate the central pixel would have a sensible color.
223   int x = width / 2;
224   int y = height / 2;
225 
226   unsigned char* data = cairo_image_surface_get_data(surface);
227   switch (format) {
228     // Most (all?) GTK images / patterns / etc use ARGB32.
229     case CAIRO_FORMAT_ARGB32: {
230       size_t offset = x * 4 + y * stride;
231       uint32_t* pixel = reinterpret_cast<uint32_t*>(data + offset);
232       *aColor = gfx::sRGBColor::UnusualFromARGB(*pixel).ToABGR();
233       return true;
234     }
235     default:
236       break;
237   }
238 
239   return false;
240 }
241 
GetUnicoBorderGradientColors(GtkStyleContext * aContext,GdkRGBA * aLightColor,GdkRGBA * aDarkColor)242 static bool GetUnicoBorderGradientColors(GtkStyleContext* aContext,
243                                          GdkRGBA* aLightColor,
244                                          GdkRGBA* aDarkColor) {
245   // Ubuntu 12.04 has GTK engine Unico-1.0.2, which overrides render_frame,
246   // providing its own border code.  Ubuntu 14.04 has
247   // Unico-1.0.3+14.04.20140109, which does not override render_frame, and
248   // so does not need special attention.  The earlier Unico can be detected
249   // by the -unico-border-gradient style property it registers.
250   // gtk_style_properties_lookup_property() is checked first to avoid the
251   // warning from gtk_style_context_get_property() when the property does
252   // not exist.  (gtk_render_frame() of GTK+ 3.16 no longer uses the
253   // engine.)
254   const char* propertyName = "-unico-border-gradient";
255   if (!gtk_style_properties_lookup_property(propertyName, nullptr, nullptr))
256     return false;
257 
258   // -unico-border-gradient is used only when the CSS node's engine is Unico.
259   GtkThemingEngine* engine;
260   GtkStateFlags state = gtk_style_context_get_state(aContext);
261   gtk_style_context_get(aContext, state, "engine", &engine, nullptr);
262   if (strcmp(g_type_name(G_TYPE_FROM_INSTANCE(engine)), "UnicoEngine") != 0)
263     return false;
264 
265   // draw_border() of Unico engine uses -unico-border-gradient
266   // in preference to border-color.
267   GValue value = G_VALUE_INIT;
268   gtk_style_context_get_property(aContext, propertyName, state, &value);
269 
270   bool result = GetGradientColors(&value, aLightColor, aDarkColor);
271 
272   g_value_unset(&value);
273   return result;
274 }
275 
276 // Sets |aLightColor| and |aDarkColor| to colors from |aContext|.  Returns
277 // true if |aContext| uses these colors to render a visible border.
278 // If returning false, then the colors returned are a fallback from the
279 // border-color value even though |aContext| does not use these colors to
280 // render a border.
GetBorderColors(GtkStyleContext * aContext,GdkRGBA * aLightColor,GdkRGBA * aDarkColor)281 static bool GetBorderColors(GtkStyleContext* aContext, GdkRGBA* aLightColor,
282                             GdkRGBA* aDarkColor) {
283   // Determine whether the border on this style context is visible.
284   GtkStateFlags state = gtk_style_context_get_state(aContext);
285   GtkBorderStyle borderStyle;
286   gtk_style_context_get(aContext, state, GTK_STYLE_PROPERTY_BORDER_STYLE,
287                         &borderStyle, nullptr);
288   bool visible = borderStyle != GTK_BORDER_STYLE_NONE &&
289                  borderStyle != GTK_BORDER_STYLE_HIDDEN;
290   if (visible) {
291     // GTK has an initial value of zero for border-widths, and so themes
292     // need to explicitly set border-widths to make borders visible.
293     GtkBorder border;
294     gtk_style_context_get_border(aContext, state, &border);
295     visible = border.top != 0 || border.right != 0 || border.bottom != 0 ||
296               border.left != 0;
297   }
298 
299   if (visible &&
300       GetUnicoBorderGradientColors(aContext, aLightColor, aDarkColor))
301     return true;
302 
303   // The initial value for the border-color is the foreground color, and so
304   // this will usually return a color distinct from the background even if
305   // there is no visible border detected.
306   gtk_style_context_get_border_color(aContext, state, aDarkColor);
307   // TODO GTK3 - update aLightColor
308   // for GTK_BORDER_STYLE_INSET/OUTSET/GROVE/RIDGE border styles.
309   // https://bugzilla.mozilla.org/show_bug.cgi?id=978172#c25
310   *aLightColor = *aDarkColor;
311   return visible;
312 }
313 
GetBorderColors(GtkStyleContext * aContext,nscolor * aLightColor,nscolor * aDarkColor)314 static bool GetBorderColors(GtkStyleContext* aContext, nscolor* aLightColor,
315                             nscolor* aDarkColor) {
316   GdkRGBA lightColor, darkColor;
317   bool ret = GetBorderColors(aContext, &lightColor, &darkColor);
318   *aLightColor = GDK_RGBA_TO_NS_RGBA(lightColor);
319   *aDarkColor = GDK_RGBA_TO_NS_RGBA(darkColor);
320   return ret;
321 }
322 
323 // Finds ideal cell highlight colors used for unfocused+selected cells distinct
324 // from both Highlight, used as focused+selected background, and the listbox
325 // background which is assumed to be similar to -moz-field
InitCellHighlightColors()326 void nsLookAndFeel::PerThemeData::InitCellHighlightColors() {
327   int32_t minLuminosityDifference = NS_SUFFICIENT_LUMINOSITY_DIFFERENCE_BG;
328   int32_t backLuminosityDifference =
329       NS_LUMINOSITY_DIFFERENCE(mMozWindowBackground, mFieldBackground);
330   if (backLuminosityDifference >= minLuminosityDifference) {
331     mMozCellHighlightBackground = mMozWindowBackground;
332     mMozCellHighlightText = mMozWindowText;
333     return;
334   }
335 
336   uint16_t hue, sat, luminance;
337   uint8_t alpha;
338   mMozCellHighlightBackground = mFieldBackground;
339   mMozCellHighlightText = mFieldText;
340 
341   NS_RGB2HSV(mMozCellHighlightBackground, hue, sat, luminance, alpha);
342 
343   uint16_t step = 30;
344   // Lighten the color if the color is very dark
345   if (luminance <= step) {
346     luminance += step;
347   }
348   // Darken it if it is very light
349   else if (luminance >= 255 - step) {
350     luminance -= step;
351   }
352   // Otherwise, compute what works best depending on the text luminance.
353   else {
354     uint16_t textHue, textSat, textLuminance;
355     uint8_t textAlpha;
356     NS_RGB2HSV(mMozCellHighlightText, textHue, textSat, textLuminance,
357                textAlpha);
358     // Text is darker than background, use a lighter shade
359     if (textLuminance < luminance) {
360       luminance += step;
361     }
362     // Otherwise, use a darker shade
363     else {
364       luminance -= step;
365     }
366   }
367   NS_HSV2RGB(mMozCellHighlightBackground, hue, sat, luminance, alpha);
368 }
369 
NativeInit()370 void nsLookAndFeel::NativeInit() { EnsureInit(); }
371 
RefreshImpl()372 void nsLookAndFeel::RefreshImpl() {
373   nsXPLookAndFeel::RefreshImpl();
374   moz_gtk_refresh();
375 
376   mInitialized = false;
377 }
378 
NativeGetColor(ColorID aID,ColorScheme aScheme,nscolor & aColor)379 nsresult nsLookAndFeel::NativeGetColor(ColorID aID, ColorScheme aScheme,
380                                        nscolor& aColor) {
381   EnsureInit();
382 
383   auto& theme = aScheme == ColorScheme::Light ? LightTheme() : DarkTheme();
384   return theme.GetColor(aID, aColor);
385 }
386 
GetColor(ColorID aID,nscolor & aColor) const387 nsresult nsLookAndFeel::PerThemeData::GetColor(ColorID aID,
388                                                nscolor& aColor) const {
389   nsresult res = NS_OK;
390 
391   switch (aID) {
392       // These colors don't seem to be used for anything anymore in Mozilla
393       // (except here at least TextSelectBackground and TextSelectForeground)
394       // The CSS2 colors below are used.
395     case ColorID::WindowBackground:
396     case ColorID::WidgetBackground:
397     case ColorID::TextBackground:
398     case ColorID::Activecaption:  // active window caption background
399     case ColorID::Appworkspace:   // MDI background color
400     case ColorID::Background:     // desktop background
401     case ColorID::Window:
402     case ColorID::Windowframe:
403     case ColorID::MozDialog:
404     case ColorID::MozCombobox:
405       aColor = mMozWindowBackground;
406       break;
407     case ColorID::WindowForeground:
408     case ColorID::WidgetForeground:
409     case ColorID::TextForeground:
410     case ColorID::Captiontext:  // text in active window caption, size box, and
411                                 // scrollbar arrow box (!)
412     case ColorID::Windowtext:
413     case ColorID::MozDialogtext:
414       aColor = mMozWindowText;
415       break;
416     case ColorID::WidgetSelectBackground:
417     case ColorID::TextSelectBackground:
418     case ColorID::IMESelectedRawTextBackground:
419     case ColorID::IMESelectedConvertedTextBackground:
420     case ColorID::MozDragtargetzone:
421     case ColorID::Highlight:  // preference selected item,
422       aColor = mTextSelectedBackground;
423       break;
424     case ColorID::TextSelectForeground:
425       if (NS_GET_A(mTextSelectedBackground) < 155) {
426         aColor = NS_SAME_AS_FOREGROUND_COLOR;
427         break;
428       }
429       [[fallthrough]];
430     case ColorID::WidgetSelectForeground:
431     case ColorID::IMESelectedRawTextForeground:
432     case ColorID::IMESelectedConvertedTextForeground:
433     case ColorID::Highlighttext:
434       aColor = mTextSelectedText;
435       break;
436     case ColorID::MozHtmlCellhighlight:
437     case ColorID::MozAccentColor:
438       aColor = mAccentColor;
439       break;
440     case ColorID::MozHtmlCellhighlighttext:
441     case ColorID::MozAccentColorForeground:
442       aColor = mAccentColorForeground;
443       break;
444     case ColorID::MozCellhighlight:
445       aColor = mMozCellHighlightBackground;
446       break;
447     case ColorID::MozCellhighlighttext:
448       aColor = mMozCellHighlightText;
449       break;
450     case ColorID::Widget3DHighlight:
451       aColor = NS_RGB(0xa0, 0xa0, 0xa0);
452       break;
453     case ColorID::Widget3DShadow:
454       aColor = NS_RGB(0x40, 0x40, 0x40);
455       break;
456     case ColorID::IMERawInputBackground:
457     case ColorID::IMEConvertedTextBackground:
458       aColor = NS_TRANSPARENT;
459       break;
460     case ColorID::IMERawInputForeground:
461     case ColorID::IMEConvertedTextForeground:
462       aColor = NS_SAME_AS_FOREGROUND_COLOR;
463       break;
464     case ColorID::IMERawInputUnderline:
465     case ColorID::IMEConvertedTextUnderline:
466       aColor = NS_SAME_AS_FOREGROUND_COLOR;
467       break;
468     case ColorID::IMESelectedRawTextUnderline:
469     case ColorID::IMESelectedConvertedTextUnderline:
470       aColor = NS_TRANSPARENT;
471       break;
472     case ColorID::SpellCheckerUnderline:
473       aColor = NS_RGB(0xff, 0, 0);
474       break;
475     case ColorID::ThemedScrollbar:
476       aColor = mThemedScrollbar;
477       break;
478     case ColorID::ThemedScrollbarInactive:
479       aColor = mThemedScrollbarInactive;
480       break;
481     case ColorID::ThemedScrollbarThumb:
482       aColor = mThemedScrollbarThumb;
483       break;
484     case ColorID::ThemedScrollbarThumbHover:
485       aColor = mThemedScrollbarThumbHover;
486       break;
487     case ColorID::ThemedScrollbarThumbActive:
488       aColor = mThemedScrollbarThumbActive;
489       break;
490     case ColorID::ThemedScrollbarThumbInactive:
491       aColor = mThemedScrollbarThumbInactive;
492       break;
493 
494       // css2  http://www.w3.org/TR/REC-CSS2/ui.html#system-colors
495     case ColorID::Activeborder:
496       // active window border
497       aColor = mMozWindowActiveBorder;
498       break;
499     case ColorID::Inactiveborder:
500       // inactive window border
501       aColor = mMozWindowInactiveBorder;
502       break;
503     case ColorID::MozGtkTitlebarText:
504       aColor = mTitlebarText;
505       break;
506     case ColorID::MozGtkTitlebarInactiveText:
507       aColor = mTitlebarInactiveText;
508       break;
509     case ColorID::Graytext:             // disabled text in windows, menus, etc.
510     case ColorID::Inactivecaptiontext:  // text in inactive window caption
511       aColor = mMenuTextInactive;
512       break;
513     case ColorID::Inactivecaption:
514       // inactive window caption
515       aColor = mMozWindowInactiveCaption;
516       break;
517     case ColorID::Infobackground:
518       // tooltip background color
519       aColor = mInfoBackground;
520       break;
521     case ColorID::Infotext:
522       // tooltip text color
523       aColor = mInfoText;
524       break;
525     case ColorID::Menu:
526       // menu background
527       aColor = mMenuBackground;
528       break;
529     case ColorID::Menutext:
530       // menu text
531       aColor = mMenuText;
532       break;
533     case ColorID::Scrollbar:
534       // scrollbar gray area
535       aColor = mMozScrollbar;
536       break;
537 
538     case ColorID::Threedface:
539     case ColorID::Buttonface:
540       // 3-D face color
541       aColor = mMozWindowBackground;
542       break;
543 
544     case ColorID::Buttontext:
545       // text on push buttons
546       aColor = mButtonText;
547       break;
548 
549     case ColorID::Buttonhighlight:
550       // 3-D highlighted edge color
551     case ColorID::Threedhighlight:
552       // 3-D highlighted outer edge color
553       aColor = mFrameOuterLightBorder;
554       break;
555 
556     case ColorID::Buttonshadow:
557       // 3-D shadow edge color
558     case ColorID::Threedshadow:
559       // 3-D shadow inner edge color
560       aColor = mFrameInnerDarkBorder;
561       break;
562 
563     case ColorID::Threedlightshadow:
564       aColor = NS_RGB(0xE0, 0xE0, 0xE0);
565       break;
566     case ColorID::Threeddarkshadow:
567       aColor = NS_RGB(0xDC, 0xDC, 0xDC);
568       break;
569 
570     case ColorID::MozEventreerow:
571     case ColorID::Field:
572       aColor = mFieldBackground;
573       break;
574     case ColorID::Fieldtext:
575       aColor = mFieldText;
576       break;
577     case ColorID::MozButtondefault:
578       // default button border color
579       aColor = mButtonDefault;
580       break;
581     case ColorID::MozButtonhoverface:
582       aColor = mButtonHoverFace;
583       break;
584     case ColorID::MozButtonhovertext:
585       aColor = mButtonHoverText;
586       break;
587     case ColorID::MozGtkButtonactivetext:
588       aColor = mButtonActiveText;
589       break;
590     case ColorID::MozMenuhover:
591       aColor = mMenuHover;
592       break;
593     case ColorID::MozMenuhovertext:
594       aColor = mMenuHoverText;
595       break;
596     case ColorID::MozOddtreerow:
597       aColor = mOddCellBackground;
598       break;
599     case ColorID::MozNativehyperlinktext:
600       aColor = mNativeHyperLinkText;
601       break;
602     case ColorID::MozComboboxtext:
603       aColor = mComboBoxText;
604       break;
605     case ColorID::MozMenubartext:
606       aColor = mMenuBarText;
607       break;
608     case ColorID::MozMenubarhovertext:
609       aColor = mMenuBarHoverText;
610       break;
611     case ColorID::MozColheadertext:
612       aColor = mMozColHeaderText;
613       break;
614     case ColorID::MozColheaderhovertext:
615       aColor = mMozColHeaderHoverText;
616       break;
617     default:
618       /* default color is BLACK */
619       aColor = 0;
620       res = NS_ERROR_FAILURE;
621       break;
622   }
623 
624   return res;
625 }
626 
CheckWidgetStyle(GtkWidget * aWidget,const char * aStyle,int32_t aResult)627 static int32_t CheckWidgetStyle(GtkWidget* aWidget, const char* aStyle,
628                                 int32_t aResult) {
629   gboolean value = FALSE;
630   gtk_widget_style_get(aWidget, aStyle, &value, nullptr);
631   return value ? aResult : 0;
632 }
633 
ConvertGTKStepperStyleToMozillaScrollArrowStyle(GtkWidget * aWidget)634 static int32_t ConvertGTKStepperStyleToMozillaScrollArrowStyle(
635     GtkWidget* aWidget) {
636   if (!aWidget) return mozilla::LookAndFeel::eScrollArrowStyle_Single;
637 
638   return CheckWidgetStyle(aWidget, "has-backward-stepper",
639                           mozilla::LookAndFeel::eScrollArrow_StartBackward) |
640          CheckWidgetStyle(aWidget, "has-forward-stepper",
641                           mozilla::LookAndFeel::eScrollArrow_EndForward) |
642          CheckWidgetStyle(aWidget, "has-secondary-backward-stepper",
643                           mozilla::LookAndFeel::eScrollArrow_EndBackward) |
644          CheckWidgetStyle(aWidget, "has-secondary-forward-stepper",
645                           mozilla::LookAndFeel::eScrollArrow_StartForward);
646 }
647 
NativeGetInt(IntID aID,int32_t & aResult)648 nsresult nsLookAndFeel::NativeGetInt(IntID aID, int32_t& aResult) {
649   nsresult res = NS_OK;
650 
651   // We use delayed initialization by EnsureInit() here
652   // to make sure mozilla::Preferences is available (Bug 115807).
653   // IntID::UseAccessibilityTheme is requested before user preferences
654   // are read, and so EnsureInit(), which depends on preference values,
655   // is deliberately delayed until required.
656   switch (aID) {
657     case IntID::ScrollButtonLeftMouseButtonAction:
658       aResult = 0;
659       break;
660     case IntID::ScrollButtonMiddleMouseButtonAction:
661       aResult = 1;
662       break;
663     case IntID::ScrollButtonRightMouseButtonAction:
664       aResult = 2;
665       break;
666     case IntID::CaretBlinkTime:
667       EnsureInit();
668       aResult = mCaretBlinkTime;
669       break;
670     case IntID::CaretWidth:
671       aResult = 1;
672       break;
673     case IntID::ShowCaretDuringSelection:
674       aResult = 0;
675       break;
676     case IntID::SelectTextfieldsOnKeyFocus: {
677       GtkSettings* settings;
678       gboolean select_on_focus;
679 
680       settings = gtk_settings_get_default();
681       g_object_get(settings, "gtk-entry-select-on-focus", &select_on_focus,
682                    nullptr);
683 
684       if (select_on_focus)
685         aResult = 1;
686       else
687         aResult = 0;
688 
689     } break;
690     case IntID::ScrollToClick: {
691       GtkSettings* settings;
692       gboolean warps_slider = FALSE;
693 
694       settings = gtk_settings_get_default();
695       if (g_object_class_find_property(G_OBJECT_GET_CLASS(settings),
696                                        "gtk-primary-button-warps-slider")) {
697         g_object_get(settings, "gtk-primary-button-warps-slider", &warps_slider,
698                      nullptr);
699       }
700 
701       if (warps_slider)
702         aResult = 1;
703       else
704         aResult = 0;
705     } break;
706     case IntID::SubmenuDelay: {
707       GtkSettings* settings;
708       gint delay;
709 
710       settings = gtk_settings_get_default();
711       g_object_get(settings, "gtk-menu-popup-delay", &delay, nullptr);
712       aResult = (int32_t)delay;
713       break;
714     }
715     case IntID::TooltipDelay: {
716       aResult = 500;
717       break;
718     }
719     case IntID::MenusCanOverlapOSBar:
720       // we want XUL popups to be able to overlap the task bar.
721       aResult = 1;
722       break;
723     case IntID::SkipNavigatingDisabledMenuItem:
724       aResult = 1;
725       break;
726     case IntID::DragThresholdX:
727     case IntID::DragThresholdY: {
728       gint threshold = 0;
729       g_object_get(gtk_settings_get_default(), "gtk-dnd-drag-threshold",
730                    &threshold, nullptr);
731 
732       aResult = threshold;
733     } break;
734     case IntID::ScrollArrowStyle: {
735       GtkWidget* scrollbar = GetWidget(MOZ_GTK_SCROLLBAR_HORIZONTAL);
736       aResult = ConvertGTKStepperStyleToMozillaScrollArrowStyle(scrollbar);
737       break;
738     }
739     case IntID::ScrollSliderStyle:
740       aResult = eScrollThumbStyle_Proportional;
741       break;
742     case IntID::TreeOpenDelay:
743       aResult = 1000;
744       break;
745     case IntID::TreeCloseDelay:
746       aResult = 1000;
747       break;
748     case IntID::TreeLazyScrollDelay:
749       aResult = 150;
750       break;
751     case IntID::TreeScrollDelay:
752       aResult = 100;
753       break;
754     case IntID::TreeScrollLinesMax:
755       aResult = 3;
756       break;
757     case IntID::DWMCompositor:
758     case IntID::WindowsClassic:
759     case IntID::WindowsDefaultTheme:
760     case IntID::WindowsThemeIdentifier:
761     case IntID::OperatingSystemVersionIdentifier:
762       aResult = 0;
763       res = NS_ERROR_NOT_IMPLEMENTED;
764       break;
765     case IntID::MacGraphiteTheme:
766       aResult = 0;
767       res = NS_ERROR_NOT_IMPLEMENTED;
768       break;
769     case IntID::AlertNotificationOrigin:
770       aResult = NS_ALERT_TOP;
771       break;
772     case IntID::IMERawInputUnderlineStyle:
773     case IntID::IMEConvertedTextUnderlineStyle:
774       aResult = NS_STYLE_TEXT_DECORATION_STYLE_SOLID;
775       break;
776     case IntID::IMESelectedRawTextUnderlineStyle:
777     case IntID::IMESelectedConvertedTextUnderline:
778       aResult = NS_STYLE_TEXT_DECORATION_STYLE_NONE;
779       break;
780     case IntID::SpellCheckerUnderlineStyle:
781       aResult = NS_STYLE_TEXT_DECORATION_STYLE_WAVY;
782       break;
783     case IntID::MenuBarDrag:
784       EnsureInit();
785       aResult = mSystemTheme.mMenuSupportsDrag;
786       break;
787     case IntID::ScrollbarButtonAutoRepeatBehavior:
788       aResult = 1;
789       break;
790     case IntID::SwipeAnimationEnabled:
791       aResult = 0;
792       break;
793     case IntID::ContextMenuOffsetVertical:
794     case IntID::ContextMenuOffsetHorizontal:
795       aResult = 2;
796       break;
797     case IntID::GTKCSDAvailable:
798       EnsureInit();
799       aResult = mCSDAvailable;
800       break;
801     case IntID::GTKCSDHideTitlebarByDefault:
802       EnsureInit();
803       aResult = mCSDHideTitlebarByDefault;
804       break;
805     case IntID::GTKCSDMaximizeButton:
806       EnsureInit();
807       aResult = mCSDMaximizeButton;
808       break;
809     case IntID::GTKCSDMinimizeButton:
810       EnsureInit();
811       aResult = mCSDMinimizeButton;
812       break;
813     case IntID::GTKCSDCloseButton:
814       EnsureInit();
815       aResult = mCSDCloseButton;
816       break;
817     case IntID::GTKCSDTransparentBackground: {
818       // Enable transparent titlebar corners for titlebar mode.
819       GdkScreen* screen = gdk_screen_get_default();
820       aResult = gdk_screen_is_composited(screen)
821                     ? (nsWindow::GtkWindowDecoration() !=
822                        nsWindow::GTK_DECORATION_NONE)
823                     : false;
824       break;
825     }
826     case IntID::GTKCSDReversedPlacement:
827       EnsureInit();
828       aResult = mCSDReversedPlacement;
829       break;
830     case IntID::PrefersReducedMotion: {
831       aResult = mPrefersReducedMotion;
832       break;
833     }
834     case IntID::SystemUsesDarkTheme: {
835       EnsureInit();
836       aResult = mSystemTheme.mIsDark;
837       break;
838     }
839     case IntID::GTKCSDMaximizeButtonPosition:
840       aResult = mCSDMaximizeButtonPosition;
841       break;
842     case IntID::GTKCSDMinimizeButtonPosition:
843       aResult = mCSDMinimizeButtonPosition;
844       break;
845     case IntID::GTKCSDCloseButtonPosition:
846       aResult = mCSDCloseButtonPosition;
847       break;
848     case IntID::UseAccessibilityTheme: {
849       EnsureInit();
850       aResult = mSystemTheme.mHighContrast;
851       break;
852     }
853     case IntID::AllowOverlayScrollbarsOverlap: {
854       aResult = 1;
855       break;
856     }
857     case IntID::ScrollbarFadeBeginDelay: {
858       aResult = 1000;
859       break;
860     }
861     case IntID::ScrollbarFadeDuration: {
862       aResult = 400;
863       break;
864     }
865     case IntID::ScrollbarDisplayOnMouseMove: {
866       aResult = 1;
867       break;
868     }
869     default:
870       aResult = 0;
871       res = NS_ERROR_FAILURE;
872   }
873 
874   return res;
875 }
876 
NativeGetFloat(FloatID aID,float & aResult)877 nsresult nsLookAndFeel::NativeGetFloat(FloatID aID, float& aResult) {
878   nsresult rv = NS_OK;
879   switch (aID) {
880     case FloatID::IMEUnderlineRelativeSize:
881       aResult = 1.0f;
882       break;
883     case FloatID::SpellCheckerUnderlineRelativeSize:
884       aResult = 1.0f;
885       break;
886     case FloatID::CaretAspectRatio:
887       EnsureInit();
888       aResult = mSystemTheme.mCaretRatio;
889       break;
890     case FloatID::TextScaleFactor:
891       aResult = gfxPlatformGtk::GetFontScaleFactor();
892       break;
893     default:
894       aResult = -1.0;
895       rv = NS_ERROR_FAILURE;
896   }
897   return rv;
898 }
899 
GetSystemFontInfo(GtkStyleContext * aStyle,nsString * aFontName,gfxFontStyle * aFontStyle)900 static void GetSystemFontInfo(GtkStyleContext* aStyle, nsString* aFontName,
901                               gfxFontStyle* aFontStyle) {
902   aFontStyle->style = FontSlantStyle::Normal();
903 
904   // As in
905   // https://git.gnome.org/browse/gtk+/tree/gtk/gtkwidget.c?h=3.22.19#n10333
906   PangoFontDescription* desc;
907   gtk_style_context_get(aStyle, gtk_style_context_get_state(aStyle), "font",
908                         &desc, nullptr);
909 
910   aFontStyle->systemFont = true;
911 
912   constexpr auto quote = u"\""_ns;
913   NS_ConvertUTF8toUTF16 family(pango_font_description_get_family(desc));
914   *aFontName = quote + family + quote;
915 
916   aFontStyle->weight = FontWeight(pango_font_description_get_weight(desc));
917 
918   // FIXME: Set aFontStyle->stretch correctly!
919   aFontStyle->stretch = FontStretch::Normal();
920 
921   float size = float(pango_font_description_get_size(desc)) / PANGO_SCALE;
922 
923   // |size| is now either pixels or pango-points (not Mozilla-points!)
924 
925   if (!pango_font_description_get_size_is_absolute(desc)) {
926     // |size| is in pango-points, so convert to pixels.
927     size *= float(gfxPlatformGtk::GetFontScaleDPI()) / POINTS_PER_INCH_FLOAT;
928   }
929 
930   // |size| is now pixels but not scaled for the hidpi displays,
931   aFontStyle->size = size;
932 
933   pango_font_description_free(desc);
934 }
935 
NativeGetFont(FontID aID,nsString & aFontName,gfxFontStyle & aFontStyle)936 bool nsLookAndFeel::NativeGetFont(FontID aID, nsString& aFontName,
937                                   gfxFontStyle& aFontStyle) {
938   return mSystemTheme.GetFont(aID, aFontName, aFontStyle);
939 }
940 
GetFont(FontID aID,nsString & aFontName,gfxFontStyle & aFontStyle) const941 bool nsLookAndFeel::PerThemeData::GetFont(FontID aID, nsString& aFontName,
942                                           gfxFontStyle& aFontStyle) const {
943   switch (aID) {
944     case FontID::Menu:             // css2
945     case FontID::MozPullDownMenu:  // css3
946       aFontName = mMenuFontName;
947       aFontStyle = mMenuFontStyle;
948       break;
949 
950     case FontID::MozField:  // css3
951     case FontID::MozList:   // css3
952       aFontName = mFieldFontName;
953       aFontStyle = mFieldFontStyle;
954       break;
955 
956     case FontID::MozButton:  // css3
957       aFontName = mButtonFontName;
958       aFontStyle = mButtonFontStyle;
959       break;
960 
961     case FontID::Caption:       // css2
962     case FontID::Icon:          // css2
963     case FontID::MessageBox:    // css2
964     case FontID::SmallCaption:  // css2
965     case FontID::StatusBar:     // css2
966     case FontID::MozWindow:     // css3
967     case FontID::MozDocument:   // css3
968     case FontID::MozWorkspace:  // css3
969     case FontID::MozDesktop:    // css3
970     case FontID::MozInfo:       // css3
971     case FontID::MozDialog:     // css3
972     default:
973       aFontName = mDefaultFontName;
974       aFontStyle = mDefaultFontStyle;
975       break;
976   }
977 
978   // Scale the font for the current monitor
979   double scaleFactor = StaticPrefs::layout_css_devPixelsPerPx();
980   if (scaleFactor > 0) {
981     aFontStyle.size *=
982         widget::ScreenHelperGTK::GetGTKMonitorScaleFactor() / scaleFactor;
983   } else {
984     // Convert gdk pixels to CSS pixels.
985     aFontStyle.size /= gfxPlatformGtk::GetFontScaleFactor();
986   }
987 
988   return true;
989 }
990 
991 // Check color contrast according to
992 // https://www.w3.org/TR/AERT/#color-contrast
HasGoodContrastVisibility(GdkRGBA & aColor1,GdkRGBA & aColor2)993 static bool HasGoodContrastVisibility(GdkRGBA& aColor1, GdkRGBA& aColor2) {
994   int32_t luminosityDifference = NS_LUMINOSITY_DIFFERENCE(
995       GDK_RGBA_TO_NS_RGBA(aColor1), GDK_RGBA_TO_NS_RGBA(aColor2));
996   if (luminosityDifference < NS_SUFFICIENT_LUMINOSITY_DIFFERENCE) {
997     return false;
998   }
999 
1000   double colorDifference = std::abs(aColor1.red - aColor2.red) +
1001                            std::abs(aColor1.green - aColor2.green) +
1002                            std::abs(aColor1.blue - aColor2.blue);
1003   return (colorDifference * 255.0 > 500.0);
1004 }
1005 
1006 // Check if the foreground/background colors match with default white/black
1007 // html page colors.
IsGtkThemeCompatibleWithHTMLColors()1008 static bool IsGtkThemeCompatibleWithHTMLColors() {
1009   GdkRGBA white = {1.0, 1.0, 1.0};
1010   GdkRGBA black = {0.0, 0.0, 0.0};
1011 
1012   GtkStyleContext* style = GetStyleContext(MOZ_GTK_WINDOW);
1013 
1014   GdkRGBA textColor;
1015   gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &textColor);
1016 
1017   // Theme text color and default white html page background
1018   if (!HasGoodContrastVisibility(textColor, white)) {
1019     return false;
1020   }
1021 
1022   GdkRGBA backgroundColor;
1023   gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL,
1024                                          &backgroundColor);
1025 
1026   // Theme background color and default white html page background
1027   if (HasGoodContrastVisibility(backgroundColor, white)) {
1028     return false;
1029   }
1030 
1031   // Theme background color and default black text color
1032   return HasGoodContrastVisibility(backgroundColor, black);
1033 }
1034 
GetGtkSettingsStringKey(const char * aKey)1035 static nsCString GetGtkSettingsStringKey(const char* aKey) {
1036   MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
1037   nsCString ret;
1038   GtkSettings* settings = gtk_settings_get_default();
1039   char* value = nullptr;
1040   g_object_get(settings, aKey, &value, nullptr);
1041   if (value) {
1042     ret.Assign(value);
1043     g_free(value);
1044   }
1045   return ret;
1046 }
1047 
GetGtkTheme()1048 static nsCString GetGtkTheme() {
1049   return GetGtkSettingsStringKey("gtk-theme-name");
1050 }
1051 
GetPreferDarkTheme()1052 static bool GetPreferDarkTheme() {
1053   GtkSettings* settings = gtk_settings_get_default();
1054   gboolean preferDarkTheme = FALSE;
1055   g_object_get(settings, "gtk-application-prefer-dark-theme", &preferDarkTheme,
1056                nullptr);
1057   return preferDarkTheme == TRUE;
1058 }
1059 
1060 // It seems GTK doesn't have an API to query if the current theme is "light" or
1061 // "dark", so we synthesize it from the CSS2 Window/WindowText colors instead,
1062 // by comparing their luminosity.
GetThemeIsDark()1063 static bool GetThemeIsDark() {
1064   GdkRGBA bg, fg;
1065   GtkStyleContext* style = GetStyleContext(MOZ_GTK_WINDOW);
1066   gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &bg);
1067   gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &fg);
1068   return RelativeLuminanceUtils::Compute(GDK_RGBA_TO_NS_RGBA(bg)) <
1069          RelativeLuminanceUtils::Compute(GDK_RGBA_TO_NS_RGBA(fg));
1070 }
1071 
ConfigureTheme(const LookAndFeelTheme & aTheme)1072 void nsLookAndFeel::ConfigureTheme(const LookAndFeelTheme& aTheme) {
1073   MOZ_ASSERT(XRE_IsContentProcess());
1074   GtkSettings* settings = gtk_settings_get_default();
1075   g_object_set(settings, "gtk-theme-name", aTheme.themeName().get(),
1076                "gtk-application-prefer-dark-theme",
1077                aTheme.preferDarkTheme() ? TRUE : FALSE, nullptr);
1078 }
1079 
RestoreSystemTheme()1080 void nsLookAndFeel::RestoreSystemTheme() {
1081   LOGLNF("RestoreSystemTheme(%s, %d)\n", mSystemTheme.mName.get(),
1082          mSystemTheme.mPreferDarkTheme);
1083 
1084   // Available on Gtk 3.20+.
1085   static auto sGtkSettingsResetProperty =
1086       (void (*)(GtkSettings*, const gchar*))dlsym(
1087           RTLD_DEFAULT, "gtk_settings_reset_property");
1088 
1089   GtkSettings* settings = gtk_settings_get_default();
1090   if (sGtkSettingsResetProperty) {
1091     sGtkSettingsResetProperty(settings, "gtk-theme-name");
1092     sGtkSettingsResetProperty(settings, "gtk-application-prefer-dark-theme");
1093   } else {
1094     g_object_set(settings, "gtk-theme-name", mSystemTheme.mName.get(),
1095                  "gtk-application-prefer-dark-theme",
1096                  mSystemTheme.mPreferDarkTheme, nullptr);
1097   }
1098   moz_gtk_refresh();
1099 }
1100 
1101 template <typename Callback>
WithAltThemeConfigured(const Callback & aFn)1102 void nsLookAndFeel::WithAltThemeConfigured(const Callback& aFn) {
1103   AutoRestore<bool> restoreIgnoreSettings(sIgnoreChangedSettings);
1104   sIgnoreChangedSettings = true;
1105   GtkSettings* settings = gtk_settings_get_default();
1106 
1107   bool fellBackToDefaultTheme = false;
1108 
1109   // Try to select the opposite variant of the current theme first...
1110   LOGLNF("    toggling gtk-application-prefer-dark-theme\n");
1111   g_object_set(settings, "gtk-application-prefer-dark-theme",
1112                !mSystemTheme.mIsDark, nullptr);
1113   moz_gtk_refresh();
1114 
1115   // Toggling gtk-application-prefer-dark-theme is not enough generally to
1116   // switch from dark to light theme.  If the theme didn't change, and we have
1117   // a dark theme, try to first remove -Dark{,er,est} from the theme name to
1118   // find the light variant.
1119   if (mSystemTheme.mIsDark && mSystemTheme.mIsDark == GetThemeIsDark()) {
1120     nsCString potentialLightThemeName = mSystemTheme.mName;
1121     // clang-format off
1122     constexpr nsLiteralCString kSubstringsToRemove[] = {
1123         "-darkest"_ns, "-darker"_ns, "-dark"_ns,
1124         "-Darkest"_ns, "-Darker"_ns, "-Dark"_ns,
1125         "_darkest"_ns, "_darker"_ns, "_dark"_ns,
1126         "_Darkest"_ns, "_Darker"_ns, "_Dark"_ns,
1127     };
1128     // clang-format on
1129     bool found = false;
1130     for (auto& s : kSubstringsToRemove) {
1131       potentialLightThemeName = mSystemTheme.mName;
1132       potentialLightThemeName.ReplaceSubstring(s, ""_ns);
1133       if (potentialLightThemeName.Length() != mSystemTheme.mName.Length()) {
1134         found = true;
1135         break;
1136       }
1137     }
1138     if (found) {
1139       g_object_set(settings, "gtk-theme-name", potentialLightThemeName.get(),
1140                    nullptr);
1141       moz_gtk_refresh();
1142     }
1143   }
1144 
1145   if (mSystemTheme.mIsDark == GetThemeIsDark()) {
1146     // If the theme still didn't change enough, fall back to either Adwaita or
1147     // Adwaita-dark.
1148     g_object_set(settings, "gtk-theme-name",
1149                  mSystemTheme.mIsDark ? "Adwaita" : "Adwaita-dark", nullptr);
1150     moz_gtk_refresh();
1151     fellBackToDefaultTheme = true;
1152   }
1153 
1154   aFn(fellBackToDefaultTheme);
1155 
1156   // Restore the system theme.
1157   RestoreSystemTheme();
1158 }
1159 
AnyColorChannelIsDifferent(nscolor aColor)1160 static bool AnyColorChannelIsDifferent(nscolor aColor) {
1161   return NS_GET_R(aColor) != NS_GET_G(aColor) ||
1162          NS_GET_R(aColor) != NS_GET_B(aColor);
1163 }
1164 
InitializeAltTheme()1165 void nsLookAndFeel::InitializeAltTheme() {
1166   WithAltThemeConfigured([&](bool aFellBackToDefaultTheme) {
1167     mAltTheme.Init();
1168     // Some of the alt theme colors we can grab from the system theme, if we
1169     // fell back to the default light / dark themes.
1170     if (aFellBackToDefaultTheme) {
1171       if (StaticPrefs::widget_gtk_alt_theme_selection()) {
1172         mAltTheme.mTextSelectedText = mSystemTheme.mTextSelectedText;
1173         mAltTheme.mTextSelectedBackground =
1174             mSystemTheme.mTextSelectedBackground;
1175       }
1176 
1177       if (StaticPrefs::widget_gtk_alt_theme_scrollbar()) {
1178         mAltTheme.mThemedScrollbar = mSystemTheme.mThemedScrollbar;
1179         mAltTheme.mThemedScrollbarInactive =
1180             mSystemTheme.mThemedScrollbarInactive;
1181         mAltTheme.mThemedScrollbarThumb = mSystemTheme.mThemedScrollbarThumb;
1182         mAltTheme.mThemedScrollbarThumbHover =
1183             mSystemTheme.mThemedScrollbarThumbHover;
1184         mAltTheme.mThemedScrollbarThumbInactive =
1185             mSystemTheme.mThemedScrollbarThumbInactive;
1186       }
1187 
1188       if (StaticPrefs::widget_gtk_alt_theme_scrollbar_active()) {
1189         mAltTheme.mThemedScrollbarThumbActive =
1190             mSystemTheme.mThemedScrollbarThumbActive;
1191       }
1192 
1193       if (StaticPrefs::widget_gtk_alt_theme_selection()) {
1194         mAltTheme.mAccentColor = mSystemTheme.mAccentColor;
1195         mAltTheme.mAccentColorForeground = mSystemTheme.mAccentColorForeground;
1196       }
1197     }
1198   });
1199 }
1200 
EnsureInit()1201 void nsLookAndFeel::EnsureInit() {
1202   if (mInitialized) {
1203     return;
1204   }
1205 
1206   LOGLNF("nsLookAndFeel::EnsureInit");
1207 
1208   // Gtk manages a screen's CSS in the settings object so we
1209   // ask Gtk to create it explicitly. Otherwise we may end up
1210   // with wrong color theme, see Bug 972382
1211   GtkSettings* settings = gtk_settings_get_default();
1212   if (MOZ_UNLIKELY(!settings)) {
1213     NS_WARNING("EnsureInit: No settings");
1214     return;
1215   }
1216 
1217   mInitialized = true;
1218   if (mSystemThemeOverridden) {
1219     // Our current theme may be different from the system theme if we're
1220     // matching the firefox theme. Make sure to restore the original system
1221     // theme.
1222     RestoreSystemTheme();
1223     mSystemThemeOverridden = false;
1224   }
1225 
1226   // gtk does non threadsafe refcounting
1227   MOZ_ASSERT(NS_IsMainThread());
1228 
1229   gboolean enableAnimations = false;
1230   g_object_get(settings, "gtk-enable-animations", &enableAnimations, nullptr);
1231   mPrefersReducedMotion = !enableAnimations;
1232 
1233   gint blink_time;
1234   gboolean blink;
1235   g_object_get(settings, "gtk-cursor-blink-time", &blink_time,
1236                "gtk-cursor-blink", &blink, nullptr);
1237   mCaretBlinkTime = blink ? (int32_t)blink_time : 0;
1238 
1239   mCSDAvailable =
1240       nsWindow::GtkWindowDecoration() != nsWindow::GTK_DECORATION_NONE;
1241   mCSDHideTitlebarByDefault = nsWindow::HideTitlebarByDefault();
1242 
1243   mSystemTheme.Init();
1244 
1245   mCSDCloseButton = false;
1246   mCSDMinimizeButton = false;
1247   mCSDMaximizeButton = false;
1248   mCSDCloseButtonPosition = 0;
1249   mCSDMinimizeButtonPosition = 0;
1250   mCSDMaximizeButtonPosition = 0;
1251 
1252   // We need to initialize whole CSD config explicitly because it's queried
1253   // as -moz-gtk* media features.
1254   ButtonLayout buttonLayout[TOOLBAR_BUTTONS];
1255 
1256   size_t activeButtons =
1257       GetGtkHeaderBarButtonLayout(Span(buttonLayout), &mCSDReversedPlacement);
1258   for (size_t i = 0; i < activeButtons; i++) {
1259     // We check if a button is represented on the right side of the tabbar.
1260     // Then we assign it a value from 3 to 5, instead of 0 to 2 when it is on
1261     // the left side.
1262     const ButtonLayout& layout = buttonLayout[i];
1263     int32_t* pos = nullptr;
1264     switch (layout.mType) {
1265       case MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE:
1266         mCSDMinimizeButton = true;
1267         pos = &mCSDMinimizeButtonPosition;
1268         break;
1269       case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE:
1270         mCSDMaximizeButton = true;
1271         pos = &mCSDMaximizeButtonPosition;
1272         break;
1273       case MOZ_GTK_HEADER_BAR_BUTTON_CLOSE:
1274         mCSDCloseButton = true;
1275         pos = &mCSDCloseButtonPosition;
1276         break;
1277       default:
1278         break;
1279     }
1280 
1281     if (pos) {
1282       *pos = i;
1283       if (layout.mAtRight) {
1284         *pos += TOOLBAR_BUTTONS;
1285       }
1286     }
1287   }
1288 
1289   // Switching themes on startup has some performance cost, so until we use the
1290   // dark colors, keep it pref'd off.
1291   if (mSystemTheme.mIsDark || StaticPrefs::widget_gtk_alt_theme_dark()) {
1292     InitializeAltTheme();
1293   } else {
1294     mAltTheme = mSystemTheme;
1295   }
1296 
1297   LOGLNF("System Theme: %s. Alt Theme: %s\n", mSystemTheme.mName.get(),
1298          mAltTheme.mName.get());
1299 
1300   MatchFirefoxThemeIfNeeded();
1301 
1302   RecordTelemetry();
1303 }
1304 
MatchFirefoxThemeIfNeeded()1305 bool nsLookAndFeel::MatchFirefoxThemeIfNeeded() {
1306   AutoRestore<bool> restoreIgnoreSettings(sIgnoreChangedSettings);
1307   sIgnoreChangedSettings = true;
1308 
1309   if (!StaticPrefs::widget_gtk_follow_firefox_theme()) {
1310     return false;
1311   }
1312 
1313   const bool matchesSystem = [&] {
1314     switch (StaticPrefs::browser_theme_toolbar_theme()) {
1315       case 0:
1316         return mSystemTheme.mIsDark;
1317       case 1:
1318         return !mSystemTheme.mIsDark;
1319       default:
1320         return true;
1321     }
1322   }();
1323 
1324   const bool usingSystem = !mSystemThemeOverridden;
1325 
1326   LOGLNF("MatchFirefoxThemeIfNeeded(matchesSystem=%d, usingSystem=%d)\n",
1327          matchesSystem, usingSystem);
1328 
1329   if (usingSystem == matchesSystem) {
1330     return false;
1331   }
1332 
1333   mSystemThemeOverridden = !matchesSystem;
1334   if (matchesSystem) {
1335     RestoreSystemTheme();
1336   } else {
1337     LOGLNF("Setting theme %s, %d\n", mAltTheme.mName.get(),
1338            mAltTheme.mPreferDarkTheme);
1339 
1340     GtkSettings* settings = gtk_settings_get_default();
1341     if (mSystemTheme.mName == mAltTheme.mName) {
1342       // Prefer setting only gtk-application-prefer-dark-theme, so we can still
1343       // get notified from notify::gtk-theme-name if the user changes the theme.
1344       g_object_set(settings, "gtk-application-prefer-dark-theme",
1345                    mAltTheme.mPreferDarkTheme, nullptr);
1346     } else {
1347       g_object_set(settings, "gtk-theme-name", mAltTheme.mName.get(),
1348                    "gtk-application-prefer-dark-theme",
1349                    mAltTheme.mPreferDarkTheme, nullptr);
1350     }
1351     moz_gtk_refresh();
1352   }
1353   return true;
1354 }
1355 
FirefoxThemeChanged(const char *,void * aInstance)1356 void nsLookAndFeel::FirefoxThemeChanged(const char*, void* aInstance) {
1357   auto* lnf = static_cast<nsLookAndFeel*>(aInstance);
1358   if (lnf->MatchFirefoxThemeIfNeeded()) {
1359     LookAndFeel::NotifyChangedAllWindows(
1360         widget::ThemeChangeKind::StyleAndLayout);
1361   }
1362 }
1363 
GetGtkContentTheme(LookAndFeelTheme & aTheme)1364 void nsLookAndFeel::GetGtkContentTheme(LookAndFeelTheme& aTheme) {
1365   if (NS_SUCCEEDED(Preferences::GetCString("widget.content.gtk-theme-override",
1366                                            aTheme.themeName()))) {
1367     return;
1368   }
1369 
1370   auto& theme = StaticPrefs::widget_content_allow_gtk_dark_theme()
1371                     ? mSystemTheme
1372                     : LightTheme();
1373   aTheme.preferDarkTheme() = theme.mPreferDarkTheme;
1374   aTheme.themeName() = theme.mName;
1375 }
1376 
GetBackgroundColor(GtkStyleContext * aStyle,nscolor aForForegroundColor,GtkStateFlags aState=GTK_STATE_FLAG_NORMAL)1377 static nscolor GetBackgroundColor(
1378     GtkStyleContext* aStyle, nscolor aForForegroundColor,
1379     GtkStateFlags aState = GTK_STATE_FLAG_NORMAL) {
1380   GdkRGBA gdkColor;
1381   gtk_style_context_get_background_color(aStyle, aState, &gdkColor);
1382   nscolor color = GDK_RGBA_TO_NS_RGBA(gdkColor);
1383   if (NS_GET_A(color)) {
1384     return color;
1385   }
1386 
1387   // Try to synthesize a color from a background-image.
1388   GValue value = G_VALUE_INIT;
1389   gtk_style_context_get_property(aStyle, "background-image", aState, &value);
1390   auto cleanup = MakeScopeExit([&] { g_value_unset(&value); });
1391 
1392   if (GetColorFromImagePattern(&value, &color)) {
1393     return color;
1394   }
1395 
1396   {
1397     GdkRGBA light, dark;
1398     if (GetGradientColors(&value, &light, &dark)) {
1399       nscolor l = GDK_RGBA_TO_NS_RGBA(light);
1400       nscolor d = GDK_RGBA_TO_NS_RGBA(dark);
1401       // Return the one with more contrast.
1402       // TODO(emilio): This could do interpolation or what not but seems
1403       // overkill.
1404       return NS_LUMINOSITY_DIFFERENCE(l, aForForegroundColor) >
1405                      NS_LUMINOSITY_DIFFERENCE(d, aForForegroundColor)
1406                  ? l
1407                  : d;
1408     }
1409   }
1410 
1411   return NS_TRANSPARENT;
1412 }
1413 
Init()1414 void nsLookAndFeel::PerThemeData::Init() {
1415   mName = GetGtkTheme();
1416 
1417   GtkStyleContext* style;
1418 
1419   mHighContrast = StaticPrefs::widget_content_gtk_high_contrast_enabled() &&
1420                   mName.Find("HighContrast"_ns) >= 0;
1421 
1422   mPreferDarkTheme = GetPreferDarkTheme();
1423 
1424   mIsDark = GetThemeIsDark();
1425 
1426   mCompatibleWithHTMLLightColors =
1427       !mIsDark && IsGtkThemeCompatibleWithHTMLColors();
1428 
1429   GdkRGBA color;
1430   // Some themes style the <trough>, while others style the <scrollbar>
1431   // itself, so we look at both and compose the colors.
1432   style = GetStyleContext(MOZ_GTK_SCROLLBAR_VERTICAL);
1433   gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color);
1434   mThemedScrollbar = GDK_RGBA_TO_NS_RGBA(color);
1435   gtk_style_context_get_background_color(style, GTK_STATE_FLAG_BACKDROP,
1436                                          &color);
1437   mThemedScrollbarInactive = GDK_RGBA_TO_NS_RGBA(color);
1438 
1439   style = GetStyleContext(MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL);
1440   gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color);
1441   mThemedScrollbar =
1442       NS_ComposeColors(mThemedScrollbar, GDK_RGBA_TO_NS_RGBA(color));
1443   gtk_style_context_get_background_color(style, GTK_STATE_FLAG_BACKDROP,
1444                                          &color);
1445   mThemedScrollbarInactive =
1446       NS_ComposeColors(mThemedScrollbarInactive, GDK_RGBA_TO_NS_RGBA(color));
1447 
1448   mMozScrollbar = mThemedScrollbar;
1449 
1450   style = GetStyleContext(MOZ_GTK_SCROLLBAR_THUMB_VERTICAL);
1451   gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color);
1452   mThemedScrollbarThumb = GDK_RGBA_TO_NS_RGBA(color);
1453   gtk_style_context_get_background_color(style, GTK_STATE_FLAG_PRELIGHT,
1454                                          &color);
1455   mThemedScrollbarThumbHover = GDK_RGBA_TO_NS_RGBA(color);
1456   gtk_style_context_get_background_color(
1457       style, GtkStateFlags(GTK_STATE_FLAG_PRELIGHT | GTK_STATE_FLAG_ACTIVE),
1458       &color);
1459   mThemedScrollbarThumbActive = GDK_RGBA_TO_NS_RGBA(color);
1460   gtk_style_context_get_background_color(style, GTK_STATE_FLAG_BACKDROP,
1461                                          &color);
1462   mThemedScrollbarThumbInactive = GDK_RGBA_TO_NS_RGBA(color);
1463 
1464   // Make sure that the thumb is visible, at least.
1465   const bool fallbackToUnthemedColors = !ShouldHonorThemeScrollbarColors() ||
1466                                         !NS_GET_A(mThemedScrollbarThumb) ||
1467                                         !NS_GET_A(mThemedScrollbarThumbHover) ||
1468                                         !NS_GET_A(mThemedScrollbarThumbActive);
1469   if (fallbackToUnthemedColors) {
1470     mMozScrollbar = mThemedScrollbar = widget::sScrollbarColor.ToABGR();
1471     mThemedScrollbarInactive = widget::sScrollbarColor.ToABGR();
1472     mThemedScrollbarThumb = widget::sScrollbarThumbColor.ToABGR();
1473     mThemedScrollbarThumbHover =
1474         nsNativeBasicTheme::AdjustUnthemedScrollbarThumbColor(
1475             mThemedScrollbarThumb, NS_EVENT_STATE_HOVER);
1476     mThemedScrollbarThumbActive =
1477         nsNativeBasicTheme::AdjustUnthemedScrollbarThumbColor(
1478             mThemedScrollbarThumb, NS_EVENT_STATE_ACTIVE);
1479     mThemedScrollbarThumbInactive = mThemedScrollbarThumb;
1480   }
1481 
1482   // The label is not added to a parent widget, but shared for constructing
1483   // different style contexts.  The node hierarchy is constructed only on
1484   // the label style context.
1485   GtkWidget* labelWidget = gtk_label_new("M");
1486   g_object_ref_sink(labelWidget);
1487 
1488   // Window colors
1489   style = GetStyleContext(MOZ_GTK_WINDOW);
1490 
1491   gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
1492   mMozWindowText = GDK_RGBA_TO_NS_RGBA(color);
1493 
1494   mMozWindowBackground = GetBackgroundColor(style, mMozWindowText);
1495 
1496   gtk_style_context_get_border_color(style, GTK_STATE_FLAG_NORMAL, &color);
1497   mMozWindowActiveBorder = GDK_RGBA_TO_NS_RGBA(color);
1498 
1499   gtk_style_context_get_border_color(style, GTK_STATE_FLAG_INSENSITIVE, &color);
1500   mMozWindowInactiveBorder = GDK_RGBA_TO_NS_RGBA(color);
1501 
1502   mMozWindowInactiveCaption =
1503       GetBackgroundColor(style, mMozWindowText, GTK_STATE_FLAG_INSENSITIVE);
1504 
1505   style = GetStyleContext(MOZ_GTK_WINDOW_CONTAINER);
1506   {
1507     GtkStyleContext* labelStyle = CreateStyleForWidget(labelWidget, style);
1508     GetSystemFontInfo(labelStyle, &mDefaultFontName, &mDefaultFontStyle);
1509     g_object_unref(labelStyle);
1510   }
1511 
1512   // tooltip foreground and background
1513   style = GetStyleContext(MOZ_GTK_TOOLTIP_BOX_LABEL);
1514   gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
1515   mInfoText = GDK_RGBA_TO_NS_RGBA(color);
1516 
1517   style = GetStyleContext(MOZ_GTK_TOOLTIP);
1518   mInfoBackground = GetBackgroundColor(style, mInfoText);
1519 
1520   style = GetStyleContext(MOZ_GTK_MENUITEM);
1521   {
1522     GtkStyleContext* accelStyle =
1523         CreateStyleForWidget(gtk_accel_label_new("M"), style);
1524 
1525     GetSystemFontInfo(accelStyle, &mMenuFontName, &mMenuFontStyle);
1526 
1527     gtk_style_context_get_color(accelStyle, GTK_STATE_FLAG_NORMAL, &color);
1528     mMenuText = GDK_RGBA_TO_NS_RGBA(color);
1529     gtk_style_context_get_color(accelStyle, GTK_STATE_FLAG_INSENSITIVE, &color);
1530     mMenuTextInactive = GDK_RGBA_TO_NS_RGBA(color);
1531     g_object_unref(accelStyle);
1532   }
1533 
1534   style = GetStyleContext(MOZ_GTK_HEADER_BAR);
1535   {
1536     gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
1537     mTitlebarText = GDK_RGBA_TO_NS_RGBA(color);
1538 
1539     gtk_style_context_get_color(style, GTK_STATE_FLAG_BACKDROP, &color);
1540     mTitlebarInactiveText = GDK_RGBA_TO_NS_RGBA(color);
1541   }
1542 
1543   style = GetStyleContext(MOZ_GTK_MENUPOPUP);
1544   mMenuBackground = [&] {
1545     nscolor color = GetBackgroundColor(style, mMenuText);
1546     if (NS_GET_A(color)) {
1547       return color;
1548     }
1549     // Some themes only style menupopups with the backdrop pseudo-class. Since a
1550     // context / popup menu always seems to match that, try that before giving
1551     // up.
1552     color = GetBackgroundColor(style, mMenuText, GTK_STATE_FLAG_BACKDROP);
1553     if (NS_GET_A(color)) {
1554       return color;
1555     }
1556     // If we get here we couldn't figure out the right color to use. Rather than
1557     // falling back to transparent, fall back to the window background.
1558     NS_WARNING(
1559         "Couldn't find menu background color, falling back to window "
1560         "background");
1561     return mMozWindowBackground;
1562   }();
1563 
1564   style = GetStyleContext(MOZ_GTK_MENUITEM);
1565   gtk_style_context_get_color(style, GTK_STATE_FLAG_PRELIGHT, &color);
1566   mMenuHoverText = GDK_RGBA_TO_NS_RGBA(color);
1567   mMenuHover =
1568       GetBackgroundColor(style, mMenuHoverText, GTK_STATE_FLAG_PRELIGHT);
1569 
1570   GtkWidget* parent = gtk_fixed_new();
1571   GtkWidget* window = gtk_window_new(GTK_WINDOW_POPUP);
1572   GtkWidget* treeView = gtk_tree_view_new();
1573   GtkWidget* linkButton = gtk_link_button_new("http://example.com/");
1574   GtkWidget* menuBar = gtk_menu_bar_new();
1575   GtkWidget* menuBarItem = gtk_menu_item_new();
1576   GtkWidget* entry = gtk_entry_new();
1577   GtkWidget* textView = gtk_text_view_new();
1578 
1579   gtk_container_add(GTK_CONTAINER(parent), treeView);
1580   gtk_container_add(GTK_CONTAINER(parent), linkButton);
1581   gtk_container_add(GTK_CONTAINER(parent), menuBar);
1582   gtk_menu_shell_append(GTK_MENU_SHELL(menuBar), menuBarItem);
1583   gtk_container_add(GTK_CONTAINER(window), parent);
1584   gtk_container_add(GTK_CONTAINER(parent), entry);
1585   gtk_container_add(GTK_CONTAINER(parent), textView);
1586 
1587   // Text colors
1588   GdkRGBA bgColor;
1589   // If the text window background is translucent, then the background of
1590   // the textview root node is visible.
1591   style = GetStyleContext(MOZ_GTK_TEXT_VIEW);
1592   gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL,
1593                                          &bgColor);
1594 
1595   style = GetStyleContext(MOZ_GTK_TEXT_VIEW_TEXT);
1596   gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color);
1597   ApplyColorOver(color, &bgColor);
1598   mFieldBackground = GDK_RGBA_TO_NS_RGBA(bgColor);
1599   gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
1600   mFieldText = GDK_RGBA_TO_NS_RGBA(color);
1601 
1602   // Selected text and background
1603   {
1604     GtkStyleContext* selectionStyle =
1605         GetStyleContext(MOZ_GTK_TEXT_VIEW_TEXT_SELECTION);
1606     auto GrabSelectionColors = [&](GtkStyleContext* style) {
1607       gtk_style_context_get_background_color(
1608           style,
1609           static_cast<GtkStateFlags>(GTK_STATE_FLAG_FOCUSED |
1610                                      GTK_STATE_FLAG_SELECTED),
1611           &color);
1612       mTextSelectedBackground = GDK_RGBA_TO_NS_RGBA(color);
1613       gtk_style_context_get_color(
1614           style,
1615           static_cast<GtkStateFlags>(GTK_STATE_FLAG_FOCUSED |
1616                                      GTK_STATE_FLAG_SELECTED),
1617           &color);
1618       mTextSelectedText = GDK_RGBA_TO_NS_RGBA(color);
1619     };
1620     GrabSelectionColors(selectionStyle);
1621     if (mTextSelectedBackground == mTextSelectedText) {
1622       // Some old distros/themes don't properly use the .selection style, so
1623       // fall back to the regular text view style.
1624       GrabSelectionColors(style);
1625     }
1626 
1627     // Default accent color is the selection background / foreground colors.
1628     mAccentColor = mTextSelectedBackground;
1629     mAccentColorForeground = mTextSelectedText;
1630 
1631     // But prefer named colors, as those are more general purpose than the
1632     // actual selection style, which might e.g. be too-transparent.
1633     //
1634     // NOTE(emilio): It's unclear which one of the theme_selected_* or the
1635     // selected_* pairs should we prefer, in all themes that define both that
1636     // I've found, they're always the same.
1637     {
1638       GdkRGBA bg, fg;
1639       const bool found =
1640           (gtk_style_context_lookup_color(style, "selected_bg_color", &bg) &&
1641            gtk_style_context_lookup_color(style, "selected_fg_color", &fg)) ||
1642           (gtk_style_context_lookup_color(style, "theme_selected_bg_color",
1643                                           &bg) &&
1644            gtk_style_context_lookup_color(style, "theme_selected_fg_color",
1645                                           &fg));
1646       if (found) {
1647         mAccentColor = GDK_RGBA_TO_NS_RGBA(bg);
1648         mAccentColorForeground = GDK_RGBA_TO_NS_RGBA(fg);
1649 
1650         // If the accent colors are semi-transparent and the theme provides a
1651         // background color, blend with them to get the "final" color, see
1652         // bug 1717077.
1653         if (NS_GET_A(mAccentColor) != 255 &&
1654             (gtk_style_context_lookup_color(style, "bg_color", &bg) ||
1655              gtk_style_context_lookup_color(style, "theme_bg_color", &bg))) {
1656           mAccentColor =
1657               NS_ComposeColors(GDK_RGBA_TO_NS_RGBA(bg), mAccentColor);
1658         }
1659 
1660         // A semi-transparent foreground color would be kinda silly, but is done
1661         // for symmetry.
1662         if (NS_GET_A(mAccentColorForeground) != 255 &&
1663             (gtk_style_context_lookup_color(style, "fg_color", &fg) ||
1664              gtk_style_context_lookup_color(style, "theme_fg_color", &fg))) {
1665           mAccentColorForeground =
1666               NS_ComposeColors(GDK_RGBA_TO_NS_RGBA(fg), mAccentColorForeground);
1667         }
1668       }
1669     }
1670 
1671     // Accent is the darker one, unless the foreground isn't really a color (is
1672     // all white / black / gray) and the background is, in which case we stick
1673     // to what we have.
1674     if (RelativeLuminanceUtils::Compute(mAccentColor) >
1675             RelativeLuminanceUtils::Compute(mAccentColorForeground) &&
1676         (AnyColorChannelIsDifferent(mAccentColorForeground) ||
1677          !AnyColorChannelIsDifferent(mAccentColor))) {
1678       std::swap(mAccentColor, mAccentColorForeground);
1679     }
1680 
1681     // Blend with white, ensuring the color is opaque, so that the UI doesn't
1682     // have to care about alpha.
1683     mAccentColorForeground =
1684         NS_ComposeColors(NS_RGB(0xff, 0xff, 0xff), mAccentColorForeground);
1685     mAccentColor = NS_ComposeColors(NS_RGB(0xff, 0xff, 0xff), mAccentColor);
1686   }
1687 
1688   // Button text color
1689   style = GetStyleContext(MOZ_GTK_BUTTON);
1690   {
1691     GtkStyleContext* labelStyle = CreateStyleForWidget(labelWidget, style);
1692     GetSystemFontInfo(labelStyle, &mButtonFontName, &mButtonFontStyle);
1693     g_object_unref(labelStyle);
1694   }
1695 
1696   gtk_style_context_get_border_color(style, GTK_STATE_FLAG_NORMAL, &color);
1697   mButtonDefault = GDK_RGBA_TO_NS_RGBA(color);
1698   gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
1699   mButtonText = GDK_RGBA_TO_NS_RGBA(color);
1700   gtk_style_context_get_color(style, GTK_STATE_FLAG_PRELIGHT, &color);
1701   mButtonHoverText = GDK_RGBA_TO_NS_RGBA(color);
1702   gtk_style_context_get_color(style, GTK_STATE_FLAG_ACTIVE, &color);
1703   mButtonActiveText = GDK_RGBA_TO_NS_RGBA(color);
1704   gtk_style_context_get_background_color(style, GTK_STATE_FLAG_PRELIGHT,
1705                                          &color);
1706   mButtonHoverFace = GDK_RGBA_TO_NS_RGBA(color);
1707   if (!NS_GET_A(mButtonHoverFace)) {
1708     mButtonHoverFace = mMozWindowBackground;
1709   }
1710 
1711   // Combobox text color
1712   style = GetStyleContext(MOZ_GTK_COMBOBOX_ENTRY_TEXTAREA);
1713   gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
1714   mComboBoxText = GDK_RGBA_TO_NS_RGBA(color);
1715 
1716   // Menubar text and hover text colors
1717   style = GetStyleContext(MOZ_GTK_MENUBARITEM);
1718   gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
1719   mMenuBarText = GDK_RGBA_TO_NS_RGBA(color);
1720   gtk_style_context_get_color(style, GTK_STATE_FLAG_PRELIGHT, &color);
1721   mMenuBarHoverText = GDK_RGBA_TO_NS_RGBA(color);
1722 
1723   // GTK's guide to fancy odd row background colors:
1724   // 1) Check if a theme explicitly defines an odd row color
1725   // 2) If not, check if it defines an even row color, and darken it
1726   //    slightly by a hardcoded value (gtkstyle.c)
1727   // 3) If neither are defined, take the base background color and
1728   //    darken that by a hardcoded value
1729   style = GetStyleContext(MOZ_GTK_TREEVIEW);
1730 
1731   // Get odd row background color
1732   gtk_style_context_save(style);
1733   gtk_style_context_add_region(style, GTK_STYLE_REGION_ROW, GTK_REGION_ODD);
1734   gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color);
1735   mOddCellBackground = GDK_RGBA_TO_NS_RGBA(color);
1736   gtk_style_context_restore(style);
1737 
1738   // Column header colors
1739   style = GetStyleContext(MOZ_GTK_TREE_HEADER_CELL);
1740   gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
1741   mMozColHeaderText = GDK_RGBA_TO_NS_RGBA(color);
1742   gtk_style_context_get_color(style, GTK_STATE_FLAG_PRELIGHT, &color);
1743   mMozColHeaderHoverText = GDK_RGBA_TO_NS_RGBA(color);
1744 
1745   // Compute cell highlight colors
1746   InitCellHighlightColors();
1747 
1748   // GtkFrame has a "border" subnode on which Adwaita draws the border.
1749   // Some themes do not draw on this node but draw a border on the widget
1750   // root node, so check the root node if no border is found on the border
1751   // node.
1752   style = GetStyleContext(MOZ_GTK_FRAME_BORDER);
1753   bool themeUsesColors =
1754       GetBorderColors(style, &mFrameOuterLightBorder, &mFrameInnerDarkBorder);
1755   if (!themeUsesColors) {
1756     style = GetStyleContext(MOZ_GTK_FRAME);
1757     GetBorderColors(style, &mFrameOuterLightBorder, &mFrameInnerDarkBorder);
1758   }
1759 
1760   // Some themes have a unified menu bar, and support window dragging on it
1761   gboolean supports_menubar_drag = FALSE;
1762   GParamSpec* param_spec = gtk_widget_class_find_style_property(
1763       GTK_WIDGET_GET_CLASS(menuBar), "window-dragging");
1764   if (param_spec) {
1765     if (g_type_is_a(G_PARAM_SPEC_VALUE_TYPE(param_spec), G_TYPE_BOOLEAN)) {
1766       gtk_widget_style_get(menuBar, "window-dragging", &supports_menubar_drag,
1767                            nullptr);
1768     }
1769   }
1770   mMenuSupportsDrag = supports_menubar_drag;
1771 
1772   // TODO: It returns wrong color for themes which
1773   // sets link color for GtkLabel only as we query
1774   // GtkLinkButton style here.
1775   style = gtk_widget_get_style_context(linkButton);
1776   gtk_style_context_get_color(style, GTK_STATE_FLAG_LINK, &color);
1777   mNativeHyperLinkText = GDK_RGBA_TO_NS_RGBA(color);
1778 
1779   // invisible character styles
1780   guint value;
1781   g_object_get(entry, "invisible-char", &value, nullptr);
1782   mInvisibleCharacter = char16_t(value);
1783 
1784   // caret styles
1785   gtk_widget_style_get(entry, "cursor-aspect-ratio", &mCaretRatio, nullptr);
1786 
1787   GetSystemFontInfo(gtk_widget_get_style_context(entry), &mFieldFontName,
1788                     &mFieldFontStyle);
1789 
1790   gtk_widget_destroy(window);
1791   g_object_unref(labelWidget);
1792 
1793   if (LOGLNF_ENABLED()) {
1794     LOGLNF("Initialized theme %s (%d)\n", mName.get(), mPreferDarkTheme);
1795     for (auto id : MakeEnumeratedRange(ColorID::End)) {
1796       nscolor color;
1797       nsresult rv = GetColor(id, color);
1798       LOGLNF(" * color %d: pref=%s success=%d value=%x\n", int(id),
1799              GetColorPrefName(id), NS_SUCCEEDED(rv),
1800              NS_SUCCEEDED(rv) ? color : 0);
1801     }
1802   }
1803 }
1804 
1805 // virtual
GetPasswordCharacterImpl()1806 char16_t nsLookAndFeel::GetPasswordCharacterImpl() {
1807   EnsureInit();
1808   return mSystemTheme.mInvisibleCharacter;
1809 }
1810 
GetEchoPasswordImpl()1811 bool nsLookAndFeel::GetEchoPasswordImpl() { return false; }
1812 
WidgetUsesImage(WidgetNodeType aNodeType)1813 bool nsLookAndFeel::WidgetUsesImage(WidgetNodeType aNodeType) {
1814   static constexpr GtkStateFlags sFlagsToCheck[]{
1815       GTK_STATE_FLAG_NORMAL, GTK_STATE_FLAG_PRELIGHT,
1816       GtkStateFlags(GTK_STATE_FLAG_PRELIGHT | GTK_STATE_FLAG_ACTIVE),
1817       GTK_STATE_FLAG_BACKDROP, GTK_STATE_FLAG_INSENSITIVE};
1818 
1819   GtkStyleContext* style = GetStyleContext(aNodeType);
1820 
1821   GValue value = G_VALUE_INIT;
1822   for (GtkStateFlags state : sFlagsToCheck) {
1823     gtk_style_context_get_property(style, "background-image", state, &value);
1824     bool hasPattern = G_VALUE_TYPE(&value) == CAIRO_GOBJECT_TYPE_PATTERN &&
1825                       g_value_get_boxed(&value);
1826     g_value_unset(&value);
1827     if (hasPattern) {
1828       return true;
1829     }
1830   }
1831   return false;
1832 }
1833 
RecordLookAndFeelSpecificTelemetry()1834 void nsLookAndFeel::RecordLookAndFeelSpecificTelemetry() {
1835   // Gtk version we're on.
1836   nsString version;
1837   version.AppendPrintf("%d.%d", gtk_major_version, gtk_minor_version);
1838   Telemetry::ScalarSet(Telemetry::ScalarID::WIDGET_GTK_VERSION, version);
1839 
1840   // Whether the current Gtk theme has scrollbar buttons.
1841   bool hasScrollbarButtons =
1842       GetInt(LookAndFeel::IntID::ScrollArrowStyle) != eScrollArrow_None;
1843   mozilla::Telemetry::ScalarSet(
1844       mozilla::Telemetry::ScalarID::WIDGET_GTK_THEME_HAS_SCROLLBAR_BUTTONS,
1845       hasScrollbarButtons);
1846 
1847   // Whether the current Gtk theme uses something other than a solid color
1848   // background for scrollbar parts.
1849   bool scrollbarUsesImage = !ShouldHonorThemeScrollbarColors();
1850   mozilla::Telemetry::ScalarSet(
1851       mozilla::Telemetry::ScalarID::WIDGET_GTK_THEME_SCROLLBAR_USES_IMAGES,
1852       scrollbarUsesImage);
1853 }
1854 
ShouldHonorThemeScrollbarColors()1855 bool nsLookAndFeel::ShouldHonorThemeScrollbarColors() {
1856   // If the Gtk theme uses anything other than solid color backgrounds for Gtk
1857   // scrollbar parts, this is a good indication that painting XUL scrollbar part
1858   // elements using colors extracted from the theme won't provide good results.
1859   return !WidgetUsesImage(MOZ_GTK_SCROLLBAR_VERTICAL) &&
1860          !WidgetUsesImage(MOZ_GTK_SCROLLBAR_CONTENTS_VERTICAL) &&
1861          !WidgetUsesImage(MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL) &&
1862          !WidgetUsesImage(MOZ_GTK_SCROLLBAR_THUMB_VERTICAL);
1863 }
1864 
1865 #undef LOGLNF
1866 #undef LOGLNF_ENABLED
1867