LibWeb: Implement relaxed parser for <select>
- Allow general content within select element - Update processing of options for select See https://github.com/whatwg/html/pull/10557
This commit is contained in:
parent
d4d335ebda
commit
7f9a10913f
10 changed files with 247 additions and 309 deletions
|
@ -36,14 +36,4 @@ void HTMLOptGroupElement::inserted()
|
|||
static_cast<HTMLSelectElement&>(*parent()).update_selectedness();
|
||||
}
|
||||
|
||||
void HTMLOptGroupElement::removed_from(Node* old_parent)
|
||||
{
|
||||
Base::removed_from(old_parent);
|
||||
|
||||
// The optgroup HTML element removing steps, given removedNode and oldParent, are:
|
||||
// 1. If oldParent is a select element and removedNode has an option child, then run oldParent's selectedness setting algorithm.
|
||||
if (old_parent && is<HTMLSelectElement>(*old_parent) && first_child_of_type<HTMLOptionElement>())
|
||||
static_cast<HTMLSelectElement&>(*old_parent).update_selectedness();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -25,7 +25,6 @@ private:
|
|||
HTMLOptGroupElement(DOM::Document&, DOM::QualifiedName);
|
||||
|
||||
virtual void initialize(JS::Realm&) override;
|
||||
virtual void removed_from(Node*) override;
|
||||
virtual void inserted() override;
|
||||
};
|
||||
|
||||
|
|
|
@ -205,28 +205,29 @@ void HTMLOptionElement::inserted()
|
|||
|
||||
set_selected_internal(selected());
|
||||
|
||||
// 1. The option HTML element insertion steps, given insertedNode, are:
|
||||
// If insertedNode's parent is a select element,
|
||||
// or insertedNode's parent is an optgroup element whose parent is a select element,
|
||||
// then run that select element's selectedness setting algorithm.
|
||||
if (is<HTMLSelectElement>(*parent()))
|
||||
static_cast<HTMLSelectElement&>(*parent()).update_selectedness();
|
||||
else if (is<HTMLOptGroupElement>(parent()) && parent()->parent() && is<HTMLSelectElement>(*parent()->parent()))
|
||||
static_cast<HTMLSelectElement&>(*parent()->parent()).update_selectedness();
|
||||
// The option HTML element insertion steps, given insertedOption, are:
|
||||
// 1. For each ancestor of insertedOption's ancestors in reverse tree order:
|
||||
for (auto* ancestor = parent_node(); ancestor; ancestor = ancestor->parent_node()) {
|
||||
// 1. If ancestor is a select element, then run the selectedness setting algorithm given ancestor and return.
|
||||
if (is<HTMLSelectElement>(*ancestor)) {
|
||||
static_cast<HTMLSelectElement&>(*ancestor).update_selectedness();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HTMLOptionElement::removed_from(Node* old_parent)
|
||||
{
|
||||
Base::removed_from(old_parent);
|
||||
|
||||
// The option HTML element removing steps, given removedNode and oldParent, are:
|
||||
// 1. If oldParent is a select element, or oldParent is an optgroup element whose parent is a select element,
|
||||
// then run that select element's selectedness setting algorithm.
|
||||
if (old_parent) {
|
||||
if (is<HTMLSelectElement>(*old_parent))
|
||||
static_cast<HTMLSelectElement&>(*old_parent).update_selectedness();
|
||||
else if (is<HTMLOptGroupElement>(*old_parent) && old_parent->parent_element() && is<HTMLSelectElement>(old_parent->parent_element()))
|
||||
static_cast<HTMLSelectElement&>(*old_parent->parent_element()).update_selectedness();
|
||||
// The option HTML element removing steps, given removedOption and oldParent, are:
|
||||
// 1. For each ancestor of oldParent's inclusive ancestors in reverse tree order:
|
||||
for (auto* ancestor = old_parent; ancestor; ancestor = ancestor->parent_node()) {
|
||||
// 1. If ancestor is a select element, then run the selectedness setting algorithm given ancestor and return.
|
||||
if (is<HTMLSelectElement>(*ancestor)) {
|
||||
static_cast<HTMLSelectElement&>(*ancestor).update_selectedness();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -199,24 +199,20 @@ GC::Ref<DOM::HTMLCollection> HTMLSelectElement::selected_options()
|
|||
// https://html.spec.whatwg.org/multipage/form-elements.html#concept-select-option-list
|
||||
Vector<GC::Root<HTMLOptionElement>> HTMLSelectElement::list_of_options() const
|
||||
{
|
||||
// The list of options for a select element consists of all the option element children of the select element,
|
||||
// and all the option element children of all the optgroup element children of the select element, in tree order.
|
||||
Vector<GC::Root<HTMLOptionElement>> list;
|
||||
// 1. Let options be « ».
|
||||
Vector<GC::Root<HTMLOptionElement>> options;
|
||||
// 2. For each node of select's descendants in tree order except the descendants which are select elements and their subtrees:
|
||||
for_each_in_subtree([&](auto& node) {
|
||||
if (is<HTMLSelectElement>(node))
|
||||
return TraversalDecision::Break;
|
||||
|
||||
for_each_child_of_type<HTMLOptionElement>([&](HTMLOptionElement& option_element) {
|
||||
list.append(GC::make_root(option_element));
|
||||
return IterationDecision::Continue;
|
||||
// 1. If node is an option element, then append node to options.
|
||||
if (is<HTMLOptionElement>(node))
|
||||
options.append(GC::make_root(const_cast<HTMLOptionElement&>(static_cast<HTMLOptionElement const&>(node))));
|
||||
return TraversalDecision::Continue;
|
||||
});
|
||||
|
||||
for_each_child_of_type<HTMLOptGroupElement>([&](HTMLOptGroupElement const& optgroup_element) {
|
||||
optgroup_element.for_each_child_of_type<HTMLOptionElement>([&](HTMLOptionElement& option_element) {
|
||||
list.append(GC::make_root(option_element));
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
|
||||
return list;
|
||||
// 3. Return options.
|
||||
return options;
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/form-elements.html#the-select-element:concept-form-reset-control
|
||||
|
|
|
@ -434,12 +434,6 @@ void HTMLParser::process_using_the_rules_for(InsertionMode mode, HTMLToken& toke
|
|||
case InsertionMode::InTableText:
|
||||
handle_in_table_text(token);
|
||||
break;
|
||||
case InsertionMode::InSelectInTable:
|
||||
handle_in_select_in_table(token);
|
||||
break;
|
||||
case InsertionMode::InSelect:
|
||||
handle_in_select(token);
|
||||
break;
|
||||
case InsertionMode::InCaption:
|
||||
handle_in_caption(token);
|
||||
break;
|
||||
|
@ -2164,7 +2158,7 @@ void HTMLParser::handle_in_body(HTMLToken& token)
|
|||
}
|
||||
|
||||
// -> An end tag whose tag name is one of: "address", "article", "aside", "blockquote", "button", "center", "details", "dialog", "dir", "div", "dl", "fieldset", "figcaption", "figure", "footer", "header", "hgroup", "listing", "main", "menu", "nav", "ol", "pre", "search", "section", "summary", "ul"
|
||||
if (token.is_end_tag() && token.tag_name().is_one_of(HTML::TagNames::address, HTML::TagNames::article, HTML::TagNames::aside, HTML::TagNames::blockquote, HTML::TagNames::button, HTML::TagNames::center, HTML::TagNames::details, HTML::TagNames::dialog, HTML::TagNames::dir, HTML::TagNames::div, HTML::TagNames::dl, HTML::TagNames::fieldset, HTML::TagNames::figcaption, HTML::TagNames::figure, HTML::TagNames::footer, HTML::TagNames::header, HTML::TagNames::hgroup, HTML::TagNames::listing, HTML::TagNames::main, HTML::TagNames::menu, HTML::TagNames::nav, HTML::TagNames::ol, HTML::TagNames::pre, HTML::TagNames::search, HTML::TagNames::section, HTML::TagNames::summary, HTML::TagNames::ul)) {
|
||||
if (token.is_end_tag() && token.tag_name().is_one_of(HTML::TagNames::address, HTML::TagNames::article, HTML::TagNames::aside, HTML::TagNames::blockquote, HTML::TagNames::button, HTML::TagNames::center, HTML::TagNames::details, HTML::TagNames::dialog, HTML::TagNames::dir, HTML::TagNames::div, HTML::TagNames::dl, HTML::TagNames::fieldset, HTML::TagNames::figcaption, HTML::TagNames::figure, HTML::TagNames::footer, HTML::TagNames::header, HTML::TagNames::hgroup, HTML::TagNames::listing, HTML::TagNames::main, HTML::TagNames::menu, HTML::TagNames::nav, HTML::TagNames::ol, HTML::TagNames::pre, HTML::TagNames::search, HTML::TagNames::section, HTML::TagNames::summary, HTML::TagNames::ul, HTML::TagNames::select)) {
|
||||
// If the stack of open elements does not have an element in scope that is an HTML element with the same tag name as that of the token, then this is a parse error; ignore the token.
|
||||
if (!m_stack_of_open_elements.has_in_scope(token.tag_name())) {
|
||||
log_parse_error();
|
||||
|
@ -2514,6 +2508,16 @@ void HTMLParser::handle_in_body(HTMLToken& token)
|
|||
if (m_stack_of_open_elements.has_in_button_scope(HTML::TagNames::p))
|
||||
close_a_p_element();
|
||||
|
||||
// If the stack of open elements has a select element in scope, then:
|
||||
if (m_stack_of_open_elements.has_in_scope(HTML::TagNames::select)) {
|
||||
// 1. Generate implied end tags.
|
||||
generate_implied_end_tags();
|
||||
|
||||
// 2. If the stack of open elements has an option element in scope or has an optgroup element in scope, then this is a parse error.
|
||||
if (m_stack_of_open_elements.has_in_scope(HTML::TagNames::option) || m_stack_of_open_elements.has_in_scope(HTML::TagNames::optgroup))
|
||||
log_parse_error();
|
||||
}
|
||||
|
||||
// Insert an HTML element for the token. Immediately pop the current node off the stack of open elements.
|
||||
(void)insert_html_element(token);
|
||||
(void)m_stack_of_open_elements.pop();
|
||||
|
@ -2606,35 +2610,65 @@ void HTMLParser::handle_in_body(HTMLToken& token)
|
|||
|
||||
// -> A start tag whose tag name is "select"
|
||||
if (token.is_start_tag() && token.tag_name() == HTML::TagNames::select) {
|
||||
// If the stack of open elements has a select element in scope then:
|
||||
if (m_stack_of_open_elements.has_in_scope(token.tag_name())) {
|
||||
// 1. Parse error.
|
||||
log_parse_error();
|
||||
|
||||
// 2. Pop elements from the stack of open elements until a select element has been popped from the stack.
|
||||
m_stack_of_open_elements.pop_until_an_element_with_tag_name_has_been_popped(token.tag_name());
|
||||
}
|
||||
// Otherwise
|
||||
else {
|
||||
// Reconstruct the active formatting elements, if any.
|
||||
reconstruct_the_active_formatting_elements();
|
||||
|
||||
// Insert an HTML element for the token.
|
||||
(void)insert_html_element(token);
|
||||
|
||||
// Set the frameset-ok flag to "not ok".
|
||||
m_frameset_ok = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// -> A start tag whose tag name is "option"
|
||||
if (token.is_start_tag() && token.tag_name() == HTML::TagNames::option) {
|
||||
// If the stack of open elements has a select element in scope, then:
|
||||
if (m_stack_of_open_elements.has_in_scope(HTML::TagNames::select)) {
|
||||
// 1. Generate implied end tags except for optgroup elements.
|
||||
generate_implied_end_tags(HTML::TagNames::optgroup);
|
||||
// 2. If the stack of open elements has an option element in scope, then this is a parse error.
|
||||
if (m_stack_of_open_elements.has_in_scope(HTML::TagNames::option))
|
||||
log_parse_error();
|
||||
}
|
||||
// Otherwise:
|
||||
else {
|
||||
// 1. If the current node is an option element, then pop the current node off the stack of open elements.
|
||||
if (current_node()->local_name() == HTML::TagNames::option)
|
||||
(void)m_stack_of_open_elements.pop();
|
||||
}
|
||||
|
||||
// Reconstruct the active formatting elements, if any.
|
||||
reconstruct_the_active_formatting_elements();
|
||||
|
||||
// Insert an HTML element for the token.
|
||||
(void)insert_html_element(token);
|
||||
|
||||
// Set the frameset-ok flag to "not ok".
|
||||
m_frameset_ok = false;
|
||||
|
||||
// If the insertion mode is one of "in table", "in caption", "in table body", "in row", or "in cell", then switch the insertion mode to "in select in table". Otherwise, switch the insertion mode to "in select".
|
||||
switch (m_insertion_mode) {
|
||||
case InsertionMode::InTable:
|
||||
case InsertionMode::InCaption:
|
||||
case InsertionMode::InTableBody:
|
||||
case InsertionMode::InRow:
|
||||
case InsertionMode::InCell:
|
||||
m_insertion_mode = InsertionMode::InSelectInTable;
|
||||
break;
|
||||
default:
|
||||
m_insertion_mode = InsertionMode::InSelect;
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// -> A start tag whose tag name is one of: "optgroup", "option"
|
||||
if (token.is_start_tag() && token.tag_name().is_one_of(HTML::TagNames::optgroup, HTML::TagNames::option)) {
|
||||
// If the current node is an option element, then pop the current node off the stack of open elements.
|
||||
if (current_node()->local_name() == HTML::TagNames::option)
|
||||
// -> A start tag whose tag name is "optgroup"
|
||||
if (token.is_start_tag() && token.tag_name() == HTML::TagNames::optgroup) {
|
||||
// If the stack of open elements has a select element in scope, then:
|
||||
if (m_stack_of_open_elements.has_in_scope(HTML::TagNames::select)) {
|
||||
// 1. Generate implied end tags.
|
||||
generate_implied_end_tags();
|
||||
// 2. If the stack of open elements has an option element in scope or has an optgroup element in scope, then this is a parse error.
|
||||
if (m_stack_of_open_elements.has_in_scope(HTML::TagNames::option) || m_stack_of_open_elements.has_in_scope(HTML::TagNames::optgroup))
|
||||
log_parse_error();
|
||||
}
|
||||
// Otherwise, if the current node is an option element, then pop the current node from the stack of open elements.
|
||||
else if (current_node()->local_name() == HTML::TagNames::option)
|
||||
(void)m_stack_of_open_elements.pop();
|
||||
|
||||
// Reconstruct the active formatting elements, if any.
|
||||
|
@ -3582,173 +3616,6 @@ AnythingElse:
|
|||
m_foster_parenting = false;
|
||||
}
|
||||
|
||||
void HTMLParser::handle_in_select_in_table(HTMLToken& token)
|
||||
{
|
||||
if (token.is_start_tag() && token.tag_name().is_one_of(HTML::TagNames::caption, HTML::TagNames::table, HTML::TagNames::tbody, HTML::TagNames::tfoot, HTML::TagNames::thead, HTML::TagNames::tr, HTML::TagNames::td, HTML::TagNames::th)) {
|
||||
log_parse_error();
|
||||
m_stack_of_open_elements.pop_until_an_element_with_tag_name_has_been_popped(HTML::TagNames::select);
|
||||
reset_the_insertion_mode_appropriately();
|
||||
process_using_the_rules_for(m_insertion_mode, token);
|
||||
return;
|
||||
}
|
||||
|
||||
if (token.is_end_tag() && token.tag_name().is_one_of(HTML::TagNames::caption, HTML::TagNames::table, HTML::TagNames::tbody, HTML::TagNames::tfoot, HTML::TagNames::thead, HTML::TagNames::tr, HTML::TagNames::td, HTML::TagNames::th)) {
|
||||
log_parse_error();
|
||||
|
||||
if (!m_stack_of_open_elements.has_in_table_scope(token.tag_name()))
|
||||
return;
|
||||
|
||||
m_stack_of_open_elements.pop_until_an_element_with_tag_name_has_been_popped(HTML::TagNames::select);
|
||||
reset_the_insertion_mode_appropriately();
|
||||
process_using_the_rules_for(m_insertion_mode, token);
|
||||
return;
|
||||
}
|
||||
|
||||
process_using_the_rules_for(InsertionMode::InSelect, token);
|
||||
}
|
||||
|
||||
void HTMLParser::handle_in_select(HTMLToken& token)
|
||||
{
|
||||
if (token.is_character()) {
|
||||
if (token.code_point() == 0) {
|
||||
log_parse_error();
|
||||
return;
|
||||
}
|
||||
insert_character(token.code_point());
|
||||
return;
|
||||
}
|
||||
|
||||
if (token.is_comment()) {
|
||||
insert_comment(token);
|
||||
return;
|
||||
}
|
||||
|
||||
if (token.is_doctype()) {
|
||||
log_parse_error();
|
||||
return;
|
||||
}
|
||||
|
||||
if (token.is_start_tag() && token.tag_name() == HTML::TagNames::html) {
|
||||
process_using_the_rules_for(InsertionMode::InBody, token);
|
||||
return;
|
||||
}
|
||||
|
||||
if (token.is_start_tag() && token.tag_name() == HTML::TagNames::option) {
|
||||
if (current_node()->local_name() == HTML::TagNames::option) {
|
||||
(void)m_stack_of_open_elements.pop();
|
||||
}
|
||||
(void)insert_html_element(token);
|
||||
return;
|
||||
}
|
||||
|
||||
if (token.is_start_tag() && token.tag_name() == HTML::TagNames::optgroup) {
|
||||
if (current_node()->local_name() == HTML::TagNames::option) {
|
||||
(void)m_stack_of_open_elements.pop();
|
||||
}
|
||||
if (current_node()->local_name() == HTML::TagNames::optgroup) {
|
||||
(void)m_stack_of_open_elements.pop();
|
||||
}
|
||||
(void)insert_html_element(token);
|
||||
return;
|
||||
}
|
||||
|
||||
// -> A start tag whose tag name is "hr"
|
||||
if (token.is_start_tag() && token.tag_name() == HTML::TagNames::hr) {
|
||||
// If the current node is an option element, pop that node from the stack of open elements.
|
||||
if (current_node()->local_name() == HTML::TagNames::option) {
|
||||
(void)m_stack_of_open_elements.pop();
|
||||
}
|
||||
// If the current node is an optgroup element, pop that node from the stack of open elements.
|
||||
if (current_node()->local_name() == HTML::TagNames::optgroup) {
|
||||
(void)m_stack_of_open_elements.pop();
|
||||
}
|
||||
// Insert an HTML element for the token. Immediately pop the current node off the stack of open elements.
|
||||
(void)insert_html_element(token);
|
||||
(void)m_stack_of_open_elements.pop();
|
||||
// Acknowledge the token's self-closing flag, if it is set.
|
||||
token.acknowledge_self_closing_flag_if_set();
|
||||
return;
|
||||
}
|
||||
|
||||
if (token.is_end_tag() && token.tag_name() == HTML::TagNames::optgroup) {
|
||||
if (current_node()->local_name() == HTML::TagNames::option && node_before_current_node()->local_name() == HTML::TagNames::optgroup)
|
||||
(void)m_stack_of_open_elements.pop();
|
||||
|
||||
if (current_node()->local_name() == HTML::TagNames::optgroup) {
|
||||
(void)m_stack_of_open_elements.pop();
|
||||
} else {
|
||||
log_parse_error();
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (token.is_end_tag() && token.tag_name() == HTML::TagNames::option) {
|
||||
if (current_node()->local_name() == HTML::TagNames::option) {
|
||||
(void)m_stack_of_open_elements.pop();
|
||||
} else {
|
||||
log_parse_error();
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (token.is_end_tag() && token.tag_name() == HTML::TagNames::select) {
|
||||
if (!m_stack_of_open_elements.has_in_select_scope(HTML::TagNames::select)) {
|
||||
VERIFY(m_parsing_fragment);
|
||||
log_parse_error();
|
||||
return;
|
||||
}
|
||||
m_stack_of_open_elements.pop_until_an_element_with_tag_name_has_been_popped(HTML::TagNames::select);
|
||||
reset_the_insertion_mode_appropriately();
|
||||
return;
|
||||
}
|
||||
|
||||
if (token.is_start_tag() && token.tag_name() == HTML::TagNames::select) {
|
||||
log_parse_error();
|
||||
|
||||
if (!m_stack_of_open_elements.has_in_select_scope(HTML::TagNames::select)) {
|
||||
VERIFY(m_parsing_fragment);
|
||||
return;
|
||||
}
|
||||
|
||||
m_stack_of_open_elements.pop_until_an_element_with_tag_name_has_been_popped(HTML::TagNames::select);
|
||||
reset_the_insertion_mode_appropriately();
|
||||
return;
|
||||
}
|
||||
|
||||
if (token.is_start_tag() && token.tag_name().is_one_of(HTML::TagNames::input, HTML::TagNames::keygen, HTML::TagNames::textarea)) {
|
||||
log_parse_error();
|
||||
|
||||
if (!m_stack_of_open_elements.has_in_select_scope(HTML::TagNames::select)) {
|
||||
VERIFY(m_parsing_fragment);
|
||||
return;
|
||||
}
|
||||
|
||||
m_stack_of_open_elements.pop_until_an_element_with_tag_name_has_been_popped(HTML::TagNames::select);
|
||||
reset_the_insertion_mode_appropriately();
|
||||
process_using_the_rules_for(m_insertion_mode, token);
|
||||
return;
|
||||
}
|
||||
|
||||
if (token.is_start_tag() && token.tag_name().is_one_of(HTML::TagNames::script, HTML::TagNames::template_)) {
|
||||
process_using_the_rules_for(InsertionMode::InHead, token);
|
||||
return;
|
||||
}
|
||||
|
||||
if (token.is_end_tag() && token.tag_name() == HTML::TagNames::template_) {
|
||||
process_using_the_rules_for(InsertionMode::InHead, token);
|
||||
return;
|
||||
}
|
||||
|
||||
if (token.is_end_of_file()) {
|
||||
process_using_the_rules_for(InsertionMode::InBody, token);
|
||||
return;
|
||||
}
|
||||
|
||||
log_parse_error();
|
||||
}
|
||||
|
||||
void HTMLParser::handle_in_caption(HTMLToken& token)
|
||||
{
|
||||
if (token.is_end_tag() && token.tag_name() == HTML::TagNames::caption) {
|
||||
|
@ -4348,25 +4215,6 @@ void HTMLParser::reset_the_insertion_mode_appropriately()
|
|||
if (node->namespace_uri() != Namespace::HTML)
|
||||
continue;
|
||||
|
||||
if (node->local_name() == HTML::TagNames::select) {
|
||||
if (!last) {
|
||||
for (ssize_t j = i; j > 0; --j) {
|
||||
auto& ancestor = m_stack_of_open_elements.elements().at(j - 1);
|
||||
|
||||
if (is<HTMLTemplateElement>(*ancestor))
|
||||
break;
|
||||
|
||||
if (is<HTMLTableElement>(*ancestor)) {
|
||||
m_insertion_mode = InsertionMode::InSelectInTable;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_insertion_mode = InsertionMode::InSelect;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!last && node->local_name().is_one_of(HTML::TagNames::td, HTML::TagNames::th)) {
|
||||
m_insertion_mode = InsertionMode::InCell;
|
||||
return;
|
||||
|
|
|
@ -16,29 +16,27 @@
|
|||
|
||||
namespace Web::HTML {
|
||||
|
||||
#define ENUMERATE_INSERTION_MODES \
|
||||
__ENUMERATE_INSERTION_MODE(Initial) \
|
||||
__ENUMERATE_INSERTION_MODE(BeforeHTML) \
|
||||
__ENUMERATE_INSERTION_MODE(BeforeHead) \
|
||||
__ENUMERATE_INSERTION_MODE(InHead) \
|
||||
__ENUMERATE_INSERTION_MODE(InHeadNoscript) \
|
||||
__ENUMERATE_INSERTION_MODE(AfterHead) \
|
||||
__ENUMERATE_INSERTION_MODE(InBody) \
|
||||
__ENUMERATE_INSERTION_MODE(Text) \
|
||||
__ENUMERATE_INSERTION_MODE(InTable) \
|
||||
__ENUMERATE_INSERTION_MODE(InTableText) \
|
||||
__ENUMERATE_INSERTION_MODE(InCaption) \
|
||||
__ENUMERATE_INSERTION_MODE(InColumnGroup) \
|
||||
__ENUMERATE_INSERTION_MODE(InTableBody) \
|
||||
__ENUMERATE_INSERTION_MODE(InRow) \
|
||||
__ENUMERATE_INSERTION_MODE(InCell) \
|
||||
__ENUMERATE_INSERTION_MODE(InSelect) \
|
||||
__ENUMERATE_INSERTION_MODE(InSelectInTable) \
|
||||
__ENUMERATE_INSERTION_MODE(InTemplate) \
|
||||
__ENUMERATE_INSERTION_MODE(AfterBody) \
|
||||
__ENUMERATE_INSERTION_MODE(InFrameset) \
|
||||
__ENUMERATE_INSERTION_MODE(AfterFrameset) \
|
||||
__ENUMERATE_INSERTION_MODE(AfterAfterBody) \
|
||||
#define ENUMERATE_INSERTION_MODES \
|
||||
__ENUMERATE_INSERTION_MODE(Initial) \
|
||||
__ENUMERATE_INSERTION_MODE(BeforeHTML) \
|
||||
__ENUMERATE_INSERTION_MODE(BeforeHead) \
|
||||
__ENUMERATE_INSERTION_MODE(InHead) \
|
||||
__ENUMERATE_INSERTION_MODE(InHeadNoscript) \
|
||||
__ENUMERATE_INSERTION_MODE(AfterHead) \
|
||||
__ENUMERATE_INSERTION_MODE(InBody) \
|
||||
__ENUMERATE_INSERTION_MODE(Text) \
|
||||
__ENUMERATE_INSERTION_MODE(InTable) \
|
||||
__ENUMERATE_INSERTION_MODE(InTableText) \
|
||||
__ENUMERATE_INSERTION_MODE(InCaption) \
|
||||
__ENUMERATE_INSERTION_MODE(InColumnGroup) \
|
||||
__ENUMERATE_INSERTION_MODE(InTableBody) \
|
||||
__ENUMERATE_INSERTION_MODE(InRow) \
|
||||
__ENUMERATE_INSERTION_MODE(InCell) \
|
||||
__ENUMERATE_INSERTION_MODE(InTemplate) \
|
||||
__ENUMERATE_INSERTION_MODE(AfterBody) \
|
||||
__ENUMERATE_INSERTION_MODE(InFrameset) \
|
||||
__ENUMERATE_INSERTION_MODE(AfterFrameset) \
|
||||
__ENUMERATE_INSERTION_MODE(AfterAfterBody) \
|
||||
__ENUMERATE_INSERTION_MODE(AfterAfterFrameset)
|
||||
|
||||
class HTMLParser final : public JS::Cell {
|
||||
|
@ -116,8 +114,6 @@ private:
|
|||
void handle_in_row(HTMLToken&);
|
||||
void handle_in_cell(HTMLToken&);
|
||||
void handle_in_table_text(HTMLToken&);
|
||||
void handle_in_select_in_table(HTMLToken&);
|
||||
void handle_in_select(HTMLToken&);
|
||||
void handle_in_caption(HTMLToken&);
|
||||
void handle_in_column_group(HTMLToken&);
|
||||
void handle_in_template(HTMLToken&);
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
namespace Web::HTML {
|
||||
|
||||
static Vector<FlyString> s_base_list { "applet"_fly_string, "caption"_fly_string, "html"_fly_string, "table"_fly_string, "td"_fly_string, "th"_fly_string, "marquee"_fly_string, "object"_fly_string, "template"_fly_string };
|
||||
static Vector<FlyString> s_base_list { "applet"_fly_string, "caption"_fly_string, "html"_fly_string, "table"_fly_string, "td"_fly_string, "th"_fly_string, "marquee"_fly_string, "object"_fly_string, "template"_fly_string, "select"_fly_string };
|
||||
|
||||
StackOfOpenElements::~StackOfOpenElements() = default;
|
||||
|
||||
|
@ -72,31 +72,6 @@ bool StackOfOpenElements::has_in_list_item_scope(FlyString const& tag_name) cons
|
|||
return has_in_scope_impl(tag_name, list);
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/parsing.html#has-an-element-in-select-scope
|
||||
// The stack of open elements is said to have a particular element in select scope
|
||||
// when it has that element in the specific scope consisting of all element types except the following:
|
||||
// - optgroup in the HTML namespace
|
||||
// - option in the HTML namespace
|
||||
// NOTE: In this case it's "all element types _except_"
|
||||
bool StackOfOpenElements::has_in_select_scope(FlyString const& tag_name) const
|
||||
{
|
||||
// https://html.spec.whatwg.org/multipage/parsing.html#has-an-element-in-the-specific-scope
|
||||
// 1. Initialize node to be the current node (the bottommost node of the stack).
|
||||
for (auto& node : m_elements.in_reverse()) {
|
||||
// 2. If node is the target node, terminate in a match state.
|
||||
if (node->local_name() == tag_name)
|
||||
return true;
|
||||
// 3. Otherwise, if node is one of the element types in list, terminate in a failure state.
|
||||
// NOTE: Here "list" refers to all elements except option and optgroup
|
||||
if (node->local_name() != HTML::TagNames::option && node->local_name() != HTML::TagNames::optgroup)
|
||||
return false;
|
||||
// 4. Otherwise, set node to the previous entry in the stack of open elements and return to step 2.
|
||||
}
|
||||
// [4.] (This will never fail, since the loop will always terminate in the previous step if the top of the stack
|
||||
// — an html element — is reached.)
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
bool StackOfOpenElements::contains(const DOM::Element& element) const
|
||||
{
|
||||
for (auto& element_on_stack : m_elements) {
|
||||
|
|
|
@ -39,7 +39,6 @@ public:
|
|||
bool has_in_button_scope(FlyString const& tag_name) const;
|
||||
bool has_in_table_scope(FlyString const& tag_name) const;
|
||||
bool has_in_list_item_scope(FlyString const& tag_name) const;
|
||||
bool has_in_select_scope(FlyString const& tag_name) const;
|
||||
|
||||
bool has_in_scope(const DOM::Element&) const;
|
||||
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
Harness status: OK
|
||||
|
||||
Found 11 tests
|
||||
|
||||
11 Pass
|
||||
Pass <div>s, <button>s, and <datalist>s should be allowed in <select>
|
||||
Pass </select> should close <button>
|
||||
Pass </select> should close <datalist>
|
||||
Pass <select> in <button> in <select> should remove inner <select>
|
||||
Pass <select> in <select><button><div> should remove inner <select>
|
||||
Pass Divs and imgs should be allowed as direct children of select and within options without a datalist
|
||||
Pass Input tags should parse inside select instead of closing the select
|
||||
Pass textarea tags should parse inside select instead of closing the select
|
||||
Pass The last test should not leave any tags open after parsing
|
||||
Pass Nested selects should be retained 1
|
||||
Pass Nested selects should be retained 2
|
|
@ -0,0 +1,118 @@
|
|||
<!DOCTYPE html>
|
||||
<link rel=author href="mailto:jarhar@chromium.org">
|
||||
<link rel=help href="https://github.com/whatwg/html/issues/9799">
|
||||
<script src="../../../../../resources/testharness.js"></script>
|
||||
<script src="../../../../../resources/testharnessreport.js"></script>
|
||||
|
||||
<body>
|
||||
|
||||
<select class=test
|
||||
data-description='<div>s, <button>s, and <datalist>s should be allowed in <select>'
|
||||
data-expect='
|
||||
<div>div 1</div>
|
||||
<button>button</button>
|
||||
<div>div 2</div>
|
||||
<datalist>
|
||||
<option>option</option>
|
||||
</datalist>
|
||||
<div>div 3</div>
|
||||
'>
|
||||
<div>div 1</div>
|
||||
<button>button</button>
|
||||
<div>div 2</div>
|
||||
<datalist>
|
||||
<option>option</option>
|
||||
</datalist>
|
||||
<div>div 3</div>
|
||||
</select>
|
||||
|
||||
<select class=test
|
||||
data-description='</select> should close <button>'
|
||||
data-expect='<button>button</button>'>
|
||||
<button>button
|
||||
</select>
|
||||
|
||||
<select class=test
|
||||
data-description='</select> should close <datalist>'
|
||||
data-expect='<datalist>datalist</datalist>'>
|
||||
<datalist>datalist
|
||||
</select>
|
||||
|
||||
<select id=nested1 class=test
|
||||
data-description='<select> in <button> in <select> should remove inner <select>'
|
||||
data-expect='<button></button>'>
|
||||
<button>
|
||||
<select id=expectafter1></select>
|
||||
<div id=expectafter1b></div>
|
||||
</button>
|
||||
</select>
|
||||
|
||||
<select id=nested2 class=test
|
||||
data-description='<select> in <select><button><div> should remove inner <select>'
|
||||
data-expect='<button><div></div></button>'>
|
||||
<button>
|
||||
<div>
|
||||
<select id=expectafter2>
|
||||
</select>
|
||||
|
||||
<select class=test
|
||||
data-description='Divs and imgs should be allowed as direct children of select and within options without a datalist'
|
||||
data-expect='
|
||||
<div>
|
||||
<option><img>option</option>
|
||||
</div>'>
|
||||
<div>
|
||||
<option><img>option</option>
|
||||
</div>
|
||||
</select>
|
||||
|
||||
<select class=test
|
||||
data-description='Input tags should parse inside select instead of closing the select'
|
||||
data-expect='<input>'>
|
||||
<input>
|
||||
</select>
|
||||
|
||||
<select class=test
|
||||
data-description='textarea tags should parse inside select instead of closing the select'
|
||||
data-expect='<textarea></textarea>'>
|
||||
<textarea></textarea>
|
||||
</select>
|
||||
|
||||
<div id=afterlast>
|
||||
keep this div after the last test case
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function removeWhitespace(t) {
|
||||
return t.replace(/\s/g,'');
|
||||
}
|
||||
document.querySelectorAll('select.test').forEach(s => {
|
||||
assert_true(!!s.dataset.description.length);
|
||||
test(() => {
|
||||
// The document.body check here and in the other tests is to make sure that a
|
||||
// previous test case didn't leave the HTML parser open on another element.
|
||||
assert_equals(s.parentNode, document.body);
|
||||
assert_equals(removeWhitespace(s.innerHTML),removeWhitespace(s.dataset.expect));
|
||||
},s.dataset.description)
|
||||
});
|
||||
|
||||
test(() => {
|
||||
assert_equals(document.getElementById('afterlast').parentNode, document.body);
|
||||
}, 'The last test should not leave any tags open after parsing');
|
||||
|
||||
test(() => {
|
||||
const outerSelect = document.getElementById('nested1');
|
||||
const innerSelect = document.getElementById('expectafter1');
|
||||
const nextDiv = document.getElementById('expectafter1b');
|
||||
assert_true(!!outerSelect);
|
||||
assert_equals(innerSelect, null,'Nested select should be removed');
|
||||
assert_equals(outerSelect.nextElementSibling, nextDiv,'Subsequent content is there too');
|
||||
}, 'Nested selects should be retained 1');
|
||||
|
||||
test(() => {
|
||||
const outerSelect = document.getElementById('nested2');
|
||||
const innerSelect = document.getElementById('expectafter2');
|
||||
assert_true(!!outerSelect);
|
||||
assert_equals(innerSelect, null,'Nested select should be pushed out as the next sibling');
|
||||
}, 'Nested selects should be retained 2');
|
||||
</script>
|
Loading…
Add table
Reference in a new issue