diff --git a/Libraries/LibWeb/Editing/Commands.cpp b/Libraries/LibWeb/Editing/Commands.cpp index 2b5346252e9..7c396febf80 100644 --- a/Libraries/LibWeb/Editing/Commands.cpp +++ b/Libraries/LibWeb/Editing/Commands.cpp @@ -62,7 +62,7 @@ bool command_delete_action(DOM::Document& document, String const&) canonicalize_whitespace(active_range.start_container(), active_range.start_offset()); // 3. Let node and offset be the active range's start node and offset. - auto node = active_range.start_container(); + GC::Ptr node = active_range.start_container(); int offset = active_range.start_offset(); // 4. Repeat the following steps: @@ -89,7 +89,7 @@ bool command_delete_action(DOM::Document& document, String const&) // 3. Otherwise, if offset is zero and node is an inline node, or if node is an invisible // node, set offset to the index of node, then set node to its parent. - if ((offset == 0 && is_inline_node(node)) || is_invisible_node(node)) { + if ((offset == 0 && is_inline_node(*node)) || is_invisible_node(*node)) { offset = node->index(); node = *node->parent(); continue; @@ -118,7 +118,7 @@ bool command_delete_action(DOM::Document& document, String const&) // 5. If node is a Text node and offset is not zero, or if node is a block node that has a child // with index offset − 1 and that child is a br or hr or img: bool block_node_child_is_relevant_type = false; - if (is_block_node(node)) { + if (is_block_node(*node)) { if (auto* child_node = node->child_at_index(offset - 1)) { auto& child_element = static_cast(*child_node); block_node_child_is_relevant_type = child_element.local_name().is_one_of(HTML::TagNames::br, HTML::TagNames::hr, HTML::TagNames::img); @@ -139,7 +139,7 @@ bool command_delete_action(DOM::Document& document, String const&) } // 6. If node is an inline node, return true. - if (is_inline_node(node)) + if (is_inline_node(*node)) return true; // 7. If node is an li or dt or dd and is the first child of its parent, and offset is zero: @@ -162,19 +162,33 @@ bool command_delete_action(DOM::Document& document, String const&) // 3. Record the values of the one-node list consisting of node, and let values be the // result. - auto values = record_the_values_of_nodes({ node }); + auto values = record_the_values_of_nodes({ *node }); // 4. Split the parent of the one-node list consisting of node. - split_the_parent_of_nodes({ node }); + split_the_parent_of_nodes({ *node }); // 5. Restore the values from values. restore_the_values_of_nodes(values); - // FIXME: 6. If node is a dd or dt, and it is not an allowed child of any of its ancestors in the + // 6. If node is a dd or dt, and it is not an allowed child of any of its ancestors in the // same editing host, set the tag name of node to the default single-line container name // and let node be the result. + if (node_element.local_name().is_one_of(HTML::TagNames::dd, HTML::TagNames::dt)) { + ancestor = node->parent(); + bool allowed_child_of_any_ancestor = false; + do { + if (is_in_same_editing_host(*node, *ancestor) && is_allowed_child_of_node(GC::Ref { *node }, GC::Ref { *ancestor })) { + allowed_child_of_any_ancestor = true; + break; + } + ancestor = ancestor->parent(); + } while (ancestor); + if (!allowed_child_of_any_ancestor) + node = set_the_tag_name(node_element, document.default_single_line_container_name()); + } - // FIXME: 7. Fix disallowed ancestors of node. + // 7. Fix disallowed ancestors of node. + fix_disallowed_ancestors_of_node(node); // 8. Return true. return true; diff --git a/Libraries/LibWeb/Editing/Internal/Algorithms.cpp b/Libraries/LibWeb/Editing/Internal/Algorithms.cpp index 7905d0bb620..ec3431ef7e5 100644 --- a/Libraries/LibWeb/Editing/Internal/Algorithms.cpp +++ b/Libraries/LibWeb/Editing/Internal/Algorithms.cpp @@ -379,6 +379,75 @@ GC::Ptr editing_host_of_node(GC::Ref node) return {}; } +// https://w3c.github.io/editing/docs/execCommand/#fix-disallowed-ancestors +void fix_disallowed_ancestors_of_node(GC::Ptr node) +{ + // 1. If node is not editable, abort these steps. + if (!node->is_editable()) + return; + + // 2. If node is not an allowed child of any of its ancestors in the same editing host: + bool allowed_child_of_any_ancestor = false; + GC::Ptr ancestor = node->parent(); + do { + if (is_in_same_editing_host(*ancestor, *node) && is_allowed_child_of_node(GC::Ref { *node }, GC::Ref { *ancestor })) { + allowed_child_of_any_ancestor = true; + break; + } + ancestor = ancestor->parent(); + } while (ancestor); + if (!allowed_child_of_any_ancestor) { + // FIXME: 1. If node is a dd or dt, wrap the one-node list consisting of node, with sibling criteria returning true for + // any dl with no attributes and false otherwise, and new parent instructions returning the result of calling + // createElement("dl") on the context object. Then abort these steps. + + // 2. If "p" is not an allowed child of the editing host of node, abort these steps. + if (!is_allowed_child_of_node(HTML::TagNames::p, GC::Ref { *editing_host_of_node(*node) })) + return; + + // 3. If node is not a prohibited paragraph child, abort these steps. + if (!is_prohibited_paragraph_child(*node)) + return; + + // 4. Set the tag name of node to the default single-line container name, and let node be the result. + node = set_the_tag_name(static_cast(*node), node->document().default_single_line_container_name()); + + // 5. Fix disallowed ancestors of node. + fix_disallowed_ancestors_of_node(node); + + // 6. Let children be node's children. + // 7. For each child in children, if child is a prohibited paragraph child: + node->for_each_child([](DOM::Node& child) { + if (!is_prohibited_paragraph_child(child)) + return IterationDecision::Continue; + + // 1. Record the values of the one-node list consisting of child, and let values be the result. + auto values = record_the_values_of_nodes({ child }); + + // 2. Split the parent of the one-node list consisting of child. + split_the_parent_of_nodes({ child }); + + // 3. Restore the values from values. + restore_the_values_of_nodes(values); + + return IterationDecision::Continue; + }); + + // 8. Abort these steps. + return; + } + + // 3. Record the values of the one-node list consisting of node, and let values be the result. + auto values = record_the_values_of_nodes({ *node }); + + // 4. While node is not an allowed child of its parent, split the parent of the one-node list consisting of node. + while (!is_allowed_child_of_node(GC::Ref { *node }, GC::Ref { *node->parent() })) + split_the_parent_of_nodes({ *node }); + + // 5. Restore the values from values. + restore_the_values_of_nodes(values); +} + // https://w3c.github.io/editing/docs/execCommand/#follows-a-line-break bool follows_a_line_break(GC::Ref node) { @@ -863,6 +932,13 @@ bool is_name_of_an_element_with_inline_contents(FlyString const& local_name) HTML::TagNames::tt); } +// https://w3c.github.io/editing/docs/execCommand/#prohibited-paragraph-child +bool is_prohibited_paragraph_child(GC::Ref node) +{ + // A prohibited paragraph child is an HTML element whose local name is a prohibited paragraph child name. + return is(*node) && is_prohibited_paragraph_child_name(static_cast(*node).local_name()); +} + // https://w3c.github.io/editing/docs/execCommand/#prohibited-paragraph-child-name bool is_prohibited_paragraph_child_name(FlyString const& local_name) { diff --git a/Libraries/LibWeb/Editing/Internal/Algorithms.h b/Libraries/LibWeb/Editing/Internal/Algorithms.h index b7ea4a61b6f..8b67bfcd6e3 100644 --- a/Libraries/LibWeb/Editing/Internal/Algorithms.h +++ b/Libraries/LibWeb/Editing/Internal/Algorithms.h @@ -25,6 +25,7 @@ String canonical_space_sequence(u32 length, bool non_breaking_start, bool non_br void canonicalize_whitespace(GC::Ref, u32 offset, bool fix_collapsed_space = true); void delete_the_selection(Selection::Selection const&); GC::Ptr editing_host_of_node(GC::Ref); +void fix_disallowed_ancestors_of_node(GC::Ptr); bool follows_a_line_break(GC::Ref); bool is_allowed_child_of_node(Variant, FlyString> child, Variant, FlyString> parent); bool is_block_boundary_point(GC::Ref, u32 offset); @@ -39,6 +40,7 @@ bool is_in_same_editing_host(GC::Ref, GC::Ref); bool is_inline_node(GC::Ref); bool is_invisible_node(GC::Ref); bool is_name_of_an_element_with_inline_contents(FlyString const&); +bool is_prohibited_paragraph_child(GC::Ref); bool is_prohibited_paragraph_child_name(FlyString const&); bool is_visible_node(GC::Ref); bool is_whitespace_node(GC::Ref);