mirror of
https://github.com/netsurf-browser/netsurf
synced 2024-11-24 15:29:45 +03:00
0f228ada91
Implement HTTP caching algorithm; this should avoid stale cache entries being used. svn path=/import/netsurf/; revision=2059
1040 lines
25 KiB
C
1040 lines
25 KiB
C
/*
|
|
* This file is part of NetSurf, http://netsurf.sourceforge.net/
|
|
* Licensed under the GNU General Public License,
|
|
* http://www.opensource.org/licenses/gpl-license
|
|
* Copyright 2005 James Bursa <bursa@users.sourceforge.net>
|
|
*/
|
|
|
|
/** \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 "netsurf/utils/config.h"
|
|
#include "netsurf/content/content.h"
|
|
#include "netsurf/content/fetch.h"
|
|
#include "netsurf/content/fetchcache.h"
|
|
#include "netsurf/css/css.h"
|
|
#include "netsurf/image/bitmap.h"
|
|
#include "netsurf/desktop/options.h"
|
|
#include "netsurf/render/html.h"
|
|
#include "netsurf/render/textplain.h"
|
|
#ifdef WITH_JPEG
|
|
#include "netsurf/image/jpeg.h"
|
|
#endif
|
|
#ifdef WITH_MNG
|
|
#include "netsurf/image/mng.h"
|
|
#endif
|
|
#ifdef WITH_GIF
|
|
#include "netsurf/image/gif.h"
|
|
#endif
|
|
#ifdef WITH_SPRITE
|
|
#include "netsurf/riscos/sprite.h"
|
|
#endif
|
|
#ifdef WITH_DRAW
|
|
#include "netsurf/riscos/draw.h"
|
|
#endif
|
|
#ifdef WITH_PLUGIN
|
|
#include "netsurf/riscos/plugin.h"
|
|
#endif
|
|
#ifdef WITH_ARTWORKS
|
|
#include "netsurf/riscos/artworks.h"
|
|
#endif
|
|
#include "netsurf/utils/log.h"
|
|
#include "netsurf/utils/messages.h"
|
|
#include "netsurf/utils/talloc.h"
|
|
#include "netsurf/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_ARTWORKS
|
|
{"application/artworks", CONTENT_ARTWORKS},
|
|
#endif
|
|
#ifdef WITH_DRAW
|
|
{"application/drawfile", CONTENT_DRAW},
|
|
{"application/x-drawfile", CONTENT_DRAW},
|
|
#endif
|
|
#ifdef WITH_THEME_INSTALL
|
|
{"application/x-netsurf-theme", CONTENT_THEME},
|
|
#endif
|
|
{"application/xhtml+xml", CONTENT_HTML},
|
|
#ifdef WITH_ARTWORKS
|
|
{"image/artworks", CONTENT_ARTWORKS},
|
|
#endif
|
|
#ifdef WITH_DRAW
|
|
{"image/drawfile", CONTENT_DRAW},
|
|
#endif
|
|
#ifdef WITH_GIF
|
|
{"image/gif", CONTENT_GIF},
|
|
#endif
|
|
#ifdef WITH_MNG
|
|
{"image/jng", CONTENT_JNG},
|
|
#endif
|
|
#ifdef WITH_JPEG
|
|
{"image/jpeg", CONTENT_JPEG},
|
|
#endif
|
|
#ifdef WITH_MNG
|
|
{"image/mng", CONTENT_MNG},
|
|
#endif
|
|
#ifdef WITH_JPEG
|
|
{"image/pjpeg", CONTENT_JPEG},
|
|
#endif
|
|
#ifdef WITH_MNG
|
|
{"image/png", CONTENT_PNG},
|
|
#endif
|
|
#ifdef WITH_DRAW
|
|
{"image/x-drawfile", CONTENT_DRAW},
|
|
#endif
|
|
#ifdef WITH_MNG
|
|
{"image/x-jng", CONTENT_JNG},
|
|
{"image/x-mng", CONTENT_MNG},
|
|
#endif
|
|
#ifdef WITH_SPRITE
|
|
{"image/x-riscos-sprite", CONTENT_SPRITE},
|
|
#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 *content_type_name[] = {
|
|
"HTML",
|
|
"TEXTPLAIN",
|
|
"CSS",
|
|
#ifdef WITH_JPEG
|
|
"JPEG",
|
|
#endif
|
|
#ifdef WITH_GIF
|
|
"GIF",
|
|
#endif
|
|
#ifdef WITH_MNG
|
|
"PNG",
|
|
"JNG",
|
|
"MNG",
|
|
#endif
|
|
#ifdef WITH_SPRITE
|
|
"SPRITE",
|
|
#endif
|
|
#ifdef WITH_DRAW
|
|
"DRAW",
|
|
#endif
|
|
#ifdef WITH_PLUGIN
|
|
"PLUGIN",
|
|
#endif
|
|
#ifdef WITH_THEME_INSTALL
|
|
"THEME",
|
|
#endif
|
|
#ifdef WITH_ARTWORKS
|
|
"ARTWORKS",
|
|
#endif
|
|
"OTHER",
|
|
"UNKNOWN"
|
|
};
|
|
|
|
const char *content_status_name[] = {
|
|
"TYPE_UNKNOWN",
|
|
"LOADING",
|
|
"READY",
|
|
"DONE",
|
|
"ERROR"
|
|
};
|
|
|
|
/** An entry in handler_map. */
|
|
struct handler_entry {
|
|
bool (*create)(struct content *c, 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, unsigned long background_colour);
|
|
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,
|
|
html_open, html_close,
|
|
true},
|
|
{textplain_create, textplain_process_data, textplain_convert,
|
|
0, 0, 0, 0, 0, 0, true},
|
|
{0, 0, css_convert, 0, css_destroy, 0, 0, 0, 0, false},
|
|
#ifdef WITH_JPEG
|
|
{0, 0, nsjpeg_convert,
|
|
0, nsjpeg_destroy, 0, nsjpeg_redraw, 0, 0, false},
|
|
#endif
|
|
#ifdef WITH_GIF
|
|
{nsgif_create, 0, nsgif_convert,
|
|
0, nsgif_destroy, 0, nsgif_redraw, 0, 0, false},
|
|
#endif
|
|
#ifdef WITH_MNG
|
|
{nsmng_create, nsmng_process_data, nsmng_convert,
|
|
0, nsmng_destroy, 0, nsmng_redraw, 0, 0, false},
|
|
{nsmng_create, nsmng_process_data, nsmng_convert,
|
|
0, nsmng_destroy, 0, nsmng_redraw, 0, 0, false},
|
|
{nsmng_create, nsmng_process_data, nsmng_convert,
|
|
0, nsmng_destroy, 0, nsmng_redraw, 0, 0, false},
|
|
#endif
|
|
#ifdef WITH_SPRITE
|
|
{0, 0, sprite_convert,
|
|
0, sprite_destroy, 0, sprite_redraw, 0, 0, false},
|
|
#endif
|
|
#ifdef WITH_DRAW
|
|
{0, 0, draw_convert,
|
|
0, draw_destroy, 0, draw_redraw, 0, 0, false},
|
|
#endif
|
|
#ifdef WITH_PLUGIN
|
|
{plugin_create, 0, plugin_convert,
|
|
plugin_reformat, plugin_destroy, 0, plugin_redraw,
|
|
plugin_open, plugin_close,
|
|
true},
|
|
#endif
|
|
#ifdef WITH_THEME_INSTALL
|
|
{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, false},
|
|
#endif
|
|
{0, 0, 0, 0, 0, 0, 0, 0, 0, false}
|
|
};
|
|
#define HANDLER_MAP_COUNT (sizeof(handler_map) / sizeof(handler_map[0]))
|
|
|
|
|
|
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;
|
|
LOG(("url %s", url));
|
|
c = talloc(0, struct content);
|
|
if (!c)
|
|
return 0;
|
|
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;
|
|
}
|
|
c->cache_data = talloc(c, struct cache_data);
|
|
if (!c->cache_data) {
|
|
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->size = sizeof(struct content);
|
|
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;
|
|
content_set_status(c, messages_get("Loading"));
|
|
c->locked = false;
|
|
c->fetch = 0;
|
|
c->source_data = 0;
|
|
c->source_size = 0;
|
|
c->source_allocated = 0;
|
|
c->total_size = 0;
|
|
c->no_error_pages = false;
|
|
c->download = false;
|
|
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->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;
|
|
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;
|
|
}
|
|
|
|
|
|
/**
|
|
* 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[])
|
|
{
|
|
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, type %i", c->url, 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);
|
|
warn_user("NoMemory", 0);
|
|
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 */
|
|
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);
|
|
warn_user("NoMemory", 0);
|
|
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);
|
|
warn_user("NoMemory", 0);
|
|
return false;
|
|
}
|
|
content_remove_user(c, callback, p1, p2);
|
|
content_broadcast(clone, CONTENT_MSG_NEWPTR, msg_data);
|
|
fetchcache_go(clone, 0, callback, p1, p2,
|
|
clone->width, clone->height,
|
|
0, 0, false);
|
|
}
|
|
}
|
|
|
|
if (handler_map[type].create) {
|
|
if (!handler_map[type].create(c, 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->status_message, sizeof (c->status_message),
|
|
status_message, ap)) < 0 ||
|
|
(int)sizeof (c->status_message) <= len)
|
|
c->status_message[sizeof (c->status_message) - 1] = '\0';
|
|
va_end(ap);
|
|
}
|
|
|
|
|
|
/**
|
|
* 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);
|
|
warn_user("NoMemory", 0);
|
|
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;
|
|
c->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", c->url));
|
|
|
|
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;
|
|
|
|
c->size = talloc_total_size(c);
|
|
|
|
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_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);
|
|
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;
|
|
size += c->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;
|
|
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 = sizeof(struct content);
|
|
talloc_free(c->mime_type);
|
|
c->mime_type = 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) {
|
|
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.
|
|
*/
|
|
|
|
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, unsigned long background_colour)
|
|
{
|
|
assert(c != 0);
|
|
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;
|
|
}
|
|
|
|
|
|
/**
|
|
* 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, user %p 0x%" PRIxPTR " 0x%" PRIxPTR,
|
|
c->url, 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, user %p 0x%" PRIxPTR " 0x%" PRIxPTR,
|
|
c->url, 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);
|
|
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 %s", 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 %s", 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)
|
|
{
|
|
}
|