netsurf/css/css.c
John Mark Bell c75a613546 Add stylesheet size + size of imported sheets onto CSS content size.
Remove imported contents once we've fetched them and imported them into their parent (once this has happened, the content object is just an empty shell, so not worth keeping around).

svn path=/trunk/netsurf/; revision=8831
2009-07-27 19:40:55 +00:00

428 lines
11 KiB
C

/*
* 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 "content/content.h"
#include "content/fetch.h"
#include "content/fetchcache.h"
#include "css/css.h"
#include "css/internal.h"
#include "desktop/gui.h"
#include "render/html.h"
#include "utils/messages.h"
static void nscss_import(content_msg msg, struct content *c,
intptr_t p1, intptr_t p2, union content_msg_data data);
/**
* Allocation callback for libcss
*
* \param ptr Pointer to reallocate, or NULL for new allocation
* \param size Number of bytes requires
* \param pw Allocation context
* \return Pointer to allocated block, or NULL on failure
*/
static void *myrealloc(void *ptr, size_t size, void *pw)
{
return realloc(ptr, size);
}
/**
* Initialise a CSS content
*
* \param c Content to initialise
* \param parent Parent content, or NULL if top-level
* \param params Content-Type parameters
* \return true on success, false on failure
*/
bool nscss_create(struct content *c, struct content *parent,
const char *params[])
{
const char *charset = NULL;
css_origin origin = CSS_ORIGIN_AUTHOR;
uint64_t media = CSS_MEDIA_ALL;
lwc_context *dict = NULL;
bool quirks = true;
uint32_t i;
union content_msg_data msg_data;
css_error error;
/** \todo what happens about the allocator? */
/** \todo proper error reporting */
/* Find charset specified on HTTP layer, if any */
/** \todo What happens if there isn't one and parent content exists? */
for (i = 0; params[i] != NULL; i += 2) {
if (strcasecmp(params[i], "charset") == 0) {
charset = params[i + 1];
break;
}
}
if (parent != NULL) {
assert(parent->type == CONTENT_HTML ||
parent->type == CONTENT_CSS);
if (parent->type == CONTENT_HTML) {
assert(parent->data.html.dict != NULL);
if (c == parent->data.html.
stylesheets[STYLESHEET_BASE].c ||
c == parent->data.html.
stylesheets[STYLESHEET_QUIRKS].c ||
c == parent->data.html.
stylesheets[STYLESHEET_ADBLOCK].c)
origin = CSS_ORIGIN_UA;
quirks = (parent->data.html.quirks !=
BINDING_QUIRKS_MODE_NONE);
for (i = 0; i < parent->data.html.stylesheet_count;
i++) {
if (parent->data.html.stylesheets[i].c == c) {
media = parent->data.html.
stylesheets[i].media;
break;
}
}
dict = parent->data.html.dict;
} else {
assert(parent->data.css.sheet != NULL);
assert(parent->data.css.dict != NULL);
error = css_stylesheet_get_origin(
parent->data.css.sheet, &origin);
if (error != CSS_OK) {
msg_data.error = "?";
content_broadcast(c, CONTENT_MSG_ERROR,
msg_data);
return false;
}
error = css_stylesheet_quirks_allowed(
parent->data.css.sheet, &quirks);
if (error != CSS_OK) {
msg_data.error = "?";
content_broadcast(c, CONTENT_MSG_ERROR,
msg_data);
return false;
}
for (i = 0; i < parent->data.css.import_count; i++) {
if (parent->data.css.imports[i].c == c) {
media = parent->data.css.
imports[i].media;
break;
}
}
dict = parent->data.css.dict;
}
}
if (dict == NULL) {
lwc_error lerror = lwc_create_context(myrealloc, NULL, &dict);
if (lerror != lwc_error_ok) {
msg_data.error = messages_get("NoMemory");
content_broadcast(c, CONTENT_MSG_ERROR, msg_data);
return false;
}
}
c->data.css.dict = lwc_context_ref(dict);
c->data.css.import_count = 0;
c->data.css.imports = NULL;
error = css_stylesheet_create(CSS_LEVEL_21, charset,
c->url, NULL, origin, media, quirks, false,
c->data.css.dict,
myrealloc, NULL,
nscss_resolve_url, NULL,
&c->data.css.sheet);
if (error != CSS_OK) {
lwc_context_unref(c->data.css.dict);
c->data.css.dict = NULL;
msg_data.error = messages_get("NoMemory");
content_broadcast(c, CONTENT_MSG_ERROR, msg_data);
return false;
}
return true;
}
/**
* 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, char *data, unsigned int size)
{
union content_msg_data msg_data;
css_error error;
error = css_stylesheet_append_data(c->data.css.sheet,
(const uint8_t *) 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);
}
/**
* Convert a CSS content ready for use
*
* \param c Content to convert
* \param w Width of area content will be displayed in
* \param h Height of area content will be displayed in
* \return true on success, false on failure
*/
bool nscss_convert(struct content *c, int w, int h)
{
union content_msg_data msg_data;
uint32_t i;
size_t size;
css_error error;
error = css_stylesheet_data_done(c->data.css.sheet);
/* Process pending imports */
while (error == CSS_IMPORTS_PENDING) {
struct nscss_import *imports;
lwc_string *uri;
uint64_t media;
css_stylesheet *sheet;
error = css_stylesheet_next_pending_import(c->data.css.sheet,
&uri, &media);
if (error != CSS_OK && error != CSS_INVALID) {
msg_data.error = "?";
content_broadcast(c, CONTENT_MSG_ERROR, msg_data);
c->status = CONTENT_STATUS_ERROR;
return false;
}
/* Give up if there are no more imports */
if (error == CSS_INVALID) {
error = CSS_OK;
break;
}
/* Increase space in table */
imports = realloc(c->data.css.imports,
(c->data.css.import_count + 1) *
sizeof(struct nscss_import));
if (imports == NULL) {
msg_data.error = "?";
content_broadcast(c, CONTENT_MSG_ERROR, msg_data);
c->status = CONTENT_STATUS_ERROR;
return false;
}
c->data.css.imports = imports;
/* Create content */
i = c->data.css.import_count;
c->data.css.imports[c->data.css.import_count].media = media;
c->data.css.imports[c->data.css.import_count++].c =
fetchcache(lwc_string_data(uri),
nscss_import, (intptr_t) c, i,
c->width, c->height, true, NULL, NULL,
false, false);
if (c->data.css.imports[i].c == NULL) {
msg_data.error = "?";
content_broadcast(c, CONTENT_MSG_ERROR, msg_data);
c->status = CONTENT_STATUS_ERROR;
return false;
}
/* Fetch content */
c->active++;
fetchcache_go(c->data.css.imports[i].c, c->url,
nscss_import, (intptr_t) c, i,
c->width, c->height, NULL, NULL, false, c);
/* Wait for import to fetch + convert */
while (c->active > 0) {
fetch_poll();
gui_multitask();
}
if (c->data.css.imports[i].c != NULL) {
sheet = c->data.css.imports[i].c->data.css.sheet;
c->data.css.imports[i].c->data.css.sheet = NULL;
} else {
error = css_stylesheet_create(CSS_LEVEL_DEFAULT,
NULL, "", NULL, CSS_ORIGIN_AUTHOR,
media, false, false, c->data.css.dict,
myrealloc, NULL,
nscss_resolve_url, NULL,
&sheet);
if (error != CSS_OK) {
msg_data.error = messages_get("NoMemory");
content_broadcast(c, CONTENT_MSG_ERROR,
msg_data);
c->status = CONTENT_STATUS_ERROR;
return false;
}
}
error = css_stylesheet_register_import(
c->data.css.sheet, sheet);
if (error != CSS_OK) {
msg_data.error = "?";
content_broadcast(c, CONTENT_MSG_ERROR, msg_data);
c->status = CONTENT_STATUS_ERROR;
return false;
}
error = CSS_IMPORTS_PENDING;
}
/* Retrieve the size of this sheet */
error = css_stylesheet_size(c->data.css.sheet, &size);
if (error != CSS_OK) {
msg_data.error = "?";
content_broadcast(c, CONTENT_MSG_ERROR, msg_data);
c->status = CONTENT_STATUS_ERROR;
return false;
}
c->size += size;
/* Add on the size of the imported sheets, removing ourselves from
* their user list as we go (they're of no use to us now, as we've
* inserted the sheet into ourselves) */
for (i = 0; i < c->data.css.import_count; i++) {
if (c->data.css.imports[i].c != NULL) {
c->size += c->data.css.imports[i].c->size;
content_remove_user(c->data.css.imports[i].c,
nscss_import, (uintptr_t) c, i);
}
c->data.css.imports[i].c = NULL;
}
/* Remove the imports */
c->data.css.import_count = 0;
free(c->data.css.imports);
c->data.css.imports = NULL;
c->status = CONTENT_STATUS_DONE;
/* Filthy hack to stop this content being reused
* when whatever is using it has finished with it. */
c->fresh = false;
return error == CSS_OK;
}
/**
* Clean up a CSS content
*
* \param c Content to clean up
*/
void nscss_destroy(struct content *c)
{
uint32_t i;
for (i = 0; i < c->data.css.import_count; i++) {
if (c->data.css.imports[i].c != NULL) {
content_remove_user(c->data.css.imports[i].c,
nscss_import, (uintptr_t) c, i);
}
c->data.css.imports[i].c = NULL;
}
free(c->data.css.imports);
if (c->data.css.sheet != NULL) {
css_stylesheet_destroy(c->data.css.sheet);
c->data.css.sheet = NULL;
}
if (c->data.css.dict != NULL) {
lwc_context_unref(c->data.css.dict);
c->data.css.dict = NULL;
}
}
/**
* Fetchcache handler for imported stylesheets
*
* \param msg Message type
* \param c Content being fetched
* \param p1 Parent content
* \param p2 Index into parent's imported stylesheet array
* \param data Message data
*/
void nscss_import(content_msg msg, struct content *c,
intptr_t p1, intptr_t p2, union content_msg_data data)
{
struct content *parent = (struct content *) p1;
uint32_t i = (uint32_t) p2;
switch (msg) {
case CONTENT_MSG_LOADING:
if (c->type != CONTENT_CSS) {
content_remove_user(c, nscss_import, p1, p2);
if (c->user_list->next == NULL) {
fetch_abort(c->fetch);
c->fetch = NULL;
c->status = CONTENT_STATUS_ERROR;
}
parent->data.css.imports[i].c = NULL;
parent->active--;
content_add_error(parent, "NotCSS", 0);
}
break;
case CONTENT_MSG_READY:
break;
case CONTENT_MSG_DONE:
parent->active--;
break;
case CONTENT_MSG_AUTH:
case CONTENT_MSG_SSL:
case CONTENT_MSG_LAUNCH:
case CONTENT_MSG_ERROR:
if (parent->data.css.imports[i].c == c) {
parent->data.css.imports[i].c = NULL;
parent->active--;
}
break;
case CONTENT_MSG_STATUS:
break;
case CONTENT_MSG_NEWPTR:
parent->data.css.imports[i].c = c;
break;
default:
assert(0);
}
}