gnome-shell-3.6.3.1/src/st/st-theme.c

No issues found

   1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
   2 /*
   3  * st-theme.c: A set of CSS stylesheets used for rule matching
   4  *
   5  * Copyright 2003-2004 Dodji Seketeli
   6  * Copyright 2008, 2009 Red Hat, Inc.
   7  *
   8  * This program is free software; you can redistribute it and/or modify it
   9  * under the terms and conditions of the GNU Lesser General Public License,
  10  * version 2.1, as published by the Free Software Foundation.
  11  *
  12  * This program is distributed in the hope it will be useful, but WITHOUT ANY
  13  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  14  * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
  15  * more details.
  16  *
  17  * You should have received a copy of the GNU Lesser General Public License
  18  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  19  *
  20  * This file started as a cut-and-paste of cr-sel-eng.c from libcroco.
  21  *
  22  * In moving it to hippo-canvas:
  23  * - Reformatted and otherwise edited to match our coding style
  24  * - Switched from handling xmlNode to handling HippoStyle
  25  * - Simplified by removing things that we don't need or that don't
  26  *   make sense in our context.
  27  * - The code to get a list of matching properties works quite differently;
  28  *   we order things in priority order, but we don't actually try to
  29  *   coalesce properties with the same name.
  30  *
  31  * In moving it to GNOME Shell:
  32  *  - Renamed again to StTheme
  33  *  - Reformatted to match the gnome-shell coding style
  34  *  - Removed notion of "theme engine" from hippo-canvas
  35  *  - pseudo-class matching changed from link enum to strings
  36  *  - Some code simplification
  37  */
  38 
  39 
  40 #include <stdlib.h>
  41 #include <string.h>
  42 
  43 #include <gio/gio.h>
  44 
  45 #include "st-theme-node.h"
  46 #include "st-theme-private.h"
  47 
  48 static GObject *st_theme_constructor (GType                  type,
  49                                       guint                  n_construct_properties,
  50                                       GObjectConstructParam *construct_properties);
  51 
  52 static void st_theme_finalize     (GObject      *object);
  53 static void st_theme_set_property (GObject      *object,
  54                                    guint         prop_id,
  55                                    const GValue *value,
  56                                    GParamSpec   *pspec);
  57 static void st_theme_get_property (GObject      *object,
  58                                    guint         prop_id,
  59                                    GValue       *value,
  60                                    GParamSpec   *pspec);
  61 
  62 struct _StTheme
  63 {
  64   GObject parent;
  65 
  66   char *application_stylesheet;
  67   char *default_stylesheet;
  68   char *theme_stylesheet;
  69   GSList *custom_stylesheets;
  70 
  71   GHashTable *stylesheets_by_filename;
  72   GHashTable *filenames_by_stylesheet;
  73 
  74   CRCascade *cascade;
  75 };
  76 
  77 struct _StThemeClass
  78 {
  79   GObjectClass parent_class;
  80 };
  81 
  82 enum
  83 {
  84   PROP_0,
  85   PROP_APPLICATION_STYLESHEET,
  86   PROP_THEME_STYLESHEET,
  87   PROP_DEFAULT_STYLESHEET
  88 };
  89 
  90 G_DEFINE_TYPE (StTheme, st_theme, G_TYPE_OBJECT)
  91 
  92 /* Quick strcmp.  Test only for == 0 or != 0, not < 0 or > 0.  */
  93 #define strqcmp(str,lit,lit_len) \
  94   (strlen (str) != (lit_len) || memcmp (str, lit, lit_len))
  95 
  96 static void
  97 st_theme_init (StTheme *theme)
  98 {
  99   theme->stylesheets_by_filename = g_hash_table_new_full (g_str_hash, g_str_equal,
 100                                                           (GDestroyNotify)g_free, (GDestroyNotify)cr_stylesheet_unref);
 101   theme->filenames_by_stylesheet = g_hash_table_new (g_direct_hash, g_direct_equal);
 102 }
 103 
 104 static void
 105 st_theme_class_init (StThemeClass *klass)
 106 {
 107   GObjectClass *object_class = G_OBJECT_CLASS (klass);
 108 
 109   object_class->constructor = st_theme_constructor;
 110   object_class->finalize = st_theme_finalize;
 111   object_class->set_property = st_theme_set_property;
 112   object_class->get_property = st_theme_get_property;
 113 
 114   /**
 115    * StTheme:application-stylesheet:
 116    *
 117    * The highest priority stylesheet, representing application-specific
 118    * styling; this is associated with the CSS "author" stylesheet.
 119    */
 120   g_object_class_install_property (object_class,
 121                                    PROP_APPLICATION_STYLESHEET,
 122                                    g_param_spec_string ("application-stylesheet",
 123                                                         "Application Stylesheet",
 124                                                         "Stylesheet with application-specific styling",
 125                                                         NULL,
 126                                                         G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
 127 
 128   /**
 129    * StTheme:theme-stylesheet:
 130    *
 131    * The second priority stylesheet, representing theme-specific styling;
 132    * this is associated with the CSS "user" stylesheet.
 133    */
 134   g_object_class_install_property (object_class,
 135                                    PROP_THEME_STYLESHEET,
 136                                    g_param_spec_string ("theme-stylesheet",
 137                                                         "Theme Stylesheet",
 138                                                         "Stylesheet with theme-specific styling",
 139                                                         NULL,
 140                                                         G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
 141 
 142   /**
 143    * StTheme:default-stylesheet:
 144    *
 145    * The lowest priority stylesheet, representing global default
 146    * styling; this is associated with the CSS "user agent" stylesheet.
 147    */
 148   g_object_class_install_property (object_class,
 149                                    PROP_DEFAULT_STYLESHEET,
 150                                    g_param_spec_string ("default-stylesheet",
 151                                                         "Default Stylesheet",
 152                                                         "Stylesheet with global default styling",
 153                                                         NULL,
 154                                                         G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
 155 
 156 }
 157 
 158 static CRStyleSheet *
 159 parse_stylesheet (const char  *filename,
 160                   GError     **error)
 161 {
 162   enum CRStatus status;
 163   CRStyleSheet *stylesheet;
 164 
 165   if (filename == NULL)
 166     return NULL;
 167 
 168   status = cr_om_parser_simply_parse_file ((const guchar *) filename,
 169                                            CR_UTF_8,
 170                                            &stylesheet);
 171 
 172   if (status != CR_OK)
 173     {
 174       g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
 175                    "Error parsing stylesheet '%s'; errcode:%d", filename, status);
 176       return NULL;
 177     }
 178 
 179   /* Extension stylesheet */
 180   stylesheet->app_data = GUINT_TO_POINTER (FALSE);
 181 
 182   return stylesheet;
 183 }
 184 
 185 CRDeclaration *
 186 _st_theme_parse_declaration_list (const char *str)
 187 {
 188   return cr_declaration_parse_list_from_buf ((const guchar *)str,
 189                                              CR_UTF_8);
 190 }
 191 
 192 /* Just g_warning for now until we have something nicer to do */
 193 static CRStyleSheet *
 194 parse_stylesheet_nofail (const char *filename)
 195 {
 196   GError *error = NULL;
 197   CRStyleSheet *result;
 198 
 199   result = parse_stylesheet (filename, &error);
 200   if (error)
 201     {
 202       g_warning ("%s", error->message);
 203       g_clear_error (&error);
 204     }
 205   return result;
 206 }
 207 
 208 static void
 209 insert_stylesheet (StTheme      *theme,
 210                    const char   *filename,
 211                    CRStyleSheet *stylesheet)
 212 {
 213   char *filename_copy;
 214 
 215   if (stylesheet == NULL)
 216     return;
 217 
 218   filename_copy = g_strdup(filename);
 219   cr_stylesheet_ref (stylesheet);
 220 
 221   g_hash_table_insert (theme->stylesheets_by_filename, filename_copy, stylesheet);
 222   g_hash_table_insert (theme->filenames_by_stylesheet, stylesheet, filename_copy);
 223 }
 224 
 225 gboolean
 226 st_theme_load_stylesheet (StTheme    *theme,
 227                           const char *path,
 228                           GError    **error)
 229 {
 230   CRStyleSheet *stylesheet;
 231 
 232   stylesheet = parse_stylesheet (path, error);
 233   if (!stylesheet)
 234     return FALSE;
 235 
 236   stylesheet->app_data = GUINT_TO_POINTER (TRUE);
 237 
 238   insert_stylesheet (theme, path, stylesheet);
 239   cr_stylesheet_ref (stylesheet);
 240   theme->custom_stylesheets = g_slist_prepend (theme->custom_stylesheets, stylesheet);
 241 
 242   return TRUE;
 243 }
 244 
 245 void
 246 st_theme_unload_stylesheet (StTheme    *theme,
 247                             const char *path)
 248 {
 249   CRStyleSheet *stylesheet;
 250 
 251   stylesheet = g_hash_table_lookup (theme->stylesheets_by_filename, path);
 252   if (!stylesheet)
 253     return;
 254 
 255   if (!g_slist_find (theme->custom_stylesheets, stylesheet))
 256     return;
 257 
 258   theme->custom_stylesheets = g_slist_remove (theme->custom_stylesheets, stylesheet);
 259   g_hash_table_remove (theme->stylesheets_by_filename, path);
 260   g_hash_table_remove (theme->filenames_by_stylesheet, stylesheet);
 261   cr_stylesheet_unref (stylesheet);
 262 }
 263 
 264 /**
 265  * st_theme_get_custom_stylesheets:
 266  * @theme: an #StTheme
 267  *
 268  * Returns: (transfer full) (element-type utf8): the list of stylesheet filenames
 269  *          that were loaded with st_theme_load_stylesheet()
 270  */
 271 GSList*
 272 st_theme_get_custom_stylesheets (StTheme *theme)
 273 {
 274   GSList *result = NULL;
 275   GSList *iter;
 276 
 277   for (iter = theme->custom_stylesheets; iter; iter = iter->next)
 278     {
 279       CRStyleSheet *stylesheet = iter->data;
 280       gchar *filename = g_hash_table_lookup (theme->filenames_by_stylesheet, stylesheet);
 281 
 282       result = g_slist_prepend (result, g_strdup (filename));
 283     }
 284 
 285   return result;
 286 }
 287 
 288 static GObject *
 289 st_theme_constructor (GType                  type,
 290                       guint                  n_construct_properties,
 291                       GObjectConstructParam *construct_properties)
 292 {
 293   GObject *object;
 294   StTheme *theme;
 295   CRStyleSheet *application_stylesheet;
 296   CRStyleSheet *theme_stylesheet;
 297   CRStyleSheet *default_stylesheet;
 298 
 299   object = (*G_OBJECT_CLASS (st_theme_parent_class)->constructor) (type,
 300                                                                       n_construct_properties,
 301                                                                       construct_properties);
 302   theme = ST_THEME (object);
 303 
 304   application_stylesheet = parse_stylesheet_nofail (theme->application_stylesheet);
 305   theme_stylesheet = parse_stylesheet_nofail (theme->theme_stylesheet);
 306   default_stylesheet = parse_stylesheet_nofail (theme->default_stylesheet);
 307 
 308   theme->cascade = cr_cascade_new (application_stylesheet,
 309                                    theme_stylesheet,
 310                                    default_stylesheet);
 311 
 312   if (theme->cascade == NULL)
 313     g_error ("Out of memory when creating cascade object");
 314 
 315   insert_stylesheet (theme, theme->application_stylesheet, application_stylesheet);
 316   insert_stylesheet (theme, theme->theme_stylesheet, theme_stylesheet);
 317   insert_stylesheet (theme, theme->default_stylesheet, default_stylesheet);
 318 
 319   return object;
 320 }
 321 
 322 static void
 323 st_theme_finalize (GObject * object)
 324 {
 325   StTheme *theme = ST_THEME (object);
 326 
 327   g_slist_foreach (theme->custom_stylesheets, (GFunc) cr_stylesheet_unref, NULL);
 328   g_slist_free (theme->custom_stylesheets);
 329   theme->custom_stylesheets = NULL;
 330 
 331   g_hash_table_destroy (theme->stylesheets_by_filename);
 332   g_hash_table_destroy (theme->filenames_by_stylesheet);
 333 
 334   g_free (theme->application_stylesheet);
 335   g_free (theme->theme_stylesheet);
 336   g_free (theme->default_stylesheet);
 337 
 338   if (theme->cascade)
 339     {
 340       cr_cascade_unref (theme->cascade);
 341       theme->cascade = NULL;
 342     }
 343 
 344   G_OBJECT_CLASS (st_theme_parent_class)->finalize (object);
 345 }
 346 
 347 static void
 348 st_theme_set_property (GObject      *object,
 349                        guint         prop_id,
 350                        const GValue *value,
 351                        GParamSpec   *pspec)
 352 {
 353   StTheme *theme = ST_THEME (object);
 354 
 355   switch (prop_id)
 356     {
 357     case PROP_APPLICATION_STYLESHEET:
 358       {
 359         const char *path = g_value_get_string (value);
 360 
 361         if (path != theme->application_stylesheet)
 362           {
 363             g_free (theme->application_stylesheet);
 364             theme->application_stylesheet = g_strdup (path);
 365           }
 366 
 367         break;
 368       }
 369     case PROP_THEME_STYLESHEET:
 370       {
 371         const char *path = g_value_get_string (value);
 372 
 373         if (path != theme->theme_stylesheet)
 374           {
 375             g_free (theme->theme_stylesheet);
 376             theme->theme_stylesheet = g_strdup (path);
 377           }
 378 
 379         break;
 380       }
 381     case PROP_DEFAULT_STYLESHEET:
 382       {
 383         const char *path = g_value_get_string (value);
 384 
 385         if (path != theme->default_stylesheet)
 386           {
 387             g_free (theme->default_stylesheet);
 388             theme->default_stylesheet = g_strdup (path);
 389           }
 390 
 391         break;
 392       }
 393     default:
 394       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 395       break;
 396     }
 397 }
 398 
 399 static void
 400 st_theme_get_property (GObject    *object,
 401                        guint       prop_id,
 402                        GValue     *value,
 403                        GParamSpec *pspec)
 404 {
 405   StTheme *theme = ST_THEME (object);
 406 
 407   switch (prop_id)
 408     {
 409     case PROP_APPLICATION_STYLESHEET:
 410       g_value_set_string (value, theme->application_stylesheet);
 411       break;
 412     case PROP_THEME_STYLESHEET:
 413       g_value_set_string (value, theme->theme_stylesheet);
 414       break;
 415     case PROP_DEFAULT_STYLESHEET:
 416       g_value_set_string (value, theme->default_stylesheet);
 417       break;
 418     default:
 419       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 420       break;
 421     }
 422 }
 423 
 424 /**
 425  * st_theme_new:
 426  * @application_stylesheet: The highest priority stylesheet, representing application-specific
 427  *   styling; this is associated with the CSS "author" stylesheet, may be %NULL
 428  * @theme_stylesheet: The second priority stylesheet, representing theme-specific styling ;
 429  *   this is associated with the CSS "user" stylesheet, may be %NULL
 430  * @default_stylesheet: The lowest priority stylesheet, representing global default styling;
 431  *   this is associated with the CSS "user agent" stylesheet, may be %NULL
 432  *
 433  * Return value: the newly created theme object
 434  **/
 435 StTheme *
 436 st_theme_new (const char       *application_stylesheet,
 437               const char       *theme_stylesheet,
 438               const char       *default_stylesheet)
 439 {
 440   StTheme *theme = g_object_new (ST_TYPE_THEME,
 441                                     "application-stylesheet", application_stylesheet,
 442                                     "theme-stylesheet", theme_stylesheet,
 443                                     "default-stylesheet", default_stylesheet,
 444                                     NULL);
 445 
 446   return theme;
 447 }
 448 
 449 static gboolean
 450 string_in_list (GString    *stryng,
 451                 const char *list)
 452 {
 453   const char *cur;
 454 
 455   for (cur = list; *cur;)
 456     {
 457       while (*cur && cr_utils_is_white_space (*cur))
 458         cur++;
 459 
 460       if (strncmp (cur, stryng->str, stryng->len) == 0)
 461         {
 462           cur +=  stryng->len;
 463           if ((!*cur) || cr_utils_is_white_space (*cur))
 464             return TRUE;
 465         }
 466 
 467       /*  skip to next whitespace character  */
 468       while (*cur && !cr_utils_is_white_space (*cur))
 469         cur++;
 470     }
 471 
 472   return FALSE;
 473 }
 474 
 475 static gboolean
 476 pseudo_class_add_sel_matches_style (StTheme         *a_this,
 477                                     CRAdditionalSel *a_add_sel,
 478                                     StThemeNode     *a_node)
 479 {
 480   const char *node_pseudo_class;
 481 
 482   g_return_val_if_fail (a_this
 483                         && a_add_sel
 484                         && a_add_sel->content.pseudo
 485                         && a_add_sel->content.pseudo->name
 486                         && a_add_sel->content.pseudo->name->stryng
 487                         && a_add_sel->content.pseudo->name->stryng->str
 488                         && a_node, FALSE);
 489 
 490   node_pseudo_class = st_theme_node_get_pseudo_class (a_node);
 491 
 492   if (node_pseudo_class == NULL)
 493     return FALSE;
 494 
 495   return string_in_list (a_add_sel->content.pseudo->name->stryng, node_pseudo_class);
 496 }
 497 
 498 /**
 499  * class_add_sel_matches_style:
 500  * @a_add_sel: The class additional selector to consider.
 501  * @a_node: The style node to consider.
 502  *
 503  * Returns: %TRUE if the class additional selector matches
 504  * the style node given in argument, %FALSE otherwise.
 505  */
 506 static gboolean
 507 class_add_sel_matches_style (CRAdditionalSel *a_add_sel,
 508                              StThemeNode     *a_node)
 509 {
 510   const char *element_class;
 511 
 512   g_return_val_if_fail (a_add_sel
 513                         && a_add_sel->type == CLASS_ADD_SELECTOR
 514                         && a_add_sel->content.class_name
 515                         && a_add_sel->content.class_name->stryng
 516                         && a_add_sel->content.class_name->stryng->str
 517                         && a_node, FALSE);
 518 
 519   element_class = st_theme_node_get_element_class (a_node);
 520   if (element_class == NULL)
 521     return FALSE;
 522 
 523   return string_in_list (a_add_sel->content.class_name->stryng, element_class);
 524 }
 525 
 526 /*
 527  *@return TRUE if the additional attribute selector matches
 528  *the current style node given in argument, FALSE otherwise.
 529  *@param a_add_sel the additional attribute selector to consider.
 530  *@param a_node the style node to consider.
 531  */
 532 static gboolean
 533 id_add_sel_matches_style (CRAdditionalSel *a_add_sel,
 534                           StThemeNode     *a_node)
 535 {
 536   gboolean result = FALSE;
 537   const char *id;
 538 
 539   g_return_val_if_fail (a_add_sel
 540                         && a_add_sel->type == ID_ADD_SELECTOR
 541                         && a_add_sel->content.id_name
 542                         && a_add_sel->content.id_name->stryng
 543                         && a_add_sel->content.id_name->stryng->str
 544                         && a_node, FALSE);
 545   g_return_val_if_fail (a_add_sel
 546                         && a_add_sel->type == ID_ADD_SELECTOR
 547                         && a_node, FALSE);
 548 
 549   id = st_theme_node_get_element_id (a_node);
 550 
 551   if (id != NULL)
 552     {
 553       if (!strqcmp (id, a_add_sel->content.id_name->stryng->str,
 554                     a_add_sel->content.id_name->stryng->len))
 555         {
 556           result = TRUE;
 557         }
 558     }
 559 
 560   return result;
 561 }
 562 
 563 /**
 564  *additional_selector_matches_style:
 565  *Evaluates if a given additional selector matches an style node.
 566  *@param a_add_sel the additional selector to consider.
 567  *@param a_node the style node to consider.
 568  *@return TRUE is a_add_sel matches a_node, FALSE otherwise.
 569  */
 570 static gboolean
 571 additional_selector_matches_style (StTheme         *a_this,
 572                                    CRAdditionalSel *a_add_sel,
 573                                    StThemeNode     *a_node)
 574 {
 575   CRAdditionalSel *cur_add_sel = NULL;
 576 
 577   g_return_val_if_fail (a_add_sel, FALSE);
 578 
 579   for (cur_add_sel = a_add_sel; cur_add_sel; cur_add_sel = cur_add_sel->next)
 580     {
 581       switch (cur_add_sel->type)
 582         {
 583         case NO_ADD_SELECTOR:
 584           return FALSE;
 585         case CLASS_ADD_SELECTOR:
 586           if (!class_add_sel_matches_style (cur_add_sel, a_node))
 587             return FALSE;
 588           break;
 589         case ID_ADD_SELECTOR:
 590           if (!id_add_sel_matches_style (cur_add_sel, a_node))
 591             return FALSE;
 592           break;
 593         case ATTRIBUTE_ADD_SELECTOR:
 594           g_warning ("Attribute selectors not supported");
 595           return FALSE;
 596         case  PSEUDO_CLASS_ADD_SELECTOR:
 597           if (!pseudo_class_add_sel_matches_style (a_this, cur_add_sel, a_node))
 598             return FALSE;
 599           break;
 600         }
 601     }
 602 
 603   return TRUE;
 604 }
 605 
 606 static gboolean
 607 element_name_matches_type (const char *element_name,
 608                            GType       element_type)
 609 {
 610   if (element_type == G_TYPE_NONE)
 611     {
 612       return strcmp (element_name, "stage") == 0;
 613     }
 614   else
 615     {
 616       GType match_type = g_type_from_name (element_name);
 617       if (match_type == G_TYPE_INVALID)
 618         return FALSE;
 619 
 620       return g_type_is_a (element_type, match_type);
 621     }
 622 }
 623 
 624 /*
 625  *Evaluate a selector (a simple selectors list) and says
 626  *if it matches the style node given in parameter.
 627  *The algorithm used here is the following:
 628  *Walk the combinator separated list of simple selectors backward, starting
 629  *from the end of the list. For each simple selector, looks if
 630  *if matches the current style.
 631  *
 632  *@param a_this the selection engine.
 633  *@param a_sel the simple selection list.
 634  *@param a_node the style node.
 635  *@param a_result out parameter. Set to true if the
 636  *selector matches the style node, FALSE otherwise.
 637  *@param a_recurse if set to TRUE, the function will walk to
 638  *the next simple selector (after the evaluation of the current one)
 639  *and recursively evaluate it. Must be usually set to TRUE unless you
 640  *know what you are doing.
 641  */
 642 static enum CRStatus
 643 sel_matches_style_real (StTheme     *a_this,
 644                         CRSimpleSel *a_sel,
 645                         StThemeNode *a_node,
 646                         gboolean    *a_result,
 647                         gboolean     a_eval_sel_list_from_end,
 648                         gboolean     a_recurse)
 649 {
 650   CRSimpleSel *cur_sel = NULL;
 651   StThemeNode *cur_node = NULL;
 652   GType cur_type;
 653 
 654   *a_result = FALSE;
 655 
 656   if (a_eval_sel_list_from_end)
 657     {
 658       /*go and get the last simple selector of the list */
 659       for (cur_sel = a_sel; cur_sel && cur_sel->next; cur_sel = cur_sel->next)
 660         ;
 661     }
 662   else
 663     {
 664       cur_sel = a_sel;
 665     }
 666 
 667   cur_node = a_node;
 668   cur_type = st_theme_node_get_element_type (cur_node);
 669 
 670   while (cur_sel)
 671     {
 672       if (((cur_sel->type_mask & TYPE_SELECTOR)
 673            && (cur_sel->name
 674                && cur_sel->name->stryng
 675                && cur_sel->name->stryng->str)
 676            &&
 677            (element_name_matches_type (cur_sel->name->stryng->str, cur_type)))
 678           || (cur_sel->type_mask & UNIVERSAL_SELECTOR))
 679         {
 680           /*
 681            *this simple selector
 682            *matches the current style node
 683            *Let's see if the preceding
 684            *simple selectors also match
 685            *their style node counterpart.
 686            */
 687           if (cur_sel->add_sel)
 688             {
 689               if (additional_selector_matches_style (a_this, cur_sel->add_sel, cur_node))
 690                 goto walk_a_step_in_expr;
 691               else
 692                 goto done;
 693             }
 694           else
 695             goto walk_a_step_in_expr;
 696         }
 697       if (!(cur_sel->type_mask & TYPE_SELECTOR)
 698           && !(cur_sel->type_mask & UNIVERSAL_SELECTOR))
 699         {
 700           if (!cur_sel->add_sel)
 701             goto done;
 702           if (additional_selector_matches_style (a_this, cur_sel->add_sel, cur_node))
 703             goto walk_a_step_in_expr;
 704           else
 705             goto done;
 706         }
 707       else
 708         {
 709           goto done;
 710         }
 711 
 712     walk_a_step_in_expr:
 713       if (a_recurse == FALSE)
 714         {
 715           *a_result = TRUE;
 716           goto done;
 717         }
 718 
 719       /*
 720        *here, depending on the combinator of cur_sel
 721        *choose the axis of the element tree traversal
 722        *and walk one step in the element tree.
 723        */
 724       if (!cur_sel->prev)
 725         break;
 726 
 727       switch (cur_sel->combinator)
 728         {
 729         case NO_COMBINATOR:
 730           break;
 731 
 732         case COMB_WS:           /*descendant selector */
 733           {
 734             StThemeNode *n = NULL;
 735 
 736             /*
 737              *walk the element tree upward looking for a parent
 738              *style that matches the preceding selector.
 739              */
 740             for (n = st_theme_node_get_parent (a_node); n; n = st_theme_node_get_parent (n))
 741               {
 742                 enum CRStatus status;
 743                 gboolean matches = FALSE;
 744 
 745                 status = sel_matches_style_real (a_this, cur_sel->prev, n, &matches, FALSE, TRUE);
 746 
 747                 if (status != CR_OK)
 748                   goto done;
 749 
 750                 if (matches)
 751                   {
 752                     cur_node = n;
 753                     cur_type = st_theme_node_get_element_type (cur_node);
 754                     break;
 755                   }
 756               }
 757 
 758             if (!n)
 759               {
 760                 /*
 761                  *didn't find any ancestor that matches
 762                  *the previous simple selector.
 763                  */
 764                 goto done;
 765               }
 766             /*
 767              *in this case, the preceding simple sel
 768              *will have been interpreted twice, which
 769              *is a cpu and mem waste ... I need to find
 770              *another way to do this. Anyway, this is
 771              *my first attempt to write this function and
 772              *I am a bit clueless.
 773              */
 774             break;
 775           }
 776 
 777         case COMB_PLUS:
 778           g_warning ("+ combinators are not supported");
 779           goto done;
 780 
 781         case COMB_GT:
 782           cur_node = st_theme_node_get_parent (cur_node);
 783           if (!cur_node)
 784             goto done;
 785           cur_type = st_theme_node_get_element_type (cur_node);
 786           break;
 787 
 788         default:
 789           goto done;
 790         }
 791 
 792       cur_sel = cur_sel->prev;
 793     }
 794 
 795   /*
 796    *if we reached this point, it means the selector matches
 797    *the style node.
 798    */
 799   *a_result = TRUE;
 800 
 801 done:
 802   return CR_OK;
 803 }
 804 
 805 static void
 806 add_matched_properties (StTheme      *a_this,
 807                         CRStyleSheet *a_nodesheet,
 808                         StThemeNode  *a_node,
 809                         GPtrArray    *props)
 810 {
 811   CRStatement *cur_stmt = NULL;
 812   CRSelector *sel_list = NULL;
 813   CRSelector *cur_sel = NULL;
 814   gboolean matches = FALSE;
 815   enum CRStatus status = CR_OK;
 816 
 817   /*
 818    *walk through the list of statements and,
 819    *get the selectors list inside the statements that
 820    *contain some, and try to match our style node in these
 821    *selectors lists.
 822    */
 823   for (cur_stmt = a_nodesheet->statements; cur_stmt; cur_stmt = cur_stmt->next)
 824     {
 825       /*
 826        *initialyze the selector list in which we will
 827        *really perform the search.
 828        */
 829       sel_list = NULL;
 830 
 831       /*
 832        *get the the damn selector list in
 833        *which we have to look
 834        */
 835       switch (cur_stmt->type)
 836         {
 837         case RULESET_STMT:
 838           if (cur_stmt->kind.ruleset && cur_stmt->kind.ruleset->sel_list)
 839             {
 840               sel_list = cur_stmt->kind.ruleset->sel_list;
 841             }
 842           break;
 843 
 844         case AT_MEDIA_RULE_STMT:
 845           if (cur_stmt->kind.media_rule
 846               && cur_stmt->kind.media_rule->rulesets
 847               && cur_stmt->kind.media_rule->rulesets->kind.ruleset
 848               && cur_stmt->kind.media_rule->rulesets->kind.ruleset->sel_list)
 849             {
 850               sel_list = cur_stmt->kind.media_rule->rulesets->kind.ruleset->sel_list;
 851             }
 852           break;
 853 
 854         case AT_IMPORT_RULE_STMT:
 855           {
 856             CRAtImportRule *import_rule = cur_stmt->kind.import_rule;
 857 
 858             if (import_rule->sheet == NULL)
 859               {
 860                 char *filename = NULL;
 861 
 862                 if (import_rule->url->stryng && import_rule->url->stryng->str)
 863                   filename = _st_theme_resolve_url (a_this,
 864                                                     a_nodesheet,
 865                                                     import_rule->url->stryng->str);
 866 
 867                 if (filename)
 868                   import_rule->sheet = parse_stylesheet (filename, NULL);
 869 
 870                 if (import_rule->sheet)
 871                   {
 872                     insert_stylesheet (a_this, filename, import_rule->sheet);
 873                     /* refcount of stylesheets starts off at zero, so we don't need to unref! */
 874                   }
 875                 else
 876                   {
 877                     /* Set a marker to avoid repeatedly trying to parse a non-existent or
 878                      * broken stylesheet
 879                      */
 880                     import_rule->sheet = (CRStyleSheet *) - 1;
 881                   }
 882 
 883                 if (filename)
 884                   g_free (filename);
 885               }
 886 
 887             if (import_rule->sheet != (CRStyleSheet *) - 1)
 888               {
 889                 add_matched_properties (a_this, import_rule->sheet,
 890                                         a_node, props);
 891               }
 892           }
 893           break;
 894         default:
 895           break;
 896         }
 897 
 898       if (!sel_list)
 899         continue;
 900 
 901       /*
 902        *now, we have a comma separated selector list to look in.
 903        *let's walk it and try to match the style node
 904        *on each item of the list.
 905        */
 906       for (cur_sel = sel_list; cur_sel; cur_sel = cur_sel->next)
 907         {
 908           if (!cur_sel->simple_sel)
 909             continue;
 910 
 911           status = sel_matches_style_real (a_this, cur_sel->simple_sel, a_node, &matches, TRUE, TRUE);
 912 
 913           if (status == CR_OK && matches)
 914             {
 915               CRDeclaration *cur_decl = NULL;
 916 
 917               /* In order to sort the matching properties, we need to compute the
 918                * specificity of the selector that actually matched this
 919                * element. In a non-thread-safe fashion, we store it in the
 920                * ruleset. (Fixing this would mean cut-and-pasting
 921                * cr_simple_sel_compute_specificity(), and have no need for
 922                * thread-safety anyways.)
 923                *
 924                * Once we've sorted the properties, the specificity no longer
 925                * matters and it can be safely overriden.
 926                */
 927               cr_simple_sel_compute_specificity (cur_sel->simple_sel);
 928 
 929               cur_stmt->specificity = cur_sel->simple_sel->specificity;
 930 
 931               for (cur_decl = cur_stmt->kind.ruleset->decl_list; cur_decl; cur_decl = cur_decl->next)
 932                 g_ptr_array_add (props, cur_decl);
 933             }
 934         }
 935     }
 936 }
 937 
 938 #define ORIGIN_OFFSET_IMPORTANT (NB_ORIGINS)
 939 #define ORIGIN_OFFSET_EXTENSION (NB_ORIGINS * 2)
 940 
 941 static inline int
 942 get_origin (const CRDeclaration * decl)
 943 {
 944   enum CRStyleOrigin origin = decl->parent_statement->parent_sheet->origin;
 945   gboolean is_extension_sheet = GPOINTER_TO_UINT (decl->parent_statement->parent_sheet->app_data);
 946 
 947   if (decl->important)
 948     origin += ORIGIN_OFFSET_IMPORTANT;
 949 
 950   if (is_extension_sheet)
 951     origin += ORIGIN_OFFSET_EXTENSION;
 952 
 953   return origin;
 954 }
 955 
 956 /* Order of comparison is so that higher priority statements compare after
 957  * lower priority statements */
 958 static int
 959 compare_declarations (gconstpointer a,
 960                       gconstpointer b)
 961 {
 962   /* g_ptr_array_sort() is broooken */
 963   CRDeclaration *decl_a = *(CRDeclaration **) a;
 964   CRDeclaration *decl_b = *(CRDeclaration **) b;
 965 
 966   int origin_a = get_origin (decl_a);
 967   int origin_b = get_origin (decl_b);
 968 
 969   if (origin_a != origin_b)
 970     return origin_a - origin_b;
 971 
 972   if (decl_a->parent_statement->specificity != decl_b->parent_statement->specificity)
 973     return decl_a->parent_statement->specificity - decl_b->parent_statement->specificity;
 974 
 975   return 0;
 976 }
 977 
 978 GPtrArray *
 979 _st_theme_get_matched_properties (StTheme        *theme,
 980                                   StThemeNode    *node)
 981 {
 982   enum CRStyleOrigin origin = 0;
 983   CRStyleSheet *sheet = NULL;
 984   GPtrArray *props = g_ptr_array_new ();
 985   GSList *iter;
 986 
 987   g_return_val_if_fail (ST_IS_THEME (theme), NULL);
 988   g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL);
 989 
 990   for (origin = ORIGIN_UA; origin < NB_ORIGINS; origin++)
 991     {
 992       sheet = cr_cascade_get_sheet (theme->cascade, origin);
 993       if (!sheet)
 994         continue;
 995 
 996       add_matched_properties (theme, sheet, node, props);
 997     }
 998 
 999   for (iter = theme->custom_stylesheets; iter; iter = iter->next)
1000     add_matched_properties (theme, iter->data, node, props);
1001 
1002   /* We count on a stable sort here so that later declarations come
1003    * after earlier declarations */
1004   g_ptr_array_sort (props, compare_declarations);
1005 
1006   return props;
1007 }
1008 
1009 /* Resolve an url from an url() reference in a stylesheet into an absolute
1010  * local filename, if possible. The resolution here is distinctly lame and
1011  * will fail on many examples.
1012  */
1013 char *
1014 _st_theme_resolve_url (StTheme      *theme,
1015                        CRStyleSheet *base_stylesheet,
1016                        const char   *url)
1017 {
1018   const char *base_filename = NULL;
1019   char *dirname;
1020   char *filename;
1021 
1022   /* Handle absolute file:/ URLs */
1023   if (g_str_has_prefix (url, "file:") ||
1024       g_str_has_prefix (url, "File:") ||
1025       g_str_has_prefix (url, "FILE:"))
1026     {
1027       GError *error = NULL;
1028       char *filename;
1029 
1030       filename = g_filename_from_uri (url, NULL, &error);
1031       if (filename == NULL)
1032         {
1033           g_warning ("%s", error->message);
1034           g_error_free (error);
1035         }
1036 
1037       return NULL;
1038     }
1039 
1040   /* Guard against http:/ URLs */
1041 
1042   if (g_str_has_prefix (url, "http:") ||
1043       g_str_has_prefix (url, "Http:") ||
1044       g_str_has_prefix (url, "HTTP:"))
1045     {
1046       g_warning ("Http URL '%s' in theme stylesheet is not supported", url);
1047       return NULL;
1048     }
1049 
1050   /* Assume anything else is a relative URL, and "resolve" it
1051    */
1052   if (url[0] == '/')
1053     return g_strdup (url);
1054 
1055   base_filename = g_hash_table_lookup (theme->filenames_by_stylesheet, base_stylesheet);
1056 
1057   if (base_filename == NULL)
1058     {
1059       g_warning ("Can't get base to resolve url '%s'", url);
1060       return NULL;
1061     }
1062 
1063   dirname = g_path_get_dirname (base_filename);
1064   filename = g_build_filename (dirname, url, NULL);
1065   g_free (dirname);
1066 
1067   return filename;
1068 }