summaryrefslogtreecommitdiff
path: root/content/handlers/html
diff options
context:
space:
mode:
Diffstat (limited to 'content/handlers/html')
-rw-r--r--content/handlers/html/Makefile28
-rw-r--r--content/handlers/html/box.c1241
-rw-r--r--content/handlers/html/box.h555
-rw-r--r--content/handlers/html/box_construct.c2746
-rw-r--r--content/handlers/html/box_construct.h113
-rw-r--r--content/handlers/html/box_inspect.c898
-rw-r--r--content/handlers/html/box_inspect.h155
-rw-r--r--content/handlers/html/box_manipulate.c352
-rw-r--r--content/handlers/html/box_manipulate.h106
-rw-r--r--content/handlers/html/box_normalise.c953
-rw-r--r--content/handlers/html/box_normalise.h66
-rw-r--r--content/handlers/html/box_special.c1935
-rw-r--r--content/handlers/html/box_special.h35
-rw-r--r--content/handlers/html/box_textarea.c102
-rw-r--r--content/handlers/html/box_textarea.h4
-rw-r--r--content/handlers/html/css.c (renamed from content/handlers/html/html_css.c)85
-rw-r--r--content/handlers/html/css.h109
-rw-r--r--content/handlers/html/css_fetcher.c (renamed from content/handlers/html/html_css_fetcher.c)13
-rw-r--r--content/handlers/html/dom_event.c789
-rw-r--r--content/handlers/html/dom_event.h43
-rw-r--r--content/handlers/html/font.c5
-rw-r--r--content/handlers/html/font.h4
-rw-r--r--content/handlers/html/form.c1259
-rw-r--r--content/handlers/html/form_internal.h124
-rw-r--r--content/handlers/html/forms.c (renamed from content/handlers/html/html_forms.c)19
-rw-r--r--content/handlers/html/html.c1213
-rw-r--r--content/handlers/html/html_interaction.c1453
-rw-r--r--content/handlers/html/imagemap.c3
-rw-r--r--content/handlers/html/interaction.c1816
-rw-r--r--content/handlers/html/interaction.h125
-rw-r--r--content/handlers/html/layout.c1506
-rw-r--r--content/handlers/html/layout_flex.c1117
-rw-r--r--content/handlers/html/layout_internal.h738
-rw-r--r--content/handlers/html/object.c (renamed from content/handlers/html/html_object.c)169
-rw-r--r--content/handlers/html/object.h86
-rw-r--r--content/handlers/html/private.h (renamed from content/handlers/html/html_internal.h)214
-rw-r--r--content/handlers/html/redraw.c (renamed from content/handlers/html/html_redraw.c)188
-rw-r--r--content/handlers/html/redraw_border.c (renamed from content/handlers/html/html_redraw_border.c)11
-rw-r--r--content/handlers/html/script.c (renamed from content/handlers/html/html_script.c)106
-rw-r--r--content/handlers/html/search.c656
-rw-r--r--content/handlers/html/search.h86
-rw-r--r--content/handlers/html/table.c1218
-rw-r--r--content/handlers/html/table.h29
-rw-r--r--content/handlers/html/textselection.c547
-rw-r--r--content/handlers/html/textselection.h43
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 &lt;object&gt; elements
- *
- * \param o The object params being destroyed.
- * \return 0 to allow talloc to continue destroying the tree.
- */
-static int box_object_talloc_destructor(struct object_params *o)
-{
- if (o->codebase != NULL)
- nsurl_unref(o->codebase);
- if (o->classid != NULL)
- nsurl_unref(o->classid);
- if (o->data != NULL)
- nsurl_unref(o->data);
-
- return 0;
-}
-
-/**
- * Generic embedded object [13.3].
+ * 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,
- &params->codebase) == false) {
- dom_string_unref(codebase);
- return false;
- }
- dom_string_unref(codebase);
- }
- if (params->codebase == NULL)
- params->codebase = nsurl_ref(content->base_url);
-
- err = dom_element_get_attribute(n, corestring_dom_classid, &classid);
- if (err == DOM_NO_ERR && classid != NULL) {
- if (box_extract_link(content, classid,
- params->codebase, &params->classid) == false) {
- dom_string_unref(classid);
- return false;
- }
- dom_string_unref(classid);
- }
-
- err = dom_element_get_attribute(n, corestring_dom_data, &data);
- if (err == DOM_NO_ERR && data != NULL) {
- if (box_extract_link(content, data,
- params->codebase, &params->data) == false) {
- dom_string_unref(data);
- return false;
- }
- dom_string_unref(data);
- }
-
- if (params->classid == NULL && params->data == NULL)
- /* nothing to embed; ignore */
- return true;
-
- /* Don't include ourself */
- if (params->classid != NULL && nsurl_compare(content->base_url,
- params->classid, NSURL_COMPLETE))
- return true;
-
- if (params->data != NULL && nsurl_compare(content->base_url,
- params->data, NSURL_COMPLETE))
- return true;
-
- /* codetype and type are MIME types */
- if (box_get_attribute(n, "codetype", params,
- &params->codetype) == false)
- return false;
- if (box_get_attribute(n, "type", params, &params->type) == false)
- return false;
-
- /* classid && !data => classid is used (consult codetype)
- * (classid || !classid) && data => data is used (consult type)
- * !classid && !data => invalid; ignored */
-
- if (params->classid != NULL && params->data == NULL &&
- params->codetype != NULL) {
- lwc_string *icodetype;
- lwc_error lerror;
-
- lerror = lwc_intern_string(params->codetype,
- strlen(params->codetype), &icodetype);
- if (lerror != lwc_error_ok)
- return false;
-
- if (content_factory_type_from_mime_type(icodetype) ==
- CONTENT_NONE) {
- /* can't handle this MIME type */
- lwc_string_unref(icodetype);
- return true;
- }
-
- lwc_string_unref(icodetype);
- }
-
- if (params->data != NULL && params->type != NULL) {
- lwc_string *itype;
- lwc_error lerror;
-
- lerror = lwc_intern_string(params->type, strlen(params->type),
- &itype);
- if (lerror != lwc_error_ok)
- return false;
-
- if (content_factory_type_from_mime_type(itype) ==
- CONTENT_NONE) {
- /* can't handle this MIME type */
- lwc_string_unref(itype);
- return true;
- }
-
- lwc_string_unref(itype);
- }
+ 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,
- &param->name) == false) {
- dom_node_unref(c);
- return false;
- }
-
- if (box_get_attribute(c, "value", param,
- &param->value) == false) {
- dom_node_unref(c);
- return false;
- }
-
- if (box_get_attribute(c, "type", param,
- &param->type) == false) {
- dom_node_unref(c);
- return false;
- }
- if (box_get_attribute(c, "valuetype", param,
- &param->valuetype) == false) {
- dom_node_unref(c);
- return false;
- }
-
- if (param->valuetype == NULL) {
- param->valuetype = talloc_strdup(param, "data");
- if (param->valuetype == NULL) {
- dom_node_unref(c);
- return false;
+ 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 &lt;iframe&gt; elements
- *
- * \param f The iframe params being destroyed.
- * \return 0 to allow talloc to continue destroying the tree.
- */
-static int box_iframes_talloc_destructor(struct content_html_iframe *f)
-{
- if (f->url != NULL) {
- nsurl_unref(f->url);
- f->url = NULL;
- }
-
- return 0;
-}
-
-
-/**
- * Inline subwindow [16.5].
- */
-
-bool box_iframe(BOX_SPECIAL_PARAMS)
-{
- nsurl *url;
- dom_string *s;
- dom_exception err;
- struct content_html_iframe *iframe;
- int i;
-
- if (box->style && ns_computed_display(box->style,
- box_is_root(n)) == CSS_DISPLAY_NONE)
- return true;
-
- if (box->style && css_computed_visibility(box->style) ==
- CSS_VISIBILITY_HIDDEN)
- /* Don't create iframe discriptors for invisible iframes
- * TODO: handle hidden iframes at browser_window generation
- * time instead? */
- return true;
-
- /* get frame URL */
- err = dom_element_get_attribute(n, corestring_dom_src, &s);
- if (err != DOM_NO_ERR || s == NULL)
- return true;
- if (box_extract_link(content, s, content->base_url, &url) == false) {
- dom_string_unref(s);
- return false;
- }
- dom_string_unref(s);
- if (url == NULL)
- return true;
-
- /* don't include ourself */
- if (nsurl_compare(content->base_url, url, NSURL_COMPLETE)) {
- nsurl_unref(url);
- return true;
- }
-
- /* create a new iframe */
- iframe = talloc(content->bctx, struct content_html_iframe);
- if (iframe == NULL) {
- nsurl_unref(url);
- return false;
- }
-
- talloc_set_destructor(iframe, box_iframes_talloc_destructor);
-
- iframe->box = box;
- iframe->margin_width = 0;
- iframe->margin_height = 0;
- iframe->name = NULL;
- iframe->url = url;
- iframe->scrolling = BW_SCROLLING_AUTO;
- iframe->border = true;
-
- /* Add this iframe to the linked list of iframes */
- iframe->next = content->iframe;
- content->iframe = iframe;
-
- /* fill in specified values */
- err = dom_element_get_attribute(n, corestring_dom_name, &s);
- if (err == DOM_NO_ERR && s != NULL) {
- iframe->name = talloc_strdup(content->bctx, dom_string_data(s));
- dom_string_unref(s);
- }
-
- err = dom_element_get_attribute(n, corestring_dom_frameborder, &s);
- if (err == DOM_NO_ERR && s != NULL) {
- i = atoi(dom_string_data(s));
- iframe->border = (i != 0);
- dom_string_unref(s);
- }
-
- err = dom_element_get_attribute(n, corestring_dom_bordercolor, &s);
- if (err == DOM_NO_ERR && s != NULL) {
- css_color color;
-
- if (nscss_parse_colour(dom_string_data(s), &color))
- iframe->border_colour = nscss_color_to_ns(color);
-
- dom_string_unref(s);
- }
-
- err = dom_element_get_attribute(n, corestring_dom_scrolling, &s);
- if (err == DOM_NO_ERR && s != NULL) {
- if (dom_string_caseless_lwc_isequal(s,
- corestring_lwc_yes))
- iframe->scrolling = BW_SCROLLING_YES;
- else if (dom_string_caseless_lwc_isequal(s,
- corestring_lwc_no))
- iframe->scrolling = BW_SCROLLING_NO;
- dom_string_unref(s);
- }
-
- err = dom_element_get_attribute(n, corestring_dom_marginwidth, &s);
- if (err == DOM_NO_ERR && s != NULL) {
- iframe->margin_width = atoi(dom_string_data(s));
- dom_string_unref(s);
- }
-
- err = dom_element_get_attribute(n, corestring_dom_marginheight, &s);
- if (err == DOM_NO_ERR && s != NULL) {
- iframe->margin_height = atoi(dom_string_data(s));
- dom_string_unref(s);
- }
-
- /* box */
- assert(box->style);
- box->flags |= IFRAME;
- box->flags |= IS_REPLACED;
-
- /* Showing iframe, so don't show alternate content */
- if (convert_children)
- *convert_children = false;
- return true;
-}
-
-
-/**
- * Helper function for adding textarea widget to box.
- *
- * This is a load of hacks to ensure boxes replaced with textareas
- * can be handled by the layout code.
- */
-
-static bool box_input_text(html_content *html, struct box *box,
- struct dom_node *node)
-{
- struct box *inline_container, *inline_box;
-
- box->type = BOX_INLINE_BLOCK;
-
- inline_container = box_create(NULL, 0, false, 0, 0, 0, 0, html->bctx);
- if (!inline_container)
- return false;
- inline_container->type = BOX_INLINE_CONTAINER;
- inline_box = box_create(NULL, box->style, false, 0, 0, box->title, 0,
- html->bctx);
- if (!inline_box)
- return false;
- inline_box->type = BOX_TEXT;
- inline_box->text = talloc_strdup(html->bctx, "");
-
- box_add_child(inline_container, inline_box);
- box_add_child(box, inline_container);
-
- return box_textarea_create_textarea(html, box, node);
-}
-
-
-/**
- * Form control [17.4].
- */
-
-bool box_input(BOX_SPECIAL_PARAMS)
-{
- struct form_control *gadget;
- 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 &lt;option&gt;
- * \param n xml element node for &lt;option&gt;
- * \return true on success, false on memory exhaustion
- */
-
-bool box_select_add_option(struct form_control *control, dom_node *n)
-{
- char *value = NULL;
- char *text = NULL;
- char *text_nowrap = NULL;
- bool selected;
- dom_string *content, *s;
- dom_exception err;
-
- err = dom_node_get_text_content(n, &content);
- if (err != DOM_NO_ERR)
- return false;
-
- if (content != NULL) {
- text = squash_whitespace(dom_string_data(content));
- dom_string_unref(content);
- } else {
- text = strdup("");
}
- if (text == NULL)
- goto no_memory;
-
- err = dom_element_get_attribute(n, corestring_dom_value, &s);
- if (err == DOM_NO_ERR && s != NULL) {
- value = strdup(dom_string_data(s));
- dom_string_unref(s);
- } else {
- value = strdup(text);
+ 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,
- &params->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 &lt;object&gt; elements
+ *
+ * \param o The object params being destroyed.
+ * \return 0 to allow talloc to continue destroying the tree.
+ */
+static int box_object_talloc_destructor(struct object_params *o)
+{
+ if (o->codebase != NULL)
+ nsurl_unref(o->codebase);
+ if (o->classid != NULL)
+ nsurl_unref(o->classid);
+ if (o->data != NULL)
+ nsurl_unref(o->data);
+
+ return 0;
+}
+
+
+/**
+ * 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 &lt;iframe&gt; elements
+ *
+ * \param f The iframe params being destroyed.
+ * \return 0 to allow talloc to continue destroying the tree.
+ */
+static int box_iframes_talloc_destructor(struct content_html_iframe *f)
+{
+ if (f->url != NULL) {
+ nsurl_unref(f->url);
+ f->url = NULL;
+ }
+
+ return 0;
+}
+
+
+/**
+ * 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 &lt;option&gt;
+ * \param n xml element node for &lt;option&gt;
+ * \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,
+ &params->data) == false) {
+ dom_string_unref(src);
+ return false;
+ }
+
+ dom_string_unref(src);
+
+ if (params->data == NULL)
+ return true;
+
+ /* Don't include ourself */
+ if (nsurl_compare(content->base_url, params->data, NSURL_COMPLETE))
+ return true;
+
+ /* add attributes as parameters to linked list */
+ err = dom_node_get_attributes(n, &attrs);
+ if (err != DOM_NO_ERR)
+ return false;
+
+ err = dom_namednodemap_get_length(attrs, &num_attrs);
+ if (err != DOM_NO_ERR) {
+ dom_namednodemap_unref(attrs);
+ return false;
+ }
+
+ for (idx = 0; idx < num_attrs; idx++) {
+ dom_attr *attr;
+ dom_string *name, *value;
+
+ err = dom_namednodemap_item(attrs, idx, (void *) &attr);
+ if (err != DOM_NO_ERR) {
+ dom_namednodemap_unref(attrs);
+ return false;
+ }
+
+ err = dom_attr_get_name(attr, &name);
+ if (err != DOM_NO_ERR) {
+ dom_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,
+ &params->codebase) == false) {
+ dom_string_unref(codebase);
+ return false;
+ }
+ dom_string_unref(codebase);
+ }
+ if (params->codebase == NULL)
+ params->codebase = nsurl_ref(content->base_url);
+
+ err = dom_element_get_attribute(n, corestring_dom_classid, &classid);
+ if (err == DOM_NO_ERR && classid != NULL) {
+ if (box_extract_link(content, classid,
+ params->codebase, &params->classid) == false) {
+ dom_string_unref(classid);
+ return false;
+ }
+ dom_string_unref(classid);
+ }
+
+ err = dom_element_get_attribute(n, corestring_dom_data, &data);
+ if (err == DOM_NO_ERR && data != NULL) {
+ if (box_extract_link(content, data,
+ params->codebase, &params->data) == false) {
+ dom_string_unref(data);
+ return false;
+ }
+ dom_string_unref(data);
+ }
+
+ if (params->classid == NULL && params->data == NULL)
+ /* nothing to embed; ignore */
+ return true;
+
+ /* Don't include ourself */
+ if (params->classid != NULL && nsurl_compare(content->base_url,
+ params->classid, NSURL_COMPLETE))
+ return true;
+
+ if (params->data != NULL && nsurl_compare(content->base_url,
+ params->data, NSURL_COMPLETE))
+ return true;
+
+ /* codetype and type are MIME types */
+ if (box_get_attribute(n, "codetype", params,
+ &params->codetype) == false)
+ return false;
+ if (box_get_attribute(n, "type", params, &params->type) == false)
+ return false;
+
+ /* classid && !data => classid is used (consult codetype)
+ * (classid || !classid) && data => data is used (consult type)
+ * !classid && !data => invalid; ignored */
+
+ if (params->classid != NULL && params->data == NULL &&
+ params->codetype != NULL) {
+ lwc_string *icodetype;
+ lwc_error lerror;
+
+ lerror = lwc_intern_string(params->codetype,
+ strlen(params->codetype), &icodetype);
+ if (lerror != lwc_error_ok)
+ return false;
+
+ if (content_factory_type_from_mime_type(icodetype) ==
+ CONTENT_NONE) {
+ /* can't handle this MIME type */
+ lwc_string_unref(icodetype);
+ return true;
+ }
+
+ lwc_string_unref(icodetype);
+ }
+
+ if (params->data != NULL && params->type != NULL) {
+ lwc_string *itype;
+ lwc_error lerror;
+
+ lerror = lwc_intern_string(params->type, strlen(params->type),
+ &itype);
+ if (lerror != lwc_error_ok)
+ return false;
+
+ if (content_factory_type_from_mime_type(itype) ==
+ CONTENT_NONE) {
+ /* can't handle this MIME type */
+ lwc_string_unref(itype);
+ return true;
+ }
+
+ lwc_string_unref(itype);
+ }
+
+ /* add parameters to linked list */
+ err = dom_node_get_first_child(n, &c);
+ if (err != DOM_NO_ERR)
+ return false;
+
+ while (c != NULL) {
+ dom_node *next;
+ dom_node_type type;
+
+ err = dom_node_get_node_type(c, &type);
+ if (err != DOM_NO_ERR) {
+ dom_node_unref(c);
+ return false;
+ }
+
+ if (type == DOM_ELEMENT_NODE) {
+ dom_string *name;
+
+ err = dom_node_get_node_name(c, &name);
+ if (err != DOM_NO_ERR) {
+ dom_node_unref(c);
+ return false;
+ }
+
+ if (!dom_string_caseless_lwc_isequal(name,
+ corestring_lwc_param)) {
+ /* The first non-param child is the start of
+ * the alt html. Therefore, we should break
+ * out of this loop. */
+ dom_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,
+ &param->name) == false) {
+ dom_node_unref(c);
+ return false;
+ }
+
+ if (box_get_attribute(c, "value", param,
+ &param->value) == false) {
+ dom_node_unref(c);
+ return false;
+ }
+
+ if (box_get_attribute(c, "type", param,
+ &param->type) == false) {
+ dom_node_unref(c);
+ return false;
+ }
+
+ if (box_get_attribute(c, "valuetype", param,
+ &param->valuetype) == false) {
+ dom_node_unref(c);
+ return false;
+ }
+
+ if (param->valuetype == NULL) {
+ param->valuetype = talloc_strdup(param, "data");
+ if (param->valuetype == NULL) {
+ dom_node_unref(c);
+ return false;
+ }
+ }
+
+ param->next = params->params;
+ params->params = param;
+ }
+
+ err = dom_node_get_next_sibling(c, &next);
+ if (err != DOM_NO_ERR) {
+ dom_node_unref(c);
+ return false;
+ }
+
+ dom_node_unref(c);
+ c = next;
+ }
+
+ box->object_params = params;
+
+ /* start fetch (MIME type is ok or not specified) */
+ box->flags |= IS_REPLACED;
+ if (!html_fetch_object(content,
+ params->data ? params->data : params->classid,
+ box,
+ CONTENT_ANY,
+ 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,
&current_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,
&current_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, &current_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