From cc7f45898bdde2793ee17f99ea4cf058250a16b5 Mon Sep 17 00:00:00 2001 From: Vincent Sanders Date: Sun, 24 Feb 2013 13:15:05 +0000 Subject: refactor stylesheet handling to separate object from within html rendering --- Makefile.sources | 6 +- render/html.c | 682 ++----------------------------------------------- render/html.h | 9 + render/html_css.c | 678 ++++++++++++++++++++++++++++++++++++++++++++++++ render/html_internal.h | 18 ++ utils/corestrings.c | 3 + utils/corestrings.h | 1 + 7 files changed, 729 insertions(+), 668 deletions(-) create mode 100644 render/html_css.c diff --git a/Makefile.sources b/Makefile.sources index 550c563bc..6810915f9 100644 --- a/Makefile.sources +++ b/Makefile.sources @@ -13,9 +13,9 @@ S_CSS := css.c dump.c internal.c select.c utils.c S_RENDER := box.c box_construct.c box_normalise.c box_textarea.c \ font.c form.c \ - html.c html_script.c html_interaction.c html_redraw.c \ - html_forms.c imagemap.c layout.c list.c search.c table.c \ - textplain.c + html.c html_css.c html_script.c html_interaction.c \ + html_redraw.c html_forms.c imagemap.c layout.c list.c \ + search.c table.c textplain.c S_UTILS := base64.c corestrings.c filename.c filepath.c hashtable.c \ libdom.c locale.c log.c messages.c nsurl.c talloc.c url.c \ diff --git a/render/html.c b/render/html.c index aeb5a5b73..37178de9c 100644 --- a/render/html.c +++ b/render/html.c @@ -72,48 +72,6 @@ static const char *html_types[] = { /* forward declared functions */ static void html_object_refresh(void *p); -/* pre-interned character set */ -static lwc_string *html_charset; - -static nsurl *html_default_stylesheet_url; -static nsurl *html_adblock_stylesheet_url; -static nsurl *html_quirks_stylesheet_url; -static nsurl *html_user_stylesheet_url; - -static nserror css_error_to_nserror(css_error error) -{ - switch (error) { - case CSS_OK: - return NSERROR_OK; - - case CSS_NOMEM: - return NSERROR_NOMEM; - - case CSS_BADPARM: - return NSERROR_BAD_PARAMETER; - - case CSS_INVALID: - return NSERROR_INVALID; - - case CSS_FILENOTFOUND: - return NSERROR_NOT_FOUND; - - case CSS_NEEDDATA: - return NSERROR_NEED_DATA; - - case CSS_BADCHARSET: - return NSERROR_BAD_ENCODING; - - case CSS_EOF: - case CSS_IMPORTS_PENDING: - case CSS_PROPERTY_NOT_SET: - default: - break; - } - return NSERROR_CSS; -} - - static void html_destroy_objects(html_content *html) { while (html->object_list != NULL) { @@ -218,8 +176,6 @@ void html_finish_conversion(html_content *c) union content_msg_data msg_data; dom_exception exc; /* returned by libdom functions */ dom_node *html; - uint32_t i; - css_error css_ret; nserror error; /* Bail out if we've been aborted */ @@ -229,55 +185,14 @@ void html_finish_conversion(html_content *c) return; } - /* check that the base stylesheet loaded; layout fails without it */ - if (c->stylesheets[STYLESHEET_BASE].data.external == NULL) { - content_broadcast_errorcode(&c->base, NSERROR_CSS_BASE); - content_set_error(&c->base); - return; - } - - /* Create selection context */ - css_ret = css_select_ctx_create(ns_realloc, c, &c->select_ctx); - if (css_ret != CSS_OK) { - content_broadcast_errorcode(&c->base, - css_error_to_nserror(css_ret)); + /* create new css selection context */ + error = html_css_new_selection_context(c, &c->select_ctx); + if (error != NSERROR_OK) { + content_broadcast_errorcode(&c->base, error); content_set_error(&c->base); return; } - /* Add sheets to it */ - for (i = STYLESHEET_BASE; i != c->stylesheet_count; i++) { - const struct html_stylesheet *hsheet = &c->stylesheets[i]; - css_stylesheet *sheet; - css_origin origin = CSS_ORIGIN_AUTHOR; - - if (i < STYLESHEET_USER) - origin = CSS_ORIGIN_UA; - else if (i < STYLESHEET_START) - origin = CSS_ORIGIN_USER; - - if (hsheet->type == HTML_STYLESHEET_EXTERNAL && - hsheet->data.external != NULL) { - sheet = nscss_get_stylesheet(hsheet->data.external); - } else if (hsheet->type == HTML_STYLESHEET_INTERNAL) { - sheet = hsheet->data.internal->sheet; - } else { - sheet = NULL; - } - - if (sheet != NULL) { - css_ret = css_select_ctx_append_sheet(c->select_ctx, - sheet, - origin, - CSS_MEDIA_SCREEN); - if (css_ret != CSS_OK) { - content_broadcast_errorcode(&c->base, - css_error_to_nserror(css_ret)); - content_set_error(&c->base); - return; - } - } - } /* fire a simple event named load at the Document's Window * object, but with its target set to the Document object (and @@ -311,492 +226,6 @@ void html_finish_conversion(html_content *c) dom_node_unref(html); } -/** - * Callback for fetchcache() for linked stylesheets. - */ - -static nserror -html_convert_css_callback(hlcache_handle *css, - const hlcache_event *event, - void *pw) -{ - html_content *parent = pw; - unsigned int i; - struct html_stylesheet *s; - - /* Find sheet */ - for (i = 0, s = parent->stylesheets; - i != parent->stylesheet_count; i++, s++) { - if (s->type == HTML_STYLESHEET_EXTERNAL && - s->data.external == css) - break; - } - - assert(i != parent->stylesheet_count); - - switch (event->type) { - case CONTENT_MSG_LOADING: - break; - - case CONTENT_MSG_READY: - break; - - case CONTENT_MSG_DONE: - LOG(("done stylesheet slot %d '%s'", i, - nsurl_access(hlcache_handle_get_url(css)))); - parent->base.active--; - LOG(("%d fetches active", parent->base.active)); - break; - - case CONTENT_MSG_ERROR: - LOG(("stylesheet %s failed: %s", - nsurl_access(hlcache_handle_get_url(css)), - event->data.error)); - hlcache_handle_release(css); - s->data.external = NULL; - parent->base.active--; - LOG(("%d fetches active", parent->base.active)); - content_add_error(&parent->base, "?", 0); - break; - - case CONTENT_MSG_STATUS: - if (event->data.explicit_status_text == NULL) { - /* Object content's status text updated */ - html_set_status(parent, - content_get_status_message(css)); - content_broadcast(&parent->base, CONTENT_MSG_STATUS, - event->data); - } else { - /* Object content wants to set explicit message */ - content_broadcast(&parent->base, CONTENT_MSG_STATUS, - event->data); - } - break; - - case CONTENT_MSG_POINTER: - /* Really don't want this to continue after the switch */ - return NSERROR_OK; - - default: - assert(0); - } - - if (parent->base.active == 0) { - html_begin_conversion(parent); - } - - return NSERROR_OK; -} - -/** - * Initialise core stylesheets - * - * \param c content structure - * \return true on success, false if an error occurred - */ - -static bool html_init_stylesheets(html_content *c) -{ - nserror ns_error; - hlcache_child_context child; - - if (c->stylesheets != NULL) { - return true; /* already initialised */ - } - - /* stylesheet 0 is the base style sheet, - * stylesheet 1 is the quirks mode style sheet, - * stylesheet 2 is the adblocking stylesheet, - * stylesheet 3 is the user stylesheet */ - c->stylesheets = calloc(STYLESHEET_START, sizeof(struct html_stylesheet)); - if (c->stylesheets == NULL) { - ns_error = NSERROR_NOMEM; - goto html_find_stylesheets_no_memory; - } - - c->stylesheets[STYLESHEET_BASE].type = HTML_STYLESHEET_EXTERNAL; - c->stylesheets[STYLESHEET_BASE].data.external = NULL; - c->stylesheets[STYLESHEET_QUIRKS].type = HTML_STYLESHEET_EXTERNAL; - c->stylesheets[STYLESHEET_QUIRKS].data.external = NULL; - c->stylesheets[STYLESHEET_ADBLOCK].type = HTML_STYLESHEET_EXTERNAL; - c->stylesheets[STYLESHEET_ADBLOCK].data.external = NULL; - c->stylesheets[STYLESHEET_USER].type = HTML_STYLESHEET_EXTERNAL; - c->stylesheets[STYLESHEET_USER].data.external = NULL; - c->stylesheet_count = STYLESHEET_START; - - child.charset = c->encoding; - child.quirks = c->base.quirks; - - ns_error = hlcache_handle_retrieve(html_default_stylesheet_url, 0, - content_get_url(&c->base), NULL, - html_convert_css_callback, c, &child, CONTENT_CSS, - &c->stylesheets[STYLESHEET_BASE].data.external); - if (ns_error != NSERROR_OK) - goto html_find_stylesheets_no_memory; - - c->base.active++; - LOG(("%d fetches active", c->base.active)); - - if (c->quirks == DOM_DOCUMENT_QUIRKS_MODE_FULL) { - ns_error = hlcache_handle_retrieve(html_quirks_stylesheet_url, - 0, content_get_url(&c->base), NULL, - html_convert_css_callback, c, &child, - CONTENT_CSS, - &c->stylesheets[STYLESHEET_QUIRKS].data.external); - if (ns_error != NSERROR_OK) - goto html_find_stylesheets_no_memory; - - c->base.active++; - LOG(("%d fetches active", c->base.active)); - - } - - if (nsoption_bool(block_ads)) { - ns_error = hlcache_handle_retrieve(html_adblock_stylesheet_url, - 0, content_get_url(&c->base), NULL, - html_convert_css_callback, c, &child, CONTENT_CSS, - &c->stylesheets[STYLESHEET_ADBLOCK]. - data.external); - if (ns_error != NSERROR_OK) - goto html_find_stylesheets_no_memory; - - c->base.active++; - LOG(("%d fetches active", c->base.active)); - - } - - ns_error = hlcache_handle_retrieve(html_user_stylesheet_url, 0, - content_get_url(&c->base), NULL, - html_convert_css_callback, c, &child, CONTENT_CSS, - &c->stylesheets[STYLESHEET_USER].data.external); - if (ns_error != NSERROR_OK) - goto html_find_stylesheets_no_memory; - - c->base.active++; - LOG(("%d fetches active", c->base.active)); - - return true; - -html_find_stylesheets_no_memory: - content_broadcast_errorcode(&c->base, ns_error); - return false; -} - -/** - * Handle notification of inline style completion - * - * \param css Inline style object - * \param pw Private data - */ -static void html_inline_style_done(struct content_css_data *css, void *pw) -{ - html_content *html = pw; - - html->base.active--; - LOG(("%d fetches active", html->base.active)); -} - -static nserror -html_stylesheet_from_domnode(html_content *c, - dom_node *node, - struct content_css_data **ret_sheet) -{ - dom_node *child, *next; - dom_exception exc; - struct content_css_data *sheet; - nserror error; - css_error csserror; - - /* create stylesheet */ - sheet = calloc(1, sizeof(struct content_css_data)); - if (sheet == NULL) { - return NSERROR_NOMEM; - } - - error = nscss_create_css_data(sheet, - nsurl_access(c->base_url), NULL, c->quirks, - html_inline_style_done, c); - if (error != NSERROR_OK) { - free(sheet); - return error; - } - - exc = dom_node_get_first_child(node, &child); - if (exc != DOM_NO_ERR) { - nscss_destroy_css_data(sheet); - free(sheet); - return NSERROR_DOM; - } - - while (child != NULL) { - dom_string *data; - - exc = dom_node_get_text_content(child, &data); - if (exc != DOM_NO_ERR) { - dom_node_unref(child); - nscss_destroy_css_data(sheet); - free(sheet); - return NSERROR_DOM; - } - - if (nscss_process_css_data(sheet, - dom_string_data(data), - dom_string_byte_length(data)) == false) { - dom_string_unref(data); - dom_node_unref(child); - nscss_destroy_css_data(sheet); - free(sheet); - return NSERROR_CSS; - } - - dom_string_unref(data); - - exc = dom_node_get_next_sibling(child, &next); - if (exc != DOM_NO_ERR) { - dom_node_unref(child); - nscss_destroy_css_data(sheet); - free(sheet); - return NSERROR_DOM; - } - - dom_node_unref(child); - child = next; - } - - c->base.active++; - LOG(("%d fetches active", c->base.active)); - - /* Convert the content -- manually, as we want the result */ - csserror = nscss_convert_css_data(sheet); - if (csserror != CSS_OK) { - /* conversion failed */ - c->base.active--; - LOG(("%d fetches active", c->base.active)); - nscss_destroy_css_data(sheet); - free(sheet); - return css_error_to_nserror(csserror); - } - - *ret_sheet = sheet; - return NSERROR_OK; -} - -/** - * Process an inline stylesheet in the document. - * - * \param c content structure - * \param style xml node of style element - * \return true on success, false if an error occurred - */ - -static struct html_stylesheet * -html_create_style_element(html_content *c, dom_node *style) -{ - dom_string *val; - dom_exception exc; - struct html_stylesheet *stylesheets; - - /* ensure sheets are initialised */ - if (html_init_stylesheets(c) == false) { - return false; - } - - /* type='text/css', or not present (invalid but common) */ - exc = dom_element_get_attribute(style, corestring_dom_type, &val); - if (exc == DOM_NO_ERR && val != NULL) { - if (!dom_string_caseless_lwc_isequal(val, - corestring_lwc_text_css)) { - dom_string_unref(val); - return NULL; - } - dom_string_unref(val); - } - - /* media contains 'screen' or 'all' or not present */ - exc = dom_element_get_attribute(style, corestring_dom_media, &val); - if (exc == DOM_NO_ERR && val != NULL) { - if (strcasestr(dom_string_data(val), "screen") == NULL && - strcasestr(dom_string_data(val), - "all") == NULL) { - dom_string_unref(val); - return NULL; - } - dom_string_unref(val); - } - - /* Extend array */ - stylesheets = realloc(c->stylesheets, - sizeof(struct html_stylesheet) * (c->stylesheet_count + 1)); - if (stylesheets == NULL) { - - content_broadcast_errorcode(&c->base, NSERROR_NOMEM); - return false; - - } - c->stylesheets = stylesheets; - - c->stylesheets[c->stylesheet_count].type = HTML_STYLESHEET_INTERNAL; - c->stylesheets[c->stylesheet_count].node = style; - c->stylesheets[c->stylesheet_count].data.internal = NULL; - c->stylesheet_count++; - - return c->stylesheets + (c->stylesheet_count - 1); -} - -static bool html_process_style_element_update(html_content *c, dom_node *style) -{ - struct content_css_data *sheet = NULL; - nserror error; - unsigned int i; - struct html_stylesheet *s; - - /* Find sheet */ - for (i = 0, s = c->stylesheets; i != c->stylesheet_count; i++, s++) { - if ((s->type == HTML_STYLESHEET_INTERNAL) && - (s->node == style)) - break; - } - if (i == c->stylesheet_count) { - s = html_create_style_element(c, style); - } - if (s == NULL) { - LOG(("Could not find or create inline stylesheet for %p", - style)); - return false; - } - - LOG(("Found sheet %p slot %d for node %p", s,i, style)); - - error = html_stylesheet_from_domnode(c, style, &sheet); - if (error != NSERROR_OK) { - LOG(("Failed to update sheet")); - content_broadcast_errorcode(&c->base, error); - return false; - } - - LOG(("Updating sheet %p with %p", s->data.internal, sheet)); - - /* Update index */ - if (s->data.internal != NULL) { - nscss_destroy_css_data(s->data.internal); - free(s->data.internal); - } - s->data.internal = sheet; - return true; -} - -static bool html_process_stylesheet_link(html_content *htmlc, dom_node *node) -{ - dom_string *rel, *type_attr, *media, *href; - struct html_stylesheet *stylesheets; - nsurl *joined; - dom_exception exc; - nserror ns_error; - hlcache_child_context child; - - /* ensure sheets are initialised */ - if (html_init_stylesheets(htmlc) == false) { - return false; - } - - /* rel= */ - exc = dom_element_get_attribute(node, corestring_dom_rel, &rel); - if (exc != DOM_NO_ERR || rel == NULL) - return true; - - if (strcasestr(dom_string_data(rel), "stylesheet") == 0) { - dom_string_unref(rel); - return true; - } else if (strcasestr(dom_string_data(rel), "alternate") != 0) { - /* Ignore alternate stylesheets */ - dom_string_unref(rel); - return true; - } - dom_string_unref(rel); - - /* type='text/css' or not present */ - exc = dom_element_get_attribute(node, corestring_dom_type, &type_attr); - if (exc == DOM_NO_ERR && type_attr != NULL) { - if (!dom_string_caseless_lwc_isequal(type_attr, - corestring_lwc_text_css)) { - dom_string_unref(type_attr); - return true; - } - dom_string_unref(type_attr); - } - - /* media contains 'screen' or 'all' or not present */ - exc = dom_element_get_attribute(node, corestring_dom_media, &media); - if (exc == DOM_NO_ERR && media != NULL) { - if (strcasestr(dom_string_data(media), "screen") == NULL && - strcasestr(dom_string_data(media), "all") == NULL) { - dom_string_unref(media); - return true; - } - dom_string_unref(media); - } - - /* href='...' */ - exc = dom_element_get_attribute(node, corestring_dom_href, &href); - if (exc != DOM_NO_ERR || href == NULL) - return true; - - /* TODO: only the first preferred stylesheets (ie. - * those with a title attribute) should be loaded - * (see HTML4 14.3) */ - - ns_error = nsurl_join(htmlc->base_url, dom_string_data(href), &joined); - if (ns_error != NSERROR_OK) { - dom_string_unref(href); - goto no_memory; - } - dom_string_unref(href); - - LOG(("linked stylesheet %i '%s'", htmlc->stylesheet_count, nsurl_access(joined))); - - /* extend stylesheets array to allow for new sheet */ - stylesheets = realloc(htmlc->stylesheets, - sizeof(struct html_stylesheet) * (htmlc->stylesheet_count + 1)); - if (stylesheets == NULL) { - nsurl_unref(joined); - ns_error = NSERROR_NOMEM; - goto no_memory; - } - - htmlc->stylesheets = stylesheets; - htmlc->stylesheets[htmlc->stylesheet_count].type = HTML_STYLESHEET_EXTERNAL; - - /* start fetch */ - child.charset = htmlc->encoding; - child.quirks = htmlc->base.quirks; - - ns_error = hlcache_handle_retrieve(joined, - 0, - content_get_url(&htmlc->base), - NULL, - html_convert_css_callback, - htmlc, - &child, - CONTENT_CSS, - &htmlc->stylesheets[htmlc->stylesheet_count].data.external); - - nsurl_unref(joined); - - if (ns_error != NSERROR_OK) - goto no_memory; - - htmlc->stylesheet_count++; - - htmlc->base.active++; - LOG(("%d fetches active", htmlc->base.active)); - - return true; - -no_memory: - content_broadcast_errorcode(&htmlc->base, ns_error); - return false; -} - /* callback for DOMNodeInserted end type */ static void dom_default_action_DOMNodeInserted_cb(struct dom_event *evt, void *pw) @@ -816,7 +245,7 @@ dom_default_action_DOMNodeInserted_cb(struct dom_event *evt, void *pw) if ((exc == DOM_NO_ERR) && (name != NULL)) { /* LOG(("element htmlc:%p node %p name:%s", htmlc, node, dom_string_data(name))); */ if (dom_string_caseless_isequal(name, corestring_dom_link)) { - html_process_stylesheet_link(htmlc, (dom_node *)node); + html_css_process_link(htmlc, (dom_node *)node); } } } @@ -842,7 +271,7 @@ dom_default_action_DOMSubtreeModified_cb(struct dom_event *evt, void *pw) if ((exc == DOM_NO_ERR) && (name != NULL)) { /* LOG(("element htmlc:%p node:%p name:%s", htmlc, node, dom_string_data(name))); */ if (dom_string_caseless_isequal(name, corestring_dom_style)) { - html_process_style_element_update(htmlc, (dom_node *)node); + html_css_update_style(htmlc, (dom_node *)node); } } } @@ -922,7 +351,7 @@ html_create_html_data(html_content *c, const http_parameter *params) selection_prepare(&c->sel, (struct content *)c, true); - nerror = http_parameter_list_find_item(params, html_charset, &charset); + nerror = http_parameter_list_find_item(params, corestring_lwc_charset, &charset); if (nerror == NSERROR_OK) { c->encoding = strdup(lwc_string_data(charset)); @@ -961,7 +390,6 @@ html_create_html_data(html_content *c, const http_parameter *params) &c->parser, &c->document); } - if (error != DOM_HUBBUB_OK) { nsurl_unref(c->base_url); c->base_url = NULL; @@ -1013,6 +441,13 @@ html_create(const content_handler *handler, return error; } + error = html_css_new_stylesheets(html); + if (error != NSERROR_OK) { + content_broadcast_errorcode(&html->base, error); + free(html); + return error; + } + *c = (struct content *) html; return NSERROR_OK; @@ -2243,11 +1678,6 @@ html_begin_conversion(html_content *htmlc) dom_node_unref(head); dom_node_unref(html); - /* ensure stylesheets are initialised */ - if (html_init_stylesheets(htmlc) == false) { - return false; - } - if (htmlc->base.active == 0) { html_finish_conversion(htmlc); } @@ -2522,7 +1952,6 @@ static void html_free_layout(html_content *htmlc) static void html_destroy(struct content *c) { html_content *html = (html_content *) c; - unsigned int i; struct form *f, *g; LOG(("content %p", c)); @@ -2582,19 +2011,7 @@ static void html_destroy(struct content *c) } /* Free stylesheets */ - for (i = 0; i != html->stylesheet_count; i++) { - if (html->stylesheets[i].type == HTML_STYLESHEET_EXTERNAL && - html->stylesheets[i].data.external != NULL) { - hlcache_handle_release( - html->stylesheets[i].data.external); - } else if (html->stylesheets[i].type == - HTML_STYLESHEET_INTERNAL && - html->stylesheets[i].data.internal != NULL) { - nscss_destroy_css_data( - html->stylesheets[i].data.internal); - } - } - free(html->stylesheets); + html_css_free_stylesheets(html); /* Free scripts */ html_free_scripts(html); @@ -3290,25 +2707,6 @@ const char *html_get_base_target(hlcache_handle *h) return c->base_target; } -/** - * Retrieve stylesheets used by HTML document - * - * \param h Content to retrieve stylesheets from - * \param n Pointer to location to receive number of sheets - * \return Pointer to array of stylesheets - */ -struct html_stylesheet *html_get_stylesheets(hlcache_handle *h, unsigned int *n) -{ - html_content *c = (html_content *) hlcache_handle_get_content(h); - - assert(c != NULL); - assert(n != NULL); - - *n = c->stylesheet_count; - - return c->stylesheets; -} - /** * Retrieve objects used by HTML document * @@ -3369,30 +2767,7 @@ static void html_fini(void) { box_construct_fini(); - if (html_user_stylesheet_url != NULL) { - nsurl_unref(html_user_stylesheet_url); - html_user_stylesheet_url = NULL; - } - - if (html_quirks_stylesheet_url != NULL) { - nsurl_unref(html_quirks_stylesheet_url); - html_quirks_stylesheet_url = NULL; - } - - if (html_adblock_stylesheet_url != NULL) { - nsurl_unref(html_adblock_stylesheet_url); - html_adblock_stylesheet_url = NULL; - } - - if (html_default_stylesheet_url != NULL) { - nsurl_unref(html_default_stylesheet_url); - html_default_stylesheet_url = NULL; - } - - if (html_charset != NULL) { - lwc_string_unref(html_charset); - html_charset = NULL; - } + html_css_fini(); } static const content_handler html_content_handler = { @@ -3423,32 +2798,9 @@ static const content_handler html_content_handler = { nserror html_init(void) { uint32_t i; - lwc_error lerror; nserror error; - lerror = lwc_intern_string("charset", SLEN("charset"), &html_charset); - if (lerror != lwc_error_ok) { - error = NSERROR_NOMEM; - goto error; - } - - error = nsurl_create("resource:default.css", - &html_default_stylesheet_url); - if (error != NSERROR_OK) - goto error; - - error = nsurl_create("resource:adblock.css", - &html_adblock_stylesheet_url); - if (error != NSERROR_OK) - goto error; - - error = nsurl_create("resource:quirks.css", - &html_quirks_stylesheet_url); - if (error != NSERROR_OK) - goto error; - - error = nsurl_create("resource:user.css", - &html_user_stylesheet_url); + error = html_css_init(); if (error != NSERROR_OK) goto error; diff --git a/render/html.h b/render/html.h index 6920fa8c7..97899f136 100644 --- a/render/html.h +++ b/render/html.h @@ -182,8 +182,17 @@ struct content_html_frames *html_get_frameset(struct hlcache_handle *h); struct content_html_iframe *html_get_iframe(struct hlcache_handle *h); nsurl *html_get_base_url(struct hlcache_handle *h); const char *html_get_base_target(struct hlcache_handle *h); + +/** + * Retrieve stylesheets used by HTML document + * + * \param h Content to retrieve stylesheets from + * \param n Pointer to location to receive number of sheets + * \return Pointer to array of stylesheets + */ struct html_stylesheet *html_get_stylesheets(struct hlcache_handle *h, unsigned int *n); + struct content_html_object *html_get_objects(struct hlcache_handle *h, unsigned int *n); bool html_get_id_offset(struct hlcache_handle *h, lwc_string *frag_id, diff --git a/render/html_css.c b/render/html_css.c new file mode 100644 index 000000000..babe052ce --- /dev/null +++ b/render/html_css.c @@ -0,0 +1,678 @@ +/* + * Copyright 2013 Vincent Sanders + * + * 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 . + */ + +/** \file + * Processing for html content css operations. + */ + +#include +#include +#include +#include +#include +#include + +#include "content/hlcache.h" +#include "desktop/options.h" +#include "render/html_internal.h" +#include "utils/corestrings.h" +#include "utils/config.h" +#include "utils/log.h" + +static nsurl *html_default_stylesheet_url; +static nsurl *html_adblock_stylesheet_url; +static nsurl *html_quirks_stylesheet_url; +static nsurl *html_user_stylesheet_url; + +static nserror css_error_to_nserror(css_error error) +{ + switch (error) { + case CSS_OK: + return NSERROR_OK; + + case CSS_NOMEM: + return NSERROR_NOMEM; + + case CSS_BADPARM: + return NSERROR_BAD_PARAMETER; + + case CSS_INVALID: + return NSERROR_INVALID; + + case CSS_FILENOTFOUND: + return NSERROR_NOT_FOUND; + + case CSS_NEEDDATA: + return NSERROR_NEED_DATA; + + case CSS_BADCHARSET: + return NSERROR_BAD_ENCODING; + + case CSS_EOF: + case CSS_IMPORTS_PENDING: + case CSS_PROPERTY_NOT_SET: + default: + break; + } + return NSERROR_CSS; +} + +/** + * Callback for fetchcache() for linked stylesheets. + */ + +static nserror +html_convert_css_callback(hlcache_handle *css, + const hlcache_event *event, + void *pw) +{ + html_content *parent = pw; + unsigned int i; + struct html_stylesheet *s; + + /* Find sheet */ + for (i = 0, s = parent->stylesheets; + i != parent->stylesheet_count; + i++, s++) { + if (s->type == HTML_STYLESHEET_EXTERNAL && + s->data.external == css) + break; + } + + assert(i != parent->stylesheet_count); + + switch (event->type) { + case CONTENT_MSG_LOADING: + break; + + case CONTENT_MSG_READY: + break; + + case CONTENT_MSG_DONE: + LOG(("done stylesheet slot %d '%s'", i, + nsurl_access(hlcache_handle_get_url(css)))); + parent->base.active--; + LOG(("%d fetches active", parent->base.active)); + break; + + case CONTENT_MSG_ERROR: + LOG(("stylesheet %s failed: %s", + nsurl_access(hlcache_handle_get_url(css)), + event->data.error)); + hlcache_handle_release(css); + s->data.external = NULL; + parent->base.active--; + LOG(("%d fetches active", parent->base.active)); + content_add_error(&parent->base, "?", 0); + break; + + case CONTENT_MSG_STATUS: + if (event->data.explicit_status_text == NULL) { + /* Object content's status text updated */ + html_set_status(parent, + content_get_status_message(css)); + content_broadcast(&parent->base, CONTENT_MSG_STATUS, + event->data); + } else { + /* Object content wants to set explicit message */ + content_broadcast(&parent->base, CONTENT_MSG_STATUS, + event->data); + } + break; + + case CONTENT_MSG_POINTER: + /* Really don't want this to continue after the switch */ + return NSERROR_OK; + + default: + assert(0); + } + + if (parent->base.active == 0) { + html_begin_conversion(parent); + } + + return NSERROR_OK; +} + +/* exported interface documented in render/html.h */ +struct html_stylesheet *html_get_stylesheets(hlcache_handle *h, unsigned int *n) +{ + html_content *c = (html_content *) hlcache_handle_get_content(h); + + assert(c != NULL); + assert(n != NULL); + + *n = c->stylesheet_count; + + return c->stylesheets; +} + + +/* exported interface documented in render/html_internal.h */ +nserror html_css_free_stylesheets(html_content *html) +{ + unsigned int i; + + for (i = 0; i != html->stylesheet_count; i++) { + if ((html->stylesheets[i].type == HTML_STYLESHEET_EXTERNAL) && + (html->stylesheets[i].data.external != NULL)) { + hlcache_handle_release(html->stylesheets[i].data.external); + } else if ((html->stylesheets[i].type == HTML_STYLESHEET_INTERNAL) && + (html->stylesheets[i].data.internal != NULL)) { + nscss_destroy_css_data(html->stylesheets[i].data.internal); + } + } + free(html->stylesheets); + + return NSERROR_OK; +} + +/* exported interface documented in render/html_internal.h */ +nserror html_css_new_stylesheets(html_content *c) +{ + nserror ns_error; + hlcache_child_context child; + + if (c->stylesheets != NULL) { + return NSERROR_OK; /* already initialised */ + } + + /* stylesheet 0 is the base style sheet, + * stylesheet 1 is the quirks mode style sheet, + * stylesheet 2 is the adblocking stylesheet, + * stylesheet 3 is the user stylesheet */ + c->stylesheets = calloc(STYLESHEET_START, sizeof(struct html_stylesheet)); + if (c->stylesheets == NULL) { + return NSERROR_NOMEM; + } + + c->stylesheets[STYLESHEET_BASE].type = HTML_STYLESHEET_EXTERNAL; + c->stylesheets[STYLESHEET_BASE].data.external = NULL; + c->stylesheets[STYLESHEET_QUIRKS].type = HTML_STYLESHEET_EXTERNAL; + c->stylesheets[STYLESHEET_QUIRKS].data.external = NULL; + c->stylesheets[STYLESHEET_ADBLOCK].type = HTML_STYLESHEET_EXTERNAL; + c->stylesheets[STYLESHEET_ADBLOCK].data.external = NULL; + c->stylesheets[STYLESHEET_USER].type = HTML_STYLESHEET_EXTERNAL; + c->stylesheets[STYLESHEET_USER].data.external = NULL; + c->stylesheet_count = STYLESHEET_START; + + child.charset = c->encoding; + child.quirks = c->base.quirks; + + ns_error = hlcache_handle_retrieve(html_default_stylesheet_url, 0, + content_get_url(&c->base), NULL, + html_convert_css_callback, c, &child, CONTENT_CSS, + &c->stylesheets[STYLESHEET_BASE].data.external); + if (ns_error != NSERROR_OK) { + return ns_error; + } + + c->base.active++; + LOG(("%d fetches active", c->base.active)); + + if (c->quirks == DOM_DOCUMENT_QUIRKS_MODE_FULL) { + ns_error = hlcache_handle_retrieve(html_quirks_stylesheet_url, + 0, content_get_url(&c->base), NULL, + html_convert_css_callback, c, &child, + CONTENT_CSS, + &c->stylesheets[STYLESHEET_QUIRKS].data.external); + if (ns_error != NSERROR_OK) { + return ns_error; + } + + c->base.active++; + LOG(("%d fetches active", c->base.active)); + + } + + if (nsoption_bool(block_ads)) { + ns_error = hlcache_handle_retrieve(html_adblock_stylesheet_url, + 0, content_get_url(&c->base), NULL, + html_convert_css_callback, c, &child, CONTENT_CSS, + &c->stylesheets[STYLESHEET_ADBLOCK]. + data.external); + if (ns_error != NSERROR_OK) { + return ns_error; + } + + c->base.active++; + LOG(("%d fetches active", c->base.active)); + + } + + ns_error = hlcache_handle_retrieve(html_user_stylesheet_url, 0, + content_get_url(&c->base), NULL, + html_convert_css_callback, c, &child, CONTENT_CSS, + &c->stylesheets[STYLESHEET_USER].data.external); + if (ns_error != NSERROR_OK) { + return ns_error; + } + + c->base.active++; + LOG(("%d fetches active", c->base.active)); + + return ns_error; +} + +/** + * Handle notification of inline style completion + * + * \param css Inline style object + * \param pw Private data + */ +static void html_inline_style_done(struct content_css_data *css, void *pw) +{ + html_content *html = pw; + + html->base.active--; + LOG(("%d fetches active", html->base.active)); +} + +static nserror +html_stylesheet_from_domnode(html_content *c, + dom_node *node, + struct content_css_data **ret_sheet) +{ + dom_node *child, *next; + dom_exception exc; + struct content_css_data *sheet; + nserror error; + css_error csserror; + + /* create stylesheet */ + sheet = calloc(1, sizeof(struct content_css_data)); + if (sheet == NULL) { + return NSERROR_NOMEM; + } + + error = nscss_create_css_data(sheet, + nsurl_access(c->base_url), NULL, c->quirks, + html_inline_style_done, c); + if (error != NSERROR_OK) { + free(sheet); + return error; + } + + exc = dom_node_get_first_child(node, &child); + if (exc != DOM_NO_ERR) { + nscss_destroy_css_data(sheet); + free(sheet); + return NSERROR_DOM; + } + + while (child != NULL) { + dom_string *data; + + exc = dom_node_get_text_content(child, &data); + if (exc != DOM_NO_ERR) { + dom_node_unref(child); + nscss_destroy_css_data(sheet); + free(sheet); + return NSERROR_DOM; + } + + if (nscss_process_css_data(sheet, + dom_string_data(data), + dom_string_byte_length(data)) == false) { + dom_string_unref(data); + dom_node_unref(child); + nscss_destroy_css_data(sheet); + free(sheet); + return NSERROR_CSS; + } + + dom_string_unref(data); + + exc = dom_node_get_next_sibling(child, &next); + if (exc != DOM_NO_ERR) { + dom_node_unref(child); + nscss_destroy_css_data(sheet); + free(sheet); + return NSERROR_DOM; + } + + dom_node_unref(child); + child = next; + } + + c->base.active++; + LOG(("%d fetches active", c->base.active)); + + /* Convert the content -- manually, as we want the result */ + csserror = nscss_convert_css_data(sheet); + if (csserror != CSS_OK) { + /* conversion failed */ + c->base.active--; + LOG(("%d fetches active", c->base.active)); + nscss_destroy_css_data(sheet); + free(sheet); + return css_error_to_nserror(csserror); + } + + *ret_sheet = sheet; + return NSERROR_OK; +} + +/** + * Process an inline stylesheet in the document. + * + * \param c content structure + * \param style xml node of style element + * \return true on success, false if an error occurred + */ + +static struct html_stylesheet * +html_create_style_element(html_content *c, dom_node *style) +{ + dom_string *val; + dom_exception exc; + struct html_stylesheet *stylesheets; + + /* type='text/css', or not present (invalid but common) */ + exc = dom_element_get_attribute(style, corestring_dom_type, &val); + if (exc == DOM_NO_ERR && val != NULL) { + if (!dom_string_caseless_lwc_isequal(val, + corestring_lwc_text_css)) { + dom_string_unref(val); + return NULL; + } + dom_string_unref(val); + } + + /* media contains 'screen' or 'all' or not present */ + exc = dom_element_get_attribute(style, corestring_dom_media, &val); + if (exc == DOM_NO_ERR && val != NULL) { + if (strcasestr(dom_string_data(val), "screen") == NULL && + strcasestr(dom_string_data(val), + "all") == NULL) { + dom_string_unref(val); + return NULL; + } + dom_string_unref(val); + } + + /* Extend array */ + stylesheets = realloc(c->stylesheets, + sizeof(struct html_stylesheet) * (c->stylesheet_count + 1)); + if (stylesheets == NULL) { + + content_broadcast_errorcode(&c->base, NSERROR_NOMEM); + return false; + + } + c->stylesheets = stylesheets; + + c->stylesheets[c->stylesheet_count].type = HTML_STYLESHEET_INTERNAL; + c->stylesheets[c->stylesheet_count].node = style; + c->stylesheets[c->stylesheet_count].data.internal = NULL; + c->stylesheet_count++; + + return c->stylesheets + (c->stylesheet_count - 1); +} + +bool html_css_update_style(html_content *c, dom_node *style) +{ + struct content_css_data *sheet = NULL; + nserror error; + unsigned int i; + struct html_stylesheet *s; + + /* Find sheet */ + for (i = 0, s = c->stylesheets; i != c->stylesheet_count; i++, s++) { + if ((s->type == HTML_STYLESHEET_INTERNAL) && + (s->node == style)) + break; + } + if (i == c->stylesheet_count) { + s = html_create_style_element(c, style); + } + if (s == NULL) { + LOG(("Could not find or create inline stylesheet for %p", + style)); + return false; + } + + LOG(("Found sheet %p slot %d for node %p", s,i, style)); + + error = html_stylesheet_from_domnode(c, style, &sheet); + if (error != NSERROR_OK) { + LOG(("Failed to update sheet")); + content_broadcast_errorcode(&c->base, error); + return false; + } + + LOG(("Updating sheet %p with %p", s->data.internal, sheet)); + + /* Update index */ + if (s->data.internal != NULL) { + nscss_destroy_css_data(s->data.internal); + free(s->data.internal); + } + s->data.internal = sheet; + return true; +} + +bool html_css_process_link(html_content *htmlc, dom_node *node) +{ + dom_string *rel, *type_attr, *media, *href; + struct html_stylesheet *stylesheets; + nsurl *joined; + dom_exception exc; + nserror ns_error; + hlcache_child_context child; + + /* rel= */ + exc = dom_element_get_attribute(node, corestring_dom_rel, &rel); + if (exc != DOM_NO_ERR || rel == NULL) + return true; + + if (strcasestr(dom_string_data(rel), "stylesheet") == 0) { + dom_string_unref(rel); + return true; + } else if (strcasestr(dom_string_data(rel), "alternate") != 0) { + /* Ignore alternate stylesheets */ + dom_string_unref(rel); + return true; + } + dom_string_unref(rel); + + /* type='text/css' or not present */ + exc = dom_element_get_attribute(node, corestring_dom_type, &type_attr); + if (exc == DOM_NO_ERR && type_attr != NULL) { + if (!dom_string_caseless_lwc_isequal(type_attr, + corestring_lwc_text_css)) { + dom_string_unref(type_attr); + return true; + } + dom_string_unref(type_attr); + } + + /* media contains 'screen' or 'all' or not present */ + exc = dom_element_get_attribute(node, corestring_dom_media, &media); + if (exc == DOM_NO_ERR && media != NULL) { + if (strcasestr(dom_string_data(media), "screen") == NULL && + strcasestr(dom_string_data(media), "all") == NULL) { + dom_string_unref(media); + return true; + } + dom_string_unref(media); + } + + /* href='...' */ + exc = dom_element_get_attribute(node, corestring_dom_href, &href); + if (exc != DOM_NO_ERR || href == NULL) + return true; + + /* TODO: only the first preferred stylesheets (ie. + * those with a title attribute) should be loaded + * (see HTML4 14.3) */ + + ns_error = nsurl_join(htmlc->base_url, dom_string_data(href), &joined); + if (ns_error != NSERROR_OK) { + dom_string_unref(href); + goto no_memory; + } + dom_string_unref(href); + + LOG(("linked stylesheet %i '%s'", htmlc->stylesheet_count, nsurl_access(joined))); + + /* extend stylesheets array to allow for new sheet */ + stylesheets = realloc(htmlc->stylesheets, + sizeof(struct html_stylesheet) * (htmlc->stylesheet_count + 1)); + if (stylesheets == NULL) { + nsurl_unref(joined); + ns_error = NSERROR_NOMEM; + goto no_memory; + } + + htmlc->stylesheets = stylesheets; + htmlc->stylesheets[htmlc->stylesheet_count].type = HTML_STYLESHEET_EXTERNAL; + + /* start fetch */ + child.charset = htmlc->encoding; + child.quirks = htmlc->base.quirks; + + ns_error = hlcache_handle_retrieve(joined, + 0, + content_get_url(&htmlc->base), + NULL, + html_convert_css_callback, + htmlc, + &child, + CONTENT_CSS, + &htmlc->stylesheets[htmlc->stylesheet_count].data.external); + + nsurl_unref(joined); + + if (ns_error != NSERROR_OK) + goto no_memory; + + htmlc->stylesheet_count++; + + htmlc->base.active++; + LOG(("%d fetches active", htmlc->base.active)); + + return true; + +no_memory: + content_broadcast_errorcode(&htmlc->base, ns_error); + return false; +} + +nserror +html_css_new_selection_context(html_content *c, css_select_ctx **ret_select_ctx) +{ + uint32_t i; + css_error css_ret; + css_select_ctx *select_ctx; + + /* check that the base stylesheet loaded; layout fails without it */ + if (c->stylesheets[STYLESHEET_BASE].data.external == NULL) { + return NSERROR_CSS_BASE; + } + + /* Create selection context */ + css_ret = css_select_ctx_create(ns_realloc, c, &select_ctx); + if (css_ret != CSS_OK) { + return css_error_to_nserror(css_ret); + } + + /* Add sheets to it */ + for (i = STYLESHEET_BASE; i != c->stylesheet_count; i++) { + const struct html_stylesheet *hsheet = &c->stylesheets[i]; + css_stylesheet *sheet = NULL; + css_origin origin = CSS_ORIGIN_AUTHOR; + + if (i < STYLESHEET_USER) { + origin = CSS_ORIGIN_UA; + } else if (i < STYLESHEET_START) { + origin = CSS_ORIGIN_USER; + } + + if ((hsheet->type == HTML_STYLESHEET_EXTERNAL) && + (hsheet->data.external != NULL)) { + sheet = nscss_get_stylesheet(hsheet->data.external); + } else if (hsheet->type == HTML_STYLESHEET_INTERNAL) { + sheet = hsheet->data.internal->sheet; + } + + if (sheet != NULL) { + css_ret = css_select_ctx_append_sheet(select_ctx, + sheet, + origin, + CSS_MEDIA_SCREEN); + if (css_ret != CSS_OK) { + css_select_ctx_destroy(select_ctx); + return css_error_to_nserror(css_ret); + } + } + } + + /* return new selection context to caller */ + *ret_select_ctx = select_ctx; + return NSERROR_OK; +} + +nserror html_css_init(void) +{ + nserror error; + + error = nsurl_create("resource:default.css", + &html_default_stylesheet_url); + if (error != NSERROR_OK) + return error; + + error = nsurl_create("resource:adblock.css", + &html_adblock_stylesheet_url); + if (error != NSERROR_OK) + return error; + + error = nsurl_create("resource:quirks.css", + &html_quirks_stylesheet_url); + if (error != NSERROR_OK) + return error; + + error = nsurl_create("resource:user.css", + &html_user_stylesheet_url); + + return error; +} + +void html_css_fini(void) +{ + if (html_user_stylesheet_url != NULL) { + nsurl_unref(html_user_stylesheet_url); + html_user_stylesheet_url = NULL; + } + + if (html_quirks_stylesheet_url != NULL) { + nsurl_unref(html_quirks_stylesheet_url); + html_quirks_stylesheet_url = NULL; + } + + if (html_adblock_stylesheet_url != NULL) { + nsurl_unref(html_adblock_stylesheet_url); + html_adblock_stylesheet_url = NULL; + } + + if (html_default_stylesheet_url != NULL) { + nsurl_unref(html_default_stylesheet_url); + html_default_stylesheet_url = NULL; + } +} diff --git a/render/html_internal.h b/render/html_internal.h index 80b126b25..2dd1c5190 100644 --- a/render/html_internal.h +++ b/render/html_internal.h @@ -255,6 +255,24 @@ struct form *html_forms_get_forms(const char *docenc, dom_html_document *doc); struct form_control *html_forms_get_control_for_node(struct form *forms, dom_node *node); +/* in render/html_css.c */ +nserror html_css_init(void); +void html_css_fini(void); + +/** + * Initialise core stylesheets for a content + * + * \param c content structure to update + * \return nserror + */ +nserror html_css_new_stylesheets(html_content *c); +nserror html_css_free_stylesheets(html_content *html); + +bool html_css_process_link(html_content *htmlc, dom_node *node); +bool html_css_update_style(html_content *c, dom_node *style); + +nserror html_css_new_selection_context(html_content *c, css_select_ctx **ret_select_ctx); + /* Useful dom_string pointers */ struct dom_string; diff --git a/utils/corestrings.c b/utils/corestrings.c index 06c7906bd..cc1d6154a 100644 --- a/utils/corestrings.c +++ b/utils/corestrings.c @@ -38,6 +38,7 @@ lwc_string *corestring_lwc_bottom; lwc_string *corestring_lwc_button; lwc_string *corestring_lwc_caption; lwc_string *corestring_lwc_center; +lwc_string *corestring_lwc_charset; lwc_string *corestring_lwc_checkbox; lwc_string *corestring_lwc_circle; lwc_string *corestring_lwc_col; @@ -257,6 +258,7 @@ void corestrings_fini(void) CSS_LWC_STRING_UNREF(bottom); CSS_LWC_STRING_UNREF(button); CSS_LWC_STRING_UNREF(caption); + CSS_LWC_STRING_UNREF(charset); CSS_LWC_STRING_UNREF(center); CSS_LWC_STRING_UNREF(checkbox); CSS_LWC_STRING_UNREF(circle); @@ -497,6 +499,7 @@ nserror corestrings_init(void) CSS_LWC_STRING_INTERN(bottom); CSS_LWC_STRING_INTERN(button); CSS_LWC_STRING_INTERN(caption); + CSS_LWC_STRING_INTERN(charset); CSS_LWC_STRING_INTERN(center); CSS_LWC_STRING_INTERN(checkbox); CSS_LWC_STRING_INTERN(circle); diff --git a/utils/corestrings.h b/utils/corestrings.h index d4e8fc3f7..8cfd23977 100644 --- a/utils/corestrings.h +++ b/utils/corestrings.h @@ -42,6 +42,7 @@ extern lwc_string *corestring_lwc_bottom; extern lwc_string *corestring_lwc_button; extern lwc_string *corestring_lwc_caption; extern lwc_string *corestring_lwc_center; +extern lwc_string *corestring_lwc_charset; extern lwc_string *corestring_lwc_checkbox; extern lwc_string *corestring_lwc_circle; extern lwc_string *corestring_lwc_col; -- cgit v1.2.3