mirror of
https://github.com/netsurf-browser/netsurf
synced 2024-11-28 17:23:09 +03:00
78d194cb77
Redirect handling is now transparent to clients of fetchcache. The new scheme works as follows: 1) Request content for URL (fetchcache() 2) Start fetch of content (fetchcache_go() 3) If no redirect, continue through LOADING, READY, DONE etc. states as before If redirect, receive NEWPTR for each redirect that occurs, then continue through LOADING, READY, DONE etc. states as before. The upshot of this is that redirects result in extra contents being created. It also means that, until LOADING has been received, the content (and thus the URL being fetched) may change. Therefore, fetchcache clients should expect to have to deal with transient data prior to LOADING occurring. As a necessary side-effect of this, the HTML object URLs and CSS @import URLs are no longer stored alongside the relevant contents. These URLs can be accessed by interrogating the url member of struct content anyway, so it was a rather redundant scheme before. svn path=/trunk/netsurf/; revision=3787
1269 lines
30 KiB
C
1269 lines
30 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_DRAW
|
|
#include "riscos/draw.h"
|
|
#endif
|
|
#ifdef WITH_PLUGIN
|
|
#include "riscos/plugin.h"
|
|
#endif
|
|
#ifdef WITH_ARTWORKS
|
|
#include "riscos/artworks.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},
|
|
#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
|
|
#ifdef WITH_MNG
|
|
{"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
|
|
#ifdef WITH_SPRITE
|
|
{"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 *content_type_name[] = {
|
|
"HTML",
|
|
"TEXTPLAIN",
|
|
"CSS",
|
|
#ifdef WITH_JPEG
|
|
"JPEG",
|
|
#endif
|
|
#ifdef WITH_GIF
|
|
"GIF",
|
|
#endif
|
|
#ifdef WITH_BMP
|
|
"BMP",
|
|
"ICO",
|
|
#endif
|
|
#ifdef WITH_MNG
|
|
"PNG",
|
|
"JNG",
|
|
"MNG",
|
|
#endif
|
|
#ifdef WITH_SPRITE
|
|
"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 *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);
|
|
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, unsigned long 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},
|
|
{0, 0, css_convert, 0, css_destroy, 0, 0, 0, 0, 0, false},
|
|
#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_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},
|
|
{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_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, false},
|
|
#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;
|
|
}
|
|
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->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->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;
|
|
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[])
|
|
{
|
|
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;
|
|
|
|
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);
|
|
content_broadcast(clone, CONTENT_MSG_NEWPTR, msg_data);
|
|
fetchcache_go(clone, referer,
|
|
callback, p1, p2,
|
|
clone->width, clone->height,
|
|
0, 0, false,
|
|
referer ? referer : c->url);
|
|
}
|
|
}
|
|
|
|
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->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;
|
|
size += c->size + talloc_total_size(c);
|
|
}
|
|
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 + talloc_total_size(c);
|
|
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;
|
|
}
|
|
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
|
|
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);
|
|
// 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, unsigned long 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)
|
|
{
|
|
}
|