summaryrefslogtreecommitdiff
path: root/Userland/Libraries
diff options
context:
space:
mode:
Diffstat (limited to 'Userland/Libraries')
-rw-r--r--Userland/Libraries/LibWeb/DOM/Document.cpp57
-rw-r--r--Userland/Libraries/LibWeb/DOM/Document.h1
-rw-r--r--Userland/Libraries/LibWeb/DOM/Document.idl2
-rw-r--r--Userland/Libraries/LibWeb/DOM/Node.cpp226
-rw-r--r--Userland/Libraries/LibWeb/DOM/Node.h17
-rw-r--r--Userland/Libraries/LibWeb/DOM/Node.idl4
-rw-r--r--Userland/Libraries/LibWeb/Page/EditEventHandler.cpp8
-rw-r--r--Userland/Libraries/LibWeb/TreeNode.h37
8 files changed, 301 insertions, 51 deletions
diff --git a/Userland/Libraries/LibWeb/DOM/Document.cpp b/Userland/Libraries/LibWeb/DOM/Document.cpp
index 3aa3100305..ebca6e9bbd 100644
--- a/Userland/Libraries/LibWeb/DOM/Document.cpp
+++ b/Userland/Libraries/LibWeb/DOM/Document.cpp
@@ -43,6 +43,7 @@
#include <LibWeb/DOM/Event.h>
#include <LibWeb/DOM/ExceptionOr.h>
#include <LibWeb/DOM/Range.h>
+#include <LibWeb/DOM/ShadowRoot.h>
#include <LibWeb/DOM/Text.h>
#include <LibWeb/DOM/Window.h>
#include <LibWeb/Dump.h>
@@ -124,7 +125,7 @@ void Document::removed_last_ref()
VERIFY(&node.document() == this);
VERIFY(!node.is_document());
if (node.parent())
- node.parent()->remove_child(node);
+ node.remove();
}
}
@@ -282,9 +283,7 @@ void Document::set_title(const String& title)
head_element->append_child(*title_element);
}
- while (RefPtr<Node> child = title_element->first_child())
- title_element->remove_child(child.release_nonnull());
-
+ title_element->remove_all_children(true);
title_element->append_child(adopt(*new Text(*this, title)));
if (auto* page = this->page()) {
@@ -659,12 +658,52 @@ NonnullRefPtrVector<HTML::HTMLScriptElement> Document::take_scripts_to_execute_a
return move(m_scripts_to_execute_as_soon_as_possible);
}
-void Document::adopt_node(Node& subtree_root)
+// https://dom.spec.whatwg.org/#concept-node-adopt
+void Document::adopt_node(Node& node)
{
- subtree_root.for_each_in_inclusive_subtree([&](auto& node) {
- node.set_document({}, *this);
- return IterationDecision::Continue;
- });
+ auto& old_document = node.document();
+ if (node.parent())
+ node.remove();
+
+ if (&old_document != this) {
+ // FIXME: This should be shadow-including.
+ node.for_each_in_inclusive_subtree([&](auto& inclusive_descendant) {
+ inclusive_descendant.set_document({}, *this);
+ // FIXME: If inclusiveDescendant is an element, then set the node document of each attribute in inclusiveDescendant’s attribute list to document.
+ return IterationDecision::Continue;
+ });
+
+ // FIXME: For each inclusiveDescendant in node’s shadow-including inclusive descendants that is custom,
+ // enqueue a custom element callback reaction with inclusiveDescendant, callback name "adoptedCallback",
+ // and an argument list containing oldDocument and document.
+
+ // FIXME: This should be shadow-including.
+ node.for_each_in_inclusive_subtree([&](auto& inclusive_descendant) {
+ inclusive_descendant.adopted_from(old_document);
+ return IterationDecision::Continue;
+ });
+ }
+}
+
+// https://dom.spec.whatwg.org/#dom-document-adoptnode
+NonnullRefPtr<Node> Document::adopt_node_binding(NonnullRefPtr<Node> node)
+{
+ if (is<Document>(*node)) {
+ dbgln("Document::adopt_node_binding: Cannot adopt a document into a document (FIXME: throw as NotSupportedError exception, see issue #6075");
+ return node;
+ }
+
+ if (is<ShadowRoot>(*node)) {
+ dbgln("Document::adopt_node_binding: Cannot adopt a shadow root into a document (FIXME: throw as HierarchyRequestError exception, see issue #6075");
+ return node;
+ }
+
+ if (is<DocumentFragment>(*node) && downcast<DocumentFragment>(*node).host())
+ return node;
+
+ adopt_node(*node);
+
+ return node;
}
const DocumentType* Document::doctype() const
diff --git a/Userland/Libraries/LibWeb/DOM/Document.h b/Userland/Libraries/LibWeb/DOM/Document.h
index c42b43f8ef..a6f87d1cbc 100644
--- a/Userland/Libraries/LibWeb/DOM/Document.h
+++ b/Userland/Libraries/LibWeb/DOM/Document.h
@@ -188,6 +188,7 @@ public:
void set_quirks_mode(QuirksMode mode) { m_quirks_mode = mode; }
void adopt_node(Node&);
+ NonnullRefPtr<Node> adopt_node_binding(NonnullRefPtr<Node>);
const DocumentType* doctype() const;
const String& compat_mode() const;
diff --git a/Userland/Libraries/LibWeb/DOM/Document.idl b/Userland/Libraries/LibWeb/DOM/Document.idl
index 2886bdcd3f..aacdae09f2 100644
--- a/Userland/Libraries/LibWeb/DOM/Document.idl
+++ b/Userland/Libraries/LibWeb/DOM/Document.idl
@@ -31,6 +31,8 @@ interface Document : Node {
Comment createComment(DOMString data);
Range createRange();
+ [CEReactions, ImplementedAs=adopt_node_binding] Node adoptNode(Node node);
+
[ImplementedAs=style_sheets_for_bindings] readonly attribute StyleSheetList styleSheets;
readonly attribute DOMString compatMode;
diff --git a/Userland/Libraries/LibWeb/DOM/Node.cpp b/Userland/Libraries/LibWeb/DOM/Node.cpp
index 61ebb37232..c2792df94a 100644
--- a/Userland/Libraries/LibWeb/DOM/Node.cpp
+++ b/Userland/Libraries/LibWeb/DOM/Node.cpp
@@ -30,11 +30,14 @@
#include <LibWeb/Bindings/EventWrapper.h>
#include <LibWeb/Bindings/NodeWrapper.h>
#include <LibWeb/Bindings/NodeWrapperFactory.h>
+#include <LibWeb/DOM/Comment.h>
+#include <LibWeb/DOM/DocumentType.h>
#include <LibWeb/DOM/Element.h>
#include <LibWeb/DOM/Event.h>
#include <LibWeb/DOM/EventDispatcher.h>
#include <LibWeb/DOM/EventListener.h>
#include <LibWeb/DOM/Node.h>
+#include <LibWeb/DOM/ProcessingInstruction.h>
#include <LibWeb/DOM/ShadowRoot.h>
#include <LibWeb/HTML/HTMLAnchorElement.h>
#include <LibWeb/Layout/InitialContainingBlockBox.h>
@@ -179,38 +182,191 @@ const Element* Node::parent_element() const
return downcast<Element>(parent());
}
-RefPtr<Node> Node::append_child(NonnullRefPtr<Node> node, bool notify)
+// https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity
+ExceptionOr<void> Node::ensure_pre_insertion_validity(NonnullRefPtr<Node> node, RefPtr<Node> child) const
{
- if (node->parent())
- node->parent()->remove_child(node);
- if (&node->document() != &document())
- document().adopt_node(node);
- TreeNode<Node>::append_child(node, notify);
- return node;
+ if (!is<Document>(this) && !is<DocumentFragment>(this) && !is<Element>(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 && 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<DocumentFragment>(*node) && !is<DocumentType>(*node) && !is<Element>(*node) && !is<Text>(*node) && !is<Comment>(*node) && !is<ProcessingInstruction>(*node))
+ return DOM::HierarchyRequestError::create("Invalid node type for insertion");
+
+ if ((is<Text>(*node) && is<Document>(this)) || (is<DocumentType>(*node) && !is<Document>(this)))
+ return DOM::HierarchyRequestError::create("Invalid node type for insertion");
+
+ if (is<Document>(this)) {
+ if (is<DocumentFragment>(*node)) {
+ auto node_element_child_count = node->element_child_count();
+ if ((node_element_child_count > 1 || node->has_child_of_type<Text>())
+ || (node_element_child_count == 1 && (has_child_of_type<Element>() || is<DocumentType>(child.ptr()) /* FIXME: or child is non-null and a doctype is following child. */))) {
+ return DOM::HierarchyRequestError::create("Invalid node type for insertion");
+ }
+ } else if (is<Element>(*node)) {
+ if (has_child_of_type<Element>() || is<DocumentType>(child.ptr()) /* FIXME: or child is non-null and a doctype is following child. */)
+ return DOM::HierarchyRequestError::create("Invalid node type for insertion");
+ } else if (is<DocumentType>(*node)) {
+ if (has_child_of_type<DocumentType>() /* FIXME: or child is non-null and an element is preceding child */ || (!child && has_child_of_type<Element>()))
+ return DOM::HierarchyRequestError::create("Invalid node type for insertion");
+ }
+ }
+
+ return {};
}
-RefPtr<Node> Node::remove_child(NonnullRefPtr<Node> node)
+// https://dom.spec.whatwg.org/#concept-node-insert
+void Node::insert_before(NonnullRefPtr<Node> node, RefPtr<Node> child, bool suppress_observers)
{
- TreeNode<Node>::remove_child(node);
- return node;
+ NonnullRefPtrVector<Node> nodes;
+ if (is<DocumentFragment>(*node))
+ nodes = downcast<DocumentFragment>(*node).child_nodes();
+ else
+ nodes.append(node);
+
+ auto count = nodes.size();
+ if (count == 0)
+ return;
+
+ if (is<DocumentFragment>(*node)) {
+ node->remove_all_children(true);
+ // FIXME: Queue a tree mutation record for node with « », nodes, null, and null.
+ }
+
+ if (child) {
+ // FIXME: For each live range whose start node is parent and start offset is greater than child’s index, increase its start offset by count.
+ // FIXME: For each live range whose end node is parent and end offset is greater than child’s index, increase its end offset by count.
+ }
+
+ // FIXME: Let previousSibling be child’s previous sibling or parent’s last child if child is null. (Currently unused so not included)
+
+ for (auto& node_to_insert : nodes) { // FIXME: In tree order
+ document().adopt_node(node_to_insert);
+
+ if (!child)
+ TreeNode<Node>::append_child(node);
+ else
+ TreeNode<Node>::insert_before(node, child);
+
+ // FIXME: If parent is a shadow host and node is a slottable, then assign a slot for node.
+ // FIXME: If parent’s root is a shadow root, and parent is a slot whose assigned nodes is the empty list, then run signal a slot change for parent.
+ // FIXME: Run assign slottables for a tree with node’s root.
+
+ // FIXME: This should be shadow-including.
+ node_to_insert.for_each_in_inclusive_subtree([&](Node& inclusive_descendant) {
+ inclusive_descendant.inserted();
+ if (inclusive_descendant.is_connected()) {
+ // FIXME: If inclusiveDescendant is custom, then enqueue a custom element callback reaction with inclusiveDescendant,
+ // callback name "connectedCallback", and an empty argument list.
+
+ // FIXME: Otherwise, try to upgrade inclusiveDescendant.
+ }
+
+ return IterationDecision::Continue;
+ });
+ }
+
+ if (!suppress_observers) {
+ // FIXME: queue a tree mutation record for parent with nodes, « », previousSibling, and child.
+ }
+
+ children_changed();
}
-RefPtr<Node> Node::insert_before(NonnullRefPtr<Node> node, RefPtr<Node> child, bool notify)
+// https://dom.spec.whatwg.org/#concept-node-pre-insert
+NonnullRefPtr<Node> Node::pre_insert(NonnullRefPtr<Node> node, RefPtr<Node> child)
{
- if (!child)
- return append_child(move(node), notify);
- if (child->parent_node() != this) {
- dbgln("FIXME: Trying to insert_before() a bogus child");
- return nullptr;
+ auto validity_result = ensure_pre_insertion_validity(node, child);
+ if (validity_result.is_exception()) {
+ dbgln("Node::pre_insert: ensure_pre_insertion_validity failed: {}. (FIXME: throw as exception, see issue #6075)", validity_result.exception().message());
+ return node;
}
- if (node->parent())
- node->parent()->remove_child(node);
- if (&node->document() != &document())
- document().adopt_node(node);
- TreeNode<Node>::insert_before(node, child, notify);
+
+ auto reference_child = child;
+ if (reference_child == node)
+ reference_child = node->next_sibling();
+
+ insert_before(node, reference_child);
return node;
}
+// https://dom.spec.whatwg.org/#concept-node-pre-remove
+NonnullRefPtr<Node> Node::pre_remove(NonnullRefPtr<Node> child)
+{
+ if (child->parent() != this) {
+ dbgln("Node::pre_remove: Child doesn't belong to this node. (FIXME: throw NotFoundError DOMException, see issue #6075)");
+ return child;
+ }
+
+ child->remove();
+
+ return child;
+}
+
+// https://dom.spec.whatwg.org/#concept-node-append
+NonnullRefPtr<Node> Node::append_child(NonnullRefPtr<Node> node)
+{
+ return pre_insert(node, nullptr);
+}
+
+// https://dom.spec.whatwg.org/#concept-node-remove
+void Node::remove(bool suppress_observers)
+{
+ auto* parent = TreeNode<Node>::parent();
+ VERIFY(parent);
+
+ // FIXME: Let index be node’s index. (Currently unused so not included)
+
+ // FIXME: For each live range whose start node is an inclusive descendant of node, set its start to (parent, index).
+ // FIXME: For each live range whose end node is an inclusive descendant of node, set its end to (parent, index).
+ // FIXME: For each live range whose start node is parent and start offset is greater than index, decrease its start offset by 1.
+ // FIXME: For each live range whose end node is parent and end offset is greater than index, decrease its end offset by 1.
+
+ // FIXME: For each NodeIterator object iterator whose root’s node document is node’s node document, run the NodeIterator pre-removing steps given node and iterator.
+
+ // FIXME: Let oldPreviousSibling be node’s previous sibling. (Currently unused so not included)
+ // FIXME: Let oldNextSibling be node’s next sibling. (Currently unused so not included)
+
+ parent->remove_child(*this);
+
+ // FIXME: If node is assigned, then run assign slottables for node’s assigned slot.
+
+ // FIXME: If parent’s root is a shadow root, and parent is a slot whose assigned nodes is the empty list, then run signal a slot change for parent.
+
+ // FIXME: If node has an inclusive descendant that is a slot, then:
+ // Run assign slottables for a tree with parent’s root.
+ // Run assign slottables for a tree with node.
+
+ removed_from(parent);
+
+ // FIXME: Let isParentConnected be parent’s connected. (Currently unused so not included)
+
+ // FIXME: If node is custom and isParentConnected is true, then enqueue a custom element callback reaction with node,
+ // callback name "disconnectedCallback", and an empty argument list.
+
+ // FIXME: This should be shadow-including.
+ for_each_in_subtree([&](Node& descendant) {
+ descendant.removed_from(nullptr);
+
+ // FIXME: If descendant is custom and isParentConnected is true, then enqueue a custom element callback reaction with descendant,
+ // callback name "disconnectedCallback", and an empty argument list.
+
+ return IterationDecision::Continue;
+ });
+
+ if (!suppress_observers) {
+ // FIXME: queue a tree mutation record for parent with « », « node », oldPreviousSibling, and oldNextSibling.
+ }
+
+ parent->children_changed();
+}
+
void Node::set_document(Badge<Document>, Document& document)
{
if (m_document == &document)
@@ -261,13 +417,15 @@ void Node::set_needs_style_update(bool value)
m_needs_style_update = value;
if (m_needs_style_update) {
- for (auto* ancestor = parent(); ancestor; ancestor = ancestor->parent())
+ for (auto* ancestor = parent(); ancestor; ancestor = ancestor->parent()) {
+ //dbgln("{}", ancestor->node_name());
ancestor->m_child_needs_style_update = true;
+ }
document().schedule_style_update();
}
}
-void Node::inserted_into(Node&)
+void Node::inserted()
{
set_needs_style_update(true);
}
@@ -290,4 +448,26 @@ NonnullRefPtrVector<Node> Node::child_nodes() const
return nodes;
}
+void Node::remove_all_children(bool suppress_observers)
+{
+ while (RefPtr<Node> child = first_child())
+ child->remove(suppress_observers);
+}
+
+// https://dom.spec.whatwg.org/#concept-tree-host-including-inclusive-ancestor
+bool Node::is_host_including_inclusive_ancestor_of(const Node& other) const
+{
+ return is_inclusive_ancestor_of(other) || (is<DocumentFragment>(other.root()) && downcast<DocumentFragment>(other.root())->host() && is_inclusive_ancestor_of(*downcast<DocumentFragment>(other.root())->host().ptr()));
+}
+
+size_t Node::element_child_count() const
+{
+ size_t count = 0;
+ for (auto* child = first_child(); child; child = child->next_sibling()) {
+ if (is<Element>(child))
+ ++count;
+ }
+ return count;
+}
+
}
diff --git a/Userland/Libraries/LibWeb/DOM/Node.h b/Userland/Libraries/LibWeb/DOM/Node.h
index d1a71bc768..946279ae45 100644
--- a/Userland/Libraries/LibWeb/DOM/Node.h
+++ b/Userland/Libraries/LibWeb/DOM/Node.h
@@ -33,6 +33,7 @@
#include <AK/Vector.h>
#include <LibWeb/Bindings/Wrappable.h>
#include <LibWeb/DOM/EventTarget.h>
+#include <LibWeb/DOM/ExceptionOr.h>
#include <LibWeb/TreeNode.h>
namespace Web::DOM {
@@ -92,9 +93,13 @@ public:
virtual bool is_editable() const;
- RefPtr<Node> append_child(NonnullRefPtr<Node>, bool notify = true);
- RefPtr<Node> insert_before(NonnullRefPtr<Node> node, RefPtr<Node> child, bool notify = true);
- RefPtr<Node> remove_child(NonnullRefPtr<Node>);
+ NonnullRefPtr<Node> pre_insert(NonnullRefPtr<Node>, RefPtr<Node>);
+ NonnullRefPtr<Node> pre_remove(NonnullRefPtr<Node>);
+
+ NonnullRefPtr<Node> append_child(NonnullRefPtr<Node>);
+ void insert_before(NonnullRefPtr<Node> node, RefPtr<Node> child, bool suppress_observers = false);
+ void remove(bool suppress_observers = false);
+ void remove_all_children(bool suppress_observers = false);
// NOTE: This is intended for the JS bindings.
bool has_child_nodes() const { return has_children(); }
@@ -165,6 +170,12 @@ public:
template<typename T>
bool fast_is() const = delete;
+ ExceptionOr<void> ensure_pre_insertion_validity(NonnullRefPtr<Node> node, RefPtr<Node> child) const;
+
+ bool is_host_including_inclusive_ancestor_of(const Node&) const;
+
+ size_t element_child_count() const;
+
protected:
Node(Document&, NodeType);
diff --git a/Userland/Libraries/LibWeb/DOM/Node.idl b/Userland/Libraries/LibWeb/DOM/Node.idl
index c79b34f2bf..5667340c8c 100644
--- a/Userland/Libraries/LibWeb/DOM/Node.idl
+++ b/Userland/Libraries/LibWeb/DOM/Node.idl
@@ -15,8 +15,8 @@ interface Node : EventTarget {
attribute DOMString textContent;
Node appendChild(Node node);
- Node insertBefore(Node node, Node? child);
- Node removeChild(Node child);
+ [ImplementedAs=pre_insert] Node insertBefore(Node node, Node? child);
+ [ImplementedAs=pre_remove] Node removeChild(Node child);
const unsigned short ELEMENT_NODE = 1;
const unsigned short ATTRIBUTE_NODE = 2;
diff --git a/Userland/Libraries/LibWeb/Page/EditEventHandler.cpp b/Userland/Libraries/LibWeb/Page/EditEventHandler.cpp
index a91818e9b0..3e12e3dc18 100644
--- a/Userland/Libraries/LibWeb/Page/EditEventHandler.cpp
+++ b/Userland/Libraries/LibWeb/Page/EditEventHandler.cpp
@@ -62,14 +62,14 @@ void EditEventHandler::handle_delete(DOM::Range& range)
for (auto* parent = end->parent(); parent; parent = parent->parent())
queued_for_deletion.remove(parent);
for (auto* node : queued_for_deletion)
- node->parent()->remove_child(*node);
+ node->remove();
// Join the parent nodes of start and end.
DOM::Node *insert_after = start, *remove_from = end, *parent_of_end = end->parent();
while (remove_from) {
auto* next_sibling = remove_from->next_sibling();
- remove_from->parent()->remove_child(*remove_from);
+ remove_from->remove();
insert_after->parent()->insert_before(*remove_from, *insert_after);
insert_after = remove_from;
@@ -77,7 +77,7 @@ void EditEventHandler::handle_delete(DOM::Range& range)
}
if (!parent_of_end->has_children()) {
if (parent_of_end->parent())
- parent_of_end->parent()->remove_child(*parent_of_end);
+ parent_of_end->remove();
}
// Join the start and end nodes.
@@ -86,7 +86,7 @@ void EditEventHandler::handle_delete(DOM::Range& range)
builder.append(end->data().substring_view(range.end_offset()));
start->set_data(builder.to_string());
- start->parent()->remove_child(*end);
+ end->remove();
}
// FIXME: When nodes are removed from the DOM, the associated layout nodes become stale and still
diff --git a/Userland/Libraries/LibWeb/TreeNode.h b/Userland/Libraries/LibWeb/TreeNode.h
index 0de95a93ce..fa4853486b 100644
--- a/Userland/Libraries/LibWeb/TreeNode.h
+++ b/Userland/Libraries/LibWeb/TreeNode.h
@@ -98,14 +98,15 @@ public:
}
bool is_ancestor_of(const TreeNode&) const;
+ bool is_inclusive_ancestor_of(const TreeNode&) const;
+ bool is_descendant_of(const TreeNode&) const;
+ bool is_inclusive_descendant_of(const TreeNode&) const;
void append_child(NonnullRefPtr<T> node);
void prepend_child(NonnullRefPtr<T> node);
void insert_before(NonnullRefPtr<T> node, RefPtr<T> child);
void remove_child(NonnullRefPtr<T> node);
- void remove_all_children();
-
bool is_child_allowed(const T&) const { return true; }
T* next_in_pre_order()
@@ -324,6 +325,12 @@ public:
}
template<typename U>
+ bool has_child_of_type() const
+ {
+ return first_child_of_type<U>() != nullptr;
+ }
+
+ template<typename U>
const U* first_ancestor_of_type() const
{
return const_cast<TreeNode*>(this)->template first_ancestor_of_type<U>();
@@ -366,14 +373,6 @@ private:
};
template<typename T>
-inline void TreeNode<T>::remove_all_children()
-{
- while (RefPtr<T> child = first_child())
- remove_child(child.release_nonnull());
-}
-
-
-template<typename T>
inline void TreeNode<T>::remove_child(NonnullRefPtr<T> node)
{
VERIFY(node->m_parent == this);
@@ -470,4 +469,22 @@ inline bool TreeNode<T>::is_ancestor_of(const TreeNode<T>& other) const
return false;
}
+template<typename T>
+inline bool TreeNode<T>::is_inclusive_ancestor_of(const TreeNode<T>& other) const
+{
+ return &other == this || is_ancestor_of(other);
+}
+
+template<typename T>
+inline bool TreeNode<T>::is_descendant_of(const TreeNode<T>& other) const
+{
+ return other.is_ancestor_of(*this);
+}
+
+template<typename T>
+inline bool TreeNode<T>::is_inclusive_descendant_of(const TreeNode<T>& other) const
+{
+ return other.is_inclusive_ancestor_of(*this);
+}
+
}