netsurf/content/handlers/html/html_object.c
Michael Drake e1a3e0427f HTML handler: Reformat passing viewport height when triggered by object.
Previously we correctly used the viewport width, but we were using the
document height instead of viewport height when an HTML child content
triggered a reformat of the parent HTML document.
2019-02-17 09:06:58 +00:00

732 lines
17 KiB
C

/*
* Copyright 2013 Vincent Sanders <vince@netsurf-browser.org>
*
* This file is part of NetSurf, http://www.netsurf-browser.org/
*
* NetSurf is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* NetSurf is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* \file
* Processing for html content object operations.
*/
#include <assert.h>
#include <ctype.h>
#include <stdint.h>
#include <string.h>
#include <strings.h>
#include <stdlib.h>
#include <nsutils/time.h>
#include "utils/corestrings.h"
#include "utils/config.h"
#include "utils/log.h"
#include "utils/nsoption.h"
#include "netsurf/content.h"
#include "netsurf/misc.h"
#include "content/hlcache.h"
#include "css/utils.h"
#include "desktop/scrollbar.h"
#include "desktop/gui_internal.h"
#include "html/html.h"
#include "html/box.h"
#include "html/html_internal.h"
/* break reference loop */
static void html_object_refresh(void *p);
/**
* Retrieve objects used by HTML document
*
* \param h Content to retrieve objects from
* \param n Pointer to location to receive number of objects
* \return Pointer to list of objects
*/
struct content_html_object *html_get_objects(hlcache_handle *h, unsigned int *n)
{
html_content *c = (html_content *) hlcache_handle_get_content(h);
assert(c != NULL);
assert(n != NULL);
*n = c->num_objects;
return c->object_list;
}
/**
* Handle object fetching or loading failure.
*
* \param box box containing object which failed to load
* \param content document of type CONTENT_HTML
* \param background the object was the background image for the box
*/
static void
html_object_failed(struct box *box, html_content *content, bool background)
{
/* Nothing to do */
return;
}
/**
* Update a box whose content has completed rendering.
*/
static void
html_object_done(struct box *box,
hlcache_handle *object,
bool background)
{
struct box *b;
if (background) {
box->background = object;
return;
}
box->object = object;
/* Normalise the box type, now it has been replaced. */
switch (box->type) {
case BOX_TABLE:
box->type = BOX_BLOCK;
break;
default:
/* TODO: Any other box types need mapping? */
break;
}
if (!(box->flags & REPLACE_DIM)) {
/* invalidate parent min, max widths */
for (b = box; b; b = b->parent)
b->max_width = UNKNOWN_MAX_WIDTH;
/* delete any clones of this box */
while (box->next && (box->next->flags & CLONE)) {
/* box_free_box(box->next); */
box->next = box->next->next;
}
}
}
/**
* Callback for hlcache_handle_retrieve() for objects.
*/
static nserror
html_object_callback(hlcache_handle *object,
const hlcache_event *event,
void *pw)
{
struct content_html_object *o = pw;
html_content *c = (html_content *) o->parent;
int x, y;
struct box *box;
box = o->box;
if (box == NULL &&
event->type != CONTENT_MSG_ERROR &&
event->type != CONTENT_MSG_ERRORCODE) {
return NSERROR_OK;
}
switch (event->type) {
case CONTENT_MSG_LOADING:
if (c->base.status != CONTENT_STATUS_LOADING && c->bw != NULL)
content_open(object,
c->bw, &c->base,
box->object_params);
break;
case CONTENT_MSG_READY:
if (content_can_reformat(object)) {
/* TODO: avoid knowledge of box internals here */
content_reformat(object, false,
box->max_width != UNKNOWN_MAX_WIDTH ?
box->width : 0,
box->max_width != UNKNOWN_MAX_WIDTH ?
box->height : 0);
/* Adjust parent content for new object size */
html_object_done(box, object, o->background);
if (c->base.status == CONTENT_STATUS_READY ||
c->base.status == CONTENT_STATUS_DONE)
content__reformat(&c->base, false,
c->base.available_width,
c->base.available_height);
}
break;
case CONTENT_MSG_DONE:
c->base.active--;
NSLOG(netsurf, INFO, "%d fetches active", c->base.active);
html_object_done(box, object, o->background);
if (c->base.status != CONTENT_STATUS_LOADING &&
box->flags & REPLACE_DIM) {
union content_msg_data data;
if (!box_visible(box))
break;
box_coords(box, &x, &y);
data.redraw.x = x + box->padding[LEFT];
data.redraw.y = y + box->padding[TOP];
data.redraw.width = box->width;
data.redraw.height = box->height;
data.redraw.full_redraw = true;
content_broadcast(&c->base, CONTENT_MSG_REDRAW, &data);
}
break;
case CONTENT_MSG_ERRORCODE:
case CONTENT_MSG_ERROR:
hlcache_handle_release(object);
o->content = NULL;
if (box != NULL) {
c->base.active--;
NSLOG(netsurf, INFO, "%d fetches active",
c->base.active);
content_add_error(&c->base, "?", 0);
html_object_failed(box, c, o->background);
}
break;
case CONTENT_MSG_REDRAW:
if (c->base.status != CONTENT_STATUS_LOADING) {
union content_msg_data data = event->data;
if (!box_visible(box))
break;
box_coords(box, &x, &y);
if (object == box->background) {
/* Redraw request is for background */
css_fixed hpos = 0, vpos = 0;
css_unit hunit = CSS_UNIT_PX;
css_unit vunit = CSS_UNIT_PX;
int width = box->padding[LEFT] + box->width +
box->padding[RIGHT];
int height = box->padding[TOP] + box->height +
box->padding[BOTTOM];
int t, h, l, w;
/* Need to know background-position */
css_computed_background_position(box->style,
&hpos, &hunit, &vpos, &vunit);
w = content_get_width(box->background);
if (hunit == CSS_UNIT_PCT) {
l = (width - w) * hpos / INTTOFIX(100);
} else {
l = FIXTOINT(nscss_len2px(&c->len_ctx,
hpos, hunit,
box->style));
}
h = content_get_height(box->background);
if (vunit == CSS_UNIT_PCT) {
t = (height - h) * vpos / INTTOFIX(100);
} else {
t = FIXTOINT(nscss_len2px(&c->len_ctx,
vpos, vunit,
box->style));
}
/* Redraw area depends on background-repeat */
switch (css_computed_background_repeat(
box->style)) {
case CSS_BACKGROUND_REPEAT_REPEAT:
data.redraw.x = 0;
data.redraw.y = 0;
data.redraw.width = box->width;
data.redraw.height = box->height;
break;
case CSS_BACKGROUND_REPEAT_REPEAT_X:
data.redraw.x = 0;
data.redraw.y += t;
data.redraw.width = box->width;
break;
case CSS_BACKGROUND_REPEAT_REPEAT_Y:
data.redraw.x += l;
data.redraw.y = 0;
data.redraw.height = box->height;
break;
case CSS_BACKGROUND_REPEAT_NO_REPEAT:
data.redraw.x += l;
data.redraw.y += t;
break;
default:
break;
}
data.redraw.object_width = box->width;
data.redraw.object_height = box->height;
/* Add offset to box */
data.redraw.x += x;
data.redraw.y += y;
data.redraw.object_x += x;
data.redraw.object_y += y;
content_broadcast(&c->base,
CONTENT_MSG_REDRAW, &data);
break;
} else {
/* Non-background case */
if (hlcache_handle_get_content(object) ==
event->data.redraw.object) {
int w = content_get_width(object);
int h = content_get_height(object);
if (w != 0) {
data.redraw.x =
data.redraw.x *
box->width / w;
data.redraw.width =
data.redraw.width *
box->width / w;
}
if (h != 0) {
data.redraw.y =
data.redraw.y *
box->height / h;
data.redraw.height =
data.redraw.height *
box->height / h;
}
data.redraw.object_width = box->width;
data.redraw.object_height = box->height;
}
data.redraw.x += x + box->padding[LEFT];
data.redraw.y += y + box->padding[TOP];
data.redraw.object_x += x + box->padding[LEFT];
data.redraw.object_y += y + box->padding[TOP];
}
content_broadcast(&c->base, CONTENT_MSG_REDRAW, &data);
}
break;
case CONTENT_MSG_REFRESH:
if (content_get_type(object) == CONTENT_HTML) {
/* only for HTML objects */
guit->misc->schedule(event->data.delay * 1000,
html_object_refresh, o);
}
break;
case CONTENT_MSG_LINK:
/* Don't care about favicons that aren't on top level content */
break;
case CONTENT_MSG_GETCTX:
*(event->data.jscontext) = NULL;
break;
case CONTENT_MSG_SCROLL:
if (box->scroll_x != NULL)
scrollbar_set(box->scroll_x, event->data.scroll.x0,
false);
if (box->scroll_y != NULL)
scrollbar_set(box->scroll_y, event->data.scroll.y0,
false);
break;
case CONTENT_MSG_DRAGSAVE:
{
union content_msg_data msg_data;
if (event->data.dragsave.content == NULL)
msg_data.dragsave.content = object;
else
msg_data.dragsave.content =
event->data.dragsave.content;
content_broadcast(&c->base, CONTENT_MSG_DRAGSAVE, &msg_data);
}
break;
case CONTENT_MSG_SAVELINK:
case CONTENT_MSG_POINTER:
case CONTENT_MSG_SELECTMENU:
case CONTENT_MSG_GADGETCLICK:
/* These messages are for browser window layer.
* we're not interested, so pass them on. */
content_broadcast(&c->base, event->type, &event->data);
break;
case CONTENT_MSG_CARET:
{
union html_focus_owner focus_owner;
focus_owner.content = box;
switch (event->data.caret.type) {
case CONTENT_CARET_REMOVE:
case CONTENT_CARET_HIDE:
html_set_focus(c, HTML_FOCUS_CONTENT, focus_owner,
true, 0, 0, 0, NULL);
break;
case CONTENT_CARET_SET_POS:
html_set_focus(c, HTML_FOCUS_CONTENT, focus_owner,
false, event->data.caret.pos.x,
event->data.caret.pos.y,
event->data.caret.pos.height,
event->data.caret.pos.clip);
break;
}
}
break;
case CONTENT_MSG_DRAG:
{
html_drag_type drag_type = HTML_DRAG_NONE;
union html_drag_owner drag_owner;
drag_owner.content = box;
switch (event->data.drag.type) {
case CONTENT_DRAG_NONE:
drag_type = HTML_DRAG_NONE;
drag_owner.no_owner = true;
break;
case CONTENT_DRAG_SCROLL:
drag_type = HTML_DRAG_CONTENT_SCROLL;
break;
case CONTENT_DRAG_SELECTION:
drag_type = HTML_DRAG_CONTENT_SELECTION;
break;
}
html_set_drag_type(c, drag_type, drag_owner,
event->data.drag.rect);
}
break;
case CONTENT_MSG_SELECTION:
{
html_selection_type sel_type;
union html_selection_owner sel_owner;
if (event->data.selection.selection) {
sel_type = HTML_SELECTION_CONTENT;
sel_owner.content = box;
} else {
sel_type = HTML_SELECTION_NONE;
sel_owner.none = true;
}
html_set_selection(c, sel_type, sel_owner,
event->data.selection.read_only);
}
break;
default:
break;
}
if (c->base.status == CONTENT_STATUS_READY &&
c->base.active == 0 &&
(event->type == CONTENT_MSG_LOADING ||
event->type == CONTENT_MSG_DONE ||
event->type == CONTENT_MSG_ERROR ||
event->type == CONTENT_MSG_ERRORCODE)) {
/* all objects have arrived */
content__reformat(&c->base, false, c->base.available_width,
c->base.available_height);
content_set_done(&c->base);
} else if (nsoption_bool(incremental_reflow) &&
event->type == CONTENT_MSG_DONE &&
box != NULL &&
!(box->flags & REPLACE_DIM) &&
(c->base.status == CONTENT_STATUS_READY ||
c->base.status == CONTENT_STATUS_DONE)) {
/* 1) the configuration option to reflow pages while
* objects are fetched is set
* 2) an object is newly fetched & converted,
* 3) the box's dimensions need to change due to being replaced
* 4) the object's parent HTML is ready for reformat,
*/
uint64_t ms_now;
nsu_getmonotonic_ms(&ms_now);
if (ms_now > c->base.reformat_time) {
/* The time since the previous reformat is
* more than the configured minimum time
* between reformats so reformat the page to
* display newly fetched objects
*/
content__reformat(&c->base,
false,
c->base.available_width,
c->base.available_height);
}
}
return NSERROR_OK;
}
/**
* Start a fetch for an object required by a page, replacing an existing object.
*
* \param object Object to replace
* \param url URL of object to fetch (copied)
* \return true on success, false on memory exhaustion
*/
static bool html_replace_object(struct content_html_object *object, nsurl *url)
{
html_content *c;
hlcache_child_context child;
html_content *page;
nserror error;
assert(object != NULL);
assert(object->box != NULL);
c = (html_content *) object->parent;
child.charset = c->encoding;
child.quirks = c->base.quirks;
if (object->content != NULL) {
/* remove existing object */
if (content_get_status(object->content) != CONTENT_STATUS_DONE) {
c->base.active--;
NSLOG(netsurf, INFO, "%d fetches active",
c->base.active);
}
hlcache_handle_release(object->content);
object->content = NULL;
object->box->object = NULL;
}
/* initialise fetch */
error = hlcache_handle_retrieve(url, HLCACHE_RETRIEVE_SNIFF_TYPE,
content_get_url(&c->base), NULL,
html_object_callback, object, &child,
object->permitted_types,
&object->content);
if (error != NSERROR_OK)
return false;
for (page = c; page != NULL; page = page->page) {
page->base.active++;
NSLOG(netsurf, INFO, "%d fetches active", c->base.active);
page->base.status = CONTENT_STATUS_READY;
}
return true;
}
/**
* schedule callback for object refresh
*/
static void html_object_refresh(void *p)
{
struct content_html_object *object = p;
nsurl *refresh_url;
assert(content_get_type(object->content) == CONTENT_HTML);
refresh_url = content_get_refresh_url(object->content);
/* Ignore if refresh URL has gone
* (may happen if fetch errored) */
if (refresh_url == NULL)
return;
content_invalidate_reuse_data(object->content);
if (!html_replace_object(object, refresh_url)) {
/** \todo handle memory exhaustion */
}
}
nserror html_object_open_objects(html_content *html, struct browser_window *bw)
{
struct content_html_object *object, *next;
for (object = html->object_list; object != NULL; object = next) {
next = object->next;
if (object->content == NULL || object->box == NULL)
continue;
if (content_get_type(object->content) == CONTENT_NONE)
continue;
content_open(object->content,
bw,
&html->base,
object->box->object_params);
}
return NSERROR_OK;
}
nserror html_object_abort_objects(html_content *htmlc)
{
struct content_html_object *object;
for (object = htmlc->object_list;
object != NULL;
object = object->next) {
if (object->content == NULL)
continue;
switch (content_get_status(object->content)) {
case CONTENT_STATUS_DONE:
/* already loaded: do nothing */
break;
case CONTENT_STATUS_READY:
hlcache_handle_abort(object->content);
/* Active count will be updated when
* html_object_callback receives
* CONTENT_MSG_DONE from this object
*/
break;
default:
hlcache_handle_abort(object->content);
hlcache_handle_release(object->content);
object->content = NULL;
if (object->box != NULL) {
htmlc->base.active--;
NSLOG(netsurf, INFO, "%d fetches active",
htmlc->base.active);
}
break;
}
}
return NSERROR_OK;
}
nserror html_object_close_objects(html_content *html)
{
struct content_html_object *object, *next;
for (object = html->object_list; object != NULL; object = next) {
next = object->next;
if (object->content == NULL || object->box == NULL)
continue;
if (content_get_type(object->content) == CONTENT_NONE)
continue;
if (content_get_type(object->content) == CONTENT_HTML) {
guit->misc->schedule(-1, html_object_refresh, object);
}
content_close(object->content);
}
return NSERROR_OK;
}
nserror html_object_free_objects(html_content *html)
{
while (html->object_list != NULL) {
struct content_html_object *victim = html->object_list;
if (victim->content != NULL) {
NSLOG(netsurf, INFO, "object %p", victim->content);
if (content_get_type(victim->content) == CONTENT_HTML) {
guit->misc->schedule(-1, html_object_refresh, victim);
}
hlcache_handle_release(victim->content);
}
html->object_list = victim->next;
free(victim);
}
return NSERROR_OK;
}
/* exported interface documented in html/html_internal.h */
bool html_fetch_object(html_content *c, nsurl *url, struct box *box,
content_type permitted_types,
int available_width, int available_height,
bool background)
{
struct content_html_object *object;
hlcache_child_context child;
nserror error;
/* If we've already been aborted, don't bother attempting the fetch */
if (c->aborted)
return true;
child.charset = c->encoding;
child.quirks = c->base.quirks;
object = calloc(1, sizeof(struct content_html_object));
if (object == NULL) {
return false;
}
object->parent = (struct content *) c;
object->next = NULL;
object->content = NULL;
object->box = box;
object->permitted_types = permitted_types;
object->background = background;
error = hlcache_handle_retrieve(url,
HLCACHE_RETRIEVE_SNIFF_TYPE,
content_get_url(&c->base), NULL,
html_object_callback, object, &child,
object->permitted_types, &object->content);
if (error != NSERROR_OK) {
free(object);
return error != NSERROR_NOMEM;
}
/* add to content object list */
object->next = c->object_list;
c->object_list = object;
c->num_objects++;
if (box != NULL) {
c->base.active++;
NSLOG(netsurf, INFO, "%d fetches active", c->base.active);
}
return true;
}