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