diff --git a/Userland/Libraries/LibWeb/DOM/Node.cpp b/Userland/Libraries/LibWeb/DOM/Node.cpp index a7484bcb84e..e84bca283ec 100644 --- a/Userland/Libraries/LibWeb/DOM/Node.cpp +++ b/Userland/Libraries/LibWeb/DOM/Node.cpp @@ -1,6 +1,7 @@ /* * Copyright (c) 2018-2020, Andreas Kling * Copyright (c) 2021, Linus Groh + * Copyright (c) 2021, Luke Wilde * * SPDX-License-Identifier: BSD-2-Clause */ @@ -345,6 +346,64 @@ void Node::remove(bool suppress_observers) parent->children_changed(); } +// https://dom.spec.whatwg.org/#concept-node-replace +ExceptionOr> Node::replace_child(NonnullRefPtr node, NonnullRefPtr child) +{ + // NOTE: This differs slightly from ensure_pre_insertion_validity. + if (!is(this) && !is(this) && !is(this)) + return DOM::HierarchyRequestError::create("Can only insert into a document, document fragment or element"); + + if (node->is_host_including_inclusive_ancestor_of(*this)) + return DOM::HierarchyRequestError::create("New node is an ancestor of this node"); + + if (child->parent() != this) + return DOM::NotFoundError::create("This node is not the parent of the given child"); + + // FIXME: All the following "Invalid node type for insertion" messages could be more descriptive. + + if (!is(*node) && !is(*node) && !is(*node) && !is(*node) && !is(*node) && !is(*node)) + return DOM::HierarchyRequestError::create("Invalid node type for insertion"); + + if ((is(*node) && is(this)) || (is(*node) && !is(this))) + return DOM::HierarchyRequestError::create("Invalid node type for insertion"); + + if (is(this)) { + if (is(*node)) { + auto node_element_child_count = downcast(*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. */))) { + 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. */) + 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 */) + return DOM::HierarchyRequestError::create("Invalid node type for insertion"); + } + } + + auto reference_child = child->next_sibling(); + if (reference_child == node) + reference_child = node->next_sibling(); + + // FIXME: Let previousSibling be child’s previous sibling. (Currently unused so not included) + // FIXME: Let removedNodes be the empty set. (Currently unused so not included) + + if (child->parent()) { + // FIXME: Set removedNodes to « child ». + child->remove(true); + } + + // FIXME: Let nodes be node’s children if node is a DocumentFragment node; otherwise « node ». (Currently unused so not included) + + insert_before(node, reference_child, true); + + // FIXME: Queue a tree mutation record for parent with nodes, removedNodes, previousSibling, and referenceChild. + + return child; +} + // https://dom.spec.whatwg.org/#concept-node-clone NonnullRefPtr Node::clone_node(Document* document, bool clone_children) const { diff --git a/Userland/Libraries/LibWeb/DOM/Node.h b/Userland/Libraries/LibWeb/DOM/Node.h index bd0a59c28f5..4751a494f80 100644 --- a/Userland/Libraries/LibWeb/DOM/Node.h +++ b/Userland/Libraries/LibWeb/DOM/Node.h @@ -82,6 +82,8 @@ public: void remove_all_children(bool suppress_observers = false); u16 compare_document_position(RefPtr other); + ExceptionOr> replace_child(NonnullRefPtr node, NonnullRefPtr child); + NonnullRefPtr clone_node(Document* document = nullptr, bool clone_children = false) const; ExceptionOr> clone_node_binding(bool deep) const; diff --git a/Userland/Libraries/LibWeb/DOM/Node.idl b/Userland/Libraries/LibWeb/DOM/Node.idl index 0c0b38cb01e..00fb334369b 100644 --- a/Userland/Libraries/LibWeb/DOM/Node.idl +++ b/Userland/Libraries/LibWeb/DOM/Node.idl @@ -18,6 +18,7 @@ interface Node : EventTarget { Node appendChild(Node node); [ImplementedAs=pre_insert] Node insertBefore(Node node, Node? child); + Node replaceChild(Node node, Node child); [ImplementedAs=pre_remove] Node removeChild(Node child); [ImplementedAs=clone_node_binding] Node cloneNode(optional boolean deep = false);