From 2a03ea30490892ac52b3da325ab78e1aa888f83e Mon Sep 17 00:00:00 2001 From: Vincent Sanders Date: Thu, 10 May 2018 11:34:26 +0100 Subject: move html and text content handlers where they belong --- content/handlers/html/Makefile | 7 + content/handlers/html/box.c | 1241 +++++++ content/handlers/html/box.h | 369 ++ content/handlers/html/box_construct.c | 3134 ++++++++++++++++ content/handlers/html/box_normalise.c | 1047 ++++++ content/handlers/html/box_textarea.c | 350 ++ content/handlers/html/box_textarea.h | 54 + content/handlers/html/font.c | 163 + content/handlers/html/font.h | 43 + content/handlers/html/form.c | 1895 ++++++++++ content/handlers/html/form_internal.h | 277 ++ content/handlers/html/html.c | 2468 +++++++++++++ content/handlers/html/html.h | 187 + content/handlers/html/html_css.c | 714 ++++ content/handlers/html/html_css_fetcher.c | 325 ++ content/handlers/html/html_forms.c | 579 +++ content/handlers/html/html_interaction.c | 1434 ++++++++ content/handlers/html/html_internal.h | 409 +++ content/handlers/html/html_object.c | 730 ++++ content/handlers/html/html_redraw.c | 1951 ++++++++++ content/handlers/html/html_redraw_border.c | 928 +++++ content/handlers/html/html_script.c | 604 ++++ content/handlers/html/imagemap.c | 804 ++++ content/handlers/html/imagemap.h | 42 + content/handlers/html/layout.c | 5432 ++++++++++++++++++++++++++++ content/handlers/html/layout.h | 45 + content/handlers/html/search.c | 656 ++++ content/handlers/html/search.h | 86 + content/handlers/html/table.c | 1080 ++++++ content/handlers/html/table.h | 39 + 30 files changed, 27093 insertions(+) create mode 100644 content/handlers/html/Makefile create mode 100644 content/handlers/html/box.c create mode 100644 content/handlers/html/box.h create mode 100644 content/handlers/html/box_construct.c create mode 100644 content/handlers/html/box_normalise.c create mode 100644 content/handlers/html/box_textarea.c create mode 100644 content/handlers/html/box_textarea.h create mode 100644 content/handlers/html/font.c create mode 100644 content/handlers/html/font.h create mode 100644 content/handlers/html/form.c create mode 100644 content/handlers/html/form_internal.h create mode 100644 content/handlers/html/html.c create mode 100644 content/handlers/html/html.h create mode 100644 content/handlers/html/html_css.c create mode 100644 content/handlers/html/html_css_fetcher.c create mode 100644 content/handlers/html/html_forms.c create mode 100644 content/handlers/html/html_interaction.c create mode 100644 content/handlers/html/html_internal.h create mode 100644 content/handlers/html/html_object.c create mode 100644 content/handlers/html/html_redraw.c create mode 100644 content/handlers/html/html_redraw_border.c create mode 100644 content/handlers/html/html_script.c create mode 100644 content/handlers/html/imagemap.c create mode 100644 content/handlers/html/imagemap.h create mode 100644 content/handlers/html/layout.c create mode 100644 content/handlers/html/layout.h create mode 100644 content/handlers/html/search.c create mode 100644 content/handlers/html/search.h create mode 100644 content/handlers/html/table.c create mode 100644 content/handlers/html/table.h (limited to 'content/handlers/html') 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 + * Copyright 2003 Phil Mellor + * Copyright 2005 John M Bell + * Copyright 2008 Michael Drake + * + * 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 . + */ + +/** + * \file + * implementation of box tree manipulation. + */ + +#include +#include +#include +#include +#include + +#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 + * Copyright 2003 Phil Mellor + * + * 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 . + */ + +/** + * \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 + *

Example Heading

+ *

Example paragraph with emphasised text etc.

\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 +#include +#include +#include + +#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 + * Copyright 2003 Phil Mellor + * Copyright 2005 John M Bell + * Copyright 2006 Richard Wilson + * Copyright 2008 Michael Drake + * + * 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 . + */ + +/** + * \file + * Implementation of conversion from DOM tree to box tree. + */ + +#include +#include +#include +#include +#include +#include + +#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 + * , so this is different to 0 */ + box->target = TARGET_SELF; + else { + /* 6.16 says that frame names must begin with [a-zA-Z] + * This doesn't match reality, so just take anything */ + box->target = talloc_strdup(content->bctx, + dom_string_data(s)); + if (!box->target) { + dom_string_unref(s); + return false; + } + } + dom_string_unref(s); + } + + return true; +} + + +/** + * Embedded image [13.2]. + */ + +bool box_image(BOX_SPECIAL_PARAMS) +{ + bool ok; + dom_string *s; + dom_exception err; + nsurl *url; + enum css_width_e wtype; + enum css_height_e htype; + css_fixed value = 0; + css_unit wunit = CSS_UNIT_PX; + css_unit hunit = CSS_UNIT_PX; + + if (box->style && ns_computed_display(box->style, + box_is_root(n)) == CSS_DISPLAY_NONE) + return true; + + /* handle alt text */ + err = dom_element_get_attribute(n, corestring_dom_alt, &s); + if (err == DOM_NO_ERR && s != NULL) { + char *alt = squash_whitespace(dom_string_data(s)); + dom_string_unref(s); + if (alt == NULL) + return false; + box->text = talloc_strdup(content->bctx, alt); + free(alt); + if (box->text == NULL) + return false; + box->length = strlen(box->text); + } + + if (nsoption_bool(foreground_images) == false) { + return true; + } + + /* imagemap associated with this image */ + if (!box_get_attribute(n, "usemap", content->bctx, &box->usemap)) + return false; + if (box->usemap && box->usemap[0] == '#') + box->usemap++; + + /* get image URL */ + err = dom_element_get_attribute(n, corestring_dom_src, &s); + if (err != DOM_NO_ERR || s == NULL) + return true; + + if (box_extract_link(content, s, content->base_url, &url) == false) { + dom_string_unref(s); + return false; + } + + dom_string_unref(s); + + if (url == NULL) + return true; + + /* start fetch */ + box->flags |= IS_REPLACED; + ok = html_fetch_object(content, url, box, image_types, + content->base.available_width, 1000, false); + nsurl_unref(url); + + wtype = css_computed_width(box->style, &value, &wunit); + htype = css_computed_height(box->style, &value, &hunit); + + if (wtype == CSS_WIDTH_SET && wunit != CSS_UNIT_PCT && + htype == CSS_HEIGHT_SET && hunit != CSS_UNIT_PCT) { + /* We know the dimensions the image will be shown at before it's + * fetched. */ + box->flags |= REPLACE_DIM; + } + + return ok; +} + + +/** + * Noscript element + */ + +bool box_noscript(BOX_SPECIAL_PARAMS) +{ + /* If scripting is enabled, do not display the contents of noscript */ + if (content->enable_scripting) + *convert_children = false; + + return true; +} + + +/** + * Destructor for object_params, for <object> elements + * + * \param o The object params being destroyed. + * \return 0 to allow talloc to continue destroying the tree. + */ +static int box_object_talloc_destructor(struct object_params *o) +{ + if (o->codebase != NULL) + nsurl_unref(o->codebase); + if (o->classid != NULL) + nsurl_unref(o->classid); + if (o->data != NULL) + nsurl_unref(o->data); + + return 0; +} + +/** + * Generic embedded object [13.3]. + */ + +bool box_object(BOX_SPECIAL_PARAMS) +{ + struct object_params *params; + struct object_param *param; + dom_string *codebase, *classid, *data; + dom_node *c; + dom_exception err; + + if (box->style && ns_computed_display(box->style, + box_is_root(n)) == CSS_DISPLAY_NONE) + return true; + + if (box_get_attribute(n, "usemap", content->bctx, &box->usemap) == + false) + return false; + if (box->usemap && box->usemap[0] == '#') + box->usemap++; + + params = talloc(content->bctx, struct object_params); + if (params == NULL) + return false; + + talloc_set_destructor(params, box_object_talloc_destructor); + + params->data = NULL; + params->type = NULL; + params->codetype = NULL; + params->codebase = NULL; + params->classid = NULL; + params->params = NULL; + + /* codebase, classid, and data are URLs + * (codebase is the base for the other two) */ + err = dom_element_get_attribute(n, corestring_dom_codebase, &codebase); + if (err == DOM_NO_ERR && codebase != NULL) { + if (box_extract_link(content, codebase, content->base_url, + ¶ms->codebase) == false) { + dom_string_unref(codebase); + return false; + } + dom_string_unref(codebase); + } + if (params->codebase == NULL) + params->codebase = nsurl_ref(content->base_url); + + err = dom_element_get_attribute(n, corestring_dom_classid, &classid); + if (err == DOM_NO_ERR && classid != NULL) { + if (box_extract_link(content, classid, + params->codebase, ¶ms->classid) == false) { + dom_string_unref(classid); + return false; + } + dom_string_unref(classid); + } + + err = dom_element_get_attribute(n, corestring_dom_data, &data); + if (err == DOM_NO_ERR && data != NULL) { + if (box_extract_link(content, data, + params->codebase, ¶ms->data) == false) { + dom_string_unref(data); + return false; + } + dom_string_unref(data); + } + + if (params->classid == NULL && params->data == NULL) + /* nothing to embed; ignore */ + return true; + + /* Don't include ourself */ + if (params->classid != NULL && nsurl_compare(content->base_url, + params->classid, NSURL_COMPLETE)) + return true; + + if (params->data != NULL && nsurl_compare(content->base_url, + params->data, NSURL_COMPLETE)) + return true; + + /* codetype and type are MIME types */ + if (box_get_attribute(n, "codetype", params, + ¶ms->codetype) == false) + return false; + if (box_get_attribute(n, "type", params, ¶ms->type) == false) + return false; + + /* classid && !data => classid is used (consult codetype) + * (classid || !classid) && data => data is used (consult type) + * !classid && !data => invalid; ignored */ + + if (params->classid != NULL && params->data == NULL && + params->codetype != NULL) { + lwc_string *icodetype; + lwc_error lerror; + + lerror = lwc_intern_string(params->codetype, + strlen(params->codetype), &icodetype); + if (lerror != lwc_error_ok) + return false; + + if (content_factory_type_from_mime_type(icodetype) == + CONTENT_NONE) { + /* can't handle this MIME type */ + lwc_string_unref(icodetype); + return true; + } + + lwc_string_unref(icodetype); + } + + if (params->data != NULL && params->type != NULL) { + lwc_string *itype; + lwc_error lerror; + + lerror = lwc_intern_string(params->type, strlen(params->type), + &itype); + if (lerror != lwc_error_ok) + return false; + + if (content_factory_type_from_mime_type(itype) == + CONTENT_NONE) { + /* can't handle this MIME type */ + lwc_string_unref(itype); + return true; + } + + lwc_string_unref(itype); + } + + /* add parameters to linked list */ + err = dom_node_get_first_child(n, &c); + if (err != DOM_NO_ERR) + return false; + + while (c != NULL) { + dom_node *next; + dom_node_type type; + + err = dom_node_get_node_type(c, &type); + if (err != DOM_NO_ERR) { + dom_node_unref(c); + return false; + } + + if (type == DOM_ELEMENT_NODE) { + dom_string *name; + + err = dom_node_get_node_name(c, &name); + if (err != DOM_NO_ERR) { + dom_node_unref(c); + return false; + } + + if (!dom_string_caseless_lwc_isequal(name, + corestring_lwc_param)) { + /* The first non-param child is the start of + * the alt html. Therefore, we should break + * out of this loop. */ + dom_node_unref(c); + break; + } + + param = talloc(params, struct object_param); + if (param == NULL) { + dom_node_unref(c); + return false; + } + param->name = NULL; + param->value = NULL; + param->type = NULL; + param->valuetype = NULL; + param->next = NULL; + + if (box_get_attribute(c, "name", param, + ¶m->name) == false) { + dom_node_unref(c); + return false; + } + + if (box_get_attribute(c, "value", param, + ¶m->value) == false) { + dom_node_unref(c); + return false; + } + + if (box_get_attribute(c, "type", param, + ¶m->type) == false) { + dom_node_unref(c); + return false; + } + + if (box_get_attribute(c, "valuetype", param, + ¶m->valuetype) == false) { + dom_node_unref(c); + return false; + } + + if (param->valuetype == NULL) { + param->valuetype = talloc_strdup(param, "data"); + if (param->valuetype == NULL) { + dom_node_unref(c); + return false; + } + } + + param->next = params->params; + params->params = param; + } + + err = dom_node_get_next_sibling(c, &next); + if (err != DOM_NO_ERR) { + dom_node_unref(c); + return false; + } + + dom_node_unref(c); + c = next; + } + + box->object_params = params; + + /* start fetch (MIME type is ok or not specified) */ + box->flags |= IS_REPLACED; + if (!html_fetch_object(content, + params->data ? params->data : params->classid, + box, CONTENT_ANY, content->base.available_width, 1000, + false)) + return false; + + *convert_children = false; + return true; +} + + +/** + * Window subdivision [16.2.1]. + */ + +bool box_frameset(BOX_SPECIAL_PARAMS) +{ + bool ok; + + if (content->frameset) { + NSLOG(netsurf, INFO, "Error: multiple framesets in document."); + /* Don't convert children */ + if (convert_children) + *convert_children = false; + /* And ignore this spurious frameset */ + box->type = BOX_NONE; + return true; + } + + content->frameset = talloc_zero(content->bctx, struct content_html_frames); + if (!content->frameset) + return false; + + ok = box_create_frameset(content->frameset, n, content); + if (ok) + box->type = BOX_NONE; + + if (convert_children) + *convert_children = false; + return ok; +} + + +/** + * Destructor for content_html_frames, for frame elements + * + * \param f The frame params being destroyed. + * \return 0 to allow talloc to continue destroying the tree. + */ +static int box_frames_talloc_destructor(struct content_html_frames *f) +{ + if (f->url != NULL) { + nsurl_unref(f->url); + f->url = NULL; + } + + return 0; +} + + +/** + * Parse a multi-length-list, as defined by HTML 4.01. + * + * \param ds dom string to parse + * \param count updated to number of entries + * \return array of struct box_multi_length, or 0 on memory exhaustion + */ +static struct frame_dimension * +box_parse_multi_lengths(const dom_string *ds, unsigned int *count) +{ + char *end; + unsigned int i, n; + struct frame_dimension *length; + const char *s; + + s = dom_string_data(ds); + + for (i = 0, n = 1; s[i]; i++) + if (s[i] == ',') + n++; + + length = calloc(n, sizeof(struct frame_dimension)); + if (!length) + return NULL; + + for (i = 0; i != n; i++) { + while (ascii_is_space(*s)) { + s++; + } + length[i].value = strtof(s, &end); + if (length[i].value <= 0) { + length[i].value = 1; + } + s = end; + switch (*s) { + case '%': + length[i].unit = FRAME_DIMENSION_PERCENT; + break; + case '*': + length[i].unit = FRAME_DIMENSION_RELATIVE; + break; + default: + length[i].unit = FRAME_DIMENSION_PIXELS; + break; + } + while (*s && *s != ',') { + s++; + } + if (*s == ',') { + s++; + } + } + + *count = n; + return length; +} + + +bool box_create_frameset(struct content_html_frames *f, dom_node *n, + html_content *content) { + unsigned int row, col, index, i; + unsigned int rows = 1, cols = 1; + dom_string *s; + dom_exception err; + nsurl *url; + struct frame_dimension *row_height = 0, *col_width = 0; + dom_node *c, *next; + struct content_html_frames *frame; + bool default_border = true; + colour default_border_colour = 0x000000; + + /* parse rows and columns */ + err = dom_element_get_attribute(n, corestring_dom_rows, &s); + if (err == DOM_NO_ERR && s != NULL) { + row_height = box_parse_multi_lengths(s, &rows); + dom_string_unref(s); + if (row_height == NULL) + return false; + } else { + row_height = calloc(1, sizeof(struct frame_dimension)); + if (row_height == NULL) + return false; + row_height->value = 100; + row_height->unit = FRAME_DIMENSION_PERCENT; + } + + err = dom_element_get_attribute(n, corestring_dom_cols, &s); + if (err == DOM_NO_ERR && s != NULL) { + col_width = box_parse_multi_lengths(s, &cols); + dom_string_unref(s); + if (col_width == NULL) { + free(row_height); + return false; + } + } else { + col_width = calloc(1, sizeof(struct frame_dimension)); + if (col_width == NULL) { + free(row_height); + return false; + } + col_width->value = 100; + col_width->unit = FRAME_DIMENSION_PERCENT; + } + + /* common extension: border="0|1" to control all children */ + err = dom_element_get_attribute(n, corestring_dom_border, &s); + if (err == DOM_NO_ERR && s != NULL) { + if ((dom_string_data(s)[0] == '0') && + (dom_string_data(s)[1] == '\0')) + default_border = false; + dom_string_unref(s); + } + + /* common extension: frameborder="yes|no" to control all children */ + err = dom_element_get_attribute(n, corestring_dom_frameborder, &s); + if (err == DOM_NO_ERR && s != NULL) { + if (dom_string_caseless_lwc_isequal(s, + corestring_lwc_no) == 0) + default_border = false; + dom_string_unref(s); + } + + /* common extension: bordercolor="#RRGGBB|" to control + *all children */ + err = dom_element_get_attribute(n, corestring_dom_bordercolor, &s); + if (err == DOM_NO_ERR && s != NULL) { + css_color color; + + if (nscss_parse_colour(dom_string_data(s), &color)) + default_border_colour = nscss_color_to_ns(color); + + dom_string_unref(s); + } + + /* update frameset and create default children */ + f->cols = cols; + f->rows = rows; + f->scrolling = BW_SCROLLING_NO; + f->children = talloc_array(content->bctx, struct content_html_frames, + (rows * cols)); + + talloc_set_destructor(f->children, box_frames_talloc_destructor); + + for (row = 0; row < rows; row++) { + for (col = 0; col < cols; col++) { + index = (row * cols) + col; + frame = &f->children[index]; + frame->cols = 0; + frame->rows = 0; + frame->width = col_width[col]; + frame->height = row_height[row]; + frame->margin_width = 0; + frame->margin_height = 0; + frame->name = NULL; + frame->url = NULL; + frame->no_resize = false; + frame->scrolling = BW_SCROLLING_AUTO; + frame->border = default_border; + frame->border_colour = default_border_colour; + frame->children = NULL; + } + } + free(col_width); + free(row_height); + + /* create the frameset windows */ + err = dom_node_get_first_child(n, &c); + if (err != DOM_NO_ERR) + return false; + + for (row = 0; c != NULL && row < rows; row++) { + for (col = 0; c != NULL && col < cols; col++) { + while (c != NULL) { + dom_node_type type; + dom_string *name; + + err = dom_node_get_node_type(c, &type); + if (err != DOM_NO_ERR) { + dom_node_unref(c); + return false; + } + + err = dom_node_get_node_name(c, &name); + if (err != DOM_NO_ERR) { + dom_node_unref(c); + return false; + } + + if (type != DOM_ELEMENT_NODE || + (!dom_string_caseless_lwc_isequal( + name, + corestring_lwc_frame) && + !dom_string_caseless_lwc_isequal( + name, + corestring_lwc_frameset + ))) { + err = dom_node_get_next_sibling(c, + &next); + if (err != DOM_NO_ERR) { + dom_string_unref(name); + dom_node_unref(c); + return false; + } + + dom_string_unref(name); + dom_node_unref(c); + c = next; + } else { + /* Got a FRAME or FRAMESET element */ + dom_string_unref(name); + break; + } + } + + if (c == NULL) + break; + + /* get current frame */ + index = (row * cols) + col; + frame = &f->children[index]; + + /* nest framesets */ + err = dom_node_get_node_name(c, &s); + if (err != DOM_NO_ERR) { + dom_node_unref(c); + return false; + } + + if (dom_string_caseless_lwc_isequal(s, + corestring_lwc_frameset)) { + dom_string_unref(s); + frame->border = 0; + if (box_create_frameset(frame, c, + content) == false) { + dom_node_unref(c); + return false; + } + + err = dom_node_get_next_sibling(c, &next); + if (err != DOM_NO_ERR) { + dom_node_unref(c); + return false; + } + + dom_node_unref(c); + c = next; + continue; + } + + dom_string_unref(s); + + /* get frame URL (not required) */ + url = NULL; + err = dom_element_get_attribute(c, corestring_dom_src, &s); + if (err == DOM_NO_ERR && s != NULL) { + box_extract_link(content, s, content->base_url, + &url); + dom_string_unref(s); + } + + /* copy url */ + if (url != NULL) { + /* no self-references */ + if (nsurl_compare(content->base_url, url, + NSURL_COMPLETE) == false) + frame->url = url; + url = NULL; + } + + /* fill in specified values */ + err = dom_element_get_attribute(c, corestring_dom_name, &s); + if (err == DOM_NO_ERR && s != NULL) { + frame->name = talloc_strdup(content->bctx, + dom_string_data(s)); + dom_string_unref(s); + } + + dom_element_has_attribute(c, corestring_dom_noresize, + &frame->no_resize); + + err = dom_element_get_attribute(c, corestring_dom_frameborder, + &s); + if (err == DOM_NO_ERR && s != NULL) { + i = atoi(dom_string_data(s)); + frame->border = (i != 0); + dom_string_unref(s); + } + + err = dom_element_get_attribute(c, corestring_dom_scrolling, &s); + if (err == DOM_NO_ERR && s != NULL) { + if (dom_string_caseless_lwc_isequal(s, + corestring_lwc_yes)) + frame->scrolling = BW_SCROLLING_YES; + else if (dom_string_caseless_lwc_isequal(s, + corestring_lwc_no)) + frame->scrolling = BW_SCROLLING_NO; + dom_string_unref(s); + } + + err = dom_element_get_attribute(c, corestring_dom_marginwidth, + &s); + if (err == DOM_NO_ERR && s != NULL) { + frame->margin_width = atoi(dom_string_data(s)); + dom_string_unref(s); + } + + err = dom_element_get_attribute(c, corestring_dom_marginheight, + &s); + if (err == DOM_NO_ERR && s != NULL) { + frame->margin_height = atoi(dom_string_data(s)); + dom_string_unref(s); + } + + err = dom_element_get_attribute(c, corestring_dom_bordercolor, + &s); + if (err == DOM_NO_ERR && s != NULL) { + css_color color; + + if (nscss_parse_colour(dom_string_data(s), + &color)) + frame->border_colour = + nscss_color_to_ns(color); + + dom_string_unref(s); + } + + /* advance */ + err = dom_node_get_next_sibling(c, &next); + if (err != DOM_NO_ERR) { + dom_node_unref(c); + return false; + } + + dom_node_unref(c); + c = next; + } + } + + /* If the last child wasn't a frame, we still need to unref it */ + if (c != NULL) { + dom_node_unref(c); + } + + return true; +} + + +/** + * Destructor for content_html_iframe, for <iframe> elements + * + * \param f The iframe params being destroyed. + * \return 0 to allow talloc to continue destroying the tree. + */ +static int box_iframes_talloc_destructor(struct content_html_iframe *f) +{ + if (f->url != NULL) { + nsurl_unref(f->url); + f->url = NULL; + } + + return 0; +} + + +/** + * Inline subwindow [16.5]. + */ + +bool box_iframe(BOX_SPECIAL_PARAMS) +{ + nsurl *url; + dom_string *s; + dom_exception err; + struct content_html_iframe *iframe; + int i; + + if (box->style && ns_computed_display(box->style, + box_is_root(n)) == CSS_DISPLAY_NONE) + return true; + + if (box->style && css_computed_visibility(box->style) == + CSS_VISIBILITY_HIDDEN) + /* Don't create iframe discriptors for invisible iframes + * TODO: handle hidden iframes at browser_window generation + * time instead? */ + return true; + + /* get frame URL */ + err = dom_element_get_attribute(n, corestring_dom_src, &s); + if (err != DOM_NO_ERR || s == NULL) + return true; + if (box_extract_link(content, s, content->base_url, &url) == false) { + dom_string_unref(s); + return false; + } + dom_string_unref(s); + if (url == NULL) + return true; + + /* don't include ourself */ + if (nsurl_compare(content->base_url, url, NSURL_COMPLETE)) { + nsurl_unref(url); + return true; + } + + /* create a new iframe */ + iframe = talloc(content->bctx, struct content_html_iframe); + if (iframe == NULL) { + nsurl_unref(url); + return false; + } + + talloc_set_destructor(iframe, box_iframes_talloc_destructor); + + iframe->box = box; + iframe->margin_width = 0; + iframe->margin_height = 0; + iframe->name = NULL; + iframe->url = url; + iframe->scrolling = BW_SCROLLING_AUTO; + iframe->border = true; + + /* Add this iframe to the linked list of iframes */ + iframe->next = content->iframe; + content->iframe = iframe; + + /* fill in specified values */ + err = dom_element_get_attribute(n, corestring_dom_name, &s); + if (err == DOM_NO_ERR && s != NULL) { + iframe->name = talloc_strdup(content->bctx, dom_string_data(s)); + dom_string_unref(s); + } + + err = dom_element_get_attribute(n, corestring_dom_frameborder, &s); + if (err == DOM_NO_ERR && s != NULL) { + i = atoi(dom_string_data(s)); + iframe->border = (i != 0); + dom_string_unref(s); + } + + err = dom_element_get_attribute(n, corestring_dom_bordercolor, &s); + if (err == DOM_NO_ERR && s != NULL) { + css_color color; + + if (nscss_parse_colour(dom_string_data(s), &color)) + iframe->border_colour = nscss_color_to_ns(color); + + dom_string_unref(s); + } + + err = dom_element_get_attribute(n, corestring_dom_scrolling, &s); + if (err == DOM_NO_ERR && s != NULL) { + if (dom_string_caseless_lwc_isequal(s, + corestring_lwc_yes)) + iframe->scrolling = BW_SCROLLING_YES; + else if (dom_string_caseless_lwc_isequal(s, + corestring_lwc_no)) + iframe->scrolling = BW_SCROLLING_NO; + dom_string_unref(s); + } + + err = dom_element_get_attribute(n, corestring_dom_marginwidth, &s); + if (err == DOM_NO_ERR && s != NULL) { + iframe->margin_width = atoi(dom_string_data(s)); + dom_string_unref(s); + } + + err = dom_element_get_attribute(n, corestring_dom_marginheight, &s); + if (err == DOM_NO_ERR && s != NULL) { + iframe->margin_height = atoi(dom_string_data(s)); + dom_string_unref(s); + } + + /* box */ + assert(box->style); + box->flags |= IFRAME; + box->flags |= IS_REPLACED; + + /* Showing iframe, so don't show alternate content */ + if (convert_children) + *convert_children = false; + return true; +} + + +/** + * Helper function for adding textarea widget to box. + * + * This is a load of hacks to ensure boxes replaced with textareas + * can be handled by the layout code. + */ + +static bool box_input_text(html_content *html, struct box *box, + struct dom_node *node) +{ + struct box *inline_container, *inline_box; + + box->type = BOX_INLINE_BLOCK; + + inline_container = box_create(NULL, 0, false, 0, 0, 0, 0, html->bctx); + if (!inline_container) + return false; + inline_container->type = BOX_INLINE_CONTAINER; + inline_box = box_create(NULL, box->style, false, 0, 0, box->title, 0, + html->bctx); + if (!inline_box) + return false; + inline_box->type = BOX_TEXT; + inline_box->text = talloc_strdup(html->bctx, ""); + + box_add_child(inline_container, inline_box); + box_add_child(box, inline_container); + + return box_textarea_create_textarea(html, box, node); +} + + +/** + * Form control [17.4]. + */ + +bool box_input(BOX_SPECIAL_PARAMS) +{ + struct form_control *gadget = NULL; + dom_string *type = NULL; + dom_exception err; + nsurl *url; + nserror error; + + dom_element_get_attribute(n, corestring_dom_type, &type); + + gadget = html_forms_get_control_for_node(content->forms, n); + if (gadget == NULL) + goto no_memory; + box->gadget = gadget; + box->flags |= IS_REPLACED; + gadget->box = box; + gadget->html = content; + + if (type && dom_string_caseless_lwc_isequal(type, + corestring_lwc_password)) { + if (box_input_text(content, box, n) == false) + goto no_memory; + + } else if (type && dom_string_caseless_lwc_isequal(type, + corestring_lwc_file)) { + box->type = BOX_INLINE_BLOCK; + + } else if (type && dom_string_caseless_lwc_isequal(type, + corestring_lwc_hidden)) { + /* no box for hidden inputs */ + box->type = BOX_NONE; + + } else if (type && + (dom_string_caseless_lwc_isequal(type, + corestring_lwc_checkbox) || + dom_string_caseless_lwc_isequal(type, + corestring_lwc_radio))) { + + } else if (type && + (dom_string_caseless_lwc_isequal(type, + corestring_lwc_submit) || + dom_string_caseless_lwc_isequal(type, + corestring_lwc_reset) || + dom_string_caseless_lwc_isequal(type, + corestring_lwc_button))) { + struct box *inline_container, *inline_box; + + if (box_button(n, content, box, 0) == false) + goto no_memory; + + inline_container = box_create(NULL, 0, false, 0, 0, 0, 0, + content->bctx); + if (inline_container == NULL) + goto no_memory; + + inline_container->type = BOX_INLINE_CONTAINER; + + inline_box = box_create(NULL, box->style, false, 0, 0, + box->title, 0, content->bctx); + if (inline_box == NULL) + goto no_memory; + + inline_box->type = BOX_TEXT; + + if (box->gadget->value != NULL) + inline_box->text = talloc_strdup(content->bctx, + box->gadget->value); + else if (box->gadget->type == GADGET_SUBMIT) + inline_box->text = talloc_strdup(content->bctx, + messages_get("Form_Submit")); + else if (box->gadget->type == GADGET_RESET) + inline_box->text = talloc_strdup(content->bctx, + messages_get("Form_Reset")); + else + inline_box->text = talloc_strdup(content->bctx, + "Button"); + + if (inline_box->text == NULL) + goto no_memory; + + inline_box->length = strlen(inline_box->text); + + box_add_child(inline_container, inline_box); + + box_add_child(box, inline_container); + + } else if (type && dom_string_caseless_lwc_isequal(type, + corestring_lwc_image)) { + gadget->type = GADGET_IMAGE; + + if (box->style && ns_computed_display(box->style, + box_is_root(n)) != CSS_DISPLAY_NONE && + nsoption_bool(foreground_images) == true) { + dom_string *s; + + err = dom_element_get_attribute(n, corestring_dom_src, &s); + if (err == DOM_NO_ERR && s != NULL) { + error = nsurl_join(content->base_url, + dom_string_data(s), &url); + dom_string_unref(s); + if (error != NSERROR_OK) + goto no_memory; + + /* if url is equivalent to the parent's url, + * we've got infinite inclusion. stop it here + */ + if (nsurl_compare(url, content->base_url, + NSURL_COMPLETE) == false) { + if (!html_fetch_object(content, url, + box, image_types, + content->base. + available_width, + 1000, false)) { + nsurl_unref(url); + goto no_memory; + } + } + nsurl_unref(url); + } + } + } else { + /* the default type is "text" */ + if (box_input_text(content, box, n) == false) + goto no_memory; + } + + if (type) + dom_string_unref(type); + + *convert_children = false; + return true; + +no_memory: + if (type) + dom_string_unref(type); + + return false; +} + + +/** + * Push button [17.5]. + */ + +bool box_button(BOX_SPECIAL_PARAMS) +{ + struct form_control *gadget; + + gadget = html_forms_get_control_for_node(content->forms, n); + if (!gadget) + return false; + + gadget->html = content; + box->gadget = gadget; + box->flags |= IS_REPLACED; + gadget->box = box; + + box->type = BOX_INLINE_BLOCK; + + /* Just render the contents */ + + return true; +} + + +/** + * Option selector [17.6]. + */ + +bool box_select(BOX_SPECIAL_PARAMS) +{ + struct box *inline_container; + struct box *inline_box; + struct form_control *gadget; + dom_node *c, *c2; + dom_node *next, *next2; + dom_exception err; + + gadget = html_forms_get_control_for_node(content->forms, n); + if (gadget == NULL) + return false; + + gadget->html = content; + err = dom_node_get_first_child(n, &c); + if (err != DOM_NO_ERR) { + form_free_control(gadget); + return false; + } + + while (c != NULL) { + dom_string *name; + + err = dom_node_get_node_name(c, &name); + if (err != DOM_NO_ERR) { + dom_node_unref(c); + form_free_control(gadget); + return false; + } + + if (dom_string_caseless_lwc_isequal(name, + corestring_lwc_option)) { + dom_string_unref(name); + + if (box_select_add_option(gadget, c) == false) { + dom_node_unref(c); + form_free_control(gadget); + return false; + } + } else if (dom_string_caseless_lwc_isequal(name, + corestring_lwc_optgroup)) { + dom_string_unref(name); + + err = dom_node_get_first_child(c, &c2); + if (err != DOM_NO_ERR) { + dom_node_unref(c); + form_free_control(gadget); + return false; + } + + while (c2 != NULL) { + dom_string *c2_name; + + err = dom_node_get_node_name(c2, &c2_name); + if (err != DOM_NO_ERR) { + dom_node_unref(c2); + dom_node_unref(c); + form_free_control(gadget); + return false; + } + + if (dom_string_caseless_lwc_isequal(c2_name, + corestring_lwc_option)) { + dom_string_unref(c2_name); + + if (box_select_add_option(gadget, + c2) == false) { + dom_node_unref(c2); + dom_node_unref(c); + return false; + } + } else { + dom_string_unref(c2_name); + } + + err = dom_node_get_next_sibling(c2, &next2); + if (err != DOM_NO_ERR) { + dom_node_unref(c2); + dom_node_unref(c); + return false; + } + + dom_node_unref(c2); + c2 = next2; + } + } else { + dom_string_unref(name); + } + + err = dom_node_get_next_sibling(c, &next); + if (err != DOM_NO_ERR) { + dom_node_unref(c); + form_free_control(gadget); + return false; + } + + dom_node_unref(c); + c = next; + } + + if (gadget->data.select.num_items == 0) { + /* no options: ignore entire select */ + form_free_control(gadget); + return true; + } + + box->type = BOX_INLINE_BLOCK; + box->gadget = gadget; + box->flags |= IS_REPLACED; + gadget->box = box; + + inline_container = box_create(NULL, 0, false, 0, 0, 0, 0, content->bctx); + if (inline_container == NULL) + goto no_memory; + inline_container->type = BOX_INLINE_CONTAINER; + inline_box = box_create(NULL, box->style, false, 0, 0, box->title, 0, + content->bctx); + if (inline_box == NULL) + goto no_memory; + inline_box->type = BOX_TEXT; + box_add_child(inline_container, inline_box); + box_add_child(box, inline_container); + + if (gadget->data.select.multiple == false && + gadget->data.select.num_selected == 0) { + gadget->data.select.current = gadget->data.select.items; + gadget->data.select.current->initial_selected = + gadget->data.select.current->selected = true; + gadget->data.select.num_selected = 1; + dom_html_option_element_set_selected( + gadget->data.select.current->node, true); + } + + if (gadget->data.select.num_selected == 0) + inline_box->text = talloc_strdup(content->bctx, + messages_get("Form_None")); + else if (gadget->data.select.num_selected == 1) + inline_box->text = talloc_strdup(content->bctx, + gadget->data.select.current->text); + else + inline_box->text = talloc_strdup(content->bctx, + messages_get("Form_Many")); + if (inline_box->text == NULL) + goto no_memory; + + inline_box->length = strlen(inline_box->text); + + *convert_children = false; + return true; + +no_memory: + return false; +} + + +/** + * Add an option to a form select control (helper function for box_select()). + * + * \param control select containing the <option> + * \param n xml element node for <option> + * \return true on success, false on memory exhaustion + */ + +bool box_select_add_option(struct form_control *control, dom_node *n) +{ + char *value = NULL; + char *text = NULL; + char *text_nowrap = NULL; + bool selected; + dom_string *content, *s; + dom_exception err; + + err = dom_node_get_text_content(n, &content); + if (err != DOM_NO_ERR) + return false; + + if (content != NULL) { + text = squash_whitespace(dom_string_data(content)); + dom_string_unref(content); + } else { + text = strdup(""); + } + + if (text == NULL) + goto no_memory; + + err = dom_element_get_attribute(n, corestring_dom_value, &s); + if (err == DOM_NO_ERR && s != NULL) { + value = strdup(dom_string_data(s)); + dom_string_unref(s); + } else { + value = strdup(text); + } + + if (value == NULL) + goto no_memory; + + dom_element_has_attribute(n, corestring_dom_selected, &selected); + + /* replace spaces/TABs with hard spaces to prevent line wrapping */ + text_nowrap = cnv_space2nbsp(text); + if (text_nowrap == NULL) + goto no_memory; + + if (form_add_option(control, value, text_nowrap, selected, n) == false) + goto no_memory; + + free(text); + + return true; + +no_memory: + free(value); + free(text); + free(text_nowrap); + return false; +} + + +/** + * Multi-line text field [17.7]. + */ + +bool box_textarea(BOX_SPECIAL_PARAMS) +{ + /* Get the form_control for the DOM node */ + box->gadget = html_forms_get_control_for_node(content->forms, n); + if (box->gadget == NULL) + return false; + + box->flags |= IS_REPLACED; + box->gadget->html = content; + box->gadget->box = box; + + if (!box_input_text(content, box, n)) + return false; + + *convert_children = false; + return true; +} + + +/** + * Embedded object (not in any HTML specification: + * see http://wp.netscape.com/assist/net_sites/new_html3_prop.html ) + */ + +bool box_embed(BOX_SPECIAL_PARAMS) +{ + struct object_params *params; + struct object_param *param; + dom_namednodemap *attrs; + unsigned long idx; + uint32_t num_attrs; + dom_string *src; + dom_exception err; + + if (box->style && ns_computed_display(box->style, + box_is_root(n)) == CSS_DISPLAY_NONE) + return true; + + params = talloc(content->bctx, struct object_params); + if (params == NULL) + return false; + + talloc_set_destructor(params, box_object_talloc_destructor); + + params->data = NULL; + params->type = NULL; + params->codetype = NULL; + params->codebase = NULL; + params->classid = NULL; + params->params = NULL; + + /* src is a URL */ + err = dom_element_get_attribute(n, corestring_dom_src, &src); + if (err != DOM_NO_ERR || src == NULL) + return true; + if (box_extract_link(content, src, content->base_url, + ¶ms->data) == false) { + dom_string_unref(src); + return false; + } + + dom_string_unref(src); + + if (params->data == NULL) + return true; + + /* Don't include ourself */ + if (nsurl_compare(content->base_url, params->data, NSURL_COMPLETE)) + return true; + + /* add attributes as parameters to linked list */ + err = dom_node_get_attributes(n, &attrs); + if (err != DOM_NO_ERR) + return false; + + err = dom_namednodemap_get_length(attrs, &num_attrs); + if (err != DOM_NO_ERR) { + dom_namednodemap_unref(attrs); + return false; + } + + for (idx = 0; idx < num_attrs; idx++) { + dom_attr *attr; + dom_string *name, *value; + + err = dom_namednodemap_item(attrs, idx, (void *) &attr); + if (err != DOM_NO_ERR) { + dom_namednodemap_unref(attrs); + return false; + } + + err = dom_attr_get_name(attr, &name); + if (err != DOM_NO_ERR) { + dom_namednodemap_unref(attrs); + return false; + } + + if (dom_string_caseless_lwc_isequal(name, corestring_lwc_src)) { + dom_string_unref(name); + continue; + } + + err = dom_attr_get_value(attr, &value); + if (err != DOM_NO_ERR) { + dom_string_unref(name); + dom_namednodemap_unref(attrs); + return false; + } + + param = talloc(content->bctx, struct object_param); + if (param == NULL) { + dom_string_unref(value); + dom_string_unref(name); + dom_namednodemap_unref(attrs); + return false; + } + + param->name = talloc_strdup(content->bctx, dom_string_data(name)); + param->value = talloc_strdup(content->bctx, dom_string_data(value)); + param->type = NULL; + param->valuetype = talloc_strdup(content->bctx, "data"); + param->next = NULL; + + dom_string_unref(value); + dom_string_unref(name); + + if (param->name == NULL || param->value == NULL || + param->valuetype == NULL) { + dom_namednodemap_unref(attrs); + return false; + } + + param->next = params->params; + params->params = param; + } + + dom_namednodemap_unref(attrs); + + box->object_params = params; + + /* start fetch */ + box->flags |= IS_REPLACED; + return html_fetch_object(content, params->data, box, CONTENT_ANY, + content->base.available_width, 1000, false); +} + +/** + * \} + */ + + +/** + * Get the value of an XML element's attribute. + * + * \param n xmlNode, of type XML_ELEMENT_NODE + * \param attribute name of attribute + * \param context talloc context for result buffer + * \param value updated to value, if the attribute is present + * \return true on success, false if attribute present but memory exhausted + * + * Note that returning true does not imply that the attribute was found. If the + * attribute was not found, *value will be unchanged. + */ + +bool box_get_attribute(dom_node *n, const char *attribute, + void *context, char **value) +{ + char *result; + dom_string *attr, *attr_name; + dom_exception err; + + err = dom_string_create_interned((const uint8_t *) attribute, + strlen(attribute), &attr_name); + if (err != DOM_NO_ERR) + return false; + + err = dom_element_get_attribute(n, attr_name, &attr); + if (err != DOM_NO_ERR) { + dom_string_unref(attr_name); + return false; + } + + dom_string_unref(attr_name); + + if (attr != NULL) { + result = talloc_strdup(context, dom_string_data(attr)); + + dom_string_unref(attr); + + if (result == NULL) + return false; + + *value = result; + } + + return true; +} + + +/* exported function documented in html/box.h */ +bool +box_extract_link(const html_content *content, + const dom_string *dsrel, + nsurl *base, + nsurl **result) +{ + char *s, *s1, *apos0 = 0, *apos1 = 0, *quot0 = 0, *quot1 = 0; + unsigned int i, j, end; + nserror error; + const char *rel; + + rel = dom_string_data(dsrel); + + s1 = s = malloc(3 * strlen(rel) + 1); + if (!s) + return false; + + /* copy to s, removing white space and control characters */ + for (i = 0; rel[i] && ascii_is_space(rel[i]); i++) + ; + for (end = strlen(rel); + (end != i) && ascii_is_space(rel[end - 1]); + end--) + ; + for (j = 0; i != end; i++) { + if ((unsigned char) rel[i] < 0x20) { + ; /* skip control characters */ + } else if (rel[i] == ' ') { + s[j++] = '%'; + s[j++] = '2'; + s[j++] = '0'; + } else { + s[j++] = rel[i]; + } + } + s[j] = 0; + + if (content->enable_scripting == false) { + /* extract first quoted string out of "javascript:" link */ + if (strncmp(s, "javascript:", 11) == 0) { + apos0 = strchr(s, '\''); + if (apos0) + apos1 = strchr(apos0 + 1, '\''); + quot0 = strchr(s, '"'); + if (quot0) + quot1 = strchr(quot0 + 1, '"'); + if (apos0 && apos1 && + (!quot0 || !quot1 || apos0 < quot0)) { + *apos1 = 0; + s1 = apos0 + 1; + } else if (quot0 && quot1) { + *quot1 = 0; + s1 = quot0 + 1; + } + } + } + + /* construct absolute URL */ + error = nsurl_join(base, s1, result); + free(s); + if (error != NSERROR_OK) { + *result = NULL; + return false; + } + + return true; +} diff --git a/content/handlers/html/box_normalise.c b/content/handlers/html/box_normalise.c new file mode 100644 index 000000000..7155cb722 --- /dev/null +++ b/content/handlers/html/box_normalise.c @@ -0,0 +1,1047 @@ +/* + * Copyright 2005 James Bursa + * Copyright 2003 Phil Mellor + * Copyright 2005 John M Bell + * Copyright 2004 Kevin Bagust + * + * 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 . + */ + +/** + * \file + * Box tree normalisation (implementation). + */ + +#include +#include +#include + +#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 + * + * 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 . + */ + +/** + * \file + * Box tree treeview box replacement (implementation). + */ + +#include + +#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 + * + * 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 . + */ + +/** + * \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 + * + * 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 . + */ + +/** + * \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 + * + * 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 . + */ + +/** + * \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 + * Copyright 2003 Phil Mellor + * Copyright 2004 John Tytgat + * Copyright 2005-9 John-Mark Bell + * Copyright 2009 Paul Blokus + * Copyright 2010 Michael Drake + * + * 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 . + */ + +/** + * \file + * Form handling functions (implementation). + */ + +#include +#include +#include +#include +#include +#include + +#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 + * + * 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 . + */ + +/** + * \file + * Interface to form handling functions internal to HTML content handler. + */ + +#ifndef NETSURF_HTML_FORM_INTERNAL_H +#define NETSURF_HTML_FORM_INTERNAL_H + +#include + +#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 + * Copyright 2010 Michael Drake + * + * 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 . + */ + +/** + * \file + * Implementation of HTML content handling. + */ + +#include +#include +#include +#include +#include +#include + +#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 + * + * 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 . + */ + +/** + * \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 + +#include +#include + +#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 + * + * 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 . + */ + +/** + * \file + * Processing for html content css operations. + */ + +#include +#include +#include +#include +#include +#include + +#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= */ + 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 + * Copyright 2013 John-Mark Bell + * + * 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 . + */ + +/** + * \file + * HTML fetcher for CSS objects + */ + +#include +#include +#include +#include +#include +#include + +#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: + * 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 + * + * 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 . + */ + +/** + * \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 + * Copyright 2006 Richard Wilson + * Copyright 2008 Michael Drake + * Copyright 2009 Paul Blokus + * + * 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 . + */ + +/** + * \file + * implementation of user interaction with a CONTENT_HTML. + */ + +#include +#include + +#include + +#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 + * + * 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 . + */ + +/** + * \file + * Private data for text/html content. + */ + +#ifndef NETSURF_HTML_HTML_INTERNAL_H +#define NETSURF_HTML_HTML_INTERNAL_H + +#include + +#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 + * + * 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 . + */ + +/** + * \file + * Processing for html content object operations. + */ + +#include +#include +#include +#include +#include +#include +#include + +#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 + * Copyright 2004-2007 John M Bell + * Copyright 2004-2007 Richard Wilson + * Copyright 2005-2006 Adrian Lees + * Copyright 2006 Rob Kendrick + * Copyright 2008 Michael Drake + * Copyright 2009 Paul Blokus + * + * 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 . + */ + +/** + * \file + * + * Redrawing CONTENT_HTML implementation. + */ + +#include "utils/config.h" +#include +#include +#include +#include +#include +#include + +#include "utils/log.h" +#include "utils/messages.h" +#include "utils/utils.h" +#include "utils/nsoption.h" +#include "netsurf/content.h" +#include "netsurf/browser_window.h" +#include "netsurf/plotters.h" +#include "netsurf/bitmap.h" +#include "netsurf/layout.h" +#include "content/content_protected.h" +#include "css/utils.h" +#include "desktop/selection.h" +#include "desktop/print.h" +#include "desktop/scrollbar.h" +#include "desktop/textarea.h" +#include "desktop/gui_internal.h" + +#include "html/box.h" +#include "html/font.h" +#include "html/form_internal.h" +#include "html/html_internal.h" +#include "html/layout.h" +#include "html/search.h" + + +bool html_redraw_debug = false; + +/** + * Determine if a box has a background that needs drawing + * + * \param box Box to consider + * \return True if box has a background, false otherwise. + */ +static bool html_redraw_box_has_background(struct box *box) +{ + if (box->background != NULL) + return true; + + if (box->style != NULL) { + css_color colour; + + css_computed_background_color(box->style, &colour); + + if (nscss_color_is_transparent(colour) == false) + return true; + } + + return false; +} + +/** + * Find the background box for a box + * + * \param box Box to find background box for + * \return Pointer to background box, or NULL if there is none + */ +static struct box *html_redraw_find_bg_box(struct box *box) +{ + /* Thanks to backwards compatibility, CSS defines the following: + * + * + If the box is for the root element and it has a background, + * use that (and then process the body box with no special case) + * + If the box is for the root element and it has no background, + * then use the background (if any) from the body element as if + * it were specified on the root. Then, when the box for the body + * element is processed, ignore the background. + * + For any other box, just use its own styling. + */ + if (box->parent == NULL) { + /* Root box */ + if (html_redraw_box_has_background(box)) + return box; + + /* No background on root box: consider body box, if any */ + if (box->children != NULL) { + if (html_redraw_box_has_background(box->children)) + return box->children; + } + } else if (box->parent != NULL && box->parent->parent == NULL) { + /* Body box: only render background if root has its own */ + if (html_redraw_box_has_background(box) && + html_redraw_box_has_background(box->parent)) + return box; + } else { + /* Any other box */ + if (html_redraw_box_has_background(box)) + return box; + } + + return NULL; +} + +/** + * Redraw a short text string, complete with highlighting + * (for selection/search) + * + * \param utf8_text pointer to UTF-8 text string + * \param utf8_len length of string, in bytes + * \param offset byte offset within textual representation + * \param space width of space that follows string (0 = no space) + * \param fstyle text style to use (pass text size unscaled) + * \param x x ordinate at which to plot text + * \param y y ordinate at which to plot text + * \param clip pointer to current clip rectangle + * \param height height of text string + * \param scale current display scale (1.0 = 100%) + * \param excluded exclude this text string from the selection + * \param c Content being redrawn. + * \param sel Selection context + * \param search Search context + * \param ctx current redraw context + * \return true iff successful and redraw should proceed + */ + +static bool +text_redraw(const char *utf8_text, + size_t utf8_len, + size_t offset, + int space, + const plot_font_style_t *fstyle, + int x, + int y, + const struct rect *clip, + int height, + float scale, + bool excluded, + struct content *c, + const struct selection *sel, + struct search_context *search, + const struct redraw_context *ctx) +{ + bool highlighted = false; + plot_font_style_t plot_fstyle = *fstyle; + nserror res; + + /* Need scaled text size to pass to plotters */ + plot_fstyle.size *= scale; + + /* is this box part of a selection? */ + if (!excluded && ctx->interactive == true) { + unsigned len = utf8_len + (space ? 1 : 0); + unsigned start_idx; + unsigned end_idx; + + /* first try the browser window's current selection */ + if (selection_defined(sel) && selection_highlighted(sel, + offset, offset + len, + &start_idx, &end_idx)) { + highlighted = true; + } + + /* what about the current search operation, if any? */ + if (!highlighted && (search != NULL) && + search_term_highlighted(c, + offset, offset + len, + &start_idx, &end_idx, + search)) { + highlighted = true; + } + + /* \todo make search terms visible within selected text */ + if (highlighted) { + struct rect r; + unsigned endtxt_idx = end_idx; + bool clip_changed = false; + bool text_visible = true; + int startx, endx; + plot_style_t pstyle_fill_hback = *plot_style_fill_white; + plot_font_style_t fstyle_hback = plot_fstyle; + + if (end_idx > utf8_len) { + /* adjust for trailing space, not present in + * utf8_text */ + assert(end_idx == utf8_len + 1); + endtxt_idx = utf8_len; + } + + res = guit->layout->width(fstyle, + utf8_text, start_idx, + &startx); + if (res != NSERROR_OK) { + startx = 0; + } + + res = guit->layout->width(fstyle, + utf8_text, endtxt_idx, + &endx); + if (res != NSERROR_OK) { + endx = 0; + } + + /* is there a trailing space that should be highlighted + * as well? */ + if (end_idx > utf8_len) { + endx += space; + } + + if (scale != 1.0) { + startx *= scale; + endx *= scale; + } + + /* draw any text preceding highlighted portion */ + if ((start_idx > 0) && + (ctx->plot->text(ctx, + &plot_fstyle, + x, + y + (int)(height * 0.75 * scale), + utf8_text, + start_idx) != NSERROR_OK)) + return false; + + pstyle_fill_hback.fill_colour = fstyle->foreground; + + /* highlighted portion */ + r.x0 = x + startx; + r.y0 = y; + r.x1 = x + endx; + r.y1 = y + height * scale; + res = ctx->plot->rectangle(ctx, &pstyle_fill_hback, &r); + if (res != NSERROR_OK) { + return false; + } + + if (start_idx > 0) { + int px0 = max(x + startx, clip->x0); + int px1 = min(x + endx, clip->x1); + + if (px0 < px1) { + r.x0 = px0; + r.y0 = clip->y0; + r.x1 = px1; + r.y1 = clip->y1; + res = ctx->plot->clip(ctx, &r); + if (res != NSERROR_OK) { + return false; + } + + clip_changed = true; + } else { + text_visible = false; + } + } + + fstyle_hback.background = + pstyle_fill_hback.fill_colour; + fstyle_hback.foreground = colour_to_bw_furthest( + pstyle_fill_hback.fill_colour); + + if (text_visible && + (ctx->plot->text(ctx, + &fstyle_hback, + x, + y + (int)(height * 0.75 * scale), + utf8_text, + endtxt_idx) != NSERROR_OK)) { + return false; + } + + /* draw any text succeeding highlighted portion */ + if (endtxt_idx < utf8_len) { + int px0 = max(x + endx, clip->x0); + if (px0 < clip->x1) { + + r.x0 = px0; + r.y0 = clip->y0; + r.x1 = clip->x1; + r.y1 = clip->y1; + res = ctx->plot->clip(ctx, &r); + if (res != NSERROR_OK) { + return false; + } + + clip_changed = true; + + res = ctx->plot->text(ctx, + &plot_fstyle, + x, + y + (int)(height * 0.75 * scale), + utf8_text, + utf8_len); + if (res != NSERROR_OK) { + return false; + } + } + } + + if (clip_changed && + (ctx->plot->clip(ctx, clip) != NSERROR_OK)) { + return false; + } + } + } + + if (!highlighted) { + res = ctx->plot->text(ctx, + &plot_fstyle, + x, + y + (int) (height * 0.75 * scale), + utf8_text, + utf8_len); + if (res != NSERROR_OK) { + return false; + } + } + return true; +} + + +/** + * Plot a checkbox. + * + * \param x left coordinate + * \param y top coordinate + * \param width dimensions of checkbox + * \param height dimensions of checkbox + * \param selected the checkbox is selected + * \param ctx current redraw context + * \return true if successful, false otherwise + */ + +static bool html_redraw_checkbox(int x, int y, int width, int height, + bool selected, const struct redraw_context *ctx) +{ + double z; + nserror res; + struct rect rect; + + z = width * 0.15; + if (z == 0) { + z = 1; + } + + rect.x0 = x; + rect.y0 = y ; + rect.x1 = x + width; + rect.y1 = y + height; + res = ctx->plot->rectangle(ctx, plot_style_fill_wbasec, &rect); + if (res != NSERROR_OK) { + return false; + } + + /* dark line across top */ + rect.y1 = y; + res = ctx->plot->line(ctx, plot_style_stroke_darkwbasec, &rect); + if (res != NSERROR_OK) { + return false; + } + + /* dark line across left */ + rect.x1 = x; + rect.y1 = y + height; + res = ctx->plot->line(ctx, plot_style_stroke_darkwbasec, &rect); + if (res != NSERROR_OK) { + return false; + } + + /* light line across right */ + rect.x0 = x + width; + rect.x1 = x + width; + res = ctx->plot->line(ctx, plot_style_stroke_lightwbasec, &rect); + if (res != NSERROR_OK) { + return false; + } + + /* light line across bottom */ + rect.x0 = x; + rect.y0 = y + height; + res = ctx->plot->line(ctx, plot_style_stroke_lightwbasec, &rect); + if (res != NSERROR_OK) { + return false; + } + + if (selected) { + if (width < 12 || height < 12) { + /* render a solid box instead of a tick */ + rect.x0 = x + z + z; + rect.y0 = y + z + z; + rect.x1 = x + width - z; + rect.y1 = y + height - z; + res = ctx->plot->rectangle(ctx, plot_style_fill_wblobc, &rect); + if (res != NSERROR_OK) { + return false; + } + } else { + /* render a tick, as it'll fit comfortably */ + rect.x0 = x + width - z; + rect.y0 = y + z; + rect.x1 = x + (z * 3); + rect.y1 = y + height - z; + res = ctx->plot->line(ctx, plot_style_stroke_wblobc, &rect); + if (res != NSERROR_OK) { + return false; + } + + rect.x0 = x + (z * 3); + rect.y0 = y + height - z; + rect.x1 = x + z + z; + rect.y1 = y + (height / 2); + res = ctx->plot->line(ctx, plot_style_stroke_wblobc, &rect); + if (res != NSERROR_OK) { + return false; + } + } + } + return true; +} + + +/** + * Plot a radio icon. + * + * \param x left coordinate + * \param y top coordinate + * \param width dimensions of radio icon + * \param height dimensions of radio icon + * \param selected the radio icon is selected + * \param ctx current redraw context + * \return true if successful, false otherwise + */ +static bool html_redraw_radio(int x, int y, int width, int height, + bool selected, const struct redraw_context *ctx) +{ + nserror res; + + /* plot background of radio button */ + res = ctx->plot->disc(ctx, + plot_style_fill_wbasec, + x + width * 0.5, + y + height * 0.5, + width * 0.5 - 1); + if (res != NSERROR_OK) { + return false; + } + + /* plot dark arc */ + res = ctx->plot->arc(ctx, + plot_style_fill_darkwbasec, + x + width * 0.5, + y + height * 0.5, + width * 0.5 - 1, + 45, + 225); + if (res != NSERROR_OK) { + return false; + } + + /* plot light arc */ + res = ctx->plot->arc(ctx, + plot_style_fill_lightwbasec, + x + width * 0.5, + y + height * 0.5, + width * 0.5 - 1, + 225, + 45); + if (res != NSERROR_OK) { + return false; + } + + if (selected) { + /* plot selection blob */ + res = ctx->plot->disc(ctx, + plot_style_fill_wblobc, + x + width * 0.5, + y + height * 0.5, + width * 0.3 - 1); + if (res != NSERROR_OK) { + return false; + } + } + + return true; +} + + +/** + * Plot a file upload input. + * + * \param x left coordinate + * \param y top coordinate + * \param width dimensions of input + * \param height dimensions of input + * \param box box of input + * \param scale scale for redraw + * \param background_colour current background colour + * \param len_ctx Length conversion context + * \param ctx current redraw context + * \return true if successful, false otherwise + */ + +static bool html_redraw_file(int x, int y, int width, int height, + struct box *box, float scale, colour background_colour, + const nscss_len_ctx *len_ctx, + const struct redraw_context *ctx) +{ + int text_width; + const char *text; + size_t length; + plot_font_style_t fstyle; + nserror res; + + font_plot_style_from_css(len_ctx, box->style, &fstyle); + fstyle.background = background_colour; + + if (box->gadget->value) { + text = box->gadget->value; + } else { + text = messages_get("Form_Drop"); + } + length = strlen(text); + + res = guit->layout->width(&fstyle, text, length, &text_width); + if (res != NSERROR_OK) { + return false; + } + text_width *= scale; + if (width < text_width + 8) { + x = x + width - text_width - 4; + } else { + x = x + 4; + } + + res = ctx->plot->text(ctx, &fstyle, x, y + height * 0.75, text, length); + if (res != NSERROR_OK) { + return false; + } + return true; +} + + +/** + * Plot background images. + * + * The reason for the presence of \a background is the backwards compatibility + * mess that is backgrounds on <body>. The background will be drawn relative + * to \a box, using the background information contained within \a background. + * + * \param x coordinate of box + * \param y coordinate of box + * \param box box to draw background image of + * \param scale scale for redraw + * \param clip current clip rectangle + * \param background_colour current background colour + * \param background box containing background details (usually \a box) + * \param len_ctx Length conversion context + * \param ctx current redraw context + * \return true if successful, false otherwise + */ + +static bool html_redraw_background(int x, int y, struct box *box, float scale, + const struct rect *clip, colour *background_colour, + struct box *background, + const nscss_len_ctx *len_ctx, + const struct redraw_context *ctx) +{ + bool repeat_x = false; + bool repeat_y = false; + bool plot_colour = true; + bool plot_content; + bool clip_to_children = false; + struct box *clip_box = box; + int ox = x, oy = y; + int width, height; + css_fixed hpos = 0, vpos = 0; + css_unit hunit = CSS_UNIT_PX, vunit = CSS_UNIT_PX; + struct box *parent; + struct rect r = *clip; + css_color bgcol; + plot_style_t pstyle_fill_bg = { + .fill_type = PLOT_OP_TYPE_SOLID, + .fill_colour = *background_colour, + }; + nserror res; + + if (ctx->background_images == false) + return true; + + plot_content = (background->background != NULL); + + if (plot_content) { + if (!box->parent) { + /* Root element, special case: + * background origin calc. is based on margin box */ + x -= box->margin[LEFT] * scale; + y -= box->margin[TOP] * scale; + width = box->margin[LEFT] + box->padding[LEFT] + + box->width + box->padding[RIGHT] + + box->margin[RIGHT]; + height = box->margin[TOP] + box->padding[TOP] + + box->height + box->padding[BOTTOM] + + box->margin[BOTTOM]; + } else { + width = box->padding[LEFT] + box->width + + box->padding[RIGHT]; + height = box->padding[TOP] + box->height + + box->padding[BOTTOM]; + } + /* handle background-repeat */ + switch (css_computed_background_repeat(background->style)) { + case CSS_BACKGROUND_REPEAT_REPEAT: + repeat_x = repeat_y = true; + /* optimisation: only plot the colour if + * bitmap is not opaque */ + plot_colour = !content_get_opaque(background->background); + break; + + case CSS_BACKGROUND_REPEAT_REPEAT_X: + repeat_x = true; + break; + + case CSS_BACKGROUND_REPEAT_REPEAT_Y: + repeat_y = true; + break; + + case CSS_BACKGROUND_REPEAT_NO_REPEAT: + break; + + default: + break; + } + + /* handle background-position */ + css_computed_background_position(background->style, + &hpos, &hunit, &vpos, &vunit); + if (hunit == CSS_UNIT_PCT) { + x += (width - + content_get_width(background->background)) * + scale * FIXTOFLT(hpos) / 100.; + } else { + x += (int) (FIXTOFLT(nscss_len2px(len_ctx, hpos, hunit, + background->style)) * scale); + } + + if (vunit == CSS_UNIT_PCT) { + y += (height - + content_get_height(background->background)) * + scale * FIXTOFLT(vpos) / 100.; + } else { + y += (int) (FIXTOFLT(nscss_len2px(len_ctx, vpos, vunit, + background->style)) * scale); + } + } + + /* special case for table rows as their background needs + * to be clipped to all the cells */ + if (box->type == BOX_TABLE_ROW) { + css_fixed h = 0, v = 0; + css_unit hu = CSS_UNIT_PX, vu = CSS_UNIT_PX; + + for (parent = box->parent; + ((parent) && (parent->type != BOX_TABLE)); + parent = parent->parent); + assert(parent && (parent->style)); + + css_computed_border_spacing(parent->style, &h, &hu, &v, &vu); + + clip_to_children = (h > 0) || (v > 0); + + if (clip_to_children) + clip_box = box->children; + } + + for (; clip_box; clip_box = clip_box->next) { + /* clip to child boxes if needed */ + if (clip_to_children) { + assert(clip_box->type == BOX_TABLE_CELL); + + /* update clip.* to the child cell */ + r.x0 = ox + (clip_box->x * scale); + r.y0 = oy + (clip_box->y * scale); + r.x1 = r.x0 + (clip_box->padding[LEFT] + + clip_box->width + + clip_box->padding[RIGHT]) * scale; + r.y1 = r.y0 + (clip_box->padding[TOP] + + clip_box->height + + clip_box->padding[BOTTOM]) * scale; + + if (r.x0 < clip->x0) r.x0 = clip->x0; + if (r.y0 < clip->y0) r.y0 = clip->y0; + if (r.x1 > clip->x1) r.x1 = clip->x1; + if (r.y1 > clip->y1) r.y1 = clip->y1; + + css_computed_background_color(clip_box->style, &bgcol); + + /* attributes override */ + /* 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 rows being clipped to child boxes loop */ + if (!clip_to_children) + return true; + } + return true; +} + + +/** + * Plot an inline's background and/or background image. + * + * \param x coordinate of box + * \param y coordinate of box + * \param box BOX_INLINE which created the background + * \param scale scale for redraw + * \param clip coordinates of clip rectangle + * \param b coordinates of border edge rectangle + * \param first true if this is the first rectangle associated with the inline + * \param last true if this is the last rectangle associated with the inline + * \param background_colour updated to current background colour if plotted + * \param len_ctx Length conversion context + * \param ctx current redraw context + * \return true if successful, false otherwise + */ + +static bool html_redraw_inline_background(int x, int y, struct box *box, + float scale, const struct rect *clip, struct rect b, + bool first, bool last, colour *background_colour, + const nscss_len_ctx *len_ctx, + const struct redraw_context *ctx) +{ + struct rect r = *clip; + bool repeat_x = false; + bool repeat_y = false; + bool plot_colour = true; + bool plot_content; + css_fixed hpos = 0, vpos = 0; + css_unit hunit = CSS_UNIT_PX, vunit = CSS_UNIT_PX; + css_color bgcol; + plot_style_t pstyle_fill_bg = { + .fill_type = PLOT_OP_TYPE_SOLID, + .fill_colour = *background_colour, + }; + nserror res; + + plot_content = (box->background != NULL); + + if (html_redraw_printing && nsoption_bool(remove_backgrounds)) + return true; + + if (plot_content) { + /* handle background-repeat */ + switch (css_computed_background_repeat(box->style)) { + case CSS_BACKGROUND_REPEAT_REPEAT: + repeat_x = repeat_y = true; + /* optimisation: only plot the colour if + * bitmap is not opaque + */ + plot_colour = !content_get_opaque(box->background); + break; + + case CSS_BACKGROUND_REPEAT_REPEAT_X: + repeat_x = true; + break; + + case CSS_BACKGROUND_REPEAT_REPEAT_Y: + repeat_y = true; + break; + + case CSS_BACKGROUND_REPEAT_NO_REPEAT: + break; + + default: + break; + } + + /* handle background-position */ + css_computed_background_position(box->style, + &hpos, &hunit, &vpos, &vunit); + if (hunit == CSS_UNIT_PCT) { + x += (b.x1 - b.x0 - + content_get_width(box->background) * + scale) * FIXTOFLT(hpos) / 100.; + + if (!repeat_x && ((hpos < 2 && !first) || + (hpos > 98 && !last))){ + plot_content = false; + } + } else { + x += (int) (FIXTOFLT(nscss_len2px(len_ctx, hpos, hunit, + box->style)) * scale); + } + + if (vunit == CSS_UNIT_PCT) { + y += (b.y1 - b.y0 - + content_get_height(box->background) * + scale) * FIXTOFLT(vpos) / 100.; + } else { + y += (int) (FIXTOFLT(nscss_len2px(len_ctx, vpos, vunit, + box->style)) * scale); + } + } + + /* plot the background colour */ + css_computed_background_color(box->style, &bgcol); + + if (nscss_color_is_transparent(bgcol) == false) { + *background_colour = nscss_color_to_ns(bgcol); + pstyle_fill_bg.fill_colour = *background_colour; + + if (plot_colour) { + res = ctx->plot->rectangle(ctx, &pstyle_fill_bg, &r); + if (res != NSERROR_OK) { + return false; + } + } + } + /* and plot the image */ + if (plot_content) { + int width = content_get_width(box->background); + int height = content_get_height(box->background); + + if (!repeat_x) { + if (r.x0 < x) + r.x0 = x; + if (r.x1 > x + width * scale) + r.x1 = x + width * scale; + } + if (!repeat_y) { + if (r.y0 < y) + r.y0 = y; + if (r.y1 > y + height * scale) + r.y1 = y + height * scale; + } + /* valid clipping rectangles only */ + if ((r.x0 < r.x1) && (r.y0 < r.y1)) { + struct content_redraw_data bg_data; + + res = ctx->plot->clip(ctx, &r); + if (res != NSERROR_OK) { + return false; + } + + bg_data.x = x; + bg_data.y = y; + bg_data.width = ceilf(width * scale); + bg_data.height = ceilf(height * scale); + bg_data.background_colour = *background_colour; + bg_data.scale = scale; + bg_data.repeat_x = repeat_x; + bg_data.repeat_y = repeat_y; + + /* We just continue if redraw fails */ + content_redraw(box->background, &bg_data, &r, ctx); + } + } + + return true; +} + + +/** + * Plot text decoration for an inline box. + * + * \param box box to plot decorations for, of type BOX_INLINE + * \param x x coordinate of parent of box + * \param y y coordinate of parent of box + * \param scale scale for redraw + * \param colour colour for decorations + * \param ratio position of line as a ratio of line height + * \param ctx current redraw context + * \return true if successful, false otherwise + */ + +static bool +html_redraw_text_decoration_inline(struct box *box, + int x, int y, + float scale, + colour colour, + float ratio, + const struct redraw_context *ctx) +{ + struct box *c; + plot_style_t plot_style_box = { + .stroke_type = PLOT_OP_TYPE_SOLID, + .stroke_colour = colour, + }; + nserror res; + struct rect rect; + + for (c = box->next; + c && c != box->inline_end; + c = c->next) { + if (c->type != BOX_TEXT) { + continue; + } + rect.x0 = (x + c->x) * scale; + rect.y0 = (y + c->y + c->height * ratio) * scale; + rect.x1 = (x + c->x + c->width) * scale; + rect.y1 = (y + c->y + c->height * ratio) * scale; + res = ctx->plot->line(ctx, &plot_style_box, &rect); + if (res != NSERROR_OK) { + return false; + } + } + return true; +} + + +/** + * Plot text decoration for an non-inline box. + * + * \param box box to plot decorations for, of type other than BOX_INLINE + * \param x x coordinate of box + * \param y y coordinate of box + * \param scale scale for redraw + * \param colour colour for decorations + * \param ratio position of line as a ratio of line height + * \param ctx current redraw context + * \return true if successful, false otherwise + */ + +static bool +html_redraw_text_decoration_block(struct box *box, + int x, int y, + float scale, + colour colour, + float ratio, + const struct redraw_context *ctx) +{ + struct box *c; + plot_style_t plot_style_box = { + .stroke_type = PLOT_OP_TYPE_SOLID, + .stroke_colour = colour, + }; + nserror res; + struct rect rect; + + /* draw through text descendants */ + for (c = box->children; c; c = c->next) { + if (c->type == BOX_TEXT) { + rect.x0 = (x + c->x) * scale; + rect.y0 = (y + c->y + c->height * ratio) * scale; + rect.x1 = (x + c->x + c->width) * scale; + rect.y1 = (y + c->y + c->height * ratio) * scale; + res = ctx->plot->line(ctx, &plot_style_box, &rect); + if (res != NSERROR_OK) { + return false; + } + } else if ((c->type == BOX_INLINE_CONTAINER) || (c->type == BOX_BLOCK)) { + if (!html_redraw_text_decoration_block(c, + x + c->x, y + c->y, + scale, colour, ratio, ctx)) + return false; + } + } + return true; +} + + +/** + * Plot text decoration for a box. + * + * \param box box to plot decorations for + * \param x_parent x coordinate of parent of box + * \param y_parent y coordinate of parent of box + * \param scale scale for redraw + * \param background_colour current background colour + * \param ctx current redraw context + * \return true if successful, false otherwise + */ + +static bool html_redraw_text_decoration(struct box *box, + int x_parent, int y_parent, float scale, + colour background_colour, const struct redraw_context *ctx) +{ + static const enum css_text_decoration_e decoration[] = { + CSS_TEXT_DECORATION_UNDERLINE, CSS_TEXT_DECORATION_OVERLINE, + CSS_TEXT_DECORATION_LINE_THROUGH }; + static const float line_ratio[] = { 0.9, 0.1, 0.5 }; + colour fgcol; + unsigned int i; + css_color col; + + css_computed_color(box->style, &col); + fgcol = nscss_color_to_ns(col); + + /* antialias colour for under/overline */ + if (html_redraw_printing == false) + fgcol = blend_colour(background_colour, fgcol); + + if (box->type == BOX_INLINE) { + if (!box->inline_end) + return true; + for (i = 0; i != NOF_ELEMENTS(decoration); i++) + if (css_computed_text_decoration(box->style) & + decoration[i]) + if (!html_redraw_text_decoration_inline(box, + x_parent, y_parent, scale, + fgcol, line_ratio[i], ctx)) + return false; + } else { + for (i = 0; i != NOF_ELEMENTS(decoration); i++) + if (css_computed_text_decoration(box->style) & + decoration[i]) + if (!html_redraw_text_decoration_block(box, + x_parent + box->x, + y_parent + box->y, + scale, + fgcol, line_ratio[i], ctx)) + return false; + } + + return true; +} + + +/** + * Redraw the text content of a box, possibly partially highlighted + * because the text has been selected, or matches a search operation. + * + * \param html The html content to redraw text within. + * \param box box with text content + * \param x x co-ord of box + * \param y y co-ord of box + * \param clip current clip rectangle + * \param scale current scale setting (1.0 = 100%) + * \param current_background_color + * \param ctx current redraw context + * \return true iff successful and redraw should proceed + */ + +static bool html_redraw_text_box(const html_content *html, struct box *box, + int x, int y, const struct rect *clip, float scale, + colour current_background_color, + const struct redraw_context *ctx) +{ + bool excluded = (box->object != NULL); + plot_font_style_t fstyle; + + font_plot_style_from_css(&html->len_ctx, box->style, &fstyle); + fstyle.background = current_background_color; + + if (!text_redraw(box->text, box->length, box->byte_offset, + box->space, &fstyle, x, y, + clip, box->height, scale, excluded, + (struct content *)html, &html->sel, + html->search, ctx)) + return false; + + return true; +} + +bool html_redraw_box(const html_content *html, struct box *box, + int x_parent, int y_parent, + const struct rect *clip, float scale, + colour current_background_color, + const struct redraw_context *ctx); + +/** + * Draw the various children of a box. + * + * \param html html content + * \param box box to draw children of + * \param x_parent coordinate of parent box + * \param y_parent coordinate of parent box + * \param clip clip rectangle + * \param scale scale for redraw + * \param current_background_color background colour under this box + * \param ctx current redraw context + * \return true if successful, false otherwise + */ + +static bool html_redraw_box_children(const html_content *html, struct box *box, + int x_parent, int y_parent, + const struct rect *clip, float scale, + colour current_background_color, + const struct redraw_context *ctx) +{ + struct box *c; + + for (c = box->children; c; c = c->next) { + + if (c->type != BOX_FLOAT_LEFT && c->type != BOX_FLOAT_RIGHT) + if (!html_redraw_box(html, c, + x_parent + box->x - + scrollbar_get_offset(box->scroll_x), + y_parent + box->y - + scrollbar_get_offset(box->scroll_y), + clip, scale, current_background_color, + ctx)) + return false; + } + for (c = box->float_children; c; c = c->next_float) + if (!html_redraw_box(html, c, + x_parent + box->x - + scrollbar_get_offset(box->scroll_x), + y_parent + box->y - + scrollbar_get_offset(box->scroll_y), + clip, scale, current_background_color, + ctx)) + return false; + + return true; +} + +/** + * Recursively draw a box. + * + * \param html html content + * \param box box to draw + * \param x_parent coordinate of parent box + * \param y_parent coordinate of parent box + * \param clip clip rectangle + * \param scale scale for redraw + * \param current_background_color background colour under this box + * \param ctx current redraw context + * \return true if successful, false otherwise + * + * x, y, clip_[xy][01] are in target coordinates. + */ + +bool html_redraw_box(const html_content *html, struct box *box, + int x_parent, int y_parent, + const struct rect *clip, const float scale, + colour current_background_color, + const struct redraw_context *ctx) +{ + const struct plotter_table *plot = ctx->plot; + int x, y; + int width, height; + int padding_left, padding_top, padding_width, padding_height; + int border_left, border_top, border_right, border_bottom; + struct rect r; + struct rect rect; + int x_scrolled, y_scrolled; + struct box *bg_box = NULL; + bool has_x_scroll, has_y_scroll; + css_computed_clip_rect css_rect; + enum css_overflow_e overflow_x = CSS_OVERFLOW_VISIBLE; + enum css_overflow_e overflow_y = CSS_OVERFLOW_VISIBLE; + + if (html_redraw_printing && (box->flags & PRINTED)) + return true; + + if (box->style != NULL) { + overflow_x = css_computed_overflow_x(box->style); + overflow_y = css_computed_overflow_y(box->style); + } + + /* avoid trivial FP maths */ + if (scale == 1.0) { + x = x_parent + box->x; + y = y_parent + box->y; + width = box->width; + height = box->height; + padding_left = box->padding[LEFT]; + padding_top = box->padding[TOP]; + padding_width = padding_left + box->width + box->padding[RIGHT]; + padding_height = padding_top + box->height + + box->padding[BOTTOM]; + border_left = box->border[LEFT].width; + border_top = box->border[TOP].width; + border_right = box->border[RIGHT].width; + border_bottom = box->border[BOTTOM].width; + } else { + x = (x_parent + box->x) * scale; + y = (y_parent + box->y) * scale; + width = box->width * scale; + height = box->height * scale; + /* left and top padding values are normally zero, + * so avoid trivial FP maths */ + padding_left = box->padding[LEFT] ? box->padding[LEFT] * scale + : 0; + padding_top = box->padding[TOP] ? box->padding[TOP] * scale + : 0; + padding_width = (box->padding[LEFT] + box->width + + box->padding[RIGHT]) * scale; + padding_height = (box->padding[TOP] + box->height + + box->padding[BOTTOM]) * scale; + border_left = box->border[LEFT].width * scale; + border_top = box->border[TOP].width * scale; + border_right = box->border[RIGHT].width * scale; + border_bottom = box->border[BOTTOM].width * scale; + } + + /* calculate rectangle covering this box and descendants */ + if (box->style && overflow_x != CSS_OVERFLOW_VISIBLE && + box->parent != NULL) { + /* box contents clipped to box size */ + r.x0 = x - border_left; + r.x1 = x + padding_width + border_right; + } else { + /* box contents can hang out of the box; use descendant box */ + if (scale == 1.0) { + r.x0 = x + box->descendant_x0; + r.x1 = x + box->descendant_x1 + 1; + } else { + r.x0 = x + box->descendant_x0 * scale; + r.x1 = x + box->descendant_x1 * scale + 1; + } + if (!box->parent) { + /* root element */ + int margin_left, margin_right; + if (scale == 1.0) { + margin_left = box->margin[LEFT]; + margin_right = box->margin[RIGHT]; + } else { + margin_left = box->margin[LEFT] * scale; + margin_right = box->margin[RIGHT] * scale; + } + r.x0 = x - border_left - margin_left < r.x0 ? + x - border_left - margin_left : r.x0; + r.x1 = x + padding_width + border_right + + margin_right > r.x1 ? + x + padding_width + border_right + + margin_right : r.x1; + } + } + + /* calculate rectangle covering this box and descendants */ + if (box->style && overflow_y != CSS_OVERFLOW_VISIBLE && + box->parent != NULL) { + /* box contents clipped to box size */ + r.y0 = y - border_top; + r.y1 = y + padding_height + border_bottom; + } else { + /* box contents can hang out of the box; use descendant box */ + if (scale == 1.0) { + r.y0 = y + box->descendant_y0; + r.y1 = y + box->descendant_y1 + 1; + } else { + r.y0 = y + box->descendant_y0 * scale; + r.y1 = y + box->descendant_y1 * scale + 1; + } + if (!box->parent) { + /* root element */ + int margin_top, margin_bottom; + if (scale == 1.0) { + margin_top = box->margin[TOP]; + margin_bottom = box->margin[BOTTOM]; + } else { + margin_top = box->margin[TOP] * scale; + margin_bottom = box->margin[BOTTOM] * scale; + } + r.y0 = y - border_top - margin_top < r.y0 ? + y - border_top - margin_top : r.y0; + r.y1 = y + padding_height + border_bottom + + margin_bottom > r.y1 ? + y + padding_height + border_bottom + + margin_bottom : r.y1; + } + } + + /* return if the rectangle is completely outside the clip rectangle */ + if (clip->y1 < r.y0 || r.y1 < clip->y0 || + clip->x1 < r.x0 || r.x1 < clip->x0) + return true; + + /*if the rectangle is under the page bottom but it can fit in a page, + don't print it now*/ + if (html_redraw_printing) { + if (r.y1 > html_redraw_printing_border) { + if (r.y1 - r.y0 <= html_redraw_printing_border && + (box->type == BOX_TEXT || + box->type == BOX_TABLE_CELL + || box->object || box->gadget)) { + /*remember the highest of all points from the + not printed elements*/ + if (r.y0 < html_redraw_printing_top_cropped) + html_redraw_printing_top_cropped = r.y0; + return true; + } + } + else box->flags |= PRINTED; /*it won't be printed anymore*/ + } + + /* if visibility is hidden render children only */ + if (box->style && css_computed_visibility(box->style) == + CSS_VISIBILITY_HIDDEN) { + if ((ctx->plot->group_start) && + (ctx->plot->group_start(ctx, "hidden box") != NSERROR_OK)) + return false; + if (!html_redraw_box_children(html, box, x_parent, y_parent, + &r, scale, current_background_color, ctx)) + return false; + return ((!ctx->plot->group_end) || (ctx->plot->group_end(ctx) == NSERROR_OK)); + } + + if ((ctx->plot->group_start) && + (ctx->plot->group_start(ctx,"vis box") != NSERROR_OK)) { + return false; + } + + if (box->style != NULL && + css_computed_position(box->style) == + CSS_POSITION_ABSOLUTE && + css_computed_clip(box->style, &css_rect) == + CSS_CLIP_RECT) { + /* We have an absolutly positioned box with a clip rect */ + if (css_rect.left_auto == false) + r.x0 = x - border_left + FIXTOINT(nscss_len2px( + &html->len_ctx, + css_rect.left, css_rect.lunit, + box->style)); + + if (css_rect.top_auto == false) + r.y0 = y - border_top + FIXTOINT(nscss_len2px( + &html->len_ctx, + css_rect.top, css_rect.tunit, + box->style)); + + if (css_rect.right_auto == false) + r.x1 = x - border_left + FIXTOINT(nscss_len2px( + &html->len_ctx, + css_rect.right, css_rect.runit, + box->style)); + + if (css_rect.bottom_auto == false) + r.y1 = y - border_top + FIXTOINT(nscss_len2px( + &html->len_ctx, + css_rect.bottom, css_rect.bunit, + box->style)); + + /* find intersection of clip rectangle and box */ + if (r.x0 < clip->x0) r.x0 = clip->x0; + if (r.y0 < clip->y0) r.y0 = clip->y0; + if (clip->x1 < r.x1) r.x1 = clip->x1; + if (clip->y1 < r.y1) r.y1 = clip->y1; + /* Nothing to do for invalid rectangles */ + if (r.x0 >= r.x1 || r.y0 >= r.y1) + /* not an error */ + return ((!ctx->plot->group_end) || + (ctx->plot->group_end(ctx) == NSERROR_OK)); + /* clip to it */ + if (ctx->plot->clip(ctx, &r) != NSERROR_OK) + return false; + + } else if (box->type == BOX_BLOCK || box->type == BOX_INLINE_BLOCK || + box->type == BOX_TABLE_CELL || box->object) { + /* find intersection of clip rectangle and box */ + if (r.x0 < clip->x0) r.x0 = clip->x0; + if (r.y0 < clip->y0) r.y0 = clip->y0; + if (clip->x1 < r.x1) r.x1 = clip->x1; + if (clip->y1 < r.y1) r.y1 = clip->y1; + /* no point trying to draw 0-width/height boxes */ + if (r.x0 == r.x1 || r.y0 == r.y1) + /* not an error */ + return ((!ctx->plot->group_end) || + (ctx->plot->group_end(ctx) == NSERROR_OK)); + /* clip to it */ + if (ctx->plot->clip(ctx, &r) != NSERROR_OK) + return false; + } else { + /* clip box is fine, clip to it */ + r = *clip; + if (ctx->plot->clip(ctx, &r) != NSERROR_OK) + return false; + } + + /* background colour and image for block level content and replaced + * inlines */ + + bg_box = html_redraw_find_bg_box(box); + + /* bg_box == NULL implies that this box should not have + * its background rendered. Otherwise filter out linebreaks, + * optimize away non-differing inlines, only plot background + * for BOX_TEXT it's in an inline */ + if (bg_box && bg_box->type != BOX_BR && + bg_box->type != BOX_TEXT && + bg_box->type != BOX_INLINE_END && + (bg_box->type != BOX_INLINE || bg_box->object || + bg_box->flags & IFRAME || box->flags & REPLACE_DIM || + (bg_box->gadget != NULL && + (bg_box->gadget->type == GADGET_TEXTAREA || + bg_box->gadget->type == GADGET_TEXTBOX || + bg_box->gadget->type == GADGET_PASSWORD)))) { + /* find intersection of clip box and border edge */ + struct rect p; + p.x0 = x - border_left < r.x0 ? r.x0 : x - border_left; + p.y0 = y - border_top < r.y0 ? r.y0 : y - border_top; + p.x1 = x + padding_width + border_right < r.x1 ? + x + padding_width + border_right : r.x1; + p.y1 = y + padding_height + border_bottom < r.y1 ? + y + padding_height + border_bottom : r.y1; + if (!box->parent) { + /* Root element, special case: + * background covers margins too */ + int m_left, m_top, m_right, m_bottom; + if (scale == 1.0) { + m_left = box->margin[LEFT]; + m_top = box->margin[TOP]; + m_right = box->margin[RIGHT]; + m_bottom = box->margin[BOTTOM]; + } else { + m_left = box->margin[LEFT] * scale; + m_top = box->margin[TOP] * scale; + m_right = box->margin[RIGHT] * scale; + m_bottom = box->margin[BOTTOM] * scale; + } + p.x0 = p.x0 - m_left < r.x0 ? r.x0 : p.x0 - m_left; + p.y0 = p.y0 - m_top < r.y0 ? r.y0 : p.y0 - m_top; + p.x1 = p.x1 + m_right < r.x1 ? p.x1 + m_right : r.x1; + p.y1 = p.y1 + m_bottom < r.y1 ? p.y1 + m_bottom : r.y1; + } + /* valid clipping rectangles only */ + if ((p.x0 < p.x1) && (p.y0 < p.y1)) { + /* plot background */ + if (!html_redraw_background(x, y, box, scale, &p, + ¤t_background_color, bg_box, + &html->len_ctx, ctx)) + return false; + /* restore previous graphics window */ + if (ctx->plot->clip(ctx, &r) != NSERROR_OK) + return false; + } + } + + /* borders for block level content and replaced inlines */ + if (box->style && + box->type != BOX_TEXT && + box->type != BOX_INLINE_END && + (box->type != BOX_INLINE || box->object || + box->flags & IFRAME || box->flags & REPLACE_DIM || + (box->gadget != NULL && + (box->gadget->type == GADGET_TEXTAREA || + box->gadget->type == GADGET_TEXTBOX || + box->gadget->type == GADGET_PASSWORD))) && + (border_top || border_right || border_bottom || border_left)) { + if (!html_redraw_borders(box, x_parent, y_parent, + padding_width, padding_height, &r, + scale, ctx)) + return false; + } + + /* backgrounds and borders for non-replaced inlines */ + if (box->style && box->type == BOX_INLINE && box->inline_end && + (html_redraw_box_has_background(box) || + border_top || border_right || + border_bottom || border_left)) { + /* inline backgrounds and borders span other boxes and may + * wrap onto separate lines */ + struct box *ib; + struct rect b; /* border edge rectangle */ + struct rect p; /* clipped rect */ + bool first = true; + int ib_x; + int ib_y = y; + int ib_p_width; + int ib_b_left, ib_b_right; + + b.x0 = x - border_left; + b.x1 = x + padding_width + border_right; + b.y0 = y - border_top; + b.y1 = y + padding_height + border_bottom; + + p.x0 = b.x0 < r.x0 ? r.x0 : b.x0; + p.x1 = b.x1 < r.x1 ? b.x1 : r.x1; + p.y0 = b.y0 < r.y0 ? r.y0 : b.y0; + p.y1 = b.y1 < r.y1 ? b.y1 : r.y1; + for (ib = box; ib; ib = ib->next) { + /* to get extents of rectangle(s) associated with + * inline, cycle though all boxes in inline, skipping + * over floats */ + if (ib->type == BOX_FLOAT_LEFT || + ib->type == BOX_FLOAT_RIGHT) + continue; + if (scale == 1.0) { + ib_x = x_parent + ib->x; + ib_y = y_parent + ib->y; + ib_p_width = ib->padding[LEFT] + ib->width + + ib->padding[RIGHT]; + ib_b_left = ib->border[LEFT].width; + ib_b_right = ib->border[RIGHT].width; + } else { + ib_x = (x_parent + ib->x) * scale; + ib_y = (y_parent + ib->y) * scale; + ib_p_width = (ib->padding[LEFT] + ib->width + + ib->padding[RIGHT]) * scale; + ib_b_left = ib->border[LEFT].width * scale; + ib_b_right = ib->border[RIGHT].width * scale; + } + + if ((ib->flags & NEW_LINE) && ib != box) { + /* inline element has wrapped, plot background + * and borders */ + if (!html_redraw_inline_background( + x, y, box, scale, &p, b, + first, false, + ¤t_background_color, + &html->len_ctx, ctx)) + return false; + /* restore previous graphics window */ + if (ctx->plot->clip(ctx, &r) != NSERROR_OK) + return false; + if (!html_redraw_inline_borders(box, b, &r, + scale, first, false, ctx)) + return false; + /* reset coords */ + b.x0 = ib_x - ib_b_left; + b.y0 = ib_y - border_top - padding_top; + b.y1 = ib_y + padding_height - padding_top + + border_bottom; + + p.x0 = b.x0 < r.x0 ? r.x0 : b.x0; + p.y0 = b.y0 < r.y0 ? r.y0 : b.y0; + p.y1 = b.y1 < r.y1 ? b.y1 : r.y1; + + first = false; + } + + /* increase width for current box */ + b.x1 = ib_x + ib_p_width + ib_b_right; + p.x1 = b.x1 < r.x1 ? b.x1 : r.x1; + + if (ib == box->inline_end) + /* reached end of BOX_INLINE span */ + break; + } + /* plot background and borders for last rectangle of + * the inline */ + if (!html_redraw_inline_background(x, ib_y, box, scale, &p, b, + first, true, ¤t_background_color, + &html->len_ctx, ctx)) + return false; + /* restore previous graphics window */ + if (ctx->plot->clip(ctx, &r) != NSERROR_OK) + return false; + if (!html_redraw_inline_borders(box, b, &r, scale, first, true, + ctx)) + return false; + + } + + /* Debug outlines */ + if (html_redraw_debug) { + int margin_left, margin_right; + int margin_top, margin_bottom; + if (scale == 1.0) { + /* avoid trivial fp maths */ + margin_left = box->margin[LEFT]; + margin_top = box->margin[TOP]; + margin_right = box->margin[RIGHT]; + margin_bottom = box->margin[BOTTOM]; + } else { + margin_left = box->margin[LEFT] * scale; + margin_top = box->margin[TOP] * scale; + margin_right = box->margin[RIGHT] * scale; + margin_bottom = box->margin[BOTTOM] * scale; + } + /* Content edge -- blue */ + rect.x0 = x + padding_left; + rect.y0 = y + padding_top; + rect.x1 = x + padding_left + width; + rect.y1 = y + padding_top + height; + if (ctx->plot->rectangle(ctx, plot_style_content_edge, &rect) != NSERROR_OK) + return false; + + /* Padding edge -- red */ + rect.x0 = x; + rect.y0 = y; + rect.x1 = x + padding_width; + rect.y1 = y + padding_height; + if (ctx->plot->rectangle(ctx, plot_style_padding_edge, &rect) != NSERROR_OK) + return false; + + /* Margin edge -- yellow */ + rect.x0 = x - border_left - margin_left; + rect.y0 = y - border_top - margin_top; + rect.x1 = x + padding_width + border_right + margin_right; + rect.y1 = y + padding_height + border_bottom + margin_bottom; + if (ctx->plot->rectangle(ctx, plot_style_margin_edge, &rect) != NSERROR_OK) + return false; + } + + /* clip to the padding edge for objects, or boxes with overflow hidden + * or scroll, unless it's the root element */ + if (box->parent != NULL) { + bool need_clip = false; + if (box->object || box->flags & IFRAME || + (overflow_x != CSS_OVERFLOW_VISIBLE && + overflow_y != CSS_OVERFLOW_VISIBLE)) { + r.x0 = x; + r.y0 = y; + r.x1 = x + padding_width; + r.y1 = y + padding_height; + if (r.x0 < clip->x0) r.x0 = clip->x0; + if (r.y0 < clip->y0) r.y0 = clip->y0; + if (clip->x1 < r.x1) r.x1 = clip->x1; + if (clip->y1 < r.y1) r.y1 = clip->y1; + if (r.x1 <= r.x0 || r.y1 <= r.y0) { + return (!ctx->plot->group_end || + (ctx->plot->group_end(ctx) == NSERROR_OK)); + } + need_clip = true; + + } else if (overflow_x != CSS_OVERFLOW_VISIBLE) { + r.x0 = x; + r.y0 = clip->y0; + r.x1 = x + padding_width; + r.y1 = clip->y1; + if (r.x0 < clip->x0) r.x0 = clip->x0; + if (clip->x1 < r.x1) r.x1 = clip->x1; + if (r.x1 <= r.x0) { + return (!ctx->plot->group_end || + (ctx->plot->group_end(ctx) == NSERROR_OK)); + } + need_clip = true; + + } else if (overflow_y != CSS_OVERFLOW_VISIBLE) { + r.x0 = clip->x0; + r.y0 = y; + r.x1 = clip->x1; + r.y1 = y + padding_height; + if (r.y0 < clip->y0) r.y0 = clip->y0; + if (clip->y1 < r.y1) r.y1 = clip->y1; + if (r.y1 <= r.y0) { + return (!ctx->plot->group_end || + (ctx->plot->group_end(ctx) == NSERROR_OK)); + } + need_clip = true; + } + + if (need_clip && + (box->type == BOX_BLOCK || + box->type == BOX_INLINE_BLOCK || + box->type == BOX_TABLE_CELL || box->object)) { + if (ctx->plot->clip(ctx, &r) != NSERROR_OK) + return false; + } + } + + /* text decoration */ + if ((box->type != BOX_TEXT) && + box->style && + css_computed_text_decoration(box->style) != CSS_TEXT_DECORATION_NONE) { + if (!html_redraw_text_decoration(box, x_parent, y_parent, + scale, current_background_color, ctx)) + return false; + } + + if (box->object && width != 0 && height != 0) { + struct content_redraw_data obj_data; + + x_scrolled = x - scrollbar_get_offset(box->scroll_x) * scale; + y_scrolled = y - scrollbar_get_offset(box->scroll_y) * scale; + + obj_data.x = x_scrolled + padding_left; + obj_data.y = y_scrolled + padding_top; + obj_data.width = width; + obj_data.height = height; + obj_data.background_colour = current_background_color; + obj_data.scale = scale; + obj_data.repeat_x = false; + obj_data.repeat_y = false; + + if (content_get_type(box->object) == CONTENT_HTML) { + obj_data.x /= scale; + obj_data.y /= scale; + } + + if (!content_redraw(box->object, &obj_data, &r, ctx)) { + /* Show image fail */ + /* Unicode (U+FFFC) 'OBJECT REPLACEMENT CHARACTER' */ + const char *obj = "\xef\xbf\xbc"; + int obj_width; + int obj_x = x + padding_left; + nserror res; + + rect.x0 = x + padding_left; + rect.y0 = y + padding_top; + rect.x1 = x + padding_left + width - 1; + rect.y1 = y + padding_top + height - 1; + res = ctx->plot->rectangle(ctx, plot_style_broken_object, &rect); + if (res != NSERROR_OK) { + return false; + } + + res = guit->layout->width(plot_fstyle_broken_object, + obj, + sizeof(obj) - 1, + &obj_width); + if (res != NSERROR_OK) { + obj_x += 1; + } else { + obj_x += width / 2 - obj_width / 2; + } + + if (ctx->plot->text(ctx, + plot_fstyle_broken_object, + obj_x, y + padding_top + (int)(height * 0.75), + obj, sizeof(obj) - 1) != NSERROR_OK) + return false; + } + + } else if (box->iframe) { + /* Offset is passed to browser window redraw unscaled */ + browser_window_redraw(box->iframe, + (x + padding_left) / scale, + (y + padding_top) / scale, &r, ctx); + + } else if (box->gadget && box->gadget->type == GADGET_CHECKBOX) { + if (!html_redraw_checkbox(x + padding_left, y + padding_top, + width, height, box->gadget->selected, ctx)) + return false; + + } else if (box->gadget && box->gadget->type == GADGET_RADIO) { + if (!html_redraw_radio(x + padding_left, y + padding_top, + width, height, box->gadget->selected, ctx)) + return false; + + } else if (box->gadget && box->gadget->type == GADGET_FILE) { + if (!html_redraw_file(x + padding_left, y + padding_top, + width, height, box, scale, + current_background_color, &html->len_ctx, ctx)) + return false; + + } else if (box->gadget && + (box->gadget->type == GADGET_TEXTAREA || + box->gadget->type == GADGET_PASSWORD || + box->gadget->type == GADGET_TEXTBOX)) { + textarea_redraw(box->gadget->data.text.ta, x, y, + current_background_color, scale, &r, ctx); + + } else if (box->text) { + if (!html_redraw_text_box(html, box, x, y, &r, scale, + current_background_color, ctx)) + return false; + + } else { + if (!html_redraw_box_children(html, box, x_parent, y_parent, &r, + scale, current_background_color, ctx)) + return false; + } + + if (box->type == BOX_BLOCK || box->type == BOX_INLINE_BLOCK || + box->type == BOX_TABLE_CELL || box->type == BOX_INLINE) + if (ctx->plot->clip(ctx, clip) != NSERROR_OK) + return false; + + /* list marker */ + if (box->list_marker) { + if (!html_redraw_box(html, box->list_marker, + x_parent + box->x - + scrollbar_get_offset(box->scroll_x), + y_parent + box->y - + scrollbar_get_offset(box->scroll_y), + clip, scale, current_background_color, ctx)) + return false; + } + + /* scrollbars */ + if (((box->style && box->type != BOX_BR && + box->type != BOX_TABLE && box->type != BOX_INLINE && + (overflow_x == CSS_OVERFLOW_SCROLL || + overflow_x == CSS_OVERFLOW_AUTO || + overflow_y == CSS_OVERFLOW_SCROLL || + overflow_y == CSS_OVERFLOW_AUTO)) || + (box->object && content_get_type(box->object) == + CONTENT_HTML)) && box->parent != NULL) { + + has_x_scroll = box_hscrollbar_present(box); + has_y_scroll = box_vscrollbar_present(box); + + if (!box_handle_scrollbars((struct content *)html, + box, has_x_scroll, has_y_scroll)) + return false; + + if (box->scroll_x != NULL) + scrollbar_redraw(box->scroll_x, + x_parent + box->x, + y_parent + box->y + box->padding[TOP] + + box->height + box->padding[BOTTOM] - + SCROLLBAR_WIDTH, clip, scale, ctx); + if (box->scroll_y != NULL) + scrollbar_redraw(box->scroll_y, + x_parent + box->x + box->padding[LEFT] + + box->width + box->padding[RIGHT] - + SCROLLBAR_WIDTH, + y_parent + box->y, clip, scale, ctx); + } + + if (box->type == BOX_BLOCK || box->type == BOX_INLINE_BLOCK || + box->type == BOX_TABLE_CELL || box->type == BOX_INLINE) { + if (ctx->plot->clip(ctx, clip) != NSERROR_OK) + return false; + } + + return ((!plot->group_end) || (ctx->plot->group_end(ctx) == NSERROR_OK)); +} + +/** + * Draw a CONTENT_HTML using the current set of plotters (plot). + * + * \param c content of type CONTENT_HTML + * \param data redraw data for this content redraw + * \param clip current clip region + * \param ctx current redraw context + * \return true if successful, false otherwise + * + * x, y, clip_[xy][01] are in target coordinates. + */ + +bool html_redraw(struct content *c, struct content_redraw_data *data, + const struct rect *clip, const struct redraw_context *ctx) +{ + html_content *html = (html_content *) c; + struct box *box; + bool result = true; + bool select, select_only; + plot_style_t pstyle_fill_bg = { + .fill_type = PLOT_OP_TYPE_SOLID, + .fill_colour = data->background_colour, + }; + + box = html->layout; + assert(box); + + /* The select menu needs special treating because, when opened, it + * reaches beyond its layout box. + */ + select = false; + select_only = false; + if (ctx->interactive && html->visible_select_menu != NULL) { + struct form_control *control = html->visible_select_menu; + select = true; + /* check if the redraw rectangle is completely inside of the + select menu */ + select_only = form_clip_inside_select_menu(control, + data->scale, clip); + } + + if (!select_only) { + /* clear to background colour */ + result = (ctx->plot->clip(ctx, clip) == NSERROR_OK); + + if (html->background_colour != NS_TRANSPARENT) + pstyle_fill_bg.fill_colour = html->background_colour; + + result &= (ctx->plot->rectangle(ctx, &pstyle_fill_bg, clip) == NSERROR_OK); + + result &= html_redraw_box(html, box, data->x, data->y, clip, + data->scale, pstyle_fill_bg.fill_colour, ctx); + } + + if (select) { + int menu_x, menu_y; + box = html->visible_select_menu->box; + box_coords(box, &menu_x, &menu_y); + + menu_x -= box->border[LEFT].width; + menu_y += box->height + box->border[BOTTOM].width + + box->padding[BOTTOM] + box->padding[TOP]; + result &= form_redraw_select_menu(html->visible_select_menu, + data->x + menu_x, data->y + menu_y, + data->scale, clip, ctx); + } + + return result; + +} diff --git a/content/handlers/html/html_redraw_border.c b/content/handlers/html/html_redraw_border.c new file mode 100644 index 000000000..2a849e853 --- /dev/null +++ b/content/handlers/html/html_redraw_border.c @@ -0,0 +1,928 @@ +/* + * Copyright 2017 Vincent Sanders + * + * 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 . + */ + +/** + * \file + * + * Redrawing CONTENT_HTML borders implementation. + */ + +#include +#include + +#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 + * + * 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 . + */ + +/** + * \file + * implementation of content handling for text/html scripts. + */ + +#include +#include +#include +#include +#include +#include +#include + +#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 + * + * 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 . + */ + +/** + * \file + * Implementation of HTML image maps + * + * \todo should this should use the general hashmap instead of its own + */ + +#include +#include +#include +#include + +#include + +#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 */ + 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 ... 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 + * + * 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 . + */ + +/** + * \file + * Interface to HTML imagemap + */ + +#ifndef NETSURF_HTML_IMAGEMAP_H +#define NETSURF_HTML_IMAGEMAP_H + +#include + +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 + * Copyright 2006 James Bursa + * Copyright 2008 Michael Drake + * Copyright 2003 Phil Mellor + * + * 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 . + */ + +/** + * \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 +#include +#include +#include +#include +#include +#include +#include + +#include "utils/log.h" +#include "utils/talloc.h" +#include "utils/utils.h" +#include "utils/nsoption.h" +#include "netsurf/inttypes.h" +#include "netsurf/content.h" +#include "netsurf/browser_window.h" +#include "netsurf/layout.h" +#include "content/content_protected.h" +#include "css/utils.h" +#include "desktop/scrollbar.h" +#include "desktop/textarea.h" + +#include "html/box.h" +#include "html/font.h" +#include "html/form_internal.h" +#include "html/html_internal.h" +#include "html/layout.h" +#include "html/table.h" + +#define AUTO INT_MIN + +/* Fixed point percentage (a) of an integer (b), to an integer */ +#define FPCT_OF_INT_TOINT(a, b) (FIXTOINT(FDIV((a * b), F_100))) + +typedef uint8_t (*css_len_func)( + const css_computed_style *style, + css_fixed *length, css_unit *unit); +typedef uint8_t (*css_border_style_func)( + const css_computed_style *style); +typedef uint8_t (*css_border_color_func)( + const css_computed_style *style, + css_color *color); + +/** Array of per-side access functions for computed style margins. */ +static const css_len_func margin_funcs[4] = { + [TOP] = css_computed_margin_top, + [RIGHT] = css_computed_margin_right, + [BOTTOM] = css_computed_margin_bottom, + [LEFT] = css_computed_margin_left, +}; + +/** Array of per-side access functions for computed style paddings. */ +static const css_len_func padding_funcs[4] = { + [TOP] = css_computed_padding_top, + [RIGHT] = css_computed_padding_right, + [BOTTOM] = css_computed_padding_bottom, + [LEFT] = css_computed_padding_left, +}; + +/** Array of per-side access functions for computed style border_widths. */ +static const css_len_func border_width_funcs[4] = { + [TOP] = css_computed_border_top_width, + [RIGHT] = css_computed_border_right_width, + [BOTTOM] = css_computed_border_bottom_width, + [LEFT] = css_computed_border_left_width, +}; + +/** Array of per-side access functions for computed style border styles. */ +static const css_border_style_func border_style_funcs[4] = { + [TOP] = css_computed_border_top_style, + [RIGHT] = css_computed_border_right_style, + [BOTTOM] = css_computed_border_bottom_style, + [LEFT] = css_computed_border_left_style, +}; + +/** Array of per-side access functions for computed style border colors. */ +static const css_border_color_func border_color_funcs[4] = { + [TOP] = css_computed_border_top_color, + [RIGHT] = css_computed_border_right_color, + [BOTTOM] = css_computed_border_bottom_color, + [LEFT] = css_computed_border_left_color, +}; + +/* forward declaration to break cycles */ +static bool layout_block_context( + struct box *block, + int viewport_height, + html_content *content); +static void layout_minmax_block( + struct box *block, + const struct gui_layout_table *font_func, + const html_content *content); + + +/** + * Compute the size of replaced boxes with auto dimensions, according to + * content. + * + * \param box Box with object + * \param width Width value in px or AUTO. If AUTO, updated to value in px. + * \param height Height value in px or AUTO. If AUTO, updated to value in px. + * \param min_width Box's min width, as given by layout_find_dimensions. + * \param max_width Box's max width, as given by layout_find_dimensions. + * \param min_height Box's min height, as given by layout_find_dimensions. + * \param max_height Box's max height, as given by layout_find_dimensions. + * + * See CSS 2.1 sections 10.3 and 10.6. + */ +static void +layout_get_object_dimensions(struct box *box, + int *width, int *height, + int min_width, int max_width, + int min_height, int max_height) +{ + assert(box->object != NULL); + assert(width != NULL && height != NULL); + + if (*width == AUTO && *height == AUTO) { + /* No given dimensions */ + + bool scaled = false; + int intrinsic_width = content_get_width(box->object); + int intrinsic_height = content_get_height(box->object); + + /* use intrinsic dimensions */ + *width = intrinsic_width; + *height = intrinsic_height; + + /* Deal with min/max-width first */ + if (min_width > 0 && min_width > *width) { + *width = min_width; + scaled = true; + } + if (max_width >= 0 && max_width < *width) { + *width = max_width; + scaled = true; + } + + if (scaled && (intrinsic_width != 0)) { + /* Update height */ + *height = (*width * intrinsic_height) / + intrinsic_width; + } + + scaled = false; + /* Deal with min/max-height */ + if (min_height > 0 && min_height > *height) { + *height = min_height; + scaled = true; + } + if (max_height >= 0 && max_height < *height) { + *height = max_height; + scaled = true; + } + + if (scaled && (intrinsic_height != 0)) { + /* Update width */ + *width = (*height * intrinsic_width) / + intrinsic_height; + } + + } else if (*width == AUTO) { + /* Have given height; width is calculated from the given height + * and ratio of intrinsic dimensions */ + int intrinsic_width = content_get_width(box->object); + int intrinsic_height = content_get_height(box->object); + + if (intrinsic_height != 0) + *width = (*height * intrinsic_width) / + intrinsic_height; + else + *width = intrinsic_width; + + if (min_width > 0 && min_width > *width) + *width = min_width; + if (max_width >= 0 && max_width < *width) + *width = max_width; + + } else if (*height == AUTO) { + /* Have given width; height is calculated from the given width + * and ratio of intrinsic dimensions */ + int intrinsic_width = content_get_width(box->object); + int intrinsic_height = content_get_height(box->object); + + if (intrinsic_width != 0) + *height = (*width * intrinsic_height) / + intrinsic_width; + else + *height = intrinsic_height; + } +} + + +/** + * Calculate the text-indent length. + * + * \param style style of block + * \param width width of containing block + * \return length of indent + */ +static int layout_text_indent( + const nscss_len_ctx *len_ctx, + const css_computed_style *style, int width) +{ + css_fixed value = 0; + css_unit unit = CSS_UNIT_PX; + + css_computed_text_indent(style, &value, &unit); + + if (unit == CSS_UNIT_PCT) { + return FPCT_OF_INT_TOINT(value, width); + } else { + return FIXTOINT(nscss_len2px(len_ctx, value, unit, style)); + } +} + + +/** + * Determine width of margin, borders, and padding on one side of a box. + * + * \param len_ctx CSS length conversion context for document + * \param style style to measure + * \param side side of box to measure + * \param margin whether margin width is required + * \param border whether border width is required + * \param padding whether padding width is required + * \param fixed increased by sum of fixed margin, border, and padding + * \param frac increased by sum of fractional margin and padding + */ +static void +calculate_mbp_width(const nscss_len_ctx *len_ctx, + const css_computed_style *style, + unsigned int side, + bool margin, + bool border, + bool padding, + int *fixed, + float *frac) +{ + css_fixed value = 0; + css_unit unit = CSS_UNIT_PX; + + assert(style); + + /* margin */ + if (margin) { + enum css_margin_e type; + + type = margin_funcs[side](style, &value, &unit); + if (type == CSS_MARGIN_SET) { + if (unit == CSS_UNIT_PCT) { + *frac += FIXTOINT(FDIV(value, F_100)); + } else { + *fixed += FIXTOINT(nscss_len2px(len_ctx, + value, unit, style)); + } + } + } + + /* border */ + if (border) { + if (border_style_funcs[side](style) != + CSS_BORDER_STYLE_NONE) { + border_width_funcs[side](style, &value, &unit); + + *fixed += FIXTOINT(nscss_len2px(len_ctx, + value, unit, style)); + } + } + + /* padding */ + if (padding) { + padding_funcs[side](style, &value, &unit); + if (unit == CSS_UNIT_PCT) { + *frac += FIXTOINT(FDIV(value, F_100)); + } else { + *fixed += FIXTOINT(nscss_len2px(len_ctx, + value, unit, style)); + } + } +} + + +/** + * Calculate minimum and maximum width of a table. + * + * \param table box of type TABLE + * \param font_func Font functions + * \param content The HTML content we are laying out. + * \post table->min_width and table->max_width filled in, + * 0 <= table->min_width <= table->max_width + */ +static void layout_minmax_table(struct box *table, + const struct gui_layout_table *font_func, + const html_content *content) +{ + unsigned int i, j; + int border_spacing_h = 0; + int table_min = 0, table_max = 0; + int extra_fixed = 0; + float extra_frac = 0; + struct column *col; + struct box *row_group, *row, *cell; + enum css_width_e wtype; + css_fixed value = 0; + css_unit unit = CSS_UNIT_PX; + + /* check if the widths have already been calculated */ + if (table->max_width != UNKNOWN_MAX_WIDTH) + return; + + if (table_calculate_column_types(&content->len_ctx, table) == false) { + NSLOG(netsurf, WARNING, + "Could not establish table column types."); + return; + } + col = table->col; + + /* start with 0 except for fixed-width columns */ + for (i = 0; i != table->columns; i++) { + if (col[i].type == COLUMN_WIDTH_FIXED) + col[i].min = col[i].max = col[i].width; + else + col[i].min = col[i].max = 0; + } + + /* border-spacing is used in the separated borders model */ + if (css_computed_border_collapse(table->style) == + CSS_BORDER_COLLAPSE_SEPARATE) { + css_fixed h = 0, v = 0; + css_unit hu = CSS_UNIT_PX, vu = CSS_UNIT_PX; + + css_computed_border_spacing(table->style, &h, &hu, &v, &vu); + + border_spacing_h = FIXTOINT(nscss_len2px(&content->len_ctx, + h, hu, table->style)); + } + + /* 1st pass: consider cells with colspan 1 only */ + for (row_group = table->children; row_group; row_group =row_group->next) + for (row = row_group->children; row; row = row->next) + for (cell = row->children; cell; cell = cell->next) { + assert(cell->type == BOX_TABLE_CELL); + assert(cell->style); + /** TODO: Handle colspan="0" correctly. + * It's currently converted to 1 in box normaisation */ + assert(cell->columns != 0); + + if (cell->columns != 1) + continue; + + layout_minmax_block(cell, font_func, content); + i = cell->start_column; + + if (col[i].positioned) + continue; + + /* update column min, max widths using cell widths */ + if (col[i].min < cell->min_width) + col[i].min = cell->min_width; + if (col[i].max < cell->max_width) + col[i].max = cell->max_width; + } + + /* 2nd pass: cells which span multiple columns */ + for (row_group = table->children; row_group; row_group =row_group->next) + for (row = row_group->children; row; row = row->next) + for (cell = row->children; cell; cell = cell->next) { + unsigned int flexible_columns = 0; + int min = 0, max = 0, fixed_width = 0, extra; + + if (cell->columns == 1) + continue; + + layout_minmax_block(cell, font_func, content); + i = cell->start_column; + + /* find min width so far of spanned columns, and count + * number of non-fixed spanned columns and total fixed width */ + for (j = 0; j != cell->columns; j++) { + min += col[i + j].min; + if (col[i + j].type == COLUMN_WIDTH_FIXED) + fixed_width += col[i + j].width; + else + flexible_columns++; + } + min += (cell->columns - 1) * border_spacing_h; + + /* distribute extra min to spanned columns */ + if (min < cell->min_width) { + if (flexible_columns == 0) { + extra = 1 + (cell->min_width - min) / + cell->columns; + for (j = 0; j != cell->columns; j++) { + col[i + j].min += extra; + if (col[i + j].max < col[i + j].min) + col[i + j].max = col[i + j].min; + } + } else { + extra = 1 + (cell->min_width - min) / + flexible_columns; + for (j = 0; j != cell->columns; j++) { + if (col[i + j].type != + COLUMN_WIDTH_FIXED) { + col[i + j].min += extra; + if (col[i + j].max < + col[i + j].min) + col[i + j].max = + col[i + j].min; + } + } + } + } + + /* find max width so far of spanned columns */ + for (j = 0; j != cell->columns; j++) + max += col[i + j].max; + max += (cell->columns - 1) * border_spacing_h; + + /* distribute extra max to spanned columns */ + if (max < cell->max_width && flexible_columns) { + extra = 1 + (cell->max_width - max) / flexible_columns; + for (j = 0; j != cell->columns; j++) + if (col[i + j].type != COLUMN_WIDTH_FIXED) + col[i + j].max += extra; + } + } + + for (i = 0; i != table->columns; i++) { + if (col[i].max < col[i].min) { + box_dump(stderr, table, 0, true); + assert(0); + } + table_min += col[i].min; + table_max += col[i].max; + } + + /* fixed width takes priority, unless it is too narrow */ + wtype = css_computed_width(table->style, &value, &unit); + if (wtype == CSS_WIDTH_SET && unit != CSS_UNIT_PCT) { + int width = FIXTOINT(nscss_len2px(&content->len_ctx, + value, unit, table->style)); + if (table_min < width) + table_min = width; + if (table_max < width) + table_max = width; + } + + /* add margins, border, padding to min, max widths */ + calculate_mbp_width(&content->len_ctx, + table->style, LEFT, true, true, true, + &extra_fixed, &extra_frac); + calculate_mbp_width(&content->len_ctx, + table->style, RIGHT, true, true, true, + &extra_fixed, &extra_frac); + if (extra_fixed < 0) + extra_fixed = 0; + if (extra_frac < 0) + extra_frac = 0; + if (1.0 <= extra_frac) + extra_frac = 0.9; + table->min_width = (table_min + extra_fixed) / (1.0 - extra_frac); + table->max_width = (table_max + extra_fixed) / (1.0 - extra_frac); + table->min_width += (table->columns + 1) * border_spacing_h; + table->max_width += (table->columns + 1) * border_spacing_h; + + assert(0 <= table->min_width && table->min_width <= table->max_width); +} + +/** + * Helper to check if a box has percentage max width. + * + * \param[in] b Box to check. + * \return true iff box has percnetage max width. + */ +static inline bool box_has_percentage_max_width(struct box *b) +{ + css_unit unit = CSS_UNIT_PX; + enum css_max_width_e type; + css_fixed value = 0; + + assert(b != NULL); + + type = css_computed_max_width(b->style, &value, &unit); + return ((type == CSS_MAX_WIDTH_SET) && (unit == CSS_UNIT_PCT)); +} + +/** + * Calculate minimum and maximum width of a line. + * + * \param first a box in an inline container + * \param line_min updated to minimum width of line starting at first + * \param line_max updated to maximum width of line starting at first + * \param first_line true iff this is the first line in the inline container + * \param line_has_height updated to true or false, depending on line + * \param font_func Font functions. + * \return first box in next line, or 0 if no more lines + * \post 0 <= *line_min <= *line_max + */ +static struct box * +layout_minmax_line(struct box *first, + int *line_min, + int *line_max, + bool first_line, + bool *line_has_height, + const struct gui_layout_table *font_func, + const html_content *content) +{ + int min = 0, max = 0, width, height, fixed; + float frac; + size_t i, j; + struct box *b; + struct box *block; + plot_font_style_t fstyle; + bool no_wrap; + + assert(first->parent); + assert(first->parent->parent); + assert(first->parent->parent->style); + + block = first->parent->parent; + no_wrap = (css_computed_white_space(block->style) == + CSS_WHITE_SPACE_NOWRAP || + css_computed_white_space(block->style) == + CSS_WHITE_SPACE_PRE); + + *line_has_height = false; + + /* corresponds to the pass 1 loop in layout_line() */ + for (b = first; b; b = b->next) { + enum css_width_e wtype; + enum css_height_e htype; + enum css_box_sizing_e bs; + css_fixed value = 0; + css_unit unit = CSS_UNIT_PX; + + assert(b->type == BOX_INLINE || b->type == BOX_INLINE_BLOCK || + b->type == BOX_FLOAT_LEFT || + b->type == BOX_FLOAT_RIGHT || + b->type == BOX_BR || b->type == BOX_TEXT || + b->type == BOX_INLINE_END); + + NSLOG(layout, DEBUG, "%p: min %i, max %i", b, min, max); + + if (b->type == BOX_BR) { + b = b->next; + break; + } + + if (b->type == BOX_FLOAT_LEFT || b->type == BOX_FLOAT_RIGHT) { + assert(b->children); + if (b->children->type == BOX_BLOCK) + layout_minmax_block(b->children, font_func, + content); + else + layout_minmax_table(b->children, font_func, + content); + b->min_width = b->children->min_width; + b->max_width = b->children->max_width; + if (min < b->min_width) + min = b->min_width; + max += b->max_width; + continue; + } + + if (b->type == BOX_INLINE_BLOCK) { + layout_minmax_block(b, font_func, content); + if (min < b->min_width) + min = b->min_width; + max += b->max_width; + + if (b->flags & HAS_HEIGHT) + *line_has_height = true; + continue; + } + + assert(b->style); + font_plot_style_from_css(&content->len_ctx, b->style, &fstyle); + + if (b->type == BOX_INLINE && !b->object && + !(b->flags & REPLACE_DIM) && + !(b->flags & IFRAME)) { + fixed = frac = 0; + calculate_mbp_width(&content->len_ctx, + b->style, LEFT, true, true, true, + &fixed, &frac); + if (!b->inline_end) + calculate_mbp_width(&content->len_ctx, + b->style, RIGHT, + true, true, true, + &fixed, &frac); + if (0 < fixed) + max += fixed; + *line_has_height = true; + /* \todo update min width, consider fractional extra */ + } else if (b->type == BOX_INLINE_END) { + fixed = frac = 0; + calculate_mbp_width(&content->len_ctx, + b->inline_end->style, RIGHT, + true, true, true, + &fixed, &frac); + if (0 < fixed) + max += fixed; + + if (b->next) { + if (b->space == UNKNOWN_WIDTH) { + font_func->width(&fstyle, " ", 1, + &b->space); + } + max += b->space; + } + + *line_has_height = true; + continue; + } + + if (!b->object && !(b->flags & IFRAME) && !b->gadget && + !(b->flags & REPLACE_DIM)) { + /* inline non-replaced, 10.3.1 and 10.6.1 */ + bool no_wrap_box; + if (!b->text) + continue; + + no_wrap_box = (css_computed_white_space(b->style) == + CSS_WHITE_SPACE_NOWRAP || + css_computed_white_space(b->style) == + CSS_WHITE_SPACE_PRE); + + if (b->width == UNKNOWN_WIDTH) { + /** \todo handle errors */ + + /* If it's a select element, we must use the + * width of the widest option text */ + if (b->parent->parent->gadget && + b->parent->parent->gadget->type + == GADGET_SELECT) { + int opt_maxwidth = 0; + struct form_option *o; + + for (o = b->parent->parent->gadget-> + data.select.items; o; + o = o->next) { + int opt_width; + font_func->width(&fstyle, + o->text, + strlen(o->text), + &opt_width); + + if (opt_maxwidth < opt_width) + opt_maxwidth =opt_width; + } + + b->width = opt_maxwidth; + if (nsoption_bool(core_select_menu)) + b->width += SCROLLBAR_WIDTH; + + } else { + font_func->width(&fstyle, b->text, + b->length, &b->width); + b->flags |= MEASURED; + } + } + max += b->width; + if (b->next) { + if (b->space == UNKNOWN_WIDTH) { + font_func->width(&fstyle, " ", 1, + &b->space); + } + max += b->space; + } + + if (no_wrap) { + /* Don't wrap due to block style, + * so min is the same as max */ + min = max; + + } else if (no_wrap_box) { + /* This inline box can't be wrapped, + * for min, consider box's width */ + if (min < b->width) + min = b->width; + + } else if (b->parent->flags & NEED_MIN) { + /* If we care what the minimum width is, + * calculate it. (It's only needed if we're + * shrinking-to-fit.) */ + /* min = widest single word */ + i = 0; + do { + for (j = i; j != b->length && + b->text[j] != ' '; j++) + ; + font_func->width(&fstyle, b->text + i, + j - i, &width); + if (min < width) + min = width; + i = j + 1; + } while (j != b->length); + } + + *line_has_height = true; + + continue; + } + + /* inline replaced, 10.3.2 and 10.6.2 */ + assert(b->style); + + /* calculate box width */ + wtype = css_computed_width(b->style, &value, &unit); + bs = css_computed_box_sizing(block->style); + if (wtype == CSS_WIDTH_SET) { + if (unit == CSS_UNIT_PCT) { + width = AUTO; + } else { + width = FIXTOINT(nscss_len2px(&content->len_ctx, + value, unit, b->style)); + + if (bs == CSS_BOX_SIZING_BORDER_BOX) { + fixed = frac = 0; + calculate_mbp_width(&content->len_ctx, + block->style, LEFT, + false, true, true, + &fixed, &frac); + calculate_mbp_width(&content->len_ctx, + block->style, RIGHT, + false, true, true, + &fixed, &frac); + if (width < fixed) { + width = fixed; + } + } + if (width < 0) + width = 0; + } + } else { + width = AUTO; + } + + /* height */ + htype = css_computed_height(b->style, &value, &unit); + if (htype == CSS_HEIGHT_SET) { + height = FIXTOINT(nscss_len2px(&content->len_ctx, + value, unit, b->style)); + } else { + height = AUTO; + } + + if (b->object || (b->flags & REPLACE_DIM)) { + if (b->object) { + int temp_height = height; + layout_get_object_dimensions(b, + &width, &temp_height, + INT_MIN, INT_MAX, + INT_MIN, INT_MAX); + } + + fixed = frac = 0; + if (bs == CSS_BOX_SIZING_BORDER_BOX) { + calculate_mbp_width(&content->len_ctx, + b->style, LEFT, + true, false, false, + &fixed, &frac); + calculate_mbp_width(&content->len_ctx, + b->style, RIGHT, + true, false, false, + &fixed, &frac); + } else { + calculate_mbp_width(&content->len_ctx, + b->style, LEFT, + true, true, true, + &fixed, &frac); + calculate_mbp_width(&content->len_ctx, + b->style, RIGHT, + true, true, true, + &fixed, &frac); + } + if (0 < width + fixed) + width += fixed; + } else if (b->flags & IFRAME) { + /* TODO: handle percentage widths properly */ + if (width == AUTO) + width = 400; + + fixed = frac = 0; + if (bs == CSS_BOX_SIZING_BORDER_BOX) { + calculate_mbp_width(&content->len_ctx, + b->style, LEFT, + true, false, false, + &fixed, &frac); + calculate_mbp_width(&content->len_ctx, + b->style, RIGHT, + true, false, false, + &fixed, &frac); + } else { + calculate_mbp_width(&content->len_ctx, + b->style, LEFT, + true, true, true, + &fixed, &frac); + calculate_mbp_width(&content->len_ctx, + b->style, RIGHT, + true, true, true, + &fixed, &frac); + } + + if (0 < width + fixed) + width += fixed; + + } else { + /* form control with no object */ + if (width == AUTO) + width = FIXTOINT(nscss_len2px( + &content->len_ctx, + INTTOFIX(1), CSS_UNIT_EM, + b->style)); + } + + if (min < width && !box_has_percentage_max_width(b)) + min = width; + if (width > 0) + max += width; + + *line_has_height = true; + } + + if (first_line) { + /* todo: handle percentage values properly */ + /* todo: handle text-indent interaction with floats */ + int text_indent = layout_text_indent(&content->len_ctx, + first->parent->parent->style, 100); + min = (min + text_indent < 0) ? 0 : min + text_indent; + max = (max + text_indent < 0) ? 0 : max + text_indent; + } + + *line_min = min; + *line_max = max; + + NSLOG(layout, DEBUG, "line_min %i, line_max %i", min, max); + + assert(b != first); + assert(0 <= *line_min); + assert(*line_min <= *line_max); + return b; +} + + +/** + * Calculate minimum and maximum width of an inline container. + * + * \param inline_container box of type INLINE_CONTAINER + * \param[out] has_height set to true if container has height + * \param font_func Font functions. + * \post inline_container->min_width and inline_container->max_width filled in, + * 0 <= inline_container->min_width <= inline_container->max_width + */ +static void +layout_minmax_inline_container(struct box *inline_container, + bool *has_height, + const struct gui_layout_table *font_func, + const html_content *content) +{ + struct box *child; + int line_min = 0, line_max = 0; + int min = 0, max = 0; + bool first_line = true; + bool line_has_height; + + assert(inline_container->type == BOX_INLINE_CONTAINER); + + /* check if the widths have already been calculated */ + if (inline_container->max_width != UNKNOWN_MAX_WIDTH) + return; + + *has_height = false; + + for (child = inline_container->children; child; ) { + child = layout_minmax_line(child, &line_min, &line_max, + first_line, &line_has_height, font_func, + content); + if (min < line_min) + min = line_min; + if (max < line_max) + max = line_max; + first_line = false; + *has_height |= line_has_height; + } + + inline_container->min_width = min; + inline_container->max_width = max; + + assert(0 <= inline_container->min_width && + inline_container->min_width <= + inline_container->max_width); +} + + +/** + * Calculate minimum and maximum width of a block. + * + * \param block box of type BLOCK, INLINE_BLOCK, or TABLE_CELL + * \param font_func font functions + * \param content The HTML content being layed out. + * \post block->min_width and block->max_width filled in, + * 0 <= block->min_width <= block->max_width + */ +static void layout_minmax_block( + struct box *block, + const struct gui_layout_table *font_func, + const html_content *content) +{ + struct box *child; + int min = 0, max = 0; + int extra_fixed = 0; + float extra_frac = 0; + enum css_width_e wtype = CSS_WIDTH_AUTO; + css_fixed width = 0; + css_unit wunit = CSS_UNIT_PX; + enum css_height_e htype = CSS_HEIGHT_AUTO; + css_fixed height = 0; + css_unit hunit = CSS_UNIT_PX; + enum css_box_sizing_e bs = CSS_BOX_SIZING_CONTENT_BOX; + bool child_has_height = false; + + assert(block->type == BOX_BLOCK || + block->type == BOX_INLINE_BLOCK || + block->type == BOX_TABLE_CELL); + + /* check if the widths have already been calculated */ + if (block->max_width != UNKNOWN_MAX_WIDTH) + return; + + if (block->style != NULL) { + wtype = css_computed_width(block->style, &width, &wunit); + htype = css_computed_height(block->style, &height, &hunit); + bs = css_computed_box_sizing(block->style); + } + + /* set whether the minimum width is of any interest for this box */ + if (((block->parent && (block->parent->type == BOX_FLOAT_LEFT || + block->parent->type == BOX_FLOAT_RIGHT)) || + block->type == BOX_INLINE_BLOCK) && + wtype != CSS_WIDTH_SET) { + /* box shrinks to fit; need minimum width */ + block->flags |= NEED_MIN; + } else if (block->type == BOX_TABLE_CELL) { + /* box shrinks to fit; need minimum width */ + block->flags |= NEED_MIN; + } else if (block->parent && (block->parent->flags & NEED_MIN) && + wtype != CSS_WIDTH_SET) { + /* box inside shrink-to-fit context; need minimum width */ + block->flags |= NEED_MIN; + } + + if (block->gadget && (block->gadget->type == GADGET_TEXTBOX || + block->gadget->type == GADGET_PASSWORD || + block->gadget->type == GADGET_FILE || + block->gadget->type == GADGET_TEXTAREA) && + block->style && wtype == CSS_WIDTH_AUTO) { + css_fixed size = INTTOFIX(10); + css_unit unit = CSS_UNIT_EM; + + min = max = FIXTOINT(nscss_len2px(&content->len_ctx, + size, unit, block->style)); + + block->flags |= HAS_HEIGHT; + } + + if (block->gadget && (block->gadget->type == GADGET_RADIO || + block->gadget->type == GADGET_CHECKBOX) && + block->style && wtype == CSS_WIDTH_AUTO) { + css_fixed size = INTTOFIX(1); + css_unit unit = CSS_UNIT_EM; + + /* form checkbox or radio button + * if width is AUTO, set it to 1em */ + min = max = FIXTOINT(nscss_len2px(&content->len_ctx, + size, unit, block->style)); + + block->flags |= HAS_HEIGHT; + } + + if (block->object) { + if (content_get_type(block->object) == CONTENT_HTML) { + layout_minmax_block(html_get_box_tree(block->object), + font_func, content); + min = html_get_box_tree(block->object)->min_width; + max = html_get_box_tree(block->object)->max_width; + } else { + min = max = content_get_width(block->object); + } + + block->flags |= HAS_HEIGHT; + } else if (block->flags & IFRAME) { + /** \todo do we need to know the min/max width of the iframe's + * content? */ + block->flags |= HAS_HEIGHT; + } else { + /* recurse through children */ + for (child = block->children; child; child = child->next) { + switch (child->type) { + case BOX_BLOCK: + layout_minmax_block(child, font_func, + content); + if (child->flags & HAS_HEIGHT) + child_has_height = true; + break; + case BOX_INLINE_CONTAINER: + if (block->flags & NEED_MIN) + child->flags |= NEED_MIN; + + layout_minmax_inline_container(child, + &child_has_height, font_func, + content); + if (child_has_height && + child == + child->parent->children) { + block->flags |= MAKE_HEIGHT; + } + break; + case BOX_TABLE: + layout_minmax_table(child, font_func, + content); + /* todo: fix for zero height tables */ + child_has_height = true; + child->flags |= MAKE_HEIGHT; + break; + default: + assert(0); + } + assert(child->max_width != UNKNOWN_MAX_WIDTH); + + if (child->style && + (css_computed_position(child->style) == + CSS_POSITION_ABSOLUTE || + css_computed_position(child->style) == + CSS_POSITION_FIXED)) { + /* This child is positioned out of normal flow, + * so it will have no affect on width */ + continue; + } + + if (min < child->min_width) + min = child->min_width; + if (max < child->max_width) + max = child->max_width; + + if (child_has_height) + block->flags |= HAS_HEIGHT; + } + } + + if (max < min) { + box_dump(stderr, block, 0, true); + assert(0); + } + + /* fixed width takes priority */ + if (block->type != BOX_TABLE_CELL && wtype == CSS_WIDTH_SET && + wunit != CSS_UNIT_PCT) { + min = max = FIXTOINT(nscss_len2px(&content->len_ctx, + width, wunit, block->style)); + if (bs == CSS_BOX_SIZING_BORDER_BOX) { + int border_box_fixed = 0; + float border_box_frac = 0; + calculate_mbp_width(&content->len_ctx, + block->style, LEFT, + false, true, true, + &border_box_fixed, &border_box_frac); + calculate_mbp_width(&content->len_ctx, + block->style, RIGHT, + false, true, true, + &border_box_fixed, &border_box_frac); + if (min < border_box_fixed) { + min = max = border_box_fixed; + } + } + } + + if (htype == CSS_HEIGHT_SET && hunit != CSS_UNIT_PCT && + height > INTTOFIX(0)) { + block->flags |= MAKE_HEIGHT; + block->flags |= HAS_HEIGHT; + } + + /* add margins, border, padding to min, max widths */ + /* Note: we don't know available width here so percentage margin + * and paddings are wrong. */ + if (bs == CSS_BOX_SIZING_BORDER_BOX && wtype == CSS_WIDTH_SET) { + /* Border and padding included in width, so just get margin */ + calculate_mbp_width(&content->len_ctx, + block->style, LEFT, true, false, false, + &extra_fixed, &extra_frac); + calculate_mbp_width(&content->len_ctx, + block->style, RIGHT, true, false, false, + &extra_fixed, &extra_frac); + } else { + calculate_mbp_width(&content->len_ctx, + block->style, LEFT, true, true, true, + &extra_fixed, &extra_frac); + calculate_mbp_width(&content->len_ctx, + block->style, RIGHT, true, true, true, + &extra_fixed, &extra_frac); + } + if (extra_fixed < 0) + extra_fixed = 0; + if (extra_frac < 0) + extra_frac = 0; + if (1.0 <= extra_frac) + extra_frac = 0.9; + if (block->style != NULL && + (css_computed_float(block->style) == CSS_FLOAT_LEFT || + css_computed_float(block->style) == CSS_FLOAT_RIGHT)) { + /* floated boxs */ + block->min_width = min + extra_fixed; + block->max_width = max + extra_fixed; + } else { + /* not floated */ + block->min_width = (min + extra_fixed) / (1.0 - extra_frac); + block->max_width = (max + extra_fixed) / (1.0 - extra_frac); + } + + assert(0 <= block->min_width && block->min_width <= block->max_width); +} + + +/** + * Adjust a specified width or height for the box-sizing property. + * + * This turns the specified dimension into a content-box dimension. + * + * \param len_ctx Length conversion context + * \param box gadget to adjust dimensions of + * \param available_width width of containing block + * \param setwidth set true if the dimension to be tweaked is a width, + * else set false for a height + * \param dimension current value for given width/height dimension. + * updated to new value after consideration of + * gadget properties. + */ +static void layout_handle_box_sizing( + const nscss_len_ctx *len_ctx, + struct box *box, + int available_width, + bool setwidth, + int *dimension) +{ + enum css_box_sizing_e bs; + + assert(box && box->style); + + bs = css_computed_box_sizing(box->style); + + if (bs == CSS_BOX_SIZING_BORDER_BOX) { + int orig = *dimension; + int fixed = 0; + float frac = 0; + + calculate_mbp_width(len_ctx, box->style, + setwidth ? LEFT : TOP, + false, true, true, &fixed, &frac); + calculate_mbp_width(len_ctx, box->style, + setwidth ? RIGHT : BOTTOM, + false, true, true, &fixed, &frac); + orig -= frac * available_width + fixed; + *dimension = orig > 0 ? orig : 0; + } +} + + +/** + * Calculate width, height, and thickness of margins, paddings, and borders. + * + * \param len_ctx Length conversion context + * \param available_width width of containing block + * \param viewport_height height of viewport in pixels or -ve if unknown + * \param box current box + * \param style style giving width, height, margins, paddings, + * and borders + * \param width updated to width, may be NULL + * \param height updated to height, may be NULL + * \param max_width updated to max-width, may be NULL + * \param min_width updated to min-width, may be NULL + * \param max_height updated to max-height, may be NULL + * \param min_height updated to min-height, may be NULL + * \param margin filled with margins, may be NULL + * \param padding filled with paddings, may be NULL + * \param border filled with border widths, may be NULL + */ +static void +layout_find_dimensions(const nscss_len_ctx *len_ctx, + int available_width, + int viewport_height, + struct box *box, + const css_computed_style *style, + int *width, + int *height, + int *max_width, + int *min_width, + int *max_height, + int *min_height, + int margin[4], + int padding[4], + struct box_border border[4]) +{ + struct box *containing_block = NULL; + unsigned int i; + + if (width) { + enum css_width_e wtype; + css_fixed value = 0; + css_unit unit = CSS_UNIT_PX; + + wtype = css_computed_width(style, &value, &unit); + + if (wtype == CSS_WIDTH_SET) { + if (unit == CSS_UNIT_PCT) { + *width = FPCT_OF_INT_TOINT( + value, available_width); + } else { + *width = FIXTOINT(nscss_len2px(len_ctx, + value, unit, style)); + } + } else { + *width = AUTO; + } + + if (*width != AUTO) { + layout_handle_box_sizing(len_ctx, box, available_width, + true, width); + } + } + + if (height) { + enum css_height_e htype; + css_fixed value = 0; + css_unit unit = CSS_UNIT_PX; + + htype = css_computed_height(style, &value, &unit); + + if (htype == CSS_HEIGHT_SET) { + if (unit == CSS_UNIT_PCT) { + enum css_height_e cbhtype; + + if (css_computed_position(box->style) == + CSS_POSITION_ABSOLUTE && + box->parent) { + /* Box is absolutely positioned */ + assert(box->float_container); + containing_block = box->float_container; + } else if (box->float_container && + css_computed_position(box->style) != + CSS_POSITION_ABSOLUTE && + (css_computed_float(box->style) == + CSS_FLOAT_LEFT || + css_computed_float(box->style) == + CSS_FLOAT_RIGHT)) { + /* Box is a float */ + assert(box->parent && + box->parent->parent && + box->parent->parent->parent); + + containing_block = + box->parent->parent->parent; + } else if (box->parent && box->parent->type != + BOX_INLINE_CONTAINER) { + /* Box is a block level element */ + containing_block = box->parent; + } else if (box->parent && box->parent->type == + BOX_INLINE_CONTAINER) { + /* Box is an inline block */ + assert(box->parent->parent); + containing_block = box->parent->parent; + } + + if (containing_block) { + css_fixed f = 0; + css_unit u = CSS_UNIT_PX; + + cbhtype = css_computed_height( + containing_block->style, + &f, &u); + } + + if (containing_block && + containing_block->height != AUTO && + (css_computed_position(box->style) == + CSS_POSITION_ABSOLUTE || + cbhtype == CSS_HEIGHT_SET)) { + /* Box is absolutely positioned or its + * containing block has a valid + * specified height. + * (CSS 2.1 Section 10.5) */ + *height = FPCT_OF_INT_TOINT(value, + containing_block->height); + } else if ((!box->parent || + !box->parent->parent) && + viewport_height >= 0) { + /* If root element or it's child + * (HTML or BODY) */ + *height = FPCT_OF_INT_TOINT(value, + viewport_height); + } else { + /* precentage height not permissible + * treat height as auto */ + *height = AUTO; + } + } else { + *height = FIXTOINT(nscss_len2px(len_ctx, + value, unit, style)); + } + } else { + *height = AUTO; + } + + if (*height != AUTO) { + layout_handle_box_sizing(len_ctx, box, available_width, + false, height); + } + } + + if (max_width) { + enum css_max_width_e type; + css_fixed value = 0; + css_unit unit = CSS_UNIT_PX; + + type = css_computed_max_width(style, &value, &unit); + + if (type == CSS_MAX_WIDTH_SET) { + if (unit == CSS_UNIT_PCT) { + *max_width = FPCT_OF_INT_TOINT(value, + available_width); + } else { + *max_width = FIXTOINT(nscss_len2px(len_ctx, + value, unit, style)); + } + } else { + /* Inadmissible */ + *max_width = -1; + } + + if (*max_width != -1) { + layout_handle_box_sizing(len_ctx, box, available_width, + true, max_width); + } + } + + if (min_width) { + enum css_min_width_e type; + css_fixed value = 0; + css_unit unit = CSS_UNIT_PX; + + type = ns_computed_min_width(style, &value, &unit); + + if (type == CSS_MIN_WIDTH_SET) { + if (unit == CSS_UNIT_PCT) { + *min_width = FPCT_OF_INT_TOINT(value, + available_width); + } else { + *min_width = FIXTOINT(nscss_len2px(len_ctx, + value, unit, style)); + } + } else { + /* Inadmissible */ + *min_width = 0; + } + + if (*min_width != 0) { + layout_handle_box_sizing(len_ctx, box, available_width, + true, min_width); + } + } + + if (max_height) { + enum css_max_height_e type; + css_fixed value = 0; + css_unit unit = CSS_UNIT_PX; + + type = css_computed_max_height(style, &value, &unit); + + if (type == CSS_MAX_HEIGHT_SET) { + if (unit == CSS_UNIT_PCT) { + /* TODO: handle percentage */ + *max_height = -1; + } else { + *max_height = FIXTOINT(nscss_len2px(len_ctx, + value, unit, style)); + } + } else { + /* Inadmissible */ + *max_height = -1; + } + } + + if (min_height) { + enum css_min_height_e type; + css_fixed value = 0; + css_unit unit = CSS_UNIT_PX; + + type = ns_computed_min_height(style, &value, &unit); + + if (type == CSS_MIN_HEIGHT_SET) { + if (unit == CSS_UNIT_PCT) { + /* TODO: handle percentage */ + *min_height = 0; + } else { + *min_height = FIXTOINT(nscss_len2px(len_ctx, + value, unit, style)); + } + } else { + /* Inadmissible */ + *min_height = 0; + } + } + + for (i = 0; i != 4; i++) { + if (margin) { + enum css_margin_e type = CSS_MARGIN_AUTO; + css_fixed value = 0; + css_unit unit = CSS_UNIT_PX; + + type = margin_funcs[i](style, &value, &unit); + + if (type == CSS_MARGIN_SET) { + if (unit == CSS_UNIT_PCT) { + margin[i] = FPCT_OF_INT_TOINT(value, + available_width); + } else { + margin[i] = FIXTOINT(nscss_len2px( + len_ctx, + value, unit, style)); + } + } else { + margin[i] = AUTO; + } + } + + if (padding) { + css_fixed value = 0; + css_unit unit = CSS_UNIT_PX; + + padding_funcs[i](style, &value, &unit); + + if (unit == CSS_UNIT_PCT) { + padding[i] = FPCT_OF_INT_TOINT(value, + available_width); + } else { + padding[i] = FIXTOINT(nscss_len2px(len_ctx, + value, unit, style)); + } + } + + /* Table cell borders are populated in table.c */ + if (border && box->type != BOX_TABLE_CELL) { + enum css_border_style_e bstyle = CSS_BORDER_STYLE_NONE; + css_color color = 0; + css_fixed value = 0; + css_unit unit = CSS_UNIT_PX; + + border_width_funcs[i](style, &value, &unit); + bstyle = border_style_funcs[i](style); + border_color_funcs[i](style, &color); + + border[i].style = bstyle; + border[i].c = color; + + if (bstyle == CSS_BORDER_STYLE_HIDDEN || + bstyle == CSS_BORDER_STYLE_NONE) + /* spec unclear: following Mozilla */ + border[i].width = 0; + else + border[i].width = FIXTOINT(nscss_len2px(len_ctx, + value, unit, style)); + + /* Special case for border-collapse: make all borders + * on table/table-row-group/table-row zero width. */ + if (css_computed_border_collapse(style) == + CSS_BORDER_COLLAPSE_COLLAPSE && + (box->type == BOX_TABLE || + box->type == BOX_TABLE_ROW_GROUP || + box->type == BOX_TABLE_ROW)) + border[i].width = 0; + } + } +} + + +/** + * Find next block that current margin collapses to. + * + * \param len_ctx Length conversion context + * \param box box to start tree-order search from (top margin is included) + * \param block box responsible for current block fromatting context + * \param viewport_height height of viewport in px + * \param max_pos_margin updated to to maximum positive margin encountered + * \param max_neg_margin updated to to maximum negative margin encountered + * \return next box that current margin collapses to, or NULL if none. + */ +static struct box* +layout_next_margin_block(const nscss_len_ctx *len_ctx, + struct box *box, + struct box *block, + int viewport_height, + int *max_pos_margin, + int *max_neg_margin) +{ + assert(block != NULL); + + while (box != NULL) { + + if (box->type == BOX_INLINE_CONTAINER || (box->style && + (css_computed_position(box->style) != + CSS_POSITION_ABSOLUTE && + css_computed_position(box->style) != + CSS_POSITION_FIXED))) { + /* Not positioned */ + + /* Get margins */ + if (box->style) { + layout_find_dimensions(len_ctx, + box->parent->width, + viewport_height, box, + box->style, + NULL, NULL, NULL, NULL, + NULL, NULL, box->margin, + box->padding, box->border); + + /* Apply top margin */ + if (*max_pos_margin < box->margin[TOP]) + *max_pos_margin = box->margin[TOP]; + else if (*max_neg_margin < -box->margin[TOP]) + *max_neg_margin = -box->margin[TOP]; + } + + /* Check whether box is the box current margin collapses + * to */ + if (box->flags & MAKE_HEIGHT || + box->border[TOP].width || + box->padding[TOP] || + (box->style && + css_computed_overflow_y(box->style) != + CSS_OVERFLOW_VISIBLE) || + (box->type == BOX_INLINE_CONTAINER && + box != box->parent->children)) { + /* Collapse to this box; return it */ + return box; + } + } + + + /* Find next box */ + if (box->type == BOX_BLOCK && !box->object && box->children && + box->style && + css_computed_overflow_y(box->style) == + CSS_OVERFLOW_VISIBLE) { + /* Down into children. */ + box = box->children; + } else { + if (!box->next) { + /* No more siblings: + * Go up to first ancestor with a sibling. */ + do { + /* Apply bottom margin */ + if (*max_pos_margin < + box->margin[BOTTOM]) + *max_pos_margin = + box->margin[BOTTOM]; + else if (*max_neg_margin < + -box->margin[BOTTOM]) + *max_neg_margin = + -box->margin[BOTTOM]; + + box = box->parent; + } while (box != block && !box->next); + + if (box == block) { + /* Margins don't collapse with stuff + * outside the block formatting context + */ + return block; + } + } + + /* Apply bottom margin */ + if (*max_pos_margin < box->margin[BOTTOM]) + *max_pos_margin = box->margin[BOTTOM]; + else if (*max_neg_margin < -box->margin[BOTTOM]) + *max_neg_margin = -box->margin[BOTTOM]; + + /* To next sibling. */ + box = box->next; + + /* Get margins */ + if (box->style) { + layout_find_dimensions(len_ctx, + box->parent->width, + viewport_height, box, + box->style, + NULL, NULL, NULL, NULL, + NULL, NULL, box->margin, + box->padding, box->border); + } + } + } + + return NULL; +} + + +/** + * Find y coordinate which clears all floats on left and/or right. + * + * \param fl first float in float list + * \param clear type of clear + * \return y coordinate relative to ancestor box for floats + */ +static int layout_clear(struct box *fl, enum css_clear_e clear) +{ + int y = 0; + for (; fl; fl = fl->next_float) { + if ((clear == CSS_CLEAR_LEFT || clear == CSS_CLEAR_BOTH) && + fl->type == BOX_FLOAT_LEFT) + if (y < fl->y + fl->height) + y = fl->y + fl->height; + if ((clear == CSS_CLEAR_RIGHT || clear == CSS_CLEAR_BOTH) && + fl->type == BOX_FLOAT_RIGHT) + if (y < fl->y + fl->height) + y = fl->y + fl->height; + } + return y; +} + + +/** + * Find left and right edges in a vertical range. + * + * \param fl first float in float list + * \param y0 start of y range to search + * \param y1 end of y range to search + * \param x0 start left edge, updated to available left edge + * \param x1 start right edge, updated to available right edge + * \param left returns float on left if present + * \param right returns float on right if present + */ +static void +find_sides(struct box *fl, + int y0, int y1, + int *x0, int *x1, + struct box **left, + struct box **right) +{ + int fy0, fy1, fx0, fx1; + + NSLOG(layout, DEBUG, "y0 %i, y1 %i, x0 %i, x1 %i", y0, y1, *x0, *x1); + + *left = *right = 0; + for (; fl; fl = fl->next_float) { + fy1 = fl->y + fl->height; + if (fy1 < y0) { + /* Floats are sorted in order of decreasing bottom pos. + * Past here, all floats will be too high to concern us. + */ + return; + } + fy0 = fl->y; + if (y0 < fy1 && fy0 <= y1) { + if (fl->type == BOX_FLOAT_LEFT) { + fx1 = fl->x + fl->width; + if (*x0 < fx1) { + *x0 = fx1; + *left = fl; + } + } else { + fx0 = fl->x; + if (fx0 < *x1) { + *x1 = fx0; + *right = fl; + } + } + } + } + + NSLOG(layout, DEBUG, "x0 %i, x1 %i, left %p, right %p", *x0, *x1, + *left, *right); +} + + + + +/** + * Solve the width constraint as given in CSS 2.1 section 10.3.3. + * + * \param box Box to solve constraint for + * \param available_width Max width available in pixels + * \param width Current box width + * \param lm Min left margin required to avoid floats in px. + * zero if not applicable + * \param rm Min right margin required to avoid floats in px. + * zero if not applicable + * \param max_width Box max-width ( -ve means no max-width to apply) + * \param min_width Box min-width ( <=0 means no min-width to apply) + * \return New box width + * + * \post \a box's left/right margins will be updated. + */ +static int +layout_solve_width(struct box *box, + int available_width, + int width, + int lm, + int rm, + int max_width, + int min_width) +{ + bool auto_width = false; + + /* Increase specified left/right margins */ + if (box->margin[LEFT] != AUTO && box->margin[LEFT] < lm && + box->margin[LEFT] >= 0) + box->margin[LEFT] = lm; + if (box->margin[RIGHT] != AUTO && box->margin[RIGHT] < rm && + box->margin[RIGHT] >= 0) + box->margin[RIGHT] = rm; + + /* Find width */ + if (width == AUTO) { + int margin_left = box->margin[LEFT]; + int margin_right = box->margin[RIGHT]; + + if (margin_left == AUTO) { + margin_left = lm; + } + if (margin_right == AUTO) { + margin_right = rm; + } + + width = available_width - + (margin_left + box->border[LEFT].width + + box->padding[LEFT] + box->padding[RIGHT] + + box->border[RIGHT].width + margin_right); + width = width < 0 ? 0 : width; + auto_width = true; + } + + if (max_width >= 0 && width > max_width) { + /* max-width is admissable and width exceeds max-width */ + width = max_width; + auto_width = false; + } + + if (min_width > 0 && width < min_width) { + /* min-width is admissable and width is less than max-width */ + width = min_width; + auto_width = false; + } + + /* Width was auto, and unconstrained by min/max width, so we're done */ + if (auto_width) { + /* any other 'auto' become 0 or the minimum required values */ + if (box->margin[LEFT] == AUTO) { + box->margin[LEFT] = lm; + } + if (box->margin[RIGHT] == AUTO) { + box->margin[RIGHT] = rm; + } + return width; + } + + /* Width was not auto, or was constrained by min/max width + * Need to compute left/right margins */ + + /* HTML alignment (only applies to over-constrained boxes) */ + if (box->margin[LEFT] != AUTO && box->margin[RIGHT] != AUTO && + box->parent != NULL && box->parent->style != NULL) { + switch (css_computed_text_align(box->parent->style)) { + case CSS_TEXT_ALIGN_LIBCSS_RIGHT: + box->margin[LEFT] = AUTO; + box->margin[RIGHT] = 0; + break; + case CSS_TEXT_ALIGN_LIBCSS_CENTER: + box->margin[LEFT] = box->margin[RIGHT] = AUTO; + break; + case CSS_TEXT_ALIGN_LIBCSS_LEFT: + box->margin[LEFT] = 0; + box->margin[RIGHT] = AUTO; + break; + default: + /* Leave it alone; no HTML alignment */ + break; + } + } + + if (box->margin[LEFT] == AUTO && box->margin[RIGHT] == AUTO) { + /* make the margins equal, centering the element */ + box->margin[LEFT] = box->margin[RIGHT] = + (available_width - lm - rm - + (box->border[LEFT].width + box->padding[LEFT] + + width + box->padding[RIGHT] + + box->border[RIGHT].width)) / 2; + + if (box->margin[LEFT] < 0) { + box->margin[RIGHT] += box->margin[LEFT]; + box->margin[LEFT] = 0; + } + + box->margin[LEFT] += lm; + + } else if (box->margin[LEFT] == AUTO) { + box->margin[LEFT] = available_width - lm - + (box->border[LEFT].width + box->padding[LEFT] + + width + box->padding[RIGHT] + + box->border[RIGHT].width + box->margin[RIGHT]); + box->margin[LEFT] = box->margin[LEFT] < lm + ? lm : box->margin[LEFT]; + } else { + /* margin-right auto or "over-constrained" */ + box->margin[RIGHT] = available_width - rm - + (box->margin[LEFT] + box->border[LEFT].width + + box->padding[LEFT] + width + + box->padding[RIGHT] + + box->border[RIGHT].width); + } + + return width; +} + + +/** + * Compute dimensions of box, margins, paddings, and borders for a block-level + * element. + * + * \param len_ctx Length conversion context + * \param available_width Max width available in pixels + * \param viewport_height Height of viewport in pixels or -ve if unknown + * \param lm min left margin required to avoid floats in px. + * zero if not applicable + * \param rm min right margin required to avoid floats in px. + * zero if not applicable + * \param box box to find dimensions of. updated with new width, + * height, margins, borders and paddings + * + * See CSS 2.1 10.3.3, 10.3.4, 10.6.2, and 10.6.3. + */ +static void +layout_block_find_dimensions(const nscss_len_ctx *len_ctx, + int available_width, + int viewport_height, + int lm, + int rm, + struct box *box) +{ + int width, max_width, min_width; + int height, max_height, min_height; + int *margin = box->margin; + int *padding = box->padding; + struct box_border *border = box->border; + const css_computed_style *style = box->style; + + layout_find_dimensions(len_ctx, available_width, viewport_height, box, + style, &width, &height, &max_width, &min_width, + &max_height, &min_height, margin, padding, border); + + if (box->object && !(box->flags & REPLACE_DIM) && + content_get_type(box->object) != CONTENT_HTML) { + /* block-level replaced element, see 10.3.4 and 10.6.2 */ + layout_get_object_dimensions(box, &width, &height, + min_width, max_width, min_height, max_height); + } + + box->width = layout_solve_width(box, available_width, width, lm, rm, + max_width, min_width); + box->height = height; + + if (margin[TOP] == AUTO) + margin[TOP] = 0; + if (margin[BOTTOM] == AUTO) + margin[BOTTOM] = 0; +} + + +/** + * Manipulate a block's [RB]padding/height/width to accommodate scrollbars + * + * \param box Box to apply scrollbar space too. Must be BOX_BLOCK. + * \param which Which scrollbar to make space for. Must be RIGHT or BOTTOM. + */ +static void layout_block_add_scrollbar(struct box *box, int which) +{ + enum css_overflow_e overflow_x, overflow_y; + + assert(box->type == BOX_BLOCK && (which == RIGHT || which == BOTTOM)); + + if (box->style == NULL) + return; + + overflow_x = css_computed_overflow_x(box->style); + overflow_y = css_computed_overflow_y(box->style); + + if (which == BOTTOM && + (overflow_x == CSS_OVERFLOW_SCROLL || + overflow_x == CSS_OVERFLOW_AUTO || + (box->object && + content_get_type(box->object) == CONTENT_HTML))) { + /* make space for scrollbar, unless height is AUTO */ + if (box->height != AUTO && + (overflow_x == CSS_OVERFLOW_SCROLL || + box_hscrollbar_present(box))) { + box->padding[BOTTOM] += SCROLLBAR_WIDTH; + } + + } else if (which == RIGHT && + (overflow_y == CSS_OVERFLOW_SCROLL || + overflow_y == CSS_OVERFLOW_AUTO || + (box->object && + content_get_type(box->object) == CONTENT_HTML))) { + /* make space for scrollbars, unless width is AUTO */ + enum css_height_e htype; + css_fixed height = 0; + css_unit hunit = CSS_UNIT_PX; + htype = css_computed_height(box->style, &height, &hunit); + + if (which == RIGHT && box->width != AUTO && + htype == CSS_HEIGHT_SET && + (overflow_y == CSS_OVERFLOW_SCROLL || + box_vscrollbar_present(box))) { + box->width -= SCROLLBAR_WIDTH; + box->padding[RIGHT] += SCROLLBAR_WIDTH; + } + } +} + + +/** + * Moves the children of a box by a specified amount + * + * \param box top of tree of boxes + * \param x the amount to move children by horizontally + * \param y the amount to move children by vertically + */ +static void layout_move_children(struct box *box, int x, int y) +{ + assert(box); + + for (box = box->children; box; box = box->next) { + box->x += x; + box->y += y; + } +} + + +/** + * Layout a table. + * + * \param table table to layout + * \param available_width width of containing block + * \param content memory pool for any new boxes + * \return true on success, false on memory exhaustion + */ +static bool layout_table(struct box *table, int available_width, + html_content *content) +{ + unsigned int columns = table->columns; /* total columns */ + unsigned int i; + unsigned int *row_span; + int *excess_y; + int table_width, min_width = 0, max_width = 0; + int required_width = 0; + int x, remainder = 0, count = 0; + int table_height = 0; + int min_height = 0; + int *xs; /* array of column x positions */ + int auto_width; + int spare_width; + int relative_sum = 0; + int border_spacing_h = 0, border_spacing_v = 0; + int spare_height; + int positioned_columns = 0; + struct box *containing_block = NULL; + struct box *c; + struct box *row; + struct box *row_group; + struct box **row_span_cell; + struct column *col; + const css_computed_style *style = table->style; + enum css_width_e wtype; + enum css_height_e htype; + css_fixed value = 0; + css_unit unit = CSS_UNIT_PX; + + assert(table->type == BOX_TABLE); + assert(style); + assert(table->children && table->children->children); + assert(columns); + + /* allocate working buffers */ + col = malloc(columns * sizeof col[0]); + excess_y = malloc(columns * sizeof excess_y[0]); + row_span = malloc(columns * sizeof row_span[0]); + row_span_cell = malloc(columns * sizeof row_span_cell[0]); + xs = malloc((columns + 1) * sizeof xs[0]); + if (!col || !xs || !row_span || !excess_y || !row_span_cell) { + free(col); + free(excess_y); + free(row_span); + free(row_span_cell); + free(xs); + return false; + } + + memcpy(col, table->col, sizeof(col[0]) * columns); + + /* find margins, paddings, and borders for table and cells */ + layout_find_dimensions(&content->len_ctx, available_width, -1, table, + style, 0, 0, 0, 0, 0, 0, table->margin, table->padding, + table->border); + for (row_group = table->children; row_group; + row_group = row_group->next) { + for (row = row_group->children; row; row = row->next) { + for (c = row->children; c; c = c->next) { + enum css_overflow_e overflow_x; + enum css_overflow_e overflow_y; + + assert(c->style); + table_used_border_for_cell( + &content->len_ctx, c); + layout_find_dimensions(&content->len_ctx, + available_width, -1, c, + c->style, 0, 0, 0, 0, 0, 0, + 0, c->padding, c->border); + + overflow_x = css_computed_overflow_x(c->style); + overflow_y = css_computed_overflow_y(c->style); + + if (overflow_x == CSS_OVERFLOW_SCROLL || + overflow_x == + CSS_OVERFLOW_AUTO) { + c->padding[BOTTOM] += SCROLLBAR_WIDTH; + } + if (overflow_y == CSS_OVERFLOW_SCROLL || + overflow_y == + CSS_OVERFLOW_AUTO) { + c->padding[RIGHT] += SCROLLBAR_WIDTH; + } + } + } + } + + /* border-spacing is used in the separated borders model */ + if (css_computed_border_collapse(style) == + CSS_BORDER_COLLAPSE_SEPARATE) { + css_fixed h = 0, v = 0; + css_unit hu = CSS_UNIT_PX, vu = CSS_UNIT_PX; + + css_computed_border_spacing(style, &h, &hu, &v, &vu); + + border_spacing_h = FIXTOINT(nscss_len2px(&content->len_ctx, + h, hu, style)); + border_spacing_v = FIXTOINT(nscss_len2px(&content->len_ctx, + v, vu, style)); + } + + /* find specified table width, or available width if auto-width */ + wtype = css_computed_width(style, &value, &unit); + if (wtype == CSS_WIDTH_SET) { + if (unit == CSS_UNIT_PCT) { + table_width = FPCT_OF_INT_TOINT(value, available_width); + } else { + table_width = + FIXTOINT(nscss_len2px(&content->len_ctx, + value, unit, style)); + } + + /* specified width includes border */ + table_width -= table->border[LEFT].width + + table->border[RIGHT].width; + table_width = table_width < 0 ? 0 : table_width; + + auto_width = table_width; + } else { + table_width = AUTO; + auto_width = available_width - + ((table->margin[LEFT] == AUTO ? 0 : + table->margin[LEFT]) + + table->border[LEFT].width + + table->padding[LEFT] + + table->padding[RIGHT] + + table->border[RIGHT].width + + (table->margin[RIGHT] == AUTO ? 0 : + table->margin[RIGHT])); + } + + /* Find any table height specified within CSS/HTML */ + htype = css_computed_height(style, &value, &unit); + if (htype == CSS_HEIGHT_SET) { + if (unit == CSS_UNIT_PCT) { + /* This is the minimum height for the table + * (see 17.5.3) */ + if (css_computed_position(table->style) == + CSS_POSITION_ABSOLUTE) { + /* Table is absolutely positioned */ + assert(table->float_container); + containing_block = table->float_container; + } else if (table->float_container && + css_computed_position(table->style) != + CSS_POSITION_ABSOLUTE && + (css_computed_float(table->style) == + CSS_FLOAT_LEFT || + css_computed_float(table->style) == + CSS_FLOAT_RIGHT)) { + /* Table is a float */ + assert(table->parent && table->parent->parent && + table->parent->parent->parent); + containing_block = + table->parent->parent->parent; + } else if (table->parent && table->parent->type != + BOX_INLINE_CONTAINER) { + /* Table is a block level element */ + containing_block = table->parent; + } else if (table->parent && table->parent->type == + BOX_INLINE_CONTAINER) { + /* Table is an inline block */ + assert(table->parent->parent); + containing_block = table->parent->parent; + } + + if (containing_block) { + css_fixed ignored = 0; + + htype = css_computed_height( + containing_block->style, + &ignored, &unit); + } + + if (containing_block && + containing_block->height != AUTO && + (css_computed_position(table->style) == + CSS_POSITION_ABSOLUTE || + htype == CSS_HEIGHT_SET)) { + /* Table is absolutely positioned or its + * containing block has a valid specified + * height. (CSS 2.1 Section 10.5) */ + min_height = FPCT_OF_INT_TOINT(value, + containing_block->height); + } + } else { + /* This is the minimum height for the table + * (see 17.5.3) */ + min_height = FIXTOINT(nscss_len2px(&content->len_ctx, + value, unit, style)); + } + } + + /* calculate width required by cells */ + for (i = 0; i != columns; i++) { + + NSLOG(layout, DEBUG, + "table %p, column %u: type %s, width %i, min %i, max %i", + table, + i, + ((const char *[]){ + "UNKNOWN", + "FIXED", + "AUTO", + "PERCENT", + "RELATIVE", + })[col[i].type], + col[i].width, + col[i].min, + col[i].max); + + + if (col[i].positioned) { + positioned_columns++; + continue; + } else if (col[i].type == COLUMN_WIDTH_FIXED) { + if (col[i].width < col[i].min) + col[i].width = col[i].max = col[i].min; + else + col[i].min = col[i].max = col[i].width; + required_width += col[i].width; + } else if (col[i].type == COLUMN_WIDTH_PERCENT) { + int width = col[i].width * auto_width / 100; + required_width += col[i].min < width ? width : + col[i].min; + } else + required_width += col[i].min; + + NSLOG(layout, DEBUG, "required_width %i", required_width); + } + required_width += (columns + 1 - positioned_columns) * + border_spacing_h; + + NSLOG(layout, DEBUG, + "width %i, min %i, max %i, auto %i, required %i", table_width, + table->min_width, table->max_width, auto_width, required_width); + + if (auto_width < required_width) { + /* table narrower than required width for columns: + * treat percentage widths as maximums */ + for (i = 0; i != columns; i++) { + if (col[i].type == COLUMN_WIDTH_RELATIVE) + continue; + if (col[i].type == COLUMN_WIDTH_PERCENT) { + col[i].max = auto_width * col[i].width / 100; + if (col[i].max < col[i].min) + col[i].max = col[i].min; + } + min_width += col[i].min; + max_width += col[i].max; + } + } else { + /* take percentages exactly */ + for (i = 0; i != columns; i++) { + if (col[i].type == COLUMN_WIDTH_RELATIVE) + continue; + if (col[i].type == COLUMN_WIDTH_PERCENT) { + int width = auto_width * col[i].width / 100; + if (width < col[i].min) + width = col[i].min; + col[i].min = col[i].width = col[i].max = width; + col[i].type = COLUMN_WIDTH_FIXED; + } + min_width += col[i].min; + max_width += col[i].max; + } + } + + /* allocate relative widths */ + spare_width = auto_width; + for (i = 0; i != columns; i++) { + if (col[i].type == COLUMN_WIDTH_RELATIVE) + relative_sum += col[i].width; + else if (col[i].type == COLUMN_WIDTH_FIXED) + spare_width -= col[i].width; + else + spare_width -= col[i].min; + } + spare_width -= (columns + 1) * border_spacing_h; + if (relative_sum != 0) { + if (spare_width < 0) + spare_width = 0; + for (i = 0; i != columns; i++) { + if (col[i].type == COLUMN_WIDTH_RELATIVE) { + col[i].min = ceil(col[i].max = + (float) spare_width + * (float) col[i].width + / relative_sum); + min_width += col[i].min; + max_width += col[i].max; + } + } + } + min_width += (columns + 1) * border_spacing_h; + max_width += (columns + 1) * border_spacing_h; + + if (auto_width <= min_width) { + /* not enough space: minimise column widths */ + for (i = 0; i < columns; i++) { + col[i].width = col[i].min; + } + table_width = min_width; + } else if (max_width <= auto_width) { + /* more space than maximum width */ + if (table_width == AUTO) { + /* for auto-width tables, make columns max width */ + for (i = 0; i < columns; i++) { + col[i].width = col[i].max; + } + table_width = max_width; + } else { + /* for fixed-width tables, distribute the extra space + * too */ + unsigned int flexible_columns = 0; + for (i = 0; i != columns; i++) + if (col[i].type != COLUMN_WIDTH_FIXED) + flexible_columns++; + if (flexible_columns == 0) { + int extra = (table_width - max_width) / columns; + remainder = (table_width - max_width) - + (extra * columns); + for (i = 0; i != columns; i++) { + col[i].width = col[i].max + extra; + count -= remainder; + if (count < 0) { + col[i].width++; + count += columns; + } + } + + } else { + int extra = (table_width - max_width) / + flexible_columns; + remainder = (table_width - max_width) - + (extra * flexible_columns); + for (i = 0; i != columns; i++) + if (col[i].type != COLUMN_WIDTH_FIXED) { + col[i].width = col[i].max + + extra; + count -= remainder; + if (count < 0) { + col[i].width++; + count += flexible_columns; + } + } + } + } + } else { + /* space between min and max: fill it exactly */ + float scale = (float) (auto_width - min_width) / + (float) (max_width - min_width); + /* fprintf(stderr, "filling, scale %f\n", scale); */ + for (i = 0; i < columns; i++) { + col[i].width = col[i].min + (int) (0.5 + + (col[i].max - col[i].min) * scale); + } + table_width = auto_width; + } + + xs[0] = x = border_spacing_h; + for (i = 0; i != columns; i++) { + if (!col[i].positioned) + x += col[i].width + border_spacing_h; + xs[i + 1] = x; + row_span[i] = 0; + excess_y[i] = 0; + row_span_cell[i] = 0; + } + + /* position cells */ + table_height = border_spacing_v; + for (row_group = table->children; row_group; + row_group = row_group->next) { + int row_group_height = 0; + for (row = row_group->children; row; row = row->next) { + int row_height = 0; + + htype = css_computed_height(row->style, &value, &unit); + if (htype == CSS_HEIGHT_SET && unit != CSS_UNIT_PCT) { + row_height = FIXTOINT(nscss_len2px( + &content->len_ctx, + value, unit, row->style)); + } + for (c = row->children; c; c = c->next) { + assert(c->style); + c->width = xs[c->start_column + c->columns] - + xs[c->start_column] - + border_spacing_h - + c->border[LEFT].width - + c->padding[LEFT] - + c->padding[RIGHT] - + c->border[RIGHT].width; + c->float_children = 0; + c->cached_place_below_level = 0; + + c->height = AUTO; + if (!layout_block_context(c, -1, content)) { + free(col); + free(excess_y); + free(row_span); + free(row_span_cell); + free(xs); + return false; + } + /* warning: c->descendant_y0 and + * c->descendant_y1 used as temporary storage + * until after vertical alignment is complete */ + c->descendant_y0 = c->height; + c->descendant_y1 = c->padding[BOTTOM]; + + htype = css_computed_height(c->style, + &value, &unit); + + if (htype == CSS_HEIGHT_SET && + unit != CSS_UNIT_PCT) { + /* some sites use height="1" or similar + * to attempt to make cells as small as + * possible, so treat it as a minimum */ + int h = FIXTOINT(nscss_len2px( + &content->len_ctx, + value, unit, c->style)); + if (c->height < h) + c->height = h; + } + /* specified row height is treated as a minimum + */ + if (c->height < row_height) + c->height = row_height; + c->x = xs[c->start_column] + + c->border[LEFT].width; + c->y = c->border[TOP].width; + for (i = 0; i != c->columns; i++) { + row_span[c->start_column + i] = c->rows; + excess_y[c->start_column + i] = + c->border[TOP].width + + c->padding[TOP] + + c->height + + c->padding[BOTTOM] + + c->border[BOTTOM].width; + row_span_cell[c->start_column + i] = 0; + } + row_span_cell[c->start_column] = c; + c->padding[BOTTOM] = -border_spacing_v - + c->border[TOP].width - + c->padding[TOP] - + c->height - + c->border[BOTTOM].width; + } + for (i = 0; i != columns; i++) + if (row_span[i] != 0) + row_span[i]--; + else + row_span_cell[i] = 0; + if (row->next || row_group->next) { + /* row height is greatest excess of a cell + * which ends in this row */ + for (i = 0; i != columns; i++) + if (row_span[i] == 0 && row_height < + excess_y[i]) + row_height = excess_y[i]; + } else { + /* except in the last row */ + for (i = 0; i != columns; i++) + if (row_height < excess_y[i]) + row_height = excess_y[i]; + } + for (i = 0; i != columns; i++) { + if (row_height < excess_y[i]) + excess_y[i] -= row_height; + else + excess_y[i] = 0; + if (row_span_cell[i] != 0) + row_span_cell[i]->padding[BOTTOM] += + row_height + + border_spacing_v; + } + + row->x = 0; + row->y = row_group_height; + row->width = table_width; + row->height = row_height; + row_group_height += row_height + border_spacing_v; + } + row_group->x = 0; + row_group->y = table_height; + row_group->width = table_width; + row_group->height = row_group_height; + table_height += row_group_height; + } + /* Table height is either the height of the contents, or specified + * height if greater */ + table_height = max(table_height, min_height); + /** \todo distribute spare height over the row groups / rows / cells */ + + /* perform vertical alignment */ + for (row_group = table->children; row_group; + row_group = row_group->next) { + for (row = row_group->children; row; row = row->next) { + for (c = row->children; c; c = c->next) { + enum css_vertical_align_e vertical_align; + + /* unextended bottom padding is in + * c->descendant_y1, and unextended + * cell height is in c->descendant_y0 */ + spare_height = (c->padding[BOTTOM] - + c->descendant_y1) + + (c->height - c->descendant_y0); + + vertical_align = css_computed_vertical_align( + c->style, &value, &unit); + + switch (vertical_align) { + case CSS_VERTICAL_ALIGN_SUB: + case CSS_VERTICAL_ALIGN_SUPER: + case CSS_VERTICAL_ALIGN_TEXT_TOP: + case CSS_VERTICAL_ALIGN_TEXT_BOTTOM: + case CSS_VERTICAL_ALIGN_SET: + case CSS_VERTICAL_ALIGN_BASELINE: + /* todo: baseline alignment, for now + * just use ALIGN_TOP */ + case CSS_VERTICAL_ALIGN_TOP: + break; + case CSS_VERTICAL_ALIGN_MIDDLE: + c->padding[TOP] += spare_height / 2; + c->padding[BOTTOM] -= spare_height / 2; + layout_move_children(c, 0, + spare_height / 2); + break; + case CSS_VERTICAL_ALIGN_BOTTOM: + c->padding[TOP] += spare_height; + c->padding[BOTTOM] -= spare_height; + layout_move_children(c, 0, + spare_height); + break; + case CSS_VERTICAL_ALIGN_INHERIT: + assert(0); + break; + } + } + } + } + + /* Top and bottom margins of 'auto' are set to 0. CSS2.1 10.6.3 */ + if (table->margin[TOP] == AUTO) + table->margin[TOP] = 0; + if (table->margin[BOTTOM] == AUTO) + table->margin[BOTTOM] = 0; + + free(col); + free(excess_y); + free(row_span); + free(row_span_cell); + free(xs); + + table->width = table_width; + table->height = table_height; + + return true; +} + + +/** + * Manimpulate box height according to CSS min-height and max-height properties + * + * \param len_ctx CSS length conversion context for document. + * \param box block to modify with any min-height or max-height + * \param container containing block for absolutely positioned elements, or + * NULL for non absolutely positioned elements. + * \return whether the height has been changed + */ +static bool layout_apply_minmax_height( + const nscss_len_ctx *len_ctx, + struct box *box, + struct box *container) +{ + int h; + struct box *containing_block = NULL; + bool updated = false; + + /* Find containing block for percentage heights */ + if (box->style != NULL && css_computed_position(box->style) == + CSS_POSITION_ABSOLUTE) { + /* Box is absolutely positioned */ + assert(container); + containing_block = container; + } else if (box->float_container && box->style != NULL && + (css_computed_float(box->style) == CSS_FLOAT_LEFT || + css_computed_float(box->style) == CSS_FLOAT_RIGHT)) { + /* Box is a float */ + assert(box->parent && box->parent->parent && + box->parent->parent->parent); + containing_block = box->parent->parent->parent; + } else if (box->parent && box->parent->type != BOX_INLINE_CONTAINER) { + /* Box is a block level element */ + containing_block = box->parent; + } else if (box->parent && box->parent->type == BOX_INLINE_CONTAINER) { + /* Box is an inline block */ + assert(box->parent->parent); + containing_block = box->parent->parent; + } + + if (box->style) { + enum css_height_e htype = CSS_HEIGHT_AUTO; + css_fixed value = 0; + css_unit unit = CSS_UNIT_PX; + + if (containing_block) { + htype = css_computed_height(containing_block->style, + &value, &unit); + } + + /* max-height */ + if (css_computed_max_height(box->style, &value, &unit) == + CSS_MAX_HEIGHT_SET) { + if (unit == CSS_UNIT_PCT) { + if (containing_block && + containing_block->height != AUTO && + (css_computed_position(box->style) == + CSS_POSITION_ABSOLUTE || + htype == CSS_HEIGHT_SET)) { + /* Box is absolutely positioned or its + * containing block has a valid + * specified height. (CSS 2.1 + * Section 10.5) */ + h = FPCT_OF_INT_TOINT(value, + containing_block->height); + if (h < box->height) { + box->height = h; + updated = true; + } + } + } else { + h = FIXTOINT(nscss_len2px(len_ctx, + value, unit, box->style)); + if (h < box->height) { + box->height = h; + updated = true; + } + } + } + + /* min-height */ + if (ns_computed_min_height(box->style, &value, &unit) == + CSS_MIN_HEIGHT_SET) { + if (unit == CSS_UNIT_PCT) { + if (containing_block && + containing_block->height != AUTO && + (css_computed_position(box->style) == + CSS_POSITION_ABSOLUTE || + htype == CSS_HEIGHT_SET)) { + /* Box is absolutely positioned or its + * containing block has a valid + * specified height. (CSS 2.1 + * Section 10.5) */ + h = FPCT_OF_INT_TOINT(value, + containing_block->height); + if (h > box->height) { + box->height = h; + updated = true; + } + } + } else { + h = FIXTOINT(nscss_len2px(len_ctx, + value, unit, box->style)); + if (h > box->height) { + box->height = h; + updated = true; + } + } + } + } + return updated; +} + + +/** + * Layout a block which contains an object. + * + * \param block box of type BLOCK, INLINE_BLOCK, TABLE, or TABLE_CELL + * \return true on success, false on memory exhaustion + */ +static bool layout_block_object(struct box *block) +{ + assert(block); + assert(block->type == BOX_BLOCK || + block->type == BOX_INLINE_BLOCK || + block->type == BOX_TABLE || + block->type == BOX_TABLE_CELL); + assert(block->object); + + NSLOG(layout, DEBUG, "block %p, object %p, width %i", block, + hlcache_handle_get_url(block->object), block->width); + + if (content_get_type(block->object) == CONTENT_HTML) { + content_reformat(block->object, false, block->width, 1); + } else { + /* Non-HTML objects */ + /* this case handled already in + * layout_block_find_dimensions() */ + } + + return true; +} + + +/** + * Insert a float into a container. + * + * \param cont block formatting context block, used to contain float + * \param b box to add to float + * + * This sorts floats in order of descending bottom edges. + */ +static void add_float_to_container(struct box *cont, struct box *b) +{ + struct box *box = cont->float_children; + int b_bottom = b->y + b->height; + + assert(b->type == BOX_FLOAT_LEFT || b->type == BOX_FLOAT_RIGHT); + + if (box == NULL) { + /* No other float children */ + b->next_float = NULL; + cont->float_children = b; + return; + } else if (b_bottom >= box->y + box->height) { + /* Goes at start of list */ + b->next_float = cont->float_children; + cont->float_children = b; + } else { + struct box *prev = NULL; + while (box != NULL && b_bottom < box->y + box->height) { + prev = box; + box = box->next_float; + } + if (prev != NULL) { + b->next_float = prev->next_float; + prev->next_float = b; + } + } +} + + +/** + * Split a text box. + * + * \param content memory pool for any new boxes + * \param fstyle style for text in text box + * \param split_box box with text to split + * \param new_length new length for text in split_box, after splitting + * \param new_width new width for text in split_box, after splitting + * \return true on success, false on memory exhaustion + * + * A new box is created and inserted into the box tree after split_box, + * containing the text after new_length excluding the initial space character. + */ +static bool +layout_text_box_split(html_content *content, + plot_font_style_t *fstyle, + struct box *split_box, + size_t new_length, + int new_width) +{ + int space_width = split_box->space; + struct box *c2; + const struct gui_layout_table *font_func = content->font_func; + bool space = (split_box->text[new_length] == ' '); + int used_length = new_length + (space ? 1 : 0); + + if ((space && space_width == 0) || space_width == UNKNOWN_WIDTH) { + /* We're need to add a space, and we don't know how big + * it's to be, OR we have a space of unknown width anyway; + * Calculate space width */ + font_func->width(fstyle, " ", 1, &space_width); + } + + if (split_box->space == UNKNOWN_WIDTH) + split_box->space = space_width; + if (!space) + space_width = 0; + + /* Create clone of split_box, c2 */ + c2 = talloc_memdup(content->bctx, split_box, sizeof *c2); + if (!c2) + return false; + c2->flags |= CLONE; + + /* Set remaining text in c2 */ + c2->text += used_length; + + /* Set c2 according to the remaining text */ + c2->width -= new_width + space_width; + c2->flags &= ~MEASURED; /* width has been estimated */ + c2->length = split_box->length - used_length; + + /* Update split_box for its reduced text */ + split_box->width = new_width; + split_box->flags |= MEASURED; + split_box->length = new_length; + split_box->space = space_width; + + /* Insert c2 into box list */ + c2->next = split_box->next; + split_box->next = c2; + c2->prev = split_box; + if (c2->next) + c2->next->prev = c2; + else + c2->parent->last = c2; + + NSLOG(layout, DEBUG, + "split_box %p len: %" PRIsizet " \"%.*s\"", + split_box, + split_box->length, + (int)split_box->length, + split_box->text); + NSLOG(layout, DEBUG, + " new_box %p len: %" PRIsizet " \"%.*s\"", + c2, + c2->length, + (int)c2->length, + c2->text); + + return true; +} + + +/** + * Compute dimensions of box, margins, paddings, and borders for a floating + * element using shrink-to-fit. Also used for inline-blocks. + * + * \param len_ctx CSS length conversion context for document. + * \param available_width Max width available in pixels + * \param style Box's style + * \param box Box for which to find dimensions + * Box margins, borders, paddings, width and + * height are updated. + */ +static void +layout_float_find_dimensions( + const nscss_len_ctx *len_ctx, + int available_width, + const css_computed_style *style, + struct box *box) +{ + int width, height, max_width, min_width, max_height, min_height; + int *margin = box->margin; + int *padding = box->padding; + struct box_border *border = box->border; + enum css_overflow_e overflow_x = css_computed_overflow_x(style); + enum css_overflow_e overflow_y = css_computed_overflow_y(style); + int scrollbar_width_x = + (overflow_x == CSS_OVERFLOW_SCROLL || + overflow_x == CSS_OVERFLOW_AUTO) ? + SCROLLBAR_WIDTH : 0; + int scrollbar_width_y = + (overflow_y == CSS_OVERFLOW_SCROLL || + overflow_y == CSS_OVERFLOW_AUTO) ? + SCROLLBAR_WIDTH : 0; + + layout_find_dimensions(len_ctx, available_width, -1, box, style, + &width, &height, &max_width, &min_width, + &max_height, &min_height, margin, padding, border); + + if (margin[LEFT] == AUTO) + margin[LEFT] = 0; + if (margin[RIGHT] == AUTO) + margin[RIGHT] = 0; + + if (box->gadget == NULL) { + padding[RIGHT] += scrollbar_width_y; + padding[BOTTOM] += scrollbar_width_x; + } + + if (box->object && !(box->flags & REPLACE_DIM) && + content_get_type(box->object) != CONTENT_HTML) { + /* Floating replaced element, with intrinsic width or height. + * See 10.3.6 and 10.6.2 */ + layout_get_object_dimensions(box, &width, &height, + min_width, max_width, min_height, max_height); + } else if (box->gadget && (box->gadget->type == GADGET_TEXTBOX || + box->gadget->type == GADGET_PASSWORD || + box->gadget->type == GADGET_FILE || + box->gadget->type == GADGET_TEXTAREA)) { + css_fixed size = 0; + css_unit unit = CSS_UNIT_EM; + + /* Give sensible dimensions to gadgets, with auto width/height, + * that don't shrink to fit contained text. */ + assert(box->style); + + if (box->gadget->type == GADGET_TEXTBOX || + box->gadget->type == GADGET_PASSWORD || + box->gadget->type == GADGET_FILE) { + if (width == AUTO) { + size = INTTOFIX(10); + width = FIXTOINT(nscss_len2px(len_ctx, + size, unit, box->style)); + } + if (box->gadget->type == GADGET_FILE && + height == AUTO) { + size = FLTTOFIX(1.5); + height = FIXTOINT(nscss_len2px(len_ctx, + size, unit, box->style)); + } + } + if (box->gadget->type == GADGET_TEXTAREA) { + if (width == AUTO) { + size = INTTOFIX(10); + width = FIXTOINT(nscss_len2px(len_ctx, + size, unit, box->style)); + } + if (height == AUTO) { + size = INTTOFIX(4); + height = FIXTOINT(nscss_len2px(len_ctx, + size, unit, box->style)); + } + } + } else if (width == AUTO) { + /* CSS 2.1 section 10.3.5 */ + width = min(max(box->min_width, available_width), + box->max_width); + + /* width includes margin, borders and padding */ + if (width == available_width) { + width -= box->margin[LEFT] + box->border[LEFT].width + + box->padding[LEFT] + + box->padding[RIGHT] + + box->border[RIGHT].width + + box->margin[RIGHT]; + } else { + /* width was obtained from a min_width or max_width + * value, so need to use the same method for calculating + * mbp as was used in layout_minmax_block() */ + int fixed = 0; + float frac = 0; + calculate_mbp_width(len_ctx, box->style, LEFT, + true, true, true, &fixed, &frac); + calculate_mbp_width(len_ctx, box->style, RIGHT, + true, true, true, &fixed, &frac); + if (fixed < 0) + fixed = 0; + + width -= fixed; + } + + if (max_width >= 0 && width > max_width) width = max_width; + if (min_width > 0 && width < min_width) width = min_width; + + } else { + if (max_width >= 0 && width > max_width) width = max_width; + if (min_width > 0 && width < min_width) width = min_width; + width -= scrollbar_width_y; + } + + box->width = width; + box->height = height; + + if (margin[TOP] == AUTO) + margin[TOP] = 0; + if (margin[BOTTOM] == AUTO) + margin[BOTTOM] = 0; +} + + +/** + * Layout the contents of a float or inline block. + * + * \param b float or inline block box + * \param width available width + * \param content memory pool for any new boxes + * \return true on success, false on memory exhaustion + */ +static bool layout_float(struct box *b, int width, html_content *content) +{ + assert(b->type == BOX_TABLE || b->type == BOX_BLOCK || + b->type == BOX_INLINE_BLOCK); + layout_float_find_dimensions(&content->len_ctx, width, b->style, b); + if (b->type == BOX_TABLE) { + if (!layout_table(b, width, content)) + return false; + if (b->margin[LEFT] == AUTO) + b->margin[LEFT] = 0; + if (b->margin[RIGHT] == AUTO) + b->margin[RIGHT] = 0; + if (b->margin[TOP] == AUTO) + b->margin[TOP] = 0; + if (b->margin[BOTTOM] == AUTO) + b->margin[BOTTOM] = 0; + } else + return layout_block_context(b, -1, content); + return true; +} + + +/** + * Position a float in the first available space. + * + * \param c float box to position + * \param width available width + * \param cx x coordinate relative to cont to place float right of + * \param y y coordinate relative to cont to place float below + * \param cont ancestor box which defines horizontal space, for floats + */ +static void +place_float_below(struct box *c, int width, int cx, int y, struct box *cont) +{ + int x0, x1, yy; + struct box *left; + struct box *right; + + yy = y > cont->cached_place_below_level ? + y : cont->cached_place_below_level; + + NSLOG(layout, DEBUG, + "c %p, width %i, cx %i, y %i, cont %p", c, + width, cx, y, cont); + + do { + y = yy; + x0 = cx; + x1 = cx + width; + find_sides(cont->float_children, y, y + c->height, &x0, &x1, + &left, &right); + if (left != 0 && right != 0) { + yy = (left->y + left->height < + right->y + right->height ? + left->y + left->height : + right->y + right->height); + } else if (left == 0 && right != 0) { + yy = right->y + right->height; + } else if (left != 0 && right == 0) { + yy = left->y + left->height; + } + } while ((left != 0 || right != 0) && (c->width > x1 - x0)); + + if (c->type == BOX_FLOAT_LEFT) { + c->x = x0; + } else { + c->x = x1 - c->width; + } + c->y = y; + cont->cached_place_below_level = y; +} + + +/** + * Calculate line height from a style. + */ +static int line_height( + const nscss_len_ctx *len_ctx, + const css_computed_style *style) +{ + enum css_line_height_e lhtype; + css_fixed lhvalue = 0; + css_unit lhunit = CSS_UNIT_PX; + css_fixed line_height; + + assert(style); + + lhtype = css_computed_line_height(style, &lhvalue, &lhunit); + if (lhtype == CSS_LINE_HEIGHT_NORMAL) { + /* Normal => use a constant of 1.3 * font-size */ + lhvalue = FLTTOFIX(1.3); + lhtype = CSS_LINE_HEIGHT_NUMBER; + } + + if (lhtype == CSS_LINE_HEIGHT_NUMBER || + lhunit == CSS_UNIT_PCT) { + line_height = nscss_len2px(len_ctx, + lhvalue, CSS_UNIT_EM, style); + + if (lhtype != CSS_LINE_HEIGHT_NUMBER) + line_height = FDIV(line_height, F_100); + } else { + assert(lhunit != CSS_UNIT_PCT); + + line_height = nscss_len2px(len_ctx, + lhvalue, lhunit, style); + } + + return FIXTOINT(line_height); +} + + +/** + * Position a line of boxes in inline formatting context. + * + * \param first box at start of line + * \param width available width on input, updated with actual width on output + * (may be incorrect if the line gets split?) + * \param y coordinate of top of line, updated on exit to bottom + * \param cx coordinate of left of line relative to cont + * \param cy coordinate of top of line relative to cont + * \param cont ancestor box which defines horizontal space, for floats + * \param indent apply any first-line indent + * \param has_text_children at least one TEXT in the inline_container + * \param next_box updated to first box for next line, or 0 at end + * \param content memory pool for any new boxes + * \return true on success, false on memory exhaustion + */ +static bool +layout_line(struct box *first, + int *width, + int *y, + int cx, + int cy, + struct box *cont, + bool indent, + bool has_text_children, + html_content *content, + struct box **next_box) +{ + int height, used_height; + int x0 = 0; + int x1 = *width; + int x, h, x_previous; + int fy = cy; + struct box *left; + struct box *right; + struct box *b; + struct box *split_box = 0; + struct box *d; + struct box *br_box = 0; + bool move_y = false; + bool place_below = false; + int space_before = 0, space_after = 0; + unsigned int inline_count = 0; + unsigned int i; + const struct gui_layout_table *font_func = content->font_func; + plot_font_style_t fstyle; + + NSLOG(layout, DEBUG, + "first %p, first->text '%.*s', width %i, y %i, cx %i, cy %i", + first, + (int)first->length, + first->text, + *width, + *y, + cx, + cy); + + /* find sides at top of line */ + x0 += cx; + x1 += cx; + find_sides(cont->float_children, cy, cy, &x0, &x1, &left, &right); + x0 -= cx; + x1 -= cx; + + if (indent) + x0 += layout_text_indent(&content->len_ctx, + first->parent->parent->style, *width); + + if (x1 < x0) + x1 = x0; + + /* get minimum line height from containing block. + * this is the line-height if there are text children and also in the + * case of an initially empty text input */ + if (has_text_children || first->parent->parent->gadget) + used_height = height = line_height(&content->len_ctx, + first->parent->parent->style); + else + /* inline containers with no text are usually for layout and + * look better with no minimum line-height */ + used_height = height = 0; + + /* pass 1: find height of line assuming sides at top of line: loop + * body executed at least once + * keep in sync with the loop in layout_minmax_line() */ + + NSLOG(layout, DEBUG, "x0 %i, x1 %i, x1 - x0 %i", x0, x1, x1 - x0); + + + for (x = 0, b = first; x <= x1 - x0 && b != 0; b = b->next) { + int min_width, max_width, min_height, max_height; + + assert(b->type == BOX_INLINE || b->type == BOX_INLINE_BLOCK || + b->type == BOX_FLOAT_LEFT || + b->type == BOX_FLOAT_RIGHT || + b->type == BOX_BR || b->type == BOX_TEXT || + b->type == BOX_INLINE_END); + + + NSLOG(layout, DEBUG, "pass 1: b %p, x %i", b, x); + + + if (b->type == BOX_BR) + break; + + if (b->type == BOX_FLOAT_LEFT || b->type == BOX_FLOAT_RIGHT) + continue; + if (b->type == BOX_INLINE_BLOCK && + (css_computed_position(b->style) == + CSS_POSITION_ABSOLUTE || + css_computed_position(b->style) == + CSS_POSITION_FIXED)) + continue; + + assert(b->style != NULL); + font_plot_style_from_css(&content->len_ctx, b->style, &fstyle); + + x += space_after; + + if (b->type == BOX_INLINE_BLOCK) { + if (b->max_width != UNKNOWN_WIDTH) + if (!layout_float(b, *width, content)) + return false; + h = b->border[TOP].width + b->padding[TOP] + b->height + + b->padding[BOTTOM] + + b->border[BOTTOM].width; + if (height < h) + height = h; + x += b->margin[LEFT] + b->border[LEFT].width + + b->padding[LEFT] + b->width + + b->padding[RIGHT] + + b->border[RIGHT].width + + b->margin[RIGHT]; + space_after = 0; + continue; + } + + if (b->type == BOX_INLINE) { + /* calculate borders, margins, and padding */ + layout_find_dimensions(&content->len_ctx, + *width, -1, b, b->style, 0, 0, 0, 0, + 0, 0, b->margin, b->padding, b->border); + for (i = 0; i != 4; i++) + if (b->margin[i] == AUTO) + b->margin[i] = 0; + x += b->margin[LEFT] + b->border[LEFT].width + + b->padding[LEFT]; + if (b->inline_end) { + b->inline_end->margin[RIGHT] = b->margin[RIGHT]; + b->inline_end->padding[RIGHT] = + b->padding[RIGHT]; + b->inline_end->border[RIGHT] = + b->border[RIGHT]; + } else { + x += b->padding[RIGHT] + + b->border[RIGHT].width + + b->margin[RIGHT]; + } + } else if (b->type == BOX_INLINE_END) { + b->width = 0; + if (b->space == UNKNOWN_WIDTH) { + font_func->width(&fstyle, " ", 1, &b->space); + /** \todo handle errors */ + } + space_after = b->space; + + x += b->padding[RIGHT] + b->border[RIGHT].width + + b->margin[RIGHT]; + continue; + } + + if (!b->object && !(b->flags & IFRAME) && !b->gadget && + !(b->flags & REPLACE_DIM)) { + /* inline non-replaced, 10.3.1 and 10.6.1 */ + b->height = line_height(&content->len_ctx, + b->style ? b->style : + b->parent->parent->style); + if (height < b->height) + height = b->height; + + if (!b->text) { + b->width = 0; + space_after = 0; + continue; + } + + if (b->width == UNKNOWN_WIDTH) { + /** \todo handle errors */ + + /* If it's a select element, we must use the + * width of the widest option text */ + if (b->parent->parent->gadget && + b->parent->parent->gadget->type + == GADGET_SELECT) { + int opt_maxwidth = 0; + struct form_option *o; + + for (o = b->parent->parent->gadget-> + data.select.items; o; + o = o->next) { + int opt_width; + font_func->width(&fstyle, + o->text, + strlen(o->text), + &opt_width); + + if (opt_maxwidth < opt_width) + opt_maxwidth =opt_width; + } + b->width = opt_maxwidth; + if (nsoption_bool(core_select_menu)) + b->width += SCROLLBAR_WIDTH; + } else { + font_func->width(&fstyle, b->text, + b->length, &b->width); + b->flags |= MEASURED; + } + } + + /* If the current text has not been measured (i.e. its + * width was estimated after splitting), and it fits on + * the line, measure it properly, so next box is placed + * correctly. */ + if (b->text && (x + b->width < x1 - x0) && + !(b->flags & MEASURED) && + b->next) { + font_func->width(&fstyle, b->text, + b->length, &b->width); + b->flags |= MEASURED; + } + + x += b->width; + if (b->space == UNKNOWN_WIDTH) { + font_func->width(&fstyle, " ", 1, &b->space); + /** \todo handle errors */ + } + space_after = b->space; + continue; + } + + space_after = 0; + + /* inline replaced, 10.3.2 and 10.6.2 */ + assert(b->style); + + layout_find_dimensions(&content->len_ctx, + *width, -1, b, b->style, + &b->width, &b->height, + &max_width, &min_width, + &max_height, &min_height, + NULL, NULL, NULL); + + if (b->object && !(b->flags & REPLACE_DIM)) { + layout_get_object_dimensions(b, &b->width, &b->height, + min_width, max_width, + min_height, max_height); + } else if (b->flags & IFRAME) { + /* TODO: should we look at the content dimensions? */ + if (b->width == AUTO) + b->width = 400; + if (b->height == AUTO) + b->height = 300; + + /* We reformat the iframe browser window to new + * dimensions in pass 2 */ + } else { + /* form control with no object */ + if (b->width == AUTO) + b->width = FIXTOINT(nscss_len2px( + &content->len_ctx, INTTOFIX(1), + CSS_UNIT_EM, b->style)); + if (b->height == AUTO) + b->height = FIXTOINT(nscss_len2px( + &content->len_ctx, INTTOFIX(1), + CSS_UNIT_EM, b->style)); + } + + /* Reformat object to new box size */ + if (b->object && content_get_type(b->object) == CONTENT_HTML && + b->width != + content_get_available_width(b->object)) { + css_fixed value = 0; + css_unit unit = CSS_UNIT_PX; + enum css_height_e htype = css_computed_height(b->style, + &value, &unit); + + content_reformat(b->object, false, b->width, b->height); + + if (htype == CSS_HEIGHT_AUTO) + b->height = content_get_height(b->object); + } + + if (height < b->height) + height = b->height; + + x += b->width; + } + + /* find new sides using this height */ + x0 = cx; + x1 = cx + *width; + find_sides(cont->float_children, cy, cy + height, &x0, &x1, + &left, &right); + x0 -= cx; + x1 -= cx; + + if (indent) + x0 += layout_text_indent(&content->len_ctx, + first->parent->parent->style, *width); + + if (x1 < x0) + x1 = x0; + + space_after = space_before = 0; + + /* pass 2: place boxes in line: loop body executed at least once */ + + NSLOG(layout, DEBUG, "x0 %i, x1 %i, x1 - x0 %i", x0, x1, x1 - x0); + + for (x = x_previous = 0, b = first; x <= x1 - x0 && b; b = b->next) { + + NSLOG(layout, DEBUG, "pass 2: b %p, x %i", b, x); + + if (b->type == BOX_INLINE_BLOCK && + (css_computed_position(b->style) == + CSS_POSITION_ABSOLUTE || + css_computed_position(b->style) == + CSS_POSITION_FIXED)) { + b->x = x + space_after; + + } else if (b->type == BOX_INLINE || + b->type == BOX_INLINE_BLOCK || + b->type == BOX_TEXT || + b->type == BOX_INLINE_END) { + assert(b->width != UNKNOWN_WIDTH); + + x_previous = x; + x += space_after; + b->x = x; + + if ((b->type == BOX_INLINE && !b->inline_end) || + b->type == BOX_INLINE_BLOCK) { + b->x += b->margin[LEFT] + b->border[LEFT].width; + x = b->x + b->padding[LEFT] + b->width + + b->padding[RIGHT] + + b->border[RIGHT].width + + b->margin[RIGHT]; + } else if (b->type == BOX_INLINE) { + b->x += b->margin[LEFT] + b->border[LEFT].width; + x = b->x + b->padding[LEFT] + b->width; + } else if (b->type == BOX_INLINE_END) { + b->height = b->inline_end->height; + x += b->padding[RIGHT] + + b->border[RIGHT].width + + b->margin[RIGHT]; + } else { + x += b->width; + } + + space_before = space_after; + if (b->object || b->flags & REPLACE_DIM || + b->flags & IFRAME) + space_after = 0; + else if (b->text || b->type == BOX_INLINE_END) { + if (b->space == UNKNOWN_WIDTH) { + font_plot_style_from_css( + &content->len_ctx, + b->style, &fstyle); + /** \todo handle errors */ + font_func->width(&fstyle, " ", 1, + &b->space); + } + space_after = b->space; + } else { + space_after = 0; + } + split_box = b; + move_y = true; + inline_count++; + } else if (b->type == BOX_BR) { + b->x = x; + b->width = 0; + br_box = b; + b = b->next; + split_box = 0; + move_y = true; + break; + + } else { + /* float */ + NSLOG(layout, DEBUG, "float %p", b); + + d = b->children; + d->float_children = 0; + d->cached_place_below_level = 0; + b->float_container = d->float_container = cont; + + if (!layout_float(d, *width, content)) + return false; + + NSLOG(layout, DEBUG, + "%p : %d %d", + d, + d->margin[TOP], + d->border[TOP].width); + + d->x = d->margin[LEFT] + d->border[LEFT].width; + d->y = d->margin[TOP] + d->border[TOP].width; + b->width = d->margin[LEFT] + d->border[LEFT].width + + d->padding[LEFT] + d->width + + d->padding[RIGHT] + + d->border[RIGHT].width + + d->margin[RIGHT]; + b->height = d->margin[TOP] + d->border[TOP].width + + d->padding[TOP] + d->height + + d->padding[BOTTOM] + + d->border[BOTTOM].width + + d->margin[BOTTOM]; + + if (b->width > (x1 - x0) - x) + place_below = true; + if (d->style && (css_computed_clear(d->style) == + CSS_CLEAR_NONE || + (css_computed_clear(d->style) == + CSS_CLEAR_LEFT && left == 0) || + (css_computed_clear(d->style) == + CSS_CLEAR_RIGHT && + right == 0) || + (css_computed_clear(d->style) == + CSS_CLEAR_BOTH && + left == 0 && right == 0)) && + (!place_below || + (left == 0 && right == 0 && x == 0)) && + cy >= cont->clear_level && + cy >= cont->cached_place_below_level) { + /* + not cleared or, + * cleared and there are no floats to clear + * + fits without needing to be placed below or, + * this line is empty with no floats + * + current y, cy, is below the clear level + * + * Float affects current line */ + if (b->type == BOX_FLOAT_LEFT) { + b->x = cx + x0; + if (b->width > 0) + x0 += b->width; + left = b; + } else { + b->x = cx + x1 - b->width; + if (b->width > 0) + x1 -= b->width; + right = b; + } + b->y = cy; + } else { + /* cleared or doesn't fit on line */ + /* place below into next available space */ + int fcy = (cy > cont->clear_level) ? cy : + cont->clear_level; + fcy = (fcy > cont->cached_place_below_level) ? + fcy : + cont->cached_place_below_level; + fy = (fy > fcy) ? fy : fcy; + fy = (fy == cy) ? fy + height : fy; + + place_float_below(b, *width, cx, fy, cont); + fy = b->y; + if (d->style && ( + (css_computed_clear(d->style) == + CSS_CLEAR_LEFT && left != 0) || + (css_computed_clear(d->style) == + CSS_CLEAR_RIGHT && + right != 0) || + (css_computed_clear(d->style) == + CSS_CLEAR_BOTH && + (left != 0 || right != 0)))) { + /* to be cleared below existing + * floats */ + if (b->type == BOX_FLOAT_LEFT) + b->x = cx; + else + b->x = cx + *width - b->width; + + fcy = layout_clear(cont->float_children, + css_computed_clear(d->style)); + if (fcy > cont->clear_level) + cont->clear_level = fcy; + if (b->y < fcy) + b->y = fcy; + } + if (b->type == BOX_FLOAT_LEFT) + left = b; + else + right = b; + } + add_float_to_container(cont, b); + + split_box = 0; + } + } + + if (x1 - x0 < x && split_box) { + /* the last box went over the end */ + size_t split = 0; + int w; + bool no_wrap = css_computed_white_space( + split_box->style) == CSS_WHITE_SPACE_NOWRAP || + css_computed_white_space( + split_box->style) == CSS_WHITE_SPACE_PRE; + + x = x_previous; + + if (!no_wrap && + (split_box->type == BOX_INLINE || + split_box->type == BOX_TEXT) && + !split_box->object && + !(split_box->flags & REPLACE_DIM) && + !(split_box->flags & IFRAME) && + !split_box->gadget && split_box->text) { + + font_plot_style_from_css(&content->len_ctx, + split_box->style, &fstyle); + /** \todo handle errors */ + font_func->split(&fstyle, + split_box->text, + split_box->length, + x1 - x0 - x - space_before, + &split, + &w); + } + + /* split == 0 implies that text can't be split */ + + if (split == 0) + w = split_box->width; + + + NSLOG(layout, DEBUG, + "splitting: split_box %p \"%.*s\", spilt %zu, w %i, " + "left %p, right %p, inline_count %u", + split_box, + (int)split_box->length, + split_box->text, + split, + w, + left, + right, + inline_count); + + if ((split == 0 || x1 - x0 <= x + space_before + w) && + !left && !right && inline_count == 1) { + /* first word of box doesn't fit, but no floats and + * first box on line so force in */ + if (split == 0 || split == split_box->length) { + /* only one word in this box, or not text + * or white-space:nowrap */ + b = split_box->next; + } else { + /* cut off first word for this line */ + if (!layout_text_box_split(content, &fstyle, + split_box, split, w)) + return false; + b = split_box->next; + } + x += space_before + w; + + NSLOG(layout, DEBUG, "forcing"); + + } else if ((split == 0 || x1 - x0 <= x + space_before + w) && + inline_count == 1) { + /* first word of first box doesn't fit, but a float is + * taking some of the width so move below it */ + assert(left || right); + used_height = 0; + if (left) { + + NSLOG(layout, DEBUG, + "cy %i, left->y %i, left->height %i", + cy, + left->y, + left->height); + + used_height = left->y + left->height - cy + 1; + + NSLOG(layout, DEBUG, "used_height %i", + used_height); + + } + if (right && used_height < + right->y + right->height - cy + 1) + used_height = right->y + right->height - cy + 1; + + if (used_height < 0) + used_height = 0; + + b = split_box; + + NSLOG(layout, DEBUG, "moving below float"); + + } else if (split == 0 || x1 - x0 <= x + space_before + w) { + /* first word of box doesn't fit so leave box for next + * line */ + b = split_box; + + NSLOG(layout, DEBUG, "leaving for next line"); + + } else { + /* fit as many words as possible */ + assert(split != 0); + + NSLOG(layout, DEBUG, "'%.*s' %i %zu %i", + (int)split_box->length, split_box->text, + x1 - x0, split, w); + + if (split != split_box->length) { + if (!layout_text_box_split(content, &fstyle, + split_box, split, w)) + return false; + b = split_box->next; + } + x += space_before + w; + + NSLOG(layout, DEBUG, "fitting words"); + + } + move_y = true; + } + + /* set positions */ + switch (css_computed_text_align(first->parent->parent->style)) { + case CSS_TEXT_ALIGN_RIGHT: + case CSS_TEXT_ALIGN_LIBCSS_RIGHT: + x0 = x1 - x; + break; + case CSS_TEXT_ALIGN_CENTER: + case CSS_TEXT_ALIGN_LIBCSS_CENTER: + x0 = (x0 + (x1 - x)) / 2; + break; + case CSS_TEXT_ALIGN_LEFT: + case CSS_TEXT_ALIGN_LIBCSS_LEFT: + case CSS_TEXT_ALIGN_JUSTIFY: + /* leave on left */ + break; + case CSS_TEXT_ALIGN_DEFAULT: + /* None; consider text direction */ + switch (css_computed_direction(first->parent->parent->style)) { + case CSS_DIRECTION_LTR: + /* leave on left */ + break; + case CSS_DIRECTION_RTL: + x0 = x1 - x; + break; + } + break; + } + + for (d = first; d != b; d = d->next) { + d->flags &= ~NEW_LINE; + + if (d->type == BOX_INLINE_BLOCK && + (css_computed_position(d->style) == + CSS_POSITION_ABSOLUTE || + css_computed_position(d->style) == + CSS_POSITION_FIXED)) { + /* positioned inline-blocks: + * set static position (x,y) only, rest of positioning + * is handled later */ + d->x += x0; + d->y = *y; + continue; + } else if ((d->type == BOX_INLINE && + ((d->object || d->gadget) == false) && + !(d->flags & IFRAME) && + !(d->flags & REPLACE_DIM)) || + d->type == BOX_BR || + d->type == BOX_TEXT || + d->type == BOX_INLINE_END) { + /* regular (non-replaced) inlines */ + d->x += x0; + d->y = *y - d->padding[TOP]; + + if (d->type == BOX_TEXT && d->height > used_height) { + /* text */ + used_height = d->height; + } + } else if ((d->type == BOX_INLINE) || + d->type == BOX_INLINE_BLOCK) { + /* replaced inlines and inline-blocks */ + d->x += x0; + d->y = *y + d->border[TOP].width + d->margin[TOP]; + h = d->margin[TOP] + d->border[TOP].width + + d->padding[TOP] + d->height + + d->padding[BOTTOM] + + d->border[BOTTOM].width + + d->margin[BOTTOM]; + if (used_height < h) + used_height = h; + } + } + + first->flags |= NEW_LINE; + + assert(b != first || (move_y && 0 < used_height && (left || right))); + + /* handle vertical-align by adjusting box y values */ + /** \todo proper vertical alignment handling */ + for (d = first; d != b; d = d->next) { + if ((d->type == BOX_INLINE && d->inline_end) || + d->type == BOX_BR || + d->type == BOX_TEXT || + d->type == BOX_INLINE_END) { + css_fixed value = 0; + css_unit unit = CSS_UNIT_PX; + switch (css_computed_vertical_align(d->style, &value, + &unit)) { + case CSS_VERTICAL_ALIGN_SUPER: + case CSS_VERTICAL_ALIGN_TOP: + case CSS_VERTICAL_ALIGN_TEXT_TOP: + /* already at top */ + break; + case CSS_VERTICAL_ALIGN_SUB: + case CSS_VERTICAL_ALIGN_BOTTOM: + case CSS_VERTICAL_ALIGN_TEXT_BOTTOM: + d->y += used_height - d->height; + break; + default: + case CSS_VERTICAL_ALIGN_BASELINE: + d->y += 0.75 * (used_height - d->height); + break; + } + } + } + + /* handle clearance for br */ + if (br_box && css_computed_clear(br_box->style) != CSS_CLEAR_NONE) { + int clear_y = layout_clear(cont->float_children, + css_computed_clear(br_box->style)); + if (used_height < clear_y - cy) + used_height = clear_y - cy; + } + + if (move_y) + *y += used_height; + *next_box = b; + *width = x; /* return actual width */ + return true; +} + + +/** + * Layout lines of text or inline boxes with floats. + * + * \param box inline container box + * \param width horizontal space available + * \param cont ancestor box which defines horizontal space, for floats + * \param cx box position relative to cont + * \param cy box position relative to cont + * \param content memory pool for any new boxes + * \return true on success, false on memory exhaustion + */ +static bool layout_inline_container(struct box *inline_container, int width, + struct box *cont, int cx, int cy, html_content *content) +{ + bool first_line = true; + bool has_text_children; + struct box *c, *next; + int y = 0; + int curwidth,maxwidth = width; + + assert(inline_container->type == BOX_INLINE_CONTAINER); + + NSLOG(layout, DEBUG, + "inline_container %p, width %i, cont %p, cx %i, cy %i", + inline_container, + width, + cont, + cx, + cy); + + + has_text_children = false; + for (c = inline_container->children; c; c = c->next) { + bool is_pre = false; + + if (c->style) { + enum css_white_space_e whitespace; + + whitespace = css_computed_white_space(c->style); + + is_pre = (whitespace == CSS_WHITE_SPACE_PRE || + whitespace == CSS_WHITE_SPACE_PRE_LINE || + whitespace == CSS_WHITE_SPACE_PRE_WRAP); + } + + if ((!c->object && !(c->flags & REPLACE_DIM) && + !(c->flags & IFRAME) && + c->text && (c->length || is_pre)) || + c->type == BOX_BR) + has_text_children = true; + } + + /** \todo fix wrapping so that a box with horizontal scrollbar will + * shrink back to 'width' if no word is wider than 'width' (Or just set + * curwidth = width and have the multiword lines wrap to the min width) + */ + for (c = inline_container->children; c; ) { + + NSLOG(layout, DEBUG, "c %p", c); + + curwidth = inline_container->width; + if (!layout_line(c, &curwidth, &y, cx, cy + y, cont, first_line, + has_text_children, content, &next)) + return false; + maxwidth = max(maxwidth,curwidth); + c = next; + first_line = false; + } + + inline_container->width = maxwidth; + inline_container->height = y; + + return true; +} + + +/** + * Layout a block formatting context. + * + * \param block BLOCK, INLINE_BLOCK, or TABLE_CELL to layout + * \param viewport_height Height of viewport in pixels or -ve if unknown + * \param content Memory pool for any new boxes + * \return true on success, false on memory exhaustion + * + * This function carries out layout of a block and its children, as described + * in CSS 2.1 9.4.1. + */ +static bool +layout_block_context(struct box *block, + int viewport_height, + html_content *content) +{ + struct box *box; + int cx, cy; /**< current coordinates */ + int max_pos_margin = 0; + int max_neg_margin = 0; + int y = 0; + int lm, rm; + struct box *margin_collapse = NULL; + bool in_margin = false; + css_fixed gadget_size; + css_unit gadget_unit; /* Checkbox / radio buttons */ + + assert(block->type == BOX_BLOCK || + block->type == BOX_INLINE_BLOCK || + block->type == BOX_TABLE_CELL); + assert(block->width != UNKNOWN_WIDTH); + assert(block->width != AUTO); + + block->float_children = NULL; + block->cached_place_below_level = 0; + block->clear_level = 0; + + /* special case if the block contains an object */ + if (block->object) { + int temp_width = block->width; + if (!layout_block_object(block)) + return false; + layout_get_object_dimensions(block, &temp_width, + &block->height, INT_MIN, INT_MAX, + INT_MIN, INT_MAX); + return true; + } else if (block->flags & REPLACE_DIM) { + return true; + } + + /* special case if the block contains an radio button or checkbox */ + if (block->gadget && (block->gadget->type == GADGET_RADIO || + block->gadget->type == GADGET_CHECKBOX)) { + /* form checkbox or radio button + * if width or height is AUTO, set it to 1em */ + gadget_unit = CSS_UNIT_EM; + gadget_size = INTTOFIX(1); + if (block->height == AUTO) + block->height = FIXTOINT(nscss_len2px( + &content->len_ctx, gadget_size, + gadget_unit, block->style)); + } + + box = block->children; + /* set current coordinates to top-left of the block */ + cx = 0; + y = cy = block->padding[TOP]; + if (box) + box->y = block->padding[TOP]; + + /* Step through the descendants of the block in depth-first order, but + * not into the children of boxes which aren't blocks. For example, if + * the tree passed to this function looks like this (box->type shown): + * + * block -> BOX_BLOCK + * BOX_BLOCK * (1) + * BOX_INLINE_CONTAINER * (2) + * BOX_INLINE + * BOX_TEXT + * ... + * BOX_BLOCK * (3) + * BOX_TABLE * (4) + * BOX_TABLE_ROW + * BOX_TABLE_CELL + * ... + * BOX_TABLE_CELL + * ... + * BOX_BLOCK * (5) + * BOX_INLINE_CONTAINER * (6) + * BOX_TEXT + * ... + * then the while loop will visit each box marked with *, setting box + * to each in the order shown. */ + while (box) { + enum css_overflow_e overflow_x = CSS_OVERFLOW_VISIBLE; + enum css_overflow_e overflow_y = CSS_OVERFLOW_VISIBLE; + + assert(box->type == BOX_BLOCK || box->type == BOX_TABLE || + box->type == BOX_INLINE_CONTAINER); + + /* Tables are laid out before being positioned, because the + * position depends on the width which is calculated in + * table layout. Blocks and inline containers are positioned + * before being laid out, because width is not dependent on + * content, and the position is required during layout for + * correct handling of floats. + */ + + if (box->style && + (css_computed_position(box->style) == + CSS_POSITION_ABSOLUTE || + css_computed_position(box->style) == + CSS_POSITION_FIXED)) { + box->x = box->parent->padding[LEFT]; + /* absolute positioned; this element will establish + * its own block context when it gets laid out later, + * so no need to look at its children now. */ + goto advance_to_next_box; + } + + /* If we don't know which box the current margin collapses + * through to, find out. Update the pos/neg margin values. */ + if (margin_collapse == NULL) { + margin_collapse = layout_next_margin_block( + &content->len_ctx, box, block, + viewport_height, + &max_pos_margin, &max_neg_margin); + /* We have a margin that has not yet been applied. */ + in_margin = true; + } + + /* Clearance. */ + y = 0; + if (box->style && css_computed_clear(box->style) != + CSS_CLEAR_NONE) + y = layout_clear(block->float_children, + css_computed_clear(box->style)); + + /* Find box's overflow properties */ + if (box->style) { + overflow_x = css_computed_overflow_x(box->style); + overflow_y = css_computed_overflow_y(box->style); + } + + /* Blocks establishing a block formatting context get minimum + * left and right margins to avoid any floats. */ + lm = rm = 0; + + if (box->type == BOX_BLOCK || box->flags & IFRAME) { + if (!box->object && !(box->flags & IFRAME) && + !(box->flags & REPLACE_DIM) && + box->style && + (overflow_x != CSS_OVERFLOW_VISIBLE || + overflow_y != CSS_OVERFLOW_VISIBLE)) { + /* box establishes new block formatting context + * so available width may be diminished due to + * floats. */ + int x0, x1, top; + struct box *left, *right; + top = cy + max_pos_margin - max_neg_margin; + top = (top > y) ? top : y; + x0 = cx; + x1 = cx + box->parent->width - + box->parent->padding[LEFT] - + box->parent->padding[RIGHT]; + find_sides(block->float_children, top, top, + &x0, &x1, &left, &right); + /* calculate min required left & right margins + * needed to avoid floats */ + lm = x0 - cx; + rm = cx + box->parent->width - + box->parent->padding[LEFT] - + box->parent->padding[RIGHT] - + x1; + } + layout_block_find_dimensions(&content->len_ctx, + box->parent->width, + viewport_height, lm, rm, box); + if (box->type == BOX_BLOCK && !(box->flags & IFRAME)) { + layout_block_add_scrollbar(box, RIGHT); + layout_block_add_scrollbar(box, BOTTOM); + } + } else if (box->type == BOX_TABLE) { + if (box->style != NULL) { + enum css_width_e wtype; + css_fixed width = 0; + css_unit unit = CSS_UNIT_PX; + + wtype = css_computed_width(box->style, &width, + &unit); + + if (wtype == CSS_WIDTH_AUTO) { + /* max available width may be + * diminished due to floats. */ + int x0, x1, top; + struct box *left, *right; + top = cy + max_pos_margin - + max_neg_margin; + top = (top > y) ? top : y; + x0 = cx; + x1 = cx + box->parent->width - + box->parent->padding[LEFT] - + box->parent->padding[RIGHT]; + find_sides(block->float_children, + top, top, &x0, &x1, + &left, &right); + /* calculate min required left & right + * margins needed to avoid floats */ + lm = x0 - cx; + rm = cx + box->parent->width - + box->parent->padding[LEFT] - + box->parent->padding[RIGHT] - + x1; + } + } + if (!layout_table(box, box->parent->width - lm - rm, + content)) + return false; + layout_solve_width(box, box->parent->width, box->width, + lm, rm, -1, -1); + } + + /* Position box: horizontal. */ + box->x = box->parent->padding[LEFT] + box->margin[LEFT] + + box->border[LEFT].width; + cx += box->x; + + /* Position box: vertical. */ + if (box->border[TOP].width) { + box->y += box->border[TOP].width; + cy += box->border[TOP].width; + } + + /* Vertical margin */ + if (((box->type == BOX_BLOCK && + (box->flags & HAS_HEIGHT)) || + box->type == BOX_TABLE || + (box->type == BOX_INLINE_CONTAINER && + box != box->parent->children) || + margin_collapse == box) && + in_margin == true) { + /* Margin goes above this box. */ + cy += max_pos_margin - max_neg_margin; + box->y += max_pos_margin - max_neg_margin; + + /* Current margin has been applied. */ + in_margin = false; + max_pos_margin = max_neg_margin = 0; + } + + /* Handle clearance */ + if (box->type != BOX_INLINE_CONTAINER && + (y > 0) && (cy < y)) { + /* box clears something*/ + box->y += y - cy; + cy = y; + } + + /* Unless the box has an overflow style of visible, the box + * establishes a new block context. */ + if (box->type == BOX_BLOCK && box->style && + (overflow_x != CSS_OVERFLOW_VISIBLE || + overflow_y != CSS_OVERFLOW_VISIBLE)) { + + layout_block_context(box, viewport_height, content); + + cy += box->padding[TOP]; + + if (box->height == AUTO) { + box->height = 0; + layout_block_add_scrollbar(box, BOTTOM); + } + + cx -= box->x; + cy += box->height + box->padding[BOTTOM] + + box->border[BOTTOM].width; + y = box->y + box->padding[TOP] + box->height + + box->padding[BOTTOM] + + box->border[BOTTOM].width; + + /* Skip children, because they are done in the new + * block context */ + goto advance_to_next_box; + } + + NSLOG(layout, DEBUG, "box %p, cx %i, cy %i", box, cx, cy); + + /* Layout (except tables). */ + if (box->object) { + if (!layout_block_object(box)) + return false; + + } else if (box->type == BOX_INLINE_CONTAINER) { + box->width = box->parent->width; + if (!layout_inline_container(box, box->width, block, + cx, cy, content)) + return false; + + } else if (box->type == BOX_TABLE) { + /* Move down to avoid floats if necessary. */ + int x0, x1; + struct box *left, *right; + y = cy; + while (1) { + enum css_width_e wtype; + css_fixed width = 0; + css_unit unit = CSS_UNIT_PX; + + wtype = css_computed_width(box->style, + &width, &unit); + + x0 = cx; + x1 = cx + box->parent->width; + find_sides(block->float_children, y, + y + box->height, + &x0, &x1, &left, &right); + if (wtype == CSS_WIDTH_AUTO) + break; + if (box->width <= x1 - x0) + break; + if (!left && !right) + break; + else if (!left) + y = right->y + right->height + 1; + else if (!right) + y = left->y + left->height + 1; + else if (left->y + left->height < + right->y + right->height) + y = left->y + left->height + 1; + else + y = right->y + right->height + 1; + } + box->x += x0 - cx; + cx = x0; + box->y += y - cy; + cy = y; + } + + /* Advance to next box. */ + if (box->type == BOX_BLOCK && !box->object && !(box->iframe) && + box->children) { + /* Down into children. */ + + if (box == margin_collapse) { + /* Current margin collapsed though to this box. + * Unset margin_collapse. */ + margin_collapse = NULL; + } + + y = box->padding[TOP]; + box = box->children; + box->y = y; + cy += y; + continue; + } else if (box->type == BOX_BLOCK || box->object || + box->flags & IFRAME) + cy += box->padding[TOP]; + + if (box->type == BOX_BLOCK && box->height == AUTO) { + box->height = 0; + layout_block_add_scrollbar(box, BOTTOM); + } + + cy += box->height + box->padding[BOTTOM] + + box->border[BOTTOM].width; + cx -= box->x; + y = box->y + box->padding[TOP] + box->height + + box->padding[BOTTOM] + + box->border[BOTTOM].width; + + advance_to_next_box: + if (!box->next) { + /* No more siblings: + * up to first ancestor with a sibling. */ + + do { + if (box == margin_collapse) { + /* Current margin collapsed though to + * this box. Unset margin_collapse. */ + margin_collapse = NULL; + } + + /* Apply bottom margin */ + if (max_pos_margin < box->margin[BOTTOM]) + max_pos_margin = box->margin[BOTTOM]; + else if (max_neg_margin < -box->margin[BOTTOM]) + max_neg_margin = -box->margin[BOTTOM]; + + box = box->parent; + if (box == block) + break; + + /* Margin is invalidated if this is a box + * margins can't collapse through. */ + if (box->type == BOX_BLOCK && + box->flags & MAKE_HEIGHT) { + margin_collapse = NULL; + in_margin = false; + max_pos_margin = max_neg_margin = 0; + } + + if (box->height == AUTO) { + box->height = y - box->padding[TOP]; + + if (box->type == BOX_BLOCK) + layout_block_add_scrollbar(box, + BOTTOM); + } else + cy += box->height - + (y - box->padding[TOP]); + + /* Apply any min-height and max-height to + * boxes in normal flow */ + if (box->style && + css_computed_position(box->style) != + CSS_POSITION_ABSOLUTE && + layout_apply_minmax_height( + &content->len_ctx, + box, NULL)) { + /* Height altered */ + /* Set current cy */ + cy += box->height - + (y - box->padding[TOP]); + } + + cy += box->padding[BOTTOM] + + box->border[BOTTOM].width; + cx -= box->x; + y = box->y + box->padding[TOP] + box->height + + box->padding[BOTTOM] + + box->border[BOTTOM].width; + + } while (box->next == NULL); + if (box == block) + break; + } + + /* To next sibling. */ + + if (box == margin_collapse) { + /* Current margin collapsed though to this box. + * Unset margin_collapse. */ + margin_collapse = NULL; + } + + if (max_pos_margin < box->margin[BOTTOM]) + max_pos_margin = box->margin[BOTTOM]; + else if (max_neg_margin < -box->margin[BOTTOM]) + max_neg_margin = -box->margin[BOTTOM]; + + box = box->next; + box->y = y; + } + + /* Account for bottom margin of last contained block */ + cy += max_pos_margin - max_neg_margin; + + /* Increase height to contain any floats inside (CSS 2.1 10.6.7). */ + for (box = block->float_children; box; box = box->next_float) { + y = box->y + box->height + box->padding[BOTTOM] + + box->border[BOTTOM].width + box->margin[BOTTOM]; + if (cy < y) + cy = y; + } + + if (block->height == AUTO) { + block->height = cy - block->padding[TOP]; + if (block->type == BOX_BLOCK) + layout_block_add_scrollbar(block, BOTTOM); + } + + if (block->style && css_computed_position(block->style) != + CSS_POSITION_ABSOLUTE) { + /* Block is in normal flow */ + layout_apply_minmax_height(&content->len_ctx, block, NULL); + } + + if (block->gadget && + (block->gadget->type == GADGET_TEXTAREA || + block->gadget->type == GADGET_PASSWORD || + block->gadget->type == GADGET_TEXTBOX)) { + plot_font_style_t fstyle; + int ta_width = block->padding[LEFT] + block->width + + block->padding[RIGHT]; + int ta_height = block->padding[TOP] + block->height + + block->padding[BOTTOM]; + font_plot_style_from_css(&content->len_ctx, + block->style, &fstyle); + fstyle.background = NS_TRANSPARENT; + textarea_set_layout(block->gadget->data.text.ta, + &fstyle, ta_width, ta_height, + block->padding[TOP], block->padding[RIGHT], + block->padding[BOTTOM], block->padding[LEFT]); + } + + return true; +} + + +/** + * Layout list markers. + */ +static void +layout_lists(struct box *box, + const struct gui_layout_table *font_func, + const nscss_len_ctx *len_ctx) +{ + struct box *child; + struct box *marker; + plot_font_style_t fstyle; + + for (child = box->children; child; child = child->next) { + if (child->list_marker) { + marker = child->list_marker; + if (marker->object) { + marker->width = + content_get_width(marker->object); + marker->x = -marker->width; + marker->height = + content_get_height(marker->object); + marker->y = (line_height(len_ctx, + marker->style) - + marker->height) / 2; + } else if (marker->text) { + if (marker->width == UNKNOWN_WIDTH) { + font_plot_style_from_css(len_ctx, + marker->style, &fstyle); + font_func->width(&fstyle, + marker->text, + marker->length, + &marker->width); + marker->flags |= MEASURED; + } + marker->x = -marker->width; + marker->y = 0; + marker->height = line_height(len_ctx, + marker->style); + } else { + marker->x = 0; + marker->y = 0; + marker->width = 0; + marker->height = 0; + } + /* Gap between marker and content */ + marker->x -= 4; + } + layout_lists(child, font_func, len_ctx); + } +} + + +/** + * Compute box offsets for a relatively or absolutely positioned box with + * respect to a box. + * + * \param len_ctx Length conversion context + * \param box box to compute offsets for + * \param containing_block box to compute percentages with respect to + * \param top updated to top offset, or AUTO + * \param right updated to right offset, or AUTO + * \param bottom updated to bottom offset, or AUTO + * \param left updated to left offset, or AUTO + * + * See CSS 2.1 9.3.2. containing_block must have width and height. + */ +static void +layout_compute_offsets(const nscss_len_ctx *len_ctx, + struct box *box, + struct box *containing_block, + int *top, + int *right, + int *bottom, + int *left) +{ + uint32_t type; + css_fixed value = 0; + css_unit unit = CSS_UNIT_PX; + + assert(containing_block->width != UNKNOWN_WIDTH && + containing_block->width != AUTO && + containing_block->height != AUTO); + + /* left */ + type = css_computed_left(box->style, &value, &unit); + if (type == CSS_LEFT_SET) { + if (unit == CSS_UNIT_PCT) { + *left = FPCT_OF_INT_TOINT(value, + containing_block->width); + } else { + *left = FIXTOINT(nscss_len2px(len_ctx, + value, unit, box->style)); + } + } else { + *left = AUTO; + } + + /* right */ + type = css_computed_right(box->style, &value, &unit); + if (type == CSS_RIGHT_SET) { + if (unit == CSS_UNIT_PCT) { + *right = FPCT_OF_INT_TOINT(value, + containing_block->width); + } else { + *right = FIXTOINT(nscss_len2px(len_ctx, + value, unit, box->style)); + } + } else { + *right = AUTO; + } + + /* top */ + type = css_computed_top(box->style, &value, &unit); + if (type == CSS_TOP_SET) { + if (unit == CSS_UNIT_PCT) { + *top = FPCT_OF_INT_TOINT(value, + containing_block->height); + } else { + *top = FIXTOINT(nscss_len2px(len_ctx, + value, unit, box->style)); + } + } else { + *top = AUTO; + } + + /* bottom */ + type = css_computed_bottom(box->style, &value, &unit); + if (type == CSS_BOTTOM_SET) { + if (unit == CSS_UNIT_PCT) { + *bottom = FPCT_OF_INT_TOINT(value, + containing_block->height); + } else { + *bottom = FIXTOINT(nscss_len2px(len_ctx, + value, unit, box->style)); + } + } else { + *bottom = AUTO; + } +} + + +/** + * Layout and position an absolutely positioned box. + * + * \param box absolute box to layout and position + * \param containing_block containing block + * \param cx position of box relative to containing_block + * \param cy position of box relative to containing_block + * \param content memory pool for any new boxes + * \return true on success, false on memory exhaustion + */ +static bool +layout_absolute(struct box *box, + struct box *containing_block, + int cx, int cy, + html_content *content) +{ + int static_left, static_top; /* static position */ + int top, right, bottom, left; + int width, height, max_width, min_width; + int *margin = box->margin; + int *padding = box->padding; + struct box_border *border = box->border; + int available_width = containing_block->width; + int space; + + assert(box->type == BOX_BLOCK || box->type == BOX_TABLE || + box->type == BOX_INLINE_BLOCK); + + /* The static position is where the box would be if it was not + * absolutely positioned. The x and y are filled in by + * layout_block_context(). */ + static_left = cx + box->x; + static_top = cy + box->y; + + if (containing_block->type == BOX_BLOCK || + containing_block->type == BOX_INLINE_BLOCK || + containing_block->type == BOX_TABLE_CELL) { + /* Block level container => temporarily increase containing + * block dimensions to include padding (we restore this + * again at the end) */ + containing_block->width += containing_block->padding[LEFT] + + containing_block->padding[RIGHT]; + containing_block->height += containing_block->padding[TOP] + + containing_block->padding[BOTTOM]; + } else { + /** \todo inline containers */ + } + + layout_compute_offsets(&content->len_ctx, box, containing_block, + &top, &right, &bottom, &left); + + /* Pass containing block into layout_find_dimensions via the float + * containing block box member. This is unused for absolutely positioned + * boxes because a box can't be floated and absolutely positioned. */ + box->float_container = containing_block; + layout_find_dimensions(&content->len_ctx, available_width, -1, + box, box->style, &width, &height, + &max_width, &min_width, 0, 0, + margin, padding, border); + box->float_container = NULL; + + /* 10.3.7 */ + NSLOG(layout, DEBUG, + "%i + %i + %i + %i + %i + %i + %i + %i + %i = %i", + left, margin[LEFT], border[LEFT].width, padding[LEFT], width, + padding[RIGHT], border[RIGHT].width, margin[RIGHT], right, + containing_block->width); + + + if (left == AUTO && width == AUTO && right == AUTO) { + if (margin[LEFT] == AUTO) + margin[LEFT] = 0; + if (margin[RIGHT] == AUTO) + margin[RIGHT] = 0; + left = static_left; + + width = min(max(box->min_width, available_width), + box->max_width); + width -= box->margin[LEFT] + box->border[LEFT].width + + box->padding[LEFT] + box->padding[RIGHT] + + box->border[RIGHT].width + box->margin[RIGHT]; + + /* Adjust for {min|max}-width */ + if (max_width >= 0 && width > max_width) width = max_width; + if (width < min_width) width = min_width; + + right = containing_block->width - + left - + margin[LEFT] - border[LEFT].width - padding[LEFT] - + width - + padding[RIGHT] - border[RIGHT].width - margin[RIGHT]; + } else if (left != AUTO && width != AUTO && right != AUTO) { + + /* Adjust for {min|max}-width */ + if (max_width >= 0 && width > max_width) width = max_width; + if (min_width > 0 && width < min_width) width = min_width; + + if (margin[LEFT] == AUTO && margin[RIGHT] == AUTO) { + space = containing_block->width - + left - border[LEFT].width - + padding[LEFT] - width - padding[RIGHT] - + border[RIGHT].width - right; + if (space < 0) { + margin[LEFT] = 0; + margin[RIGHT] = space; + } else { + margin[LEFT] = margin[RIGHT] = space / 2; + } + } else if (margin[LEFT] == AUTO) { + margin[LEFT] = containing_block->width - + left - border[LEFT].width - + padding[LEFT] - width - padding[RIGHT] - + border[RIGHT].width - margin[RIGHT] - + right; + } else if (margin[RIGHT] == AUTO) { + margin[RIGHT] = containing_block->width - + left - margin[LEFT] - + border[LEFT].width - + padding[LEFT] - width - padding[RIGHT] - + border[RIGHT].width - right; + } else { + right = containing_block->width - + left - margin[LEFT] - + border[LEFT].width - + padding[LEFT] - width - padding[RIGHT] - + border[RIGHT].width - margin[RIGHT]; + } + } else { + if (margin[LEFT] == AUTO) + margin[LEFT] = 0; + if (margin[RIGHT] == AUTO) + margin[RIGHT] = 0; + + if (left == AUTO && width == AUTO && right != AUTO) { + available_width -= right; + + width = min(max(box->min_width, available_width), + box->max_width); + width -= box->margin[LEFT] + box->border[LEFT].width + + box->padding[LEFT] + box->padding[RIGHT] + + box->border[RIGHT].width + box->margin[RIGHT]; + + /* Adjust for {min|max}-width */ + if (max_width >= 0 && width > max_width) + width = max_width; + if (width < min_width) + width = min_width; + + left = containing_block->width - + margin[LEFT] - border[LEFT].width - + padding[LEFT] - width - padding[RIGHT] - + border[RIGHT].width - margin[RIGHT] - + right; + } else if (left == AUTO && width != AUTO && right == AUTO) { + + /* Adjust for {min|max}-width */ + if (max_width >= 0 && width > max_width) + width = max_width; + if (min_width > 0 && width < min_width) + width = min_width; + + left = static_left; + right = containing_block->width - + left - margin[LEFT] - + border[LEFT].width - + padding[LEFT] - width - padding[RIGHT] - + border[RIGHT].width - margin[RIGHT]; + } else if (left != AUTO && width == AUTO && right == AUTO) { + available_width -= left; + + width = min(max(box->min_width, available_width), + box->max_width); + width -= box->margin[LEFT] + box->border[LEFT].width + + box->padding[LEFT] + box->padding[RIGHT] + + box->border[RIGHT].width + box->margin[RIGHT]; + + /* Adjust for {min|max}-width */ + if (max_width >= 0 && width > max_width) + width = max_width; + if (width < min_width) + width = min_width; + + right = containing_block->width - + left - margin[LEFT] - + border[LEFT].width - + padding[LEFT] - width - padding[RIGHT] - + border[RIGHT].width - margin[RIGHT]; + } else if (left == AUTO && width != AUTO && right != AUTO) { + + /* Adjust for {min|max}-width */ + if (max_width >= 0 && width > max_width) + width = max_width; + if (width < min_width) + width = min_width; + + left = containing_block->width - + margin[LEFT] - border[LEFT].width - + padding[LEFT] - width - padding[RIGHT] - + border[RIGHT].width - margin[RIGHT] - + right; + } else if (left != AUTO && width == AUTO && right != AUTO) { + width = containing_block->width - + left - margin[LEFT] - + border[LEFT].width - + padding[LEFT] - padding[RIGHT] - + border[RIGHT].width - margin[RIGHT] - + right; + + /* Adjust for {min|max}-width */ + if (max_width >= 0 && width > max_width) + width = max_width; + if (width < min_width) + width = min_width; + + } else if (left != AUTO && width != AUTO && right == AUTO) { + + /* Adjust for {min|max}-width */ + if (max_width >= 0 && width > max_width) + width = max_width; + if (width < min_width) + width = min_width; + + right = containing_block->width - + left - margin[LEFT] - + border[LEFT].width - + padding[LEFT] - width - padding[RIGHT] - + border[RIGHT].width - margin[RIGHT]; + } + } + + NSLOG(layout, DEBUG, + "%i + %i + %i + %i + %i + %i + %i + %i + %i = %i", + left, margin[LEFT], border[LEFT].width, padding[LEFT], width, + padding[RIGHT], border[RIGHT].width, margin[RIGHT], right, + containing_block->width); + + box->x = left + margin[LEFT] + border[LEFT].width - cx; + if (containing_block->type == BOX_BLOCK || + containing_block->type == BOX_INLINE_BLOCK || + containing_block->type == BOX_TABLE_CELL) { + /* Block-level ancestor => reset container's width */ + containing_block->width -= containing_block->padding[LEFT] + + containing_block->padding[RIGHT]; + } else { + /** \todo inline ancestors */ + } + box->width = width; + box->height = height; + + if (box->type == BOX_BLOCK || box->type == BOX_INLINE_BLOCK || + box->object || box->flags & IFRAME) { + if (!layout_block_context(box, -1, content)) + return false; + } else if (box->type == BOX_TABLE) { + /* layout_table also expects the containing block to be + * stored in the float_container field */ + box->float_container = containing_block; + /* \todo layout_table considers margins etc. again */ + if (!layout_table(box, width, content)) + return false; + box->float_container = NULL; + layout_solve_width(box, box->parent->width, box->width, 0, 0, + -1, -1); + } + + /* 10.6.4 */ + NSLOG(layout, DEBUG, + "%i + %i + %i + %i + %i + %i + %i + %i + %i = %i", + top, margin[TOP], border[TOP].width, padding[TOP], height, + padding[BOTTOM], border[BOTTOM].width, margin[BOTTOM], bottom, + containing_block->height); + + if (top == AUTO && height == AUTO && bottom == AUTO) { + top = static_top; + height = box->height; + if (margin[TOP] == AUTO) + margin[TOP] = 0; + if (margin[BOTTOM] == AUTO) + margin[BOTTOM] = 0; + bottom = containing_block->height - + top - margin[TOP] - border[TOP].width - + padding[TOP] - height - padding[BOTTOM] - + border[BOTTOM].width - margin[BOTTOM]; + } else if (top != AUTO && height != AUTO && bottom != AUTO) { + if (margin[TOP] == AUTO && margin[BOTTOM] == AUTO) { + space = containing_block->height - + top - border[TOP].width - padding[TOP] - + height - padding[BOTTOM] - + border[BOTTOM].width - bottom; + margin[TOP] = margin[BOTTOM] = space / 2; + } else if (margin[TOP] == AUTO) { + margin[TOP] = containing_block->height - + top - border[TOP].width - padding[TOP] - + height - padding[BOTTOM] - + border[BOTTOM].width - margin[BOTTOM] - + bottom; + } else if (margin[BOTTOM] == AUTO) { + margin[BOTTOM] = containing_block->height - + top - margin[TOP] - border[TOP].width - + padding[TOP] - height - + padding[BOTTOM] - border[BOTTOM].width - + bottom; + } else { + bottom = containing_block->height - + top - margin[TOP] - border[TOP].width - + padding[TOP] - height - + padding[BOTTOM] - border[BOTTOM].width - + margin[BOTTOM]; + } + } else { + if (margin[TOP] == AUTO) + margin[TOP] = 0; + if (margin[BOTTOM] == AUTO) + margin[BOTTOM] = 0; + if (top == AUTO && height == AUTO && bottom != AUTO) { + height = box->height; + top = containing_block->height - + margin[TOP] - border[TOP].width - + padding[TOP] - height - + padding[BOTTOM] - border[BOTTOM].width - + margin[BOTTOM] - bottom; + } else if (top == AUTO && height != AUTO && bottom == AUTO) { + top = static_top; + bottom = containing_block->height - + top - margin[TOP] - border[TOP].width - + padding[TOP] - height - + padding[BOTTOM] - border[BOTTOM].width - + margin[BOTTOM]; + } else if (top != AUTO && height == AUTO && bottom == AUTO) { + height = box->height; + bottom = containing_block->height - + top - margin[TOP] - border[TOP].width - + padding[TOP] - height - + padding[BOTTOM] - border[BOTTOM].width - + margin[BOTTOM]; + } else if (top == AUTO && height != AUTO && bottom != AUTO) { + top = containing_block->height - + margin[TOP] - border[TOP].width - + padding[TOP] - height - + padding[BOTTOM] - border[BOTTOM].width - + margin[BOTTOM] - bottom; + } else if (top != AUTO && height == AUTO && bottom != AUTO) { + height = containing_block->height - + top - margin[TOP] - border[TOP].width - + padding[TOP] - padding[BOTTOM] - + border[BOTTOM].width - margin[BOTTOM] - + bottom; + } else if (top != AUTO && height != AUTO && bottom == AUTO) { + bottom = containing_block->height - + top - margin[TOP] - border[TOP].width - + padding[TOP] - height - + padding[BOTTOM] - border[BOTTOM].width - + margin[BOTTOM]; + } + } + + NSLOG(layout, DEBUG, + "%i + %i + %i + %i + %i + %i + %i + %i + %i = %i", + top, margin[TOP], border[TOP].width, padding[TOP], height, + padding[BOTTOM], border[BOTTOM].width, margin[BOTTOM], bottom, + containing_block->height); + + box->y = top + margin[TOP] + border[TOP].width - cy; + if (containing_block->type == BOX_BLOCK || + containing_block->type == BOX_INLINE_BLOCK || + containing_block->type == BOX_TABLE_CELL) { + /* Block-level ancestor => reset container's height */ + containing_block->height -= containing_block->padding[TOP] + + containing_block->padding[BOTTOM]; + } else { + /** \todo Inline ancestors */ + } + box->height = height; + layout_apply_minmax_height(&content->len_ctx, box, containing_block); + + return true; +} + + +/** + * Recursively layout and position absolutely positioned boxes. + * + * \param box tree of boxes to layout + * \param containing_block current containing block + * \param cx position of box relative to containing_block + * \param cy position of box relative to containing_block + * \param content memory pool for any new boxes + * \return true on success, false on memory exhaustion + */ +static bool +layout_position_absolute(struct box *box, + struct box *containing_block, + int cx, int cy, + html_content *content) +{ + struct box *c; + + for (c = box->children; c; c = c->next) { + if ((c->type == BOX_BLOCK || c->type == BOX_TABLE || + c->type == BOX_INLINE_BLOCK) && + (css_computed_position(c->style) == + CSS_POSITION_ABSOLUTE || + css_computed_position(c->style) == + CSS_POSITION_FIXED)) { + if (!layout_absolute(c, containing_block, + cx, cy, content)) + return false; + if (!layout_position_absolute(c, c, 0, 0, content)) + return false; + } else if (c->style && css_computed_position(c->style) == + CSS_POSITION_RELATIVE) { + if (!layout_position_absolute(c, c, 0, 0, content)) + return false; + } else { + int px, py; + if (c->style && (css_computed_float(c->style) == + CSS_FLOAT_LEFT || + css_computed_float(c->style) == + CSS_FLOAT_RIGHT)) { + /* Float x/y coords are relative to nearest + * ansestor with float_children, rather than + * relative to parent. Need to get x/y relative + * to parent */ + struct box *p; + px = c->x; + py = c->y; + for (p = box->parent; p && !p->float_children; + p = p->parent) { + px -= p->x; + py -= p->y; + } + } else { + /* Not a float, so box x/y coords are relative + * to parent */ + px = c->x; + py = c->y; + } + if (!layout_position_absolute(c, containing_block, + cx + px, cy + py, content)) + return false; + } + } + + return true; +} + + +/** + * Compute a box's relative offset as per CSS 2.1 9.4.3 + * + * \param len_ctx Length conversion context + * \param box Box to compute relative offsets for. + * \param x Receives relative offset in x. + * \param y Receives relative offset in y. + */ +static void layout_compute_relative_offset( + const nscss_len_ctx *len_ctx, + struct box *box, + int *x, + int *y) +{ + int left, right, top, bottom; + struct box *containing_block; + + assert(box && box->parent && box->style && + css_computed_position(box->style) == + CSS_POSITION_RELATIVE); + + if (box->float_container && + (css_computed_float(box->style) == CSS_FLOAT_LEFT || + css_computed_float(box->style) == CSS_FLOAT_RIGHT)) { + containing_block = box->float_container; + } else { + containing_block = box->parent; + } + + layout_compute_offsets(len_ctx, box, containing_block, + &top, &right, &bottom, &left); + + if (left == AUTO && right == AUTO) + left = right = 0; + else if (left == AUTO) + /* left is auto => computed = -right */ + left = -right; + else if (right == AUTO) + /* right is auto => computed = -left */ + right = -left; + else { + /* over constrained => examine direction property + * of containing block */ + if (containing_block->style && + css_computed_direction( + containing_block->style) == + CSS_DIRECTION_RTL) { + /* right wins */ + left = -right; + } else { + /* assume LTR in all other cases */ + right = -left; + } + } + + assert(left == -right); + + if (top == AUTO && bottom == AUTO) { + top = bottom = 0; + } else if (top == AUTO) { + top = -bottom; + } else { + /* bottom is AUTO, or neither are AUTO */ + bottom = -top; + } + + NSLOG(layout, DEBUG, "left %i, right %i, top %i, bottom %i", left, + right, top, bottom); + + *x = left; + *y = top; +} + + +/** + * Adjust positions of relatively positioned boxes. + * + * \param len_ctx Length conversion context + * \param root box to adjust the position of + * \param fp box which forms the block formatting context for children of + * "root" which are floats + * \param fx x offset due to intervening relatively positioned boxes + * between current box, "root", and the block formatting context + * box, "fp", for float children of "root" + * \param fy y offset due to intervening relatively positioned boxes + * between current box, "root", and the block formatting context + * box, "fp", for float children of "root" + */ +static void +layout_position_relative( + const nscss_len_ctx *len_ctx, + struct box *root, + struct box *fp, + int fx, + int fy) +{ + struct box *box; /* for children of "root" */ + struct box *fn; /* for block formatting context box for children of + * "box" */ + struct box *fc; /* for float children of the block formatting context, + * "fp" */ + int x, y; /* for the offsets resulting from any relative + * positioning on the current block */ + int fnx, fny; /* for affsets which apply to flat children of "box" */ + + /**\todo ensure containing box is large enough after moving boxes */ + + assert(root); + + /* Normal children */ + for (box = root->children; box; box = box->next) { + + if (box->type == BOX_TEXT) + continue; + + /* If relatively positioned, get offsets */ + if (box->style && css_computed_position(box->style) == + CSS_POSITION_RELATIVE) + layout_compute_relative_offset( + len_ctx, box, &x, &y); + else + x = y = 0; + + /* Adjust float coordinates. + * (note float x and y are relative to their block formatting + * context box and not their parent) */ + if (box->style && (css_computed_float(box->style) == + CSS_FLOAT_LEFT || + css_computed_float(box->style) == + CSS_FLOAT_RIGHT) && + (fx != 0 || fy != 0)) { + /* box is a float and there is a float offset to + * apply */ + for (fc = fp->float_children; fc; fc = fc->next_float) { + if (box == fc->children) { + /* Box is floated in the block + * formatting context block, fp. + * Apply float offsets. */ + box->x += fx; + box->y += fy; + fx = fy = 0; + } + } + } + + if (box->float_children) { + fn = box; + fnx = fny = 0; + } else { + fn = fp; + fnx = fx + x; + fny = fy + y; + } + + /* recurse first */ + layout_position_relative(len_ctx, box, fn, fnx, fny); + + /* Ignore things we're not interested in. */ + if (!box->style || (box->style && + css_computed_position(box->style) != + CSS_POSITION_RELATIVE)) + continue; + + box->x += x; + box->y += y; + + /* Handle INLINEs - their "children" are in fact + * the sibling boxes between the INLINE and + * INLINE_END boxes */ + if (box->type == BOX_INLINE && box->inline_end) { + struct box *b; + for (b = box->next; b && b != box->inline_end; + b = b->next) { + b->x += x; + b->y += y; + } + } + } +} + + +/** + * Find a box's bounding box relative to itself, i.e. the box's border edge box + * + * \param len_ctx Length conversion context + * \param box box find bounding box of + * \param desc_x0 updated to left of box's bbox + * \param desc_y0 updated to top of box's bbox + * \param desc_x1 updated to right of box's bbox + * \param desc_y1 updated to bottom of box's bbox + */ +static void +layout_get_box_bbox( + const nscss_len_ctx *len_ctx, + struct box *box, + int *desc_x0, int *desc_y0, + int *desc_x1, int *desc_y1) +{ + *desc_x0 = -box->border[LEFT].width; + *desc_y0 = -box->border[TOP].width; + *desc_x1 = box->padding[LEFT] + box->width + box->padding[RIGHT] + + box->border[RIGHT].width; + *desc_y1 = box->padding[TOP] + box->height + box->padding[BOTTOM] + + box->border[BOTTOM].width; + + /* To stop the top of text getting clipped when css line-height is + * reduced, we increase the top of the descendant bbox. */ + if (box->type == BOX_BLOCK && box->style != NULL && + css_computed_overflow_y(box->style) == + CSS_OVERFLOW_VISIBLE && + box->object == NULL) { + css_fixed font_size = 0; + css_unit font_unit = CSS_UNIT_PT; + int text_height; + + css_computed_font_size(box->style, &font_size, &font_unit); + text_height = nscss_len2px(len_ctx, font_size, font_unit, + box->style); + text_height = FIXTOINT(text_height * 3 / 4); + *desc_y0 = (*desc_y0 < -text_height) ? *desc_y0 : -text_height; + } +} + + +/** + * Apply changes to box descendant_[xy][01] values due to given child. + * + * \param len_ctx Length conversion context + * \param box box to update + * \param child a box, which may affect box's descendant bbox + * \param off_x offset to apply to child->x coord to treat as child of box + * \param off_y offset to apply to child->y coord to treat as child of box + */ +static void +layout_update_descendant_bbox( + const nscss_len_ctx *len_ctx, + struct box *box, + struct box *child, + int off_x, + int off_y) +{ + int child_desc_x0, child_desc_y0, child_desc_x1, child_desc_y1; + + /* get coordinates of child relative to box */ + int child_x = child->x - off_x; + int child_y = child->y - off_y; + + bool html_object = (child->object && + content_get_type(child->object) == CONTENT_HTML); + + enum css_overflow_e overflow_x = CSS_OVERFLOW_VISIBLE; + enum css_overflow_e overflow_y = CSS_OVERFLOW_VISIBLE; + + if (child->style != NULL) { + overflow_x = css_computed_overflow_x(child->style); + overflow_y = css_computed_overflow_y(child->style); + } + + /* Get child's border edge */ + layout_get_box_bbox(len_ctx, child, + &child_desc_x0, &child_desc_y0, + &child_desc_x1, &child_desc_y1); + + if (overflow_x == CSS_OVERFLOW_VISIBLE && + html_object == false) { + /* get child's descendant bbox relative to box */ + child_desc_x0 = child->descendant_x0; + child_desc_x1 = child->descendant_x1; + } + if (overflow_y == CSS_OVERFLOW_VISIBLE && + html_object == false) { + /* get child's descendant bbox relative to box */ + child_desc_y0 = child->descendant_y0; + child_desc_y1 = child->descendant_y1; + } + + child_desc_x0 += child_x; + child_desc_y0 += child_y; + child_desc_x1 += child_x; + child_desc_y1 += child_y; + + /* increase box's descendant bbox to contain descendants */ + if (child_desc_x0 < box->descendant_x0) + box->descendant_x0 = child_desc_x0; + if (child_desc_y0 < box->descendant_y0) + box->descendant_y0 = child_desc_y0; + if (box->descendant_x1 < child_desc_x1) + box->descendant_x1 = child_desc_x1; + if (box->descendant_y1 < child_desc_y1) + box->descendant_y1 = child_desc_y1; +} + + +/** + * Recursively calculate the descendant_[xy][01] values for a laid-out box tree + * and inform iframe browser windows of their size and position. + * + * \param len_ctx Length conversion context + * \param box tree of boxes to update + */ +static void layout_calculate_descendant_bboxes( + const nscss_len_ctx *len_ctx, + struct box *box) +{ + struct box *child; + + assert(box->width != UNKNOWN_WIDTH); + assert(box->height != AUTO); + /* assert((box->width >= 0) && (box->height >= 0)); */ + + /* Initialise box's descendant box to border edge box */ + layout_get_box_bbox(len_ctx, box, + &box->descendant_x0, &box->descendant_y0, + &box->descendant_x1, &box->descendant_y1); + + /* Extend it to contain HTML contents if box is replaced */ + if (box->object && content_get_type(box->object) == CONTENT_HTML) { + if (box->descendant_x1 < content_get_width(box->object)) + box->descendant_x1 = content_get_width(box->object); + if (box->descendant_y1 < content_get_height(box->object)) + box->descendant_y1 = content_get_height(box->object); + } + + if (box->iframe != NULL) { + int x, y; + box_coords(box, &x, &y); + + browser_window_set_position(box->iframe, x, y); + browser_window_set_dimensions(box->iframe, + box->width, box->height); + browser_window_reformat(box->iframe, true, + box->width, box->height); + } + + if (box->type == BOX_INLINE || box->type == BOX_TEXT) + return; + + if (box->type == BOX_INLINE_END) { + box = box->inline_end; + for (child = box->next; child; + child = child->next) { + if (child->type == BOX_FLOAT_LEFT || + child->type == BOX_FLOAT_RIGHT) + continue; + + layout_update_descendant_bbox(len_ctx, box, child, + box->x, box->y); + + if (child == box->inline_end) + break; + } + return; + } + + if (box->flags & REPLACE_DIM) + /* Box's children aren't displayed if the box is replaced */ + return; + + for (child = box->children; child; child = child->next) { + if (child->type == BOX_FLOAT_LEFT || + child->type == BOX_FLOAT_RIGHT) + continue; + + layout_calculate_descendant_bboxes(len_ctx, child); + + if (box->style && css_computed_overflow_x(box->style) == + CSS_OVERFLOW_HIDDEN && + css_computed_overflow_y(box->style) == + CSS_OVERFLOW_HIDDEN) + continue; + + layout_update_descendant_bbox(len_ctx, box, child, 0, 0); + } + + for (child = box->float_children; child; child = child->next_float) { + assert(child->type == BOX_FLOAT_LEFT || + child->type == BOX_FLOAT_RIGHT); + + layout_calculate_descendant_bboxes(len_ctx, child); + + layout_update_descendant_bbox(len_ctx, box, child, 0, 0); + } + + if (box->list_marker) { + child = box->list_marker; + layout_calculate_descendant_bboxes(len_ctx, child); + + layout_update_descendant_bbox(len_ctx, box, child, 0, 0); + } +} + + +/* exported function documented in html/layout.h */ +bool layout_document(html_content *content, int width, int height) +{ + bool ret; + struct box *doc = content->layout; + const struct gui_layout_table *font_func = content->font_func; + + layout_minmax_block(doc, font_func, content); + + layout_block_find_dimensions(&content->len_ctx, + width, height, 0, 0, doc); + doc->x = doc->margin[LEFT] + doc->border[LEFT].width; + doc->y = doc->margin[TOP] + doc->border[TOP].width; + width -= doc->margin[LEFT] + doc->border[LEFT].width + + doc->padding[LEFT] + doc->padding[RIGHT] + + doc->border[RIGHT].width + doc->margin[RIGHT]; + if (width < 0) { + width = 0; + } + doc->width = width; + + ret = layout_block_context(doc, height, content); + + /* make and fill available height */ + if (doc->y + doc->padding[TOP] + doc->height + doc->padding[BOTTOM] + + doc->border[BOTTOM].width + doc->margin[BOTTOM] < + height) { + doc->height = height - (doc->y + doc->padding[TOP] + + doc->padding[BOTTOM] + + doc->border[BOTTOM].width + + doc->margin[BOTTOM]); + if (doc->children) + doc->children->height = doc->height - + (doc->children->margin[TOP] + + doc->children->border[TOP].width + + doc->children->padding[TOP] + + doc->children->padding[BOTTOM] + + doc->children->border[BOTTOM].width + + doc->children->margin[BOTTOM]); + } + + layout_lists(doc, font_func, &content->len_ctx); + layout_position_absolute(doc, doc, 0, 0, content); + layout_position_relative(&content->len_ctx, doc, doc, 0, 0); + + layout_calculate_descendant_bboxes(&content->len_ctx, doc); + + return ret; +} diff --git a/content/handlers/html/layout.h b/content/handlers/html/layout.h new file mode 100644 index 000000000..0811e81de --- /dev/null +++ b/content/handlers/html/layout.h @@ -0,0 +1,45 @@ +/* + * Copyright 2003 James Bursa + * + * 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 . + */ + +/** + * \file + * interface to HTML layout. + * + * The main interface to the layout code is layout_document(), which takes a + * normalized box tree and assigns coordinates and dimensions to the boxes, and + * also adds boxes to the tree (eg. when formatting lines of text). + */ + +#ifndef NETSURF_HTML_LAYOUT_H +#define NETSURF_HTML_LAYOUT_H + +struct box; +struct html_content; +struct gui_layout_table; + +/** + * Calculate positions of boxes in a document. + * + * \param content content of type CONTENT_HTML + * \param width available width + * \param height available height + * \return true on success, false on memory exhaustion + */ +bool layout_document(struct html_content *content, int width, int height); + +#endif diff --git a/content/handlers/html/search.c b/content/handlers/html/search.c new file mode 100644 index 000000000..9ba2957e4 --- /dev/null +++ b/content/handlers/html/search.c @@ -0,0 +1,656 @@ +/* + * Copyright 2004 John M Bell + * Copyright 2005 Adrian Lees + * Copyright 2009 Mark Benjamin + * + * 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 . + */ + +/** + * \file + * Free text search (core) + */ + +#include +#include +#include + +#include "utils/config.h" +#include "utils/log.h" +#include "utils/messages.h" +#include "utils/utils.h" +#include "content/content.h" +#include "content/hlcache.h" +#include "desktop/selection.h" +#include "netsurf/search.h" +#include "netsurf/misc.h" +#include "desktop/gui_internal.h" + +#include "text/textplain.h" +#include "html/box.h" +#include "html/html.h" +#include "html/html_internal.h" +#include "html/search.h" + +#ifndef NOF_ELEMENTS +#define NOF_ELEMENTS(array) (sizeof(array)/sizeof(*(array))) +#endif + + +struct list_entry { + unsigned start_idx; /* start position of match */ + unsigned end_idx; /* end of match */ + + struct box *start_box; /* used only for html contents */ + struct box *end_box; + + struct selection *sel; + + struct list_entry *prev; + struct list_entry *next; +}; + +struct search_context { + void *gui_p; + struct content *c; + struct list_entry *found; + struct list_entry *current; /* first for select all */ + char *string; + bool prev_case_sens; + bool newsearch; + bool is_html; +}; + + +/* Exported function documented in search.h */ +struct search_context * search_create_context(struct content *c, + content_type type, void *gui_data) +{ + struct search_context *context; + struct list_entry *search_head; + + if (type != CONTENT_HTML && type != CONTENT_TEXTPLAIN) { + return NULL; + } + + context = malloc(sizeof(struct search_context)); + if (context == NULL) { + guit->misc->warning("NoMemory", 0); + return NULL; + } + + search_head = malloc(sizeof(struct list_entry)); + if (search_head == NULL) { + guit->misc->warning("NoMemory", 0); + free(context); + return NULL; + } + + search_head->start_idx = 0; + search_head->end_idx = 0; + search_head->start_box = NULL; + search_head->end_box = NULL; + search_head->sel = NULL; + search_head->prev = NULL; + search_head->next = NULL; + + context->found = search_head; + context->current = NULL; + context->string = NULL; + context->prev_case_sens = false; + context->newsearch = true; + context->c = c; + context->is_html = (type == CONTENT_HTML) ? true : false; + context->gui_p = gui_data; + + return context; +} + + +/** + * Release the memory used by the list of matches, + * deleting selection objects too + */ + +static void free_matches(struct search_context *context) +{ + struct list_entry *a; + struct list_entry *b; + + a = context->found->next; + + /* empty the list before clearing and deleting the + * selections because the the clearing updates the + * screen immediately, causing nested accesses to the list */ + + context->found->prev = NULL; + context->found->next = NULL; + + for (; a; a = b) { + b = a->next; + if (a->sel) { + selection_clear(a->sel, true); + selection_destroy(a->sel); + } + free(a); + } +} + + +/** + * Find the first occurrence of 'match' in 'string' and return its index + * + * \param string the string to be searched (unterminated) + * \param s_len length of the string to be searched + * \param pattern the pattern for which we are searching (unterminated) + * \param p_len length of pattern + * \param case_sens true iff case sensitive match required + * \param m_len accepts length of match in bytes + * \return pointer to first match, NULL if none + */ + +static const char *find_pattern(const char *string, int s_len, + const char *pattern, int p_len, bool case_sens, + unsigned int *m_len) +{ + struct { const char *ss, *s, *p; bool first; } context[16]; + const char *ep = pattern + p_len; + const char *es = string + s_len; + const char *p = pattern - 1; /* a virtual '*' before the pattern */ + const char *ss = string; + const char *s = string; + bool first = true; + int top = 0; + + while (p < ep) { + bool matches; + if (p < pattern || *p == '*') { + char ch; + + /* skip any further asterisks; one is the same as many + */ + do p++; while (p < ep && *p == '*'); + + /* if we're at the end of the pattern, yes, it matches + */ + if (p >= ep) break; + + /* anything matches a # so continue matching from + here, and stack a context that will try to match + the wildcard against the next character */ + + ch = *p; + if (ch != '#') { + /* scan forwards until we find a match for + this char */ + if (!case_sens) ch = toupper(ch); + while (s < es) { + if (case_sens) { + if (*s == ch) break; + } else if (toupper(*s) == ch) + break; + s++; + } + } + + if (s < es) { + /* remember where we are in case the match + fails; we may then resume */ + if (top < (int)NOF_ELEMENTS(context)) { + context[top].ss = ss; + context[top].s = s + 1; + context[top].p = p - 1; + /* ptr to last asterisk */ + context[top].first = first; + top++; + } + + if (first) { + ss = s; + /* remember first non-'*' char */ + first = false; + } + + matches = true; + } else { + matches = false; + } + + } else if (s < es) { + char ch = *p; + if (ch == '#') + matches = true; + else { + if (case_sens) + matches = (*s == ch); + else + matches = (toupper(*s) == toupper(ch)); + } + if (matches && first) { + ss = s; /* remember first non-'*' char */ + first = false; + } + } else { + matches = false; + } + + if (matches) { + p++; s++; + } else { + /* doesn't match, + * resume with stacked context if we have one */ + if (--top < 0) + return NULL; /* no match, give up */ + + ss = context[top].ss; + s = context[top].s; + p = context[top].p; + first = context[top].first; + } + } + + /* end of pattern reached */ + *m_len = max(s - ss, 1); + return ss; +} + + +/** + * Add a new entry to the list of matches + * + * \param start_idx Offset of match start within textual representation + * \param end_idx Offset of match end + * \param context The search context to add the entry to. + * \return Pointer to added entry, NULL iff failed. + */ + +static struct list_entry *add_entry(unsigned start_idx, unsigned end_idx, + struct search_context *context) +{ + struct list_entry *entry; + + /* found string in box => add to list */ + entry = calloc(1, sizeof(*entry)); + if (!entry) { + guit->misc->warning("NoMemory", 0); + return NULL; + } + + entry->start_idx = start_idx; + entry->end_idx = end_idx; + entry->sel = NULL; + + entry->next = 0; + entry->prev = context->found->prev; + + if (context->found->prev == NULL) + context->found->next = entry; + else + context->found->prev->next = entry; + + context->found->prev = entry; + + return entry; +} + + +/** + * Finds all occurrences of a given string in the html box tree + * + * \param pattern the string pattern to search for + * \param p_len pattern length + * \param cur pointer to the current box + * \param case_sens whether to perform a case sensitive search + * \param context The search context to add the entry to. + * \return true on success, false on memory allocation failure + */ +static bool find_occurrences_html(const char *pattern, int p_len, + struct box *cur, bool case_sens, + struct search_context *context) +{ + struct box *a; + + /* ignore this box, if there's no visible text */ + if (!cur->object && cur->text) { + const char *text = cur->text; + unsigned length = cur->length; + + while (length > 0) { + struct list_entry *entry; + unsigned match_length; + unsigned match_offset; + const char *new_text; + const char *pos = find_pattern(text, length, + pattern, p_len, case_sens, + &match_length); + if (!pos) + break; + + /* found string in box => add to list */ + match_offset = pos - cur->text; + + entry = add_entry(cur->byte_offset + match_offset, + cur->byte_offset + + match_offset + + match_length, context); + if (!entry) + return false; + + entry->start_box = cur; + entry->end_box = cur; + + new_text = pos + match_length; + length -= (new_text - text); + text = new_text; + } + } + + /* and recurse */ + for (a = cur->children; a; a = a->next) { + if (!find_occurrences_html(pattern, p_len, a, case_sens, + context)) + return false; + } + + return true; +} + + +/** + * Finds all occurrences of a given string in a textplain content + * + * \param pattern the string pattern to search for + * \param p_len pattern length + * \param c the content to be searched + * \param case_sens whether to perform a case sensitive search + * \param context The search context to add the entry to. + * \return true on success, false on memory allocation failure + */ + +static bool find_occurrences_text(const char *pattern, int p_len, + struct content *c, bool case_sens, + struct search_context *context) +{ + int nlines = textplain_line_count(c); + int line; + + for(line = 0; line < nlines; line++) { + size_t offset, length; + const char *text = textplain_get_line(c, line, + &offset, &length); + if (text) { + while (length > 0) { + struct list_entry *entry; + unsigned match_length; + size_t start_idx; + const char *new_text; + const char *pos = find_pattern(text, length, + pattern, p_len, case_sens, + &match_length); + if (!pos) + break; + + /* found string in line => add to list */ + start_idx = offset + (pos - text); + entry = add_entry(start_idx, start_idx + + match_length, context); + if (!entry) + return false; + + new_text = pos + match_length; + offset += (new_text - text); + length -= (new_text - text); + text = new_text; + } + } + } + + return true; +} + + +/** + * Search for a string in the box tree + * + * \param string the string to search for + * \param string_len length of search string + * \param context The search context to add the entry to. + * \param flags flags to control the search. + */ +static void search_text(const char *string, int string_len, + struct search_context *context, search_flags_t flags) +{ + struct rect bounds; + struct box *box = NULL; + union content_msg_data msg_data; + bool case_sensitive, forwards, showall; + + case_sensitive = ((flags & SEARCH_FLAG_CASE_SENSITIVE) != 0) ? + true : false; + forwards = ((flags & SEARCH_FLAG_FORWARDS) != 0) ? true : false; + showall = ((flags & SEARCH_FLAG_SHOWALL) != 0) ? true : false; + + if (context->c == NULL) + return; + + if (context->is_html == true) { + html_content *html = (html_content *)context->c; + + box = html->layout; + + if (!box) + return; + } + + + /* check if we need to start a new search or continue an old one */ + if (context->newsearch) { + bool res; + + if (context->string != NULL) + free(context->string); + + context->current = NULL; + free_matches(context); + + context->string = malloc(string_len + 1); + if (context->string != NULL) { + memcpy(context->string, string, string_len); + context->string[string_len] = '\0'; + } + + guit->search->hourglass(true, context->gui_p); + + if (context->is_html == true) { + res = find_occurrences_html(string, string_len, + box, case_sensitive, context); + } else { + res = find_occurrences_text(string, string_len, + context->c, case_sensitive, context); + } + + if (!res) { + free_matches(context); + guit->search->hourglass(false, context->gui_p); + return; + } + guit->search->hourglass(false, context->gui_p); + + context->prev_case_sens = case_sensitive; + + /* new search, beginning at the top of the page */ + context->current = context->found->next; + context->newsearch = false; + + } else if (context->current != NULL) { + /* continued search in the direction specified */ + if (forwards) { + if (context->current->next) + context->current = context->current->next; + } else { + if (context->current->prev) + context->current = context->current->prev; + } + } + + guit->search->status((context->current != NULL), context->gui_p); + + search_show_all(showall, context); + + guit->search->back_state((context->current != NULL) && + (context->current->prev != NULL), + context->gui_p); + guit->search->forward_state((context->current != NULL) && + (context->current->next != NULL), + context->gui_p); + + if (context->current == NULL) + return; + + if (context->is_html == true) { + /* get box position and jump to it */ + box_coords(context->current->start_box, &bounds.x0, &bounds.y0); + /* \todo: move x0 in by correct idx */ + box_coords(context->current->end_box, &bounds.x1, &bounds.y1); + /* \todo: move x1 in by correct idx */ + bounds.x1 += context->current->end_box->width; + bounds.y1 += context->current->end_box->height; + } else { + textplain_coords_from_range(context->c, + context->current->start_idx, + context->current->end_idx, &bounds); + } + + msg_data.scroll.area = true; + msg_data.scroll.x0 = bounds.x0; + msg_data.scroll.y0 = bounds.y0; + msg_data.scroll.x1 = bounds.x1; + msg_data.scroll.y1 = bounds.y1; + content_broadcast(context->c, CONTENT_MSG_SCROLL, &msg_data); +} + + +/* Exported function documented in search.h */ +void search_step(struct search_context *context, search_flags_t flags, + const char *string) +{ + int string_len; + int i = 0; + + if (context == NULL) { + guit->misc->warning("SearchError", 0); + return; + } + + guit->search->add_recent(string, context->gui_p); + + string_len = strlen(string); + for (i = 0; i < string_len; i++) + if (string[i] != '#' && string[i] != '*') + break; + if (i >= string_len) { + union content_msg_data msg_data; + free_matches(context); + + guit->search->status(true, context->gui_p); + guit->search->back_state(false, context->gui_p); + guit->search->forward_state(false, context->gui_p); + + msg_data.scroll.area = false; + msg_data.scroll.x0 = 0; + msg_data.scroll.y0 = 0; + content_broadcast(context->c, CONTENT_MSG_SCROLL, &msg_data); + return; + } + search_text(string, string_len, context, flags); +} + + +/* Exported function documented in search.h */ +bool search_term_highlighted(struct content *c, + unsigned start_offset, unsigned end_offset, + unsigned *start_idx, unsigned *end_idx, + struct search_context *context) +{ + if (c == context->c) { + struct list_entry *a; + for (a = context->found->next; a; a = a->next) + if (a->sel && selection_defined(a->sel) && + selection_highlighted(a->sel, + start_offset, end_offset, + start_idx, end_idx)) + return true; + } + + return false; +} + + +/* Exported function documented in search.h */ +void search_show_all(bool all, struct search_context *context) +{ + struct list_entry *a; + + for (a = context->found->next; a; a = a->next) { + bool add = true; + if (!all && a != context->current) { + add = false; + if (a->sel) { + selection_clear(a->sel, true); + selection_destroy(a->sel); + a->sel = NULL; + } + } + if (add && !a->sel) { + + if (context->is_html == true) { + html_content *html = (html_content *)context->c; + a->sel = selection_create(context->c, true); + if (!a->sel) + continue; + + selection_init(a->sel, html->layout, + &html->len_ctx); + } else { + a->sel = selection_create(context->c, false); + if (!a->sel) + continue; + + selection_init(a->sel, NULL, NULL); + } + + selection_set_start(a->sel, a->start_idx); + selection_set_end(a->sel, a->end_idx); + } + } +} + + +/* Exported function documented in search.h */ +void search_destroy_context(struct search_context *context) +{ + assert(context != NULL); + + if (context->string != NULL) { + guit->search->add_recent(context->string, context->gui_p); + free(context->string); + } + + guit->search->forward_state(true, context->gui_p); + guit->search->back_state(true, context->gui_p); + + free_matches(context); + free(context); +} diff --git a/content/handlers/html/search.h b/content/handlers/html/search.h new file mode 100644 index 000000000..5c9408e3e --- /dev/null +++ b/content/handlers/html/search.h @@ -0,0 +1,86 @@ +/* + * Copyright 2009 Mark Benjamin + * + * 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 . + */ + +/** + * \file + * Interface to HTML searching. + */ + +#ifndef NETSURF_HTML_SEARCH_H +#define NETSURF_HTML_SEARCH_H + +#include +#include + +#include "desktop/search.h" + +struct search_context; + +/** + * create a search_context + * + * \param c The content the search_context is connected to + * \param type The content type of c + * \param context A context pointer passed to the provider routines. + * \return A new search context or NULL on error. + */ +struct search_context *search_create_context(struct content *c, + content_type type, void *context); + +/** + * Ends the search process, invalidating all state + * freeing the list of found boxes + */ +void search_destroy_context(struct search_context *context); + +/** + * Begins/continues the search process + * + * \note that this may be called many times for a single search. + * + * \param context The search context in use. + * \param flags The flags forward/back etc + * \param string The string to match + */ +void search_step(struct search_context *context, search_flags_t flags, + const char * string); + +/** + * Specifies whether all matches or just the current match should + * be highlighted in the search text. + */ +void search_show_all(bool all, struct search_context *context); + +/** + * Determines whether any portion of the given text box should be + * selected because it matches the current search string. + * + * \param c The content to hilight within. + * \param start_offset byte offset within text of string to be checked + * \param end_offset byte offset within text + * \param start_idx byte offset within string of highlight start + * \param end_idx byte offset of highlight end + * \param context The search context to hilight entries from. + * \return true iff part of the box should be highlighted + */ +bool search_term_highlighted(struct content *c, + unsigned start_offset, unsigned end_offset, + unsigned *start_idx, unsigned *end_idx, + struct search_context *context); + +#endif diff --git a/content/handlers/html/table.c b/content/handlers/html/table.c new file mode 100644 index 000000000..5609e8f29 --- /dev/null +++ b/content/handlers/html/table.c @@ -0,0 +1,1080 @@ +/* + * Copyright 2005 James Bursa + * Copyright 2005 Richard Wilson + * + * 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 . + */ + +/** + * \file + * implementation of HTML table processing and layout. + */ + +#include +#include + +#include "utils/log.h" +#include "utils/talloc.h" +#include "css/utils.h" + +#include "html/box.h" +#include "html/table.h" + +/* Define to enable verbose table debug */ +#undef TABLE_DEBUG + +/** + * Container for border values during table border calculations + */ +struct border { + enum css_border_style_e style; /**< border-style */ + enum css_border_color_e color; /**< border-color type */ + css_color c; /**< border-color value */ + css_fixed width; /**< border-width length */ + css_unit unit; /**< border-width units */ +}; + +static void table_used_left_border_for_cell( + const nscss_len_ctx *len_ctx, + struct box *cell); +static void table_used_top_border_for_cell( + const nscss_len_ctx *len_ctx, + struct box *cell); +static void table_used_right_border_for_cell( + const nscss_len_ctx *len_ctx, + struct box *cell); +static void table_used_bottom_border_for_cell( + const nscss_len_ctx *len_ctx, + struct box *cell); +static bool table_border_is_more_eyecatching( + const nscss_len_ctx *len_ctx, + const struct border *a, + box_type a_src, + const struct border *b, + box_type b_src); +static void table_cell_top_process_table( + const nscss_len_ctx *len_ctx, + struct box *table, + struct border *a, + box_type *a_src); +static bool table_cell_top_process_group( + const nscss_len_ctx *len_ctx, + struct box *cell, + struct box *group, + struct border *a, + box_type *a_src); +static bool table_cell_top_process_row( + const nscss_len_ctx *len_ctx, + struct box *cell, + struct box *row, + struct border *a, + box_type *a_src); + + +/** + * Determine the column width types for a table. + * + * \param len_ctx Length conversion context + * \param table box of type BOX_TABLE + * \return true on success, false on memory exhaustion + * + * The table->col array is allocated and type and width are filled in for each + * column. + */ + +bool table_calculate_column_types( + const nscss_len_ctx *len_ctx, + struct box *table) +{ + unsigned int i, j; + struct column *col; + struct box *row_group, *row, *cell; + + if (table->col) + /* table->col already constructed, for example frameset table */ + return true; + + table->col = col = talloc_array(table, struct column, table->columns); + if (!col) + return false; + + for (i = 0; i != table->columns; i++) { + col[i].type = COLUMN_WIDTH_UNKNOWN; + col[i].width = 0; + col[i].positioned = true; + } + + /* 1st pass: cells with colspan 1 only */ + for (row_group = table->children; row_group; row_group =row_group->next) + for (row = row_group->children; row; row = row->next) + for (cell = row->children; cell; cell = cell->next) { + enum css_width_e type; + css_fixed value = 0; + css_unit unit = CSS_UNIT_PX; + + assert(cell->type == BOX_TABLE_CELL); + assert(cell->style); + + if (cell->columns != 1) + continue; + i = cell->start_column; + + if (css_computed_position(cell->style) != + CSS_POSITION_ABSOLUTE && + css_computed_position(cell->style) != + CSS_POSITION_FIXED) { + col[i].positioned = false; + } + + type = css_computed_width(cell->style, &value, &unit); + + /* fixed width takes priority over any other width type */ + if (col[i].type != COLUMN_WIDTH_FIXED && + type == CSS_WIDTH_SET && unit != CSS_UNIT_PCT) { + col[i].type = COLUMN_WIDTH_FIXED; + col[i].width = FIXTOINT(nscss_len2px(len_ctx, + value, unit, cell->style)); + if (col[i].width < 0) + col[i].width = 0; + continue; + } + + if (col[i].type != COLUMN_WIDTH_UNKNOWN) + continue; + + if (type == CSS_WIDTH_SET && unit == CSS_UNIT_PCT) { + col[i].type = COLUMN_WIDTH_PERCENT; + col[i].width = FIXTOINT(value); + if (col[i].width < 0) + col[i].width = 0; + } else if (type == CSS_WIDTH_AUTO) { + col[i].type = COLUMN_WIDTH_AUTO; + } + } + + /* 2nd pass: cells which span multiple columns */ + for (row_group = table->children; row_group; row_group =row_group->next) + for (row = row_group->children; row; row = row->next) + for (cell = row->children; cell; cell = cell->next) { + unsigned int fixed_columns = 0, percent_columns = 0, + auto_columns = 0, unknown_columns = 0; + int fixed_width = 0, percent_width = 0; + enum css_width_e type; + css_fixed value = 0; + css_unit unit = CSS_UNIT_PX; + + if (cell->columns == 1) + continue; + i = cell->start_column; + + for (j = i; j < i + cell->columns; j++) { + col[j].positioned = false; + } + + /* count column types in spanned cells */ + for (j = 0; j != cell->columns; j++) { + if (col[i + j].type == COLUMN_WIDTH_FIXED) { + fixed_width += col[i + j].width; + fixed_columns++; + } else if (col[i + j].type == COLUMN_WIDTH_PERCENT) { + percent_width += col[i + j].width; + percent_columns++; + } else if (col[i + j].type == COLUMN_WIDTH_AUTO) { + auto_columns++; + } else { + unknown_columns++; + } + } + + if (!unknown_columns) + continue; + + type = css_computed_width(cell->style, &value, &unit); + + /* if cell is fixed width, and all spanned columns are fixed + * or unknown width, split extra width among unknown columns */ + if (type == CSS_WIDTH_SET && unit != CSS_UNIT_PCT && + fixed_columns + unknown_columns == + cell->columns) { + int width = (FIXTOFLT(nscss_len2px(len_ctx, value, unit, + cell->style)) - fixed_width) / + unknown_columns; + if (width < 0) + width = 0; + for (j = 0; j != cell->columns; j++) { + if (col[i + j].type == COLUMN_WIDTH_UNKNOWN) { + col[i + j].type = COLUMN_WIDTH_FIXED; + col[i + j].width = width; + } + } + } + + /* as above for percentage width */ + if (type == CSS_WIDTH_SET && unit == CSS_UNIT_PCT && + percent_columns + unknown_columns == + cell->columns) { + int width = (FIXTOFLT(value) - + percent_width) / unknown_columns; + if (width < 0) + width = 0; + for (j = 0; j != cell->columns; j++) { + if (col[i + j].type == COLUMN_WIDTH_UNKNOWN) { + col[i + j].type = COLUMN_WIDTH_PERCENT; + col[i + j].width = width; + } + } + } + } + + /* use AUTO if no width type was specified */ + for (i = 0; i != table->columns; i++) { + if (col[i].type == COLUMN_WIDTH_UNKNOWN) + col[i].type = COLUMN_WIDTH_AUTO; + } + +#ifdef TABLE_DEBUG + for (i = 0; i != table->columns; i++) + NSLOG(netsurf, INFO, + "table %p, column %u: type %s, width %i", table, i, ((const char *[]){ + "UNKNOWN", + "FIXED", + "AUTO", + "PERCENT", + "RELATIVE", + })[col[i].type], col[i].width); +#endif + + return true; +} + +/** + * Calculate used values of border-{trbl}-{style,color,width} for table cells. + * + * \param len_ctx Length conversion context + * \param cell Table cell to consider + * + * \post \a cell's border array is populated + */ +void table_used_border_for_cell( + const nscss_len_ctx *len_ctx, + struct box *cell) +{ + int side; + + assert(cell->type == BOX_TABLE_CELL); + + if (css_computed_border_collapse(cell->style) == + CSS_BORDER_COLLAPSE_SEPARATE) { + css_fixed width = 0; + css_unit unit = CSS_UNIT_PX; + + /* Left border */ + cell->border[LEFT].style = + css_computed_border_left_style(cell->style); + css_computed_border_left_color(cell->style, + &cell->border[LEFT].c); + css_computed_border_left_width(cell->style, &width, &unit); + cell->border[LEFT].width = + FIXTOINT(nscss_len2px(len_ctx, + width, unit, cell->style)); + + /* Top border */ + cell->border[TOP].style = + css_computed_border_top_style(cell->style); + css_computed_border_top_color(cell->style, + &cell->border[TOP].c); + css_computed_border_top_width(cell->style, &width, &unit); + cell->border[TOP].width = + FIXTOINT(nscss_len2px(len_ctx, + width, unit, cell->style)); + + /* Right border */ + cell->border[RIGHT].style = + css_computed_border_right_style(cell->style); + css_computed_border_right_color(cell->style, + &cell->border[RIGHT].c); + css_computed_border_right_width(cell->style, &width, &unit); + cell->border[RIGHT].width = + FIXTOINT(nscss_len2px(len_ctx, + width, unit, cell->style)); + + /* Bottom border */ + cell->border[BOTTOM].style = + css_computed_border_bottom_style(cell->style); + css_computed_border_bottom_color(cell->style, + &cell->border[BOTTOM].c); + css_computed_border_bottom_width(cell->style, &width, &unit); + cell->border[BOTTOM].width = + FIXTOINT(nscss_len2px(len_ctx, + width, unit, cell->style)); + } else { + /* Left border */ + table_used_left_border_for_cell(len_ctx, cell); + + /* Top border */ + table_used_top_border_for_cell(len_ctx, cell); + + /* Right border */ + table_used_right_border_for_cell(len_ctx, cell); + + /* Bottom border */ + table_used_bottom_border_for_cell(len_ctx, cell); + } + + /* Finally, ensure that any borders configured as + * hidden or none have zero width. (c.f. layout_find_dimensions) */ + for (side = 0; side != 4; side++) { + if (cell->border[side].style == CSS_BORDER_STYLE_HIDDEN || + cell->border[side].style == + CSS_BORDER_STYLE_NONE) + cell->border[side].width = 0; + } +} + +/****************************************************************************** + * Helpers for used border calculations * + ******************************************************************************/ + +/** + * Calculate used values of border-left-{style,color,width} + * + * \param len_ctx Length conversion context + * \param cell Table cell to consider + */ +void table_used_left_border_for_cell( + const nscss_len_ctx *len_ctx, + struct box *cell) +{ + struct border a, b; + box_type a_src, b_src; + + /** \todo Need column and column_group, too */ + + /* Initialise to computed left border for cell */ + a.style = css_computed_border_left_style(cell->style); + a.color = css_computed_border_left_color(cell->style, &a.c); + css_computed_border_left_width(cell->style, &a.width, &a.unit); + a.width = nscss_len2px(len_ctx, a.width, a.unit, cell->style); + a.unit = CSS_UNIT_PX; + a_src = BOX_TABLE_CELL; + + if (cell->prev != NULL || cell->start_column != 0) { + /* Cell to the left -- consider its right border */ + struct box *prev = NULL; + + if (cell->prev == NULL) { + struct box *row; + + /* Spanned from a previous row in current row group */ + for (row = cell->parent; row != NULL; row = row->prev) { + for (prev = row->children; prev != NULL; + prev = prev->next) { + if (prev->start_column + + prev->columns == + cell->start_column) + break; + } + + if (prev != NULL) + break; + } + + assert(prev != NULL); + } else { + prev = cell->prev; + } + + b.style = css_computed_border_right_style(prev->style); + b.color = css_computed_border_right_color(prev->style, &b.c); + css_computed_border_right_width(prev->style, &b.width, &b.unit); + b.width = nscss_len2px(len_ctx, b.width, b.unit, prev->style); + b.unit = CSS_UNIT_PX; + b_src = BOX_TABLE_CELL; + + if (table_border_is_more_eyecatching(len_ctx, + &a, a_src, &b, b_src)) { + a = b; + a_src = b_src; + } + } else { + /* First cell in row, so consider rows and row group */ + struct box *row = cell->parent; + struct box *group = row->parent; + struct box *table = group->parent; + unsigned int rows = cell->rows; + + while (rows-- > 0 && row != NULL) { + /* Spanned rows -- consider their left border */ + b.style = css_computed_border_left_style(row->style); + b.color = css_computed_border_left_color( + row->style, &b.c); + css_computed_border_left_width( + row->style, &b.width, &b.unit); + b.width = nscss_len2px(len_ctx, + b.width, b.unit, row->style); + b.unit = CSS_UNIT_PX; + b_src = BOX_TABLE_ROW; + + if (table_border_is_more_eyecatching(len_ctx, + &a, a_src, &b, b_src)) { + a = b; + a_src = b_src; + } + + row = row->next; + } + + /** \todo can cells span row groups? */ + + /* Row group -- consider its left border */ + b.style = css_computed_border_left_style(group->style); + b.color = css_computed_border_left_color(group->style, &b.c); + css_computed_border_left_width(group->style, &b.width, &b.unit); + b.width = nscss_len2px(len_ctx, b.width, b.unit, group->style); + b.unit = CSS_UNIT_PX; + b_src = BOX_TABLE_ROW_GROUP; + + if (table_border_is_more_eyecatching(len_ctx, + &a, a_src, &b, b_src)) { + a = b; + a_src = b_src; + } + + /* The table itself -- consider its left border */ + b.style = css_computed_border_left_style(table->style); + b.color = css_computed_border_left_color(table->style, &b.c); + css_computed_border_left_width(table->style, &b.width, &b.unit); + b.width = nscss_len2px(len_ctx, b.width, b.unit, table->style); + b.unit = CSS_UNIT_PX; + b_src = BOX_TABLE; + + if (table_border_is_more_eyecatching(len_ctx, + &a, a_src, &b, b_src)) { + a = b; + a_src = b_src; + } + } + + /* a now contains the used left border for the cell */ + cell->border[LEFT].style = a.style; + cell->border[LEFT].c = a.c; + cell->border[LEFT].width = FIXTOINT(nscss_len2px(len_ctx, + a.width, a.unit, cell->style)); +} + +/** + * Calculate used values of border-top-{style,color,width} + * + * \param len_ctx Length conversion context + * \param cell Table cell to consider + */ +void table_used_top_border_for_cell( + const nscss_len_ctx *len_ctx, + struct box *cell) +{ + struct border a, b; + box_type a_src, b_src; + struct box *row = cell->parent; + bool process_group = false; + + /* Initialise to computed top border for cell */ + a.style = css_computed_border_top_style(cell->style); + css_computed_border_top_color(cell->style, &a.c); + css_computed_border_top_width(cell->style, &a.width, &a.unit); + a.width = nscss_len2px(len_ctx, a.width, a.unit, cell->style); + a.unit = CSS_UNIT_PX; + a_src = BOX_TABLE_CELL; + + /* Top border of row */ + b.style = css_computed_border_top_style(row->style); + css_computed_border_top_color(row->style, &b.c); + css_computed_border_top_width(row->style, &b.width, &b.unit); + b.width = nscss_len2px(len_ctx, b.width, b.unit, row->style); + b.unit = CSS_UNIT_PX; + b_src = BOX_TABLE_ROW; + + if (table_border_is_more_eyecatching(len_ctx, &a, a_src, &b, b_src)) { + a = b; + a_src = b_src; + } + + if (row->prev != NULL) { + /* Consider row(s) above */ + while (table_cell_top_process_row(len_ctx, cell, row->prev, + &a, &a_src) == false) { + if (row->prev->prev == NULL) { + /* Consider row group */ + process_group = true; + break; + } else { + row = row->prev; + } + } + } else { + process_group = true; + } + + if (process_group) { + struct box *group = row->parent; + + /* Top border of row group */ + b.style = css_computed_border_top_style(group->style); + b.color = css_computed_border_top_color(group->style, &b.c); + css_computed_border_top_width(group->style, &b.width, &b.unit); + b.width = nscss_len2px(len_ctx, b.width, b.unit, group->style); + b.unit = CSS_UNIT_PX; + b_src = BOX_TABLE_ROW_GROUP; + + if (table_border_is_more_eyecatching(len_ctx, + &a, a_src, &b, b_src)) { + a = b; + a_src = b_src; + } + + if (group->prev == NULL) { + /* Top border of table */ + table_cell_top_process_table(len_ctx, + group->parent, &a, &a_src); + } else { + /* Process previous group(s) */ + while (table_cell_top_process_group(len_ctx, + cell, group->prev, + &a, &a_src) == false) { + if (group->prev->prev == NULL) { + /* Top border of table */ + table_cell_top_process_table(len_ctx, + group->parent, + &a, &a_src); + break; + } else { + group = group->prev; + } + } + } + } + + /* a now contains the used top border for the cell */ + cell->border[TOP].style = a.style; + cell->border[TOP].c = a.c; + cell->border[TOP].width = FIXTOINT(nscss_len2px(len_ctx, + a.width, a.unit, cell->style)); +} + +/** + * Calculate used values of border-right-{style,color,width} + * + * \param len_ctx Length conversion context + * \param cell Table cell to consider + */ +void table_used_right_border_for_cell( + const nscss_len_ctx *len_ctx, + struct box *cell) +{ + struct border a, b; + box_type a_src, b_src; + + /** \todo Need column and column_group, too */ + + /* Initialise to computed right border for cell */ + a.style = css_computed_border_right_style(cell->style); + css_computed_border_right_color(cell->style, &a.c); + css_computed_border_right_width(cell->style, &a.width, &a.unit); + a.width = nscss_len2px(len_ctx, a.width, a.unit, cell->style); + a.unit = CSS_UNIT_PX; + a_src = BOX_TABLE_CELL; + + if (cell->next != NULL || cell->start_column + cell->columns != + cell->parent->parent->parent->columns) { + /* Cell is not at right edge of table -- no right border */ + a.style = CSS_BORDER_STYLE_NONE; + a.width = 0; + a.unit = CSS_UNIT_PX; + } else { + /* Last cell in row, so consider rows and row group */ + struct box *row = cell->parent; + struct box *group = row->parent; + struct box *table = group->parent; + unsigned int rows = cell->rows; + + while (rows-- > 0 && row != NULL) { + /* Spanned rows -- consider their right border */ + b.style = css_computed_border_right_style(row->style); + b.color = css_computed_border_right_color( + row->style, &b.c); + css_computed_border_right_width( + row->style, &b.width, &b.unit); + b.width = nscss_len2px(len_ctx, + b.width, b.unit, row->style); + b.unit = CSS_UNIT_PX; + b_src = BOX_TABLE_ROW; + + if (table_border_is_more_eyecatching(len_ctx, + &a, a_src, &b, b_src)) { + a = b; + a_src = b_src; + } + + row = row->next; + } + + /** \todo can cells span row groups? */ + + /* Row group -- consider its right border */ + b.style = css_computed_border_right_style(group->style); + b.color = css_computed_border_right_color(group->style, &b.c); + css_computed_border_right_width(group->style, + &b.width, &b.unit); + b.width = nscss_len2px(len_ctx, b.width, b.unit, group->style); + b.unit = CSS_UNIT_PX; + b_src = BOX_TABLE_ROW_GROUP; + + if (table_border_is_more_eyecatching(len_ctx, + &a, a_src, &b, b_src)) { + a = b; + a_src = b_src; + } + + /* The table itself -- consider its right border */ + b.style = css_computed_border_right_style(table->style); + b.color = css_computed_border_right_color(table->style, &b.c); + css_computed_border_right_width(table->style, + &b.width, &b.unit); + b.width = nscss_len2px(len_ctx, b.width, b.unit, table->style); + b.unit = CSS_UNIT_PX; + b_src = BOX_TABLE; + + if (table_border_is_more_eyecatching(len_ctx, + &a, a_src, &b, b_src)) { + a = b; + a_src = b_src; + } + } + + /* a now contains the used right border for the cell */ + cell->border[RIGHT].style = a.style; + cell->border[RIGHT].c = a.c; + cell->border[RIGHT].width = FIXTOINT(nscss_len2px(len_ctx, + a.width, a.unit, cell->style)); +} + +/** + * Calculate used values of border-bottom-{style,color,width} + * + * \param len_ctx Length conversion context + * \param cell Table cell to consider + */ +void table_used_bottom_border_for_cell( + const nscss_len_ctx *len_ctx, + struct box *cell) +{ + struct border a, b; + box_type a_src, b_src; + struct box *row = cell->parent; + unsigned int rows = cell->rows; + + /* Initialise to computed bottom border for cell */ + a.style = css_computed_border_bottom_style(cell->style); + css_computed_border_bottom_color(cell->style, &a.c); + css_computed_border_bottom_width(cell->style, &a.width, &a.unit); + a.width = nscss_len2px(len_ctx, a.width, a.unit, cell->style); + a.unit = CSS_UNIT_PX; + a_src = BOX_TABLE_CELL; + + while (rows-- > 0 && row != NULL) + row = row->next; + + /** \todo Can cells span row groups? */ + + if (row != NULL) { + /* Cell is not at bottom edge of table -- no bottom border */ + a.style = CSS_BORDER_STYLE_NONE; + a.width = 0; + a.unit = CSS_UNIT_PX; + } else { + /* Cell at bottom of table, so consider row and row group */ + struct box *row = cell->parent; + struct box *group = row->parent; + struct box *table = group->parent; + + /* Bottom border of row */ + b.style = css_computed_border_bottom_style(row->style); + b.color = css_computed_border_bottom_color(row->style, &b.c); + css_computed_border_bottom_width(row->style, &b.width, &b.unit); + b.width = nscss_len2px(len_ctx, b.width, b.unit, row->style); + b.unit = CSS_UNIT_PX; + b_src = BOX_TABLE_ROW; + + if (table_border_is_more_eyecatching(len_ctx, + &a, a_src, &b, b_src)) { + a = b; + a_src = b_src; + } + + /* Row group -- consider its bottom border */ + b.style = css_computed_border_bottom_style(group->style); + b.color = css_computed_border_bottom_color(group->style, &b.c); + css_computed_border_bottom_width(group->style, + &b.width, &b.unit); + b.width = nscss_len2px(len_ctx, b.width, b.unit, group->style); + b.unit = CSS_UNIT_PX; + b_src = BOX_TABLE_ROW_GROUP; + + if (table_border_is_more_eyecatching(len_ctx, + &a, a_src, &b, b_src)) { + a = b; + a_src = b_src; + } + + /* The table itself -- consider its bottom border */ + b.style = css_computed_border_bottom_style(table->style); + b.color = css_computed_border_bottom_color(table->style, &b.c); + css_computed_border_bottom_width(table->style, + &b.width, &b.unit); + b.width = nscss_len2px(len_ctx, b.width, b.unit, table->style); + b.unit = CSS_UNIT_PX; + b_src = BOX_TABLE; + + if (table_border_is_more_eyecatching(len_ctx, + &a, a_src, &b, b_src)) { + a = b; + } + } + + /* a now contains the used bottom border for the cell */ + cell->border[BOTTOM].style = a.style; + cell->border[BOTTOM].c = a.c; + cell->border[BOTTOM].width = FIXTOINT(nscss_len2px(len_ctx, + a.width, a.unit, cell->style)); +} + +/** + * Determine if a border style is more eyecatching than another + * + * \param len_ctx Length conversion context + * \param a Reference border style + * \param a_src Source of \a a + * \param b Candidate border style + * \param b_src Source of \a b + * \return True if \a b is more eyecatching than \a a + */ +bool table_border_is_more_eyecatching( + const nscss_len_ctx *len_ctx, + const struct border *a, + box_type a_src, + const struct border *b, + box_type b_src) +{ + css_fixed awidth, bwidth; + int impact = 0; + + /* See CSS 2.1 $17.6.2.1 */ + + /* 1 + 2 -- hidden beats everything, none beats nothing */ + if (a->style == CSS_BORDER_STYLE_HIDDEN || + b->style == CSS_BORDER_STYLE_NONE) + return false; + + if (b->style == CSS_BORDER_STYLE_HIDDEN || + a->style == CSS_BORDER_STYLE_NONE) + return true; + + /* 3a -- wider borders beat narrow ones */ + /* The widths must be absolute, which will be the case + * if they've come from a computed style. */ + assert(a->unit != CSS_UNIT_EM && a->unit != CSS_UNIT_EX); + assert(b->unit != CSS_UNIT_EM && b->unit != CSS_UNIT_EX); + awidth = nscss_len2px(len_ctx, a->width, a->unit, NULL); + bwidth = nscss_len2px(len_ctx, b->width, b->unit, NULL); + + if (awidth < bwidth) + return true; + else if (bwidth < awidth) + return false; + + /* 3b -- sort by style */ + switch (a->style) { + case CSS_BORDER_STYLE_DOUBLE: impact++; /* Fall through */ + case CSS_BORDER_STYLE_SOLID: impact++; /* Fall through */ + case CSS_BORDER_STYLE_DASHED: impact++; /* Fall through */ + case CSS_BORDER_STYLE_DOTTED: impact++; /* Fall through */ + case CSS_BORDER_STYLE_RIDGE: impact++; /* Fall through */ + case CSS_BORDER_STYLE_OUTSET: impact++; /* Fall through */ + case CSS_BORDER_STYLE_GROOVE: impact++; /* Fall through */ + case CSS_BORDER_STYLE_INSET: impact++; /* Fall through */ + default: + break; + } + + switch (b->style) { + case CSS_BORDER_STYLE_DOUBLE: impact--; /* Fall through */ + case CSS_BORDER_STYLE_SOLID: impact--; /* Fall through */ + case CSS_BORDER_STYLE_DASHED: impact--; /* Fall through */ + case CSS_BORDER_STYLE_DOTTED: impact--; /* Fall through */ + case CSS_BORDER_STYLE_RIDGE: impact--; /* Fall through */ + case CSS_BORDER_STYLE_OUTSET: impact--; /* Fall through */ + case CSS_BORDER_STYLE_GROOVE: impact--; /* Fall through */ + case CSS_BORDER_STYLE_INSET: impact--; /* Fall through */ + default: + break; + } + + if (impact < 0) + return true; + else if (impact > 0) + return false; + + /* 4a -- sort by origin */ + impact = 0; + + /** \todo COL/COL_GROUP */ + switch (a_src) { + case BOX_TABLE_CELL: impact++; /* Fall through */ + case BOX_TABLE_ROW: impact++; /* Fall through */ + case BOX_TABLE_ROW_GROUP: impact++; /* Fall through */ + case BOX_TABLE: impact++; /* Fall through */ + default: + break; + } + + /** \todo COL/COL_GROUP */ + switch (b_src) { + case BOX_TABLE_CELL: impact--; /* Fall through */ + case BOX_TABLE_ROW: impact--; /* Fall through */ + case BOX_TABLE_ROW_GROUP: impact--; /* Fall through */ + case BOX_TABLE: impact--; /* Fall through */ + default: + break; + } + + if (impact < 0) + return true; + else if (impact > 0) + return false; + + /* 4b -- furthest left (if direction: ltr) and towards top wins */ + /** \todo Currently assumes b satisifies this */ + return true; +} + +/****************************************************************************** + * Helpers for top border collapsing * + ******************************************************************************/ + +/** + * Process a table + * + * \param len_ctx Length conversion context + * \param table Table to process + * \param a Current border style for cell + * \param a_src Source of \a a + * + * \post \a a will be updated with most eyecatching style + * \post \a a_src will be updated also + */ +void table_cell_top_process_table( + const nscss_len_ctx *len_ctx, + struct box *table, + struct border *a, + box_type *a_src) +{ + struct border b; + box_type b_src; + + /* Top border of table */ + b.style = css_computed_border_top_style(table->style); + b.color = css_computed_border_top_color(table->style, &b.c); + css_computed_border_top_width(table->style, &b.width, &b.unit); + b.width = nscss_len2px(len_ctx, b.width, b.unit, table->style); + b.unit = CSS_UNIT_PX; + b_src = BOX_TABLE; + + if (table_border_is_more_eyecatching(len_ctx, a, *a_src, &b, b_src)) { + *a = b; + *a_src = b_src; + } +} + +/** + * Process a group + * + * \param len_ctx Length conversion context + * \param cell Cell being considered + * \param group Group to process + * \param a Current border style for cell + * \param a_src Source of \a a + * \return true if group has non-empty rows, false otherwise + * + * \post \a a will be updated with most eyecatching style + * \post \a a_src will be updated also + */ +bool table_cell_top_process_group( + const nscss_len_ctx *len_ctx, + struct box *cell, + struct box *group, + struct border *a, + box_type *a_src) +{ + struct border b; + box_type b_src; + + /* Bottom border of group */ + b.style = css_computed_border_bottom_style(group->style); + b.color = css_computed_border_bottom_color(group->style, &b.c); + css_computed_border_bottom_width(group->style, &b.width, &b.unit); + b.width = nscss_len2px(len_ctx, b.width, b.unit, group->style); + b.unit = CSS_UNIT_PX; + b_src = BOX_TABLE_ROW_GROUP; + + if (table_border_is_more_eyecatching(len_ctx, a, *a_src, &b, b_src)) { + *a = b; + *a_src = b_src; + } + + if (group->last != NULL) { + /* Process rows in group, starting with last */ + struct box *row = group->last; + + while (table_cell_top_process_row(len_ctx, cell, row, + a, a_src) == false) { + if (row->prev == NULL) { + return false; + } else { + row = row->prev; + } + } + } else { + /* Group is empty, so consider its top border */ + b.style = css_computed_border_top_style(group->style); + b.color = css_computed_border_top_color(group->style, &b.c); + css_computed_border_top_width(group->style, &b.width, &b.unit); + b.width = nscss_len2px(len_ctx, b.width, b.unit, group->style); + b.unit = CSS_UNIT_PX; + b_src = BOX_TABLE_ROW_GROUP; + + if (table_border_is_more_eyecatching(len_ctx, + a, *a_src, &b, b_src)) { + *a = b; + *a_src = b_src; + } + + return false; + } + + return true; +} + +/** + * Process a row + * + * \param len_ctx Length conversion context + * \param cell Cell being considered + * \param row Row to process + * \param a Current border style for cell + * \param a_src Source of \a a + * \return true if row has cells, false otherwise + * + * \post \a a will be updated with most eyecatching style + * \post \a a_src will be updated also + */ +bool table_cell_top_process_row( + const nscss_len_ctx *len_ctx, + struct box *cell, + struct box *row, + struct border *a, + box_type *a_src) +{ + struct border b; + box_type b_src; + + /* Bottom border of row */ + b.style = css_computed_border_bottom_style(row->style); + b.color = css_computed_border_bottom_color(row->style, &b.c); + css_computed_border_bottom_width(row->style, &b.width, &b.unit); + b.width = nscss_len2px(len_ctx, b.width, b.unit, row->style); + b.unit = CSS_UNIT_PX; + b_src = BOX_TABLE_ROW; + + if (table_border_is_more_eyecatching(len_ctx, a, *a_src, &b, b_src)) { + *a = b; + *a_src = b_src; + } + + if (row->children == NULL) { + /* Row is empty, so consider its top border */ + b.style = css_computed_border_top_style(row->style); + b.color = css_computed_border_top_color(row->style, &b.c); + css_computed_border_top_width(row->style, &b.width, &b.unit); + b.width = nscss_len2px(len_ctx, b.width, b.unit, row->style); + b.unit = CSS_UNIT_PX; + b_src = BOX_TABLE_ROW; + + if (table_border_is_more_eyecatching(len_ctx, + a, *a_src, &b, b_src)) { + *a = b; + *a_src = b_src; + } + + return false; + } else { + /* Process cells that are directly above the cell being + * considered. They may not be in this row, but in one of the + * rows above it in the case where rowspan > 1. */ + struct box *c; + bool processed = false; + + while (processed == false) { + for (c = row->children; c != NULL; c = c->next) { + /* Ignore cells to the left */ + if (c->start_column + c->columns - 1 < + cell->start_column) + continue; + /* Ignore cells to the right */ + if (c->start_column > cell->start_column + + cell->columns - 1) + continue; + + /* Flag that we've processed a cell */ + processed = true; + + /* Consider bottom border */ + b.style = css_computed_border_bottom_style( + c->style); + b.color = css_computed_border_bottom_color( + c->style, &b.c); + css_computed_border_bottom_width(c->style, + &b.width, &b.unit); + b.width = nscss_len2px(len_ctx, + b.width, b.unit, c->style); + b.unit = CSS_UNIT_PX; + b_src = BOX_TABLE_CELL; + + if (table_border_is_more_eyecatching(len_ctx, + a, *a_src, &b, b_src)) { + *a = b; + *a_src = b_src; + } + } + + if (processed == false) { + /* There must be a preceding row */ + assert(row->prev != NULL); + + row = row->prev; + } + } + } + + return true; +} diff --git a/content/handlers/html/table.h b/content/handlers/html/table.h new file mode 100644 index 000000000..11ab653c6 --- /dev/null +++ b/content/handlers/html/table.h @@ -0,0 +1,39 @@ +/* + * Copyright 2005 James Bursa + * Copyright 2005 Richard Wilson + * + * 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 . + */ + +/** + * \file + * Interface to HTML table processing and layout. + */ + +#ifndef NETSURF_HTML_TABLE_H +#define NETSURF_HTML_TABLE_H + +#include + +struct box; + +bool table_calculate_column_types( + const nscss_len_ctx *len_ctx, + struct box *table); +void table_used_border_for_cell( + const nscss_len_ctx *len_ctx, + struct box *cell); + +#endif -- cgit v1.2.3