1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include <dlfcn.h>
8 #include <gtk/gtk.h>
9 #include "WidgetStyleCache.h"
10 #include "gtkdrawing.h"
11 #include "mozilla/Assertions.h"
12 #include "mozilla/PodOperations.h"
13 #include "nsDebug.h"
14 #include "nsPrintfCString.h"
15 #include "nsString.h"
16 
17 #define STATE_FLAG_DIR_LTR (1U << 7)
18 #define STATE_FLAG_DIR_RTL (1U << 8)
19 static_assert(GTK_STATE_FLAG_DIR_LTR == STATE_FLAG_DIR_LTR &&
20                   GTK_STATE_FLAG_DIR_RTL == STATE_FLAG_DIR_RTL,
21               "incorrect direction state flags");
22 
23 static GtkWidget* sWidgetStorage[MOZ_GTK_WIDGET_NODE_COUNT];
24 static GtkStyleContext* sStyleStorage[MOZ_GTK_WIDGET_NODE_COUNT];
25 
26 static GtkStyleContext* GetWidgetRootStyle(WidgetNodeType aNodeType);
27 static GtkStyleContext* GetCssNodeStyleInternal(WidgetNodeType aNodeType);
28 
CreateWindowWidget()29 static GtkWidget* CreateWindowWidget() {
30   GtkWidget* widget = gtk_window_new(GTK_WINDOW_POPUP);
31   MOZ_RELEASE_ASSERT(widget, "We're missing GtkWindow widget!");
32   gtk_widget_set_name(widget, "MozillaGtkWidget");
33   return widget;
34 }
35 
CreateWindowContainerWidget()36 static GtkWidget* CreateWindowContainerWidget() {
37   GtkWidget* widget = gtk_fixed_new();
38   gtk_container_add(GTK_CONTAINER(GetWidget(MOZ_GTK_WINDOW)), widget);
39   return widget;
40 }
41 
AddToWindowContainer(GtkWidget * widget)42 static void AddToWindowContainer(GtkWidget* widget) {
43   gtk_container_add(GTK_CONTAINER(GetWidget(MOZ_GTK_WINDOW_CONTAINER)), widget);
44 }
45 
CreateScrollbarWidget(WidgetNodeType aAppearance,GtkOrientation aOrientation)46 static GtkWidget* CreateScrollbarWidget(WidgetNodeType aAppearance,
47                                         GtkOrientation aOrientation) {
48   GtkWidget* widget = gtk_scrollbar_new(aOrientation, nullptr);
49   AddToWindowContainer(widget);
50   return widget;
51 }
52 
CreateCheckboxWidget()53 static GtkWidget* CreateCheckboxWidget() {
54   GtkWidget* widget = gtk_check_button_new_with_label("M");
55   AddToWindowContainer(widget);
56   return widget;
57 }
58 
CreateRadiobuttonWidget()59 static GtkWidget* CreateRadiobuttonWidget() {
60   GtkWidget* widget = gtk_radio_button_new_with_label(nullptr, "M");
61   AddToWindowContainer(widget);
62   return widget;
63 }
64 
CreateMenuBarWidget()65 static GtkWidget* CreateMenuBarWidget() {
66   GtkWidget* widget = gtk_menu_bar_new();
67   AddToWindowContainer(widget);
68   return widget;
69 }
70 
CreateMenuPopupWidget()71 static GtkWidget* CreateMenuPopupWidget() {
72   GtkWidget* widget = gtk_menu_new();
73   gtk_menu_attach_to_widget(GTK_MENU(widget), GetWidget(MOZ_GTK_WINDOW),
74                             nullptr);
75   return widget;
76 }
77 
CreateProgressWidget()78 static GtkWidget* CreateProgressWidget() {
79   GtkWidget* widget = gtk_progress_bar_new();
80   AddToWindowContainer(widget);
81   return widget;
82 }
83 
CreateTooltipWidget()84 static GtkWidget* CreateTooltipWidget() {
85   MOZ_ASSERT(gtk_check_version(3, 20, 0) != nullptr,
86              "CreateTooltipWidget should be used for Gtk < 3.20 only.");
87   GtkWidget* widget = CreateWindowWidget();
88   GtkStyleContext* style = gtk_widget_get_style_context(widget);
89   gtk_style_context_add_class(style, GTK_STYLE_CLASS_TOOLTIP);
90   return widget;
91 }
92 
CreateExpanderWidget()93 static GtkWidget* CreateExpanderWidget() {
94   GtkWidget* widget = gtk_expander_new("M");
95   AddToWindowContainer(widget);
96   return widget;
97 }
98 
CreateFrameWidget()99 static GtkWidget* CreateFrameWidget() {
100   GtkWidget* widget = gtk_frame_new(nullptr);
101   AddToWindowContainer(widget);
102   return widget;
103 }
104 
CreateGripperWidget()105 static GtkWidget* CreateGripperWidget() {
106   GtkWidget* widget = gtk_handle_box_new();
107   AddToWindowContainer(widget);
108   return widget;
109 }
110 
CreateToolbarWidget()111 static GtkWidget* CreateToolbarWidget() {
112   GtkWidget* widget = gtk_toolbar_new();
113   gtk_container_add(GTK_CONTAINER(GetWidget(MOZ_GTK_GRIPPER)), widget);
114   return widget;
115 }
116 
CreateToolbarSeparatorWidget()117 static GtkWidget* CreateToolbarSeparatorWidget() {
118   GtkWidget* widget = GTK_WIDGET(gtk_separator_tool_item_new());
119   AddToWindowContainer(widget);
120   return widget;
121 }
122 
CreateButtonWidget()123 static GtkWidget* CreateButtonWidget() {
124   GtkWidget* widget = gtk_button_new_with_label("M");
125   AddToWindowContainer(widget);
126   return widget;
127 }
128 
CreateToggleButtonWidget()129 static GtkWidget* CreateToggleButtonWidget() {
130   GtkWidget* widget = gtk_toggle_button_new();
131   AddToWindowContainer(widget);
132   return widget;
133 }
134 
CreateButtonArrowWidget()135 static GtkWidget* CreateButtonArrowWidget() {
136   GtkWidget* widget = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_OUT);
137   gtk_container_add(GTK_CONTAINER(GetWidget(MOZ_GTK_TOGGLE_BUTTON)), widget);
138   gtk_widget_show(widget);
139   return widget;
140 }
141 
CreateSpinWidget()142 static GtkWidget* CreateSpinWidget() {
143   GtkWidget* widget = gtk_spin_button_new(nullptr, 1, 0);
144   AddToWindowContainer(widget);
145   return widget;
146 }
147 
CreateEntryWidget()148 static GtkWidget* CreateEntryWidget() {
149   GtkWidget* widget = gtk_entry_new();
150   AddToWindowContainer(widget);
151   return widget;
152 }
153 
CreateComboBoxWidget()154 static GtkWidget* CreateComboBoxWidget() {
155   GtkWidget* widget = gtk_combo_box_new();
156   AddToWindowContainer(widget);
157   return widget;
158 }
159 
160 typedef struct {
161   GType type;
162   GtkWidget** widget;
163 } GtkInnerWidgetInfo;
164 
GetInnerWidget(GtkWidget * widget,gpointer client_data)165 static void GetInnerWidget(GtkWidget* widget, gpointer client_data) {
166   auto info = static_cast<GtkInnerWidgetInfo*>(client_data);
167 
168   if (G_TYPE_CHECK_INSTANCE_TYPE(widget, info->type)) {
169     *info->widget = widget;
170   }
171 }
172 
CreateComboBoxButtonWidget()173 static GtkWidget* CreateComboBoxButtonWidget() {
174   GtkWidget* comboBox = GetWidget(MOZ_GTK_COMBOBOX);
175   GtkWidget* comboBoxButton = nullptr;
176 
177   /* Get its inner Button */
178   GtkInnerWidgetInfo info = {GTK_TYPE_TOGGLE_BUTTON, &comboBoxButton};
179   gtk_container_forall(GTK_CONTAINER(comboBox), GetInnerWidget, &info);
180 
181   if (!comboBoxButton) {
182     /* Shouldn't be reached with current internal gtk implementation; we
183      * use a generic toggle button as last resort fallback to avoid
184      * crashing. */
185     comboBoxButton = GetWidget(MOZ_GTK_TOGGLE_BUTTON);
186   } else {
187     /* We need to have pointers to the inner widgets (button, separator, arrow)
188      * of the ComboBox to get the correct rendering from theme engines which
189      * special cases their look. Since the inner layout can change, we ask GTK
190      * to NULL our pointers when they are about to become invalid because the
191      * corresponding widgets don't exist anymore. It's the role of
192      * g_object_add_weak_pointer().
193      * Note that if we don't find the inner widgets (which shouldn't happen), we
194      * fallback to use generic "non-inner" widgets, and they don't need that
195      * kind of weak pointer since they are explicit children of gProtoLayout and
196      * as such GTK holds a strong reference to them. */
197     g_object_add_weak_pointer(
198         G_OBJECT(comboBoxButton),
199         reinterpret_cast<gpointer*>(sWidgetStorage) + MOZ_GTK_COMBOBOX_BUTTON);
200   }
201 
202   return comboBoxButton;
203 }
204 
CreateComboBoxArrowWidget()205 static GtkWidget* CreateComboBoxArrowWidget() {
206   GtkWidget* comboBoxButton = GetWidget(MOZ_GTK_COMBOBOX_BUTTON);
207   GtkWidget* comboBoxArrow = nullptr;
208 
209   /* Get the widgets inside the Button */
210   GtkWidget* buttonChild = gtk_bin_get_child(GTK_BIN(comboBoxButton));
211   if (GTK_IS_BOX(buttonChild)) {
212     /* appears-as-list = FALSE, cell-view = TRUE; the button
213      * contains an hbox. This hbox is there because the ComboBox
214      * needs to place a cell renderer, a separator, and an arrow in
215      * the button when appears-as-list is FALSE. */
216     GtkInnerWidgetInfo info = {GTK_TYPE_ARROW, &comboBoxArrow};
217     gtk_container_forall(GTK_CONTAINER(buttonChild), GetInnerWidget, &info);
218   } else if (GTK_IS_ARROW(buttonChild)) {
219     /* appears-as-list = TRUE, or cell-view = FALSE;
220      * the button only contains an arrow */
221     comboBoxArrow = buttonChild;
222   }
223 
224   if (!comboBoxArrow) {
225     /* Shouldn't be reached with current internal gtk implementation;
226      * we gButtonArrowWidget as last resort fallback to avoid
227      * crashing. */
228     comboBoxArrow = GetWidget(MOZ_GTK_BUTTON_ARROW);
229   } else {
230     g_object_add_weak_pointer(
231         G_OBJECT(comboBoxArrow),
232         reinterpret_cast<gpointer*>(sWidgetStorage) + MOZ_GTK_COMBOBOX_ARROW);
233   }
234 
235   return comboBoxArrow;
236 }
237 
CreateComboBoxSeparatorWidget()238 static GtkWidget* CreateComboBoxSeparatorWidget() {
239   // Ensure to search for separator only once as it can fail
240   // TODO - it won't initialize after ResetWidgetCache() call
241   static bool isMissingSeparator = false;
242   if (isMissingSeparator) return nullptr;
243 
244   /* Get the widgets inside the Button */
245   GtkWidget* comboBoxSeparator = nullptr;
246   GtkWidget* buttonChild =
247       gtk_bin_get_child(GTK_BIN(GetWidget(MOZ_GTK_COMBOBOX_BUTTON)));
248   if (GTK_IS_BOX(buttonChild)) {
249     /* appears-as-list = FALSE, cell-view = TRUE; the button
250      * contains an hbox. This hbox is there because the ComboBox
251      * needs to place a cell renderer, a separator, and an arrow in
252      * the button when appears-as-list is FALSE. */
253     GtkInnerWidgetInfo info = {GTK_TYPE_SEPARATOR, &comboBoxSeparator};
254     gtk_container_forall(GTK_CONTAINER(buttonChild), GetInnerWidget, &info);
255   }
256 
257   if (comboBoxSeparator) {
258     g_object_add_weak_pointer(G_OBJECT(comboBoxSeparator),
259                               reinterpret_cast<gpointer*>(sWidgetStorage) +
260                                   MOZ_GTK_COMBOBOX_SEPARATOR);
261   } else {
262     /* comboBoxSeparator may be NULL
263      * when "appears-as-list" = TRUE or "cell-view" = FALSE;
264      * if there is no separator, then we just won't paint it. */
265     isMissingSeparator = true;
266   }
267 
268   return comboBoxSeparator;
269 }
270 
CreateComboBoxEntryWidget()271 static GtkWidget* CreateComboBoxEntryWidget() {
272   GtkWidget* widget = gtk_combo_box_new_with_entry();
273   AddToWindowContainer(widget);
274   return widget;
275 }
276 
CreateComboBoxEntryTextareaWidget()277 static GtkWidget* CreateComboBoxEntryTextareaWidget() {
278   GtkWidget* comboBoxTextarea = nullptr;
279 
280   /* Get its inner Entry and Button */
281   GtkInnerWidgetInfo info = {GTK_TYPE_ENTRY, &comboBoxTextarea};
282   gtk_container_forall(GTK_CONTAINER(GetWidget(MOZ_GTK_COMBOBOX_ENTRY)),
283                        GetInnerWidget, &info);
284 
285   if (!comboBoxTextarea) {
286     comboBoxTextarea = GetWidget(MOZ_GTK_ENTRY);
287   } else {
288     g_object_add_weak_pointer(
289         G_OBJECT(comboBoxTextarea),
290         reinterpret_cast<gpointer*>(sWidgetStorage) + MOZ_GTK_COMBOBOX_ENTRY);
291   }
292 
293   return comboBoxTextarea;
294 }
295 
CreateComboBoxEntryButtonWidget()296 static GtkWidget* CreateComboBoxEntryButtonWidget() {
297   GtkWidget* comboBoxButton = nullptr;
298 
299   /* Get its inner Entry and Button */
300   GtkInnerWidgetInfo info = {GTK_TYPE_TOGGLE_BUTTON, &comboBoxButton};
301   gtk_container_forall(GTK_CONTAINER(GetWidget(MOZ_GTK_COMBOBOX_ENTRY)),
302                        GetInnerWidget, &info);
303 
304   if (!comboBoxButton) {
305     comboBoxButton = GetWidget(MOZ_GTK_TOGGLE_BUTTON);
306   } else {
307     g_object_add_weak_pointer(G_OBJECT(comboBoxButton),
308                               reinterpret_cast<gpointer*>(sWidgetStorage) +
309                                   MOZ_GTK_COMBOBOX_ENTRY_BUTTON);
310   }
311 
312   return comboBoxButton;
313 }
314 
CreateComboBoxEntryArrowWidget()315 static GtkWidget* CreateComboBoxEntryArrowWidget() {
316   GtkWidget* comboBoxArrow = nullptr;
317 
318   /* Get the Arrow inside the Button */
319   GtkWidget* buttonChild =
320       gtk_bin_get_child(GTK_BIN(GetWidget(MOZ_GTK_COMBOBOX_ENTRY_BUTTON)));
321 
322   if (GTK_IS_BOX(buttonChild)) {
323     /* appears-as-list = FALSE, cell-view = TRUE; the button
324      * contains an hbox. This hbox is there because the ComboBox
325      * needs to place a cell renderer, a separator, and an arrow in
326      * the button when appears-as-list is FALSE. */
327     GtkInnerWidgetInfo info = {GTK_TYPE_ARROW, &comboBoxArrow};
328     gtk_container_forall(GTK_CONTAINER(buttonChild), GetInnerWidget, &info);
329   } else if (GTK_IS_ARROW(buttonChild)) {
330     /* appears-as-list = TRUE, or cell-view = FALSE;
331      * the button only contains an arrow */
332     comboBoxArrow = buttonChild;
333   }
334 
335   if (!comboBoxArrow) {
336     /* Shouldn't be reached with current internal gtk implementation;
337      * we gButtonArrowWidget as last resort fallback to avoid
338      * crashing. */
339     comboBoxArrow = GetWidget(MOZ_GTK_BUTTON_ARROW);
340   } else {
341     g_object_add_weak_pointer(G_OBJECT(comboBoxArrow),
342                               reinterpret_cast<gpointer*>(sWidgetStorage) +
343                                   MOZ_GTK_COMBOBOX_ENTRY_ARROW);
344   }
345 
346   return comboBoxArrow;
347 }
348 
CreateScrolledWindowWidget()349 static GtkWidget* CreateScrolledWindowWidget() {
350   GtkWidget* widget = gtk_scrolled_window_new(nullptr, nullptr);
351   AddToWindowContainer(widget);
352   return widget;
353 }
354 
CreateMenuSeparatorWidget()355 static GtkWidget* CreateMenuSeparatorWidget() {
356   GtkWidget* widget = gtk_separator_menu_item_new();
357   gtk_menu_shell_append(GTK_MENU_SHELL(GetWidget(MOZ_GTK_MENUPOPUP)), widget);
358   return widget;
359 }
360 
CreateTreeViewWidget()361 static GtkWidget* CreateTreeViewWidget() {
362   GtkWidget* widget = gtk_tree_view_new();
363   AddToWindowContainer(widget);
364   return widget;
365 }
366 
CreateTreeHeaderCellWidget()367 static GtkWidget* CreateTreeHeaderCellWidget() {
368   /*
369    * Some GTK engines paint the first and last cell
370    * of a TreeView header with a highlight.
371    * Since we do not know where our widget will be relative
372    * to the other buttons in the TreeView header, we must
373    * paint it as a button that is between two others,
374    * thus ensuring it is neither the first or last button
375    * in the header.
376    * GTK doesn't give us a way to do this explicitly,
377    * so we must paint with a button that is between two
378    * others.
379    */
380   GtkTreeViewColumn* firstTreeViewColumn;
381   GtkTreeViewColumn* middleTreeViewColumn;
382   GtkTreeViewColumn* lastTreeViewColumn;
383 
384   GtkWidget* treeView = GetWidget(MOZ_GTK_TREEVIEW);
385 
386   /* Create and append our three columns */
387   firstTreeViewColumn = gtk_tree_view_column_new();
388   gtk_tree_view_column_set_title(firstTreeViewColumn, "M");
389   gtk_tree_view_append_column(GTK_TREE_VIEW(treeView), firstTreeViewColumn);
390 
391   middleTreeViewColumn = gtk_tree_view_column_new();
392   gtk_tree_view_column_set_title(middleTreeViewColumn, "M");
393   gtk_tree_view_append_column(GTK_TREE_VIEW(treeView), middleTreeViewColumn);
394 
395   lastTreeViewColumn = gtk_tree_view_column_new();
396   gtk_tree_view_column_set_title(lastTreeViewColumn, "M");
397   gtk_tree_view_append_column(GTK_TREE_VIEW(treeView), lastTreeViewColumn);
398 
399   /* Use the middle column's header for our button */
400   return gtk_tree_view_column_get_button(middleTreeViewColumn);
401 }
402 
CreateTreeHeaderSortArrowWidget()403 static GtkWidget* CreateTreeHeaderSortArrowWidget() {
404   /* TODO, but it can't be NULL */
405   GtkWidget* widget = gtk_button_new();
406   AddToWindowContainer(widget);
407   return widget;
408 }
409 
CreateHPanedWidget()410 static GtkWidget* CreateHPanedWidget() {
411   GtkWidget* widget = gtk_paned_new(GTK_ORIENTATION_HORIZONTAL);
412   AddToWindowContainer(widget);
413   return widget;
414 }
415 
CreateVPanedWidget()416 static GtkWidget* CreateVPanedWidget() {
417   GtkWidget* widget = gtk_paned_new(GTK_ORIENTATION_VERTICAL);
418   AddToWindowContainer(widget);
419   return widget;
420 }
421 
CreateScaleWidget(GtkOrientation aOrientation)422 static GtkWidget* CreateScaleWidget(GtkOrientation aOrientation) {
423   GtkWidget* widget = gtk_scale_new(aOrientation, nullptr);
424   AddToWindowContainer(widget);
425   return widget;
426 }
427 
CreateNotebookWidget()428 static GtkWidget* CreateNotebookWidget() {
429   GtkWidget* widget = gtk_notebook_new();
430   AddToWindowContainer(widget);
431   return widget;
432 }
433 
CreateHeaderBarWidget(WidgetNodeType aAppearance,bool aIsSolidCSDStyleUsed)434 static void CreateHeaderBarWidget(WidgetNodeType aAppearance,
435                                   bool aIsSolidCSDStyleUsed) {
436   sWidgetStorage[aAppearance] = gtk_header_bar_new();
437 
438   GtkWidget* window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
439   GtkStyleContext* style = gtk_widget_get_style_context(window);
440 
441   if (aAppearance == MOZ_GTK_HEADER_BAR_MAXIMIZED) {
442     gtk_style_context_add_class(style, "maximized");
443     MOZ_ASSERT(sWidgetStorage[MOZ_GTK_HEADERBAR_WINDOW_MAXIMIZED] == nullptr,
444                "Window widget is already created!");
445     sWidgetStorage[MOZ_GTK_HEADERBAR_WINDOW_MAXIMIZED] = window;
446   } else {
447     MOZ_ASSERT(sWidgetStorage[MOZ_GTK_HEADERBAR_WINDOW] == nullptr,
448                "Window widget is already created!");
449     sWidgetStorage[MOZ_GTK_HEADERBAR_WINDOW] = window;
450   }
451 
452   // Headerbar has to be placed to window with csd or solid-csd style
453   // to properly draw the decorated.
454   gtk_style_context_add_class(style,
455                               aIsSolidCSDStyleUsed ? "solid-csd" : "csd");
456 
457   GtkWidget* fixed = gtk_fixed_new();
458   gtk_container_add(GTK_CONTAINER(window), fixed);
459   gtk_container_add(GTK_CONTAINER(fixed), sWidgetStorage[aAppearance]);
460 
461   // Emulate what create_titlebar() at gtkwindow.c does.
462   style = gtk_widget_get_style_context(sWidgetStorage[aAppearance]);
463   gtk_style_context_add_class(style, "titlebar");
464 
465   // TODO: Define default-decoration titlebar style as workaround
466   // to ensure the titlebar buttons does not overflow outside.
467   // Recently the titlebar size is calculated as
468   // tab size + titlebar border/padding (default-decoration has 6px padding
469   // at default Adwaita theme).
470   // We need to fix titlebar size calculation to also include
471   // titlebar button sizes. (Bug 1419442)
472   gtk_style_context_add_class(style, "default-decoration");
473 }
474 
475 #define ICON_SCALE_VARIANTS 2
476 
LoadWidgetIconPixbuf(GtkWidget * aWidgetIcon)477 static void LoadWidgetIconPixbuf(GtkWidget* aWidgetIcon) {
478   GtkStyleContext* style = gtk_widget_get_style_context(aWidgetIcon);
479 
480   const gchar* iconName;
481   GtkIconSize gtkIconSize;
482   gtk_image_get_icon_name(GTK_IMAGE(aWidgetIcon), &iconName, &gtkIconSize);
483 
484   gint iconWidth, iconHeight;
485   gtk_icon_size_lookup(gtkIconSize, &iconWidth, &iconHeight);
486 
487   /* Those are available since Gtk+ 3.10 as well as GtkHeaderBar */
488   for (int scale = 1; scale < ICON_SCALE_VARIANTS + 1; scale++) {
489     GtkIconInfo* gtkIconInfo = gtk_icon_theme_lookup_icon_for_scale(
490         gtk_icon_theme_get_default(), iconName, iconWidth, scale,
491         (GtkIconLookupFlags)0);
492 
493     if (!gtkIconInfo) {
494       // We miss the icon, nothing to do here.
495       return;
496     }
497 
498     gboolean unused;
499     GdkPixbuf* iconPixbuf = gtk_icon_info_load_symbolic_for_context(
500         gtkIconInfo, style, &unused, nullptr);
501     g_object_unref(G_OBJECT(gtkIconInfo));
502 
503     cairo_surface_t* iconSurface =
504         gdk_cairo_surface_create_from_pixbuf(iconPixbuf, scale, nullptr);
505     g_object_unref(iconPixbuf);
506 
507     nsPrintfCString surfaceName("MozillaIconSurface%d", scale);
508     g_object_set_data_full(G_OBJECT(aWidgetIcon), surfaceName.get(),
509                            iconSurface, (GDestroyNotify)cairo_surface_destroy);
510   }
511 }
512 
GetWidgetIconSurface(GtkWidget * aWidgetIcon,int aScale)513 cairo_surface_t* GetWidgetIconSurface(GtkWidget* aWidgetIcon, int aScale) {
514   if (aScale > ICON_SCALE_VARIANTS) {
515     aScale = ICON_SCALE_VARIANTS;
516   }
517 
518   nsPrintfCString surfaceName("MozillaIconSurface%d", aScale);
519   return (cairo_surface_t*)g_object_get_data(G_OBJECT(aWidgetIcon),
520                                              surfaceName.get());
521 }
522 
CreateHeaderBarButton(GtkWidget * aParentWidget,WidgetNodeType aAppearance)523 static void CreateHeaderBarButton(GtkWidget* aParentWidget,
524                                   WidgetNodeType aAppearance) {
525   GtkWidget* widget = gtk_button_new();
526 
527   // We have to add button to widget hierarchy now to pick
528   // right icon style at LoadWidgetIconPixbuf().
529   if (GTK_IS_BOX(aParentWidget)) {
530     gtk_box_pack_start(GTK_BOX(aParentWidget), widget, FALSE, FALSE, 0);
531   } else {
532     gtk_container_add(GTK_CONTAINER(aParentWidget), widget);
533   }
534 
535   // We bypass GetWidget() here because we create all titlebar
536   // buttons at once when a first one is requested.
537   NS_ASSERTION(sWidgetStorage[aAppearance] == nullptr,
538                "Titlebar button is already created!");
539   sWidgetStorage[aAppearance] = widget;
540 
541   // We need to show the button widget now as GtkBox does not
542   // place invisible widgets and we'll miss first-child/last-child
543   // css selectors at the buttons otherwise.
544   gtk_widget_show(widget);
545 
546   GtkStyleContext* style = gtk_widget_get_style_context(widget);
547   gtk_style_context_add_class(style, "titlebutton");
548 
549   GtkWidget* image = nullptr;
550   switch (aAppearance) {
551     case MOZ_GTK_HEADER_BAR_BUTTON_CLOSE:
552       gtk_style_context_add_class(style, "close");
553       image = gtk_image_new_from_icon_name("window-close-symbolic",
554                                            GTK_ICON_SIZE_MENU);
555       break;
556     case MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE:
557       gtk_style_context_add_class(style, "minimize");
558       image = gtk_image_new_from_icon_name("window-minimize-symbolic",
559                                            GTK_ICON_SIZE_MENU);
560       break;
561 
562     case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE:
563       gtk_style_context_add_class(style, "maximize");
564       image = gtk_image_new_from_icon_name("window-maximize-symbolic",
565                                            GTK_ICON_SIZE_MENU);
566       break;
567 
568     case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE_RESTORE:
569       gtk_style_context_add_class(style, "maximize");
570       image = gtk_image_new_from_icon_name("window-restore-symbolic",
571                                            GTK_ICON_SIZE_MENU);
572       break;
573     default:
574       break;
575   }
576 
577   gtk_widget_set_valign(widget, GTK_ALIGN_CENTER);
578   g_object_set(image, "use-fallback", TRUE, NULL);
579   gtk_container_add(GTK_CONTAINER(widget), image);
580 
581   // We bypass GetWidget() here by explicit sWidgetStorage[] update so
582   // invalidate the style as well as GetWidget() does.
583   style = gtk_widget_get_style_context(image);
584   gtk_style_context_invalidate(style);
585 
586   LoadWidgetIconPixbuf(image);
587 }
588 
IsToolbarButtonEnabled(ButtonLayout * aButtonLayout,size_t aButtonNums,WidgetNodeType aAppearance)589 static bool IsToolbarButtonEnabled(ButtonLayout* aButtonLayout,
590                                    size_t aButtonNums,
591                                    WidgetNodeType aAppearance) {
592   for (size_t i = 0; i < aButtonNums; i++) {
593     if (aButtonLayout[i].mType == aAppearance) {
594       return true;
595     }
596   }
597   return false;
598 }
599 
CreateHeaderBarButtons()600 static void CreateHeaderBarButtons() {
601   GtkWidget* headerBar = sWidgetStorage[MOZ_GTK_HEADER_BAR];
602   MOZ_ASSERT(headerBar != nullptr, "We're missing header bar widget!");
603 
604   gint buttonSpacing = 6;
605   g_object_get(headerBar, "spacing", &buttonSpacing, nullptr);
606 
607   GtkWidget* buttonBox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, buttonSpacing);
608   gtk_container_add(GTK_CONTAINER(GetWidget(MOZ_GTK_HEADER_BAR)), buttonBox);
609   // We support only LTR headerbar layout for now.
610   gtk_style_context_add_class(gtk_widget_get_style_context(buttonBox),
611                               GTK_STYLE_CLASS_LEFT);
612 
613   ButtonLayout buttonLayout[TOOLBAR_BUTTONS];
614 
615   size_t activeButtons =
616       GetGtkHeaderBarButtonLayout(mozilla::Span(buttonLayout), nullptr);
617 
618   if (IsToolbarButtonEnabled(buttonLayout, activeButtons,
619                              MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE)) {
620     CreateHeaderBarButton(buttonBox, MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE);
621   }
622   if (IsToolbarButtonEnabled(buttonLayout, activeButtons,
623                              MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE)) {
624     CreateHeaderBarButton(buttonBox, MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE);
625     // We don't pack "restore" headerbar button to box as it's an icon
626     // placeholder. Pack it only to header bar to get correct style.
627     CreateHeaderBarButton(GetWidget(MOZ_GTK_HEADER_BAR),
628                           MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE_RESTORE);
629   }
630   if (IsToolbarButtonEnabled(buttonLayout, activeButtons,
631                              MOZ_GTK_HEADER_BAR_BUTTON_CLOSE)) {
632     CreateHeaderBarButton(buttonBox, MOZ_GTK_HEADER_BAR_BUTTON_CLOSE);
633   }
634 }
635 
CreateHeaderBar()636 static void CreateHeaderBar() {
637   const bool isSolidCSDStyleUsed = []() {
638     GtkWidget* window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
639     gtk_window_set_titlebar(GTK_WINDOW(window), gtk_header_bar_new());
640     gtk_widget_realize(window);
641     GtkStyleContext* windowStyle = gtk_widget_get_style_context(window);
642     bool ret = gtk_style_context_has_class(windowStyle, "solid-csd");
643     gtk_widget_destroy(window);
644     return ret;
645   }();
646 
647   CreateHeaderBarWidget(MOZ_GTK_HEADER_BAR, isSolidCSDStyleUsed);
648   CreateHeaderBarWidget(MOZ_GTK_HEADER_BAR_MAXIMIZED, isSolidCSDStyleUsed);
649   CreateHeaderBarButtons();
650 }
651 
CreateWidget(WidgetNodeType aAppearance)652 static GtkWidget* CreateWidget(WidgetNodeType aAppearance) {
653   switch (aAppearance) {
654     case MOZ_GTK_WINDOW:
655       return CreateWindowWidget();
656     case MOZ_GTK_WINDOW_CONTAINER:
657       return CreateWindowContainerWidget();
658     case MOZ_GTK_CHECKBUTTON_CONTAINER:
659       return CreateCheckboxWidget();
660     case MOZ_GTK_PROGRESSBAR:
661       return CreateProgressWidget();
662     case MOZ_GTK_RADIOBUTTON_CONTAINER:
663       return CreateRadiobuttonWidget();
664     case MOZ_GTK_SCROLLBAR_HORIZONTAL:
665       return CreateScrollbarWidget(aAppearance, GTK_ORIENTATION_HORIZONTAL);
666     case MOZ_GTK_SCROLLBAR_VERTICAL:
667       return CreateScrollbarWidget(aAppearance, GTK_ORIENTATION_VERTICAL);
668     case MOZ_GTK_MENUBAR:
669       return CreateMenuBarWidget();
670     case MOZ_GTK_MENUPOPUP:
671       return CreateMenuPopupWidget();
672     case MOZ_GTK_MENUSEPARATOR:
673       return CreateMenuSeparatorWidget();
674     case MOZ_GTK_EXPANDER:
675       return CreateExpanderWidget();
676     case MOZ_GTK_FRAME:
677       return CreateFrameWidget();
678     case MOZ_GTK_GRIPPER:
679       return CreateGripperWidget();
680     case MOZ_GTK_TOOLBAR:
681       return CreateToolbarWidget();
682     case MOZ_GTK_TOOLBAR_SEPARATOR:
683       return CreateToolbarSeparatorWidget();
684     case MOZ_GTK_SPINBUTTON:
685       return CreateSpinWidget();
686     case MOZ_GTK_BUTTON:
687       return CreateButtonWidget();
688     case MOZ_GTK_TOGGLE_BUTTON:
689       return CreateToggleButtonWidget();
690     case MOZ_GTK_BUTTON_ARROW:
691       return CreateButtonArrowWidget();
692     case MOZ_GTK_ENTRY:
693     case MOZ_GTK_DROPDOWN_ENTRY:
694       return CreateEntryWidget();
695     case MOZ_GTK_SCROLLED_WINDOW:
696       return CreateScrolledWindowWidget();
697     case MOZ_GTK_TREEVIEW:
698       return CreateTreeViewWidget();
699     case MOZ_GTK_TREE_HEADER_CELL:
700       return CreateTreeHeaderCellWidget();
701     case MOZ_GTK_TREE_HEADER_SORTARROW:
702       return CreateTreeHeaderSortArrowWidget();
703     case MOZ_GTK_SPLITTER_HORIZONTAL:
704       return CreateHPanedWidget();
705     case MOZ_GTK_SPLITTER_VERTICAL:
706       return CreateVPanedWidget();
707     case MOZ_GTK_SCALE_HORIZONTAL:
708       return CreateScaleWidget(GTK_ORIENTATION_HORIZONTAL);
709     case MOZ_GTK_SCALE_VERTICAL:
710       return CreateScaleWidget(GTK_ORIENTATION_VERTICAL);
711     case MOZ_GTK_NOTEBOOK:
712       return CreateNotebookWidget();
713     case MOZ_GTK_COMBOBOX:
714       return CreateComboBoxWidget();
715     case MOZ_GTK_COMBOBOX_BUTTON:
716       return CreateComboBoxButtonWidget();
717     case MOZ_GTK_COMBOBOX_ARROW:
718       return CreateComboBoxArrowWidget();
719     case MOZ_GTK_COMBOBOX_SEPARATOR:
720       return CreateComboBoxSeparatorWidget();
721     case MOZ_GTK_COMBOBOX_ENTRY:
722       return CreateComboBoxEntryWidget();
723     case MOZ_GTK_COMBOBOX_ENTRY_TEXTAREA:
724       return CreateComboBoxEntryTextareaWidget();
725     case MOZ_GTK_COMBOBOX_ENTRY_BUTTON:
726       return CreateComboBoxEntryButtonWidget();
727     case MOZ_GTK_COMBOBOX_ENTRY_ARROW:
728       return CreateComboBoxEntryArrowWidget();
729     case MOZ_GTK_HEADERBAR_WINDOW:
730     case MOZ_GTK_HEADERBAR_WINDOW_MAXIMIZED:
731     case MOZ_GTK_HEADER_BAR:
732     case MOZ_GTK_HEADER_BAR_MAXIMIZED:
733     case MOZ_GTK_HEADER_BAR_BUTTON_CLOSE:
734     case MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE:
735     case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE:
736     case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE_RESTORE:
737       /* Create header bar widgets once and fill with child elements as we need
738          the header bar fully configured to get a correct style */
739       CreateHeaderBar();
740       return sWidgetStorage[aAppearance];
741     default:
742       /* Not implemented */
743       return nullptr;
744   }
745 }
746 
GetWidget(WidgetNodeType aAppearance)747 GtkWidget* GetWidget(WidgetNodeType aAppearance) {
748   GtkWidget* widget = sWidgetStorage[aAppearance];
749   if (!widget) {
750     widget = CreateWidget(aAppearance);
751     // Some widgets (MOZ_GTK_COMBOBOX_SEPARATOR for instance) may not be
752     // available or implemented.
753     if (!widget) {
754       return nullptr;
755     }
756     // In GTK versions prior to 3.18, automatic invalidation of style contexts
757     // for widgets was delayed until the next resize event.  Gecko however,
758     // typically uses the style context before the resize event runs and so an
759     // explicit invalidation may be required.  This is necessary if a style
760     // property was retrieved before all changes were made to the style
761     // context.  One such situation is where gtk_button_construct_child()
762     // retrieves the style property "image-spacing" during construction of the
763     // GtkButton, before its parent is set to provide inheritance of ancestor
764     // properties.  More recent GTK versions do not need this, but do not
765     // re-resolve until required and so invalidation does not trigger
766     // unnecessary resolution in general.
767     GtkStyleContext* style = gtk_widget_get_style_context(widget);
768     gtk_style_context_invalidate(style);
769 
770     sWidgetStorage[aAppearance] = widget;
771   }
772   return widget;
773 }
774 
AddStyleClassesFromStyle(GtkStyleContext * aDest,GtkStyleContext * aSrc)775 static void AddStyleClassesFromStyle(GtkStyleContext* aDest,
776                                      GtkStyleContext* aSrc) {
777   GList* classes = gtk_style_context_list_classes(aSrc);
778   for (GList* link = classes; link; link = link->next) {
779     gtk_style_context_add_class(aDest, static_cast<gchar*>(link->data));
780   }
781   g_list_free(classes);
782 }
783 
CreateStyleForWidget(GtkWidget * aWidget,GtkStyleContext * aParentStyle)784 GtkStyleContext* CreateStyleForWidget(GtkWidget* aWidget,
785                                       GtkStyleContext* aParentStyle) {
786   static auto sGtkWidgetClassGetCSSName =
787       reinterpret_cast<const char* (*)(GtkWidgetClass*)>(
788           dlsym(RTLD_DEFAULT, "gtk_widget_class_get_css_name"));
789 
790   GtkWidgetClass* widgetClass = GTK_WIDGET_GET_CLASS(aWidget);
791   const gchar* name = sGtkWidgetClassGetCSSName
792                           ? sGtkWidgetClassGetCSSName(widgetClass)
793                           : nullptr;
794 
795   GtkStyleContext* context =
796       CreateCSSNode(name, aParentStyle, G_TYPE_FROM_CLASS(widgetClass));
797 
798   // Classes are stored on the style context instead of the path so that any
799   // future gtk_style_context_save() will inherit classes on the head CSS
800   // node, in the same way as happens when called on a style context owned by
801   // a widget.
802   //
803   // Classes can be stored on a GtkCssNodeDeclaration and/or the path.
804   // gtk_style_context_save() reuses the GtkCssNodeDeclaration, and appends a
805   // new object to the path, without copying the classes from the old path
806   // head.  The new head picks up classes from the GtkCssNodeDeclaration, but
807   // not the path.  GtkWidgets store their classes on the
808   // GtkCssNodeDeclaration, so make sure to add classes there.
809   //
810   // Picking up classes from the style context also means that
811   // https://bugzilla.gnome.org/show_bug.cgi?id=767312, which can stop
812   // gtk_widget_path_append_for_widget() from finding classes in GTK 3.20,
813   // is not a problem.
814   GtkStyleContext* widgetStyle = gtk_widget_get_style_context(aWidget);
815   AddStyleClassesFromStyle(context, widgetStyle);
816 
817   // Release any floating reference on aWidget.
818   g_object_ref_sink(aWidget);
819   g_object_unref(aWidget);
820 
821   return context;
822 }
823 
CreateStyleForWidget(GtkWidget * aWidget,WidgetNodeType aParentType)824 static GtkStyleContext* CreateStyleForWidget(GtkWidget* aWidget,
825                                              WidgetNodeType aParentType) {
826   return CreateStyleForWidget(aWidget, GetWidgetRootStyle(aParentType));
827 }
828 
CreateCSSNode(const char * aName,GtkStyleContext * aParentStyle,GType aType)829 GtkStyleContext* CreateCSSNode(const char* aName, GtkStyleContext* aParentStyle,
830                                GType aType) {
831   static auto sGtkWidgetPathIterSetObjectName =
832       reinterpret_cast<void (*)(GtkWidgetPath*, gint, const char*)>(
833           dlsym(RTLD_DEFAULT, "gtk_widget_path_iter_set_object_name"));
834 
835   GtkWidgetPath* path;
836   if (aParentStyle) {
837     path = gtk_widget_path_copy(gtk_style_context_get_path(aParentStyle));
838     // Copy classes from the parent style context to its corresponding node in
839     // the path, because GTK will only match against ancestor classes if they
840     // are on the path.
841     GList* classes = gtk_style_context_list_classes(aParentStyle);
842     for (GList* link = classes; link; link = link->next) {
843       gtk_widget_path_iter_add_class(path, -1, static_cast<gchar*>(link->data));
844     }
845     g_list_free(classes);
846   } else {
847     path = gtk_widget_path_new();
848   }
849 
850   gtk_widget_path_append_type(path, aType);
851 
852   if (sGtkWidgetPathIterSetObjectName) {
853     (*sGtkWidgetPathIterSetObjectName)(path, -1, aName);
854   }
855 
856   GtkStyleContext* context = gtk_style_context_new();
857   gtk_style_context_set_path(context, path);
858   gtk_style_context_set_parent(context, aParentStyle);
859   gtk_widget_path_unref(path);
860 
861   // In GTK 3.4, gtk_render_* functions use |theming_engine| on the style
862   // context without ensuring any style resolution sets it appropriately
863   // in style_data_lookup(). e.g.
864   // https://git.gnome.org/browse/gtk+/tree/gtk/gtkstylecontext.c?h=3.4.4#n3847
865   //
866   // That can result in incorrect drawing on first draw.  To work around this,
867   // force a style look-up to set |theming_engine|.  It is sufficient to do
868   // this only on context creation, instead of after every modification to the
869   // context, because themes typically (Ambiance and oxygen-gtk, at least) set
870   // the "engine" property with the '*' selector.
871   if (GTK_MAJOR_VERSION == 3 && gtk_get_minor_version() < 6) {
872     GdkRGBA unused;
873     gtk_style_context_get_color(context, GTK_STATE_FLAG_NORMAL, &unused);
874   }
875 
876   return context;
877 }
878 
879 // Return a style context matching that of the root CSS node of a widget.
880 // This is used by all GTK versions.
GetWidgetRootStyle(WidgetNodeType aNodeType)881 static GtkStyleContext* GetWidgetRootStyle(WidgetNodeType aNodeType) {
882   GtkStyleContext* style = sStyleStorage[aNodeType];
883   if (style) return style;
884 
885   switch (aNodeType) {
886     case MOZ_GTK_MENUBARITEM:
887       style = CreateStyleForWidget(gtk_menu_item_new(), MOZ_GTK_MENUBAR);
888       break;
889     case MOZ_GTK_MENUITEM:
890       style = CreateStyleForWidget(gtk_menu_item_new(), MOZ_GTK_MENUPOPUP);
891       break;
892     case MOZ_GTK_CHECKMENUITEM:
893       style =
894           CreateStyleForWidget(gtk_check_menu_item_new(), MOZ_GTK_MENUPOPUP);
895       break;
896     case MOZ_GTK_RADIOMENUITEM:
897       style = CreateStyleForWidget(gtk_radio_menu_item_new(nullptr),
898                                    MOZ_GTK_MENUPOPUP);
899       break;
900     case MOZ_GTK_TEXT_VIEW:
901       style =
902           CreateStyleForWidget(gtk_text_view_new(), MOZ_GTK_SCROLLED_WINDOW);
903       break;
904     case MOZ_GTK_TOOLTIP:
905       if (gtk_check_version(3, 20, 0) != nullptr) {
906         // The tooltip style class is added first in CreateTooltipWidget()
907         // and transfered to style in CreateStyleForWidget().
908         GtkWidget* tooltipWindow = CreateTooltipWidget();
909         style = CreateStyleForWidget(tooltipWindow, nullptr);
910         gtk_widget_destroy(tooltipWindow);  // Release GtkWindow self-reference.
911       } else {
912         // We create this from the path because GtkTooltipWindow is not public.
913         style = CreateCSSNode("tooltip", nullptr, GTK_TYPE_TOOLTIP);
914         gtk_style_context_add_class(style, GTK_STYLE_CLASS_BACKGROUND);
915       }
916       break;
917     case MOZ_GTK_TOOLTIP_BOX:
918       style = CreateStyleForWidget(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0),
919                                    MOZ_GTK_TOOLTIP);
920       break;
921     case MOZ_GTK_TOOLTIP_BOX_LABEL:
922       style = CreateStyleForWidget(gtk_label_new(nullptr), MOZ_GTK_TOOLTIP_BOX);
923       break;
924     default:
925       GtkWidget* widget = GetWidget(aNodeType);
926       MOZ_ASSERT(widget);
927       return gtk_widget_get_style_context(widget);
928   }
929 
930   MOZ_ASSERT(style);
931   sStyleStorage[aNodeType] = style;
932   return style;
933 }
934 
CreateChildCSSNode(const char * aName,WidgetNodeType aParentNodeType)935 static GtkStyleContext* CreateChildCSSNode(const char* aName,
936                                            WidgetNodeType aParentNodeType) {
937   return CreateCSSNode(aName, GetCssNodeStyleInternal(aParentNodeType));
938 }
939 
940 // Create a style context equivalent to a saved root style context of
941 // |aAppearance| with |aStyleClass| as an additional class.  This is used to
942 // produce a context equivalent to what GTK versions < 3.20 use for many
943 // internal parts of widgets.
CreateSubStyleWithClass(WidgetNodeType aAppearance,const gchar * aStyleClass)944 static GtkStyleContext* CreateSubStyleWithClass(WidgetNodeType aAppearance,
945                                                 const gchar* aStyleClass) {
946   static auto sGtkWidgetPathIterGetObjectName =
947       reinterpret_cast<const char* (*)(const GtkWidgetPath*, gint)>(
948           dlsym(RTLD_DEFAULT, "gtk_widget_path_iter_get_object_name"));
949 
950   GtkStyleContext* parentStyle = GetWidgetRootStyle(aAppearance);
951 
952   // Create a new context that behaves like |parentStyle| would after
953   // gtk_style_context_save(parentStyle).
954   //
955   // Avoiding gtk_style_context_save() avoids the need to manage the
956   // restore, and a new context permits caching style resolution.
957   //
958   // gtk_style_context_save(context) changes the node hierarchy of |context|
959   // to add a new GtkCssNodeDeclaration that is a copy of its original node.
960   // The new node is a child of the original node, and so the new heirarchy is
961   // one level deeper.  The new node receives the same classes as the
962   // original, but any changes to the classes on |context| will change only
963   // the new node.  The new node inherits properties from the original node
964   // (which retains the original heirarchy and classes) and matches CSS rules
965   // with the new heirarchy and any changes to the classes.
966   //
967   // The change in hierarchy can produce some surprises in matching theme CSS
968   // rules (e.g. https://bugzilla.gnome.org/show_bug.cgi?id=761870#c2), but it
969   // is important here to produce the same behavior so that rules match the
970   // same widget parts in Gecko as they do in GTK.
971   //
972   // When using public GTK API to construct style contexts, a widget path is
973   // required.  CSS rules are not matched against the style context heirarchy
974   // but according to the heirarchy in the widget path.  The path that matches
975   // the same CSS rules as a saved context is like the path of |parentStyle|
976   // but with an extra copy of the head (last) object appended.  Setting
977   // |parentStyle| as the parent context provides the same inheritance of
978   // properties from the widget root node.
979   const GtkWidgetPath* parentPath = gtk_style_context_get_path(parentStyle);
980   const gchar* name = sGtkWidgetPathIterGetObjectName
981                           ? sGtkWidgetPathIterGetObjectName(parentPath, -1)
982                           : nullptr;
983   GType objectType = gtk_widget_path_get_object_type(parentPath);
984 
985   GtkStyleContext* style = CreateCSSNode(name, parentStyle, objectType);
986 
987   // Start with the same classes on the new node as were on |parentStyle|.
988   // GTK puts no regions or junction_sides on widget root nodes, and so there
989   // is no need to copy these.
990   AddStyleClassesFromStyle(style, parentStyle);
991 
992   gtk_style_context_add_class(style, aStyleClass);
993   return style;
994 }
995 
996 /* GetCssNodeStyleInternal is used by Gtk >= 3.20 */
GetCssNodeStyleInternal(WidgetNodeType aNodeType)997 static GtkStyleContext* GetCssNodeStyleInternal(WidgetNodeType aNodeType) {
998   GtkStyleContext* style = sStyleStorage[aNodeType];
999   if (style) return style;
1000 
1001   switch (aNodeType) {
1002     case MOZ_GTK_SCROLLBAR_CONTENTS_HORIZONTAL:
1003       style = CreateChildCSSNode("contents", MOZ_GTK_SCROLLBAR_HORIZONTAL);
1004       break;
1005     case MOZ_GTK_SCROLLBAR_TROUGH_HORIZONTAL:
1006       style = CreateChildCSSNode(GTK_STYLE_CLASS_TROUGH,
1007                                  MOZ_GTK_SCROLLBAR_CONTENTS_HORIZONTAL);
1008       break;
1009     case MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL:
1010       style = CreateChildCSSNode(GTK_STYLE_CLASS_SLIDER,
1011                                  MOZ_GTK_SCROLLBAR_TROUGH_HORIZONTAL);
1012       break;
1013     case MOZ_GTK_SCROLLBAR_CONTENTS_VERTICAL:
1014       style = CreateChildCSSNode("contents", MOZ_GTK_SCROLLBAR_VERTICAL);
1015       break;
1016     case MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL:
1017       style = CreateChildCSSNode(GTK_STYLE_CLASS_TROUGH,
1018                                  MOZ_GTK_SCROLLBAR_CONTENTS_VERTICAL);
1019       break;
1020     case MOZ_GTK_SCROLLBAR_THUMB_VERTICAL:
1021       style = CreateChildCSSNode(GTK_STYLE_CLASS_SLIDER,
1022                                  MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL);
1023       break;
1024     case MOZ_GTK_SCROLLBAR_BUTTON:
1025       style = CreateChildCSSNode(GTK_STYLE_CLASS_BUTTON,
1026                                  MOZ_GTK_SCROLLBAR_CONTENTS_VERTICAL);
1027       break;
1028     case MOZ_GTK_RADIOBUTTON:
1029       style = CreateChildCSSNode(GTK_STYLE_CLASS_RADIO,
1030                                  MOZ_GTK_RADIOBUTTON_CONTAINER);
1031       break;
1032     case MOZ_GTK_CHECKBUTTON:
1033       style = CreateChildCSSNode(GTK_STYLE_CLASS_CHECK,
1034                                  MOZ_GTK_CHECKBUTTON_CONTAINER);
1035       break;
1036     case MOZ_GTK_RADIOMENUITEM_INDICATOR:
1037       style = CreateChildCSSNode(GTK_STYLE_CLASS_RADIO, MOZ_GTK_RADIOMENUITEM);
1038       break;
1039     case MOZ_GTK_CHECKMENUITEM_INDICATOR:
1040       style = CreateChildCSSNode(GTK_STYLE_CLASS_CHECK, MOZ_GTK_CHECKMENUITEM);
1041       break;
1042     case MOZ_GTK_PROGRESS_TROUGH:
1043       /* Progress bar background (trough) */
1044       style = CreateChildCSSNode(GTK_STYLE_CLASS_TROUGH, MOZ_GTK_PROGRESSBAR);
1045       break;
1046     case MOZ_GTK_PROGRESS_CHUNK:
1047       style = CreateChildCSSNode("progress", MOZ_GTK_PROGRESS_TROUGH);
1048       break;
1049     case MOZ_GTK_GRIPPER:
1050       // TODO - create from CSS node
1051       style = CreateSubStyleWithClass(MOZ_GTK_GRIPPER, GTK_STYLE_CLASS_GRIP);
1052       break;
1053     case MOZ_GTK_SPINBUTTON_ENTRY:
1054       // TODO - create from CSS node
1055       style =
1056           CreateSubStyleWithClass(MOZ_GTK_SPINBUTTON, GTK_STYLE_CLASS_ENTRY);
1057       break;
1058     case MOZ_GTK_SCROLLED_WINDOW:
1059       // TODO - create from CSS node
1060       style = CreateSubStyleWithClass(MOZ_GTK_SCROLLED_WINDOW,
1061                                       GTK_STYLE_CLASS_FRAME);
1062       break;
1063     case MOZ_GTK_TEXT_VIEW_TEXT_SELECTION:
1064       style = CreateChildCSSNode("selection", MOZ_GTK_TEXT_VIEW_TEXT);
1065       break;
1066     case MOZ_GTK_TEXT_VIEW_TEXT:
1067     case MOZ_GTK_RESIZER:
1068       style = CreateChildCSSNode("text", MOZ_GTK_TEXT_VIEW);
1069       if (aNodeType == MOZ_GTK_RESIZER) {
1070         // The "grip" class provides the correct builtin icon from
1071         // gtk_render_handle().  The icon is drawn with shaded variants of
1072         // the background color, and so a transparent background would lead to
1073         // a transparent resizer.  gtk_render_handle() also uses the
1074         // background color to draw a background, and so this style otherwise
1075         // matches what is used in GtkTextView to match the background with
1076         // textarea elements.
1077         GdkRGBA color;
1078         gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL,
1079                                                &color);
1080         if (color.alpha == 0.0) {
1081           g_object_unref(style);
1082           style = CreateStyleForWidget(gtk_text_view_new(),
1083                                        MOZ_GTK_SCROLLED_WINDOW);
1084         }
1085         gtk_style_context_add_class(style, GTK_STYLE_CLASS_GRIP);
1086       }
1087       break;
1088     case MOZ_GTK_FRAME_BORDER:
1089       style = CreateChildCSSNode("border", MOZ_GTK_FRAME);
1090       break;
1091     case MOZ_GTK_TREEVIEW_VIEW:
1092       // TODO - create from CSS node
1093       style = CreateSubStyleWithClass(MOZ_GTK_TREEVIEW, GTK_STYLE_CLASS_VIEW);
1094       break;
1095     case MOZ_GTK_TREEVIEW_EXPANDER:
1096       // TODO - create from CSS node
1097       style =
1098           CreateSubStyleWithClass(MOZ_GTK_TREEVIEW, GTK_STYLE_CLASS_EXPANDER);
1099       break;
1100     case MOZ_GTK_SPLITTER_SEPARATOR_HORIZONTAL:
1101       style = CreateChildCSSNode("separator", MOZ_GTK_SPLITTER_HORIZONTAL);
1102       break;
1103     case MOZ_GTK_SPLITTER_SEPARATOR_VERTICAL:
1104       style = CreateChildCSSNode("separator", MOZ_GTK_SPLITTER_VERTICAL);
1105       break;
1106     case MOZ_GTK_SCALE_CONTENTS_HORIZONTAL:
1107       style = CreateChildCSSNode("contents", MOZ_GTK_SCALE_HORIZONTAL);
1108       break;
1109     case MOZ_GTK_SCALE_CONTENTS_VERTICAL:
1110       style = CreateChildCSSNode("contents", MOZ_GTK_SCALE_VERTICAL);
1111       break;
1112     case MOZ_GTK_SCALE_TROUGH_HORIZONTAL:
1113       style = CreateChildCSSNode(GTK_STYLE_CLASS_TROUGH,
1114                                  MOZ_GTK_SCALE_CONTENTS_HORIZONTAL);
1115       break;
1116     case MOZ_GTK_SCALE_TROUGH_VERTICAL:
1117       style = CreateChildCSSNode(GTK_STYLE_CLASS_TROUGH,
1118                                  MOZ_GTK_SCALE_CONTENTS_VERTICAL);
1119       break;
1120     case MOZ_GTK_SCALE_THUMB_HORIZONTAL:
1121       style = CreateChildCSSNode(GTK_STYLE_CLASS_SLIDER,
1122                                  MOZ_GTK_SCALE_TROUGH_HORIZONTAL);
1123       break;
1124     case MOZ_GTK_SCALE_THUMB_VERTICAL:
1125       style = CreateChildCSSNode(GTK_STYLE_CLASS_SLIDER,
1126                                  MOZ_GTK_SCALE_TROUGH_VERTICAL);
1127       break;
1128     case MOZ_GTK_TAB_TOP: {
1129       // TODO - create from CSS node
1130       style = CreateSubStyleWithClass(MOZ_GTK_NOTEBOOK, GTK_STYLE_CLASS_TOP);
1131       gtk_style_context_add_region(style, GTK_STYLE_REGION_TAB,
1132                                    static_cast<GtkRegionFlags>(0));
1133       break;
1134     }
1135     case MOZ_GTK_TAB_BOTTOM: {
1136       // TODO - create from CSS node
1137       style = CreateSubStyleWithClass(MOZ_GTK_NOTEBOOK, GTK_STYLE_CLASS_BOTTOM);
1138       gtk_style_context_add_region(style, GTK_STYLE_REGION_TAB,
1139                                    static_cast<GtkRegionFlags>(0));
1140       break;
1141     }
1142     case MOZ_GTK_NOTEBOOK:
1143     case MOZ_GTK_NOTEBOOK_HEADER:
1144     case MOZ_GTK_TABPANELS:
1145     case MOZ_GTK_TAB_SCROLLARROW: {
1146       // TODO - create from CSS node
1147       GtkWidget* widget = GetWidget(MOZ_GTK_NOTEBOOK);
1148       return gtk_widget_get_style_context(widget);
1149     }
1150     case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE_RESTORE: {
1151       NS_ASSERTION(
1152           false, "MOZ_GTK_HEADER_BAR_BUTTON_RESTORE is used as an icon only!");
1153       return nullptr;
1154     }
1155     case MOZ_GTK_WINDOW_DECORATION: {
1156       GtkStyleContext* parentStyle =
1157           CreateSubStyleWithClass(MOZ_GTK_WINDOW, "csd");
1158       style = CreateCSSNode("decoration", parentStyle);
1159       g_object_unref(parentStyle);
1160       break;
1161     }
1162     case MOZ_GTK_WINDOW_DECORATION_SOLID: {
1163       GtkStyleContext* parentStyle =
1164           CreateSubStyleWithClass(MOZ_GTK_WINDOW, "solid-csd");
1165       style = CreateCSSNode("decoration", parentStyle);
1166       g_object_unref(parentStyle);
1167       break;
1168     }
1169     default:
1170       return GetWidgetRootStyle(aNodeType);
1171   }
1172 
1173   MOZ_ASSERT(style, "missing style context for node type");
1174   sStyleStorage[aNodeType] = style;
1175   return style;
1176 }
1177 
1178 /* GetWidgetStyleInternal is used by Gtk < 3.20 */
GetWidgetStyleInternal(WidgetNodeType aNodeType)1179 static GtkStyleContext* GetWidgetStyleInternal(WidgetNodeType aNodeType) {
1180   GtkStyleContext* style = sStyleStorage[aNodeType];
1181   if (style) return style;
1182 
1183   switch (aNodeType) {
1184     case MOZ_GTK_SCROLLBAR_TROUGH_HORIZONTAL:
1185       style = CreateSubStyleWithClass(MOZ_GTK_SCROLLBAR_HORIZONTAL,
1186                                       GTK_STYLE_CLASS_TROUGH);
1187       break;
1188     case MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL:
1189       style = CreateSubStyleWithClass(MOZ_GTK_SCROLLBAR_HORIZONTAL,
1190                                       GTK_STYLE_CLASS_SLIDER);
1191       break;
1192     case MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL:
1193       style = CreateSubStyleWithClass(MOZ_GTK_SCROLLBAR_VERTICAL,
1194                                       GTK_STYLE_CLASS_TROUGH);
1195       break;
1196     case MOZ_GTK_SCROLLBAR_THUMB_VERTICAL:
1197       style = CreateSubStyleWithClass(MOZ_GTK_SCROLLBAR_VERTICAL,
1198                                       GTK_STYLE_CLASS_SLIDER);
1199       break;
1200     case MOZ_GTK_RADIOBUTTON:
1201       style = CreateSubStyleWithClass(MOZ_GTK_RADIOBUTTON_CONTAINER,
1202                                       GTK_STYLE_CLASS_RADIO);
1203       break;
1204     case MOZ_GTK_CHECKBUTTON:
1205       style = CreateSubStyleWithClass(MOZ_GTK_CHECKBUTTON_CONTAINER,
1206                                       GTK_STYLE_CLASS_CHECK);
1207       break;
1208     case MOZ_GTK_RADIOMENUITEM_INDICATOR:
1209       style =
1210           CreateSubStyleWithClass(MOZ_GTK_RADIOMENUITEM, GTK_STYLE_CLASS_RADIO);
1211       break;
1212     case MOZ_GTK_CHECKMENUITEM_INDICATOR:
1213       style =
1214           CreateSubStyleWithClass(MOZ_GTK_CHECKMENUITEM, GTK_STYLE_CLASS_CHECK);
1215       break;
1216     case MOZ_GTK_PROGRESS_TROUGH:
1217       style =
1218           CreateSubStyleWithClass(MOZ_GTK_PROGRESSBAR, GTK_STYLE_CLASS_TROUGH);
1219       break;
1220     case MOZ_GTK_PROGRESS_CHUNK:
1221       style = CreateSubStyleWithClass(MOZ_GTK_PROGRESSBAR,
1222                                       GTK_STYLE_CLASS_PROGRESSBAR);
1223       gtk_style_context_remove_class(style, GTK_STYLE_CLASS_TROUGH);
1224       break;
1225     case MOZ_GTK_GRIPPER:
1226       style = CreateSubStyleWithClass(MOZ_GTK_GRIPPER, GTK_STYLE_CLASS_GRIP);
1227       break;
1228     case MOZ_GTK_SPINBUTTON_ENTRY:
1229       style =
1230           CreateSubStyleWithClass(MOZ_GTK_SPINBUTTON, GTK_STYLE_CLASS_ENTRY);
1231       break;
1232     case MOZ_GTK_SCROLLED_WINDOW:
1233       style = CreateSubStyleWithClass(MOZ_GTK_SCROLLED_WINDOW,
1234                                       GTK_STYLE_CLASS_FRAME);
1235       break;
1236     case MOZ_GTK_TEXT_VIEW_TEXT:
1237     case MOZ_GTK_RESIZER:
1238       // GTK versions prior to 3.20 do not have the view class on the root
1239       // node, but add this to determine the background for the text window.
1240       style = CreateSubStyleWithClass(MOZ_GTK_TEXT_VIEW, GTK_STYLE_CLASS_VIEW);
1241       if (aNodeType == MOZ_GTK_RESIZER) {
1242         // The "grip" class provides the correct builtin icon from
1243         // gtk_render_handle().  The icon is drawn with shaded variants of
1244         // the background color, and so a transparent background would lead to
1245         // a transparent resizer.  gtk_render_handle() also uses the
1246         // background color to draw a background, and so this style otherwise
1247         // matches MOZ_GTK_TEXT_VIEW_TEXT to match the background with
1248         // textarea elements.  GtkTextView creates a separate text window and
1249         // so the background should not be transparent.
1250         gtk_style_context_add_class(style, GTK_STYLE_CLASS_GRIP);
1251       }
1252       break;
1253     case MOZ_GTK_FRAME_BORDER:
1254       return GetWidgetRootStyle(MOZ_GTK_FRAME);
1255     case MOZ_GTK_TREEVIEW_VIEW:
1256       style = CreateSubStyleWithClass(MOZ_GTK_TREEVIEW, GTK_STYLE_CLASS_VIEW);
1257       break;
1258     case MOZ_GTK_TREEVIEW_EXPANDER:
1259       style =
1260           CreateSubStyleWithClass(MOZ_GTK_TREEVIEW, GTK_STYLE_CLASS_EXPANDER);
1261       break;
1262     case MOZ_GTK_SPLITTER_SEPARATOR_HORIZONTAL:
1263       style = CreateSubStyleWithClass(MOZ_GTK_SPLITTER_HORIZONTAL,
1264                                       GTK_STYLE_CLASS_PANE_SEPARATOR);
1265       break;
1266     case MOZ_GTK_SPLITTER_SEPARATOR_VERTICAL:
1267       style = CreateSubStyleWithClass(MOZ_GTK_SPLITTER_VERTICAL,
1268                                       GTK_STYLE_CLASS_PANE_SEPARATOR);
1269       break;
1270     case MOZ_GTK_SCALE_TROUGH_HORIZONTAL:
1271       style = CreateSubStyleWithClass(MOZ_GTK_SCALE_HORIZONTAL,
1272                                       GTK_STYLE_CLASS_TROUGH);
1273       break;
1274     case MOZ_GTK_SCALE_TROUGH_VERTICAL:
1275       style = CreateSubStyleWithClass(MOZ_GTK_SCALE_VERTICAL,
1276                                       GTK_STYLE_CLASS_TROUGH);
1277       break;
1278     case MOZ_GTK_SCALE_THUMB_HORIZONTAL:
1279       style = CreateSubStyleWithClass(MOZ_GTK_SCALE_HORIZONTAL,
1280                                       GTK_STYLE_CLASS_SLIDER);
1281       break;
1282     case MOZ_GTK_SCALE_THUMB_VERTICAL:
1283       style = CreateSubStyleWithClass(MOZ_GTK_SCALE_VERTICAL,
1284                                       GTK_STYLE_CLASS_SLIDER);
1285       break;
1286     case MOZ_GTK_TAB_TOP:
1287       style = CreateSubStyleWithClass(MOZ_GTK_NOTEBOOK, GTK_STYLE_CLASS_TOP);
1288       gtk_style_context_add_region(style, GTK_STYLE_REGION_TAB,
1289                                    static_cast<GtkRegionFlags>(0));
1290       break;
1291     case MOZ_GTK_TAB_BOTTOM:
1292       style = CreateSubStyleWithClass(MOZ_GTK_NOTEBOOK, GTK_STYLE_CLASS_BOTTOM);
1293       gtk_style_context_add_region(style, GTK_STYLE_REGION_TAB,
1294                                    static_cast<GtkRegionFlags>(0));
1295       break;
1296     case MOZ_GTK_NOTEBOOK:
1297     case MOZ_GTK_NOTEBOOK_HEADER:
1298     case MOZ_GTK_TABPANELS:
1299     case MOZ_GTK_TAB_SCROLLARROW: {
1300       GtkWidget* widget = GetWidget(MOZ_GTK_NOTEBOOK);
1301       return gtk_widget_get_style_context(widget);
1302     }
1303     default:
1304       return GetWidgetRootStyle(aNodeType);
1305   }
1306 
1307   MOZ_ASSERT(style);
1308   sStyleStorage[aNodeType] = style;
1309   return style;
1310 }
1311 
ResetWidgetCache(void)1312 void ResetWidgetCache(void) {
1313   for (int i = 0; i < MOZ_GTK_WIDGET_NODE_COUNT; i++) {
1314     if (sStyleStorage[i]) g_object_unref(sStyleStorage[i]);
1315   }
1316   mozilla::PodArrayZero(sStyleStorage);
1317 
1318   /* This will destroy all of our widgets */
1319   if (sWidgetStorage[MOZ_GTK_WINDOW]) {
1320     gtk_widget_destroy(sWidgetStorage[MOZ_GTK_WINDOW]);
1321   }
1322   if (sWidgetStorage[MOZ_GTK_HEADERBAR_WINDOW]) {
1323     gtk_widget_destroy(sWidgetStorage[MOZ_GTK_HEADERBAR_WINDOW]);
1324   }
1325   if (sWidgetStorage[MOZ_GTK_HEADERBAR_WINDOW_MAXIMIZED]) {
1326     gtk_widget_destroy(sWidgetStorage[MOZ_GTK_HEADERBAR_WINDOW_MAXIMIZED]);
1327   }
1328 
1329   /* Clear already freed arrays */
1330   mozilla::PodArrayZero(sWidgetStorage);
1331 }
1332 
GetStyleContext(WidgetNodeType aNodeType,int aScale,GtkTextDirection aDirection,GtkStateFlags aStateFlags)1333 GtkStyleContext* GetStyleContext(WidgetNodeType aNodeType, int aScale,
1334                                  GtkTextDirection aDirection,
1335                                  GtkStateFlags aStateFlags) {
1336   GtkStyleContext* style;
1337   if (gtk_check_version(3, 20, 0) != nullptr) {
1338     style = GetWidgetStyleInternal(aNodeType);
1339   } else {
1340     style = GetCssNodeStyleInternal(aNodeType);
1341     StyleContextSetScale(style, aScale);
1342   }
1343   bool stateChanged = false;
1344   bool stateHasDirection = gtk_get_minor_version() >= 8;
1345   GtkStateFlags oldState = gtk_style_context_get_state(style);
1346   MOZ_ASSERT(!(aStateFlags & (STATE_FLAG_DIR_LTR | STATE_FLAG_DIR_RTL)));
1347   unsigned newState = aStateFlags;
1348   if (stateHasDirection) {
1349     switch (aDirection) {
1350       case GTK_TEXT_DIR_LTR:
1351         newState |= STATE_FLAG_DIR_LTR;
1352         break;
1353       case GTK_TEXT_DIR_RTL:
1354         newState |= STATE_FLAG_DIR_RTL;
1355         break;
1356       default:
1357         MOZ_FALLTHROUGH_ASSERT("Bad GtkTextDirection");
1358       case GTK_TEXT_DIR_NONE:
1359         // GtkWidget uses a default direction if neither is explicitly
1360         // specified, but here DIR_NONE is interpreted as meaning the
1361         // direction is not important, so don't change the direction
1362         // unnecessarily.
1363         newState |= oldState & (STATE_FLAG_DIR_LTR | STATE_FLAG_DIR_RTL);
1364     }
1365   } else if (aDirection != GTK_TEXT_DIR_NONE) {
1366     GtkTextDirection oldDirection = gtk_style_context_get_direction(style);
1367     if (aDirection != oldDirection) {
1368       gtk_style_context_set_direction(style, aDirection);
1369       stateChanged = true;
1370     }
1371   }
1372   if (oldState != newState) {
1373     gtk_style_context_set_state(style, static_cast<GtkStateFlags>(newState));
1374     stateChanged = true;
1375   }
1376   // This invalidate is necessary for unsaved style contexts from GtkWidgets
1377   // in pre-3.18 GTK, because automatic invalidation of such contexts
1378   // was delayed until a resize event runs.
1379   //
1380   // https://bugzilla.mozilla.org/show_bug.cgi?id=1272194#c7
1381   //
1382   // Avoid calling invalidate on contexts that are not owned and constructed
1383   // by widgets to avoid performing build_properties() (in 3.16 stylecontext.c)
1384   // unnecessarily early.
1385   if (stateChanged && sWidgetStorage[aNodeType]) {
1386     gtk_style_context_invalidate(style);
1387   }
1388   return style;
1389 }
1390 
CreateStyleContextWithStates(WidgetNodeType aNodeType,int aScale,GtkTextDirection aDirection,GtkStateFlags aStateFlags)1391 GtkStyleContext* CreateStyleContextWithStates(WidgetNodeType aNodeType,
1392                                               int aScale,
1393                                               GtkTextDirection aDirection,
1394                                               GtkStateFlags aStateFlags) {
1395   GtkStyleContext* style =
1396       GetStyleContext(aNodeType, aScale, aDirection, aStateFlags);
1397   GtkWidgetPath* path = gtk_widget_path_copy(gtk_style_context_get_path(style));
1398 
1399   static auto sGtkWidgetPathIterGetState =
1400       (GtkStateFlags(*)(const GtkWidgetPath*, gint))dlsym(
1401           RTLD_DEFAULT, "gtk_widget_path_iter_get_state");
1402   static auto sGtkWidgetPathIterSetState =
1403       (void (*)(GtkWidgetPath*, gint, GtkStateFlags))dlsym(
1404           RTLD_DEFAULT, "gtk_widget_path_iter_set_state");
1405 
1406   int pathLength = gtk_widget_path_length(path);
1407   for (int i = 0; i < pathLength; i++) {
1408     unsigned state = aStateFlags | sGtkWidgetPathIterGetState(path, i);
1409     sGtkWidgetPathIterSetState(path, i, GtkStateFlags(state));
1410   }
1411 
1412   style = gtk_style_context_new();
1413   gtk_style_context_set_path(style, path);
1414   gtk_widget_path_unref(path);
1415 
1416   return style;
1417 }
1418 
StyleContextSetScale(GtkStyleContext * style,gint aScaleFactor)1419 void StyleContextSetScale(GtkStyleContext* style, gint aScaleFactor) {
1420   // Support HiDPI styles on Gtk 3.20+
1421   static auto sGtkStyleContextSetScalePtr =
1422       (void (*)(GtkStyleContext*, gint))dlsym(RTLD_DEFAULT,
1423                                               "gtk_style_context_set_scale");
1424   if (sGtkStyleContextSetScalePtr && style) {
1425     sGtkStyleContextSetScalePtr(style, aScaleFactor);
1426   }
1427 }
1428