From 4860bd14179e94a7d073714c1c5b21fd9c200dd7 Mon Sep 17 00:00:00 2001 From: John Mark Bell Date: Wed, 5 Jan 2011 00:44:04 +0000 Subject: Simultaneously select styles for base + pseudo elements. svn path=/trunk/libcss/; revision=11211 --- src/select/select.c | 313 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 195 insertions(+), 118 deletions(-) (limited to 'src/select/select.c') diff --git a/src/select/select.c b/src/select/select.c index 48062d7..5c68699 100644 --- a/src/select/select.c +++ b/src/select/select.c @@ -45,8 +45,10 @@ struct css_select_ctx { void *pw; /**< Client-specific private data */ }; -static css_error set_hint(css_select_state *state, uint32_t i); -static css_error set_initial(css_select_state *state, uint32_t i, void *parent); +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 select_from_sheet(css_select_ctx *ctx, const css_stylesheet *sheet, css_origin origin, @@ -65,10 +67,10 @@ static css_error match_universal_combinator(css_select_ctx *ctx, 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); + 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, bool *match_pseudo_element); + bool *match, css_pseudo_element *pseudo_element); static css_error cascade_style(const css_style *style, css_select_state *state); #ifdef DEBUG_CHAIN_MATCHING @@ -272,12 +274,11 @@ css_error css_select_ctx_get_sheet(css_select_ctx *ctx, uint32_t index, * * \param ctx Selection context to use * \param node Node to select style for - * \param pseudo_element Pseudo element to select for, instead * \param media Currently active media types * \param inline_style Corresponding inline style for node, or NULL - * \param result Pointer to style to populate (assumed clean) * \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 @@ -290,12 +291,11 @@ css_error css_select_ctx_get_sheet(css_select_ctx *ctx, uint32_t index, * update the fully computed style for a node when layout changes. */ css_error css_select_style(css_select_ctx *ctx, void *node, - uint32_t pseudo_element, uint64_t media, - const css_stylesheet *inline_style, - css_computed_style *result, - css_select_handler *handler, void *pw) + uint64_t media, const css_stylesheet *inline_style, + css_select_handler *handler, void *pw, + css_select_results **result) { - uint32_t i; + uint32_t i, j; css_error error; css_select_state state; void *parent = NULL; @@ -303,22 +303,37 @@ css_error css_select_style(css_select_ctx *ctx, void *node, if (ctx == NULL || node == NULL || result == NULL || handler == NULL) return CSS_BADPARM; - /* Inline style has no meaning if selecting for a pseudo element */ - if (pseudo_element != CSS_PSEUDO_ELEMENT_NONE && inline_style != NULL) - return CSS_BADPARM; - /* Set up the selection state */ memset(&state, 0, sizeof(css_select_state)); state.node = node; - state.pseudo_element = pseudo_element; state.media = media; - state.result = result; state.handler = handler; state.pw = pw; - error = handler->parent_node(pw, node, &parent); - if (error) + /* Allocate the result set */ + state.results = ctx->alloc(NULL, sizeof(css_select_results) + + CSS_PSEUDO_ELEMENT_COUNT * + sizeof(css_computed_style *), 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; /* Iterate through the top-level stylesheets, selecting styles * from those which apply to our current media requirements and @@ -348,6 +363,11 @@ css_error css_select_style(css_select_ctx *ctx, void *node, /* 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; @@ -357,26 +377,44 @@ css_error css_select_style(css_select_ctx *ctx, void *node, /* Take account of presentational hints and fix up any remaining * unset properties. */ for (i = 0; i < CSS_N_PROPERTIES; i++) { - /* Apply presentational hints if we're not selecting for - * a pseudo element, and the property is unset or the - * existing property value did not come from an author - * stylesheet or a user sheet using !important. */ - if (pseudo_element == CSS_PSEUDO_ELEMENT_NONE && - (state.props[i].set == false || - (state.props[i].origin != CSS_ORIGIN_AUTHOR && - state.props[i].important == false))) { - error = set_hint(&state, i); - if (error != CSS_OK) - goto cleanup; - } + for (j = 0; j < CSS_PSEUDO_ELEMENT_COUNT; j++) { + prop_state *prop = &state.props[i][j]; + + /* Skip non-existent pseudo elements */ + if (state.results->styles[j] == NULL) + continue; + + /* Apply presentational hints if we're not selecting for + * a pseudo element, and the property is unset or the + * existing property value did not come from an author + * stylesheet or a user sheet using !important. */ + if (j == CSS_PSEUDO_ELEMENT_NONE && + (prop->set == false || + (prop->origin != CSS_ORIGIN_AUTHOR && + prop->important == false))) { + state.current_pseudo = j; + state.computed = state.results->styles[j]; + + 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 (state.props[i].set == false || (parent == NULL && - state.props[i].inherit == true)) { - error = set_initial(&state, i, parent); - if (error != CSS_OK) - goto cleanup; + /* If the property is still unset or we're not selecting + * for a pseudo element and it's set to inherit and + * we're the root element, then set it to its initial + * value. */ + if (prop->set == false || + (j == CSS_PSEUDO_ELEMENT_NONE && + parent == NULL && + prop->inherit == true)) { + state.current_pseudo = j; + state.computed = state.results->styles[j]; + + error = set_initial(&state, i, j, parent); + if (error != CSS_OK) + goto cleanup; + } } } @@ -385,14 +423,25 @@ css_error css_select_style(css_select_ctx *ctx, void *node, * computed, and the default border-{top,right,bottom,left}-color * is set to the computed value of color. */ if (parent == NULL) { - error = compute_absolute_values(NULL, result, + /* Only compute absolute values for the base element */ + error = compute_absolute_values(NULL, + state.results->styles[CSS_PSEUDO_ELEMENT_NONE], handler->compute_font_size, pw); if (error != CSS_OK) goto cleanup; } + *result = state.results; error = CSS_OK; + cleanup: + /* Only clean up the results if there's an error. + * If there is no error, we're going to pass ownership of + * the results to the client */ + if (error != CSS_OK && state.results != NULL) { + css_select_results_destroy(state.results); + } + if (ctx->n_sheets > 0 && ctx->sheets[0].sheet != NULL) { if (state.universal != NULL) lwc_string_unref(state.universal); @@ -417,14 +466,40 @@ cleanup: if (state.after != NULL) lwc_string_unref(state.after); } + return error; } +/** + * Destroy a selection result set + * + * \param results Result set to destroy + * \return CSS_OK on success, appropriate error otherwise + */ +css_error css_select_results_destroy(css_select_results *results) +{ + uint32_t i; + + if (results == NULL) + return CSS_BADPARM; + + if (results->styles != NULL) { + for (i = 0; i < CSS_PSEUDO_ELEMENT_COUNT; i++) { + if (results->styles[i] != NULL) + css_computed_style_destroy(results->styles[i]); + } + } + + results->alloc(results, 0, results->pw); + + return CSS_OK; +} + /****************************************************************************** * Selection engine internals below here * ******************************************************************************/ -css_error set_hint(css_select_state *state, uint32_t i) +css_error set_hint(css_select_state *state, uint32_t prop) { css_hint hint; css_error error; @@ -434,35 +509,40 @@ css_error set_hint(css_select_state *state, uint32_t i) /* Retrieve this property's hint from the client */ error = state->handler->node_presentational_hint(state->pw, - state->node, i, &hint); + state->node, prop, &hint); if (error != CSS_OK) return (error == CSS_PROPERTY_NOT_SET) ? CSS_OK : error; /* Hint defined -- set it in the result */ - error = prop_dispatch[i].set_from_hint(&hint, state->result); + error = prop_dispatch[prop].set_from_hint(&hint, state->computed); if (error != CSS_OK) return error; /* Keep selection state in sync with reality */ - state->props[i].set = 1; - state->props[i].specificity = 0; - state->props[i].origin = CSS_ORIGIN_AUTHOR; - state->props[i].important = 0; - state->props[i].inherit = (hint.status == 0); + state->props[prop][CSS_PSEUDO_ELEMENT_NONE].set = 1; + state->props[prop][CSS_PSEUDO_ELEMENT_NONE].specificity = 0; + state->props[prop][CSS_PSEUDO_ELEMENT_NONE].origin = CSS_ORIGIN_AUTHOR; + state->props[prop][CSS_PSEUDO_ELEMENT_NONE].important = 0; + state->props[prop][CSS_PSEUDO_ELEMENT_NONE].inherit = + (hint.status == 0); return CSS_OK; } -css_error set_initial(css_select_state *state, uint32_t i, void *parent) +css_error set_initial(css_select_state *state, + uint32_t prop, css_pseudo_element pseudo, + void *parent) { css_error error; /* Do nothing if this property is inherited (the default state * of a clean computed style is for everything to be set to inherit) * - * If the node is tree root, everything should be defaulted. + * If the node is tree root and we're dealing with the base element, + * everything should be defaulted. */ - if (prop_dispatch[i].inherited == false || parent == NULL) { + if (prop_dispatch[prop].inherited == false || + (pseudo == CSS_PSEUDO_ELEMENT_NONE && parent == NULL)) { /* Remaining properties are neither inherited nor * already set. Thus, we set them to their initial * values here. Except, however, if the property in @@ -472,23 +552,23 @@ css_error set_initial(css_select_state *state, uint32_t i, void *parent) * accessors to return the initial values for the * property. */ - if (prop_dispatch[i].group == GROUP_NORMAL) { - error = prop_dispatch[i].initial(state); + if (prop_dispatch[prop].group == GROUP_NORMAL) { + error = prop_dispatch[prop].initial(state); if (error != CSS_OK) return error; - } else if (prop_dispatch[i].group == GROUP_UNCOMMON && - state->result->uncommon != NULL) { - error = prop_dispatch[i].initial(state); + } else if (prop_dispatch[prop].group == GROUP_UNCOMMON && + state->computed->uncommon != NULL) { + error = prop_dispatch[prop].initial(state); if (error != CSS_OK) return error; - } else if (prop_dispatch[i].group == GROUP_PAGE && - state->result->page != NULL) { - error = prop_dispatch[i].initial(state); + } else if (prop_dispatch[prop].group == GROUP_PAGE && + state->computed->page != NULL) { + error = prop_dispatch[prop].initial(state); if (error != CSS_OK) return error; - } else if (prop_dispatch[i].group == GROUP_AURAL && - state->result->aural != NULL) { - error = prop_dispatch[i].initial(state); + } else if (prop_dispatch[prop].group == GROUP_AURAL && + state->computed->aural != NULL) { + error = prop_dispatch[prop].initial(state); if (error != CSS_OK) return error; } @@ -541,6 +621,8 @@ css_error select_from_sheet(css_select_ctx *ctx, const css_stylesheet *sheet, state->sheet = s; state->current_origin = origin; + /** \todo This can be hoisted into css_select_style: + * the lwc context is global now */ error = intern_strings_for_sheet(ctx, s, state); if (error != CSS_OK) return error; @@ -882,6 +964,9 @@ css_error match_selector_chain(css_select_ctx *ctx, { const css_selector *s = selector; void *node = state->node; + const css_selector_detail *detail = &s->data; + bool match = false; + css_pseudo_element pseudo; css_error error; #ifdef DEBUG_CHAIN_MATCHING @@ -890,31 +975,24 @@ css_error match_selector_chain(css_select_ctx *ctx, fprintf(stderr, "\n"); #endif - do { - void *next_node = NULL; - const css_selector_detail *detail = &s->data; - bool match = false; - - /* If this is the first selector in the chain, we must match - * its details. The details of subsequent selectors will be - * matched when processing the combinator. - * - * Note that pseudo elements will only appear as details of - * the first selector in the chain, as the parser will reject - * any selector chains containing pseudo elements anywhere - * else. - */ - if (s == selector) { - /* Match details on this selector */ - error = match_details(ctx, node, detail, state, &match); - if (error != CSS_OK) - return error; + /* Match the details of the first selector in the chain. + * + * Note that pseudo elements will only appear as details of + * the first selector in the chain, as the parser will reject + * any selector chains containing pseudo elements anywhere + * else. + */ + error = match_details(ctx, node, detail, state, &match, &pseudo); + if (error != CSS_OK) + return error; - /* Details don't match, so reject selector chain */ - if (match == false) - return CSS_OK; + /* Details don't match, so reject selector chain */ + if (match == false) + return CSS_OK; - } + /* Iterate up the selector chain, matching combinators */ + do { + void *next_node = NULL; /* Consider any combinator on this selector */ if (s->data.comb != CSS_COMBINATOR_NONE && @@ -953,6 +1031,17 @@ css_error match_selector_chain(css_select_ctx *ctx, if (((css_rule_selector *) selector->rule)->style == NULL) return CSS_OK; + /* Ensure that the appropriate computed style exists */ + if (state->results->styles[pseudo] == NULL) { + error = css_computed_style_create(ctx->alloc, ctx->pw, + &state->results->styles[pseudo]); + if (error != CSS_OK) + return error; + } + + state->current_pseudo = pseudo; + state->computed = state->results->styles[pseudo]; + return cascade_style(((css_rule_selector *) selector->rule)->style, state); } @@ -994,7 +1083,8 @@ css_error match_named_combinator(css_select_ctx *ctx, css_combinator type, if (n != NULL) { /* Match its details */ - error = match_details(ctx, n, detail, state, &match); + error = match_details(ctx, n, detail, state, + &match, NULL); if (error != CSS_OK) return error; @@ -1046,7 +1136,8 @@ css_error match_universal_combinator(css_select_ctx *ctx, css_combinator type, if (n != NULL) { /* Match its details */ - error = match_details(ctx, n, detail, state, &match); + error = match_details(ctx, n, detail, state, + &match, NULL); if (error != CSS_OK) return error; @@ -1070,10 +1161,10 @@ css_error match_universal_combinator(css_select_ctx *ctx, css_combinator type, css_error match_details(css_select_ctx *ctx, void *node, const css_selector_detail *detail, css_select_state *state, - bool *match) + bool *match, css_pseudo_element *pseudo_element) { css_error error; - bool match_pseudo_element = false; + css_pseudo_element pseudo = CSS_PSEUDO_ELEMENT_NONE; /* We match by default (if there are no details than the element * selector, then we must match) */ @@ -1086,8 +1177,7 @@ css_error match_details(css_select_ctx *ctx, void *node, * can be avoided unless absolutely necessary)? */ do { - error = match_detail(ctx, node, detail, state, match, - &match_pseudo_element); + error = match_detail(ctx, node, detail, state, match, &pseudo); if (error != CSS_OK) return error; @@ -1101,19 +1191,16 @@ css_error match_details(css_select_ctx *ctx, void *node, detail = NULL; } while (detail != NULL); - /* If we're selecting for a pseudo element and the selector chain did - * not match the pseudo element, it's not a match; reject the selector - * chain */ - if (state->pseudo_element != CSS_PSEUDO_ELEMENT_NONE && - match_pseudo_element == false) - *match = false; + /* Return the applicable pseudo element, if required */ + if (pseudo_element != NULL) + *pseudo_element = pseudo; return CSS_OK; } css_error match_detail(css_select_ctx *ctx, void *node, const css_selector_detail *detail, css_select_state *state, - bool *match, bool *match_pseudo_element) + bool *match, css_pseudo_element *pseudo_element) { css_error error = CSS_OK; @@ -1160,26 +1247,16 @@ css_error match_detail(css_select_ctx *ctx, void *node, *match = false; break; case CSS_SELECTOR_PSEUDO_ELEMENT: - if (detail->name == state->first_line && - state->pseudo_element == - CSS_PSEUDO_ELEMENT_FIRST_LINE) { - *match = true; - *match_pseudo_element = true; - } else if (detail->name == state->first_letter && - state->pseudo_element == - CSS_PSEUDO_ELEMENT_FIRST_LETTER) { - *match = true; - *match_pseudo_element = true; - } else if (detail->name == state->before && - state->pseudo_element == - CSS_PSEUDO_ELEMENT_BEFORE) { - *match = true; - *match_pseudo_element = true; - } else if (detail->name == state->after && - state->pseudo_element == - CSS_PSEUDO_ELEMENT_AFTER) { - *match = true; - *match_pseudo_element = true; + *match = true; + + if (detail->name == state->first_line) { + *pseudo_element = CSS_PSEUDO_ELEMENT_FIRST_LINE; + } else if (detail->name == state->first_letter) { + *pseudo_element = CSS_PSEUDO_ELEMENT_FIRST_LETTER; + } else if (detail->name == state->before) { + *pseudo_element = CSS_PSEUDO_ELEMENT_BEFORE; + } else if (detail->name == state->after) { + *pseudo_element = CSS_PSEUDO_ELEMENT_AFTER; } else *match = false; break; @@ -1228,7 +1305,7 @@ css_error cascade_style(const css_style *style, css_select_state *state) bool outranks_existing(uint16_t op, bool important, css_select_state *state, bool inherit) { - prop_state *existing = &state->props[op]; + prop_state *existing = &state->props[op][state->current_pseudo]; bool outranks = false; /* Sorting on origin & importance gives the following: -- cgit v1.2.3