/peek-build/src/netdepends/libcss/src/select/libcssselect.c
C | 1392 lines | 947 code | 194 blank | 251 comment | 420 complexity | 904cf8ddf95ee5f502e61e9f2711ce1a MD5 | raw file
1/* 2 * This file is part of LibCSS 3 * Licensed under the MIT License, 4 * http://www.opensource.org/licenses/mit-license.php 5 * Copyright 2009 John-Mark Bell <jmb@netsurf-browser.org> 6 */ 7 8#include <assert.h> 9#include <string.h> 10 11#include <libcss/select.h> 12 13#include "bytecode/bytecode.h" 14#include "bytecode/opcodes.h" 15#include "stylesheet.h" 16#include "select/computed.h" 17#include "select/dispatch.h" 18#include "select/hash.h" 19#include "select/propset.h" 20#include "select/select.h" 21#include "utils/parserutilserror.h" 22#include "utils/utils.h" 23 24/* Define this to enable verbose messages when matching selector chains */ 25#undef DEBUG_CHAIN_MATCHING 26 27/** 28 * Container for stylesheet selection info 29 */ 30typedef struct css_select_sheet { 31 const css_stylesheet *sheet; /**< Stylesheet */ 32 css_origin origin; /**< Stylesheet origin */ 33 uint64_t media; /**< Applicable media */ 34} css_select_sheet; 35 36/** 37 * CSS selection context 38 */ 39struct css_select_ctx { 40 uint32_t n_sheets; /**< Number of sheets */ 41 42 css_select_sheet *sheets; /**< Array of sheets */ 43 44 css_allocator_fn alloc; /**< Allocation routine */ 45 void *pw; /**< Client-specific private data */ 46}; 47 48static css_error set_hint(css_select_state *state, uint32_t i); 49static css_error set_initial(css_select_state *state, uint32_t i, void *parent); 50 51static css_error select_from_sheet(css_select_ctx *ctx, 52 const css_stylesheet *sheet, css_origin origin, 53 css_select_state *state); 54static css_error intern_strings_for_sheet(css_select_ctx *ctx, 55 const css_stylesheet *sheet, css_select_state *state); 56static css_error match_selectors_in_sheet(css_select_ctx *ctx, 57 const css_stylesheet *sheet, css_select_state *state); 58static css_error match_selector_chain(css_select_ctx *ctx, 59 const css_selector *selector, css_select_state *state); 60static css_error match_named_combinator(css_select_ctx *ctx, 61 css_combinator type, const css_selector *selector, 62 css_select_state *state, void *node, void **next_node); 63static css_error match_universal_combinator(css_select_ctx *ctx, 64 css_combinator type, const css_selector *selector, 65 css_select_state *state, void *node, void **next_node); 66static css_error match_details(css_select_ctx *ctx, void *node, 67 const css_selector_detail *detail, css_select_state *state, 68 bool *match); 69static css_error match_detail(css_select_ctx *ctx, void *node, 70 const css_selector_detail *detail, css_select_state *state, 71 bool *match); 72static css_error cascade_style(const css_style *style, css_select_state *state); 73 74#ifdef DEBUG_CHAIN_MATCHING 75static void dump_chain(const css_selector *selector); 76#endif 77 78 79/** 80 * Create a selection context 81 * 82 * \param alloc Memory (de)allocation function 83 * \param pw Client-specific private data 84 * \param result Pointer to location to receive created context 85 * \return CSS_OK on success, appropriate error otherwise. 86 */ 87css_error css_select_ctx_create(css_allocator_fn alloc, void *pw, 88 css_select_ctx **result) 89{ 90 css_select_ctx *c; 91 92 if (alloc == NULL || result == NULL) 93 return CSS_BADPARM; 94 95 c = alloc(NULL, sizeof(css_select_ctx), pw); 96 if (c == NULL) 97 return CSS_NOMEM; 98 99 c->n_sheets = 0; 100 c->sheets = NULL; 101 102 c->alloc = alloc; 103 c->pw = pw; 104 105 *result = c; 106 107 return CSS_OK; 108} 109 110/** 111 * Destroy a selection context 112 * 113 * \param ctx The context to destroy 114 * \return CSS_OK on success, appropriate error otherwise 115 */ 116css_error css_select_ctx_destroy(css_select_ctx *ctx) 117{ 118 if (ctx == NULL) 119 return CSS_BADPARM; 120 121 if (ctx->sheets != NULL) 122 ctx->alloc(ctx->sheets, 0, ctx->pw); 123 124 ctx->alloc(ctx, 0, ctx->pw); 125 126 return CSS_OK; 127} 128 129/** 130 * Append a stylesheet to a selection context 131 * 132 * \param ctx The context to append to 133 * \param sheet The sheet to append 134 * \param origin Origin of the sheet 135 * \param media Media types to which the sheet applies 136 * \return CSS_OK on success, appropriate error otherwise 137 */ 138css_error css_select_ctx_append_sheet(css_select_ctx *ctx, 139 const css_stylesheet *sheet, css_origin origin, 140 uint64_t media) 141{ 142 if (ctx == NULL || sheet == NULL) 143 return CSS_BADPARM; 144 145 return css_select_ctx_insert_sheet(ctx, sheet, ctx->n_sheets, 146 origin, media); 147} 148 149/** 150 * Insert a stylesheet into a selection context 151 * 152 * \param ctx The context to insert into 153 * \param sheet Sheet to insert 154 * \param index Index in context to insert sheet 155 * \param origin Origin of the sheet 156 * \param media Media types to which the sheet applies 157 * \return CSS_OK on success, appropriate error otherwise 158 */ 159css_error css_select_ctx_insert_sheet(css_select_ctx *ctx, 160 const css_stylesheet *sheet, uint32_t index, 161 css_origin origin, uint64_t media) 162{ 163 css_select_sheet *temp; 164 165 if (ctx == NULL || sheet == NULL) 166 return CSS_BADPARM; 167 168 /* Inline styles cannot be inserted into a selection context */ 169 if (sheet->inline_style) 170 return CSS_INVALID; 171 172 /* Index must be in the range [0, n_sheets] 173 * The latter being equivalent to append */ 174 if (index > ctx->n_sheets) 175 return CSS_INVALID; 176 177 temp = ctx->alloc(ctx->sheets, 178 (ctx->n_sheets + 1) * sizeof(css_select_sheet), 179 ctx->pw); 180 if (temp == NULL) 181 return CSS_NOMEM; 182 183 ctx->sheets = temp; 184 185 if (index < ctx->n_sheets) { 186 memmove(&ctx->sheets[index + 1], &ctx->sheets[index], 187 (ctx->n_sheets - index) * sizeof(css_select_sheet)); 188 } 189 190 ctx->sheets[index].sheet = sheet; 191 ctx->sheets[index].origin = origin; 192 ctx->sheets[index].media = media; 193 194 ctx->n_sheets++; 195 196 return CSS_OK; 197} 198 199/** 200 * Remove a sheet from a selection context 201 * 202 * \param ctx The context to remove from 203 * \param sheet Sheet to remove 204 * \return CSS_OK on success, appropriate error otherwise 205 */ 206css_error css_select_ctx_remove_sheet(css_select_ctx *ctx, 207 const css_stylesheet *sheet) 208{ 209 uint32_t index; 210 211 if (ctx == NULL || sheet == NULL) 212 return CSS_BADPARM; 213 214 for (index = 0; index < ctx->n_sheets; index++) { 215 if (ctx->sheets[index].sheet == sheet) 216 break; 217 } 218 219 if (index == ctx->n_sheets) 220 return CSS_INVALID; 221 222 memmove(&ctx->sheets[index], &ctx->sheets[index + 1], 223 (ctx->n_sheets - index) * sizeof(css_select_sheet)); 224 225 ctx->n_sheets--; 226 227 return CSS_OK; 228 229} 230 231/** 232 * Count the number of top-level sheets in a selection context 233 * 234 * \param ctx Context to consider 235 * \param count Pointer to location to receive count of sheets 236 * \return CSS_OK on success, appropriate error otherwise 237 */ 238css_error css_select_ctx_count_sheets(css_select_ctx *ctx, uint32_t *count) 239{ 240 if (ctx == NULL || count == NULL) 241 return CSS_BADPARM; 242 243 *count = ctx->n_sheets; 244 245 return CSS_OK; 246} 247 248/** 249 * Retrieve a sheet from a selection context 250 * 251 * \param ctx Context to look in 252 * \param index Index in context to look 253 * \param sheet Pointer to location to receive sheet 254 * \return CSS_OK on success, appropriate error otherwise 255 */ 256css_error css_select_ctx_get_sheet(css_select_ctx *ctx, uint32_t index, 257 const css_stylesheet **sheet) 258{ 259 if (ctx == NULL || sheet == NULL) 260 return CSS_BADPARM; 261 262 if (index > ctx->n_sheets) 263 return CSS_INVALID; 264 265 *sheet = ctx->sheets[index].sheet; 266 267 return CSS_OK; 268} 269 270/** 271 * Select a style for the given node 272 * 273 * \param ctx Selection context to use 274 * \param node Node to select style for 275 * \param pseudo_element Pseudo element to select for, instead 276 * \param media Currently active media types 277 * \param inline_style Corresponding inline style for node, or NULL 278 * \param result Pointer to style to populate (assumed clean) 279 * \param handler Dispatch table of handler functions 280 * \param pw Client-specific private data for handler functions 281 * \return CSS_OK on success, appropriate error otherwise. 282 * 283 * In computing the style, no reference is made to the parent node's 284 * style. Therefore, the resultant computed style is not ready for 285 * immediate use, as some properties may be marked as inherited. 286 * Use css_computed_style_compose() to obtain a fully computed style. 287 * 288 * This two-step approach to style computation is designed to allow 289 * the client to store the partially computed style and efficiently 290 * update the fully computed style for a node when layout changes. 291 */ 292css_error css_select_style(css_select_ctx *ctx, void *node, 293 uint32_t pseudo_element, uint64_t media, 294 const css_stylesheet *inline_style, 295 css_computed_style *result, 296 css_select_handler *handler, void *pw) 297{ 298 uint32_t i; 299 css_error error; 300 css_select_state state; 301 void *parent = NULL; 302 303 if (ctx == NULL || node == NULL || result == NULL || handler == NULL) 304 return CSS_BADPARM; 305 306 /* Set up the selection state */ 307 memset(&state, 0, sizeof(css_select_state)); 308 state.node = node; 309 state.pseudo_element = pseudo_element; 310 state.media = media; 311 state.result = result; 312 state.handler = handler; 313 state.pw = pw; 314 315 error = handler->parent_node(pw, node, &parent); 316 if (error) 317 return error; 318 319 /* Iterate through the top-level stylesheets, selecting styles 320 * from those which apply to our current media requirements and 321 * are not disabled */ 322 for (i = 0; i < ctx->n_sheets; i++) { 323 if ((ctx->sheets[i].media & media) != 0 && 324 ctx->sheets[i].sheet->disabled == false) { 325 error = select_from_sheet(ctx, ctx->sheets[i].sheet, 326 ctx->sheets[i].origin, &state); 327 if (error != CSS_OK) 328 goto cleanup; 329 } 330 } 331 332 /* Consider any inline style for the node */ 333 if (inline_style != NULL) { 334 css_rule_selector *sel = 335 (css_rule_selector *) inline_style->rule_list; 336 337 /* Sanity check style */ 338 if (inline_style->rule_count != 1 || 339 inline_style->rule_list->type != CSS_RULE_SELECTOR || 340 inline_style->rule_list->items != 0) { 341 error = CSS_INVALID; 342 goto cleanup; 343 } 344 345 /* No bytecode if input was empty or wholly invalid */ 346 if (sel->style != NULL) { 347 error = cascade_style(sel->style, &state); 348 if (error != CSS_OK) 349 goto cleanup; 350 } 351 } 352 353 /* Take account of presentational hints and fix up any remaining 354 * unset properties. */ 355 for (i = 0; i < CSS_N_PROPERTIES; i++) { 356 /* If the existing property value came from an author 357 * stylesheet or a user sheet using !important, then leave 358 * it alone. */ 359 if (state.props[i].set == false || 360 (state.props[i].origin != CSS_ORIGIN_AUTHOR && 361 state.props[i].important == false)) { 362 error = set_hint(&state, i); 363 if (error != CSS_OK) 364 goto cleanup; 365 } 366 367 /* If the property is still unset or it's set to inherit and 368 * we're the root element, then set it to its initial value. */ 369 if (state.props[i].set == false || (parent == NULL && 370 state.props[i].inherit == true)) { 371 error = set_initial(&state, i, parent); 372 if (error != CSS_OK) 373 goto cleanup; 374 } 375 } 376 377 /* If this is the root element, then we must ensure that all 378 * length values are absolute, display and float are correctly 379 * computed, and the default border-{top,right,bottom,left}-color 380 * is set to the computed value of color. */ 381 if (parent == NULL) { 382 error = compute_absolute_values(NULL, result, 383 handler->compute_font_size, pw); 384 if (error != CSS_OK) 385 goto cleanup; 386 } 387 388 error = CSS_OK; 389cleanup: 390 if (ctx->n_sheets > 0 && ctx->sheets[0].sheet != NULL) { 391 if (state.universal != NULL) 392 lwc_string_unref(state.universal); 393 if (state.first_child != NULL) 394 lwc_string_unref(state.first_child); 395 if (state.link != NULL) 396 lwc_string_unref(state.link); 397 if (state.visited != NULL) 398 lwc_string_unref(state.visited); 399 if (state.hover != NULL) 400 lwc_string_unref(state.hover); 401 if (state.active != NULL) 402 lwc_string_unref(state.active); 403 if (state.focus != NULL) 404 lwc_string_unref(state.focus); 405 if (state.first_line != NULL) 406 lwc_string_unref(state.first_line); 407 if (state.first_letter != NULL) 408 lwc_string_unref(state.first_letter); 409 if (state.before != NULL) 410 lwc_string_unref(state.before); 411 if (state.after != NULL) 412 lwc_string_unref(state.after); 413 } 414 return error; 415} 416 417/****************************************************************************** 418 * Selection engine internals below here * 419 ******************************************************************************/ 420 421css_error set_hint(css_select_state *state, uint32_t i) 422{ 423 css_hint hint; 424 css_error error; 425 426 /* Initialise hint */ 427 memset(&hint, 0, sizeof(css_hint)); 428 429 /* Retrieve this property's hint from the client */ 430 error = state->handler->node_presentational_hint(state->pw, 431 state->node, i, &hint); 432 if (error != CSS_OK) 433 return (error == CSS_PROPERTY_NOT_SET) ? CSS_OK : error; 434 435 /* Hint defined -- set it in the result */ 436 error = prop_dispatch[i].set_from_hint(&hint, state->result); 437 if (error != CSS_OK) 438 return error; 439 440 /* Keep selection state in sync with reality */ 441 state->props[i].set = 1; 442 state->props[i].specificity = 0; 443 state->props[i].origin = CSS_ORIGIN_AUTHOR; 444 state->props[i].important = 0; 445 state->props[i].inherit = (hint.status == 0); 446 447 return CSS_OK; 448} 449 450css_error set_initial(css_select_state *state, uint32_t i, void *parent) 451{ 452 css_error error; 453 454 /* Do nothing if this property is inherited (the default state 455 * of a clean computed style is for everything to be set to inherit) 456 * 457 * If the node is tree root, everything should be defaulted. 458 */ 459 if (prop_dispatch[i].inherited == false || parent == NULL) { 460 /* Remaining properties are neither inherited nor 461 * already set. Thus, we set them to their initial 462 * values here. Except, however, if the property in 463 * question resides in one of the extension blocks and 464 * the extension block has yet to be allocated. In that 465 * case, we do nothing and leave it to the property 466 * accessors to return the initial values for the 467 * property. 468 */ 469 if (prop_dispatch[i].group == GROUP_NORMAL) { 470 error = prop_dispatch[i].initial(state); 471 if (error != CSS_OK) 472 return error; 473 } else if (prop_dispatch[i].group == GROUP_UNCOMMON && 474 state->result->uncommon != NULL) { 475 error = prop_dispatch[i].initial(state); 476 if (error != CSS_OK) 477 return error; 478 } else if (prop_dispatch[i].group == GROUP_PAGE && 479 state->result->page != NULL) { 480 error = prop_dispatch[i].initial(state); 481 if (error != CSS_OK) 482 return error; 483 } else if (prop_dispatch[i].group == GROUP_AURAL && 484 state->result->aural != NULL) { 485 error = prop_dispatch[i].initial(state); 486 if (error != CSS_OK) 487 return error; 488 } 489 } 490 491 return CSS_OK; 492} 493 494css_error select_from_sheet(css_select_ctx *ctx, const css_stylesheet *sheet, 495 css_origin origin, css_select_state *state) 496{ 497 const css_stylesheet *s = sheet; 498 const css_rule *rule = s->rule_list; 499 uint32_t sp = 0; 500#define IMPORT_STACK_SIZE 256 501 const css_rule *import_stack[IMPORT_STACK_SIZE]; 502 503 do { 504 /* Find first non-charset rule, if we're at the list head */ 505 if (rule == s->rule_list) { 506 for (; rule != NULL; rule = rule->next) { 507 if (rule->type != CSS_RULE_CHARSET) 508 break; 509 } 510 } 511 512 if (rule != NULL && rule->type == CSS_RULE_IMPORT) { 513 /* Current rule is an import */ 514 const css_rule_import *import = 515 (const css_rule_import *) rule; 516 517 if (import->sheet != NULL && 518 (import->media & state->media) != 0) { 519 /* It's applicable, so process it */ 520 assert(sp < IMPORT_STACK_SIZE - 1); 521 522 import_stack[sp++] = rule; 523 524 s = import->sheet; 525 rule = s->rule_list; 526 } else { 527 /* Not applicable; skip over it */ 528 rule = rule->next; 529 } 530 } else { 531 /* Gone past import rules in this sheet */ 532 css_error error; 533 534 /* Process this sheet */ 535 state->sheet = s; 536 state->current_origin = origin; 537 538 error = intern_strings_for_sheet(ctx, s, state); 539 if (error != CSS_OK) 540 return error; 541 542 error = match_selectors_in_sheet(ctx, s, state); 543 if (error != CSS_OK) 544 return error; 545 546 /* Find next sheet to process */ 547 if (sp > 0) { 548 sp--; 549 rule = import_stack[sp]->next; 550 s = import_stack[sp]->parent; 551 } else { 552 s = NULL; 553 } 554 } 555 } while (s != NULL); 556 557 return CSS_OK; 558} 559 560css_error intern_strings_for_sheet(css_select_ctx *ctx, 561 const css_stylesheet *sheet, css_select_state *state) 562{ 563 lwc_error error; 564 565 UNUSED(ctx); 566 UNUSED(sheet); 567 568 /* Universal selector */ 569 if (state->universal != NULL) 570 return CSS_OK; 571 572 error = lwc_intern_string("*", SLEN("*"), &state->universal); 573 if (error != lwc_error_ok) 574 return css_error_from_lwc_error(error); 575 576 /* Pseudo classes */ 577 error = lwc_intern_string( 578 "first-child", SLEN("first-child"), 579 &state->first_child); 580 if (error != lwc_error_ok) 581 return css_error_from_lwc_error(error); 582 583 error = lwc_intern_string( 584 "link", SLEN("link"), 585 &state->link); 586 if (error != lwc_error_ok) 587 return css_error_from_lwc_error(error); 588 589 error = lwc_intern_string( 590 "visited", SLEN("visited"), 591 &state->visited); 592 if (error != lwc_error_ok) 593 return css_error_from_lwc_error(error); 594 595 error = lwc_intern_string( 596 "hover", SLEN("hover"), 597 &state->hover); 598 if (error != lwc_error_ok) 599 return css_error_from_lwc_error(error); 600 601 error = lwc_intern_string( 602 "active", SLEN("active"), 603 &state->active); 604 if (error != lwc_error_ok) 605 return css_error_from_lwc_error(error); 606 607 error = lwc_intern_string( 608 "focus", SLEN("focus"), 609 &state->focus); 610 if (error != lwc_error_ok) 611 return css_error_from_lwc_error(error); 612 613 /* Pseudo elements */ 614 error = lwc_intern_string( 615 "first-line", SLEN("first-line"), 616 &state->first_line); 617 if (error != lwc_error_ok) 618 return css_error_from_lwc_error(error); 619 620 error = lwc_intern_string( 621 "first_letter", SLEN("first-letter"), 622 &state->first_letter); 623 if (error != lwc_error_ok) 624 return css_error_from_lwc_error(error); 625 626 error = lwc_intern_string( 627 "before", SLEN("before"), 628 &state->before); 629 if (error != lwc_error_ok) 630 return css_error_from_lwc_error(error); 631 632 error = lwc_intern_string( 633 "after", SLEN("after"), 634 &state->after); 635 if (error != lwc_error_ok) 636 return css_error_from_lwc_error(error); 637 638 return CSS_OK; 639} 640 641static bool _selectors_pending(const css_selector **node, 642 const css_selector **id, const css_selector ***classes, 643 uint32_t n_classes, const css_selector **univ) 644{ 645 bool pending = false; 646 uint32_t i; 647 648 pending |= *node != NULL; 649 pending |= *id != NULL; 650 pending |= *univ != NULL; 651 652 if (classes != NULL && n_classes > 0) { 653 for (i = 0; i < n_classes; i++) 654 pending |= *(classes[i]) != NULL; 655 } 656 657 return pending; 658} 659 660static inline bool _selector_less_specific(const css_selector *ref, 661 const css_selector *cand) 662{ 663 bool result = true; 664 665 if (cand == NULL) 666 return false; 667 668 if (ref == NULL) 669 return true; 670 671 /* Sort by specificity */ 672 if (cand->specificity < ref->specificity) { 673 result = true; 674 } else if (ref->specificity < cand->specificity) { 675 result = false; 676 } else { 677 /* Then by rule index -- earliest wins */ 678 if (cand->rule->index < ref->rule->index) 679 result = true; 680 else 681 result = false; 682 } 683 684 return result; 685} 686 687static const css_selector *_selector_next(const css_selector **node, 688 const css_selector **id, const css_selector ***classes, 689 uint32_t n_classes, const css_selector **univ) 690{ 691 const css_selector *ret = NULL; 692 693 if (_selector_less_specific(ret, *node)) 694 ret = *node; 695 696 if (_selector_less_specific(ret, *id)) 697 ret = *id; 698 699 if (_selector_less_specific(ret, *univ)) 700 ret = *univ; 701 702 if (classes != NULL && n_classes > 0) { 703 uint32_t i; 704 705 for (i = 0; i < n_classes; i++) { 706 if (_selector_less_specific(ret, *(classes[i]))) 707 ret = *(classes[i]); 708 } 709 } 710 711 return ret; 712} 713 714css_error match_selectors_in_sheet(css_select_ctx *ctx, 715 const css_stylesheet *sheet, css_select_state *state) 716{ 717 static const css_selector *empty_selector = NULL; 718 lwc_string *element = NULL; 719 lwc_string *id = NULL; 720 lwc_string **classes = NULL; 721 uint32_t n_classes = 0, i = 0; 722 const css_selector **node_selectors = &empty_selector; 723 css_selector_hash_iterator node_iterator; 724 const css_selector **id_selectors = &empty_selector; 725 css_selector_hash_iterator id_iterator; 726 const css_selector ***class_selectors = NULL; 727 css_selector_hash_iterator class_iterator; 728 const css_selector **univ_selectors = &empty_selector; 729 css_selector_hash_iterator univ_iterator; 730 css_error error; 731 732 /* Get node's name */ 733 error = state->handler->node_name(state->pw, state->node, 734 &element); 735 if (error != CSS_OK) 736 return error; 737 738 /* Get node's ID, if any */ 739 error = state->handler->node_id(state->pw, state->node, 740 &id); 741 if (error != CSS_OK) 742 goto cleanup; 743 744 /* Get node's classes, if any */ 745 /** \todo Do we really want to force the client to allocate a new array 746 * every time we call this? It seems hugely inefficient, given they can 747 * cache the data. */ 748 error = state->handler->node_classes(state->pw, state->node, 749 &classes, &n_classes); 750 if (error != CSS_OK) 751 goto cleanup; 752 753 /* Find hash chain that applies to current node */ 754 error = css_selector_hash_find(sheet->selectors, element, 755 &node_iterator, &node_selectors); 756 if (error != CSS_OK) 757 goto cleanup; 758 759 if (classes != NULL && n_classes > 0) { 760 /* Find hash chains for node classes */ 761 class_selectors = ctx->alloc(NULL, 762 n_classes * sizeof(css_selector **), ctx->pw); 763 if (class_selectors == NULL) { 764 error = CSS_NOMEM; 765 goto cleanup; 766 } 767 768 for (i = 0; i < n_classes; i++) { 769 error = css_selector_hash_find_by_class( 770 sheet->selectors, classes[i], 771 &class_iterator, &class_selectors[i]); 772 if (error != CSS_OK) 773 goto cleanup; 774 } 775 } 776 777 if (id != NULL) { 778 /* Find hash chain for node ID */ 779 error = css_selector_hash_find_by_id(sheet->selectors, id, 780 &id_iterator, &id_selectors); 781 if (error != CSS_OK) 782 goto cleanup; 783 } 784 785 /* Find hash chain for universal selector */ 786 error = css_selector_hash_find_universal(sheet->selectors, 787 &univ_iterator, &univ_selectors); 788 if (error != CSS_OK) 789 goto cleanup; 790 791 /* Process matching selectors, if any */ 792 while (_selectors_pending(node_selectors, id_selectors, 793 class_selectors, n_classes, univ_selectors)) { 794 const css_selector *selector; 795 css_rule *rule, *parent; 796 bool process = true; 797 798 /* Selectors must be matched in ascending order of specificity 799 * and rule index. (c.f. outranks_existing()) 800 * 801 * Pick the least specific/earliest occurring selector. 802 */ 803 selector = _selector_next(node_selectors, id_selectors, 804 class_selectors, n_classes, univ_selectors); 805 806 /* Ignore any selectors contained in rules which are a child 807 * of an @media block that doesn't match the current media 808 * requirements. */ 809 for (rule = selector->rule; rule != NULL; rule = parent) { 810 if (rule->type == CSS_RULE_MEDIA && 811 (((css_rule_media *) rule)->media & 812 state->media) == 0) { 813 process = false; 814 break; 815 } 816 817 if (rule->ptype != CSS_RULE_PARENT_STYLESHEET) 818 parent = rule->parent; 819 else 820 parent = NULL; 821 } 822 823 if (process) { 824 error = match_selector_chain(ctx, selector, state); 825 if (error != CSS_OK) 826 goto cleanup; 827 } 828 829 /* Advance to next selector in whichever chain we extracted 830 * the processed selector from. */ 831 if (selector == *node_selectors) { 832 error = node_iterator(sheet->selectors, 833 node_selectors, &node_selectors); 834 } else if (selector == *id_selectors) { 835 error = id_iterator(sheet->selectors, 836 id_selectors, &id_selectors); 837 } else if (selector == *univ_selectors) { 838 error = univ_iterator(sheet->selectors, 839 univ_selectors, &univ_selectors); 840 } else { 841 for (i = 0; i < n_classes; i++) { 842 if (selector == *(class_selectors[i])) { 843 error = class_iterator(sheet->selectors, 844 class_selectors[i], 845 &class_selectors[i]); 846 break; 847 } 848 } 849 } 850 851 if (error != CSS_OK) 852 goto cleanup; 853 } 854 855 error = CSS_OK; 856cleanup: 857 if (class_selectors != NULL) 858 ctx->alloc(class_selectors, 0, ctx->pw); 859 860 if (classes != NULL) { 861 for (i = 0; i < n_classes; i++) 862 lwc_string_unref(classes[i]); 863 864 ctx->alloc(classes, 0, ctx->pw); 865 } 866 867 if (id != NULL) 868 lwc_string_unref(id); 869 870 lwc_string_unref(element); 871 return error; 872} 873 874css_error match_selector_chain(css_select_ctx *ctx, 875 const css_selector *selector, css_select_state *state) 876{ 877 const css_selector *s = selector; 878 void *node = state->node; 879 css_error error; 880 881#ifdef DEBUG_CHAIN_MATCHING 882 fprintf(stderr, "matching: "); 883 dump_chain(selector); 884 fprintf(stderr, "\n"); 885#endif 886 887 do { 888 void *next_node = NULL; 889 const css_selector_detail *detail = &s->data; 890 bool match = false; 891 892 /* If this is the first selector in the chain, we must match 893 * its details. The details of subsequent selectors will be 894 * matched when processing the combinator. */ 895 if (s == selector) { 896 /* Match details on this selector */ 897 error = match_details(ctx, node, detail, state, &match); 898 if (error != CSS_OK) 899 return error; 900 901 /* Details don't match, so reject selector chain */ 902 if (match == false) 903 return CSS_OK; 904 905 } 906 907 /* Consider any combinator on this selector */ 908 if (s->data.comb != CSS_COMBINATOR_NONE && 909 s->combinator->data.name != state->universal) { 910 /* Named combinator */ 911 error = match_named_combinator(ctx, s->data.comb, 912 s->combinator, state, node, &next_node); 913 if (error != CSS_OK) 914 return error; 915 916 /* No match for combinator, so reject selector chain */ 917 if (next_node == NULL) 918 return CSS_OK; 919 } else if (s->data.comb != CSS_COMBINATOR_NONE && 920 s->combinator->data.name == state->universal) { 921 /* Universal combinator */ 922 error = match_universal_combinator(ctx, s->data.comb, 923 s->combinator, state, node, &next_node); 924 if (error != CSS_OK) 925 return error; 926 927 /* No match for combinator, so reject selector chain */ 928 if (next_node == NULL) 929 return CSS_OK; 930 } 931 932 /* Details matched, so progress to combining selector */ 933 s = s->combinator; 934 node = next_node; 935 } while (s != NULL); 936 937 /* If we got here, then the entire selector chain matched, so cascade */ 938 state->current_specificity = selector->specificity; 939 940 /* No bytecode if rule body is empty or wholly invalid */ 941 if (((css_rule_selector *) selector->rule)->style == NULL) 942 return CSS_OK; 943 944 return cascade_style(((css_rule_selector *) selector->rule)->style, 945 state); 946} 947 948css_error match_named_combinator(css_select_ctx *ctx, css_combinator type, 949 const css_selector *selector, css_select_state *state, 950 void *node, void **next_node) 951{ 952 const css_selector_detail *detail = &selector->data; 953 void *n = node; 954 css_error error; 955 956 do { 957 bool match = false; 958 959 /* Find candidate node */ 960 switch (type) { 961 case CSS_COMBINATOR_ANCESTOR: 962 error = state->handler->named_ancestor_node(state->pw, 963 n, selector->data.name, &n); 964 if (error != CSS_OK) 965 return error; 966 break; 967 case CSS_COMBINATOR_PARENT: 968 error = state->handler->named_parent_node(state->pw, 969 n, selector->data.name, &n); 970 if (error != CSS_OK) 971 return error; 972 break; 973 case CSS_COMBINATOR_SIBLING: 974 error = state->handler->named_sibling_node(state->pw, 975 n, selector->data.name, &n); 976 if (error != CSS_OK) 977 return error; 978 break; 979 case CSS_COMBINATOR_NONE: 980 break; 981 } 982 983 if (n != NULL) { 984 /* Match its details */ 985 error = match_details(ctx, n, detail, state, &match); 986 if (error != CSS_OK) 987 return error; 988 989 /* If we found a match, use it */ 990 if (match == true) 991 break; 992 993 /* For parent and sibling selectors, only adjacent 994 * nodes are valid. Thus, if we failed to match, 995 * give up. */ 996 if (type == CSS_COMBINATOR_PARENT || 997 type == CSS_COMBINATOR_SIBLING) 998 n = NULL; 999 } 1000 } while (n != NULL); 1001 1002 *next_node = n; 1003 1004 return CSS_OK; 1005} 1006 1007css_error match_universal_combinator(css_select_ctx *ctx, css_combinator type, 1008 const css_selector *selector, css_select_state *state, 1009 void *node, void **next_node) 1010{ 1011 const css_selector_detail *detail = &selector->data; 1012 void *n = node; 1013 css_error error; 1014 1015 do { 1016 bool match = false; 1017 1018 /* Find candidate node */ 1019 switch (type) { 1020 case CSS_COMBINATOR_ANCESTOR: 1021 case CSS_COMBINATOR_PARENT: 1022 error = state->handler->parent_node(state->pw, n, &n); 1023 if (error != CSS_OK) 1024 return error; 1025 break; 1026 case CSS_COMBINATOR_SIBLING: 1027 error = state->handler->sibling_node(state->pw, n, &n); 1028 if (error != CSS_OK) 1029 return error; 1030 break; 1031 case CSS_COMBINATOR_NONE: 1032 break; 1033 } 1034 1035 if (n != NULL) { 1036 /* Match its details */ 1037 error = match_details(ctx, n, detail, state, &match); 1038 if (error != CSS_OK) 1039 return error; 1040 1041 /* If we found a match, use it */ 1042 if (match == true) 1043 break; 1044 1045 /* For parent and sibling selectors, only adjacent 1046 * nodes are valid. Thus, if we failed to match, 1047 * give up. */ 1048 if (type == CSS_COMBINATOR_PARENT || 1049 type == CSS_COMBINATOR_SIBLING) 1050 n = NULL; 1051 } 1052 } while (n != NULL); 1053 1054 *next_node = n; 1055 1056 return CSS_OK; 1057} 1058 1059css_error match_details(css_select_ctx *ctx, void *node, 1060 const css_selector_detail *detail, css_select_state *state, 1061 bool *match) 1062{ 1063 css_error error; 1064 1065 /* We match by default (if there are no details than the element 1066 * selector, then we must match) */ 1067 *match = true; 1068 1069 /** \todo Some details are easier to test than others (e.g. dashmatch 1070 * actually requires looking at data rather than simply comparing 1071 * pointers). Should we consider sorting the detail list such that the 1072 * simpler details come first (and thus the expensive match routines 1073 * can be avoided unless absolutely necessary)? */ 1074 1075 do { 1076 error = match_detail(ctx, node, detail, state, match); 1077 if (error != CSS_OK) 1078 return error; 1079 1080 /* Detail doesn't match, so reject selector chain */ 1081 if (*match == false) 1082 return CSS_OK; 1083 1084 if (detail->next) 1085 detail++; 1086 else 1087 detail = NULL; 1088 } while (detail != NULL); 1089 1090 return CSS_OK; 1091} 1092 1093css_error match_detail(css_select_ctx *ctx, void *node, 1094 const css_selector_detail *detail, css_select_state *state, 1095 bool *match) 1096{ 1097 css_error error = CSS_OK; 1098 1099 UNUSED(ctx); 1100 1101 switch (detail->type) { 1102 case CSS_SELECTOR_ELEMENT: 1103 if (lwc_string_length(detail->name) == 1 && 1104 lwc_string_data(detail->name)[0] == '*') { 1105 *match = true; 1106 } else { 1107 error = state->handler->node_has_name(state->pw, node, 1108 detail->name, match); 1109 } 1110 break; 1111 case CSS_SELECTOR_CLASS: 1112 error = state->handler->node_has_class(state->pw, node, 1113 detail->name, match); 1114 break; 1115 case CSS_SELECTOR_ID: 1116 error = state->handler->node_has_id(state->pw, node, 1117 detail->name, match); 1118 break; 1119 case CSS_SELECTOR_PSEUDO_CLASS: 1120 if (detail->name == state->first_child) { 1121 error = state->handler->node_is_first_child(state->pw, 1122 node, match); 1123 } else if (detail->name == state->link) { 1124 error = state->handler->node_is_link(state->pw, 1125 node, match); 1126 } else if (detail->name == state->visited) { 1127 error = state->handler->node_is_visited(state->pw, 1128 node, match); 1129 } else if (detail->name == state->hover) { 1130 error = state->handler->node_is_hover(state->pw, 1131 node, match); 1132 } else if (detail->name == state->active) { 1133 error = state->handler->node_is_active(state->pw, 1134 node, match); 1135 } else if (detail->name == state->focus) { 1136 error = state->handler->node_is_focus(state->pw, 1137 node, match); 1138 } else 1139 *match = false; 1140 break; 1141 case CSS_SELECTOR_PSEUDO_ELEMENT: 1142 if (detail->name == state->first_line && 1143 state->pseudo_element == 1144 CSS_PSEUDO_ELEMENT_FIRST_LINE) 1145 *match = true; 1146 else if (detail->name == state->first_letter && 1147 state->pseudo_element == 1148 CSS_PSEUDO_ELEMENT_FIRST_LETTER) 1149 *match = true; 1150 else if (detail->name == state->before && 1151 state->pseudo_element == 1152 CSS_PSEUDO_ELEMENT_BEFORE) 1153 *match = true; 1154 else if (detail->name == state->after && 1155 state->pseudo_element == 1156 CSS_PSEUDO_ELEMENT_AFTER) 1157 *match = true; 1158 else 1159 *match = false; 1160 break; 1161 case CSS_SELECTOR_ATTRIBUTE: 1162 error = state->handler->node_has_attribute(state->pw, node, 1163 detail->name, match); 1164 break; 1165 case CSS_SELECTOR_ATTRIBUTE_EQUAL: 1166 error = state->handler->node_has_attribute_equal(state->pw, 1167 node, detail->name, detail->value, match); 1168 break; 1169 case CSS_SELECTOR_ATTRIBUTE_DASHMATCH: 1170 error = state->handler->node_has_attribute_dashmatch(state->pw, 1171 node, detail->name, detail->value, match); 1172 break; 1173 case CSS_SELECTOR_ATTRIBUTE_INCLUDES: 1174 error = state->handler->node_has_attribute_includes(state->pw, 1175 node, detail->name, detail->value, match); 1176 break; 1177 } 1178 1179 return error; 1180} 1181 1182css_error cascade_style(const css_style *style, css_select_state *state) 1183{ 1184 css_style s = *style; 1185 1186 while (s.length > 0) { 1187 opcode_t op; 1188 css_error error; 1189 uint32_t opv = *((uint32_t *) s.bytecode); 1190 1191 advance_bytecode(&s, sizeof(opv)); 1192 1193 op = getOpcode(opv); 1194 1195 error = prop_dispatch[op].cascade(opv, &s, state); 1196 if (error != CSS_OK) 1197 return error; 1198 } 1199 1200 return CSS_OK; 1201} 1202 1203bool outranks_existing(uint16_t op, bool important, css_select_state *state, 1204 bool inherit) 1205{ 1206 prop_state *existing = &state->props[op]; 1207 bool outranks = false; 1208 1209 /* Sorting on origin & importance gives the following: 1210 * 1211 * | UA, - | UA, i | USER, - | USER, i | AUTHOR, - | AUTHOR, i 1212 * |---------------------------------------------------------- 1213 * UA , - | S S Y Y Y Y 1214 * UA , i | S S Y Y Y Y 1215 * USER , - | - - S Y Y Y 1216 * USER , i | - - - S - - 1217 * AUTHOR, - | - - - Y S Y 1218 * AUTHOR, i | - - - Y - S 1219 * 1220 * Where the columns represent the origin/importance of the property 1221 * being considered and the rows represent the origin/importance of 1222 * the existing property. 1223 * 1224 * - means that the existing property must be preserved 1225 * Y means that the new property must be applied 1226 * S means that the specificities of the rules must be considered. 1227 * 1228 * If specificities are considered, the highest specificity wins. 1229 * If specificities are equal, then the rule defined last wins. 1230 * 1231 * We have no need to explicitly consider the ordering of rules if 1232 * the specificities are the same because: 1233 * 1234 * a) We process stylesheets in order 1235 * b) The selector hash chains within a sheet are ordered such that 1236 * more specific rules come after less specific ones and, when 1237 * specificities are identical, rules defined later occur after 1238 * those defined earlier. 1239 * 1240 * Therefore, where we consider specificity, below, the property 1241 * currently being considered will always be applied if its specificity 1242 * is greater than or equal to that of the existing property. 1243 */ 1244 1245 if (existing->set == 0) { 1246 /* Property hasn't been set before, new one wins */ 1247 outranks = true; 1248 } else { 1249 assert(CSS_ORIGIN_UA < CSS_ORIGIN_USER); 1250 assert(CSS_ORIGIN_USER < CSS_ORIGIN_AUTHOR); 1251 1252 if (existing->origin < state->current_origin) { 1253 /* New origin has more weight than existing one. 1254 * Thus, new property wins, except when the existing 1255 * one is USER, i. */ 1256 if (existing->important == 0 || 1257 existing->origin != CSS_ORIGIN_USER) { 1258 outranks = true; 1259 } 1260 } else if (existing->origin == state->current_origin) { 1261 /* Origins are identical, consider importance, except 1262 * for UA stylesheets, when specificity is always 1263 * considered (as importance is meaningless) */ 1264 if (existing->origin == CSS_ORIGIN_UA) { 1265 if (state->current_specificity >= 1266 existing->specificity) { 1267 outranks = true; 1268 } 1269 } else if (existing->important == 0 && important) { 1270 /* New is more important than old. */ 1271 outranks = true; 1272 } else if (existing->important && important == false) { 1273 /* Old is more important than new */ 1274 } else { 1275 /* Same importance, consider specificity */ 1276 if (state->current_specificity >= 1277 existing->specificity) { 1278 outranks = true; 1279 } 1280 } 1281 } else { 1282 /* Existing origin has more weight than new one. 1283 * Thus, existing property wins, except when the new 1284 * one is USER, i. */ 1285 if (state->current_origin == CSS_ORIGIN_USER && 1286 important) { 1287 outranks = true; 1288 } 1289 } 1290 } 1291 1292 if (outranks) { 1293 /* The new property is about to replace the old one. 1294 * Update our state to reflect this. */ 1295 existing->set = 1; 1296 existing->specificity = state->current_specificity; 1297 existing->origin = state->current_origin; 1298 existing->important = important; 1299 existing->inherit = inherit; 1300 } 1301 1302 return outranks; 1303} 1304 1305/****************************************************************************** 1306 * Debug helpers * 1307 ******************************************************************************/ 1308#ifdef DEBUG_CHAIN_MATCHING 1309void dump_chain(const css_selector *selector) 1310{ 1311 const css_selector_detail *detail = &selector->data; 1312 1313 if (selector->data.comb != CSS_COMBINATOR_NONE) 1314 dump_chain(selector->combinator); 1315 1316 if (selector->data.comb == CSS_COMBINATOR_ANCESTOR) 1317 fprintf(stderr, " "); 1318 else if (selector->data.comb == CSS_COMBINATOR_SIBLING) 1319 fprintf(stderr, " + "); 1320 else if (selector->data.comb == CSS_COMBINATOR_PARENT) 1321 fprintf(stderr, " > "); 1322 1323 do { 1324 switch (detail->type) { 1325 case CSS_SELECTOR_ELEMENT: 1326 if (lwc_string_length(detail->name) == 1 && 1327 lwc_string_data(detail->name)[0] == '*' && 1328 detail->next == 1) { 1329 break; 1330 } 1331 fprintf(stderr, "%.*s", 1332 (int) lwc_string_length(detail->name), 1333 lwc_string_data(detail->name)); 1334 break; 1335 case CSS_SELECTOR_CLASS: 1336 fprintf(stderr, ".%.*s", 1337 (int) lwc_string_length(detail->name), 1338 lwc_string_data(detail->name)); 1339 break; 1340 case CSS_SELECTOR_ID: 1341 fprintf(stderr, "#%.*s", 1342 (int) lwc_string_length(detail->name), 1343 lwc_string_data(detail->name)); 1344 break; 1345 case CSS_SELECTOR_PSEUDO_CLASS: 1346 case CSS_SELECTOR_PSEUDO_ELEMENT: 1347 fprintf(stderr, ":%.*s", 1348 (int) lwc_string_length(detail->name), 1349 lwc_string_data(detail->name)); 1350 1351 if (detail->value != NULL) { 1352 fprintf(stderr, "(%.*s)", 1353 (int) lwc_string_length(detail->value), 1354 lwc_string_data(detail->value)); 1355 } 1356 break; 1357 case CSS_SELECTOR_ATTRIBUTE: 1358 fprintf(stderr, "[%.*s]", 1359 (int) lwc_string_length(detail->name), 1360 lwc_string_data(detail->name)); 1361 break; 1362 case CSS_SELECTOR_ATTRIBUTE_EQUAL: 1363 fprintf(stderr, "[%.*s=\"%.*s\"]", 1364 (int) lwc_string_length(detail->name), 1365 lwc_string_data(detail->name), 1366 (int) lwc_string_length(detail->value), 1367 lwc_string_data(detail->value)); 1368 break; 1369 case CSS_SELECTOR_ATTRIBUTE_DASHMATCH: 1370 fprintf(stderr, "[%.*s|=\"%.*s\"]", 1371 (int) lwc_string_length(detail->name), 1372 lwc_string_data(detail->name), 1373 (int) lwc_string_length(detail->value), 1374 lwc_string_data(detail->value)); 1375 break; 1376 case CSS_SELECTOR_ATTRIBUTE_INCLUDES: 1377 fprintf(stderr, "[%.*s~=\"%.*s\"]", 1378 (int) lwc_string_length(detail->name), 1379 lwc_string_data(detail->name), 1380 (int) lwc_string_length(detail->value), 1381 lwc_string_data(detail->value)); 1382 break; 1383 } 1384 1385 if (detail->next) 1386 detail++; 1387 else 1388 detail = NULL; 1389 } while (detail); 1390} 1391#endif 1392