diff options
Diffstat (limited to 'content/handlers/html')
45 files changed, 13672 insertions, 9391 deletions
diff --git a/content/handlers/html/Makefile b/content/handlers/html/Makefile index afefba27d..e41cc1d22 100644 --- a/content/handlers/html/Makefile +++ b/content/handlers/html/Makefile @@ -1,7 +1,25 @@ # 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 +S_HTML := box_construct.c \ + box_inspect.c \ + box_manipulate.c \ + box_normalise.c \ + box_special.c \ + box_textarea.c \ + css.c \ + css_fetcher.c \ + dom_event.c \ + font.c \ + form.c \ + forms.c \ + html.c \ + imagemap.c \ + interaction.c \ + layout.c \ + layout_flex.c \ + object.c \ + redraw.c \ + redraw_border.c \ + script.c \ + table.c \ + textselection.c diff --git a/content/handlers/html/box.c b/content/handlers/html/box.c deleted file mode 100644 index d9e649558..000000000 --- a/content/handlers/html/box.c +++ /dev/null @@ -1,1241 +0,0 @@ -/* - * Copyright 2005-2007 James Bursa <bursa@users.sourceforge.net> - * Copyright 2003 Phil Mellor <monkeyson@users.sourceforge.net> - * Copyright 2005 John M Bell <jmb202@ecs.soton.ac.uk> - * Copyright 2008 Michael Drake <tlsa@netsurf-browser.org> - * - * This file is part of NetSurf, http://www.netsurf-browser.org/ - * - * NetSurf is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * NetSurf is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -/** - * \file - * implementation of box tree manipulation. - */ - -#include <assert.h> -#include <stdbool.h> -#include <stdio.h> -#include <string.h> -#include <dom/dom.h> - -#include "utils/nsoption.h" -#include "utils/log.h" -#include "utils/talloc.h" -#include "netsurf/misc.h" -#include "netsurf/content.h" -#include "netsurf/mouse.h" -#include "css/utils.h" -#include "css/dump.h" -#include "desktop/scrollbar.h" -#include "desktop/gui_internal.h" - -#include "html/box.h" -#include "html/form_internal.h" -#include "html/html_internal.h" - -#define box_is_float(box) (box->type == BOX_FLOAT_LEFT || \ - box->type == BOX_FLOAT_RIGHT) - -/** - * Destructor for box nodes which own styles - * - * \param b The box being destroyed. - * \return 0 to allow talloc to continue destroying the tree. - */ -static int box_talloc_destructor(struct box *b) -{ - struct html_scrollbar_data *data; - - if ((b->flags & STYLE_OWNED) && b->style != NULL) { - css_computed_style_destroy(b->style); - b->style = NULL; - } - - if (b->styles != NULL) { - css_select_results_destroy(b->styles); - b->styles = NULL; - } - - if (b->href != NULL) - nsurl_unref(b->href); - - if (b->id != NULL) { - lwc_string_unref(b->id); - } - - if (b->node != NULL) { - dom_node_unref(b->node); - } - - if (b->scroll_x != NULL) { - data = scrollbar_get_data(b->scroll_x); - scrollbar_destroy(b->scroll_x); - free(data); - } - - if (b->scroll_y != NULL) { - data = scrollbar_get_data(b->scroll_y); - scrollbar_destroy(b->scroll_y); - free(data); - } - - return 0; -} - -/** - * Create a box tree node. - * - * \param styles selection results for the box, or NULL - * \param style computed style for the box (not copied), or 0 - * \param style_owned whether style is owned by this box - * \param href href for the box (copied), or 0 - * \param target target for the box (not copied), or 0 - * \param title title for the box (not copied), or 0 - * \param id id for the box (not copied), or 0 - * \param context context for allocations - * \return allocated and initialised box, or 0 on memory exhaustion - * - * styles is always owned by the box, if it is set. - * style is only owned by the box in the case of implied boxes. - */ - -struct box * box_create(css_select_results *styles, css_computed_style *style, - bool style_owned, nsurl *href, const char *target, - const char *title, lwc_string *id, void *context) -{ - unsigned int i; - struct box *box; - - box = talloc(context, struct box); - if (!box) { - return 0; - } - - talloc_set_destructor(box, box_talloc_destructor); - - box->type = BOX_INLINE; - box->flags = 0; - box->flags = style_owned ? (box->flags | STYLE_OWNED) : box->flags; - box->styles = styles; - box->style = style; - box->x = box->y = 0; - box->width = UNKNOWN_WIDTH; - box->height = 0; - box->descendant_x0 = box->descendant_y0 = 0; - box->descendant_x1 = box->descendant_y1 = 0; - for (i = 0; i != 4; i++) - box->margin[i] = box->padding[i] = box->border[i].width = 0; - box->scroll_x = box->scroll_y = NULL; - box->min_width = 0; - box->max_width = UNKNOWN_MAX_WIDTH; - box->byte_offset = 0; - box->text = NULL; - box->length = 0; - box->space = 0; - box->href = (href == NULL) ? NULL : nsurl_ref(href); - box->target = target; - box->title = title; - box->columns = 1; - box->rows = 1; - box->start_column = 0; - box->next = NULL; - box->prev = NULL; - box->children = NULL; - box->last = NULL; - box->parent = NULL; - box->inline_end = NULL; - box->float_children = NULL; - box->float_container = NULL; - box->next_float = NULL; - box->cached_place_below_level = 0; - box->list_marker = NULL; - box->col = NULL; - box->gadget = NULL; - box->usemap = NULL; - box->id = id; - box->background = NULL; - box->object = NULL; - box->object_params = NULL; - box->iframe = NULL; - box->node = NULL; - - return box; -} - -/** - * Add a child to a box tree node. - * - * \param parent box giving birth - * \param child box to link as last child of parent - */ - -void box_add_child(struct box *parent, struct box *child) -{ - assert(parent); - assert(child); - - if (parent->children != 0) { /* has children already */ - parent->last->next = child; - child->prev = parent->last; - } else { /* this is the first child */ - parent->children = child; - child->prev = 0; - } - - parent->last = child; - child->parent = parent; -} - - -/** - * Insert a new box as a sibling to a box in a tree. - * - * \param box box already in tree - * \param new_box box to link into tree as next sibling - */ - -void box_insert_sibling(struct box *box, struct box *new_box) -{ - new_box->parent = box->parent; - new_box->prev = box; - new_box->next = box->next; - box->next = new_box; - if (new_box->next) - new_box->next->prev = new_box; - else if (new_box->parent) - new_box->parent->last = new_box; -} - - -/** - * Unlink a box from the box tree and then free it recursively. - * - * \param box box to unlink and free recursively. - */ - -void box_unlink_and_free(struct box *box) -{ - struct box *parent = box->parent; - struct box *next = box->next; - struct box *prev = box->prev; - - if (parent) { - if (parent->children == box) - parent->children = next; - if (parent->last == box) - parent->last = next ? next : prev; - } - - if (prev) - prev->next = next; - if (next) - next->prev = prev; - - box_free(box); -} - - -/** - * Free a box tree recursively. - * - * \param box box to free recursively - * - * The box and all its children is freed. - */ - -void box_free(struct box *box) -{ - struct box *child, *next; - - /* free children first */ - for (child = box->children; child; child = next) { - next = child->next; - box_free(child); - } - - /* last this box */ - box_free_box(box); -} - - -/** - * Free the data in a single box structure. - * - * \param box box to free - */ - -void box_free_box(struct box *box) -{ - if (!(box->flags & CLONE)) { - if (box->gadget) - form_free_control(box->gadget); - if (box->scroll_x != NULL) - scrollbar_destroy(box->scroll_x); - if (box->scroll_y != NULL) - scrollbar_destroy(box->scroll_y); - if (box->styles != NULL) - css_select_results_destroy(box->styles); - } - - talloc_free(box); -} - - -/** - * Find the absolute coordinates of a box. - * - * \param box the box to calculate coordinates of - * \param x updated to x coordinate - * \param y updated to y coordinate - */ - -void box_coords(struct box *box, int *x, int *y) -{ - *x = box->x; - *y = box->y; - while (box->parent) { - if (box_is_float(box)) { - do { - box = box->parent; - } while (!box->float_children); - } else - box = box->parent; - *x += box->x - scrollbar_get_offset(box->scroll_x); - *y += box->y - scrollbar_get_offset(box->scroll_y); - } -} - - -/** - * Find the bounds of a box. - * - * \param box the box to calculate bounds of - * \param r receives bounds - */ - -void box_bounds(struct box *box, struct rect *r) -{ - int width, height; - - box_coords(box, &r->x0, &r->y0); - - width = box->padding[LEFT] + box->width + box->padding[RIGHT]; - height = box->padding[TOP] + box->height + box->padding[BOTTOM]; - - r->x1 = r->x0 + width; - r->y1 = r->y0 + height; -} - - -/** - * Determine if a point lies within a box. - * - * \param[in] len_ctx CSS length conversion context to use. - * \param[in] box Box to consider - * \param[in] x Coordinate relative to box - * \param[in] y Coordinate relative to box - * \param[out] physically If function returning true, physically is set true - * iff point is within the box's physical dimensions and - * false if the point is not within the box's physical - * dimensions but is in the area defined by the box's - * descendants. If function returns false, physically - * is undefined. - * \return true if the point is within the box or a descendant box - * - * This is a helper function for box_at_point(). - */ - -static bool box_contains_point( - const nscss_len_ctx *len_ctx, - const struct box *box, - int x, - int y, - bool *physically) -{ - css_computed_clip_rect css_rect; - - if (box->style != NULL && - css_computed_position(box->style) == - CSS_POSITION_ABSOLUTE && - css_computed_clip(box->style, &css_rect) == - CSS_CLIP_RECT) { - /* We have an absolutly positioned box with a clip rect */ - struct rect r = { - .x0 = box->border[LEFT].width, - .y0 = box->border[TOP].width, - .x1 = box->padding[LEFT] + box->width + - box->border[RIGHT].width + - box->padding[RIGHT], - .y1 = box->padding[TOP] + box->height + - box->border[BOTTOM].width + - box->padding[BOTTOM] - }; - if (x >= r.x0 && x < r.x1 && y >= r.y0 && y < r.y1) - *physically = true; - else - *physically = false; - - /* Adjust rect to css clip region */ - if (css_rect.left_auto == false) { - r.x0 += FIXTOINT(nscss_len2px(len_ctx, - css_rect.left, css_rect.lunit, - box->style)); - } - if (css_rect.top_auto == false) { - r.y0 += FIXTOINT(nscss_len2px(len_ctx, - css_rect.top, css_rect.tunit, - box->style)); - } - if (css_rect.right_auto == false) { - r.x1 = box->border[LEFT].width + - FIXTOINT(nscss_len2px(len_ctx, - css_rect.right, - css_rect.runit, - box->style)); - } - if (css_rect.bottom_auto == false) { - r.y1 = box->border[TOP].width + - FIXTOINT(nscss_len2px(len_ctx, - css_rect.bottom, - css_rect.bunit, - box->style)); - } - - /* Test if point is in clipped box */ - if (x >= r.x0 && x < r.x1 && y >= r.y0 && y < r.y1) { - /* inside clip area */ - return true; - } - - /* Not inside clip area */ - return false; - } - if (x >= -box->border[LEFT].width && - x < box->padding[LEFT] + box->width + - box->padding[RIGHT] + box->border[RIGHT].width && - y >= -box->border[TOP].width && - y < box->padding[TOP] + box->height + - box->padding[BOTTOM] + box->border[BOTTOM].width) { - *physically = true; - return true; - } - if (box->list_marker && box->list_marker->x - box->x <= x + - box->list_marker->border[LEFT].width && - x < box->list_marker->x - box->x + - box->list_marker->padding[LEFT] + - box->list_marker->width + - box->list_marker->border[RIGHT].width + - box->list_marker->padding[RIGHT] && - box->list_marker->y - box->y <= y + - box->list_marker->border[TOP].width && - y < box->list_marker->y - box->y + - box->list_marker->padding[TOP] + - box->list_marker->height + - box->list_marker->border[BOTTOM].width + - box->list_marker->padding[BOTTOM]) { - *physically = true; - return true; - } - if ((box->style && css_computed_overflow_x(box->style) == - CSS_OVERFLOW_VISIBLE) || !box->style) { - if (box->descendant_x0 <= x && - x < box->descendant_x1) { - *physically = false; - return true; - } - } - if ((box->style && css_computed_overflow_y(box->style) == - CSS_OVERFLOW_VISIBLE) || !box->style) { - if (box->descendant_y0 <= y && - y < box->descendant_y1) { - *physically = false; - return true; - } - } - return false; -} - - -/** Direction to move in a box-tree walk */ -enum box_walk_dir { - BOX_WALK_CHILDREN, - BOX_WALK_PARENT, - BOX_WALK_NEXT_SIBLING, - BOX_WALK_FLOAT_CHILDREN, - BOX_WALK_NEXT_FLOAT_SIBLING, - BOX_WALK_FLOAT_CONTAINER -}; - - -/** - * Move from box to next box in given direction, adjusting for box coord change - * - * \param b box to move from from - * \param dir direction to move in - * \param x box's global x-coord, updated to position of next box - * \param y box's global y-coord, updated to position of next box - * - * If no box can be found in given direction, NULL is returned. - */ -static inline struct box *box_move_xy(struct box *b, enum box_walk_dir dir, - int *x, int *y) -{ - struct box *rb = NULL; - - switch (dir) { - case BOX_WALK_CHILDREN: - b = b->children; - if (b == NULL) - break; - *x += b->x; - *y += b->y; - if (!box_is_float(b)) { - rb = b; - break; - } - /* fall through */ - - case BOX_WALK_NEXT_SIBLING: - do { - *x -= b->x; - *y -= b->y; - b = b->next; - if (b == NULL) - break; - *x += b->x; - *y += b->y; - } while (box_is_float(b)); - rb = b; - break; - - case BOX_WALK_PARENT: - *x -= b->x; - *y -= b->y; - rb = b->parent; - break; - - case BOX_WALK_FLOAT_CHILDREN: - b = b->float_children; - if (b == NULL) - break; - *x += b->x; - *y += b->y; - rb = b; - break; - - case BOX_WALK_NEXT_FLOAT_SIBLING: - *x -= b->x; - *y -= b->y; - b = b->next_float; - if (b == NULL) - break; - *x += b->x; - *y += b->y; - rb = b; - break; - - case BOX_WALK_FLOAT_CONTAINER: - *x -= b->x; - *y -= b->y; - rb = b->float_container; - break; - - default: - assert(0 && "Bad box walk type."); - } - - return rb; -} - - -/** - * Itterator for walking to next box in interaction order - * - * \param b box to find next box from - * \param x box's global x-coord, updated to position of next box - * \param y box's global y-coord, updated to position of next box - * \param skip_children whether to skip box's children - * - * This walks to a boxes float children before its children. When walking - * children, floating boxes are skipped. - */ -static inline struct box *box_next_xy(struct box *b, int *x, int *y, - bool skip_children) -{ - struct box *n; - int tx, ty; - - assert(b != NULL); - - if (skip_children) { - /* Caller is not interested in any kind of children */ - goto skip_children; - } - - tx = *x; ty = *y; - n = box_move_xy(b, BOX_WALK_FLOAT_CHILDREN, &tx, &ty); - if (n) { - /* Next node is float child */ - *x = tx; - *y = ty; - return n; - } -done_float_children: - - tx = *x; ty = *y; - n = box_move_xy(b, BOX_WALK_CHILDREN, &tx, &ty); - if (n) { - /* Next node is child */ - *x = tx; - *y = ty; - return n; - } - -skip_children: - tx = *x; ty = *y; - n = box_move_xy(b, BOX_WALK_NEXT_FLOAT_SIBLING, &tx, &ty); - if (n) { - /* Go to next float sibling */ - *x = tx; - *y = ty; - return n; - } - - if (box_is_float(b)) { - /* Done floats, but the float container may have children, - * or siblings, or ansestors with siblings. Change to - * float container and move past handling its float children. - */ - b = box_move_xy(b, BOX_WALK_FLOAT_CONTAINER, x, y); - goto done_float_children; - } - - /* Go to next sibling, or nearest ancestor with next sibling. */ - while (b) { - while (!b->next && b->parent) { - b = box_move_xy(b, BOX_WALK_PARENT, x, y); - if (box_is_float(b)) { - /* Go on to next float, if there is one */ - goto skip_children; - } - } - if (!b->next) { - /* No more boxes */ - return NULL; - } - - tx = *x; ty = *y; - n = box_move_xy(b, BOX_WALK_NEXT_SIBLING, &tx, &ty); - if (n) { - /* Go to non-float (ancestor) sibling */ - *x = tx; - *y = ty; - return n; - - } else if (b->parent) { - b = box_move_xy(b, BOX_WALK_PARENT, x, y); - if (box_is_float(b)) { - /* Go on to next float, if there is one */ - goto skip_children; - } - - } else { - /* No more boxes */ - return NULL; - } - } - - assert(b != NULL); - return NULL; -} - - - -/** - * Find the boxes at a point. - * - * \param len_ctx CSS length conversion context for document. - * \param box box to search children of - * \param x point to find, in global document coordinates - * \param y point to find, in global document coordinates - * \param box_x position of box, in global document coordinates, updated - * to position of returned box, if any - * \param box_y position of box, in global document coordinates, updated - * to position of returned box, if any - * \return box at given point, or 0 if none found - * - * To find all the boxes in the hierarchy at a certain point, use code like - * this: - * \code - * struct box *box = top_of_document_to_search; - * int box_x = 0, box_y = 0; - * - * while ((box = box_at_point(len_ctx, box, x, y, &box_x, &box_y))) { - * // process box - * } - * \endcode - */ - -struct box *box_at_point(const nscss_len_ctx *len_ctx, - struct box *box, const int x, const int y, - int *box_x, int *box_y) -{ - bool skip_children; - bool physically; - - assert(box); - - skip_children = false; - while ((box = box_next_xy(box, box_x, box_y, skip_children))) { - if (box_contains_point(len_ctx, box, x - *box_x, y - *box_y, - &physically)) { - *box_x -= scrollbar_get_offset(box->scroll_x); - *box_y -= scrollbar_get_offset(box->scroll_y); - - if (physically) - return box; - - skip_children = false; - } else { - skip_children = true; - } - } - - return NULL; -} - - -/** - * Check whether box is nearer mouse coordinates than current nearest box - * - * \param box box to test - * \param bx position of box, in global document coordinates - * \param by position of box, in global document coordinates - * \param x mouse point, in global document coordinates - * \param y mouse point, in global document coordinates - * \param dir direction in which to search (-1 = above-left, - * +1 = below-right) - * \param nearest nearest text box found, or NULL if none - * updated if box is nearer than existing nearest - * \param tx position of text_box, in global document coordinates - * updated if box is nearer than existing nearest - * \param ty position of text_box, in global document coordinates - * updated if box is nearer than existing nearest - * \param nr_xd distance to nearest text box found - * updated if box is nearer than existing nearest - * \param nr_yd distance to nearest text box found - * updated if box is nearer than existing nearest - * \return true if mouse point is inside box - */ - -static bool box_nearer_text_box(struct box *box, int bx, int by, - int x, int y, int dir, struct box **nearest, int *tx, int *ty, - int *nr_xd, int *nr_yd) -{ - int w = box->padding[LEFT] + box->width + box->padding[RIGHT]; - int h = box->padding[TOP] + box->height + box->padding[BOTTOM]; - int y1 = by + h; - int x1 = bx + w; - int yd = INT_MAX; - int xd = INT_MAX; - - if (x >= bx && x1 > x && y >= by && y1 > y) { - *nearest = box; - *tx = bx; - *ty = by; - return true; - } - - if (box->parent->list_marker != box) { - if (dir < 0) { - /* consider only those children (partly) above-left */ - if (by <= y && bx < x) { - yd = y <= y1 ? 0 : y - y1; - xd = x <= x1 ? 0 : x - x1; - } - } else { - /* consider only those children (partly) below-right */ - if (y1 > y && x1 > x) { - yd = y > by ? 0 : by - y; - xd = x > bx ? 0 : bx - x; - } - } - - /* give y displacement precedence over x */ - if (yd < *nr_yd || (yd == *nr_yd && xd <= *nr_xd)) { - *nr_yd = yd; - *nr_xd = xd; - *nearest = box; - *tx = bx; - *ty = by; - } - } - return false; -} - - -/** - * Pick the text box child of 'box' that is closest to and above-left - * (dir -ve) or below-right (dir +ve) of the point 'x,y' - * - * \param box parent box - * \param bx position of box, in global document coordinates - * \param by position of box, in global document coordinates - * \param fx position of float parent, in global document coordinates - * \param fy position of float parent, in global document coordinates - * \param x mouse point, in global document coordinates - * \param y mouse point, in global document coordinates - * \param dir direction in which to search (-1 = above-left, - * +1 = below-right) - * \param nearest nearest text box found, or NULL if none - * updated if a descendant of box is nearer than old nearest - * \param tx position of nearest, in global document coordinates - * updated if a descendant of box is nearer than old nearest - * \param ty position of nearest, in global document coordinates - * updated if a descendant of box is nearer than old nearest - * \param nr_xd distance to nearest text box found - * updated if a descendant of box is nearer than old nearest - * \param nr_yd distance to nearest text box found - * updated if a descendant of box is nearer than old nearest - * \return true if mouse point is inside text_box - */ - -static bool box_nearest_text_box(struct box *box, int bx, int by, - int fx, int fy, int x, int y, int dir, struct box **nearest, - int *tx, int *ty, int *nr_xd, int *nr_yd) -{ - struct box *child = box->children; - int c_bx, c_by; - int c_fx, c_fy; - bool in_box = false; - - if (*nearest == NULL) { - *nr_xd = INT_MAX / 2; /* displacement of 'nearest so far' */ - *nr_yd = INT_MAX / 2; - } - if (box->type == BOX_INLINE_CONTAINER) { - int bw = box->padding[LEFT] + box->width + box->padding[RIGHT]; - int bh = box->padding[TOP] + box->height + box->padding[BOTTOM]; - int b_y1 = by + bh; - int b_x1 = bx + bw; - if (x >= bx && b_x1 > x && y >= by && b_y1 > y) { - in_box = true; - } - } - - while (child) { - if (child->type == BOX_FLOAT_LEFT || - child->type == BOX_FLOAT_RIGHT) { - c_bx = fx + child->x - - scrollbar_get_offset(child->scroll_x); - c_by = fy + child->y - - scrollbar_get_offset(child->scroll_y); - } else { - c_bx = bx + child->x - - scrollbar_get_offset(child->scroll_x); - c_by = by + child->y - - scrollbar_get_offset(child->scroll_y); - } - if (child->float_children) { - c_fx = c_bx; - c_fy = c_by; - } else { - c_fx = fx; - c_fy = fy; - } - if (in_box && child->text && !child->object) { - if (box_nearer_text_box(child, - c_bx, c_by, x, y, dir, nearest, - tx, ty, nr_xd, nr_yd)) - return true; - } else { - if (child->list_marker) { - if (box_nearer_text_box( - child->list_marker, - c_bx + child->list_marker->x, - c_by + child->list_marker->y, - x, y, dir, nearest, - tx, ty, nr_xd, nr_yd)) - return true; - } - if (box_nearest_text_box(child, c_bx, c_by, - c_fx, c_fy, x, y, dir, nearest, tx, ty, - nr_xd, nr_yd)) - return true; - } - child = child->next; - } - - return false; -} - - -/** - * Peform pick text on browser window contents to locate the box under - * the mouse pointer, or nearest in the given direction if the pointer is - * not over a text box. - * - * \param html an HTML content - * \param x coordinate of mouse - * \param y coordinate of mouse - * \param dir direction to search (-1 = above-left, +1 = below-right) - * \param dx receives x ordinate of mouse relative to text box - * \param dy receives y ordinate of mouse relative to text box - */ - -struct box *box_pick_text_box(struct html_content *html, - int x, int y, int dir, int *dx, int *dy) -{ - struct box *text_box = NULL; - struct box *box; - int nr_xd, nr_yd; - int bx, by; - int fx, fy; - int tx, ty; - - if (html == NULL) - return NULL; - - box = html->layout; - bx = box->margin[LEFT]; - by = box->margin[TOP]; - fx = bx; - fy = by; - - if (!box_nearest_text_box(box, bx, by, fx, fy, x, y, - dir, &text_box, &tx, &ty, &nr_xd, &nr_yd)) { - if (text_box && text_box->text && !text_box->object) { - int w = (text_box->padding[LEFT] + - text_box->width + - text_box->padding[RIGHT]); - int h = (text_box->padding[TOP] + - text_box->height + - text_box->padding[BOTTOM]); - int x1, y1; - - y1 = ty + h; - x1 = tx + w; - - /* ensure point lies within the text box */ - if (x < tx) x = tx; - if (y < ty) y = ty; - if (y > y1) y = y1; - if (x > x1) x = x1; - } - } - - /* return coordinates relative to box */ - *dx = x - tx; - *dy = y - ty; - - return text_box; -} - - -/** - * Find a box based upon its id attribute. - * - * \param box box tree to search - * \param id id to look for - * \return the box or 0 if not found - */ - -struct box *box_find_by_id(struct box *box, lwc_string *id) -{ - struct box *a, *b; - bool m; - - if (box->id != NULL && - lwc_string_isequal(id, box->id, &m) == lwc_error_ok && - m == true) - return box; - - for (a = box->children; a; a = a->next) { - if ((b = box_find_by_id(a, id)) != NULL) - return b; - } - - return NULL; -} - - -/** - * Determine if a box is visible when the tree is rendered. - * - * \param box box to check - * \return true iff the box is rendered - */ - -bool box_visible(struct box *box) -{ - /* visibility: hidden */ - if (box->style && css_computed_visibility(box->style) == - CSS_VISIBILITY_HIDDEN) - return false; - - return true; -} - - -/** - * Print a box tree to a file. - */ - -void box_dump(FILE *stream, struct box *box, unsigned int depth, bool style) -{ - unsigned int i; - struct box *c, *prev; - - for (i = 0; i != depth; i++) - fprintf(stream, " "); - - fprintf(stream, "%p ", box); - fprintf(stream, "x%i y%i w%i h%i ", box->x, box->y, - box->width, box->height); - if (box->max_width != UNKNOWN_MAX_WIDTH) - fprintf(stream, "min%i max%i ", box->min_width, box->max_width); - fprintf(stream, "(%i %i %i %i) ", - box->descendant_x0, box->descendant_y0, - box->descendant_x1, box->descendant_y1); - - fprintf(stream, "m(%i %i %i %i) ", - box->margin[TOP], box->margin[LEFT], - box->margin[BOTTOM], box->margin[RIGHT]); - - switch (box->type) { - case BOX_BLOCK: fprintf(stream, "BLOCK "); break; - case BOX_INLINE_CONTAINER: fprintf(stream, "INLINE_CONTAINER "); break; - case BOX_INLINE: fprintf(stream, "INLINE "); break; - case BOX_INLINE_END: fprintf(stream, "INLINE_END "); break; - case BOX_INLINE_BLOCK: fprintf(stream, "INLINE_BLOCK "); break; - case BOX_TABLE: fprintf(stream, "TABLE [columns %i] ", - box->columns); break; - case BOX_TABLE_ROW: fprintf(stream, "TABLE_ROW "); break; - case BOX_TABLE_CELL: fprintf(stream, "TABLE_CELL [columns %i, " - "start %i, rows %i] ", box->columns, - box->start_column, box->rows); break; - case BOX_TABLE_ROW_GROUP: fprintf(stream, "TABLE_ROW_GROUP "); break; - case BOX_FLOAT_LEFT: fprintf(stream, "FLOAT_LEFT "); break; - case BOX_FLOAT_RIGHT: fprintf(stream, "FLOAT_RIGHT "); break; - case BOX_BR: fprintf(stream, "BR "); break; - case BOX_TEXT: fprintf(stream, "TEXT "); break; - default: fprintf(stream, "Unknown box type "); - } - - if (box->text) - fprintf(stream, "%li '%.*s' ", (unsigned long) box->byte_offset, - (int) box->length, box->text); - if (box->space) - fprintf(stream, "space "); - if (box->object) { - fprintf(stream, "(object '%s') ", - nsurl_access(hlcache_handle_get_url(box->object))); - } - if (box->iframe) { - fprintf(stream, "(iframe) "); - } - if (box->gadget) - fprintf(stream, "(gadget) "); - if (style && box->style) - nscss_dump_computed_style(stream, box->style); - if (box->href) - fprintf(stream, " -> '%s'", nsurl_access(box->href)); - if (box->target) - fprintf(stream, " |%s|", box->target); - if (box->title) - fprintf(stream, " [%s]", box->title); - if (box->id) - fprintf(stream, " ID:%s", lwc_string_data(box->id)); - if (box->type == BOX_INLINE || box->type == BOX_INLINE_END) - fprintf(stream, " inline_end %p", box->inline_end); - if (box->float_children) - fprintf(stream, " float_children %p", box->float_children); - if (box->next_float) - fprintf(stream, " next_float %p", box->next_float); - if (box->float_container) - fprintf(stream, " float_container %p", box->float_container); - if (box->col) { - fprintf(stream, " (columns"); - for (i = 0; i != box->columns; i++) - fprintf(stream, " (%s %s %i %i %i)", - ((const char *[]) {"UNKNOWN", "FIXED", - "AUTO", "PERCENT", "RELATIVE"}) - [box->col[i].type], - ((const char *[]) {"normal", - "positioned"}) - [box->col[i].positioned], - box->col[i].width, - box->col[i].min, box->col[i].max); - fprintf(stream, ")"); - } - if (box->node != NULL) { - dom_string *name; - if (dom_node_get_node_name(box->node, &name) == DOM_NO_ERR) { - fprintf(stream, " <%s>", dom_string_data(name)); - dom_string_unref(name); - } - } - fprintf(stream, "\n"); - - if (box->list_marker) { - for (i = 0; i != depth; i++) - fprintf(stream, " "); - fprintf(stream, "list_marker:\n"); - box_dump(stream, box->list_marker, depth + 1, style); - } - - for (c = box->children; c && c->next; c = c->next) - ; - if (box->last != c) - fprintf(stream, "warning: box->last %p (should be %p) " - "(box %p)\n", box->last, c, box); - for (prev = 0, c = box->children; c; prev = c, c = c->next) { - if (c->parent != box) - fprintf(stream, "warning: box->parent %p (should be " - "%p) (box on next line)\n", - c->parent, box); - if (c->prev != prev) - fprintf(stream, "warning: box->prev %p (should be " - "%p) (box on next line)\n", - c->prev, prev); - box_dump(stream, c, depth + 1, style); - } -} - -/** - * Applies the given scroll setup to a box. This includes scroll - * creation/deletion as well as scroll dimension updates. - * - * \param c content in which the box is located - * \param box the box to handle the scrolls for - * \param bottom whether the horizontal scrollbar should be present - * \param right whether the vertical scrollbar should be present - * \return true on success false otherwise - */ -bool box_handle_scrollbars(struct content *c, struct box *box, - bool bottom, bool right) -{ - struct html_scrollbar_data *data; - int visible_width, visible_height; - int full_width, full_height; - - if (!bottom && box->scroll_x != NULL) { - data = scrollbar_get_data(box->scroll_x); - scrollbar_destroy(box->scroll_x); - free(data); - box->scroll_x = NULL; - } - - if (!right && box->scroll_y != NULL) { - data = scrollbar_get_data(box->scroll_y); - scrollbar_destroy(box->scroll_y); - free(data); - box->scroll_y = NULL; - } - - if (!bottom && !right) - return true; - - visible_width = box->width + box->padding[RIGHT] + box->padding[LEFT]; - visible_height = box->height + box->padding[TOP] + box->padding[BOTTOM]; - - full_width = ((box->descendant_x1 - box->border[RIGHT].width) > - visible_width) ? - box->descendant_x1 + box->padding[RIGHT] : - visible_width; - full_height = ((box->descendant_y1 - box->border[BOTTOM].width) > - visible_height) ? - box->descendant_y1 + box->padding[BOTTOM] : - visible_height; - - if (right) { - if (box->scroll_y == NULL) { - data = malloc(sizeof(struct html_scrollbar_data)); - if (data == NULL) { - NSLOG(netsurf, INFO, "malloc failed"); - guit->misc->warning("NoMemory", 0); - return false; - } - data->c = c; - data->box = box; - if (scrollbar_create(false, visible_height, - full_height, visible_height, - data, html_overflow_scroll_callback, - &(box->scroll_y)) != NSERROR_OK) { - return false; - } - } else { - scrollbar_set_extents(box->scroll_y, visible_height, - visible_height, full_height); - } - } - if (bottom) { - if (box->scroll_x == NULL) { - data = malloc(sizeof(struct html_scrollbar_data)); - if (data == NULL) { - NSLOG(netsurf, INFO, "malloc failed"); - guit->misc->warning("NoMemory", 0); - return false; - } - data->c = c; - data->box = box; - if (scrollbar_create(true, - visible_width - - (right ? SCROLLBAR_WIDTH : 0), - full_width, visible_width, - data, html_overflow_scroll_callback, - &box->scroll_x) != NSERROR_OK) { - return false; - } - } else { - scrollbar_set_extents(box->scroll_x, - visible_width - - (right ? SCROLLBAR_WIDTH : 0), - visible_width, full_width); - } - } - - if (right && bottom) - scrollbar_make_pair(box->scroll_x, box->scroll_y); - - return true; -} - -/** - * Determine if a box has a vertical scrollbar. - * - * \param box scrolling box - * \return the box has a vertical scrollbar - */ - -bool box_vscrollbar_present(const struct box * const box) -{ - return box->padding[TOP] + box->height + box->padding[BOTTOM] + - box->border[BOTTOM].width < box->descendant_y1; -} - - -/** - * Determine if a box has a horizontal scrollbar. - * - * \param box scrolling box - * \return the box has a horizontal scrollbar - */ - -bool box_hscrollbar_present(const struct box * const box) -{ - return box->padding[LEFT] + box->width + box->padding[RIGHT] + - box->border[RIGHT].width < box->descendant_x1; -} diff --git a/content/handlers/html/box.h b/content/handlers/html/box.h index 0952b841b..df2b99d87 100644 --- a/content/handlers/html/box.h +++ b/content/handlers/html/box.h @@ -1,6 +1,7 @@ /* * Copyright 2005 James Bursa <bursa@users.sourceforge.net> * Copyright 2003 Phil Mellor <monkeyson@users.sourceforge.net> + * Copyright 2020 Vincent Sanders <vince@netsurf-browser.org> * * This file is part of NetSurf, http://www.netsurf-browser.org/ * @@ -19,69 +20,8 @@ /** * \file - * Box tree construction and manipulation (interface). + * Box interface. * - * This stage of rendering converts a tree of dom_nodes (produced by libdom) - * to a tree of struct box. The box tree represents the structure of the - * document as given by the CSS display and float properties. - * - * For example, consider the following HTML: - * \code - * <h1>Example Heading</h1> - * <p>Example paragraph <em>with emphasised text</em> etc.</p> \endcode - * - * This would produce approximately the following box tree with default CSS - * rules: - * \code - * BOX_BLOCK (corresponds to h1) - * BOX_INLINE_CONTAINER - * BOX_INLINE "Example Heading" - * BOX_BLOCK (p) - * BOX_INLINE_CONTAINER - * BOX_INLINE "Example paragraph " - * BOX_INLINE "with emphasised text" (em) - * BOX_INLINE "etc." \endcode - * - * Note that the em has been collapsed into the INLINE_CONTAINER. - * - * If these CSS rules were applied: - * \code - * h1 { display: table-cell } - * p { display: table-cell } - * em { float: left; width: 5em } \endcode - * - * then the box tree would instead look like this: - * \code - * BOX_TABLE - * BOX_TABLE_ROW_GROUP - * BOX_TABLE_ROW - * BOX_TABLE_CELL (h1) - * BOX_INLINE_CONTAINER - * BOX_INLINE "Example Heading" - * BOX_TABLE_CELL (p) - * BOX_INLINE_CONTAINER - * BOX_INLINE "Example paragraph " - * BOX_FLOAT_LEFT (em) - * BOX_BLOCK - * BOX_INLINE_CONTAINER - * BOX_INLINE "with emphasised text" - * BOX_INLINE "etc." \endcode - * - * Here implied boxes have been added and a float is present. - * - * A box tree is "normalized" if the following is satisfied: - * \code - * parent permitted child nodes - * BLOCK, INLINE_BLOCK BLOCK, INLINE_CONTAINER, TABLE - * INLINE_CONTAINER INLINE, INLINE_BLOCK, FLOAT_LEFT, FLOAT_RIGHT, BR, TEXT, - * INLINE_END - * INLINE none - * TABLE at least 1 TABLE_ROW_GROUP - * TABLE_ROW_GROUP at least 1 TABLE_ROW - * TABLE_ROW at least 1 TABLE_CELL - * TABLE_CELL BLOCK, INLINE_CONTAINER, TABLE (same as BLOCK) - * FLOAT_(LEFT|RIGHT) exactly 1 BLOCK or TABLE - * \endcode */ #ifndef NETSURF_HTML_BOX_H @@ -89,7 +29,6 @@ #include <limits.h> #include <stdbool.h> -#include <stdio.h> #include <libcss/libcss.h> #include "content/handlers/css/utils.h" @@ -97,9 +36,6 @@ struct content; struct box; struct browser_window; -struct column; -struct object_params; -struct object_param; struct html_content; struct nsurl; struct dom_node; @@ -109,20 +45,36 @@ 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. */ + +/** + * Type of a struct box. + */ typedef enum { - BOX_BLOCK, BOX_INLINE_CONTAINER, BOX_INLINE, - BOX_TABLE, BOX_TABLE_ROW, BOX_TABLE_CELL, + 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_FLOAT_LEFT, + BOX_FLOAT_RIGHT, + BOX_INLINE_BLOCK, + BOX_BR, + BOX_TEXT, + BOX_INLINE_END, + BOX_NONE, + BOX_FLEX, + BOX_INLINE_FLEX, } box_type; -/** Flags for a struct box. */ +/** + * 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 */ @@ -139,9 +91,13 @@ typedef enum { IS_REPLACED = 1 << 12 /* box is a replaced element */ } box_flags; -/* Sides of a box */ + +/** + * Sides of a box + */ enum box_side { TOP, RIGHT, BOTTOM, LEFT }; + /** * Container for box border details */ @@ -151,31 +107,190 @@ struct box_border { int width; /**< border-width (pixels) */ }; -/** Node in box tree. All dimensions are in pixels. */ + +/** + * 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; +}; + + +/** + * Linked list of object element parameters. + */ +struct object_param { + char *name; + char *value; + char *type; + char *valuetype; + struct object_param *next; +}; + + +/** + * 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; +}; + + +/** + * Node in box tree. All dimensions are in pixels. + */ struct box { - /** Type of box. */ + /** + * Type of box. + */ box_type type; - /** Box flags */ + /** + * Box flags + */ box_flags flags; - /** Computed styles for elements and their pseudo elements. NULL on - * non-element boxes. */ + /** + * DOM node that generated this box or NULL + */ + struct dom_node *node; + + /** + * 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. */ + /** + * 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_. */ + /** + * value of id attribute (or name for anchors) + */ + lwc_string *id; + + + /** + * Next sibling box, or NULL. + */ + struct box *next; + + /** + * Previous sibling box, or NULL. + */ + struct box *prev; + + /** + * First child box, or NULL. + */ + struct box *children; + + /** + * Last child box, or NULL. + */ + struct box *last; + + /** + * Parent box, or NULL. + */ + struct box *parent; + + /** + * 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 NULL. 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; + + + /** + * 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. */ + /** + * 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.). */ + /** + * Width of content box (excluding padding etc.). + */ + int width; + /** + * Height of content box (excluding padding etc.). + */ + int height; /* These four variables determine the maximum extent of a box's * descendants. They are relative to the x,y coordinates of the box. @@ -196,185 +311,147 @@ struct box { 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. */ + /** + * Margin: TOP, RIGHT, BOTTOM, LEFT. + */ + int margin[4]; + + /** + * Padding: TOP, RIGHT, BOTTOM, LEFT. + */ + int padding[4]; + + /** + * Border: TOP, RIGHT, BOTTOM, LEFT. + */ + struct box_border border[4]; - struct scrollbar *scroll_x; /**< Horizontal scroll. */ - struct scrollbar *scroll_y; /**< Vertical scroll. */ + /** + * Horizontal scroll. + */ + struct scrollbar *scroll_x; + + /** + * Vertical scroll. + */ + struct scrollbar *scroll_y; - /** Width of box taking all line breaks (including margins etc). Must - * be non-negative. */ + /** + * 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. */ + + /** + * 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. */ + /** + * Text, or NULL if none. Unterminated. + */ + char *text; + + /** + * Length of text. + */ + size_t length; - /** Width of space after current text (depends on font and size). */ + /** + * 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; + /** + * Byte offset within a textual representation of this content. + */ + size_t byte_offset; - /** 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; + /** + * Link, or NULL. + */ + struct nsurl *href; - /** List marker box if this is a list-item, or 0. */ - struct box *list_marker; + /** + * Link target, or NULL. + */ + const char *target; - struct column *col; /**< Array of table column data for TABLE only. */ + /** + * Title, or NULL. + */ + const char *title; - /** 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) */ + /** + * Number of columns for TABLE / TABLE_CELL. + */ + unsigned int columns; - /** Background image for this box, or 0 if none */ - struct hlcache_handle *background; + /** + * Number of rows for TABLE only. + */ + unsigned int rows; - /** 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; + /** + * Start column for TABLE_CELL only. + */ + unsigned int start_column; - /** Iframe's browser_window, or NULL if none */ - struct browser_window *iframe; + /** + * Array of table column data for TABLE only. + */ + struct column *col; - struct dom_node *node; /**< DOM node that generated this box or NULL */ -}; + /** + * List item value. + */ + int list_value; -/** 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; -}; + /** + * List marker box if this is a list-item, or NULL. + */ + struct box *list_marker; -/** 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; -}; + /** + * Form control data, or NULL if not a form control. + */ + struct form_control* gadget; -/** 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); + /** + * (Image)map to use with this object, or NULL if none + */ + char *usemap; -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); + /** + * Background image for this box, or NULL if none + */ + struct hlcache_handle *background; -bool box_normalise_block( - struct box *block, - const struct box *root, - struct html_content *c); -/** - * Check if layout box is a first child. - * - * \param[in] b Box to check. - * \return true iff box is first child. - */ -static inline bool box_is_first_child(struct box *b) -{ - return (b->parent == NULL || b == b->parent->children); -} + /** + * Object in this box (usually an image), or NULL if none. + */ + struct hlcache_handle* object; + + /** + * Parameters for the object, or NULL. + */ + struct object_params *object_params; + + + /** + * Iframe's browser_window, or NULL if none + */ + struct browser_window *iframe; + +}; + #endif diff --git a/content/handlers/html/box_construct.c b/content/handlers/html/box_construct.c index 4d0cba748..8519c2b1d 100644 --- a/content/handlers/html/box_construct.c +++ b/content/handlers/html/box_construct.c @@ -25,35 +25,28 @@ * Implementation of conversion from DOM tree to box tree. */ -#include <assert.h> -#include <stdio.h> -#include <stdbool.h> -#include <stdlib.h> #include <string.h> -#include <strings.h> +#include <dom/dom.h> -#include "utils/config.h" +#include "utils/errors.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 "utils/nsurl.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/html.h" +#include "html/private.h" +#include "html/object.h" #include "html/box.h" -#include "html/box_textarea.h" +#include "html/box_manipulate.h" +#include "html/box_construct.h" +#include "html/box_special.h" +#include "html/box_normalise.h" #include "html/form_internal.h" -#include "html/html_internal.h" /** * Context for box tree construction @@ -77,7 +70,7 @@ 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; + struct nsurl *href; /** Current frame target, or NULL if none */ const char *target; /** Current title attribute, or NULL if none */ @@ -93,137 +86,39 @@ struct box_construct_props { 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}, - {"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*/ + BOX_BLOCK, /* 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 */ + BOX_FLEX, /* CSS_DISPLAY_FLEX */ + BOX_INLINE_FLEX, /* CSS_DISPLAY_INLINE_FLEX */ + BOX_BLOCK, /* CSS_DISPLAY_GRID */ + BOX_INLINE_BLOCK, /* CSS_DISPLAY_INLINE_GRID */ }; -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; -} +/** + * determine if a box is the root node + * + * \param n node to check + * \return true if node is root else false. + */ static inline bool box_is_root(dom_node *n) { dom_node *parent; @@ -250,207 +145,215 @@ static inline bool box_is_root(dom_node *n) } /** - * 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 + * Extract transient construction properties * - * \note \a n will be unreferenced + * \param n Current DOM node to convert + * \param props Property object to populate */ -static dom_node *next_node(dom_node *n, html_content *content, - bool convert_children) +static void +box_extract_properties(dom_node *n, struct box_construct_props *props) { - 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; + memset(props, 0, sizeof(*props)); - err = dom_node_get_parent_node(n, &parent); - if (err != DOM_NO_ERR) { - dom_node_unref(n); - return NULL; - } + props->node_is_root = box_is_root(n); - assert(parent != NULL); + /* 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; - err = dom_node_get_next_sibling(parent, - &parent_next); - if (err != DOM_NO_ERR) { - dom_node_unref(parent); - dom_node_unref(n); - return NULL; - } + /* 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; - if (parent_next != NULL) { - dom_node_unref(parent_next); - dom_node_unref(parent); - break; - } + parent_box = box_for_node(parent_node); - dom_node_unref(n); - n = parent; - parent = NULL; + 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; - if (box_for_node(n) != NULL) { - box_construct_element_after( - n, content); - } + dom_node_unref(parent_node); + break; + } else { + if (current_node != n) + dom_node_unref(current_node); + current_node = parent_node; + parent_node = NULL; } + } - if (box_is_root(n) == false) { - dom_node *parent = NULL; + /* Find containing block (may be parent) */ + while (true) { + struct box *b; - err = dom_node_get_parent_node(n, &parent); - if (err != DOM_NO_ERR) { - dom_node_unref(n); - return NULL; - } + 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; + } - assert(parent != NULL); + if (current_node != n) + dom_node_unref(current_node); - err = dom_node_get_next_sibling(parent, &next); - if (err != DOM_NO_ERR) { - dom_node_unref(parent); - dom_node_unref(n); - return NULL; - } + b = box_for_node(parent_node); - if (box_for_node(parent) != NULL) { - box_construct_element_after(parent, - content); - } + /* 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); + dom_node_unref(parent_node); + break; + } else { + current_node = parent_node; + parent_node = NULL; } - - dom_node_unref(n); } } - return next; + /* 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; } + /** - * Convert an ELEMENT node to a box tree fragment, - * then schedule conversion of the next ELEMENT node + * 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 */ -void convert_xml_to_box(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) { - 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); + dom_string *s = NULL; + css_stylesheet *inline_style = NULL; + css_select_results *styles; + nscss_select_ctx ctx; - if (box_construct_element(ctx, &convert_children) == false) { - ctx->cb(ctx->content, false); - dom_node_unref(ctx->n); - free(ctx); - return; + /* Firstly, construct inline stylesheet, if any */ + if (nsoption_bool(author_level_css)) { + dom_exception err; + err = dom_element_get_attribute(n, corestring_dom_style, &s); + if (err != DOM_NO_ERR) { + return NULL; } + } - /* 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; + 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); - 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; - } + dom_string_unref(s); - if (type == DOM_ELEMENT_NODE) - break; + if (inline_style == NULL) + return NULL; + } - 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; - } - } + /* 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; - next = next_node(next, ctx->content, true); - } + /* Select style for element */ + styles = nscss_get_style(&ctx, n, &c->media, &c->unit_len_ctx, + inline_style); - ctx->n = next; + /* No longer need inline style */ + if (inline_style != NULL) + css_stylesheet_destroy(inline_style); - if (next == NULL) { - /* Conversion complete */ - struct box root; + return styles; +} - memset(&root, 0, sizeof(root)); - root.type = BOX_BLOCK; - root.children = root.last = ctx->root_box; - root.children->parent = &root; +/** + * 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; - /** \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; + /* Nothing to generate if the parent box is not a block */ + if (box->type != BOX_BLOCK) + return; - ctx->cb(ctx->content, true); - } + /* 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; + } - assert(ctx->n == NULL); + /* 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 */ - free(ctx); + /** \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; } - } while (++num_processed < max_processed_before_yield); - /* More work to do: schedule a continuation */ - guit->misc->schedule(0, (void *)convert_xml_to_box, ctx); + /* set box type from computed display */ + gen->type = box_map[ns_computed_display( + style, box_is_root(n))]; + + box_add_child(box, gen); + } } + /** * Construct a list marker box * @@ -460,11 +363,15 @@ void convert_xml_to_box(struct box_construct_ctx *ctx) * \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) +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; + enum css_list_style_type_e list_style_type; marker = box_create(NULL, box->style, false, NULL, NULL, title, NULL, ctx->bctx); @@ -473,81 +380,33 @@ static bool box_construct_marker(struct box *box, const char *title, marker->type = BOX_BLOCK; + list_style_type = css_computed_list_style_type(box->style); + /** \todo marker content (list-style-type) */ - switch (css_computed_list_style_type(box->style)) { + switch (list_style_type) { 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; + default: + /* Numerical list counters get handled in layout. */ + /* Fall through. */ case CSS_LIST_STYLE_TYPE_NONE: - marker->text = 0; + marker->text = NULL; marker->length = 0; break; } @@ -566,9 +425,11 @@ static bool box_construct_marker(struct box *box, const char *title, if (error != NSERROR_OK) return false; - if (html_fetch_object(ctx->content, url, marker, image_types, - ctx->content->base.available_width, 1000, false) == - false) { + if (html_fetch_object(ctx->content, + url, + marker, + image_types, + false) == false) { nsurl_unref(url); return false; } @@ -581,147 +442,22 @@ static bool box_construct_marker(struct box *box, const char *title, 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) +static inline bool box__style_is_float(const struct box *box) { - 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); - } + return css_computed_float(box->style) == CSS_FLOAT_LEFT || + css_computed_float(box->style) == CSS_FLOAT_RIGHT; } -/** - * 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) +static inline bool box__is_flex(const struct box *box) { - 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; - } - } - } + return box->type == BOX_FLEX || box->type == BOX_INLINE_FLEX; +} - /* 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; +static inline bool box__containing_block_is_flex( + const struct box_construct_props *props) +{ + return props->containing_block != NULL && + box__is_flex(props->containing_block); } /** @@ -731,15 +467,14 @@ static void box_extract_properties(dom_node *n, * \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) +static bool +box_construct_element(struct box_construct_ctx *ctx, bool *convert_children) { dom_string *title0, *s; lwc_string *id = NULL; + enum css_display_e css_display; 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; @@ -836,16 +571,15 @@ bool box_construct_element(struct box_construct_ctx *ctx, dom_string_unref(s); } + css_display = ns_computed_display_static(box->style); + /* 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)) { + css_computed_position(box->style) == CSS_POSITION_FIXED) && + (css_display == CSS_DISPLAY_INLINE || + css_display == CSS_DISPLAY_INLINE_BLOCK || + css_display == CSS_DISPLAY_INLINE_TABLE || + css_display == CSS_DISPLAY_INLINE_FLEX)) { /* 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. @@ -859,24 +593,28 @@ bool box_construct_element(struct box_construct_ctx *ctx, /* Normal mapping */ box->type = box_map[ns_computed_display(box->style, props.node_is_root)]; + + if (props.containing_block->type == BOX_FLEX || + props.containing_block->type == BOX_INLINE_FLEX) { + /* Blockification */ + switch (box->type) { + case BOX_INLINE_FLEX: + box->type = BOX_FLEX; + break; + case BOX_INLINE_BLOCK: + box->type = BOX_BLOCK; + break; + default: + break; + } + } } - err = dom_node_get_node_name(ctx->n, &s); - if (err != DOM_NO_ERR || s == NULL) + if (convert_special_elements(ctx->n, + ctx->content, + box, + convert_children) == false) { 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 */ @@ -922,8 +660,9 @@ bool box_construct_element(struct box_construct_ctx *ctx, (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) && + box->type == BOX_INLINE_FLEX || + (box__style_is_float(box) && + !box__containing_block_is_flex(&props))) && 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 @@ -955,10 +694,11 @@ bool box_construct_element(struct box_construct_ctx *ctx, 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) { + if (html_fetch_object(ctx->content, + url, + box, + image_types, + true) == false) { nsurl_unref(url); return false; } @@ -970,6 +710,7 @@ bool box_construct_element(struct box_construct_ctx *ctx, box->flags |= CONVERT_CHILDREN; if (box->type == BOX_INLINE || box->type == BOX_BR || + box->type == BOX_INLINE_FLEX || box->type == BOX_INLINE_BLOCK) { /* Inline container must exist, as we'll have * created it above if it didn't */ @@ -986,6 +727,7 @@ bool box_construct_element(struct box_construct_ctx *ctx, } if (props.node_is_root == false && + box__containing_block_is_flex(&props) == false && (css_computed_float(box->style) == CSS_FLOAT_LEFT || css_computed_float(box->style) == @@ -1016,6 +758,7 @@ bool box_construct_element(struct box_construct_ctx *ctx, return true; } + /** * Complete construction of the box tree for an element. * @@ -1024,7 +767,7 @@ bool box_construct_element(struct box_construct_ctx *ctx, * * This will be called after all children of an element have been processed */ -void box_construct_element_after(dom_node *n, html_content *content) +static void box_construct_element_after(dom_node *n, html_content *content) { struct box_construct_props props; struct box *box = box_for_node(n); @@ -1083,14 +826,168 @@ void box_construct_element_after(dom_node *n, html_content *content) } } + +/** + * 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; +} + + +/** + * 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 + */ +static 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] = ascii_to_upper(s[i]); + break; + case CSS_TEXT_TRANSFORM_LOWERCASE: + for (i = 0; i < len; ++i) + if ((unsigned char) s[i] < 0x80) + s[i] = ascii_to_lower(s[i]); + break; + case CSS_TEXT_TRANSFORM_CAPITALIZE: + if ((unsigned char) s[0] < 0x80) + s[0] = ascii_to_upper(s[0]); + for (i = 1; i < len; ++i) + if ((unsigned char) s[i] < 0x80 && + ascii_is_space(s[i - 1])) + s[i] = ascii_to_upper(s[i]); + break; + default: + break; + } +} + + /** * 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) +static bool box_construct_text(struct box_construct_ctx *ctx) { struct box_construct_props props; struct box *box = NULL; @@ -1326,1755 +1223,164 @@ bool box_construct_text(struct box_construct_ctx *ctx) 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, &c->media, inline_style); - - /* No longer need inline style */ - if (inline_style != NULL) - css_stylesheet_destroy(inline_style); - - return styles; -} - - -/** - * Apply the CSS text-transform property to given text for its ASCII chars. - * - * \param s string to transform - * \param len length of s - * \param tt transform type - */ - -void box_text_transform(char *s, unsigned int len, enum css_text_transform_e tt) -{ - unsigned int i; - if (len == 0) - return; - switch (tt) { - case CSS_TEXT_TRANSFORM_UPPERCASE: - for (i = 0; i < len; ++i) - if ((unsigned char) s[i] < 0x80) - s[i] = toupper(s[i]); - break; - case CSS_TEXT_TRANSFORM_LOWERCASE: - for (i = 0; i < len; ++i) - if ((unsigned char) s[i] < 0x80) - s[i] = tolower(s[i]); - break; - case CSS_TEXT_TRANSFORM_CAPITALIZE: - if ((unsigned char) s[0] < 0x80) - s[0] = toupper(s[0]); - for (i = 1; i < len; ++i) - if ((unsigned char) s[i] < 0x80 && - isspace(s[i - 1])) - s[i] = toupper(s[i]); - break; - default: - break; - } -} - - -/** - * \name Special case element handlers - * - * These functions are called by box_construct_element() when an element is - * being converted, according to the entries in element_table. - * - * The parameters are the xmlNode, the content for the document, and a partly - * filled in box structure for the element. - * - * Return true on success, false on memory exhaustion. Set *convert_children - * to false if children of this element in the XML tree should be skipped (for - * example, if they have been processed in some special way already). - * - * Elements ordered as in the HTML 4.01 specification. Section numbers in - * brackets [] refer to the spec. - * - * \{ - */ - -/** - * Document body [7.5.1]. - */ - -bool box_body(BOX_SPECIAL_PARAMS) -{ - css_color color; - - css_computed_background_color(box->style, &color); - if (nscss_color_is_transparent(color)) - content->background_colour = NS_TRANSPARENT; - else - content->background_colour = nscss_color_to_ns(color); - - return true; -} - - -/** - * Forced line break [9.3.2]. - */ - -bool box_br(BOX_SPECIAL_PARAMS) -{ - box->type = BOX_BR; - return true; -} - -/** - * Preformatted text [9.3.4]. - */ - -bool box_pre(BOX_SPECIAL_PARAMS) -{ - box->flags |= PRE_STRIP; - return true; -} - -/** - * Anchor [12.2]. - */ - -bool box_a(BOX_SPECIAL_PARAMS) -{ - bool ok; - nsurl *url; - dom_string *s; - dom_exception err; - - err = dom_element_get_attribute(n, corestring_dom_href, &s); - if (err == DOM_NO_ERR && s != NULL) { - ok = box_extract_link(content, s, content->base_url, &url); - dom_string_unref(s); - if (!ok) - return false; - if (url) { - if (box->href != NULL) - nsurl_unref(box->href); - box->href = url; - } - } - - /* name and id share the same namespace */ - err = dom_element_get_attribute(n, corestring_dom_name, &s); - if (err == DOM_NO_ERR && s != NULL) { - lwc_string *lwc_name; - - err = dom_string_intern(s, &lwc_name); - - dom_string_unref(s); - - if (err == DOM_NO_ERR) { - /* name replaces existing id - * TODO: really? */ - if (box->id != NULL) - lwc_string_unref(box->id); - - box->id = lwc_name; - } - } - - /* target frame [16.3] */ - err = dom_element_get_attribute(n, corestring_dom_target, &s); - if (err == DOM_NO_ERR && s != NULL) { - if (dom_string_caseless_lwc_isequal(s, - corestring_lwc__blank)) - box->target = TARGET_BLANK; - else if (dom_string_caseless_lwc_isequal(s, - corestring_lwc__top)) - box->target = TARGET_TOP; - else if (dom_string_caseless_lwc_isequal(s, - corestring_lwc__parent)) - box->target = TARGET_PARENT; - else if (dom_string_caseless_lwc_isequal(s, - corestring_lwc__self)) - /* the default may have been overridden by a - * <base target=...>, so this is different to 0 */ - box->target = TARGET_SELF; - else { - /* 6.16 says that frame names must begin with [a-zA-Z] - * This doesn't match reality, so just take anything */ - box->target = talloc_strdup(content->bctx, - dom_string_data(s)); - if (!box->target) { - dom_string_unref(s); - return false; - } - } - dom_string_unref(s); - } - - return true; -} - - -/** - * Embedded image [13.2]. - */ - -bool box_image(BOX_SPECIAL_PARAMS) -{ - bool ok; - dom_string *s; - dom_exception err; - nsurl *url; - enum css_width_e wtype; - enum css_height_e htype; - css_fixed value = 0; - css_unit wunit = CSS_UNIT_PX; - css_unit hunit = CSS_UNIT_PX; - - if (box->style && ns_computed_display(box->style, - box_is_root(n)) == CSS_DISPLAY_NONE) - return true; - - /* handle alt text */ - err = dom_element_get_attribute(n, corestring_dom_alt, &s); - if (err == DOM_NO_ERR && s != NULL) { - char *alt = squash_whitespace(dom_string_data(s)); - dom_string_unref(s); - if (alt == NULL) - return false; - box->text = talloc_strdup(content->bctx, alt); - free(alt); - if (box->text == NULL) - return false; - box->length = strlen(box->text); - } - - if (nsoption_bool(foreground_images) == false) { - return true; - } - - /* imagemap associated with this image */ - if (!box_get_attribute(n, "usemap", content->bctx, &box->usemap)) - return false; - if (box->usemap && box->usemap[0] == '#') - box->usemap++; - - /* get image URL */ - err = dom_element_get_attribute(n, corestring_dom_src, &s); - if (err != DOM_NO_ERR || s == NULL) - return true; - - if (box_extract_link(content, s, content->base_url, &url) == false) { - dom_string_unref(s); - return false; - } - - dom_string_unref(s); - - if (url == NULL) - return true; - - /* start fetch */ - box->flags |= IS_REPLACED; - ok = html_fetch_object(content, url, box, image_types, - content->base.available_width, 1000, false); - nsurl_unref(url); - - wtype = css_computed_width(box->style, &value, &wunit); - htype = css_computed_height(box->style, &value, &hunit); - - if (wtype == CSS_WIDTH_SET && wunit != CSS_UNIT_PCT && - htype == CSS_HEIGHT_SET && hunit != CSS_UNIT_PCT) { - /* We know the dimensions the image will be shown at before it's - * fetched. */ - box->flags |= REPLACE_DIM; - } - - return ok; -} - - -/** - * Noscript element - */ - -bool box_noscript(BOX_SPECIAL_PARAMS) -{ - /* If scripting is enabled, do not display the contents of noscript */ - if (content->enable_scripting) - *convert_children = false; - - return true; -} - /** - * Destructor for object_params, for <object> elements - * - * \param o The object params being destroyed. - * \return 0 to allow talloc to continue destroying the tree. - */ -static int box_object_talloc_destructor(struct object_params *o) -{ - if (o->codebase != NULL) - nsurl_unref(o->codebase); - if (o->classid != NULL) - nsurl_unref(o->classid); - if (o->data != NULL) - nsurl_unref(o->data); - - return 0; -} - -/** - * Generic embedded object [13.3]. + * Convert an ELEMENT node to a box tree fragment, + * then schedule conversion of the next ELEMENT node */ - -bool box_object(BOX_SPECIAL_PARAMS) +static void convert_xml_to_box(struct box_construct_ctx *ctx) { - 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); - } + dom_node *next; + bool convert_children; + uint32_t num_processed = 0; + const uint32_t max_processed_before_yield = 10; - /* add parameters to linked list */ - err = dom_node_get_first_child(n, &c); - if (err != DOM_NO_ERR) - return false; + do { + convert_children = true; - while (c != NULL) { - dom_node *next; - dom_node_type type; + assert(ctx->n != NULL); - err = dom_node_get_node_type(c, &type); - if (err != DOM_NO_ERR) { - dom_node_unref(c); - return false; + if (box_construct_element(ctx, &convert_children) == false) { + ctx->cb(ctx->content, false); + dom_node_unref(ctx->n); + free(ctx); + return; } - if (type == DOM_ELEMENT_NODE) { - dom_string *name; + /* 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_name(c, &name); + err = dom_node_get_node_type(next, &type); if (err != DOM_NO_ERR) { - dom_node_unref(c); - return false; + ctx->cb(ctx->content, false); + dom_node_unref(next); + free(ctx); + return; } - 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); + if (type == DOM_ELEMENT_NODE) 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; + 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; } } - param->next = params->params; - params->params = param; - } - - err = dom_node_get_next_sibling(c, &next); - if (err != DOM_NO_ERR) { - dom_node_unref(c); - return false; - } - - dom_node_unref(c); - c = next; - } - - box->object_params = params; - - /* start fetch (MIME type is ok or not specified) */ - box->flags |= IS_REPLACED; - if (!html_fetch_object(content, - params->data ? params->data : params->classid, - box, CONTENT_ANY, content->base.available_width, 1000, - false)) - return false; - - *convert_children = false; - return true; -} - - -/** - * Window subdivision [16.2.1]. - */ - -bool box_frameset(BOX_SPECIAL_PARAMS) -{ - bool ok; - - if (content->frameset) { - NSLOG(netsurf, INFO, "Error: multiple framesets in document."); - /* Don't convert children */ - if (convert_children) - *convert_children = false; - /* And ignore this spurious frameset */ - box->type = BOX_NONE; - return true; - } - - content->frameset = talloc_zero(content->bctx, struct content_html_frames); - if (!content->frameset) - return false; - - ok = box_create_frameset(content->frameset, n, content); - if (ok) - box->type = BOX_NONE; - - if (convert_children) - *convert_children = false; - return ok; -} - - -/** - * Destructor for content_html_frames, for frame elements - * - * \param f The frame params being destroyed. - * \return 0 to allow talloc to continue destroying the tree. - */ -static int box_frames_talloc_destructor(struct content_html_frames *f) -{ - if (f->url != NULL) { - nsurl_unref(f->url); - f->url = NULL; - } - - return 0; -} - - -/** - * Parse a multi-length-list, as defined by HTML 4.01. - * - * \param ds dom string to parse - * \param count updated to number of entries - * \return array of struct box_multi_length, or 0 on memory exhaustion - */ -static struct frame_dimension * -box_parse_multi_lengths(const dom_string *ds, unsigned int *count) -{ - char *end; - unsigned int i, n; - struct frame_dimension *length; - const char *s; - - s = dom_string_data(ds); - - for (i = 0, n = 1; s[i]; i++) - if (s[i] == ',') - n++; - - length = calloc(n, sizeof(struct frame_dimension)); - if (!length) - return NULL; - - for (i = 0; i != n; i++) { - while (ascii_is_space(*s)) { - s++; - } - length[i].value = strtof(s, &end); - if (length[i].value <= 0) { - length[i].value = 1; - } - s = end; - switch (*s) { - case '%': - length[i].unit = FRAME_DIMENSION_PERCENT; - break; - case '*': - length[i].unit = FRAME_DIMENSION_RELATIVE; - break; - default: - length[i].unit = FRAME_DIMENSION_PIXELS; - break; - } - while (*s && *s != ',') { - s++; - } - if (*s == ',') { - s++; - } - } - - *count = n; - return length; -} - - -bool box_create_frameset(struct content_html_frames *f, dom_node *n, - html_content *content) { - unsigned int row, col, index, i; - unsigned int rows = 1, cols = 1; - dom_string *s; - dom_exception err; - nsurl *url; - struct frame_dimension *row_height = 0, *col_width = 0; - dom_node *c, *next; - struct content_html_frames *frame; - bool default_border = true; - colour default_border_colour = 0x000000; - - /* parse rows and columns */ - err = dom_element_get_attribute(n, corestring_dom_rows, &s); - if (err == DOM_NO_ERR && s != NULL) { - row_height = box_parse_multi_lengths(s, &rows); - dom_string_unref(s); - if (row_height == NULL) - return false; - } else { - row_height = calloc(1, sizeof(struct frame_dimension)); - if (row_height == NULL) - return false; - row_height->value = 100; - row_height->unit = FRAME_DIMENSION_PERCENT; - } - - err = dom_element_get_attribute(n, corestring_dom_cols, &s); - if (err == DOM_NO_ERR && s != NULL) { - col_width = box_parse_multi_lengths(s, &cols); - dom_string_unref(s); - if (col_width == NULL) { - free(row_height); - return false; - } - } else { - col_width = calloc(1, sizeof(struct frame_dimension)); - if (col_width == NULL) { - free(row_height); - return false; - } - col_width->value = 100; - col_width->unit = FRAME_DIMENSION_PERCENT; - } - - /* common extension: border="0|1" to control all children */ - err = dom_element_get_attribute(n, corestring_dom_border, &s); - if (err == DOM_NO_ERR && s != NULL) { - if ((dom_string_data(s)[0] == '0') && - (dom_string_data(s)[1] == '\0')) - default_border = false; - dom_string_unref(s); - } - - /* common extension: frameborder="yes|no" to control all children */ - err = dom_element_get_attribute(n, corestring_dom_frameborder, &s); - if (err == DOM_NO_ERR && s != NULL) { - if (dom_string_caseless_lwc_isequal(s, - corestring_lwc_no) == 0) - default_border = false; - dom_string_unref(s); - } - - /* common extension: bordercolor="#RRGGBB|<named colour>" to control - *all children */ - err = dom_element_get_attribute(n, corestring_dom_bordercolor, &s); - if (err == DOM_NO_ERR && s != NULL) { - css_color color; - - if (nscss_parse_colour(dom_string_data(s), &color)) - default_border_colour = nscss_color_to_ns(color); - - dom_string_unref(s); - } - - /* update frameset and create default children */ - f->cols = cols; - f->rows = rows; - f->scrolling = BW_SCROLLING_NO; - f->children = talloc_array(content->bctx, struct content_html_frames, - (rows * cols)); - - talloc_set_destructor(f->children, box_frames_talloc_destructor); - - for (row = 0; row < rows; row++) { - for (col = 0; col < cols; col++) { - index = (row * cols) + col; - frame = &f->children[index]; - frame->cols = 0; - frame->rows = 0; - frame->width = col_width[col]; - frame->height = row_height[row]; - frame->margin_width = 0; - frame->margin_height = 0; - frame->name = NULL; - frame->url = NULL; - frame->no_resize = false; - frame->scrolling = BW_SCROLLING_AUTO; - frame->border = default_border; - frame->border_colour = default_border_colour; - frame->children = NULL; + next = next_node(next, ctx->content, true); } - } - 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); - } + ctx->n = next; - 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); - } + if (next == NULL) { + /* Conversion complete */ + struct box root; - err = dom_element_get_attribute(c, corestring_dom_bordercolor, - &s); - if (err == DOM_NO_ERR && s != NULL) { - css_color color; + memset(&root, 0, sizeof(root)); - if (nscss_parse_colour(dom_string_data(s), - &color)) - frame->border_colour = - nscss_color_to_ns(color); + root.type = BOX_BLOCK; + root.children = root.last = ctx->root_box; + root.children->parent = &root; - dom_string_unref(s); - } + /** \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; - /* advance */ - err = dom_node_get_next_sibling(c, &next); - if (err != DOM_NO_ERR) { - dom_node_unref(c); - return false; + ctx->cb(ctx->content, true); } - 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; - dom_string *type = NULL; - dom_exception err; - nsurl *url; - nserror error; - - gadget = html_forms_get_control_for_node(content->forms, n); - if (gadget == NULL) { - return false; - } - - box->gadget = gadget; - box->flags |= IS_REPLACED; - gadget->box = box; - gadget->html = content; - - /* get entry type */ - err = dom_element_get_attribute(n, corestring_dom_type, &type); - if ((err != DOM_NO_ERR) || (type == NULL)) { - /* no type so "text" is assumed */ - if (box_input_text(content, box, n) == false) { - return false; - } - *convert_children = false; - return true; - } - + assert(ctx->n == NULL); - if (dom_string_caseless_lwc_isequal(type, corestring_lwc_password)) { - if (box_input_text(content, box, n) == false) - goto no_memory; - - } else if (dom_string_caseless_lwc_isequal(type, corestring_lwc_file)) { - box->type = BOX_INLINE_BLOCK; - - } else if (dom_string_caseless_lwc_isequal(type, - corestring_lwc_hidden)) { - /* no box for hidden inputs */ - box->type = BOX_NONE; - - } else if ((dom_string_caseless_lwc_isequal(type, - corestring_lwc_checkbox) || - dom_string_caseless_lwc_isequal(type, - corestring_lwc_radio))) { - - } else if (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 (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); - } + free(ctx); + return; } - } else { - /* unhandled type the default is "text" */ - if (box_input_text(content, box, n) == false) - goto no_memory; - } - - dom_string_unref(type); - - *convert_children = false; - - return true; - -no_memory: - 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 */ + } while (++num_processed < max_processed_before_yield); - return true; + /* More work to do: schedule a continuation */ + guit->misc->schedule(0, (void *)convert_xml_to_box, ctx); } -/** - * Option selector [17.6]. - */ - -bool box_select(BOX_SPECIAL_PARAMS) +/* exported function documented in html/box_construct.h */ +nserror +dom_to_box(dom_node *n, + html_content *c, + box_construct_complete_cb cb, + void **box_conversion_context) { - 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); - form_free_control(gadget); - 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); - form_free_control(gadget); - return false; - } + struct box_construct_ctx *ctx; - dom_node_unref(c2); - c2 = next2; - } - } else { - dom_string_unref(name); - } + assert(box_conversion_context != NULL); - err = dom_node_get_next_sibling(c, &next); - if (err != DOM_NO_ERR) { - dom_node_unref(c); - form_free_control(gadget); - return false; + 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; } - - 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); + ctx = malloc(sizeof(*ctx)); + if (ctx == NULL) { + return NSERROR_NOMEM; } - 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; + ctx->content = c; + ctx->n = dom_node_ref(n); + ctx->root_box = NULL; + ctx->cb = cb; + ctx->bctx = c->bctx; - if (!box_input_text(content, box, n)) - return false; + *box_conversion_context = ctx; - *convert_children = false; - return true; + return guit->misc->schedule(0, (void *)convert_xml_to_box, ctx); } -/** - * 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) +/* exported function documented in html/box_construct.h */ +nserror cancel_dom_to_box(void *box_conversion_context) { - 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); + struct box_construct_ctx *ctx = box_conversion_context; + nserror err; - 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; + err = guit->misc->schedule(-1, (void *)convert_xml_to_box, ctx); + if (err != NSERROR_OK) { + return err; } - 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; - } + dom_node_unref(ctx->n); + free(ctx); - 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); + return NSERROR_OK; } -/** - * \} - */ - -/** - * 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) +/* exported function documented in html/box_construct.h */ +struct box *box_for_node(dom_node *n) { - char *result; - dom_string *attr, *attr_name; + struct box *box = NULL; dom_exception err; - err = dom_string_create_interned((const uint8_t *) attribute, - strlen(attribute), &attr_name); + err = dom_node_get_user_data(n, corestring_dom___ns_key_box_node_data, + (void *) &box); 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 NULL; - return true; + return box; } - -/* exported function documented in html/box.h */ +/* exported function documented in html/box_construct.h */ bool box_extract_link(const html_content *content, const dom_string *dsrel, diff --git a/content/handlers/html/box_construct.h b/content/handlers/html/box_construct.h new file mode 100644 index 000000000..f4bd119b1 --- /dev/null +++ b/content/handlers/html/box_construct.h @@ -0,0 +1,113 @@ +/* + * Copyright 2020 Vincent Sanders <vince@netsurf-browser.org> + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/** + * \file + * HTML Box tree construction interface. + * + * This stage of rendering converts a tree of dom_nodes (produced by libdom) + * to a tree of struct box. The box tree represents the structure of the + * document as given by the CSS display and float properties. + * + * For example, consider the following HTML: + * \code + * <h1>Example Heading</h1> + * <p>Example paragraph <em>with emphasised text</em> etc.</p> \endcode + * + * This would produce approximately the following box tree with default CSS + * rules: + * \code + * BOX_BLOCK (corresponds to h1) + * BOX_INLINE_CONTAINER + * BOX_INLINE "Example Heading" + * BOX_BLOCK (p) + * BOX_INLINE_CONTAINER + * BOX_INLINE "Example paragraph " + * BOX_INLINE "with emphasised text" (em) + * BOX_INLINE "etc." \endcode + * + * Note that the em has been collapsed into the INLINE_CONTAINER. + * + * If these CSS rules were applied: + * \code + * h1 { display: table-cell } + * p { display: table-cell } + * em { float: left; width: 5em } \endcode + * + * then the box tree would instead look like this: + * \code + * BOX_TABLE + * BOX_TABLE_ROW_GROUP + * BOX_TABLE_ROW + * BOX_TABLE_CELL (h1) + * BOX_INLINE_CONTAINER + * BOX_INLINE "Example Heading" + * BOX_TABLE_CELL (p) + * BOX_INLINE_CONTAINER + * BOX_INLINE "Example paragraph " + * BOX_FLOAT_LEFT (em) + * BOX_BLOCK + * BOX_INLINE_CONTAINER + * BOX_INLINE "with emphasised text" + * BOX_INLINE "etc." \endcode + * + * Here implied boxes have been added and a float is present. + */ + +#ifndef NETSURF_HTML_BOX_CONSTRUCT_H +#define NETSURF_HTML_BOX_CONSTRUCT_H + +/** + * Construct a box tree from a dom and html content + * + * \param n dom document + * \param c content of type CONTENT_HTML to construct box tree in + * \param cb callback to report conversion completion + * \param box_conversion_context pointer that recives the conversion context + * \return netsurf error code indicating status of call + */ +nserror dom_to_box(struct dom_node *n, struct html_content *c, box_construct_complete_cb cb, void **box_conversion_context); + + +/** + * aborts any ongoing box construction + */ +nserror cancel_dom_to_box(void *box_conversion_context); + + +/** + * Retrieve the box for a dom node, if there is one + * + * \param node The DOM node + * \return The box if there is one + */ +struct box *box_for_node(struct dom_node *node); + +/** + * 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); + +#endif diff --git a/content/handlers/html/box_inspect.c b/content/handlers/html/box_inspect.c new file mode 100644 index 000000000..6591b6446 --- /dev/null +++ b/content/handlers/html/box_inspect.c @@ -0,0 +1,898 @@ +/* + * Copyright 2008 Michael Drake <tlsa@netsurf-browser.org> + * Copyright 2020 Vincent Sanders <vince@netsurf-browser.org> + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/** + * \file + * implementation of box tree inspection. + */ + +#include <stdio.h> +#include <dom/dom.h> + +#include "utils/utils.h" +#include "utils/nsurl.h" +#include "utils/errors.h" +#include "netsurf/types.h" +#include "netsurf/content.h" +#include "netsurf/mouse.h" +#include "css/utils.h" +#include "css/dump.h" +#include "desktop/scrollbar.h" + +#include "html/private.h" +#include "html/box.h" +#include "html/box_inspect.h" + +/** + * 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 +}; + +#define box_is_float(box) (box->type == BOX_FLOAT_LEFT || \ + box->type == BOX_FLOAT_RIGHT) + +/** + * Determine if a point lies within a box. + * + * \param[in] unit_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 css_unit_ctx *unit_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(css_unit_len2device_px( + box->style, + unit_len_ctx, + css_rect.left, + css_rect.lunit)); + } + if (css_rect.top_auto == false) { + r.y0 += FIXTOINT(css_unit_len2device_px( + box->style, + unit_len_ctx, + css_rect.top, + css_rect.tunit)); + } + if (css_rect.right_auto == false) { + r.x1 = box->border[LEFT].width + + FIXTOINT(css_unit_len2device_px( + box->style, + unit_len_ctx, + css_rect.right, + css_rect.runit)); + } + if (css_rect.bottom_auto == false) { + r.y1 = box->border[TOP].width + + FIXTOINT(css_unit_len2device_px( + box->style, + unit_len_ctx, + css_rect.bottom, + css_rect.bunit)); + } + + /* 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; +} + + +/** + * 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; + } + fallthrough; + + 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; +} + + +/** + * 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; +} + + +/* Exported function documented in html/box.h */ +void box_coords(struct box *box, int *x, int *y) +{ + *x = box->x; + *y = box->y; + while (box->parent) { + if (box_is_float(box)) { + assert(box->float_container); + box = box->float_container; + } else { + box = box->parent; + } + *x += box->x - scrollbar_get_offset(box->scroll_x); + *y += box->y - scrollbar_get_offset(box->scroll_y); + } +} + + +/* Exported function documented in html/box.h */ +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; +} + + +/* Exported function documented in html/box.h */ +struct box * +box_at_point(const css_unit_ctx *unit_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(unit_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; +} + + +/* Exported function documented in html/box.h */ +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; +} + + +/* Exported function documented in html/box.h */ +bool box_visible(struct box *box) +{ + /* visibility: hidden */ + if (box->style && + css_computed_visibility(box->style) == CSS_VISIBILITY_HIDDEN) { + return false; + } + + return true; +} + + +/* Exported function documented in html/box.h */ +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, "desc(%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; + + case BOX_FLEX: + fprintf(stream, "FLEX "); + break; + + case BOX_INLINE_FLEX: + fprintf(stream, "INLINE_FLEX "); + 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); + } +} + + +/* exported interface documented in html/box.h */ +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; +} + + +/* exported interface documented in html/box.h */ +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; +} + + +/* Exported function documented in html/box.h */ +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; +} diff --git a/content/handlers/html/box_inspect.h b/content/handlers/html/box_inspect.h new file mode 100644 index 000000000..a218326d8 --- /dev/null +++ b/content/handlers/html/box_inspect.h @@ -0,0 +1,155 @@ +/* + * Copyright 2020 Vincent Sanders <vince@netsurf-browser.org> + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/** + * \file + * HTML Box tree inspection interface. + */ + +#ifndef NETSURF_HTML_BOX_INSPECT_H +#define NETSURF_HTML_BOX_INSPECT_H + +/** + * 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); + + +/** + * 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); + + +/** + * Find the boxes at a point. + * + * \param unit_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(unit_len_ctx, box, x, y, &box_x, &box_y))) { + * // process box + * } + * \endcode + */ +struct box *box_at_point(const css_unit_ctx *unit_len_ctx, struct box *box, const int x, const int y, int *box_x, int *box_y); + + +/** + * 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); + + +/** + * 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); + + +/** + * Print a box tree to a file. + */ +void box_dump(FILE *stream, struct box *box, unsigned int depth, bool style); + + +/** + * 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 *box); + + +/** + * 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 *box); + + +/** + * 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); + + +/** + * Check if layout box is a first child. + * + * \param[in] b Box to check. + * \return true iff box is first child. + */ +static inline bool box_is_first_child(struct box *b) +{ + return (b->parent == NULL || b == b->parent->children); +} + +static inline unsigned box_count_children(const struct box *b) +{ + const struct box *c = b->children; + unsigned count = 0; + + while (c != NULL) { + count++; + c = c->next; + } + + return count; +} + +#endif diff --git a/content/handlers/html/box_manipulate.c b/content/handlers/html/box_manipulate.c new file mode 100644 index 000000000..8073a7fd1 --- /dev/null +++ b/content/handlers/html/box_manipulate.c @@ -0,0 +1,352 @@ +/* + * Copyright 2005-2007 James Bursa <bursa@users.sourceforge.net> + * Copyright 2003 Phil Mellor <monkeyson@users.sourceforge.net> + * Copyright 2005 John M Bell <jmb202@ecs.soton.ac.uk> + * Copyright 2008 Michael Drake <tlsa@netsurf-browser.org> + * Copyright 2020 Vincent Sanders <vince@netsurf-browser.org> + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/** + * \file + * implementation of box tree manipulation. + */ + + +#include "utils/errors.h" +#include "utils/talloc.h" +#include "utils/nsurl.h" +#include "netsurf/types.h" +#include "netsurf/mouse.h" +#include "desktop/scrollbar.h" + +#include "html/private.h" +#include "html/form_internal.h" +#include "html/interaction.h" +#include "html/box.h" +#include "html/box_manipulate.h" + + +/** + * 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; +} + + +/* Exported function documented in html/box.h */ +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_value = 1; + 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; +} + + +/* Exported function documented in html/box.h */ +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; +} + + +/* Exported function documented in html/box.h */ +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; +} + + +/* Exported function documented in html/box.h */ +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); +} + + +/* Exported function documented in html/box.h */ +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); +} + + +/* Exported function documented in html/box.h */ +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); +} + + +/* exported interface documented in html/box.h */ +nserror +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; + nserror res; + + 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 NSERROR_OK; + } + + 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) { + return NSERROR_NOMEM; + } + data->c = c; + data->box = box; + res = scrollbar_create(false, + visible_height, + full_height, + visible_height, + data, + html_overflow_scroll_callback, + &(box->scroll_y)); + if (res != NSERROR_OK) { + return res; + } + } 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) { + return NSERROR_OK; + } + data->c = c; + data->box = box; + res = scrollbar_create(true, + visible_width - (right ? SCROLLBAR_WIDTH : 0), + full_width, + visible_width, + data, + html_overflow_scroll_callback, + &box->scroll_x); + if (res != NSERROR_OK) { + return res; + } + } 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 NSERROR_OK; +} + + diff --git a/content/handlers/html/box_manipulate.h b/content/handlers/html/box_manipulate.h new file mode 100644 index 000000000..43a66a7d9 --- /dev/null +++ b/content/handlers/html/box_manipulate.h @@ -0,0 +1,106 @@ +/* + * Copyright 2005 James Bursa <bursa@users.sourceforge.net> + * Copyright 2003 Phil Mellor <monkeyson@users.sourceforge.net> + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/** + * \file + * Box tree manipulation interface. + */ + +#ifndef NETSURF_HTML_BOX_MANIPULATE_H +#define NETSURF_HTML_BOX_MANIPULATE_H + + +/** + * 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, struct nsurl *href, const char *target, const char *title, lwc_string *id, void *context); + + +/** + * 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); + + +/** + * 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); + + +/** + * 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); + + +/** + * 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); + + +/** + * Free the data in a single box structure. + * + * \param box box to free + */ +void box_free_box(struct box *box); + + +/** + * 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 + */ +nserror box_handle_scrollbars(struct content *c, struct box *box, + bool bottom, bool right); + + +#endif diff --git a/content/handlers/html/box_normalise.c b/content/handlers/html/box_normalise.c index 7155cb722..8f25b031f 100644 --- a/content/handlers/html/box_normalise.c +++ b/content/handlers/html/box_normalise.c @@ -21,7 +21,7 @@ /** * \file - * Box tree normalisation (implementation). + * Box tree normalisation implementation. */ #include <assert.h> @@ -32,9 +32,11 @@ #include "utils/errors.h" #include "css/select.h" -#include "html/box.h" -#include "html/html_internal.h" +#include "html/private.h" #include "html/table.h" +#include "html/box.h" +#include "html/box_manipulate.h" +#include "html/box_normalise.h" /* Define to enable box normalise debug */ #undef BOX_NORMALISE_DEBUG @@ -66,282 +68,321 @@ struct columns { }; -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 + * Compute the column index at which the current cell begins. + * Additionally, update the column record to reflect row spanning. * - * 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 + * \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 */ +static 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 */ -bool box_normalise_block( - struct box *block, - const struct box *root, - html_content *c) + /* 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; +} + + +static 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 *table; + struct box *cell = NULL; css_computed_style *style; + unsigned int i; nscss_select_ctx ctx; - assert(block != NULL); - assert(root != NULL); + assert(row != NULL); + assert(row->type == BOX_TABLE_ROW); 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); + NSLOG(netsurf, INFO, "row %p", row); #endif - next_child = child->next; /* child may be destroyed */ + for (child = row->children; child != NULL; child = next_child) { + next_child = child->next; switch (child->type) { - case BOX_BLOCK: + case BOX_TABLE_CELL: /* ok */ if (box_normalise_block(child, root, c) == false) return false; + cell = child; break; + case BOX_FLEX: + case BOX_BLOCK: 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); + /* 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, block->style); + style = nscss_get_blank_style(&ctx, &c->unit_len_ctx, + row->style); if (style == NULL) return false; - table = box_create(NULL, style, true, block->href, - block->target, NULL, NULL, c->bctx); - if (table == NULL) { + cell = box_create(NULL, style, true, row->href, + row->target, NULL, NULL, c->bctx); + if (cell == NULL) { css_computed_style_destroy(style); return false; } - table->type = BOX_TABLE; + cell->type = BOX_TABLE_CELL; if (child->prev == NULL) - block->children = table; + row->children = cell; else - child->prev->next = table; + child->prev->next = cell; - table->prev = child->prev; + cell->prev = child->prev; while (child != NULL && ( + child->type == BOX_FLEX || + child->type == BOX_BLOCK || + child->type == BOX_INLINE_CONTAINER || + child->type == BOX_TABLE || child->type == BOX_TABLE_ROW_GROUP || - child->type == BOX_TABLE_ROW || - child->type == BOX_TABLE_CELL)) { - box_add_child(table, child); + child->type == BOX_TABLE_ROW)) { + box_add_child(cell, 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; + assert(cell->last != NULL); + + cell->last->next = NULL; + cell->next = next_child = child; + if (cell->next != NULL) + cell->next->prev = cell; else - block->last = table; - table->parent = block; + row->last = cell; + cell->parent = row; - if (box_normalise_table(table, root, c) == false) + if (box_normalise_block(cell, root, c) == false) return false; break; + case BOX_INLINE: + case BOX_INLINE_END: + case BOX_INLINE_FLEX: + 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; } -bool box_normalise_table( - struct box *table, - const struct box *root, - 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) { struct box *child; struct box *next_child; - struct box *row_group; + struct box *row; css_computed_style *style; - struct columns col_info; nscss_select_ctx ctx; + unsigned int group_row_count = 0; - assert(table != NULL); - assert(table->type == BOX_TABLE); + assert(row_group != 0); + assert(row_group->type == BOX_TABLE_ROW_GROUP); ctx.root_style = root->style; #ifdef BOX_NORMALISE_DEBUG - NSLOG(netsurf, INFO, "table %p", table); + NSLOG(netsurf, INFO, "row_group %p", row_group); #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) { + for (child = row_group->children; child != NULL; child = next_child) { next_child = child->next; + switch (child->type) { - case BOX_TABLE_ROW_GROUP: + case BOX_TABLE_ROW: /* ok */ - if (box_normalise_table_row_group(child, root, - &col_info, c) == false) { - free(col_info.spans); + group_row_count++; + if (box_normalise_table_row(child, root, col_info, + c) == false) return false; - } break; + case BOX_FLEX: case BOX_BLOCK: case BOX_INLINE_CONTAINER: case BOX_TABLE: - case BOX_TABLE_ROW: + case BOX_TABLE_ROW_GROUP: case BOX_TABLE_CELL: - /* insert implied table row group */ - assert(table->style != NULL); + /* 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, table->style); - if (style == NULL) { - free(col_info.spans); + style = nscss_get_blank_style(&ctx, &c->unit_len_ctx, + row_group->style); + if (style == NULL) return false; - } - row_group = box_create(NULL, style, true, table->href, - table->target, NULL, NULL, c->bctx); - if (row_group == NULL) { + row = box_create(NULL, style, true, row_group->href, + row_group->target, NULL, NULL, c->bctx); + if (row == NULL) { css_computed_style_destroy(style); - free(col_info.spans); return false; } - - row_group->type = BOX_TABLE_ROW_GROUP; + row->type = BOX_TABLE_ROW; if (child->prev == NULL) - table->children = row_group; + row_group->children = row; else - child->prev->next = row_group; + child->prev->next = row; - row_group->prev = child->prev; + row->prev = child->prev; while (child != NULL && ( + child->type == BOX_FLEX || child->type == BOX_BLOCK || child->type == BOX_INLINE_CONTAINER || child->type == BOX_TABLE || - child->type == BOX_TABLE_ROW || + child->type == BOX_TABLE_ROW_GROUP || child->type == BOX_TABLE_CELL)) { - box_add_child(row_group, child); + box_add_child(row, child); next_child = child->next; child->next = NULL; child = next_child; } - assert(row_group->last != NULL); + assert(row->last != NULL); - row_group->last->next = NULL; - row_group->next = next_child = child; - if (row_group->next != NULL) - row_group->next->prev = row_group; + row->last->next = NULL; + row->next = next_child = child; + if (row->next != NULL) + row->next->prev = row; else - table->last = row_group; - row_group->parent = table; + row_group->last = row; + row->parent = row_group; - if (box_normalise_table_row_group(row_group, root, - &col_info, c) == false) { - free(col_info.spans); + 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_FLEX: case BOX_INLINE_BLOCK: case BOX_FLOAT_LEFT: case BOX_FLOAT_RIGHT: @@ -352,48 +393,26 @@ bool box_normalise_table( 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; - + if (row_group->children == NULL) { #ifdef BOX_NORMALISE_DEBUG NSLOG(netsurf, INFO, - "table->children == 0, creating implied row"); + "row_group->children == 0, inserting implied row"); #endif - assert(table->style != NULL); + 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, 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); + style = nscss_get_blank_style(&ctx, &c->unit_len_ctx, + row_group->style); if (style == NULL) { - box_free(row_group); - free(col_info.spans); return false; } @@ -401,8 +420,6 @@ bool box_normalise_table( 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; @@ -410,21 +427,16 @@ bool box_normalise_table( row->parent = row_group; row_group->children = row_group->last = row; - row_group->parent = table; - table->children = table->last = row_group; - - table->rows = 1; - } + group_row_count = 1; - if (box_normalise_table_spans(table, root, col_info.spans, c) == false) { - free(col_info.spans); - return false; + /* Keep table's row count in sync */ + col_info->num_rows++; } - free(col_info.spans); + row_group->rows = group_row_count; #ifdef BOX_NORMALISE_DEBUG - NSLOG(netsurf, INFO, "table %p done", table); + NSLOG(netsurf, INFO, "row_group %p done", row_group); #endif return true; @@ -441,12 +453,11 @@ bool box_normalise_table( * \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) +static 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; @@ -531,6 +542,7 @@ bool box_normalise_table_spans( ctx.universal = c->universal; style = nscss_get_blank_style(&ctx, + &c->unit_len_ctx, table_row->style); if (style == NULL) return false; @@ -601,101 +613,118 @@ bool box_normalise_table_spans( } -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(struct box *table, const struct box *root, html_content * c) { struct box *child; struct box *next_child; - struct box *row; + struct box *row_group; css_computed_style *style; + struct columns col_info; nscss_select_ctx ctx; - unsigned int group_row_count = 0; - assert(row_group != 0); - assert(row_group->type == BOX_TABLE_ROW_GROUP); + assert(table != NULL); + assert(table->type == BOX_TABLE); ctx.root_style = root->style; #ifdef BOX_NORMALISE_DEBUG - NSLOG(netsurf, INFO, "row_group %p", row_group); + NSLOG(netsurf, INFO, "table %p", table); #endif - for (child = row_group->children; child != NULL; child = next_child) { - next_child = child->next; + 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: + case BOX_TABLE_ROW_GROUP: /* ok */ - group_row_count++; - if (box_normalise_table_row(child, root, col_info, - c) == false) + if (box_normalise_table_row_group(child, root, + &col_info, c) == false) { + free(col_info.spans); return false; + } break; + case BOX_FLEX: case BOX_BLOCK: case BOX_INLINE_CONTAINER: case BOX_TABLE: - case BOX_TABLE_ROW_GROUP: + case BOX_TABLE_ROW: case BOX_TABLE_CELL: - /* insert implied table row */ - assert(row_group->style != NULL); + /* 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, row_group->style); - if (style == NULL) + style = nscss_get_blank_style(&ctx, &c->unit_len_ctx, + table->style); + if (style == NULL) { + 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) { + 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->type = BOX_TABLE_ROW; + + row_group->type = BOX_TABLE_ROW_GROUP; if (child->prev == NULL) - row_group->children = row; + table->children = row_group; else - child->prev->next = row; + child->prev->next = row_group; - row->prev = child->prev; + row_group->prev = child->prev; while (child != NULL && ( + child->type == BOX_FLEX || child->type == BOX_BLOCK || child->type == BOX_INLINE_CONTAINER || child->type == BOX_TABLE || - child->type == BOX_TABLE_ROW_GROUP || + child->type == BOX_TABLE_ROW || child->type == BOX_TABLE_CELL)) { - box_add_child(row, child); + box_add_child(row_group, child); next_child = child->next; child->next = NULL; child = next_child; } - assert(row->last != NULL); + assert(row_group->last != NULL); - row->last->next = NULL; - row->next = next_child = child; - if (row->next != NULL) - row->next->prev = row; + row_group->last->next = NULL; + row_group->next = next_child = child; + if (row_group->next != NULL) + row_group->next->prev = row_group; else - row_group->last = row; - row->parent = row_group; + table->last = row_group; + row_group->parent = table; - group_row_count++; - if (box_normalise_table_row(row, root, col_info, - c) == false) + 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_FLEX: case BOX_INLINE_BLOCK: case BOX_FLOAT_LEFT: case BOX_FLOAT_RIGHT: @@ -706,25 +735,50 @@ bool box_normalise_table_row_group( assert(0); break; default: + fprintf(stderr, "%i\n", child->type); assert(0); } } - if (row_group->children == NULL) { + 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, - "row_group->children == 0, inserting implied row"); + "table->children == 0, creating implied row"); #endif - assert(row_group->style != NULL); + 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, row_group->style); + style = nscss_get_blank_style(&ctx, &c->unit_len_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, &c->unit_len_ctx, + row_group->style); if (style == NULL) { + box_free(row_group); + free(col_info.spans); return false; } @@ -732,6 +786,8 @@ bool box_normalise_table_row_group( 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; @@ -739,114 +795,129 @@ bool box_normalise_table_row_group( row->parent = row_group; row_group->children = row_group->last = row; - group_row_count = 1; + row_group->parent = table; + table->children = table->last = row_group; - /* Keep table's row count in sync */ - col_info->num_rows++; + table->rows = 1; } - row_group->rows = group_row_count; + 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, "row_group %p done", row_group); + NSLOG(netsurf, INFO, "table %p done", table); #endif return true; } - -bool box_normalise_table_row( - struct box *row, +static bool box_normalise_flex( + struct box *flex_container, const struct box *root, - struct columns *col_info, - html_content * c) + html_content *c) { struct box *child; struct box *next_child; - struct box *cell = NULL; + struct box *implied_flex_item; css_computed_style *style; - unsigned int i; nscss_select_ctx ctx; - assert(row != NULL); - assert(row->type == BOX_TABLE_ROW); + assert(flex_container != NULL); + assert(root != NULL); ctx.root_style = root->style; #ifdef BOX_NORMALISE_DEBUG - NSLOG(netsurf, INFO, "row %p", row); + NSLOG(netsurf, INFO, "flex_container %p, flex_container->type %u", + flex_container, flex_container->type); #endif - for (child = row->children; child != NULL; child = next_child) { - next_child = child->next; + assert(flex_container->type == BOX_FLEX || + flex_container->type == BOX_INLINE_FLEX); + + for (child = flex_container->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_TABLE_CELL: + case BOX_FLEX: /* ok */ - if (box_normalise_block(child, root, c) == false) + if (box_normalise_flex(child, root, c) == false) return false; - cell = child; break; case BOX_BLOCK: + /* ok */ + if (box_normalise_block(child, root, c) == false) + return false; + break; case BOX_INLINE_CONTAINER: - case BOX_TABLE: - case BOX_TABLE_ROW_GROUP: - case BOX_TABLE_ROW: - /* insert implied table cell */ - assert(row->style != NULL); + /* insert implied flex item */ + assert(flex_container->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); + style = nscss_get_blank_style(&ctx, &c->unit_len_ctx, + flex_container->style); if (style == NULL) return false; - cell = box_create(NULL, style, true, row->href, - row->target, NULL, NULL, c->bctx); - if (cell == NULL) { + implied_flex_item = box_create(NULL, style, true, + flex_container->href, + flex_container->target, + NULL, NULL, c->bctx); + if (implied_flex_item == NULL) { css_computed_style_destroy(style); return false; } - cell->type = BOX_TABLE_CELL; + implied_flex_item->type = BOX_BLOCK; if (child->prev == NULL) - row->children = cell; + flex_container->children = implied_flex_item; else - child->prev->next = cell; + child->prev->next = implied_flex_item; - cell->prev = child->prev; + implied_flex_item->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); + while (child != NULL && + child->type == BOX_INLINE_CONTAINER) { + box_add_child(implied_flex_item, 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; + implied_flex_item->last->next = NULL; + implied_flex_item->next = next_child = child; + if (implied_flex_item->next != NULL) + implied_flex_item->next->prev = implied_flex_item; else - row->last = cell; - cell->parent = row; + flex_container->last = implied_flex_item; + implied_flex_item->parent = flex_container; - if (box_normalise_block(cell, root, c) == false) + if (box_normalise_block(implied_flex_item, + 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_FLEX: case BOX_INLINE_BLOCK: case BOX_FLOAT_LEFT: case BOX_FLOAT_RIGHT: @@ -856,119 +927,74 @@ bool box_normalise_table_row( 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 - */ + case BOX_TABLE_ROW_GROUP: + case BOX_TABLE_ROW: + case BOX_TABLE_CELL: + /* insert implied table */ + assert(flex_container->style != NULL); -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 */ + ctx.ctx = c->select_ctx; + ctx.quirks = (c->quirks == DOM_DOCUMENT_QUIRKS_MODE_FULL); + ctx.base_url = c->base_url; + ctx.universal = c->universal; - /* 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++; - } + style = nscss_get_blank_style(&ctx, &c->unit_len_ctx, + flex_container->style); + if (style == NULL) + return false; - /* Update current column with calculated start */ - col_info->current_column = cell_start_col; + implied_flex_item = box_create(NULL, style, true, + flex_container->href, + flex_container->target, + NULL, NULL, c->bctx); + if (implied_flex_item == NULL) { + css_computed_style_destroy(style); + return false; + } + implied_flex_item->type = BOX_TABLE; - /* 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; + if (child->prev == NULL) + flex_container->children = implied_flex_item; + else + child->prev->next = implied_flex_item; - cell_end_col = cell_start_col + col_span; + implied_flex_item->prev = child->prev; - 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; + while (child != NULL && ( + child->type == BOX_TABLE_ROW_GROUP || + child->type == BOX_TABLE_ROW || + child->type == BOX_TABLE_CELL)) { + box_add_child(implied_flex_item, child); - col_info->spans = spans; - col_info->num_columns = cell_end_col; + next_child = child->next; + child->next = NULL; + child = next_child; + } - /* Mark new final column as sentinel */ - col_info->spans[cell_end_col].row_span = 0; - col_info->spans[cell_end_col].auto_row = false; - } + implied_flex_item->last->next = NULL; + implied_flex_item->next = next_child = child; + if (implied_flex_item->next != NULL) + implied_flex_item->next->prev = implied_flex_item; + else + flex_container->last = implied_flex_item; + implied_flex_item->parent = flex_container; - /* 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; + if (box_normalise_table(implied_flex_item, + root, c) == false) + return false; + break; + default: + assert(0); + } } - /* 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) +static bool +box_normalise_inline_container(struct box *cont, + const struct box *root, + html_content * c) { struct box *child; struct box *next_child; @@ -994,6 +1020,11 @@ bool box_normalise_inline_container( if (box_normalise_block(child, root, c) == false) return false; break; + case BOX_INLINE_FLEX: + /* ok */ + if (box_normalise_flex(child, root, c) == false) + return false; + break; case BOX_FLOAT_LEFT: case BOX_FLOAT_RIGHT: /* ok */ @@ -1010,6 +1041,11 @@ bool box_normalise_inline_container( c) == false) return false; break; + case BOX_FLEX: + if (box_normalise_flex(child->children, root, + c) == false) + return false; + break; default: assert(0); } @@ -1028,6 +1064,7 @@ bool box_normalise_inline_container( box_free(child); } break; + case BOX_FLEX: case BOX_BLOCK: case BOX_INLINE_CONTAINER: case BOX_TABLE: @@ -1045,3 +1082,125 @@ bool box_normalise_inline_container( return true; } + +/* Exported function documented in html/box_normalise.h */ +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_FLEX: + /* ok */ + if (box_normalise_flex(child, root, c) == false) + return false; + break; + 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_FLEX: + 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, &c->unit_len_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; +} diff --git a/content/handlers/html/box_normalise.h b/content/handlers/html/box_normalise.h new file mode 100644 index 000000000..377cd9019 --- /dev/null +++ b/content/handlers/html/box_normalise.h @@ -0,0 +1,66 @@ +/* + * Copyright 2020 Vincent Sanders <vince@netsurf-browser.org> + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/** + * \file + * HTML Box tree normalise interface. + * + * 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_NORMALISE_H +#define NETSURF_HTML_BOX_NORMALISE_H + +/** + * 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, FLEX + * FLEX, INLINE_FLEX BLOCK, INLINE_CONTAINER, TABLE, FLEX + * INLINE_CONTAINER INLINE, INLINE_BLOCK, FLOAT_LEFT, FLOAT_RIGHT, BR, TEXT, INLINE_FLEX + * 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, FLEX (same as BLOCK) + * FLOAT_(LEFT|RIGHT) exactly 1 BLOCK, TABLE or FLEX + * \endcode + */ +bool box_normalise_block(struct box *block, const struct box *root, struct html_content *c); + +#endif diff --git a/content/handlers/html/box_special.c b/content/handlers/html/box_special.c new file mode 100644 index 000000000..db3c4126d --- /dev/null +++ b/content/handlers/html/box_special.c @@ -0,0 +1,1935 @@ +/* + * Copyright 2005 James Bursa <bursa@users.sourceforge.net> + * Copyright 2003 Phil Mellor <monkeyson@users.sourceforge.net> + * Copyright 2005 John M Bell <jmb202@ecs.soton.ac.uk> + * Copyright 2006 Richard Wilson <info@tinct.net> + * Copyright 2008 Michael Drake <tlsa@netsurf-browser.org> + * Copyright 2020 Vincent Sanders <vince@netsurf-browser.org> + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/** + * \file + * Implementation of special element handling conversion. + */ + +#include <string.h> +#include <stdbool.h> +#include <dom/dom.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 "utils/nsurl.h" +#include "netsurf/plot_style.h" +#include "css/hints.h" +#include "desktop/frame_types.h" +#include "content/content_factory.h" + +#include "html/html.h" +#include "html/private.h" +#include "html/object.h" +#include "html/box.h" +#include "html/box_manipulate.h" +#include "html/box_construct.h" +#include "html/box_special.h" +#include "html/box_textarea.h" +#include "html/form_internal.h" + + +static const content_type image_types = CONTENT_IMAGE; + + +/** + * determine if a box is the root node + * + * \param n node to check + * \return true if node is root else false. + */ +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; +} + + +/** + * 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; +} + + +/** + * 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; +} + + +/** + * 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; +} + + +/** + * create a frameset box tree + */ +static bool +box_create_frameset(struct content_html_frames *f, + dom_node *n, + html_content *content) +{ + unsigned int row, col, index, i; + unsigned int rows = 1, cols = 1; + dom_string *s; + dom_exception err; + nsurl *url; + struct frame_dimension *row_height = 0, *col_width = 0; + dom_node *c, *next; + struct content_html_frames *frame; + bool default_border = true; + colour default_border_colour = 0x000000; + + /* parse rows and columns */ + err = dom_element_get_attribute(n, corestring_dom_rows, &s); + if (err == DOM_NO_ERR && s != NULL) { + row_height = box_parse_multi_lengths(s, &rows); + dom_string_unref(s); + if (row_height == NULL) + return false; + } else { + row_height = calloc(1, sizeof(struct frame_dimension)); + if (row_height == NULL) + return false; + row_height->value = 100; + row_height->unit = FRAME_DIMENSION_PERCENT; + } + + err = dom_element_get_attribute(n, corestring_dom_cols, &s); + if (err == DOM_NO_ERR && s != NULL) { + col_width = box_parse_multi_lengths(s, &cols); + dom_string_unref(s); + if (col_width == NULL) { + free(row_height); + return false; + } + } else { + col_width = calloc(1, sizeof(struct frame_dimension)); + if (col_width == NULL) { + free(row_height); + return false; + } + col_width->value = 100; + col_width->unit = FRAME_DIMENSION_PERCENT; + } + + /* common extension: border="0|1" to control all children */ + err = dom_element_get_attribute(n, corestring_dom_border, &s); + if (err == DOM_NO_ERR && s != NULL) { + if ((dom_string_data(s)[0] == '0') && + (dom_string_data(s)[1] == '\0')) + default_border = false; + dom_string_unref(s); + } + + /* common extension: frameborder="yes|no" to control all children */ + err = dom_element_get_attribute(n, corestring_dom_frameborder, &s); + if (err == DOM_NO_ERR && s != NULL) { + if (dom_string_caseless_lwc_isequal(s, + corestring_lwc_no) == 0) + default_border = false; + dom_string_unref(s); + } + + /* common extension: bordercolor="#RRGGBB|<named colour>" to control + *all children */ + err = dom_element_get_attribute(n, corestring_dom_bordercolor, &s); + if (err == DOM_NO_ERR && s != NULL) { + css_color color; + + if (nscss_parse_colour(dom_string_data(s), &color)) + default_border_colour = nscss_color_to_ns(color); + + dom_string_unref(s); + } + + /* update frameset and create default children */ + f->cols = cols; + f->rows = rows; + f->scrolling = BW_SCROLLING_NO; + f->children = talloc_array(content->bctx, struct content_html_frames, + (rows * cols)); + + talloc_set_destructor(f->children, box_frames_talloc_destructor); + + for (row = 0; row < rows; row++) { + for (col = 0; col < cols; col++) { + index = (row * cols) + col; + frame = &f->children[index]; + frame->cols = 0; + frame->rows = 0; + frame->width = col_width[col]; + frame->height = row_height[row]; + frame->margin_width = 0; + frame->margin_height = 0; + frame->name = NULL; + frame->url = NULL; + frame->no_resize = false; + frame->scrolling = BW_SCROLLING_AUTO; + frame->border = default_border; + frame->border_colour = default_border_colour; + frame->children = NULL; + } + } + free(col_width); + free(row_height); + + /* create the frameset windows */ + err = dom_node_get_first_child(n, &c); + if (err != DOM_NO_ERR) + return false; + + for (row = 0; c != NULL && row < rows; row++) { + for (col = 0; c != NULL && col < cols; col++) { + while (c != NULL) { + dom_node_type type; + dom_string *name; + + err = dom_node_get_node_type(c, &type); + if (err != DOM_NO_ERR) { + dom_node_unref(c); + return false; + } + + err = dom_node_get_node_name(c, &name); + if (err != DOM_NO_ERR) { + dom_node_unref(c); + return false; + } + + if (type != DOM_ELEMENT_NODE || + (!dom_string_caseless_lwc_isequal( + name, + corestring_lwc_frame) && + !dom_string_caseless_lwc_isequal( + name, + corestring_lwc_frameset + ))) { + err = dom_node_get_next_sibling(c, + &next); + if (err != DOM_NO_ERR) { + dom_string_unref(name); + dom_node_unref(c); + return false; + } + + dom_string_unref(name); + dom_node_unref(c); + c = next; + } else { + /* Got a FRAME or FRAMESET element */ + dom_string_unref(name); + break; + } + } + + if (c == NULL) + break; + + /* get current frame */ + index = (row * cols) + col; + frame = &f->children[index]; + + /* nest framesets */ + err = dom_node_get_node_name(c, &s); + if (err != DOM_NO_ERR) { + dom_node_unref(c); + return false; + } + + if (dom_string_caseless_lwc_isequal(s, + corestring_lwc_frameset)) { + dom_string_unref(s); + frame->border = 0; + if (box_create_frameset(frame, c, + content) == false) { + dom_node_unref(c); + return false; + } + + err = dom_node_get_next_sibling(c, &next); + if (err != DOM_NO_ERR) { + dom_node_unref(c); + return false; + } + + dom_node_unref(c); + c = next; + continue; + } + + dom_string_unref(s); + + /* get frame URL (not required) */ + url = NULL; + err = dom_element_get_attribute(c, corestring_dom_src, &s); + if (err == DOM_NO_ERR && s != NULL) { + box_extract_link(content, s, content->base_url, + &url); + dom_string_unref(s); + } + + /* copy url */ + if (url != NULL) { + /* no self-references */ + if (nsurl_compare(content->base_url, url, + NSURL_COMPLETE) == false) + frame->url = url; + url = NULL; + } + + /* fill in specified values */ + err = dom_element_get_attribute(c, corestring_dom_name, &s); + if (err == DOM_NO_ERR && s != NULL) { + frame->name = talloc_strdup(content->bctx, + dom_string_data(s)); + dom_string_unref(s); + } + + if (dom_element_has_attribute(c, corestring_dom_noresize, + &frame->no_resize) != DOM_NO_ERR) { + /* If we can't read the attribute for some reason, + * assume we didn't have it. + */ + frame->no_resize = false; + } + + 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; +} + + +/** + * Get the value of a dom node element's attribute. + * + * \param n dom 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 returning true does not imply that the attribute was found. If the + * attribute was not found, *value will be unchanged. + */ +static 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; +} + + +/** + * 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; + uint8_t display = css_computed_display_static(box->style); + + switch (display) { + case CSS_DISPLAY_GRID: + case CSS_DISPLAY_FLEX: + case CSS_DISPLAY_BLOCK: + box->type = BOX_BLOCK; + break; + default: + box->type = BOX_INLINE_BLOCK; + break; + } + + 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); +} + + +/** + * 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 + */ +static 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; + + if (dom_element_has_attribute(n, corestring_dom_selected, &selected) != DOM_NO_ERR) { + /* Assume not selected if we can't read the attribute presence */ + selected = false; + } + + /* 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; +} + + +/** + * \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. + * + * \{ + */ + +/** + * special element handler for Anchor [12.2]. + */ +static bool +box_a(dom_node *n, + html_content *content, + struct box *box, + bool *convert_children) +{ + 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 = "_blank"; + else if (dom_string_caseless_lwc_isequal(s, + corestring_lwc__top)) + box->target = "_top"; + else if (dom_string_caseless_lwc_isequal(s, + corestring_lwc__parent)) + box->target = "_parent"; + else if (dom_string_caseless_lwc_isequal(s, + corestring_lwc__self)) + /* the default may have been overridden by a + * <base target=...>, so this is different to 0 */ + box->target = "_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; +} + + +/** + * Document body special element handler [7.5.1]. + */ +static bool +box_body(dom_node *n, + html_content *content, + struct box *box, + bool *convert_children) +{ + 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; +} + + +/** + * special element handler for forced line break [9.3.2]. + */ +static bool +box_br(dom_node *n, + html_content *content, + struct box *box, + bool *convert_children) +{ + box->type = BOX_BR; + return true; +} + + +/** + * special element handler for Push button [17.5]. + */ +static bool +box_button(dom_node *n, + html_content *content, + struct box *box, + bool *convert_children) +{ + 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; +} + + +/** + * Canvas element + */ +static bool +box_canvas(dom_node *n, + html_content *content, + struct box *box, + bool *convert_children) +{ + /* If scripting is not enabled display the contents of canvas */ + if (!content->enable_scripting) { + return true; + } + *convert_children = false; + + if (box->style && ns_computed_display(box->style, + box_is_root(n)) == CSS_DISPLAY_NONE) + return true; + + /* This is replaced content */ + box->flags |= IS_REPLACED | REPLACE_DIM; + + return true; +} + + +/** + * Embedded object (not in any HTML specification: + * see http://wp.netscape.com/assist/net_sites/new_html3_prop.html ) + */ +static bool +box_embed(dom_node *n, + html_content *content, + struct box *box, + bool *convert_children) +{ + 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_node_unref(attr); + dom_namednodemap_unref(attrs); + return false; + } + + if (dom_string_caseless_lwc_isequal(name, corestring_lwc_src)) { + dom_node_unref(attr); + dom_string_unref(name); + continue; + } + + err = dom_attr_get_value(attr, &value); + if (err != DOM_NO_ERR) { + dom_node_unref(attr); + dom_string_unref(name); + dom_namednodemap_unref(attrs); + return false; + } + + param = talloc(content->bctx, struct object_param); + if (param == NULL) { + dom_node_unref(attr); + 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); + dom_node_unref(attr); + + 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, false); +} + + +/** + * Window subdivision [16.2.1]. + */ +static bool +box_frameset(dom_node *n, + html_content *content, + struct box *box, + bool *convert_children) +{ + 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; +} + + +/** + * Inline subwindow [16.5]. + */ +static bool +box_iframe(dom_node *n, + html_content *content, + struct box *box, + bool *convert_children) +{ + 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; +} + + +/** + * Embedded image [13.2]. + */ +static bool +box_image(dom_node *n, + html_content *content, + struct box *box, + bool *convert_children) +{ + 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, 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; +} + + +/** + * Form control [17.4]. + */ +static bool +box_input(dom_node *n, + html_content *content, + struct box *box, + bool *convert_children) +{ + struct form_control *gadget; + dom_string *type = NULL; + dom_exception err; + nsurl *url; + nserror error; + + gadget = html_forms_get_control_for_node(content->forms, n); + if (gadget == NULL) { + return false; + } + + box->gadget = gadget; + box->flags |= IS_REPLACED; + gadget->box = box; + gadget->html = content; + + /* get entry type */ + err = dom_element_get_attribute(n, corestring_dom_type, &type); + if ((err != DOM_NO_ERR) || (type == NULL)) { + /* no type so "text" is assumed */ + if (box_input_text(content, box, n) == false) { + return false; + } + *convert_children = false; + return true; + } + + if (dom_string_caseless_lwc_isequal(type, corestring_lwc_password)) { + if (box_input_text(content, box, n) == false) + goto no_memory; + + } else if (dom_string_caseless_lwc_isequal(type, corestring_lwc_file)) { + box->type = BOX_INLINE_BLOCK; + + } else if (dom_string_caseless_lwc_isequal(type, + corestring_lwc_hidden)) { + /* no box for hidden inputs */ + box->type = BOX_NONE; + + } else if ((dom_string_caseless_lwc_isequal(type, + corestring_lwc_checkbox) || + dom_string_caseless_lwc_isequal(type, + corestring_lwc_radio))) { + + } else if (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 (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, + false)) { + nsurl_unref(url); + goto no_memory; + } + } + nsurl_unref(url); + } + } + } else { + /* unhandled type the default is "text" */ + if (box_input_text(content, box, n) == false) + goto no_memory; + } + + dom_string_unref(type); + + *convert_children = false; + + return true; + +no_memory: + dom_string_unref(type); + + return false; +} + + +/** + * Noscript element + */ +static bool +box_noscript(dom_node *n, + html_content *content, + struct box *box, + bool *convert_children) +{ + /* If scripting is enabled, do not display the contents of noscript */ + if (content->enable_scripting) { + *convert_children = false; + } + + return true; +} + + +/** + * Generic embedded object [13.3]. + */ +static bool +box_object(dom_node *n, + html_content *content, + struct box *box, + bool *convert_children) +{ + 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_string_unref(name); + dom_node_unref(c); + break; + } + dom_string_unref(name); + + 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, + false)) + return false; + + *convert_children = false; + return true; +} + + +/** + * Preformatted text [9.3.4]. + */ +static bool +box_pre(dom_node *n, + html_content *content, + struct box *box, + bool *convert_children) +{ + box->flags |= PRE_STRIP; + return true; +} + + +/** + * Option selector [17.6]. + */ +static bool +box_select(dom_node *n, + html_content *content, + struct box *box, + bool *convert_children) +{ + 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); + form_free_control(gadget); + 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); + form_free_control(gadget); + 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; +} + + +/** + * Multi-line text field [17.7]. + */ +static bool box_textarea(dom_node *n, + html_content *content, + struct box *box, + bool *convert_children) +{ + /* 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; +} + + +/** + * \} + */ + + +/* exported interface documented in html/box_special.h */ +bool +convert_special_elements(dom_node *node, + html_content *content, + struct box *box, + bool *convert_children) +{ + dom_exception exc; + dom_html_element_type tag_type; + bool res; + + 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_A: + res = box_a(node, content, box, convert_children); + break; + + case DOM_HTML_ELEMENT_TYPE_BODY: + res = box_body(node, content, box, convert_children); + break; + + case DOM_HTML_ELEMENT_TYPE_BR: + res = box_br(node, content, box, convert_children); + break; + + case DOM_HTML_ELEMENT_TYPE_BUTTON: + res = box_button(node, content, box, convert_children); + break; + + case DOM_HTML_ELEMENT_TYPE_CANVAS: + res = box_canvas(node, content, box, convert_children); + break; + + case DOM_HTML_ELEMENT_TYPE_EMBED: + res = box_embed(node, content, box, convert_children); + break; + + case DOM_HTML_ELEMENT_TYPE_FRAMESET: + res = box_frameset(node, content, box, convert_children); + break; + + case DOM_HTML_ELEMENT_TYPE_IFRAME: + res = box_iframe(node, content, box, convert_children); + break; + + case DOM_HTML_ELEMENT_TYPE_IMG: + res = box_image(node, content, box, convert_children); + break; + + case DOM_HTML_ELEMENT_TYPE_INPUT: + res = box_input(node, content, box, convert_children); + break; + + case DOM_HTML_ELEMENT_TYPE_NOSCRIPT: + res = box_noscript(node, content, box, convert_children); + break; + + case DOM_HTML_ELEMENT_TYPE_OBJECT: + res = box_object(node, content, box, convert_children); + break; + + case DOM_HTML_ELEMENT_TYPE_PRE: + res = box_pre(node, content, box, convert_children); + break; + + case DOM_HTML_ELEMENT_TYPE_SELECT: + res = box_select(node, content, box, convert_children); + break; + + case DOM_HTML_ELEMENT_TYPE_TEXTAREA: + res = box_textarea(node, content, box, convert_children); + break; + + default: + res = true; + } + + return res; +} diff --git a/content/handlers/html/box_special.h b/content/handlers/html/box_special.h new file mode 100644 index 000000000..973ab976a --- /dev/null +++ b/content/handlers/html/box_special.h @@ -0,0 +1,35 @@ +/* + * Copyright 2020 Vincent Sanders <vince@netsurf-browser.org> + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/** + * \file + * HTML Box tree construction special element conversion interface. + */ + +#ifndef NETSURF_HTML_BOX_SPECIAL_H +#define NETSURF_HTML_BOX_SPECIAL_H + + +/** + * call an elements special conversion handler + * + * \return true if box construction should continue else false on error. + */ +bool convert_special_elements(dom_node *node, html_content *content, struct box *box, bool *convert_children); + +#endif diff --git a/content/handlers/html/box_textarea.c b/content/handlers/html/box_textarea.c index f0ba9f9de..d08660030 100644 --- a/content/handlers/html/box_textarea.c +++ b/content/handlers/html/box_textarea.c @@ -21,6 +21,7 @@ * Box tree treeview box replacement (implementation). */ +#include <string.h> #include <dom/dom.h> #include "utils/config.h" @@ -29,90 +30,97 @@ #include "netsurf/keypress.h" #include "netsurf/misc.h" #include "desktop/textarea.h" -#include "desktop/gui_internal.h" -#include "html/html_internal.h" +#include "html/private.h" +#include "html/interaction.h" #include "html/box.h" +#include "html/box_inspect.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) +nserror 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; - nserror res; + struct content *c = (struct content *)html; + nserror res = NSERROR_OK; assert(ta != NULL); - if (gadget->type != GADGET_TEXTAREA) { - switch (key) { - case NS_KEY_NL: - case NS_KEY_CR: - if (form) { - res = form_submit(content_get_url(c), - html->bw, - form, - NULL); - if (res != NSERROR_OK) { - guit->misc->warning(messages_get_errorcode(res), NULL); - } + if (gadget->type == GADGET_TEXTAREA) { + if (textarea_keypress(ta, key)) { + return NSERROR_OK; + } else { + return NSERROR_INVALID; + } + } - } - return true; + /* non textarea input */ + switch (key) { + case NS_KEY_NL: + case NS_KEY_CR: + if (form) { + res = form_submit(content_get_url(c), + html->bw, + form, + NULL); + } + break; - case NS_KEY_TAB: + 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) + 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); + if (next_input != NULL) { + textarea_set_caret(ta, -1); + textarea_set_caret(next_input->data.text.ta, 0); + } } - return true; + break; - case NS_KEY_SHIFT_TAB: + 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) + 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); + if (prev_input != NULL) { + textarea_set_caret(ta, -1); + textarea_set_caret(prev_input->data.text.ta, 0); + } } - return true; + break; - default: - /* Pass to textarea widget */ - break; + default: + /* Pass to textarea widget */ + if (!textarea_keypress(ta, key)) { + res = NSERROR_INVALID; } + break; } - return textarea_keypress(ta, key); + return res; } diff --git a/content/handlers/html/box_textarea.h b/content/handlers/html/box_textarea.h index 822fc8b10..219ef2301 100644 --- a/content/handlers/html/box_textarea.h +++ b/content/handlers/html/box_textarea.h @@ -45,8 +45,8 @@ bool box_textarea_create_textarea(struct html_content *html, * \param html html content object * \param box box with textarea widget * \param key keypress - * \return true iff keypress handled + * \return NSERROR_OK iff keypress handled */ -bool box_textarea_keypress(struct html_content *html, struct box *box, uint32_t key); +nserror box_textarea_keypress(struct html_content *html, struct box *box, uint32_t key); #endif diff --git a/content/handlers/html/html_css.c b/content/handlers/html/css.c index 2a2fde6c6..0bc38844f 100644 --- a/content/handlers/html/html_css.c +++ b/content/handlers/html/css.c @@ -21,6 +21,8 @@ * Processing for html content css operations. */ +#include "utils/config.h" + #include <assert.h> #include <ctype.h> #include <stdint.h> @@ -30,8 +32,8 @@ #include "utils/nsoption.h" #include "utils/corestrings.h" -#include "utils/config.h" #include "utils/log.h" +#include "netsurf/inttypes.h" #include "netsurf/misc.h" #include "netsurf/content.h" #include "content/hlcache.h" @@ -39,13 +41,17 @@ #include "desktop/gui_internal.h" #include "html/html.h" -#include "html/html_internal.h" +#include "html/private.h" +#include "html/css.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; +/** + * Convert css error to netsurf error. + */ static nserror css_error_to_nserror(css_error error) { switch (error) { @@ -79,10 +85,10 @@ static nserror css_error_to_nserror(css_error error) return NSERROR_CSS; } + /** * Callback for fetchcache() for stylesheets. */ - static nserror html_convert_css_callback(hlcache_handle *css, const hlcache_event *event, @@ -114,15 +120,12 @@ html_convert_css_callback(hlcache_handle *css, case CONTENT_MSG_ERROR: NSLOG(netsurf, INFO, "stylesheet %s failed: %s", nsurl_access(hlcache_handle_get_url(css)), - event->data.error); - /* fall through */ + event->data.errordata.errormsg); - 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: @@ -140,6 +143,7 @@ html_convert_css_callback(hlcache_handle *css, return NSERROR_OK; } + static nserror html_stylesheet_from_domnode(html_content *c, dom_node *node, @@ -170,7 +174,7 @@ html_stylesheet_from_domnode(html_content *c, dom_string_unref(style); - snprintf(urlbuf, sizeof(urlbuf), "x-ns-css:%u", key); + snprintf(urlbuf, sizeof(urlbuf), "x-ns-css:%"PRIu32"", key); error = nsurl_create(urlbuf, &url); if (error != NSERROR_OK) { @@ -194,6 +198,7 @@ html_stylesheet_from_domnode(html_content *c, return NSERROR_OK; } + /** * Process an inline stylesheet in the document. * @@ -201,7 +206,6 @@ html_stylesheet_from_domnode(html_content *c, * \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) { @@ -238,7 +242,7 @@ html_create_style_element(html_content *c, dom_node *style) (c->stylesheet_count + 1)); if (stylesheets == NULL) { - content_broadcast_errorcode(&c->base, NSERROR_NOMEM); + content_broadcast_error(&c->base, NSERROR_NOMEM, NULL); return false; } @@ -253,8 +257,9 @@ html_create_style_element(html_content *c, dom_node *style) return c->stylesheets + (c->stylesheet_count - 1); } -static bool html_css_process_modified_style(html_content *c, - struct html_stylesheet *s) + +static bool +html_css_process_modified_style(html_content *c, struct html_stylesheet *s) { hlcache_handle *sheet = NULL; nserror error; @@ -262,7 +267,7 @@ static bool html_css_process_modified_style(html_content *c, 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); + content_broadcast_error(&c->base, error, NULL); return false; } @@ -290,6 +295,10 @@ static bool html_css_process_modified_style(html_content *c, return true; } + +/** + * process a stylesheet that has been modified. + */ static void html_css_process_modified_styles(void *pw) { html_content *c = pw; @@ -309,6 +318,8 @@ static void html_css_process_modified_styles(void *pw) } } + +/* exported function documented in html/css.h */ bool html_css_update_style(html_content *c, dom_node *style) { unsigned int i; @@ -336,6 +347,8 @@ bool html_css_update_style(html_content *c, dom_node *style) return true; } + +/* exported function documented in html/css.h */ bool html_css_process_style(html_content *c, dom_node *node) { unsigned int i; @@ -367,6 +380,8 @@ bool html_css_process_style(html_content *c, dom_node *node) return true; } + +/* exported function documented in html/css.h */ bool html_css_process_link(html_content *htmlc, dom_node *node) { dom_string *rel, *type_attr, *media, *href; @@ -381,16 +396,20 @@ bool html_css_process_link(html_content *htmlc, dom_node *node) if (exc != DOM_NO_ERR || rel == NULL) return true; - if (strcasestr(dom_string_data(rel), "stylesheet") == 0) { + if (strcasestr(dom_string_data(rel), "stylesheet") == NULL) { dom_string_unref(rel); return true; - } else if (strcasestr(dom_string_data(rel), "alternate") != 0) { + } else if (strcasestr(dom_string_data(rel), "alternate") != NULL) { /* Ignore alternate stylesheets */ dom_string_unref(rel); return true; } dom_string_unref(rel); + if (nsoption_bool(author_level_css) == false) { + return true; + } + /* 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) { @@ -470,10 +489,11 @@ bool html_css_process_link(html_content *htmlc, dom_node *node) return true; no_memory: - content_broadcast_errorcode(&htmlc->base, ns_error); + content_broadcast_error(&htmlc->base, ns_error, NULL); return false; } + /* exported interface documented in html/html.h */ struct html_stylesheet *html_get_stylesheets(hlcache_handle *h, unsigned int *n) { @@ -488,7 +508,26 @@ struct html_stylesheet *html_get_stylesheets(hlcache_handle *h, unsigned int *n) } -/* exported interface documented in html/html_internal.h */ +/* exported function documented in html/css.h */ +bool html_css_saw_insecure_stylesheets(html_content *html) +{ + struct html_stylesheet *s; + unsigned int i; + + for (i = 0, s = html->stylesheets; i < html->stylesheet_count; + i++, s++) { + if (s->sheet != NULL) { + if (content_saw_insecure_objects(s->sheet)) { + return true; + } + } + } + + return false; +} + + +/* exported function documented in html/css.h */ nserror html_css_free_stylesheets(html_content *html) { unsigned int i; @@ -508,7 +547,8 @@ nserror html_css_free_stylesheets(html_content *html) return NSERROR_OK; } -/* exported interface documented in html/html_internal.h */ + +/* exported function documented in html/css.h */ nserror html_css_quirks_stylesheets(html_content *c) { nserror ns_error = NSERROR_OK; @@ -536,7 +576,8 @@ nserror html_css_quirks_stylesheets(html_content *c) return ns_error; } -/* exported interface documented in html/html_internal.h */ + +/* exported function documented in html/css.h */ nserror html_css_new_stylesheets(html_content *c) { nserror ns_error; @@ -606,6 +647,8 @@ nserror html_css_new_stylesheets(html_content *c) return ns_error; } + +/* exported function documented in html/css.h */ nserror html_css_new_selection_context(html_content *c, css_select_ctx **ret_select_ctx) { @@ -668,6 +711,8 @@ html_css_new_selection_context(html_content *c, css_select_ctx **ret_select_ctx) return NSERROR_OK; } + +/* exported function documented in html/css.h */ nserror html_css_init(void) { nserror error; @@ -697,6 +742,8 @@ nserror html_css_init(void) return error; } + +/* exported function documented in html/css.h */ void html_css_fini(void) { if (html_user_stylesheet_url != NULL) { diff --git a/content/handlers/html/css.h b/content/handlers/html/css.h new file mode 100644 index 000000000..35f6a61e9 --- /dev/null +++ b/content/handlers/html/css.h @@ -0,0 +1,109 @@ +/* + * Copyright 2020 Vincent Sanders <vince@netsurf-browser.org> + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/** + * \file + * HTML content handler CSS interface. + */ + +#ifndef NETSURF_HTML_CSS_H +#define NETSURF_HTML_CSS_H + +/** + * Initialise html content css handling. + * + * \return NSERROR_OK on success else error code + */ +nserror html_css_init(void); + +/** + * Finalise html content css handling. + */ +void html_css_fini(void); + +/** + * create a new css selection context for an html content. + * + * \param c The html content to create css selction on. + * \param select_ctx A pointer to receive the new context. + * \return NSERROR_OK on success and \a select_ctx updated else error code + */ +nserror html_css_new_selection_context(struct html_content *c, css_select_ctx **select_ctx); + +/** + * Initialise core stylesheets for a content + * + * \param c content structure to update + * \return NSERROR_OK on success or error code + */ +nserror html_css_new_stylesheets(struct html_content *c); + +/** + * Initialise quirk stylesheets for a content + * + * \param c content structure to update + * \return NSERROR_OK on success or error code + */ +nserror html_css_quirks_stylesheets(struct html_content *c); + +/** + * Free all css stylesheets associated with an HTML content. + * + * \param html The HTML content to free stylesheets from. + * \return NSERROR_OK on success or error code. + */ +nserror html_css_free_stylesheets(struct html_content *html); + +/** + * determine if any of the stylesheets were loaded insecurely + * + * \param htmlc The HTML content to check. + * \return true if there were insecurely loadd stylesheets else false. + */ +bool html_css_saw_insecure_stylesheets(struct html_content *htmlc); + +/** + * process a css stylesheet dom LINK node + * + * \param htmlc The HTML content. + * \param node the DOM link node to process. + * \return true on success else false. + */ +bool html_css_process_link(struct html_content *htmlc, dom_node *node); + +/** + * process a css style dom node + * + * \param htmlc The HTML content. + * \param node the DOM node to process. + * \return true on success else false. + */ +bool html_css_process_style(struct html_content *htmlc, dom_node *node); + +/** + * process a css style dom node update + * + * \param htmlc The HTML content. + * \param node the DOM node to process. + * \return true on success else false. + */ +bool html_css_update_style(struct html_content *htmlc, dom_node *node); + + + +#endif diff --git a/content/handlers/html/html_css_fetcher.c b/content/handlers/html/css_fetcher.c index 7987ea094..4e0f672e9 100644 --- a/content/handlers/html/html_css_fetcher.c +++ b/content/handlers/html/css_fetcher.c @@ -31,6 +31,7 @@ #include "netsurf/inttypes.h" #include "utils/config.h" +#include "utils/corestrings.h" #include "utils/log.h" #include "utils/ring.h" #include "utils/nsurl.h" @@ -38,7 +39,7 @@ #include "content/fetch.h" #include "content/fetchers.h" -#include "html/html_internal.h" +#include "html/private.h" typedef struct html_css_fetcher_item { uint32_t key; @@ -284,7 +285,6 @@ static void html_css_fetcher_poll(lwc_string *scheme) /* 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, @@ -296,13 +296,8 @@ nserror html_css_fetcher_register(void) .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); + return fetcher_add(lwc_string_ref(corestring_lwc_x_ns_css), + &html_css_fetcher_ops); } /* exported interface documented in html_internal.h */ diff --git a/content/handlers/html/dom_event.c b/content/handlers/html/dom_event.c new file mode 100644 index 000000000..d42882515 --- /dev/null +++ b/content/handlers/html/dom_event.c @@ -0,0 +1,789 @@ +/* + * Copyright 2010 Michael Drake <tlsa@netsurf-browser.org> + * Copyright 2020 Vincent Sanders <vince@netsurf-browser.org> + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/** + * \file + * Implementation of HTML content DOM event handling. + */ + +#include <string.h> + +#include "utils/config.h" +#include "utils/utils.h" +#include "utils/corestrings.h" +#include "utils/nsoption.h" +#include "utils/log.h" +#include "utils/ascii.h" +#include "utils/string.h" +#include "utils/nsurl.h" +#include "content/content.h" +#include "javascript/js.h" + +#include "netsurf/bitmap.h" + +#include "html/private.h" +#include "html/object.h" +#include "html/css.h" +#include "html/box.h" +#include "html/box_construct.h" +#include "html/form_internal.h" +#include "html/dom_event.h" + + +/** + * process a base element being inserted into the DOM + * + * \param htmlc The html content containing the DOM + * \param node The DOM node being inserted + * \return NSERROR_OK on success else appropriate error code + */ +static bool html_process_inserted_base(html_content *htmlc, 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 (htmlc->base_url != NULL) { + nsurl_unref(htmlc->base_url); + } + htmlc->base_url = url; + } + } + + + /* get target attribute if present and not already set */ + if (htmlc->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)) { + htmlc->base_target = strdup(dom_string_data(atr_string)); + } + dom_string_unref(atr_string); + } + + return true; +} + + + +/** + * Process img element being inserted into the DOM. + * + * \param htmlc The html content containing the DOM + * \param node The DOM node being inserted + * \return NSERROR_OK on success else appropriate error code + */ +static bool html_process_inserted_img(html_content *htmlc, 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(htmlc->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(htmlc, url, NULL, CONTENT_IMAGE, false); + nsurl_unref(url); + + return success; +} + + +/** + * process a LINK element being inserted into the DOM + * + * \note only the http-equiv attribute for refresh is currently considered + * + * \param htmlc The html content containing the DOM + * \param n The DOM node being inserted + * \return NSERROR_OK on success else appropriate error code + */ +static bool html_process_inserted_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; + + /* Handle stylesheet loading */ + html_css_process_link(c, (dom_node *)node); + + /* try Generic link handling */ + + 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 */ + (void)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 */ + (void)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 */ + (void)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 */ + (void)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; +} + + +/* handler for a SCRIPT which has been added to a tree */ +static void +dom_SCRIPT_showed_up(html_content *htmlc, dom_html_script_element *script) +{ + dom_exception exc; + dom_html_script_element_flags flags; + dom_hubbub_error res; + bool within; + + if (!htmlc->enable_scripting) { + NSLOG(netsurf, INFO, "Encountered a script, but scripting is off, ignoring"); + return; + } + + NSLOG(netsurf, DEEPDEBUG, "Encountered a script, node %p showed up", script); + + exc = dom_html_script_element_get_flags(script, &flags); + if (exc != DOM_NO_ERR) { + NSLOG(netsurf, DEEPDEBUG, "Unable to retrieve flags, giving up"); + return; + } + + if (flags & DOM_HTML_SCRIPT_ELEMENT_FLAG_PARSER_INSERTED) { + NSLOG(netsurf, DEBUG, "Script was parser inserted, skipping"); + return; + } + + exc = dom_node_contains(htmlc->document, script, &within); + if (exc != DOM_NO_ERR) { + NSLOG(netsurf, DEBUG, "Unable to determine if script was within document, ignoring"); + return; + } + + if (!within) { + NSLOG(netsurf, DEBUG, "Script was not within the document, ignoring for now"); + return; + } + + res = html_process_script(htmlc, (dom_node *) script); + if (res == DOM_HUBBUB_OK) { + NSLOG(netsurf, DEEPDEBUG, "Inserted script has finished running"); + } else { + if (res == (DOM_HUBBUB_HUBBUB_ERR | HUBBUB_PAUSED)) { + NSLOG(netsurf, DEEPDEBUG, "Inserted script has launced asynchronously"); + } else { + NSLOG(netsurf, DEEPDEBUG, "Failure starting script"); + } + } +} + + +/** + * process a META element being inserted into the DOM + * + * \note only the http-equiv attribute for refresh is currently considered + * + * \param htmlc The html content containing the DOM + * \param n The DOM node being inserted + * \return NSERROR_OK on success else appropriate error code + */ +static nserror html_process_inserted_meta(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; + + if (c->refresh) { + /* refresh already delt with */ + return 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; +} + + +/** + * Process title element being inserted into the DOM. + * + * https://html.spec.whatwg.org/multipage/semantics.html#the-title-element + * + * \param htmlc The html content containing the DOM + * \param node The DOM node being inserted + * \return NSERROR_OK on success else appropriate error code + */ +static nserror html_process_inserted_title(html_content *htmlc, dom_node *node) +{ + if (htmlc->title == NULL) { + /* only the first title is considered */ + htmlc->title = dom_node_ref(node); + } + return NSERROR_OK; +} + + +/** 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; +} + + +/** + * Deal with input elements being modified by resyncing their gadget + * if they have one. + */ +static void html_texty_element_update(html_content *htmlc, dom_node *node) +{ + struct box *box = box_for_node(node); + if (box == NULL) { + return; /* No Box (yet?) so no gadget to update */ + } + if (box->gadget == NULL) { + return; /* No gadget yet (under construction perhaps?) */ + } + form_gadget_sync_with_dom(box->gadget); + /* And schedule a redraw for the box */ + html__redraw_a_box(htmlc, box); +} + + +/** + * 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)) { + /* failed to obtain the event target node */ + return; + } + + 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_BASE: + html_process_inserted_base(htmlc, (dom_node *)node); + break; + + case DOM_HTML_ELEMENT_TYPE_IMG: + html_process_inserted_img(htmlc, (dom_node *)node); + break; + + case DOM_HTML_ELEMENT_TYPE_LINK: + html_process_inserted_link(htmlc, (dom_node *)node); + break; + + case DOM_HTML_ELEMENT_TYPE_META: + html_process_inserted_meta(htmlc, (dom_node *)node); + break; + + case DOM_HTML_ELEMENT_TYPE_STYLE: + if (nsoption_bool(author_level_css)) { + html_css_process_style(htmlc, (dom_node *)node); + } + break; + + case DOM_HTML_ELEMENT_TYPE_SCRIPT: + dom_SCRIPT_showed_up(htmlc, + (dom_html_script_element *)node); + break; + + case DOM_HTML_ELEMENT_TYPE_TITLE: + html_process_inserted_title(htmlc, (dom_node *)node); + break; + + default: + break; + } + + if (htmlc->enable_scripting) { + /* ensure javascript context is available */ + if (htmlc->jsthread == NULL) { + union content_msg_data msg_data; + + msg_data.jsthread = &htmlc->jsthread; + content_broadcast(&htmlc->base, + CONTENT_MSG_GETTHREAD, + &msg_data); + NSLOG(netsurf, INFO, + "javascript context: %p (htmlc: %p)", + htmlc->jsthread, + htmlc); + } + if (htmlc->jsthread != NULL) { + js_handle_new_element(htmlc->jsthread, + (dom_element *) node); + } + } + } + dom_node_unref(node); +} + + +/** + * callback for DOMNodeInsertedIntoDocument end type + */ +static void +dom_default_action_DOMNodeInsertedIntoDocument_cb(struct dom_event *evt, + void *pw) +{ + html_content *htmlc = pw; + dom_event_target *node; + dom_node_type type; + dom_exception exc; + + 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 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_SCRIPT: + dom_SCRIPT_showed_up(htmlc, (dom_html_script_element *) node); + fallthrough; + default: + break; + } + } + dom_node_unref(node); + } +} + + +/** + * callback for DOMSubtreeModified 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: + if (nsoption_bool(author_level_css)) { + html_css_update_style(htmlc, + (dom_node *)node); + } + break; + case DOM_HTML_ELEMENT_TYPE_TEXTAREA: + case DOM_HTML_ELEMENT_TYPE_INPUT: + html_texty_element_update(htmlc, (dom_node *)node); + fallthrough; + default: + break; + } + } + dom_node_unref(node); + } +} + + +/** + * callback for default action finished + */ +static void +dom_default_action_finished_cb(struct dom_event *evt, void *pw) +{ + html_content *htmlc = pw; + + if (htmlc->jsthread != NULL) + js_event_cleanup(htmlc->jsthread, evt); +} + + +/* exported interface documented in html/dom_event.c */ +dom_default_action_callback +html_dom_event_fetcher(dom_string *type, + dom_default_action_phase phase, + void **pw) +{ + NSLOG(netsurf, DEEPDEBUG, + "phase:%d type:%s", phase, 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_DOMNodeInsertedIntoDocument)) { + return dom_default_action_DOMNodeInsertedIntoDocument_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; +} diff --git a/content/handlers/html/dom_event.h b/content/handlers/html/dom_event.h new file mode 100644 index 000000000..dec828df7 --- /dev/null +++ b/content/handlers/html/dom_event.h @@ -0,0 +1,43 @@ +/* + * Copyright 2020 Vincent Sanders <vince@netsurf-browser.org> + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/** + * \file + * HTML content DOM event handling interface + */ + +#ifndef NETSURF_HTML_DOM_EVENT_H +#define NETSURF_HTML_DOM_EVENT_H + +/** + * html content DOM action 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 + */ +dom_default_action_callback html_dom_event_fetcher(dom_string *type, dom_default_action_phase phase, void **pw); + +#endif diff --git a/content/handlers/html/font.c b/content/handlers/html/font.c index 7ebe16825..4a64759d7 100644 --- a/content/handlers/html/font.c +++ b/content/handlers/html/font.c @@ -133,7 +133,7 @@ static plot_font_flags_t plot_font_flags(enum css_font_style_e style, /* exported function documented in html/font.h */ void font_plot_style_from_css( - const nscss_len_ctx *len_ctx, + const css_unit_ctx *unit_len_ctx, const css_computed_style *css, plot_font_style_t *fstyle) { @@ -147,7 +147,8 @@ void font_plot_style_from_css( fstyle->families = families; css_computed_font_size(css, &length, &unit); - fstyle->size = FIXTOINT(FMUL(nscss_len2pt(len_ctx, length, unit), + fstyle->size = FIXTOINT(FMUL(css_unit_font_size_len2pt(css, + unit_len_ctx, length, unit), INTTOFIX(PLOT_STYLE_SCALE))); /* Clamp font size to configured minimum */ diff --git a/content/handlers/html/font.h b/content/handlers/html/font.h index 5f69ee7d3..26f5bf289 100644 --- a/content/handlers/html/font.h +++ b/content/handlers/html/font.h @@ -32,11 +32,11 @@ struct plot_font_style; /** * Populate a font style using data from a computed CSS style * - * \param len_ctx Length conversion context + * \param unit_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, +void font_plot_style_from_css(const css_unit_ctx *unit_len_ctx, const css_computed_style *css, struct plot_font_style *fstyle); diff --git a/content/handlers/html/form.c b/content/handlers/html/form.c index 3d787e762..9b6768a56 100644 --- a/content/handlers/html/form.c +++ b/content/handlers/html/form.c @@ -41,6 +41,7 @@ #include "utils/utf8.h" #include "utils/ascii.h" #include "netsurf/browser_window.h" +#include "netsurf/inttypes.h" #include "netsurf/mouse.h" #include "netsurf/plotters.h" #include "netsurf/misc.h" @@ -50,14 +51,14 @@ #include "desktop/knockout.h" #include "desktop/scrollbar.h" #include "desktop/textarea.h" -#include "desktop/gui_internal.h" +#include "html/html.h" +#include "html/private.h" +#include "html/layout.h" #include "html/box.h" +#include "html/box_inspect.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 @@ -88,247 +89,82 @@ static plot_font_style_t plot_fstyle_entry = { .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. + * Convert a string from UTF-8 to the specified charset + * As a final fallback, this will attempt to convert to ISO-8859-1. * - * \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. + * \todo Return charset used? * - * \param control structure to free + * \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 */ -void form_free_control(struct form_control *control) +static char * +form_encode_item(const char *item, + uint32_t len, + const char *charset, + const char *fallback) { - 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; + nserror err; + char *ret = NULL; + char cset[256]; - 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 (!item || !charset) + return NULL; - if (control->type == GADGET_TEXTAREA || - control->type == GADGET_TEXTBOX || - control->type == GADGET_PASSWORD) { + snprintf(cset, sizeof cset, "%s//TRANSLIT", charset); - if (control->data.text.initial != NULL) { - dom_string_unref(control->data.text.initial); - } + 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 (control->data.text.ta != NULL) { - textarea_destroy(control->data.text.ta); - } - } + 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); - /* 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 (err == NSERROR_BAD_ENCODING) { + /* and without transliteration */ + snprintf(cset, sizeof cset, + "%s", fallback); + err = utf8_to_enc(item, cset, 0, &ret); + } } - 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; + + 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; + } - free(control); + return ret; } /** - * 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 + * string allocation size for numeric values in multipart data */ -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; -} - -/** string allocation size for numeric values in multipart data */ #define FETCH_DATA_INT_VALUE_SIZE 20 + /** * append split key name and integer value to a multipart data list * @@ -456,6 +292,7 @@ fetch_data_list_add(dom_string *name, return NSERROR_OK; } + /** * process form HTMLTextAreaElement into multipart data. * @@ -529,6 +366,7 @@ form_dom_to_data_textarea(dom_html_text_area_element *text_area_element, return res; } + static nserror form_dom_to_data_select_option(dom_html_option_element *option_element, dom_string *keyname, @@ -571,6 +409,7 @@ form_dom_to_data_select_option(dom_html_option_element *option_element, return res; } + /** * process form HTMLSelectElement into multipart data. * @@ -648,7 +487,7 @@ form_dom_to_data_select(dom_html_select_element *select_element, &option_element); if (exp != DOM_NO_ERR) { NSLOG(netsurf, INFO, - "Could not get options item %d", option_index); + "Could not get options item %"PRId32, option_index); res = NSERROR_DOM; } else { res = form_dom_to_data_select_option( @@ -672,6 +511,7 @@ form_dom_to_data_select(dom_html_select_element *select_element, return res; } + static nserror form_dom_to_data_input_submit(dom_html_input_element *input_element, dom_string *inputname, @@ -712,7 +552,6 @@ form_dom_to_data_input_submit(dom_html_input_element *input_element, } - static nserror form_dom_to_data_input_image(dom_html_input_element *input_element, dom_string *inputname, @@ -769,6 +608,7 @@ form_dom_to_data_input_image(dom_html_input_element *input_element, return res; } + static nserror form_dom_to_data_input_checkbox(dom_html_input_element *input_element, dom_string *inputname, @@ -818,6 +658,7 @@ form_dom_to_data_input_checkbox(dom_html_input_element *input_element, return res; } + static nserror form_dom_to_data_input_file(dom_html_input_element *input_element, dom_string *inputname, @@ -861,6 +702,7 @@ form_dom_to_data_input_file(dom_html_input_element *input_element, return res; } + static nserror form_dom_to_data_input_text(dom_html_input_element *input_element, dom_string *inputname, @@ -891,6 +733,7 @@ form_dom_to_data_input_text(dom_html_input_element *input_element, return res; } + /** * process form input element into multipart data. * @@ -1015,6 +858,7 @@ form_dom_to_data_input(dom_html_input_element *input_element, return res; } + /** * process form HTMLButtonElement into multipart data. * @@ -1124,6 +968,68 @@ form_dom_to_data_button(dom_html_button_element *button_element, /** + * 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 + */ +static char *form_acceptable_charset(struct form *form) +{ + char *temp, *c; + + 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); +} + + +/** * Construct multipart data list from 'successful' controls via the DOM. * * All text strings in the successful controls list will be in the charset most @@ -1195,7 +1101,7 @@ form_dom_to_data(struct form *form, exp = dom_html_collection_item(elements, element_idx, &element); if (exp != DOM_NO_ERR) { NSLOG(netsurf, INFO, - "retrieving form element %d failed with %d", + "retrieving form element %"PRIu32" failed with %d", element_idx, exp); res = NSERROR_DOM; goto form_dom_to_data_error; @@ -1205,7 +1111,7 @@ form_dom_to_data(struct form *form, exp = dom_node_get_node_name(element, &nodename); if (exp != DOM_NO_ERR) { NSLOG(netsurf, INFO, - "getting element node name %d failed with %d", + "getting element node name %"PRIu32" failed with %d", element_idx, exp); dom_node_unref(element); res = NSERROR_DOM; @@ -1349,140 +1255,321 @@ form_url_encode(struct form *form, return NSERROR_OK; } + /** - * 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 + * Callback for the select menus scroll */ -char *form_acceptable_charset(struct form *form) +static void +form_select_menu_scroll_callback(void *client_data, + struct scrollbar_msg_data *scrollbar_data) { - char *temp, *c; + struct form_control *control = client_data; + struct form_select_menu *menu = control->data.select.menu; + html_content *html = (html_content *)menu->c; - 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"); + 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; } +} - /* 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); - } +/** + * 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; - /* is UTF-8 specified? */ - c = strstr(temp, "UTF-8"); - if (c) { - free(temp); - return strdup("UTF-8"); + 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; + } } - /* dispense with temporary copy */ - free(temp); + talloc_free(inline_box->text); + inline_box->text = 0; - /* according to RFC2070, the accept-charsets attribute of the - * form element contains a space and/or comma separated list */ - c = form->accept_charsets; + 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")); + } - /** \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++; + 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; - return strndup(form->accept_charsets, c - form->accept_charsets); + html__redraw_a_box(html, control->box); + + return ret; } + /** - * 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? + * Handle a click on the area of the currently opened select menu. * - * \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 + * \param control the select menu which received the click + * \param x X coordinate of click + * \param y Y coordinate of click */ -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) { - nserror err; - char *ret = NULL; - char cset[256]; + 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; - if (!item || !charset) - return NULL; + scroll = scrollbar_get_offset(menu->scrollbar); - snprintf(cset, sizeof cset, "%s//TRANSLIT", charset); + line_height = menu->line_height; + line_height_with_spacing = line_height + + line_height * SELECT_LINE_SPACING; - 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); + 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 (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 (option != NULL) { + form__select_process_selection(html, control, i); + } - if (err == NSERROR_BAD_ENCODING) { - /* and without transliteration */ - snprintf(cset, sizeof cset, - "%s", fallback); - err = utf8_to_enc(item, cset, 0, &ret); - } - } + menu->callback(menu->client_data, 0, 0, menu->width, menu->height); +} - 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); - } + +/* exported interface documented in html/form_internal.h */ +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; + } +} + + +/* exported interface documented in html/form_internal.h */ +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->last_synced_value != NULL) { + free(control->last_synced_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; } } } - if (err == NSERROR_NOMEM) { - return NULL; + + if (control->node_value != NULL) { + dom_string_unref(control->node_value); } - return ret; + free(control); +} + + +/* exported interface documented in html/form_internal.h */ +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_open_select_menu(void *client_data, - struct form_control *control, - select_menu_redraw_callback callback, - struct content *c) +nserror +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; @@ -1490,15 +1577,14 @@ bool form_open_select_menu(void *client_data, int total_height; struct form_select_menu *menu; html_content *html = (html_content *)c; - + nserror res; /* 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; + return NSERROR_NOMEM; } control->data.select.menu = menu; @@ -1506,16 +1592,15 @@ bool form_open_select_menu(void *client_data, box = control->box; menu->width = box->width + - box->border[RIGHT].width + - box->border[LEFT].width + - box->padding[RIGHT] + box->padding[LEFT]; + box->border[RIGHT].width + box->padding[RIGHT] + + box->border[LEFT].width + box->padding[LEFT]; - font_plot_style_from_css(&html->len_ctx, control->box->style, - &fstyle); + font_plot_style_from_css(&html->unit_len_ctx, + control->box->style, &fstyle); menu->f_size = fstyle.size; menu->line_height = FIXTOINT(FDIV((FMUL(FLTTOFIX(1.2), - FMUL(nscss_screen_dpi, + FMUL(html->unit_len_ctx.device_dpi, INTTOFIX(fstyle.size / PLOT_STYLE_SCALE)))), F_72)); @@ -1528,28 +1613,31 @@ bool form_open_select_menu(void *client_data, 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) { + res = scrollbar_create(false, + menu->height, + total_height, + menu->height, + control, + form_select_menu_scroll_callback, + &(menu->scrollbar)); + if (res != NSERROR_OK) { + control->data.select.menu = NULL; free(menu); - return false; + return res; } menu->c = c; + } else { + menu = control->data.select.menu; } - else menu = control->data.select.menu; menu->callback(client_data, 0, 0, menu->width, menu->height); - return true; + return NSERROR_OK; } @@ -1564,9 +1652,12 @@ void form_free_select_menu(struct form_control *control) /* 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) +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; @@ -1710,17 +1801,12 @@ bool form_redraw_select_menu(struct form_control *control, int x, int y, 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) + +/* private interface described in html/form_internal.h */ +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; @@ -1734,99 +1820,16 @@ bool form_clip_inside_select_menu(struct form_control *control, float scale, height *= scale; } - if (clip->x0 >= 0 && clip->x1 <= width && - clip->y0 >= 0 && clip->y1 <= height) + 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) { @@ -1835,6 +1838,7 @@ nserror form_select_process_selection(struct form_control *control, int item) 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) @@ -1849,12 +1853,14 @@ form_select_get_option(struct form_control *control, int 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) { @@ -1863,56 +1869,11 @@ nserror form_control_bounding_rect(struct form_control *control, struct rect *r) } -/** - * 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) +/* private interface described in html/form_internal.h */ +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; @@ -1957,16 +1918,12 @@ const char *form_select_mouse_action(struct form_control *control, 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) + +/* private interface described in html/form_internal.h */ +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; @@ -1997,61 +1954,14 @@ void form_select_mouse_drag_end(struct form_control *control, y1 = menu->height; - if (x > x0 && x < x1 - SCROLLBAR_WIDTH && y > y0 && y < y1) + 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 - */ + +/* private interface described in html/form_internal.h */ void form_select_get_dimensions(struct form_control *control, int *width, int *height) { @@ -2059,9 +1969,8 @@ void form_select_get_dimensions(struct form_control *control, *height = control->data.select.menu->height; } -/** - * Callback for the core select menu. - */ + +/* private interface described in html/form_internal.h */ void form_select_menu_callback(void *client_data, int x, int y, int width, int height) { @@ -2081,12 +1990,7 @@ void form_select_menu_callback(void *client_data, } -/** - * Set a radio form control and clear the others in the group. - * - * \param radio form control of type GADGET_RADIO - */ - +/* private interface described in html/form_internal.h */ void form_radio_set(struct form_control *radio) { struct form_control *control; @@ -2098,15 +2002,33 @@ void form_radio_set(struct form_control *radio) if (radio->selected) return; - for (control = radio->form->controls; control; - control = control->next) { + /* Clear selected state for other controls in + * the same radio button group */ + for (control = radio->form->controls; + control != NULL; + control = control->next) { + /* Only interested in radio inputs */ if (control->type != GADGET_RADIO) continue; + + /* Ignore ourself */ if (control == radio) continue; - if (strcmp(control->name, radio->name) != 0) + + /* Ignore inputs where: + * a) this or the other control have no name attribute + * b) this or the other control have an empty name attribute + * c) the control names do not match + */ + if ((control->name == NULL) || + (radio->name == NULL) || + (control->name[0] == '\0') || + (radio->name[0] == '\0') || + strcmp(control->name, radio->name) != 0) continue; + /* Other control is in the same radio button group: clear its + * selected state */ if (control->selected) { control->selected = false; dom_html_input_element_set_checked(control->node, false); @@ -2201,6 +2123,8 @@ form_submit(nsurl *page_url, return res; } + +/* exported interface documented in html/form_internal.h */ void form_gadget_update_value(struct form_control *control, char *value) { switch (control->type) { @@ -2235,4 +2159,203 @@ void form_gadget_update_value(struct form_control *control, char *value) /* Do nothing */ break; } + + /* Finally, sync this with the DOM */ + form_gadget_sync_with_dom(control); +} + + +/* Exported API, see html/form_internal.h */ +void +form_gadget_sync_with_dom(struct form_control *control) +{ + dom_exception exc; + dom_string *value = NULL; + bool changed_dom = false; + + if (control->syncing || + (control->type != GADGET_TEXTBOX && + control->type != GADGET_PASSWORD && + control->type != GADGET_HIDDEN && + control->type != GADGET_TEXTAREA)) { + /* Not a control we support, or the control is already + * mid-sync so we don't want to disrupt that + */ + return; + } + + control->syncing = true; + + /* If we've changed value, sync that toward the DOM */ + if ((control->last_synced_value == NULL && + control->value != NULL && + control->value[0] != '\0') || + (control->last_synced_value != NULL && + control->value != NULL && + strcmp(control->value, control->last_synced_value) != 0)) { + char *dup = strdup(control->value); + if (dup == NULL) { + goto out; + } + if (control->last_synced_value != NULL) { + free(control->last_synced_value); + } + control->last_synced_value = dup; + exc = dom_string_create((uint8_t *)(control->value), + strlen(control->value), &value); + if (exc != DOM_NO_ERR) { + goto out; + } + if (control->node_value != NULL) { + dom_string_unref(control->node_value); + } + control->node_value = value; + value = NULL; + if (control->type == GADGET_TEXTAREA) { + exc = dom_html_text_area_element_set_value(control->node, control->node_value); + } else { + exc = dom_html_input_element_set_value(control->node, control->node_value); + } + if (exc != DOM_NO_ERR) { + goto out; + } + changed_dom = true; + } + + /* Now check if the DOM has changed since our last go */ + if (control->type == GADGET_TEXTAREA) { + exc = dom_html_text_area_element_get_value(control->node, &value); + } else { + exc = dom_html_input_element_get_value(control->node, &value); + } + + if (exc != DOM_NO_ERR) { + /* Nothing much we can do here */ + goto out; + } + + if (!dom_string_isequal(control->node_value, value)) { + /* The DOM has changed */ + if (!changed_dom) { + /* And it wasn't us */ + char *value_s = strndup( + dom_string_data(value), + dom_string_byte_length(value)); + char *dup = NULL; + if (value_s == NULL) { + goto out; + } + dup = strdup(value_s); + if (dup == NULL) { + free(value_s); + goto out; + } + free(control->value); + control->value = value_s; + free(control->last_synced_value); + control->last_synced_value = dup; + if (control->type != GADGET_HIDDEN && + control->data.text.ta != NULL) { + textarea_set_text(control->data.text.ta, + value_s); + } + } + control->node_value = value; + value = NULL; + } + +out: + if (value != NULL) + dom_string_unref(value); + control->syncing = false; +} + + +/* 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; } diff --git a/content/handlers/html/form_internal.h b/content/handlers/html/form_internal.h index f76f126b4..292a5df44 100644 --- a/content/handlers/html/form_internal.h +++ b/content/handlers/html/form_internal.h @@ -72,6 +72,8 @@ struct image_input_coords { /** Form control. */ struct form_control { void *node; /**< Corresponding DOM node */ + struct dom_string *node_value; /**< The last value sync'd with the DOM */ + bool syncing; /**< Set if a DOM sync is in-progress */ struct html_content *html; /**< HTML content containing control */ form_control_type type; /**< Type of control */ @@ -81,6 +83,7 @@ struct form_control { char *name; /**< Control name */ char *value; /**< Current value of control */ char *initial_value; /**< Initial value of control */ + char *last_synced_value; /**< The last value sync'd to the DOM */ bool disabled; /**< Whether control is disabled */ struct box *box; /**< Box for control */ @@ -177,6 +180,7 @@ struct form *form_new(void *node, const char *action, const char *target, */ void form_free(struct form *form); + /** * Create a struct form_control. * @@ -186,33 +190,53 @@ void form_free(struct form *form); */ struct form_control *form_new_control(void *node, form_control_type type); + +/** + * 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); + + +/** + * Free a struct form_control. + * + * \param control structure to free + */ void form_free_control(struct form_control *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); -bool form_successful_controls(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 + * \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 NSERROR_OK on sucess else error code. */ -bool form_open_select_menu(void *client_data, +nserror 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. * @@ -237,15 +261,70 @@ bool form_redraw_select_menu(struct form_control *control, int x, int y, float scale, const struct rect *clip, const struct redraw_context *ctx); + +/** + * 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); + + +/** + * 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, enum browser_mouse_state mouse, int x, int y); + + +/** + * 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, enum browser_mouse_state mouse, int x, int y); + + +/** + * 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); + +/** + * Callback for the core select menu. + */ +void form_select_menu_callback(void *client_data, + int x, int y, int width, int 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); + /** * navigate browser window based on form submission. * @@ -257,8 +336,25 @@ void form_select_get_dimensions(struct form_control *control, nserror 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); +/** + * Update gadget value. + */ void form_gadget_update_value(struct form_control *control, char *value); + +/** + * Synchronise this gadget with its associated DOM node. + * + * If the DOM has changed and the gadget has not, the DOM's new value is + * imported into the gadget. If the gadget's value has changed and the DOM's + * has not, the gadget's value is pushed into the DOM. + * If both have changed, the gadget's value wins. + * + * \param control The form gadget to synchronise + * + * \note Currently this will only synchronise input gadgets (text/password) + */ +void form_gadget_sync_with_dom(struct form_control *control); + #endif diff --git a/content/handlers/html/html_forms.c b/content/handlers/html/forms.c index 915eb002f..4669154e9 100644 --- a/content/handlers/html/html_forms.c +++ b/content/handlers/html/forms.c @@ -21,12 +21,14 @@ * HTML form handling implementation */ +#include <string.h> + #include "utils/config.h" #include "utils/corestrings.h" #include "utils/log.h" #include "html/form_internal.h" -#include "html/html_internal.h" +#include "html/private.h" /** * process form element from dom @@ -391,7 +393,18 @@ parse_input_element(struct form *forms, dom_html_input_element *input) control = NULL; goto out; } + + control->last_synced_value = strdup(control->value); + if (control->last_synced_value == NULL) { + form_free_control(control); + control = NULL; + goto out; + } + + control->node_value = dom_string_ref(ds_value); } + /* Force the gadget and DOM to be in sync */ + form_gadget_sync_with_dom(control); } if (form != NULL && control != NULL) @@ -528,8 +541,8 @@ invent_fake_gadget(dom_node *node) } /* documented in html_internal.h */ -struct form_control *html_forms_get_control_for_node(struct form *forms, - dom_node *node) +struct form_control * +html_forms_get_control_for_node(struct form *forms, dom_node *node) { struct form *f; struct form_control *ctl = NULL; diff --git a/content/handlers/html/html.c b/content/handlers/html/html.c index ba80ad12e..82f5f1388 100644 --- a/content/handlers/html/html.c +++ b/content/handlers/html/html.c @@ -44,9 +44,12 @@ #include "netsurf/content.h" #include "netsurf/browser_window.h" #include "netsurf/utf8.h" +#include "netsurf/keypress.h" #include "netsurf/layout.h" #include "netsurf/misc.h" #include "content/hlcache.h" +#include "content/content_factory.h" +#include "content/textsearch.h" #include "desktop/selection.h" #include "desktop/scrollbar.h" #include "desktop/textarea.h" @@ -55,13 +58,19 @@ #include "desktop/gui_internal.h" #include "html/html.h" +#include "html/private.h" +#include "html/dom_event.h" +#include "html/css.h" +#include "html/object.h" #include "html/html_save.h" -#include "html/html_internal.h" +#include "html/interaction.h" #include "html/box.h" +#include "html/box_construct.h" +#include "html/box_inspect.h" #include "html/form_internal.h" #include "html/imagemap.h" #include "html/layout.h" -#include "html/search.h" +#include "html/textselection.h" #define CHUNK 4096 @@ -76,9 +85,31 @@ static const char *html_types[] = { "text/html" }; +/** + * Fire an event at the DOM + * + * Helper that swallows DOM errors. + * + * \param[in] event the event to fire at the DOM + * \param[in] target the event target + * \return true on success + */ +static bool fire_dom_event(dom_event *event, dom_node *target) +{ + dom_exception exc; + bool result; + + exc = dom_event_target_dispatch_event(target, event, &result); + if (exc != DOM_NO_ERR) { + return false; + } + + return result; +} + /* Exported interface, see html_internal.h */ -bool fire_dom_event(dom_string *type, dom_node *target, - bool bubbles, bool cancelable) +bool fire_generic_dom_event(dom_string *type, dom_node *target, + bool bubbles, bool cancelable) { dom_exception exc; dom_event *evt; @@ -92,11 +123,86 @@ bool fire_dom_event(dom_string *type, dom_node *target, 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); + (int)dom_string_length(type), dom_string_data(type), target); + result = fire_dom_event(evt, target); + dom_event_unref(evt); + return result; +} + +/* Exported interface, see html_internal.h */ +bool fire_dom_keyboard_event(dom_string *type, dom_node *target, + bool bubbles, bool cancelable, uint32_t key) +{ + bool is_special = key <= 0x001F || (0x007F <= key && key <= 0x009F); + dom_string *dom_key = NULL; + dom_keyboard_event *evt; + dom_exception exc; + bool result; + + if (is_special) { + switch (key) { + case NS_KEY_ESCAPE: + dom_key = dom_string_ref(corestring_dom_Escape); + break; + case NS_KEY_LEFT: + dom_key = dom_string_ref(corestring_dom_ArrowLeft); + break; + case NS_KEY_RIGHT: + dom_key = dom_string_ref(corestring_dom_ArrowRight); + break; + case NS_KEY_UP: + dom_key = dom_string_ref(corestring_dom_ArrowUp); + break; + case NS_KEY_DOWN: + dom_key = dom_string_ref(corestring_dom_ArrowDown); + break; + case NS_KEY_PAGE_UP: + dom_key = dom_string_ref(corestring_dom_PageUp); + break; + case NS_KEY_PAGE_DOWN: + dom_key = dom_string_ref(corestring_dom_PageDown); + break; + case NS_KEY_TEXT_START: + dom_key = dom_string_ref(corestring_dom_Home); + break; + case NS_KEY_TEXT_END: + dom_key = dom_string_ref(corestring_dom_End); + break; + default: + dom_key = NULL; + break; + } + } else { + char utf8[6]; + size_t length = utf8_from_ucs4(key, utf8); + utf8[length] = '\0'; + + exc = dom_string_create((const uint8_t *)utf8, strlen(utf8), + &dom_key); + if (exc != DOM_NO_ERR) { + return exc; + } + } + + exc = dom_keyboard_event_create(&evt); if (exc != DOM_NO_ERR) { - result = false; + dom_string_unref(dom_key); + return false; } + + exc = dom_keyboard_event_init(evt, type, bubbles, cancelable, NULL, + dom_key, NULL, DOM_KEY_LOCATION_STANDARD, false, + false, false, false, false, false); + dom_string_unref(dom_key); + if (exc != DOM_NO_ERR) { + dom_event_unref(evt); + return false; + } + + NSLOG(netsurf, INFO, "Dispatching '%*s' against %p", + (int)dom_string_length(type), dom_string_data(type), target); + + result = fire_dom_event((dom_event *) evt, target); dom_event_unref(evt); return result; } @@ -113,16 +219,18 @@ static void html_box_convert_done(html_content *c, bool success) dom_exception exc; /* returned by libdom functions */ dom_node *html; - NSLOG(netsurf, INFO, "Done XML to box (%p)", c); + NSLOG(netsurf, INFO, "DOM to box conversion complete (content %p)", c); + + c->box_conversion_context = NULL; /* 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); + content_broadcast_error(&c->base, NSERROR_BOX_CONVERT, NULL); } else { - content_broadcast_errorcode(&c->base, NSERROR_STOPPED); + content_broadcast_error(&c->base, NSERROR_STOPPED, NULL); } content_set_error(&c->base); @@ -144,7 +252,7 @@ static void html_box_convert_done(html_content *c, bool success) * like the other error paths */ NSLOG(netsurf, INFO, "error retrieving html element from dom"); - content_broadcast_errorcode(&c->base, NSERROR_DOM); + content_broadcast_error(&c->base, NSERROR_DOM, NULL); content_set_error(&c->base); return; } @@ -154,7 +262,7 @@ static void html_box_convert_done(html_content *c, bool success) if (err != NSERROR_OK) { NSLOG(netsurf, INFO, "imagemap extraction failed"); html_object_free_objects(c); - content_broadcast_errorcode(&c->base, err); + content_broadcast_error(&c->base, err, NULL); content_set_error(&c->base); dom_node_unref(html); return; @@ -167,411 +275,39 @@ static void html_box_convert_done(html_content *c, bool success) content_set_ready(&c->base); - if (c->base.active == 0) { - content_set_done(&c->base); - } + html_proceed_to_done(c); 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) +/* Documented in html_internal.h */ +nserror +html_proceed_to_done(html_content *html) { - 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); + switch (content__get_status(&html->base)) { + case CONTENT_STATUS_READY: + if (html->base.active == 0) { + content_set_done(&html->base); return NSERROR_OK; } - } else { - /* Insufficient input, ignore this header */ - dom_string_unref(content); + break; + case CONTENT_STATUS_DONE: + /* fallthrough */ + case CONTENT_STATUS_LOADING: return NSERROR_OK; + default: + NSLOG(netsurf, ERROR, "Content status unexpectedly not LOADING/READY/DONE"); + break; } - - /* *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; + return NSERROR_UNKNOWN; } -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; -} static void html_get_dimensions(html_content *htmlc) { + css_fixed device_dpi = nscss_screen_dpi; + unsigned f_size; + unsigned f_min; unsigned w; unsigned h; union content_msg_data msg_data = { @@ -583,13 +319,22 @@ static void html_get_dimensions(html_content *htmlc) content_broadcast(&htmlc->base, CONTENT_MSG_GETDIMS, &msg_data); - htmlc->media.width = nscss_pixels_physical_to_css(INTTOFIX(w)); - htmlc->media.height = nscss_pixels_physical_to_css(INTTOFIX(h)); - htmlc->media.client_font_size = - FDIV(INTTOFIX(nsoption_int(font_size)), F_10); - htmlc->media.client_line_height = - FMUL(nscss_len2px(NULL, htmlc->media.client_font_size, - CSS_UNIT_PT, NULL), FLTTOFIX(1.33)); + + w = css_unit_device2css_px(INTTOFIX(w), device_dpi); + h = css_unit_device2css_px(INTTOFIX(h), device_dpi); + + htmlc->media.width = w; + htmlc->media.height = h; + htmlc->unit_len_ctx.viewport_width = w; + htmlc->unit_len_ctx.viewport_height = h; + htmlc->unit_len_ctx.device_dpi = device_dpi; + + /** \todo Change nsoption font sizes to px. */ + f_size = FDIV(FMUL(F_96, FDIV(INTTOFIX(nsoption_int(font_size)), F_10)), F_72); + f_min = FDIV(FMUL(F_96, FDIV(INTTOFIX(nsoption_int(font_min_size)), F_10)), F_72); + + htmlc->unit_len_ctx.font_size_default = f_size; + htmlc->unit_len_ctx.font_size_minimum = f_min; } /* exported function documented in html/html_internal.h */ @@ -602,7 +347,7 @@ void html_finish_conversion(html_content *htmlc) /* Bail out if we've been aborted */ if (htmlc->aborted) { - content_broadcast_errorcode(&htmlc->base, NSERROR_STOPPED); + content_broadcast_error(&htmlc->base, NSERROR_STOPPED, NULL); content_set_error(&htmlc->base); return; } @@ -626,7 +371,7 @@ void html_finish_conversion(html_content *htmlc) /* 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_broadcast_error(&htmlc->base, error, NULL); content_set_error(&htmlc->base); return; } @@ -636,8 +381,8 @@ void html_finish_conversion(html_content *htmlc) * 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); + if (htmlc->jsthread != NULL) { + js_fire_event(htmlc->jsthread, "load", htmlc->document, NULL); } /* convert dom tree to box tree */ @@ -649,19 +394,19 @@ void html_finish_conversion(html_content *htmlc) 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_broadcast_error(&htmlc->base, NSERROR_DOM, NULL); content_set_error(&htmlc->base); return; } html_get_dimensions(htmlc); - error = dom_to_box(html, htmlc, html_box_convert_done); + error = dom_to_box(html, htmlc, html_box_convert_done, &htmlc->box_conversion_context); 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_broadcast_error(&htmlc->base, error, NULL); content_set_error(&htmlc->base); return; } @@ -669,248 +414,6 @@ void html_finish_conversion(html_content *htmlc) dom_node_unref(html); } -/* handler for a SCRIPT which has been added to a tree */ -static void -dom_SCRIPT_showed_up(html_content *htmlc, dom_html_script_element *script) -{ - dom_exception exc; - dom_html_script_element_flags flags; - dom_hubbub_error res; - bool within; - - if (!htmlc->enable_scripting) { - NSLOG(netsurf, INFO, "Encountered a script, but scripting is off, ignoring"); - return; - } - - NSLOG(netsurf, DEEPDEBUG, "Encountered a script, node %p showed up", script); - - exc = dom_html_script_element_get_flags(script, &flags); - if (exc != DOM_NO_ERR) { - NSLOG(netsurf, DEEPDEBUG, "Unable to retrieve flags, giving up"); - return; - } - - if (flags & DOM_HTML_SCRIPT_ELEMENT_FLAG_PARSER_INSERTED) { - NSLOG(netsurf, DEBUG, "Script was parser inserted, skipping"); - return; - } - - exc = dom_node_contains(htmlc->document, script, &within); - if (exc != DOM_NO_ERR) { - NSLOG(netsurf, DEBUG, "Unable to determine if script was within document, ignoring"); - return; - } - - if (!within) { - NSLOG(netsurf, DEBUG, "Script was not within the document, ignoring for now"); - return; - } - - res = html_process_script(htmlc, (dom_node *) script); - if (res == DOM_HUBBUB_OK) { - NSLOG(netsurf, DEEPDEBUG, "Inserted script has finished running"); - } else { - if (res == (DOM_HUBBUB_HUBBUB_ERR | HUBBUB_PAUSED)) { - NSLOG(netsurf, DEEPDEBUG, "Inserted script has launced asynchronously"); - } else { - NSLOG(netsurf, DEEPDEBUG, "Failure starting script"); - } - } -} - -/* 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; - case DOM_HTML_ELEMENT_TYPE_SCRIPT: - dom_SCRIPT_showed_up(htmlc, (dom_html_script_element *) 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 DOMNodeInsertedIntoDocument end type */ -static void -dom_default_action_DOMNodeInsertedIntoDocument_cb(struct dom_event *evt, void *pw) -{ - html_content *htmlc = pw; - dom_event_target *node; - dom_node_type type; - dom_exception exc; - - 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 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_SCRIPT: - dom_SCRIPT_showed_up(htmlc, (dom_html_script_element *) node); - default: - break; - } - } - dom_node_unref(node); - } -} - -/* callback for DOMSubtreeModified 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, "phase:%d type:%s", phase, 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_DOMNodeInsertedIntoDocument)) { - return dom_default_action_DOMNodeInsertedIntoDocument_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, @@ -955,6 +458,8 @@ html_create_html_data(html_content *c, const http_parameter *params) dom_hubbub_error error; dom_exception err; void *old_node_data; + const char *prefer_color_mode = (nsoption_bool(prefer_dark_mode)) ? + "dark" : "light"; c->parser = NULL; c->parse_completed = false; @@ -991,11 +496,9 @@ html_create_html_data(html_content *c, const http_parameter *params) 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->jsthread = NULL; c->enable_scripting = nsoption_bool(enable_javascript); c->base.active = 1; /* The html content itself is active */ @@ -1004,7 +507,14 @@ html_create_html_data(html_content *c, const http_parameter *params) return NSERROR_NOMEM; } - selection_prepare(&c->sel, (struct content *)c, true); + if (lwc_intern_string(prefer_color_mode, strlen(prefer_color_mode), + &c->media.prefers_color_scheme) != lwc_error_ok) { + lwc_string_unref(c->universal); + c->universal = NULL; + return NSERROR_NOMEM; + } + + c->sel = selection_create((struct content *)c); nerror = http_parameter_list_find_item(params, corestring_lwc_charset, &charset); if (nerror == NSERROR_OK) { @@ -1015,6 +525,8 @@ html_create_html_data(html_content *c, const http_parameter *params) if (c->encoding == NULL) { lwc_string_unref(c->universal); c->universal = NULL; + lwc_string_unref(c->media.prefers_color_scheme); + c->media.prefers_color_scheme = NULL; return NSERROR_NOMEM; } @@ -1028,7 +540,7 @@ html_create_html_data(html_content *c, const http_parameter *params) parse_params.msg = NULL; parse_params.script = html_process_script; parse_params.ctx = c; - parse_params.daf = dom_event_fetcher; + parse_params.daf = html_dom_event_fetcher; error = dom_hubbub_parser_create(&parse_params, &c->parser, @@ -1051,6 +563,8 @@ html_create_html_data(html_content *c, const http_parameter *params) lwc_string_unref(c->universal); c->universal = NULL; + lwc_string_unref(c->media.prefers_color_scheme); + c->media.prefers_color_scheme = NULL; return libdom_hubbub_error_to_nserror(error); } @@ -1067,6 +581,8 @@ html_create_html_data(html_content *c, const http_parameter *params) lwc_string_unref(c->universal); c->universal = NULL; + lwc_string_unref(c->media.prefers_color_scheme); + c->media.prefers_color_scheme = NULL; NSLOG(netsurf, INFO, "Unable to set user data."); return NSERROR_DOM; @@ -1110,14 +626,14 @@ html_create(const content_handler *handler, error = html_create_html_data(html, params); if (error != NSERROR_OK) { - content_broadcast_errorcode(&html->base, error); + content_broadcast_error(&html->base, error, NULL); free(html); return error; } error = html_css_new_stylesheets(html); if (error != NSERROR_OK) { - content_broadcast_errorcode(&html->base, error); + content_broadcast_error(&html->base, error, NULL); free(html); return error; } @@ -1172,7 +688,7 @@ html_process_encoding_change(struct content *c, parse_params.msg = NULL; parse_params.script = html_process_script; parse_params.ctx = html; - parse_params.daf = dom_event_fetcher; + parse_params.daf = html_dom_event_fetcher; /* Create new binding, using the new encoding */ error = dom_hubbub_parser_create(&parse_params, @@ -1236,7 +752,7 @@ html_process_data(struct content *c, const char *data, unsigned int size) /* broadcast the error if necessary */ if (err != NSERROR_OK) { - content_broadcast_errorcode(c, err); + content_broadcast_error(c, err, NULL); return false; } @@ -1338,11 +854,19 @@ html_begin_conversion(html_content *htmlc) NSLOG(netsurf, INFO, "Completing parse (%p)", htmlc); /* complete parsing */ error = dom_hubbub_parser_completed(htmlc->parser); + if (error == DOM_HUBBUB_HUBBUB_ERR_PAUSED && htmlc->base.active > 0) { + /* The act of completing the parse failed because we've + * encountered a sync script which needs to run + */ + NSLOG(netsurf, INFO, "Completing parse brought synchronous JS to light, cannot complete yet"); + return true; + } if (error != DOM_HUBBUB_OK) { NSLOG(netsurf, INFO, "Parsing failed"); - content_broadcast_errorcode(&htmlc->base, - libdom_hubbub_error_to_nserror(error)); + content_broadcast_error(&htmlc->base, + libdom_hubbub_error_to_nserror(error), + NULL); return false; } @@ -1360,7 +884,7 @@ html_begin_conversion(html_content *htmlc) NSLOG(netsurf, INFO, "Conversion aborted (%p) (active: %u)", htmlc, htmlc->base.active); content_set_error(&htmlc->base); - content_broadcast_errorcode(&htmlc->base, NSERROR_STOPPED); + content_broadcast_error(&htmlc->base, NSERROR_STOPPED, NULL); return false; } @@ -1381,15 +905,17 @@ html_begin_conversion(html_content *htmlc) encoding = dom_hubbub_parser_get_encoding(htmlc->parser, &htmlc->encoding_source); if (encoding == NULL) { - content_broadcast_errorcode(&htmlc->base, - NSERROR_NOMEM); + content_broadcast_error(&htmlc->base, + NSERROR_NOMEM, + NULL); return false; } htmlc->encoding = strdup(encoding); if (htmlc->encoding == NULL) { - content_broadcast_errorcode(&htmlc->base, - NSERROR_NOMEM); + content_broadcast_error(&htmlc->base, + NSERROR_NOMEM, + NULL); return false; } } @@ -1398,7 +924,7 @@ html_begin_conversion(html_content *htmlc) 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_broadcast_error(&htmlc->base, NSERROR_DOM, NULL); return false; } @@ -1408,7 +934,7 @@ html_begin_conversion(html_content *htmlc) (!dom_string_caseless_lwc_isequal(node_name, corestring_lwc_html))) { NSLOG(netsurf, INFO, "root element not html"); - content_broadcast_errorcode(&htmlc->base, NSERROR_DOM); + content_broadcast_error(&htmlc->base, NSERROR_DOM, NULL); dom_node_unref(html); return false; } @@ -1434,7 +960,7 @@ html_begin_conversion(html_content *htmlc) } if (ns_error != NSERROR_OK) { - content_broadcast_errorcode(&htmlc->base, ns_error); + content_broadcast_error(&htmlc->base, ns_error, NULL); dom_node_unref(html); return false; @@ -1444,8 +970,9 @@ html_begin_conversion(html_content *htmlc) f->action = strdup(nsurl_access(action)); nsurl_unref(action); if (f->action == NULL) { - content_broadcast_errorcode(&htmlc->base, - NSERROR_NOMEM); + content_broadcast_error(&htmlc->base, + NSERROR_NOMEM, + NULL); dom_node_unref(html); return false; @@ -1455,8 +982,9 @@ html_begin_conversion(html_content *htmlc) if (f->document_charset == NULL) { f->document_charset = strdup(htmlc->encoding); if (f->document_charset == NULL) { - content_broadcast_errorcode(&htmlc->base, - NSERROR_NOMEM); + content_broadcast_error(&htmlc->base, + NSERROR_NOMEM, + NULL); dom_node_unref(html); return false; } @@ -1484,17 +1012,15 @@ 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; + if (htmlc->jsthread != NULL) { + /* Close the JS thread to cancel out any callbacks */ + js_closethread(htmlc->jsthread); + } break; case CONTENT_STATUS_READY: @@ -1536,9 +1062,11 @@ static void html_reformat(struct content *c, int width, int height) htmlc->reflowing = true; - htmlc->len_ctx.vw = nscss_pixels_physical_to_css(width); - htmlc->len_ctx.vh = nscss_pixels_physical_to_css(height); - htmlc->len_ctx.root_style = htmlc->layout->style; + htmlc->unit_len_ctx.viewport_width = css_unit_device2css_px( + INTTOFIX(width), htmlc->unit_len_ctx.device_dpi); + htmlc->unit_len_ctx.viewport_height = css_unit_device2css_px( + INTTOFIX(height), htmlc->unit_len_ctx.device_dpi); + htmlc->unit_len_ctx.root_style = htmlc->layout->style; layout_document(htmlc, width, height); layout = htmlc->layout; @@ -1557,9 +1085,10 @@ static void html_reformat(struct content *c, int width, int height) if (c->height < layout->y + layout->descendant_y1) c->height = layout->y + layout->descendant_y1; - selection_reinit(&htmlc->sel, htmlc->layout); + selection_reinit(htmlc->sel); htmlc->reflowing = false; + htmlc->had_initial_layout = true; /* calculate next reflow time at three times what it took to reflow */ nsu_getmonotonic_ms(&ms_after); @@ -1677,6 +1206,15 @@ static void html_destroy(struct content *c) NSLOG(netsurf, INFO, "content %p", c); + /* If we're still converting a layout, cancel it */ + if (html->box_conversion_context != NULL) { + if (cancel_dom_to_box(html->box_conversion_context) != NSERROR_OK) { + NSLOG(netsurf, CRITICAL, "WARNING, Unable to cancel conversion context, browser may crash"); + } + } + + selection_destroy(html->sel); + /* Destroy forms */ for (f = html->forms; f != NULL; f = g) { g = f->prev; @@ -1692,6 +1230,14 @@ static void html_destroy(struct content *c) if (html->base_url) nsurl_unref(html->base_url); + /* At this point we can be moderately confident the JS is offline + * so we destroy the JS thread. + */ + if (html->jsthread != NULL) { + js_destroythread(html->jsthread); + html->jsthread = NULL; + } + if (html->parser != NULL) { dom_hubbub_parser_destroy(html->parser); html->parser = NULL; @@ -1743,6 +1289,11 @@ static void html_destroy(struct content *c) html->universal = NULL; } + if (html->media.prefers_color_scheme != NULL) { + lwc_string_unref(html->media.prefers_color_scheme); + html->media.prefers_color_scheme = NULL; + } + /* Free stylesheets */ html_css_free_stylesheets(html); @@ -1774,7 +1325,7 @@ static nserror html_clone(const struct content *old, struct content **newc) * Handle a window containing a CONTENT_HTML being opened. */ -static void +static nserror html_open(struct content *c, struct browser_window *bw, struct content *page, @@ -1789,11 +1340,13 @@ html_open(struct content *c, html->drag_owner.no_owner = true; /* text selection */ - selection_init(&html->sel, html->layout, &html->len_ctx); + selection_init(html->sel); html->selection_type = HTML_SELECTION_NONE; html->selection_owner.none = true; html_object_open_objects(html, bw); + + return NSERROR_OK; } @@ -1801,27 +1354,25 @@ html_open(struct content *c, * Handle a window containing a CONTENT_HTML being closed. */ -static void html_close(struct content *c) +static nserror html_close(struct content *c) { html_content *htmlc = (html_content *) c; + nserror ret = NSERROR_OK; - selection_clear(&htmlc->sel, false); - - if (htmlc->search != NULL) { - search_destroy_context(htmlc->search); - } + selection_clear(htmlc->sel, false); /* 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); + + if (htmlc->jsthread != NULL) { + /* Close, but do not destroy (yet) the JS thread */ + ret = js_closethread(htmlc->jsthread); + } + + return ret; } @@ -1844,7 +1395,7 @@ static void html_clear_selection(struct content *c) break; case HTML_SELECTION_SELF: assert(html->selection_owner.none == false); - selection_clear(&html->sel, true); + selection_clear(html->sel, true); break; case HTML_SELECTION_CONTENT: content_clear_selection(html->selection_owner.content->object); @@ -1873,7 +1424,7 @@ static char *html_get_selection(struct content *c) gadget->data.text.ta); case HTML_SELECTION_SELF: assert(html->selection_owner.none == false); - return selection_get_copy(&html->sel); + return selection_get_copy(html->sel); case HTML_SELECTION_CONTENT: return content_get_selection( html->selection_owner.content->object); @@ -1910,7 +1461,7 @@ html_get_contextual_content(struct content *c, int x, int y, struct box *next; int box_x = 0, box_y = 0; - while ((next = box_at_point(&html->len_ctx, box, x, y, + while ((next = box_at_point(&html->unit_len_ctx, box, x, y, &box_x, &box_y)) != NULL) { box = next; @@ -1921,8 +1472,11 @@ html_get_contextual_content(struct content *c, int x, int y, } if (box->iframe) { + float scale = browser_window_get_scale(box->iframe); browser_window_get_features(box->iframe, - x - box_x, y - box_y, data); + (x - box_x) * scale, + (y - box_y) * scale, + data); } if (box->object) @@ -1988,7 +1542,7 @@ html_scroll_at_point(struct content *c, int x, int y, int scrx, int scry) /* TODO: invert order; visit deepest box first */ - while ((next = box_at_point(&html->len_ctx, box, x, y, + while ((next = box_at_point(&html->unit_len_ctx, box, x, y, &box_x, &box_y)) != NULL) { box = next; @@ -1997,9 +1551,15 @@ html_scroll_at_point(struct content *c, int x, int y, int scrx, int scry) 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; + if (box->iframe) { + float scale = browser_window_get_scale(box->iframe); + + if (browser_window_scroll_at_point(box->iframe, + (x - box_x) * scale, + (y - box_y) * scale, + scrx, scry) == true) + return true; + } /* Pass into textarea widget */ if (box->gadget && (box->gadget->type == GADGET_TEXTAREA || @@ -2131,19 +1691,25 @@ static bool html_drop_file_at_point(struct content *c, int x, int y, char *file) 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, + while ((next = box_at_point(&html->unit_len_ctx, box, x, y, &box_x, &box_y)) != NULL) { box = next; - if (box->style && css_computed_visibility(box->style) == - CSS_VISIBILITY_HIDDEN) + 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->iframe) { + float scale = browser_window_get_scale(box->iframe); + return browser_window_drop_file_at_point( + box->iframe, + (x - box_x) * scale, + (y - box_y) * scale, + file); + } - if (box->object && content_drop_file_at_point(box->object, + if (box->object && + content_drop_file_at_point(box->object, x - box_x, y - box_y, file) == true) return true; @@ -2235,10 +1801,11 @@ static bool html_drop_file_at_point(struct content *c, int x, int y, char *file) ret = guit->utf8->local_to_utf8(buffer, file_len, &utf8_buff); if (ret != NSERROR_OK) { /* bad encoding shouldn't happen */ + NSLOG(netsurf, ERROR, + "local to utf8 encoding failed (%s)", + messages_get_errorcode(ret)); assert(ret != NSERROR_BAD_ENCODING); - NSLOG(netsurf, INFO, "local to utf8 encoding failed"); free(buffer); - guit->misc->warning("NoMemory", NULL); return true; } @@ -2520,7 +2087,7 @@ bool html_get_id_offset(hlcache_handle *h, lwc_string *frag_id, int *x, int *y) return false; } -static bool html_exec(struct content *c, const char *src, size_t srclen) +bool html_exec(struct content *c, const char *src, size_t srclen) { html_content *htmlc = (html_content *)c; bool result = false; @@ -2547,41 +2114,41 @@ static bool html_exec(struct content *c, const char *src, size_t srclen) NSLOG(netsurf, DEEPDEBUG, "Unable to retrieve body element"); goto out_no_body; } - + err = dom_document_create_text_node(htmlc->document, dom_src, &text_node); if (err != DOM_NO_ERR) { NSLOG(netsurf, DEEPDEBUG, "Unable to exec, could not create text node"); goto out_no_text_node; } - + err = dom_document_create_element(htmlc->document, corestring_dom_SCRIPT, &script_node); if (err != DOM_NO_ERR) { NSLOG(netsurf, DEEPDEBUG, "Unable to exec, could not create script node"); goto out_no_script_node; } - + err = dom_node_append_child(script_node, text_node, &spare_node); if (err != DOM_NO_ERR) { NSLOG(netsurf, DEEPDEBUG, "Unable to exec, could not insert code node into script node"); goto out_unparented; } dom_node_unref(spare_node); /* We do not need the spare ref at all */ - + err = dom_node_append_child(body_node, script_node, &spare_node); if (err != DOM_NO_ERR) { NSLOG(netsurf, DEEPDEBUG, "Unable to exec, could not insert script node into document body"); goto out_unparented; } dom_node_unref(spare_node); /* Again no need for the spare ref */ - + /* We successfully inserted the node into the DOM */ - + result = true; - + /* Now we unwind, starting by removing the script from wherever it * ended up parented */ - + err = dom_node_get_parent_node(script_node, &spare_node); if (err == DOM_NO_ERR && spare_node != NULL) { dom_node *second_spare; @@ -2604,6 +2171,35 @@ out_no_string: return result; } +/* See \ref content_saw_insecure_objects */ +static bool +html_saw_insecure_objects(struct content *c) +{ + html_content *htmlc = (html_content *)c; + struct content_html_object *obj = htmlc->object_list; + + /* Check through the object list */ + while (obj != NULL) { + if (obj->content != NULL) { + if (content_saw_insecure_objects(obj->content)) + return true; + } + obj = obj->next; + } + + /* Now check the script list */ + if (html_saw_insecure_scripts(htmlc)) { + return true; + } + + /* Now check stylesheets */ + if (html_css_saw_insecure_stylesheets(htmlc)) { + return true; + } + + return false; +} + /** * Compute the type of a content * @@ -2620,6 +2216,133 @@ static void html_fini(void) html_css_fini(); } +/** + * Finds all occurrences of a given string in an html box + * + * \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 nserror +find_occurrences_html_box(const char *pattern, + int p_len, + struct box *cur, + bool case_sens, + struct textsearch_context *context) +{ + struct box *a; + nserror res = NSERROR_OK; + + /* 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) { + unsigned match_length; + unsigned match_offset; + const char *new_text; + const char *pos; + + pos = content_textsearch_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; + + res = content_textsearch_add_match(context, + cur->byte_offset + match_offset, + cur->byte_offset + match_offset + match_length, + cur, + cur); + if (res != NSERROR_OK) { + return res; + } + + new_text = pos + match_length; + length -= (new_text - text); + text = new_text; + } + } + + /* and recurse */ + for (a = cur->children; a; a = a->next) { + res = find_occurrences_html_box(pattern, + p_len, + a, + case_sens, + context); + if (res != NSERROR_OK) { + return res; + } + } + + return res; +} + +/** + * 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 c The content to search + * \param csens 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 nserror +html_textsearch_find(struct content *c, + struct textsearch_context *context, + const char *pattern, + int p_len, + bool csens) +{ + html_content *html = (html_content *)c; + + if (html->layout == NULL) { + return NSERROR_INVALID; + } + + return find_occurrences_html_box(pattern, + p_len, + html->layout, + csens, + context); +} + + +static nserror +html_textsearch_bounds(struct content *c, + unsigned start_idx, + unsigned end_idx, + struct box *start_box, + struct box *end_box, + struct rect *bounds) +{ + /* get box position and jump to it */ + box_coords(start_box, &bounds->x0, &bounds->y0); + /* \todo: move x0 in by correct idx */ + box_coords(end_box, &bounds->x1, &bounds->y1); + /* \todo: move x1 in by correct idx */ + bounds->x1 += end_box->width; + bounds->y1 += end_box->height; + + return NSERROR_OK; +} + + +/** + * HTML content handler function table + */ static const content_handler html_content_handler = { .fini = html_fini, .create = html_create, @@ -2639,17 +2362,23 @@ static const content_handler html_content_handler = { .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, .exec = html_exec, + .saw_insecure_objects = html_saw_insecure_objects, + .textsearch_find = html_textsearch_find, + .textsearch_bounds = html_textsearch_bounds, + .textselection_redraw = html_textselection_redraw, + .textselection_copy = html_textselection_copy, + .textselection_get_end = html_textselection_get_end, .no_share = true, }; + +/* exported function documented in html/html.h */ nserror html_init(void) { uint32_t i; @@ -2673,19 +2402,3 @@ error: 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_interaction.c b/content/handlers/html/html_interaction.c deleted file mode 100644 index da4c67c40..000000000 --- a/content/handlers/html/html_interaction.c +++ /dev/null @@ -1,1453 +0,0 @@ -/* - * Copyright 2006 James Bursa <bursa@users.sourceforge.net> - * Copyright 2006 Richard Wilson <info@tinct.net> - * Copyright 2008 Michael Drake <tlsa@netsurf-browser.org> - * Copyright 2009 Paul Blokus <paul_pl@users.sourceforge.net> - * - * This file is part of NetSurf, http://www.netsurf-browser.org/ - * - * NetSurf is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * NetSurf is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -/** - * \file - * implementation of user interaction with a CONTENT_HTML. - */ - -#include <assert.h> -#include <stdbool.h> - -#include <dom/dom.h> - -#include "utils/corestrings.h" -#include "utils/messages.h" -#include "utils/utils.h" -#include "utils/log.h" -#include "utils/nsoption.h" -#include "netsurf/content.h" -#include "netsurf/browser_window.h" -#include "netsurf/mouse.h" -#include "netsurf/misc.h" -#include "netsurf/layout.h" -#include "netsurf/keypress.h" -#include "content/hlcache.h" -#include "desktop/frames.h" -#include "desktop/scrollbar.h" -#include "desktop/selection.h" -#include "desktop/textarea.h" -#include "javascript/js.h" -#include "desktop/gui_internal.h" - -#include "html/box.h" -#include "html/box_textarea.h" -#include "html/font.h" -#include "html/form_internal.h" -#include "html/html_internal.h" -#include "html/imagemap.h" -#include "html/search.h" - -/** - * Get pointer shape for given box - * - * \param box box in question - * \param imagemap whether an imagemap applies to the box - */ - -static browser_pointer_shape get_pointer_shape(struct box *box, bool imagemap) -{ - browser_pointer_shape pointer; - css_computed_style *style; - enum css_cursor_e cursor; - lwc_string **cursor_uris; - - if (box->type == BOX_FLOAT_LEFT || box->type == BOX_FLOAT_RIGHT) - style = box->children->style; - else - style = box->style; - - if (style == NULL) - return BROWSER_POINTER_DEFAULT; - - cursor = css_computed_cursor(style, &cursor_uris); - - switch (cursor) { - case CSS_CURSOR_AUTO: - if (box->href || (box->gadget && - (box->gadget->type == GADGET_IMAGE || - box->gadget->type == GADGET_SUBMIT)) || - imagemap) { - /* link */ - pointer = BROWSER_POINTER_POINT; - } else if (box->gadget && - (box->gadget->type == GADGET_TEXTBOX || - box->gadget->type == GADGET_PASSWORD || - box->gadget->type == GADGET_TEXTAREA)) { - /* text input */ - pointer = BROWSER_POINTER_CARET; - } else { - /* html content doesn't mind */ - pointer = BROWSER_POINTER_AUTO; - } - break; - case CSS_CURSOR_CROSSHAIR: - pointer = BROWSER_POINTER_CROSS; - break; - case CSS_CURSOR_POINTER: - pointer = BROWSER_POINTER_POINT; - break; - case CSS_CURSOR_MOVE: - pointer = BROWSER_POINTER_MOVE; - break; - case CSS_CURSOR_E_RESIZE: - pointer = BROWSER_POINTER_RIGHT; - break; - case CSS_CURSOR_W_RESIZE: - pointer = BROWSER_POINTER_LEFT; - break; - case CSS_CURSOR_N_RESIZE: - pointer = BROWSER_POINTER_UP; - break; - case CSS_CURSOR_S_RESIZE: - pointer = BROWSER_POINTER_DOWN; - break; - case CSS_CURSOR_NE_RESIZE: - pointer = BROWSER_POINTER_RU; - break; - case CSS_CURSOR_SW_RESIZE: - pointer = BROWSER_POINTER_LD; - break; - case CSS_CURSOR_SE_RESIZE: - pointer = BROWSER_POINTER_RD; - break; - case CSS_CURSOR_NW_RESIZE: - pointer = BROWSER_POINTER_LU; - break; - case CSS_CURSOR_TEXT: - pointer = BROWSER_POINTER_CARET; - break; - case CSS_CURSOR_WAIT: - pointer = BROWSER_POINTER_WAIT; - break; - case CSS_CURSOR_PROGRESS: - pointer = BROWSER_POINTER_PROGRESS; - break; - case CSS_CURSOR_HELP: - pointer = BROWSER_POINTER_HELP; - break; - default: - pointer = BROWSER_POINTER_DEFAULT; - break; - } - - return pointer; -} - - -/** - * Start drag scrolling the contents of a box - * - * \param box the box to be scrolled - * \param x x ordinate of initial mouse position - * \param y y ordinate - */ - -static void html_box_drag_start(struct box *box, int x, int y) -{ - int box_x, box_y; - int scroll_mouse_x, scroll_mouse_y; - - box_coords(box, &box_x, &box_y); - - if (box->scroll_x != NULL) { - scroll_mouse_x = x - box_x ; - scroll_mouse_y = y - (box_y + box->padding[TOP] + - box->height + box->padding[BOTTOM] - - SCROLLBAR_WIDTH); - scrollbar_start_content_drag(box->scroll_x, - scroll_mouse_x, scroll_mouse_y); - } else if (box->scroll_y != NULL) { - scroll_mouse_x = x - (box_x + box->padding[LEFT] + - box->width + box->padding[RIGHT] - - SCROLLBAR_WIDTH); - scroll_mouse_y = y - box_y; - - scrollbar_start_content_drag(box->scroll_y, - scroll_mouse_x, scroll_mouse_y); - } -} - - -/** - * End overflow scroll scrollbar drags - * - * \param html html content - * \param mouse state of mouse buttons and modifier keys - * \param x coordinate of mouse - * \param y coordinate of mouse - * \param dir Direction of drag - */ -static size_t html_selection_drag_end(struct html_content *html, - browser_mouse_state mouse, int x, int y, int dir) -{ - int pixel_offset; - struct box *box; - int dx, dy; - size_t idx = 0; - - box = box_pick_text_box(html, x, y, dir, &dx, &dy); - if (box) { - plot_font_style_t fstyle; - - font_plot_style_from_css(&html->len_ctx, box->style, &fstyle); - - guit->layout->position(&fstyle, box->text, box->length, - dx, &idx, &pixel_offset); - - idx += box->byte_offset; - } - - return idx; -} - - -/** - * Handle mouse tracking (including drags) in an HTML content window. - * - * \param c content of type html - * \param bw browser window - * \param mouse state of mouse buttons and modifier keys - * \param x coordinate of mouse - * \param y coordinate of mouse - */ - -void html_mouse_track(struct content *c, struct browser_window *bw, - browser_mouse_state mouse, int x, int y) -{ - html_mouse_action(c, bw, mouse, x, y); -} - -/** - * Helper for file gadgets to store their filename. - * - * Stores the filename unencoded on the dom node associated with the - * gadget. - * - * \todo Get rid of this crap eventually - * - * \param operation DOM operation - * \param key DOM node key being considerd - * \param _data The data assocated with the key - * \param src The source DOM node. - * \param dst The destination DOM node. - */ -static void -html__image_coords_dom_user_data_handler(dom_node_operation operation, - dom_string *key, - void *_data, - struct dom_node *src, - struct dom_node *dst) -{ - struct image_input_coords *oldcoords, *coords = _data, *newcoords; - - if (!dom_string_isequal(corestring_dom___ns_key_image_coords_node_data, - key) || coords == NULL) { - return; - } - - switch (operation) { - case DOM_NODE_CLONED: - newcoords = calloc(1, sizeof(*newcoords)); - if (newcoords != NULL) { - *newcoords = *coords; - if (dom_node_set_user_data(dst, - corestring_dom___ns_key_image_coords_node_data, - newcoords, - html__image_coords_dom_user_data_handler, - &oldcoords) == DOM_NO_ERR) { - free(oldcoords); - } - } - break; - - case DOM_NODE_DELETED: - free(coords); - break; - - case DOM_NODE_RENAMED: - case DOM_NODE_IMPORTED: - case DOM_NODE_ADOPTED: - break; - - default: - NSLOG(netsurf, INFO, "User data operation not handled."); - assert(0); - } -} - - -/** - * 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 - */ -static 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); - } -} - - -/** - * 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); - - nserror res = NSERROR_OK; - - 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) { - res = nsurl_get_utf8(url, &url_s, &url_l); - if (res != 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: - res = form_submit(content_get_url(c), - browser_window_find_target(bw, target, mouse), - gadget->form, - gadget); - break; - - case ACTION_GO: - res = 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: - res = NSERROR_OK; - break; - } - - if (res != NSERROR_OK) { - guit->misc->warning(messages_get_errorcode(res), NULL); - } - -} - - -/** - * 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; - } -} - - -/* 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/imagemap.c b/content/handlers/html/imagemap.c index 0c3576842..f23be2353 100644 --- a/content/handlers/html/imagemap.c +++ b/content/handlers/html/imagemap.c @@ -36,7 +36,8 @@ #include "content/hlcache.h" #include "html/box.h" -#include "html/html_internal.h" +#include "html/box_construct.h" +#include "html/private.h" #include "html/imagemap.h" #define HASH_SIZE 31 /* fixed size hash table */ diff --git a/content/handlers/html/interaction.c b/content/handlers/html/interaction.c new file mode 100644 index 000000000..0a843e026 --- /dev/null +++ b/content/handlers/html/interaction.c @@ -0,0 +1,1816 @@ +/* + * Copyright 2006 James Bursa <bursa@users.sourceforge.net> + * Copyright 2006 Richard Wilson <info@tinct.net> + * Copyright 2008 Michael Drake <tlsa@netsurf-browser.org> + * Copyright 2009 Paul Blokus <paul_pl@users.sourceforge.net> + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/** + * \file + * implementation of user interaction with a CONTENT_HTML. + */ + +#include <assert.h> +#include <stdbool.h> +#include <string.h> + +#include <dom/dom.h> + +#include "utils/corestrings.h" +#include "utils/messages.h" +#include "utils/utils.h" +#include "utils/log.h" +#include "utils/nsoption.h" +#include "netsurf/content.h" +#include "netsurf/browser_window.h" +#include "netsurf/mouse.h" +#include "netsurf/misc.h" +#include "netsurf/layout.h" +#include "netsurf/keypress.h" +#include "content/hlcache.h" +#include "content/textsearch.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/box_inspect.h" +#include "html/font.h" +#include "html/form_internal.h" +#include "html/private.h" +#include "html/imagemap.h" +#include "html/interaction.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->unit_len_ctx, box->style, &fstyle); + + guit->layout->position(&fstyle, box->text, box->length, + dx, &idx, &pixel_offset); + + idx += box->byte_offset; + } + + return idx; +} + + +/** + * 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); + } +} + + +/** + * 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 + */ +static 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); + } +} + + +/** + * handle html mouse action when select menu is open + * + */ +static nserror +mouse_action_select_menu(html_content *html, + struct browser_window *bw, + browser_mouse_state mouse, + int x, int y) +{ + struct box *box; + int box_x = 0; + int box_y = 0; + const char *status; + int width, height; + struct hlcache_handle *bw_content; + browser_drag_type bw_drag_type; + + assert(html->visible_select_menu != NULL); + + bw_drag_type = browser_window_get_drag_type(bw); + if (bw_drag_type != DRAGGING_NONE && !mouse) { + /* drag end: select menu */ + form_select_mouse_drag_end(html->visible_select_menu, mouse, x, y); + } + + 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) { + /* set status if menu still open */ + union content_msg_data msg_data; + msg_data.explicit_status_text = status; + content_broadcast((struct content *)html, + CONTENT_MSG_STATUS, + &msg_data); + return NSERROR_OK; + } + + /* close menu and redraw where it was */ + form_select_get_dimensions(html->visible_select_menu, &width, &height); + + html->visible_select_menu = NULL; + + bw_content = browser_window_get_content(bw); + content_request_redraw(bw_content, + box_x, + box_y, + width, + height); + return NSERROR_OK; +} + + +/** + * handle html mouse action when a selection drag is being performed + * + */ +static nserror +mouse_action_drag_selection(html_content *html, + struct browser_window *bw, + browser_mouse_state mouse, + int x, int y) +{ + struct box *box; + int dir = -1; + int dx, dy; + size_t idx; + union html_drag_owner drag_owner; + int pixel_offset; + plot_font_style_t fstyle; + + if (!mouse) { + /* End of selection drag */ + 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 NSERROR_OK; + } + + if (selection_dragging_start(html->sel)) { + dir = 1; + } + + box = box_pick_text_box(html, x, y, dir, &dx, &dy); + if (box != NULL) { + font_plot_style_from_css(&html->unit_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 NSERROR_OK; +} + + +/** + * handle html mouse action when a scrollbar drag is being performed + * + */ +static nserror +mouse_action_drag_scrollbar(html_content *html, + struct browser_window *bw, + browser_mouse_state mouse, + int x, int y) +{ + struct scrollbar *scr; + struct html_scrollbar_data *data; + struct box *box; + int box_x = 0; + int box_y = 0; + const char *status; + int scroll_mouse_x = 0, scroll_mouse_y = 0; + scrollbar_mouse_status scrollbar_status; + + scr = html->drag_owner.scrollbar; + + if (!mouse) { + /* drag end: scrollbar */ + html_overflow_scroll_drag_end(scr, mouse, x, y); + } + + data = scrollbar_get_data(scr); + + 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); + scrollbar_status = 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; + + scrollbar_status = scrollbar_mouse_action(scr, + mouse, + scroll_mouse_x, + scroll_mouse_y); + } + status = scrollbar_mouse_status_to_message(scrollbar_status); + + if (status != NULL) { + union content_msg_data msg_data; + + msg_data.explicit_status_text = status; + content_broadcast((struct content *)html, + CONTENT_MSG_STATUS, + &msg_data); + } + + return NSERROR_OK; +} + + +/** + * handle mouse actions while dragging in a text area + */ +static nserror +mouse_action_drag_textarea(html_content *html, + struct browser_window *bw, + browser_mouse_state mouse, + int x, int y) +{ + struct box *box; + int box_x = 0; + int box_y = 0; + + 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 NSERROR_OK; +} + + +/** + * handle mouse actions while dragging in a content + */ +static nserror +mouse_action_drag_content(html_content *html, + struct browser_window *bw, + browser_mouse_state mouse, + int x, int y) +{ + struct box *box; + int box_x = 0; + int box_y = 0; + + 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 NSERROR_OK; +} + + +/** + * local structure containing all the mouse action state information + */ +struct mouse_action_state { + struct { + const char *status; /**< status text */ + browser_pointer_shape pointer; /**< pointer shape */ + enum { + ACTION_NONE, + ACTION_NOSEND, /**< do not send status and pointer message */ + ACTION_SUBMIT, + ACTION_GO, + ACTION_JS, + } action; + } result; + + /** dom node */ + struct dom_node *node; + + /** html object */ + struct { + struct box *box; + int pos_x; + int pos_y; + } html_object; + + /** non html object */ + hlcache_handle *object; + + /** iframe */ + struct browser_window *iframe; + + /** link either from href or imagemap */ + struct { + struct box *box; + nsurl *url; + const char *target; + bool is_imagemap; + } link; + + /** gadget */ + struct { + struct form_control *control; + struct box *box; + int box_x; + int box_y; + const char *target; + } gadget; + + /** title */ + const char *title; + + /** candidate box for drag operation */ + struct box *drag_candidate; + + /** scrollbar */ + struct { + struct scrollbar *bar; + int mouse_x; + int mouse_y; + } scroll; + + /** text in box */ + struct { + struct box *box; + int box_x; + } text; +}; + + +/** + * iterate the box tree for deepest node at coordinates + * + * extracts mouse action node information by descending 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 + */ +static nserror +get_mouse_action_node(html_content *html, + int x, int y, + struct mouse_action_state *man) +{ + struct box *box; + int box_x = 0; + int box_y = 0; + + /* initialise the mouse action state data */ + memset(man, 0, sizeof(struct mouse_action_state)); + man->node = html->layout->node; /* Default dom node to the <HTML> */ + man->result.pointer = BROWSER_POINTER_DEFAULT; + + /* 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]; + + do { + /* skip hidden boxes */ + if ((box->style != NULL) && + (css_computed_visibility(box->style) == + CSS_VISIBILITY_HIDDEN)) { + goto next_box; + } + + if (box->node != NULL) { + man->node = box->node; + } + + if (box->object) { + if (content_get_type(box->object) == CONTENT_HTML) { + man->html_object.box = box; + man->html_object.pos_x = box_x; + man->html_object.pos_y = box_y; + } else { + man->object = box->object; + } + } + + if (box->iframe) { + man->iframe = box->iframe; + } + + if (box->href) { + man->link.url = box->href; + man->link.target = box->target; + man->link.box = box; + man->link.is_imagemap = false; + } + + if (box->usemap) { + man->link.url = imagemap_get(html, + box->usemap, + box_x, + box_y, + x, y, + &man->link.target); + man->link.box = box; + man->link.is_imagemap = true; + } + + if (box->gadget) { + man->gadget.control = box->gadget; + man->gadget.box = box; + man->gadget.box_x = box_x; + man->gadget.box_y = box_y; + if (box->gadget->form) { + man->gadget.target = box->gadget->form->target; + } + } + + if (box->title) { + man->title = box->title; + } + + man->result.pointer = get_pointer_shape(box, false); + + if ((box->scroll_x != NULL) || + (box->scroll_y != NULL)) { + int padding_left; + int padding_right; + int padding_top; + int padding_bottom; + + if (man->drag_candidate == NULL) { + man->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 */ + + man->scroll.bar = box->scroll_y; + man->scroll.mouse_x = x - (padding_right - SCROLLBAR_WIDTH); + man->scroll.mouse_y = y - padding_top; + break; + + } else if ((box->scroll_x != NULL) && + (y > (padding_bottom - + SCROLLBAR_WIDTH))) { + /* mouse above horizontal box scroll */ + + man->scroll.bar = box->scroll_x; + man->scroll.mouse_x = x - padding_left; + man->scroll.mouse_y = y - (padding_bottom - SCROLLBAR_WIDTH); + break; + } + } + } + + if (box->text && !box->object) { + man->text.box = box; + man->text.box_x = box_x; + } + + next_box: + /* iterate to next box */ + box = box_at_point(&html->unit_len_ctx, box, x, y, &box_x, &box_y); + } while (box != 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(man->node != NULL); + + return NSERROR_OK; +} + + +/** + * process mouse activity on a form gadget + */ +static nserror +gadget_mouse_action(html_content *html, + browser_mouse_state mouse, + int x, int y, + struct mouse_action_state *mas) +{ + struct content *c = (struct content *)html; + textarea_mouse_status ta_status; + union content_msg_data msg_data; + nserror res; + bool click; + 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); + + switch (mas->gadget.control->type) { + case GADGET_SELECT: + mas->result.status = messages_get("FormSelect"); + mas->result.pointer = BROWSER_POINTER_MENU; + if (mouse & BROWSER_MOUSE_CLICK_1 && + nsoption_bool(core_select_menu)) { + html->visible_select_menu = mas->gadget.control; + res = form_open_select_menu(c, + mas->gadget.control, + form_select_menu_callback, + c); + if (res != NSERROR_OK) { + NSLOG(netsurf, ERROR, "%s", + messages_get_errorcode(res)); + html->visible_select_menu = NULL; + } + mas->result.pointer = BROWSER_POINTER_DEFAULT; + } else if (mouse & BROWSER_MOUSE_CLICK_1) { + msg_data.select_menu.gadget = mas->gadget.control; + content_broadcast(c, + CONTENT_MSG_SELECTMENU, + &msg_data); + } + break; + + case GADGET_CHECKBOX: + mas->result.status = messages_get("FormCheckbox"); + if (mouse & BROWSER_MOUSE_CLICK_1) { + mas->gadget.control->selected = !mas->gadget.control->selected; + dom_html_input_element_set_checked( + (dom_html_input_element *)(mas->gadget.control->node), + mas->gadget.control->selected); + html__redraw_a_box(html, mas->gadget.box); + } + break; + + case GADGET_RADIO: + mas->result.status = messages_get("FormRadio"); + if (mouse & BROWSER_MOUSE_CLICK_1) { + form_radio_set(mas->gadget.control); + } + 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 NSERROR_OK; + } + coords->x = x - mas->gadget.box_x; + coords->y = y - mas->gadget.box_y; + if (dom_node_set_user_data( + mas->gadget.control->node, + corestring_dom___ns_key_image_coords_node_data, + coords, + html__image_coords_dom_user_data_handler, + &oldcoords) != DOM_NO_ERR) { + return NSERROR_OK; + } + free(oldcoords); + } + fallthrough; + + case GADGET_SUBMIT: + if (mas->gadget.control->form) { + static char status_buffer[200]; + + snprintf(status_buffer, + sizeof status_buffer, + messages_get("FormSubmit"), + mas->gadget.control->form->action); + mas->result.status = status_buffer; + mas->result.pointer = get_pointer_shape(mas->gadget.box, + false); + if (mouse & (BROWSER_MOUSE_CLICK_1 | + BROWSER_MOUSE_CLICK_2)) { + mas->result.action = ACTION_SUBMIT; + } + } else { + mas->result.status = messages_get("FormBadSubmit"); + } + break; + + case GADGET_TEXTBOX: + case GADGET_PASSWORD: + case GADGET_TEXTAREA: + if (mas->gadget.control->type == GADGET_TEXTAREA) { + mas->result.status = messages_get("FormTextarea"); + } else { + mas->result.status = messages_get("FormTextbox"); + } + + if (click && + (html->selection_type != HTML_SELECTION_TEXTAREA || + html->selection_owner.textarea != mas->gadget.box)) { + union html_selection_owner sel_owner; + sel_owner.none = true; + html_set_selection(html, + HTML_SELECTION_NONE, + sel_owner, + true); + } + + ta_status = textarea_mouse_action(mas->gadget.control->data.text.ta, + mouse, + x - mas->gadget.box_x, + y - mas->gadget.box_y); + + if (ta_status & TEXTAREA_MOUSE_EDITOR) { + mas->result.pointer = get_pointer_shape(mas->gadget.box, false); + } else { + mas->result.pointer = BROWSER_POINTER_DEFAULT; + mas->result.status = scrollbar_mouse_status_to_message(ta_status >> 3); + } + break; + + case GADGET_HIDDEN: + /* not possible: no box generated */ + break; + + case GADGET_RESET: + mas->result.status = messages_get("FormReset"); + break; + + case GADGET_FILE: + mas->result.status = messages_get("FormFile"); + if (mouse & BROWSER_MOUSE_CLICK_1) { + msg_data.gadget_click.gadget = mas->gadget.control; + content_broadcast(c, + CONTENT_MSG_GADGETCLICK, + &msg_data); + } + break; + + case GADGET_BUTTON: + /* This gadget cannot be activated */ + mas->result.status = messages_get("FormButton"); + break; + } + + return NSERROR_OK; +} + + +/** + * process mouse activity on an iframe + */ +static nserror +iframe_mouse_action(struct browser_window *bw, + browser_mouse_state mouse, + int x, int y, + struct mouse_action_state *mas) +{ + int pos_x, pos_y; + float scale; + + scale = browser_window_get_scale(bw); + + browser_window_get_position(mas->iframe, false, &pos_x, &pos_y); + + if (mouse & BROWSER_MOUSE_CLICK_1 || + mouse & BROWSER_MOUSE_CLICK_2) { + browser_window_mouse_click(mas->iframe, + mouse, + (x * scale) - pos_x, + (y * scale) - pos_y); + } else { + browser_window_mouse_track(mas->iframe, + mouse, + (x * scale) - pos_x, + (y * scale) - pos_y); + } + mas->result.action = ACTION_NOSEND; + + return NSERROR_OK; +} + + +/** + * process mouse activity on an html object + */ +static nserror +html_object_mouse_action(html_content *html, + struct browser_window *bw, + browser_mouse_state mouse, + int x, int y, + struct mouse_action_state *mas) +{ + bool click; + 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 (click && + (html->selection_type != HTML_SELECTION_CONTENT || + html->selection_owner.content != mas->html_object.box)) { + union html_selection_owner sel_owner; + 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(mas->html_object.box->object, + bw, + mouse, + x - mas->html_object.pos_x, + y - mas->html_object.pos_y); + } else { + content_mouse_track(mas->html_object.box->object, + bw, + mouse, + x - mas->html_object.pos_x, + y - mas->html_object.pos_y); + } + + mas->result.action = ACTION_NOSEND; + return NSERROR_OK; +} + + +/** + * determine if a url has a javascript scheme + * + * \param urm The url to check. + * \return true if the url is a javascript scheme else false + */ +static bool is_javascript_navigate_url(nsurl *url) +{ + bool is_js = false; + lwc_string *scheme; + + scheme = nsurl_get_component(url, NSURL_SCHEME); + if (scheme != NULL) { + if (scheme == corestring_lwc_javascript) { + is_js = true; + } + lwc_string_unref(scheme); + } + return is_js; +} + + +/** + * process mouse activity on a link + */ +static nserror +link_mouse_action(html_content *html, + struct browser_window *bw, + browser_mouse_state mouse, + int x, int y, + struct mouse_action_state *mas) +{ + nserror res; + char *url_s = NULL; + size_t url_l = 0; + static char status_buffer[200]; + union content_msg_data msg_data; + + if (nsoption_bool(display_decoded_idn) == true) { + res = nsurl_get_utf8(mas->link.url, &url_s, &url_l); + if (res != 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 (mas->title) { + snprintf(status_buffer, + sizeof status_buffer, + "%s: %s", + url_s ? url_s : nsurl_access(mas->link.url), + mas->title); + } else { + snprintf(status_buffer, + sizeof status_buffer, + "%s", + url_s ? url_s : nsurl_access(mas->link.url)); + } + + if (url_s != NULL) { + free(url_s); + } + + mas->result.status = status_buffer; + + mas->result.pointer = get_pointer_shape(mas->link.box, + mas->link.is_imagemap); + + if (mouse & BROWSER_MOUSE_CLICK_1 && + mouse & BROWSER_MOUSE_MOD_1) { + /* force download of link */ + browser_window_navigate(bw, + mas->link.url, + content_get_url((struct content *)html), + BW_NAVIGATE_DOWNLOAD, + NULL, + NULL, + NULL); + + } else if (mouse & BROWSER_MOUSE_CLICK_2 && + mouse & BROWSER_MOUSE_MOD_1) { + msg_data.savelink.url = mas->link.url; + msg_data.savelink.title = mas->title; + content_broadcast((struct content *)html, + CONTENT_MSG_SAVELINK, + &msg_data); + + } else if (mouse & (BROWSER_MOUSE_CLICK_1 | BROWSER_MOUSE_CLICK_2)) { + if (is_javascript_navigate_url(mas->link.url)) { + mas->result.action = ACTION_JS; + } else { + mas->result.action = ACTION_GO; + } + } + + return NSERROR_OK; +} + + + +/** + * process mouse activity if it is not anything else + */ +static nserror +default_mouse_action(html_content *html, + struct browser_window *bw, + browser_mouse_state mouse, + int x, int y, + struct mouse_action_state *mas) +{ + struct content *c = (struct content *)html; + bool done = false; + + /* frame resizing */ + if (browser_window_frame_resize_start(bw, mouse, x, y, &mas->result.pointer)) { + if (mouse & (BROWSER_MOUSE_DRAG_1 | BROWSER_MOUSE_DRAG_2)) { + mas->result.status = messages_get("FrameDrag"); + } + done = true; + } + + /* if clicking in the main page, remove the selection from any + * text areas */ + if (!done) { + union html_selection_owner sel_owner; + bool click; + 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 (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 (mas->text.box) { + int pixel_offset; + size_t idx; + plot_font_style_t fstyle; + + font_plot_style_from_css(&html->unit_len_ctx, + mas->text.box->style, + &fstyle); + + guit->layout->position(&fstyle, + mas->text.box->text, + mas->text.box->length, + x - mas->text.box_x, + &idx, + &pixel_offset); + + if (selection_click(html->sel, + html->bw, + mouse, + mas->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); + mas->result.status = messages_get("Selecting"); + } + + done = true; + } + + } else if (mouse & BROWSER_MOUSE_PRESS_1) { + sel_owner.none = true; + selection_clear(html->sel, true); + } + + if (selection_active(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) { + union content_msg_data msg_data; + if (mas->title) { + mas->result.status = mas->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 (mas->drag_candidate == NULL) { + browser_window_page_drag_start(bw, + x, y); + } else { + html_box_drag_start(mas->drag_candidate, + x, y); + } + mas->result.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 (mas->drag_candidate == NULL) { + browser_window_page_drag_start(bw, + x, y); + } else { + html_box_drag_start(mas->drag_candidate, + x, y); + } + mas->result.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); + } + + return NSERROR_OK; +} + + +/** + * handle non dragging mouse actions + */ +static nserror +mouse_action_drag_none(html_content *html, + struct browser_window *bw, + browser_mouse_state mouse, + int x, int y) +{ + nserror res; + struct content *c = (struct content *)html; + union content_msg_data msg_data; + lwc_string *path; + + /** + * computed state + * + * not on heap to avoid allocation or stack because it is large + */ + static struct mouse_action_state mas; + + res = get_mouse_action_node(html, x, y, &mas); + if (res != NSERROR_OK) { + return res; + } + + if (mas.scroll.bar) { + mas.result.status = scrollbar_mouse_status_to_message( + scrollbar_mouse_action(mas.scroll.bar, + mouse, + mas.scroll.mouse_x, + mas.scroll.mouse_y)); + mas.result.pointer = BROWSER_POINTER_DEFAULT; + + } else if (mas.gadget.control) { + res = gadget_mouse_action(html, mouse, x, y, &mas); + + } else if ((mas.object != NULL) && (mouse & BROWSER_MOUSE_MOD_2)) { + + if (mouse & BROWSER_MOUSE_DRAG_2) { + msg_data.dragsave.type = CONTENT_SAVE_NATIVE; + msg_data.dragsave.content = mas.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 = mas.object; + content_broadcast(c, CONTENT_MSG_DRAGSAVE, &msg_data); + } + + /* \todo should have a drag-saving object msg */ + + } else if (mas.iframe != NULL) { + res = iframe_mouse_action(bw, mouse, x, y, &mas); + + } else if (mas.html_object.box != NULL) { + res = html_object_mouse_action(html, bw, mouse, x, y, &mas); + + } else if (mas.link.url != NULL) { + res = link_mouse_action(html, bw, mouse, x, y, &mas); + + } else { + res = default_mouse_action(html, bw, mouse, x, y, &mas); + + } + if (res != NSERROR_OK) { + return res; + } + + /* send status and pointer message */ + if (mas.result.action != ACTION_NOSEND) { + msg_data.explicit_status_text = mas.result.status; + content_broadcast(c, CONTENT_MSG_STATUS, &msg_data); + + msg_data.pointer = mas.result.pointer; + content_broadcast(c, CONTENT_MSG_POINTER, &msg_data); + } + + /* fire dom click event */ + if (mouse & BROWSER_MOUSE_CLICK_1) { + fire_generic_dom_event(corestring_dom_click, mas.node, true, true); + } + + /* deferred actions that can cause this browser_window to be destroyed + * and must therefore be done after set_status/pointer + */ + switch (mas.result.action) { + case ACTION_SUBMIT: + res = form_submit(content_get_url(c), + browser_window_find_target(bw, + mas.gadget.target, + mouse), + mas.gadget.control->form, + mas.gadget.control); + break; + + case ACTION_GO: + res = browser_window_navigate( + browser_window_find_target(bw, + mas.link.target, + mouse), + mas.link.url, + content_get_url(c), + BW_NAVIGATE_HISTORY, + NULL, + NULL, + NULL); + break; + + case ACTION_JS: + path = nsurl_get_component(mas.link.url, NSURL_PATH); + if (path != NULL) { + html_exec(c, + lwc_string_data(path), + lwc_string_length(path)); + lwc_string_unref(path); + } + break; + + case ACTION_NOSEND: + case ACTION_NONE: + res = NSERROR_OK; + break; + } + + return res; +} + + +/* exported interface documented in html/interaction.h */ +nserror html_mouse_track(struct content *c, + struct browser_window *bw, + browser_mouse_state mouse, + int x, int y) +{ + return html_mouse_action(c, bw, mouse, x, y); +} + + +/* exported interface documented in html/interaction.h */ +nserror +html_mouse_action(struct content *c, + struct browser_window *bw, + browser_mouse_state mouse, + int x, int y) +{ + html_content *html = (html_content *)c; + nserror res = NSERROR_OK; + + /* handle open select menu */ + if (html->visible_select_menu != NULL) { + return mouse_action_select_menu(html, bw, mouse, x, y); + } + + /* handle content drag */ + switch (html->drag_type) { + case HTML_DRAG_SELECTION: + res = mouse_action_drag_selection(html, bw, mouse, x, y); + break; + + case HTML_DRAG_SCROLLBAR: + res = mouse_action_drag_scrollbar(html, bw, mouse, x, y); + break; + + case HTML_DRAG_TEXTAREA_SELECTION: + case HTML_DRAG_TEXTAREA_SCROLLBAR: + res = mouse_action_drag_textarea(html, bw, mouse, x, y); + break; + + case HTML_DRAG_CONTENT_SELECTION: + case HTML_DRAG_CONTENT_SCROLL: + res = mouse_action_drag_content(html, bw, mouse, x, y); + break; + + case HTML_DRAG_NONE: + res = mouse_action_drag_none(html, bw, mouse, x, y); + break; + + default: + /* Unknown content related drag type */ + assert(0 && "Unknown content related drag type"); + } + + if (res != NSERROR_OK) { + NSLOG(netsurf, ERROR, "%s", messages_get_errorcode(res)); + } + + return res; +} + + +/** + * 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; + + /** \todo + * At the moment, the front end interface for keypress only gives + * us a UCS4 key value. This doesn't doesn't have all the information + * we need to fill out the event properly. We don't get to know about + * modifier keys, and things like CTRL+C are passed in as + * \ref NS_KEY_COPY_SELECTION, a magic value outside the valid Unicode + * range. + * + * We need to: + * + * 1. Update the front end interface so that both press and release + * events reach the core. + * 2. Stop encoding the special keys like \ref NS_KEY_COPY_SELECTION as + * magic values in the front ends, so we just get the events, e.g.: + * 1. Press ctrl + * 2. Press c + * 3. Release c + * 4. Release ctrl + * 3. Pass all the new info to the DOM KeyboardEvent events. + * 4. If there is a focused element, fire the event at that, instead of + * `html->layout->node`. + * 5. Rebuild the \ref NS_KEY_COPY_SELECTION values from the info we + * now get given, and use that for the code below this + * \ref fire_dom_keyboard_event call. + * 6. Move the code after this \ref fire_dom_keyboard_event call into + * the default action handler for DOM events. + * + * This will mean that if the JavaScript event listener does + * `event.preventDefault()` then we won't handle the event when + * we're not supposed to. + */ + if (html->layout != NULL && html->layout->node != NULL) { + fire_dom_keyboard_event(corestring_dom_keydown, + html->layout->node, true, true, key); + } + + switch (html->focus_type) { + case HTML_FOCUS_CONTENT: + return content_keypress(html->focus_owner.content->object, key); + + case HTML_FOCUS_TEXTAREA: + if (box_textarea_keypress(html, html->focus_owner.textarea, key) == NSERROR_OK) { + return true; + } else { + return false; + } + + 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 there's no selection, leave Escape for the caller */ + return selection_clear(sel, true); + } + + return false; +} + + +/** + * 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; + } +} + + +/* 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); + fallthrough; + 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); + fallthrough; + 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/interaction.h b/content/handlers/html/interaction.h new file mode 100644 index 000000000..f92e3adb0 --- /dev/null +++ b/content/handlers/html/interaction.h @@ -0,0 +1,125 @@ +/* + * Copyright 2004 James Bursa <bursa@users.sourceforge.net> + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/** + * \file + * HTML content user interaction handling + */ + +#ifndef NETSURF_HTML_INTERACTION_H +#define NETSURF_HTML_INTERACTION_H + +#include "desktop/search.h" /* search flags enum */ + +/** + * Context for scrollbar + */ +struct html_scrollbar_data { + struct content *c; + struct box *box; +}; + +/** + * 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 + */ +nserror html_mouse_track(struct content *c, struct browser_window *bw, + browser_mouse_state mouse, int x, int y); + + +/** + * Handle mouse clicks and movements in an HTML content window. + * + * 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. + * + * \param c content of type html + * \param bw browser window + * \param mouse state of mouse buttons and modifier keys + * \param x x coordinate of mouse + * \param y y coordinate of mouse + * \return NSERROR_OK or appropriate error code. + */ +nserror 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); + + +/** + * 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); + + +#endif diff --git a/content/handlers/html/layout.c b/content/handlers/html/layout.c index f4a1a206f..76ce24df5 100644 --- a/content/handlers/html/layout.c +++ b/content/handlers/html/layout.c @@ -47,10 +47,13 @@ #include "utils/talloc.h" #include "utils/utils.h" #include "utils/nsoption.h" +#include "utils/corestrings.h" +#include "utils/nsurl.h" #include "netsurf/inttypes.h" #include "netsurf/content.h" #include "netsurf/browser_window.h" #include "netsurf/layout.h" +#include "content/content.h" #include "content/content_protected.h" #include "css/utils.h" #include "desktop/scrollbar.h" @@ -58,29 +61,17 @@ #include "html/html.h" #include "html/html_save.h" -#include "html/html_internal.h" +#include "html/private.h" #include "html/box.h" +#include "html/box_inspect.h" #include "html/font.h" #include "html/form_internal.h" #include "html/layout.h" +#include "html/layout_internal.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] = { +const css_len_func margin_funcs[4] = { [TOP] = css_computed_margin_top, [RIGHT] = css_computed_margin_right, [BOTTOM] = css_computed_margin_bottom, @@ -88,7 +79,7 @@ static const css_len_func margin_funcs[4] = { }; /** Array of per-side access functions for computed style paddings. */ -static const css_len_func padding_funcs[4] = { +const css_len_func padding_funcs[4] = { [TOP] = css_computed_padding_top, [RIGHT] = css_computed_padding_right, [BOTTOM] = css_computed_padding_bottom, @@ -96,7 +87,7 @@ static const css_len_func padding_funcs[4] = { }; /** Array of per-side access functions for computed style border_widths. */ -static const css_len_func border_width_funcs[4] = { +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, @@ -104,7 +95,7 @@ static const css_len_func border_width_funcs[4] = { }; /** Array of per-side access functions for computed style border styles. */ -static const css_border_style_func border_style_funcs[4] = { +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, @@ -112,7 +103,7 @@ static const css_border_style_func border_style_funcs[4] = { }; /** Array of per-side access functions for computed style border colors. */ -static const css_border_color_func border_color_funcs[4] = { +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, @@ -120,16 +111,11 @@ static const css_border_color_func border_color_funcs[4] = { }; /* 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. @@ -220,6 +206,11 @@ layout_get_object_dimensions(struct box *box, int intrinsic_width = content_get_width(box->object); int intrinsic_height = content_get_height(box->object); + if (min_width > 0 && min_width > *width) + *width = min_width; + if (max_width >= 0 && max_width < *width) + *width = max_width; + if (intrinsic_width != 0) *height = (*width * intrinsic_height) / intrinsic_width; @@ -237,7 +228,7 @@ layout_get_object_dimensions(struct box *box, * \return length of indent */ static int layout_text_indent( - const nscss_len_ctx *len_ctx, + const css_unit_ctx *unit_len_ctx, const css_computed_style *style, int width) { css_fixed value = 0; @@ -248,73 +239,8 @@ static int layout_text_indent( 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)); - } + return FIXTOINT(css_unit_len2device_px(style, unit_len_ctx, + value, unit)); } } @@ -347,8 +273,8 @@ static void layout_minmax_table(struct box *table, if (table->max_width != UNKNOWN_MAX_WIDTH) return; - if (table_calculate_column_types(&content->len_ctx, table) == false) { - NSLOG(netsurf, WARNING, + if (table_calculate_column_types(&content->unit_len_ctx, table) == false) { + NSLOG(netsurf, ERROR, "Could not establish table column types."); return; } @@ -370,8 +296,10 @@ static void layout_minmax_table(struct box *table, css_computed_border_spacing(table->style, &h, &hu, &v, &vu); - border_spacing_h = FIXTOINT(nscss_len2px(&content->len_ctx, - h, hu, table->style)); + border_spacing_h = FIXTOINT(css_unit_len2device_px( + table->style, + &content->unit_len_ctx, + h, hu)); } /* 1st pass: consider cells with colspan 1 only */ @@ -476,8 +404,10 @@ static void layout_minmax_table(struct box *table, /* 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)); + int width = FIXTOINT(css_unit_len2device_px( + table->style, + &content->unit_len_ctx, + value, unit)); if (table_min < width) table_min = width; if (table_max < width) @@ -485,10 +415,10 @@ static void layout_minmax_table(struct box *table, } /* add margins, border, padding to min, max widths */ - calculate_mbp_width(&content->len_ctx, + calculate_mbp_width(&content->unit_len_ctx, table->style, LEFT, true, true, true, &extra_fixed, &extra_frac); - calculate_mbp_width(&content->len_ctx, + calculate_mbp_width(&content->unit_len_ctx, table->style, RIGHT, true, true, true, &extra_fixed, &extra_frac); if (extra_fixed < 0) @@ -572,11 +502,7 @@ layout_minmax_line(struct box *first, 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); + assert(lh__box_is_inline_content(b)); NSLOG(layout, DEBUG, "%p: min %i, max %i", b, min, max); @@ -585,13 +511,13 @@ layout_minmax_line(struct box *first, break; } - if (b->type == BOX_FLOAT_LEFT || b->type == BOX_FLOAT_RIGHT) { + if (lh__box_is_float_box(b)) { assert(b->children); - if (b->children->type == BOX_BLOCK) - layout_minmax_block(b->children, font_func, + if (b->children->type == BOX_TABLE) + layout_minmax_table(b->children, font_func, content); else - layout_minmax_table(b->children, font_func, + layout_minmax_block(b->children, font_func, content); b->min_width = b->children->min_width; b->max_width = b->children->max_width; @@ -601,7 +527,7 @@ layout_minmax_line(struct box *first, continue; } - if (b->type == BOX_INLINE_BLOCK) { + if (b->type == BOX_INLINE_BLOCK || b->type == BOX_INLINE_FLEX) { layout_minmax_block(b, font_func, content); if (min < b->min_width) min = b->min_width; @@ -613,17 +539,17 @@ layout_minmax_line(struct box *first, } assert(b->style); - font_plot_style_from_css(&content->len_ctx, b->style, &fstyle); + font_plot_style_from_css(&content->unit_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, + calculate_mbp_width(&content->unit_len_ctx, b->style, LEFT, true, true, true, &fixed, &frac); if (!b->inline_end) - calculate_mbp_width(&content->len_ctx, + calculate_mbp_width(&content->unit_len_ctx, b->style, RIGHT, true, true, true, &fixed, &frac); @@ -633,7 +559,7 @@ layout_minmax_line(struct box *first, /* \todo update min width, consider fractional extra */ } else if (b->type == BOX_INLINE_END) { fixed = frac = 0; - calculate_mbp_width(&content->len_ctx, + calculate_mbp_width(&content->unit_len_ctx, b->inline_end->style, RIGHT, true, true, true, &fixed, &frac); @@ -652,8 +578,7 @@ layout_minmax_line(struct box *first, continue; } - if (!b->object && !(b->flags & IFRAME) && !b->gadget && - !(b->flags & REPLACE_DIM)) { + if (lh__box_is_replace(b) == false) { /* inline non-replaced, 10.3.1 and 10.6.1 */ bool no_wrap_box; if (!b->text) @@ -751,16 +676,18 @@ layout_minmax_line(struct box *first, if (unit == CSS_UNIT_PCT) { width = AUTO; } else { - width = FIXTOINT(nscss_len2px(&content->len_ctx, - value, unit, b->style)); + width = FIXTOINT(css_unit_len2device_px( + b->style, + &content->unit_len_ctx, + value, unit)); if (bs == CSS_BOX_SIZING_BORDER_BOX) { fixed = frac = 0; - calculate_mbp_width(&content->len_ctx, + calculate_mbp_width(&content->unit_len_ctx, block->style, LEFT, false, true, true, &fixed, &frac); - calculate_mbp_width(&content->len_ctx, + calculate_mbp_width(&content->unit_len_ctx, block->style, RIGHT, false, true, true, &fixed, &frac); @@ -778,8 +705,10 @@ layout_minmax_line(struct box *first, /* 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)); + height = FIXTOINT(css_unit_len2device_px( + b->style, + &content->unit_len_ctx, + value, unit)); } else { height = AUTO; } @@ -795,20 +724,20 @@ layout_minmax_line(struct box *first, fixed = frac = 0; if (bs == CSS_BOX_SIZING_BORDER_BOX) { - calculate_mbp_width(&content->len_ctx, + calculate_mbp_width(&content->unit_len_ctx, b->style, LEFT, true, false, false, &fixed, &frac); - calculate_mbp_width(&content->len_ctx, + calculate_mbp_width(&content->unit_len_ctx, b->style, RIGHT, true, false, false, &fixed, &frac); } else { - calculate_mbp_width(&content->len_ctx, + calculate_mbp_width(&content->unit_len_ctx, b->style, LEFT, true, true, true, &fixed, &frac); - calculate_mbp_width(&content->len_ctx, + calculate_mbp_width(&content->unit_len_ctx, b->style, RIGHT, true, true, true, &fixed, &frac); @@ -822,20 +751,20 @@ layout_minmax_line(struct box *first, fixed = frac = 0; if (bs == CSS_BOX_SIZING_BORDER_BOX) { - calculate_mbp_width(&content->len_ctx, + calculate_mbp_width(&content->unit_len_ctx, b->style, LEFT, true, false, false, &fixed, &frac); - calculate_mbp_width(&content->len_ctx, + calculate_mbp_width(&content->unit_len_ctx, b->style, RIGHT, true, false, false, &fixed, &frac); } else { - calculate_mbp_width(&content->len_ctx, + calculate_mbp_width(&content->unit_len_ctx, b->style, LEFT, true, true, true, &fixed, &frac); - calculate_mbp_width(&content->len_ctx, + calculate_mbp_width(&content->unit_len_ctx, b->style, RIGHT, true, true, true, &fixed, &frac); @@ -847,10 +776,10 @@ layout_minmax_line(struct box *first, } else { /* form control with no object */ if (width == AUTO) - width = FIXTOINT(nscss_len2px( - &content->len_ctx, - INTTOFIX(1), CSS_UNIT_EM, - b->style)); + width = FIXTOINT(css_unit_len2device_px( + b->style, + &content->unit_len_ctx, + INTTOFIX(1), CSS_UNIT_EM)); } if (min < width && !box_has_percentage_max_width(b)) @@ -864,7 +793,7 @@ layout_minmax_line(struct box *first, if (first_line) { /* todo: handle percentage values properly */ /* todo: handle text-indent interaction with floats */ - int text_indent = layout_text_indent(&content->len_ctx, + int text_indent = layout_text_indent(&content->unit_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; @@ -931,7 +860,6 @@ layout_minmax_inline_container(struct box *inline_container, inline_container->max_width); } - /** * Calculate minimum and maximum width of a block. * @@ -957,9 +885,13 @@ static void layout_minmax_block( css_fixed height = 0; css_unit hunit = CSS_UNIT_PX; enum css_box_sizing_e bs = CSS_BOX_SIZING_CONTENT_BOX; + bool using_min_border_box = false; + bool using_max_border_box = false; bool child_has_height = false; assert(block->type == BOX_BLOCK || + block->type == BOX_FLEX || + block->type == BOX_INLINE_FLEX || block->type == BOX_INLINE_BLOCK || block->type == BOX_TABLE_CELL); @@ -974,9 +906,9 @@ static void layout_minmax_block( } /* 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) && + if (((block->parent && lh__box_is_float_box(block->parent)) || + block->type == BOX_INLINE_BLOCK || + block->type == BOX_INLINE_FLEX) && wtype != CSS_WIDTH_SET) { /* box shrinks to fit; need minimum width */ block->flags |= NEED_MIN; @@ -987,6 +919,9 @@ static void layout_minmax_block( wtype != CSS_WIDTH_SET) { /* box inside shrink-to-fit context; need minimum width */ block->flags |= NEED_MIN; + } else if (block->parent && (block->parent->type == BOX_FLEX)) { + /* box is flex item */ + block->flags |= NEED_MIN; } if (block->gadget && (block->gadget->type == GADGET_TEXTBOX || @@ -997,8 +932,8 @@ static void layout_minmax_block( css_fixed size = INTTOFIX(10); css_unit unit = CSS_UNIT_EM; - min = max = FIXTOINT(nscss_len2px(&content->len_ctx, - size, unit, block->style)); + min = max = FIXTOINT(css_unit_len2device_px(block->style, + &content->unit_len_ctx, size, unit)); block->flags |= HAS_HEIGHT; } @@ -1011,8 +946,8 @@ static void layout_minmax_block( /* 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)); + min = max = FIXTOINT(css_unit_len2device_px(block->style, + &content->unit_len_ctx, size, unit)); block->flags |= HAS_HEIGHT; } @@ -1036,6 +971,7 @@ static void layout_minmax_block( /* recurse through children */ for (child = block->children; child; child = child->next) { switch (child->type) { + case BOX_FLEX: case BOX_BLOCK: layout_minmax_block(child, font_func, content); @@ -1077,10 +1013,24 @@ static void layout_minmax_block( continue; } - if (min < child->min_width) - min = child->min_width; - if (max < child->max_width) - max = child->max_width; + if (lh__box_is_flex_container(block) && + lh__flex_main_is_horizontal(block)) { + if (block->style != NULL && + css_computed_flex_wrap(block->style) == + CSS_FLEX_WRAP_NOWRAP) { + min += child->min_width; + } else { + if (min < child->min_width) + min = child->min_width; + } + max += child->max_width; + + } else { + 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; @@ -1093,23 +1043,40 @@ static void layout_minmax_block( } /* 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 (block->type != BOX_TABLE_CELL && !lh__box_is_flex_item(block)) { + bool border_box = bs == CSS_BOX_SIZING_BORDER_BOX; + enum css_max_width_e max_type; + enum css_min_width_e min_type; + css_unit unit = CSS_UNIT_PX; + css_fixed value = 0; + + if (wtype == CSS_WIDTH_SET && wunit != CSS_UNIT_PCT) { + min = max = FIXTOINT( + css_unit_len2device_px(block->style, + &content->unit_len_ctx, width, wunit)); + using_max_border_box = border_box; + using_min_border_box = border_box; + } + + min_type = css_computed_min_width(block->style, &value, &unit); + if (min_type == CSS_MIN_WIDTH_SET && unit != CSS_UNIT_PCT) { + int val = FIXTOINT(css_unit_len2device_px(block->style, + &content->unit_len_ctx, value, unit)); + + if (min < val) { + min = val; + using_min_border_box = border_box; + } + } + + max_type = css_computed_max_width(block->style, &value, &unit); + if (max_type == CSS_MAX_WIDTH_SET && unit != CSS_UNIT_PCT) { + int val = FIXTOINT(css_unit_len2device_px(block->style, + &content->unit_len_ctx, value, unit)); + + if (val >= 0 && max > val) { + max = val; + using_max_border_box = border_box; } } } @@ -1123,22 +1090,30 @@ static void layout_minmax_block( /* 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); + calculate_mbp_width(&content->unit_len_ctx, block->style, LEFT, + false, true, true, &extra_fixed, &extra_frac); + calculate_mbp_width(&content->unit_len_ctx, block->style, RIGHT, + false, true, true, &extra_fixed, &extra_frac); + + if (using_max_border_box) { + max -= extra_fixed; + max = max(max, 0); + } + + if (using_min_border_box) { + min -= extra_fixed; + min = max(min, 0); + } + + if (max < min) { + min = max; } + + calculate_mbp_width(&content->unit_len_ctx, block->style, LEFT, + true, false, false, &extra_fixed, &extra_frac); + calculate_mbp_width(&content->unit_len_ctx, block->style, RIGHT, + true, false, false, &extra_fixed, &extra_frac); + if (extra_fixed < 0) extra_fixed = 0; if (extra_frac < 0) @@ -1157,375 +1132,15 @@ static void layout_minmax_block( 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; - } - } + assert(0 <= block->min_width); + assert(block->min_width <= block->max_width); } /** * Find next block that current margin collapses to. * - * \param len_ctx Length conversion context + * \param unit_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 @@ -1534,7 +1149,7 @@ layout_find_dimensions(const nscss_len_ctx *len_ctx, * \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, +layout_next_margin_block(const css_unit_ctx *unit_len_ctx, struct box *box, struct box *block, int viewport_height, @@ -1554,7 +1169,7 @@ layout_next_margin_block(const nscss_len_ctx *len_ctx, /* Get margins */ if (box->style) { - layout_find_dimensions(len_ctx, + layout_find_dimensions(unit_len_ctx, box->parent->width, viewport_height, box, box->style, @@ -1629,7 +1244,7 @@ layout_next_margin_block(const nscss_len_ctx *len_ctx, /* Get margins */ if (box->style) { - layout_find_dimensions(len_ctx, + layout_find_dimensions(unit_len_ctx, box->parent->width, viewport_height, box, box->style, @@ -1866,7 +1481,7 @@ layout_solve_width(struct box *box, * Compute dimensions of box, margins, paddings, and borders for a block-level * element. * - * \param len_ctx Length conversion context + * \param unit_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. @@ -1879,7 +1494,7 @@ layout_solve_width(struct box *box, * 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, +layout_block_find_dimensions(const css_unit_ctx *unit_len_ctx, int available_width, int viewport_height, int lm, @@ -1893,7 +1508,7 @@ layout_block_find_dimensions(const nscss_len_ctx *len_ctx, struct box_border *border = box->border; const css_computed_style *style = box->style; - layout_find_dimensions(len_ctx, available_width, viewport_height, box, + layout_find_dimensions(unit_len_ctx, available_width, viewport_height, box, style, &width, &height, &max_width, &min_width, &max_height, &min_height, margin, padding, border); @@ -1985,15 +1600,10 @@ static void layout_move_children(struct box *box, int x, int 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, +/* Documented in layout_internal.h */ +bool layout_table( + struct box *table, + int available_width, html_content *content) { unsigned int columns = table->columns; /* total columns */ @@ -2047,7 +1657,7 @@ static bool layout_table(struct box *table, int available_width, 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, + layout_find_dimensions(&content->unit_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; @@ -2059,8 +1669,8 @@ static bool layout_table(struct box *table, int available_width, assert(c->style); table_used_border_for_cell( - &content->len_ctx, c); - layout_find_dimensions(&content->len_ctx, + &content->unit_len_ctx, c); + layout_find_dimensions(&content->unit_len_ctx, available_width, -1, c, c->style, 0, 0, 0, 0, 0, 0, 0, c->padding, c->border); @@ -2090,10 +1700,10 @@ static bool layout_table(struct box *table, int available_width, 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)); + border_spacing_h = FIXTOINT(css_unit_len2device_px( + style, &content->unit_len_ctx, h, hu)); + border_spacing_v = FIXTOINT(css_unit_len2device_px( + style, &content->unit_len_ctx, v, vu)); } /* find specified table width, or available width if auto-width */ @@ -2103,8 +1713,9 @@ static bool layout_table(struct box *table, int available_width, table_width = FPCT_OF_INT_TOINT(value, available_width); } else { table_width = - FIXTOINT(nscss_len2px(&content->len_ctx, - value, unit, style)); + FIXTOINT(css_unit_len2device_px( + style, &content->unit_len_ctx, + value, unit)); } /* specified width includes border */ @@ -2182,8 +1793,9 @@ static bool layout_table(struct box *table, int available_width, } else { /* This is the minimum height for the table * (see 17.5.3) */ - min_height = FIXTOINT(nscss_len2px(&content->len_ctx, - value, unit, style)); + min_height = FIXTOINT(css_unit_len2device_px( + style, &content->unit_len_ctx, + value, unit)); } } @@ -2373,9 +1985,10 @@ static bool layout_table(struct box *table, int available_width, 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)); + row_height = FIXTOINT(css_unit_len2device_px( + row->style, + &content->unit_len_ctx, + value, unit)); } for (c = row->children; c; c = c->next) { assert(c->style); @@ -2412,9 +2025,10 @@ static bool layout_table(struct box *table, int available_width, /* 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)); + int h = FIXTOINT(css_unit_len2device_px( + c->style, + &content->unit_len_ctx, + value, unit)); if (c->height < h) c->height = h; } @@ -2558,14 +2172,14 @@ static bool layout_table(struct box *table, int available_width, /** * Manimpulate box height according to CSS min-height and max-height properties * - * \param len_ctx CSS length conversion context for document. + * \param unit_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, + const css_unit_ctx *unit_len_ctx, struct box *box, struct box *container) { @@ -2626,8 +2240,9 @@ static bool layout_apply_minmax_height( } } } else { - h = FIXTOINT(nscss_len2px(len_ctx, - value, unit, box->style)); + h = FIXTOINT(css_unit_len2device_px( + box->style, unit_len_ctx, + value, unit)); if (h < box->height) { box->height = h; updated = true; @@ -2656,8 +2271,9 @@ static bool layout_apply_minmax_height( } } } else { - h = FIXTOINT(nscss_len2px(len_ctx, - value, unit, box->style)); + h = FIXTOINT(css_unit_len2device_px( + box->style, unit_len_ctx, + value, unit)); if (h > box->height) { box->height = h; updated = true; @@ -2679,7 +2295,9 @@ static bool layout_block_object(struct box *block) { assert(block); assert(block->type == BOX_BLOCK || + block->type == BOX_FLEX || block->type == BOX_INLINE_BLOCK || + block->type == BOX_INLINE_FLEX || block->type == BOX_TABLE || block->type == BOX_TABLE_CELL); assert(block->object); @@ -2687,7 +2305,7 @@ static bool layout_block_object(struct box *block) 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) { + if (content_can_reformat(block->object)) { content_reformat(block->object, false, block->width, 1); } else { /* Non-HTML objects */ @@ -2825,7 +2443,7 @@ layout_text_box_split(html_content *content, * 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 unit_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 @@ -2834,7 +2452,7 @@ layout_text_box_split(html_content *content, */ static void layout_float_find_dimensions( - const nscss_len_ctx *len_ctx, + const css_unit_ctx *unit_len_ctx, int available_width, const css_computed_style *style, struct box *box) @@ -2854,7 +2472,7 @@ layout_float_find_dimensions( overflow_y == CSS_OVERFLOW_AUTO) ? SCROLLBAR_WIDTH : 0; - layout_find_dimensions(len_ctx, available_width, -1, box, style, + layout_find_dimensions(unit_len_ctx, available_width, -1, box, style, &width, &height, &max_width, &min_width, &max_height, &min_height, margin, padding, border); @@ -2890,26 +2508,30 @@ layout_float_find_dimensions( box->gadget->type == GADGET_FILE) { if (width == AUTO) { size = INTTOFIX(10); - width = FIXTOINT(nscss_len2px(len_ctx, - size, unit, box->style)); + width = FIXTOINT(css_unit_len2device_px( + box->style, unit_len_ctx, + size, unit)); } if (box->gadget->type == GADGET_FILE && height == AUTO) { size = FLTTOFIX(1.5); - height = FIXTOINT(nscss_len2px(len_ctx, - size, unit, box->style)); + height = FIXTOINT(css_unit_len2device_px( + box->style, unit_len_ctx, + size, unit)); } } if (box->gadget->type == GADGET_TEXTAREA) { if (width == AUTO) { size = INTTOFIX(10); - width = FIXTOINT(nscss_len2px(len_ctx, - size, unit, box->style)); + width = FIXTOINT(css_unit_len2device_px( + box->style, unit_len_ctx, + size, unit)); } if (height == AUTO) { size = INTTOFIX(4); - height = FIXTOINT(nscss_len2px(len_ctx, - size, unit, box->style)); + height = FIXTOINT(css_unit_len2device_px( + box->style, unit_len_ctx, + size, unit)); } } } else if (width == AUTO) { @@ -2930,9 +2552,9 @@ layout_float_find_dimensions( * mbp as was used in layout_minmax_block() */ int fixed = 0; float frac = 0; - calculate_mbp_width(len_ctx, box->style, LEFT, + calculate_mbp_width(unit_len_ctx, box->style, LEFT, true, true, true, &fixed, &frac); - calculate_mbp_width(len_ctx, box->style, RIGHT, + calculate_mbp_width(unit_len_ctx, box->style, RIGHT, true, true, true, &fixed, &frac); if (fixed < 0) fixed = 0; @@ -2969,12 +2591,20 @@ layout_float_find_dimensions( */ 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; + assert(b->type == BOX_TABLE || + b->type == BOX_BLOCK || + b->type == BOX_INLINE_BLOCK || + b->type == BOX_FLEX || + b->type == BOX_INLINE_FLEX); + layout_float_find_dimensions(&content->unit_len_ctx, width, b->style, b); + if (b->type == BOX_TABLE || b->type == BOX_INLINE_FLEX) { + if (b->type == BOX_TABLE) { + if (!layout_table(b, width, content)) + return false; + } else { + if (!layout_flex(b, width, content)) + return false; + } if (b->margin[LEFT] == AUTO) b->margin[LEFT] = 0; if (b->margin[RIGHT] == AUTO) @@ -2983,8 +2613,9 @@ static bool layout_float(struct box *b, int width, html_content *content) b->margin[TOP] = 0; if (b->margin[BOTTOM] == AUTO) b->margin[BOTTOM] = 0; - } else + } else { return layout_block_context(b, -1, content); + } return true; } @@ -3044,7 +2675,7 @@ place_float_below(struct box *c, int width, int cx, int y, struct box *cont) * Calculate line height from a style. */ static int line_height( - const nscss_len_ctx *len_ctx, + const css_unit_ctx *unit_len_ctx, const css_computed_style *style) { enum css_line_height_e lhtype; @@ -3063,16 +2694,16 @@ static int line_height( if (lhtype == CSS_LINE_HEIGHT_NUMBER || lhunit == CSS_UNIT_PCT) { - line_height = nscss_len2px(len_ctx, - lhvalue, CSS_UNIT_EM, style); + line_height = css_unit_len2device_px(style, unit_len_ctx, + lhvalue, CSS_UNIT_EM); 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); + line_height = css_unit_len2device_px(style, unit_len_ctx, + lhvalue, lhunit); } return FIXTOINT(line_height); @@ -3144,7 +2775,7 @@ layout_line(struct box *first, x1 -= cx; if (indent) - x0 += layout_text_indent(&content->len_ctx, + x0 += layout_text_indent(&content->unit_len_ctx, first->parent->parent->style, *width); if (x1 < x0) @@ -3154,7 +2785,7 @@ layout_line(struct box *first, * 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, + used_height = height = line_height(&content->unit_len_ctx, first->parent->parent->style); else /* inline containers with no text are usually for layout and @@ -3171,20 +2802,14 @@ layout_line(struct box *first, 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); - + assert(lh__box_is_inline_content(b)); 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) + if (lh__box_is_float_box(b)) continue; if (b->type == BOX_INLINE_BLOCK && (css_computed_position(b->style) == @@ -3194,11 +2819,12 @@ layout_line(struct box *first, continue; assert(b->style != NULL); - font_plot_style_from_css(&content->len_ctx, b->style, &fstyle); + font_plot_style_from_css(&content->unit_len_ctx, b->style, &fstyle); x += space_after; - if (b->type == BOX_INLINE_BLOCK) { + if (b->type == BOX_INLINE_BLOCK || + b->type == BOX_INLINE_FLEX) { if (b->max_width != UNKNOWN_WIDTH) if (!layout_float(b, *width, content)) return false; @@ -3218,7 +2844,7 @@ layout_line(struct box *first, if (b->type == BOX_INLINE) { /* calculate borders, margins, and padding */ - layout_find_dimensions(&content->len_ctx, + layout_find_dimensions(&content->unit_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++) @@ -3250,10 +2876,9 @@ layout_line(struct box *first, continue; } - if (!b->object && !(b->flags & IFRAME) && !b->gadget && - !(b->flags & REPLACE_DIM)) { + if (lh__box_is_replace(b) == false) { /* inline non-replaced, 10.3.1 and 10.6.1 */ - b->height = line_height(&content->len_ctx, + b->height = line_height(&content->unit_len_ctx, b->style ? b->style : b->parent->parent->style); if (height < b->height) @@ -3324,7 +2949,7 @@ layout_line(struct box *first, /* inline replaced, 10.3.2 and 10.6.2 */ assert(b->style); - layout_find_dimensions(&content->len_ctx, + layout_find_dimensions(&content->unit_len_ctx, *width, -1, b, b->style, &b->width, &b->height, &max_width, &min_width, @@ -3347,17 +2972,19 @@ layout_line(struct box *first, } 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)); + b->width = FIXTOINT(css_unit_len2device_px( + b->style, + &content->unit_len_ctx, INTTOFIX(1), + CSS_UNIT_EM)); if (b->height == AUTO) - b->height = FIXTOINT(nscss_len2px( - &content->len_ctx, INTTOFIX(1), - CSS_UNIT_EM, b->style)); + b->height = FIXTOINT(css_unit_len2device_px( + b->style, + &content->unit_len_ctx, INTTOFIX(1), + CSS_UNIT_EM)); } /* Reformat object to new box size */ - if (b->object && content_get_type(b->object) == CONTENT_HTML && + if (b->object && content_can_reformat(b->object) && b->width != content_get_available_width(b->object)) { css_fixed value = 0; @@ -3386,7 +3013,7 @@ layout_line(struct box *first, x1 -= cx; if (indent) - x0 += layout_text_indent(&content->len_ctx, + x0 += layout_text_indent(&content->unit_len_ctx, first->parent->parent->style, *width); if (x1 < x0) @@ -3409,10 +3036,7 @@ layout_line(struct box *first, 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) { + } else if (lh__box_is_inline_flow(b)) { assert(b->width != UNKNOWN_WIDTH); x_previous = x; @@ -3420,7 +3044,8 @@ layout_line(struct box *first, b->x = x; if ((b->type == BOX_INLINE && !b->inline_end) || - b->type == BOX_INLINE_BLOCK) { + b->type == BOX_INLINE_BLOCK || + b->type == BOX_INLINE_FLEX) { b->x += b->margin[LEFT] + b->border[LEFT].width; x = b->x + b->padding[LEFT] + b->width + b->padding[RIGHT] + @@ -3445,7 +3070,7 @@ layout_line(struct box *first, else if (b->text || b->type == BOX_INLINE_END) { if (b->space == UNKNOWN_WIDTH) { font_plot_style_from_css( - &content->len_ctx, + &content->unit_len_ctx, b->style, &fstyle); /** \todo handle errors */ font_func->width(&fstyle, " ", 1, @@ -3599,7 +3224,7 @@ layout_line(struct box *first, !(split_box->flags & IFRAME) && !split_box->gadget && split_box->text) { - font_plot_style_from_css(&content->len_ctx, + font_plot_style_from_css(&content->unit_len_ctx, split_box->style, &fstyle); /** \todo handle errors */ font_func->split(&fstyle, @@ -3751,9 +3376,7 @@ layout_line(struct box *first, d->y = *y; continue; } else if ((d->type == BOX_INLINE && - ((d->object || d->gadget) == false) && - !(d->flags & IFRAME) && - !(d->flags & REPLACE_DIM)) || + lh__box_is_replace(d) == false) || d->type == BOX_BR || d->type == BOX_TEXT || d->type == BOX_INLINE_END) { @@ -3874,8 +3497,7 @@ static bool layout_inline_container(struct box *inline_container, int width, whitespace == CSS_WHITE_SPACE_PRE_WRAP); } - if ((!c->object && !(c->flags & REPLACE_DIM) && - !(c->flags & IFRAME) && + if ((lh__box_is_object(c) == false && c->text && (c->length || is_pre)) || c->type == BOX_BR) has_text_children = true; @@ -3905,21 +3527,11 @@ static bool layout_inline_container(struct box *inline_container, int width, } -/** - * 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) +/* Documented in layout_intertnal.h */ +bool layout_block_context( + struct box *block, + int viewport_height, + html_content *content) { struct box *box; int cx, cy; /**< current coordinates */ @@ -3934,7 +3546,9 @@ layout_block_context(struct box *block, assert(block->type == BOX_BLOCK || block->type == BOX_INLINE_BLOCK || - block->type == BOX_TABLE_CELL); + block->type == BOX_TABLE_CELL || + block->type == BOX_FLEX || + block->type == BOX_INLINE_FLEX); assert(block->width != UNKNOWN_WIDTH); assert(block->width != AUTO); @@ -3963,9 +3577,10 @@ layout_block_context(struct box *block, 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)); + block->height = FIXTOINT(css_unit_len2device_px( + block->style, + &content->unit_len_ctx, + gadget_size, gadget_unit)); } box = block->children; @@ -4002,7 +3617,9 @@ layout_block_context(struct box *block, 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 || + assert(box->type == BOX_BLOCK || + box->type == BOX_FLEX || + box->type == BOX_TABLE || box->type == BOX_INLINE_CONTAINER); /* Tables are laid out before being positioned, because the @@ -4029,7 +3646,7 @@ layout_block_context(struct box *block, * 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, + &content->unit_len_ctx, box, block, viewport_height, &max_pos_margin, &max_neg_margin); /* We have a margin that has not yet been applied. */ @@ -4053,9 +3670,10 @@ layout_block_context(struct box *block, * 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) && + if (box->type == BOX_FLEX || + box->type == BOX_BLOCK || + box->flags & IFRAME) { + if (lh__box_is_object(box) == false && box->style && (overflow_x != CSS_OVERFLOW_VISIBLE || overflow_y != CSS_OVERFLOW_VISIBLE)) { @@ -4080,7 +3698,7 @@ layout_block_context(struct box *block, box->parent->padding[RIGHT] - x1; } - layout_block_find_dimensions(&content->len_ctx, + layout_block_find_dimensions(&content->unit_len_ctx, box->parent->width, viewport_height, lm, rm, box); if (box->type == BOX_BLOCK && !(box->flags & IFRAME)) { @@ -4140,6 +3758,7 @@ layout_block_context(struct box *block, /* Vertical margin */ if (((box->type == BOX_BLOCK && (box->flags & HAS_HEIGHT)) || + box->type == BOX_FLEX || box->type == BOX_TABLE || (box->type == BOX_INLINE_CONTAINER && !box_is_first_child(box)) || @@ -4164,11 +3783,19 @@ layout_block_context(struct box *block, /* 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)) { + if (box->type == BOX_FLEX || + (box->type == BOX_BLOCK && box->style && + (overflow_x != CSS_OVERFLOW_VISIBLE || + overflow_y != CSS_OVERFLOW_VISIBLE))) { - layout_block_context(box, viewport_height, content); + if (box->type == BOX_FLEX) { + if (!layout_flex(box, box->width, content)) { + return false; + } + } else { + layout_block_context(box, + viewport_height, content); + } cy += box->padding[TOP]; @@ -4189,7 +3816,8 @@ layout_block_context(struct box *block, goto advance_to_next_box; } - NSLOG(layout, DEBUG, "box %p, cx %i, cy %i", box, cx, cy); + NSLOG(layout, DEBUG, "box %p, cx %i, cy %i, width %i", + box, cx, cy, box->width); /* Layout (except tables). */ if (box->object) { @@ -4321,7 +3949,7 @@ layout_block_context(struct box *block, css_computed_position(box->style) != CSS_POSITION_ABSOLUTE && layout_apply_minmax_height( - &content->len_ctx, + &content->unit_len_ctx, box, NULL)) { /* Height altered */ /* Set current cy */ @@ -4378,7 +4006,7 @@ layout_block_context(struct box *block, 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); + layout_apply_minmax_height(&content->unit_len_ctx, block, NULL); } if (block->gadget && @@ -4390,7 +4018,7 @@ layout_block_context(struct box *block, block->padding[RIGHT]; int ta_height = block->padding[TOP] + block->height + block->padding[BOTTOM]; - font_plot_style_from_css(&content->len_ctx, + font_plot_style_from_css(&content->unit_len_ctx, block->style, &fstyle); fstyle.background = NS_TRANSPARENT; textarea_set_layout(block->gadget->data.text.ta, @@ -4402,36 +4030,426 @@ layout_block_context(struct box *block, return true; } +/** + * Get a dom node's element tag type. + * + * \param[in] node Node to get tag type of. + * \param[in] type Returns element tag type on success. + * \return true if on success, false otherwise. + */ +static bool +layout__get_element_tag( + const dom_node *node, + dom_html_element_type *type) +{ + dom_html_element_type element_type; + dom_node_type node_type; + dom_exception exc; + + exc = dom_node_get_node_type(node, &node_type); + if (exc != DOM_NO_ERR || + node_type != DOM_ELEMENT_NODE) { + return false; + } + + exc = dom_html_element_get_tag_type(node, &element_type); + if (exc != DOM_NO_ERR) { + return false; + } + + *type = element_type; + return true; +} + + +/** + * Check a node's tag type. + * + * \param[in] node Node to check tag type of. + * \param[in] type Tag type to test for. + * \return true if if node has given type, false otherwise. + */ +static inline bool +layout__check_element_type( + const dom_node *node, + dom_html_element_type type) +{ + dom_html_element_type element_type; + + if (!layout__get_element_tag(node, &element_type)) { + return false; + } + + return element_type == type; +} + + +/** + * Helper to get attribute value from a LI node. + * + * \param[in] li_node DOM node for the LI element; + * \param[out] value_out Returns the value on success. + * \return true if node has value, otherwise false. + */ +static bool +layout__get_li_value(dom_node *li_node, dom_long *value_out) +{ + dom_exception exc; + dom_long value; + bool has_value; + + /** \todo + * dom_html_li_element_get_value() is rubbish and we can't tell + * a lack of value attribute or invalid value from a perfectly + * valid '-1'. + * + * This helps for the common case of no value. However we should + * fix libdom to have some kind of sane interface to get numerical + * attributes. + */ + exc = dom_element_has_attribute(li_node, + corestring_dom_value, + &has_value); + if (exc != DOM_NO_ERR || has_value == false) { + return false; + } + + exc = dom_html_li_element_get_value( + (dom_html_li_element *)li_node, + &value); + if (exc != DOM_NO_ERR) { + return false; + } + + *value_out = value; + return true; +} + + +/** + * Helper to get start attribute value from a OL node. + * + * \param[in] ol_node DOM node for the OL element; + * \param[out] start_out Returns the value on success. + * \return true if node has value, otherwise false. + */ +static bool +layout__get_ol_start(dom_node *ol_node, dom_long *start_out) +{ + dom_exception exc; + dom_long start; + bool has_start; + + /** \todo + * see layout__get_li_value(). + */ + exc = dom_element_has_attribute(ol_node, + corestring_dom_start, + &has_start); + if (exc != DOM_NO_ERR || has_start == false) { + return false; + } + + exc = dom_html_olist_element_get_start( + (dom_html_olist_element *)ol_node, + &start); + if (exc != DOM_NO_ERR) { + return false; + } + + *start_out = start; + return true; +} + + +/** + * Helper to get reversed attribute value from a OL node. + * + * \param[in] ol_node DOM node for the OL element; + * \return true if node has reversed, otherwise false. + */ +static bool +layout__get_ol_reversed(dom_node *ol_node) +{ + dom_exception exc; + bool has_reversed; + + exc = dom_element_has_attribute(ol_node, + corestring_dom_reversed, + &has_reversed); + if (exc != DOM_NO_ERR) { + return false; + } + + return has_reversed; +} + + +/** + * Get the number of list items for a list owner. + * + * \param[in] list_owner DOM node to count list items for. + * \param[in] count_out Returns list item count on success. + * \return true on success, otherwise false. + */ +static bool +layout__get_list_item_count( + dom_node *list_owner, dom_long *count_out) +{ + dom_html_element_type tag_type; + dom_exception exc; + dom_node *child; + int count; + + if (list_owner == NULL) { + return false; + } + + if (!layout__get_element_tag(list_owner, &tag_type)) { + return false; + } + + if (tag_type != DOM_HTML_ELEMENT_TYPE_OL && + tag_type != DOM_HTML_ELEMENT_TYPE_UL) { + return false; + } + + exc = dom_node_get_first_child(list_owner, &child); + if (exc != DOM_NO_ERR) { + return false; + } + + count = 0; + while (child != NULL) { + dom_node *temp_node; + + if (layout__check_element_type(child, + DOM_HTML_ELEMENT_TYPE_LI)) { + struct box *child_box; + if (dom_node_get_user_data(child, + corestring_dom___ns_key_box_node_data, + &child_box) != DOM_NO_ERR) { + dom_node_unref(child); + return false; + } + + if (child_box != NULL && + child_box->list_marker != NULL) { + count++; + } + } + + exc = dom_node_get_next_sibling(child, &temp_node); + dom_node_unref(child); + if (exc != DOM_NO_ERR) { + return false; + } + + child = temp_node; + } + + *count_out = count; + return true; +} + + +/** + * Handle list item counting, if this is a list owner box. + * + * \param[in] box Box to do list item counting for. + */ +static void +layout__ordered_list_count( + struct box *box) +{ + dom_html_element_type tag_type; + dom_exception exc; + dom_node *child; + int step = 1; + dom_long next; + + if (box->node == NULL) { + return; + } + + if (!layout__get_element_tag(box->node, &tag_type)) { + return; + } + + if (tag_type != DOM_HTML_ELEMENT_TYPE_OL && + tag_type != DOM_HTML_ELEMENT_TYPE_UL) { + return; + } + + next = 1; + if (tag_type == DOM_HTML_ELEMENT_TYPE_OL) { + bool have_start = layout__get_ol_start(box->node, &next); + bool have_reversed = layout__get_ol_reversed(box->node); + + if (have_reversed) { + step = -1; + } + + if (!have_start && have_reversed) { + layout__get_list_item_count(box->node, &next); + } + } + + exc = dom_node_get_first_child(box->node, &child); + if (exc != DOM_NO_ERR) { + return; + } + + while (child != NULL) { + dom_node *temp_node; + + if (layout__check_element_type(child, + DOM_HTML_ELEMENT_TYPE_LI)) { + struct box *child_box; + + if (dom_node_get_user_data(child, + corestring_dom___ns_key_box_node_data, + &child_box) != DOM_NO_ERR) { + dom_node_unref(child); + return; + } + + if (child_box != NULL && + child_box->list_marker != NULL) { + dom_long value; + struct box *marker = child_box->list_marker; + if (layout__get_li_value(child, &value)) { + marker->list_value = value; + next = marker->list_value; + } else { + marker->list_value = next; + } + next += step; + } + } + + exc = dom_node_get_next_sibling(child, &temp_node); + dom_node_unref(child); + if (exc != DOM_NO_ERR) { + return; + } + + child = temp_node; + } +} + +/** + * Set up the marker text for a numerical list item. + * + * \param[in] content The HTML content. + * \param[in] box The list item's main box. + */ +static void +layout__set_numerical_marker_text( + const html_content *content, + struct box *box) +{ + struct box *marker = box->list_marker; + size_t counter_len; + css_error css_res; + enum { + /** + * initial length of a list marker buffer + * + * enough for 9,999,999,999,999,999,999 in decimal + * or five characters for 4-byte UTF-8. + */ + LIST_MARKER_SIZE = 20, + }; + + marker->text = talloc_array(content->bctx, char, LIST_MARKER_SIZE); + if (marker->text == NULL) { + return; + } + + css_res = css_computed_format_list_style(box->style, marker->list_value, + marker->text, LIST_MARKER_SIZE, &counter_len); + if (css_res == CSS_OK) { + if (counter_len > LIST_MARKER_SIZE) { + /* Use computed size as marker did not fit in + * default allocation. */ + marker->text = talloc_realloc(content->bctx, + marker->text, + char, + counter_len); + if (marker->text == NULL) { + return; + } + css_computed_format_list_style(box->style, + marker->list_value, marker->text, + counter_len, &counter_len); + } + marker->length = counter_len; + } +} + +/** + * Find out if box's style represents a numerical list style type. + * + * \param[in] b Box with style to test. + * \return true if box has numerical list style type, false otherwise. + */ +static bool +layout__list_item_is_numerical( + const struct box *b) +{ + enum css_list_style_type_e t = css_computed_list_style_type(b->style); + + switch (t) { + case CSS_LIST_STYLE_TYPE_DISC: /* Fall through. */ + case CSS_LIST_STYLE_TYPE_CIRCLE: /* Fall through. */ + case CSS_LIST_STYLE_TYPE_SQUARE: /* Fall through. */ + case CSS_LIST_STYLE_TYPE_NONE: + return false; + + default: + 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) +layout_lists(const html_content *content, struct box *box) { struct box *child; - struct box *marker; - plot_font_style_t fstyle; + + layout__ordered_list_count(box); for (child = box->children; child; child = child->next) { if (child->list_marker) { - marker = child->list_marker; + struct box *marker = child->list_marker; + + if (layout__list_item_is_numerical(child)) { + if (marker->text == NULL) { + layout__set_numerical_marker_text( + content, child); + } + } 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->y = (line_height( + &content->unit_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, + plot_font_style_t fstyle; + font_plot_style_from_css( + &content->unit_len_ctx, + marker->style, + &fstyle); + content->font_func->width(&fstyle, marker->text, marker->length, &marker->width); @@ -4439,7 +4457,8 @@ layout_lists(struct box *box, } marker->x = -marker->width; marker->y = 0; - marker->height = line_height(len_ctx, + marker->height = line_height( + &content->unit_len_ctx, marker->style); } else { marker->x = 0; @@ -4450,7 +4469,7 @@ layout_lists(struct box *box, /* Gap between marker and content */ marker->x -= 4; } - layout_lists(child, font_func, len_ctx); + layout_lists(content, child); } } @@ -4459,7 +4478,7 @@ layout_lists(struct box *box, * Compute box offsets for a relatively or absolutely positioned box with * respect to a box. * - * \param len_ctx Length conversion context + * \param unit_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 @@ -4470,7 +4489,7 @@ layout_lists(struct box *box, * 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, +layout_compute_offsets(const css_unit_ctx *unit_len_ctx, struct box *box, struct box *containing_block, int *top, @@ -4482,9 +4501,9 @@ layout_compute_offsets(const nscss_len_ctx *len_ctx, css_fixed value = 0; css_unit unit = CSS_UNIT_PX; - assert(containing_block->width != UNKNOWN_WIDTH && - containing_block->width != AUTO && - containing_block->height != AUTO); + assert(containing_block->width != UNKNOWN_WIDTH); + assert(containing_block->width != AUTO); + assert(containing_block->height != AUTO); /* left */ type = css_computed_left(box->style, &value, &unit); @@ -4493,8 +4512,9 @@ layout_compute_offsets(const nscss_len_ctx *len_ctx, *left = FPCT_OF_INT_TOINT(value, containing_block->width); } else { - *left = FIXTOINT(nscss_len2px(len_ctx, - value, unit, box->style)); + *left = FIXTOINT(css_unit_len2device_px( + box->style, unit_len_ctx, + value, unit)); } } else { *left = AUTO; @@ -4507,8 +4527,9 @@ layout_compute_offsets(const nscss_len_ctx *len_ctx, *right = FPCT_OF_INT_TOINT(value, containing_block->width); } else { - *right = FIXTOINT(nscss_len2px(len_ctx, - value, unit, box->style)); + *right = FIXTOINT(css_unit_len2device_px( + box->style, unit_len_ctx, + value, unit)); } } else { *right = AUTO; @@ -4521,8 +4542,9 @@ layout_compute_offsets(const nscss_len_ctx *len_ctx, *top = FPCT_OF_INT_TOINT(value, containing_block->height); } else { - *top = FIXTOINT(nscss_len2px(len_ctx, - value, unit, box->style)); + *top = FIXTOINT(css_unit_len2device_px( + box->style, unit_len_ctx, + value, unit)); } } else { *top = AUTO; @@ -4535,8 +4557,9 @@ layout_compute_offsets(const nscss_len_ctx *len_ctx, *bottom = FPCT_OF_INT_TOINT(value, containing_block->height); } else { - *bottom = FIXTOINT(nscss_len2px(len_ctx, - value, unit, box->style)); + *bottom = FIXTOINT(css_unit_len2device_px( + box->style, unit_len_ctx, + value, unit)); } } else { *bottom = AUTO; @@ -4570,7 +4593,9 @@ layout_absolute(struct box *box, int space; assert(box->type == BOX_BLOCK || box->type == BOX_TABLE || - box->type == BOX_INLINE_BLOCK); + box->type == BOX_INLINE_BLOCK || + box->type == BOX_FLEX || + box->type == BOX_INLINE_FLEX); /* The static position is where the box would be if it was not * absolutely positioned. The x and y are filled in by @@ -4588,18 +4613,16 @@ layout_absolute(struct box *box, 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, + layout_compute_offsets(&content->unit_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, + layout_find_dimensions(&content->unit_len_ctx, available_width, -1, box, box->style, &width, &height, &max_width, &min_width, 0, 0, margin, padding, border); @@ -4807,6 +4830,13 @@ layout_absolute(struct box *box, box->float_container = NULL; layout_solve_width(box, box->parent->width, box->width, 0, 0, -1, -1); + } else if (box->type == BOX_FLEX || box->type == BOX_INLINE_FLEX) { + /* layout_table also expects the containing block to be + * stored in the float_container field */ + box->float_container = containing_block; + if (!layout_flex(box, width, content)) + return false; + box->float_container = NULL; } /* 10.6.4 */ @@ -4917,7 +4947,7 @@ layout_absolute(struct box *box, /** \todo Inline ancestors */ } box->height = height; - layout_apply_minmax_height(&content->len_ctx, box, containing_block); + layout_apply_minmax_height(&content->unit_len_ctx, box, containing_block); return true; } @@ -4943,7 +4973,9 @@ layout_position_absolute(struct box *box, for (c = box->children; c; c = c->next) { if ((c->type == BOX_BLOCK || c->type == BOX_TABLE || - c->type == BOX_INLINE_BLOCK) && + c->type == BOX_INLINE_BLOCK || + c->type == BOX_FLEX || + c->type == BOX_INLINE_FLEX) && (css_computed_position(c->style) == CSS_POSITION_ABSOLUTE || css_computed_position(c->style) == @@ -4994,13 +5026,13 @@ layout_position_absolute(struct box *box, /** * Compute a box's relative offset as per CSS 2.1 9.4.3 * - * \param len_ctx Length conversion context + * \param unit_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, + const css_unit_ctx *unit_len_ctx, struct box *box, int *x, int *y) @@ -5020,7 +5052,7 @@ static void layout_compute_relative_offset( containing_block = box->parent; } - layout_compute_offsets(len_ctx, box, containing_block, + layout_compute_offsets(unit_len_ctx, box, containing_block, &top, &right, &bottom, &left); if (left == AUTO && right == AUTO) @@ -5068,7 +5100,7 @@ static void layout_compute_relative_offset( /** * Adjust positions of relatively positioned boxes. * - * \param len_ctx Length conversion context + * \param unit_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 @@ -5081,7 +5113,7 @@ static void layout_compute_relative_offset( */ static void layout_position_relative( - const nscss_len_ctx *len_ctx, + const css_unit_ctx *unit_len_ctx, struct box *root, struct box *fp, int fx, @@ -5110,7 +5142,7 @@ layout_position_relative( if (box->style && css_computed_position(box->style) == CSS_POSITION_RELATIVE) layout_compute_relative_offset( - len_ctx, box, &x, &y); + unit_len_ctx, box, &x, &y); else x = y = 0; @@ -5146,7 +5178,7 @@ layout_position_relative( } /* recurse first */ - layout_position_relative(len_ctx, box, fn, fnx, fny); + layout_position_relative(unit_len_ctx, box, fn, fnx, fny); /* Ignore things we're not interested in. */ if (!box->style || (box->style && @@ -5175,7 +5207,7 @@ layout_position_relative( /** * Find a box's bounding box relative to itself, i.e. the box's border edge box * - * \param len_ctx Length conversion context + * \param unit_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 @@ -5184,7 +5216,7 @@ layout_position_relative( */ static void layout_get_box_bbox( - const nscss_len_ctx *len_ctx, + const css_unit_ctx *unit_len_ctx, struct box *box, int *desc_x0, int *desc_y0, int *desc_x1, int *desc_y1) @@ -5207,8 +5239,8 @@ layout_get_box_bbox( 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 = css_unit_len2device_px(box->style, unit_len_ctx, + font_size, font_unit); text_height = FIXTOINT(text_height * 3 / 4); *desc_y0 = (*desc_y0 < -text_height) ? *desc_y0 : -text_height; } @@ -5218,7 +5250,7 @@ layout_get_box_bbox( /** * Apply changes to box descendant_[xy][01] values due to given child. * - * \param len_ctx Length conversion context + * \param unit_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 @@ -5226,7 +5258,7 @@ layout_get_box_bbox( */ static void layout_update_descendant_bbox( - const nscss_len_ctx *len_ctx, + const css_unit_ctx *unit_len_ctx, struct box *box, struct box *child, int off_x, @@ -5250,7 +5282,7 @@ layout_update_descendant_bbox( } /* Get child's border edge */ - layout_get_box_bbox(len_ctx, child, + layout_get_box_bbox(unit_len_ctx, child, &child_desc_x0, &child_desc_y0, &child_desc_x1, &child_desc_y1); @@ -5288,11 +5320,11 @@ layout_update_descendant_bbox( * 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 unit_len_ctx Length conversion context * \param box tree of boxes to update */ static void layout_calculate_descendant_bboxes( - const nscss_len_ctx *len_ctx, + const css_unit_ctx *unit_len_ctx, struct box *box) { struct box *child; @@ -5302,7 +5334,7 @@ static void layout_calculate_descendant_bboxes( /* assert((box->width >= 0) && (box->height >= 0)); */ /* Initialise box's descendant box to border edge box */ - layout_get_box_bbox(len_ctx, box, + layout_get_box_bbox(unit_len_ctx, box, &box->descendant_x0, &box->descendant_y0, &box->descendant_x1, &box->descendant_y1); @@ -5336,7 +5368,7 @@ static void layout_calculate_descendant_bboxes( child->type == BOX_FLOAT_RIGHT) continue; - layout_update_descendant_bbox(len_ctx, box, child, + layout_update_descendant_bbox(unit_len_ctx, box, child, box->x, box->y); if (child == box->inline_end) @@ -5354,7 +5386,7 @@ static void layout_calculate_descendant_bboxes( child->type == BOX_FLOAT_RIGHT) continue; - layout_calculate_descendant_bboxes(len_ctx, child); + layout_calculate_descendant_bboxes(unit_len_ctx, child); if (box->style && css_computed_overflow_x(box->style) == CSS_OVERFLOW_HIDDEN && @@ -5362,23 +5394,23 @@ static void layout_calculate_descendant_bboxes( CSS_OVERFLOW_HIDDEN) continue; - layout_update_descendant_bbox(len_ctx, box, child, 0, 0); + layout_update_descendant_bbox(unit_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_calculate_descendant_bboxes(unit_len_ctx, child); - layout_update_descendant_bbox(len_ctx, box, child, 0, 0); + layout_update_descendant_bbox(unit_len_ctx, box, child, 0, 0); } if (box->list_marker) { child = box->list_marker; - layout_calculate_descendant_bboxes(len_ctx, child); + layout_calculate_descendant_bboxes(unit_len_ctx, child); - layout_update_descendant_bbox(len_ctx, box, child, 0, 0); + layout_update_descendant_bbox(unit_len_ctx, box, child, 0, 0); } } @@ -5396,7 +5428,7 @@ bool layout_document(html_content *content, int width, int height) layout_minmax_block(doc, font_func, content); - layout_block_find_dimensions(&content->len_ctx, + layout_block_find_dimensions(&content->unit_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; @@ -5428,11 +5460,11 @@ bool layout_document(html_content *content, int width, int height) doc->children->margin[BOTTOM]); } - layout_lists(doc, font_func, &content->len_ctx); + layout_lists(content, doc); layout_position_absolute(doc, doc, 0, 0, content); - layout_position_relative(&content->len_ctx, doc, doc, 0, 0); + layout_position_relative(&content->unit_len_ctx, doc, doc, 0, 0); - layout_calculate_descendant_bboxes(&content->len_ctx, doc); + layout_calculate_descendant_bboxes(&content->unit_len_ctx, doc); return ret; } diff --git a/content/handlers/html/layout_flex.c b/content/handlers/html/layout_flex.c new file mode 100644 index 000000000..bde3c5bd1 --- /dev/null +++ b/content/handlers/html/layout_flex.c @@ -0,0 +1,1117 @@ +/* + * Copyright 2022 Michael Drake <tlsa@netsurf-browser.org> + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/** + * \file + * HTML layout implementation: display: flex. + * + * 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 <string.h> + +#include "utils/log.h" +#include "utils/utils.h" + +#include "html/box.h" +#include "html/html.h" +#include "html/private.h" +#include "html/box_inspect.h" +#include "html/layout_internal.h" + +/** + * Flex item data + */ +struct flex_item_data { + enum css_flex_basis_e basis; + css_fixed basis_length; + css_unit basis_unit; + struct box *box; + + css_fixed shrink; + css_fixed grow; + + int min_main; + int max_main; + int min_cross; + int max_cross; + + int target_main_size; + int base_size; + int main_size; + size_t line; + + bool freeze; + bool min_violation; + bool max_violation; +}; + +/** + * Flex line data + */ +struct flex_line_data { + int main_size; + int cross_size; + + int used_main_size; + int main_auto_margin_count; + + int pos; + + size_t first; + size_t count; + size_t frozen; +}; + +/** + * Flex layout context + */ +struct flex_ctx { + html_content *content; + const struct box *flex; + const css_unit_ctx *unit_len_ctx; + + int main_size; + int cross_size; + + int available_main; + int available_cross; + + bool horizontal; + bool main_reversed; + enum css_flex_wrap_e wrap; + + struct flex_items { + size_t count; + struct flex_item_data *data; + } item; + + struct flex_lines { + size_t count; + size_t alloc; + struct flex_line_data *data; + } line; +}; + +/** + * Destroy a flex layout context + * + * \param[in] ctx Flex layout context + */ +static void layout_flex_ctx__destroy(struct flex_ctx *ctx) +{ + if (ctx != NULL) { + free(ctx->item.data); + free(ctx->line.data); + free(ctx); + } +} + +/** + * Create a flex layout context + * + * \param[in] content HTML content containing flex box + * \param[in] flex Box to create layout context for + * \return flex layout context or NULL on error + */ +static struct flex_ctx *layout_flex_ctx__create( + html_content *content, + const struct box *flex) +{ + struct flex_ctx *ctx; + + ctx = calloc(1, sizeof(*ctx)); + if (ctx == NULL) { + return NULL; + } + ctx->line.alloc = 1; + + ctx->item.count = box_count_children(flex); + ctx->item.data = calloc(ctx->item.count, sizeof(*ctx->item.data)); + if (ctx->item.data == NULL) { + layout_flex_ctx__destroy(ctx); + return NULL; + } + + ctx->line.alloc = 1; + ctx->line.data = calloc(ctx->line.alloc, sizeof(*ctx->line.data)); + if (ctx->line.data == NULL) { + layout_flex_ctx__destroy(ctx); + return NULL; + } + + ctx->flex = flex; + ctx->content = content; + ctx->unit_len_ctx = &content->unit_len_ctx; + + ctx->wrap = css_computed_flex_wrap(flex->style); + ctx->horizontal = lh__flex_main_is_horizontal(flex); + ctx->main_reversed = lh__flex_direction_reversed(flex); + + return ctx; +} + +/** + * Find box side representing the start of flex container in main direction. + * + * \param[in] ctx Flex layout context. + * \return the start side. + */ +static enum box_side layout_flex__main_start_side( + const struct flex_ctx *ctx) +{ + if (ctx->horizontal) { + return (ctx->main_reversed) ? RIGHT : LEFT; + } else { + return (ctx->main_reversed) ? BOTTOM : TOP; + } +} + +/** + * Find box side representing the end of flex container in main direction. + * + * \param[in] ctx Flex layout context. + * \return the end side. + */ +static enum box_side layout_flex__main_end_side( + const struct flex_ctx *ctx) +{ + if (ctx->horizontal) { + return (ctx->main_reversed) ? LEFT : RIGHT; + } else { + return (ctx->main_reversed) ? TOP : BOTTOM; + } +} + +/** + * Perform layout on a flex item + * + * \param[in] ctx Flex layout context + * \param[in] item Item to lay out + * \param[in] available_width Available width for item in pixels + * \return true on success false on failure + */ +static bool layout_flex_item( + const struct flex_ctx *ctx, + const struct flex_item_data *item, + int available_width) +{ + bool success; + struct box *b = item->box; + + switch (b->type) { + case BOX_BLOCK: + success = layout_block_context(b, -1, ctx->content); + break; + case BOX_TABLE: + b->float_container = b->parent; + success = layout_table(b, available_width, ctx->content); + b->float_container = NULL; + break; + case BOX_FLEX: + b->float_container = b->parent; + success = layout_flex(b, available_width, ctx->content); + b->float_container = NULL; + break; + default: + assert(0 && "Bad flex item back type"); + success = false; + break; + } + + if (!success) { + NSLOG(flex, ERROR, "box %p: layout failed", b); + } + + return success; +} + +/** + * Calculate an item's base and target main sizes. + * + * \param[in] ctx Flex layout context + * \param[in] item Item to get sizes of + * \param[in] available_width Available width in pixels + * \return true on success false on failure + */ +static inline bool layout_flex__base_and_main_sizes( + const struct flex_ctx *ctx, + struct flex_item_data *item, + int available_width) +{ + struct box *b = item->box; + int content_min_width = b->min_width; + int content_max_width = b->max_width; + int delta_outer_main = lh__delta_outer_main(ctx->flex, b); + + NSLOG(flex, DEEPDEBUG, "box %p: delta_outer_main: %i", + b, delta_outer_main); + + if (item->basis == CSS_FLEX_BASIS_SET) { + if (item->basis_unit == CSS_UNIT_PCT) { + item->base_size = FPCT_OF_INT_TOINT( + item->basis_length, + available_width); + } else { + item->base_size = FIXTOINT(css_unit_len2device_px( + b->style, ctx->unit_len_ctx, + item->basis_length, + item->basis_unit)); + } + + } else if (item->basis == CSS_FLEX_BASIS_AUTO) { + item->base_size = ctx->horizontal ? b->width : b->height; + } else { + item->base_size = AUTO; + } + + if (ctx->horizontal == false) { + if (b->width == AUTO) { + b->width = min(max(content_min_width, available_width), + content_max_width); + b->width -= lh__delta_outer_width(b); + } + + if (!layout_flex_item(ctx, item, b->width)) { + return false; + } + } + + if (item->base_size == AUTO) { + if (ctx->horizontal == false) { + item->base_size = b->height; + } else { + item->base_size = content_max_width - delta_outer_main; + } + } + + item->base_size += delta_outer_main; + + if (ctx->horizontal) { + item->base_size = min(item->base_size, available_width); + item->base_size = max(item->base_size, content_min_width); + } + + item->target_main_size = item->base_size; + item->main_size = item->base_size; + + if (item->max_main > 0 && + item->main_size > item->max_main + delta_outer_main) { + item->main_size = item->max_main + delta_outer_main; + } + + if (item->main_size < item->min_main + delta_outer_main) { + item->main_size = item->min_main + delta_outer_main; + } + + NSLOG(flex, DEEPDEBUG, "flex-item box: %p: base_size: %i, main_size %i", + b, item->base_size, item->main_size); + + return true; +} + +/** + * Fill out all item's data in a flex container. + * + * \param[in] ctx Flex layout context + * \param[in] flex Flex box + * \param[in] available_width Available width in pixels + */ +static void layout_flex_ctx__populate_item_data( + const struct flex_ctx *ctx, + const struct box *flex, + int available_width) +{ + size_t i = 0; + bool horizontal = ctx->horizontal; + + for (struct box *b = flex->children; b != NULL; b = b->next) { + struct flex_item_data *item = &ctx->item.data[i++]; + + b->float_container = b->parent; + layout_find_dimensions(ctx->unit_len_ctx, available_width, -1, + b, b->style, &b->width, &b->height, + horizontal ? &item->max_main : &item->max_cross, + horizontal ? &item->min_main : &item->min_cross, + horizontal ? &item->max_cross : &item->max_main, + horizontal ? &item->min_cross : &item->min_main, + b->margin, b->padding, b->border); + b->float_container = NULL; + + NSLOG(flex, DEEPDEBUG, "flex-item box: %p: width: %i", + b, b->width); + + item->box = b; + item->basis = css_computed_flex_basis(b->style, + &item->basis_length, &item->basis_unit); + + css_computed_flex_shrink(b->style, &item->shrink); + css_computed_flex_grow(b->style, &item->grow); + + layout_flex__base_and_main_sizes(ctx, item, available_width); + } +} + +/** + * Ensure context's lines array has a free space + * + * \param[in] ctx Flex layout context + * \return true on success false on out of memory + */ +static bool layout_flex_ctx__ensure_line(struct flex_ctx *ctx) +{ + struct flex_line_data *temp; + size_t line_alloc = ctx->line.alloc * 2; + + if (ctx->line.alloc > ctx->line.count) { + return true; + } + + temp = realloc(ctx->line.data, sizeof(*ctx->line.data) * line_alloc); + if (temp == NULL) { + return false; + } + ctx->line.data = temp; + + memset(ctx->line.data + ctx->line.alloc, 0, + sizeof(*ctx->line.data) * (line_alloc - ctx->line.alloc)); + ctx->line.alloc = line_alloc; + + return true; +} + +/** + * Assigns flex items to the line and returns the line + * + * \param[in] ctx Flex layout context + * \param[in] item_index Index to first item to assign to this line + * \return Pointer to the new line, or NULL on error. + */ +static struct flex_line_data *layout_flex__build_line(struct flex_ctx *ctx, + size_t item_index) +{ + enum box_side start_side = layout_flex__main_start_side(ctx); + enum box_side end_side = layout_flex__main_end_side(ctx); + struct flex_line_data *line; + int used_main = 0; + + if (!layout_flex_ctx__ensure_line(ctx)) { + return NULL; + } + + line = &ctx->line.data[ctx->line.count]; + line->first = item_index; + + NSLOG(flex, DEEPDEBUG, "flex container %p: available main: %i", + ctx->flex, ctx->available_main); + + while (item_index < ctx->item.count) { + struct flex_item_data *item = &ctx->item.data[item_index]; + struct box *b = item->box; + int pos_main; + + pos_main = ctx->horizontal ? + item->main_size : + b->height + lh__delta_outer_main(ctx->flex, b); + + if (ctx->wrap == CSS_FLEX_WRAP_NOWRAP || + pos_main + used_main <= ctx->available_main || + lh__box_is_absolute(item->box) || + ctx->available_main == AUTO || + line->count == 0 || + pos_main == 0) { + if (lh__box_is_absolute(item->box) == false) { + line->main_size += item->main_size; + used_main += pos_main; + + if (b->margin[start_side] == AUTO) { + line->main_auto_margin_count++; + } + if (b->margin[end_side] == AUTO) { + line->main_auto_margin_count++; + } + } + item->line = ctx->line.count; + line->count++; + item_index++; + } else { + break; + } + } + + if (line->count > 0) { + ctx->line.count++; + } else { + NSLOG(layout, ERROR, "Failed to fit any flex items"); + } + + return line; +} + +/** + * Freeze an item on a line + * + * \param[in] line Line to containing item + * \param[in] item Item to freeze + */ +static inline void layout_flex__item_freeze( + struct flex_line_data *line, + struct flex_item_data *item) +{ + item->freeze = true; + line->frozen++; + + if (!lh__box_is_absolute(item->box)){ + line->used_main_size += item->target_main_size; + } + + NSLOG(flex, DEEPDEBUG, "flex-item box: %p: " + "Frozen at target_main_size: %i", + item->box, item->target_main_size); +} + +/** + * Calculate remaining free space and unfrozen item factor sum + * + * \param[in] ctx Flex layout context + * \param[in] line Line to calculate free space on + * \param[out] unfrozen_factor_sum Returns sum of unfrozen item's flex factors + * \param[in] initial_free_main Initial free space in main direction + * \param[in] available_main Available space in main direction + * \param[in] grow Whether to grow or shrink + * return remaining free space on line + */ +static inline int layout_flex__remaining_free_main( + struct flex_ctx *ctx, + struct flex_line_data *line, + css_fixed *unfrozen_factor_sum, + int initial_free_main, + int available_main, + bool grow) +{ + int remaining_free_main = available_main; + size_t item_count = line->first + line->count; + + *unfrozen_factor_sum = 0; + + for (size_t i = line->first; i < item_count; i++) { + struct flex_item_data *item = &ctx->item.data[i]; + + if (item->freeze) { + remaining_free_main -= item->target_main_size; + } else { + remaining_free_main -= item->base_size; + + *unfrozen_factor_sum += grow ? + item->grow : item->shrink; + } + } + + if (*unfrozen_factor_sum < F_1) { + int free_space = FIXTOINT(FMUL(INTTOFIX(initial_free_main), + *unfrozen_factor_sum)); + + if (free_space < remaining_free_main) { + remaining_free_main = free_space; + } + } + + NSLOG(flex, DEEPDEBUG, "Remaining free space: %i", + remaining_free_main); + + return remaining_free_main; +} + +/** + * Clamp flex item target main size and get min/max violations + * + * \param[in] ctx Flex layout context + * \param[in] line Line to align items on + * return total violation in pixels + */ +static inline int layout_flex__get_min_max_violations( + struct flex_ctx *ctx, + struct flex_line_data *line) +{ + + int total_violation = 0; + size_t item_count = line->first + line->count; + + for (size_t i = line->first; i < item_count; i++) { + struct flex_item_data *item = &ctx->item.data[i]; + int target_main_size = item->target_main_size; + + NSLOG(flex, DEEPDEBUG, "item %p: target_main_size: %i", + item->box, target_main_size); + + if (item->freeze) { + continue; + } + + if (item->max_main > 0 && + target_main_size > item->max_main) { + target_main_size = item->max_main; + item->max_violation = true; + NSLOG(flex, DEEPDEBUG, "Violation: max_main: %i", + item->max_main); + } + + if (target_main_size < item->min_main) { + target_main_size = item->min_main; + item->min_violation = true; + NSLOG(flex, DEEPDEBUG, "Violation: min_main: %i", + item->min_main); + } + + if (target_main_size < item->box->min_width) { + target_main_size = item->box->min_width; + item->min_violation = true; + NSLOG(flex, DEEPDEBUG, "Violation: box min_width: %i", + item->box->min_width); + } + + if (target_main_size < 0) { + target_main_size = 0; + item->min_violation = true; + NSLOG(flex, DEEPDEBUG, "Violation: less than 0"); + } + + total_violation += target_main_size - item->target_main_size; + item->target_main_size = target_main_size; + } + + NSLOG(flex, DEEPDEBUG, "Total violation: %i", total_violation); + + return total_violation; +} + +/** + * Distribute remaining free space proportional to the flex factors. + * + * Remaining free space may be negative. + * + * \param[in] ctx Flex layout context + * \param[in] line Line to distribute free space on + * \param[in] unfrozen_factor_sum Sum of unfrozen item's flex factors + * \param[in] remaining_free_main Remaining free space in main direction + * \param[in] grow Whether to grow or shrink + */ +static inline void layout_flex__distribute_free_main( + struct flex_ctx *ctx, + struct flex_line_data *line, + css_fixed unfrozen_factor_sum, + int remaining_free_main, + bool grow) +{ + size_t item_count = line->first + line->count; + + if (grow) { + css_fixed remainder = 0; + for (size_t i = line->first; i < item_count; i++) { + struct flex_item_data *item = &ctx->item.data[i]; + css_fixed result; + css_fixed ratio; + + if (item->freeze) { + continue; + } + + ratio = FDIV(item->grow, unfrozen_factor_sum); + result = FMUL(INTTOFIX(remaining_free_main), ratio) + + remainder; + + item->target_main_size = item->base_size + + FIXTOINT(result); + remainder = FIXFRAC(result); + } + } else { + css_fixed scaled_shrink_factor_sum = 0; + css_fixed remainder = 0; + + for (size_t i = line->first; i < item_count; i++) { + struct flex_item_data *item = &ctx->item.data[i]; + css_fixed scaled_shrink_factor; + + if (item->freeze) { + continue; + } + + scaled_shrink_factor = FMUL( + item->shrink, + INTTOFIX(item->base_size)); + scaled_shrink_factor_sum += scaled_shrink_factor; + } + + for (size_t i = line->first; i < item_count; i++) { + struct flex_item_data *item = &ctx->item.data[i]; + css_fixed scaled_shrink_factor; + css_fixed result; + css_fixed ratio; + + if (item->freeze) { + continue; + } else if (scaled_shrink_factor_sum == 0) { + item->target_main_size = item->main_size; + layout_flex__item_freeze(line, item); + continue; + } + + scaled_shrink_factor = FMUL( + item->shrink, + INTTOFIX(item->base_size)); + ratio = FDIV(scaled_shrink_factor, + scaled_shrink_factor_sum); + result = FMUL(INTTOFIX(abs(remaining_free_main)), + ratio) + remainder; + + item->target_main_size = item->base_size - + FIXTOINT(result); + remainder = FIXFRAC(result); + } + } +} + +/** + * Resolve flexible item lengths along a line. + * + * See 9.7 of Tests CSS Flexible Box Layout Module Level 1. + * + * \param[in] ctx Flex layout context + * \param[in] line Line to resolve + * \return true on success, false on failure. + */ +static bool layout_flex__resolve_line( + struct flex_ctx *ctx, + struct flex_line_data *line) +{ + size_t item_count = line->first + line->count; + int available_main = ctx->available_main; + int initial_free_main; + bool grow; + + if (available_main == AUTO) { + available_main = INT_MAX; + } + + grow = (line->main_size < available_main); + initial_free_main = available_main; + + NSLOG(flex, DEEPDEBUG, "box %p: line %zu: first: %zu, count: %zu", + ctx->flex, line - ctx->line.data, + line->first, line->count); + NSLOG(flex, DEEPDEBUG, "Line main_size: %i, available_main: %i", + line->main_size, available_main); + + for (size_t i = line->first; i < item_count; i++) { + struct flex_item_data *item = &ctx->item.data[i]; + + /* 3. Size inflexible items */ + if (grow) { + if (item->grow == 0 || + item->base_size > item->main_size) { + item->target_main_size = item->main_size; + layout_flex__item_freeze(line, item); + } + } else { + if (item->shrink == 0 || + item->base_size < item->main_size) { + item->target_main_size = item->main_size; + layout_flex__item_freeze(line, item); + } + } + + /* 4. Calculate initial free space */ + if (item->freeze) { + initial_free_main -= item->target_main_size; + } else { + initial_free_main -= item->base_size; + } + } + + /* 5. Loop */ + while (line->frozen < line->count) { + css_fixed unfrozen_factor_sum; + int remaining_free_main; + int total_violation; + + NSLOG(flex, DEEPDEBUG, "flex-container: %p: Resolver pass", + ctx->flex); + + /* b */ + remaining_free_main = layout_flex__remaining_free_main(ctx, + line, &unfrozen_factor_sum, initial_free_main, + available_main, grow); + + /* c */ + if (remaining_free_main != 0) { + layout_flex__distribute_free_main(ctx, + line, unfrozen_factor_sum, + remaining_free_main, grow); + } + + /* d */ + total_violation = layout_flex__get_min_max_violations( + ctx, line); + + /* e */ + for (size_t i = line->first; i < item_count; i++) { + struct flex_item_data *item = &ctx->item.data[i]; + + if (item->freeze) { + continue; + } + + if (total_violation == 0 || + (total_violation > 0 && item->min_violation) || + (total_violation < 0 && item->max_violation)) { + layout_flex__item_freeze(line, item); + } + } + } + + return true; +} + +/** + * Position items along a line + * + * \param[in] ctx Flex layout context + * \param[in] line Line to resolve + * \return true on success, false on failure. + */ +static bool layout_flex__place_line_items_main( + struct flex_ctx *ctx, + struct flex_line_data *line) +{ + int main_pos = ctx->flex->padding[layout_flex__main_start_side(ctx)]; + int post_multiplier = ctx->main_reversed ? 0 : 1; + int pre_multiplier = ctx->main_reversed ? -1 : 0; + size_t item_count = line->first + line->count; + int extra_remainder = 0; + int extra = 0; + + if (ctx->main_reversed) { + main_pos = lh__box_size_main(ctx->horizontal, ctx->flex) - + main_pos; + } + + if (ctx->available_main != AUTO && + ctx->available_main != UNKNOWN_WIDTH && + ctx->available_main > line->used_main_size) { + if (line->main_auto_margin_count > 0) { + extra = ctx->available_main - line->used_main_size; + + extra_remainder = extra % line->main_auto_margin_count; + extra /= line->main_auto_margin_count; + } + } + + for (size_t i = line->first; i < item_count; i++) { + enum box_side main_end = ctx->horizontal ? RIGHT : BOTTOM; + enum box_side main_start = ctx->horizontal ? LEFT : TOP; + struct flex_item_data *item = &ctx->item.data[i]; + struct box *b = item->box; + int extra_total = 0; + int extra_post = 0; + int extra_pre = 0; + int box_size_main; + int *box_pos_main; + + if (ctx->horizontal) { + b->width = item->target_main_size - + lh__delta_outer_width(b); + + if (!layout_flex_item(ctx, item, b->width)) { + return false; + } + } + + box_size_main = lh__box_size_main(ctx->horizontal, b); + box_pos_main = ctx->horizontal ? &b->x : &b->y; + + if (!lh__box_is_absolute(b)) { + if (b->margin[main_start] == AUTO) { + extra_pre = extra + extra_remainder; + } + if (b->margin[main_end] == AUTO) { + extra_post = extra + extra_remainder; + } + extra_total = extra_pre + extra_post; + + main_pos += pre_multiplier * + (extra_total + box_size_main + + lh__delta_outer_main(ctx->flex, b)); + } + + *box_pos_main = main_pos + lh__non_auto_margin(b, main_start) + + extra_pre + b->border[main_start].width; + + if (!lh__box_is_absolute(b)) { + int cross_size; + int box_size_cross = lh__box_size_cross( + ctx->horizontal, b); + + main_pos += post_multiplier * + (extra_total + box_size_main + + lh__delta_outer_main(ctx->flex, b)); + + cross_size = box_size_cross + lh__delta_outer_cross( + ctx->flex, b); + if (line->cross_size < cross_size) { + line->cross_size = cross_size; + } + } + } + + return true; +} + +/** + * Collect items onto lines and place items along the lines + * + * \param[in] ctx Flex layout context + * \return true on success, false on failure. + */ +static bool layout_flex__collect_items_into_lines( + struct flex_ctx *ctx) +{ + size_t pos = 0; + + while (pos < ctx->item.count) { + struct flex_line_data *line; + + line = layout_flex__build_line(ctx, pos); + if (line == NULL) { + return false; + } + + pos += line->count; + + NSLOG(flex, DEEPDEBUG, "flex-container: %p: " + "fitted: %zu (total: %zu/%zu)", + ctx->flex, line->count, + pos, ctx->item.count); + + if (!layout_flex__resolve_line(ctx, line)) { + return false; + } + + if (!layout_flex__place_line_items_main(ctx, line)) { + return false; + } + + ctx->cross_size += line->cross_size; + if (ctx->main_size < line->main_size) { + ctx->main_size = line->main_size; + } + } + + return true; +} + +/** + * Align items on a line. + * + * \param[in] ctx Flex layout context + * \param[in] line Line to align items on + * \param[in] extra Extra line width in pixels + */ +static void layout_flex__place_line_items_cross(struct flex_ctx *ctx, + struct flex_line_data *line, int extra) +{ + enum box_side cross_start = ctx->horizontal ? TOP : LEFT; + size_t item_count = line->first + line->count; + + for (size_t i = line->first; i < item_count; i++) { + struct flex_item_data *item = &ctx->item.data[i]; + struct box *b = item->box; + int cross_free_space; + int *box_size_cross; + int *box_pos_cross; + + box_pos_cross = ctx->horizontal ? &b->y : &b->x; + box_size_cross = lh__box_size_cross_ptr(ctx->horizontal, b); + + cross_free_space = line->cross_size + extra - *box_size_cross - + lh__delta_outer_cross(ctx->flex, b); + + switch (lh__box_align_self(ctx->flex, b)) { + default: + case CSS_ALIGN_SELF_STRETCH: + if (lh__box_size_cross_is_auto(ctx->horizontal, b)) { + *box_size_cross += cross_free_space; + + /* Relayout children for stretch. */ + if (!layout_flex_item(ctx, item, b->width)) { + return; + } + } + fallthrough; + case CSS_ALIGN_SELF_FLEX_START: + *box_pos_cross = ctx->flex->padding[cross_start] + + line->pos + + lh__non_auto_margin(b, cross_start) + + b->border[cross_start].width; + break; + + case CSS_ALIGN_SELF_FLEX_END: + *box_pos_cross = ctx->flex->padding[cross_start] + + line->pos + cross_free_space + + lh__non_auto_margin(b, cross_start) + + b->border[cross_start].width; + break; + + case CSS_ALIGN_SELF_BASELINE: + case CSS_ALIGN_SELF_CENTER: + *box_pos_cross = ctx->flex->padding[cross_start] + + line->pos + cross_free_space / 2 + + lh__non_auto_margin(b, cross_start) + + b->border[cross_start].width; + break; + } + } +} + +/** + * Place the lines and align the items on the line. + * + * \param[in] ctx Flex layout context + */ +static void layout_flex__place_lines(struct flex_ctx *ctx) +{ + bool reversed = ctx->wrap == CSS_FLEX_WRAP_WRAP_REVERSE; + int line_pos = reversed ? ctx->cross_size : 0; + int post_multiplier = reversed ? 0 : 1; + int pre_multiplier = reversed ? -1 : 0; + int extra_remainder = 0; + int extra = 0; + + if (ctx->available_cross != AUTO && + ctx->available_cross > ctx->cross_size && + ctx->line.count > 0) { + extra = ctx->available_cross - ctx->cross_size; + + extra_remainder = extra % ctx->line.count; + extra /= ctx->line.count; + } + + for (size_t i = 0; i < ctx->line.count; i++) { + struct flex_line_data *line = &ctx->line.data[i]; + + line_pos += pre_multiplier * line->cross_size; + line->pos = line_pos; + line_pos += post_multiplier * line->cross_size + + extra + extra_remainder; + + layout_flex__place_line_items_cross(ctx, line, + extra + extra_remainder); + + if (extra_remainder > 0) { + extra_remainder--; + } + } +} + +/** + * Layout a flex container. + * + * \param[in] flex table to layout + * \param[in] available_width width of containing block + * \param[in] content memory pool for any new boxes + * \return true on success, false on memory exhaustion + */ +bool layout_flex(struct box *flex, int available_width, + html_content *content) +{ + int max_height, min_height; + struct flex_ctx *ctx; + bool success = false; + + ctx = layout_flex_ctx__create(content, flex); + if (ctx == NULL) { + return false; + } + + NSLOG(flex, DEEPDEBUG, "box %p: %s, available_width %i, width: %i", + flex, ctx->horizontal ? "horizontal" : "vertical", + available_width, flex->width); + + layout_find_dimensions( + ctx->unit_len_ctx, available_width, -1, + flex, flex->style, NULL, &flex->height, + NULL, NULL, &max_height, &min_height, + flex->margin, flex->padding, flex->border); + + available_width = min(available_width, flex->width); + + if (ctx->horizontal) { + ctx->available_main = available_width; + ctx->available_cross = ctx->flex->height; + } else { + ctx->available_main = ctx->flex->height; + ctx->available_cross = available_width; + } + + NSLOG(flex, DEEPDEBUG, "box %p: available_main: %i", + flex, ctx->available_main); + NSLOG(flex, DEEPDEBUG, "box %p: available_cross: %i", + flex, ctx->available_cross); + + layout_flex_ctx__populate_item_data(ctx, flex, available_width); + + /* Place items onto lines. */ + success = layout_flex__collect_items_into_lines(ctx); + if (!success) { + goto cleanup; + } + + layout_flex__place_lines(ctx); + + if (flex->height == AUTO) { + flex->height = ctx->horizontal ? + ctx->cross_size : + ctx->main_size; + } + + if (flex->height != AUTO) { + if (max_height >= 0 && flex->height > max_height) { + flex->height = max_height; + } + if (min_height > 0 && flex->height < min_height) { + flex->height = min_height; + } + } + + success = true; + +cleanup: + layout_flex_ctx__destroy(ctx); + + NSLOG(flex, DEEPDEBUG, "box %p: %s: w: %i, h: %i", flex, + success ? "success" : "failure", + flex->width, flex->height); + return success; +} diff --git a/content/handlers/html/layout_internal.h b/content/handlers/html/layout_internal.h new file mode 100644 index 000000000..d094462ec --- /dev/null +++ b/content/handlers/html/layout_internal.h @@ -0,0 +1,738 @@ +/* + * Copyright 2003 James Bursa <bursa@users.sourceforge.net> + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/** + * \file + * HTML layout private interface. + */ + +#ifndef NETSURF_HTML_LAYOUT_INTERNAL_H +#define NETSURF_HTML_LAYOUT_INTERNAL_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))) + +/** + * 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. + */ +bool layout_block_context( + struct box *block, + int viewport_height, + html_content *content); + +/** + * 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 + */ +bool layout_table( + struct box *table, + int available_width, + html_content *content); + +/** + * Layout a flex container. + * + * \param[in] flex table to layout + * \param[in] available_width width of containing block + * \param[in] content memory pool for any new boxes + * \return true on success, false on memory exhaustion + */ +bool layout_flex( + struct box *flex, + int available_width, + html_content *content); + +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. */ +extern const css_len_func margin_funcs[4]; + +/** Array of per-side access functions for computed style paddings. */ +extern const css_len_func padding_funcs[4]; + +/** Array of per-side access functions for computed style border_widths. */ +extern const css_len_func border_width_funcs[4]; + +/** Array of per-side access functions for computed style border styles. */ +extern const css_border_style_func border_style_funcs[4]; + +/** Array of per-side access functions for computed style border colors. */ +extern const css_border_color_func border_color_funcs[4]; + +/** Layout helper: Check whether box is a float. */ +static inline bool lh__box_is_float_box(const struct box *b) +{ + return b->type == BOX_FLOAT_LEFT || + b->type == BOX_FLOAT_RIGHT; +} + +/** Layout helper: Check whether box takes part in inline flow. */ +static inline bool lh__box_is_inline_flow(const struct box *b) +{ + return b->type == BOX_INLINE || + b->type == BOX_INLINE_FLEX || + b->type == BOX_INLINE_BLOCK || + b->type == BOX_TEXT || + b->type == BOX_INLINE_END; +} + +/** Layout helper: Check whether box takes part in inline flow. */ +static inline bool lh__box_is_flex_container(const struct box *b) +{ + return b->type == BOX_FLEX || + b->type == BOX_INLINE_FLEX; +} + +/** Layout helper: Check whether box takes part in inline flow. */ +static inline bool lh__box_is_flex_item(const struct box *b) +{ + return (b->parent != NULL) && lh__box_is_flex_container(b->parent); +} + +/** Layout helper: Check whether box is inline level. (Includes BR.) */ +static inline bool lh__box_is_inline_level(const struct box *b) +{ + return lh__box_is_inline_flow(b) || + b->type == BOX_BR; +} + +/** Layout helper: Check whether box is inline level. (Includes BR, floats.) */ +static inline bool lh__box_is_inline_content(const struct box *b) +{ + return lh__box_is_float_box(b) || + lh__box_is_inline_level(b); +} + +/** Layout helper: Check whether box is an object. */ +static inline bool lh__box_is_object(const struct box *b) +{ + return b->object || + (b->flags & (IFRAME | REPLACE_DIM)); +} + +/** Layout helper: Check whether box is replaced. */ +static inline bool lh__box_is_replace(const struct box *b) +{ + return b->gadget || + lh__box_is_object(b); +} + +/** Layout helper: Check for CSS border on given side. */ +static inline bool lh__have_border( + enum box_side side, + const css_computed_style *style) +{ + return border_style_funcs[side](style) != CSS_BORDER_STYLE_NONE; +} + +static inline bool lh__box_is_absolute(const struct box *b) +{ + return css_computed_position(b->style) == CSS_POSITION_ABSOLUTE || + css_computed_position(b->style) == CSS_POSITION_FIXED; +} + +static inline bool lh__flex_main_is_horizontal(const struct box *flex) +{ + const css_computed_style *style = flex->style; + + assert(style != NULL); + + switch (css_computed_flex_direction(style)) { + default: /* Fallthrough. */ + case CSS_FLEX_DIRECTION_ROW: /* Fallthrough. */ + case CSS_FLEX_DIRECTION_ROW_REVERSE: + return true; + case CSS_FLEX_DIRECTION_COLUMN: /* Fallthrough. */ + case CSS_FLEX_DIRECTION_COLUMN_REVERSE: + return false; + } +} + +static inline bool lh__flex_direction_reversed(const struct box *flex) +{ + switch (css_computed_flex_direction(flex->style)) { + default: /* Fallthrough. */ + case CSS_FLEX_DIRECTION_ROW_REVERSE: /* Fallthrough. */ + case CSS_FLEX_DIRECTION_COLUMN_REVERSE: + return true; + case CSS_FLEX_DIRECTION_ROW: /* Fallthrough. */ + case CSS_FLEX_DIRECTION_COLUMN: + return false; + } +} + +static inline int lh__non_auto_margin(const struct box *b, enum box_side side) +{ + return (b->margin[side] == AUTO) ? 0 : b->margin[side]; +} + +static inline int lh__delta_outer_height(const struct box *b) +{ + return b->padding[TOP] + + b->padding[BOTTOM] + + b->border[TOP].width + + b->border[BOTTOM].width + + lh__non_auto_margin(b, TOP) + + lh__non_auto_margin(b, BOTTOM); +} + +static inline int lh__delta_outer_width(const struct box *b) +{ + return b->padding[LEFT] + + b->padding[RIGHT] + + b->border[LEFT].width + + b->border[RIGHT].width + + lh__non_auto_margin(b, LEFT) + + lh__non_auto_margin(b, RIGHT); +} + +static inline int lh__delta_outer_main( + const struct box *flex, + const struct box *b) +{ + if (lh__flex_main_is_horizontal(flex)) { + return lh__delta_outer_width(b); + } else { + return lh__delta_outer_height(b); + } +} + +static inline int lh__delta_outer_cross( + const struct box *flex, + const struct box *b) +{ + if (lh__flex_main_is_horizontal(flex) == false) { + return lh__delta_outer_width(b); + } else { + return lh__delta_outer_height(b); + } +} + +static inline int *lh__box_size_main_ptr( + bool horizontal, + struct box *b) +{ + return horizontal ? &b->width : &b->height; +} + +static inline int *lh__box_size_cross_ptr( + bool horizontal, + struct box *b) +{ + return horizontal ? &b->height : &b->width; +} + +static inline int lh__box_size_main( + bool horizontal, + const struct box *b) +{ + return horizontal ? b->width : b->height; +} + +static inline int lh__box_size_cross( + bool horizontal, + const struct box *b) +{ + return horizontal ? b->height : b->width; +} + +static inline bool lh__box_size_cross_is_auto( + bool horizontal, + struct box *b) +{ + css_fixed length; + css_unit unit; + + if (horizontal) { + return css_computed_height(b->style, + &length, &unit) == CSS_HEIGHT_AUTO; + } else { + return css_computed_width(b->style, + &length, &unit) == CSS_WIDTH_AUTO; + } +} + +static inline enum css_align_self_e lh__box_align_self( + const struct box *flex, + const struct box *item) +{ + enum css_align_self_e align_self = css_computed_align_self(item->style); + + if (align_self == CSS_ALIGN_SELF_AUTO) { + align_self = css_computed_align_items(flex->style); + } + + return align_self; +} + +/** + * Determine width of margin, borders, and padding on one side of a box. + * + * \param unit_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 inline void calculate_mbp_width( + const css_unit_ctx *unit_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 += FIXTOFLT(FDIV(value, F_100)); + } else { + *fixed += FIXTOINT(css_unit_len2device_px( + style, unit_len_ctx, + value, unit)); + } + } + } + + /* border */ + if (border) { + if (lh__have_border(side, style)) { + border_width_funcs[side](style, &value, &unit); + + *fixed += FIXTOINT(css_unit_len2device_px( + style, unit_len_ctx, + value, unit)); + } + } + + /* padding */ + if (padding) { + padding_funcs[side](style, &value, &unit); + if (unit == CSS_UNIT_PCT) { + *frac += FIXTOFLT(FDIV(value, F_100)); + } else { + *fixed += FIXTOINT(css_unit_len2device_px( + style, unit_len_ctx, + value, unit)); + } + } +} + +/** + * Adjust a specified width or height for the box-sizing property. + * + * This turns the specified dimension into a content-box dimension. + * + * \param unit_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 inline void layout_handle_box_sizing( + const css_unit_ctx *unit_len_ctx, + const 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(unit_len_ctx, box->style, + setwidth ? LEFT : TOP, + false, true, true, &fixed, &frac); + calculate_mbp_width(unit_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 unit_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 inline void layout_find_dimensions( + const css_unit_ctx *unit_len_ctx, + int available_width, + int viewport_height, + const 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(css_unit_len2device_px( + style, unit_len_ctx, + value, unit)); + } + } else { + *width = AUTO; + } + + if (*width != AUTO) { + layout_handle_box_sizing(unit_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(css_unit_len2device_px( + style, unit_len_ctx, + value, unit)); + } + } else { + *height = AUTO; + } + + if (*height != AUTO) { + layout_handle_box_sizing(unit_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(css_unit_len2device_px( + style, unit_len_ctx, + value, unit)); + } + } else { + /* Inadmissible */ + *max_width = -1; + } + + if (*max_width != -1) { + layout_handle_box_sizing(unit_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(css_unit_len2device_px( + style, unit_len_ctx, + value, unit)); + } + } else { + /* Inadmissible */ + *min_width = 0; + } + + if (*min_width != 0) { + layout_handle_box_sizing(unit_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(css_unit_len2device_px( + style, unit_len_ctx, + value, unit)); + } + } 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(css_unit_len2device_px( + style, unit_len_ctx, + value, unit)); + } + } 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(css_unit_len2device_px( + style, unit_len_ctx, + value, unit)); + } + } 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(css_unit_len2device_px( + style, unit_len_ctx, + value, unit)); + } + } + + /* 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(css_unit_len2device_px( + style, unit_len_ctx, + value, unit)); + + /* 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; + } + } +} + +#endif diff --git a/content/handlers/html/html_object.c b/content/handlers/html/object.c index 1dc8e645d..a1f020bf7 100644 --- a/content/handlers/html/html_object.c +++ b/content/handlers/html/object.c @@ -41,8 +41,11 @@ #include "desktop/gui_internal.h" #include "html/html.h" +#include "html/private.h" +#include "html/interaction.h" #include "html/box.h" -#include "html/html_internal.h" +#include "html/box_inspect.h" +#include "html/object.h" /* break reference loop */ static void html_object_refresh(void *p); @@ -122,10 +125,35 @@ html_object_done(struct box *box, } } + /** - * Callback for hlcache_handle_retrieve() for objects. + * Callback for hlcache_handle_retrieve() for objects with no box. */ +static nserror +html_object_nobox_callback(hlcache_handle *object, + const hlcache_event *event, + void *pw) +{ + struct content_html_object *chobject = pw; + + switch (event->type) { + case CONTENT_MSG_ERROR: + hlcache_handle_release(object); + + chobject->content = NULL; + break; + + default: + break; + } + return NSERROR_OK; +} + + +/** + * Callback for hlcache_handle_retrieve() for objects with a box. + */ static nserror html_object_callback(hlcache_handle *object, const hlcache_event *event, @@ -137,11 +165,6 @@ html_object_callback(hlcache_handle *object, 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: @@ -180,6 +203,10 @@ html_object_callback(hlcache_handle *object, box->flags & REPLACE_DIM) { union content_msg_data data; + if (c->had_initial_layout == false) { + break; + } + if (!box_visible(box)) break; @@ -189,32 +216,31 @@ html_object_callback(hlcache_handle *object, 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); + c->base.active--; + NSLOG(netsurf, INFO, "%d fetches active", c->base.active); + + html_object_failed(box, c, o->background); - 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 (c->had_initial_layout == false) { + break; + } + if (!box_visible(box)) break; @@ -239,18 +265,20 @@ html_object_callback(hlcache_handle *object, if (hunit == CSS_UNIT_PCT) { l = (width - w) * hpos / INTTOFIX(100); } else { - l = FIXTOINT(nscss_len2px(&c->len_ctx, - hpos, hunit, - box->style)); + l = FIXTOINT(css_unit_len2device_px( + box->style, + &c->unit_len_ctx, + hpos, hunit)); } 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)); + t = FIXTOINT(css_unit_len2device_px( + box->style, + &c->unit_len_ctx, + vpos, vunit)); } /* Redraw area depends on background-repeat */ @@ -284,53 +312,39 @@ html_object_callback(hlcache_handle *object, 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 * + int w = content_get_width(object); + int h = content_get_height(object); + + if (w != 0 && box->width != w) { + /* Not showing image at intrinsic + * width; need to scale the redraw + * request area. */ + data.redraw.x = data.redraw.x * box->width / w; - data.redraw.width = + data.redraw.width = data.redraw.width * box->width / w; - } + } - if (h != 0) { - data.redraw.y = - data.redraw.y * + if (h != 0 && box->height != w) { + /* Not showing image at intrinsic + * height; need to scale the redraw + * request area. */ + data.redraw.y = data.redraw.y * box->height / h; - data.redraw.height = + 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); @@ -350,8 +364,9 @@ html_object_callback(hlcache_handle *object, /* Don't care about favicons that aren't on top level content */ break; - case CONTENT_MSG_GETCTX: - *(event->data.jscontext) = NULL; + case CONTENT_MSG_GETTHREAD: + /* Objects don't have JS threads */ + *(event->data.jsthread) = NULL; break; case CONTENT_MSG_GETDIMS: @@ -462,8 +477,7 @@ html_object_callback(hlcache_handle *object, c->base.active == 0 && (event->type == CONTENT_MSG_LOADING || event->type == CONTENT_MSG_DONE || - event->type == CONTENT_MSG_ERROR || - event->type == CONTENT_MSG_ERRORCODE)) { + event->type == CONTENT_MSG_ERROR)) { /* all objects have arrived */ content__reformat(&c->base, false, c->base.available_width, c->base.available_height); @@ -498,6 +512,7 @@ html_object_callback(hlcache_handle *object, return NSERROR_OK; } + /** * Start a fetch for an object required by a page, replacing an existing object. * @@ -505,7 +520,6 @@ html_object_callback(hlcache_handle *object, * \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; @@ -558,7 +572,6 @@ static bool html_replace_object(struct content_html_object *object, nsurl *url) /** * schedule callback for object refresh */ - static void html_object_refresh(void *p) { struct content_html_object *object = p; @@ -580,6 +593,8 @@ static void html_object_refresh(void *p) } } + +/* exported interface documented in html/object.h */ nserror html_object_open_objects(html_content *html, struct browser_window *bw) { struct content_html_object *object, *next; @@ -601,6 +616,8 @@ nserror html_object_open_objects(html_content *html, struct browser_window *bw) return NSERROR_OK; } + +/* exported interface documented in html/object.h */ nserror html_object_abort_objects(html_content *htmlc) { struct content_html_object *object; @@ -641,6 +658,8 @@ nserror html_object_abort_objects(html_content *htmlc) return NSERROR_OK; } + +/* exported interface documented in html/object.h */ nserror html_object_close_objects(html_content *html) { struct content_html_object *object, *next; @@ -663,6 +682,8 @@ nserror html_object_close_objects(html_content *html) return NSERROR_OK; } + +/* exported interface documented in html/object.h */ nserror html_object_free_objects(html_content *html) { while (html->object_list != NULL) { @@ -684,14 +705,16 @@ nserror html_object_free_objects(html_content *html) } - -/* 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) +/* exported interface documented in html/object.h */ +bool +html_fetch_object(html_content *c, + nsurl *url, + struct box *box, + content_type permitted_types, + bool background) { struct content_html_object *object; + hlcache_handle_callback object_callback; hlcache_child_context child; nserror error; @@ -707,6 +730,12 @@ bool html_fetch_object(html_content *c, nsurl *url, struct box *box, return false; } + if (box == NULL) { + object_callback = html_object_nobox_callback; + } else { + object_callback = html_object_callback; + } + object->parent = (struct content *) c; object->next = NULL; object->content = NULL; @@ -715,10 +744,14 @@ bool html_fetch_object(html_content *c, nsurl *url, struct box *box, 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); + HLCACHE_RETRIEVE_SNIFF_TYPE, + content_get_url(&c->base), + NULL, + object_callback, + object, + &child, + object->permitted_types, + &object->content); if (error != NSERROR_OK) { free(object); return error != NSERROR_NOMEM; diff --git a/content/handlers/html/object.h b/content/handlers/html/object.h new file mode 100644 index 000000000..67d770c9e --- /dev/null +++ b/content/handlers/html/object.h @@ -0,0 +1,86 @@ +/* + * Copyright 2020 Vincent Sanders <vince@netsurf-browser.org> + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/** + * \file + * HTML content object interface + */ + +#ifndef NETSURF_HTML_OBJECT_H +#define NETSURF_HTML_OBJECT_H + +struct html_content; +struct browser_window; +struct box; +struct nsurl; + +/** + * Start a fetch for an object required by a page. + * + * The created content object is added to the HTML content which is + * updated as the fetch progresses. The box (if any) is updated when + * the object content becomes done. + * + * \param c content of type CONTENT_HTML + * \param url URL of object to fetch + * \param box box that will contain the object or NULL if none + * \param permitted_types bitmap of acceptable types + * \param background this is a background image + * \return true on success, false on memory exhaustion + */ +bool html_fetch_object(struct html_content *c, struct nsurl *url, struct box *box, content_type permitted_types, bool background); + +/** + * release memory of content objects associated with a HTML content + * + * The content objects contents should have been previously closed + * with html_object_close_objects(). + * + * \param html The html content to release the objects from. + * \return NSERROR_OK on success else appropriate error code. + */ +nserror html_object_free_objects(struct html_content *html); + +/** + * close content of content objects associated with a HTML content + * + * \param html The html content to close the objects from. + * \return NSERROR_OK on success else appropriate error code. + */ +nserror html_object_close_objects(struct html_content *html); + + +/** + * open content of content objects associated with a HTML content + * + * \param html The html content to open the objects from. + * \param bw Browser window handle to open contents with. + * \return NSERROR_OK on success else appropriate error code. + */ +nserror html_object_open_objects(struct html_content *html, struct browser_window *bw); + + +/** + * abort any content objects that have not completed fetching. + * + * \param html The html content to abort the objects from. + * \return NSERROR_OK on success else appropriate error code. + */ +nserror html_object_abort_objects(struct html_content *html); + +#endif diff --git a/content/handlers/html/html_internal.h b/content/handlers/html/private.h index 2ba96f0f8..56cd957d5 100644 --- a/content/handlers/html/html_internal.h +++ b/content/handlers/html/private.h @@ -21,18 +21,20 @@ * Private data for text/html content. */ -#ifndef NETSURF_HTML_HTML_INTERNAL_H -#define NETSURF_HTML_HTML_INTERNAL_H +#ifndef NETSURF_HTML_PRIVATE_H +#define NETSURF_HTML_PRIVATE_H -#include <libcss/libcss.h> #include <dom/bindings/hubbub/parser.h> #include "netsurf/types.h" #include "content/content_protected.h" -#include "desktop/selection.h" +#include "content/handlers/css/utils.h" + struct gui_layout_table; struct scrollbar_msg_data; +struct content_redraw_data; +struct selection; typedef enum { HTML_DRAG_NONE, /** No drag */ @@ -85,11 +87,6 @@ union html_focus_owner { struct box *content; }; -struct html_scrollbar_data { - struct content *c; - struct box *box; -}; - /** * Data specific to CONTENT_HTML. */ @@ -111,13 +108,10 @@ typedef struct html_content { dom_hubbub_encoding_source encoding_source; /** Base URL (may be a copy of content->url). */ - nsurl *base_url; + struct 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; @@ -127,6 +121,9 @@ typedef struct html_content { /** Whether a layout (reflow) is in progress */ bool reflowing; + /** Whether an initial layout has been done */ + bool had_initial_layout; + /** Whether scripts are enabled for this content */ bool enable_scripting; @@ -135,6 +132,10 @@ typedef struct html_content { /** A talloc context purely for the render box tree */ int *bctx; + /** A context pointer for the box conversion, NULL if no conversion + * is in progress. + */ + void *box_conversion_context; /** Box tree, or NULL. */ struct box *layout; /** Document background colour. */ @@ -147,8 +148,8 @@ typedef struct html_content { unsigned int scripts_count; /** Scripts */ struct html_script *scripts; - /** javascript context */ - struct jscontext *jscontext; + /** javascript thread in use */ + struct jsthread *jsthread; /** Number of entries in stylesheet_content. */ unsigned int stylesheet_count; @@ -158,6 +159,8 @@ typedef struct html_content { css_select_ctx *select_ctx; /**< Style selection media specification */ css_media media; + /** CSS length conversion context for document. */ + css_unit_ctx unit_len_ctx; /**< Universal selector */ lwc_string *universal; @@ -199,65 +202,33 @@ typedef struct html_content { union html_focus_owner focus_owner; /** HTML content's own text selection object */ - struct selection sel; + struct selection *sel; - /** Open core-handled form SELECT menu, - * or NULL if none currently open. */ + /** + * 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 + * Render padding and margin box outlines in html_redraw(). */ -void html_set_drag_type(html_content *html, html_drag_type drag_type, - union html_drag_owner drag_owner, const struct rect *rect); +extern bool html_redraw_debug; -/** - * 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); + +/* in html/html.c */ /** - * Set our input focus, and inform whatever owns the content + * redraw a box * - * \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 + * \param htmlc HTML content + * \param box The box to redraw. */ -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); +void html__redraw_a_box(html_content *htmlc, struct box *box); -struct browser_window *html_get_browser_window(struct content *c); - /** * Complete conversion of an HTML document * @@ -265,6 +236,7 @@ struct browser_window *html_get_browser_window(struct content *c); */ void html_finish_conversion(html_content *htmlc); + /** * Test if an HTML content conversion can begin * @@ -273,6 +245,7 @@ void html_finish_conversion(html_content *htmlc); */ bool html_can_begin_conversion(html_content *htmlc); + /** * Begin conversion of an HTML document * @@ -280,34 +253,12 @@ bool html_can_begin_conversion(html_content *htmlc); */ 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); +/** + * execute some text as a script element + */ +bool html_exec(struct content *c, const char *src, size_t srclen); -/* in html/html_script.c */ -dom_hubbub_error html_process_script(void *ctx, dom_node *node); /** * Attempt script execution for defer and async scripts @@ -321,6 +272,7 @@ dom_hubbub_error html_process_script(void *ctx, dom_node *node); */ nserror html_script_exec(html_content *htmlc, bool allow_defer); + /** * Free all script resources and references for a html content. * @@ -329,82 +281,70 @@ nserror html_script_exec(html_content *htmlc, bool allow_defer); */ 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. + * Check if any of the scripts loaded were insecure */ -nserror html_script_invalidate_ctx(html_content *htmlc); +bool html_saw_insecure_scripts(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 + * Complete the HTML content state machine *iff* all scripts are finished */ -nserror html_css_new_stylesheets(html_content *c); -nserror html_css_quirks_stylesheets(html_content *c); -nserror html_css_free_stylesheets(html_content *html); +nserror html_proceed_to_done(html_content *html); + + +/* in 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/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); -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/script.c */ +dom_hubbub_error html_process_script(void *ctx, dom_node *node); + + +/* in 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_fetcher.c */ +/* in 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, +nserror html_css_fetcher_add_item(dom_string *data, struct 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 fire_generic_dom_event(dom_string *type, dom_node *target, bool bubbles, bool cancelable); +/** + * Construct a keyboard event and fire it at the DOM + */ +bool fire_dom_keyboard_event(dom_string *type, dom_node *target, + bool bubbles, bool cancelable, uint32_t key); + /* Useful dom_string pointers */ struct dom_string; diff --git a/content/handlers/html/html_redraw.c b/content/handlers/html/redraw.c index d05df8753..f770699bb 100644 --- a/content/handlers/html/html_redraw.c +++ b/content/handlers/html/redraw.c @@ -40,12 +40,15 @@ #include "utils/messages.h" #include "utils/utils.h" #include "utils/nsoption.h" +#include "utils/corestrings.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.h" #include "content/content_protected.h" +#include "content/textsearch.h" #include "css/utils.h" #include "desktop/selection.h" #include "desktop/print.h" @@ -54,11 +57,12 @@ #include "desktop/gui_internal.h" #include "html/box.h" +#include "html/box_inspect.h" +#include "html/box_manipulate.h" #include "html/font.h" #include "html/form_internal.h" -#include "html/html_internal.h" +#include "html/private.h" #include "html/layout.h" -#include "html/search.h" bool html_redraw_debug = false; @@ -164,7 +168,6 @@ text_redraw(const char *utf8_text, bool excluded, struct content *c, const struct selection *sel, - struct search_context *search, const struct redraw_context *ctx) { bool highlighted = false; @@ -181,18 +184,22 @@ text_redraw(const char *utf8_text, 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)) { + if (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)) { + if (!highlighted && + (c->textsearch.context != NULL) && + content_textsearch_ishighlighted(c->textsearch.context, + offset, + offset + len, + &start_idx, + &end_idx)) { highlighted = true; } @@ -521,14 +528,14 @@ static bool html_redraw_radio(int x, int y, int width, int height, * \param box box of input * \param scale scale for redraw * \param background_colour current background colour - * \param len_ctx Length conversion context + * \param unit_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 css_unit_ctx *unit_len_ctx, const struct redraw_context *ctx) { int text_width; @@ -537,7 +544,7 @@ static bool html_redraw_file(int x, int y, int width, int height, plot_font_style_t fstyle; nserror res; - font_plot_style_from_css(len_ctx, box->style, &fstyle); + font_plot_style_from_css(unit_len_ctx, box->style, &fstyle); fstyle.background = background_colour; if (box->gadget->value) { @@ -580,7 +587,7 @@ static bool html_redraw_file(int x, int y, int width, int height, * \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 unit_len_ctx Length conversion context * \param ctx current redraw context * \return true if successful, false otherwise */ @@ -588,7 +595,7 @@ static bool html_redraw_file(int x, int y, int width, int height, 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 css_unit_ctx *unit_len_ctx, const struct redraw_context *ctx) { bool repeat_x = false; @@ -665,8 +672,9 @@ static bool html_redraw_background(int x, int y, struct box *box, float scale, content_get_width(background->background)) * scale * FIXTOFLT(hpos) / 100.; } else { - x += (int) (FIXTOFLT(nscss_len2px(len_ctx, hpos, hunit, - background->style)) * scale); + x += (int) (FIXTOFLT(css_unit_len2device_px( + background->style, unit_len_ctx, + hpos, hunit)) * scale); } if (vunit == CSS_UNIT_PCT) { @@ -674,8 +682,9 @@ static bool html_redraw_background(int x, int y, struct box *box, float scale, content_get_height(background->background)) * scale * FIXTOFLT(vpos) / 100.; } else { - y += (int) (FIXTOFLT(nscss_len2px(len_ctx, vpos, vunit, - background->style)) * scale); + y += (int) (FIXTOFLT(css_unit_len2device_px( + background->style, unit_len_ctx, + vpos, vunit)) * scale); } } @@ -807,7 +816,7 @@ static bool html_redraw_background(int x, int y, struct box *box, float scale, * \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 unit_len_ctx Length conversion context * \param ctx current redraw context * \return true if successful, false otherwise */ @@ -815,7 +824,7 @@ static bool html_redraw_background(int x, int y, struct box *box, float scale, 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 css_unit_ctx *unit_len_ctx, const struct redraw_context *ctx) { struct rect r = *clip; @@ -876,8 +885,9 @@ static bool html_redraw_inline_background(int x, int y, struct box *box, plot_content = false; } } else { - x += (int) (FIXTOFLT(nscss_len2px(len_ctx, hpos, hunit, - box->style)) * scale); + x += (int) (FIXTOFLT(css_unit_len2device_px( + box->style, unit_len_ctx, + hpos, hunit)) * scale); } if (vunit == CSS_UNIT_PCT) { @@ -885,8 +895,9 @@ static bool html_redraw_inline_background(int x, int y, struct box *box, content_get_height(box->background) * scale) * FIXTOFLT(vpos) / 100.; } else { - y += (int) (FIXTOFLT(nscss_len2px(len_ctx, vpos, vunit, - box->style)) * scale); + y += (int) (FIXTOFLT(css_unit_len2device_px( + box->style, unit_len_ctx, + vpos, vunit)) * scale); } } @@ -1127,14 +1138,22 @@ static bool html_redraw_text_box(const html_content *html, struct box *box, bool excluded = (box->object != NULL); plot_font_style_t fstyle; - font_plot_style_from_css(&html->len_ctx, box->style, &fstyle); + font_plot_style_from_css(&html->unit_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)) + 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, + ctx)) return false; return true; @@ -1224,10 +1243,12 @@ bool html_redraw_box(const html_content *html, struct box *box, 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; + dom_exception exc; + dom_html_element_type tag_type; + if (html_redraw_printing && (box->flags & PRINTED)) return true; @@ -1388,28 +1409,24 @@ bool html_redraw_box(const html_content *html, struct box *box, 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)); + r.x0 = x - border_left + FIXTOINT(css_unit_len2device_px( + box->style, &html->unit_len_ctx, + css_rect.left, css_rect.lunit)); 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)); + r.y0 = y - border_top + FIXTOINT(css_unit_len2device_px( + box->style, &html->unit_len_ctx, + css_rect.top, css_rect.tunit)); 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)); + r.x1 = x - border_left + FIXTOINT(css_unit_len2device_px( + box->style, &html->unit_len_ctx, + css_rect.right, css_rect.runit)); 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)); + r.y1 = y - border_top + FIXTOINT(css_unit_len2device_px( + box->style, &html->unit_len_ctx, + css_rect.bottom, css_rect.bunit)); /* find intersection of clip rectangle and box */ if (r.x0 < clip->x0) r.x0 = clip->x0; @@ -1498,7 +1515,7 @@ bool html_redraw_box(const html_content *html, struct box *box, /* plot background */ if (!html_redraw_background(x, y, box, scale, &p, ¤t_background_color, bg_box, - &html->len_ctx, ctx)) + &html->unit_len_ctx, ctx)) return false; /* restore previous graphics window */ if (ctx->plot->clip(ctx, &r) != NSERROR_OK) @@ -1578,7 +1595,7 @@ bool html_redraw_box(const html_content *html, struct box *box, x, y, box, scale, &p, b, first, false, ¤t_background_color, - &html->len_ctx, ctx)) + &html->unit_len_ctx, ctx)) return false; /* restore previous graphics window */ if (ctx->plot->clip(ctx, &r) != NSERROR_OK) @@ -1611,7 +1628,7 @@ bool html_redraw_box(const html_content *html, struct box *box, * the inline */ if (!html_redraw_inline_background(x, ib_y, box, scale, &p, b, first, true, ¤t_background_color, - &html->len_ctx, ctx)) + &html->unit_len_ctx, ctx)) return false; /* restore previous graphics window */ if (ctx->plot->clip(ctx, &r) != NSERROR_OK) @@ -1729,6 +1746,15 @@ bool html_redraw_box(const html_content *html, struct box *box, return false; } + if (box->node != NULL) { + exc = dom_html_element_get_tag_type(box->node, &tag_type); + if (exc != DOM_NO_ERR) { + tag_type = DOM_HTML_ELEMENT_TYPE__UNKNOWN; + } + } else { + tag_type = DOM_HTML_ELEMENT_TYPE__UNKNOWN; + } + if (box->object && width != 0 && height != 0) { struct content_redraw_data obj_data; @@ -1782,12 +1808,27 @@ bool html_redraw_box(const html_content *html, struct box *box, obj, sizeof(obj) - 1) != NSERROR_OK) return false; } - + } else if (tag_type == DOM_HTML_ELEMENT_TYPE_CANVAS && + box->node != NULL && + box->flags & REPLACE_DIM) { + /* Canvas to draw */ + struct bitmap *bitmap = NULL; + exc = dom_node_get_user_data(box->node, + corestring_dom___ns_key_canvas_node_data, + &bitmap); + if (exc != DOM_NO_ERR) { + bitmap = NULL; + } + if (bitmap != NULL && + ctx->plot->bitmap(ctx, bitmap, x + padding_left, y + padding_top, + width, height, current_background_color, + BITMAPF_NONE) != 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); + x + padding_left, + y + padding_top, &r, ctx); } else if (box->gadget && box->gadget->type == GADGET_CHECKBOX) { if (!html_redraw_checkbox(x + padding_left, y + padding_top, @@ -1802,7 +1843,7 @@ bool html_redraw_box(const html_content *html, struct box *box, } 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)) + current_background_color, &html->unit_len_ctx, ctx)) return false; } else if (box->gadget && @@ -1841,20 +1882,29 @@ bool html_redraw_box(const html_content *html, struct box *box, /* 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)) + box->type != BOX_TABLE && box->type != BOX_INLINE && + (box->gadget == NULL || box->gadget->type != GADGET_TEXTAREA) && + (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) { + nserror res; + bool has_x_scroll = (overflow_x == CSS_OVERFLOW_SCROLL); + bool has_y_scroll = (overflow_y == CSS_OVERFLOW_SCROLL); + + has_x_scroll |= (overflow_x == CSS_OVERFLOW_AUTO) && + box_hscrollbar_present(box); + has_y_scroll |= (overflow_y == CSS_OVERFLOW_AUTO) && + box_vscrollbar_present(box); + + res = box_handle_scrollbars((struct content *)html, + box, has_x_scroll, has_y_scroll); + if (res != NSERROR_OK) { + NSLOG(netsurf, INFO, "%s", messages_get_errorcode(res)); return false; + } if (box->scroll_x != NULL) scrollbar_redraw(box->scroll_x, diff --git a/content/handlers/html/html_redraw_border.c b/content/handlers/html/redraw_border.c index 0b3d858e6..3a1f6f308 100644 --- a/content/handlers/html/html_redraw_border.c +++ b/content/handlers/html/redraw_border.c @@ -25,12 +25,13 @@ #include <stdbool.h> #include <stdlib.h> +#include "utils/utils.h" #include "utils/log.h" #include "netsurf/plotters.h" #include "netsurf/css.h" #include "html/box.h" -#include "html/html_internal.h" +#include "html/private.h" static plot_style_t plot_style_bdr = { @@ -121,7 +122,7 @@ html_redraw_border_plot(const int side, switch (style) { case CSS_BORDER_STYLE_DOTTED: plot_style_bdr.stroke_type = PLOT_OP_TYPE_DOT; - /* fall through */ + fallthrough; case CSS_BORDER_STYLE_DASHED: rect.x0 = (p[0] + p[2]) / 2; rect.y0 = (p[1] + p[3]) / 2; @@ -131,7 +132,7 @@ html_redraw_border_plot(const int side, break; case CSS_BORDER_STYLE_SOLID: - /* fall through to default */ + /* solid is the default */ default: if (rectangular || thickness == 1) { @@ -190,7 +191,7 @@ html_redraw_border_plot(const int side, case CSS_BORDER_STYLE_GROOVE: light = 3 - light; - /* fall through */ + fallthrough; case CSS_BORDER_STYLE_RIDGE: /* choose correct colours for each part of the border line */ if (light <= 1) { @@ -300,7 +301,7 @@ html_redraw_border_plot(const int side, case CSS_BORDER_STYLE_INSET: light = (light + 2) % 4; - /* fall through */ + fallthrough; case CSS_BORDER_STYLE_OUTSET: /* choose correct colours for each part of the border line */ switch (light) { diff --git a/content/handlers/html/html_script.c b/content/handlers/html/script.c index 2a72d512e..554fc4f70 100644 --- a/content/handlers/html/html_script.c +++ b/content/handlers/html/script.c @@ -36,13 +36,14 @@ #include "netsurf/content.h" #include "javascript/js.h" #include "content/content_protected.h" +#include "content/content_factory.h" #include "content/fetch.h" #include "content/hlcache.h" #include "html/html.h" -#include "html/html_internal.h" +#include "html/private.h" -typedef bool (script_handler_t)(struct jscontext *jscontext, const uint8_t *data, size_t size, const char *name); +typedef bool (script_handler_t)(struct jsthread *jsthread, const uint8_t *data, size_t size, const char *name); static script_handler_t *select_script_handler(content_type ctype) @@ -60,8 +61,9 @@ nserror html_script_exec(html_content *c, bool allow_defer) unsigned int i; struct html_script *s; script_handler_t *script_handler; + bool have_run_something = false; - if (c->jscontext == NULL) { + if (c->jsthread == NULL) { return NSERROR_BAD_PARAMETER; } @@ -94,8 +96,9 @@ nserror html_script_exec(html_content *c, bool allow_defer) size_t size; data = content_get_source_data( s->data.handle, &size ); - script_handler(c->jscontext, data, size, + script_handler(c->jsthread, data, size, nsurl_access(hlcache_handle_get_url(s->data.handle))); + have_run_something = true; /* We have to re-acquire this here since the * c->scripts array may have been reallocated * as a result of executing this script. @@ -108,6 +111,10 @@ nserror html_script_exec(html_content *c, bool allow_defer) } } + if (have_run_something) { + return html_proceed_to_done(c); + } + return NSERROR_OK; } @@ -183,15 +190,12 @@ convert_script_async_cb(hlcache_handle *script, case CONTENT_MSG_ERROR: NSLOG(netsurf, INFO, "script %s failed: %s", nsurl_access(hlcache_handle_get_url(script)), - event->data.error); - /* fall through */ + event->data.errordata.errormsg); - 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; @@ -210,7 +214,7 @@ convert_script_async_cb(hlcache_handle *script, * scripts as they come in. */ else if (parent->conversion_begun) { - html_script_exec(parent, false); + return html_script_exec(parent, false); } return NSERROR_OK; @@ -249,15 +253,12 @@ convert_script_defer_cb(hlcache_handle *script, case CONTENT_MSG_ERROR: NSLOG(netsurf, INFO, "script %s failed: %s", nsurl_access(hlcache_handle_get_url(script)), - event->data.error); - /* fall through */ + event->data.errordata.errormsg); - 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; @@ -288,6 +289,15 @@ convert_script_sync_cb(hlcache_handle *script, struct html_script *s; script_handler_t *script_handler; dom_hubbub_error err; + unsigned int active_sync_scripts = 0; + + /* Count sync scripts which have yet to complete (other than us) */ + for (i = 0, s = parent->scripts; i != parent->scripts_count; i++, s++) { + if (s->type == HTML_SCRIPT_SYNC && + s->data.handle != script && s->already_started == false) { + active_sync_scripts++; + } + } /* Find script */ for (i = 0, s = parent->scripts; i != parent->scripts_count; i++, s++) { @@ -308,17 +318,17 @@ convert_script_sync_cb(hlcache_handle *script, /* attempt to execute script */ script_handler = select_script_handler(content_get_type(s->data.handle)); - if (script_handler != NULL && parent->jscontext != NULL) { + if (script_handler != NULL && parent->jsthread != NULL) { /* script has a handler */ const uint8_t *data; size_t size; data = content_get_source_data(s->data.handle, &size ); - script_handler(parent->jscontext, data, size, + script_handler(parent->jsthread, data, size, nsurl_access(hlcache_handle_get_url(s->data.handle))); } /* continue parse */ - if (parent->parser != NULL) { + if (parent->parser != NULL && active_sync_scripts == 0) { err = dom_hubbub_parser_pause(parent->parser, false); if (err != DOM_HUBBUB_OK) { NSLOG(netsurf, INFO, "unpause returned 0x%x", err); @@ -330,21 +340,18 @@ convert_script_sync_cb(hlcache_handle *script, case CONTENT_MSG_ERROR: NSLOG(netsurf, INFO, "script %s failed: %s", nsurl_access(hlcache_handle_get_url(script)), - event->data.error); - /* fall through */ + event->data.errordata.errormsg); - 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 */ - if (parent->parser != NULL) { + if (parent->parser != NULL && active_sync_scripts == 0) { err = dom_hubbub_parser_pause(parent->parser, false); if (err != DOM_HUBBUB_OK) { NSLOG(netsurf, INFO, "unpause returned 0x%x", err); @@ -390,7 +397,7 @@ exec_src_script(html_content *c, /* 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); + content_broadcast_error(&c->base, NSERROR_NOMEM, NULL); return DOM_HUBBUB_NOMEM; } @@ -452,7 +459,7 @@ exec_src_script(html_content *c, nscript = html_process_new_script(c, mimetype, script_type); if (nscript == NULL) { nsurl_unref(joined); - content_broadcast_errorcode(&c->base, NSERROR_NOMEM); + content_broadcast_error(&c->base, NSERROR_NOMEM, NULL); return DOM_HUBBUB_NOMEM; } @@ -488,6 +495,7 @@ exec_src_script(html_content *c, switch (script_type) { case HTML_SCRIPT_SYNC: ret = DOM_HUBBUB_HUBBUB_ERR | HUBBUB_PAUSED; + break; case HTML_SCRIPT_ASYNC: break; @@ -522,7 +530,7 @@ exec_inline_script(html_content *c, dom_node *node, dom_string *mimetype) if (nscript == NULL) { dom_string_unref(script); - content_broadcast_errorcode(&c->base, NSERROR_NOMEM); + content_broadcast_error(&c->base, NSERROR_NOMEM, NULL); return DOM_HUBBUB_NOMEM; } @@ -531,12 +539,16 @@ exec_inline_script(html_content *c, dom_node *node, dom_string *mimetype) nscript->already_started = true; /* ensure script handler for content type */ - dom_string_intern(mimetype, &lwcmimetype); + exc = dom_string_intern(mimetype, &lwcmimetype); + if (exc != DOM_NO_ERR) { + return DOM_HUBBUB_DOM; + } + script_handler = select_script_handler(content_factory_type_from_mime_type(lwcmimetype)); lwc_string_unref(lwcmimetype); if (script_handler != NULL) { - script_handler(c->jscontext, + script_handler(c->jsthread, (const uint8_t *)dom_string_data(script), dom_string_byte_length(script), "?inline script?"); @@ -562,13 +574,13 @@ html_process_script(void *ctx, dom_node *node) /* 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) { + if (c->jsthread == 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) { + msg_data.jsthread = &c->jsthread; + content_broadcast(&c->base, CONTENT_MSG_GETTHREAD, &msg_data); + NSLOG(netsurf, INFO, "javascript context %p ", c->jsthread); + if (c->jsthread == NULL) { /* no context and it could not be created, abort */ return DOM_HUBBUB_OK; } @@ -596,6 +608,31 @@ html_process_script(void *ctx, dom_node *node) } /* exported internal interface documented in html/html_internal.h */ +bool html_saw_insecure_scripts(html_content *htmlc) +{ + struct html_script *s; + unsigned int i; + + for (i = 0, s = htmlc->scripts; i != htmlc->scripts_count; i++, s++) { + if (s->type == HTML_SCRIPT_INLINE) { + /* Inline scripts are no less secure than their + * containing HTML content + */ + continue; + } + if (s->data.handle == NULL) { + /* We've not begun loading this? */ + continue; + } + if (content_saw_insecure_objects(s->data.handle)) { + return true; + } + } + + return false; +} + +/* exported internal interface documented in html/html_internal.h */ nserror html_script_free(html_content *html) { unsigned int i; @@ -626,10 +663,3 @@ nserror html_script_free(html_content *html) 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/search.c b/content/handlers/html/search.c deleted file mode 100644 index 9ba2957e4..000000000 --- a/content/handlers/html/search.c +++ /dev/null @@ -1,656 +0,0 @@ -/* - * Copyright 2004 John M Bell <jmb202@ecs.soton.ac.uk> - * Copyright 2005 Adrian Lees <adrianl@users.sourceforge.net> - * Copyright 2009 Mark Benjamin <netsurf-browser.org.MarkBenjamin@dfgh.net> - * - * This file is part of NetSurf, http://www.netsurf-browser.org/ - * - * NetSurf is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * NetSurf is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -/** - * \file - * Free text search (core) - */ - -#include <ctype.h> -#include <string.h> -#include <dom/dom.h> - -#include "utils/config.h" -#include "utils/log.h" -#include "utils/messages.h" -#include "utils/utils.h" -#include "content/content.h" -#include "content/hlcache.h" -#include "desktop/selection.h" -#include "netsurf/search.h" -#include "netsurf/misc.h" -#include "desktop/gui_internal.h" - -#include "text/textplain.h" -#include "html/box.h" -#include "html/html.h" -#include "html/html_internal.h" -#include "html/search.h" - -#ifndef NOF_ELEMENTS -#define NOF_ELEMENTS(array) (sizeof(array)/sizeof(*(array))) -#endif - - -struct list_entry { - unsigned start_idx; /* start position of match */ - unsigned end_idx; /* end of match */ - - struct box *start_box; /* used only for html contents */ - struct box *end_box; - - struct selection *sel; - - struct list_entry *prev; - struct list_entry *next; -}; - -struct search_context { - void *gui_p; - struct content *c; - struct list_entry *found; - struct list_entry *current; /* first for select all */ - char *string; - bool prev_case_sens; - bool newsearch; - bool is_html; -}; - - -/* Exported function documented in search.h */ -struct search_context * search_create_context(struct content *c, - content_type type, void *gui_data) -{ - struct search_context *context; - struct list_entry *search_head; - - if (type != CONTENT_HTML && type != CONTENT_TEXTPLAIN) { - return NULL; - } - - context = malloc(sizeof(struct search_context)); - if (context == NULL) { - guit->misc->warning("NoMemory", 0); - return NULL; - } - - search_head = malloc(sizeof(struct list_entry)); - if (search_head == NULL) { - guit->misc->warning("NoMemory", 0); - free(context); - return NULL; - } - - search_head->start_idx = 0; - search_head->end_idx = 0; - search_head->start_box = NULL; - search_head->end_box = NULL; - search_head->sel = NULL; - search_head->prev = NULL; - search_head->next = NULL; - - context->found = search_head; - context->current = NULL; - context->string = NULL; - context->prev_case_sens = false; - context->newsearch = true; - context->c = c; - context->is_html = (type == CONTENT_HTML) ? true : false; - context->gui_p = gui_data; - - return context; -} - - -/** - * Release the memory used by the list of matches, - * deleting selection objects too - */ - -static void free_matches(struct search_context *context) -{ - struct list_entry *a; - struct list_entry *b; - - a = context->found->next; - - /* empty the list before clearing and deleting the - * selections because the the clearing updates the - * screen immediately, causing nested accesses to the list */ - - context->found->prev = NULL; - context->found->next = NULL; - - for (; a; a = b) { - b = a->next; - if (a->sel) { - selection_clear(a->sel, true); - selection_destroy(a->sel); - } - free(a); - } -} - - -/** - * Find the first occurrence of 'match' in 'string' and return its index - * - * \param string the string to be searched (unterminated) - * \param s_len length of the string to be searched - * \param pattern the pattern for which we are searching (unterminated) - * \param p_len length of pattern - * \param case_sens true iff case sensitive match required - * \param m_len accepts length of match in bytes - * \return pointer to first match, NULL if none - */ - -static const char *find_pattern(const char *string, int s_len, - const char *pattern, int p_len, bool case_sens, - unsigned int *m_len) -{ - struct { const char *ss, *s, *p; bool first; } context[16]; - const char *ep = pattern + p_len; - const char *es = string + s_len; - const char *p = pattern - 1; /* a virtual '*' before the pattern */ - const char *ss = string; - const char *s = string; - bool first = true; - int top = 0; - - while (p < ep) { - bool matches; - if (p < pattern || *p == '*') { - char ch; - - /* skip any further asterisks; one is the same as many - */ - do p++; while (p < ep && *p == '*'); - - /* if we're at the end of the pattern, yes, it matches - */ - if (p >= ep) break; - - /* anything matches a # so continue matching from - here, and stack a context that will try to match - the wildcard against the next character */ - - ch = *p; - if (ch != '#') { - /* scan forwards until we find a match for - this char */ - if (!case_sens) ch = toupper(ch); - while (s < es) { - if (case_sens) { - if (*s == ch) break; - } else if (toupper(*s) == ch) - break; - s++; - } - } - - if (s < es) { - /* remember where we are in case the match - fails; we may then resume */ - if (top < (int)NOF_ELEMENTS(context)) { - context[top].ss = ss; - context[top].s = s + 1; - context[top].p = p - 1; - /* ptr to last asterisk */ - context[top].first = first; - top++; - } - - if (first) { - ss = s; - /* remember first non-'*' char */ - first = false; - } - - matches = true; - } else { - matches = false; - } - - } else if (s < es) { - char ch = *p; - if (ch == '#') - matches = true; - else { - if (case_sens) - matches = (*s == ch); - else - matches = (toupper(*s) == toupper(ch)); - } - if (matches && first) { - ss = s; /* remember first non-'*' char */ - first = false; - } - } else { - matches = false; - } - - if (matches) { - p++; s++; - } else { - /* doesn't match, - * resume with stacked context if we have one */ - if (--top < 0) - return NULL; /* no match, give up */ - - ss = context[top].ss; - s = context[top].s; - p = context[top].p; - first = context[top].first; - } - } - - /* end of pattern reached */ - *m_len = max(s - ss, 1); - return ss; -} - - -/** - * Add a new entry to the list of matches - * - * \param start_idx Offset of match start within textual representation - * \param end_idx Offset of match end - * \param context The search context to add the entry to. - * \return Pointer to added entry, NULL iff failed. - */ - -static struct list_entry *add_entry(unsigned start_idx, unsigned end_idx, - struct search_context *context) -{ - struct list_entry *entry; - - /* found string in box => add to list */ - entry = calloc(1, sizeof(*entry)); - if (!entry) { - guit->misc->warning("NoMemory", 0); - return NULL; - } - - entry->start_idx = start_idx; - entry->end_idx = end_idx; - entry->sel = NULL; - - entry->next = 0; - entry->prev = context->found->prev; - - if (context->found->prev == NULL) - context->found->next = entry; - else - context->found->prev->next = entry; - - context->found->prev = entry; - - return entry; -} - - -/** - * Finds all occurrences of a given string in the html box tree - * - * \param pattern the string pattern to search for - * \param p_len pattern length - * \param cur pointer to the current box - * \param case_sens whether to perform a case sensitive search - * \param context The search context to add the entry to. - * \return true on success, false on memory allocation failure - */ -static bool find_occurrences_html(const char *pattern, int p_len, - struct box *cur, bool case_sens, - struct search_context *context) -{ - struct box *a; - - /* ignore this box, if there's no visible text */ - if (!cur->object && cur->text) { - const char *text = cur->text; - unsigned length = cur->length; - - while (length > 0) { - struct list_entry *entry; - unsigned match_length; - unsigned match_offset; - const char *new_text; - const char *pos = find_pattern(text, length, - pattern, p_len, case_sens, - &match_length); - if (!pos) - break; - - /* found string in box => add to list */ - match_offset = pos - cur->text; - - entry = add_entry(cur->byte_offset + match_offset, - cur->byte_offset + - match_offset + - match_length, context); - if (!entry) - return false; - - entry->start_box = cur; - entry->end_box = cur; - - new_text = pos + match_length; - length -= (new_text - text); - text = new_text; - } - } - - /* and recurse */ - for (a = cur->children; a; a = a->next) { - if (!find_occurrences_html(pattern, p_len, a, case_sens, - context)) - return false; - } - - return true; -} - - -/** - * Finds all occurrences of a given string in a textplain content - * - * \param pattern the string pattern to search for - * \param p_len pattern length - * \param c the content to be searched - * \param case_sens whether to perform a case sensitive search - * \param context The search context to add the entry to. - * \return true on success, false on memory allocation failure - */ - -static bool find_occurrences_text(const char *pattern, int p_len, - struct content *c, bool case_sens, - struct search_context *context) -{ - int nlines = textplain_line_count(c); - int line; - - for(line = 0; line < nlines; line++) { - size_t offset, length; - const char *text = textplain_get_line(c, line, - &offset, &length); - if (text) { - while (length > 0) { - struct list_entry *entry; - unsigned match_length; - size_t start_idx; - const char *new_text; - const char *pos = find_pattern(text, length, - pattern, p_len, case_sens, - &match_length); - if (!pos) - break; - - /* found string in line => add to list */ - start_idx = offset + (pos - text); - entry = add_entry(start_idx, start_idx + - match_length, context); - if (!entry) - return false; - - new_text = pos + match_length; - offset += (new_text - text); - length -= (new_text - text); - text = new_text; - } - } - } - - return true; -} - - -/** - * Search for a string in the box tree - * - * \param string the string to search for - * \param string_len length of search string - * \param context The search context to add the entry to. - * \param flags flags to control the search. - */ -static void search_text(const char *string, int string_len, - struct search_context *context, search_flags_t flags) -{ - struct rect bounds; - struct box *box = NULL; - union content_msg_data msg_data; - bool case_sensitive, forwards, showall; - - case_sensitive = ((flags & SEARCH_FLAG_CASE_SENSITIVE) != 0) ? - true : false; - forwards = ((flags & SEARCH_FLAG_FORWARDS) != 0) ? true : false; - showall = ((flags & SEARCH_FLAG_SHOWALL) != 0) ? true : false; - - if (context->c == NULL) - return; - - if (context->is_html == true) { - html_content *html = (html_content *)context->c; - - box = html->layout; - - if (!box) - return; - } - - - /* check if we need to start a new search or continue an old one */ - if (context->newsearch) { - bool res; - - if (context->string != NULL) - free(context->string); - - context->current = NULL; - free_matches(context); - - context->string = malloc(string_len + 1); - if (context->string != NULL) { - memcpy(context->string, string, string_len); - context->string[string_len] = '\0'; - } - - guit->search->hourglass(true, context->gui_p); - - if (context->is_html == true) { - res = find_occurrences_html(string, string_len, - box, case_sensitive, context); - } else { - res = find_occurrences_text(string, string_len, - context->c, case_sensitive, context); - } - - if (!res) { - free_matches(context); - guit->search->hourglass(false, context->gui_p); - return; - } - guit->search->hourglass(false, context->gui_p); - - context->prev_case_sens = case_sensitive; - - /* new search, beginning at the top of the page */ - context->current = context->found->next; - context->newsearch = false; - - } else if (context->current != NULL) { - /* continued search in the direction specified */ - if (forwards) { - if (context->current->next) - context->current = context->current->next; - } else { - if (context->current->prev) - context->current = context->current->prev; - } - } - - guit->search->status((context->current != NULL), context->gui_p); - - search_show_all(showall, context); - - guit->search->back_state((context->current != NULL) && - (context->current->prev != NULL), - context->gui_p); - guit->search->forward_state((context->current != NULL) && - (context->current->next != NULL), - context->gui_p); - - if (context->current == NULL) - return; - - if (context->is_html == true) { - /* get box position and jump to it */ - box_coords(context->current->start_box, &bounds.x0, &bounds.y0); - /* \todo: move x0 in by correct idx */ - box_coords(context->current->end_box, &bounds.x1, &bounds.y1); - /* \todo: move x1 in by correct idx */ - bounds.x1 += context->current->end_box->width; - bounds.y1 += context->current->end_box->height; - } else { - textplain_coords_from_range(context->c, - context->current->start_idx, - context->current->end_idx, &bounds); - } - - msg_data.scroll.area = true; - msg_data.scroll.x0 = bounds.x0; - msg_data.scroll.y0 = bounds.y0; - msg_data.scroll.x1 = bounds.x1; - msg_data.scroll.y1 = bounds.y1; - content_broadcast(context->c, CONTENT_MSG_SCROLL, &msg_data); -} - - -/* Exported function documented in search.h */ -void search_step(struct search_context *context, search_flags_t flags, - const char *string) -{ - int string_len; - int i = 0; - - if (context == NULL) { - guit->misc->warning("SearchError", 0); - return; - } - - guit->search->add_recent(string, context->gui_p); - - string_len = strlen(string); - for (i = 0; i < string_len; i++) - if (string[i] != '#' && string[i] != '*') - break; - if (i >= string_len) { - union content_msg_data msg_data; - free_matches(context); - - guit->search->status(true, context->gui_p); - guit->search->back_state(false, context->gui_p); - guit->search->forward_state(false, context->gui_p); - - msg_data.scroll.area = false; - msg_data.scroll.x0 = 0; - msg_data.scroll.y0 = 0; - content_broadcast(context->c, CONTENT_MSG_SCROLL, &msg_data); - return; - } - search_text(string, string_len, context, flags); -} - - -/* Exported function documented in search.h */ -bool search_term_highlighted(struct content *c, - unsigned start_offset, unsigned end_offset, - unsigned *start_idx, unsigned *end_idx, - struct search_context *context) -{ - if (c == context->c) { - struct list_entry *a; - for (a = context->found->next; a; a = a->next) - if (a->sel && selection_defined(a->sel) && - selection_highlighted(a->sel, - start_offset, end_offset, - start_idx, end_idx)) - return true; - } - - return false; -} - - -/* Exported function documented in search.h */ -void search_show_all(bool all, struct search_context *context) -{ - struct list_entry *a; - - for (a = context->found->next; a; a = a->next) { - bool add = true; - if (!all && a != context->current) { - add = false; - if (a->sel) { - selection_clear(a->sel, true); - selection_destroy(a->sel); - a->sel = NULL; - } - } - if (add && !a->sel) { - - if (context->is_html == true) { - html_content *html = (html_content *)context->c; - a->sel = selection_create(context->c, true); - if (!a->sel) - continue; - - selection_init(a->sel, html->layout, - &html->len_ctx); - } else { - a->sel = selection_create(context->c, false); - if (!a->sel) - continue; - - selection_init(a->sel, NULL, NULL); - } - - selection_set_start(a->sel, a->start_idx); - selection_set_end(a->sel, a->end_idx); - } - } -} - - -/* Exported function documented in search.h */ -void search_destroy_context(struct search_context *context) -{ - assert(context != NULL); - - if (context->string != NULL) { - guit->search->add_recent(context->string, context->gui_p); - free(context->string); - } - - guit->search->forward_state(true, context->gui_p); - guit->search->back_state(true, context->gui_p); - - free_matches(context); - free(context); -} diff --git a/content/handlers/html/search.h b/content/handlers/html/search.h deleted file mode 100644 index 5c9408e3e..000000000 --- a/content/handlers/html/search.h +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2009 Mark Benjamin <netsurf-browser.org.MarkBenjamin@dfgh.net> - * - * This file is part of NetSurf, http://www.netsurf-browser.org/ - * - * NetSurf is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * NetSurf is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -/** - * \file - * Interface to HTML searching. - */ - -#ifndef NETSURF_HTML_SEARCH_H -#define NETSURF_HTML_SEARCH_H - -#include <ctype.h> -#include <string.h> - -#include "desktop/search.h" - -struct search_context; - -/** - * create a search_context - * - * \param c The content the search_context is connected to - * \param type The content type of c - * \param context A context pointer passed to the provider routines. - * \return A new search context or NULL on error. - */ -struct search_context *search_create_context(struct content *c, - content_type type, void *context); - -/** - * Ends the search process, invalidating all state - * freeing the list of found boxes - */ -void search_destroy_context(struct search_context *context); - -/** - * Begins/continues the search process - * - * \note that this may be called many times for a single search. - * - * \param context The search context in use. - * \param flags The flags forward/back etc - * \param string The string to match - */ -void search_step(struct search_context *context, search_flags_t flags, - const char * string); - -/** - * Specifies whether all matches or just the current match should - * be highlighted in the search text. - */ -void search_show_all(bool all, struct search_context *context); - -/** - * Determines whether any portion of the given text box should be - * selected because it matches the current search string. - * - * \param c The content to hilight within. - * \param start_offset byte offset within text of string to be checked - * \param end_offset byte offset within text - * \param start_idx byte offset within string of highlight start - * \param end_idx byte offset of highlight end - * \param context The search context to hilight entries from. - * \return true iff part of the box should be highlighted - */ -bool search_term_highlighted(struct content *c, - unsigned start_offset, unsigned end_offset, - unsigned *start_idx, unsigned *end_idx, - struct search_context *context); - -#endif diff --git a/content/handlers/html/table.c b/content/handlers/html/table.c index 5609e8f29..f8762e862 100644 --- a/content/handlers/html/table.c +++ b/content/handlers/html/table.c @@ -26,6 +26,7 @@ #include <dom/dom.h> #include "utils/log.h" +#include "utils/utils.h" #include "utils/talloc.h" #include "css/utils.h" @@ -46,316 +47,344 @@ struct border { 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 + * Determine if a border style is more eyecatching than another * - * The table->col array is allocated and type and width are filled in for each - * column. + * \param unit_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_calculate_column_types( - const nscss_len_ctx *len_ctx, - struct box *table) +static bool +table_border_is_more_eyecatching(const css_unit_ctx *unit_len_ctx, + const struct border *a, + box_type a_src, + const struct border *b, + box_type b_src) { - unsigned int i, j; - struct column *col; - struct box *row_group, *row, *cell; + css_fixed awidth, bwidth; + int impact = 0; - if (table->col) - /* table->col already constructed, for example frameset table */ + /* 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; - table->col = col = talloc_array(table, struct column, table->columns); - if (!col) + /* 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 = css_unit_len2device_px(NULL, unit_len_ctx, a->width, a->unit); + bwidth = css_unit_len2device_px(NULL, unit_len_ctx, b->width, b->unit); + + if (awidth < bwidth) + return true; + else if (bwidth < awidth) return false; - for (i = 0; i != table->columns; i++) { - col[i].type = COLUMN_WIDTH_UNKNOWN; - col[i].width = 0; - col[i].positioned = true; + /* 3b -- sort by style */ + switch (a->style) { + case CSS_BORDER_STYLE_DOUBLE: impact++; fallthrough; + case CSS_BORDER_STYLE_SOLID: impact++; fallthrough; + case CSS_BORDER_STYLE_DASHED: impact++; fallthrough; + case CSS_BORDER_STYLE_DOTTED: impact++; fallthrough; + case CSS_BORDER_STYLE_RIDGE: impact++; fallthrough; + case CSS_BORDER_STYLE_OUTSET: impact++; fallthrough; + case CSS_BORDER_STYLE_GROOVE: impact++; fallthrough; + case CSS_BORDER_STYLE_INSET: impact++; fallthrough; + default: + break; } - /* 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; + switch (b->style) { + case CSS_BORDER_STYLE_DOUBLE: impact--; fallthrough; + case CSS_BORDER_STYLE_SOLID: impact--; fallthrough; + case CSS_BORDER_STYLE_DASHED: impact--; fallthrough; + case CSS_BORDER_STYLE_DOTTED: impact--; fallthrough; + case CSS_BORDER_STYLE_RIDGE: impact--; fallthrough; + case CSS_BORDER_STYLE_OUTSET: impact--; fallthrough; + case CSS_BORDER_STYLE_GROOVE: impact--; fallthrough; + case CSS_BORDER_STYLE_INSET: impact--; fallthrough; + default: + break; + } - assert(cell->type == BOX_TABLE_CELL); - assert(cell->style); + if (impact < 0) + return true; + else if (impact > 0) + return false; - if (cell->columns != 1) - continue; - i = cell->start_column; + /* 4a -- sort by origin */ + impact = 0; - if (css_computed_position(cell->style) != - CSS_POSITION_ABSOLUTE && - css_computed_position(cell->style) != - CSS_POSITION_FIXED) { - col[i].positioned = false; - } + /** \todo COL/COL_GROUP */ + switch (a_src) { + case BOX_TABLE_CELL: impact++; fallthrough; + case BOX_TABLE_ROW: impact++; fallthrough; + case BOX_TABLE_ROW_GROUP: impact++; fallthrough; + case BOX_TABLE: impact++; fallthrough; + default: + break; + } - 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; - } + /** \todo COL/COL_GROUP */ + switch (b_src) { + case BOX_TABLE_CELL: impact--; fallthrough; + case BOX_TABLE_ROW: impact--; fallthrough; + case BOX_TABLE_ROW_GROUP: impact--; fallthrough; + case BOX_TABLE: impact--; fallthrough; + default: + break; + } - if (col[i].type != COLUMN_WIDTH_UNKNOWN) - continue; + if (impact < 0) + return true; + else if (impact > 0) + return false; - 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; - } + /* 4b -- furthest left (if direction: ltr) and towards top wins */ + /** \todo Currently assumes b satisifies this */ + return true; +} + + +/** + * Process a table + * + * \param unit_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 + */ +static void +table_cell_top_process_table(const css_unit_ctx *unit_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 = css_unit_len2device_px(table->style, unit_len_ctx, + b.width, b.unit); + b.unit = CSS_UNIT_PX; + b_src = BOX_TABLE; + + if (table_border_is_more_eyecatching(unit_len_ctx, a, *a_src, &b, b_src)) { + *a = b; + *a_src = b_src; } +} - /* 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; +/** + * Process a row + * + * \param unit_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 + */ +static bool +table_cell_top_process_row(const css_unit_ctx *unit_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 = css_unit_len2device_px(row->style, unit_len_ctx, + b.width, b.unit); + b.unit = CSS_UNIT_PX; + b_src = BOX_TABLE_ROW; - for (j = i; j < i + cell->columns; j++) { - col[j].positioned = false; - } + if (table_border_is_more_eyecatching(unit_len_ctx, a, *a_src, &b, b_src)) { + *a = b; + *a_src = b_src; + } - /* 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 (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 = css_unit_len2device_px(row->style, unit_len_ctx, + b.width, b.unit); + b.unit = CSS_UNIT_PX; + b_src = BOX_TABLE_ROW; + + if (table_border_is_more_eyecatching(unit_len_ctx, + a, *a_src, &b, b_src)) { + *a = b; + *a_src = b_src; } - 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; + 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 = css_unit_len2device_px( + c->style, unit_len_ctx, + b.width, b.unit); + b.unit = CSS_UNIT_PX; + b_src = BOX_TABLE_CELL; + + if (table_border_is_more_eyecatching(unit_len_ctx, + a, + *a_src, + &b, + b_src)) { + *a = b; + *a_src = b_src; } } - } - /* 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; - } + if (processed == false) { + /* There must be a preceding row */ + assert(row->prev != NULL); + + row = row->prev; } } } - /* 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. + * Process a group * - * \param len_ctx Length conversion context - * \param cell Table cell to consider + * \param unit_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 cell's border array is populated + * \post \a a will be updated with most eyecatching style + * \post \a a_src will be updated also */ -void table_used_border_for_cell( - const nscss_len_ctx *len_ctx, - struct box *cell) +static bool +table_cell_top_process_group(const css_unit_ctx *unit_len_ctx, + struct box *cell, + struct box *group, + struct border *a, + box_type *a_src) { - 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; + struct border b; + box_type b_src; - /* 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)); + /* 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 = css_unit_len2device_px(group->style, unit_len_ctx, + b.width, b.unit); + b.unit = CSS_UNIT_PX; + b_src = BOX_TABLE_ROW_GROUP; - /* 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)); + if (table_border_is_more_eyecatching(unit_len_ctx, a, *a_src, &b, b_src)) { + *a = b; + *a_src = b_src; + } - /* 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)); + if (group->last != NULL) { + /* Process rows in group, starting with last */ + struct box *row = group->last; - /* 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)); + while (table_cell_top_process_row(unit_len_ctx, cell, row, + a, a_src) == false) { + if (row->prev == NULL) { + return false; + } else { + row = row->prev; + } + } } else { - /* Left border */ - table_used_left_border_for_cell(len_ctx, cell); - - /* Top border */ - table_used_top_border_for_cell(len_ctx, cell); + /* 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 = css_unit_len2device_px(group->style, unit_len_ctx, + b.width, b.unit); + b.unit = CSS_UNIT_PX; + b_src = BOX_TABLE_ROW_GROUP; - /* Right border */ - table_used_right_border_for_cell(len_ctx, cell); + if (table_border_is_more_eyecatching(unit_len_ctx, + a, *a_src, &b, b_src)) { + *a = b; + *a_src = b_src; + } - /* Bottom border */ - table_used_bottom_border_for_cell(len_ctx, cell); + return false; } - /* 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; - } + return true; } -/****************************************************************************** - * Helpers for used border calculations * - ******************************************************************************/ /** * Calculate used values of border-left-{style,color,width} * - * \param len_ctx Length conversion context + * \param unit_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) +static void +table_used_left_border_for_cell(const css_unit_ctx *unit_len_ctx, struct box *cell) { struct border a, b; box_type a_src, b_src; @@ -366,7 +395,8 @@ void table_used_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.width = css_unit_len2device_px(cell->style, unit_len_ctx, + a.width, a.unit); a.unit = CSS_UNIT_PX; a_src = BOX_TABLE_CELL; @@ -380,10 +410,10 @@ void table_used_left_border_for_cell( /* 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) { + prev = prev->next) { if (prev->start_column + - prev->columns == - cell->start_column) + prev->columns == + cell->start_column) break; } @@ -399,12 +429,13 @@ void table_used_left_border_for_cell( 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.width = css_unit_len2device_px(prev->style, unit_len_ctx, + b.width, b.unit); b.unit = CSS_UNIT_PX; b_src = BOX_TABLE_CELL; - if (table_border_is_more_eyecatching(len_ctx, - &a, a_src, &b, b_src)) { + if (table_border_is_more_eyecatching(unit_len_ctx, + &a, a_src, &b, b_src)) { a = b; a_src = b_src; } @@ -419,16 +450,17 @@ void table_used_left_border_for_cell( /* 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); + 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); + row->style, &b.width, &b.unit); + b.width = css_unit_len2device_px( + row->style, unit_len_ctx, + b.width, b.unit); b.unit = CSS_UNIT_PX; b_src = BOX_TABLE_ROW; - if (table_border_is_more_eyecatching(len_ctx, - &a, a_src, &b, b_src)) { + if (table_border_is_more_eyecatching(unit_len_ctx, + &a, a_src, &b, b_src)) { a = b; a_src = b_src; } @@ -442,12 +474,13 @@ void table_used_left_border_for_cell( 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.width = css_unit_len2device_px(group->style, unit_len_ctx, + b.width, b.unit); 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)) { + if (table_border_is_more_eyecatching(unit_len_ctx, + &a, a_src, &b, b_src)) { a = b; a_src = b_src; } @@ -456,12 +489,13 @@ void table_used_left_border_for_cell( 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.width = css_unit_len2device_px(table->style, unit_len_ctx, + b.width, b.unit); b.unit = CSS_UNIT_PX; b_src = BOX_TABLE; - if (table_border_is_more_eyecatching(len_ctx, - &a, a_src, &b, b_src)) { + if (table_border_is_more_eyecatching(unit_len_ctx, + &a, a_src, &b, b_src)) { a = b; a_src = b_src; } @@ -470,19 +504,19 @@ void table_used_left_border_for_cell( /* 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)); + cell->border[LEFT].width = FIXTOINT(css_unit_len2device_px( + cell->style, unit_len_ctx, a.width, a.unit)); } + /** * Calculate used values of border-top-{style,color,width} * - * \param len_ctx Length conversion context + * \param unit_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) +static void +table_used_top_border_for_cell(const css_unit_ctx *unit_len_ctx, struct box *cell) { struct border a, b; box_type a_src, b_src; @@ -493,7 +527,8 @@ void table_used_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.width = css_unit_len2device_px(cell->style, unit_len_ctx, + a.width, a.unit); a.unit = CSS_UNIT_PX; a_src = BOX_TABLE_CELL; @@ -501,19 +536,20 @@ void table_used_top_border_for_cell( 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.width = css_unit_len2device_px(row->style, unit_len_ctx, + b.width, b.unit); b.unit = CSS_UNIT_PX; b_src = BOX_TABLE_ROW; - if (table_border_is_more_eyecatching(len_ctx, &a, a_src, &b, b_src)) { + if (table_border_is_more_eyecatching(unit_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) { + while (table_cell_top_process_row(unit_len_ctx, cell, row->prev, + &a, &a_src) == false) { if (row->prev->prev == NULL) { /* Consider row group */ process_group = true; @@ -533,30 +569,31 @@ void table_used_top_border_for_cell( 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.width = css_unit_len2device_px(group->style, unit_len_ctx, + b.width, b.unit); 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)) { + if (table_border_is_more_eyecatching(unit_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); + table_cell_top_process_table(unit_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) { + while (table_cell_top_process_group(unit_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); + table_cell_top_process_table(unit_len_ctx, + group->parent, + &a, &a_src); break; } else { group = group->prev; @@ -568,19 +605,18 @@ void table_used_top_border_for_cell( /* 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)); + cell->border[TOP].width = FIXTOINT(css_unit_len2device_px( + cell->style, unit_len_ctx, a.width, a.unit)); } /** * Calculate used values of border-right-{style,color,width} * - * \param len_ctx Length conversion context + * \param unit_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) +static void +table_used_right_border_for_cell(const css_unit_ctx *unit_len_ctx, struct box *cell) { struct border a, b; box_type a_src, b_src; @@ -591,12 +627,13 @@ void table_used_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.width = css_unit_len2device_px(cell->style, unit_len_ctx, + a.width, a.unit); 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->parent->parent->parent->columns) { /* Cell is not at right edge of table -- no right border */ a.style = CSS_BORDER_STYLE_NONE; a.width = 0; @@ -611,17 +648,20 @@ void table_used_right_border_for_cell( 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.color = css_computed_border_right_color(row->style, + &b.c); + css_computed_border_right_width(row->style, + &b.width, + &b.unit); + b.width = css_unit_len2device_px( + row->style, unit_len_ctx, + b.width, b.unit); b.unit = CSS_UNIT_PX; b_src = BOX_TABLE_ROW; - if (table_border_is_more_eyecatching(len_ctx, - &a, a_src, &b, b_src)) { + if (table_border_is_more_eyecatching(unit_len_ctx, + &a, a_src, + &b, b_src)) { a = b; a_src = b_src; } @@ -635,13 +675,14 @@ void table_used_right_border_for_cell( 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.width, &b.unit); + b.width = css_unit_len2device_px(group->style, unit_len_ctx, + b.width, b.unit); 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)) { + if (table_border_is_more_eyecatching(unit_len_ctx, + &a, a_src, &b, b_src)) { a = b; a_src = b_src; } @@ -650,13 +691,15 @@ void table_used_right_border_for_cell( 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.width, &b.unit); + b.width = css_unit_len2device_px(table->style, unit_len_ctx, + b.width, b.unit); b.unit = CSS_UNIT_PX; b_src = BOX_TABLE; - if (table_border_is_more_eyecatching(len_ctx, - &a, a_src, &b, b_src)) { + if (table_border_is_more_eyecatching(unit_len_ctx, + &a, a_src, + &b, b_src)) { a = b; a_src = b_src; } @@ -665,19 +708,20 @@ void table_used_right_border_for_cell( /* 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)); + cell->border[RIGHT].width = FIXTOINT(css_unit_len2device_px( + cell->style, unit_len_ctx, a.width, a.unit)); } + /** * Calculate used values of border-bottom-{style,color,width} * - * \param len_ctx Length conversion context + * \param unit_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) +static void +table_used_bottom_border_for_cell(const css_unit_ctx *unit_len_ctx, + struct box *cell) { struct border a, b; box_type a_src, b_src; @@ -688,7 +732,8 @@ void table_used_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.width = css_unit_len2device_px(cell->style, unit_len_ctx, + a.width, a.unit); a.unit = CSS_UNIT_PX; a_src = BOX_TABLE_CELL; @@ -712,12 +757,13 @@ void table_used_bottom_border_for_cell( 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.width = css_unit_len2device_px(row->style, unit_len_ctx, + b.width, b.unit); b.unit = CSS_UNIT_PX; b_src = BOX_TABLE_ROW; - if (table_border_is_more_eyecatching(len_ctx, - &a, a_src, &b, b_src)) { + if (table_border_is_more_eyecatching(unit_len_ctx, + &a, a_src, &b, b_src)) { a = b; a_src = b_src; } @@ -726,13 +772,14 @@ void table_used_bottom_border_for_cell( 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.width, &b.unit); + b.width = css_unit_len2device_px(group->style, unit_len_ctx, + b.width, b.unit); 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)) { + if (table_border_is_more_eyecatching(unit_len_ctx, + &a, a_src, &b, b_src)) { a = b; a_src = b_src; } @@ -741,13 +788,14 @@ void table_used_bottom_border_for_cell( 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.width, &b.unit); + b.width = css_unit_len2device_px(table->style, unit_len_ctx, + b.width, b.unit); b.unit = CSS_UNIT_PX; b_src = BOX_TABLE; - if (table_border_is_more_eyecatching(len_ctx, - &a, a_src, &b, b_src)) { + if (table_border_is_more_eyecatching(unit_len_ctx, + &a, a_src, &b, b_src)) { a = b; } } @@ -755,326 +803,262 @@ void table_used_bottom_border_for_cell( /* 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)); + cell->border[BOTTOM].width = FIXTOINT(css_unit_len2device_px( + cell->style, unit_len_ctx, a.width, a.unit)); } -/** - * 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; +/* exported interface documented in html/table.h */ +bool +table_calculate_column_types(const css_unit_ctx *unit_len_ctx, struct box *table) +{ + unsigned int i, j; + struct column *col; + struct box *row_group, *row, *cell; - if (b->style == CSS_BORDER_STYLE_HIDDEN || - a->style == CSS_BORDER_STYLE_NONE) + if (table->col) + /* table->col already constructed, for example frameset table */ 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) + table->col = col = talloc_array(table, struct column, table->columns); + if (!col) 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; + for (i = 0; i != table->columns; i++) { + col[i].type = COLUMN_WIDTH_UNKNOWN; + col[i].width = 0; + col[i].positioned = true; } - 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; - } + /* 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; - /** \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; - } + assert(cell->type == BOX_TABLE_CELL); + assert(cell->style); - if (impact < 0) - return true; - else if (impact > 0) - return false; + if (cell->columns != 1) + continue; + i = cell->start_column; - /* 4b -- furthest left (if direction: ltr) and towards top wins */ - /** \todo Currently assumes b satisifies this */ - return true; -} + if (css_computed_position(cell->style) != + CSS_POSITION_ABSOLUTE && + css_computed_position(cell->style) != + CSS_POSITION_FIXED) { + col[i].positioned = false; + } -/****************************************************************************** - * Helpers for top border collapsing * - ******************************************************************************/ + 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(css_unit_len2device_px( + cell->style, + unit_len_ctx, + value, unit)); + if (col[i].width < 0) + col[i].width = 0; + continue; + } -/** - * 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; + if (col[i].type != COLUMN_WIDTH_UNKNOWN) + continue; - /* 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 (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; + } + } - if (table_border_is_more_eyecatching(len_ctx, a, *a_src, &b, b_src)) { - *a = b; - *a_src = b_src; - } -} + /* 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; -/** - * 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; + for (j = i; j < i + cell->columns; j++) { + col[j].positioned = false; + } - /* 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; + /* 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 (table_border_is_more_eyecatching(len_ctx, a, *a_src, &b, b_src)) { - *a = b; - *a_src = b_src; - } + if (!unknown_columns) + continue; - if (group->last != NULL) { - /* Process rows in group, starting with last */ - struct box *row = group->last; + 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(css_unit_len2device_px( + cell->style, + unit_len_ctx, + value, unit)) - + 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; + } + } + } - while (table_cell_top_process_row(len_ctx, cell, row, - a, a_src) == false) { - if (row->prev == NULL) { - return false; - } else { - row = row->prev; + /* 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; + } + } + } } - } - } 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; + /* 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; } -/** - * 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; - } +/* exported interface documented in html/table.h */ +void table_used_border_for_cell(const css_unit_ctx *unit_len_ctx, struct box *cell) +{ + int side; - 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; + assert(cell->type == BOX_TABLE_CELL); - if (table_border_is_more_eyecatching(len_ctx, - a, *a_src, &b, b_src)) { - *a = b; - *a_src = b_src; - } + if (css_computed_border_collapse(cell->style) == + CSS_BORDER_COLLAPSE_SEPARATE) { + css_fixed width = 0; + css_unit unit = CSS_UNIT_PX; - 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; + /* 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(css_unit_len2device_px( + cell->style, unit_len_ctx, + width, unit)); - 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; + /* 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(css_unit_len2device_px( + cell->style, unit_len_ctx, + width, unit)); - /* Flag that we've processed a cell */ - processed = true; + /* 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(css_unit_len2device_px( + cell->style, unit_len_ctx, + width, unit)); - /* 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; + /* 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(css_unit_len2device_px( + cell->style, unit_len_ctx, + width, unit)); + } else { + /* Left border */ + table_used_left_border_for_cell(unit_len_ctx, cell); - if (table_border_is_more_eyecatching(len_ctx, - a, *a_src, &b, b_src)) { - *a = b; - *a_src = b_src; - } - } + /* Top border */ + table_used_top_border_for_cell(unit_len_ctx, cell); - if (processed == false) { - /* There must be a preceding row */ - assert(row->prev != NULL); + /* Right border */ + table_used_right_border_for_cell(unit_len_ctx, cell); - row = row->prev; - } - } + /* Bottom border */ + table_used_bottom_border_for_cell(unit_len_ctx, cell); } - return true; + /* 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; + } } diff --git a/content/handlers/html/table.h b/content/handlers/html/table.h index 11ab653c6..557032b06 100644 --- a/content/handlers/html/table.h +++ b/content/handlers/html/table.h @@ -29,11 +29,28 @@ 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); + +/** + * Determine the column width types for a table. + * + * \param unit_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 css_unit_ctx *unit_len_ctx, struct box *table); + + +/** + * Calculate used values of border-{trbl}-{style,color,width} for table cells. + * + * \param unit_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 css_unit_ctx *unit_len_ctx, struct box *cell); #endif diff --git a/content/handlers/html/textselection.c b/content/handlers/html/textselection.c new file mode 100644 index 000000000..9b83e73ec --- /dev/null +++ b/content/handlers/html/textselection.c @@ -0,0 +1,547 @@ +/* + * Copyright 2020 Vincent Sanders <vince@netsurf-browser.org> + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/** + * \file + * implementation of text selection for a HTML content. + */ + +#include <stdlib.h> + +#include "utils/errors.h" +#include "utils/utils.h" +#include "netsurf/types.h" +#include "netsurf/plot_style.h" +#include "desktop/selection.h" +#include "desktop/save_text.h" + +#include "html/private.h" +#include "html/box.h" +#include "html/box_inspect.h" +#include "html/font.h" +#include "html/textselection.h" + +#define SPACE_LEN(b) ((b->space == 0) ? 0 : 1) + +struct rdw_info { + bool inited; + struct rect r; +}; + + +/** + * Tests whether a text box lies partially within the given range of + * byte offsets, returning the start and end indexes of the bytes + * that are enclosed. + * + * \param box box to be tested + * \param start_idx byte offset of start of range + * \param end_idx byte offset of end of range + * \param start_offset receives the start offset of the selected part + * \param end_offset receives the end offset of the selected part + * \return true iff the range encloses at least part of the box + */ +static bool +selected_part(struct box *box, + unsigned start_idx, + unsigned end_idx, + unsigned *start_offset, + unsigned *end_offset) +{ + size_t box_length = box->length + SPACE_LEN(box); + + if (box_length > 0) { + if ((box->byte_offset >= start_idx) && + (box->byte_offset + box_length <= end_idx)) { + + /* fully enclosed */ + *start_offset = 0; + *end_offset = box_length; + return true; + } else if ((box->byte_offset + box_length > start_idx) && + (box->byte_offset < end_idx)) { + /* partly enclosed */ + int offset = 0; + int len; + + if (box->byte_offset < start_idx) { + offset = start_idx - box->byte_offset; + } + + len = box_length - offset; + + if (box->byte_offset + box_length > end_idx) { + len = end_idx - (box->byte_offset + offset); + } + + *start_offset = offset; + *end_offset = offset + len; + + return true; + } + } + return false; +} + + +/** + * Traverse the given box subtree adding the boxes inside the + * selection to the coordinate range. + * + * \param box box subtree + * \param start_idx start of range within textual representation (bytes) + * \param end_idx end of range + * \param rdwi redraw range to fill in + * \param do_marker whether deal enter any marker box + * \return NSERROR_OK on success else error code + */ +static nserror +coords_from_range(struct box *box, + unsigned start_idx, + unsigned end_idx, + struct rdw_info *rdwi, + bool do_marker) +{ + struct box *child; + nserror res; + + assert(box); + + /* If selection starts inside marker */ + if (box->parent && + box->parent->list_marker == box && + !do_marker) { + /* set box to main list element */ + box = box->parent; + } + + /* If box has a list marker */ + if (box->list_marker) { + /* do the marker box before continuing with the rest of the + * list element */ + res = coords_from_range(box->list_marker, + start_idx, + end_idx, + rdwi, + true); + if (res != NSERROR_OK) { + return res; + } + } + + /* we can prune this subtree, it's after the selection */ + if (box->byte_offset >= end_idx) { + return NSERROR_OK; + } + + /* read before calling the handler in case it modifies the tree */ + child = box->children; + + if ((box->type != BOX_BR) && + !((box->type == BOX_FLOAT_LEFT || + box->type == BOX_FLOAT_RIGHT) && + !box->text)) { + unsigned start_off; + unsigned end_off; + + if (selected_part(box, start_idx, end_idx, &start_off, &end_off)) { + int width, height; + int x, y; + + /** + * \todo it should be possible to reduce the redrawn + * area using the offsets + */ + box_coords(box, &x, &y); + + width = box->padding[LEFT] + box->width + box->padding[RIGHT]; + height = box->padding[TOP] + box->height + box->padding[BOTTOM]; + + if ((box->type == BOX_TEXT) && + (box->space != 0)) { + width += box->space; + } + + if (rdwi->inited) { + if (x < rdwi->r.x0) { + rdwi->r.x0 = x; + } + if (y < rdwi->r.y0) { + rdwi->r.y0 = y; + } + if (x + width > rdwi->r.x1) { + rdwi->r.x1 = x + width; + } + if (y + height > rdwi->r.y1) { + rdwi->r.y1 = y + height; + } + } else { + rdwi->inited = true; + rdwi->r.x0 = x; + rdwi->r.y0 = y; + rdwi->r.x1 = x + width; + rdwi->r.y1 = y + height; + } + } + } + + /* find the first child that could lie partially within the selection; + * this is important at the top-levels of the tree for pruning subtrees + * that lie entirely before the selection */ + + if (child) { + struct box *next = child->next; + + while (next && next->byte_offset < start_idx) { + child = next; + next = child->next; + } + + while (child) { + /* read before calling the handler in case it modifies + * the tree */ + struct box *next = child->next; + + res = coords_from_range(child, + start_idx, + end_idx, + rdwi, + false); + if (res != NSERROR_OK) { + return res; + } + + child = next; + } + } + + return NSERROR_OK; +} + + +/** + * Append the contents of a box to a selection along with style information + * + * \param text pointer to text being added, or NULL for newline + * \param length length of text to be appended (bytes) + * \param box pointer to text box, or NULL if from textplain + * \param unit_len_ctx Length conversion context + * \param handle selection string to append to + * \param whitespace_text whitespace to place before text for formatting + * may be NULL + * \param whitespace_length length of whitespace_text + * \return NSERROR_OK iff successful and traversal should continue else error code + */ +static nserror +selection_copy_box(const char *text, + size_t length, + struct box *box, + const css_unit_ctx *unit_len_ctx, + struct selection_string *handle, + const char *whitespace_text, + size_t whitespace_length) +{ + bool add_space = false; + plot_font_style_t style; + plot_font_style_t *pstyle = NULL; + + /* add any whitespace which precedes the text from this box */ + if (whitespace_text != NULL && + whitespace_length > 0) { + if (!selection_string_append(whitespace_text, + whitespace_length, + false, + pstyle, + handle)) { + return NSERROR_NOMEM; + } + } + + if (box != NULL) { + /* HTML */ + add_space = (box->space != 0); + + if (box->style != NULL) { + /* Override default font style */ + font_plot_style_from_css(unit_len_ctx, box->style, &style); + pstyle = &style; + } else { + /* If there's no style, there must be no text */ + assert(box->text == NULL); + } + } + + /* add the text from this box */ + if (!selection_string_append(text, length, add_space, pstyle, handle)) { + return NSERROR_NOMEM; + } + + return NSERROR_OK; +} + + +/** + * Traverse the given box subtree, calling selection copy for all + * boxes that lie (partially) within the given range + * + * \param box box subtree + * \param unit_len_ctx Length conversion context. + * \param start_idx start of range within textual representation (bytes) + * \param end_idx end of range + * \param handler handler function to call + * \param handle handle to pass + * \param before type of whitespace to place before next encountered text + * \param first whether this is the first box with text + * \param do_marker whether deal enter any marker box + * \return NSERROR_OK on sucess else error code + */ +static nserror +selection_copy(struct box *box, + const css_unit_ctx *unit_len_ctx, + unsigned start_idx, + unsigned end_idx, + struct selection_string *selstr, + save_text_whitespace *before, + bool *first, + bool do_marker) +{ + nserror res; + struct box *child; + const char *whitespace_text = ""; + size_t whitespace_length = 0; + + assert(box); + + /* If selection starts inside marker */ + if (box->parent && + box->parent->list_marker == box && + !do_marker) { + /* set box to main list element */ + box = box->parent; + } + + /* If box has a list marker */ + if (box->list_marker) { + /* do the marker box before continuing with the rest of the + * list element */ + res = selection_copy(box->list_marker, + unit_len_ctx, + start_idx, + end_idx, + selstr, + before, + first, + true); + if (res != NSERROR_OK) { + return res; + } + } + + /* we can prune this subtree, it's after the selection */ + if (box->byte_offset >= end_idx) { + return NSERROR_OK; + } + + /* read before calling the handler in case it modifies the tree */ + child = box->children; + + /* If nicely formatted output of the selected text is required, work + * out what whitespace should be placed before the next bit of text */ + if (before) { + save_text_solve_whitespace(box, + first, + before, + &whitespace_text, + &whitespace_length); + } else { + whitespace_text = NULL; + } + + if ((box->type != BOX_BR) && + !((box->type == BOX_FLOAT_LEFT || + box->type == BOX_FLOAT_RIGHT) && + !box->text)) { + unsigned start_off; + unsigned end_off; + + if (selected_part(box, start_idx, end_idx, &start_off, &end_off)) { + res = selection_copy_box(box->text + start_off, + min(box->length, end_off) - start_off, + box, + unit_len_ctx, + selstr, + whitespace_text, + whitespace_length); + if (res != NSERROR_OK) { + return res; + } + if (before) { + *first = false; + *before = WHITESPACE_NONE; + } + } + } + + /* find the first child that could lie partially within the selection; + * this is important at the top-levels of the tree for pruning subtrees + * that lie entirely before the selection */ + + if (child) { + struct box *next = child->next; + + while (next && next->byte_offset < start_idx) { + child = next; + next = child->next; + } + + while (child) { + /* read before calling the handler in case it modifies + * the tree */ + struct box *next = child->next; + + res = selection_copy(child, + unit_len_ctx, + start_idx, + end_idx, + selstr, + before, + first, + false); + if (res != NSERROR_OK) { + return res; + } + + child = next; + } + } + + return NSERROR_OK; +} + + +/** + * Label each text box in the given box subtree with its position + * in a textual representation of the content. + * + * \param box The box at root of subtree + * \param idx current position within textual representation + * \return updated position + */ +static unsigned selection_label_subtree(struct box *box, unsigned idx) +{ + struct box *child; + + assert(box != NULL); + + child = box->children; + + box->byte_offset = idx; + + if (box->text) { + idx += box->length + SPACE_LEN(box); + } + + while (child) { + if (child->list_marker) { + idx = selection_label_subtree(child->list_marker, idx); + } + + idx = selection_label_subtree(child, idx); + child = child->next; + } + + return idx; +} + + +/* exported interface documented in html/textselection.h */ +nserror +html_textselection_redraw(struct content *c, + unsigned start_idx, + unsigned end_idx) +{ + nserror res; + html_content *html = (html_content *)c; + struct rdw_info rdw; + + if (html->layout == NULL) { + return NSERROR_INVALID; + } + + rdw.inited = false; + + res = coords_from_range(html->layout, start_idx, end_idx, &rdw, false); + if (res != NSERROR_OK) { + return res; + } + + if (rdw.inited) { + content__request_redraw(c, + rdw.r.x0, + rdw.r.y0, + rdw.r.x1 - rdw.r.x0, + rdw.r.y1 - rdw.r.y0); + } + + return NSERROR_OK; +} + + +/* exported interface documented in html/textselection.h */ +nserror +html_textselection_copy(struct content *c, + unsigned start_idx, + unsigned end_idx, + struct selection_string *selstr) +{ + html_content *html = (html_content *)c; + save_text_whitespace before = WHITESPACE_NONE; + bool first = true; + + if (html->layout == NULL) { + return NSERROR_INVALID; + } + + return selection_copy(html->layout, + &html->unit_len_ctx, + start_idx, + end_idx, + selstr, + &before, + &first, + false); +} + + +/* exported interface documented in html/textselection.h */ +nserror +html_textselection_get_end(struct content *c, unsigned *end_idx) +{ + html_content *html = (html_content *)c; + unsigned root_idx; + + if (html->layout == NULL) { + return NSERROR_INVALID; + } + + root_idx = 0; + + *end_idx = selection_label_subtree(html->layout, root_idx); + + return NSERROR_OK; +} diff --git a/content/handlers/html/textselection.h b/content/handlers/html/textselection.h new file mode 100644 index 000000000..46db045ef --- /dev/null +++ b/content/handlers/html/textselection.h @@ -0,0 +1,43 @@ +/* + * Copyright 2020 Vincent Sanders <vince@netsurf-browser.org> + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/** + * \file + * HTML text selection handling + */ + +#ifndef NETSURF_HTML_TEXTSELECTION_H +#define NETSURF_HTML_TEXTSELECTION_H + +struct content; +struct selection; + +nserror html_textselection_redraw(struct content *c, unsigned start_idx, unsigned end_idx); + +nserror html_textselection_copy(struct content *c, unsigned start_idx, unsigned end_idx, struct selection_string *selstr); + +/** + * get maximum index of text section. + * + * \param[in] c The content to measure + * \param[out] end_idx pointer to value to recive result + * \return NSERROR_OK and \a end_idx updated else error code + */ +nserror html_textselection_get_end(struct content *c, unsigned *end_idx); + +#endif |