CSS

Gtk’s CSS engine is dispatched to to render nearly all of an app’s UI, but Odysseus only calls it directly to make it’s “status buttons” look like they’re inside the rightside “addressbar”. These status icons are used as a trusted UI to communicate security and other information.

This evening I would like to discuss how GTK’s CSS engine works, and compare it to both WebKit’s and my own CSS engines to make this more interesting.

GTK widgets accesses GTK’s CSS engine via a GtkStyleContext, which in turn is a shallow abstraction around a GtkStyleCascade and it’s GtkCSSNode input. Plus it maps the output into Cairo (or for GTK4, GSK) drawing routines.

It’s central and most complex method “.peek_style_property(GType, GParamSpec)” essentially just wraps Gtk.StyleCascade.get_style_property() with a binary-searched cache.

GtkStyleCascade in turn is an implementation GtkStyleProvider that wraps multiple of them.

GtkCSSProvider is the main implementation of GtkStyleProvider (initialized by GtkSettings), who’s own code is mostly dedicated to parsing CSS files at known filepaths. Which it does using GtkCSSParser for reading common CSS tokens, and the embedded GtkCSSScanner class maintains a stack of GtkCSSSections for error recovery.

Parsing the properties is dispatched via a hashmap GtkStyleProperty class property.

Next there’s some post-processing after a CSS file is parsed, involving:

  1. Sorting the style rules, by dispatching to it’s selector (the parser ensures one selector per style rule).
  2. Restructure the selectors into a tree structure, by repeatedly: a) Choosing an optimal “simple selector” to branch on b) Adding that branch to the flat tree c) Insert the child then tail into the flat tree
  3. Fixup any pointers in that tree.

GTK interprets the selectors by using iteration and dynamic dispatch over the individual tests, which in turn performs the tests on a GtkWidgetPath/GtkCSSNode via a GtkCSSMatcher.

For CSS cascade and inheritance GTK maps each CSS property to an index into an array, and uses a bloom filter to quickly check if it should bother examing each selected style rule. The inherited properties are passed into the .get_style_property() method.

Finally some CSS values do need to be interpreted into more primitive values, which GTK does via dynamic dispatch. Or for animations these’ll be applied by the GtkCSSNode storing the input & output via a suite of helper classes.