summaryrefslogtreecommitdiff
path: root/content
diff options
context:
space:
mode:
authorVincent Sanders <vince@netsurf-browser.org>2018-05-10 11:34:26 +0100
committerVincent Sanders <vince@kyllikki.org>2018-05-10 13:37:02 +0100
commit2a03ea30490892ac52b3da325ab78e1aa888f83e (patch)
treed041e4a2aab3b224ad41612d47ea2119895e27ac /content
parent1b892391d7859398c212b9fda5b532308fa6e8fd (diff)
downloadnetsurf-2a03ea30490892ac52b3da325ab78e1aa888f83e.tar.gz
netsurf-2a03ea30490892ac52b3da325ab78e1aa888f83e.tar.bz2
move html and text content handlers where they belong
Diffstat (limited to 'content')
-rw-r--r--content/handlers/Makefile11
-rw-r--r--content/handlers/html/Makefile7
-rw-r--r--content/handlers/html/box.c1241
-rw-r--r--content/handlers/html/box.h369
-rw-r--r--content/handlers/html/box_construct.c3134
-rw-r--r--content/handlers/html/box_normalise.c1047
-rw-r--r--content/handlers/html/box_textarea.c350
-rw-r--r--content/handlers/html/box_textarea.h54
-rw-r--r--content/handlers/html/font.c163
-rw-r--r--content/handlers/html/font.h43
-rw-r--r--content/handlers/html/form.c1895
-rw-r--r--content/handlers/html/form_internal.h277
-rw-r--r--content/handlers/html/html.c2468
-rw-r--r--content/handlers/html/html.h187
-rw-r--r--content/handlers/html/html_css.c714
-rw-r--r--content/handlers/html/html_css_fetcher.c325
-rw-r--r--content/handlers/html/html_forms.c579
-rw-r--r--content/handlers/html/html_interaction.c1434
-rw-r--r--content/handlers/html/html_internal.h409
-rw-r--r--content/handlers/html/html_object.c730
-rw-r--r--content/handlers/html/html_redraw.c1951
-rw-r--r--content/handlers/html/html_redraw_border.c928
-rw-r--r--content/handlers/html/html_script.c604
-rw-r--r--content/handlers/html/imagemap.c804
-rw-r--r--content/handlers/html/imagemap.h42
-rw-r--r--content/handlers/html/layout.c5432
-rw-r--r--content/handlers/html/layout.h45
-rw-r--r--content/handlers/html/search.c656
-rw-r--r--content/handlers/html/search.h86
-rw-r--r--content/handlers/html/table.c1080
-rw-r--r--content/handlers/html/table.h39
-rw-r--r--content/handlers/javascript/duktape/Document.bnd2
-rw-r--r--content/handlers/javascript/duktape/Window.bnd4
-rw-r--r--content/handlers/text/Makefile3
-rw-r--r--content/handlers/text/textplain.c1576
-rw-r--r--content/handlers/text/textplain.h139
36 files changed, 28825 insertions, 3 deletions
diff --git a/content/handlers/Makefile b/content/handlers/Makefile
index 2f2da3aed..ea9d0c84d 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 000000000..afefba27d
--- /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 000000000..52cf12413
--- /dev/null
+++ b/content/handlers/html/box.c
@@ -0,0 +1,1241 @@
+/*
+ * Copyright 2005-2007 James Bursa <bursa@users.sourceforge.net>
+ * Copyright 2003 Phil Mellor <monkeyson@users.sourceforge.net>
+ * Copyright 2005 John M Bell <jmb202@ecs.soton.ac.uk>
+ * Copyright 2008 Michael Drake <tlsa@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 000000000..f096b6714
--- /dev/null
+++ b/content/handlers/html/box.h
@@ -0,0 +1,369 @@
+/*
+ * Copyright 2005 James Bursa <bursa@users.sourceforge.net>
+ * Copyright 2003 Phil Mellor <monkeyson@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 000000000..9c19391de
--- /dev/null
+++ b/content/handlers/html/box_construct.c
@@ -0,0 +1,3134 @@
+/*
+ * Copyright 2005 James Bursa <bursa@users.sourceforge.net>
+ * Copyright 2003 Phil Mellor <monkeyson@users.sourceforge.net>
+ * Copyright 2005 John M Bell <jmb202@ecs.soton.ac.uk>
+ * Copyright 2006 Richard Wilson <info@tinct.net>
+ * Copyright 2008 Michael Drake <tlsa@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 &lt;object&gt; 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,
+ &params->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, &params->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, &params->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,
+ &params->codetype) == false)
+ return false;
+ if (box_get_attribute(n, "type", params, &params->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,
+ &param->name) == false) {
+ dom_node_unref(c);
+ return false;
+ }
+
+ if (box_get_attribute(c, "value", param,
+ &param->value) == false) {
+ dom_node_unref(c);
+ return false;
+ }
+
+ if (box_get_attribute(c, "type", param,
+ &param->type) == false) {
+ dom_node_unref(c);
+ return false;
+ }
+
+ if (box_get_attribute(c, "valuetype", param,
+ &param->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 &lt;iframe&gt; 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 &lt;option&gt;
+ * \param n xml element node for &lt;option&gt;
+ * \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,
+ &params->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 000000000..7155cb722
--- /dev/null
+++ b/content/handlers/html/box_normalise.c
@@ -0,0 +1,1047 @@
+/*
+ * Copyright 2005 James Bursa <bursa@users.sourceforge.net>
+ * Copyright 2003 Phil Mellor <monkeyson@users.sourceforge.net>
+ * Copyright 2005 John M Bell <jmb202@ecs.soton.ac.uk>
+ * Copyright 2004 Kevin Bagust <kevin.bagust@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 000000000..abd28fb86
--- /dev/null
+++ b/content/handlers/html/box_textarea.c
@@ -0,0 +1,350 @@
+/*
+ * Copyright 2013 Michael Drake <tlsa@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 000000000..e2b02e811
--- /dev/null
+++ b/content/handlers/html/box_textarea.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2013 Michael Drake <tlsa@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 000000000..9dbf5922b
--- /dev/null
+++ b/content/handlers/html/font.c
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2009 John-Mark Bell <jmb@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 000000000..5f69ee7d3
--- /dev/null
+++ b/content/handlers/html/font.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2014 Vincent Sanders <vince@netsurf-browser.org>
+ *
+ * This file is part of NetSurf, http://www.netsurf-browser.org/
+ *
+ * NetSurf is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * NetSurf is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * 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 000000000..9fe2e771a
--- /dev/null
+++ b/content/handlers/html/form.c
@@ -0,0 +1,1895 @@
+/*
+ * Copyright 2004 James Bursa <bursa@users.sourceforge.net>
+ * Copyright 2003 Phil Mellor <monkeyson@users.sourceforge.net>
+ * Copyright 2004 John Tytgat <joty@netsurf-browser.org>
+ * Copyright 2005-9 John-Mark Bell <jmb@netsurf-browser.org>
+ * Copyright 2009 Paul Blokus <paul_pl@users.sourceforge.net>
+ * Copyright 2010 Michael Drake <tlsa@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 000000000..a77e823b3
--- /dev/null
+++ b/content/handlers/html/form_internal.h
@@ -0,0 +1,277 @@
+/*
+ * Copyright 2014 Vincent Sanders <vince@netsurf-browser.org>
+ *
+ * This file is part of NetSurf, http://www.netsurf-browser.org/
+ *
+ * NetSurf is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * NetSurf is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * 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 000000000..18c1a7afb
--- /dev/null
+++ b/content/handlers/html/html.c
@@ -0,0 +1,2468 @@
+/*
+ * Copyright 2007 James Bursa <bursa@users.sourceforge.net>
+ * Copyright 2010 Michael Drake <tlsa@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 000000000..691e969a5
--- /dev/null
+++ b/content/handlers/html/html.h
@@ -0,0 +1,187 @@
+/*
+ * Copyright 2004 James Bursa <bursa@users.sourceforge.net>
+ *
+ * This file is part of NetSurf, http://www.netsurf-browser.org/
+ *
+ * NetSurf is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * NetSurf is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * 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 000000000..b67d19af6
--- /dev/null
+++ b/content/handlers/html/html_css.c
@@ -0,0 +1,714 @@
+/*
+ * Copyright 2013 Vincent Sanders <vince@netsurf-browser.org>
+ *
+ * This file is part of NetSurf, http://www.netsurf-browser.org/
+ *
+ * NetSurf is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * NetSurf is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * Processing for html content 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 000000000..7987ea094
--- /dev/null
+++ b/content/handlers/html/html_css_fetcher.c
@@ -0,0 +1,325 @@
+/*
+ * Copyright 2008 Rob Kendrick <rjek@netsurf-browser.org>
+ * Copyright 2013 John-Mark Bell <jmb@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 000000000..915eb002f
--- /dev/null
+++ b/content/handlers/html/html_forms.c
@@ -0,0 +1,579 @@
+/*
+ * Copyright 2011 Vincent Sanders <vince@netsurf-browser.org>
+ *
+ * This file is part of NetSurf, http://www.netsurf-browser.org/
+ *
+ * NetSurf is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * NetSurf is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * 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 000000000..898f55bc2
--- /dev/null
+++ b/content/handlers/html/html_interaction.c
@@ -0,0 +1,1434 @@
+/*
+ * Copyright 2006 James Bursa <bursa@users.sourceforge.net>
+ * Copyright 2006 Richard Wilson <info@tinct.net>
+ * Copyright 2008 Michael Drake <tlsa@netsurf-browser.org>
+ * Copyright 2009 Paul Blokus <paul_pl@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 000000000..b9eca663e
--- /dev/null
+++ b/content/handlers/html/html_internal.h
@@ -0,0 +1,409 @@
+/*
+ * Copyright 2004 James Bursa <bursa@users.sourceforge.net>
+ *
+ * This file is part of NetSurf, http://www.netsurf-browser.org/
+ *
+ * NetSurf is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * NetSurf is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * 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.html#the-script-element
+ *
+ * \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 000000000..c8715e3fb
--- /dev/null
+++ b/content/handlers/html/html_object.c
@@ -0,0 +1,730 @@
+/*
+ * Copyright 2013 Vincent Sanders <vince@netsurf-browser.org>
+ *
+ * This file is part of NetSurf, http://www.netsurf-browser.org/
+ *
+ * NetSurf is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * NetSurf is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * Processing for html content object operations.
+ */
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdint.h>
+#include <string.h>
+#include <strings.h>
+#include <stdlib.h>
+#include <nsutils/time.h>
+
+#include "utils/corestrings.h"
+#include "utils/config.h"
+#include "utils/log.h"
+#include "utils/nsoption.h"
+#include "netsurf/content.h"
+#include "netsurf/misc.h"
+#include "content/hlcache.h"
+#include "css/utils.h"
+#include "desktop/scrollbar.h"
+#include "desktop/gui_internal.h"
+
+#include "html/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 000000000..d05df8753
--- /dev/null
+++ b/content/handlers/html/html_redraw.c
@@ -0,0 +1,1951 @@
+/*
+ * Copyright 2004-2008 James Bursa <bursa@users.sourceforge.net>
+ * Copyright 2004-2007 John M Bell <jmb202@ecs.soton.ac.uk>
+ * Copyright 2004-2007 Richard Wilson <info@tinct.net>
+ * Copyright 2005-2006 Adrian Lees <adrianl@users.sourceforge.net>
+ * Copyright 2006 Rob Kendrick <rjek@netsurf-browser.org>
+ * Copyright 2008 Michael Drake <tlsa@netsurf-browser.org>
+ * Copyright 2009 Paul Blokus <paul_pl@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 &lt;body&gt;. 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,
+ &current_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,
+ &current_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, &current_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 000000000..2a849e853
--- /dev/null
+++ b/content/handlers/html/html_redraw_border.c
@@ -0,0 +1,928 @@
+/*
+ * Copyright 2017 Vincent Sanders <vince@netsurf-browser.org>
+ *
+ * This file is part of NetSurf, http://www.netsurf-browser.org/
+ *
+ * NetSurf is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * NetSurf is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ *
+ * 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 000000000..e18a0caa0
--- /dev/null
+++ b/content/handlers/html/html_script.c
@@ -0,0 +1,604 @@
+/*
+ * Copyright 2012 Vincent Sanders <vince@netsurf-browser.org>
+ *
+ * This file is part of NetSurf, http://www.netsurf-browser.org/
+ *
+ * NetSurf is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * NetSurf is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * 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 000000000..5f4dd54b3
--- /dev/null
+++ b/content/handlers/html/imagemap.c
@@ -0,0 +1,804 @@
+/*
+ * Copyright 2004 John M Bell <jmb202@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 000000000..8e189a072
--- /dev/null
+++ b/content/handlers/html/imagemap.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2004 John M Bell <jmb202@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 000000000..6941d6759
--- /dev/null
+++ b/content/handlers/html/layout.c
@@ -0,0 +1,5432 @@
+/*
+ * Copyright 2005 Richard Wilson <info@tinct.net>
+ * Copyright 2006 James Bursa <bursa@users.sourceforge.net>
+ * Copyright 2008 Michael Drake <tlsa@netsurf-browser.org>
+ * Copyright 2003 Phil Mellor <monkeyson@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 &&