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