1855 lines · 51.0 KB · C
RawDownload
/* * This file is part of LibCSS * Licensed under the MIT License, *                http://www.opensource.org/licenses/mit-license.php * Copyright 2009 John-Mark Bell <jmb@netsurf-browser.org> */ #include <assert.h>#include <string.h> #include <libcss/select.h> #include "bytecode/bytecode.h"#include "bytecode/opcodes.h"#include "stylesheet.h"#include "select/computed.h"#include "select/dispatch.h"#include "select/hash.h"#include "select/propset.h"#include "select/select.h"#include "utils/parserutilserror.h"#include "utils/utils.h" /* Define this to enable verbose messages when matching selector chains */#undef DEBUG_CHAIN_MATCHING /** * Container for stylesheet selection info */typedef struct css_select_sheet {	const css_stylesheet *sheet;	/**< Stylesheet */	css_origin origin;		/**< Stylesheet origin */	uint64_t media;			/**< Applicable media */} css_select_sheet; /** * CSS selection context */struct css_select_ctx {	uint32_t n_sheets;		/**< Number of sheets */ 	css_select_sheet *sheets;	/**< Array of sheets */ 	css_allocator_fn alloc;		/**< Allocation routine */	void *pw;			/**< Client-specific private data */ 	/* Useful interned strings */	lwc_string *universal;	lwc_string *first_child;	lwc_string *link;	lwc_string *visited;	lwc_string *hover;	lwc_string *active;	lwc_string *focus;	lwc_string *nth_child;	lwc_string *nth_last_child;	lwc_string *nth_of_type;	lwc_string *nth_last_of_type;	lwc_string *last_child;	lwc_string *first_of_type;	lwc_string *last_of_type;	lwc_string *only_child;	lwc_string *only_of_type;	lwc_string *root;	lwc_string *empty;	lwc_string *target;	lwc_string *lang;	lwc_string *enabled;	lwc_string *disabled;	lwc_string *checked;	lwc_string *first_line;	lwc_string *first_letter;	lwc_string *before;	lwc_string *after;}; static css_error set_hint(css_select_state *state, uint32_t prop);static css_error set_initial(css_select_state *state, 		uint32_t prop, css_pseudo_element pseudo,		void *parent); static css_error intern_strings(css_select_ctx *ctx);static void destroy_strings(css_select_ctx *ctx); static css_error select_from_sheet(css_select_ctx *ctx, 		const css_stylesheet *sheet, css_origin origin,		css_select_state *state);static css_error match_selectors_in_sheet(css_select_ctx *ctx, 		const css_stylesheet *sheet, css_select_state *state);static css_error match_selector_chain(css_select_ctx *ctx, 		const css_selector *selector, css_select_state *state);static css_error match_named_combinator(css_select_ctx *ctx, 		css_combinator type, const css_selector *selector, 		css_select_state *state, void *node, void **next_node);static css_error match_universal_combinator(css_select_ctx *ctx, 		css_combinator type, const css_selector *selector, 		css_select_state *state, void *node, void **next_node);static css_error match_details(css_select_ctx *ctx, void *node, 		const css_selector_detail *detail, css_select_state *state, 		bool *match, css_pseudo_element *pseudo_element);static css_error match_detail(css_select_ctx *ctx, void *node, 		const css_selector_detail *detail, css_select_state *state, 		bool *match, css_pseudo_element *pseudo_element);static css_error cascade_style(const css_style *style, css_select_state *state); #ifdef DEBUG_CHAIN_MATCHINGstatic void dump_chain(const css_selector *selector);#endif  /** * Create a selection context * * \param alloc   Memory (de)allocation function * \param pw      Client-specific private data * \param result  Pointer to location to receive created context * \return CSS_OK on success, appropriate error otherwise. */css_error css_select_ctx_create(css_allocator_fn alloc, void *pw,		css_select_ctx **result){	css_select_ctx *c;	css_error error; 	if (alloc == NULL || result == NULL)		return CSS_BADPARM; 	c = alloc(NULL, sizeof(css_select_ctx), pw);	if (c == NULL)		return CSS_NOMEM; 	memset(c, 0, sizeof(css_select_ctx)); 	error = intern_strings(c);	if (error != CSS_OK) {		alloc(c, 0, pw);		return error;	} 	c->alloc = alloc;	c->pw = pw; 	*result = c; 	return CSS_OK;} /** * Destroy a selection context * * \param ctx  The context to destroy * \return CSS_OK on success, appropriate error otherwise */css_error css_select_ctx_destroy(css_select_ctx *ctx){	if (ctx == NULL)		return CSS_BADPARM; 	destroy_strings(ctx); 	if (ctx->sheets != NULL)		ctx->alloc(ctx->sheets, 0, ctx->pw); 	ctx->alloc(ctx, 0, ctx->pw); 	return CSS_OK;} /** * Append a stylesheet to a selection context * * \param ctx     The context to append to * \param sheet   The sheet to append * \param origin  Origin of the sheet * \param media   Media types to which the sheet applies * \return CSS_OK on success, appropriate error otherwise */css_error css_select_ctx_append_sheet(css_select_ctx *ctx, 		const css_stylesheet *sheet, css_origin origin,		uint64_t media){	if (ctx == NULL || sheet == NULL)		return CSS_BADPARM; 	return css_select_ctx_insert_sheet(ctx, sheet, ctx->n_sheets,			origin, media);} /** * Insert a stylesheet into a selection context *  * \param ctx    The context to insert into * \param sheet  Sheet to insert * \param index  Index in context to insert sheet * \param origin  Origin of the sheet * \param media   Media types to which the sheet applies * \return CSS_OK on success, appropriate error otherwise */css_error css_select_ctx_insert_sheet(css_select_ctx *ctx,		const css_stylesheet *sheet, uint32_t index,		css_origin origin, uint64_t media){	css_select_sheet *temp; 	if (ctx == NULL || sheet == NULL)		return CSS_BADPARM; 	/* Inline styles cannot be inserted into a selection context */	if (sheet->inline_style)		return CSS_INVALID; 	/* Index must be in the range [0, n_sheets]	 * The latter being equivalent to append */	if (index > ctx->n_sheets)		return CSS_INVALID; 	temp = ctx->alloc(ctx->sheets, 			(ctx->n_sheets + 1) * sizeof(css_select_sheet),			ctx->pw);	if (temp == NULL)		return CSS_NOMEM; 	ctx->sheets = temp; 	if (index < ctx->n_sheets) {		memmove(&ctx->sheets[index + 1], &ctx->sheets[index],			(ctx->n_sheets - index) * sizeof(css_select_sheet));	} 	ctx->sheets[index].sheet = sheet;	ctx->sheets[index].origin = origin;	ctx->sheets[index].media = media; 	ctx->n_sheets++; 	return CSS_OK;} /** * Remove a sheet from a selection context * * \param ctx    The context to remove from * \param sheet  Sheet to remove * \return CSS_OK on success, appropriate error otherwise */css_error css_select_ctx_remove_sheet(css_select_ctx *ctx,		const css_stylesheet *sheet){	uint32_t index; 	if (ctx == NULL || sheet == NULL)		return CSS_BADPARM; 	for (index = 0; index < ctx->n_sheets; index++) {		if (ctx->sheets[index].sheet == sheet)			break;	} 	if (index == ctx->n_sheets)		return CSS_INVALID; 	memmove(&ctx->sheets[index], &ctx->sheets[index + 1],			(ctx->n_sheets - index) * sizeof(css_select_sheet)); 	ctx->n_sheets--; 	return CSS_OK; } /** * Count the number of top-level sheets in a selection context * * \param ctx    Context to consider * \param count  Pointer to location to receive count of sheets * \return CSS_OK on success, appropriate error otherwise */css_error css_select_ctx_count_sheets(css_select_ctx *ctx, uint32_t *count){	if (ctx == NULL || count == NULL)		return CSS_BADPARM; 	*count = ctx->n_sheets; 	return CSS_OK;} /** * Retrieve a sheet from a selection context * * \param ctx    Context to look in * \param index  Index in context to look * \param sheet  Pointer to location to receive sheet * \return CSS_OK on success, appropriate error otherwise */css_error css_select_ctx_get_sheet(css_select_ctx *ctx, uint32_t index,		const css_stylesheet **sheet){	if (ctx == NULL || sheet == NULL)		return CSS_BADPARM; 	if (index > ctx->n_sheets)		return CSS_INVALID; 	*sheet = ctx->sheets[index].sheet; 	return CSS_OK;} /** * Select a style for the given node * * \param ctx             Selection context to use * \param node            Node to select style for * \param media           Currently active media types * \param inline_style    Corresponding inline style for node, or NULL * \param handler         Dispatch table of handler functions * \param pw              Client-specific private data for handler functions * \param result          Pointer to location to receive result set * \return CSS_OK on success, appropriate error otherwise. * * In computing the style, no reference is made to the parent node's * style. Therefore, the resultant computed style is not ready for * immediate use, as some properties may be marked as inherited. * Use css_computed_style_compose() to obtain a fully computed style. * * This two-step approach to style computation is designed to allow * the client to store the partially computed style and efficiently * update the fully computed style for a node when layout changes. */css_error css_select_style(css_select_ctx *ctx, void *node,		uint64_t media, const css_stylesheet *inline_style,		css_select_handler *handler, void *pw,		css_select_results **result){	uint32_t i, j;	css_error error;	css_select_state state;	void *parent = NULL; 	if (ctx == NULL || node == NULL || result == NULL || handler == NULL ||			handler->handler_version != 					CSS_SELECT_HANDLER_VERSION_1)		return CSS_BADPARM; 	/* Set up the selection state */	memset(&state, 0, sizeof(css_select_state));	state.node = node;	state.media = media;	state.handler = handler;	state.pw = pw; 	/* Allocate the result set */	state.results = ctx->alloc(NULL, sizeof(css_select_results), ctx->pw);	if (state.results == NULL)		return CSS_NOMEM; 	state.results->alloc = ctx->alloc;	state.results->pw = ctx->pw; 	for (i = 0; i < CSS_PSEUDO_ELEMENT_COUNT; i++)		state.results->styles[i] = NULL; 	/* Base element style is guaranteed to exist */	error = css_computed_style_create(ctx->alloc, ctx->pw,			&state.results->styles[CSS_PSEUDO_ELEMENT_NONE]);	if (error != CSS_OK) {		ctx->alloc(state.results, 0, ctx->pw);		return error;	} 	error = handler->parent_node(pw, node, &parent);	if (error != CSS_OK)		goto cleanup; 	/* Get node's name */	error = handler->node_name(pw, node, &state.element);	if (error != CSS_OK)		return error; 	/* Get node's ID, if any */	error = handler->node_id(pw, node, &state.id);	if (error != CSS_OK)		goto cleanup; 	/* Get node's classes, if any */	/** \todo Do we really want to force the client to allocate a new array 	 * every time we call this? It seems hugely inefficient, given they can 	 * cache the data. */	error = handler->node_classes(pw, node,				&state.classes, &state.n_classes);	if (error != CSS_OK)		goto cleanup; 	/* Iterate through the top-level stylesheets, selecting styles	 * from those which apply to our current media requirements and	 * are not disabled */	for (i = 0; i < ctx->n_sheets; i++) {		const css_select_sheet s = ctx->sheets[i]; 		if ((s.media & media) != 0 &&				s.sheet->disabled == false) {			error = select_from_sheet(ctx, s.sheet, 					s.origin, &state);			if (error != CSS_OK)				goto cleanup;		}	} 	/* Consider any inline style for the node */	if (inline_style != NULL) {		css_rule_selector *sel = 				(css_rule_selector *) inline_style->rule_list; 		/* Sanity check style */		if (inline_style->rule_count != 1 ||			inline_style->rule_list->type != CSS_RULE_SELECTOR || 				inline_style->rule_list->items != 0) {			error = CSS_INVALID;			goto cleanup;		} 		/* No bytecode if input was empty or wholly invalid */		if (sel->style != NULL) {			/* Inline style applies to base element only */			state.current_pseudo = CSS_PSEUDO_ELEMENT_NONE;			state.computed = state.results->styles[					CSS_PSEUDO_ELEMENT_NONE]; 			error = cascade_style(sel->style, &state);			if (error != CSS_OK)				goto cleanup;		}	} 	/* Take account of presentational hints and fix up any remaining	 * unset properties. */ 	/* Base element */	state.current_pseudo = CSS_PSEUDO_ELEMENT_NONE;	state.computed = state.results->styles[CSS_PSEUDO_ELEMENT_NONE];	for (i = 0; i < CSS_N_PROPERTIES; i++) {		const prop_state *prop = 				&state.props[i][CSS_PSEUDO_ELEMENT_NONE]; 		/* Apply presentational hints if the property is unset or 		 * the existing property value did not come from an author 		 * stylesheet or a user sheet using !important. */		if (prop->set == false ||				(prop->origin != CSS_ORIGIN_AUTHOR &&				prop->important == false)) {			error = set_hint(&state, i);			if (error != CSS_OK)				goto cleanup;		} 		/* If the property is still unset or it's set to inherit 		 * and we're the root element, then set it to its initial 		 * value. */		if (prop->set == false || 				(parent == NULL && 				prop->inherit == true)) {			error = set_initial(&state, i, 					CSS_PSEUDO_ELEMENT_NONE, parent);			if (error != CSS_OK)				goto cleanup;		}	} 	/* Pseudo elements, if any */	for (j = CSS_PSEUDO_ELEMENT_NONE + 1; j < CSS_PSEUDO_ELEMENT_COUNT; j++) {		state.current_pseudo = j;		state.computed = state.results->styles[j]; 		/* Skip non-existent pseudo elements */		if (state.computed == NULL)			continue; 		for (i = 0; i < CSS_N_PROPERTIES; i++) {			const prop_state *prop = &state.props[i][j]; 			/* If the property is still unset then set it 			 * to its initial value. */			if (prop->set == false) {				error = set_initial(&state, i, j, parent);				if (error != CSS_OK)					goto cleanup;			}		}	} 	/* If this is the root element, then we must ensure that all	 * length values are absolute, display and float are correctly 	 * computed, and the default border-{top,right,bottom,left}-color 	 * is set to the computed value of color. */	if (parent == NULL) {		/* Only compute absolute values for the base element */		error = css__compute_absolute_values(NULL,				state.results->styles[CSS_PSEUDO_ELEMENT_NONE],				handler->compute_font_size, pw);

Lines 1–500 of 1855.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t