summaryrefslogtreecommitdiff
path: root/content/handlers/css/css.c
diff options
context:
space:
mode:
Diffstat (limited to 'content/handlers/css/css.c')
-rw-r--r--content/handlers/css/css.c834
1 files changed, 834 insertions, 0 deletions
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;
+}