no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD CLOSED TREE
[gecko.git] / widget / gtk / WidgetStyleCache.cpp
blob9a67096d525e64dd65e38734e2fcf716d1987f08
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* CreateGripperWidget() {
118 GtkWidget* widget = gtk_handle_box_new();
119 AddToWindowContainer(widget);
120 return widget;
123 static GtkWidget* CreateToolbarWidget() {
124 GtkWidget* widget = gtk_toolbar_new();
125 gtk_container_add(GTK_CONTAINER(GetWidget(MOZ_GTK_GRIPPER)), widget);
126 return widget;
129 static GtkWidget* CreateToolbarSeparatorWidget() {
130 GtkWidget* widget = GTK_WIDGET(gtk_separator_tool_item_new());
131 AddToWindowContainer(widget);
132 return widget;
135 static GtkWidget* CreateButtonWidget() {
136 GtkWidget* widget = gtk_button_new_with_label("M");
137 AddToWindowContainer(widget);
138 return widget;
141 static GtkWidget* CreateToggleButtonWidget() {
142 GtkWidget* widget = gtk_toggle_button_new();
143 AddToWindowContainer(widget);
144 return widget;
147 static GtkWidget* CreateButtonArrowWidget() {
148 GtkWidget* widget = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_OUT);
149 gtk_container_add(GTK_CONTAINER(GetWidget(MOZ_GTK_TOGGLE_BUTTON)), widget);
150 gtk_widget_show(widget);
151 return widget;
154 static GtkWidget* CreateSpinWidget() {
155 GtkWidget* widget = gtk_spin_button_new(nullptr, 1, 0);
156 AddToWindowContainer(widget);
157 return widget;
160 static GtkWidget* CreateEntryWidget() {
161 GtkWidget* widget = gtk_entry_new();
162 AddToWindowContainer(widget);
163 return widget;
166 static GtkWidget* CreateComboBoxWidget() {
167 GtkWidget* widget = gtk_combo_box_new();
168 AddToWindowContainer(widget);
169 return widget;
172 typedef struct {
173 GType type;
174 GtkWidget** widget;
175 } GtkInnerWidgetInfo;
177 static void GetInnerWidget(GtkWidget* widget, gpointer client_data) {
178 auto info = static_cast<GtkInnerWidgetInfo*>(client_data);
180 if (G_TYPE_CHECK_INSTANCE_TYPE(widget, info->type)) {
181 *info->widget = widget;
185 static GtkWidget* CreateComboBoxButtonWidget() {
186 GtkWidget* comboBox = GetWidget(MOZ_GTK_COMBOBOX);
187 GtkWidget* comboBoxButton = nullptr;
189 /* Get its inner Button */
190 GtkInnerWidgetInfo info = {GTK_TYPE_TOGGLE_BUTTON, &comboBoxButton};
191 gtk_container_forall(GTK_CONTAINER(comboBox), GetInnerWidget, &info);
193 if (!comboBoxButton) {
194 /* Shouldn't be reached with current internal gtk implementation; we
195 * use a generic toggle button as last resort fallback to avoid
196 * crashing. */
197 comboBoxButton = GetWidget(MOZ_GTK_TOGGLE_BUTTON);
198 } else {
199 /* We need to have pointers to the inner widgets (button, separator, arrow)
200 * of the ComboBox to get the correct rendering from theme engines which
201 * special cases their look. Since the inner layout can change, we ask GTK
202 * to NULL our pointers when they are about to become invalid because the
203 * corresponding widgets don't exist anymore. It's the role of
204 * g_object_add_weak_pointer().
205 * Note that if we don't find the inner widgets (which shouldn't happen), we
206 * fallback to use generic "non-inner" widgets, and they don't need that
207 * kind of weak pointer since they are explicit children of gProtoLayout and
208 * as such GTK holds a strong reference to them. */
209 g_object_add_weak_pointer(
210 G_OBJECT(comboBoxButton),
211 reinterpret_cast<gpointer*>(sWidgetStorage) + MOZ_GTK_COMBOBOX_BUTTON);
214 return comboBoxButton;
217 static GtkWidget* CreateComboBoxArrowWidget() {
218 GtkWidget* comboBoxButton = GetWidget(MOZ_GTK_COMBOBOX_BUTTON);
219 GtkWidget* comboBoxArrow = nullptr;
221 /* Get the widgets inside the Button */
222 GtkWidget* buttonChild = gtk_bin_get_child(GTK_BIN(comboBoxButton));
223 if (GTK_IS_BOX(buttonChild)) {
224 /* appears-as-list = FALSE, cell-view = TRUE; the button
225 * contains an hbox. This hbox is there because the ComboBox
226 * needs to place a cell renderer, a separator, and an arrow in
227 * the button when appears-as-list is FALSE. */
228 GtkInnerWidgetInfo info = {GTK_TYPE_ARROW, &comboBoxArrow};
229 gtk_container_forall(GTK_CONTAINER(buttonChild), GetInnerWidget, &info);
230 } else if (GTK_IS_ARROW(buttonChild)) {
231 /* appears-as-list = TRUE, or cell-view = FALSE;
232 * the button only contains an arrow */
233 comboBoxArrow = buttonChild;
236 if (!comboBoxArrow) {
237 /* Shouldn't be reached with current internal gtk implementation;
238 * we gButtonArrowWidget as last resort fallback to avoid
239 * crashing. */
240 comboBoxArrow = GetWidget(MOZ_GTK_BUTTON_ARROW);
241 } else {
242 g_object_add_weak_pointer(
243 G_OBJECT(comboBoxArrow),
244 reinterpret_cast<gpointer*>(sWidgetStorage) + MOZ_GTK_COMBOBOX_ARROW);
247 return comboBoxArrow;
250 static GtkWidget* CreateComboBoxSeparatorWidget() {
251 // Ensure to search for separator only once as it can fail
252 // TODO - it won't initialize after ResetWidgetCache() call
253 static bool isMissingSeparator = false;
254 if (isMissingSeparator) return nullptr;
256 /* Get the widgets inside the Button */
257 GtkWidget* comboBoxSeparator = nullptr;
258 GtkWidget* buttonChild =
259 gtk_bin_get_child(GTK_BIN(GetWidget(MOZ_GTK_COMBOBOX_BUTTON)));
260 if (GTK_IS_BOX(buttonChild)) {
261 /* appears-as-list = FALSE, cell-view = TRUE; the button
262 * contains an hbox. This hbox is there because the ComboBox
263 * needs to place a cell renderer, a separator, and an arrow in
264 * the button when appears-as-list is FALSE. */
265 GtkInnerWidgetInfo info = {GTK_TYPE_SEPARATOR, &comboBoxSeparator};
266 gtk_container_forall(GTK_CONTAINER(buttonChild), GetInnerWidget, &info);
269 if (comboBoxSeparator) {
270 g_object_add_weak_pointer(G_OBJECT(comboBoxSeparator),
271 reinterpret_cast<gpointer*>(sWidgetStorage) +
272 MOZ_GTK_COMBOBOX_SEPARATOR);
273 } else {
274 /* comboBoxSeparator may be NULL
275 * when "appears-as-list" = TRUE or "cell-view" = FALSE;
276 * if there is no separator, then we just won't paint it. */
277 isMissingSeparator = true;
280 return comboBoxSeparator;
283 static GtkWidget* CreateComboBoxEntryWidget() {
284 GtkWidget* widget = gtk_combo_box_new_with_entry();
285 AddToWindowContainer(widget);
286 return widget;
289 static GtkWidget* CreateComboBoxEntryTextareaWidget() {
290 GtkWidget* comboBoxTextarea = nullptr;
292 /* Get its inner Entry and Button */
293 GtkInnerWidgetInfo info = {GTK_TYPE_ENTRY, &comboBoxTextarea};
294 gtk_container_forall(GTK_CONTAINER(GetWidget(MOZ_GTK_COMBOBOX_ENTRY)),
295 GetInnerWidget, &info);
297 if (!comboBoxTextarea) {
298 comboBoxTextarea = GetWidget(MOZ_GTK_ENTRY);
299 } else {
300 g_object_add_weak_pointer(
301 G_OBJECT(comboBoxTextarea),
302 reinterpret_cast<gpointer*>(sWidgetStorage) + MOZ_GTK_COMBOBOX_ENTRY);
305 return comboBoxTextarea;
308 static GtkWidget* CreateComboBoxEntryButtonWidget() {
309 GtkWidget* comboBoxButton = nullptr;
311 /* Get its inner Entry and Button */
312 GtkInnerWidgetInfo info = {GTK_TYPE_TOGGLE_BUTTON, &comboBoxButton};
313 gtk_container_forall(GTK_CONTAINER(GetWidget(MOZ_GTK_COMBOBOX_ENTRY)),
314 GetInnerWidget, &info);
316 if (!comboBoxButton) {
317 comboBoxButton = GetWidget(MOZ_GTK_TOGGLE_BUTTON);
318 } else {
319 g_object_add_weak_pointer(G_OBJECT(comboBoxButton),
320 reinterpret_cast<gpointer*>(sWidgetStorage) +
321 MOZ_GTK_COMBOBOX_ENTRY_BUTTON);
324 return comboBoxButton;
327 static GtkWidget* CreateComboBoxEntryArrowWidget() {
328 GtkWidget* comboBoxArrow = nullptr;
330 /* Get the Arrow inside the Button */
331 GtkWidget* buttonChild =
332 gtk_bin_get_child(GTK_BIN(GetWidget(MOZ_GTK_COMBOBOX_ENTRY_BUTTON)));
334 if (GTK_IS_BOX(buttonChild)) {
335 /* appears-as-list = FALSE, cell-view = TRUE; the button
336 * contains an hbox. This hbox is there because the ComboBox
337 * needs to place a cell renderer, a separator, and an arrow in
338 * the button when appears-as-list is FALSE. */
339 GtkInnerWidgetInfo info = {GTK_TYPE_ARROW, &comboBoxArrow};
340 gtk_container_forall(GTK_CONTAINER(buttonChild), GetInnerWidget, &info);
341 } else if (GTK_IS_ARROW(buttonChild)) {
342 /* appears-as-list = TRUE, or cell-view = FALSE;
343 * the button only contains an arrow */
344 comboBoxArrow = buttonChild;
347 if (!comboBoxArrow) {
348 /* Shouldn't be reached with current internal gtk implementation;
349 * we gButtonArrowWidget as last resort fallback to avoid
350 * crashing. */
351 comboBoxArrow = GetWidget(MOZ_GTK_BUTTON_ARROW);
352 } else {
353 g_object_add_weak_pointer(G_OBJECT(comboBoxArrow),
354 reinterpret_cast<gpointer*>(sWidgetStorage) +
355 MOZ_GTK_COMBOBOX_ENTRY_ARROW);
358 return comboBoxArrow;
361 static GtkWidget* CreateScrolledWindowWidget() {
362 GtkWidget* widget = gtk_scrolled_window_new(nullptr, nullptr);
363 AddToWindowContainer(widget);
364 return widget;
367 static GtkWidget* CreateTreeViewWidget() {
368 GtkWidget* widget = gtk_tree_view_new();
369 AddToWindowContainer(widget);
370 return widget;
373 static GtkWidget* CreateTreeHeaderCellWidget() {
375 * Some GTK engines paint the first and last cell
376 * of a TreeView header with a highlight.
377 * Since we do not know where our widget will be relative
378 * to the other buttons in the TreeView header, we must
379 * paint it as a button that is between two others,
380 * thus ensuring it is neither the first or last button
381 * in the header.
382 * GTK doesn't give us a way to do this explicitly,
383 * so we must paint with a button that is between two
384 * others.
386 GtkTreeViewColumn* firstTreeViewColumn;
387 GtkTreeViewColumn* middleTreeViewColumn;
388 GtkTreeViewColumn* lastTreeViewColumn;
390 GtkWidget* treeView = GetWidget(MOZ_GTK_TREEVIEW);
392 /* Create and append our three columns */
393 firstTreeViewColumn = gtk_tree_view_column_new();
394 gtk_tree_view_column_set_title(firstTreeViewColumn, "M");
395 gtk_tree_view_append_column(GTK_TREE_VIEW(treeView), firstTreeViewColumn);
397 middleTreeViewColumn = gtk_tree_view_column_new();
398 gtk_tree_view_column_set_title(middleTreeViewColumn, "M");
399 gtk_tree_view_append_column(GTK_TREE_VIEW(treeView), middleTreeViewColumn);
401 lastTreeViewColumn = gtk_tree_view_column_new();
402 gtk_tree_view_column_set_title(lastTreeViewColumn, "M");
403 gtk_tree_view_append_column(GTK_TREE_VIEW(treeView), lastTreeViewColumn);
405 /* Use the middle column's header for our button */
406 return gtk_tree_view_column_get_button(middleTreeViewColumn);
409 static GtkWidget* CreateTreeHeaderSortArrowWidget() {
410 /* TODO, but it can't be NULL */
411 GtkWidget* widget = gtk_button_new();
412 AddToWindowContainer(widget);
413 return widget;
416 static GtkWidget* CreateHPanedWidget() {
417 GtkWidget* widget = gtk_paned_new(GTK_ORIENTATION_HORIZONTAL);
418 AddToWindowContainer(widget);
419 return widget;
422 static GtkWidget* CreateVPanedWidget() {
423 GtkWidget* widget = gtk_paned_new(GTK_ORIENTATION_VERTICAL);
424 AddToWindowContainer(widget);
425 return widget;
428 static GtkWidget* CreateScaleWidget(GtkOrientation aOrientation) {
429 GtkWidget* widget = gtk_scale_new(aOrientation, nullptr);
430 AddToWindowContainer(widget);
431 return widget;
434 static GtkWidget* CreateNotebookWidget() {
435 GtkWidget* widget = gtk_notebook_new();
436 AddToWindowContainer(widget);
437 return widget;
440 static bool HasBackground(GtkStyleContext* aStyle) {
441 GdkRGBA gdkColor;
442 gtk_style_context_get_background_color(aStyle, GTK_STATE_FLAG_NORMAL,
443 &gdkColor);
444 if (gdkColor.alpha != 0.0) {
445 return true;
448 GValue value = G_VALUE_INIT;
449 gtk_style_context_get_property(aStyle, "background-image",
450 GTK_STATE_FLAG_NORMAL, &value);
451 auto cleanup = mozilla::MakeScopeExit([&] { g_value_unset(&value); });
452 return g_value_get_boxed(&value);
455 static void CreateHeaderBarWidget(WidgetNodeType aAppearance) {
456 GtkWidget* window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
457 GtkStyleContext* windowStyle = gtk_widget_get_style_context(window);
459 // Headerbar has to be placed to window with csd or solid-csd style
460 // to properly draw the decorated.
461 gtk_style_context_add_class(windowStyle,
462 IsSolidCSDStyleUsed() ? "solid-csd" : "csd");
464 GtkWidget* fixed = gtk_fixed_new();
465 GtkStyleContext* fixedStyle = gtk_widget_get_style_context(fixed);
466 gtk_style_context_add_class(fixedStyle, "titlebar");
468 GtkWidget* headerBar = gtk_header_bar_new();
470 // Emulate what create_titlebar() at gtkwindow.c does.
471 GtkStyleContext* headerBarStyle = gtk_widget_get_style_context(headerBar);
472 gtk_style_context_add_class(headerBarStyle, "titlebar");
474 // TODO: Define default-decoration titlebar style as workaround
475 // to ensure the titlebar buttons does not overflow outside.
476 // Recently the titlebar size is calculated as
477 // tab size + titlebar border/padding (default-decoration has 6px padding
478 // at default Adwaita theme).
479 // We need to fix titlebar size calculation to also include
480 // titlebar button sizes. (Bug 1419442)
481 gtk_style_context_add_class(headerBarStyle, "default-decoration");
483 sWidgetStorage[aAppearance] = headerBar;
484 if (aAppearance == MOZ_GTK_HEADER_BAR_MAXIMIZED) {
485 MOZ_ASSERT(!sWidgetStorage[MOZ_GTK_HEADERBAR_WINDOW_MAXIMIZED],
486 "Window widget is already created!");
487 MOZ_ASSERT(!sWidgetStorage[MOZ_GTK_HEADERBAR_FIXED_MAXIMIZED],
488 "Fixed widget is already created!");
490 gtk_style_context_add_class(windowStyle, "maximized");
492 sWidgetStorage[MOZ_GTK_HEADERBAR_WINDOW_MAXIMIZED] = window;
493 sWidgetStorage[MOZ_GTK_HEADERBAR_FIXED_MAXIMIZED] = fixed;
494 } else {
495 MOZ_ASSERT(!sWidgetStorage[MOZ_GTK_HEADERBAR_WINDOW],
496 "Window widget is already created!");
497 MOZ_ASSERT(!sWidgetStorage[MOZ_GTK_HEADERBAR_FIXED],
498 "Fixed widget is already created!");
499 sWidgetStorage[MOZ_GTK_HEADERBAR_WINDOW] = window;
500 sWidgetStorage[MOZ_GTK_HEADERBAR_FIXED] = fixed;
503 gtk_container_add(GTK_CONTAINER(window), fixed);
504 gtk_container_add(GTK_CONTAINER(fixed), headerBar);
506 gtk_style_context_invalidate(headerBarStyle);
507 gtk_style_context_invalidate(fixedStyle);
509 // Some themes like Elementary's style the container of the headerbar rather
510 // than the header bar itself.
511 bool& shouldDrawContainer = aAppearance == MOZ_GTK_HEADER_BAR
512 ? gHeaderBarShouldDrawContainer
513 : gMaximizedHeaderBarShouldDrawContainer;
514 shouldDrawContainer = [&] {
515 const bool headerBarHasBackground = HasBackground(headerBarStyle);
516 if (headerBarHasBackground && GetBorderRadius(headerBarStyle)) {
517 return false;
519 if (HasBackground(fixedStyle) &&
520 (GetBorderRadius(fixedStyle) || !headerBarHasBackground)) {
521 return true;
523 return false;
524 }();
527 #define ICON_SCALE_VARIANTS 2
529 static void LoadWidgetIconPixbuf(GtkWidget* aWidgetIcon) {
530 GtkStyleContext* style = gtk_widget_get_style_context(aWidgetIcon);
532 const gchar* iconName;
533 GtkIconSize gtkIconSize;
534 gtk_image_get_icon_name(GTK_IMAGE(aWidgetIcon), &iconName, &gtkIconSize);
536 gint iconWidth, iconHeight;
537 gtk_icon_size_lookup(gtkIconSize, &iconWidth, &iconHeight);
539 /* Those are available since Gtk+ 3.10 as well as GtkHeaderBar */
540 for (int scale = 1; scale < ICON_SCALE_VARIANTS + 1; scale++) {
541 GtkIconInfo* gtkIconInfo = gtk_icon_theme_lookup_icon_for_scale(
542 gtk_icon_theme_get_default(), iconName, iconWidth, scale,
543 (GtkIconLookupFlags)0);
545 if (!gtkIconInfo) {
546 // We miss the icon, nothing to do here.
547 return;
550 gboolean unused;
551 GdkPixbuf* iconPixbuf = gtk_icon_info_load_symbolic_for_context(
552 gtkIconInfo, style, &unused, nullptr);
553 g_object_unref(G_OBJECT(gtkIconInfo));
555 cairo_surface_t* iconSurface =
556 gdk_cairo_surface_create_from_pixbuf(iconPixbuf, scale, nullptr);
557 g_object_unref(iconPixbuf);
559 nsPrintfCString surfaceName("MozillaIconSurface%d", scale);
560 g_object_set_data_full(G_OBJECT(aWidgetIcon), surfaceName.get(),
561 iconSurface, (GDestroyNotify)cairo_surface_destroy);
565 cairo_surface_t* GetWidgetIconSurface(GtkWidget* aWidgetIcon, int aScale) {
566 if (aScale > ICON_SCALE_VARIANTS) {
567 aScale = ICON_SCALE_VARIANTS;
570 nsPrintfCString surfaceName("MozillaIconSurface%d", aScale);
571 return (cairo_surface_t*)g_object_get_data(G_OBJECT(aWidgetIcon),
572 surfaceName.get());
575 static void CreateHeaderBarButton(GtkWidget* aParentWidget,
576 WidgetNodeType aAppearance) {
577 GtkWidget* widget = gtk_button_new();
579 // We have to add button to widget hierarchy now to pick
580 // right icon style at LoadWidgetIconPixbuf().
581 if (GTK_IS_BOX(aParentWidget)) {
582 gtk_box_pack_start(GTK_BOX(aParentWidget), widget, FALSE, FALSE, 0);
583 } else {
584 gtk_container_add(GTK_CONTAINER(aParentWidget), widget);
587 // We bypass GetWidget() here because we create all titlebar
588 // buttons at once when a first one is requested.
589 NS_ASSERTION(!sWidgetStorage[aAppearance],
590 "Titlebar button is already created!");
591 sWidgetStorage[aAppearance] = widget;
593 // We need to show the button widget now as GtkBox does not
594 // place invisible widgets and we'll miss first-child/last-child
595 // css selectors at the buttons otherwise.
596 gtk_widget_show(widget);
598 GtkStyleContext* style = gtk_widget_get_style_context(widget);
599 gtk_style_context_add_class(style, "titlebutton");
601 GtkWidget* image = nullptr;
602 switch (aAppearance) {
603 case MOZ_GTK_HEADER_BAR_BUTTON_CLOSE:
604 gtk_style_context_add_class(style, "close");
605 image = gtk_image_new_from_icon_name("window-close-symbolic",
606 GTK_ICON_SIZE_MENU);
607 break;
608 case MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE:
609 gtk_style_context_add_class(style, "minimize");
610 image = gtk_image_new_from_icon_name("window-minimize-symbolic",
611 GTK_ICON_SIZE_MENU);
612 break;
614 case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE:
615 gtk_style_context_add_class(style, "maximize");
616 image = gtk_image_new_from_icon_name("window-maximize-symbolic",
617 GTK_ICON_SIZE_MENU);
618 break;
620 case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE_RESTORE:
621 gtk_style_context_add_class(style, "maximize");
622 image = gtk_image_new_from_icon_name("window-restore-symbolic",
623 GTK_ICON_SIZE_MENU);
624 break;
625 default:
626 break;
629 gtk_widget_set_valign(widget, GTK_ALIGN_CENTER);
630 g_object_set(image, "use-fallback", TRUE, NULL);
631 gtk_container_add(GTK_CONTAINER(widget), image);
633 // We bypass GetWidget() here by explicit sWidgetStorage[] update so
634 // invalidate the style as well as GetWidget() does.
635 style = gtk_widget_get_style_context(image);
636 gtk_style_context_invalidate(style);
638 LoadWidgetIconPixbuf(image);
641 static bool IsToolbarButtonEnabled(ButtonLayout* aButtonLayout,
642 size_t aButtonNums,
643 WidgetNodeType aAppearance) {
644 for (size_t i = 0; i < aButtonNums; i++) {
645 if (aButtonLayout[i].mType == aAppearance) {
646 return true;
649 return false;
652 bool IsSolidCSDStyleUsed() {
653 if (gCSDStyle == CSDStyle::Unknown) {
654 bool solid;
656 GtkWidget* window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
657 gtk_window_set_titlebar(GTK_WINDOW(window), gtk_header_bar_new());
658 gtk_widget_realize(window);
659 GtkStyleContext* windowStyle = gtk_widget_get_style_context(window);
660 solid = gtk_style_context_has_class(windowStyle, "solid-csd");
661 gtk_widget_destroy(window);
663 gCSDStyle = solid ? CSDStyle::Solid : CSDStyle::Normal;
665 return gCSDStyle == CSDStyle::Solid;
668 static void CreateHeaderBarButtons() {
669 GtkWidget* headerBar = sWidgetStorage[MOZ_GTK_HEADER_BAR];
670 MOZ_ASSERT(headerBar, "We're missing header bar widget!");
672 gint buttonSpacing = 6;
673 g_object_get(headerBar, "spacing", &buttonSpacing, nullptr);
675 GtkWidget* buttonBox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, buttonSpacing);
676 gtk_container_add(GTK_CONTAINER(headerBar), buttonBox);
677 // We support only LTR headerbar layout for now.
678 gtk_style_context_add_class(gtk_widget_get_style_context(buttonBox),
679 GTK_STYLE_CLASS_LEFT);
681 ButtonLayout buttonLayout[TOOLBAR_BUTTONS];
683 size_t activeButtons =
684 GetGtkHeaderBarButtonLayout(mozilla::Span(buttonLayout), nullptr);
686 if (IsToolbarButtonEnabled(buttonLayout, activeButtons,
687 MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE)) {
688 CreateHeaderBarButton(buttonBox, MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE);
690 if (IsToolbarButtonEnabled(buttonLayout, activeButtons,
691 MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE)) {
692 CreateHeaderBarButton(buttonBox, MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE);
693 // We don't pack "restore" headerbar button to box as it's an icon
694 // placeholder. Pack it only to header bar to get correct style.
695 CreateHeaderBarButton(GetWidget(MOZ_GTK_HEADER_BAR_MAXIMIZED),
696 MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE_RESTORE);
698 if (IsToolbarButtonEnabled(buttonLayout, activeButtons,
699 MOZ_GTK_HEADER_BAR_BUTTON_CLOSE)) {
700 CreateHeaderBarButton(buttonBox, MOZ_GTK_HEADER_BAR_BUTTON_CLOSE);
704 static void CreateHeaderBar() {
705 CreateHeaderBarWidget(MOZ_GTK_HEADER_BAR);
706 CreateHeaderBarWidget(MOZ_GTK_HEADER_BAR_MAXIMIZED);
707 CreateHeaderBarButtons();
710 static GtkWidget* CreateWidget(WidgetNodeType aAppearance) {
711 switch (aAppearance) {
712 case MOZ_GTK_WINDOW:
713 return CreateWindowWidget();
714 case MOZ_GTK_WINDOW_CONTAINER:
715 return CreateWindowContainerWidget();
716 case MOZ_GTK_CHECKBUTTON_CONTAINER:
717 return CreateCheckboxWidget();
718 case MOZ_GTK_PROGRESSBAR:
719 return CreateProgressWidget();
720 case MOZ_GTK_RADIOBUTTON_CONTAINER:
721 return CreateRadiobuttonWidget();
722 case MOZ_GTK_SCROLLBAR_VERTICAL:
723 return CreateScrollbarWidget(aAppearance, GTK_ORIENTATION_VERTICAL);
724 case MOZ_GTK_MENUPOPUP:
725 return CreateMenuPopupWidget();
726 case MOZ_GTK_MENUBAR:
727 return CreateMenuBarWidget();
728 case MOZ_GTK_EXPANDER:
729 return CreateExpanderWidget();
730 case MOZ_GTK_FRAME:
731 return CreateFrameWidget();
732 case MOZ_GTK_GRIPPER:
733 return CreateGripperWidget();
734 case MOZ_GTK_TOOLBAR:
735 return CreateToolbarWidget();
736 case MOZ_GTK_TOOLBAR_SEPARATOR:
737 return CreateToolbarSeparatorWidget();
738 case MOZ_GTK_SPINBUTTON:
739 return CreateSpinWidget();
740 case MOZ_GTK_BUTTON:
741 return CreateButtonWidget();
742 case MOZ_GTK_TOGGLE_BUTTON:
743 return CreateToggleButtonWidget();
744 case MOZ_GTK_BUTTON_ARROW:
745 return CreateButtonArrowWidget();
746 case MOZ_GTK_ENTRY:
747 case MOZ_GTK_DROPDOWN_ENTRY:
748 return CreateEntryWidget();
749 case MOZ_GTK_SCROLLED_WINDOW:
750 return CreateScrolledWindowWidget();
751 case MOZ_GTK_TREEVIEW:
752 return CreateTreeViewWidget();
753 case MOZ_GTK_TREE_HEADER_CELL:
754 return CreateTreeHeaderCellWidget();
755 case MOZ_GTK_TREE_HEADER_SORTARROW:
756 return CreateTreeHeaderSortArrowWidget();
757 case MOZ_GTK_SPLITTER_HORIZONTAL:
758 return CreateHPanedWidget();
759 case MOZ_GTK_SPLITTER_VERTICAL:
760 return CreateVPanedWidget();
761 case MOZ_GTK_SCALE_HORIZONTAL:
762 return CreateScaleWidget(GTK_ORIENTATION_HORIZONTAL);
763 case MOZ_GTK_SCALE_VERTICAL:
764 return CreateScaleWidget(GTK_ORIENTATION_VERTICAL);
765 case MOZ_GTK_NOTEBOOK:
766 return CreateNotebookWidget();
767 case MOZ_GTK_COMBOBOX:
768 return CreateComboBoxWidget();
769 case MOZ_GTK_COMBOBOX_BUTTON:
770 return CreateComboBoxButtonWidget();
771 case MOZ_GTK_COMBOBOX_ARROW:
772 return CreateComboBoxArrowWidget();
773 case MOZ_GTK_COMBOBOX_SEPARATOR:
774 return CreateComboBoxSeparatorWidget();
775 case MOZ_GTK_COMBOBOX_ENTRY:
776 return CreateComboBoxEntryWidget();
777 case MOZ_GTK_COMBOBOX_ENTRY_TEXTAREA:
778 return CreateComboBoxEntryTextareaWidget();
779 case MOZ_GTK_COMBOBOX_ENTRY_BUTTON:
780 return CreateComboBoxEntryButtonWidget();
781 case MOZ_GTK_COMBOBOX_ENTRY_ARROW:
782 return CreateComboBoxEntryArrowWidget();
783 case MOZ_GTK_HEADERBAR_WINDOW:
784 case MOZ_GTK_HEADERBAR_WINDOW_MAXIMIZED:
785 case MOZ_GTK_HEADERBAR_FIXED:
786 case MOZ_GTK_HEADERBAR_FIXED_MAXIMIZED:
787 case MOZ_GTK_HEADER_BAR:
788 case MOZ_GTK_HEADER_BAR_MAXIMIZED:
789 case MOZ_GTK_HEADER_BAR_BUTTON_CLOSE:
790 case MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE:
791 case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE:
792 case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE_RESTORE:
793 /* Create header bar widgets once and fill with child elements as we need
794 the header bar fully configured to get a correct style */
795 CreateHeaderBar();
796 return sWidgetStorage[aAppearance];
797 default:
798 /* Not implemented */
799 return nullptr;
803 GtkWidget* GetWidget(WidgetNodeType aAppearance) {
804 GtkWidget* widget = sWidgetStorage[aAppearance];
805 if (!widget) {
806 widget = CreateWidget(aAppearance);
807 // Some widgets (MOZ_GTK_COMBOBOX_SEPARATOR for instance) may not be
808 // available or implemented.
809 if (!widget) {
810 return nullptr;
812 // In GTK versions prior to 3.18, automatic invalidation of style contexts
813 // for widgets was delayed until the next resize event. Gecko however,
814 // typically uses the style context before the resize event runs and so an
815 // explicit invalidation may be required. This is necessary if a style
816 // property was retrieved before all changes were made to the style
817 // context. One such situation is where gtk_button_construct_child()
818 // retrieves the style property "image-spacing" during construction of the
819 // GtkButton, before its parent is set to provide inheritance of ancestor
820 // properties. More recent GTK versions do not need this, but do not
821 // re-resolve until required and so invalidation does not trigger
822 // unnecessary resolution in general.
823 GtkStyleContext* style = gtk_widget_get_style_context(widget);
824 gtk_style_context_invalidate(style);
826 sWidgetStorage[aAppearance] = widget;
828 return widget;
831 static void AddStyleClassesFromStyle(GtkStyleContext* aDest,
832 GtkStyleContext* aSrc) {
833 GList* classes = gtk_style_context_list_classes(aSrc);
834 for (GList* link = classes; link; link = link->next) {
835 gtk_style_context_add_class(aDest, static_cast<gchar*>(link->data));
837 g_list_free(classes);
840 GtkStyleContext* CreateStyleForWidget(GtkWidget* aWidget,
841 GtkStyleContext* aParentStyle) {
842 static auto sGtkWidgetClassGetCSSName =
843 reinterpret_cast<const char* (*)(GtkWidgetClass*)>(
844 dlsym(RTLD_DEFAULT, "gtk_widget_class_get_css_name"));
846 GtkWidgetClass* widgetClass = GTK_WIDGET_GET_CLASS(aWidget);
847 const gchar* name = sGtkWidgetClassGetCSSName
848 ? sGtkWidgetClassGetCSSName(widgetClass)
849 : nullptr;
851 GtkStyleContext* context =
852 CreateCSSNode(name, aParentStyle, G_TYPE_FROM_CLASS(widgetClass));
854 // Classes are stored on the style context instead of the path so that any
855 // future gtk_style_context_save() will inherit classes on the head CSS
856 // node, in the same way as happens when called on a style context owned by
857 // a widget.
859 // Classes can be stored on a GtkCssNodeDeclaration and/or the path.
860 // gtk_style_context_save() reuses the GtkCssNodeDeclaration, and appends a
861 // new object to the path, without copying the classes from the old path
862 // head. The new head picks up classes from the GtkCssNodeDeclaration, but
863 // not the path. GtkWidgets store their classes on the
864 // GtkCssNodeDeclaration, so make sure to add classes there.
866 // Picking up classes from the style context also means that
867 // https://bugzilla.gnome.org/show_bug.cgi?id=767312, which can stop
868 // gtk_widget_path_append_for_widget() from finding classes in GTK 3.20,
869 // is not a problem.
870 GtkStyleContext* widgetStyle = gtk_widget_get_style_context(aWidget);
871 AddStyleClassesFromStyle(context, widgetStyle);
873 // Release any floating reference on aWidget.
874 g_object_ref_sink(aWidget);
875 g_object_unref(aWidget);
877 return context;
880 static GtkStyleContext* CreateStyleForWidget(GtkWidget* aWidget,
881 WidgetNodeType aParentType) {
882 return CreateStyleForWidget(aWidget, GetWidgetRootStyle(aParentType));
885 GtkStyleContext* CreateCSSNode(const char* aName, GtkStyleContext* aParentStyle,
886 GType aType) {
887 static auto sGtkWidgetPathIterSetObjectName =
888 reinterpret_cast<void (*)(GtkWidgetPath*, gint, const char*)>(
889 dlsym(RTLD_DEFAULT, "gtk_widget_path_iter_set_object_name"));
891 GtkWidgetPath* path;
892 if (aParentStyle) {
893 path = gtk_widget_path_copy(gtk_style_context_get_path(aParentStyle));
894 // Copy classes from the parent style context to its corresponding node in
895 // the path, because GTK will only match against ancestor classes if they
896 // are on the path.
897 GList* classes = gtk_style_context_list_classes(aParentStyle);
898 for (GList* link = classes; link; link = link->next) {
899 gtk_widget_path_iter_add_class(path, -1, static_cast<gchar*>(link->data));
901 g_list_free(classes);
902 } else {
903 path = gtk_widget_path_new();
906 gtk_widget_path_append_type(path, aType);
908 if (sGtkWidgetPathIterSetObjectName) {
909 (*sGtkWidgetPathIterSetObjectName)(path, -1, aName);
912 GtkStyleContext* context = gtk_style_context_new();
913 gtk_style_context_set_path(context, path);
914 gtk_style_context_set_parent(context, aParentStyle);
915 gtk_widget_path_unref(path);
917 // In GTK 3.4, gtk_render_* functions use |theming_engine| on the style
918 // context without ensuring any style resolution sets it appropriately
919 // in style_data_lookup(). e.g.
920 // https://git.gnome.org/browse/gtk+/tree/gtk/gtkstylecontext.c?h=3.4.4#n3847
922 // That can result in incorrect drawing on first draw. To work around this,
923 // force a style look-up to set |theming_engine|. It is sufficient to do
924 // this only on context creation, instead of after every modification to the
925 // context, because themes typically (Ambiance and oxygen-gtk, at least) set
926 // the "engine" property with the '*' selector.
927 if (GTK_MAJOR_VERSION == 3 && gtk_get_minor_version() < 6) {
928 GdkRGBA unused;
929 gtk_style_context_get_color(context, GTK_STATE_FLAG_NORMAL, &unused);
932 return context;
935 // Return a style context matching that of the root CSS node of a widget.
936 // This is used by all GTK versions.
937 static GtkStyleContext* GetWidgetRootStyle(WidgetNodeType aNodeType) {
938 GtkStyleContext* style = sStyleStorage[aNodeType];
939 if (style) return style;
941 switch (aNodeType) {
942 case MOZ_GTK_MENUITEM:
943 style = CreateStyleForWidget(gtk_menu_item_new(), MOZ_GTK_MENUPOPUP);
944 break;
945 case MOZ_GTK_MENUBARITEM:
946 style = CreateStyleForWidget(gtk_menu_item_new(), MOZ_GTK_MENUBAR);
947 break;
948 case MOZ_GTK_TEXT_VIEW:
949 style =
950 CreateStyleForWidget(gtk_text_view_new(), MOZ_GTK_SCROLLED_WINDOW);
951 break;
952 case MOZ_GTK_TOOLTIP:
953 if (gtk_check_version(3, 20, 0) != nullptr) {
954 // The tooltip style class is added first in CreateTooltipWidget()
955 // and transfered to style in CreateStyleForWidget().
956 GtkWidget* tooltipWindow = CreateTooltipWidget();
957 style = CreateStyleForWidget(tooltipWindow, nullptr);
958 gtk_widget_destroy(tooltipWindow); // Release GtkWindow self-reference.
959 } else {
960 // We create this from the path because GtkTooltipWindow is not public.
961 style = CreateCSSNode("tooltip", nullptr, GTK_TYPE_TOOLTIP);
962 gtk_style_context_add_class(style, GTK_STYLE_CLASS_BACKGROUND);
964 break;
965 case MOZ_GTK_TOOLTIP_BOX:
966 style = CreateStyleForWidget(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0),
967 MOZ_GTK_TOOLTIP);
968 break;
969 case MOZ_GTK_TOOLTIP_BOX_LABEL:
970 style = CreateStyleForWidget(gtk_label_new(nullptr), MOZ_GTK_TOOLTIP_BOX);
971 break;
972 default:
973 GtkWidget* widget = GetWidget(aNodeType);
974 MOZ_ASSERT(widget);
975 return gtk_widget_get_style_context(widget);
978 MOZ_ASSERT(style);
979 sStyleStorage[aNodeType] = style;
980 return style;
983 static GtkStyleContext* CreateChildCSSNode(const char* aName,
984 WidgetNodeType aParentNodeType) {
985 return CreateCSSNode(aName, GetCssNodeStyleInternal(aParentNodeType));
988 // Create a style context equivalent to a saved root style context of
989 // |aAppearance| with |aStyleClass| as an additional class. This is used to
990 // produce a context equivalent to what GTK versions < 3.20 use for many
991 // internal parts of widgets.
992 static GtkStyleContext* CreateSubStyleWithClass(WidgetNodeType aAppearance,
993 const gchar* aStyleClass) {
994 static auto sGtkWidgetPathIterGetObjectName =
995 reinterpret_cast<const char* (*)(const GtkWidgetPath*, gint)>(
996 dlsym(RTLD_DEFAULT, "gtk_widget_path_iter_get_object_name"));
998 GtkStyleContext* parentStyle = GetWidgetRootStyle(aAppearance);
1000 // Create a new context that behaves like |parentStyle| would after
1001 // gtk_style_context_save(parentStyle).
1003 // Avoiding gtk_style_context_save() avoids the need to manage the
1004 // restore, and a new context permits caching style resolution.
1006 // gtk_style_context_save(context) changes the node hierarchy of |context|
1007 // to add a new GtkCssNodeDeclaration that is a copy of its original node.
1008 // The new node is a child of the original node, and so the new heirarchy is
1009 // one level deeper. The new node receives the same classes as the
1010 // original, but any changes to the classes on |context| will change only
1011 // the new node. The new node inherits properties from the original node
1012 // (which retains the original heirarchy and classes) and matches CSS rules
1013 // with the new heirarchy and any changes to the classes.
1015 // The change in hierarchy can produce some surprises in matching theme CSS
1016 // rules (e.g. https://bugzilla.gnome.org/show_bug.cgi?id=761870#c2), but it
1017 // is important here to produce the same behavior so that rules match the
1018 // same widget parts in Gecko as they do in GTK.
1020 // When using public GTK API to construct style contexts, a widget path is
1021 // required. CSS rules are not matched against the style context heirarchy
1022 // but according to the heirarchy in the widget path. The path that matches
1023 // the same CSS rules as a saved context is like the path of |parentStyle|
1024 // but with an extra copy of the head (last) object appended. Setting
1025 // |parentStyle| as the parent context provides the same inheritance of
1026 // properties from the widget root node.
1027 const GtkWidgetPath* parentPath = gtk_style_context_get_path(parentStyle);
1028 const gchar* name = sGtkWidgetPathIterGetObjectName
1029 ? sGtkWidgetPathIterGetObjectName(parentPath, -1)
1030 : nullptr;
1031 GType objectType = gtk_widget_path_get_object_type(parentPath);
1033 GtkStyleContext* style = CreateCSSNode(name, parentStyle, objectType);
1035 // Start with the same classes on the new node as were on |parentStyle|.
1036 // GTK puts no regions or junction_sides on widget root nodes, and so there
1037 // is no need to copy these.
1038 AddStyleClassesFromStyle(style, parentStyle);
1040 gtk_style_context_add_class(style, aStyleClass);
1041 return style;
1044 /* GetCssNodeStyleInternal is used by Gtk >= 3.20 */
1045 static GtkStyleContext* GetCssNodeStyleInternal(WidgetNodeType aNodeType) {
1046 GtkStyleContext* style = sStyleStorage[aNodeType];
1047 if (style) return style;
1049 switch (aNodeType) {
1050 case MOZ_GTK_SCROLLBAR_CONTENTS_VERTICAL:
1051 style = CreateChildCSSNode("contents", MOZ_GTK_SCROLLBAR_VERTICAL);
1052 break;
1053 case MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL:
1054 style = CreateChildCSSNode(GTK_STYLE_CLASS_TROUGH,
1055 MOZ_GTK_SCROLLBAR_CONTENTS_VERTICAL);
1056 break;
1057 case MOZ_GTK_SCROLLBAR_THUMB_VERTICAL:
1058 style = CreateChildCSSNode(GTK_STYLE_CLASS_SLIDER,
1059 MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL);
1060 break;
1061 case MOZ_GTK_RADIOBUTTON:
1062 style = CreateChildCSSNode(GTK_STYLE_CLASS_RADIO,
1063 MOZ_GTK_RADIOBUTTON_CONTAINER);
1064 break;
1065 case MOZ_GTK_CHECKBUTTON:
1066 style = CreateChildCSSNode(GTK_STYLE_CLASS_CHECK,
1067 MOZ_GTK_CHECKBUTTON_CONTAINER);
1068 break;
1069 case MOZ_GTK_PROGRESS_TROUGH:
1070 /* Progress bar background (trough) */
1071 style = CreateChildCSSNode(GTK_STYLE_CLASS_TROUGH, MOZ_GTK_PROGRESSBAR);
1072 break;
1073 case MOZ_GTK_PROGRESS_CHUNK:
1074 style = CreateChildCSSNode("progress", MOZ_GTK_PROGRESS_TROUGH);
1075 break;
1076 case MOZ_GTK_GRIPPER:
1077 // TODO - create from CSS node
1078 style = CreateSubStyleWithClass(MOZ_GTK_GRIPPER, GTK_STYLE_CLASS_GRIP);
1079 break;
1080 case MOZ_GTK_SPINBUTTON_ENTRY:
1081 // TODO - create from CSS node
1082 style =
1083 CreateSubStyleWithClass(MOZ_GTK_SPINBUTTON, GTK_STYLE_CLASS_ENTRY);
1084 break;
1085 case MOZ_GTK_SCROLLED_WINDOW:
1086 // TODO - create from CSS node
1087 style = CreateSubStyleWithClass(MOZ_GTK_SCROLLED_WINDOW,
1088 GTK_STYLE_CLASS_FRAME);
1089 break;
1090 case MOZ_GTK_TEXT_VIEW_TEXT_SELECTION:
1091 style = CreateChildCSSNode("selection", MOZ_GTK_TEXT_VIEW_TEXT);
1092 break;
1093 case MOZ_GTK_TEXT_VIEW_TEXT:
1094 case MOZ_GTK_RESIZER:
1095 style = CreateChildCSSNode("text", MOZ_GTK_TEXT_VIEW);
1096 if (aNodeType == MOZ_GTK_RESIZER) {
1097 // The "grip" class provides the correct builtin icon from
1098 // gtk_render_handle(). The icon is drawn with shaded variants of
1099 // the background color, and so a transparent background would lead to
1100 // a transparent resizer. gtk_render_handle() also uses the
1101 // background color to draw a background, and so this style otherwise
1102 // matches what is used in GtkTextView to match the background with
1103 // textarea elements.
1104 GdkRGBA color;
1105 gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL,
1106 &color);
1107 if (color.alpha == 0.0) {
1108 g_object_unref(style);
1109 style = CreateStyleForWidget(gtk_text_view_new(),
1110 MOZ_GTK_SCROLLED_WINDOW);
1112 gtk_style_context_add_class(style, GTK_STYLE_CLASS_GRIP);
1114 break;
1115 case MOZ_GTK_FRAME_BORDER:
1116 style = CreateChildCSSNode("border", MOZ_GTK_FRAME);
1117 break;
1118 case MOZ_GTK_TREEVIEW_VIEW:
1119 // TODO - create from CSS node
1120 style = CreateSubStyleWithClass(MOZ_GTK_TREEVIEW, GTK_STYLE_CLASS_VIEW);
1121 break;
1122 case MOZ_GTK_TREEVIEW_EXPANDER:
1123 // TODO - create from CSS node
1124 style =
1125 CreateSubStyleWithClass(MOZ_GTK_TREEVIEW, GTK_STYLE_CLASS_EXPANDER);
1126 break;
1127 case MOZ_GTK_SPLITTER_SEPARATOR_HORIZONTAL:
1128 style = CreateChildCSSNode("separator", MOZ_GTK_SPLITTER_HORIZONTAL);
1129 break;
1130 case MOZ_GTK_SPLITTER_SEPARATOR_VERTICAL:
1131 style = CreateChildCSSNode("separator", MOZ_GTK_SPLITTER_VERTICAL);
1132 break;
1133 case MOZ_GTK_SCALE_CONTENTS_HORIZONTAL:
1134 style = CreateChildCSSNode("contents", MOZ_GTK_SCALE_HORIZONTAL);
1135 break;
1136 case MOZ_GTK_SCALE_CONTENTS_VERTICAL:
1137 style = CreateChildCSSNode("contents", MOZ_GTK_SCALE_VERTICAL);
1138 break;
1139 case MOZ_GTK_SCALE_TROUGH_HORIZONTAL:
1140 style = CreateChildCSSNode(GTK_STYLE_CLASS_TROUGH,
1141 MOZ_GTK_SCALE_CONTENTS_HORIZONTAL);
1142 break;
1143 case MOZ_GTK_SCALE_TROUGH_VERTICAL:
1144 style = CreateChildCSSNode(GTK_STYLE_CLASS_TROUGH,
1145 MOZ_GTK_SCALE_CONTENTS_VERTICAL);
1146 break;
1147 case MOZ_GTK_SCALE_THUMB_HORIZONTAL:
1148 style = CreateChildCSSNode(GTK_STYLE_CLASS_SLIDER,
1149 MOZ_GTK_SCALE_TROUGH_HORIZONTAL);
1150 break;
1151 case MOZ_GTK_SCALE_THUMB_VERTICAL:
1152 style = CreateChildCSSNode(GTK_STYLE_CLASS_SLIDER,
1153 MOZ_GTK_SCALE_TROUGH_VERTICAL);
1154 break;
1155 case MOZ_GTK_TAB_TOP: {
1156 // TODO - create from CSS node
1157 style = CreateSubStyleWithClass(MOZ_GTK_NOTEBOOK, GTK_STYLE_CLASS_TOP);
1158 gtk_style_context_add_region(style, GTK_STYLE_REGION_TAB,
1159 static_cast<GtkRegionFlags>(0));
1160 break;
1162 case MOZ_GTK_TAB_BOTTOM: {
1163 // TODO - create from CSS node
1164 style = CreateSubStyleWithClass(MOZ_GTK_NOTEBOOK, GTK_STYLE_CLASS_BOTTOM);
1165 gtk_style_context_add_region(style, GTK_STYLE_REGION_TAB,
1166 static_cast<GtkRegionFlags>(0));
1167 break;
1169 case MOZ_GTK_NOTEBOOK:
1170 case MOZ_GTK_NOTEBOOK_HEADER:
1171 case MOZ_GTK_TABPANELS:
1172 case MOZ_GTK_TAB_SCROLLARROW: {
1173 // TODO - create from CSS node
1174 GtkWidget* widget = GetWidget(MOZ_GTK_NOTEBOOK);
1175 return gtk_widget_get_style_context(widget);
1177 case MOZ_GTK_WINDOW_DECORATION: {
1178 GtkStyleContext* parentStyle =
1179 CreateSubStyleWithClass(MOZ_GTK_WINDOW, "csd");
1180 style = CreateCSSNode("decoration", parentStyle);
1181 g_object_unref(parentStyle);
1182 break;
1184 case MOZ_GTK_WINDOW_DECORATION_SOLID: {
1185 GtkStyleContext* parentStyle =
1186 CreateSubStyleWithClass(MOZ_GTK_WINDOW, "solid-csd");
1187 style = CreateCSSNode("decoration", parentStyle);
1188 g_object_unref(parentStyle);
1189 break;
1191 default:
1192 return GetWidgetRootStyle(aNodeType);
1195 MOZ_ASSERT(style, "missing style context for node type");
1196 sStyleStorage[aNodeType] = style;
1197 return style;
1200 /* GetWidgetStyleInternal is used by Gtk < 3.20 */
1201 static GtkStyleContext* GetWidgetStyleInternal(WidgetNodeType aNodeType) {
1202 GtkStyleContext* style = sStyleStorage[aNodeType];
1203 if (style) return style;
1205 switch (aNodeType) {
1206 case MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL:
1207 style = CreateSubStyleWithClass(MOZ_GTK_SCROLLBAR_VERTICAL,
1208 GTK_STYLE_CLASS_TROUGH);
1209 break;
1210 case MOZ_GTK_SCROLLBAR_THUMB_VERTICAL:
1211 style = CreateSubStyleWithClass(MOZ_GTK_SCROLLBAR_VERTICAL,
1212 GTK_STYLE_CLASS_SLIDER);
1213 break;
1214 case MOZ_GTK_RADIOBUTTON:
1215 style = CreateSubStyleWithClass(MOZ_GTK_RADIOBUTTON_CONTAINER,
1216 GTK_STYLE_CLASS_RADIO);
1217 break;
1218 case MOZ_GTK_CHECKBUTTON:
1219 style = CreateSubStyleWithClass(MOZ_GTK_CHECKBUTTON_CONTAINER,
1220 GTK_STYLE_CLASS_CHECK);
1221 break;
1222 case MOZ_GTK_PROGRESS_TROUGH:
1223 style =
1224 CreateSubStyleWithClass(MOZ_GTK_PROGRESSBAR, GTK_STYLE_CLASS_TROUGH);
1225 break;
1226 case MOZ_GTK_PROGRESS_CHUNK:
1227 style = CreateSubStyleWithClass(MOZ_GTK_PROGRESSBAR,
1228 GTK_STYLE_CLASS_PROGRESSBAR);
1229 gtk_style_context_remove_class(style, GTK_STYLE_CLASS_TROUGH);
1230 break;
1231 case MOZ_GTK_GRIPPER:
1232 style = CreateSubStyleWithClass(MOZ_GTK_GRIPPER, GTK_STYLE_CLASS_GRIP);
1233 break;
1234 case MOZ_GTK_SPINBUTTON_ENTRY:
1235 style =
1236 CreateSubStyleWithClass(MOZ_GTK_SPINBUTTON, GTK_STYLE_CLASS_ENTRY);
1237 break;
1238 case MOZ_GTK_SCROLLED_WINDOW:
1239 style = CreateSubStyleWithClass(MOZ_GTK_SCROLLED_WINDOW,
1240 GTK_STYLE_CLASS_FRAME);
1241 break;
1242 case MOZ_GTK_TEXT_VIEW_TEXT:
1243 case MOZ_GTK_RESIZER:
1244 // GTK versions prior to 3.20 do not have the view class on the root
1245 // node, but add this to determine the background for the text window.
1246 style = CreateSubStyleWithClass(MOZ_GTK_TEXT_VIEW, GTK_STYLE_CLASS_VIEW);
1247 if (aNodeType == MOZ_GTK_RESIZER) {
1248 // The "grip" class provides the correct builtin icon from
1249 // gtk_render_handle(). The icon is drawn with shaded variants of
1250 // the background color, and so a transparent background would lead to
1251 // a transparent resizer. gtk_render_handle() also uses the
1252 // background color to draw a background, and so this style otherwise
1253 // matches MOZ_GTK_TEXT_VIEW_TEXT to match the background with
1254 // textarea elements. GtkTextView creates a separate text window and
1255 // so the background should not be transparent.
1256 gtk_style_context_add_class(style, GTK_STYLE_CLASS_GRIP);
1258 break;
1259 case MOZ_GTK_FRAME_BORDER:
1260 return GetWidgetRootStyle(MOZ_GTK_FRAME);
1261 case MOZ_GTK_TREEVIEW_VIEW:
1262 style = CreateSubStyleWithClass(MOZ_GTK_TREEVIEW, GTK_STYLE_CLASS_VIEW);
1263 break;
1264 case MOZ_GTK_TREEVIEW_EXPANDER:
1265 style =
1266 CreateSubStyleWithClass(MOZ_GTK_TREEVIEW, GTK_STYLE_CLASS_EXPANDER);
1267 break;
1268 case MOZ_GTK_SPLITTER_SEPARATOR_HORIZONTAL:
1269 style = CreateSubStyleWithClass(MOZ_GTK_SPLITTER_HORIZONTAL,
1270 GTK_STYLE_CLASS_PANE_SEPARATOR);
1271 break;
1272 case MOZ_GTK_SPLITTER_SEPARATOR_VERTICAL:
1273 style = CreateSubStyleWithClass(MOZ_GTK_SPLITTER_VERTICAL,
1274 GTK_STYLE_CLASS_PANE_SEPARATOR);
1275 break;
1276 case MOZ_GTK_SCALE_TROUGH_HORIZONTAL:
1277 style = CreateSubStyleWithClass(MOZ_GTK_SCALE_HORIZONTAL,
1278 GTK_STYLE_CLASS_TROUGH);
1279 break;
1280 case MOZ_GTK_SCALE_TROUGH_VERTICAL:
1281 style = CreateSubStyleWithClass(MOZ_GTK_SCALE_VERTICAL,
1282 GTK_STYLE_CLASS_TROUGH);
1283 break;
1284 case MOZ_GTK_SCALE_THUMB_HORIZONTAL:
1285 style = CreateSubStyleWithClass(MOZ_GTK_SCALE_HORIZONTAL,
1286 GTK_STYLE_CLASS_SLIDER);
1287 break;
1288 case MOZ_GTK_SCALE_THUMB_VERTICAL:
1289 style = CreateSubStyleWithClass(MOZ_GTK_SCALE_VERTICAL,
1290 GTK_STYLE_CLASS_SLIDER);
1291 break;
1292 case MOZ_GTK_TAB_TOP:
1293 style = CreateSubStyleWithClass(MOZ_GTK_NOTEBOOK, GTK_STYLE_CLASS_TOP);
1294 gtk_style_context_add_region(style, GTK_STYLE_REGION_TAB,
1295 static_cast<GtkRegionFlags>(0));
1296 break;
1297 case MOZ_GTK_TAB_BOTTOM:
1298 style = CreateSubStyleWithClass(MOZ_GTK_NOTEBOOK, GTK_STYLE_CLASS_BOTTOM);
1299 gtk_style_context_add_region(style, GTK_STYLE_REGION_TAB,
1300 static_cast<GtkRegionFlags>(0));
1301 break;
1302 case MOZ_GTK_NOTEBOOK:
1303 case MOZ_GTK_NOTEBOOK_HEADER:
1304 case MOZ_GTK_TABPANELS:
1305 case MOZ_GTK_TAB_SCROLLARROW: {
1306 GtkWidget* widget = GetWidget(MOZ_GTK_NOTEBOOK);
1307 return gtk_widget_get_style_context(widget);
1309 default:
1310 return GetWidgetRootStyle(aNodeType);
1313 MOZ_ASSERT(style);
1314 sStyleStorage[aNodeType] = style;
1315 return style;
1318 void ResetWidgetCache() {
1319 for (int i = 0; i < MOZ_GTK_WIDGET_NODE_COUNT; i++) {
1320 if (sStyleStorage[i]) g_object_unref(sStyleStorage[i]);
1322 mozilla::PodArrayZero(sStyleStorage);
1324 gCSDStyle = CSDStyle::Unknown;
1326 /* This will destroy all of our widgets */
1327 if (sWidgetStorage[MOZ_GTK_WINDOW]) {
1328 gtk_widget_destroy(sWidgetStorage[MOZ_GTK_WINDOW]);
1330 if (sWidgetStorage[MOZ_GTK_HEADERBAR_WINDOW]) {
1331 gtk_widget_destroy(sWidgetStorage[MOZ_GTK_HEADERBAR_WINDOW]);
1333 if (sWidgetStorage[MOZ_GTK_HEADERBAR_WINDOW_MAXIMIZED]) {
1334 gtk_widget_destroy(sWidgetStorage[MOZ_GTK_HEADERBAR_WINDOW_MAXIMIZED]);
1337 /* Clear already freed arrays */
1338 mozilla::PodArrayZero(sWidgetStorage);
1341 GtkStyleContext* GetStyleContext(WidgetNodeType aNodeType, int aScale,
1342 GtkTextDirection aDirection,
1343 GtkStateFlags aStateFlags) {
1344 GtkStyleContext* style;
1345 if (gtk_check_version(3, 20, 0) != nullptr) {
1346 style = GetWidgetStyleInternal(aNodeType);
1347 } else {
1348 style = GetCssNodeStyleInternal(aNodeType);
1349 StyleContextSetScale(style, aScale);
1351 bool stateChanged = false;
1352 bool stateHasDirection = gtk_get_minor_version() >= 8;
1353 GtkStateFlags oldState = gtk_style_context_get_state(style);
1354 MOZ_ASSERT(!(aStateFlags & (STATE_FLAG_DIR_LTR | STATE_FLAG_DIR_RTL)));
1355 unsigned newState = aStateFlags;
1356 if (stateHasDirection) {
1357 switch (aDirection) {
1358 case GTK_TEXT_DIR_LTR:
1359 newState |= STATE_FLAG_DIR_LTR;
1360 break;
1361 case GTK_TEXT_DIR_RTL:
1362 newState |= STATE_FLAG_DIR_RTL;
1363 break;
1364 default:
1365 MOZ_FALLTHROUGH_ASSERT("Bad GtkTextDirection");
1366 case GTK_TEXT_DIR_NONE:
1367 // GtkWidget uses a default direction if neither is explicitly
1368 // specified, but here DIR_NONE is interpreted as meaning the
1369 // direction is not important, so don't change the direction
1370 // unnecessarily.
1371 newState |= oldState & (STATE_FLAG_DIR_LTR | STATE_FLAG_DIR_RTL);
1373 } else if (aDirection != GTK_TEXT_DIR_NONE) {
1374 GtkTextDirection oldDirection = gtk_style_context_get_direction(style);
1375 if (aDirection != oldDirection) {
1376 gtk_style_context_set_direction(style, aDirection);
1377 stateChanged = true;
1380 if (oldState != newState) {
1381 gtk_style_context_set_state(style, static_cast<GtkStateFlags>(newState));
1382 stateChanged = true;
1384 // This invalidate is necessary for unsaved style contexts from GtkWidgets
1385 // in pre-3.18 GTK, because automatic invalidation of such contexts
1386 // was delayed until a resize event runs.
1388 // https://bugzilla.mozilla.org/show_bug.cgi?id=1272194#c7
1390 // Avoid calling invalidate on contexts that are not owned and constructed
1391 // by widgets to avoid performing build_properties() (in 3.16 stylecontext.c)
1392 // unnecessarily early.
1393 if (stateChanged && sWidgetStorage[aNodeType]) {
1394 gtk_style_context_invalidate(style);
1396 return style;
1399 GtkStyleContext* CreateStyleContextWithStates(WidgetNodeType aNodeType,
1400 int aScale,
1401 GtkTextDirection aDirection,
1402 GtkStateFlags aStateFlags) {
1403 GtkStyleContext* style =
1404 GetStyleContext(aNodeType, aScale, aDirection, aStateFlags);
1405 GtkWidgetPath* path = gtk_widget_path_copy(gtk_style_context_get_path(style));
1407 static auto sGtkWidgetPathIterGetState =
1408 (GtkStateFlags(*)(const GtkWidgetPath*, gint))dlsym(
1409 RTLD_DEFAULT, "gtk_widget_path_iter_get_state");
1410 static auto sGtkWidgetPathIterSetState =
1411 (void (*)(GtkWidgetPath*, gint, GtkStateFlags))dlsym(
1412 RTLD_DEFAULT, "gtk_widget_path_iter_set_state");
1414 int pathLength = gtk_widget_path_length(path);
1415 for (int i = 0; i < pathLength; i++) {
1416 unsigned state = aStateFlags | sGtkWidgetPathIterGetState(path, i);
1417 sGtkWidgetPathIterSetState(path, i, GtkStateFlags(state));
1420 style = gtk_style_context_new();
1421 gtk_style_context_set_path(style, path);
1422 gtk_widget_path_unref(path);
1424 return style;
1427 void StyleContextSetScale(GtkStyleContext* style, gint aScaleFactor) {
1428 // Support HiDPI styles on Gtk 3.20+
1429 static auto sGtkStyleContextSetScalePtr =
1430 (void (*)(GtkStyleContext*, gint))dlsym(RTLD_DEFAULT,
1431 "gtk_style_context_set_scale");
1432 if (sGtkStyleContextSetScalePtr && style) {
1433 sGtkStyleContextSetScalePtr(style, aScaleFactor);
1437 bool HeaderBarShouldDrawContainer(WidgetNodeType aNodeType) {
1438 MOZ_ASSERT(aNodeType == MOZ_GTK_HEADER_BAR ||
1439 aNodeType == MOZ_GTK_HEADER_BAR_MAXIMIZED);
1440 mozilla::Unused << GetWidget(aNodeType);
1441 return aNodeType == MOZ_GTK_HEADER_BAR
1442 ? gHeaderBarShouldDrawContainer
1443 : gMaximizedHeaderBarShouldDrawContainer;
1446 gint GetBorderRadius(GtkStyleContext* aStyle) {
1447 GValue value = G_VALUE_INIT;
1448 // NOTE(emilio): In an ideal world, we'd query the two longhands
1449 // (border-top-left-radius and border-top-right-radius) separately. However,
1450 // that doesn't work (GTK rejects the query with:
1452 // Style property "border-top-left-radius" is not gettable
1454 // However! Getting border-radius does work, and it does return the
1455 // border-top-left-radius as a gint:
1457 // https://docs.gtk.org/gtk3/const.STYLE_PROPERTY_BORDER_RADIUS.html
1458 // https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-3-20/gtk/gtkcssshorthandpropertyimpl.c#L961-977
1460 // So we abuse this fact, and make the assumption here that the
1461 // border-top-{left,right}-radius are the same, and roll with it.
1462 gtk_style_context_get_property(aStyle, "border-radius", GTK_STATE_FLAG_NORMAL,
1463 &value);
1464 gint result = 0;
1465 auto type = G_VALUE_TYPE(&value);
1466 if (type == G_TYPE_INT) {
1467 result = g_value_get_int(&value);
1468 } else {
1469 NS_WARNING(nsPrintfCString("Unknown value type %lu for border-radius", type)
1470 .get());
1472 g_value_unset(&value);
1473 return result;