From 6722943b81c0dba84ed187b2d117cc92972117ed Mon Sep 17 00:00:00 2001 From: Vincent Sanders Date: Thu, 26 May 2016 11:18:41 +0100 Subject: move the CSS content handler --- content/handlers/css/select.c | 1854 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1854 insertions(+) create mode 100644 content/handlers/css/select.c (limited to 'content/handlers/css/select.c') diff --git a/content/handlers/css/select.c b/content/handlers/css/select.c new file mode 100644 index 000000000..ea324e773 --- /dev/null +++ b/content/handlers/css/select.c @@ -0,0 +1,1854 @@ +/* + * Copyright 2009 John-Mark Bell + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include + +#include "content/urldb.h" +#include "desktop/system_colour.h" +#include "utils/nsoption.h" +#include "utils/corestrings.h" +#include "utils/log.h" + +#include "internal.h" +#include "hints.h" +#include "select.h" + +static css_error node_name(void *pw, void *node, css_qname *qname); +static css_error node_classes(void *pw, void *node, + lwc_string ***classes, uint32_t *n_classes); +static css_error node_id(void *pw, void *node, lwc_string **id); +static css_error named_parent_node(void *pw, void *node, + const css_qname *qname, void **parent); +static css_error named_sibling_node(void *pw, void *node, + const css_qname *qname, void **sibling); +static css_error named_generic_sibling_node(void *pw, void *node, + const css_qname *qname, void **sibling); +static css_error parent_node(void *pw, void *node, void **parent); +static css_error sibling_node(void *pw, void *node, void **sibling); +static css_error node_has_name(void *pw, void *node, + const css_qname *qname, bool *match); +static css_error node_has_class(void *pw, void *node, + lwc_string *name, bool *match); +static css_error node_has_id(void *pw, void *node, + lwc_string *name, bool *match); +static css_error node_has_attribute(void *pw, void *node, + const css_qname *qname, bool *match); +static css_error node_has_attribute_equal(void *pw, void *node, + const css_qname *qname, lwc_string *value, + bool *match); +static css_error node_has_attribute_dashmatch(void *pw, void *node, + const css_qname *qname, lwc_string *value, + bool *match); +static css_error node_has_attribute_includes(void *pw, void *node, + const css_qname *qname, lwc_string *value, + bool *match); +static css_error node_has_attribute_prefix(void *pw, void *node, + const css_qname *qname, lwc_string *value, + bool *match); +static css_error node_has_attribute_suffix(void *pw, void *node, + const css_qname *qname, lwc_string *value, + bool *match); +static css_error node_has_attribute_substring(void *pw, void *node, + const css_qname *qname, lwc_string *value, + bool *match); +static css_error node_is_root(void *pw, void *node, bool *match); +static css_error node_count_siblings(void *pw, void *node, + bool same_name, bool after, int32_t *count); +static css_error node_is_empty(void *pw, void *node, bool *match); +static css_error node_is_link(void *pw, void *node, bool *match); +static css_error node_is_hover(void *pw, void *node, bool *match); +static css_error node_is_active(void *pw, void *node, bool *match); +static css_error node_is_focus(void *pw, void *node, bool *match); +static css_error node_is_enabled(void *pw, void *node, bool *match); +static css_error node_is_disabled(void *pw, void *node, bool *match); +static css_error node_is_checked(void *pw, void *node, bool *match); +static css_error node_is_target(void *pw, void *node, bool *match); +static css_error node_is_lang(void *pw, void *node, + lwc_string *lang, bool *match); +static css_error ua_default_for_property(void *pw, uint32_t property, + css_hint *hint); +static css_error set_libcss_node_data(void *pw, void *node, + void *libcss_node_data); +static css_error get_libcss_node_data(void *pw, void *node, + void **libcss_node_data); + +static css_error nscss_compute_font_size(void *pw, const css_hint *parent, + css_hint *size); + + +/** + * Selection callback table for libcss + */ +static css_select_handler selection_handler = { + CSS_SELECT_HANDLER_VERSION_1, + + node_name, + node_classes, + node_id, + named_ancestor_node, + named_parent_node, + named_sibling_node, + named_generic_sibling_node, + parent_node, + sibling_node, + node_has_name, + node_has_class, + node_has_id, + node_has_attribute, + node_has_attribute_equal, + node_has_attribute_dashmatch, + node_has_attribute_includes, + node_has_attribute_prefix, + node_has_attribute_suffix, + node_has_attribute_substring, + node_is_root, + node_count_siblings, + node_is_empty, + node_is_link, + node_is_visited, + node_is_hover, + node_is_active, + node_is_focus, + node_is_enabled, + node_is_disabled, + node_is_checked, + node_is_target, + node_is_lang, + node_presentational_hint, + ua_default_for_property, + nscss_compute_font_size, + set_libcss_node_data, + get_libcss_node_data +}; + +/** + * Create an inline style + * + * \param data Source data + * \param len Length of data in bytes + * \param charset Charset of data, or NULL if unknown + * \param url Base URL of document containing data + * \param allow_quirks True to permit CSS parsing quirks + * \return Pointer to stylesheet, or NULL on failure. + */ +css_stylesheet *nscss_create_inline_style(const uint8_t *data, size_t len, + const char *charset, const char *url, bool allow_quirks) +{ + css_stylesheet_params params; + css_stylesheet *sheet; + css_error error; + + params.params_version = CSS_STYLESHEET_PARAMS_VERSION_1; + params.level = CSS_LEVEL_DEFAULT; + params.charset = charset; + params.url = url; + params.title = NULL; + params.allow_quirks = allow_quirks; + params.inline_style = true; + params.resolve = nscss_resolve_url; + params.resolve_pw = NULL; + params.import = NULL; + params.import_pw = NULL; + params.color = ns_system_colour; + params.color_pw = NULL; + params.font = NULL; + params.font_pw = NULL; + + error = css_stylesheet_create(¶ms, &sheet); + if (error != CSS_OK) { + LOG("Failed creating sheet: %d", error); + return NULL; + } + + error = css_stylesheet_append_data(sheet, data, len); + if (error != CSS_OK && error != CSS_NEEDDATA) { + LOG("failed appending data: %d", error); + css_stylesheet_destroy(sheet); + return NULL; + } + + error = css_stylesheet_data_done(sheet); + if (error != CSS_OK) { + LOG("failed completing parse: %d", error); + css_stylesheet_destroy(sheet); + return NULL; + } + + return sheet; +} + +/* Handler for libcss_node_data, stored as libdom node user data */ +static void nscss_dom_user_data_handler(dom_node_operation operation, + dom_string *key, void *data, struct dom_node *src, + struct dom_node *dst) +{ + css_error error; + + if (dom_string_isequal(corestring_dom___ns_key_libcss_node_data, + key) == false || data == NULL) { + return; + } + + switch (operation) { + case DOM_NODE_CLONED: + error = css_libcss_node_data_handler(&selection_handler, + CSS_NODE_CLONED, + NULL, src, dst, data); + if (error != CSS_OK) + LOG("Failed to clone libcss_node_data."); + break; + + case DOM_NODE_RENAMED: + error = css_libcss_node_data_handler(&selection_handler, + CSS_NODE_MODIFIED, + NULL, src, NULL, data); + if (error != CSS_OK) + LOG("Failed to update libcss_node_data."); + break; + + case DOM_NODE_IMPORTED: + case DOM_NODE_ADOPTED: + case DOM_NODE_DELETED: + error = css_libcss_node_data_handler(&selection_handler, + CSS_NODE_DELETED, + NULL, src, NULL, data); + if (error != CSS_OK) + LOG("Failed to delete libcss_node_data."); + break; + + default: + LOG("User data operation not handled."); + assert(0); + } +} + +/** + * Get style selection results for an element + * + * \param ctx CSS selection context + * \param n Element to select for + * \param media Permitted media types + * \param inline_style Inline style associated with element, or NULL + * \return Pointer to selection results (containing computed styles), + * or NULL on failure + */ +css_select_results *nscss_get_style(nscss_select_ctx *ctx, dom_node *n, + uint64_t media, const css_stylesheet *inline_style) +{ + css_select_results *styles; + int pseudo_element; + css_error error; + + /* Select style for node */ + error = css_select_style(ctx->ctx, n, media, inline_style, + &selection_handler, ctx, &styles); + + if (error != CSS_OK || styles == NULL) { + /* Failed selecting partial style -- bail out */ + return NULL; + } + + /* If there's a parent style, compose with partial to obtain + * complete computed style for element */ + if (ctx->parent_style != NULL) { + /* Complete the computed style, by composing with the parent + * element's style */ + error = css_computed_style_compose(ctx->parent_style, + styles->styles[CSS_PSEUDO_ELEMENT_NONE], + nscss_compute_font_size, NULL, + styles->styles[CSS_PSEUDO_ELEMENT_NONE]); + if (error != CSS_OK) { + css_select_results_destroy(styles); + return NULL; + } + } + + for (pseudo_element = CSS_PSEUDO_ELEMENT_NONE + 1; + pseudo_element < CSS_PSEUDO_ELEMENT_COUNT; + pseudo_element++) { + + if (pseudo_element == CSS_PSEUDO_ELEMENT_FIRST_LETTER || + pseudo_element == CSS_PSEUDO_ELEMENT_FIRST_LINE) + /* TODO: Handle first-line and first-letter pseudo + * element computed style completion */ + continue; + + if (styles->styles[pseudo_element] == NULL) + /* There were no rules concerning this pseudo element */ + continue; + + /* Complete the pseudo element's computed style, by composing + * with the base element's style */ + error = css_computed_style_compose( + styles->styles[CSS_PSEUDO_ELEMENT_NONE], + styles->styles[pseudo_element], + nscss_compute_font_size, NULL, + styles->styles[pseudo_element]); + if (error != CSS_OK) { + /* TODO: perhaps this shouldn't be quite so + * catastrophic? */ + css_select_results_destroy(styles); + return NULL; + } + } + + return styles; +} + +/** + * Get an initial style + * + * \param ctx CSS selection context + * \return Pointer to partial computed style, or NULL on failure + */ +static css_computed_style *nscss_get_initial_style(nscss_select_ctx *ctx) +{ + css_computed_style *style; + css_error error; + + error = css_computed_style_create(&style); + if (error != CSS_OK) + return NULL; + + error = css_computed_style_initialise(style, &selection_handler, ctx); + if (error != CSS_OK) { + css_computed_style_destroy(style); + return NULL; + } + + return style; +} + +/** + * Get a blank style + * + * \param ctx CSS selection context + * \param parent Parent style to cascade inherited properties from + * \return Pointer to blank style, or NULL on failure + */ +css_computed_style *nscss_get_blank_style(nscss_select_ctx *ctx, + const css_computed_style *parent) +{ + css_computed_style *partial; + css_error error; + + partial = nscss_get_initial_style(ctx); + if (partial == NULL) + return NULL; + + error = css_computed_style_compose(parent, partial, + nscss_compute_font_size, NULL, partial); + if (error != CSS_OK) { + css_computed_style_destroy(partial); + return NULL; + } + + return partial; +} + +/** + * Font size computation callback for libcss + * + * \param pw Computation context + * \param parent Parent font size (absolute) + * \param size Font size to compute + * \return CSS_OK on success + * + * \post \a size will be an absolute font size + */ +css_error nscss_compute_font_size(void *pw, const css_hint *parent, + css_hint *size) +{ + /** + * Table of font-size keyword scale factors + * + * These are multiplied by the configured default font size + * to produce an absolute size for the relevant keyword + */ + static const css_fixed factors[] = { + FLTTOFIX(0.5625), /* xx-small */ + FLTTOFIX(0.6250), /* x-small */ + FLTTOFIX(0.8125), /* small */ + FLTTOFIX(1.0000), /* medium */ + FLTTOFIX(1.1250), /* large */ + FLTTOFIX(1.5000), /* x-large */ + FLTTOFIX(2.0000) /* xx-large */ + }; + css_hint_length parent_size; + + /* Grab parent size, defaulting to medium if none */ + if (parent == NULL) { + parent_size.value = FDIV(FMUL(factors[CSS_FONT_SIZE_MEDIUM - 1], + INTTOFIX(nsoption_int(font_size))), + INTTOFIX(10)); + parent_size.unit = CSS_UNIT_PT; + } else { + assert(parent->status == CSS_FONT_SIZE_DIMENSION); + assert(parent->data.length.unit != CSS_UNIT_EM); + assert(parent->data.length.unit != CSS_UNIT_EX); + assert(parent->data.length.unit != CSS_UNIT_PCT); + + parent_size = parent->data.length; + } + + assert(size->status != CSS_FONT_SIZE_INHERIT); + + if (size->status < CSS_FONT_SIZE_LARGER) { + /* Keyword -- simple */ + size->data.length.value = FDIV(FMUL(factors[size->status - 1], + INTTOFIX(nsoption_int(font_size))), F_10); + size->data.length.unit = CSS_UNIT_PT; + } else if (size->status == CSS_FONT_SIZE_LARGER) { + /** \todo Step within table, if appropriate */ + size->data.length.value = + FMUL(parent_size.value, FLTTOFIX(1.2)); + size->data.length.unit = parent_size.unit; + } else if (size->status == CSS_FONT_SIZE_SMALLER) { + /** \todo Step within table, if appropriate */ + size->data.length.value = + FDIV(parent_size.value, FLTTOFIX(1.2)); + size->data.length.unit = parent_size.unit; + } else if (size->data.length.unit == CSS_UNIT_EM || + size->data.length.unit == CSS_UNIT_EX) { + size->data.length.value = + FMUL(size->data.length.value, parent_size.value); + + if (size->data.length.unit == CSS_UNIT_EX) { + /* 1ex = 0.6em in NetSurf */ + size->data.length.value = FMUL(size->data.length.value, + FLTTOFIX(0.6)); + } + + size->data.length.unit = parent_size.unit; + } else if (size->data.length.unit == CSS_UNIT_PCT) { + size->data.length.value = FDIV(FMUL(size->data.length.value, + parent_size.value), INTTOFIX(100)); + size->data.length.unit = parent_size.unit; + } + + size->status = CSS_FONT_SIZE_DIMENSION; + + return CSS_OK; +} + +/****************************************************************************** + * Style selection callbacks * + ******************************************************************************/ + +/** + * Callback to retrieve a node's name. + * + * \param pw HTML document + * \param node DOM node + * \param qname Pointer to location to receive node name + * \return CSS_OK on success, + * CSS_NOMEM on memory exhaustion. + */ +css_error node_name(void *pw, void *node, css_qname *qname) +{ + dom_node *n = node; + dom_string *name; + dom_exception err; + + err = dom_node_get_node_name(n, &name); + if (err != DOM_NO_ERR) + return CSS_NOMEM; + + qname->ns = NULL; + + err = dom_string_intern(name, &qname->name); + if (err != DOM_NO_ERR) { + dom_string_unref(name); + return CSS_NOMEM; + } + + dom_string_unref(name); + + return CSS_OK; +} + +/** + * Callback to retrieve a node's classes. + * + * \param pw HTML document + * \param node DOM node + * \param classes Pointer to location to receive class name array + * \param n_classes Pointer to location to receive length of class name array + * \return CSS_OK on success, + * CSS_NOMEM on memory exhaustion. + * + * \note The returned array will be destroyed by libcss. Therefore, it must + * be allocated using the same allocator as used by libcss during style + * selection. + */ +css_error node_classes(void *pw, void *node, + lwc_string ***classes, uint32_t *n_classes) +{ + dom_node *n = node; + dom_exception err; + + *classes = NULL; + *n_classes = 0; + + err = dom_element_get_classes(n, classes, n_classes); + if (err != DOM_NO_ERR) + return CSS_NOMEM; + + return CSS_OK; +} + +/** + * Callback to retrieve a node's ID. + * + * \param pw HTML document + * \param node DOM node + * \param id Pointer to location to receive id value + * \return CSS_OK on success, + * CSS_NOMEM on memory exhaustion. + */ +css_error node_id(void *pw, void *node, lwc_string **id) +{ + dom_node *n = node; + dom_string *attr; + dom_exception err; + + *id = NULL; + + /** \todo Assumes an HTML DOM */ + err = dom_html_element_get_id(n, &attr); + if (err != DOM_NO_ERR) + return CSS_NOMEM; + + if (attr != NULL) { + err = dom_string_intern(attr, id); + if (err != DOM_NO_ERR) { + dom_string_unref(attr); + return CSS_NOMEM; + } + dom_string_unref(attr); + } + + return CSS_OK; +} + +/** + * Callback to find a named ancestor node. + * + * \param pw HTML document + * \param node DOM node + * \param qname Node name to search for + * \param ancestor Pointer to location to receive ancestor + * \return CSS_OK. + * + * \post \a ancestor will contain the result, or NULL if there is no match + */ +css_error named_ancestor_node(void *pw, void *node, + const css_qname *qname, void **ancestor) +{ + dom_element_named_ancestor_node(node, qname->name, + (struct dom_element **)ancestor); + + return CSS_OK; +} + +/** + * Callback to find a named parent node + * + * \param pw HTML document + * \param node DOM node + * \param qname Node name to search for + * \param parent Pointer to location to receive parent + * \return CSS_OK. + * + * \post \a parent will contain the result, or NULL if there is no match + */ +css_error named_parent_node(void *pw, void *node, + const css_qname *qname, void **parent) +{ + dom_element_named_parent_node(node, qname->name, + (struct dom_element **)parent); + + return CSS_OK; +} + +/** + * Callback to find a named sibling node. + * + * \param pw HTML document + * \param node DOM node + * \param qname Node name to search for + * \param sibling Pointer to location to receive sibling + * \return CSS_OK. + * + * \post \a sibling will contain the result, or NULL if there is no match + */ +css_error named_sibling_node(void *pw, void *node, + const css_qname *qname, void **sibling) +{ + dom_node *n = node; + dom_node *prev; + dom_exception err; + + *sibling = NULL; + + /* Find sibling element */ + err = dom_node_get_previous_sibling(n, &n); + if (err != DOM_NO_ERR) + return CSS_OK; + + while (n != NULL) { + dom_node_type type; + + err = dom_node_get_node_type(n, &type); + if (err != DOM_NO_ERR) { + dom_node_unref(n); + return CSS_OK; + } + + if (type == DOM_ELEMENT_NODE) + break; + + err = dom_node_get_previous_sibling(n, &prev); + if (err != DOM_NO_ERR) { + dom_node_unref(n); + return CSS_OK; + } + + dom_node_unref(n); + n = prev; + } + + if (n != NULL) { + dom_string *name; + + err = dom_node_get_node_name(n, &name); + if (err != DOM_NO_ERR) { + dom_node_unref(n); + return CSS_OK; + } + + dom_node_unref(n); + + if (dom_string_caseless_lwc_isequal(name, qname->name)) { + *sibling = n; + } + + dom_string_unref(name); + } + + return CSS_OK; +} + +/** + * Callback to find a named generic sibling node. + * + * \param pw HTML document + * \param node DOM node + * \param qname Node name to search for + * \param sibling Pointer to location to receive ancestor + * \return CSS_OK. + * + * \post \a sibling will contain the result, or NULL if there is no match + */ +css_error named_generic_sibling_node(void *pw, void *node, + const css_qname *qname, void **sibling) +{ + dom_node *n = node; + dom_node *prev; + dom_exception err; + + *sibling = NULL; + + err = dom_node_get_previous_sibling(n, &n); + if (err != DOM_NO_ERR) + return CSS_OK; + + while (n != NULL) { + dom_node_type type; + dom_string *name; + + err = dom_node_get_node_type(n, &type); + if (err != DOM_NO_ERR) { + dom_node_unref(n); + return CSS_OK; + } + + if (type == DOM_ELEMENT_NODE) { + err = dom_node_get_node_name(n, &name); + if (err != DOM_NO_ERR) { + dom_node_unref(n); + return CSS_OK; + } + + if (dom_string_caseless_lwc_isequal(name, + qname->name)) { + dom_string_unref(name); + dom_node_unref(n); + *sibling = n; + break; + } + dom_string_unref(name); + } + + err = dom_node_get_previous_sibling(n, &prev); + if (err != DOM_NO_ERR) { + dom_node_unref(n); + return CSS_OK; + } + + dom_node_unref(n); + n = prev; + } + + return CSS_OK; +} + +/** + * Callback to retrieve the parent of a node. + * + * \param pw HTML document + * \param node DOM node + * \param parent Pointer to location to receive parent + * \return CSS_OK. + * + * \post \a parent will contain the result, or NULL if there is no match + */ +css_error parent_node(void *pw, void *node, void **parent) +{ + dom_element_parent_node(node, (struct dom_element **)parent); + + return CSS_OK; +} + +/** + * Callback to retrieve the preceding sibling of a node. + * + * \param pw HTML document + * \param node DOM node + * \param sibling Pointer to location to receive sibling + * \return CSS_OK. + * + * \post \a sibling will contain the result, or NULL if there is no match + */ +css_error sibling_node(void *pw, void *node, void **sibling) +{ + dom_node *n = node; + dom_node *prev; + dom_exception err; + + *sibling = NULL; + + /* Find sibling element */ + err = dom_node_get_previous_sibling(n, &n); + if (err != DOM_NO_ERR) + return CSS_OK; + + while (n != NULL) { + dom_node_type type; + + err = dom_node_get_node_type(n, &type); + if (err != DOM_NO_ERR) { + dom_node_unref(n); + return CSS_OK; + } + + if (type == DOM_ELEMENT_NODE) + break; + + err = dom_node_get_previous_sibling(n, &prev); + if (err != DOM_NO_ERR) { + dom_node_unref(n); + return CSS_OK; + } + + dom_node_unref(n); + n = prev; + } + + if (n != NULL) { + /** \todo Sort out reference counting */ + dom_node_unref(n); + + *sibling = n; + } + + return CSS_OK; +} + +/** + * Callback to determine if a node has the given name. + * + * \param pw HTML document + * \param node DOM node + * \param qname Name to match + * \param match Pointer to location to receive result + * \return CSS_OK. + * + * \post \a match will contain true if the node matches and false otherwise. + */ +css_error node_has_name(void *pw, void *node, + const css_qname *qname, bool *match) +{ + nscss_select_ctx *ctx = pw; + dom_node *n = node; + + if (lwc_string_isequal(qname->name, ctx->universal, match) == + lwc_error_ok && *match == false) { + dom_string *name; + dom_exception err; + + err = dom_node_get_node_name(n, &name); + if (err != DOM_NO_ERR) + return CSS_OK; + + /* Element names are case insensitive in HTML */ + *match = dom_string_caseless_lwc_isequal(name, qname->name); + + dom_string_unref(name); + } + + return CSS_OK; +} + +/** + * Callback to determine if a node has the given class. + * + * \param pw HTML document + * \param node DOM node + * \param name Name to match + * \param match Pointer to location to receive result + * \return CSS_OK. + * + * \post \a match will contain true if the node matches and false otherwise. + */ +css_error node_has_class(void *pw, void *node, + lwc_string *name, bool *match) +{ + dom_node *n = node; + dom_exception err; + + /** \todo: Ensure that libdom performs case-insensitive + * matching in quirks mode */ + err = dom_element_has_class(n, name, match); + + assert(err == DOM_NO_ERR); + + return CSS_OK; +} + +/** + * Callback to determine if a node has the given id. + * + * \param pw HTML document + * \param node DOM node + * \param name Name to match + * \param match Pointer to location to receive result + * \return CSS_OK. + * + * \post \a match will contain true if the node matches and false otherwise. + */ +css_error node_has_id(void *pw, void *node, + lwc_string *name, bool *match) +{ + dom_node *n = node; + dom_string *attr; + dom_exception err; + + *match = false; + + /** \todo Assumes an HTML DOM */ + err = dom_html_element_get_id(n, &attr); + if (err != DOM_NO_ERR) + return CSS_OK; + + if (attr != NULL) { + *match = dom_string_lwc_isequal(attr, name); + + dom_string_unref(attr); + } + + return CSS_OK; +} + +/** + * Callback to determine if a node has an attribute with the given name. + * + * \param pw HTML document + * \param node DOM node + * \param qname Name to match + * \param match Pointer to location to receive result + * \return CSS_OK on success, + * CSS_NOMEM on memory exhaustion. + * + * \post \a match will contain true if the node matches and false otherwise. + */ +css_error node_has_attribute(void *pw, void *node, + const css_qname *qname, bool *match) +{ + dom_node *n = node; + dom_string *name; + dom_exception err; + + err = dom_string_create_interned( + (const uint8_t *) lwc_string_data(qname->name), + lwc_string_length(qname->name), &name); + if (err != DOM_NO_ERR) + return CSS_NOMEM; + + err = dom_element_has_attribute(n, name, match); + if (err != DOM_NO_ERR) { + dom_string_unref(name); + return CSS_OK; + } + + dom_string_unref(name); + + return CSS_OK; +} + +/** + * Callback to determine if a node has an attribute with given name and value. + * + * \param pw HTML document + * \param node DOM node + * \param qname Name to match + * \param value Value to match + * \param match Pointer to location to receive result + * \return CSS_OK on success, + * CSS_NOMEM on memory exhaustion. + * + * \post \a match will contain true if the node matches and false otherwise. + */ +css_error node_has_attribute_equal(void *pw, void *node, + const css_qname *qname, lwc_string *value, + bool *match) +{ + dom_node *n = node; + dom_string *name; + dom_string *atr_val; + dom_exception err; + + size_t vlen = lwc_string_length(value); + + if (vlen == 0) { + *match = false; + return CSS_OK; + } + + err = dom_string_create_interned( + (const uint8_t *) lwc_string_data(qname->name), + lwc_string_length(qname->name), &name); + if (err != DOM_NO_ERR) + return CSS_NOMEM; + + err = dom_element_get_attribute(n, name, &atr_val); + if ((err != DOM_NO_ERR) || (atr_val == NULL)) { + dom_string_unref(name); + *match = false; + return CSS_OK; + } + + dom_string_unref(name); + + *match = dom_string_caseless_lwc_isequal(atr_val, value); + + dom_string_unref(atr_val); + + return CSS_OK; +} + +/** + * Callback to determine if a node has an attribute with the given name whose + * value dashmatches that given. + * + * \param pw HTML document + * \param node DOM node + * \param qname Name to match + * \param value Value to match + * \param match Pointer to location to receive result + * \return CSS_OK on success, + * CSS_NOMEM on memory exhaustion. + * + * \post \a match will contain true if the node matches and false otherwise. + */ +css_error node_has_attribute_dashmatch(void *pw, void *node, + const css_qname *qname, lwc_string *value, + bool *match) +{ + dom_node *n = node; + dom_string *name; + dom_string *atr_val; + dom_exception err; + + size_t vlen = lwc_string_length(value); + + if (vlen == 0) { + *match = false; + return CSS_OK; + } + + err = dom_string_create_interned( + (const uint8_t *) lwc_string_data(qname->name), + lwc_string_length(qname->name), &name); + if (err != DOM_NO_ERR) + return CSS_NOMEM; + + err = dom_element_get_attribute(n, name, &atr_val); + if ((err != DOM_NO_ERR) || (atr_val == NULL)) { + dom_string_unref(name); + *match = false; + return CSS_OK; + } + + dom_string_unref(name); + + /* check for exact match */ + *match = dom_string_caseless_lwc_isequal(atr_val, value); + + /* check for dashmatch */ + if (*match == false) { + const char *vdata = lwc_string_data(value); + const char *data = (const char *) dom_string_data(atr_val); + size_t len = dom_string_byte_length(atr_val); + + if (len > vlen && data[vlen] == '-' && + strncasecmp(data, vdata, vlen) == 0) { + *match = true; + } + } + + dom_string_unref(atr_val); + + return CSS_OK; +} + +/** + * Callback to determine if a node has an attribute with the given name whose + * value includes that given. + * + * \param pw HTML document + * \param node DOM node + * \param qname Name to match + * \param value Value to match + * \param match Pointer to location to receive result + * \return CSS_OK on success, + * CSS_NOMEM on memory exhaustion. + * + * \post \a match will contain true if the node matches and false otherwise. + */ +css_error node_has_attribute_includes(void *pw, void *node, + const css_qname *qname, lwc_string *value, + bool *match) +{ + dom_node *n = node; + dom_string *name; + dom_string *atr_val; + dom_exception err; + size_t vlen = lwc_string_length(value); + const char *p; + const char *start; + const char *end; + + *match = false; + + if (vlen == 0) { + return CSS_OK; + } + + err = dom_string_create_interned( + (const uint8_t *) lwc_string_data(qname->name), + lwc_string_length(qname->name), &name); + if (err != DOM_NO_ERR) + return CSS_NOMEM; + + err = dom_element_get_attribute(n, name, &atr_val); + if ((err != DOM_NO_ERR) || (atr_val == NULL)) { + dom_string_unref(name); + *match = false; + return CSS_OK; + } + + dom_string_unref(name); + + /* check for match */ + start = (const char *) dom_string_data(atr_val); + end = start + dom_string_byte_length(atr_val); + + for (p = start; p <= end; p++) { + if (*p == ' ' || *p == '\0') { + if ((size_t) (p - start) == vlen && + strncasecmp(start, + lwc_string_data(value), + vlen) == 0) { + *match = true; + break; + } + + start = p + 1; + } + } + + dom_string_unref(atr_val); + + return CSS_OK; +} + +/** + * Callback to determine if a node has an attribute with the given name whose + * value has the prefix given. + * + * \param pw HTML document + * \param node DOM node + * \param qname Name to match + * \param value Value to match + * \param match Pointer to location to receive result + * \return CSS_OK on success, + * CSS_NOMEM on memory exhaustion. + * + * \post \a match will contain true if the node matches and false otherwise. + */ +css_error node_has_attribute_prefix(void *pw, void *node, + const css_qname *qname, lwc_string *value, + bool *match) +{ + dom_node *n = node; + dom_string *name; + dom_string *atr_val; + dom_exception err; + + size_t vlen = lwc_string_length(value); + + if (vlen == 0) { + *match = false; + return CSS_OK; + } + + err = dom_string_create_interned( + (const uint8_t *) lwc_string_data(qname->name), + lwc_string_length(qname->name), &name); + if (err != DOM_NO_ERR) + return CSS_NOMEM; + + err = dom_element_get_attribute(n, name, &atr_val); + if ((err != DOM_NO_ERR) || (atr_val == NULL)) { + dom_string_unref(name); + *match = false; + return CSS_OK; + } + + dom_string_unref(name); + + /* check for exact match */ + *match = dom_string_caseless_lwc_isequal(atr_val, value); + + /* check for prefix match */ + if (*match == false) { + const char *data = (const char *) dom_string_data(atr_val); + size_t len = dom_string_byte_length(atr_val); + + if ((len >= vlen) && + (strncasecmp(data, lwc_string_data(value), vlen) == 0)) { + *match = true; + } + } + + dom_string_unref(atr_val); + + return CSS_OK; +} + +/** + * Callback to determine if a node has an attribute with the given name whose + * value has the suffix given. + * + * \param pw HTML document + * \param node DOM node + * \param qname Name to match + * \param value Value to match + * \param match Pointer to location to receive result + * \return CSS_OK on success, + * CSS_NOMEM on memory exhaustion. + * + * \post \a match will contain true if the node matches and false otherwise. + */ +css_error node_has_attribute_suffix(void *pw, void *node, + const css_qname *qname, lwc_string *value, + bool *match) +{ + dom_node *n = node; + dom_string *name; + dom_string *atr_val; + dom_exception err; + + size_t vlen = lwc_string_length(value); + + if (vlen == 0) { + *match = false; + return CSS_OK; + } + + err = dom_string_create_interned( + (const uint8_t *) lwc_string_data(qname->name), + lwc_string_length(qname->name), &name); + if (err != DOM_NO_ERR) + return CSS_NOMEM; + + err = dom_element_get_attribute(n, name, &atr_val); + if ((err != DOM_NO_ERR) || (atr_val == NULL)) { + dom_string_unref(name); + *match = false; + return CSS_OK; + } + + dom_string_unref(name); + + /* check for exact match */ + *match = dom_string_caseless_lwc_isequal(atr_val, value); + + /* check for prefix match */ + if (*match == false) { + const char *data = (const char *) dom_string_data(atr_val); + size_t len = dom_string_byte_length(atr_val); + + const char *start = (char *) data + len - vlen; + + if ((len >= vlen) && + (strncasecmp(start, lwc_string_data(value), vlen) == 0)) { + *match = true; + } + + + } + + dom_string_unref(atr_val); + + return CSS_OK; +} + +/** + * Callback to determine if a node has an attribute with the given name whose + * value contains the substring given. + * + * \param pw HTML document + * \param node DOM node + * \param qname Name to match + * \param value Value to match + * \param match Pointer to location to receive result + * \return CSS_OK on success, + * CSS_NOMEM on memory exhaustion. + * + * \post \a match will contain true if the node matches and false otherwise. + */ +css_error node_has_attribute_substring(void *pw, void *node, + const css_qname *qname, lwc_string *value, + bool *match) +{ + dom_node *n = node; + dom_string *name; + dom_string *atr_val; + dom_exception err; + + size_t vlen = lwc_string_length(value); + + if (vlen == 0) { + *match = false; + return CSS_OK; + } + + err = dom_string_create_interned( + (const uint8_t *) lwc_string_data(qname->name), + lwc_string_length(qname->name), &name); + if (err != DOM_NO_ERR) + return CSS_NOMEM; + + err = dom_element_get_attribute(n, name, &atr_val); + if ((err != DOM_NO_ERR) || (atr_val == NULL)) { + dom_string_unref(name); + *match = false; + return CSS_OK; + } + + dom_string_unref(name); + + /* check for exact match */ + *match = dom_string_caseless_lwc_isequal(atr_val, value); + + /* check for prefix match */ + if (*match == false) { + const char *vdata = lwc_string_data(value); + const char *start = (const char *) dom_string_data(atr_val); + size_t len = dom_string_byte_length(atr_val); + const char *last_start = start + len - vlen; + + if (len >= vlen) { + while (start <= last_start) { + if (strncasecmp(start, vdata, + vlen) == 0) { + *match = true; + break; + } + + start++; + } + } + } + + dom_string_unref(atr_val); + + return CSS_OK; +} + +/** + * Callback to determine if a node is the root node of the document. + * + * \param pw HTML document + * \param node DOM node + * \param match Pointer to location to receive result + * \return CSS_OK. + * + * \post \a match will contain true if the node matches and false otherwise. + */ +css_error node_is_root(void *pw, void *node, bool *match) +{ + dom_node *n = node; + dom_node *parent; + dom_node_type type; + dom_exception err; + + err = dom_node_get_parent_node(n, &parent); + if (err != DOM_NO_ERR) { + return CSS_NOMEM; + } + + if (parent != NULL) { + err = dom_node_get_node_type(parent, &type); + + dom_node_unref(parent); + + if (err != DOM_NO_ERR) + return CSS_NOMEM; + + if (type != DOM_DOCUMENT_NODE) { + *match = false; + return CSS_OK; + } + } + + *match = true; + + return CSS_OK; +} + +static int +node_count_siblings_check(dom_node *node, + bool check_name, + dom_string *name) +{ + dom_node_type type; + int ret = 0; + dom_exception exc; + + if (node == NULL) + return 0; + + exc = dom_node_get_node_type(node, &type); + if ((exc != DOM_NO_ERR) || (type != DOM_ELEMENT_NODE)) { + return 0; + } + + if (check_name) { + dom_string *node_name = NULL; + exc = dom_node_get_node_name(node, &node_name); + + if ((exc == DOM_NO_ERR) && (node_name != NULL)) { + + if (dom_string_caseless_isequal(name, + node_name)) { + ret = 1; + } + dom_string_unref(node_name); + } + } else { + ret = 1; + } + + return ret; +} + +/** + * Callback to count a node's siblings. + * + * \param pw HTML document + * \param n DOM node + * \param same_name Only count siblings with the same name, or all + * \param after Count anteceding instead of preceding siblings + * \param count Pointer to location to receive result + * \return CSS_OK. + * + * \post \a count will contain the number of siblings + */ +css_error node_count_siblings(void *pw, void *n, bool same_name, + bool after, int32_t *count) +{ + int32_t cnt = 0; + dom_exception exc; + dom_string *node_name = NULL; + + if (same_name) { + dom_node *node = n; + exc = dom_node_get_node_name(node, &node_name); + if ((exc != DOM_NO_ERR) || (node_name == NULL)) { + return CSS_NOMEM; + } + } + + if (after) { + dom_node *node = dom_node_ref(n); + dom_node *next; + + do { + exc = dom_node_get_next_sibling(node, &next); + if ((exc != DOM_NO_ERR)) + break; + + dom_node_unref(node); + node = next; + + cnt += node_count_siblings_check(node, same_name, node_name); + } while (node != NULL); + } else { + dom_node *node = dom_node_ref(n); + dom_node *next; + + do { + exc = dom_node_get_previous_sibling(node, &next); + if ((exc != DOM_NO_ERR)) + break; + + dom_node_unref(node); + node = next; + + cnt += node_count_siblings_check(node, same_name, node_name); + + } while (node != NULL); + } + + if (node_name != NULL) { + dom_string_unref(node_name); + } + + *count = cnt; + return CSS_OK; +} + +/** + * Callback to determine if a node is empty. + * + * \param pw HTML document + * \param node DOM node + * \param match Pointer to location to receive result + * \return CSS_OK. + * + * \post \a match will contain true if the node is empty and false otherwise. + */ +css_error node_is_empty(void *pw, void *node, bool *match) +{ + dom_node *n = node, *next; + dom_exception err; + + *match = true; + + err = dom_node_get_first_child(n, &n); + if (err != DOM_NO_ERR) { + return CSS_BADPARM; + } + + while (n != NULL) { + dom_node_type ntype; + err = dom_node_get_node_type(n, &ntype); + if (err != DOM_NO_ERR) { + dom_node_unref(n); + return CSS_BADPARM; + } + + if (ntype == DOM_ELEMENT_NODE || + ntype == DOM_TEXT_NODE) { + *match = false; + dom_node_unref(n); + break; + } + + err = dom_node_get_next_sibling(n, &next); + if (err != DOM_NO_ERR) { + dom_node_unref(n); + return CSS_BADPARM; + } + dom_node_unref(n); + n = next; + } + + return CSS_OK; +} + +/** + * Callback to determine if a node is a linking element. + * + * \param pw HTML document + * \param n DOM node + * \param match Pointer to location to receive result + * \return CSS_OK. + * + * \post \a match will contain true if the node matches and false otherwise. + */ +css_error node_is_link(void *pw, void *n, bool *match) +{ + dom_node *node = n; + dom_exception exc; + dom_string *node_name = NULL; + + exc = dom_node_get_node_name(node, &node_name); + if ((exc != DOM_NO_ERR) || (node_name == NULL)) { + return CSS_NOMEM; + } + + if (dom_string_caseless_lwc_isequal(node_name, corestring_lwc_a)) { + bool has_href; + exc = dom_element_has_attribute(node, corestring_dom_href, + &has_href); + if ((exc == DOM_NO_ERR) && (has_href)) { + *match = true; + } else { + *match = false; + } + } else { + *match = false; + } + dom_string_unref(node_name); + + return CSS_OK; +} + +/** + * Callback to determine if a node is a linking element whose target has been + * visited. + * + * \param pw HTML document + * \param node DOM node + * \param match Pointer to location to receive result + * \return CSS_OK. + * + * \post \a match will contain true if the node matches and false otherwise. + */ +css_error node_is_visited(void *pw, void *node, bool *match) +{ + nscss_select_ctx *ctx = pw; + nsurl *url; + nserror error; + const struct url_data *data; + + dom_exception exc; + dom_node *n = node; + dom_string *s = NULL; + + *match = false; + + exc = dom_node_get_node_name(n, &s); + if ((exc != DOM_NO_ERR) || (s == NULL)) { + return CSS_NOMEM; + } + + if (!dom_string_caseless_lwc_isequal(s, corestring_lwc_a)) { + /* Can't be visited; not ancher element */ + dom_string_unref(s); + return CSS_OK; + } + + /* Finished with node name string */ + dom_string_unref(s); + s = NULL; + + exc = dom_element_get_attribute(n, corestring_dom_href, &s); + if ((exc != DOM_NO_ERR) || (s == NULL)) { + /* Can't be visited; not got a URL */ + return CSS_OK; + } + + /* Make href absolute */ + /* TODO: this duplicates what we do for box->href + * should we put the absolute URL on the dom node? */ + error = nsurl_join(ctx->base_url, dom_string_data(s), &url); + + /* Finished with href string */ + dom_string_unref(s); + + if (error != NSERROR_OK) { + /* Couldn't make nsurl object */ + return CSS_NOMEM; + } + + data = urldb_get_url_data(url); + + /* Visited if in the db and has + * non-zero visit count */ + if (data != NULL && data->visits > 0) + *match = true; + + nsurl_unref(url); + + return CSS_OK; +} + +/** + * Callback to determine if a node is currently being hovered over. + * + * \param pw HTML document + * \param node DOM node + * \param match Pointer to location to receive result + * \return CSS_OK. + * + * \post \a match will contain true if the node matches and false otherwise. + */ +css_error node_is_hover(void *pw, void *node, bool *match) +{ + /** \todo Support hovering */ + + *match = false; + + return CSS_OK; +} + +/** + * Callback to determine if a node is currently activated. + * + * \param pw HTML document + * \param node DOM node + * \param match Pointer to location to receive result + * \return CSS_OK. + * + * \post \a match will contain true if the node matches and false otherwise. + */ +css_error node_is_active(void *pw, void *node, bool *match) +{ + /** \todo Support active nodes */ + + *match = false; + + return CSS_OK; +} + +/** + * Callback to determine if a node has the input focus. + * + * \param pw HTML document + * \param node DOM node + * \param match Pointer to location to receive result + * \return CSS_OK. + * + * \post \a match will contain true if the node matches and false otherwise. + */ +css_error node_is_focus(void *pw, void *node, bool *match) +{ + /** \todo Support focussed nodes */ + + *match = false; + + return CSS_OK; +} + +/** + * Callback to determine if a node is enabled. + * + * \param pw HTML document + * \param node DOM node + * \param match Pointer to location to receive result + * \return CSS_OK. + * + * \post \a match with contain true if the node is enabled and false otherwise. + */ +css_error node_is_enabled(void *pw, void *node, bool *match) +{ + /** \todo Support enabled nodes */ + + *match = false; + + return CSS_OK; +} + +/** + * Callback to determine if a node is disabled. + * + * \param pw HTML document + * \param node DOM node + * \param match Pointer to location to receive result + * \return CSS_OK. + * + * \post \a match with contain true if the node is disabled and false otherwise. + */ +css_error node_is_disabled(void *pw, void *node, bool *match) +{ + /** \todo Support disabled nodes */ + + *match = false; + + return CSS_OK; +} + +/** + * Callback to determine if a node is checked. + * + * \param pw HTML document + * \param node DOM node + * \param match Pointer to location to receive result + * \return CSS_OK. + * + * \post \a match with contain true if the node is checked and false otherwise. + */ +css_error node_is_checked(void *pw, void *node, bool *match) +{ + /** \todo Support checked nodes */ + + *match = false; + + return CSS_OK; +} + +/** + * Callback to determine if a node is the target of the document URL. + * + * \param pw HTML document + * \param node DOM node + * \param match Pointer to location to receive result + * \return CSS_OK. + * + * \post \a match with contain true if the node matches and false otherwise. + */ +css_error node_is_target(void *pw, void *node, bool *match) +{ + /** \todo Support target */ + + *match = false; + + return CSS_OK; +} + +/** + * Callback to determine if a node has the given language + * + * \param pw HTML document + * \param node DOM node + * \param lang Language specifier to match + * \param match Pointer to location to receive result + * \return CSS_OK. + * + * \post \a match will contain true if the node matches and false otherwise. + */ +css_error node_is_lang(void *pw, void *node, + lwc_string *lang, bool *match) +{ + /** \todo Support languages */ + + *match = false; + + return CSS_OK; +} + +/** + * Callback to retrieve the User-Agent defaults for a CSS property. + * + * \param pw HTML document + * \param property Property to retrieve defaults for + * \param hint Pointer to hint object to populate + * \return CSS_OK on success, + * CSS_INVALID if the property should not have a user-agent default. + */ +css_error ua_default_for_property(void *pw, uint32_t property, css_hint *hint) +{ + if (property == CSS_PROP_COLOR) { + hint->data.color = 0xff000000; + hint->status = CSS_COLOR_COLOR; + } else if (property == CSS_PROP_FONT_FAMILY) { + hint->data.strings = NULL; + switch (nsoption_int(font_default)) { + case PLOT_FONT_FAMILY_SANS_SERIF: + hint->status = CSS_FONT_FAMILY_SANS_SERIF; + break; + case PLOT_FONT_FAMILY_SERIF: + hint->status = CSS_FONT_FAMILY_SERIF; + break; + case PLOT_FONT_FAMILY_MONOSPACE: + hint->status = CSS_FONT_FAMILY_MONOSPACE; + break; + case PLOT_FONT_FAMILY_CURSIVE: + hint->status = CSS_FONT_FAMILY_CURSIVE; + break; + case PLOT_FONT_FAMILY_FANTASY: + hint->status = CSS_FONT_FAMILY_FANTASY; + break; + } + } else if (property == CSS_PROP_QUOTES) { + /** \todo Not exactly useful :) */ + hint->data.strings = NULL; + hint->status = CSS_QUOTES_NONE; + } else if (property == CSS_PROP_VOICE_FAMILY) { + /** \todo Fix this when we have voice-family done */ + hint->data.strings = NULL; + hint->status = 0; + } else { + return CSS_INVALID; + } + + return CSS_OK; +} + +css_error set_libcss_node_data(void *pw, void *node, void *libcss_node_data) +{ + dom_node *n = node; + dom_exception err; + void *old_node_data; + + /* Set this node's node data */ + err = dom_node_set_user_data(n, + corestring_dom___ns_key_libcss_node_data, + libcss_node_data, nscss_dom_user_data_handler, + (void *) &old_node_data); + if (err != DOM_NO_ERR) { + return CSS_NOMEM; + } + + assert(old_node_data == NULL); + + return CSS_OK; +} + +css_error get_libcss_node_data(void *pw, void *node, void **libcss_node_data) +{ + dom_node *n = node; + dom_exception err; + + /* Get this node's node data */ + err = dom_node_get_user_data(n, + corestring_dom___ns_key_libcss_node_data, + libcss_node_data); + if (err != DOM_NO_ERR) { + return CSS_NOMEM; + } + + return CSS_OK; +} -- cgit v1.2.3