netsurf/content/content.c

1346 lines
33 KiB
C

/*
* Copyright 2005-2007 James Bursa <bursa@users.sourceforge.net>
*
* 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
* Content handling (implementation).
*
* This implementation is based on the ::handler_map array, which maps
* ::content_type to the functions which implement that type.
*/
#include <assert.h>
#include <inttypes.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <time.h>
#include "utils/config.h"
#include "content/content.h"
#include "content/fetch.h"
#include "content/fetchcache.h"
#include "css/css.h"
#include "image/bitmap.h"
#include "desktop/options.h"
#include "render/directory.h"
#include "render/html.h"
#include "render/textplain.h"
#ifdef WITH_JPEG
#include "image/jpeg.h"
#endif
#ifdef WITH_MNG
#include "image/mng.h"
#endif
#ifdef WITH_GIF
#include "image/gif.h"
#endif
#ifdef WITH_BMP
#include "image/bmp.h"
#include "image/ico.h"
#endif
#ifdef WITH_NS_SVG
#include "image/svg.h"
#endif
#ifdef WITH_RSVG
#include "image/rsvg.h"
#endif
#ifdef WITH_SPRITE
#include "riscos/sprite.h"
#endif
#ifdef WITH_NSSPRITE
#include "image/nssprite.h"
#endif
#ifdef WITH_DRAW
#include "riscos/draw.h"
#endif
#ifdef WITH_PLUGIN
#include "riscos/plugin.h"
#endif
#ifdef WITH_ARTWORKS
#include "riscos/artworks.h"
#endif
#ifdef WITH_PNG
#include "image/png.h"
#endif
#include "utils/log.h"
#include "utils/messages.h"
#include "utils/talloc.h"
#include "utils/utils.h"
/** Linked list of all content structures. May include more than one content
* per URL. Doubly-linked. */
struct content *content_list = 0;
/** An entry in mime_map. */
struct mime_entry {
char mime_type[40];
content_type type;
};
/** A map from MIME type to ::content_type. Must be sorted by mime_type. */
static const struct mime_entry mime_map[] = {
#ifdef WITH_BMP
{"application/bmp", CONTENT_BMP},
#endif
#ifdef WITH_DRAW
{"application/drawfile", CONTENT_DRAW},
#endif
#ifdef WITH_BMP
{"application/ico", CONTENT_ICO},
{"application/preview", CONTENT_BMP},
{"application/x-bmp", CONTENT_BMP},
#endif
#ifdef WITH_DRAW
{"application/x-drawfile", CONTENT_DRAW},
#endif
#ifdef WITH_BMP
{"application/x-ico", CONTENT_ICO},
#endif
{"application/x-netsurf-directory", CONTENT_DIRECTORY},
#ifdef WITH_THEME_INSTALL
{"application/x-netsurf-theme", CONTENT_THEME},
#endif
#ifdef WITH_BMP
{"application/x-win-bitmap", CONTENT_BMP},
#endif
{"application/xhtml+xml", CONTENT_HTML},
#ifdef WITH_BMP
{"image/bmp", CONTENT_BMP},
#endif
#ifdef WITH_DRAW
{"image/drawfile", CONTENT_DRAW},
#endif
#ifdef WITH_GIF
{"image/gif", CONTENT_GIF},
#endif
#ifdef WITH_BMP
{"image/ico", CONTENT_ICO},
#endif
#ifdef WITH_MNG
{"image/jng", CONTENT_JNG},
#endif
#ifdef WITH_JPEG
{"image/jpeg", CONTENT_JPEG},
{"image/jpg", CONTENT_JPEG},
#endif
#ifdef WITH_MNG
{"image/mng", CONTENT_MNG},
#endif
#ifdef WITH_BMP
{"image/ms-bmp", CONTENT_BMP},
#endif
#ifdef WITH_JPEG
{"image/pjpeg", CONTENT_JPEG},
#endif
#if defined(WITH_MNG) || defined(WITH_PNG)
{"image/png", CONTENT_PNG},
#endif
#if defined(WITH_NS_SVG) || defined (WITH_RSVG)
{"image/svg", CONTENT_SVG},
{"image/svg+xml", CONTENT_SVG},
#endif
#ifdef WITH_ARTWORKS
{"image/x-artworks", CONTENT_ARTWORKS},
#endif
#ifdef WITH_BMP
{"image/x-bitmap", CONTENT_BMP},
{"image/x-bmp", CONTENT_BMP},
#endif
#ifdef WITH_DRAW
{"image/x-drawfile", CONTENT_DRAW},
#endif
#ifdef WITH_BMP
{"image/x-icon", CONTENT_ICO},
#endif
#ifdef WITH_MNG
{"image/x-jng", CONTENT_JNG},
{"image/x-mng", CONTENT_MNG},
#endif
#ifdef WITH_BMP
{"image/x-ms-bmp", CONTENT_BMP},
#endif
#if defined(WITH_SPRITE) || defined(WITH_NSSPRITE)
{"image/x-riscos-sprite", CONTENT_SPRITE},
#endif
#ifdef WITH_BMP
{"image/x-win-bitmap", CONTENT_BMP},
{"image/x-windows-bmp", CONTENT_BMP},
{"image/x-xbitmap", CONTENT_BMP},
#endif
{"text/css", CONTENT_CSS},
{"text/html", CONTENT_HTML},
{"text/plain", CONTENT_TEXTPLAIN},
#ifdef WITH_MNG
{"video/mng", CONTENT_MNG},
{"video/x-mng", CONTENT_MNG},
#endif
};
#define MIME_MAP_COUNT (sizeof(mime_map) / sizeof(mime_map[0]))
const char * const content_type_name[] = {
"HTML",
"TEXTPLAIN",
"CSS",
#ifdef WITH_JPEG
"JPEG",
#endif
#ifdef WITH_GIF
"GIF",
#endif
#ifdef WITH_BMP
"BMP",
"ICO",
#endif
#if defined(WITH_MNG) || defined(WITH_PNG)
"PNG",
#endif
#ifdef WITH_MNG
"JNG",
"MNG",
#endif
#if defined(WITH_SPRITE) || defined(WITH_NSSPRITE)
"SPRITE",
#endif
#ifdef WITH_DRAW
"DRAW",
#endif
#ifdef WITH_PLUGIN
"PLUGIN",
#endif
"DIRECTORY",
#ifdef WITH_THEME_INSTALL
"THEME",
#endif
#ifdef WITH_ARTWORKS
"ARTWORKS",
#endif
#if defined(WITH_NS_SVG) || defined(WITH_RSVG)
"SVG",
#endif
"OTHER",
"UNKNOWN"
};
const char * const content_status_name[] = {
"TYPE_UNKNOWN",
"LOADING",
"READY",
"DONE",
"ERROR"
};
/** An entry in handler_map. */
struct handler_entry {
bool (*create)(struct content *c, struct content *parent,
const char *params[]);
bool (*process_data)(struct content *c, char *data, unsigned int size);
bool (*convert)(struct content *c, int width, int height);
void (*reformat)(struct content *c, int width, int height);
void (*destroy)(struct content *c);
void (*stop)(struct content *c);
bool (*redraw)(struct content *c, int x, int y,
int width, int height,
int clip_x0, int clip_y0, int clip_x1, int clip_y1,
float scale, colour background_colour);
bool (*redraw_tiled)(struct content *c, int x, int y,
int width, int height,
int clip_x0, int clip_y0, int clip_x1, int clip_y1,
float scale, colour background_colour,
bool repeat_x, bool repeat_y);
void (*open)(struct content *c, struct browser_window *bw,
struct content *page, unsigned int index,
struct box *box,
struct object_params *params);
void (*close)(struct content *c);
/** There must be one content per user for this type. */
bool no_share;
};
/** A table of handler functions, indexed by ::content_type.
* Must be ordered as enum ::content_type. */
static const struct handler_entry handler_map[] = {
{html_create, html_process_data, html_convert,
html_reformat, html_destroy, html_stop, html_redraw, 0,
html_open, html_close,
true},
{textplain_create, textplain_process_data, textplain_convert,
textplain_reformat, textplain_destroy, 0, textplain_redraw, 0,
0, 0, true},
{nscss_create, nscss_process_data, nscss_convert, 0, nscss_destroy,
0, 0, 0, 0, 0, true},
#ifdef WITH_JPEG
{0, 0, nsjpeg_convert, 0, nsjpeg_destroy, 0,
nsjpeg_redraw, nsjpeg_redraw_tiled, 0, 0, false},
#endif
#ifdef WITH_GIF
{nsgif_create, 0, nsgif_convert, 0, nsgif_destroy, 0,
nsgif_redraw, nsgif_redraw_tiled, 0, 0, false},
#endif
#ifdef WITH_BMP
{nsbmp_create, 0, nsbmp_convert, 0, nsbmp_destroy, 0,
nsbmp_redraw, nsbmp_redraw_tiled, 0, 0, false},
{nsico_create, 0, nsico_convert, 0, nsico_destroy, 0,
nsico_redraw, nsico_redraw_tiled, 0, 0, false},
#endif
#ifdef WITH_PNG
{nspng_create, nspng_process_data, nspng_convert,
0, nspng_destroy, 0, nspng_redraw, nspng_redraw_tiled,
0, 0, false},
#else
#ifdef WITH_MNG
{nsmng_create, nsmng_process_data, nsmng_convert,
0, nsmng_destroy, 0, nsmng_redraw, nsmng_redraw_tiled,
0, 0, false},
#endif
#endif
#ifdef WITH_MNG
{nsmng_create, nsmng_process_data, nsmng_convert,
0, nsmng_destroy, 0, nsmng_redraw, nsmng_redraw_tiled,
0, 0, false},
{nsmng_create, nsmng_process_data, nsmng_convert,
0, nsmng_destroy, 0, nsmng_redraw, nsmng_redraw_tiled,
0, 0, false},
#endif
#ifdef WITH_SPRITE
{0, 0, sprite_convert,
0, sprite_destroy, 0, sprite_redraw, 0, 0, 0, false},
#endif
#ifdef WITH_NSSPRITE
{0, 0, nssprite_convert,
0, nssprite_destroy, 0, nssprite_redraw, 0, 0, 0, false},
#endif
#ifdef WITH_DRAW
{0, 0, draw_convert,
0, draw_destroy, 0, draw_redraw, 0, 0, 0, false},
#endif
#ifdef WITH_PLUGIN
{plugin_create, 0, plugin_convert,
plugin_reformat, plugin_destroy, 0, plugin_redraw, 0,
plugin_open, plugin_close,
true},
#endif
{directory_create, 0, directory_convert,
0, directory_destroy, 0, 0, 0, 0, 0, true},
#ifdef WITH_THEME_INSTALL
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, false},
#endif
#ifdef WITH_ARTWORKS
{0, 0, artworks_convert,
0, artworks_destroy, 0, artworks_redraw, 0, 0, 0, false},
#endif
#ifdef WITH_NS_SVG
{svg_create, 0, svg_convert,
0, svg_destroy, 0, svg_redraw, 0, 0, 0, true},
#endif
#ifdef WITH_RSVG
{rsvg_create, rsvg_process_data, rsvg_convert,
0, rsvg_destroy, 0, rsvg_redraw, 0, 0, 0, false},
#endif
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, false}
};
#define HANDLER_MAP_COUNT (sizeof(handler_map) / sizeof(handler_map[0]))
static void content_update_status(struct content *c);
static void content_destroy(struct content *c);
static void content_stop_check(struct content *c);
/**
* Convert a MIME type to a content_type.
*
* The returned ::content_type will always be suitable for content_set_type().
*/
content_type content_lookup(const char *mime_type)
{
struct mime_entry *m;
m = bsearch(mime_type, mime_map, MIME_MAP_COUNT, sizeof(mime_map[0]),
(int (*)(const void *, const void *)) strcasecmp);
if (m == 0) {
#ifdef WITH_PLUGIN
if (plugin_handleable(mime_type))
return CONTENT_PLUGIN;
#endif
return CONTENT_OTHER;
}
return m->type;
}
/**
* Create a new content structure.
*
* \param url URL of content, copied
* \return the new content structure, or 0 on memory exhaustion
*
* The type is initialised to CONTENT_UNKNOWN, and the status to
* CONTENT_STATUS_TYPE_UNKNOWN.
*/
struct content * content_create(const char *url)
{
struct content *c;
struct content_user *user_sentinel;
c = talloc_zero(0, struct content);
if (!c)
return 0;
LOG(("url %s -> %p", url, c));
user_sentinel = talloc(c, struct content_user);
if (!user_sentinel) {
talloc_free(c);
return 0;
}
c->url = talloc_strdup(c, url);
if (!c->url) {
talloc_free(c);
return 0;
}
talloc_set_name_const(c, c->url);
c->type = CONTENT_UNKNOWN;
c->mime_type = 0;
c->status = CONTENT_STATUS_TYPE_UNKNOWN;
c->width = 0;
c->height = 0;
c->available_width = 0;
c->refresh = 0;
c->bitmap = 0;
c->fresh = false;
c->time = wallclock();
c->size = 0;
c->title = 0;
c->active = 0;
user_sentinel->callback = 0;
user_sentinel->p1 = user_sentinel->p2 = 0;
user_sentinel->next = 0;
c->user_list = user_sentinel;
c->sub_status[0] = 0;
c->locked = false;
c->fetch = 0;
c->source_data = 0;
c->source_size = 0;
c->source_allocated = 0;
c->total_size = 0;
c->http_code = 0;
c->no_error_pages = false;
c->download = false;
c->tried_with_auth = false;
c->redirect_count = 0;
c->error_count = 0;
c->cache_data.req_time = 0;
c->cache_data.res_time = 0;
c->cache_data.date = 0;
c->cache_data.expires = 0;
c->cache_data.age = INVALID_AGE;
c->cache_data.max_age = INVALID_AGE;
c->cache_data.no_cache = false;
c->cache_data.etag = 0;
c->cache_data.last_modified = 0;
content_set_status(c, messages_get("Loading"));
c->prev = 0;
c->next = content_list;
if (content_list)
content_list->prev = c;
content_list = c;
return c;
}
/**
* Get a content from the memory cache.
*
* \param url URL of content
* \return content if found, or 0
*
* Searches the list of contents for one corresponding to the given url, and
* which is fresh and shareable.
*/
struct content * content_get(const char *url)
{
struct content *c;
for (c = content_list; c; c = c->next) {
if (!c->fresh)
/* not fresh */
continue;
if (c->status == CONTENT_STATUS_ERROR)
/* error state */
continue;
/** \todo We need to reconsider the entire caching strategy in
* the light of data being shared between specific contents.
*
* For example, string dictionaries are owned by the document,
* and all stylesheets used by the document share the same
* dictionary.
*
* The CSS content handler retrieves the dictionary from its
* parent content. This relies upon there being a 1:1 mapping
* between documents and stylesheets.
*
* The type of a content is only known once we've received the
* headers from the fetch layer (and potentially some of the
* content data, too, if we ever sniff for the type). There
* is thus a problem with returning contents of unknown type
* here -- when we subsequently discover that they must only
* have one user, we clone them. By that point, however, we've
* no idea what the parent content is, which means that they
* end up with the wrong parent (and thus wrong dictionary).
*
* Of course, the problem with ignoring unknown content types
* here is that, for all the content types which may be shared,
* we end up duplicating them and wasting memory. Hence the
* need to reconsider everything.
*/
if (c->type == CONTENT_UNKNOWN)
continue;
if (c->type != CONTENT_UNKNOWN &&
handler_map[c->type].no_share &&
c->user_list->next)
/* not shareable, and has a user already */
continue;
if (strcmp(c->url, url))
continue;
return c;
}
return 0;
}
/**
* Get a READY or DONE content from the memory cache.
*
* \param url URL of content
* \return content if found, or 0
*
* Searches the list of contents for one corresponding to the given url, and
* which is fresh, shareable and either READY or DONE.
*/
struct content * content_get_ready(const char *url)
{
struct content *c;
for (c = content_list; c; c = c->next) {
if (!c->fresh)
/* not fresh */
continue;
if (c->status != CONTENT_STATUS_READY &&
c->status != CONTENT_STATUS_DONE)
/* not ready or done */
continue;
if (c->type != CONTENT_UNKNOWN &&
handler_map[c->type].no_share &&
c->user_list->next)
/* not shareable, and has a user already */
continue;
if (strcmp(c->url, url))
continue;
return c;
}
return 0;
}
/**
* Get whether a content can reformat
*
* \param c content to check
* \return whether the content can reformat
*/
bool content_can_reformat(struct content *c)
{
return (handler_map[c->type].reformat != NULL);
}
/**
* Initialise the content for the specified type.
*
* \param c content structure
* \param type content_type to initialise to
* \param mime_type MIME-type string for this content
* \param params array of strings, ordered attribute, value, attribute, ..., 0
* \return true on success, false on error and error broadcast to users and
* possibly reported
*
* The type is updated to the given type, and a copy of mime_type is taken. The
* status is changed to CONTENT_STATUS_LOADING. CONTENT_MSG_LOADING is sent to
* all users. The create function for the type is called to initialise the type
* specific parts of the content structure.
*/
bool content_set_type(struct content *c, content_type type,
const char *mime_type, const char *params[],
struct content *parent)
{
union content_msg_data msg_data;
struct content *clone;
void (*callback)(content_msg msg, struct content *c, intptr_t p1,
intptr_t p2, union content_msg_data data);
intptr_t p1, p2;
assert(c != 0);
assert(c->status == CONTENT_STATUS_TYPE_UNKNOWN);
assert(type < CONTENT_UNKNOWN);
LOG(("content %s (%p), type %i", c->url, c, type));
c->mime_type = talloc_strdup(c, mime_type);
if (!c->mime_type) {
c->status = CONTENT_STATUS_ERROR;
msg_data.error = messages_get("NoMemory");
content_broadcast(c, CONTENT_MSG_ERROR, msg_data);
return false;
}
c->type = type;
c->status = CONTENT_STATUS_LOADING;
if (handler_map[type].no_share && c->user_list->next &&
c->user_list->next->next) {
/* type not shareable, and more than one user: split into
* a content per user */
const char *referer =
c->fetch ? fetch_get_referer(c->fetch) : NULL;
struct content *parent =
c->fetch ? fetch_get_parent(c->fetch) : NULL;
while (c->user_list->next->next) {
clone = content_create(c->url);
if (!clone) {
c->type = CONTENT_UNKNOWN;
c->status = CONTENT_STATUS_ERROR;
msg_data.error = messages_get("NoMemory");
content_broadcast(c, CONTENT_MSG_ERROR,
msg_data);
return false;
}
clone->width = c->width;
clone->height = c->height;
clone->fresh = c->fresh;
callback = c->user_list->next->next->callback;
p1 = c->user_list->next->next->p1;
p2 = c->user_list->next->next->p2;
if (!content_add_user(clone, callback, p1, p2)) {
c->type = CONTENT_UNKNOWN;
c->status = CONTENT_STATUS_ERROR;
content_destroy(clone);
msg_data.error = messages_get("NoMemory");
content_broadcast(c, CONTENT_MSG_ERROR,
msg_data);
return false;
}
content_remove_user(c, callback, p1, p2);
msg_data.new_url = NULL;
content_broadcast(clone, CONTENT_MSG_NEWPTR, msg_data);
fetchcache_go(clone, referer,
callback, p1, p2,
clone->width, clone->height,
0, 0, false, parent);
}
}
if (handler_map[type].create) {
if (!handler_map[type].create(c, parent, params)) {
c->type = CONTENT_UNKNOWN;
c->status = CONTENT_STATUS_ERROR;
return false;
}
}
content_broadcast(c, CONTENT_MSG_LOADING, msg_data);
return true;
}
/**
* Updates content with new status.
*
* The textual status contained in the content is updated with given string.
*
* \param status_message new textual status
*/
void content_set_status(struct content *c, const char *status_message, ...)
{
va_list ap;
int len;
va_start(ap, status_message);
if ((len = vsnprintf(c->sub_status, sizeof (c->sub_status),
status_message, ap)) < 0 ||
(int)sizeof (c->sub_status) <= len)
c->sub_status[sizeof (c->sub_status) - 1] = '\0';
va_end(ap);
content_update_status(c);
}
void content_update_status(struct content *c)
{
char token[20];
const char *status;
unsigned int time;
snprintf(token, sizeof token, "HTTP%li", c->http_code);
status = messages_get(token);
if (status == token)
status = token + 4;
if (c->status == CONTENT_STATUS_TYPE_UNKNOWN ||
c->status == CONTENT_STATUS_LOADING ||
c->status == CONTENT_STATUS_READY)
time = wallclock() - c->time;
else
time = c->time;
snprintf(c->status_message, sizeof (c->status_message),
"%s (%.1fs) %s", status,
(float) time / 100, c->sub_status);
/* LOG(("%s", c->status_message)); */
}
/**
* Process a block of source data.
*
* Calls the process_data function for the content.
*
* \param c content structure
* \param data new data to process
* \param size size of data
* \return true on success, false on error and error broadcast to users and
* possibly reported
*/
bool content_process_data(struct content *c, const char *data,
unsigned int size)
{
char *source_data;
union content_msg_data msg_data;
unsigned int extra_space;
assert(c);
assert(c->type < HANDLER_MAP_COUNT);
assert(c->status == CONTENT_STATUS_LOADING);
if ((c->source_size + size) > c->source_allocated) {
extra_space = (c->source_size + size) / 4;
if (extra_space < 65536)
extra_space = 65536;
source_data = talloc_realloc(c, c->source_data, char,
c->source_size + size + extra_space);
if (!source_data) {
c->status = CONTENT_STATUS_ERROR;
msg_data.error = messages_get("NoMemory");
content_broadcast(c, CONTENT_MSG_ERROR, msg_data);
return false;
}
c->source_data = source_data;
c->source_allocated = c->source_size + size + extra_space;
}
memcpy(c->source_data + c->source_size, data, size);
c->source_size += size;
if (handler_map[c->type].process_data) {
if (!handler_map[c->type].process_data(c,
c->source_data + c->source_size - size, size)) {
c->status = CONTENT_STATUS_ERROR;
return false;
}
}
return true;
}
/**
* All data has arrived, convert for display.
*
* Calls the convert function for the content.
*
* - If the conversion succeeds, but there is still some processing required
* (eg. loading images), the content gets status CONTENT_STATUS_READY, and a
* CONTENT_MSG_READY is sent to all users.
* - If the conversion succeeds and is complete, the content gets status
* CONTENT_STATUS_DONE, and CONTENT_MSG_READY then CONTENT_MSG_DONE are sent.
* - If the conversion fails, CONTENT_MSG_ERROR is sent. The content will soon
* be destroyed and must no longer be used.
*/
void content_convert(struct content *c, int width, int height)
{
union content_msg_data msg_data;
char *source_data;
assert(c);
assert(c->type < HANDLER_MAP_COUNT);
assert(c->status == CONTENT_STATUS_LOADING);
assert(!c->locked);
LOG(("content %s (%p)", c->url, c));
if (c->source_allocated != c->source_size) {
source_data = talloc_realloc(c, c->source_data, char,
c->source_size);
if (source_data) {
c->source_data = source_data;
c->source_allocated = c->source_size;
}
}
c->locked = true;
c->available_width = width;
if (handler_map[c->type].convert) {
if (!handler_map[c->type].convert(c, width, height)) {
c->status = CONTENT_STATUS_ERROR;
c->locked = false;
return;
}
} else {
c->status = CONTENT_STATUS_DONE;
}
c->locked = false;
assert(c->status == CONTENT_STATUS_READY ||
c->status == CONTENT_STATUS_DONE);
content_broadcast(c, CONTENT_MSG_READY, msg_data);
if (c->status == CONTENT_STATUS_DONE)
content_set_done(c);
}
/**
* Put a content in status CONTENT_STATUS_DONE.
*/
void content_set_done(struct content *c)
{
union content_msg_data msg_data;
c->status = CONTENT_STATUS_DONE;
c->time = wallclock() - c->time;
content_update_status(c);
content_broadcast(c, CONTENT_MSG_DONE, msg_data);
}
/**
* Reformat to new size.
*
* Calls the reformat function for the content.
*/
void content_reformat(struct content *c, int width, int height)
{
union content_msg_data data;
assert(c != 0);
assert(c->status == CONTENT_STATUS_READY ||
c->status == CONTENT_STATUS_DONE);
assert(!c->locked);
LOG(("%p %s", c, c->url));
c->locked = true;
c->available_width = width;
if (handler_map[c->type].reformat) {
handler_map[c->type].reformat(c, width, height);
content_broadcast(c, CONTENT_MSG_REFORMAT, data);
}
c->locked = false;
}
/**
* Clean unused contents from the content_list.
*
* Destroys any contents in the content_list with no users or in
* CONTENT_STATUS_ERROR. Fresh contents in CONTENT_STATUS_DONE may be kept even
* with no users.
*
* Each content is also checked for stop requests.
*/
void content_clean(void)
{
unsigned int size;
struct content *c, *next, *prev;
/* destroy unused stale contents and contents with errors */
for (c = content_list; c; c = next) {
next = c->next;
/* this function must not be called from a content function */
assert(!c->locked);
if (c->user_list->next && c->status != CONTENT_STATUS_ERROR)
/* content has users */
continue;
if (c->fresh && c->status == CONTENT_STATUS_DONE)
/* content is fresh */
continue;
/* content can be destroyed */
content_destroy(c);
}
/* check for pending stops */
for (c = content_list; c; c = c->next) {
if (c->status == CONTENT_STATUS_READY)
content_stop_check(c);
}
/* attempt to shrink the memory cache (unused fresh contents) */
size = 0;
next = 0;
for (c = content_list; c; c = c->next) {
next = c;
c->talloc_size = talloc_total_size(c);
size += c->size + c->talloc_size;
}
for (c = next; c && (unsigned int) option_memory_cache_size < size;
c = prev) {
prev = c->prev;
if (c->user_list->next)
continue;
size -= c->size + c->talloc_size;
content_destroy(c);
}
}
/**
* Destroy and free a content.
*
* Calls the destroy function for the content, and frees the structure.
*/
void content_destroy(struct content *c)
{
assert(c);
LOG(("content %p %s", c, c->url));
assert(!c->locked);
if (c->fetch)
fetch_abort(c->fetch);
if (c->next)
c->next->prev = c->prev;
if (c->prev)
c->prev->next = c->next;
else
content_list = c->next;
if (c->type < HANDLER_MAP_COUNT && handler_map[c->type].destroy)
handler_map[c->type].destroy(c);
talloc_free(c);
}
/**
* Reset a content.
*
* Calls the destroy function for the content, but does not free
* the structure.
*/
void content_reset(struct content *c)
{
assert(c != 0);
LOG(("content %p %s", c, c->url));
assert(!c->locked);
if (c->type < HANDLER_MAP_COUNT && handler_map[c->type].destroy)
handler_map[c->type].destroy(c);
c->type = CONTENT_UNKNOWN;
c->status = CONTENT_STATUS_TYPE_UNKNOWN;
c->size = 0;
talloc_free(c->mime_type);
c->mime_type = 0;
talloc_free(c->refresh);
c->refresh = 0;
talloc_free(c->title);
c->title = 0;
talloc_free(c->source_data);
c->source_data = 0;
c->source_size = c->source_allocated = 0;
}
/**
* Free all contents in the content_list.
*/
void content_quit(void)
{
bool progress = true;
struct content *c, *next;
while (content_list && progress) {
progress = false;
for (c = content_list; c; c = next) {
assert(c->next != c);
next = c->next;
if (c->user_list->next &&
c->status != CONTENT_STATUS_ERROR)
/* content has users */
continue;
/* content can be destroyed */
content_destroy(c);
progress = true;
}
}
if (content_list) {
LOG(("bug: some contents could not be destroyed"));
}
}
/**
* Display content on screen.
*
* Calls the redraw function for the content, if it exists.
*
* \param c content
* \param x coordinate for top-left of redraw
* \param y coordinate for top-left of redraw
* \param width available width (not used for HTML redraw)
* \param height available height (not used for HTML redraw)
* \param clip_x0 clip rectangle left
* \param clip_y0 clip rectangle top
* \param clip_x1 clip rectangle right
* \param clip_y1 clip rectangle bottom
* \param scale scale for redraw
* \param background_colour the background colour
* \return true if successful, false otherwise
*
* x, y and clip_* are coordinates from the top left of the canvas area.
*
* The top left corner of the clip rectangle is (clip_x0, clip_y0) and
* the bottom right corner of the clip rectangle is (clip_x1, clip_y1).
* Units for x, y and clip_* are pixels.
*/
bool content_redraw(struct content *c, int x, int y,
int width, int height,
int clip_x0, int clip_y0, int clip_x1, int clip_y1,
float scale, colour background_colour)
{
assert(c != 0);
// LOG(("%p %s", c, c->url));
if (c->locked)
/* not safe to attempt redraw */
return true;
if (handler_map[c->type].redraw)
return handler_map[c->type].redraw(c, x, y, width, height,
clip_x0, clip_y0, clip_x1, clip_y1, scale,
background_colour);
return true;
}
/**
* Display content on screen with optional tiling.
*
* Calls the redraw_tile function for the content, or emulates it with the
* redraw function if it doesn't exist.
*/
bool content_redraw_tiled(struct content *c, int x, int y,
int width, int height,
int clip_x0, int clip_y0, int clip_x1, int clip_y1,
float scale, colour background_colour,
bool repeat_x, bool repeat_y)
{
int x0, y0, x1, y1;
assert(c != 0);
// LOG(("%p %s", c, c->url));
if (c->locked)
/* not safe to attempt redraw */
return true;
if (handler_map[c->type].redraw_tiled) {
return handler_map[c->type].redraw_tiled(c, x, y, width, height,
clip_x0, clip_y0, clip_x1, clip_y1, scale,
background_colour, repeat_x, repeat_y);
} else {
/* ensure we have a redrawable content */
if ((!handler_map[c->type].redraw) || (width == 0) ||
(height == 0))
return true;
/* simple optimisation for no repeat (common for backgrounds) */
if ((!repeat_x) && (!repeat_y))
return handler_map[c->type].redraw(c, x, y, width,
height, clip_x0, clip_y0, clip_x1, clip_y1,
scale, background_colour);
/* find the redraw boundaries to loop within*/
x0 = x;
if (repeat_x) {
for (; x0 > clip_x0; x0 -= width);
x1 = clip_x1;
} else {
x1 = x + 1;
}
y0 = y;
if (repeat_y) {
for (; y0 > clip_y0; y0 -= height);
y1 = clip_y1;
} else {
y1 = y + 1;
}
/* repeatedly plot our content */
for (y = y0; y < y1; y += height)
for (x = x0; x < x1; x += width)
if (!handler_map[c->type].redraw(c, x, y,
width, height,
clip_x0, clip_y0,
clip_x1, clip_y1,
scale, background_colour))
return false;
}
return true;
}
/**
* Register a user for callbacks.
*
* \param c the content to register
* \param callback the callback function
* \param p1, p2 callback private data
* \return true on success, false otherwise on memory exhaustion
*
* The callback will be called with p1 and p2 when content_broadcast() is
* called with the content.
*/
bool content_add_user(struct content *c,
void (*callback)(content_msg msg, struct content *c,
intptr_t p1, intptr_t p2, union content_msg_data data),
intptr_t p1, intptr_t p2)
{
struct content_user *user;
LOG(("content %s (%p), user %p 0x%" PRIxPTR " 0x%" PRIxPTR,
c->url, c, callback, p1, p2));
user = talloc(c, struct content_user);
if (!user)
return false;
user->callback = callback;
user->p1 = p1;
user->p2 = p2;
user->stop = false;
user->next = c->user_list->next;
c->user_list->next = user;
return true;
}
/**
* Search the users of a content for the specified user.
*
* \return a content_user struct for the user, or 0 if not found
*/
struct content_user * content_find_user(struct content *c,
void (*callback)(content_msg msg, struct content *c,
intptr_t p1, intptr_t p2, union content_msg_data data),
intptr_t p1, intptr_t p2)
{
struct content_user *user;
/* user_list starts with a sentinel */
for (user = c->user_list; user->next &&
!(user->next->callback == callback &&
user->next->p1 == p1 &&
user->next->p2 == p2); user = user->next)
;
return user->next;
}
/**
* Remove a callback user.
*
* The callback function, p1, and p2 must be identical to those passed to
* content_add_user().
*/
void content_remove_user(struct content *c,
void (*callback)(content_msg msg, struct content *c,
intptr_t p1, intptr_t p2, union content_msg_data data),
intptr_t p1, intptr_t p2)
{
struct content_user *user, *next;
LOG(("content %s (%p), user %p 0x%" PRIxPTR " 0x%" PRIxPTR,
c->url, c, callback, p1, p2));
/* user_list starts with a sentinel */
for (user = c->user_list; user->next != 0 &&
!(user->next->callback == callback &&
user->next->p1 == p1 &&
user->next->p2 == p2); user = user->next)
;
if (user->next == 0) {
LOG(("user not found in list"));
assert(0);
return;
}
next = user->next;
user->next = next->next;
talloc_free(next);
}
/**
* Send a message to all users.
*/
void content_broadcast(struct content *c, content_msg msg,
union content_msg_data data)
{
struct content_user *user, *next;
assert(c);
// LOG(("%p %s -> %d", c, c->url, msg));
for (user = c->user_list->next; user != 0; user = next) {
next = user->next; /* user may be destroyed during callback */
if (user->callback != 0)
user->callback(msg, c, user->p1, user->p2, data);
}
}
/**
* Stop a content loading.
*
* May only be called in CONTENT_STATUS_READY only. If all users have requested
* stop, the loading is stopped and the content placed in CONTENT_STATUS_DONE.
*/
void content_stop(struct content *c,
void (*callback)(content_msg msg, struct content *c,
intptr_t p1, intptr_t p2, union content_msg_data data),
intptr_t p1, intptr_t p2)
{
struct content_user *user;
assert(c->status == CONTENT_STATUS_READY);
user = content_find_user(c, callback, p1, p2);
if (!user) {
LOG(("user not found in list"));
assert(0);
return;
}
LOG(("%p %s: stop user %p 0x%" PRIxPTR " 0x%" PRIxPTR,
c, c->url, callback, p1, p2));
user->stop = true;
}
/**
* Check if all users have requested a stop, and do it if so.
*/
void content_stop_check(struct content *c)
{
struct content_user *user;
union content_msg_data data;
assert(c->status == CONTENT_STATUS_READY);
/* user_list starts with a sentinel */
for (user = c->user_list->next; user; user = user->next)
if (!user->stop)
return;
LOG(("%p %s", c, c->url));
/* all users have requested stop */
assert(handler_map[c->type].stop);
handler_map[c->type].stop(c);
assert(c->status == CONTENT_STATUS_DONE);
content_set_status(c, messages_get("Stopped"));
content_broadcast(c, CONTENT_MSG_DONE, data);
}
/**
* A window containing the content has been opened.
*
* \param c content that has been opened
* \param bw browser window containing the content
* \param page content of type CONTENT_HTML containing c, or 0 if not an
* object within a page
* \param index index in page->data.html.object, or 0 if not an object
* \param box box containing c, or 0 if not an object
* \param params object parameters, or 0 if not an object
*
* Calls the open function for the content.
*/
void content_open(struct content *c, struct browser_window *bw,
struct content *page, unsigned int index, struct box *box,
struct object_params *params)
{
assert(c != 0);
assert(c->type < CONTENT_UNKNOWN);
LOG(("content %p %s", c, c->url));
if (handler_map[c->type].open)
handler_map[c->type].open(c, bw, page, index, box, params);
}
/**
* The window containing the content has been closed.
*
* Calls the close function for the content.
*/
void content_close(struct content *c)
{
assert(c != 0);
assert(c->type < CONTENT_UNKNOWN);
LOG(("content %p %s", c, c->url));
if (handler_map[c->type].close)
handler_map[c->type].close(c);
}
void content_add_error(struct content *c, const char *token,
unsigned int line)
{
}