summaryrefslogtreecommitdiff
path: root/content/handlers/html/css.c
diff options
context:
space:
mode:
authorVincent Sanders <vince@kyllikki.org>2020-04-30 21:59:21 +0100
committerVincent Sanders <vince@kyllikki.org>2020-04-30 21:59:21 +0100
commit6b6bbad2b7765378b5135a26b3d410d6b0079966 (patch)
tree7ef0447745ba7f4d3cadb176cb90b9a063555d57 /content/handlers/html/css.c
parente8d0ba15ad4a91b4644cac0095f40bb3246ec509 (diff)
downloadnetsurf-6b6bbad2b7765378b5135a26b3d410d6b0079966.tar.gz
netsurf-6b6bbad2b7765378b5135a26b3d410d6b0079966.tar.bz2
remove unecessary filename prefixes in the html content handler
Diffstat (limited to 'content/handlers/html/css.c')
-rw-r--r--content/handlers/html/css.c739
1 files changed, 739 insertions, 0 deletions
diff --git a/content/handlers/html/css.c b/content/handlers/html/css.c
new file mode 100644
index 000000000..23d51f1be
--- /dev/null
+++ b/content/handlers/html/css.c
@@ -0,0 +1,739 @@
+/*
+ * Copyright 2013 Vincent Sanders <vince@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/>.
+ */
+
+/**
+ * \file
+ * Processing for html content css operations.
+ */
+
+#include "utils/config.h"
+
+#define _GNU_SOURCE /* strcasestr needs this for string.h */
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdint.h>
+#include <string.h>
+#include <strings.h>
+#include <stdlib.h>
+
+#include "utils/nsoption.h"
+#include "utils/corestrings.h"
+#include "utils/log.h"
+#include "netsurf/misc.h"
+#include "netsurf/content.h"
+#include "content/hlcache.h"
+#include "css/css.h"
+#include "desktop/gui_internal.h"
+
+#include "html/html.h"
+#include "html/html_internal.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 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->sheet == css)
+ break;
+ }
+
+ assert(i != parent->stylesheet_count);
+
+ switch (event->type) {
+
+ case CONTENT_MSG_DONE:
+ NSLOG(netsurf, INFO, "done stylesheet slot %d '%s'", i,
+ nsurl_access(hlcache_handle_get_url(css)));
+ parent->base.active--;
+ NSLOG(netsurf, INFO, "%d fetches active", parent->base.active);
+ break;
+
+ case CONTENT_MSG_ERROR:
+ NSLOG(netsurf, INFO, "stylesheet %s failed: %s",
+ nsurl_access(hlcache_handle_get_url(css)),
+ event->data.errordata.errormsg);
+
+ hlcache_handle_release(css);
+ s->sheet = NULL;
+ parent->base.active--;
+ NSLOG(netsurf, INFO, "%d fetches active", parent->base.active);
+ content_add_error(&parent->base, "?", 0);
+ break;
+
+ case CONTENT_MSG_POINTER:
+ /* Really don't want this to continue after the switch */
+ return NSERROR_OK;
+
+ default:
+ break;
+ }
+
+ if (html_can_begin_conversion(parent)) {
+ html_begin_conversion(parent);
+ }
+
+ return NSERROR_OK;
+}
+
+static nserror
+html_stylesheet_from_domnode(html_content *c,
+ dom_node *node,
+ hlcache_handle **sheet)
+{
+ hlcache_child_context child;
+ dom_string *style;
+ nsurl *url;
+ dom_exception exc;
+ nserror error;
+ uint32_t key;
+ char urlbuf[64];
+
+ child.charset = c->encoding;
+ child.quirks = c->base.quirks;
+
+ exc = dom_node_get_text_content(node, &style);
+ if ((exc != DOM_NO_ERR) || (style == NULL)) {
+ NSLOG(netsurf, INFO, "No text content");
+ return NSERROR_OK;
+ }
+
+ error = html_css_fetcher_add_item(style, c->base_url, &key);
+ if (error != NSERROR_OK) {
+ dom_string_unref(style);
+ return error;
+ }
+
+ dom_string_unref(style);
+
+ snprintf(urlbuf, sizeof(urlbuf), "x-ns-css:%u", key);
+
+ error = nsurl_create(urlbuf, &url);
+ if (error != NSERROR_OK) {
+ return error;
+ }
+
+ error = hlcache_handle_retrieve(url, 0,
+ content_get_url(&c->base), NULL,
+ html_convert_css_callback, c, &child, CONTENT_CSS,
+ sheet);
+ if (error != NSERROR_OK) {
+ nsurl_unref(url);
+ return error;
+ }
+
+ nsurl_unref(url);
+
+ c->base.active++;
+ NSLOG(netsurf, INFO, "%d fetches active", c->base.active);
+
+ 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_error(&c->base, NSERROR_NOMEM, NULL);
+ return false;
+
+ }
+ c->stylesheets = stylesheets;
+
+ c->stylesheets[c->stylesheet_count].node = dom_node_ref(style);
+ c->stylesheets[c->stylesheet_count].sheet = NULL;
+ c->stylesheets[c->stylesheet_count].modified = false;
+ c->stylesheets[c->stylesheet_count].unused = false;
+ c->stylesheet_count++;
+
+ return c->stylesheets + (c->stylesheet_count - 1);
+}
+
+static bool html_css_process_modified_style(html_content *c,
+ struct html_stylesheet *s)
+{
+ hlcache_handle *sheet = NULL;
+ nserror error;
+
+ error = html_stylesheet_from_domnode(c, s->node, &sheet);
+ if (error != NSERROR_OK) {
+ NSLOG(netsurf, INFO, "Failed to update sheet");
+ content_broadcast_error(&c->base, error, NULL);
+ return false;
+ }
+
+ if (sheet != NULL) {
+ NSLOG(netsurf, INFO, "Updating sheet %p with %p", s->sheet,
+ sheet);
+
+ if (s->sheet != NULL) {
+ switch (content_get_status(s->sheet)) {
+ case CONTENT_STATUS_DONE:
+ break;
+ default:
+ hlcache_handle_abort(s->sheet);
+ c->base.active--;
+ NSLOG(netsurf, INFO, "%d fetches active",
+ c->base.active);
+ }
+ hlcache_handle_release(s->sheet);
+ }
+ s->sheet = sheet;
+ }
+
+ s->modified = false;
+
+ return true;
+}
+
+static void html_css_process_modified_styles(void *pw)
+{
+ html_content *c = pw;
+ struct html_stylesheet *s;
+ unsigned int i;
+ bool all_done = true;
+
+ for (i = 0, s = c->stylesheets; i != c->stylesheet_count; i++, s++) {
+ if (c->stylesheets[i].modified) {
+ all_done &= html_css_process_modified_style(c, s);
+ }
+ }
+
+ /* If we failed to process any sheet, schedule a retry */
+ if (all_done == false) {
+ guit->misc->schedule(1000, html_css_process_modified_styles, c);
+ }
+}
+
+bool html_css_update_style(html_content *c, dom_node *style)
+{
+ unsigned int i;
+ struct html_stylesheet *s;
+
+ /* Find sheet */
+ for (i = 0, s = c->stylesheets; i != c->stylesheet_count; i++, s++) {
+ if (s->node == style)
+ break;
+ }
+ if (i == c->stylesheet_count) {
+ s = html_create_style_element(c, style);
+ }
+ if (s == NULL) {
+ NSLOG(netsurf, INFO,
+ "Could not find or create inline stylesheet for %p",
+ style);
+ return false;
+ }
+
+ s->modified = true;
+
+ guit->misc->schedule(0, html_css_process_modified_styles, c);
+
+ return true;
+}
+
+bool html_css_process_style(html_content *c, dom_node *node)
+{
+ unsigned int i;
+ dom_string *val;
+ dom_exception exc;
+ struct html_stylesheet *s;
+
+ /* Find sheet */
+ for (i = 0, s = c->stylesheets; i != c->stylesheet_count; i++, s++) {
+ if (s->node == node)
+ break;
+ }
+
+ /* Should already exist */
+ if (i == c->stylesheet_count) {
+ return false;
+ }
+
+ exc = dom_element_get_attribute(node, 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) {
+ s->unused = true;
+ }
+ dom_string_unref(val);
+ }
+
+ 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=<space separated list, including 'stylesheet'> */
+ 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);
+
+ NSLOG(netsurf, INFO, "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].node = NULL;
+ htmlc->stylesheets[htmlc->stylesheet_count].modified = false;
+ htmlc->stylesheets[htmlc->stylesheet_count].unused = false;
+
+ /* 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].sheet);
+
+ nsurl_unref(joined);
+
+ if (ns_error != NSERROR_OK)
+ goto no_memory;
+
+ htmlc->stylesheet_count++;
+
+ htmlc->base.active++;
+ NSLOG(netsurf, INFO, "%d fetches active", htmlc->base.active);
+
+ return true;
+
+no_memory:
+ content_broadcast_error(&htmlc->base, ns_error, NULL);
+ return false;
+}
+
+/* exported interface documented in html/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 html/html_internal.h */
+bool html_saw_insecure_stylesheets(html_content *html)
+{
+ struct html_stylesheet *s;
+ unsigned int i;
+
+ for (i = 0, s = html->stylesheets; i < html->stylesheet_count;
+ i++, s++) {
+ if (s->sheet != NULL) {
+ if (content_saw_insecure_objects(s->sheet)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+/* exported interface documented in html/html_internal.h */
+nserror html_css_free_stylesheets(html_content *html)
+{
+ unsigned int i;
+
+ guit->misc->schedule(-1, html_css_process_modified_styles, html);
+
+ for (i = 0; i != html->stylesheet_count; i++) {
+ if (html->stylesheets[i].sheet != NULL) {
+ hlcache_handle_release(html->stylesheets[i].sheet);
+ }
+ if (html->stylesheets[i].node != NULL) {
+ dom_node_unref(html->stylesheets[i].node);
+ }
+ }
+ free(html->stylesheets);
+
+ return NSERROR_OK;
+}
+
+/* exported interface documented in html/html_internal.h */
+nserror html_css_quirks_stylesheets(html_content *c)
+{
+ nserror ns_error = NSERROR_OK;
+ hlcache_child_context child;
+
+ assert(c->stylesheets != NULL);
+
+ if (c->quirks == DOM_DOCUMENT_QUIRKS_MODE_FULL) {
+ child.charset = c->encoding;
+ child.quirks = c->base.quirks;
+
+ 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].sheet);
+ if (ns_error != NSERROR_OK) {
+ return ns_error;
+ }
+
+ c->base.active++;
+ NSLOG(netsurf, INFO, "%d fetches active", c->base.active);
+ }
+
+ return ns_error;
+}
+
+/* exported interface documented in html/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].sheet = NULL;
+ c->stylesheets[STYLESHEET_QUIRKS].sheet = NULL;
+ c->stylesheets[STYLESHEET_ADBLOCK].sheet = NULL;
+ c->stylesheets[STYLESHEET_USER].sheet = 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].sheet);
+ if (ns_error != NSERROR_OK) {
+ return ns_error;
+ }
+
+ c->base.active++;
+ NSLOG(netsurf, INFO, "%d fetches active", c->base.active);
+
+
+ if (nsoption_bool(block_advertisements)) {
+ 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].sheet);
+ if (ns_error != NSERROR_OK) {
+ return ns_error;
+ }
+
+ c->base.active++;
+ NSLOG(netsurf, INFO, "%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].sheet);
+ if (ns_error != NSERROR_OK) {
+ return ns_error;
+ }
+
+ c->base.active++;
+ NSLOG(netsurf, INFO, "%d fetches active", c->base.active);
+
+ return ns_error;
+}
+
+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].sheet == NULL) {
+ return NSERROR_CSS_BASE;
+ }
+
+ /* Create selection context */
+ css_ret = css_select_ctx_create(&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;
+
+ /* Filter out stylesheets for non-screen media. */
+ /* TODO: We should probably pass the sheet in anyway, and let
+ * libcss handle the filtering.
+ */
+ if (hsheet->unused) {
+ continue;
+ }
+
+ if (i < STYLESHEET_USER) {
+ origin = CSS_ORIGIN_UA;
+ } else if (i < STYLESHEET_START) {
+ origin = CSS_ORIGIN_USER;
+ }
+
+ if (hsheet->sheet != NULL) {
+ sheet = nscss_get_stylesheet(hsheet->sheet);
+ }
+
+ if (sheet != NULL) {
+ /* TODO: Pass the sheet's full media query, instead of
+ * "screen".
+ */
+ css_ret = css_select_ctx_append_sheet(select_ctx,
+ sheet,
+ origin,
+ "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 = html_css_fetcher_register();
+ if (error != NSERROR_OK)
+ return 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;
+ }
+}