/* * This file is part of Hubbub. * Licensed under the MIT License, * http://www.opensource.org/licenses/mit-license.php * Copyright 2008 John-Mark Bell */ #include #include #include "treebuilder/modes.h" #include "treebuilder/internal.h" #include "treebuilder/treebuilder.h" #include "utils/utils.h" #undef DEBUG_IN_BODY /** * Bookmark for formatting list. Used in adoption agency */ typedef struct bookmark { formatting_list_entry *prev; /**< Previous entry */ formatting_list_entry *next; /**< Next entry */ } bookmark; static hubbub_error process_character(hubbub_treebuilder *treebuilder, const hubbub_token *token); static hubbub_error process_start_tag(hubbub_treebuilder *treebuilder, const hubbub_token *token); static hubbub_error process_end_tag(hubbub_treebuilder *treebuilder, const hubbub_token *token); static hubbub_error process_html_in_body(hubbub_treebuilder *treebuilder, const hubbub_token *token); static hubbub_error process_body_in_body(hubbub_treebuilder *treebuilder, const hubbub_token *token); static hubbub_error process_frameset_in_body(hubbub_treebuilder *treebuilder, const hubbub_token *token); static hubbub_error process_container_in_body(hubbub_treebuilder *treebuilder, const hubbub_token *token); static hubbub_error process_hN_in_body(hubbub_treebuilder *treebuilder, const hubbub_token *token); static hubbub_error process_form_in_body(hubbub_treebuilder *treebuilder, const hubbub_token *token); static hubbub_error process_dd_dt_li_in_body(hubbub_treebuilder *treebuilder, const hubbub_token *token, element_type type); static hubbub_error process_plaintext_in_body(hubbub_treebuilder *treebuilder, const hubbub_token *token); static hubbub_error process_a_in_body(hubbub_treebuilder *treebuilder, const hubbub_token *token); static hubbub_error process_presentational_in_body( hubbub_treebuilder *treebuilder, const hubbub_token *token, element_type type); static hubbub_error process_nobr_in_body(hubbub_treebuilder *treebuilder, const hubbub_token *token); static hubbub_error process_button_in_body(hubbub_treebuilder *treebuilder, const hubbub_token *token); static hubbub_error process_applet_marquee_object_in_body( hubbub_treebuilder *treebuilder, const hubbub_token *token, element_type type); static hubbub_error process_hr_in_body(hubbub_treebuilder *treebuilder, const hubbub_token *token); static hubbub_error process_image_in_body(hubbub_treebuilder *treebuilder, const hubbub_token *token); static hubbub_error process_isindex_in_body(hubbub_treebuilder *treebuilder, const hubbub_token *token); static hubbub_error process_textarea_in_body(hubbub_treebuilder *treebuilder, const hubbub_token *token); static hubbub_error process_select_in_body(hubbub_treebuilder *treebuilder, const hubbub_token *token); static hubbub_error process_opt_in_body(hubbub_treebuilder *treebuilder, const hubbub_token *token); static hubbub_error process_phrasing_in_body(hubbub_treebuilder *treebuilder, const hubbub_token *token); static hubbub_error process_0body_in_body(hubbub_treebuilder *treebuilder); static hubbub_error process_0container_in_body(hubbub_treebuilder *treebuilder, element_type type); static hubbub_error process_0form_in_body(hubbub_treebuilder *treebuilder); static hubbub_error process_0p_in_body(hubbub_treebuilder *treebuilder); static hubbub_error process_0dd_dt_li_in_body(hubbub_treebuilder *treebuilder, element_type type); static hubbub_error process_0h_in_body(hubbub_treebuilder *treebuilder, element_type type); static hubbub_error process_0presentational_in_body( hubbub_treebuilder *treebuilder, element_type type); static hubbub_error process_0applet_button_marquee_object_in_body( hubbub_treebuilder *treebuilder, element_type type); static hubbub_error process_0br_in_body(hubbub_treebuilder *treebuilder); static hubbub_error process_0generic_in_body(hubbub_treebuilder *treebuilder, element_type type); static hubbub_error aa_find_and_validate_formatting_element( hubbub_treebuilder *treebuilder, element_type type, formatting_list_entry **element); static formatting_list_entry *aa_find_formatting_element( hubbub_treebuilder *treebuilder, element_type type); static hubbub_error aa_find_furthest_block(hubbub_treebuilder *treebuilder, formatting_list_entry *formatting_element, uint32_t *furthest_block); static hubbub_error aa_reparent_node(hubbub_treebuilder *treebuilder, void *node, void *new_parent, void **reparented); static hubbub_error aa_find_bookmark_location_reparenting_misnested( hubbub_treebuilder *treebuilder, uint32_t formatting_element, uint32_t *furthest_block, bookmark *bookmark, uint32_t *last_node); static hubbub_error aa_remove_element_stack_item( hubbub_treebuilder *treebuilder, uint32_t index, uint32_t limit); static hubbub_error aa_clone_and_replace_entries( hubbub_treebuilder *treebuilder, formatting_list_entry *element); /** * Handle tokens in "in body" insertion mode * * \param treebuilder The treebuilder instance * \param token The token to process * \return True to reprocess the token, false otherwise */ hubbub_error handle_in_body(hubbub_treebuilder *treebuilder, const hubbub_token *token) { hubbub_error err = HUBBUB_OK; uint32_t i; #if !defined(NDEBUG) && defined(DEBUG_IN_BODY) fprintf(stdout, "Processing token %d\n", token->type); element_stack_dump(treebuilder, stdout); formatting_list_dump(treebuilder, stdout); #endif if (treebuilder->context.strip_leading_lr && token->type != HUBBUB_TOKEN_CHARACTER) { /* Reset the LR stripping flag */ treebuilder->context.strip_leading_lr = false; } switch (token->type) { case HUBBUB_TOKEN_CHARACTER: err = process_character(treebuilder, token); break; case HUBBUB_TOKEN_COMMENT: err = process_comment_append(treebuilder, token, treebuilder->context.element_stack[ treebuilder->context.current_node].node); break; case HUBBUB_TOKEN_DOCTYPE: /** \todo parse error */ break; case HUBBUB_TOKEN_START_TAG: err = process_start_tag(treebuilder, token); break; case HUBBUB_TOKEN_END_TAG: err = process_end_tag(treebuilder, token); break; case HUBBUB_TOKEN_EOF: for (i = treebuilder->context.current_node; i > 0; i--) { element_type type = treebuilder->context.element_stack[i].type; if (!(type == DD || type == DT || type == LI || type == P || type == TBODY || type == TD || type == TFOOT || type == TH || type == THEAD || type == TR || type == BODY)) { /** \todo parse error */ break; } } break; } #if !defined(NDEBUG) && defined(DEBUG_IN_BODY) fprintf(stdout, "Processed\n"); element_stack_dump(treebuilder, stdout); formatting_list_dump(treebuilder, stdout); #endif return err; } /** * Process a character token * * \param treebuilder The treebuilder instance * \param token The token to process */ hubbub_error process_character(hubbub_treebuilder *treebuilder, const hubbub_token *token) { hubbub_error err = HUBBUB_OK; hubbub_string dummy = token->data.character; bool lr_flag = treebuilder->context.strip_leading_lr; const uint8_t *p; err = reconstruct_active_formatting_list(treebuilder); if (err != HUBBUB_OK) return err; if (treebuilder->context.strip_leading_lr) { const uint8_t *str = dummy.ptr; if (*str == '\n') { dummy.ptr++; dummy.len--; } treebuilder->context.strip_leading_lr = false; } if (dummy.len) { err = append_text(treebuilder, &dummy); if (err != HUBBUB_OK) { /* Restore LR stripping flag */ treebuilder->context.strip_leading_lr = lr_flag; return err; } } if (treebuilder->context.frameset_ok) { for (p = dummy.ptr; p < dummy.ptr + dummy.len; p++) { if (*p != 0x0009 && *p != 0x000a && *p != 0x000c && *p != 0x0020) { treebuilder->context.frameset_ok = false; break; } } } return HUBBUB_OK; } /** * Process a start tag * * \param treebuilder The treebuilder instance * \param token The token to process * \return HUBBUB_OK on success, * HUBBUB_REPROCESS to reprocess the token, * appropriate error otherwise. */ hubbub_error process_start_tag(hubbub_treebuilder *treebuilder, const hubbub_token *token) { hubbub_error err = HUBBUB_OK; element_type type = element_type_from_name(treebuilder, &token->data.tag.name); if (type == HTML) { err = process_html_in_body(treebuilder, token); } else if (type == BASE || type == COMMAND || type == LINK || type == META || type == NOFRAMES || type == SCRIPT || type == STYLE || type == TITLE) { /* Process as "in head" */ err = handle_in_head(treebuilder, token); } else if (type == BODY) { err = process_body_in_body(treebuilder, token); } else if (type == FRAMESET) { err = process_frameset_in_body(treebuilder, token); } else if (type == ADDRESS || type == ARTICLE || type == ASIDE || type == BLOCKQUOTE || type == CENTER || type == DATAGRID || type == DETAILS || type == DIALOG || type == DIR || type == DIV || type == DL || type == FIELDSET || type == FIGURE || type == FOOTER || type == HEADER || type == MENU || type == NAV || type == OL || type == P || type == SECTION || type == UL) { err = process_container_in_body(treebuilder, token); } else if (type == H1 || type == H2 || type == H3 || type == H4 || type == H5 || type == H6) { err = process_hN_in_body(treebuilder, token); } else if (type == PRE || type == LISTING) { err = process_container_in_body(treebuilder, token); if (err == HUBBUB_OK) { treebuilder->context.strip_leading_lr = true; treebuilder->context.frameset_ok = false; } } else if (type == FORM) { err = process_form_in_body(treebuilder, token); } else if (type == DD || type == DT || type == LI) { err = process_dd_dt_li_in_body(treebuilder, token, type); } else if (type == PLAINTEXT) { err = process_plaintext_in_body(treebuilder, token); } else if (type == A) { err = process_a_in_body(treebuilder, token); } else if (type == B || type == BIG || type == CODE || type == EM || type == FONT || type == I || type == S || type == SMALL || type == STRIKE || type == STRONG || type == TT || type == U) { err = process_presentational_in_body(treebuilder, token, type); } else if (type == NOBR) { err = process_nobr_in_body(treebuilder, token); } else if (type == BUTTON) { err = process_button_in_body(treebuilder, token); } else if (type == APPLET || type == MARQUEE || type == OBJECT) { err = process_applet_marquee_object_in_body(treebuilder, token, type); } else if (type == XMP) { err = reconstruct_active_formatting_list(treebuilder); if (err != HUBBUB_OK) return err; treebuilder->context.frameset_ok = false; err = parse_generic_rcdata(treebuilder, token, false); } else if (type == TABLE) { err = process_container_in_body(treebuilder, token); if (err == HUBBUB_OK) { treebuilder->context.frameset_ok = false; treebuilder->context.element_stack[ current_table(treebuilder)].tainted = false; treebuilder->context.mode = IN_TABLE; } } else if (type == AREA || type == BASEFONT || type == BGSOUND || type == BR || type == EMBED || type == IMG || type == INPUT || type == PARAM || type == SPACER || type == WBR) { err = reconstruct_active_formatting_list(treebuilder); if (err != HUBBUB_OK) return err; err = insert_element(treebuilder, &token->data.tag, false); if (err == HUBBUB_OK) treebuilder->context.frameset_ok = false; } else if (type == HR) { err = process_hr_in_body(treebuilder, token); } else if (type == IMAGE) { err = process_image_in_body(treebuilder, token); } else if (type == ISINDEX) { err = process_isindex_in_body(treebuilder, token); } else if (type == TEXTAREA) { err = process_textarea_in_body(treebuilder, token); } else if (type == IFRAME || type == NOEMBED || type == NOFRAMES || (treebuilder->context.enable_scripting && type == NOSCRIPT)) { if (type == IFRAME) treebuilder->context.frameset_ok = false; err = parse_generic_rcdata(treebuilder, token, false); } else if (type == SELECT) { err = process_select_in_body(treebuilder, token); if (err != HUBBUB_OK) return err; if (treebuilder->context.mode == IN_BODY) { treebuilder->context.mode = IN_SELECT; } else if (treebuilder->context.mode == IN_TABLE || treebuilder->context.mode == IN_CAPTION || treebuilder->context.mode == IN_COLUMN_GROUP || treebuilder->context.mode == IN_TABLE_BODY || treebuilder->context.mode == IN_ROW || treebuilder->context.mode == IN_CELL) { treebuilder->context.mode = IN_SELECT_IN_TABLE; } } else if (type == OPTGROUP || type == OPTION) { err = process_opt_in_body(treebuilder, token); } else if (type == RP || type == RT) { /** \todo ruby */ } else if (type == MATH || type == SVG) { hubbub_tag tag = token->data.tag; err = reconstruct_active_formatting_list(treebuilder); if (err != HUBBUB_OK) return err; adjust_foreign_attributes(treebuilder, &tag); if (type == SVG) { adjust_svg_attributes(treebuilder, &tag); tag.ns = HUBBUB_NS_SVG; } else { adjust_mathml_attributes(treebuilder, &tag); tag.ns = HUBBUB_NS_MATHML; } if (token->data.tag.self_closing) { err = insert_element(treebuilder, &tag, false); /** \todo ack sc flag */ } else { err = insert_element(treebuilder, &tag, true); if (err == HUBBUB_OK) { treebuilder->context.second_mode = treebuilder->context.mode; treebuilder->context.mode = IN_FOREIGN_CONTENT; } } } else if (type == CAPTION || type == COL || type == COLGROUP || type == FRAME || type == HEAD || type == TBODY || type == TD || type == TFOOT || type == TH || type == THEAD || type == TR) { /** \todo parse error */ } else { err = process_phrasing_in_body(treebuilder, token); } return err; } /** * Process an end tag * * \param treebuilder The treebuilder instance * \param token The token to process * \return True to reprocess the token */ hubbub_error process_end_tag(hubbub_treebuilder *treebuilder, const hubbub_token *token) { hubbub_error err = HUBBUB_OK; element_type type = element_type_from_name(treebuilder, &token->data.tag.name); if (type == BODY) { err = process_0body_in_body(treebuilder); /* Never reprocess */ if (err == HUBBUB_REPROCESS) err = HUBBUB_OK; } else if (type == HTML) { /* Act as if has been seen then, if * that wasn't ignored, reprocess this token */ err = process_0body_in_body(treebuilder); } else if (type == ADDRESS || type == ARTICLE || type == ASIDE || type == BLOCKQUOTE || type == CENTER || type == DIR || type == DATAGRID || type == DIV || type == DL || type == FIELDSET || type == FOOTER || type == HEADER || type == LISTING || type == MENU || type == NAV || type == OL || type == PRE || type == SECTION || type == UL) { err = process_0container_in_body(treebuilder, type); } else if (type == FORM) { err = process_0form_in_body(treebuilder); } else if (type == P) { err = process_0p_in_body(treebuilder); } else if (type == DD || type == DT || type == LI) { err = process_0dd_dt_li_in_body(treebuilder, type); } else if (type == H1 || type == H2 || type == H3 || type == H4 || type == H5 || type == H6) { err = process_0h_in_body(treebuilder, type); } else if (type == A || type == B || type == BIG || type == CODE || type == EM || type == FONT || type == I || type == NOBR || type == S || type == SMALL || type == STRIKE || type == STRONG || type == TT || type == U) { err = process_0presentational_in_body(treebuilder, type); } else if (type == APPLET || type == BUTTON || type == MARQUEE || type == OBJECT) { err = process_0applet_button_marquee_object_in_body( treebuilder, type); } else if (type == BR) { err = process_0br_in_body(treebuilder); } else if (type == AREA || type == BASEFONT || type == BGSOUND || type == EMBED || type == HR || type == IFRAME || type == IMAGE || type == IMG || type == INPUT || type == ISINDEX || type == NOEMBED || type == NOFRAMES || type == PARAM || type == SELECT || type == SPACER || type == TABLE || type == TEXTAREA || type == WBR || (treebuilder->context.enable_scripting && type == NOSCRIPT)) { /** \todo parse error */ } else { err = process_0generic_in_body(treebuilder, type); } return err; } /** * Process a html start tag as if in "in body" * * \param treebuilder The treebuilder instance * \param token The token to process */ hubbub_error process_html_in_body(hubbub_treebuilder *treebuilder, const hubbub_token *token) { /** \todo parse error */ return treebuilder->tree_handler->add_attributes( treebuilder->tree_handler->ctx, treebuilder->context.element_stack[0].node, token->data.tag.attributes, token->data.tag.n_attributes); } /** * Process a body start tag as if in "in body" * * \param treebuilder The treebuilder instance * \param token The token to process */ hubbub_error process_body_in_body(hubbub_treebuilder *treebuilder, const hubbub_token *token) { /** \todo parse error */ if (treebuilder->context.current_node < 1 || treebuilder->context.element_stack[1].type != BODY) return HUBBUB_OK; return treebuilder->tree_handler->add_attributes( treebuilder->tree_handler->ctx, treebuilder->context.element_stack[1].node, token->data.tag.attributes, token->data.tag.n_attributes); } /** * Process a frameset start tag as if in "in body" * * \param treebuilder The treebuilder instance * \param token The token to process */ hubbub_error process_frameset_in_body(hubbub_treebuilder *treebuilder, const hubbub_token *token) { hubbub_error err = HUBBUB_OK; /** \todo parse error */ if (treebuilder->context.current_node < 1 || treebuilder->context.element_stack[1].type != BODY) return HUBBUB_OK; if (treebuilder->context.frameset_ok == false) return HUBBUB_OK; err = remove_node_from_dom(treebuilder, treebuilder->context.element_stack[1].node); if (err != HUBBUB_OK) return err; err = element_stack_pop_until(treebuilder, BODY); assert(err == HUBBUB_OK); err = insert_element(treebuilder, &token->data.tag, true); if (err == HUBBUB_OK) treebuilder->context.mode = IN_FRAMESET; return err; } /** * Process a generic container start tag as if in "in body" * * \param treebuilder The treebuilder instance * \param token The token to process */ hubbub_error process_container_in_body(hubbub_treebuilder *treebuilder, const hubbub_token *token) { hubbub_error err; if (element_in_scope(treebuilder, P, false)) { err = process_0p_in_body(treebuilder); if (err != HUBBUB_OK) return err; } return insert_element(treebuilder, &token->data.tag, true); } /** * Process a hN start tag as if in "in body" * * \param treebuilder The treebuilder instance * \param token The token to process */ hubbub_error process_hN_in_body(hubbub_treebuilder *treebuilder, const hubbub_token *token) { hubbub_error err; element_type type; if (element_in_scope(treebuilder, P, false)) { err = process_0p_in_body(treebuilder); if (err != HUBBUB_OK) return err; } type = treebuilder->context.element_stack[ treebuilder->context.current_node].type; if (type == H1 || type == H2 || type == H3 || type == H4 || type == H5 || type == H6) { hubbub_ns ns; element_type otype; void *node; /** \todo parse error */ err = element_stack_pop(treebuilder, &ns, &otype, &node); assert(err == HUBBUB_OK); treebuilder->tree_handler->unref_node( treebuilder->tree_handler->ctx, node); } return insert_element(treebuilder, &token->data.tag, true); } /** * Process a form start tag as if in "in body" * * \param treebuilder The treebuilder instance * \param token The token to process */ hubbub_error process_form_in_body(hubbub_treebuilder *treebuilder, const hubbub_token *token) { hubbub_error err; if (treebuilder->context.form_element != NULL) { /** \todo parse error */ } else { if (element_in_scope(treebuilder, P, false)) { err = process_0p_in_body(treebuilder); if (err != HUBBUB_OK) return err; } err = insert_element(treebuilder, &token->data.tag, true); if (err != HUBBUB_OK) return err; /* Claim a reference on the node and * use it as the current form element */ treebuilder->tree_handler->ref_node( treebuilder->tree_handler->ctx, treebuilder->context.element_stack[ treebuilder->context.current_node].node); treebuilder->context.form_element = treebuilder->context.element_stack[ treebuilder->context.current_node].node; } return HUBBUB_OK; } /** * Process a dd, dt or li start tag as if in "in body" * * \param treebuilder The treebuilder instance * \param token The token to process * \param type The element type */ hubbub_error process_dd_dt_li_in_body(hubbub_treebuilder *treebuilder, const hubbub_token *token, element_type type) { hubbub_error err; element_context *stack = treebuilder->context.element_stack; uint32_t node; treebuilder->context.frameset_ok = false; if (element_in_scope(treebuilder, P, false)) { err = process_0p_in_body(treebuilder); if (err != HUBBUB_OK) return err; } /* Find last LI/(DD,DT) on stack, if any */ for (node = treebuilder->context.current_node; node > 0; node--) { element_type ntype = stack[node].type; if (type == LI && ntype == LI) break; if (((type == DD || type == DT) && (ntype == DD || ntype == DT))) break; if (!is_formatting_element(ntype) && !is_phrasing_element(ntype) && ntype != ADDRESS && ntype != DIV) break; } /* If we found one, then pop all nodes up to and including it */ if (stack[node].type == LI || stack[node].type == DD || stack[node].type == DT) { /* Check that we're only popping one node * and emit a parse error if not */ if (treebuilder->context.current_node > node) { /** \todo parse error */ } do { hubbub_ns ns; element_type otype; void *node; err = element_stack_pop(treebuilder, &ns, &otype, &node); assert(err == HUBBUB_OK); treebuilder->tree_handler->unref_node( treebuilder->tree_handler->ctx, node); } while (treebuilder->context.current_node >= node); } return insert_element(treebuilder, &token->data.tag, true); } /** * Process a plaintext start tag as if in "in body" * * \param treebuilder The treebuilder instance * \param token The token to process */ hubbub_error process_plaintext_in_body(hubbub_treebuilder *treebuilder, const hubbub_token *token) { hubbub_error err; hubbub_tokeniser_optparams params; if (element_in_scope(treebuilder, P, false)) { err = process_0p_in_body(treebuilder); if (err != HUBBUB_OK) return err; } err = insert_element(treebuilder, &token->data.tag, true); if (err != HUBBUB_OK) return err; params.content_model.model = HUBBUB_CONTENT_MODEL_PLAINTEXT; err = hubbub_tokeniser_setopt(treebuilder->tokeniser, HUBBUB_TOKENISER_CONTENT_MODEL, ¶ms); assert(err == HUBBUB_OK); return HUBBUB_OK; } /** * Process an "a" start tag as if in "in body" * * \param treebuilder The treebuilder instance * \param token The token to process */ hubbub_error process_a_in_body(hubbub_treebuilder *treebuilder, const hubbub_token *token) { hubbub_error err; formatting_list_entry *entry = aa_find_formatting_element(treebuilder, A); if (entry != NULL) { uint32_t index = entry->stack_index; void *node = entry->details.node; formatting_list_entry *entry2; /** \todo parse error */ /* Act as if were seen */ err = process_0presentational_in_body(treebuilder, A); if (err != HUBBUB_OK) return err; entry2 = aa_find_formatting_element(treebuilder, A); /* Remove from formatting list, if it's still there */ if (entry2 == entry && entry2->details.node == node) { hubbub_ns ons; element_type otype; void *onode; uint32_t oindex; err = formatting_list_remove(treebuilder, entry, &ons, &otype, &onode, &oindex); assert(err == HUBBUB_OK); treebuilder->tree_handler->unref_node( treebuilder->tree_handler->ctx, onode); } /* Remove from the stack of open elements, if still there */ if (index <= treebuilder->context.current_node && treebuilder->context.element_stack[index].node == node) { hubbub_ns ns; element_type otype; void *onode; err = element_stack_remove(treebuilder, index, &ns, &otype, &onode); assert(err == HUBBUB_OK); treebuilder->tree_handler->unref_node( treebuilder->tree_handler->ctx, onode); } } err = reconstruct_active_formatting_list(treebuilder); if (err != HUBBUB_OK) return err; err = insert_element(treebuilder, &token->data.tag, true); if (err != HUBBUB_OK) return err; treebuilder->tree_handler->ref_node(treebuilder->tree_handler->ctx, treebuilder->context.element_stack[ treebuilder->context.current_node].node); err = formatting_list_append(treebuilder, token->data.tag.ns, A, treebuilder->context.element_stack[ treebuilder->context.current_node].node, treebuilder->context.current_node); if (err != HUBBUB_OK) { hubbub_ns ns; element_type type; void *node; remove_node_from_dom(treebuilder, treebuilder->context.element_stack[ treebuilder->context.current_node].node); element_stack_pop(treebuilder, &ns, &type, &node); /* Unref twice (once for stack, once for formatting list) */ treebuilder->tree_handler->unref_node( treebuilder->tree_handler->ctx, node); treebuilder->tree_handler->unref_node( treebuilder->tree_handler->ctx, node); return err; } return HUBBUB_OK; } /** * Process a b, big, em, font, i, s, small, * strike, strong, tt, or u start tag as if in "in body" * * \param treebuilder The treebuilder instance * \param token The token to process * \param type The element type */ hubbub_error process_presentational_in_body(hubbub_treebuilder *treebuilder, const hubbub_token *token, element_type type) { hubbub_error err; err = reconstruct_active_formatting_list(treebuilder); if (err != HUBBUB_OK) return err; err = insert_element(treebuilder, &token->data.tag, true); if (err != HUBBUB_OK) return err; treebuilder->tree_handler->ref_node(treebuilder->tree_handler->ctx, treebuilder->context.element_stack[ treebuilder->context.current_node].node); err = formatting_list_append(treebuilder, token->data.tag.ns, type, treebuilder->context.element_stack[ treebuilder->context.current_node].node, treebuilder->context.current_node); if (err != HUBBUB_OK) { hubbub_ns ns; element_type type; void *node; remove_node_from_dom(treebuilder, treebuilder->context.element_stack[ treebuilder->context.current_node].node); element_stack_pop(treebuilder, &ns, &type, &node); /* Unref twice (once for stack, once for formatting list) */ treebuilder->tree_handler->unref_node( treebuilder->tree_handler->ctx, node); treebuilder->tree_handler->unref_node( treebuilder->tree_handler->ctx, node); return err; } return HUBBUB_OK; } /** * Process a nobr start tag as if in "in body" * * \param treebuilder The treebuilder instance * \param token The token to process */ hubbub_error process_nobr_in_body(hubbub_treebuilder *treebuilder, const hubbub_token *token) { hubbub_error err; err = reconstruct_active_formatting_list(treebuilder); if (err != HUBBUB_OK) return err; if (element_in_scope(treebuilder, NOBR, false)) { /** \todo parse error */ /* Act as if were seen */ err = process_0presentational_in_body(treebuilder, NOBR); if (err != HUBBUB_OK) return err; /* Yes, again */ err = reconstruct_active_formatting_list(treebuilder); if (err != HUBBUB_OK) return err; } err = insert_element(treebuilder, &token->data.tag, true); if (err != HUBBUB_OK) return err; treebuilder->tree_handler->ref_node( treebuilder->tree_handler->ctx, treebuilder->context.element_stack[ treebuilder->context.current_node].node); err = formatting_list_append(treebuilder, token->data.tag.ns, NOBR, treebuilder->context.element_stack[ treebuilder->context.current_node].node, treebuilder->context.current_node); if (err != HUBBUB_OK) { hubbub_ns ns; element_type type; void *node; remove_node_from_dom(treebuilder, treebuilder->context.element_stack[ treebuilder->context.current_node].node); element_stack_pop(treebuilder, &ns, &type, &node); /* Unref twice (once for stack, once for formatting list) */ treebuilder->tree_handler->unref_node( treebuilder->tree_handler->ctx, node); treebuilder->tree_handler->unref_node( treebuilder->tree_handler->ctx, node); return err; } return HUBBUB_OK; } /** * Process a button start tag as if in "in body" * * \param treebuilder The treebuilder instance * \param token The token to process */ hubbub_error process_button_in_body(hubbub_treebuilder *treebuilder, const hubbub_token *token) { hubbub_error err; if (element_in_scope(treebuilder, BUTTON, false)) { /** \todo parse error */ /* Act as if has been seen */ err = process_0applet_button_marquee_object_in_body( treebuilder, BUTTON); assert(err == HUBBUB_OK); } err = reconstruct_active_formatting_list(treebuilder); if (err != HUBBUB_OK) return err; err = insert_element(treebuilder, &token->data.tag, true); if (err != HUBBUB_OK) return err; treebuilder->tree_handler->ref_node( treebuilder->tree_handler->ctx, treebuilder->context.element_stack[ treebuilder->context.current_node].node); err = formatting_list_append(treebuilder, token->data.tag.ns, BUTTON, treebuilder->context.element_stack[ treebuilder->context.current_node].node, treebuilder->context.current_node); if (err != HUBBUB_OK) { hubbub_ns ns; element_type type; void *node; remove_node_from_dom(treebuilder, treebuilder->context.element_stack[ treebuilder->context.current_node].node); element_stack_pop(treebuilder, &ns, &type, &node); /* Unref twice (once for stack, once for formatting list) */ treebuilder->tree_handler->unref_node( treebuilder->tree_handler->ctx, node); treebuilder->tree_handler->unref_node( treebuilder->tree_handler->ctx, node); return err; } treebuilder->context.frameset_ok = false; return HUBBUB_OK; } /** * Process an applet, marquee or object start tag as if in "in body" * * \param treebuilder The treebuilder instance * \param token The token to process * \param type The element type */ hubbub_error process_applet_marquee_object_in_body( hubbub_treebuilder *treebuilder, const hubbub_token *token, element_type type) { hubbub_error err; err = reconstruct_active_formatting_list(treebuilder); if (err != HUBBUB_OK) return err; err = insert_element(treebuilder, &token->data.tag, true); if (err != HUBBUB_OK) return err; treebuilder->tree_handler->ref_node( treebuilder->tree_handler->ctx, treebuilder->context.element_stack[ treebuilder->context.current_node].node); err = formatting_list_append(treebuilder, token->data.tag.ns, type, treebuilder->context.element_stack[ treebuilder->context.current_node].node, treebuilder->context.current_node); if (err != HUBBUB_OK) { hubbub_ns ns; element_type type; void *node; remove_node_from_dom(treebuilder, treebuilder->context.element_stack[ treebuilder->context.current_node].node); element_stack_pop(treebuilder, &ns, &type, &node); /* Unref twice (once for stack, once for formatting list) */ treebuilder->tree_handler->unref_node( treebuilder->tree_handler->ctx, node); treebuilder->tree_handler->unref_node( treebuilder->tree_handler->ctx, node); return err; } treebuilder->context.frameset_ok = false; return HUBBUB_OK; } /** * Process an hr start tag as if in "in body" * * \param treebuilder The treebuilder instance * \param token The token to process */ hubbub_error process_hr_in_body(hubbub_treebuilder *treebuilder, const hubbub_token *token) { hubbub_error err; if (element_in_scope(treebuilder, P, false)) { err = process_0p_in_body(treebuilder); if (err != HUBBUB_OK) return err; } err = insert_element(treebuilder, &token->data.tag, false); if (err == HUBBUB_OK) treebuilder->context.frameset_ok = false; return err; } /** * Process an image start tag as if in "in body" * * \param treebuilder The treebuilder instance * \param token The token to process */ hubbub_error process_image_in_body(hubbub_treebuilder *treebuilder, const hubbub_token *token) { hubbub_error err; hubbub_tag tag; tag.ns = HUBBUB_NS_HTML; tag.name.ptr = (const uint8_t *) "img"; tag.name.len = SLEN("img"); tag.n_attributes = token->data.tag.n_attributes; tag.attributes = token->data.tag.attributes; err = reconstruct_active_formatting_list(treebuilder); if (err != HUBBUB_OK) return err; return insert_element(treebuilder, &tag, false); } /** * Process an isindex start tag as if in "in body" * * \param treebuilder The treebuilder instance * \param token The token to process */ hubbub_error process_isindex_in_body(hubbub_treebuilder *treebuilder, const hubbub_token *token) { hubbub_error err; hubbub_token dummy; hubbub_attribute *action = NULL; hubbub_attribute *prompt = NULL; hubbub_attribute *attrs = NULL; size_t n_attrs = 0; /** \todo parse error */ if (treebuilder->context.form_element != NULL) return HUBBUB_OK; /* First up, clone the token's attributes */ if (token->data.tag.n_attributes > 0) { uint32_t i; attrs = treebuilder->alloc(NULL, (token->data.tag.n_attributes + 1) * sizeof(hubbub_attribute), treebuilder->alloc_pw); if (attrs == NULL) return HUBBUB_NOMEM; for (i = 0; i < token->data.tag.n_attributes; i++) { hubbub_attribute *attr = &token->data.tag.attributes[i]; const uint8_t *name = attr->name.ptr; if (strncmp((const char *) name, "action", attr->name.len) == 0) { action = attr; } else if (strncmp((const char *) name, "prompt", attr->name.len) == 0) { prompt = attr; } else if (strncmp((const char *) name, "name", attr->name.len) == 0) { } else { attrs[n_attrs++] = *attr; } } attrs[n_attrs].ns = HUBBUB_NS_HTML; attrs[n_attrs].name.ptr = (const uint8_t *) "name"; attrs[n_attrs].name.len = SLEN("name"); attrs[n_attrs].value.ptr = (const uint8_t *) "isindex"; attrs[n_attrs].value.len = SLEN("isindex"); n_attrs++; } /* isindex algorithm */ /* Set up dummy as a start tag token */ dummy.type = HUBBUB_TOKEN_START_TAG; dummy.data.tag.ns = HUBBUB_NS_HTML; /* Act as if
were seen */ dummy.data.tag.name.ptr = (const uint8_t *) "form"; dummy.data.tag.name.len = SLEN("form"); dummy.data.tag.n_attributes = action != NULL ? 1 : 0; dummy.data.tag.attributes = action; err = process_form_in_body(treebuilder, &dummy); if (err != HUBBUB_OK) { treebuilder->alloc(attrs, 0, treebuilder->alloc_pw); return err; } /* Act as if
were seen */ dummy.data.tag.name.ptr = (const uint8_t *) "hr"; dummy.data.tag.name.len = SLEN("hr"); dummy.data.tag.n_attributes = 0; dummy.data.tag.attributes = NULL; err = process_hr_in_body(treebuilder, &dummy); if (err != HUBBUB_OK) { treebuilder->alloc(attrs, 0, treebuilder->alloc_pw); return err; } /* Act as if

were seen */ dummy.data.tag.name.ptr = (const uint8_t *) "p"; dummy.data.tag.name.len = SLEN("p"); dummy.data.tag.n_attributes = 0; dummy.data.tag.attributes = NULL; err = process_container_in_body(treebuilder, &dummy); if (err != HUBBUB_OK) { treebuilder->alloc(attrs, 0, treebuilder->alloc_pw); return err; } /* Act as if was seen */ err = process_0generic_in_body(treebuilder, LABEL); assert(err == HUBBUB_OK); /* Act as if

was seen */ err = process_0p_in_body(treebuilder); if (err != HUBBUB_OK) return err; /* Act as if
was seen */ dummy.data.tag.name.ptr = (const uint8_t *) "hr"; dummy.data.tag.name.len = SLEN("hr"); dummy.data.tag.n_attributes = 0; dummy.data.tag.attributes = NULL; err = process_hr_in_body(treebuilder, &dummy); if (err != HUBBUB_OK) return err; /* Act as if
was seen */ return process_0container_in_body(treebuilder, FORM); } /** * Process a textarea start tag as if in "in body" * * \param treebuilder The treebuilder instance * \param token The token to process */ hubbub_error process_textarea_in_body(hubbub_treebuilder *treebuilder, const hubbub_token *token) { treebuilder->context.strip_leading_lr = true; treebuilder->context.frameset_ok = false; return parse_generic_rcdata(treebuilder, token, true); } /** * Process a select start tag as if in "in body" * * \param treebuilder The treebuilder instance * \param token The token to process */ hubbub_error process_select_in_body(hubbub_treebuilder *treebuilder, const hubbub_token *token) { hubbub_error err; err = reconstruct_active_formatting_list(treebuilder); if (err != HUBBUB_OK) return err; err = insert_element(treebuilder, &token->data.tag, true); if (err == HUBBUB_OK) treebuilder->context.frameset_ok = false; return err; } /** * Process an option or optgroup start tag as if in "in body" * * \param treebuilder The treebuilder instance * \param token The token to process */ hubbub_error process_opt_in_body(hubbub_treebuilder *treebuilder, const hubbub_token *token) { hubbub_error err; if (element_in_scope(treebuilder, OPTION, false)) { err = process_0generic_in_body(treebuilder, OPTION); /* Cannot fail */ assert(err == HUBBUB_OK); } err = reconstruct_active_formatting_list(treebuilder); if (err != HUBBUB_OK) return err; return insert_element(treebuilder, &token->data.tag, true); } /** * Process a phrasing start tag as if in "in body" * * \param treebuilder The treebuilder instance * \param token The token to process */ hubbub_error process_phrasing_in_body(hubbub_treebuilder *treebuilder, const hubbub_token *token) { hubbub_error err; err = reconstruct_active_formatting_list(treebuilder); if (err != HUBBUB_OK) return err; return insert_element(treebuilder, &token->data.tag, true); } /** * Process a body end tag as if in "in body" * * \param treebuilder The treebuilder instance * \return True if processed, false otherwise */ hubbub_error process_0body_in_body(hubbub_treebuilder *treebuilder) { hubbub_error err = HUBBUB_OK; if (!element_in_scope(treebuilder, BODY, false)) { /** \todo parse error */ } else { element_context *stack = treebuilder->context.element_stack; uint32_t node; for (node = treebuilder->context.current_node; node > 0; node--) { element_type ntype = stack[node].type; if (ntype != DD && ntype != DT && ntype != LI && ntype != OPTGROUP && ntype != OPTION && ntype != P && ntype != RP && ntype != RT && ntype != TBODY && ntype != TD && ntype != TFOOT && ntype != TH && ntype != THEAD && ntype != TR && ntype != BODY) { /** \todo parse error */ } } if (treebuilder->context.mode == IN_BODY) treebuilder->context.mode = AFTER_BODY; err = HUBBUB_REPROCESS; } return err; } /** * Process a container end tag as if in "in body" * * \param treebuilder The treebuilder instance * \param type The element type */ hubbub_error process_0container_in_body(hubbub_treebuilder *treebuilder, element_type type) { if (!element_in_scope(treebuilder, type, false)) { /** \todo parse error */ } else { uint32_t popped = 0; element_type otype; close_implied_end_tags(treebuilder, UNKNOWN); do { hubbub_ns ns; void *node; element_stack_pop(treebuilder, &ns, &otype, &node); treebuilder->tree_handler->unref_node( treebuilder->tree_handler->ctx, node); popped++; } while (otype != type); if (popped > 1) { /** \todo parse error */ } } return HUBBUB_OK; } /** * Process a form end tag as if in "in body" * * \param treebuilder The treebuilder instance */ hubbub_error process_0form_in_body(hubbub_treebuilder *treebuilder) { void *node = treebuilder->context.form_element; uint32_t idx = 0; if (treebuilder->context.form_element != NULL) treebuilder->tree_handler->unref_node( treebuilder->tree_handler->ctx, treebuilder->context.form_element); treebuilder->context.form_element = NULL; idx = element_in_scope(treebuilder, FORM, false); if (idx == 0 || node == NULL || treebuilder->context.element_stack[idx].node != node) { /** \todo parse error */ } else { hubbub_ns ns; element_type otype; void *onode; close_implied_end_tags(treebuilder, UNKNOWN); if (treebuilder->context.element_stack[ treebuilder->context.current_node].node != node) { /** \todo parse error */ } element_stack_remove(treebuilder, idx, &ns, &otype, &onode); treebuilder->tree_handler->unref_node( treebuilder->tree_handler->ctx, onode); } return HUBBUB_OK; } /** * Process a p end tag as if in "in body" * * \param treebuilder The treebuilder instance */ hubbub_error process_0p_in_body(hubbub_treebuilder *treebuilder) { hubbub_error err = HUBBUB_OK; uint32_t popped = 0; if (treebuilder->context.element_stack[ treebuilder->context.current_node].type != P) { /** \todo parse error */ } while (element_in_scope(treebuilder, P, false)) { hubbub_ns ns; element_type type; void *node; err = element_stack_pop(treebuilder, &ns, &type, &node); assert(err == HUBBUB_OK); treebuilder->tree_handler->unref_node( treebuilder->tree_handler->ctx, node); popped++; } if (popped == 0) { hubbub_token dummy; dummy.type = HUBBUB_TOKEN_START_TAG; dummy.data.tag.ns = HUBBUB_NS_HTML; dummy.data.tag.name.ptr = (const uint8_t *) "p"; dummy.data.tag.name.len = SLEN("p"); dummy.data.tag.n_attributes = 0; dummy.data.tag.attributes = NULL; err = process_container_in_body(treebuilder, &dummy); if (err != HUBBUB_OK) return err; /* Reprocess the end tag. This is safe as we've just * inserted a

into the current scope */ err = process_0p_in_body(treebuilder); /* Cannot fail */ assert(err == HUBBUB_OK); } return err; } /** * Process a dd, dt, or li end tag as if in "in body" * * \param treebuilder The treebuilder instance * \param type The element type */ hubbub_error process_0dd_dt_li_in_body(hubbub_treebuilder *treebuilder, element_type type) { if (!element_in_scope(treebuilder, type, false)) { /** \todo parse error */ } else { uint32_t popped = 0; element_type otype; close_implied_end_tags(treebuilder, type); do { hubbub_ns ns; void *node; element_stack_pop(treebuilder, &ns, &otype, &node); treebuilder->tree_handler->unref_node( treebuilder->tree_handler->ctx, node); popped++; } while (otype != type); if (popped > 1) { /** \todo parse error */ } } return HUBBUB_OK; } /** * Process a h1, h2, h3, h4, h5, or h6 end tag as if in "in body" * * \param treebuilder The treebuilder instance * \param type The element type */ hubbub_error process_0h_in_body(hubbub_treebuilder *treebuilder, element_type type) { UNUSED(type); /** \todo optimise this */ if (element_in_scope(treebuilder, H1, false) || element_in_scope(treebuilder, H2, false) || element_in_scope(treebuilder, H3, false) || element_in_scope(treebuilder, H4, false) || element_in_scope(treebuilder, H5, false) || element_in_scope(treebuilder, H6, false)) { uint32_t popped = 0; element_type otype; close_implied_end_tags(treebuilder, UNKNOWN); do { hubbub_ns ns; void *node; element_stack_pop(treebuilder, &ns, &otype, &node); treebuilder->tree_handler->unref_node( treebuilder->tree_handler->ctx, node); popped++; } while (otype != H1 && otype != H2 && otype != H3 && otype != H4 && otype != H5 && otype != H6); if (popped > 1) { /** \todo parse error */ } } else { /** \todo parse error */ } return HUBBUB_OK; } /** * Process a presentational end tag as if in "in body" * * \param treebuilder The treebuilder instance * \param type The element type */ hubbub_error process_0presentational_in_body(hubbub_treebuilder *treebuilder, element_type type) { hubbub_error err; /* Welcome to the adoption agency */ while (true) { element_context *stack = treebuilder->context.element_stack; formatting_list_entry *entry; uint32_t formatting_element; uint32_t common_ancestor; uint32_t furthest_block; bookmark bookmark; uint32_t last_node; void *reparented; void *fe_clone = NULL; void *clone_appended = NULL; hubbub_ns ons; element_type otype; void *onode; uint32_t oindex; /* 1 */ err = aa_find_and_validate_formatting_element(treebuilder, type, &entry); assert(err == HUBBUB_OK || err == HUBBUB_REPROCESS); if (err == HUBBUB_OK) return err; assert(entry->details.type == type); /* Take a copy of the stack index for use * during stack manipulation */ formatting_element = entry->stack_index; /* 2 & 3 */ err = aa_find_furthest_block(treebuilder, entry, &furthest_block); assert(err == HUBBUB_OK || err == HUBBUB_REPROCESS); if (err == HUBBUB_OK) return err; /* 4 */ common_ancestor = formatting_element - 1; /* 5 */ bookmark.prev = entry->prev; bookmark.next = entry->next; /* 6 */ err = aa_find_bookmark_location_reparenting_misnested( treebuilder, formatting_element, &furthest_block, &bookmark, &last_node); if (err != HUBBUB_OK) return err; /* 7 */ if (stack[common_ancestor].type == TABLE || stack[common_ancestor].type == TBODY || stack[common_ancestor].type == TFOOT || stack[common_ancestor].type == THEAD || stack[common_ancestor].type == TR) { err = aa_insert_into_foster_parent(treebuilder, stack[last_node].node, &reparented); } else { err = aa_reparent_node(treebuilder, stack[last_node].node, stack[common_ancestor].node, &reparented); } if (err != HUBBUB_OK) return err; treebuilder->tree_handler->unref_node( treebuilder->tree_handler->ctx, stack[last_node].node); /* If the reparented node is not the same as the one we were * previously using, then have it take the place of the other * one in the formatting list and stack. */ if (reparented != stack[last_node].node) { struct formatting_list_entry *node_entry; for (node_entry = treebuilder->context.formatting_list_end; node_entry != NULL; node_entry = node_entry->prev) { if (node_entry->stack_index == last_node) { treebuilder->tree_handler->ref_node( treebuilder->tree_handler->ctx, reparented); node_entry->details.node = reparented; treebuilder->tree_handler->unref_node( treebuilder->tree_handler->ctx, stack[last_node].node); break; } } /* Already have enough references, so don't need to * explicitly reference it here. */ stack[last_node].node = reparented; } /* 8 */ err = treebuilder->tree_handler->clone_node( treebuilder->tree_handler->ctx, entry->details.node, false, &fe_clone); if (err != HUBBUB_OK) return err; /* 9 */ err = treebuilder->tree_handler->reparent_children( treebuilder->tree_handler->ctx, stack[furthest_block].node, fe_clone); if (err != HUBBUB_OK) { treebuilder->tree_handler->unref_node( treebuilder->tree_handler->ctx, fe_clone); return err; } /* 10 */ err = treebuilder->tree_handler->append_child( treebuilder->tree_handler->ctx, stack[furthest_block].node, fe_clone, &clone_appended); if (err != HUBBUB_OK) { treebuilder->tree_handler->unref_node( treebuilder->tree_handler->ctx, fe_clone); return err; } if (clone_appended != fe_clone) { /* No longer interested in fe_clone */ treebuilder->tree_handler->unref_node( treebuilder->tree_handler->ctx, fe_clone); /* Need an extra reference, as we'll insert into the * formatting list and element stack */ treebuilder->tree_handler->ref_node( treebuilder->tree_handler->ctx, clone_appended); } /* 11 and 12 are reversed here so that we know the correct * stack index to use when inserting into the formatting list */ /* 12 */ err = aa_remove_element_stack_item(treebuilder, formatting_element, furthest_block); assert(err == HUBBUB_OK); /* Fix up furthest block index */ furthest_block--; /* Now, in the gap after furthest block, * we insert an entry for clone */ stack[furthest_block + 1].type = entry->details.type; stack[furthest_block + 1].node = clone_appended; /* 11 */ err = formatting_list_remove(treebuilder, entry, &ons, &otype, &onode, &oindex); assert(err == HUBBUB_OK); treebuilder->tree_handler->unref_node( treebuilder->tree_handler->ctx, onode); err = formatting_list_insert(treebuilder, bookmark.prev, bookmark.next, ons, otype, clone_appended, furthest_block + 1); if (err != HUBBUB_OK) { treebuilder->tree_handler->unref_node( treebuilder->tree_handler->ctx, clone_appended); return err; } /* 13 */ } } /** * Adoption agency: find and validate the formatting element * * \param treebuilder The treebuilder instance * \param type Element type to search for * \param element Pointer to location to receive list entry * \return HUBBUB_REPROCESS to continue processing, * HUBBUB_OK to stop. */ hubbub_error aa_find_and_validate_formatting_element( hubbub_treebuilder *treebuilder, element_type type, formatting_list_entry **element) { formatting_list_entry *entry; entry = aa_find_formatting_element(treebuilder, type); if (entry == NULL || (entry->stack_index != 0 && element_in_scope(treebuilder, entry->details.type, false) != entry->stack_index)) { /** \todo parse error */ return HUBBUB_OK; } if (entry->stack_index == 0) { /* Not in element stack => remove from formatting list */ hubbub_ns ns; element_type type; void *node; uint32_t index; /** \todo parse error */ formatting_list_remove(treebuilder, entry, &ns, &type, &node, &index); treebuilder->tree_handler->unref_node( treebuilder->tree_handler->ctx, node); return HUBBUB_OK; } if (entry->stack_index != treebuilder->context.current_node) { /** \todo parse error */ } *element = entry; return HUBBUB_REPROCESS; } /** * Adoption agency: find formatting element * * \param treebuilder The treebuilder instance * \param type Type of element to search for * \return Pointer to formatting element, or NULL if none found */ formatting_list_entry *aa_find_formatting_element( hubbub_treebuilder *treebuilder, element_type type) { formatting_list_entry *entry; for (entry = treebuilder->context.formatting_list_end; entry != NULL; entry = entry->prev) { /* Assumption: HTML and TABLE elements are not in the list */ if (is_scoping_element(entry->details.type) || entry->details.type == type) break; } /* Check if we stopped on a marker, rather than a formatting element */ if (entry != NULL && is_scoping_element(entry->details.type)) entry = NULL; return entry; } /** * Adoption agency: find furthest block * * \param treebuilder The treebuilder instance * \param formatting_element The formatting element * \param furthest_block Pointer to location to receive furthest block * \return HUBBUB_REPROCESS to continue processing (::furthest_block filled in), * HUBBUB_OK to stop. */ hubbub_error aa_find_furthest_block(hubbub_treebuilder *treebuilder, formatting_list_entry *formatting_element, uint32_t *furthest_block) { uint32_t fe_index = formatting_element->stack_index; uint32_t fb; for (fb = fe_index + 1; fb <= treebuilder->context.current_node; fb++) { element_type type = treebuilder->context.element_stack[fb].type; if (!(is_phrasing_element(type) || is_formatting_element(type))) break; } if (fb > treebuilder->context.current_node) { hubbub_ns ns; element_type type; void *node; uint32_t index; /* Pop all elements off the stack up to, * and including, the formatting element */ do { element_stack_pop(treebuilder, &ns, &type, &node); treebuilder->tree_handler->unref_node( treebuilder->tree_handler->ctx, node); } while (treebuilder->context.current_node >= fe_index); /* Remove the formatting element from the list */ formatting_list_remove(treebuilder, formatting_element, &ns, &type, &node, &index); treebuilder->tree_handler->unref_node( treebuilder->tree_handler->ctx, node); return HUBBUB_OK; } *furthest_block = fb; return HUBBUB_REPROCESS; } /** * Adoption agency: reparent a node * * \param treebuilder The treebuilder instance * \param node The node to reparent * \param new_parent The new parent * \param reparented Pointer to location to receive reparented node * \return HUBBUB_OK on success, appropriate error otherwise */ hubbub_error aa_reparent_node(hubbub_treebuilder *treebuilder, void *node, void *new_parent, void **reparented) { hubbub_error err; err = remove_node_from_dom(treebuilder, node); if (err != HUBBUB_OK) return err; return treebuilder->tree_handler->append_child( treebuilder->tree_handler->ctx, new_parent, node, reparented); } /** * Adoption agency: this is step 6 * * \param treebuilder The treebuilder instance * \param formatting_element The stack index of the formatting element * \param furthest_block Pointer to index of furthest block in element * stack (updated on exit) * \param bookmark Pointer to bookmark (pre-initialised) * \param last_node Pointer to location to receive index of last node */ hubbub_error aa_find_bookmark_location_reparenting_misnested( hubbub_treebuilder *treebuilder, uint32_t formatting_element, uint32_t *furthest_block, bookmark *bookmark, uint32_t *last_node) { hubbub_error err; element_context *stack = treebuilder->context.element_stack; uint32_t node, last, fb; formatting_list_entry *node_entry; node = last = fb = *furthest_block; while (true) { void *reparented; /* i */ node--; /* ii */ for (node_entry = treebuilder->context.formatting_list_end; node_entry != NULL; node_entry = node_entry->prev) { if (node_entry->stack_index == node) break; } /* Node is not in list of active formatting elements */ if (node_entry == NULL) { err = aa_remove_element_stack_item(treebuilder, node, treebuilder->context.current_node); assert(err == HUBBUB_OK); /* Update furthest block index and the last node index, * as these are always below node in the stack */ fb--; last--; /* Fixup the current_node index */ treebuilder->context.current_node--; /* Back to i */ continue; } /* iii */ if (node == formatting_element) break; /* iv */ if (last == fb) { bookmark->prev = node_entry; bookmark->next = node_entry->next; } /* v */ err = aa_clone_and_replace_entries(treebuilder, node_entry); if (err != HUBBUB_OK) return err; /* vi */ err = aa_reparent_node(treebuilder, stack[last].node, stack[node].node, &reparented); if (err != HUBBUB_OK) return err; treebuilder->tree_handler->unref_node( treebuilder->tree_handler->ctx, stack[last].node); /* If the reparented node is not the same as the one we were * previously using, then have it take the place of the other * one in the formatting list and stack. */ if (reparented != stack[last].node) { for (node_entry = treebuilder->context.formatting_list_end; node_entry != NULL; node_entry = node_entry->prev) { if (node_entry->stack_index == last) { treebuilder->tree_handler->ref_node( treebuilder->tree_handler->ctx, reparented); node_entry->details.node = reparented; treebuilder->tree_handler->unref_node( treebuilder->tree_handler->ctx, stack[last].node); break; } } /* Already have enough references, so don't need to * explicitly reference it here. */ stack[last].node = reparented; } /* vii */ last = node; /* viii */ } *furthest_block = fb; *last_node = last; return HUBBUB_OK; } /** * Adoption agency: remove an entry from the stack at the given index * * \param treebuilder The treebuilder instance * \param index The index of the item to remove * \param limit The index of the last item to move * * Preconditions: index < limit, limit <= current_node * Postcondition: stack[limit] is empty */ hubbub_error aa_remove_element_stack_item(hubbub_treebuilder *treebuilder, uint32_t index, uint32_t limit) { element_context *stack = treebuilder->context.element_stack; uint32_t n; assert(index < limit); assert(limit <= treebuilder->context.current_node); /* First, scan over subsequent entries in the stack, * searching for them in the list of active formatting * entries. If found, update the corresponding * formatting list entry's stack index to match the * new stack location */ for (n = index + 1; n <= limit; n++) { if (is_formatting_element(stack[n].type) || (is_scoping_element(stack[n].type) && stack[n].type != HTML && stack[n].type != TABLE)) { formatting_list_entry *e; for (e = treebuilder->context.formatting_list_end; e != NULL; e = e->prev) { if (e->stack_index == n) e->stack_index--; } } } /* Reduce node's reference count */ treebuilder->tree_handler->unref_node(treebuilder->tree_handler->ctx, stack[index].node); /* Now, shuffle the stack up one, removing node in the process */ memmove(&stack[index], &stack[index + 1], (limit - index) * sizeof(element_context)); return HUBBUB_OK; } /** * Adoption agency: shallow clone a node and replace its formatting list * and element stack entries * * \param treebuilder The treebuilder instance * \param element The item in the formatting list containing the node */ hubbub_error aa_clone_and_replace_entries(hubbub_treebuilder *treebuilder, formatting_list_entry *element) { hubbub_error err; hubbub_ns ons; element_type otype; uint32_t oindex; void *clone, *onode; /* Shallow clone of node */ err = treebuilder->tree_handler->clone_node( treebuilder->tree_handler->ctx, element->details.node, false, &clone); if (err != HUBBUB_OK) return err; /* Replace formatting list entry for node with clone */ err = formatting_list_replace(treebuilder, element, element->details.ns, element->details.type, clone, element->stack_index, &ons, &otype, &onode, &oindex); assert(err == HUBBUB_OK); treebuilder->tree_handler->unref_node(treebuilder->tree_handler->ctx, onode); treebuilder->tree_handler->ref_node(treebuilder->tree_handler->ctx, clone); /* Replace node's stack entry with clone */ treebuilder->context.element_stack[element->stack_index].node = clone; treebuilder->tree_handler->unref_node(treebuilder->tree_handler->ctx, onode); return HUBBUB_OK; } /** * Adoption agency: locate foster parent and insert node into it * * \param treebuilder The treebuilder instance * \param node The node to insert * \param inserted Pointer to location to receive inserted node * \return HUBBUB_OK on success, appropriate error otherwise */ hubbub_error aa_insert_into_foster_parent(hubbub_treebuilder *treebuilder, void *node, void **inserted) { hubbub_error err; element_context *stack = treebuilder->context.element_stack; void *foster_parent = NULL; bool insert = false; uint32_t cur_table = current_table(treebuilder); stack[cur_table].tainted = true; if (cur_table == 0) { treebuilder->tree_handler->ref_node( treebuilder->tree_handler->ctx, stack[0].node); foster_parent = stack[0].node; } else { void *t_parent = NULL; treebuilder->tree_handler->get_parent( treebuilder->tree_handler->ctx, stack[cur_table].node, true, &t_parent); if (t_parent != NULL) { foster_parent = t_parent; insert = true; } else { treebuilder->tree_handler->ref_node( treebuilder->tree_handler->ctx, stack[cur_table - 1].node); foster_parent = stack[cur_table - 1].node; } } err = remove_node_from_dom(treebuilder, node); if (err != HUBBUB_OK) { treebuilder->tree_handler->unref_node( treebuilder->tree_handler->ctx, foster_parent); return err; } if (insert) { err = treebuilder->tree_handler->insert_before( treebuilder->tree_handler->ctx, foster_parent, node, stack[cur_table].node, inserted); } else { err = treebuilder->tree_handler->append_child( treebuilder->tree_handler->ctx, foster_parent, node, inserted); } if (err != HUBBUB_OK) { treebuilder->tree_handler->unref_node( treebuilder->tree_handler->ctx, foster_parent); return err; } treebuilder->tree_handler->unref_node(treebuilder->tree_handler->ctx, foster_parent); return HUBBUB_OK; } /** * Process an applet, button, marquee, or object end tag as if in "in body" * * \param treebuilder The treebuilder instance * \param type The element type */ hubbub_error process_0applet_button_marquee_object_in_body( hubbub_treebuilder *treebuilder, element_type type) { if (!element_in_scope(treebuilder, type, false)) { /** \todo parse error */ } else { uint32_t popped = 0; element_type otype; close_implied_end_tags(treebuilder, UNKNOWN); do { hubbub_ns ns; void *node; element_stack_pop(treebuilder, &ns, &otype, &node); treebuilder->tree_handler->unref_node( treebuilder->tree_handler->ctx, node); popped++; } while (otype != type); if (popped > 1) { /** \todo parse error */ } clear_active_formatting_list_to_marker(treebuilder); } return HUBBUB_OK; } /** * Process a br end tag as if in "in body" * * \param treebuilder The treebuilder instance */ hubbub_error process_0br_in_body(hubbub_treebuilder *treebuilder) { hubbub_error err; hubbub_tag tag; /** \todo parse error */ /* Act as if
has been seen. */ tag.ns = HUBBUB_NS_HTML; tag.name.ptr = (const uint8_t *) "br"; tag.name.len = SLEN("br"); tag.n_attributes = 0; tag.attributes = NULL; err = reconstruct_active_formatting_list(treebuilder); if (err != HUBBUB_OK) return err; return insert_element(treebuilder, &tag, false); } /** * Process a generic end tag as if in "in body" * * \param treebuilder The treebuilder instance * \param type The element type */ hubbub_error process_0generic_in_body(hubbub_treebuilder *treebuilder, element_type type) { element_context *stack = treebuilder->context.element_stack; uint32_t node = treebuilder->context.current_node; do { if (stack[node].type == type) { uint32_t popped = 0; element_type otype; close_implied_end_tags(treebuilder, UNKNOWN); while (treebuilder->context.current_node >= node) { hubbub_ns ns; void *node; element_stack_pop(treebuilder, &ns, &otype, &node); treebuilder->tree_handler->unref_node( treebuilder->tree_handler->ctx, node); popped++; if (otype == type) break; } if (popped > 1) { /** \todo parse error */ } break; } else if (!is_formatting_element(stack[node].type) && !is_phrasing_element(stack[node].type)) { /** \todo parse error */ break; } } while (--node > 0); return HUBBUB_OK; }