summaryrefslogtreecommitdiff
path: root/content
diff options
context:
space:
mode:
authorVincent Sanders <vince@kyllikki.org>2016-05-26 11:18:41 +0100
committerVincent Sanders <vince@kyllikki.org>2016-05-26 11:18:41 +0100
commit6722943b81c0dba84ed187b2d117cc92972117ed (patch)
tree5de3de53a92696c9a4b9edcfd54e7f608ad2fb04 /content
parentc25eb6b7e7a08d7e2aaf33994c2c24053cf47b82 (diff)
downloadnetsurf-6722943b81c0dba84ed187b2d117cc92972117ed.tar.gz
netsurf-6722943b81c0dba84ed187b2d117cc92972117ed.tar.bz2
move the CSS content handler
Diffstat (limited to 'content')
-rw-r--r--content/handlers/Makefile7
-rw-r--r--content/handlers/css/Makefile4
-rw-r--r--content/handlers/css/css.c834
-rw-r--r--content/handlers/css/css.h62
-rw-r--r--content/handlers/css/dump.c1793
-rw-r--r--content/handlers/css/dump.h30
-rw-r--r--content/handlers/css/hints.c1604
-rw-r--r--content/handlers/css/hints.h55
-rw-r--r--content/handlers/css/internal.c63
-rw-r--r--content/handlers/css/internal.h35
-rw-r--r--content/handlers/css/select.c1854
-rw-r--r--content/handlers/css/select.h58
-rw-r--r--content/handlers/css/utils.c127
-rw-r--r--content/handlers/css/utils.h49
-rw-r--r--content/handlers/image/Makefile2
-rw-r--r--content/handlers/image/svg.c2
16 files changed, 6576 insertions, 3 deletions
diff --git a/content/handlers/Makefile b/content/handlers/Makefile
index 97d2813b4..a12d8cc21 100644
--- a/content/handlers/Makefile
+++ b/content/handlers/Makefile
@@ -1,4 +1,9 @@
# Image content handler sources
include content/handlers/image/Makefile
-S_IMAGE := $(addprefix content/handlers/,$(S_IMAGE))
+S_IMAGE := $(addprefix content/handlers/image/,$(S_IMAGE))
+
+# CSS sources
+include content/handlers/css/Makefile
+
+S_CSS := $(addprefix content/handlers/css/,$(S_CSS))
diff --git a/content/handlers/css/Makefile b/content/handlers/css/Makefile
new file mode 100644
index 000000000..bbfc8d7b4
--- /dev/null
+++ b/content/handlers/css/Makefile
@@ -0,0 +1,4 @@
+# CSS sources
+
+S_CSS := css.c dump.c internal.c hints.c select.c utils.c
+
diff --git a/content/handlers/css/css.c b/content/handlers/css/css.c
new file mode 100644
index 000000000..4c0cb7a4c
--- /dev/null
+++ b/content/handlers/css/css.c
@@ -0,0 +1,834 @@
+/*
+ * 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 <libwapcaplet/libwapcaplet.h>
+#include <dom/dom.h>
+
+#include "content/content_protected.h"
+#include "content/fetch.h"
+#include "content/hlcache.h"
+#include "desktop/system_colour.h"
+#include "utils/corestrings.h"
+#include "utils/utils.h"
+#include "utils/http.h"
+#include "utils/log.h"
+#include "utils/messages.h"
+
+#include "css.h"
+#include "hints.h"
+#include "internal.h"
+
+/* Define to trace import fetches */
+#undef NSCSS_IMPORT_TRACE
+
+struct content_css_data;
+
+/**
+ * Type of callback called when a CSS object has finished
+ *
+ * \param css CSS object that has completed
+ * \param pw Client-specific data
+ */
+typedef void (*nscss_done_callback)(struct content_css_data *css, void *pw);
+
+/**
+ * CSS content data
+ */
+struct content_css_data
+{
+ css_stylesheet *sheet; /**< Stylesheet object */
+ char *charset; /**< Character set of stylesheet */
+ struct nscss_import *imports; /**< Array of imported sheets */
+ uint32_t import_count; /**< Number of sheets imported */
+ uint32_t next_to_register; /**< Index of next import to register */
+ nscss_done_callback done; /**< Completion callback */
+ void *pw; /**< Client data */
+};
+
+/**
+ * CSS content data
+ */
+typedef struct nscss_content
+{
+ struct content base; /**< Underlying content object */
+
+ struct content_css_data data; /**< CSS data */
+} nscss_content;
+
+/**
+ * Context for import fetches
+ */
+typedef struct {
+ struct content_css_data *css; /**< Object containing import */
+ uint32_t index; /**< Index into parent sheet's
+ * imports array */
+} nscss_import_ctx;
+
+static bool nscss_process_data(struct content *c, const char *data,
+ unsigned int size);
+static bool nscss_convert(struct content *c);
+static void nscss_destroy(struct content *c);
+static nserror nscss_clone(const struct content *old, struct content **newc);
+static bool nscss_matches_quirks(const struct content *c, bool quirks);
+static content_type nscss_content_type(void);
+
+static nserror nscss_create_css_data(struct content_css_data *c,
+ const char *url, const char *charset, bool quirks,
+ nscss_done_callback done, void *pw);
+static css_error nscss_process_css_data(struct content_css_data *c, const char *data,
+ unsigned int size);
+static css_error nscss_convert_css_data(struct content_css_data *c);
+static void nscss_destroy_css_data(struct content_css_data *c);
+
+static void nscss_content_done(struct content_css_data *css, void *pw);
+static css_error nscss_handle_import(void *pw, css_stylesheet *parent,
+ lwc_string *url, uint64_t media);
+static nserror nscss_import(hlcache_handle *handle,
+ const hlcache_event *event, void *pw);
+static css_error nscss_import_complete(nscss_import_ctx *ctx);
+
+static css_error nscss_register_imports(struct content_css_data *c);
+static css_error nscss_register_import(struct content_css_data *c,
+ const hlcache_handle *import);
+
+
+static css_stylesheet *blank_import;
+
+
+/**
+ * Initialise a CSS content
+ *
+ * \param handler content handler
+ * \param imime_type mime-type
+ * \param params Content-Type parameters
+ * \param llcache handle to content
+ * \param fallback_charset The character set to fallback to.
+ * \param quirks allow quirks
+ * \param c Content to initialise
+ * \return NSERROR_OK or error cod eon faliure
+ */
+static nserror
+nscss_create(const content_handler *handler,
+ lwc_string *imime_type,
+ const http_parameter *params,
+ llcache_handle *llcache,
+ const char *fallback_charset,
+ bool quirks,
+ struct content **c)
+{
+ nscss_content *result;
+ const char *charset = NULL;
+ const char *xnsbase = NULL;
+ lwc_string *charset_value = NULL;
+ union content_msg_data msg_data;
+ nserror error;
+
+ result = calloc(1, sizeof(nscss_content));
+ if (result == NULL)
+ return NSERROR_NOMEM;
+
+ error = content__init(&result->base, handler, imime_type,
+ params, llcache, fallback_charset, quirks);
+ if (error != NSERROR_OK) {
+ free(result);
+ return error;
+ }
+
+ /* Find charset specified on HTTP layer, if any */
+ error = http_parameter_list_find_item(params, corestring_lwc_charset,
+ &charset_value);
+ if (error != NSERROR_OK || lwc_string_length(charset_value) == 0) {
+ /* No charset specified, use fallback, if any */
+ /** \todo libcss will take this as gospel, which is wrong */
+ charset = fallback_charset;
+ } else {
+ charset = lwc_string_data(charset_value);
+ }
+
+ /* Compute base URL for stylesheet */
+ xnsbase = llcache_handle_get_header(llcache, "X-NS-Base");
+ if (xnsbase == NULL) {
+ xnsbase = nsurl_access(content_get_url(&result->base));
+ }
+
+ error = nscss_create_css_data(&result->data,
+ xnsbase, charset, result->base.quirks,
+ nscss_content_done, result);
+ if (error != NSERROR_OK) {
+ msg_data.error = messages_get("NoMemory");
+ content_broadcast(&result->base, CONTENT_MSG_ERROR, msg_data);
+ if (charset_value != NULL)
+ lwc_string_unref(charset_value);
+ free(result);
+ return error;
+ }
+
+ if (charset_value != NULL)
+ lwc_string_unref(charset_value);
+
+ *c = (struct content *) result;
+
+ return NSERROR_OK;
+}
+
+/**
+ * Create a struct content_css_data, creating a stylesheet object
+ *
+ * \param c Struct to populate
+ * \param url URL of stylesheet
+ * \param charset Stylesheet charset
+ * \param quirks Stylesheet quirks mode
+ * \param done Callback to call when content has completed
+ * \param pw Client data for \a done
+ * \return NSERROR_OK on success, NSERROR_NOMEM on memory exhaustion
+ */
+static nserror nscss_create_css_data(struct content_css_data *c,
+ const char *url, const char *charset, bool quirks,
+ nscss_done_callback done, void *pw)
+{
+ css_error error;
+ css_stylesheet_params params;
+
+ c->pw = pw;
+ c->done = done;
+ c->next_to_register = (uint32_t) -1;
+ c->import_count = 0;
+ c->imports = NULL;
+ if (charset != NULL)
+ c->charset = strdup(charset);
+ else
+ c->charset = NULL;
+
+ 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 = quirks;
+ params.inline_style = false;
+ params.resolve = nscss_resolve_url;
+ params.resolve_pw = NULL;
+ params.import = nscss_handle_import;
+ params.import_pw = c;
+ params.color = ns_system_colour;
+ params.color_pw = NULL;
+ params.font = NULL;
+ params.font_pw = NULL;
+
+ error = css_stylesheet_create(&params, &c->sheet);
+ if (error != CSS_OK) {
+ return NSERROR_NOMEM;
+ }
+
+ return NSERROR_OK;
+}
+
+/**
+ * Process CSS source data
+ *
+ * \param c Content structure
+ * \param data Data to process
+ * \param size Number of bytes to process
+ * \return true on success, false on failure
+ */
+bool nscss_process_data(struct content *c, const char *data, unsigned int size)
+{
+ nscss_content *css = (nscss_content *) c;
+ union content_msg_data msg_data;
+ css_error error;
+
+ error = nscss_process_css_data(&css->data, data, size);
+ if (error != CSS_OK && error != CSS_NEEDDATA) {
+ msg_data.error = "?";
+ content_broadcast(c, CONTENT_MSG_ERROR, msg_data);
+ }
+
+ return (error == CSS_OK || error == CSS_NEEDDATA);
+}
+
+/**
+ * Process CSS data
+ *
+ * \param c CSS content object
+ * \param data Data to process
+ * \param size Number of bytes to process
+ * \return CSS_OK on success, appropriate error otherwise
+ */
+static css_error nscss_process_css_data(struct content_css_data *c,
+ const char *data, unsigned int size)
+{
+ return css_stylesheet_append_data(c->sheet,
+ (const uint8_t *) data, size);
+}
+
+/**
+ * Convert a CSS content ready for use
+ *
+ * \param c Content to convert
+ * \return true on success, false on failure
+ */
+bool nscss_convert(struct content *c)
+{
+ nscss_content *css = (nscss_content *) c;
+ union content_msg_data msg_data;
+ css_error error;
+
+ error = nscss_convert_css_data(&css->data);
+ if (error != CSS_OK) {
+ msg_data.error = "?";
+ content_broadcast(c, CONTENT_MSG_ERROR, msg_data);
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Convert CSS data ready for use
+ *
+ * \param c CSS data to convert
+ * \return CSS error
+ */
+static css_error nscss_convert_css_data(struct content_css_data *c)
+{
+ css_error error;
+
+ error = css_stylesheet_data_done(c->sheet);
+
+ /* Process pending imports */
+ if (error == CSS_IMPORTS_PENDING) {
+ /* We must not have registered any imports yet */
+ assert(c->next_to_register == (uint32_t) -1);
+
+ /* Start registering, until we find one that
+ * hasn't finished fetching */
+ c->next_to_register = 0;
+ error = nscss_register_imports(c);
+ } else if (error == CSS_OK) {
+ /* No imports, and no errors, so complete conversion */
+ c->done(c, c->pw);
+ } else {
+ const char *url;
+
+ if (css_stylesheet_get_url(c->sheet, &url) == CSS_OK) {
+ LOG("Failed converting %p %s (%d)", c, url, error);
+ } else {
+ LOG("Failed converting %p (%d)", c, error);
+ }
+ }
+
+ return error;
+}
+
+/**
+ * Clean up a CSS content
+ *
+ * \param c Content to clean up
+ */
+void nscss_destroy(struct content *c)
+{
+ nscss_content *css = (nscss_content *) c;
+
+ nscss_destroy_css_data(&css->data);
+}
+
+/**
+ * Clean up CSS data
+ *
+ * \param c CSS data to clean up
+ */
+static void nscss_destroy_css_data(struct content_css_data *c)
+{
+ uint32_t i;
+
+ for (i = 0; i < c->import_count; i++) {
+ if (c->imports[i].c != NULL) {
+ hlcache_handle_release(c->imports[i].c);
+ }
+ c->imports[i].c = NULL;
+ }
+
+ free(c->imports);
+
+ if (c->sheet != NULL) {
+ css_stylesheet_destroy(c->sheet);
+ c->sheet = NULL;
+ }
+
+ free(c->charset);
+}
+
+nserror nscss_clone(const struct content *old, struct content **newc)
+{
+ const nscss_content *old_css = (const nscss_content *) old;
+ nscss_content *new_css;
+ const char *data;
+ unsigned long size;
+ nserror error;
+
+ new_css = calloc(1, sizeof(nscss_content));
+ if (new_css == NULL)
+ return NSERROR_NOMEM;
+
+ /* Clone content */
+ error = content__clone(old, &new_css->base);
+ if (error != NSERROR_OK) {
+ content_destroy(&new_css->base);
+ return error;
+ }
+
+ /* Simply replay create/process/convert */
+ error = nscss_create_css_data(&new_css->data,
+ nsurl_access(content_get_url(&new_css->base)),
+ old_css->data.charset,
+ new_css->base.quirks,
+ nscss_content_done, new_css);
+ if (error != NSERROR_OK) {
+ content_destroy(&new_css->base);
+ return error;
+ }
+
+ data = content__get_source_data(&new_css->base, &size);
+ if (size > 0) {
+ if (nscss_process_data(&new_css->base, data, size) == false) {
+ content_destroy(&new_css->base);
+ return NSERROR_CLONE_FAILED;
+ }
+ }
+
+ if (old->status == CONTENT_STATUS_READY ||
+ old->status == CONTENT_STATUS_DONE) {
+ if (nscss_convert(&new_css->base) == false) {
+ content_destroy(&new_css->base);
+ return NSERROR_CLONE_FAILED;
+ }
+ }
+
+ *newc = (struct content *) new_css;
+
+ return NSERROR_OK;
+}
+
+bool nscss_matches_quirks(const struct content *c, bool quirks)
+{
+ return c->quirks == quirks;
+}
+
+/* exported interface documented in netsurf/css.h */
+css_stylesheet *nscss_get_stylesheet(struct hlcache_handle *h)
+{
+ nscss_content *c = (nscss_content *) hlcache_handle_get_content(h);
+
+ assert(c != NULL);
+
+ return c->data.sheet;
+}
+
+/* exported interface documented in netsurf/css.h */
+struct nscss_import *nscss_get_imports(hlcache_handle *h, uint32_t *n)
+{
+ nscss_content *c = (nscss_content *) hlcache_handle_get_content(h);
+
+ assert(c != NULL);
+ assert(n != NULL);
+
+ *n = c->data.import_count;
+
+ return c->data.imports;
+}
+
+/**
+ * Compute the type of a content
+ *
+ * \return CONTENT_CSS
+ */
+content_type nscss_content_type(void)
+{
+ return CONTENT_CSS;
+}
+
+/*****************************************************************************
+ * Object completion *
+ *****************************************************************************/
+
+/**
+ * Handle notification that a CSS object is done
+ *
+ * \param css CSS object
+ * \param pw Private data
+ */
+void nscss_content_done(struct content_css_data *css, void *pw)
+{
+ union content_msg_data msg_data;
+ struct content *c = pw;
+ uint32_t i;
+ size_t size;
+ css_error error;
+
+ /* Retrieve the size of this sheet */
+ error = css_stylesheet_size(css->sheet, &size);
+ if (error != CSS_OK) {
+ msg_data.error = "?";
+ content_broadcast(c, CONTENT_MSG_ERROR, msg_data);
+ content_set_error(c);
+ return;
+ }
+ c->size += size;
+
+ /* Add on the size of the imported sheets */
+ for (i = 0; i < css->import_count; i++) {
+ if (css->imports[i].c != NULL) {
+ struct content *import = hlcache_handle_get_content(
+ css->imports[i].c);
+
+ if (import != NULL) {
+ c->size += import->size;
+ }
+ }
+ }
+
+ /* Finally, catch the content's users up with reality */
+ content_set_ready(c);
+ content_set_done(c);
+}
+
+/*****************************************************************************
+ * Import handling *
+ *****************************************************************************/
+
+/**
+ * Handle notification of the need for an imported stylesheet
+ *
+ * \param pw CSS object requesting the import
+ * \param parent Stylesheet requesting the import
+ * \param url URL of the imported sheet
+ * \param media Applicable media for the imported sheet
+ * \return CSS_OK on success, appropriate error otherwise
+ */
+css_error nscss_handle_import(void *pw, css_stylesheet *parent,
+ lwc_string *url, uint64_t media)
+{
+ content_type accept = CONTENT_CSS;
+ struct content_css_data *c = pw;
+ nscss_import_ctx *ctx;
+ hlcache_child_context child;
+ struct nscss_import *imports;
+ const char *referer;
+ css_error error;
+ nserror nerror;
+
+ nsurl *ns_url;
+ nsurl *ns_ref;
+
+ assert(parent == c->sheet);
+
+ error = css_stylesheet_get_url(c->sheet, &referer);
+ if (error != CSS_OK) {
+ return error;
+ }
+
+ ctx = malloc(sizeof(*ctx));
+ if (ctx == NULL)
+ return CSS_NOMEM;
+
+ ctx->css = c;
+ ctx->index = c->import_count;
+
+ /* Increase space in table */
+ imports = realloc(c->imports, (c->import_count + 1) *
+ sizeof(struct nscss_import));
+ if (imports == NULL) {
+ free(ctx);
+ return CSS_NOMEM;
+ }
+ c->imports = imports;
+
+ /** \todo fallback charset */
+ child.charset = NULL;
+ error = css_stylesheet_quirks_allowed(c->sheet, &child.quirks);
+ if (error != CSS_OK) {
+ free(ctx);
+ return error;
+ }
+
+ /* Create content */
+ c->imports[c->import_count].media = media;
+
+ /** \todo Why aren't we getting a relative url part, to join? */
+ nerror = nsurl_create(lwc_string_data(url), &ns_url);
+ if (nerror != NSERROR_OK) {
+ free(ctx);
+ return CSS_NOMEM;
+ }
+
+ /** \todo Constructing nsurl for referer here is silly, avoid */
+ nerror = nsurl_create(referer, &ns_ref);
+ if (nerror != NSERROR_OK) {
+ nsurl_unref(ns_url);
+ free(ctx);
+ return CSS_NOMEM;
+ }
+
+ /* Avoid importing ourself */
+ if (nsurl_compare(ns_url, ns_ref, NSURL_COMPLETE)) {
+ c->imports[c->import_count].c = NULL;
+ /* No longer require context as we're not fetching anything */
+ free(ctx);
+ ctx = NULL;
+ } else {
+ nerror = hlcache_handle_retrieve(ns_url,
+ 0, ns_ref, NULL, nscss_import, ctx,
+ &child, accept,
+ &c->imports[c->import_count].c);
+ if (nerror != NSERROR_OK) {
+ free(ctx);
+ return CSS_NOMEM;
+ }
+ }
+
+ nsurl_unref(ns_url);
+ nsurl_unref(ns_ref);
+
+#ifdef NSCSS_IMPORT_TRACE
+ LOG("Import %d '%s' -> (handle: %p ctx: %p)", c->import_count, lwc_string_data(url), c->imports[c->import_count].c, ctx);
+#endif
+
+ c->import_count++;
+
+ return CSS_OK;
+}
+
+/**
+ * Handler for imported stylesheet events
+ *
+ * \param handle Handle for stylesheet
+ * \param event Event object
+ * \param pw Callback context
+ * \return NSERROR_OK on success, appropriate error otherwise
+ */
+nserror nscss_import(hlcache_handle *handle,
+ const hlcache_event *event, void *pw)
+{
+ nscss_import_ctx *ctx = pw;
+ css_error error = CSS_OK;
+
+#ifdef NSCSS_IMPORT_TRACE
+ LOG("Event %d for %p (%p)", event->type, handle, ctx);
+#endif
+
+ assert(ctx->css->imports[ctx->index].c == handle);
+
+ switch (event->type) {
+ case CONTENT_MSG_DONE:
+ error = nscss_import_complete(ctx);
+ break;
+
+ case CONTENT_MSG_ERROR:
+ hlcache_handle_release(handle);
+ ctx->css->imports[ctx->index].c = NULL;
+
+ error = nscss_import_complete(ctx);
+ /* Already released handle */
+ break;
+
+ default:
+ break;
+ }
+
+ /* Preserve out-of-memory. Anything else is OK */
+ return error == CSS_NOMEM ? NSERROR_NOMEM : NSERROR_OK;
+}
+
+/**
+ * Handle an imported stylesheet completing
+ *
+ * \param ctx Import context
+ * \return CSS_OK on success, appropriate error otherwise
+ */
+css_error nscss_import_complete(nscss_import_ctx *ctx)
+{
+ css_error error = CSS_OK;
+
+ /* If this import is the next to be registered, do so */
+ if (ctx->css->next_to_register == ctx->index)
+ error = nscss_register_imports(ctx->css);
+
+#ifdef NSCSS_IMPORT_TRACE
+ LOG("Destroying import context %p for %d", ctx, ctx->index);
+#endif
+
+ /* No longer need import context */
+ free(ctx);
+
+ return error;
+}
+
+/*****************************************************************************
+ * Import registration *
+ *****************************************************************************/
+
+/**
+ * Register imports with a stylesheet
+ *
+ * \param c CSS object containing the imports
+ * \return CSS_OK on success, appropriate error otherwise
+ */
+css_error nscss_register_imports(struct content_css_data *c)
+{
+ uint32_t index;
+ css_error error;
+
+ assert(c->next_to_register != (uint32_t) -1);
+ assert(c->next_to_register < c->import_count);
+
+ /* Register imported sheets */
+ for (index = c->next_to_register; index < c->import_count; index++) {
+ /* Stop registering if we encounter one whose fetch hasn't
+ * completed yet. We'll resume at this point when it has
+ * completed.
+ */
+ if (c->imports[index].c != NULL &&
+ content_get_status(c->imports[index].c) !=
+ CONTENT_STATUS_DONE) {
+ break;
+ }
+
+ error = nscss_register_import(c, c->imports[index].c);
+ if (error != CSS_OK)
+ return error;
+ }
+
+ /* Record identity of the next import to register */
+ c->next_to_register = (uint32_t) index;
+
+ if (c->next_to_register == c->import_count) {
+ /* No more imports: notify parent that we're DONE */
+ c->done(c, c->pw);
+ }
+
+ return CSS_OK;
+}
+
+
+/**
+ * Register an import with a stylesheet
+ *
+ * \param c CSS object that requested the import
+ * \param import Cache handle of import, or NULL for blank
+ * \return CSS_OK on success, appropriate error otherwise
+ */
+css_error nscss_register_import(struct content_css_data *c,
+ const hlcache_handle *import)
+{
+ css_stylesheet *sheet;
+ css_error error;
+
+ if (import != NULL) {
+ nscss_content *s =
+ (nscss_content *) hlcache_handle_get_content(import);
+ sheet = s->data.sheet;
+ } else {
+ /* Create a blank sheet if needed. */
+ if (blank_import == NULL) {
+ css_stylesheet_params params;
+
+ params.params_version = CSS_STYLESHEET_PARAMS_VERSION_1;
+ params.level = CSS_LEVEL_DEFAULT;
+ params.charset = NULL;
+ params.url = "";
+ params.title = NULL;
+ params.allow_quirks = false;
+ params.inline_style = false;
+ 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, &blank_import);
+ if (error != CSS_OK) {
+ return error;
+ }
+
+ error = css_stylesheet_data_done(blank_import);
+ if (error != CSS_OK) {
+ css_stylesheet_destroy(blank_import);
+ return error;
+ }
+ }
+
+ sheet = blank_import;
+ }
+
+ error = css_stylesheet_register_import(c->sheet, sheet);
+ if (error != CSS_OK) {
+ return error;
+ }
+
+ return error;
+}
+
+/**
+ * Clean up after the CSS content handler
+ */
+static void nscss_fini(void)
+{
+ if (blank_import != NULL) {
+ css_stylesheet_destroy(blank_import);
+ blank_import = NULL;
+ }
+ css_hint_fini();
+}
+
+static const content_handler css_content_handler = {
+ .fini = nscss_fini,
+ .create = nscss_create,
+ .process_data = nscss_process_data,
+ .data_complete = nscss_convert,
+ .destroy = nscss_destroy,
+ .clone = nscss_clone,
+ .matches_quirks = nscss_matches_quirks,
+ .type = nscss_content_type,
+ .no_share = false,
+};
+
+/* exported interface documented in netsurf/css.h */
+nserror nscss_init(void)
+{
+ nserror error;
+
+ error = content_factory_register_handler("text/css",
+ &css_content_handler);
+ if (error != NSERROR_OK)
+ goto error;
+
+ error = css_hint_init();
+ if (error != NSERROR_OK)
+ goto error;
+
+ return NSERROR_OK;
+
+error:
+ nscss_fini();
+
+ return error;
+}
diff --git a/content/handlers/css/css.h b/content/handlers/css/css.h
new file mode 100644
index 000000000..4b38829f0
--- /dev/null
+++ b/content/handlers/css/css.h
@@ -0,0 +1,62 @@
+/*
+ * 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/>.
+ */
+
+#ifndef netsurf_css_css_h_
+#define netsurf_css_css_h_
+
+#include <stdint.h>
+
+#include <libcss/libcss.h>
+
+#include "utils/errors.h"
+
+struct hlcache_handle;
+
+/**
+ * Imported stylesheet record
+ */
+struct nscss_import {
+ struct hlcache_handle *c; /**< Content containing sheet */
+ uint64_t media; /**< Media types that sheet applies to */
+};
+
+/**
+ * Initialise the CSS content handler
+ *
+ * \return NSERROR_OK on success or error code on faliure
+ */
+nserror nscss_init(void);
+
+/**
+ * Retrieve the stylesheet object associated with a CSS content
+ *
+ * \param h Stylesheet content
+ * \return Pointer to stylesheet object
+ */
+css_stylesheet *nscss_get_stylesheet(struct hlcache_handle *h);
+
+/**
+ * Retrieve imported stylesheets
+ *
+ * \param h Stylesheet containing imports
+ * \param n Pointer to location to receive number of imports
+ * \return Pointer to array of imported stylesheets
+ */
+struct nscss_import *nscss_get_imports(struct hlcache_handle *h, uint32_t *n);
+
+#endif
diff --git a/content/handlers/css/dump.c b/content/handlers/css/dump.c
new file mode 100644
index 000000000..125b133cf
--- /dev/null
+++ b/content/handlers/css/dump.c
@@ -0,0 +1,1793 @@
+/*
+ * 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 <stdio.h>
+#include <libcss/libcss.h>
+
+#include "dump.h"
+
+/**
+ * Dump a fixed point value to the stream in a textual form.
+ *
+ * \param stream Stream to write to
+ * \param f Value to write
+ */
+static void dump_css_fixed(FILE *stream, css_fixed f)
+{
+#define NSCSS_ABS(x) (uint32_t)((x) < 0 ? -(x) : (x))
+ uint32_t uintpart = FIXTOINT(NSCSS_ABS(f));
+ /* + 500 to ensure round to nearest (division will truncate) */
+ uint32_t fracpart = ((NSCSS_ABS(f) & 0x3ff) * 1000 + 500) / (1 << 10);
+#undef NSCSS_ABS
+
+ fprintf(stream, "%s%d.%03d", f < 0 ? "-" : "", uintpart, fracpart);
+}
+
+/**
+ * Dump a numeric value to the stream in a textual form.
+ *
+ * \param stream Stream to write to
+ * \param val Value to write
+ */
+static void dump_css_number(FILE *stream, css_fixed val)
+{
+ if (INTTOFIX(FIXTOINT(val)) == val)
+ fprintf(stream, "%d", FIXTOINT(val));
+ else
+ dump_css_fixed(stream, val);
+}
+
+/**
+ * Dump a dimension to the stream in a textual form.
+ *
+ * \param stream Stream to write to
+ * \param val Value to write
+ * \param unit Unit to write
+ */
+static void dump_css_unit(FILE *stream, css_fixed val, css_unit unit)
+{
+ dump_css_number(stream, val);
+
+ switch (unit) {
+ case CSS_UNIT_PX:
+ fprintf(stream, "px");
+ break;
+ case CSS_UNIT_EX:
+ fprintf(stream, "ex");
+ break;
+ case CSS_UNIT_EM:
+ fprintf(stream, "em");
+ break;
+ case CSS_UNIT_IN:
+ fprintf(stream, "in");
+ break;
+ case CSS_UNIT_CM:
+ fprintf(stream, "cm");
+ break;
+ case CSS_UNIT_MM:
+ fprintf(stream, "mm");
+ break;
+ case CSS_UNIT_PT:
+ fprintf(stream, "pt");
+ break;
+ case CSS_UNIT_PC:
+ fprintf(stream, "pc");
+ break;
+ case CSS_UNIT_PCT:
+ fprintf(stream, "%%");
+ break;
+ case CSS_UNIT_DEG:
+ fprintf(stream, "deg");
+ break;
+ case CSS_UNIT_GRAD:
+ fprintf(stream, "grad");
+ break;
+ case CSS_UNIT_RAD:
+ fprintf(stream, "rad");
+ break;
+ case CSS_UNIT_MS:
+ fprintf(stream, "ms");
+ break;
+ case CSS_UNIT_S:
+ fprintf(stream, "s");
+ break;
+ case CSS_UNIT_HZ:
+ fprintf(stream, "Hz");
+ break;
+ case CSS_UNIT_KHZ:
+ fprintf(stream, "kHz");
+ break;
+ }
+}
+
+/* exported interface documented in content/handlers/css/dump.h */
+void nscss_dump_computed_style(FILE *stream, const css_computed_style *style)
+{
+ uint8_t val;
+ css_color color = 0;
+ lwc_string *url = NULL;
+ css_fixed len1 = 0, len2 = 0;
+ css_unit unit1 = CSS_UNIT_PX, unit2 = CSS_UNIT_PX;
+ css_computed_clip_rect rect = { 0, 0, 0, 0, CSS_UNIT_PX, CSS_UNIT_PX,
+ CSS_UNIT_PX, CSS_UNIT_PX, true, true,
+ true, true };
+ const css_computed_content_item *content = NULL;
+ const css_computed_counter *counter = NULL;
+ lwc_string **string_list = NULL;
+ int32_t zindex = 0;
+
+ fprintf(stream, "{ ");
+
+ /* background-attachment */
+ val = css_computed_background_attachment(style);
+ switch (val) {
+ case CSS_BACKGROUND_ATTACHMENT_FIXED:
+ fprintf(stream, "background-attachment: fixed ");
+ break;
+ case CSS_BACKGROUND_ATTACHMENT_SCROLL:
+ fprintf(stream, "background-attachment: scroll ");
+ break;
+ default:
+ break;
+ }
+
+ /* background-color */
+ val = css_computed_background_color(style, &color);
+ switch (val) {
+ case CSS_BACKGROUND_COLOR_COLOR:
+ fprintf(stream, "background-color: #%08x ", color);
+ break;
+ default:
+ break;
+ }
+
+ /* background-image */
+ val = css_computed_background_image(style, &url);
+ if (val == CSS_BACKGROUND_IMAGE_IMAGE && url != NULL) {
+ fprintf(stream, "background-image: url('%.*s') ",
+ (int) lwc_string_length(url),
+ lwc_string_data(url));
+ } else if (val == CSS_BACKGROUND_IMAGE_NONE) {
+ fprintf(stream, "background-image: none ");
+ }
+
+ /* background-position */
+ val = css_computed_background_position(style, &len1, &unit1,
+ &len2, &unit2);
+ if (val == CSS_BACKGROUND_POSITION_SET) {
+ fprintf(stream, "background-position: ");
+ dump_css_unit(stream, len1, unit1);
+ fprintf(stream, " ");
+ dump_css_unit(stream, len2, unit2);
+ fprintf(stream, " ");
+ }
+
+ /* background-repeat */
+ val = css_computed_background_repeat(style);
+ switch (val) {
+ case CSS_BACKGROUND_REPEAT_REPEAT_X:
+ fprintf(stream, "background-repeat: repeat-x ");
+ break;
+ case CSS_BACKGROUND_REPEAT_REPEAT_Y:
+ fprintf(stream, "background-repeat: repeat-y ");
+ break;
+ case CSS_BACKGROUND_REPEAT_REPEAT:
+ fprintf(stream, "background-repeat: repeat ");
+ break;
+ case CSS_BACKGROUND_REPEAT_NO_REPEAT:
+ fprintf(stream, "background-repeat: no-repeat ");
+ break;
+ default:
+ break;
+ }
+
+ /* border-collapse */
+ val = css_computed_border_collapse(style);
+ switch (val) {
+ case CSS_BORDER_COLLAPSE_SEPARATE:
+ fprintf(stream, "border-collapse: separate ");
+ break;
+ case CSS_BORDER_COLLAPSE_COLLAPSE:
+ fprintf(stream, "border-collapse: collapse ");
+ break;
+ default:
+
+ break;
+ }
+
+ /* border-spacing */
+ val = css_computed_border_spacing(style, &len1, &unit1, &len2, &unit2);
+ if (val == CSS_BORDER_SPACING_SET) {
+ fprintf(stream, "border-spacing: ");
+ dump_css_unit(stream, len1, unit1);
+ fprintf(stream, " ");
+ dump_css_unit(stream, len2, unit2);
+ fprintf(stream, " ");
+ }
+
+ /* border-top-color */
+ val = css_computed_border_top_color(style, &color);
+ switch (val) {
+ case CSS_BORDER_COLOR_COLOR:
+ fprintf(stream, "border-top-color: #%08x ", color);
+ break;
+ default:
+ break;
+ }
+
+ /* border-right-color */
+ val = css_computed_border_right_color(style, &color);
+ switch (val) {
+ case CSS_BORDER_COLOR_COLOR:
+ fprintf(stream, "border-right-color: #%08x ", color);
+ break;
+ default:
+ break;
+ }
+
+ /* border-bottom-color */
+ val = css_computed_border_bottom_color(style, &color);
+ switch (val) {
+ case CSS_BORDER_COLOR_COLOR:
+ fprintf(stream, "border-bottom-color: #%08x ", color);
+ break;
+ default:
+ break;
+ }
+
+ /* border-left-color */
+ val = css_computed_border_left_color(style, &color);
+ switch (val) {
+ case CSS_BORDER_COLOR_COLOR:
+ fprintf(stream, "border-left-color: #%08x ", color);
+ break;
+ default:
+ break;
+ }
+
+ /* border-top-style */
+ val = css_computed_border_top_style(style);
+ switch (val) {
+ case CSS_BORDER_STYLE_NONE:
+ fprintf(stream, "border-top-style: none ");
+ break;
+ case CSS_BORDER_STYLE_HIDDEN:
+ fprintf(stream, "border-top-style: hidden ");
+ break;
+ case CSS_BORDER_STYLE_DOTTED:
+ fprintf(stream, "border-top-style: dotted ");
+ break;
+ case CSS_BORDER_STYLE_DASHED:
+ fprintf(stream, "border-top-style: dashed ");
+ break;
+ case CSS_BORDER_STYLE_SOLID:
+ fprintf(stream, "border-top-style: solid ");
+ break;
+ case CSS_BORDER_STYLE_DOUBLE:
+ fprintf(stream, "border-top-style: double ");
+ break;
+ case CSS_BORDER_STYLE_GROOVE:
+ fprintf(stream, "border-top-style: groove ");
+ break;
+ case CSS_BORDER_STYLE_RIDGE:
+ fprintf(stream, "border-top-style: ridge ");
+ break;
+ case CSS_BORDER_STYLE_INSET:
+ fprintf(stream, "border-top-style: inset ");
+ break;
+ case CSS_BORDER_STYLE_OUTSET:
+ fprintf(stream, "border-top-style: outset ");
+ break;
+ default:
+ break;
+ }
+
+ /* border-right-style */
+ val = css_computed_border_right_style(style);
+ switch (val) {
+ case CSS_BORDER_STYLE_NONE:
+ fprintf(stream, "border-right-style: none ");
+ break;
+ case CSS_BORDER_STYLE_HIDDEN:
+ fprintf(stream, "border-right-style: hidden ");
+ break;
+ case CSS_BORDER_STYLE_DOTTED:
+ fprintf(stream, "border-right-style: dotted ");
+ break;
+ case CSS_BORDER_STYLE_DASHED:
+ fprintf(stream, "border-right-style: dashed ");
+ break;
+ case CSS_BORDER_STYLE_SOLID:
+ fprintf(stream, "border-right-style: solid ");
+ break;
+ case CSS_BORDER_STYLE_DOUBLE:
+ fprintf(stream, "border-right-style: double ");
+ break;
+ case CSS_BORDER_STYLE_GROOVE:
+ fprintf(stream, "border-right-style: groove ");
+ break;
+ case CSS_BORDER_STYLE_RIDGE:
+ fprintf(stream, "border-right-style: ridge ");
+ break;
+ case CSS_BORDER_STYLE_INSET:
+ fprintf(stream, "border-right-style: inset ");
+ break;
+ case CSS_BORDER_STYLE_OUTSET:
+ fprintf(stream, "border-right-style: outset ");
+ break;
+ default:
+ break;
+ }
+
+ /* border-bottom-style */
+ val = css_computed_border_bottom_style(style);
+ switch (val) {
+ case CSS_BORDER_STYLE_NONE:
+ fprintf(stream, "border-bottom-style: none ");
+ break;
+ case CSS_BORDER_STYLE_HIDDEN:
+ fprintf(stream, "border-bottom-style: hidden ");
+ break;
+ case CSS_BORDER_STYLE_DOTTED:
+ fprintf(stream, "border-bottom-style: dotted ");
+ break;
+ case CSS_BORDER_STYLE_DASHED:
+ fprintf(stream, "border-bottom-style: dashed ");
+ break;
+ case CSS_BORDER_STYLE_SOLID:
+ fprintf(stream, "border-bottom-style: solid ");
+ break;
+ case CSS_BORDER_STYLE_DOUBLE:
+ fprintf(stream, "border-bottom-style: double ");
+ break;
+ case CSS_BORDER_STYLE_GROOVE:
+ fprintf(stream, "border-bottom-style: groove ");
+ break;
+ case CSS_BORDER_STYLE_RIDGE:
+ fprintf(stream, "border-bottom-style: ridge ");
+ break;
+ case CSS_BORDER_STYLE_INSET:
+ fprintf(stream, "border-bottom-style: inset ");
+ break;
+ case CSS_BORDER_STYLE_OUTSET:
+ fprintf(stream, "border-bottom-style: outset ");
+ break;
+ default:
+ break;
+ }
+
+ /* border-left-style */
+ val = css_computed_border_left_style(style);
+ switch (val) {
+ case CSS_BORDER_STYLE_NONE:
+ fprintf(stream, "border-left-style: none ");
+ break;
+ case CSS_BORDER_STYLE_HIDDEN:
+ fprintf(stream, "border-left-style: hidden ");
+ break;
+ case CSS_BORDER_STYLE_DOTTED:
+ fprintf(stream, "border-left-style: dotted ");
+ break;
+ case CSS_BORDER_STYLE_DASHED:
+ fprintf(stream, "border-left-style: dashed ");
+ break;
+ case CSS_BORDER_STYLE_SOLID:
+ fprintf(stream, "border-left-style: solid ");
+ break;
+ case CSS_BORDER_STYLE_DOUBLE:
+ fprintf(stream, "border-left-style: double ");
+ break;
+ case CSS_BORDER_STYLE_GROOVE:
+ fprintf(stream, "border-left-style: groove ");
+ break;
+ case CSS_BORDER_STYLE_RIDGE:
+ fprintf(stream, "border-left-style: ridge ");
+ break;
+ case CSS_BORDER_STYLE_INSET:
+ fprintf(stream, "border-left-style: inset ");
+ break;
+ case CSS_BORDER_STYLE_OUTSET:
+ fprintf(stream, "border-left-style: outset ");
+ break;
+ default:
+ break;
+ }
+
+ /* border-top-width */
+ val = css_computed_border_top_width(style, &len1, &unit1);
+ switch (val) {
+ case CSS_BORDER_WIDTH_THIN:
+ fprintf(stream, "border-top-width: thin ");
+ break;
+ case CSS_BORDER_WIDTH_MEDIUM:
+ fprintf(stream, "border-top-width: medium ");
+ break;
+ case CSS_BORDER_WIDTH_THICK:
+ fprintf(stream, "border-top-width: thick ");
+ break;
+ case CSS_BORDER_WIDTH_WIDTH:
+ fprintf(stream, "border-top-width: ");
+ dump_css_unit(stream, len1, unit1);
+ fprintf(stream, " ");
+ break;
+ default:
+ break;
+ }
+
+ /* border-right-width */
+ val = css_computed_border_right_width(style, &len1, &unit1);
+ switch (val) {
+ case CSS_BORDER_WIDTH_THIN:
+ fprintf(stream, "border-right-width: thin ");
+ break;
+ case CSS_BORDER_WIDTH_MEDIUM:
+ fprintf(stream, "border-right-width: medium ");
+ break;
+ case CSS_BORDER_WIDTH_THICK:
+ fprintf(stream, "border-right-width: thick ");
+ break;
+ case CSS_BORDER_WIDTH_WIDTH:
+ fprintf(stream, "border-right-width: ");
+ dump_css_unit(stream, len1, unit1);
+ fprintf(stream, " ");
+ break;
+ default:
+ break;
+ }
+
+ /* border-bottom-width */
+ val = css_computed_border_bottom_width(style, &len1, &unit1);
+ switch (val) {
+ case CSS_BORDER_WIDTH_THIN:
+ fprintf(stream, "border-bottom-width: thin ");
+ break;
+ case CSS_BORDER_WIDTH_MEDIUM:
+ fprintf(stream, "border-bottom-width: medium ");
+ break;
+ case CSS_BORDER_WIDTH_THICK:
+ fprintf(stream, "border-bottom-width: thick ");
+ break;
+ case CSS_BORDER_WIDTH_WIDTH:
+ fprintf(stream, "border-bottom-width: ");
+ dump_css_unit(stream, len1, unit1);
+ fprintf(stream, " ");
+ break;
+ default:
+ break;
+ }
+
+ /* border-left-width */
+ val = css_computed_border_left_width(style, &len1, &unit1);
+ switch (val) {
+ case CSS_BORDER_WIDTH_THIN:
+ fprintf(stream, "border-left-width: thin ");
+ break;
+ case CSS_BORDER_WIDTH_MEDIUM:
+ fprintf(stream, "border-left-width: medium ");
+ break;
+ case CSS_BORDER_WIDTH_THICK:
+ fprintf(stream, "border-left-width: thick ");
+ break;
+ case CSS_BORDER_WIDTH_WIDTH:
+ fprintf(stream, "border-left-width: ");
+ dump_css_unit(stream, len1, unit1);
+ fprintf(stream, " ");
+ break;
+ default:
+ break;
+ }
+
+ /* bottom */
+ val = css_computed_bottom(style, &len1, &unit1);
+ switch (val) {
+ case CSS_BOTTOM_AUTO:
+ fprintf(stream, "bottom: auto ");
+ break;
+ case CSS_BOTTOM_SET:
+ fprintf(stream, "bottom: ");
+ dump_css_unit(stream, len1, unit1);
+ fprintf(stream, " ");
+ break;
+ default:
+ break;
+ }
+
+ /* caption-side */
+ val = css_computed_caption_side(style);
+ switch (val) {
+ case CSS_CAPTION_SIDE_TOP:
+ fprintf(stream, "caption_side: top ");
+ break;
+ case CSS_CAPTION_SIDE_BOTTOM:
+ fprintf(stream, "caption_side: bottom ");
+ break;
+ default:
+ break;
+ }
+
+ /* clear */
+ val = css_computed_clear(style);
+ switch (val) {
+ case CSS_CLEAR_NONE:
+ fprintf(stream, "clear: none ");
+ break;
+ case CSS_CLEAR_LEFT:
+ fprintf(stream, "clear: left ");
+ break;
+ case CSS_CLEAR_RIGHT:
+ fprintf(stream, "clear: right ");
+ break;
+ case CSS_CLEAR_BOTH:
+ fprintf(stream, "clear: both ");
+ break;
+ default:
+ break;
+ }
+
+ /* clip */
+ val = css_computed_clip(style, &rect);
+ switch (val) {
+ case CSS_CLIP_AUTO:
+ fprintf(stream, "clip: auto ");
+ break;
+ case CSS_CLIP_RECT:
+ fprintf(stream, "clip: rect( ");
+
+ if (rect.top_auto)
+ fprintf(stream, "auto");
+ else
+ dump_css_unit(stream, rect.top, rect.tunit);
+ fprintf(stream, ", ");
+
+ if (rect.right_auto)
+ fprintf(stream, "auto");
+ else
+ dump_css_unit(stream, rect.right, rect.runit);
+ fprintf(stream, ", ");
+
+ if (rect.bottom_auto)
+ fprintf(stream, "auto");
+ else
+ dump_css_unit(stream, rect.bottom, rect.bunit);
+ fprintf(stream, ", ");
+
+ if (rect.left_auto)
+ fprintf(stream, "auto");
+ else
+ dump_css_unit(stream, rect.left, rect.lunit);
+ fprintf(stream, ") ");
+ break;
+ default:
+ break;
+ }
+
+ /* color */
+ val = css_computed_color(style, &color);
+ if (val == CSS_COLOR_COLOR) {
+ fprintf(stream, "color: #%08x ", color);
+ }
+
+ /* content */
+ val = css_computed_content(style, &content);
+ switch (val) {
+ case CSS_CONTENT_NONE:
+ fprintf(stream, "content: none ");
+ break;
+ case CSS_CONTENT_NORMAL:
+ fprintf(stream, "content: normal ");
+ break;
+ case CSS_CONTENT_SET:
+ fprintf(stream, "content:");
+
+ while (content->type != CSS_COMPUTED_CONTENT_NONE) {
+ fprintf(stream, " ");
+
+ switch (content->type) {
+ case CSS_COMPUTED_CONTENT_STRING:
+ fprintf(stream, "\"%.*s\"",
+ (int) lwc_string_length(
+ content->data.string),
+ lwc_string_data(
+ content->data.string));
+ break;
+ case CSS_COMPUTED_CONTENT_URI:
+ fprintf(stream, "uri(\"%.*s\")",
+ (int) lwc_string_length(
+ content->data.uri),
+ lwc_string_data(
+ content->data.uri));
+ break;
+ case CSS_COMPUTED_CONTENT_COUNTER:
+ fprintf(stream, "counter(%.*s)",
+ (int) lwc_string_length(
+ content->data.counter.name),
+ lwc_string_data(
+ content->data.counter.name));
+ break;
+ case CSS_COMPUTED_CONTENT_COUNTERS:
+ fprintf(stream, "counters(%.*s, \"%.*s\")",
+ (int) lwc_string_length(
+ content->data.counters.name),
+ lwc_string_data(
+ content->data.counters.name),
+ (int) lwc_string_length(
+ content->data.counters.sep),
+ lwc_string_data(
+ content->data.counters.sep));
+ break;
+ case CSS_COMPUTED_CONTENT_ATTR:
+ fprintf(stream, "attr(%.*s)",
+ (int) lwc_string_length(
+ content->data.attr),
+ lwc_string_data(
+ content->data.attr));
+ break;
+ case CSS_COMPUTED_CONTENT_OPEN_QUOTE:
+ fprintf(stream, "open-quote");
+ break;
+ case CSS_COMPUTED_CONTENT_CLOSE_QUOTE:
+ fprintf(stream, "close-quote");
+ break;
+ case CSS_COMPUTED_CONTENT_NO_OPEN_QUOTE:
+ fprintf(stream, "no-open-quote");
+ break;
+ case CSS_COMPUTED_CONTENT_NO_CLOSE_QUOTE:
+ fprintf(stream, "no-close-quote");
+ break;
+ }
+
+ content++;
+ }
+
+ fprintf(stream, " ");
+ break;
+ default:
+ break;
+ }
+
+ /* counter-increment */
+ val = css_computed_counter_increment(style, &counter);
+ if ((val == CSS_COUNTER_INCREMENT_NONE) || (counter == NULL)) {
+ fprintf(stream, "counter-increment: none ");
+ } else {
+ fprintf(stream, "counter-increment:");
+
+ while (counter->name != NULL) {
+ fprintf(stream, " %.*s ",
+ (int) lwc_string_length(counter->name),
+ lwc_string_data(counter->name));
+
+ dump_css_fixed(stream, counter->value);
+
+ counter++;
+ }
+
+ fprintf(stream, " ");
+ }
+
+ /* counter-reset */
+ val = css_computed_counter_reset(style, &counter);
+ if ((val == CSS_COUNTER_RESET_NONE) || (counter == NULL)) {
+ fprintf(stream, "counter-reset: none ");
+ } else {
+ fprintf(stream, "counter-reset:");
+
+ while (counter->name != NULL) {
+ fprintf(stream, " %.*s ",
+ (int) lwc_string_length(counter->name),
+ lwc_string_data(counter->name));
+
+ dump_css_fixed(stream, counter->value);
+
+ counter++;
+ }
+
+ fprintf(stream, " ");
+ }
+
+ /* cursor */
+ val = css_computed_cursor(style, &string_list);
+ fprintf(stream, "cursor:");
+
+ if (string_list != NULL) {
+ while (*string_list != NULL) {
+ fprintf(stream, " url\"%.*s\")",
+ (int) lwc_string_length(*string_list),
+ lwc_string_data(*string_list));
+
+ string_list++;
+ }
+ }
+ switch (val) {
+ case CSS_CURSOR_AUTO:
+ fprintf(stream, " auto ");
+ break;
+ case CSS_CURSOR_CROSSHAIR:
+ fprintf(stream, " crosshair ");
+ break;
+ case CSS_CURSOR_DEFAULT:
+ fprintf(stream, " default ");
+ break;
+ case CSS_CURSOR_POINTER:
+ fprintf(stream, " pointer ");
+ break;
+ case CSS_CURSOR_MOVE:
+ fprintf(stream, " move ");
+ break;
+ case CSS_CURSOR_E_RESIZE:
+ fprintf(stream, " e-resize ");
+ break;
+ case CSS_CURSOR_NE_RESIZE:
+ fprintf(stream, " ne-resize ");
+ break;
+ case CSS_CURSOR_NW_RESIZE:
+ fprintf(stream, " nw-resize ");
+ break;
+ case CSS_CURSOR_N_RESIZE:
+ fprintf(stream, " n-resize ");
+ break;
+ case CSS_CURSOR_SE_RESIZE:
+ fprintf(stream, " se-resize ");
+ break;
+ case CSS_CURSOR_SW_RESIZE:
+ fprintf(stream, " sw-resize ");
+ break;
+ case CSS_CURSOR_S_RESIZE:
+ fprintf(stream, " s-resize ");
+ break;
+ case CSS_CURSOR_W_RESIZE:
+ fprintf(stream, " w-resize ");
+ break;
+ case CSS_CURSOR_TEXT:
+ fprintf(stream, " text ");
+ break;
+ case CSS_CURSOR_WAIT:
+ fprintf(stream, " wait ");
+ break;
+ case CSS_CURSOR_HELP:
+ fprintf(stream, " help ");
+ break;
+ case CSS_CURSOR_PROGRESS:
+ fprintf(stream, " progress ");
+ break;
+ default:
+ break;
+ }
+
+ /* direction */
+ val = css_computed_direction(style);
+ switch (val) {
+ case CSS_DIRECTION_LTR:
+ fprintf(stream, "direction: ltr ");
+ break;
+ case CSS_DIRECTION_RTL:
+ fprintf(stream, "direction: rtl ");
+ break;
+ default:
+ break;
+ }
+
+ /* display */
+ val = css_computed_display_static(style);
+ switch (val) {
+ case CSS_DISPLAY_INLINE:
+ fprintf(stream, "display: inline ");
+ break;
+ case CSS_DISPLAY_BLOCK:
+ fprintf(stream, "display: block ");
+ break;
+ case CSS_DISPLAY_LIST_ITEM:
+ fprintf(stream, "display: list-item ");
+ break;
+ case CSS_DISPLAY_RUN_IN:
+ fprintf(stream, "display: run-in ");
+ break;
+ case CSS_DISPLAY_INLINE_BLOCK:
+ fprintf(stream, "display: inline-block ");
+ break;
+ case CSS_DISPLAY_TABLE:
+ fprintf(stream, "display: table ");
+ break;
+ case CSS_DISPLAY_INLINE_TABLE:
+ fprintf(stream, "display: inline-table ");
+ break;
+ case CSS_DISPLAY_TABLE_ROW_GROUP:
+ fprintf(stream, "display: table-row-group ");
+ break;
+ case CSS_DISPLAY_TABLE_HEADER_GROUP:
+ fprintf(stream, "display: table-header-group ");
+ break;
+ case CSS_DISPLAY_TABLE_FOOTER_GROUP:
+ fprintf(stream, "display: table-footer-group ");
+ break;
+ case CSS_DISPLAY_TABLE_ROW:
+ fprintf(stream, "display: table-row ");
+ break;
+ case CSS_DISPLAY_TABLE_COLUMN_GROUP:
+ fprintf(stream, "display: table-column-group ");
+ break;
+ case CSS_DISPLAY_TABLE_COLUMN:
+ fprintf(stream, "display: table-column ");
+ break;
+ case CSS_DISPLAY_TABLE_CELL:
+ fprintf(stream, "display: table-cell ");
+ break;
+ case CSS_DISPLAY_TABLE_CAPTION:
+ fprintf(stream, "display: table-caption ");
+ break;
+ case CSS_DISPLAY_NONE:
+ fprintf(stream, "display: none ");
+ break;
+ default:
+ break;
+ }
+
+ /* empty-cells */
+ val = css_computed_empty_cells(style);
+ switch (val) {
+ case CSS_EMPTY_CELLS_SHOW:
+ fprintf(stream, "empty-cells: show ");
+ break;
+ case CSS_EMPTY_CELLS_HIDE:
+ fprintf(stream, "empty-cells: hide ");
+ break;
+ default:
+ break;
+ }
+
+ /* float */
+ val = css_computed_float(style);
+ switch (val) {
+ case CSS_FLOAT_LEFT:
+ fprintf(stream, "float: left ");
+ break;
+ case CSS_FLOAT_RIGHT:
+ fprintf(stream, "float: right ");
+ break;
+ case CSS_FLOAT_NONE:
+ fprintf(stream, "float: none ");
+ break;
+ default:
+ break;
+ }
+
+ /* font-family */
+ val = css_computed_font_family(style, &string_list);
+ if (val != CSS_FONT_FAMILY_INHERIT) {
+ fprintf(stream, "font-family:");
+
+ if (string_list != NULL) {
+ while (*string_list != NULL) {
+ fprintf(stream, " \"%.*s\"",
+ (int) lwc_string_length(*string_list),
+ lwc_string_data(*string_list));
+
+ string_list++;
+ }
+ }
+ switch (val) {
+ case CSS_FONT_FAMILY_SERIF:
+ fprintf(stream, " serif ");
+ break;
+ case CSS_FONT_FAMILY_SANS_SERIF:
+ fprintf(stream, " sans-serif ");
+ break;
+ case CSS_FONT_FAMILY_CURSIVE:
+ fprintf(stream, " cursive ");
+ break;
+ case CSS_FONT_FAMILY_FANTASY:
+ fprintf(stream, " fantasy ");
+ break;
+ case CSS_FONT_FAMILY_MONOSPACE:
+ fprintf(stream, " monospace ");
+ break;
+ }
+ }
+
+ /* font-size */
+ val = css_computed_font_size(style, &len1, &unit1);
+ switch (val) {
+ case CSS_FONT_SIZE_XX_SMALL:
+ fprintf(stream, "font-size: xx-small ");
+ break;
+ case CSS_FONT_SIZE_X_SMALL:
+ fprintf(stream, "font-size: x-small ");
+ break;
+ case CSS_FONT_SIZE_SMALL:
+ fprintf(stream, "font-size: small ");
+ break;
+ case CSS_FONT_SIZE_MEDIUM:
+ fprintf(stream, "font-size: medium ");
+ break;
+ case CSS_FONT_SIZE_LARGE:
+ fprintf(stream, "font-size: large ");
+ break;
+ case CSS_FONT_SIZE_X_LARGE:
+ fprintf(stream, "font-size: x-large ");
+ break;
+ case CSS_FONT_SIZE_XX_LARGE:
+ fprintf(stream, "font-size: xx-large ");
+ break;
+ case CSS_FONT_SIZE_LARGER:
+ fprintf(stream, "font-size: larger ");
+ break;
+ case CSS_FONT_SIZE_SMALLER:
+ fprintf(stream, "font-size: smaller ");
+ break;
+ case CSS_FONT_SIZE_DIMENSION:
+ fprintf(stream, "font-size: ");
+
+ dump_css_unit(stream, len1, unit1);
+
+ fprintf(stream, " ");
+ break;
+ default:
+ break;
+ }
+
+ /* font-style */
+ val = css_computed_font_style(style);
+ switch (val) {
+ case CSS_FONT_STYLE_NORMAL:
+ fprintf(stream, "font-style: normal ");
+ break;
+ case CSS_FONT_STYLE_ITALIC:
+ fprintf(stream, "font-style: italic ");
+ break;
+ case CSS_FONT_STYLE_OBLIQUE:
+ fprintf(stream, "font-style: oblique ");
+ break;
+ default:
+ break;
+ }
+
+ /* font-variant */
+ val = css_computed_font_variant(style);
+ switch (val) {
+ case CSS_FONT_VARIANT_NORMAL:
+ fprintf(stream, "font-variant: normal ");
+ break;
+ case CSS_FONT_VARIANT_SMALL_CAPS:
+ fprintf(stream, "font-variant: small-caps ");
+ break;
+ default:
+ break;
+ }
+
+ /* font-weight */
+ val = css_computed_font_weight(style);
+ switch (val) {
+ case CSS_FONT_WEIGHT_NORMAL:
+ fprintf(stream, "font-weight: normal ");
+ break;
+ case CSS_FONT_WEIGHT_BOLD:
+ fprintf(stream, "font-weight: bold ");
+ break;
+ case CSS_FONT_WEIGHT_BOLDER:
+ fprintf(stream, "font-weight: bolder ");
+ break;
+ case CSS_FONT_WEIGHT_LIGHTER:
+ fprintf(stream, "font-weight: lighter ");
+ break;
+ case CSS_FONT_WEIGHT_100:
+ fprintf(stream, "font-weight: 100 ");
+ break;
+ case CSS_FONT_WEIGHT_200:
+ fprintf(stream, "font-weight: 200 ");
+ break;
+ case CSS_FONT_WEIGHT_300:
+ fprintf(stream, "font-weight: 300 ");
+ break;
+ case CSS_FONT_WEIGHT_400:
+ fprintf(stream, "font-weight: 400 ");
+ break;
+ case CSS_FONT_WEIGHT_500:
+ fprintf(stream, "font-weight: 500 ");
+ break;
+ case CSS_FONT_WEIGHT_600:
+ fprintf(stream, "font-weight: 600 ");
+ break;
+ case CSS_FONT_WEIGHT_700:
+ fprintf(stream, "font-weight: 700 ");
+ break;
+ case CSS_FONT_WEIGHT_800:
+ fprintf(stream, "font-weight: 800 ");
+ break;
+ case CSS_FONT_WEIGHT_900:
+ fprintf(stream, "font-weight: 900 ");
+ break;
+ default:
+ break;
+ }
+
+ /* height */
+ val = css_computed_height(style, &len1, &unit1);
+ switch (val) {
+ case CSS_HEIGHT_AUTO:
+ fprintf(stream, "height: auto ");
+ break;
+ case CSS_HEIGHT_SET:
+ fprintf(stream, "height: ");
+
+ dump_css_unit(stream, len1, unit1);
+
+ fprintf(stream, " ");
+ break;
+ default:
+ break;
+ }
+
+ /* left */
+ val = css_computed_left(style, &len1, &unit1);
+ switch (val) {
+ case CSS_LEFT_AUTO:
+ fprintf(stream, "left: auto ");
+ break;
+ case CSS_LEFT_SET:
+ fprintf(stream, "left: ");
+
+ dump_css_unit(stream, len1, unit1);
+
+ fprintf(stream, " ");
+ break;
+ default:
+ break;
+ }
+
+ /* letter-spacing */
+ val = css_computed_letter_spacing(style, &len1, &unit1);
+ switch (val) {
+ case CSS_LETTER_SPACING_NORMAL:
+ fprintf(stream, "letter-spacing: normal ");
+ break;
+ case CSS_LETTER_SPACING_SET:
+ fprintf(stream, "letter-spacing: ");
+
+ dump_css_unit(stream, len1, unit1);
+
+ fprintf(stream, " ");
+ break;
+ default:
+ break;
+ }
+
+ /* line-height */
+ val = css_computed_line_height(style, &len1, &unit1);
+ switch (val) {
+ case CSS_LINE_HEIGHT_NORMAL:
+ fprintf(stream, "line-height: normal ");
+ break;
+ case CSS_LINE_HEIGHT_NUMBER:
+ fprintf(stream, "line-height: ");
+
+ dump_css_fixed(stream, len1);
+
+ fprintf(stream, " ");
+ break;
+ case CSS_LINE_HEIGHT_DIMENSION:
+ fprintf(stream, "line-height: ");
+
+ dump_css_unit(stream, len1, unit1);
+
+ fprintf(stream, " ");
+ break;
+ default:
+ break;
+ }
+
+ /* list-style-image */
+ val = css_computed_list_style_image(style, &url);
+ if (url != NULL) {
+ fprintf(stream, "list-style-image: url('%.*s') ",
+ (int) lwc_string_length(url),
+ lwc_string_data(url));
+ } else if (val == CSS_LIST_STYLE_IMAGE_NONE) {
+ fprintf(stream, "list-style-image: none ");
+ }
+
+ /* list-style-position */
+ val = css_computed_list_style_position(style);
+ switch (val) {
+ case CSS_LIST_STYLE_POSITION_INSIDE:
+ fprintf(stream, "list-style-position: inside ");
+ break;
+ case CSS_LIST_STYLE_POSITION_OUTSIDE:
+ fprintf(stream, "list-style-position: outside ");
+ break;
+ default:
+ break;
+ }
+
+ /* list-style-type */
+ val = css_computed_list_style_type(style);
+ switch (val) {
+ case CSS_LIST_STYLE_TYPE_DISC:
+ fprintf(stream, "list-style-type: disc ");
+ break;
+ case CSS_LIST_STYLE_TYPE_CIRCLE:
+ fprintf(stream, "list-style-type: circle ");
+ break;
+ case CSS_LIST_STYLE_TYPE_SQUARE:
+ fprintf(stream, "list-style-type: square ");
+ break;
+ case CSS_LIST_STYLE_TYPE_DECIMAL:
+ fprintf(stream, "list-style-type: decimal ");
+ break;
+ case CSS_LIST_STYLE_TYPE_DECIMAL_LEADING_ZERO:
+ fprintf(stream, "list-style-type: decimal-leading-zero ");
+ break;
+ case CSS_LIST_STYLE_TYPE_LOWER_ROMAN:
+ fprintf(stream, "list-style-type: lower-roman ");
+ break;
+ case CSS_LIST_STYLE_TYPE_UPPER_ROMAN:
+ fprintf(stream, "list-style-type: upper-roman ");
+ break;
+ case CSS_LIST_STYLE_TYPE_LOWER_GREEK:
+ fprintf(stream, "list-style-type: lower-greek ");
+ break;
+ case CSS_LIST_STYLE_TYPE_LOWER_LATIN:
+ fprintf(stream, "list-style-type: lower-latin ");
+ break;
+ case CSS_LIST_STYLE_TYPE_UPPER_LATIN:
+ fprintf(stream, "list-style-type: upper-latin ");
+ break;
+ case CSS_LIST_STYLE_TYPE_ARMENIAN:
+ fprintf(stream, "list-style-type: armenian ");
+ break;
+ case CSS_LIST_STYLE_TYPE_GEORGIAN:
+ fprintf(stream, "list-style-type: georgian ");
+ break;
+ case CSS_LIST_STYLE_TYPE_LOWER_ALPHA:
+ fprintf(stream, "list-style-type: lower-alpha ");
+ break;
+ case CSS_LIST_STYLE_TYPE_UPPER_ALPHA:
+ fprintf(stream, "list-style-type: upper-alpha ");
+ break;
+ case CSS_LIST_STYLE_TYPE_NONE:
+ fprintf(stream, "list-style-type: none ");
+ break;
+ default:
+ break;
+ }
+
+ /* margin-top */
+ val = css_computed_margin_top(style, &len1, &unit1);
+ switch (val) {
+ case CSS_MARGIN_AUTO:
+ fprintf(stream, "margin-top: auto ");
+ break;
+ case CSS_MARGIN_SET:
+ fprintf(stream, "margin-top: ");
+
+ dump_css_unit(stream, len1, unit1);
+
+ fprintf(stream, " ");
+ break;
+ default:
+ break;
+ }
+
+ /* margin-right */
+ val = css_computed_margin_right(style, &len1, &unit1);
+ switch (val) {
+ case CSS_MARGIN_AUTO:
+ fprintf(stream, "margin-right: auto ");
+ break;
+ case CSS_MARGIN_SET:
+ fprintf(stream, "margin-right: ");
+
+ dump_css_unit(stream, len1, unit1);
+
+ fprintf(stream, " ");
+ break;
+ default:
+ break;
+ }
+
+ /* margin-bottom */
+ val = css_computed_margin_bottom(style, &len1, &unit1);
+ switch (val) {
+ case CSS_MARGIN_AUTO:
+ fprintf(stream, "margin-bottom: auto ");
+ break;
+ case CSS_MARGIN_SET:
+ fprintf(stream, "margin-bottom: ");
+
+ dump_css_unit(stream, len1, unit1);
+
+ fprintf(stream, " ");
+ break;
+ default:
+ break;
+ }
+
+ /* margin-left */
+ val = css_computed_margin_left(style, &len1, &unit1);
+ switch (val) {
+ case CSS_MARGIN_AUTO:
+ fprintf(stream, "margin-left: auto ");
+ break;
+ case CSS_MARGIN_SET:
+ fprintf(stream, "margin-left: ");
+
+ dump_css_unit(stream, len1, unit1);
+
+ fprintf(stream, " ");
+ break;
+ default:
+ break;
+ }
+
+ /* max-height */
+ val = css_computed_max_height(style, &len1, &unit1);
+ switch (val) {
+ case CSS_MAX_HEIGHT_NONE:
+ fprintf(stream, "max-height: none ");
+ break;
+ case CSS_MAX_HEIGHT_SET:
+ fprintf(stream, "max-height: ");
+
+ dump_css_unit(stream, len1, unit1);
+
+ fprintf(stream, " ");
+ break;
+ default:
+ break;
+ }
+
+ /* max-width */
+ val = css_computed_max_width(style, &len1, &unit1);
+ switch (val) {
+ case CSS_MAX_WIDTH_NONE:
+ fprintf(stream, "max-width: none ");
+ break;
+ case CSS_MAX_WIDTH_SET:
+ fprintf(stream, "max-width: ");
+
+ dump_css_unit(stream, len1, unit1);
+
+ fprintf(stream, " ");
+ break;
+ default:
+ break;
+ }
+
+ /* min-height */
+ val = css_computed_min_height(style, &len1, &unit1);
+ switch (val) {
+ case CSS_MIN_HEIGHT_SET:
+ fprintf(stream, "min-height: ");
+
+ dump_css_unit(stream, len1, unit1);
+
+ fprintf(stream, " ");
+ break;
+ default:
+ break;
+ }
+
+ /* min-width */
+ val = css_computed_min_width(style, &len1, &unit1);
+ switch (val) {
+ case CSS_MIN_WIDTH_SET:
+ fprintf(stream, "min-width: ");
+
+ dump_css_unit(stream, len1, unit1);
+
+ fprintf(stream, " ");
+ break;
+ default:
+ break;
+ }
+
+ /* opacity */
+ val = css_computed_opacity(style, &len1);
+ switch (val) {
+ case CSS_OPACITY_SET:
+ fprintf(stream, "opacity: ");
+
+ dump_css_fixed(stream, len1);
+
+ fprintf(stream, " ");
+ break;
+ default:
+ break;
+ }
+
+ /* outline-color */
+ val = css_computed_outline_color(style, &color);
+ switch (val) {
+ case CSS_OUTLINE_COLOR_INVERT:
+ fprintf(stream, "outline-color: invert ");
+ break;
+ case CSS_OUTLINE_COLOR_COLOR:
+ fprintf(stream, "outline-color: #%08x ", color);
+ break;
+ default:
+ break;
+ }
+
+ /* outline-style */
+ val = css_computed_outline_style(style);
+ switch (val) {
+ case CSS_OUTLINE_STYLE_NONE:
+ fprintf(stream, "outline-style: none ");
+ break;
+ case CSS_OUTLINE_STYLE_DOTTED:
+ fprintf(stream, "outline-style: dotted ");
+ break;
+ case CSS_OUTLINE_STYLE_DASHED:
+ fprintf(stream, "outline-style: dashed ");
+ break;
+ case CSS_OUTLINE_STYLE_SOLID:
+ fprintf(stream, "outline-style: solid ");
+ break;
+ case CSS_OUTLINE_STYLE_DOUBLE:
+ fprintf(stream, "outline-style: double ");
+ break;
+ case CSS_OUTLINE_STYLE_GROOVE:
+ fprintf(stream, "outline-style: groove ");
+ break;
+ case CSS_OUTLINE_STYLE_RIDGE:
+ fprintf(stream, "outline-style: ridge ");
+ break;
+ case CSS_OUTLINE_STYLE_INSET:
+ fprintf(stream, "outline-style: inset ");
+ break;
+ case CSS_OUTLINE_STYLE_OUTSET:
+ fprintf(stream, "outline-style: outset ");
+ break;
+ default:
+ break;
+ }
+
+ /* outline-width */
+ val = css_computed_outline_width(style, &len1, &unit1);
+ switch (val) {
+ case CSS_OUTLINE_WIDTH_THIN:
+ fprintf(stream, "outline-width: thin ");
+ break;
+ case CSS_OUTLINE_WIDTH_MEDIUM:
+ fprintf(stream, "outline-width: medium ");
+ break;
+ case CSS_OUTLINE_WIDTH_THICK:
+ fprintf(stream, "outline-width: thick ");
+ break;
+ case CSS_OUTLINE_WIDTH_WIDTH:
+ fprintf(stream, "outline-width: ");
+
+ dump_css_unit(stream, len1, unit1);
+
+ fprintf(stream, " ");
+ break;
+ default:
+ break;
+ }
+
+ /* overflow */
+ val = css_computed_overflow_x(style);
+ switch (val) {
+ case CSS_OVERFLOW_VISIBLE:
+ fprintf(stream, "overflow-x: visible ");
+ break;
+ case CSS_OVERFLOW_HIDDEN:
+ fprintf(stream, "overflow-x: hidden ");
+ break;
+ case CSS_OVERFLOW_SCROLL:
+ fprintf(stream, "overflow-x: scroll ");
+ break;
+ case CSS_OVERFLOW_AUTO:
+ fprintf(stream, "overflow-x auto ");
+ break;
+ default:
+ break;
+ }
+
+ /* overflow */
+ val = css_computed_overflow_y(style);
+ switch (val) {
+ case CSS_OVERFLOW_VISIBLE:
+ fprintf(stream, "overflow-y: visible ");
+ break;
+ case CSS_OVERFLOW_HIDDEN:
+ fprintf(stream, "overflow-y: hidden ");
+ break;
+ case CSS_OVERFLOW_SCROLL:
+ fprintf(stream, "overflow-y: scroll ");
+ break;
+ case CSS_OVERFLOW_AUTO:
+ fprintf(stream, "overflow-y: auto ");
+ break;
+ default:
+ break;
+ }
+
+ /* padding-top */
+ val = css_computed_padding_top(style, &len1, &unit1);
+ switch (val) {
+ case CSS_PADDING_SET:
+ fprintf(stream, "padding-top: ");
+
+ dump_css_unit(stream, len1, unit1);
+
+ fprintf(stream, " ");
+ break;
+ default:
+ break;
+ }
+
+ /* padding-right */
+ val = css_computed_padding_right(style, &len1, &unit1);
+ switch (val) {
+ case CSS_PADDING_SET:
+ fprintf(stream, "padding-right: ");
+
+ dump_css_unit(stream, len1, unit1);
+
+ fprintf(stream, " ");
+ break;
+ default:
+ break;
+ }
+
+ /* padding-bottom */
+ val = css_computed_padding_bottom(style, &len1, &unit1);
+ switch (val) {
+ case CSS_PADDING_SET:
+ fprintf(stream, "padding-bottom: ");
+
+ dump_css_unit(stream, len1, unit1);
+
+ fprintf(stream, " ");
+ break;
+ default:
+ break;
+ }
+
+ /* padding-left */
+ val = css_computed_padding_left(style, &len1, &unit1);
+ switch (val) {
+ case CSS_PADDING_SET:
+ fprintf(stream, "padding-left: ");
+
+ dump_css_unit(stream, len1, unit1);
+
+ fprintf(stream, " ");
+ break;
+ default:
+ break;
+ }
+
+ /* position */
+ val = css_computed_position(style);
+ switch (val) {
+ case CSS_POSITION_STATIC:
+ fprintf(stream, "position: static ");
+ break;
+ case CSS_POSITION_RELATIVE:
+ fprintf(stream, "position: relative ");
+ break;
+ case CSS_POSITION_ABSOLUTE:
+ fprintf(stream, "position: absolute ");
+ break;
+ case CSS_POSITION_FIXED:
+ fprintf(stream, "position: fixed ");
+ break;
+ default:
+ break;
+ }
+
+ /* quotes */
+ val = css_computed_quotes(style, &string_list);
+ if (val == CSS_QUOTES_STRING && string_list != NULL) {
+ fprintf(stream, "quotes:");
+
+ while (*string_list != NULL) {
+ fprintf(stream, " \"%.*s\"",
+ (int) lwc_string_length(*string_list),
+ lwc_string_data(*string_list));
+
+ string_list++;
+ }
+
+ fprintf(stream, " ");
+ } else {
+ switch (val) {
+ case CSS_QUOTES_NONE:
+ fprintf(stream, "quotes: none ");
+ break;
+ default:
+ break;
+ }
+ }
+
+ /* right */
+ val = css_computed_right(style, &len1, &unit1);
+ switch (val) {
+ case CSS_RIGHT_AUTO:
+ fprintf(stream, "right: auto ");
+ break;
+ case CSS_RIGHT_SET:
+ fprintf(stream, "right: ");
+
+ dump_css_unit(stream, len1, unit1);
+
+ fprintf(stream, " ");
+ break;
+ default:
+ break;
+ }
+
+ /* table-layout */
+ val = css_computed_table_layout(style);
+ switch (val) {
+ case CSS_TABLE_LAYOUT_AUTO:
+ fprintf(stream, "table-layout: auto ");
+ break;
+ case CSS_TABLE_LAYOUT_FIXED:
+ fprintf(stream, "table-layout: fixed ");
+ break;
+ default:
+ break;
+ }
+
+ /* text-align */
+ val = css_computed_text_align(style);
+ switch (val) {
+ case CSS_TEXT_ALIGN_LEFT:
+ fprintf(stream, "text-align: left ");
+ break;
+ case CSS_TEXT_ALIGN_RIGHT:
+ fprintf(stream, "text-align: right ");
+ break;
+ case CSS_TEXT_ALIGN_CENTER:
+ fprintf(stream, "text-align: center ");
+ break;
+ case CSS_TEXT_ALIGN_JUSTIFY:
+ fprintf(stream, "text-align: justify ");
+ break;
+ case CSS_TEXT_ALIGN_DEFAULT:
+ fprintf(stream, "text-align: default ");
+ break;
+ case CSS_TEXT_ALIGN_LIBCSS_LEFT:
+ fprintf(stream, "text-align: -libcss-left ");
+ break;
+ case CSS_TEXT_ALIGN_LIBCSS_CENTER:
+ fprintf(stream, "text-align: -libcss-center ");
+ break;
+ case CSS_TEXT_ALIGN_LIBCSS_RIGHT:
+ fprintf(stream, "text-align: -libcss-right ");
+ break;
+ default:
+ break;
+ }
+
+ /* text-decoration */
+ val = css_computed_text_decoration(style);
+ if (val == CSS_TEXT_DECORATION_NONE) {
+ fprintf(stream, "text-decoration: none ");
+ } else {
+ fprintf(stream, "text-decoration:");
+
+ if (val & CSS_TEXT_DECORATION_BLINK) {
+ fprintf(stream, " blink");
+ }
+ if (val & CSS_TEXT_DECORATION_LINE_THROUGH) {
+ fprintf(stream, " line-through");
+ }
+ if (val & CSS_TEXT_DECORATION_OVERLINE) {
+ fprintf(stream, " overline");
+ }
+ if (val & CSS_TEXT_DECORATION_UNDERLINE) {
+ fprintf(stream, " underline");
+ }
+
+ fprintf(stream, " ");
+ }
+
+ /* text-indent */
+ val = css_computed_text_indent(style, &len1, &unit1);
+ switch (val) {
+ case CSS_TEXT_INDENT_SET:
+ fprintf(stream, "text-indent: ");
+
+ dump_css_unit(stream, len1, unit1);
+
+ fprintf(stream, " ");
+ break;
+ default:
+ break;
+ }
+
+ /* text-transform */
+ val = css_computed_text_transform(style);
+ switch (val) {
+ case CSS_TEXT_TRANSFORM_CAPITALIZE:
+ fprintf(stream, "text-transform: capitalize ");
+ break;
+ case CSS_TEXT_TRANSFORM_UPPERCASE:
+ fprintf(stream, "text-transform: uppercase ");
+ break;
+ case CSS_TEXT_TRANSFORM_LOWERCASE:
+ fprintf(stream, "text-transform: lowercase ");
+ break;
+ case CSS_TEXT_TRANSFORM_NONE:
+ fprintf(stream, "text-transform: none ");
+ break;
+ default:
+ break;
+ }
+
+ /* top */
+ val = css_computed_top(style, &len1, &unit1);
+ switch (val) {
+ case CSS_TOP_AUTO:
+ fprintf(stream, "top: auto ");
+ break;
+ case CSS_TOP_SET:
+ fprintf(stream, "top: ");
+
+ dump_css_unit(stream, len1, unit1);
+
+ fprintf(stream, " ");
+ break;
+ default:
+ break;
+ }
+
+ /* unicode-bidi */
+ val = css_computed_unicode_bidi(style);
+ switch (val) {
+ case CSS_UNICODE_BIDI_NORMAL:
+ fprintf(stream, "unicode-bidi: normal ");
+ break;
+ case CSS_UNICODE_BIDI_EMBED:
+ fprintf(stream, "unicode-bidi: embed ");
+ break;
+ case CSS_UNICODE_BIDI_BIDI_OVERRIDE:
+ fprintf(stream, "unicode-bidi: bidi-override ");
+ break;
+ default:
+ break;
+ }
+
+ /* vertical-align */
+ val = css_computed_vertical_align(style, &len1, &unit1);
+ switch (val) {
+ case CSS_VERTICAL_ALIGN_BASELINE:
+ fprintf(stream, "vertical-align: baseline ");
+ break;
+ case CSS_VERTICAL_ALIGN_SUB:
+ fprintf(stream, "vertical-align: sub ");
+ break;
+ case CSS_VERTICAL_ALIGN_SUPER:
+ fprintf(stream, "vertical-align: super ");
+ break;
+ case CSS_VERTICAL_ALIGN_TOP:
+ fprintf(stream, "vertical-align: top ");
+ break;
+ case CSS_VERTICAL_ALIGN_TEXT_TOP:
+ fprintf(stream, "vertical-align: text-top ");
+ break;
+ case CSS_VERTICAL_ALIGN_MIDDLE:
+ fprintf(stream, "vertical-align: middle ");
+ break;
+ case CSS_VERTICAL_ALIGN_BOTTOM:
+ fprintf(stream, "vertical-align: bottom ");
+ break;
+ case CSS_VERTICAL_ALIGN_TEXT_BOTTOM:
+ fprintf(stream, "vertical-align: text-bottom ");
+ break;
+ case CSS_VERTICAL_ALIGN_SET:
+ fprintf(stream, "vertical-align: ");
+
+ dump_css_unit(stream, len1, unit1);
+
+ fprintf(stream, " ");
+ break;
+ default:
+ break;
+ }
+
+ /* visibility */
+ val = css_computed_visibility(style);
+ switch (val) {
+ case CSS_VISIBILITY_VISIBLE:
+ fprintf(stream, "visibility: visible ");
+ break;
+ case CSS_VISIBILITY_HIDDEN:
+ fprintf(stream, "visibility: hidden ");
+ break;
+ case CSS_VISIBILITY_COLLAPSE:
+ fprintf(stream, "visibility: collapse ");
+ break;
+ default:
+ break;
+ }
+
+ /* white-space */
+ val = css_computed_white_space(style);
+ switch (val) {
+ case CSS_WHITE_SPACE_NORMAL:
+ fprintf(stream, "white-space: normal ");
+ break;
+ case CSS_WHITE_SPACE_PRE:
+ fprintf(stream, "white-space: pre ");
+ break;
+ case CSS_WHITE_SPACE_NOWRAP:
+ fprintf(stream, "white-space: nowrap ");
+ break;
+ case CSS_WHITE_SPACE_PRE_WRAP:
+ fprintf(stream, "white-space: pre-wrap ");
+ break;
+ case CSS_WHITE_SPACE_PRE_LINE:
+ fprintf(stream, "white-space: pre-line ");
+ break;
+ default:
+ break;
+ }
+
+ /* width */
+ val = css_computed_width(style, &len1, &unit1);
+ switch (val) {
+ case CSS_WIDTH_AUTO:
+ fprintf(stream, "width: auto ");
+ break;
+ case CSS_WIDTH_SET:
+ fprintf(stream, "width: ");
+
+ dump_css_unit(stream, len1, unit1);
+
+ fprintf(stream, " ");
+ break;
+ default:
+ break;
+ }
+
+ /* word-spacing */
+ val = css_computed_word_spacing(style, &len1, &unit1);
+ switch (val) {
+ case CSS_WORD_SPACING_NORMAL:
+ fprintf(stream, "word-spacing: normal ");
+ break;
+ case CSS_WORD_SPACING_SET:
+ fprintf(stream, "word-spacing: ");
+
+ dump_css_unit(stream, len1, unit1);
+
+ fprintf(stream, " ");
+ break;
+ default:
+ break;
+ }
+
+ /* z-index */
+ val = css_computed_z_index(style, &zindex);
+ switch (val) {
+ case CSS_Z_INDEX_AUTO:
+ fprintf(stream, "z-index: auto ");
+ break;
+ case CSS_Z_INDEX_SET:
+ fprintf(stream, "z-index: %d ", zindex);
+ break;
+ default:
+ break;
+ }
+
+ fprintf(stream, "}");
+}
diff --git a/content/handlers/css/dump.h b/content/handlers/css/dump.h
new file mode 100644
index 000000000..470e56481
--- /dev/null
+++ b/content/handlers/css/dump.h
@@ -0,0 +1,30 @@
+/*
+ * 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/>.
+ */
+
+#ifndef NETSURF_CSS_DUMP_H_
+#define NETSURF_CSS_DUMP_H_
+
+/**
+ * Dump a computed style \a style to the give file handle \a stream.
+ *
+ * \param stream Stream to write to
+ * \param style Computed style to dump
+ */
+void nscss_dump_computed_style(FILE *stream, const css_computed_style *style);
+
+#endif
diff --git a/content/handlers/css/hints.c b/content/handlers/css/hints.c
new file mode 100644
index 000000000..f394ea836
--- /dev/null
+++ b/content/handlers/css/hints.c
@@ -0,0 +1,1604 @@
+/*
+ * 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 <string.h>
+#include <strings.h>
+
+#include "utils/nsoption.h"
+#include "utils/corestrings.h"
+#include "utils/log.h"
+#include "utils/nsurl.h"
+#include "utils/utils.h"
+
+#include "hints.h"
+#include "select.h"
+
+#define LOG_STATS
+#undef LOG_STATS
+
+/******************************************************************************
+ * Utility functions *
+ ******************************************************************************/
+
+/**
+ * Determine if a given character is whitespace
+ *
+ * \param c Character to consider
+ * \return true if character is whitespace, false otherwise
+ */
+static bool isWhitespace(char c)
+{
+ return c == ' ' || c == '\t' || c == '\f' || c == '\r' || c == '\n';
+}
+
+/**
+ * Determine if a given character is a valid hex digit
+ *
+ * \param c Character to consider
+ * \return true if character is a valid hex digit, false otherwise
+ */
+static bool isHex(char c)
+{
+ return ('0' <= c && c <= '9') ||
+ ('A' <= (c & ~0x20) && (c & ~0x20) <= 'F');
+}
+
+/**
+ * Convert a character representing a hex digit to the corresponding hex value
+ *
+ * \param c Character to convert
+ * \return Hex value represented by character
+ *
+ * \note This function assumes an ASCII-compatible character set
+ */
+static uint8_t charToHex(char c)
+{
+ /* 0-9 */
+ c -= '0';
+
+ /* A-F */
+ if (c > 9)
+ c -= 'A' - '9' - 1;
+
+ /* a-f */
+ if (c > 15)
+ c -= 'a' - 'A';
+
+ return c;
+}
+
+
+/******************************************************************************
+ * Common parsing functions *
+ ******************************************************************************/
+
+/**
+ * Parse a number string
+ *
+ * \param data Data to parse (NUL-terminated)
+ * \param maybe_negative Negative numbers permitted
+ * \param real Floating point numbers permitted
+ * \param value Pointer to location to receive numeric value
+ * \param consumed Pointer to location to receive number of input
+ * bytes consumed
+ * \return true on success, false on invalid input
+ */
+static bool parse_number(const char *data, bool maybe_negative, bool real,
+ css_fixed *value, size_t *consumed)
+{
+ size_t len;
+ const uint8_t *ptr;
+ int32_t intpart = 0;
+ int32_t fracpart = 0;
+ int32_t pwr = 1;
+ int sign = 1;
+
+ *consumed = 0;
+
+ len = strlen(data);
+ ptr = (const uint8_t *) data;
+
+ if (len == 0)
+ return false;
+
+ /* Skip leading whitespace */
+ while (len > 0 && isWhitespace(ptr[0])) {
+ len--;
+ ptr++;
+ }
+
+ if (len == 0)
+ return false;
+
+ /* Extract sign, if any */
+ if (ptr[0] == '+') {
+ len--;
+ ptr++;
+ } else if (ptr[0] == '-' && maybe_negative) {
+ sign = -1;
+ len--;
+ ptr++;
+ }
+
+ if (len == 0)
+ return false;
+
+ /* Must have a digit [0,9] */
+ if ('0' > ptr[0] || ptr[0] > '9')
+ return false;
+
+ /* Now extract intpart, assuming base 10 */
+ while (len > 0) {
+ /* Stop on first non-digit */
+ if (ptr[0] < '0' || '9' < ptr[0])
+ break;
+
+ /* Prevent overflow of 'intpart'; proper clamping below */
+ if (intpart < (1 << 22)) {
+ intpart *= 10;
+ intpart += ptr[0] - '0';
+ }
+ ptr++;
+ len--;
+ }
+
+ /* And fracpart, again, assuming base 10 */
+ if (real && len > 1 && ptr[0] == '.' &&
+ ('0' <= ptr[1] && ptr[1] <= '9')) {
+ ptr++;
+ len--;
+
+ while (len > 0) {
+ if (ptr[0] < '0' || '9' < ptr[0])
+ break;
+
+ if (pwr < 1000000) {
+ pwr *= 10;
+ fracpart *= 10;
+ fracpart += ptr[0] - '0';
+ }
+ ptr++;
+ len--;
+ }
+
+ fracpart = ((1 << 10) * fracpart + pwr/2) / pwr;
+ if (fracpart >= (1 << 10)) {
+ intpart++;
+ fracpart &= (1 << 10) - 1;
+ }
+ }
+
+ if (sign > 0) {
+ /* If the result is larger than we can represent,
+ * then clamp to the maximum value we can store. */
+ if (intpart >= (1 << 21)) {
+ intpart = (1 << 21) - 1;
+ fracpart = (1 << 10) - 1;
+ }
+ } else {
+ /* If the negated result is smaller than we can represent
+ * then clamp to the minimum value we can store. */
+ if (intpart >= (1 << 21)) {
+ intpart = -(1 << 21);
+ fracpart = 0;
+ } else {
+ intpart = -intpart;
+ if (fracpart) {
+ fracpart = (1 << 10) - fracpart;
+ intpart--;
+ }
+ }
+ }
+
+ *value = (intpart << 10) | fracpart;
+
+ *consumed = ptr - (const uint8_t *) data;
+
+ return true;
+}
+
+/**
+ * Parse a dimension string
+ *
+ * \param data Data to parse (NUL-terminated)
+ * \param strict Whether to enforce strict parsing rules
+ * \param length Pointer to location to receive dimension's length
+ * \param unit Pointer to location to receive dimension's unit
+ * \return true on success, false on invalid input
+ */
+static bool parse_dimension(const char *data, bool strict, css_fixed *length,
+ css_unit *unit)
+{
+ size_t len;
+ size_t read;
+ css_fixed value;
+
+ len = strlen(data);
+
+ if (parse_number(data, false, true, &value, &read) == false)
+ return false;
+
+ if (strict && value < INTTOFIX(1))
+ return false;
+
+ *length = value;
+
+ if (len > read && data[read] == '%')
+ *unit = CSS_UNIT_PCT;
+ else
+ *unit = CSS_UNIT_PX;
+
+ return true;
+}
+
+/**
+ * Mapping of colour name to CSS color
+ */
+struct colour_map {
+ const char *name;
+ css_color color;
+};
+
+/**
+ * Name comparator for named colour matching
+ *
+ * \param a Name to match
+ * \param b Colour map entry to consider
+ * \return 0 on match,
+ * < 0 if a < b,
+ * > 0 if b > a.
+ */
+static int cmp_colour_name(const void *a, const void *b)
+{
+ const char *aa = a;
+ const struct colour_map *bb = b;
+
+ return strcasecmp(aa, bb->name);
+}
+
+/**
+ * Parse a named colour
+ *
+ * \param name Name to parse
+ * \param result Pointer to location to receive css_color
+ * \return true on success, false on invalid input
+ */
+static bool parse_named_colour(const char *name, css_color *result)
+{
+ static const struct colour_map named_colours[] = {
+ { "aliceblue", 0xfff0f8ff },
+ { "antiquewhite", 0xfffaebd7 },
+ { "aqua", 0xff00ffff },
+ { "aquamarine", 0xff7fffd4 },
+ { "azure", 0xfff0ffff },
+ { "beige", 0xfff5f5dc },
+ { "bisque", 0xffffe4c4 },
+ { "black", 0xff000000 },
+ { "blanchedalmond", 0xffffebcd },
+ { "blue", 0xff0000ff },
+ { "blueviolet", 0xff8a2be2 },
+ { "brown", 0xffa52a2a },
+ { "burlywood", 0xffdeb887 },
+ { "cadetblue", 0xff5f9ea0 },
+ { "chartreuse", 0xff7fff00 },
+ { "chocolate", 0xffd2691e },
+ { "coral", 0xffff7f50 },
+ { "cornflowerblue", 0xff6495ed },
+ { "cornsilk", 0xfffff8dc },
+ { "crimson", 0xffdc143c },
+ { "cyan", 0xff00ffff },
+ { "darkblue", 0xff00008b },
+ { "darkcyan", 0xff008b8b },
+ { "darkgoldenrod", 0xffb8860b },
+ { "darkgray", 0xffa9a9a9 },
+ { "darkgreen", 0xff006400 },
+ { "darkgrey", 0xffa9a9a9 },
+ { "darkkhaki", 0xffbdb76b },
+ { "darkmagenta", 0xff8b008b },
+ { "darkolivegreen", 0xff556b2f },
+ { "darkorange", 0xffff8c00 },
+ { "darkorchid", 0xff9932cc },
+ { "darkred", 0xff8b0000 },
+ { "darksalmon", 0xffe9967a },
+ { "darkseagreen", 0xff8fbc8f },
+ { "darkslateblue", 0xff483d8b },
+ { "darkslategray", 0xff2f4f4f },
+ { "darkslategrey", 0xff2f4f4f },
+ { "darkturquoise", 0xff00ced1 },
+ { "darkviolet", 0xff9400d3 },
+ { "deeppink", 0xffff1493 },
+ { "deepskyblue", 0xff00bfff },
+ { "dimgray", 0xff696969 },
+ { "dimgrey", 0xff696969 },
+ { "dodgerblue", 0xff1e90ff },
+ { "feldspar", 0xffd19275 },
+ { "firebrick", 0xffb22222 },
+ { "floralwhite", 0xfffffaf0 },
+ { "forestgreen", 0xff228b22 },
+ { "fuchsia", 0xffff00ff },
+ { "gainsboro", 0xffdcdcdc },
+ { "ghostwhite", 0xfff8f8ff },
+ { "gold", 0xffffd700 },
+ { "goldenrod", 0xffdaa520 },
+ { "gray", 0xff808080 },
+ { "green", 0xff008000 },
+ { "greenyellow", 0xffadff2f },
+ { "grey", 0xff808080 },
+ { "honeydew", 0xfff0fff0 },
+ { "hotpink", 0xffff69b4 },
+ { "indianred", 0xffcd5c5c },
+ { "indigo", 0xff4b0082 },
+ { "ivory", 0xfffffff0 },
+ { "khaki", 0xfff0e68c },
+ { "lavender", 0xffe6e6fa },
+ { "lavenderblush", 0xfffff0f5 },
+ { "lawngreen", 0xff7cfc00 },
+ { "lemonchiffon", 0xfffffacd },
+ { "lightblue", 0xffadd8e6 },
+ { "lightcoral", 0xfff08080 },
+ { "lightcyan", 0xffe0ffff },
+ { "lightgoldenrodyellow", 0xfffafad2 },
+ { "lightgray", 0xffd3d3d3 },
+ { "lightgreen", 0xff90ee90 },
+ { "lightgrey", 0xffd3d3d3 },
+ { "lightpink", 0xffffb6c1 },
+ { "lightsalmon", 0xffffa07a },
+ { "lightseagreen", 0xff20b2aa },
+ { "lightskyblue", 0xff87cefa },
+ { "lightslateblue", 0xff8470ff },
+ { "lightslategray", 0xff778899 },
+ { "lightslategrey", 0xff778899 },
+ { "lightsteelblue", 0xffb0c4de },
+ { "lightyellow", 0xffffffe0 },
+ { "lime", 0xff00ff00 },
+ { "limegreen", 0xff32cd32 },
+ { "linen", 0xfffaf0e6 },
+ { "magenta", 0xffff00ff },
+ { "maroon", 0xff800000 },
+ { "mediumaquamarine", 0xff66cdaa },
+ { "mediumblue", 0xff0000cd },
+ { "mediumorchid", 0xffba55d3 },
+ { "mediumpurple", 0xff9370db },
+ { "mediumseagreen", 0xff3cb371 },
+ { "mediumslateblue", 0xff7b68ee },
+ { "mediumspringgreen", 0xff00fa9a },
+ { "mediumturquoise", 0xff48d1cc },
+ { "mediumvioletred", 0xffc71585 },
+ { "midnightblue", 0xff191970 },
+ { "mintcream", 0xfff5fffa },
+ { "mistyrose", 0xffffe4e1 },
+ { "moccasin", 0xffffe4b5 },
+ { "navajowhite", 0xffffdead },
+ { "navy", 0xff000080 },
+ { "oldlace", 0xfffdf5e6 },
+ { "olive", 0xff808000 },
+ { "olivedrab", 0xff6b8e23 },
+ { "orange", 0xffffa500 },
+ { "orangered", 0xffff4500 },
+ { "orchid", 0xffda70d6 },
+ { "palegoldenrod", 0xffeee8aa },
+ { "palegreen", 0xff98fb98 },
+ { "paleturquoise", 0xffafeeee },
+ { "palevioletred", 0xffdb7093 },
+ { "papayawhip", 0xffffefd5 },
+ { "peachpuff", 0xffffdab9 },
+ { "peru", 0xffcd853f },
+ { "pink", 0xffffc0cb },
+ { "plum", 0xffdda0dd },
+ { "powderblue", 0xffb0e0e6 },
+ { "purple", 0xff800080 },
+ { "red", 0xffff0000 },
+ { "rosybrown", 0xffbc8f8f },
+ { "royalblue", 0xff4169e1 },
+ { "saddlebrown", 0xff8b4513 },
+ { "salmon", 0xfffa8072 },
+ { "sandybrown", 0xfff4a460 },
+ { "seagreen", 0xff2e8b57 },
+ { "seashell", 0xfffff5ee },
+ { "sienna", 0xffa0522d },
+ { "silver", 0xffc0c0c0 },
+ { "skyblue", 0xff87ceeb },
+ { "slateblue", 0xff6a5acd },
+ { "slategray", 0xff708090 },
+ { "slategrey", 0xff708090 },
+ { "snow", 0xfffffafa },
+ { "springgreen", 0xff00ff7f },
+ { "steelblue", 0xff4682b4 },
+ { "tan", 0xffd2b48c },
+ { "teal", 0xff008080 },
+ { "thistle", 0xffd8bfd8 },
+ { "tomato", 0xffff6347 },
+ { "turquoise", 0xff40e0d0 },
+ { "violet", 0xffee82ee },
+ { "violetred", 0xffd02090 },
+ { "wheat", 0xfff5deb3 },
+ { "white", 0xffffffff },
+ { "whitesmoke", 0xfff5f5f5 },
+ { "yellow", 0xffffff00 },
+ { "yellowgreen", 0xff9acd32 }
+ };
+ const struct colour_map *entry;
+
+ entry = bsearch(name, named_colours,
+ sizeof(named_colours) / sizeof(named_colours[0]),
+ sizeof(named_colours[0]),
+ cmp_colour_name);
+
+ if (entry != NULL)
+ *result = entry->color;
+
+ return entry != NULL;
+}
+
+/* exported interface documented in content/handlers/css/hints.h */
+bool nscss_parse_colour(const char *data, css_color *result)
+{
+ size_t len = strlen(data);
+ uint8_t r, g, b;
+
+ /* 2 */
+ if (len == 0)
+ return false;
+
+ /* 3 */
+ if (len == SLEN("transparent") && strcasecmp(data, "transparent") == 0)
+ return false;
+
+ /* 4 */
+ if (parse_named_colour(data, result))
+ return true;
+
+ /** \todo Implement HTML5's utterly insane legacy colour parsing */
+
+ if (data[0] == '#') {
+ data++;
+ len--;
+ }
+
+ if (len == 3 && isHex(data[0]) && isHex(data[1]) && isHex(data[2])) {
+ r = charToHex(data[0]);
+ g = charToHex(data[1]);
+ b = charToHex(data[2]);
+
+ r |= (r << 4);
+ g |= (g << 4);
+ b |= (b << 4);
+
+ *result = (0xff << 24) | (r << 16) | (g << 8) | b;
+
+ return true;
+ } else if (len == 6 && isHex(data[0]) && isHex(data[1]) &&
+ isHex(data[2]) && isHex(data[3]) && isHex(data[4]) &&
+ isHex(data[5])) {
+ r = (charToHex(data[0]) << 4) | charToHex(data[1]);
+ g = (charToHex(data[2]) << 4) | charToHex(data[3]);
+ b = (charToHex(data[4]) << 4) | charToHex(data[5]);
+
+ *result = (0xff << 24) | (r << 16) | (g << 8) | b;
+
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * Parse a font \@size attribute
+ *
+ * \param size Data to parse (NUL-terminated)
+ * \param val Pointer to location to receive enum value
+ * \param len Pointer to location to receive length
+ * \param unit Pointer to location to receive unit
+ * \return True on success, false on failure
+ */
+static bool parse_font_size(const char *size, uint8_t *val,
+ css_fixed *len, css_unit *unit)
+{
+ static const uint8_t size_map[] = {
+ CSS_FONT_SIZE_XX_SMALL,
+ CSS_FONT_SIZE_SMALL,
+ CSS_FONT_SIZE_MEDIUM,
+ CSS_FONT_SIZE_LARGE,
+ CSS_FONT_SIZE_X_LARGE,
+ CSS_FONT_SIZE_XX_LARGE,
+ CSS_FONT_SIZE_DIMENSION /* xxx-large (see below) */
+ };
+
+ const char *p = size;
+ char mode;
+ int value = 0;
+
+ /* Skip whitespace */
+ while (*p != '\0' && isWhitespace(*p))
+ p++;
+
+ mode = *p;
+
+ /* Skip +/- */
+ if (mode == '+' || mode == '-')
+ p++;
+
+ /* Need at least one digit */
+ if (*p < '0' || *p > '9') {
+ return false;
+ }
+
+ /* Consume digits, computing value */
+ while ('0' <= *p && *p <= '9') {
+ value = value * 10 + (*p - '0');
+ p++;
+ }
+
+ /* Resolve relative sizes */
+ if (mode == '+')
+ value += 3;
+ else if (mode == '-')
+ value = 3 - value;
+
+ /* Clamp to range [1,7] */
+ if (value < 1)
+ value = 1;
+ else if (value > 7)
+ value = 7;
+
+ if (value == 7) {
+ /* Manufacture xxx-large */
+ *len = FDIV(FMUL(INTTOFIX(3), INTTOFIX(nsoption_int(font_size))),
+ F_10);
+ } else {
+ /* Len is irrelevant */
+ *len = 0;
+ }
+
+ *unit = CSS_UNIT_PT;
+ *val = size_map[value - 1];
+
+ return true;
+}
+
+
+/******************************************************************************
+ * Hint context management *
+ ******************************************************************************/
+
+#define MAX_HINTS_PER_ELEMENT 32
+
+struct css_hint_ctx {
+ struct css_hint *hints;
+ uint32_t len;
+};
+
+struct css_hint_ctx hint_ctx;
+
+nserror css_hint_init(void)
+{
+ hint_ctx.hints = malloc(sizeof(struct css_hint) *
+ MAX_HINTS_PER_ELEMENT);
+ if (hint_ctx.hints == NULL) {
+ return NSERROR_NOMEM;
+ }
+
+ return NSERROR_OK;
+}
+
+void css_hint_fini(void)
+{
+ hint_ctx.len = 0;
+ free(hint_ctx.hints);
+}
+
+static void css_hint_clean(void)
+{
+ hint_ctx.len = 0;
+}
+
+static inline struct css_hint * css_hint_advance(struct css_hint *hint)
+{
+ hint_ctx.len++;
+ assert(hint_ctx.len < MAX_HINTS_PER_ELEMENT);
+
+ return ++hint;
+}
+
+static void css_hint_get_hints(struct css_hint **hints, uint32_t *nhints)
+{
+ *hints = hint_ctx.hints;
+ *nhints = hint_ctx.len;
+}
+
+
+/******************************************************************************
+ * Presentational hint handlers *
+ ******************************************************************************/
+
+static void css_hint_table_cell_border_padding(
+ nscss_select_ctx *ctx,
+ dom_node *node)
+{
+ struct css_hint *hint = &hint_ctx.hints[hint_ctx.len];
+ css_qname qs;
+ dom_string *attr = NULL;
+ dom_node *tablenode = NULL;
+ dom_exception exc;
+
+ qs.ns = NULL;
+ qs.name = lwc_string_ref(corestring_lwc_table);
+ if (named_ancestor_node(ctx, node, &qs,
+ (void *)&tablenode) != CSS_OK) {
+ /* Didn't find, or had error */
+ lwc_string_unref(qs.name);
+ return;
+ }
+ lwc_string_unref(qs.name);
+
+ if (tablenode == NULL) {
+ return;
+ }
+ /* No need to unref tablenode, named_ancestor_node does not
+ * return a reffed node to the CSS
+ */
+
+ exc = dom_element_get_attribute(tablenode,
+ corestring_dom_border, &attr);
+
+ if (exc == DOM_NO_ERR && attr != NULL) {
+ uint32_t hint_prop;
+ css_hint_length hint_length;
+
+ if (parse_dimension(
+ dom_string_data(attr), false,
+ &hint_length.value,
+ &hint_length.unit) &&
+ INTTOFIX(0) != hint_length.value) {
+
+ for (hint_prop = CSS_PROP_BORDER_TOP_STYLE;
+ hint_prop <= CSS_PROP_BORDER_LEFT_STYLE;
+ hint_prop++) {
+ hint->prop = hint_prop;
+ hint->status = CSS_BORDER_STYLE_INSET;
+ hint = css_hint_advance(hint);
+ }
+
+ for (hint_prop = CSS_PROP_BORDER_TOP_WIDTH;
+ hint_prop <= CSS_PROP_BORDER_LEFT_WIDTH;
+ hint_prop++) {
+ hint->prop = hint_prop;
+ hint->data.length.value = INTTOFIX(1);
+ hint->data.length.unit = CSS_UNIT_PX;
+ hint->status = CSS_BORDER_WIDTH_WIDTH;
+ hint = css_hint_advance(hint);
+ }
+ }
+ dom_string_unref(attr);
+ }
+
+ exc = dom_element_get_attribute(tablenode,
+ corestring_dom_bordercolor, &attr);
+
+ if (exc == DOM_NO_ERR && attr != NULL) {
+ uint32_t hint_prop;
+ css_color hint_color;
+
+ if (nscss_parse_colour(
+ (const char *)dom_string_data(attr),
+ &hint_color)) {
+
+ for (hint_prop = CSS_PROP_BORDER_TOP_COLOR;
+ hint_prop <= CSS_PROP_BORDER_LEFT_COLOR;
+ hint_prop++) {
+ hint->prop = hint_prop;
+ hint->data.color = hint_color;
+ hint->status = CSS_BORDER_COLOR_COLOR;
+ hint = css_hint_advance(hint);
+ }
+ }
+ dom_string_unref(attr);
+ }
+
+ exc = dom_element_get_attribute(tablenode,
+ corestring_dom_cellpadding, &attr);
+
+ if (exc == DOM_NO_ERR && attr != NULL) {
+ uint32_t hint_prop;
+ css_hint_length hint_length;
+
+ if (parse_dimension(
+ dom_string_data(attr), false,
+ &hint_length.value,
+ &hint_length.unit)) {
+
+ for (hint_prop = CSS_PROP_PADDING_TOP;
+ hint_prop <= CSS_PROP_PADDING_LEFT;
+ hint_prop++) {
+ hint->prop = hint_prop;
+ hint->data.length.value = hint_length.value;
+ hint->data.length.unit = hint_length.unit;
+ hint->status = CSS_PADDING_SET;
+ hint = css_hint_advance(hint);
+ }
+ }
+ dom_string_unref(attr);
+ }
+}
+
+static void css_hint_vertical_align_table_cells(
+ nscss_select_ctx *ctx,
+ dom_node *node)
+{
+ struct css_hint *hint = &hint_ctx.hints[hint_ctx.len];
+ dom_string *attr = NULL;
+ dom_exception err;
+
+ err = dom_element_get_attribute(node,
+ corestring_dom_valign, &attr);
+
+ if (err == DOM_NO_ERR && attr != NULL) {
+ if (dom_string_caseless_lwc_isequal(attr,
+ corestring_lwc_top)) {
+ hint->prop = CSS_PROP_VERTICAL_ALIGN;
+ hint->status = CSS_VERTICAL_ALIGN_TOP;
+ hint = css_hint_advance(hint);
+
+ } else if (dom_string_caseless_lwc_isequal(attr,
+ corestring_lwc_middle)) {
+ hint->prop = CSS_PROP_VERTICAL_ALIGN;
+ hint->status = CSS_VERTICAL_ALIGN_MIDDLE;
+ hint = css_hint_advance(hint);
+
+ } else if (dom_string_caseless_lwc_isequal(attr,
+ corestring_lwc_bottom)) {
+ hint->prop = CSS_PROP_VERTICAL_ALIGN;
+ hint->status = CSS_VERTICAL_ALIGN_BOTTOM;
+ hint = css_hint_advance(hint);
+
+ } else if (dom_string_caseless_lwc_isequal(attr,
+ corestring_lwc_baseline)) {
+ hint->prop = CSS_PROP_VERTICAL_ALIGN;
+ hint->status = CSS_VERTICAL_ALIGN_BASELINE;
+ hint = css_hint_advance(hint);
+ }
+ dom_string_unref(attr);
+ }
+}
+
+static void css_hint_vertical_align_replaced(
+ nscss_select_ctx *ctx,
+ dom_node *node)
+{
+ struct css_hint *hint = &hint_ctx.hints[hint_ctx.len];
+ dom_string *attr = NULL;
+ dom_exception err;
+
+ err = dom_element_get_attribute(node,
+ corestring_dom_valign, &attr);
+
+ if (err == DOM_NO_ERR && attr != NULL) {
+ if (dom_string_caseless_lwc_isequal(attr,
+ corestring_lwc_top)) {
+ hint->prop = CSS_PROP_VERTICAL_ALIGN;
+ hint->status = CSS_VERTICAL_ALIGN_TOP;
+ hint = css_hint_advance(hint);
+
+ } else if (dom_string_caseless_lwc_isequal(attr,
+ corestring_lwc_bottom) ||
+ dom_string_caseless_lwc_isequal(attr,
+ corestring_lwc_baseline)) {
+ hint->prop = CSS_PROP_VERTICAL_ALIGN;
+ hint->status = CSS_VERTICAL_ALIGN_BASELINE;
+ hint = css_hint_advance(hint);
+
+ } else if (dom_string_caseless_lwc_isequal(attr,
+ corestring_lwc_texttop)) {
+ hint->prop = CSS_PROP_VERTICAL_ALIGN;
+ hint->status = CSS_VERTICAL_ALIGN_TEXT_TOP;
+ hint = css_hint_advance(hint);
+
+ } else if (dom_string_caseless_lwc_isequal(attr,
+ corestring_lwc_absmiddle) ||
+ dom_string_caseless_lwc_isequal(attr,
+ corestring_lwc_abscenter)) {
+ hint->prop = CSS_PROP_VERTICAL_ALIGN;
+ hint->status = CSS_VERTICAL_ALIGN_MIDDLE;
+ hint = css_hint_advance(hint);
+ }
+ dom_string_unref(attr);
+ }
+}
+
+static void css_hint_text_align_normal(
+ nscss_select_ctx *ctx,
+ dom_node *node)
+{
+ struct css_hint *hint = &hint_ctx.hints[hint_ctx.len];
+ dom_string *align = NULL;
+ dom_exception err;
+
+ err = dom_element_get_attribute(node,
+ corestring_dom_align, &align);
+ if (err == DOM_NO_ERR && align != NULL) {
+ if (dom_string_caseless_lwc_isequal(align,
+ corestring_lwc_left)) {
+ hint->prop = CSS_PROP_TEXT_ALIGN;
+ hint->status = CSS_TEXT_ALIGN_LEFT;
+ hint = css_hint_advance(hint);
+
+ } else if (dom_string_caseless_lwc_isequal(align,
+ corestring_lwc_center)) {
+ hint->prop = CSS_PROP_TEXT_ALIGN;
+ hint->status = CSS_TEXT_ALIGN_CENTER;
+ hint = css_hint_advance(hint);
+
+ } else if (dom_string_caseless_lwc_isequal(align,
+ corestring_lwc_right)) {
+ hint->prop = CSS_PROP_TEXT_ALIGN;
+ hint->status = CSS_TEXT_ALIGN_RIGHT;
+ hint = css_hint_advance(hint);
+
+ } else if (dom_string_caseless_lwc_isequal(align,
+ corestring_lwc_justify)) {
+ hint->prop = CSS_PROP_TEXT_ALIGN;
+ hint->status = CSS_TEXT_ALIGN_JUSTIFY;
+ hint = css_hint_advance(hint);
+ }
+ dom_string_unref(align);
+ }
+}
+
+static void css_hint_text_align_center(
+ nscss_select_ctx *ctx,
+ dom_node *node)
+{
+ struct css_hint *hint = &hint_ctx.hints[hint_ctx.len];
+
+ hint->prop = CSS_PROP_TEXT_ALIGN;
+ hint->status = CSS_TEXT_ALIGN_LIBCSS_CENTER;
+ hint = css_hint_advance(hint);
+}
+
+static void css_hint_margin_left_right_align_center(
+ nscss_select_ctx *ctx,
+ dom_node *node)
+{
+ struct css_hint *hint = &hint_ctx.hints[hint_ctx.len];
+ dom_string *attr;
+ dom_exception exc;
+
+ exc = dom_element_get_attribute(node,
+ corestring_dom_align, &attr);
+
+ if (exc == DOM_NO_ERR && attr != NULL) {
+ if (dom_string_caseless_lwc_isequal(attr,
+ corestring_lwc_center) ||
+ dom_string_caseless_lwc_isequal(attr,
+ corestring_lwc_abscenter) ||
+ dom_string_caseless_lwc_isequal(attr,
+ corestring_lwc_middle) ||
+ dom_string_caseless_lwc_isequal(attr,
+ corestring_lwc_absmiddle)) {
+ hint->prop = CSS_PROP_MARGIN_LEFT;
+ hint->status = CSS_MARGIN_AUTO;
+ hint = css_hint_advance(hint);
+
+ hint->prop = CSS_PROP_MARGIN_RIGHT;
+ hint->status = CSS_MARGIN_AUTO;
+ hint = css_hint_advance(hint);
+ }
+ dom_string_unref(attr);
+ }
+}
+
+static void css_hint_text_align_special(
+ nscss_select_ctx *ctx,
+ dom_node *node)
+{
+ struct css_hint *hint = &hint_ctx.hints[hint_ctx.len];
+ dom_string *align = NULL;
+ dom_exception err;
+
+ err = dom_element_get_attribute(node,
+ corestring_dom_align, &align);
+
+ if (err == DOM_NO_ERR && align != NULL) {
+ if (dom_string_caseless_lwc_isequal(align,
+ corestring_lwc_center)) {
+ hint->prop = CSS_PROP_TEXT_ALIGN;
+ hint->status = CSS_TEXT_ALIGN_LIBCSS_CENTER;
+ hint = css_hint_advance(hint);
+
+ } else if (dom_string_caseless_lwc_isequal(align,
+ corestring_lwc_left)) {
+ hint->prop = CSS_PROP_TEXT_ALIGN;
+ hint->status = CSS_TEXT_ALIGN_LIBCSS_LEFT;
+ hint = css_hint_advance(hint);
+
+ } else if (dom_string_caseless_lwc_isequal(align,
+ corestring_lwc_right)) {
+ hint->prop = CSS_PROP_TEXT_ALIGN;
+ hint->status = CSS_TEXT_ALIGN_LIBCSS_RIGHT;
+ hint = css_hint_advance(hint);
+
+ } else if (dom_string_caseless_lwc_isequal(align,
+ corestring_lwc_justify)) {
+ hint->prop = CSS_PROP_TEXT_ALIGN;
+ hint->status = CSS_TEXT_ALIGN_JUSTIFY;
+ hint = css_hint_advance(hint);
+ }
+ dom_string_unref(align);
+ }
+}
+
+static void css_hint_text_align_table_special(
+ nscss_select_ctx *ctx,
+ dom_node *node)
+{
+ struct css_hint *hint = &hint_ctx.hints[hint_ctx.len];
+
+ hint->prop = CSS_PROP_TEXT_ALIGN;
+ hint->status = CSS_TEXT_ALIGN_INHERIT_IF_NON_MAGIC;
+ hint = css_hint_advance(hint);
+}
+
+static void css_hint_margin_hspace_vspace(
+ nscss_select_ctx *ctx,
+ dom_node *node)
+{
+ struct css_hint *hint = &hint_ctx.hints[hint_ctx.len];
+ dom_string *attr = NULL;
+ dom_exception exc;
+
+ exc = dom_element_get_attribute(node,
+ corestring_dom_vspace, &attr);
+
+ if (exc == DOM_NO_ERR && attr != NULL) {
+ css_hint_length hint_length;
+ if (parse_dimension(
+ dom_string_data(attr), false,
+ &hint_length.value,
+ &hint_length.unit)) {
+ hint->prop = CSS_PROP_MARGIN_TOP;
+ hint->data.length.value = hint_length.value;
+ hint->data.length.unit = hint_length.unit;
+ hint->status = CSS_MARGIN_SET;
+ hint = css_hint_advance(hint);
+
+ hint->prop = CSS_PROP_MARGIN_BOTTOM;
+ hint->data.length.value = hint_length.value;
+ hint->data.length.unit = hint_length.unit;
+ hint->status = CSS_MARGIN_SET;
+ hint = css_hint_advance(hint);
+ }
+ dom_string_unref(attr);
+ }
+
+ exc = dom_element_get_attribute(node,
+ corestring_dom_hspace, &attr);
+
+ if (exc == DOM_NO_ERR && attr != NULL) {
+ css_hint_length hint_length;
+ if (parse_dimension(
+ dom_string_data(attr), false,
+ &hint_length.value,
+ &hint_length.unit)) {
+ hint->prop = CSS_PROP_MARGIN_LEFT;
+ hint->data.length.value = hint_length.value;
+ hint->data.length.unit = hint_length.unit;
+ hint->status = CSS_MARGIN_SET;
+ hint = css_hint_advance(hint);
+
+ hint->prop = CSS_PROP_MARGIN_RIGHT;
+ hint->data.length.value = hint_length.value;
+ hint->data.length.unit = hint_length.unit;
+ hint->status = CSS_MARGIN_SET;
+ hint = css_hint_advance(hint);
+ }
+ dom_string_unref(attr);
+ }
+}
+
+static void css_hint_margin_left_right_hr(
+ nscss_select_ctx *ctx,
+ dom_node *node)
+{
+ struct css_hint *hint = &hint_ctx.hints[hint_ctx.len];
+ dom_string *attr;
+ dom_exception exc;
+
+ exc = dom_element_get_attribute(node,
+ corestring_dom_align, &attr);
+
+ if (exc == DOM_NO_ERR && attr != NULL) {
+ if (dom_string_caseless_lwc_isequal(attr,
+ corestring_lwc_left)) {
+ hint->prop = CSS_PROP_MARGIN_LEFT;
+ hint->data.length.value = 0;
+ hint->data.length.unit = CSS_UNIT_PX;
+ hint->status = CSS_MARGIN_SET;
+ hint = css_hint_advance(hint);
+
+ hint->prop = CSS_PROP_MARGIN_RIGHT;
+ hint->status = CSS_MARGIN_AUTO;
+ hint = css_hint_advance(hint);
+
+ } else if (dom_string_caseless_lwc_isequal(attr,
+ corestring_lwc_center)) {
+ hint->prop = CSS_PROP_MARGIN_LEFT;
+ hint->status = CSS_MARGIN_AUTO;
+ hint = css_hint_advance(hint);
+
+ hint->prop = CSS_PROP_MARGIN_RIGHT;
+ hint->status = CSS_MARGIN_AUTO;
+ hint = css_hint_advance(hint);
+
+ } else if (dom_string_caseless_lwc_isequal(attr,
+ corestring_lwc_right)) {
+ hint->prop = CSS_PROP_MARGIN_LEFT;
+ hint->status = CSS_MARGIN_AUTO;
+ hint = css_hint_advance(hint);
+
+ hint->prop = CSS_PROP_MARGIN_RIGHT;
+ hint->data.length.value = 0;
+ hint->data.length.unit = CSS_UNIT_PX;
+ hint->status = CSS_MARGIN_SET;
+ hint = css_hint_advance(hint);
+ }
+ dom_string_unref(attr);
+ }
+}
+
+static void css_hint_table_spacing_border(
+ nscss_select_ctx *ctx,
+ dom_node *node)
+{
+ struct css_hint *hint = &hint_ctx.hints[hint_ctx.len];
+ dom_exception exc;
+ dom_string *attr = NULL;
+
+ exc = dom_element_get_attribute(node, corestring_dom_border, &attr);
+
+ if (exc == DOM_NO_ERR && attr != NULL) {
+ uint32_t hint_prop;
+ css_hint_length hint_length;
+
+ for (hint_prop = CSS_PROP_BORDER_TOP_STYLE;
+ hint_prop <= CSS_PROP_BORDER_LEFT_STYLE;
+ hint_prop++) {
+ hint->prop = hint_prop;
+ hint->status = CSS_BORDER_STYLE_OUTSET;
+ hint = css_hint_advance(hint);
+ }
+
+ if (parse_dimension(
+ dom_string_data(attr), false,
+ &hint_length.value,
+ &hint_length.unit)) {
+
+ for (hint_prop = CSS_PROP_BORDER_TOP_WIDTH;
+ hint_prop <= CSS_PROP_BORDER_LEFT_WIDTH;
+ hint_prop++) {
+ hint->prop = hint_prop;
+ hint->data.length.value = hint_length.value;
+ hint->data.length.unit = hint_length.unit;
+ hint->status = CSS_BORDER_WIDTH_WIDTH;
+ hint = css_hint_advance(hint);
+ }
+ }
+ dom_string_unref(attr);
+ }
+
+ exc = dom_element_get_attribute(node,
+ corestring_dom_bordercolor, &attr);
+
+ if (exc == DOM_NO_ERR && attr != NULL) {
+ uint32_t hint_prop;
+ css_color hint_color;
+
+ if (nscss_parse_colour(
+ (const char *)dom_string_data(attr),
+ &hint_color)) {
+
+ for (hint_prop = CSS_PROP_BORDER_TOP_COLOR;
+ hint_prop <= CSS_PROP_BORDER_LEFT_COLOR;
+ hint_prop++) {
+ hint->prop = hint_prop;
+ hint->data.color = hint_color;
+ hint->status = CSS_BORDER_COLOR_COLOR;
+ hint = css_hint_advance(hint);
+ }
+ }
+ dom_string_unref(attr);
+ }
+
+ exc = dom_element_get_attribute(node,
+ corestring_dom_cellspacing, &attr);
+
+ if (exc == DOM_NO_ERR && attr != NULL) {
+ if (parse_dimension(
+ (const char *)dom_string_data(attr), false,
+ &hint->data.position.h.value,
+ &hint->data.position.h.unit)) {
+ hint->prop = CSS_PROP_BORDER_SPACING;
+ hint->data.position.v = hint->data.position.h;
+ hint->status = CSS_BORDER_SPACING_SET;
+ hint = css_hint_advance(hint);
+ }
+ dom_string_unref(attr);
+ }
+}
+
+static void css_hint_height(
+ nscss_select_ctx *ctx,
+ dom_node *node)
+{
+ struct css_hint *hint = &hint_ctx.hints[hint_ctx.len];
+ dom_string *attr = NULL;
+ dom_exception err;
+
+ err = dom_element_get_attribute(node,
+ corestring_dom_height, &attr);
+
+ if (err == DOM_NO_ERR && attr != NULL) {
+ if (parse_dimension(
+ (const char *)dom_string_data(attr), false,
+ &hint->data.length.value,
+ &hint->data.length.unit)) {
+ hint->prop = CSS_PROP_HEIGHT;
+ hint->status = CSS_HEIGHT_SET;
+ hint = css_hint_advance(hint);
+ }
+ dom_string_unref(attr);
+ }
+}
+
+static void css_hint_width(
+ nscss_select_ctx *ctx,
+ dom_node *node)
+{
+ struct css_hint *hint = &hint_ctx.hints[hint_ctx.len];
+ dom_string *attr = NULL;
+ dom_exception err;
+
+ err = dom_element_get_attribute(node,
+ corestring_dom_width, &attr);
+
+ if (err == DOM_NO_ERR && attr != NULL) {
+ if (parse_dimension(
+ (const char *)dom_string_data(attr), false,
+ &hint->data.length.value,
+ &hint->data.length.unit)) {
+ hint->prop = CSS_PROP_WIDTH;
+ hint->status = CSS_WIDTH_SET;
+ hint = css_hint_advance(hint);
+ }
+ dom_string_unref(attr);
+ }
+}
+
+static void css_hint_height_width_textarea(
+ nscss_select_ctx *ctx,
+ dom_node *node)
+{
+ struct css_hint *hint = &hint_ctx.hints[hint_ctx.len];
+ dom_string *attr = NULL;
+ dom_exception err;
+
+ err = dom_element_get_attribute(node,
+ corestring_dom_rows, &attr);
+
+ if (err == DOM_NO_ERR && attr != NULL) {
+ if (parse_dimension(
+ (const char *)dom_string_data(attr), false,
+ &hint->data.length.value,
+ &hint->data.length.unit)) {
+ hint->prop = CSS_PROP_HEIGHT;
+ hint->data.length.unit = CSS_UNIT_EM;
+ hint->status = CSS_HEIGHT_SET;
+ hint = css_hint_advance(hint);
+ }
+ dom_string_unref(attr);
+ }
+
+ err = dom_element_get_attribute(node,
+ corestring_dom_cols, &attr);
+
+ if (err == DOM_NO_ERR && attr != NULL) {
+ if (parse_dimension(
+ (const char *)dom_string_data(attr), false,
+ &hint->data.length.value,
+ &hint->data.length.unit)) {
+ hint->prop = CSS_PROP_WIDTH;
+ hint->data.length.unit = CSS_UNIT_EX;
+ hint->status = CSS_WIDTH_SET;
+ hint = css_hint_advance(hint);
+ }
+ dom_string_unref(attr);
+ }
+}
+
+static void css_hint_width_input(
+ nscss_select_ctx *ctx,
+ dom_node *node)
+{
+ struct css_hint *hint = &(hint_ctx.hints[hint_ctx.len]);
+ dom_string *attr = NULL;
+ dom_exception err;
+
+ err = dom_element_get_attribute(node,
+ corestring_dom_size, &attr);
+
+ if (err == DOM_NO_ERR && attr != NULL) {
+ if (parse_dimension(
+ (const char *)dom_string_data(attr), false,
+ &hint->data.length.value,
+ &hint->data.length.unit)) {
+ dom_string *attr2 = NULL;
+
+ err = dom_element_get_attribute(node,
+ corestring_dom_type, &attr2);
+ if (err == DOM_NO_ERR) {
+
+ hint->prop = CSS_PROP_WIDTH;
+ hint->status = CSS_WIDTH_SET;
+
+ if (attr2 == NULL ||
+ dom_string_caseless_lwc_isequal(
+ attr2,
+ corestring_lwc_text) ||
+ dom_string_caseless_lwc_isequal(
+ attr2,
+ corestring_lwc_search) ||
+ dom_string_caseless_lwc_isequal(
+ attr2,
+ corestring_lwc_password) ||
+ dom_string_caseless_lwc_isequal(
+ attr2,
+ corestring_lwc_file)) {
+ hint->data.length.unit = CSS_UNIT_EX;
+ }
+ if (attr2 != NULL) {
+ dom_string_unref(attr2);
+ }
+ hint = css_hint_advance(hint);
+ }
+ }
+ dom_string_unref(attr);
+ }
+}
+
+static void css_hint_anchor_color(
+ nscss_select_ctx *ctx,
+ dom_node *node)
+{
+ struct css_hint *hint = &hint_ctx.hints[hint_ctx.len];
+ css_error error;
+ dom_exception err;
+ dom_string *color;
+ dom_node *bodynode = NULL;
+
+ /* find body node */
+ css_qname qs;
+ bool is_visited;
+
+ qs.ns = NULL;
+ qs.name = lwc_string_ref(corestring_lwc_body);
+ if (named_ancestor_node(ctx, node, &qs,
+ (void *)&bodynode) != CSS_OK) {
+ /* Didn't find, or had error */
+ lwc_string_unref(qs.name);
+ return ;
+ }
+ lwc_string_unref(qs.name);
+
+ if (bodynode == NULL) {
+ return;
+ }
+
+ error = node_is_visited(ctx, node, &is_visited);
+ if (error != CSS_OK)
+ return;
+
+ if (is_visited) {
+ err = dom_element_get_attribute(bodynode,
+ corestring_dom_vlink, &color);
+ } else {
+ err = dom_element_get_attribute(bodynode,
+ corestring_dom_link, &color);
+ }
+
+ if (err == DOM_NO_ERR && color != NULL) {
+ if (nscss_parse_colour(
+ (const char *)dom_string_data(color),
+ &hint->data.color)) {
+ hint->prop = CSS_PROP_COLOR;
+ hint->status = CSS_COLOR_COLOR;
+ hint = css_hint_advance(hint);
+ }
+ dom_string_unref(color);
+ }
+}
+
+static void css_hint_body_color(
+ nscss_select_ctx *ctx,
+ dom_node *node)
+{
+ struct css_hint *hint = &hint_ctx.hints[hint_ctx.len];
+ dom_exception err;
+ dom_string *color;
+
+ err = dom_element_get_attribute(node, corestring_dom_text, &color);
+
+ if (err == DOM_NO_ERR && color != NULL) {
+ if (nscss_parse_colour(
+ (const char *)dom_string_data(color),
+ &hint->data.color)) {
+ hint->prop = CSS_PROP_COLOR;
+ hint->status = CSS_COLOR_COLOR;
+ hint = css_hint_advance(hint);
+ }
+ dom_string_unref(color);
+ }
+}
+
+static void css_hint_color(
+ nscss_select_ctx *ctx,
+ dom_node *node)
+{
+ struct css_hint *hint = &hint_ctx.hints[hint_ctx.len];
+ dom_exception err;
+ dom_string *color;
+
+ err = dom_element_get_attribute(node, corestring_dom_color, &color);
+
+ if (err == DOM_NO_ERR && color != NULL) {
+ if (nscss_parse_colour(
+ (const char *)dom_string_data(color),
+ &hint->data.color)) {
+ hint->prop = CSS_PROP_COLOR;
+ hint->status = CSS_COLOR_COLOR;
+ hint = css_hint_advance(hint);
+ }
+ dom_string_unref(color);
+ }
+}
+
+static void css_hint_font_size(
+ nscss_select_ctx *ctx,
+ dom_node *node)
+{
+ struct css_hint *hint = &hint_ctx.hints[hint_ctx.len];
+ dom_exception err;
+ dom_string *size;
+
+ err = dom_element_get_attribute(node, corestring_dom_size, &size);
+ if (err == DOM_NO_ERR && size != NULL) {
+ if (parse_font_size(
+ (const char *)dom_string_data(size),
+ &hint->status,
+ &hint->data.length.value,
+ &hint->data.length.unit)) {
+ hint->prop = CSS_PROP_FONT_SIZE;
+ hint = css_hint_advance(hint);
+ }
+ dom_string_unref(size);
+ }
+}
+
+static void css_hint_float(
+ nscss_select_ctx *ctx,
+ dom_node *node)
+{
+ struct css_hint *hint = &hint_ctx.hints[hint_ctx.len];
+ dom_exception err;
+ dom_string *align;
+
+ err = dom_element_get_attribute(node, corestring_dom_align, &align);
+ if (err == DOM_NO_ERR && align != NULL) {
+ if (dom_string_caseless_lwc_isequal(align,
+ corestring_lwc_left)) {
+ hint->prop = CSS_PROP_FLOAT;
+ hint->status = CSS_FLOAT_LEFT;
+ hint = css_hint_advance(hint);
+
+ } else if (dom_string_caseless_lwc_isequal(align,
+ corestring_lwc_right)) {
+ hint->prop = CSS_PROP_FLOAT;
+ hint->status = CSS_FLOAT_RIGHT;
+ hint = css_hint_advance(hint);
+ }
+ dom_string_unref(align);
+ }
+}
+
+static void css_hint_caption_side(
+ nscss_select_ctx *ctx,
+ dom_node *node)
+{
+ struct css_hint *hint = &hint_ctx.hints[hint_ctx.len];
+ dom_exception err;
+ dom_string *align = NULL;
+
+ err = dom_element_get_attribute(node, corestring_dom_align, &align);
+ if (err == DOM_NO_ERR && align != NULL) {
+ if (dom_string_caseless_lwc_isequal(align,
+ corestring_lwc_bottom)) {
+ hint->prop = CSS_PROP_CAPTION_SIDE;
+ hint->status = CSS_CAPTION_SIDE_BOTTOM;
+ hint = css_hint_advance(hint);
+ }
+ dom_string_unref(align);
+ }
+}
+
+static void css_hint_bg_color(
+ nscss_select_ctx *ctx,
+ dom_node *node)
+{
+ struct css_hint *hint = &hint_ctx.hints[hint_ctx.len];
+ dom_exception err;
+ dom_string *bgcolor;
+
+ err = dom_element_get_attribute(node,
+ corestring_dom_bgcolor, &bgcolor);
+ if (err == DOM_NO_ERR && bgcolor != NULL) {
+ if (nscss_parse_colour(
+ (const char *)dom_string_data(bgcolor),
+ &hint->data.color)) {
+ hint->prop = CSS_PROP_BACKGROUND_COLOR;
+ hint->status = CSS_BACKGROUND_COLOR_COLOR;
+ hint = css_hint_advance(hint);
+ }
+ dom_string_unref(bgcolor);
+ }
+}
+
+static void css_hint_bg_image(
+ nscss_select_ctx *ctx,
+ dom_node *node)
+{
+ struct css_hint *hint = &(hint_ctx.hints[hint_ctx.len]);
+ dom_exception err;
+ dom_string *attr;
+
+ err = dom_element_get_attribute(node,
+ corestring_dom_background, &attr);
+ if (err == DOM_NO_ERR && attr != NULL) {
+ nsurl *url;
+ nserror error = nsurl_join(ctx->base_url,
+ (const char *)dom_string_data(attr), &url);
+ dom_string_unref(attr);
+
+ if (error == NSERROR_OK) {
+ lwc_string *iurl;
+ lwc_error lerror = lwc_intern_string(nsurl_access(url),
+ nsurl_length(url), &iurl);
+ nsurl_unref(url);
+
+ if (lerror == lwc_error_ok) {
+ hint->prop = CSS_PROP_BACKGROUND_IMAGE;
+ hint->data.string = iurl;
+ hint->status = CSS_BACKGROUND_IMAGE_IMAGE;
+ hint = css_hint_advance(hint);
+ }
+ }
+ }
+}
+
+
+/* Exported function, documeted in css/hints.h */
+css_error node_presentational_hint(void *pw, void *node,
+ uint32_t *nhints, css_hint **hints)
+{
+ dom_exception exc;
+ dom_html_element_type tag_type;
+
+ css_hint_clean();
+
+ exc = dom_html_element_get_tag_type(node, &tag_type);
+ if (exc != DOM_NO_ERR) {
+ tag_type = DOM_HTML_ELEMENT_TYPE__UNKNOWN;
+ }
+
+ switch (tag_type) {
+ case DOM_HTML_ELEMENT_TYPE_TH:
+ case DOM_HTML_ELEMENT_TYPE_TD:
+ css_hint_width(pw, node);
+ css_hint_table_cell_border_padding(pw, node);
+ /* fallthrough */
+ case DOM_HTML_ELEMENT_TYPE_TR:
+ css_hint_height(pw, node);
+ /* fallthrough */
+ case DOM_HTML_ELEMENT_TYPE_THEAD:
+ case DOM_HTML_ELEMENT_TYPE_TBODY:
+ case DOM_HTML_ELEMENT_TYPE_TFOOT:
+ css_hint_text_align_special(pw, node);
+ /* fallthrough */
+ case DOM_HTML_ELEMENT_TYPE_COL:
+ css_hint_vertical_align_table_cells(pw, node);
+ break;
+ case DOM_HTML_ELEMENT_TYPE_APPLET:
+ case DOM_HTML_ELEMENT_TYPE_IMG:
+ css_hint_margin_hspace_vspace(pw, node);
+ /* fallthrough */
+ case DOM_HTML_ELEMENT_TYPE_EMBED:
+ case DOM_HTML_ELEMENT_TYPE_IFRAME:
+ case DOM_HTML_ELEMENT_TYPE_OBJECT:
+ css_hint_height(pw, node);
+ css_hint_width(pw, node);
+ css_hint_vertical_align_replaced(pw, node);
+ css_hint_float(pw, node);
+ break;
+ case DOM_HTML_ELEMENT_TYPE_P:
+ case DOM_HTML_ELEMENT_TYPE_H1:
+ case DOM_HTML_ELEMENT_TYPE_H2:
+ case DOM_HTML_ELEMENT_TYPE_H3:
+ case DOM_HTML_ELEMENT_TYPE_H4:
+ case DOM_HTML_ELEMENT_TYPE_H5:
+ case DOM_HTML_ELEMENT_TYPE_H6:
+ css_hint_text_align_normal(pw, node);
+ break;
+ case DOM_HTML_ELEMENT_TYPE_CENTER:
+ css_hint_text_align_center(pw, node);
+ break;
+ case DOM_HTML_ELEMENT_TYPE_CAPTION:
+ css_hint_caption_side(pw, node);
+ /* fallthrough */
+ case DOM_HTML_ELEMENT_TYPE_DIV:
+ css_hint_text_align_special(pw, node);
+ break;
+ case DOM_HTML_ELEMENT_TYPE_TABLE:
+ css_hint_text_align_table_special(pw, node);
+ css_hint_table_spacing_border(pw, node);
+ css_hint_float(pw, node);
+ css_hint_margin_left_right_align_center(pw, node);
+ css_hint_width(pw, node);
+ break;
+ case DOM_HTML_ELEMENT_TYPE_HR:
+ css_hint_margin_left_right_hr(pw, node);
+ break;
+ case DOM_HTML_ELEMENT_TYPE_TEXTAREA:
+ css_hint_height_width_textarea(pw, node);
+ break;
+ case DOM_HTML_ELEMENT_TYPE_INPUT:
+ css_hint_width_input(pw, node);
+ break;
+ case DOM_HTML_ELEMENT_TYPE_A:
+ css_hint_anchor_color(pw, node);
+ break;
+ case DOM_HTML_ELEMENT_TYPE_FONT:
+ css_hint_font_size(pw, node);
+ break;
+ case DOM_HTML_ELEMENT_TYPE_BODY:
+ css_hint_body_color(pw, node);
+ break;
+ default:
+ break;
+ }
+
+ if (tag_type != DOM_HTML_ELEMENT_TYPE__UNKNOWN) {
+ css_hint_color(pw, node);
+ css_hint_bg_color(pw, node);
+ css_hint_bg_image(pw, node);
+ }
+
+#ifdef LOG_STATS
+ LOG("Properties with hints: %i", hint_ctx.len);
+#endif
+
+ css_hint_get_hints(hints, nhints);
+
+ return CSS_OK;
+}
diff --git a/content/handlers/css/hints.h b/content/handlers/css/hints.h
new file mode 100644
index 000000000..2c1835970
--- /dev/null
+++ b/content/handlers/css/hints.h
@@ -0,0 +1,55 @@
+/*
+ * 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/>.
+ */
+
+#ifndef NETSURF_CSS_HINTS_H_
+#define NETSURF_CSS_HINTS_H_
+
+#include <stdint.h>
+
+#include <libcss/libcss.h>
+
+nserror css_hint_init(void);
+void css_hint_fini(void);
+
+/**
+ * Callback to retrieve presentational hints for a node
+ *
+ * \param[in] pw HTML document
+ * \param[in] node DOM node
+ * \param[out] nhints number of hints retrived
+ * \param[out] hints retrived hints
+ * \return CSS_OK on success,
+ * CSS_PROPERTY_NOT_SET if there is no hint for the requested property,
+ * CSS_NOMEM on memory exhaustion.
+ */
+css_error node_presentational_hint(
+ void *pw,
+ void *node,
+ uint32_t *nhints,
+ css_hint **hints);
+
+/**
+ * Parser for colours specified in attribute values.
+ *
+ * \param data Data to parse (NUL-terminated)
+ * \param result Pointer to location to receive resulting css_color
+ * \return true on success, false on invalid input
+ */
+bool nscss_parse_colour(const char *data, css_color *result);
+
+#endif
diff --git a/content/handlers/css/internal.c b/content/handlers/css/internal.c
new file mode 100644
index 000000000..d66b87a54
--- /dev/null
+++ b/content/handlers/css/internal.c
@@ -0,0 +1,63 @@
+/*
+ * 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 <string.h>
+#include <libcss/libcss.h>
+
+#include "utils/nsurl.h"
+
+#include "internal.h"
+
+/* exported interface documented in content/handlers/css/internal.h */
+css_error nscss_resolve_url(void *pw, const char *base,
+ lwc_string *rel, lwc_string **abs)
+{
+ lwc_error lerror;
+ nserror error;
+ nsurl *nsbase;
+ nsurl *nsabs;
+
+ /* Create nsurl from base */
+ /* TODO: avoid this */
+ error = nsurl_create(base, &nsbase);
+ if (error != NSERROR_OK) {
+ return error == NSERROR_NOMEM ? CSS_NOMEM : CSS_INVALID;
+ }
+
+ /* Resolve URI */
+ error = nsurl_join(nsbase, lwc_string_data(rel), &nsabs);
+ if (error != NSERROR_OK) {
+ nsurl_unref(nsbase);
+ return error == NSERROR_NOMEM ? CSS_NOMEM : CSS_INVALID;
+ }
+
+ nsurl_unref(nsbase);
+
+ /* Intern it */
+ lerror = lwc_intern_string(nsurl_access(nsabs),
+ nsurl_length(nsabs), abs);
+ if (lerror != lwc_error_ok) {
+ *abs = NULL;
+ nsurl_unref(nsabs);
+ return lerror == lwc_error_oom ? CSS_NOMEM : CSS_INVALID;
+ }
+
+ nsurl_unref(nsabs);
+
+ return CSS_OK;
+}
diff --git a/content/handlers/css/internal.h b/content/handlers/css/internal.h
new file mode 100644
index 000000000..5c539c843
--- /dev/null
+++ b/content/handlers/css/internal.h
@@ -0,0 +1,35 @@
+/*
+ * 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/>.
+ */
+
+#ifndef NETSURF_CSS_INTERNAL_H_
+#define NETSURF_CSS_INTERNAL_H_
+
+/**
+ * URL resolution callback for libcss
+ *
+ * \param pw Resolution context
+ * \param base Base URI
+ * \param rel Relative URL
+ * \param abs Pointer to location to receive resolved URL
+ * \return CSS_OK on success,
+ * CSS_NOMEM on memory exhaustion,
+ * CSS_INVALID if resolution failed.
+ */
+css_error nscss_resolve_url(void *pw, const char *base, lwc_string *rel, lwc_string **abs);
+
+#endif
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;
+}
diff --git a/content/handlers/css/select.h b/content/handlers/css/select.h
new file mode 100644
index 000000000..abfb85814
--- /dev/null
+++ b/content/handlers/css/select.h
@@ -0,0 +1,58 @@
+/*
+ * 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/>.
+ */
+
+#ifndef NETSURF_CSS_SELECT_H_
+#define NETSURF_CSS_SELECT_H_
+
+#include <stdint.h>
+
+#include <dom/dom.h>
+
+#include <libcss/libcss.h>
+
+struct content;
+struct nsurl;
+
+/**
+ * Selection context
+ */
+typedef struct nscss_select_ctx
+{
+ css_select_ctx *ctx;
+ bool quirks;
+ struct nsurl *base_url;
+ lwc_string *universal;
+ const css_computed_style *parent_style;
+} nscss_select_ctx;
+
+css_stylesheet *nscss_create_inline_style(const uint8_t *data, size_t len,
+ const char *charset, const char *url, bool allow_quirks);
+
+css_select_results *nscss_get_style(nscss_select_ctx *ctx, dom_node *n,
+ uint64_t media, const css_stylesheet *inline_style);
+
+css_computed_style *nscss_get_blank_style(nscss_select_ctx *ctx,
+ const css_computed_style *parent);
+
+
+css_error named_ancestor_node(void *pw, void *node,
+ const css_qname *qname, void **ancestor);
+
+css_error node_is_visited(void *pw, void *node, bool *match);
+
+#endif
diff --git a/content/handlers/css/utils.c b/content/handlers/css/utils.c
new file mode 100644
index 000000000..59ad41216
--- /dev/null
+++ b/content/handlers/css/utils.c
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2004 James Bursa <james@netsurf-browser.org>
+ * 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 "utils/nsoption.h"
+#include "utils/log.h"
+
+#include "utils.h"
+
+/** Screen DPI in fixed point units: defaults to 90, which RISC OS uses */
+css_fixed nscss_screen_dpi = F_90;
+
+/* exported interface documented in content/handlers/css/utils.h */
+css_fixed nscss_len2pt(css_fixed length, css_unit unit)
+{
+ /* Length must not be relative */
+ assert(unit != CSS_UNIT_EM && unit != CSS_UNIT_EX);
+
+ switch (unit) {
+ /* We assume the screen and any other output has the same dpi */
+ /* 1in = DPIpx => 1px = (72/DPI)pt */
+ case CSS_UNIT_PX: return FDIV(FMUL(length, F_72), nscss_screen_dpi);
+ /* 1in = 72pt */
+ case CSS_UNIT_IN: return FMUL(length, F_72);
+ /* 1in = 2.54cm => 1cm = (72/2.54)pt */
+ case CSS_UNIT_CM: return FMUL(length,
+ FDIV(F_72, FLTTOFIX(2.54)));
+ /* 1in = 25.4mm => 1mm = (72/25.4)pt */
+ case CSS_UNIT_MM: return FMUL(length,
+ FDIV(F_72, FLTTOFIX(25.4)));
+ case CSS_UNIT_PT: return length;
+ /* 1pc = 12pt */
+ case CSS_UNIT_PC: return FMUL(length, INTTOFIX(12));
+ default: break;
+ }
+
+ return 0;
+}
+
+
+/* exported interface documented in content/handlers/css/utils.h */
+css_fixed nscss_len2px(css_fixed length, css_unit unit,
+ const css_computed_style *style)
+{
+ /* We assume the screen and any other output has the same dpi */
+ css_fixed px_per_unit;
+
+ assert(style != NULL || (unit != CSS_UNIT_EM && unit != CSS_UNIT_EX));
+
+ switch (unit) {
+ case CSS_UNIT_EM:
+ case CSS_UNIT_EX:
+ {
+ css_fixed font_size = 0;
+ css_unit font_unit = CSS_UNIT_PT;
+
+ css_computed_font_size(style, &font_size, &font_unit);
+
+ /* Convert to points */
+ font_size = nscss_len2pt(font_size, font_unit);
+
+ /* Clamp to configured minimum */
+ if (font_size < FDIV(INTTOFIX(nsoption_int(font_min_size)), F_10)) {
+ font_size = FDIV(INTTOFIX(nsoption_int(font_min_size)), F_10);
+ }
+
+ /* Convert to pixels (manually, to maximise precision)
+ * 1in = 72pt => 1pt = (DPI/72)px */
+ px_per_unit = FDIV(FMUL(font_size, nscss_screen_dpi), F_72);
+
+ /* Scale ex units: we use a fixed ratio of 1ex = 0.6em */
+ if (unit == CSS_UNIT_EX)
+ px_per_unit = FMUL(px_per_unit, FLTTOFIX(0.6));
+ }
+ break;
+ case CSS_UNIT_PX:
+ px_per_unit = F_1;
+ break;
+ /* 1in = DPIpx */
+ case CSS_UNIT_IN:
+ px_per_unit = nscss_screen_dpi;
+ break;
+ /* 1in = 2.54cm => 1cm = (DPI/2.54)px */
+ case CSS_UNIT_CM:
+ px_per_unit = FDIV(nscss_screen_dpi, FLTTOFIX(2.54));
+ break;
+ /* 1in = 25.4mm => 1mm = (DPI/25.4)px */
+ case CSS_UNIT_MM:
+ px_per_unit = FDIV(nscss_screen_dpi, FLTTOFIX(25.4));
+ break;
+ /* 1in = 72pt => 1pt = (DPI/72)px */
+ case CSS_UNIT_PT:
+ px_per_unit = FDIV(nscss_screen_dpi, F_72);
+ break;
+ /* 1pc = 12pt => 1in = 6pc => 1pc = (DPI/6)px */
+ case CSS_UNIT_PC:
+ px_per_unit = FDIV(nscss_screen_dpi, INTTOFIX(6));
+ break;
+ default:
+ px_per_unit = 0;
+ break;
+ }
+
+ /* Ensure we round px_per_unit to the nearest whole number of pixels:
+ * the use of FIXTOINT() below will truncate. */
+ px_per_unit += F_0_5;
+
+ /* Calculate total number of pixels */
+ return FMUL(length, TRUNCATEFIX(px_per_unit));
+}
diff --git a/content/handlers/css/utils.h b/content/handlers/css/utils.h
new file mode 100644
index 000000000..58a5ea6e6
--- /dev/null
+++ b/content/handlers/css/utils.h
@@ -0,0 +1,49 @@
+/*
+ * 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/>.
+ */
+
+#ifndef NETSURF_CSS_UTILS_H_
+#define NETSURF_CSS_UTILS_H_
+
+#include <libcss/libcss.h>
+
+#include "netsurf/css.h"
+
+/** DPI of the screen, in fixed point units */
+extern css_fixed nscss_screen_dpi;
+
+/**
+ * Convert an absolute CSS length to points.
+ *
+ * \param[in] length Absolute CSS length.
+ * \param[in] unit Unit of the length.
+ * \return length in points
+ */
+css_fixed nscss_len2pt(css_fixed length, css_unit unit);
+
+/**
+ * Convert a CSS length to pixels.
+ *
+ * \param length Length to convert
+ * \param unit Corresponding unit
+ * \param style Computed style applying to length. May be NULL if unit is
+ * neither em nor ex
+ * \return length in pixels
+ */
+css_fixed nscss_len2px(css_fixed length, css_unit unit, const css_computed_style *style);
+
+#endif
diff --git a/content/handlers/image/Makefile b/content/handlers/image/Makefile
index 5851a1c43..541cd2cf9 100644
--- a/content/handlers/image/Makefile
+++ b/content/handlers/image/Makefile
@@ -12,4 +12,4 @@ S_IMAGE_$(NETSURF_USE_NSSVG) += svg.c
S_IMAGE_$(NETSURF_USE_RSVG) += rsvg.c
S_IMAGE_$(NETSURF_USE_VIDEO) += video.c
-S_IMAGE := $(addprefix image/,$(S_IMAGE_YES))
+S_IMAGE := $(S_IMAGE_YES)
diff --git a/content/handlers/image/svg.c b/content/handlers/image/svg.c
index c91b00650..3eff684fd 100644
--- a/content/handlers/image/svg.c
+++ b/content/handlers/image/svg.c
@@ -23,13 +23,13 @@
#include <assert.h>
#include <limits.h>
#include <string.h>
+#include <stdlib.h>
#include <svgtiny.h>
#include "utils/messages.h"
#include "utils/utils.h"
#include "content/content_protected.h"
-#include "css/css.h"
#include "desktop/plotters.h"
#include "svg.h"