PageRenderTime 10ms CodeModel.GetById 6ms app.highlight 113ms RepoModel.GetById 1ms app.codeStats 0ms

/peek-build/src/netdepends/libcss/src/select/libcssselect.c

https://bitbucket.org/C0deMaver1ck/peeklinux
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