netsurf/content/handlers/html/html_css.c
Michael Drake a03b4a3c14 CSS: Update for change to libcss append sheet API.
When appending stylesheets to the selection context, it now
takes the media query string associated with the sheet, rather
than the type bitfield.

TODO:

We need to pass all the sheets in, with their full media
query string, rather than filtering it ourselves and setting
the ones we pass in to "screen".

Signed-off-by: Michael Drake <michael.drake@codethink.co.uk>
2019-05-04 14:51:42 +01:00

722 lines
17 KiB
C

/*
* 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 <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/config.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.error);
/* fall through */
case CONTENT_MSG_ERRORCODE:
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_errorcode(&c->base, NSERROR_NOMEM);
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_errorcode(&c->base, error);
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_errorcode(&htmlc->base, ns_error);
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 */
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;
}
}