summaryrefslogtreecommitdiff
path: root/content/handlers/css/select.c
diff options
context:
space:
mode:
Diffstat (limited to 'content/handlers/css/select.c')
-rw-r--r--content/handlers/css/select.c1854
1 files changed, 1854 insertions, 0 deletions
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 <jmb@netsurf-browser.org>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+#include <string.h>
+#include <strings.h>
+
+#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(&params, &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;
+}