]> Pileus Git - ~andy/gtk/blobdiff - gtk/gtkcssselector.c
label: Fix memleak
[~andy/gtk] / gtk / gtkcssselector.c
index 29ca387e3dbf0ac49347c3b1f3e055d139e8b106..216090faaddccaeba251026712d5066ddac2c63e 100644 (file)
 #include "gtkcssprovider.h"
 #include "gtkstylecontextprivate.h"
 
+/* 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 {
@@ -39,6 +50,8 @@ struct _GtkCssSelectorClass {
                                     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);
 
@@ -57,13 +70,14 @@ struct _GtkCssSelector
                                              - GUINT_TO_POINTER() for PSEUDOCLASS_REGION/STATE */
 };
 
+#define GTK_CSS_SELECTOR_TREE_EMPTY_OFFSET G_MAXINT32
 struct _GtkCssSelectorTree
 {
   GtkCssSelector selector;
-  GtkCssSelectorTree *parent;
-  GtkCssSelectorTree *previous;
-  GtkCssSelectorTree *sibling;
-  gpointer *matches; /* pointers that we return as matches if selector matches */
+  gint32 parent_offset;
+  gint32 previous_offset;
+  gint32 sibling_offset;
+  gint32 matches_offset; /* pointers that we return as matches if selector matches */
 };
 
 static gboolean
@@ -81,16 +95,27 @@ gtk_css_selector_hash (const GtkCssSelector *selector)
   return GPOINTER_TO_UINT (selector->class) ^ GPOINTER_TO_UINT (selector->data);
 }
 
+static gpointer *
+gtk_css_selector_tree_get_matches (const GtkCssSelectorTree *tree)
+{
+  if (tree->matches_offset == GTK_CSS_SELECTOR_TREE_EMPTY_OFFSET)
+    return NULL;
+
+  return (gpointer *) ((guint8 *)tree + tree->matches_offset);
+}
+
 static void
 gtk_css_selector_tree_found_match (const GtkCssSelectorTree *tree,
                                   GHashTable *res)
 {
   int i;
+  gpointer *matches;
 
-  if (tree->matches)
+  matches = gtk_css_selector_tree_get_matches (tree);
+  if (matches)
     {
-      for (i = 0; tree->matches[i] != NULL; i++)
-       g_hash_table_insert (res, tree->matches[i], tree->matches[i]);
+      for (i = 0; matches[i] != NULL; i++)
+       g_hash_table_insert (res, matches[i], matches[i]);
     }
 }
 
@@ -105,6 +130,16 @@ gtk_css_selector_tree_match (const GtkCssSelectorTree *tree,
   tree->selector.class->tree_match (tree, matcher, res);
 }
 
+static GtkCssChange
+gtk_css_selector_tree_get_change (const GtkCssSelectorTree *tree,
+                                 const GtkCssMatcher      *matcher)
+{
+  if (tree == NULL)
+    return 0;
+
+  return tree->selector.class->tree_get_change (tree, matcher);
+}
+
 static gboolean
 gtk_css_selector_match (const GtkCssSelector *selector,
                         const GtkCssMatcher  *matcher)
@@ -132,22 +167,60 @@ gtk_css_selector_previous (const GtkCssSelector *selector)
   return selector->class ? selector : NULL;
 }
 
+static const GtkCssSelectorTree *
+gtk_css_selector_tree_at_offset (const GtkCssSelectorTree *tree,
+                                gint32 offset)
+{
+  if (offset == GTK_CSS_SELECTOR_TREE_EMPTY_OFFSET)
+    return NULL;
+
+  return (GtkCssSelectorTree *) ((guint8 *)tree + offset);
+}
+
 static const GtkCssSelectorTree *
 gtk_css_selector_tree_get_parent (const GtkCssSelectorTree *tree)
 {
-  return tree->parent;
+  return gtk_css_selector_tree_at_offset (tree, tree->parent_offset);
 }
 
 static const GtkCssSelectorTree *
 gtk_css_selector_tree_get_previous (const GtkCssSelectorTree *tree)
 {
-  return tree->previous;
+  return gtk_css_selector_tree_at_offset (tree, tree->previous_offset);
 }
 
 static const GtkCssSelectorTree *
 gtk_css_selector_tree_get_sibling (const GtkCssSelectorTree *tree)
 {
-  return tree->sibling;
+  return gtk_css_selector_tree_at_offset (tree, tree->sibling_offset);
+}
+
+static void
+gtk_css_selector_tree_match_previous (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))
+    gtk_css_selector_tree_match (prev, matcher, res);
+}
+
+static GtkCssChange
+gtk_css_selector_tree_get_previous_change (const GtkCssSelectorTree *tree,
+                                          const GtkCssMatcher      *matcher)
+{
+  GtkCssChange previous_change = 0;
+  const GtkCssSelectorTree *prev;
+
+  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);
+
+  return previous_change;
 }
 
 /* DESCENDANT */
@@ -182,16 +255,12 @@ gtk_css_selector_descendant_tree_match (const GtkCssSelectorTree *tree,
                                        GHashTable *res)
 {
   GtkCssMatcher ancestor;
-  const GtkCssSelectorTree *prev;
 
   while (_gtk_css_matcher_get_parent (&ancestor, matcher))
     {
       matcher = &ancestor;
 
-      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);
+      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 */
@@ -200,6 +269,33 @@ gtk_css_selector_descendant_tree_match (const GtkCssSelectorTree *tree,
     }
 }
 
+static GtkCssChange
+gtk_css_selector_descendant_tree_get_change (const GtkCssSelectorTree *tree,
+                                            const GtkCssMatcher  *matcher)
+{
+  GtkCssMatcher ancestor;
+  GtkCssChange change, previous_change;
+
+  change = 0;
+  previous_change = 0;
+  while (_gtk_css_matcher_get_parent (&ancestor, matcher))
+    {
+      matcher = &ancestor;
+
+      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 (previous_change != 0)
+    change |= _gtk_css_change_for_child (previous_change) | GTK_CSS_CHANGE_GOT_MATCH;
+
+  return change;
+}
+
 static int
 gtk_css_selector_descendant_compare_one (const GtkCssSelector *a,
                                         const GtkCssSelector *b)
@@ -219,6 +315,7 @@ static const GtkCssSelectorClass GTK_CSS_SELECTOR_DESCENDANT = {
   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
 };
@@ -250,15 +347,32 @@ gtk_css_selector_child_tree_match (const GtkCssSelectorTree *tree,
                                   GHashTable *res)
 {
   GtkCssMatcher parent;
-  const GtkCssSelectorTree *prev;
 
   if (!_gtk_css_matcher_get_parent (&parent, matcher))
     return;
 
-  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, &parent, res);
+  gtk_css_selector_tree_match_previous (tree, &parent, res);
+}
+
+
+static GtkCssChange
+gtk_css_selector_child_tree_get_change (const GtkCssSelectorTree *tree,
+                                       const GtkCssMatcher  *matcher)
+{
+  GtkCssMatcher parent;
+  GtkCssChange change, previous_change;
+
+  if (!_gtk_css_matcher_get_parent (&parent, matcher))
+    return 0;
+
+  change = 0;
+
+  previous_change = gtk_css_selector_tree_get_previous_change (tree, &parent);
+
+  if (previous_change != 0)
+    change |= _gtk_css_change_for_child (previous_change) | GTK_CSS_CHANGE_GOT_MATCH;
+
+  return change;
 }
 
 static GtkCssChange
@@ -280,6 +394,7 @@ static const GtkCssSelectorClass GTK_CSS_SELECTOR_CHILD = {
   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
 };
@@ -316,16 +431,12 @@ gtk_css_selector_sibling_tree_match (const GtkCssSelectorTree *tree,
                                     GHashTable *res)
 {
   GtkCssMatcher previous;
-  const GtkCssSelectorTree *prev;
 
   while (_gtk_css_matcher_get_previous (&previous, matcher))
     {
       matcher = &previous;
 
-      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);
+      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 */
@@ -334,6 +445,34 @@ gtk_css_selector_sibling_tree_match (const GtkCssSelectorTree *tree,
     }
 }
 
+static GtkCssChange
+gtk_css_selector_sibling_tree_get_change (const GtkCssSelectorTree *tree,
+                                         const GtkCssMatcher  *matcher)
+{
+  GtkCssMatcher previous;
+  GtkCssChange change, previous_change;
+
+  change = 0;
+
+  previous_change = 0;
+  while (_gtk_css_matcher_get_previous (&previous, matcher))
+    {
+      matcher = &previous;
+
+      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 (previous_change != 0)
+    change |= _gtk_css_change_for_sibling (previous_change) |  GTK_CSS_CHANGE_GOT_MATCH;
+
+  return change;
+}
+
 static GtkCssChange
 gtk_css_selector_sibling_get_change (const GtkCssSelector *selector, GtkCssChange previous_change)
 {
@@ -354,6 +493,7 @@ static const GtkCssSelectorClass GTK_CSS_SELECTOR_SIBLING = {
   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
 };
@@ -385,17 +525,33 @@ gtk_css_selector_adjacent_tree_match (const GtkCssSelectorTree *tree,
                                      GHashTable *res)
 {
   GtkCssMatcher previous;
-  const GtkCssSelectorTree *prev;
 
   if (!_gtk_css_matcher_get_previous (&previous, matcher))
     return;
 
   matcher = &previous;
 
-  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);
+  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
@@ -417,6 +573,7 @@ static const GtkCssSelectorClass GTK_CSS_SELECTOR_ADJACENT = {
   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
 };
@@ -452,7 +609,7 @@ gtk_css_selector_any_tree_match (const GtkCssSelectorTree *tree,
                                 const GtkCssMatcher  *matcher,
                                 GHashTable *res)
 {
-  const GtkCssSelectorTree *prev, *prev2;
+  const GtkCssSelectorTree *prev;
 
   gtk_css_selector_tree_found_match (tree, res);
 
@@ -462,17 +619,43 @@ gtk_css_selector_any_tree_match (const GtkCssSelectorTree *tree,
     {
       if (prev->selector.class == &GTK_CSS_SELECTOR_DESCENDANT &&
          _gtk_css_matcher_has_regions (matcher))
-       {
-         for (prev2 = gtk_css_selector_tree_get_previous (prev);
-              prev2 != NULL;
-              prev2 = gtk_css_selector_tree_get_sibling (prev2))
-           gtk_css_selector_tree_match (prev2, matcher, res);
-       }
+       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)
 {
@@ -492,24 +675,84 @@ static const GtkCssSelectorClass GTK_CSS_SELECTOR_ANY = {
   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, selector->data);
+  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_name (matcher, selector->data))
+  if (!_gtk_css_matcher_has_type (matcher, ((TypeReference *)selector->data)->type))
     return FALSE;
 
   return gtk_css_selector_match (gtk_css_selector_previous (selector), matcher);
@@ -520,17 +763,34 @@ gtk_css_selector_name_tree_match (const GtkCssSelectorTree *tree,
                                  const GtkCssMatcher  *matcher,
                                  GHashTable *res)
 {
-  const GtkCssSelectorTree *prev;
-
-  if (!_gtk_css_matcher_has_name (matcher, tree->selector.data))
+  if (!_gtk_css_matcher_has_type (matcher, ((TypeReference *)tree->selector.data)->type))
     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))
-    gtk_css_selector_tree_match (prev, matcher, 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;
 }
 
 
@@ -544,7 +804,8 @@ static int
 gtk_css_selector_name_compare_one (const GtkCssSelector *a,
                                   const GtkCssSelector *b)
 {
-  return strcmp (a->data, b->data);
+  return strcmp (((TypeReference *)a->data)->name,
+                ((TypeReference *)b->data)->name);
 }
 
 static const GtkCssSelectorClass GTK_CSS_SELECTOR_NAME = {
@@ -553,6 +814,7 @@ static const GtkCssSelectorClass GTK_CSS_SELECTOR_NAME = {
   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
 };
@@ -588,7 +850,7 @@ gtk_css_selector_region_tree_match (const GtkCssSelectorTree *tree,
                                    const GtkCssMatcher  *matcher,
                                    GHashTable *res)
 {
-  const GtkCssSelectorTree *prev, *prev2;
+  const GtkCssSelectorTree *prev;
 
   if (!_gtk_css_matcher_has_region (matcher, tree->selector.data, 0))
     return;
@@ -600,17 +862,48 @@ gtk_css_selector_region_tree_match (const GtkCssSelectorTree *tree,
        prev = gtk_css_selector_tree_get_sibling (prev))
     {
       if (prev->selector.class == &GTK_CSS_SELECTOR_DESCENDANT)
-       {
-         for (prev2 = gtk_css_selector_tree_get_previous (prev);
-              prev2 != NULL;
-              prev2 = gtk_css_selector_tree_get_sibling (prev2))
-           gtk_css_selector_tree_match (prev2, matcher, res);
-       }
+       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)
 {
@@ -636,6 +929,7 @@ static const GtkCssSelectorClass GTK_CSS_SELECTOR_REGION = {
   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
 };
@@ -665,17 +959,34 @@ gtk_css_selector_class_tree_match (const GtkCssSelectorTree *tree,
                                   const GtkCssMatcher  *matcher,
                                   GHashTable *res)
 {
-  const GtkCssSelectorTree *prev;
-
   if (!_gtk_css_matcher_has_class (matcher, GPOINTER_TO_UINT (tree->selector.data)))
     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))
-    gtk_css_selector_tree_match (prev, matcher, 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
@@ -698,6 +1009,7 @@ static const GtkCssSelectorClass GTK_CSS_SELECTOR_CLASS = {
   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
 };
@@ -727,17 +1039,34 @@ gtk_css_selector_id_tree_match (const GtkCssSelectorTree *tree,
                                const GtkCssMatcher  *matcher,
                                GHashTable *res)
 {
-  const GtkCssSelectorTree *prev;
-
   if (!_gtk_css_matcher_has_id (matcher, tree->selector.data))
     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))
-    gtk_css_selector_tree_match (prev, matcher, 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
@@ -760,6 +1089,7 @@ static const GtkCssSelectorClass GTK_CSS_SELECTOR_ID = {
   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
 };
@@ -777,7 +1107,9 @@ gtk_css_selector_pseudoclass_state_print (const GtkCssSelector *selector,
     "insensitive",
     "inconsistent",
     "focus",
-    "backdrop"
+    "backdrop",
+    "dir(ltr)",
+    "dir(rtl)"
   };
   guint i, state;
 
@@ -814,19 +1146,37 @@ gtk_css_selector_pseudoclass_state_tree_match (const GtkCssSelectorTree *tree,
                                               GHashTable *res)
 {
   GtkStateFlags state = GPOINTER_TO_UINT (tree->selector.data);
-  const GtkCssSelectorTree *prev;
 
   if ((_gtk_css_matcher_get_state (matcher) & state) != state)
     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))
-    gtk_css_selector_tree_match (prev, matcher, 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)
@@ -847,6 +1197,7 @@ static const GtkCssSelectorClass GTK_CSS_SELECTOR_PSEUDOCLASS_STATE = {
   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
 };
@@ -990,11 +1341,8 @@ gtk_css_selector_pseudoclass_position_print (const GtkCssSelector *selector,
 }
 
 static gboolean
-gtk_css_selector_pseudoclass_position_match_for_region (const GtkCssSelector *selector,
-                                                        const GtkCssMatcher  *matcher)
+get_selector_flags_for_position_region_match (const GtkCssSelector *selector, GtkRegionFlags *selector_flags)
 {
-  GtkRegionFlags selector_flags;
-  const GtkCssSelector *previous;
   PositionType type;
   int a, b;
 
@@ -1003,30 +1351,44 @@ gtk_css_selector_pseudoclass_position_match_for_region (const GtkCssSelector *se
     {
     case POSITION_FORWARD:
       if (a == 0 && b == 1)
-        selector_flags = GTK_REGION_FIRST;
+        *selector_flags = GTK_REGION_FIRST;
       else if (a == 2 && b == 0)
-        selector_flags = GTK_REGION_EVEN;
+        *selector_flags = GTK_REGION_EVEN;
       else if (a == 2 && b == 1)
-        selector_flags = GTK_REGION_ODD;
+        *selector_flags = GTK_REGION_ODD;
       else
         return FALSE;
       break;
     case POSITION_BACKWARD:
       if (a == 0 && b == 1)
-        selector_flags = GTK_REGION_LAST;
+        *selector_flags = GTK_REGION_LAST;
       else
         return FALSE;
       break;
     case POSITION_ONLY:
-      selector_flags = GTK_REGION_ONLY;
+      *selector_flags = GTK_REGION_ONLY;
       break;
     case POSITION_SORTED:
-      selector_flags = GTK_REGION_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))
@@ -1041,17 +1403,12 @@ gtk_css_selector_pseudoclass_position_match_for_region (const GtkCssSelector *se
 }
 
 static gboolean
-gtk_css_selector_pseudoclass_position_match (const GtkCssSelector *selector,
-                                             const GtkCssMatcher  *matcher)
+get_position_match (const GtkCssSelector *selector,
+                   const GtkCssMatcher  *matcher)
 {
-  const GtkCssSelector *previous;
   PositionType type;
   int a, b;
 
-  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);
-
   decode_position (selector, &type, &a, &b);
   switch (type)
     {
@@ -1075,6 +1432,22 @@ gtk_css_selector_pseudoclass_position_match (const GtkCssSelector *selector,
       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);
 }
 
@@ -1086,38 +1459,9 @@ gtk_css_selector_pseudoclass_position_tree_match_for_region (const GtkCssSelecto
 {
   const GtkCssSelectorTree *prev2;
   GtkRegionFlags selector_flags;
-  PositionType type;
-  int a, b;
 
-  decode_position (&tree->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;
-      break;
-    case POSITION_BACKWARD:
-      if (a == 0 && b == 1)
-        selector_flags = GTK_REGION_LAST;
-      else
-        return;
-      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;
-    }
+  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;
@@ -1140,8 +1484,6 @@ gtk_css_selector_pseudoclass_position_tree_match (const GtkCssSelectorTree *tree
                                                  GHashTable *res)
 {
   const GtkCssSelectorTree *prev;
-  PositionType type;
-  int a, b;
 
   for (prev = gtk_css_selector_tree_get_previous (tree);
        prev != NULL;
@@ -1151,28 +1493,8 @@ gtk_css_selector_pseudoclass_position_tree_match (const GtkCssSelectorTree *tree
        gtk_css_selector_pseudoclass_position_tree_match_for_region (tree, prev, matcher, res);
     }
 
-  decode_position (&tree->selector, &type, &a, &b);
-  switch (type)
-    {
-    case POSITION_FORWARD:
-      if (!_gtk_css_matcher_has_position (matcher, TRUE, a, b))
-       return;
-      break;
-    case POSITION_BACKWARD:
-      if (!_gtk_css_matcher_has_position (matcher, FALSE, a, b))
-       return;
-      break;
-    case POSITION_ONLY:
-      if (!_gtk_css_matcher_has_position (matcher, TRUE, 0, 1) ||
-         !_gtk_css_matcher_has_position (matcher, FALSE, 0, 1))
-       return;
-      break;
-    case POSITION_SORTED:
-      return;
-    default:
-      g_assert_not_reached ();
-      return;
-    }
+  if (!get_position_match (&tree->selector, matcher))
+    return;
 
   gtk_css_selector_tree_found_match (tree, res);
 
@@ -1183,6 +1505,77 @@ gtk_css_selector_pseudoclass_position_tree_match (const GtkCssSelectorTree *tree
     }
 }
 
+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)
 {
@@ -1202,6 +1595,7 @@ static const GtkCssSelectorClass GTK_CSS_SELECTOR_PSEUDOCLASS_POSITION = {
   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
 };
@@ -1431,7 +1825,9 @@ parse_selector_pseudo_class (GtkCssParser   *parser,
     { "inconsistent", GTK_STATE_FLAG_INCONSISTENT, },
     { "focused",      GTK_STATE_FLAG_FOCUSED, },
     { "focus",        GTK_STATE_FLAG_FOCUSED, },
-    { "backdrop",     GTK_STATE_FLAG_BACKDROP, }
+    { "backdrop",     GTK_STATE_FLAG_BACKDROP, },
+    { "dir(ltr)",     GTK_STATE_FLAG_DIR_LTR, },
+    { "dir(rtl)",     GTK_STATE_FLAG_DIR_RTL, }
   };
   guint i;
 
@@ -1473,11 +1869,14 @@ try_parse_name (GtkCssParser   *parser,
   name = _gtk_css_parser_try_ident (parser, FALSE);
   if (name)
     {
-      selector = gtk_css_selector_new (_gtk_style_context_check_region_name (name)
-                                       ? &GTK_CSS_SELECTOR_REGION
-                                       : &GTK_CSS_SELECTOR_NAME,
-                                       selector,
-                                       g_intern_string (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))
@@ -1584,6 +1983,8 @@ _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);
@@ -1615,6 +2016,8 @@ _gtk_css_selector_matches (const GtkCssSelector *selector,
   g_return_val_if_fail (selector != NULL, FALSE);
   g_return_val_if_fail (matcher != NULL, FALSE);
 
+  update_type_references ();
+
   return gtk_css_selector_match (selector, matcher);
 }
 
@@ -1753,6 +2156,8 @@ _gtk_css_selector_tree_match_all (const GtkCssSelectorTree *tree,
   GHashTableIter iter;
   gpointer key;
 
+  update_type_references ();
+
   res = g_hash_table_new (g_direct_hash, g_direct_equal);
 
   for (; tree != NULL;
@@ -1772,6 +2177,22 @@ _gtk_css_selector_tree_match_all (const GtkCssSelectorTree *tree,
   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)
@@ -1844,9 +2265,6 @@ _gtk_css_selector_tree_free (GtkCssSelectorTree *tree)
   if (tree == NULL)
     return;
 
-  _gtk_css_selector_tree_free (tree->sibling);
-  _gtk_css_selector_tree_free (tree->previous);
-
   g_free (tree);
 }
 
@@ -1857,24 +2275,43 @@ typedef struct {
   GtkCssSelectorTree **selector_match;
 } GtkCssSelectorRuleSetInfo;
 
+static GtkCssSelectorTree *
+get_tree (GByteArray *array, gint32 offset)
+{
+  return (GtkCssSelectorTree *) (array->data + offset);
+}
 
 static GtkCssSelectorTree *
-subdivide_infos (GList *infos, GtkCssSelectorTree *parent)
+alloc_tree (GByteArray *array, gint32 *offset)
 {
-  GHashTable *ht = gtk_css_selectors_count_initial_init ();
+  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;
+  GtkCssSelector max_selector;
   GHashTableIter iter;
   guint max_count;
   gpointer key, value;
   GPtrArray *exact_matches;
+  gint32 res;
 
   if (infos == NULL)
-    return NULL;
+    return GTK_CSS_SELECTOR_TREE_EMPTY_OFFSET;
+
+  ht = gtk_css_selectors_count_initial_init ();
 
   for (l = infos; l != NULL; l = l->next)
     {
@@ -1886,7 +2323,6 @@ subdivide_infos (GList *infos, GtkCssSelectorTree *parent)
      as that makes it possible to skip the largest amount of checks later */
 
   max_count = 0;
-  max_selector = NULL;
 
   g_hash_table_iter_init (&iter, ht);
   while (g_hash_table_iter_next (&iter, &key, &value))
@@ -1894,28 +2330,28 @@ subdivide_infos (GList *infos, GtkCssSelectorTree *parent)
       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))
+         gtk_css_selector_compare_one (selector, &max_selector) < 0))
        {
          max_count = GPOINTER_TO_UINT (value);
-         max_selector = selector;
+         max_selector = *selector;
        }
     }
 
   matched = NULL;
   remaining = NULL;
 
-  tree = g_new0 (GtkCssSelectorTree, 1);
-  tree->parent = parent;
-  tree->selector = *max_selector;
+  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, &tree->selector))
+      if (gtk_css_selectors_has_initial_selector (info->current_selector, &max_selector))
        {
-         info->current_selector = gtk_css_selectors_skip_initial_selector (info->current_selector, &tree->selector);
+         info->current_selector = gtk_css_selectors_skip_initial_selector (info->current_selector, &max_selector);
          if (info->current_selector == NULL)
            {
              /* Matches current node */
@@ -1923,7 +2359,7 @@ subdivide_infos (GList *infos, GtkCssSelectorTree *parent)
                exact_matches = g_ptr_array_new ();
              g_ptr_array_add (exact_matches, info->match);
              if (info->selector_match != NULL)
-               *info->selector_match = tree;
+               *info->selector_match = GUINT_TO_POINTER (tree_offset);
            }
          else
            matched = g_list_prepend (matched, info);
@@ -1937,19 +2373,26 @@ subdivide_infos (GList *infos, GtkCssSelectorTree *parent)
   if (exact_matches)
     {
       g_ptr_array_add (exact_matches, NULL); /* Null terminate */
-      tree->matches = g_ptr_array_free (exact_matches, FALSE);
+      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;
 
-  if (matched)
-    tree->previous = subdivide_infos (matched, tree);
+  res = subdivide_infos (array, matched, tree_offset);
+  get_tree (array, tree_offset)->previous_offset = res;
 
-  if (remaining)
-    tree->sibling = subdivide_infos (remaining, parent);
+  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;
+  return tree_offset;
 }
 
 struct _GtkCssSelectorTreeBuilder {
@@ -1983,12 +2426,60 @@ _gtk_css_selector_tree_builder_add (GtkCssSelectorTreeBuilder *builder,
   builder->infos = g_list_prepend (builder->infos, info);
 }
 
+/* Convert all offsets to node-relative */
+static void
+fixup_offsets (GtkCssSelectorTree *tree, guint8 *data)
+{
+  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);
+
+      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);
 
-  tree = subdivide_infos (builder->infos, NULL);
+  /* 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
   {