1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* vim:expandtab:shiftwidth=4:tabstop=4:
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 
11 #include "nsLookAndFeel.h"
12 
13 #include <gtk/gtk.h>
14 #include <gdk/gdk.h>
15 
16 #include <pango/pango.h>
17 #include <pango/pango-fontmap.h>
18 
19 #include <fontconfig/fontconfig.h>
20 #include "gfxPlatformGtk.h"
21 #include "ScreenHelperGTK.h"
22 
23 #include "gtkdrawing.h"
24 #include "nsStyleConsts.h"
25 #include "gfxFontConstants.h"
26 #include "WidgetUtils.h"
27 #include "nsWindow.h"
28 
29 #include "mozilla/gfx/2D.h"
30 
31 #include <cairo-gobject.h>
32 #include "WidgetStyleCache.h"
33 #include "prenv.h"
34 
35 using mozilla::LookAndFeel;
36 
37 #define GDK_COLOR_TO_NS_RGB(c) \
38   ((nscolor)NS_RGB(c.red >> 8, c.green >> 8, c.blue >> 8))
39 #define GDK_RGBA_TO_NS_RGBA(c)                                    \
40   ((nscolor)NS_RGBA((int)((c).red * 255), (int)((c).green * 255), \
41                     (int)((c).blue * 255), (int)((c).alpha * 255)))
42 
43 #if !GTK_CHECK_VERSION(3, 12, 0)
44 #define GTK_STATE_FLAG_LINK (static_cast<GtkStateFlags>(1 << 9))
45 #endif
46 
nsLookAndFeel()47 nsLookAndFeel::nsLookAndFeel()
48     : nsXPLookAndFeel(),
49       mDefaultFontCached(false),
50       mButtonFontCached(false),
51       mFieldFontCached(false),
52       mMenuFontCached(false),
53       mInitialized(false) {}
54 
~nsLookAndFeel()55 nsLookAndFeel::~nsLookAndFeel() {}
56 
57 // Modifies color |*aDest| as if a pattern of color |aSource| was painted with
58 // CAIRO_OPERATOR_OVER to a surface with color |*aDest|.
ApplyColorOver(const GdkRGBA & aSource,GdkRGBA * aDest)59 static void ApplyColorOver(const GdkRGBA& aSource, GdkRGBA* aDest) {
60   gdouble sourceCoef = aSource.alpha;
61   gdouble destCoef = aDest->alpha * (1.0 - sourceCoef);
62   gdouble resultAlpha = sourceCoef + destCoef;
63   if (resultAlpha != 0.0) {  // don't divide by zero
64     destCoef /= resultAlpha;
65     sourceCoef /= resultAlpha;
66     aDest->red = sourceCoef * aSource.red + destCoef * aDest->red;
67     aDest->green = sourceCoef * aSource.green + destCoef * aDest->green;
68     aDest->blue = sourceCoef * aSource.blue + destCoef * aDest->blue;
69     aDest->alpha = resultAlpha;
70   }
71 }
72 
GetLightAndDarkness(const GdkRGBA & aColor,double * aLightness,double * aDarkness)73 static void GetLightAndDarkness(const GdkRGBA& aColor, double* aLightness,
74                                 double* aDarkness) {
75   double sum = aColor.red + aColor.green + aColor.blue;
76   *aLightness = sum * aColor.alpha;
77   *aDarkness = (3.0 - sum) * aColor.alpha;
78 }
79 
GetGradientColors(const GValue * aValue,GdkRGBA * aLightColor,GdkRGBA * aDarkColor)80 static bool GetGradientColors(const GValue* aValue, GdkRGBA* aLightColor,
81                               GdkRGBA* aDarkColor) {
82   if (!G_TYPE_CHECK_VALUE_TYPE(aValue, CAIRO_GOBJECT_TYPE_PATTERN))
83     return false;
84 
85   auto pattern = static_cast<cairo_pattern_t*>(g_value_get_boxed(aValue));
86   if (!pattern) return false;
87 
88   // Just picking the lightest and darkest colors as simple samples rather
89   // than trying to blend, which could get messy if there are many stops.
90   if (CAIRO_STATUS_SUCCESS !=
91       cairo_pattern_get_color_stop_rgba(pattern, 0, nullptr, &aDarkColor->red,
92                                         &aDarkColor->green, &aDarkColor->blue,
93                                         &aDarkColor->alpha))
94     return false;
95 
96   double maxLightness, maxDarkness;
97   GetLightAndDarkness(*aDarkColor, &maxLightness, &maxDarkness);
98   *aLightColor = *aDarkColor;
99 
100   GdkRGBA stop;
101   for (int index = 1;
102        CAIRO_STATUS_SUCCESS ==
103        cairo_pattern_get_color_stop_rgba(pattern, index, nullptr, &stop.red,
104                                          &stop.green, &stop.blue, &stop.alpha);
105        ++index) {
106     double lightness, darkness;
107     GetLightAndDarkness(stop, &lightness, &darkness);
108     if (lightness > maxLightness) {
109       maxLightness = lightness;
110       *aLightColor = stop;
111     }
112     if (darkness > maxDarkness) {
113       maxDarkness = darkness;
114       *aDarkColor = stop;
115     }
116   }
117 
118   return true;
119 }
120 
GetUnicoBorderGradientColors(GtkStyleContext * aContext,GdkRGBA * aLightColor,GdkRGBA * aDarkColor)121 static bool GetUnicoBorderGradientColors(GtkStyleContext* aContext,
122                                          GdkRGBA* aLightColor,
123                                          GdkRGBA* aDarkColor) {
124   // Ubuntu 12.04 has GTK engine Unico-1.0.2, which overrides render_frame,
125   // providing its own border code.  Ubuntu 14.04 has
126   // Unico-1.0.3+14.04.20140109, which does not override render_frame, and
127   // so does not need special attention.  The earlier Unico can be detected
128   // by the -unico-border-gradient style property it registers.
129   // gtk_style_properties_lookup_property() is checked first to avoid the
130   // warning from gtk_style_context_get_property() when the property does
131   // not exist.  (gtk_render_frame() of GTK+ 3.16 no longer uses the
132   // engine.)
133   const char* propertyName = "-unico-border-gradient";
134   if (!gtk_style_properties_lookup_property(propertyName, nullptr, nullptr))
135     return false;
136 
137   // -unico-border-gradient is used only when the CSS node's engine is Unico.
138   GtkThemingEngine* engine;
139   GtkStateFlags state = gtk_style_context_get_state(aContext);
140   gtk_style_context_get(aContext, state, "engine", &engine, nullptr);
141   if (strcmp(g_type_name(G_TYPE_FROM_INSTANCE(engine)), "UnicoEngine") != 0)
142     return false;
143 
144   // draw_border() of Unico engine uses -unico-border-gradient
145   // in preference to border-color.
146   GValue value = G_VALUE_INIT;
147   gtk_style_context_get_property(aContext, propertyName, state, &value);
148 
149   bool result = GetGradientColors(&value, aLightColor, aDarkColor);
150 
151   g_value_unset(&value);
152   return result;
153 }
154 
155 // Sets |aLightColor| and |aDarkColor| to colors from |aContext|.  Returns
156 // true if |aContext| uses these colors to render a visible border.
157 // If returning false, then the colors returned are a fallback from the
158 // border-color value even though |aContext| does not use these colors to
159 // render a border.
GetBorderColors(GtkStyleContext * aContext,GdkRGBA * aLightColor,GdkRGBA * aDarkColor)160 static bool GetBorderColors(GtkStyleContext* aContext, GdkRGBA* aLightColor,
161                             GdkRGBA* aDarkColor) {
162   // Determine whether the border on this style context is visible.
163   GtkStateFlags state = gtk_style_context_get_state(aContext);
164   GtkBorderStyle borderStyle;
165   gtk_style_context_get(aContext, state, GTK_STYLE_PROPERTY_BORDER_STYLE,
166                         &borderStyle, nullptr);
167   bool visible = borderStyle != GTK_BORDER_STYLE_NONE &&
168                  borderStyle != GTK_BORDER_STYLE_HIDDEN;
169   if (visible) {
170     // GTK has an initial value of zero for border-widths, and so themes
171     // need to explicitly set border-widths to make borders visible.
172     GtkBorder border;
173     gtk_style_context_get_border(aContext, GTK_STATE_FLAG_NORMAL, &border);
174     visible = border.top != 0 || border.right != 0 || border.bottom != 0 ||
175               border.left != 0;
176   }
177 
178   if (visible &&
179       GetUnicoBorderGradientColors(aContext, aLightColor, aDarkColor))
180     return true;
181 
182   // The initial value for the border-color is the foreground color, and so
183   // this will usually return a color distinct from the background even if
184   // there is no visible border detected.
185   gtk_style_context_get_border_color(aContext, state, aDarkColor);
186   // TODO GTK3 - update aLightColor
187   // for GTK_BORDER_STYLE_INSET/OUTSET/GROVE/RIDGE border styles.
188   // https://bugzilla.mozilla.org/show_bug.cgi?id=978172#c25
189   *aLightColor = *aDarkColor;
190   return visible;
191 }
192 
GetBorderColors(GtkStyleContext * aContext,nscolor * aLightColor,nscolor * aDarkColor)193 static bool GetBorderColors(GtkStyleContext* aContext, nscolor* aLightColor,
194                             nscolor* aDarkColor) {
195   GdkRGBA lightColor, darkColor;
196   bool ret = GetBorderColors(aContext, &lightColor, &darkColor);
197   *aLightColor = GDK_RGBA_TO_NS_RGBA(lightColor);
198   *aDarkColor = GDK_RGBA_TO_NS_RGBA(darkColor);
199   return ret;
200 }
201 
NativeInit()202 void nsLookAndFeel::NativeInit() { EnsureInit(); }
203 
RefreshImpl()204 void nsLookAndFeel::RefreshImpl() {
205   nsXPLookAndFeel::RefreshImpl();
206   moz_gtk_refresh();
207 
208   mDefaultFontCached = false;
209   mButtonFontCached = false;
210   mFieldFontCached = false;
211   mMenuFontCached = false;
212 
213   mInitialized = false;
214 }
215 
NativeGetColor(ColorID aID,nscolor & aColor)216 nsresult nsLookAndFeel::NativeGetColor(ColorID aID, nscolor& aColor) {
217   EnsureInit();
218 
219   nsresult res = NS_OK;
220 
221   switch (aID) {
222       // These colors don't seem to be used for anything anymore in Mozilla
223       // (except here at least TextSelectBackground and TextSelectForeground)
224       // The CSS2 colors below are used.
225     case eColorID_WindowBackground:
226     case eColorID_WidgetBackground:
227     case eColorID_TextBackground:
228     case eColorID_activecaption:  // active window caption background
229     case eColorID_appworkspace:   // MDI background color
230     case eColorID_background:     // desktop background
231     case eColorID_window:
232     case eColorID_windowframe:
233     case eColorID__moz_dialog:
234     case eColorID__moz_combobox:
235       aColor = mMozWindowBackground;
236       break;
237     case eColorID_WindowForeground:
238     case eColorID_WidgetForeground:
239     case eColorID_TextForeground:
240     case eColorID_captiontext:  // text in active window caption, size box, and
241                                 // scrollbar arrow box (!)
242     case eColorID_windowtext:
243     case eColorID__moz_dialogtext:
244       aColor = mMozWindowText;
245       break;
246     case eColorID_WidgetSelectBackground:
247     case eColorID_TextSelectBackground:
248     case eColorID_IMESelectedRawTextBackground:
249     case eColorID_IMESelectedConvertedTextBackground:
250     case eColorID__moz_dragtargetzone:
251     case eColorID__moz_cellhighlight:
252     case eColorID__moz_html_cellhighlight:
253     case eColorID_highlight:  // preference selected item,
254       aColor = mTextSelectedBackground;
255       break;
256     case eColorID_WidgetSelectForeground:
257     case eColorID_TextSelectForeground:
258     case eColorID_IMESelectedRawTextForeground:
259     case eColorID_IMESelectedConvertedTextForeground:
260     case eColorID_highlighttext:
261     case eColorID__moz_cellhighlighttext:
262     case eColorID__moz_html_cellhighlighttext:
263       aColor = mTextSelectedText;
264       break;
265     case eColorID_Widget3DHighlight:
266       aColor = NS_RGB(0xa0, 0xa0, 0xa0);
267       break;
268     case eColorID_Widget3DShadow:
269       aColor = NS_RGB(0x40, 0x40, 0x40);
270       break;
271     case eColorID_IMERawInputBackground:
272     case eColorID_IMEConvertedTextBackground:
273       aColor = NS_TRANSPARENT;
274       break;
275     case eColorID_IMERawInputForeground:
276     case eColorID_IMEConvertedTextForeground:
277       aColor = NS_SAME_AS_FOREGROUND_COLOR;
278       break;
279     case eColorID_IMERawInputUnderline:
280     case eColorID_IMEConvertedTextUnderline:
281       aColor = NS_SAME_AS_FOREGROUND_COLOR;
282       break;
283     case eColorID_IMESelectedRawTextUnderline:
284     case eColorID_IMESelectedConvertedTextUnderline:
285       aColor = NS_TRANSPARENT;
286       break;
287     case eColorID_SpellCheckerUnderline:
288       aColor = NS_RGB(0xff, 0, 0);
289       break;
290 
291       // css2  http://www.w3.org/TR/REC-CSS2/ui.html#system-colors
292     case eColorID_activeborder:
293       // active window border
294       aColor = mMozWindowActiveBorder;
295       break;
296     case eColorID_inactiveborder:
297       // inactive window border
298       aColor = mMozWindowInactiveBorder;
299       break;
300     case eColorID_graytext:             // disabled text in windows, menus, etc.
301     case eColorID_inactivecaptiontext:  // text in inactive window caption
302       aColor = mMenuTextInactive;
303       break;
304     case eColorID_inactivecaption:
305       // inactive window caption
306       aColor = mMozWindowInactiveCaption;
307       break;
308     case eColorID_infobackground:
309       // tooltip background color
310       aColor = mInfoBackground;
311       break;
312     case eColorID_infotext:
313       // tooltip text color
314       aColor = mInfoText;
315       break;
316     case eColorID_menu:
317       // menu background
318       aColor = mMenuBackground;
319       break;
320     case eColorID_menutext:
321       // menu text
322       aColor = mMenuText;
323       break;
324     case eColorID_scrollbar:
325       // scrollbar gray area
326       aColor = mMozScrollbar;
327       break;
328 
329     case eColorID_threedlightshadow:
330       // 3-D highlighted inner edge color
331       // always same as background in GTK code
332     case eColorID_threedface:
333     case eColorID_buttonface:
334       // 3-D face color
335       aColor = mMozWindowBackground;
336       break;
337 
338     case eColorID_buttontext:
339       // text on push buttons
340       aColor = mButtonText;
341       break;
342 
343     case eColorID_buttonhighlight:
344       // 3-D highlighted edge color
345     case eColorID_threedhighlight:
346       // 3-D highlighted outer edge color
347       aColor = mFrameOuterLightBorder;
348       break;
349 
350     case eColorID_buttonshadow:
351       // 3-D shadow edge color
352     case eColorID_threedshadow:
353       // 3-D shadow inner edge color
354       aColor = mFrameInnerDarkBorder;
355       break;
356 
357     case eColorID_threeddarkshadow:
358       // Hardcode to black
359       aColor = NS_RGB(0x00, 0x00, 0x00);
360       break;
361 
362     case eColorID__moz_eventreerow:
363     case eColorID__moz_field:
364       aColor = mMozFieldBackground;
365       break;
366     case eColorID__moz_fieldtext:
367       aColor = mMozFieldText;
368       break;
369     case eColorID__moz_buttondefault:
370       // default button border color
371       aColor = mButtonDefault;
372       break;
373     case eColorID__moz_buttonhoverface:
374       aColor = mButtonHoverFace;
375       break;
376     case eColorID__moz_buttonhovertext:
377       aColor = mButtonHoverText;
378       break;
379     case eColorID__moz_menuhover:
380       aColor = mMenuHover;
381       break;
382     case eColorID__moz_menuhovertext:
383       aColor = mMenuHoverText;
384       break;
385     case eColorID__moz_oddtreerow:
386       aColor = mOddCellBackground;
387       break;
388     case eColorID__moz_nativehyperlinktext:
389       aColor = mNativeHyperLinkText;
390       break;
391     case eColorID__moz_comboboxtext:
392       aColor = mComboBoxText;
393       break;
394     case eColorID__moz_menubartext:
395       aColor = mMenuBarText;
396       break;
397     case eColorID__moz_menubarhovertext:
398       aColor = mMenuBarHoverText;
399       break;
400     case eColorID__moz_gtk_info_bar_text:
401       aColor = mInfoBarText;
402       break;
403     default:
404       /* default color is BLACK */
405       aColor = 0;
406       res = NS_ERROR_FAILURE;
407       break;
408   }
409 
410   return res;
411 }
412 
CheckWidgetStyle(GtkWidget * aWidget,const char * aStyle,int32_t aResult)413 static int32_t CheckWidgetStyle(GtkWidget* aWidget, const char* aStyle,
414                                 int32_t aResult) {
415   gboolean value = FALSE;
416   gtk_widget_style_get(aWidget, aStyle, &value, nullptr);
417   return value ? aResult : 0;
418 }
419 
ConvertGTKStepperStyleToMozillaScrollArrowStyle(GtkWidget * aWidget)420 static int32_t ConvertGTKStepperStyleToMozillaScrollArrowStyle(
421     GtkWidget* aWidget) {
422   if (!aWidget) return mozilla::LookAndFeel::eScrollArrowStyle_Single;
423 
424   return CheckWidgetStyle(aWidget, "has-backward-stepper",
425                           mozilla::LookAndFeel::eScrollArrow_StartBackward) |
426          CheckWidgetStyle(aWidget, "has-forward-stepper",
427                           mozilla::LookAndFeel::eScrollArrow_EndForward) |
428          CheckWidgetStyle(aWidget, "has-secondary-backward-stepper",
429                           mozilla::LookAndFeel::eScrollArrow_EndBackward) |
430          CheckWidgetStyle(aWidget, "has-secondary-forward-stepper",
431                           mozilla::LookAndFeel::eScrollArrow_StartForward);
432 }
433 
GetIntImpl(IntID aID,int32_t & aResult)434 nsresult nsLookAndFeel::GetIntImpl(IntID aID, int32_t& aResult) {
435   nsresult res = NS_OK;
436 
437   // Set these before they can get overrided in the nsXPLookAndFeel.
438   switch (aID) {
439     case eIntID_ScrollButtonLeftMouseButtonAction:
440       aResult = 0;
441       return NS_OK;
442     case eIntID_ScrollButtonMiddleMouseButtonAction:
443       aResult = 1;
444       return NS_OK;
445     case eIntID_ScrollButtonRightMouseButtonAction:
446       aResult = 2;
447       return NS_OK;
448     default:
449       break;
450   }
451 
452   res = nsXPLookAndFeel::GetIntImpl(aID, aResult);
453   if (NS_SUCCEEDED(res)) return res;
454   res = NS_OK;
455 
456   // We use delayed initialization by EnsureInit() here
457   // to make sure mozilla::Preferences is available (Bug 115807).
458   // eIntID_UseAccessibilityTheme is requested before user preferences
459   // are read, and so EnsureInit(), which depends on preference values,
460   // is deliberately delayed until required.
461   switch (aID) {
462     case eIntID_CaretBlinkTime: {
463       GtkSettings* settings;
464       gint blink_time;
465       gboolean blink;
466 
467       settings = gtk_settings_get_default();
468       g_object_get(settings, "gtk-cursor-blink-time", &blink_time,
469                    "gtk-cursor-blink", &blink, nullptr);
470 
471       if (blink)
472         aResult = (int32_t)blink_time;
473       else
474         aResult = 0;
475       break;
476     }
477     case eIntID_CaretWidth:
478       aResult = 1;
479       break;
480     case eIntID_ShowCaretDuringSelection:
481       aResult = 0;
482       break;
483     case eIntID_SelectTextfieldsOnKeyFocus: {
484       GtkWidget* entry;
485       GtkSettings* settings;
486       gboolean select_on_focus;
487 
488       entry = gtk_entry_new();
489       g_object_ref_sink(entry);
490       settings = gtk_widget_get_settings(entry);
491       g_object_get(settings, "gtk-entry-select-on-focus", &select_on_focus,
492                    nullptr);
493 
494       if (select_on_focus)
495         aResult = 1;
496       else
497         aResult = 0;
498 
499       gtk_widget_destroy(entry);
500       g_object_unref(entry);
501     } break;
502     case eIntID_ScrollToClick: {
503       GtkSettings* settings;
504       gboolean warps_slider = FALSE;
505 
506       settings = gtk_settings_get_default();
507       if (g_object_class_find_property(G_OBJECT_GET_CLASS(settings),
508                                        "gtk-primary-button-warps-slider")) {
509         g_object_get(settings, "gtk-primary-button-warps-slider", &warps_slider,
510                      nullptr);
511       }
512 
513       if (warps_slider)
514         aResult = 1;
515       else
516         aResult = 0;
517     } break;
518     case eIntID_SubmenuDelay: {
519       GtkSettings* settings;
520       gint delay;
521 
522       settings = gtk_settings_get_default();
523       g_object_get(settings, "gtk-menu-popup-delay", &delay, nullptr);
524       aResult = (int32_t)delay;
525       break;
526     }
527     case eIntID_TooltipDelay: {
528       aResult = 500;
529       break;
530     }
531     case eIntID_MenusCanOverlapOSBar:
532       // we want XUL popups to be able to overlap the task bar.
533       aResult = 1;
534       break;
535     case eIntID_SkipNavigatingDisabledMenuItem:
536       aResult = 1;
537       break;
538     case eIntID_DragThresholdX:
539     case eIntID_DragThresholdY: {
540       GtkWidget* box = gtk_hbox_new(FALSE, 5);
541       gint threshold = 0;
542       g_object_get(gtk_widget_get_settings(box), "gtk-dnd-drag-threshold",
543                    &threshold, nullptr);
544       g_object_ref_sink(box);
545 
546       aResult = threshold;
547     } break;
548     case eIntID_ScrollArrowStyle: {
549       GtkWidget* scrollbar = GetWidget(MOZ_GTK_SCROLLBAR_HORIZONTAL);
550       aResult = ConvertGTKStepperStyleToMozillaScrollArrowStyle(scrollbar);
551       break;
552     }
553     case eIntID_ScrollSliderStyle:
554       aResult = eScrollThumbStyle_Proportional;
555       break;
556     case eIntID_TreeOpenDelay:
557       aResult = 1000;
558       break;
559     case eIntID_TreeCloseDelay:
560       aResult = 1000;
561       break;
562     case eIntID_TreeLazyScrollDelay:
563       aResult = 150;
564       break;
565     case eIntID_TreeScrollDelay:
566       aResult = 100;
567       break;
568     case eIntID_TreeScrollLinesMax:
569       aResult = 3;
570       break;
571     case eIntID_DWMCompositor:
572     case eIntID_WindowsClassic:
573     case eIntID_WindowsDefaultTheme:
574     case eIntID_WindowsThemeIdentifier:
575     case eIntID_OperatingSystemVersionIdentifier:
576       aResult = 0;
577       res = NS_ERROR_NOT_IMPLEMENTED;
578       break;
579     case eIntID_TouchEnabled:
580       aResult = mozilla::widget::WidgetUtils::IsTouchDeviceSupportPresent();
581       break;
582     case eIntID_MacGraphiteTheme:
583       aResult = 0;
584       res = NS_ERROR_NOT_IMPLEMENTED;
585       break;
586     case eIntID_AlertNotificationOrigin:
587       aResult = NS_ALERT_TOP;
588       break;
589     case eIntID_IMERawInputUnderlineStyle:
590     case eIntID_IMEConvertedTextUnderlineStyle:
591       aResult = NS_STYLE_TEXT_DECORATION_STYLE_SOLID;
592       break;
593     case eIntID_IMESelectedRawTextUnderlineStyle:
594     case eIntID_IMESelectedConvertedTextUnderline:
595       aResult = NS_STYLE_TEXT_DECORATION_STYLE_NONE;
596       break;
597     case eIntID_SpellCheckerUnderlineStyle:
598       aResult = NS_STYLE_TEXT_DECORATION_STYLE_WAVY;
599       break;
600     case eIntID_MenuBarDrag:
601       EnsureInit();
602       aResult = mMenuSupportsDrag;
603       break;
604     case eIntID_ScrollbarButtonAutoRepeatBehavior:
605       aResult = 1;
606       break;
607     case eIntID_SwipeAnimationEnabled:
608       aResult = 0;
609       break;
610     case eIntID_ContextMenuOffsetVertical:
611     case eIntID_ContextMenuOffsetHorizontal:
612       aResult = 2;
613       break;
614     case eIntID_GTKCSDAvailable:
615       EnsureInit();
616       aResult = mCSDAvailable;
617       break;
618     case eIntID_GTKCSDMaximizeButton:
619       EnsureInit();
620       aResult = mCSDMaximizeButton;
621       break;
622     case eIntID_GTKCSDMinimizeButton:
623       EnsureInit();
624       aResult = mCSDMinimizeButton;
625       break;
626     case eIntID_GTKCSDCloseButton:
627       EnsureInit();
628       aResult = mCSDCloseButton;
629       break;
630     default:
631       aResult = 0;
632       res = NS_ERROR_FAILURE;
633   }
634 
635   return res;
636 }
637 
GetFloatImpl(FloatID aID,float & aResult)638 nsresult nsLookAndFeel::GetFloatImpl(FloatID aID, float& aResult) {
639   nsresult res = NS_OK;
640   res = nsXPLookAndFeel::GetFloatImpl(aID, aResult);
641   if (NS_SUCCEEDED(res)) return res;
642   res = NS_OK;
643 
644   switch (aID) {
645     case eFloatID_IMEUnderlineRelativeSize:
646       aResult = 1.0f;
647       break;
648     case eFloatID_SpellCheckerUnderlineRelativeSize:
649       aResult = 1.0f;
650       break;
651     case eFloatID_CaretAspectRatio:
652       EnsureInit();
653       aResult = mCaretRatio;
654       break;
655     default:
656       aResult = -1.0;
657       res = NS_ERROR_FAILURE;
658   }
659   return res;
660 }
661 
GetSystemFontInfo(GtkStyleContext * aStyle,nsString * aFontName,gfxFontStyle * aFontStyle)662 static void GetSystemFontInfo(GtkStyleContext* aStyle, nsString* aFontName,
663                               gfxFontStyle* aFontStyle) {
664   aFontStyle->style = NS_FONT_STYLE_NORMAL;
665 
666   // As in
667   // https://git.gnome.org/browse/gtk+/tree/gtk/gtkwidget.c?h=3.22.19#n10333
668   PangoFontDescription* desc;
669   gtk_style_context_get(aStyle, gtk_style_context_get_state(aStyle), "font",
670                         &desc, nullptr);
671 
672   aFontStyle->systemFont = true;
673 
674   NS_NAMED_LITERAL_STRING(quote, "\"");
675   NS_ConvertUTF8toUTF16 family(pango_font_description_get_family(desc));
676   *aFontName = quote + family + quote;
677 
678   aFontStyle->weight = pango_font_description_get_weight(desc);
679 
680   // FIXME: Set aFontStyle->stretch correctly!
681   aFontStyle->stretch = NS_FONT_STRETCH_NORMAL;
682 
683   float size = float(pango_font_description_get_size(desc)) / PANGO_SCALE;
684 
685   // |size| is now either pixels or pango-points (not Mozilla-points!)
686 
687   if (!pango_font_description_get_size_is_absolute(desc)) {
688     // |size| is in pango-points, so convert to pixels.
689     size *= float(gfxPlatformGtk::GetFontScaleDPI()) / POINTS_PER_INCH_FLOAT;
690   }
691   // |size| is now pixels but not scaled for the hidpi displays,
692   // this needs to be done in GetFontImpl where the aDevPixPerCSSPixel
693   // parameter is provided.
694 
695   aFontStyle->size = size;
696 
697   pango_font_description_free(desc);
698 }
699 
GetFontImpl(FontID aID,nsString & aFontName,gfxFontStyle & aFontStyle,float aDevPixPerCSSPixel)700 bool nsLookAndFeel::GetFontImpl(FontID aID, nsString& aFontName,
701                                 gfxFontStyle& aFontStyle,
702                                 float aDevPixPerCSSPixel) {
703   switch (aID) {
704     case eFont_Menu:          // css2
705     case eFont_PullDownMenu:  // css3
706       aFontName = mMenuFontName;
707       aFontStyle = mMenuFontStyle;
708       break;
709 
710     case eFont_Field:  // css3
711     case eFont_List:   // css3
712       aFontName = mFieldFontName;
713       aFontStyle = mFieldFontStyle;
714       break;
715 
716     case eFont_Button:  // css3
717       aFontName = mButtonFontName;
718       aFontStyle = mButtonFontStyle;
719       break;
720 
721     case eFont_Caption:       // css2
722     case eFont_Icon:          // css2
723     case eFont_MessageBox:    // css2
724     case eFont_SmallCaption:  // css2
725     case eFont_StatusBar:     // css2
726     case eFont_Window:        // css3
727     case eFont_Document:      // css3
728     case eFont_Workspace:     // css3
729     case eFont_Desktop:       // css3
730     case eFont_Info:          // css3
731     case eFont_Dialog:        // css3
732     case eFont_Tooltips:      // moz
733     case eFont_Widget:        // moz
734     default:
735       aFontName = mDefaultFontName;
736       aFontStyle = mDefaultFontStyle;
737       break;
738   }
739   // Scale the font for the current monitor
740   double scaleFactor = nsIWidget::DefaultScaleOverride();
741   if (scaleFactor > 0) {
742     aFontStyle.size *=
743         mozilla::widget::ScreenHelperGTK::GetGTKMonitorScaleFactor();
744   } else {
745     // Remove effect of font scale because it has been already applied in
746     // GetSystemFontInfo
747     aFontStyle.size *=
748         aDevPixPerCSSPixel / gfxPlatformGtk::GetFontScaleFactor();
749   }
750   return true;
751 }
752 
EnsureInit()753 void nsLookAndFeel::EnsureInit() {
754   GdkColor colorValue;
755   GdkColor* colorValuePtr;
756 
757   if (mInitialized) return;
758   mInitialized = true;
759 
760   // gtk does non threadsafe refcounting
761   MOZ_ASSERT(NS_IsMainThread());
762 
763   GdkRGBA color;
764   GtkStyleContext* style;
765 
766   // Gtk manages a screen's CSS in the settings object so we
767   // ask Gtk to create it explicitly. Otherwise we may end up
768   // with wrong color theme, see Bug 972382
769   GtkSettings* settings = gtk_settings_get_for_screen(gdk_screen_get_default());
770 
771   // Dark themes interacts poorly with widget styling (see bug 1216658).
772   // We disable dark themes by default for all processes (chrome, web content)
773   // but allow user to overide it by prefs.
774   const gchar* dark_setting = "gtk-application-prefer-dark-theme";
775   gboolean darkThemeDefault;
776   g_object_get(settings, dark_setting, &darkThemeDefault, nullptr);
777 
778   // To avoid triggering reload of theme settings unnecessarily, only set the
779   // setting when necessary.
780   if (darkThemeDefault) {
781     bool allowDarkTheme;
782     if (XRE_IsContentProcess()) {
783       allowDarkTheme = mozilla::Preferences::GetBool(
784           "widget.content.allow-gtk-dark-theme", false);
785     } else {
786       allowDarkTheme = (PR_GetEnv("MOZ_ALLOW_GTK_DARK_THEME") != nullptr) ||
787                        mozilla::Preferences::GetBool(
788                            "widget.chrome.allow-gtk-dark-theme", false);
789     }
790     if (!allowDarkTheme) {
791       g_object_set(settings, dark_setting, FALSE, nullptr);
792     }
793   }
794 
795   // Allow content Gtk theme override by pref, it's useful when styled Gtk+
796   // widgets break web content.
797   if (XRE_IsContentProcess()) {
798     nsAutoCString contentThemeName;
799     mozilla::Preferences::GetCString("widget.content.gtk-theme-override",
800                                      contentThemeName);
801     if (!contentThemeName.IsEmpty()) {
802       g_object_set(settings, "gtk-theme-name", contentThemeName.get(), nullptr);
803     }
804   }
805 
806   // The label is not added to a parent widget, but shared for constructing
807   // different style contexts.  The node hierarchy is constructed only on
808   // the label style context.
809   GtkWidget* labelWidget = gtk_label_new("M");
810   g_object_ref_sink(labelWidget);
811 
812   // Scrollbar colors
813   style = GetStyleContext(MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL);
814   gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color);
815   mMozScrollbar = GDK_RGBA_TO_NS_RGBA(color);
816 
817   // Window colors
818   style = GetStyleContext(MOZ_GTK_WINDOW);
819   gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color);
820   mMozWindowBackground = GDK_RGBA_TO_NS_RGBA(color);
821   gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
822   mMozWindowText = GDK_RGBA_TO_NS_RGBA(color);
823   gtk_style_context_get_border_color(style, GTK_STATE_FLAG_NORMAL, &color);
824   mMozWindowActiveBorder = GDK_RGBA_TO_NS_RGBA(color);
825   gtk_style_context_get_border_color(style, GTK_STATE_FLAG_INSENSITIVE, &color);
826   mMozWindowInactiveBorder = GDK_RGBA_TO_NS_RGBA(color);
827   gtk_style_context_get_background_color(style, GTK_STATE_FLAG_INSENSITIVE,
828                                          &color);
829   mMozWindowInactiveCaption = GDK_RGBA_TO_NS_RGBA(color);
830 
831   style = GetStyleContext(MOZ_GTK_WINDOW_CONTAINER);
832   {
833     GtkStyleContext* labelStyle = CreateStyleForWidget(labelWidget, style);
834     GetSystemFontInfo(labelStyle, &mDefaultFontName, &mDefaultFontStyle);
835     g_object_unref(labelStyle);
836   }
837 
838   // tooltip foreground and background
839   style = GetStyleContext(MOZ_GTK_TOOLTIP);
840   gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color);
841   mInfoBackground = GDK_RGBA_TO_NS_RGBA(color);
842 
843   style = GetStyleContext(MOZ_GTK_TOOLTIP_BOX_LABEL);
844   gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
845   mInfoText = GDK_RGBA_TO_NS_RGBA(color);
846 
847   style = GetStyleContext(MOZ_GTK_MENUITEM);
848   {
849     GtkStyleContext* accelStyle =
850         CreateStyleForWidget(gtk_accel_label_new("M"), style);
851 
852     GetSystemFontInfo(accelStyle, &mMenuFontName, &mMenuFontStyle);
853 
854     gtk_style_context_get_color(accelStyle, GTK_STATE_FLAG_NORMAL, &color);
855     mMenuText = GDK_RGBA_TO_NS_RGBA(color);
856     gtk_style_context_get_color(accelStyle, GTK_STATE_FLAG_INSENSITIVE, &color);
857     mMenuTextInactive = GDK_RGBA_TO_NS_RGBA(color);
858     g_object_unref(accelStyle);
859   }
860 
861   style = GetStyleContext(MOZ_GTK_MENUPOPUP);
862   gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color);
863   mMenuBackground = GDK_RGBA_TO_NS_RGBA(color);
864 
865   style = GetStyleContext(MOZ_GTK_MENUITEM);
866   gtk_style_context_get_background_color(style, GTK_STATE_FLAG_PRELIGHT,
867                                          &color);
868   mMenuHover = GDK_RGBA_TO_NS_RGBA(color);
869   gtk_style_context_get_color(style, GTK_STATE_FLAG_PRELIGHT, &color);
870   mMenuHoverText = GDK_RGBA_TO_NS_RGBA(color);
871 
872   GtkWidget* parent = gtk_fixed_new();
873   GtkWidget* window = gtk_window_new(GTK_WINDOW_POPUP);
874   GtkWidget* treeView = gtk_tree_view_new();
875   GtkWidget* linkButton = gtk_link_button_new("http://example.com/");
876   GtkWidget* menuBar = gtk_menu_bar_new();
877   GtkWidget* menuBarItem = gtk_menu_item_new();
878   GtkWidget* entry = gtk_entry_new();
879   GtkWidget* textView = gtk_text_view_new();
880 
881   gtk_container_add(GTK_CONTAINER(parent), treeView);
882   gtk_container_add(GTK_CONTAINER(parent), linkButton);
883   gtk_container_add(GTK_CONTAINER(parent), menuBar);
884   gtk_menu_shell_append(GTK_MENU_SHELL(menuBar), menuBarItem);
885   gtk_container_add(GTK_CONTAINER(window), parent);
886   gtk_container_add(GTK_CONTAINER(parent), entry);
887   gtk_container_add(GTK_CONTAINER(parent), textView);
888 
889   // Text colors
890   GdkRGBA bgColor;
891   // If the text window background is translucent, then the background of
892   // the textview root node is visible.
893   style = GetStyleContext(MOZ_GTK_TEXT_VIEW);
894   gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL,
895                                          &bgColor);
896 
897   style = GetStyleContext(MOZ_GTK_TEXT_VIEW_TEXT);
898   gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color);
899   ApplyColorOver(color, &bgColor);
900   mMozFieldBackground = GDK_RGBA_TO_NS_RGBA(bgColor);
901   gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
902   mMozFieldText = GDK_RGBA_TO_NS_RGBA(color);
903 
904   // Selected text and background
905   gtk_style_context_get_background_color(
906       style,
907       static_cast<GtkStateFlags>(GTK_STATE_FLAG_FOCUSED |
908                                  GTK_STATE_FLAG_SELECTED),
909       &color);
910   mTextSelectedBackground = GDK_RGBA_TO_NS_RGBA(color);
911   gtk_style_context_get_color(
912       style,
913       static_cast<GtkStateFlags>(GTK_STATE_FLAG_FOCUSED |
914                                  GTK_STATE_FLAG_SELECTED),
915       &color);
916   mTextSelectedText = GDK_RGBA_TO_NS_RGBA(color);
917 
918   // Button text color
919   style = GetStyleContext(MOZ_GTK_BUTTON);
920   {
921     GtkStyleContext* labelStyle = CreateStyleForWidget(labelWidget, style);
922 
923     GetSystemFontInfo(labelStyle, &mButtonFontName, &mButtonFontStyle);
924 
925     gtk_style_context_get_border_color(style, GTK_STATE_FLAG_NORMAL, &color);
926     mButtonDefault = GDK_RGBA_TO_NS_RGBA(color);
927     gtk_style_context_get_color(labelStyle, GTK_STATE_FLAG_NORMAL, &color);
928     mButtonText = GDK_RGBA_TO_NS_RGBA(color);
929     gtk_style_context_get_color(labelStyle, GTK_STATE_FLAG_PRELIGHT, &color);
930     mButtonHoverText = GDK_RGBA_TO_NS_RGBA(color);
931     gtk_style_context_get_background_color(style, GTK_STATE_FLAG_PRELIGHT,
932                                            &color);
933     mButtonHoverFace = GDK_RGBA_TO_NS_RGBA(color);
934     g_object_unref(labelStyle);
935   }
936 
937   // Combobox text color
938   style = GetStyleContext(MOZ_GTK_COMBOBOX_ENTRY_TEXTAREA);
939   gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
940   mComboBoxText = GDK_RGBA_TO_NS_RGBA(color);
941 
942   // Menubar text and hover text colors
943   style = GetStyleContext(MOZ_GTK_MENUBARITEM);
944   gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
945   mMenuBarText = GDK_RGBA_TO_NS_RGBA(color);
946   gtk_style_context_get_color(style, GTK_STATE_FLAG_PRELIGHT, &color);
947   mMenuBarHoverText = GDK_RGBA_TO_NS_RGBA(color);
948 
949   // GTK's guide to fancy odd row background colors:
950   // 1) Check if a theme explicitly defines an odd row color
951   // 2) If not, check if it defines an even row color, and darken it
952   //    slightly by a hardcoded value (gtkstyle.c)
953   // 3) If neither are defined, take the base background color and
954   //    darken that by a hardcoded value
955   style = GetStyleContext(MOZ_GTK_TREEVIEW);
956 
957   // Get odd row background color
958   gtk_style_context_save(style);
959   gtk_style_context_add_region(style, GTK_STYLE_REGION_ROW, GTK_REGION_ODD);
960   gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color);
961   mOddCellBackground = GDK_RGBA_TO_NS_RGBA(color);
962   gtk_style_context_restore(style);
963 
964   // GtkFrame has a "border" subnode on which Adwaita draws the border.
965   // Some themes do not draw on this node but draw a border on the widget
966   // root node, so check the root node if no border is found on the border
967   // node.
968   style = GetStyleContext(MOZ_GTK_FRAME_BORDER);
969   bool themeUsesColors =
970       GetBorderColors(style, &mFrameOuterLightBorder, &mFrameInnerDarkBorder);
971   if (!themeUsesColors) {
972     style = GetStyleContext(MOZ_GTK_FRAME);
973     GetBorderColors(style, &mFrameOuterLightBorder, &mFrameInnerDarkBorder);
974   }
975 
976   // GtkInfoBar
977   // TODO - Use WidgetCache for it?
978   GtkWidget* infoBar = gtk_info_bar_new();
979   GtkWidget* infoBarContent =
980       gtk_info_bar_get_content_area(GTK_INFO_BAR(infoBar));
981   GtkWidget* infoBarLabel = gtk_label_new(nullptr);
982   gtk_container_add(GTK_CONTAINER(parent), infoBar);
983   gtk_container_add(GTK_CONTAINER(infoBarContent), infoBarLabel);
984   style = gtk_widget_get_style_context(infoBarLabel);
985   gtk_style_context_add_class(style, GTK_STYLE_CLASS_INFO);
986   gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
987   mInfoBarText = GDK_RGBA_TO_NS_RGBA(color);
988   // Some themes have a unified menu bar, and support window dragging on it
989   gboolean supports_menubar_drag = FALSE;
990   GParamSpec* param_spec = gtk_widget_class_find_style_property(
991       GTK_WIDGET_GET_CLASS(menuBar), "window-dragging");
992   if (param_spec) {
993     if (g_type_is_a(G_PARAM_SPEC_VALUE_TYPE(param_spec), G_TYPE_BOOLEAN)) {
994       gtk_widget_style_get(menuBar, "window-dragging", &supports_menubar_drag,
995                            nullptr);
996     }
997   }
998   mMenuSupportsDrag = supports_menubar_drag;
999 
1000   if (gtk_check_version(3, 12, 0) == nullptr) {
1001     // TODO: It returns wrong color for themes which
1002     // sets link color for GtkLabel only as we query
1003     // GtkLinkButton style here.
1004     style = gtk_widget_get_style_context(linkButton);
1005     gtk_style_context_get_color(style, GTK_STATE_FLAG_LINK, &color);
1006     mNativeHyperLinkText = GDK_RGBA_TO_NS_RGBA(color);
1007   } else {
1008     colorValuePtr = nullptr;
1009     gtk_widget_style_get(linkButton, "link-color", &colorValuePtr, nullptr);
1010     if (colorValuePtr) {
1011       colorValue = *colorValuePtr;  // we can't pass deref pointers to
1012                                     // GDK_COLOR_TO_NS_RGB
1013       mNativeHyperLinkText = GDK_COLOR_TO_NS_RGB(colorValue);
1014       gdk_color_free(colorValuePtr);
1015     } else {
1016       mNativeHyperLinkText = NS_RGB(0x00, 0x00, 0xEE);
1017     }
1018   }
1019 
1020   // invisible character styles
1021   guint value;
1022   g_object_get(entry, "invisible-char", &value, nullptr);
1023   mInvisibleCharacter = char16_t(value);
1024 
1025   // caret styles
1026   gtk_widget_style_get(entry, "cursor-aspect-ratio", &mCaretRatio, nullptr);
1027 
1028   GetSystemFontInfo(gtk_widget_get_style_context(entry), &mFieldFontName,
1029                     &mFieldFontStyle);
1030 
1031   gtk_widget_destroy(window);
1032   g_object_unref(labelWidget);
1033 
1034   mCSDAvailable =
1035       nsWindow::GetSystemCSDSupportLevel() != nsWindow::CSD_SUPPORT_NONE;
1036 
1037   mCSDCloseButton = false;
1038   mCSDMinimizeButton = false;
1039   mCSDMaximizeButton = false;
1040 
1041   // We need to initialize whole CSD config explicitly because it's queried
1042   // as -moz-gtk* media features.
1043   WidgetNodeType buttonLayout[TOOLBAR_BUTTONS];
1044 
1045   int activeButtons =
1046       GetGtkHeaderBarButtonLayout(buttonLayout, TOOLBAR_BUTTONS);
1047   for (int i = 0; i < activeButtons; i++) {
1048     switch (buttonLayout[i]) {
1049       case MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE:
1050         mCSDMinimizeButton = true;
1051         break;
1052       case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE:
1053         mCSDMaximizeButton = true;
1054         break;
1055       case MOZ_GTK_HEADER_BAR_BUTTON_CLOSE:
1056         mCSDCloseButton = true;
1057         break;
1058       default:
1059         break;
1060     }
1061   }
1062 }
1063 
1064 // virtual
GetPasswordCharacterImpl()1065 char16_t nsLookAndFeel::GetPasswordCharacterImpl() {
1066   EnsureInit();
1067   return mInvisibleCharacter;
1068 }
1069 
GetEchoPasswordImpl()1070 bool nsLookAndFeel::GetEchoPasswordImpl() { return false; }
1071