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
* CreateMenuBarWidget() {
76 GtkWidget
* widget
= gtk_menu_bar_new();
77 AddToWindowContainer(widget
);
81 static GtkWidget
* CreateMenuPopupWidget() {
82 GtkWidget
* widget
= gtk_menu_new();
83 GtkStyleContext
* style
= gtk_widget_get_style_context(widget
);
84 gtk_style_context_add_class(style
, GTK_STYLE_CLASS_POPUP
);
85 gtk_menu_attach_to_widget(GTK_MENU(widget
), GetWidget(MOZ_GTK_WINDOW
),
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
* CreateGripperWidget() {
118 GtkWidget
* widget
= gtk_handle_box_new();
119 AddToWindowContainer(widget
);
123 static GtkWidget
* CreateToolbarWidget() {
124 GtkWidget
* widget
= gtk_toolbar_new();
125 gtk_container_add(GTK_CONTAINER(GetWidget(MOZ_GTK_GRIPPER
)), widget
);
129 static GtkWidget
* CreateToolbarSeparatorWidget() {
130 GtkWidget
* widget
= GTK_WIDGET(gtk_separator_tool_item_new());
131 AddToWindowContainer(widget
);
135 static GtkWidget
* CreateButtonWidget() {
136 GtkWidget
* widget
= gtk_button_new_with_label("M");
137 AddToWindowContainer(widget
);
141 static GtkWidget
* CreateToggleButtonWidget() {
142 GtkWidget
* widget
= gtk_toggle_button_new();
143 AddToWindowContainer(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
);
154 static GtkWidget
* CreateSpinWidget() {
155 GtkWidget
* widget
= gtk_spin_button_new(nullptr, 1, 0);
156 AddToWindowContainer(widget
);
160 static GtkWidget
* CreateEntryWidget() {
161 GtkWidget
* widget
= gtk_entry_new();
162 AddToWindowContainer(widget
);
166 static GtkWidget
* CreateComboBoxWidget() {
167 GtkWidget
* widget
= gtk_combo_box_new();
168 AddToWindowContainer(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
197 comboBoxButton
= GetWidget(MOZ_GTK_TOGGLE_BUTTON
);
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
240 comboBoxArrow
= GetWidget(MOZ_GTK_BUTTON_ARROW
);
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
);
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
);
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
);
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
);
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
351 comboBoxArrow
= GetWidget(MOZ_GTK_BUTTON_ARROW
);
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
);
367 static GtkWidget
* CreateMenuSeparatorWidget() {
368 GtkWidget
* widget
= gtk_separator_menu_item_new();
369 gtk_menu_shell_append(GTK_MENU_SHELL(GetWidget(MOZ_GTK_MENUPOPUP
)), widget
);
373 static GtkWidget
* CreateTreeViewWidget() {
374 GtkWidget
* widget
= gtk_tree_view_new();
375 AddToWindowContainer(widget
);
379 static GtkWidget
* CreateTreeHeaderCellWidget() {
381 * Some GTK engines paint the first and last cell
382 * of a TreeView header with a highlight.
383 * Since we do not know where our widget will be relative
384 * to the other buttons in the TreeView header, we must
385 * paint it as a button that is between two others,
386 * thus ensuring it is neither the first or last button
388 * GTK doesn't give us a way to do this explicitly,
389 * so we must paint with a button that is between two
392 GtkTreeViewColumn
* firstTreeViewColumn
;
393 GtkTreeViewColumn
* middleTreeViewColumn
;
394 GtkTreeViewColumn
* lastTreeViewColumn
;
396 GtkWidget
* treeView
= GetWidget(MOZ_GTK_TREEVIEW
);
398 /* Create and append our three columns */
399 firstTreeViewColumn
= gtk_tree_view_column_new();
400 gtk_tree_view_column_set_title(firstTreeViewColumn
, "M");
401 gtk_tree_view_append_column(GTK_TREE_VIEW(treeView
), firstTreeViewColumn
);
403 middleTreeViewColumn
= gtk_tree_view_column_new();
404 gtk_tree_view_column_set_title(middleTreeViewColumn
, "M");
405 gtk_tree_view_append_column(GTK_TREE_VIEW(treeView
), middleTreeViewColumn
);
407 lastTreeViewColumn
= gtk_tree_view_column_new();
408 gtk_tree_view_column_set_title(lastTreeViewColumn
, "M");
409 gtk_tree_view_append_column(GTK_TREE_VIEW(treeView
), lastTreeViewColumn
);
411 /* Use the middle column's header for our button */
412 return gtk_tree_view_column_get_button(middleTreeViewColumn
);
415 static GtkWidget
* CreateTreeHeaderSortArrowWidget() {
416 /* TODO, but it can't be NULL */
417 GtkWidget
* widget
= gtk_button_new();
418 AddToWindowContainer(widget
);
422 static GtkWidget
* CreateHPanedWidget() {
423 GtkWidget
* widget
= gtk_paned_new(GTK_ORIENTATION_HORIZONTAL
);
424 AddToWindowContainer(widget
);
428 static GtkWidget
* CreateVPanedWidget() {
429 GtkWidget
* widget
= gtk_paned_new(GTK_ORIENTATION_VERTICAL
);
430 AddToWindowContainer(widget
);
434 static GtkWidget
* CreateScaleWidget(GtkOrientation aOrientation
) {
435 GtkWidget
* widget
= gtk_scale_new(aOrientation
, nullptr);
436 AddToWindowContainer(widget
);
440 static GtkWidget
* CreateNotebookWidget() {
441 GtkWidget
* widget
= gtk_notebook_new();
442 AddToWindowContainer(widget
);
446 static bool HasBackground(GtkStyleContext
* aStyle
) {
448 gtk_style_context_get_background_color(aStyle
, GTK_STATE_FLAG_NORMAL
,
450 if (gdkColor
.alpha
!= 0.0) {
454 GValue value
= G_VALUE_INIT
;
455 gtk_style_context_get_property(aStyle
, "background-image",
456 GTK_STATE_FLAG_NORMAL
, &value
);
457 auto cleanup
= mozilla::MakeScopeExit([&] { g_value_unset(&value
); });
458 return g_value_get_boxed(&value
);
461 static void CreateHeaderBarWidget(WidgetNodeType aAppearance
) {
462 GtkWidget
* window
= gtk_window_new(GTK_WINDOW_TOPLEVEL
);
463 GtkStyleContext
* windowStyle
= gtk_widget_get_style_context(window
);
465 // Headerbar has to be placed to window with csd or solid-csd style
466 // to properly draw the decorated.
467 gtk_style_context_add_class(windowStyle
,
468 IsSolidCSDStyleUsed() ? "solid-csd" : "csd");
470 GtkWidget
* fixed
= gtk_fixed_new();
471 GtkStyleContext
* fixedStyle
= gtk_widget_get_style_context(fixed
);
472 gtk_style_context_add_class(fixedStyle
, "titlebar");
474 GtkWidget
* headerBar
= gtk_header_bar_new();
476 // Emulate what create_titlebar() at gtkwindow.c does.
477 GtkStyleContext
* headerBarStyle
= gtk_widget_get_style_context(headerBar
);
478 gtk_style_context_add_class(headerBarStyle
, "titlebar");
480 // TODO: Define default-decoration titlebar style as workaround
481 // to ensure the titlebar buttons does not overflow outside.
482 // Recently the titlebar size is calculated as
483 // tab size + titlebar border/padding (default-decoration has 6px padding
484 // at default Adwaita theme).
485 // We need to fix titlebar size calculation to also include
486 // titlebar button sizes. (Bug 1419442)
487 gtk_style_context_add_class(headerBarStyle
, "default-decoration");
489 sWidgetStorage
[aAppearance
] = headerBar
;
490 if (aAppearance
== MOZ_GTK_HEADER_BAR_MAXIMIZED
) {
491 MOZ_ASSERT(!sWidgetStorage
[MOZ_GTK_HEADERBAR_WINDOW_MAXIMIZED
],
492 "Window widget is already created!");
493 MOZ_ASSERT(!sWidgetStorage
[MOZ_GTK_HEADERBAR_FIXED_MAXIMIZED
],
494 "Fixed widget is already created!");
496 gtk_style_context_add_class(windowStyle
, "maximized");
498 sWidgetStorage
[MOZ_GTK_HEADERBAR_WINDOW_MAXIMIZED
] = window
;
499 sWidgetStorage
[MOZ_GTK_HEADERBAR_FIXED_MAXIMIZED
] = fixed
;
501 MOZ_ASSERT(!sWidgetStorage
[MOZ_GTK_HEADERBAR_WINDOW
],
502 "Window widget is already created!");
503 MOZ_ASSERT(!sWidgetStorage
[MOZ_GTK_HEADERBAR_FIXED
],
504 "Fixed widget is already created!");
505 sWidgetStorage
[MOZ_GTK_HEADERBAR_WINDOW
] = window
;
506 sWidgetStorage
[MOZ_GTK_HEADERBAR_FIXED
] = fixed
;
509 gtk_container_add(GTK_CONTAINER(window
), fixed
);
510 gtk_container_add(GTK_CONTAINER(fixed
), headerBar
);
512 gtk_style_context_invalidate(headerBarStyle
);
513 gtk_style_context_invalidate(fixedStyle
);
515 // Some themes like Elementary's style the container of the headerbar rather
516 // than the header bar itself.
517 bool& shouldDrawContainer
= aAppearance
== MOZ_GTK_HEADER_BAR
518 ? gHeaderBarShouldDrawContainer
519 : gMaximizedHeaderBarShouldDrawContainer
;
520 shouldDrawContainer
= [&] {
521 const bool headerBarHasBackground
= HasBackground(headerBarStyle
);
522 if (headerBarHasBackground
&& GetBorderRadius(headerBarStyle
)) {
525 if (HasBackground(fixedStyle
) &&
526 (GetBorderRadius(fixedStyle
) || !headerBarHasBackground
)) {
533 #define ICON_SCALE_VARIANTS 2
535 static void LoadWidgetIconPixbuf(GtkWidget
* aWidgetIcon
) {
536 GtkStyleContext
* style
= gtk_widget_get_style_context(aWidgetIcon
);
538 const gchar
* iconName
;
539 GtkIconSize gtkIconSize
;
540 gtk_image_get_icon_name(GTK_IMAGE(aWidgetIcon
), &iconName
, >kIconSize
);
542 gint iconWidth
, iconHeight
;
543 gtk_icon_size_lookup(gtkIconSize
, &iconWidth
, &iconHeight
);
545 /* Those are available since Gtk+ 3.10 as well as GtkHeaderBar */
546 for (int scale
= 1; scale
< ICON_SCALE_VARIANTS
+ 1; scale
++) {
547 GtkIconInfo
* gtkIconInfo
= gtk_icon_theme_lookup_icon_for_scale(
548 gtk_icon_theme_get_default(), iconName
, iconWidth
, scale
,
549 (GtkIconLookupFlags
)0);
552 // We miss the icon, nothing to do here.
557 GdkPixbuf
* iconPixbuf
= gtk_icon_info_load_symbolic_for_context(
558 gtkIconInfo
, style
, &unused
, nullptr);
559 g_object_unref(G_OBJECT(gtkIconInfo
));
561 cairo_surface_t
* iconSurface
=
562 gdk_cairo_surface_create_from_pixbuf(iconPixbuf
, scale
, nullptr);
563 g_object_unref(iconPixbuf
);
565 nsPrintfCString
surfaceName("MozillaIconSurface%d", scale
);
566 g_object_set_data_full(G_OBJECT(aWidgetIcon
), surfaceName
.get(),
567 iconSurface
, (GDestroyNotify
)cairo_surface_destroy
);
571 cairo_surface_t
* GetWidgetIconSurface(GtkWidget
* aWidgetIcon
, int aScale
) {
572 if (aScale
> ICON_SCALE_VARIANTS
) {
573 aScale
= ICON_SCALE_VARIANTS
;
576 nsPrintfCString
surfaceName("MozillaIconSurface%d", aScale
);
577 return (cairo_surface_t
*)g_object_get_data(G_OBJECT(aWidgetIcon
),
581 static void CreateHeaderBarButton(GtkWidget
* aParentWidget
,
582 WidgetNodeType aAppearance
) {
583 GtkWidget
* widget
= gtk_button_new();
585 // We have to add button to widget hierarchy now to pick
586 // right icon style at LoadWidgetIconPixbuf().
587 if (GTK_IS_BOX(aParentWidget
)) {
588 gtk_box_pack_start(GTK_BOX(aParentWidget
), widget
, FALSE
, FALSE
, 0);
590 gtk_container_add(GTK_CONTAINER(aParentWidget
), widget
);
593 // We bypass GetWidget() here because we create all titlebar
594 // buttons at once when a first one is requested.
595 NS_ASSERTION(!sWidgetStorage
[aAppearance
],
596 "Titlebar button is already created!");
597 sWidgetStorage
[aAppearance
] = widget
;
599 // We need to show the button widget now as GtkBox does not
600 // place invisible widgets and we'll miss first-child/last-child
601 // css selectors at the buttons otherwise.
602 gtk_widget_show(widget
);
604 GtkStyleContext
* style
= gtk_widget_get_style_context(widget
);
605 gtk_style_context_add_class(style
, "titlebutton");
607 GtkWidget
* image
= nullptr;
608 switch (aAppearance
) {
609 case MOZ_GTK_HEADER_BAR_BUTTON_CLOSE
:
610 gtk_style_context_add_class(style
, "close");
611 image
= gtk_image_new_from_icon_name("window-close-symbolic",
614 case MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE
:
615 gtk_style_context_add_class(style
, "minimize");
616 image
= gtk_image_new_from_icon_name("window-minimize-symbolic",
620 case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE
:
621 gtk_style_context_add_class(style
, "maximize");
622 image
= gtk_image_new_from_icon_name("window-maximize-symbolic",
626 case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE_RESTORE
:
627 gtk_style_context_add_class(style
, "maximize");
628 image
= gtk_image_new_from_icon_name("window-restore-symbolic",
635 gtk_widget_set_valign(widget
, GTK_ALIGN_CENTER
);
636 g_object_set(image
, "use-fallback", TRUE
, NULL
);
637 gtk_container_add(GTK_CONTAINER(widget
), image
);
639 // We bypass GetWidget() here by explicit sWidgetStorage[] update so
640 // invalidate the style as well as GetWidget() does.
641 style
= gtk_widget_get_style_context(image
);
642 gtk_style_context_invalidate(style
);
644 LoadWidgetIconPixbuf(image
);
647 static bool IsToolbarButtonEnabled(ButtonLayout
* aButtonLayout
,
649 WidgetNodeType aAppearance
) {
650 for (size_t i
= 0; i
< aButtonNums
; i
++) {
651 if (aButtonLayout
[i
].mType
== aAppearance
) {
658 bool IsSolidCSDStyleUsed() {
659 if (gCSDStyle
== CSDStyle::Unknown
) {
662 GtkWidget
* window
= gtk_window_new(GTK_WINDOW_TOPLEVEL
);
663 gtk_window_set_titlebar(GTK_WINDOW(window
), gtk_header_bar_new());
664 gtk_widget_realize(window
);
665 GtkStyleContext
* windowStyle
= gtk_widget_get_style_context(window
);
666 solid
= gtk_style_context_has_class(windowStyle
, "solid-csd");
667 gtk_widget_destroy(window
);
669 gCSDStyle
= solid
? CSDStyle::Solid
: CSDStyle::Normal
;
671 return gCSDStyle
== CSDStyle::Solid
;
674 static void CreateHeaderBarButtons() {
675 GtkWidget
* headerBar
= sWidgetStorage
[MOZ_GTK_HEADER_BAR
];
676 MOZ_ASSERT(headerBar
!= nullptr, "We're missing header bar widget!");
678 gint buttonSpacing
= 6;
679 g_object_get(headerBar
, "spacing", &buttonSpacing
, nullptr);
681 GtkWidget
* buttonBox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, buttonSpacing
);
682 gtk_container_add(GTK_CONTAINER(GetWidget(MOZ_GTK_HEADER_BAR
)), buttonBox
);
683 // We support only LTR headerbar layout for now.
684 gtk_style_context_add_class(gtk_widget_get_style_context(buttonBox
),
685 GTK_STYLE_CLASS_LEFT
);
687 ButtonLayout buttonLayout
[TOOLBAR_BUTTONS
];
689 size_t activeButtons
=
690 GetGtkHeaderBarButtonLayout(mozilla::Span(buttonLayout
), nullptr);
692 if (IsToolbarButtonEnabled(buttonLayout
, activeButtons
,
693 MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE
)) {
694 CreateHeaderBarButton(buttonBox
, MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE
);
696 if (IsToolbarButtonEnabled(buttonLayout
, activeButtons
,
697 MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE
)) {
698 CreateHeaderBarButton(buttonBox
, MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE
);
699 // We don't pack "restore" headerbar button to box as it's an icon
700 // placeholder. Pack it only to header bar to get correct style.
701 CreateHeaderBarButton(GetWidget(MOZ_GTK_HEADER_BAR
),
702 MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE_RESTORE
);
704 if (IsToolbarButtonEnabled(buttonLayout
, activeButtons
,
705 MOZ_GTK_HEADER_BAR_BUTTON_CLOSE
)) {
706 CreateHeaderBarButton(buttonBox
, MOZ_GTK_HEADER_BAR_BUTTON_CLOSE
);
710 static void CreateHeaderBar() {
711 CreateHeaderBarWidget(MOZ_GTK_HEADER_BAR
);
712 CreateHeaderBarWidget(MOZ_GTK_HEADER_BAR_MAXIMIZED
);
713 CreateHeaderBarButtons();
716 static GtkWidget
* CreateWidget(WidgetNodeType aAppearance
) {
717 switch (aAppearance
) {
719 return CreateWindowWidget();
720 case MOZ_GTK_WINDOW_CONTAINER
:
721 return CreateWindowContainerWidget();
722 case MOZ_GTK_CHECKBUTTON_CONTAINER
:
723 return CreateCheckboxWidget();
724 case MOZ_GTK_PROGRESSBAR
:
725 return CreateProgressWidget();
726 case MOZ_GTK_RADIOBUTTON_CONTAINER
:
727 return CreateRadiobuttonWidget();
728 case MOZ_GTK_SCROLLBAR_VERTICAL
:
729 return CreateScrollbarWidget(aAppearance
, GTK_ORIENTATION_VERTICAL
);
730 case MOZ_GTK_MENUBAR
:
731 return CreateMenuBarWidget();
732 case MOZ_GTK_MENUPOPUP
:
733 return CreateMenuPopupWidget();
734 case MOZ_GTK_MENUSEPARATOR
:
735 return CreateMenuSeparatorWidget();
736 case MOZ_GTK_EXPANDER
:
737 return CreateExpanderWidget();
739 return CreateFrameWidget();
740 case MOZ_GTK_GRIPPER
:
741 return CreateGripperWidget();
742 case MOZ_GTK_TOOLBAR
:
743 return CreateToolbarWidget();
744 case MOZ_GTK_TOOLBAR_SEPARATOR
:
745 return CreateToolbarSeparatorWidget();
746 case MOZ_GTK_SPINBUTTON
:
747 return CreateSpinWidget();
749 return CreateButtonWidget();
750 case MOZ_GTK_TOGGLE_BUTTON
:
751 return CreateToggleButtonWidget();
752 case MOZ_GTK_BUTTON_ARROW
:
753 return CreateButtonArrowWidget();
755 case MOZ_GTK_DROPDOWN_ENTRY
:
756 return CreateEntryWidget();
757 case MOZ_GTK_SCROLLED_WINDOW
:
758 return CreateScrolledWindowWidget();
759 case MOZ_GTK_TREEVIEW
:
760 return CreateTreeViewWidget();
761 case MOZ_GTK_TREE_HEADER_CELL
:
762 return CreateTreeHeaderCellWidget();
763 case MOZ_GTK_TREE_HEADER_SORTARROW
:
764 return CreateTreeHeaderSortArrowWidget();
765 case MOZ_GTK_SPLITTER_HORIZONTAL
:
766 return CreateHPanedWidget();
767 case MOZ_GTK_SPLITTER_VERTICAL
:
768 return CreateVPanedWidget();
769 case MOZ_GTK_SCALE_HORIZONTAL
:
770 return CreateScaleWidget(GTK_ORIENTATION_HORIZONTAL
);
771 case MOZ_GTK_SCALE_VERTICAL
:
772 return CreateScaleWidget(GTK_ORIENTATION_VERTICAL
);
773 case MOZ_GTK_NOTEBOOK
:
774 return CreateNotebookWidget();
775 case MOZ_GTK_COMBOBOX
:
776 return CreateComboBoxWidget();
777 case MOZ_GTK_COMBOBOX_BUTTON
:
778 return CreateComboBoxButtonWidget();
779 case MOZ_GTK_COMBOBOX_ARROW
:
780 return CreateComboBoxArrowWidget();
781 case MOZ_GTK_COMBOBOX_SEPARATOR
:
782 return CreateComboBoxSeparatorWidget();
783 case MOZ_GTK_COMBOBOX_ENTRY
:
784 return CreateComboBoxEntryWidget();
785 case MOZ_GTK_COMBOBOX_ENTRY_TEXTAREA
:
786 return CreateComboBoxEntryTextareaWidget();
787 case MOZ_GTK_COMBOBOX_ENTRY_BUTTON
:
788 return CreateComboBoxEntryButtonWidget();
789 case MOZ_GTK_COMBOBOX_ENTRY_ARROW
:
790 return CreateComboBoxEntryArrowWidget();
791 case MOZ_GTK_HEADERBAR_WINDOW
:
792 case MOZ_GTK_HEADERBAR_WINDOW_MAXIMIZED
:
793 case MOZ_GTK_HEADERBAR_FIXED
:
794 case MOZ_GTK_HEADERBAR_FIXED_MAXIMIZED
:
795 case MOZ_GTK_HEADER_BAR
:
796 case MOZ_GTK_HEADER_BAR_MAXIMIZED
:
797 case MOZ_GTK_HEADER_BAR_BUTTON_CLOSE
:
798 case MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE
:
799 case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE
:
800 case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE_RESTORE
:
801 /* Create header bar widgets once and fill with child elements as we need
802 the header bar fully configured to get a correct style */
804 return sWidgetStorage
[aAppearance
];
806 /* Not implemented */
811 GtkWidget
* GetWidget(WidgetNodeType aAppearance
) {
812 GtkWidget
* widget
= sWidgetStorage
[aAppearance
];
814 widget
= CreateWidget(aAppearance
);
815 // Some widgets (MOZ_GTK_COMBOBOX_SEPARATOR for instance) may not be
816 // available or implemented.
820 // In GTK versions prior to 3.18, automatic invalidation of style contexts
821 // for widgets was delayed until the next resize event. Gecko however,
822 // typically uses the style context before the resize event runs and so an
823 // explicit invalidation may be required. This is necessary if a style
824 // property was retrieved before all changes were made to the style
825 // context. One such situation is where gtk_button_construct_child()
826 // retrieves the style property "image-spacing" during construction of the
827 // GtkButton, before its parent is set to provide inheritance of ancestor
828 // properties. More recent GTK versions do not need this, but do not
829 // re-resolve until required and so invalidation does not trigger
830 // unnecessary resolution in general.
831 GtkStyleContext
* style
= gtk_widget_get_style_context(widget
);
832 gtk_style_context_invalidate(style
);
834 sWidgetStorage
[aAppearance
] = widget
;
839 static void AddStyleClassesFromStyle(GtkStyleContext
* aDest
,
840 GtkStyleContext
* aSrc
) {
841 GList
* classes
= gtk_style_context_list_classes(aSrc
);
842 for (GList
* link
= classes
; link
; link
= link
->next
) {
843 gtk_style_context_add_class(aDest
, static_cast<gchar
*>(link
->data
));
845 g_list_free(classes
);
848 GtkStyleContext
* CreateStyleForWidget(GtkWidget
* aWidget
,
849 GtkStyleContext
* aParentStyle
) {
850 static auto sGtkWidgetClassGetCSSName
=
851 reinterpret_cast<const char* (*)(GtkWidgetClass
*)>(
852 dlsym(RTLD_DEFAULT
, "gtk_widget_class_get_css_name"));
854 GtkWidgetClass
* widgetClass
= GTK_WIDGET_GET_CLASS(aWidget
);
855 const gchar
* name
= sGtkWidgetClassGetCSSName
856 ? sGtkWidgetClassGetCSSName(widgetClass
)
859 GtkStyleContext
* context
=
860 CreateCSSNode(name
, aParentStyle
, G_TYPE_FROM_CLASS(widgetClass
));
862 // Classes are stored on the style context instead of the path so that any
863 // future gtk_style_context_save() will inherit classes on the head CSS
864 // node, in the same way as happens when called on a style context owned by
867 // Classes can be stored on a GtkCssNodeDeclaration and/or the path.
868 // gtk_style_context_save() reuses the GtkCssNodeDeclaration, and appends a
869 // new object to the path, without copying the classes from the old path
870 // head. The new head picks up classes from the GtkCssNodeDeclaration, but
871 // not the path. GtkWidgets store their classes on the
872 // GtkCssNodeDeclaration, so make sure to add classes there.
874 // Picking up classes from the style context also means that
875 // https://bugzilla.gnome.org/show_bug.cgi?id=767312, which can stop
876 // gtk_widget_path_append_for_widget() from finding classes in GTK 3.20,
878 GtkStyleContext
* widgetStyle
= gtk_widget_get_style_context(aWidget
);
879 AddStyleClassesFromStyle(context
, widgetStyle
);
881 // Release any floating reference on aWidget.
882 g_object_ref_sink(aWidget
);
883 g_object_unref(aWidget
);
888 static GtkStyleContext
* CreateStyleForWidget(GtkWidget
* aWidget
,
889 WidgetNodeType aParentType
) {
890 return CreateStyleForWidget(aWidget
, GetWidgetRootStyle(aParentType
));
893 GtkStyleContext
* CreateCSSNode(const char* aName
, GtkStyleContext
* aParentStyle
,
895 static auto sGtkWidgetPathIterSetObjectName
=
896 reinterpret_cast<void (*)(GtkWidgetPath
*, gint
, const char*)>(
897 dlsym(RTLD_DEFAULT
, "gtk_widget_path_iter_set_object_name"));
901 path
= gtk_widget_path_copy(gtk_style_context_get_path(aParentStyle
));
902 // Copy classes from the parent style context to its corresponding node in
903 // the path, because GTK will only match against ancestor classes if they
905 GList
* classes
= gtk_style_context_list_classes(aParentStyle
);
906 for (GList
* link
= classes
; link
; link
= link
->next
) {
907 gtk_widget_path_iter_add_class(path
, -1, static_cast<gchar
*>(link
->data
));
909 g_list_free(classes
);
911 path
= gtk_widget_path_new();
914 gtk_widget_path_append_type(path
, aType
);
916 if (sGtkWidgetPathIterSetObjectName
) {
917 (*sGtkWidgetPathIterSetObjectName
)(path
, -1, aName
);
920 GtkStyleContext
* context
= gtk_style_context_new();
921 gtk_style_context_set_path(context
, path
);
922 gtk_style_context_set_parent(context
, aParentStyle
);
923 gtk_widget_path_unref(path
);
925 // In GTK 3.4, gtk_render_* functions use |theming_engine| on the style
926 // context without ensuring any style resolution sets it appropriately
927 // in style_data_lookup(). e.g.
928 // https://git.gnome.org/browse/gtk+/tree/gtk/gtkstylecontext.c?h=3.4.4#n3847
930 // That can result in incorrect drawing on first draw. To work around this,
931 // force a style look-up to set |theming_engine|. It is sufficient to do
932 // this only on context creation, instead of after every modification to the
933 // context, because themes typically (Ambiance and oxygen-gtk, at least) set
934 // the "engine" property with the '*' selector.
935 if (GTK_MAJOR_VERSION
== 3 && gtk_get_minor_version() < 6) {
937 gtk_style_context_get_color(context
, GTK_STATE_FLAG_NORMAL
, &unused
);
943 // Return a style context matching that of the root CSS node of a widget.
944 // This is used by all GTK versions.
945 static GtkStyleContext
* GetWidgetRootStyle(WidgetNodeType aNodeType
) {
946 GtkStyleContext
* style
= sStyleStorage
[aNodeType
];
947 if (style
) return style
;
950 case MOZ_GTK_MENUBARITEM
:
951 style
= CreateStyleForWidget(gtk_menu_item_new(), MOZ_GTK_MENUBAR
);
953 case MOZ_GTK_MENUITEM
:
954 style
= CreateStyleForWidget(gtk_menu_item_new(), MOZ_GTK_MENUPOPUP
);
956 case MOZ_GTK_CHECKMENUITEM
:
958 CreateStyleForWidget(gtk_check_menu_item_new(), MOZ_GTK_MENUPOPUP
);
960 case MOZ_GTK_RADIOMENUITEM
:
961 style
= CreateStyleForWidget(gtk_radio_menu_item_new(nullptr),
964 case MOZ_GTK_TEXT_VIEW
:
966 CreateStyleForWidget(gtk_text_view_new(), MOZ_GTK_SCROLLED_WINDOW
);
968 case MOZ_GTK_TOOLTIP
:
969 if (gtk_check_version(3, 20, 0) != nullptr) {
970 // The tooltip style class is added first in CreateTooltipWidget()
971 // and transfered to style in CreateStyleForWidget().
972 GtkWidget
* tooltipWindow
= CreateTooltipWidget();
973 style
= CreateStyleForWidget(tooltipWindow
, nullptr);
974 gtk_widget_destroy(tooltipWindow
); // Release GtkWindow self-reference.
976 // We create this from the path because GtkTooltipWindow is not public.
977 style
= CreateCSSNode("tooltip", nullptr, GTK_TYPE_TOOLTIP
);
978 gtk_style_context_add_class(style
, GTK_STYLE_CLASS_BACKGROUND
);
981 case MOZ_GTK_TOOLTIP_BOX
:
982 style
= CreateStyleForWidget(gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 0),
985 case MOZ_GTK_TOOLTIP_BOX_LABEL
:
986 style
= CreateStyleForWidget(gtk_label_new(nullptr), MOZ_GTK_TOOLTIP_BOX
);
989 GtkWidget
* widget
= GetWidget(aNodeType
);
991 return gtk_widget_get_style_context(widget
);
995 sStyleStorage
[aNodeType
] = style
;
999 static GtkStyleContext
* CreateChildCSSNode(const char* aName
,
1000 WidgetNodeType aParentNodeType
) {
1001 return CreateCSSNode(aName
, GetCssNodeStyleInternal(aParentNodeType
));
1004 // Create a style context equivalent to a saved root style context of
1005 // |aAppearance| with |aStyleClass| as an additional class. This is used to
1006 // produce a context equivalent to what GTK versions < 3.20 use for many
1007 // internal parts of widgets.
1008 static GtkStyleContext
* CreateSubStyleWithClass(WidgetNodeType aAppearance
,
1009 const gchar
* aStyleClass
) {
1010 static auto sGtkWidgetPathIterGetObjectName
=
1011 reinterpret_cast<const char* (*)(const GtkWidgetPath
*, gint
)>(
1012 dlsym(RTLD_DEFAULT
, "gtk_widget_path_iter_get_object_name"));
1014 GtkStyleContext
* parentStyle
= GetWidgetRootStyle(aAppearance
);
1016 // Create a new context that behaves like |parentStyle| would after
1017 // gtk_style_context_save(parentStyle).
1019 // Avoiding gtk_style_context_save() avoids the need to manage the
1020 // restore, and a new context permits caching style resolution.
1022 // gtk_style_context_save(context) changes the node hierarchy of |context|
1023 // to add a new GtkCssNodeDeclaration that is a copy of its original node.
1024 // The new node is a child of the original node, and so the new heirarchy is
1025 // one level deeper. The new node receives the same classes as the
1026 // original, but any changes to the classes on |context| will change only
1027 // the new node. The new node inherits properties from the original node
1028 // (which retains the original heirarchy and classes) and matches CSS rules
1029 // with the new heirarchy and any changes to the classes.
1031 // The change in hierarchy can produce some surprises in matching theme CSS
1032 // rules (e.g. https://bugzilla.gnome.org/show_bug.cgi?id=761870#c2), but it
1033 // is important here to produce the same behavior so that rules match the
1034 // same widget parts in Gecko as they do in GTK.
1036 // When using public GTK API to construct style contexts, a widget path is
1037 // required. CSS rules are not matched against the style context heirarchy
1038 // but according to the heirarchy in the widget path. The path that matches
1039 // the same CSS rules as a saved context is like the path of |parentStyle|
1040 // but with an extra copy of the head (last) object appended. Setting
1041 // |parentStyle| as the parent context provides the same inheritance of
1042 // properties from the widget root node.
1043 const GtkWidgetPath
* parentPath
= gtk_style_context_get_path(parentStyle
);
1044 const gchar
* name
= sGtkWidgetPathIterGetObjectName
1045 ? sGtkWidgetPathIterGetObjectName(parentPath
, -1)
1047 GType objectType
= gtk_widget_path_get_object_type(parentPath
);
1049 GtkStyleContext
* style
= CreateCSSNode(name
, parentStyle
, objectType
);
1051 // Start with the same classes on the new node as were on |parentStyle|.
1052 // GTK puts no regions or junction_sides on widget root nodes, and so there
1053 // is no need to copy these.
1054 AddStyleClassesFromStyle(style
, parentStyle
);
1056 gtk_style_context_add_class(style
, aStyleClass
);
1060 /* GetCssNodeStyleInternal is used by Gtk >= 3.20 */
1061 static GtkStyleContext
* GetCssNodeStyleInternal(WidgetNodeType aNodeType
) {
1062 GtkStyleContext
* style
= sStyleStorage
[aNodeType
];
1063 if (style
) return style
;
1065 switch (aNodeType
) {
1066 case MOZ_GTK_SCROLLBAR_CONTENTS_VERTICAL
:
1067 style
= CreateChildCSSNode("contents", MOZ_GTK_SCROLLBAR_VERTICAL
);
1069 case MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL
:
1070 style
= CreateChildCSSNode(GTK_STYLE_CLASS_TROUGH
,
1071 MOZ_GTK_SCROLLBAR_CONTENTS_VERTICAL
);
1073 case MOZ_GTK_SCROLLBAR_THUMB_VERTICAL
:
1074 style
= CreateChildCSSNode(GTK_STYLE_CLASS_SLIDER
,
1075 MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL
);
1077 case MOZ_GTK_RADIOBUTTON
:
1078 style
= CreateChildCSSNode(GTK_STYLE_CLASS_RADIO
,
1079 MOZ_GTK_RADIOBUTTON_CONTAINER
);
1081 case MOZ_GTK_CHECKBUTTON
:
1082 style
= CreateChildCSSNode(GTK_STYLE_CLASS_CHECK
,
1083 MOZ_GTK_CHECKBUTTON_CONTAINER
);
1085 case MOZ_GTK_RADIOMENUITEM_INDICATOR
:
1086 style
= CreateChildCSSNode(GTK_STYLE_CLASS_RADIO
, MOZ_GTK_RADIOMENUITEM
);
1088 case MOZ_GTK_CHECKMENUITEM_INDICATOR
:
1089 style
= CreateChildCSSNode(GTK_STYLE_CLASS_CHECK
, MOZ_GTK_CHECKMENUITEM
);
1091 case MOZ_GTK_PROGRESS_TROUGH
:
1092 /* Progress bar background (trough) */
1093 style
= CreateChildCSSNode(GTK_STYLE_CLASS_TROUGH
, MOZ_GTK_PROGRESSBAR
);
1095 case MOZ_GTK_PROGRESS_CHUNK
:
1096 style
= CreateChildCSSNode("progress", MOZ_GTK_PROGRESS_TROUGH
);
1098 case MOZ_GTK_GRIPPER
:
1099 // TODO - create from CSS node
1100 style
= CreateSubStyleWithClass(MOZ_GTK_GRIPPER
, GTK_STYLE_CLASS_GRIP
);
1102 case MOZ_GTK_SPINBUTTON_ENTRY
:
1103 // TODO - create from CSS node
1105 CreateSubStyleWithClass(MOZ_GTK_SPINBUTTON
, GTK_STYLE_CLASS_ENTRY
);
1107 case MOZ_GTK_SCROLLED_WINDOW
:
1108 // TODO - create from CSS node
1109 style
= CreateSubStyleWithClass(MOZ_GTK_SCROLLED_WINDOW
,
1110 GTK_STYLE_CLASS_FRAME
);
1112 case MOZ_GTK_TEXT_VIEW_TEXT_SELECTION
:
1113 style
= CreateChildCSSNode("selection", MOZ_GTK_TEXT_VIEW_TEXT
);
1115 case MOZ_GTK_TEXT_VIEW_TEXT
:
1116 case MOZ_GTK_RESIZER
:
1117 style
= CreateChildCSSNode("text", MOZ_GTK_TEXT_VIEW
);
1118 if (aNodeType
== MOZ_GTK_RESIZER
) {
1119 // The "grip" class provides the correct builtin icon from
1120 // gtk_render_handle(). The icon is drawn with shaded variants of
1121 // the background color, and so a transparent background would lead to
1122 // a transparent resizer. gtk_render_handle() also uses the
1123 // background color to draw a background, and so this style otherwise
1124 // matches what is used in GtkTextView to match the background with
1125 // textarea elements.
1127 gtk_style_context_get_background_color(style
, GTK_STATE_FLAG_NORMAL
,
1129 if (color
.alpha
== 0.0) {
1130 g_object_unref(style
);
1131 style
= CreateStyleForWidget(gtk_text_view_new(),
1132 MOZ_GTK_SCROLLED_WINDOW
);
1134 gtk_style_context_add_class(style
, GTK_STYLE_CLASS_GRIP
);
1137 case MOZ_GTK_FRAME_BORDER
:
1138 style
= CreateChildCSSNode("border", MOZ_GTK_FRAME
);
1140 case MOZ_GTK_TREEVIEW_VIEW
:
1141 // TODO - create from CSS node
1142 style
= CreateSubStyleWithClass(MOZ_GTK_TREEVIEW
, GTK_STYLE_CLASS_VIEW
);
1144 case MOZ_GTK_TREEVIEW_EXPANDER
:
1145 // TODO - create from CSS node
1147 CreateSubStyleWithClass(MOZ_GTK_TREEVIEW
, GTK_STYLE_CLASS_EXPANDER
);
1149 case MOZ_GTK_SPLITTER_SEPARATOR_HORIZONTAL
:
1150 style
= CreateChildCSSNode("separator", MOZ_GTK_SPLITTER_HORIZONTAL
);
1152 case MOZ_GTK_SPLITTER_SEPARATOR_VERTICAL
:
1153 style
= CreateChildCSSNode("separator", MOZ_GTK_SPLITTER_VERTICAL
);
1155 case MOZ_GTK_SCALE_CONTENTS_HORIZONTAL
:
1156 style
= CreateChildCSSNode("contents", MOZ_GTK_SCALE_HORIZONTAL
);
1158 case MOZ_GTK_SCALE_CONTENTS_VERTICAL
:
1159 style
= CreateChildCSSNode("contents", MOZ_GTK_SCALE_VERTICAL
);
1161 case MOZ_GTK_SCALE_TROUGH_HORIZONTAL
:
1162 style
= CreateChildCSSNode(GTK_STYLE_CLASS_TROUGH
,
1163 MOZ_GTK_SCALE_CONTENTS_HORIZONTAL
);
1165 case MOZ_GTK_SCALE_TROUGH_VERTICAL
:
1166 style
= CreateChildCSSNode(GTK_STYLE_CLASS_TROUGH
,
1167 MOZ_GTK_SCALE_CONTENTS_VERTICAL
);
1169 case MOZ_GTK_SCALE_THUMB_HORIZONTAL
:
1170 style
= CreateChildCSSNode(GTK_STYLE_CLASS_SLIDER
,
1171 MOZ_GTK_SCALE_TROUGH_HORIZONTAL
);
1173 case MOZ_GTK_SCALE_THUMB_VERTICAL
:
1174 style
= CreateChildCSSNode(GTK_STYLE_CLASS_SLIDER
,
1175 MOZ_GTK_SCALE_TROUGH_VERTICAL
);
1177 case MOZ_GTK_TAB_TOP
: {
1178 // TODO - create from CSS node
1179 style
= CreateSubStyleWithClass(MOZ_GTK_NOTEBOOK
, GTK_STYLE_CLASS_TOP
);
1180 gtk_style_context_add_region(style
, GTK_STYLE_REGION_TAB
,
1181 static_cast<GtkRegionFlags
>(0));
1184 case MOZ_GTK_TAB_BOTTOM
: {
1185 // TODO - create from CSS node
1186 style
= CreateSubStyleWithClass(MOZ_GTK_NOTEBOOK
, GTK_STYLE_CLASS_BOTTOM
);
1187 gtk_style_context_add_region(style
, GTK_STYLE_REGION_TAB
,
1188 static_cast<GtkRegionFlags
>(0));
1191 case MOZ_GTK_NOTEBOOK
:
1192 case MOZ_GTK_NOTEBOOK_HEADER
:
1193 case MOZ_GTK_TABPANELS
:
1194 case MOZ_GTK_TAB_SCROLLARROW
: {
1195 // TODO - create from CSS node
1196 GtkWidget
* widget
= GetWidget(MOZ_GTK_NOTEBOOK
);
1197 return gtk_widget_get_style_context(widget
);
1199 case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE_RESTORE
: {
1201 false, "MOZ_GTK_HEADER_BAR_BUTTON_RESTORE is used as an icon only!");
1204 case MOZ_GTK_MENUPOPUP_DECORATION
: {
1205 GtkStyleContext
* parentStyle
=
1206 CreateSubStyleWithClass(MOZ_GTK_MENUPOPUP
, "csd");
1207 style
= CreateCSSNode("decoration", parentStyle
);
1208 g_object_unref(parentStyle
);
1211 case MOZ_GTK_WINDOW_DECORATION
: {
1212 GtkStyleContext
* parentStyle
=
1213 CreateSubStyleWithClass(MOZ_GTK_WINDOW
, "csd");
1214 style
= CreateCSSNode("decoration", parentStyle
);
1215 g_object_unref(parentStyle
);
1218 case MOZ_GTK_WINDOW_DECORATION_SOLID
: {
1219 GtkStyleContext
* parentStyle
=
1220 CreateSubStyleWithClass(MOZ_GTK_WINDOW
, "solid-csd");
1221 style
= CreateCSSNode("decoration", parentStyle
);
1222 g_object_unref(parentStyle
);
1226 return GetWidgetRootStyle(aNodeType
);
1229 MOZ_ASSERT(style
, "missing style context for node type");
1230 sStyleStorage
[aNodeType
] = style
;
1234 /* GetWidgetStyleInternal is used by Gtk < 3.20 */
1235 static GtkStyleContext
* GetWidgetStyleInternal(WidgetNodeType aNodeType
) {
1236 GtkStyleContext
* style
= sStyleStorage
[aNodeType
];
1237 if (style
) return style
;
1239 switch (aNodeType
) {
1240 case MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL
:
1241 style
= CreateSubStyleWithClass(MOZ_GTK_SCROLLBAR_VERTICAL
,
1242 GTK_STYLE_CLASS_TROUGH
);
1244 case MOZ_GTK_SCROLLBAR_THUMB_VERTICAL
:
1245 style
= CreateSubStyleWithClass(MOZ_GTK_SCROLLBAR_VERTICAL
,
1246 GTK_STYLE_CLASS_SLIDER
);
1248 case MOZ_GTK_RADIOBUTTON
:
1249 style
= CreateSubStyleWithClass(MOZ_GTK_RADIOBUTTON_CONTAINER
,
1250 GTK_STYLE_CLASS_RADIO
);
1252 case MOZ_GTK_CHECKBUTTON
:
1253 style
= CreateSubStyleWithClass(MOZ_GTK_CHECKBUTTON_CONTAINER
,
1254 GTK_STYLE_CLASS_CHECK
);
1256 case MOZ_GTK_RADIOMENUITEM_INDICATOR
:
1258 CreateSubStyleWithClass(MOZ_GTK_RADIOMENUITEM
, GTK_STYLE_CLASS_RADIO
);
1260 case MOZ_GTK_CHECKMENUITEM_INDICATOR
:
1262 CreateSubStyleWithClass(MOZ_GTK_CHECKMENUITEM
, GTK_STYLE_CLASS_CHECK
);
1264 case MOZ_GTK_PROGRESS_TROUGH
:
1266 CreateSubStyleWithClass(MOZ_GTK_PROGRESSBAR
, GTK_STYLE_CLASS_TROUGH
);
1268 case MOZ_GTK_PROGRESS_CHUNK
:
1269 style
= CreateSubStyleWithClass(MOZ_GTK_PROGRESSBAR
,
1270 GTK_STYLE_CLASS_PROGRESSBAR
);
1271 gtk_style_context_remove_class(style
, GTK_STYLE_CLASS_TROUGH
);
1273 case MOZ_GTK_GRIPPER
:
1274 style
= CreateSubStyleWithClass(MOZ_GTK_GRIPPER
, GTK_STYLE_CLASS_GRIP
);
1276 case MOZ_GTK_SPINBUTTON_ENTRY
:
1278 CreateSubStyleWithClass(MOZ_GTK_SPINBUTTON
, GTK_STYLE_CLASS_ENTRY
);
1280 case MOZ_GTK_SCROLLED_WINDOW
:
1281 style
= CreateSubStyleWithClass(MOZ_GTK_SCROLLED_WINDOW
,
1282 GTK_STYLE_CLASS_FRAME
);
1284 case MOZ_GTK_TEXT_VIEW_TEXT
:
1285 case MOZ_GTK_RESIZER
:
1286 // GTK versions prior to 3.20 do not have the view class on the root
1287 // node, but add this to determine the background for the text window.
1288 style
= CreateSubStyleWithClass(MOZ_GTK_TEXT_VIEW
, GTK_STYLE_CLASS_VIEW
);
1289 if (aNodeType
== MOZ_GTK_RESIZER
) {
1290 // The "grip" class provides the correct builtin icon from
1291 // gtk_render_handle(). The icon is drawn with shaded variants of
1292 // the background color, and so a transparent background would lead to
1293 // a transparent resizer. gtk_render_handle() also uses the
1294 // background color to draw a background, and so this style otherwise
1295 // matches MOZ_GTK_TEXT_VIEW_TEXT to match the background with
1296 // textarea elements. GtkTextView creates a separate text window and
1297 // so the background should not be transparent.
1298 gtk_style_context_add_class(style
, GTK_STYLE_CLASS_GRIP
);
1301 case MOZ_GTK_FRAME_BORDER
:
1302 return GetWidgetRootStyle(MOZ_GTK_FRAME
);
1303 case MOZ_GTK_TREEVIEW_VIEW
:
1304 style
= CreateSubStyleWithClass(MOZ_GTK_TREEVIEW
, GTK_STYLE_CLASS_VIEW
);
1306 case MOZ_GTK_TREEVIEW_EXPANDER
:
1308 CreateSubStyleWithClass(MOZ_GTK_TREEVIEW
, GTK_STYLE_CLASS_EXPANDER
);
1310 case MOZ_GTK_SPLITTER_SEPARATOR_HORIZONTAL
:
1311 style
= CreateSubStyleWithClass(MOZ_GTK_SPLITTER_HORIZONTAL
,
1312 GTK_STYLE_CLASS_PANE_SEPARATOR
);
1314 case MOZ_GTK_SPLITTER_SEPARATOR_VERTICAL
:
1315 style
= CreateSubStyleWithClass(MOZ_GTK_SPLITTER_VERTICAL
,
1316 GTK_STYLE_CLASS_PANE_SEPARATOR
);
1318 case MOZ_GTK_SCALE_TROUGH_HORIZONTAL
:
1319 style
= CreateSubStyleWithClass(MOZ_GTK_SCALE_HORIZONTAL
,
1320 GTK_STYLE_CLASS_TROUGH
);
1322 case MOZ_GTK_SCALE_TROUGH_VERTICAL
:
1323 style
= CreateSubStyleWithClass(MOZ_GTK_SCALE_VERTICAL
,
1324 GTK_STYLE_CLASS_TROUGH
);
1326 case MOZ_GTK_SCALE_THUMB_HORIZONTAL
:
1327 style
= CreateSubStyleWithClass(MOZ_GTK_SCALE_HORIZONTAL
,
1328 GTK_STYLE_CLASS_SLIDER
);
1330 case MOZ_GTK_SCALE_THUMB_VERTICAL
:
1331 style
= CreateSubStyleWithClass(MOZ_GTK_SCALE_VERTICAL
,
1332 GTK_STYLE_CLASS_SLIDER
);
1334 case MOZ_GTK_TAB_TOP
:
1335 style
= CreateSubStyleWithClass(MOZ_GTK_NOTEBOOK
, GTK_STYLE_CLASS_TOP
);
1336 gtk_style_context_add_region(style
, GTK_STYLE_REGION_TAB
,
1337 static_cast<GtkRegionFlags
>(0));
1339 case MOZ_GTK_TAB_BOTTOM
:
1340 style
= CreateSubStyleWithClass(MOZ_GTK_NOTEBOOK
, GTK_STYLE_CLASS_BOTTOM
);
1341 gtk_style_context_add_region(style
, GTK_STYLE_REGION_TAB
,
1342 static_cast<GtkRegionFlags
>(0));
1344 case MOZ_GTK_NOTEBOOK
:
1345 case MOZ_GTK_NOTEBOOK_HEADER
:
1346 case MOZ_GTK_TABPANELS
:
1347 case MOZ_GTK_TAB_SCROLLARROW
: {
1348 GtkWidget
* widget
= GetWidget(MOZ_GTK_NOTEBOOK
);
1349 return gtk_widget_get_style_context(widget
);
1352 return GetWidgetRootStyle(aNodeType
);
1356 sStyleStorage
[aNodeType
] = style
;
1360 void ResetWidgetCache() {
1361 for (int i
= 0; i
< MOZ_GTK_WIDGET_NODE_COUNT
; i
++) {
1362 if (sStyleStorage
[i
]) g_object_unref(sStyleStorage
[i
]);
1364 mozilla::PodArrayZero(sStyleStorage
);
1366 gCSDStyle
= CSDStyle::Unknown
;
1368 /* This will destroy all of our widgets */
1369 if (sWidgetStorage
[MOZ_GTK_WINDOW
]) {
1370 gtk_widget_destroy(sWidgetStorage
[MOZ_GTK_WINDOW
]);
1372 if (sWidgetStorage
[MOZ_GTK_HEADERBAR_WINDOW
]) {
1373 gtk_widget_destroy(sWidgetStorage
[MOZ_GTK_HEADERBAR_WINDOW
]);
1375 if (sWidgetStorage
[MOZ_GTK_HEADERBAR_WINDOW_MAXIMIZED
]) {
1376 gtk_widget_destroy(sWidgetStorage
[MOZ_GTK_HEADERBAR_WINDOW_MAXIMIZED
]);
1379 /* Clear already freed arrays */
1380 mozilla::PodArrayZero(sWidgetStorage
);
1383 GtkStyleContext
* GetStyleContext(WidgetNodeType aNodeType
, int aScale
,
1384 GtkTextDirection aDirection
,
1385 GtkStateFlags aStateFlags
) {
1386 GtkStyleContext
* style
;
1387 if (gtk_check_version(3, 20, 0) != nullptr) {
1388 style
= GetWidgetStyleInternal(aNodeType
);
1390 style
= GetCssNodeStyleInternal(aNodeType
);
1391 StyleContextSetScale(style
, aScale
);
1393 bool stateChanged
= false;
1394 bool stateHasDirection
= gtk_get_minor_version() >= 8;
1395 GtkStateFlags oldState
= gtk_style_context_get_state(style
);
1396 MOZ_ASSERT(!(aStateFlags
& (STATE_FLAG_DIR_LTR
| STATE_FLAG_DIR_RTL
)));
1397 unsigned newState
= aStateFlags
;
1398 if (stateHasDirection
) {
1399 switch (aDirection
) {
1400 case GTK_TEXT_DIR_LTR
:
1401 newState
|= STATE_FLAG_DIR_LTR
;
1403 case GTK_TEXT_DIR_RTL
:
1404 newState
|= STATE_FLAG_DIR_RTL
;
1407 MOZ_FALLTHROUGH_ASSERT("Bad GtkTextDirection");
1408 case GTK_TEXT_DIR_NONE
:
1409 // GtkWidget uses a default direction if neither is explicitly
1410 // specified, but here DIR_NONE is interpreted as meaning the
1411 // direction is not important, so don't change the direction
1413 newState
|= oldState
& (STATE_FLAG_DIR_LTR
| STATE_FLAG_DIR_RTL
);
1415 } else if (aDirection
!= GTK_TEXT_DIR_NONE
) {
1416 GtkTextDirection oldDirection
= gtk_style_context_get_direction(style
);
1417 if (aDirection
!= oldDirection
) {
1418 gtk_style_context_set_direction(style
, aDirection
);
1419 stateChanged
= true;
1422 if (oldState
!= newState
) {
1423 gtk_style_context_set_state(style
, static_cast<GtkStateFlags
>(newState
));
1424 stateChanged
= true;
1426 // This invalidate is necessary for unsaved style contexts from GtkWidgets
1427 // in pre-3.18 GTK, because automatic invalidation of such contexts
1428 // was delayed until a resize event runs.
1430 // https://bugzilla.mozilla.org/show_bug.cgi?id=1272194#c7
1432 // Avoid calling invalidate on contexts that are not owned and constructed
1433 // by widgets to avoid performing build_properties() (in 3.16 stylecontext.c)
1434 // unnecessarily early.
1435 if (stateChanged
&& sWidgetStorage
[aNodeType
]) {
1436 gtk_style_context_invalidate(style
);
1441 GtkStyleContext
* CreateStyleContextWithStates(WidgetNodeType aNodeType
,
1443 GtkTextDirection aDirection
,
1444 GtkStateFlags aStateFlags
) {
1445 GtkStyleContext
* style
=
1446 GetStyleContext(aNodeType
, aScale
, aDirection
, aStateFlags
);
1447 GtkWidgetPath
* path
= gtk_widget_path_copy(gtk_style_context_get_path(style
));
1449 static auto sGtkWidgetPathIterGetState
=
1450 (GtkStateFlags(*)(const GtkWidgetPath
*, gint
))dlsym(
1451 RTLD_DEFAULT
, "gtk_widget_path_iter_get_state");
1452 static auto sGtkWidgetPathIterSetState
=
1453 (void (*)(GtkWidgetPath
*, gint
, GtkStateFlags
))dlsym(
1454 RTLD_DEFAULT
, "gtk_widget_path_iter_set_state");
1456 int pathLength
= gtk_widget_path_length(path
);
1457 for (int i
= 0; i
< pathLength
; i
++) {
1458 unsigned state
= aStateFlags
| sGtkWidgetPathIterGetState(path
, i
);
1459 sGtkWidgetPathIterSetState(path
, i
, GtkStateFlags(state
));
1462 style
= gtk_style_context_new();
1463 gtk_style_context_set_path(style
, path
);
1464 gtk_widget_path_unref(path
);
1469 void StyleContextSetScale(GtkStyleContext
* style
, gint aScaleFactor
) {
1470 // Support HiDPI styles on Gtk 3.20+
1471 static auto sGtkStyleContextSetScalePtr
=
1472 (void (*)(GtkStyleContext
*, gint
))dlsym(RTLD_DEFAULT
,
1473 "gtk_style_context_set_scale");
1474 if (sGtkStyleContextSetScalePtr
&& style
) {
1475 sGtkStyleContextSetScalePtr(style
, aScaleFactor
);
1479 bool HeaderBarShouldDrawContainer(WidgetNodeType aNodeType
) {
1480 MOZ_ASSERT(aNodeType
== MOZ_GTK_HEADER_BAR
||
1481 aNodeType
== MOZ_GTK_HEADER_BAR_MAXIMIZED
);
1482 mozilla::Unused
<< GetWidget(aNodeType
);
1483 return aNodeType
== MOZ_GTK_HEADER_BAR
1484 ? gHeaderBarShouldDrawContainer
1485 : gMaximizedHeaderBarShouldDrawContainer
;
1488 gint
GetBorderRadius(GtkStyleContext
* aStyle
) {
1489 GValue value
= G_VALUE_INIT
;
1490 // NOTE(emilio): In an ideal world, we'd query the two longhands
1491 // (border-top-left-radius and border-top-right-radius) separately. However,
1492 // that doesn't work (GTK rejects the query with:
1494 // Style property "border-top-left-radius" is not gettable
1496 // However! Getting border-radius does work, and it does return the
1497 // border-top-left-radius as a gint:
1499 // https://docs.gtk.org/gtk3/const.STYLE_PROPERTY_BORDER_RADIUS.html
1500 // https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-3-20/gtk/gtkcssshorthandpropertyimpl.c#L961-977
1502 // So we abuse this fact, and make the assumption here that the
1503 // border-top-{left,right}-radius are the same, and roll with it.
1504 gtk_style_context_get_property(aStyle
, "border-radius", GTK_STATE_FLAG_NORMAL
,
1507 auto type
= G_VALUE_TYPE(&value
);
1508 if (type
== G_TYPE_INT
) {
1509 result
= g_value_get_int(&value
);
1511 NS_WARNING(nsPrintfCString("Unknown value type %lu for border-radius", type
)
1514 g_value_unset(&value
);