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/. */
9 #include "WidgetStyleCache.h"
10 #include "gtkdrawing.h"
11 #include "mozilla/Assertions.h"
12 #include "mozilla/PodOperations.h"
13 #include "mozilla/ScopeExit.h"
15 #include "nsPrintfCString.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");
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");
46 static GtkWidget
* CreateWindowContainerWidget() {
47 GtkWidget
* widget
= gtk_fixed_new();
48 gtk_container_add(GTK_CONTAINER(GetWidget(MOZ_GTK_WINDOW
)), 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
);
63 static GtkWidget
* CreateCheckboxWidget() {
64 GtkWidget
* widget
= gtk_check_button_new_with_label("M");
65 AddToWindowContainer(widget
);
69 static GtkWidget
* CreateRadiobuttonWidget() {
70 GtkWidget
* widget
= gtk_radio_button_new_with_label(nullptr, "M");
71 AddToWindowContainer(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
),
84 static GtkWidget
* CreateMenuBarWidget() {
85 GtkWidget
* widget
= gtk_menu_bar_new();
86 AddToWindowContainer(widget
);
90 static GtkWidget
* CreateProgressWidget() {
91 GtkWidget
* widget
= gtk_progress_bar_new();
92 AddToWindowContainer(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
);
105 static GtkWidget
* CreateExpanderWidget() {
106 GtkWidget
* widget
= gtk_expander_new("M");
107 AddToWindowContainer(widget
);
111 static GtkWidget
* CreateFrameWidget() {
112 GtkWidget
* widget
= gtk_frame_new(nullptr);
113 AddToWindowContainer(widget
);
117 static GtkWidget
* CreateButtonWidget() {
118 GtkWidget
* widget
= gtk_button_new_with_label("M");
119 AddToWindowContainer(widget
);
123 static GtkWidget
* CreateToggleButtonWidget() {
124 GtkWidget
* widget
= gtk_toggle_button_new();
125 AddToWindowContainer(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
);
136 static GtkWidget
* CreateSpinWidget() {
137 GtkWidget
* widget
= gtk_spin_button_new(nullptr, 1, 0);
138 AddToWindowContainer(widget
);
142 static GtkWidget
* CreateEntryWidget() {
143 GtkWidget
* widget
= gtk_entry_new();
144 AddToWindowContainer(widget
);
148 static GtkWidget
* CreateComboBoxWidget() {
149 GtkWidget
* widget
= gtk_combo_box_new();
150 AddToWindowContainer(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
179 comboBoxButton
= GetWidget(MOZ_GTK_TOGGLE_BUTTON
);
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
222 comboBoxArrow
= GetWidget(MOZ_GTK_BUTTON_ARROW
);
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
);
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
);
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
);
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
);
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
333 comboBoxArrow
= GetWidget(MOZ_GTK_BUTTON_ARROW
);
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
);
349 static GtkWidget
* CreateTreeViewWidget() {
350 GtkWidget
* widget
= gtk_tree_view_new();
351 AddToWindowContainer(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
364 * GTK doesn't give us a way to do this explicitly,
365 * so we must paint with a button that is between two
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
);
397 static GtkWidget
* CreateVPanedWidget() {
398 GtkWidget
* widget
= gtk_paned_new(GTK_ORIENTATION_VERTICAL
);
399 AddToWindowContainer(widget
);
403 static GtkWidget
* CreateScaleWidget(GtkOrientation aOrientation
) {
404 GtkWidget
* widget
= gtk_scale_new(aOrientation
, nullptr);
405 AddToWindowContainer(widget
);
409 static GtkWidget
* CreateNotebookWidget() {
410 GtkWidget
* widget
= gtk_notebook_new();
411 AddToWindowContainer(widget
);
415 static bool HasBackground(GtkStyleContext
* aStyle
) {
417 gtk_style_context_get_background_color(aStyle
, GTK_STATE_FLAG_NORMAL
,
419 if (gdkColor
.alpha
!= 0.0) {
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
;
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
)) {
494 if (HasBackground(fixedStyle
) &&
495 (GetBorderRadius(fixedStyle
) || !headerBarHasBackground
)) {
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
, >kIconSize
);
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);
521 // We miss the icon, nothing to do here.
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
),
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);
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",
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",
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",
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",
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
,
618 WidgetNodeType aAppearance
) {
619 for (size_t i
= 0; i
< aButtonNums
; i
++) {
620 if (aButtonLayout
[i
].mType
== aAppearance
) {
627 bool IsSolidCSDStyleUsed() {
628 if (gCSDStyle
== CSDStyle::Unknown
) {
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 GtkWidget
* buttonBox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 0);
648 gtk_container_add(GTK_CONTAINER(headerBar
), buttonBox
);
649 // We support only LTR headerbar layout for now.
650 gtk_style_context_add_class(gtk_widget_get_style_context(buttonBox
),
651 GTK_STYLE_CLASS_LEFT
);
653 ButtonLayout buttonLayout
[TOOLBAR_BUTTONS
];
655 size_t activeButtons
=
656 GetGtkHeaderBarButtonLayout(mozilla::Span(buttonLayout
), nullptr);
658 if (IsToolbarButtonEnabled(buttonLayout
, activeButtons
,
659 MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE
)) {
660 CreateHeaderBarButton(buttonBox
, MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE
);
662 if (IsToolbarButtonEnabled(buttonLayout
, activeButtons
,
663 MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE
)) {
664 CreateHeaderBarButton(buttonBox
, MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE
);
665 // We don't pack "restore" headerbar button to box as it's an icon
666 // placeholder. Pack it only to header bar to get correct style.
667 CreateHeaderBarButton(GetWidget(MOZ_GTK_HEADER_BAR_MAXIMIZED
),
668 MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE_RESTORE
);
670 if (IsToolbarButtonEnabled(buttonLayout
, activeButtons
,
671 MOZ_GTK_HEADER_BAR_BUTTON_CLOSE
)) {
672 CreateHeaderBarButton(buttonBox
, MOZ_GTK_HEADER_BAR_BUTTON_CLOSE
);
676 static void CreateHeaderBar() {
677 CreateHeaderBarWidget(MOZ_GTK_HEADER_BAR
);
678 CreateHeaderBarWidget(MOZ_GTK_HEADER_BAR_MAXIMIZED
);
679 CreateHeaderBarButtons();
682 static GtkWidget
* CreateWidget(WidgetNodeType aAppearance
) {
683 switch (aAppearance
) {
685 return CreateWindowWidget();
686 case MOZ_GTK_WINDOW_CONTAINER
:
687 return CreateWindowContainerWidget();
688 case MOZ_GTK_CHECKBUTTON_CONTAINER
:
689 return CreateCheckboxWidget();
690 case MOZ_GTK_PROGRESSBAR
:
691 return CreateProgressWidget();
692 case MOZ_GTK_RADIOBUTTON_CONTAINER
:
693 return CreateRadiobuttonWidget();
694 case MOZ_GTK_SCROLLBAR_VERTICAL
:
695 return CreateScrollbarWidget(aAppearance
, GTK_ORIENTATION_VERTICAL
);
696 case MOZ_GTK_MENUPOPUP
:
697 return CreateMenuPopupWidget();
698 case MOZ_GTK_MENUBAR
:
699 return CreateMenuBarWidget();
700 case MOZ_GTK_EXPANDER
:
701 return CreateExpanderWidget();
703 return CreateFrameWidget();
704 case MOZ_GTK_SPINBUTTON
:
705 return CreateSpinWidget();
707 return CreateButtonWidget();
708 case MOZ_GTK_TOGGLE_BUTTON
:
709 return CreateToggleButtonWidget();
710 case MOZ_GTK_BUTTON_ARROW
:
711 return CreateButtonArrowWidget();
713 case MOZ_GTK_DROPDOWN_ENTRY
:
714 return CreateEntryWidget();
715 case MOZ_GTK_SCROLLED_WINDOW
:
716 return CreateScrolledWindowWidget();
717 case MOZ_GTK_TREEVIEW
:
718 return CreateTreeViewWidget();
719 case MOZ_GTK_TREE_HEADER_CELL
:
720 return CreateTreeHeaderCellWidget();
721 case MOZ_GTK_SPLITTER_HORIZONTAL
:
722 return CreateHPanedWidget();
723 case MOZ_GTK_SPLITTER_VERTICAL
:
724 return CreateVPanedWidget();
725 case MOZ_GTK_SCALE_HORIZONTAL
:
726 return CreateScaleWidget(GTK_ORIENTATION_HORIZONTAL
);
727 case MOZ_GTK_SCALE_VERTICAL
:
728 return CreateScaleWidget(GTK_ORIENTATION_VERTICAL
);
729 case MOZ_GTK_NOTEBOOK
:
730 return CreateNotebookWidget();
731 case MOZ_GTK_COMBOBOX
:
732 return CreateComboBoxWidget();
733 case MOZ_GTK_COMBOBOX_BUTTON
:
734 return CreateComboBoxButtonWidget();
735 case MOZ_GTK_COMBOBOX_ARROW
:
736 return CreateComboBoxArrowWidget();
737 case MOZ_GTK_COMBOBOX_SEPARATOR
:
738 return CreateComboBoxSeparatorWidget();
739 case MOZ_GTK_COMBOBOX_ENTRY
:
740 return CreateComboBoxEntryWidget();
741 case MOZ_GTK_COMBOBOX_ENTRY_TEXTAREA
:
742 return CreateComboBoxEntryTextareaWidget();
743 case MOZ_GTK_COMBOBOX_ENTRY_BUTTON
:
744 return CreateComboBoxEntryButtonWidget();
745 case MOZ_GTK_COMBOBOX_ENTRY_ARROW
:
746 return CreateComboBoxEntryArrowWidget();
747 case MOZ_GTK_HEADERBAR_WINDOW
:
748 case MOZ_GTK_HEADERBAR_WINDOW_MAXIMIZED
:
749 case MOZ_GTK_HEADERBAR_FIXED
:
750 case MOZ_GTK_HEADERBAR_FIXED_MAXIMIZED
:
751 case MOZ_GTK_HEADER_BAR
:
752 case MOZ_GTK_HEADER_BAR_MAXIMIZED
:
753 case MOZ_GTK_HEADER_BAR_BUTTON_CLOSE
:
754 case MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE
:
755 case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE
:
756 case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE_RESTORE
:
757 /* Create header bar widgets once and fill with child elements as we need
758 the header bar fully configured to get a correct style */
760 return sWidgetStorage
[aAppearance
];
762 /* Not implemented */
767 GtkWidget
* GetWidget(WidgetNodeType aAppearance
) {
768 GtkWidget
* widget
= sWidgetStorage
[aAppearance
];
770 widget
= CreateWidget(aAppearance
);
771 // Some widgets (MOZ_GTK_COMBOBOX_SEPARATOR for instance) may not be
772 // available or implemented.
776 // In GTK versions prior to 3.18, automatic invalidation of style contexts
777 // for widgets was delayed until the next resize event. Gecko however,
778 // typically uses the style context before the resize event runs and so an
779 // explicit invalidation may be required. This is necessary if a style
780 // property was retrieved before all changes were made to the style
781 // context. One such situation is where gtk_button_construct_child()
782 // retrieves the style property "image-spacing" during construction of the
783 // GtkButton, before its parent is set to provide inheritance of ancestor
784 // properties. More recent GTK versions do not need this, but do not
785 // re-resolve until required and so invalidation does not trigger
786 // unnecessary resolution in general.
787 GtkStyleContext
* style
= gtk_widget_get_style_context(widget
);
788 gtk_style_context_invalidate(style
);
790 sWidgetStorage
[aAppearance
] = widget
;
795 static void AddStyleClassesFromStyle(GtkStyleContext
* aDest
,
796 GtkStyleContext
* aSrc
) {
797 GList
* classes
= gtk_style_context_list_classes(aSrc
);
798 for (GList
* link
= classes
; link
; link
= link
->next
) {
799 gtk_style_context_add_class(aDest
, static_cast<gchar
*>(link
->data
));
801 g_list_free(classes
);
804 GtkStyleContext
* CreateStyleForWidget(GtkWidget
* aWidget
,
805 GtkStyleContext
* aParentStyle
) {
806 static auto sGtkWidgetClassGetCSSName
=
807 reinterpret_cast<const char* (*)(GtkWidgetClass
*)>(
808 dlsym(RTLD_DEFAULT
, "gtk_widget_class_get_css_name"));
810 GtkWidgetClass
* widgetClass
= GTK_WIDGET_GET_CLASS(aWidget
);
811 const gchar
* name
= sGtkWidgetClassGetCSSName
812 ? sGtkWidgetClassGetCSSName(widgetClass
)
815 GtkStyleContext
* context
=
816 CreateCSSNode(name
, aParentStyle
, G_TYPE_FROM_CLASS(widgetClass
));
818 // Classes are stored on the style context instead of the path so that any
819 // future gtk_style_context_save() will inherit classes on the head CSS
820 // node, in the same way as happens when called on a style context owned by
823 // Classes can be stored on a GtkCssNodeDeclaration and/or the path.
824 // gtk_style_context_save() reuses the GtkCssNodeDeclaration, and appends a
825 // new object to the path, without copying the classes from the old path
826 // head. The new head picks up classes from the GtkCssNodeDeclaration, but
827 // not the path. GtkWidgets store their classes on the
828 // GtkCssNodeDeclaration, so make sure to add classes there.
830 // Picking up classes from the style context also means that
831 // https://bugzilla.gnome.org/show_bug.cgi?id=767312, which can stop
832 // gtk_widget_path_append_for_widget() from finding classes in GTK 3.20,
834 GtkStyleContext
* widgetStyle
= gtk_widget_get_style_context(aWidget
);
835 AddStyleClassesFromStyle(context
, widgetStyle
);
837 // Release any floating reference on aWidget.
838 g_object_ref_sink(aWidget
);
839 g_object_unref(aWidget
);
844 static GtkStyleContext
* CreateStyleForWidget(GtkWidget
* aWidget
,
845 WidgetNodeType aParentType
) {
846 return CreateStyleForWidget(aWidget
, GetWidgetRootStyle(aParentType
));
849 GtkStyleContext
* CreateCSSNode(const char* aName
, GtkStyleContext
* aParentStyle
,
851 static auto sGtkWidgetPathIterSetObjectName
=
852 reinterpret_cast<void (*)(GtkWidgetPath
*, gint
, const char*)>(
853 dlsym(RTLD_DEFAULT
, "gtk_widget_path_iter_set_object_name"));
857 path
= gtk_widget_path_copy(gtk_style_context_get_path(aParentStyle
));
858 // Copy classes from the parent style context to its corresponding node in
859 // the path, because GTK will only match against ancestor classes if they
861 GList
* classes
= gtk_style_context_list_classes(aParentStyle
);
862 for (GList
* link
= classes
; link
; link
= link
->next
) {
863 gtk_widget_path_iter_add_class(path
, -1, static_cast<gchar
*>(link
->data
));
865 g_list_free(classes
);
867 path
= gtk_widget_path_new();
870 gtk_widget_path_append_type(path
, aType
);
872 if (sGtkWidgetPathIterSetObjectName
) {
873 (*sGtkWidgetPathIterSetObjectName
)(path
, -1, aName
);
876 GtkStyleContext
* context
= gtk_style_context_new();
877 gtk_style_context_set_path(context
, path
);
878 gtk_style_context_set_parent(context
, aParentStyle
);
879 gtk_widget_path_unref(path
);
881 // In GTK 3.4, gtk_render_* functions use |theming_engine| on the style
882 // context without ensuring any style resolution sets it appropriately
883 // in style_data_lookup(). e.g.
884 // https://git.gnome.org/browse/gtk+/tree/gtk/gtkstylecontext.c?h=3.4.4#n3847
886 // That can result in incorrect drawing on first draw. To work around this,
887 // force a style look-up to set |theming_engine|. It is sufficient to do
888 // this only on context creation, instead of after every modification to the
889 // context, because themes typically (Ambiance and oxygen-gtk, at least) set
890 // the "engine" property with the '*' selector.
891 if (GTK_MAJOR_VERSION
== 3 && gtk_get_minor_version() < 6) {
893 gtk_style_context_get_color(context
, GTK_STATE_FLAG_NORMAL
, &unused
);
899 // Return a style context matching that of the root CSS node of a widget.
900 // This is used by all GTK versions.
901 static GtkStyleContext
* GetWidgetRootStyle(WidgetNodeType aNodeType
) {
902 GtkStyleContext
* style
= sStyleStorage
[aNodeType
];
903 if (style
) return style
;
906 case MOZ_GTK_MENUITEM
:
907 style
= CreateStyleForWidget(gtk_menu_item_new(), MOZ_GTK_MENUPOPUP
);
909 case MOZ_GTK_MENUBARITEM
:
910 style
= CreateStyleForWidget(gtk_menu_item_new(), MOZ_GTK_MENUBAR
);
912 case MOZ_GTK_TEXT_VIEW
:
914 CreateStyleForWidget(gtk_text_view_new(), MOZ_GTK_SCROLLED_WINDOW
);
916 case MOZ_GTK_TOOLTIP
:
917 if (gtk_check_version(3, 20, 0) != nullptr) {
918 // The tooltip style class is added first in CreateTooltipWidget()
919 // and transfered to style in CreateStyleForWidget().
920 GtkWidget
* tooltipWindow
= CreateTooltipWidget();
921 style
= CreateStyleForWidget(tooltipWindow
, nullptr);
922 gtk_widget_destroy(tooltipWindow
); // Release GtkWindow self-reference.
924 // We create this from the path because GtkTooltipWindow is not public.
925 style
= CreateCSSNode("tooltip", nullptr, GTK_TYPE_TOOLTIP
);
926 gtk_style_context_add_class(style
, GTK_STYLE_CLASS_BACKGROUND
);
929 case MOZ_GTK_TOOLTIP_BOX
:
930 style
= CreateStyleForWidget(gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 0),
933 case MOZ_GTK_TOOLTIP_BOX_LABEL
:
934 style
= CreateStyleForWidget(gtk_label_new(nullptr), MOZ_GTK_TOOLTIP_BOX
);
937 GtkWidget
* widget
= GetWidget(aNodeType
);
939 return gtk_widget_get_style_context(widget
);
943 sStyleStorage
[aNodeType
] = style
;
947 static GtkStyleContext
* CreateChildCSSNode(const char* aName
,
948 WidgetNodeType aParentNodeType
) {
949 return CreateCSSNode(aName
, GetCssNodeStyleInternal(aParentNodeType
));
952 // Create a style context equivalent to a saved root style context of
953 // |aAppearance| with |aStyleClass| as an additional class. This is used to
954 // produce a context equivalent to what GTK versions < 3.20 use for many
955 // internal parts of widgets.
956 static GtkStyleContext
* CreateSubStyleWithClass(WidgetNodeType aAppearance
,
957 const gchar
* aStyleClass
) {
958 static auto sGtkWidgetPathIterGetObjectName
=
959 reinterpret_cast<const char* (*)(const GtkWidgetPath
*, gint
)>(
960 dlsym(RTLD_DEFAULT
, "gtk_widget_path_iter_get_object_name"));
962 GtkStyleContext
* parentStyle
= GetWidgetRootStyle(aAppearance
);
964 // Create a new context that behaves like |parentStyle| would after
965 // gtk_style_context_save(parentStyle).
967 // Avoiding gtk_style_context_save() avoids the need to manage the
968 // restore, and a new context permits caching style resolution.
970 // gtk_style_context_save(context) changes the node hierarchy of |context|
971 // to add a new GtkCssNodeDeclaration that is a copy of its original node.
972 // The new node is a child of the original node, and so the new heirarchy is
973 // one level deeper. The new node receives the same classes as the
974 // original, but any changes to the classes on |context| will change only
975 // the new node. The new node inherits properties from the original node
976 // (which retains the original heirarchy and classes) and matches CSS rules
977 // with the new heirarchy and any changes to the classes.
979 // The change in hierarchy can produce some surprises in matching theme CSS
980 // rules (e.g. https://bugzilla.gnome.org/show_bug.cgi?id=761870#c2), but it
981 // is important here to produce the same behavior so that rules match the
982 // same widget parts in Gecko as they do in GTK.
984 // When using public GTK API to construct style contexts, a widget path is
985 // required. CSS rules are not matched against the style context heirarchy
986 // but according to the heirarchy in the widget path. The path that matches
987 // the same CSS rules as a saved context is like the path of |parentStyle|
988 // but with an extra copy of the head (last) object appended. Setting
989 // |parentStyle| as the parent context provides the same inheritance of
990 // properties from the widget root node.
991 const GtkWidgetPath
* parentPath
= gtk_style_context_get_path(parentStyle
);
992 const gchar
* name
= sGtkWidgetPathIterGetObjectName
993 ? sGtkWidgetPathIterGetObjectName(parentPath
, -1)
995 GType objectType
= gtk_widget_path_get_object_type(parentPath
);
997 GtkStyleContext
* style
= CreateCSSNode(name
, parentStyle
, objectType
);
999 // Start with the same classes on the new node as were on |parentStyle|.
1000 // GTK puts no regions or junction_sides on widget root nodes, and so there
1001 // is no need to copy these.
1002 AddStyleClassesFromStyle(style
, parentStyle
);
1004 gtk_style_context_add_class(style
, aStyleClass
);
1008 /* GetCssNodeStyleInternal is used by Gtk >= 3.20 */
1009 static GtkStyleContext
* GetCssNodeStyleInternal(WidgetNodeType aNodeType
) {
1010 GtkStyleContext
* style
= sStyleStorage
[aNodeType
];
1011 if (style
) return style
;
1013 switch (aNodeType
) {
1014 case MOZ_GTK_SCROLLBAR_CONTENTS_VERTICAL
:
1015 style
= CreateChildCSSNode("contents", MOZ_GTK_SCROLLBAR_VERTICAL
);
1017 case MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL
:
1018 style
= CreateChildCSSNode(GTK_STYLE_CLASS_TROUGH
,
1019 MOZ_GTK_SCROLLBAR_CONTENTS_VERTICAL
);
1021 case MOZ_GTK_SCROLLBAR_THUMB_VERTICAL
:
1022 style
= CreateChildCSSNode(GTK_STYLE_CLASS_SLIDER
,
1023 MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL
);
1025 case MOZ_GTK_RADIOBUTTON
:
1026 style
= CreateChildCSSNode(GTK_STYLE_CLASS_RADIO
,
1027 MOZ_GTK_RADIOBUTTON_CONTAINER
);
1029 case MOZ_GTK_CHECKBUTTON
:
1030 style
= CreateChildCSSNode(GTK_STYLE_CLASS_CHECK
,
1031 MOZ_GTK_CHECKBUTTON_CONTAINER
);
1033 case MOZ_GTK_PROGRESS_TROUGH
:
1034 /* Progress bar background (trough) */
1035 style
= CreateChildCSSNode(GTK_STYLE_CLASS_TROUGH
, MOZ_GTK_PROGRESSBAR
);
1037 case MOZ_GTK_PROGRESS_CHUNK
:
1038 style
= CreateChildCSSNode("progress", MOZ_GTK_PROGRESS_TROUGH
);
1040 case MOZ_GTK_SPINBUTTON_ENTRY
:
1041 // TODO - create from CSS node
1043 CreateSubStyleWithClass(MOZ_GTK_SPINBUTTON
, GTK_STYLE_CLASS_ENTRY
);
1045 case MOZ_GTK_SCROLLED_WINDOW
:
1046 // TODO - create from CSS node
1047 style
= CreateSubStyleWithClass(MOZ_GTK_SCROLLED_WINDOW
,
1048 GTK_STYLE_CLASS_FRAME
);
1050 case MOZ_GTK_TEXT_VIEW_TEXT_SELECTION
:
1051 style
= CreateChildCSSNode("selection", MOZ_GTK_TEXT_VIEW_TEXT
);
1053 case MOZ_GTK_TEXT_VIEW_TEXT
:
1054 case MOZ_GTK_RESIZER
:
1055 style
= CreateChildCSSNode("text", MOZ_GTK_TEXT_VIEW
);
1056 if (aNodeType
== MOZ_GTK_RESIZER
) {
1057 // The "grip" class provides the correct builtin icon from
1058 // gtk_render_handle(). The icon is drawn with shaded variants of
1059 // the background color, and so a transparent background would lead to
1060 // a transparent resizer. gtk_render_handle() also uses the
1061 // background color to draw a background, and so this style otherwise
1062 // matches what is used in GtkTextView to match the background with
1063 // textarea elements.
1065 gtk_style_context_get_background_color(style
, GTK_STATE_FLAG_NORMAL
,
1067 if (color
.alpha
== 0.0) {
1068 g_object_unref(style
);
1069 style
= CreateStyleForWidget(gtk_text_view_new(),
1070 MOZ_GTK_SCROLLED_WINDOW
);
1072 gtk_style_context_add_class(style
, GTK_STYLE_CLASS_GRIP
);
1075 case MOZ_GTK_FRAME_BORDER
:
1076 style
= CreateChildCSSNode("border", MOZ_GTK_FRAME
);
1078 case MOZ_GTK_TREEVIEW_VIEW
:
1079 // TODO - create from CSS node
1080 style
= CreateSubStyleWithClass(MOZ_GTK_TREEVIEW
, GTK_STYLE_CLASS_VIEW
);
1082 case MOZ_GTK_SPLITTER_SEPARATOR_HORIZONTAL
:
1083 style
= CreateChildCSSNode("separator", MOZ_GTK_SPLITTER_HORIZONTAL
);
1085 case MOZ_GTK_SPLITTER_SEPARATOR_VERTICAL
:
1086 style
= CreateChildCSSNode("separator", MOZ_GTK_SPLITTER_VERTICAL
);
1088 case MOZ_GTK_SCALE_CONTENTS_HORIZONTAL
:
1089 style
= CreateChildCSSNode("contents", MOZ_GTK_SCALE_HORIZONTAL
);
1091 case MOZ_GTK_SCALE_CONTENTS_VERTICAL
:
1092 style
= CreateChildCSSNode("contents", MOZ_GTK_SCALE_VERTICAL
);
1094 case MOZ_GTK_SCALE_TROUGH_HORIZONTAL
:
1095 style
= CreateChildCSSNode(GTK_STYLE_CLASS_TROUGH
,
1096 MOZ_GTK_SCALE_CONTENTS_HORIZONTAL
);
1098 case MOZ_GTK_SCALE_TROUGH_VERTICAL
:
1099 style
= CreateChildCSSNode(GTK_STYLE_CLASS_TROUGH
,
1100 MOZ_GTK_SCALE_CONTENTS_VERTICAL
);
1102 case MOZ_GTK_SCALE_THUMB_HORIZONTAL
:
1103 style
= CreateChildCSSNode(GTK_STYLE_CLASS_SLIDER
,
1104 MOZ_GTK_SCALE_TROUGH_HORIZONTAL
);
1106 case MOZ_GTK_SCALE_THUMB_VERTICAL
:
1107 style
= CreateChildCSSNode(GTK_STYLE_CLASS_SLIDER
,
1108 MOZ_GTK_SCALE_TROUGH_VERTICAL
);
1110 case MOZ_GTK_TAB_TOP
: {
1111 // TODO - create from CSS node
1112 style
= CreateSubStyleWithClass(MOZ_GTK_NOTEBOOK
, GTK_STYLE_CLASS_TOP
);
1113 gtk_style_context_add_region(style
, GTK_STYLE_REGION_TAB
,
1114 static_cast<GtkRegionFlags
>(0));
1117 case MOZ_GTK_TAB_BOTTOM
: {
1118 // TODO - create from CSS node
1119 style
= CreateSubStyleWithClass(MOZ_GTK_NOTEBOOK
, GTK_STYLE_CLASS_BOTTOM
);
1120 gtk_style_context_add_region(style
, GTK_STYLE_REGION_TAB
,
1121 static_cast<GtkRegionFlags
>(0));
1124 case MOZ_GTK_NOTEBOOK
:
1125 case MOZ_GTK_NOTEBOOK_HEADER
:
1126 case MOZ_GTK_TABPANELS
:
1127 case MOZ_GTK_TAB_SCROLLARROW
: {
1128 // TODO - create from CSS node
1129 GtkWidget
* widget
= GetWidget(MOZ_GTK_NOTEBOOK
);
1130 return gtk_widget_get_style_context(widget
);
1132 case MOZ_GTK_WINDOW_DECORATION
: {
1133 GtkStyleContext
* parentStyle
=
1134 CreateSubStyleWithClass(MOZ_GTK_WINDOW
, "csd");
1135 style
= CreateCSSNode("decoration", parentStyle
);
1136 g_object_unref(parentStyle
);
1139 case MOZ_GTK_WINDOW_DECORATION_SOLID
: {
1140 GtkStyleContext
* parentStyle
=
1141 CreateSubStyleWithClass(MOZ_GTK_WINDOW
, "solid-csd");
1142 style
= CreateCSSNode("decoration", parentStyle
);
1143 g_object_unref(parentStyle
);
1147 return GetWidgetRootStyle(aNodeType
);
1150 MOZ_ASSERT(style
, "missing style context for node type");
1151 sStyleStorage
[aNodeType
] = style
;
1155 /* GetWidgetStyleInternal is used by Gtk < 3.20 */
1156 static GtkStyleContext
* GetWidgetStyleInternal(WidgetNodeType aNodeType
) {
1157 GtkStyleContext
* style
= sStyleStorage
[aNodeType
];
1158 if (style
) return style
;
1160 switch (aNodeType
) {
1161 case MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL
:
1162 style
= CreateSubStyleWithClass(MOZ_GTK_SCROLLBAR_VERTICAL
,
1163 GTK_STYLE_CLASS_TROUGH
);
1165 case MOZ_GTK_SCROLLBAR_THUMB_VERTICAL
:
1166 style
= CreateSubStyleWithClass(MOZ_GTK_SCROLLBAR_VERTICAL
,
1167 GTK_STYLE_CLASS_SLIDER
);
1169 case MOZ_GTK_RADIOBUTTON
:
1170 style
= CreateSubStyleWithClass(MOZ_GTK_RADIOBUTTON_CONTAINER
,
1171 GTK_STYLE_CLASS_RADIO
);
1173 case MOZ_GTK_CHECKBUTTON
:
1174 style
= CreateSubStyleWithClass(MOZ_GTK_CHECKBUTTON_CONTAINER
,
1175 GTK_STYLE_CLASS_CHECK
);
1177 case MOZ_GTK_PROGRESS_TROUGH
:
1179 CreateSubStyleWithClass(MOZ_GTK_PROGRESSBAR
, GTK_STYLE_CLASS_TROUGH
);
1181 case MOZ_GTK_PROGRESS_CHUNK
:
1182 style
= CreateSubStyleWithClass(MOZ_GTK_PROGRESSBAR
,
1183 GTK_STYLE_CLASS_PROGRESSBAR
);
1184 gtk_style_context_remove_class(style
, GTK_STYLE_CLASS_TROUGH
);
1186 case MOZ_GTK_SPINBUTTON_ENTRY
:
1188 CreateSubStyleWithClass(MOZ_GTK_SPINBUTTON
, GTK_STYLE_CLASS_ENTRY
);
1190 case MOZ_GTK_SCROLLED_WINDOW
:
1191 style
= CreateSubStyleWithClass(MOZ_GTK_SCROLLED_WINDOW
,
1192 GTK_STYLE_CLASS_FRAME
);
1194 case MOZ_GTK_TEXT_VIEW_TEXT
:
1195 case MOZ_GTK_RESIZER
:
1196 // GTK versions prior to 3.20 do not have the view class on the root
1197 // node, but add this to determine the background for the text window.
1198 style
= CreateSubStyleWithClass(MOZ_GTK_TEXT_VIEW
, GTK_STYLE_CLASS_VIEW
);
1199 if (aNodeType
== MOZ_GTK_RESIZER
) {
1200 // The "grip" class provides the correct builtin icon from
1201 // gtk_render_handle(). The icon is drawn with shaded variants of
1202 // the background color, and so a transparent background would lead to
1203 // a transparent resizer. gtk_render_handle() also uses the
1204 // background color to draw a background, and so this style otherwise
1205 // matches MOZ_GTK_TEXT_VIEW_TEXT to match the background with
1206 // textarea elements. GtkTextView creates a separate text window and
1207 // so the background should not be transparent.
1208 gtk_style_context_add_class(style
, GTK_STYLE_CLASS_GRIP
);
1211 case MOZ_GTK_FRAME_BORDER
:
1212 return GetWidgetRootStyle(MOZ_GTK_FRAME
);
1213 case MOZ_GTK_TREEVIEW_VIEW
:
1214 style
= CreateSubStyleWithClass(MOZ_GTK_TREEVIEW
, GTK_STYLE_CLASS_VIEW
);
1216 case MOZ_GTK_SPLITTER_SEPARATOR_HORIZONTAL
:
1217 style
= CreateSubStyleWithClass(MOZ_GTK_SPLITTER_HORIZONTAL
,
1218 GTK_STYLE_CLASS_PANE_SEPARATOR
);
1220 case MOZ_GTK_SPLITTER_SEPARATOR_VERTICAL
:
1221 style
= CreateSubStyleWithClass(MOZ_GTK_SPLITTER_VERTICAL
,
1222 GTK_STYLE_CLASS_PANE_SEPARATOR
);
1224 case MOZ_GTK_SCALE_TROUGH_HORIZONTAL
:
1225 style
= CreateSubStyleWithClass(MOZ_GTK_SCALE_HORIZONTAL
,
1226 GTK_STYLE_CLASS_TROUGH
);
1228 case MOZ_GTK_SCALE_TROUGH_VERTICAL
:
1229 style
= CreateSubStyleWithClass(MOZ_GTK_SCALE_VERTICAL
,
1230 GTK_STYLE_CLASS_TROUGH
);
1232 case MOZ_GTK_SCALE_THUMB_HORIZONTAL
:
1233 style
= CreateSubStyleWithClass(MOZ_GTK_SCALE_HORIZONTAL
,
1234 GTK_STYLE_CLASS_SLIDER
);
1236 case MOZ_GTK_SCALE_THUMB_VERTICAL
:
1237 style
= CreateSubStyleWithClass(MOZ_GTK_SCALE_VERTICAL
,
1238 GTK_STYLE_CLASS_SLIDER
);
1240 case MOZ_GTK_TAB_TOP
:
1241 style
= CreateSubStyleWithClass(MOZ_GTK_NOTEBOOK
, GTK_STYLE_CLASS_TOP
);
1242 gtk_style_context_add_region(style
, GTK_STYLE_REGION_TAB
,
1243 static_cast<GtkRegionFlags
>(0));
1245 case MOZ_GTK_TAB_BOTTOM
:
1246 style
= CreateSubStyleWithClass(MOZ_GTK_NOTEBOOK
, GTK_STYLE_CLASS_BOTTOM
);
1247 gtk_style_context_add_region(style
, GTK_STYLE_REGION_TAB
,
1248 static_cast<GtkRegionFlags
>(0));
1250 case MOZ_GTK_NOTEBOOK
:
1251 case MOZ_GTK_NOTEBOOK_HEADER
:
1252 case MOZ_GTK_TABPANELS
:
1253 case MOZ_GTK_TAB_SCROLLARROW
: {
1254 GtkWidget
* widget
= GetWidget(MOZ_GTK_NOTEBOOK
);
1255 return gtk_widget_get_style_context(widget
);
1258 return GetWidgetRootStyle(aNodeType
);
1262 sStyleStorage
[aNodeType
] = style
;
1266 void ResetWidgetCache() {
1267 for (int i
= 0; i
< MOZ_GTK_WIDGET_NODE_COUNT
; i
++) {
1268 if (sStyleStorage
[i
]) g_object_unref(sStyleStorage
[i
]);
1270 mozilla::PodArrayZero(sStyleStorage
);
1272 gCSDStyle
= CSDStyle::Unknown
;
1274 /* This will destroy all of our widgets */
1275 if (sWidgetStorage
[MOZ_GTK_WINDOW
]) {
1276 gtk_widget_destroy(sWidgetStorage
[MOZ_GTK_WINDOW
]);
1278 if (sWidgetStorage
[MOZ_GTK_HEADERBAR_WINDOW
]) {
1279 gtk_widget_destroy(sWidgetStorage
[MOZ_GTK_HEADERBAR_WINDOW
]);
1281 if (sWidgetStorage
[MOZ_GTK_HEADERBAR_WINDOW_MAXIMIZED
]) {
1282 gtk_widget_destroy(sWidgetStorage
[MOZ_GTK_HEADERBAR_WINDOW_MAXIMIZED
]);
1285 /* Clear already freed arrays */
1286 mozilla::PodArrayZero(sWidgetStorage
);
1289 GtkStyleContext
* GetStyleContext(WidgetNodeType aNodeType
, int aScale
,
1290 GtkTextDirection aDirection
,
1291 GtkStateFlags aStateFlags
) {
1292 GtkStyleContext
* style
;
1293 if (gtk_check_version(3, 20, 0) != nullptr) {
1294 style
= GetWidgetStyleInternal(aNodeType
);
1296 style
= GetCssNodeStyleInternal(aNodeType
);
1297 StyleContextSetScale(style
, aScale
);
1299 bool stateChanged
= false;
1300 bool stateHasDirection
= gtk_get_minor_version() >= 8;
1301 GtkStateFlags oldState
= gtk_style_context_get_state(style
);
1302 MOZ_ASSERT(!(aStateFlags
& (STATE_FLAG_DIR_LTR
| STATE_FLAG_DIR_RTL
)));
1303 unsigned newState
= aStateFlags
;
1304 if (stateHasDirection
) {
1305 switch (aDirection
) {
1306 case GTK_TEXT_DIR_LTR
:
1307 newState
|= STATE_FLAG_DIR_LTR
;
1309 case GTK_TEXT_DIR_RTL
:
1310 newState
|= STATE_FLAG_DIR_RTL
;
1313 MOZ_FALLTHROUGH_ASSERT("Bad GtkTextDirection");
1314 case GTK_TEXT_DIR_NONE
:
1315 // GtkWidget uses a default direction if neither is explicitly
1316 // specified, but here DIR_NONE is interpreted as meaning the
1317 // direction is not important, so don't change the direction
1319 newState
|= oldState
& (STATE_FLAG_DIR_LTR
| STATE_FLAG_DIR_RTL
);
1321 } else if (aDirection
!= GTK_TEXT_DIR_NONE
) {
1322 GtkTextDirection oldDirection
= gtk_style_context_get_direction(style
);
1323 if (aDirection
!= oldDirection
) {
1324 gtk_style_context_set_direction(style
, aDirection
);
1325 stateChanged
= true;
1328 if (oldState
!= newState
) {
1329 gtk_style_context_set_state(style
, static_cast<GtkStateFlags
>(newState
));
1330 stateChanged
= true;
1332 // This invalidate is necessary for unsaved style contexts from GtkWidgets
1333 // in pre-3.18 GTK, because automatic invalidation of such contexts
1334 // was delayed until a resize event runs.
1336 // https://bugzilla.mozilla.org/show_bug.cgi?id=1272194#c7
1338 // Avoid calling invalidate on contexts that are not owned and constructed
1339 // by widgets to avoid performing build_properties() (in 3.16 stylecontext.c)
1340 // unnecessarily early.
1341 if (stateChanged
&& sWidgetStorage
[aNodeType
]) {
1342 gtk_style_context_invalidate(style
);
1347 GtkStyleContext
* CreateStyleContextWithStates(WidgetNodeType aNodeType
,
1349 GtkTextDirection aDirection
,
1350 GtkStateFlags aStateFlags
) {
1351 GtkStyleContext
* style
=
1352 GetStyleContext(aNodeType
, aScale
, aDirection
, aStateFlags
);
1353 GtkWidgetPath
* path
= gtk_widget_path_copy(gtk_style_context_get_path(style
));
1355 static auto sGtkWidgetPathIterGetState
=
1356 (GtkStateFlags(*)(const GtkWidgetPath
*, gint
))dlsym(
1357 RTLD_DEFAULT
, "gtk_widget_path_iter_get_state");
1358 static auto sGtkWidgetPathIterSetState
=
1359 (void (*)(GtkWidgetPath
*, gint
, GtkStateFlags
))dlsym(
1360 RTLD_DEFAULT
, "gtk_widget_path_iter_set_state");
1362 int pathLength
= gtk_widget_path_length(path
);
1363 for (int i
= 0; i
< pathLength
; i
++) {
1364 unsigned state
= aStateFlags
| sGtkWidgetPathIterGetState(path
, i
);
1365 sGtkWidgetPathIterSetState(path
, i
, GtkStateFlags(state
));
1368 style
= gtk_style_context_new();
1369 gtk_style_context_set_path(style
, path
);
1370 gtk_widget_path_unref(path
);
1375 void StyleContextSetScale(GtkStyleContext
* style
, gint aScaleFactor
) {
1376 // Support HiDPI styles on Gtk 3.20+
1377 static auto sGtkStyleContextSetScalePtr
=
1378 (void (*)(GtkStyleContext
*, gint
))dlsym(RTLD_DEFAULT
,
1379 "gtk_style_context_set_scale");
1380 if (sGtkStyleContextSetScalePtr
&& style
) {
1381 sGtkStyleContextSetScalePtr(style
, aScaleFactor
);
1385 bool HeaderBarShouldDrawContainer(WidgetNodeType aNodeType
) {
1386 MOZ_ASSERT(aNodeType
== MOZ_GTK_HEADER_BAR
||
1387 aNodeType
== MOZ_GTK_HEADER_BAR_MAXIMIZED
);
1388 mozilla::Unused
<< GetWidget(aNodeType
);
1389 return aNodeType
== MOZ_GTK_HEADER_BAR
1390 ? gHeaderBarShouldDrawContainer
1391 : gMaximizedHeaderBarShouldDrawContainer
;
1394 gint
GetBorderRadius(GtkStyleContext
* aStyle
) {
1395 GValue value
= G_VALUE_INIT
;
1396 // NOTE(emilio): In an ideal world, we'd query the two longhands
1397 // (border-top-left-radius and border-top-right-radius) separately. However,
1398 // that doesn't work (GTK rejects the query with:
1400 // Style property "border-top-left-radius" is not gettable
1402 // However! Getting border-radius does work, and it does return the
1403 // border-top-left-radius as a gint:
1405 // https://docs.gtk.org/gtk3/const.STYLE_PROPERTY_BORDER_RADIUS.html
1406 // https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-3-20/gtk/gtkcssshorthandpropertyimpl.c#L961-977
1408 // So we abuse this fact, and make the assumption here that the
1409 // border-top-{left,right}-radius are the same, and roll with it.
1410 gtk_style_context_get_property(aStyle
, "border-radius", GTK_STATE_FLAG_NORMAL
,
1413 auto type
= G_VALUE_TYPE(&value
);
1414 if (type
== G_TYPE_INT
) {
1415 result
= g_value_get_int(&value
);
1417 NS_WARNING(nsPrintfCString("Unknown value type %lu for border-radius", type
)
1420 g_value_unset(&value
);