From b577de7c5bbba6c2f9a0b4ede3f814ea3023ded6 Mon Sep 17 00:00:00 2001 From: Vincent Sanders Date: Sun, 26 Apr 2020 11:19:26 +0100 Subject: remove forward declarations reorder module to remove need for forward function declarations. remove unecessary and confusing parameter macros --- content/handlers/html/box_construct.c | 4904 +++++++++++++++++---------------- 1 file changed, 2509 insertions(+), 2395 deletions(-) (limited to 'content/handlers/html/box_construct.c') diff --git a/content/handlers/html/box_construct.c b/content/handlers/html/box_construct.c index 6271405fe..b1f3461ac 100644 --- a/content/handlers/html/box_construct.c +++ b/content/handlers/html/box_construct.c @@ -99,153 +99,37 @@ 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 + * mapping from CSS display to box type this table must be in sync + * with libcss' css_display enum */ - -nserror dom_to_box(dom_node *n, html_content *c, box_construct_complete_cb cb, void **box_conversion_context) -{ - struct box_construct_ctx *ctx; - - assert(box_conversion_context != NULL); - - 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; - - *box_conversion_context = ctx; - - return guit->misc->schedule(0, (void *)convert_xml_to_box, ctx); -} - -nserror cancel_dom_to_box(void *box_conversion_context) -{ - struct box_construct_ctx *ctx = box_conversion_context; - nserror err; - - err = guit->misc->schedule(-1, (void *)convert_xml_to_box, ctx); - if (err != NSERROR_OK) { - return err; - } - - dom_node_unref(ctx->n); - free(ctx); - - return NSERROR_OK; -} - - -/* 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*/ + 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 */ }; -/* Exported function, see box.h */ -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; @@ -271,2844 +155,3074 @@ static inline bool box_is_root(dom_node *n) return true; } + /** - * Find the next node in the DOM tree, completing - * element construction where appropriate. - * - * \param n Current node - * \param content Containing content - * \param convert_children Whether to consider children of \a n - * \return Next node to process, or NULL if complete + * Destructor for object_params, for <object> elements * - * \note \a n will be unreferenced + * \param o The object params being destroyed. + * \return 0 to allow talloc to continue destroying the tree. */ -static dom_node *next_node(dom_node *n, html_content *content, - bool convert_children) +static int box_object_talloc_destructor(struct object_params *o) { - 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 (o->codebase != NULL) + nsurl_unref(o->codebase); + if (o->classid != NULL) + nsurl_unref(o->classid); + if (o->data != NULL) + nsurl_unref(o->data); - if (box_is_root(n) == false) { - dom_node *parent = NULL; + return 0; +} - err = dom_node_get_parent_node(n, &parent); - if (err != DOM_NO_ERR) { - dom_node_unref(n); - return NULL; - } - assert(parent != NULL); +/** + * 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; - err = dom_node_get_next_sibling(parent, &next); - if (err != DOM_NO_ERR) { - dom_node_unref(parent); - dom_node_unref(n); - return NULL; - } + s = dom_string_data(ds); - if (box_for_node(parent) != NULL) { - box_construct_element_after(parent, - content); - } + for (i = 0, n = 1; s[i]; i++) + if (s[i] == ',') + n++; - dom_node_unref(parent); - } + length = calloc(n, sizeof(struct frame_dimension)); + if (!length) + return NULL; - dom_node_unref(n); + 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++; } } - return next; + *count = n; + return length; } + /** - * Convert an ELEMENT node to a box tree fragment, - * then schedule conversion of the next ELEMENT node + * 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. */ -void convert_xml_to_box(struct box_construct_ctx *ctx) +static int box_frames_talloc_destructor(struct content_html_frames *f) { - dom_node *next; - bool convert_children; - uint32_t num_processed = 0; - const uint32_t max_processed_before_yield = 10; - - do { - convert_children = true; + if (f->url != NULL) { + nsurl_unref(f->url); + f->url = NULL; + } - assert(ctx->n != NULL); + return 0; +} - if (box_construct_element(ctx, &convert_children) == false) { - ctx->cb(ctx->content, false); - dom_node_unref(ctx->n); - free(ctx); - return; + +/** + * 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; + } - /* 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; + /* 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); + } - 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; + /* common extension: frameborder="yes|no" to control all children */ + err = dom_element_get_attribute(n, corestring_dom_frameborder, &s); + if (err == DOM_NO_ERR && s != NULL) { + if (dom_string_caseless_lwc_isequal(s, + corestring_lwc_no) == 0) + default_border = false; + dom_string_unref(s); + } + + /* common extension: bordercolor="#RRGGBB|" to control + *all children */ + err = dom_element_get_attribute(n, corestring_dom_bordercolor, &s); + if (err == DOM_NO_ERR && s != NULL) { + css_color color; + + if (nscss_parse_colour(dom_string_data(s), &color)) + default_border_colour = nscss_color_to_ns(color); + + dom_string_unref(s); + } + + /* update frameset and create default children */ + f->cols = cols; + f->rows = rows; + f->scrolling = BW_SCROLLING_NO; + f->children = talloc_array(content->bctx, struct content_html_frames, + (rows * cols)); + + talloc_set_destructor(f->children, box_frames_talloc_destructor); + + for (row = 0; row < rows; row++) { + for (col = 0; col < cols; col++) { + index = (row * cols) + col; + frame = &f->children[index]; + frame->cols = 0; + frame->rows = 0; + frame->width = col_width[col]; + frame->height = row_height[row]; + frame->margin_width = 0; + frame->margin_height = 0; + frame->name = NULL; + frame->url = NULL; + frame->no_resize = false; + frame->scrolling = BW_SCROLLING_AUTO; + frame->border = default_border; + frame->border_colour = default_border_colour; + frame->children = NULL; + } + } + free(col_width); + free(row_height); + + /* create the frameset windows */ + err = dom_node_get_first_child(n, &c); + if (err != DOM_NO_ERR) + return false; + + for (row = 0; c != NULL && row < rows; row++) { + for (col = 0; c != NULL && col < cols; col++) { + while (c != NULL) { + dom_node_type type; + dom_string *name; + + err = dom_node_get_node_type(c, &type); + if (err != DOM_NO_ERR) { + dom_node_unref(c); + return false; + } + + err = dom_node_get_node_name(c, &name); + if (err != DOM_NO_ERR) { + dom_node_unref(c); + return false; + } + + if (type != DOM_ELEMENT_NODE || + (!dom_string_caseless_lwc_isequal( + name, + corestring_lwc_frame) && + !dom_string_caseless_lwc_isequal( + name, + corestring_lwc_frameset + ))) { + err = dom_node_get_next_sibling(c, + &next); + if (err != DOM_NO_ERR) { + dom_string_unref(name); + dom_node_unref(c); + return false; + } + + dom_string_unref(name); + dom_node_unref(c); + c = next; + } else { + /* Got a FRAME or FRAMESET element */ + dom_string_unref(name); + break; + } } - if (type == DOM_ELEMENT_NODE) + if (c == NULL) break; - if (type == DOM_TEXT_NODE) { - ctx->n = next; - if (box_construct_text(ctx) == false) { - ctx->cb(ctx->content, false); - dom_node_unref(ctx->n); - free(ctx); - return; + /* 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; } - next = next_node(next, ctx->content, true); + dom_string_unref(s); + + /* get frame URL (not required) */ + url = NULL; + err = dom_element_get_attribute(c, corestring_dom_src, &s); + if (err == DOM_NO_ERR && s != NULL) { + box_extract_link(content, s, content->base_url, + &url); + dom_string_unref(s); + } + + /* copy url */ + if (url != NULL) { + /* no self-references */ + if (nsurl_compare(content->base_url, url, + NSURL_COMPLETE) == false) + frame->url = url; + url = NULL; + } + + /* fill in specified values */ + err = dom_element_get_attribute(c, corestring_dom_name, &s); + if (err == DOM_NO_ERR && s != NULL) { + frame->name = talloc_strdup(content->bctx, + dom_string_data(s)); + dom_string_unref(s); + } + + if (dom_element_has_attribute(c, corestring_dom_noresize, + &frame->no_resize) != DOM_NO_ERR) { + /* If we can't read the attribute for some reason, + * assume we didn't have it. + */ + frame->no_resize = false; + } + + err = dom_element_get_attribute(c, corestring_dom_frameborder, + &s); + if (err == DOM_NO_ERR && s != NULL) { + i = atoi(dom_string_data(s)); + frame->border = (i != 0); + dom_string_unref(s); + } + + err = dom_element_get_attribute(c, corestring_dom_scrolling, &s); + if (err == DOM_NO_ERR && s != NULL) { + if (dom_string_caseless_lwc_isequal(s, + corestring_lwc_yes)) + frame->scrolling = BW_SCROLLING_YES; + else if (dom_string_caseless_lwc_isequal(s, + corestring_lwc_no)) + frame->scrolling = BW_SCROLLING_NO; + dom_string_unref(s); + } + + err = dom_element_get_attribute(c, corestring_dom_marginwidth, + &s); + if (err == DOM_NO_ERR && s != NULL) { + frame->margin_width = atoi(dom_string_data(s)); + dom_string_unref(s); + } + + err = dom_element_get_attribute(c, corestring_dom_marginheight, + &s); + if (err == DOM_NO_ERR && s != NULL) { + frame->margin_height = atoi(dom_string_data(s)); + dom_string_unref(s); + } + + err = dom_element_get_attribute(c, corestring_dom_bordercolor, + &s); + if (err == DOM_NO_ERR && s != NULL) { + css_color color; + + if (nscss_parse_colour(dom_string_data(s), + &color)) + frame->border_colour = + nscss_color_to_ns(color); + + dom_string_unref(s); + } + + /* advance */ + err = dom_node_get_next_sibling(c, &next); + if (err != DOM_NO_ERR) { + dom_node_unref(c); + return false; + } + + dom_node_unref(c); + c = next; + } + } + + /* If the last child wasn't a frame, we still need to unref it */ + if (c != NULL) { + dom_node_unref(c); + } + + return true; +} + + +/** + * Destructor for content_html_iframe, for <iframe> elements + * + * \param f The iframe params being destroyed. + * \return 0 to allow talloc to continue destroying the tree. + */ +static int box_iframes_talloc_destructor(struct content_html_iframe *f) +{ + if (f->url != NULL) { + nsurl_unref(f->url); + f->url = NULL; + } + + return 0; +} + + +/** + * Get the value of a dom node element's attribute. + * + * \param n dom element node + * \param attribute name of attribute + * \param context talloc context for result buffer + * \param value updated to value, if the attribute is present + * \return true on success, false if attribute present but memory exhausted + * + * \note returning true does not imply that the attribute was found. If the + * attribute was not found, *value will be unchanged. + */ +static bool +box_get_attribute(dom_node *n, + const char *attribute, + void *context, + char **value) +{ + char *result; + dom_string *attr, *attr_name; + dom_exception err; + + err = dom_string_create_interned((const uint8_t *) attribute, + strlen(attribute), &attr_name); + if (err != DOM_NO_ERR) + return false; + + err = dom_element_get_attribute(n, attr_name, &attr); + if (err != DOM_NO_ERR) { + dom_string_unref(attr_name); + return false; + } + + dom_string_unref(attr_name); + + if (attr != NULL) { + result = talloc_strdup(context, dom_string_data(attr)); + + dom_string_unref(attr); + + if (result == NULL) + return false; + + *value = result; + } + + return true; +} + + +/** + * Helper function for adding textarea widget to box. + * + * This is a load of hacks to ensure boxes replaced with textareas + * can be handled by the layout code. + */ +static bool +box_input_text(html_content *html, struct box *box, struct dom_node *node) +{ + struct box *inline_container, *inline_box; + + 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); +} + + +/** + * Add an option to a form select control (helper function for box_select()). + * + * \param control select containing the <option> + * \param n xml element node for <option> + * \return true on success, false on memory exhaustion + */ +static bool box_select_add_option(struct form_control *control, dom_node *n) +{ + char *value = NULL; + char *text = NULL; + char *text_nowrap = NULL; + bool selected; + dom_string *content, *s; + dom_exception err; + + err = dom_node_get_text_content(n, &content); + if (err != DOM_NO_ERR) + return false; + + if (content != NULL) { + text = squash_whitespace(dom_string_data(content)); + dom_string_unref(content); + } else { + text = strdup(""); + } + + if (text == NULL) + goto no_memory; + + err = dom_element_get_attribute(n, corestring_dom_value, &s); + if (err == DOM_NO_ERR && s != NULL) { + value = strdup(dom_string_data(s)); + dom_string_unref(s); + } else { + value = strdup(text); + } + + if (value == NULL) + goto no_memory; + + if (dom_element_has_attribute(n, corestring_dom_selected, &selected) != DOM_NO_ERR) { + /* Assume not selected if we can't read the attribute presence */ + selected = false; + } + + /* replace spaces/TABs with hard spaces to prevent line wrapping */ + text_nowrap = cnv_space2nbsp(text); + if (text_nowrap == NULL) + goto no_memory; + + if (form_add_option(control, value, text_nowrap, selected, n) == false) + goto no_memory; + + free(text); + + return true; + +no_memory: + free(value); + free(text); + free(text_nowrap); + return false; +} + + +/** + * \name Special case element handlers + * + * These functions are called by box_construct_element() when an element is + * being converted, according to the entries in element_table. + * + * The parameters are the xmlNode, the content for the document, and a partly + * filled in box structure for the element. + * + * Return true on success, false on memory exhaustion. Set *convert_children + * to false if children of this element in the XML tree should be skipped (for + * example, if they have been processed in some special way already). + * + * Elements ordered as in the HTML 4.01 specification. Section numbers in + * brackets [] refer to the spec. + * + * \{ + */ + +/** + * special element handler for Anchor [12.2]. + */ +static bool +box_a(dom_node *n, + html_content *content, + struct box *box, + bool *convert_children) +{ + bool ok; + nsurl *url; + dom_string *s; + dom_exception err; + + err = dom_element_get_attribute(n, corestring_dom_href, &s); + if (err == DOM_NO_ERR && s != NULL) { + ok = box_extract_link(content, s, content->base_url, &url); + dom_string_unref(s); + if (!ok) + return false; + if (url) { + if (box->href != NULL) + nsurl_unref(box->href); + box->href = url; } + } - ctx->n = next; + /* 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; - if (next == NULL) { - /* Conversion complete */ - struct box root; + err = dom_string_intern(s, &lwc_name); - memset(&root, 0, sizeof(root)); + dom_string_unref(s); - root.type = BOX_BLOCK; - root.children = root.last = ctx->root_box; - root.children->parent = &root; + if (err == DOM_NO_ERR) { + /* name replaces existing id + * TODO: really? */ + if (box->id != NULL) + lwc_string_unref(box->id); - /** \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; + box->id = lwc_name; + } + } - ctx->cb(ctx->content, true); + /* target frame [16.3] */ + err = dom_element_get_attribute(n, corestring_dom_target, &s); + if (err == DOM_NO_ERR && s != NULL) { + if (dom_string_caseless_lwc_isequal(s, + corestring_lwc__blank)) + box->target = TARGET_BLANK; + else if (dom_string_caseless_lwc_isequal(s, + corestring_lwc__top)) + box->target = TARGET_TOP; + else if (dom_string_caseless_lwc_isequal(s, + corestring_lwc__parent)) + box->target = TARGET_PARENT; + else if (dom_string_caseless_lwc_isequal(s, + corestring_lwc__self)) + /* the default may have been overridden by a + * , so this is different to 0 */ + box->target = TARGET_SELF; + else { + /* 6.16 says that frame names must begin with [a-zA-Z] + * This doesn't match reality, so just take anything */ + box->target = talloc_strdup(content->bctx, + dom_string_data(s)); + if (!box->target) { + dom_string_unref(s); + return false; } - - assert(ctx->n == NULL); - - free(ctx); - return; } - } while (++num_processed < max_processed_before_yield); + dom_string_unref(s); + } - /* More work to do: schedule a continuation */ - guit->misc->schedule(0, (void *)convert_xml_to_box, ctx); + return true; } + /** - * Construct a list marker box - * - * \param box Box to attach marker to - * \param title Current title attribute - * \param ctx Box construction context - * \param parent Current block-level container - * \return true on success, false on memory exhaustion + * Document body special element handler [7.5.1]. */ -static bool box_construct_marker(struct box *box, const char *title, - struct box_construct_ctx *ctx, struct box *parent) +static bool +box_body(dom_node *n, + html_content *content, + struct box *box, + bool *convert_children) { - lwc_string *image_uri; - struct box *marker; - - marker = box_create(NULL, box->style, false, NULL, NULL, title, - NULL, ctx->bctx); - if (marker == false) - return false; - - marker->type = BOX_BLOCK; + css_color color; - /** \todo marker content (list-style-type) */ - switch (css_computed_list_style_type(box->style)) { - case CSS_LIST_STYLE_TYPE_DISC: - /* 2022 BULLET */ - marker->text = (char *) "\342\200\242"; - marker->length = 3; - break; - case CSS_LIST_STYLE_TYPE_CIRCLE: - /* 25CB WHITE CIRCLE */ - marker->text = (char *) "\342\227\213"; - marker->length = 3; - break; - case CSS_LIST_STYLE_TYPE_SQUARE: - /* 25AA BLACK SMALL SQUARE */ - marker->text = (char *) "\342\226\252"; - marker->length = 3; - break; - case CSS_LIST_STYLE_TYPE_DECIMAL: - case CSS_LIST_STYLE_TYPE_LOWER_ALPHA: - case CSS_LIST_STYLE_TYPE_LOWER_ROMAN: - case CSS_LIST_STYLE_TYPE_UPPER_ALPHA: - case CSS_LIST_STYLE_TYPE_UPPER_ROMAN: - default: - if (parent->last) { - struct box *last = parent->last; + 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); + } - /* 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; + return true; +} - 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; - } - } +/** + * 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; +} - marker->text = talloc_array(ctx->bctx, char, 20); - if (marker->text == NULL) - return false; - snprintf(marker->text, 20, "%u.", marker->rows); - marker->length = strlen(marker->text); - break; - case CSS_LIST_STYLE_TYPE_NONE: - marker->text = 0; - marker->length = 0; - break; - } +/** + * 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; - if (css_computed_list_style_image(box->style, &image_uri) == CSS_LIST_STYLE_IMAGE_URI && - (image_uri != NULL) && - (nsoption_bool(foreground_images) == true)) { - nsurl *url; - nserror error; + gadget = html_forms_get_control_for_node(content->forms, n); + if (!gadget) + return false; - /* TODO: we get a url out of libcss as a lwc string, but - * earlier we already had it as a nsurl after we - * nsurl_joined it. Can this be improved? - * For now, just making another nsurl. */ - error = nsurl_create(lwc_string_data(image_uri), &url); - if (error != NSERROR_OK) - return false; + gadget->html = content; + box->gadget = gadget; + box->flags |= IS_REPLACED; + gadget->box = box; - if (html_fetch_object(ctx->content, url, marker, image_types, - ctx->content->base.available_width, 1000, false) == - false) { - nsurl_unref(url); - return false; - } - nsurl_unref(url); - } + box->type = BOX_INLINE_BLOCK; - box->list_marker = marker; - marker->parent = box; + /* Just render the contents */ 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 ) + * Embedded object (not in any HTML specification: + * see http://wp.netscape.com/assist/net_sites/new_html3_prop.html ) */ -static void box_construct_generate(dom_node *n, html_content *content, - struct box *box, const css_computed_style *style) +static bool +box_embed(dom_node *n, + html_content *content, + struct box *box, + bool *convert_children) { - struct box *gen = NULL; - enum css_display_e computed_display; - const css_computed_content_item *c_item; + struct object_params *params; + struct object_param *param; + dom_namednodemap *attrs; + unsigned long idx; + uint32_t num_attrs; + dom_string *src; + dom_exception err; + + if (box->style && + ns_computed_display(box->style, box_is_root(n)) == CSS_DISPLAY_NONE) + return true; + + params = talloc(content->bctx, struct object_params); + if (params == NULL) + return false; + + talloc_set_destructor(params, box_object_talloc_destructor); + + params->data = NULL; + params->type = NULL; + params->codetype = NULL; + params->codebase = NULL; + params->classid = NULL; + params->params = NULL; + + /* src is a URL */ + err = dom_element_get_attribute(n, corestring_dom_src, &src); + if (err != DOM_NO_ERR || src == NULL) + return true; + if (box_extract_link(content, src, content->base_url, + ¶ms->data) == false) { + dom_string_unref(src); + return false; + } - /* Nothing to generate if the parent box is not a block */ - if (box->type != BOX_BLOCK) - return; + dom_string_unref(src); - /* 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; + 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; } - /* 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 */ + for (idx = 0; idx < num_attrs; idx++) { + dom_attr *attr; + dom_string *name, *value; - /** \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; + err = dom_namednodemap_item(attrs, idx, (void *) &attr); + if (err != DOM_NO_ERR) { + dom_namednodemap_unref(attrs); + return false; } - /* set box type from computed display */ - gen->type = box_map[ns_computed_display( - style, box_is_root(n))]; + err = dom_attr_get_name(attr, &name); + if (err != DOM_NO_ERR) { + dom_node_unref(attr); + dom_namednodemap_unref(attrs); + return false; + } - box_add_child(box, gen); - } -} + if (dom_string_caseless_lwc_isequal(name, corestring_lwc_src)) { + dom_node_unref(attr); + dom_string_unref(name); + continue; + } -/** - * Extract transient construction properties - * - * \param n Current DOM node to convert - * \param props Property object to populate - */ -static void box_extract_properties(dom_node *n, - struct box_construct_props *props) -{ - memset(props, 0, sizeof(*props)); + 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; + } - props->node_is_root = box_is_root(n); + 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; + } - /* 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; + 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; - /* 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; + dom_string_unref(value); + dom_string_unref(name); + dom_node_unref(attr); - parent_box = box_for_node(parent_node); + if (param->name == NULL || param->value == NULL || + param->valuetype == NULL) { + dom_namednodemap_unref(attrs); + return false; + } - 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; + param->next = params->params; + params->params = param; + } - dom_node_unref(parent_node); - break; - } else { - if (current_node != n) - dom_node_unref(current_node); - current_node = parent_node; - parent_node = NULL; - } - } + dom_namednodemap_unref(attrs); - /* Find containing block (may be parent) */ - while (true) { - struct box *b; + box->object_params = params; - 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; - } + /* start fetch */ + box->flags |= IS_REPLACED; + return html_fetch_object(content, params->data, box, CONTENT_ANY, + content->base.available_width, 1000, false); +} - if (current_node != n) - dom_node_unref(current_node); - b = box_for_node(parent_node); +/** + * Window subdivision [16.2.1]. + */ +static bool +box_frameset(dom_node *n, + html_content *content, + struct box *box, + bool *convert_children) +{ + bool ok; - /* 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; + 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; + } - dom_node_unref(parent_node); - break; - } else { - current_node = parent_node; - parent_node = NULL; - } - } + content->frameset = talloc_zero(content->bctx, + struct content_html_frames); + if (!content->frameset) { + return false; } - /* 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; + ok = box_create_frameset(content->frameset, n, content); + if (ok) { + box->type = BOX_NONE; + } + + if (convert_children) { + *convert_children = false; + } + return ok; } + /** - * Construct the box tree for an XML element. - * - * \param ctx Tree construction context - * \param convert_children Whether to convert children - * \return true on success, false on memory exhaustion + * Inline subwindow [16.5]. */ - -bool box_construct_element(struct box_construct_ctx *ctx, - bool *convert_children) +static bool +box_iframe(dom_node *n, + html_content *content, + struct box *box, + bool *convert_children) { - dom_string *title0, *s; - lwc_string *id = NULL; - struct box *box = NULL, *old_box; - css_select_results *styles = NULL; - struct element_entry *element; - lwc_string *bgimage_uri; + nsurl *url; + dom_string *s; dom_exception err; - struct box_construct_props props; - const css_computed_style *root_style = NULL; - - assert(ctx->n != NULL); + struct content_html_iframe *iframe; + int i; - box_extract_properties(ctx->n, &props); + if (box->style && + ns_computed_display(box->style, box_is_root(n)) == CSS_DISPLAY_NONE) + return true; - if (props.containing_block != NULL) { - /* In case the containing block is a pre block, we clear - * the PRE_STRIP flag since it is not used if we follow - * the pre with a tag */ - props.containing_block->flags &= ~PRE_STRIP; + if (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; } - if (props.node_is_root == false) { - root_style = ctx->root_box->style; + /* 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; - styles = box_get_style(ctx->content, props.parent_style, root_style, - ctx->n); - if (styles == NULL) - return false; + /* don't include ourself */ + if (nsurl_compare(content->base_url, url, NSURL_COMPLETE)) { + nsurl_unref(url); + return true; + } - /* Extract title attribute, if present */ - err = dom_element_get_attribute(ctx->n, corestring_dom_title, &title0); - if (err != DOM_NO_ERR) + /* create a new iframe */ + iframe = talloc(content->bctx, struct content_html_iframe); + if (iframe == NULL) { + nsurl_unref(url); return false; + } - if (title0 != NULL) { - char *t = squash_whitespace(dom_string_data(title0)); - - dom_string_unref(title0); - - if (t == NULL) - return false; + talloc_set_destructor(iframe, box_iframes_talloc_destructor); - props.title = talloc_strdup(ctx->bctx, t); + 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; - free(t); + /* Add this iframe to the linked list of iframes */ + iframe->next = content->iframe; + content->iframe = iframe; - if (props.title == NULL) - return false; + /* 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); } - /* Extract id attribute, if present */ - err = dom_element_get_attribute(ctx->n, corestring_dom_id, &s); - if (err != DOM_NO_ERR) - return false; - - if (s != NULL) { - err = dom_string_intern(s, &id); - if (err != DOM_NO_ERR) - id = NULL; - + 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); } - box = box_create(styles, styles->styles[CSS_PSEUDO_ELEMENT_NONE], false, - props.href, props.target, props.title, id, - ctx->bctx); - if (box == NULL) - return false; + err = dom_element_get_attribute(n, corestring_dom_bordercolor, &s); + if (err == DOM_NO_ERR && s != NULL) { + css_color color; - /* If this is the root box, add it to the context */ - if (props.node_is_root) - ctx->root_box = box; + if (nscss_parse_colour(dom_string_data(s), &color)) + iframe->border_colour = nscss_color_to_ns(color); - /* Deal with colspan/rowspan */ - err = dom_element_get_attribute(ctx->n, corestring_dom_colspan, &s); - if (err != DOM_NO_ERR) - return false; + dom_string_unref(s); + } - if (s != NULL) { - const char *val = dom_string_data(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); + } - if ('0' <= val[0] && val[0] <= '9') - box->columns = strtol(val, NULL, 10); + 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); } - err = dom_element_get_attribute(ctx->n, corestring_dom_rowspan, &s); - if (err != DOM_NO_ERR) - return false; + /* box */ + assert(box->style); + box->flags |= IFRAME; + box->flags |= IS_REPLACED; - if (s != NULL) { - const char *val = dom_string_data(s); + /* Showing iframe, so don't show alternate content */ + if (convert_children) + *convert_children = false; + return true; +} - if ('0' <= val[0] && val[0] <= '9') - box->rows = strtol(val, NULL, 10); +/** + * 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); } - /* Set box type from computed display */ - if ((css_computed_position(box->style) == CSS_POSITION_ABSOLUTE || - css_computed_position(box->style) == - CSS_POSITION_FIXED) && - (ns_computed_display_static(box->style) == - CSS_DISPLAY_INLINE || - ns_computed_display_static(box->style) == - CSS_DISPLAY_INLINE_BLOCK || - ns_computed_display_static(box->style) == - CSS_DISPLAY_INLINE_TABLE)) { - /* Special case for absolute positioning: make absolute inlines - * into inline block so that the boxes are constructed in an - * inline container as if they were not absolutely positioned. - * Layout expects and handles this. */ - box->type = box_map[CSS_DISPLAY_INLINE_BLOCK]; - } else if (props.node_is_root) { - /* Special case for root element: force it to BLOCK, or the - * rest of the layout will break. */ - box->type = BOX_BLOCK; - } else { - /* Normal mapping */ - box->type = box_map[ns_computed_display(box->style, - props.node_is_root)]; + if (nsoption_bool(foreground_images) == false) { + return true; } - err = dom_node_get_node_name(ctx->n, &s); - if (err != DOM_NO_ERR || s == NULL) + /* 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++; - /* Special elements */ - element = bsearch(dom_string_data(s), element_table, - ELEMENT_TABLE_COUNT, sizeof(element_table[0]), - (int (*)(const void *, const void *)) strcasecmp); + /* 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 (element != NULL) { - /* A special convert function exists for this element */ - if (element->convert(ctx->n, ctx->content, box, - convert_children) == false) - return false; - } + if (url == NULL) + return true; - /* Handle the :before pseudo element */ - if (!(box->flags & IS_REPLACED)) { - box_construct_generate(ctx->n, ctx->content, box, - box->styles->styles[CSS_PSEUDO_ELEMENT_BEFORE]); - } + /* start fetch */ + box->flags |= IS_REPLACED; + ok = html_fetch_object(content, url, box, image_types, + content->base.available_width, 1000, false); + nsurl_unref(url); - if (box->type == BOX_NONE || (ns_computed_display(box->style, - props.node_is_root) == CSS_DISPLAY_NONE && - props.node_is_root == false)) { - css_select_results_destroy(styles); - box->styles = NULL; - box->style = NULL; + wtype = css_computed_width(box->style, &value, &wunit); + htype = css_computed_height(box->style, &value, &hunit); - /* Invalidate associated gadget, if any */ - if (box->gadget != NULL) { - box->gadget->box = NULL; - box->gadget = NULL; - } + 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; + } - /* Can't do this, because the lifetimes of boxes and gadgets - * are inextricably linked. Fortunately, talloc will save us - * (for now) */ - /* box_free_box(box); */ + return ok; +} - *convert_children = false; - return true; - } +/** + * 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; - /* Attach DOM node to box */ - err = dom_node_set_user_data(ctx->n, - corestring_dom___ns_key_box_node_data, box, NULL, - (void *) &old_box); - if (err != DOM_NO_ERR) + gadget = html_forms_get_control_for_node(content->forms, n); + if (gadget == NULL) { return false; + } - /* Attach box to DOM node */ - box->node = dom_node_ref(ctx->n); - - if (props.inline_container == NULL && - (box->type == BOX_INLINE || - box->type == BOX_BR || - box->type == BOX_INLINE_BLOCK || - css_computed_float(box->style) == CSS_FLOAT_LEFT || - css_computed_float(box->style) == CSS_FLOAT_RIGHT) && - props.node_is_root == false) { - /* Found an inline child of a block without a current container - * (i.e. this box is the first child of its parent, or was - * preceded by block-level siblings) */ - assert(props.containing_block != NULL && - "Box must have containing block."); + box->gadget = gadget; + box->flags |= IS_REPLACED; + gadget->box = box; + gadget->html = content; - props.inline_container = box_create(NULL, NULL, false, NULL, - NULL, NULL, NULL, ctx->bctx); - if (props.inline_container == NULL) + /* 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; - - props.inline_container->type = BOX_INLINE_CONTAINER; - - box_add_child(props.containing_block, props.inline_container); + } + *convert_children = false; + return true; } - /* Kick off fetch for any background image */ - if (css_computed_background_image(box->style, &bgimage_uri) == - CSS_BACKGROUND_IMAGE_IMAGE && bgimage_uri != NULL && - nsoption_bool(background_images) == true) { - nsurl *url; - nserror error; + if (dom_string_caseless_lwc_isequal(type, corestring_lwc_password)) { + if (box_input_text(content, box, n) == false) + goto no_memory; - /* TODO: we get a url out of libcss as a lwc string, but - * earlier we already had it as a nsurl after we - * nsurl_joined it. Can this be improved? - * For now, just making another nsurl. */ - error = nsurl_create(lwc_string_data(bgimage_uri), &url); - if (error == NSERROR_OK) { - /* Fetch image if we got a valid URL */ - if (html_fetch_object(ctx->content, url, box, - image_types, - ctx->content->base.available_width, - 1000, true) == false) { - nsurl_unref(url); - return false; - } - nsurl_unref(url); - } - } + } else if (dom_string_caseless_lwc_isequal(type, corestring_lwc_file)) { + box->type = BOX_INLINE_BLOCK; - if (*convert_children) - box->flags |= CONVERT_CHILDREN; + } else if (dom_string_caseless_lwc_isequal(type, + corestring_lwc_hidden)) { + /* no box for hidden inputs */ + box->type = BOX_NONE; - if (box->type == BOX_INLINE || box->type == BOX_BR || - box->type == BOX_INLINE_BLOCK) { - /* Inline container must exist, as we'll have - * created it above if it didn't */ - assert(props.inline_container != NULL); + } else if ((dom_string_caseless_lwc_isequal(type, + corestring_lwc_checkbox) || + dom_string_caseless_lwc_isequal(type, + corestring_lwc_radio))) { - box_add_child(props.inline_container, box); - } else { - if (ns_computed_display(box->style, props.node_is_root) == - CSS_DISPLAY_LIST_ITEM) { - /* List item: compute marker */ - if (box_construct_marker(box, props.title, ctx, - props.containing_block) == false) - return false; - } + } 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 (props.node_is_root == false && - (css_computed_float(box->style) == - CSS_FLOAT_LEFT || - css_computed_float(box->style) == - CSS_FLOAT_RIGHT)) { - /* Float: insert a float between the parent and box. */ - struct box *flt = box_create(NULL, NULL, false, - props.href, props.target, props.title, - NULL, ctx->bctx); - if (flt == NULL) - return false; + if (box_button(n, content, box, 0) == false) + goto no_memory; - if (css_computed_float(box->style) == CSS_FLOAT_LEFT) - flt->type = BOX_FLOAT_LEFT; - else - flt->type = BOX_FLOAT_RIGHT; + inline_container = box_create(NULL, 0, false, 0, 0, 0, 0, + content->bctx); + if (inline_container == NULL) + goto no_memory; - box_add_child(props.inline_container, flt); - box_add_child(flt, box); - } else { - /* Non-floated block-level box: add to containing block - * if there is one. If we're the root box, then there - * won't be. */ - if (props.containing_block != NULL) - box_add_child(props.containing_block, box); - } - } + inline_container->type = BOX_INLINE_CONTAINER; - return true; -} + inline_box = box_create(NULL, box->style, false, 0, 0, + box->title, 0, content->bctx); + if (inline_box == NULL) + goto no_memory; -/** - * Complete construction of the box tree for an element. - * - * \param n DOM node to construct for - * \param content Containing document - * - * This will be called after all children of an element have been processed - */ -void box_construct_element_after(dom_node *n, html_content *content) -{ - struct box_construct_props props; - struct box *box = box_for_node(n); + inline_box->type = BOX_TEXT; - assert(box != NULL); + 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"); - box_extract_properties(n, &props); + if (inline_box->text == NULL) + goto no_memory; - if (box->type == BOX_INLINE || box->type == BOX_BR) { - /* Insert INLINE_END into containing block */ - struct box *inline_end; - bool has_children; - dom_exception err; + inline_box->length = strlen(inline_box->text); - err = dom_node_has_child_nodes(n, &has_children); - if (err != DOM_NO_ERR) - return; + box_add_child(inline_container, inline_box); - if (has_children == false || - (box->flags & CONVERT_CHILDREN) == 0) { - /* No children, or didn't want children converted */ - return; - } + box_add_child(box, inline_container); - if (props.inline_container == NULL) { - /* Create inline container if we don't have one */ - props.inline_container = box_create(NULL, NULL, false, - NULL, NULL, NULL, NULL, content->bctx); - if (props.inline_container == NULL) - return; + } else if (dom_string_caseless_lwc_isequal(type, + corestring_lwc_image)) { + gadget->type = GADGET_IMAGE; - props.inline_container->type = BOX_INLINE_CONTAINER; + if (box->style && + ns_computed_display(box->style, + box_is_root(n)) != CSS_DISPLAY_NONE && + nsoption_bool(foreground_images) == true) { + dom_string *s; - box_add_child(props.containing_block, - props.inline_container); + err = dom_element_get_attribute(n, corestring_dom_src, &s); + if (err == DOM_NO_ERR && s != NULL) { + error = nsurl_join(content->base_url, + dom_string_data(s), &url); + dom_string_unref(s); + if (error != NSERROR_OK) + goto no_memory; + + /* if url is equivalent to the parent's url, + * we've got infinite inclusion. stop it here + */ + if (nsurl_compare(url, content->base_url, + NSURL_COMPLETE) == false) { + if (!html_fetch_object(content, url, + box, image_types, + content->base. + available_width, + 1000, false)) { + nsurl_unref(url); + goto no_memory; + } + } + nsurl_unref(url); + } } + } else { + /* unhandled type the default is "text" */ + if (box_input_text(content, box, n) == false) + goto no_memory; + } - inline_end = box_create(NULL, box->style, false, - box->href, box->target, box->title, - box->id == NULL ? NULL : - lwc_string_ref(box->id), content->bctx); - if (inline_end != NULL) { - inline_end->type = BOX_INLINE_END; + dom_string_unref(type); - assert(props.inline_container != NULL); + *convert_children = false; - box_add_child(props.inline_container, inline_end); + return true; - box->inline_end = inline_end; - inline_end->inline_end = box; - } - } else if (!(box->flags & IS_REPLACED)) { - /* Handle the :after pseudo element */ - box_construct_generate(n, content, box, - box->styles->styles[CSS_PSEUDO_ELEMENT_AFTER]); - } +no_memory: + dom_string_unref(type); + + return false; } + /** - * Construct the box tree for an XML text node. - * - * \param ctx Tree construction context - * \return true on success, false on memory exhaustion + * Noscript element */ - -bool box_construct_text(struct box_construct_ctx *ctx) +static bool +box_noscript(dom_node *n, + html_content *content, + struct box *box, + bool *convert_children) { - struct box_construct_props props; - struct box *box = NULL; - dom_string *content; - dom_exception err; - - assert(ctx->n != NULL); - - box_extract_properties(ctx->n, &props); - - assert(props.containing_block != NULL); + /* If scripting is enabled, do not display the contents of noscript */ + if (content->enable_scripting) { + *convert_children = false; + } - err = dom_characterdata_get_data(ctx->n, &content); - if (err != DOM_NO_ERR || content == NULL) - return false; + return true; +} - if (css_computed_white_space(props.parent_style) == - CSS_WHITE_SPACE_NORMAL || - css_computed_white_space(props.parent_style) == - CSS_WHITE_SPACE_NOWRAP) { - char *text; - text = squash_whitespace(dom_string_data(content)); +/** + * 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; - dom_string_unref(content); + if (box->style && + ns_computed_display(box->style, box_is_root(n)) == CSS_DISPLAY_NONE) + return true; - if (text == NULL) - return false; + if (box_get_attribute(n, "usemap", content->bctx, &box->usemap) == + false) + return false; + if (box->usemap && box->usemap[0] == '#') + box->usemap++; - /* if the text is just a space, combine it with the preceding - * text node, if any */ - if (text[0] == ' ' && text[1] == 0) { - if (props.inline_container != NULL) { - assert(props.inline_container->last != NULL); + params = talloc(content->bctx, struct object_params); + if (params == NULL) + return false; - props.inline_container->last->space = - UNKNOWN_WIDTH; - } + talloc_set_destructor(params, box_object_talloc_destructor); - free(text); + params->data = NULL; + params->type = NULL; + params->codetype = NULL; + params->codebase = NULL; + params->classid = NULL; + params->params = NULL; - return true; + /* codebase, classid, and data are URLs + * (codebase is the base for the other two) */ + err = dom_element_get_attribute(n, corestring_dom_codebase, &codebase); + if (err == DOM_NO_ERR && codebase != NULL) { + if (box_extract_link(content, codebase, content->base_url, + ¶ms->codebase) == false) { + dom_string_unref(codebase); + return false; } + dom_string_unref(codebase); + } + if (params->codebase == NULL) + params->codebase = nsurl_ref(content->base_url); - if (props.inline_container == NULL) { - /* Child of a block without a current container - * (i.e. this box is the first child of its parent, or - * was preceded by block-level siblings) */ - props.inline_container = box_create(NULL, NULL, false, - NULL, NULL, NULL, NULL, ctx->bctx); - if (props.inline_container == NULL) { - free(text); - return false; - } - - props.inline_container->type = BOX_INLINE_CONTAINER; - - box_add_child(props.containing_block, - props.inline_container); + err = dom_element_get_attribute(n, corestring_dom_classid, &classid); + if (err == DOM_NO_ERR && classid != NULL) { + if (box_extract_link(content, classid, + params->codebase, ¶ms->classid) == false) { + dom_string_unref(classid); + return false; } + dom_string_unref(classid); + } - /** \todo Dropping const here is not clever */ - box = box_create(NULL, - (css_computed_style *) props.parent_style, - false, props.href, props.target, props.title, - NULL, ctx->bctx); - if (box == NULL) { - free(text); + err = dom_element_get_attribute(n, corestring_dom_data, &data); + if (err == DOM_NO_ERR && data != NULL) { + if (box_extract_link(content, data, + params->codebase, ¶ms->data) == false) { + dom_string_unref(data); return false; } + dom_string_unref(data); + } - box->type = BOX_TEXT; - - box->text = talloc_strdup(ctx->bctx, text); - free(text); - if (box->text == NULL) - return false; + if (params->classid == NULL && params->data == NULL) + /* nothing to embed; ignore */ + return true; - box->length = strlen(box->text); + /* Don't include ourself */ + if (params->classid != NULL && nsurl_compare(content->base_url, + params->classid, NSURL_COMPLETE)) + return true; - /* strip ending space char off */ - if (box->length > 1 && box->text[box->length - 1] == ' ') { - box->space = UNKNOWN_WIDTH; - box->length--; - } + if (params->data != NULL && nsurl_compare(content->base_url, + params->data, NSURL_COMPLETE)) + return true; - if (css_computed_text_transform(props.parent_style) != - CSS_TEXT_TRANSFORM_NONE) - box_text_transform(box->text, box->length, - css_computed_text_transform( - props.parent_style)); + /* codetype and type are MIME types */ + if (box_get_attribute(n, "codetype", params, + ¶ms->codetype) == false) + return false; + if (box_get_attribute(n, "type", params, ¶ms->type) == false) + return false; - box_add_child(props.inline_container, box); + /* classid && !data => classid is used (consult codetype) + * (classid || !classid) && data => data is used (consult type) + * !classid && !data => invalid; ignored */ - if (box->text[0] == ' ') { - box->length--; + if (params->classid != NULL && params->data == NULL && + params->codetype != NULL) { + lwc_string *icodetype; + lwc_error lerror; - memmove(box->text, &box->text[1], box->length); + lerror = lwc_intern_string(params->codetype, + strlen(params->codetype), &icodetype); + if (lerror != lwc_error_ok) + return false; - if (box->prev != NULL) - box->prev->space = UNKNOWN_WIDTH; + if (content_factory_type_from_mime_type(icodetype) == + CONTENT_NONE) { + /* can't handle this MIME type */ + lwc_string_unref(icodetype); + return true; } - } else { - /* white-space: pre */ - char *text; - size_t text_len = dom_string_byte_length(content); - size_t i; - char *current; - enum css_white_space_e white_space = - css_computed_white_space(props.parent_style); - /* note: pre-wrap/pre-line are unimplemented */ - assert(white_space == CSS_WHITE_SPACE_PRE || - white_space == CSS_WHITE_SPACE_PRE_LINE || - white_space == CSS_WHITE_SPACE_PRE_WRAP); + lwc_string_unref(icodetype); + } - text = malloc(text_len + 1); - dom_string_unref(content); + if (params->data != NULL && params->type != NULL) { + lwc_string *itype; + lwc_error lerror; - if (text == NULL) + lerror = lwc_intern_string(params->type, strlen(params->type), + &itype); + if (lerror != lwc_error_ok) return false; - memcpy(text, dom_string_data(content), text_len); - text[text_len] = '\0'; + if (content_factory_type_from_mime_type(itype) == + CONTENT_NONE) { + /* can't handle this MIME type */ + lwc_string_unref(itype); + return true; + } - /* TODO: Handle tabs properly */ - for (i = 0; i < text_len; i++) - if (text[i] == '\t') - text[i] = ' '; + lwc_string_unref(itype); + } - if (css_computed_text_transform(props.parent_style) != - CSS_TEXT_TRANSFORM_NONE) - box_text_transform(text, strlen(text), - css_computed_text_transform( - props.parent_style)); + /* add parameters to linked list */ + err = dom_node_get_first_child(n, &c); + if (err != DOM_NO_ERR) + return false; - current = text; + while (c != NULL) { + dom_node *next; + dom_node_type type; - /* swallow a single leading new line */ - if (props.containing_block->flags & PRE_STRIP) { - switch (*current) { - case '\n': - current++; - break; - case '\r': - current++; - if (*current == '\n') - current++; - break; - } - props.containing_block->flags &= ~PRE_STRIP; + err = dom_node_get_node_type(c, &type); + if (err != DOM_NO_ERR) { + dom_node_unref(c); + return false; } - do { - size_t len = strcspn(current, "\r\n"); - - char old = current[len]; - - current[len] = 0; - - if (props.inline_container == NULL) { - /* Child of a block without a current container - * (i.e. this box is the first child of its - * parent, or was preceded by block-level - * siblings) */ - props.inline_container = box_create(NULL, NULL, - false, NULL, NULL, NULL, NULL, - ctx->bctx); - if (props.inline_container == NULL) { - free(text); - return false; - } + if (type == DOM_ELEMENT_NODE) { + dom_string *name; - props.inline_container->type = - BOX_INLINE_CONTAINER; + err = dom_node_get_node_name(c, &name); + if (err != DOM_NO_ERR) { + dom_node_unref(c); + return false; + } - box_add_child(props.containing_block, - props.inline_container); + 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); - /** \todo Dropping const isn't clever */ - box = box_create(NULL, - (css_computed_style *) props.parent_style, - false, props.href, props.target, props.title, - NULL, ctx->bctx); - if (box == NULL) { - free(text); + param = talloc(params, struct object_param); + if (param == NULL) { + dom_node_unref(c); + return false; + } + param->name = NULL; + param->value = NULL; + param->type = NULL; + param->valuetype = NULL; + param->next = NULL; + + if (box_get_attribute(c, "name", param, + ¶m->name) == false) { + dom_node_unref(c); return false; } - box->type = BOX_TEXT; + if (box_get_attribute(c, "value", param, + ¶m->value) == false) { + dom_node_unref(c); + return false; + } - box->text = talloc_strdup(ctx->bctx, current); - if (box->text == NULL) { - free(text); + if (box_get_attribute(c, "type", param, + ¶m->type) == false) { + dom_node_unref(c); return false; } - box->length = strlen(box->text); + if (box_get_attribute(c, "valuetype", param, + ¶m->valuetype) == false) { + dom_node_unref(c); + return false; + } - box_add_child(props.inline_container, box); + if (param->valuetype == NULL) { + param->valuetype = talloc_strdup(param, "data"); + if (param->valuetype == NULL) { + dom_node_unref(c); + return false; + } + } - current[len] = old; + param->next = params->params; + params->params = param; + } - current += len; + err = dom_node_get_next_sibling(c, &next); + if (err != DOM_NO_ERR) { + dom_node_unref(c); + return false; + } - if (current[0] != '\0') { - /* Linebreak: create new inline container */ - props.inline_container = box_create(NULL, NULL, - false, NULL, NULL, NULL, NULL, - ctx->bctx); - if (props.inline_container == NULL) { - free(text); - return false; - } + dom_node_unref(c); + c = next; + } - props.inline_container->type = - BOX_INLINE_CONTAINER; + box->object_params = params; - box_add_child(props.containing_block, - props.inline_container); + /* 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; - if (current[0] == '\r' && current[1] == '\n') - current += 2; - else - current++; - } - } while (*current); + *convert_children = false; + return true; +} - free(text); - } +/** + * 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; } + /** - * 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 + * Option selector [17.6]. */ -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 bool +box_select(dom_node *n, + html_content *content, + struct box *box, + bool *convert_children) { - dom_string *s; + struct box *inline_container; + struct box *inline_box; + struct form_control *gadget; + dom_node *c, *c2; + dom_node *next, *next2; 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; + gadget = html_forms_get_control_for_node(content->forms, n); + if (gadget == NULL) + return false; - 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); + gadget->html = content; + err = dom_node_get_first_child(n, &c); + if (err != DOM_NO_ERR) { + form_free_control(gadget); + return false; + } - dom_string_unref(s); + while (c != NULL) { + dom_string *name; - if (inline_style == NULL) - return NULL; - } + err = dom_node_get_node_name(c, &name); + if (err != DOM_NO_ERR) { + dom_node_unref(c); + form_free_control(gadget); + return false; + } - /* 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; + if (dom_string_caseless_lwc_isequal(name, + corestring_lwc_option)) { + dom_string_unref(name); - /* Select style for element */ - styles = nscss_get_style(&ctx, n, &c->media, inline_style); + 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); - /* No longer need inline style */ - if (inline_style != NULL) - css_stylesheet_destroy(inline_style); + err = dom_node_get_first_child(c, &c2); + if (err != DOM_NO_ERR) { + dom_node_unref(c); + form_free_control(gadget); + return false; + } - return styles; -} + 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; + } -/** - * 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 - */ + if (dom_string_caseless_lwc_isequal(c2_name, + corestring_lwc_option)) { + dom_string_unref(c2_name); -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; - } -} + 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); + } -/** - * \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. - * - * \{ - */ + 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; + } -/** - * Document body [7.5.1]. - */ + if (gadget->data.select.num_items == 0) { + /* no options: ignore entire select */ + form_free_control(gadget); + return true; + } -bool box_body(BOX_SPECIAL_PARAMS) -{ - css_color color; + box->type = BOX_INLINE_BLOCK; + box->gadget = gadget; + box->flags |= IS_REPLACED; + gadget->box = box; - css_computed_background_color(box->style, &color); - if (nscss_color_is_transparent(color)) - content->background_colour = NS_TRANSPARENT; + 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 - content->background_colour = nscss_color_to_ns(color); + 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; } /** - * Forced line break [9.3.2]. + * Multi-line text field [17.7]. */ - -bool box_br(BOX_SPECIAL_PARAMS) +static bool box_textarea(dom_node *n, + html_content *content, + struct box *box, + bool *convert_children) { - box->type = BOX_BR; - return true; -} + /* 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; -/** - * Preformatted text [9.3.4]. - */ + box->flags |= IS_REPLACED; + box->gadget->html = content; + box->gadget->box = box; -bool box_pre(BOX_SPECIAL_PARAMS) -{ - box->flags |= PRE_STRIP; + if (!box_input_text(content, box, n)) + return false; + + *convert_children = false; 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; - } - } +/** + * special element table entry + */ +struct element_entry { + char name[10]; /* element type */ + bool (*convert)(dom_node *n, + html_content *content, + struct box *box, + bool *convert_children); +}; - /* target frame [16.3] */ - err = dom_element_get_attribute(n, corestring_dom_target, &s); - if (err == DOM_NO_ERR && s != NULL) { - if (dom_string_caseless_lwc_isequal(s, - corestring_lwc__blank)) - box->target = TARGET_BLANK; - else if (dom_string_caseless_lwc_isequal(s, - corestring_lwc__top)) - box->target = TARGET_TOP; - else if (dom_string_caseless_lwc_isequal(s, - corestring_lwc__parent)) - box->target = TARGET_PARENT; - else if (dom_string_caseless_lwc_isequal(s, - corestring_lwc__self)) - /* the default may have been overridden by a - * , so this is different to 0 */ - box->target = TARGET_SELF; - else { - /* 6.16 says that frame names must begin with [a-zA-Z] - * This doesn't match reality, so just take anything */ - box->target = talloc_strdup(content->bctx, - dom_string_data(s)); - if (!box->target) { - dom_string_unref(s); - return false; - } - } - dom_string_unref(s); - } - return true; -} +/** + * special element handler table + * + * \note the table must be sorted ascending alphabeticaly by name + */ +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])) /** - * Embedded image [13.2]. + * Extract transient construction properties + * + * \param n Current DOM node to convert + * \param props Property object to populate */ - -bool box_image(BOX_SPECIAL_PARAMS) +static void +box_extract_properties(dom_node *n, struct box_construct_props *props) { - 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; - } + memset(props, 0, sizeof(*props)); - /* 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++; + props->node_is_root = box_is_root(n); - /* get image URL */ - err = dom_element_get_attribute(n, corestring_dom_src, &s); - if (err != DOM_NO_ERR || s == NULL) - return true; + /* 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; - if (box_extract_link(content, s, content->base_url, &url) == false) { - dom_string_unref(s); - return false; - } + /* 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; - dom_string_unref(s); + parent_box = box_for_node(parent_node); - if (url == NULL) - return true; + 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; - /* start fetch */ - box->flags |= IS_REPLACED; - ok = html_fetch_object(content, url, box, image_types, - content->base.available_width, 1000, false); - nsurl_unref(url); + dom_node_unref(parent_node); + break; + } else { + if (current_node != n) + dom_node_unref(current_node); + current_node = parent_node; + parent_node = NULL; + } + } - wtype = css_computed_width(box->style, &value, &wunit); - htype = css_computed_height(box->style, &value, &hunit); + /* Find containing block (may be parent) */ + while (true) { + struct box *b; - 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; - } + 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; + } - return ok; -} + if (current_node != n) + dom_node_unref(current_node); + b = box_for_node(parent_node); -/** - * Noscript element - */ + /* 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; -bool box_noscript(BOX_SPECIAL_PARAMS) -{ - /* If scripting is enabled, do not display the contents of noscript */ - if (content->enable_scripting) - *convert_children = false; + dom_node_unref(parent_node); + break; + } else { + current_node = parent_node; + parent_node = NULL; + } + } + } - return true; + /* 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; } /** - * Destructor for object_params, for <object> elements + * Get the style for an element. * - * \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]. + * \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 */ - -bool box_object(BOX_SPECIAL_PARAMS) +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) { - struct object_params *params; - struct object_param *param; - dom_string *codebase, *classid, *data; - dom_node *c; + dom_string *s; dom_exception err; + css_stylesheet *inline_style = NULL; + css_select_results *styles; + nscss_select_ctx ctx; - 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; + /* Firstly, construct inline stylesheet, if any */ + err = dom_element_get_attribute(n, corestring_dom_style, &s); + if (err != DOM_NO_ERR) + return NULL; - /* codebase, classid, and data are URLs - * (codebase is the base for the other two) */ - err = dom_element_get_attribute(n, corestring_dom_codebase, &codebase); - if (err == DOM_NO_ERR && codebase != NULL) { - if (box_extract_link(content, codebase, content->base_url, - ¶ms->codebase) == false) { - dom_string_unref(codebase); - return false; - } - dom_string_unref(codebase); - } - if (params->codebase == NULL) - params->codebase = nsurl_ref(content->base_url); + 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_element_get_attribute(n, corestring_dom_classid, &classid); - if (err == DOM_NO_ERR && classid != NULL) { - if (box_extract_link(content, classid, - params->codebase, ¶ms->classid) == false) { - dom_string_unref(classid); - return false; - } - dom_string_unref(classid); - } + dom_string_unref(s); - err = dom_element_get_attribute(n, corestring_dom_data, &data); - if (err == DOM_NO_ERR && data != NULL) { - if (box_extract_link(content, data, - params->codebase, ¶ms->data) == false) { - dom_string_unref(data); - return false; - } - dom_string_unref(data); + if (inline_style == NULL) + return NULL; } - if (params->classid == NULL && params->data == NULL) - /* nothing to embed; ignore */ - return true; - - /* Don't include ourself */ - if (params->classid != NULL && nsurl_compare(content->base_url, - params->classid, NSURL_COMPLETE)) - return true; - - if (params->data != NULL && nsurl_compare(content->base_url, - params->data, NSURL_COMPLETE)) - return true; - - /* codetype and type are MIME types */ - if (box_get_attribute(n, "codetype", params, - ¶ms->codetype) == false) - return false; - if (box_get_attribute(n, "type", params, ¶ms->type) == false) - return false; - - /* classid && !data => classid is used (consult codetype) - * (classid || !classid) && data => data is used (consult type) - * !classid && !data => invalid; ignored */ - - if (params->classid != NULL && params->data == NULL && - params->codetype != NULL) { - lwc_string *icodetype; - lwc_error lerror; + /* 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; - lerror = lwc_intern_string(params->codetype, - strlen(params->codetype), &icodetype); - if (lerror != lwc_error_ok) - return false; + /* Select style for element */ + styles = nscss_get_style(&ctx, n, &c->media, inline_style); - if (content_factory_type_from_mime_type(icodetype) == - CONTENT_NONE) { - /* can't handle this MIME type */ - lwc_string_unref(icodetype); - return true; - } + /* No longer need inline style */ + if (inline_style != NULL) + css_stylesheet_destroy(inline_style); - lwc_string_unref(icodetype); - } + return styles; +} - 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; +/** + * 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; - if (content_factory_type_from_mime_type(itype) == - CONTENT_NONE) { - /* can't handle this MIME type */ - lwc_string_unref(itype); - return true; - } + /* Nothing to generate if the parent box is not a block */ + if (box->type != BOX_BLOCK) + return; - lwc_string_unref(itype); + /* 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; } - /* 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; + /* 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 */ - err = dom_node_get_node_type(c, &type); - if (err != DOM_NO_ERR) { - dom_node_unref(c); - return false; + /** \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; } - if (type == DOM_ELEMENT_NODE) { - dom_string *name; + /* set box type from computed display */ + gen->type = box_map[ns_computed_display( + style, box_is_root(n))]; - err = dom_node_get_node_name(c, &name); - if (err != DOM_NO_ERR) { - dom_node_unref(c); - return false; - } + box_add_child(box, gen); + } +} - 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; +/** + * Construct a list marker box + * + * \param box Box to attach marker to + * \param title Current title attribute + * \param ctx Box construction context + * \param parent Current block-level container + * \return true on success, false on memory exhaustion + */ +static bool +box_construct_marker(struct box *box, + const char *title, + struct box_construct_ctx *ctx, + struct box *parent) +{ + lwc_string *image_uri; + struct box *marker; - if (box_get_attribute(c, "name", param, - ¶m->name) == false) { - dom_node_unref(c); - return false; - } + marker = box_create(NULL, box->style, false, NULL, NULL, title, + NULL, ctx->bctx); + if (marker == false) + return false; - if (box_get_attribute(c, "value", param, - ¶m->value) == false) { - dom_node_unref(c); - return false; - } + marker->type = BOX_BLOCK; - if (box_get_attribute(c, "type", param, - ¶m->type) == false) { - dom_node_unref(c); - return false; - } + /** \todo marker content (list-style-type) */ + switch (css_computed_list_style_type(box->style)) { + case CSS_LIST_STYLE_TYPE_DISC: + /* 2022 BULLET */ + marker->text = (char *) "\342\200\242"; + marker->length = 3; + break; + case CSS_LIST_STYLE_TYPE_CIRCLE: + /* 25CB WHITE CIRCLE */ + marker->text = (char *) "\342\227\213"; + marker->length = 3; + break; + case CSS_LIST_STYLE_TYPE_SQUARE: + /* 25AA BLACK SMALL SQUARE */ + marker->text = (char *) "\342\226\252"; + marker->length = 3; + break; + case CSS_LIST_STYLE_TYPE_DECIMAL: + case CSS_LIST_STYLE_TYPE_LOWER_ALPHA: + case CSS_LIST_STYLE_TYPE_LOWER_ROMAN: + case CSS_LIST_STYLE_TYPE_UPPER_ALPHA: + case CSS_LIST_STYLE_TYPE_UPPER_ROMAN: + default: + if (parent->last) { + struct box *last = parent->last; - if (box_get_attribute(c, "valuetype", param, - ¶m->valuetype) == false) { - dom_node_unref(c); - return false; - } + /* 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; - if (param->valuetype == NULL) { - param->valuetype = talloc_strdup(param, "data"); - if (param->valuetype == NULL) { - dom_node_unref(c); - return false; + 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; } } - param->next = params->params; - params->params = param; + if (last && last->list_marker) { + marker->rows = last->list_marker->rows + 1; + } } - err = dom_node_get_next_sibling(c, &next); - if (err != DOM_NO_ERR) { - dom_node_unref(c); + marker->text = talloc_array(ctx->bctx, char, 20); + if (marker->text == NULL) return false; - } - dom_node_unref(c); - c = next; + snprintf(marker->text, 20, "%u.", marker->rows); + marker->length = strlen(marker->text); + break; + case CSS_LIST_STYLE_TYPE_NONE: + marker->text = 0; + marker->length = 0; + break; } - box->object_params = params; + if (css_computed_list_style_image(box->style, &image_uri) == CSS_LIST_STYLE_IMAGE_URI && + (image_uri != NULL) && + (nsoption_bool(foreground_images) == true)) { + nsurl *url; + nserror error; - /* 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; + /* TODO: we get a url out of libcss as a lwc string, but + * earlier we already had it as a nsurl after we + * nsurl_joined it. Can this be improved? + * For now, just making another nsurl. */ + error = nsurl_create(lwc_string_data(image_uri), &url); + if (error != NSERROR_OK) + return false; + + if (html_fetch_object(ctx->content, url, marker, image_types, + ctx->content->base.available_width, 1000, false) == + false) { + nsurl_unref(url); + return false; + } + nsurl_unref(url); + } + + box->list_marker = marker; + marker->parent = box; - *convert_children = false; return true; } /** - * Window subdivision [16.2.1]. + * Construct the box tree for an XML element. + * + * \param ctx Tree construction context + * \param convert_children Whether to convert children + * \return true on success, false on memory exhaustion */ - -bool box_frameset(BOX_SPECIAL_PARAMS) +static bool +box_construct_element(struct box_construct_ctx *ctx, 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; + dom_string *title0, *s; + lwc_string *id = NULL; + struct box *box = NULL, *old_box; + css_select_results *styles = NULL; + struct element_entry *element; + lwc_string *bgimage_uri; + dom_exception err; + struct box_construct_props props; + const css_computed_style *root_style = NULL; - ok = box_create_frameset(content->frameset, n, content); - if (ok) - box->type = BOX_NONE; + assert(ctx->n != NULL); - if (convert_children) - *convert_children = false; - return ok; -} + box_extract_properties(ctx->n, &props); + if (props.containing_block != NULL) { + /* In case the containing block is a pre block, we clear + * the PRE_STRIP flag since it is not used if we follow + * the pre with a tag */ + props.containing_block->flags &= ~PRE_STRIP; + } -/** - * 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; + if (props.node_is_root == false) { + root_style = ctx->root_box->style; } - return 0; -} + styles = box_get_style(ctx->content, props.parent_style, root_style, + ctx->n); + if (styles == NULL) + return false; + + /* Extract title attribute, if present */ + err = dom_element_get_attribute(ctx->n, corestring_dom_title, &title0); + if (err != DOM_NO_ERR) + return false; + if (title0 != NULL) { + char *t = squash_whitespace(dom_string_data(title0)); -/** - * 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; + dom_string_unref(title0); - s = dom_string_data(ds); + if (t == NULL) + return false; - for (i = 0, n = 1; s[i]; i++) - if (s[i] == ',') - n++; + props.title = talloc_strdup(ctx->bctx, t); - length = calloc(n, sizeof(struct frame_dimension)); - if (!length) - return NULL; + free(t); - 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++; - } + if (props.title == NULL) + return false; } - *count = n; - return length; -} - + /* Extract id attribute, if present */ + err = dom_element_get_attribute(ctx->n, corestring_dom_id, &s); + if (err != DOM_NO_ERR) + return false; -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; + if (s != NULL) { + err = dom_string_intern(s, &id); + if (err != DOM_NO_ERR) + id = NULL; - /* 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); + box = box_create(styles, styles->styles[CSS_PSEUDO_ELEMENT_NONE], false, + props.href, props.target, props.title, id, + ctx->bctx); + if (box == NULL) + return false; + + /* If this is the root box, add it to the context */ + if (props.node_is_root) + ctx->root_box = box; + + /* Deal with colspan/rowspan */ + err = dom_element_get_attribute(ctx->n, corestring_dom_colspan, &s); + if (err != DOM_NO_ERR) + return false; + + if (s != NULL) { + const char *val = dom_string_data(s); + + if ('0' <= val[0] && val[0] <= '9') + box->columns = strtol(val, NULL, 10); + dom_string_unref(s); - 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; + err = dom_element_get_attribute(ctx->n, corestring_dom_rowspan, &s); + if (err != DOM_NO_ERR) + return false; + + if (s != NULL) { + const char *val = dom_string_data(s); + + if ('0' <= val[0] && val[0] <= '9') + box->rows = strtol(val, NULL, 10); + dom_string_unref(s); } - /* 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); + /* Set box type from computed display */ + if ((css_computed_position(box->style) == CSS_POSITION_ABSOLUTE || + css_computed_position(box->style) == + CSS_POSITION_FIXED) && + (ns_computed_display_static(box->style) == + CSS_DISPLAY_INLINE || + ns_computed_display_static(box->style) == + CSS_DISPLAY_INLINE_BLOCK || + ns_computed_display_static(box->style) == + CSS_DISPLAY_INLINE_TABLE)) { + /* Special case for absolute positioning: make absolute inlines + * into inline block so that the boxes are constructed in an + * inline container as if they were not absolutely positioned. + * Layout expects and handles this. */ + box->type = box_map[CSS_DISPLAY_INLINE_BLOCK]; + } else if (props.node_is_root) { + /* Special case for root element: force it to BLOCK, or the + * rest of the layout will break. */ + box->type = BOX_BLOCK; + } else { + /* Normal mapping */ + box->type = box_map[ns_computed_display(box->style, + props.node_is_root)]; } - /* common extension: bordercolor="#RRGGBB|" to control - *all children */ - err = dom_element_get_attribute(n, corestring_dom_bordercolor, &s); - if (err == DOM_NO_ERR && s != NULL) { - css_color color; + err = dom_node_get_node_name(ctx->n, &s); + if (err != DOM_NO_ERR || s == NULL) + return false; - if (nscss_parse_colour(dom_string_data(s), &color)) - default_border_colour = nscss_color_to_ns(color); + /* 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); + 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; } - /* 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)); + /* Handle the :before pseudo element */ + if (!(box->flags & IS_REPLACED)) { + box_construct_generate(ctx->n, ctx->content, box, + box->styles->styles[CSS_PSEUDO_ELEMENT_BEFORE]); + } - talloc_set_destructor(f->children, box_frames_talloc_destructor); + if (box->type == BOX_NONE || (ns_computed_display(box->style, + props.node_is_root) == CSS_DISPLAY_NONE && + props.node_is_root == false)) { + css_select_results_destroy(styles); + box->styles = NULL; + box->style = NULL; - 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; + /* Invalidate associated gadget, if any */ + if (box->gadget != NULL) { + box->gadget->box = NULL; + box->gadget = NULL; } + + /* Can't do this, because the lifetimes of boxes and gadgets + * are inextricably linked. Fortunately, talloc will save us + * (for now) */ + /* box_free_box(box); */ + + *convert_children = false; + + return true; } - free(col_width); - free(row_height); - /* create the frameset windows */ - err = dom_node_get_first_child(n, &c); + /* Attach DOM node to box */ + err = dom_node_set_user_data(ctx->n, + corestring_dom___ns_key_box_node_data, box, NULL, + (void *) &old_box); if (err != DOM_NO_ERR) return false; - 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; + /* Attach box to DOM node */ + box->node = dom_node_ref(ctx->n); + + if (props.inline_container == NULL && + (box->type == BOX_INLINE || + box->type == BOX_BR || + box->type == BOX_INLINE_BLOCK || + css_computed_float(box->style) == CSS_FLOAT_LEFT || + css_computed_float(box->style) == CSS_FLOAT_RIGHT) && + props.node_is_root == false) { + /* Found an inline child of a block without a current container + * (i.e. this box is the first child of its parent, or was + * preceded by block-level siblings) */ + assert(props.containing_block != NULL && + "Box must have containing block."); + + props.inline_container = box_create(NULL, NULL, false, NULL, + NULL, NULL, NULL, ctx->bctx); + if (props.inline_container == NULL) + return false; - err = dom_node_get_node_type(c, &type); - if (err != DOM_NO_ERR) { - dom_node_unref(c); - return false; - } + props.inline_container->type = BOX_INLINE_CONTAINER; - err = dom_node_get_node_name(c, &name); - if (err != DOM_NO_ERR) { - dom_node_unref(c); - return false; - } + box_add_child(props.containing_block, props.inline_container); + } - 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; - } + /* Kick off fetch for any background image */ + if (css_computed_background_image(box->style, &bgimage_uri) == + CSS_BACKGROUND_IMAGE_IMAGE && bgimage_uri != NULL && + nsoption_bool(background_images) == true) { + nsurl *url; + nserror error; - dom_string_unref(name); - dom_node_unref(c); - c = next; - } else { - /* Got a FRAME or FRAMESET element */ - dom_string_unref(name); - break; - } + /* TODO: we get a url out of libcss as a lwc string, but + * earlier we already had it as a nsurl after we + * nsurl_joined it. Can this be improved? + * For now, just making another nsurl. */ + error = nsurl_create(lwc_string_data(bgimage_uri), &url); + if (error == NSERROR_OK) { + /* Fetch image if we got a valid URL */ + if (html_fetch_object(ctx->content, url, box, + image_types, + ctx->content->base.available_width, + 1000, true) == false) { + nsurl_unref(url); + return false; } + nsurl_unref(url); + } + } - if (c == NULL) - break; + if (*convert_children) + box->flags |= CONVERT_CHILDREN; - /* get current frame */ - index = (row * cols) + col; - frame = &f->children[index]; + if (box->type == BOX_INLINE || box->type == BOX_BR || + box->type == BOX_INLINE_BLOCK) { + /* Inline container must exist, as we'll have + * created it above if it didn't */ + assert(props.inline_container != NULL); - /* nest framesets */ - err = dom_node_get_node_name(c, &s); - if (err != DOM_NO_ERR) { - dom_node_unref(c); + box_add_child(props.inline_container, box); + } else { + if (ns_computed_display(box->style, props.node_is_root) == + CSS_DISPLAY_LIST_ITEM) { + /* List item: compute marker */ + if (box_construct_marker(box, props.title, ctx, + props.containing_block) == false) return false; - } + } - if (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; - } + if (props.node_is_root == false && + (css_computed_float(box->style) == + CSS_FLOAT_LEFT || + css_computed_float(box->style) == + CSS_FLOAT_RIGHT)) { + /* Float: insert a float between the parent and box. */ + struct box *flt = box_create(NULL, NULL, false, + props.href, props.target, props.title, + NULL, ctx->bctx); + if (flt == NULL) + return false; - err = dom_node_get_next_sibling(c, &next); - if (err != DOM_NO_ERR) { - dom_node_unref(c); - return false; - } + if (css_computed_float(box->style) == CSS_FLOAT_LEFT) + flt->type = BOX_FLOAT_LEFT; + else + flt->type = BOX_FLOAT_RIGHT; - dom_node_unref(c); - c = next; - continue; - } + box_add_child(props.inline_container, flt); + box_add_child(flt, box); + } else { + /* Non-floated block-level box: add to containing block + * if there is one. If we're the root box, then there + * won't be. */ + if (props.containing_block != NULL) + box_add_child(props.containing_block, box); + } + } - dom_string_unref(s); + return true; +} - /* 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; - } +/** + * Complete construction of the box tree for an element. + * + * \param n DOM node to construct for + * \param content Containing document + * + * This will be called after all children of an element have been processed + */ +static void box_construct_element_after(dom_node *n, html_content *content) +{ + struct box_construct_props props; + struct box *box = box_for_node(n); - /* 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); - } + assert(box != NULL); - 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; - } + box_extract_properties(n, &props); - 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); - } + if (box->type == BOX_INLINE || box->type == BOX_BR) { + /* Insert INLINE_END into containing block */ + struct box *inline_end; + bool has_children; + dom_exception err; - err = dom_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_node_has_child_nodes(n, &has_children); + if (err != DOM_NO_ERR) + return; - 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); - } + if (has_children == false || + (box->flags & CONVERT_CHILDREN) == 0) { + /* No children, or didn't want children converted */ + return; + } - 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 (props.inline_container == NULL) { + /* Create inline container if we don't have one */ + props.inline_container = box_create(NULL, NULL, false, + NULL, NULL, NULL, NULL, content->bctx); + if (props.inline_container == NULL) + return; - err = dom_element_get_attribute(c, corestring_dom_bordercolor, - &s); - if (err == DOM_NO_ERR && s != NULL) { - css_color color; + props.inline_container->type = BOX_INLINE_CONTAINER; - if (nscss_parse_colour(dom_string_data(s), - &color)) - frame->border_colour = - nscss_color_to_ns(color); + box_add_child(props.containing_block, + props.inline_container); + } + + inline_end = box_create(NULL, box->style, false, + box->href, box->target, box->title, + box->id == NULL ? NULL : + lwc_string_ref(box->id), content->bctx); + if (inline_end != NULL) { + inline_end->type = BOX_INLINE_END; - dom_string_unref(s); - } + assert(props.inline_container != NULL); - /* advance */ - err = dom_node_get_next_sibling(c, &next); - if (err != DOM_NO_ERR) { - dom_node_unref(c); - return false; - } + box_add_child(props.inline_container, inline_end); - dom_node_unref(c); - c = next; + box->inline_end = inline_end; + inline_end->inline_end = box; } + } else if (!(box->flags & IS_REPLACED)) { + /* Handle the :after pseudo element */ + box_construct_generate(n, content, box, + box->styles->styles[CSS_PSEUDO_ELEMENT_AFTER]); } - - /* If the last child wasn't a frame, we still need to unref it */ - if (c != NULL) { - dom_node_unref(c); - } - - return true; } /** - * Destructor for content_html_iframe, for <iframe> elements + * Find the next node in the DOM tree, completing element construction + * where appropriate. * - * \param f The iframe params being destroyed. - * \return 0 to allow talloc to continue destroying the tree. + * \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 int box_iframes_talloc_destructor(struct content_html_iframe *f) +static dom_node * +next_node(dom_node *n, html_content *content, bool convert_children) { - if (f->url != NULL) { - nsurl_unref(f->url); - f->url = NULL; - } + dom_node *next = NULL; + bool has_children; + dom_exception err; - return 0; -} + 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; + } -/** - * Inline subwindow [16.5]. - */ + 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); -bool box_iframe(BOX_SPECIAL_PARAMS) -{ - nsurl *url; - dom_string *s; - dom_exception err; - struct content_html_iframe *iframe; - int i; + while (box_is_root(n) == false) { + dom_node *parent = NULL; + dom_node *parent_next = NULL; - if (box->style && ns_computed_display(box->style, - box_is_root(n)) == CSS_DISPLAY_NONE) - return true; + err = dom_node_get_parent_node(n, &parent); + if (err != DOM_NO_ERR) { + dom_node_unref(n); + return NULL; + } - 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; + assert(parent != NULL); - /* 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; + err = dom_node_get_next_sibling(parent, + &parent_next); + if (err != DOM_NO_ERR) { + dom_node_unref(parent); + dom_node_unref(n); + return NULL; + } - /* don't include ourself */ - if (nsurl_compare(content->base_url, url, NSURL_COMPLETE)) { - nsurl_unref(url); - return true; - } + if (parent_next != NULL) { + dom_node_unref(parent_next); + dom_node_unref(parent); + break; + } - /* create a new iframe */ - iframe = talloc(content->bctx, struct content_html_iframe); - if (iframe == NULL) { - nsurl_unref(url); - return false; - } + dom_node_unref(n); + n = parent; + parent = NULL; - talloc_set_destructor(iframe, box_iframes_talloc_destructor); + if (box_for_node(n) != NULL) { + box_construct_element_after( + n, content); + } + } - 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; + if (box_is_root(n) == false) { + dom_node *parent = NULL; - /* Add this iframe to the linked list of iframes */ - iframe->next = content->iframe; - content->iframe = iframe; + err = dom_node_get_parent_node(n, &parent); + if (err != DOM_NO_ERR) { + dom_node_unref(n); + return NULL; + } - /* 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); - } + assert(parent != NULL); - 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_node_get_next_sibling(parent, &next); + if (err != DOM_NO_ERR) { + dom_node_unref(parent); + dom_node_unref(n); + return NULL; + } - err = dom_element_get_attribute(n, corestring_dom_bordercolor, &s); - if (err == DOM_NO_ERR && s != NULL) { - css_color color; + if (box_for_node(parent) != NULL) { + box_construct_element_after(parent, + content); + } - if (nscss_parse_colour(dom_string_data(s), &color)) - iframe->border_colour = nscss_color_to_ns(color); + dom_node_unref(parent); + } - dom_string_unref(s); + dom_node_unref(n); + } } - 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); - } + return next; +} - 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); +/** + * 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] = 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; } - - /* 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. + * Construct the box tree for an XML text node. * - * This is a load of hacks to ensure boxes replaced with textareas - * can be handled by the layout code. + * \param ctx Tree construction context + * \return true on success, false on memory exhaustion */ - -static bool box_input_text(html_content *html, struct box *box, - struct dom_node *node) +static bool box_construct_text(struct box_construct_ctx *ctx) { - struct box *inline_container, *inline_box; + struct box_construct_props props; + struct box *box = NULL; + dom_string *content; + dom_exception err; - box->type = BOX_INLINE_BLOCK; + assert(ctx->n != NULL); - 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) + box_extract_properties(ctx->n, &props); + + assert(props.containing_block != NULL); + + err = dom_characterdata_get_data(ctx->n, &content); + if (err != DOM_NO_ERR || content == NULL) return false; - 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); + if (css_computed_white_space(props.parent_style) == + CSS_WHITE_SPACE_NORMAL || + css_computed_white_space(props.parent_style) == + CSS_WHITE_SPACE_NOWRAP) { + char *text; - return box_textarea_create_textarea(html, box, node); -} + text = squash_whitespace(dom_string_data(content)); + dom_string_unref(content); -/** - * Form control [17.4]. - */ + if (text == NULL) + return false; -bool box_input(BOX_SPECIAL_PARAMS) -{ - struct form_control *gadget; - dom_string *type = NULL; - dom_exception err; - nsurl *url; - nserror error; + /* if the text is just a space, combine it with the preceding + * text node, if any */ + if (text[0] == ' ' && text[1] == 0) { + if (props.inline_container != NULL) { + assert(props.inline_container->last != NULL); - gadget = html_forms_get_control_for_node(content->forms, n); - if (gadget == NULL) { - return false; - } + props.inline_container->last->space = + UNKNOWN_WIDTH; + } - box->gadget = gadget; - box->flags |= IS_REPLACED; - gadget->box = box; - gadget->html = content; + free(text); - /* 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 true; + } + + if (props.inline_container == NULL) { + /* Child of a block without a current container + * (i.e. this box is the first child of its parent, or + * was preceded by block-level siblings) */ + props.inline_container = box_create(NULL, NULL, false, + NULL, NULL, NULL, NULL, ctx->bctx); + if (props.inline_container == NULL) { + free(text); + return false; + } + + props.inline_container->type = BOX_INLINE_CONTAINER; + + box_add_child(props.containing_block, + props.inline_container); + } + + /** \todo Dropping const here is not clever */ + box = box_create(NULL, + (css_computed_style *) props.parent_style, + false, props.href, props.target, props.title, + NULL, ctx->bctx); + if (box == NULL) { + free(text); return false; } - *convert_children = false; - return true; - } + box->type = BOX_TEXT; - if (dom_string_caseless_lwc_isequal(type, corestring_lwc_password)) { - if (box_input_text(content, box, n) == false) - goto no_memory; + box->text = talloc_strdup(ctx->bctx, text); + free(text); + if (box->text == NULL) + return false; - } else if (dom_string_caseless_lwc_isequal(type, corestring_lwc_file)) { - box->type = BOX_INLINE_BLOCK; + box->length = strlen(box->text); - } else if (dom_string_caseless_lwc_isequal(type, - corestring_lwc_hidden)) { - /* no box for hidden inputs */ - box->type = BOX_NONE; + /* strip ending space char off */ + if (box->length > 1 && box->text[box->length - 1] == ' ') { + box->space = UNKNOWN_WIDTH; + box->length--; + } - } else if ((dom_string_caseless_lwc_isequal(type, - corestring_lwc_checkbox) || - dom_string_caseless_lwc_isequal(type, - corestring_lwc_radio))) { + if (css_computed_text_transform(props.parent_style) != + CSS_TEXT_TRANSFORM_NONE) + box_text_transform(box->text, box->length, + css_computed_text_transform( + props.parent_style)); - } 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; + box_add_child(props.inline_container, box); - if (box_button(n, content, box, 0) == false) - goto no_memory; + if (box->text[0] == ' ') { + box->length--; - inline_container = box_create(NULL, 0, false, 0, 0, 0, 0, - content->bctx); - if (inline_container == NULL) - goto no_memory; + memmove(box->text, &box->text[1], box->length); - inline_container->type = BOX_INLINE_CONTAINER; + if (box->prev != NULL) + box->prev->space = UNKNOWN_WIDTH; + } + } else { + /* white-space: pre */ + char *text; + size_t text_len = dom_string_byte_length(content); + size_t i; + char *current; + enum css_white_space_e white_space = + css_computed_white_space(props.parent_style); - inline_box = box_create(NULL, box->style, false, 0, 0, - box->title, 0, content->bctx); - if (inline_box == NULL) - goto no_memory; + /* note: pre-wrap/pre-line are unimplemented */ + assert(white_space == CSS_WHITE_SPACE_PRE || + white_space == CSS_WHITE_SPACE_PRE_LINE || + white_space == CSS_WHITE_SPACE_PRE_WRAP); - inline_box->type = BOX_TEXT; + text = malloc(text_len + 1); + dom_string_unref(content); - 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 (text == NULL) + return false; - if (inline_box->text == NULL) - goto no_memory; + memcpy(text, dom_string_data(content), text_len); + text[text_len] = '\0'; - inline_box->length = strlen(inline_box->text); + /* TODO: Handle tabs properly */ + for (i = 0; i < text_len; i++) + if (text[i] == '\t') + text[i] = ' '; - box_add_child(inline_container, inline_box); + if (css_computed_text_transform(props.parent_style) != + CSS_TEXT_TRANSFORM_NONE) + box_text_transform(text, strlen(text), + css_computed_text_transform( + props.parent_style)); - box_add_child(box, inline_container); + current = text; - } else if (dom_string_caseless_lwc_isequal(type, - corestring_lwc_image)) { - gadget->type = GADGET_IMAGE; + /* swallow a single leading new line */ + if (props.containing_block->flags & PRE_STRIP) { + switch (*current) { + case '\n': + current++; + break; + case '\r': + current++; + if (*current == '\n') + current++; + break; + } + props.containing_block->flags &= ~PRE_STRIP; + } - if (box->style && - ns_computed_display(box->style, - box_is_root(n)) != CSS_DISPLAY_NONE && - nsoption_bool(foreground_images) == true) { - dom_string *s; + do { + size_t len = strcspn(current, "\r\n"); - 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; + char old = current[len]; - /* 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; - } + current[len] = 0; + + if (props.inline_container == NULL) { + /* Child of a block without a current container + * (i.e. this box is the first child of its + * parent, or was preceded by block-level + * siblings) */ + props.inline_container = box_create(NULL, NULL, + false, NULL, NULL, NULL, NULL, + ctx->bctx); + if (props.inline_container == NULL) { + free(text); + return false; } - nsurl_unref(url); + + props.inline_container->type = + BOX_INLINE_CONTAINER; + + box_add_child(props.containing_block, + props.inline_container); } - } - } else { - /* unhandled type the default is "text" */ - if (box_input_text(content, box, n) == false) - goto no_memory; - } - dom_string_unref(type); + /** \todo Dropping const isn't clever */ + box = box_create(NULL, + (css_computed_style *) props.parent_style, + false, props.href, props.target, props.title, + NULL, ctx->bctx); + if (box == NULL) { + free(text); + return false; + } - *convert_children = false; + box->type = BOX_TEXT; - return true; + box->text = talloc_strdup(ctx->bctx, current); + if (box->text == NULL) { + free(text); + return false; + } -no_memory: - dom_string_unref(type); + box->length = strlen(box->text); - return false; -} + box_add_child(props.inline_container, box); + current[len] = old; -/** - * Push button [17.5]. - */ + current += len; -bool box_button(BOX_SPECIAL_PARAMS) -{ - struct form_control *gadget; + if (current[0] != '\0') { + /* Linebreak: create new inline container */ + props.inline_container = box_create(NULL, NULL, + false, NULL, NULL, NULL, NULL, + ctx->bctx); + if (props.inline_container == NULL) { + free(text); + return false; + } - gadget = html_forms_get_control_for_node(content->forms, n); - if (!gadget) - return false; + props.inline_container->type = + BOX_INLINE_CONTAINER; - gadget->html = content; - box->gadget = gadget; - box->flags |= IS_REPLACED; - gadget->box = box; + box_add_child(props.containing_block, + props.inline_container); - box->type = BOX_INLINE_BLOCK; + if (current[0] == '\r' && current[1] == '\n') + current += 2; + else + current++; + } + } while (*current); - /* Just render the contents */ + free(text); + } return true; } /** - * Option selector [17.6]. + * Convert an ELEMENT node to a box tree fragment, + * then schedule conversion of the next ELEMENT node */ - -bool box_select(BOX_SPECIAL_PARAMS) +static void convert_xml_to_box(struct box_construct_ctx *ctx) { - 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; + dom_node *next; + bool convert_children; + uint32_t num_processed = 0; + const uint32_t max_processed_before_yield = 10; - gadget->html = content; - err = dom_node_get_first_child(n, &c); - if (err != DOM_NO_ERR) { - form_free_control(gadget); - return false; - } + do { + convert_children = true; - while (c != NULL) { - dom_string *name; + assert(ctx->n != NULL); - err = dom_node_get_node_name(c, &name); - if (err != DOM_NO_ERR) { - dom_node_unref(c); - form_free_control(gadget); - return false; + if (box_construct_element(ctx, &convert_children) == false) { + ctx->cb(ctx->content, false); + dom_node_unref(ctx->n); + free(ctx); + return; } - 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); + /* 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_first_child(c, &c2); + err = dom_node_get_node_type(next, &type); if (err != DOM_NO_ERR) { - dom_node_unref(c); - form_free_control(gadget); - return false; + ctx->cb(ctx->content, false); + dom_node_unref(next); + free(ctx); + return; } - while (c2 != NULL) { - dom_string *c2_name; + if (type == DOM_ELEMENT_NODE) + break; - 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 (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; } + } - if (dom_string_caseless_lwc_isequal(c2_name, - corestring_lwc_option)) { - dom_string_unref(c2_name); + next = next_node(next, ctx->content, true); + } - 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); - } + ctx->n = next; - 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; - } + if (next == NULL) { + /* Conversion complete */ + struct box root; - dom_node_unref(c2); - c2 = next2; + memset(&root, 0, sizeof(root)); + + root.type = BOX_BLOCK; + root.children = root.last = ctx->root_box; + root.children->parent = &root; + + /** \todo Remove box_normalise_block */ + if (box_normalise_block(&root, ctx->root_box, + ctx->content) == false) { + ctx->cb(ctx->content, false); + } else { + ctx->content->layout = root.children; + ctx->content->layout->parent = NULL; + + ctx->cb(ctx->content, true); } - } 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; + assert(ctx->n == NULL); + + free(ctx); + return; } + } while (++num_processed < max_processed_before_yield); - dom_node_unref(c); - c = next; - } + /* More work to do: schedule a continuation */ + guit->misc->schedule(0, (void *)convert_xml_to_box, ctx); +} - 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; +/* Exported function, documented in box.h */ +nserror +dom_to_box(dom_node *n, + html_content *c, + box_construct_complete_cb cb, + void **box_conversion_context) +{ + struct box_construct_ctx *ctx; - 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); + assert(box_conversion_context != NULL); - 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 (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; } - 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); + ctx->content = c; + ctx->n = dom_node_ref(n); + ctx->root_box = NULL; + ctx->cb = cb; + ctx->bctx = c->bctx; - *convert_children = false; - return true; + *box_conversion_context = ctx; -no_memory: - return false; + return guit->misc->schedule(0, (void *)convert_xml_to_box, ctx); } -/** - * Add an option to a form select control (helper function for box_select()). - * - * \param control select containing the <option> - * \param n xml element node for <option> - * \return true on success, false on memory exhaustion - */ +/* Exported function, see box.h */ +nserror cancel_dom_to_box(void *box_conversion_context) +{ + struct box_construct_ctx *ctx = box_conversion_context; + nserror err; + + err = guit->misc->schedule(-1, (void *)convert_xml_to_box, ctx); + if (err != NSERROR_OK) { + return err; + } + + dom_node_unref(ctx->n); + free(ctx); + + return NSERROR_OK; +} + -bool box_select_add_option(struct form_control *control, dom_node *n) +/* Exported function, see box.h */ +struct box *box_for_node(dom_node *n) { - char *value = NULL; - char *text = NULL; - char *text_nowrap = NULL; - bool selected; - dom_string *content, *s; + struct box *box = NULL; dom_exception err; - err = dom_node_get_text_content(n, &content); + err = dom_node_get_user_data(n, corestring_dom___ns_key_box_node_data, + (void *) &box); if (err != DOM_NO_ERR) - return false; - - if (content != NULL) { - text = squash_whitespace(dom_string_data(content)); - dom_string_unref(content); - } else { - text = strdup(""); - } + return NULL; - if (text == NULL) - goto no_memory; + return box; +} - 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; -} -/** - * Multi-line text field [17.7]. - */ -bool box_textarea(BOX_SPECIAL_PARAMS) -{ - /* Get the form_control for the DOM node */ - box->gadget = html_forms_get_control_for_node(content->forms, n); - if (box->gadget == NULL) - return false; - box->flags |= IS_REPLACED; - box->gadget->html = content; - box->gadget->box = box; - if (!box_input_text(content, box, n)) - return false; - *convert_children = false; - return true; -} -/** - * Embedded object (not in any HTML specification: - * see http://wp.netscape.com/assist/net_sites/new_html3_prop.html ) - */ -bool box_embed(BOX_SPECIAL_PARAMS) -{ - struct object_params *params; - struct object_param *param; - dom_namednodemap *attrs; - unsigned long idx; - uint32_t num_attrs; - dom_string *src; - dom_exception err; - if (box->style && ns_computed_display(box->style, - box_is_root(n)) == CSS_DISPLAY_NONE) - return true; - params = talloc(content->bctx, struct object_params); - if (params == NULL) - return false; - talloc_set_destructor(params, box_object_talloc_destructor); - params->data = NULL; - params->type = NULL; - params->codetype = NULL; - params->codebase = NULL; - params->classid = NULL; - params->params = NULL; - /* src is a URL */ - err = dom_element_get_attribute(n, corestring_dom_src, &src); - if (err != DOM_NO_ERR || src == NULL) - return true; - if (box_extract_link(content, src, content->base_url, - ¶ms->data) == false) { - dom_string_unref(src); - return false; - } - dom_string_unref(src); - if (params->data == NULL) - return true; - /* Don't include ourself */ - if (nsurl_compare(content->base_url, params->data, NSURL_COMPLETE)) - return true; - /* add attributes as parameters to linked list */ - err = dom_node_get_attributes(n, &attrs); - if (err != DOM_NO_ERR) - return false; - err = dom_namednodemap_get_length(attrs, &num_attrs); - if (err != DOM_NO_ERR) { - dom_namednodemap_unref(attrs); - return false; - } - for (idx = 0; idx < num_attrs; idx++) { - dom_attr *attr; - dom_string *name, *value; - err = dom_namednodemap_item(attrs, idx, (void *) &attr); - if (err != DOM_NO_ERR) { - dom_namednodemap_unref(attrs); - return false; - } - err = dom_attr_get_name(attr, &name); - if (err != DOM_NO_ERR) { - dom_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, - content->base.available_width, 1000, false); -} -/** - * \} - */ -/** - * Get the value of an XML element's attribute. - * - * \param n xmlNode, of type XML_ELEMENT_NODE - * \param attribute name of attribute - * \param context talloc context for result buffer - * \param value updated to value, if the attribute is present - * \return true on success, false if attribute present but memory exhausted - * - * Note that returning true does not imply that the attribute was found. If the - * attribute was not found, *value will be unchanged. - */ -bool box_get_attribute(dom_node *n, const char *attribute, - void *context, char **value) -{ - char *result; - dom_string *attr, *attr_name; - dom_exception err; - err = dom_string_create_interned((const uint8_t *) attribute, - strlen(attribute), &attr_name); - if (err != DOM_NO_ERR) - return false; - err = dom_element_get_attribute(n, attr_name, &attr); - if (err != DOM_NO_ERR) { - dom_string_unref(attr_name); - return false; - } - dom_string_unref(attr_name); - if (attr != NULL) { - result = talloc_strdup(context, dom_string_data(attr)); - dom_string_unref(attr); - if (result == NULL) - return false; - *value = result; - } - return true; -} /* exported function documented in html/box.h */ -- cgit v1.2.3