netsurf/content/content.c
Michael Drake 07242c7415 Content: Make the content structure aware of viewport height.
It was always aware of viewport width, but since adding support for
vh CSS units, the HTML content handler also needs viewport height.

Signed-off-by: Michael Drake <michael.drake@codethink.co.uk>
2019-02-17 09:06:58 +00:00

1495 lines
34 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.
*/
#include <stdint.h>
#include <stdlib.h>
#include <nsutils/time.h>
#include "netsurf/inttypes.h"
#include "utils/log.h"
#include "utils/messages.h"
#include "netsurf/browser_window.h"
#include "netsurf/bitmap.h"
#include "netsurf/content.h"
#include "desktop/knockout.h"
#include "desktop/gui_internal.h"
#include "content/content_protected.h"
#include "content/content_debug.h"
#include "content/hlcache.h"
#define URL_FMT_SPC "%.140s"
const char * const content_status_name[] = {
"LOADING",
"READY",
"DONE",
"ERROR"
};
static nserror content_llcache_callback(llcache_handle *llcache,
const llcache_event *event, void *pw);
static void content_convert(struct content *c);
/**
* Initialise a new content structure.
*
* \param c Content to initialise
* \param handler Content handler
* \param imime_type MIME type of content
* \param params HTTP parameters
* \param llcache Source data handle
* \param fallback_charset Fallback charset
* \param quirks Quirkiness of content
* \return NSERROR_OK on success, appropriate error otherwise
*/
nserror content__init(struct content *c, const content_handler *handler,
lwc_string *imime_type, const struct http_parameter *params,
llcache_handle *llcache, const char *fallback_charset,
bool quirks)
{
struct content_user *user_sentinel;
nserror error;
NSLOG(netsurf, INFO, "url "URL_FMT_SPC" -> %p",
nsurl_access_log(llcache_handle_get_url(llcache)), c);
user_sentinel = calloc(1, sizeof(struct content_user));
if (user_sentinel == NULL) {
return NSERROR_NOMEM;
}
if (fallback_charset != NULL) {
c->fallback_charset = strdup(fallback_charset);
if (c->fallback_charset == NULL) {
free(user_sentinel);
return NSERROR_NOMEM;
}
}
c->llcache = llcache;
c->mime_type = lwc_string_ref(imime_type);
c->handler = handler;
c->status = CONTENT_STATUS_LOADING;
c->width = 0;
c->height = 0;
c->available_width = 0;
c->available_height = 0;
c->quirks = quirks;
c->refresh = 0;
nsu_getmonotonic_ms(&c->time);
c->size = 0;
c->title = NULL;
c->active = 0;
user_sentinel->callback = NULL;
user_sentinel->pw = NULL;
user_sentinel->next = NULL;
c->user_list = user_sentinel;
c->sub_status[0] = 0;
c->locked = false;
c->total_size = 0;
c->http_code = 0;
c->error_count = 0;
content_set_status(c, messages_get("Loading"));
/* Finally, claim low-level cache events */
error = llcache_handle_change_callback(llcache,
content_llcache_callback, c);
if (error != NSERROR_OK) {
lwc_string_unref(c->mime_type);
return error;
}
return NSERROR_OK;
}
/**
* Handler for low-level cache events
*
* \param llcache Low-level cache handle
* \param event Event details
* \param pw Pointer to our context
* \return NSERROR_OK on success, appropriate error otherwise
*/
nserror content_llcache_callback(llcache_handle *llcache,
const llcache_event *event, void *pw)
{
struct content *c = pw;
union content_msg_data msg_data;
nserror error = NSERROR_OK;
switch (event->type) {
case LLCACHE_EVENT_HAD_HEADERS:
/* Will never happen: handled in hlcache */
break;
case LLCACHE_EVENT_HAD_DATA:
if (c->handler->process_data != NULL) {
if (c->handler->process_data(c,
(const char *) event->data.data.buf,
event->data.data.len) == false) {
llcache_handle_abort(c->llcache);
c->status = CONTENT_STATUS_ERROR;
/** \todo It's not clear what error this is */
error = NSERROR_NOMEM;
}
}
break;
case LLCACHE_EVENT_DONE:
{
size_t source_size;
(void) llcache_handle_get_source_data(llcache, &source_size);
content_set_status(c, messages_get("Processing"));
msg_data.explicit_status_text = NULL;
content_broadcast(c, CONTENT_MSG_STATUS, &msg_data);
content_convert(c);
}
break;
case LLCACHE_EVENT_ERROR:
/** \todo Error page? */
c->status = CONTENT_STATUS_ERROR;
msg_data.error = event->data.msg;
content_broadcast(c, CONTENT_MSG_ERROR, &msg_data);
break;
case LLCACHE_EVENT_PROGRESS:
content_set_status(c, event->data.msg);
msg_data.explicit_status_text = NULL;
content_broadcast(c, CONTENT_MSG_STATUS, &msg_data);
break;
case LLCACHE_EVENT_REDIRECT:
msg_data.redirect.from = event->data.redirect.from;
msg_data.redirect.to = event->data.redirect.to;
content_broadcast(c, CONTENT_MSG_REDIRECT, &msg_data);
break;
}
return error;
}
/**
* Get whether a content can reformat
*
* \param h content to check
* \return whether the content can reformat
*/
bool content_can_reformat(hlcache_handle *h)
{
struct content *c = hlcache_handle_get_content(h);
if (c == NULL)
return false;
return (c->handler->reformat != NULL);
}
static void content_update_status(struct content *c)
{
if (c->status == CONTENT_STATUS_LOADING ||
c->status == CONTENT_STATUS_READY) {
/* Not done yet */
snprintf(c->status_message, sizeof (c->status_message),
"%s%s%s", messages_get("Fetching"),
c->sub_status[0] != '\0' ? ", " : " ",
c->sub_status);
} else {
snprintf(c->status_message, sizeof (c->status_message),
"%s (%.1fs)", messages_get("Done"),
(float) c->time / 1000);
}
}
/**
* Updates content with new status.
*
* The textual status contained in the content is updated with given string.
*
* \param c The content to set status in.
* \param status_message new textual status
*/
void content_set_status(struct content *c, const char *status_message)
{
size_t len = strlen(status_message);
if (len >= sizeof(c->sub_status)) {
len = sizeof(c->sub_status) - 1;
}
memcpy(c->sub_status, status_message, len);
c->sub_status[len] = '\0';
content_update_status(c);
}
/**
* 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)
{
assert(c);
assert(c->status == CONTENT_STATUS_LOADING ||
c->status == CONTENT_STATUS_ERROR);
if (c->status != CONTENT_STATUS_LOADING)
return;
if (c->locked == true)
return;
NSLOG(netsurf, INFO, "content "URL_FMT_SPC" (%p)",
nsurl_access_log(llcache_handle_get_url(c->llcache)), c);
if (c->handler->data_complete != NULL) {
c->locked = true;
if (c->handler->data_complete(c) == false) {
content_set_error(c);
}
/* Conversion to the READY state will unlock the content */
} else {
content_set_ready(c);
content_set_done(c);
}
}
/**
* Put a content in status CONTENT_STATUS_READY and unlock the content.
*/
void content_set_ready(struct content *c)
{
/* The content must be locked at this point, as it can only
* become READY after conversion. */
assert(c->locked);
c->locked = false;
c->status = CONTENT_STATUS_READY;
content_update_status(c);
content_broadcast(c, CONTENT_MSG_READY, NULL);
}
/**
* Put a content in status CONTENT_STATUS_DONE.
*/
void content_set_done(struct content *c)
{
uint64_t now_ms;
nsu_getmonotonic_ms(&now_ms);
c->status = CONTENT_STATUS_DONE;
c->time = now_ms - c->time;
content_update_status(c);
content_broadcast(c, CONTENT_MSG_DONE, NULL);
}
/**
* Put a content in status CONTENT_STATUS_ERROR and unlock the content.
*
* \note We expect the caller to broadcast an error report if needed.
*/
void content_set_error(struct content *c)
{
c->locked = false;
c->status = CONTENT_STATUS_ERROR;
}
/**
* Reformat to new size.
*
* Calls the reformat function for the content.
*/
void content_reformat(hlcache_handle *h, bool background,
int width, int height)
{
content__reformat(hlcache_handle_get_content(h), background,
width, height);
}
void content__reformat(struct content *c, bool background,
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 == false);
c->available_width = width;
c->available_height = height;
if (c->handler->reformat != NULL) {
c->locked = true;
c->handler->reformat(c, width, height);
c->locked = false;
data.background = background;
content_broadcast(c, CONTENT_MSG_REFORMAT, &data);
}
}
/**
* Destroy and free a content.
*
* Calls the destroy function for the content, and frees the structure.
*/
void content_destroy(struct content *c)
{
struct content_rfc5988_link *link;
assert(c);
NSLOG(netsurf, INFO, "content %p %s", c,
nsurl_access_log(llcache_handle_get_url(c->llcache)));
assert(c->locked == false);
if (c->handler->destroy != NULL)
c->handler->destroy(c);
llcache_handle_release(c->llcache);
c->llcache = NULL;
lwc_string_unref(c->mime_type);
/* release metadata links */
link = c->links;
while (link != NULL) {
link = content__free_rfc5988_link(link);
}
/* free the user list */
if (c->user_list != NULL) {
free(c->user_list);
}
/* free the title */
if (c->title != NULL) {
free(c->title);
}
/* free the fallback characterset */
if (c->fallback_charset != NULL) {
free(c->fallback_charset);
}
free(c);
}
/**
* Handle mouse movements in a content window.
*
* \param h Content handle
* \param bw browser window
* \param mouse state of mouse buttons and modifier keys
* \param x coordinate of mouse
* \param y coordinate of mouse
*/
void content_mouse_track(hlcache_handle *h, struct browser_window *bw,
browser_mouse_state mouse, int x, int y)
{
struct content *c = hlcache_handle_get_content(h);
assert(c != NULL);
if (c->handler->mouse_track != NULL) {
c->handler->mouse_track(c, bw, mouse, x, y);
} else {
union content_msg_data msg_data;
msg_data.pointer = BROWSER_POINTER_AUTO;
content_broadcast(c, CONTENT_MSG_POINTER, &msg_data);
}
return;
}
/**
* Handle mouse clicks and movements in a content window.
*
* \param h Content handle
* \param bw browser window
* \param mouse state of mouse buttons and modifier keys
* \param x coordinate of mouse
* \param y coordinate of mouse
*
* This function handles both hovering and clicking. It is important that the
* code path is identical (except that hovering doesn't carry out the action),
* so that the status bar reflects exactly what will happen. Having separate
* code paths opens the possibility that an attacker will make the status bar
* show some harmless action where clicking will be harmful.
*/
void content_mouse_action(hlcache_handle *h, struct browser_window *bw,
browser_mouse_state mouse, int x, int y)
{
struct content *c = hlcache_handle_get_content(h);
assert(c != NULL);
if (c->handler->mouse_action != NULL)
c->handler->mouse_action(c, bw, mouse, x, y);
return;
}
/**
* Handle keypresses.
*
* \param h Content handle
* \param key The UCS4 character codepoint
* \return true if key handled, false otherwise
*/
bool content_keypress(struct hlcache_handle *h, uint32_t key)
{
struct content *c = hlcache_handle_get_content(h);
assert(c != NULL);
if (c->handler->keypress != NULL)
return c->handler->keypress(c, key);
return false;
}
/**
* Request a redraw of an area of a content
*
* \param h high-level cache handle
* \param x x co-ord of left edge
* \param y y co-ord of top edge
* \param width Width of rectangle
* \param height Height of rectangle
*/
void content_request_redraw(struct hlcache_handle *h,
int x, int y, int width, int height)
{
content__request_redraw(hlcache_handle_get_content(h),
x, y, width, height);
}
/**
* Request a redraw of an area of a content
*
* \param c Content
* \param x x co-ord of left edge
* \param y y co-ord of top edge
* \param width Width of rectangle
* \param height Height of rectangle
*/
void content__request_redraw(struct content *c,
int x, int y, int width, int height)
{
union content_msg_data data;
if (c == NULL)
return;
data.redraw.x = x;
data.redraw.y = y;
data.redraw.width = width;
data.redraw.height = height;
data.redraw.full_redraw = true;
data.redraw.object = c;
data.redraw.object_x = 0;
data.redraw.object_y = 0;
data.redraw.object_width = c->width;
data.redraw.object_height = c->height;
content_broadcast(c, CONTENT_MSG_REDRAW, &data);
}
/* exported interface, documented in content/content.h */
bool content_redraw(hlcache_handle *h, struct content_redraw_data *data,
const struct rect *clip, const struct redraw_context *ctx)
{
struct content *c = hlcache_handle_get_content(h);
assert(c != NULL);
if (c->locked) {
/* not safe to attempt redraw */
return true;
}
/* ensure we have a redrawable content */
if (c->handler->redraw == NULL) {
return true;
}
return c->handler->redraw(c, data, clip, ctx);
}
/* exported interface, documented in content/content.h */
bool content_scaled_redraw(struct hlcache_handle *h,
int width, int height, const struct redraw_context *ctx)
{
struct content *c = hlcache_handle_get_content(h);
struct redraw_context new_ctx = *ctx;
struct rect clip;
struct content_redraw_data data;
bool plot_ok = true;
assert(c != NULL);
/* ensure it is safe to attempt redraw */
if (c->locked) {
return true;
}
/* ensure we have a redrawable content */
if (c->handler->redraw == NULL) {
return true;
}
NSLOG(netsurf, INFO, "Content %p %dx%d ctx:%p", c, width, height, ctx);
if (ctx->plot->option_knockout) {
knockout_plot_start(ctx, &new_ctx);
}
/* Set clip rectangle to required thumbnail size */
clip.x0 = 0;
clip.y0 = 0;
clip.x1 = width;
clip.y1 = height;
new_ctx.plot->clip(&new_ctx, &clip);
/* Plot white background */
plot_ok &= (new_ctx.plot->rectangle(&new_ctx,
plot_style_fill_white,
&clip) == NSERROR_OK);
/* Set up content redraw data */
data.x = 0;
data.y = 0;
data.width = width;
data.height = height;
data.background_colour = 0xFFFFFF;
data.repeat_x = false;
data.repeat_y = false;
/* Find the scale factor to use if the content has a width */
if (c->width) {
data.scale = (float)width / (float)c->width;
} else {
data.scale = 1.0;
}
/* Render the content */
plot_ok &= c->handler->redraw(c, &data, &clip, &new_ctx);
if (ctx->plot->option_knockout) {
knockout_plot_end(ctx);
}
return plot_ok;
}
/**
* Register a user for callbacks.
*
* \param c the content to register
* \param callback the callback function
* \param pw callback private data
* \return true on success, false otherwise on memory exhaustion
*
* The callback will be called when content_broadcast() is
* called with the content.
*/
bool content_add_user(
struct content *c,
void (*callback)(
struct content *c,
content_msg msg,
const union content_msg_data *data,
void *pw),
void *pw)
{
struct content_user *user;
NSLOG(netsurf, INFO, "content "URL_FMT_SPC" (%p), user %p %p",
nsurl_access_log(llcache_handle_get_url(c->llcache)),
c, callback, pw);
user = malloc(sizeof(struct content_user));
if (!user)
return false;
user->callback = callback;
user->pw = pw;
user->next = c->user_list->next;
c->user_list->next = user;
if (c->handler->add_user != NULL)
c->handler->add_user(c);
return true;
}
/**
* Remove a callback user.
*
* The callback function and pw must be identical to those passed to
* content_add_user().
*/
void content_remove_user(
struct content *c,
void (*callback)(
struct content *c,
content_msg msg,
const union content_msg_data *data,
void *pw),
void *pw)
{
struct content_user *user, *next;
NSLOG(netsurf, INFO, "content "URL_FMT_SPC" (%p), user %p %p",
nsurl_access_log(llcache_handle_get_url(c->llcache)),
c, callback, pw);
/* user_list starts with a sentinel */
for (user = c->user_list; user->next != 0 &&
!(user->next->callback == callback &&
user->next->pw == pw); user = user->next)
;
if (user->next == 0) {
NSLOG(netsurf, INFO, "user not found in list");
assert(0);
return;
}
if (c->handler->remove_user != NULL)
c->handler->remove_user(c);
next = user->next;
user->next = next->next;
free(next);
}
/**
* Count users for the content.
*/
uint32_t content_count_users(struct content *c)
{
struct content_user *user;
uint32_t counter = 0;
assert(c != NULL);
for (user = c->user_list; user != NULL; user = user->next)
counter += 1;
assert(counter > 0);
return counter - 1; /* Subtract 1 for the sentinel */
}
/**
* Determine if quirks mode matches
*
* \param c Content to consider
* \param quirks Quirks mode to match
* \return True if quirks match, false otherwise
*/
bool content_matches_quirks(struct content *c, bool quirks)
{
if (c->handler->matches_quirks == NULL)
return true;
return c->handler->matches_quirks(c, quirks);
}
/**
* Determine if a content is shareable
*
* \param c Content to consider
* \return True if content is shareable, false otherwise
*/
bool content_is_shareable(struct content *c)
{
return c->handler->no_share == false;
}
/**
* Send a message to all users.
*/
void content_broadcast(struct content *c, content_msg msg,
const union content_msg_data *data)
{
struct content_user *user, *next;
assert(c);
NSLOG(netsurf, DEEPDEBUG, "%p -> msg:%d", c, 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(c, msg, data, user->pw);
}
}
/* exported interface documented in content_protected.h */
void content_broadcast_errorcode(struct content *c, nserror errorcode)
{
struct content_user *user, *next;
union content_msg_data data;
assert(c);
data.errorcode = errorcode;
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(c, CONTENT_MSG_ERRORCODE,
&data, user->pw);
}
}
}
/**
* A window containing the content has been opened.
*
* \param h handle to content that has been opened
* \param bw browser window containing the content
* \param page content of type CONTENT_HTML containing h, or 0 if not an
* object within a page
* \param params object parameters, or 0 if not an object
*
* Calls the open function for the content.
*/
void content_open(hlcache_handle *h, struct browser_window *bw,
struct content *page, struct object_params *params)
{
struct content *c = hlcache_handle_get_content(h);
assert(c != 0);
NSLOG(netsurf, INFO, "content %p %s", c,
nsurl_access_log(llcache_handle_get_url(c->llcache)));
if (c->handler->open != NULL)
c->handler->open(c, bw, page, params);
}
/**
* The window containing the content has been closed.
*
* Calls the close function for the content.
*/
void content_close(hlcache_handle *h)
{
struct content *c = hlcache_handle_get_content(h);
assert(c != 0);
NSLOG(netsurf, INFO, "content %p %s", c,
nsurl_access_log(llcache_handle_get_url(c->llcache)));
if (c->handler->close != NULL)
c->handler->close(c);
}
/**
* Tell a content that any selection it has, or one of its objects has, must be
* cleared.
*/
void content_clear_selection(hlcache_handle *h)
{
struct content *c = hlcache_handle_get_content(h);
assert(c != 0);
if (c->handler->get_selection != NULL)
c->handler->clear_selection(c);
}
/**
* Get a text selection from a content. Ownership is passed to the caller,
* who must free() it.
*/
char * content_get_selection(hlcache_handle *h)
{
struct content *c = hlcache_handle_get_content(h);
assert(c != 0);
if (c->handler->get_selection != NULL)
return c->handler->get_selection(c);
else
return NULL;
}
/* exported interface documented in content/content.h */
nserror content_get_contextual_content(struct hlcache_handle *h,
int x, int y, struct browser_window_features *data)
{
struct content *c = hlcache_handle_get_content(h);
assert(c != 0);
if (c->handler->get_contextual_content != NULL) {
return c->handler->get_contextual_content(c, x, y, data);
}
data->object = h;
return NSERROR_OK;
}
bool content_scroll_at_point(struct hlcache_handle *h,
int x, int y, int scrx, int scry)
{
struct content *c = hlcache_handle_get_content(h);
assert(c != 0);
if (c->handler->scroll_at_point != NULL)
return c->handler->scroll_at_point(c, x, y, scrx, scry);
return false;
}
bool content_drop_file_at_point(struct hlcache_handle *h,
int x, int y, char *file)
{
struct content *c = hlcache_handle_get_content(h);
assert(c != 0);
if (c->handler->drop_file_at_point != NULL)
return c->handler->drop_file_at_point(c, x, y, file);
return false;
}
void content_search(struct hlcache_handle *h, void *context,
search_flags_t flags, const char *string)
{
struct content *c = hlcache_handle_get_content(h);
assert(c != 0);
if (c->handler->search != NULL) {
c->handler->search(c, context, flags, string);
}
}
void content_search_clear(struct hlcache_handle *h)
{
struct content *c = hlcache_handle_get_content(h);
assert(c != 0);
if (c->handler->search_clear != NULL) {
c->handler->search_clear(c);
}
}
/* exported interface documented in content/content.h */
nserror content_debug_dump(struct hlcache_handle *h, FILE *f, enum content_debug op)
{
struct content *c = hlcache_handle_get_content(h);
assert(c != 0);
if (c->handler->debug_dump == NULL) {
return NSERROR_NOT_IMPLEMENTED;
}
return c->handler->debug_dump(c, f, op);
}
/* exported interface documented in content/content.h */
nserror content_debug(struct hlcache_handle *h, enum content_debug op)
{
struct content *c = hlcache_handle_get_content(h);
if (c == NULL) {
return NSERROR_BAD_PARAMETER;
}
if (c->handler->debug == NULL) {
return NSERROR_NOT_IMPLEMENTED;
}
return c->handler->debug(c, op);
}
void content_add_error(struct content *c, const char *token,
unsigned int line)
{
}
/* exported interface documented in content/content.h */
struct content_rfc5988_link *
content_find_rfc5988_link(hlcache_handle *h, lwc_string *rel)
{
struct content *c = hlcache_handle_get_content(h);
struct content_rfc5988_link *link = c->links;
bool rel_match = false;
while (link != NULL) {
if (lwc_string_caseless_isequal(link->rel, rel,
&rel_match) == lwc_error_ok && rel_match) {
break;
}
link = link->next;
}
return link;
}
struct content_rfc5988_link *
content__free_rfc5988_link(struct content_rfc5988_link *link)
{
struct content_rfc5988_link *next;
next = link->next;
lwc_string_unref(link->rel);
nsurl_unref(link->href);
if (link->hreflang != NULL) {
lwc_string_unref(link->hreflang);
}
if (link->type != NULL) {
lwc_string_unref(link->type);
}
if (link->media != NULL) {
lwc_string_unref(link->media);
}
if (link->sizes != NULL) {
lwc_string_unref(link->sizes);
}
free(link);
return next;
}
bool content__add_rfc5988_link(struct content *c,
const struct content_rfc5988_link *link)
{
struct content_rfc5988_link *newlink;
union content_msg_data msg_data;
/* a link relation must be present for it to be a link */
if (link->rel == NULL) {
return false;
}
/* a link href must be present for it to be a link */
if (link->href == NULL) {
return false;
}
newlink = calloc(1, sizeof(struct content_rfc5988_link));
if (newlink == NULL) {
return false;
}
/* copy values */
newlink->rel = lwc_string_ref(link->rel);
newlink->href = nsurl_ref(link->href);
if (link->hreflang != NULL) {
newlink->hreflang = lwc_string_ref(link->hreflang);
}
if (link->type != NULL) {
newlink->type = lwc_string_ref(link->type);
}
if (link->media != NULL) {
newlink->media = lwc_string_ref(link->media);
}
if (link->sizes != NULL) {
newlink->sizes = lwc_string_ref(link->sizes);
}
/* add to metadata link to list */
newlink->next = c->links;
c->links = newlink;
/* broadcast the data */
msg_data.rfc5988_link = newlink;
content_broadcast(c, CONTENT_MSG_LINK, &msg_data);
return true;
}
/* exported interface documented in content/content.h */
nsurl *content_get_url(struct content *c)
{
if (c == NULL)
return NULL;
return llcache_handle_get_url(c->llcache);
}
/* exported interface documented in content/content.h */
content_type content_get_type(hlcache_handle *h)
{
struct content *c = hlcache_handle_get_content(h);
if (c == NULL)
return CONTENT_NONE;
return c->handler->type();
}
/* exported interface documented in content/content.h */
lwc_string *content_get_mime_type(hlcache_handle *h)
{
return content__get_mime_type(hlcache_handle_get_content(h));
}
/* exported interface documented in content/content_protected.h */
lwc_string *content__get_mime_type(struct content *c)
{
if (c == NULL)
return NULL;
return lwc_string_ref(c->mime_type);
}
/* exported interface documented in content/content_protected.h */
bool content__set_title(struct content *c, const char *title)
{
char *new_title = strdup(title);
if (new_title == NULL)
return false;
if (c->title != NULL)
free(c->title);
c->title = new_title;
return true;
}
/* exported interface documented in content/content.h */
const char *content_get_title(hlcache_handle *h)
{
return content__get_title(hlcache_handle_get_content(h));
}
/* exported interface documented in content/content_protected.h */
const char *content__get_title(struct content *c)
{
if (c == NULL)
return NULL;
return c->title != NULL ? c->title :
nsurl_access(llcache_handle_get_url(c->llcache));
}
/* exported interface documented in content/content.h */
content_status content_get_status(hlcache_handle *h)
{
return content__get_status(hlcache_handle_get_content(h));
}
/* exported interface documented in content/content_protected.h */
content_status content__get_status(struct content *c)
{
if (c == NULL)
return CONTENT_STATUS_ERROR;
return c->status;
}
/* exported interface documented in content/content.h */
const char *content_get_status_message(hlcache_handle *h)
{
return content__get_status_message(hlcache_handle_get_content(h));
}
/* exported interface documented in content/content_protected.h */
const char *content__get_status_message(struct content *c)
{
if (c == NULL)
return NULL;
return c->status_message;
}
/* exported interface documented in content/content.h */
int content_get_width(hlcache_handle *h)
{
return content__get_width(hlcache_handle_get_content(h));
}
/* exported interface documented in content/content_protected.h */
int content__get_width(struct content *c)
{
if (c == NULL)
return 0;
return c->width;
}
/* exported interface documented in content/content.h */
int content_get_height(hlcache_handle *h)
{
return content__get_height(hlcache_handle_get_content(h));
}
/* exported interface documented in content/content_protected.h */
int content__get_height(struct content *c)
{
if (c == NULL)
return 0;
return c->height;
}
/* exported interface documented in content/content.h */
int content_get_available_width(hlcache_handle *h)
{
return content__get_available_width(hlcache_handle_get_content(h));
}
/* exported interface documented in content/content_protected.h */
int content__get_available_width(struct content *c)
{
if (c == NULL)
return 0;
return c->available_width;
}
/* exported interface documented in content/content.h */
const char *content_get_source_data(hlcache_handle *h, unsigned long *size)
{
return content__get_source_data(hlcache_handle_get_content(h), size);
}
/* exported interface documented in content/content_protected.h */
const char *content__get_source_data(struct content *c, unsigned long *size)
{
const uint8_t *data;
size_t len;
assert(size != NULL);
if (c == NULL)
return NULL;
data = llcache_handle_get_source_data(c->llcache, &len);
*size = (unsigned long) len;
return (const char *) data;
}
/* exported interface documented in content/content.h */
void content_invalidate_reuse_data(hlcache_handle *h)
{
content__invalidate_reuse_data(hlcache_handle_get_content(h));
}
/* exported interface documented in content/content_protected.h */
void content__invalidate_reuse_data(struct content *c)
{
if (c == NULL || c->llcache == NULL)
return;
/* Invalidate low-level cache data */
llcache_handle_invalidate_cache_data(c->llcache);
}
/* exported interface documented in content/content.h */
nsurl *content_get_refresh_url(hlcache_handle *h)
{
return content__get_refresh_url(hlcache_handle_get_content(h));
}
/* exported interface documented in content/content_protected.h */
nsurl *content__get_refresh_url(struct content *c)
{
if (c == NULL)
return NULL;
return c->refresh;
}
/* exported interface documented in content/content.h */
struct bitmap *content_get_bitmap(hlcache_handle *h)
{
return content__get_bitmap(hlcache_handle_get_content(h));
}
/* exported interface documented in content/content_protected.h */
struct bitmap *content__get_bitmap(struct content *c)
{
struct bitmap *bitmap = NULL;
if ((c != NULL) &&
(c->handler != NULL) &&
(c->handler->type != NULL) &&
(c->handler->type() == CONTENT_IMAGE) &&
(c->handler->get_internal != NULL) ) {
bitmap = c->handler->get_internal(c, NULL);
}
return bitmap;
}
/* exported interface documented in content/content.h */
bool content_get_opaque(hlcache_handle *h)
{
return content__get_opaque(hlcache_handle_get_content(h));
}
/* exported interface documented in content/content_protected.h */
bool content__get_opaque(struct content *c)
{
bool opaque = false;
if ((c != NULL) &&
(c->handler != NULL) &&
(c->handler->type != NULL) &&
(c->handler->type() == CONTENT_IMAGE) &&
(c->handler->get_internal != NULL) ) {
struct bitmap *bitmap = NULL;
bitmap = c->handler->get_internal(c, NULL);
if (bitmap != NULL) {
opaque = guit->bitmap->get_opaque(bitmap);
}
}
return opaque;
}
/* exported interface documented in content/content.h */
bool content_get_quirks(hlcache_handle *h)
{
struct content *c = hlcache_handle_get_content(h);
if (c == NULL)
return false;
return c->quirks;
}
/* exported interface documented in content/content.h */
const char *content_get_encoding(hlcache_handle *h, enum content_encoding_type op)
{
return content__get_encoding(hlcache_handle_get_content(h), op);
}
/* exported interface documented in content/content_protected.h */
const char *content__get_encoding(struct content *c, enum content_encoding_type op)
{
const char *encoding = NULL;
if ((c != NULL) &&
(c->handler != NULL) &&
(c->handler->get_encoding != NULL) ) {
encoding = c->handler->get_encoding(c, op);
}
return encoding;
}
/* exported interface documented in content/content.h */
bool content_is_locked(hlcache_handle *h)
{
return content__is_locked(hlcache_handle_get_content(h));
}
/* exported interface documented in content/content_protected.h */
bool content__is_locked(struct content *c)
{
return c->locked;
}
/**
* Retrieve the low-level cache handle for a content
*
* \param c Content to retrieve from
* \return Low-level cache handle
*/
const llcache_handle *content_get_llcache_handle(struct content *c)
{
if (c == NULL)
return NULL;
return c->llcache;
}
/**
* Clone a content object in its current state.
*
* \param c Content to clone
* \return Clone of \a c
*/
struct content *content_clone(struct content *c)
{
struct content *nc;
nserror error;
error = c->handler->clone(c, &nc);
if (error != NSERROR_OK)
return NULL;
return nc;
};
/**
* Clone a content's data members
*
* \param c Content to clone
* \param nc Content to populate
* \return NSERROR_OK on success, appropriate error otherwise
*/
nserror content__clone(const struct content *c, struct content *nc)
{
nserror error;
error = llcache_handle_clone(c->llcache, &(nc->llcache));
if (error != NSERROR_OK) {
return error;
}
llcache_handle_change_callback(nc->llcache,
content_llcache_callback, nc);
nc->mime_type = lwc_string_ref(c->mime_type);
nc->handler = c->handler;
nc->status = c->status;
nc->width = c->width;
nc->height = c->height;
nc->available_width = c->available_width;
nc->quirks = c->quirks;
if (c->fallback_charset != NULL) {
nc->fallback_charset = strdup(c->fallback_charset);
if (nc->fallback_charset == NULL) {
return NSERROR_NOMEM;
}
}
if (c->refresh != NULL) {
nc->refresh = nsurl_ref(c->refresh);
if (nc->refresh == NULL) {
return NSERROR_NOMEM;
}
}
nc->time = c->time;
nc->reformat_time = c->reformat_time;
nc->size = c->size;
if (c->title != NULL) {
nc->title = strdup(c->title);
if (nc->title == NULL) {
return NSERROR_NOMEM;
}
}
nc->active = c->active;
nc->user_list = calloc(1, sizeof(struct content_user));
if (nc->user_list == NULL) {
return NSERROR_NOMEM;
}
memcpy(&(nc->status_message), &(c->status_message), 120);
memcpy(&(nc->sub_status), &(c->sub_status), 80);
nc->locked = c->locked;
nc->total_size = c->total_size;
nc->http_code = c->http_code;
return NSERROR_OK;
}
/**
* Abort a content object
*
* \param c The content object to abort
* \return NSERROR_OK on success, otherwise appropriate error
*/
nserror content_abort(struct content *c)
{
NSLOG(netsurf, INFO, "Aborting %p", c);
if (c->handler->stop != NULL)
c->handler->stop(c);
/* And for now, abort our llcache object */
return llcache_handle_abort(c->llcache);
}