]> Pileus Git - ~andy/gtk/blobdiff - gtk/gtkcssselector.c
stylecontext: Do invalidation on first resize container
[~andy/gtk] / gtk / gtkcssselector.c
index 71247c0cc368cbb586519bfef4c61285f63a2699..216090faaddccaeba251026712d5066ddac2c63e 100644 (file)
 
 #include "gtkcssselectorprivate.h"
 
+#include <stdlib.h>
+#include <string.h>
+
 #include "gtkcssprovider.h"
 #include "gtkstylecontextprivate.h"
 
-typedef enum {
-  GTK_CSS_COMBINE_DESCANDANT,
-  GTK_CSS_COMBINE_CHILD
-} GtkCssCombinator;
+/* When checking for changes via the tree we need to know if a rule further
+   down the tree matched, because if so we need to add "our bit" to the
+   Change. For instance in a a match like *.class:active we'll
+   get a tree that first checks :active, if that matches we continue down
+   to the tree, and if we get a match we add CHANGE_CLASS. However, the
+   end of the tree where we have a match is an ANY which doesn't actually
+   modify the change, so we don't know if we have a match or not. We fix
+   this by setting GTK_CSS_CHANGE_GOT_MATCH which lets us guarantee
+   that change != 0 on any match. */
+#define GTK_CSS_CHANGE_GOT_MATCH GTK_CSS_CHANGE_RESERVED_BIT
+
+typedef struct _GtkCssSelectorClass GtkCssSelectorClass;
+
+struct _GtkCssSelectorClass {
+  const char        *name;
+
+  void              (* print)       (const GtkCssSelector       *selector,
+                                     GString                    *string);
+  gboolean          (* match)       (const GtkCssSelector       *selector,
+                                     const GtkCssMatcher        *matcher);
+  void              (* tree_match)  (const GtkCssSelectorTree   *tree,
+                                     const GtkCssMatcher        *matcher,
+                                    GHashTable                  *res);
+  GtkCssChange      (* get_change)  (const GtkCssSelector       *selector,
+                                    GtkCssChange                previous_change);
+  GtkCssChange      (* tree_get_change)  (const GtkCssSelectorTree *tree,
+                                         const GtkCssMatcher      *matcher);
+  int               (* compare_one) (const GtkCssSelector       *a,
+                                    const GtkCssSelector       *b);
+
+  guint         increase_id_specificity :1;
+  guint         increase_class_specificity :1;
+  guint         increase_element_specificity :1;
+  guint         is_simple :1;
+  guint         must_keep_order :1; /* Due to region weirdness these must be kept before a DESCENDANT, so don't reorder */
+};
 
 struct _GtkCssSelector
 {
-  GtkCssSelector *  previous;        /* link to next element in selector or NULL if last */
-  GtkCssCombinator  combine;         /* how to combine with the previous element */
-  const char *      name;            /* quarked name of element we match or NULL if any */
-  GType             type;            /* cache for type belonging to name - G_TYPE_INVALID if uncached */
-  GQuark *          ids;             /* 0-terminated list of required ids or NULL if none */
-  GQuark *          classes;         /* 0-terminated list of required classes or NULL if none */
-  GtkRegionFlags    pseudo_classes;  /* required pseudo classes */
-  GtkStateFlags     state;           /* required state flags (currently not checked when matching) */
+  const GtkCssSelectorClass *class;       /* type of check this selector does */
+  gconstpointer              data;        /* data for matching:
+                                             - interned string for CLASS, NAME and ID
+                                             - GUINT_TO_POINTER() for PSEUDOCLASS_REGION/STATE */
 };
 
-static GtkCssSelector *
-gtk_css_selector_new (GtkCssSelector         *previous,
-                      GtkCssCombinator        combine,
-                      const char *            name,
-                      GQuark *                ids,
-                      GQuark *                classes,
-                      GtkRegionFlags          pseudo_classes,
-                      GtkStateFlags           state)
-{
-  GtkCssSelector *selector;
-
-  selector = g_slice_new0 (GtkCssSelector);
-  selector->previous = previous;
-  selector->combine = combine;
-  selector->name = name ? g_quark_to_string (g_quark_from_string (name)) : NULL;
-  selector->type = !name || _gtk_style_context_check_region_name (name) ? G_TYPE_NONE : G_TYPE_INVALID;
-  selector->ids = ids;
-  selector->classes = classes;
-  selector->pseudo_classes = pseudo_classes;
-  selector->state = state;
+#define GTK_CSS_SELECTOR_TREE_EMPTY_OFFSET G_MAXINT32
+struct _GtkCssSelectorTree
+{
+  GtkCssSelector selector;
+  gint32 parent_offset;
+  gint32 previous_offset;
+  gint32 sibling_offset;
+  gint32 matches_offset; /* pointers that we return as matches if selector matches */
+};
 
-  return selector;
+static gboolean
+gtk_css_selector_equal (const GtkCssSelector *a,
+                       const GtkCssSelector *b)
+{
+  return
+    a->class == b->class &&
+    a->data == b->data;
 }
 
-static gboolean
-parse_selector_class (GtkCssParser *parser, GArray *classes)
+static guint
+gtk_css_selector_hash (const GtkCssSelector *selector)
 {
-  GQuark qname;
-  char *name;
-    
-  name = _gtk_css_parser_try_name (parser, FALSE);
+  return GPOINTER_TO_UINT (selector->class) ^ GPOINTER_TO_UINT (selector->data);
+}
 
-  if (name == NULL)
-    {
-      _gtk_css_parser_error (parser, "Expected a valid name for class");
-      return FALSE;
-    }
+static gpointer *
+gtk_css_selector_tree_get_matches (const GtkCssSelectorTree *tree)
+{
+  if (tree->matches_offset == GTK_CSS_SELECTOR_TREE_EMPTY_OFFSET)
+    return NULL;
 
-  qname = g_quark_from_string (name);
-  g_array_append_val (classes, qname);
-  g_free (name);
-  return TRUE;
+  return (gpointer *) ((guint8 *)tree + tree->matches_offset);
 }
 
-static gboolean
-parse_selector_name (GtkCssParser *parser, GArray *names)
+static void
+gtk_css_selector_tree_found_match (const GtkCssSelectorTree *tree,
+                                  GHashTable *res)
 {
-  GQuark qname;
-  char *name;
-    
-  name = _gtk_css_parser_try_name (parser, FALSE);
+  int i;
+  gpointer *matches;
 
-  if (name == NULL)
+  matches = gtk_css_selector_tree_get_matches (tree);
+  if (matches)
     {
-      _gtk_css_parser_error (parser, "Expected a valid name for id");
-      return FALSE;
+      for (i = 0; matches[i] != NULL; i++)
+       g_hash_table_insert (res, matches[i], matches[i]);
     }
-
-  qname = g_quark_from_string (name);
-  g_array_append_val (names, qname);
-  g_free (name);
-  return TRUE;
 }
 
-static gboolean
-parse_selector_pseudo_class (GtkCssParser   *parser,
-                             GtkRegionFlags *region_to_modify,
-                             GtkStateFlags  *state_to_modify)
+static void
+gtk_css_selector_tree_match (const GtkCssSelectorTree *tree,
+                            const GtkCssMatcher  *matcher,
+                            GHashTable *res)
 {
-  struct {
-    const char *name;
-    GtkRegionFlags region_flag;
-    GtkStateFlags state_flag;
-  } pseudo_classes[] = {
-    { "first-child",  GTK_REGION_FIRST, 0 },
-    { "last-child",   GTK_REGION_LAST, 0 },
-    { "only-child",   GTK_REGION_ONLY, 0 },
-    { "sorted",       GTK_REGION_SORTED, 0 },
-    { "active",       0, GTK_STATE_FLAG_ACTIVE },
-    { "prelight",     0, GTK_STATE_FLAG_PRELIGHT },
-    { "hover",        0, GTK_STATE_FLAG_PRELIGHT },
-    { "selected",     0, GTK_STATE_FLAG_SELECTED },
-    { "insensitive",  0, GTK_STATE_FLAG_INSENSITIVE },
-    { "inconsistent", 0, GTK_STATE_FLAG_INCONSISTENT },
-    { "focused",      0, GTK_STATE_FLAG_FOCUSED },
-    { "focus",        0, GTK_STATE_FLAG_FOCUSED },
-    { "backdrop",     0, GTK_STATE_FLAG_BACKDROP },
-    { NULL, }
-  }, nth_child_classes[] = {
-    { "first",        GTK_REGION_FIRST, 0 },
-    { "last",         GTK_REGION_LAST, 0 },
-    { "even",         GTK_REGION_EVEN, 0 },
-    { "odd",          GTK_REGION_ODD, 0 },
-    { NULL, }
-  }, *classes;
-  guint i;
-  char *name;
-  GError *error;
+  if (tree == NULL)
+    return;
 
-  name = _gtk_css_parser_try_ident (parser, FALSE);
-  if (name == NULL)
-    {
-      _gtk_css_parser_error (parser, "Missing name of pseudo-class");
-      return FALSE;
-    }
+  tree->selector.class->tree_match (tree, matcher, res);
+}
 
-  if (_gtk_css_parser_try (parser, "(", TRUE))
-    {
-      char *function = name;
+static GtkCssChange
+gtk_css_selector_tree_get_change (const GtkCssSelectorTree *tree,
+                                 const GtkCssMatcher      *matcher)
+{
+  if (tree == NULL)
+    return 0;
 
-      name = _gtk_css_parser_try_ident (parser, TRUE);
-      if (!_gtk_css_parser_try (parser, ")", FALSE))
-        {
-          _gtk_css_parser_error (parser, "Missing closing bracket for pseudo-class");
-          return FALSE;
-        }
+  return tree->selector.class->tree_get_change (tree, matcher);
+}
 
-      if (g_ascii_strcasecmp (function, "nth-child") != 0)
-        {
-          error = g_error_new (GTK_CSS_PROVIDER_ERROR,
-                               GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE,
-                               "Unknown pseudo-class '%s(%s)'", function, name ? name : "");
-          _gtk_css_parser_take_error (parser, error);
-          g_free (function);
-          g_free (name);
-          return FALSE;
-        }
-      
-      g_free (function);
-    
-      if (name == NULL)
-        {
-          error = g_error_new (GTK_CSS_PROVIDER_ERROR,
-                               GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE,
-                               "Unknown pseudo-class 'nth-child(%s)'", name);
-          _gtk_css_parser_take_error (parser, error);
-          return FALSE;
-        }
+static gboolean
+gtk_css_selector_match (const GtkCssSelector *selector,
+                        const GtkCssMatcher  *matcher)
+{
+  if (selector == NULL)
+    return TRUE;
 
-      classes = nth_child_classes;
-    }
-  else
-    classes = pseudo_classes;
+  return selector->class->match (selector, matcher);
+}
 
-  for (i = 0; classes[i].name != NULL; i++)
-    {
-      if (g_ascii_strcasecmp (name, classes[i].name) == 0)
-        {
-          if ((*region_to_modify & classes[i].region_flag) ||
-              (*state_to_modify & classes[i].state_flag))
-            {
-              if (classes == nth_child_classes)
-                _gtk_css_parser_error (parser, "Duplicate pseudo-class 'nth-child(%s)'", name);
-              else
-                _gtk_css_parser_error (parser, "Duplicate pseudo-class '%s'", name);
-            }
-          *region_to_modify |= classes[i].region_flag;
-          *state_to_modify |= classes[i].state_flag;
+static int
+gtk_css_selector_compare_one (const GtkCssSelector *a, const GtkCssSelector *b)
+{
+  if (a->class != b->class)
+    return strcmp (a->class->name, b->class->name);
+  else
+    return a->class->compare_one (a, b);
+}
+  
+static const GtkCssSelector *
+gtk_css_selector_previous (const GtkCssSelector *selector)
+{
+  selector = selector + 1;
 
-          g_free (name);
-          return TRUE;
-        }
-    }
+  return selector->class ? selector : NULL;
+}
 
-  if (classes == nth_child_classes)
-    error = g_error_new (GTK_CSS_PROVIDER_ERROR,
-                         GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE,
-                         "Unknown pseudo-class 'nth-child(%s)'", name);
-  else
-    error = g_error_new (GTK_CSS_PROVIDER_ERROR,
-                         GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE,
-                         "Unknown pseudo-class '%s'", name);
+static const GtkCssSelectorTree *
+gtk_css_selector_tree_at_offset (const GtkCssSelectorTree *tree,
+                                gint32 offset)
+{
+  if (offset == GTK_CSS_SELECTOR_TREE_EMPTY_OFFSET)
+    return NULL;
 
-  g_free (name);
-  _gtk_css_parser_take_error (parser, error);
+  return (GtkCssSelectorTree *) ((guint8 *)tree + offset);
+}
 
-  return FALSE;
+static const GtkCssSelectorTree *
+gtk_css_selector_tree_get_parent (const GtkCssSelectorTree *tree)
+{
+  return gtk_css_selector_tree_at_offset (tree, tree->parent_offset);
 }
 
-static gboolean
-parse_simple_selector (GtkCssParser *parser,
-                       char **name,
-                       GArray *ids,
-                       GArray *classes,
-                       GtkRegionFlags *pseudo_classes,
-                       GtkStateFlags *state)
-{
-  gboolean parsed_something;
-  
-  *name = _gtk_css_parser_try_ident (parser, FALSE);
-  if (*name)
-    parsed_something = TRUE;
-  else
-    parsed_something = _gtk_css_parser_try (parser, "*", FALSE);
+static const GtkCssSelectorTree *
+gtk_css_selector_tree_get_previous (const GtkCssSelectorTree *tree)
+{
+  return gtk_css_selector_tree_at_offset (tree, tree->previous_offset);
+}
 
-  do {
-      if (_gtk_css_parser_try (parser, "#", FALSE))
-        {
-          if (!parse_selector_name (parser, ids))
-            return FALSE;
-        }
-      else if (_gtk_css_parser_try (parser, ".", FALSE))
-        {
-          if (!parse_selector_class (parser, classes))
-            return FALSE;
-        }
-      else if (_gtk_css_parser_try (parser, ":", FALSE))
-        {
-          if (!parse_selector_pseudo_class (parser, pseudo_classes, state))
-            return FALSE;
-        }
-      else if (!parsed_something)
-        {
-          _gtk_css_parser_error (parser, "Expected a valid selector");
-          return FALSE;
-        }
-      else
-        break;
+static const GtkCssSelectorTree *
+gtk_css_selector_tree_get_sibling (const GtkCssSelectorTree *tree)
+{
+  return gtk_css_selector_tree_at_offset (tree, tree->sibling_offset);
+}
 
-      parsed_something = TRUE;
-    }
-  while (!_gtk_css_parser_is_eof (parser));
+static void
+gtk_css_selector_tree_match_previous (const GtkCssSelectorTree *tree,
+                                     const GtkCssMatcher *matcher,
+                                     GHashTable *res)
+{
+  const GtkCssSelectorTree *prev;
 
-  _gtk_css_parser_skip_whitespace (parser);
-  return TRUE;
+  for (prev = gtk_css_selector_tree_get_previous (tree);
+       prev != NULL;
+       prev = gtk_css_selector_tree_get_sibling (prev))
+    gtk_css_selector_tree_match (prev, matcher, res);
 }
 
-GtkCssSelector *
-_gtk_css_selector_parse (GtkCssParser *parser)
+static GtkCssChange
+gtk_css_selector_tree_get_previous_change (const GtkCssSelectorTree *tree,
+                                          const GtkCssMatcher      *matcher)
 {
-  GtkCssSelector *selector = NULL;
+  GtkCssChange previous_change = 0;
+  const GtkCssSelectorTree *prev;
 
-  do {
-      char *name = NULL;
-      GArray *ids = g_array_new (TRUE, FALSE, sizeof (GQuark));
-      GArray *classes = g_array_new (TRUE, FALSE, sizeof (GQuark));
-      GtkRegionFlags pseudo_classes = 0;
-      GtkStateFlags state = 0;
-      GtkCssCombinator combine = GTK_CSS_COMBINE_DESCANDANT;
-
-      if (selector)
-        {
-          if (_gtk_css_parser_try (parser, ">", TRUE))
-            combine = GTK_CSS_COMBINE_CHILD;
-        }
+  for (prev = gtk_css_selector_tree_get_previous (tree);
+       prev != NULL;
+       prev = gtk_css_selector_tree_get_sibling (prev))
+    previous_change |= gtk_css_selector_tree_get_change (prev, matcher);
 
-      if (!parse_simple_selector (parser, &name, ids, classes, &pseudo_classes, &state))
-        {
-          g_array_free (ids, TRUE);
-          g_array_free (classes, TRUE);
-          if (selector)
-            _gtk_css_selector_free (selector);
-          return NULL;
-        }
+  return previous_change;
+}
 
-      selector = gtk_css_selector_new (selector,
-                                       combine,
-                                       name,
-                                       (GQuark *) g_array_free (ids, ids->len == 0),
-                                       (GQuark *) g_array_free (classes, classes->len == 0),
-                                       pseudo_classes,
-                                       state);
-      g_free (name);
-    }
-  while (!_gtk_css_parser_is_eof (parser) &&
-         !_gtk_css_parser_begins_with (parser, ',') &&
-         !_gtk_css_parser_begins_with (parser, '{'));
+/* DESCENDANT */
 
-  return selector;
+static void
+gtk_css_selector_descendant_print (const GtkCssSelector *selector,
+                                   GString              *string)
+{
+  g_string_append_c (string, ' ');
 }
 
-void
-_gtk_css_selector_free (GtkCssSelector *selector)
+static gboolean
+gtk_css_selector_descendant_match (const GtkCssSelector *selector,
+                                   const GtkCssMatcher  *matcher)
 {
-  g_return_if_fail (selector != NULL);
+  GtkCssMatcher ancestor;
 
-  if (selector->previous)
-    _gtk_css_selector_free (selector->previous);
+  while (_gtk_css_matcher_get_parent (&ancestor, matcher))
+    {
+      matcher = &ancestor;
 
-  g_free (selector->ids);
-  g_free (selector->classes);
+      if (gtk_css_selector_match (gtk_css_selector_previous (selector), matcher))
+        return TRUE;
+    }
 
-  g_slice_free (GtkCssSelector, selector);
+  return FALSE;
 }
 
-void
-_gtk_css_selector_print (const GtkCssSelector *selector,
-                         GString *             str)
+static void
+gtk_css_selector_descendant_tree_match (const GtkCssSelectorTree *tree,
+                                       const GtkCssMatcher  *matcher,
+                                       GHashTable *res)
 {
-  if (selector->previous)
+  GtkCssMatcher ancestor;
+
+  while (_gtk_css_matcher_get_parent (&ancestor, matcher))
     {
-      _gtk_css_selector_print (selector->previous, str);
-      switch (selector->combine)
-        {
-          case GTK_CSS_COMBINE_DESCANDANT:
-            g_string_append (str, " ");
-            break;
-          case GTK_CSS_COMBINE_CHILD:
-            g_string_append (str, " > ");
-            break;
-          default:
-            g_assert_not_reached ();
-        }
+      matcher = &ancestor;
+
+      gtk_css_selector_tree_match_previous (tree, matcher, res);
+
+      /* any matchers are dangerous here, as we may loop forever, but
+        we can terminate now as all possible matches have already been added */
+      if (_gtk_css_matcher_matches_any (matcher))
+       break;
     }
+}
 
-  if (selector->name)
-    g_string_append (str, selector->name);
-  else if (selector->ids == NULL && 
-           selector->classes == NULL && 
-           selector->pseudo_classes == 0 &&
-           selector->state == 0)
-    g_string_append (str, "*");
+static GtkCssChange
+gtk_css_selector_descendant_tree_get_change (const GtkCssSelectorTree *tree,
+                                            const GtkCssMatcher  *matcher)
+{
+  GtkCssMatcher ancestor;
+  GtkCssChange change, previous_change;
 
-  if (selector->ids)
+  change = 0;
+  previous_change = 0;
+  while (_gtk_css_matcher_get_parent (&ancestor, matcher))
     {
-      GQuark *id;
+      matcher = &ancestor;
 
-      for (id = selector->ids; *id != 0; id++)
-        {
-          g_string_append_c (str, '#');
-          g_string_append (str, g_quark_to_string (*id));
-        }
+      previous_change |= gtk_css_selector_tree_get_previous_change (tree, matcher);
+
+      /* any matchers are dangerous here, as we may loop forever, but
+        we can terminate now as all possible matches have already been added */
+      if (_gtk_css_matcher_matches_any (matcher))
+       break;
     }
 
-  if (selector->classes)
-    {
-      GQuark *class;
+  if (previous_change != 0)
+    change |= _gtk_css_change_for_child (previous_change) | GTK_CSS_CHANGE_GOT_MATCH;
 
-      for (class = selector->classes; *class != 0; class++)
-        {
-          g_string_append_c (str, '.');
-          g_string_append (str, g_quark_to_string (*class));
-        }
-    }
+  return change;
+}
 
-  if (selector->pseudo_classes)
-    {
-      static const char * flag_names[] = {
-        "nth-child(even)",
-        "nth-child(odd)",
-        "first-child",
-        "last-child",
-        "only-child",
-        "sorted"
-      };
-      guint i;
+static int
+gtk_css_selector_descendant_compare_one (const GtkCssSelector *a,
+                                        const GtkCssSelector *b)
+{
+  return 0;
+}
+  
+static GtkCssChange
+gtk_css_selector_descendant_get_change (const GtkCssSelector *selector, GtkCssChange previous_change)
+{
+  return _gtk_css_change_for_child (previous_change);
+}
 
-      for (i = 0; i < G_N_ELEMENTS (flag_names); i++)
-        {
-          if (selector->pseudo_classes & (1 << i))
-            {
-              g_string_append_c (str, ':');
-              g_string_append (str, flag_names[i]);
-            }
-        }
-    }
+static const GtkCssSelectorClass GTK_CSS_SELECTOR_DESCENDANT = {
+  "descendant",
+  gtk_css_selector_descendant_print,
+  gtk_css_selector_descendant_match,
+  gtk_css_selector_descendant_tree_match,
+  gtk_css_selector_descendant_get_change,
+  gtk_css_selector_descendant_tree_get_change,
+  gtk_css_selector_descendant_compare_one,
+  FALSE, FALSE, FALSE, FALSE, FALSE
+};
 
-  if (selector->state)
-    {
-      static const char * state_names[] = {
-        "active",
-        "hover",
-        "selected",
-        "insensitive",
-        "inconsistent",
-        "focus",
-        "backdrop"
-      };
-      guint i;
+/* CHILD */
 
-      for (i = 0; i < G_N_ELEMENTS (state_names); i++)
-        {
-          if (selector->state & (1 << i))
-            {
-              g_string_append_c (str, ':');
-              g_string_append (str, state_names[i]);
-            }
-        }
-    }
+static void
+gtk_css_selector_child_print (const GtkCssSelector *selector,
+                              GString              *string)
+{
+  g_string_append (string, " > ");
 }
 
-char *
-_gtk_css_selector_to_string (const GtkCssSelector *selector)
+static gboolean
+gtk_css_selector_child_match (const GtkCssSelector *selector,
+                              const GtkCssMatcher  *matcher)
 {
-  GString *string;
+  GtkCssMatcher parent;
 
-  g_return_val_if_fail (selector != NULL, NULL);
+  if (!_gtk_css_matcher_get_parent (&parent, matcher))
+    return FALSE;
 
-  string = g_string_new (NULL);
+  return gtk_css_selector_match (gtk_css_selector_previous (selector), &parent);
+}
 
-  _gtk_css_selector_print (selector, string);
+static void
+gtk_css_selector_child_tree_match (const GtkCssSelectorTree *tree,
+                                  const GtkCssMatcher  *matcher,
+                                  GHashTable *res)
+{
+  GtkCssMatcher parent;
 
-  return g_string_free (string, FALSE);
+  if (!_gtk_css_matcher_get_parent (&parent, matcher))
+    return;
+
+  gtk_css_selector_tree_match_previous (tree, &parent, res);
 }
 
-static GtkRegionFlags
-compute_region_flags_for_index (const GtkWidgetPath *path,
-                                guint                id)
+
+static GtkCssChange
+gtk_css_selector_child_tree_get_change (const GtkCssSelectorTree *tree,
+                                       const GtkCssMatcher  *matcher)
 {
-  const GtkWidgetPath *siblings;
-  guint sibling_id, n_siblings;
-  GtkRegionFlags flags;
-  
-  siblings = gtk_widget_path_iter_get_siblings (path, id);
-  if (siblings == NULL)
+  GtkCssMatcher parent;
+  GtkCssChange change, previous_change;
+
+  if (!_gtk_css_matcher_get_parent (&parent, matcher))
     return 0;
 
-  sibling_id = gtk_widget_path_iter_get_sibling_index (path, id);
-  n_siblings = gtk_widget_path_length (siblings);
+  change = 0;
+
+  previous_change = gtk_css_selector_tree_get_previous_change (tree, &parent);
 
-  flags = (sibling_id % 2) ? GTK_REGION_EVEN : GTK_REGION_ODD;
-  if (sibling_id == 0)
-    flags |= GTK_REGION_FIRST;
-  if (sibling_id + 1 == n_siblings)
-    flags |= GTK_REGION_LAST;
-  if (n_siblings == 1)
-    flags |= GTK_REGION_ONLY;
+  if (previous_change != 0)
+    change |= _gtk_css_change_for_child (previous_change) | GTK_CSS_CHANGE_GOT_MATCH;
 
-  return flags;
+  return change;
 }
 
-static gboolean
-gtk_css_selector_matches_type (const GtkCssSelector      *selector,
-                               const GtkWidgetPath       *path,
-                               guint                      id)
+static GtkCssChange
+gtk_css_selector_child_get_change (const GtkCssSelector *selector, GtkCssChange previous_change)
 {
-  if (selector->pseudo_classes)
-    {
-      GtkRegionFlags flags = compute_region_flags_for_index (path, id);
-
-      if ((selector->pseudo_classes & flags) != selector->pseudo_classes)
-        return FALSE;
-    }
-
-  if (selector->name == NULL)
-    return TRUE;
+  return _gtk_css_change_for_child (previous_change);
+}
 
-  if (selector->type == G_TYPE_NONE)
-    return FALSE;
+static int
+gtk_css_selector_child_compare_one (const GtkCssSelector *a,
+                                   const GtkCssSelector *b)
+{
+  return 0;
+}
 
-  /* ugh, assigning to a const variable */
-  if (selector->type == G_TYPE_INVALID)
-    ((GtkCssSelector *) selector)->type = g_type_from_name (selector->name);
+static const GtkCssSelectorClass GTK_CSS_SELECTOR_CHILD = {
+  "child",
+  gtk_css_selector_child_print,
+  gtk_css_selector_child_match,
+  gtk_css_selector_child_tree_match,
+  gtk_css_selector_child_get_change,
+  gtk_css_selector_child_tree_get_change,
+  gtk_css_selector_child_compare_one,
+  FALSE, FALSE, FALSE, FALSE, FALSE
+};
 
-  if (selector->type == G_TYPE_INVALID)
-    return FALSE;
+/* SIBLING */
 
-  return g_type_is_a (gtk_widget_path_iter_get_object_type (path, id), selector->type);
+static void
+gtk_css_selector_sibling_print (const GtkCssSelector *selector,
+                                GString              *string)
+{
+  g_string_append (string, " ~ ");
 }
 
 static gboolean
-gtk_css_selector_matches_region (const GtkCssSelector      *selector,
-                                 const GtkWidgetPath       *path,
-                                 guint                      id,
-                                 const char *               region)
+gtk_css_selector_sibling_match (const GtkCssSelector *selector,
+                                const GtkCssMatcher  *matcher)
 {
-  GtkRegionFlags flags;
-
-  if (selector->name == NULL)
-    return TRUE;
-  
-  if (selector->name != region)
-    return FALSE;
+  GtkCssMatcher previous;
 
-  if (!gtk_widget_path_iter_has_region (path, id, region, &flags))
+  while (_gtk_css_matcher_get_previous (&previous, matcher))
     {
-      /* This function must be called with existing regions */
-      g_assert_not_reached ();
+      matcher = &previous;
+
+      if (gtk_css_selector_match (gtk_css_selector_previous (selector), matcher))
+        return TRUE;
     }
 
-  return (selector->pseudo_classes & flags) == selector->pseudo_classes;
+  return FALSE;
 }
 
-static gboolean
-gtk_css_selector_matches_rest (const GtkCssSelector      *selector,
-                               const GtkWidgetPath       *path,
-                               guint                      id)
+static void
+gtk_css_selector_sibling_tree_match (const GtkCssSelectorTree *tree,
+                                    const GtkCssMatcher  *matcher,
+                                    GHashTable *res)
 {
-  if (selector->ids)
-    {
-      GQuark *name;
-      
-      for (name = selector->ids; *name; name++)
-        {
-          if (!gtk_widget_path_iter_has_qname (path, id, *name))
-            return FALSE;
-        }
-    }
+  GtkCssMatcher previous;
 
-  if (selector->classes)
+  while (_gtk_css_matcher_get_previous (&previous, matcher))
     {
-      GQuark *class;
-      
-      for (class = selector->classes; *class; class++)
-        {
-          if (!gtk_widget_path_iter_has_qclass (path, id, *class))
-            return FALSE;
-        }
-    }
+      matcher = &previous;
 
-  return TRUE;
-}
+      gtk_css_selector_tree_match_previous (tree, matcher, res);
 
-static gboolean
-gtk_css_selector_matches_previous (const GtkCssSelector      *selector,
-                                   const GtkWidgetPath       *path,
-                                   guint                      id,
-                                   GSList                    *regions);
+      /* any matchers are dangerous here, as we may loop forever, but
+        we can terminate now as all possible matches have already been added */
+      if (_gtk_css_matcher_matches_any (matcher))
+       break;
+    }
+}
 
-static gboolean
-gtk_css_selector_matches_from (const GtkCssSelector      *selector,
-                               const GtkWidgetPath       *path,
-                               guint                      id,
-                               GSList                    *regions)
+static GtkCssChange
+gtk_css_selector_sibling_tree_get_change (const GtkCssSelectorTree *tree,
+                                         const GtkCssMatcher  *matcher)
 {
-  GSList *l;
+  GtkCssMatcher previous;
+  GtkCssChange change, previous_change;
 
-  if (!gtk_css_selector_matches_rest (selector, path, id))
-    return FALSE;
+  change = 0;
 
-  for (l = regions; l; l = l->next)
+  previous_change = 0;
+  while (_gtk_css_matcher_get_previous (&previous, matcher))
     {
-      const char *region = l->data;
+      matcher = &previous;
 
-      if (gtk_css_selector_matches_region (selector, path, id, region))
-        {
-          GSList *remaining;
-          gboolean match;
-
-          remaining = g_slist_copy (regions);
-          remaining = g_slist_remove (remaining, region);
-          match = gtk_css_selector_matches_previous (selector,
-                                                     path,
-                                                     id,
-                                                     remaining);
-          g_slist_free (remaining);
-          if (match)
-            return TRUE;
-        }
+      previous_change |= gtk_css_selector_tree_get_previous_change (tree, matcher);
+
+      /* any matchers are dangerous here, as we may loop forever, but
+        we can terminate now as all possible matches have already been added */
+      if (_gtk_css_matcher_matches_any (matcher))
+       break;
     }
 
-  if (gtk_css_selector_matches_type (selector, path, id))
-    {
-      GSList *regions;
-      gboolean match;
-      
-      if (id <= 0)
-        return selector->previous == NULL;
+  if (previous_change != 0)
+    change |= _gtk_css_change_for_sibling (previous_change) |  GTK_CSS_CHANGE_GOT_MATCH;
 
-      regions = gtk_widget_path_iter_list_regions (path, id - 1);
-      match = gtk_css_selector_matches_previous (selector,
-                                                 path,
-                                                 id - 1,
-                                                 regions);
-      g_slist_free (regions);
-      return match;
-    }
+  return change;
+}
 
-  return FALSE;
+static GtkCssChange
+gtk_css_selector_sibling_get_change (const GtkCssSelector *selector, GtkCssChange previous_change)
+{
+  return _gtk_css_change_for_sibling (previous_change);
 }
 
-static gboolean
-gtk_css_selector_matches_previous (const GtkCssSelector      *selector,
-                                   const GtkWidgetPath       *path,
-                                   guint                      id,
-                                   GSList                    *regions)
+static int
+gtk_css_selector_sibling_compare_one (const GtkCssSelector *a,
+                                     const GtkCssSelector *b)
 {
-  if (!selector->previous)
-    return TRUE;
+  return 0;
+}
+  
 
-  if (gtk_css_selector_matches_from (selector->previous,
-                                     path,
-                                     id,
-                                     regions))
-    return TRUE;
+static const GtkCssSelectorClass GTK_CSS_SELECTOR_SIBLING = {
+  "sibling",
+  gtk_css_selector_sibling_print,
+  gtk_css_selector_sibling_match,
+  gtk_css_selector_sibling_tree_match,
+  gtk_css_selector_sibling_get_change,
+  gtk_css_selector_sibling_tree_get_change,
+  gtk_css_selector_sibling_compare_one,
+  FALSE, FALSE, FALSE, FALSE, FALSE
+};
 
-  if (selector->combine == GTK_CSS_COMBINE_DESCANDANT)
-    {
-      /* with this magic we run the loop while id >= 0 */
-      while (id-- != 0)
-        {
-          GSList *list;
-          gboolean match;
-
-          list = gtk_widget_path_iter_list_regions (path, id);
-          match = gtk_css_selector_matches_from (selector->previous,
-                                                 path,
-                                                 id,
-                                                 list);
-          g_slist_free (list);
-          if (match)
-            return TRUE;
-        }
-    }
+/* ADJACENT */
 
-  return FALSE;
+static void
+gtk_css_selector_adjacent_print (const GtkCssSelector *selector,
+                                 GString              *string)
+{
+  g_string_append (string, " + ");
 }
 
-/**
- * _gtk_css_selector_matches:
+static gboolean
+gtk_css_selector_adjacent_match (const GtkCssSelector *selector,
+                                 const GtkCssMatcher  *matcher)
+{
+  GtkCssMatcher previous;
+
+  if (!_gtk_css_matcher_get_previous (&previous, matcher))
+    return FALSE;
+
+  return gtk_css_selector_match (gtk_css_selector_previous (selector), &previous);
+}
+
+static void
+gtk_css_selector_adjacent_tree_match (const GtkCssSelectorTree *tree,
+                                     const GtkCssMatcher  *matcher,
+                                     GHashTable *res)
+{
+  GtkCssMatcher previous;
+
+  if (!_gtk_css_matcher_get_previous (&previous, matcher))
+    return;
+
+  matcher = &previous;
+
+  gtk_css_selector_tree_match_previous (tree, matcher, res);
+}
+
+static GtkCssChange
+gtk_css_selector_adjacent_tree_get_change (const GtkCssSelectorTree *tree,
+                                          const GtkCssMatcher  *matcher)
+{
+  GtkCssMatcher previous;
+  GtkCssChange change, previous_change;
+
+  if (!_gtk_css_matcher_get_previous (&previous, matcher))
+    return 0;
+
+  change = 0;
+
+  previous_change = gtk_css_selector_tree_get_previous_change (tree, &previous);
+
+  if (previous_change != 0)
+    change |= _gtk_css_change_for_sibling (previous_change) |  GTK_CSS_CHANGE_GOT_MATCH;
+
+  return change;
+}
+
+static GtkCssChange
+gtk_css_selector_adjacent_get_change (const GtkCssSelector *selector, GtkCssChange previous_change)
+{
+  return _gtk_css_change_for_sibling (previous_change);
+}
+
+static int
+gtk_css_selector_adjacent_compare_one (const GtkCssSelector *a,
+                                      const GtkCssSelector *b)
+{
+  return 0;
+}
+
+static const GtkCssSelectorClass GTK_CSS_SELECTOR_ADJACENT = {
+  "adjacent",
+  gtk_css_selector_adjacent_print,
+  gtk_css_selector_adjacent_match,
+  gtk_css_selector_adjacent_tree_match,
+  gtk_css_selector_adjacent_get_change,
+  gtk_css_selector_adjacent_tree_get_change,
+  gtk_css_selector_adjacent_compare_one,
+  FALSE, FALSE, FALSE, FALSE, FALSE
+};
+
+/* ANY */
+
+static void
+gtk_css_selector_any_print (const GtkCssSelector *selector,
+                            GString              *string)
+{
+  g_string_append_c (string, '*');
+}
+
+static gboolean
+gtk_css_selector_any_match (const GtkCssSelector *selector,
+                            const GtkCssMatcher  *matcher)
+{
+  const GtkCssSelector *previous = gtk_css_selector_previous (selector);
+  
+  if (previous &&
+      previous->class == &GTK_CSS_SELECTOR_DESCENDANT &&
+      _gtk_css_matcher_has_regions (matcher))
+    {
+      if (gtk_css_selector_match (gtk_css_selector_previous (previous), matcher))
+        return TRUE;
+    }
+  
+  return gtk_css_selector_match (previous, matcher);
+}
+
+static void
+gtk_css_selector_any_tree_match (const GtkCssSelectorTree *tree,
+                                const GtkCssMatcher  *matcher,
+                                GHashTable *res)
+{
+  const GtkCssSelectorTree *prev;
+
+  gtk_css_selector_tree_found_match (tree, res);
+
+  for (prev = gtk_css_selector_tree_get_previous (tree);
+       prev != NULL;
+       prev = gtk_css_selector_tree_get_sibling (prev))
+    {
+      if (prev->selector.class == &GTK_CSS_SELECTOR_DESCENDANT &&
+         _gtk_css_matcher_has_regions (matcher))
+       gtk_css_selector_tree_match_previous (prev, matcher, res);
+
+      gtk_css_selector_tree_match (prev, matcher, res);
+    }
+}
+
+static GtkCssChange
+gtk_css_selector_any_tree_get_change (const GtkCssSelectorTree *tree,
+                                     const GtkCssMatcher  *matcher)
+{
+  const GtkCssSelectorTree *prev;
+  GtkCssChange change, previous_change;
+
+  change = 0;
+
+  if (tree->matches_offset != GTK_CSS_SELECTOR_TREE_EMPTY_OFFSET)
+    change |= GTK_CSS_CHANGE_GOT_MATCH;
+
+  previous_change = 0;
+  for (prev = gtk_css_selector_tree_get_previous (tree);
+       prev != NULL;
+       prev = gtk_css_selector_tree_get_sibling (prev))
+    {
+      if (prev->selector.class == &GTK_CSS_SELECTOR_DESCENDANT &&
+         _gtk_css_matcher_has_regions (matcher))
+       previous_change |= gtk_css_selector_tree_get_previous_change (prev, matcher);
+
+      previous_change |= gtk_css_selector_tree_get_change (prev, matcher);
+    }
+
+  if (previous_change != 0)
+    change |= previous_change | GTK_CSS_CHANGE_GOT_MATCH;
+
+  return change;
+}
+
+
+static GtkCssChange
+gtk_css_selector_any_get_change (const GtkCssSelector *selector, GtkCssChange previous_change)
+{
+  return previous_change;
+}
+
+static int
+gtk_css_selector_any_compare_one (const GtkCssSelector *a,
+                                 const GtkCssSelector *b)
+{
+  return 0;
+}
+
+static const GtkCssSelectorClass GTK_CSS_SELECTOR_ANY = {
+  "any",
+  gtk_css_selector_any_print,
+  gtk_css_selector_any_match,
+  gtk_css_selector_any_tree_match,
+  gtk_css_selector_any_get_change,
+  gtk_css_selector_any_tree_get_change,
+  gtk_css_selector_any_compare_one,
+  FALSE, FALSE, FALSE, TRUE, TRUE
+};
+
+/* NAME */
+
+typedef struct {
+  GType type;
+  const char *name;
+} TypeReference;
+
+static GHashTable *type_refs_ht = NULL;
+static guint type_refs_last_serial = 0;
+
+static TypeReference *
+get_type_reference (const char *name)
+{
+  TypeReference *ref;
+
+
+  if (type_refs_ht == NULL)
+    type_refs_ht = g_hash_table_new (g_str_hash, g_str_equal);
+
+  ref = g_hash_table_lookup (type_refs_ht, name);
+
+  if (ref != NULL)
+    return ref;
+
+  ref = g_slice_new (TypeReference);
+  ref->name = g_intern_string (name);
+  ref->type = g_type_from_name (ref->name);
+
+  g_hash_table_insert (type_refs_ht,
+                      (gpointer)ref->name, ref);
+
+  return ref;
+}
+
+static void
+update_type_references (void)
+{
+  GHashTableIter iter;
+  guint serial;
+  gpointer value;
+
+  serial = g_type_get_type_registration_serial ();
+
+  if (serial == type_refs_last_serial)
+    return;
+
+  type_refs_last_serial = serial;
+
+  if (type_refs_ht == NULL)
+    return;
+
+  g_hash_table_iter_init (&iter, type_refs_ht);
+  while (g_hash_table_iter_next (&iter,
+                                NULL, &value))
+    {
+      TypeReference *ref = value;
+      if (ref->type == G_TYPE_INVALID)
+       ref->type = g_type_from_name (ref->name);
+    }
+}
+
+static void
+gtk_css_selector_name_print (const GtkCssSelector *selector,
+                             GString              *string)
+{
+  g_string_append (string, ((TypeReference *)selector->data)->name);
+}
+
+static gboolean
+gtk_css_selector_name_match (const GtkCssSelector *selector,
+                             const GtkCssMatcher  *matcher)
+{
+  if (!_gtk_css_matcher_has_type (matcher, ((TypeReference *)selector->data)->type))
+    return FALSE;
+
+  return gtk_css_selector_match (gtk_css_selector_previous (selector), matcher);
+}
+
+static void
+gtk_css_selector_name_tree_match (const GtkCssSelectorTree *tree,
+                                 const GtkCssMatcher  *matcher,
+                                 GHashTable *res)
+{
+  if (!_gtk_css_matcher_has_type (matcher, ((TypeReference *)tree->selector.data)->type))
+    return;
+
+  gtk_css_selector_tree_found_match (tree, res);
+
+  gtk_css_selector_tree_match_previous (tree, matcher, res);
+}
+
+static GtkCssChange
+gtk_css_selector_name_tree_get_change (const GtkCssSelectorTree *tree,
+                                      const GtkCssMatcher  *matcher)
+{
+  GtkCssChange change, previous_change;
+
+  if (!_gtk_css_matcher_has_type (matcher, ((TypeReference *)tree->selector.data)->type))
+    return 0;
+
+  change = 0;
+
+  if (tree->matches_offset != GTK_CSS_SELECTOR_TREE_EMPTY_OFFSET)
+    change |= GTK_CSS_CHANGE_NAME | GTK_CSS_CHANGE_GOT_MATCH;
+
+  previous_change = gtk_css_selector_tree_get_previous_change (tree, matcher);
+
+  if (previous_change)
+    change |= previous_change | GTK_CSS_CHANGE_NAME | GTK_CSS_CHANGE_GOT_MATCH;
+
+  return change;
+}
+
+
+static GtkCssChange
+gtk_css_selector_name_get_change (const GtkCssSelector *selector, GtkCssChange previous_change)
+{
+  return previous_change | GTK_CSS_CHANGE_NAME;
+}
+
+static int
+gtk_css_selector_name_compare_one (const GtkCssSelector *a,
+                                  const GtkCssSelector *b)
+{
+  return strcmp (((TypeReference *)a->data)->name,
+                ((TypeReference *)b->data)->name);
+}
+
+static const GtkCssSelectorClass GTK_CSS_SELECTOR_NAME = {
+  "name",
+  gtk_css_selector_name_print,
+  gtk_css_selector_name_match,
+  gtk_css_selector_name_tree_match,
+  gtk_css_selector_name_get_change,
+  gtk_css_selector_name_tree_get_change,
+  gtk_css_selector_name_compare_one,
+  FALSE, FALSE, TRUE, TRUE, FALSE
+};
+
+/* REGION */
+
+static void
+gtk_css_selector_region_print (const GtkCssSelector *selector,
+                               GString              *string)
+{
+  g_string_append (string, selector->data);
+}
+
+static gboolean
+gtk_css_selector_region_match (const GtkCssSelector *selector,
+                               const GtkCssMatcher  *matcher)
+{
+  const GtkCssSelector *previous;
+
+  if (!_gtk_css_matcher_has_region (matcher, selector->data, 0))
+    return FALSE;
+
+  previous = gtk_css_selector_previous (selector);
+  if (previous && previous->class == &GTK_CSS_SELECTOR_DESCENDANT &&
+      gtk_css_selector_match (gtk_css_selector_previous (previous), matcher))
+    return TRUE;
+
+  return gtk_css_selector_match (previous, matcher);
+}
+
+static void
+gtk_css_selector_region_tree_match (const GtkCssSelectorTree *tree,
+                                   const GtkCssMatcher  *matcher,
+                                   GHashTable *res)
+{
+  const GtkCssSelectorTree *prev;
+
+  if (!_gtk_css_matcher_has_region (matcher, tree->selector.data, 0))
+    return;
+
+  gtk_css_selector_tree_found_match (tree, res);
+
+  for (prev = gtk_css_selector_tree_get_previous (tree);
+       prev != NULL;
+       prev = gtk_css_selector_tree_get_sibling (prev))
+    {
+      if (prev->selector.class == &GTK_CSS_SELECTOR_DESCENDANT)
+       gtk_css_selector_tree_match_previous (prev, matcher, res);
+
+      gtk_css_selector_tree_match (prev, matcher, res);
+    }
+}
+
+static GtkCssChange
+gtk_css_selector_region_tree_get_change (const GtkCssSelectorTree *tree,
+                                        const GtkCssMatcher  *matcher)
+{
+  const GtkCssSelectorTree *prev;
+  GtkCssChange change, previous_change;
+
+  if (!_gtk_css_matcher_has_region (matcher, tree->selector.data, 0))
+    return 0;
+
+  change = 0;
+
+  if (tree->matches_offset != GTK_CSS_SELECTOR_TREE_EMPTY_OFFSET)
+    change |= GTK_CSS_CHANGE_REGION | GTK_CSS_CHANGE_GOT_MATCH;
+
+  previous_change = 0;
+  for (prev = gtk_css_selector_tree_get_previous (tree);
+       prev != NULL;
+       prev = gtk_css_selector_tree_get_sibling (prev))
+    {
+      if (prev->selector.class == &GTK_CSS_SELECTOR_DESCENDANT)
+       previous_change |= gtk_css_selector_tree_get_previous_change (prev, matcher);
+
+      previous_change |= gtk_css_selector_tree_get_change (prev, matcher);
+    }
+
+  if (previous_change != 0)
+    {
+      previous_change |= GTK_CSS_CHANGE_REGION;
+      previous_change |= _gtk_css_change_for_child (previous_change);
+      change |= previous_change | GTK_CSS_CHANGE_GOT_MATCH;
+    }
+
+  return change;
+}
+
+static GtkCssChange
+gtk_css_selector_region_get_change (const GtkCssSelector *selector, GtkCssChange previous_change)
+{
+  GtkCssChange change;
+
+  change = previous_change;
+  change |= GTK_CSS_CHANGE_REGION;
+  change |= _gtk_css_change_for_child (change);
+
+  return change;
+}
+
+static int
+gtk_css_selector_region_compare_one (const GtkCssSelector *a,
+                                    const GtkCssSelector *b)
+{
+  return strcmp (a->data, b->data);
+}
+
+static const GtkCssSelectorClass GTK_CSS_SELECTOR_REGION = {
+  "region",
+  gtk_css_selector_region_print,
+  gtk_css_selector_region_match,
+  gtk_css_selector_region_tree_match,
+  gtk_css_selector_region_get_change,
+  gtk_css_selector_region_tree_get_change,
+  gtk_css_selector_region_compare_one,
+  FALSE, FALSE, TRUE, TRUE, TRUE
+};
+
+/* CLASS */
+
+static void
+gtk_css_selector_class_print (const GtkCssSelector *selector,
+                              GString              *string)
+{
+  g_string_append_c (string, '.');
+  g_string_append (string, g_quark_to_string (GPOINTER_TO_UINT (selector->data)));
+}
+
+static gboolean
+gtk_css_selector_class_match (const GtkCssSelector *selector,
+                              const GtkCssMatcher  *matcher)
+{
+  if (!_gtk_css_matcher_has_class (matcher, GPOINTER_TO_UINT (selector->data)))
+    return FALSE;
+
+  return gtk_css_selector_match (gtk_css_selector_previous (selector), matcher);
+}
+
+static void
+gtk_css_selector_class_tree_match (const GtkCssSelectorTree *tree,
+                                  const GtkCssMatcher  *matcher,
+                                  GHashTable *res)
+{
+  if (!_gtk_css_matcher_has_class (matcher, GPOINTER_TO_UINT (tree->selector.data)))
+    return;
+
+  gtk_css_selector_tree_found_match (tree, res);
+
+  gtk_css_selector_tree_match_previous (tree, matcher, res);
+}
+
+static GtkCssChange
+gtk_css_selector_class_tree_get_change (const GtkCssSelectorTree *tree,
+                                       const GtkCssMatcher  *matcher)
+{
+  GtkCssChange change, previous_change;
+
+  if (!_gtk_css_matcher_has_class (matcher, GPOINTER_TO_UINT (tree->selector.data)))
+    return 0;
+
+  change = 0;
+
+  if (tree->matches_offset != GTK_CSS_SELECTOR_TREE_EMPTY_OFFSET)
+    change |= GTK_CSS_CHANGE_CLASS | GTK_CSS_CHANGE_GOT_MATCH;
+
+  previous_change = gtk_css_selector_tree_get_previous_change (tree, matcher);
+
+  if (previous_change != 0)
+    change |= previous_change | GTK_CSS_CHANGE_CLASS | GTK_CSS_CHANGE_GOT_MATCH;
+
+  return change;
+}
+
+static GtkCssChange
+gtk_css_selector_class_get_change (const GtkCssSelector *selector, GtkCssChange previous_change)
+{
+  return previous_change | GTK_CSS_CHANGE_CLASS;
+}
+
+static int
+gtk_css_selector_class_compare_one (const GtkCssSelector *a,
+                                   const GtkCssSelector *b)
+{
+  return strcmp (g_quark_to_string (GPOINTER_TO_UINT (a->data)),
+                g_quark_to_string (GPOINTER_TO_UINT (b->data)));
+}
+
+static const GtkCssSelectorClass GTK_CSS_SELECTOR_CLASS = {
+  "class",
+  gtk_css_selector_class_print,
+  gtk_css_selector_class_match,
+  gtk_css_selector_class_tree_match,
+  gtk_css_selector_class_get_change,
+  gtk_css_selector_class_tree_get_change,
+  gtk_css_selector_class_compare_one,
+  FALSE, TRUE, FALSE, TRUE, FALSE
+};
+
+/* ID */
+
+static void
+gtk_css_selector_id_print (const GtkCssSelector *selector,
+                           GString              *string)
+{
+  g_string_append_c (string, '#');
+  g_string_append (string, selector->data);
+}
+
+static gboolean
+gtk_css_selector_id_match (const GtkCssSelector *selector,
+                           const GtkCssMatcher  *matcher)
+{
+  if (!_gtk_css_matcher_has_id (matcher, selector->data))
+    return FALSE;
+
+  return gtk_css_selector_match (gtk_css_selector_previous (selector), matcher);
+}
+
+static void
+gtk_css_selector_id_tree_match (const GtkCssSelectorTree *tree,
+                               const GtkCssMatcher  *matcher,
+                               GHashTable *res)
+{
+  if (!_gtk_css_matcher_has_id (matcher, tree->selector.data))
+    return;
+
+  gtk_css_selector_tree_found_match (tree, res);
+
+  gtk_css_selector_tree_match_previous (tree, matcher, res);
+}
+
+static GtkCssChange
+gtk_css_selector_id_tree_get_change (const GtkCssSelectorTree *tree,
+                                    const GtkCssMatcher  *matcher)
+{
+  GtkCssChange change, previous_change;
+
+  if (!_gtk_css_matcher_has_id (matcher, tree->selector.data))
+    return 0;
+
+  change = 0;
+
+  if (tree->matches_offset != GTK_CSS_SELECTOR_TREE_EMPTY_OFFSET)
+    change |= GTK_CSS_CHANGE_ID | GTK_CSS_CHANGE_GOT_MATCH;
+
+  previous_change = gtk_css_selector_tree_get_previous_change (tree, matcher);
+
+  if (previous_change != 0)
+    change |= previous_change | GTK_CSS_CHANGE_ID | GTK_CSS_CHANGE_GOT_MATCH;
+
+  return change;
+}
+
+static GtkCssChange
+gtk_css_selector_id_get_change (const GtkCssSelector *selector, GtkCssChange previous_change)
+{
+  return previous_change | GTK_CSS_CHANGE_ID;
+}
+
+
+static int
+gtk_css_selector_id_compare_one (const GtkCssSelector *a,
+                                const GtkCssSelector *b)
+{
+  return strcmp (a->data, b->data);
+}
+
+static const GtkCssSelectorClass GTK_CSS_SELECTOR_ID = {
+  "id",
+  gtk_css_selector_id_print,
+  gtk_css_selector_id_match,
+  gtk_css_selector_id_tree_match,
+  gtk_css_selector_id_get_change,
+  gtk_css_selector_id_tree_get_change,
+  gtk_css_selector_id_compare_one,
+  TRUE, FALSE, FALSE, TRUE, FALSE
+};
+
+/* PSEUDOCLASS FOR STATE */
+
+static void
+gtk_css_selector_pseudoclass_state_print (const GtkCssSelector *selector,
+                                          GString              *string)
+{
+  static const char * state_names[] = {
+    "active",
+    "hover",
+    "selected",
+    "insensitive",
+    "inconsistent",
+    "focus",
+    "backdrop",
+    "dir(ltr)",
+    "dir(rtl)"
+  };
+  guint i, state;
+
+  state = GPOINTER_TO_UINT (selector->data);
+  g_string_append_c (string, ':');
+
+  for (i = 0; i < G_N_ELEMENTS (state_names); i++)
+    {
+      if (state == (1 << i))
+        {
+          g_string_append (string, state_names[i]);
+          return;
+        }
+    }
+
+  g_assert_not_reached ();
+}
+
+static gboolean
+gtk_css_selector_pseudoclass_state_match (const GtkCssSelector *selector,
+                                          const GtkCssMatcher  *matcher)
+{
+  GtkStateFlags state = GPOINTER_TO_UINT (selector->data);
+
+  if ((_gtk_css_matcher_get_state (matcher) & state) != state)
+    return FALSE;
+
+  return gtk_css_selector_match (gtk_css_selector_previous (selector), matcher);
+}
+
+static void
+gtk_css_selector_pseudoclass_state_tree_match (const GtkCssSelectorTree *tree,
+                                              const GtkCssMatcher  *matcher,
+                                              GHashTable *res)
+{
+  GtkStateFlags state = GPOINTER_TO_UINT (tree->selector.data);
+
+  if ((_gtk_css_matcher_get_state (matcher) & state) != state)
+    return;
+
+  gtk_css_selector_tree_found_match (tree, res);
+
+  gtk_css_selector_tree_match_previous (tree, matcher, res);
+}
+
+static GtkCssChange
+gtk_css_selector_pseudoclass_state_tree_get_change (const GtkCssSelectorTree *tree,
+                                                   const GtkCssMatcher  *matcher)
+{
+  GtkStateFlags state = GPOINTER_TO_UINT (tree->selector.data);
+  GtkCssChange change, previous_change;
+
+  if ((_gtk_css_matcher_get_state (matcher) & state) != state)
+    return 0;
+
+  change = 0;
+
+  if (tree->matches_offset != GTK_CSS_SELECTOR_TREE_EMPTY_OFFSET)
+    change |= GTK_CSS_CHANGE_STATE | GTK_CSS_CHANGE_GOT_MATCH;
+
+  previous_change = gtk_css_selector_tree_get_previous_change (tree, matcher);
+
+  if (previous_change != 0)
+    change |= previous_change | GTK_CSS_CHANGE_STATE | GTK_CSS_CHANGE_GOT_MATCH;
+
+  return change;
+}
+
+static GtkCssChange
+gtk_css_selector_pseudoclass_state_get_change (const GtkCssSelector *selector, GtkCssChange previous_change)
+{
+  return previous_change | GTK_CSS_CHANGE_STATE;
+}
+
+static int
+gtk_css_selector_pseudoclass_state_compare_one (const GtkCssSelector *a,
+                                               const GtkCssSelector *b)
+{
+  return GPOINTER_TO_UINT (a->data) - GPOINTER_TO_UINT (b->data);
+}
+
+static const GtkCssSelectorClass GTK_CSS_SELECTOR_PSEUDOCLASS_STATE = {
+  "pseudoclass-state",
+  gtk_css_selector_pseudoclass_state_print,
+  gtk_css_selector_pseudoclass_state_match,
+  gtk_css_selector_pseudoclass_state_tree_match,
+  gtk_css_selector_pseudoclass_state_get_change,
+  gtk_css_selector_pseudoclass_state_tree_get_change,
+  gtk_css_selector_pseudoclass_state_compare_one,
+  FALSE, TRUE, FALSE, TRUE, FALSE
+};
+
+/* PSEUDOCLASS FOR POSITION */
+
+typedef enum {
+  POSITION_FORWARD,
+  POSITION_BACKWARD,
+  POSITION_ONLY,
+  POSITION_SORTED
+} PositionType;
+#define POSITION_TYPE_BITS 2
+#define POSITION_NUMBER_BITS ((sizeof (gpointer) * 8 - POSITION_TYPE_BITS) / 2)
+
+static gconstpointer
+encode_position (PositionType type,
+                 int          a,
+                 int          b)
+{
+  union {
+    gconstpointer p;
+    struct {
+      gssize type :POSITION_TYPE_BITS;
+      gssize a :POSITION_NUMBER_BITS;
+      gssize b :POSITION_NUMBER_BITS;
+    } data;
+  } result;
+  G_STATIC_ASSERT (sizeof (gconstpointer) == sizeof (result));
+
+  g_assert (type < (1 << POSITION_TYPE_BITS));
+
+  result.data.type = type;
+  result.data.a = a;
+  result.data.b = b;
+
+  return result.p;
+}
+
+static void
+decode_position (const GtkCssSelector *selector,
+                 PositionType         *type,
+                 int                  *a,
+                 int                  *b)
+{
+  union {
+    gconstpointer p;
+    struct {
+      gssize type :POSITION_TYPE_BITS;
+      gssize a :POSITION_NUMBER_BITS;
+      gssize b :POSITION_NUMBER_BITS;
+    } data;
+  } result;
+  G_STATIC_ASSERT (sizeof (gconstpointer) == sizeof (result));
+
+  result.p = selector->data;
+
+  *type = result.data.type & ((1 << POSITION_TYPE_BITS) - 1);
+  *a = result.data.a;
+  *b = result.data.b;
+}
+
+static void
+gtk_css_selector_pseudoclass_position_print (const GtkCssSelector *selector,
+                                             GString              *string)
+{
+  PositionType type;
+  int a, b;
+
+  decode_position (selector, &type, &a, &b);
+  switch (type)
+    {
+    case POSITION_FORWARD:
+      if (a == 0)
+        {
+          if (b == 1)
+            g_string_append (string, ":first-child");
+          else
+            g_string_append_printf (string, ":nth-child(%d)", b);
+        }
+      else if (a == 2 && b == 0)
+        g_string_append (string, ":nth-child(even)");
+      else if (a == 2 && b == 1)
+        g_string_append (string, ":nth-child(odd)");
+      else
+        {
+          g_string_append (string, ":nth-child(");
+          if (a == 1)
+            g_string_append (string, "n");
+          else if (a == -1)
+            g_string_append (string, "-n");
+          else
+            g_string_append_printf (string, "%dn", a);
+          if (b > 0)
+            g_string_append_printf (string, "+%d)", b);
+          else if (b < 0)
+            g_string_append_printf (string, "%d)", b);
+          else
+            g_string_append (string, ")");
+        }
+      break;
+    case POSITION_BACKWARD:
+      if (a == 0)
+        {
+          if (b == 1)
+            g_string_append (string, ":last-child");
+          else
+            g_string_append_printf (string, ":nth-last-child(%d)", b);
+        }
+      else if (a == 2 && b == 0)
+        g_string_append (string, ":nth-last-child(even)");
+      else if (a == 2 && b == 1)
+        g_string_append (string, ":nth-last-child(odd)");
+      else
+        {
+          g_string_append (string, ":nth-last-child(");
+          if (a == 1)
+            g_string_append (string, "n");
+          else if (a == -1)
+            g_string_append (string, "-n");
+          else
+            g_string_append_printf (string, "%dn", a);
+          if (b > 0)
+            g_string_append_printf (string, "+%d)", b);
+          else if (b < 0)
+            g_string_append_printf (string, "%d)", b);
+          else
+            g_string_append (string, ")");
+        }
+      break;
+    case POSITION_ONLY:
+      g_string_append (string, ":only-child");
+      break;
+    case POSITION_SORTED:
+      g_string_append (string, ":sorted");
+      break;
+    default:
+      g_assert_not_reached ();
+      break;
+    }
+}
+
+static gboolean
+get_selector_flags_for_position_region_match (const GtkCssSelector *selector, GtkRegionFlags *selector_flags)
+{
+  PositionType type;
+  int a, b;
+
+  decode_position (selector, &type, &a, &b);
+  switch (type)
+    {
+    case POSITION_FORWARD:
+      if (a == 0 && b == 1)
+        *selector_flags = GTK_REGION_FIRST;
+      else if (a == 2 && b == 0)
+        *selector_flags = GTK_REGION_EVEN;
+      else if (a == 2 && b == 1)
+        *selector_flags = GTK_REGION_ODD;
+      else
+        return FALSE;
+      break;
+    case POSITION_BACKWARD:
+      if (a == 0 && b == 1)
+        *selector_flags = GTK_REGION_LAST;
+      else
+        return FALSE;
+      break;
+    case POSITION_ONLY:
+      *selector_flags = GTK_REGION_ONLY;
+      break;
+    case POSITION_SORTED:
+      *selector_flags = GTK_REGION_SORTED;
+      break;
+    default:
+      g_assert_not_reached ();
+      break;
+    }
+
+  return TRUE;
+}
+
+static gboolean
+gtk_css_selector_pseudoclass_position_match_for_region (const GtkCssSelector *selector,
+                                                        const GtkCssMatcher  *matcher)
+{
+  GtkRegionFlags selector_flags;
+  const GtkCssSelector *previous;
+
+  if (!get_selector_flags_for_position_region_match (selector, &selector_flags))
+      return FALSE;
+
+  selector = gtk_css_selector_previous (selector);
+
+  if (!_gtk_css_matcher_has_region (matcher, selector->data, selector_flags))
+    return FALSE;
+
+  previous = gtk_css_selector_previous (selector);
+  if (previous && previous->class == &GTK_CSS_SELECTOR_DESCENDANT &&
+      gtk_css_selector_match (gtk_css_selector_previous (previous), matcher))
+    return TRUE;
+
+  return gtk_css_selector_match (previous, matcher);
+}
+
+static gboolean
+get_position_match (const GtkCssSelector *selector,
+                   const GtkCssMatcher  *matcher)
+{
+  PositionType type;
+  int a, b;
+
+  decode_position (selector, &type, &a, &b);
+  switch (type)
+    {
+    case POSITION_FORWARD:
+      if (!_gtk_css_matcher_has_position (matcher, TRUE, a, b))
+        return FALSE;
+      break;
+    case POSITION_BACKWARD:
+      if (!_gtk_css_matcher_has_position (matcher, FALSE, a, b))
+        return FALSE;
+      break;
+    case POSITION_ONLY:
+      if (!_gtk_css_matcher_has_position (matcher, TRUE, 0, 1) ||
+          !_gtk_css_matcher_has_position (matcher, FALSE, 0, 1))
+        return FALSE;
+      break;
+    case POSITION_SORTED:
+      return FALSE;
+    default:
+      g_assert_not_reached ();
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+static gboolean
+gtk_css_selector_pseudoclass_position_match (const GtkCssSelector *selector,
+                                             const GtkCssMatcher  *matcher)
+{
+  const GtkCssSelector *previous;
+
+  previous = gtk_css_selector_previous (selector);
+  if (previous && previous->class == &GTK_CSS_SELECTOR_REGION)
+    return gtk_css_selector_pseudoclass_position_match_for_region (selector, matcher);
+
+  if (!get_position_match (selector, matcher))
+    return FALSE;
+
+  return gtk_css_selector_match (previous, matcher);
+}
+
+static void
+gtk_css_selector_pseudoclass_position_tree_match_for_region (const GtkCssSelectorTree *tree,
+                                                            const GtkCssSelectorTree *prev,
+                                                            const GtkCssMatcher  *matcher,
+                                                            GHashTable *res)
+{
+  const GtkCssSelectorTree *prev2;
+  GtkRegionFlags selector_flags;
+
+  if (!get_selector_flags_for_position_region_match (&tree->selector, &selector_flags))
+      return;
+
+  if (!_gtk_css_matcher_has_region (matcher, prev->selector.data, selector_flags))
+    return;
+
+  gtk_css_selector_tree_found_match (prev, res);
+
+  for (prev2 = gtk_css_selector_tree_get_previous (prev);
+       prev2 != NULL;
+       prev2 = gtk_css_selector_tree_get_sibling (prev2))
+    {
+      if (prev2->selector.class == &GTK_CSS_SELECTOR_DESCENDANT)
+       gtk_css_selector_tree_match (gtk_css_selector_tree_get_previous (prev2), matcher, res);
+      gtk_css_selector_tree_match (prev2, matcher, res);
+    }
+}
+
+static void
+gtk_css_selector_pseudoclass_position_tree_match (const GtkCssSelectorTree *tree,
+                                                 const GtkCssMatcher  *matcher,
+                                                 GHashTable *res)
+{
+  const GtkCssSelectorTree *prev;
+
+  for (prev = gtk_css_selector_tree_get_previous (tree);
+       prev != NULL;
+       prev = gtk_css_selector_tree_get_sibling (prev))
+    {
+      if (prev->selector.class == &GTK_CSS_SELECTOR_REGION)
+       gtk_css_selector_pseudoclass_position_tree_match_for_region (tree, prev, matcher, res);
+    }
+
+  if (!get_position_match (&tree->selector, matcher))
+    return;
+
+  gtk_css_selector_tree_found_match (tree, res);
+
+  for (prev = gtk_css_selector_tree_get_previous (tree); prev != NULL; prev = gtk_css_selector_tree_get_sibling (prev))
+    {
+      if (prev->selector.class != &GTK_CSS_SELECTOR_REGION)
+       gtk_css_selector_tree_match (prev, matcher, res);
+    }
+}
+
+static GtkCssChange
+gtk_css_selector_pseudoclass_position_tree_get_change_for_region (const GtkCssSelectorTree *tree,
+                                                                 const GtkCssSelectorTree *prev,
+                                                                 const GtkCssMatcher  *matcher)
+{
+  const GtkCssSelectorTree *prev2;
+  GtkRegionFlags selector_flags;
+  GtkCssChange change, previous_change;
+
+  if (!get_selector_flags_for_position_region_match (&tree->selector, &selector_flags))
+      return 0;
+
+  if (!_gtk_css_matcher_has_region (matcher, prev->selector.data, selector_flags))
+    return 0;
+
+  change = 0;
+  if (tree->matches_offset != GTK_CSS_SELECTOR_TREE_EMPTY_OFFSET)
+    change |= GTK_CSS_CHANGE_POSITION | GTK_CSS_CHANGE_GOT_MATCH;
+
+  previous_change = 0;
+  for (prev2 = gtk_css_selector_tree_get_previous (prev);
+       prev2 != NULL;
+       prev2 = gtk_css_selector_tree_get_sibling (prev2))
+    {
+      if (prev2->selector.class == &GTK_CSS_SELECTOR_DESCENDANT)
+       previous_change |= gtk_css_selector_tree_get_change (gtk_css_selector_tree_get_previous (prev2), matcher);
+      previous_change |= gtk_css_selector_tree_get_change (prev2, matcher);
+    }
+
+  if (previous_change != 0)
+    change |= previous_change | GTK_CSS_CHANGE_POSITION | GTK_CSS_CHANGE_GOT_MATCH;
+
+  return change;
+}
+
+static GtkCssChange
+gtk_css_selector_pseudoclass_position_tree_get_change (const GtkCssSelectorTree *tree,
+                                                      const GtkCssMatcher  *matcher)
+{
+  const GtkCssSelectorTree *prev;
+  GtkCssChange change, previous_change;
+
+  change = 0;
+
+  for (prev = gtk_css_selector_tree_get_previous (tree);
+       prev != NULL;
+       prev = gtk_css_selector_tree_get_sibling (prev))
+    {
+      if (prev->selector.class == &GTK_CSS_SELECTOR_REGION)
+       change |= gtk_css_selector_pseudoclass_position_tree_get_change_for_region (tree, prev, matcher);
+    }
+
+  if (!get_position_match (&tree->selector, matcher))
+    return change;
+
+  if (tree->matches_offset != GTK_CSS_SELECTOR_TREE_EMPTY_OFFSET)
+    change |= GTK_CSS_CHANGE_POSITION | GTK_CSS_CHANGE_GOT_MATCH;
+
+  previous_change = 0;
+  for (prev = gtk_css_selector_tree_get_previous (tree); prev != NULL; prev = gtk_css_selector_tree_get_sibling (prev))
+    {
+      if (prev->selector.class != &GTK_CSS_SELECTOR_REGION)
+       previous_change |= gtk_css_selector_tree_get_change (prev, matcher);
+    }
+
+  if (previous_change != 0)
+    change |= previous_change | GTK_CSS_CHANGE_POSITION | GTK_CSS_CHANGE_GOT_MATCH;
+
+  return change;
+}
+
+static GtkCssChange
+gtk_css_selector_pseudoclass_position_get_change (const GtkCssSelector *selector, GtkCssChange previous_change)
+{
+  return previous_change | GTK_CSS_CHANGE_POSITION;
+}
+
+static int
+gtk_css_selector_pseudoclass_position_compare_one (const GtkCssSelector *a,
+                                                  const GtkCssSelector *b)
+{
+  return GPOINTER_TO_UINT (a->data) - GPOINTER_TO_UINT (b->data);
+}
+
+static const GtkCssSelectorClass GTK_CSS_SELECTOR_PSEUDOCLASS_POSITION = {
+  "pseudoclass-position",
+  gtk_css_selector_pseudoclass_position_print,
+  gtk_css_selector_pseudoclass_position_match,
+  gtk_css_selector_pseudoclass_position_tree_match,
+  gtk_css_selector_pseudoclass_position_get_change,
+  gtk_css_selector_pseudoclass_position_tree_get_change,
+  gtk_css_selector_pseudoclass_position_compare_one,
+  FALSE, TRUE, FALSE, TRUE, TRUE
+};
+
+/* API */
+
+static guint
+gtk_css_selector_size (const GtkCssSelector *selector)
+{
+  guint size = 0;
+
+  while (selector)
+    {
+      selector = gtk_css_selector_previous (selector);
+      size++;
+    }
+
+  return size;
+}
+
+static GtkCssSelector *
+gtk_css_selector_new (const GtkCssSelectorClass *class,
+                      GtkCssSelector            *selector,
+                      gconstpointer              data)
+{
+  guint size;
+
+  size = gtk_css_selector_size (selector);
+  selector = g_realloc (selector, sizeof (GtkCssSelector) * (size + 1) + sizeof (gpointer));
+  if (size == 0)
+    selector[1].class = NULL;
+  else
+    memmove (selector + 1, selector, sizeof (GtkCssSelector) * size + sizeof (gpointer));
+
+  selector->class = class;
+  selector->data = data;
+
+  return selector;
+}
+
+static GtkCssSelector *
+parse_selector_class (GtkCssParser *parser, GtkCssSelector *selector)
+{
+  char *name;
+    
+  name = _gtk_css_parser_try_name (parser, FALSE);
+
+  if (name == NULL)
+    {
+      _gtk_css_parser_error (parser, "Expected a valid name for class");
+      if (selector)
+        _gtk_css_selector_free (selector);
+      return NULL;
+    }
+
+  selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_CLASS,
+                                   selector,
+                                   GUINT_TO_POINTER (g_quark_from_string (name)));
+
+  g_free (name);
+
+  return selector;
+}
+
+static GtkCssSelector *
+parse_selector_id (GtkCssParser *parser, GtkCssSelector *selector)
+{
+  char *name;
+    
+  name = _gtk_css_parser_try_name (parser, FALSE);
+
+  if (name == NULL)
+    {
+      _gtk_css_parser_error (parser, "Expected a valid name for id");
+      if (selector)
+        _gtk_css_selector_free (selector);
+      return NULL;
+    }
+
+  selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_ID,
+                                   selector,
+                                   g_intern_string (name));
+
+  g_free (name);
+
+  return selector;
+}
+
+static GtkCssSelector *
+parse_selector_pseudo_class_nth_child (GtkCssParser   *parser,
+                                       GtkCssSelector *selector,
+                                       PositionType    type)
+{
+  int a, b;
+
+  if (!_gtk_css_parser_try (parser, "(", TRUE))
+    {
+      _gtk_css_parser_error (parser, "Missing opening bracket for pseudo-class");
+      if (selector)
+        _gtk_css_selector_free (selector);
+      return NULL;
+    }
+
+  if (_gtk_css_parser_try (parser, "even", TRUE))
+    {
+      a = 2;
+      b = 0;
+    }
+  else if (_gtk_css_parser_try (parser, "odd", TRUE))
+    {
+      a = 2;
+      b = 1;
+    }
+  else if (type == POSITION_FORWARD &&
+           _gtk_css_parser_try (parser, "first", TRUE))
+    {
+      a = 0;
+      b = 1;
+    }
+  else if (type == POSITION_FORWARD &&
+           _gtk_css_parser_try (parser, "last", TRUE))
+    {
+      a = 0;
+      b = 1;
+      type = POSITION_BACKWARD;
+    }
+  else
+    {
+      int multiplier;
+
+      if (_gtk_css_parser_try (parser, "+", TRUE))
+        multiplier = 1;
+      else if (_gtk_css_parser_try (parser, "-", TRUE))
+        multiplier = -1;
+      else
+        multiplier = 1;
+
+      if (_gtk_css_parser_try_int (parser, &a))
+        {
+          if (a < 0)
+            {
+              _gtk_css_parser_error (parser, "Expected an integer");
+              if (selector)
+                _gtk_css_selector_free (selector);
+              return NULL;
+            }
+          a *= multiplier;
+        }
+      else if (_gtk_css_parser_has_prefix (parser, "n"))
+        {
+          a = multiplier;
+        }
+      else
+        {
+          _gtk_css_parser_error (parser, "Expected an integer");
+          if (selector)
+            _gtk_css_selector_free (selector);
+          return NULL;
+        }
+
+      if (_gtk_css_parser_try (parser, "n", TRUE))
+        {
+          if (_gtk_css_parser_try (parser, "+", TRUE))
+            multiplier = 1;
+          else if (_gtk_css_parser_try (parser, "-", TRUE))
+            multiplier = -1;
+          else
+            multiplier = 1;
+
+          if (_gtk_css_parser_try_int (parser, &b))
+            {
+              if (b < 0)
+                {
+                  _gtk_css_parser_error (parser, "Expected an integer");
+                  if (selector)
+                    _gtk_css_selector_free (selector);
+                  return NULL;
+                }
+            }
+          else
+            b = 0;
+
+          b *= multiplier;
+        }
+      else
+        {
+          b = a;
+          a = 0;
+        }
+    }
+
+  if (!_gtk_css_parser_try (parser, ")", FALSE))
+    {
+      _gtk_css_parser_error (parser, "Missing closing bracket for pseudo-class");
+      if (selector)
+        _gtk_css_selector_free (selector);
+      return NULL;
+    }
+
+  selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_PSEUDOCLASS_POSITION,
+                                   selector,
+                                   encode_position (type, a, b));
+
+  return selector;
+}
+
+static GtkCssSelector *
+parse_selector_pseudo_class (GtkCssParser   *parser,
+                             GtkCssSelector *selector)
+{
+  static const struct {
+    const char    *name;
+    GtkStateFlags  state_flag;
+    PositionType   position_type;
+    int            position_a;
+    int            position_b;
+  } pseudo_classes[] = {
+    { "first-child",  0,                           POSITION_FORWARD,  0, 1 },
+    { "last-child",   0,                           POSITION_BACKWARD, 0, 1 },
+    { "only-child",   0,                           POSITION_ONLY,     0, 0 },
+    { "sorted",       0,                           POSITION_SORTED,   0, 0 },
+    { "active",       GTK_STATE_FLAG_ACTIVE, },
+    { "prelight",     GTK_STATE_FLAG_PRELIGHT, },
+    { "hover",        GTK_STATE_FLAG_PRELIGHT, },
+    { "selected",     GTK_STATE_FLAG_SELECTED, },
+    { "insensitive",  GTK_STATE_FLAG_INSENSITIVE, },
+    { "inconsistent", GTK_STATE_FLAG_INCONSISTENT, },
+    { "focused",      GTK_STATE_FLAG_FOCUSED, },
+    { "focus",        GTK_STATE_FLAG_FOCUSED, },
+    { "backdrop",     GTK_STATE_FLAG_BACKDROP, },
+    { "dir(ltr)",     GTK_STATE_FLAG_DIR_LTR, },
+    { "dir(rtl)",     GTK_STATE_FLAG_DIR_RTL, }
+  };
+  guint i;
+
+  if (_gtk_css_parser_try (parser, "nth-child", FALSE))
+    return parse_selector_pseudo_class_nth_child (parser, selector, POSITION_FORWARD);
+  else if (_gtk_css_parser_try (parser, "nth-last-child", FALSE))
+    return parse_selector_pseudo_class_nth_child (parser, selector, POSITION_BACKWARD);
+
+  for (i = 0; i < G_N_ELEMENTS (pseudo_classes); i++)
+    {
+      if (_gtk_css_parser_try (parser, pseudo_classes[i].name, FALSE))
+        {
+          if (pseudo_classes[i].state_flag)
+            selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_PSEUDOCLASS_STATE,
+                                             selector,
+                                             GUINT_TO_POINTER (pseudo_classes[i].state_flag));
+          else
+            selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_PSEUDOCLASS_POSITION,
+                                             selector,
+                                             encode_position (pseudo_classes[i].position_type,
+                                                              pseudo_classes[i].position_a,
+                                                              pseudo_classes[i].position_b));
+          return selector;
+        }
+    }
+      
+  _gtk_css_parser_error (parser, "Missing name of pseudo-class");
+  if (selector)
+    _gtk_css_selector_free (selector);
+  return NULL;
+}
+
+static GtkCssSelector *
+try_parse_name (GtkCssParser   *parser,
+                GtkCssSelector *selector)
+{
+  char *name;
+
+  name = _gtk_css_parser_try_ident (parser, FALSE);
+  if (name)
+    {
+      if (_gtk_style_context_check_region_name (name))
+       selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_REGION,
+                                        selector,
+                                        g_intern_string (name));
+      else
+       selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_NAME,
+                                        selector,
+                                        get_type_reference (name));
+      g_free (name);
+    }
+  else if (_gtk_css_parser_try (parser, "*", FALSE))
+    selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_ANY, selector, NULL);
+  
+  return selector;
+}
+
+static GtkCssSelector *
+parse_simple_selector (GtkCssParser   *parser,
+                       GtkCssSelector *selector)
+{
+  guint size = gtk_css_selector_size (selector);
+  
+  selector = try_parse_name (parser, selector);
+
+  do {
+      if (_gtk_css_parser_try (parser, "#", FALSE))
+        selector = parse_selector_id (parser, selector);
+      else if (_gtk_css_parser_try (parser, ".", FALSE))
+        selector = parse_selector_class (parser, selector);
+      else if (_gtk_css_parser_try (parser, ":", FALSE))
+        selector = parse_selector_pseudo_class (parser, selector);
+      else if (gtk_css_selector_size (selector) == size)
+        {
+          _gtk_css_parser_error (parser, "Expected a valid selector");
+          if (selector)
+            _gtk_css_selector_free (selector);
+          return NULL;
+        }
+      else
+        break;
+    }
+  while (selector && !_gtk_css_parser_is_eof (parser));
+
+  _gtk_css_parser_skip_whitespace (parser);
+
+  return selector;
+}
+
+GtkCssSelector *
+_gtk_css_selector_parse (GtkCssParser *parser)
+{
+  GtkCssSelector *selector = NULL;
+
+  while ((selector = parse_simple_selector (parser, selector)) &&
+         !_gtk_css_parser_is_eof (parser) &&
+         !_gtk_css_parser_begins_with (parser, ',') &&
+         !_gtk_css_parser_begins_with (parser, '{'))
+    {
+      if (_gtk_css_parser_try (parser, "+", TRUE))
+        selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_ADJACENT, selector, NULL);
+      else if (_gtk_css_parser_try (parser, "~", TRUE))
+        selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_SIBLING, selector, NULL);
+      else if (_gtk_css_parser_try (parser, ">", TRUE))
+        selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_CHILD, selector, NULL);
+      else
+        selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_DESCENDANT, selector, NULL);
+    }
+
+  return selector;
+}
+
+void
+_gtk_css_selector_free (GtkCssSelector *selector)
+{
+  g_return_if_fail (selector != NULL);
+
+  g_free (selector);
+}
+
+void
+_gtk_css_selector_print (const GtkCssSelector *selector,
+                         GString *             str)
+{
+  const GtkCssSelector *previous;
+
+  g_return_if_fail (selector != NULL);
+
+  previous = gtk_css_selector_previous (selector);
+  if (previous)
+    _gtk_css_selector_print (previous, str);
+
+  selector->class->print (selector, str);
+}
+
+char *
+_gtk_css_selector_to_string (const GtkCssSelector *selector)
+{
+  GString *string;
+
+  g_return_val_if_fail (selector != NULL, NULL);
+
+  string = g_string_new (NULL);
+
+  _gtk_css_selector_print (selector, string);
+
+  return g_string_free (string, FALSE);
+}
+
+
+GtkCssChange
+_gtk_css_selector_tree_match_get_change (const GtkCssSelectorTree *tree)
+{
+  GtkCssChange change = 0;
+
+  update_type_references ();
+
+  while (tree)
+    {
+      change = tree->selector.class->get_change (&tree->selector, change);
+      tree = gtk_css_selector_tree_get_parent (tree);
+    }
+
+  return change;
+}
+
+/**
+ * _gtk_css_selector_matches:
  * @selector: the selector
  * @path: the path to check
  * @state: The state to match
@@ -652,40 +2009,16 @@ gtk_css_selector_matches_previous (const GtkCssSelector      *selector,
  * Returns: %TRUE if the selector matches @path
  **/
 gboolean
-_gtk_css_selector_matches (const GtkCssSelector      *selector,
-                           const GtkWidgetPath       *path,
-                           GtkStateFlags              state)
+_gtk_css_selector_matches (const GtkCssSelector *selector,
+                           const GtkCssMatcher  *matcher)
 {
-  GSList *list;
-  gboolean match;
-  guint length;
 
   g_return_val_if_fail (selector != NULL, FALSE);
-  g_return_val_if_fail (path != NULL, FALSE);
-
-  if ((selector->state & state) != selector->state)
-    return FALSE;
-
-  length = gtk_widget_path_length (path);
-  if (length == 0)
-    return FALSE;
+  g_return_val_if_fail (matcher != NULL, FALSE);
 
-  list = gtk_widget_path_iter_list_regions (path, length - 1);
-  match = gtk_css_selector_matches_from (selector,
-                                         path,
-                                         length - 1,
-                                         list);
-  g_slist_free (list);
-  return match;
-}
+  update_type_references ();
 
-static guint
-count_bits (guint v)
-{
-  /* http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel */
-  v = v - ((v >> 1) & 0x55555555);
-  v = (v & 0x33333333) + ((v >> 2) & 0x33333333);
-  return (((v + (v >> 4)) & 0xF0F0F0F) * 0x1010101) >> 24;
+  return gtk_css_selector_match (selector, matcher);
 }
 
 /* Computes specificity according to CSS 2.1.
@@ -696,23 +2029,17 @@ _gtk_css_selector_get_specificity (const GtkCssSelector *selector,
                                    guint                *classes,
                                    guint                *elements)
 {
-  GQuark *count;
-
-  if (selector->previous)
-    _gtk_css_selector_get_specificity (selector->previous, ids, classes, elements);
-
-  if (selector->ids)
-    for (count = selector->ids; *count; count++)
-      (*ids)++;
-
-  if (selector->classes)
-    for (count = selector->classes; *count; count++)
-      (*classes)++;
-  
-  *classes += count_bits (selector->state) + count_bits (selector->pseudo_classes);
-
-  if (selector->name)
-    (*elements)++;
+  for (; selector; selector = gtk_css_selector_previous (selector))
+    {
+      const GtkCssSelectorClass *klass = selector->class;
+
+      if (klass->increase_id_specificity)
+        (*ids)++;
+      if (klass->increase_class_specificity)
+        (*classes)++;
+      if (klass->increase_element_specificity)
+        (*elements)++;
+    }
 }
 
 int
@@ -737,11 +2064,431 @@ _gtk_css_selector_compare (const GtkCssSelector *a,
   return a_elements - b_elements;
 }
 
-GtkStateFlags
-_gtk_css_selector_get_state_flags (GtkCssSelector *selector)
+
+/******************** SelectorTree handling *****************/
+
+static GHashTable *
+gtk_css_selectors_count_initial_init (void)
+{
+  return g_hash_table_new ((GHashFunc)gtk_css_selector_hash, (GEqualFunc)gtk_css_selector_equal);
+}
+
+static void
+gtk_css_selectors_count_initial (const GtkCssSelector *selector, GHashTable *hash)
+{
+  if (!selector->class->is_simple || selector->class->must_keep_order)
+    {
+      guint count = GPOINTER_TO_INT (g_hash_table_lookup (hash, selector));
+      g_hash_table_replace (hash, (gpointer)selector, GUINT_TO_POINTER (count + 1));
+      return;
+    }
+
+  for (;
+       selector && selector->class->is_simple && !selector->class->must_keep_order;
+       selector = gtk_css_selector_previous (selector))
+    {
+      guint count = GPOINTER_TO_INT (g_hash_table_lookup (hash, selector));
+      g_hash_table_replace (hash, (gpointer)selector, GUINT_TO_POINTER (count + 1));
+    }
+}
+
+static gboolean
+gtk_css_selectors_has_initial_selector (const GtkCssSelector *selector, const GtkCssSelector *initial)
+{
+  if (!selector->class->is_simple || selector->class->must_keep_order)
+    return gtk_css_selector_equal (selector, initial);
+
+  for (;
+       selector && selector->class->is_simple && !selector->class->must_keep_order;
+       selector = gtk_css_selector_previous (selector))
+    {
+      if (gtk_css_selector_equal (selector, initial))
+       return TRUE;
+    }
+
+  return FALSE;
+}
+
+static GtkCssSelector *
+gtk_css_selectors_skip_initial_selector (GtkCssSelector *selector, const GtkCssSelector *initial)
+{
+  GtkCssSelector *found;
+  GtkCssSelector tmp;
+
+  /* If the initial simple selector is not first, move it there so we can skip it
+     without losing any other selectors */
+  if (!gtk_css_selector_equal (selector, initial))
+    {
+      for (found = selector; found && found->class->is_simple; found = (GtkCssSelector *)gtk_css_selector_previous (found))
+       {
+         if (gtk_css_selector_equal (found, initial))
+           break;
+       }
+
+      g_assert (found != NULL && found->class->is_simple);
+
+      tmp = *found;
+      *found = *selector;
+      *selector = tmp;
+    }
+
+  return (GtkCssSelector *)gtk_css_selector_previous (selector);
+}
+
+static int
+direct_ptr_compare (const void *_a, const void *_b)
+{
+  gpointer *a = (gpointer *)_a;
+  gpointer *b = (gpointer *)_b;
+  if (*a < *b)
+    return -1;
+  else if (*a == *b)
+    return 0;
+  return 1;
+}
+
+GPtrArray *
+_gtk_css_selector_tree_match_all (const GtkCssSelectorTree *tree,
+                                 const GtkCssMatcher *matcher)
+{
+  GHashTable *res;
+  GPtrArray *array;
+  GHashTableIter iter;
+  gpointer key;
+
+  update_type_references ();
+
+  res = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+  for (; tree != NULL;
+       tree = gtk_css_selector_tree_get_sibling (tree))
+    gtk_css_selector_tree_match (tree, matcher, res);
+
+  array = g_ptr_array_sized_new (g_hash_table_size (res));
+
+  g_hash_table_iter_init (&iter, res);
+  while (g_hash_table_iter_next (&iter, &key, NULL))
+    g_ptr_array_add (array, key);
+
+  g_hash_table_destroy (res);
+
+  qsort (array->pdata, array->len, sizeof (gpointer), direct_ptr_compare);
+
+  return array;
+}
+
+GtkCssChange
+_gtk_css_selector_tree_get_change_all (const GtkCssSelectorTree *tree,
+                                      const GtkCssMatcher *matcher)
+{
+  GtkCssChange change;
+
+  change = 0;
+
+  for (; tree != NULL;
+       tree = gtk_css_selector_tree_get_sibling (tree))
+    change |= gtk_css_selector_tree_get_change (tree, matcher);
+
+  /* Never return reserved bit set */
+  return change & ~GTK_CSS_CHANGE_RESERVED_BIT;
+}
+
+#ifdef PRINT_TREE
+static void
+_gtk_css_selector_tree_print (GtkCssSelectorTree *tree, GString *str, char *prefix)
+{
+  gboolean first = TRUE;
+  int len, i;
+
+  for (; tree != NULL; tree = tree->siblings, first = FALSE)
+    {
+      if (!first)
+       g_string_append (str, prefix);
+
+      if (first)
+       {
+         if (tree->siblings)
+           g_string_append (str, "─┬─");
+         else
+           g_string_append (str, "───");
+       }
+      else
+       {
+         if (tree->siblings)
+           g_string_append (str, " ├─");
+         else
+           g_string_append (str, " └─");
+       }
+
+      len = str->len;
+      tree->selector.class->print (&tree->selector, str);
+      len = str->len - len;
+
+      if (gtk_css_selector_tree_get_previous (tree))
+       {
+         GString *prefix2 = g_string_new (prefix);
+
+         if (tree->siblings)
+           g_string_append (prefix2, " │ ");
+         else
+           g_string_append (prefix2, "   ");
+         for (i = 0; i < len; i++)
+           g_string_append_c (prefix2, ' ');
+
+         _gtk_css_selector_tree_print (gtk_css_selector_tree_get_previous (tree), str, prefix2->str);
+         g_string_free (prefix2, TRUE);
+       }
+      else
+       g_string_append (str, "\n");
+    }
+}
+#endif
+
+void
+_gtk_css_selector_tree_match_print (const GtkCssSelectorTree *tree,
+                                   GString *str)
+{
+  const GtkCssSelectorTree *parent;
+
+  g_return_if_fail (tree != NULL);
+
+  tree->selector.class->print (&tree->selector, str);
+
+  parent = gtk_css_selector_tree_get_parent (tree);
+  if (parent != NULL)
+    _gtk_css_selector_tree_match_print (parent, str);
+}
+
+void
+_gtk_css_selector_tree_free (GtkCssSelectorTree *tree)
+{
+  if (tree == NULL)
+    return;
+
+  g_free (tree);
+}
+
+
+typedef struct {
+  gpointer match;
+  GtkCssSelector *current_selector;
+  GtkCssSelectorTree **selector_match;
+} GtkCssSelectorRuleSetInfo;
+
+static GtkCssSelectorTree *
+get_tree (GByteArray *array, gint32 offset)
+{
+  return (GtkCssSelectorTree *) (array->data + offset);
+}
+
+static GtkCssSelectorTree *
+alloc_tree (GByteArray *array, gint32 *offset)
+{
+  GtkCssSelectorTree tree = { { NULL} };
+
+  *offset = array->len;
+  g_byte_array_append (array, (guint8 *)&tree, sizeof (GtkCssSelectorTree));
+  return get_tree (array, *offset);
+}
+
+static gint32
+subdivide_infos (GByteArray *array, GList *infos, gint32 parent_offset)
+{
+  GHashTable *ht;
+  GList *l;
+  GList *matched;
+  GList *remaining;
+  gint32 tree_offset;
+  GtkCssSelectorTree *tree;
+  GtkCssSelectorRuleSetInfo *info;
+  GtkCssSelector max_selector;
+  GHashTableIter iter;
+  guint max_count;
+  gpointer key, value;
+  GPtrArray *exact_matches;
+  gint32 res;
+
+  if (infos == NULL)
+    return GTK_CSS_SELECTOR_TREE_EMPTY_OFFSET;
+
+  ht = gtk_css_selectors_count_initial_init ();
+
+  for (l = infos; l != NULL; l = l->next)
+    {
+      info = l->data;
+      gtk_css_selectors_count_initial (info->current_selector, ht);
+    }
+
+  /* Pick the selector with highest count, and use as decision on this level
+     as that makes it possible to skip the largest amount of checks later */
+
+  max_count = 0;
+
+  g_hash_table_iter_init (&iter, ht);
+  while (g_hash_table_iter_next (&iter, &key, &value))
+    {
+      GtkCssSelector *selector = key;
+      if (GPOINTER_TO_UINT (value) > max_count ||
+         (GPOINTER_TO_UINT (value) == max_count &&
+         gtk_css_selector_compare_one (selector, &max_selector) < 0))
+       {
+         max_count = GPOINTER_TO_UINT (value);
+         max_selector = *selector;
+       }
+    }
+
+  matched = NULL;
+  remaining = NULL;
+
+  tree = alloc_tree (array, &tree_offset);
+  tree->parent_offset = parent_offset;
+  tree->selector = max_selector;
+
+  exact_matches = NULL;
+  for (l = infos; l != NULL; l = l->next)
+    {
+      info = l->data;
+
+      if (gtk_css_selectors_has_initial_selector (info->current_selector, &max_selector))
+       {
+         info->current_selector = gtk_css_selectors_skip_initial_selector (info->current_selector, &max_selector);
+         if (info->current_selector == NULL)
+           {
+             /* Matches current node */
+             if (exact_matches == NULL)
+               exact_matches = g_ptr_array_new ();
+             g_ptr_array_add (exact_matches, info->match);
+             if (info->selector_match != NULL)
+               *info->selector_match = GUINT_TO_POINTER (tree_offset);
+           }
+         else
+           matched = g_list_prepend (matched, info);
+       }
+      else
+       {
+         remaining = g_list_prepend (remaining, info);
+       }
+    }
+
+  if (exact_matches)
+    {
+      g_ptr_array_add (exact_matches, NULL); /* Null terminate */
+      res = array->len;
+      g_byte_array_append (array, (guint8 *)exact_matches->pdata,
+                          exact_matches->len * sizeof (gpointer));
+      g_ptr_array_free (exact_matches, TRUE);
+    }
+  else
+    res = GTK_CSS_SELECTOR_TREE_EMPTY_OFFSET;
+  get_tree (array, tree_offset)->matches_offset = res;
+
+  res = subdivide_infos (array, matched, tree_offset);
+  get_tree (array, tree_offset)->previous_offset = res;
+
+  res = subdivide_infos (array, remaining, parent_offset);
+  get_tree (array, tree_offset)->sibling_offset = res;
+
+  g_list_free (matched);
+  g_list_free (remaining);
+  g_hash_table_unref (ht);
+
+  return tree_offset;
+}
+
+struct _GtkCssSelectorTreeBuilder {
+  GList  *infos;
+};
+
+GtkCssSelectorTreeBuilder *
+_gtk_css_selector_tree_builder_new (void)
+{
+  return g_new0 (GtkCssSelectorTreeBuilder, 1);
+}
+
+void
+_gtk_css_selector_tree_builder_free  (GtkCssSelectorTreeBuilder *builder)
+{
+  g_list_free_full (builder->infos, g_free);
+  g_free (builder);
+}
+
+void
+_gtk_css_selector_tree_builder_add (GtkCssSelectorTreeBuilder *builder,
+                                   GtkCssSelector            *selectors,
+                                   GtkCssSelectorTree       **selector_match,
+                                   gpointer                   match)
+{
+  GtkCssSelectorRuleSetInfo *info = g_new0 (GtkCssSelectorRuleSetInfo, 1);
+
+  info->match = match;
+  info->current_selector = selectors;
+  info->selector_match = selector_match;
+  builder->infos = g_list_prepend (builder->infos, info);
+}
+
+/* Convert all offsets to node-relative */
+static void
+fixup_offsets (GtkCssSelectorTree *tree, guint8 *data)
 {
-  g_return_val_if_fail (selector != NULL, 0);
+  while (tree != NULL)
+    {
+      if (tree->parent_offset != GTK_CSS_SELECTOR_TREE_EMPTY_OFFSET)
+       tree->parent_offset -= ((guint8 *)tree - data);
+
+      if (tree->previous_offset != GTK_CSS_SELECTOR_TREE_EMPTY_OFFSET)
+       tree->previous_offset -= ((guint8 *)tree - data);
 
-  return selector->state;
+      if (tree->sibling_offset != GTK_CSS_SELECTOR_TREE_EMPTY_OFFSET)
+       tree->sibling_offset -= ((guint8 *)tree - data);
+
+      if (tree->matches_offset != GTK_CSS_SELECTOR_TREE_EMPTY_OFFSET)
+       tree->matches_offset -= ((guint8 *)tree - data);
+
+      fixup_offsets ((GtkCssSelectorTree *)gtk_css_selector_tree_get_previous (tree), data);
+
+      tree = (GtkCssSelectorTree *)gtk_css_selector_tree_get_sibling (tree);
+    }
 }
 
+GtkCssSelectorTree *
+_gtk_css_selector_tree_builder_build (GtkCssSelectorTreeBuilder *builder)
+{
+  GtkCssSelectorTree *tree;
+  GByteArray *array;
+  guint8 *data;
+  guint len;
+  GList *l;
+  GtkCssSelectorRuleSetInfo *info;
+
+  array = g_byte_array_new ();
+  subdivide_infos (array, builder->infos, GTK_CSS_SELECTOR_TREE_EMPTY_OFFSET);
+
+  len = array->len;
+  data = g_byte_array_free (array, FALSE);
+
+  /* shrink to final size */
+  data = g_realloc (data, len);
+
+  tree = (GtkCssSelectorTree *)data;
+
+  fixup_offsets (tree, data);
+
+  /* Convert offsets to final pointers */
+  for (l = builder->infos; l != NULL; l = l->next)
+    {
+      info = l->data;
+      if (info->selector_match)
+       *info->selector_match = (GtkCssSelectorTree *)(data + GPOINTER_TO_UINT (*info->selector_match));
+    }
+
+#ifdef PRINT_TREE
+  {
+    GString *s = g_string_new ("");
+    _gtk_css_selector_tree_print (tree, s, "");
+    g_print ("%s", s->str);
+    g_string_free (s, TRUE);
+  }
+#endif
+
+  return tree;
+}