diff --git a/Userland/Libraries/LibWeb/DOM/Node.cpp b/Userland/Libraries/LibWeb/DOM/Node.cpp index c78dc61ab5..201da2877d 100644 --- a/Userland/Libraries/LibWeb/DOM/Node.cpp +++ b/Userland/Libraries/LibWeb/DOM/Node.cpp @@ -242,14 +242,14 @@ ExceptionOr Node::ensure_pre_insertion_validity(NonnullRefPtr node, if (is(*node)) { auto node_element_child_count = verify_cast(*node).child_element_count(); if ((node_element_child_count > 1 || node->has_child_of_type()) - || (node_element_child_count == 1 && (has_child_of_type() || is(child.ptr()) /* FIXME: or child is non-null and a doctype is following child. */))) { + || (node_element_child_count == 1 && (has_child_of_type() || is(child.ptr()) || (child && child->has_following_node_of_type_in_tree_order())))) { return DOM::HierarchyRequestError::create("Invalid node type for insertion"); } } else if (is(*node)) { - if (has_child_of_type() || is(child.ptr()) /* FIXME: or child is non-null and a doctype is following child. */) + if (has_child_of_type() || is(child.ptr()) || (child && child->has_following_node_of_type_in_tree_order())) return DOM::HierarchyRequestError::create("Invalid node type for insertion"); } else if (is(*node)) { - if (has_child_of_type() /* FIXME: or child is non-null and an element is preceding child */ || (!child && has_child_of_type())) + if (has_child_of_type() || (child && child->has_preceding_node_of_type_in_tree_order()) || (!child && has_child_of_type())) return DOM::HierarchyRequestError::create("Invalid node type for insertion"); } } @@ -424,14 +424,14 @@ ExceptionOr> Node::replace_child(NonnullRefPtr node, N if (is(*node)) { auto node_element_child_count = verify_cast(*node).child_element_count(); if ((node_element_child_count > 1 || node->has_child_of_type()) - || (node_element_child_count == 1 && (first_child_of_type() != child /* FIXME: or a doctype is following child. */))) { + || (node_element_child_count == 1 && (first_child_of_type() != child || child->has_following_node_of_type_in_tree_order()))) { return DOM::HierarchyRequestError::create("Invalid node type for insertion"); } } else if (is(*node)) { - if (first_child_of_type() != child /* FIXME: or a doctype is following child. */) + if (first_child_of_type() != child || child->has_following_node_of_type_in_tree_order()) return DOM::HierarchyRequestError::create("Invalid node type for insertion"); } else if (is(*node)) { - if (first_child_of_type() != node /* FIXME: or an element is preceding child */) + if (first_child_of_type() != node || child->has_preceding_node_of_type_in_tree_order()) return DOM::HierarchyRequestError::create("Invalid node type for insertion"); } } diff --git a/Userland/Libraries/LibWeb/TreeNode.h b/Userland/Libraries/LibWeb/TreeNode.h index 7091ed3470..466e8ef517 100644 --- a/Userland/Libraries/LibWeb/TreeNode.h +++ b/Userland/Libraries/LibWeb/TreeNode.h @@ -137,12 +137,29 @@ public: return node; } - const T* next_in_pre_order() const + T const* next_in_pre_order() const { return const_cast(this)->next_in_pre_order(); } - bool is_before(const T& other) const + T* previous_in_pre_order() + { + if (auto* node = previous_sibling()) { + while (node->last_child()) + node = node->last_child(); + + return node; + } + + return parent(); + } + + T const* previous_in_pre_order() const + { + return const_cast(this)->previous_in_pre_order(); + } + + bool is_before(T const& other) const { if (this == &other) return false; @@ -153,6 +170,28 @@ public: return false; } + // https://dom.spec.whatwg.org/#concept-tree-preceding (Object A is 'typename U' and Object B is 'this') + template + bool has_preceding_node_of_type_in_tree_order() const + { + for (auto* node = previous_in_pre_order(); node; node = node->previous_in_pre_order()) { + if (is(node)) + return true; + } + return false; + } + + // https://dom.spec.whatwg.org/#concept-tree-following (Object A is 'typename U' and Object B is 'this') + template + bool has_following_node_of_type_in_tree_order() const + { + for (auto* node = next_in_pre_order(); node; node = node->next_in_pre_order()) { + if (is(node)) + return true; + } + return false; + } + template IterationDecision for_each_in_inclusive_subtree(Callback callback) const {