/* * Copyright 2009 John-Mark Bell * * This file is part of NetSurf, http://www.netsurf-browser.org/ * * NetSurf is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * NetSurf is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include "content/content_protected.h" #include "content/fetch.h" #include "content/hlcache.h" #include "css/css.h" #include "css/internal.h" #include "desktop/gui.h" #include "render/html.h" #include "utils/utils.h" #include "utils/http.h" #include "utils/log.h" #include "utils/messages.h" /** * Context for import fetches */ typedef struct { struct content_css_data *css; /**< Object containing import */ const char *referer; /**< URL of containing object */ nscss_done_callback cb; /**< Completion callback */ void *pw; /**< Client data */ } nscss_import_ctx; static void nscss_content_done(struct content_css_data *css, void *pw); static css_error nscss_request_import(struct content_css_data *c, nscss_import_ctx *ctx); static css_error nscss_import_complete(struct content_css_data *c, const hlcache_handle *import); static nserror nscss_import(hlcache_handle *handle, const hlcache_event *event, void *pw); /** * Initialise a CSS content * * \param c Content to initialise * \param params Content-Type parameters * \return true on success, false on failure */ bool nscss_create(struct content *c, const http_parameter *params) { const char *charset = NULL; union content_msg_data msg_data; nserror error; /** \todo what happens about the allocator? */ /** \todo proper error reporting */ /* Find charset specified on HTTP layer, if any */ error = http_parameter_list_find_item(params, "charset", &charset); if (error != NSERROR_OK || *charset == '\0') { /* No charset specified, use fallback, if any */ /** \todo libcss will take this as gospel, which is wrong */ charset = c->fallback_charset; } if (nscss_create_css_data(&c->data.css, content__get_url(c), charset, c->quirks) != NSERROR_OK) { msg_data.error = messages_get("NoMemory"); content_broadcast(c, CONTENT_MSG_ERROR, msg_data); return false; } return true; } /** * 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 * \return NSERROR_OK on success, NSERROR_NOMEM on memory exhaustion */ nserror nscss_create_css_data(struct content_css_data *c, const char *url, const char *charset, bool quirks) { css_error error; css_stylesheet_params params; c->import_count = 0; c->imports = NULL; if (charset != NULL) c->charset = strdup(charset); else c->charset = NULL; 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 = NULL; params.import_pw = NULL; params.color = NULL; params.color_pw = NULL; error = css_stylesheet_create(¶ms, ns_realloc, NULL, &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) { union content_msg_data msg_data; css_error error; error = nscss_process_css_data(&c->data.css, 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 */ 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) { union content_msg_data msg_data; css_error error; error = nscss_convert_css_data(&c->data.css, nscss_content_done, c); if (error != CSS_OK) { msg_data.error = "?"; content_broadcast(c, CONTENT_MSG_ERROR, msg_data); c->status = CONTENT_STATUS_ERROR; return false; } return true; } /** * 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); c->status = CONTENT_STATUS_ERROR; 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 */ if (css->import_count == 0) { /* No imports? Ok, so we've not returned from nscss_convert yet. * Just set the status, as content_convert will notify users */ c->status = CONTENT_STATUS_DONE; } else { content_set_ready(c); content_set_done(c); } } /** * Convert CSS data ready for use * * \param c CSS data to convert * \param callback Callback to call when imports are fetched * \param pw Client data for callback * \return CSS error */ css_error nscss_convert_css_data(struct content_css_data *c, nscss_done_callback callback, void *pw) { css_error error; error = css_stylesheet_data_done(c->sheet); /* Process pending imports */ if (error == CSS_IMPORTS_PENDING) { const char *referer; nscss_import_ctx *ctx; 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->referer = referer; ctx->cb = callback; ctx->pw = pw; error = nscss_request_import(c, ctx); if (error != CSS_OK) free(ctx); } else if (error == CSS_OK) { /* No imports, and no errors, so complete conversion */ callback(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_destroy_css_data(&c->data.css); } /** * Clean up CSS data * * \param c CSS data to clean up */ 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); } bool nscss_clone(const struct content *old, struct content *new_content) { const char *data; unsigned long size; /* Simply replay create/process/convert */ if (nscss_create_css_data(&new_content->data.css, content__get_url(new_content), old->data.css.charset, new_content->quirks) != NSERROR_OK) return false; data = content__get_source_data(new_content, &size); if (size > 0) { if (nscss_process_data(new_content, data, size) == false) return false; } if (old->status == CONTENT_STATUS_READY || old->status == CONTENT_STATUS_DONE) { if (nscss_convert(new_content) == false) return false; } return true; } /** * 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(hlcache_handle *h, uint32_t *n) { struct content *c = hlcache_handle_get_content(h); assert(c != NULL); assert(c->type == CONTENT_CSS); assert(n != NULL); *n = c->data.css.import_count; return c->data.css.imports; } /** * Request that the next import fetch is triggered * * \param c CSS object requesting the import * \param ctx Import context * \return CSS_OK on success, * CSS_NOMEM on memory exhaustion * CSS_INVALID if no imports remain */ css_error nscss_request_import(struct content_css_data *c, nscss_import_ctx *ctx) { static const content_type accept[] = { CONTENT_CSS, CONTENT_UNKNOWN }; hlcache_child_context child; struct nscss_import *imports; lwc_string *uri; uint64_t media; css_error error; nserror nerror; error = css_stylesheet_next_pending_import(c->sheet, &uri, &media); if (error != CSS_OK) { return error; } /* Increase space in table */ imports = realloc(c->imports, (c->import_count + 1) * sizeof(struct nscss_import)); if (imports == NULL) { 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) { return error; } /* Create content */ c->imports[c->import_count].media = media; nerror = hlcache_handle_retrieve(lwc_string_data(uri), 0, ctx->referer, NULL, nscss_import, ctx, &child, accept, &c->imports[c->import_count++].c); lwc_string_unref(uri); if (nerror != NSERROR_OK) { return CSS_NOMEM; } return CSS_OK; } /** * Handle the completion of an import fetch * * \param c CSS object that requested the import * \param import Cache handle of import, or NULL on failure * \return CSS_OK on success, appropriate error otherwise */ css_error nscss_import_complete(struct content_css_data *c, const hlcache_handle *import) { css_stylesheet *sheet; css_error error; if (import != NULL) { struct content *s = hlcache_handle_get_content(import); sheet = s->data.css.sheet; } else { static css_stylesheet *blank_import; /* Create a blank sheet if needed. */ if (blank_import == NULL) { css_stylesheet_params params; 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 = NULL; params.color_pw = NULL; error = css_stylesheet_create(¶ms, ns_realloc, NULL, &blank_import); if (error != CSS_OK) { return error; } } sheet = blank_import; } error = css_stylesheet_register_import(c->sheet, sheet); if (error != CSS_OK) { return error; } return error; } /** * 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; bool next = false; switch (event->type) { case CONTENT_MSG_LOADING: if (content_get_type(handle) != CONTENT_CSS) { assert(0 && "Non-CSS type unexpected"); } break; case CONTENT_MSG_READY: break; case CONTENT_MSG_DONE: error = nscss_import_complete(ctx->css, handle); if (error != CSS_OK) { hlcache_handle_release(handle); ctx->css->imports[ctx->css->import_count - 1].c = NULL; } next = true; break; case CONTENT_MSG_ERROR: assert(ctx->css->imports[ ctx->css->import_count - 1].c == handle); hlcache_handle_release(handle); ctx->css->imports[ctx->css->import_count - 1].c = NULL; error = nscss_import_complete(ctx->css, NULL); /* Already released handle */ next = true; break; case CONTENT_MSG_STATUS: break; default: assert(0); } /* Request next import, if we're in a position to do so */ if (error == CSS_OK && next) error = nscss_request_import(ctx->css, ctx); if (error != CSS_OK) { /* No more imports, or error: notify parent that we're DONE */ ctx->cb(ctx->css, ctx->pw); /* No longer need import context */ free(ctx); } /* Preserve out-of-memory. Invalid is OK */ return error == CSS_NOMEM ? NSERROR_NOMEM : NSERROR_OK; }