netsurf: branch master updated. release/3.7-109-g2a03ea3
by NetSurf Browser Project
Gitweb links:
...log http://git.netsurf-browser.org/netsurf.git/shortlog/2a03ea30490892ac52b3d...
...commit http://git.netsurf-browser.org/netsurf.git/commit/2a03ea30490892ac52b3da3...
...tree http://git.netsurf-browser.org/netsurf.git/tree/2a03ea30490892ac52b3da325...
The branch, master has been updated
via 2a03ea30490892ac52b3da325ab78e1aa888f83e (commit)
via 1b892391d7859398c212b9fda5b532308fa6e8fd (commit)
from 2290c208ba69bb9f98d33c470712f9df5cc6c040 (commit)
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
commitdiff http://git.netsurf-browser.org/netsurf.git/commit/?id=2a03ea30490892ac52b...
commit 2a03ea30490892ac52b3da325ab78e1aa888f83e
Author: Vincent Sanders <vince(a)netsurf-browser.org>
Commit: Vincent Sanders <vince(a)kyllikki.org>
move html and text content handlers where they belong
diff --git a/Makefile b/Makefile
index 52f55d5..3109a77 100644
--- a/Makefile
+++ b/Makefile
@@ -617,9 +617,6 @@ include frontends/Makefile
# Content sources
include content/Makefile
-# render sources
-include render/Makefile
-
# utility sources
include utils/Makefile
@@ -636,7 +633,6 @@ include desktop/Makefile
S_COMMON := \
$(S_CONTENT) \
$(S_FETCHERS) \
- $(S_RENDER) \
$(S_UTILS) \
$(S_HTTP) \
$(S_NSURL) \
diff --git a/content/handlers/Makefile b/content/handlers/Makefile
index 2f2da3a..ea9d0c8 100644
--- a/content/handlers/Makefile
+++ b/content/handlers/Makefile
@@ -13,4 +13,15 @@ include content/handlers/javascript/Makefile
S_CONTENT += $(addprefix handlers/javascript/,$(S_JAVASCRIPT))
+# HTML content handler sources
+include content/handlers/html/Makefile
+
+S_CONTENT += $(addprefix handlers/html/,$(S_HTML))
+
+# Text content handler sources
+include content/handlers/text/Makefile
+
+S_CONTENT += $(addprefix handlers/text/,$(S_TEXT))
+
+# extend the include search path
INCLUDE_DIRS += content/handlers
diff --git a/content/handlers/html/Makefile b/content/handlers/html/Makefile
new file mode 100644
index 0000000..afefba2
--- /dev/null
+++ b/content/handlers/html/Makefile
@@ -0,0 +1,7 @@
+# HTML content handler sources
+
+S_HTML := box.c box_construct.c box_normalise.c box_textarea.c \
+ font.c form.c imagemap.c layout.c search.c table.c \
+ html.c html_css.c html_css_fetcher.c html_script.c \
+ html_interaction.c html_redraw.c html_redraw_border.c \
+ html_forms.c html_object.c
diff --git a/content/handlers/html/box.c b/content/handlers/html/box.c
new file mode 100644
index 0000000..52cf124
--- /dev/null
+++ b/content/handlers/html/box.c
@@ -0,0 +1,1241 @@
+/*
+ * Copyright 2005-2007 James Bursa <bursa(a)users.sourceforge.net>
+ * Copyright 2003 Phil Mellor <monkeyson(a)users.sourceforge.net>
+ * Copyright 2005 John M Bell <jmb202(a)ecs.soton.ac.uk>
+ * Copyright 2008 Michael Drake <tlsa(a)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
+ * implementation of box tree manipulation.
+ */
+
+#include <assert.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <dom/dom.h>
+
+#include "utils/nsoption.h"
+#include "utils/log.h"
+#include "utils/talloc.h"
+#include "netsurf/misc.h"
+#include "netsurf/content.h"
+#include "netsurf/mouse.h"
+#include "css/utils.h"
+#include "css/dump.h"
+#include "desktop/scrollbar.h"
+#include "desktop/gui_internal.h"
+
+#include "html/box.h"
+#include "html/form_internal.h"
+#include "html/html_internal.h"
+
+#define box_is_float(box) (box->type == BOX_FLOAT_LEFT || \
+ box->type == BOX_FLOAT_RIGHT)
+
+/**
+ * Destructor for box nodes which own styles
+ *
+ * \param b The box being destroyed.
+ * \return 0 to allow talloc to continue destroying the tree.
+ */
+static int box_talloc_destructor(struct box *b)
+{
+ struct html_scrollbar_data *data;
+
+ if ((b->flags & STYLE_OWNED) && b->style != NULL) {
+ css_computed_style_destroy(b->style);
+ b->style = NULL;
+ }
+
+ if (b->styles != NULL) {
+ css_select_results_destroy(b->styles);
+ b->styles = NULL;
+ }
+
+ if (b->href != NULL)
+ nsurl_unref(b->href);
+
+ if (b->id != NULL) {
+ lwc_string_unref(b->id);
+ }
+
+ if (b->node != NULL) {
+ dom_node_unref(b->node);
+ }
+
+ if (b->scroll_x != NULL) {
+ data = scrollbar_get_data(b->scroll_x);
+ scrollbar_destroy(b->scroll_x);
+ free(data);
+ }
+
+ if (b->scroll_y != NULL) {
+ data = scrollbar_get_data(b->scroll_y);
+ scrollbar_destroy(b->scroll_y);
+ free(data);
+ }
+
+ return 0;
+}
+
+/**
+ * Create a box tree node.
+ *
+ * \param styles selection results for the box, or NULL
+ * \param style computed style for the box (not copied), or 0
+ * \param style_owned whether style is owned by this box
+ * \param href href for the box (copied), or 0
+ * \param target target for the box (not copied), or 0
+ * \param title title for the box (not copied), or 0
+ * \param id id for the box (not copied), or 0
+ * \param context context for allocations
+ * \return allocated and initialised box, or 0 on memory exhaustion
+ *
+ * styles is always owned by the box, if it is set.
+ * style is only owned by the box in the case of implied boxes.
+ */
+
+struct box * box_create(css_select_results *styles, css_computed_style *style,
+ bool style_owned, nsurl *href, const char *target,
+ const char *title, lwc_string *id, void *context)
+{
+ unsigned int i;
+ struct box *box;
+
+ box = talloc(context, struct box);
+ if (!box) {
+ return 0;
+ }
+
+ talloc_set_destructor(box, box_talloc_destructor);
+
+ box->type = BOX_INLINE;
+ box->flags = 0;
+ box->flags = style_owned ? (box->flags | STYLE_OWNED) : box->flags;
+ box->styles = styles;
+ box->style = style;
+ box->x = box->y = 0;
+ box->width = UNKNOWN_WIDTH;
+ box->height = 0;
+ box->descendant_x0 = box->descendant_y0 = 0;
+ box->descendant_x1 = box->descendant_y1 = 0;
+ for (i = 0; i != 4; i++)
+ box->margin[i] = box->padding[i] = box->border[i].width = 0;
+ box->scroll_x = box->scroll_y = NULL;
+ box->min_width = 0;
+ box->max_width = UNKNOWN_MAX_WIDTH;
+ box->byte_offset = 0;
+ box->text = NULL;
+ box->length = 0;
+ box->space = 0;
+ box->href = (href == NULL) ? NULL : nsurl_ref(href);
+ box->target = target;
+ box->title = title;
+ box->columns = 1;
+ box->rows = 1;
+ box->start_column = 0;
+ box->next = NULL;
+ box->prev = NULL;
+ box->children = NULL;
+ box->last = NULL;
+ box->parent = NULL;
+ box->inline_end = NULL;
+ box->float_children = NULL;
+ box->float_container = NULL;
+ box->next_float = NULL;
+ box->cached_place_below_level = 0;
+ box->list_marker = NULL;
+ box->col = NULL;
+ box->gadget = NULL;
+ box->usemap = NULL;
+ box->id = id;
+ box->background = NULL;
+ box->object = NULL;
+ box->object_params = NULL;
+ box->iframe = NULL;
+ box->node = NULL;
+
+ return box;
+}
+
+/**
+ * Add a child to a box tree node.
+ *
+ * \param parent box giving birth
+ * \param child box to link as last child of parent
+ */
+
+void box_add_child(struct box *parent, struct box *child)
+{
+ assert(parent);
+ assert(child);
+
+ if (parent->children != 0) { /* has children already */
+ parent->last->next = child;
+ child->prev = parent->last;
+ } else { /* this is the first child */
+ parent->children = child;
+ child->prev = 0;
+ }
+
+ parent->last = child;
+ child->parent = parent;
+}
+
+
+/**
+ * Insert a new box as a sibling to a box in a tree.
+ *
+ * \param box box already in tree
+ * \param new_box box to link into tree as next sibling
+ */
+
+void box_insert_sibling(struct box *box, struct box *new_box)
+{
+ new_box->parent = box->parent;
+ new_box->prev = box;
+ new_box->next = box->next;
+ box->next = new_box;
+ if (new_box->next)
+ new_box->next->prev = new_box;
+ else if (new_box->parent)
+ new_box->parent->last = new_box;
+}
+
+
+/**
+ * Unlink a box from the box tree and then free it recursively.
+ *
+ * \param box box to unlink and free recursively.
+ */
+
+void box_unlink_and_free(struct box *box)
+{
+ struct box *parent = box->parent;
+ struct box *next = box->next;
+ struct box *prev = box->prev;
+
+ if (parent) {
+ if (parent->children == box)
+ parent->children = next;
+ if (parent->last == box)
+ parent->last = next ? next : prev;
+ }
+
+ if (prev)
+ prev->next = next;
+ if (next)
+ next->prev = prev;
+
+ box_free(box);
+}
+
+
+/**
+ * Free a box tree recursively.
+ *
+ * \param box box to free recursively
+ *
+ * The box and all its children is freed.
+ */
+
+void box_free(struct box *box)
+{
+ struct box *child, *next;
+
+ /* free children first */
+ for (child = box->children; child; child = next) {
+ next = child->next;
+ box_free(child);
+ }
+
+ /* last this box */
+ box_free_box(box);
+}
+
+
+/**
+ * Free the data in a single box structure.
+ *
+ * \param box box to free
+ */
+
+void box_free_box(struct box *box)
+{
+ if (!(box->flags & CLONE)) {
+ if (box->gadget)
+ form_free_control(box->gadget);
+ if (box->scroll_x != NULL)
+ scrollbar_destroy(box->scroll_x);
+ if (box->scroll_y != NULL)
+ scrollbar_destroy(box->scroll_y);
+ if (box->styles != NULL)
+ css_select_results_destroy(box->styles);
+ }
+
+ talloc_free(box);
+}
+
+
+/**
+ * Find the absolute coordinates of a box.
+ *
+ * \param box the box to calculate coordinates of
+ * \param x updated to x coordinate
+ * \param y updated to y coordinate
+ */
+
+void box_coords(struct box *box, int *x, int *y)
+{
+ *x = box->x;
+ *y = box->y;
+ while (box->parent) {
+ if (box_is_float(box)) {
+ do {
+ box = box->parent;
+ } while (!box->float_children);
+ } else
+ box = box->parent;
+ *x += box->x - scrollbar_get_offset(box->scroll_x);
+ *y += box->y - scrollbar_get_offset(box->scroll_y);
+ }
+}
+
+
+/**
+ * Find the bounds of a box.
+ *
+ * \param box the box to calculate bounds of
+ * \param r receives bounds
+ */
+
+void box_bounds(struct box *box, struct rect *r)
+{
+ int width, height;
+
+ box_coords(box, &r->x0, &r->y0);
+
+ width = box->padding[LEFT] + box->width + box->padding[RIGHT];
+ height = box->padding[TOP] + box->height + box->padding[BOTTOM];
+
+ r->x1 = r->x0 + width;
+ r->y1 = r->y0 + height;
+}
+
+
+/**
+ * Determine if a point lies within a box.
+ *
+ * \param[in] len_ctx CSS length conversion context to use.
+ * \param[in] box Box to consider
+ * \param[in] x Coordinate relative to box
+ * \param[in] y Coordinate relative to box
+ * \param[out] physically If function returning true, physically is set true
+ * iff point is within the box's physical dimensions and
+ * false if the point is not within the box's physical
+ * dimensions but is in the area defined by the box's
+ * descendants. If function returns false, physically
+ * is undefined.
+ * \return true if the point is within the box or a descendant box
+ *
+ * This is a helper function for box_at_point().
+ */
+
+static bool box_contains_point(
+ const nscss_len_ctx *len_ctx,
+ const struct box *box,
+ int x,
+ int y,
+ bool *physically)
+{
+ css_computed_clip_rect css_rect;
+
+ if (box->style != NULL &&
+ css_computed_position(box->style) ==
+ CSS_POSITION_ABSOLUTE &&
+ css_computed_clip(box->style, &css_rect) ==
+ CSS_CLIP_RECT) {
+ /* We have an absolutly positioned box with a clip rect */
+ struct rect r = {
+ .x0 = box->border[LEFT].width,
+ .y0 = box->border[TOP].width,
+ .x1 = box->padding[LEFT] + box->width +
+ box->border[RIGHT].width +
+ box->padding[RIGHT],
+ .y1 = box->padding[TOP] + box->height +
+ box->border[BOTTOM].width +
+ box->padding[BOTTOM]
+ };
+ if (x >= r.x0 && x < r.x1 && y >= r.y0 && y < r.y1)
+ *physically = true;
+ else
+ *physically = false;
+
+ /* Adjust rect to css clip region */
+ if (css_rect.left_auto == false) {
+ r.x0 += FIXTOINT(nscss_len2px(len_ctx,
+ css_rect.left, css_rect.lunit,
+ box->style));
+ }
+ if (css_rect.top_auto == false) {
+ r.y0 += FIXTOINT(nscss_len2px(len_ctx,
+ css_rect.top, css_rect.tunit,
+ box->style));
+ }
+ if (css_rect.right_auto == false) {
+ r.x1 = box->border[LEFT].width +
+ FIXTOINT(nscss_len2px(len_ctx,
+ css_rect.right,
+ css_rect.runit,
+ box->style));
+ }
+ if (css_rect.bottom_auto == false) {
+ r.y1 = box->border[TOP].width +
+ FIXTOINT(nscss_len2px(len_ctx,
+ css_rect.bottom,
+ css_rect.bunit,
+ box->style));
+ }
+
+ /* Test if point is in clipped box */
+ if (x >= r.x0 && x < r.x1 && y >= r.y0 && y < r.y1) {
+ /* inside clip area */
+ return true;
+ }
+
+ /* Not inside clip area */
+ return false;
+ }
+ if (x >= -box->border[LEFT].width &&
+ x < box->padding[LEFT] + box->width +
+ box->padding[RIGHT] + box->border[RIGHT].width &&
+ y >= -box->border[TOP].width &&
+ y < box->padding[TOP] + box->height +
+ box->padding[BOTTOM] + box->border[BOTTOM].width) {
+ *physically = true;
+ return true;
+ }
+ if (box->list_marker && box->list_marker->x - box->x <= x +
+ box->list_marker->border[LEFT].width &&
+ x < box->list_marker->x - box->x +
+ box->list_marker->padding[LEFT] +
+ box->list_marker->width +
+ box->list_marker->border[RIGHT].width +
+ box->list_marker->padding[RIGHT] &&
+ box->list_marker->y - box->y <= y +
+ box->list_marker->border[TOP].width &&
+ y < box->list_marker->y - box->y +
+ box->list_marker->padding[TOP] +
+ box->list_marker->height +
+ box->list_marker->border[BOTTOM].width +
+ box->list_marker->padding[BOTTOM]) {
+ *physically = true;
+ return true;
+ }
+ if ((box->style && css_computed_overflow_x(box->style) ==
+ CSS_OVERFLOW_VISIBLE) || !box->style) {
+ if (box->descendant_x0 <= x &&
+ x < box->descendant_x1) {
+ *physically = false;
+ return true;
+ }
+ }
+ if ((box->style && css_computed_overflow_y(box->style) ==
+ CSS_OVERFLOW_VISIBLE) || !box->style) {
+ if (box->descendant_y0 <= y &&
+ y < box->descendant_y1) {
+ *physically = false;
+ return true;
+ }
+ }
+ return false;
+}
+
+
+/** Direction to move in a box-tree walk */
+enum box_walk_dir {
+ BOX_WALK_CHILDREN,
+ BOX_WALK_PARENT,
+ BOX_WALK_NEXT_SIBLING,
+ BOX_WALK_FLOAT_CHILDREN,
+ BOX_WALK_NEXT_FLOAT_SIBLING,
+ BOX_WALK_FLOAT_CONTAINER
+};
+
+
+/**
+ * Move from box to next box in given direction, adjusting for box coord change
+ *
+ * \param b box to move from from
+ * \param dir direction to move in
+ * \param x box's global x-coord, updated to position of next box
+ * \param y box's global y-coord, updated to position of next box
+ *
+ * If no box can be found in given direction, NULL is returned.
+ */
+static inline struct box *box_move_xy(struct box *b, enum box_walk_dir dir,
+ int *x, int *y)
+{
+ struct box *rb = NULL;
+
+ switch (dir) {
+ case BOX_WALK_CHILDREN:
+ b = b->children;
+ if (b == NULL)
+ break;
+ *x += b->x;
+ *y += b->y;
+ if (!box_is_float(b)) {
+ rb = b;
+ break;
+ }
+ /* Fall through */
+
+ case BOX_WALK_NEXT_SIBLING:
+ do {
+ *x -= b->x;
+ *y -= b->y;
+ b = b->next;
+ if (b == NULL)
+ break;
+ *x += b->x;
+ *y += b->y;
+ } while (box_is_float(b));
+ rb = b;
+ break;
+
+ case BOX_WALK_PARENT:
+ *x -= b->x;
+ *y -= b->y;
+ rb = b->parent;
+ break;
+
+ case BOX_WALK_FLOAT_CHILDREN:
+ b = b->float_children;
+ if (b == NULL)
+ break;
+ *x += b->x;
+ *y += b->y;
+ rb = b;
+ break;
+
+ case BOX_WALK_NEXT_FLOAT_SIBLING:
+ *x -= b->x;
+ *y -= b->y;
+ b = b->next_float;
+ if (b == NULL)
+ break;
+ *x += b->x;
+ *y += b->y;
+ rb = b;
+ break;
+
+ case BOX_WALK_FLOAT_CONTAINER:
+ *x -= b->x;
+ *y -= b->y;
+ rb = b->float_container;
+ break;
+
+ default:
+ assert(0 && "Bad box walk type.");
+ }
+
+ return rb;
+}
+
+
+/**
+ * Itterator for walking to next box in interaction order
+ *
+ * \param b box to find next box from
+ * \param x box's global x-coord, updated to position of next box
+ * \param y box's global y-coord, updated to position of next box
+ * \param skip_children whether to skip box's children
+ *
+ * This walks to a boxes float children before its children. When walking
+ * children, floating boxes are skipped.
+ */
+static inline struct box *box_next_xy(struct box *b, int *x, int *y,
+ bool skip_children)
+{
+ struct box *n;
+ int tx, ty;
+
+ assert(b != NULL);
+
+ if (skip_children) {
+ /* Caller is not interested in any kind of children */
+ goto skip_children;
+ }
+
+ tx = *x; ty = *y;
+ n = box_move_xy(b, BOX_WALK_FLOAT_CHILDREN, &tx, &ty);
+ if (n) {
+ /* Next node is float child */
+ *x = tx;
+ *y = ty;
+ return n;
+ }
+done_float_children:
+
+ tx = *x; ty = *y;
+ n = box_move_xy(b, BOX_WALK_CHILDREN, &tx, &ty);
+ if (n) {
+ /* Next node is child */
+ *x = tx;
+ *y = ty;
+ return n;
+ }
+
+skip_children:
+ tx = *x; ty = *y;
+ n = box_move_xy(b, BOX_WALK_NEXT_FLOAT_SIBLING, &tx, &ty);
+ if (n) {
+ /* Go to next float sibling */
+ *x = tx;
+ *y = ty;
+ return n;
+ }
+
+ if (box_is_float(b)) {
+ /* Done floats, but the float container may have children,
+ * or siblings, or ansestors with siblings. Change to
+ * float container and move past handling its float children.
+ */
+ b = box_move_xy(b, BOX_WALK_FLOAT_CONTAINER, x, y);
+ goto done_float_children;
+ }
+
+ /* Go to next sibling, or nearest ancestor with next sibling. */
+ while (b) {
+ while (!b->next && b->parent) {
+ b = box_move_xy(b, BOX_WALK_PARENT, x, y);
+ if (box_is_float(b)) {
+ /* Go on to next float, if there is one */
+ goto skip_children;
+ }
+ }
+ if (!b->next) {
+ /* No more boxes */
+ return NULL;
+ }
+
+ tx = *x; ty = *y;
+ n = box_move_xy(b, BOX_WALK_NEXT_SIBLING, &tx, &ty);
+ if (n) {
+ /* Go to non-float (ancestor) sibling */
+ *x = tx;
+ *y = ty;
+ return n;
+
+ } else if (b->parent) {
+ b = box_move_xy(b, BOX_WALK_PARENT, x, y);
+ if (box_is_float(b)) {
+ /* Go on to next float, if there is one */
+ goto skip_children;
+ }
+
+ } else {
+ /* No more boxes */
+ return NULL;
+ }
+ }
+
+ assert(b != NULL);
+ return NULL;
+}
+
+
+
+/**
+ * Find the boxes at a point.
+ *
+ * \param len_ctx CSS length conversion context for document.
+ * \param box box to search children of
+ * \param x point to find, in global document coordinates
+ * \param y point to find, in global document coordinates
+ * \param box_x position of box, in global document coordinates, updated
+ * to position of returned box, if any
+ * \param box_y position of box, in global document coordinates, updated
+ * to position of returned box, if any
+ * \return box at given point, or 0 if none found
+ *
+ * To find all the boxes in the hierarchy at a certain point, use code like
+ * this:
+ * \code
+ * struct box *box = top_of_document_to_search;
+ * int box_x = 0, box_y = 0;
+ *
+ * while ((box = box_at_point(len_ctx, box, x, y, &box_x, &box_y))) {
+ * // process box
+ * }
+ * \endcode
+ */
+
+struct box *box_at_point(const nscss_len_ctx *len_ctx,
+ struct box *box, const int x, const int y,
+ int *box_x, int *box_y)
+{
+ bool skip_children;
+ bool physically;
+
+ assert(box);
+
+ skip_children = false;
+ while ((box = box_next_xy(box, box_x, box_y, skip_children))) {
+ if (box_contains_point(len_ctx, box, x - *box_x, y - *box_y,
+ &physically)) {
+ *box_x -= scrollbar_get_offset(box->scroll_x);
+ *box_y -= scrollbar_get_offset(box->scroll_y);
+
+ if (physically)
+ return box;
+
+ skip_children = false;
+ } else {
+ skip_children = true;
+ }
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Check whether box is nearer mouse coordinates than current nearest box
+ *
+ * \param box box to test
+ * \param bx position of box, in global document coordinates
+ * \param by position of box, in global document coordinates
+ * \param x mouse point, in global document coordinates
+ * \param y mouse point, in global document coordinates
+ * \param dir direction in which to search (-1 = above-left,
+ * +1 = below-right)
+ * \param nearest nearest text box found, or NULL if none
+ * updated if box is nearer than existing nearest
+ * \param tx position of text_box, in global document coordinates
+ * updated if box is nearer than existing nearest
+ * \param ty position of text_box, in global document coordinates
+ * updated if box is nearer than existing nearest
+ * \param nr_xd distance to nearest text box found
+ * updated if box is nearer than existing nearest
+ * \param nr_yd distance to nearest text box found
+ * updated if box is nearer than existing nearest
+ * \return true if mouse point is inside box
+ */
+
+static bool box_nearer_text_box(struct box *box, int bx, int by,
+ int x, int y, int dir, struct box **nearest, int *tx, int *ty,
+ int *nr_xd, int *nr_yd)
+{
+ int w = box->padding[LEFT] + box->width + box->padding[RIGHT];
+ int h = box->padding[TOP] + box->height + box->padding[BOTTOM];
+ int y1 = by + h;
+ int x1 = bx + w;
+ int yd = INT_MAX;
+ int xd = INT_MAX;
+
+ if (x >= bx && x1 > x && y >= by && y1 > y) {
+ *nearest = box;
+ *tx = bx;
+ *ty = by;
+ return true;
+ }
+
+ if (box->parent->list_marker != box) {
+ if (dir < 0) {
+ /* consider only those children (partly) above-left */
+ if (by <= y && bx < x) {
+ yd = y <= y1 ? 0 : y - y1;
+ xd = x <= x1 ? 0 : x - x1;
+ }
+ } else {
+ /* consider only those children (partly) below-right */
+ if (y1 > y && x1 > x) {
+ yd = y > by ? 0 : by - y;
+ xd = x > bx ? 0 : bx - x;
+ }
+ }
+
+ /* give y displacement precedence over x */
+ if (yd < *nr_yd || (yd == *nr_yd && xd <= *nr_xd)) {
+ *nr_yd = yd;
+ *nr_xd = xd;
+ *nearest = box;
+ *tx = bx;
+ *ty = by;
+ }
+ }
+ return false;
+}
+
+
+/**
+ * Pick the text box child of 'box' that is closest to and above-left
+ * (dir -ve) or below-right (dir +ve) of the point 'x,y'
+ *
+ * \param box parent box
+ * \param bx position of box, in global document coordinates
+ * \param by position of box, in global document coordinates
+ * \param fx position of float parent, in global document coordinates
+ * \param fy position of float parent, in global document coordinates
+ * \param x mouse point, in global document coordinates
+ * \param y mouse point, in global document coordinates
+ * \param dir direction in which to search (-1 = above-left,
+ * +1 = below-right)
+ * \param nearest nearest text box found, or NULL if none
+ * updated if a descendant of box is nearer than old nearest
+ * \param tx position of nearest, in global document coordinates
+ * updated if a descendant of box is nearer than old nearest
+ * \param ty position of nearest, in global document coordinates
+ * updated if a descendant of box is nearer than old nearest
+ * \param nr_xd distance to nearest text box found
+ * updated if a descendant of box is nearer than old nearest
+ * \param nr_yd distance to nearest text box found
+ * updated if a descendant of box is nearer than old nearest
+ * \return true if mouse point is inside text_box
+ */
+
+static bool box_nearest_text_box(struct box *box, int bx, int by,
+ int fx, int fy, int x, int y, int dir, struct box **nearest,
+ int *tx, int *ty, int *nr_xd, int *nr_yd)
+{
+ struct box *child = box->children;
+ int c_bx, c_by;
+ int c_fx, c_fy;
+ bool in_box = false;
+
+ if (*nearest == NULL) {
+ *nr_xd = INT_MAX / 2; /* displacement of 'nearest so far' */
+ *nr_yd = INT_MAX / 2;
+ }
+ if (box->type == BOX_INLINE_CONTAINER) {
+ int bw = box->padding[LEFT] + box->width + box->padding[RIGHT];
+ int bh = box->padding[TOP] + box->height + box->padding[BOTTOM];
+ int b_y1 = by + bh;
+ int b_x1 = bx + bw;
+ if (x >= bx && b_x1 > x && y >= by && b_y1 > y) {
+ in_box = true;
+ }
+ }
+
+ while (child) {
+ if (child->type == BOX_FLOAT_LEFT ||
+ child->type == BOX_FLOAT_RIGHT) {
+ c_bx = fx + child->x -
+ scrollbar_get_offset(child->scroll_x);
+ c_by = fy + child->y -
+ scrollbar_get_offset(child->scroll_y);
+ } else {
+ c_bx = bx + child->x -
+ scrollbar_get_offset(child->scroll_x);
+ c_by = by + child->y -
+ scrollbar_get_offset(child->scroll_y);
+ }
+ if (child->float_children) {
+ c_fx = c_bx;
+ c_fy = c_by;
+ } else {
+ c_fx = fx;
+ c_fy = fy;
+ }
+ if (in_box && child->text && !child->object) {
+ if (box_nearer_text_box(child,
+ c_bx, c_by, x, y, dir, nearest,
+ tx, ty, nr_xd, nr_yd))
+ return true;
+ } else {
+ if (child->list_marker) {
+ if (box_nearer_text_box(
+ child->list_marker,
+ c_bx + child->list_marker->x,
+ c_by + child->list_marker->y,
+ x, y, dir, nearest,
+ tx, ty, nr_xd, nr_yd))
+ return true;
+ }
+ if (box_nearest_text_box(child, c_bx, c_by,
+ c_fx, c_fy, x, y, dir, nearest, tx, ty,
+ nr_xd, nr_yd))
+ return true;
+ }
+ child = child->next;
+ }
+
+ return false;
+}
+
+
+/**
+ * Peform pick text on browser window contents to locate the box under
+ * the mouse pointer, or nearest in the given direction if the pointer is
+ * not over a text box.
+ *
+ * \param html an HTML content
+ * \param x coordinate of mouse
+ * \param y coordinate of mouse
+ * \param dir direction to search (-1 = above-left, +1 = below-right)
+ * \param dx receives x ordinate of mouse relative to text box
+ * \param dy receives y ordinate of mouse relative to text box
+ */
+
+struct box *box_pick_text_box(struct html_content *html,
+ int x, int y, int dir, int *dx, int *dy)
+{
+ struct box *text_box = NULL;
+ struct box *box;
+ int nr_xd, nr_yd;
+ int bx, by;
+ int fx, fy;
+ int tx, ty;
+
+ if (html == NULL)
+ return NULL;
+
+ box = html->layout;
+ bx = box->margin[LEFT];
+ by = box->margin[TOP];
+ fx = bx;
+ fy = by;
+
+ if (!box_nearest_text_box(box, bx, by, fx, fy, x, y,
+ dir, &text_box, &tx, &ty, &nr_xd, &nr_yd)) {
+ if (text_box && text_box->text && !text_box->object) {
+ int w = (text_box->padding[LEFT] +
+ text_box->width +
+ text_box->padding[RIGHT]);
+ int h = (text_box->padding[TOP] +
+ text_box->height +
+ text_box->padding[BOTTOM]);
+ int x1, y1;
+
+ y1 = ty + h;
+ x1 = tx + w;
+
+ /* ensure point lies within the text box */
+ if (x < tx) x = tx;
+ if (y < ty) y = ty;
+ if (y > y1) y = y1;
+ if (x > x1) x = x1;
+ }
+ }
+
+ /* return coordinates relative to box */
+ *dx = x - tx;
+ *dy = y - ty;
+
+ return text_box;
+}
+
+
+/**
+ * Find a box based upon its id attribute.
+ *
+ * \param box box tree to search
+ * \param id id to look for
+ * \return the box or 0 if not found
+ */
+
+struct box *box_find_by_id(struct box *box, lwc_string *id)
+{
+ struct box *a, *b;
+ bool m;
+
+ if (box->id != NULL &&
+ lwc_string_isequal(id, box->id, &m) == lwc_error_ok &&
+ m == true)
+ return box;
+
+ for (a = box->children; a; a = a->next) {
+ if ((b = box_find_by_id(a, id)) != NULL)
+ return b;
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Determine if a box is visible when the tree is rendered.
+ *
+ * \param box box to check
+ * \return true iff the box is rendered
+ */
+
+bool box_visible(struct box *box)
+{
+ /* visibility: hidden */
+ if (box->style && css_computed_visibility(box->style) ==
+ CSS_VISIBILITY_HIDDEN)
+ return false;
+
+ return true;
+}
+
+
+/**
+ * Print a box tree to a file.
+ */
+
+void box_dump(FILE *stream, struct box *box, unsigned int depth, bool style)
+{
+ unsigned int i;
+ struct box *c, *prev;
+
+ for (i = 0; i != depth; i++)
+ fprintf(stream, " ");
+
+ fprintf(stream, "%p ", box);
+ fprintf(stream, "x%i y%i w%i h%i ", box->x, box->y,
+ box->width, box->height);
+ if (box->max_width != UNKNOWN_MAX_WIDTH)
+ fprintf(stream, "min%i max%i ", box->min_width, box->max_width);
+ fprintf(stream, "(%i %i %i %i) ",
+ box->descendant_x0, box->descendant_y0,
+ box->descendant_x1, box->descendant_y1);
+
+ fprintf(stream, "m(%i %i %i %i) ",
+ box->margin[TOP], box->margin[LEFT],
+ box->margin[BOTTOM], box->margin[RIGHT]);
+
+ switch (box->type) {
+ case BOX_BLOCK: fprintf(stream, "BLOCK "); break;
+ case BOX_INLINE_CONTAINER: fprintf(stream, "INLINE_CONTAINER "); break;
+ case BOX_INLINE: fprintf(stream, "INLINE "); break;
+ case BOX_INLINE_END: fprintf(stream, "INLINE_END "); break;
+ case BOX_INLINE_BLOCK: fprintf(stream, "INLINE_BLOCK "); break;
+ case BOX_TABLE: fprintf(stream, "TABLE [columns %i] ",
+ box->columns); break;
+ case BOX_TABLE_ROW: fprintf(stream, "TABLE_ROW "); break;
+ case BOX_TABLE_CELL: fprintf(stream, "TABLE_CELL [columns %i, "
+ "start %i, rows %i] ", box->columns,
+ box->start_column, box->rows); break;
+ case BOX_TABLE_ROW_GROUP: fprintf(stream, "TABLE_ROW_GROUP "); break;
+ case BOX_FLOAT_LEFT: fprintf(stream, "FLOAT_LEFT "); break;
+ case BOX_FLOAT_RIGHT: fprintf(stream, "FLOAT_RIGHT "); break;
+ case BOX_BR: fprintf(stream, "BR "); break;
+ case BOX_TEXT: fprintf(stream, "TEXT "); break;
+ default: fprintf(stream, "Unknown box type ");
+ }
+
+ if (box->text)
+ fprintf(stream, "%li '%.*s' ", (unsigned long) box->byte_offset,
+ (int) box->length, box->text);
+ if (box->space)
+ fprintf(stream, "space ");
+ if (box->object) {
+ fprintf(stream, "(object '%s') ",
+ nsurl_access(hlcache_handle_get_url(box->object)));
+ }
+ if (box->iframe) {
+ fprintf(stream, "(iframe) ");
+ }
+ if (box->gadget)
+ fprintf(stream, "(gadget) ");
+ if (style && box->style)
+ nscss_dump_computed_style(stream, box->style);
+ if (box->href)
+ fprintf(stream, " -> '%s'", nsurl_access(box->href));
+ if (box->target)
+ fprintf(stream, " |%s|", box->target);
+ if (box->title)
+ fprintf(stream, " [%s]", box->title);
+ if (box->id)
+ fprintf(stream, " ID:%s", lwc_string_data(box->id));
+ if (box->type == BOX_INLINE || box->type == BOX_INLINE_END)
+ fprintf(stream, " inline_end %p", box->inline_end);
+ if (box->float_children)
+ fprintf(stream, " float_children %p", box->float_children);
+ if (box->next_float)
+ fprintf(stream, " next_float %p", box->next_float);
+ if (box->float_container)
+ fprintf(stream, " float_container %p", box->float_container);
+ if (box->col) {
+ fprintf(stream, " (columns");
+ for (i = 0; i != box->columns; i++)
+ fprintf(stream, " (%s %s %i %i %i)",
+ ((const char *[]) {"UNKNOWN", "FIXED",
+ "AUTO", "PERCENT", "RELATIVE"})
+ [box->col[i].type],
+ ((const char *[]) {"normal",
+ "positioned"})
+ [box->col[i].positioned],
+ box->col[i].width,
+ box->col[i].min, box->col[i].max);
+ fprintf(stream, ")");
+ }
+ if (box->node != NULL) {
+ dom_string *name;
+ if (dom_node_get_node_name(box->node, &name) == DOM_NO_ERR) {
+ fprintf(stream, " <%s>", dom_string_data(name));
+ dom_string_unref(name);
+ }
+ }
+ fprintf(stream, "\n");
+
+ if (box->list_marker) {
+ for (i = 0; i != depth; i++)
+ fprintf(stream, " ");
+ fprintf(stream, "list_marker:\n");
+ box_dump(stream, box->list_marker, depth + 1, style);
+ }
+
+ for (c = box->children; c && c->next; c = c->next)
+ ;
+ if (box->last != c)
+ fprintf(stream, "warning: box->last %p (should be %p) "
+ "(box %p)\n", box->last, c, box);
+ for (prev = 0, c = box->children; c; prev = c, c = c->next) {
+ if (c->parent != box)
+ fprintf(stream, "warning: box->parent %p (should be "
+ "%p) (box on next line)\n",
+ c->parent, box);
+ if (c->prev != prev)
+ fprintf(stream, "warning: box->prev %p (should be "
+ "%p) (box on next line)\n",
+ c->prev, prev);
+ box_dump(stream, c, depth + 1, style);
+ }
+}
+
+/**
+ * Applies the given scroll setup to a box. This includes scroll
+ * creation/deletion as well as scroll dimension updates.
+ *
+ * \param c content in which the box is located
+ * \param box the box to handle the scrolls for
+ * \param bottom whether the horizontal scrollbar should be present
+ * \param right whether the vertical scrollbar should be present
+ * \return true on success false otherwise
+ */
+bool box_handle_scrollbars(struct content *c, struct box *box,
+ bool bottom, bool right)
+{
+ struct html_scrollbar_data *data;
+ int visible_width, visible_height;
+ int full_width, full_height;
+
+ if (!bottom && box->scroll_x != NULL) {
+ data = scrollbar_get_data(box->scroll_x);
+ scrollbar_destroy(box->scroll_x);
+ free(data);
+ box->scroll_x = NULL;
+ }
+
+ if (!right && box->scroll_y != NULL) {
+ data = scrollbar_get_data(box->scroll_y);
+ scrollbar_destroy(box->scroll_y);
+ free(data);
+ box->scroll_y = NULL;
+ }
+
+ if (!bottom && !right)
+ return true;
+
+ visible_width = box->width + box->padding[RIGHT] + box->padding[LEFT];
+ visible_height = box->height + box->padding[TOP] + box->padding[BOTTOM];
+
+ full_width = ((box->descendant_x1 - box->border[RIGHT].width) >
+ visible_width) ?
+ box->descendant_x1 + box->padding[RIGHT] :
+ visible_width;
+ full_height = ((box->descendant_y1 - box->border[BOTTOM].width) >
+ visible_height) ?
+ box->descendant_y1 + box->padding[BOTTOM] :
+ visible_height;
+
+ if (right) {
+ if (box->scroll_y == NULL) {
+ data = malloc(sizeof(struct html_scrollbar_data));
+ if (data == NULL) {
+ NSLOG(netsurf, INFO, "malloc failed");
+ guit->misc->warning("NoMemory", 0);
+ return false;
+ }
+ data->c = c;
+ data->box = box;
+ if (scrollbar_create(false, visible_height,
+ full_height, visible_height,
+ data, html_overflow_scroll_callback,
+ &(box->scroll_y)) != NSERROR_OK) {
+ return false;
+ }
+ } else {
+ scrollbar_set_extents(box->scroll_y, visible_height,
+ visible_height, full_height);
+ }
+ }
+ if (bottom) {
+ if (box->scroll_x == NULL) {
+ data = malloc(sizeof(struct html_scrollbar_data));
+ if (data == NULL) {
+ NSLOG(netsurf, INFO, "malloc failed");
+ guit->misc->warning("NoMemory", 0);
+ return false;
+ }
+ data->c = c;
+ data->box = box;
+ if (scrollbar_create(true,
+ visible_width -
+ (right ? SCROLLBAR_WIDTH : 0),
+ full_width, visible_width,
+ data, html_overflow_scroll_callback,
+ &box->scroll_x) != NSERROR_OK) {
+ return false;
+ }
+ } else {
+ scrollbar_set_extents(box->scroll_x,
+ visible_width -
+ (right ? SCROLLBAR_WIDTH : 0),
+ visible_width, full_width);
+ }
+ }
+
+ if (right && bottom)
+ scrollbar_make_pair(box->scroll_x, box->scroll_y);
+
+ return true;
+}
+
+/**
+ * Determine if a box has a vertical scrollbar.
+ *
+ * \param box scrolling box
+ * \return the box has a vertical scrollbar
+ */
+
+bool box_vscrollbar_present(const struct box * const box)
+{
+ return box->padding[TOP] + box->height + box->padding[BOTTOM] +
+ box->border[BOTTOM].width < box->descendant_y1;
+}
+
+
+/**
+ * Determine if a box has a horizontal scrollbar.
+ *
+ * \param box scrolling box
+ * \return the box has a horizontal scrollbar
+ */
+
+bool box_hscrollbar_present(const struct box * const box)
+{
+ return box->padding[LEFT] + box->width + box->padding[RIGHT] +
+ box->border[RIGHT].width < box->descendant_x1;
+}
diff --git a/content/handlers/html/box.h b/content/handlers/html/box.h
new file mode 100644
index 0000000..f096b67
--- /dev/null
+++ b/content/handlers/html/box.h
@@ -0,0 +1,369 @@
+/*
+ * Copyright 2005 James Bursa <bursa(a)users.sourceforge.net>
+ * Copyright 2003 Phil Mellor <monkeyson(a)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
+ * Box tree construction and manipulation (interface).
+ *
+ * This stage of rendering converts a tree of dom_nodes (produced by libdom)
+ * to a tree of struct box. The box tree represents the structure of the
+ * document as given by the CSS display and float properties.
+ *
+ * For example, consider the following HTML:
+ * \code
+ * <h1>Example Heading</h1>
+ * <p>Example paragraph <em>with emphasised text</em> etc.</p> \endcode
+ *
+ * This would produce approximately the following box tree with default CSS
+ * rules:
+ * \code
+ * BOX_BLOCK (corresponds to h1)
+ * BOX_INLINE_CONTAINER
+ * BOX_INLINE "Example Heading"
+ * BOX_BLOCK (p)
+ * BOX_INLINE_CONTAINER
+ * BOX_INLINE "Example paragraph "
+ * BOX_INLINE "with emphasised text" (em)
+ * BOX_INLINE "etc." \endcode
+ *
+ * Note that the em has been collapsed into the INLINE_CONTAINER.
+ *
+ * If these CSS rules were applied:
+ * \code
+ * h1 { display: table-cell }
+ * p { display: table-cell }
+ * em { float: left; width: 5em } \endcode
+ *
+ * then the box tree would instead look like this:
+ * \code
+ * BOX_TABLE
+ * BOX_TABLE_ROW_GROUP
+ * BOX_TABLE_ROW
+ * BOX_TABLE_CELL (h1)
+ * BOX_INLINE_CONTAINER
+ * BOX_INLINE "Example Heading"
+ * BOX_TABLE_CELL (p)
+ * BOX_INLINE_CONTAINER
+ * BOX_INLINE "Example paragraph "
+ * BOX_FLOAT_LEFT (em)
+ * BOX_BLOCK
+ * BOX_INLINE_CONTAINER
+ * BOX_INLINE "with emphasised text"
+ * BOX_INLINE "etc." \endcode
+ *
+ * Here implied boxes have been added and a float is present.
+ *
+ * A box tree is "normalized" if the following is satisfied:
+ * \code
+ * parent permitted child nodes
+ * BLOCK, INLINE_BLOCK BLOCK, INLINE_CONTAINER, TABLE
+ * INLINE_CONTAINER INLINE, INLINE_BLOCK, FLOAT_LEFT, FLOAT_RIGHT, BR, TEXT,
+ * INLINE_END
+ * INLINE none
+ * TABLE at least 1 TABLE_ROW_GROUP
+ * TABLE_ROW_GROUP at least 1 TABLE_ROW
+ * TABLE_ROW at least 1 TABLE_CELL
+ * TABLE_CELL BLOCK, INLINE_CONTAINER, TABLE (same as BLOCK)
+ * FLOAT_(LEFT|RIGHT) exactly 1 BLOCK or TABLE
+ * \endcode
+ */
+
+#ifndef NETSURF_HTML_BOX_H
+#define NETSURF_HTML_BOX_H
+
+#include <limits.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <libcss/libcss.h>
+
+#include "content/handlers/css/utils.h"
+
+struct content;
+struct box;
+struct browser_window;
+struct column;
+struct object_params;
+struct object_param;
+struct html_content;
+struct nsurl;
+struct dom_node;
+struct dom_string;
+struct rect;
+
+#define UNKNOWN_WIDTH INT_MAX
+#define UNKNOWN_MAX_WIDTH INT_MAX
+
+typedef void (*box_construct_complete_cb)(struct html_content *c, bool success);
+
+/** Type of a struct box. */
+typedef enum {
+ BOX_BLOCK, BOX_INLINE_CONTAINER, BOX_INLINE,
+ BOX_TABLE, BOX_TABLE_ROW, BOX_TABLE_CELL,
+ BOX_TABLE_ROW_GROUP,
+ BOX_FLOAT_LEFT, BOX_FLOAT_RIGHT,
+ BOX_INLINE_BLOCK, BOX_BR, BOX_TEXT,
+ BOX_INLINE_END, BOX_NONE
+} box_type;
+
+
+/** Flags for a struct box. */
+typedef enum {
+ NEW_LINE = 1 << 0, /* first inline on a new line */
+ STYLE_OWNED = 1 << 1, /* style is owned by this box */
+ PRINTED = 1 << 2, /* box has already been printed */
+ PRE_STRIP = 1 << 3, /* PRE tag needing leading newline stripped */
+ CLONE = 1 << 4, /* continuation of previous box from wrapping */
+ MEASURED = 1 << 5, /* text box width has been measured */
+ HAS_HEIGHT = 1 << 6, /* box has height (perhaps due to children) */
+ MAKE_HEIGHT = 1 << 7, /* box causes its own height */
+ NEED_MIN = 1 << 8, /* minimum width is required for layout */
+ REPLACE_DIM = 1 << 9, /* replaced element has given dimensions */
+ IFRAME = 1 << 10, /* box contains an iframe */
+ CONVERT_CHILDREN = 1 << 11, /* wanted children converting */
+ IS_REPLACED = 1 << 12 /* box is a replaced element */
+} box_flags;
+
+/* Sides of a box */
+enum box_side { TOP, RIGHT, BOTTOM, LEFT };
+
+/**
+ * Container for box border details
+ */
+struct box_border {
+ enum css_border_style_e style; /**< border-style */
+ css_color c; /**< border-color value */
+ int width; /**< border-width (pixels) */
+};
+
+/** Node in box tree. All dimensions are in pixels. */
+struct box {
+ /** Type of box. */
+ box_type type;
+
+ /** Box flags */
+ box_flags flags;
+
+ /** Computed styles for elements and their pseudo elements. NULL on
+ * non-element boxes. */
+ css_select_results *styles;
+
+ /** Style for this box. 0 for INLINE_CONTAINER and FLOAT_*. Pointer into
+ * a box's 'styles' select results, except for implied boxes, where it
+ * is a pointer to an owned computed style. */
+ css_computed_style *style;
+
+ /** Coordinate of left padding edge relative to parent box, or relative
+ * to ancestor that contains this box in float_children for FLOAT_. */
+ int x;
+ /** Coordinate of top padding edge, relative as for x. */
+ int y;
+
+ int width; /**< Width of content box (excluding padding etc.). */
+ int height; /**< Height of content box (excluding padding etc.). */
+
+ /* These four variables determine the maximum extent of a box's
+ * descendants. They are relative to the x,y coordinates of the box.
+ *
+ * Their use depends on the overflow CSS property:
+ *
+ * Overflow: Usage:
+ * visible The content of the box is displayed within these
+ * dimensions.
+ * hidden These are ignored. Content is plotted within the box
+ * dimensions.
+ * scroll These are used to determine the extent of the
+ * scrollable area.
+ * auto As "scroll".
+ */
+ int descendant_x0; /**< left edge of descendants */
+ int descendant_y0; /**< top edge of descendants */
+ int descendant_x1; /**< right edge of descendants */
+ int descendant_y1; /**< bottom edge of descendants */
+
+ int margin[4]; /**< Margin: TOP, RIGHT, BOTTOM, LEFT. */
+ int padding[4]; /**< Padding: TOP, RIGHT, BOTTOM, LEFT. */
+ struct box_border border[4]; /**< Border: TOP, RIGHT, BOTTOM, LEFT. */
+
+ struct scrollbar *scroll_x; /**< Horizontal scroll. */
+ struct scrollbar *scroll_y; /**< Vertical scroll. */
+
+ /** Width of box taking all line breaks (including margins etc). Must
+ * be non-negative. */
+ int min_width;
+ /** Width that would be taken with no line breaks. Must be
+ * non-negative. */
+ int max_width;
+
+ /**< Byte offset within a textual representation of this content. */
+ size_t byte_offset;
+
+ char *text; /**< Text, or 0 if none. Unterminated. */
+ size_t length; /**< Length of text. */
+
+ /** Width of space after current text (depends on font and size). */
+ int space;
+
+ struct nsurl *href; /**< Link, or 0. */
+ const char *target; /**< Link target, or 0. */
+ const char *title; /**< Title, or 0. */
+
+ unsigned int columns; /**< Number of columns for TABLE / TABLE_CELL. */
+ unsigned int rows; /**< Number of rows for TABLE only. */
+ unsigned int start_column; /**< Start column for TABLE_CELL only. */
+
+ struct box *next; /**< Next sibling box, or 0. */
+ struct box *prev; /**< Previous sibling box, or 0. */
+ struct box *children; /**< First child box, or 0. */
+ struct box *last; /**< Last child box, or 0. */
+ struct box *parent; /**< Parent box, or 0. */
+ /** INLINE_END box corresponding to this INLINE box, or INLINE box
+ * corresponding to this INLINE_END box. */
+ struct box *inline_end;
+
+ /** First float child box, or 0. Float boxes are in the tree twice, in
+ * this list for the block box which defines the area for floats, and
+ * also in the standard tree given by children, next, prev, etc. */
+ struct box *float_children;
+ /** Next sibling float box. */
+ struct box *next_float;
+ /** If box is a float, points to box's containing block */
+ struct box *float_container;
+ /** Level below which subsequent floats must be cleared.
+ * This is used only for boxes with float_children */
+ int clear_level;
+
+ /* Level below which floats have been placed. */
+ int cached_place_below_level;
+
+ /** List marker box if this is a list-item, or 0. */
+ struct box *list_marker;
+
+ struct column *col; /**< Array of table column data for TABLE only. */
+
+ /** Form control data, or 0 if not a form control. */
+ struct form_control* gadget;
+
+ char *usemap; /** (Image)map to use with this object, or 0 if none */
+ lwc_string *id; /**< value of id attribute (or name for anchors) */
+
+ /** Background image for this box, or 0 if none */
+ struct hlcache_handle *background;
+
+ /** Object in this box (usually an image), or 0 if none. */
+ struct hlcache_handle* object;
+ /** Parameters for the object, or 0. */
+ struct object_params *object_params;
+
+ /** Iframe's browser_window, or NULL if none */
+ struct browser_window *iframe;
+
+ struct dom_node *node; /**< DOM node that generated this box or NULL */
+};
+
+/** Table column data. */
+struct column {
+ /** Type of column. */
+ enum { COLUMN_WIDTH_UNKNOWN, COLUMN_WIDTH_FIXED,
+ COLUMN_WIDTH_AUTO, COLUMN_WIDTH_PERCENT,
+ COLUMN_WIDTH_RELATIVE } type;
+ /** Preferred width of column. Pixels for FIXED, percentage for PERCENT,
+ * relative units for RELATIVE, unused for AUTO. */
+ int width;
+ /** Minimum width of content. */
+ int min;
+ /** Maximum width of content. */
+ int max;
+ /** Whether all of column's cells are css positioned. */
+ bool positioned;
+};
+
+/** Parameters for object element and similar elements. */
+struct object_params {
+ struct nsurl *data;
+ char *type;
+ char *codetype;
+ struct nsurl *codebase;
+ struct nsurl *classid;
+ struct object_param *params;
+};
+
+/** Linked list of object element parameters. */
+struct object_param {
+ char *name;
+ char *value;
+ char *type;
+ char *valuetype;
+ struct object_param *next;
+};
+
+/** Frame target names (constant pointers to save duplicating the strings many
+ * times). We convert _blank to _top for user-friendliness. */
+extern const char *TARGET_SELF;
+extern const char *TARGET_PARENT;
+extern const char *TARGET_TOP;
+extern const char *TARGET_BLANK;
+
+
+
+struct box * box_create(css_select_results *styles, css_computed_style *style,
+ bool style_owned, struct nsurl *href, const char *target,
+ const char *title, lwc_string *id, void *context);
+void box_add_child(struct box *parent, struct box *child);
+void box_insert_sibling(struct box *box, struct box *new_box);
+void box_unlink_and_free(struct box *box);
+void box_free(struct box *box);
+void box_free_box(struct box *box);
+void box_bounds(struct box *box, struct rect *r);
+void box_coords(struct box *box, int *x, int *y);
+struct box *box_at_point(
+ const nscss_len_ctx *len_ctx,
+ struct box *box, const int x, const int y,
+ int *box_x, int *box_y);
+struct box *box_pick_text_box(struct html_content *html,
+ int x, int y, int dir, int *dx, int *dy);
+struct box *box_find_by_id(struct box *box, lwc_string *id);
+bool box_visible(struct box *box);
+void box_dump(FILE *stream, struct box *box, unsigned int depth, bool style);
+
+/**
+ * Extract a URL from a relative link, handling junk like whitespace and
+ * attempting to read a real URL from "javascript:" links.
+ *
+ * \param content html content
+ * \param dsrel relative URL text taken from page
+ * \param base base for relative URLs
+ * \param result updated to target URL on heap, unchanged if extract failed
+ * \return true on success, false on memory exhaustion
+ */
+bool box_extract_link(const struct html_content *content, const struct dom_string *dsrel, struct nsurl *base, struct nsurl **result);
+
+bool box_handle_scrollbars(struct content *c, struct box *box,
+ bool bottom, bool right);
+bool box_vscrollbar_present(const struct box *box);
+bool box_hscrollbar_present(const struct box *box);
+
+nserror dom_to_box(struct dom_node *n, struct html_content *c,
+ box_construct_complete_cb cb);
+
+bool box_normalise_block(
+ struct box *block,
+ const struct box *root,
+ struct html_content *c);
+
+#endif
diff --git a/content/handlers/html/box_construct.c b/content/handlers/html/box_construct.c
new file mode 100644
index 0000000..9c19391
--- /dev/null
+++ b/content/handlers/html/box_construct.c
@@ -0,0 +1,3134 @@
+/*
+ * Copyright 2005 James Bursa <bursa(a)users.sourceforge.net>
+ * Copyright 2003 Phil Mellor <monkeyson(a)users.sourceforge.net>
+ * Copyright 2005 John M Bell <jmb202(a)ecs.soton.ac.uk>
+ * Copyright 2006 Richard Wilson <info(a)tinct.net>
+ * Copyright 2008 Michael Drake <tlsa(a)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
+ * Implementation of conversion from DOM tree to box tree.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+
+#include "utils/config.h"
+#include "utils/nsoption.h"
+#include "utils/corestrings.h"
+#include "utils/log.h"
+#include "utils/messages.h"
+#include "utils/talloc.h"
+#include "utils/string.h"
+#include "utils/ascii.h"
+#include "netsurf/css.h"
+#include "netsurf/misc.h"
+#include "netsurf/plot_style.h"
+#include "content/content_protected.h"
+#include "css/hints.h"
+#include "css/select.h"
+#include "css/utils.h"
+#include "desktop/gui_internal.h"
+
+#include "html/box.h"
+#include "html/box_textarea.h"
+#include "html/form_internal.h"
+#include "html/html_internal.h"
+
+/**
+ * Context for box tree construction
+ */
+struct box_construct_ctx {
+ html_content *content; /**< Content we're constructing for */
+
+ dom_node *n; /**< Current node to process */
+
+ struct box *root_box; /**< Root box in the tree */
+
+ box_construct_complete_cb cb; /**< Callback to invoke on completion */
+
+ int *bctx; /**< talloc context */
+};
+
+/**
+ * Transient properties for construction of current node
+ */
+struct box_construct_props {
+ /** Style from which to inherit, or NULL if none */
+ const css_computed_style *parent_style;
+ /** Current link target, or NULL if none */
+ nsurl *href;
+ /** Current frame target, or NULL if none */
+ const char *target;
+ /** Current title attribute, or NULL if none */
+ const char *title;
+ /** Identity of the current block-level container */
+ struct box *containing_block;
+ /** Current container for inlines, or NULL if none
+ * \note If non-NULL, will be the last child of containing_block */
+ struct box *inline_container;
+ /** Whether the current node is the root of the DOM tree */
+ bool node_is_root;
+};
+
+static const content_type image_types = CONTENT_IMAGE;
+
+/* the strings are not important, since we just compare the pointers */
+const char *TARGET_SELF = "_self";
+const char *TARGET_PARENT = "_parent";
+const char *TARGET_TOP = "_top";
+const char *TARGET_BLANK = "_blank";
+
+static void convert_xml_to_box(struct box_construct_ctx *ctx);
+static bool box_construct_element(struct box_construct_ctx *ctx,
+ bool *convert_children);
+static void box_construct_element_after(dom_node *n, html_content *content);
+static bool box_construct_text(struct box_construct_ctx *ctx);
+static css_select_results * box_get_style(html_content *c,
+ const css_computed_style *parent_style,
+ const css_computed_style *root_style, dom_node *n);
+static void box_text_transform(char *s, unsigned int len,
+ enum css_text_transform_e tt);
+#define BOX_SPECIAL_PARAMS dom_node *n, html_content *content, \
+ struct box *box, bool *convert_children
+static bool box_a(BOX_SPECIAL_PARAMS);
+static bool box_body(BOX_SPECIAL_PARAMS);
+static bool box_br(BOX_SPECIAL_PARAMS);
+static bool box_image(BOX_SPECIAL_PARAMS);
+static bool box_textarea(BOX_SPECIAL_PARAMS);
+static bool box_select(BOX_SPECIAL_PARAMS);
+static bool box_input(BOX_SPECIAL_PARAMS);
+static bool box_button(BOX_SPECIAL_PARAMS);
+static bool box_frameset(BOX_SPECIAL_PARAMS);
+static bool box_create_frameset(struct content_html_frames *f, dom_node *n,
+ html_content *content);
+static bool box_select_add_option(struct form_control *control, dom_node *n);
+static bool box_noscript(BOX_SPECIAL_PARAMS);
+static bool box_object(BOX_SPECIAL_PARAMS);
+static bool box_embed(BOX_SPECIAL_PARAMS);
+static bool box_pre(BOX_SPECIAL_PARAMS);
+static bool box_iframe(BOX_SPECIAL_PARAMS);
+static bool box_get_attribute(dom_node *n, const char *attribute,
+ void *context, char **value);
+
+/* element_table must be sorted by name */
+struct element_entry {
+ char name[10]; /* element type */
+ bool (*convert)(BOX_SPECIAL_PARAMS);
+};
+static const struct element_entry element_table[] = {
+ {"a", box_a},
+ {"body", box_body},
+ {"br", box_br},
+ {"button", box_button},
+ {"embed", box_embed},
+ {"frameset", box_frameset},
+ {"iframe", box_iframe},
+ {"image", box_image},
+ {"img", box_image},
+ {"input", box_input},
+ {"noscript", box_noscript},
+ {"object", box_object},
+ {"pre", box_pre},
+ {"select", box_select},
+ {"textarea", box_textarea}
+};
+#define ELEMENT_TABLE_COUNT (sizeof(element_table) / sizeof(element_table[0]))
+
+/**
+ * Construct a box tree from an xml tree and stylesheets.
+ *
+ * \param n xml tree
+ * \param c content of type CONTENT_HTML to construct box tree in
+ * \param cb callback to report conversion completion
+ * \return netsurf error code indicating status of call
+ */
+
+nserror dom_to_box(dom_node *n, html_content *c, box_construct_complete_cb cb)
+{
+ struct box_construct_ctx *ctx;
+
+ if (c->bctx == NULL) {
+ /* create a context allocation for this box tree */
+ c->bctx = talloc_zero(0, int);
+ if (c->bctx == NULL) {
+ return NSERROR_NOMEM;
+ }
+ }
+
+ ctx = malloc(sizeof(*ctx));
+ if (ctx == NULL) {
+ return NSERROR_NOMEM;
+ }
+
+ ctx->content = c;
+ ctx->n = dom_node_ref(n);
+ ctx->root_box = NULL;
+ ctx->cb = cb;
+ ctx->bctx = c->bctx;
+
+ return guit->misc->schedule(0, (void *)convert_xml_to_box, ctx);
+}
+
+/* mapping from CSS display to box type
+ * this table must be in sync with libcss' css_display enum */
+static const box_type box_map[] = {
+ 0, /*CSS_DISPLAY_INHERIT,*/
+ BOX_INLINE, /*CSS_DISPLAY_INLINE,*/
+ BOX_BLOCK, /*CSS_DISPLAY_BLOCK,*/
+ BOX_BLOCK, /*CSS_DISPLAY_LIST_ITEM,*/
+ BOX_INLINE, /*CSS_DISPLAY_RUN_IN,*/
+ BOX_INLINE_BLOCK, /*CSS_DISPLAY_INLINE_BLOCK,*/
+ BOX_TABLE, /*CSS_DISPLAY_TABLE,*/
+ BOX_TABLE, /*CSS_DISPLAY_INLINE_TABLE,*/
+ BOX_TABLE_ROW_GROUP, /*CSS_DISPLAY_TABLE_ROW_GROUP,*/
+ BOX_TABLE_ROW_GROUP, /*CSS_DISPLAY_TABLE_HEADER_GROUP,*/
+ BOX_TABLE_ROW_GROUP, /*CSS_DISPLAY_TABLE_FOOTER_GROUP,*/
+ BOX_TABLE_ROW, /*CSS_DISPLAY_TABLE_ROW,*/
+ BOX_NONE, /*CSS_DISPLAY_TABLE_COLUMN_GROUP,*/
+ BOX_NONE, /*CSS_DISPLAY_TABLE_COLUMN,*/
+ BOX_TABLE_CELL, /*CSS_DISPLAY_TABLE_CELL,*/
+ BOX_INLINE, /*CSS_DISPLAY_TABLE_CAPTION,*/
+ BOX_NONE /*CSS_DISPLAY_NONE*/
+};
+
+static inline struct box *box_for_node(dom_node *n)
+{
+ struct box *box = NULL;
+ dom_exception err;
+
+ err = dom_node_get_user_data(n, corestring_dom___ns_key_box_node_data,
+ (void *) &box);
+ if (err != DOM_NO_ERR)
+ return NULL;
+
+ return box;
+}
+
+static inline bool box_is_root(dom_node *n)
+{
+ dom_node *parent;
+ dom_node_type type;
+ dom_exception err;
+
+ err = dom_node_get_parent_node(n, &parent);
+ if (err != DOM_NO_ERR)
+ return false;
+
+ if (parent != NULL) {
+ err = dom_node_get_node_type(parent, &type);
+
+ dom_node_unref(parent);
+
+ if (err != DOM_NO_ERR)
+ return false;
+
+ if (type != DOM_DOCUMENT_NODE)
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Find the next node in the DOM tree, completing
+ * element construction where appropriate.
+ *
+ * \param n Current node
+ * \param content Containing content
+ * \param convert_children Whether to consider children of \a n
+ * \return Next node to process, or NULL if complete
+ *
+ * \note \a n will be unreferenced
+ */
+static dom_node *next_node(dom_node *n, html_content *content,
+ bool convert_children)
+{
+ dom_node *next = NULL;
+ bool has_children;
+ dom_exception err;
+
+ err = dom_node_has_child_nodes(n, &has_children);
+ if (err != DOM_NO_ERR) {
+ dom_node_unref(n);
+ return NULL;
+ }
+
+ if (convert_children && has_children) {
+ err = dom_node_get_first_child(n, &next);
+ if (err != DOM_NO_ERR) {
+ dom_node_unref(n);
+ return NULL;
+ }
+ dom_node_unref(n);
+ } else {
+ err = dom_node_get_next_sibling(n, &next);
+ if (err != DOM_NO_ERR) {
+ dom_node_unref(n);
+ return NULL;
+ }
+
+ if (next != NULL) {
+ if (box_for_node(n) != NULL)
+ box_construct_element_after(n, content);
+ dom_node_unref(n);
+ } else {
+ if (box_for_node(n) != NULL)
+ box_construct_element_after(n, content);
+
+ while (box_is_root(n) == false) {
+ dom_node *parent = NULL;
+ dom_node *parent_next = NULL;
+
+ err = dom_node_get_parent_node(n, &parent);
+ if (err != DOM_NO_ERR) {
+ dom_node_unref(n);
+ return NULL;
+ }
+
+ assert(parent != NULL);
+
+ err = dom_node_get_next_sibling(parent,
+ &parent_next);
+ if (err != DOM_NO_ERR) {
+ dom_node_unref(parent);
+ dom_node_unref(n);
+ return NULL;
+ }
+
+ if (parent_next != NULL) {
+ dom_node_unref(parent_next);
+ dom_node_unref(parent);
+ break;
+ }
+
+ dom_node_unref(n);
+ n = parent;
+ parent = NULL;
+
+ if (box_for_node(n) != NULL) {
+ box_construct_element_after(
+ n, content);
+ }
+ }
+
+ if (box_is_root(n) == false) {
+ dom_node *parent = NULL;
+
+ err = dom_node_get_parent_node(n, &parent);
+ if (err != DOM_NO_ERR) {
+ dom_node_unref(n);
+ return NULL;
+ }
+
+ assert(parent != NULL);
+
+ err = dom_node_get_next_sibling(parent, &next);
+ if (err != DOM_NO_ERR) {
+ dom_node_unref(parent);
+ dom_node_unref(n);
+ return NULL;
+ }
+
+ if (box_for_node(parent) != NULL) {
+ box_construct_element_after(parent,
+ content);
+ }
+
+ dom_node_unref(parent);
+ }
+
+ dom_node_unref(n);
+ }
+ }
+
+ return next;
+}
+
+/**
+ * Convert an ELEMENT node to a box tree fragment,
+ * then schedule conversion of the next ELEMENT node
+ */
+void convert_xml_to_box(struct box_construct_ctx *ctx)
+{
+ dom_node *next;
+ bool convert_children;
+ uint32_t num_processed = 0;
+ const uint32_t max_processed_before_yield = 10;
+
+ do {
+ convert_children = true;
+
+ assert(ctx->n != NULL);
+
+ if (box_construct_element(ctx, &convert_children) == false) {
+ ctx->cb(ctx->content, false);
+ dom_node_unref(ctx->n);
+ free(ctx);
+ return;
+ }
+
+ /* Find next element to process, converting text nodes as we go */
+ next = next_node(ctx->n, ctx->content, convert_children);
+ while (next != NULL) {
+ dom_node_type type;
+ dom_exception err;
+
+ err = dom_node_get_node_type(next, &type);
+ if (err != DOM_NO_ERR) {
+ ctx->cb(ctx->content, false);
+ dom_node_unref(next);
+ free(ctx);
+ return;
+ }
+
+ if (type == DOM_ELEMENT_NODE)
+ break;
+
+ if (type == DOM_TEXT_NODE) {
+ ctx->n = next;
+ if (box_construct_text(ctx) == false) {
+ ctx->cb(ctx->content, false);
+ dom_node_unref(ctx->n);
+ free(ctx);
+ return;
+ }
+ }
+
+ next = next_node(next, ctx->content, true);
+ }
+
+ ctx->n = next;
+
+ if (next == NULL) {
+ /* Conversion complete */
+ struct box root;
+
+ memset(&root, 0, sizeof(root));
+
+ root.type = BOX_BLOCK;
+ root.children = root.last = ctx->root_box;
+ root.children->parent = &root;
+
+ /** \todo Remove box_normalise_block */
+ if (box_normalise_block(&root, ctx->root_box,
+ ctx->content) == false) {
+ ctx->cb(ctx->content, false);
+ } else {
+ ctx->content->layout = root.children;
+ ctx->content->layout->parent = NULL;
+
+ ctx->cb(ctx->content, true);
+ }
+
+ assert(ctx->n == NULL);
+
+ free(ctx);
+ return;
+ }
+ } while (++num_processed < max_processed_before_yield);
+
+ /* More work to do: schedule a continuation */
+ guit->misc->schedule(0, (void *)convert_xml_to_box, ctx);
+}
+
+/**
+ * Construct a list marker box
+ *
+ * \param box Box to attach marker to
+ * \param title Current title attribute
+ * \param ctx Box construction context
+ * \param parent Current block-level container
+ * \return true on success, false on memory exhaustion
+ */
+static bool box_construct_marker(struct box *box, const char *title,
+ struct box_construct_ctx *ctx, struct box *parent)
+{
+ lwc_string *image_uri;
+ struct box *marker;
+
+ marker = box_create(NULL, box->style, false, NULL, NULL, title,
+ NULL, ctx->bctx);
+ if (marker == false)
+ return false;
+
+ marker->type = BOX_BLOCK;
+
+ /** \todo marker content (list-style-type) */
+ switch (css_computed_list_style_type(box->style)) {
+ case CSS_LIST_STYLE_TYPE_DISC:
+ /* 2022 BULLET */
+ marker->text = (char *) "\342\200\242";
+ marker->length = 3;
+ break;
+ case CSS_LIST_STYLE_TYPE_CIRCLE:
+ /* 25CB WHITE CIRCLE */
+ marker->text = (char *) "\342\227\213";
+ marker->length = 3;
+ break;
+ case CSS_LIST_STYLE_TYPE_SQUARE:
+ /* 25AA BLACK SMALL SQUARE */
+ marker->text = (char *) "\342\226\252";
+ marker->length = 3;
+ break;
+ case CSS_LIST_STYLE_TYPE_DECIMAL:
+ case CSS_LIST_STYLE_TYPE_LOWER_ALPHA:
+ case CSS_LIST_STYLE_TYPE_LOWER_ROMAN:
+ case CSS_LIST_STYLE_TYPE_UPPER_ALPHA:
+ case CSS_LIST_STYLE_TYPE_UPPER_ROMAN:
+ default:
+ if (parent->last) {
+ struct box *last = parent->last;
+
+ /* Drill down into last child of parent
+ * to find the list marker (if any)
+ *
+ * Floated list boxes end up as:
+ *
+ * parent
+ * BOX_INLINE_CONTAINER
+ * BOX_FLOAT_{LEFT,RIGHT}
+ * BOX_BLOCK <-- list box
+ * ...
+ */
+ while (last != NULL && last->list_marker == NULL) {
+ struct box *last_inner = last;
+
+ while (last_inner != NULL) {
+ if (last_inner->list_marker != NULL)
+ break;
+ if (last_inner->type ==
+ BOX_INLINE_CONTAINER ||
+ last_inner->type ==
+ BOX_FLOAT_LEFT ||
+ last_inner->type ==
+ BOX_FLOAT_RIGHT) {
+ last_inner = last_inner->last;
+ } else {
+ last_inner = NULL;
+ }
+ }
+ if (last_inner != NULL) {
+ last = last_inner;
+ } else {
+ last = last->prev;
+ }
+ }
+
+ if (last && last->list_marker) {
+ marker->rows = last->list_marker->rows + 1;
+ }
+ }
+
+ marker->text = talloc_array(ctx->bctx, char, 20);
+ if (marker->text == NULL)
+ return false;
+
+ snprintf(marker->text, 20, "%u.", marker->rows);
+ marker->length = strlen(marker->text);
+ break;
+ case CSS_LIST_STYLE_TYPE_NONE:
+ marker->text = 0;
+ marker->length = 0;
+ break;
+ }
+
+ if (css_computed_list_style_image(box->style, &image_uri) == CSS_LIST_STYLE_IMAGE_URI &&
+ (image_uri != NULL) &&
+ (nsoption_bool(foreground_images) == true)) {
+ nsurl *url;
+ nserror error;
+
+ /* TODO: we get a url out of libcss as a lwc string, but
+ * earlier we already had it as a nsurl after we
+ * nsurl_joined it. Can this be improved?
+ * For now, just making another nsurl. */
+ error = nsurl_create(lwc_string_data(image_uri), &url);
+ if (error != NSERROR_OK)
+ return false;
+
+ if (html_fetch_object(ctx->content, url, marker, image_types,
+ ctx->content->base.available_width, 1000, false) ==
+ false) {
+ nsurl_unref(url);
+ return false;
+ }
+ nsurl_unref(url);
+ }
+
+ box->list_marker = marker;
+ marker->parent = box;
+
+ return true;
+}
+
+/**
+ * Construct the box required for a generated element.
+ *
+ * \param n XML node of type XML_ELEMENT_NODE
+ * \param content Content of type CONTENT_HTML that is being processed
+ * \param box Box which may have generated content
+ * \param style Complete computed style for pseudo element, or NULL
+ *
+ * TODO:
+ * This is currently incomplete. It just does enough to support the clearfix
+ * hack. ( http://www.positioniseverything.net/easyclearing.html )
+ */
+static void box_construct_generate(dom_node *n, html_content *content,
+ struct box *box, const css_computed_style *style)
+{
+ struct box *gen = NULL;
+ enum css_display_e computed_display;
+ const css_computed_content_item *c_item;
+
+ /* Nothing to generate if the parent box is not a block */
+ if (box->type != BOX_BLOCK)
+ return;
+
+ /* To determine if an element has a pseudo element, we select
+ * for it and test to see if the returned style's content
+ * property is set to normal. */
+ if (style == NULL ||
+ css_computed_content(style, &c_item) ==
+ CSS_CONTENT_NORMAL) {
+ /* No pseudo element */
+ return;
+ }
+
+ /* create box for this element */
+ computed_display = ns_computed_display(style, box_is_root(n));
+ if (computed_display == CSS_DISPLAY_BLOCK ||
+ computed_display == CSS_DISPLAY_TABLE) {
+ /* currently only support block level boxes */
+
+ /** \todo Not wise to drop const from the computed style */
+ gen = box_create(NULL, (css_computed_style *) style,
+ false, NULL, NULL, NULL, NULL, content->bctx);
+ if (gen == NULL) {
+ return;
+ }
+
+ /* set box type from computed display */
+ gen->type = box_map[ns_computed_display(
+ style, box_is_root(n))];
+
+ box_add_child(box, gen);
+ }
+}
+
+/**
+ * Extract transient construction properties
+ *
+ * \param n Current DOM node to convert
+ * \param props Property object to populate
+ */
+static void box_extract_properties(dom_node *n,
+ struct box_construct_props *props)
+{
+ memset(props, 0, sizeof(*props));
+
+ props->node_is_root = box_is_root(n);
+
+ /* Extract properties from containing DOM node */
+ if (props->node_is_root == false) {
+ dom_node *current_node = n;
+ dom_node *parent_node = NULL;
+ struct box *parent_box;
+ dom_exception err;
+
+ /* Find ancestor node containing parent box */
+ while (true) {
+ err = dom_node_get_parent_node(current_node,
+ &parent_node);
+ if (err != DOM_NO_ERR || parent_node == NULL)
+ break;
+
+ parent_box = box_for_node(parent_node);
+
+ if (parent_box != NULL) {
+ props->parent_style = parent_box->style;
+ props->href = parent_box->href;
+ props->target = parent_box->target;
+ props->title = parent_box->title;
+
+ dom_node_unref(parent_node);
+ break;
+ } else {
+ if (current_node != n)
+ dom_node_unref(current_node);
+ current_node = parent_node;
+ parent_node = NULL;
+ }
+ }
+
+ /* Find containing block (may be parent) */
+ while (true) {
+ struct box *b;
+
+ err = dom_node_get_parent_node(current_node,
+ &parent_node);
+ if (err != DOM_NO_ERR || parent_node == NULL) {
+ if (current_node != n)
+ dom_node_unref(current_node);
+ break;
+ }
+
+ if (current_node != n)
+ dom_node_unref(current_node);
+
+ b = box_for_node(parent_node);
+
+ /* Children of nodes that created an inline box
+ * will generate boxes which are attached as
+ * _siblings_ of the box generated for their
+ * parent node. Note, however, that we'll still
+ * use the parent node's styling as the parent
+ * style, above. */
+ if (b != NULL && b->type != BOX_INLINE &&
+ b->type != BOX_BR) {
+ props->containing_block = b;
+
+ dom_node_unref(parent_node);
+ break;
+ } else {
+ current_node = parent_node;
+ parent_node = NULL;
+ }
+ }
+ }
+
+ /* Compute current inline container, if any */
+ if (props->containing_block != NULL &&
+ props->containing_block->last != NULL &&
+ props->containing_block->last->type ==
+ BOX_INLINE_CONTAINER)
+ props->inline_container = props->containing_block->last;
+}
+
+/**
+ * Construct the box tree for an XML element.
+ *
+ * \param ctx Tree construction context
+ * \param convert_children Whether to convert children
+ * \return true on success, false on memory exhaustion
+ */
+
+bool box_construct_element(struct box_construct_ctx *ctx,
+ bool *convert_children)
+{
+ dom_string *title0, *s;
+ lwc_string *id = NULL;
+ struct box *box = NULL, *old_box;
+ css_select_results *styles = NULL;
+ struct element_entry *element;
+ lwc_string *bgimage_uri;
+ dom_exception err;
+ struct box_construct_props props;
+ const css_computed_style *root_style = NULL;
+
+ assert(ctx->n != NULL);
+
+ box_extract_properties(ctx->n, &props);
+
+ if (props.containing_block != NULL) {
+ /* In case the containing block is a pre block, we clear
+ * the PRE_STRIP flag since it is not used if we follow
+ * the pre with a tag */
+ props.containing_block->flags &= ~PRE_STRIP;
+ }
+
+ if (props.node_is_root == false) {
+ root_style = ctx->root_box->style;
+ }
+
+ styles = box_get_style(ctx->content, props.parent_style, root_style,
+ ctx->n);
+ if (styles == NULL)
+ return false;
+
+ /* Extract title attribute, if present */
+ err = dom_element_get_attribute(ctx->n, corestring_dom_title, &title0);
+ if (err != DOM_NO_ERR)
+ return false;
+
+ if (title0 != NULL) {
+ char *t = squash_whitespace(dom_string_data(title0));
+
+ dom_string_unref(title0);
+
+ if (t == NULL)
+ return false;
+
+ props.title = talloc_strdup(ctx->bctx, t);
+
+ free(t);
+
+ if (props.title == NULL)
+ return false;
+ }
+
+ /* Extract id attribute, if present */
+ err = dom_element_get_attribute(ctx->n, corestring_dom_id, &s);
+ if (err != DOM_NO_ERR)
+ return false;
+
+ if (s != NULL) {
+ err = dom_string_intern(s, &id);
+ if (err != DOM_NO_ERR)
+ id = NULL;
+
+ dom_string_unref(s);
+ }
+
+ box = box_create(styles, styles->styles[CSS_PSEUDO_ELEMENT_NONE], false,
+ props.href, props.target, props.title, id,
+ ctx->bctx);
+ if (box == NULL)
+ return false;
+
+ /* If this is the root box, add it to the context */
+ if (props.node_is_root)
+ ctx->root_box = box;
+
+ /* Deal with colspan/rowspan */
+ err = dom_element_get_attribute(ctx->n, corestring_dom_colspan, &s);
+ if (err != DOM_NO_ERR)
+ return false;
+
+ if (s != NULL) {
+ const char *val = dom_string_data(s);
+
+ if ('0' <= val[0] && val[0] <= '9')
+ box->columns = strtol(val, NULL, 10);
+
+ dom_string_unref(s);
+ }
+
+ err = dom_element_get_attribute(ctx->n, corestring_dom_rowspan, &s);
+ if (err != DOM_NO_ERR)
+ return false;
+
+ if (s != NULL) {
+ const char *val = dom_string_data(s);
+
+ if ('0' <= val[0] && val[0] <= '9')
+ box->rows = strtol(val, NULL, 10);
+
+ dom_string_unref(s);
+ }
+
+ /* Set box type from computed display */
+ if ((css_computed_position(box->style) == CSS_POSITION_ABSOLUTE ||
+ css_computed_position(box->style) ==
+ CSS_POSITION_FIXED) &&
+ (ns_computed_display_static(box->style) ==
+ CSS_DISPLAY_INLINE ||
+ ns_computed_display_static(box->style) ==
+ CSS_DISPLAY_INLINE_BLOCK ||
+ ns_computed_display_static(box->style) ==
+ CSS_DISPLAY_INLINE_TABLE)) {
+ /* Special case for absolute positioning: make absolute inlines
+ * into inline block so that the boxes are constructed in an
+ * inline container as if they were not absolutely positioned.
+ * Layout expects and handles this. */
+ box->type = box_map[CSS_DISPLAY_INLINE_BLOCK];
+ } else if (props.node_is_root) {
+ /* Special case for root element: force it to BLOCK, or the
+ * rest of the layout will break. */
+ box->type = BOX_BLOCK;
+ } else {
+ /* Normal mapping */
+ box->type = box_map[ns_computed_display(box->style,
+ props.node_is_root)];
+ }
+
+ err = dom_node_get_node_name(ctx->n, &s);
+ if (err != DOM_NO_ERR || s == NULL)
+ return false;
+
+ /* Special elements */
+ element = bsearch(dom_string_data(s), element_table,
+ ELEMENT_TABLE_COUNT, sizeof(element_table[0]),
+ (int (*)(const void *, const void *)) strcasecmp);
+
+ dom_string_unref(s);
+
+ if (element != NULL) {
+ /* A special convert function exists for this element */
+ if (element->convert(ctx->n, ctx->content, box,
+ convert_children) == false)
+ return false;
+ }
+
+ /* Handle the :before pseudo element */
+ if (!(box->flags & IS_REPLACED)) {
+ box_construct_generate(ctx->n, ctx->content, box,
+ box->styles->styles[CSS_PSEUDO_ELEMENT_BEFORE]);
+ }
+
+ if (box->type == BOX_NONE || (ns_computed_display(box->style,
+ props.node_is_root) == CSS_DISPLAY_NONE &&
+ props.node_is_root == false)) {
+ css_select_results_destroy(styles);
+ box->styles = NULL;
+ box->style = NULL;
+
+ /* Invalidate associated gadget, if any */
+ if (box->gadget != NULL) {
+ box->gadget->box = NULL;
+ box->gadget = NULL;
+ }
+
+ /* Can't do this, because the lifetimes of boxes and gadgets
+ * are inextricably linked. Fortunately, talloc will save us
+ * (for now) */
+ /* box_free_box(box); */
+
+ *convert_children = false;
+
+ return true;
+ }
+
+ /* Attach DOM node to box */
+ err = dom_node_set_user_data(ctx->n,
+ corestring_dom___ns_key_box_node_data, box, NULL,
+ (void *) &old_box);
+ if (err != DOM_NO_ERR)
+ return false;
+
+ /* Attach box to DOM node */
+ box->node = dom_node_ref(ctx->n);
+
+ if (props.inline_container == NULL &&
+ (box->type == BOX_INLINE ||
+ box->type == BOX_BR ||
+ box->type == BOX_INLINE_BLOCK ||
+ css_computed_float(box->style) == CSS_FLOAT_LEFT ||
+ css_computed_float(box->style) == CSS_FLOAT_RIGHT) &&
+ props.node_is_root == false) {
+ /* Found an inline child of a block without a current container
+ * (i.e. this box is the first child of its parent, or was
+ * preceded by block-level siblings) */
+ assert(props.containing_block != NULL &&
+ "Box must have containing block.");
+
+ props.inline_container = box_create(NULL, NULL, false, NULL,
+ NULL, NULL, NULL, ctx->bctx);
+ if (props.inline_container == NULL)
+ return false;
+
+ props.inline_container->type = BOX_INLINE_CONTAINER;
+
+ box_add_child(props.containing_block, props.inline_container);
+ }
+
+ /* Kick off fetch for any background image */
+ if (css_computed_background_image(box->style, &bgimage_uri) ==
+ CSS_BACKGROUND_IMAGE_IMAGE && bgimage_uri != NULL &&
+ nsoption_bool(background_images) == true) {
+ nsurl *url;
+ nserror error;
+
+ /* TODO: we get a url out of libcss as a lwc string, but
+ * earlier we already had it as a nsurl after we
+ * nsurl_joined it. Can this be improved?
+ * For now, just making another nsurl. */
+ error = nsurl_create(lwc_string_data(bgimage_uri), &url);
+ if (error == NSERROR_OK) {
+ /* Fetch image if we got a valid URL */
+ if (html_fetch_object(ctx->content, url, box,
+ image_types,
+ ctx->content->base.available_width,
+ 1000, true) == false) {
+ nsurl_unref(url);
+ return false;
+ }
+ nsurl_unref(url);
+ }
+ }
+
+ if (*convert_children)
+ box->flags |= CONVERT_CHILDREN;
+
+ if (box->type == BOX_INLINE || box->type == BOX_BR ||
+ box->type == BOX_INLINE_BLOCK) {
+ /* Inline container must exist, as we'll have
+ * created it above if it didn't */
+ assert(props.inline_container != NULL);
+
+ box_add_child(props.inline_container, box);
+ } else {
+ if (ns_computed_display(box->style, props.node_is_root) ==
+ CSS_DISPLAY_LIST_ITEM) {
+ /* List item: compute marker */
+ if (box_construct_marker(box, props.title, ctx,
+ props.containing_block) == false)
+ return false;
+ }
+
+ if (props.node_is_root == false &&
+ (css_computed_float(box->style) ==
+ CSS_FLOAT_LEFT ||
+ css_computed_float(box->style) ==
+ CSS_FLOAT_RIGHT)) {
+ /* Float: insert a float between the parent and box. */
+ struct box *flt = box_create(NULL, NULL, false,
+ props.href, props.target, props.title,
+ NULL, ctx->bctx);
+ if (flt == NULL)
+ return false;
+
+ if (css_computed_float(box->style) == CSS_FLOAT_LEFT)
+ flt->type = BOX_FLOAT_LEFT;
+ else
+ flt->type = BOX_FLOAT_RIGHT;
+
+ box_add_child(props.inline_container, flt);
+ box_add_child(flt, box);
+ } else {
+ /* Non-floated block-level box: add to containing block
+ * if there is one. If we're the root box, then there
+ * won't be. */
+ if (props.containing_block != NULL)
+ box_add_child(props.containing_block, box);
+ }
+ }
+
+ return true;
+}
+
+/**
+ * Complete construction of the box tree for an element.
+ *
+ * \param n DOM node to construct for
+ * \param content Containing document
+ *
+ * This will be called after all children of an element have been processed
+ */
+void box_construct_element_after(dom_node *n, html_content *content)
+{
+ struct box_construct_props props;
+ struct box *box = box_for_node(n);
+
+ assert(box != NULL);
+
+ box_extract_properties(n, &props);
+
+ if (box->type == BOX_INLINE || box->type == BOX_BR) {
+ /* Insert INLINE_END into containing block */
+ struct box *inline_end;
+ bool has_children;
+ dom_exception err;
+
+ err = dom_node_has_child_nodes(n, &has_children);
+ if (err != DOM_NO_ERR)
+ return;
+
+ if (has_children == false ||
+ (box->flags & CONVERT_CHILDREN) == 0) {
+ /* No children, or didn't want children converted */
+ return;
+ }
+
+ if (props.inline_container == NULL) {
+ /* Create inline container if we don't have one */
+ props.inline_container = box_create(NULL, NULL, false,
+ NULL, NULL, NULL, NULL, content->bctx);
+ if (props.inline_container == NULL)
+ return;
+
+ props.inline_container->type = BOX_INLINE_CONTAINER;
+
+ box_add_child(props.containing_block,
+ props.inline_container);
+ }
+
+ inline_end = box_create(NULL, box->style, false,
+ box->href, box->target, box->title,
+ box->id == NULL ? NULL :
+ lwc_string_ref(box->id), content->bctx);
+ if (inline_end != NULL) {
+ inline_end->type = BOX_INLINE_END;
+
+ assert(props.inline_container != NULL);
+
+ box_add_child(props.inline_container, inline_end);
+
+ box->inline_end = inline_end;
+ inline_end->inline_end = box;
+ }
+ } else if (!(box->flags & IS_REPLACED)) {
+ /* Handle the :after pseudo element */
+ box_construct_generate(n, content, box,
+ box->styles->styles[CSS_PSEUDO_ELEMENT_AFTER]);
+ }
+}
+
+/**
+ * Construct the box tree for an XML text node.
+ *
+ * \param ctx Tree construction context
+ * \return true on success, false on memory exhaustion
+ */
+
+bool box_construct_text(struct box_construct_ctx *ctx)
+{
+ struct box_construct_props props;
+ struct box *box = NULL;
+ dom_string *content;
+ dom_exception err;
+
+ assert(ctx->n != NULL);
+
+ box_extract_properties(ctx->n, &props);
+
+ assert(props.containing_block != NULL);
+
+ err = dom_characterdata_get_data(ctx->n, &content);
+ if (err != DOM_NO_ERR || content == NULL)
+ return false;
+
+ if (css_computed_white_space(props.parent_style) ==
+ CSS_WHITE_SPACE_NORMAL ||
+ css_computed_white_space(props.parent_style) ==
+ CSS_WHITE_SPACE_NOWRAP) {
+ char *text;
+
+ text = squash_whitespace(dom_string_data(content));
+
+ dom_string_unref(content);
+
+ if (text == NULL)
+ return false;
+
+ /* if the text is just a space, combine it with the preceding
+ * text node, if any */
+ if (text[0] == ' ' && text[1] == 0) {
+ if (props.inline_container != NULL) {
+ assert(props.inline_container->last != NULL);
+
+ props.inline_container->last->space =
+ UNKNOWN_WIDTH;
+ }
+
+ free(text);
+
+ return true;
+ }
+
+ if (props.inline_container == NULL) {
+ /* Child of a block without a current container
+ * (i.e. this box is the first child of its parent, or
+ * was preceded by block-level siblings) */
+ props.inline_container = box_create(NULL, NULL, false,
+ NULL, NULL, NULL, NULL, ctx->bctx);
+ if (props.inline_container == NULL) {
+ free(text);
+ return false;
+ }
+
+ props.inline_container->type = BOX_INLINE_CONTAINER;
+
+ box_add_child(props.containing_block,
+ props.inline_container);
+ }
+
+ /** \todo Dropping const here is not clever */
+ box = box_create(NULL,
+ (css_computed_style *) props.parent_style,
+ false, props.href, props.target, props.title,
+ NULL, ctx->bctx);
+ if (box == NULL) {
+ free(text);
+ return false;
+ }
+
+ box->type = BOX_TEXT;
+
+ box->text = talloc_strdup(ctx->bctx, text);
+ free(text);
+ if (box->text == NULL)
+ return false;
+
+ box->length = strlen(box->text);
+
+ /* strip ending space char off */
+ if (box->length > 1 && box->text[box->length - 1] == ' ') {
+ box->space = UNKNOWN_WIDTH;
+ box->length--;
+ }
+
+ if (css_computed_text_transform(props.parent_style) !=
+ CSS_TEXT_TRANSFORM_NONE)
+ box_text_transform(box->text, box->length,
+ css_computed_text_transform(
+ props.parent_style));
+
+ box_add_child(props.inline_container, box);
+
+ if (box->text[0] == ' ') {
+ box->length--;
+
+ memmove(box->text, &box->text[1], box->length);
+
+ if (box->prev != NULL)
+ box->prev->space = UNKNOWN_WIDTH;
+ }
+ } else {
+ /* white-space: pre */
+ char *text;
+ size_t text_len = dom_string_byte_length(content);
+ size_t i;
+ char *current;
+ enum css_white_space_e white_space =
+ css_computed_white_space(props.parent_style);
+
+ /* note: pre-wrap/pre-line are unimplemented */
+ assert(white_space == CSS_WHITE_SPACE_PRE ||
+ white_space == CSS_WHITE_SPACE_PRE_LINE ||
+ white_space == CSS_WHITE_SPACE_PRE_WRAP);
+
+ text = malloc(text_len + 1);
+ dom_string_unref(content);
+
+ if (text == NULL)
+ return false;
+
+ memcpy(text, dom_string_data(content), text_len);
+ text[text_len] = '\0';
+
+ /* TODO: Handle tabs properly */
+ for (i = 0; i < text_len; i++)
+ if (text[i] == '\t')
+ text[i] = ' ';
+
+ if (css_computed_text_transform(props.parent_style) !=
+ CSS_TEXT_TRANSFORM_NONE)
+ box_text_transform(text, strlen(text),
+ css_computed_text_transform(
+ props.parent_style));
+
+ current = text;
+
+ /* swallow a single leading new line */
+ if (props.containing_block->flags & PRE_STRIP) {
+ switch (*current) {
+ case '\n':
+ current++;
+ break;
+ case '\r':
+ current++;
+ if (*current == '\n')
+ current++;
+ break;
+ }
+ props.containing_block->flags &= ~PRE_STRIP;
+ }
+
+ do {
+ size_t len = strcspn(current, "\r\n");
+
+ char old = current[len];
+
+ current[len] = 0;
+
+ if (props.inline_container == NULL) {
+ /* Child of a block without a current container
+ * (i.e. this box is the first child of its
+ * parent, or was preceded by block-level
+ * siblings) */
+ props.inline_container = box_create(NULL, NULL,
+ false, NULL, NULL, NULL, NULL,
+ ctx->bctx);
+ if (props.inline_container == NULL) {
+ free(text);
+ return false;
+ }
+
+ props.inline_container->type =
+ BOX_INLINE_CONTAINER;
+
+ box_add_child(props.containing_block,
+ props.inline_container);
+ }
+
+ /** \todo Dropping const isn't clever */
+ box = box_create(NULL,
+ (css_computed_style *) props.parent_style,
+ false, props.href, props.target, props.title,
+ NULL, ctx->bctx);
+ if (box == NULL) {
+ free(text);
+ return false;
+ }
+
+ box->type = BOX_TEXT;
+
+ box->text = talloc_strdup(ctx->bctx, current);
+ if (box->text == NULL) {
+ free(text);
+ return false;
+ }
+
+ box->length = strlen(box->text);
+
+ box_add_child(props.inline_container, box);
+
+ current[len] = old;
+
+ current += len;
+
+ if (current[0] != '\0') {
+ /* Linebreak: create new inline container */
+ props.inline_container = box_create(NULL, NULL,
+ false, NULL, NULL, NULL, NULL,
+ ctx->bctx);
+ if (props.inline_container == NULL) {
+ free(text);
+ return false;
+ }
+
+ props.inline_container->type =
+ BOX_INLINE_CONTAINER;
+
+ box_add_child(props.containing_block,
+ props.inline_container);
+
+ if (current[0] == '\r' && current[1] == '\n')
+ current += 2;
+ else
+ current++;
+ }
+ } while (*current);
+
+ free(text);
+ }
+
+ return true;
+}
+
+/**
+ * Get the style for an element.
+ *
+ * \param c content of type CONTENT_HTML that is being processed
+ * \param parent_style style at this point in xml tree, or NULL for root
+ * \param root_style root node's style, or NULL for root
+ * \param n node in xml tree
+ * \return the new style, or NULL on memory exhaustion
+ */
+css_select_results *box_get_style(html_content *c,
+ const css_computed_style *parent_style,
+ const css_computed_style *root_style, dom_node *n)
+{
+ dom_string *s;
+ dom_exception err;
+ css_stylesheet *inline_style = NULL;
+ css_select_results *styles;
+ nscss_select_ctx ctx;
+
+ /* Firstly, construct inline stylesheet, if any */
+ err = dom_element_get_attribute(n, corestring_dom_style, &s);
+ if (err != DOM_NO_ERR)
+ return NULL;
+
+ if (s != NULL) {
+ inline_style = nscss_create_inline_style(
+ (const uint8_t *) dom_string_data(s),
+ dom_string_byte_length(s),
+ c->encoding,
+ nsurl_access(c->base_url),
+ c->quirks != DOM_DOCUMENT_QUIRKS_MODE_NONE);
+
+ dom_string_unref(s);
+
+ if (inline_style == NULL)
+ return NULL;
+ }
+
+ /* Populate selection context */
+ ctx.ctx = c->select_ctx;
+ ctx.quirks = (c->quirks == DOM_DOCUMENT_QUIRKS_MODE_FULL);
+ ctx.base_url = c->base_url;
+ ctx.universal = c->universal;
+ ctx.root_style = root_style;
+ ctx.parent_style = parent_style;
+
+ /* Select style for element */
+ styles = nscss_get_style(&ctx, n, CSS_MEDIA_SCREEN, inline_style);
+
+ /* No longer need inline style */
+ if (inline_style != NULL)
+ css_stylesheet_destroy(inline_style);
+
+ return styles;
+}
+
+
+/**
+ * Apply the CSS text-transform property to given text for its ASCII chars.
+ *
+ * \param s string to transform
+ * \param len length of s
+ * \param tt transform type
+ */
+
+void box_text_transform(char *s, unsigned int len, enum css_text_transform_e tt)
+{
+ unsigned int i;
+ if (len == 0)
+ return;
+ switch (tt) {
+ case CSS_TEXT_TRANSFORM_UPPERCASE:
+ for (i = 0; i < len; ++i)
+ if ((unsigned char) s[i] < 0x80)
+ s[i] = toupper(s[i]);
+ break;
+ case CSS_TEXT_TRANSFORM_LOWERCASE:
+ for (i = 0; i < len; ++i)
+ if ((unsigned char) s[i] < 0x80)
+ s[i] = tolower(s[i]);
+ break;
+ case CSS_TEXT_TRANSFORM_CAPITALIZE:
+ if ((unsigned char) s[0] < 0x80)
+ s[0] = toupper(s[0]);
+ for (i = 1; i < len; ++i)
+ if ((unsigned char) s[i] < 0x80 &&
+ isspace(s[i - 1]))
+ s[i] = toupper(s[i]);
+ break;
+ default:
+ break;
+ }
+}
+
+
+/**
+ * \name Special case element handlers
+ *
+ * These functions are called by box_construct_element() when an element is
+ * being converted, according to the entries in element_table.
+ *
+ * The parameters are the xmlNode, the content for the document, and a partly
+ * filled in box structure for the element.
+ *
+ * Return true on success, false on memory exhaustion. Set *convert_children
+ * to false if children of this element in the XML tree should be skipped (for
+ * example, if they have been processed in some special way already).
+ *
+ * Elements ordered as in the HTML 4.01 specification. Section numbers in
+ * brackets [] refer to the spec.
+ *
+ * \{
+ */
+
+/**
+ * Document body [7.5.1].
+ */
+
+bool box_body(BOX_SPECIAL_PARAMS)
+{
+ css_color color;
+
+ css_computed_background_color(box->style, &color);
+ if (nscss_color_is_transparent(color))
+ content->background_colour = NS_TRANSPARENT;
+ else
+ content->background_colour = nscss_color_to_ns(color);
+
+ return true;
+}
+
+
+/**
+ * Forced line break [9.3.2].
+ */
+
+bool box_br(BOX_SPECIAL_PARAMS)
+{
+ box->type = BOX_BR;
+ return true;
+}
+
+/**
+ * Preformatted text [9.3.4].
+ */
+
+bool box_pre(BOX_SPECIAL_PARAMS)
+{
+ box->flags |= PRE_STRIP;
+ return true;
+}
+
+/**
+ * Anchor [12.2].
+ */
+
+bool box_a(BOX_SPECIAL_PARAMS)
+{
+ bool ok;
+ nsurl *url;
+ dom_string *s;
+ dom_exception err;
+
+ err = dom_element_get_attribute(n, corestring_dom_href, &s);
+ if (err == DOM_NO_ERR && s != NULL) {
+ ok = box_extract_link(content, s, content->base_url, &url);
+ dom_string_unref(s);
+ if (!ok)
+ return false;
+ if (url) {
+ if (box->href != NULL)
+ nsurl_unref(box->href);
+ box->href = url;
+ }
+ }
+
+ /* name and id share the same namespace */
+ err = dom_element_get_attribute(n, corestring_dom_name, &s);
+ if (err == DOM_NO_ERR && s != NULL) {
+ lwc_string *lwc_name;
+
+ err = dom_string_intern(s, &lwc_name);
+
+ dom_string_unref(s);
+
+ if (err == DOM_NO_ERR) {
+ /* name replaces existing id
+ * TODO: really? */
+ if (box->id != NULL)
+ lwc_string_unref(box->id);
+
+ box->id = lwc_name;
+ }
+ }
+
+ /* target frame [16.3] */
+ err = dom_element_get_attribute(n, corestring_dom_target, &s);
+ if (err == DOM_NO_ERR && s != NULL) {
+ if (dom_string_caseless_lwc_isequal(s,
+ corestring_lwc__blank))
+ box->target = TARGET_BLANK;
+ else if (dom_string_caseless_lwc_isequal(s,
+ corestring_lwc__top))
+ box->target = TARGET_TOP;
+ else if (dom_string_caseless_lwc_isequal(s,
+ corestring_lwc__parent))
+ box->target = TARGET_PARENT;
+ else if (dom_string_caseless_lwc_isequal(s,
+ corestring_lwc__self))
+ /* the default may have been overridden by a
+ * <base target=...>, so this is different to 0 */
+ box->target = TARGET_SELF;
+ else {
+ /* 6.16 says that frame names must begin with [a-zA-Z]
+ * This doesn't match reality, so just take anything */
+ box->target = talloc_strdup(content->bctx,
+ dom_string_data(s));
+ if (!box->target) {
+ dom_string_unref(s);
+ return false;
+ }
+ }
+ dom_string_unref(s);
+ }
+
+ return true;
+}
+
+
+/**
+ * Embedded image [13.2].
+ */
+
+bool box_image(BOX_SPECIAL_PARAMS)
+{
+ bool ok;
+ dom_string *s;
+ dom_exception err;
+ nsurl *url;
+ enum css_width_e wtype;
+ enum css_height_e htype;
+ css_fixed value = 0;
+ css_unit wunit = CSS_UNIT_PX;
+ css_unit hunit = CSS_UNIT_PX;
+
+ if (box->style && ns_computed_display(box->style,
+ box_is_root(n)) == CSS_DISPLAY_NONE)
+ return true;
+
+ /* handle alt text */
+ err = dom_element_get_attribute(n, corestring_dom_alt, &s);
+ if (err == DOM_NO_ERR && s != NULL) {
+ char *alt = squash_whitespace(dom_string_data(s));
+ dom_string_unref(s);
+ if (alt == NULL)
+ return false;
+ box->text = talloc_strdup(content->bctx, alt);
+ free(alt);
+ if (box->text == NULL)
+ return false;
+ box->length = strlen(box->text);
+ }
+
+ if (nsoption_bool(foreground_images) == false) {
+ return true;
+ }
+
+ /* imagemap associated with this image */
+ if (!box_get_attribute(n, "usemap", content->bctx, &box->usemap))
+ return false;
+ if (box->usemap && box->usemap[0] == '#')
+ box->usemap++;
+
+ /* get image URL */
+ err = dom_element_get_attribute(n, corestring_dom_src, &s);
+ if (err != DOM_NO_ERR || s == NULL)
+ return true;
+
+ if (box_extract_link(content, s, content->base_url, &url) == false) {
+ dom_string_unref(s);
+ return false;
+ }
+
+ dom_string_unref(s);
+
+ if (url == NULL)
+ return true;
+
+ /* start fetch */
+ box->flags |= IS_REPLACED;
+ ok = html_fetch_object(content, url, box, image_types,
+ content->base.available_width, 1000, false);
+ nsurl_unref(url);
+
+ wtype = css_computed_width(box->style, &value, &wunit);
+ htype = css_computed_height(box->style, &value, &hunit);
+
+ if (wtype == CSS_WIDTH_SET && wunit != CSS_UNIT_PCT &&
+ htype == CSS_HEIGHT_SET && hunit != CSS_UNIT_PCT) {
+ /* We know the dimensions the image will be shown at before it's
+ * fetched. */
+ box->flags |= REPLACE_DIM;
+ }
+
+ return ok;
+}
+
+
+/**
+ * Noscript element
+ */
+
+bool box_noscript(BOX_SPECIAL_PARAMS)
+{
+ /* If scripting is enabled, do not display the contents of noscript */
+ if (content->enable_scripting)
+ *convert_children = false;
+
+ return true;
+}
+
+
+/**
+ * Destructor for object_params, for <object> elements
+ *
+ * \param o The object params being destroyed.
+ * \return 0 to allow talloc to continue destroying the tree.
+ */
+static int box_object_talloc_destructor(struct object_params *o)
+{
+ if (o->codebase != NULL)
+ nsurl_unref(o->codebase);
+ if (o->classid != NULL)
+ nsurl_unref(o->classid);
+ if (o->data != NULL)
+ nsurl_unref(o->data);
+
+ return 0;
+}
+
+/**
+ * Generic embedded object [13.3].
+ */
+
+bool box_object(BOX_SPECIAL_PARAMS)
+{
+ struct object_params *params;
+ struct object_param *param;
+ dom_string *codebase, *classid, *data;
+ dom_node *c;
+ dom_exception err;
+
+ if (box->style && ns_computed_display(box->style,
+ box_is_root(n)) == CSS_DISPLAY_NONE)
+ return true;
+
+ if (box_get_attribute(n, "usemap", content->bctx, &box->usemap) ==
+ false)
+ return false;
+ if (box->usemap && box->usemap[0] == '#')
+ box->usemap++;
+
+ params = talloc(content->bctx, struct object_params);
+ if (params == NULL)
+ return false;
+
+ talloc_set_destructor(params, box_object_talloc_destructor);
+
+ params->data = NULL;
+ params->type = NULL;
+ params->codetype = NULL;
+ params->codebase = NULL;
+ params->classid = NULL;
+ params->params = NULL;
+
+ /* codebase, classid, and data are URLs
+ * (codebase is the base for the other two) */
+ err = dom_element_get_attribute(n, corestring_dom_codebase, &codebase);
+ if (err == DOM_NO_ERR && codebase != NULL) {
+ if (box_extract_link(content, codebase, content->base_url,
+ ¶ms->codebase) == false) {
+ dom_string_unref(codebase);
+ return false;
+ }
+ dom_string_unref(codebase);
+ }
+ if (params->codebase == NULL)
+ params->codebase = nsurl_ref(content->base_url);
+
+ err = dom_element_get_attribute(n, corestring_dom_classid, &classid);
+ if (err == DOM_NO_ERR && classid != NULL) {
+ if (box_extract_link(content, classid,
+ params->codebase, ¶ms->classid) == false) {
+ dom_string_unref(classid);
+ return false;
+ }
+ dom_string_unref(classid);
+ }
+
+ err = dom_element_get_attribute(n, corestring_dom_data, &data);
+ if (err == DOM_NO_ERR && data != NULL) {
+ if (box_extract_link(content, data,
+ params->codebase, ¶ms->data) == false) {
+ dom_string_unref(data);
+ return false;
+ }
+ dom_string_unref(data);
+ }
+
+ if (params->classid == NULL && params->data == NULL)
+ /* nothing to embed; ignore */
+ return true;
+
+ /* Don't include ourself */
+ if (params->classid != NULL && nsurl_compare(content->base_url,
+ params->classid, NSURL_COMPLETE))
+ return true;
+
+ if (params->data != NULL && nsurl_compare(content->base_url,
+ params->data, NSURL_COMPLETE))
+ return true;
+
+ /* codetype and type are MIME types */
+ if (box_get_attribute(n, "codetype", params,
+ ¶ms->codetype) == false)
+ return false;
+ if (box_get_attribute(n, "type", params, ¶ms->type) == false)
+ return false;
+
+ /* classid && !data => classid is used (consult codetype)
+ * (classid || !classid) && data => data is used (consult type)
+ * !classid && !data => invalid; ignored */
+
+ if (params->classid != NULL && params->data == NULL &&
+ params->codetype != NULL) {
+ lwc_string *icodetype;
+ lwc_error lerror;
+
+ lerror = lwc_intern_string(params->codetype,
+ strlen(params->codetype), &icodetype);
+ if (lerror != lwc_error_ok)
+ return false;
+
+ if (content_factory_type_from_mime_type(icodetype) ==
+ CONTENT_NONE) {
+ /* can't handle this MIME type */
+ lwc_string_unref(icodetype);
+ return true;
+ }
+
+ lwc_string_unref(icodetype);
+ }
+
+ if (params->data != NULL && params->type != NULL) {
+ lwc_string *itype;
+ lwc_error lerror;
+
+ lerror = lwc_intern_string(params->type, strlen(params->type),
+ &itype);
+ if (lerror != lwc_error_ok)
+ return false;
+
+ if (content_factory_type_from_mime_type(itype) ==
+ CONTENT_NONE) {
+ /* can't handle this MIME type */
+ lwc_string_unref(itype);
+ return true;
+ }
+
+ lwc_string_unref(itype);
+ }
+
+ /* add parameters to linked list */
+ err = dom_node_get_first_child(n, &c);
+ if (err != DOM_NO_ERR)
+ return false;
+
+ while (c != NULL) {
+ dom_node *next;
+ dom_node_type type;
+
+ err = dom_node_get_node_type(c, &type);
+ if (err != DOM_NO_ERR) {
+ dom_node_unref(c);
+ return false;
+ }
+
+ if (type == DOM_ELEMENT_NODE) {
+ dom_string *name;
+
+ err = dom_node_get_node_name(c, &name);
+ if (err != DOM_NO_ERR) {
+ dom_node_unref(c);
+ return false;
+ }
+
+ if (!dom_string_caseless_lwc_isequal(name,
+ corestring_lwc_param)) {
+ /* The first non-param child is the start of
+ * the alt html. Therefore, we should break
+ * out of this loop. */
+ dom_node_unref(c);
+ break;
+ }
+
+ param = talloc(params, struct object_param);
+ if (param == NULL) {
+ dom_node_unref(c);
+ return false;
+ }
+ param->name = NULL;
+ param->value = NULL;
+ param->type = NULL;
+ param->valuetype = NULL;
+ param->next = NULL;
+
+ if (box_get_attribute(c, "name", param,
+ ¶m->name) == false) {
+ dom_node_unref(c);
+ return false;
+ }
+
+ if (box_get_attribute(c, "value", param,
+ ¶m->value) == false) {
+ dom_node_unref(c);
+ return false;
+ }
+
+ if (box_get_attribute(c, "type", param,
+ ¶m->type) == false) {
+ dom_node_unref(c);
+ return false;
+ }
+
+ if (box_get_attribute(c, "valuetype", param,
+ ¶m->valuetype) == false) {
+ dom_node_unref(c);
+ return false;
+ }
+
+ if (param->valuetype == NULL) {
+ param->valuetype = talloc_strdup(param, "data");
+ if (param->valuetype == NULL) {
+ dom_node_unref(c);
+ return false;
+ }
+ }
+
+ param->next = params->params;
+ params->params = param;
+ }
+
+ err = dom_node_get_next_sibling(c, &next);
+ if (err != DOM_NO_ERR) {
+ dom_node_unref(c);
+ return false;
+ }
+
+ dom_node_unref(c);
+ c = next;
+ }
+
+ box->object_params = params;
+
+ /* start fetch (MIME type is ok or not specified) */
+ box->flags |= IS_REPLACED;
+ if (!html_fetch_object(content,
+ params->data ? params->data : params->classid,
+ box, CONTENT_ANY, content->base.available_width, 1000,
+ false))
+ return false;
+
+ *convert_children = false;
+ return true;
+}
+
+
+/**
+ * Window subdivision [16.2.1].
+ */
+
+bool box_frameset(BOX_SPECIAL_PARAMS)
+{
+ bool ok;
+
+ if (content->frameset) {
+ NSLOG(netsurf, INFO, "Error: multiple framesets in document.");
+ /* Don't convert children */
+ if (convert_children)
+ *convert_children = false;
+ /* And ignore this spurious frameset */
+ box->type = BOX_NONE;
+ return true;
+ }
+
+ content->frameset = talloc_zero(content->bctx, struct content_html_frames);
+ if (!content->frameset)
+ return false;
+
+ ok = box_create_frameset(content->frameset, n, content);
+ if (ok)
+ box->type = BOX_NONE;
+
+ if (convert_children)
+ *convert_children = false;
+ return ok;
+}
+
+
+/**
+ * Destructor for content_html_frames, for frame elements
+ *
+ * \param f The frame params being destroyed.
+ * \return 0 to allow talloc to continue destroying the tree.
+ */
+static int box_frames_talloc_destructor(struct content_html_frames *f)
+{
+ if (f->url != NULL) {
+ nsurl_unref(f->url);
+ f->url = NULL;
+ }
+
+ return 0;
+}
+
+
+/**
+ * Parse a multi-length-list, as defined by HTML 4.01.
+ *
+ * \param ds dom string to parse
+ * \param count updated to number of entries
+ * \return array of struct box_multi_length, or 0 on memory exhaustion
+ */
+static struct frame_dimension *
+box_parse_multi_lengths(const dom_string *ds, unsigned int *count)
+{
+ char *end;
+ unsigned int i, n;
+ struct frame_dimension *length;
+ const char *s;
+
+ s = dom_string_data(ds);
+
+ for (i = 0, n = 1; s[i]; i++)
+ if (s[i] == ',')
+ n++;
+
+ length = calloc(n, sizeof(struct frame_dimension));
+ if (!length)
+ return NULL;
+
+ for (i = 0; i != n; i++) {
+ while (ascii_is_space(*s)) {
+ s++;
+ }
+ length[i].value = strtof(s, &end);
+ if (length[i].value <= 0) {
+ length[i].value = 1;
+ }
+ s = end;
+ switch (*s) {
+ case '%':
+ length[i].unit = FRAME_DIMENSION_PERCENT;
+ break;
+ case '*':
+ length[i].unit = FRAME_DIMENSION_RELATIVE;
+ break;
+ default:
+ length[i].unit = FRAME_DIMENSION_PIXELS;
+ break;
+ }
+ while (*s && *s != ',') {
+ s++;
+ }
+ if (*s == ',') {
+ s++;
+ }
+ }
+
+ *count = n;
+ return length;
+}
+
+
+bool box_create_frameset(struct content_html_frames *f, dom_node *n,
+ html_content *content) {
+ unsigned int row, col, index, i;
+ unsigned int rows = 1, cols = 1;
+ dom_string *s;
+ dom_exception err;
+ nsurl *url;
+ struct frame_dimension *row_height = 0, *col_width = 0;
+ dom_node *c, *next;
+ struct content_html_frames *frame;
+ bool default_border = true;
+ colour default_border_colour = 0x000000;
+
+ /* parse rows and columns */
+ err = dom_element_get_attribute(n, corestring_dom_rows, &s);
+ if (err == DOM_NO_ERR && s != NULL) {
+ row_height = box_parse_multi_lengths(s, &rows);
+ dom_string_unref(s);
+ if (row_height == NULL)
+ return false;
+ } else {
+ row_height = calloc(1, sizeof(struct frame_dimension));
+ if (row_height == NULL)
+ return false;
+ row_height->value = 100;
+ row_height->unit = FRAME_DIMENSION_PERCENT;
+ }
+
+ err = dom_element_get_attribute(n, corestring_dom_cols, &s);
+ if (err == DOM_NO_ERR && s != NULL) {
+ col_width = box_parse_multi_lengths(s, &cols);
+ dom_string_unref(s);
+ if (col_width == NULL) {
+ free(row_height);
+ return false;
+ }
+ } else {
+ col_width = calloc(1, sizeof(struct frame_dimension));
+ if (col_width == NULL) {
+ free(row_height);
+ return false;
+ }
+ col_width->value = 100;
+ col_width->unit = FRAME_DIMENSION_PERCENT;
+ }
+
+ /* common extension: border="0|1" to control all children */
+ err = dom_element_get_attribute(n, corestring_dom_border, &s);
+ if (err == DOM_NO_ERR && s != NULL) {
+ if ((dom_string_data(s)[0] == '0') &&
+ (dom_string_data(s)[1] == '\0'))
+ default_border = false;
+ dom_string_unref(s);
+ }
+
+ /* common extension: frameborder="yes|no" to control all children */
+ err = dom_element_get_attribute(n, corestring_dom_frameborder, &s);
+ if (err == DOM_NO_ERR && s != NULL) {
+ if (dom_string_caseless_lwc_isequal(s,
+ corestring_lwc_no) == 0)
+ default_border = false;
+ dom_string_unref(s);
+ }
+
+ /* common extension: bordercolor="#RRGGBB|<named colour>" to control
+ *all children */
+ err = dom_element_get_attribute(n, corestring_dom_bordercolor, &s);
+ if (err == DOM_NO_ERR && s != NULL) {
+ css_color color;
+
+ if (nscss_parse_colour(dom_string_data(s), &color))
+ default_border_colour = nscss_color_to_ns(color);
+
+ dom_string_unref(s);
+ }
+
+ /* update frameset and create default children */
+ f->cols = cols;
+ f->rows = rows;
+ f->scrolling = BW_SCROLLING_NO;
+ f->children = talloc_array(content->bctx, struct content_html_frames,
+ (rows * cols));
+
+ talloc_set_destructor(f->children, box_frames_talloc_destructor);
+
+ for (row = 0; row < rows; row++) {
+ for (col = 0; col < cols; col++) {
+ index = (row * cols) + col;
+ frame = &f->children[index];
+ frame->cols = 0;
+ frame->rows = 0;
+ frame->width = col_width[col];
+ frame->height = row_height[row];
+ frame->margin_width = 0;
+ frame->margin_height = 0;
+ frame->name = NULL;
+ frame->url = NULL;
+ frame->no_resize = false;
+ frame->scrolling = BW_SCROLLING_AUTO;
+ frame->border = default_border;
+ frame->border_colour = default_border_colour;
+ frame->children = NULL;
+ }
+ }
+ free(col_width);
+ free(row_height);
+
+ /* create the frameset windows */
+ err = dom_node_get_first_child(n, &c);
+ if (err != DOM_NO_ERR)
+ return false;
+
+ for (row = 0; c != NULL && row < rows; row++) {
+ for (col = 0; c != NULL && col < cols; col++) {
+ while (c != NULL) {
+ dom_node_type type;
+ dom_string *name;
+
+ err = dom_node_get_node_type(c, &type);
+ if (err != DOM_NO_ERR) {
+ dom_node_unref(c);
+ return false;
+ }
+
+ err = dom_node_get_node_name(c, &name);
+ if (err != DOM_NO_ERR) {
+ dom_node_unref(c);
+ return false;
+ }
+
+ if (type != DOM_ELEMENT_NODE ||
+ (!dom_string_caseless_lwc_isequal(
+ name,
+ corestring_lwc_frame) &&
+ !dom_string_caseless_lwc_isequal(
+ name,
+ corestring_lwc_frameset
+ ))) {
+ err = dom_node_get_next_sibling(c,
+ &next);
+ if (err != DOM_NO_ERR) {
+ dom_string_unref(name);
+ dom_node_unref(c);
+ return false;
+ }
+
+ dom_string_unref(name);
+ dom_node_unref(c);
+ c = next;
+ } else {
+ /* Got a FRAME or FRAMESET element */
+ dom_string_unref(name);
+ break;
+ }
+ }
+
+ if (c == NULL)
+ break;
+
+ /* get current frame */
+ index = (row * cols) + col;
+ frame = &f->children[index];
+
+ /* nest framesets */
+ err = dom_node_get_node_name(c, &s);
+ if (err != DOM_NO_ERR) {
+ dom_node_unref(c);
+ return false;
+ }
+
+ if (dom_string_caseless_lwc_isequal(s,
+ corestring_lwc_frameset)) {
+ dom_string_unref(s);
+ frame->border = 0;
+ if (box_create_frameset(frame, c,
+ content) == false) {
+ dom_node_unref(c);
+ return false;
+ }
+
+ err = dom_node_get_next_sibling(c, &next);
+ if (err != DOM_NO_ERR) {
+ dom_node_unref(c);
+ return false;
+ }
+
+ dom_node_unref(c);
+ c = next;
+ continue;
+ }
+
+ dom_string_unref(s);
+
+ /* get frame URL (not required) */
+ url = NULL;
+ err = dom_element_get_attribute(c, corestring_dom_src, &s);
+ if (err == DOM_NO_ERR && s != NULL) {
+ box_extract_link(content, s, content->base_url,
+ &url);
+ dom_string_unref(s);
+ }
+
+ /* copy url */
+ if (url != NULL) {
+ /* no self-references */
+ if (nsurl_compare(content->base_url, url,
+ NSURL_COMPLETE) == false)
+ frame->url = url;
+ url = NULL;
+ }
+
+ /* fill in specified values */
+ err = dom_element_get_attribute(c, corestring_dom_name, &s);
+ if (err == DOM_NO_ERR && s != NULL) {
+ frame->name = talloc_strdup(content->bctx,
+ dom_string_data(s));
+ dom_string_unref(s);
+ }
+
+ dom_element_has_attribute(c, corestring_dom_noresize,
+ &frame->no_resize);
+
+ err = dom_element_get_attribute(c, corestring_dom_frameborder,
+ &s);
+ if (err == DOM_NO_ERR && s != NULL) {
+ i = atoi(dom_string_data(s));
+ frame->border = (i != 0);
+ dom_string_unref(s);
+ }
+
+ err = dom_element_get_attribute(c, corestring_dom_scrolling, &s);
+ if (err == DOM_NO_ERR && s != NULL) {
+ if (dom_string_caseless_lwc_isequal(s,
+ corestring_lwc_yes))
+ frame->scrolling = BW_SCROLLING_YES;
+ else if (dom_string_caseless_lwc_isequal(s,
+ corestring_lwc_no))
+ frame->scrolling = BW_SCROLLING_NO;
+ dom_string_unref(s);
+ }
+
+ err = dom_element_get_attribute(c, corestring_dom_marginwidth,
+ &s);
+ if (err == DOM_NO_ERR && s != NULL) {
+ frame->margin_width = atoi(dom_string_data(s));
+ dom_string_unref(s);
+ }
+
+ err = dom_element_get_attribute(c, corestring_dom_marginheight,
+ &s);
+ if (err == DOM_NO_ERR && s != NULL) {
+ frame->margin_height = atoi(dom_string_data(s));
+ dom_string_unref(s);
+ }
+
+ err = dom_element_get_attribute(c, corestring_dom_bordercolor,
+ &s);
+ if (err == DOM_NO_ERR && s != NULL) {
+ css_color color;
+
+ if (nscss_parse_colour(dom_string_data(s),
+ &color))
+ frame->border_colour =
+ nscss_color_to_ns(color);
+
+ dom_string_unref(s);
+ }
+
+ /* advance */
+ err = dom_node_get_next_sibling(c, &next);
+ if (err != DOM_NO_ERR) {
+ dom_node_unref(c);
+ return false;
+ }
+
+ dom_node_unref(c);
+ c = next;
+ }
+ }
+
+ /* If the last child wasn't a frame, we still need to unref it */
+ if (c != NULL) {
+ dom_node_unref(c);
+ }
+
+ return true;
+}
+
+
+/**
+ * Destructor for content_html_iframe, for <iframe> elements
+ *
+ * \param f The iframe params being destroyed.
+ * \return 0 to allow talloc to continue destroying the tree.
+ */
+static int box_iframes_talloc_destructor(struct content_html_iframe *f)
+{
+ if (f->url != NULL) {
+ nsurl_unref(f->url);
+ f->url = NULL;
+ }
+
+ return 0;
+}
+
+
+/**
+ * Inline subwindow [16.5].
+ */
+
+bool box_iframe(BOX_SPECIAL_PARAMS)
+{
+ nsurl *url;
+ dom_string *s;
+ dom_exception err;
+ struct content_html_iframe *iframe;
+ int i;
+
+ if (box->style && ns_computed_display(box->style,
+ box_is_root(n)) == CSS_DISPLAY_NONE)
+ return true;
+
+ if (box->style && css_computed_visibility(box->style) ==
+ CSS_VISIBILITY_HIDDEN)
+ /* Don't create iframe discriptors for invisible iframes
+ * TODO: handle hidden iframes at browser_window generation
+ * time instead? */
+ return true;
+
+ /* get frame URL */
+ err = dom_element_get_attribute(n, corestring_dom_src, &s);
+ if (err != DOM_NO_ERR || s == NULL)
+ return true;
+ if (box_extract_link(content, s, content->base_url, &url) == false) {
+ dom_string_unref(s);
+ return false;
+ }
+ dom_string_unref(s);
+ if (url == NULL)
+ return true;
+
+ /* don't include ourself */
+ if (nsurl_compare(content->base_url, url, NSURL_COMPLETE)) {
+ nsurl_unref(url);
+ return true;
+ }
+
+ /* create a new iframe */
+ iframe = talloc(content->bctx, struct content_html_iframe);
+ if (iframe == NULL) {
+ nsurl_unref(url);
+ return false;
+ }
+
+ talloc_set_destructor(iframe, box_iframes_talloc_destructor);
+
+ iframe->box = box;
+ iframe->margin_width = 0;
+ iframe->margin_height = 0;
+ iframe->name = NULL;
+ iframe->url = url;
+ iframe->scrolling = BW_SCROLLING_AUTO;
+ iframe->border = true;
+
+ /* Add this iframe to the linked list of iframes */
+ iframe->next = content->iframe;
+ content->iframe = iframe;
+
+ /* fill in specified values */
+ err = dom_element_get_attribute(n, corestring_dom_name, &s);
+ if (err == DOM_NO_ERR && s != NULL) {
+ iframe->name = talloc_strdup(content->bctx, dom_string_data(s));
+ dom_string_unref(s);
+ }
+
+ err = dom_element_get_attribute(n, corestring_dom_frameborder, &s);
+ if (err == DOM_NO_ERR && s != NULL) {
+ i = atoi(dom_string_data(s));
+ iframe->border = (i != 0);
+ dom_string_unref(s);
+ }
+
+ err = dom_element_get_attribute(n, corestring_dom_bordercolor, &s);
+ if (err == DOM_NO_ERR && s != NULL) {
+ css_color color;
+
+ if (nscss_parse_colour(dom_string_data(s), &color))
+ iframe->border_colour = nscss_color_to_ns(color);
+
+ dom_string_unref(s);
+ }
+
+ err = dom_element_get_attribute(n, corestring_dom_scrolling, &s);
+ if (err == DOM_NO_ERR && s != NULL) {
+ if (dom_string_caseless_lwc_isequal(s,
+ corestring_lwc_yes))
+ iframe->scrolling = BW_SCROLLING_YES;
+ else if (dom_string_caseless_lwc_isequal(s,
+ corestring_lwc_no))
+ iframe->scrolling = BW_SCROLLING_NO;
+ dom_string_unref(s);
+ }
+
+ err = dom_element_get_attribute(n, corestring_dom_marginwidth, &s);
+ if (err == DOM_NO_ERR && s != NULL) {
+ iframe->margin_width = atoi(dom_string_data(s));
+ dom_string_unref(s);
+ }
+
+ err = dom_element_get_attribute(n, corestring_dom_marginheight, &s);
+ if (err == DOM_NO_ERR && s != NULL) {
+ iframe->margin_height = atoi(dom_string_data(s));
+ dom_string_unref(s);
+ }
+
+ /* box */
+ assert(box->style);
+ box->flags |= IFRAME;
+ box->flags |= IS_REPLACED;
+
+ /* Showing iframe, so don't show alternate content */
+ if (convert_children)
+ *convert_children = false;
+ return true;
+}
+
+
+/**
+ * Helper function for adding textarea widget to box.
+ *
+ * This is a load of hacks to ensure boxes replaced with textareas
+ * can be handled by the layout code.
+ */
+
+static bool box_input_text(html_content *html, struct box *box,
+ struct dom_node *node)
+{
+ struct box *inline_container, *inline_box;
+
+ box->type = BOX_INLINE_BLOCK;
+
+ inline_container = box_create(NULL, 0, false, 0, 0, 0, 0, html->bctx);
+ if (!inline_container)
+ return false;
+ inline_container->type = BOX_INLINE_CONTAINER;
+ inline_box = box_create(NULL, box->style, false, 0, 0, box->title, 0,
+ html->bctx);
+ if (!inline_box)
+ return false;
+ inline_box->type = BOX_TEXT;
+ inline_box->text = talloc_strdup(html->bctx, "");
+
+ box_add_child(inline_container, inline_box);
+ box_add_child(box, inline_container);
+
+ return box_textarea_create_textarea(html, box, node);
+}
+
+
+/**
+ * Form control [17.4].
+ */
+
+bool box_input(BOX_SPECIAL_PARAMS)
+{
+ struct form_control *gadget = NULL;
+ dom_string *type = NULL;
+ dom_exception err;
+ nsurl *url;
+ nserror error;
+
+ dom_element_get_attribute(n, corestring_dom_type, &type);
+
+ gadget = html_forms_get_control_for_node(content->forms, n);
+ if (gadget == NULL)
+ goto no_memory;
+ box->gadget = gadget;
+ box->flags |= IS_REPLACED;
+ gadget->box = box;
+ gadget->html = content;
+
+ if (type && dom_string_caseless_lwc_isequal(type,
+ corestring_lwc_password)) {
+ if (box_input_text(content, box, n) == false)
+ goto no_memory;
+
+ } else if (type && dom_string_caseless_lwc_isequal(type,
+ corestring_lwc_file)) {
+ box->type = BOX_INLINE_BLOCK;
+
+ } else if (type && dom_string_caseless_lwc_isequal(type,
+ corestring_lwc_hidden)) {
+ /* no box for hidden inputs */
+ box->type = BOX_NONE;
+
+ } else if (type &&
+ (dom_string_caseless_lwc_isequal(type,
+ corestring_lwc_checkbox) ||
+ dom_string_caseless_lwc_isequal(type,
+ corestring_lwc_radio))) {
+
+ } else if (type &&
+ (dom_string_caseless_lwc_isequal(type,
+ corestring_lwc_submit) ||
+ dom_string_caseless_lwc_isequal(type,
+ corestring_lwc_reset) ||
+ dom_string_caseless_lwc_isequal(type,
+ corestring_lwc_button))) {
+ struct box *inline_container, *inline_box;
+
+ if (box_button(n, content, box, 0) == false)
+ goto no_memory;
+
+ inline_container = box_create(NULL, 0, false, 0, 0, 0, 0,
+ content->bctx);
+ if (inline_container == NULL)
+ goto no_memory;
+
+ inline_container->type = BOX_INLINE_CONTAINER;
+
+ inline_box = box_create(NULL, box->style, false, 0, 0,
+ box->title, 0, content->bctx);
+ if (inline_box == NULL)
+ goto no_memory;
+
+ inline_box->type = BOX_TEXT;
+
+ if (box->gadget->value != NULL)
+ inline_box->text = talloc_strdup(content->bctx,
+ box->gadget->value);
+ else if (box->gadget->type == GADGET_SUBMIT)
+ inline_box->text = talloc_strdup(content->bctx,
+ messages_get("Form_Submit"));
+ else if (box->gadget->type == GADGET_RESET)
+ inline_box->text = talloc_strdup(content->bctx,
+ messages_get("Form_Reset"));
+ else
+ inline_box->text = talloc_strdup(content->bctx,
+ "Button");
+
+ if (inline_box->text == NULL)
+ goto no_memory;
+
+ inline_box->length = strlen(inline_box->text);
+
+ box_add_child(inline_container, inline_box);
+
+ box_add_child(box, inline_container);
+
+ } else if (type && dom_string_caseless_lwc_isequal(type,
+ corestring_lwc_image)) {
+ gadget->type = GADGET_IMAGE;
+
+ if (box->style && ns_computed_display(box->style,
+ box_is_root(n)) != CSS_DISPLAY_NONE &&
+ nsoption_bool(foreground_images) == true) {
+ dom_string *s;
+
+ err = dom_element_get_attribute(n, corestring_dom_src, &s);
+ if (err == DOM_NO_ERR && s != NULL) {
+ error = nsurl_join(content->base_url,
+ dom_string_data(s), &url);
+ dom_string_unref(s);
+ if (error != NSERROR_OK)
+ goto no_memory;
+
+ /* if url is equivalent to the parent's url,
+ * we've got infinite inclusion. stop it here
+ */
+ if (nsurl_compare(url, content->base_url,
+ NSURL_COMPLETE) == false) {
+ if (!html_fetch_object(content, url,
+ box, image_types,
+ content->base.
+ available_width,
+ 1000, false)) {
+ nsurl_unref(url);
+ goto no_memory;
+ }
+ }
+ nsurl_unref(url);
+ }
+ }
+ } else {
+ /* the default type is "text" */
+ if (box_input_text(content, box, n) == false)
+ goto no_memory;
+ }
+
+ if (type)
+ dom_string_unref(type);
+
+ *convert_children = false;
+ return true;
+
+no_memory:
+ if (type)
+ dom_string_unref(type);
+
+ return false;
+}
+
+
+/**
+ * Push button [17.5].
+ */
+
+bool box_button(BOX_SPECIAL_PARAMS)
+{
+ struct form_control *gadget;
+
+ gadget = html_forms_get_control_for_node(content->forms, n);
+ if (!gadget)
+ return false;
+
+ gadget->html = content;
+ box->gadget = gadget;
+ box->flags |= IS_REPLACED;
+ gadget->box = box;
+
+ box->type = BOX_INLINE_BLOCK;
+
+ /* Just render the contents */
+
+ return true;
+}
+
+
+/**
+ * Option selector [17.6].
+ */
+
+bool box_select(BOX_SPECIAL_PARAMS)
+{
+ struct box *inline_container;
+ struct box *inline_box;
+ struct form_control *gadget;
+ dom_node *c, *c2;
+ dom_node *next, *next2;
+ dom_exception err;
+
+ gadget = html_forms_get_control_for_node(content->forms, n);
+ if (gadget == NULL)
+ return false;
+
+ gadget->html = content;
+ err = dom_node_get_first_child(n, &c);
+ if (err != DOM_NO_ERR) {
+ form_free_control(gadget);
+ return false;
+ }
+
+ while (c != NULL) {
+ dom_string *name;
+
+ err = dom_node_get_node_name(c, &name);
+ if (err != DOM_NO_ERR) {
+ dom_node_unref(c);
+ form_free_control(gadget);
+ return false;
+ }
+
+ if (dom_string_caseless_lwc_isequal(name,
+ corestring_lwc_option)) {
+ dom_string_unref(name);
+
+ if (box_select_add_option(gadget, c) == false) {
+ dom_node_unref(c);
+ form_free_control(gadget);
+ return false;
+ }
+ } else if (dom_string_caseless_lwc_isequal(name,
+ corestring_lwc_optgroup)) {
+ dom_string_unref(name);
+
+ err = dom_node_get_first_child(c, &c2);
+ if (err != DOM_NO_ERR) {
+ dom_node_unref(c);
+ form_free_control(gadget);
+ return false;
+ }
+
+ while (c2 != NULL) {
+ dom_string *c2_name;
+
+ err = dom_node_get_node_name(c2, &c2_name);
+ if (err != DOM_NO_ERR) {
+ dom_node_unref(c2);
+ dom_node_unref(c);
+ form_free_control(gadget);
+ return false;
+ }
+
+ if (dom_string_caseless_lwc_isequal(c2_name,
+ corestring_lwc_option)) {
+ dom_string_unref(c2_name);
+
+ if (box_select_add_option(gadget,
+ c2) == false) {
+ dom_node_unref(c2);
+ dom_node_unref(c);
+ return false;
+ }
+ } else {
+ dom_string_unref(c2_name);
+ }
+
+ err = dom_node_get_next_sibling(c2, &next2);
+ if (err != DOM_NO_ERR) {
+ dom_node_unref(c2);
+ dom_node_unref(c);
+ return false;
+ }
+
+ dom_node_unref(c2);
+ c2 = next2;
+ }
+ } else {
+ dom_string_unref(name);
+ }
+
+ err = dom_node_get_next_sibling(c, &next);
+ if (err != DOM_NO_ERR) {
+ dom_node_unref(c);
+ form_free_control(gadget);
+ return false;
+ }
+
+ dom_node_unref(c);
+ c = next;
+ }
+
+ if (gadget->data.select.num_items == 0) {
+ /* no options: ignore entire select */
+ form_free_control(gadget);
+ return true;
+ }
+
+ box->type = BOX_INLINE_BLOCK;
+ box->gadget = gadget;
+ box->flags |= IS_REPLACED;
+ gadget->box = box;
+
+ inline_container = box_create(NULL, 0, false, 0, 0, 0, 0, content->bctx);
+ if (inline_container == NULL)
+ goto no_memory;
+ inline_container->type = BOX_INLINE_CONTAINER;
+ inline_box = box_create(NULL, box->style, false, 0, 0, box->title, 0,
+ content->bctx);
+ if (inline_box == NULL)
+ goto no_memory;
+ inline_box->type = BOX_TEXT;
+ box_add_child(inline_container, inline_box);
+ box_add_child(box, inline_container);
+
+ if (gadget->data.select.multiple == false &&
+ gadget->data.select.num_selected == 0) {
+ gadget->data.select.current = gadget->data.select.items;
+ gadget->data.select.current->initial_selected =
+ gadget->data.select.current->selected = true;
+ gadget->data.select.num_selected = 1;
+ dom_html_option_element_set_selected(
+ gadget->data.select.current->node, true);
+ }
+
+ if (gadget->data.select.num_selected == 0)
+ inline_box->text = talloc_strdup(content->bctx,
+ messages_get("Form_None"));
+ else if (gadget->data.select.num_selected == 1)
+ inline_box->text = talloc_strdup(content->bctx,
+ gadget->data.select.current->text);
+ else
+ inline_box->text = talloc_strdup(content->bctx,
+ messages_get("Form_Many"));
+ if (inline_box->text == NULL)
+ goto no_memory;
+
+ inline_box->length = strlen(inline_box->text);
+
+ *convert_children = false;
+ return true;
+
+no_memory:
+ return false;
+}
+
+
+/**
+ * Add an option to a form select control (helper function for box_select()).
+ *
+ * \param control select containing the <option>
+ * \param n xml element node for <option>
+ * \return true on success, false on memory exhaustion
+ */
+
+bool box_select_add_option(struct form_control *control, dom_node *n)
+{
+ char *value = NULL;
+ char *text = NULL;
+ char *text_nowrap = NULL;
+ bool selected;
+ dom_string *content, *s;
+ dom_exception err;
+
+ err = dom_node_get_text_content(n, &content);
+ if (err != DOM_NO_ERR)
+ return false;
+
+ if (content != NULL) {
+ text = squash_whitespace(dom_string_data(content));
+ dom_string_unref(content);
+ } else {
+ text = strdup("");
+ }
+
+ if (text == NULL)
+ goto no_memory;
+
+ err = dom_element_get_attribute(n, corestring_dom_value, &s);
+ if (err == DOM_NO_ERR && s != NULL) {
+ value = strdup(dom_string_data(s));
+ dom_string_unref(s);
+ } else {
+ value = strdup(text);
+ }
+
+ if (value == NULL)
+ goto no_memory;
+
+ dom_element_has_attribute(n, corestring_dom_selected, &selected);
+
+ /* replace spaces/TABs with hard spaces to prevent line wrapping */
+ text_nowrap = cnv_space2nbsp(text);
+ if (text_nowrap == NULL)
+ goto no_memory;
+
+ if (form_add_option(control, value, text_nowrap, selected, n) == false)
+ goto no_memory;
+
+ free(text);
+
+ return true;
+
+no_memory:
+ free(value);
+ free(text);
+ free(text_nowrap);
+ return false;
+}
+
+
+/**
+ * Multi-line text field [17.7].
+ */
+
+bool box_textarea(BOX_SPECIAL_PARAMS)
+{
+ /* Get the form_control for the DOM node */
+ box->gadget = html_forms_get_control_for_node(content->forms, n);
+ if (box->gadget == NULL)
+ return false;
+
+ box->flags |= IS_REPLACED;
+ box->gadget->html = content;
+ box->gadget->box = box;
+
+ if (!box_input_text(content, box, n))
+ return false;
+
+ *convert_children = false;
+ return true;
+}
+
+
+/**
+ * Embedded object (not in any HTML specification:
+ * see http://wp.netscape.com/assist/net_sites/new_html3_prop.html )
+ */
+
+bool box_embed(BOX_SPECIAL_PARAMS)
+{
+ struct object_params *params;
+ struct object_param *param;
+ dom_namednodemap *attrs;
+ unsigned long idx;
+ uint32_t num_attrs;
+ dom_string *src;
+ dom_exception err;
+
+ if (box->style && ns_computed_display(box->style,
+ box_is_root(n)) == CSS_DISPLAY_NONE)
+ return true;
+
+ params = talloc(content->bctx, struct object_params);
+ if (params == NULL)
+ return false;
+
+ talloc_set_destructor(params, box_object_talloc_destructor);
+
+ params->data = NULL;
+ params->type = NULL;
+ params->codetype = NULL;
+ params->codebase = NULL;
+ params->classid = NULL;
+ params->params = NULL;
+
+ /* src is a URL */
+ err = dom_element_get_attribute(n, corestring_dom_src, &src);
+ if (err != DOM_NO_ERR || src == NULL)
+ return true;
+ if (box_extract_link(content, src, content->base_url,
+ ¶ms->data) == false) {
+ dom_string_unref(src);
+ return false;
+ }
+
+ dom_string_unref(src);
+
+ if (params->data == NULL)
+ return true;
+
+ /* Don't include ourself */
+ if (nsurl_compare(content->base_url, params->data, NSURL_COMPLETE))
+ return true;
+
+ /* add attributes as parameters to linked list */
+ err = dom_node_get_attributes(n, &attrs);
+ if (err != DOM_NO_ERR)
+ return false;
+
+ err = dom_namednodemap_get_length(attrs, &num_attrs);
+ if (err != DOM_NO_ERR) {
+ dom_namednodemap_unref(attrs);
+ return false;
+ }
+
+ for (idx = 0; idx < num_attrs; idx++) {
+ dom_attr *attr;
+ dom_string *name, *value;
+
+ err = dom_namednodemap_item(attrs, idx, (void *) &attr);
+ if (err != DOM_NO_ERR) {
+ dom_namednodemap_unref(attrs);
+ return false;
+ }
+
+ err = dom_attr_get_name(attr, &name);
+ if (err != DOM_NO_ERR) {
+ dom_namednodemap_unref(attrs);
+ return false;
+ }
+
+ if (dom_string_caseless_lwc_isequal(name, corestring_lwc_src)) {
+ dom_string_unref(name);
+ continue;
+ }
+
+ err = dom_attr_get_value(attr, &value);
+ if (err != DOM_NO_ERR) {
+ dom_string_unref(name);
+ dom_namednodemap_unref(attrs);
+ return false;
+ }
+
+ param = talloc(content->bctx, struct object_param);
+ if (param == NULL) {
+ dom_string_unref(value);
+ dom_string_unref(name);
+ dom_namednodemap_unref(attrs);
+ return false;
+ }
+
+ param->name = talloc_strdup(content->bctx, dom_string_data(name));
+ param->value = talloc_strdup(content->bctx, dom_string_data(value));
+ param->type = NULL;
+ param->valuetype = talloc_strdup(content->bctx, "data");
+ param->next = NULL;
+
+ dom_string_unref(value);
+ dom_string_unref(name);
+
+ if (param->name == NULL || param->value == NULL ||
+ param->valuetype == NULL) {
+ dom_namednodemap_unref(attrs);
+ return false;
+ }
+
+ param->next = params->params;
+ params->params = param;
+ }
+
+ dom_namednodemap_unref(attrs);
+
+ box->object_params = params;
+
+ /* start fetch */
+ box->flags |= IS_REPLACED;
+ return html_fetch_object(content, params->data, box, CONTENT_ANY,
+ content->base.available_width, 1000, false);
+}
+
+/**
+ * \}
+ */
+
+
+/**
+ * Get the value of an XML element's attribute.
+ *
+ * \param n xmlNode, of type XML_ELEMENT_NODE
+ * \param attribute name of attribute
+ * \param context talloc context for result buffer
+ * \param value updated to value, if the attribute is present
+ * \return true on success, false if attribute present but memory exhausted
+ *
+ * Note that returning true does not imply that the attribute was found. If the
+ * attribute was not found, *value will be unchanged.
+ */
+
+bool box_get_attribute(dom_node *n, const char *attribute,
+ void *context, char **value)
+{
+ char *result;
+ dom_string *attr, *attr_name;
+ dom_exception err;
+
+ err = dom_string_create_interned((const uint8_t *) attribute,
+ strlen(attribute), &attr_name);
+ if (err != DOM_NO_ERR)
+ return false;
+
+ err = dom_element_get_attribute(n, attr_name, &attr);
+ if (err != DOM_NO_ERR) {
+ dom_string_unref(attr_name);
+ return false;
+ }
+
+ dom_string_unref(attr_name);
+
+ if (attr != NULL) {
+ result = talloc_strdup(context, dom_string_data(attr));
+
+ dom_string_unref(attr);
+
+ if (result == NULL)
+ return false;
+
+ *value = result;
+ }
+
+ return true;
+}
+
+
+/* exported function documented in html/box.h */
+bool
+box_extract_link(const html_content *content,
+ const dom_string *dsrel,
+ nsurl *base,
+ nsurl **result)
+{
+ char *s, *s1, *apos0 = 0, *apos1 = 0, *quot0 = 0, *quot1 = 0;
+ unsigned int i, j, end;
+ nserror error;
+ const char *rel;
+
+ rel = dom_string_data(dsrel);
+
+ s1 = s = malloc(3 * strlen(rel) + 1);
+ if (!s)
+ return false;
+
+ /* copy to s, removing white space and control characters */
+ for (i = 0; rel[i] && ascii_is_space(rel[i]); i++)
+ ;
+ for (end = strlen(rel);
+ (end != i) && ascii_is_space(rel[end - 1]);
+ end--)
+ ;
+ for (j = 0; i != end; i++) {
+ if ((unsigned char) rel[i] < 0x20) {
+ ; /* skip control characters */
+ } else if (rel[i] == ' ') {
+ s[j++] = '%';
+ s[j++] = '2';
+ s[j++] = '0';
+ } else {
+ s[j++] = rel[i];
+ }
+ }
+ s[j] = 0;
+
+ if (content->enable_scripting == false) {
+ /* extract first quoted string out of "javascript:" link */
+ if (strncmp(s, "javascript:", 11) == 0) {
+ apos0 = strchr(s, '\'');
+ if (apos0)
+ apos1 = strchr(apos0 + 1, '\'');
+ quot0 = strchr(s, '"');
+ if (quot0)
+ quot1 = strchr(quot0 + 1, '"');
+ if (apos0 && apos1 &&
+ (!quot0 || !quot1 || apos0 < quot0)) {
+ *apos1 = 0;
+ s1 = apos0 + 1;
+ } else if (quot0 && quot1) {
+ *quot1 = 0;
+ s1 = quot0 + 1;
+ }
+ }
+ }
+
+ /* construct absolute URL */
+ error = nsurl_join(base, s1, result);
+ free(s);
+ if (error != NSERROR_OK) {
+ *result = NULL;
+ return false;
+ }
+
+ return true;
+}
diff --git a/content/handlers/html/box_normalise.c b/content/handlers/html/box_normalise.c
new file mode 100644
index 0000000..7155cb7
--- /dev/null
+++ b/content/handlers/html/box_normalise.c
@@ -0,0 +1,1047 @@
+/*
+ * Copyright 2005 James Bursa <bursa(a)users.sourceforge.net>
+ * Copyright 2003 Phil Mellor <monkeyson(a)users.sourceforge.net>
+ * Copyright 2005 John M Bell <jmb202(a)ecs.soton.ac.uk>
+ * Copyright 2004 Kevin Bagust <kevin.bagust(a)ntlworld.com>
+ *
+ * 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
+ * Box tree normalisation (implementation).
+ */
+
+#include <assert.h>
+#include <stdbool.h>
+#include <string.h>
+
+#include "utils/log.h"
+#include "utils/errors.h"
+#include "css/select.h"
+
+#include "html/box.h"
+#include "html/html_internal.h"
+#include "html/table.h"
+
+/* Define to enable box normalise debug */
+#undef BOX_NORMALISE_DEBUG
+
+/**
+ * Row spanning information for a cell
+ */
+struct span_info {
+ /** Number of rows this cell spans */
+ unsigned int row_span;
+ /** Row group of cell */
+ struct box *rg;
+ /** The cell in this column spans all rows until the end of the table */
+ bool auto_row;
+};
+
+/**
+ * Column record for a table
+ */
+struct columns {
+ /** Current column index */
+ unsigned int current_column;
+ /** Number of columns in main part of table 1..max columns */
+ unsigned int num_columns;
+ /** Information about columns in main table, array [0, num_columns) */
+ struct span_info *spans;
+ /** Number of rows in table */
+ unsigned int num_rows;
+};
+
+
+static bool box_normalise_table(
+ struct box *table,
+ const struct box *root,
+ html_content *c);
+static bool box_normalise_table_spans(
+ struct box *table,
+ const struct box *root,
+ struct span_info *spans,
+ html_content *c);
+static bool box_normalise_table_row_group(
+ struct box *row_group,
+ const struct box *root,
+ struct columns *col_info,
+ html_content *c);
+static bool box_normalise_table_row(
+ struct box *row,
+ const struct box *root,
+ struct columns *col_info,
+ html_content *c);
+static bool calculate_table_row(struct columns *col_info,
+ unsigned int col_span, unsigned int row_span,
+ unsigned int *start_column, struct box *cell);
+static bool box_normalise_inline_container(
+ struct box *cont,
+ const struct box *root,
+ html_content *c);
+
+/**
+ * Ensure the box tree is correctly nested by adding and removing nodes.
+ *
+ * \param block box of type BLOCK, INLINE_BLOCK, or TABLE_CELL
+ * \param root root box of document
+ * \param c content of boxes
+ * \return true on success, false on memory exhaustion
+ *
+ * The tree is modified to satisfy the following:
+ * \code
+ * parent permitted child nodes
+ * BLOCK, INLINE_BLOCK BLOCK, INLINE_CONTAINER, TABLE
+ * INLINE_CONTAINER INLINE, INLINE_BLOCK, FLOAT_LEFT, FLOAT_RIGHT, BR, TEXT
+ * INLINE, TEXT none
+ * TABLE at least 1 TABLE_ROW_GROUP
+ * TABLE_ROW_GROUP at least 1 TABLE_ROW
+ * TABLE_ROW at least 1 TABLE_CELL
+ * TABLE_CELL BLOCK, INLINE_CONTAINER, TABLE (same as BLOCK)
+ * FLOAT_(LEFT|RIGHT) exactly 1 BLOCK or TABLE
+ * \endcode
+ */
+
+bool box_normalise_block(
+ struct box *block,
+ const struct box *root,
+ html_content *c)
+{
+ struct box *child;
+ struct box *next_child;
+ struct box *table;
+ css_computed_style *style;
+ nscss_select_ctx ctx;
+
+ assert(block != NULL);
+ assert(root != NULL);
+
+ ctx.root_style = root->style;
+
+#ifdef BOX_NORMALISE_DEBUG
+ NSLOG(netsurf, INFO, "block %p, block->type %u", block, block->type);
+#endif
+
+ assert(block->type == BOX_BLOCK || block->type == BOX_INLINE_BLOCK ||
+ block->type == BOX_TABLE_CELL);
+
+ for (child = block->children; child != NULL; child = next_child) {
+#ifdef BOX_NORMALISE_DEBUG
+ NSLOG(netsurf, INFO, "child %p, child->type = %d", child,
+ child->type);
+#endif
+
+ next_child = child->next; /* child may be destroyed */
+
+ switch (child->type) {
+ case BOX_BLOCK:
+ /* ok */
+ if (box_normalise_block(child, root, c) == false)
+ return false;
+ break;
+ case BOX_INLINE_CONTAINER:
+ if (box_normalise_inline_container(child, root, c) == false)
+ return false;
+ break;
+ case BOX_TABLE:
+ if (box_normalise_table(child, root, c) == false)
+ return false;
+ break;
+ case BOX_INLINE:
+ case BOX_INLINE_END:
+ case BOX_INLINE_BLOCK:
+ case BOX_FLOAT_LEFT:
+ case BOX_FLOAT_RIGHT:
+ case BOX_BR:
+ case BOX_TEXT:
+ /* should have been wrapped in inline
+ container by convert_xml_to_box() */
+ assert(0);
+ break;
+ case BOX_TABLE_ROW_GROUP:
+ case BOX_TABLE_ROW:
+ case BOX_TABLE_CELL:
+ /* insert implied table */
+ assert(block->style != NULL);
+
+ ctx.ctx = c->select_ctx;
+ ctx.quirks = (c->quirks == DOM_DOCUMENT_QUIRKS_MODE_FULL);
+ ctx.base_url = c->base_url;
+ ctx.universal = c->universal;
+
+ style = nscss_get_blank_style(&ctx, block->style);
+ if (style == NULL)
+ return false;
+
+ table = box_create(NULL, style, true, block->href,
+ block->target, NULL, NULL, c->bctx);
+ if (table == NULL) {
+ css_computed_style_destroy(style);
+ return false;
+ }
+ table->type = BOX_TABLE;
+
+ if (child->prev == NULL)
+ block->children = table;
+ else
+ child->prev->next = table;
+
+ table->prev = child->prev;
+
+ while (child != NULL && (
+ child->type == BOX_TABLE_ROW_GROUP ||
+ child->type == BOX_TABLE_ROW ||
+ child->type == BOX_TABLE_CELL)) {
+ box_add_child(table, child);
+
+ next_child = child->next;
+ child->next = NULL;
+ child = next_child;
+ }
+
+ table->last->next = NULL;
+ table->next = next_child = child;
+ if (table->next != NULL)
+ table->next->prev = table;
+ else
+ block->last = table;
+ table->parent = block;
+
+ if (box_normalise_table(table, root, c) == false)
+ return false;
+ break;
+ default:
+ assert(0);
+ }
+ }
+
+ return true;
+}
+
+
+bool box_normalise_table(
+ struct box *table,
+ const struct box *root,
+ html_content * c)
+{
+ struct box *child;
+ struct box *next_child;
+ struct box *row_group;
+ css_computed_style *style;
+ struct columns col_info;
+ nscss_select_ctx ctx;
+
+ assert(table != NULL);
+ assert(table->type == BOX_TABLE);
+
+ ctx.root_style = root->style;
+
+#ifdef BOX_NORMALISE_DEBUG
+ NSLOG(netsurf, INFO, "table %p", table);
+#endif
+
+ col_info.num_columns = 1;
+ col_info.current_column = 0;
+ col_info.spans = malloc(2 * sizeof *col_info.spans);
+ if (col_info.spans == NULL)
+ return false;
+
+ col_info.spans[0].row_span = col_info.spans[1].row_span = 0;
+ col_info.spans[0].auto_row = false;
+ col_info.spans[1].auto_row = false;
+ col_info.num_rows = 0;
+
+ for (child = table->children; child != NULL; child = next_child) {
+ next_child = child->next;
+ switch (child->type) {
+ case BOX_TABLE_ROW_GROUP:
+ /* ok */
+ if (box_normalise_table_row_group(child, root,
+ &col_info, c) == false) {
+ free(col_info.spans);
+ return false;
+ }
+ break;
+ case BOX_BLOCK:
+ case BOX_INLINE_CONTAINER:
+ case BOX_TABLE:
+ case BOX_TABLE_ROW:
+ case BOX_TABLE_CELL:
+ /* insert implied table row group */
+ assert(table->style != NULL);
+
+ ctx.ctx = c->select_ctx;
+ ctx.quirks = (c->quirks == DOM_DOCUMENT_QUIRKS_MODE_FULL);
+ ctx.base_url = c->base_url;
+ ctx.universal = c->universal;
+
+ style = nscss_get_blank_style(&ctx, table->style);
+ if (style == NULL) {
+ free(col_info.spans);
+ return false;
+ }
+
+ row_group = box_create(NULL, style, true, table->href,
+ table->target, NULL, NULL, c->bctx);
+ if (row_group == NULL) {
+ css_computed_style_destroy(style);
+ free(col_info.spans);
+ return false;
+ }
+
+ row_group->type = BOX_TABLE_ROW_GROUP;
+
+ if (child->prev == NULL)
+ table->children = row_group;
+ else
+ child->prev->next = row_group;
+
+ row_group->prev = child->prev;
+
+ while (child != NULL && (
+ child->type == BOX_BLOCK ||
+ child->type == BOX_INLINE_CONTAINER ||
+ child->type == BOX_TABLE ||
+ child->type == BOX_TABLE_ROW ||
+ child->type == BOX_TABLE_CELL)) {
+ box_add_child(row_group, child);
+
+ next_child = child->next;
+ child->next = NULL;
+ child = next_child;
+ }
+
+ assert(row_group->last != NULL);
+
+ row_group->last->next = NULL;
+ row_group->next = next_child = child;
+ if (row_group->next != NULL)
+ row_group->next->prev = row_group;
+ else
+ table->last = row_group;
+ row_group->parent = table;
+
+ if (box_normalise_table_row_group(row_group, root,
+ &col_info, c) == false) {
+ free(col_info.spans);
+ return false;
+ }
+ break;
+ case BOX_INLINE:
+ case BOX_INLINE_END:
+ case BOX_INLINE_BLOCK:
+ case BOX_FLOAT_LEFT:
+ case BOX_FLOAT_RIGHT:
+ case BOX_BR:
+ case BOX_TEXT:
+ /* should have been wrapped in inline
+ container by convert_xml_to_box() */
+ assert(0);
+ break;
+ default:
+ fprintf(stderr, "%i\n", child->type);
+ assert(0);
+ }
+ }
+
+ table->columns = col_info.num_columns;
+ table->rows = col_info.num_rows;
+
+ if (table->children == NULL) {
+ struct box *row;
+
+#ifdef BOX_NORMALISE_DEBUG
+ NSLOG(netsurf, INFO,
+ "table->children == 0, creating implied row");
+#endif
+
+ assert(table->style != NULL);
+
+ ctx.ctx = c->select_ctx;
+ ctx.quirks = (c->quirks == DOM_DOCUMENT_QUIRKS_MODE_FULL);
+ ctx.base_url = c->base_url;
+ ctx.universal = c->universal;
+
+ style = nscss_get_blank_style(&ctx, table->style);
+ if (style == NULL) {
+ free(col_info.spans);
+ return false;
+ }
+
+ row_group = box_create(NULL, style, true, table->href,
+ table->target, NULL, NULL, c->bctx);
+ if (row_group == NULL) {
+ css_computed_style_destroy(style);
+ free(col_info.spans);
+ return false;
+ }
+ row_group->type = BOX_TABLE_ROW_GROUP;
+
+ style = nscss_get_blank_style(&ctx, row_group->style);
+ if (style == NULL) {
+ box_free(row_group);
+ free(col_info.spans);
+ return false;
+ }
+
+ row = box_create(NULL, style, true, row_group->href,
+ row_group->target, NULL, NULL, c->bctx);
+ if (row == NULL) {
+ css_computed_style_destroy(style);
+ box_free(row_group);
+ free(col_info.spans);
+ return false;
+ }
+ row->type = BOX_TABLE_ROW;
+
+ row->parent = row_group;
+ row_group->children = row_group->last = row;
+
+ row_group->parent = table;
+ table->children = table->last = row_group;
+
+ table->rows = 1;
+ }
+
+ if (box_normalise_table_spans(table, root, col_info.spans, c) == false) {
+ free(col_info.spans);
+ return false;
+ }
+
+ free(col_info.spans);
+
+#ifdef BOX_NORMALISE_DEBUG
+ NSLOG(netsurf, INFO, "table %p done", table);
+#endif
+
+ return true;
+}
+
+
+/**
+ * Normalise table cell column/row counts for colspan/rowspan = 0.
+ * Additionally, generate empty cells.
+ *
+ * \param table Table to process
+ * \param root root box of document
+ * \param spans Array of length table->columns for use in empty cell detection
+ * \param c Content containing table
+ * \return True on success, false on memory exhaustion.
+ */
+
+bool box_normalise_table_spans(
+ struct box *table,
+ const struct box *root,
+ struct span_info *spans,
+ html_content *c)
+{
+ struct box *table_row_group;
+ struct box *table_row;
+ struct box *table_cell;
+ unsigned int rows_left = table->rows;
+ unsigned int group_rows_left;
+ unsigned int col;
+ nscss_select_ctx ctx;
+
+ ctx.root_style = root->style;
+
+ /* Clear span data */
+ memset(spans, 0, table->columns * sizeof(struct span_info));
+
+ /* Scan table, filling in width and height of table cells with
+ * colspan = 0 and rowspan = 0. Also generate empty cells */
+ for (table_row_group = table->children;
+ table_row_group != NULL;
+ table_row_group = table_row_group->next) {
+
+ group_rows_left = table_row_group->rows;
+
+ for (table_row = table_row_group->children;
+ table_row != NULL;
+ table_row = table_row->next) {
+
+ for (table_cell = table_row->children;
+ table_cell != NULL;
+ table_cell = table_cell->next) {
+
+ /* colspan = 0 -> colspan = 1 */
+ if (table_cell->columns == 0) {
+ table_cell->columns = 1;
+ }
+
+ /* if rowspan is 0 it is expanded to
+ * the number of rows left in the row
+ * group
+ */
+ if (table_cell->rows == 0) {
+ table_cell->rows = group_rows_left;
+ }
+
+ /* limit rowspans within group */
+ if (table_cell->rows > group_rows_left) {
+ table_cell->rows = group_rows_left;
+ }
+
+ /* Record span information */
+ for (col = table_cell->start_column;
+ col < table_cell->start_column +
+ table_cell->columns; col++) {
+ spans[col].row_span = table_cell->rows;
+ }
+ }
+
+ /* Reduce span count of each column */
+ for (col = 0; col < table->columns; col++) {
+ if (spans[col].row_span == 0) {
+ unsigned int start = col;
+ css_computed_style *style;
+ struct box *cell, *prev;
+
+ /* If it's already zero, then we need
+ * to generate an empty cell for the
+ * gap in the row that spans as many
+ * columns as remain blank.
+ */
+ assert(table_row->style != NULL);
+
+ /* Find width of gap */
+ while (col < table->columns &&
+ spans[col].row_span ==
+ 0) {
+ col++;
+ }
+
+ ctx.ctx = c->select_ctx;
+ ctx.quirks = (c->quirks ==
+ DOM_DOCUMENT_QUIRKS_MODE_FULL);
+ ctx.base_url = c->base_url;
+ ctx.universal = c->universal;
+
+ style = nscss_get_blank_style(&ctx,
+ table_row->style);
+ if (style == NULL)
+ return false;
+
+ cell = box_create(NULL, style, true,
+ table_row->href,
+ table_row->target,
+ NULL, NULL, c->bctx);
+ if (cell == NULL) {
+ css_computed_style_destroy(
+ style);
+ return false;
+ }
+ cell->type = BOX_TABLE_CELL;
+
+ cell->rows = 1;
+ cell->columns = col - start;
+ cell->start_column = start;
+
+ /* Find place to insert cell */
+ for (prev = table_row->children;
+ prev != NULL;
+ prev = prev->next) {
+ if (prev->start_column +
+ prev->columns ==
+ start)
+ break;
+ if (prev->next == NULL)
+ break;
+ }
+
+ /* Insert it */
+ if (prev == NULL) {
+ if (table_row->children != NULL)
+ table_row->children->
+ prev = cell;
+ else
+ table_row->last = cell;
+
+ cell->next =
+ table_row->children;
+ table_row->children = cell;
+ } else {
+ if (prev->next != NULL)
+ prev->next->prev = cell;
+ else
+ table_row->last = cell;
+
+ cell->next = prev->next;
+ prev->next = cell;
+ cell->prev = prev;
+ }
+ cell->parent = table_row;
+ } else {
+ spans[col].row_span--;
+ }
+ }
+
+ assert(rows_left > 0);
+
+ rows_left--;
+ }
+
+ group_rows_left--;
+ }
+
+ return true;
+}
+
+
+bool box_normalise_table_row_group(
+ struct box *row_group,
+ const struct box *root,
+ struct columns *col_info,
+ html_content * c)
+{
+ struct box *child;
+ struct box *next_child;
+ struct box *row;
+ css_computed_style *style;
+ nscss_select_ctx ctx;
+ unsigned int group_row_count = 0;
+
+ assert(row_group != 0);
+ assert(row_group->type == BOX_TABLE_ROW_GROUP);
+
+ ctx.root_style = root->style;
+
+#ifdef BOX_NORMALISE_DEBUG
+ NSLOG(netsurf, INFO, "row_group %p", row_group);
+#endif
+
+ for (child = row_group->children; child != NULL; child = next_child) {
+ next_child = child->next;
+
+ switch (child->type) {
+ case BOX_TABLE_ROW:
+ /* ok */
+ group_row_count++;
+ if (box_normalise_table_row(child, root, col_info,
+ c) == false)
+ return false;
+ break;
+ case BOX_BLOCK:
+ case BOX_INLINE_CONTAINER:
+ case BOX_TABLE:
+ case BOX_TABLE_ROW_GROUP:
+ case BOX_TABLE_CELL:
+ /* insert implied table row */
+ assert(row_group->style != NULL);
+
+ ctx.ctx = c->select_ctx;
+ ctx.quirks = (c->quirks == DOM_DOCUMENT_QUIRKS_MODE_FULL);
+ ctx.base_url = c->base_url;
+ ctx.universal = c->universal;
+
+ style = nscss_get_blank_style(&ctx, row_group->style);
+ if (style == NULL)
+ return false;
+
+ row = box_create(NULL, style, true, row_group->href,
+ row_group->target, NULL, NULL, c->bctx);
+ if (row == NULL) {
+ css_computed_style_destroy(style);
+ return false;
+ }
+ row->type = BOX_TABLE_ROW;
+
+ if (child->prev == NULL)
+ row_group->children = row;
+ else
+ child->prev->next = row;
+
+ row->prev = child->prev;
+
+ while (child != NULL && (
+ child->type == BOX_BLOCK ||
+ child->type == BOX_INLINE_CONTAINER ||
+ child->type == BOX_TABLE ||
+ child->type == BOX_TABLE_ROW_GROUP ||
+ child->type == BOX_TABLE_CELL)) {
+ box_add_child(row, child);
+
+ next_child = child->next;
+ child->next = NULL;
+ child = next_child;
+ }
+
+ assert(row->last != NULL);
+
+ row->last->next = NULL;
+ row->next = next_child = child;
+ if (row->next != NULL)
+ row->next->prev = row;
+ else
+ row_group->last = row;
+ row->parent = row_group;
+
+ group_row_count++;
+ if (box_normalise_table_row(row, root, col_info,
+ c) == false)
+ return false;
+ break;
+ case BOX_INLINE:
+ case BOX_INLINE_END:
+ case BOX_INLINE_BLOCK:
+ case BOX_FLOAT_LEFT:
+ case BOX_FLOAT_RIGHT:
+ case BOX_BR:
+ case BOX_TEXT:
+ /* should have been wrapped in inline
+ container by convert_xml_to_box() */
+ assert(0);
+ break;
+ default:
+ assert(0);
+ }
+ }
+
+ if (row_group->children == NULL) {
+#ifdef BOX_NORMALISE_DEBUG
+ NSLOG(netsurf, INFO,
+ "row_group->children == 0, inserting implied row");
+#endif
+
+ assert(row_group->style != NULL);
+
+ ctx.ctx = c->select_ctx;
+ ctx.quirks = (c->quirks == DOM_DOCUMENT_QUIRKS_MODE_FULL);
+ ctx.base_url = c->base_url;
+ ctx.universal = c->universal;
+
+ style = nscss_get_blank_style(&ctx, row_group->style);
+ if (style == NULL) {
+ return false;
+ }
+
+ row = box_create(NULL, style, true, row_group->href,
+ row_group->target, NULL, NULL, c->bctx);
+ if (row == NULL) {
+ css_computed_style_destroy(style);
+ return false;
+ }
+ row->type = BOX_TABLE_ROW;
+
+ row->parent = row_group;
+ row_group->children = row_group->last = row;
+
+ group_row_count = 1;
+
+ /* Keep table's row count in sync */
+ col_info->num_rows++;
+ }
+
+ row_group->rows = group_row_count;
+
+#ifdef BOX_NORMALISE_DEBUG
+ NSLOG(netsurf, INFO, "row_group %p done", row_group);
+#endif
+
+ return true;
+}
+
+
+bool box_normalise_table_row(
+ struct box *row,
+ const struct box *root,
+ struct columns *col_info,
+ html_content * c)
+{
+ struct box *child;
+ struct box *next_child;
+ struct box *cell = NULL;
+ css_computed_style *style;
+ unsigned int i;
+ nscss_select_ctx ctx;
+
+ assert(row != NULL);
+ assert(row->type == BOX_TABLE_ROW);
+
+ ctx.root_style = root->style;
+
+#ifdef BOX_NORMALISE_DEBUG
+ NSLOG(netsurf, INFO, "row %p", row);
+#endif
+
+ for (child = row->children; child != NULL; child = next_child) {
+ next_child = child->next;
+
+ switch (child->type) {
+ case BOX_TABLE_CELL:
+ /* ok */
+ if (box_normalise_block(child, root, c) == false)
+ return false;
+ cell = child;
+ break;
+ case BOX_BLOCK:
+ case BOX_INLINE_CONTAINER:
+ case BOX_TABLE:
+ case BOX_TABLE_ROW_GROUP:
+ case BOX_TABLE_ROW:
+ /* insert implied table cell */
+ assert(row->style != NULL);
+
+ ctx.ctx = c->select_ctx;
+ ctx.quirks = (c->quirks == DOM_DOCUMENT_QUIRKS_MODE_FULL);
+ ctx.base_url = c->base_url;
+ ctx.universal = c->universal;
+
+ style = nscss_get_blank_style(&ctx, row->style);
+ if (style == NULL)
+ return false;
+
+ cell = box_create(NULL, style, true, row->href,
+ row->target, NULL, NULL, c->bctx);
+ if (cell == NULL) {
+ css_computed_style_destroy(style);
+ return false;
+ }
+ cell->type = BOX_TABLE_CELL;
+
+ if (child->prev == NULL)
+ row->children = cell;
+ else
+ child->prev->next = cell;
+
+ cell->prev = child->prev;
+
+ while (child != NULL && (
+ child->type == BOX_BLOCK ||
+ child->type == BOX_INLINE_CONTAINER ||
+ child->type == BOX_TABLE ||
+ child->type == BOX_TABLE_ROW_GROUP ||
+ child->type == BOX_TABLE_ROW)) {
+ box_add_child(cell, child);
+
+ next_child = child->next;
+ child->next = NULL;
+ child = next_child;
+ }
+
+ assert(cell->last != NULL);
+
+ cell->last->next = NULL;
+ cell->next = next_child = child;
+ if (cell->next != NULL)
+ cell->next->prev = cell;
+ else
+ row->last = cell;
+ cell->parent = row;
+
+ if (box_normalise_block(cell, root, c) == false)
+ return false;
+ break;
+ case BOX_INLINE:
+ case BOX_INLINE_END:
+ case BOX_INLINE_BLOCK:
+ case BOX_FLOAT_LEFT:
+ case BOX_FLOAT_RIGHT:
+ case BOX_BR:
+ case BOX_TEXT:
+ /* should have been wrapped in inline
+ container by convert_xml_to_box() */
+ assert(0);
+ break;
+ default:
+ assert(0);
+ }
+
+ if (calculate_table_row(col_info, cell->columns, cell->rows,
+ &cell->start_column, cell) == false)
+ return false;
+ }
+
+
+ /* Update row spanning details for all columns */
+ for (i = 0; i < col_info->num_columns; i++) {
+ if (col_info->spans[i].row_span != 0 &&
+ col_info->spans[i].auto_row == false) {
+ /* This cell spans rows, and is not an auto row.
+ * Reduce number of rows left to span */
+ col_info->spans[i].row_span--;
+ }
+ }
+
+ /* Reset current column for next row */
+ col_info->current_column = 0;
+
+ /* Increment row counter */
+ col_info->num_rows++;
+
+#ifdef BOX_NORMALISE_DEBUG
+ NSLOG(netsurf, INFO, "row %p done", row);
+#endif
+
+ return true;
+}
+
+
+/**
+ * Compute the column index at which the current cell begins.
+ * Additionally, update the column record to reflect row spanning.
+ *
+ * \param col_info Column record
+ * \param col_span Number of columns that current cell spans
+ * \param row_span Number of rows that current cell spans
+ * \param start_column Pointer to location to receive column index
+ * \param cell Box for current table cell
+ * \return true on success, false on memory exhaustion
+ */
+
+bool calculate_table_row(struct columns *col_info,
+ unsigned int col_span, unsigned int row_span,
+ unsigned int *start_column, struct box *cell)
+{
+ unsigned int cell_start_col = col_info->current_column;
+ unsigned int cell_end_col;
+ unsigned int i;
+ struct span_info *spans;
+ struct box *rg = cell->parent->parent; /* Cell's row group */
+
+ /* Skip columns with cells spanning from above */
+ /* TODO: Need to ignore cells spanning from above that belong to
+ * different row group. We don't have that info here. */
+ while (col_info->spans[cell_start_col].row_span != 0 &&
+ col_info->spans[cell_start_col].rg == rg) {
+ cell_start_col++;
+ }
+
+ /* Update current column with calculated start */
+ col_info->current_column = cell_start_col;
+
+ /* If this cell has a colspan of 0, then assume 1.
+ * No other browser supports colspan=0, anyway. */
+ if (col_span == 0)
+ col_span = 1;
+
+ cell_end_col = cell_start_col + col_span;
+
+ if (col_info->num_columns < cell_end_col) {
+ /* It appears that this row has more columns than
+ * the maximum recorded for the table so far.
+ * Allocate more span records. */
+ spans = realloc(col_info->spans,
+ sizeof *spans * (cell_end_col + 1));
+ if (spans == NULL)
+ return false;
+
+ col_info->spans = spans;
+ col_info->num_columns = cell_end_col;
+
+ /* Mark new final column as sentinel */
+ col_info->spans[cell_end_col].row_span = 0;
+ col_info->spans[cell_end_col].auto_row = false;
+ }
+
+ /* This cell may span multiple columns. If it also wants to span
+ * multiple rows, temporarily assume it spans 1 row only. This will
+ * be fixed up in box_normalise_table_spans() */
+ for (i = cell_start_col; i < cell_end_col; i++) {
+ col_info->spans[i].row_span = (row_span == 0) ? 1 : row_span;
+ col_info->spans[i].auto_row = (row_span == 0);
+ col_info->spans[i].rg = rg;
+ }
+
+ /* Update current column with calculated end. */
+ col_info->current_column = cell_end_col;
+
+ *start_column = cell_start_col;
+
+ return true;
+}
+
+
+bool box_normalise_inline_container(
+ struct box *cont,
+ const struct box *root,
+ html_content * c)
+{
+ struct box *child;
+ struct box *next_child;
+
+ assert(cont != NULL);
+ assert(cont->type == BOX_INLINE_CONTAINER);
+
+#ifdef BOX_NORMALISE_DEBUG
+ NSLOG(netsurf, INFO, "cont %p", cont);
+#endif
+
+ for (child = cont->children; child != NULL; child = next_child) {
+ next_child = child->next;
+ switch (child->type) {
+ case BOX_INLINE:
+ case BOX_INLINE_END:
+ case BOX_BR:
+ case BOX_TEXT:
+ /* ok */
+ break;
+ case BOX_INLINE_BLOCK:
+ /* ok */
+ if (box_normalise_block(child, root, c) == false)
+ return false;
+ break;
+ case BOX_FLOAT_LEFT:
+ case BOX_FLOAT_RIGHT:
+ /* ok */
+ assert(child->children != NULL);
+
+ switch (child->children->type) {
+ case BOX_BLOCK:
+ if (box_normalise_block(child->children, root,
+ c) == false)
+ return false;
+ break;
+ case BOX_TABLE:
+ if (box_normalise_table(child->children, root,
+ c) == false)
+ return false;
+ break;
+ default:
+ assert(0);
+ }
+
+ if (child->children == NULL) {
+ /* the child has destroyed itself: remove float */
+ if (child->prev == NULL)
+ child->parent->children = child->next;
+ else
+ child->prev->next = child->next;
+ if (child->next != NULL)
+ child->next->prev = child->prev;
+ else
+ child->parent->last = child->prev;
+
+ box_free(child);
+ }
+ break;
+ case BOX_BLOCK:
+ case BOX_INLINE_CONTAINER:
+ case BOX_TABLE:
+ case BOX_TABLE_ROW_GROUP:
+ case BOX_TABLE_ROW:
+ case BOX_TABLE_CELL:
+ default:
+ assert(0);
+ }
+ }
+
+#ifdef BOX_NORMALISE_DEBUG
+ NSLOG(netsurf, INFO, "cont %p done", cont);
+#endif
+
+ return true;
+}
diff --git a/content/handlers/html/box_textarea.c b/content/handlers/html/box_textarea.c
new file mode 100644
index 0000000..abd28fb
--- /dev/null
+++ b/content/handlers/html/box_textarea.c
@@ -0,0 +1,350 @@
+/*
+ * Copyright 2013 Michael Drake <tlsa(a)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
+ * Box tree treeview box replacement (implementation).
+ */
+
+#include <dom/dom.h>
+
+#include "utils/config.h"
+#include "utils/log.h"
+#include "netsurf/keypress.h"
+#include "desktop/textarea.h"
+
+#include "html/box_textarea.h"
+#include "html/font.h"
+#include "html/form_internal.h"
+
+
+bool box_textarea_keypress(html_content *html, struct box *box, uint32_t key)
+{
+ struct form_control *gadget = box->gadget;
+ struct textarea *ta = gadget->data.text.ta;
+ struct form* form = box->gadget->form;
+ struct content *c = (struct content *) html;
+
+ assert(ta != NULL);
+
+ if (gadget->type != GADGET_TEXTAREA) {
+ switch (key) {
+ case NS_KEY_NL:
+ case NS_KEY_CR:
+ if (form)
+ form_submit(content_get_url(c), html->bw,
+ form, 0);
+ return true;
+
+ case NS_KEY_TAB:
+ {
+ struct form_control *next_input;
+ /* Find next text entry field that is actually
+ * displayed (i.e. has an associated box) */
+ for (next_input = gadget->next;
+ next_input &&
+ ((next_input->type != GADGET_TEXTBOX &&
+ next_input->type != GADGET_TEXTAREA &&
+ next_input->type != GADGET_PASSWORD) ||
+ !next_input->box);
+ next_input = next_input->next)
+ ;
+ if (!next_input)
+ return true;
+
+ textarea_set_caret(ta, -1);
+ textarea_set_caret(next_input->data.text.ta, 0);
+ }
+ return true;
+
+ case NS_KEY_SHIFT_TAB:
+ {
+ struct form_control *prev_input;
+ /* Find previous text entry field that is actually
+ * displayed (i.e. has an associated box) */
+ for (prev_input = gadget->prev;
+ prev_input &&
+ ((prev_input->type != GADGET_TEXTBOX &&
+ prev_input->type != GADGET_TEXTAREA &&
+ prev_input->type != GADGET_PASSWORD) ||
+ !prev_input->box);
+ prev_input = prev_input->prev)
+ ;
+ if (!prev_input)
+ return true;
+
+ textarea_set_caret(ta, -1);
+ textarea_set_caret(prev_input->data.text.ta, 0);
+ }
+ return true;
+
+ default:
+ /* Pass to textarea widget */
+ break;
+ }
+ }
+
+ return textarea_keypress(ta, key);
+}
+
+
+/**
+ * Callback for html form textareas.
+ */
+static void box_textarea_callback(void *data, struct textarea_msg *msg)
+{
+ struct form_textarea_data *d = data;
+ struct form_control *gadget = d->gadget;
+ struct html_content *html = d->gadget->html;
+ struct box *box = gadget->box;
+
+ switch (msg->type) {
+ case TEXTAREA_MSG_DRAG_REPORT:
+ if (msg->data.drag == TEXTAREA_DRAG_NONE) {
+ /* Textarea drag finished */
+ html_drag_type drag_type = HTML_DRAG_NONE;
+ union html_drag_owner drag_owner;
+ drag_owner.no_owner = true;
+
+ html_set_drag_type(html, drag_type, drag_owner,
+ NULL);
+ } else {
+ /* Textarea drag started */
+ struct rect rect = {
+ .x0 = INT_MIN,
+ .y0 = INT_MIN,
+ .x1 = INT_MAX,
+ .y1 = INT_MAX
+ };
+ union html_drag_owner drag_owner;
+ drag_owner.textarea = box;
+
+ switch (msg->data.drag) {
+ case TEXTAREA_DRAG_SCROLLBAR:
+ html_set_drag_type(html,
+ HTML_DRAG_TEXTAREA_SCROLLBAR,
+ drag_owner,
+ &rect);
+ break;
+
+ case TEXTAREA_DRAG_SELECTION:
+ html_set_drag_type(html,
+ HTML_DRAG_TEXTAREA_SELECTION,
+ drag_owner,
+ &rect);
+ break;
+
+ default:
+ NSLOG(netsurf, INFO,
+ "Drag type %d not handled.",
+ msg->data.drag);
+ /* This is a logic faliure in the
+ * front end code so abort.
+ */
+ assert(0);
+ break;
+ }
+ }
+ break;
+
+ case TEXTAREA_MSG_REDRAW_REQUEST:
+ {
+ /* Request redraw of the required textarea rectangle */
+ int x, y;
+
+ if (html->reflowing == true) {
+ /* Can't redraw during layout, and it will
+ * be redrawn after layout anyway. */
+ break;
+ }
+
+ box_coords(box, &x, &y);
+
+ content__request_redraw((struct content *)html,
+ x + msg->data.redraw.x0,
+ y + msg->data.redraw.y0,
+ msg->data.redraw.x1 - msg->data.redraw.x0,
+ msg->data.redraw.y1 - msg->data.redraw.y0);
+ }
+ break;
+
+ case TEXTAREA_MSG_SELECTION_REPORT:
+ if (msg->data.selection.have_selection) {
+ /* Textarea now has a selection */
+ union html_selection_owner sel_owner;
+ sel_owner.textarea = box;
+
+ html_set_selection(html, HTML_SELECTION_TEXTAREA,
+ sel_owner,
+ msg->data.selection.read_only);
+ } else {
+ /* The textarea now has no selection */
+ union html_selection_owner sel_owner;
+ sel_owner.none = true;
+
+ html_set_selection(html, HTML_SELECTION_NONE,
+ sel_owner, true);
+ }
+ break;
+
+ case TEXTAREA_MSG_CARET_UPDATE:
+ if (html->bw == NULL)
+ break;
+
+ if (msg->data.caret.type == TEXTAREA_CARET_HIDE) {
+ union html_focus_owner focus_owner;
+ focus_owner.textarea = box;
+ html_set_focus(html, HTML_FOCUS_TEXTAREA,
+ focus_owner, true, 0, 0, 0, NULL);
+ } else {
+ union html_focus_owner focus_owner;
+ focus_owner.textarea = box;
+ html_set_focus(html, HTML_FOCUS_TEXTAREA,
+ focus_owner, false,
+ msg->data.caret.pos.x,
+ msg->data.caret.pos.y,
+ msg->data.caret.pos.height,
+ msg->data.caret.pos.clip);
+ }
+ break;
+
+ case TEXTAREA_MSG_TEXT_MODIFIED:
+ form_gadget_update_value(gadget,
+ strndup(msg->data.modified.text,
+ msg->data.modified.len));
+ break;
+ }
+}
+
+
+/* Exported interface, documented in box_textarea.h */
+bool box_textarea_create_textarea(html_content *html,
+ struct box *box, struct dom_node *node)
+{
+ dom_string *dom_text = NULL;
+ dom_exception err;
+ textarea_setup ta_setup;
+ textarea_flags ta_flags;
+ plot_font_style_t fstyle = {
+ .family = PLOT_FONT_FAMILY_SANS_SERIF,
+ .size = 10 * FONT_SIZE_SCALE,
+ .weight = 400,
+ .flags = FONTF_NONE,
+ .background = 0,
+ .foreground = 0,
+ };
+ bool read_only = false;
+ bool disabled = false;
+ struct form_control *gadget = box->gadget;
+ const char *text;
+
+ assert(gadget != NULL);
+ assert(gadget->type == GADGET_TEXTAREA ||
+ gadget->type == GADGET_TEXTBOX ||
+ gadget->type == GADGET_PASSWORD);
+
+ if (gadget->type == GADGET_TEXTAREA) {
+ dom_html_text_area_element *textarea =
+ (dom_html_text_area_element *) node;
+ ta_flags = TEXTAREA_MULTILINE;
+
+ err = dom_html_text_area_element_get_read_only(
+ textarea, &read_only);
+ if (err != DOM_NO_ERR)
+ return false;
+
+ err = dom_html_text_area_element_get_disabled(
+ textarea, &disabled);
+ if (err != DOM_NO_ERR)
+ return false;
+
+ /* Get the textarea's initial content */
+ err = dom_html_text_area_element_get_value(textarea, &dom_text);
+ if (err != DOM_NO_ERR)
+ return false;
+
+ } else {
+ dom_html_input_element *input = (dom_html_input_element *) node;
+
+ err = dom_html_input_element_get_read_only(
+ input, &read_only);
+ if (err != DOM_NO_ERR)
+ return false;
+
+ err = dom_html_input_element_get_disabled(
+ input, &disabled);
+ if (err != DOM_NO_ERR)
+ return false;
+
+ if (gadget->type == GADGET_PASSWORD)
+ ta_flags = TEXTAREA_PASSWORD;
+ else
+ ta_flags = TEXTAREA_DEFAULT;
+
+ /* Get initial text */
+ err = dom_html_input_element_get_value(input, &dom_text);
+ if (err != DOM_NO_ERR)
+ return false;
+ }
+
+ if (dom_text != NULL) {
+ text = dom_string_data(dom_text);
+ } else {
+ /* No initial text, or failed reading it;
+ * use a blank string */
+ text = "";
+ }
+
+ if (read_only || disabled)
+ ta_flags |= TEXTAREA_READONLY;
+
+ gadget->data.text.data.gadget = gadget;
+
+ /* Reset to correct values by layout */
+ ta_setup.width = 200;
+ ta_setup.height = 20;
+ ta_setup.pad_top = 4;
+ ta_setup.pad_right = 4;
+ ta_setup.pad_bottom = 4;
+ ta_setup.pad_left = 4;
+
+ /* Set remaining data */
+ ta_setup.border_width = 0;
+ ta_setup.border_col = 0x000000;
+ ta_setup.text = fstyle;
+ ta_setup.text.background = NS_TRANSPARENT;
+ /* Make selected text either black or white, as gives greatest contrast
+ * with background colour. */
+ ta_setup.selected_bg = fstyle.foreground;
+ ta_setup.selected_text = colour_to_bw_furthest(ta_setup.selected_bg);
+
+ /* Hand reference to dom text over to gadget */
+ gadget->data.text.initial = dom_text;
+
+ gadget->data.text.ta = textarea_create(ta_flags, &ta_setup,
+ box_textarea_callback, &gadget->data.text.data);
+
+ if (gadget->data.text.ta == NULL) {
+ return false;
+ }
+
+ if (!textarea_set_text(gadget->data.text.ta, text))
+ return false;
+
+ return true;
+}
diff --git a/content/handlers/html/box_textarea.h b/content/handlers/html/box_textarea.h
new file mode 100644
index 0000000..e2b02e8
--- /dev/null
+++ b/content/handlers/html/box_textarea.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2013 Michael Drake <tlsa(a)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
+ * Box tree treeview box replacement (interface).
+ */
+
+#ifndef NETSURF_HTML_BOX_TEXTAREA_H
+#define NETSURF_HTML_BOX_TEXTAREA_H
+
+
+#include "html/box.h"
+#include "html/html_internal.h"
+
+struct dom_node;
+
+/**
+ * Create textarea widget for a form element
+ *
+ * \param html html content object
+ * \param box box with gadget to be given textarea widget
+ * \param node DOM node for form element
+ */
+bool box_textarea_create_textarea(html_content *html,
+ struct box *box, struct dom_node *node);
+
+
+/**
+ * Handle form textarea keypress input
+ *
+ * \param html html content object
+ * \param box box with textarea widget
+ * \param key keypress
+ * \return true iff keypress handled
+ */
+bool box_textarea_keypress(html_content *html, struct box *box, uint32_t key);
+
+#endif
diff --git a/content/handlers/html/font.c b/content/handlers/html/font.c
new file mode 100644
index 0000000..9dbf592
--- /dev/null
+++ b/content/handlers/html/font.c
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2009 John-Mark Bell <jmb(a)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
+ *
+ * HTML internal font handling implementation.
+ */
+
+#include "utils/nsoption.h"
+#include "netsurf/plot_style.h"
+#include "css/utils.h"
+
+#include "html/font.h"
+
+/**
+ * Map a generic CSS font family to a generic plot font family
+ *
+ * \param css Generic CSS font family
+ * \return Plot font family
+ */
+static plot_font_generic_family_t
+plot_font_generic_family(enum css_font_family_e css)
+{
+ plot_font_generic_family_t plot;
+
+ switch (css) {
+ case CSS_FONT_FAMILY_SERIF:
+ plot = PLOT_FONT_FAMILY_SERIF;
+ break;
+ case CSS_FONT_FAMILY_MONOSPACE:
+ plot = PLOT_FONT_FAMILY_MONOSPACE;
+ break;
+ case CSS_FONT_FAMILY_CURSIVE:
+ plot = PLOT_FONT_FAMILY_CURSIVE;
+ break;
+ case CSS_FONT_FAMILY_FANTASY:
+ plot = PLOT_FONT_FAMILY_FANTASY;
+ break;
+ case CSS_FONT_FAMILY_SANS_SERIF:
+ default:
+ plot = PLOT_FONT_FAMILY_SANS_SERIF;
+ break;
+ }
+
+ return plot;
+}
+
+/**
+ * Map a CSS font weight to a plot weight value
+ *
+ * \param css CSS font weight
+ * \return Plot weight
+ */
+static int plot_font_weight(enum css_font_weight_e css)
+{
+ int weight;
+
+ switch (css) {
+ case CSS_FONT_WEIGHT_100:
+ weight = 100;
+ break;
+ case CSS_FONT_WEIGHT_200:
+ weight = 200;
+ break;
+ case CSS_FONT_WEIGHT_300:
+ weight = 300;
+ break;
+ case CSS_FONT_WEIGHT_400:
+ case CSS_FONT_WEIGHT_NORMAL:
+ default:
+ weight = 400;
+ break;
+ case CSS_FONT_WEIGHT_500:
+ weight = 500;
+ break;
+ case CSS_FONT_WEIGHT_600:
+ weight = 600;
+ break;
+ case CSS_FONT_WEIGHT_700:
+ case CSS_FONT_WEIGHT_BOLD:
+ weight = 700;
+ break;
+ case CSS_FONT_WEIGHT_800:
+ weight = 800;
+ break;
+ case CSS_FONT_WEIGHT_900:
+ weight = 900;
+ break;
+ }
+
+ return weight;
+}
+
+/**
+ * Map a CSS font style and font variant to plot font flags
+ *
+ * \param style CSS font style
+ * \param variant CSS font variant
+ * \return Computed plot flags
+ */
+static plot_font_flags_t plot_font_flags(enum css_font_style_e style,
+ enum css_font_variant_e variant)
+{
+ plot_font_flags_t flags = FONTF_NONE;
+
+ if (style == CSS_FONT_STYLE_ITALIC)
+ flags |= FONTF_ITALIC;
+ else if (style == CSS_FONT_STYLE_OBLIQUE)
+ flags |= FONTF_OBLIQUE;
+
+ if (variant == CSS_FONT_VARIANT_SMALL_CAPS)
+ flags |= FONTF_SMALLCAPS;
+
+ return flags;
+}
+
+
+/* exported function documented in html/font.h */
+void font_plot_style_from_css(
+ const nscss_len_ctx *len_ctx,
+ const css_computed_style *css,
+ plot_font_style_t *fstyle)
+{
+ lwc_string **families;
+ css_fixed length = 0;
+ css_unit unit = CSS_UNIT_PX;
+ css_color col;
+
+ fstyle->family = plot_font_generic_family(
+ css_computed_font_family(css, &families));
+
+ css_computed_font_size(css, &length, &unit);
+ fstyle->size = FIXTOINT(FMUL(nscss_len2pt(len_ctx, length, unit),
+ INTTOFIX(FONT_SIZE_SCALE)));
+
+ /* Clamp font size to configured minimum */
+ if (fstyle->size < (nsoption_int(font_min_size) * FONT_SIZE_SCALE) / 10)
+ fstyle->size = (nsoption_int(font_min_size) * FONT_SIZE_SCALE) / 10;
+
+ fstyle->weight = plot_font_weight(css_computed_font_weight(css));
+ fstyle->flags = plot_font_flags(css_computed_font_style(css),
+ css_computed_font_variant(css));
+
+ css_computed_color(css, &col);
+ fstyle->foreground = nscss_color_to_ns(col);
+ fstyle->background = 0;
+}
diff --git a/content/handlers/html/font.h b/content/handlers/html/font.h
new file mode 100644
index 0000000..5f69ee7
--- /dev/null
+++ b/content/handlers/html/font.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2014 Vincent Sanders <vince(a)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
+ * Internal font handling interfaces.
+ *
+ * These functions provide font related services. They all work on
+ * UTF-8 strings with lengths given.
+ */
+
+#ifndef NETSURF_HTML_FONT_H
+#define NETSURF_HTML_FONT_H
+
+struct plot_font_style;
+
+/**
+ * Populate a font style using data from a computed CSS style
+ *
+ * \param len_ctx Length conversion context
+ * \param css Computed style to consider
+ * \param fstyle Font style to populate
+ */
+void font_plot_style_from_css(const nscss_len_ctx *len_ctx,
+ const css_computed_style *css,
+ struct plot_font_style *fstyle);
+
+#endif
diff --git a/content/handlers/html/form.c b/content/handlers/html/form.c
new file mode 100644
index 0000000..9fe2e77
--- /dev/null
+++ b/content/handlers/html/form.c
@@ -0,0 +1,1895 @@
+/*
+ * Copyright 2004 James Bursa <bursa(a)users.sourceforge.net>
+ * Copyright 2003 Phil Mellor <monkeyson(a)users.sourceforge.net>
+ * Copyright 2004 John Tytgat <joty(a)netsurf-browser.org>
+ * Copyright 2005-9 John-Mark Bell <jmb(a)netsurf-browser.org>
+ * Copyright 2009 Paul Blokus <paul_pl(a)users.sourceforge.net>
+ * Copyright 2010 Michael Drake <tlsa(a)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
+ * Form handling functions (implementation).
+ */
+
+#include <assert.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <dom/dom.h>
+
+#include "utils/corestrings.h"
+#include "utils/log.h"
+#include "utils/messages.h"
+#include "utils/talloc.h"
+#include "utils/url.h"
+#include "utils/utf8.h"
+#include "utils/ascii.h"
+#include "netsurf/browser_window.h"
+#include "netsurf/mouse.h"
+#include "netsurf/plotters.h"
+#include "netsurf/misc.h"
+#include "content/fetch.h"
+#include "content/hlcache.h"
+#include "css/utils.h"
+#include "desktop/knockout.h"
+#include "desktop/scrollbar.h"
+#include "desktop/textarea.h"
+#include "desktop/gui_internal.h"
+
+#include "html/box.h"
+#include "html/font.h"
+#include "html/form_internal.h"
+#include "html/html.h"
+#include "html/html_internal.h"
+#include "html/layout.h"
+
+#define MAX_SELECT_HEIGHT 210
+#define SELECT_LINE_SPACING 0.2
+#define SELECT_BORDER_WIDTH 1
+#define SELECT_SELECTED_COLOUR 0xDB9370
+
+struct form_select_menu {
+ int line_height;
+ int width, height;
+ struct scrollbar *scrollbar;
+ int f_size;
+ bool scroll_capture;
+ select_menu_redraw_callback callback;
+ void *client_data;
+ struct content *c;
+};
+
+static plot_style_t plot_style_fill_selected = {
+ .fill_type = PLOT_OP_TYPE_SOLID,
+ .fill_colour = SELECT_SELECTED_COLOUR,
+};
+
+static plot_font_style_t plot_fstyle_entry = {
+ .family = PLOT_FONT_FAMILY_SANS_SERIF,
+ .weight = 400,
+ .flags = FONTF_NONE,
+ .background = 0xffffff,
+ .foreground = 0x000000,
+};
+
+static char *form_acceptable_charset(struct form *form);
+static char *form_encode_item(const char *item, uint32_t len, const char *charset,
+ const char *fallback);
+static void form_select_menu_clicked(struct form_control *control,
+ int x, int y);
+static void form_select_menu_scroll_callback(void *client_data,
+ struct scrollbar_msg_data *scrollbar_data);
+
+/* exported interface documented in html/form_internal.h */
+struct form *form_new(void *node, const char *action, const char *target,
+ form_method method, const char *charset,
+ const char *doc_charset)
+{
+ struct form *form;
+
+ form = calloc(1, sizeof *form);
+ if (!form)
+ return NULL;
+
+ form->action = strdup(action != NULL ? action : "");
+ if (form->action == NULL) {
+ free(form);
+ return NULL;
+ }
+
+ form->target = target != NULL ? strdup(target) : NULL;
+ if (target != NULL && form->target == NULL) {
+ free(form->action);
+ free(form);
+ return NULL;
+ }
+
+ form->method = method;
+
+ form->accept_charsets = charset != NULL ? strdup(charset) : NULL;
+ if (charset != NULL && form->accept_charsets == NULL) {
+ free(form->target);
+ free(form->action);
+ free(form);
+ return NULL;
+ }
+
+ form->document_charset = doc_charset != NULL ? strdup(doc_charset)
+ : NULL;
+ if (doc_charset && form->document_charset == NULL) {
+ free(form->accept_charsets);
+ free(form->target);
+ free(form->action);
+ free(form);
+ return NULL;
+ }
+
+ form->node = node;
+
+ return form;
+}
+
+
+/* exported interface documented in html/form_internal.h */
+void form_free(struct form *form)
+{
+ struct form_control *c, *d;
+
+ for (c = form->controls; c != NULL; c = d) {
+ d = c->next;
+
+ form_free_control(c);
+ }
+
+ free(form->action);
+ free(form->target);
+ free(form->accept_charsets);
+ free(form->document_charset);
+
+ free(form);
+}
+
+/* exported interface documented in html/form_internal.h */
+struct form_control *form_new_control(void *node, form_control_type type)
+{
+ struct form_control *control;
+
+ control = calloc(1, sizeof *control);
+ if (control == NULL)
+ return NULL;
+
+ control->node = node;
+ control->type = type;
+
+ return control;
+}
+
+
+/**
+ * Add a control to the list of controls in a form.
+ *
+ * \param form The form to add the control to
+ * \param control The control to add
+ */
+void form_add_control(struct form *form, struct form_control *control)
+{
+ if (form == NULL) {
+ return;
+ }
+
+ control->form = form;
+
+ if (form->controls != NULL) {
+ assert(form->last_control);
+
+ form->last_control->next = control;
+ control->prev = form->last_control;
+ control->next = NULL;
+ form->last_control = control;
+ } else {
+ form->controls = form->last_control = control;
+ }
+}
+
+
+/**
+ * Free a struct form_control.
+ *
+ * \param control structure to free
+ */
+void form_free_control(struct form_control *control)
+{
+ struct form_control *c;
+ assert(control != NULL);
+
+ NSLOG(netsurf, INFO, "Control:%p name:%p value:%p initial:%p",
+ control, control->name, control->value, control->initial_value);
+ free(control->name);
+ free(control->value);
+ free(control->initial_value);
+
+ if (control->type == GADGET_SELECT) {
+ struct form_option *option, *next;
+
+ for (option = control->data.select.items; option;
+ option = next) {
+ next = option->next;
+ NSLOG(netsurf, INFO,
+ "select option:%p text:%p value:%p", option,
+ option->text, option->value);
+ free(option->text);
+ free(option->value);
+ free(option);
+ }
+ if (control->data.select.menu != NULL) {
+ form_free_select_menu(control);
+ }
+ }
+
+ if (control->type == GADGET_TEXTAREA ||
+ control->type == GADGET_TEXTBOX ||
+ control->type == GADGET_PASSWORD) {
+
+ if (control->data.text.initial != NULL) {
+ dom_string_unref(control->data.text.initial);
+ }
+
+ if (control->data.text.ta != NULL) {
+ textarea_destroy(control->data.text.ta);
+ }
+ }
+
+ /* unlink the control from the form */
+ if (control->form != NULL) {
+ for (c = control->form->controls; c != NULL; c = c->next) {
+ if (c->next == control) {
+ c->next = control->next;
+ if (control->form->last_control == control)
+ control->form->last_control = c;
+ break;
+ }
+ if (c == control) {
+ /* can only happen if control was first control */
+ control->form->controls = control->next;
+ if (control->form->last_control == control)
+ control->form->controls =
+ control->form->last_control = NULL;
+ break;
+ }
+ }
+ }
+
+ free(control);
+}
+
+
+/**
+ * Add an option to a form select control.
+ *
+ * \param control form control of type GADGET_SELECT
+ * \param value value of option, used directly (not copied)
+ * \param text text for option, used directly (not copied)
+ * \param selected this option is selected
+ * \param node the DOM node this option is associated with
+ * \return true on success, false on memory exhaustion
+ */
+bool form_add_option(struct form_control *control, char *value, char *text,
+ bool selected, void *node)
+{
+ struct form_option *option;
+
+ assert(control);
+ assert(control->type == GADGET_SELECT);
+
+ option = calloc(1, sizeof *option);
+ if (!option)
+ return false;
+
+ option->value = value;
+ option->text = text;
+
+ /* add to linked list */
+ if (control->data.select.items == 0)
+ control->data.select.items = option;
+ else
+ control->data.select.last_item->next = option;
+ control->data.select.last_item = option;
+
+ /* set selected */
+ if (selected && (control->data.select.num_selected == 0 ||
+ control->data.select.multiple)) {
+ option->selected = option->initial_selected = true;
+ control->data.select.num_selected++;
+ control->data.select.current = option;
+ }
+
+ control->data.select.num_items++;
+
+ option->node = node;
+
+ return true;
+}
+
+
+/* exported interface documented in html/form_internal.h */
+bool form_successful_controls_dom(struct form *_form,
+ struct form_control *_submit_button,
+ struct fetch_multipart_data **successful_controls)
+{
+ dom_html_form_element *form = _form->node;
+ dom_html_element *submit_button = (_submit_button != NULL) ? _submit_button->node : NULL;
+ dom_html_collection *form_elements = NULL;
+ dom_html_options_collection *options = NULL;
+ dom_node *form_element = NULL, *option_element = NULL;
+ dom_exception err;
+ dom_string *nodename = NULL, *inputname = NULL, *inputvalue = NULL, *inputtype = NULL;
+ struct fetch_multipart_data sentinel, *last_success, *success_new;
+ bool had_submit = false, element_disabled, checked;
+ char *charset, *rawfile_temp = NULL, *basename;
+ uint32_t index, element_count;
+ struct image_input_coords *coords;
+
+ last_success = &sentinel;
+ sentinel.next = NULL;
+
+ /** \todo Replace this call with something DOMish */
+ charset = form_acceptable_charset(_form);
+ if (charset == NULL) {
+ NSLOG(netsurf, INFO, "failed to find charset");
+ return false;
+ }
+
+#define ENCODE_ITEM(i) (((i) == NULL) ? ( \
+ form_encode_item("", 0, charset, _form->document_charset) \
+ ):( \
+ form_encode_item(dom_string_data(i), dom_string_byte_length(i), \
+ charset, _form->document_charset) \
+ ))
+
+ err = dom_html_form_element_get_elements(form, &form_elements);
+
+ if (err != DOM_NO_ERR) {
+ NSLOG(netsurf, INFO, "Could not get form elements");
+ goto dom_no_memory;
+ }
+
+
+ err = dom_html_collection_get_length(form_elements, &element_count);
+
+ if (err != DOM_NO_ERR) {
+ NSLOG(netsurf, INFO, "Could not get form element count");
+ goto dom_no_memory;
+ }
+
+ for (index = 0; index < element_count; index++) {
+ if (form_element != NULL) {
+ dom_node_unref(form_element);
+ form_element = NULL;
+ }
+ if (nodename != NULL) {
+ dom_string_unref(nodename);
+ nodename = NULL;
+ }
+ if (inputname != NULL) {
+ dom_string_unref(inputname);
+ inputname = NULL;
+ }
+ if (inputvalue != NULL) {
+ dom_string_unref(inputvalue);
+ inputvalue = NULL;
+ }
+ if (inputtype != NULL) {
+ dom_string_unref(inputtype);
+ inputtype = NULL;
+ }
+ if (options != NULL) {
+ dom_html_options_collection_unref(options);
+ options = NULL;
+ }
+ err = dom_html_collection_item(form_elements,
+ index, &form_element);
+ if (err != DOM_NO_ERR) {
+ NSLOG(netsurf, INFO,
+ "Could not retrieve form element %d", index);
+ goto dom_no_memory;
+ }
+
+ /* Form elements are one of:
+ * HTMLButtonElement
+ * HTMLInputElement
+ * HTMLTextAreaElement
+ * HTMLSelectElement
+ */
+ err = dom_node_get_node_name(form_element, &nodename);
+ if (err != DOM_NO_ERR) {
+ NSLOG(netsurf, INFO, "Could not get node name");
+ goto dom_no_memory;
+ }
+
+ if (dom_string_isequal(nodename, corestring_dom_TEXTAREA)) {
+ err = dom_html_text_area_element_get_disabled(
+ (dom_html_text_area_element *)form_element,
+ &element_disabled);
+ if (err != DOM_NO_ERR) {
+ NSLOG(netsurf, INFO,
+ "Could not get text area disabled property");
+ goto dom_no_memory;
+ }
+ err = dom_html_text_area_element_get_name(
+ (dom_html_text_area_element *)form_element,
+ &inputname);
+ if (err != DOM_NO_ERR) {
+ NSLOG(netsurf, INFO,
+ "Could not get text area name property");
+ goto dom_no_memory;
+ }
+ } else if (dom_string_isequal(nodename, corestring_dom_SELECT)) {
+ err = dom_html_select_element_get_disabled(
+ (dom_html_select_element *)form_element,
+ &element_disabled);
+ if (err != DOM_NO_ERR) {
+ NSLOG(netsurf, INFO,
+ "Could not get select disabled property");
+ goto dom_no_memory;
+ }
+ err = dom_html_select_element_get_name(
+ (dom_html_select_element *)form_element,
+ &inputname);
+ if (err != DOM_NO_ERR) {
+ NSLOG(netsurf, INFO,
+ "Could not get select name property");
+ goto dom_no_memory;
+ }
+ } else if (dom_string_isequal(nodename, corestring_dom_INPUT)) {
+ err = dom_html_input_element_get_disabled(
+ (dom_html_input_element *)form_element,
+ &element_disabled);
+ if (err != DOM_NO_ERR) {
+ NSLOG(netsurf, INFO,
+ "Could not get input disabled property");
+ goto dom_no_memory;
+ }
+ err = dom_html_input_element_get_name(
+ (dom_html_input_element *)form_element,
+ &inputname);
+ if (err != DOM_NO_ERR) {
+ NSLOG(netsurf, INFO,
+ "Could not get input name property");
+ goto dom_no_memory;
+ }
+ } else if (dom_string_isequal(nodename, corestring_dom_BUTTON)) {
+ err = dom_html_button_element_get_disabled(
+ (dom_html_button_element *)form_element,
+ &element_disabled);
+ if (err != DOM_NO_ERR) {
+ NSLOG(netsurf, INFO,
+ "Could not get button disabled property");
+ goto dom_no_memory;
+ }
+ err = dom_html_button_element_get_name(
+ (dom_html_button_element *)form_element,
+ &inputname);
+ if (err != DOM_NO_ERR) {
+ NSLOG(netsurf, INFO,
+ "Could not get button name property");
+ goto dom_no_memory;
+ }
+ } else {
+ /* Unknown element type came through! */
+ NSLOG(netsurf, INFO, "Unknown element type: %*s",
+ (int)dom_string_byte_length(nodename),
+ dom_string_data(nodename));
+ goto dom_no_memory;
+ }
+ if (element_disabled)
+ continue;
+ if (inputname == NULL)
+ continue;
+
+ if (dom_string_isequal(nodename, corestring_dom_TEXTAREA)) {
+ err = dom_html_text_area_element_get_value(
+ (dom_html_text_area_element *)form_element,
+ &inputvalue);
+ if (err != DOM_NO_ERR) {
+ NSLOG(netsurf, INFO,
+ "Could not get text area content");
+ goto dom_no_memory;
+ }
+ } else if (dom_string_isequal(nodename, corestring_dom_SELECT)) {
+ uint32_t options_count, option_index;
+ err = dom_html_select_element_get_options(
+ (dom_html_select_element *)form_element,
+ &options);
+ if (err != DOM_NO_ERR) {
+ NSLOG(netsurf, INFO,
+ "Could not get select options collection");
+ goto dom_no_memory;
+ }
+ err = dom_html_options_collection_get_length(
+ options, &options_count);
+ if (err != DOM_NO_ERR) {
+ NSLOG(netsurf, INFO,
+ "Could not get select options collection length");
+ goto dom_no_memory;
+ }
+ for(option_index = 0; option_index < options_count;
+ ++option_index) {
+ bool selected;
+ if (option_element != NULL) {
+ dom_node_unref(option_element);
+ option_element = NULL;
+ }
+ if (inputvalue != NULL) {
+ dom_string_unref(inputvalue);
+ inputvalue = NULL;
+ }
+ err = dom_html_options_collection_item(
+ options, option_index, &option_element);
+ if (err != DOM_NO_ERR) {
+ NSLOG(netsurf, INFO,
+ "Could not get options item %d",
+ option_index);
+ goto dom_no_memory;
+ }
+ err = dom_html_option_element_get_selected(
+ (dom_html_option_element *)option_element,
+ &selected);
+ if (err != DOM_NO_ERR) {
+ NSLOG(netsurf, INFO,
+ "Could not get option selected property");
+ goto dom_no_memory;
+ }
+ if (!selected)
+ continue;
+ err = dom_html_option_element_get_value(
+ (dom_html_option_element *)option_element,
+ &inputvalue);
+ if (err != DOM_NO_ERR) {
+ NSLOG(netsurf, INFO,
+ "Could not get option value");
+ goto dom_no_memory;
+ }
+
+ success_new = calloc(1, sizeof(*success_new));
+ if (success_new == NULL) {
+ NSLOG(netsurf, INFO,
+ "Could not allocate data for option");
+ goto dom_no_memory;
+ }
+
+ last_success->next = success_new;
+ last_success = success_new;
+
+ success_new->name = ENCODE_ITEM(inputname);
+ if (success_new->name == NULL) {
+ NSLOG(netsurf, INFO,
+ "Could not encode name for option");
+ goto dom_no_memory;
+ }
+ success_new->value = ENCODE_ITEM(inputvalue);
+ if (success_new->value == NULL) {
+ NSLOG(netsurf, INFO,
+ "Could not encode value for option");
+ goto dom_no_memory;
+ }
+ }
+ continue;
+ } else if (dom_string_isequal(nodename, corestring_dom_BUTTON)) {
+ err = dom_html_button_element_get_type(
+ (dom_html_button_element *) form_element,
+ &inputtype);
+ if (err != DOM_NO_ERR) {
+ NSLOG(netsurf, INFO,
+ "Could not get button element type");
+ goto dom_no_memory;
+ }
+ if (dom_string_caseless_isequal(
+ inputtype, corestring_dom_submit)) {
+
+ if (submit_button == NULL && !had_submit) {
+ /* no button used, and first submit
+ * node found, so use it
+ */
+ had_submit = true;
+ } else if ((dom_node *)submit_button !=
+ (dom_node *)form_element) {
+ continue;
+ }
+
+ err = dom_html_button_element_get_value(
+ (dom_html_button_element *)form_element,
+ &inputvalue);
+ if (err != DOM_NO_ERR) {
+ NSLOG(netsurf, INFO,
+ "Could not get submit button value");
+ goto dom_no_memory;
+ }
+ /* Drop through to report successful button */
+ } else {
+ continue;
+ }
+ } else if (dom_string_isequal(nodename, corestring_dom_INPUT)) {
+ /* Things to consider here */
+ /* Buttons -- only if the successful control */
+ /* radio and checkbox -- only if selected */
+ /* file -- also get the rawfile */
+ /* everything else -- just value */
+ err = dom_html_input_element_get_type(
+ (dom_html_input_element *) form_element,
+ &inputtype);
+ if (err != DOM_NO_ERR) {
+ NSLOG(netsurf, INFO,
+ "Could not get input element type");
+ goto dom_no_memory;
+ }
+ if (dom_string_caseless_isequal(
+ inputtype, corestring_dom_submit)) {
+
+ if (submit_button == NULL && !had_submit) {
+ /* no button used, and first submit
+ * node found, so use it
+ */
+ had_submit = true;
+ } else if ((dom_node *)submit_button !=
+ (dom_node *)form_element) {
+ continue;
+ }
+
+ err = dom_html_input_element_get_value(
+ (dom_html_input_element *)form_element,
+ &inputvalue);
+ if (err != DOM_NO_ERR) {
+ NSLOG(netsurf, INFO,
+ "Could not get submit button value");
+ goto dom_no_memory;
+ }
+ /* Drop through to report the successful button */
+ } else if (dom_string_caseless_isequal(
+ inputtype, corestring_dom_image)) {
+ /* We *ONLY* use an image input if it was the
+ * thing which activated us
+ */
+ if ((dom_node *)submit_button !=
+ (dom_node *)form_element)
+ continue;
+
+ err = dom_node_get_user_data(
+ form_element,
+ corestring_dom___ns_key_image_coords_node_data,
+ &coords);
+ if (err != DOM_NO_ERR) {
+ NSLOG(netsurf, INFO,
+ "Could not get image XY data");
+ goto dom_no_memory;
+ }
+ if (coords == NULL) {
+ NSLOG(netsurf, INFO,
+ "No XY data on the image input");
+ goto dom_no_memory;
+ }
+
+ basename = ENCODE_ITEM(inputname);
+
+ success_new = calloc(1, sizeof(*success_new));
+ if (success_new == NULL) {
+ free(basename);
+ NSLOG(netsurf, INFO,
+ "Could not allocate data for image.x");
+ goto dom_no_memory;
+ }
+
+ last_success->next = success_new;
+ last_success = success_new;
+
+ success_new->name = malloc(strlen(basename) + 3);
+ if (success_new->name == NULL) {
+ free(basename);
+ NSLOG(netsurf, INFO,
+ "Could not allocate name for image.x");
+ goto dom_no_memory;
+ }
+ success_new->value = malloc(20);
+ if (success_new->value == NULL) {
+ free(basename);
+ NSLOG(netsurf, INFO,
+ "Could not allocate value for image.x");
+ goto dom_no_memory;
+ }
+ sprintf(success_new->name, "%s.x", basename);
+ sprintf(success_new->value, "%d", coords->x);
+
+ success_new = calloc(1, sizeof(*success_new));
+ if (success_new == NULL) {
+ free(basename);
+ NSLOG(netsurf, INFO,
+ "Could not allocate data for image.y");
+ goto dom_no_memory;
+ }
+
+ last_success->next = success_new;
+ last_success = success_new;
+
+ success_new->name = malloc(strlen(basename) + 3);
+ if (success_new->name == NULL) {
+ free(basename);
+ NSLOG(netsurf, INFO,
+ "Could not allocate name for image.y");
+ goto dom_no_memory;
+ }
+ success_new->value = malloc(20);
+ if (success_new->value == NULL) {
+ free(basename);
+ NSLOG(netsurf, INFO,
+ "Could not allocate value for image.y");
+ goto dom_no_memory;
+ }
+ sprintf(success_new->name, "%s.y", basename);
+ sprintf(success_new->value, "%d", coords->y);
+ free(basename);
+ continue;
+ } else if (dom_string_caseless_isequal(
+ inputtype, corestring_dom_radio) ||
+ dom_string_caseless_isequal(
+ inputtype, corestring_dom_checkbox)) {
+ err = dom_html_input_element_get_checked(
+ (dom_html_input_element *)form_element,
+ &checked);
+ if (err != DOM_NO_ERR) {
+ NSLOG(netsurf, INFO,
+ "Could not get input element checked");
+ goto dom_no_memory;
+ }
+ if (!checked)
+ continue;
+ err = dom_html_input_element_get_value(
+ (dom_html_input_element *)form_element,
+ &inputvalue);
+ if (err != DOM_NO_ERR) {
+ NSLOG(netsurf, INFO,
+ "Could not get input element value");
+ goto dom_no_memory;
+ }
+ if (inputvalue == NULL) {
+ inputvalue = dom_string_ref(
+ corestring_dom_on);
+ }
+ /* Fall through to simple allocation */
+ } else if (dom_string_caseless_isequal(
+ inputtype, corestring_dom_file)) {
+
+ err = dom_html_input_element_get_value(
+ (dom_html_input_element *)form_element,
+ &inputvalue);
+ if (err != DOM_NO_ERR) {
+ NSLOG(netsurf, INFO,
+ "Could not get file value");
+ goto dom_no_memory;
+ }
+ err = dom_node_get_user_data(
+ form_element,
+ corestring_dom___ns_key_file_name_node_data,
+ &rawfile_temp);
+ if (err != DOM_NO_ERR) {
+ NSLOG(netsurf, INFO,
+ "Could not get file rawname");
+ goto dom_no_memory;
+ }
+ rawfile_temp = strdup(rawfile_temp != NULL ?
+ rawfile_temp :
+ "");
+ if (rawfile_temp == NULL) {
+ NSLOG(netsurf, INFO,
+ "Could not copy file rawname");
+ goto dom_no_memory;
+ }
+ /* Fall out to the allocation */
+ } else if (dom_string_caseless_isequal(
+ inputtype, corestring_dom_reset) ||
+ dom_string_caseless_isequal(
+ inputtype, corestring_dom_button)) {
+ /* Skip these */
+ NSLOG(netsurf, INFO,
+ "Skipping RESET and BUTTON");
+ continue;
+ } else {
+ /* Everything else is treated as text values */
+ err = dom_html_input_element_get_value(
+ (dom_html_input_element *)form_element,
+ &inputvalue);
+ if (err != DOM_NO_ERR) {
+ NSLOG(netsurf, INFO,
+ "Could not get input value");
+ goto dom_no_memory;
+ }
+ /* Fall out to the allocation */
+ }
+ }
+
+ success_new = calloc(1, sizeof(*success_new));
+ if (success_new == NULL) {
+ NSLOG(netsurf, INFO,
+ "Could not allocate data for generic");
+ goto dom_no_memory;
+ }
+
+ last_success->next = success_new;
+ last_success = success_new;
+
+ success_new->name = ENCODE_ITEM(inputname);
+ if (success_new->name == NULL) {
+ NSLOG(netsurf, INFO,
+ "Could not encode name for generic");
+ goto dom_no_memory;
+ }
+ success_new->value = ENCODE_ITEM(inputvalue);
+ if (success_new->value == NULL) {
+ NSLOG(netsurf, INFO,
+ "Could not encode value for generic");
+ goto dom_no_memory;
+ }
+ if (rawfile_temp != NULL) {
+ success_new->file = true;
+ success_new->rawfile = rawfile_temp;
+ rawfile_temp = NULL;
+ }
+ }
+
+ free(charset);
+
+ if (form_element != NULL) {
+ dom_node_unref(form_element);
+ }
+
+ if (form_elements != NULL) {
+ dom_html_collection_unref(form_elements);
+ }
+
+ if (nodename != NULL) {
+ dom_string_unref(nodename);
+ }
+
+ if (inputname != NULL) {
+ dom_string_unref(inputname);
+ }
+
+ if (inputvalue != NULL) {
+ dom_string_unref(inputvalue);
+ }
+
+ if (options != NULL) {
+ dom_html_options_collection_unref(options);
+ }
+
+ if (option_element != NULL) {
+ dom_node_unref(option_element);
+ }
+
+ if (inputtype != NULL) {
+ dom_string_unref(inputtype);
+ }
+
+ if (rawfile_temp != NULL) {
+ free(rawfile_temp);
+ }
+
+ *successful_controls = sentinel.next;
+
+ return true;
+
+dom_no_memory:
+ free(charset);
+ fetch_multipart_data_destroy(sentinel.next);
+
+ if (form_elements != NULL)
+ dom_html_collection_unref(form_elements);
+ if (form_element != NULL)
+ dom_node_unref(form_element);
+ if (nodename != NULL)
+ dom_string_unref(nodename);
+ if (inputname != NULL)
+ dom_string_unref(inputname);
+ if (inputvalue != NULL)
+ dom_string_unref(inputvalue);
+ if (options != NULL)
+ dom_html_options_collection_unref(options);
+ if (option_element != NULL)
+ dom_node_unref(option_element);
+ if (inputtype != NULL)
+ dom_string_unref(inputtype);
+ if (rawfile_temp != NULL)
+ free(rawfile_temp);
+
+ return false;
+}
+#undef ENCODE_ITEM
+
+/**
+ * Encode controls using application/x-www-form-urlencoded.
+ *
+ * \param form form to which successful controls relate
+ * \param control linked list of fetch_multipart_data
+ * \param query_string iff true add '?' to the start of returned data
+ * \return URL-encoded form, or 0 on memory exhaustion
+ */
+
+static char *form_url_encode(struct form *form,
+ struct fetch_multipart_data *control,
+ bool query_string)
+{
+ char *name, *value;
+ char *s, *s2;
+ unsigned int len, len1, len_init;
+ nserror url_err;
+
+ if (query_string)
+ s = malloc(2);
+ else
+ s = malloc(1);
+
+ if (s == NULL)
+ return NULL;
+
+ if (query_string) {
+ s[0] = '?';
+ s[1] = '\0';
+ len_init = len = 1;
+ } else {
+ s[0] = '\0';
+ len_init = len = 0;
+ }
+
+ for (; control; control = control->next) {
+ url_err = url_escape(control->name, true, NULL, &name);
+ if (url_err == NSERROR_NOMEM) {
+ free(s);
+ return NULL;
+ }
+
+ assert(url_err == NSERROR_OK);
+
+ url_err = url_escape(control->value, true, NULL, &value);
+ if (url_err == NSERROR_NOMEM) {
+ free(name);
+ free(s);
+ return NULL;
+ }
+
+ assert(url_err == NSERROR_OK);
+
+ len1 = len + strlen(name) + strlen(value) + 2;
+ s2 = realloc(s, len1 + 1);
+ if (!s2) {
+ free(value);
+ free(name);
+ free(s);
+ return NULL;
+ }
+ s = s2;
+ sprintf(s + len, "%s=%s&", name, value);
+ len = len1;
+ free(name);
+ free(value);
+ }
+
+ if (len > len_init) {
+ /* Replace trailing '&' */
+ s[len - 1] = '\0';
+ }
+ return s;
+}
+
+/**
+ * Find an acceptable character set encoding with which to submit the form
+ *
+ * \param form The form
+ * \return Pointer to charset name (on heap, caller should free) or NULL
+ */
+char *form_acceptable_charset(struct form *form)
+{
+ char *temp, *c;
+
+ if (!form)
+ return NULL;
+
+ if (!form->accept_charsets) {
+ /* no accept-charsets attribute for this form */
+ if (form->document_charset)
+ /* document charset present, so use it */
+ return strdup(form->document_charset);
+ else
+ /* no document charset, so default to 8859-1 */
+ return strdup("ISO-8859-1");
+ }
+
+ /* make temporary copy of accept-charsets attribute */
+ temp = strdup(form->accept_charsets);
+ if (!temp)
+ return NULL;
+
+ /* make it upper case */
+ for (c = temp; *c; c++) {
+ *c = ascii_to_upper(*c);
+ }
+
+ /* is UTF-8 specified? */
+ c = strstr(temp, "UTF-8");
+ if (c) {
+ free(temp);
+ return strdup("UTF-8");
+ }
+
+ /* dispense with temporary copy */
+ free(temp);
+
+ /* according to RFC2070, the accept-charsets attribute of the
+ * form element contains a space and/or comma separated list */
+ c = form->accept_charsets;
+
+ /** \todo an improvement would be to choose an encoding
+ * acceptable to the server which covers as much of the input
+ * values as possible. Additionally, we need to handle the
+ * case where none of the acceptable encodings cover all the
+ * textual input values. For now, we just extract the first
+ * element of the charset list
+ */
+ while (*c && !ascii_is_space(*c)) {
+ if (*c == ',')
+ break;
+ c++;
+ }
+
+ return strndup(form->accept_charsets, c - form->accept_charsets);
+}
+
+/**
+ * Convert a string from UTF-8 to the specified charset
+ * As a final fallback, this will attempt to convert to ISO-8859-1.
+ *
+ * \todo Return charset used?
+ *
+ * \param item String to convert
+ * \param len Length of string to convert
+ * \param charset Destination charset
+ * \param fallback Fallback charset (may be NULL),
+ * used iff converting to charset fails
+ * \return Pointer to converted string (on heap, caller frees), or NULL
+ */
+char *form_encode_item(const char *item, uint32_t len, const char *charset,
+ const char *fallback)
+{
+ nserror err;
+ char *ret = NULL;
+ char cset[256];
+
+ if (!item || !charset)
+ return NULL;
+
+ snprintf(cset, sizeof cset, "%s//TRANSLIT", charset);
+
+ err = utf8_to_enc(item, cset, 0, &ret);
+ if (err == NSERROR_BAD_ENCODING) {
+ /* charset not understood, try without transliteration */
+ snprintf(cset, sizeof cset, "%s", charset);
+ err = utf8_to_enc(item, cset, len, &ret);
+
+ if (err == NSERROR_BAD_ENCODING) {
+ /* nope, try fallback charset (if any) */
+ if (fallback) {
+ snprintf(cset, sizeof cset,
+ "%s//TRANSLIT", fallback);
+ err = utf8_to_enc(item, cset, 0, &ret);
+
+ if (err == NSERROR_BAD_ENCODING) {
+ /* and without transliteration */
+ snprintf(cset, sizeof cset,
+ "%s", fallback);
+ err = utf8_to_enc(item, cset, 0, &ret);
+ }
+ }
+
+ if (err == NSERROR_BAD_ENCODING) {
+ /* that also failed, use 8859-1 */
+ err = utf8_to_enc(item, "ISO-8859-1//TRANSLIT",
+ 0, &ret);
+ if (err == NSERROR_BAD_ENCODING) {
+ /* and without transliteration */
+ err = utf8_to_enc(item, "ISO-8859-1",
+ 0, &ret);
+ }
+ }
+ }
+ }
+ if (err == NSERROR_NOMEM) {
+ return NULL;
+ }
+
+ return ret;
+}
+
+/* exported interface documented in html/form_internal.h */
+bool form_open_select_menu(void *client_data,
+ struct form_control *control,
+ select_menu_redraw_callback callback,
+ struct content *c)
+{
+ int line_height_with_spacing;
+ struct box *box;
+ plot_font_style_t fstyle;
+ int total_height;
+ struct form_select_menu *menu;
+ html_content *html = (html_content *)c;
+
+
+ /* if the menu is opened for the first time */
+ if (control->data.select.menu == NULL) {
+
+ menu = calloc(1, sizeof (struct form_select_menu));
+ if (menu == NULL) {
+ guit->misc->warning("NoMemory", 0);
+ return false;
+ }
+
+ control->data.select.menu = menu;
+
+ box = control->box;
+
+ menu->width = box->width +
+ box->border[RIGHT].width +
+ box->border[LEFT].width +
+ box->padding[RIGHT] + box->padding[LEFT];
+
+ font_plot_style_from_css(&html->len_ctx, control->box->style,
+ &fstyle);
+ menu->f_size = fstyle.size;
+
+ menu->line_height = FIXTOINT(FDIV((FMUL(FLTTOFIX(1.2),
+ FMUL(nscss_screen_dpi,
+ INTTOFIX(fstyle.size / FONT_SIZE_SCALE)))),
+ F_72));
+
+ line_height_with_spacing = menu->line_height +
+ menu->line_height *
+ SELECT_LINE_SPACING;
+
+ total_height = control->data.select.num_items *
+ line_height_with_spacing;
+ menu->height = total_height;
+
+ if (menu->height > MAX_SELECT_HEIGHT) {
+
+ menu->height = MAX_SELECT_HEIGHT;
+ }
+ menu->client_data = client_data;
+ menu->callback = callback;
+ if (scrollbar_create(false,
+ menu->height,
+ total_height,
+ menu->height,
+ control,
+ form_select_menu_scroll_callback,
+ &(menu->scrollbar)) != NSERROR_OK) {
+ free(menu);
+ return false;
+ }
+ menu->c = c;
+ }
+ else menu = control->data.select.menu;
+
+ menu->callback(client_data, 0, 0, menu->width, menu->height);
+
+ return true;
+}
+
+
+/* exported interface documented in html/form_internal.h */
+void form_free_select_menu(struct form_control *control)
+{
+ if (control->data.select.menu->scrollbar != NULL)
+ scrollbar_destroy(control->data.select.menu->scrollbar);
+ free(control->data.select.menu);
+ control->data.select.menu = NULL;
+}
+
+
+/* exported interface documented in html/form_internal.h */
+bool form_redraw_select_menu(struct form_control *control, int x, int y,
+ float scale, const struct rect *clip,
+ const struct redraw_context *ctx)
+{
+ struct box *box;
+ struct form_select_menu *menu = control->data.select.menu;
+ struct form_option *option;
+ int line_height, line_height_with_spacing;
+ int width, height;
+ int x0, y0, x1, scrollbar_x, y1, y2, y3;
+ int item_y;
+ int text_pos_offset, text_x;
+ int scrollbar_width = SCROLLBAR_WIDTH;
+ int i;
+ int scroll;
+ int x_cp, y_cp;
+ struct rect r;
+ struct rect rect;
+ nserror res;
+
+ box = control->box;
+
+ x_cp = x;
+ y_cp = y;
+ width = menu->width;
+ height = menu->height;
+ line_height = menu->line_height;
+
+ line_height_with_spacing = line_height +
+ line_height * SELECT_LINE_SPACING;
+ scroll = scrollbar_get_offset(menu->scrollbar);
+
+ if (scale != 1.0) {
+ x *= scale;
+ y *= scale;
+ width *= scale;
+ height *= scale;
+ scrollbar_width *= scale;
+
+ i = scroll / line_height_with_spacing;
+ scroll -= i * line_height_with_spacing;
+ line_height *= scale;
+ line_height_with_spacing *= scale;
+ scroll *= scale;
+ scroll += i * line_height_with_spacing;
+ }
+
+
+ x0 = x;
+ y0 = y;
+ x1 = x + width - 1;
+ y1 = y + height - 1;
+ scrollbar_x = x1 - scrollbar_width;
+
+ r.x0 = x0;
+ r.y0 = y0;
+ r.x1 = x1 + 1;
+ r.y1 = y1 + 1;
+ res = ctx->plot->clip(ctx, &r);
+ if (res != NSERROR_OK) {
+ return false;
+ }
+
+ rect.x0 = x0;
+ rect.y0 = y0;
+ rect.x1 = x1;
+ rect.y1 = y1;
+ res = ctx->plot->rectangle(ctx, plot_style_stroke_darkwbasec, &rect);
+ if (res != NSERROR_OK) {
+ return false;
+ }
+
+ x0 = x0 + SELECT_BORDER_WIDTH;
+ y0 = y0 + SELECT_BORDER_WIDTH;
+ x1 = x1 - SELECT_BORDER_WIDTH;
+ y1 = y1 - SELECT_BORDER_WIDTH;
+ height = height - 2 * SELECT_BORDER_WIDTH;
+
+ r.x0 = x0;
+ r.y0 = y0;
+ r.x1 = x1 + 1;
+ r.y1 = y1 + 1;
+ res = ctx->plot->clip(ctx, &r);
+ if (res != NSERROR_OK) {
+ return false;
+ }
+
+ res = ctx->plot->rectangle(ctx, plot_style_fill_lightwbasec, &r);
+ if (res != NSERROR_OK) {
+ return false;
+ }
+
+ option = control->data.select.items;
+ item_y = line_height_with_spacing;
+
+ while (item_y < scroll) {
+ option = option->next;
+ item_y += line_height_with_spacing;
+ }
+ item_y -= line_height_with_spacing;
+ text_pos_offset = y - scroll +
+ (int) (line_height * (0.75 + SELECT_LINE_SPACING));
+ text_x = x + (box->border[LEFT].width + box->padding[LEFT]) * scale;
+
+ plot_fstyle_entry.size = menu->f_size;
+
+ while (option && item_y - scroll < height) {
+
+ if (option->selected) {
+ y2 = y + item_y - scroll;
+ y3 = y + item_y + line_height_with_spacing - scroll;
+
+ rect.x0 = x0;
+ rect.y0 = y0 > y2 ? y0 : y2;
+ rect.x1 = scrollbar_x + 1;
+ rect.y1 = y3 < y1 + 1 ? y3 : y1 + 1;
+ res = ctx->plot->rectangle(ctx, &plot_style_fill_selected, &rect);
+ if (res != NSERROR_OK) {
+ return false;
+ }
+ }
+
+ y2 = text_pos_offset + item_y;
+ res = ctx->plot->text(ctx,
+ &plot_fstyle_entry,
+ text_x, y2,
+ option->text, strlen(option->text));
+ if (res != NSERROR_OK) {
+ return false;
+ }
+
+ item_y += line_height_with_spacing;
+ option = option->next;
+ }
+
+ res = scrollbar_redraw(menu->scrollbar,
+ x_cp + menu->width - SCROLLBAR_WIDTH,
+ y_cp,
+ clip, scale, ctx);
+ if (res != NSERROR_OK) {
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Check whether a clipping rectangle is completely contained in the
+ * select menu.
+ *
+ * \param control the select menu to check the clipping rectangle for
+ * \param scale the current browser window scale
+ * \param clip the clipping rectangle
+ * \return true if inside false otherwise
+ */
+bool form_clip_inside_select_menu(struct form_control *control, float scale,
+ const struct rect *clip)
+{
+ struct form_select_menu *menu = control->data.select.menu;
+ int width, height;
+
+
+ width = menu->width;
+ height = menu->height;
+
+ if (scale != 1.0) {
+ width *= scale;
+ height *= scale;
+ }
+
+ if (clip->x0 >= 0 && clip->x1 <= width &&
+ clip->y0 >= 0 && clip->y1 <= height)
+ return true;
+
+ return false;
+}
+
+
+/**
+ * Process a selection from a form select menu.
+ *
+ * \param html The html content handle for the form
+ * \param control form control with menu
+ * \param item index of item selected from the menu
+ * \return NSERROR_OK or appropriate error code.
+ */
+static nserror form__select_process_selection(html_content *html,
+ struct form_control *control, int item)
+{
+ struct box *inline_box;
+ struct form_option *o;
+ int count;
+ nserror ret = NSERROR_OK;
+
+ assert(control != NULL);
+ assert(html != NULL);
+
+ /** \todo Even though the form code is effectively part of the html
+ * content handler, poking around inside contents is not good
+ */
+
+ inline_box = control->box->children->children;
+
+ for (count = 0, o = control->data.select.items;
+ o != NULL;
+ count++, o = o->next) {
+ if (!control->data.select.multiple && o->selected) {
+ o->selected = false;
+ dom_html_option_element_set_selected(o->node, false);
+ }
+
+ if (count == item) {
+ if (control->data.select.multiple) {
+ if (o->selected) {
+ o->selected = false;
+ dom_html_option_element_set_selected(
+ o->node, false);
+ control->data.select.num_selected--;
+ } else {
+ o->selected = true;
+ dom_html_option_element_set_selected(
+ o->node, true);
+ control->data.select.num_selected++;
+ }
+ } else {
+ dom_html_option_element_set_selected(
+ o->node, true);
+ o->selected = true;
+ }
+ }
+
+ if (o->selected) {
+ control->data.select.current = o;
+ }
+ }
+
+ talloc_free(inline_box->text);
+ inline_box->text = 0;
+
+ if (control->data.select.num_selected == 0) {
+ inline_box->text = talloc_strdup(html->bctx,
+ messages_get("Form_None"));
+ } else if (control->data.select.num_selected == 1) {
+ inline_box->text = talloc_strdup(html->bctx,
+ control->data.select.current->text);
+ } else {
+ inline_box->text = talloc_strdup(html->bctx,
+ messages_get("Form_Many"));
+ }
+
+ if (!inline_box->text) {
+ ret = NSERROR_NOMEM;
+ inline_box->length = 0;
+ } else {
+ inline_box->length = strlen(inline_box->text);
+ }
+ inline_box->width = control->box->width;
+
+ html__redraw_a_box(html, control->box);
+
+ return ret;
+}
+
+/* exported interface documented in netsurf/form.h */
+nserror form_select_process_selection(struct form_control *control, int item)
+{
+ assert(control != NULL);
+
+ return form__select_process_selection(control->html, control, item);
+}
+
+/* exported interface documented in netsurf/form.h */
+struct form_option *
+form_select_get_option(struct form_control *control, int item)
+{
+ struct form_option *opt;
+
+ opt = control->data.select.items;
+ while ((opt != NULL) && (item > 0)) {
+ opt = opt->next;
+ item--;
+ }
+ return opt;
+}
+
+/* exported interface documented in netsurf/form.h */
+char *form_control_get_name(struct form_control *control)
+{
+ return control->name;
+}
+
+/* exported interface documented in netsurf/form.h */
+nserror form_control_bounding_rect(struct form_control *control, struct rect *r)
+{
+ box_bounds( control->box, r );
+ return NSERROR_OK;
+}
+
+
+/**
+ * Handle a click on the area of the currently opened select menu.
+ *
+ * \param control the select menu which received the click
+ * \param x X coordinate of click
+ * \param y Y coordinate of click
+ */
+void form_select_menu_clicked(struct form_control *control, int x, int y)
+{
+ struct form_select_menu *menu = control->data.select.menu;
+ struct form_option *option;
+ html_content *html = (html_content *)menu->c;
+ int line_height, line_height_with_spacing;
+ int item_bottom_y;
+ int scroll, i;
+
+ scroll = scrollbar_get_offset(menu->scrollbar);
+
+ line_height = menu->line_height;
+ line_height_with_spacing = line_height +
+ line_height * SELECT_LINE_SPACING;
+
+ option = control->data.select.items;
+ item_bottom_y = line_height_with_spacing;
+ i = 0;
+ while (option && item_bottom_y < scroll + y) {
+ item_bottom_y += line_height_with_spacing;
+ option = option->next;
+ i++;
+ }
+
+ if (option != NULL) {
+ form__select_process_selection(html, control, i);
+ }
+
+ menu->callback(menu->client_data, 0, 0, menu->width, menu->height);
+}
+
+/**
+ * Handle mouse action for the currently opened select menu.
+ *
+ * \param control the select menu which received the mouse action
+ * \param mouse current mouse state
+ * \param x X coordinate of click
+ * \param y Y coordinate of click
+ * \return text for the browser status bar or NULL if the menu has
+ * to be closed
+ */
+const char *form_select_mouse_action(struct form_control *control,
+ browser_mouse_state mouse, int x, int y)
+{
+ struct form_select_menu *menu = control->data.select.menu;
+ int x0, y0, x1, y1, scrollbar_x;
+ const char *status = NULL;
+ bool multiple = control->data.select.multiple;
+
+ x0 = 0;
+ y0 = 0;
+ x1 = menu->width;
+ y1 = menu->height;
+ scrollbar_x = x1 - SCROLLBAR_WIDTH;
+
+ if (menu->scroll_capture ||
+ (x > scrollbar_x && x < x1 && y > y0 && y < y1)) {
+ /* The scroll is currently capturing all events or the mouse
+ * event is taking place on the scrollbar widget area
+ */
+ x -= scrollbar_x;
+ return scrollbar_mouse_status_to_message(
+ scrollbar_mouse_action(menu->scrollbar,
+ mouse, x, y));
+ }
+
+
+ if (x > x0 && x < scrollbar_x && y > y0 && y < y1) {
+ /* over option area */
+
+ if (mouse & (BROWSER_MOUSE_CLICK_1 | BROWSER_MOUSE_CLICK_2))
+ /* button 1 or 2 click */
+ form_select_menu_clicked(control, x, y);
+
+ if (!(mouse & BROWSER_MOUSE_CLICK_1 && !multiple))
+ /* anything but a button 1 click over a single select
+ menu */
+ status = messages_get(control->data.select.multiple ?
+ "SelectMClick" : "SelectClick");
+
+ } else if (!(mouse & (BROWSER_MOUSE_CLICK_1 | BROWSER_MOUSE_CLICK_2)))
+ /* if not a button 1 or 2 click*/
+ status = messages_get("SelectClose");
+
+ return status;
+}
+
+/**
+ * Handle mouse drag end for the currently opened select menu.
+ *
+ * \param control the select menu which received the mouse drag end
+ * \param mouse current mouse state
+ * \param x X coordinate of drag end
+ * \param y Y coordinate of drag end
+ */
+void form_select_mouse_drag_end(struct form_control *control,
+ browser_mouse_state mouse, int x, int y)
+{
+ int x0, y0, x1, y1;
+ int box_x, box_y;
+ struct box *box;
+ struct form_select_menu *menu = control->data.select.menu;
+
+ box = control->box;
+
+ /* Get global coords of scrollbar */
+ box_coords(box, &box_x, &box_y);
+ box_x -= box->border[LEFT].width;
+ box_y += box->height + box->border[BOTTOM].width +
+ box->padding[BOTTOM] + box->padding[TOP];
+
+ /* Get drag end coords relative to scrollbar */
+ x = x - box_x;
+ y = y - box_y;
+
+ if (menu->scroll_capture) {
+ x -= menu->width - SCROLLBAR_WIDTH;
+ scrollbar_mouse_drag_end(menu->scrollbar, mouse, x, y);
+ return;
+ }
+
+ x0 = 0;
+ y0 = 0;
+ x1 = menu->width;
+ y1 = menu->height;
+
+
+ if (x > x0 && x < x1 - SCROLLBAR_WIDTH && y > y0 && y < y1)
+ /* handle drag end above the option area like a regular click */
+ form_select_menu_clicked(control, x, y);
+}
+
+/**
+ * Callback for the select menus scroll
+ */
+void form_select_menu_scroll_callback(void *client_data,
+ struct scrollbar_msg_data *scrollbar_data)
+{
+ struct form_control *control = client_data;
+ struct form_select_menu *menu = control->data.select.menu;
+ html_content *html = (html_content *)menu->c;
+
+ switch (scrollbar_data->msg) {
+ case SCROLLBAR_MSG_MOVED:
+ menu->callback(menu->client_data,
+ 0, 0,
+ menu->width,
+ menu->height);
+ break;
+ case SCROLLBAR_MSG_SCROLL_START:
+ {
+ struct rect rect = {
+ .x0 = scrollbar_data->x0,
+ .y0 = scrollbar_data->y0,
+ .x1 = scrollbar_data->x1,
+ .y1 = scrollbar_data->y1
+ };
+
+ browser_window_set_drag_type(html->bw,
+ DRAGGING_CONTENT_SCROLLBAR, &rect);
+
+ menu->scroll_capture = true;
+ }
+ break;
+ case SCROLLBAR_MSG_SCROLL_FINISHED:
+ menu->scroll_capture = false;
+
+ browser_window_set_drag_type(html->bw,
+ DRAGGING_NONE, NULL);
+ break;
+ default:
+ break;
+ }
+}
+
+/**
+ * Get the dimensions of a select menu.
+ *
+ * \param control the select menu to get the dimensions of
+ * \param width gets updated to menu width
+ * \param height gets updated to menu height
+ */
+void form_select_get_dimensions(struct form_control *control,
+ int *width, int *height)
+{
+ *width = control->data.select.menu->width;
+ *height = control->data.select.menu->height;
+}
+
+/**
+ * Callback for the core select menu.
+ */
+void form_select_menu_callback(void *client_data,
+ int x, int y, int width, int height)
+{
+ html_content *html = client_data;
+ int menu_x, menu_y;
+ struct box *box;
+
+ box = html->visible_select_menu->box;
+ box_coords(box, &menu_x, &menu_y);
+
+ menu_x -= box->border[LEFT].width;
+ menu_y += box->height + box->border[BOTTOM].width +
+ box->padding[BOTTOM] +
+ box->padding[TOP];
+ content__request_redraw((struct content *)html, menu_x + x, menu_y + y,
+ width, height);
+}
+
+
+/**
+ * Set a radio form control and clear the others in the group.
+ *
+ * \param radio form control of type GADGET_RADIO
+ */
+
+void form_radio_set(struct form_control *radio)
+{
+ struct form_control *control;
+
+ assert(radio);
+ if (!radio->form)
+ return;
+
+ if (radio->selected)
+ return;
+
+ for (control = radio->form->controls; control;
+ control = control->next) {
+ if (control->type != GADGET_RADIO)
+ continue;
+ if (control == radio)
+ continue;
+ if (strcmp(control->name, radio->name) != 0)
+ continue;
+
+ if (control->selected) {
+ control->selected = false;
+ dom_html_input_element_set_checked(control->node, false);
+ html__redraw_a_box(radio->html, control->box);
+ }
+ }
+
+ radio->selected = true;
+ dom_html_input_element_set_checked(radio->node, true);
+ html__redraw_a_box(radio->html, radio->box);
+}
+
+
+/**
+ * Collect controls and submit a form.
+ */
+
+void form_submit(nsurl *page_url, struct browser_window *target,
+ struct form *form, struct form_control *submit_button)
+{
+ char *data = NULL;
+ struct fetch_multipart_data *success;
+ nsurl *action_url;
+ nsurl *action_query;
+ nserror error;
+
+ assert(form != NULL);
+
+ if (form_successful_controls_dom(form, submit_button, &success) == false) {
+ guit->misc->warning("NoMemory", 0);
+ return;
+ }
+
+ /* Decompose action */
+ if (nsurl_create(form->action, &action_url) != NSERROR_OK) {
+ free(data);
+ fetch_multipart_data_destroy(success);
+ guit->misc->warning("NoMemory", 0);
+ return;
+ }
+
+ switch (form->method) {
+ case method_GET:
+ data = form_url_encode(form, success, true);
+ if (data == NULL) {
+ fetch_multipart_data_destroy(success);
+ guit->misc->warning("NoMemory", 0);
+ return;
+ }
+
+ /* Replace query segment */
+ error = nsurl_replace_query(action_url, data, &action_query);
+ if (error != NSERROR_OK) {
+ nsurl_unref(action_query);
+ free(data);
+ fetch_multipart_data_destroy(success);
+ guit->misc->warning(messages_get_errorcode(error), 0);
+ return;
+ }
+
+ /* Construct submit url */
+ browser_window_navigate(target,
+ action_query,
+ page_url,
+ BW_NAVIGATE_HISTORY,
+ NULL,
+ NULL,
+ NULL);
+
+ nsurl_unref(action_query);
+ break;
+
+ case method_POST_URLENC:
+ data = form_url_encode(form, success, false);
+ if (data == NULL) {
+ fetch_multipart_data_destroy(success);
+ guit->misc->warning("NoMemory", 0);
+ nsurl_unref(action_url);
+ return;
+ }
+
+ browser_window_navigate(target,
+ action_url,
+ page_url,
+ BW_NAVIGATE_HISTORY,
+ data,
+ NULL,
+ NULL);
+ break;
+
+ case method_POST_MULTIPART:
+ browser_window_navigate(target,
+ action_url,
+ page_url,
+ BW_NAVIGATE_HISTORY,
+ NULL,
+ success,
+ NULL);
+
+ break;
+ }
+
+ nsurl_unref(action_url);
+ fetch_multipart_data_destroy(success);
+ free(data);
+}
+
+void form_gadget_update_value(struct form_control *control, char *value)
+{
+ switch (control->type) {
+ case GADGET_HIDDEN:
+ case GADGET_TEXTBOX:
+ case GADGET_TEXTAREA:
+ case GADGET_PASSWORD:
+ case GADGET_FILE:
+ if (control->value != NULL) {
+ free(control->value);
+ }
+ control->value = value;
+ if (control->node != NULL) {
+ dom_exception err;
+ dom_string *str;
+ err = dom_string_create((uint8_t *)value,
+ strlen(value), &str);
+ if (err == DOM_NO_ERR) {
+ if (control->type == GADGET_TEXTAREA)
+ err = dom_html_text_area_element_set_value(
+ (dom_html_text_area_element *)(control->node),
+ str);
+ else
+ err = dom_html_input_element_set_value(
+ (dom_html_input_element *)(control->node),
+ str);
+ dom_string_unref(str);
+ }
+ }
+ break;
+ default:
+ /* Do nothing */
+ break;
+ }
+}
diff --git a/content/handlers/html/form_internal.h b/content/handlers/html/form_internal.h
new file mode 100644
index 0000000..a77e823
--- /dev/null
+++ b/content/handlers/html/form_internal.h
@@ -0,0 +1,277 @@
+/*
+ * Copyright 2014 Vincent Sanders <vince(a)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
+ * Interface to form handling functions internal to HTML content handler.
+ */
+
+#ifndef NETSURF_HTML_FORM_INTERNAL_H
+#define NETSURF_HTML_FORM_INTERNAL_H
+
+#include <stdbool.h>
+
+#include "netsurf/form.h"
+
+struct box;
+struct form_control;
+struct form_option;
+struct form_select_menu;
+struct form;
+struct html_content;
+struct dom_string;
+struct content;
+struct nsurl;
+struct fetch_multipart_data;
+struct redraw_context;
+struct browser_window;
+
+enum browser_mouse_state;
+
+/** Type of a struct form_control. */
+typedef enum {
+ GADGET_HIDDEN,
+ GADGET_TEXTBOX,
+ GADGET_RADIO,
+ GADGET_CHECKBOX,
+ GADGET_SELECT,
+ GADGET_TEXTAREA,
+ GADGET_IMAGE,
+ GADGET_PASSWORD,
+ GADGET_SUBMIT,
+ GADGET_RESET,
+ GADGET_FILE,
+ GADGET_BUTTON
+} form_control_type;
+
+/** Data for textarea */
+struct form_textarea_data {
+ struct form_control *gadget;
+};
+
+struct image_input_coords {
+ int x;
+ int y;
+};
+
+/** Form control. */
+struct form_control {
+ void *node; /**< Corresponding DOM node */
+ struct html_content *html; /**< HTML content containing control */
+
+ form_control_type type; /**< Type of control */
+
+ struct form *form; /**< Containing form */
+
+ char *name; /**< Control name */
+ char *value; /**< Current value of control */
+ char *initial_value; /**< Initial value of control */
+ bool disabled; /**< Whether control is disabled */
+
+ struct box *box; /**< Box for control */
+
+ unsigned int length; /**< Number of characters in control */
+ unsigned int maxlength; /**< Maximum characters permitted */
+
+ bool selected; /**< Whether control is selected */
+
+ union {
+ struct {
+ int mx, my;
+ } image;
+ struct {
+ int num_items;
+ struct form_option *items, *last_item;
+ bool multiple;
+ int num_selected;
+ /** Currently selected item, if num_selected == 1. */
+ struct form_option *current;
+ struct form_select_menu *menu;
+ } select;
+ struct {
+ struct textarea *ta;
+ struct dom_string *initial;
+ struct form_textarea_data data;
+ } text; /**< input type=text or textarea */
+ } data;
+
+ struct form_control *prev; /**< Previous control in this form */
+ struct form_control *next; /**< Next control in this form. */
+};
+
+/** Form submit method. */
+typedef enum {
+ method_GET, /**< GET, always url encoded. */
+ method_POST_URLENC, /**< POST, url encoded. */
+ method_POST_MULTIPART /**< POST, multipart/form-data. */
+} form_method;
+
+/** HTML form. */
+struct form {
+ void *node; /**< Corresponding DOM node */
+
+ char *action; /**< Absolute URL to submit to. */
+ char *target; /**< Target to submit to. */
+ form_method method; /**< Method and enctype. */
+ char *accept_charsets; /**< Charset to submit form in */
+ char *document_charset; /**< Charset of document containing form */
+ struct form_control *controls; /**< Linked list of controls. */
+ struct form_control *last_control; /**< Last control in list. */
+
+ struct form *prev; /**< Previous form in doc. */
+};
+
+/**
+ * Called by the select menu when it wants an area to be redrawn. The
+ * coordinates are menu origin relative.
+ *
+ * \param client_data data which was passed to form_open_select_menu
+ * \param x X coordinate of redraw rectangle
+ * \param y Y coordinate of redraw rectangle
+ * \param width width of redraw rectangle
+ * \param height height of redraw rectangle
+ */
+typedef void(*select_menu_redraw_callback)(void *client_data,
+ int x, int y, int width, int height);
+
+/**
+ * Create a struct form.
+ *
+ * \param node DOM node associated with form
+ * \param action URL to submit form to, or NULL for default
+ * \param target Target frame of form, or NULL for default
+ * \param method method and enctype
+ * \param charset acceptable encodings for form submission, or NULL
+ * \param doc_charset encoding of containing document, or NULL
+ * \return A new form or NULL on memory exhaustion
+ */
+struct form *form_new(void *node, const char *action, const char *target,
+ form_method method, const char *charset,
+ const char *doc_charset);
+
+/**
+ * Free a form and any controls it owns.
+ *
+ * \note There may exist controls attached to box tree nodes which are not
+ * associated with any form. These will leak at present. Ideally, they will
+ * be cleaned up when the box tree is destroyed. As that currently happens
+ * via talloc, this won't happen. These controls are distinguishable, as their
+ * form field will be NULL.
+ *
+ * \param form The form to free
+ */
+void form_free(struct form *form);
+
+/**
+ * Create a struct form_control.
+ *
+ * \param node Associated DOM node
+ * \param type control type
+ * \return a new structure, or NULL on memory exhaustion
+ */
+struct form_control *form_new_control(void *node, form_control_type type);
+
+void form_add_control(struct form *form, struct form_control *control);
+void form_free_control(struct form_control *control);
+bool form_add_option(struct form_control *control, char *value, char *text,
+ bool selected, void *node);
+bool form_successful_controls(struct form *form,
+ struct form_control *submit_button,
+ struct fetch_multipart_data **successful_controls);
+
+/**
+ * Identify 'successful' controls via the DOM.
+ *
+ * All text strings in the successful controls list will be in the charset most
+ * appropriate for submission. Therefore, no utf8_to_* processing should be
+ * performed upon them.
+ *
+ * \todo The chosen charset needs to be made available such that it can be
+ * included in the submission request (e.g. in the fetch's Content-Type header)
+ *
+ * See HTML 4.01 section 17.13.2.
+ *
+ * \param[in] form form to search for successful controls
+ * \param[in] submit_button control used to submit the form, if any
+ * \param[out] successful_controls updated to point to linked list of
+ * fetch_multipart_data, 0 if no controls
+ * \return true on success, false on memory exhaustion
+ */
+bool form_successful_controls_dom(struct form *form,
+ struct form_control *submit_button,
+ struct fetch_multipart_data **successful_controls);
+
+
+/**
+ * Open a select menu for a select form control, creating it if necessary.
+ *
+ * \param client_data data passed to the redraw callback
+ * \param control The select form control for which the menu is being opened
+ * \param redraw_callback The callback to redraw the select menu.
+ * \param c The content the select menu is opening for.
+ * \return false on memory exhaustion, true otherwise
+ */
+bool form_open_select_menu(void *client_data,
+ struct form_control *control,
+ select_menu_redraw_callback redraw_callback,
+ struct content *c);
+
+
+void form_select_menu_callback(void *client_data,
+ int x, int y, int width, int height);
+
+
+/**
+ * Destroy a select menu and free allocated memory.
+ *
+ * \param control the select form control owning the select menu being
+ * destroyed.
+ */
+void form_free_select_menu(struct form_control *control);
+
+
+/**
+ * Redraw an opened select menu.
+ *
+ * \param control the select menu being redrawn
+ * \param x the X coordinate to draw the menu at
+ * \param y the Y coordinate to draw the menu at
+ * \param scale current redraw scale
+ * \param clip clipping rectangle
+ * \param ctx current redraw context
+ * \return true on success, false otherwise
+ */
+bool form_redraw_select_menu(struct form_control *control, int x, int y,
+ float scale, const struct rect *clip,
+ const struct redraw_context *ctx);
+
+bool form_clip_inside_select_menu(struct form_control *control, float scale,
+ const struct rect *clip);
+const char *form_select_mouse_action(struct form_control *control,
+ enum browser_mouse_state mouse, int x, int y);
+void form_select_mouse_drag_end(struct form_control *control,
+ enum browser_mouse_state mouse, int x, int y);
+void form_select_get_dimensions(struct form_control *control,
+ int *width, int *height);
+void form_submit(struct nsurl *page_url, struct browser_window *target,
+ struct form *form, struct form_control *submit_button);
+void form_radio_set(struct form_control *radio);
+
+void form_gadget_update_value(struct form_control *control, char *value);
+
+#endif
diff --git a/content/handlers/html/html.c b/content/handlers/html/html.c
new file mode 100644
index 0000000..18c1a7a
--- /dev/null
+++ b/content/handlers/html/html.c
@@ -0,0 +1,2468 @@
+/*
+ * Copyright 2007 James Bursa <bursa(a)users.sourceforge.net>
+ * Copyright 2010 Michael Drake <tlsa(a)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
+ * Implementation of HTML content handling.
+ */
+
+#include <assert.h>
+#include <stdint.h>
+#include <string.h>
+#include <strings.h>
+#include <stdlib.h>
+#include <nsutils/time.h>
+
+#include "utils/utils.h"
+#include "utils/config.h"
+#include "utils/corestrings.h"
+#include "utils/http.h"
+#include "utils/libdom.h"
+#include "utils/log.h"
+#include "utils/messages.h"
+#include "utils/talloc.h"
+#include "utils/utf8.h"
+#include "utils/nsoption.h"
+#include "utils/string.h"
+#include "utils/ascii.h"
+#include "netsurf/content.h"
+#include "netsurf/browser_window.h"
+#include "netsurf/utf8.h"
+#include "netsurf/layout.h"
+#include "netsurf/misc.h"
+#include "content/hlcache.h"
+#include "desktop/selection.h"
+#include "desktop/scrollbar.h"
+#include "desktop/textarea.h"
+#include "netsurf/bitmap.h"
+#include "javascript/js.h"
+#include "desktop/gui_internal.h"
+
+#include "html/box.h"
+#include "html/form_internal.h"
+#include "html/html_internal.h"
+#include "html/imagemap.h"
+#include "html/layout.h"
+#include "html/search.h"
+
+#define CHUNK 4096
+
+/* Change these to 1 to cause a dump to stderr of the frameset or box
+ * when the trees have been built.
+ */
+#define ALWAYS_DUMP_FRAMESET 0
+#define ALWAYS_DUMP_BOX 0
+
+static const char *html_types[] = {
+ "application/xhtml+xml",
+ "text/html"
+};
+
+/* Exported interface, see html_internal.h */
+bool fire_dom_event(dom_string *type, dom_node *target,
+ bool bubbles, bool cancelable)
+{
+ dom_exception exc;
+ dom_event *evt;
+ bool result;
+
+ exc = dom_event_create(&evt);
+ if (exc != DOM_NO_ERR) return false;
+ exc = dom_event_init(evt, type, bubbles, cancelable);
+ if (exc != DOM_NO_ERR) {
+ dom_event_unref(evt);
+ return false;
+ }
+ NSLOG(netsurf, INFO, "Dispatching '%*s' against %p",
+ dom_string_length(type), dom_string_data(type), target);
+ exc = dom_event_target_dispatch_event(target, evt, &result);
+ if (exc != DOM_NO_ERR) {
+ result = false;
+ }
+ dom_event_unref(evt);
+ return result;
+}
+
+/**
+ * Perform post-box-creation conversion of a document
+ *
+ * \param c HTML content to complete conversion of
+ * \param success Whether box tree construction was successful
+ */
+static void html_box_convert_done(html_content *c, bool success)
+{
+ nserror err;
+ dom_exception exc; /* returned by libdom functions */
+ dom_node *html;
+
+ NSLOG(netsurf, INFO, "Done XML to box (%p)", c);
+
+ /* Clean up and report error if unsuccessful or aborted */
+ if ((success == false) || (c->aborted)) {
+ html_object_free_objects(c);
+
+ if (success == false) {
+ content_broadcast_errorcode(&c->base, NSERROR_BOX_CONVERT);
+ } else {
+ content_broadcast_errorcode(&c->base, NSERROR_STOPPED);
+ }
+
+ content_set_error(&c->base);
+ return;
+ }
+
+
+#if ALWAYS_DUMP_BOX
+ box_dump(stderr, c->layout->children, 0, true);
+#endif
+#if ALWAYS_DUMP_FRAMESET
+ if (c->frameset)
+ html_dump_frameset(c->frameset, 0);
+#endif
+
+ exc = dom_document_get_document_element(c->document, (void *) &html);
+ if ((exc != DOM_NO_ERR) || (html == NULL)) {
+ /** @todo should this call html_object_free_objects(c);
+ * like the other error paths
+ */
+ NSLOG(netsurf, INFO, "error retrieving html element from dom");
+ content_broadcast_errorcode(&c->base, NSERROR_DOM);
+ content_set_error(&c->base);
+ return;
+ }
+
+ /* extract image maps - can't do this sensibly in dom_to_box */
+ err = imagemap_extract(c);
+ if (err != NSERROR_OK) {
+ NSLOG(netsurf, INFO, "imagemap extraction failed");
+ html_object_free_objects(c);
+ content_broadcast_errorcode(&c->base, err);
+ content_set_error(&c->base);
+ dom_node_unref(html);
+ return;
+ }
+ /*imagemap_dump(c);*/
+
+ /* Destroy the parser binding */
+ dom_hubbub_parser_destroy(c->parser);
+ c->parser = NULL;
+
+ content_set_ready(&c->base);
+
+ if (c->base.active == 0) {
+ content_set_done(&c->base);
+ }
+
+ dom_node_unref(html);
+}
+
+
+/** process link node */
+static bool html_process_link(html_content *c, dom_node *node)
+{
+ struct content_rfc5988_link link; /* the link added to the content */
+ dom_exception exc; /* returned by libdom functions */
+ dom_string *atr_string;
+ nserror error;
+
+ memset(&link, 0, sizeof(struct content_rfc5988_link));
+
+ /* check that the relation exists - w3c spec says must be present */
+ exc = dom_element_get_attribute(node, corestring_dom_rel, &atr_string);
+ if ((exc != DOM_NO_ERR) || (atr_string == NULL)) {
+ return false;
+ }
+ /* get a lwc string containing the link relation */
+ exc = dom_string_intern(atr_string, &link.rel);
+ dom_string_unref(atr_string);
+ if (exc != DOM_NO_ERR) {
+ return false;
+ }
+
+ /* check that the href exists - w3c spec says must be present */
+ exc = dom_element_get_attribute(node, corestring_dom_href, &atr_string);
+ if ((exc != DOM_NO_ERR) || (atr_string == NULL)) {
+ lwc_string_unref(link.rel);
+ return false;
+ }
+
+ /* get nsurl */
+ error = nsurl_join(c->base_url, dom_string_data(atr_string),
+ &link.href);
+ dom_string_unref(atr_string);
+ if (error != NSERROR_OK) {
+ lwc_string_unref(link.rel);
+ return false;
+ }
+
+ /* look for optional properties -- we don't care if internment fails */
+
+ exc = dom_element_get_attribute(node,
+ corestring_dom_hreflang, &atr_string);
+ if ((exc == DOM_NO_ERR) && (atr_string != NULL)) {
+ /* get a lwc string containing the href lang */
+ exc = dom_string_intern(atr_string, &link.hreflang);
+ dom_string_unref(atr_string);
+ }
+
+ exc = dom_element_get_attribute(node,
+ corestring_dom_type, &atr_string);
+ if ((exc == DOM_NO_ERR) && (atr_string != NULL)) {
+ /* get a lwc string containing the type */
+ exc = dom_string_intern(atr_string, &link.type);
+ dom_string_unref(atr_string);
+ }
+
+ exc = dom_element_get_attribute(node,
+ corestring_dom_media, &atr_string);
+ if ((exc == DOM_NO_ERR) && (atr_string != NULL)) {
+ /* get a lwc string containing the media */
+ exc = dom_string_intern(atr_string, &link.media);
+ dom_string_unref(atr_string);
+ }
+
+ exc = dom_element_get_attribute(node,
+ corestring_dom_sizes, &atr_string);
+ if ((exc == DOM_NO_ERR) && (atr_string != NULL)) {
+ /* get a lwc string containing the sizes */
+ exc = dom_string_intern(atr_string, &link.sizes);
+ dom_string_unref(atr_string);
+ }
+
+ /* add to content */
+ content__add_rfc5988_link(&c->base, &link);
+
+ if (link.sizes != NULL)
+ lwc_string_unref(link.sizes);
+ if (link.media != NULL)
+ lwc_string_unref(link.media);
+ if (link.type != NULL)
+ lwc_string_unref(link.type);
+ if (link.hreflang != NULL)
+ lwc_string_unref(link.hreflang);
+
+ nsurl_unref(link.href);
+ lwc_string_unref(link.rel);
+
+ return true;
+}
+
+/** process title node */
+static bool html_process_title(html_content *c, dom_node *node)
+{
+ dom_exception exc; /* returned by libdom functions */
+ dom_string *title;
+ char *title_str;
+ bool success;
+
+ exc = dom_node_get_text_content(node, &title);
+ if ((exc != DOM_NO_ERR) || (title == NULL)) {
+ return false;
+ }
+
+ title_str = squash_whitespace(dom_string_data(title));
+ dom_string_unref(title);
+
+ if (title_str == NULL) {
+ return false;
+ }
+
+ success = content__set_title(&c->base, title_str);
+
+ free(title_str);
+
+ return success;
+}
+
+static bool html_process_base(html_content *c, dom_node *node)
+{
+ dom_exception exc; /* returned by libdom functions */
+ dom_string *atr_string;
+
+ /* get href attribute if present */
+ exc = dom_element_get_attribute(node,
+ corestring_dom_href, &atr_string);
+ if ((exc == DOM_NO_ERR) && (atr_string != NULL)) {
+ nsurl *url;
+ nserror error;
+
+ /* get url from string */
+ error = nsurl_create(dom_string_data(atr_string), &url);
+ dom_string_unref(atr_string);
+ if (error == NSERROR_OK) {
+ if (c->base_url != NULL)
+ nsurl_unref(c->base_url);
+ c->base_url = url;
+ }
+ }
+
+
+ /* get target attribute if present and not already set */
+ if (c->base_target != NULL) {
+ return true;
+ }
+
+ exc = dom_element_get_attribute(node,
+ corestring_dom_target, &atr_string);
+ if ((exc == DOM_NO_ERR) && (atr_string != NULL)) {
+ /* Validation rules from the HTML5 spec for the base element:
+ * The target must be one of _blank, _self, _parent, or
+ * _top or any identifier which does not begin with an
+ * underscore
+ */
+ if (*dom_string_data(atr_string) != '_' ||
+ dom_string_caseless_lwc_isequal(atr_string,
+ corestring_lwc__blank) ||
+ dom_string_caseless_lwc_isequal(atr_string,
+ corestring_lwc__self) ||
+ dom_string_caseless_lwc_isequal(atr_string,
+ corestring_lwc__parent) ||
+ dom_string_caseless_lwc_isequal(atr_string,
+ corestring_lwc__top)) {
+ c->base_target = strdup(dom_string_data(atr_string));
+ }
+ dom_string_unref(atr_string);
+ }
+
+ return true;
+}
+
+static nserror html_meta_refresh_process_element(html_content *c, dom_node *n)
+{
+ union content_msg_data msg_data;
+ const char *url, *end, *refresh = NULL;
+ char *new_url;
+ char quote = '\0';
+ dom_string *equiv, *content;
+ dom_exception exc;
+ nsurl *nsurl;
+ nserror error = NSERROR_OK;
+
+ exc = dom_element_get_attribute(n, corestring_dom_http_equiv, &equiv);
+ if (exc != DOM_NO_ERR) {
+ return NSERROR_DOM;
+ }
+
+ if (equiv == NULL) {
+ return NSERROR_OK;
+ }
+
+ if (!dom_string_caseless_lwc_isequal(equiv, corestring_lwc_refresh)) {
+ dom_string_unref(equiv);
+ return NSERROR_OK;
+ }
+
+ dom_string_unref(equiv);
+
+ exc = dom_element_get_attribute(n, corestring_dom_content, &content);
+ if (exc != DOM_NO_ERR) {
+ return NSERROR_DOM;
+ }
+
+ if (content == NULL) {
+ return NSERROR_OK;
+ }
+
+ end = dom_string_data(content) + dom_string_byte_length(content);
+
+ /* content := *LWS intpart fracpart? *LWS [';' *LWS *1url *LWS]
+ * intpart := 1*DIGIT
+ * fracpart := 1*('.' | DIGIT)
+ * url := "url" *LWS '=' *LWS (url-nq | url-sq | url-dq)
+ * url-nq := *urlchar
+ * url-sq := "'" *(urlchar | '"') "'"
+ * url-dq := '"' *(urlchar | "'") '"'
+ * urlchar := [#x9#x21#x23-#x26#x28-#x7E] | nonascii
+ * nonascii := [#x80-#xD7FF#xE000-#xFFFD#x10000-#x10FFFF]
+ */
+
+ url = dom_string_data(content);
+
+ /* *LWS */
+ while (url < end && ascii_is_space(*url)) {
+ url++;
+ }
+
+ /* intpart */
+ if (url == end || (*url < '0' || '9' < *url)) {
+ /* Empty content, or invalid timeval */
+ dom_string_unref(content);
+ return NSERROR_OK;
+ }
+
+ msg_data.delay = (int) strtol(url, &new_url, 10);
+ /* a very small delay and self-referencing URL can cause a loop
+ * that grinds machines to a halt. To prevent this we set a
+ * minimum refresh delay of 1s. */
+ if (msg_data.delay < 1) {
+ msg_data.delay = 1;
+ }
+
+ url = new_url;
+
+ /* fracpart? (ignored, as delay is integer only) */
+ while (url < end && (('0' <= *url && *url <= '9') ||
+ *url == '.')) {
+ url++;
+ }
+
+ /* *LWS */
+ while (url < end && ascii_is_space(*url)) {
+ url++;
+ }
+
+ /* ';' */
+ if (url < end && *url == ';')
+ url++;
+
+ /* *LWS */
+ while (url < end && ascii_is_space(*url)) {
+ url++;
+ }
+
+ if (url == end) {
+ /* Just delay specified, so refresh current page */
+ dom_string_unref(content);
+
+ c->base.refresh = nsurl_ref(
+ content_get_url(&c->base));
+
+ content_broadcast(&c->base, CONTENT_MSG_REFRESH, &msg_data);
+
+ return NSERROR_OK;
+ }
+
+ /* "url" */
+ if (url <= end - 3) {
+ if (strncasecmp(url, "url", 3) == 0) {
+ url += 3;
+ } else {
+ /* Unexpected input, ignore this header */
+ dom_string_unref(content);
+ return NSERROR_OK;
+ }
+ } else {
+ /* Insufficient input, ignore this header */
+ dom_string_unref(content);
+ return NSERROR_OK;
+ }
+
+ /* *LWS */
+ while (url < end && ascii_is_space(*url)) {
+ url++;
+ }
+
+ /* '=' */
+ if (url < end) {
+ if (*url == '=') {
+ url++;
+ } else {
+ /* Unexpected input, ignore this header */
+ dom_string_unref(content);
+ return NSERROR_OK;
+ }
+ } else {
+ /* Insufficient input, ignore this header */
+ dom_string_unref(content);
+ return NSERROR_OK;
+ }
+
+ /* *LWS */
+ while (url < end && ascii_is_space(*url)) {
+ url++;
+ }
+
+ /* '"' or "'" */
+ if (url < end && (*url == '"' || *url == '\'')) {
+ quote = *url;
+ url++;
+ }
+
+ /* Start of URL */
+ refresh = url;
+
+ if (quote != 0) {
+ /* url-sq | url-dq */
+ while (url < end && *url != quote)
+ url++;
+ } else {
+ /* url-nq */
+ while (url < end && !ascii_is_space(*url))
+ url++;
+ }
+
+ /* '"' or "'" or *LWS (we don't care) */
+ if (url > refresh) {
+ /* There's a URL */
+ new_url = strndup(refresh, url - refresh);
+ if (new_url == NULL) {
+ dom_string_unref(content);
+ return NSERROR_NOMEM;
+ }
+
+ error = nsurl_join(c->base_url, new_url, &nsurl);
+ if (error == NSERROR_OK) {
+ /* broadcast valid refresh url */
+
+ c->base.refresh = nsurl;
+
+ content_broadcast(&c->base, CONTENT_MSG_REFRESH,
+ &msg_data);
+ c->refresh = true;
+ }
+
+ free(new_url);
+
+ }
+
+ dom_string_unref(content);
+
+ return error;
+}
+
+static bool html_process_img(html_content *c, dom_node *node)
+{
+ dom_string *src;
+ nsurl *url;
+ nserror err;
+ dom_exception exc;
+ bool success;
+
+ /* Do nothing if foreground images are disabled */
+ if (nsoption_bool(foreground_images) == false) {
+ return true;
+ }
+
+ exc = dom_element_get_attribute(node, corestring_dom_src, &src);
+ if (exc != DOM_NO_ERR || src == NULL) {
+ return true;
+ }
+
+ err = nsurl_join(c->base_url, dom_string_data(src), &url);
+ if (err != NSERROR_OK) {
+ dom_string_unref(src);
+ return false;
+ }
+ dom_string_unref(src);
+
+ /* Speculatively fetch the image */
+ success = html_fetch_object(c, url, NULL, CONTENT_IMAGE, 0, 0, false);
+ nsurl_unref(url);
+
+ return success;
+}
+
+/* exported function documented in html/html_internal.h */
+void html_finish_conversion(html_content *htmlc)
+{
+ union content_msg_data msg_data;
+ dom_exception exc; /* returned by libdom functions */
+ dom_node *html;
+ nserror error;
+
+ /* Bail out if we've been aborted */
+ if (htmlc->aborted) {
+ content_broadcast_errorcode(&htmlc->base, NSERROR_STOPPED);
+ content_set_error(&htmlc->base);
+ return;
+ }
+
+ /* create new css selection context */
+ error = html_css_new_selection_context(htmlc, &htmlc->select_ctx);
+ if (error != NSERROR_OK) {
+ content_broadcast_errorcode(&htmlc->base, error);
+ content_set_error(&htmlc->base);
+ return;
+ }
+
+
+ /* fire a simple event named load at the Document's Window
+ * object, but with its target set to the Document object (and
+ * the currentTarget set to the Window object)
+ */
+ if (htmlc->jscontext != NULL) {
+ js_fire_event(htmlc->jscontext, "load", htmlc->document, NULL);
+ }
+
+ /* convert dom tree to box tree */
+ NSLOG(netsurf, INFO, "DOM to box (%p)", htmlc);
+ content_set_status(&htmlc->base, messages_get("Processing"));
+ msg_data.explicit_status_text = NULL;
+ content_broadcast(&htmlc->base, CONTENT_MSG_STATUS, &msg_data);
+
+ exc = dom_document_get_document_element(htmlc->document, (void *) &html);
+ if ((exc != DOM_NO_ERR) || (html == NULL)) {
+ NSLOG(netsurf, INFO, "error retrieving html element from dom");
+ content_broadcast_errorcode(&htmlc->base, NSERROR_DOM);
+ content_set_error(&htmlc->base);
+ return;
+ }
+
+ error = dom_to_box(html, htmlc, html_box_convert_done);
+ if (error != NSERROR_OK) {
+ NSLOG(netsurf, INFO, "box conversion failed");
+ dom_node_unref(html);
+ html_object_free_objects(htmlc);
+ content_broadcast_errorcode(&htmlc->base, error);
+ content_set_error(&htmlc->base);
+ return;
+ }
+
+ dom_node_unref(html);
+}
+
+/* callback for DOMNodeInserted end type */
+static void
+dom_default_action_DOMNodeInserted_cb(struct dom_event *evt, void *pw)
+{
+ dom_event_target *node;
+ dom_node_type type;
+ dom_exception exc;
+ html_content *htmlc = pw;
+
+ exc = dom_event_get_target(evt, &node);
+ if ((exc == DOM_NO_ERR) && (node != NULL)) {
+ exc = dom_node_get_node_type(node, &type);
+ if ((exc == DOM_NO_ERR) && (type == DOM_ELEMENT_NODE)) {
+ /* an element node has been inserted */
+ dom_html_element_type tag_type;
+
+ exc = dom_html_element_get_tag_type(node, &tag_type);
+ if (exc != DOM_NO_ERR) {
+ tag_type = DOM_HTML_ELEMENT_TYPE__UNKNOWN;
+ }
+
+ switch (tag_type) {
+ case DOM_HTML_ELEMENT_TYPE_LINK:
+ /* Handle stylesheet loading */
+ html_css_process_link(htmlc, (dom_node *)node);
+ /* Generic link handling */
+ html_process_link(htmlc, (dom_node *)node);
+ break;
+ case DOM_HTML_ELEMENT_TYPE_META:
+ if (htmlc->refresh)
+ break;
+ html_meta_refresh_process_element(htmlc,
+ (dom_node *)node);
+ break;
+ case DOM_HTML_ELEMENT_TYPE_TITLE:
+ if (htmlc->title != NULL)
+ break;
+ htmlc->title = dom_node_ref(node);
+ break;
+ case DOM_HTML_ELEMENT_TYPE_BASE:
+ html_process_base(htmlc, (dom_node *)node);
+ break;
+ case DOM_HTML_ELEMENT_TYPE_IMG:
+ html_process_img(htmlc, (dom_node *) node);
+ break;
+ case DOM_HTML_ELEMENT_TYPE_STYLE:
+ html_css_process_style(htmlc, (dom_node *) node);
+ break;
+ default:
+ break;
+ }
+ if (htmlc->enable_scripting) {
+ /* ensure javascript context is available */
+ if (htmlc->jscontext == NULL) {
+ union content_msg_data msg_data;
+
+ msg_data.jscontext = &htmlc->jscontext;
+ content_broadcast(&htmlc->base,
+ CONTENT_MSG_GETCTX,
+ &msg_data);
+ NSLOG(netsurf, INFO,
+ "javascript context: %p (htmlc: %p)",
+ htmlc->jscontext,
+ htmlc);
+ }
+ if (htmlc->jscontext != NULL) {
+ js_handle_new_element(htmlc->jscontext,
+ (dom_element *) node);
+ }
+ }
+ }
+ dom_node_unref(node);
+ }
+}
+
+/* callback for DOMNodeInserted end type */
+static void
+dom_default_action_DOMSubtreeModified_cb(struct dom_event *evt, void *pw)
+{
+ dom_event_target *node;
+ dom_node_type type;
+ dom_exception exc;
+ html_content *htmlc = pw;
+
+ exc = dom_event_get_target(evt, &node);
+ if ((exc == DOM_NO_ERR) && (node != NULL)) {
+ if (htmlc->title == (dom_node *)node) {
+ /* Node is our title node */
+ html_process_title(htmlc, (dom_node *)node);
+ dom_node_unref(node);
+ return;
+ }
+
+ exc = dom_node_get_node_type(node, &type);
+ if ((exc == DOM_NO_ERR) && (type == DOM_ELEMENT_NODE)) {
+ /* an element node has been modified */
+ dom_html_element_type tag_type;
+
+ exc = dom_html_element_get_tag_type(node, &tag_type);
+ if (exc != DOM_NO_ERR) {
+ tag_type = DOM_HTML_ELEMENT_TYPE__UNKNOWN;
+ }
+
+ switch (tag_type) {
+ case DOM_HTML_ELEMENT_TYPE_STYLE:
+ html_css_update_style(htmlc, (dom_node *)node);
+ break;
+ default:
+ break;
+ }
+ }
+ dom_node_unref(node);
+ }
+}
+
+static void
+dom_default_action_finished_cb(struct dom_event *evt, void *pw)
+{
+ html_content *htmlc = pw;
+
+ if (htmlc->jscontext != NULL)
+ js_event_cleanup(htmlc->jscontext, evt);
+}
+
+/* callback function selector
+ *
+ * selects a callback function for libdom to call based on the type and phase.
+ * dom_default_action_phase from events/document_event.h
+ *
+ * The principle events are:
+ * DOMSubtreeModified
+ * DOMAttrModified
+ * DOMNodeInserted
+ * DOMNodeInsertedIntoDocument
+ *
+ * @return callback function pointer or NULL for none
+ */
+static dom_default_action_callback
+dom_event_fetcher(dom_string *type,
+ dom_default_action_phase phase,
+ void **pw)
+{
+ NSLOG(netsurf, DEEPDEBUG, "type:%s", dom_string_data(type));
+
+ if (phase == DOM_DEFAULT_ACTION_END) {
+ if (dom_string_isequal(type, corestring_dom_DOMNodeInserted)) {
+ return dom_default_action_DOMNodeInserted_cb;
+ } else if (dom_string_isequal(type, corestring_dom_DOMSubtreeModified)) {
+ return dom_default_action_DOMSubtreeModified_cb;
+ }
+ } else if (phase == DOM_DEFAULT_ACTION_FINISHED) {
+ return dom_default_action_finished_cb;
+ }
+ return NULL;
+}
+
+static void
+html_document_user_data_handler(dom_node_operation operation,
+ dom_string *key, void *data,
+ struct dom_node *src,
+ struct dom_node *dst)
+{
+ if (dom_string_isequal(corestring_dom___ns_key_html_content_data,
+ key) == false || data == NULL) {
+ return;
+ }
+
+ switch (operation) {
+ case DOM_NODE_CLONED:
+ NSLOG(netsurf, INFO, "Cloned");
+ break;
+ case DOM_NODE_RENAMED:
+ NSLOG(netsurf, INFO, "Renamed");
+ break;
+ case DOM_NODE_IMPORTED:
+ NSLOG(netsurf, INFO, "imported");
+ break;
+ case DOM_NODE_ADOPTED:
+ NSLOG(netsurf, INFO, "Adopted");
+ break;
+ case DOM_NODE_DELETED:
+ /* This is the only path I expect */
+ break;
+ default:
+ NSLOG(netsurf, INFO, "User data operation not handled.");
+ assert(0);
+ }
+}
+
+
+static nserror
+html_create_html_data(html_content *c, const http_parameter *params)
+{
+ lwc_string *charset;
+ nserror nerror;
+ dom_hubbub_parser_params parse_params;
+ dom_hubbub_error error;
+ dom_exception err;
+ void *old_node_data;
+
+ c->parser = NULL;
+ c->parse_completed = false;
+ c->document = NULL;
+ c->quirks = DOM_DOCUMENT_QUIRKS_MODE_NONE;
+ c->encoding = NULL;
+ c->base_url = nsurl_ref(content_get_url(&c->base));
+ c->base_target = NULL;
+ c->aborted = false;
+ c->refresh = false;
+ c->reflowing = false;
+ c->title = NULL;
+ c->bctx = NULL;
+ c->layout = NULL;
+ c->background_colour = NS_TRANSPARENT;
+ c->stylesheet_count = 0;
+ c->stylesheets = NULL;
+ c->select_ctx = NULL;
+ c->universal = NULL;
+ c->num_objects = 0;
+ c->object_list = NULL;
+ c->forms = NULL;
+ c->imagemaps = NULL;
+ c->bw = NULL;
+ c->frameset = NULL;
+ c->iframe = NULL;
+ c->page = NULL;
+ c->font_func = guit->layout;
+ c->drag_type = HTML_DRAG_NONE;
+ c->drag_owner.no_owner = true;
+ c->selection_type = HTML_SELECTION_NONE;
+ c->selection_owner.none = true;
+ c->focus_type = HTML_FOCUS_SELF;
+ c->focus_owner.self = true;
+ c->search = NULL;
+ c->search_string = NULL;
+ c->scripts_count = 0;
+ c->scripts = NULL;
+ c->jscontext = NULL;
+
+ c->enable_scripting = nsoption_bool(enable_javascript);
+ c->base.active = 1; /* The html content itself is active */
+
+ if (lwc_intern_string("*", SLEN("*"), &c->universal) != lwc_error_ok) {
+ return NSERROR_NOMEM;
+ }
+
+ selection_prepare(&c->sel, (struct content *)c, true);
+
+ nerror = http_parameter_list_find_item(params, corestring_lwc_charset, &charset);
+ if (nerror == NSERROR_OK) {
+ c->encoding = strdup(lwc_string_data(charset));
+
+ lwc_string_unref(charset);
+
+ if (c->encoding == NULL) {
+ lwc_string_unref(c->universal);
+ c->universal = NULL;
+ return NSERROR_NOMEM;
+
+ }
+ c->encoding_source = DOM_HUBBUB_ENCODING_SOURCE_HEADER;
+ }
+
+ /* Create the parser binding */
+ parse_params.enc = c->encoding;
+ parse_params.fix_enc = true;
+ parse_params.enable_script = c->enable_scripting;
+ parse_params.msg = NULL;
+ parse_params.script = html_process_script;
+ parse_params.ctx = c;
+ parse_params.daf = dom_event_fetcher;
+
+ error = dom_hubbub_parser_create(&parse_params,
+ &c->parser,
+ &c->document);
+ if ((error != DOM_HUBBUB_OK) && (c->encoding != NULL)) {
+ /* Ok, we don't support the declared encoding. Bailing out
+ * isn't exactly user-friendly, so fall back to autodetect */
+ free(c->encoding);
+ c->encoding = NULL;
+
+ parse_params.enc = c->encoding;
+
+ error = dom_hubbub_parser_create(&parse_params,
+ &c->parser,
+ &c->document);
+ }
+ if (error != DOM_HUBBUB_OK) {
+ nsurl_unref(c->base_url);
+ c->base_url = NULL;
+
+ lwc_string_unref(c->universal);
+ c->universal = NULL;
+
+ return libdom_hubbub_error_to_nserror(error);
+ }
+
+ err = dom_node_set_user_data(c->document,
+ corestring_dom___ns_key_html_content_data,
+ c, html_document_user_data_handler,
+ (void *) &old_node_data);
+ if (err != DOM_NO_ERR) {
+ dom_hubbub_parser_destroy(c->parser);
+ nsurl_unref(c->base_url);
+ c->base_url = NULL;
+
+ lwc_string_unref(c->universal);
+ c->universal = NULL;
+
+ NSLOG(netsurf, INFO, "Unable to set user data.");
+ return NSERROR_DOM;
+ }
+
+ assert(old_node_data == NULL);
+
+ return NSERROR_OK;
+
+}
+
+/**
+ * Create a CONTENT_HTML.
+ *
+ * The content_html_data structure is initialized and the HTML parser is
+ * created.
+ */
+
+static nserror
+html_create(const content_handler *handler,
+ lwc_string *imime_type,
+ const http_parameter *params,
+ llcache_handle *llcache,
+ const char *fallback_charset,
+ bool quirks,
+ struct content **c)
+{
+ html_content *html;
+ nserror error;
+
+ html = calloc(1, sizeof(html_content));
+ if (html == NULL)
+ return NSERROR_NOMEM;
+
+ error = content__init(&html->base, handler, imime_type, params,
+ llcache, fallback_charset, quirks);
+ if (error != NSERROR_OK) {
+ free(html);
+ return error;
+ }
+
+ error = html_create_html_data(html, params);
+ if (error != NSERROR_OK) {
+ content_broadcast_errorcode(&html->base, error);
+ free(html);
+ return error;
+ }
+
+ error = html_css_new_stylesheets(html);
+ if (error != NSERROR_OK) {
+ content_broadcast_errorcode(&html->base, error);
+ free(html);
+ return error;
+ }
+
+ *c = (struct content *) html;
+
+ return NSERROR_OK;
+}
+
+
+
+static nserror
+html_process_encoding_change(struct content *c,
+ const char *data,
+ unsigned int size)
+{
+ html_content *html = (html_content *) c;
+ dom_hubbub_parser_params parse_params;
+ dom_hubbub_error error;
+ const char *encoding;
+ const char *source_data;
+ unsigned long source_size;
+
+ /* Retrieve new encoding */
+ encoding = dom_hubbub_parser_get_encoding(html->parser,
+ &html->encoding_source);
+ if (encoding == NULL) {
+ return NSERROR_NOMEM;
+ }
+
+ if (html->encoding != NULL) {
+ free(html->encoding);
+ html->encoding = NULL;
+ }
+
+ html->encoding = strdup(encoding);
+ if (html->encoding == NULL) {
+ return NSERROR_NOMEM;
+ }
+
+ /* Destroy binding */
+ dom_hubbub_parser_destroy(html->parser);
+ html->parser = NULL;
+
+ if (html->document != NULL) {
+ dom_node_unref(html->document);
+ }
+
+ parse_params.enc = html->encoding;
+ parse_params.fix_enc = true;
+ parse_params.enable_script = html->enable_scripting;
+ parse_params.msg = NULL;
+ parse_params.script = html_process_script;
+ parse_params.ctx = html;
+ parse_params.daf = dom_event_fetcher;
+
+ /* Create new binding, using the new encoding */
+ error = dom_hubbub_parser_create(&parse_params,
+ &html->parser,
+ &html->document);
+ if (error != DOM_HUBBUB_OK) {
+ /* Ok, we don't support the declared encoding. Bailing out
+ * isn't exactly user-friendly, so fall back to Windows-1252 */
+ free(html->encoding);
+ html->encoding = strdup("Windows-1252");
+ if (html->encoding == NULL) {
+ return NSERROR_NOMEM;
+ }
+ parse_params.enc = html->encoding;
+
+ error = dom_hubbub_parser_create(&parse_params,
+ &html->parser,
+ &html->document);
+
+ if (error != DOM_HUBBUB_OK) {
+ return libdom_hubbub_error_to_nserror(error);
+ }
+
+ }
+
+ source_data = content__get_source_data(c, &source_size);
+
+ /* Reprocess all the data. This is safe because
+ * the encoding is now specified at parser start which means
+ * it cannot be changed again.
+ */
+ error = dom_hubbub_parser_parse_chunk(html->parser,
+ (const uint8_t *)source_data,
+ source_size);
+
+ return libdom_hubbub_error_to_nserror(error);
+}
+
+
+/**
+ * Process data for CONTENT_HTML.
+ */
+
+static bool
+html_process_data(struct content *c, const char *data, unsigned int size)
+{
+ html_content *html = (html_content *) c;
+ dom_hubbub_error dom_ret;
+ nserror err = NSERROR_OK; /* assume its all going to be ok */
+
+ dom_ret = dom_hubbub_parser_parse_chunk(html->parser,
+ (const uint8_t *) data,
+ size);
+
+ err = libdom_hubbub_error_to_nserror(dom_ret);
+
+ /* deal with encoding change */
+ if (err == NSERROR_ENCODING_CHANGE) {
+ err = html_process_encoding_change(c, data, size);
+ }
+
+ /* broadcast the error if necessary */
+ if (err != NSERROR_OK) {
+ content_broadcast_errorcode(c, err);
+ return false;
+ }
+
+ return true;
+}
+
+
+/**
+ * Convert a CONTENT_HTML for display.
+ *
+ * The following steps are carried out in order:
+ *
+ * - parsing to an XML tree is completed
+ * - stylesheets are fetched
+ * - the XML tree is converted to a box tree and object fetches are started
+ *
+ * On exit, the content status will be either CONTENT_STATUS_DONE if the
+ * document is completely loaded or CONTENT_STATUS_READY if objects are still
+ * being fetched.
+ */
+
+static bool html_convert(struct content *c)
+{
+ html_content *htmlc = (html_content *) c;
+ dom_exception exc; /* returned by libdom functions */
+
+ /* The quirk check and associated stylesheet fetch is "safe"
+ * once the root node has been inserted into the document
+ * which must have happened by this point in the parse.
+ *
+ * faliure to retrive the quirk mode or to start the
+ * stylesheet fetch is non fatal as this "only" affects the
+ * render and it would annoy the user to fail the entire
+ * render for want of a quirks stylesheet.
+ */
+ exc = dom_document_get_quirks_mode(htmlc->document, &htmlc->quirks);
+ if (exc == DOM_NO_ERR) {
+ html_css_quirks_stylesheets(htmlc);
+ NSLOG(netsurf, INFO, "quirks set to %d", htmlc->quirks);
+ }
+
+ htmlc->base.active--; /* the html fetch is no longer active */
+ NSLOG(netsurf, INFO, "%d fetches active (%p)", htmlc->base.active, c);
+
+ /* The parse cannot be completed here because it may be paused
+ * untill all the resources being fetched have completed.
+ */
+
+ /* if there are no active fetches in progress no scripts are
+ * being fetched or they completed already.
+ */
+ if (html_can_begin_conversion(htmlc)) {
+ return html_begin_conversion(htmlc);
+ }
+ return true;
+}
+
+/* Exported interface documented in html_internal.h */
+bool html_can_begin_conversion(html_content *htmlc)
+{
+ unsigned int i;
+
+ if (htmlc->base.active != 0)
+ return false;
+
+ for (i = 0; i != htmlc->stylesheet_count; i++) {
+ if (htmlc->stylesheets[i].modified)
+ return false;
+ }
+
+ return true;
+}
+
+bool
+html_begin_conversion(html_content *htmlc)
+{
+ dom_node *html;
+ nserror ns_error;
+ struct form *f;
+ dom_exception exc; /* returned by libdom functions */
+ dom_string *node_name = NULL;
+ dom_hubbub_error error;
+
+ /* The act of completing the parse can result in additional data
+ * being flushed through the parser. This may result in new style or
+ * script nodes, upon which the conversion depends. Thus, once we
+ * have completed the parse, we must check again to see if we can
+ * begin the conversion. If we can't, we must stop and wait for the
+ * new styles/scripts to be processed. Once they have been processed,
+ * we will be called again to begin the conversion for real. Thus,
+ * we must also ensure that we don't attempt to complete the parse
+ * multiple times, so store a flag to indicate that parsing is
+ * complete to avoid repeating the completion pointlessly.
+ */
+ if (htmlc->parse_completed == false) {
+ NSLOG(netsurf, INFO, "Completing parse (%p)", htmlc);
+ /* complete parsing */
+ error = dom_hubbub_parser_completed(htmlc->parser);
+ if (error != DOM_HUBBUB_OK) {
+ NSLOG(netsurf, INFO, "Parsing failed");
+
+ content_broadcast_errorcode(&htmlc->base,
+ libdom_hubbub_error_to_nserror(error));
+
+ return false;
+ }
+ htmlc->parse_completed = true;
+ }
+
+ if (html_can_begin_conversion(htmlc) == false) {
+ NSLOG(netsurf, INFO, "Can't begin conversion (%p)", htmlc);
+ /* We can't proceed (see commentary above) */
+ return true;
+ }
+
+ /* Give up processing if we've been aborted */
+ if (htmlc->aborted) {
+ NSLOG(netsurf, INFO, "Conversion aborted (%p) (active: %u)",
+ htmlc, htmlc->base.active);
+ content_set_error(&htmlc->base);
+ content_broadcast_errorcode(&htmlc->base, NSERROR_STOPPED);
+ return false;
+ }
+
+ /* complete script execution */
+ html_script_exec(htmlc);
+
+ /* fire a simple event that bubbles named DOMContentLoaded at
+ * the Document.
+ */
+
+ /* get encoding */
+ if (htmlc->encoding == NULL) {
+ const char *encoding;
+
+ encoding = dom_hubbub_parser_get_encoding(htmlc->parser,
+ &htmlc->encoding_source);
+ if (encoding == NULL) {
+ content_broadcast_errorcode(&htmlc->base,
+ NSERROR_NOMEM);
+ return false;
+ }
+
+ htmlc->encoding = strdup(encoding);
+ if (htmlc->encoding == NULL) {
+ content_broadcast_errorcode(&htmlc->base,
+ NSERROR_NOMEM);
+ return false;
+ }
+ }
+
+ /* locate root element and ensure it is html */
+ exc = dom_document_get_document_element(htmlc->document, (void *) &html);
+ if ((exc != DOM_NO_ERR) || (html == NULL)) {
+ NSLOG(netsurf, INFO, "error retrieving html element from dom");
+ content_broadcast_errorcode(&htmlc->base, NSERROR_DOM);
+ return false;
+ }
+
+ exc = dom_node_get_node_name(html, &node_name);
+ if ((exc != DOM_NO_ERR) ||
+ (node_name == NULL) ||
+ (!dom_string_caseless_lwc_isequal(node_name,
+ corestring_lwc_html))) {
+ NSLOG(netsurf, INFO, "root element not html");
+ content_broadcast_errorcode(&htmlc->base, NSERROR_DOM);
+ dom_node_unref(html);
+ return false;
+ }
+ dom_string_unref(node_name);
+
+ /* Retrieve forms from parser */
+ htmlc->forms = html_forms_get_forms(htmlc->encoding,
+ (dom_html_document *) htmlc->document);
+ for (f = htmlc->forms; f != NULL; f = f->prev) {
+ nsurl *action;
+
+ /* Make all actions absolute */
+ if (f->action == NULL || f->action[0] == '\0') {
+ /* HTML5 4.10.22.3 step 9 */
+ nsurl *doc_addr = content_get_url(&htmlc->base);
+ ns_error = nsurl_join(htmlc->base_url,
+ nsurl_access(doc_addr),
+ &action);
+ } else {
+ ns_error = nsurl_join(htmlc->base_url,
+ f->action,
+ &action);
+ }
+
+ if (ns_error != NSERROR_OK) {
+ content_broadcast_errorcode(&htmlc->base, ns_error);
+
+ dom_node_unref(html);
+ return false;
+ }
+
+ free(f->action);
+ f->action = strdup(nsurl_access(action));
+ nsurl_unref(action);
+ if (f->action == NULL) {
+ content_broadcast_errorcode(&htmlc->base,
+ NSERROR_NOMEM);
+
+ dom_node_unref(html);
+ return false;
+ }
+
+ /* Ensure each form has a document encoding */
+ if (f->document_charset == NULL) {
+ f->document_charset = strdup(htmlc->encoding);
+ if (f->document_charset == NULL) {
+ content_broadcast_errorcode(&htmlc->base,
+ NSERROR_NOMEM);
+ dom_node_unref(html);
+ return false;
+ }
+ }
+ }
+
+ dom_node_unref(html);
+
+ if (htmlc->base.active == 0) {
+ html_finish_conversion(htmlc);
+ }
+
+ return true;
+}
+
+
+/**
+ * Stop loading a CONTENT_HTML.
+ *
+ * called when the content is aborted. This must clean up any state
+ * created during the fetch.
+ */
+
+static void html_stop(struct content *c)
+{
+ html_content *htmlc = (html_content *) c;
+
+ /* invalidate the html content reference to the javascript context
+ * as it is about to become invalid and must not be used any
+ * more.
+ */
+ html_script_invalidate_ctx(htmlc);
+
+ switch (c->status) {
+ case CONTENT_STATUS_LOADING:
+ /* Still loading; simply flag that we've been aborted
+ * html_convert/html_finish_conversion will do the rest */
+ htmlc->aborted = true;
+ break;
+
+ case CONTENT_STATUS_READY:
+ html_object_abort_objects(htmlc);
+
+ /* If there are no further active fetches and we're still
+ * in the READY state, transition to the DONE state. */
+ if (c->status == CONTENT_STATUS_READY && c->active == 0) {
+ content_set_done(c);
+ }
+
+ break;
+
+ case CONTENT_STATUS_DONE:
+ /* Nothing to do */
+ break;
+
+ default:
+ NSLOG(netsurf, INFO, "Unexpected status %d (%p)", c->status,
+ c);
+ assert(0);
+ }
+}
+
+
+/**
+ * Reformat a CONTENT_HTML to a new width.
+ */
+
+static void html_reformat(struct content *c, int width, int height)
+{
+ html_content *htmlc = (html_content *) c;
+ struct box *layout;
+ uint64_t ms_before;
+ uint64_t ms_after;
+ uint64_t ms_interval;
+
+ nsu_getmonotonic_ms(&ms_before);
+
+ htmlc->reflowing = true;
+
+ htmlc->len_ctx.vw = width;
+ htmlc->len_ctx.vh = height;
+ htmlc->len_ctx.root_style = htmlc->layout->style;
+
+ layout_document(htmlc, width, height);
+ layout = htmlc->layout;
+
+ /* width and height are at least margin box of document */
+ c->width = layout->x + layout->padding[LEFT] + layout->width +
+ layout->padding[RIGHT] + layout->border[RIGHT].width +
+ layout->margin[RIGHT];
+ c->height = layout->y + layout->padding[TOP] + layout->height +
+ layout->padding[BOTTOM] + layout->border[BOTTOM].width +
+ layout->margin[BOTTOM];
+
+ /* if boxes overflow right or bottom edge, expand to contain it */
+ if (c->width < layout->x + layout->descendant_x1)
+ c->width = layout->x + layout->descendant_x1;
+ if (c->height < layout->y + layout->descendant_y1)
+ c->height = layout->y + layout->descendant_y1;
+
+ selection_reinit(&htmlc->sel, htmlc->layout);
+
+ htmlc->reflowing = false;
+
+ /* calculate next reflow time at three times what it took to reflow */
+ nsu_getmonotonic_ms(&ms_after);
+
+ ms_interval = (ms_before - ms_after) * 3;
+ if (ms_interval < (nsoption_uint(min_reflow_period) * 10)) {
+ ms_interval = nsoption_uint(min_reflow_period) * 10;
+ }
+ c->reformat_time = ms_after + ms_interval;
+}
+
+
+/**
+ * Redraw a box.
+ *
+ * \param h content containing the box, of type CONTENT_HTML
+ * \param box box to redraw
+ */
+
+void html_redraw_a_box(hlcache_handle *h, struct box *box)
+{
+ int x, y;
+
+ box_coords(box, &x, &y);
+
+ content_request_redraw(h, x, y,
+ box->padding[LEFT] + box->width + box->padding[RIGHT],
+ box->padding[TOP] + box->height + box->padding[BOTTOM]);
+}
+
+
+/**
+ * Redraw a box.
+ *
+ * \param html content containing the box, of type CONTENT_HTML
+ * \param box box to redraw.
+ */
+
+void html__redraw_a_box(struct html_content *html, struct box *box)
+{
+ int x, y;
+
+ box_coords(box, &x, &y);
+
+ content__request_redraw((struct content *)html, x, y,
+ box->padding[LEFT] + box->width + box->padding[RIGHT],
+ box->padding[TOP] + box->height + box->padding[BOTTOM]);
+}
+
+static void html_destroy_frameset(struct content_html_frames *frameset)
+{
+ int i;
+
+ if (frameset->name) {
+ talloc_free(frameset->name);
+ frameset->name = NULL;
+ }
+ if (frameset->url) {
+ talloc_free(frameset->url);
+ frameset->url = NULL;
+ }
+ if (frameset->children) {
+ for (i = 0; i < (frameset->rows * frameset->cols); i++) {
+ if (frameset->children[i].name) {
+ talloc_free(frameset->children[i].name);
+ frameset->children[i].name = NULL;
+ }
+ if (frameset->children[i].url) {
+ nsurl_unref(frameset->children[i].url);
+ frameset->children[i].url = NULL;
+ }
+ if (frameset->children[i].children)
+ html_destroy_frameset(&frameset->children[i]);
+ }
+ talloc_free(frameset->children);
+ frameset->children = NULL;
+ }
+}
+
+static void html_destroy_iframe(struct content_html_iframe *iframe)
+{
+ struct content_html_iframe *next;
+ next = iframe;
+ while ((iframe = next) != NULL) {
+ next = iframe->next;
+ if (iframe->name)
+ talloc_free(iframe->name);
+ if (iframe->url) {
+ nsurl_unref(iframe->url);
+ iframe->url = NULL;
+ }
+ talloc_free(iframe);
+ }
+}
+
+
+static void html_free_layout(html_content *htmlc)
+{
+ if (htmlc->bctx != NULL) {
+ /* freeing talloc context should let the entire box
+ * set be destroyed
+ */
+ talloc_free(htmlc->bctx);
+ }
+}
+
+/**
+ * Destroy a CONTENT_HTML and free all resources it owns.
+ */
+
+static void html_destroy(struct content *c)
+{
+ html_content *html = (html_content *) c;
+ struct form *f, *g;
+
+ NSLOG(netsurf, INFO, "content %p", c);
+
+ /* Destroy forms */
+ for (f = html->forms; f != NULL; f = g) {
+ g = f->prev;
+
+ form_free(f);
+ }
+
+ imagemap_destroy(html);
+
+ if (c->refresh)
+ nsurl_unref(c->refresh);
+
+ if (html->base_url)
+ nsurl_unref(html->base_url);
+
+ if (html->parser != NULL) {
+ dom_hubbub_parser_destroy(html->parser);
+ html->parser = NULL;
+ }
+
+ if (html->document != NULL) {
+ dom_node_unref(html->document);
+ html->document = NULL;
+ }
+
+ if (html->title != NULL) {
+ dom_node_unref(html->title);
+ html->title = NULL;
+ }
+
+ /* Free encoding */
+ if (html->encoding != NULL) {
+ free(html->encoding);
+ html->encoding = NULL;
+ }
+
+ /* Free base target */
+ if (html->base_target != NULL) {
+ free(html->base_target);
+ html->base_target = NULL;
+ }
+
+ /* Free frameset */
+ if (html->frameset != NULL) {
+ html_destroy_frameset(html->frameset);
+ talloc_free(html->frameset);
+ html->frameset = NULL;
+ }
+
+ /* Free iframes */
+ if (html->iframe != NULL) {
+ html_destroy_iframe(html->iframe);
+ html->iframe = NULL;
+ }
+
+ /* Destroy selection context */
+ if (html->select_ctx != NULL) {
+ css_select_ctx_destroy(html->select_ctx);
+ html->select_ctx = NULL;
+ }
+
+ if (html->universal != NULL) {
+ lwc_string_unref(html->universal);
+ html->universal = NULL;
+ }
+
+ /* Free stylesheets */
+ html_css_free_stylesheets(html);
+
+ /* Free scripts */
+ html_script_free(html);
+
+ /* Free objects */
+ html_object_free_objects(html);
+
+ /* free layout */
+ html_free_layout(html);
+}
+
+
+static nserror html_clone(const struct content *old, struct content **newc)
+{
+ /** \todo Clone HTML specifics */
+
+ /* In the meantime, we should never be called, as HTML contents
+ * cannot be shared and we're not intending to fix printing's
+ * cloning of documents. */
+ assert(0 && "html_clone should never be called");
+
+ return true;
+}
+
+
+/**
+ * Handle a window containing a CONTENT_HTML being opened.
+ */
+
+static void
+html_open(struct content *c,
+ struct browser_window *bw,
+ struct content *page,
+ struct object_params *params)
+{
+ html_content *html = (html_content *) c;
+
+ html->bw = bw;
+ html->page = (html_content *) page;
+
+ html->drag_type = HTML_DRAG_NONE;
+ html->drag_owner.no_owner = true;
+
+ /* text selection */
+ selection_init(&html->sel, html->layout, &html->len_ctx);
+ html->selection_type = HTML_SELECTION_NONE;
+ html->selection_owner.none = true;
+
+ html_object_open_objects(html, bw);
+}
+
+
+/**
+ * Handle a window containing a CONTENT_HTML being closed.
+ */
+
+static void html_close(struct content *c)
+{
+ html_content *htmlc = (html_content *) c;
+
+ selection_clear(&htmlc->sel, false);
+
+ if (htmlc->search != NULL) {
+ search_destroy_context(htmlc->search);
+ }
+
+ /* clear the html content reference to the browser window */
+ htmlc->bw = NULL;
+
+ /* invalidate the html content reference to the javascript context
+ * as it is about to become invalid and must not be used any
+ * more.
+ */
+ html_script_invalidate_ctx(htmlc);
+
+ /* remove all object references from the html content */
+ html_object_close_objects(htmlc);
+}
+
+
+/**
+ * Return an HTML content's selection context
+ */
+
+static void html_clear_selection(struct content *c)
+{
+ html_content *html = (html_content *) c;
+
+ switch (html->selection_type) {
+ case HTML_SELECTION_NONE:
+ /* Nothing to do */
+ assert(html->selection_owner.none == true);
+ break;
+ case HTML_SELECTION_TEXTAREA:
+ textarea_clear_selection(html->selection_owner.textarea->
+ gadget->data.text.ta);
+ break;
+ case HTML_SELECTION_SELF:
+ assert(html->selection_owner.none == false);
+ selection_clear(&html->sel, true);
+ break;
+ case HTML_SELECTION_CONTENT:
+ content_clear_selection(html->selection_owner.content->object);
+ break;
+ default:
+ break;
+ }
+
+ /* There is no selection now. */
+ html->selection_type = HTML_SELECTION_NONE;
+ html->selection_owner.none = true;
+}
+
+
+/**
+ * Return an HTML content's selection context
+ */
+
+static char *html_get_selection(struct content *c)
+{
+ html_content *html = (html_content *) c;
+
+ switch (html->selection_type) {
+ case HTML_SELECTION_TEXTAREA:
+ return textarea_get_selection(html->selection_owner.textarea->
+ gadget->data.text.ta);
+ case HTML_SELECTION_SELF:
+ assert(html->selection_owner.none == false);
+ return selection_get_copy(&html->sel);
+ case HTML_SELECTION_CONTENT:
+ return content_get_selection(
+ html->selection_owner.content->object);
+ case HTML_SELECTION_NONE:
+ /* Nothing to do */
+ assert(html->selection_owner.none == true);
+ break;
+ default:
+ break;
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Get access to any content, link URLs and objects (images) currently
+ * at the given (x, y) coordinates.
+ *
+ * \param[in] c html content to look inside
+ * \param[in] x x-coordinate of point of interest
+ * \param[in] y y-coordinate of point of interest
+ * \param[out] data Positional features struct to be updated with any
+ * relevent content, or set to NULL if none.
+ * \return NSERROR_OK on success else appropriate error code.
+ */
+static nserror
+html_get_contextual_content(struct content *c, int x, int y,
+ struct browser_window_features *data)
+{
+ html_content *html = (html_content *) c;
+
+ struct box *box = html->layout;
+ struct box *next;
+ int box_x = 0, box_y = 0;
+
+ while ((next = box_at_point(&html->len_ctx, box, x, y,
+ &box_x, &box_y)) != NULL) {
+ box = next;
+
+ /* hidden boxes are ignored */
+ if ((box->style != NULL) &&
+ css_computed_visibility(box->style) == CSS_VISIBILITY_HIDDEN) {
+ continue;
+ }
+
+ if (box->iframe) {
+ browser_window_get_features(box->iframe,
+ x - box_x, y - box_y, data);
+ }
+
+ if (box->object)
+ content_get_contextual_content(box->object,
+ x - box_x, y - box_y, data);
+
+ if (box->object)
+ data->object = box->object;
+
+ if (box->href)
+ data->link = box->href;
+
+ if (box->usemap) {
+ const char *target = NULL;
+ nsurl *url = imagemap_get(html, box->usemap, box_x,
+ box_y, x, y, &target);
+ /* Box might have imagemap, but no actual link area
+ * at point */
+ if (url != NULL)
+ data->link = url;
+ }
+ if (box->gadget) {
+ switch (box->gadget->type) {
+ case GADGET_TEXTBOX:
+ case GADGET_TEXTAREA:
+ case GADGET_PASSWORD:
+ data->form_features = CTX_FORM_TEXT;
+ break;
+
+ case GADGET_FILE:
+ data->form_features = CTX_FORM_FILE;
+ break;
+
+ default:
+ data->form_features = CTX_FORM_NONE;
+ break;
+ }
+ }
+ }
+ return NSERROR_OK;
+}
+
+
+/**
+ * Scroll deepest thing within the content which can be scrolled at given point
+ *
+ * \param c html content to look inside
+ * \param x x-coordinate of point of interest
+ * \param y y-coordinate of point of interest
+ * \param scrx number of px try to scroll something in x direction
+ * \param scry number of px try to scroll something in y direction
+ * \return true iff scroll was consumed by something in the content
+ */
+static bool
+html_scroll_at_point(struct content *c, int x, int y, int scrx, int scry)
+{
+ html_content *html = (html_content *) c;
+
+ struct box *box = html->layout;
+ struct box *next;
+ int box_x = 0, box_y = 0;
+ bool handled_scroll = false;
+
+ /* TODO: invert order; visit deepest box first */
+
+ while ((next = box_at_point(&html->len_ctx, box, x, y,
+ &box_x, &box_y)) != NULL) {
+ box = next;
+
+ if (box->style && css_computed_visibility(box->style) ==
+ CSS_VISIBILITY_HIDDEN)
+ continue;
+
+ /* Pass into iframe */
+ if (box->iframe && browser_window_scroll_at_point(box->iframe,
+ x - box_x, y - box_y, scrx, scry) == true)
+ return true;
+
+ /* Pass into textarea widget */
+ if (box->gadget && (box->gadget->type == GADGET_TEXTAREA ||
+ box->gadget->type == GADGET_PASSWORD ||
+ box->gadget->type == GADGET_TEXTBOX) &&
+ textarea_scroll(box->gadget->data.text.ta,
+ scrx, scry) == true)
+ return true;
+
+ /* Pass into object */
+ if (box->object != NULL && content_scroll_at_point(
+ box->object, x - box_x, y - box_y,
+ scrx, scry) == true)
+ return true;
+
+ /* Handle box scrollbars */
+ if (box->scroll_y && scrollbar_scroll(box->scroll_y, scry))
+ handled_scroll = true;
+
+ if (box->scroll_x && scrollbar_scroll(box->scroll_x, scrx))
+ handled_scroll = true;
+
+ if (handled_scroll == true)
+ return true;
+ }
+
+ return false;
+}
+
+/** Helper for file gadgets to store their filename unencoded on the
+ * dom node associated with the gadget.
+ *
+ * \todo Get rid of this crap eventually
+ */
+static void html__dom_user_data_handler(dom_node_operation operation,
+ dom_string *key, void *_data, struct dom_node *src,
+ struct dom_node *dst)
+{
+ char *oldfile;
+ char *data = (char *)_data;
+
+ if (!dom_string_isequal(corestring_dom___ns_key_file_name_node_data,
+ key) || data == NULL) {
+ return;
+ }
+
+ switch (operation) {
+ case DOM_NODE_CLONED:
+ if (dom_node_set_user_data(dst,
+ corestring_dom___ns_key_file_name_node_data,
+ strdup(data), html__dom_user_data_handler,
+ &oldfile) == DOM_NO_ERR) {
+ if (oldfile != NULL)
+ free(oldfile);
+ }
+ break;
+
+ case DOM_NODE_RENAMED:
+ case DOM_NODE_IMPORTED:
+ case DOM_NODE_ADOPTED:
+ break;
+
+ case DOM_NODE_DELETED:
+ free(data);
+ break;
+ default:
+ NSLOG(netsurf, INFO, "User data operation not handled.");
+ assert(0);
+ }
+}
+
+static void html__set_file_gadget_filename(struct content *c,
+ struct form_control *gadget, const char *fn)
+{
+ nserror ret;
+ char *utf8_fn, *oldfile = NULL;
+ html_content *html = (html_content *)c;
+ struct box *file_box = gadget->box;
+
+ ret = guit->utf8->local_to_utf8(fn, 0, &utf8_fn);
+ if (ret != NSERROR_OK) {
+ assert(ret != NSERROR_BAD_ENCODING);
+ NSLOG(netsurf, INFO,
+ "utf8 to local encoding conversion failed");
+ /* Load was for us - just no memory */
+ return;
+ }
+
+ form_gadget_update_value(gadget, utf8_fn);
+
+ /* corestring_dom___ns_key_file_name_node_data */
+ if (dom_node_set_user_data((dom_node *)file_box->gadget->node,
+ corestring_dom___ns_key_file_name_node_data,
+ strdup(fn), html__dom_user_data_handler,
+ &oldfile) == DOM_NO_ERR) {
+ if (oldfile != NULL)
+ free(oldfile);
+ }
+
+ /* Redraw box. */
+ html__redraw_a_box(html, file_box);
+}
+
+void html_set_file_gadget_filename(struct hlcache_handle *hl,
+ struct form_control *gadget, const char *fn)
+{
+ return html__set_file_gadget_filename(hlcache_handle_get_content(hl),
+ gadget, fn);
+}
+
+/**
+ * Drop a file onto a content at a particular point, or determine if a file
+ * may be dropped onto the content at given point.
+ *
+ * \param c html content to look inside
+ * \param x x-coordinate of point of interest
+ * \param y y-coordinate of point of interest
+ * \param file path to file to be dropped, or NULL to know if drop allowed
+ * \return true iff file drop has been handled, or if drop possible (NULL file)
+ */
+static bool html_drop_file_at_point(struct content *c, int x, int y, char *file)
+{
+ html_content *html = (html_content *) c;
+
+ struct box *box = html->layout;
+ struct box *next;
+ struct box *file_box = NULL;
+ struct box *text_box = NULL;
+ int box_x = 0, box_y = 0;
+
+ /* Scan box tree for boxes that can handle drop */
+ while ((next = box_at_point(&html->len_ctx, box, x, y,
+ &box_x, &box_y)) != NULL) {
+ box = next;
+
+ if (box->style && css_computed_visibility(box->style) ==
+ CSS_VISIBILITY_HIDDEN)
+ continue;
+
+ if (box->iframe)
+ return browser_window_drop_file_at_point(box->iframe,
+ x - box_x, y - box_y, file);
+
+ if (box->object && content_drop_file_at_point(box->object,
+ x - box_x, y - box_y, file) == true)
+ return true;
+
+ if (box->gadget) {
+ switch (box->gadget->type) {
+ case GADGET_FILE:
+ file_box = box;
+ break;
+
+ case GADGET_TEXTBOX:
+ case GADGET_TEXTAREA:
+ case GADGET_PASSWORD:
+ text_box = box;
+ break;
+
+ default: /* appease compiler */
+ break;
+ }
+ }
+ }
+
+ if (!file_box && !text_box)
+ /* No box capable of handling drop */
+ return false;
+
+ if (file == NULL)
+ /* There is a box capable of handling drop here */
+ return true;
+
+ /* Handle the drop */
+ if (file_box) {
+ /* File dropped on file input */
+ html__set_file_gadget_filename(c, file_box->gadget, file);
+
+ } else {
+ /* File dropped on text input */
+
+ size_t file_len;
+ FILE *fp = NULL;
+ char *buffer;
+ char *utf8_buff;
+ nserror ret;
+ unsigned int size;
+ int bx, by;
+
+ /* Open file */
+ fp = fopen(file, "rb");
+ if (fp == NULL) {
+ /* Couldn't open file, but drop was for us */
+ return true;
+ }
+
+ /* Get filesize */
+ fseek(fp, 0, SEEK_END);
+ file_len = ftell(fp);
+ fseek(fp, 0, SEEK_SET);
+
+ if ((long)file_len == -1) {
+ /* unable to get file length, but drop was for us */
+ fclose(fp);
+ return true;
+ }
+
+ /* Allocate buffer for file data */
+ buffer = malloc(file_len + 1);
+ if (buffer == NULL) {
+ /* No memory, but drop was for us */
+ fclose(fp);
+ return true;
+ }
+
+ /* Stick file into buffer */
+ if (file_len != fread(buffer, 1, file_len, fp)) {
+ /* Failed, but drop was for us */
+ free(buffer);
+ fclose(fp);
+ return true;
+ }
+
+ /* Done with file */
+ fclose(fp);
+
+ /* Ensure buffer's string termination */
+ buffer[file_len] = '\0';
+
+ /* TODO: Sniff for text? */
+
+ /* Convert to UTF-8 */
+ ret = guit->utf8->local_to_utf8(buffer, file_len, &utf8_buff);
+ if (ret != NSERROR_OK) {
+ /* bad encoding shouldn't happen */
+ assert(ret != NSERROR_BAD_ENCODING);
+ NSLOG(netsurf, INFO, "local to utf8 encoding failed");
+ free(buffer);
+ guit->misc->warning("NoMemory", NULL);
+ return true;
+ }
+
+ /* Done with buffer */
+ free(buffer);
+
+ /* Get new length */
+ size = strlen(utf8_buff);
+
+ /* Simulate a click over the input box, to place caret */
+ box_coords(text_box, &bx, &by);
+ textarea_mouse_action(text_box->gadget->data.text.ta,
+ BROWSER_MOUSE_PRESS_1, x - bx, y - by);
+
+ /* Paste the file as text */
+ textarea_drop_text(text_box->gadget->data.text.ta,
+ utf8_buff, size);
+
+ free(utf8_buff);
+ }
+
+ return true;
+}
+
+
+/**
+ * set debug status.
+ *
+ * \param c The content to debug
+ * \param op The debug operation type
+ */
+static nserror
+html_debug(struct content *c, enum content_debug op)
+{
+ html_redraw_debug = !html_redraw_debug;
+
+ return NSERROR_OK;
+}
+
+
+/**
+ * Dump debug info concerning the html_content
+ *
+ * \param c The content to debug
+ * \param f The file to dump to
+ * \param op The debug dump type
+ */
+static nserror
+html_debug_dump(struct content *c, FILE *f, enum content_debug op)
+{
+ html_content *htmlc = (html_content *)c;
+ dom_node *html;
+ dom_exception exc; /* returned by libdom functions */
+ nserror ret;
+
+ assert(htmlc != NULL);
+
+ if (op == CONTENT_DEBUG_RENDER) {
+ assert(htmlc->layout != NULL);
+ box_dump(f, htmlc->layout, 0, true);
+ ret = NSERROR_OK;
+ } else {
+ if (htmlc->document == NULL) {
+ NSLOG(netsurf, INFO, "No document to dump");
+ return NSERROR_DOM;
+ }
+
+ exc = dom_document_get_document_element(htmlc->document, (void *) &html);
+ if ((exc != DOM_NO_ERR) || (html == NULL)) {
+ NSLOG(netsurf, INFO, "Unable to obtain root node");
+ return NSERROR_DOM;
+ }
+
+ ret = libdom_dump_structure(html, f, 0);
+
+ NSLOG(netsurf, INFO, "DOM structure dump returning %d", ret);
+
+ dom_node_unref(html);
+ }
+
+ return ret;
+}
+
+
+#if ALWAYS_DUMP_FRAMESET
+/**
+ * Print a frameset tree to stderr.
+ */
+
+static void
+html_dump_frameset(struct content_html_frames *frame, unsigned int depth)
+{
+ unsigned int i;
+ int row, col, index;
+ const char *unit[] = {"px", "%", "*"};
+ const char *scrolling[] = {"auto", "yes", "no"};
+
+ assert(frame);
+
+ fprintf(stderr, "%p ", frame);
+
+ fprintf(stderr, "(%i %i) ", frame->rows, frame->cols);
+
+ fprintf(stderr, "w%g%s ", frame->width.value, unit[frame->width.unit]);
+ fprintf(stderr, "h%g%s ", frame->height.value,unit[frame->height.unit]);
+ fprintf(stderr, "(margin w%i h%i) ",
+ frame->margin_width, frame->margin_height);
+
+ if (frame->name)
+ fprintf(stderr, "'%s' ", frame->name);
+ if (frame->url)
+ fprintf(stderr, "<%s> ", frame->url);
+
+ if (frame->no_resize)
+ fprintf(stderr, "noresize ");
+ fprintf(stderr, "(scrolling %s) ", scrolling[frame->scrolling]);
+ if (frame->border)
+ fprintf(stderr, "border %x ",
+ (unsigned int) frame->border_colour);
+
+ fprintf(stderr, "\n");
+
+ if (frame->children) {
+ for (row = 0; row != frame->rows; row++) {
+ for (col = 0; col != frame->cols; col++) {
+ for (i = 0; i != depth; i++)
+ fprintf(stderr, " ");
+ fprintf(stderr, "(%i %i): ", row, col);
+ index = (row * frame->cols) + col;
+ html_dump_frameset(&frame->children[index],
+ depth + 1);
+ }
+ }
+ }
+}
+
+#endif
+
+/**
+ * Retrieve HTML document tree
+ *
+ * \param h HTML content to retrieve document tree from
+ * \return Pointer to document tree
+ */
+dom_document *html_get_document(hlcache_handle *h)
+{
+ html_content *c = (html_content *) hlcache_handle_get_content(h);
+
+ assert(c != NULL);
+
+ return c->document;
+}
+
+/**
+ * Retrieve box tree
+ *
+ * \param h HTML content to retrieve tree from
+ * \return Pointer to box tree
+ *
+ * \todo This API must die, as must all use of the box tree outside of
+ * HTML content handler
+ */
+struct box *html_get_box_tree(hlcache_handle *h)
+{
+ html_content *c = (html_content *) hlcache_handle_get_content(h);
+
+ assert(c != NULL);
+
+ return c->layout;
+}
+
+/**
+ * Retrieve the charset of an HTML document
+ *
+ * \param c Content to retrieve charset from
+ * \param op The content encoding operation to perform.
+ * \return Pointer to charset, or NULL
+ */
+static const char *html_encoding(const struct content *c, enum content_encoding_type op)
+{
+ html_content *html = (html_content *) c;
+ static char enc_token[10] = "Encoding0";
+
+ assert(html != NULL);
+
+ if (op == CONTENT_ENCODING_SOURCE) {
+ enc_token[8] = '0' + html->encoding_source;
+ return messages_get(enc_token);
+ }
+
+ return html->encoding;
+}
+
+
+/**
+ * Retrieve framesets used in an HTML document
+ *
+ * \param h Content to inspect
+ * \return Pointer to framesets, or NULL if none
+ */
+struct content_html_frames *html_get_frameset(hlcache_handle *h)
+{
+ html_content *c = (html_content *) hlcache_handle_get_content(h);
+
+ assert(c != NULL);
+
+ return c->frameset;
+}
+
+/**
+ * Retrieve iframes used in an HTML document
+ *
+ * \param h Content to inspect
+ * \return Pointer to iframes, or NULL if none
+ */
+struct content_html_iframe *html_get_iframe(hlcache_handle *h)
+{
+ html_content *c = (html_content *) hlcache_handle_get_content(h);
+
+ assert(c != NULL);
+
+ return c->iframe;
+}
+
+/**
+ * Retrieve an HTML content's base URL
+ *
+ * \param h Content to retrieve base target from
+ * \return Pointer to URL
+ */
+nsurl *html_get_base_url(hlcache_handle *h)
+{
+ html_content *c = (html_content *) hlcache_handle_get_content(h);
+
+ assert(c != NULL);
+
+ return c->base_url;
+}
+
+/**
+ * Retrieve an HTML content's base target
+ *
+ * \param h Content to retrieve base target from
+ * \return Pointer to target, or NULL if none
+ */
+const char *html_get_base_target(hlcache_handle *h)
+{
+ html_content *c = (html_content *) hlcache_handle_get_content(h);
+
+ assert(c != NULL);
+
+ return c->base_target;
+}
+
+
+/**
+ * Retrieve layout coordinates of box with given id
+ *
+ * \param h HTML document to search
+ * \param frag_id String containing an element id
+ * \param x Updated to global x coord iff id found
+ * \param y Updated to global y coord iff id found
+ * \return true iff id found
+ */
+bool html_get_id_offset(hlcache_handle *h, lwc_string *frag_id, int *x, int *y)
+{
+ struct box *pos;
+ struct box *layout;
+
+ if (content_get_type(h) != CONTENT_HTML)
+ return false;
+
+ layout = html_get_box_tree(h);
+
+ if ((pos = box_find_by_id(layout, frag_id)) != 0) {
+ box_coords(pos, x, y);
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Compute the type of a content
+ *
+ * \return CONTENT_HTML
+ */
+static content_type html_content_type(void)
+{
+ return CONTENT_HTML;
+}
+
+
+static void html_fini(void)
+{
+ html_css_fini();
+}
+
+static const content_handler html_content_handler = {
+ .fini = html_fini,
+ .create = html_create,
+ .process_data = html_process_data,
+ .data_complete = html_convert,
+ .reformat = html_reformat,
+ .destroy = html_destroy,
+ .stop = html_stop,
+ .mouse_track = html_mouse_track,
+ .mouse_action = html_mouse_action,
+ .keypress = html_keypress,
+ .redraw = html_redraw,
+ .open = html_open,
+ .close = html_close,
+ .get_selection = html_get_selection,
+ .clear_selection = html_clear_selection,
+ .get_contextual_content = html_get_contextual_content,
+ .scroll_at_point = html_scroll_at_point,
+ .drop_file_at_point = html_drop_file_at_point,
+ .search = html_search,
+ .search_clear = html_search_clear,
+ .debug_dump = html_debug_dump,
+ .debug = html_debug,
+ .clone = html_clone,
+ .get_encoding = html_encoding,
+ .type = html_content_type,
+ .no_share = true,
+};
+
+nserror html_init(void)
+{
+ uint32_t i;
+ nserror error;
+
+ error = html_css_init();
+ if (error != NSERROR_OK)
+ goto error;
+
+ for (i = 0; i < NOF_ELEMENTS(html_types); i++) {
+ error = content_factory_register_handler(html_types[i],
+ &html_content_handler);
+ if (error != NSERROR_OK)
+ goto error;
+ }
+
+ return NSERROR_OK;
+
+error:
+ html_fini();
+
+ return error;
+}
+
+/**
+ * Get the browser window containing an HTML content
+ *
+ * \param c HTML content
+ * \return the browser window
+ */
+struct browser_window *html_get_browser_window(struct content *c)
+{
+ html_content *html = (html_content *) c;
+
+ assert(c != NULL);
+ assert(c->handler == &html_content_handler);
+
+ return html->bw;
+}
diff --git a/content/handlers/html/html.h b/content/handlers/html/html.h
new file mode 100644
index 0000000..691e969
--- /dev/null
+++ b/content/handlers/html/html.h
@@ -0,0 +1,187 @@
+/*
+ * Copyright 2004 James Bursa <bursa(a)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
+ * Interface to text/html content handler.
+ *
+ * These functions should in general be called via the content interface.
+ */
+
+#ifndef NETSURF_HTML_HTML_H
+#define NETSURF_HTML_HTML_H
+
+#include <stdbool.h>
+
+#include <dom/dom.h>
+#include <dom/bindings/hubbub/parser.h>
+
+#include "netsurf/types.h"
+#include "netsurf/content_type.h"
+#include "netsurf/browser_window.h"
+#include "netsurf/mouse.h"
+#include "desktop/frame_types.h"
+
+struct fetch_multipart_data;
+struct box;
+struct rect;
+struct browser_window;
+struct content;
+struct hlcache_handle;
+struct http_parameter;
+struct imagemap;
+struct object_params;
+struct plotters;
+struct textarea;
+struct scrollbar;
+struct scrollbar_msg_data;
+struct search_context;
+struct selection;
+struct nsurl;
+struct plot_font_style;
+
+/**
+ * Container for stylesheets used by an HTML document
+ */
+struct html_stylesheet {
+ struct dom_node *node; /**< dom node associated with sheet */
+ struct hlcache_handle *sheet;
+ bool modified;
+ bool unused;
+};
+
+/**
+ * Container for scripts used by an HTML document
+ */
+struct html_script {
+ /** Type of script */
+ enum html_script_type { HTML_SCRIPT_INLINE,
+ HTML_SCRIPT_SYNC,
+ HTML_SCRIPT_DEFER,
+ HTML_SCRIPT_ASYNC } type;
+ union {
+ struct hlcache_handle *handle;
+ struct dom_string *string;
+ } data; /**< Script data */
+ struct dom_string *mimetype;
+ struct dom_string *encoding;
+ bool already_started;
+ bool parser_inserted;
+ bool force_async;
+ bool ready_exec;
+ bool async;
+ bool defer;
+};
+
+
+/**
+ * An object (img, object, etc. tag) in a CONTENT_HTML document.
+ */
+struct content_html_object {
+ struct content *parent; /**< Parent document */
+ struct content_html_object *next; /**< Next in chain */
+
+ struct hlcache_handle *content; /**< Content, or 0. */
+ struct box *box; /**< Node in box tree containing it. */
+ /** Bitmap of acceptable content types */
+ content_type permitted_types;
+ bool background; /**< This object is a background image. */
+};
+
+struct html_scrollbar_data {
+ struct content *c;
+ struct box *box;
+};
+
+/** Frame tree (frameset or frame tag) */
+struct content_html_frames {
+ int cols; /** number of columns in frameset */
+ int rows; /** number of rows in frameset */
+
+ struct frame_dimension width; /** frame width */
+ struct frame_dimension height; /** frame width */
+ int margin_width; /** frame margin width */
+ int margin_height; /** frame margin height */
+
+ char *name; /** frame name (for targetting) */
+ struct nsurl *url; /** frame url */
+
+ bool no_resize; /** frame is not resizable */
+ browser_scrolling scrolling; /** scrolling characteristics */
+ bool border; /** frame has a border */
+ colour border_colour; /** frame border colour */
+
+ struct content_html_frames *children; /** [cols * rows] children */
+};
+
+/** Inline frame list (iframe tag) */
+struct content_html_iframe {
+ struct box *box;
+
+ int margin_width; /** frame margin width */
+ int margin_height; /** frame margin height */
+
+ char *name; /** frame name (for targetting) */
+ struct nsurl *url; /** frame url */
+
+ browser_scrolling scrolling; /** scrolling characteristics */
+ bool border; /** frame has a border */
+ colour border_colour; /** frame border colour */
+
+ struct content_html_iframe *next;
+};
+
+/* entries in stylesheet_content */
+#define STYLESHEET_BASE 0 /* base style sheet */
+#define STYLESHEET_QUIRKS 1 /* quirks mode stylesheet */
+#define STYLESHEET_ADBLOCK 2 /* adblocking stylesheet */
+#define STYLESHEET_USER 3 /* user stylesheet */
+#define STYLESHEET_START 4 /* start of document stylesheets */
+
+nserror html_init(void);
+
+void html_redraw_a_box(struct hlcache_handle *h, struct box *box);
+
+void html_overflow_scroll_drag_end(struct scrollbar *scrollbar,
+ browser_mouse_state mouse, int x, int y);
+
+dom_document *html_get_document(struct hlcache_handle *h);
+struct box *html_get_box_tree(struct hlcache_handle *h);
+struct content_html_frames *html_get_frameset(struct hlcache_handle *h);
+struct content_html_iframe *html_get_iframe(struct hlcache_handle *h);
+struct nsurl *html_get_base_url(struct hlcache_handle *h);
+const char *html_get_base_target(struct hlcache_handle *h);
+void html_set_file_gadget_filename(struct hlcache_handle *hl,
+ struct form_control *gadget, const char *fn);
+
+/**
+ * Retrieve stylesheets used by HTML document
+ *
+ * \param h Content to retrieve stylesheets from
+ * \param n Pointer to location to receive number of sheets
+ * \return Pointer to array of stylesheets
+ */
+struct html_stylesheet *html_get_stylesheets(struct hlcache_handle *h,
+ unsigned int *n);
+
+struct content_html_object *html_get_objects(struct hlcache_handle *h,
+ unsigned int *n);
+bool html_get_id_offset(struct hlcache_handle *h, lwc_string *frag_id,
+ int *x, int *y);
+
+#endif
diff --git a/content/handlers/html/html_css.c b/content/handlers/html/html_css.c
new file mode 100644
index 0000000..b67d19a
--- /dev/null
+++ b/content/handlers/html/html_css.c
@@ -0,0 +1,714 @@
+/*
+ * Copyright 2013 Vincent Sanders <vince(a)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 css operations.
+ */
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdint.h>
+#include <string.h>
+#include <strings.h>
+#include <stdlib.h>
+
+#include "utils/nsoption.h"
+#include "utils/corestrings.h"
+#include "utils/config.h"
+#include "utils/log.h"
+#include "netsurf/misc.h"
+#include "netsurf/content.h"
+#include "content/hlcache.h"
+#include "css/css.h"
+#include "desktop/gui_internal.h"
+
+#include "html/html_internal.h"
+
+static nsurl *html_default_stylesheet_url;
+static nsurl *html_adblock_stylesheet_url;
+static nsurl *html_quirks_stylesheet_url;
+static nsurl *html_user_stylesheet_url;
+
+static nserror css_error_to_nserror(css_error error)
+{
+ switch (error) {
+ case CSS_OK:
+ return NSERROR_OK;
+
+ case CSS_NOMEM:
+ return NSERROR_NOMEM;
+
+ case CSS_BADPARM:
+ return NSERROR_BAD_PARAMETER;
+
+ case CSS_INVALID:
+ return NSERROR_INVALID;
+
+ case CSS_FILENOTFOUND:
+ return NSERROR_NOT_FOUND;
+
+ case CSS_NEEDDATA:
+ return NSERROR_NEED_DATA;
+
+ case CSS_BADCHARSET:
+ return NSERROR_BAD_ENCODING;
+
+ case CSS_EOF:
+ case CSS_IMPORTS_PENDING:
+ case CSS_PROPERTY_NOT_SET:
+ default:
+ break;
+ }
+ return NSERROR_CSS;
+}
+
+/**
+ * Callback for fetchcache() for stylesheets.
+ */
+
+static nserror
+html_convert_css_callback(hlcache_handle *css,
+ const hlcache_event *event,
+ void *pw)
+{
+ html_content *parent = pw;
+ unsigned int i;
+ struct html_stylesheet *s;
+
+ /* Find sheet */
+ for (i = 0, s = parent->stylesheets;
+ i != parent->stylesheet_count;
+ i++, s++) {
+ if (s->sheet == css)
+ break;
+ }
+
+ assert(i != parent->stylesheet_count);
+
+ switch (event->type) {
+
+ case CONTENT_MSG_DONE:
+ NSLOG(netsurf, INFO, "done stylesheet slot %d '%s'", i,
+ nsurl_access(hlcache_handle_get_url(css)));
+ parent->base.active--;
+ NSLOG(netsurf, INFO, "%d fetches active", parent->base.active);
+ break;
+
+ case CONTENT_MSG_ERROR:
+ NSLOG(netsurf, INFO, "stylesheet %s failed: %s",
+ nsurl_access(hlcache_handle_get_url(css)),
+ event->data.error);
+ /* fall through */
+
+ case CONTENT_MSG_ERRORCODE:
+ hlcache_handle_release(css);
+ s->sheet = NULL;
+ parent->base.active--;
+ NSLOG(netsurf, INFO, "%d fetches active", parent->base.active);
+ content_add_error(&parent->base, "?", 0);
+ break;
+
+ case CONTENT_MSG_POINTER:
+ /* Really don't want this to continue after the switch */
+ return NSERROR_OK;
+
+ default:
+ break;
+ }
+
+ if (html_can_begin_conversion(parent)) {
+ html_begin_conversion(parent);
+ }
+
+ return NSERROR_OK;
+}
+
+static nserror
+html_stylesheet_from_domnode(html_content *c,
+ dom_node *node,
+ hlcache_handle **sheet)
+{
+ hlcache_child_context child;
+ dom_string *style;
+ nsurl *url;
+ dom_exception exc;
+ nserror error;
+ uint32_t key;
+ char urlbuf[64];
+
+ child.charset = c->encoding;
+ child.quirks = c->base.quirks;
+
+ exc = dom_node_get_text_content(node, &style);
+ if ((exc != DOM_NO_ERR) || (style == NULL)) {
+ NSLOG(netsurf, INFO, "No text content");
+ return NSERROR_OK;
+ }
+
+ error = html_css_fetcher_add_item(style, c->base_url, &key);
+ if (error != NSERROR_OK) {
+ dom_string_unref(style);
+ return error;
+ }
+
+ dom_string_unref(style);
+
+ snprintf(urlbuf, sizeof(urlbuf), "x-ns-css:%u", key);
+
+ error = nsurl_create(urlbuf, &url);
+ if (error != NSERROR_OK) {
+ return error;
+ }
+
+ error = hlcache_handle_retrieve(url, 0,
+ content_get_url(&c->base), NULL,
+ html_convert_css_callback, c, &child, CONTENT_CSS,
+ sheet);
+ if (error != NSERROR_OK) {
+ nsurl_unref(url);
+ return error;
+ }
+
+ nsurl_unref(url);
+
+ c->base.active++;
+ NSLOG(netsurf, INFO, "%d fetches active", c->base.active);
+
+ return NSERROR_OK;
+}
+
+/**
+ * Process an inline stylesheet in the document.
+ *
+ * \param c content structure
+ * \param style xml node of style element
+ * \return true on success, false if an error occurred
+ */
+
+static struct html_stylesheet *
+html_create_style_element(html_content *c, dom_node *style)
+{
+ dom_string *val;
+ dom_exception exc;
+ struct html_stylesheet *stylesheets;
+
+ /* type='text/css', or not present (invalid but common) */
+ exc = dom_element_get_attribute(style, corestring_dom_type, &val);
+ if (exc == DOM_NO_ERR && val != NULL) {
+ if (!dom_string_caseless_lwc_isequal(val,
+ corestring_lwc_text_css)) {
+ dom_string_unref(val);
+ return NULL;
+ }
+ dom_string_unref(val);
+ }
+
+ /* media contains 'screen' or 'all' or not present */
+ exc = dom_element_get_attribute(style, corestring_dom_media, &val);
+ if (exc == DOM_NO_ERR && val != NULL) {
+ if (strcasestr(dom_string_data(val), "screen") == NULL &&
+ strcasestr(dom_string_data(val),
+ "all") == NULL) {
+ dom_string_unref(val);
+ return NULL;
+ }
+ dom_string_unref(val);
+ }
+
+ /* Extend array */
+ stylesheets = realloc(c->stylesheets,
+ sizeof(struct html_stylesheet) *
+ (c->stylesheet_count + 1));
+ if (stylesheets == NULL) {
+
+ content_broadcast_errorcode(&c->base, NSERROR_NOMEM);
+ return false;
+
+ }
+ c->stylesheets = stylesheets;
+
+ c->stylesheets[c->stylesheet_count].node = dom_node_ref(style);
+ c->stylesheets[c->stylesheet_count].sheet = NULL;
+ c->stylesheets[c->stylesheet_count].modified = false;
+ c->stylesheets[c->stylesheet_count].unused = false;
+ c->stylesheet_count++;
+
+ return c->stylesheets + (c->stylesheet_count - 1);
+}
+
+static bool html_css_process_modified_style(html_content *c,
+ struct html_stylesheet *s)
+{
+ hlcache_handle *sheet = NULL;
+ nserror error;
+
+ error = html_stylesheet_from_domnode(c, s->node, &sheet);
+ if (error != NSERROR_OK) {
+ NSLOG(netsurf, INFO, "Failed to update sheet");
+ content_broadcast_errorcode(&c->base, error);
+ return false;
+ }
+
+ if (sheet != NULL) {
+ NSLOG(netsurf, INFO, "Updating sheet %p with %p", s->sheet,
+ sheet);
+
+ if (s->sheet != NULL) {
+ switch (content_get_status(s->sheet)) {
+ case CONTENT_STATUS_DONE:
+ break;
+ default:
+ hlcache_handle_abort(s->sheet);
+ c->base.active--;
+ NSLOG(netsurf, INFO, "%d fetches active",
+ c->base.active);
+ }
+ hlcache_handle_release(s->sheet);
+ }
+ s->sheet = sheet;
+ }
+
+ s->modified = false;
+
+ return true;
+}
+
+static void html_css_process_modified_styles(void *pw)
+{
+ html_content *c = pw;
+ struct html_stylesheet *s;
+ unsigned int i;
+ bool all_done = true;
+
+ for (i = 0, s = c->stylesheets; i != c->stylesheet_count; i++, s++) {
+ if (c->stylesheets[i].modified) {
+ all_done &= html_css_process_modified_style(c, s);
+ }
+ }
+
+ /* If we failed to process any sheet, schedule a retry */
+ if (all_done == false) {
+ guit->misc->schedule(1000, html_css_process_modified_styles, c);
+ }
+}
+
+bool html_css_update_style(html_content *c, dom_node *style)
+{
+ unsigned int i;
+ struct html_stylesheet *s;
+
+ /* Find sheet */
+ for (i = 0, s = c->stylesheets; i != c->stylesheet_count; i++, s++) {
+ if (s->node == style)
+ break;
+ }
+ if (i == c->stylesheet_count) {
+ s = html_create_style_element(c, style);
+ }
+ if (s == NULL) {
+ NSLOG(netsurf, INFO,
+ "Could not find or create inline stylesheet for %p",
+ style);
+ return false;
+ }
+
+ s->modified = true;
+
+ guit->misc->schedule(0, html_css_process_modified_styles, c);
+
+ return true;
+}
+
+bool html_css_process_style(html_content *c, dom_node *node)
+{
+ unsigned int i;
+ dom_string *val;
+ dom_exception exc;
+ struct html_stylesheet *s;
+
+ /* Find sheet */
+ for (i = 0, s = c->stylesheets; i != c->stylesheet_count; i++, s++) {
+ if (s->node == node)
+ break;
+ }
+
+ /* Should already exist */
+ if (i == c->stylesheet_count) {
+ return false;
+ }
+
+ exc = dom_element_get_attribute(node, corestring_dom_media, &val);
+ if (exc == DOM_NO_ERR && val != NULL) {
+ if (strcasestr(dom_string_data(val), "screen") == NULL &&
+ strcasestr(dom_string_data(val),
+ "all") == NULL) {
+ s->unused = true;
+ }
+ dom_string_unref(val);
+ }
+
+ return true;
+}
+
+bool html_css_process_link(html_content *htmlc, dom_node *node)
+{
+ dom_string *rel, *type_attr, *media, *href;
+ struct html_stylesheet *stylesheets;
+ nsurl *joined;
+ dom_exception exc;
+ nserror ns_error;
+ hlcache_child_context child;
+
+ /* rel=<space separated list, including 'stylesheet'> */
+ exc = dom_element_get_attribute(node, corestring_dom_rel, &rel);
+ if (exc != DOM_NO_ERR || rel == NULL)
+ return true;
+
+ if (strcasestr(dom_string_data(rel), "stylesheet") == 0) {
+ dom_string_unref(rel);
+ return true;
+ } else if (strcasestr(dom_string_data(rel), "alternate") != 0) {
+ /* Ignore alternate stylesheets */
+ dom_string_unref(rel);
+ return true;
+ }
+ dom_string_unref(rel);
+
+ /* type='text/css' or not present */
+ exc = dom_element_get_attribute(node, corestring_dom_type, &type_attr);
+ if (exc == DOM_NO_ERR && type_attr != NULL) {
+ if (!dom_string_caseless_lwc_isequal(type_attr,
+ corestring_lwc_text_css)) {
+ dom_string_unref(type_attr);
+ return true;
+ }
+ dom_string_unref(type_attr);
+ }
+
+ /* media contains 'screen' or 'all' or not present */
+ exc = dom_element_get_attribute(node, corestring_dom_media, &media);
+ if (exc == DOM_NO_ERR && media != NULL) {
+ if (strcasestr(dom_string_data(media), "screen") == NULL &&
+ strcasestr(dom_string_data(media), "all") == NULL) {
+ dom_string_unref(media);
+ return true;
+ }
+ dom_string_unref(media);
+ }
+
+ /* href='...' */
+ exc = dom_element_get_attribute(node, corestring_dom_href, &href);
+ if (exc != DOM_NO_ERR || href == NULL)
+ return true;
+
+ /* TODO: only the first preferred stylesheets (ie.
+ * those with a title attribute) should be loaded
+ * (see HTML4 14.3) */
+
+ ns_error = nsurl_join(htmlc->base_url, dom_string_data(href), &joined);
+ if (ns_error != NSERROR_OK) {
+ dom_string_unref(href);
+ goto no_memory;
+ }
+ dom_string_unref(href);
+
+ NSLOG(netsurf, INFO, "linked stylesheet %i '%s'",
+ htmlc->stylesheet_count, nsurl_access(joined));
+
+ /* extend stylesheets array to allow for new sheet */
+ stylesheets = realloc(htmlc->stylesheets,
+ sizeof(struct html_stylesheet) *
+ (htmlc->stylesheet_count + 1));
+ if (stylesheets == NULL) {
+ nsurl_unref(joined);
+ ns_error = NSERROR_NOMEM;
+ goto no_memory;
+ }
+
+ htmlc->stylesheets = stylesheets;
+ htmlc->stylesheets[htmlc->stylesheet_count].node = NULL;
+ htmlc->stylesheets[htmlc->stylesheet_count].modified = false;
+ htmlc->stylesheets[htmlc->stylesheet_count].unused = false;
+
+ /* start fetch */
+ child.charset = htmlc->encoding;
+ child.quirks = htmlc->base.quirks;
+
+ ns_error = hlcache_handle_retrieve(joined, 0,
+ content_get_url(&htmlc->base),
+ NULL, html_convert_css_callback,
+ htmlc, &child, CONTENT_CSS,
+ &htmlc->stylesheets[htmlc->stylesheet_count].sheet);
+
+ nsurl_unref(joined);
+
+ if (ns_error != NSERROR_OK)
+ goto no_memory;
+
+ htmlc->stylesheet_count++;
+
+ htmlc->base.active++;
+ NSLOG(netsurf, INFO, "%d fetches active", htmlc->base.active);
+
+ return true;
+
+no_memory:
+ content_broadcast_errorcode(&htmlc->base, ns_error);
+ return false;
+}
+
+/* exported interface documented in html/html.h */
+struct html_stylesheet *html_get_stylesheets(hlcache_handle *h, unsigned int *n)
+{
+ html_content *c = (html_content *) hlcache_handle_get_content(h);
+
+ assert(c != NULL);
+ assert(n != NULL);
+
+ *n = c->stylesheet_count;
+
+ return c->stylesheets;
+}
+
+
+/* exported interface documented in html/html_internal.h */
+nserror html_css_free_stylesheets(html_content *html)
+{
+ unsigned int i;
+
+ guit->misc->schedule(-1, html_css_process_modified_styles, html);
+
+ for (i = 0; i != html->stylesheet_count; i++) {
+ if (html->stylesheets[i].sheet != NULL) {
+ hlcache_handle_release(html->stylesheets[i].sheet);
+ }
+ if (html->stylesheets[i].node != NULL) {
+ dom_node_unref(html->stylesheets[i].node);
+ }
+ }
+ free(html->stylesheets);
+
+ return NSERROR_OK;
+}
+
+/* exported interface documented in html/html_internal.h */
+nserror html_css_quirks_stylesheets(html_content *c)
+{
+ nserror ns_error = NSERROR_OK;
+ hlcache_child_context child;
+
+ assert(c->stylesheets != NULL);
+
+ if (c->quirks == DOM_DOCUMENT_QUIRKS_MODE_FULL) {
+ child.charset = c->encoding;
+ child.quirks = c->base.quirks;
+
+ ns_error = hlcache_handle_retrieve(html_quirks_stylesheet_url,
+ 0, content_get_url(&c->base), NULL,
+ html_convert_css_callback, c, &child,
+ CONTENT_CSS,
+ &c->stylesheets[STYLESHEET_QUIRKS].sheet);
+ if (ns_error != NSERROR_OK) {
+ return ns_error;
+ }
+
+ c->base.active++;
+ NSLOG(netsurf, INFO, "%d fetches active", c->base.active);
+ }
+
+ return ns_error;
+}
+
+/* exported interface documented in html/html_internal.h */
+nserror html_css_new_stylesheets(html_content *c)
+{
+ nserror ns_error;
+ hlcache_child_context child;
+
+ if (c->stylesheets != NULL) {
+ return NSERROR_OK; /* already initialised */
+ }
+
+ /* stylesheet 0 is the base style sheet,
+ * stylesheet 1 is the quirks mode style sheet,
+ * stylesheet 2 is the adblocking stylesheet,
+ * stylesheet 3 is the user stylesheet */
+ c->stylesheets = calloc(STYLESHEET_START,
+ sizeof(struct html_stylesheet));
+ if (c->stylesheets == NULL) {
+ return NSERROR_NOMEM;
+ }
+
+ c->stylesheets[STYLESHEET_BASE].sheet = NULL;
+ c->stylesheets[STYLESHEET_QUIRKS].sheet = NULL;
+ c->stylesheets[STYLESHEET_ADBLOCK].sheet = NULL;
+ c->stylesheets[STYLESHEET_USER].sheet = NULL;
+ c->stylesheet_count = STYLESHEET_START;
+
+ child.charset = c->encoding;
+ child.quirks = c->base.quirks;
+
+ ns_error = hlcache_handle_retrieve(html_default_stylesheet_url, 0,
+ content_get_url(&c->base), NULL,
+ html_convert_css_callback, c, &child, CONTENT_CSS,
+ &c->stylesheets[STYLESHEET_BASE].sheet);
+ if (ns_error != NSERROR_OK) {
+ return ns_error;
+ }
+
+ c->base.active++;
+ NSLOG(netsurf, INFO, "%d fetches active", c->base.active);
+
+
+ if (nsoption_bool(block_advertisements)) {
+ ns_error = hlcache_handle_retrieve(html_adblock_stylesheet_url,
+ 0, content_get_url(&c->base), NULL,
+ html_convert_css_callback,
+ c, &child, CONTENT_CSS,
+ &c->stylesheets[STYLESHEET_ADBLOCK].sheet);
+ if (ns_error != NSERROR_OK) {
+ return ns_error;
+ }
+
+ c->base.active++;
+ NSLOG(netsurf, INFO, "%d fetches active", c->base.active);
+
+ }
+
+ ns_error = hlcache_handle_retrieve(html_user_stylesheet_url, 0,
+ content_get_url(&c->base), NULL,
+ html_convert_css_callback, c, &child, CONTENT_CSS,
+ &c->stylesheets[STYLESHEET_USER].sheet);
+ if (ns_error != NSERROR_OK) {
+ return ns_error;
+ }
+
+ c->base.active++;
+ NSLOG(netsurf, INFO, "%d fetches active", c->base.active);
+
+ return ns_error;
+}
+
+nserror
+html_css_new_selection_context(html_content *c, css_select_ctx **ret_select_ctx)
+{
+ uint32_t i;
+ css_error css_ret;
+ css_select_ctx *select_ctx;
+
+ /* check that the base stylesheet loaded; layout fails without it */
+ if (c->stylesheets[STYLESHEET_BASE].sheet == NULL) {
+ return NSERROR_CSS_BASE;
+ }
+
+ /* Create selection context */
+ css_ret = css_select_ctx_create(&select_ctx);
+ if (css_ret != CSS_OK) {
+ return css_error_to_nserror(css_ret);
+ }
+
+ /* Add sheets to it */
+ for (i = STYLESHEET_BASE; i != c->stylesheet_count; i++) {
+ const struct html_stylesheet *hsheet = &c->stylesheets[i];
+ css_stylesheet *sheet = NULL;
+ css_origin origin = CSS_ORIGIN_AUTHOR;
+
+ /* Filter out stylesheets for non-screen media. */
+ if (hsheet->unused) {
+ continue;
+ }
+
+ if (i < STYLESHEET_USER) {
+ origin = CSS_ORIGIN_UA;
+ } else if (i < STYLESHEET_START) {
+ origin = CSS_ORIGIN_USER;
+ }
+
+ if (hsheet->sheet != NULL) {
+ sheet = nscss_get_stylesheet(hsheet->sheet);
+ }
+
+ if (sheet != NULL) {
+ css_ret = css_select_ctx_append_sheet(select_ctx,
+ sheet,
+ origin,
+ CSS_MEDIA_SCREEN);
+ if (css_ret != CSS_OK) {
+ css_select_ctx_destroy(select_ctx);
+ return css_error_to_nserror(css_ret);
+ }
+ }
+ }
+
+ /* return new selection context to caller */
+ *ret_select_ctx = select_ctx;
+ return NSERROR_OK;
+}
+
+nserror html_css_init(void)
+{
+ nserror error;
+
+ error = html_css_fetcher_register();
+ if (error != NSERROR_OK)
+ return error;
+
+ error = nsurl_create("resource:default.css",
+ &html_default_stylesheet_url);
+ if (error != NSERROR_OK)
+ return error;
+
+ error = nsurl_create("resource:adblock.css",
+ &html_adblock_stylesheet_url);
+ if (error != NSERROR_OK)
+ return error;
+
+ error = nsurl_create("resource:quirks.css",
+ &html_quirks_stylesheet_url);
+ if (error != NSERROR_OK)
+ return error;
+
+ error = nsurl_create("resource:user.css",
+ &html_user_stylesheet_url);
+
+ return error;
+}
+
+void html_css_fini(void)
+{
+ if (html_user_stylesheet_url != NULL) {
+ nsurl_unref(html_user_stylesheet_url);
+ html_user_stylesheet_url = NULL;
+ }
+
+ if (html_quirks_stylesheet_url != NULL) {
+ nsurl_unref(html_quirks_stylesheet_url);
+ html_quirks_stylesheet_url = NULL;
+ }
+
+ if (html_adblock_stylesheet_url != NULL) {
+ nsurl_unref(html_adblock_stylesheet_url);
+ html_adblock_stylesheet_url = NULL;
+ }
+
+ if (html_default_stylesheet_url != NULL) {
+ nsurl_unref(html_default_stylesheet_url);
+ html_default_stylesheet_url = NULL;
+ }
+}
diff --git a/content/handlers/html/html_css_fetcher.c b/content/handlers/html/html_css_fetcher.c
new file mode 100644
index 0000000..7987ea0
--- /dev/null
+++ b/content/handlers/html/html_css_fetcher.c
@@ -0,0 +1,325 @@
+/*
+ * Copyright 2008 Rob Kendrick <rjek(a)netsurf-browser.org>
+ * Copyright 2013 John-Mark Bell <jmb(a)netsurf-browser.org>
+ *
+ * This file is part of NetSurf.
+ *
+ * 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
+ * HTML fetcher for CSS objects
+ */
+
+#include <assert.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <dom/dom.h>
+#include <libwapcaplet/libwapcaplet.h>
+
+#include "netsurf/inttypes.h"
+#include "utils/config.h"
+#include "utils/log.h"
+#include "utils/ring.h"
+#include "utils/nsurl.h"
+#include "utils/utils.h"
+#include "content/fetch.h"
+#include "content/fetchers.h"
+
+#include "html/html_internal.h"
+
+typedef struct html_css_fetcher_item {
+ uint32_t key;
+ dom_string *data;
+ nsurl *base_url;
+
+ struct html_css_fetcher_item *r_next, *r_prev;
+} html_css_fetcher_item;
+
+typedef struct html_css_fetcher_context {
+ struct fetch *parent_fetch;
+
+ nsurl *url;
+ html_css_fetcher_item *item;
+
+ bool aborted;
+ bool locked;
+
+ struct html_css_fetcher_context *r_next, *r_prev;
+} html_css_fetcher_context;
+
+static uint32_t current_key = 0;
+static html_css_fetcher_item *items = NULL;
+static html_css_fetcher_context *ring = NULL;
+
+static bool html_css_fetcher_initialise(lwc_string *scheme)
+{
+ NSLOG(netsurf, INFO, "html_css_fetcher_initialise called for %s",
+ lwc_string_data(scheme));
+ return true;
+}
+
+static void html_css_fetcher_finalise(lwc_string *scheme)
+{
+ NSLOG(netsurf, INFO, "html_css_fetcher_finalise called for %s",
+ lwc_string_data(scheme));
+}
+
+static bool html_css_fetcher_can_fetch(const nsurl *url)
+{
+ return true;
+}
+
+static void *html_css_fetcher_setup(struct fetch *parent_fetch, nsurl *url,
+ bool only_2xx, bool downgrade_tls, const char *post_urlenc,
+ const struct fetch_multipart_data *post_multipart,
+ const char **headers)
+{
+ html_css_fetcher_context *ctx;
+ lwc_string *path;
+ uint32_t key;
+ html_css_fetcher_item *item, *found = NULL;
+
+ /* format of a x-ns-css URL is:
+ * x-ns-url:<key>
+ * Where key is an unsigned 32bit integer
+ */
+
+ path = nsurl_get_component(url, NSURL_PATH);
+ /* The path must exist */
+ if (path == NULL) {
+ return NULL;
+ }
+
+ key = strtoul(lwc_string_data(path), NULL, 10);
+
+ lwc_string_unref(path);
+
+ /* There must be at least one item */
+ if (items == NULL) {
+ return NULL;
+ }
+
+ item = items;
+ do {
+ if (item->key == key) {
+ found = item;
+ break;
+ }
+
+ item = item->r_next;
+ } while (item != items);
+
+ /* We must have found the item */
+ if (found == NULL) {
+ return NULL;
+ }
+
+ ctx = calloc(1, sizeof(*ctx));
+ if (ctx == NULL)
+ return NULL;
+
+ ctx->parent_fetch = parent_fetch;
+ ctx->url = nsurl_ref(url);
+ ctx->item = found;
+
+ RING_INSERT(ring, ctx);
+
+ return ctx;
+}
+
+static bool html_css_fetcher_start(void *ctx)
+{
+ return true;
+}
+
+static void html_css_fetcher_free(void *ctx)
+{
+ html_css_fetcher_context *c = ctx;
+
+ nsurl_unref(c->url);
+ if (c->item != NULL) {
+ nsurl_unref(c->item->base_url);
+ dom_string_unref(c->item->data);
+ RING_REMOVE(items, c->item);
+ free(c->item);
+ }
+ RING_REMOVE(ring, c);
+ free(ctx);
+}
+
+static void html_css_fetcher_abort(void *ctx)
+{
+ html_css_fetcher_context *c = ctx;
+
+ /* To avoid the poll loop having to deal with the fetch context
+ * disappearing from under it, we simply flag the abort here.
+ * The poll loop itself will perform the appropriate cleanup.
+ */
+ c->aborted = true;
+}
+
+static void html_css_fetcher_send_callback(const fetch_msg *msg,
+ html_css_fetcher_context *c)
+{
+ c->locked = true;
+ fetch_send_callback(msg, c->parent_fetch);
+ c->locked = false;
+}
+
+static void html_css_fetcher_poll(lwc_string *scheme)
+{
+ fetch_msg msg;
+ html_css_fetcher_context *c, *next;
+
+ if (ring == NULL) return;
+
+ /* Iterate over ring, processing each pending fetch */
+ c = ring;
+ do {
+ /* Ignore fetches that have been flagged as locked.
+ * This allows safe re-entrant calls to this function.
+ * Re-entrancy can occur if, as a result of a callback,
+ * the interested party causes fetch_poll() to be called
+ * again.
+ */
+ if (c->locked == true) {
+ next = c->r_next;
+ continue;
+ }
+
+ /* Only process non-aborted fetches */
+ if (c->aborted) {
+ /* Nothing to do */
+ assert(c->locked == false);
+ } else if (c->item != NULL) {
+ char header[4096];
+
+ fetch_set_http_code(c->parent_fetch, 200);
+
+ /* Any callback can result in the fetch being aborted.
+ * Therefore, we _must_ check for this after _every_
+ * call to html_css_fetcher_send_callback().
+ */
+ snprintf(header, sizeof header,
+ "Content-Type: text/css; charset=utf-8");
+ msg.type = FETCH_HEADER;
+ msg.data.header_or_data.buf = (const uint8_t *) header;
+ msg.data.header_or_data.len = strlen(header);
+ html_css_fetcher_send_callback(&msg, c);
+
+ if (c->aborted == false) {
+ snprintf(header, sizeof header,
+ "Content-Length: %"PRIsizet,
+ dom_string_byte_length(c->item->data));
+ msg.type = FETCH_HEADER;
+ msg.data.header_or_data.buf =
+ (const uint8_t *) header;
+ msg.data.header_or_data.len = strlen(header);
+ html_css_fetcher_send_callback(&msg, c);
+ }
+
+ if (c->aborted == false) {
+ snprintf(header, sizeof header,
+ "X-NS-Base: %.*s",
+ (int) nsurl_length(c->item->base_url),
+ nsurl_access(c->item->base_url));
+ msg.type = FETCH_HEADER;
+ msg.data.header_or_data.buf =
+ (const uint8_t *) header;
+ msg.data.header_or_data.len = strlen(header);
+ html_css_fetcher_send_callback(&msg, c);
+ }
+
+ if (c->aborted == false) {
+ msg.type = FETCH_DATA;
+ msg.data.header_or_data.buf =
+ (const uint8_t *)
+ dom_string_data(c->item->data);
+ msg.data.header_or_data.len =
+ dom_string_byte_length(c->item->data);
+ html_css_fetcher_send_callback(&msg, c);
+ }
+
+ if (c->aborted == false) {
+ msg.type = FETCH_FINISHED;
+ html_css_fetcher_send_callback(&msg, c);
+ }
+ } else {
+ NSLOG(netsurf, INFO, "Processing of %s failed!",
+ nsurl_access(c->url));
+
+ /* Ensure that we're unlocked here. If we aren't,
+ * then html_css_fetcher_process() is broken.
+ */
+ assert(c->locked == false);
+ }
+
+ /* Compute next fetch item at the last possible moment as
+ * processing this item may have added to the ring.
+ */
+ next = c->r_next;
+
+ fetch_remove_from_queues(c->parent_fetch);
+ fetch_free(c->parent_fetch);
+
+ /* Advance to next ring entry, exiting if we've reached
+ * the start of the ring or the ring has become empty
+ */
+ } while ( (c = next) != ring && ring != NULL);
+}
+
+/* exported interface documented in html_internal.h */
+nserror html_css_fetcher_register(void)
+{
+ lwc_string *scheme;
+ const struct fetcher_operation_table html_css_fetcher_ops = {
+ .initialise = html_css_fetcher_initialise,
+ .acceptable = html_css_fetcher_can_fetch,
+ .setup = html_css_fetcher_setup,
+ .start = html_css_fetcher_start,
+ .abort = html_css_fetcher_abort,
+ .free = html_css_fetcher_free,
+ .poll = html_css_fetcher_poll,
+ .finalise = html_css_fetcher_finalise
+ };
+
+ if (lwc_intern_string("x-ns-css", SLEN("x-ns-css"),
+ &scheme) != lwc_error_ok) {
+ NSLOG(netsurf, INFO, "could not intern \"x-ns-css\".");
+ return NSERROR_INIT_FAILED;
+ }
+
+ return fetcher_add(scheme, &html_css_fetcher_ops);
+}
+
+/* exported interface documented in html_internal.h */
+nserror
+html_css_fetcher_add_item(dom_string *data, nsurl *base_url, uint32_t *key)
+{
+ html_css_fetcher_item *item = malloc(sizeof(*item));
+
+ if (item == NULL) {
+ return NSERROR_NOMEM;
+ }
+
+ *key = item->key = current_key++;
+ item->data = dom_string_ref(data);
+ item->base_url = nsurl_ref(base_url);
+
+ RING_INSERT(items, item);
+
+ return NSERROR_OK;
+}
diff --git a/content/handlers/html/html_forms.c b/content/handlers/html/html_forms.c
new file mode 100644
index 0000000..915eb00
--- /dev/null
+++ b/content/handlers/html/html_forms.c
@@ -0,0 +1,579 @@
+/*
+ * Copyright 2011 Vincent Sanders <vince(a)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
+ * HTML form handling implementation
+ */
+
+#include "utils/config.h"
+#include "utils/corestrings.h"
+#include "utils/log.h"
+
+#include "html/form_internal.h"
+#include "html/html_internal.h"
+
+/**
+ * process form element from dom
+ */
+static struct form *
+parse_form_element(const char *docenc, dom_node *node)
+{
+ dom_string *ds_action = NULL;
+ dom_string *ds_charset = NULL;
+ dom_string *ds_target = NULL;
+ dom_string *ds_method = NULL;
+ dom_string *ds_enctype = NULL;
+ char *action = NULL, *charset = NULL, *target = NULL;
+ form_method method;
+ dom_html_form_element *formele = (dom_html_form_element *)(node);
+ struct form * ret = NULL;
+
+ /* Retrieve the attributes from the node */
+ if (dom_html_form_element_get_action(formele,
+ &ds_action) != DOM_NO_ERR)
+ goto out;
+
+ if (dom_html_form_element_get_accept_charset(formele,
+ &ds_charset) != DOM_NO_ERR)
+ goto out;
+
+ if (dom_html_form_element_get_target(formele,
+ &ds_target) != DOM_NO_ERR)
+ goto out;
+
+ if (dom_html_form_element_get_method(formele,
+ &ds_method) != DOM_NO_ERR)
+ goto out;
+
+ if (dom_html_form_element_get_enctype(formele,
+ &ds_enctype) != DOM_NO_ERR)
+ goto out;
+
+ /* Extract the plain attributes ready for use. We have to do this
+ * because we cannot guarantee that the dom_strings are NULL terminated
+ * and thus we copy them.
+ */
+ if (ds_action != NULL)
+ action = strndup(dom_string_data(ds_action),
+ dom_string_byte_length(ds_action));
+
+ if (ds_charset != NULL)
+ charset = strndup(dom_string_data(ds_charset),
+ dom_string_byte_length(ds_charset));
+
+ if (ds_target != NULL)
+ target = strndup(dom_string_data(ds_target),
+ dom_string_byte_length(ds_target));
+
+ /* Determine the method */
+ method = method_GET;
+ if (ds_method != NULL) {
+ if (dom_string_caseless_lwc_isequal(ds_method,
+ corestring_lwc_post)) {
+ method = method_POST_URLENC;
+ if (ds_enctype != NULL) {
+ if (dom_string_caseless_lwc_isequal(ds_enctype,
+ corestring_lwc_multipart_form_data)) {
+
+ method = method_POST_MULTIPART;
+ }
+ }
+ }
+ }
+
+ /* Construct the form object */
+ ret = form_new(node, action, target, method, charset, docenc);
+
+out:
+ if (ds_action != NULL)
+ dom_string_unref(ds_action);
+ if (ds_charset != NULL)
+ dom_string_unref(ds_charset);
+ if (ds_target != NULL)
+ dom_string_unref(ds_target);
+ if (ds_method != NULL)
+ dom_string_unref(ds_method);
+ if (ds_enctype != NULL)
+ dom_string_unref(ds_enctype);
+ if (action != NULL)
+ free(action);
+ if (charset != NULL)
+ free(charset);
+ if (target != NULL)
+ free(target);
+ return ret;
+}
+
+/* documented in html_internal.h */
+struct form *html_forms_get_forms(const char *docenc, dom_html_document *doc)
+{
+ dom_html_collection *forms;
+ struct form *ret = NULL, *newf;
+ dom_node *node;
+ unsigned long n;
+ uint32_t nforms;
+
+ if (doc == NULL)
+ return NULL;
+
+ /* Attempt to build a set of all the forms */
+ if (dom_html_document_get_forms(doc, &forms) != DOM_NO_ERR)
+ return NULL;
+
+ /* Count the number of forms so we can iterate */
+ if (dom_html_collection_get_length(forms, &nforms) != DOM_NO_ERR)
+ goto out;
+
+ /* Iterate the forms collection, making form structs for returning */
+ for (n = 0; n < nforms; ++n) {
+ if (dom_html_collection_item(forms, n, &node) != DOM_NO_ERR) {
+ goto out;
+ }
+ newf = parse_form_element(docenc, node);
+ dom_node_unref(node);
+ if (newf == NULL) {
+ goto err;
+ }
+ newf->prev = ret;
+ ret = newf;
+ }
+
+ /* All went well */
+ goto out;
+err:
+ while (ret != NULL) {
+ struct form *prev = ret->prev;
+ /* Destroy ret */
+ free(ret);
+ ret = prev;
+ }
+out:
+ /* Finished with the collection, return it */
+ dom_html_collection_unref(forms);
+
+ return ret;
+}
+
+static struct form *
+find_form(struct form *forms, dom_html_form_element *form)
+{
+ while (forms != NULL) {
+ if (forms->node == form)
+ break;
+ forms = forms->prev;
+ }
+
+ return forms;
+}
+
+static struct form_control *
+parse_button_element(struct form *forms, dom_html_button_element *button)
+{
+ struct form_control *control = NULL;
+ dom_exception err;
+ dom_html_form_element *form = NULL;
+ dom_string *ds_type = NULL;
+ dom_string *ds_value = NULL;
+ dom_string *ds_name = NULL;
+
+ err = dom_html_button_element_get_form(button, &form);
+ if (err != DOM_NO_ERR)
+ goto out;
+
+ err = dom_html_button_element_get_type(button, &ds_type);
+ if (err != DOM_NO_ERR)
+ goto out;
+
+ if (ds_type == NULL) {
+ control = form_new_control(button, GADGET_SUBMIT);
+ } else {
+ if (dom_string_caseless_lwc_isequal(ds_type,
+ corestring_lwc_submit)) {
+ control = form_new_control(button, GADGET_SUBMIT);
+ } else if (dom_string_caseless_lwc_isequal(ds_type,
+ corestring_lwc_reset)) {
+ control = form_new_control(button, GADGET_RESET);
+ } else {
+ control = form_new_control(button, GADGET_BUTTON);
+ }
+ }
+
+ if (control == NULL)
+ goto out;
+
+ err = dom_html_button_element_get_value(button, &ds_value);
+ if (err != DOM_NO_ERR)
+ goto out;
+ err = dom_html_button_element_get_name(button, &ds_name);
+ if (err != DOM_NO_ERR)
+ goto out;
+
+ if (ds_value != NULL) {
+ control->value = strndup(
+ dom_string_data(ds_value),
+ dom_string_byte_length(ds_value));
+
+ if (control->value == NULL) {
+ form_free_control(control);
+ control = NULL;
+ goto out;
+ }
+ }
+
+ if (ds_name != NULL) {
+ control->name = strndup(
+ dom_string_data(ds_name),
+ dom_string_byte_length(ds_name));
+
+ if (control->name == NULL) {
+ form_free_control(control);
+ control = NULL;
+ goto out;
+ }
+ }
+
+ if (form != NULL && control != NULL)
+ form_add_control(find_form(forms, form), control);
+
+out:
+ if (form != NULL)
+ dom_node_unref(form);
+ if (ds_type != NULL)
+ dom_string_unref(ds_type);
+ if (ds_value != NULL)
+ dom_string_unref(ds_value);
+ if (ds_name != NULL)
+ dom_string_unref(ds_name);
+
+ return control;
+}
+
+static struct form_control *
+parse_input_element(struct form *forms, dom_html_input_element *input)
+{
+ struct form_control *control = NULL;
+ dom_html_form_element *form = NULL;
+ dom_string *ds_type = NULL;
+ dom_string *ds_name = NULL;
+ dom_string *ds_value = NULL;
+
+ char *name = NULL;
+
+ if (dom_html_input_element_get_form(input, &form) != DOM_NO_ERR)
+ goto out;
+
+ if (dom_html_input_element_get_type(input, &ds_type) != DOM_NO_ERR)
+ goto out;
+
+ if (dom_html_input_element_get_name(input, &ds_name) != DOM_NO_ERR)
+ goto out;
+
+ if (ds_name != NULL)
+ name = strndup(dom_string_data(ds_name),
+ dom_string_byte_length(ds_name));
+
+ if (ds_type != NULL && dom_string_caseless_lwc_isequal(ds_type,
+ corestring_lwc_password)) {
+ control = form_new_control(input, GADGET_PASSWORD);
+ } else if (ds_type != NULL && dom_string_caseless_lwc_isequal(ds_type,
+ corestring_lwc_file)) {
+ control = form_new_control(input, GADGET_FILE);
+ } else if (ds_type != NULL && dom_string_caseless_lwc_isequal(ds_type,
+ corestring_lwc_hidden)) {
+ control = form_new_control(input, GADGET_HIDDEN);
+ } else if (ds_type != NULL && dom_string_caseless_lwc_isequal(ds_type,
+ corestring_lwc_checkbox)) {
+ control = form_new_control(input, GADGET_CHECKBOX);
+ } else if (ds_type != NULL && dom_string_caseless_lwc_isequal(ds_type,
+ corestring_lwc_radio)) {
+ control = form_new_control(input, GADGET_RADIO);
+ } else if (ds_type != NULL && dom_string_caseless_lwc_isequal(ds_type,
+ corestring_lwc_submit)) {
+ control = form_new_control(input, GADGET_SUBMIT);
+ } else if (ds_type != NULL && dom_string_caseless_lwc_isequal(ds_type,
+ corestring_lwc_reset)) {
+ control = form_new_control(input, GADGET_RESET);
+ } else if (ds_type != NULL && dom_string_caseless_lwc_isequal(ds_type,
+ corestring_lwc_button)) {
+ control = form_new_control(input, GADGET_BUTTON);
+ } else if (ds_type != NULL && dom_string_caseless_lwc_isequal(ds_type,
+ corestring_lwc_image)) {
+ control = form_new_control(input, GADGET_IMAGE);
+ } else {
+ control = form_new_control(input, GADGET_TEXTBOX);
+ }
+
+ if (control == NULL)
+ goto out;
+
+ if (name != NULL) {
+ /* Hand the name string over */
+ control->name = name;
+ name = NULL;
+ }
+
+ if (control->type == GADGET_CHECKBOX || control->type == GADGET_RADIO) {
+ bool selected;
+ if (dom_html_input_element_get_checked(
+ input, &selected) == DOM_NO_ERR) {
+ control->selected = selected;
+ }
+ }
+
+ if (control->type == GADGET_PASSWORD ||
+ control->type == GADGET_TEXTBOX) {
+ int32_t maxlength;
+ if (dom_html_input_element_get_max_length(
+ input, &maxlength) != DOM_NO_ERR) {
+ maxlength = -1;
+ }
+
+ if (maxlength >= 0) {
+ /* Got valid maxlength */
+ control->maxlength = maxlength;
+ } else {
+ /* Input has no maxlength attr, or
+ * dom_html_input_element_get_max_length failed.
+ *
+ * Set it to something insane. */
+ control->maxlength = UINT_MAX;
+ }
+ }
+
+ if (control->type != GADGET_FILE && control->type != GADGET_IMAGE) {
+ if (dom_html_input_element_get_value(
+ input, &ds_value) == DOM_NO_ERR) {
+ if (ds_value != NULL) {
+ control->value = strndup(
+ dom_string_data(ds_value),
+ dom_string_byte_length(ds_value));
+ if (control->value == NULL) {
+ form_free_control(control);
+ control = NULL;
+ goto out;
+ }
+ control->length = strlen(control->value);
+ }
+ }
+
+ if (control->type == GADGET_TEXTBOX ||
+ control->type == GADGET_PASSWORD) {
+ if (control->value == NULL) {
+ control->value = strdup("");
+ if (control->value == NULL) {
+ form_free_control(control);
+ control = NULL;
+ goto out;
+ }
+
+ control->length = 0;
+ }
+
+ control->initial_value = strdup(control->value);
+ if (control->initial_value == NULL) {
+ form_free_control(control);
+ control = NULL;
+ goto out;
+ }
+ }
+ }
+
+ if (form != NULL && control != NULL)
+ form_add_control(find_form(forms, form), control);
+
+out:
+ if (form != NULL)
+ dom_node_unref(form);
+ if (ds_type != NULL)
+ dom_string_unref(ds_type);
+ if (ds_name != NULL)
+ dom_string_unref(ds_name);
+ if (ds_value != NULL)
+ dom_string_unref(ds_value);
+
+ if (name != NULL)
+ free(name);
+
+ return control;
+}
+
+static struct form_control *
+parse_textarea_element(struct form *forms, dom_html_text_area_element *ta)
+{
+ struct form_control *control = NULL;
+ dom_html_form_element *form = NULL;
+ dom_string *ds_name = NULL;
+
+ char *name = NULL;
+
+ if (dom_html_text_area_element_get_form(ta, &form) != DOM_NO_ERR)
+ goto out;
+
+ if (dom_html_text_area_element_get_name(ta, &ds_name) != DOM_NO_ERR)
+ goto out;
+
+ if (ds_name != NULL)
+ name = strndup(dom_string_data(ds_name),
+ dom_string_byte_length(ds_name));
+
+ control = form_new_control(ta, GADGET_TEXTAREA);
+
+ if (control == NULL)
+ goto out;
+
+ if (name != NULL) {
+ /* Hand the name string over */
+ control->name = name;
+ name = NULL;
+ }
+
+ if (form != NULL && control != NULL)
+ form_add_control(find_form(forms, form), control);
+
+out:
+ if (form != NULL)
+ dom_node_unref(form);
+ if (ds_name != NULL)
+ dom_string_unref(ds_name);
+
+ if (name != NULL)
+ free(name);
+
+
+ return control;
+}
+
+static struct form_control *
+parse_select_element(struct form *forms, dom_html_select_element *select)
+{
+ struct form_control *control = NULL;
+ dom_html_form_element *form = NULL;
+ dom_string *ds_name = NULL;
+
+ char *name = NULL;
+
+ if (dom_html_select_element_get_form(select, &form) != DOM_NO_ERR)
+ goto out;
+
+ if (dom_html_select_element_get_name(select, &ds_name) != DOM_NO_ERR)
+ goto out;
+
+ if (ds_name != NULL)
+ name = strndup(dom_string_data(ds_name),
+ dom_string_byte_length(ds_name));
+
+ control = form_new_control(select, GADGET_SELECT);
+
+ if (control == NULL)
+ goto out;
+
+ if (name != NULL) {
+ /* Hand the name string over */
+ control->name = name;
+ name = NULL;
+ }
+
+ dom_html_select_element_get_multiple(select,
+ &(control->data.select.multiple));
+
+ if (form != NULL && control != NULL)
+ form_add_control(find_form(forms, form), control);
+
+out:
+ if (form != NULL)
+ dom_node_unref(form);
+ if (ds_name != NULL)
+ dom_string_unref(ds_name);
+
+ if (name != NULL)
+ free(name);
+
+
+ return control;
+}
+
+
+static struct form_control *
+invent_fake_gadget(dom_node *node)
+{
+ struct form_control *ctl = form_new_control(node, GADGET_HIDDEN);
+ if (ctl != NULL) {
+ ctl->value = strdup("");
+ ctl->initial_value = strdup("");
+ ctl->name = strdup("foo");
+
+ if (ctl->value == NULL || ctl->initial_value == NULL ||
+ ctl->name == NULL) {
+ form_free_control(ctl);
+ ctl = NULL;
+ }
+ }
+ return ctl;
+}
+
+/* documented in html_internal.h */
+struct form_control *html_forms_get_control_for_node(struct form *forms,
+ dom_node *node)
+{
+ struct form *f;
+ struct form_control *ctl = NULL;
+ dom_exception err;
+ dom_string *ds_name = NULL;
+
+ /* Step one, see if we already have a control */
+ for (f = forms; f != NULL; f = f->prev) {
+ for (ctl = f->controls; ctl != NULL; ctl = ctl->next) {
+ if (ctl->node == node)
+ return ctl;
+ }
+ }
+
+ /* Step two, extract the node's name so we can construct a gadget. */
+ err = dom_element_get_tag_name(node, &ds_name);
+ if (err == DOM_NO_ERR && ds_name != NULL) {
+
+ /* Step three, attempt to work out what gadget to make */
+ if (dom_string_caseless_lwc_isequal(ds_name,
+ corestring_lwc_button)) {
+ ctl = parse_button_element(forms,
+ (dom_html_button_element *) node);
+ } else if (dom_string_caseless_lwc_isequal(ds_name,
+ corestring_lwc_input)) {
+ ctl = parse_input_element(forms,
+ (dom_html_input_element *) node);
+ } else if (dom_string_caseless_lwc_isequal(ds_name,
+ corestring_lwc_textarea)) {
+ ctl = parse_textarea_element(forms,
+ (dom_html_text_area_element *) node);
+ } else if (dom_string_caseless_lwc_isequal(ds_name,
+ corestring_lwc_select)) {
+ ctl = parse_select_element(forms,
+ (dom_html_select_element *) node);
+ }
+ }
+
+ /* If all else fails, fake gadget time */
+ if (ctl == NULL)
+ ctl = invent_fake_gadget(node);
+
+ if (ds_name != NULL)
+ dom_string_unref(ds_name);
+
+ return ctl;
+}
diff --git a/content/handlers/html/html_interaction.c b/content/handlers/html/html_interaction.c
new file mode 100644
index 0000000..898f55b
--- /dev/null
+++ b/content/handlers/html/html_interaction.c
@@ -0,0 +1,1434 @@
+/*
+ * Copyright 2006 James Bursa <bursa(a)users.sourceforge.net>
+ * Copyright 2006 Richard Wilson <info(a)tinct.net>
+ * Copyright 2008 Michael Drake <tlsa(a)netsurf-browser.org>
+ * Copyright 2009 Paul Blokus <paul_pl(a)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
+ * implementation of user interaction with a CONTENT_HTML.
+ */
+
+#include <assert.h>
+#include <stdbool.h>
+
+#include <dom/dom.h>
+
+#include "utils/corestrings.h"
+#include "utils/messages.h"
+#include "utils/utils.h"
+#include "utils/log.h"
+#include "utils/nsoption.h"
+#include "netsurf/content.h"
+#include "netsurf/browser_window.h"
+#include "netsurf/mouse.h"
+#include "netsurf/misc.h"
+#include "netsurf/layout.h"
+#include "netsurf/keypress.h"
+#include "content/hlcache.h"
+#include "desktop/frames.h"
+#include "desktop/scrollbar.h"
+#include "desktop/selection.h"
+#include "desktop/textarea.h"
+#include "javascript/js.h"
+#include "desktop/gui_internal.h"
+
+#include "html/box.h"
+#include "html/box_textarea.h"
+#include "html/font.h"
+#include "html/form_internal.h"
+#include "html/html_internal.h"
+#include "html/imagemap.h"
+#include "html/search.h"
+
+/**
+ * Get pointer shape for given box
+ *
+ * \param box box in question
+ * \param imagemap whether an imagemap applies to the box
+ */
+
+static browser_pointer_shape get_pointer_shape(struct box *box, bool imagemap)
+{
+ browser_pointer_shape pointer;
+ css_computed_style *style;
+ enum css_cursor_e cursor;
+ lwc_string **cursor_uris;
+
+ if (box->type == BOX_FLOAT_LEFT || box->type == BOX_FLOAT_RIGHT)
+ style = box->children->style;
+ else
+ style = box->style;
+
+ if (style == NULL)
+ return BROWSER_POINTER_DEFAULT;
+
+ cursor = css_computed_cursor(style, &cursor_uris);
+
+ switch (cursor) {
+ case CSS_CURSOR_AUTO:
+ if (box->href || (box->gadget &&
+ (box->gadget->type == GADGET_IMAGE ||
+ box->gadget->type == GADGET_SUBMIT)) ||
+ imagemap) {
+ /* link */
+ pointer = BROWSER_POINTER_POINT;
+ } else if (box->gadget &&
+ (box->gadget->type == GADGET_TEXTBOX ||
+ box->gadget->type == GADGET_PASSWORD ||
+ box->gadget->type == GADGET_TEXTAREA)) {
+ /* text input */
+ pointer = BROWSER_POINTER_CARET;
+ } else {
+ /* html content doesn't mind */
+ pointer = BROWSER_POINTER_AUTO;
+ }
+ break;
+ case CSS_CURSOR_CROSSHAIR:
+ pointer = BROWSER_POINTER_CROSS;
+ break;
+ case CSS_CURSOR_POINTER:
+ pointer = BROWSER_POINTER_POINT;
+ break;
+ case CSS_CURSOR_MOVE:
+ pointer = BROWSER_POINTER_MOVE;
+ break;
+ case CSS_CURSOR_E_RESIZE:
+ pointer = BROWSER_POINTER_RIGHT;
+ break;
+ case CSS_CURSOR_W_RESIZE:
+ pointer = BROWSER_POINTER_LEFT;
+ break;
+ case CSS_CURSOR_N_RESIZE:
+ pointer = BROWSER_POINTER_UP;
+ break;
+ case CSS_CURSOR_S_RESIZE:
+ pointer = BROWSER_POINTER_DOWN;
+ break;
+ case CSS_CURSOR_NE_RESIZE:
+ pointer = BROWSER_POINTER_RU;
+ break;
+ case CSS_CURSOR_SW_RESIZE:
+ pointer = BROWSER_POINTER_LD;
+ break;
+ case CSS_CURSOR_SE_RESIZE:
+ pointer = BROWSER_POINTER_RD;
+ break;
+ case CSS_CURSOR_NW_RESIZE:
+ pointer = BROWSER_POINTER_LU;
+ break;
+ case CSS_CURSOR_TEXT:
+ pointer = BROWSER_POINTER_CARET;
+ break;
+ case CSS_CURSOR_WAIT:
+ pointer = BROWSER_POINTER_WAIT;
+ break;
+ case CSS_CURSOR_PROGRESS:
+ pointer = BROWSER_POINTER_PROGRESS;
+ break;
+ case CSS_CURSOR_HELP:
+ pointer = BROWSER_POINTER_HELP;
+ break;
+ default:
+ pointer = BROWSER_POINTER_DEFAULT;
+ break;
+ }
+
+ return pointer;
+}
+
+
+/**
+ * Start drag scrolling the contents of a box
+ *
+ * \param box the box to be scrolled
+ * \param x x ordinate of initial mouse position
+ * \param y y ordinate
+ */
+
+static void html_box_drag_start(struct box *box, int x, int y)
+{
+ int box_x, box_y;
+ int scroll_mouse_x, scroll_mouse_y;
+
+ box_coords(box, &box_x, &box_y);
+
+ if (box->scroll_x != NULL) {
+ scroll_mouse_x = x - box_x ;
+ scroll_mouse_y = y - (box_y + box->padding[TOP] +
+ box->height + box->padding[BOTTOM] -
+ SCROLLBAR_WIDTH);
+ scrollbar_start_content_drag(box->scroll_x,
+ scroll_mouse_x, scroll_mouse_y);
+ } else if (box->scroll_y != NULL) {
+ scroll_mouse_x = x - (box_x + box->padding[LEFT] +
+ box->width + box->padding[RIGHT] -
+ SCROLLBAR_WIDTH);
+ scroll_mouse_y = y - box_y;
+
+ scrollbar_start_content_drag(box->scroll_y,
+ scroll_mouse_x, scroll_mouse_y);
+ }
+}
+
+
+/**
+ * End overflow scroll scrollbar drags
+ *
+ * \param html html content
+ * \param mouse state of mouse buttons and modifier keys
+ * \param x coordinate of mouse
+ * \param y coordinate of mouse
+ * \param dir Direction of drag
+ */
+static size_t html_selection_drag_end(struct html_content *html,
+ browser_mouse_state mouse, int x, int y, int dir)
+{
+ int pixel_offset;
+ struct box *box;
+ int dx, dy;
+ size_t idx = 0;
+
+ box = box_pick_text_box(html, x, y, dir, &dx, &dy);
+ if (box) {
+ plot_font_style_t fstyle;
+
+ font_plot_style_from_css(&html->len_ctx, box->style, &fstyle);
+
+ guit->layout->position(&fstyle, box->text, box->length,
+ dx, &idx, &pixel_offset);
+
+ idx += box->byte_offset;
+ }
+
+ return idx;
+}
+
+
+/**
+ * Handle mouse tracking (including drags) in an HTML content window.
+ *
+ * \param c content of type html
+ * \param bw browser window
+ * \param mouse state of mouse buttons and modifier keys
+ * \param x coordinate of mouse
+ * \param y coordinate of mouse
+ */
+
+void html_mouse_track(struct content *c, struct browser_window *bw,
+ browser_mouse_state mouse, int x, int y)
+{
+ html_mouse_action(c, bw, mouse, x, y);
+}
+
+/**
+ * Helper for file gadgets to store their filename.
+ *
+ * Stores the filename unencoded on the dom node associated with the
+ * gadget.
+ *
+ * \todo Get rid of this crap eventually
+ *
+ * \param operation DOM operation
+ * \param key DOM node key being considerd
+ * \param _data The data assocated with the key
+ * \param src The source DOM node.
+ * \param dst The destination DOM node.
+ */
+static void
+html__image_coords_dom_user_data_handler(dom_node_operation operation,
+ dom_string *key,
+ void *_data,
+ struct dom_node *src,
+ struct dom_node *dst)
+{
+ struct image_input_coords *oldcoords, *coords = _data, *newcoords;
+
+ if (!dom_string_isequal(corestring_dom___ns_key_image_coords_node_data,
+ key) || coords == NULL) {
+ return;
+ }
+
+ switch (operation) {
+ case DOM_NODE_CLONED:
+ newcoords = calloc(1, sizeof(*newcoords));
+ if (newcoords != NULL) {
+ *newcoords = *coords;
+ if (dom_node_set_user_data(dst,
+ corestring_dom___ns_key_image_coords_node_data,
+ newcoords,
+ html__image_coords_dom_user_data_handler,
+ &oldcoords) == DOM_NO_ERR) {
+ free(oldcoords);
+ }
+ }
+ break;
+
+ case DOM_NODE_DELETED:
+ free(coords);
+ break;
+
+ case DOM_NODE_RENAMED:
+ case DOM_NODE_IMPORTED:
+ case DOM_NODE_ADOPTED:
+ break;
+
+ default:
+ NSLOG(netsurf, INFO, "User data operation not handled.");
+ assert(0);
+ }
+}
+
+/**
+ * Handle mouse clicks and movements in an HTML content window.
+ *
+ * \param c content of type html
+ * \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 html_mouse_action(struct content *c, struct browser_window *bw,
+ browser_mouse_state mouse, int x, int y)
+{
+ html_content *html = (html_content *) c;
+ enum { ACTION_NONE, ACTION_SUBMIT, ACTION_GO } action = ACTION_NONE;
+ const char *title = 0;
+ nsurl *url = 0;
+ char *url_s = NULL;
+ size_t url_l = 0;
+ const char *target = 0;
+ char status_buffer[200];
+ const char *status = 0;
+ browser_pointer_shape pointer = BROWSER_POINTER_DEFAULT;
+ bool imagemap = false;
+ int box_x = 0, box_y = 0;
+ int gadget_box_x = 0, gadget_box_y = 0;
+ int html_object_pos_x = 0, html_object_pos_y = 0;
+ int text_box_x = 0;
+ struct box *url_box = 0;
+ struct box *gadget_box = 0;
+ struct box *text_box = 0;
+ struct box *box;
+ struct form_control *gadget = 0;
+ hlcache_handle *object = NULL;
+ struct box *html_object_box = NULL;
+ struct browser_window *iframe = NULL;
+ struct box *drag_candidate = NULL;
+ struct scrollbar *scrollbar = NULL;
+ plot_font_style_t fstyle;
+ int scroll_mouse_x = 0, scroll_mouse_y = 0;
+ int padding_left, padding_right, padding_top, padding_bottom;
+ browser_drag_type drag_type = browser_window_get_drag_type(bw);
+ union content_msg_data msg_data;
+ struct dom_node *node = NULL;
+ union html_drag_owner drag_owner;
+ union html_selection_owner sel_owner;
+ bool click = mouse & (BROWSER_MOUSE_PRESS_1 | BROWSER_MOUSE_PRESS_2 |
+ BROWSER_MOUSE_CLICK_1 | BROWSER_MOUSE_CLICK_2 |
+ BROWSER_MOUSE_DRAG_1 | BROWSER_MOUSE_DRAG_2);
+
+ if (drag_type != DRAGGING_NONE && !mouse &&
+ html->visible_select_menu != NULL) {
+ /* drag end: select menu */
+ form_select_mouse_drag_end(html->visible_select_menu,
+ mouse, x, y);
+ }
+
+ if (html->visible_select_menu != NULL) {
+ box = html->visible_select_menu->box;
+ box_coords(box, &box_x, &box_y);
+
+ box_x -= box->border[LEFT].width;
+ box_y += box->height + box->border[BOTTOM].width +
+ box->padding[BOTTOM] + box->padding[TOP];
+ status = form_select_mouse_action(html->visible_select_menu,
+ mouse, x - box_x, y - box_y);
+ if (status != NULL) {
+ msg_data.explicit_status_text = status;
+ content_broadcast(c, CONTENT_MSG_STATUS, &msg_data);
+ } else {
+ int width, height;
+ form_select_get_dimensions(html->visible_select_menu,
+ &width, &height);
+ html->visible_select_menu = NULL;
+ browser_window_redraw_rect(bw, box_x, box_y,
+ width, height);
+ }
+ return;
+ }
+
+ if (html->drag_type == HTML_DRAG_SELECTION) {
+ /* Selection drag */
+ struct box *box;
+ int dir = -1;
+ int dx, dy;
+
+ if (!mouse) {
+ /* End of selection drag */
+ int dir = -1;
+ size_t idx;
+
+ if (selection_dragging_start(&html->sel))
+ dir = 1;
+
+ idx = html_selection_drag_end(html, mouse, x, y, dir);
+
+ if (idx != 0)
+ selection_track(&html->sel, mouse, idx);
+
+ drag_owner.no_owner = true;
+ html_set_drag_type(html, HTML_DRAG_NONE,
+ drag_owner, NULL);
+ return;
+ }
+
+ if (selection_dragging_start(&html->sel))
+ dir = 1;
+
+ box = box_pick_text_box(html, x, y, dir, &dx, &dy);
+
+ if (box != NULL) {
+ int pixel_offset;
+ size_t idx;
+ plot_font_style_t fstyle;
+
+ font_plot_style_from_css(&html->len_ctx,
+ box->style, &fstyle);
+
+ guit->layout->position(&fstyle,
+ box->text, box->length,
+ dx, &idx, &pixel_offset);
+
+ selection_track(&html->sel, mouse,
+ box->byte_offset + idx);
+ }
+ return;
+ }
+
+ if (html->drag_type == HTML_DRAG_SCROLLBAR) {
+ struct scrollbar *scr = html->drag_owner.scrollbar;
+ struct html_scrollbar_data *data = scrollbar_get_data(scr);
+
+ if (!mouse) {
+ /* drag end: scrollbar */
+ html_overflow_scroll_drag_end(scr, mouse, x, y);
+ }
+
+ box = data->box;
+ box_coords(box, &box_x, &box_y);
+ if (scrollbar_is_horizontal(scr)) {
+ scroll_mouse_x = x - box_x ;
+ scroll_mouse_y = y - (box_y + box->padding[TOP] +
+ box->height + box->padding[BOTTOM] -
+ SCROLLBAR_WIDTH);
+ status = scrollbar_mouse_status_to_message(
+ scrollbar_mouse_action(scr, mouse,
+ scroll_mouse_x,
+ scroll_mouse_y));
+ } else {
+ scroll_mouse_x = x - (box_x + box->padding[LEFT] +
+ box->width + box->padding[RIGHT] -
+ SCROLLBAR_WIDTH);
+ scroll_mouse_y = y - box_y;
+ status = scrollbar_mouse_status_to_message(
+ scrollbar_mouse_action(scr, mouse,
+ scroll_mouse_x,
+ scroll_mouse_y));
+ }
+
+ msg_data.explicit_status_text = status;
+ content_broadcast(c, CONTENT_MSG_STATUS, &msg_data);
+ return;
+ }
+
+ if (html->drag_type == HTML_DRAG_TEXTAREA_SELECTION ||
+ html->drag_type == HTML_DRAG_TEXTAREA_SCROLLBAR) {
+ box = html->drag_owner.textarea;
+ assert(box->gadget != NULL);
+ assert(box->gadget->type == GADGET_TEXTAREA ||
+ box->gadget->type == GADGET_PASSWORD ||
+ box->gadget->type == GADGET_TEXTBOX);
+
+ box_coords(box, &box_x, &box_y);
+ textarea_mouse_action(box->gadget->data.text.ta, mouse,
+ x - box_x, y - box_y);
+
+ /* TODO: Set appropriate statusbar message */
+ return;
+ }
+
+ if (html->drag_type == HTML_DRAG_CONTENT_SELECTION ||
+ html->drag_type == HTML_DRAG_CONTENT_SCROLL) {
+ box = html->drag_owner.content;
+ assert(box->object != NULL);
+
+ box_coords(box, &box_x, &box_y);
+ content_mouse_track(box->object, bw, mouse,
+ x - box_x, y - box_y);
+ return;
+ }
+
+ if (html->drag_type == HTML_DRAG_CONTENT_SELECTION) {
+ box = html->drag_owner.content;
+ assert(box->object != NULL);
+
+ box_coords(box, &box_x, &box_y);
+ content_mouse_track(box->object, bw, mouse,
+ x - box_x, y - box_y);
+ return;
+ }
+
+ /* Content related drags handled by now */
+ assert(html->drag_type == HTML_DRAG_NONE);
+
+ /* search the box tree for a link, imagemap, form control, or
+ * box with scrollbars
+ */
+
+ box = html->layout;
+
+ /* Consider the margins of the html page now */
+ box_x = box->margin[LEFT];
+ box_y = box->margin[TOP];
+
+ /* descend through visible boxes setting more specific values for:
+ * box - deepest box at point
+ * html_object_box - html object
+ * html_object_pos_x - html object
+ * html_object_pos_y - html object
+ * object - non html object
+ * iframe - iframe
+ * url - href or imagemap
+ * target - href or imagemap or gadget
+ * url_box - href or imagemap
+ * imagemap - imagemap
+ * gadget - gadget
+ * gadget_box - gadget
+ * gadget_box_x - gadget
+ * gadget_box_y - gadget
+ * title - title
+ * pointer
+ *
+ * drag_candidate - first box with scroll
+ * padding_left - box with scroll
+ * padding_right
+ * padding_top
+ * padding_bottom
+ * scrollbar - inside padding box stops decent
+ * scroll_mouse_x - inside padding box stops decent
+ * scroll_mouse_y - inside padding box stops decent
+ *
+ * text_box - text box
+ * text_box_x - text_box
+ */
+ do {
+ if ((box->style != NULL) &&
+ (css_computed_visibility(box->style) ==
+ CSS_VISIBILITY_HIDDEN)) {
+ continue;
+ }
+
+ if (box->node != NULL) {
+ node = box->node;
+ }
+
+ if (box->object) {
+ if (content_get_type(box->object) == CONTENT_HTML) {
+ html_object_box = box;
+ html_object_pos_x = box_x;
+ html_object_pos_y = box_y;
+ } else {
+ object = box->object;
+ }
+ }
+
+ if (box->iframe) {
+ iframe = box->iframe;
+ }
+
+ if (box->href) {
+ url = box->href;
+ target = box->target;
+ url_box = box;
+ }
+
+ if (box->usemap) {
+ url = imagemap_get(html, box->usemap,
+ box_x, box_y, x, y, &target);
+ if (url) {
+ imagemap = true;
+ url_box = box;
+ }
+ }
+
+ if (box->gadget) {
+ gadget = box->gadget;
+ gadget_box = box;
+ gadget_box_x = box_x;
+ gadget_box_y = box_y;
+ if (gadget->form)
+ target = gadget->form->target;
+ }
+
+ if (box->title) {
+ title = box->title;
+ }
+
+ pointer = get_pointer_shape(box, false);
+
+ if ((box->scroll_x != NULL) ||
+ (box->scroll_y != NULL)) {
+
+ if (drag_candidate == NULL) {
+ drag_candidate = box;
+ }
+
+ padding_left = box_x +
+ scrollbar_get_offset(box->scroll_x);
+ padding_right = padding_left + box->padding[LEFT] +
+ box->width + box->padding[RIGHT];
+ padding_top = box_y +
+ scrollbar_get_offset(box->scroll_y);
+ padding_bottom = padding_top + box->padding[TOP] +
+ box->height + box->padding[BOTTOM];
+
+ if ((x > padding_left) &&
+ (x < padding_right) &&
+ (y > padding_top) &&
+ (y < padding_bottom)) {
+ /* mouse inside padding box */
+
+ if ((box->scroll_y != NULL) &&
+ (x > (padding_right -
+ SCROLLBAR_WIDTH))) {
+ /* mouse above vertical box scroll */
+
+ scrollbar = box->scroll_y;
+ scroll_mouse_x = x - (padding_right -
+ SCROLLBAR_WIDTH);
+ scroll_mouse_y = y - padding_top;
+ break;
+
+ } else if ((box->scroll_x != NULL) &&
+ (y > (padding_bottom -
+ SCROLLBAR_WIDTH))) {
+ /* mouse above horizontal box scroll */
+
+ scrollbar = box->scroll_x;
+ scroll_mouse_x = x - padding_left;
+ scroll_mouse_y = y - (padding_bottom -
+ SCROLLBAR_WIDTH);
+ break;
+ }
+ }
+ }
+
+ if (box->text && !box->object) {
+ text_box = box;
+ text_box_x = box_x;
+ }
+ } while ((box = box_at_point(&html->len_ctx, box, x, y,
+ &box_x, &box_y)) != NULL);
+
+ /* use of box_x, box_y, or content below this point is probably a
+ * mistake; they will refer to the last box returned by box_at_point */
+ assert(node != NULL);
+
+ if (scrollbar) {
+ status = scrollbar_mouse_status_to_message(
+ scrollbar_mouse_action(scrollbar, mouse,
+ scroll_mouse_x,
+ scroll_mouse_y));
+ pointer = BROWSER_POINTER_DEFAULT;
+ } else if (gadget) {
+ textarea_mouse_status ta_status;
+
+ switch (gadget->type) {
+ case GADGET_SELECT:
+ status = messages_get("FormSelect");
+ pointer = BROWSER_POINTER_MENU;
+ if (mouse & BROWSER_MOUSE_CLICK_1 &&
+ nsoption_bool(core_select_menu)) {
+ html->visible_select_menu = gadget;
+ form_open_select_menu(c, gadget,
+ form_select_menu_callback,
+ c);
+ pointer = BROWSER_POINTER_DEFAULT;
+ } else if (mouse & BROWSER_MOUSE_CLICK_1) {
+ msg_data.select_menu.gadget = gadget;
+ content_broadcast(c, CONTENT_MSG_SELECTMENU,
+ &msg_data);
+ }
+ break;
+ case GADGET_CHECKBOX:
+ status = messages_get("FormCheckbox");
+ if (mouse & BROWSER_MOUSE_CLICK_1) {
+ gadget->selected = !gadget->selected;
+ dom_html_input_element_set_checked(
+ (dom_html_input_element *)(gadget->node),
+ gadget->selected);
+ html__redraw_a_box(html, gadget_box);
+ }
+ break;
+ case GADGET_RADIO:
+ status = messages_get("FormRadio");
+ if (mouse & BROWSER_MOUSE_CLICK_1)
+ form_radio_set(gadget);
+ break;
+ case GADGET_IMAGE:
+ /* This falls through to SUBMIT */
+ if (mouse & BROWSER_MOUSE_CLICK_1) {
+ struct image_input_coords *coords, *oldcoords;
+ /** \todo Find a way to not ignore errors */
+ coords = calloc(1, sizeof(*coords));
+ if (coords == NULL) {
+ return;
+ }
+ coords->x = x - gadget_box_x;
+ coords->y = y - gadget_box_y;
+ if (dom_node_set_user_data(
+ gadget->node,
+ corestring_dom___ns_key_image_coords_node_data,
+ coords, html__image_coords_dom_user_data_handler,
+ &oldcoords) != DOM_NO_ERR)
+ return;
+ free(oldcoords);
+ }
+ /* Fall through */
+ case GADGET_SUBMIT:
+ if (gadget->form) {
+ snprintf(status_buffer, sizeof status_buffer,
+ messages_get("FormSubmit"),
+ gadget->form->action);
+ status = status_buffer;
+ pointer = get_pointer_shape(gadget_box, false);
+ if (mouse & (BROWSER_MOUSE_CLICK_1 |
+ BROWSER_MOUSE_CLICK_2))
+ action = ACTION_SUBMIT;
+ } else {
+ status = messages_get("FormBadSubmit");
+ }
+ break;
+ case GADGET_TEXTBOX:
+ case GADGET_PASSWORD:
+ case GADGET_TEXTAREA:
+ if (gadget->type == GADGET_TEXTAREA)
+ status = messages_get("FormTextarea");
+ else
+ status = messages_get("FormTextbox");
+
+ if (click && (html->selection_type !=
+ HTML_SELECTION_TEXTAREA ||
+ html->selection_owner.textarea !=
+ gadget_box)) {
+ sel_owner.none = true;
+ html_set_selection(html, HTML_SELECTION_NONE,
+ sel_owner, true);
+ }
+
+ ta_status = textarea_mouse_action(gadget->data.text.ta,
+ mouse, x - gadget_box_x,
+ y - gadget_box_y);
+
+ if (ta_status & TEXTAREA_MOUSE_EDITOR) {
+ pointer = get_pointer_shape(gadget_box, false);
+ } else {
+ pointer = BROWSER_POINTER_DEFAULT;
+ status = scrollbar_mouse_status_to_message(
+ ta_status >> 3);
+ }
+ break;
+ case GADGET_HIDDEN:
+ /* not possible: no box generated */
+ break;
+ case GADGET_RESET:
+ status = messages_get("FormReset");
+ break;
+ case GADGET_FILE:
+ status = messages_get("FormFile");
+ if (mouse & BROWSER_MOUSE_CLICK_1) {
+ msg_data.gadget_click.gadget = gadget;
+ content_broadcast(c, CONTENT_MSG_GADGETCLICK,
+ &msg_data);
+ }
+ break;
+ case GADGET_BUTTON:
+ /* This gadget cannot be activated */
+ status = messages_get("FormButton");
+ break;
+ }
+
+ } else if (object && (mouse & BROWSER_MOUSE_MOD_2)) {
+
+ if (mouse & BROWSER_MOUSE_DRAG_2) {
+ msg_data.dragsave.type = CONTENT_SAVE_NATIVE;
+ msg_data.dragsave.content = object;
+ content_broadcast(c, CONTENT_MSG_DRAGSAVE, &msg_data);
+
+ } else if (mouse & BROWSER_MOUSE_DRAG_1) {
+ msg_data.dragsave.type = CONTENT_SAVE_ORIG;
+ msg_data.dragsave.content = object;
+ content_broadcast(c, CONTENT_MSG_DRAGSAVE, &msg_data);
+ }
+
+ /* \todo should have a drag-saving object msg */
+
+ } else if (iframe) {
+ int pos_x, pos_y;
+ float scale = browser_window_get_scale(bw);
+
+ browser_window_get_position(iframe, false, &pos_x, &pos_y);
+
+ pos_x /= scale;
+ pos_y /= scale;
+
+ if (mouse & BROWSER_MOUSE_CLICK_1 ||
+ mouse & BROWSER_MOUSE_CLICK_2) {
+ browser_window_mouse_click(iframe, mouse,
+ x - pos_x, y - pos_y);
+ } else {
+ browser_window_mouse_track(iframe, mouse,
+ x - pos_x, y - pos_y);
+ }
+ } else if (html_object_box) {
+
+ if (click && (html->selection_type != HTML_SELECTION_CONTENT ||
+ html->selection_owner.content !=
+ html_object_box)) {
+ sel_owner.none = true;
+ html_set_selection(html, HTML_SELECTION_NONE,
+ sel_owner, true);
+ }
+ if (mouse & BROWSER_MOUSE_CLICK_1 ||
+ mouse & BROWSER_MOUSE_CLICK_2) {
+ content_mouse_action(html_object_box->object,
+ bw, mouse,
+ x - html_object_pos_x,
+ y - html_object_pos_y);
+ } else {
+ content_mouse_track(html_object_box->object,
+ bw, mouse,
+ x - html_object_pos_x,
+ y - html_object_pos_y);
+ }
+ } else if (url) {
+ if (nsoption_bool(display_decoded_idn) == true) {
+ if (nsurl_get_utf8(url, &url_s, &url_l) != NSERROR_OK) {
+ /* Unable to obtain a decoded IDN. This is not a fatal error.
+ * Ensure the string pointer is NULL so we use the encoded version. */
+ url_s = NULL;
+ }
+ }
+
+ if (title) {
+ snprintf(status_buffer, sizeof status_buffer, "%s: %s",
+ url_s ? url_s : nsurl_access(url), title);
+ } else {
+ snprintf(status_buffer, sizeof status_buffer, "%s",
+ url_s ? url_s : nsurl_access(url));
+ }
+
+ status = status_buffer;
+
+ if (url_s != NULL)
+ free(url_s);
+
+ pointer = get_pointer_shape(url_box, imagemap);
+
+ if (mouse & BROWSER_MOUSE_CLICK_1 &&
+ mouse & BROWSER_MOUSE_MOD_1) {
+ /* force download of link */
+ browser_window_navigate(bw,
+ url,
+ content_get_url(c),
+ BW_NAVIGATE_DOWNLOAD,
+ NULL,
+ NULL,
+ NULL);
+
+ } else if (mouse & BROWSER_MOUSE_CLICK_2 &&
+ mouse & BROWSER_MOUSE_MOD_1) {
+ msg_data.savelink.url = url;
+ msg_data.savelink.title = title;
+ content_broadcast(c, CONTENT_MSG_SAVELINK, &msg_data);
+
+ } else if (mouse & (BROWSER_MOUSE_CLICK_1 |
+ BROWSER_MOUSE_CLICK_2))
+ action = ACTION_GO;
+ } else {
+ bool done = false;
+
+ /* frame resizing */
+ if (browser_window_frame_resize_start(bw, mouse, x, y,
+ &pointer)) {
+ if (mouse & (BROWSER_MOUSE_DRAG_1 |
+ BROWSER_MOUSE_DRAG_2)) {
+ status = messages_get("FrameDrag");
+ }
+ done = true;
+ }
+
+ /* if clicking in the main page, remove the selection from any
+ * text areas */
+ if (!done) {
+
+ if (click && html->focus_type != HTML_FOCUS_SELF) {
+ union html_focus_owner fo;
+ fo.self = true;
+ html_set_focus(html, HTML_FOCUS_SELF, fo,
+ true, 0, 0, 0, NULL);
+ }
+ if (click && html->selection_type !=
+ HTML_SELECTION_SELF) {
+ sel_owner.none = true;
+ html_set_selection(html, HTML_SELECTION_NONE,
+ sel_owner, true);
+ }
+
+ if (text_box) {
+ int pixel_offset;
+ size_t idx;
+
+ font_plot_style_from_css(&html->len_ctx,
+ text_box->style, &fstyle);
+
+ guit->layout->position(&fstyle,
+ text_box->text,
+ text_box->length,
+ x - text_box_x,
+ &idx,
+ &pixel_offset);
+
+ if (selection_click(&html->sel, mouse,
+ text_box->byte_offset + idx)) {
+ /* key presses must be directed at the
+ * main browser window, paste text
+ * operations ignored */
+ html_drag_type drag_type;
+ union html_drag_owner drag_owner;
+
+ if (selection_dragging(&html->sel)) {
+ drag_type = HTML_DRAG_SELECTION;
+ drag_owner.no_owner = true;
+ html_set_drag_type(html,
+ drag_type,
+ drag_owner,
+ NULL);
+ status = messages_get(
+ "Selecting");
+ }
+
+ done = true;
+ }
+
+ } else if (mouse & BROWSER_MOUSE_PRESS_1) {
+ sel_owner.none = true;
+ selection_clear(&html->sel, true);
+ }
+
+ if (selection_defined(&html->sel)) {
+ sel_owner.none = false;
+ html_set_selection(html, HTML_SELECTION_SELF,
+ sel_owner, true);
+ } else if (click && html->selection_type !=
+ HTML_SELECTION_NONE) {
+ sel_owner.none = true;
+ html_set_selection(html, HTML_SELECTION_NONE,
+ sel_owner, true);
+ }
+ }
+
+ if (!done) {
+ if (title)
+ status = title;
+
+ if (mouse & BROWSER_MOUSE_DRAG_1) {
+ if (mouse & BROWSER_MOUSE_MOD_2) {
+ msg_data.dragsave.type =
+ CONTENT_SAVE_COMPLETE;
+ msg_data.dragsave.content = NULL;
+ content_broadcast(c,
+ CONTENT_MSG_DRAGSAVE,
+ &msg_data);
+ } else {
+ if (drag_candidate == NULL) {
+ browser_window_page_drag_start(
+ bw, x, y);
+ } else {
+ html_box_drag_start(
+ drag_candidate,
+ x, y);
+ }
+ pointer = BROWSER_POINTER_MOVE;
+ }
+ }
+ else if (mouse & BROWSER_MOUSE_DRAG_2) {
+ if (mouse & BROWSER_MOUSE_MOD_2) {
+ msg_data.dragsave.type =
+ CONTENT_SAVE_SOURCE;
+ msg_data.dragsave.content = NULL;
+ content_broadcast(c,
+ CONTENT_MSG_DRAGSAVE,
+ &msg_data);
+ } else {
+ if (drag_candidate == NULL) {
+ browser_window_page_drag_start(
+ bw, x, y);
+ } else {
+ html_box_drag_start(
+ drag_candidate,
+ x, y);
+ }
+ pointer = BROWSER_POINTER_MOVE;
+ }
+ }
+ }
+ if (mouse && mouse < BROWSER_MOUSE_MOD_1) {
+ /* ensure key presses still act on the browser window */
+ union html_focus_owner fo;
+ fo.self = true;
+ html_set_focus(html, HTML_FOCUS_SELF, fo,
+ true, 0, 0, 0, NULL);
+ }
+ }
+
+ if (!iframe && !html_object_box) {
+ msg_data.explicit_status_text = status;
+ content_broadcast(c, CONTENT_MSG_STATUS, &msg_data);
+
+ msg_data.pointer = pointer;
+ content_broadcast(c, CONTENT_MSG_POINTER, &msg_data);
+ }
+
+ /* fire dom click event */
+ if (mouse & BROWSER_MOUSE_CLICK_1) {
+ fire_dom_event(corestring_dom_click, node, true, true);
+ }
+
+ /* deferred actions that can cause this browser_window to be destroyed
+ * and must therefore be done after set_status/pointer
+ */
+ switch (action) {
+ case ACTION_SUBMIT:
+ form_submit(content_get_url(c),
+ browser_window_find_target(bw, target, mouse),
+ gadget->form, gadget);
+ break;
+ case ACTION_GO:
+ browser_window_navigate(browser_window_find_target(bw, target, mouse),
+ url,
+ content_get_url(c),
+ BW_NAVIGATE_HISTORY,
+ NULL,
+ NULL,
+ NULL);
+ break;
+ case ACTION_NONE:
+ break;
+ }
+}
+
+
+/**
+ * Handle keypresses.
+ *
+ * \param c content of type HTML
+ * \param key The UCS4 character codepoint
+ * \return true if key handled, false otherwise
+ */
+
+bool html_keypress(struct content *c, uint32_t key)
+{
+ html_content *html = (html_content *) c;
+ struct selection *sel = &html->sel;
+ struct box *box;
+
+ switch (html->focus_type) {
+ case HTML_FOCUS_CONTENT:
+ box = html->focus_owner.content;
+ return content_keypress(box->object, key);
+
+ case HTML_FOCUS_TEXTAREA:
+ box = html->focus_owner.textarea;
+ return box_textarea_keypress(html, box, key);
+
+ default:
+ /* Deal with it below */
+ break;
+ }
+
+ switch (key) {
+ case NS_KEY_COPY_SELECTION:
+ selection_copy_to_clipboard(sel);
+ return true;
+
+ case NS_KEY_CLEAR_SELECTION:
+ selection_clear(sel, true);
+ return true;
+
+ case NS_KEY_SELECT_ALL:
+ selection_select_all(sel);
+ return true;
+
+ case NS_KEY_ESCAPE:
+ if (selection_defined(sel)) {
+ selection_clear(sel, true);
+ return true;
+ }
+
+ /* if there's no selection, leave Escape for the caller */
+ return false;
+ }
+
+ return false;
+}
+
+
+/**
+ * Handle search.
+ *
+ * \param c content of type HTML
+ * \param context front end private data
+ * \param flags search flags
+ * \param string search string
+ */
+void html_search(struct content *c, void *context,
+ search_flags_t flags, const char *string)
+{
+ html_content *html = (html_content *)c;
+
+ assert(c != NULL);
+
+ if (string != NULL && html->search_string != NULL &&
+ strcmp(string, html->search_string) == 0 &&
+ html->search != NULL) {
+ /* Continue prev. search */
+ search_step(html->search, flags, string);
+
+ } else if (string != NULL) {
+ /* New search */
+ free(html->search_string);
+ html->search_string = strdup(string);
+ if (html->search_string == NULL)
+ return;
+
+ if (html->search != NULL) {
+ search_destroy_context(html->search);
+ html->search = NULL;
+ }
+
+ html->search = search_create_context(c, CONTENT_HTML, context);
+
+ if (html->search == NULL)
+ return;
+
+ search_step(html->search, flags, string);
+
+ } else {
+ /* Clear search */
+ html_search_clear(c);
+
+ free(html->search_string);
+ html->search_string = NULL;
+ }
+}
+
+
+/**
+ * Terminate a search.
+ *
+ * \param c content of type HTML
+ */
+void html_search_clear(struct content *c)
+{
+ html_content *html = (html_content *)c;
+
+ assert(c != NULL);
+
+ free(html->search_string);
+ html->search_string = NULL;
+
+ if (html->search != NULL) {
+ search_destroy_context(html->search);
+ }
+ html->search = NULL;
+}
+
+
+/**
+ * Callback for in-page scrollbars.
+ */
+void html_overflow_scroll_callback(void *client_data,
+ struct scrollbar_msg_data *scrollbar_data)
+{
+ struct html_scrollbar_data *data = client_data;
+ html_content *html = (html_content *)data->c;
+ struct box *box = data->box;
+ union content_msg_data msg_data;
+ html_drag_type drag_type;
+ union html_drag_owner drag_owner;
+
+ switch(scrollbar_data->msg) {
+ case SCROLLBAR_MSG_MOVED:
+
+ if (html->reflowing == true) {
+ /* Can't redraw during layout, and it will
+ * be redrawn after layout anyway. */
+ break;
+ }
+
+ html__redraw_a_box(html, box);
+ break;
+ case SCROLLBAR_MSG_SCROLL_START:
+ {
+ struct rect rect = {
+ .x0 = scrollbar_data->x0,
+ .y0 = scrollbar_data->y0,
+ .x1 = scrollbar_data->x1,
+ .y1 = scrollbar_data->y1
+ };
+ drag_type = HTML_DRAG_SCROLLBAR;
+ drag_owner.scrollbar = scrollbar_data->scrollbar;
+ html_set_drag_type(html, drag_type, drag_owner, &rect);
+ }
+ break;
+ case SCROLLBAR_MSG_SCROLL_FINISHED:
+ drag_type = HTML_DRAG_NONE;
+ drag_owner.no_owner = true;
+ html_set_drag_type(html, drag_type, drag_owner, NULL);
+
+ msg_data.pointer = BROWSER_POINTER_AUTO;
+ content_broadcast(data->c, CONTENT_MSG_POINTER, &msg_data);
+ break;
+ }
+}
+
+
+/**
+ * End overflow scroll scrollbar drags
+ *
+ * \param scrollbar scrollbar widget
+ * \param mouse state of mouse buttons and modifier keys
+ * \param x coordinate of mouse
+ * \param y coordinate of mouse
+ */
+void html_overflow_scroll_drag_end(struct scrollbar *scrollbar,
+ browser_mouse_state mouse, int x, int y)
+{
+ int scroll_mouse_x, scroll_mouse_y, box_x, box_y;
+ struct html_scrollbar_data *data = scrollbar_get_data(scrollbar);
+ struct box *box;
+
+ box = data->box;
+ box_coords(box, &box_x, &box_y);
+
+ if (scrollbar_is_horizontal(scrollbar)) {
+ scroll_mouse_x = x - box_x;
+ scroll_mouse_y = y - (box_y + box->padding[TOP] +
+ box->height + box->padding[BOTTOM] -
+ SCROLLBAR_WIDTH);
+ scrollbar_mouse_drag_end(scrollbar, mouse,
+ scroll_mouse_x, scroll_mouse_y);
+ } else {
+ scroll_mouse_x = x - (box_x + box->padding[LEFT] +
+ box->width + box->padding[RIGHT] -
+ SCROLLBAR_WIDTH);
+ scroll_mouse_y = y - box_y;
+ scrollbar_mouse_drag_end(scrollbar, mouse,
+ scroll_mouse_x, scroll_mouse_y);
+ }
+}
+
+/* Documented in html_internal.h */
+void html_set_drag_type(html_content *html, html_drag_type drag_type,
+ union html_drag_owner drag_owner, const struct rect *rect)
+{
+ union content_msg_data msg_data;
+
+ assert(html != NULL);
+
+ html->drag_type = drag_type;
+ html->drag_owner = drag_owner;
+
+ switch (drag_type) {
+ case HTML_DRAG_NONE:
+ assert(drag_owner.no_owner == true);
+ msg_data.drag.type = CONTENT_DRAG_NONE;
+ break;
+
+ case HTML_DRAG_SCROLLBAR:
+ case HTML_DRAG_TEXTAREA_SCROLLBAR:
+ case HTML_DRAG_CONTENT_SCROLL:
+ msg_data.drag.type = CONTENT_DRAG_SCROLL;
+ break;
+
+ case HTML_DRAG_SELECTION:
+ assert(drag_owner.no_owner == true);
+ /* Fall through */
+ case HTML_DRAG_TEXTAREA_SELECTION:
+ case HTML_DRAG_CONTENT_SELECTION:
+ msg_data.drag.type = CONTENT_DRAG_SELECTION;
+ break;
+ }
+ msg_data.drag.rect = rect;
+
+ /* Inform of the content's drag status change */
+ content_broadcast((struct content *)html, CONTENT_MSG_DRAG, &msg_data);
+}
+
+/* Documented in html_internal.h */
+void html_set_focus(html_content *html, html_focus_type focus_type,
+ union html_focus_owner focus_owner, bool hide_caret,
+ int x, int y, int height, const struct rect *clip)
+{
+ union content_msg_data msg_data;
+ int x_off = 0;
+ int y_off = 0;
+ struct rect cr;
+ bool textarea_lost_focus = html->focus_type == HTML_FOCUS_TEXTAREA &&
+ focus_type != HTML_FOCUS_TEXTAREA;
+
+ assert(html != NULL);
+
+ switch (focus_type) {
+ case HTML_FOCUS_SELF:
+ assert(focus_owner.self == true);
+ if (html->focus_type == HTML_FOCUS_SELF)
+ /* Don't need to tell anyone anything */
+ return;
+ break;
+
+ case HTML_FOCUS_CONTENT:
+ box_coords(focus_owner.content, &x_off, &y_off);
+ break;
+
+ case HTML_FOCUS_TEXTAREA:
+ box_coords(focus_owner.textarea, &x_off, &y_off);
+ break;
+ }
+
+ html->focus_type = focus_type;
+ html->focus_owner = focus_owner;
+
+ if (textarea_lost_focus) {
+ msg_data.caret.type = CONTENT_CARET_REMOVE;
+ } else if (focus_type != HTML_FOCUS_SELF && hide_caret) {
+ msg_data.caret.type = CONTENT_CARET_HIDE;
+ } else {
+ if (clip != NULL) {
+ cr = *clip;
+ cr.x0 += x_off;
+ cr.y0 += y_off;
+ cr.x1 += x_off;
+ cr.y1 += y_off;
+ }
+
+ msg_data.caret.type = CONTENT_CARET_SET_POS;
+ msg_data.caret.pos.x = x + x_off;
+ msg_data.caret.pos.y = y + y_off;
+ msg_data.caret.pos.height = height;
+ msg_data.caret.pos.clip = (clip == NULL) ? NULL : &cr;
+ }
+
+ /* Inform of the content's drag status change */
+ content_broadcast((struct content *)html, CONTENT_MSG_CARET, &msg_data);
+}
+
+/* Documented in html_internal.h */
+void html_set_selection(html_content *html, html_selection_type selection_type,
+ union html_selection_owner selection_owner, bool read_only)
+{
+ union content_msg_data msg_data;
+ struct box *box;
+ bool changed = false;
+ bool same_type = html->selection_type == selection_type;
+
+ assert(html != NULL);
+
+ if ((selection_type == HTML_SELECTION_NONE &&
+ html->selection_type != HTML_SELECTION_NONE) ||
+ (selection_type != HTML_SELECTION_NONE &&
+ html->selection_type == HTML_SELECTION_NONE))
+ /* Existance of selection has changed, and we'll need to
+ * inform our owner */
+ changed = true;
+
+ /* Clear any existing selection */
+ if (html->selection_type != HTML_SELECTION_NONE) {
+ switch (html->selection_type) {
+ case HTML_SELECTION_SELF:
+ if (same_type)
+ break;
+ selection_clear(&html->sel, true);
+ break;
+ case HTML_SELECTION_TEXTAREA:
+ if (same_type && html->selection_owner.textarea ==
+ selection_owner.textarea)
+ break;
+ box = html->selection_owner.textarea;
+ textarea_clear_selection(box->gadget->data.text.ta);
+ break;
+ case HTML_SELECTION_CONTENT:
+ if (same_type && html->selection_owner.content ==
+ selection_owner.content)
+ break;
+ box = html->selection_owner.content;
+ content_clear_selection(box->object);
+ break;
+ default:
+ break;
+ }
+ }
+
+ html->selection_type = selection_type;
+ html->selection_owner = selection_owner;
+
+ if (!changed)
+ /* Don't need to report lack of change to owner */
+ return;
+
+ /* Prepare msg */
+ switch (selection_type) {
+ case HTML_SELECTION_NONE:
+ assert(selection_owner.none == true);
+ msg_data.selection.selection = false;
+ break;
+ case HTML_SELECTION_SELF:
+ assert(selection_owner.none == false);
+ /* fall through */
+ case HTML_SELECTION_TEXTAREA:
+ case HTML_SELECTION_CONTENT:
+ msg_data.selection.selection = true;
+ break;
+ default:
+ break;
+ }
+ msg_data.selection.read_only = read_only;
+
+ /* Inform of the content's selection status change */
+ content_broadcast((struct content *)html, CONTENT_MSG_SELECTION,
+ &msg_data);
+}
diff --git a/content/handlers/html/html_internal.h b/content/handlers/html/html_internal.h
new file mode 100644
index 0000000..b9eca66
--- /dev/null
+++ b/content/handlers/html/html_internal.h
@@ -0,0 +1,409 @@
+/*
+ * Copyright 2004 James Bursa <bursa(a)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
+ * Private data for text/html content.
+ */
+
+#ifndef NETSURF_HTML_HTML_INTERNAL_H
+#define NETSURF_HTML_HTML_INTERNAL_H
+
+#include <libcss/libcss.h>
+
+#include "content/handlers/css/utils.h"
+#include "content/content_protected.h"
+#include "desktop/selection.h"
+
+#include "html/html.h"
+
+struct gui_layout_table;
+
+typedef enum {
+ HTML_DRAG_NONE, /** No drag */
+ HTML_DRAG_SELECTION, /** Own; Text selection */
+ HTML_DRAG_SCROLLBAR, /** Not own; drag in scrollbar widget */
+ HTML_DRAG_TEXTAREA_SELECTION, /** Not own; drag in textarea widget */
+ HTML_DRAG_TEXTAREA_SCROLLBAR, /** Not own; drag in textarea widget */
+ HTML_DRAG_CONTENT_SELECTION, /** Not own; drag in child content */
+ HTML_DRAG_CONTENT_SCROLL /** Not own; drag in child content */
+} html_drag_type;
+
+union html_drag_owner {
+ bool no_owner;
+ struct box *content;
+ struct scrollbar *scrollbar;
+ struct box *textarea;
+}; /**< For drags we don't own */
+
+typedef enum {
+ HTML_SELECTION_NONE, /** No selection */
+ HTML_SELECTION_TEXTAREA, /** Selection in one of our textareas */
+ HTML_SELECTION_SELF, /** Selection in this html content */
+ HTML_SELECTION_CONTENT /** Selection in child content */
+} html_selection_type;
+union html_selection_owner {
+ bool none;
+ struct box *textarea;
+ struct box *content;
+}; /**< For getting at selections in this content or things in this content */
+
+typedef enum {
+ HTML_FOCUS_SELF, /** Focus is our own */
+ HTML_FOCUS_CONTENT, /** Focus belongs to child content */
+ HTML_FOCUS_TEXTAREA /** Focus belongs to textarea */
+} html_focus_type;
+union html_focus_owner {
+ bool self;
+ struct box *textarea;
+ struct box *content;
+}; /**< For directing input */
+
+/** Data specific to CONTENT_HTML. */
+typedef struct html_content {
+ struct content base;
+
+ dom_hubbub_parser *parser; /**< Parser object handle */
+ bool parse_completed; /**< Whether the parse has been completed */
+
+ /** Document tree */
+ dom_document *document;
+ /** Quirkyness of document */
+ dom_document_quirks_mode quirks;
+
+ /** Encoding of source, NULL if unknown. */
+ char *encoding;
+ /** Source of encoding information. */
+ dom_hubbub_encoding_source encoding_source;
+
+ /** Base URL (may be a copy of content->url). */
+ nsurl *base_url;
+ /** Base target */
+ char *base_target;
+
+ /** CSS length conversion context for document. */
+ nscss_len_ctx len_ctx;
+
+ /** Content has been aborted in the LOADING state */
+ bool aborted;
+
+ /** Whether a meta refresh has been handled */
+ bool refresh;
+
+ /** Whether a layout (reflow) is in progress */
+ bool reflowing;
+
+ /** Whether scripts are enabled for this content */
+ bool enable_scripting;
+
+ /* Title element node */
+ dom_node *title;
+
+ /** A talloc context purely for the render box tree */
+ int *bctx;
+ /** Box tree, or NULL. */
+ struct box *layout;
+ /** Document background colour. */
+ colour background_colour;
+
+ /** Font callback table */
+ const struct gui_layout_table *font_func;
+
+ /** Number of entries in scripts */
+ unsigned int scripts_count;
+ /** Scripts */
+ struct html_script *scripts;
+ /** javascript context */
+ struct jscontext *jscontext;
+
+ /** Number of entries in stylesheet_content. */
+ unsigned int stylesheet_count;
+ /** Stylesheets. Each may be NULL. */
+ struct html_stylesheet *stylesheets;
+ /**< Style selection context */
+ css_select_ctx *select_ctx;
+ /**< Universal selector */
+ lwc_string *universal;
+
+ /** Number of entries in object_list. */
+ unsigned int num_objects;
+ /** List of objects. */
+ struct content_html_object *object_list;
+ /** Forms, in reverse order to document. */
+ struct form *forms;
+ /** Hash table of imagemaps. */
+ struct imagemap **imagemaps;
+
+ /** Browser window containing this document, or NULL if not open. */
+ struct browser_window *bw;
+
+ /** Frameset information */
+ struct content_html_frames *frameset;
+
+ /** Inline frame information */
+ struct content_html_iframe *iframe;
+
+ /** Content of type CONTENT_HTML containing this, or NULL if not an
+ * object within a page. */
+ struct html_content *page;
+
+ /** Current drag type */
+ html_drag_type drag_type;
+ /** Widget capturing all mouse events */
+ union html_drag_owner drag_owner;
+
+ /** Current selection state */
+ html_selection_type selection_type;
+ /** Current selection owner */
+ union html_selection_owner selection_owner;
+
+ /** Current input focus target type */
+ html_focus_type focus_type;
+ /** Current input focus target */
+ union html_focus_owner focus_owner;
+
+ /** HTML content's own text selection object */
+ struct selection sel;
+
+ /** Open core-handled form SELECT menu,
+ * or NULL if none currently open. */
+ struct form_control *visible_select_menu;
+
+ /** Context for free text search, or NULL if none */
+ struct search_context *search;
+ /** Search string or NULL */
+ char *search_string;
+
+} html_content;
+
+/** Render padding and margin box outlines in html_redraw(). */
+extern bool html_redraw_debug;
+
+void html__redraw_a_box(html_content *html, struct box *box);
+
+/**
+ * Set our drag status, and inform whatever owns the content
+ *
+ * \param html HTML content
+ * \param drag_type Type of drag
+ * \param drag_owner What owns the drag
+ * \param rect Pointer movement bounds
+ */
+void html_set_drag_type(html_content *html, html_drag_type drag_type,
+ union html_drag_owner drag_owner, const struct rect *rect);
+
+/**
+ * Set our selection status, and inform whatever owns the content
+ *
+ * \param html HTML content
+ * \param selection_type Type of selection
+ * \param selection_owner What owns the selection
+ * \param read_only True iff selection is read only
+ */
+void html_set_selection(html_content *html, html_selection_type selection_type,
+ union html_selection_owner selection_owner, bool read_only);
+
+/**
+ * Set our input focus, and inform whatever owns the content
+ *
+ * \param html HTML content
+ * \param focus_type Type of input focus
+ * \param focus_owner What owns the focus
+ * \param hide_caret True iff caret to be hidden
+ * \param x Carret x-coord rel to owner
+ * \param y Carret y-coord rel to owner
+ * \param height Carret height
+ * \param clip Carret clip rect
+ */
+void html_set_focus(html_content *html, html_focus_type focus_type,
+ union html_focus_owner focus_owner, bool hide_caret,
+ int x, int y, int height, const struct rect *clip);
+
+
+struct browser_window *html_get_browser_window(struct content *c);
+
+/**
+ * Complete conversion of an HTML document
+ *
+ * \param htmlc Content to convert
+ */
+void html_finish_conversion(html_content *htmlc);
+
+/**
+ * Test if an HTML content conversion can begin
+ *
+ * \param htmlc html content to test
+ * \return true iff the html content conversion can begin
+ */
+bool html_can_begin_conversion(html_content *htmlc);
+
+/**
+ * Begin conversion of an HTML document
+ *
+ * \param htmlc Content to convert
+ */
+bool html_begin_conversion(html_content *htmlc);
+
+/* in html/html_redraw.c */
+bool html_redraw(struct content *c, struct content_redraw_data *data,
+ const struct rect *clip, const struct redraw_context *ctx);
+
+/* in html/html_redraw_border.c */
+bool html_redraw_borders(struct box *box, int x_parent, int y_parent,
+ int p_width, int p_height, const struct rect *clip, float scale,
+ const struct redraw_context *ctx);
+
+bool html_redraw_inline_borders(struct box *box, struct rect b,
+ const struct rect *clip, float scale, bool first, bool last,
+ const struct redraw_context *ctx);
+
+/* in html/html_interaction.c */
+void html_mouse_track(struct content *c, struct browser_window *bw,
+ browser_mouse_state mouse, int x, int y);
+void html_mouse_action(struct content *c, struct browser_window *bw,
+ browser_mouse_state mouse, int x, int y);
+bool html_keypress(struct content *c, uint32_t key);
+void html_overflow_scroll_callback(void *client_data,
+ struct scrollbar_msg_data *scrollbar_data);
+void html_search(struct content *c, void *context,
+ search_flags_t flags, const char *string);
+void html_search_clear(struct content *c);
+
+
+/* in html/html_script.c */
+dom_hubbub_error html_process_script(void *ctx, dom_node *node);
+
+/**
+ * Attempt script execution for defer and async scripts
+ *
+ * execute scripts using algorithm found in:
+ * http://www.whatwg.org/specs/web-apps/current-work/multipage/scripting-1.h...
+ *
+ * \param htmlc html content.
+ * \return NSERROR_OK error code.
+ */
+nserror html_script_exec(html_content *htmlc);
+
+/**
+ * Free all script resources and references for a html content.
+ *
+ * \param htmlc html content.
+ * \return NSERROR_OK or error code.
+ */
+nserror html_script_free(html_content *htmlc);
+
+/**
+ * Ensure the html content javascript context is invalidated.
+ *
+ * \param htmlc html content.
+ * \return NSERROR_OK or error code.
+ */
+nserror html_script_invalidate_ctx(html_content *htmlc);
+
+/* in html/html_forms.c */
+struct form *html_forms_get_forms(const char *docenc, dom_html_document *doc);
+struct form_control *html_forms_get_control_for_node(struct form *forms,
+ dom_node *node);
+
+/* in html/html_css.c */
+nserror html_css_init(void);
+void html_css_fini(void);
+
+/**
+ * Initialise core stylesheets for a content
+ *
+ * \param c content structure to update
+ * \return nserror
+ */
+nserror html_css_new_stylesheets(html_content *c);
+nserror html_css_quirks_stylesheets(html_content *c);
+nserror html_css_free_stylesheets(html_content *html);
+
+bool html_css_process_link(html_content *htmlc, dom_node *node);
+bool html_css_process_style(html_content *htmlc, dom_node *node);
+bool html_css_update_style(html_content *c, dom_node *style);
+
+nserror html_css_new_selection_context(html_content *c,
+ css_select_ctx **ret_select_ctx);
+
+/* in html/html_css_fetcher.c */
+/**
+ * Register the fetcher for the pseudo x-ns-css scheme.
+ *
+ * \return NSERROR_OK on successful registration or error code on failure.
+ */
+nserror html_css_fetcher_register(void);
+nserror html_css_fetcher_add_item(dom_string *data, nsurl *base_url,
+ uint32_t *key);
+
+/* in html/html_object.c */
+
+/**
+ * Start a fetch for an object required by a page.
+ *
+ * \param c content of type CONTENT_HTML
+ * \param url URL of object to fetch (copied)
+ * \param box box that will contain the object
+ * \param permitted_types bitmap of acceptable types
+ * \param available_width estimate of width of object
+ * \param available_height estimate of height of object
+ * \param background this is a background image
+ * \return true on success, false on memory exhaustion
+ */
+bool html_fetch_object(html_content *c, nsurl *url, struct box *box,
+ content_type permitted_types,
+ int available_width, int available_height,
+ bool background);
+
+nserror html_object_free_objects(html_content *html);
+nserror html_object_close_objects(html_content *html);
+nserror html_object_open_objects(html_content *html, struct browser_window *bw);
+nserror html_object_abort_objects(html_content *html);
+
+/* Events */
+/**
+ * Construct an event and fire it at the DOM
+ *
+ */
+bool fire_dom_event(dom_string *type, dom_node *target,
+ bool bubbles, bool cancelable);
+
+/* Useful dom_string pointers */
+struct dom_string;
+
+extern struct dom_string *html_dom_string_map;
+extern struct dom_string *html_dom_string_id;
+extern struct dom_string *html_dom_string_name;
+extern struct dom_string *html_dom_string_area;
+extern struct dom_string *html_dom_string_a;
+extern struct dom_string *html_dom_string_nohref;
+extern struct dom_string *html_dom_string_href;
+extern struct dom_string *html_dom_string_target;
+extern struct dom_string *html_dom_string_shape;
+extern struct dom_string *html_dom_string_default;
+extern struct dom_string *html_dom_string_rect;
+extern struct dom_string *html_dom_string_rectangle;
+extern struct dom_string *html_dom_string_coords;
+extern struct dom_string *html_dom_string_circle;
+extern struct dom_string *html_dom_string_poly;
+extern struct dom_string *html_dom_string_polygon;
+extern struct dom_string *html_dom_string_text_javascript;
+extern struct dom_string *html_dom_string_type;
+extern struct dom_string *html_dom_string_src;
+
+#endif
diff --git a/content/handlers/html/html_object.c b/content/handlers/html/html_object.c
new file mode 100644
index 0000000..c8715e3
--- /dev/null
+++ b/content/handlers/html/html_object.c
@@ -0,0 +1,730 @@
+/*
+ * Copyright 2013 Vincent Sanders <vince(a)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/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.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.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.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;
+}
diff --git a/content/handlers/html/html_redraw.c b/content/handlers/html/html_redraw.c
new file mode 100644
index 0000000..d05df87
--- /dev/null
+++ b/content/handlers/html/html_redraw.c
@@ -0,0 +1,1951 @@
+/*
+ * Copyright 2004-2008 James Bursa <bursa(a)users.sourceforge.net>
+ * Copyright 2004-2007 John M Bell <jmb202(a)ecs.soton.ac.uk>
+ * Copyright 2004-2007 Richard Wilson <info(a)tinct.net>
+ * Copyright 2005-2006 Adrian Lees <adrianl(a)users.sourceforge.net>
+ * Copyright 2006 Rob Kendrick <rjek(a)netsurf-browser.org>
+ * Copyright 2008 Michael Drake <tlsa(a)netsurf-browser.org>
+ * Copyright 2009 Paul Blokus <paul_pl(a)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
+ *
+ * Redrawing CONTENT_HTML implementation.
+ */
+
+#include "utils/config.h"
+#include <assert.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <dom/dom.h>
+
+#include "utils/log.h"
+#include "utils/messages.h"
+#include "utils/utils.h"
+#include "utils/nsoption.h"
+#include "netsurf/content.h"
+#include "netsurf/browser_window.h"
+#include "netsurf/plotters.h"
+#include "netsurf/bitmap.h"
+#include "netsurf/layout.h"
+#include "content/content_protected.h"
+#include "css/utils.h"
+#include "desktop/selection.h"
+#include "desktop/print.h"
+#include "desktop/scrollbar.h"
+#include "desktop/textarea.h"
+#include "desktop/gui_internal.h"
+
+#include "html/box.h"
+#include "html/font.h"
+#include "html/form_internal.h"
+#include "html/html_internal.h"
+#include "html/layout.h"
+#include "html/search.h"
+
+
+bool html_redraw_debug = false;
+
+/**
+ * Determine if a box has a background that needs drawing
+ *
+ * \param box Box to consider
+ * \return True if box has a background, false otherwise.
+ */
+static bool html_redraw_box_has_background(struct box *box)
+{
+ if (box->background != NULL)
+ return true;
+
+ if (box->style != NULL) {
+ css_color colour;
+
+ css_computed_background_color(box->style, &colour);
+
+ if (nscss_color_is_transparent(colour) == false)
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * Find the background box for a box
+ *
+ * \param box Box to find background box for
+ * \return Pointer to background box, or NULL if there is none
+ */
+static struct box *html_redraw_find_bg_box(struct box *box)
+{
+ /* Thanks to backwards compatibility, CSS defines the following:
+ *
+ * + If the box is for the root element and it has a background,
+ * use that (and then process the body box with no special case)
+ * + If the box is for the root element and it has no background,
+ * then use the background (if any) from the body element as if
+ * it were specified on the root. Then, when the box for the body
+ * element is processed, ignore the background.
+ * + For any other box, just use its own styling.
+ */
+ if (box->parent == NULL) {
+ /* Root box */
+ if (html_redraw_box_has_background(box))
+ return box;
+
+ /* No background on root box: consider body box, if any */
+ if (box->children != NULL) {
+ if (html_redraw_box_has_background(box->children))
+ return box->children;
+ }
+ } else if (box->parent != NULL && box->parent->parent == NULL) {
+ /* Body box: only render background if root has its own */
+ if (html_redraw_box_has_background(box) &&
+ html_redraw_box_has_background(box->parent))
+ return box;
+ } else {
+ /* Any other box */
+ if (html_redraw_box_has_background(box))
+ return box;
+ }
+
+ return NULL;
+}
+
+/**
+ * Redraw a short text string, complete with highlighting
+ * (for selection/search)
+ *
+ * \param utf8_text pointer to UTF-8 text string
+ * \param utf8_len length of string, in bytes
+ * \param offset byte offset within textual representation
+ * \param space width of space that follows string (0 = no space)
+ * \param fstyle text style to use (pass text size unscaled)
+ * \param x x ordinate at which to plot text
+ * \param y y ordinate at which to plot text
+ * \param clip pointer to current clip rectangle
+ * \param height height of text string
+ * \param scale current display scale (1.0 = 100%)
+ * \param excluded exclude this text string from the selection
+ * \param c Content being redrawn.
+ * \param sel Selection context
+ * \param search Search context
+ * \param ctx current redraw context
+ * \return true iff successful and redraw should proceed
+ */
+
+static bool
+text_redraw(const char *utf8_text,
+ size_t utf8_len,
+ size_t offset,
+ int space,
+ const plot_font_style_t *fstyle,
+ int x,
+ int y,
+ const struct rect *clip,
+ int height,
+ float scale,
+ bool excluded,
+ struct content *c,
+ const struct selection *sel,
+ struct search_context *search,
+ const struct redraw_context *ctx)
+{
+ bool highlighted = false;
+ plot_font_style_t plot_fstyle = *fstyle;
+ nserror res;
+
+ /* Need scaled text size to pass to plotters */
+ plot_fstyle.size *= scale;
+
+ /* is this box part of a selection? */
+ if (!excluded && ctx->interactive == true) {
+ unsigned len = utf8_len + (space ? 1 : 0);
+ unsigned start_idx;
+ unsigned end_idx;
+
+ /* first try the browser window's current selection */
+ if (selection_defined(sel) && selection_highlighted(sel,
+ offset, offset + len,
+ &start_idx, &end_idx)) {
+ highlighted = true;
+ }
+
+ /* what about the current search operation, if any? */
+ if (!highlighted && (search != NULL) &&
+ search_term_highlighted(c,
+ offset, offset + len,
+ &start_idx, &end_idx,
+ search)) {
+ highlighted = true;
+ }
+
+ /* \todo make search terms visible within selected text */
+ if (highlighted) {
+ struct rect r;
+ unsigned endtxt_idx = end_idx;
+ bool clip_changed = false;
+ bool text_visible = true;
+ int startx, endx;
+ plot_style_t pstyle_fill_hback = *plot_style_fill_white;
+ plot_font_style_t fstyle_hback = plot_fstyle;
+
+ if (end_idx > utf8_len) {
+ /* adjust for trailing space, not present in
+ * utf8_text */
+ assert(end_idx == utf8_len + 1);
+ endtxt_idx = utf8_len;
+ }
+
+ res = guit->layout->width(fstyle,
+ utf8_text, start_idx,
+ &startx);
+ if (res != NSERROR_OK) {
+ startx = 0;
+ }
+
+ res = guit->layout->width(fstyle,
+ utf8_text, endtxt_idx,
+ &endx);
+ if (res != NSERROR_OK) {
+ endx = 0;
+ }
+
+ /* is there a trailing space that should be highlighted
+ * as well? */
+ if (end_idx > utf8_len) {
+ endx += space;
+ }
+
+ if (scale != 1.0) {
+ startx *= scale;
+ endx *= scale;
+ }
+
+ /* draw any text preceding highlighted portion */
+ if ((start_idx > 0) &&
+ (ctx->plot->text(ctx,
+ &plot_fstyle,
+ x,
+ y + (int)(height * 0.75 * scale),
+ utf8_text,
+ start_idx) != NSERROR_OK))
+ return false;
+
+ pstyle_fill_hback.fill_colour = fstyle->foreground;
+
+ /* highlighted portion */
+ r.x0 = x + startx;
+ r.y0 = y;
+ r.x1 = x + endx;
+ r.y1 = y + height * scale;
+ res = ctx->plot->rectangle(ctx, &pstyle_fill_hback, &r);
+ if (res != NSERROR_OK) {
+ return false;
+ }
+
+ if (start_idx > 0) {
+ int px0 = max(x + startx, clip->x0);
+ int px1 = min(x + endx, clip->x1);
+
+ if (px0 < px1) {
+ r.x0 = px0;
+ r.y0 = clip->y0;
+ r.x1 = px1;
+ r.y1 = clip->y1;
+ res = ctx->plot->clip(ctx, &r);
+ if (res != NSERROR_OK) {
+ return false;
+ }
+
+ clip_changed = true;
+ } else {
+ text_visible = false;
+ }
+ }
+
+ fstyle_hback.background =
+ pstyle_fill_hback.fill_colour;
+ fstyle_hback.foreground = colour_to_bw_furthest(
+ pstyle_fill_hback.fill_colour);
+
+ if (text_visible &&
+ (ctx->plot->text(ctx,
+ &fstyle_hback,
+ x,
+ y + (int)(height * 0.75 * scale),
+ utf8_text,
+ endtxt_idx) != NSERROR_OK)) {
+ return false;
+ }
+
+ /* draw any text succeeding highlighted portion */
+ if (endtxt_idx < utf8_len) {
+ int px0 = max(x + endx, clip->x0);
+ if (px0 < clip->x1) {
+
+ r.x0 = px0;
+ r.y0 = clip->y0;
+ r.x1 = clip->x1;
+ r.y1 = clip->y1;
+ res = ctx->plot->clip(ctx, &r);
+ if (res != NSERROR_OK) {
+ return false;
+ }
+
+ clip_changed = true;
+
+ res = ctx->plot->text(ctx,
+ &plot_fstyle,
+ x,
+ y + (int)(height * 0.75 * scale),
+ utf8_text,
+ utf8_len);
+ if (res != NSERROR_OK) {
+ return false;
+ }
+ }
+ }
+
+ if (clip_changed &&
+ (ctx->plot->clip(ctx, clip) != NSERROR_OK)) {
+ return false;
+ }
+ }
+ }
+
+ if (!highlighted) {
+ res = ctx->plot->text(ctx,
+ &plot_fstyle,
+ x,
+ y + (int) (height * 0.75 * scale),
+ utf8_text,
+ utf8_len);
+ if (res != NSERROR_OK) {
+ return false;
+ }
+ }
+ return true;
+}
+
+
+/**
+ * Plot a checkbox.
+ *
+ * \param x left coordinate
+ * \param y top coordinate
+ * \param width dimensions of checkbox
+ * \param height dimensions of checkbox
+ * \param selected the checkbox is selected
+ * \param ctx current redraw context
+ * \return true if successful, false otherwise
+ */
+
+static bool html_redraw_checkbox(int x, int y, int width, int height,
+ bool selected, const struct redraw_context *ctx)
+{
+ double z;
+ nserror res;
+ struct rect rect;
+
+ z = width * 0.15;
+ if (z == 0) {
+ z = 1;
+ }
+
+ rect.x0 = x;
+ rect.y0 = y ;
+ rect.x1 = x + width;
+ rect.y1 = y + height;
+ res = ctx->plot->rectangle(ctx, plot_style_fill_wbasec, &rect);
+ if (res != NSERROR_OK) {
+ return false;
+ }
+
+ /* dark line across top */
+ rect.y1 = y;
+ res = ctx->plot->line(ctx, plot_style_stroke_darkwbasec, &rect);
+ if (res != NSERROR_OK) {
+ return false;
+ }
+
+ /* dark line across left */
+ rect.x1 = x;
+ rect.y1 = y + height;
+ res = ctx->plot->line(ctx, plot_style_stroke_darkwbasec, &rect);
+ if (res != NSERROR_OK) {
+ return false;
+ }
+
+ /* light line across right */
+ rect.x0 = x + width;
+ rect.x1 = x + width;
+ res = ctx->plot->line(ctx, plot_style_stroke_lightwbasec, &rect);
+ if (res != NSERROR_OK) {
+ return false;
+ }
+
+ /* light line across bottom */
+ rect.x0 = x;
+ rect.y0 = y + height;
+ res = ctx->plot->line(ctx, plot_style_stroke_lightwbasec, &rect);
+ if (res != NSERROR_OK) {
+ return false;
+ }
+
+ if (selected) {
+ if (width < 12 || height < 12) {
+ /* render a solid box instead of a tick */
+ rect.x0 = x + z + z;
+ rect.y0 = y + z + z;
+ rect.x1 = x + width - z;
+ rect.y1 = y + height - z;
+ res = ctx->plot->rectangle(ctx, plot_style_fill_wblobc, &rect);
+ if (res != NSERROR_OK) {
+ return false;
+ }
+ } else {
+ /* render a tick, as it'll fit comfortably */
+ rect.x0 = x + width - z;
+ rect.y0 = y + z;
+ rect.x1 = x + (z * 3);
+ rect.y1 = y + height - z;
+ res = ctx->plot->line(ctx, plot_style_stroke_wblobc, &rect);
+ if (res != NSERROR_OK) {
+ return false;
+ }
+
+ rect.x0 = x + (z * 3);
+ rect.y0 = y + height - z;
+ rect.x1 = x + z + z;
+ rect.y1 = y + (height / 2);
+ res = ctx->plot->line(ctx, plot_style_stroke_wblobc, &rect);
+ if (res != NSERROR_OK) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+
+/**
+ * Plot a radio icon.
+ *
+ * \param x left coordinate
+ * \param y top coordinate
+ * \param width dimensions of radio icon
+ * \param height dimensions of radio icon
+ * \param selected the radio icon is selected
+ * \param ctx current redraw context
+ * \return true if successful, false otherwise
+ */
+static bool html_redraw_radio(int x, int y, int width, int height,
+ bool selected, const struct redraw_context *ctx)
+{
+ nserror res;
+
+ /* plot background of radio button */
+ res = ctx->plot->disc(ctx,
+ plot_style_fill_wbasec,
+ x + width * 0.5,
+ y + height * 0.5,
+ width * 0.5 - 1);
+ if (res != NSERROR_OK) {
+ return false;
+ }
+
+ /* plot dark arc */
+ res = ctx->plot->arc(ctx,
+ plot_style_fill_darkwbasec,
+ x + width * 0.5,
+ y + height * 0.5,
+ width * 0.5 - 1,
+ 45,
+ 225);
+ if (res != NSERROR_OK) {
+ return false;
+ }
+
+ /* plot light arc */
+ res = ctx->plot->arc(ctx,
+ plot_style_fill_lightwbasec,
+ x + width * 0.5,
+ y + height * 0.5,
+ width * 0.5 - 1,
+ 225,
+ 45);
+ if (res != NSERROR_OK) {
+ return false;
+ }
+
+ if (selected) {
+ /* plot selection blob */
+ res = ctx->plot->disc(ctx,
+ plot_style_fill_wblobc,
+ x + width * 0.5,
+ y + height * 0.5,
+ width * 0.3 - 1);
+ if (res != NSERROR_OK) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+/**
+ * Plot a file upload input.
+ *
+ * \param x left coordinate
+ * \param y top coordinate
+ * \param width dimensions of input
+ * \param height dimensions of input
+ * \param box box of input
+ * \param scale scale for redraw
+ * \param background_colour current background colour
+ * \param len_ctx Length conversion context
+ * \param ctx current redraw context
+ * \return true if successful, false otherwise
+ */
+
+static bool html_redraw_file(int x, int y, int width, int height,
+ struct box *box, float scale, colour background_colour,
+ const nscss_len_ctx *len_ctx,
+ const struct redraw_context *ctx)
+{
+ int text_width;
+ const char *text;
+ size_t length;
+ plot_font_style_t fstyle;
+ nserror res;
+
+ font_plot_style_from_css(len_ctx, box->style, &fstyle);
+ fstyle.background = background_colour;
+
+ if (box->gadget->value) {
+ text = box->gadget->value;
+ } else {
+ text = messages_get("Form_Drop");
+ }
+ length = strlen(text);
+
+ res = guit->layout->width(&fstyle, text, length, &text_width);
+ if (res != NSERROR_OK) {
+ return false;
+ }
+ text_width *= scale;
+ if (width < text_width + 8) {
+ x = x + width - text_width - 4;
+ } else {
+ x = x + 4;
+ }
+
+ res = ctx->plot->text(ctx, &fstyle, x, y + height * 0.75, text, length);
+ if (res != NSERROR_OK) {
+ return false;
+ }
+ return true;
+}
+
+
+/**
+ * Plot background images.
+ *
+ * The reason for the presence of \a background is the backwards compatibility
+ * mess that is backgrounds on <body>. The background will be drawn relative
+ * to \a box, using the background information contained within \a background.
+ *
+ * \param x coordinate of box
+ * \param y coordinate of box
+ * \param box box to draw background image of
+ * \param scale scale for redraw
+ * \param clip current clip rectangle
+ * \param background_colour current background colour
+ * \param background box containing background details (usually \a box)
+ * \param len_ctx Length conversion context
+ * \param ctx current redraw context
+ * \return true if successful, false otherwise
+ */
+
+static bool html_redraw_background(int x, int y, struct box *box, float scale,
+ const struct rect *clip, colour *background_colour,
+ struct box *background,
+ const nscss_len_ctx *len_ctx,
+ const struct redraw_context *ctx)
+{
+ bool repeat_x = false;
+ bool repeat_y = false;
+ bool plot_colour = true;
+ bool plot_content;
+ bool clip_to_children = false;
+ struct box *clip_box = box;
+ int ox = x, oy = y;
+ int width, height;
+ css_fixed hpos = 0, vpos = 0;
+ css_unit hunit = CSS_UNIT_PX, vunit = CSS_UNIT_PX;
+ struct box *parent;
+ struct rect r = *clip;
+ css_color bgcol;
+ plot_style_t pstyle_fill_bg = {
+ .fill_type = PLOT_OP_TYPE_SOLID,
+ .fill_colour = *background_colour,
+ };
+ nserror res;
+
+ if (ctx->background_images == false)
+ return true;
+
+ plot_content = (background->background != NULL);
+
+ if (plot_content) {
+ if (!box->parent) {
+ /* Root element, special case:
+ * background origin calc. is based on margin box */
+ x -= box->margin[LEFT] * scale;
+ y -= box->margin[TOP] * scale;
+ width = box->margin[LEFT] + box->padding[LEFT] +
+ box->width + box->padding[RIGHT] +
+ box->margin[RIGHT];
+ height = box->margin[TOP] + box->padding[TOP] +
+ box->height + box->padding[BOTTOM] +
+ box->margin[BOTTOM];
+ } else {
+ width = box->padding[LEFT] + box->width +
+ box->padding[RIGHT];
+ height = box->padding[TOP] + box->height +
+ box->padding[BOTTOM];
+ }
+ /* handle background-repeat */
+ switch (css_computed_background_repeat(background->style)) {
+ case CSS_BACKGROUND_REPEAT_REPEAT:
+ repeat_x = repeat_y = true;
+ /* optimisation: only plot the colour if
+ * bitmap is not opaque */
+ plot_colour = !content_get_opaque(background->background);
+ break;
+
+ case CSS_BACKGROUND_REPEAT_REPEAT_X:
+ repeat_x = true;
+ break;
+
+ case CSS_BACKGROUND_REPEAT_REPEAT_Y:
+ repeat_y = true;
+ break;
+
+ case CSS_BACKGROUND_REPEAT_NO_REPEAT:
+ break;
+
+ default:
+ break;
+ }
+
+ /* handle background-position */
+ css_computed_background_position(background->style,
+ &hpos, &hunit, &vpos, &vunit);
+ if (hunit == CSS_UNIT_PCT) {
+ x += (width -
+ content_get_width(background->background)) *
+ scale * FIXTOFLT(hpos) / 100.;
+ } else {
+ x += (int) (FIXTOFLT(nscss_len2px(len_ctx, hpos, hunit,
+ background->style)) * scale);
+ }
+
+ if (vunit == CSS_UNIT_PCT) {
+ y += (height -
+ content_get_height(background->background)) *
+ scale * FIXTOFLT(vpos) / 100.;
+ } else {
+ y += (int) (FIXTOFLT(nscss_len2px(len_ctx, vpos, vunit,
+ background->style)) * scale);
+ }
+ }
+
+ /* special case for table rows as their background needs
+ * to be clipped to all the cells */
+ if (box->type == BOX_TABLE_ROW) {
+ css_fixed h = 0, v = 0;
+ css_unit hu = CSS_UNIT_PX, vu = CSS_UNIT_PX;
+
+ for (parent = box->parent;
+ ((parent) && (parent->type != BOX_TABLE));
+ parent = parent->parent);
+ assert(parent && (parent->style));
+
+ css_computed_border_spacing(parent->style, &h, &hu, &v, &vu);
+
+ clip_to_children = (h > 0) || (v > 0);
+
+ if (clip_to_children)
+ clip_box = box->children;
+ }
+
+ for (; clip_box; clip_box = clip_box->next) {
+ /* clip to child boxes if needed */
+ if (clip_to_children) {
+ assert(clip_box->type == BOX_TABLE_CELL);
+
+ /* update clip.* to the child cell */
+ r.x0 = ox + (clip_box->x * scale);
+ r.y0 = oy + (clip_box->y * scale);
+ r.x1 = r.x0 + (clip_box->padding[LEFT] +
+ clip_box->width +
+ clip_box->padding[RIGHT]) * scale;
+ r.y1 = r.y0 + (clip_box->padding[TOP] +
+ clip_box->height +
+ clip_box->padding[BOTTOM]) * scale;
+
+ if (r.x0 < clip->x0) r.x0 = clip->x0;
+ if (r.y0 < clip->y0) r.y0 = clip->y0;
+ if (r.x1 > clip->x1) r.x1 = clip->x1;
+ if (r.y1 > clip->y1) r.y1 = clip->y1;
+
+ css_computed_background_color(clip_box->style, &bgcol);
+
+ /* <td> attributes override <tr> */
+ /* if the background content is opaque there
+ * is no need to plot underneath it.
+ */
+ if ((r.x0 >= r.x1) ||
+ (r.y0 >= r.y1) ||
+ (nscss_color_is_transparent(bgcol) == false) ||
+ ((clip_box->background != NULL) &&
+ content_get_opaque(clip_box->background)))
+ continue;
+ }
+
+ /* plot the background colour */
+ css_computed_background_color(background->style, &bgcol);
+
+ if (nscss_color_is_transparent(bgcol) == false) {
+ *background_colour = nscss_color_to_ns(bgcol);
+ pstyle_fill_bg.fill_colour = *background_colour;
+ if (plot_colour) {
+ res = ctx->plot->rectangle(ctx, &pstyle_fill_bg, &r);
+ if (res != NSERROR_OK) {
+ return false;
+ }
+ }
+ }
+ /* and plot the image */
+ if (plot_content) {
+ width = content_get_width(background->background);
+ height = content_get_height(background->background);
+
+ /* ensure clip area only as large as required */
+ if (!repeat_x) {
+ if (r.x0 < x)
+ r.x0 = x;
+ if (r.x1 > x + width * scale)
+ r.x1 = x + width * scale;
+ }
+ if (!repeat_y) {
+ if (r.y0 < y)
+ r.y0 = y;
+ if (r.y1 > y + height * scale)
+ r.y1 = y + height * scale;
+ }
+ /* valid clipping rectangles only */
+ if ((r.x0 < r.x1) && (r.y0 < r.y1)) {
+ struct content_redraw_data bg_data;
+
+ res = ctx->plot->clip(ctx, &r);
+ if (res != NSERROR_OK) {
+ return false;
+ }
+
+ bg_data.x = x;
+ bg_data.y = y;
+ bg_data.width = ceilf(width * scale);
+ bg_data.height = ceilf(height * scale);
+ bg_data.background_colour = *background_colour;
+ bg_data.scale = scale;
+ bg_data.repeat_x = repeat_x;
+ bg_data.repeat_y = repeat_y;
+
+ /* We just continue if redraw fails */
+ content_redraw(background->background,
+ &bg_data, &r, ctx);
+ }
+ }
+
+ /* only <tr> rows being clipped to child boxes loop */
+ if (!clip_to_children)
+ return true;
+ }
+ return true;
+}
+
+
+/**
+ * Plot an inline's background and/or background image.
+ *
+ * \param x coordinate of box
+ * \param y coordinate of box
+ * \param box BOX_INLINE which created the background
+ * \param scale scale for redraw
+ * \param clip coordinates of clip rectangle
+ * \param b coordinates of border edge rectangle
+ * \param first true if this is the first rectangle associated with the inline
+ * \param last true if this is the last rectangle associated with the inline
+ * \param background_colour updated to current background colour if plotted
+ * \param len_ctx Length conversion context
+ * \param ctx current redraw context
+ * \return true if successful, false otherwise
+ */
+
+static bool html_redraw_inline_background(int x, int y, struct box *box,
+ float scale, const struct rect *clip, struct rect b,
+ bool first, bool last, colour *background_colour,
+ const nscss_len_ctx *len_ctx,
+ const struct redraw_context *ctx)
+{
+ struct rect r = *clip;
+ bool repeat_x = false;
+ bool repeat_y = false;
+ bool plot_colour = true;
+ bool plot_content;
+ css_fixed hpos = 0, vpos = 0;
+ css_unit hunit = CSS_UNIT_PX, vunit = CSS_UNIT_PX;
+ css_color bgcol;
+ plot_style_t pstyle_fill_bg = {
+ .fill_type = PLOT_OP_TYPE_SOLID,
+ .fill_colour = *background_colour,
+ };
+ nserror res;
+
+ plot_content = (box->background != NULL);
+
+ if (html_redraw_printing && nsoption_bool(remove_backgrounds))
+ return true;
+
+ if (plot_content) {
+ /* handle background-repeat */
+ switch (css_computed_background_repeat(box->style)) {
+ case CSS_BACKGROUND_REPEAT_REPEAT:
+ repeat_x = repeat_y = true;
+ /* optimisation: only plot the colour if
+ * bitmap is not opaque
+ */
+ plot_colour = !content_get_opaque(box->background);
+ break;
+
+ case CSS_BACKGROUND_REPEAT_REPEAT_X:
+ repeat_x = true;
+ break;
+
+ case CSS_BACKGROUND_REPEAT_REPEAT_Y:
+ repeat_y = true;
+ break;
+
+ case CSS_BACKGROUND_REPEAT_NO_REPEAT:
+ break;
+
+ default:
+ break;
+ }
+
+ /* handle background-position */
+ css_computed_background_position(box->style,
+ &hpos, &hunit, &vpos, &vunit);
+ if (hunit == CSS_UNIT_PCT) {
+ x += (b.x1 - b.x0 -
+ content_get_width(box->background) *
+ scale) * FIXTOFLT(hpos) / 100.;
+
+ if (!repeat_x && ((hpos < 2 && !first) ||
+ (hpos > 98 && !last))){
+ plot_content = false;
+ }
+ } else {
+ x += (int) (FIXTOFLT(nscss_len2px(len_ctx, hpos, hunit,
+ box->style)) * scale);
+ }
+
+ if (vunit == CSS_UNIT_PCT) {
+ y += (b.y1 - b.y0 -
+ content_get_height(box->background) *
+ scale) * FIXTOFLT(vpos) / 100.;
+ } else {
+ y += (int) (FIXTOFLT(nscss_len2px(len_ctx, vpos, vunit,
+ box->style)) * scale);
+ }
+ }
+
+ /* plot the background colour */
+ css_computed_background_color(box->style, &bgcol);
+
+ if (nscss_color_is_transparent(bgcol) == false) {
+ *background_colour = nscss_color_to_ns(bgcol);
+ pstyle_fill_bg.fill_colour = *background_colour;
+
+ if (plot_colour) {
+ res = ctx->plot->rectangle(ctx, &pstyle_fill_bg, &r);
+ if (res != NSERROR_OK) {
+ return false;
+ }
+ }
+ }
+ /* and plot the image */
+ if (plot_content) {
+ int width = content_get_width(box->background);
+ int height = content_get_height(box->background);
+
+ if (!repeat_x) {
+ if (r.x0 < x)
+ r.x0 = x;
+ if (r.x1 > x + width * scale)
+ r.x1 = x + width * scale;
+ }
+ if (!repeat_y) {
+ if (r.y0 < y)
+ r.y0 = y;
+ if (r.y1 > y + height * scale)
+ r.y1 = y + height * scale;
+ }
+ /* valid clipping rectangles only */
+ if ((r.x0 < r.x1) && (r.y0 < r.y1)) {
+ struct content_redraw_data bg_data;
+
+ res = ctx->plot->clip(ctx, &r);
+ if (res != NSERROR_OK) {
+ return false;
+ }
+
+ bg_data.x = x;
+ bg_data.y = y;
+ bg_data.width = ceilf(width * scale);
+ bg_data.height = ceilf(height * scale);
+ bg_data.background_colour = *background_colour;
+ bg_data.scale = scale;
+ bg_data.repeat_x = repeat_x;
+ bg_data.repeat_y = repeat_y;
+
+ /* We just continue if redraw fails */
+ content_redraw(box->background, &bg_data, &r, ctx);
+ }
+ }
+
+ return true;
+}
+
+
+/**
+ * Plot text decoration for an inline box.
+ *
+ * \param box box to plot decorations for, of type BOX_INLINE
+ * \param x x coordinate of parent of box
+ * \param y y coordinate of parent of box
+ * \param scale scale for redraw
+ * \param colour colour for decorations
+ * \param ratio position of line as a ratio of line height
+ * \param ctx current redraw context
+ * \return true if successful, false otherwise
+ */
+
+static bool
+html_redraw_text_decoration_inline(struct box *box,
+ int x, int y,
+ float scale,
+ colour colour,
+ float ratio,
+ const struct redraw_context *ctx)
+{
+ struct box *c;
+ plot_style_t plot_style_box = {
+ .stroke_type = PLOT_OP_TYPE_SOLID,
+ .stroke_colour = colour,
+ };
+ nserror res;
+ struct rect rect;
+
+ for (c = box->next;
+ c && c != box->inline_end;
+ c = c->next) {
+ if (c->type != BOX_TEXT) {
+ continue;
+ }
+ rect.x0 = (x + c->x) * scale;
+ rect.y0 = (y + c->y + c->height * ratio) * scale;
+ rect.x1 = (x + c->x + c->width) * scale;
+ rect.y1 = (y + c->y + c->height * ratio) * scale;
+ res = ctx->plot->line(ctx, &plot_style_box, &rect);
+ if (res != NSERROR_OK) {
+ return false;
+ }
+ }
+ return true;
+}
+
+
+/**
+ * Plot text decoration for an non-inline box.
+ *
+ * \param box box to plot decorations for, of type other than BOX_INLINE
+ * \param x x coordinate of box
+ * \param y y coordinate of box
+ * \param scale scale for redraw
+ * \param colour colour for decorations
+ * \param ratio position of line as a ratio of line height
+ * \param ctx current redraw context
+ * \return true if successful, false otherwise
+ */
+
+static bool
+html_redraw_text_decoration_block(struct box *box,
+ int x, int y,
+ float scale,
+ colour colour,
+ float ratio,
+ const struct redraw_context *ctx)
+{
+ struct box *c;
+ plot_style_t plot_style_box = {
+ .stroke_type = PLOT_OP_TYPE_SOLID,
+ .stroke_colour = colour,
+ };
+ nserror res;
+ struct rect rect;
+
+ /* draw through text descendants */
+ for (c = box->children; c; c = c->next) {
+ if (c->type == BOX_TEXT) {
+ rect.x0 = (x + c->x) * scale;
+ rect.y0 = (y + c->y + c->height * ratio) * scale;
+ rect.x1 = (x + c->x + c->width) * scale;
+ rect.y1 = (y + c->y + c->height * ratio) * scale;
+ res = ctx->plot->line(ctx, &plot_style_box, &rect);
+ if (res != NSERROR_OK) {
+ return false;
+ }
+ } else if ((c->type == BOX_INLINE_CONTAINER) || (c->type == BOX_BLOCK)) {
+ if (!html_redraw_text_decoration_block(c,
+ x + c->x, y + c->y,
+ scale, colour, ratio, ctx))
+ return false;
+ }
+ }
+ return true;
+}
+
+
+/**
+ * Plot text decoration for a box.
+ *
+ * \param box box to plot decorations for
+ * \param x_parent x coordinate of parent of box
+ * \param y_parent y coordinate of parent of box
+ * \param scale scale for redraw
+ * \param background_colour current background colour
+ * \param ctx current redraw context
+ * \return true if successful, false otherwise
+ */
+
+static bool html_redraw_text_decoration(struct box *box,
+ int x_parent, int y_parent, float scale,
+ colour background_colour, const struct redraw_context *ctx)
+{
+ static const enum css_text_decoration_e decoration[] = {
+ CSS_TEXT_DECORATION_UNDERLINE, CSS_TEXT_DECORATION_OVERLINE,
+ CSS_TEXT_DECORATION_LINE_THROUGH };
+ static const float line_ratio[] = { 0.9, 0.1, 0.5 };
+ colour fgcol;
+ unsigned int i;
+ css_color col;
+
+ css_computed_color(box->style, &col);
+ fgcol = nscss_color_to_ns(col);
+
+ /* antialias colour for under/overline */
+ if (html_redraw_printing == false)
+ fgcol = blend_colour(background_colour, fgcol);
+
+ if (box->type == BOX_INLINE) {
+ if (!box->inline_end)
+ return true;
+ for (i = 0; i != NOF_ELEMENTS(decoration); i++)
+ if (css_computed_text_decoration(box->style) &
+ decoration[i])
+ if (!html_redraw_text_decoration_inline(box,
+ x_parent, y_parent, scale,
+ fgcol, line_ratio[i], ctx))
+ return false;
+ } else {
+ for (i = 0; i != NOF_ELEMENTS(decoration); i++)
+ if (css_computed_text_decoration(box->style) &
+ decoration[i])
+ if (!html_redraw_text_decoration_block(box,
+ x_parent + box->x,
+ y_parent + box->y,
+ scale,
+ fgcol, line_ratio[i], ctx))
+ return false;
+ }
+
+ return true;
+}
+
+
+/**
+ * Redraw the text content of a box, possibly partially highlighted
+ * because the text has been selected, or matches a search operation.
+ *
+ * \param html The html content to redraw text within.
+ * \param box box with text content
+ * \param x x co-ord of box
+ * \param y y co-ord of box
+ * \param clip current clip rectangle
+ * \param scale current scale setting (1.0 = 100%)
+ * \param current_background_color
+ * \param ctx current redraw context
+ * \return true iff successful and redraw should proceed
+ */
+
+static bool html_redraw_text_box(const html_content *html, struct box *box,
+ int x, int y, const struct rect *clip, float scale,
+ colour current_background_color,
+ const struct redraw_context *ctx)
+{
+ bool excluded = (box->object != NULL);
+ plot_font_style_t fstyle;
+
+ font_plot_style_from_css(&html->len_ctx, box->style, &fstyle);
+ fstyle.background = current_background_color;
+
+ if (!text_redraw(box->text, box->length, box->byte_offset,
+ box->space, &fstyle, x, y,
+ clip, box->height, scale, excluded,
+ (struct content *)html, &html->sel,
+ html->search, ctx))
+ return false;
+
+ return true;
+}
+
+bool html_redraw_box(const html_content *html, struct box *box,
+ int x_parent, int y_parent,
+ const struct rect *clip, float scale,
+ colour current_background_color,
+ const struct redraw_context *ctx);
+
+/**
+ * Draw the various children of a box.
+ *
+ * \param html html content
+ * \param box box to draw children of
+ * \param x_parent coordinate of parent box
+ * \param y_parent coordinate of parent box
+ * \param clip clip rectangle
+ * \param scale scale for redraw
+ * \param current_background_color background colour under this box
+ * \param ctx current redraw context
+ * \return true if successful, false otherwise
+ */
+
+static bool html_redraw_box_children(const html_content *html, struct box *box,
+ int x_parent, int y_parent,
+ const struct rect *clip, float scale,
+ colour current_background_color,
+ const struct redraw_context *ctx)
+{
+ struct box *c;
+
+ for (c = box->children; c; c = c->next) {
+
+ if (c->type != BOX_FLOAT_LEFT && c->type != BOX_FLOAT_RIGHT)
+ if (!html_redraw_box(html, c,
+ x_parent + box->x -
+ scrollbar_get_offset(box->scroll_x),
+ y_parent + box->y -
+ scrollbar_get_offset(box->scroll_y),
+ clip, scale, current_background_color,
+ ctx))
+ return false;
+ }
+ for (c = box->float_children; c; c = c->next_float)
+ if (!html_redraw_box(html, c,
+ x_parent + box->x -
+ scrollbar_get_offset(box->scroll_x),
+ y_parent + box->y -
+ scrollbar_get_offset(box->scroll_y),
+ clip, scale, current_background_color,
+ ctx))
+ return false;
+
+ return true;
+}
+
+/**
+ * Recursively draw a box.
+ *
+ * \param html html content
+ * \param box box to draw
+ * \param x_parent coordinate of parent box
+ * \param y_parent coordinate of parent box
+ * \param clip clip rectangle
+ * \param scale scale for redraw
+ * \param current_background_color background colour under this box
+ * \param ctx current redraw context
+ * \return true if successful, false otherwise
+ *
+ * x, y, clip_[xy][01] are in target coordinates.
+ */
+
+bool html_redraw_box(const html_content *html, struct box *box,
+ int x_parent, int y_parent,
+ const struct rect *clip, const float scale,
+ colour current_background_color,
+ const struct redraw_context *ctx)
+{
+ const struct plotter_table *plot = ctx->plot;
+ int x, y;
+ int width, height;
+ int padding_left, padding_top, padding_width, padding_height;
+ int border_left, border_top, border_right, border_bottom;
+ struct rect r;
+ struct rect rect;
+ int x_scrolled, y_scrolled;
+ struct box *bg_box = NULL;
+ bool has_x_scroll, has_y_scroll;
+ css_computed_clip_rect css_rect;
+ enum css_overflow_e overflow_x = CSS_OVERFLOW_VISIBLE;
+ enum css_overflow_e overflow_y = CSS_OVERFLOW_VISIBLE;
+
+ if (html_redraw_printing && (box->flags & PRINTED))
+ return true;
+
+ if (box->style != NULL) {
+ overflow_x = css_computed_overflow_x(box->style);
+ overflow_y = css_computed_overflow_y(box->style);
+ }
+
+ /* avoid trivial FP maths */
+ if (scale == 1.0) {
+ x = x_parent + box->x;
+ y = y_parent + box->y;
+ width = box->width;
+ height = box->height;
+ padding_left = box->padding[LEFT];
+ padding_top = box->padding[TOP];
+ padding_width = padding_left + box->width + box->padding[RIGHT];
+ padding_height = padding_top + box->height +
+ box->padding[BOTTOM];
+ border_left = box->border[LEFT].width;
+ border_top = box->border[TOP].width;
+ border_right = box->border[RIGHT].width;
+ border_bottom = box->border[BOTTOM].width;
+ } else {
+ x = (x_parent + box->x) * scale;
+ y = (y_parent + box->y) * scale;
+ width = box->width * scale;
+ height = box->height * scale;
+ /* left and top padding values are normally zero,
+ * so avoid trivial FP maths */
+ padding_left = box->padding[LEFT] ? box->padding[LEFT] * scale
+ : 0;
+ padding_top = box->padding[TOP] ? box->padding[TOP] * scale
+ : 0;
+ padding_width = (box->padding[LEFT] + box->width +
+ box->padding[RIGHT]) * scale;
+ padding_height = (box->padding[TOP] + box->height +
+ box->padding[BOTTOM]) * scale;
+ border_left = box->border[LEFT].width * scale;
+ border_top = box->border[TOP].width * scale;
+ border_right = box->border[RIGHT].width * scale;
+ border_bottom = box->border[BOTTOM].width * scale;
+ }
+
+ /* calculate rectangle covering this box and descendants */
+ if (box->style && overflow_x != CSS_OVERFLOW_VISIBLE &&
+ box->parent != NULL) {
+ /* box contents clipped to box size */
+ r.x0 = x - border_left;
+ r.x1 = x + padding_width + border_right;
+ } else {
+ /* box contents can hang out of the box; use descendant box */
+ if (scale == 1.0) {
+ r.x0 = x + box->descendant_x0;
+ r.x1 = x + box->descendant_x1 + 1;
+ } else {
+ r.x0 = x + box->descendant_x0 * scale;
+ r.x1 = x + box->descendant_x1 * scale + 1;
+ }
+ if (!box->parent) {
+ /* root element */
+ int margin_left, margin_right;
+ if (scale == 1.0) {
+ margin_left = box->margin[LEFT];
+ margin_right = box->margin[RIGHT];
+ } else {
+ margin_left = box->margin[LEFT] * scale;
+ margin_right = box->margin[RIGHT] * scale;
+ }
+ r.x0 = x - border_left - margin_left < r.x0 ?
+ x - border_left - margin_left : r.x0;
+ r.x1 = x + padding_width + border_right +
+ margin_right > r.x1 ?
+ x + padding_width + border_right +
+ margin_right : r.x1;
+ }
+ }
+
+ /* calculate rectangle covering this box and descendants */
+ if (box->style && overflow_y != CSS_OVERFLOW_VISIBLE &&
+ box->parent != NULL) {
+ /* box contents clipped to box size */
+ r.y0 = y - border_top;
+ r.y1 = y + padding_height + border_bottom;
+ } else {
+ /* box contents can hang out of the box; use descendant box */
+ if (scale == 1.0) {
+ r.y0 = y + box->descendant_y0;
+ r.y1 = y + box->descendant_y1 + 1;
+ } else {
+ r.y0 = y + box->descendant_y0 * scale;
+ r.y1 = y + box->descendant_y1 * scale + 1;
+ }
+ if (!box->parent) {
+ /* root element */
+ int margin_top, margin_bottom;
+ if (scale == 1.0) {
+ margin_top = box->margin[TOP];
+ margin_bottom = box->margin[BOTTOM];
+ } else {
+ margin_top = box->margin[TOP] * scale;
+ margin_bottom = box->margin[BOTTOM] * scale;
+ }
+ r.y0 = y - border_top - margin_top < r.y0 ?
+ y - border_top - margin_top : r.y0;
+ r.y1 = y + padding_height + border_bottom +
+ margin_bottom > r.y1 ?
+ y + padding_height + border_bottom +
+ margin_bottom : r.y1;
+ }
+ }
+
+ /* return if the rectangle is completely outside the clip rectangle */
+ if (clip->y1 < r.y0 || r.y1 < clip->y0 ||
+ clip->x1 < r.x0 || r.x1 < clip->x0)
+ return true;
+
+ /*if the rectangle is under the page bottom but it can fit in a page,
+ don't print it now*/
+ if (html_redraw_printing) {
+ if (r.y1 > html_redraw_printing_border) {
+ if (r.y1 - r.y0 <= html_redraw_printing_border &&
+ (box->type == BOX_TEXT ||
+ box->type == BOX_TABLE_CELL
+ || box->object || box->gadget)) {
+ /*remember the highest of all points from the
+ not printed elements*/
+ if (r.y0 < html_redraw_printing_top_cropped)
+ html_redraw_printing_top_cropped = r.y0;
+ return true;
+ }
+ }
+ else box->flags |= PRINTED; /*it won't be printed anymore*/
+ }
+
+ /* if visibility is hidden render children only */
+ if (box->style && css_computed_visibility(box->style) ==
+ CSS_VISIBILITY_HIDDEN) {
+ if ((ctx->plot->group_start) &&
+ (ctx->plot->group_start(ctx, "hidden box") != NSERROR_OK))
+ return false;
+ if (!html_redraw_box_children(html, box, x_parent, y_parent,
+ &r, scale, current_background_color, ctx))
+ return false;
+ return ((!ctx->plot->group_end) || (ctx->plot->group_end(ctx) == NSERROR_OK));
+ }
+
+ if ((ctx->plot->group_start) &&
+ (ctx->plot->group_start(ctx,"vis box") != NSERROR_OK)) {
+ return false;
+ }
+
+ if (box->style != NULL &&
+ css_computed_position(box->style) ==
+ CSS_POSITION_ABSOLUTE &&
+ css_computed_clip(box->style, &css_rect) ==
+ CSS_CLIP_RECT) {
+ /* We have an absolutly positioned box with a clip rect */
+ if (css_rect.left_auto == false)
+ r.x0 = x - border_left + FIXTOINT(nscss_len2px(
+ &html->len_ctx,
+ css_rect.left, css_rect.lunit,
+ box->style));
+
+ if (css_rect.top_auto == false)
+ r.y0 = y - border_top + FIXTOINT(nscss_len2px(
+ &html->len_ctx,
+ css_rect.top, css_rect.tunit,
+ box->style));
+
+ if (css_rect.right_auto == false)
+ r.x1 = x - border_left + FIXTOINT(nscss_len2px(
+ &html->len_ctx,
+ css_rect.right, css_rect.runit,
+ box->style));
+
+ if (css_rect.bottom_auto == false)
+ r.y1 = y - border_top + FIXTOINT(nscss_len2px(
+ &html->len_ctx,
+ css_rect.bottom, css_rect.bunit,
+ box->style));
+
+ /* find intersection of clip rectangle and box */
+ if (r.x0 < clip->x0) r.x0 = clip->x0;
+ if (r.y0 < clip->y0) r.y0 = clip->y0;
+ if (clip->x1 < r.x1) r.x1 = clip->x1;
+ if (clip->y1 < r.y1) r.y1 = clip->y1;
+ /* Nothing to do for invalid rectangles */
+ if (r.x0 >= r.x1 || r.y0 >= r.y1)
+ /* not an error */
+ return ((!ctx->plot->group_end) ||
+ (ctx->plot->group_end(ctx) == NSERROR_OK));
+ /* clip to it */
+ if (ctx->plot->clip(ctx, &r) != NSERROR_OK)
+ return false;
+
+ } else if (box->type == BOX_BLOCK || box->type == BOX_INLINE_BLOCK ||
+ box->type == BOX_TABLE_CELL || box->object) {
+ /* find intersection of clip rectangle and box */
+ if (r.x0 < clip->x0) r.x0 = clip->x0;
+ if (r.y0 < clip->y0) r.y0 = clip->y0;
+ if (clip->x1 < r.x1) r.x1 = clip->x1;
+ if (clip->y1 < r.y1) r.y1 = clip->y1;
+ /* no point trying to draw 0-width/height boxes */
+ if (r.x0 == r.x1 || r.y0 == r.y1)
+ /* not an error */
+ return ((!ctx->plot->group_end) ||
+ (ctx->plot->group_end(ctx) == NSERROR_OK));
+ /* clip to it */
+ if (ctx->plot->clip(ctx, &r) != NSERROR_OK)
+ return false;
+ } else {
+ /* clip box is fine, clip to it */
+ r = *clip;
+ if (ctx->plot->clip(ctx, &r) != NSERROR_OK)
+ return false;
+ }
+
+ /* background colour and image for block level content and replaced
+ * inlines */
+
+ bg_box = html_redraw_find_bg_box(box);
+
+ /* bg_box == NULL implies that this box should not have
+ * its background rendered. Otherwise filter out linebreaks,
+ * optimize away non-differing inlines, only plot background
+ * for BOX_TEXT it's in an inline */
+ if (bg_box && bg_box->type != BOX_BR &&
+ bg_box->type != BOX_TEXT &&
+ bg_box->type != BOX_INLINE_END &&
+ (bg_box->type != BOX_INLINE || bg_box->object ||
+ bg_box->flags & IFRAME || box->flags & REPLACE_DIM ||
+ (bg_box->gadget != NULL &&
+ (bg_box->gadget->type == GADGET_TEXTAREA ||
+ bg_box->gadget->type == GADGET_TEXTBOX ||
+ bg_box->gadget->type == GADGET_PASSWORD)))) {
+ /* find intersection of clip box and border edge */
+ struct rect p;
+ p.x0 = x - border_left < r.x0 ? r.x0 : x - border_left;
+ p.y0 = y - border_top < r.y0 ? r.y0 : y - border_top;
+ p.x1 = x + padding_width + border_right < r.x1 ?
+ x + padding_width + border_right : r.x1;
+ p.y1 = y + padding_height + border_bottom < r.y1 ?
+ y + padding_height + border_bottom : r.y1;
+ if (!box->parent) {
+ /* Root element, special case:
+ * background covers margins too */
+ int m_left, m_top, m_right, m_bottom;
+ if (scale == 1.0) {
+ m_left = box->margin[LEFT];
+ m_top = box->margin[TOP];
+ m_right = box->margin[RIGHT];
+ m_bottom = box->margin[BOTTOM];
+ } else {
+ m_left = box->margin[LEFT] * scale;
+ m_top = box->margin[TOP] * scale;
+ m_right = box->margin[RIGHT] * scale;
+ m_bottom = box->margin[BOTTOM] * scale;
+ }
+ p.x0 = p.x0 - m_left < r.x0 ? r.x0 : p.x0 - m_left;
+ p.y0 = p.y0 - m_top < r.y0 ? r.y0 : p.y0 - m_top;
+ p.x1 = p.x1 + m_right < r.x1 ? p.x1 + m_right : r.x1;
+ p.y1 = p.y1 + m_bottom < r.y1 ? p.y1 + m_bottom : r.y1;
+ }
+ /* valid clipping rectangles only */
+ if ((p.x0 < p.x1) && (p.y0 < p.y1)) {
+ /* plot background */
+ if (!html_redraw_background(x, y, box, scale, &p,
+ ¤t_background_color, bg_box,
+ &html->len_ctx, ctx))
+ return false;
+ /* restore previous graphics window */
+ if (ctx->plot->clip(ctx, &r) != NSERROR_OK)
+ return false;
+ }
+ }
+
+ /* borders for block level content and replaced inlines */
+ if (box->style &&
+ box->type != BOX_TEXT &&
+ box->type != BOX_INLINE_END &&
+ (box->type != BOX_INLINE || box->object ||
+ box->flags & IFRAME || box->flags & REPLACE_DIM ||
+ (box->gadget != NULL &&
+ (box->gadget->type == GADGET_TEXTAREA ||
+ box->gadget->type == GADGET_TEXTBOX ||
+ box->gadget->type == GADGET_PASSWORD))) &&
+ (border_top || border_right || border_bottom || border_left)) {
+ if (!html_redraw_borders(box, x_parent, y_parent,
+ padding_width, padding_height, &r,
+ scale, ctx))
+ return false;
+ }
+
+ /* backgrounds and borders for non-replaced inlines */
+ if (box->style && box->type == BOX_INLINE && box->inline_end &&
+ (html_redraw_box_has_background(box) ||
+ border_top || border_right ||
+ border_bottom || border_left)) {
+ /* inline backgrounds and borders span other boxes and may
+ * wrap onto separate lines */
+ struct box *ib;
+ struct rect b; /* border edge rectangle */
+ struct rect p; /* clipped rect */
+ bool first = true;
+ int ib_x;
+ int ib_y = y;
+ int ib_p_width;
+ int ib_b_left, ib_b_right;
+
+ b.x0 = x - border_left;
+ b.x1 = x + padding_width + border_right;
+ b.y0 = y - border_top;
+ b.y1 = y + padding_height + border_bottom;
+
+ p.x0 = b.x0 < r.x0 ? r.x0 : b.x0;
+ p.x1 = b.x1 < r.x1 ? b.x1 : r.x1;
+ p.y0 = b.y0 < r.y0 ? r.y0 : b.y0;
+ p.y1 = b.y1 < r.y1 ? b.y1 : r.y1;
+ for (ib = box; ib; ib = ib->next) {
+ /* to get extents of rectangle(s) associated with
+ * inline, cycle though all boxes in inline, skipping
+ * over floats */
+ if (ib->type == BOX_FLOAT_LEFT ||
+ ib->type == BOX_FLOAT_RIGHT)
+ continue;
+ if (scale == 1.0) {
+ ib_x = x_parent + ib->x;
+ ib_y = y_parent + ib->y;
+ ib_p_width = ib->padding[LEFT] + ib->width +
+ ib->padding[RIGHT];
+ ib_b_left = ib->border[LEFT].width;
+ ib_b_right = ib->border[RIGHT].width;
+ } else {
+ ib_x = (x_parent + ib->x) * scale;
+ ib_y = (y_parent + ib->y) * scale;
+ ib_p_width = (ib->padding[LEFT] + ib->width +
+ ib->padding[RIGHT]) * scale;
+ ib_b_left = ib->border[LEFT].width * scale;
+ ib_b_right = ib->border[RIGHT].width * scale;
+ }
+
+ if ((ib->flags & NEW_LINE) && ib != box) {
+ /* inline element has wrapped, plot background
+ * and borders */
+ if (!html_redraw_inline_background(
+ x, y, box, scale, &p, b,
+ first, false,
+ ¤t_background_color,
+ &html->len_ctx, ctx))
+ return false;
+ /* restore previous graphics window */
+ if (ctx->plot->clip(ctx, &r) != NSERROR_OK)
+ return false;
+ if (!html_redraw_inline_borders(box, b, &r,
+ scale, first, false, ctx))
+ return false;
+ /* reset coords */
+ b.x0 = ib_x - ib_b_left;
+ b.y0 = ib_y - border_top - padding_top;
+ b.y1 = ib_y + padding_height - padding_top +
+ border_bottom;
+
+ p.x0 = b.x0 < r.x0 ? r.x0 : b.x0;
+ p.y0 = b.y0 < r.y0 ? r.y0 : b.y0;
+ p.y1 = b.y1 < r.y1 ? b.y1 : r.y1;
+
+ first = false;
+ }
+
+ /* increase width for current box */
+ b.x1 = ib_x + ib_p_width + ib_b_right;
+ p.x1 = b.x1 < r.x1 ? b.x1 : r.x1;
+
+ if (ib == box->inline_end)
+ /* reached end of BOX_INLINE span */
+ break;
+ }
+ /* plot background and borders for last rectangle of
+ * the inline */
+ if (!html_redraw_inline_background(x, ib_y, box, scale, &p, b,
+ first, true, ¤t_background_color,
+ &html->len_ctx, ctx))
+ return false;
+ /* restore previous graphics window */
+ if (ctx->plot->clip(ctx, &r) != NSERROR_OK)
+ return false;
+ if (!html_redraw_inline_borders(box, b, &r, scale, first, true,
+ ctx))
+ return false;
+
+ }
+
+ /* Debug outlines */
+ if (html_redraw_debug) {
+ int margin_left, margin_right;
+ int margin_top, margin_bottom;
+ if (scale == 1.0) {
+ /* avoid trivial fp maths */
+ margin_left = box->margin[LEFT];
+ margin_top = box->margin[TOP];
+ margin_right = box->margin[RIGHT];
+ margin_bottom = box->margin[BOTTOM];
+ } else {
+ margin_left = box->margin[LEFT] * scale;
+ margin_top = box->margin[TOP] * scale;
+ margin_right = box->margin[RIGHT] * scale;
+ margin_bottom = box->margin[BOTTOM] * scale;
+ }
+ /* Content edge -- blue */
+ rect.x0 = x + padding_left;
+ rect.y0 = y + padding_top;
+ rect.x1 = x + padding_left + width;
+ rect.y1 = y + padding_top + height;
+ if (ctx->plot->rectangle(ctx, plot_style_content_edge, &rect) != NSERROR_OK)
+ return false;
+
+ /* Padding edge -- red */
+ rect.x0 = x;
+ rect.y0 = y;
+ rect.x1 = x + padding_width;
+ rect.y1 = y + padding_height;
+ if (ctx->plot->rectangle(ctx, plot_style_padding_edge, &rect) != NSERROR_OK)
+ return false;
+
+ /* Margin edge -- yellow */
+ rect.x0 = x - border_left - margin_left;
+ rect.y0 = y - border_top - margin_top;
+ rect.x1 = x + padding_width + border_right + margin_right;
+ rect.y1 = y + padding_height + border_bottom + margin_bottom;
+ if (ctx->plot->rectangle(ctx, plot_style_margin_edge, &rect) != NSERROR_OK)
+ return false;
+ }
+
+ /* clip to the padding edge for objects, or boxes with overflow hidden
+ * or scroll, unless it's the root element */
+ if (box->parent != NULL) {
+ bool need_clip = false;
+ if (box->object || box->flags & IFRAME ||
+ (overflow_x != CSS_OVERFLOW_VISIBLE &&
+ overflow_y != CSS_OVERFLOW_VISIBLE)) {
+ r.x0 = x;
+ r.y0 = y;
+ r.x1 = x + padding_width;
+ r.y1 = y + padding_height;
+ if (r.x0 < clip->x0) r.x0 = clip->x0;
+ if (r.y0 < clip->y0) r.y0 = clip->y0;
+ if (clip->x1 < r.x1) r.x1 = clip->x1;
+ if (clip->y1 < r.y1) r.y1 = clip->y1;
+ if (r.x1 <= r.x0 || r.y1 <= r.y0) {
+ return (!ctx->plot->group_end ||
+ (ctx->plot->group_end(ctx) == NSERROR_OK));
+ }
+ need_clip = true;
+
+ } else if (overflow_x != CSS_OVERFLOW_VISIBLE) {
+ r.x0 = x;
+ r.y0 = clip->y0;
+ r.x1 = x + padding_width;
+ r.y1 = clip->y1;
+ if (r.x0 < clip->x0) r.x0 = clip->x0;
+ if (clip->x1 < r.x1) r.x1 = clip->x1;
+ if (r.x1 <= r.x0) {
+ return (!ctx->plot->group_end ||
+ (ctx->plot->group_end(ctx) == NSERROR_OK));
+ }
+ need_clip = true;
+
+ } else if (overflow_y != CSS_OVERFLOW_VISIBLE) {
+ r.x0 = clip->x0;
+ r.y0 = y;
+ r.x1 = clip->x1;
+ r.y1 = y + padding_height;
+ if (r.y0 < clip->y0) r.y0 = clip->y0;
+ if (clip->y1 < r.y1) r.y1 = clip->y1;
+ if (r.y1 <= r.y0) {
+ return (!ctx->plot->group_end ||
+ (ctx->plot->group_end(ctx) == NSERROR_OK));
+ }
+ need_clip = true;
+ }
+
+ if (need_clip &&
+ (box->type == BOX_BLOCK ||
+ box->type == BOX_INLINE_BLOCK ||
+ box->type == BOX_TABLE_CELL || box->object)) {
+ if (ctx->plot->clip(ctx, &r) != NSERROR_OK)
+ return false;
+ }
+ }
+
+ /* text decoration */
+ if ((box->type != BOX_TEXT) &&
+ box->style &&
+ css_computed_text_decoration(box->style) != CSS_TEXT_DECORATION_NONE) {
+ if (!html_redraw_text_decoration(box, x_parent, y_parent,
+ scale, current_background_color, ctx))
+ return false;
+ }
+
+ if (box->object && width != 0 && height != 0) {
+ struct content_redraw_data obj_data;
+
+ x_scrolled = x - scrollbar_get_offset(box->scroll_x) * scale;
+ y_scrolled = y - scrollbar_get_offset(box->scroll_y) * scale;
+
+ obj_data.x = x_scrolled + padding_left;
+ obj_data.y = y_scrolled + padding_top;
+ obj_data.width = width;
+ obj_data.height = height;
+ obj_data.background_colour = current_background_color;
+ obj_data.scale = scale;
+ obj_data.repeat_x = false;
+ obj_data.repeat_y = false;
+
+ if (content_get_type(box->object) == CONTENT_HTML) {
+ obj_data.x /= scale;
+ obj_data.y /= scale;
+ }
+
+ if (!content_redraw(box->object, &obj_data, &r, ctx)) {
+ /* Show image fail */
+ /* Unicode (U+FFFC) 'OBJECT REPLACEMENT CHARACTER' */
+ const char *obj = "\xef\xbf\xbc";
+ int obj_width;
+ int obj_x = x + padding_left;
+ nserror res;
+
+ rect.x0 = x + padding_left;
+ rect.y0 = y + padding_top;
+ rect.x1 = x + padding_left + width - 1;
+ rect.y1 = y + padding_top + height - 1;
+ res = ctx->plot->rectangle(ctx, plot_style_broken_object, &rect);
+ if (res != NSERROR_OK) {
+ return false;
+ }
+
+ res = guit->layout->width(plot_fstyle_broken_object,
+ obj,
+ sizeof(obj) - 1,
+ &obj_width);
+ if (res != NSERROR_OK) {
+ obj_x += 1;
+ } else {
+ obj_x += width / 2 - obj_width / 2;
+ }
+
+ if (ctx->plot->text(ctx,
+ plot_fstyle_broken_object,
+ obj_x, y + padding_top + (int)(height * 0.75),
+ obj, sizeof(obj) - 1) != NSERROR_OK)
+ return false;
+ }
+
+ } else if (box->iframe) {
+ /* Offset is passed to browser window redraw unscaled */
+ browser_window_redraw(box->iframe,
+ (x + padding_left) / scale,
+ (y + padding_top) / scale, &r, ctx);
+
+ } else if (box->gadget && box->gadget->type == GADGET_CHECKBOX) {
+ if (!html_redraw_checkbox(x + padding_left, y + padding_top,
+ width, height, box->gadget->selected, ctx))
+ return false;
+
+ } else if (box->gadget && box->gadget->type == GADGET_RADIO) {
+ if (!html_redraw_radio(x + padding_left, y + padding_top,
+ width, height, box->gadget->selected, ctx))
+ return false;
+
+ } else if (box->gadget && box->gadget->type == GADGET_FILE) {
+ if (!html_redraw_file(x + padding_left, y + padding_top,
+ width, height, box, scale,
+ current_background_color, &html->len_ctx, ctx))
+ return false;
+
+ } else if (box->gadget &&
+ (box->gadget->type == GADGET_TEXTAREA ||
+ box->gadget->type == GADGET_PASSWORD ||
+ box->gadget->type == GADGET_TEXTBOX)) {
+ textarea_redraw(box->gadget->data.text.ta, x, y,
+ current_background_color, scale, &r, ctx);
+
+ } else if (box->text) {
+ if (!html_redraw_text_box(html, box, x, y, &r, scale,
+ current_background_color, ctx))
+ return false;
+
+ } else {
+ if (!html_redraw_box_children(html, box, x_parent, y_parent, &r,
+ scale, current_background_color, ctx))
+ return false;
+ }
+
+ if (box->type == BOX_BLOCK || box->type == BOX_INLINE_BLOCK ||
+ box->type == BOX_TABLE_CELL || box->type == BOX_INLINE)
+ if (ctx->plot->clip(ctx, clip) != NSERROR_OK)
+ return false;
+
+ /* list marker */
+ if (box->list_marker) {
+ if (!html_redraw_box(html, box->list_marker,
+ x_parent + box->x -
+ scrollbar_get_offset(box->scroll_x),
+ y_parent + box->y -
+ scrollbar_get_offset(box->scroll_y),
+ clip, scale, current_background_color, ctx))
+ return false;
+ }
+
+ /* scrollbars */
+ if (((box->style && box->type != BOX_BR &&
+ box->type != BOX_TABLE && box->type != BOX_INLINE &&
+ (overflow_x == CSS_OVERFLOW_SCROLL ||
+ overflow_x == CSS_OVERFLOW_AUTO ||
+ overflow_y == CSS_OVERFLOW_SCROLL ||
+ overflow_y == CSS_OVERFLOW_AUTO)) ||
+ (box->object && content_get_type(box->object) ==
+ CONTENT_HTML)) && box->parent != NULL) {
+
+ has_x_scroll = box_hscrollbar_present(box);
+ has_y_scroll = box_vscrollbar_present(box);
+
+ if (!box_handle_scrollbars((struct content *)html,
+ box, has_x_scroll, has_y_scroll))
+ return false;
+
+ if (box->scroll_x != NULL)
+ scrollbar_redraw(box->scroll_x,
+ x_parent + box->x,
+ y_parent + box->y + box->padding[TOP] +
+ box->height + box->padding[BOTTOM] -
+ SCROLLBAR_WIDTH, clip, scale, ctx);
+ if (box->scroll_y != NULL)
+ scrollbar_redraw(box->scroll_y,
+ x_parent + box->x + box->padding[LEFT] +
+ box->width + box->padding[RIGHT] -
+ SCROLLBAR_WIDTH,
+ y_parent + box->y, clip, scale, ctx);
+ }
+
+ if (box->type == BOX_BLOCK || box->type == BOX_INLINE_BLOCK ||
+ box->type == BOX_TABLE_CELL || box->type == BOX_INLINE) {
+ if (ctx->plot->clip(ctx, clip) != NSERROR_OK)
+ return false;
+ }
+
+ return ((!plot->group_end) || (ctx->plot->group_end(ctx) == NSERROR_OK));
+}
+
+/**
+ * Draw a CONTENT_HTML using the current set of plotters (plot).
+ *
+ * \param c content of type CONTENT_HTML
+ * \param data redraw data for this content redraw
+ * \param clip current clip region
+ * \param ctx current redraw context
+ * \return true if successful, false otherwise
+ *
+ * x, y, clip_[xy][01] are in target coordinates.
+ */
+
+bool html_redraw(struct content *c, struct content_redraw_data *data,
+ const struct rect *clip, const struct redraw_context *ctx)
+{
+ html_content *html = (html_content *) c;
+ struct box *box;
+ bool result = true;
+ bool select, select_only;
+ plot_style_t pstyle_fill_bg = {
+ .fill_type = PLOT_OP_TYPE_SOLID,
+ .fill_colour = data->background_colour,
+ };
+
+ box = html->layout;
+ assert(box);
+
+ /* The select menu needs special treating because, when opened, it
+ * reaches beyond its layout box.
+ */
+ select = false;
+ select_only = false;
+ if (ctx->interactive && html->visible_select_menu != NULL) {
+ struct form_control *control = html->visible_select_menu;
+ select = true;
+ /* check if the redraw rectangle is completely inside of the
+ select menu */
+ select_only = form_clip_inside_select_menu(control,
+ data->scale, clip);
+ }
+
+ if (!select_only) {
+ /* clear to background colour */
+ result = (ctx->plot->clip(ctx, clip) == NSERROR_OK);
+
+ if (html->background_colour != NS_TRANSPARENT)
+ pstyle_fill_bg.fill_colour = html->background_colour;
+
+ result &= (ctx->plot->rectangle(ctx, &pstyle_fill_bg, clip) == NSERROR_OK);
+
+ result &= html_redraw_box(html, box, data->x, data->y, clip,
+ data->scale, pstyle_fill_bg.fill_colour, ctx);
+ }
+
+ if (select) {
+ int menu_x, menu_y;
+ box = html->visible_select_menu->box;
+ box_coords(box, &menu_x, &menu_y);
+
+ menu_x -= box->border[LEFT].width;
+ menu_y += box->height + box->border[BOTTOM].width +
+ box->padding[BOTTOM] + box->padding[TOP];
+ result &= form_redraw_select_menu(html->visible_select_menu,
+ data->x + menu_x, data->y + menu_y,
+ data->scale, clip, ctx);
+ }
+
+ return result;
+
+}
diff --git a/content/handlers/html/html_redraw_border.c b/content/handlers/html/html_redraw_border.c
new file mode 100644
index 0000000..2a849e8
--- /dev/null
+++ b/content/handlers/html/html_redraw_border.c
@@ -0,0 +1,928 @@
+/*
+ * Copyright 2017 Vincent Sanders <vince(a)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
+ *
+ * Redrawing CONTENT_HTML borders implementation.
+ */
+
+#include <stdbool.h>
+#include <stdlib.h>
+
+#include "utils/log.h"
+#include "netsurf/plotters.h"
+#include "netsurf/css.h"
+
+#include "html/box.h"
+#include "html/html_internal.h"
+
+
+static plot_style_t plot_style_bdr = {
+ .stroke_type = PLOT_OP_TYPE_DASH,
+};
+static plot_style_t plot_style_fillbdr = {
+ .fill_type = PLOT_OP_TYPE_SOLID,
+};
+static plot_style_t plot_style_fillbdr_dark = {
+ .fill_type = PLOT_OP_TYPE_SOLID,
+};
+static plot_style_t plot_style_fillbdr_light = {
+ .fill_type = PLOT_OP_TYPE_SOLID,
+};
+static plot_style_t plot_style_fillbdr_ddark = {
+ .fill_type = PLOT_OP_TYPE_SOLID,
+};
+static plot_style_t plot_style_fillbdr_dlight = {
+ .fill_type = PLOT_OP_TYPE_SOLID,
+};
+
+
+static inline nserror
+plot_clipped_rectangle(const struct redraw_context *ctx,
+ const plot_style_t *style,
+ const struct rect *clip,
+ struct rect *rect)
+{
+ nserror res;
+
+ rect->x0 = (clip->x0 > rect->x0) ? clip->x0 : rect->x0;
+ rect->y0 = (clip->y0 > rect->y0) ? clip->y0 : rect->y0;
+ rect->x1 = (clip->x1 < rect->x1) ? clip->x1 : rect->x1;
+ rect->y1 = (clip->y1 < rect->y1) ? clip->y1 : rect->y1;
+ if ((rect->x0 < rect->x1) && (rect->y0 < rect->y1)) {
+ /* valid clip rectangles only */
+ res = ctx->plot->rectangle(ctx, style, rect);
+ } else {
+ res = NSERROR_OK;
+ }
+ return res;
+}
+
+
+/**
+ * Draw one border.
+ *
+ * \param side index of border side (TOP, RIGHT, BOTTOM, LEFT)
+ * \param p array of precomputed border vertices
+ * \param c colour for border
+ * \param style border line style
+ * \param thickness border thickness
+ * \param rectangular whether border is rectangular
+ * \param clip cliping area for redrawing border.
+ * \param ctx current redraw context
+ * \return NSERROR_OK if successful otherwise appropriate error code
+ */
+static nserror
+html_redraw_border_plot(const int side,
+ const int *p,
+ colour c,
+ enum css_border_style_e style,
+ int thickness,
+ bool rectangular,
+ const struct rect *clip,
+ const struct redraw_context *ctx)
+{
+ int z[8]; /* Vertices of border part */
+ unsigned int light = side;
+ plot_style_t *plot_style_bdr_in;
+ plot_style_t *plot_style_bdr_out;
+ nserror res = NSERROR_OK;
+ struct rect rect;
+
+ if (c == NS_TRANSPARENT) {
+ return res;
+ }
+
+ plot_style_bdr.stroke_type = PLOT_OP_TYPE_DASH;
+ plot_style_bdr.stroke_colour = c;
+ plot_style_bdr.stroke_width = thickness;
+ plot_style_fillbdr.fill_colour = c;
+ plot_style_fillbdr_dark.fill_colour = darken_colour(c);
+ plot_style_fillbdr_light.fill_colour = lighten_colour(c);
+ plot_style_fillbdr_ddark.fill_colour = double_darken_colour(c);
+ plot_style_fillbdr_dlight.fill_colour = double_lighten_colour(c);
+
+ switch (style) {
+ case CSS_BORDER_STYLE_DOTTED:
+ plot_style_bdr.stroke_type = PLOT_OP_TYPE_DOT;
+ /* fall through */
+ case CSS_BORDER_STYLE_DASHED:
+ rect.x0 = (p[0] + p[2]) / 2;
+ rect.y0 = (p[1] + p[3]) / 2;
+ rect.x1 = (p[4] + p[6]) / 2;
+ rect.y1 = (p[5] + p[7]) / 2;
+ res = ctx->plot->line(ctx, &plot_style_bdr, &rect);
+ break;
+
+ case CSS_BORDER_STYLE_SOLID:
+ /* fall through to default */
+ default:
+ if (rectangular || thickness == 1) {
+
+ if (side == TOP || side == RIGHT) {
+ rect.x0 = p[2];
+ rect.y0 = p[3];
+ if ((side == TOP) &&
+ (p[4] - p[6] != 0)) {
+ rect.x1 = p[4];
+ } else {
+ rect.x1 = p[6];
+ }
+ rect.y1 = p[7];
+ } else {
+ rect.x0 = p[6];
+ rect.y0 = p[7];
+ rect.x1 = p[2];
+ if ((side == LEFT) &&
+ (p[1] - p[3] != 0)) {
+ rect.y1 = p[1];
+ } else {
+ rect.y1 = p[3];
+ }
+ }
+ res = plot_clipped_rectangle(ctx,
+ &plot_style_fillbdr,
+ clip,
+ &rect);
+ } else {
+ res = ctx->plot->polygon(ctx, &plot_style_fillbdr, p, 4);
+ }
+ break;
+
+ case CSS_BORDER_STYLE_DOUBLE:
+ z[0] = p[0];
+ z[1] = p[1];
+ z[2] = (p[0] * 2 + p[2]) / 3;
+ z[3] = (p[1] * 2 + p[3]) / 3;
+ z[4] = (p[6] * 2 + p[4]) / 3;
+ z[5] = (p[7] * 2 + p[5]) / 3;
+ z[6] = p[6];
+ z[7] = p[7];
+ res = ctx->plot->polygon(ctx, &plot_style_fillbdr, z, 4);
+ if (res == NSERROR_OK) {
+ z[0] = p[2];
+ z[1] = p[3];
+ z[2] = (p[2] * 2 + p[0]) / 3;
+ z[3] = (p[3] * 2 + p[1]) / 3;
+ z[4] = (p[4] * 2 + p[6]) / 3;
+ z[5] = (p[5] * 2 + p[7]) / 3;
+ z[6] = p[4];
+ z[7] = p[5];
+ res = ctx->plot->polygon(ctx, &plot_style_fillbdr, z, 4);
+ }
+ break;
+
+ case CSS_BORDER_STYLE_GROOVE:
+ light = 3 - light;
+ /* fall through */
+ case CSS_BORDER_STYLE_RIDGE:
+ /* choose correct colours for each part of the border line */
+ if (light <= 1) {
+ plot_style_bdr_in = &plot_style_fillbdr_dark;
+ plot_style_bdr_out = &plot_style_fillbdr_light;
+ } else {
+ plot_style_bdr_in = &plot_style_fillbdr_light;
+ plot_style_bdr_out = &plot_style_fillbdr_dark;
+ }
+
+ /* Render border */
+ if ((rectangular || thickness == 2) && thickness != 1) {
+ /* Border made up from two parts and can be
+ * plotted with rectangles
+ */
+
+ /* First part */
+ if (side == TOP || side == RIGHT) {
+ rect.x0 = (p[0] + p[2]) / 2;
+ rect.y0 = (p[1] + p[3]) / 2;
+ rect.x1 = p[6];
+ rect.y1 = p[7];
+ } else {
+ rect.x0 = p[6];
+ rect.y0 = p[7];
+ rect.x1 = (p[0] + p[2]) / 2;
+ rect.y1 = (p[1] + p[3]) / 2;
+ }
+ res = plot_clipped_rectangle(ctx,
+ plot_style_bdr_in,
+ clip,
+ &rect);
+ if (res != NSERROR_OK) {
+ return res;
+ }
+
+ /* Second part */
+ if (side == TOP || side == RIGHT) {
+ rect.x0 = p[2];
+ rect.y0 = p[3];
+ rect.x1 = (p[6] + p[4]) / 2;
+ rect.y1 = (p[7] + p[5]) / 2;
+ } else {
+ rect.x0 = (p[6] + p[4]) / 2;
+ rect.y0 = (p[7] + p[5]) / 2;
+ rect.x1 = p[2];
+ rect.y1 = p[3];
+ }
+ res = plot_clipped_rectangle(ctx,
+ plot_style_bdr_out,
+ clip,
+ &rect);
+ } else if (thickness == 1) {
+ /* Border made up from one part which can be
+ * plotted as a rectangle
+ */
+
+ if (side == TOP || side == RIGHT) {
+ rect.x0 = p[2];
+ rect.y0 = p[3];
+ rect.x1 = p[6];
+ rect.y1 = p[7];
+ rect.x1 = ((side == TOP) && (p[4] - p[6] != 0)) ?
+ rect.x1 + p[4] - p[6] : rect.x1;
+
+ res = plot_clipped_rectangle(ctx,
+ plot_style_bdr_in,
+ clip,
+ &rect);
+ } else {
+ rect.x0 = p[6];
+ rect.y0 = p[7];
+ rect.x1 = p[2];
+ rect.y1 = p[3];
+ rect.y1 = ((side == LEFT) && (p[1] - p[3] != 0)) ?
+ rect.y1 + p[1] - p[3] : rect.y1;
+ res = plot_clipped_rectangle(ctx,
+ plot_style_bdr_out,
+ clip,
+ &rect);
+ }
+ } else {
+ /* Border made up from two parts and can't be
+ * plotted with rectangles
+ */
+ z[0] = p[0];
+ z[1] = p[1];
+ z[2] = (p[0] + p[2]) / 2;
+ z[3] = (p[1] + p[3]) / 2;
+ z[4] = (p[6] + p[4]) / 2;
+ z[5] = (p[7] + p[5]) / 2;
+ z[6] = p[6];
+ z[7] = p[7];
+ res = ctx->plot->polygon(ctx, plot_style_bdr_in, z, 4);
+ if (res == NSERROR_OK) {
+ z[0] = p[2];
+ z[1] = p[3];
+ z[6] = p[4];
+ z[7] = p[5];
+ res = ctx->plot->polygon(ctx,
+ plot_style_bdr_out,
+ z,
+ 4);
+ }
+ }
+ break;
+
+ case CSS_BORDER_STYLE_INSET:
+ light = (light + 2) % 4;
+ /* fall through */
+ case CSS_BORDER_STYLE_OUTSET:
+ /* choose correct colours for each part of the border line */
+ switch (light) {
+ case 0:
+ plot_style_bdr_in = &plot_style_fillbdr_light;
+ plot_style_bdr_out = &plot_style_fillbdr_dlight;
+ break;
+ case 1:
+ plot_style_bdr_in = &plot_style_fillbdr_ddark;
+ plot_style_bdr_out = &plot_style_fillbdr_dark;
+ break;
+ case 2:
+ plot_style_bdr_in = &plot_style_fillbdr_dark;
+ plot_style_bdr_out = &plot_style_fillbdr_ddark;
+ break;
+ case 3:
+ plot_style_bdr_in = &plot_style_fillbdr_dlight;
+ plot_style_bdr_out = &plot_style_fillbdr_light;
+ break;
+ default:
+ plot_style_bdr_in = &plot_style_fillbdr;
+ plot_style_bdr_out = &plot_style_fillbdr;
+ break;
+ }
+
+ /* Render border */
+ if ((rectangular || thickness == 2) && thickness != 1) {
+ /* Border made up from two parts and can be
+ * plotted with rectangles
+ */
+
+ /* First part */
+ if (side == TOP || side == RIGHT) {
+ rect.x0 = (p[0] + p[2]) / 2;
+ rect.y0 = (p[1] + p[3]) / 2;
+ rect.x1 = p[6];
+ rect.y1 = p[7];
+ } else {
+ rect.x0 = p[6];
+ rect.y0 = p[7];
+ rect.x1 = (p[0] + p[2]) / 2;
+ rect.y1 = (p[1] + p[3]) / 2;
+ }
+ res = plot_clipped_rectangle(ctx,
+ plot_style_bdr_in,
+ clip,
+ &rect);
+ if (res != NSERROR_OK) {
+ return res;
+ }
+
+ /* Second part */
+ if (side == TOP || side == RIGHT) {
+ rect.x0 = p[2];
+ rect.y0 = p[3];
+ rect.x1 = (p[6] + p[4]) / 2;
+ rect.y1 = (p[7] + p[5]) / 2;
+ } else {
+ rect.x0 = (p[6] + p[4]) / 2;
+ rect.y0 = (p[7] + p[5]) / 2;
+ rect.x1 = p[2];
+ rect.y1 = p[3];
+ }
+ res = plot_clipped_rectangle(ctx,
+ plot_style_bdr_out,
+ clip,
+ &rect);
+ } else if (thickness == 1) {
+ /* Border made up from one part which can be
+ * plotted as a rectangle
+ */
+
+ if (side == TOP || side == RIGHT) {
+ rect.x0 = p[2];
+ rect.y0 = p[3];
+ rect.x1 = p[6];
+ rect.y1 = p[7];
+ rect.x1 = ((side == TOP) && (p[4] - p[6] != 0)) ?
+ rect.x1 + p[4] - p[6] : rect.x1;
+ res = plot_clipped_rectangle(ctx,
+ plot_style_bdr_in,
+ clip,
+ &rect);
+ } else {
+ rect.x0 = p[6];
+ rect.y0 = p[7];
+ rect.x1 = p[2];
+ rect.y1 = p[3];
+ rect.y1 = ((side == LEFT) && (p[1] - p[3] != 0)) ?
+ rect.y1 + p[1] - p[3] : rect.y1;
+ res = plot_clipped_rectangle(ctx,
+ plot_style_bdr_out,
+ clip,
+ &rect);
+ }
+ } else {
+ /* Border made up from two parts and can't be
+ * plotted with rectangles
+ */
+
+ z[0] = p[0];
+ z[1] = p[1];
+ z[2] = (p[0] + p[2]) / 2;
+ z[3] = (p[1] + p[3]) / 2;
+ z[4] = (p[6] + p[4]) / 2;
+ z[5] = (p[7] + p[5]) / 2;
+ z[6] = p[6];
+ z[7] = p[7];
+ res = ctx->plot->polygon(ctx, plot_style_bdr_in, z, 4);
+ if (res != NSERROR_OK) {
+ return res;
+ }
+ z[0] = p[2];
+ z[1] = p[3];
+ z[6] = p[4];
+ z[7] = p[5];
+ res = ctx->plot->polygon(ctx, plot_style_bdr_out, z, 4);
+ }
+ break;
+ }
+
+ return res;
+}
+
+
+/**
+ * Draw borders for a box.
+ *
+ * \param box box to draw
+ * \param x_parent coordinate of left padding edge of parent of box
+ * \param y_parent coordinate of top padding edge of parent of box
+ * \param p_width width of padding box
+ * \param p_height height of padding box
+ * \param clip cliping area for redrawing border.
+ * \param scale scale for redraw
+ * \param ctx current redraw context
+ * \return true if successful, false otherwise
+ */
+bool
+html_redraw_borders(struct box *box,
+ int x_parent,
+ int y_parent,
+ int p_width,
+ int p_height,
+ const struct rect *clip,
+ float scale,
+ const struct redraw_context *ctx)
+{
+ unsigned int sides[] = { LEFT, RIGHT, TOP, BOTTOM };
+ int top = box->border[TOP].width;
+ int right = box->border[RIGHT].width;
+ int bottom = box->border[BOTTOM].width;
+ int left = box->border[LEFT].width;
+ int x, y;
+ unsigned int i, side;
+ int p[8]; /* Box border vertices */
+ int z[8]; /* Border vertices */
+ bool square_end_1 = false;
+ bool square_end_2 = false;
+ nserror res;
+
+ x = x_parent + box->x;
+ y = y_parent + box->y;
+
+ if (scale != 1.0) {
+ top *= scale;
+ right *= scale;
+ bottom *= scale;
+ left *= scale;
+ x *= scale;
+ y *= scale;
+ }
+
+ assert(box->style);
+
+ /* Calculate border vertices
+ *
+ * A----------------------+
+ * | \ / |
+ * | B--------------+ |
+ * | | | |
+ * | +--------------C |
+ * | / \ |
+ * +----------------------D
+ */
+ p[0] = x - left; p[1] = y - top; /* A */
+ p[2] = x; p[3] = y; /* B */
+ p[4] = x + p_width; p[5] = y + p_height; /* C */
+ p[6] = x + p_width + right; p[7] = y + p_height + bottom; /* D */
+
+ for (i = 0; i != 4; i++) {
+ colour col = 0;
+ side = sides[i]; /* plot order */
+
+ if (box->border[side].width == 0 ||
+ nscss_color_is_transparent(box->border[side].c)) {
+ continue;
+ }
+
+ switch (side) {
+ case LEFT:
+ square_end_1 = (top == 0);
+ square_end_2 = (bottom == 0);
+
+ z[0] = p[0]; z[1] = p[7];
+ z[2] = p[2]; z[3] = p[5];
+ z[4] = p[2]; z[5] = p[3];
+ z[6] = p[0]; z[7] = p[1];
+
+ if (nscss_color_is_transparent(box->border[TOP].c) == false &&
+ box->border[TOP].style != CSS_BORDER_STYLE_DOUBLE) {
+ /* make border overhang top corner fully,
+ * if top border is opaque
+ */
+ z[5] -= top;
+ square_end_1 = true;
+ }
+ if (nscss_color_is_transparent(box->border[BOTTOM].c) == false &&
+ box->border[BOTTOM].style != CSS_BORDER_STYLE_DOUBLE) {
+ /* make border overhang bottom corner fully,
+ * if bottom border is opaque
+ */
+ z[3] += bottom;
+ square_end_2 = true;
+ }
+
+ col = nscss_color_to_ns(box->border[side].c);
+
+ res = html_redraw_border_plot(side,
+ z,
+ col,
+ box->border[side].style,
+ box->border[side].width * scale,
+ square_end_1 && square_end_2,
+ clip,
+ ctx);
+ if (res != NSERROR_OK) {
+ return false;
+ }
+ break;
+
+ case RIGHT:
+ square_end_1 = (top == 0);
+ square_end_2 = (bottom == 0);
+
+ z[0] = p[6]; z[1] = p[1];
+ z[2] = p[4]; z[3] = p[3];
+ z[4] = p[4]; z[5] = p[5];
+ z[6] = p[6]; z[7] = p[7];
+
+ if (nscss_color_is_transparent(box->border[TOP].c) == false &&
+ box->border[TOP].style != CSS_BORDER_STYLE_DOUBLE) {
+ /* make border overhang top corner fully,
+ * if top border is opaque
+ */
+ z[3] -= top;
+ square_end_1 = true;
+ }
+ if (nscss_color_is_transparent(box->border[BOTTOM].c) == false &&
+ box->border[BOTTOM].style != CSS_BORDER_STYLE_DOUBLE) {
+ /* make border overhang bottom corner fully,
+ * if bottom border is opaque
+ */
+ z[5] += bottom;
+ square_end_2 = true;
+ }
+
+ col = nscss_color_to_ns(box->border[side].c);
+
+ res = html_redraw_border_plot(side,
+ z,
+ col,
+ box->border[side].style,
+ box->border[side].width * scale,
+ square_end_1 && square_end_2,
+ clip,
+ ctx);
+ if (res != NSERROR_OK) {
+ return false;
+ }
+ break;
+
+ case TOP:
+ if (clip->y0 > p[3]) {
+ /* clip rectangle is below border; nothing to
+ * plot
+ */
+ continue;
+ }
+
+ square_end_1 = (left == 0);
+ square_end_2 = (right == 0);
+
+ z[0] = p[2]; z[1] = p[3];
+ z[2] = p[0]; z[3] = p[1];
+ z[4] = p[6]; z[5] = p[1];
+ z[6] = p[4]; z[7] = p[3];
+
+ if (box->border[TOP].style == CSS_BORDER_STYLE_SOLID &&
+ box->border[TOP].c == box->border[LEFT].c) {
+ /* don't bother overlapping left corner if
+ * it's the same colour anyway
+ */
+ z[2] += left;
+ square_end_1 = true;
+ }
+ if (box->border[TOP].style == CSS_BORDER_STYLE_SOLID &&
+ box->border[TOP].c == box->border[RIGHT].c) {
+ /* don't bother overlapping right corner if
+ * it's the same colour anyway
+ */
+ z[4] -= right;
+ square_end_2 = true;
+ }
+
+ col = nscss_color_to_ns(box->border[side].c);
+
+ res = html_redraw_border_plot(side,
+ z,
+ col,
+ box->border[side].style,
+ box->border[side].width * scale,
+ square_end_1 && square_end_2,
+ clip,
+ ctx);
+ if (res != NSERROR_OK) {
+ return false;
+ }
+ break;
+
+ case BOTTOM:
+ if (clip->y1 < p[5]) {
+ /* clip rectangle is above border; nothing to
+ * plot
+ */
+ continue;
+ }
+
+ square_end_1 = (left == 0);
+ square_end_2 = (right == 0);
+
+ z[0] = p[4]; z[1] = p[5];
+ z[2] = p[6]; z[3] = p[7];
+ z[4] = p[0]; z[5] = p[7];
+ z[6] = p[2]; z[7] = p[5];
+
+ if (box->border[BOTTOM].style == CSS_BORDER_STYLE_SOLID &&
+ box->border[BOTTOM].c == box->border[LEFT].c) {
+ /* don't bother overlapping left corner if
+ * it's the same colour anyway
+ */
+ z[4] += left;
+ square_end_1 = true;
+ }
+ if (box->border[BOTTOM].style == CSS_BORDER_STYLE_SOLID &&
+ box->border[BOTTOM].c == box->border[RIGHT].c) {
+ /* don't bother overlapping right corner if
+ * it's the same colour anyway
+ */
+ z[2] -= right;
+ square_end_2 = true;
+ }
+
+ col = nscss_color_to_ns(box->border[side].c);
+
+ res = html_redraw_border_plot(side,
+ z,
+ col,
+ box->border[side].style,
+ box->border[side].width * scale,
+ square_end_1 && square_end_2,
+ clip,
+ ctx);
+ if (res != NSERROR_OK) {
+ return false;
+ }
+ break;
+
+ default:
+ assert(side == TOP || side == BOTTOM ||
+ side == LEFT || side == RIGHT);
+ break;
+ }
+ }
+
+ return true;
+}
+
+
+/**
+ * Draw an inline's borders.
+ *
+ * \param box BOX_INLINE which created the border
+ * \param b coordinates of border edge rectangle
+ * \param clip cliping area for redrawing border.
+ * \param scale scale for redraw
+ * \param first true if this is the first rectangle associated with the inline
+ * \param last true if this is the last rectangle associated with the inline
+ * \param ctx current redraw context
+ * \return true if successful, false otherwise
+ */
+bool
+html_redraw_inline_borders(struct box *box,
+ struct rect b,
+ const struct rect *clip,
+ float scale,
+ bool first,
+ bool last,
+ const struct redraw_context *ctx)
+{
+ int top = box->border[TOP].width;
+ int right = box->border[RIGHT].width;
+ int bottom = box->border[BOTTOM].width;
+ int left = box->border[LEFT].width;
+ colour col;
+ int p[8]; /* Box border vertices */
+ int z[8]; /* Border vertices */
+ bool square_end_1;
+ bool square_end_2;
+ nserror res;
+
+ if (scale != 1.0) {
+ top *= scale;
+ right *= scale;
+ bottom *= scale;
+ left *= scale;
+ }
+
+ /* Calculate border vertices
+ *
+ * A----------------------+
+ * | \ / |
+ * | B--------------+ |
+ * | | | |
+ * | +--------------C |
+ * | / \ |
+ * +----------------------D
+ */
+ p[0] = b.x0; p[1] = b.y0; /* A */
+ p[2] = first ? b.x0 + left : b.x0; p[3] = b.y0 + top; /* B */
+ p[4] = last ? b.x1 - right : b.x1; p[5] = b.y1 - bottom; /* C */
+ p[6] = b.x1; p[7] = b.y1; /* D */
+
+ assert(box->style);
+
+ /* Left */
+ square_end_1 = (top == 0);
+ square_end_2 = (bottom == 0);
+ if (left != 0 &&
+ first &&
+ nscss_color_is_transparent(box->border[LEFT].c) == false) {
+ col = nscss_color_to_ns(box->border[LEFT].c);
+
+ z[0] = p[0]; z[1] = p[7];
+ z[2] = p[2]; z[3] = p[5];
+ z[4] = p[2]; z[5] = p[3];
+ z[6] = p[0]; z[7] = p[1];
+
+ if (nscss_color_is_transparent(box->border[TOP].c) == false &&
+ box->border[TOP].style != CSS_BORDER_STYLE_DOUBLE) {
+ /* make border overhang top corner fully,
+ * if top border is opaque
+ */
+ z[5] -= top;
+ square_end_1 = true;
+ }
+
+ if (nscss_color_is_transparent(box->border[BOTTOM].c) == false &&
+ box->border[BOTTOM].style != CSS_BORDER_STYLE_DOUBLE) {
+ /* make border overhang bottom corner fully,
+ * if bottom border is opaque
+ */
+ z[3] += bottom;
+ square_end_2 = true;
+ }
+
+ res = html_redraw_border_plot(LEFT,
+ z,
+ col,
+ box->border[LEFT].style,
+ left,
+ square_end_1 && square_end_2,
+ clip,
+ ctx);
+ if (res != NSERROR_OK) {
+ return false;
+ }
+ }
+
+ /* Right */
+ square_end_1 = (top == 0);
+ square_end_2 = (bottom == 0);
+ if (right != 0 &&
+ last &&
+ nscss_color_is_transparent(box->border[RIGHT].c) == false) {
+ col = nscss_color_to_ns(box->border[RIGHT].c);
+
+ z[0] = p[6]; z[1] = p[1];
+ z[2] = p[4]; z[3] = p[3];
+ z[4] = p[4]; z[5] = p[5];
+ z[6] = p[6]; z[7] = p[7];
+
+ if (nscss_color_is_transparent(box->border[TOP].c) == false &&
+ box->border[TOP].style != CSS_BORDER_STYLE_DOUBLE) {
+ /* make border overhang top corner fully,
+ * if top border is opaque
+ */
+ z[3] -= top;
+ square_end_1 = true;
+ }
+
+ if (nscss_color_is_transparent(box->border[BOTTOM].c) == false &&
+ box->border[BOTTOM].style != CSS_BORDER_STYLE_DOUBLE) {
+ /* make border overhang bottom corner fully,
+ * if bottom border is opaque
+ */
+ z[5] += bottom;
+ square_end_2 = true;
+ }
+
+ res = html_redraw_border_plot(RIGHT,
+ z,
+ col,
+ box->border[RIGHT].style,
+ right,
+ square_end_1 && square_end_2,
+ clip,
+ ctx);
+ if (res != NSERROR_OK) {
+ return false;
+ }
+ }
+
+ /* Top */
+ square_end_1 = (left == 0);
+ square_end_2 = (right == 0);
+ if (top != 0 &&
+ nscss_color_is_transparent(box->border[TOP].c) == false) {
+ col = nscss_color_to_ns(box->border[TOP].c);
+
+ z[0] = p[2]; z[1] = p[3];
+ z[2] = p[0]; z[3] = p[1];
+ z[4] = p[6]; z[5] = p[1];
+ z[6] = p[4]; z[7] = p[3];
+
+ if (first &&
+ box->border[TOP].style == CSS_BORDER_STYLE_SOLID &&
+ box->border[TOP].c == box->border[LEFT].c) {
+ /* don't bother overlapping left corner if
+ * it's the same colour anyway
+ */
+ z[2] += left;
+ square_end_1 = true;
+ }
+
+ if (last &&
+ box->border[TOP].style == CSS_BORDER_STYLE_SOLID &&
+ box->border[TOP].c == box->border[RIGHT].c) {
+ /* don't bother overlapping right corner if
+ * it's the same colour anyway
+ */
+ z[4] -= right;
+ square_end_2 = true;
+ }
+
+ res = html_redraw_border_plot(TOP,
+ z,
+ col,
+ box->border[TOP].style,
+ top,
+ square_end_1 && square_end_2,
+ clip,
+ ctx);
+ if (res != NSERROR_OK) {
+ return false;
+ }
+ }
+
+ /* Bottom */
+ square_end_1 = (left == 0);
+ square_end_2 = (right == 0);
+ if (bottom != 0 &&
+ nscss_color_is_transparent(box->border[BOTTOM].c) == false) {
+ col = nscss_color_to_ns(box->border[BOTTOM].c);
+
+ z[0] = p[4]; z[1] = p[5];
+ z[2] = p[6]; z[3] = p[7];
+ z[4] = p[0]; z[5] = p[7];
+ z[6] = p[2]; z[7] = p[5];
+
+ if (first &&
+ box->border[BOTTOM].style == CSS_BORDER_STYLE_SOLID &&
+ box->border[BOTTOM].c == box->border[LEFT].c) {
+ /* don't bother overlapping left corner if
+ * it's the same colour anyway
+ */
+ z[4] += left;
+ square_end_1 = true;
+ }
+
+ if (last &&
+ box->border[BOTTOM].style == CSS_BORDER_STYLE_SOLID &&
+ box->border[BOTTOM].c == box->border[RIGHT].c) {
+ /* don't bother overlapping right corner if
+ * it's the same colour anyway
+ */
+ z[2] -= right;
+ square_end_2 = true;
+ }
+
+ res = html_redraw_border_plot(BOTTOM,
+ z,
+ col,
+ box->border[BOTTOM].style,
+ bottom,
+ square_end_1 && square_end_2,
+ clip,
+ ctx);
+ if (res != NSERROR_OK) {
+ return false;
+ }
+ }
+
+ return true;
+}
diff --git a/content/handlers/html/html_script.c b/content/handlers/html/html_script.c
new file mode 100644
index 0000000..e18a0ca
--- /dev/null
+++ b/content/handlers/html/html_script.c
@@ -0,0 +1,604 @@
+/*
+ * Copyright 2012 Vincent Sanders <vince(a)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
+ * implementation of content handling for text/html scripts.
+ */
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include <strings.h>
+#include <stdlib.h>
+
+#include "utils/config.h"
+#include "utils/corestrings.h"
+#include "utils/log.h"
+#include "utils/messages.h"
+#include "netsurf/content.h"
+#include "javascript/js.h"
+#include "content/content_protected.h"
+#include "content/fetch.h"
+#include "content/hlcache.h"
+
+#include "html/html_internal.h"
+
+typedef bool (script_handler_t)(struct jscontext *jscontext, const char *data, size_t size) ;
+
+
+static script_handler_t *select_script_handler(content_type ctype)
+{
+ if (ctype == CONTENT_JS) {
+ return js_exec;
+ }
+ return NULL;
+}
+
+
+/* exported internal interface documented in html/html_internal.h */
+nserror html_script_exec(html_content *c)
+{
+ unsigned int i;
+ struct html_script *s;
+ script_handler_t *script_handler;
+
+ if (c->jscontext == NULL) {
+ return NSERROR_BAD_PARAMETER;
+ }
+
+ for (i = 0, s = c->scripts; i != c->scripts_count; i++, s++) {
+ if (s->already_started) {
+ continue;
+ }
+
+ if ((s->type == HTML_SCRIPT_ASYNC) ||
+ (s->type == HTML_SCRIPT_DEFER)) {
+ /* ensure script content is present */
+ if (s->data.handle == NULL)
+ continue;
+
+ /* ensure script content fetch status is not an error */
+ if (content_get_status(s->data.handle) ==
+ CONTENT_STATUS_ERROR)
+ continue;
+
+ /* ensure script handler for content type */
+ script_handler = select_script_handler(
+ content_get_type(s->data.handle));
+ if (script_handler == NULL)
+ continue; /* unsupported type */
+
+ if (content_get_status(s->data.handle) ==
+ CONTENT_STATUS_DONE) {
+ /* external script is now available */
+ const char *data;
+ unsigned long size;
+ data = content_get_source_data(
+ s->data.handle, &size );
+ script_handler(c->jscontext, data, size);
+
+ s->already_started = true;
+
+ }
+ }
+ }
+
+ return NSERROR_OK;
+}
+
+/* create new html script entry */
+static struct html_script *
+html_process_new_script(html_content *c,
+ dom_string *mimetype,
+ enum html_script_type type)
+{
+ struct html_script *nscript;
+ /* add space for new script entry */
+ nscript = realloc(c->scripts,
+ sizeof(struct html_script) * (c->scripts_count + 1));
+ if (nscript == NULL) {
+ return NULL;
+ }
+
+ c->scripts = nscript;
+
+ /* increment script entry count */
+ nscript = &c->scripts[c->scripts_count];
+ c->scripts_count++;
+
+ nscript->already_started = false;
+ nscript->parser_inserted = false;
+ nscript->force_async = true;
+ nscript->ready_exec = false;
+ nscript->async = false;
+ nscript->defer = false;
+
+ nscript->type = type;
+
+ nscript->mimetype = dom_string_ref(mimetype); /* reference mimetype */
+
+ return nscript;
+}
+
+/**
+ * Callback for asyncronous scripts
+ */
+static nserror
+convert_script_async_cb(hlcache_handle *script,
+ const hlcache_event *event,
+ void *pw)
+{
+ html_content *parent = pw;
+ unsigned int i;
+ struct html_script *s;
+
+ /* Find script */
+ for (i = 0, s = parent->scripts; i != parent->scripts_count; i++, s++) {
+ if (s->type == HTML_SCRIPT_ASYNC && s->data.handle == script)
+ break;
+ }
+
+ assert(i != parent->scripts_count);
+
+ switch (event->type) {
+ case CONTENT_MSG_LOADING:
+ break;
+
+ case CONTENT_MSG_READY:
+ break;
+
+ case CONTENT_MSG_DONE:
+ NSLOG(netsurf, INFO, "script %d done '%s'", i,
+ nsurl_access(hlcache_handle_get_url(script)));
+ parent->base.active--;
+ NSLOG(netsurf, INFO, "%d fetches active", parent->base.active);
+
+ break;
+
+ case CONTENT_MSG_ERROR:
+ NSLOG(netsurf, INFO, "script %s failed: %s",
+ nsurl_access(hlcache_handle_get_url(script)),
+ event->data.error);
+ /* fall through */
+
+ case CONTENT_MSG_ERRORCODE:
+ hlcache_handle_release(script);
+ s->data.handle = NULL;
+ parent->base.active--;
+ NSLOG(netsurf, INFO, "%d fetches active", parent->base.active);
+ content_add_error(&parent->base, "?", 0);
+
+ break;
+
+ default:
+ break;
+ }
+
+ /* if there are no active fetches remaining begin post parse
+ * conversion
+ */
+ if (html_can_begin_conversion(parent)) {
+ html_begin_conversion(parent);
+ }
+
+ return NSERROR_OK;
+}
+
+/**
+ * Callback for defer scripts
+ */
+static nserror
+convert_script_defer_cb(hlcache_handle *script,
+ const hlcache_event *event,
+ void *pw)
+{
+ html_content *parent = pw;
+ unsigned int i;
+ struct html_script *s;
+
+ /* Find script */
+ for (i = 0, s = parent->scripts; i != parent->scripts_count; i++, s++) {
+ if (s->type == HTML_SCRIPT_DEFER && s->data.handle == script)
+ break;
+ }
+
+ assert(i != parent->scripts_count);
+
+ switch (event->type) {
+
+ case CONTENT_MSG_DONE:
+ NSLOG(netsurf, INFO, "script %d done '%s'", i,
+ nsurl_access(hlcache_handle_get_url(script)));
+ parent->base.active--;
+ NSLOG(netsurf, INFO, "%d fetches active", parent->base.active);
+
+ break;
+
+ case CONTENT_MSG_ERROR:
+ NSLOG(netsurf, INFO, "script %s failed: %s",
+ nsurl_access(hlcache_handle_get_url(script)),
+ event->data.error);
+ /* fall through */
+
+ case CONTENT_MSG_ERRORCODE:
+ hlcache_handle_release(script);
+ s->data.handle = NULL;
+ parent->base.active--;
+ NSLOG(netsurf, INFO, "%d fetches active", parent->base.active);
+ content_add_error(&parent->base, "?", 0);
+
+ break;
+
+ default:
+ break;
+ }
+
+ /* if there are no active fetches remaining begin post parse
+ * conversion
+ */
+ if (html_can_begin_conversion(parent)) {
+ html_begin_conversion(parent);
+ }
+
+ return NSERROR_OK;
+}
+
+/**
+ * Callback for syncronous scripts
+ */
+static nserror
+convert_script_sync_cb(hlcache_handle *script,
+ const hlcache_event *event,
+ void *pw)
+{
+ html_content *parent = pw;
+ unsigned int i;
+ struct html_script *s;
+ script_handler_t *script_handler;
+ dom_hubbub_error err;
+
+ /* Find script */
+ for (i = 0, s = parent->scripts; i != parent->scripts_count; i++, s++) {
+ if (s->type == HTML_SCRIPT_SYNC && s->data.handle == script)
+ break;
+ }
+
+ assert(i != parent->scripts_count);
+
+ switch (event->type) {
+ case CONTENT_MSG_DONE:
+ NSLOG(netsurf, INFO, "script %d done '%s'", i,
+ nsurl_access(hlcache_handle_get_url(script)));
+ parent->base.active--;
+ NSLOG(netsurf, INFO, "%d fetches active", parent->base.active);
+
+ s->already_started = true;
+
+ /* attempt to execute script */
+ script_handler = select_script_handler(content_get_type(s->data.handle));
+ if (script_handler != NULL && parent->jscontext != NULL) {
+ /* script has a handler */
+ const char *data;
+ unsigned long size;
+ data = content_get_source_data(s->data.handle, &size );
+ script_handler(parent->jscontext, data, size);
+ }
+
+ /* continue parse */
+ err = dom_hubbub_parser_pause(parent->parser, false);
+ if (err != DOM_HUBBUB_OK) {
+ NSLOG(netsurf, INFO, "unpause returned 0x%x", err);
+ }
+
+ break;
+
+ case CONTENT_MSG_ERROR:
+ NSLOG(netsurf, INFO, "script %s failed: %s",
+ nsurl_access(hlcache_handle_get_url(script)),
+ event->data.error);
+ /* fall through */
+
+ case CONTENT_MSG_ERRORCODE:
+ hlcache_handle_release(script);
+ s->data.handle = NULL;
+ parent->base.active--;
+
+ NSLOG(netsurf, INFO, "%d fetches active", parent->base.active);
+ content_add_error(&parent->base, "?", 0);
+
+ s->already_started = true;
+
+ /* continue parse */
+ err = dom_hubbub_parser_pause(parent->parser, false);
+ if (err != DOM_HUBBUB_OK) {
+ NSLOG(netsurf, INFO, "unpause returned 0x%x", err);
+ }
+
+ break;
+
+ default:
+ break;
+ }
+
+ /* if there are no active fetches remaining begin post parse
+ * conversion
+ */
+ if (html_can_begin_conversion(parent)) {
+ html_begin_conversion(parent);
+ }
+
+ return NSERROR_OK;
+}
+
+/**
+ * process a script with a src tag
+ */
+static dom_hubbub_error
+exec_src_script(html_content *c,
+ dom_node *node,
+ dom_string *mimetype,
+ dom_string *src)
+{
+ nserror ns_error;
+ nsurl *joined;
+ hlcache_child_context child;
+ struct html_script *nscript;
+ bool async;
+ bool defer;
+ enum html_script_type script_type;
+ hlcache_handle_callback script_cb;
+ dom_hubbub_error ret = DOM_HUBBUB_OK;
+ dom_exception exc; /* returned by libdom functions */
+
+ /* src url */
+ ns_error = nsurl_join(c->base_url, dom_string_data(src), &joined);
+ if (ns_error != NSERROR_OK) {
+ content_broadcast_errorcode(&c->base, NSERROR_NOMEM);
+ return DOM_HUBBUB_NOMEM;
+ }
+
+ NSLOG(netsurf, INFO, "script %i '%s'", c->scripts_count,
+ nsurl_access(joined));
+
+ /* there are three ways to process the script tag at this point:
+ *
+ * Syncronously pause the parent parse and continue after
+ * the script has downloaded and executed. (default)
+ * Async Start the script downloading and execute it when it
+ * becomes available.
+ * Defered Start the script downloading and execute it when
+ * the page has completed parsing, may be set along
+ * with async where it is ignored.
+ */
+
+ /* we interpret the presence of the async and defer attribute
+ * as true and ignore its value, technically only the empty
+ * value or the attribute name itself are valid. However
+ * various browsers interpret this in various ways the most
+ * compatible approach is to be liberal and accept any
+ * value. Note setting the values to "false" still makes them true!
+ */
+ exc = dom_element_has_attribute(node, corestring_dom_async, &async);
+ if (exc != DOM_NO_ERR) {
+ return DOM_HUBBUB_OK; /* dom error */
+ }
+
+ if (async) {
+ /* asyncronous script */
+ script_type = HTML_SCRIPT_ASYNC;
+ script_cb = convert_script_async_cb;
+
+ } else {
+ exc = dom_element_has_attribute(node,
+ corestring_dom_defer, &defer);
+ if (exc != DOM_NO_ERR) {
+ return DOM_HUBBUB_OK; /* dom error */
+ }
+
+ if (defer) {
+ /* defered script */
+ script_type = HTML_SCRIPT_DEFER;
+ script_cb = convert_script_defer_cb;
+ } else {
+ /* syncronous script */
+ script_type = HTML_SCRIPT_SYNC;
+ script_cb = convert_script_sync_cb;
+ }
+ }
+
+ nscript = html_process_new_script(c, mimetype, script_type);
+ if (nscript == NULL) {
+ nsurl_unref(joined);
+ content_broadcast_errorcode(&c->base, NSERROR_NOMEM);
+ return DOM_HUBBUB_NOMEM;
+ }
+
+ /* set up child fetch encoding and quirks */
+ child.charset = c->encoding;
+ child.quirks = c->base.quirks;
+
+ ns_error = hlcache_handle_retrieve(joined,
+ 0,
+ content_get_url(&c->base),
+ NULL,
+ script_cb,
+ c,
+ &child,
+ CONTENT_SCRIPT,
+ &nscript->data.handle);
+
+
+ nsurl_unref(joined);
+
+ if (ns_error != NSERROR_OK) {
+ /* @todo Deal with fetch error better. currently assume
+ * fetch never became active
+ */
+ /* mark duff script fetch as already started */
+ nscript->already_started = true;
+ NSLOG(netsurf, INFO, "Fetch failed with error %d", ns_error);
+ } else {
+ /* update base content active fetch count */
+ c->base.active++;
+ NSLOG(netsurf, INFO, "%d fetches active", c->base.active);
+
+ switch (script_type) {
+ case HTML_SCRIPT_SYNC:
+ ret = DOM_HUBBUB_HUBBUB_ERR | HUBBUB_PAUSED;
+
+ case HTML_SCRIPT_ASYNC:
+ break;
+
+ case HTML_SCRIPT_DEFER:
+ break;
+
+ default:
+ assert(0);
+ }
+ }
+
+ return ret;
+}
+
+static dom_hubbub_error
+exec_inline_script(html_content *c, dom_node *node, dom_string *mimetype)
+{
+ dom_string *script;
+ dom_exception exc; /* returned by libdom functions */
+ struct lwc_string_s *lwcmimetype;
+ script_handler_t *script_handler;
+ struct html_script *nscript;
+
+ /* does not appear to be a src so script is inline content */
+ exc = dom_node_get_text_content(node, &script);
+ if ((exc != DOM_NO_ERR) || (script == NULL)) {
+ return DOM_HUBBUB_OK; /* no contents, skip */
+ }
+
+ nscript = html_process_new_script(c, mimetype, HTML_SCRIPT_INLINE);
+ if (nscript == NULL) {
+ dom_string_unref(script);
+
+ content_broadcast_errorcode(&c->base, NSERROR_NOMEM);
+ return DOM_HUBBUB_NOMEM;
+
+ }
+
+ nscript->data.string = script;
+ nscript->already_started = true;
+
+ /* ensure script handler for content type */
+ dom_string_intern(mimetype, &lwcmimetype);
+ script_handler = select_script_handler(content_factory_type_from_mime_type(lwcmimetype));
+ lwc_string_unref(lwcmimetype);
+
+ if (script_handler != NULL) {
+ script_handler(c->jscontext,
+ dom_string_data(script),
+ dom_string_byte_length(script));
+ }
+ return DOM_HUBBUB_OK;
+}
+
+
+/**
+ * process script node parser callback
+ *
+ *
+ */
+dom_hubbub_error
+html_process_script(void *ctx, dom_node *node)
+{
+ html_content *c = (html_content *)ctx;
+ dom_exception exc; /* returned by libdom functions */
+ dom_string *src, *mimetype;
+ dom_hubbub_error err = DOM_HUBBUB_OK;
+
+ /* ensure javascript context is available */
+ /* We should only ever be here if scripting was enabled for this
+ * content so it's correct to make a javascript context if there
+ * isn't one already. */
+ if (c->jscontext == NULL) {
+ union content_msg_data msg_data;
+
+ msg_data.jscontext = &c->jscontext;
+ content_broadcast(&c->base, CONTENT_MSG_GETCTX, &msg_data);
+ NSLOG(netsurf, INFO, "javascript context %p ", c->jscontext);
+ if (c->jscontext == NULL) {
+ /* no context and it could not be created, abort */
+ return DOM_HUBBUB_OK;
+ }
+ }
+
+ NSLOG(netsurf, INFO, "content %p parser %p node %p", c, c->parser,
+ node);
+
+ exc = dom_element_get_attribute(node, corestring_dom_type, &mimetype);
+ if (exc != DOM_NO_ERR || mimetype == NULL) {
+ mimetype = dom_string_ref(corestring_dom_text_javascript);
+ }
+
+ exc = dom_element_get_attribute(node, corestring_dom_src, &src);
+ if (exc != DOM_NO_ERR || src == NULL) {
+ err = exec_inline_script(c, node, mimetype);
+ } else {
+ err = exec_src_script(c, node, mimetype, src);
+ dom_string_unref(src);
+ }
+
+ dom_string_unref(mimetype);
+
+ return err;
+}
+
+/* exported internal interface documented in html/html_internal.h */
+nserror html_script_free(html_content *html)
+{
+ unsigned int i;
+
+ for (i = 0; i != html->scripts_count; i++) {
+ if (html->scripts[i].mimetype != NULL) {
+ dom_string_unref(html->scripts[i].mimetype);
+ }
+
+ if ((html->scripts[i].type == HTML_SCRIPT_INLINE) &&
+ (html->scripts[i].data.string != NULL)) {
+
+ dom_string_unref(html->scripts[i].data.string);
+
+ } else if ((html->scripts[i].type == HTML_SCRIPT_SYNC) &&
+ (html->scripts[i].data.handle != NULL)) {
+
+ hlcache_handle_release(html->scripts[i].data.handle);
+
+ }
+ }
+ free(html->scripts);
+
+ return NSERROR_OK;
+}
+
+/* exported internal interface documented in html/html_internal.h */
+nserror html_script_invalidate_ctx(html_content *htmlc)
+{
+ htmlc->jscontext = NULL;
+ return NSERROR_OK;
+}
diff --git a/content/handlers/html/imagemap.c b/content/handlers/html/imagemap.c
new file mode 100644
index 0000000..5f4dd54
--- /dev/null
+++ b/content/handlers/html/imagemap.c
@@ -0,0 +1,804 @@
+/*
+ * Copyright 2004 John M Bell <jmb202(a)ecs.soton.ac.uk>
+ *
+ * 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
+ * Implementation of HTML image maps
+ *
+ * \todo should this should use the general hashmap instead of its own
+ */
+
+#include <assert.h>
+#include <stdbool.h>
+#include <string.h>
+#include <strings.h>
+
+#include <dom/dom.h>
+
+#include "utils/log.h"
+#include "utils/corestrings.h"
+#include "content/content_protected.h"
+#include "content/hlcache.h"
+
+#include "html/box.h"
+#include "html/html_internal.h"
+#include "html/imagemap.h"
+
+#define HASH_SIZE 31 /* fixed size hash table */
+
+typedef enum {
+ IMAGEMAP_DEFAULT,
+ IMAGEMAP_RECT,
+ IMAGEMAP_CIRCLE,
+ IMAGEMAP_POLY
+} imagemap_entry_type;
+
+struct mapentry {
+ imagemap_entry_type type; /**< type of shape */
+ nsurl *url; /**< absolute url to go to */
+ char *target; /**< target frame (if any) */
+ union {
+ struct {
+ int x; /**< x coordinate of centre */
+ int y; /**< y coordinate of center */
+ int r; /**< radius of circle */
+ } circle;
+ struct {
+ int x0; /**< left hand edge */
+ int y0; /**< top edge */
+ int x1; /**< right hand edge */
+ int y1; /**< bottom edge */
+ } rect;
+ struct {
+ int num; /**< number of points */
+ float *xcoords; /**< x coordinates */
+ float *ycoords; /**< y coordinates */
+ } poly;
+ } bounds;
+ struct mapentry *next; /**< next entry in list */
+};
+
+struct imagemap {
+ char *key; /**< key for this entry */
+ struct mapentry *list; /**< pointer to linked list of entries */
+ struct imagemap *next; /**< next entry in this hash chain */
+};
+
+/**
+ * Create hashtable of imagemaps
+ *
+ * \param c The containing content
+ * \return true on success, false otherwise
+ */
+static bool imagemap_create(html_content *c)
+{
+ assert(c != NULL);
+
+ if (c->imagemaps == NULL) {
+ c->imagemaps = calloc(HASH_SIZE, sizeof(struct imagemap *));
+ if (c->imagemaps == NULL) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/**
+ * Hash function.
+ *
+ * \param key The key to hash.
+ * \return The hashed value.
+ */
+static unsigned int imagemap_hash(const char *key)
+{
+ unsigned int z = 0;
+
+ if (key == 0) return 0;
+
+ for (; *key != 0; key++) {
+ z += *key & 0x1f;
+ }
+
+ return (z % (HASH_SIZE - 1)) + 1;
+}
+
+/**
+ * Add an imagemap to the hashtable, creating it if it doesn't exist
+ *
+ * \param c The containing content
+ * \param key The name of the imagemap
+ * \param list List of map regions
+ * \return true on succes, false otherwise
+ */
+static bool
+imagemap_add(html_content *c, dom_string *key, struct mapentry *list)
+{
+ struct imagemap *map;
+ unsigned int slot;
+
+ assert(c != NULL);
+ assert(key != NULL);
+ assert(list != NULL);
+
+ if (imagemap_create(c) == false)
+ return false;
+
+ map = calloc(1, sizeof(*map));
+ if (map == NULL)
+ return false;
+
+ /* \todo Stop relying on NULL termination of dom_string */
+ map->key = strdup(dom_string_data(key));
+ if (map->key == NULL) {
+ free(map);
+ return false;
+ }
+
+ map->list = list;
+
+ slot = imagemap_hash(map->key);
+
+ map->next = c->imagemaps[slot];
+ c->imagemaps[slot] = map;
+
+ return true;
+}
+
+/**
+ * Free list of imagemap entries
+ *
+ * \param list Pointer to head of list
+ */
+static void imagemap_freelist(struct mapentry *list)
+{
+ struct mapentry *entry, *prev;
+
+ assert(list != NULL);
+
+ entry = list;
+
+ while (entry != NULL) {
+ prev = entry;
+
+ nsurl_unref(entry->url);
+
+ if (entry->target)
+ free(entry->target);
+
+ if (entry->type == IMAGEMAP_POLY) {
+ free(entry->bounds.poly.xcoords);
+ free(entry->bounds.poly.ycoords);
+ }
+
+ entry = entry->next;
+ free(prev);
+ }
+}
+
+/**
+ * Destroy hashtable of imagemaps
+ *
+ * \param c The containing content
+ */
+void imagemap_destroy(html_content *c)
+{
+ unsigned int i;
+
+ assert(c != NULL);
+
+ /* no imagemaps -> return */
+ if (c->imagemaps == NULL)
+ return;
+
+ for (i = 0; i != HASH_SIZE; i++) {
+ struct imagemap *map, *next;
+
+ map = c->imagemaps[i];
+ while (map != NULL) {
+ next = map->next;
+ imagemap_freelist(map->list);
+ free(map->key);
+ free(map);
+ map = next;
+ }
+ }
+
+ free(c->imagemaps);
+}
+
+/**
+ * Dump imagemap data to the log
+ *
+ * \param c The containing content
+ */
+void imagemap_dump(html_content *c)
+{
+ unsigned int i;
+
+ int j;
+
+ assert(c != NULL);
+
+ if (c->imagemaps == NULL)
+ return;
+
+ for (i = 0; i != HASH_SIZE; i++) {
+ struct imagemap *map;
+ struct mapentry *entry;
+
+ map = c->imagemaps[i];
+ while (map != NULL) {
+ NSLOG(netsurf, INFO, "Imagemap: %s", map->key);
+
+ for (entry = map->list; entry; entry = entry->next) {
+ switch (entry->type) {
+ case IMAGEMAP_DEFAULT:
+ NSLOG(netsurf, INFO, "\tDefault: %s",
+ nsurl_access(entry->url));
+ break;
+ case IMAGEMAP_RECT:
+ NSLOG(netsurf, INFO,
+ "\tRectangle: %s: [(%d,%d),(%d,%d)]",
+ nsurl_access(entry->url),
+ entry->bounds.rect.x0,
+ entry->bounds.rect.y0,
+ entry->bounds.rect.x1,
+ entry->bounds.rect.y1);
+ break;
+ case IMAGEMAP_CIRCLE:
+ NSLOG(netsurf, INFO,
+ "\tCircle: %s: [(%d,%d),%d]",
+ nsurl_access(entry->url),
+ entry->bounds.circle.x,
+ entry->bounds.circle.y,
+ entry->bounds.circle.r);
+ break;
+ case IMAGEMAP_POLY:
+ NSLOG(netsurf, INFO,
+ "\tPolygon: %s:",
+ nsurl_access(entry->url));
+ for (j = 0; j != entry->bounds.poly.num;
+ j++) {
+ fprintf(stderr, "(%d,%d) ",
+ (int)entry->bounds.poly.xcoords[j],
+ (int)entry->bounds.poly.ycoords[j]);
+ }
+ fprintf(stderr,"\n");
+ break;
+ }
+ }
+ map = map->next;
+ }
+ }
+}
+
+/**
+ * Adds an imagemap entry to the list
+ *
+ * \param c The html content that the imagemap belongs to
+ * \param n The xmlNode representing the entry to add
+ * \param base_url Base URL for resolving relative URLs
+ * \param entry Pointer to list of entries
+ * \param tagtype The type of tag
+ * \return false on memory exhaustion, true otherwise
+ */
+static bool
+imagemap_addtolist(const struct html_content *c, dom_node *n, nsurl *base_url,
+ struct mapentry **entry, dom_string *tagtype)
+{
+ dom_exception exc;
+ dom_string *href = NULL, *target = NULL, *shape = NULL;
+ dom_string *coords = NULL;
+ struct mapentry *new_map, *temp;
+ bool ret = true;
+
+ if (dom_string_caseless_isequal(tagtype, corestring_dom_area)) {
+ bool nohref = false;
+ exc = dom_element_has_attribute(n,
+ corestring_dom_nohref, &nohref);
+ if ((exc != DOM_NO_ERR) || nohref)
+ /* Skip <area nohref="anything" /> */
+ goto ok_out;
+ }
+
+ exc = dom_element_get_attribute(n, corestring_dom_href, &href);
+ if (exc != DOM_NO_ERR || href == NULL) {
+ /* No href="" attribute, skip this element */
+ goto ok_out;
+ }
+
+ exc = dom_element_get_attribute(n, corestring_dom_target, &target);
+ if (exc != DOM_NO_ERR) {
+ goto ok_out;
+ }
+
+ exc = dom_element_get_attribute(n, corestring_dom_shape, &shape);
+ if (exc != DOM_NO_ERR) {
+ goto ok_out;
+ }
+
+ /* If there's no shape, we default to rectangles */
+ if (shape == NULL)
+ shape = dom_string_ref(corestring_dom_rect);
+
+ if (!dom_string_caseless_lwc_isequal(shape, corestring_lwc_default)) {
+ /* If not 'default' and there's no 'coords' give up */
+ exc = dom_element_get_attribute(n, corestring_dom_coords,
+ &coords);
+ if (exc != DOM_NO_ERR || coords == NULL) {
+ goto ok_out;
+ }
+ }
+
+ new_map = calloc(1, sizeof(*new_map));
+ if (new_map == NULL) {
+ goto bad_out;
+ }
+
+ if (dom_string_caseless_lwc_isequal(shape, corestring_lwc_rect) ||
+ dom_string_caseless_lwc_isequal(shape, corestring_lwc_rectangle))
+ new_map->type = IMAGEMAP_RECT;
+ else if (dom_string_caseless_lwc_isequal(shape, corestring_lwc_circle))
+ new_map->type = IMAGEMAP_CIRCLE;
+ else if (dom_string_caseless_lwc_isequal(shape, corestring_lwc_poly) ||
+ dom_string_caseless_lwc_isequal(shape, corestring_lwc_polygon))
+ new_map->type = IMAGEMAP_POLY;
+ else if (dom_string_caseless_lwc_isequal(shape, corestring_lwc_default))
+ new_map->type = IMAGEMAP_DEFAULT;
+ else
+ goto bad_out;
+
+ if (box_extract_link(c, href, base_url, &new_map->url) == false)
+ goto bad_out;
+
+ if (new_map->url == NULL) {
+ /* non-fatal error -> ignore this */
+ goto ok_free_map_out;
+ }
+
+ if (target != NULL) {
+ /* Copy target into the map */
+ new_map->target = malloc(dom_string_byte_length(target) + 1);
+ if (new_map->target == NULL)
+ goto bad_out;
+ /* Safe, but relies on dom_strings being NULL terminated */
+ /* \todo Do this better */
+ strcpy(new_map->target, dom_string_data(target));
+ }
+
+ if (new_map->type != IMAGEMAP_DEFAULT) {
+ int x, y;
+ float *xcoords, *ycoords;
+ /* coordinates are a comma-separated list of values */
+ char *val = strtok((char *)dom_string_data(coords), ",");
+ int num = 1;
+
+ switch (new_map->type) {
+ case IMAGEMAP_RECT:
+ /* (left, top, right, bottom) */
+ while (val != NULL && num <= 4) {
+ switch (num) {
+ case 1:
+ new_map->bounds.rect.x0 = atoi(val);
+ break;
+ case 2:
+ new_map->bounds.rect.y0 = atoi(val);
+ break;
+ case 3:
+ new_map->bounds.rect.x1 = atoi(val);
+ break;
+ case 4:
+ new_map->bounds.rect.y1 = atoi(val);
+ break;
+ }
+
+ num++;
+ val = strtok(NULL, ",");
+ }
+ break;
+ case IMAGEMAP_CIRCLE:
+ /* (x, y, radius ) */
+ while (val != NULL && num <= 3) {
+ switch (num) {
+ case 1:
+ new_map->bounds.circle.x = atoi(val);
+ break;
+ case 2:
+ new_map->bounds.circle.y = atoi(val);
+ break;
+ case 3:
+ new_map->bounds.circle.r = atoi(val);
+ break;
+ }
+
+ num++;
+ val = strtok(NULL, ",");
+ }
+ break;
+ case IMAGEMAP_POLY:
+ new_map->bounds.poly.xcoords = NULL;
+ new_map->bounds.poly.ycoords = NULL;
+
+ while (val != NULL) {
+ x = atoi(val);
+
+ val = strtok(NULL, ",");
+ if (val == NULL)
+ break;
+
+ y = atoi(val);
+
+ xcoords = realloc(new_map->bounds.poly.xcoords,
+ num * sizeof(float));
+ if (xcoords == NULL) {
+ goto bad_out;
+ }
+ new_map->bounds.poly.xcoords = xcoords;
+
+ ycoords = realloc(new_map->bounds.poly.ycoords,
+ num * sizeof(float));
+ if (ycoords == NULL) {
+ goto bad_out;
+ }
+ new_map->bounds.poly.ycoords = ycoords;
+
+ new_map->bounds.poly.xcoords[num - 1] = x;
+ new_map->bounds.poly.ycoords[num - 1] = y;
+
+ num++;
+ val = strtok(NULL, ",");
+ }
+
+ new_map->bounds.poly.num = num - 1;
+
+ break;
+ default:
+ break;
+ }
+ }
+
+ new_map->next = NULL;
+
+ if (*entry) {
+ /* add to END of list */
+ for (temp = (*entry); temp->next != NULL; temp = temp->next)
+ ;
+ temp->next = new_map;
+ } else {
+ (*entry) = new_map;
+ }
+
+ /* All good, linked in, let's clean up */
+ goto ok_out;
+
+bad_out:
+ ret = false;
+ok_free_map_out:
+ if (new_map != NULL) {
+ if (new_map->url != NULL)
+ nsurl_unref(new_map->url);
+ if (new_map->type == IMAGEMAP_POLY &&
+ new_map->bounds.poly.ycoords != NULL)
+ free(new_map->bounds.poly.ycoords);
+ if (new_map->type == IMAGEMAP_POLY &&
+ new_map->bounds.poly.xcoords != NULL)
+ free(new_map->bounds.poly.xcoords);
+ if (new_map->target != NULL)
+ free(new_map->target);
+
+ free(new_map);
+ }
+ok_out:
+ if (href != NULL)
+ dom_string_unref(href);
+ if (target != NULL)
+ dom_string_unref(target);
+ if (shape != NULL)
+ dom_string_unref(shape);
+ if (coords != NULL)
+ dom_string_unref(coords);
+
+ return ret;
+}
+
+/**
+ * Extract an imagemap from html source
+ *
+ * \param node XML node containing map
+ * \param c Content containing document
+ * \param entry List of map entries
+ * \param tname The sub-tags to consider on this pass
+ * \return false on memory exhaustion, true otherwise
+ */
+static bool
+imagemap_extract_map_entries(dom_node *node, html_content *c,
+ struct mapentry **entry, dom_string *tname)
+{
+ dom_nodelist *nlist;
+ dom_exception exc;
+ unsigned long ent;
+ uint32_t tag_count;
+
+ exc = dom_element_get_elements_by_tag_name(node, tname, &nlist);
+ if (exc != DOM_NO_ERR) {
+ return false;
+ }
+
+ exc = dom_nodelist_get_length(nlist, &tag_count);
+ if (exc != DOM_NO_ERR) {
+ dom_nodelist_unref(nlist);
+ return false;
+ }
+
+ for (ent = 0; ent < tag_count; ++ent) {
+ dom_node *subnode;
+
+ exc = dom_nodelist_item(nlist, ent, &subnode);
+ if (exc != DOM_NO_ERR) {
+ dom_nodelist_unref(nlist);
+ return false;
+ }
+ if (imagemap_addtolist(c, subnode, c->base_url,
+ entry, tname) == false) {
+ dom_node_unref(subnode);
+ dom_nodelist_unref(nlist);
+ return false;
+ }
+ dom_node_unref(subnode);
+ }
+
+ dom_nodelist_unref(nlist);
+
+ return true;
+}
+
+/**
+ * Extract an imagemap from html source
+ *
+ * \param node XML node containing map
+ * \param c Content containing document
+ * \param entry List of map entries
+ * \return false on memory exhaustion, true otherwise
+ */
+static bool imagemap_extract_map(dom_node *node, html_content *c,
+ struct mapentry **entry)
+{
+ if (imagemap_extract_map_entries(node, c, entry,
+ corestring_dom_area) == false)
+ return false;
+ return imagemap_extract_map_entries(node, c, entry,
+ corestring_dom_a);
+}
+
+/**
+ * Extract all imagemaps from a document tree
+ *
+ * \param c The content to extract imagemaps from.
+ * \return false on memory exhaustion, true otherwise
+ */
+nserror
+imagemap_extract(html_content *c)
+{
+ dom_nodelist *nlist;
+ dom_exception exc;
+ unsigned long mapnr;
+ uint32_t maybe_maps;
+ nserror ret = NSERROR_OK;
+
+ exc = dom_document_get_elements_by_tag_name(c->document,
+ corestring_dom_map,
+ &nlist);
+ if (exc != DOM_NO_ERR) {
+ return NSERROR_DOM;
+ }
+
+ exc = dom_nodelist_get_length(nlist, &maybe_maps);
+ if (exc != DOM_NO_ERR) {
+ ret = NSERROR_DOM;
+ goto out_nlist;
+ }
+
+ for (mapnr = 0; mapnr < maybe_maps; ++mapnr) {
+ dom_node *node;
+ dom_string *name;
+ exc = dom_nodelist_item(nlist, mapnr, &node);
+ if (exc != DOM_NO_ERR) {
+ ret = NSERROR_DOM;
+ goto out_nlist;
+ }
+
+ exc = dom_element_get_attribute(node, corestring_dom_id,
+ &name);
+ if (exc != DOM_NO_ERR) {
+ dom_node_unref(node);
+ ret = NSERROR_DOM;
+ goto out_nlist;
+ }
+
+ if (name == NULL) {
+ exc = dom_element_get_attribute(node,
+ corestring_dom_name,
+ &name);
+ if (exc != DOM_NO_ERR) {
+ dom_node_unref(node);
+ ret = NSERROR_DOM;
+ goto out_nlist;
+ }
+ }
+
+ if (name != NULL) {
+ struct mapentry *entry = NULL;
+ if (imagemap_extract_map(node, c, &entry) == false) {
+ if (entry != NULL) {
+ imagemap_freelist(entry);
+ }
+
+ dom_string_unref(name);
+ dom_node_unref(node);
+ ret = NSERROR_NOMEM; /** @todo check this */
+ goto out_nlist;
+ }
+
+ /* imagemap_extract_map may not extract anything,
+ * so entry can still be NULL here. This isn't an
+ * error as it just means that we've encountered
+ * an incorrectly defined <map>...</map> block
+ */
+ if ((entry != NULL) &&
+ (imagemap_add(c, name, entry) == false)) {
+ imagemap_freelist(entry);
+
+ dom_string_unref(name);
+ dom_node_unref(node);
+ ret = NSERROR_NOMEM; /** @todo check this */
+ goto out_nlist;
+ }
+ }
+
+ dom_string_unref(name);
+ dom_node_unref(node);
+ }
+
+out_nlist:
+
+ dom_nodelist_unref(nlist);
+
+ return ret;
+}
+
+/**
+ * Test if a point lies within an arbitrary polygon
+ * Modified from comp.graphics.algorithms FAQ 2.03
+ *
+ * \param num Number of vertices
+ * \param xpt Array of x coordinates
+ * \param ypt Array of y coordinates
+ * \param x Left hand edge of containing box
+ * \param y Top edge of containing box
+ * \param click_x X coordinate of click
+ * \param click_y Y coordinate of click
+ * \return 1 if point is in polygon, 0 if outside. 0 or 1 if on boundary
+ */
+static int
+imagemap_point_in_poly(int num, float *xpt, float *ypt, unsigned long x,
+ unsigned long y, unsigned long click_x, unsigned long click_y)
+{
+ int i, j, c = 0;
+
+ assert(xpt != NULL);
+ assert(ypt != NULL);
+
+ for (i = 0, j = num - 1; i < num; j = i++) {
+ if ((((ypt[i] + y <= click_y) && (click_y < ypt[j] + y)) ||
+ ((ypt[j] + y <= click_y) && (click_y < ypt[i] + y))) &&
+ (click_x < (xpt[j] - xpt[i]) *
+ (click_y - (ypt[i] + y)) / (ypt[j] - ypt[i]) + xpt[i] + x))
+ c = !c;
+ }
+
+ return c;
+}
+
+/**
+ * Retrieve url associated with imagemap entry
+ *
+ * \param c The containing content
+ * \param key The map name to search for
+ * \param x The left edge of the containing box
+ * \param y The top edge of the containing box
+ * \param click_x The horizontal location of the click
+ * \param click_y The vertical location of the click
+ * \param target Pointer to location to receive target pointer (if any)
+ * \return The url associated with this area, or NULL if not found
+ */
+nsurl *imagemap_get(struct html_content *c, const char *key,
+ unsigned long x, unsigned long y,
+ unsigned long click_x, unsigned long click_y,
+ const char **target)
+{
+ unsigned int slot = 0;
+ struct imagemap *map;
+ struct mapentry *entry;
+ unsigned long cx, cy;
+
+ assert(c != NULL);
+
+ if (key == NULL)
+ return NULL;
+
+ if (c->imagemaps == NULL)
+ return NULL;
+
+ slot = imagemap_hash(key);
+
+ for (map = c->imagemaps[slot]; map != NULL; map = map->next) {
+ if (map->key != NULL && strcasecmp(map->key, key) == 0)
+ break;
+ }
+
+ if (map == NULL || map->list == NULL)
+ return NULL;
+
+ for (entry = map->list; entry; entry = entry->next) {
+ switch (entry->type) {
+ case IMAGEMAP_DEFAULT:
+ /* just return the URL. no checks required */
+ if (target)
+ *target = entry->target;
+ return entry->url;
+ break;
+ case IMAGEMAP_RECT:
+ if (click_x >= x + entry->bounds.rect.x0 &&
+ click_x <= x + entry->bounds.rect.x1 &&
+ click_y >= y + entry->bounds.rect.y0 &&
+ click_y <= y + entry->bounds.rect.y1) {
+ if (target)
+ *target = entry->target;
+ return entry->url;
+ }
+ break;
+ case IMAGEMAP_CIRCLE:
+ cx = x + entry->bounds.circle.x - click_x;
+ cy = y + entry->bounds.circle.y - click_y;
+ if ((cx * cx + cy * cy) <=
+ (unsigned long) (entry->bounds.circle.r *
+ entry->bounds.circle.r)) {
+ if (target)
+ *target = entry->target;
+ return entry->url;
+ }
+ break;
+ case IMAGEMAP_POLY:
+ if (imagemap_point_in_poly(entry->bounds.poly.num,
+ entry->bounds.poly.xcoords,
+ entry->bounds.poly.ycoords, x, y,
+ click_x, click_y)) {
+ if (target)
+ *target = entry->target;
+ return entry->url;
+ }
+ break;
+ }
+ }
+
+ if (target)
+ *target = NULL;
+
+ return NULL;
+}
diff --git a/content/handlers/html/imagemap.h b/content/handlers/html/imagemap.h
new file mode 100644
index 0000000..8e189a0
--- /dev/null
+++ b/content/handlers/html/imagemap.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2004 John M Bell <jmb202(a)ecs.soton.ac.uk>
+ *
+ * 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
+ * Interface to HTML imagemap
+ */
+
+#ifndef NETSURF_HTML_IMAGEMAP_H
+#define NETSURF_HTML_IMAGEMAP_H
+
+#include <dom/dom.h>
+
+struct html_content;
+struct hlcache_handle;
+struct nsurl;
+
+void imagemap_destroy(struct html_content *c);
+void imagemap_dump(struct html_content *c);
+nserror imagemap_extract(struct html_content *c);
+
+struct nsurl *imagemap_get(struct html_content *c, const char *key,
+ unsigned long x, unsigned long y,
+ unsigned long click_x, unsigned long click_y,
+ const char **target);
+
+#endif
diff --git a/content/handlers/html/layout.c b/content/handlers/html/layout.c
new file mode 100644
index 0000000..6941d67
--- /dev/null
+++ b/content/handlers/html/layout.c
@@ -0,0 +1,5432 @@
+/*
+ * Copyright 2005 Richard Wilson <info(a)tinct.net>
+ * Copyright 2006 James Bursa <bursa(a)users.sourceforge.net>
+ * Copyright 2008 Michael Drake <tlsa(a)netsurf-browser.org>
+ * Copyright 2003 Phil Mellor <monkeyson(a)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
+ * HTML layout implementation.
+ *
+ * Layout is carried out in two stages:
+ *
+ * 1. + calculation of minimum / maximum box widths, and
+ * + determination of whether block level boxes will have >zero height
+ *
+ * 2. + layout (position and dimensions)
+ *
+ * In most cases the functions for the two stages are a corresponding pair
+ * layout_minmax_X() and layout_X().
+ */
+
+#include <assert.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <dom/dom.h>
+
+#include "utils/log.h"
+#include "utils/talloc.h"
+#include "utils/utils.h"
+#include "utils/nsoption.h"
+#include "netsurf/inttypes.h"
+#include "netsurf/content.h"
+#include "netsurf/browser_window.h"
+#include "netsurf/layout.h"
+#include "content/content_protected.h"
+#include "css/utils.h"
+#include "desktop/scrollbar.h"
+#include "desktop/textarea.h"
+
+#include "html/box.h"
+#include "html/font.h"
+#include "html/form_internal.h"
+#include "html/html_internal.h"
+#include "html/layout.h"
+#include "html/table.h"
+
+#define AUTO INT_MIN
+
+/* Fixed point percentage (a) of an integer (b), to an integer */
+#define FPCT_OF_INT_TOINT(a, b) (FIXTOINT(FDIV((a * b), F_100)))
+
+typedef uint8_t (*css_len_func)(
+ const css_computed_style *style,
+ css_fixed *length, css_unit *unit);
+typedef uint8_t (*css_border_style_func)(
+ const css_computed_style *style);
+typedef uint8_t (*css_border_color_func)(
+ const css_computed_style *style,
+ css_color *color);
+
+/** Array of per-side access functions for computed style margins. */
+static const css_len_func margin_funcs[4] = {
+ [TOP] = css_computed_margin_top,
+ [RIGHT] = css_computed_margin_right,
+ [BOTTOM] = css_computed_margin_bottom,
+ [LEFT] = css_computed_margin_left,
+};
+
+/** Array of per-side access functions for computed style paddings. */
+static const css_len_func padding_funcs[4] = {
+ [TOP] = css_computed_padding_top,
+ [RIGHT] = css_computed_padding_right,
+ [BOTTOM] = css_computed_padding_bottom,
+ [LEFT] = css_computed_padding_left,
+};
+
+/** Array of per-side access functions for computed style border_widths. */
+static const css_len_func border_width_funcs[4] = {
+ [TOP] = css_computed_border_top_width,
+ [RIGHT] = css_computed_border_right_width,
+ [BOTTOM] = css_computed_border_bottom_width,
+ [LEFT] = css_computed_border_left_width,
+};
+
+/** Array of per-side access functions for computed style border styles. */
+static const css_border_style_func border_style_funcs[4] = {
+ [TOP] = css_computed_border_top_style,
+ [RIGHT] = css_computed_border_right_style,
+ [BOTTOM] = css_computed_border_bottom_style,
+ [LEFT] = css_computed_border_left_style,
+};
+
+/** Array of per-side access functions for computed style border colors. */
+static const css_border_color_func border_color_funcs[4] = {
+ [TOP] = css_computed_border_top_color,
+ [RIGHT] = css_computed_border_right_color,
+ [BOTTOM] = css_computed_border_bottom_color,
+ [LEFT] = css_computed_border_left_color,
+};
+
+/* forward declaration to break cycles */
+static bool layout_block_context(
+ struct box *block,
+ int viewport_height,
+ html_content *content);
+static void layout_minmax_block(
+ struct box *block,
+ const struct gui_layout_table *font_func,
+ const html_content *content);
+
+
+/**
+ * Compute the size of replaced boxes with auto dimensions, according to
+ * content.
+ *
+ * \param box Box with object
+ * \param width Width value in px or AUTO. If AUTO, updated to value in px.
+ * \param height Height value in px or AUTO. If AUTO, updated to value in px.
+ * \param min_width Box's min width, as given by layout_find_dimensions.
+ * \param max_width Box's max width, as given by layout_find_dimensions.
+ * \param min_height Box's min height, as given by layout_find_dimensions.
+ * \param max_height Box's max height, as given by layout_find_dimensions.
+ *
+ * See CSS 2.1 sections 10.3 and 10.6.
+ */
+static void
+layout_get_object_dimensions(struct box *box,
+ int *width, int *height,
+ int min_width, int max_width,
+ int min_height, int max_height)
+{
+ assert(box->object != NULL);
+ assert(width != NULL && height != NULL);
+
+ if (*width == AUTO && *height == AUTO) {
+ /* No given dimensions */
+
+ bool scaled = false;
+ int intrinsic_width = content_get_width(box->object);
+ int intrinsic_height = content_get_height(box->object);
+
+ /* use intrinsic dimensions */
+ *width = intrinsic_width;
+ *height = intrinsic_height;
+
+ /* Deal with min/max-width first */
+ if (min_width > 0 && min_width > *width) {
+ *width = min_width;
+ scaled = true;
+ }
+ if (max_width >= 0 && max_width < *width) {
+ *width = max_width;
+ scaled = true;
+ }
+
+ if (scaled && (intrinsic_width != 0)) {
+ /* Update height */
+ *height = (*width * intrinsic_height) /
+ intrinsic_width;
+ }
+
+ scaled = false;
+ /* Deal with min/max-height */
+ if (min_height > 0 && min_height > *height) {
+ *height = min_height;
+ scaled = true;
+ }
+ if (max_height >= 0 && max_height < *height) {
+ *height = max_height;
+ scaled = true;
+ }
+
+ if (scaled && (intrinsic_height != 0)) {
+ /* Update width */
+ *width = (*height * intrinsic_width) /
+ intrinsic_height;
+ }
+
+ } else if (*width == AUTO) {
+ /* Have given height; width is calculated from the given height
+ * and ratio of intrinsic dimensions */
+ int intrinsic_width = content_get_width(box->object);
+ int intrinsic_height = content_get_height(box->object);
+
+ if (intrinsic_height != 0)
+ *width = (*height * intrinsic_width) /
+ intrinsic_height;
+ else
+ *width = intrinsic_width;
+
+ if (min_width > 0 && min_width > *width)
+ *width = min_width;
+ if (max_width >= 0 && max_width < *width)
+ *width = max_width;
+
+ } else if (*height == AUTO) {
+ /* Have given width; height is calculated from the given width
+ * and ratio of intrinsic dimensions */
+ int intrinsic_width = content_get_width(box->object);
+ int intrinsic_height = content_get_height(box->object);
+
+ if (intrinsic_width != 0)
+ *height = (*width * intrinsic_height) /
+ intrinsic_width;
+ else
+ *height = intrinsic_height;
+ }
+}
+
+
+/**
+ * Calculate the text-indent length.
+ *
+ * \param style style of block
+ * \param width width of containing block
+ * \return length of indent
+ */
+static int layout_text_indent(
+ const nscss_len_ctx *len_ctx,
+ const css_computed_style *style, int width)
+{
+ css_fixed value = 0;
+ css_unit unit = CSS_UNIT_PX;
+
+ css_computed_text_indent(style, &value, &unit);
+
+ if (unit == CSS_UNIT_PCT) {
+ return FPCT_OF_INT_TOINT(value, width);
+ } else {
+ return FIXTOINT(nscss_len2px(len_ctx, value, unit, style));
+ }
+}
+
+
+/**
+ * Determine width of margin, borders, and padding on one side of a box.
+ *
+ * \param len_ctx CSS length conversion context for document
+ * \param style style to measure
+ * \param side side of box to measure
+ * \param margin whether margin width is required
+ * \param border whether border width is required
+ * \param padding whether padding width is required
+ * \param fixed increased by sum of fixed margin, border, and padding
+ * \param frac increased by sum of fractional margin and padding
+ */
+static void
+calculate_mbp_width(const nscss_len_ctx *len_ctx,
+ const css_computed_style *style,
+ unsigned int side,
+ bool margin,
+ bool border,
+ bool padding,
+ int *fixed,
+ float *frac)
+{
+ css_fixed value = 0;
+ css_unit unit = CSS_UNIT_PX;
+
+ assert(style);
+
+ /* margin */
+ if (margin) {
+ enum css_margin_e type;
+
+ type = margin_funcs[side](style, &value, &unit);
+ if (type == CSS_MARGIN_SET) {
+ if (unit == CSS_UNIT_PCT) {
+ *frac += FIXTOINT(FDIV(value, F_100));
+ } else {
+ *fixed += FIXTOINT(nscss_len2px(len_ctx,
+ value, unit, style));
+ }
+ }
+ }
+
+ /* border */
+ if (border) {
+ if (border_style_funcs[side](style) !=
+ CSS_BORDER_STYLE_NONE) {
+ border_width_funcs[side](style, &value, &unit);
+
+ *fixed += FIXTOINT(nscss_len2px(len_ctx,
+ value, unit, style));
+ }
+ }
+
+ /* padding */
+ if (padding) {
+ padding_funcs[side](style, &value, &unit);
+ if (unit == CSS_UNIT_PCT) {
+ *frac += FIXTOINT(FDIV(value, F_100));
+ } else {
+ *fixed += FIXTOINT(nscss_len2px(len_ctx,
+ value, unit, style));
+ }
+ }
+}
+
+
+/**
+ * Calculate minimum and maximum width of a table.
+ *
+ * \param table box of type TABLE
+ * \param font_func Font functions
+ * \param content The HTML content we are laying out.
+ * \post table->min_width and table->max_width filled in,
+ * 0 <= table->min_width <= table->max_width
+ */
+static void layout_minmax_table(struct box *table,
+ const struct gui_layout_table *font_func,
+ const html_content *content)
+{
+ unsigned int i, j;
+ int border_spacing_h = 0;
+ int table_min = 0, table_max = 0;
+ int extra_fixed = 0;
+ float extra_frac = 0;
+ struct column *col;
+ struct box *row_group, *row, *cell;
+ enum css_width_e wtype;
+ css_fixed value = 0;
+ css_unit unit = CSS_UNIT_PX;
+
+ /* check if the widths have already been calculated */
+ if (table->max_width != UNKNOWN_MAX_WIDTH)
+ return;
+
+ if (table_calculate_column_types(&content->len_ctx, table) == false) {
+ NSLOG(netsurf, WARNING,
+ "Could not establish table column types.");
+ return;
+ }
+ col = table->col;
+
+ /* start with 0 except for fixed-width columns */
+ for (i = 0; i != table->columns; i++) {
+ if (col[i].type == COLUMN_WIDTH_FIXED)
+ col[i].min = col[i].max = col[i].width;
+ else
+ col[i].min = col[i].max = 0;
+ }
+
+ /* border-spacing is used in the separated borders model */
+ if (css_computed_border_collapse(table->style) ==
+ CSS_BORDER_COLLAPSE_SEPARATE) {
+ css_fixed h = 0, v = 0;
+ css_unit hu = CSS_UNIT_PX, vu = CSS_UNIT_PX;
+
+ css_computed_border_spacing(table->style, &h, &hu, &v, &vu);
+
+ border_spacing_h = FIXTOINT(nscss_len2px(&content->len_ctx,
+ h, hu, table->style));
+ }
+
+ /* 1st pass: consider cells with colspan 1 only */
+ for (row_group = table->children; row_group; row_group =row_group->next)
+ for (row = row_group->children; row; row = row->next)
+ for (cell = row->children; cell; cell = cell->next) {
+ assert(cell->type == BOX_TABLE_CELL);
+ assert(cell->style);
+ /** TODO: Handle colspan="0" correctly.
+ * It's currently converted to 1 in box normaisation */
+ assert(cell->columns != 0);
+
+ if (cell->columns != 1)
+ continue;
+
+ layout_minmax_block(cell, font_func, content);
+ i = cell->start_column;
+
+ if (col[i].positioned)
+ continue;
+
+ /* update column min, max widths using cell widths */
+ if (col[i].min < cell->min_width)
+ col[i].min = cell->min_width;
+ if (col[i].max < cell->max_width)
+ col[i].max = cell->max_width;
+ }
+
+ /* 2nd pass: cells which span multiple columns */
+ for (row_group = table->children; row_group; row_group =row_group->next)
+ for (row = row_group->children; row; row = row->next)
+ for (cell = row->children; cell; cell = cell->next) {
+ unsigned int flexible_columns = 0;
+ int min = 0, max = 0, fixed_width = 0, extra;
+
+ if (cell->columns == 1)
+ continue;
+
+ layout_minmax_block(cell, font_func, content);
+ i = cell->start_column;
+
+ /* find min width so far of spanned columns, and count
+ * number of non-fixed spanned columns and total fixed width */
+ for (j = 0; j != cell->columns; j++) {
+ min += col[i + j].min;
+ if (col[i + j].type == COLUMN_WIDTH_FIXED)
+ fixed_width += col[i + j].width;
+ else
+ flexible_columns++;
+ }
+ min += (cell->columns - 1) * border_spacing_h;
+
+ /* distribute extra min to spanned columns */
+ if (min < cell->min_width) {
+ if (flexible_columns == 0) {
+ extra = 1 + (cell->min_width - min) /
+ cell->columns;
+ for (j = 0; j != cell->columns; j++) {
+ col[i + j].min += extra;
+ if (col[i + j].max < col[i + j].min)
+ col[i + j].max = col[i + j].min;
+ }
+ } else {
+ extra = 1 + (cell->min_width - min) /
+ flexible_columns;
+ for (j = 0; j != cell->columns; j++) {
+ if (col[i + j].type !=
+ COLUMN_WIDTH_FIXED) {
+ col[i + j].min += extra;
+ if (col[i + j].max <
+ col[i + j].min)
+ col[i + j].max =
+ col[i + j].min;
+ }
+ }
+ }
+ }
+
+ /* find max width so far of spanned columns */
+ for (j = 0; j != cell->columns; j++)
+ max += col[i + j].max;
+ max += (cell->columns - 1) * border_spacing_h;
+
+ /* distribute extra max to spanned columns */
+ if (max < cell->max_width && flexible_columns) {
+ extra = 1 + (cell->max_width - max) / flexible_columns;
+ for (j = 0; j != cell->columns; j++)
+ if (col[i + j].type != COLUMN_WIDTH_FIXED)
+ col[i + j].max += extra;
+ }
+ }
+
+ for (i = 0; i != table->columns; i++) {
+ if (col[i].max < col[i].min) {
+ box_dump(stderr, table, 0, true);
+ assert(0);
+ }
+ table_min += col[i].min;
+ table_max += col[i].max;
+ }
+
+ /* fixed width takes priority, unless it is too narrow */
+ wtype = css_computed_width(table->style, &value, &unit);
+ if (wtype == CSS_WIDTH_SET && unit != CSS_UNIT_PCT) {
+ int width = FIXTOINT(nscss_len2px(&content->len_ctx,
+ value, unit, table->style));
+ if (table_min < width)
+ table_min = width;
+ if (table_max < width)
+ table_max = width;
+ }
+
+ /* add margins, border, padding to min, max widths */
+ calculate_mbp_width(&content->len_ctx,
+ table->style, LEFT, true, true, true,
+ &extra_fixed, &extra_frac);
+ calculate_mbp_width(&content->len_ctx,
+ table->style, RIGHT, true, true, true,
+ &extra_fixed, &extra_frac);
+ if (extra_fixed < 0)
+ extra_fixed = 0;
+ if (extra_frac < 0)
+ extra_frac = 0;
+ if (1.0 <= extra_frac)
+ extra_frac = 0.9;
+ table->min_width = (table_min + extra_fixed) / (1.0 - extra_frac);
+ table->max_width = (table_max + extra_fixed) / (1.0 - extra_frac);
+ table->min_width += (table->columns + 1) * border_spacing_h;
+ table->max_width += (table->columns + 1) * border_spacing_h;
+
+ assert(0 <= table->min_width && table->min_width <= table->max_width);
+}
+
+/**
+ * Helper to check if a box has percentage max width.
+ *
+ * \param[in] b Box to check.
+ * \return true iff box has percnetage max width.
+ */
+static inline bool box_has_percentage_max_width(struct box *b)
+{
+ css_unit unit = CSS_UNIT_PX;
+ enum css_max_width_e type;
+ css_fixed value = 0;
+
+ assert(b != NULL);
+
+ type = css_computed_max_width(b->style, &value, &unit);
+ return ((type == CSS_MAX_WIDTH_SET) && (unit == CSS_UNIT_PCT));
+}
+
+/**
+ * Calculate minimum and maximum width of a line.
+ *
+ * \param first a box in an inline container
+ * \param line_min updated to minimum width of line starting at first
+ * \param line_max updated to maximum width of line starting at first
+ * \param first_line true iff this is the first line in the inline container
+ * \param line_has_height updated to true or false, depending on line
+ * \param font_func Font functions.
+ * \return first box in next line, or 0 if no more lines
+ * \post 0 <= *line_min <= *line_max
+ */
+static struct box *
+layout_minmax_line(struct box *first,
+ int *line_min,
+ int *line_max,
+ bool first_line,
+ bool *line_has_height,
+ const struct gui_layout_table *font_func,
+ const html_content *content)
+{
+ int min = 0, max = 0, width, height, fixed;
+ float frac;
+ size_t i, j;
+ struct box *b;
+ struct box *block;
+ plot_font_style_t fstyle;
+ bool no_wrap;
+
+ assert(first->parent);
+ assert(first->parent->parent);
+ assert(first->parent->parent->style);
+
+ block = first->parent->parent;
+ no_wrap = (css_computed_white_space(block->style) ==
+ CSS_WHITE_SPACE_NOWRAP ||
+ css_computed_white_space(block->style) ==
+ CSS_WHITE_SPACE_PRE);
+
+ *line_has_height = false;
+
+ /* corresponds to the pass 1 loop in layout_line() */
+ for (b = first; b; b = b->next) {
+ enum css_width_e wtype;
+ enum css_height_e htype;
+ enum css_box_sizing_e bs;
+ css_fixed value = 0;
+ css_unit unit = CSS_UNIT_PX;
+
+ assert(b->type == BOX_INLINE || b->type == BOX_INLINE_BLOCK ||
+ b->type == BOX_FLOAT_LEFT ||
+ b->type == BOX_FLOAT_RIGHT ||
+ b->type == BOX_BR || b->type == BOX_TEXT ||
+ b->type == BOX_INLINE_END);
+
+ NSLOG(layout, DEBUG, "%p: min %i, max %i", b, min, max);
+
+ if (b->type == BOX_BR) {
+ b = b->next;
+ break;
+ }
+
+ if (b->type == BOX_FLOAT_LEFT || b->type == BOX_FLOAT_RIGHT) {
+ assert(b->children);
+ if (b->children->type == BOX_BLOCK)
+ layout_minmax_block(b->children, font_func,
+ content);
+ else
+ layout_minmax_table(b->children, font_func,
+ content);
+ b->min_width = b->children->min_width;
+ b->max_width = b->children->max_width;
+ if (min < b->min_width)
+ min = b->min_width;
+ max += b->max_width;
+ continue;
+ }
+
+ if (b->type == BOX_INLINE_BLOCK) {
+ layout_minmax_block(b, font_func, content);
+ if (min < b->min_width)
+ min = b->min_width;
+ max += b->max_width;
+
+ if (b->flags & HAS_HEIGHT)
+ *line_has_height = true;
+ continue;
+ }
+
+ assert(b->style);
+ font_plot_style_from_css(&content->len_ctx, b->style, &fstyle);
+
+ if (b->type == BOX_INLINE && !b->object &&
+ !(b->flags & REPLACE_DIM) &&
+ !(b->flags & IFRAME)) {
+ fixed = frac = 0;
+ calculate_mbp_width(&content->len_ctx,
+ b->style, LEFT, true, true, true,
+ &fixed, &frac);
+ if (!b->inline_end)
+ calculate_mbp_width(&content->len_ctx,
+ b->style, RIGHT,
+ true, true, true,
+ &fixed, &frac);
+ if (0 < fixed)
+ max += fixed;
+ *line_has_height = true;
+ /* \todo update min width, consider fractional extra */
+ } else if (b->type == BOX_INLINE_END) {
+ fixed = frac = 0;
+ calculate_mbp_width(&content->len_ctx,
+ b->inline_end->style, RIGHT,
+ true, true, true,
+ &fixed, &frac);
+ if (0 < fixed)
+ max += fixed;
+
+ if (b->next) {
+ if (b->space == UNKNOWN_WIDTH) {
+ font_func->width(&fstyle, " ", 1,
+ &b->space);
+ }
+ max += b->space;
+ }
+
+ *line_has_height = true;
+ continue;
+ }
+
+ if (!b->object && !(b->flags & IFRAME) && !b->gadget &&
+ !(b->flags & REPLACE_DIM)) {
+ /* inline non-replaced, 10.3.1 and 10.6.1 */
+ bool no_wrap_box;
+ if (!b->text)
+ continue;
+
+ no_wrap_box = (css_computed_white_space(b->style) ==
+ CSS_WHITE_SPACE_NOWRAP ||
+ css_computed_white_space(b->style) ==
+ CSS_WHITE_SPACE_PRE);
+
+ if (b->width == UNKNOWN_WIDTH) {
+ /** \todo handle errors */
+
+ /* If it's a select element, we must use the
+ * width of the widest option text */
+ if (b->parent->parent->gadget &&
+ b->parent->parent->gadget->type
+ == GADGET_SELECT) {
+ int opt_maxwidth = 0;
+ struct form_option *o;
+
+ for (o = b->parent->parent->gadget->
+ data.select.items; o;
+ o = o->next) {
+ int opt_width;
+ font_func->width(&fstyle,
+ o->text,
+ strlen(o->text),
+ &opt_width);
+
+ if (opt_maxwidth < opt_width)
+ opt_maxwidth =opt_width;
+ }
+
+ b->width = opt_maxwidth;
+ if (nsoption_bool(core_select_menu))
+ b->width += SCROLLBAR_WIDTH;
+
+ } else {
+ font_func->width(&fstyle, b->text,
+ b->length, &b->width);
+ b->flags |= MEASURED;
+ }
+ }
+ max += b->width;
+ if (b->next) {
+ if (b->space == UNKNOWN_WIDTH) {
+ font_func->width(&fstyle, " ", 1,
+ &b->space);
+ }
+ max += b->space;
+ }
+
+ if (no_wrap) {
+ /* Don't wrap due to block style,
+ * so min is the same as max */
+ min = max;
+
+ } else if (no_wrap_box) {
+ /* This inline box can't be wrapped,
+ * for min, consider box's width */
+ if (min < b->width)
+ min = b->width;
+
+ } else if (b->parent->flags & NEED_MIN) {
+ /* If we care what the minimum width is,
+ * calculate it. (It's only needed if we're
+ * shrinking-to-fit.) */
+ /* min = widest single word */
+ i = 0;
+ do {
+ for (j = i; j != b->length &&
+ b->text[j] != ' '; j++)
+ ;
+ font_func->width(&fstyle, b->text + i,
+ j - i, &width);
+ if (min < width)
+ min = width;
+ i = j + 1;
+ } while (j != b->length);
+ }
+
+ *line_has_height = true;
+
+ continue;
+ }
+
+ /* inline replaced, 10.3.2 and 10.6.2 */
+ assert(b->style);
+
+ /* calculate box width */
+ wtype = css_computed_width(b->style, &value, &unit);
+ bs = css_computed_box_sizing(block->style);
+ if (wtype == CSS_WIDTH_SET) {
+ if (unit == CSS_UNIT_PCT) {
+ width = AUTO;
+ } else {
+ width = FIXTOINT(nscss_len2px(&content->len_ctx,
+ value, unit, b->style));
+
+ if (bs == CSS_BOX_SIZING_BORDER_BOX) {
+ fixed = frac = 0;
+ calculate_mbp_width(&content->len_ctx,
+ block->style, LEFT,
+ false, true, true,
+ &fixed, &frac);
+ calculate_mbp_width(&content->len_ctx,
+ block->style, RIGHT,
+ false, true, true,
+ &fixed, &frac);
+ if (width < fixed) {
+ width = fixed;
+ }
+ }
+ if (width < 0)
+ width = 0;
+ }
+ } else {
+ width = AUTO;
+ }
+
+ /* height */
+ htype = css_computed_height(b->style, &value, &unit);
+ if (htype == CSS_HEIGHT_SET) {
+ height = FIXTOINT(nscss_len2px(&content->len_ctx,
+ value, unit, b->style));
+ } else {
+ height = AUTO;
+ }
+
+ if (b->object || (b->flags & REPLACE_DIM)) {
+ if (b->object) {
+ int temp_height = height;
+ layout_get_object_dimensions(b,
+ &width, &temp_height,
+ INT_MIN, INT_MAX,
+ INT_MIN, INT_MAX);
+ }
+
+ fixed = frac = 0;
+ if (bs == CSS_BOX_SIZING_BORDER_BOX) {
+ calculate_mbp_width(&content->len_ctx,
+ b->style, LEFT,
+ true, false, false,
+ &fixed, &frac);
+ calculate_mbp_width(&content->len_ctx,
+ b->style, RIGHT,
+ true, false, false,
+ &fixed, &frac);
+ } else {
+ calculate_mbp_width(&content->len_ctx,
+ b->style, LEFT,
+ true, true, true,
+ &fixed, &frac);
+ calculate_mbp_width(&content->len_ctx,
+ b->style, RIGHT,
+ true, true, true,
+ &fixed, &frac);
+ }
+ if (0 < width + fixed)
+ width += fixed;
+ } else if (b->flags & IFRAME) {
+ /* TODO: handle percentage widths properly */
+ if (width == AUTO)
+ width = 400;
+
+ fixed = frac = 0;
+ if (bs == CSS_BOX_SIZING_BORDER_BOX) {
+ calculate_mbp_width(&content->len_ctx,
+ b->style, LEFT,
+ true, false, false,
+ &fixed, &frac);
+ calculate_mbp_width(&content->len_ctx,
+ b->style, RIGHT,
+ true, false, false,
+ &fixed, &frac);
+ } else {
+ calculate_mbp_width(&content->len_ctx,
+ b->style, LEFT,
+ true, true, true,
+ &fixed, &frac);
+ calculate_mbp_width(&content->len_ctx,
+ b->style, RIGHT,
+ true, true, true,
+ &fixed, &frac);
+ }
+
+ if (0 < width + fixed)
+ width += fixed;
+
+ } else {
+ /* form control with no object */
+ if (width == AUTO)
+ width = FIXTOINT(nscss_len2px(
+ &content->len_ctx,
+ INTTOFIX(1), CSS_UNIT_EM,
+ b->style));
+ }
+
+ if (min < width && !box_has_percentage_max_width(b))
+ min = width;
+ if (width > 0)
+ max += width;
+
+ *line_has_height = true;
+ }
+
+ if (first_line) {
+ /* todo: handle percentage values properly */
+ /* todo: handle text-indent interaction with floats */
+ int text_indent = layout_text_indent(&content->len_ctx,
+ first->parent->parent->style, 100);
+ min = (min + text_indent < 0) ? 0 : min + text_indent;
+ max = (max + text_indent < 0) ? 0 : max + text_indent;
+ }
+
+ *line_min = min;
+ *line_max = max;
+
+ NSLOG(layout, DEBUG, "line_min %i, line_max %i", min, max);
+
+ assert(b != first);
+ assert(0 <= *line_min);
+ assert(*line_min <= *line_max);
+ return b;
+}
+
+
+/**
+ * Calculate minimum and maximum width of an inline container.
+ *
+ * \param inline_container box of type INLINE_CONTAINER
+ * \param[out] has_height set to true if container has height
+ * \param font_func Font functions.
+ * \post inline_container->min_width and inline_container->max_width filled in,
+ * 0 <= inline_container->min_width <= inline_container->max_width
+ */
+static void
+layout_minmax_inline_container(struct box *inline_container,
+ bool *has_height,
+ const struct gui_layout_table *font_func,
+ const html_content *content)
+{
+ struct box *child;
+ int line_min = 0, line_max = 0;
+ int min = 0, max = 0;
+ bool first_line = true;
+ bool line_has_height;
+
+ assert(inline_container->type == BOX_INLINE_CONTAINER);
+
+ /* check if the widths have already been calculated */
+ if (inline_container->max_width != UNKNOWN_MAX_WIDTH)
+ return;
+
+ *has_height = false;
+
+ for (child = inline_container->children; child; ) {
+ child = layout_minmax_line(child, &line_min, &line_max,
+ first_line, &line_has_height, font_func,
+ content);
+ if (min < line_min)
+ min = line_min;
+ if (max < line_max)
+ max = line_max;
+ first_line = false;
+ *has_height |= line_has_height;
+ }
+
+ inline_container->min_width = min;
+ inline_container->max_width = max;
+
+ assert(0 <= inline_container->min_width &&
+ inline_container->min_width <=
+ inline_container->max_width);
+}
+
+
+/**
+ * Calculate minimum and maximum width of a block.
+ *
+ * \param block box of type BLOCK, INLINE_BLOCK, or TABLE_CELL
+ * \param font_func font functions
+ * \param content The HTML content being layed out.
+ * \post block->min_width and block->max_width filled in,
+ * 0 <= block->min_width <= block->max_width
+ */
+static void layout_minmax_block(
+ struct box *block,
+ const struct gui_layout_table *font_func,
+ const html_content *content)
+{
+ struct box *child;
+ int min = 0, max = 0;
+ int extra_fixed = 0;
+ float extra_frac = 0;
+ enum css_width_e wtype = CSS_WIDTH_AUTO;
+ css_fixed width = 0;
+ css_unit wunit = CSS_UNIT_PX;
+ enum css_height_e htype = CSS_HEIGHT_AUTO;
+ css_fixed height = 0;
+ css_unit hunit = CSS_UNIT_PX;
+ enum css_box_sizing_e bs = CSS_BOX_SIZING_CONTENT_BOX;
+ bool child_has_height = false;
+
+ assert(block->type == BOX_BLOCK ||
+ block->type == BOX_INLINE_BLOCK ||
+ block->type == BOX_TABLE_CELL);
+
+ /* check if the widths have already been calculated */
+ if (block->max_width != UNKNOWN_MAX_WIDTH)
+ return;
+
+ if (block->style != NULL) {
+ wtype = css_computed_width(block->style, &width, &wunit);
+ htype = css_computed_height(block->style, &height, &hunit);
+ bs = css_computed_box_sizing(block->style);
+ }
+
+ /* set whether the minimum width is of any interest for this box */
+ if (((block->parent && (block->parent->type == BOX_FLOAT_LEFT ||
+ block->parent->type == BOX_FLOAT_RIGHT)) ||
+ block->type == BOX_INLINE_BLOCK) &&
+ wtype != CSS_WIDTH_SET) {
+ /* box shrinks to fit; need minimum width */
+ block->flags |= NEED_MIN;
+ } else if (block->type == BOX_TABLE_CELL) {
+ /* box shrinks to fit; need minimum width */
+ block->flags |= NEED_MIN;
+ } else if (block->parent && (block->parent->flags & NEED_MIN) &&
+ wtype != CSS_WIDTH_SET) {
+ /* box inside shrink-to-fit context; need minimum width */
+ block->flags |= NEED_MIN;
+ }
+
+ if (block->gadget && (block->gadget->type == GADGET_TEXTBOX ||
+ block->gadget->type == GADGET_PASSWORD ||
+ block->gadget->type == GADGET_FILE ||
+ block->gadget->type == GADGET_TEXTAREA) &&
+ block->style && wtype == CSS_WIDTH_AUTO) {
+ css_fixed size = INTTOFIX(10);
+ css_unit unit = CSS_UNIT_EM;
+
+ min = max = FIXTOINT(nscss_len2px(&content->len_ctx,
+ size, unit, block->style));
+
+ block->flags |= HAS_HEIGHT;
+ }
+
+ if (block->gadget && (block->gadget->type == GADGET_RADIO ||
+ block->gadget->type == GADGET_CHECKBOX) &&
+ block->style && wtype == CSS_WIDTH_AUTO) {
+ css_fixed size = INTTOFIX(1);
+ css_unit unit = CSS_UNIT_EM;
+
+ /* form checkbox or radio button
+ * if width is AUTO, set it to 1em */
+ min = max = FIXTOINT(nscss_len2px(&content->len_ctx,
+ size, unit, block->style));
+
+ block->flags |= HAS_HEIGHT;
+ }
+
+ if (block->object) {
+ if (content_get_type(block->object) == CONTENT_HTML) {
+ layout_minmax_block(html_get_box_tree(block->object),
+ font_func, content);
+ min = html_get_box_tree(block->object)->min_width;
+ max = html_get_box_tree(block->object)->max_width;
+ } else {
+ min = max = content_get_width(block->object);
+ }
+
+ block->flags |= HAS_HEIGHT;
+ } else if (block->flags & IFRAME) {
+ /** \todo do we need to know the min/max width of the iframe's
+ * content? */
+ block->flags |= HAS_HEIGHT;
+ } else {
+ /* recurse through children */
+ for (child = block->children; child; child = child->next) {
+ switch (child->type) {
+ case BOX_BLOCK:
+ layout_minmax_block(child, font_func,
+ content);
+ if (child->flags & HAS_HEIGHT)
+ child_has_height = true;
+ break;
+ case BOX_INLINE_CONTAINER:
+ if (block->flags & NEED_MIN)
+ child->flags |= NEED_MIN;
+
+ layout_minmax_inline_container(child,
+ &child_has_height, font_func,
+ content);
+ if (child_has_height &&
+ child ==
+ child->parent->children) {
+ block->flags |= MAKE_HEIGHT;
+ }
+ break;
+ case BOX_TABLE:
+ layout_minmax_table(child, font_func,
+ content);
+ /* todo: fix for zero height tables */
+ child_has_height = true;
+ child->flags |= MAKE_HEIGHT;
+ break;
+ default:
+ assert(0);
+ }
+ assert(child->max_width != UNKNOWN_MAX_WIDTH);
+
+ if (child->style &&
+ (css_computed_position(child->style) ==
+ CSS_POSITION_ABSOLUTE ||
+ css_computed_position(child->style) ==
+ CSS_POSITION_FIXED)) {
+ /* This child is positioned out of normal flow,
+ * so it will have no affect on width */
+ continue;
+ }
+
+ if (min < child->min_width)
+ min = child->min_width;
+ if (max < child->max_width)
+ max = child->max_width;
+
+ if (child_has_height)
+ block->flags |= HAS_HEIGHT;
+ }
+ }
+
+ if (max < min) {
+ box_dump(stderr, block, 0, true);
+ assert(0);
+ }
+
+ /* fixed width takes priority */
+ if (block->type != BOX_TABLE_CELL && wtype == CSS_WIDTH_SET &&
+ wunit != CSS_UNIT_PCT) {
+ min = max = FIXTOINT(nscss_len2px(&content->len_ctx,
+ width, wunit, block->style));
+ if (bs == CSS_BOX_SIZING_BORDER_BOX) {
+ int border_box_fixed = 0;
+ float border_box_frac = 0;
+ calculate_mbp_width(&content->len_ctx,
+ block->style, LEFT,
+ false, true, true,
+ &border_box_fixed, &border_box_frac);
+ calculate_mbp_width(&content->len_ctx,
+ block->style, RIGHT,
+ false, true, true,
+ &border_box_fixed, &border_box_frac);
+ if (min < border_box_fixed) {
+ min = max = border_box_fixed;
+ }
+ }
+ }
+
+ if (htype == CSS_HEIGHT_SET && hunit != CSS_UNIT_PCT &&
+ height > INTTOFIX(0)) {
+ block->flags |= MAKE_HEIGHT;
+ block->flags |= HAS_HEIGHT;
+ }
+
+ /* add margins, border, padding to min, max widths */
+ /* Note: we don't know available width here so percentage margin
+ * and paddings are wrong. */
+ if (bs == CSS_BOX_SIZING_BORDER_BOX && wtype == CSS_WIDTH_SET) {
+ /* Border and padding included in width, so just get margin */
+ calculate_mbp_width(&content->len_ctx,
+ block->style, LEFT, true, false, false,
+ &extra_fixed, &extra_frac);
+ calculate_mbp_width(&content->len_ctx,
+ block->style, RIGHT, true, false, false,
+ &extra_fixed, &extra_frac);
+ } else {
+ calculate_mbp_width(&content->len_ctx,
+ block->style, LEFT, true, true, true,
+ &extra_fixed, &extra_frac);
+ calculate_mbp_width(&content->len_ctx,
+ block->style, RIGHT, true, true, true,
+ &extra_fixed, &extra_frac);
+ }
+ if (extra_fixed < 0)
+ extra_fixed = 0;
+ if (extra_frac < 0)
+ extra_frac = 0;
+ if (1.0 <= extra_frac)
+ extra_frac = 0.9;
+ if (block->style != NULL &&
+ (css_computed_float(block->style) == CSS_FLOAT_LEFT ||
+ css_computed_float(block->style) == CSS_FLOAT_RIGHT)) {
+ /* floated boxs */
+ block->min_width = min + extra_fixed;
+ block->max_width = max + extra_fixed;
+ } else {
+ /* not floated */
+ block->min_width = (min + extra_fixed) / (1.0 - extra_frac);
+ block->max_width = (max + extra_fixed) / (1.0 - extra_frac);
+ }
+
+ assert(0 <= block->min_width && block->min_width <= block->max_width);
+}
+
+
+/**
+ * Adjust a specified width or height for the box-sizing property.
+ *
+ * This turns the specified dimension into a content-box dimension.
+ *
+ * \param len_ctx Length conversion context
+ * \param box gadget to adjust dimensions of
+ * \param available_width width of containing block
+ * \param setwidth set true if the dimension to be tweaked is a width,
+ * else set false for a height
+ * \param dimension current value for given width/height dimension.
+ * updated to new value after consideration of
+ * gadget properties.
+ */
+static void layout_handle_box_sizing(
+ const nscss_len_ctx *len_ctx,
+ struct box *box,
+ int available_width,
+ bool setwidth,
+ int *dimension)
+{
+ enum css_box_sizing_e bs;
+
+ assert(box && box->style);
+
+ bs = css_computed_box_sizing(box->style);
+
+ if (bs == CSS_BOX_SIZING_BORDER_BOX) {
+ int orig = *dimension;
+ int fixed = 0;
+ float frac = 0;
+
+ calculate_mbp_width(len_ctx, box->style,
+ setwidth ? LEFT : TOP,
+ false, true, true, &fixed, &frac);
+ calculate_mbp_width(len_ctx, box->style,
+ setwidth ? RIGHT : BOTTOM,
+ false, true, true, &fixed, &frac);
+ orig -= frac * available_width + fixed;
+ *dimension = orig > 0 ? orig : 0;
+ }
+}
+
+
+/**
+ * Calculate width, height, and thickness of margins, paddings, and borders.
+ *
+ * \param len_ctx Length conversion context
+ * \param available_width width of containing block
+ * \param viewport_height height of viewport in pixels or -ve if unknown
+ * \param box current box
+ * \param style style giving width, height, margins, paddings,
+ * and borders
+ * \param width updated to width, may be NULL
+ * \param height updated to height, may be NULL
+ * \param max_width updated to max-width, may be NULL
+ * \param min_width updated to min-width, may be NULL
+ * \param max_height updated to max-height, may be NULL
+ * \param min_height updated to min-height, may be NULL
+ * \param margin filled with margins, may be NULL
+ * \param padding filled with paddings, may be NULL
+ * \param border filled with border widths, may be NULL
+ */
+static void
+layout_find_dimensions(const nscss_len_ctx *len_ctx,
+ int available_width,
+ int viewport_height,
+ struct box *box,
+ const css_computed_style *style,
+ int *width,
+ int *height,
+ int *max_width,
+ int *min_width,
+ int *max_height,
+ int *min_height,
+ int margin[4],
+ int padding[4],
+ struct box_border border[4])
+{
+ struct box *containing_block = NULL;
+ unsigned int i;
+
+ if (width) {
+ enum css_width_e wtype;
+ css_fixed value = 0;
+ css_unit unit = CSS_UNIT_PX;
+
+ wtype = css_computed_width(style, &value, &unit);
+
+ if (wtype == CSS_WIDTH_SET) {
+ if (unit == CSS_UNIT_PCT) {
+ *width = FPCT_OF_INT_TOINT(
+ value, available_width);
+ } else {
+ *width = FIXTOINT(nscss_len2px(len_ctx,
+ value, unit, style));
+ }
+ } else {
+ *width = AUTO;
+ }
+
+ if (*width != AUTO) {
+ layout_handle_box_sizing(len_ctx, box, available_width,
+ true, width);
+ }
+ }
+
+ if (height) {
+ enum css_height_e htype;
+ css_fixed value = 0;
+ css_unit unit = CSS_UNIT_PX;
+
+ htype = css_computed_height(style, &value, &unit);
+
+ if (htype == CSS_HEIGHT_SET) {
+ if (unit == CSS_UNIT_PCT) {
+ enum css_height_e cbhtype;
+
+ if (css_computed_position(box->style) ==
+ CSS_POSITION_ABSOLUTE &&
+ box->parent) {
+ /* Box is absolutely positioned */
+ assert(box->float_container);
+ containing_block = box->float_container;
+ } else if (box->float_container &&
+ css_computed_position(box->style) !=
+ CSS_POSITION_ABSOLUTE &&
+ (css_computed_float(box->style) ==
+ CSS_FLOAT_LEFT ||
+ css_computed_float(box->style) ==
+ CSS_FLOAT_RIGHT)) {
+ /* Box is a float */
+ assert(box->parent &&
+ box->parent->parent &&
+ box->parent->parent->parent);
+
+ containing_block =
+ box->parent->parent->parent;
+ } else if (box->parent && box->parent->type !=
+ BOX_INLINE_CONTAINER) {
+ /* Box is a block level element */
+ containing_block = box->parent;
+ } else if (box->parent && box->parent->type ==
+ BOX_INLINE_CONTAINER) {
+ /* Box is an inline block */
+ assert(box->parent->parent);
+ containing_block = box->parent->parent;
+ }
+
+ if (containing_block) {
+ css_fixed f = 0;
+ css_unit u = CSS_UNIT_PX;
+
+ cbhtype = css_computed_height(
+ containing_block->style,
+ &f, &u);
+ }
+
+ if (containing_block &&
+ containing_block->height != AUTO &&
+ (css_computed_position(box->style) ==
+ CSS_POSITION_ABSOLUTE ||
+ cbhtype == CSS_HEIGHT_SET)) {
+ /* Box is absolutely positioned or its
+ * containing block has a valid
+ * specified height.
+ * (CSS 2.1 Section 10.5) */
+ *height = FPCT_OF_INT_TOINT(value,
+ containing_block->height);
+ } else if ((!box->parent ||
+ !box->parent->parent) &&
+ viewport_height >= 0) {
+ /* If root element or it's child
+ * (HTML or BODY) */
+ *height = FPCT_OF_INT_TOINT(value,
+ viewport_height);
+ } else {
+ /* precentage height not permissible
+ * treat height as auto */
+ *height = AUTO;
+ }
+ } else {
+ *height = FIXTOINT(nscss_len2px(len_ctx,
+ value, unit, style));
+ }
+ } else {
+ *height = AUTO;
+ }
+
+ if (*height != AUTO) {
+ layout_handle_box_sizing(len_ctx, box, available_width,
+ false, height);
+ }
+ }
+
+ if (max_width) {
+ enum css_max_width_e type;
+ css_fixed value = 0;
+ css_unit unit = CSS_UNIT_PX;
+
+ type = css_computed_max_width(style, &value, &unit);
+
+ if (type == CSS_MAX_WIDTH_SET) {
+ if (unit == CSS_UNIT_PCT) {
+ *max_width = FPCT_OF_INT_TOINT(value,
+ available_width);
+ } else {
+ *max_width = FIXTOINT(nscss_len2px(len_ctx,
+ value, unit, style));
+ }
+ } else {
+ /* Inadmissible */
+ *max_width = -1;
+ }
+
+ if (*max_width != -1) {
+ layout_handle_box_sizing(len_ctx, box, available_width,
+ true, max_width);
+ }
+ }
+
+ if (min_width) {
+ enum css_min_width_e type;
+ css_fixed value = 0;
+ css_unit unit = CSS_UNIT_PX;
+
+ type = ns_computed_min_width(style, &value, &unit);
+
+ if (type == CSS_MIN_WIDTH_SET) {
+ if (unit == CSS_UNIT_PCT) {
+ *min_width = FPCT_OF_INT_TOINT(value,
+ available_width);
+ } else {
+ *min_width = FIXTOINT(nscss_len2px(len_ctx,
+ value, unit, style));
+ }
+ } else {
+ /* Inadmissible */
+ *min_width = 0;
+ }
+
+ if (*min_width != 0) {
+ layout_handle_box_sizing(len_ctx, box, available_width,
+ true, min_width);
+ }
+ }
+
+ if (max_height) {
+ enum css_max_height_e type;
+ css_fixed value = 0;
+ css_unit unit = CSS_UNIT_PX;
+
+ type = css_computed_max_height(style, &value, &unit);
+
+ if (type == CSS_MAX_HEIGHT_SET) {
+ if (unit == CSS_UNIT_PCT) {
+ /* TODO: handle percentage */
+ *max_height = -1;
+ } else {
+ *max_height = FIXTOINT(nscss_len2px(len_ctx,
+ value, unit, style));
+ }
+ } else {
+ /* Inadmissible */
+ *max_height = -1;
+ }
+ }
+
+ if (min_height) {
+ enum css_min_height_e type;
+ css_fixed value = 0;
+ css_unit unit = CSS_UNIT_PX;
+
+ type = ns_computed_min_height(style, &value, &unit);
+
+ if (type == CSS_MIN_HEIGHT_SET) {
+ if (unit == CSS_UNIT_PCT) {
+ /* TODO: handle percentage */
+ *min_height = 0;
+ } else {
+ *min_height = FIXTOINT(nscss_len2px(len_ctx,
+ value, unit, style));
+ }
+ } else {
+ /* Inadmissible */
+ *min_height = 0;
+ }
+ }
+
+ for (i = 0; i != 4; i++) {
+ if (margin) {
+ enum css_margin_e type = CSS_MARGIN_AUTO;
+ css_fixed value = 0;
+ css_unit unit = CSS_UNIT_PX;
+
+ type = margin_funcs[i](style, &value, &unit);
+
+ if (type == CSS_MARGIN_SET) {
+ if (unit == CSS_UNIT_PCT) {
+ margin[i] = FPCT_OF_INT_TOINT(value,
+ available_width);
+ } else {
+ margin[i] = FIXTOINT(nscss_len2px(
+ len_ctx,
+ value, unit, style));
+ }
+ } else {
+ margin[i] = AUTO;
+ }
+ }
+
+ if (padding) {
+ css_fixed value = 0;
+ css_unit unit = CSS_UNIT_PX;
+
+ padding_funcs[i](style, &value, &unit);
+
+ if (unit == CSS_UNIT_PCT) {
+ padding[i] = FPCT_OF_INT_TOINT(value,
+ available_width);
+ } else {
+ padding[i] = FIXTOINT(nscss_len2px(len_ctx,
+ value, unit, style));
+ }
+ }
+
+ /* Table cell borders are populated in table.c */
+ if (border && box->type != BOX_TABLE_CELL) {
+ enum css_border_style_e bstyle = CSS_BORDER_STYLE_NONE;
+ css_color color = 0;
+ css_fixed value = 0;
+ css_unit unit = CSS_UNIT_PX;
+
+ border_width_funcs[i](style, &value, &unit);
+ bstyle = border_style_funcs[i](style);
+ border_color_funcs[i](style, &color);
+
+ border[i].style = bstyle;
+ border[i].c = color;
+
+ if (bstyle == CSS_BORDER_STYLE_HIDDEN ||
+ bstyle == CSS_BORDER_STYLE_NONE)
+ /* spec unclear: following Mozilla */
+ border[i].width = 0;
+ else
+ border[i].width = FIXTOINT(nscss_len2px(len_ctx,
+ value, unit, style));
+
+ /* Special case for border-collapse: make all borders
+ * on table/table-row-group/table-row zero width. */
+ if (css_computed_border_collapse(style) ==
+ CSS_BORDER_COLLAPSE_COLLAPSE &&
+ (box->type == BOX_TABLE ||
+ box->type == BOX_TABLE_ROW_GROUP ||
+ box->type == BOX_TABLE_ROW))
+ border[i].width = 0;
+ }
+ }
+}
+
+
+/**
+ * Find next block that current margin collapses to.
+ *
+ * \param len_ctx Length conversion context
+ * \param box box to start tree-order search from (top margin is included)
+ * \param block box responsible for current block fromatting context
+ * \param viewport_height height of viewport in px
+ * \param max_pos_margin updated to to maximum positive margin encountered
+ * \param max_neg_margin updated to to maximum negative margin encountered
+ * \return next box that current margin collapses to, or NULL if none.
+ */
+static struct box*
+layout_next_margin_block(const nscss_len_ctx *len_ctx,
+ struct box *box,
+ struct box *block,
+ int viewport_height,
+ int *max_pos_margin,
+ int *max_neg_margin)
+{
+ assert(block != NULL);
+
+ while (box != NULL) {
+
+ if (box->type == BOX_INLINE_CONTAINER || (box->style &&
+ (css_computed_position(box->style) !=
+ CSS_POSITION_ABSOLUTE &&
+ css_computed_position(box->style) !=
+ CSS_POSITION_FIXED))) {
+ /* Not positioned */
+
+ /* Get margins */
+ if (box->style) {
+ layout_find_dimensions(len_ctx,
+ box->parent->width,
+ viewport_height, box,
+ box->style,
+ NULL, NULL, NULL, NULL,
+ NULL, NULL, box->margin,
+ box->padding, box->border);
+
+ /* Apply top margin */
+ if (*max_pos_margin < box->margin[TOP])
+ *max_pos_margin = box->margin[TOP];
+ else if (*max_neg_margin < -box->margin[TOP])
+ *max_neg_margin = -box->margin[TOP];
+ }
+
+ /* Check whether box is the box current margin collapses
+ * to */
+ if (box->flags & MAKE_HEIGHT ||
+ box->border[TOP].width ||
+ box->padding[TOP] ||
+ (box->style &&
+ css_computed_overflow_y(box->style) !=
+ CSS_OVERFLOW_VISIBLE) ||
+ (box->type == BOX_INLINE_CONTAINER &&
+ box != box->parent->children)) {
+ /* Collapse to this box; return it */
+ return box;
+ }
+ }
+
+
+ /* Find next box */
+ if (box->type == BOX_BLOCK && !box->object && box->children &&
+ box->style &&
+ css_computed_overflow_y(box->style) ==
+ CSS_OVERFLOW_VISIBLE) {
+ /* Down into children. */
+ box = box->children;
+ } else {
+ if (!box->next) {
+ /* No more siblings:
+ * Go up to first ancestor with a sibling. */
+ do {
+ /* Apply bottom margin */
+ if (*max_pos_margin <
+ box->margin[BOTTOM])
+ *max_pos_margin =
+ box->margin[BOTTOM];
+ else if (*max_neg_margin <
+ -box->margin[BOTTOM])
+ *max_neg_margin =
+ -box->margin[BOTTOM];
+
+ box = box->parent;
+ } while (box != block && !box->next);
+
+ if (box == block) {
+ /* Margins don't collapse with stuff
+ * outside the block formatting context
+ */
+ return block;
+ }
+ }
+
+ /* Apply bottom margin */
+ if (*max_pos_margin < box->margin[BOTTOM])
+ *max_pos_margin = box->margin[BOTTOM];
+ else if (*max_neg_margin < -box->margin[BOTTOM])
+ *max_neg_margin = -box->margin[BOTTOM];
+
+ /* To next sibling. */
+ box = box->next;
+
+ /* Get margins */
+ if (box->style) {
+ layout_find_dimensions(len_ctx,
+ box->parent->width,
+ viewport_height, box,
+ box->style,
+ NULL, NULL, NULL, NULL,
+ NULL, NULL, box->margin,
+ box->padding, box->border);
+ }
+ }
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Find y coordinate which clears all floats on left and/or right.
+ *
+ * \param fl first float in float list
+ * \param clear type of clear
+ * \return y coordinate relative to ancestor box for floats
+ */
+static int layout_clear(struct box *fl, enum css_clear_e clear)
+{
+ int y = 0;
+ for (; fl; fl = fl->next_float) {
+ if ((clear == CSS_CLEAR_LEFT || clear == CSS_CLEAR_BOTH) &&
+ fl->type == BOX_FLOAT_LEFT)
+ if (y < fl->y + fl->height)
+ y = fl->y + fl->height;
+ if ((clear == CSS_CLEAR_RIGHT || clear == CSS_CLEAR_BOTH) &&
+ fl->type == BOX_FLOAT_RIGHT)
+ if (y < fl->y + fl->height)
+ y = fl->y + fl->height;
+ }
+ return y;
+}
+
+
+/**
+ * Find left and right edges in a vertical range.
+ *
+ * \param fl first float in float list
+ * \param y0 start of y range to search
+ * \param y1 end of y range to search
+ * \param x0 start left edge, updated to available left edge
+ * \param x1 start right edge, updated to available right edge
+ * \param left returns float on left if present
+ * \param right returns float on right if present
+ */
+static void
+find_sides(struct box *fl,
+ int y0, int y1,
+ int *x0, int *x1,
+ struct box **left,
+ struct box **right)
+{
+ int fy0, fy1, fx0, fx1;
+
+ NSLOG(layout, DEBUG, "y0 %i, y1 %i, x0 %i, x1 %i", y0, y1, *x0, *x1);
+
+ *left = *right = 0;
+ for (; fl; fl = fl->next_float) {
+ fy1 = fl->y + fl->height;
+ if (fy1 < y0) {
+ /* Floats are sorted in order of decreasing bottom pos.
+ * Past here, all floats will be too high to concern us.
+ */
+ return;
+ }
+ fy0 = fl->y;
+ if (y0 < fy1 && fy0 <= y1) {
+ if (fl->type == BOX_FLOAT_LEFT) {
+ fx1 = fl->x + fl->width;
+ if (*x0 < fx1) {
+ *x0 = fx1;
+ *left = fl;
+ }
+ } else {
+ fx0 = fl->x;
+ if (fx0 < *x1) {
+ *x1 = fx0;
+ *right = fl;
+ }
+ }
+ }
+ }
+
+ NSLOG(layout, DEBUG, "x0 %i, x1 %i, left %p, right %p", *x0, *x1,
+ *left, *right);
+}
+
+
+
+
+/**
+ * Solve the width constraint as given in CSS 2.1 section 10.3.3.
+ *
+ * \param box Box to solve constraint for
+ * \param available_width Max width available in pixels
+ * \param width Current box width
+ * \param lm Min left margin required to avoid floats in px.
+ * zero if not applicable
+ * \param rm Min right margin required to avoid floats in px.
+ * zero if not applicable
+ * \param max_width Box max-width ( -ve means no max-width to apply)
+ * \param min_width Box min-width ( <=0 means no min-width to apply)
+ * \return New box width
+ *
+ * \post \a box's left/right margins will be updated.
+ */
+static int
+layout_solve_width(struct box *box,
+ int available_width,
+ int width,
+ int lm,
+ int rm,
+ int max_width,
+ int min_width)
+{
+ bool auto_width = false;
+
+ /* Increase specified left/right margins */
+ if (box->margin[LEFT] != AUTO && box->margin[LEFT] < lm &&
+ box->margin[LEFT] >= 0)
+ box->margin[LEFT] = lm;
+ if (box->margin[RIGHT] != AUTO && box->margin[RIGHT] < rm &&
+ box->margin[RIGHT] >= 0)
+ box->margin[RIGHT] = rm;
+
+ /* Find width */
+ if (width == AUTO) {
+ int margin_left = box->margin[LEFT];
+ int margin_right = box->margin[RIGHT];
+
+ if (margin_left == AUTO) {
+ margin_left = lm;
+ }
+ if (margin_right == AUTO) {
+ margin_right = rm;
+ }
+
+ width = available_width -
+ (margin_left + box->border[LEFT].width +
+ box->padding[LEFT] + box->padding[RIGHT] +
+ box->border[RIGHT].width + margin_right);
+ width = width < 0 ? 0 : width;
+ auto_width = true;
+ }
+
+ if (max_width >= 0 && width > max_width) {
+ /* max-width is admissable and width exceeds max-width */
+ width = max_width;
+ auto_width = false;
+ }
+
+ if (min_width > 0 && width < min_width) {
+ /* min-width is admissable and width is less than max-width */
+ width = min_width;
+ auto_width = false;
+ }
+
+ /* Width was auto, and unconstrained by min/max width, so we're done */
+ if (auto_width) {
+ /* any other 'auto' become 0 or the minimum required values */
+ if (box->margin[LEFT] == AUTO) {
+ box->margin[LEFT] = lm;
+ }
+ if (box->margin[RIGHT] == AUTO) {
+ box->margin[RIGHT] = rm;
+ }
+ return width;
+ }
+
+ /* Width was not auto, or was constrained by min/max width
+ * Need to compute left/right margins */
+
+ /* HTML alignment (only applies to over-constrained boxes) */
+ if (box->margin[LEFT] != AUTO && box->margin[RIGHT] != AUTO &&
+ box->parent != NULL && box->parent->style != NULL) {
+ switch (css_computed_text_align(box->parent->style)) {
+ case CSS_TEXT_ALIGN_LIBCSS_RIGHT:
+ box->margin[LEFT] = AUTO;
+ box->margin[RIGHT] = 0;
+ break;
+ case CSS_TEXT_ALIGN_LIBCSS_CENTER:
+ box->margin[LEFT] = box->margin[RIGHT] = AUTO;
+ break;
+ case CSS_TEXT_ALIGN_LIBCSS_LEFT:
+ box->margin[LEFT] = 0;
+ box->margin[RIGHT] = AUTO;
+ break;
+ default:
+ /* Leave it alone; no HTML alignment */
+ break;
+ }
+ }
+
+ if (box->margin[LEFT] == AUTO && box->margin[RIGHT] == AUTO) {
+ /* make the margins equal, centering the element */
+ box->margin[LEFT] = box->margin[RIGHT] =
+ (available_width - lm - rm -
+ (box->border[LEFT].width + box->padding[LEFT] +
+ width + box->padding[RIGHT] +
+ box->border[RIGHT].width)) / 2;
+
+ if (box->margin[LEFT] < 0) {
+ box->margin[RIGHT] += box->margin[LEFT];
+ box->margin[LEFT] = 0;
+ }
+
+ box->margin[LEFT] += lm;
+
+ } else if (box->margin[LEFT] == AUTO) {
+ box->margin[LEFT] = available_width - lm -
+ (box->border[LEFT].width + box->padding[LEFT] +
+ width + box->padding[RIGHT] +
+ box->border[RIGHT].width + box->margin[RIGHT]);
+ box->margin[LEFT] = box->margin[LEFT] < lm
+ ? lm : box->margin[LEFT];
+ } else {
+ /* margin-right auto or "over-constrained" */
+ box->margin[RIGHT] = available_width - rm -
+ (box->margin[LEFT] + box->border[LEFT].width +
+ box->padding[LEFT] + width +
+ box->padding[RIGHT] +
+ box->border[RIGHT].width);
+ }
+
+ return width;
+}
+
+
+/**
+ * Compute dimensions of box, margins, paddings, and borders for a block-level
+ * element.
+ *
+ * \param len_ctx Length conversion context
+ * \param available_width Max width available in pixels
+ * \param viewport_height Height of viewport in pixels or -ve if unknown
+ * \param lm min left margin required to avoid floats in px.
+ * zero if not applicable
+ * \param rm min right margin required to avoid floats in px.
+ * zero if not applicable
+ * \param box box to find dimensions of. updated with new width,
+ * height, margins, borders and paddings
+ *
+ * See CSS 2.1 10.3.3, 10.3.4, 10.6.2, and 10.6.3.
+ */
+static void
+layout_block_find_dimensions(const nscss_len_ctx *len_ctx,
+ int available_width,
+ int viewport_height,
+ int lm,
+ int rm,
+ struct box *box)
+{
+ int width, max_width, min_width;
+ int height, max_height, min_height;
+ int *margin = box->margin;
+ int *padding = box->padding;
+ struct box_border *border = box->border;
+ const css_computed_style *style = box->style;
+
+ layout_find_dimensions(len_ctx, available_width, viewport_height, box,
+ style, &width, &height, &max_width, &min_width,
+ &max_height, &min_height, margin, padding, border);
+
+ if (box->object && !(box->flags & REPLACE_DIM) &&
+ content_get_type(box->object) != CONTENT_HTML) {
+ /* block-level replaced element, see 10.3.4 and 10.6.2 */
+ layout_get_object_dimensions(box, &width, &height,
+ min_width, max_width, min_height, max_height);
+ }
+
+ box->width = layout_solve_width(box, available_width, width, lm, rm,
+ max_width, min_width);
+ box->height = height;
+
+ if (margin[TOP] == AUTO)
+ margin[TOP] = 0;
+ if (margin[BOTTOM] == AUTO)
+ margin[BOTTOM] = 0;
+}
+
+
+/**
+ * Manipulate a block's [RB]padding/height/width to accommodate scrollbars
+ *
+ * \param box Box to apply scrollbar space too. Must be BOX_BLOCK.
+ * \param which Which scrollbar to make space for. Must be RIGHT or BOTTOM.
+ */
+static void layout_block_add_scrollbar(struct box *box, int which)
+{
+ enum css_overflow_e overflow_x, overflow_y;
+
+ assert(box->type == BOX_BLOCK && (which == RIGHT || which == BOTTOM));
+
+ if (box->style == NULL)
+ return;
+
+ overflow_x = css_computed_overflow_x(box->style);
+ overflow_y = css_computed_overflow_y(box->style);
+
+ if (which == BOTTOM &&
+ (overflow_x == CSS_OVERFLOW_SCROLL ||
+ overflow_x == CSS_OVERFLOW_AUTO ||
+ (box->object &&
+ content_get_type(box->object) == CONTENT_HTML))) {
+ /* make space for scrollbar, unless height is AUTO */
+ if (box->height != AUTO &&
+ (overflow_x == CSS_OVERFLOW_SCROLL ||
+ box_hscrollbar_present(box))) {
+ box->padding[BOTTOM] += SCROLLBAR_WIDTH;
+ }
+
+ } else if (which == RIGHT &&
+ (overflow_y == CSS_OVERFLOW_SCROLL ||
+ overflow_y == CSS_OVERFLOW_AUTO ||
+ (box->object &&
+ content_get_type(box->object) == CONTENT_HTML))) {
+ /* make space for scrollbars, unless width is AUTO */
+ enum css_height_e htype;
+ css_fixed height = 0;
+ css_unit hunit = CSS_UNIT_PX;
+ htype = css_computed_height(box->style, &height, &hunit);
+
+ if (which == RIGHT && box->width != AUTO &&
+ htype == CSS_HEIGHT_SET &&
+ (overflow_y == CSS_OVERFLOW_SCROLL ||
+ box_vscrollbar_present(box))) {
+ box->width -= SCROLLBAR_WIDTH;
+ box->padding[RIGHT] += SCROLLBAR_WIDTH;
+ }
+ }
+}
+
+
+/**
+ * Moves the children of a box by a specified amount
+ *
+ * \param box top of tree of boxes
+ * \param x the amount to move children by horizontally
+ * \param y the amount to move children by vertically
+ */
+static void layout_move_children(struct box *box, int x, int y)
+{
+ assert(box);
+
+ for (box = box->children; box; box = box->next) {
+ box->x += x;
+ box->y += y;
+ }
+}
+
+
+/**
+ * Layout a table.
+ *
+ * \param table table to layout
+ * \param available_width width of containing block
+ * \param content memory pool for any new boxes
+ * \return true on success, false on memory exhaustion
+ */
+static bool layout_table(struct box *table, int available_width,
+ html_content *content)
+{
+ unsigned int columns = table->columns; /* total columns */
+ unsigned int i;
+ unsigned int *row_span;
+ int *excess_y;
+ int table_width, min_width = 0, max_width = 0;
+ int required_width = 0;
+ int x, remainder = 0, count = 0;
+ int table_height = 0;
+ int min_height = 0;
+ int *xs; /* array of column x positions */
+ int auto_width;
+ int spare_width;
+ int relative_sum = 0;
+ int border_spacing_h = 0, border_spacing_v = 0;
+ int spare_height;
+ int positioned_columns = 0;
+ struct box *containing_block = NULL;
+ struct box *c;
+ struct box *row;
+ struct box *row_group;
+ struct box **row_span_cell;
+ struct column *col;
+ const css_computed_style *style = table->style;
+ enum css_width_e wtype;
+ enum css_height_e htype;
+ css_fixed value = 0;
+ css_unit unit = CSS_UNIT_PX;
+
+ assert(table->type == BOX_TABLE);
+ assert(style);
+ assert(table->children && table->children->children);
+ assert(columns);
+
+ /* allocate working buffers */
+ col = malloc(columns * sizeof col[0]);
+ excess_y = malloc(columns * sizeof excess_y[0]);
+ row_span = malloc(columns * sizeof row_span[0]);
+ row_span_cell = malloc(columns * sizeof row_span_cell[0]);
+ xs = malloc((columns + 1) * sizeof xs[0]);
+ if (!col || !xs || !row_span || !excess_y || !row_span_cell) {
+ free(col);
+ free(excess_y);
+ free(row_span);
+ free(row_span_cell);
+ free(xs);
+ return false;
+ }
+
+ memcpy(col, table->col, sizeof(col[0]) * columns);
+
+ /* find margins, paddings, and borders for table and cells */
+ layout_find_dimensions(&content->len_ctx, available_width, -1, table,
+ style, 0, 0, 0, 0, 0, 0, table->margin, table->padding,
+ table->border);
+ for (row_group = table->children; row_group;
+ row_group = row_group->next) {
+ for (row = row_group->children; row; row = row->next) {
+ for (c = row->children; c; c = c->next) {
+ enum css_overflow_e overflow_x;
+ enum css_overflow_e overflow_y;
+
+ assert(c->style);
+ table_used_border_for_cell(
+ &content->len_ctx, c);
+ layout_find_dimensions(&content->len_ctx,
+ available_width, -1, c,
+ c->style, 0, 0, 0, 0, 0, 0,
+ 0, c->padding, c->border);
+
+ overflow_x = css_computed_overflow_x(c->style);
+ overflow_y = css_computed_overflow_y(c->style);
+
+ if (overflow_x == CSS_OVERFLOW_SCROLL ||
+ overflow_x ==
+ CSS_OVERFLOW_AUTO) {
+ c->padding[BOTTOM] += SCROLLBAR_WIDTH;
+ }
+ if (overflow_y == CSS_OVERFLOW_SCROLL ||
+ overflow_y ==
+ CSS_OVERFLOW_AUTO) {
+ c->padding[RIGHT] += SCROLLBAR_WIDTH;
+ }
+ }
+ }
+ }
+
+ /* border-spacing is used in the separated borders model */
+ if (css_computed_border_collapse(style) ==
+ CSS_BORDER_COLLAPSE_SEPARATE) {
+ css_fixed h = 0, v = 0;
+ css_unit hu = CSS_UNIT_PX, vu = CSS_UNIT_PX;
+
+ css_computed_border_spacing(style, &h, &hu, &v, &vu);
+
+ border_spacing_h = FIXTOINT(nscss_len2px(&content->len_ctx,
+ h, hu, style));
+ border_spacing_v = FIXTOINT(nscss_len2px(&content->len_ctx,
+ v, vu, style));
+ }
+
+ /* find specified table width, or available width if auto-width */
+ wtype = css_computed_width(style, &value, &unit);
+ if (wtype == CSS_WIDTH_SET) {
+ if (unit == CSS_UNIT_PCT) {
+ table_width = FPCT_OF_INT_TOINT(value, available_width);
+ } else {
+ table_width =
+ FIXTOINT(nscss_len2px(&content->len_ctx,
+ value, unit, style));
+ }
+
+ /* specified width includes border */
+ table_width -= table->border[LEFT].width +
+ table->border[RIGHT].width;
+ table_width = table_width < 0 ? 0 : table_width;
+
+ auto_width = table_width;
+ } else {
+ table_width = AUTO;
+ auto_width = available_width -
+ ((table->margin[LEFT] == AUTO ? 0 :
+ table->margin[LEFT]) +
+ table->border[LEFT].width +
+ table->padding[LEFT] +
+ table->padding[RIGHT] +
+ table->border[RIGHT].width +
+ (table->margin[RIGHT] == AUTO ? 0 :
+ table->margin[RIGHT]));
+ }
+
+ /* Find any table height specified within CSS/HTML */
+ htype = css_computed_height(style, &value, &unit);
+ if (htype == CSS_HEIGHT_SET) {
+ if (unit == CSS_UNIT_PCT) {
+ /* This is the minimum height for the table
+ * (see 17.5.3) */
+ if (css_computed_position(table->style) ==
+ CSS_POSITION_ABSOLUTE) {
+ /* Table is absolutely positioned */
+ assert(table->float_container);
+ containing_block = table->float_container;
+ } else if (table->float_container &&
+ css_computed_position(table->style) !=
+ CSS_POSITION_ABSOLUTE &&
+ (css_computed_float(table->style) ==
+ CSS_FLOAT_LEFT ||
+ css_computed_float(table->style) ==
+ CSS_FLOAT_RIGHT)) {
+ /* Table is a float */
+ assert(table->parent && table->parent->parent &&
+ table->parent->parent->parent);
+ containing_block =
+ table->parent->parent->parent;
+ } else if (table->parent && table->parent->type !=
+ BOX_INLINE_CONTAINER) {
+ /* Table is a block level element */
+ containing_block = table->parent;
+ } else if (table->parent && table->parent->type ==
+ BOX_INLINE_CONTAINER) {
+ /* Table is an inline block */
+ assert(table->parent->parent);
+ containing_block = table->parent->parent;
+ }
+
+ if (containing_block) {
+ css_fixed ignored = 0;
+
+ htype = css_computed_height(
+ containing_block->style,
+ &ignored, &unit);
+ }
+
+ if (containing_block &&
+ containing_block->height != AUTO &&
+ (css_computed_position(table->style) ==
+ CSS_POSITION_ABSOLUTE ||
+ htype == CSS_HEIGHT_SET)) {
+ /* Table is absolutely positioned or its
+ * containing block has a valid specified
+ * height. (CSS 2.1 Section 10.5) */
+ min_height = FPCT_OF_INT_TOINT(value,
+ containing_block->height);
+ }
+ } else {
+ /* This is the minimum height for the table
+ * (see 17.5.3) */
+ min_height = FIXTOINT(nscss_len2px(&content->len_ctx,
+ value, unit, style));
+ }
+ }
+
+ /* calculate width required by cells */
+ for (i = 0; i != columns; i++) {
+
+ NSLOG(layout, DEBUG,
+ "table %p, column %u: type %s, width %i, min %i, max %i",
+ table,
+ i,
+ ((const char *[]){
+ "UNKNOWN",
+ "FIXED",
+ "AUTO",
+ "PERCENT",
+ "RELATIVE",
+ })[col[i].type],
+ col[i].width,
+ col[i].min,
+ col[i].max);
+
+
+ if (col[i].positioned) {
+ positioned_columns++;
+ continue;
+ } else if (col[i].type == COLUMN_WIDTH_FIXED) {
+ if (col[i].width < col[i].min)
+ col[i].width = col[i].max = col[i].min;
+ else
+ col[i].min = col[i].max = col[i].width;
+ required_width += col[i].width;
+ } else if (col[i].type == COLUMN_WIDTH_PERCENT) {
+ int width = col[i].width * auto_width / 100;
+ required_width += col[i].min < width ? width :
+ col[i].min;
+ } else
+ required_width += col[i].min;
+
+ NSLOG(layout, DEBUG, "required_width %i", required_width);
+ }
+ required_width += (columns + 1 - positioned_columns) *
+ border_spacing_h;
+
+ NSLOG(layout, DEBUG,
+ "width %i, min %i, max %i, auto %i, required %i", table_width,
+ table->min_width, table->max_width, auto_width, required_width);
+
+ if (auto_width < required_width) {
+ /* table narrower than required width for columns:
+ * treat percentage widths as maximums */
+ for (i = 0; i != columns; i++) {
+ if (col[i].type == COLUMN_WIDTH_RELATIVE)
+ continue;
+ if (col[i].type == COLUMN_WIDTH_PERCENT) {
+ col[i].max = auto_width * col[i].width / 100;
+ if (col[i].max < col[i].min)
+ col[i].max = col[i].min;
+ }
+ min_width += col[i].min;
+ max_width += col[i].max;
+ }
+ } else {
+ /* take percentages exactly */
+ for (i = 0; i != columns; i++) {
+ if (col[i].type == COLUMN_WIDTH_RELATIVE)
+ continue;
+ if (col[i].type == COLUMN_WIDTH_PERCENT) {
+ int width = auto_width * col[i].width / 100;
+ if (width < col[i].min)
+ width = col[i].min;
+ col[i].min = col[i].width = col[i].max = width;
+ col[i].type = COLUMN_WIDTH_FIXED;
+ }
+ min_width += col[i].min;
+ max_width += col[i].max;
+ }
+ }
+
+ /* allocate relative widths */
+ spare_width = auto_width;
+ for (i = 0; i != columns; i++) {
+ if (col[i].type == COLUMN_WIDTH_RELATIVE)
+ relative_sum += col[i].width;
+ else if (col[i].type == COLUMN_WIDTH_FIXED)
+ spare_width -= col[i].width;
+ else
+ spare_width -= col[i].min;
+ }
+ spare_width -= (columns + 1) * border_spacing_h;
+ if (relative_sum != 0) {
+ if (spare_width < 0)
+ spare_width = 0;
+ for (i = 0; i != columns; i++) {
+ if (col[i].type == COLUMN_WIDTH_RELATIVE) {
+ col[i].min = ceil(col[i].max =
+ (float) spare_width
+ * (float) col[i].width
+ / relative_sum);
+ min_width += col[i].min;
+ max_width += col[i].max;
+ }
+ }
+ }
+ min_width += (columns + 1) * border_spacing_h;
+ max_width += (columns + 1) * border_spacing_h;
+
+ if (auto_width <= min_width) {
+ /* not enough space: minimise column widths */
+ for (i = 0; i < columns; i++) {
+ col[i].width = col[i].min;
+ }
+ table_width = min_width;
+ } else if (max_width <= auto_width) {
+ /* more space than maximum width */
+ if (table_width == AUTO) {
+ /* for auto-width tables, make columns max width */
+ for (i = 0; i < columns; i++) {
+ col[i].width = col[i].max;
+ }
+ table_width = max_width;
+ } else {
+ /* for fixed-width tables, distribute the extra space
+ * too */
+ unsigned int flexible_columns = 0;
+ for (i = 0; i != columns; i++)
+ if (col[i].type != COLUMN_WIDTH_FIXED)
+ flexible_columns++;
+ if (flexible_columns == 0) {
+ int extra = (table_width - max_width) / columns;
+ remainder = (table_width - max_width) -
+ (extra * columns);
+ for (i = 0; i != columns; i++) {
+ col[i].width = col[i].max + extra;
+ count -= remainder;
+ if (count < 0) {
+ col[i].width++;
+ count += columns;
+ }
+ }
+
+ } else {
+ int extra = (table_width - max_width) /
+ flexible_columns;
+ remainder = (table_width - max_width) -
+ (extra * flexible_columns);
+ for (i = 0; i != columns; i++)
+ if (col[i].type != COLUMN_WIDTH_FIXED) {
+ col[i].width = col[i].max +
+ extra;
+ count -= remainder;
+ if (count < 0) {
+ col[i].width++;
+ count += flexible_columns;
+ }
+ }
+ }
+ }
+ } else {
+ /* space between min and max: fill it exactly */
+ float scale = (float) (auto_width - min_width) /
+ (float) (max_width - min_width);
+ /* fprintf(stderr, "filling, scale %f\n", scale); */
+ for (i = 0; i < columns; i++) {
+ col[i].width = col[i].min + (int) (0.5 +
+ (col[i].max - col[i].min) * scale);
+ }
+ table_width = auto_width;
+ }
+
+ xs[0] = x = border_spacing_h;
+ for (i = 0; i != columns; i++) {
+ if (!col[i].positioned)
+ x += col[i].width + border_spacing_h;
+ xs[i + 1] = x;
+ row_span[i] = 0;
+ excess_y[i] = 0;
+ row_span_cell[i] = 0;
+ }
+
+ /* position cells */
+ table_height = border_spacing_v;
+ for (row_group = table->children; row_group;
+ row_group = row_group->next) {
+ int row_group_height = 0;
+ for (row = row_group->children; row; row = row->next) {
+ int row_height = 0;
+
+ htype = css_computed_height(row->style, &value, &unit);
+ if (htype == CSS_HEIGHT_SET && unit != CSS_UNIT_PCT) {
+ row_height = FIXTOINT(nscss_len2px(
+ &content->len_ctx,
+ value, unit, row->style));
+ }
+ for (c = row->children; c; c = c->next) {
+ assert(c->style);
+ c->width = xs[c->start_column + c->columns] -
+ xs[c->start_column] -
+ border_spacing_h -
+ c->border[LEFT].width -
+ c->padding[LEFT] -
+ c->padding[RIGHT] -
+ c->border[RIGHT].width;
+ c->float_children = 0;
+ c->cached_place_below_level = 0;
+
+ c->height = AUTO;
+ if (!layout_block_context(c, -1, content)) {
+ free(col);
+ free(excess_y);
+ free(row_span);
+ free(row_span_cell);
+ free(xs);
+ return false;
+ }
+ /* warning: c->descendant_y0 and
+ * c->descendant_y1 used as temporary storage
+ * until after vertical alignment is complete */
+ c->descendant_y0 = c->height;
+ c->descendant_y1 = c->padding[BOTTOM];
+
+ htype = css_computed_height(c->style,
+ &value, &unit);
+
+ if (htype == CSS_HEIGHT_SET &&
+ unit != CSS_UNIT_PCT) {
+ /* some sites use height="1" or similar
+ * to attempt to make cells as small as
+ * possible, so treat it as a minimum */
+ int h = FIXTOINT(nscss_len2px(
+ &content->len_ctx,
+ value, unit, c->style));
+ if (c->height < h)
+ c->height = h;
+ }
+ /* specified row height is treated as a minimum
+ */
+ if (c->height < row_height)
+ c->height = row_height;
+ c->x = xs[c->start_column] +
+ c->border[LEFT].width;
+ c->y = c->border[TOP].width;
+ for (i = 0; i != c->columns; i++) {
+ row_span[c->start_column + i] = c->rows;
+ excess_y[c->start_column + i] =
+ c->border[TOP].width +
+ c->padding[TOP] +
+ c->height +
+ c->padding[BOTTOM] +
+ c->border[BOTTOM].width;
+ row_span_cell[c->start_column + i] = 0;
+ }
+ row_span_cell[c->start_column] = c;
+ c->padding[BOTTOM] = -border_spacing_v -
+ c->border[TOP].width -
+ c->padding[TOP] -
+ c->height -
+ c->border[BOTTOM].width;
+ }
+ for (i = 0; i != columns; i++)
+ if (row_span[i] != 0)
+ row_span[i]--;
+ else
+ row_span_cell[i] = 0;
+ if (row->next || row_group->next) {
+ /* row height is greatest excess of a cell
+ * which ends in this row */
+ for (i = 0; i != columns; i++)
+ if (row_span[i] == 0 && row_height <
+ excess_y[i])
+ row_height = excess_y[i];
+ } else {
+ /* except in the last row */
+ for (i = 0; i != columns; i++)
+ if (row_height < excess_y[i])
+ row_height = excess_y[i];
+ }
+ for (i = 0; i != columns; i++) {
+ if (row_height < excess_y[i])
+ excess_y[i] -= row_height;
+ else
+ excess_y[i] = 0;
+ if (row_span_cell[i] != 0)
+ row_span_cell[i]->padding[BOTTOM] +=
+ row_height +
+ border_spacing_v;
+ }
+
+ row->x = 0;
+ row->y = row_group_height;
+ row->width = table_width;
+ row->height = row_height;
+ row_group_height += row_height + border_spacing_v;
+ }
+ row_group->x = 0;
+ row_group->y = table_height;
+ row_group->width = table_width;
+ row_group->height = row_group_height;
+ table_height += row_group_height;
+ }
+ /* Table height is either the height of the contents, or specified
+ * height if greater */
+ table_height = max(table_height, min_height);
+ /** \todo distribute spare height over the row groups / rows / cells */
+
+ /* perform vertical alignment */
+ for (row_group = table->children; row_group;
+ row_group = row_group->next) {
+ for (row = row_group->children; row; row = row->next) {
+ for (c = row->children; c; c = c->next) {
+ enum css_vertical_align_e vertical_align;
+
+ /* unextended bottom padding is in
+ * c->descendant_y1, and unextended
+ * cell height is in c->descendant_y0 */
+ spare_height = (c->padding[BOTTOM] -
+ c->descendant_y1) +
+ (c->height - c->descendant_y0);
+
+ vertical_align = css_computed_vertical_align(
+ c->style, &value, &unit);
+
+ switch (vertical_align) {
+ case CSS_VERTICAL_ALIGN_SUB:
+ case CSS_VERTICAL_ALIGN_SUPER:
+ case CSS_VERTICAL_ALIGN_TEXT_TOP:
+ case CSS_VERTICAL_ALIGN_TEXT_BOTTOM:
+ case CSS_VERTICAL_ALIGN_SET:
+ case CSS_VERTICAL_ALIGN_BASELINE:
+ /* todo: baseline alignment, for now
+ * just use ALIGN_TOP */
+ case CSS_VERTICAL_ALIGN_TOP:
+ break;
+ case CSS_VERTICAL_ALIGN_MIDDLE:
+ c->padding[TOP] += spare_height / 2;
+ c->padding[BOTTOM] -= spare_height / 2;
+ layout_move_children(c, 0,
+ spare_height / 2);
+ break;
+ case CSS_VERTICAL_ALIGN_BOTTOM:
+ c->padding[TOP] += spare_height;
+ c->padding[BOTTOM] -= spare_height;
+ layout_move_children(c, 0,
+ spare_height);
+ break;
+ case CSS_VERTICAL_ALIGN_INHERIT:
+ assert(0);
+ break;
+ }
+ }
+ }
+ }
+
+ /* Top and bottom margins of 'auto' are set to 0. CSS2.1 10.6.3 */
+ if (table->margin[TOP] == AUTO)
+ table->margin[TOP] = 0;
+ if (table->margin[BOTTOM] == AUTO)
+ table->margin[BOTTOM] = 0;
+
+ free(col);
+ free(excess_y);
+ free(row_span);
+ free(row_span_cell);
+ free(xs);
+
+ table->width = table_width;
+ table->height = table_height;
+
+ return true;
+}
+
+
+/**
+ * Manimpulate box height according to CSS min-height and max-height properties
+ *
+ * \param len_ctx CSS length conversion context for document.
+ * \param box block to modify with any min-height or max-height
+ * \param container containing block for absolutely positioned elements, or
+ * NULL for non absolutely positioned elements.
+ * \return whether the height has been changed
+ */
+static bool layout_apply_minmax_height(
+ const nscss_len_ctx *len_ctx,
+ struct box *box,
+ struct box *container)
+{
+ int h;
+ struct box *containing_block = NULL;
+ bool updated = false;
+
+ /* Find containing block for percentage heights */
+ if (box->style != NULL && css_computed_position(box->style) ==
+ CSS_POSITION_ABSOLUTE) {
+ /* Box is absolutely positioned */
+ assert(container);
+ containing_block = container;
+ } else if (box->float_container && box->style != NULL &&
+ (css_computed_float(box->style) == CSS_FLOAT_LEFT ||
+ css_computed_float(box->style) == CSS_FLOAT_RIGHT)) {
+ /* Box is a float */
+ assert(box->parent && box->parent->parent &&
+ box->parent->parent->parent);
+ containing_block = box->parent->parent->parent;
+ } else if (box->parent && box->parent->type != BOX_INLINE_CONTAINER) {
+ /* Box is a block level element */
+ containing_block = box->parent;
+ } else if (box->parent && box->parent->type == BOX_INLINE_CONTAINER) {
+ /* Box is an inline block */
+ assert(box->parent->parent);
+ containing_block = box->parent->parent;
+ }
+
+ if (box->style) {
+ enum css_height_e htype = CSS_HEIGHT_AUTO;
+ css_fixed value = 0;
+ css_unit unit = CSS_UNIT_PX;
+
+ if (containing_block) {
+ htype = css_computed_height(containing_block->style,
+ &value, &unit);
+ }
+
+ /* max-height */
+ if (css_computed_max_height(box->style, &value, &unit) ==
+ CSS_MAX_HEIGHT_SET) {
+ if (unit == CSS_UNIT_PCT) {
+ if (containing_block &&
+ containing_block->height != AUTO &&
+ (css_computed_position(box->style) ==
+ CSS_POSITION_ABSOLUTE ||
+ htype == CSS_HEIGHT_SET)) {
+ /* Box is absolutely positioned or its
+ * containing block has a valid
+ * specified height. (CSS 2.1
+ * Section 10.5) */
+ h = FPCT_OF_INT_TOINT(value,
+ containing_block->height);
+ if (h < box->height) {
+ box->height = h;
+ updated = true;
+ }
+ }
+ } else {
+ h = FIXTOINT(nscss_len2px(len_ctx,
+ value, unit, box->style));
+ if (h < box->height) {
+ box->height = h;
+ updated = true;
+ }
+ }
+ }
+
+ /* min-height */
+ if (ns_computed_min_height(box->style, &value, &unit) ==
+ CSS_MIN_HEIGHT_SET) {
+ if (unit == CSS_UNIT_PCT) {
+ if (containing_block &&
+ containing_block->height != AUTO &&
+ (css_computed_position(box->style) ==
+ CSS_POSITION_ABSOLUTE ||
+ htype == CSS_HEIGHT_SET)) {
+ /* Box is absolutely positioned or its
+ * containing block has a valid
+ * specified height. (CSS 2.1
+ * Section 10.5) */
+ h = FPCT_OF_INT_TOINT(value,
+ containing_block->height);
+ if (h > box->height) {
+ box->height = h;
+ updated = true;
+ }
+ }
+ } else {
+ h = FIXTOINT(nscss_len2px(len_ctx,
+ value, unit, box->style));
+ if (h > box->height) {
+ box->height = h;
+ updated = true;
+ }
+ }
+ }
+ }
+ return updated;
+}
+
+
+/**
+ * Layout a block which contains an object.
+ *
+ * \param block box of type BLOCK, INLINE_BLOCK, TABLE, or TABLE_CELL
+ * \return true on success, false on memory exhaustion
+ */
+static bool layout_block_object(struct box *block)
+{
+ assert(block);
+ assert(block->type == BOX_BLOCK ||
+ block->type == BOX_INLINE_BLOCK ||
+ block->type == BOX_TABLE ||
+ block->type == BOX_TABLE_CELL);
+ assert(block->object);
+
+ NSLOG(layout, DEBUG, "block %p, object %p, width %i", block,
+ hlcache_handle_get_url(block->object), block->width);
+
+ if (content_get_type(block->object) == CONTENT_HTML) {
+ content_reformat(block->object, false, block->width, 1);
+ } else {
+ /* Non-HTML objects */
+ /* this case handled already in
+ * layout_block_find_dimensions() */
+ }
+
+ return true;
+}
+
+
+/**
+ * Insert a float into a container.
+ *
+ * \param cont block formatting context block, used to contain float
+ * \param b box to add to float
+ *
+ * This sorts floats in order of descending bottom edges.
+ */
+static void add_float_to_container(struct box *cont, struct box *b)
+{
+ struct box *box = cont->float_children;
+ int b_bottom = b->y + b->height;
+
+ assert(b->type == BOX_FLOAT_LEFT || b->type == BOX_FLOAT_RIGHT);
+
+ if (box == NULL) {
+ /* No other float children */
+ b->next_float = NULL;
+ cont->float_children = b;
+ return;
+ } else if (b_bottom >= box->y + box->height) {
+ /* Goes at start of list */
+ b->next_float = cont->float_children;
+ cont->float_children = b;
+ } else {
+ struct box *prev = NULL;
+ while (box != NULL && b_bottom < box->y + box->height) {
+ prev = box;
+ box = box->next_float;
+ }
+ if (prev != NULL) {
+ b->next_float = prev->next_float;
+ prev->next_float = b;
+ }
+ }
+}
+
+
+/**
+ * Split a text box.
+ *
+ * \param content memory pool for any new boxes
+ * \param fstyle style for text in text box
+ * \param split_box box with text to split
+ * \param new_length new length for text in split_box, after splitting
+ * \param new_width new width for text in split_box, after splitting
+ * \return true on success, false on memory exhaustion
+ *
+ * A new box is created and inserted into the box tree after split_box,
+ * containing the text after new_length excluding the initial space character.
+ */
+static bool
+layout_text_box_split(html_content *content,
+ plot_font_style_t *fstyle,
+ struct box *split_box,
+ size_t new_length,
+ int new_width)
+{
+ int space_width = split_box->space;
+ struct box *c2;
+ const struct gui_layout_table *font_func = content->font_func;
+ bool space = (split_box->text[new_length] == ' ');
+ int used_length = new_length + (space ? 1 : 0);
+
+ if ((space && space_width == 0) || space_width == UNKNOWN_WIDTH) {
+ /* We're need to add a space, and we don't know how big
+ * it's to be, OR we have a space of unknown width anyway;
+ * Calculate space width */
+ font_func->width(fstyle, " ", 1, &space_width);
+ }
+
+ if (split_box->space == UNKNOWN_WIDTH)
+ split_box->space = space_width;
+ if (!space)
+ space_width = 0;
+
+ /* Create clone of split_box, c2 */
+ c2 = talloc_memdup(content->bctx, split_box, sizeof *c2);
+ if (!c2)
+ return false;
+ c2->flags |= CLONE;
+
+ /* Set remaining text in c2 */
+ c2->text += used_length;
+
+ /* Set c2 according to the remaining text */
+ c2->width -= new_width + space_width;
+ c2->flags &= ~MEASURED; /* width has been estimated */
+ c2->length = split_box->length - used_length;
+
+ /* Update split_box for its reduced text */
+ split_box->width = new_width;
+ split_box->flags |= MEASURED;
+ split_box->length = new_length;
+ split_box->space = space_width;
+
+ /* Insert c2 into box list */
+ c2->next = split_box->next;
+ split_box->next = c2;
+ c2->prev = split_box;
+ if (c2->next)
+ c2->next->prev = c2;
+ else
+ c2->parent->last = c2;
+
+ NSLOG(layout, DEBUG,
+ "split_box %p len: %" PRIsizet " \"%.*s\"",
+ split_box,
+ split_box->length,
+ (int)split_box->length,
+ split_box->text);
+ NSLOG(layout, DEBUG,
+ " new_box %p len: %" PRIsizet " \"%.*s\"",
+ c2,
+ c2->length,
+ (int)c2->length,
+ c2->text);
+
+ return true;
+}
+
+
+/**
+ * Compute dimensions of box, margins, paddings, and borders for a floating
+ * element using shrink-to-fit. Also used for inline-blocks.
+ *
+ * \param len_ctx CSS length conversion context for document.
+ * \param available_width Max width available in pixels
+ * \param style Box's style
+ * \param box Box for which to find dimensions
+ * Box margins, borders, paddings, width and
+ * height are updated.
+ */
+static void
+layout_float_find_dimensions(
+ const nscss_len_ctx *len_ctx,
+ int available_width,
+ const css_computed_style *style,
+ struct box *box)
+{
+ int width, height, max_width, min_width, max_height, min_height;
+ int *margin = box->margin;
+ int *padding = box->padding;
+ struct box_border *border = box->border;
+ enum css_overflow_e overflow_x = css_computed_overflow_x(style);
+ enum css_overflow_e overflow_y = css_computed_overflow_y(style);
+ int scrollbar_width_x =
+ (overflow_x == CSS_OVERFLOW_SCROLL ||
+ overflow_x == CSS_OVERFLOW_AUTO) ?
+ SCROLLBAR_WIDTH : 0;
+ int scrollbar_width_y =
+ (overflow_y == CSS_OVERFLOW_SCROLL ||
+ overflow_y == CSS_OVERFLOW_AUTO) ?
+ SCROLLBAR_WIDTH : 0;
+
+ layout_find_dimensions(len_ctx, available_width, -1, box, style,
+ &width, &height, &max_width, &min_width,
+ &max_height, &min_height, margin, padding, border);
+
+ if (margin[LEFT] == AUTO)
+ margin[LEFT] = 0;
+ if (margin[RIGHT] == AUTO)
+ margin[RIGHT] = 0;
+
+ if (box->gadget == NULL) {
+ padding[RIGHT] += scrollbar_width_y;
+ padding[BOTTOM] += scrollbar_width_x;
+ }
+
+ if (box->object && !(box->flags & REPLACE_DIM) &&
+ content_get_type(box->object) != CONTENT_HTML) {
+ /* Floating replaced element, with intrinsic width or height.
+ * See 10.3.6 and 10.6.2 */
+ layout_get_object_dimensions(box, &width, &height,
+ min_width, max_width, min_height, max_height);
+ } else if (box->gadget && (box->gadget->type == GADGET_TEXTBOX ||
+ box->gadget->type == GADGET_PASSWORD ||
+ box->gadget->type == GADGET_FILE ||
+ box->gadget->type == GADGET_TEXTAREA)) {
+ css_fixed size = 0;
+ css_unit unit = CSS_UNIT_EM;
+
+ /* Give sensible dimensions to gadgets, with auto width/height,
+ * that don't shrink to fit contained text. */
+ assert(box->style);
+
+ if (box->gadget->type == GADGET_TEXTBOX ||
+ box->gadget->type == GADGET_PASSWORD ||
+ box->gadget->type == GADGET_FILE) {
+ if (width == AUTO) {
+ size = INTTOFIX(10);
+ width = FIXTOINT(nscss_len2px(len_ctx,
+ size, unit, box->style));
+ }
+ if (box->gadget->type == GADGET_FILE &&
+ height == AUTO) {
+ size = FLTTOFIX(1.5);
+ height = FIXTOINT(nscss_len2px(len_ctx,
+ size, unit, box->style));
+ }
+ }
+ if (box->gadget->type == GADGET_TEXTAREA) {
+ if (width == AUTO) {
+ size = INTTOFIX(10);
+ width = FIXTOINT(nscss_len2px(len_ctx,
+ size, unit, box->style));
+ }
+ if (height == AUTO) {
+ size = INTTOFIX(4);
+ height = FIXTOINT(nscss_len2px(len_ctx,
+ size, unit, box->style));
+ }
+ }
+ } else if (width == AUTO) {
+ /* CSS 2.1 section 10.3.5 */
+ width = min(max(box->min_width, available_width),
+ box->max_width);
+
+ /* width includes margin, borders and padding */
+ if (width == available_width) {
+ width -= box->margin[LEFT] + box->border[LEFT].width +
+ box->padding[LEFT] +
+ box->padding[RIGHT] +
+ box->border[RIGHT].width +
+ box->margin[RIGHT];
+ } else {
+ /* width was obtained from a min_width or max_width
+ * value, so need to use the same method for calculating
+ * mbp as was used in layout_minmax_block() */
+ int fixed = 0;
+ float frac = 0;
+ calculate_mbp_width(len_ctx, box->style, LEFT,
+ true, true, true, &fixed, &frac);
+ calculate_mbp_width(len_ctx, box->style, RIGHT,
+ true, true, true, &fixed, &frac);
+ if (fixed < 0)
+ fixed = 0;
+
+ width -= fixed;
+ }
+
+ if (max_width >= 0 && width > max_width) width = max_width;
+ if (min_width > 0 && width < min_width) width = min_width;
+
+ } else {
+ if (max_width >= 0 && width > max_width) width = max_width;
+ if (min_width > 0 && width < min_width) width = min_width;
+ width -= scrollbar_width_y;
+ }
+
+ box->width = width;
+ box->height = height;
+
+ if (margin[TOP] == AUTO)
+ margin[TOP] = 0;
+ if (margin[BOTTOM] == AUTO)
+ margin[BOTTOM] = 0;
+}
+
+
+/**
+ * Layout the contents of a float or inline block.
+ *
+ * \param b float or inline block box
+ * \param width available width
+ * \param content memory pool for any new boxes
+ * \return true on success, false on memory exhaustion
+ */
+static bool layout_float(struct box *b, int width, html_content *content)
+{
+ assert(b->type == BOX_TABLE || b->type == BOX_BLOCK ||
+ b->type == BOX_INLINE_BLOCK);
+ layout_float_find_dimensions(&content->len_ctx, width, b->style, b);
+ if (b->type == BOX_TABLE) {
+ if (!layout_table(b, width, content))
+ return false;
+ if (b->margin[LEFT] == AUTO)
+ b->margin[LEFT] = 0;
+ if (b->margin[RIGHT] == AUTO)
+ b->margin[RIGHT] = 0;
+ if (b->margin[TOP] == AUTO)
+ b->margin[TOP] = 0;
+ if (b->margin[BOTTOM] == AUTO)
+ b->margin[BOTTOM] = 0;
+ } else
+ return layout_block_context(b, -1, content);
+ return true;
+}
+
+
+/**
+ * Position a float in the first available space.
+ *
+ * \param c float box to position
+ * \param width available width
+ * \param cx x coordinate relative to cont to place float right of
+ * \param y y coordinate relative to cont to place float below
+ * \param cont ancestor box which defines horizontal space, for floats
+ */
+static void
+place_float_below(struct box *c, int width, int cx, int y, struct box *cont)
+{
+ int x0, x1, yy;
+ struct box *left;
+ struct box *right;
+
+ yy = y > cont->cached_place_below_level ?
+ y : cont->cached_place_below_level;
+
+ NSLOG(layout, DEBUG,
+ "c %p, width %i, cx %i, y %i, cont %p", c,
+ width, cx, y, cont);
+
+ do {
+ y = yy;
+ x0 = cx;
+ x1 = cx + width;
+ find_sides(cont->float_children, y, y + c->height, &x0, &x1,
+ &left, &right);
+ if (left != 0 && right != 0) {
+ yy = (left->y + left->height <
+ right->y + right->height ?
+ left->y + left->height :
+ right->y + right->height);
+ } else if (left == 0 && right != 0) {
+ yy = right->y + right->height;
+ } else if (left != 0 && right == 0) {
+ yy = left->y + left->height;
+ }
+ } while ((left != 0 || right != 0) && (c->width > x1 - x0));
+
+ if (c->type == BOX_FLOAT_LEFT) {
+ c->x = x0;
+ } else {
+ c->x = x1 - c->width;
+ }
+ c->y = y;
+ cont->cached_place_below_level = y;
+}
+
+
+/**
+ * Calculate line height from a style.
+ */
+static int line_height(
+ const nscss_len_ctx *len_ctx,
+ const css_computed_style *style)
+{
+ enum css_line_height_e lhtype;
+ css_fixed lhvalue = 0;
+ css_unit lhunit = CSS_UNIT_PX;
+ css_fixed line_height;
+
+ assert(style);
+
+ lhtype = css_computed_line_height(style, &lhvalue, &lhunit);
+ if (lhtype == CSS_LINE_HEIGHT_NORMAL) {
+ /* Normal => use a constant of 1.3 * font-size */
+ lhvalue = FLTTOFIX(1.3);
+ lhtype = CSS_LINE_HEIGHT_NUMBER;
+ }
+
+ if (lhtype == CSS_LINE_HEIGHT_NUMBER ||
+ lhunit == CSS_UNIT_PCT) {
+ line_height = nscss_len2px(len_ctx,
+ lhvalue, CSS_UNIT_EM, style);
+
+ if (lhtype != CSS_LINE_HEIGHT_NUMBER)
+ line_height = FDIV(line_height, F_100);
+ } else {
+ assert(lhunit != CSS_UNIT_PCT);
+
+ line_height = nscss_len2px(len_ctx,
+ lhvalue, lhunit, style);
+ }
+
+ return FIXTOINT(line_height);
+}
+
+
+/**
+ * Position a line of boxes in inline formatting context.
+ *
+ * \param first box at start of line
+ * \param width available width on input, updated with actual width on output
+ * (may be incorrect if the line gets split?)
+ * \param y coordinate of top of line, updated on exit to bottom
+ * \param cx coordinate of left of line relative to cont
+ * \param cy coordinate of top of line relative to cont
+ * \param cont ancestor box which defines horizontal space, for floats
+ * \param indent apply any first-line indent
+ * \param has_text_children at least one TEXT in the inline_container
+ * \param next_box updated to first box for next line, or 0 at end
+ * \param content memory pool for any new boxes
+ * \return true on success, false on memory exhaustion
+ */
+static bool
+layout_line(struct box *first,
+ int *width,
+ int *y,
+ int cx,
+ int cy,
+ struct box *cont,
+ bool indent,
+ bool has_text_children,
+ html_content *content,
+ struct box **next_box)
+{
+ int height, used_height;
+ int x0 = 0;
+ int x1 = *width;
+ int x, h, x_previous;
+ int fy = cy;
+ struct box *left;
+ struct box *right;
+ struct box *b;
+ struct box *split_box = 0;
+ struct box *d;
+ struct box *br_box = 0;
+ bool move_y = false;
+ bool place_below = false;
+ int space_before = 0, space_after = 0;
+ unsigned int inline_count = 0;
+ unsigned int i;
+ const struct gui_layout_table *font_func = content->font_func;
+ plot_font_style_t fstyle;
+
+ NSLOG(layout, DEBUG,
+ "first %p, first->text '%.*s', width %i, y %i, cx %i, cy %i",
+ first,
+ (int)first->length,
+ first->text,
+ *width,
+ *y,
+ cx,
+ cy);
+
+ /* find sides at top of line */
+ x0 += cx;
+ x1 += cx;
+ find_sides(cont->float_children, cy, cy, &x0, &x1, &left, &right);
+ x0 -= cx;
+ x1 -= cx;
+
+ if (indent)
+ x0 += layout_text_indent(&content->len_ctx,
+ first->parent->parent->style, *width);
+
+ if (x1 < x0)
+ x1 = x0;
+
+ /* get minimum line height from containing block.
+ * this is the line-height if there are text children and also in the
+ * case of an initially empty text input */
+ if (has_text_children || first->parent->parent->gadget)
+ used_height = height = line_height(&content->len_ctx,
+ first->parent->parent->style);
+ else
+ /* inline containers with no text are usually for layout and
+ * look better with no minimum line-height */
+ used_height = height = 0;
+
+ /* pass 1: find height of line assuming sides at top of line: loop
+ * body executed at least once
+ * keep in sync with the loop in layout_minmax_line() */
+
+ NSLOG(layout, DEBUG, "x0 %i, x1 %i, x1 - x0 %i", x0, x1, x1 - x0);
+
+
+ for (x = 0, b = first; x <= x1 - x0 && b != 0; b = b->next) {
+ int min_width, max_width, min_height, max_height;
+
+ assert(b->type == BOX_INLINE || b->type == BOX_INLINE_BLOCK ||
+ b->type == BOX_FLOAT_LEFT ||
+ b->type == BOX_FLOAT_RIGHT ||
+ b->type == BOX_BR || b->type == BOX_TEXT ||
+ b->type == BOX_INLINE_END);
+
+
+ NSLOG(layout, DEBUG, "pass 1: b %p, x %i", b, x);
+
+
+ if (b->type == BOX_BR)
+ break;
+
+ if (b->type == BOX_FLOAT_LEFT || b->type == BOX_FLOAT_RIGHT)
+ continue;
+ if (b->type == BOX_INLINE_BLOCK &&
+ (css_computed_position(b->style) ==
+ CSS_POSITION_ABSOLUTE ||
+ css_computed_position(b->style) ==
+ CSS_POSITION_FIXED))
+ continue;
+
+ assert(b->style != NULL);
+ font_plot_style_from_css(&content->len_ctx, b->style, &fstyle);
+
+ x += space_after;
+
+ if (b->type == BOX_INLINE_BLOCK) {
+ if (b->max_width != UNKNOWN_WIDTH)
+ if (!layout_float(b, *width, content))
+ return false;
+ h = b->border[TOP].width + b->padding[TOP] + b->height +
+ b->padding[BOTTOM] +
+ b->border[BOTTOM].width;
+ if (height < h)
+ height = h;
+ x += b->margin[LEFT] + b->border[LEFT].width +
+ b->padding[LEFT] + b->width +
+ b->padding[RIGHT] +
+ b->border[RIGHT].width +
+ b->margin[RIGHT];
+ space_after = 0;
+ continue;
+ }
+
+ if (b->type == BOX_INLINE) {
+ /* calculate borders, margins, and padding */
+ layout_find_dimensions(&content->len_ctx,
+ *width, -1, b, b->style, 0, 0, 0, 0,
+ 0, 0, b->margin, b->padding, b->border);
+ for (i = 0; i != 4; i++)
+ if (b->margin[i] == AUTO)
+ b->margin[i] = 0;
+ x += b->margin[LEFT] + b->border[LEFT].width +
+ b->padding[LEFT];
+ if (b->inline_end) {
+ b->inline_end->margin[RIGHT] = b->margin[RIGHT];
+ b->inline_end->padding[RIGHT] =
+ b->padding[RIGHT];
+ b->inline_end->border[RIGHT] =
+ b->border[RIGHT];
+ } else {
+ x += b->padding[RIGHT] +
+ b->border[RIGHT].width +
+ b->margin[RIGHT];
+ }
+ } else if (b->type == BOX_INLINE_END) {
+ b->width = 0;
+ if (b->space == UNKNOWN_WIDTH) {
+ font_func->width(&fstyle, " ", 1, &b->space);
+ /** \todo handle errors */
+ }
+ space_after = b->space;
+
+ x += b->padding[RIGHT] + b->border[RIGHT].width +
+ b->margin[RIGHT];
+ continue;
+ }
+
+ if (!b->object && !(b->flags & IFRAME) && !b->gadget &&
+ !(b->flags & REPLACE_DIM)) {
+ /* inline non-replaced, 10.3.1 and 10.6.1 */
+ b->height = line_height(&content->len_ctx,
+ b->style ? b->style :
+ b->parent->parent->style);
+ if (height < b->height)
+ height = b->height;
+
+ if (!b->text) {
+ b->width = 0;
+ space_after = 0;
+ continue;
+ }
+
+ if (b->width == UNKNOWN_WIDTH) {
+ /** \todo handle errors */
+
+ /* If it's a select element, we must use the
+ * width of the widest option text */
+ if (b->parent->parent->gadget &&
+ b->parent->parent->gadget->type
+ == GADGET_SELECT) {
+ int opt_maxwidth = 0;
+ struct form_option *o;
+
+ for (o = b->parent->parent->gadget->
+ data.select.items; o;
+ o = o->next) {
+ int opt_width;
+ font_func->width(&fstyle,
+ o->text,
+ strlen(o->text),
+ &opt_width);
+
+ if (opt_maxwidth < opt_width)
+ opt_maxwidth =opt_width;
+ }
+ b->width = opt_maxwidth;
+ if (nsoption_bool(core_select_menu))
+ b->width += SCROLLBAR_WIDTH;
+ } else {
+ font_func->width(&fstyle, b->text,
+ b->length, &b->width);
+ b->flags |= MEASURED;
+ }
+ }
+
+ /* If the current text has not been measured (i.e. its
+ * width was estimated after splitting), and it fits on
+ * the line, measure it properly, so next box is placed
+ * correctly. */
+ if (b->text && (x + b->width < x1 - x0) &&
+ !(b->flags & MEASURED) &&
+ b->next) {
+ font_func->width(&fstyle, b->text,
+ b->length, &b->width);
+ b->flags |= MEASURED;
+ }
+
+ x += b->width;
+ if (b->space == UNKNOWN_WIDTH) {
+ font_func->width(&fstyle, " ", 1, &b->space);
+ /** \todo handle errors */
+ }
+ space_after = b->space;
+ continue;
+ }
+
+ space_after = 0;
+
+ /* inline replaced, 10.3.2 and 10.6.2 */
+ assert(b->style);
+
+ layout_find_dimensions(&content->len_ctx,
+ *width, -1, b, b->style,
+ &b->width, &b->height,
+ &max_width, &min_width,
+ &max_height, &min_height,
+ NULL, NULL, NULL);
+
+ if (b->object && !(b->flags & REPLACE_DIM)) {
+ layout_get_object_dimensions(b, &b->width, &b->height,
+ min_width, max_width,
+ min_height, max_height);
+ } else if (b->flags & IFRAME) {
+ /* TODO: should we look at the content dimensions? */
+ if (b->width == AUTO)
+ b->width = 400;
+ if (b->height == AUTO)
+ b->height = 300;
+
+ /* We reformat the iframe browser window to new
+ * dimensions in pass 2 */
+ } else {
+ /* form control with no object */
+ if (b->width == AUTO)
+ b->width = FIXTOINT(nscss_len2px(
+ &content->len_ctx, INTTOFIX(1),
+ CSS_UNIT_EM, b->style));
+ if (b->height == AUTO)
+ b->height = FIXTOINT(nscss_len2px(
+ &content->len_ctx, INTTOFIX(1),
+ CSS_UNIT_EM, b->style));
+ }
+
+ /* Reformat object to new box size */
+ if (b->object && content_get_type(b->object) == CONTENT_HTML &&
+ b->width !=
+ content_get_available_width(b->object)) {
+ css_fixed value = 0;
+ css_unit unit = CSS_UNIT_PX;
+ enum css_height_e htype = css_computed_height(b->style,
+ &value, &unit);
+
+ content_reformat(b->object, false, b->width, b->height);
+
+ if (htype == CSS_HEIGHT_AUTO)
+ b->height = content_get_height(b->object);
+ }
+
+ if (height < b->height)
+ height = b->height;
+
+ x += b->width;
+ }
+
+ /* find new sides using this height */
+ x0 = cx;
+ x1 = cx + *width;
+ find_sides(cont->float_children, cy, cy + height, &x0, &x1,
+ &left, &right);
+ x0 -= cx;
+ x1 -= cx;
+
+ if (indent)
+ x0 += layout_text_indent(&content->len_ctx,
+ first->parent->parent->style, *width);
+
+ if (x1 < x0)
+ x1 = x0;
+
+ space_after = space_before = 0;
+
+ /* pass 2: place boxes in line: loop body executed at least once */
+
+ NSLOG(layout, DEBUG, "x0 %i, x1 %i, x1 - x0 %i", x0, x1, x1 - x0);
+
+ for (x = x_previous = 0, b = first; x <= x1 - x0 && b; b = b->next) {
+
+ NSLOG(layout, DEBUG, "pass 2: b %p, x %i", b, x);
+
+ if (b->type == BOX_INLINE_BLOCK &&
+ (css_computed_position(b->style) ==
+ CSS_POSITION_ABSOLUTE ||
+ css_computed_position(b->style) ==
+ CSS_POSITION_FIXED)) {
+ b->x = x + space_after;
+
+ } else if (b->type == BOX_INLINE ||
+ b->type == BOX_INLINE_BLOCK ||
+ b->type == BOX_TEXT ||
+ b->type == BOX_INLINE_END) {
+ assert(b->width != UNKNOWN_WIDTH);
+
+ x_previous = x;
+ x += space_after;
+ b->x = x;
+
+ if ((b->type == BOX_INLINE && !b->inline_end) ||
+ b->type == BOX_INLINE_BLOCK) {
+ b->x += b->margin[LEFT] + b->border[LEFT].width;
+ x = b->x + b->padding[LEFT] + b->width +
+ b->padding[RIGHT] +
+ b->border[RIGHT].width +
+ b->margin[RIGHT];
+ } else if (b->type == BOX_INLINE) {
+ b->x += b->margin[LEFT] + b->border[LEFT].width;
+ x = b->x + b->padding[LEFT] + b->width;
+ } else if (b->type == BOX_INLINE_END) {
+ b->height = b->inline_end->height;
+ x += b->padding[RIGHT] +
+ b->border[RIGHT].width +
+ b->margin[RIGHT];
+ } else {
+ x += b->width;
+ }
+
+ space_before = space_after;
+ if (b->object || b->flags & REPLACE_DIM ||
+ b->flags & IFRAME)
+ space_after = 0;
+ else if (b->text || b->type == BOX_INLINE_END) {
+ if (b->space == UNKNOWN_WIDTH) {
+ font_plot_style_from_css(
+ &content->len_ctx,
+ b->style, &fstyle);
+ /** \todo handle errors */
+ font_func->width(&fstyle, " ", 1,
+ &b->space);
+ }
+ space_after = b->space;
+ } else {
+ space_after = 0;
+ }
+ split_box = b;
+ move_y = true;
+ inline_count++;
+ } else if (b->type == BOX_BR) {
+ b->x = x;
+ b->width = 0;
+ br_box = b;
+ b = b->next;
+ split_box = 0;
+ move_y = true;
+ break;
+
+ } else {
+ /* float */
+ NSLOG(layout, DEBUG, "float %p", b);
+
+ d = b->children;
+ d->float_children = 0;
+ d->cached_place_below_level = 0;
+ b->float_container = d->float_container = cont;
+
+ if (!layout_float(d, *width, content))
+ return false;
+
+ NSLOG(layout, DEBUG,
+ "%p : %d %d",
+ d,
+ d->margin[TOP],
+ d->border[TOP].width);
+
+ d->x = d->margin[LEFT] + d->border[LEFT].width;
+ d->y = d->margin[TOP] + d->border[TOP].width;
+ b->width = d->margin[LEFT] + d->border[LEFT].width +
+ d->padding[LEFT] + d->width +
+ d->padding[RIGHT] +
+ d->border[RIGHT].width +
+ d->margin[RIGHT];
+ b->height = d->margin[TOP] + d->border[TOP].width +
+ d->padding[TOP] + d->height +
+ d->padding[BOTTOM] +
+ d->border[BOTTOM].width +
+ d->margin[BOTTOM];
+
+ if (b->width > (x1 - x0) - x)
+ place_below = true;
+ if (d->style && (css_computed_clear(d->style) ==
+ CSS_CLEAR_NONE ||
+ (css_computed_clear(d->style) ==
+ CSS_CLEAR_LEFT && left == 0) ||
+ (css_computed_clear(d->style) ==
+ CSS_CLEAR_RIGHT &&
+ right == 0) ||
+ (css_computed_clear(d->style) ==
+ CSS_CLEAR_BOTH &&
+ left == 0 && right == 0)) &&
+ (!place_below ||
+ (left == 0 && right == 0 && x == 0)) &&
+ cy >= cont->clear_level &&
+ cy >= cont->cached_place_below_level) {
+ /* + not cleared or,
+ * cleared and there are no floats to clear
+ * + fits without needing to be placed below or,
+ * this line is empty with no floats
+ * + current y, cy, is below the clear level
+ *
+ * Float affects current line */
+ if (b->type == BOX_FLOAT_LEFT) {
+ b->x = cx + x0;
+ if (b->width > 0)
+ x0 += b->width;
+ left = b;
+ } else {
+ b->x = cx + x1 - b->width;
+ if (b->width > 0)
+ x1 -= b->width;
+ right = b;
+ }
+ b->y = cy;
+ } else {
+ /* cleared or doesn't fit on line */
+ /* place below into next available space */
+ int fcy = (cy > cont->clear_level) ? cy :
+ cont->clear_level;
+ fcy = (fcy > cont->cached_place_below_level) ?
+ fcy :
+ cont->cached_place_below_level;
+ fy = (fy > fcy) ? fy : fcy;
+ fy = (fy == cy) ? fy + height : fy;
+
+ place_float_below(b, *width, cx, fy, cont);
+ fy = b->y;
+ if (d->style && (
+ (css_computed_clear(d->style) ==
+ CSS_CLEAR_LEFT && left != 0) ||
+ (css_computed_clear(d->style) ==
+ CSS_CLEAR_RIGHT &&
+ right != 0) ||
+ (css_computed_clear(d->style) ==
+ CSS_CLEAR_BOTH &&
+ (left != 0 || right != 0)))) {
+ /* to be cleared below existing
+ * floats */
+ if (b->type == BOX_FLOAT_LEFT)
+ b->x = cx;
+ else
+ b->x = cx + *width - b->width;
+
+ fcy = layout_clear(cont->float_children,
+ css_computed_clear(d->style));
+ if (fcy > cont->clear_level)
+ cont->clear_level = fcy;
+ if (b->y < fcy)
+ b->y = fcy;
+ }
+ if (b->type == BOX_FLOAT_LEFT)
+ left = b;
+ else
+ right = b;
+ }
+ add_float_to_container(cont, b);
+
+ split_box = 0;
+ }
+ }
+
+ if (x1 - x0 < x && split_box) {
+ /* the last box went over the end */
+ size_t split = 0;
+ int w;
+ bool no_wrap = css_computed_white_space(
+ split_box->style) == CSS_WHITE_SPACE_NOWRAP ||
+ css_computed_white_space(
+ split_box->style) == CSS_WHITE_SPACE_PRE;
+
+ x = x_previous;
+
+ if (!no_wrap &&
+ (split_box->type == BOX_INLINE ||
+ split_box->type == BOX_TEXT) &&
+ !split_box->object &&
+ !(split_box->flags & REPLACE_DIM) &&
+ !(split_box->flags & IFRAME) &&
+ !split_box->gadget && split_box->text) {
+
+ font_plot_style_from_css(&content->len_ctx,
+ split_box->style, &fstyle);
+ /** \todo handle errors */
+ font_func->split(&fstyle,
+ split_box->text,
+ split_box->length,
+ x1 - x0 - x - space_before,
+ &split,
+ &w);
+ }
+
+ /* split == 0 implies that text can't be split */
+
+ if (split == 0)
+ w = split_box->width;
+
+
+ NSLOG(layout, DEBUG,
+ "splitting: split_box %p \"%.*s\", spilt %zu, w %i, "
+ "left %p, right %p, inline_count %u",
+ split_box,
+ (int)split_box->length,
+ split_box->text,
+ split,
+ w,
+ left,
+ right,
+ inline_count);
+
+ if ((split == 0 || x1 - x0 <= x + space_before + w) &&
+ !left && !right && inline_count == 1) {
+ /* first word of box doesn't fit, but no floats and
+ * first box on line so force in */
+ if (split == 0 || split == split_box->length) {
+ /* only one word in this box, or not text
+ * or white-space:nowrap */
+ b = split_box->next;
+ } else {
+ /* cut off first word for this line */
+ if (!layout_text_box_split(content, &fstyle,
+ split_box, split, w))
+ return false;
+ b = split_box->next;
+ }
+ x += space_before + w;
+
+ NSLOG(layout, DEBUG, "forcing");
+
+ } else if ((split == 0 || x1 - x0 <= x + space_before + w) &&
+ inline_count == 1) {
+ /* first word of first box doesn't fit, but a float is
+ * taking some of the width so move below it */
+ assert(left || right);
+ used_height = 0;
+ if (left) {
+
+ NSLOG(layout, DEBUG,
+ "cy %i, left->y %i, left->height %i",
+ cy,
+ left->y,
+ left->height);
+
+ used_height = left->y + left->height - cy + 1;
+
+ NSLOG(layout, DEBUG, "used_height %i",
+ used_height);
+
+ }
+ if (right && used_height <
+ right->y + right->height - cy + 1)
+ used_height = right->y + right->height - cy + 1;
+
+ if (used_height < 0)
+ used_height = 0;
+
+ b = split_box;
+
+ NSLOG(layout, DEBUG, "moving below float");
+
+ } else if (split == 0 || x1 - x0 <= x + space_before + w) {
+ /* first word of box doesn't fit so leave box for next
+ * line */
+ b = split_box;
+
+ NSLOG(layout, DEBUG, "leaving for next line");
+
+ } else {
+ /* fit as many words as possible */
+ assert(split != 0);
+
+ NSLOG(layout, DEBUG, "'%.*s' %i %zu %i",
+ (int)split_box->length, split_box->text,
+ x1 - x0, split, w);
+
+ if (split != split_box->length) {
+ if (!layout_text_box_split(content, &fstyle,
+ split_box, split, w))
+ return false;
+ b = split_box->next;
+ }
+ x += space_before + w;
+
+ NSLOG(layout, DEBUG, "fitting words");
+
+ }
+ move_y = true;
+ }
+
+ /* set positions */
+ switch (css_computed_text_align(first->parent->parent->style)) {
+ case CSS_TEXT_ALIGN_RIGHT:
+ case CSS_TEXT_ALIGN_LIBCSS_RIGHT:
+ x0 = x1 - x;
+ break;
+ case CSS_TEXT_ALIGN_CENTER:
+ case CSS_TEXT_ALIGN_LIBCSS_CENTER:
+ x0 = (x0 + (x1 - x)) / 2;
+ break;
+ case CSS_TEXT_ALIGN_LEFT:
+ case CSS_TEXT_ALIGN_LIBCSS_LEFT:
+ case CSS_TEXT_ALIGN_JUSTIFY:
+ /* leave on left */
+ break;
+ case CSS_TEXT_ALIGN_DEFAULT:
+ /* None; consider text direction */
+ switch (css_computed_direction(first->parent->parent->style)) {
+ case CSS_DIRECTION_LTR:
+ /* leave on left */
+ break;
+ case CSS_DIRECTION_RTL:
+ x0 = x1 - x;
+ break;
+ }
+ break;
+ }
+
+ for (d = first; d != b; d = d->next) {
+ d->flags &= ~NEW_LINE;
+
+ if (d->type == BOX_INLINE_BLOCK &&
+ (css_computed_position(d->style) ==
+ CSS_POSITION_ABSOLUTE ||
+ css_computed_position(d->style) ==
+ CSS_POSITION_FIXED)) {
+ /* positioned inline-blocks:
+ * set static position (x,y) only, rest of positioning
+ * is handled later */
+ d->x += x0;
+ d->y = *y;
+ continue;
+ } else if ((d->type == BOX_INLINE &&
+ ((d->object || d->gadget) == false) &&
+ !(d->flags & IFRAME) &&
+ !(d->flags & REPLACE_DIM)) ||
+ d->type == BOX_BR ||
+ d->type == BOX_TEXT ||
+ d->type == BOX_INLINE_END) {
+ /* regular (non-replaced) inlines */
+ d->x += x0;
+ d->y = *y - d->padding[TOP];
+
+ if (d->type == BOX_TEXT && d->height > used_height) {
+ /* text */
+ used_height = d->height;
+ }
+ } else if ((d->type == BOX_INLINE) ||
+ d->type == BOX_INLINE_BLOCK) {
+ /* replaced inlines and inline-blocks */
+ d->x += x0;
+ d->y = *y + d->border[TOP].width + d->margin[TOP];
+ h = d->margin[TOP] + d->border[TOP].width +
+ d->padding[TOP] + d->height +
+ d->padding[BOTTOM] +
+ d->border[BOTTOM].width +
+ d->margin[BOTTOM];
+ if (used_height < h)
+ used_height = h;
+ }
+ }
+
+ first->flags |= NEW_LINE;
+
+ assert(b != first || (move_y && 0 < used_height && (left || right)));
+
+ /* handle vertical-align by adjusting box y values */
+ /** \todo proper vertical alignment handling */
+ for (d = first; d != b; d = d->next) {
+ if ((d->type == BOX_INLINE && d->inline_end) ||
+ d->type == BOX_BR ||
+ d->type == BOX_TEXT ||
+ d->type == BOX_INLINE_END) {
+ css_fixed value = 0;
+ css_unit unit = CSS_UNIT_PX;
+ switch (css_computed_vertical_align(d->style, &value,
+ &unit)) {
+ case CSS_VERTICAL_ALIGN_SUPER:
+ case CSS_VERTICAL_ALIGN_TOP:
+ case CSS_VERTICAL_ALIGN_TEXT_TOP:
+ /* already at top */
+ break;
+ case CSS_VERTICAL_ALIGN_SUB:
+ case CSS_VERTICAL_ALIGN_BOTTOM:
+ case CSS_VERTICAL_ALIGN_TEXT_BOTTOM:
+ d->y += used_height - d->height;
+ break;
+ default:
+ case CSS_VERTICAL_ALIGN_BASELINE:
+ d->y += 0.75 * (used_height - d->height);
+ break;
+ }
+ }
+ }
+
+ /* handle clearance for br */
+ if (br_box && css_computed_clear(br_box->style) != CSS_CLEAR_NONE) {
+ int clear_y = layout_clear(cont->float_children,
+ css_computed_clear(br_box->style));
+ if (used_height < clear_y - cy)
+ used_height = clear_y - cy;
+ }
+
+ if (move_y)
+ *y += used_height;
+ *next_box = b;
+ *width = x; /* return actual width */
+ return true;
+}
+
+
+/**
+ * Layout lines of text or inline boxes with floats.
+ *
+ * \param box inline container box
+ * \param width horizontal space available
+ * \param cont ancestor box which defines horizontal space, for floats
+ * \param cx box position relative to cont
+ * \param cy box position relative to cont
+ * \param content memory pool for any new boxes
+ * \return true on success, false on memory exhaustion
+ */
+static bool layout_inline_container(struct box *inline_container, int width,
+ struct box *cont, int cx, int cy, html_content *content)
+{
+ bool first_line = true;
+ bool has_text_children;
+ struct box *c, *next;
+ int y = 0;
+ int curwidth,maxwidth = width;
+
+ assert(inline_container->type == BOX_INLINE_CONTAINER);
+
+ NSLOG(layout, DEBUG,
+ "inline_container %p, width %i, cont %p, cx %i, cy %i",
+ inline_container,
+ width,
+ cont,
+ cx,
+ cy);
+
+
+ has_text_children = false;
+ for (c = inline_container->children; c; c = c->next) {
+ bool is_pre = false;
+
+ if (c->style) {
+ enum css_white_space_e whitespace;
+
+ whitespace = css_computed_white_space(c->style);
+
+ is_pre = (whitespace == CSS_WHITE_SPACE_PRE ||
+ whitespace == CSS_WHITE_SPACE_PRE_LINE ||
+ whitespace == CSS_WHITE_SPACE_PRE_WRAP);
+ }
+
+ if ((!c->object && !(c->flags & REPLACE_DIM) &&
+ !(c->flags & IFRAME) &&
+ c->text && (c->length || is_pre)) ||
+ c->type == BOX_BR)
+ has_text_children = true;
+ }
+
+ /** \todo fix wrapping so that a box with horizontal scrollbar will
+ * shrink back to 'width' if no word is wider than 'width' (Or just set
+ * curwidth = width and have the multiword lines wrap to the min width)
+ */
+ for (c = inline_container->children; c; ) {
+
+ NSLOG(layout, DEBUG, "c %p", c);
+
+ curwidth = inline_container->width;
+ if (!layout_line(c, &curwidth, &y, cx, cy + y, cont, first_line,
+ has_text_children, content, &next))
+ return false;
+ maxwidth = max(maxwidth,curwidth);
+ c = next;
+ first_line = false;
+ }
+
+ inline_container->width = maxwidth;
+ inline_container->height = y;
+
+ return true;
+}
+
+
+/**
+ * Layout a block formatting context.
+ *
+ * \param block BLOCK, INLINE_BLOCK, or TABLE_CELL to layout
+ * \param viewport_height Height of viewport in pixels or -ve if unknown
+ * \param content Memory pool for any new boxes
+ * \return true on success, false on memory exhaustion
+ *
+ * This function carries out layout of a block and its children, as described
+ * in CSS 2.1 9.4.1.
+ */
+static bool
+layout_block_context(struct box *block,
+ int viewport_height,
+ html_content *content)
+{
+ struct box *box;
+ int cx, cy; /**< current coordinates */
+ int max_pos_margin = 0;
+ int max_neg_margin = 0;
+ int y = 0;
+ int lm, rm;
+ struct box *margin_collapse = NULL;
+ bool in_margin = false;
+ css_fixed gadget_size;
+ css_unit gadget_unit; /* Checkbox / radio buttons */
+
+ assert(block->type == BOX_BLOCK ||
+ block->type == BOX_INLINE_BLOCK ||
+ block->type == BOX_TABLE_CELL);
+ assert(block->width != UNKNOWN_WIDTH);
+ assert(block->width != AUTO);
+
+ block->float_children = NULL;
+ block->cached_place_below_level = 0;
+ block->clear_level = 0;
+
+ /* special case if the block contains an object */
+ if (block->object) {
+ int temp_width = block->width;
+ if (!layout_block_object(block))
+ return false;
+ layout_get_object_dimensions(block, &temp_width,
+ &block->height, INT_MIN, INT_MAX,
+ INT_MIN, INT_MAX);
+ return true;
+ } else if (block->flags & REPLACE_DIM) {
+ return true;
+ }
+
+ /* special case if the block contains an radio button or checkbox */
+ if (block->gadget && (block->gadget->type == GADGET_RADIO ||
+ block->gadget->type == GADGET_CHECKBOX)) {
+ /* form checkbox or radio button
+ * if width or height is AUTO, set it to 1em */
+ gadget_unit = CSS_UNIT_EM;
+ gadget_size = INTTOFIX(1);
+ if (block->height == AUTO)
+ block->height = FIXTOINT(nscss_len2px(
+ &content->len_ctx, gadget_size,
+ gadget_unit, block->style));
+ }
+
+ box = block->children;
+ /* set current coordinates to top-left of the block */
+ cx = 0;
+ y = cy = block->padding[TOP];
+ if (box)
+ box->y = block->padding[TOP];
+
+ /* Step through the descendants of the block in depth-first order, but
+ * not into the children of boxes which aren't blocks. For example, if
+ * the tree passed to this function looks like this (box->type shown):
+ *
+ * block -> BOX_BLOCK
+ * BOX_BLOCK * (1)
+ * BOX_INLINE_CONTAINER * (2)
+ * BOX_INLINE
+ * BOX_TEXT
+ * ...
+ * BOX_BLOCK * (3)
+ * BOX_TABLE * (4)
+ * BOX_TABLE_ROW
+ * BOX_TABLE_CELL
+ * ...
+ * BOX_TABLE_CELL
+ * ...
+ * BOX_BLOCK * (5)
+ * BOX_INLINE_CONTAINER * (6)
+ * BOX_TEXT
+ * ...
+ * then the while loop will visit each box marked with *, setting box
+ * to each in the order shown. */
+ while (box) {
+ enum css_overflow_e overflow_x = CSS_OVERFLOW_VISIBLE;
+ enum css_overflow_e overflow_y = CSS_OVERFLOW_VISIBLE;
+
+ assert(box->type == BOX_BLOCK || box->type == BOX_TABLE ||
+ box->type == BOX_INLINE_CONTAINER);
+
+ /* Tables are laid out before being positioned, because the
+ * position depends on the width which is calculated in
+ * table layout. Blocks and inline containers are positioned
+ * before being laid out, because width is not dependent on
+ * content, and the position is required during layout for
+ * correct handling of floats.
+ */
+
+ if (box->style &&
+ (css_computed_position(box->style) ==
+ CSS_POSITION_ABSOLUTE ||
+ css_computed_position(box->style) ==
+ CSS_POSITION_FIXED)) {
+ box->x = box->parent->padding[LEFT];
+ /* absolute positioned; this element will establish
+ * its own block context when it gets laid out later,
+ * so no need to look at its children now. */
+ goto advance_to_next_box;
+ }
+
+ /* If we don't know which box the current margin collapses
+ * through to, find out. Update the pos/neg margin values. */
+ if (margin_collapse == NULL) {
+ margin_collapse = layout_next_margin_block(
+ &content->len_ctx, box, block,
+ viewport_height,
+ &max_pos_margin, &max_neg_margin);
+ /* We have a margin that has not yet been applied. */
+ in_margin = true;
+ }
+
+ /* Clearance. */
+ y = 0;
+ if (box->style && css_computed_clear(box->style) !=
+ CSS_CLEAR_NONE)
+ y = layout_clear(block->float_children,
+ css_computed_clear(box->style));
+
+ /* Find box's overflow properties */
+ if (box->style) {
+ overflow_x = css_computed_overflow_x(box->style);
+ overflow_y = css_computed_overflow_y(box->style);
+ }
+
+ /* Blocks establishing a block formatting context get minimum
+ * left and right margins to avoid any floats. */
+ lm = rm = 0;
+
+ if (box->type == BOX_BLOCK || box->flags & IFRAME) {
+ if (!box->object && !(box->flags & IFRAME) &&
+ !(box->flags & REPLACE_DIM) &&
+ box->style &&
+ (overflow_x != CSS_OVERFLOW_VISIBLE ||
+ overflow_y != CSS_OVERFLOW_VISIBLE)) {
+ /* box establishes new block formatting context
+ * so available width may be diminished due to
+ * floats. */
+ int x0, x1, top;
+ struct box *left, *right;
+ top = cy + max_pos_margin - max_neg_margin;
+ top = (top > y) ? top : y;
+ x0 = cx;
+ x1 = cx + box->parent->width -
+ box->parent->padding[LEFT] -
+ box->parent->padding[RIGHT];
+ find_sides(block->float_children, top, top,
+ &x0, &x1, &left, &right);
+ /* calculate min required left & right margins
+ * needed to avoid floats */
+ lm = x0 - cx;
+ rm = cx + box->parent->width -
+ box->parent->padding[LEFT] -
+ box->parent->padding[RIGHT] -
+ x1;
+ }
+ layout_block_find_dimensions(&content->len_ctx,
+ box->parent->width,
+ viewport_height, lm, rm, box);
+ if (box->type == BOX_BLOCK && !(box->flags & IFRAME)) {
+ layout_block_add_scrollbar(box, RIGHT);
+ layout_block_add_scrollbar(box, BOTTOM);
+ }
+ } else if (box->type == BOX_TABLE) {
+ if (box->style != NULL) {
+ enum css_width_e wtype;
+ css_fixed width = 0;
+ css_unit unit = CSS_UNIT_PX;
+
+ wtype = css_computed_width(box->style, &width,
+ &unit);
+
+ if (wtype == CSS_WIDTH_AUTO) {
+ /* max available width may be
+ * diminished due to floats. */
+ int x0, x1, top;
+ struct box *left, *right;
+ top = cy + max_pos_margin -
+ max_neg_margin;
+ top = (top > y) ? top : y;
+ x0 = cx;
+ x1 = cx + box->parent->width -
+ box->parent->padding[LEFT] -
+ box->parent->padding[RIGHT];
+ find_sides(block->float_children,
+ top, top, &x0, &x1,
+ &left, &right);
+ /* calculate min required left & right
+ * margins needed to avoid floats */
+ lm = x0 - cx;
+ rm = cx + box->parent->width -
+ box->parent->padding[LEFT] -
+ box->parent->padding[RIGHT] -
+ x1;
+ }
+ }
+ if (!layout_table(box, box->parent->width - lm - rm,
+ content))
+ return false;
+ layout_solve_width(box, box->parent->width, box->width,
+ lm, rm, -1, -1);
+ }
+
+ /* Position box: horizontal. */
+ box->x = box->parent->padding[LEFT] + box->margin[LEFT] +
+ box->border[LEFT].width;
+ cx += box->x;
+
+ /* Position box: vertical. */
+ if (box->border[TOP].width) {
+ box->y += box->border[TOP].width;
+ cy += box->border[TOP].width;
+ }
+
+ /* Vertical margin */
+ if (((box->type == BOX_BLOCK &&
+ (box->flags & HAS_HEIGHT)) ||
+ box->type == BOX_TABLE ||
+ (box->type == BOX_INLINE_CONTAINER &&
+ box != box->parent->children) ||
+ margin_collapse == box) &&
+ in_margin == true) {
+ /* Margin goes above this box. */
+ cy += max_pos_margin - max_neg_margin;
+ box->y += max_pos_margin - max_neg_margin;
+
+ /* Current margin has been applied. */
+ in_margin = false;
+ max_pos_margin = max_neg_margin = 0;
+ }
+
+ /* Handle clearance */
+ if (box->type != BOX_INLINE_CONTAINER &&
+ (y > 0) && (cy < y)) {
+ /* box clears something*/
+ box->y += y - cy;
+ cy = y;
+ }
+
+ /* Unless the box has an overflow style of visible, the box
+ * establishes a new block context. */
+ if (box->type == BOX_BLOCK && box->style &&
+ (overflow_x != CSS_OVERFLOW_VISIBLE ||
+ overflow_y != CSS_OVERFLOW_VISIBLE)) {
+
+ layout_block_context(box, viewport_height, content);
+
+ cy += box->padding[TOP];
+
+ if (box->height == AUTO) {
+ box->height = 0;
+ layout_block_add_scrollbar(box, BOTTOM);
+ }
+
+ cx -= box->x;
+ cy += box->height + box->padding[BOTTOM] +
+ box->border[BOTTOM].width;
+ y = box->y + box->padding[TOP] + box->height +
+ box->padding[BOTTOM] +
+ box->border[BOTTOM].width;
+
+ /* Skip children, because they are done in the new
+ * block context */
+ goto advance_to_next_box;
+ }
+
+ NSLOG(layout, DEBUG, "box %p, cx %i, cy %i", box, cx, cy);
+
+ /* Layout (except tables). */
+ if (box->object) {
+ if (!layout_block_object(box))
+ return false;
+
+ } else if (box->type == BOX_INLINE_CONTAINER) {
+ box->width = box->parent->width;
+ if (!layout_inline_container(box, box->width, block,
+ cx, cy, content))
+ return false;
+
+ } else if (box->type == BOX_TABLE) {
+ /* Move down to avoid floats if necessary. */
+ int x0, x1;
+ struct box *left, *right;
+ y = cy;
+ while (1) {
+ enum css_width_e wtype;
+ css_fixed width = 0;
+ css_unit unit = CSS_UNIT_PX;
+
+ wtype = css_computed_width(box->style,
+ &width, &unit);
+
+ x0 = cx;
+ x1 = cx + box->parent->width;
+ find_sides(block->float_children, y,
+ y + box->height,
+ &x0, &x1, &left, &right);
+ if (wtype == CSS_WIDTH_AUTO)
+ break;
+ if (box->width <= x1 - x0)
+ break;
+ if (!left && !right)
+ break;
+ else if (!left)
+ y = right->y + right->height + 1;
+ else if (!right)
+ y = left->y + left->height + 1;
+ else if (left->y + left->height <
+ right->y + right->height)
+ y = left->y + left->height + 1;
+ else
+ y = right->y + right->height + 1;
+ }
+ box->x += x0 - cx;
+ cx = x0;
+ box->y += y - cy;
+ cy = y;
+ }
+
+ /* Advance to next box. */
+ if (box->type == BOX_BLOCK && !box->object && !(box->iframe) &&
+ box->children) {
+ /* Down into children. */
+
+ if (box == margin_collapse) {
+ /* Current margin collapsed though to this box.
+ * Unset margin_collapse. */
+ margin_collapse = NULL;
+ }
+
+ y = box->padding[TOP];
+ box = box->children;
+ box->y = y;
+ cy += y;
+ continue;
+ } else if (box->type == BOX_BLOCK || box->object ||
+ box->flags & IFRAME)
+ cy += box->padding[TOP];
+
+ if (box->type == BOX_BLOCK && box->height == AUTO) {
+ box->height = 0;
+ layout_block_add_scrollbar(box, BOTTOM);
+ }
+
+ cy += box->height + box->padding[BOTTOM] +
+ box->border[BOTTOM].width;
+ cx -= box->x;
+ y = box->y + box->padding[TOP] + box->height +
+ box->padding[BOTTOM] +
+ box->border[BOTTOM].width;
+
+ advance_to_next_box:
+ if (!box->next) {
+ /* No more siblings:
+ * up to first ancestor with a sibling. */
+
+ do {
+ if (box == margin_collapse) {
+ /* Current margin collapsed though to
+ * this box. Unset margin_collapse. */
+ margin_collapse = NULL;
+ }
+
+ /* Apply bottom margin */
+ if (max_pos_margin < box->margin[BOTTOM])
+ max_pos_margin = box->margin[BOTTOM];
+ else if (max_neg_margin < -box->margin[BOTTOM])
+ max_neg_margin = -box->margin[BOTTOM];
+
+ box = box->parent;
+ if (box == block)
+ break;
+
+ /* Margin is invalidated if this is a box
+ * margins can't collapse through. */
+ if (box->type == BOX_BLOCK &&
+ box->flags & MAKE_HEIGHT) {
+ margin_collapse = NULL;
+ in_margin = false;
+ max_pos_margin = max_neg_margin = 0;
+ }
+
+ if (box->height == AUTO) {
+ box->height = y - box->padding[TOP];
+
+ if (box->type == BOX_BLOCK)
+ layout_block_add_scrollbar(box,
+ BOTTOM);
+ } else
+ cy += box->height -
+ (y - box->padding[TOP]);
+
+ /* Apply any min-height and max-height to
+ * boxes in normal flow */
+ if (box->style &&
+ css_computed_position(box->style) !=
+ CSS_POSITION_ABSOLUTE &&
+ layout_apply_minmax_height(
+ &content->len_ctx,
+ box, NULL)) {
+ /* Height altered */
+ /* Set current cy */
+ cy += box->height -
+ (y - box->padding[TOP]);
+ }
+
+ cy += box->padding[BOTTOM] +
+ box->border[BOTTOM].width;
+ cx -= box->x;
+ y = box->y + box->padding[TOP] + box->height +
+ box->padding[BOTTOM] +
+ box->border[BOTTOM].width;
+
+ } while (box->next == NULL);
+ if (box == block)
+ break;
+ }
+
+ /* To next sibling. */
+
+ if (box == margin_collapse) {
+ /* Current margin collapsed though to this box.
+ * Unset margin_collapse. */
+ margin_collapse = NULL;
+ }
+
+ if (max_pos_margin < box->margin[BOTTOM])
+ max_pos_margin = box->margin[BOTTOM];
+ else if (max_neg_margin < -box->margin[BOTTOM])
+ max_neg_margin = -box->margin[BOTTOM];
+
+ box = box->next;
+ box->y = y;
+ }
+
+ /* Account for bottom margin of last contained block */
+ cy += max_pos_margin - max_neg_margin;
+
+ /* Increase height to contain any floats inside (CSS 2.1 10.6.7). */
+ for (box = block->float_children; box; box = box->next_float) {
+ y = box->y + box->height + box->padding[BOTTOM] +
+ box->border[BOTTOM].width + box->margin[BOTTOM];
+ if (cy < y)
+ cy = y;
+ }
+
+ if (block->height == AUTO) {
+ block->height = cy - block->padding[TOP];
+ if (block->type == BOX_BLOCK)
+ layout_block_add_scrollbar(block, BOTTOM);
+ }
+
+ if (block->style && css_computed_position(block->style) !=
+ CSS_POSITION_ABSOLUTE) {
+ /* Block is in normal flow */
+ layout_apply_minmax_height(&content->len_ctx, block, NULL);
+ }
+
+ if (block->gadget &&
+ (block->gadget->type == GADGET_TEXTAREA ||
+ block->gadget->type == GADGET_PASSWORD ||
+ block->gadget->type == GADGET_TEXTBOX)) {
+ plot_font_style_t fstyle;
+ int ta_width = block->padding[LEFT] + block->width +
+ block->padding[RIGHT];
+ int ta_height = block->padding[TOP] + block->height +
+ block->padding[BOTTOM];
+ font_plot_style_from_css(&content->len_ctx,
+ block->style, &fstyle);
+ fstyle.background = NS_TRANSPARENT;
+ textarea_set_layout(block->gadget->data.text.ta,
+ &fstyle, ta_width, ta_height,
+ block->padding[TOP], block->padding[RIGHT],
+ block->padding[BOTTOM], block->padding[LEFT]);
+ }
+
+ return true;
+}
+
+
+/**
+ * Layout list markers.
+ */
+static void
+layout_lists(struct box *box,
+ const struct gui_layout_table *font_func,
+ const nscss_len_ctx *len_ctx)
+{
+ struct box *child;
+ struct box *marker;
+ plot_font_style_t fstyle;
+
+ for (child = box->children; child; child = child->next) {
+ if (child->list_marker) {
+ marker = child->list_marker;
+ if (marker->object) {
+ marker->width =
+ content_get_width(marker->object);
+ marker->x = -marker->width;
+ marker->height =
+ content_get_height(marker->object);
+ marker->y = (line_height(len_ctx,
+ marker->style) -
+ marker->height) / 2;
+ } else if (marker->text) {
+ if (marker->width == UNKNOWN_WIDTH) {
+ font_plot_style_from_css(len_ctx,
+ marker->style, &fstyle);
+ font_func->width(&fstyle,
+ marker->text,
+ marker->length,
+ &marker->width);
+ marker->flags |= MEASURED;
+ }
+ marker->x = -marker->width;
+ marker->y = 0;
+ marker->height = line_height(len_ctx,
+ marker->style);
+ } else {
+ marker->x = 0;
+ marker->y = 0;
+ marker->width = 0;
+ marker->height = 0;
+ }
+ /* Gap between marker and content */
+ marker->x -= 4;
+ }
+ layout_lists(child, font_func, len_ctx);
+ }
+}
+
+
+/**
+ * Compute box offsets for a relatively or absolutely positioned box with
+ * respect to a box.
+ *
+ * \param len_ctx Length conversion context
+ * \param box box to compute offsets for
+ * \param containing_block box to compute percentages with respect to
+ * \param top updated to top offset, or AUTO
+ * \param right updated to right offset, or AUTO
+ * \param bottom updated to bottom offset, or AUTO
+ * \param left updated to left offset, or AUTO
+ *
+ * See CSS 2.1 9.3.2. containing_block must have width and height.
+ */
+static void
+layout_compute_offsets(const nscss_len_ctx *len_ctx,
+ struct box *box,
+ struct box *containing_block,
+ int *top,
+ int *right,
+ int *bottom,
+ int *left)
+{
+ uint32_t type;
+ css_fixed value = 0;
+ css_unit unit = CSS_UNIT_PX;
+
+ assert(containing_block->width != UNKNOWN_WIDTH &&
+ containing_block->width != AUTO &&
+ containing_block->height != AUTO);
+
+ /* left */
+ type = css_computed_left(box->style, &value, &unit);
+ if (type == CSS_LEFT_SET) {
+ if (unit == CSS_UNIT_PCT) {
+ *left = FPCT_OF_INT_TOINT(value,
+ containing_block->width);
+ } else {
+ *left = FIXTOINT(nscss_len2px(len_ctx,
+ value, unit, box->style));
+ }
+ } else {
+ *left = AUTO;
+ }
+
+ /* right */
+ type = css_computed_right(box->style, &value, &unit);
+ if (type == CSS_RIGHT_SET) {
+ if (unit == CSS_UNIT_PCT) {
+ *right = FPCT_OF_INT_TOINT(value,
+ containing_block->width);
+ } else {
+ *right = FIXTOINT(nscss_len2px(len_ctx,
+ value, unit, box->style));
+ }
+ } else {
+ *right = AUTO;
+ }
+
+ /* top */
+ type = css_computed_top(box->style, &value, &unit);
+ if (type == CSS_TOP_SET) {
+ if (unit == CSS_UNIT_PCT) {
+ *top = FPCT_OF_INT_TOINT(value,
+ containing_block->height);
+ } else {
+ *top = FIXTOINT(nscss_len2px(len_ctx,
+ value, unit, box->style));
+ }
+ } else {
+ *top = AUTO;
+ }
+
+ /* bottom */
+ type = css_computed_bottom(box->style, &value, &unit);
+ if (type == CSS_BOTTOM_SET) {
+ if (unit == CSS_UNIT_PCT) {
+ *bottom = FPCT_OF_INT_TOINT(value,
+ containing_block->height);
+ } else {
+ *bottom = FIXTOINT(nscss_len2px(len_ctx,
+ value, unit, box->style));
+ }
+ } else {
+ *bottom = AUTO;
+ }
+}
+
+
+/**
+ * Layout and position an absolutely positioned box.
+ *
+ * \param box absolute box to layout and position
+ * \param containing_block containing block
+ * \param cx position of box relative to containing_block
+ * \param cy position of box relative to containing_block
+ * \param content memory pool for any new boxes
+ * \return true on success, false on memory exhaustion
+ */
+static bool
+layout_absolute(struct box *box,
+ struct box *containing_block,
+ int cx, int cy,
+ html_content *content)
+{
+ int static_left, static_top; /* static position */
+ int top, right, bottom, left;
+ int width, height, max_width, min_width;
+ int *margin = box->margin;
+ int *padding = box->padding;
+ struct box_border *border = box->border;
+ int available_width = containing_block->width;
+ int space;
+
+ assert(box->type == BOX_BLOCK || box->type == BOX_TABLE ||
+ box->type == BOX_INLINE_BLOCK);
+
+ /* The static position is where the box would be if it was not
+ * absolutely positioned. The x and y are filled in by
+ * layout_block_context(). */
+ static_left = cx + box->x;
+ static_top = cy + box->y;
+
+ if (containing_block->type == BOX_BLOCK ||
+ containing_block->type == BOX_INLINE_BLOCK ||
+ containing_block->type == BOX_TABLE_CELL) {
+ /* Block level container => temporarily increase containing
+ * block dimensions to include padding (we restore this
+ * again at the end) */
+ containing_block->width += containing_block->padding[LEFT] +
+ containing_block->padding[RIGHT];
+ containing_block->height += containing_block->padding[TOP] +
+ containing_block->padding[BOTTOM];
+ } else {
+ /** \todo inline containers */
+ }
+
+ layout_compute_offsets(&content->len_ctx, box, containing_block,
+ &top, &right, &bottom, &left);
+
+ /* Pass containing block into layout_find_dimensions via the float
+ * containing block box member. This is unused for absolutely positioned
+ * boxes because a box can't be floated and absolutely positioned. */
+ box->float_container = containing_block;
+ layout_find_dimensions(&content->len_ctx, available_width, -1,
+ box, box->style, &width, &height,
+ &max_width, &min_width, 0, 0,
+ margin, padding, border);
+ box->float_container = NULL;
+
+ /* 10.3.7 */
+ NSLOG(layout, DEBUG,
+ "%i + %i + %i + %i + %i + %i + %i + %i + %i = %i",
+ left, margin[LEFT], border[LEFT].width, padding[LEFT], width,
+ padding[RIGHT], border[RIGHT].width, margin[RIGHT], right,
+ containing_block->width);
+
+
+ if (left == AUTO && width == AUTO && right == AUTO) {
+ if (margin[LEFT] == AUTO)
+ margin[LEFT] = 0;
+ if (margin[RIGHT] == AUTO)
+ margin[RIGHT] = 0;
+ left = static_left;
+
+ width = min(max(box->min_width, available_width),
+ box->max_width);
+ width -= box->margin[LEFT] + box->border[LEFT].width +
+ box->padding[LEFT] + box->padding[RIGHT] +
+ box->border[RIGHT].width + box->margin[RIGHT];
+
+ /* Adjust for {min|max}-width */
+ if (max_width >= 0 && width > max_width) width = max_width;
+ if (width < min_width) width = min_width;
+
+ right = containing_block->width -
+ left -
+ margin[LEFT] - border[LEFT].width - padding[LEFT] -
+ width -
+ padding[RIGHT] - border[RIGHT].width - margin[RIGHT];
+ } else if (left != AUTO && width != AUTO && right != AUTO) {
+
+ /* Adjust for {min|max}-width */
+ if (max_width >= 0 && width > max_width) width = max_width;
+ if (min_width > 0 && width < min_width) width = min_width;
+
+ if (margin[LEFT] == AUTO && margin[RIGHT] == AUTO) {
+ space = containing_block->width -
+ left - border[LEFT].width -
+ padding[LEFT] - width - padding[RIGHT] -
+ border[RIGHT].width - right;
+ if (space < 0) {
+ margin[LEFT] = 0;
+ margin[RIGHT] = space;
+ } else {
+ margin[LEFT] = margin[RIGHT] = space / 2;
+ }
+ } else if (margin[LEFT] == AUTO) {
+ margin[LEFT] = containing_block->width -
+ left - border[LEFT].width -
+ padding[LEFT] - width - padding[RIGHT] -
+ border[RIGHT].width - margin[RIGHT] -
+ right;
+ } else if (margin[RIGHT] == AUTO) {
+ margin[RIGHT] = containing_block->width -
+ left - margin[LEFT] -
+ border[LEFT].width -
+ padding[LEFT] - width - padding[RIGHT] -
+ border[RIGHT].width - right;
+ } else {
+ right = containing_block->width -
+ left - margin[LEFT] -
+ border[LEFT].width -
+ padding[LEFT] - width - padding[RIGHT] -
+ border[RIGHT].width - margin[RIGHT];
+ }
+ } else {
+ if (margin[LEFT] == AUTO)
+ margin[LEFT] = 0;
+ if (margin[RIGHT] == AUTO)
+ margin[RIGHT] = 0;
+
+ if (left == AUTO && width == AUTO && right != AUTO) {
+ available_width -= right;
+
+ width = min(max(box->min_width, available_width),
+ box->max_width);
+ width -= box->margin[LEFT] + box->border[LEFT].width +
+ box->padding[LEFT] + box->padding[RIGHT] +
+ box->border[RIGHT].width + box->margin[RIGHT];
+
+ /* Adjust for {min|max}-width */
+ if (max_width >= 0 && width > max_width)
+ width = max_width;
+ if (width < min_width)
+ width = min_width;
+
+ left = containing_block->width -
+ margin[LEFT] - border[LEFT].width -
+ padding[LEFT] - width - padding[RIGHT] -
+ border[RIGHT].width - margin[RIGHT] -
+ right;
+ } else if (left == AUTO && width != AUTO && right == AUTO) {
+
+ /* Adjust for {min|max}-width */
+ if (max_width >= 0 && width > max_width)
+ width = max_width;
+ if (min_width > 0 && width < min_width)
+ width = min_width;
+
+ left = static_left;
+ right = containing_block->width -
+ left - margin[LEFT] -
+ border[LEFT].width -
+ padding[LEFT] - width - padding[RIGHT] -
+ border[RIGHT].width - margin[RIGHT];
+ } else if (left != AUTO && width == AUTO && right == AUTO) {
+ available_width -= left;
+
+ width = min(max(box->min_width, available_width),
+ box->max_width);
+ width -= box->margin[LEFT] + box->border[LEFT].width +
+ box->padding[LEFT] + box->padding[RIGHT] +
+ box->border[RIGHT].width + box->margin[RIGHT];
+
+ /* Adjust for {min|max}-width */
+ if (max_width >= 0 && width > max_width)
+ width = max_width;
+ if (width < min_width)
+ width = min_width;
+
+ right = containing_block->width -
+ left - margin[LEFT] -
+ border[LEFT].width -
+ padding[LEFT] - width - padding[RIGHT] -
+ border[RIGHT].width - margin[RIGHT];
+ } else if (left == AUTO && width != AUTO && right != AUTO) {
+
+ /* Adjust for {min|max}-width */
+ if (max_width >= 0 && width > max_width)
+ width = max_width;
+ if (width < min_width)
+ width = min_width;
+
+ left = containing_block->width -
+ margin[LEFT] - border[LEFT].width -
+ padding[LEFT] - width - padding[RIGHT] -
+ border[RIGHT].width - margin[RIGHT] -
+ right;
+ } else if (left != AUTO && width == AUTO && right != AUTO) {
+ width = containing_block->width -
+ left - margin[LEFT] -
+ border[LEFT].width -
+ padding[LEFT] - padding[RIGHT] -
+ border[RIGHT].width - margin[RIGHT] -
+ right;
+
+ /* Adjust for {min|max}-width */
+ if (max_width >= 0 && width > max_width)
+ width = max_width;
+ if (width < min_width)
+ width = min_width;
+
+ } else if (left != AUTO && width != AUTO && right == AUTO) {
+
+ /* Adjust for {min|max}-width */
+ if (max_width >= 0 && width > max_width)
+ width = max_width;
+ if (width < min_width)
+ width = min_width;
+
+ right = containing_block->width -
+ left - margin[LEFT] -
+ border[LEFT].width -
+ padding[LEFT] - width - padding[RIGHT] -
+ border[RIGHT].width - margin[RIGHT];
+ }
+ }
+
+ NSLOG(layout, DEBUG,
+ "%i + %i + %i + %i + %i + %i + %i + %i + %i = %i",
+ left, margin[LEFT], border[LEFT].width, padding[LEFT], width,
+ padding[RIGHT], border[RIGHT].width, margin[RIGHT], right,
+ containing_block->width);
+
+ box->x = left + margin[LEFT] + border[LEFT].width - cx;
+ if (containing_block->type == BOX_BLOCK ||
+ containing_block->type == BOX_INLINE_BLOCK ||
+ containing_block->type == BOX_TABLE_CELL) {
+ /* Block-level ancestor => reset container's width */
+ containing_block->width -= containing_block->padding[LEFT] +
+ containing_block->padding[RIGHT];
+ } else {
+ /** \todo inline ancestors */
+ }
+ box->width = width;
+ box->height = height;
+
+ if (box->type == BOX_BLOCK || box->type == BOX_INLINE_BLOCK ||
+ box->object || box->flags & IFRAME) {
+ if (!layout_block_context(box, -1, content))
+ return false;
+ } else if (box->type == BOX_TABLE) {
+ /* layout_table also expects the containing block to be
+ * stored in the float_container field */
+ box->float_container = containing_block;
+ /* \todo layout_table considers margins etc. again */
+ if (!layout_table(box, width, content))
+ return false;
+ box->float_container = NULL;
+ layout_solve_width(box, box->parent->width, box->width, 0, 0,
+ -1, -1);
+ }
+
+ /* 10.6.4 */
+ NSLOG(layout, DEBUG,
+ "%i + %i + %i + %i + %i + %i + %i + %i + %i = %i",
+ top, margin[TOP], border[TOP].width, padding[TOP], height,
+ padding[BOTTOM], border[BOTTOM].width, margin[BOTTOM], bottom,
+ containing_block->height);
+
+ if (top == AUTO && height == AUTO && bottom == AUTO) {
+ top = static_top;
+ height = box->height;
+ if (margin[TOP] == AUTO)
+ margin[TOP] = 0;
+ if (margin[BOTTOM] == AUTO)
+ margin[BOTTOM] = 0;
+ bottom = containing_block->height -
+ top - margin[TOP] - border[TOP].width -
+ padding[TOP] - height - padding[BOTTOM] -
+ border[BOTTOM].width - margin[BOTTOM];
+ } else if (top != AUTO && height != AUTO && bottom != AUTO) {
+ if (margin[TOP] == AUTO && margin[BOTTOM] == AUTO) {
+ space = containing_block->height -
+ top - border[TOP].width - padding[TOP] -
+ height - padding[BOTTOM] -
+ border[BOTTOM].width - bottom;
+ margin[TOP] = margin[BOTTOM] = space / 2;
+ } else if (margin[TOP] == AUTO) {
+ margin[TOP] = containing_block->height -
+ top - border[TOP].width - padding[TOP] -
+ height - padding[BOTTOM] -
+ border[BOTTOM].width - margin[BOTTOM] -
+ bottom;
+ } else if (margin[BOTTOM] == AUTO) {
+ margin[BOTTOM] = containing_block->height -
+ top - margin[TOP] - border[TOP].width -
+ padding[TOP] - height -
+ padding[BOTTOM] - border[BOTTOM].width -
+ bottom;
+ } else {
+ bottom = containing_block->height -
+ top - margin[TOP] - border[TOP].width -
+ padding[TOP] - height -
+ padding[BOTTOM] - border[BOTTOM].width -
+ margin[BOTTOM];
+ }
+ } else {
+ if (margin[TOP] == AUTO)
+ margin[TOP] = 0;
+ if (margin[BOTTOM] == AUTO)
+ margin[BOTTOM] = 0;
+ if (top == AUTO && height == AUTO && bottom != AUTO) {
+ height = box->height;
+ top = containing_block->height -
+ margin[TOP] - border[TOP].width -
+ padding[TOP] - height -
+ padding[BOTTOM] - border[BOTTOM].width -
+ margin[BOTTOM] - bottom;
+ } else if (top == AUTO && height != AUTO && bottom == AUTO) {
+ top = static_top;
+ bottom = containing_block->height -
+ top - margin[TOP] - border[TOP].width -
+ padding[TOP] - height -
+ padding[BOTTOM] - border[BOTTOM].width -
+ margin[BOTTOM];
+ } else if (top != AUTO && height == AUTO && bottom == AUTO) {
+ height = box->height;
+ bottom = containing_block->height -
+ top - margin[TOP] - border[TOP].width -
+ padding[TOP] - height -
+ padding[BOTTOM] - border[BOTTOM].width -
+ margin[BOTTOM];
+ } else if (top == AUTO && height != AUTO && bottom != AUTO) {
+ top = containing_block->height -
+ margin[TOP] - border[TOP].width -
+ padding[TOP] - height -
+ padding[BOTTOM] - border[BOTTOM].width -
+ margin[BOTTOM] - bottom;
+ } else if (top != AUTO && height == AUTO && bottom != AUTO) {
+ height = containing_block->height -
+ top - margin[TOP] - border[TOP].width -
+ padding[TOP] - padding[BOTTOM] -
+ border[BOTTOM].width - margin[BOTTOM] -
+ bottom;
+ } else if (top != AUTO && height != AUTO && bottom == AUTO) {
+ bottom = containing_block->height -
+ top - margin[TOP] - border[TOP].width -
+ padding[TOP] - height -
+ padding[BOTTOM] - border[BOTTOM].width -
+ margin[BOTTOM];
+ }
+ }
+
+ NSLOG(layout, DEBUG,
+ "%i + %i + %i + %i + %i + %i + %i + %i + %i = %i",
+ top, margin[TOP], border[TOP].width, padding[TOP], height,
+ padding[BOTTOM], border[BOTTOM].width, margin[BOTTOM], bottom,
+ containing_block->height);
+
+ box->y = top + margin[TOP] + border[TOP].width - cy;
+ if (containing_block->type == BOX_BLOCK ||
+ containing_block->type == BOX_INLINE_BLOCK ||
+ containing_block->type == BOX_TABLE_CELL) {
+ /* Block-level ancestor => reset container's height */
+ containing_block->height -= containing_block->padding[TOP] +
+ containing_block->padding[BOTTOM];
+ } else {
+ /** \todo Inline ancestors */
+ }
+ box->height = height;
+ layout_apply_minmax_height(&content->len_ctx, box, containing_block);
+
+ return true;
+}
+
+
+/**
+ * Recursively layout and position absolutely positioned boxes.
+ *
+ * \param box tree of boxes to layout
+ * \param containing_block current containing block
+ * \param cx position of box relative to containing_block
+ * \param cy position of box relative to containing_block
+ * \param content memory pool for any new boxes
+ * \return true on success, false on memory exhaustion
+ */
+static bool
+layout_position_absolute(struct box *box,
+ struct box *containing_block,
+ int cx, int cy,
+ html_content *content)
+{
+ struct box *c;
+
+ for (c = box->children; c; c = c->next) {
+ if ((c->type == BOX_BLOCK || c->type == BOX_TABLE ||
+ c->type == BOX_INLINE_BLOCK) &&
+ (css_computed_position(c->style) ==
+ CSS_POSITION_ABSOLUTE ||
+ css_computed_position(c->style) ==
+ CSS_POSITION_FIXED)) {
+ if (!layout_absolute(c, containing_block,
+ cx, cy, content))
+ return false;
+ if (!layout_position_absolute(c, c, 0, 0, content))
+ return false;
+ } else if (c->style && css_computed_position(c->style) ==
+ CSS_POSITION_RELATIVE) {
+ if (!layout_position_absolute(c, c, 0, 0, content))
+ return false;
+ } else {
+ int px, py;
+ if (c->style && (css_computed_float(c->style) ==
+ CSS_FLOAT_LEFT ||
+ css_computed_float(c->style) ==
+ CSS_FLOAT_RIGHT)) {
+ /* Float x/y coords are relative to nearest
+ * ansestor with float_children, rather than
+ * relative to parent. Need to get x/y relative
+ * to parent */
+ struct box *p;
+ px = c->x;
+ py = c->y;
+ for (p = box->parent; p && !p->float_children;
+ p = p->parent) {
+ px -= p->x;
+ py -= p->y;
+ }
+ } else {
+ /* Not a float, so box x/y coords are relative
+ * to parent */
+ px = c->x;
+ py = c->y;
+ }
+ if (!layout_position_absolute(c, containing_block,
+ cx + px, cy + py, content))
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+/**
+ * Compute a box's relative offset as per CSS 2.1 9.4.3
+ *
+ * \param len_ctx Length conversion context
+ * \param box Box to compute relative offsets for.
+ * \param x Receives relative offset in x.
+ * \param y Receives relative offset in y.
+ */
+static void layout_compute_relative_offset(
+ const nscss_len_ctx *len_ctx,
+ struct box *box,
+ int *x,
+ int *y)
+{
+ int left, right, top, bottom;
+ struct box *containing_block;
+
+ assert(box && box->parent && box->style &&
+ css_computed_position(box->style) ==
+ CSS_POSITION_RELATIVE);
+
+ if (box->float_container &&
+ (css_computed_float(box->style) == CSS_FLOAT_LEFT ||
+ css_computed_float(box->style) == CSS_FLOAT_RIGHT)) {
+ containing_block = box->float_container;
+ } else {
+ containing_block = box->parent;
+ }
+
+ layout_compute_offsets(len_ctx, box, containing_block,
+ &top, &right, &bottom, &left);
+
+ if (left == AUTO && right == AUTO)
+ left = right = 0;
+ else if (left == AUTO)
+ /* left is auto => computed = -right */
+ left = -right;
+ else if (right == AUTO)
+ /* right is auto => computed = -left */
+ right = -left;
+ else {
+ /* over constrained => examine direction property
+ * of containing block */
+ if (containing_block->style &&
+ css_computed_direction(
+ containing_block->style) ==
+ CSS_DIRECTION_RTL) {
+ /* right wins */
+ left = -right;
+ } else {
+ /* assume LTR in all other cases */
+ right = -left;
+ }
+ }
+
+ assert(left == -right);
+
+ if (top == AUTO && bottom == AUTO) {
+ top = bottom = 0;
+ } else if (top == AUTO) {
+ top = -bottom;
+ } else {
+ /* bottom is AUTO, or neither are AUTO */
+ bottom = -top;
+ }
+
+ NSLOG(layout, DEBUG, "left %i, right %i, top %i, bottom %i", left,
+ right, top, bottom);
+
+ *x = left;
+ *y = top;
+}
+
+
+/**
+ * Adjust positions of relatively positioned boxes.
+ *
+ * \param len_ctx Length conversion context
+ * \param root box to adjust the position of
+ * \param fp box which forms the block formatting context for children of
+ * "root" which are floats
+ * \param fx x offset due to intervening relatively positioned boxes
+ * between current box, "root", and the block formatting context
+ * box, "fp", for float children of "root"
+ * \param fy y offset due to intervening relatively positioned boxes
+ * between current box, "root", and the block formatting context
+ * box, "fp", for float children of "root"
+ */
+static void
+layout_position_relative(
+ const nscss_len_ctx *len_ctx,
+ struct box *root,
+ struct box *fp,
+ int fx,
+ int fy)
+{
+ struct box *box; /* for children of "root" */
+ struct box *fn; /* for block formatting context box for children of
+ * "box" */
+ struct box *fc; /* for float children of the block formatting context,
+ * "fp" */
+ int x, y; /* for the offsets resulting from any relative
+ * positioning on the current block */
+ int fnx, fny; /* for affsets which apply to flat children of "box" */
+
+ /**\todo ensure containing box is large enough after moving boxes */
+
+ assert(root);
+
+ /* Normal children */
+ for (box = root->children; box; box = box->next) {
+
+ if (box->type == BOX_TEXT)
+ continue;
+
+ /* If relatively positioned, get offsets */
+ if (box->style && css_computed_position(box->style) ==
+ CSS_POSITION_RELATIVE)
+ layout_compute_relative_offset(
+ len_ctx, box, &x, &y);
+ else
+ x = y = 0;
+
+ /* Adjust float coordinates.
+ * (note float x and y are relative to their block formatting
+ * context box and not their parent) */
+ if (box->style && (css_computed_float(box->style) ==
+ CSS_FLOAT_LEFT ||
+ css_computed_float(box->style) ==
+ CSS_FLOAT_RIGHT) &&
+ (fx != 0 || fy != 0)) {
+ /* box is a float and there is a float offset to
+ * apply */
+ for (fc = fp->float_children; fc; fc = fc->next_float) {
+ if (box == fc->children) {
+ /* Box is floated in the block
+ * formatting context block, fp.
+ * Apply float offsets. */
+ box->x += fx;
+ box->y += fy;
+ fx = fy = 0;
+ }
+ }
+ }
+
+ if (box->float_children) {
+ fn = box;
+ fnx = fny = 0;
+ } else {
+ fn = fp;
+ fnx = fx + x;
+ fny = fy + y;
+ }
+
+ /* recurse first */
+ layout_position_relative(len_ctx, box, fn, fnx, fny);
+
+ /* Ignore things we're not interested in. */
+ if (!box->style || (box->style &&
+ css_computed_position(box->style) !=
+ CSS_POSITION_RELATIVE))
+ continue;
+
+ box->x += x;
+ box->y += y;
+
+ /* Handle INLINEs - their "children" are in fact
+ * the sibling boxes between the INLINE and
+ * INLINE_END boxes */
+ if (box->type == BOX_INLINE && box->inline_end) {
+ struct box *b;
+ for (b = box->next; b && b != box->inline_end;
+ b = b->next) {
+ b->x += x;
+ b->y += y;
+ }
+ }
+ }
+}
+
+
+/**
+ * Find a box's bounding box relative to itself, i.e. the box's border edge box
+ *
+ * \param len_ctx Length conversion context
+ * \param box box find bounding box of
+ * \param desc_x0 updated to left of box's bbox
+ * \param desc_y0 updated to top of box's bbox
+ * \param desc_x1 updated to right of box's bbox
+ * \param desc_y1 updated to bottom of box's bbox
+ */
+static void
+layout_get_box_bbox(
+ const nscss_len_ctx *len_ctx,
+ struct box *box,
+ int *desc_x0, int *desc_y0,
+ int *desc_x1, int *desc_y1)
+{
+ *desc_x0 = -box->border[LEFT].width;
+ *desc_y0 = -box->border[TOP].width;
+ *desc_x1 = box->padding[LEFT] + box->width + box->padding[RIGHT] +
+ box->border[RIGHT].width;
+ *desc_y1 = box->padding[TOP] + box->height + box->padding[BOTTOM] +
+ box->border[BOTTOM].width;
+
+ /* To stop the top of text getting clipped when css line-height is
+ * reduced, we increase the top of the descendant bbox. */
+ if (box->type == BOX_BLOCK && box->style != NULL &&
+ css_computed_overflow_y(box->style) ==
+ CSS_OVERFLOW_VISIBLE &&
+ box->object == NULL) {
+ css_fixed font_size = 0;
+ css_unit font_unit = CSS_UNIT_PT;
+ int text_height;
+
+ css_computed_font_size(box->style, &font_size, &font_unit);
+ text_height = nscss_len2px(len_ctx, font_size, font_unit,
+ box->style);
+ text_height = FIXTOINT(text_height * 3 / 4);
+ *desc_y0 = (*desc_y0 < -text_height) ? *desc_y0 : -text_height;
+ }
+}
+
+
+/**
+ * Apply changes to box descendant_[xy][01] values due to given child.
+ *
+ * \param len_ctx Length conversion context
+ * \param box box to update
+ * \param child a box, which may affect box's descendant bbox
+ * \param off_x offset to apply to child->x coord to treat as child of box
+ * \param off_y offset to apply to child->y coord to treat as child of box
+ */
+static void
+layout_update_descendant_bbox(
+ const nscss_len_ctx *len_ctx,
+ struct box *box,
+ struct box *child,
+ int off_x,
+ int off_y)
+{
+ int child_desc_x0, child_desc_y0, child_desc_x1, child_desc_y1;
+
+ /* get coordinates of child relative to box */
+ int child_x = child->x - off_x;
+ int child_y = child->y - off_y;
+
+ bool html_object = (child->object &&
+ content_get_type(child->object) == CONTENT_HTML);
+
+ enum css_overflow_e overflow_x = CSS_OVERFLOW_VISIBLE;
+ enum css_overflow_e overflow_y = CSS_OVERFLOW_VISIBLE;
+
+ if (child->style != NULL) {
+ overflow_x = css_computed_overflow_x(child->style);
+ overflow_y = css_computed_overflow_y(child->style);
+ }
+
+ /* Get child's border edge */
+ layout_get_box_bbox(len_ctx, child,
+ &child_desc_x0, &child_desc_y0,
+ &child_desc_x1, &child_desc_y1);
+
+ if (overflow_x == CSS_OVERFLOW_VISIBLE &&
+ html_object == false) {
+ /* get child's descendant bbox relative to box */
+ child_desc_x0 = child->descendant_x0;
+ child_desc_x1 = child->descendant_x1;
+ }
+ if (overflow_y == CSS_OVERFLOW_VISIBLE &&
+ html_object == false) {
+ /* get child's descendant bbox relative to box */
+ child_desc_y0 = child->descendant_y0;
+ child_desc_y1 = child->descendant_y1;
+ }
+
+ child_desc_x0 += child_x;
+ child_desc_y0 += child_y;
+ child_desc_x1 += child_x;
+ child_desc_y1 += child_y;
+
+ /* increase box's descendant bbox to contain descendants */
+ if (child_desc_x0 < box->descendant_x0)
+ box->descendant_x0 = child_desc_x0;
+ if (child_desc_y0 < box->descendant_y0)
+ box->descendant_y0 = child_desc_y0;
+ if (box->descendant_x1 < child_desc_x1)
+ box->descendant_x1 = child_desc_x1;
+ if (box->descendant_y1 < child_desc_y1)
+ box->descendant_y1 = child_desc_y1;
+}
+
+
+/**
+ * Recursively calculate the descendant_[xy][01] values for a laid-out box tree
+ * and inform iframe browser windows of their size and position.
+ *
+ * \param len_ctx Length conversion context
+ * \param box tree of boxes to update
+ */
+static void layout_calculate_descendant_bboxes(
+ const nscss_len_ctx *len_ctx,
+ struct box *box)
+{
+ struct box *child;
+
+ assert(box->width != UNKNOWN_WIDTH);
+ assert(box->height != AUTO);
+ /* assert((box->width >= 0) && (box->height >= 0)); */
+
+ /* Initialise box's descendant box to border edge box */
+ layout_get_box_bbox(len_ctx, box,
+ &box->descendant_x0, &box->descendant_y0,
+ &box->descendant_x1, &box->descendant_y1);
+
+ /* Extend it to contain HTML contents if box is replaced */
+ if (box->object && content_get_type(box->object) == CONTENT_HTML) {
+ if (box->descendant_x1 < content_get_width(box->object))
+ box->descendant_x1 = content_get_width(box->object);
+ if (box->descendant_y1 < content_get_height(box->object))
+ box->descendant_y1 = content_get_height(box->object);
+ }
+
+ if (box->iframe != NULL) {
+ int x, y;
+ box_coords(box, &x, &y);
+
+ browser_window_set_position(box->iframe, x, y);
+ browser_window_set_dimensions(box->iframe,
+ box->width, box->height);
+ browser_window_reformat(box->iframe, true,
+ box->width, box->height);
+ }
+
+ if (box->type == BOX_INLINE || box->type == BOX_TEXT)
+ return;
+
+ if (box->type == BOX_INLINE_END) {
+ box = box->inline_end;
+ for (child = box->next; child;
+ child = child->next) {
+ if (child->type == BOX_FLOAT_LEFT ||
+ child->type == BOX_FLOAT_RIGHT)
+ continue;
+
+ layout_update_descendant_bbox(len_ctx, box, child,
+ box->x, box->y);
+
+ if (child == box->inline_end)
+ break;
+ }
+ return;
+ }
+
+ if (box->flags & REPLACE_DIM)
+ /* Box's children aren't displayed if the box is replaced */
+ return;
+
+ for (child = box->children; child; child = child->next) {
+ if (child->type == BOX_FLOAT_LEFT ||
+ child->type == BOX_FLOAT_RIGHT)
+ continue;
+
+ layout_calculate_descendant_bboxes(len_ctx, child);
+
+ if (box->style && css_computed_overflow_x(box->style) ==
+ CSS_OVERFLOW_HIDDEN &&
+ css_computed_overflow_y(box->style) ==
+ CSS_OVERFLOW_HIDDEN)
+ continue;
+
+ layout_update_descendant_bbox(len_ctx, box, child, 0, 0);
+ }
+
+ for (child = box->float_children; child; child = child->next_float) {
+ assert(child->type == BOX_FLOAT_LEFT ||
+ child->type == BOX_FLOAT_RIGHT);
+
+ layout_calculate_descendant_bboxes(len_ctx, child);
+
+ layout_update_descendant_bbox(len_ctx, box, child, 0, 0);
+ }
+
+ if (box->list_marker) {
+ child = box->list_marker;
+ layout_calculate_descendant_bboxes(len_ctx, child);
+
+ layout_update_descendant_bbox(len_ctx, box, child, 0, 0);
+ }
+}
+
+
+/* exported function documented in html/layout.h */
+bool layout_document(html_content *content, int width, int height)
+{
+ bool ret;
+ struct box *doc = content->layout;
+ const struct gui_layout_table *font_func = content->font_func;
+
+ layout_minmax_block(doc, font_func, content);
+
+ layout_block_find_dimensions(&content->len_ctx,
+ width, height, 0, 0, doc);
+ doc->x = doc->margin[LEFT] + doc->border[LEFT].width;
+ doc->y = doc->margin[TOP] + doc->border[TOP].width;
+ width -= doc->margin[LEFT] + doc->border[LEFT].width +
+ doc->padding[LEFT] + doc->padding[RIGHT] +
+ doc->border[RIGHT].width + doc->margin[RIGHT];
+ if (width < 0) {
+ width = 0;
+ }
+ doc->width = width;
+
+ ret = layout_block_context(doc, height, content);
+
+ /* make <html> and <body> fill available height */
+ if (doc->y + doc->padding[TOP] + doc->height + doc->padding[BOTTOM] +
+ doc->border[BOTTOM].width + doc->margin[BOTTOM] <
+ height) {
+ doc->height = height - (doc->y + doc->padding[TOP] +
+ doc->padding[BOTTOM] +
+ doc->border[BOTTOM].width +
+ doc->margin[BOTTOM]);
+ if (doc->children)
+ doc->children->height = doc->height -
+ (doc->children->margin[TOP] +
+ doc->children->border[TOP].width +
+ doc->children->padding[TOP] +
+ doc->children->padding[BOTTOM] +
+ doc->children->border[BOTTOM].width +
+ doc->children->margin[BOTTOM]);
+ }
+
+ layout_lists(doc, font_func, &content->len_ctx);
+ layout_position_absolute(doc, doc, 0, 0, content);
+ layout_position_relative(&content->len_ctx, doc, doc, 0, 0);
+
+ layout_calculate_descendant_bboxes(&content->len_ctx, doc);
+
+ return ret;
+}
diff --git a/content/handlers/html/layout.h b/content/handlers/html/layout.h
new file mode 100644
index 0000000..0811e81
--- /dev/null
+++ b/content/handlers/html/layout.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2003 James Bursa <bursa(a)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
+ * interface to HTML layout.
+ *
+ * The main interface to the layout code is layout_document(), which takes a
+ * normalized box tree and assigns coordinates and dimensions to the boxes, and
+ * also adds boxes to the tree (eg. when formatting lines of text).
+ */
+
+#ifndef NETSURF_HTML_LAYOUT_H
+#define NETSURF_HTML_LAYOUT_H
+
+struct box;
+struct html_content;
+struct gui_layout_table;
+
+/**
+ * Calculate positions of boxes in a document.
+ *
+ * \param content content of type CONTENT_HTML
+ * \param width available width
+ * \param height available height
+ * \return true on success, false on memory exhaustion
+ */
+bool layout_document(struct html_content *content, int width, int height);
+
+#endif
diff --git a/content/handlers/html/search.c b/content/handlers/html/search.c
new file mode 100644
index 0000000..9ba2957
--- /dev/null
+++ b/content/handlers/html/search.c
@@ -0,0 +1,656 @@
+/*
+ * Copyright 2004 John M Bell <jmb202(a)ecs.soton.ac.uk>
+ * Copyright 2005 Adrian Lees <adrianl(a)users.sourceforge.net>
+ * Copyright 2009 Mark Benjamin <netsurf-browser.org.MarkBenjamin(a)dfgh.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
+ * Free text search (core)
+ */
+
+#include <ctype.h>
+#include <string.h>
+#include <dom/dom.h>
+
+#include "utils/config.h"
+#include "utils/log.h"
+#include "utils/messages.h"
+#include "utils/utils.h"
+#include "content/content.h"
+#include "content/hlcache.h"
+#include "desktop/selection.h"
+#include "netsurf/search.h"
+#include "netsurf/misc.h"
+#include "desktop/gui_internal.h"
+
+#include "text/textplain.h"
+#include "html/box.h"
+#include "html/html.h"
+#include "html/html_internal.h"
+#include "html/search.h"
+
+#ifndef NOF_ELEMENTS
+#define NOF_ELEMENTS(array) (sizeof(array)/sizeof(*(array)))
+#endif
+
+
+struct list_entry {
+ unsigned start_idx; /* start position of match */
+ unsigned end_idx; /* end of match */
+
+ struct box *start_box; /* used only for html contents */
+ struct box *end_box;
+
+ struct selection *sel;
+
+ struct list_entry *prev;
+ struct list_entry *next;
+};
+
+struct search_context {
+ void *gui_p;
+ struct content *c;
+ struct list_entry *found;
+ struct list_entry *current; /* first for select all */
+ char *string;
+ bool prev_case_sens;
+ bool newsearch;
+ bool is_html;
+};
+
+
+/* Exported function documented in search.h */
+struct search_context * search_create_context(struct content *c,
+ content_type type, void *gui_data)
+{
+ struct search_context *context;
+ struct list_entry *search_head;
+
+ if (type != CONTENT_HTML && type != CONTENT_TEXTPLAIN) {
+ return NULL;
+ }
+
+ context = malloc(sizeof(struct search_context));
+ if (context == NULL) {
+ guit->misc->warning("NoMemory", 0);
+ return NULL;
+ }
+
+ search_head = malloc(sizeof(struct list_entry));
+ if (search_head == NULL) {
+ guit->misc->warning("NoMemory", 0);
+ free(context);
+ return NULL;
+ }
+
+ search_head->start_idx = 0;
+ search_head->end_idx = 0;
+ search_head->start_box = NULL;
+ search_head->end_box = NULL;
+ search_head->sel = NULL;
+ search_head->prev = NULL;
+ search_head->next = NULL;
+
+ context->found = search_head;
+ context->current = NULL;
+ context->string = NULL;
+ context->prev_case_sens = false;
+ context->newsearch = true;
+ context->c = c;
+ context->is_html = (type == CONTENT_HTML) ? true : false;
+ context->gui_p = gui_data;
+
+ return context;
+}
+
+
+/**
+ * Release the memory used by the list of matches,
+ * deleting selection objects too
+ */
+
+static void free_matches(struct search_context *context)
+{
+ struct list_entry *a;
+ struct list_entry *b;
+
+ a = context->found->next;
+
+ /* empty the list before clearing and deleting the
+ * selections because the the clearing updates the
+ * screen immediately, causing nested accesses to the list */
+
+ context->found->prev = NULL;
+ context->found->next = NULL;
+
+ for (; a; a = b) {
+ b = a->next;
+ if (a->sel) {
+ selection_clear(a->sel, true);
+ selection_destroy(a->sel);
+ }
+ free(a);
+ }
+}
+
+
+/**
+ * Find the first occurrence of 'match' in 'string' and return its index
+ *
+ * \param string the string to be searched (unterminated)
+ * \param s_len length of the string to be searched
+ * \param pattern the pattern for which we are searching (unterminated)
+ * \param p_len length of pattern
+ * \param case_sens true iff case sensitive match required
+ * \param m_len accepts length of match in bytes
+ * \return pointer to first match, NULL if none
+ */
+
+static const char *find_pattern(const char *string, int s_len,
+ const char *pattern, int p_len, bool case_sens,
+ unsigned int *m_len)
+{
+ struct { const char *ss, *s, *p; bool first; } context[16];
+ const char *ep = pattern + p_len;
+ const char *es = string + s_len;
+ const char *p = pattern - 1; /* a virtual '*' before the pattern */
+ const char *ss = string;
+ const char *s = string;
+ bool first = true;
+ int top = 0;
+
+ while (p < ep) {
+ bool matches;
+ if (p < pattern || *p == '*') {
+ char ch;
+
+ /* skip any further asterisks; one is the same as many
+ */
+ do p++; while (p < ep && *p == '*');
+
+ /* if we're at the end of the pattern, yes, it matches
+ */
+ if (p >= ep) break;
+
+ /* anything matches a # so continue matching from
+ here, and stack a context that will try to match
+ the wildcard against the next character */
+
+ ch = *p;
+ if (ch != '#') {
+ /* scan forwards until we find a match for
+ this char */
+ if (!case_sens) ch = toupper(ch);
+ while (s < es) {
+ if (case_sens) {
+ if (*s == ch) break;
+ } else if (toupper(*s) == ch)
+ break;
+ s++;
+ }
+ }
+
+ if (s < es) {
+ /* remember where we are in case the match
+ fails; we may then resume */
+ if (top < (int)NOF_ELEMENTS(context)) {
+ context[top].ss = ss;
+ context[top].s = s + 1;
+ context[top].p = p - 1;
+ /* ptr to last asterisk */
+ context[top].first = first;
+ top++;
+ }
+
+ if (first) {
+ ss = s;
+ /* remember first non-'*' char */
+ first = false;
+ }
+
+ matches = true;
+ } else {
+ matches = false;
+ }
+
+ } else if (s < es) {
+ char ch = *p;
+ if (ch == '#')
+ matches = true;
+ else {
+ if (case_sens)
+ matches = (*s == ch);
+ else
+ matches = (toupper(*s) == toupper(ch));
+ }
+ if (matches && first) {
+ ss = s; /* remember first non-'*' char */
+ first = false;
+ }
+ } else {
+ matches = false;
+ }
+
+ if (matches) {
+ p++; s++;
+ } else {
+ /* doesn't match,
+ * resume with stacked context if we have one */
+ if (--top < 0)
+ return NULL; /* no match, give up */
+
+ ss = context[top].ss;
+ s = context[top].s;
+ p = context[top].p;
+ first = context[top].first;
+ }
+ }
+
+ /* end of pattern reached */
+ *m_len = max(s - ss, 1);
+ return ss;
+}
+
+
+/**
+ * Add a new entry to the list of matches
+ *
+ * \param start_idx Offset of match start within textual representation
+ * \param end_idx Offset of match end
+ * \param context The search context to add the entry to.
+ * \return Pointer to added entry, NULL iff failed.
+ */
+
+static struct list_entry *add_entry(unsigned start_idx, unsigned end_idx,
+ struct search_context *context)
+{
+ struct list_entry *entry;
+
+ /* found string in box => add to list */
+ entry = calloc(1, sizeof(*entry));
+ if (!entry) {
+ guit->misc->warning("NoMemory", 0);
+ return NULL;
+ }
+
+ entry->start_idx = start_idx;
+ entry->end_idx = end_idx;
+ entry->sel = NULL;
+
+ entry->next = 0;
+ entry->prev = context->found->prev;
+
+ if (context->found->prev == NULL)
+ context->found->next = entry;
+ else
+ context->found->prev->next = entry;
+
+ context->found->prev = entry;
+
+ return entry;
+}
+
+
+/**
+ * Finds all occurrences of a given string in the html box tree
+ *
+ * \param pattern the string pattern to search for
+ * \param p_len pattern length
+ * \param cur pointer to the current box
+ * \param case_sens whether to perform a case sensitive search
+ * \param context The search context to add the entry to.
+ * \return true on success, false on memory allocation failure
+ */
+static bool find_occurrences_html(const char *pattern, int p_len,
+ struct box *cur, bool case_sens,
+ struct search_context *context)
+{
+ struct box *a;
+
+ /* ignore this box, if there's no visible text */
+ if (!cur->object && cur->text) {
+ const char *text = cur->text;
+ unsigned length = cur->length;
+
+ while (length > 0) {
+ struct list_entry *entry;
+ unsigned match_length;
+ unsigned match_offset;
+ const char *new_text;
+ const char *pos = find_pattern(text, length,
+ pattern, p_len, case_sens,
+ &match_length);
+ if (!pos)
+ break;
+
+ /* found string in box => add to list */
+ match_offset = pos - cur->text;
+
+ entry = add_entry(cur->byte_offset + match_offset,
+ cur->byte_offset +
+ match_offset +
+ match_length, context);
+ if (!entry)
+ return false;
+
+ entry->start_box = cur;
+ entry->end_box = cur;
+
+ new_text = pos + match_length;
+ length -= (new_text - text);
+ text = new_text;
+ }
+ }
+
+ /* and recurse */
+ for (a = cur->children; a; a = a->next) {
+ if (!find_occurrences_html(pattern, p_len, a, case_sens,
+ context))
+ return false;
+ }
+
+ return true;
+}
+
+
+/**
+ * Finds all occurrences of a given string in a textplain content
+ *
+ * \param pattern the string pattern to search for
+ * \param p_len pattern length
+ * \param c the content to be searched
+ * \param case_sens whether to perform a case sensitive search
+ * \param context The search context to add the entry to.
+ * \return true on success, false on memory allocation failure
+ */
+
+static bool find_occurrences_text(const char *pattern, int p_len,
+ struct content *c, bool case_sens,
+ struct search_context *context)
+{
+ int nlines = textplain_line_count(c);
+ int line;
+
+ for(line = 0; line < nlines; line++) {
+ size_t offset, length;
+ const char *text = textplain_get_line(c, line,
+ &offset, &length);
+ if (text) {
+ while (length > 0) {
+ struct list_entry *entry;
+ unsigned match_length;
+ size_t start_idx;
+ const char *new_text;
+ const char *pos = find_pattern(text, length,
+ pattern, p_len, case_sens,
+ &match_length);
+ if (!pos)
+ break;
+
+ /* found string in line => add to list */
+ start_idx = offset + (pos - text);
+ entry = add_entry(start_idx, start_idx +
+ match_length, context);
+ if (!entry)
+ return false;
+
+ new_text = pos + match_length;
+ offset += (new_text - text);
+ length -= (new_text - text);
+ text = new_text;
+ }
+ }
+ }
+
+ return true;
+}
+
+
+/**
+ * Search for a string in the box tree
+ *
+ * \param string the string to search for
+ * \param string_len length of search string
+ * \param context The search context to add the entry to.
+ * \param flags flags to control the search.
+ */
+static void search_text(const char *string, int string_len,
+ struct search_context *context, search_flags_t flags)
+{
+ struct rect bounds;
+ struct box *box = NULL;
+ union content_msg_data msg_data;
+ bool case_sensitive, forwards, showall;
+
+ case_sensitive = ((flags & SEARCH_FLAG_CASE_SENSITIVE) != 0) ?
+ true : false;
+ forwards = ((flags & SEARCH_FLAG_FORWARDS) != 0) ? true : false;
+ showall = ((flags & SEARCH_FLAG_SHOWALL) != 0) ? true : false;
+
+ if (context->c == NULL)
+ return;
+
+ if (context->is_html == true) {
+ html_content *html = (html_content *)context->c;
+
+ box = html->layout;
+
+ if (!box)
+ return;
+ }
+
+
+ /* check if we need to start a new search or continue an old one */
+ if (context->newsearch) {
+ bool res;
+
+ if (context->string != NULL)
+ free(context->string);
+
+ context->current = NULL;
+ free_matches(context);
+
+ context->string = malloc(string_len + 1);
+ if (context->string != NULL) {
+ memcpy(context->string, string, string_len);
+ context->string[string_len] = '\0';
+ }
+
+ guit->search->hourglass(true, context->gui_p);
+
+ if (context->is_html == true) {
+ res = find_occurrences_html(string, string_len,
+ box, case_sensitive, context);
+ } else {
+ res = find_occurrences_text(string, string_len,
+ context->c, case_sensitive, context);
+ }
+
+ if (!res) {
+ free_matches(context);
+ guit->search->hourglass(false, context->gui_p);
+ return;
+ }
+ guit->search->hourglass(false, context->gui_p);
+
+ context->prev_case_sens = case_sensitive;
+
+ /* new search, beginning at the top of the page */
+ context->current = context->found->next;
+ context->newsearch = false;
+
+ } else if (context->current != NULL) {
+ /* continued search in the direction specified */
+ if (forwards) {
+ if (context->current->next)
+ context->current = context->current->next;
+ } else {
+ if (context->current->prev)
+ context->current = context->current->prev;
+ }
+ }
+
+ guit->search->status((context->current != NULL), context->gui_p);
+
+ search_show_all(showall, context);
+
+ guit->search->back_state((context->current != NULL) &&
+ (context->current->prev != NULL),
+ context->gui_p);
+ guit->search->forward_state((context->current != NULL) &&
+ (context->current->next != NULL),
+ context->gui_p);
+
+ if (context->current == NULL)
+ return;
+
+ if (context->is_html == true) {
+ /* get box position and jump to it */
+ box_coords(context->current->start_box, &bounds.x0, &bounds.y0);
+ /* \todo: move x0 in by correct idx */
+ box_coords(context->current->end_box, &bounds.x1, &bounds.y1);
+ /* \todo: move x1 in by correct idx */
+ bounds.x1 += context->current->end_box->width;
+ bounds.y1 += context->current->end_box->height;
+ } else {
+ textplain_coords_from_range(context->c,
+ context->current->start_idx,
+ context->current->end_idx, &bounds);
+ }
+
+ msg_data.scroll.area = true;
+ msg_data.scroll.x0 = bounds.x0;
+ msg_data.scroll.y0 = bounds.y0;
+ msg_data.scroll.x1 = bounds.x1;
+ msg_data.scroll.y1 = bounds.y1;
+ content_broadcast(context->c, CONTENT_MSG_SCROLL, &msg_data);
+}
+
+
+/* Exported function documented in search.h */
+void search_step(struct search_context *context, search_flags_t flags,
+ const char *string)
+{
+ int string_len;
+ int i = 0;
+
+ if (context == NULL) {
+ guit->misc->warning("SearchError", 0);
+ return;
+ }
+
+ guit->search->add_recent(string, context->gui_p);
+
+ string_len = strlen(string);
+ for (i = 0; i < string_len; i++)
+ if (string[i] != '#' && string[i] != '*')
+ break;
+ if (i >= string_len) {
+ union content_msg_data msg_data;
+ free_matches(context);
+
+ guit->search->status(true, context->gui_p);
+ guit->search->back_state(false, context->gui_p);
+ guit->search->forward_state(false, context->gui_p);
+
+ msg_data.scroll.area = false;
+ msg_data.scroll.x0 = 0;
+ msg_data.scroll.y0 = 0;
+ content_broadcast(context->c, CONTENT_MSG_SCROLL, &msg_data);
+ return;
+ }
+ search_text(string, string_len, context, flags);
+}
+
+
+/* Exported function documented in search.h */
+bool search_term_highlighted(struct content *c,
+ unsigned start_offset, unsigned end_offset,
+ unsigned *start_idx, unsigned *end_idx,
+ struct search_context *context)
+{
+ if (c == context->c) {
+ struct list_entry *a;
+ for (a = context->found->next; a; a = a->next)
+ if (a->sel && selection_defined(a->sel) &&
+ selection_highlighted(a->sel,
+ start_offset, end_offset,
+ start_idx, end_idx))
+ return true;
+ }
+
+ return false;
+}
+
+
+/* Exported function documented in search.h */
+void search_show_all(bool all, struct search_context *context)
+{
+ struct list_entry *a;
+
+ for (a = context->found->next; a; a = a->next) {
+ bool add = true;
+ if (!all && a != context->current) {
+ add = false;
+ if (a->sel) {
+ selection_clear(a->sel, true);
+ selection_destroy(a->sel);
+ a->sel = NULL;
+ }
+ }
+ if (add && !a->sel) {
+
+ if (context->is_html == true) {
+ html_content *html = (html_content *)context->c;
+ a->sel = selection_create(context->c, true);
+ if (!a->sel)
+ continue;
+
+ selection_init(a->sel, html->layout,
+ &html->len_ctx);
+ } else {
+ a->sel = selection_create(context->c, false);
+ if (!a->sel)
+ continue;
+
+ selection_init(a->sel, NULL, NULL);
+ }
+
+ selection_set_start(a->sel, a->start_idx);
+ selection_set_end(a->sel, a->end_idx);
+ }
+ }
+}
+
+
+/* Exported function documented in search.h */
+void search_destroy_context(struct search_context *context)
+{
+ assert(context != NULL);
+
+ if (context->string != NULL) {
+ guit->search->add_recent(context->string, context->gui_p);
+ free(context->string);
+ }
+
+ guit->search->forward_state(true, context->gui_p);
+ guit->search->back_state(true, context->gui_p);
+
+ free_matches(context);
+ free(context);
+}
diff --git a/content/handlers/html/search.h b/content/handlers/html/search.h
new file mode 100644
index 0000000..5c9408e
--- /dev/null
+++ b/content/handlers/html/search.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2009 Mark Benjamin <netsurf-browser.org.MarkBenjamin(a)dfgh.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
+ * Interface to HTML searching.
+ */
+
+#ifndef NETSURF_HTML_SEARCH_H
+#define NETSURF_HTML_SEARCH_H
+
+#include <ctype.h>
+#include <string.h>
+
+#include "desktop/search.h"
+
+struct search_context;
+
+/**
+ * create a search_context
+ *
+ * \param c The content the search_context is connected to
+ * \param type The content type of c
+ * \param context A context pointer passed to the provider routines.
+ * \return A new search context or NULL on error.
+ */
+struct search_context *search_create_context(struct content *c,
+ content_type type, void *context);
+
+/**
+ * Ends the search process, invalidating all state
+ * freeing the list of found boxes
+ */
+void search_destroy_context(struct search_context *context);
+
+/**
+ * Begins/continues the search process
+ *
+ * \note that this may be called many times for a single search.
+ *
+ * \param context The search context in use.
+ * \param flags The flags forward/back etc
+ * \param string The string to match
+ */
+void search_step(struct search_context *context, search_flags_t flags,
+ const char * string);
+
+/**
+ * Specifies whether all matches or just the current match should
+ * be highlighted in the search text.
+ */
+void search_show_all(bool all, struct search_context *context);
+
+/**
+ * Determines whether any portion of the given text box should be
+ * selected because it matches the current search string.
+ *
+ * \param c The content to hilight within.
+ * \param start_offset byte offset within text of string to be checked
+ * \param end_offset byte offset within text
+ * \param start_idx byte offset within string of highlight start
+ * \param end_idx byte offset of highlight end
+ * \param context The search context to hilight entries from.
+ * \return true iff part of the box should be highlighted
+ */
+bool search_term_highlighted(struct content *c,
+ unsigned start_offset, unsigned end_offset,
+ unsigned *start_idx, unsigned *end_idx,
+ struct search_context *context);
+
+#endif
diff --git a/content/handlers/html/table.c b/content/handlers/html/table.c
new file mode 100644
index 0000000..5609e8f
--- /dev/null
+++ b/content/handlers/html/table.c
@@ -0,0 +1,1080 @@
+/*
+ * Copyright 2005 James Bursa <bursa(a)users.sourceforge.net>
+ * Copyright 2005 Richard Wilson <info(a)tinct.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
+ * implementation of HTML table processing and layout.
+ */
+
+#include <assert.h>
+#include <dom/dom.h>
+
+#include "utils/log.h"
+#include "utils/talloc.h"
+#include "css/utils.h"
+
+#include "html/box.h"
+#include "html/table.h"
+
+/* Define to enable verbose table debug */
+#undef TABLE_DEBUG
+
+/**
+ * Container for border values during table border calculations
+ */
+struct border {
+ enum css_border_style_e style; /**< border-style */
+ enum css_border_color_e color; /**< border-color type */
+ css_color c; /**< border-color value */
+ css_fixed width; /**< border-width length */
+ css_unit unit; /**< border-width units */
+};
+
+static void table_used_left_border_for_cell(
+ const nscss_len_ctx *len_ctx,
+ struct box *cell);
+static void table_used_top_border_for_cell(
+ const nscss_len_ctx *len_ctx,
+ struct box *cell);
+static void table_used_right_border_for_cell(
+ const nscss_len_ctx *len_ctx,
+ struct box *cell);
+static void table_used_bottom_border_for_cell(
+ const nscss_len_ctx *len_ctx,
+ struct box *cell);
+static bool table_border_is_more_eyecatching(
+ const nscss_len_ctx *len_ctx,
+ const struct border *a,
+ box_type a_src,
+ const struct border *b,
+ box_type b_src);
+static void table_cell_top_process_table(
+ const nscss_len_ctx *len_ctx,
+ struct box *table,
+ struct border *a,
+ box_type *a_src);
+static bool table_cell_top_process_group(
+ const nscss_len_ctx *len_ctx,
+ struct box *cell,
+ struct box *group,
+ struct border *a,
+ box_type *a_src);
+static bool table_cell_top_process_row(
+ const nscss_len_ctx *len_ctx,
+ struct box *cell,
+ struct box *row,
+ struct border *a,
+ box_type *a_src);
+
+
+/**
+ * Determine the column width types for a table.
+ *
+ * \param len_ctx Length conversion context
+ * \param table box of type BOX_TABLE
+ * \return true on success, false on memory exhaustion
+ *
+ * The table->col array is allocated and type and width are filled in for each
+ * column.
+ */
+
+bool table_calculate_column_types(
+ const nscss_len_ctx *len_ctx,
+ struct box *table)
+{
+ unsigned int i, j;
+ struct column *col;
+ struct box *row_group, *row, *cell;
+
+ if (table->col)
+ /* table->col already constructed, for example frameset table */
+ return true;
+
+ table->col = col = talloc_array(table, struct column, table->columns);
+ if (!col)
+ return false;
+
+ for (i = 0; i != table->columns; i++) {
+ col[i].type = COLUMN_WIDTH_UNKNOWN;
+ col[i].width = 0;
+ col[i].positioned = true;
+ }
+
+ /* 1st pass: cells with colspan 1 only */
+ for (row_group = table->children; row_group; row_group =row_group->next)
+ for (row = row_group->children; row; row = row->next)
+ for (cell = row->children; cell; cell = cell->next) {
+ enum css_width_e type;
+ css_fixed value = 0;
+ css_unit unit = CSS_UNIT_PX;
+
+ assert(cell->type == BOX_TABLE_CELL);
+ assert(cell->style);
+
+ if (cell->columns != 1)
+ continue;
+ i = cell->start_column;
+
+ if (css_computed_position(cell->style) !=
+ CSS_POSITION_ABSOLUTE &&
+ css_computed_position(cell->style) !=
+ CSS_POSITION_FIXED) {
+ col[i].positioned = false;
+ }
+
+ type = css_computed_width(cell->style, &value, &unit);
+
+ /* fixed width takes priority over any other width type */
+ if (col[i].type != COLUMN_WIDTH_FIXED &&
+ type == CSS_WIDTH_SET && unit != CSS_UNIT_PCT) {
+ col[i].type = COLUMN_WIDTH_FIXED;
+ col[i].width = FIXTOINT(nscss_len2px(len_ctx,
+ value, unit, cell->style));
+ if (col[i].width < 0)
+ col[i].width = 0;
+ continue;
+ }
+
+ if (col[i].type != COLUMN_WIDTH_UNKNOWN)
+ continue;
+
+ if (type == CSS_WIDTH_SET && unit == CSS_UNIT_PCT) {
+ col[i].type = COLUMN_WIDTH_PERCENT;
+ col[i].width = FIXTOINT(value);
+ if (col[i].width < 0)
+ col[i].width = 0;
+ } else if (type == CSS_WIDTH_AUTO) {
+ col[i].type = COLUMN_WIDTH_AUTO;
+ }
+ }
+
+ /* 2nd pass: cells which span multiple columns */
+ for (row_group = table->children; row_group; row_group =row_group->next)
+ for (row = row_group->children; row; row = row->next)
+ for (cell = row->children; cell; cell = cell->next) {
+ unsigned int fixed_columns = 0, percent_columns = 0,
+ auto_columns = 0, unknown_columns = 0;
+ int fixed_width = 0, percent_width = 0;
+ enum css_width_e type;
+ css_fixed value = 0;
+ css_unit unit = CSS_UNIT_PX;
+
+ if (cell->columns == 1)
+ continue;
+ i = cell->start_column;
+
+ for (j = i; j < i + cell->columns; j++) {
+ col[j].positioned = false;
+ }
+
+ /* count column types in spanned cells */
+ for (j = 0; j != cell->columns; j++) {
+ if (col[i + j].type == COLUMN_WIDTH_FIXED) {
+ fixed_width += col[i + j].width;
+ fixed_columns++;
+ } else if (col[i + j].type == COLUMN_WIDTH_PERCENT) {
+ percent_width += col[i + j].width;
+ percent_columns++;
+ } else if (col[i + j].type == COLUMN_WIDTH_AUTO) {
+ auto_columns++;
+ } else {
+ unknown_columns++;
+ }
+ }
+
+ if (!unknown_columns)
+ continue;
+
+ type = css_computed_width(cell->style, &value, &unit);
+
+ /* if cell is fixed width, and all spanned columns are fixed
+ * or unknown width, split extra width among unknown columns */
+ if (type == CSS_WIDTH_SET && unit != CSS_UNIT_PCT &&
+ fixed_columns + unknown_columns ==
+ cell->columns) {
+ int width = (FIXTOFLT(nscss_len2px(len_ctx, value, unit,
+ cell->style)) - fixed_width) /
+ unknown_columns;
+ if (width < 0)
+ width = 0;
+ for (j = 0; j != cell->columns; j++) {
+ if (col[i + j].type == COLUMN_WIDTH_UNKNOWN) {
+ col[i + j].type = COLUMN_WIDTH_FIXED;
+ col[i + j].width = width;
+ }
+ }
+ }
+
+ /* as above for percentage width */
+ if (type == CSS_WIDTH_SET && unit == CSS_UNIT_PCT &&
+ percent_columns + unknown_columns ==
+ cell->columns) {
+ int width = (FIXTOFLT(value) -
+ percent_width) / unknown_columns;
+ if (width < 0)
+ width = 0;
+ for (j = 0; j != cell->columns; j++) {
+ if (col[i + j].type == COLUMN_WIDTH_UNKNOWN) {
+ col[i + j].type = COLUMN_WIDTH_PERCENT;
+ col[i + j].width = width;
+ }
+ }
+ }
+ }
+
+ /* use AUTO if no width type was specified */
+ for (i = 0; i != table->columns; i++) {
+ if (col[i].type == COLUMN_WIDTH_UNKNOWN)
+ col[i].type = COLUMN_WIDTH_AUTO;
+ }
+
+#ifdef TABLE_DEBUG
+ for (i = 0; i != table->columns; i++)
+ NSLOG(netsurf, INFO,
+ "table %p, column %u: type %s, width %i", table, i, ((const char *[]){
+ "UNKNOWN",
+ "FIXED",
+ "AUTO",
+ "PERCENT",
+ "RELATIVE",
+ })[col[i].type], col[i].width);
+#endif
+
+ return true;
+}
+
+/**
+ * Calculate used values of border-{trbl}-{style,color,width} for table cells.
+ *
+ * \param len_ctx Length conversion context
+ * \param cell Table cell to consider
+ *
+ * \post \a cell's border array is populated
+ */
+void table_used_border_for_cell(
+ const nscss_len_ctx *len_ctx,
+ struct box *cell)
+{
+ int side;
+
+ assert(cell->type == BOX_TABLE_CELL);
+
+ if (css_computed_border_collapse(cell->style) ==
+ CSS_BORDER_COLLAPSE_SEPARATE) {
+ css_fixed width = 0;
+ css_unit unit = CSS_UNIT_PX;
+
+ /* Left border */
+ cell->border[LEFT].style =
+ css_computed_border_left_style(cell->style);
+ css_computed_border_left_color(cell->style,
+ &cell->border[LEFT].c);
+ css_computed_border_left_width(cell->style, &width, &unit);
+ cell->border[LEFT].width =
+ FIXTOINT(nscss_len2px(len_ctx,
+ width, unit, cell->style));
+
+ /* Top border */
+ cell->border[TOP].style =
+ css_computed_border_top_style(cell->style);
+ css_computed_border_top_color(cell->style,
+ &cell->border[TOP].c);
+ css_computed_border_top_width(cell->style, &width, &unit);
+ cell->border[TOP].width =
+ FIXTOINT(nscss_len2px(len_ctx,
+ width, unit, cell->style));
+
+ /* Right border */
+ cell->border[RIGHT].style =
+ css_computed_border_right_style(cell->style);
+ css_computed_border_right_color(cell->style,
+ &cell->border[RIGHT].c);
+ css_computed_border_right_width(cell->style, &width, &unit);
+ cell->border[RIGHT].width =
+ FIXTOINT(nscss_len2px(len_ctx,
+ width, unit, cell->style));
+
+ /* Bottom border */
+ cell->border[BOTTOM].style =
+ css_computed_border_bottom_style(cell->style);
+ css_computed_border_bottom_color(cell->style,
+ &cell->border[BOTTOM].c);
+ css_computed_border_bottom_width(cell->style, &width, &unit);
+ cell->border[BOTTOM].width =
+ FIXTOINT(nscss_len2px(len_ctx,
+ width, unit, cell->style));
+ } else {
+ /* Left border */
+ table_used_left_border_for_cell(len_ctx, cell);
+
+ /* Top border */
+ table_used_top_border_for_cell(len_ctx, cell);
+
+ /* Right border */
+ table_used_right_border_for_cell(len_ctx, cell);
+
+ /* Bottom border */
+ table_used_bottom_border_for_cell(len_ctx, cell);
+ }
+
+ /* Finally, ensure that any borders configured as
+ * hidden or none have zero width. (c.f. layout_find_dimensions) */
+ for (side = 0; side != 4; side++) {
+ if (cell->border[side].style == CSS_BORDER_STYLE_HIDDEN ||
+ cell->border[side].style ==
+ CSS_BORDER_STYLE_NONE)
+ cell->border[side].width = 0;
+ }
+}
+
+/******************************************************************************
+ * Helpers for used border calculations *
+ ******************************************************************************/
+
+/**
+ * Calculate used values of border-left-{style,color,width}
+ *
+ * \param len_ctx Length conversion context
+ * \param cell Table cell to consider
+ */
+void table_used_left_border_for_cell(
+ const nscss_len_ctx *len_ctx,
+ struct box *cell)
+{
+ struct border a, b;
+ box_type a_src, b_src;
+
+ /** \todo Need column and column_group, too */
+
+ /* Initialise to computed left border for cell */
+ a.style = css_computed_border_left_style(cell->style);
+ a.color = css_computed_border_left_color(cell->style, &a.c);
+ css_computed_border_left_width(cell->style, &a.width, &a.unit);
+ a.width = nscss_len2px(len_ctx, a.width, a.unit, cell->style);
+ a.unit = CSS_UNIT_PX;
+ a_src = BOX_TABLE_CELL;
+
+ if (cell->prev != NULL || cell->start_column != 0) {
+ /* Cell to the left -- consider its right border */
+ struct box *prev = NULL;
+
+ if (cell->prev == NULL) {
+ struct box *row;
+
+ /* Spanned from a previous row in current row group */
+ for (row = cell->parent; row != NULL; row = row->prev) {
+ for (prev = row->children; prev != NULL;
+ prev = prev->next) {
+ if (prev->start_column +
+ prev->columns ==
+ cell->start_column)
+ break;
+ }
+
+ if (prev != NULL)
+ break;
+ }
+
+ assert(prev != NULL);
+ } else {
+ prev = cell->prev;
+ }
+
+ b.style = css_computed_border_right_style(prev->style);
+ b.color = css_computed_border_right_color(prev->style, &b.c);
+ css_computed_border_right_width(prev->style, &b.width, &b.unit);
+ b.width = nscss_len2px(len_ctx, b.width, b.unit, prev->style);
+ b.unit = CSS_UNIT_PX;
+ b_src = BOX_TABLE_CELL;
+
+ if (table_border_is_more_eyecatching(len_ctx,
+ &a, a_src, &b, b_src)) {
+ a = b;
+ a_src = b_src;
+ }
+ } else {
+ /* First cell in row, so consider rows and row group */
+ struct box *row = cell->parent;
+ struct box *group = row->parent;
+ struct box *table = group->parent;
+ unsigned int rows = cell->rows;
+
+ while (rows-- > 0 && row != NULL) {
+ /* Spanned rows -- consider their left border */
+ b.style = css_computed_border_left_style(row->style);
+ b.color = css_computed_border_left_color(
+ row->style, &b.c);
+ css_computed_border_left_width(
+ row->style, &b.width, &b.unit);
+ b.width = nscss_len2px(len_ctx,
+ b.width, b.unit, row->style);
+ b.unit = CSS_UNIT_PX;
+ b_src = BOX_TABLE_ROW;
+
+ if (table_border_is_more_eyecatching(len_ctx,
+ &a, a_src, &b, b_src)) {
+ a = b;
+ a_src = b_src;
+ }
+
+ row = row->next;
+ }
+
+ /** \todo can cells span row groups? */
+
+ /* Row group -- consider its left border */
+ b.style = css_computed_border_left_style(group->style);
+ b.color = css_computed_border_left_color(group->style, &b.c);
+ css_computed_border_left_width(group->style, &b.width, &b.unit);
+ b.width = nscss_len2px(len_ctx, b.width, b.unit, group->style);
+ b.unit = CSS_UNIT_PX;
+ b_src = BOX_TABLE_ROW_GROUP;
+
+ if (table_border_is_more_eyecatching(len_ctx,
+ &a, a_src, &b, b_src)) {
+ a = b;
+ a_src = b_src;
+ }
+
+ /* The table itself -- consider its left border */
+ b.style = css_computed_border_left_style(table->style);
+ b.color = css_computed_border_left_color(table->style, &b.c);
+ css_computed_border_left_width(table->style, &b.width, &b.unit);
+ b.width = nscss_len2px(len_ctx, b.width, b.unit, table->style);
+ b.unit = CSS_UNIT_PX;
+ b_src = BOX_TABLE;
+
+ if (table_border_is_more_eyecatching(len_ctx,
+ &a, a_src, &b, b_src)) {
+ a = b;
+ a_src = b_src;
+ }
+ }
+
+ /* a now contains the used left border for the cell */
+ cell->border[LEFT].style = a.style;
+ cell->border[LEFT].c = a.c;
+ cell->border[LEFT].width = FIXTOINT(nscss_len2px(len_ctx,
+ a.width, a.unit, cell->style));
+}
+
+/**
+ * Calculate used values of border-top-{style,color,width}
+ *
+ * \param len_ctx Length conversion context
+ * \param cell Table cell to consider
+ */
+void table_used_top_border_for_cell(
+ const nscss_len_ctx *len_ctx,
+ struct box *cell)
+{
+ struct border a, b;
+ box_type a_src, b_src;
+ struct box *row = cell->parent;
+ bool process_group = false;
+
+ /* Initialise to computed top border for cell */
+ a.style = css_computed_border_top_style(cell->style);
+ css_computed_border_top_color(cell->style, &a.c);
+ css_computed_border_top_width(cell->style, &a.width, &a.unit);
+ a.width = nscss_len2px(len_ctx, a.width, a.unit, cell->style);
+ a.unit = CSS_UNIT_PX;
+ a_src = BOX_TABLE_CELL;
+
+ /* Top border of row */
+ b.style = css_computed_border_top_style(row->style);
+ css_computed_border_top_color(row->style, &b.c);
+ css_computed_border_top_width(row->style, &b.width, &b.unit);
+ b.width = nscss_len2px(len_ctx, b.width, b.unit, row->style);
+ b.unit = CSS_UNIT_PX;
+ b_src = BOX_TABLE_ROW;
+
+ if (table_border_is_more_eyecatching(len_ctx, &a, a_src, &b, b_src)) {
+ a = b;
+ a_src = b_src;
+ }
+
+ if (row->prev != NULL) {
+ /* Consider row(s) above */
+ while (table_cell_top_process_row(len_ctx, cell, row->prev,
+ &a, &a_src) == false) {
+ if (row->prev->prev == NULL) {
+ /* Consider row group */
+ process_group = true;
+ break;
+ } else {
+ row = row->prev;
+ }
+ }
+ } else {
+ process_group = true;
+ }
+
+ if (process_group) {
+ struct box *group = row->parent;
+
+ /* Top border of row group */
+ b.style = css_computed_border_top_style(group->style);
+ b.color = css_computed_border_top_color(group->style, &b.c);
+ css_computed_border_top_width(group->style, &b.width, &b.unit);
+ b.width = nscss_len2px(len_ctx, b.width, b.unit, group->style);
+ b.unit = CSS_UNIT_PX;
+ b_src = BOX_TABLE_ROW_GROUP;
+
+ if (table_border_is_more_eyecatching(len_ctx,
+ &a, a_src, &b, b_src)) {
+ a = b;
+ a_src = b_src;
+ }
+
+ if (group->prev == NULL) {
+ /* Top border of table */
+ table_cell_top_process_table(len_ctx,
+ group->parent, &a, &a_src);
+ } else {
+ /* Process previous group(s) */
+ while (table_cell_top_process_group(len_ctx,
+ cell, group->prev,
+ &a, &a_src) == false) {
+ if (group->prev->prev == NULL) {
+ /* Top border of table */
+ table_cell_top_process_table(len_ctx,
+ group->parent,
+ &a, &a_src);
+ break;
+ } else {
+ group = group->prev;
+ }
+ }
+ }
+ }
+
+ /* a now contains the used top border for the cell */
+ cell->border[TOP].style = a.style;
+ cell->border[TOP].c = a.c;
+ cell->border[TOP].width = FIXTOINT(nscss_len2px(len_ctx,
+ a.width, a.unit, cell->style));
+}
+
+/**
+ * Calculate used values of border-right-{style,color,width}
+ *
+ * \param len_ctx Length conversion context
+ * \param cell Table cell to consider
+ */
+void table_used_right_border_for_cell(
+ const nscss_len_ctx *len_ctx,
+ struct box *cell)
+{
+ struct border a, b;
+ box_type a_src, b_src;
+
+ /** \todo Need column and column_group, too */
+
+ /* Initialise to computed right border for cell */
+ a.style = css_computed_border_right_style(cell->style);
+ css_computed_border_right_color(cell->style, &a.c);
+ css_computed_border_right_width(cell->style, &a.width, &a.unit);
+ a.width = nscss_len2px(len_ctx, a.width, a.unit, cell->style);
+ a.unit = CSS_UNIT_PX;
+ a_src = BOX_TABLE_CELL;
+
+ if (cell->next != NULL || cell->start_column + cell->columns !=
+ cell->parent->parent->parent->columns) {
+ /* Cell is not at right edge of table -- no right border */
+ a.style = CSS_BORDER_STYLE_NONE;
+ a.width = 0;
+ a.unit = CSS_UNIT_PX;
+ } else {
+ /* Last cell in row, so consider rows and row group */
+ struct box *row = cell->parent;
+ struct box *group = row->parent;
+ struct box *table = group->parent;
+ unsigned int rows = cell->rows;
+
+ while (rows-- > 0 && row != NULL) {
+ /* Spanned rows -- consider their right border */
+ b.style = css_computed_border_right_style(row->style);
+ b.color = css_computed_border_right_color(
+ row->style, &b.c);
+ css_computed_border_right_width(
+ row->style, &b.width, &b.unit);
+ b.width = nscss_len2px(len_ctx,
+ b.width, b.unit, row->style);
+ b.unit = CSS_UNIT_PX;
+ b_src = BOX_TABLE_ROW;
+
+ if (table_border_is_more_eyecatching(len_ctx,
+ &a, a_src, &b, b_src)) {
+ a = b;
+ a_src = b_src;
+ }
+
+ row = row->next;
+ }
+
+ /** \todo can cells span row groups? */
+
+ /* Row group -- consider its right border */
+ b.style = css_computed_border_right_style(group->style);
+ b.color = css_computed_border_right_color(group->style, &b.c);
+ css_computed_border_right_width(group->style,
+ &b.width, &b.unit);
+ b.width = nscss_len2px(len_ctx, b.width, b.unit, group->style);
+ b.unit = CSS_UNIT_PX;
+ b_src = BOX_TABLE_ROW_GROUP;
+
+ if (table_border_is_more_eyecatching(len_ctx,
+ &a, a_src, &b, b_src)) {
+ a = b;
+ a_src = b_src;
+ }
+
+ /* The table itself -- consider its right border */
+ b.style = css_computed_border_right_style(table->style);
+ b.color = css_computed_border_right_color(table->style, &b.c);
+ css_computed_border_right_width(table->style,
+ &b.width, &b.unit);
+ b.width = nscss_len2px(len_ctx, b.width, b.unit, table->style);
+ b.unit = CSS_UNIT_PX;
+ b_src = BOX_TABLE;
+
+ if (table_border_is_more_eyecatching(len_ctx,
+ &a, a_src, &b, b_src)) {
+ a = b;
+ a_src = b_src;
+ }
+ }
+
+ /* a now contains the used right border for the cell */
+ cell->border[RIGHT].style = a.style;
+ cell->border[RIGHT].c = a.c;
+ cell->border[RIGHT].width = FIXTOINT(nscss_len2px(len_ctx,
+ a.width, a.unit, cell->style));
+}
+
+/**
+ * Calculate used values of border-bottom-{style,color,width}
+ *
+ * \param len_ctx Length conversion context
+ * \param cell Table cell to consider
+ */
+void table_used_bottom_border_for_cell(
+ const nscss_len_ctx *len_ctx,
+ struct box *cell)
+{
+ struct border a, b;
+ box_type a_src, b_src;
+ struct box *row = cell->parent;
+ unsigned int rows = cell->rows;
+
+ /* Initialise to computed bottom border for cell */
+ a.style = css_computed_border_bottom_style(cell->style);
+ css_computed_border_bottom_color(cell->style, &a.c);
+ css_computed_border_bottom_width(cell->style, &a.width, &a.unit);
+ a.width = nscss_len2px(len_ctx, a.width, a.unit, cell->style);
+ a.unit = CSS_UNIT_PX;
+ a_src = BOX_TABLE_CELL;
+
+ while (rows-- > 0 && row != NULL)
+ row = row->next;
+
+ /** \todo Can cells span row groups? */
+
+ if (row != NULL) {
+ /* Cell is not at bottom edge of table -- no bottom border */
+ a.style = CSS_BORDER_STYLE_NONE;
+ a.width = 0;
+ a.unit = CSS_UNIT_PX;
+ } else {
+ /* Cell at bottom of table, so consider row and row group */
+ struct box *row = cell->parent;
+ struct box *group = row->parent;
+ struct box *table = group->parent;
+
+ /* Bottom border of row */
+ b.style = css_computed_border_bottom_style(row->style);
+ b.color = css_computed_border_bottom_color(row->style, &b.c);
+ css_computed_border_bottom_width(row->style, &b.width, &b.unit);
+ b.width = nscss_len2px(len_ctx, b.width, b.unit, row->style);
+ b.unit = CSS_UNIT_PX;
+ b_src = BOX_TABLE_ROW;
+
+ if (table_border_is_more_eyecatching(len_ctx,
+ &a, a_src, &b, b_src)) {
+ a = b;
+ a_src = b_src;
+ }
+
+ /* Row group -- consider its bottom border */
+ b.style = css_computed_border_bottom_style(group->style);
+ b.color = css_computed_border_bottom_color(group->style, &b.c);
+ css_computed_border_bottom_width(group->style,
+ &b.width, &b.unit);
+ b.width = nscss_len2px(len_ctx, b.width, b.unit, group->style);
+ b.unit = CSS_UNIT_PX;
+ b_src = BOX_TABLE_ROW_GROUP;
+
+ if (table_border_is_more_eyecatching(len_ctx,
+ &a, a_src, &b, b_src)) {
+ a = b;
+ a_src = b_src;
+ }
+
+ /* The table itself -- consider its bottom border */
+ b.style = css_computed_border_bottom_style(table->style);
+ b.color = css_computed_border_bottom_color(table->style, &b.c);
+ css_computed_border_bottom_width(table->style,
+ &b.width, &b.unit);
+ b.width = nscss_len2px(len_ctx, b.width, b.unit, table->style);
+ b.unit = CSS_UNIT_PX;
+ b_src = BOX_TABLE;
+
+ if (table_border_is_more_eyecatching(len_ctx,
+ &a, a_src, &b, b_src)) {
+ a = b;
+ }
+ }
+
+ /* a now contains the used bottom border for the cell */
+ cell->border[BOTTOM].style = a.style;
+ cell->border[BOTTOM].c = a.c;
+ cell->border[BOTTOM].width = FIXTOINT(nscss_len2px(len_ctx,
+ a.width, a.unit, cell->style));
+}
+
+/**
+ * Determine if a border style is more eyecatching than another
+ *
+ * \param len_ctx Length conversion context
+ * \param a Reference border style
+ * \param a_src Source of \a a
+ * \param b Candidate border style
+ * \param b_src Source of \a b
+ * \return True if \a b is more eyecatching than \a a
+ */
+bool table_border_is_more_eyecatching(
+ const nscss_len_ctx *len_ctx,
+ const struct border *a,
+ box_type a_src,
+ const struct border *b,
+ box_type b_src)
+{
+ css_fixed awidth, bwidth;
+ int impact = 0;
+
+ /* See CSS 2.1 $17.6.2.1 */
+
+ /* 1 + 2 -- hidden beats everything, none beats nothing */
+ if (a->style == CSS_BORDER_STYLE_HIDDEN ||
+ b->style == CSS_BORDER_STYLE_NONE)
+ return false;
+
+ if (b->style == CSS_BORDER_STYLE_HIDDEN ||
+ a->style == CSS_BORDER_STYLE_NONE)
+ return true;
+
+ /* 3a -- wider borders beat narrow ones */
+ /* The widths must be absolute, which will be the case
+ * if they've come from a computed style. */
+ assert(a->unit != CSS_UNIT_EM && a->unit != CSS_UNIT_EX);
+ assert(b->unit != CSS_UNIT_EM && b->unit != CSS_UNIT_EX);
+ awidth = nscss_len2px(len_ctx, a->width, a->unit, NULL);
+ bwidth = nscss_len2px(len_ctx, b->width, b->unit, NULL);
+
+ if (awidth < bwidth)
+ return true;
+ else if (bwidth < awidth)
+ return false;
+
+ /* 3b -- sort by style */
+ switch (a->style) {
+ case CSS_BORDER_STYLE_DOUBLE: impact++; /* Fall through */
+ case CSS_BORDER_STYLE_SOLID: impact++; /* Fall through */
+ case CSS_BORDER_STYLE_DASHED: impact++; /* Fall through */
+ case CSS_BORDER_STYLE_DOTTED: impact++; /* Fall through */
+ case CSS_BORDER_STYLE_RIDGE: impact++; /* Fall through */
+ case CSS_BORDER_STYLE_OUTSET: impact++; /* Fall through */
+ case CSS_BORDER_STYLE_GROOVE: impact++; /* Fall through */
+ case CSS_BORDER_STYLE_INSET: impact++; /* Fall through */
+ default:
+ break;
+ }
+
+ switch (b->style) {
+ case CSS_BORDER_STYLE_DOUBLE: impact--; /* Fall through */
+ case CSS_BORDER_STYLE_SOLID: impact--; /* Fall through */
+ case CSS_BORDER_STYLE_DASHED: impact--; /* Fall through */
+ case CSS_BORDER_STYLE_DOTTED: impact--; /* Fall through */
+ case CSS_BORDER_STYLE_RIDGE: impact--; /* Fall through */
+ case CSS_BORDER_STYLE_OUTSET: impact--; /* Fall through */
+ case CSS_BORDER_STYLE_GROOVE: impact--; /* Fall through */
+ case CSS_BORDER_STYLE_INSET: impact--; /* Fall through */
+ default:
+ break;
+ }
+
+ if (impact < 0)
+ return true;
+ else if (impact > 0)
+ return false;
+
+ /* 4a -- sort by origin */
+ impact = 0;
+
+ /** \todo COL/COL_GROUP */
+ switch (a_src) {
+ case BOX_TABLE_CELL: impact++; /* Fall through */
+ case BOX_TABLE_ROW: impact++; /* Fall through */
+ case BOX_TABLE_ROW_GROUP: impact++; /* Fall through */
+ case BOX_TABLE: impact++; /* Fall through */
+ default:
+ break;
+ }
+
+ /** \todo COL/COL_GROUP */
+ switch (b_src) {
+ case BOX_TABLE_CELL: impact--; /* Fall through */
+ case BOX_TABLE_ROW: impact--; /* Fall through */
+ case BOX_TABLE_ROW_GROUP: impact--; /* Fall through */
+ case BOX_TABLE: impact--; /* Fall through */
+ default:
+ break;
+ }
+
+ if (impact < 0)
+ return true;
+ else if (impact > 0)
+ return false;
+
+ /* 4b -- furthest left (if direction: ltr) and towards top wins */
+ /** \todo Currently assumes b satisifies this */
+ return true;
+}
+
+/******************************************************************************
+ * Helpers for top border collapsing *
+ ******************************************************************************/
+
+/**
+ * Process a table
+ *
+ * \param len_ctx Length conversion context
+ * \param table Table to process
+ * \param a Current border style for cell
+ * \param a_src Source of \a a
+ *
+ * \post \a a will be updated with most eyecatching style
+ * \post \a a_src will be updated also
+ */
+void table_cell_top_process_table(
+ const nscss_len_ctx *len_ctx,
+ struct box *table,
+ struct border *a,
+ box_type *a_src)
+{
+ struct border b;
+ box_type b_src;
+
+ /* Top border of table */
+ b.style = css_computed_border_top_style(table->style);
+ b.color = css_computed_border_top_color(table->style, &b.c);
+ css_computed_border_top_width(table->style, &b.width, &b.unit);
+ b.width = nscss_len2px(len_ctx, b.width, b.unit, table->style);
+ b.unit = CSS_UNIT_PX;
+ b_src = BOX_TABLE;
+
+ if (table_border_is_more_eyecatching(len_ctx, a, *a_src, &b, b_src)) {
+ *a = b;
+ *a_src = b_src;
+ }
+}
+
+/**
+ * Process a group
+ *
+ * \param len_ctx Length conversion context
+ * \param cell Cell being considered
+ * \param group Group to process
+ * \param a Current border style for cell
+ * \param a_src Source of \a a
+ * \return true if group has non-empty rows, false otherwise
+ *
+ * \post \a a will be updated with most eyecatching style
+ * \post \a a_src will be updated also
+ */
+bool table_cell_top_process_group(
+ const nscss_len_ctx *len_ctx,
+ struct box *cell,
+ struct box *group,
+ struct border *a,
+ box_type *a_src)
+{
+ struct border b;
+ box_type b_src;
+
+ /* Bottom border of group */
+ b.style = css_computed_border_bottom_style(group->style);
+ b.color = css_computed_border_bottom_color(group->style, &b.c);
+ css_computed_border_bottom_width(group->style, &b.width, &b.unit);
+ b.width = nscss_len2px(len_ctx, b.width, b.unit, group->style);
+ b.unit = CSS_UNIT_PX;
+ b_src = BOX_TABLE_ROW_GROUP;
+
+ if (table_border_is_more_eyecatching(len_ctx, a, *a_src, &b, b_src)) {
+ *a = b;
+ *a_src = b_src;
+ }
+
+ if (group->last != NULL) {
+ /* Process rows in group, starting with last */
+ struct box *row = group->last;
+
+ while (table_cell_top_process_row(len_ctx, cell, row,
+ a, a_src) == false) {
+ if (row->prev == NULL) {
+ return false;
+ } else {
+ row = row->prev;
+ }
+ }
+ } else {
+ /* Group is empty, so consider its top border */
+ b.style = css_computed_border_top_style(group->style);
+ b.color = css_computed_border_top_color(group->style, &b.c);
+ css_computed_border_top_width(group->style, &b.width, &b.unit);
+ b.width = nscss_len2px(len_ctx, b.width, b.unit, group->style);
+ b.unit = CSS_UNIT_PX;
+ b_src = BOX_TABLE_ROW_GROUP;
+
+ if (table_border_is_more_eyecatching(len_ctx,
+ a, *a_src, &b, b_src)) {
+ *a = b;
+ *a_src = b_src;
+ }
+
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Process a row
+ *
+ * \param len_ctx Length conversion context
+ * \param cell Cell being considered
+ * \param row Row to process
+ * \param a Current border style for cell
+ * \param a_src Source of \a a
+ * \return true if row has cells, false otherwise
+ *
+ * \post \a a will be updated with most eyecatching style
+ * \post \a a_src will be updated also
+ */
+bool table_cell_top_process_row(
+ const nscss_len_ctx *len_ctx,
+ struct box *cell,
+ struct box *row,
+ struct border *a,
+ box_type *a_src)
+{
+ struct border b;
+ box_type b_src;
+
+ /* Bottom border of row */
+ b.style = css_computed_border_bottom_style(row->style);
+ b.color = css_computed_border_bottom_color(row->style, &b.c);
+ css_computed_border_bottom_width(row->style, &b.width, &b.unit);
+ b.width = nscss_len2px(len_ctx, b.width, b.unit, row->style);
+ b.unit = CSS_UNIT_PX;
+ b_src = BOX_TABLE_ROW;
+
+ if (table_border_is_more_eyecatching(len_ctx, a, *a_src, &b, b_src)) {
+ *a = b;
+ *a_src = b_src;
+ }
+
+ if (row->children == NULL) {
+ /* Row is empty, so consider its top border */
+ b.style = css_computed_border_top_style(row->style);
+ b.color = css_computed_border_top_color(row->style, &b.c);
+ css_computed_border_top_width(row->style, &b.width, &b.unit);
+ b.width = nscss_len2px(len_ctx, b.width, b.unit, row->style);
+ b.unit = CSS_UNIT_PX;
+ b_src = BOX_TABLE_ROW;
+
+ if (table_border_is_more_eyecatching(len_ctx,
+ a, *a_src, &b, b_src)) {
+ *a = b;
+ *a_src = b_src;
+ }
+
+ return false;
+ } else {
+ /* Process cells that are directly above the cell being
+ * considered. They may not be in this row, but in one of the
+ * rows above it in the case where rowspan > 1. */
+ struct box *c;
+ bool processed = false;
+
+ while (processed == false) {
+ for (c = row->children; c != NULL; c = c->next) {
+ /* Ignore cells to the left */
+ if (c->start_column + c->columns - 1 <
+ cell->start_column)
+ continue;
+ /* Ignore cells to the right */
+ if (c->start_column > cell->start_column +
+ cell->columns - 1)
+ continue;
+
+ /* Flag that we've processed a cell */
+ processed = true;
+
+ /* Consider bottom border */
+ b.style = css_computed_border_bottom_style(
+ c->style);
+ b.color = css_computed_border_bottom_color(
+ c->style, &b.c);
+ css_computed_border_bottom_width(c->style,
+ &b.width, &b.unit);
+ b.width = nscss_len2px(len_ctx,
+ b.width, b.unit, c->style);
+ b.unit = CSS_UNIT_PX;
+ b_src = BOX_TABLE_CELL;
+
+ if (table_border_is_more_eyecatching(len_ctx,
+ a, *a_src, &b, b_src)) {
+ *a = b;
+ *a_src = b_src;
+ }
+ }
+
+ if (processed == false) {
+ /* There must be a preceding row */
+ assert(row->prev != NULL);
+
+ row = row->prev;
+ }
+ }
+ }
+
+ return true;
+}
diff --git a/content/handlers/html/table.h b/content/handlers/html/table.h
new file mode 100644
index 0000000..11ab653
--- /dev/null
+++ b/content/handlers/html/table.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2005 James Bursa <bursa(a)users.sourceforge.net>
+ * Copyright 2005 Richard Wilson <info(a)tinct.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
+ * Interface to HTML table processing and layout.
+ */
+
+#ifndef NETSURF_HTML_TABLE_H
+#define NETSURF_HTML_TABLE_H
+
+#include <stdbool.h>
+
+struct box;
+
+bool table_calculate_column_types(
+ const nscss_len_ctx *len_ctx,
+ struct box *table);
+void table_used_border_for_cell(
+ const nscss_len_ctx *len_ctx,
+ struct box *cell);
+
+#endif
diff --git a/content/handlers/javascript/duktape/Document.bnd b/content/handlers/javascript/duktape/Document.bnd
index ece417d..5de7245 100644
--- a/content/handlers/javascript/duktape/Document.bnd
+++ b/content/handlers/javascript/duktape/Document.bnd
@@ -14,7 +14,7 @@ prologue Document()
#include "utils/libdom.h"
#include "utils/utils.h"
#include "content/hlcache.h"
-#include "render/html_internal.h"
+#include "html/html_internal.h"
#include "content/urldb.h"
#define HANDLER_MAGIC MAGIC(HANDLER_MAP)
diff --git a/content/handlers/javascript/duktape/Window.bnd b/content/handlers/javascript/duktape/Window.bnd
index 3f680d4..f647fd1 100644
--- a/content/handlers/javascript/duktape/Window.bnd
+++ b/content/handlers/javascript/duktape/Window.bnd
@@ -15,8 +15,8 @@ class Window {
#include "utils/nsurl.h"
#include "netsurf/browser_window.h"
#include "content/hlcache.h"
-#include "render/html.h"
-#include "render/html_internal.h"
+#include "html/html.h"
+#include "html/html_internal.h"
%};
};
diff --git a/content/handlers/text/Makefile b/content/handlers/text/Makefile
new file mode 100644
index 0000000..83d5dbd
--- /dev/null
+++ b/content/handlers/text/Makefile
@@ -0,0 +1,3 @@
+# text content handler sources
+
+S_TEXT := textplain.c
diff --git a/content/handlers/text/textplain.c b/content/handlers/text/textplain.c
new file mode 100644
index 0000000..e6d167b
--- /dev/null
+++ b/content/handlers/text/textplain.c
@@ -0,0 +1,1576 @@
+/*
+ * Copyright 2006 James Bursa <bursa(a)users.sourceforge.net>
+ * Copyright 2006 Adrian Lees <adrianl(a)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
+ *
+ * plain text content handling implementation.
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <stddef.h>
+#include <string.h>
+#include <strings.h>
+#include <math.h>
+
+#include <parserutils/input/inputstream.h>
+#include <parserutils/charset/utf8.h>
+
+#include "utils/corestrings.h"
+#include "utils/http.h"
+#include "utils/log.h"
+#include "utils/messages.h"
+#include "utils/utils.h"
+#include "utils/utf8.h"
+#include "netsurf/content.h"
+#include "netsurf/keypress.h"
+#include "netsurf/browser_window.h"
+#include "netsurf/plotters.h"
+#include "netsurf/layout.h"
+#include "content/content_protected.h"
+#include "content/hlcache.h"
+#include "css/utils.h"
+#include "utils/nsoption.h"
+#include "desktop/search.h"
+#include "desktop/selection.h"
+#include "desktop/gui_internal.h"
+
+#include "html/search.h"
+#include "text/textplain.h"
+
+struct textplain_line {
+ size_t start;
+ size_t length;
+};
+
+typedef struct textplain_content {
+ struct content base;
+
+ lwc_string *encoding;
+ void *inputstream;
+ char *utf8_data;
+ size_t utf8_data_size;
+ size_t utf8_data_allocated;
+ unsigned long physical_line_count;
+ struct textplain_line *physical_line;
+ int formatted_width;
+ struct browser_window *bw;
+
+ struct selection sel; /** Selection state */
+
+ /** Context for free text search, or NULL if none */
+ struct search_context *search;
+ /** Current search string, or NULL if none */
+ char *search_string;
+} textplain_content;
+
+
+#define CHUNK 32768 /* Must be a power of 2 */
+#define MARGIN 4
+
+#define TAB_WIDTH 8 /* must be power of 2 currently */
+#define TEXT_SIZE 10 * FONT_SIZE_SCALE /* Unscaled text size in pt */
+
+static plot_font_style_t textplain_style = {
+ .family = PLOT_FONT_FAMILY_MONOSPACE,
+ .size = TEXT_SIZE,
+ .weight = 400,
+ .flags = FONTF_NONE,
+ .background = 0xffffff,
+ .foreground = 0x000000,
+};
+
+static int textplain_tab_width = 256; /* try for a sensible default */
+
+static lwc_string *textplain_default_charset;
+
+
+/**
+ * Clean up after the text content handler
+ */
+static void textplain_fini(void)
+{
+ if (textplain_default_charset != NULL) {
+ lwc_string_unref(textplain_default_charset);
+ textplain_default_charset = NULL;
+ }
+}
+
+
+/**
+ * Work around feature in libparserutils
+ *
+ * if the client provides an encoding up front, but does not provide a
+ * charset detection callback, then libparserutils will replace the
+ * provided encoding with UTF-8. This breaks our input handling.
+ *
+ * Avoid this by providing a callback that does precisely nothing,
+ * thus preserving whatever charset information we decided on in
+ * textplain_create.
+ */
+static parserutils_error
+textplain_charset_hack(const uint8_t *data,
+ size_t len,
+ uint16_t *mibenum,
+ uint32_t *source)
+{
+ return PARSERUTILS_OK;
+}
+
+
+/**
+ * setup plain text render.
+ *
+ * \param[in] c content object.
+ * \param[in] encoding the encoding of the content.
+ * \return NSERROR_OK else appropriate error code.
+ */
+static nserror
+textplain_create_internal(textplain_content *c, lwc_string *encoding)
+{
+ char *utf8_data;
+ parserutils_inputstream *stream;
+ parserutils_error error;
+
+ textplain_style.size = (nsoption_int(font_size) * FONT_SIZE_SCALE) / 10;
+
+ utf8_data = malloc(CHUNK);
+ if (utf8_data == NULL)
+ goto no_memory;
+
+ error = parserutils_inputstream_create(lwc_string_data(encoding), 0,
+ textplain_charset_hack, &stream);
+ if (error == PARSERUTILS_BADENCODING) {
+ /* Fall back to Windows-1252 */
+ error = parserutils_inputstream_create("Windows-1252", 0,
+ textplain_charset_hack, &stream);
+ }
+ if (error != PARSERUTILS_OK) {
+ free(utf8_data);
+ goto no_memory;
+ }
+
+ c->encoding = lwc_string_ref(encoding);
+ c->inputstream = stream;
+ c->utf8_data = utf8_data;
+ c->utf8_data_size = 0;
+ c->utf8_data_allocated = CHUNK;
+ c->physical_line = 0;
+ c->physical_line_count = 0;
+ c->formatted_width = 0;
+ c->bw = NULL;
+
+ selection_prepare(&c->sel, (struct content *)c, false);
+
+ return NSERROR_OK;
+
+no_memory:
+ content_broadcast_errorcode(&c->base, NSERROR_NOMEM);
+
+ return NSERROR_NOMEM;
+}
+
+
+/**
+ * Create a CONTENT_TEXTPLAIN.
+ */
+static nserror
+textplain_create(const content_handler *handler,
+ lwc_string *imime_type,
+ const http_parameter *params,
+ llcache_handle *llcache,
+ const char *fallback_charset,
+ bool quirks,
+ struct content **c)
+{
+ textplain_content *text;
+ nserror error;
+ lwc_string *encoding;
+
+ text = calloc(1, sizeof(textplain_content));
+ if (text == NULL) {
+ return NSERROR_NOMEM;
+ }
+
+ error = content__init(&text->base, handler, imime_type, params,
+ llcache, fallback_charset, quirks);
+ if (error != NSERROR_OK) {
+ free(text);
+ return error;
+ }
+
+ error = http_parameter_list_find_item(params, corestring_lwc_charset,
+ &encoding);
+ if (error != NSERROR_OK) {
+ encoding = lwc_string_ref(textplain_default_charset);
+ }
+
+ error = textplain_create_internal(text, encoding);
+ if (error != NSERROR_OK) {
+ lwc_string_unref(encoding);
+ free(text);
+ return error;
+ }
+
+ lwc_string_unref(encoding);
+
+ *c = (struct content *) text;
+
+ return NSERROR_OK;
+}
+
+
+/**
+ * copy utf8 encoded data
+ */
+static bool
+textplain_copy_utf8_data(textplain_content *c, const uint8_t *buf, size_t len)
+{
+ if (c->utf8_data_size + len >= c->utf8_data_allocated) {
+ /* Compute next multiple of chunk above the required space */
+ size_t allocated;
+ char *utf8_data;
+
+ allocated = (c->utf8_data_size + len + CHUNK - 1) & ~(CHUNK - 1);
+ utf8_data = realloc(c->utf8_data, allocated);
+ if (utf8_data == NULL)
+ return false;
+
+ c->utf8_data = utf8_data;
+ c->utf8_data_allocated = allocated;
+ }
+
+ memcpy(c->utf8_data + c->utf8_data_size, buf, len);
+ c->utf8_data_size += len;
+
+ return true;
+}
+
+
+/**
+ * drain input
+ */
+static bool
+textplain_drain_input(textplain_content *c,
+ parserutils_inputstream *stream,
+ parserutils_error terminator)
+{
+ static const uint8_t *u_fffd = (const uint8_t *) "\xef\xbf\xfd";
+ const uint8_t *ch;
+ size_t chlen, offset = 0;
+
+ while (parserutils_inputstream_peek(stream, offset, &ch, &chlen) !=
+ terminator) {
+ /* Replace all instances of NUL with U+FFFD */
+ if (chlen == 1 && *ch == 0) {
+ if (offset > 0) {
+ /* Obtain pointer to start of input data */
+ parserutils_inputstream_peek(stream, 0,
+ &ch, &chlen);
+ /* Copy from it up to the start of the NUL */
+ if (textplain_copy_utf8_data(c, ch,
+ offset) == false)
+ return false;
+ }
+
+ /* Emit U+FFFD */
+ if (textplain_copy_utf8_data(c, u_fffd, 3) == false)
+ return false;
+
+ /* Advance inputstream past the NUL we just read */
+ parserutils_inputstream_advance(stream, offset + 1);
+ /* Reset the read offset */
+ offset = 0;
+ } else {
+ /* Accumulate input */
+ offset += chlen;
+
+ if (offset > CHUNK) {
+ /* Obtain pointer to start of input data */
+ parserutils_inputstream_peek(stream, 0,
+ &ch, &chlen);
+
+ /* Emit the data we've read */
+ if (textplain_copy_utf8_data(c, ch,
+ offset) == false)
+ return false;
+
+ /* Advance the inputstream */
+ parserutils_inputstream_advance(stream, offset);
+ /* Reset the read offset */
+ offset = 0;
+ }
+ }
+ }
+
+ if (offset > 0) {
+ /* Obtain pointer to start of input data */
+ parserutils_inputstream_peek(stream, 0, &ch, &chlen);
+ /* Emit any data remaining */
+ if (textplain_copy_utf8_data(c, ch, offset) == false)
+ return false;
+
+ /* Advance the inputstream past the data we've read */
+ parserutils_inputstream_advance(stream, offset);
+ }
+
+ return true;
+}
+
+
+/**
+ * Process data for CONTENT_TEXTPLAIN.
+ */
+static bool
+textplain_process_data(struct content *c, const char *data, unsigned int size)
+{
+ textplain_content *text = (textplain_content *) c;
+ parserutils_inputstream *stream = text->inputstream;
+ parserutils_error error;
+
+ error = parserutils_inputstream_append(stream,
+ (const uint8_t *) data, size);
+ if (error != PARSERUTILS_OK) {
+ goto no_memory;
+ }
+
+ if (textplain_drain_input(text, stream, PARSERUTILS_NEEDDATA) == false)
+ goto no_memory;
+
+ return true;
+
+no_memory:
+ content_broadcast_errorcode(c, NSERROR_NOMEM);
+ return false;
+}
+
+
+/**
+ * Convert a CONTENT_TEXTPLAIN for display.
+ */
+static bool textplain_convert(struct content *c)
+{
+ textplain_content *text = (textplain_content *) c;
+ parserutils_inputstream *stream = text->inputstream;
+ parserutils_error error;
+
+ error = parserutils_inputstream_append(stream, NULL, 0);
+ if (error != PARSERUTILS_OK) {
+ return false;
+ }
+
+ if (textplain_drain_input(text, stream, PARSERUTILS_EOF) == false)
+ return false;
+
+ parserutils_inputstream_destroy(stream);
+ text->inputstream = NULL;
+
+ content_set_ready(c);
+ content_set_done(c);
+ content_set_status(c, messages_get("Done"));
+
+ return true;
+}
+
+
+/**
+ * Calculate the line height, in pixels
+ *
+ * \return Line height, in pixels
+ */
+static float textplain_line_height(void)
+{
+ /* Size is in points, so convert to pixels.
+ * Then use a constant line height of 1.2 x font size.
+ */
+ return FIXTOFLT(FDIV((FMUL(FLTTOFIX(1.2), FMUL(nscss_screen_dpi, INTTOFIX((textplain_style.size / FONT_SIZE_SCALE))))), F_72));
+}
+
+
+/**
+ * Reformat a CONTENT_TEXTPLAIN to a new width.
+ */
+static void textplain_reformat(struct content *c, int width, int height)
+{
+ textplain_content *text = (textplain_content *) c;
+ char *utf8_data = text->utf8_data;
+ size_t utf8_data_size = text->utf8_data_size;
+ unsigned long line_count = 0;
+ struct textplain_line *line = text->physical_line;
+ struct textplain_line *line1;
+ size_t i, space, col;
+ size_t columns = 80;
+ int character_width;
+ size_t line_start;
+ nserror res;
+
+ NSLOG(netsurf, INFO, "content %p w:%d h:%d", c, width, height);
+
+ /* compute available columns (assuming monospaced font) - use 8
+ * characters for better accuracy
+ */
+ res = guit->layout->width(&textplain_style,
+ "ABCDEFGH", 8,
+ &character_width);
+ if (res != NSERROR_OK) {
+ return;
+ }
+
+ columns = (width - MARGIN - MARGIN) * 8 / character_width;
+ textplain_tab_width = (TAB_WIDTH * character_width) / 8;
+
+ text->formatted_width = width;
+
+ text->physical_line_count = 0;
+
+ if (!line) {
+ text->physical_line = line =
+ malloc(sizeof(struct textplain_line) * (1024 + 3));
+ if (!line)
+ goto no_memory;
+ }
+
+ line[line_count++].start = line_start = 0;
+ space = 0;
+ i = 0;
+ col = 0;
+ while (i < utf8_data_size) {
+ size_t csize; /* number of bytes in character */
+ uint32_t chr;
+ bool term;
+ size_t next_col;
+ parserutils_error perror;
+
+ perror = parserutils_charset_utf8_to_ucs4((const uint8_t *)utf8_data + i, utf8_data_size - i, &chr, &csize);
+ if (perror != PARSERUTILS_OK) {
+ chr = 0xfffd;
+ }
+
+ term = (chr == '\n' || chr == '\r');
+
+ next_col = col + 1;
+
+ if (chr == '\t') {
+ next_col = (next_col + TAB_WIDTH - 1) & ~(TAB_WIDTH - 1);
+ }
+
+ if (term || next_col >= columns) {
+ if (line_count % 1024 == 0) {
+ line1 = realloc(line,
+ sizeof(struct textplain_line) *
+ (line_count + 1024 + 3));
+ if (!line1)
+ goto no_memory;
+ text->physical_line = line = line1;
+ }
+
+ if (term) {
+ line[line_count-1].length = i - line_start;
+
+ /* skip second char of CR/LF or LF/CR pair */
+ if (i + 1 < utf8_data_size &&
+ utf8_data[i+1] != utf8_data[i] &&
+ (utf8_data[i+1] == '\n' ||
+ utf8_data[i+1] == '\r')) {
+ i++;
+ }
+ } else {
+ if (space) {
+ /* break at last space in line */
+ i = space;
+ line[line_count-1].length = (i + 1) - line_start;
+ } else
+ line[line_count-1].length = i - line_start;
+ }
+
+ line[line_count++].start = line_start = i + 1;
+ col = 0;
+ space = 0;
+ } else {
+ col++;
+ if (chr == ' ')
+ space = i;
+ }
+ i += csize;
+ }
+ line[line_count-1].length = i - line[line_count-1].start;
+ line[line_count].start = utf8_data_size;
+
+ text->physical_line_count = line_count;
+ c->width = width;
+ c->height = line_count * textplain_line_height() + MARGIN + MARGIN;
+
+ return;
+
+no_memory:
+ NSLOG(netsurf, INFO, "out of memory (line_count %lu)", line_count);
+ return;
+}
+
+
+/**
+ * Destroy a CONTENT_TEXTPLAIN and free all resources it owns.
+ */
+
+static void textplain_destroy(struct content *c)
+{
+ textplain_content *text = (textplain_content *) c;
+
+ lwc_string_unref(text->encoding);
+
+ if (text->inputstream != NULL) {
+ parserutils_inputstream_destroy(text->inputstream);
+ }
+
+ if (text->physical_line != NULL) {
+ free(text->physical_line);
+ }
+
+ if (text->utf8_data != NULL) {
+ free(text->utf8_data);
+ }
+}
+
+
+static nserror textplain_clone(const struct content *old, struct content **newc)
+{
+ const textplain_content *old_text = (textplain_content *) old;
+ textplain_content *text;
+ nserror error;
+ const char *data;
+ unsigned long size;
+
+ text = calloc(1, sizeof(textplain_content));
+ if (text == NULL)
+ return NSERROR_NOMEM;
+
+ error = content__clone(old, &text->base);
+ if (error != NSERROR_OK) {
+ content_destroy(&text->base);
+ return error;
+ }
+
+ /* Simply replay create/process/convert */
+ error = textplain_create_internal(text, old_text->encoding);
+ if (error != NSERROR_OK) {
+ content_destroy(&text->base);
+ return error;
+ }
+
+ data = content__get_source_data(&text->base, &size);
+ if (size > 0) {
+ if (textplain_process_data(&text->base, data, size) == false) {
+ content_destroy(&text->base);
+ return NSERROR_NOMEM;
+ }
+ }
+
+ if (old->status == CONTENT_STATUS_READY ||
+ old->status == CONTENT_STATUS_DONE) {
+ if (textplain_convert(&text->base) == false) {
+ content_destroy(&text->base);
+ return NSERROR_CLONE_FAILED;
+ }
+ }
+
+ return NSERROR_OK;
+}
+
+
+static content_type textplain_content_type(void)
+{
+ return CONTENT_TEXTPLAIN;
+}
+
+
+/**
+ * Handle mouse clicks and movements in a TEXTPLAIN content window.
+ *
+ * \param c content of type textplain
+ * \param bw browser window
+ * \param mouse mouse state on action
+ * \param x coordinate of mouse
+ * \param y coordinate of mouse
+ */
+static void
+textplain_mouse_action(struct content *c,
+ struct browser_window *bw,
+ browser_mouse_state mouse,
+ int x, int y)
+{
+ textplain_content *text = (textplain_content *) c;
+ browser_pointer_shape pointer = BROWSER_POINTER_DEFAULT;
+ union content_msg_data msg_data;
+ const char *status = 0;
+ size_t idx;
+ int dir = 0;
+
+ browser_window_set_drag_type(bw, DRAGGING_NONE, NULL);
+
+ idx = textplain_offset_from_coords(c, x, y, dir);
+ if (selection_click(&text->sel, mouse, idx)) {
+
+ if (selection_dragging(&text->sel)) {
+ browser_window_set_drag_type(bw,
+ DRAGGING_SELECTION, NULL);
+ status = messages_get("Selecting");
+ }
+
+ } else {
+ if (mouse & (BROWSER_MOUSE_DRAG_1 | BROWSER_MOUSE_DRAG_2)) {
+ browser_window_page_drag_start(bw, x, y);
+ pointer = BROWSER_POINTER_MOVE;
+ }
+ }
+
+ msg_data.explicit_status_text = status;
+ content_broadcast(c, CONTENT_MSG_STATUS, &msg_data);
+
+ msg_data.pointer = pointer;
+ content_broadcast(c, CONTENT_MSG_POINTER, &msg_data);
+}
+
+
+/**
+ * Handle mouse tracking (including drags) in a TEXTPLAIN content window.
+ *
+ * \param c content of type textplain
+ * \param bw browser window
+ * \param mouse state of mouse buttons and modifier keys
+ * \param x coordinate of mouse
+ * \param y coordinate of mouse
+ */
+static void
+textplain_mouse_track(struct content *c,
+ struct browser_window *bw,
+ browser_mouse_state mouse,
+ int x, int y)
+{
+ textplain_content *text = (textplain_content *) c;
+
+ if (browser_window_get_drag_type(bw) == DRAGGING_SELECTION && !mouse) {
+ int dir = -1;
+ size_t idx;
+
+ if (selection_dragging_start(&text->sel))
+ dir = 1;
+
+ idx = textplain_offset_from_coords(c, x, y, dir);
+ selection_track(&text->sel, mouse, idx);
+
+ browser_window_set_drag_type(bw, DRAGGING_NONE, NULL);
+ }
+
+ switch (browser_window_get_drag_type(bw)) {
+
+ case DRAGGING_SELECTION: {
+ int dir = -1;
+ size_t idx;
+
+ if (selection_dragging_start(&text->sel)) dir = 1;
+
+ idx = textplain_offset_from_coords(c, x, y, dir);
+ selection_track(&text->sel, mouse, idx);
+ }
+ break;
+
+ default:
+ textplain_mouse_action(c, bw, mouse, x, y);
+ break;
+ }
+}
+
+
+/**
+ * Handle keypresses.
+ *
+ * \param c content of type CONTENT_TEXTPLAIN
+ * \param key The UCS4 character codepoint
+ * \return true if key handled, false otherwise
+ */
+static bool textplain_keypress(struct content *c, uint32_t key)
+{
+ textplain_content *text = (textplain_content *) c;
+ struct selection *sel = &text->sel;
+
+ switch (key) {
+ case NS_KEY_COPY_SELECTION:
+ selection_copy_to_clipboard(sel);
+ return true;
+
+ case NS_KEY_CLEAR_SELECTION:
+ selection_clear(sel, true);
+ return true;
+
+ case NS_KEY_SELECT_ALL:
+ selection_select_all(sel);
+ return true;
+
+ case NS_KEY_ESCAPE:
+ if (selection_defined(sel)) {
+ selection_clear(sel, true);
+ return true;
+ }
+
+ /* if there's no selection, leave Escape for the caller */
+ return false;
+ }
+
+ return false;
+}
+
+
+/**
+ * Terminate a search.
+ *
+ * \param c content of type text
+ */
+static void textplain_search_clear(struct content *c)
+{
+ textplain_content *text = (textplain_content *) c;
+
+ assert(c != NULL);
+
+ free(text->search_string);
+ text->search_string = NULL;
+
+ if (text->search != NULL) {
+ search_destroy_context(text->search);
+ }
+ text->search = NULL;
+}
+
+
+/**
+ * Handle search.
+ *
+ * \param c content of type text
+ * \param gui_data front end private data
+ * \param flags search flags
+ * \param string search string
+ */
+static void textplain_search(struct content *c, void *gui_data,
+ search_flags_t flags, const char *string)
+{
+ textplain_content *text = (textplain_content *) c;
+
+ assert(c != NULL);
+
+ if (string != NULL && text->search_string != NULL &&
+ strcmp(string, text->search_string) == 0 &&
+ text->search != NULL) {
+ /* Continue prev. search */
+ search_step(text->search, flags, string);
+
+ } else if (string != NULL) {
+ /* New search */
+ free(text->search_string);
+ text->search_string = strdup(string);
+ if (text->search_string == NULL)
+ return;
+
+ if (text->search != NULL) {
+ search_destroy_context(text->search);
+ text->search = NULL;
+ }
+
+ text->search = search_create_context(c, CONTENT_TEXTPLAIN,
+ gui_data);
+
+ if (text->search == NULL)
+ return;
+
+ search_step(text->search, flags, string);
+
+ } else {
+ /* Clear search */
+ textplain_search_clear(c);
+
+ free(text->search_string);
+ text->search_string = NULL;
+ }
+}
+
+
+/**
+ * Redraw a text string with highlighting
+ * (for selection/search)
+ *
+ * \param utf8_text pointer to UTF-8 text string
+ * \param utf8_len length of string, in bytes
+ * \param offset byte offset within textual representation
+ * \param x x ordinate at which to plot text
+ * \param y y ordinate at which to plot text
+ * \param clip pointer to current clip rectangle
+ * \param height height of text string
+ * \param scale current display scale (1.0 = 100%)
+ * \param text Content being redrawn.
+ * \param sel Selection context
+ * \param search Search context
+ * \param ctx current redraw context
+ * \return true iff successful and redraw should proceed
+ */
+static bool
+text_draw(const char *utf8_text,
+ size_t utf8_len,
+ size_t offset,
+ int x,
+ int y,
+ const struct rect *clip,
+ int height,
+ float scale,
+ textplain_content *text,
+ const struct selection *sel,
+ struct search_context *search,
+ const struct redraw_context *ctx)
+{
+ bool highlighted = false;
+ plot_font_style_t plot_fstyle;
+ nserror res;
+
+ /* Need scaled text size to pass to plotters */
+ plot_fstyle = textplain_style;
+ plot_fstyle.size *= scale;
+
+ /* is this box part of a selection? */
+ if (ctx->interactive == true) {
+ unsigned len = utf8_len;
+ unsigned start_idx;
+ unsigned end_idx;
+
+ /* first try the browser window's current selection */
+ if (selection_defined(sel) &&
+ selection_highlighted(sel,
+ offset,
+ offset + len,
+ &start_idx,
+ &end_idx)) {
+ highlighted = true;
+ }
+
+ /* what about the current search operation, if any? */
+ if (!highlighted &&
+ (search != NULL) &&
+ search_term_highlighted((struct content *)text,
+ offset,
+ offset + len,
+ &start_idx,
+ &end_idx,
+ search)) {
+ highlighted = true;
+ }
+
+ /* \todo make search terms visible within selected text */
+ if (highlighted) {
+ struct rect r;
+ unsigned endtxt_idx = end_idx;
+ bool clip_changed = false;
+ bool text_visible = true;
+ int startx, endx;
+ plot_style_t pstyle_fill_hback = *plot_style_fill_white;
+ plot_font_style_t fstyle_hback = plot_fstyle;
+
+ if (end_idx > utf8_len) {
+ /* adjust for trailing space, not present in
+ * utf8_text
+ */
+ assert(end_idx == utf8_len + 1);
+ endtxt_idx = utf8_len;
+ }
+
+ res = guit->layout->width(&textplain_style,
+ utf8_text,
+ start_idx,
+ &startx);
+ if (res != NSERROR_OK) {
+ startx = 0;
+ }
+
+ res = guit->layout->width(&textplain_style,
+ utf8_text,
+ endtxt_idx,
+ &endx);
+ if (res != NSERROR_OK) {
+ endx = 0;
+ }
+
+ if (scale != 1.0) {
+ startx *= scale;
+ endx *= scale;
+ }
+
+ /* draw any text preceding highlighted portion */
+ if (start_idx > 0) {
+ res = ctx->plot->text(ctx,
+ &plot_fstyle,
+ x,
+ y + (int)(height * 0.75 * scale),
+ utf8_text,
+ start_idx);
+ if (res != NSERROR_OK) {
+ return false;
+ }
+ }
+
+ pstyle_fill_hback.fill_colour = textplain_style.foreground;
+
+ /* highlighted portion */
+ r.x0 = x + startx;
+ r.y0 = y;
+ r.x1 = x + endx;
+ r.y1 = y + height * scale;
+ res = ctx->plot->rectangle(ctx, &pstyle_fill_hback, &r);
+ if (res != NSERROR_OK) {
+ return false;
+ }
+
+ if (start_idx > 0) {
+ int px0 = max(x + startx, clip->x0);
+ int px1 = min(x + endx, clip->x1);
+
+ if (px0 < px1) {
+ r.x0 = px0;
+ r.y0 = clip->y0;
+ r.x1 = px1;
+ r.y1 = clip->y1;
+ res = ctx->plot->clip(ctx, &r);
+ if (res != NSERROR_OK) {
+ return false;
+ }
+
+ clip_changed = true;
+ } else {
+ text_visible = false;
+ }
+ }
+
+ fstyle_hback.background =
+ pstyle_fill_hback.fill_colour;
+ fstyle_hback.foreground = colour_to_bw_furthest(
+ pstyle_fill_hback.fill_colour);
+
+ if (text_visible &&
+ (ctx->plot->text(ctx,
+ &fstyle_hback,
+ x,
+ y + (int)(height * 0.75 * scale),
+ utf8_text,
+ endtxt_idx) != NSERROR_OK)) {
+ return false;
+ }
+
+ /* draw any text succeeding highlighted portion */
+ if (endtxt_idx < utf8_len) {
+ int px0 = max(x + endx, clip->x0);
+ if (px0 < clip->x1) {
+
+ r.x0 = px0;
+ r.y0 = clip->y0;
+ r.x1 = clip->x1;
+ r.y1 = clip->y1;
+ res = ctx->plot->clip(ctx, &r);
+ if (res != NSERROR_OK) {
+ return false;
+ }
+
+ clip_changed = true;
+
+ res = ctx->plot->text(ctx,
+ &plot_fstyle,
+ x,
+ y + (int)(height * 0.75 * scale),
+ utf8_text,
+ utf8_len);
+ if (res != NSERROR_OK) {
+ return false;
+ }
+ }
+ }
+
+ if (clip_changed &&
+ (ctx->plot->clip(ctx, clip) != NSERROR_OK)) {
+ return false;
+ }
+ }
+ }
+
+ if (!highlighted) {
+ res = ctx->plot->text(ctx,
+ &plot_fstyle,
+ x,
+ y + (int) (height * 0.75 * scale),
+ utf8_text,
+ utf8_len);
+ if (res != NSERROR_OK) {
+ return false;
+ }
+ }
+ return true;
+}
+
+
+/**
+ * Draw a CONTENT_TEXTPLAIN using the current set of plotters (plot).
+ *
+ * x, y, clip_[xy][01] are in target coordinates.
+ *
+ * \param c content of type CONTENT_TEXTPLAIN
+ * \param data redraw data for this content redraw
+ * \param clip current clip region
+ * \param ctx current redraw context
+ * \return true if successful, false otherwise
+ */
+static bool
+textplain_redraw(struct content *c,
+ struct content_redraw_data *data,
+ const struct rect *clip,
+ const struct redraw_context *ctx)
+{
+ textplain_content *text = (textplain_content *) c;
+ struct browser_window *bw = text->bw;
+ char *utf8_data = text->utf8_data;
+ long lineno;
+ int x = data->x;
+ int y = data->y;
+ unsigned long line_count = text->physical_line_count;
+ float line_height = textplain_line_height();
+ float scaled_line_height = line_height * data->scale;
+ long line0 = (clip->y0 - y * data->scale) / scaled_line_height - 1;
+ long line1 = (clip->y1 - y * data->scale) / scaled_line_height + 1;
+ struct textplain_line *line = text->physical_line;
+ size_t length;
+ plot_style_t *plot_style_highlight;
+ nserror res;
+
+ if (line0 < 0)
+ line0 = 0;
+ if (line1 < 0)
+ line1 = 0;
+ if (line_count < (unsigned long) line0)
+ line0 = line_count;
+ if (line_count < (unsigned long) line1)
+ line1 = line_count;
+ if (line1 < line0)
+ line1 = line0;
+
+ res = ctx->plot->rectangle(ctx, plot_style_fill_white, clip);
+ if (res != NSERROR_OK) {
+ return false;
+ }
+
+ if (!line)
+ return true;
+
+ /* choose a suitable background colour for any highlighted text */
+ if ((data->background_colour & 0x808080) == 0x808080)
+ plot_style_highlight = plot_style_fill_black;
+ else
+ plot_style_highlight = plot_style_fill_white;
+
+ /* Set up font plot style */
+ textplain_style.background = data->background_colour;
+
+ x = (x + MARGIN) * data->scale;
+ y = (y + MARGIN) * data->scale;
+ for (lineno = line0; lineno != line1; lineno++) {
+ const char *text_d = utf8_data + line[lineno].start;
+ int tab_width = textplain_tab_width * data->scale;
+ size_t offset = 0;
+ int tx = x;
+
+ if (!tab_width) tab_width = 1;
+
+ length = line[lineno].length;
+ if (!length)
+ continue;
+
+ while (offset < length) {
+ size_t next_offset = offset;
+ int width;
+ int ntx;
+ nserror res;
+
+ while ((next_offset < length) &&
+ (text_d[next_offset] != '\t')) {
+ next_offset = utf8_next(text_d,
+ length,
+ next_offset);
+ }
+
+ if (!text_draw(text_d + offset,
+ next_offset - offset,
+ line[lineno].start + offset,
+ tx,
+ y + (lineno * scaled_line_height),
+ clip,
+ line_height,
+ data->scale,
+ text,
+ &text->sel,
+ text->search,
+ ctx)) {
+ return false;
+ }
+
+ if (next_offset >= length)
+ break;
+
+ res = guit->layout->width(&textplain_style,
+ &text_d[offset],
+ next_offset - offset,
+ &width);
+ /* locate end of string and align to next tab position */
+ if (res == NSERROR_OK) {
+ tx += (int)(width * data->scale);
+ }
+
+ ntx = x + ((1 + (tx - x) / tab_width) * tab_width);
+
+ /* if the tab character lies within the
+ * selection, if any, then we must draw it as
+ * a filled rectangle so that it's consistent
+ * with background of the selected text
+ */
+
+ if (bw) {
+ unsigned tab_ofst = line[lineno].start + next_offset;
+ struct selection *sel = &text->sel;
+ bool highlighted = false;
+
+ if (selection_defined(sel)) {
+ unsigned start_idx, end_idx;
+ if (selection_highlighted(sel,
+ tab_ofst,
+ tab_ofst + 1,
+ &start_idx,
+ &end_idx))
+ highlighted = true;
+ }
+
+ if (!highlighted && (text->search != NULL)) {
+ unsigned start_idx, end_idx;
+ if (search_term_highlighted(c,
+ tab_ofst,
+ tab_ofst + 1,
+ &start_idx,
+ &end_idx,
+ text->search))
+ highlighted = true;
+ }
+
+ if (highlighted) {
+ struct rect rect;
+ rect.x0 = tx;
+ rect.y0 = y + (lineno * scaled_line_height);
+ rect.x1 = ntx;
+ rect.y1 = rect.y0 + scaled_line_height;
+ res = ctx->plot->rectangle(ctx,
+ plot_style_highlight,
+ &rect);
+ if (res != NSERROR_OK) {
+ return false;
+ }
+ }
+ }
+
+ offset = next_offset + 1;
+ tx = ntx;
+ }
+ }
+
+ return true;
+}
+
+
+/**
+ * Handle a window containing a CONTENT_TEXTPLAIN being opened.
+ */
+static void
+textplain_open(struct content *c,
+ struct browser_window *bw,
+ struct content *page,
+ struct object_params *params)
+{
+ textplain_content *text = (textplain_content *) c;
+
+ text->bw = bw;
+
+ /* text selection */
+ selection_init(&text->sel, NULL, NULL);
+}
+
+
+/**
+ * Handle a window containing a CONTENT_TEXTPLAIN being closed.
+ */
+static void textplain_close(struct content *c)
+{
+ textplain_content *text = (textplain_content *) c;
+
+ if (text->search != NULL) {
+ search_destroy_context(text->search);
+ }
+
+ text->bw = NULL;
+}
+
+
+/**
+ * Return an textplain content's selection context
+ */
+static char *textplain_get_selection(struct content *c)
+{
+ textplain_content *text = (textplain_content *) c;
+
+ return selection_get_copy(&text->sel);
+}
+
+
+/**
+ * Convert a character offset within a line of text into the
+ * horizontal co-ordinate
+ *
+ * The conversion takes into account the font being used and any tabs
+ * in the text
+ *
+ * \param text line of text
+ * \param offset char offset within text
+ * \param length line length
+ * \return x ordinate
+ */
+static int
+textplain_coord_from_offset(const char *text, size_t offset, size_t length)
+{
+ int x = 0;
+
+ while (offset > 0) {
+ size_t next_offset = 0;
+ int tx;
+
+ while (next_offset < offset && text[next_offset] != '\t') {
+ next_offset = utf8_next(text, length, next_offset);
+ }
+
+ guit->layout->width(&textplain_style, text, next_offset, &tx);
+
+ x += tx;
+
+ if (next_offset >= offset)
+ break;
+
+ /* align to next tab boundary */
+ next_offset++;
+ x = (1 + (x / textplain_tab_width)) * textplain_tab_width;
+ offset -= next_offset;
+ text += next_offset;
+ length -= next_offset;
+ }
+
+ return x;
+}
+
+
+/**
+ * plain text content handler table
+ */
+static const content_handler textplain_content_handler = {
+ .fini = textplain_fini,
+ .create = textplain_create,
+ .process_data = textplain_process_data,
+ .data_complete = textplain_convert,
+ .reformat = textplain_reformat,
+ .destroy = textplain_destroy,
+ .mouse_track = textplain_mouse_track,
+ .mouse_action = textplain_mouse_action,
+ .keypress = textplain_keypress,
+ .search = textplain_search,
+ .search_clear = textplain_search_clear,
+ .redraw = textplain_redraw,
+ .open = textplain_open,
+ .close = textplain_close,
+ .get_selection = textplain_get_selection,
+ .clone = textplain_clone,
+ .type = textplain_content_type,
+ .no_share = true,
+};
+
+
+/* exported interface documented in html/textplain.h */
+nserror textplain_init(void)
+{
+ lwc_error lerror;
+ nserror error;
+
+ lerror = lwc_intern_string("Windows-1252",
+ SLEN("Windows-1252"),
+ &textplain_default_charset);
+ if (lerror != lwc_error_ok) {
+ return NSERROR_NOMEM;
+ }
+
+ error = content_factory_register_handler("text/plain",
+ &textplain_content_handler);
+ if (error != NSERROR_OK) {
+ lwc_string_unref(textplain_default_charset);
+ }
+
+ return error;
+}
+
+
+/* exported interface documented in html/textplain.h */
+unsigned long textplain_line_count(struct content *c)
+{
+ textplain_content *text = (textplain_content *) c;
+
+ assert(c != NULL);
+
+ return text->physical_line_count;
+}
+
+
+/* exported interface documented in html/textplain.h */
+size_t textplain_size(struct content *c)
+{
+ textplain_content *text = (textplain_content *) c;
+
+ assert(c != NULL);
+
+ return text->utf8_data_size;
+}
+
+
+/* exported interface documented in html/textplain.h */
+size_t textplain_offset_from_coords(struct content *c, int x, int y, int dir)
+{
+ textplain_content *textc = (textplain_content *) c;
+ float line_height = textplain_line_height();
+ struct textplain_line *line;
+ const char *text;
+ unsigned nlines;
+ size_t length;
+ int idx;
+
+ assert(c != NULL);
+
+ y = (int)((float)(y - MARGIN) / line_height);
+ x -= MARGIN;
+
+ nlines = textc->physical_line_count;
+ if (!nlines)
+ return 0;
+
+ if (y <= 0) y = 0;
+ else if ((unsigned)y >= nlines)
+ y = nlines - 1;
+
+ line = &textc->physical_line[y];
+ text = textc->utf8_data + line->start;
+ length = line->length;
+ idx = 0;
+
+ while (x > 0) {
+ size_t next_offset = 0;
+ int width = INT_MAX;
+
+ while (next_offset < length && text[next_offset] != '\t') {
+ next_offset = utf8_next(text, length, next_offset);
+ }
+
+ if (next_offset < length) {
+ guit->layout->width(&textplain_style,
+ text,
+ next_offset,
+ &width);
+ }
+
+ if (x <= width) {
+ int pixel_offset;
+ size_t char_offset;
+
+ guit->layout->position(&textplain_style,
+ text, next_offset, x,
+ &char_offset, &pixel_offset);
+
+ idx += char_offset;
+ break;
+ }
+
+ x -= width;
+ length -= next_offset;
+ text += next_offset;
+ idx += next_offset;
+
+ /* check if it's within the tab */
+ width = textplain_tab_width - (width % textplain_tab_width);
+ if (x <= width) break;
+
+ x -= width;
+ length--;
+ text++;
+ idx++;
+ }
+
+ return line->start + idx;
+}
+
+
+/* exported interface documented in html/textplain.h */
+void
+textplain_coords_from_range(struct content *c,
+ unsigned start,
+ unsigned end,
+ struct rect *r)
+{
+ textplain_content *text = (textplain_content *) c;
+ float line_height = textplain_line_height();
+ char *utf8_data;
+ struct textplain_line *line;
+ unsigned lineno = 0;
+ unsigned nlines;
+
+ assert(c != NULL);
+ assert(start <= end);
+ assert(end <= text->utf8_data_size);
+
+ utf8_data = text->utf8_data;
+ nlines = text->physical_line_count;
+ line = text->physical_line;
+
+ /* find start */
+ lineno = textplain_find_line(c, start);
+
+ r->y0 = (int)(MARGIN + lineno * line_height);
+
+ if (lineno + 1 <= nlines || line[lineno + 1].start >= end) {
+ /* \todo - it may actually be more efficient just to
+ * run forwards most of the time
+ */
+
+ /* find end */
+ lineno = textplain_find_line(c, end);
+
+ r->x0 = 0;
+ r->x1 = text->formatted_width;
+ } else {
+ /* single line */
+ const char *text = utf8_data + line[lineno].start;
+
+ r->x0 = textplain_coord_from_offset(text,
+ start - line[lineno].start,
+ line[lineno].length);
+
+ r->x1 = textplain_coord_from_offset(text,
+ end - line[lineno].start,
+ line[lineno].length);
+ }
+
+ r->y1 = (int)(MARGIN + (lineno + 1) * line_height);
+}
+
+
+/* exported interface documented in html/textplain.h */
+char *
+textplain_get_line(struct content *c,
+ unsigned lineno,
+ size_t *poffset,
+ size_t *plen)
+{
+ textplain_content *text = (textplain_content *) c;
+ struct textplain_line *line;
+
+ assert(c != NULL);
+
+ if (lineno >= text->physical_line_count)
+ return NULL;
+ line = &text->physical_line[lineno];
+
+ *poffset = line->start;
+ *plen = line->length;
+ return text->utf8_data + line->start;
+}
+
+
+/* exported interface documented in html/textplain.h */
+int textplain_find_line(struct content *c, unsigned offset)
+{
+ textplain_content *text = (textplain_content *) c;
+ struct textplain_line *line;
+ int nlines;
+ int lineno = 0;
+
+ assert(c != NULL);
+
+ line = text->physical_line;
+ nlines = text->physical_line_count;
+
+ if (offset > text->utf8_data_size) {
+ return -1;
+ }
+
+/* \todo - implement binary search here */
+ while (lineno < nlines && line[lineno].start < offset) {
+ lineno++;
+ }
+ if (line[lineno].start > offset) {
+ lineno--;
+ }
+
+ return lineno;
+}
+
+
+/* exported interface documented in html/textplain.h */
+char *
+textplain_get_raw_data(struct content *c,
+ unsigned start,
+ unsigned end,
+ size_t *plen)
+{
+ textplain_content *text = (textplain_content *) c;
+ size_t utf8_size;
+
+ assert(c != NULL);
+
+ utf8_size = text->utf8_data_size;
+
+ /* any text at all? */
+ if (!utf8_size) return NULL;
+
+ /* clamp to valid offset range */
+ if (start >= utf8_size) start = utf8_size;
+ if (end >= utf8_size) end = utf8_size;
+
+ *plen = end - start;
+
+ return text->utf8_data + start;
+}
+
+
+/* exported interface documented in html/textplain.h */
+struct browser_window *textplain_get_browser_window(struct content *c)
+{
+ textplain_content *text = (textplain_content *) c;
+
+ assert(c != NULL);
+ assert(c->handler == &textplain_content_handler);
+
+ return text->bw;
+}
diff --git a/content/handlers/text/textplain.h b/content/handlers/text/textplain.h
new file mode 100644
index 0000000..23917fb
--- /dev/null
+++ b/content/handlers/text/textplain.h
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2006 James Bursa <bursa(a)users.sourceforge.net>
+ * Copyright 2006 Adrian Lees <adrianl(a)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
+ *
+ * Interface to content handler for plain text.
+ */
+
+#ifndef NETSURF_HTML_TEXTPLAIN_H
+#define NETSURF_HTML_TEXTPLAIN_H
+
+#include <stddef.h>
+#include "netsurf/mouse.h"
+
+struct content;
+struct hlcache_handle;
+struct http_parameter;
+struct rect;
+
+/**
+ * Initialise the text content handler
+ *
+ * \return NSERROR_OK on success else appropriate error code.
+ */
+nserror textplain_init(void);
+
+
+/**
+ * Retrieve number of lines in content
+ *
+ * \param[in] c Content to retrieve line count from
+ * \return Number of lines
+ */
+unsigned long textplain_line_count(struct content *c);
+
+
+/**
+ * Retrieve the size (in bytes) of text data
+ *
+ * \param[in] c Content to retrieve size of
+ * \return Size, in bytes, of data
+ */
+size_t textplain_size(struct content *c);
+
+
+/**
+ * Return byte offset within UTF8 textplain content.
+ *
+ * given the co-ordinates of a point within a textplain content. 'dir'
+ * specifies the direction in which to search (-1 = above-left, +1 =
+ * below-right) if the co-ordinates are not contained within a line.
+ *
+ * \param[in] c content of type CONTENT_TEXTPLAIN
+ * \param[in] x x ordinate of point
+ * \param[in] y y ordinate of point
+ * \param[in] dir direction of search if not within line
+ * \return byte offset of character containing (or nearest to) point
+ */
+size_t textplain_offset_from_coords(struct content *c, int x, int y, int dir);
+
+
+/**
+ * Given a range of byte offsets within a UTF8 textplain content,
+ * return a box that fully encloses the text
+ *
+ * \param[in] c content of type CONTENT_TEXTPLAIN
+ * \param[in] start byte offset of start of text range
+ * \param[in] end byte offset of end
+ * \param[out] r rectangle to be completed
+ */
+void textplain_coords_from_range(struct content *c,
+ unsigned start, unsigned end, struct rect *r);
+
+/**
+ * Return a pointer to the requested line of text.
+ *
+ * \param[in] c content of type CONTENT_TEXTPLAIN
+ * \param[in] lineno line number
+ * \param[out] poffset receives byte offset of line start within text
+ * \param[out] plen receives length of returned line
+ * \return pointer to text, or NULL if invalid line number
+ */
+char *textplain_get_line(struct content *c, unsigned lineno,
+ size_t *poffset, size_t *plen);
+
+
+/**
+ * Find line number of byte in text
+ *
+ * Given a byte offset within the text, return the line number
+ * of the line containing that offset.
+ *
+ * \param[in] c content of type CONTENT_TEXTPLAIN
+ * \param[in] offset byte offset within textual representation
+ * \return line number, or -1 if offset invalid (larger than size)
+ */
+int textplain_find_line(struct content *c, unsigned offset);
+
+
+/**
+ * Return a pointer to the raw UTF-8 data, as opposed to the reformatted
+ * text to fit the window width. Thus only hard newlines are preserved
+ * in the saved/copied text of a selection.
+ *
+ * \param[in] c content of type CONTENT_TEXTPLAIN
+ * \param[in] start starting byte offset within UTF-8 text
+ * \param[in] end ending byte offset
+ * \param[out] plen receives validated length
+ * \return pointer to text, or NULL if no text
+ */
+char *textplain_get_raw_data(struct content *c, unsigned start, unsigned end, size_t *plen);
+
+
+/**
+ * Get the browser window containing a textplain content
+ *
+ * \param[in] c text/plain content
+ * \return the browser window
+ */
+struct browser_window *textplain_get_browser_window(struct content *c);
+
+#endif
diff --git a/desktop/browser.c b/desktop/browser.c
index 88d78ba..5890162 100644
--- a/desktop/browser.c
+++ b/desktop/browser.c
@@ -56,9 +56,9 @@
#include "content/hlcache.h"
#include "content/urldb.h"
#include "css/utils.h"
-#include "render/form_internal.h"
-#include "render/html.h"
-#include "render/box.h"
+#include "html/form_internal.h"
+#include "html/html.h"
+#include "html/box.h"
#include "javascript/js.h"
#include "desktop/browser_history.h"
diff --git a/desktop/frames.c b/desktop/frames.c
index e222876..ebc54c6 100644
--- a/desktop/frames.c
+++ b/desktop/frames.c
@@ -33,8 +33,8 @@
#include "utils/utils.h"
#include "netsurf/content.h"
#include "content/hlcache.h"
-#include "render/html.h"
-#include "render/box.h"
+#include "html/html.h"
+#include "html/box.h"
#include "desktop/browser_private.h"
#include "desktop/frames.h"
diff --git a/desktop/netsurf.c b/desktop/netsurf.c
index 8aa949a..76ff4b1 100644
--- a/desktop/netsurf.c
+++ b/desktop/netsurf.c
@@ -41,8 +41,8 @@
#include "image/image.h"
#include "image/image_cache.h"
#include "javascript/js.h"
-#include "render/html.h"
-#include "render/textplain.h"
+#include "html/html.h"
+#include "text/textplain.h"
#include "netsurf/browser_window.h"
#include "desktop/system_colour.h"
diff --git a/desktop/print.c b/desktop/print.c
index 5c0333a..de579dc 100644
--- a/desktop/print.c
+++ b/desktop/print.c
@@ -34,7 +34,7 @@
#include "netsurf/plotters.h"
#include "content/hlcache.h"
#include "css/utils.h"
-#include "render/box.h"
+#include "html/box.h"
#include "desktop/print.h"
#include "desktop/printer.h"
diff --git a/desktop/save_complete.c b/desktop/save_complete.c
index 9a88ad1..cd4ab30 100644
--- a/desktop/save_complete.c
+++ b/desktop/save_complete.c
@@ -43,8 +43,8 @@
#include "netsurf/content.h"
#include "content/hlcache.h"
#include "css/css.h"
-#include "render/box.h"
-#include "render/html.h"
+#include "html/box.h"
+#include "html/html.h"
#include "netsurf/misc.h"
#include "desktop/gui_internal.h"
diff --git a/desktop/save_text.c b/desktop/save_text.c
index 791ae92..c4abb16 100644
--- a/desktop/save_text.c
+++ b/desktop/save_text.c
@@ -32,8 +32,8 @@
#include "utils/utf8.h"
#include "utils/utils.h"
#include "netsurf/content.h"
-#include "render/box.h"
-#include "render/html.h"
+#include "html/box.h"
+#include "html/html.h"
#include "netsurf/utf8.h"
#include "desktop/gui_internal.h"
diff --git a/desktop/selection.c b/desktop/selection.c
index 5cb43b8..35eabb2 100644
--- a/desktop/selection.c
+++ b/desktop/selection.c
@@ -32,10 +32,10 @@
#include "utils/utf8.h"
#include "utils/utils.h"
#include "netsurf/form.h"
-#include "render/box.h"
-#include "render/html_internal.h"
-#include "render/font.h"
-#include "render/textplain.h"
+#include "html/box.h"
+#include "html/html_internal.h"
+#include "html/font.h"
+#include "text/textplain.h"
#include "netsurf/mouse.h"
#include "desktop/browser_private.h"
diff --git a/desktop/textinput.c b/desktop/textinput.c
index c0e0ba8..7fc95f7 100644
--- a/desktop/textinput.c
+++ b/desktop/textinput.c
@@ -37,9 +37,9 @@
#include "netsurf/form.h"
#include "netsurf/window.h"
#include "netsurf/keypress.h"
-#include "render/box.h"
-#include "render/html_internal.h"
-#include "render/layout.h"
+#include "html/box.h"
+#include "html/html_internal.h"
+#include "html/layout.h"
#include "desktop/browser_private.h"
#include "desktop/textinput.h"
diff --git a/frontends/amiga/dt_sound.c b/frontends/amiga/dt_sound.c
index e0f48da..ae313bb 100644
--- a/frontends/amiga/dt_sound.c
+++ b/frontends/amiga/dt_sound.c
@@ -33,7 +33,7 @@
#include "utils/messages.h"
#include "netsurf/plotters.h"
#include "netsurf/content.h"
-#include "render/box.h"
+#include "html/box.h"
#include "content/llcache.h"
#include "content/content_protected.h"
diff --git a/render/Makefile b/render/Makefile
deleted file mode 100644
index dc2e31c..0000000
--- a/render/Makefile
+++ /dev/null
@@ -1,10 +0,0 @@
-# Render sources
-
-S_RENDER := box.c box_construct.c box_normalise.c box_textarea.c \
- font.c form.c imagemap.c layout.c search.c table.c textplain.c \
- html.c html_css.c html_css_fetcher.c html_script.c \
- html_interaction.c html_redraw.c html_redraw_border.c \
- html_forms.c html_object.c
-
-
-S_RENDER := $(addprefix render/,$(S_RENDER))
diff --git a/render/box.c b/render/box.c
deleted file mode 100644
index c97e898..0000000
--- a/render/box.c
+++ /dev/null
@@ -1,1242 +0,0 @@
-/*
- * Copyright 2005-2007 James Bursa <bursa(a)users.sourceforge.net>
- * Copyright 2003 Phil Mellor <monkeyson(a)users.sourceforge.net>
- * Copyright 2005 John M Bell <jmb202(a)ecs.soton.ac.uk>
- * Copyright 2008 Michael Drake <tlsa(a)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
- * implementation of box tree manipulation.
- */
-
-#include <assert.h>
-#include <stdbool.h>
-#include <stdio.h>
-#include <string.h>
-#include <dom/dom.h>
-
-#include "utils/nsoption.h"
-#include "utils/log.h"
-#include "utils/talloc.h"
-#include "netsurf/misc.h"
-#include "netsurf/content.h"
-#include "netsurf/mouse.h"
-#include "css/utils.h"
-#include "css/dump.h"
-#include "desktop/scrollbar.h"
-#include "desktop/gui_internal.h"
-
-#include "render/box.h"
-#include "render/form_internal.h"
-#include "render/html_internal.h"
-
-#define box_is_float(box) (box->type == BOX_FLOAT_LEFT || \
- box->type == BOX_FLOAT_RIGHT)
-
-/**
- * Destructor for box nodes which own styles
- *
- * \param b The box being destroyed.
- * \return 0 to allow talloc to continue destroying the tree.
- */
-static int box_talloc_destructor(struct box *b)
-{
- struct html_scrollbar_data *data;
-
- if ((b->flags & STYLE_OWNED) && b->style != NULL) {
- css_computed_style_destroy(b->style);
- b->style = NULL;
- }
-
- if (b->styles != NULL) {
- css_select_results_destroy(b->styles);
- b->styles = NULL;
- }
-
- if (b->href != NULL)
- nsurl_unref(b->href);
-
- if (b->id != NULL) {
- lwc_string_unref(b->id);
- }
-
- if (b->node != NULL) {
- dom_node_unref(b->node);
- }
-
- if (b->scroll_x != NULL) {
- data = scrollbar_get_data(b->scroll_x);
- scrollbar_destroy(b->scroll_x);
- free(data);
- }
-
- if (b->scroll_y != NULL) {
- data = scrollbar_get_data(b->scroll_y);
- scrollbar_destroy(b->scroll_y);
- free(data);
- }
-
- return 0;
-}
-
-/**
- * Create a box tree node.
- *
- * \param styles selection results for the box, or NULL
- * \param style computed style for the box (not copied), or 0
- * \param style_owned whether style is owned by this box
- * \param href href for the box (copied), or 0
- * \param target target for the box (not copied), or 0
- * \param title title for the box (not copied), or 0
- * \param id id for the box (not copied), or 0
- * \param context context for allocations
- * \return allocated and initialised box, or 0 on memory exhaustion
- *
- * styles is always owned by the box, if it is set.
- * style is only owned by the box in the case of implied boxes.
- */
-
-struct box * box_create(css_select_results *styles, css_computed_style *style,
- bool style_owned, nsurl *href, const char *target,
- const char *title, lwc_string *id, void *context)
-{
- unsigned int i;
- struct box *box;
-
- box = talloc(context, struct box);
- if (!box) {
- return 0;
- }
-
- talloc_set_destructor(box, box_talloc_destructor);
-
- box->type = BOX_INLINE;
- box->flags = 0;
- box->flags = style_owned ? (box->flags | STYLE_OWNED) : box->flags;
- box->styles = styles;
- box->style = style;
- box->x = box->y = 0;
- box->width = UNKNOWN_WIDTH;
- box->height = 0;
- box->descendant_x0 = box->descendant_y0 = 0;
- box->descendant_x1 = box->descendant_y1 = 0;
- for (i = 0; i != 4; i++)
- box->margin[i] = box->padding[i] = box->border[i].width = 0;
- box->scroll_x = box->scroll_y = NULL;
- box->min_width = 0;
- box->max_width = UNKNOWN_MAX_WIDTH;
- box->byte_offset = 0;
- box->text = NULL;
- box->length = 0;
- box->space = 0;
- box->href = (href == NULL) ? NULL : nsurl_ref(href);
- box->target = target;
- box->title = title;
- box->columns = 1;
- box->rows = 1;
- box->start_column = 0;
- box->next = NULL;
- box->prev = NULL;
- box->children = NULL;
- box->last = NULL;
- box->parent = NULL;
- box->inline_end = NULL;
- box->float_children = NULL;
- box->float_container = NULL;
- box->next_float = NULL;
- box->cached_place_below_level = 0;
- box->list_marker = NULL;
- box->col = NULL;
- box->gadget = NULL;
- box->usemap = NULL;
- box->id = id;
- box->background = NULL;
- box->object = NULL;
- box->object_params = NULL;
- box->iframe = NULL;
- box->node = NULL;
-
- return box;
-}
-
-/**
- * Add a child to a box tree node.
- *
- * \param parent box giving birth
- * \param child box to link as last child of parent
- */
-
-void box_add_child(struct box *parent, struct box *child)
-{
- assert(parent);
- assert(child);
-
- if (parent->children != 0) { /* has children already */
- parent->last->next = child;
- child->prev = parent->last;
- } else { /* this is the first child */
- parent->children = child;
- child->prev = 0;
- }
-
- parent->last = child;
- child->parent = parent;
-}
-
-
-/**
- * Insert a new box as a sibling to a box in a tree.
- *
- * \param box box already in tree
- * \param new_box box to link into tree as next sibling
- */
-
-void box_insert_sibling(struct box *box, struct box *new_box)
-{
- new_box->parent = box->parent;
- new_box->prev = box;
- new_box->next = box->next;
- box->next = new_box;
- if (new_box->next)
- new_box->next->prev = new_box;
- else if (new_box->parent)
- new_box->parent->last = new_box;
-}
-
-
-/**
- * Unlink a box from the box tree and then free it recursively.
- *
- * \param box box to unlink and free recursively.
- */
-
-void box_unlink_and_free(struct box *box)
-{
- struct box *parent = box->parent;
- struct box *next = box->next;
- struct box *prev = box->prev;
-
- if (parent) {
- if (parent->children == box)
- parent->children = next;
- if (parent->last == box)
- parent->last = next ? next : prev;
- }
-
- if (prev)
- prev->next = next;
- if (next)
- next->prev = prev;
-
- box_free(box);
-}
-
-
-/**
- * Free a box tree recursively.
- *
- * \param box box to free recursively
- *
- * The box and all its children is freed.
- */
-
-void box_free(struct box *box)
-{
- struct box *child, *next;
-
- /* free children first */
- for (child = box->children; child; child = next) {
- next = child->next;
- box_free(child);
- }
-
- /* last this box */
- box_free_box(box);
-}
-
-
-/**
- * Free the data in a single box structure.
- *
- * \param box box to free
- */
-
-void box_free_box(struct box *box)
-{
- if (!(box->flags & CLONE)) {
- if (box->gadget)
- form_free_control(box->gadget);
- if (box->scroll_x != NULL)
- scrollbar_destroy(box->scroll_x);
- if (box->scroll_y != NULL)
- scrollbar_destroy(box->scroll_y);
- if (box->styles != NULL)
- css_select_results_destroy(box->styles);
- }
-
- talloc_free(box);
-}
-
-
-/**
- * Find the absolute coordinates of a box.
- *
- * \param box the box to calculate coordinates of
- * \param x updated to x coordinate
- * \param y updated to y coordinate
- */
-
-void box_coords(struct box *box, int *x, int *y)
-{
- *x = box->x;
- *y = box->y;
- while (box->parent) {
- if (box_is_float(box)) {
- do {
- box = box->parent;
- } while (!box->float_children);
- } else
- box = box->parent;
- *x += box->x - scrollbar_get_offset(box->scroll_x);
- *y += box->y - scrollbar_get_offset(box->scroll_y);
- }
-}
-
-
-/**
- * Find the bounds of a box.
- *
- * \param box the box to calculate bounds of
- * \param r receives bounds
- */
-
-void box_bounds(struct box *box, struct rect *r)
-{
- int width, height;
-
- box_coords(box, &r->x0, &r->y0);
-
- width = box->padding[LEFT] + box->width + box->padding[RIGHT];
- height = box->padding[TOP] + box->height + box->padding[BOTTOM];
-
- r->x1 = r->x0 + width;
- r->y1 = r->y0 + height;
-}
-
-
-/**
- * Determine if a point lies within a box.
- *
- * \param[in] len_ctx CSS length conversion context to use.
- * \param[in] box Box to consider
- * \param[in] x Coordinate relative to box
- * \param[in] y Coordinate relative to box
- * \param[out] physically If function returning true, physically is set true
- * iff point is within the box's physical dimensions and
- * false if the point is not within the box's physical
- * dimensions but is in the area defined by the box's
- * descendants. If function returns false, physically
- * is undefined.
- * \return true if the point is within the box or a descendant box
- *
- * This is a helper function for box_at_point().
- */
-
-static bool box_contains_point(
- const nscss_len_ctx *len_ctx,
- const struct box *box,
- int x,
- int y,
- bool *physically)
-{
- css_computed_clip_rect css_rect;
-
- if (box->style != NULL &&
- css_computed_position(box->style) ==
- CSS_POSITION_ABSOLUTE &&
- css_computed_clip(box->style, &css_rect) ==
- CSS_CLIP_RECT) {
- /* We have an absolutly positioned box with a clip rect */
- struct rect r = {
- .x0 = box->border[LEFT].width,
- .y0 = box->border[TOP].width,
- .x1 = box->padding[LEFT] + box->width +
- box->border[RIGHT].width +
- box->padding[RIGHT],
- .y1 = box->padding[TOP] + box->height +
- box->border[BOTTOM].width +
- box->padding[BOTTOM]
- };
- if (x >= r.x0 && x < r.x1 && y >= r.y0 && y < r.y1)
- *physically = true;
- else
- *physically = false;
-
- /* Adjust rect to css clip region */
- if (css_rect.left_auto == false) {
- r.x0 += FIXTOINT(nscss_len2px(len_ctx,
- css_rect.left, css_rect.lunit,
- box->style));
- }
- if (css_rect.top_auto == false) {
- r.y0 += FIXTOINT(nscss_len2px(len_ctx,
- css_rect.top, css_rect.tunit,
- box->style));
- }
- if (css_rect.right_auto == false) {
- r.x1 = box->border[LEFT].width +
- FIXTOINT(nscss_len2px(len_ctx,
- css_rect.right,
- css_rect.runit,
- box->style));
- }
- if (css_rect.bottom_auto == false) {
- r.y1 = box->border[TOP].width +
- FIXTOINT(nscss_len2px(len_ctx,
- css_rect.bottom,
- css_rect.bunit,
- box->style));
- }
-
- /* Test if point is in clipped box */
- if (x >= r.x0 && x < r.x1 && y >= r.y0 && y < r.y1) {
- /* inside clip area */
- return true;
- }
-
- /* Not inside clip area */
- return false;
- }
- if (x >= -box->border[LEFT].width &&
- x < box->padding[LEFT] + box->width +
- box->padding[RIGHT] + box->border[RIGHT].width &&
- y >= -box->border[TOP].width &&
- y < box->padding[TOP] + box->height +
- box->padding[BOTTOM] + box->border[BOTTOM].width) {
- *physically = true;
- return true;
- }
- if (box->list_marker && box->list_marker->x - box->x <= x +
- box->list_marker->border[LEFT].width &&
- x < box->list_marker->x - box->x +
- box->list_marker->padding[LEFT] +
- box->list_marker->width +
- box->list_marker->border[RIGHT].width +
- box->list_marker->padding[RIGHT] &&
- box->list_marker->y - box->y <= y +
- box->list_marker->border[TOP].width &&
- y < box->list_marker->y - box->y +
- box->list_marker->padding[TOP] +
- box->list_marker->height +
- box->list_marker->border[BOTTOM].width +
- box->list_marker->padding[BOTTOM]) {
- *physically = true;
- return true;
- }
- if ((box->style && css_computed_overflow_x(box->style) ==
- CSS_OVERFLOW_VISIBLE) || !box->style) {
- if (box->descendant_x0 <= x &&
- x < box->descendant_x1) {
- *physically = false;
- return true;
- }
- }
- if ((box->style && css_computed_overflow_y(box->style) ==
- CSS_OVERFLOW_VISIBLE) || !box->style) {
- if (box->descendant_y0 <= y &&
- y < box->descendant_y1) {
- *physically = false;
- return true;
- }
- }
- return false;
-}
-
-
-/** Direction to move in a box-tree walk */
-enum box_walk_dir {
- BOX_WALK_CHILDREN,
- BOX_WALK_PARENT,
- BOX_WALK_NEXT_SIBLING,
- BOX_WALK_FLOAT_CHILDREN,
- BOX_WALK_NEXT_FLOAT_SIBLING,
- BOX_WALK_FLOAT_CONTAINER
-};
-
-
-/**
- * Move from box to next box in given direction, adjusting for box coord change
- *
- * \param b box to move from from
- * \param dir direction to move in
- * \param x box's global x-coord, updated to position of next box
- * \param y box's global y-coord, updated to position of next box
- *
- * If no box can be found in given direction, NULL is returned.
- */
-static inline struct box *box_move_xy(struct box *b, enum box_walk_dir dir,
- int *x, int *y)
-{
- struct box *rb = NULL;
-
- switch (dir) {
- case BOX_WALK_CHILDREN:
- b = b->children;
- if (b == NULL)
- break;
- *x += b->x;
- *y += b->y;
- if (!box_is_float(b)) {
- rb = b;
- break;
- }
- /* Fall through */
-
- case BOX_WALK_NEXT_SIBLING:
- do {
- *x -= b->x;
- *y -= b->y;
- b = b->next;
- if (b == NULL)
- break;
- *x += b->x;
- *y += b->y;
- } while (box_is_float(b));
- rb = b;
- break;
-
- case BOX_WALK_PARENT:
- *x -= b->x;
- *y -= b->y;
- rb = b->parent;
- break;
-
- case BOX_WALK_FLOAT_CHILDREN:
- b = b->float_children;
- if (b == NULL)
- break;
- *x += b->x;
- *y += b->y;
- rb = b;
- break;
-
- case BOX_WALK_NEXT_FLOAT_SIBLING:
- *x -= b->x;
- *y -= b->y;
- b = b->next_float;
- if (b == NULL)
- break;
- *x += b->x;
- *y += b->y;
- rb = b;
- break;
-
- case BOX_WALK_FLOAT_CONTAINER:
- *x -= b->x;
- *y -= b->y;
- rb = b->float_container;
- break;
-
- default:
- assert(0 && "Bad box walk type.");
- }
-
- return rb;
-}
-
-
-/**
- * Itterator for walking to next box in interaction order
- *
- * \param b box to find next box from
- * \param x box's global x-coord, updated to position of next box
- * \param y box's global y-coord, updated to position of next box
- * \param skip_children whether to skip box's children
- *
- * This walks to a boxes float children before its children. When walking
- * children, floating boxes are skipped.
- */
-static inline struct box *box_next_xy(struct box *b, int *x, int *y,
- bool skip_children)
-{
- struct box *n;
- int tx, ty;
-
- assert(b != NULL);
-
- if (skip_children) {
- /* Caller is not interested in any kind of children */
- goto skip_children;
- }
-
- tx = *x; ty = *y;
- n = box_move_xy(b, BOX_WALK_FLOAT_CHILDREN, &tx, &ty);
- if (n) {
- /* Next node is float child */
- *x = tx;
- *y = ty;
- return n;
- }
-done_float_children:
-
- tx = *x; ty = *y;
- n = box_move_xy(b, BOX_WALK_CHILDREN, &tx, &ty);
- if (n) {
- /* Next node is child */
- *x = tx;
- *y = ty;
- return n;
- }
-
-skip_children:
- tx = *x; ty = *y;
- n = box_move_xy(b, BOX_WALK_NEXT_FLOAT_SIBLING, &tx, &ty);
- if (n) {
- /* Go to next float sibling */
- *x = tx;
- *y = ty;
- return n;
- }
-
- if (box_is_float(b)) {
- /* Done floats, but the float container may have children,
- * or siblings, or ansestors with siblings. Change to
- * float container and move past handling its float children.
- */
- b = box_move_xy(b, BOX_WALK_FLOAT_CONTAINER, x, y);
- goto done_float_children;
- }
-
- /* Go to next sibling, or nearest ancestor with next sibling. */
- while (b) {
- while (!b->next && b->parent) {
- b = box_move_xy(b, BOX_WALK_PARENT, x, y);
- if (box_is_float(b)) {
- /* Go on to next float, if there is one */
- goto skip_children;
- }
- }
- if (!b->next) {
- /* No more boxes */
- return NULL;
- }
-
- tx = *x; ty = *y;
- n = box_move_xy(b, BOX_WALK_NEXT_SIBLING, &tx, &ty);
- if (n) {
- /* Go to non-float (ancestor) sibling */
- *x = tx;
- *y = ty;
- return n;
-
- } else if (b->parent) {
- b = box_move_xy(b, BOX_WALK_PARENT, x, y);
- if (box_is_float(b)) {
- /* Go on to next float, if there is one */
- goto skip_children;
- }
-
- } else {
- /* No more boxes */
- return NULL;
- }
- }
-
- assert(b != NULL);
- return NULL;
-}
-
-
-
-/**
- * Find the boxes at a point.
- *
- * \param len_ctx CSS length conversion context for document.
- * \param box box to search children of
- * \param x point to find, in global document coordinates
- * \param y point to find, in global document coordinates
- * \param box_x position of box, in global document coordinates, updated
- * to position of returned box, if any
- * \param box_y position of box, in global document coordinates, updated
- * to position of returned box, if any
- * \return box at given point, or 0 if none found
- *
- * To find all the boxes in the hierarchy at a certain point, use code like
- * this:
- * \code
- * struct box *box = top_of_document_to_search;
- * int box_x = 0, box_y = 0;
- *
- * while ((box = box_at_point(len_ctx, box, x, y, &box_x, &box_y))) {
- * // process box
- * }
- * \endcode
- */
-
-struct box *box_at_point(const nscss_len_ctx *len_ctx,
- struct box *box, const int x, const int y,
- int *box_x, int *box_y)
-{
- bool skip_children;
- bool physically;
-
- assert(box);
-
- skip_children = false;
- while ((box = box_next_xy(box, box_x, box_y, skip_children))) {
- if (box_contains_point(len_ctx, box, x - *box_x, y - *box_y,
- &physically)) {
- *box_x -= scrollbar_get_offset(box->scroll_x);
- *box_y -= scrollbar_get_offset(box->scroll_y);
-
- if (physically)
- return box;
-
- skip_children = false;
- } else {
- skip_children = true;
- }
- }
-
- return NULL;
-}
-
-
-/**
- * Check whether box is nearer mouse coordinates than current nearest box
- *
- * \param box box to test
- * \param bx position of box, in global document coordinates
- * \param by position of box, in global document coordinates
- * \param x mouse point, in global document coordinates
- * \param y mouse point, in global document coordinates
- * \param dir direction in which to search (-1 = above-left,
- * +1 = below-right)
- * \param nearest nearest text box found, or NULL if none
- * updated if box is nearer than existing nearest
- * \param tx position of text_box, in global document coordinates
- * updated if box is nearer than existing nearest
- * \param ty position of text_box, in global document coordinates
- * updated if box is nearer than existing nearest
- * \param nr_xd distance to nearest text box found
- * updated if box is nearer than existing nearest
- * \param nr_yd distance to nearest text box found
- * updated if box is nearer than existing nearest
- * \return true if mouse point is inside box
- */
-
-static bool box_nearer_text_box(struct box *box, int bx, int by,
- int x, int y, int dir, struct box **nearest, int *tx, int *ty,
- int *nr_xd, int *nr_yd)
-{
- int w = box->padding[LEFT] + box->width + box->padding[RIGHT];
- int h = box->padding[TOP] + box->height + box->padding[BOTTOM];
- int y1 = by + h;
- int x1 = bx + w;
- int yd = INT_MAX;
- int xd = INT_MAX;
-
- if (x >= bx && x1 > x && y >= by && y1 > y) {
- *nearest = box;
- *tx = bx;
- *ty = by;
- return true;
- }
-
- if (box->parent->list_marker != box) {
- if (dir < 0) {
- /* consider only those children (partly) above-left */
- if (by <= y && bx < x) {
- yd = y <= y1 ? 0 : y - y1;
- xd = x <= x1 ? 0 : x - x1;
- }
- } else {
- /* consider only those children (partly) below-right */
- if (y1 > y && x1 > x) {
- yd = y > by ? 0 : by - y;
- xd = x > bx ? 0 : bx - x;
- }
- }
-
- /* give y displacement precedence over x */
- if (yd < *nr_yd || (yd == *nr_yd && xd <= *nr_xd)) {
- *nr_yd = yd;
- *nr_xd = xd;
- *nearest = box;
- *tx = bx;
- *ty = by;
- }
- }
- return false;
-}
-
-
-/**
- * Pick the text box child of 'box' that is closest to and above-left
- * (dir -ve) or below-right (dir +ve) of the point 'x,y'
- *
- * \param box parent box
- * \param bx position of box, in global document coordinates
- * \param by position of box, in global document coordinates
- * \param fx position of float parent, in global document coordinates
- * \param fy position of float parent, in global document coordinates
- * \param x mouse point, in global document coordinates
- * \param y mouse point, in global document coordinates
- * \param dir direction in which to search (-1 = above-left,
- * +1 = below-right)
- * \param nearest nearest text box found, or NULL if none
- * updated if a descendant of box is nearer than old nearest
- * \param tx position of nearest, in global document coordinates
- * updated if a descendant of box is nearer than old nearest
- * \param ty position of nearest, in global document coordinates
- * updated if a descendant of box is nearer than old nearest
- * \param nr_xd distance to nearest text box found
- * updated if a descendant of box is nearer than old nearest
- * \param nr_yd distance to nearest text box found
- * updated if a descendant of box is nearer than old nearest
- * \return true if mouse point is inside text_box
- */
-
-static bool box_nearest_text_box(struct box *box, int bx, int by,
- int fx, int fy, int x, int y, int dir, struct box **nearest,
- int *tx, int *ty, int *nr_xd, int *nr_yd)
-{
- struct box *child = box->children;
- int c_bx, c_by;
- int c_fx, c_fy;
- bool in_box = false;
-
- if (*nearest == NULL) {
- *nr_xd = INT_MAX / 2; /* displacement of 'nearest so far' */
- *nr_yd = INT_MAX / 2;
- }
- if (box->type == BOX_INLINE_CONTAINER) {
- int bw = box->padding[LEFT] + box->width + box->padding[RIGHT];
- int bh = box->padding[TOP] + box->height + box->padding[BOTTOM];
- int b_y1 = by + bh;
- int b_x1 = bx + bw;
- if (x >= bx && b_x1 > x && y >= by && b_y1 > y) {
- in_box = true;
- }
- }
-
- while (child) {
- if (child->type == BOX_FLOAT_LEFT ||
- child->type == BOX_FLOAT_RIGHT) {
- c_bx = fx + child->x -
- scrollbar_get_offset(child->scroll_x);
- c_by = fy + child->y -
- scrollbar_get_offset(child->scroll_y);
- } else {
- c_bx = bx + child->x -
- scrollbar_get_offset(child->scroll_x);
- c_by = by + child->y -
- scrollbar_get_offset(child->scroll_y);
- }
- if (child->float_children) {
- c_fx = c_bx;
- c_fy = c_by;
- } else {
- c_fx = fx;
- c_fy = fy;
- }
- if (in_box && child->text && !child->object) {
- if (box_nearer_text_box(child,
- c_bx, c_by, x, y, dir, nearest,
- tx, ty, nr_xd, nr_yd))
- return true;
- } else {
- if (child->list_marker) {
- if (box_nearer_text_box(
- child->list_marker,
- c_bx + child->list_marker->x,
- c_by + child->list_marker->y,
- x, y, dir, nearest,
- tx, ty, nr_xd, nr_yd))
- return true;
- }
- if (box_nearest_text_box(child, c_bx, c_by,
- c_fx, c_fy, x, y, dir, nearest, tx, ty,
- nr_xd, nr_yd))
- return true;
- }
- child = child->next;
- }
-
- return false;
-}
-
-
-/**
- * Peform pick text on browser window contents to locate the box under
- * the mouse pointer, or nearest in the given direction if the pointer is
- * not over a text box.
- *
- * \param html an HTML content
- * \param x coordinate of mouse
- * \param y coordinate of mouse
- * \param dir direction to search (-1 = above-left, +1 = below-right)
- * \param dx receives x ordinate of mouse relative to text box
- * \param dy receives y ordinate of mouse relative to text box
- */
-
-struct box *box_pick_text_box(struct html_content *html,
- int x, int y, int dir, int *dx, int *dy)
-{
- struct box *text_box = NULL;
- struct box *box;
- int nr_xd, nr_yd;
- int bx, by;
- int fx, fy;
- int tx, ty;
-
- if (html == NULL)
- return NULL;
-
- box = html->layout;
- bx = box->margin[LEFT];
- by = box->margin[TOP];
- fx = bx;
- fy = by;
-
- if (!box_nearest_text_box(box, bx, by, fx, fy, x, y,
- dir, &text_box, &tx, &ty, &nr_xd, &nr_yd)) {
- if (text_box && text_box->text && !text_box->object) {
- int w = (text_box->padding[LEFT] +
- text_box->width +
- text_box->padding[RIGHT]);
- int h = (text_box->padding[TOP] +
- text_box->height +
- text_box->padding[BOTTOM]);
- int x1, y1;
-
- y1 = ty + h;
- x1 = tx + w;
-
- /* ensure point lies within the text box */
- if (x < tx) x = tx;
- if (y < ty) y = ty;
- if (y > y1) y = y1;
- if (x > x1) x = x1;
- }
- }
-
- /* return coordinates relative to box */
- *dx = x - tx;
- *dy = y - ty;
-
- return text_box;
-}
-
-
-/**
- * Find a box based upon its id attribute.
- *
- * \param box box tree to search
- * \param id id to look for
- * \return the box or 0 if not found
- */
-
-struct box *box_find_by_id(struct box *box, lwc_string *id)
-{
- struct box *a, *b;
- bool m;
-
- if (box->id != NULL &&
- lwc_string_isequal(id, box->id, &m) == lwc_error_ok &&
- m == true)
- return box;
-
- for (a = box->children; a; a = a->next) {
- if ((b = box_find_by_id(a, id)) != NULL)
- return b;
- }
-
- return NULL;
-}
-
-
-/**
- * Determine if a box is visible when the tree is rendered.
- *
- * \param box box to check
- * \return true iff the box is rendered
- */
-
-bool box_visible(struct box *box)
-{
- /* visibility: hidden */
- if (box->style && css_computed_visibility(box->style) ==
- CSS_VISIBILITY_HIDDEN)
- return false;
-
- return true;
-}
-
-
-/**
- * Print a box tree to a file.
- */
-
-void box_dump(FILE *stream, struct box *box, unsigned int depth, bool style)
-{
- unsigned int i;
- struct box *c, *prev;
-
- for (i = 0; i != depth; i++)
- fprintf(stream, " ");
-
- fprintf(stream, "%p ", box);
- fprintf(stream, "x%i y%i w%i h%i ", box->x, box->y,
- box->width, box->height);
- if (box->max_width != UNKNOWN_MAX_WIDTH)
- fprintf(stream, "min%i max%i ", box->min_width, box->max_width);
- fprintf(stream, "(%i %i %i %i) ",
- box->descendant_x0, box->descendant_y0,
- box->descendant_x1, box->descendant_y1);
-
- fprintf(stream, "m(%i %i %i %i) ",
- box->margin[TOP], box->margin[LEFT],
- box->margin[BOTTOM], box->margin[RIGHT]);
-
- switch (box->type) {
- case BOX_BLOCK: fprintf(stream, "BLOCK "); break;
- case BOX_INLINE_CONTAINER: fprintf(stream, "INLINE_CONTAINER "); break;
- case BOX_INLINE: fprintf(stream, "INLINE "); break;
- case BOX_INLINE_END: fprintf(stream, "INLINE_END "); break;
- case BOX_INLINE_BLOCK: fprintf(stream, "INLINE_BLOCK "); break;
- case BOX_TABLE: fprintf(stream, "TABLE [columns %i] ",
- box->columns); break;
- case BOX_TABLE_ROW: fprintf(stream, "TABLE_ROW "); break;
- case BOX_TABLE_CELL: fprintf(stream, "TABLE_CELL [columns %i, "
- "start %i, rows %i] ", box->columns,
- box->start_column, box->rows); break;
- case BOX_TABLE_ROW_GROUP: fprintf(stream, "TABLE_ROW_GROUP "); break;
- case BOX_FLOAT_LEFT: fprintf(stream, "FLOAT_LEFT "); break;
- case BOX_FLOAT_RIGHT: fprintf(stream, "FLOAT_RIGHT "); break;
- case BOX_BR: fprintf(stream, "BR "); break;
- case BOX_TEXT: fprintf(stream, "TEXT "); break;
- default: fprintf(stream, "Unknown box type ");
- }
-
- if (box->text)
- fprintf(stream, "%li '%.*s' ", (unsigned long) box->byte_offset,
- (int) box->length, box->text);
- if (box->space)
- fprintf(stream, "space ");
- if (box->object) {
- fprintf(stream, "(object '%s') ",
- nsurl_access(hlcache_handle_get_url(box->object)));
- }
- if (box->iframe) {
- fprintf(stream, "(iframe) ");
- }
- if (box->gadget)
- fprintf(stream, "(gadget) ");
- if (style && box->style)
- nscss_dump_computed_style(stream, box->style);
- if (box->href)
- fprintf(stream, " -> '%s'", nsurl_access(box->href));
- if (box->target)
- fprintf(stream, " |%s|", box->target);
- if (box->title)
- fprintf(stream, " [%s]", box->title);
- if (box->id)
- fprintf(stream, " ID:%s", lwc_string_data(box->id));
- if (box->type == BOX_INLINE || box->type == BOX_INLINE_END)
- fprintf(stream, " inline_end %p", box->inline_end);
- if (box->float_children)
- fprintf(stream, " float_children %p", box->float_children);
- if (box->next_float)
- fprintf(stream, " next_float %p", box->next_float);
- if (box->float_container)
- fprintf(stream, " float_container %p", box->float_container);
- if (box->col) {
- fprintf(stream, " (columns");
- for (i = 0; i != box->columns; i++)
- fprintf(stream, " (%s %s %i %i %i)",
- ((const char *[]) {"UNKNOWN", "FIXED",
- "AUTO", "PERCENT", "RELATIVE"})
- [box->col[i].type],
- ((const char *[]) {"normal",
- "positioned"})
- [box->col[i].positioned],
- box->col[i].width,
- box->col[i].min, box->col[i].max);
- fprintf(stream, ")");
- }
- if (box->node != NULL) {
- dom_string *name;
- if (dom_node_get_node_name(box->node, &name) == DOM_NO_ERR) {
- fprintf(stream, " <%s>", dom_string_data(name));
- dom_string_unref(name);
- }
- }
- fprintf(stream, "\n");
-
- if (box->list_marker) {
- for (i = 0; i != depth; i++)
- fprintf(stream, " ");
- fprintf(stream, "list_marker:\n");
- box_dump(stream, box->list_marker, depth + 1, style);
- }
-
- for (c = box->children; c && c->next; c = c->next)
- ;
- if (box->last != c)
- fprintf(stream, "warning: box->last %p (should be %p) "
- "(box %p)\n", box->last, c, box);
- for (prev = 0, c = box->children; c; prev = c, c = c->next) {
- if (c->parent != box)
- fprintf(stream, "warning: box->parent %p (should be "
- "%p) (box on next line)\n",
- c->parent, box);
- if (c->prev != prev)
- fprintf(stream, "warning: box->prev %p (should be "
- "%p) (box on next line)\n",
- c->prev, prev);
- box_dump(stream, c, depth + 1, style);
- }
-}
-
-/**
- * Applies the given scroll setup to a box. This includes scroll
- * creation/deletion as well as scroll dimension updates.
- *
- * \param c content in which the box is located
- * \param box the box to handle the scrolls for
- * \param bottom whether the horizontal scrollbar should be present
- * \param right whether the vertical scrollbar should be present
- * \return true on success false otherwise
- */
-bool box_handle_scrollbars(struct content *c, struct box *box,
- bool bottom, bool right)
-{
- struct html_scrollbar_data *data;
- int visible_width, visible_height;
- int full_width, full_height;
-
- if (!bottom && box->scroll_x != NULL) {
- data = scrollbar_get_data(box->scroll_x);
- scrollbar_destroy(box->scroll_x);
- free(data);
- box->scroll_x = NULL;
- }
-
- if (!right && box->scroll_y != NULL) {
- data = scrollbar_get_data(box->scroll_y);
- scrollbar_destroy(box->scroll_y);
- free(data);
- box->scroll_y = NULL;
- }
-
- if (!bottom && !right)
- return true;
-
- visible_width = box->width + box->padding[RIGHT] + box->padding[LEFT];
- visible_height = box->height + box->padding[TOP] + box->padding[BOTTOM];
-
- full_width = ((box->descendant_x1 - box->border[RIGHT].width) >
- visible_width) ?
- box->descendant_x1 + box->padding[RIGHT] :
- visible_width;
- full_height = ((box->descendant_y1 - box->border[BOTTOM].width) >
- visible_height) ?
- box->descendant_y1 + box->padding[BOTTOM] :
- visible_height;
-
- if (right) {
- if (box->scroll_y == NULL) {
- data = malloc(sizeof(struct html_scrollbar_data));
- if (data == NULL) {
- NSLOG(netsurf, INFO, "malloc failed");
- guit->misc->warning("NoMemory", 0);
- return false;
- }
- data->c = c;
- data->box = box;
- if (scrollbar_create(false, visible_height,
- full_height, visible_height,
- data, html_overflow_scroll_callback,
- &(box->scroll_y)) != NSERROR_OK) {
- return false;
- }
- } else {
- scrollbar_set_extents(box->scroll_y, visible_height,
- visible_height, full_height);
- }
- }
- if (bottom) {
- if (box->scroll_x == NULL) {
- data = malloc(sizeof(struct html_scrollbar_data));
- if (data == NULL) {
- NSLOG(netsurf, INFO, "malloc failed");
- guit->misc->warning("NoMemory", 0);
- return false;
- }
- data->c = c;
- data->box = box;
- if (scrollbar_create(true,
- visible_width -
- (right ? SCROLLBAR_WIDTH : 0),
- full_width, visible_width,
- data, html_overflow_scroll_callback,
- &box->scroll_x) != NSERROR_OK) {
- return false;
- }
- } else {
- scrollbar_set_extents(box->scroll_x,
- visible_width -
- (right ? SCROLLBAR_WIDTH : 0),
- visible_width, full_width);
- }
- }
-
- if (right && bottom)
- scrollbar_make_pair(box->scroll_x, box->scroll_y);
-
- return true;
-}
-
-/**
- * Determine if a box has a vertical scrollbar.
- *
- * \param box scrolling box
- * \return the box has a vertical scrollbar
- */
-
-bool box_vscrollbar_present(const struct box * const box)
-{
- return box->padding[TOP] + box->height + box->padding[BOTTOM] +
- box->border[BOTTOM].width < box->descendant_y1;
-}
-
-
-/**
- * Determine if a box has a horizontal scrollbar.
- *
- * \param box scrolling box
- * \return the box has a horizontal scrollbar
- */
-
-bool box_hscrollbar_present(const struct box * const box)
-{
- return box->padding[LEFT] + box->width + box->padding[RIGHT] +
- box->border[RIGHT].width < box->descendant_x1;
-}
-
diff --git a/render/box.h b/render/box.h
deleted file mode 100644
index 1af0a8b..0000000
--- a/render/box.h
+++ /dev/null
@@ -1,368 +0,0 @@
-/*
- * Copyright 2005 James Bursa <bursa(a)users.sourceforge.net>
- * Copyright 2003 Phil Mellor <monkeyson(a)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
- * Box tree construction and manipulation (interface).
- *
- * This stage of rendering converts a tree of dom_nodes (produced by libdom)
- * to a tree of struct box. The box tree represents the structure of the
- * document as given by the CSS display and float properties.
- *
- * For example, consider the following HTML:
- * \code
- * <h1>Example Heading</h1>
- * <p>Example paragraph <em>with emphasised text</em> etc.</p> \endcode
- *
- * This would produce approximately the following box tree with default CSS
- * rules:
- * \code
- * BOX_BLOCK (corresponds to h1)
- * BOX_INLINE_CONTAINER
- * BOX_INLINE "Example Heading"
- * BOX_BLOCK (p)
- * BOX_INLINE_CONTAINER
- * BOX_INLINE "Example paragraph "
- * BOX_INLINE "with emphasised text" (em)
- * BOX_INLINE "etc." \endcode
- *
- * Note that the em has been collapsed into the INLINE_CONTAINER.
- *
- * If these CSS rules were applied:
- * \code
- * h1 { display: table-cell }
- * p { display: table-cell }
- * em { float: left; width: 5em } \endcode
- *
- * then the box tree would instead look like this:
- * \code
- * BOX_TABLE
- * BOX_TABLE_ROW_GROUP
- * BOX_TABLE_ROW
- * BOX_TABLE_CELL (h1)
- * BOX_INLINE_CONTAINER
- * BOX_INLINE "Example Heading"
- * BOX_TABLE_CELL (p)
- * BOX_INLINE_CONTAINER
- * BOX_INLINE "Example paragraph "
- * BOX_FLOAT_LEFT (em)
- * BOX_BLOCK
- * BOX_INLINE_CONTAINER
- * BOX_INLINE "with emphasised text"
- * BOX_INLINE "etc." \endcode
- *
- * Here implied boxes have been added and a float is present.
- *
- * A box tree is "normalized" if the following is satisfied:
- * \code
- * parent permitted child nodes
- * BLOCK, INLINE_BLOCK BLOCK, INLINE_CONTAINER, TABLE
- * INLINE_CONTAINER INLINE, INLINE_BLOCK, FLOAT_LEFT, FLOAT_RIGHT, BR, TEXT,
- * INLINE_END
- * INLINE none
- * TABLE at least 1 TABLE_ROW_GROUP
- * TABLE_ROW_GROUP at least 1 TABLE_ROW
- * TABLE_ROW at least 1 TABLE_CELL
- * TABLE_CELL BLOCK, INLINE_CONTAINER, TABLE (same as BLOCK)
- * FLOAT_(LEFT|RIGHT) exactly 1 BLOCK or TABLE
- * \endcode
- */
-
-#ifndef _NETSURF_RENDER_BOX_H_
-#define _NETSURF_RENDER_BOX_H_
-
-#include <limits.h>
-#include <stdbool.h>
-#include <stdio.h>
-#include <libcss/libcss.h>
-
-#include "content/handlers/css/utils.h"
-
-struct content;
-struct box;
-struct browser_window;
-struct column;
-struct object_params;
-struct object_param;
-struct html_content;
-struct nsurl;
-struct dom_node;
-struct dom_string;
-struct rect;
-
-#define UNKNOWN_WIDTH INT_MAX
-#define UNKNOWN_MAX_WIDTH INT_MAX
-
-typedef void (*box_construct_complete_cb)(struct html_content *c, bool success);
-
-/** Type of a struct box. */
-typedef enum {
- BOX_BLOCK, BOX_INLINE_CONTAINER, BOX_INLINE,
- BOX_TABLE, BOX_TABLE_ROW, BOX_TABLE_CELL,
- BOX_TABLE_ROW_GROUP,
- BOX_FLOAT_LEFT, BOX_FLOAT_RIGHT,
- BOX_INLINE_BLOCK, BOX_BR, BOX_TEXT,
- BOX_INLINE_END, BOX_NONE
-} box_type;
-
-
-/** Flags for a struct box. */
-typedef enum {
- NEW_LINE = 1 << 0, /* first inline on a new line */
- STYLE_OWNED = 1 << 1, /* style is owned by this box */
- PRINTED = 1 << 2, /* box has already been printed */
- PRE_STRIP = 1 << 3, /* PRE tag needing leading newline stripped */
- CLONE = 1 << 4, /* continuation of previous box from wrapping */
- MEASURED = 1 << 5, /* text box width has been measured */
- HAS_HEIGHT = 1 << 6, /* box has height (perhaps due to children) */
- MAKE_HEIGHT = 1 << 7, /* box causes its own height */
- NEED_MIN = 1 << 8, /* minimum width is required for layout */
- REPLACE_DIM = 1 << 9, /* replaced element has given dimensions */
- IFRAME = 1 << 10, /* box contains an iframe */
- CONVERT_CHILDREN = 1 << 11, /* wanted children converting */
- IS_REPLACED = 1 << 12 /* box is a replaced element */
-} box_flags;
-
-/* Sides of a box */
-enum box_side { TOP, RIGHT, BOTTOM, LEFT };
-
-/**
- * Container for box border details
- */
-struct box_border {
- enum css_border_style_e style; /**< border-style */
- css_color c; /**< border-color value */
- int width; /**< border-width (pixels) */
-};
-
-/** Node in box tree. All dimensions are in pixels. */
-struct box {
- /** Type of box. */
- box_type type;
-
- /** Box flags */
- box_flags flags;
-
- /** Computed styles for elements and their pseudo elements. NULL on
- * non-element boxes. */
- css_select_results *styles;
-
- /** Style for this box. 0 for INLINE_CONTAINER and FLOAT_*. Pointer into
- * a box's 'styles' select results, except for implied boxes, where it
- * is a pointer to an owned computed style. */
- css_computed_style *style;
-
- /** Coordinate of left padding edge relative to parent box, or relative
- * to ancestor that contains this box in float_children for FLOAT_. */
- int x;
- /** Coordinate of top padding edge, relative as for x. */
- int y;
-
- int width; /**< Width of content box (excluding padding etc.). */
- int height; /**< Height of content box (excluding padding etc.). */
-
- /* These four variables determine the maximum extent of a box's
- * descendants. They are relative to the x,y coordinates of the box.
- *
- * Their use depends on the overflow CSS property:
- *
- * Overflow: Usage:
- * visible The content of the box is displayed within these
- * dimensions.
- * hidden These are ignored. Content is plotted within the box
- * dimensions.
- * scroll These are used to determine the extent of the
- * scrollable area.
- * auto As "scroll".
- */
- int descendant_x0; /**< left edge of descendants */
- int descendant_y0; /**< top edge of descendants */
- int descendant_x1; /**< right edge of descendants */
- int descendant_y1; /**< bottom edge of descendants */
-
- int margin[4]; /**< Margin: TOP, RIGHT, BOTTOM, LEFT. */
- int padding[4]; /**< Padding: TOP, RIGHT, BOTTOM, LEFT. */
- struct box_border border[4]; /**< Border: TOP, RIGHT, BOTTOM, LEFT. */
-
- struct scrollbar *scroll_x; /**< Horizontal scroll. */
- struct scrollbar *scroll_y; /**< Vertical scroll. */
-
- /** Width of box taking all line breaks (including margins etc). Must
- * be non-negative. */
- int min_width;
- /** Width that would be taken with no line breaks. Must be
- * non-negative. */
- int max_width;
-
- /**< Byte offset within a textual representation of this content. */
- size_t byte_offset;
-
- char *text; /**< Text, or 0 if none. Unterminated. */
- size_t length; /**< Length of text. */
-
- /** Width of space after current text (depends on font and size). */
- int space;
-
- struct nsurl *href; /**< Link, or 0. */
- const char *target; /**< Link target, or 0. */
- const char *title; /**< Title, or 0. */
-
- unsigned int columns; /**< Number of columns for TABLE / TABLE_CELL. */
- unsigned int rows; /**< Number of rows for TABLE only. */
- unsigned int start_column; /**< Start column for TABLE_CELL only. */
-
- struct box *next; /**< Next sibling box, or 0. */
- struct box *prev; /**< Previous sibling box, or 0. */
- struct box *children; /**< First child box, or 0. */
- struct box *last; /**< Last child box, or 0. */
- struct box *parent; /**< Parent box, or 0. */
- /** INLINE_END box corresponding to this INLINE box, or INLINE box
- * corresponding to this INLINE_END box. */
- struct box *inline_end;
-
- /** First float child box, or 0. Float boxes are in the tree twice, in
- * this list for the block box which defines the area for floats, and
- * also in the standard tree given by children, next, prev, etc. */
- struct box *float_children;
- /** Next sibling float box. */
- struct box *next_float;
- /** If box is a float, points to box's containing block */
- struct box *float_container;
- /** Level below which subsequent floats must be cleared.
- * This is used only for boxes with float_children */
- int clear_level;
-
- /* Level below which floats have been placed. */
- int cached_place_below_level;
-
- /** List marker box if this is a list-item, or 0. */
- struct box *list_marker;
-
- struct column *col; /**< Array of table column data for TABLE only. */
-
- /** Form control data, or 0 if not a form control. */
- struct form_control* gadget;
-
- char *usemap; /** (Image)map to use with this object, or 0 if none */
- lwc_string *id; /**< value of id attribute (or name for anchors) */
-
- /** Background image for this box, or 0 if none */
- struct hlcache_handle *background;
-
- /** Object in this box (usually an image), or 0 if none. */
- struct hlcache_handle* object;
- /** Parameters for the object, or 0. */
- struct object_params *object_params;
-
- /** Iframe's browser_window, or NULL if none */
- struct browser_window *iframe;
-
- struct dom_node *node; /**< DOM node that generated this box or NULL */
-};
-
-/** Table column data. */
-struct column {
- /** Type of column. */
- enum { COLUMN_WIDTH_UNKNOWN, COLUMN_WIDTH_FIXED,
- COLUMN_WIDTH_AUTO, COLUMN_WIDTH_PERCENT,
- COLUMN_WIDTH_RELATIVE } type;
- /** Preferred width of column. Pixels for FIXED, percentage for PERCENT,
- * relative units for RELATIVE, unused for AUTO. */
- int width;
- /** Minimum width of content. */
- int min;
- /** Maximum width of content. */
- int max;
- /** Whether all of column's cells are css positioned. */
- bool positioned;
-};
-
-/** Parameters for object element and similar elements. */
-struct object_params {
- struct nsurl *data;
- char *type;
- char *codetype;
- struct nsurl *codebase;
- struct nsurl *classid;
- struct object_param *params;
-};
-
-/** Linked list of object element parameters. */
-struct object_param {
- char *name;
- char *value;
- char *type;
- char *valuetype;
- struct object_param *next;
-};
-
-/** Frame target names (constant pointers to save duplicating the strings many
- * times). We convert _blank to _top for user-friendliness. */
-extern const char *TARGET_SELF;
-extern const char *TARGET_PARENT;
-extern const char *TARGET_TOP;
-extern const char *TARGET_BLANK;
-
-
-
-struct box * box_create(css_select_results *styles, css_computed_style *style,
- bool style_owned, struct nsurl *href, const char *target,
- const char *title, lwc_string *id, void *context);
-void box_add_child(struct box *parent, struct box *child);
-void box_insert_sibling(struct box *box, struct box *new_box);
-void box_unlink_and_free(struct box *box);
-void box_free(struct box *box);
-void box_free_box(struct box *box);
-void box_bounds(struct box *box, struct rect *r);
-void box_coords(struct box *box, int *x, int *y);
-struct box *box_at_point(
- const nscss_len_ctx *len_ctx,
- struct box *box, const int x, const int y,
- int *box_x, int *box_y);
-struct box *box_pick_text_box(struct html_content *html,
- int x, int y, int dir, int *dx, int *dy);
-struct box *box_find_by_id(struct box *box, lwc_string *id);
-bool box_visible(struct box *box);
-void box_dump(FILE *stream, struct box *box, unsigned int depth, bool style);
-
-/**
- * Extract a URL from a relative link, handling junk like whitespace and
- * attempting to read a real URL from "javascript:" links.
- *
- * \param content html content
- * \param dsrel relative URL text taken from page
- * \param base base for relative URLs
- * \param result updated to target URL on heap, unchanged if extract failed
- * \return true on success, false on memory exhaustion
- */
-bool box_extract_link(const struct html_content *content, const struct dom_string *dsrel, struct nsurl *base, struct nsurl **result);
-
-bool box_handle_scrollbars(struct content *c, struct box *box,
- bool bottom, bool right);
-bool box_vscrollbar_present(const struct box *box);
-bool box_hscrollbar_present(const struct box *box);
-
-nserror dom_to_box(struct dom_node *n, struct html_content *c,
- box_construct_complete_cb cb);
-
-bool box_normalise_block(
- struct box *block,
- const struct box *root,
- struct html_content *c);
-
-#endif
diff --git a/render/box_construct.c b/render/box_construct.c
deleted file mode 100644
index 1aa99e2..0000000
--- a/render/box_construct.c
+++ /dev/null
@@ -1,3137 +0,0 @@
-/*
- * Copyright 2005 James Bursa <bursa(a)users.sourceforge.net>
- * Copyright 2003 Phil Mellor <monkeyson(a)users.sourceforge.net>
- * Copyright 2005 John M Bell <jmb202(a)ecs.soton.ac.uk>
- * Copyright 2006 Richard Wilson <info(a)tinct.net>
- * Copyright 2008 Michael Drake <tlsa(a)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
- * Implementation of conversion from DOM tree to box tree.
- */
-
-#include <assert.h>
-#include <stdio.h>
-#include <stdbool.h>
-#include <stdlib.h>
-#include <string.h>
-#include <strings.h>
-
-#include "utils/config.h"
-#include "utils/nsoption.h"
-#include "utils/corestrings.h"
-#include "utils/log.h"
-#include "utils/messages.h"
-#include "utils/talloc.h"
-#include "utils/string.h"
-#include "utils/ascii.h"
-#include "netsurf/css.h"
-#include "netsurf/misc.h"
-#include "netsurf/plot_style.h"
-#include "content/content_protected.h"
-#include "css/hints.h"
-#include "css/select.h"
-#include "css/utils.h"
-#include "desktop/gui_internal.h"
-
-#include "render/box.h"
-#include "render/box_textarea.h"
-#include "render/form_internal.h"
-#include "render/html_internal.h"
-
-/**
- * Context for box tree construction
- */
-struct box_construct_ctx {
- html_content *content; /**< Content we're constructing for */
-
- dom_node *n; /**< Current node to process */
-
- struct box *root_box; /**< Root box in the tree */
-
- box_construct_complete_cb cb; /**< Callback to invoke on completion */
-
- int *bctx; /**< talloc context */
-};
-
-/**
- * Transient properties for construction of current node
- */
-struct box_construct_props {
- /** Style from which to inherit, or NULL if none */
- const css_computed_style *parent_style;
- /** Current link target, or NULL if none */
- nsurl *href;
- /** Current frame target, or NULL if none */
- const char *target;
- /** Current title attribute, or NULL if none */
- const char *title;
- /** Identity of the current block-level container */
- struct box *containing_block;
- /** Current container for inlines, or NULL if none
- * \note If non-NULL, will be the last child of containing_block */
- struct box *inline_container;
- /** Whether the current node is the root of the DOM tree */
- bool node_is_root;
-};
-
-static const content_type image_types = CONTENT_IMAGE;
-
-/* the strings are not important, since we just compare the pointers */
-const char *TARGET_SELF = "_self";
-const char *TARGET_PARENT = "_parent";
-const char *TARGET_TOP = "_top";
-const char *TARGET_BLANK = "_blank";
-
-static void convert_xml_to_box(struct box_construct_ctx *ctx);
-static bool box_construct_element(struct box_construct_ctx *ctx,
- bool *convert_children);
-static void box_construct_element_after(dom_node *n, html_content *content);
-static bool box_construct_text(struct box_construct_ctx *ctx);
-static css_select_results * box_get_style(html_content *c,
- const css_computed_style *parent_style,
- const css_computed_style *root_style, dom_node *n);
-static void box_text_transform(char *s, unsigned int len,
- enum css_text_transform_e tt);
-#define BOX_SPECIAL_PARAMS dom_node *n, html_content *content, \
- struct box *box, bool *convert_children
-static bool box_a(BOX_SPECIAL_PARAMS);
-static bool box_body(BOX_SPECIAL_PARAMS);
-static bool box_br(BOX_SPECIAL_PARAMS);
-static bool box_image(BOX_SPECIAL_PARAMS);
-static bool box_textarea(BOX_SPECIAL_PARAMS);
-static bool box_select(BOX_SPECIAL_PARAMS);
-static bool box_input(BOX_SPECIAL_PARAMS);
-static bool box_button(BOX_SPECIAL_PARAMS);
-static bool box_frameset(BOX_SPECIAL_PARAMS);
-static bool box_create_frameset(struct content_html_frames *f, dom_node *n,
- html_content *content);
-static bool box_select_add_option(struct form_control *control, dom_node *n);
-static bool box_noscript(BOX_SPECIAL_PARAMS);
-static bool box_object(BOX_SPECIAL_PARAMS);
-static bool box_embed(BOX_SPECIAL_PARAMS);
-static bool box_pre(BOX_SPECIAL_PARAMS);
-static bool box_iframe(BOX_SPECIAL_PARAMS);
-static bool box_get_attribute(dom_node *n, const char *attribute,
- void *context, char **value);
-
-/* element_table must be sorted by name */
-struct element_entry {
- char name[10]; /* element type */
- bool (*convert)(BOX_SPECIAL_PARAMS);
-};
-static const struct element_entry element_table[] = {
- {"a", box_a},
- {"body", box_body},
- {"br", box_br},
- {"button", box_button},
- {"embed", box_embed},
- {"frameset", box_frameset},
- {"iframe", box_iframe},
- {"image", box_image},
- {"img", box_image},
- {"input", box_input},
- {"noscript", box_noscript},
- {"object", box_object},
- {"pre", box_pre},
- {"select", box_select},
- {"textarea", box_textarea}
-};
-#define ELEMENT_TABLE_COUNT (sizeof(element_table) / sizeof(element_table[0]))
-
-/**
- * Construct a box tree from an xml tree and stylesheets.
- *
- * \param n xml tree
- * \param c content of type CONTENT_HTML to construct box tree in
- * \param cb callback to report conversion completion
- * \return netsurf error code indicating status of call
- */
-
-nserror dom_to_box(dom_node *n, html_content *c, box_construct_complete_cb cb)
-{
- struct box_construct_ctx *ctx;
-
- if (c->bctx == NULL) {
- /* create a context allocation for this box tree */
- c->bctx = talloc_zero(0, int);
- if (c->bctx == NULL) {
- return NSERROR_NOMEM;
- }
- }
-
- ctx = malloc(sizeof(*ctx));
- if (ctx == NULL) {
- return NSERROR_NOMEM;
- }
-
- ctx->content = c;
- ctx->n = dom_node_ref(n);
- ctx->root_box = NULL;
- ctx->cb = cb;
- ctx->bctx = c->bctx;
-
- return guit->misc->schedule(0, (void *)convert_xml_to_box, ctx);
-}
-
-/* mapping from CSS display to box type
- * this table must be in sync with libcss' css_display enum */
-static const box_type box_map[] = {
- 0, /*CSS_DISPLAY_INHERIT,*/
- BOX_INLINE, /*CSS_DISPLAY_INLINE,*/
- BOX_BLOCK, /*CSS_DISPLAY_BLOCK,*/
- BOX_BLOCK, /*CSS_DISPLAY_LIST_ITEM,*/
- BOX_INLINE, /*CSS_DISPLAY_RUN_IN,*/
- BOX_INLINE_BLOCK, /*CSS_DISPLAY_INLINE_BLOCK,*/
- BOX_TABLE, /*CSS_DISPLAY_TABLE,*/
- BOX_TABLE, /*CSS_DISPLAY_INLINE_TABLE,*/
- BOX_TABLE_ROW_GROUP, /*CSS_DISPLAY_TABLE_ROW_GROUP,*/
- BOX_TABLE_ROW_GROUP, /*CSS_DISPLAY_TABLE_HEADER_GROUP,*/
- BOX_TABLE_ROW_GROUP, /*CSS_DISPLAY_TABLE_FOOTER_GROUP,*/
- BOX_TABLE_ROW, /*CSS_DISPLAY_TABLE_ROW,*/
- BOX_NONE, /*CSS_DISPLAY_TABLE_COLUMN_GROUP,*/
- BOX_NONE, /*CSS_DISPLAY_TABLE_COLUMN,*/
- BOX_TABLE_CELL, /*CSS_DISPLAY_TABLE_CELL,*/
- BOX_INLINE, /*CSS_DISPLAY_TABLE_CAPTION,*/
- BOX_NONE /*CSS_DISPLAY_NONE*/
-};
-
-static inline struct box *box_for_node(dom_node *n)
-{
- struct box *box = NULL;
- dom_exception err;
-
- err = dom_node_get_user_data(n, corestring_dom___ns_key_box_node_data,
- (void *) &box);
- if (err != DOM_NO_ERR)
- return NULL;
-
- return box;
-}
-
-static inline bool box_is_root(dom_node *n)
-{
- dom_node *parent;
- dom_node_type type;
- dom_exception err;
-
- err = dom_node_get_parent_node(n, &parent);
- if (err != DOM_NO_ERR)
- return false;
-
- if (parent != NULL) {
- err = dom_node_get_node_type(parent, &type);
-
- dom_node_unref(parent);
-
- if (err != DOM_NO_ERR)
- return false;
-
- if (type != DOM_DOCUMENT_NODE)
- return false;
- }
-
- return true;
-}
-
-/**
- * Find the next node in the DOM tree, completing
- * element construction where appropriate.
- *
- * \param n Current node
- * \param content Containing content
- * \param convert_children Whether to consider children of \a n
- * \return Next node to process, or NULL if complete
- *
- * \note \a n will be unreferenced
- */
-static dom_node *next_node(dom_node *n, html_content *content,
- bool convert_children)
-{
- dom_node *next = NULL;
- bool has_children;
- dom_exception err;
-
- err = dom_node_has_child_nodes(n, &has_children);
- if (err != DOM_NO_ERR) {
- dom_node_unref(n);
- return NULL;
- }
-
- if (convert_children && has_children) {
- err = dom_node_get_first_child(n, &next);
- if (err != DOM_NO_ERR) {
- dom_node_unref(n);
- return NULL;
- }
- dom_node_unref(n);
- } else {
- err = dom_node_get_next_sibling(n, &next);
- if (err != DOM_NO_ERR) {
- dom_node_unref(n);
- return NULL;
- }
-
- if (next != NULL) {
- if (box_for_node(n) != NULL)
- box_construct_element_after(n, content);
- dom_node_unref(n);
- } else {
- if (box_for_node(n) != NULL)
- box_construct_element_after(n, content);
-
- while (box_is_root(n) == false) {
- dom_node *parent = NULL;
- dom_node *parent_next = NULL;
-
- err = dom_node_get_parent_node(n, &parent);
- if (err != DOM_NO_ERR) {
- dom_node_unref(n);
- return NULL;
- }
-
- assert(parent != NULL);
-
- err = dom_node_get_next_sibling(parent,
- &parent_next);
- if (err != DOM_NO_ERR) {
- dom_node_unref(parent);
- dom_node_unref(n);
- return NULL;
- }
-
- if (parent_next != NULL) {
- dom_node_unref(parent_next);
- dom_node_unref(parent);
- break;
- }
-
- dom_node_unref(n);
- n = parent;
- parent = NULL;
-
- if (box_for_node(n) != NULL) {
- box_construct_element_after(
- n, content);
- }
- }
-
- if (box_is_root(n) == false) {
- dom_node *parent = NULL;
-
- err = dom_node_get_parent_node(n, &parent);
- if (err != DOM_NO_ERR) {
- dom_node_unref(n);
- return NULL;
- }
-
- assert(parent != NULL);
-
- err = dom_node_get_next_sibling(parent, &next);
- if (err != DOM_NO_ERR) {
- dom_node_unref(parent);
- dom_node_unref(n);
- return NULL;
- }
-
- if (box_for_node(parent) != NULL) {
- box_construct_element_after(parent,
- content);
- }
-
- dom_node_unref(parent);
- }
-
- dom_node_unref(n);
- }
- }
-
- return next;
-}
-
-/**
- * Convert an ELEMENT node to a box tree fragment,
- * then schedule conversion of the next ELEMENT node
- */
-void convert_xml_to_box(struct box_construct_ctx *ctx)
-{
- dom_node *next;
- bool convert_children;
- uint32_t num_processed = 0;
- const uint32_t max_processed_before_yield = 10;
-
- do {
- convert_children = true;
-
- assert(ctx->n != NULL);
-
- if (box_construct_element(ctx, &convert_children) == false) {
- ctx->cb(ctx->content, false);
- dom_node_unref(ctx->n);
- free(ctx);
- return;
- }
-
- /* Find next element to process, converting text nodes as we go */
- next = next_node(ctx->n, ctx->content, convert_children);
- while (next != NULL) {
- dom_node_type type;
- dom_exception err;
-
- err = dom_node_get_node_type(next, &type);
- if (err != DOM_NO_ERR) {
- ctx->cb(ctx->content, false);
- dom_node_unref(next);
- free(ctx);
- return;
- }
-
- if (type == DOM_ELEMENT_NODE)
- break;
-
- if (type == DOM_TEXT_NODE) {
- ctx->n = next;
- if (box_construct_text(ctx) == false) {
- ctx->cb(ctx->content, false);
- dom_node_unref(ctx->n);
- free(ctx);
- return;
- }
- }
-
- next = next_node(next, ctx->content, true);
- }
-
- ctx->n = next;
-
- if (next == NULL) {
- /* Conversion complete */
- struct box root;
-
- memset(&root, 0, sizeof(root));
-
- root.type = BOX_BLOCK;
- root.children = root.last = ctx->root_box;
- root.children->parent = &root;
-
- /** \todo Remove box_normalise_block */
- if (box_normalise_block(&root, ctx->root_box,
- ctx->content) == false) {
- ctx->cb(ctx->content, false);
- } else {
- ctx->content->layout = root.children;
- ctx->content->layout->parent = NULL;
-
- ctx->cb(ctx->content, true);
- }
-
- assert(ctx->n == NULL);
-
- free(ctx);
- return;
- }
- } while (++num_processed < max_processed_before_yield);
-
- /* More work to do: schedule a continuation */
- guit->misc->schedule(0, (void *)convert_xml_to_box, ctx);
-}
-
-/**
- * Construct a list marker box
- *
- * \param box Box to attach marker to
- * \param title Current title attribute
- * \param ctx Box construction context
- * \param parent Current block-level container
- * \return true on success, false on memory exhaustion
- */
-static bool box_construct_marker(struct box *box, const char *title,
- struct box_construct_ctx *ctx, struct box *parent)
-{
- lwc_string *image_uri;
- struct box *marker;
-
- marker = box_create(NULL, box->style, false, NULL, NULL, title,
- NULL, ctx->bctx);
- if (marker == false)
- return false;
-
- marker->type = BOX_BLOCK;
-
- /** \todo marker content (list-style-type) */
- switch (css_computed_list_style_type(box->style)) {
- case CSS_LIST_STYLE_TYPE_DISC:
- /* 2022 BULLET */
- marker->text = (char *) "\342\200\242";
- marker->length = 3;
- break;
- case CSS_LIST_STYLE_TYPE_CIRCLE:
- /* 25CB WHITE CIRCLE */
- marker->text = (char *) "\342\227\213";
- marker->length = 3;
- break;
- case CSS_LIST_STYLE_TYPE_SQUARE:
- /* 25AA BLACK SMALL SQUARE */
- marker->text = (char *) "\342\226\252";
- marker->length = 3;
- break;
- case CSS_LIST_STYLE_TYPE_DECIMAL:
- case CSS_LIST_STYLE_TYPE_LOWER_ALPHA:
- case CSS_LIST_STYLE_TYPE_LOWER_ROMAN:
- case CSS_LIST_STYLE_TYPE_UPPER_ALPHA:
- case CSS_LIST_STYLE_TYPE_UPPER_ROMAN:
- default:
- if (parent->last) {
- struct box *last = parent->last;
-
- /* Drill down into last child of parent
- * to find the list marker (if any)
- *
- * Floated list boxes end up as:
- *
- * parent
- * BOX_INLINE_CONTAINER
- * BOX_FLOAT_{LEFT,RIGHT}
- * BOX_BLOCK <-- list box
- * ...
- */
- while (last != NULL && last->list_marker == NULL) {
- struct box *last_inner = last;
-
- while (last_inner != NULL) {
- if (last_inner->list_marker != NULL)
- break;
- if (last_inner->type ==
- BOX_INLINE_CONTAINER ||
- last_inner->type ==
- BOX_FLOAT_LEFT ||
- last_inner->type ==
- BOX_FLOAT_RIGHT) {
- last_inner = last_inner->last;
- } else {
- last_inner = NULL;
- }
- }
- if (last_inner != NULL) {
- last = last_inner;
- } else {
- last = last->prev;
- }
- }
-
- if (last && last->list_marker) {
- marker->rows = last->list_marker->rows + 1;
- }
- }
-
- marker->text = talloc_array(ctx->bctx, char, 20);
- if (marker->text == NULL)
- return false;
-
- snprintf(marker->text, 20, "%u.", marker->rows);
- marker->length = strlen(marker->text);
- break;
- case CSS_LIST_STYLE_TYPE_NONE:
- marker->text = 0;
- marker->length = 0;
- break;
- }
-
- if (css_computed_list_style_image(box->style, &image_uri) == CSS_LIST_STYLE_IMAGE_URI &&
- (image_uri != NULL) &&
- (nsoption_bool(foreground_images) == true)) {
- nsurl *url;
- nserror error;
-
- /* TODO: we get a url out of libcss as a lwc string, but
- * earlier we already had it as a nsurl after we
- * nsurl_joined it. Can this be improved?
- * For now, just making another nsurl. */
- error = nsurl_create(lwc_string_data(image_uri), &url);
- if (error != NSERROR_OK)
- return false;
-
- if (html_fetch_object(ctx->content, url, marker, image_types,
- ctx->content->base.available_width, 1000, false) ==
- false) {
- nsurl_unref(url);
- return false;
- }
- nsurl_unref(url);
- }
-
- box->list_marker = marker;
- marker->parent = box;
-
- return true;
-}
-
-/**
- * Construct the box required for a generated element.
- *
- * \param n XML node of type XML_ELEMENT_NODE
- * \param content Content of type CONTENT_HTML that is being processed
- * \param box Box which may have generated content
- * \param style Complete computed style for pseudo element, or NULL
- *
- * TODO:
- * This is currently incomplete. It just does enough to support the clearfix
- * hack. ( http://www.positioniseverything.net/easyclearing.html )
- */
-static void box_construct_generate(dom_node *n, html_content *content,
- struct box *box, const css_computed_style *style)
-{
- struct box *gen = NULL;
- enum css_display_e computed_display;
- const css_computed_content_item *c_item;
-
- /* Nothing to generate if the parent box is not a block */
- if (box->type != BOX_BLOCK)
- return;
-
- /* To determine if an element has a pseudo element, we select
- * for it and test to see if the returned style's content
- * property is set to normal. */
- if (style == NULL ||
- css_computed_content(style, &c_item) ==
- CSS_CONTENT_NORMAL) {
- /* No pseudo element */
- return;
- }
-
- /* create box for this element */
- computed_display = ns_computed_display(style, box_is_root(n));
- if (computed_display == CSS_DISPLAY_BLOCK ||
- computed_display == CSS_DISPLAY_TABLE) {
- /* currently only support block level boxes */
-
- /** \todo Not wise to drop const from the computed style */
- gen = box_create(NULL, (css_computed_style *) style,
- false, NULL, NULL, NULL, NULL, content->bctx);
- if (gen == NULL) {
- return;
- }
-
- /* set box type from computed display */
- gen->type = box_map[ns_computed_display(
- style, box_is_root(n))];
-
- box_add_child(box, gen);
- }
-}
-
-/**
- * Extract transient construction properties
- *
- * \param n Current DOM node to convert
- * \param props Property object to populate
- */
-static void box_extract_properties(dom_node *n,
- struct box_construct_props *props)
-{
- memset(props, 0, sizeof(*props));
-
- props->node_is_root = box_is_root(n);
-
- /* Extract properties from containing DOM node */
- if (props->node_is_root == false) {
- dom_node *current_node = n;
- dom_node *parent_node = NULL;
- struct box *parent_box;
- dom_exception err;
-
- /* Find ancestor node containing parent box */
- while (true) {
- err = dom_node_get_parent_node(current_node,
- &parent_node);
- if (err != DOM_NO_ERR || parent_node == NULL)
- break;
-
- parent_box = box_for_node(parent_node);
-
- if (parent_box != NULL) {
- props->parent_style = parent_box->style;
- props->href = parent_box->href;
- props->target = parent_box->target;
- props->title = parent_box->title;
-
- dom_node_unref(parent_node);
- break;
- } else {
- if (current_node != n)
- dom_node_unref(current_node);
- current_node = parent_node;
- parent_node = NULL;
- }
- }
-
- /* Find containing block (may be parent) */
- while (true) {
- struct box *b;
-
- err = dom_node_get_parent_node(current_node,
- &parent_node);
- if (err != DOM_NO_ERR || parent_node == NULL) {
- if (current_node != n)
- dom_node_unref(current_node);
- break;
- }
-
- if (current_node != n)
- dom_node_unref(current_node);
-
- b = box_for_node(parent_node);
-
- /* Children of nodes that created an inline box
- * will generate boxes which are attached as
- * _siblings_ of the box generated for their
- * parent node. Note, however, that we'll still
- * use the parent node's styling as the parent
- * style, above. */
- if (b != NULL && b->type != BOX_INLINE &&
- b->type != BOX_BR) {
- props->containing_block = b;
-
- dom_node_unref(parent_node);
- break;
- } else {
- current_node = parent_node;
- parent_node = NULL;
- }
- }
- }
-
- /* Compute current inline container, if any */
- if (props->containing_block != NULL &&
- props->containing_block->last != NULL &&
- props->containing_block->last->type ==
- BOX_INLINE_CONTAINER)
- props->inline_container = props->containing_block->last;
-}
-
-/**
- * Construct the box tree for an XML element.
- *
- * \param ctx Tree construction context
- * \param convert_children Whether to convert children
- * \return true on success, false on memory exhaustion
- */
-
-bool box_construct_element(struct box_construct_ctx *ctx,
- bool *convert_children)
-{
- dom_string *title0, *s;
- lwc_string *id = NULL;
- struct box *box = NULL, *old_box;
- css_select_results *styles = NULL;
- struct element_entry *element;
- lwc_string *bgimage_uri;
- dom_exception err;
- struct box_construct_props props;
- const css_computed_style *root_style = NULL;
-
- assert(ctx->n != NULL);
-
- box_extract_properties(ctx->n, &props);
-
- if (props.containing_block != NULL) {
- /* In case the containing block is a pre block, we clear
- * the PRE_STRIP flag since it is not used if we follow
- * the pre with a tag */
- props.containing_block->flags &= ~PRE_STRIP;
- }
-
- if (props.node_is_root == false) {
- root_style = ctx->root_box->style;
- }
-
- styles = box_get_style(ctx->content, props.parent_style, root_style,
- ctx->n);
- if (styles == NULL)
- return false;
-
- /* Extract title attribute, if present */
- err = dom_element_get_attribute(ctx->n, corestring_dom_title, &title0);
- if (err != DOM_NO_ERR)
- return false;
-
- if (title0 != NULL) {
- char *t = squash_whitespace(dom_string_data(title0));
-
- dom_string_unref(title0);
-
- if (t == NULL)
- return false;
-
- props.title = talloc_strdup(ctx->bctx, t);
-
- free(t);
-
- if (props.title == NULL)
- return false;
- }
-
- /* Extract id attribute, if present */
- err = dom_element_get_attribute(ctx->n, corestring_dom_id, &s);
- if (err != DOM_NO_ERR)
- return false;
-
- if (s != NULL) {
- err = dom_string_intern(s, &id);
- if (err != DOM_NO_ERR)
- id = NULL;
-
- dom_string_unref(s);
- }
-
- box = box_create(styles, styles->styles[CSS_PSEUDO_ELEMENT_NONE], false,
- props.href, props.target, props.title, id,
- ctx->bctx);
- if (box == NULL)
- return false;
-
- /* If this is the root box, add it to the context */
- if (props.node_is_root)
- ctx->root_box = box;
-
- /* Deal with colspan/rowspan */
- err = dom_element_get_attribute(ctx->n, corestring_dom_colspan, &s);
- if (err != DOM_NO_ERR)
- return false;
-
- if (s != NULL) {
- const char *val = dom_string_data(s);
-
- if ('0' <= val[0] && val[0] <= '9')
- box->columns = strtol(val, NULL, 10);
-
- dom_string_unref(s);
- }
-
- err = dom_element_get_attribute(ctx->n, corestring_dom_rowspan, &s);
- if (err != DOM_NO_ERR)
- return false;
-
- if (s != NULL) {
- const char *val = dom_string_data(s);
-
- if ('0' <= val[0] && val[0] <= '9')
- box->rows = strtol(val, NULL, 10);
-
- dom_string_unref(s);
- }
-
- /* Set box type from computed display */
- if ((css_computed_position(box->style) == CSS_POSITION_ABSOLUTE ||
- css_computed_position(box->style) ==
- CSS_POSITION_FIXED) &&
- (ns_computed_display_static(box->style) ==
- CSS_DISPLAY_INLINE ||
- ns_computed_display_static(box->style) ==
- CSS_DISPLAY_INLINE_BLOCK ||
- ns_computed_display_static(box->style) ==
- CSS_DISPLAY_INLINE_TABLE)) {
- /* Special case for absolute positioning: make absolute inlines
- * into inline block so that the boxes are constructed in an
- * inline container as if they were not absolutely positioned.
- * Layout expects and handles this. */
- box->type = box_map[CSS_DISPLAY_INLINE_BLOCK];
- } else if (props.node_is_root) {
- /* Special case for root element: force it to BLOCK, or the
- * rest of the layout will break. */
- box->type = BOX_BLOCK;
- } else {
- /* Normal mapping */
- box->type = box_map[ns_computed_display(box->style,
- props.node_is_root)];
- }
-
- err = dom_node_get_node_name(ctx->n, &s);
- if (err != DOM_NO_ERR || s == NULL)
- return false;
-
- /* Special elements */
- element = bsearch(dom_string_data(s), element_table,
- ELEMENT_TABLE_COUNT, sizeof(element_table[0]),
- (int (*)(const void *, const void *)) strcasecmp);
-
- dom_string_unref(s);
-
- if (element != NULL) {
- /* A special convert function exists for this element */
- if (element->convert(ctx->n, ctx->content, box,
- convert_children) == false)
- return false;
- }
-
- /* Handle the :before pseudo element */
- if (!(box->flags & IS_REPLACED)) {
- box_construct_generate(ctx->n, ctx->content, box,
- box->styles->styles[CSS_PSEUDO_ELEMENT_BEFORE]);
- }
-
- if (box->type == BOX_NONE || (ns_computed_display(box->style,
- props.node_is_root) == CSS_DISPLAY_NONE &&
- props.node_is_root == false)) {
- css_select_results_destroy(styles);
- box->styles = NULL;
- box->style = NULL;
-
- /* Invalidate associated gadget, if any */
- if (box->gadget != NULL) {
- box->gadget->box = NULL;
- box->gadget = NULL;
- }
-
- /* Can't do this, because the lifetimes of boxes and gadgets
- * are inextricably linked. Fortunately, talloc will save us
- * (for now) */
- /* box_free_box(box); */
-
- *convert_children = false;
-
- return true;
- }
-
- /* Attach DOM node to box */
- err = dom_node_set_user_data(ctx->n,
- corestring_dom___ns_key_box_node_data, box, NULL,
- (void *) &old_box);
- if (err != DOM_NO_ERR)
- return false;
-
- /* Attach box to DOM node */
- box->node = dom_node_ref(ctx->n);
-
- if (props.inline_container == NULL &&
- (box->type == BOX_INLINE ||
- box->type == BOX_BR ||
- box->type == BOX_INLINE_BLOCK ||
- css_computed_float(box->style) == CSS_FLOAT_LEFT ||
- css_computed_float(box->style) == CSS_FLOAT_RIGHT) &&
- props.node_is_root == false) {
- /* Found an inline child of a block without a current container
- * (i.e. this box is the first child of its parent, or was
- * preceded by block-level siblings) */
- assert(props.containing_block != NULL &&
- "Box must have containing block.");
-
- props.inline_container = box_create(NULL, NULL, false, NULL,
- NULL, NULL, NULL, ctx->bctx);
- if (props.inline_container == NULL)
- return false;
-
- props.inline_container->type = BOX_INLINE_CONTAINER;
-
- box_add_child(props.containing_block, props.inline_container);
- }
-
- /* Kick off fetch for any background image */
- if (css_computed_background_image(box->style, &bgimage_uri) ==
- CSS_BACKGROUND_IMAGE_IMAGE && bgimage_uri != NULL &&
- nsoption_bool(background_images) == true) {
- nsurl *url;
- nserror error;
-
- /* TODO: we get a url out of libcss as a lwc string, but
- * earlier we already had it as a nsurl after we
- * nsurl_joined it. Can this be improved?
- * For now, just making another nsurl. */
- error = nsurl_create(lwc_string_data(bgimage_uri), &url);
- if (error == NSERROR_OK) {
- /* Fetch image if we got a valid URL */
- if (html_fetch_object(ctx->content, url, box,
- image_types,
- ctx->content->base.available_width,
- 1000, true) == false) {
- nsurl_unref(url);
- return false;
- }
- nsurl_unref(url);
- }
- }
-
- if (*convert_children)
- box->flags |= CONVERT_CHILDREN;
-
- if (box->type == BOX_INLINE || box->type == BOX_BR ||
- box->type == BOX_INLINE_BLOCK) {
- /* Inline container must exist, as we'll have
- * created it above if it didn't */
- assert(props.inline_container != NULL);
-
- box_add_child(props.inline_container, box);
- } else {
- if (ns_computed_display(box->style, props.node_is_root) ==
- CSS_DISPLAY_LIST_ITEM) {
- /* List item: compute marker */
- if (box_construct_marker(box, props.title, ctx,
- props.containing_block) == false)
- return false;
- }
-
- if (props.node_is_root == false &&
- (css_computed_float(box->style) ==
- CSS_FLOAT_LEFT ||
- css_computed_float(box->style) ==
- CSS_FLOAT_RIGHT)) {
- /* Float: insert a float between the parent and box. */
- struct box *flt = box_create(NULL, NULL, false,
- props.href, props.target, props.title,
- NULL, ctx->bctx);
- if (flt == NULL)
- return false;
-
- if (css_computed_float(box->style) == CSS_FLOAT_LEFT)
- flt->type = BOX_FLOAT_LEFT;
- else
- flt->type = BOX_FLOAT_RIGHT;
-
- box_add_child(props.inline_container, flt);
- box_add_child(flt, box);
- } else {
- /* Non-floated block-level box: add to containing block
- * if there is one. If we're the root box, then there
- * won't be. */
- if (props.containing_block != NULL)
- box_add_child(props.containing_block, box);
- }
- }
-
- return true;
-}
-
-/**
- * Complete construction of the box tree for an element.
- *
- * \param n DOM node to construct for
- * \param content Containing document
- *
- * This will be called after all children of an element have been processed
- */
-void box_construct_element_after(dom_node *n, html_content *content)
-{
- struct box_construct_props props;
- struct box *box = box_for_node(n);
-
- assert(box != NULL);
-
- box_extract_properties(n, &props);
-
- if (box->type == BOX_INLINE || box->type == BOX_BR) {
- /* Insert INLINE_END into containing block */
- struct box *inline_end;
- bool has_children;
- dom_exception err;
-
- err = dom_node_has_child_nodes(n, &has_children);
- if (err != DOM_NO_ERR)
- return;
-
- if (has_children == false ||
- (box->flags & CONVERT_CHILDREN) == 0) {
- /* No children, or didn't want children converted */
- return;
- }
-
- if (props.inline_container == NULL) {
- /* Create inline container if we don't have one */
- props.inline_container = box_create(NULL, NULL, false,
- NULL, NULL, NULL, NULL, content->bctx);
- if (props.inline_container == NULL)
- return;
-
- props.inline_container->type = BOX_INLINE_CONTAINER;
-
- box_add_child(props.containing_block,
- props.inline_container);
- }
-
- inline_end = box_create(NULL, box->style, false,
- box->href, box->target, box->title,
- box->id == NULL ? NULL :
- lwc_string_ref(box->id), content->bctx);
- if (inline_end != NULL) {
- inline_end->type = BOX_INLINE_END;
-
- assert(props.inline_container != NULL);
-
- box_add_child(props.inline_container, inline_end);
-
- box->inline_end = inline_end;
- inline_end->inline_end = box;
- }
- } else if (!(box->flags & IS_REPLACED)) {
- /* Handle the :after pseudo element */
- box_construct_generate(n, content, box,
- box->styles->styles[CSS_PSEUDO_ELEMENT_AFTER]);
- }
-}
-
-/**
- * Construct the box tree for an XML text node.
- *
- * \param ctx Tree construction context
- * \return true on success, false on memory exhaustion
- */
-
-bool box_construct_text(struct box_construct_ctx *ctx)
-{
- struct box_construct_props props;
- struct box *box = NULL;
- dom_string *content;
- dom_exception err;
-
- assert(ctx->n != NULL);
-
- box_extract_properties(ctx->n, &props);
-
- assert(props.containing_block != NULL);
-
- err = dom_characterdata_get_data(ctx->n, &content);
- if (err != DOM_NO_ERR || content == NULL)
- return false;
-
- if (css_computed_white_space(props.parent_style) ==
- CSS_WHITE_SPACE_NORMAL ||
- css_computed_white_space(props.parent_style) ==
- CSS_WHITE_SPACE_NOWRAP) {
- char *text;
-
- text = squash_whitespace(dom_string_data(content));
-
- dom_string_unref(content);
-
- if (text == NULL)
- return false;
-
- /* if the text is just a space, combine it with the preceding
- * text node, if any */
- if (text[0] == ' ' && text[1] == 0) {
- if (props.inline_container != NULL) {
- assert(props.inline_container->last != NULL);
-
- props.inline_container->last->space =
- UNKNOWN_WIDTH;
- }
-
- free(text);
-
- return true;
- }
-
- if (props.inline_container == NULL) {
- /* Child of a block without a current container
- * (i.e. this box is the first child of its parent, or
- * was preceded by block-level siblings) */
- props.inline_container = box_create(NULL, NULL, false,
- NULL, NULL, NULL, NULL, ctx->bctx);
- if (props.inline_container == NULL) {
- free(text);
- return false;
- }
-
- props.inline_container->type = BOX_INLINE_CONTAINER;
-
- box_add_child(props.containing_block,
- props.inline_container);
- }
-
- /** \todo Dropping const here is not clever */
- box = box_create(NULL,
- (css_computed_style *) props.parent_style,
- false, props.href, props.target, props.title,
- NULL, ctx->bctx);
- if (box == NULL) {
- free(text);
- return false;
- }
-
- box->type = BOX_TEXT;
-
- box->text = talloc_strdup(ctx->bctx, text);
- free(text);
- if (box->text == NULL)
- return false;
-
- box->length = strlen(box->text);
-
- /* strip ending space char off */
- if (box->length > 1 && box->text[box->length - 1] == ' ') {
- box->space = UNKNOWN_WIDTH;
- box->length--;
- }
-
- if (css_computed_text_transform(props.parent_style) !=
- CSS_TEXT_TRANSFORM_NONE)
- box_text_transform(box->text, box->length,
- css_computed_text_transform(
- props.parent_style));
-
- box_add_child(props.inline_container, box);
-
- if (box->text[0] == ' ') {
- box->length--;
-
- memmove(box->text, &box->text[1], box->length);
-
- if (box->prev != NULL)
- box->prev->space = UNKNOWN_WIDTH;
- }
- } else {
- /* white-space: pre */
- char *text;
- size_t text_len = dom_string_byte_length(content);
- size_t i;
- char *current;
- enum css_white_space_e white_space =
- css_computed_white_space(props.parent_style);
-
- /* note: pre-wrap/pre-line are unimplemented */
- assert(white_space == CSS_WHITE_SPACE_PRE ||
- white_space == CSS_WHITE_SPACE_PRE_LINE ||
- white_space == CSS_WHITE_SPACE_PRE_WRAP);
-
- text = malloc(text_len + 1);
- dom_string_unref(content);
-
- if (text == NULL)
- return false;
-
- memcpy(text, dom_string_data(content), text_len);
- text[text_len] = '\0';
-
- /* TODO: Handle tabs properly */
- for (i = 0; i < text_len; i++)
- if (text[i] == '\t')
- text[i] = ' ';
-
- if (css_computed_text_transform(props.parent_style) !=
- CSS_TEXT_TRANSFORM_NONE)
- box_text_transform(text, strlen(text),
- css_computed_text_transform(
- props.parent_style));
-
- current = text;
-
- /* swallow a single leading new line */
- if (props.containing_block->flags & PRE_STRIP) {
- switch (*current) {
- case '\n':
- current++;
- break;
- case '\r':
- current++;
- if (*current == '\n')
- current++;
- break;
- }
- props.containing_block->flags &= ~PRE_STRIP;
- }
-
- do {
- size_t len = strcspn(current, "\r\n");
-
- char old = current[len];
-
- current[len] = 0;
-
- if (props.inline_container == NULL) {
- /* Child of a block without a current container
- * (i.e. this box is the first child of its
- * parent, or was preceded by block-level
- * siblings) */
- props.inline_container = box_create(NULL, NULL,
- false, NULL, NULL, NULL, NULL,
- ctx->bctx);
- if (props.inline_container == NULL) {
- free(text);
- return false;
- }
-
- props.inline_container->type =
- BOX_INLINE_CONTAINER;
-
- box_add_child(props.containing_block,
- props.inline_container);
- }
-
- /** \todo Dropping const isn't clever */
- box = box_create(NULL,
- (css_computed_style *) props.parent_style,
- false, props.href, props.target, props.title,
- NULL, ctx->bctx);
- if (box == NULL) {
- free(text);
- return false;
- }
-
- box->type = BOX_TEXT;
-
- box->text = talloc_strdup(ctx->bctx, current);
- if (box->text == NULL) {
- free(text);
- return false;
- }
-
- box->length = strlen(box->text);
-
- box_add_child(props.inline_container, box);
-
- current[len] = old;
-
- current += len;
-
- if (current[0] != '\0') {
- /* Linebreak: create new inline container */
- props.inline_container = box_create(NULL, NULL,
- false, NULL, NULL, NULL, NULL,
- ctx->bctx);
- if (props.inline_container == NULL) {
- free(text);
- return false;
- }
-
- props.inline_container->type =
- BOX_INLINE_CONTAINER;
-
- box_add_child(props.containing_block,
- props.inline_container);
-
- if (current[0] == '\r' && current[1] == '\n')
- current += 2;
- else
- current++;
- }
- } while (*current);
-
- free(text);
- }
-
- return true;
-}
-
-/**
- * Get the style for an element.
- *
- * \param c content of type CONTENT_HTML that is being processed
- * \param parent_style style at this point in xml tree, or NULL for root
- * \param root_style root node's style, or NULL for root
- * \param n node in xml tree
- * \return the new style, or NULL on memory exhaustion
- */
-css_select_results *box_get_style(html_content *c,
- const css_computed_style *parent_style,
- const css_computed_style *root_style, dom_node *n)
-{
- dom_string *s;
- dom_exception err;
- css_stylesheet *inline_style = NULL;
- css_select_results *styles;
- nscss_select_ctx ctx;
-
- /* Firstly, construct inline stylesheet, if any */
- err = dom_element_get_attribute(n, corestring_dom_style, &s);
- if (err != DOM_NO_ERR)
- return NULL;
-
- if (s != NULL) {
- inline_style = nscss_create_inline_style(
- (const uint8_t *) dom_string_data(s),
- dom_string_byte_length(s),
- c->encoding,
- nsurl_access(c->base_url),
- c->quirks != DOM_DOCUMENT_QUIRKS_MODE_NONE);
-
- dom_string_unref(s);
-
- if (inline_style == NULL)
- return NULL;
- }
-
- /* Populate selection context */
- ctx.ctx = c->select_ctx;
- ctx.quirks = (c->quirks == DOM_DOCUMENT_QUIRKS_MODE_FULL);
- ctx.base_url = c->base_url;
- ctx.universal = c->universal;
- ctx.root_style = root_style;
- ctx.parent_style = parent_style;
-
- /* Select style for element */
- styles = nscss_get_style(&ctx, n, CSS_MEDIA_SCREEN, inline_style);
-
- /* No longer need inline style */
- if (inline_style != NULL)
- css_stylesheet_destroy(inline_style);
-
- return styles;
-}
-
-
-/**
- * Apply the CSS text-transform property to given text for its ASCII chars.
- *
- * \param s string to transform
- * \param len length of s
- * \param tt transform type
- */
-
-void box_text_transform(char *s, unsigned int len, enum css_text_transform_e tt)
-{
- unsigned int i;
- if (len == 0)
- return;
- switch (tt) {
- case CSS_TEXT_TRANSFORM_UPPERCASE:
- for (i = 0; i < len; ++i)
- if ((unsigned char) s[i] < 0x80)
- s[i] = toupper(s[i]);
- break;
- case CSS_TEXT_TRANSFORM_LOWERCASE:
- for (i = 0; i < len; ++i)
- if ((unsigned char) s[i] < 0x80)
- s[i] = tolower(s[i]);
- break;
- case CSS_TEXT_TRANSFORM_CAPITALIZE:
- if ((unsigned char) s[0] < 0x80)
- s[0] = toupper(s[0]);
- for (i = 1; i < len; ++i)
- if ((unsigned char) s[i] < 0x80 &&
- isspace(s[i - 1]))
- s[i] = toupper(s[i]);
- break;
- default:
- break;
- }
-}
-
-
-/**
- * \name Special case element handlers
- *
- * These functions are called by box_construct_element() when an element is
- * being converted, according to the entries in element_table.
- *
- * The parameters are the xmlNode, the content for the document, and a partly
- * filled in box structure for the element.
- *
- * Return true on success, false on memory exhaustion. Set *convert_children
- * to false if children of this element in the XML tree should be skipped (for
- * example, if they have been processed in some special way already).
- *
- * Elements ordered as in the HTML 4.01 specification. Section numbers in
- * brackets [] refer to the spec.
- *
- * \{
- */
-
-/**
- * Document body [7.5.1].
- */
-
-bool box_body(BOX_SPECIAL_PARAMS)
-{
- css_color color;
-
- css_computed_background_color(box->style, &color);
- if (nscss_color_is_transparent(color))
- content->background_colour = NS_TRANSPARENT;
- else
- content->background_colour = nscss_color_to_ns(color);
-
- return true;
-}
-
-
-/**
- * Forced line break [9.3.2].
- */
-
-bool box_br(BOX_SPECIAL_PARAMS)
-{
- box->type = BOX_BR;
- return true;
-}
-
-/**
- * Preformatted text [9.3.4].
- */
-
-bool box_pre(BOX_SPECIAL_PARAMS)
-{
- box->flags |= PRE_STRIP;
- return true;
-}
-
-/**
- * Anchor [12.2].
- */
-
-bool box_a(BOX_SPECIAL_PARAMS)
-{
- bool ok;
- nsurl *url;
- dom_string *s;
- dom_exception err;
-
- err = dom_element_get_attribute(n, corestring_dom_href, &s);
- if (err == DOM_NO_ERR && s != NULL) {
- ok = box_extract_link(content, s, content->base_url, &url);
- dom_string_unref(s);
- if (!ok)
- return false;
- if (url) {
- if (box->href != NULL)
- nsurl_unref(box->href);
- box->href = url;
- }
- }
-
- /* name and id share the same namespace */
- err = dom_element_get_attribute(n, corestring_dom_name, &s);
- if (err == DOM_NO_ERR && s != NULL) {
- lwc_string *lwc_name;
-
- err = dom_string_intern(s, &lwc_name);
-
- dom_string_unref(s);
-
- if (err == DOM_NO_ERR) {
- /* name replaces existing id
- * TODO: really? */
- if (box->id != NULL)
- lwc_string_unref(box->id);
-
- box->id = lwc_name;
- }
- }
-
- /* target frame [16.3] */
- err = dom_element_get_attribute(n, corestring_dom_target, &s);
- if (err == DOM_NO_ERR && s != NULL) {
- if (dom_string_caseless_lwc_isequal(s,
- corestring_lwc__blank))
- box->target = TARGET_BLANK;
- else if (dom_string_caseless_lwc_isequal(s,
- corestring_lwc__top))
- box->target = TARGET_TOP;
- else if (dom_string_caseless_lwc_isequal(s,
- corestring_lwc__parent))
- box->target = TARGET_PARENT;
- else if (dom_string_caseless_lwc_isequal(s,
- corestring_lwc__self))
- /* the default may have been overridden by a
- * <base target=...>, so this is different to 0 */
- box->target = TARGET_SELF;
- else {
- /* 6.16 says that frame names must begin with [a-zA-Z]
- * This doesn't match reality, so just take anything */
- box->target = talloc_strdup(content->bctx,
- dom_string_data(s));
- if (!box->target) {
- dom_string_unref(s);
- return false;
- }
- }
- dom_string_unref(s);
- }
-
- return true;
-}
-
-
-/**
- * Embedded image [13.2].
- */
-
-bool box_image(BOX_SPECIAL_PARAMS)
-{
- bool ok;
- dom_string *s;
- dom_exception err;
- nsurl *url;
- enum css_width_e wtype;
- enum css_height_e htype;
- css_fixed value = 0;
- css_unit wunit = CSS_UNIT_PX;
- css_unit hunit = CSS_UNIT_PX;
-
- if (box->style && ns_computed_display(box->style,
- box_is_root(n)) == CSS_DISPLAY_NONE)
- return true;
-
- /* handle alt text */
- err = dom_element_get_attribute(n, corestring_dom_alt, &s);
- if (err == DOM_NO_ERR && s != NULL) {
- char *alt = squash_whitespace(dom_string_data(s));
- dom_string_unref(s);
- if (alt == NULL)
- return false;
- box->text = talloc_strdup(content->bctx, alt);
- free(alt);
- if (box->text == NULL)
- return false;
- box->length = strlen(box->text);
- }
-
- if (nsoption_bool(foreground_images) == false) {
- return true;
- }
-
- /* imagemap associated with this image */
- if (!box_get_attribute(n, "usemap", content->bctx, &box->usemap))
- return false;
- if (box->usemap && box->usemap[0] == '#')
- box->usemap++;
-
- /* get image URL */
- err = dom_element_get_attribute(n, corestring_dom_src, &s);
- if (err != DOM_NO_ERR || s == NULL)
- return true;
-
- if (box_extract_link(content, s, content->base_url, &url) == false) {
- dom_string_unref(s);
- return false;
- }
-
- dom_string_unref(s);
-
- if (url == NULL)
- return true;
-
- /* start fetch */
- box->flags |= IS_REPLACED;
- ok = html_fetch_object(content, url, box, image_types,
- content->base.available_width, 1000, false);
- nsurl_unref(url);
-
- wtype = css_computed_width(box->style, &value, &wunit);
- htype = css_computed_height(box->style, &value, &hunit);
-
- if (wtype == CSS_WIDTH_SET && wunit != CSS_UNIT_PCT &&
- htype == CSS_HEIGHT_SET && hunit != CSS_UNIT_PCT) {
- /* We know the dimensions the image will be shown at before it's
- * fetched. */
- box->flags |= REPLACE_DIM;
- }
-
- return ok;
-}
-
-
-/**
- * Noscript element
- */
-
-bool box_noscript(BOX_SPECIAL_PARAMS)
-{
- /* If scripting is enabled, do not display the contents of noscript */
- if (content->enable_scripting)
- *convert_children = false;
-
- return true;
-}
-
-
-/**
- * Destructor for object_params, for <object> elements
- *
- * \param o The object params being destroyed.
- * \return 0 to allow talloc to continue destroying the tree.
- */
-static int box_object_talloc_destructor(struct object_params *o)
-{
- if (o->codebase != NULL)
- nsurl_unref(o->codebase);
- if (o->classid != NULL)
- nsurl_unref(o->classid);
- if (o->data != NULL)
- nsurl_unref(o->data);
-
- return 0;
-}
-
-/**
- * Generic embedded object [13.3].
- */
-
-bool box_object(BOX_SPECIAL_PARAMS)
-{
- struct object_params *params;
- struct object_param *param;
- dom_string *codebase, *classid, *data;
- dom_node *c;
- dom_exception err;
-
- if (box->style && ns_computed_display(box->style,
- box_is_root(n)) == CSS_DISPLAY_NONE)
- return true;
-
- if (box_get_attribute(n, "usemap", content->bctx, &box->usemap) ==
- false)
- return false;
- if (box->usemap && box->usemap[0] == '#')
- box->usemap++;
-
- params = talloc(content->bctx, struct object_params);
- if (params == NULL)
- return false;
-
- talloc_set_destructor(params, box_object_talloc_destructor);
-
- params->data = NULL;
- params->type = NULL;
- params->codetype = NULL;
- params->codebase = NULL;
- params->classid = NULL;
- params->params = NULL;
-
- /* codebase, classid, and data are URLs
- * (codebase is the base for the other two) */
- err = dom_element_get_attribute(n, corestring_dom_codebase, &codebase);
- if (err == DOM_NO_ERR && codebase != NULL) {
- if (box_extract_link(content, codebase, content->base_url,
- ¶ms->codebase) == false) {
- dom_string_unref(codebase);
- return false;
- }
- dom_string_unref(codebase);
- }
- if (params->codebase == NULL)
- params->codebase = nsurl_ref(content->base_url);
-
- err = dom_element_get_attribute(n, corestring_dom_classid, &classid);
- if (e