From c60ad5b0b89d806448d4dffc5b01054d020490f7 Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Fri, 3 Jan 2025 15:24:30 +0000 Subject: [PATCH] LibWeb/DOM: Update node cloning to latest spec Main difference is that a chunk of the "clone a node" steps are pulled out into a "clone a single node" algorithm. Reflects these spec PRs: https://github.com/whatwg/dom/pull/1332 https://github.com/whatwg/dom/pull/1334 Though this code is quite old so there may also be older spec changes included here. --- Libraries/LibWeb/DOM/Node.cpp | 270 ++++++++++++++++++++-------------- Libraries/LibWeb/DOM/Node.h | 7 +- 2 files changed, 162 insertions(+), 115 deletions(-) diff --git a/Libraries/LibWeb/DOM/Node.cpp b/Libraries/LibWeb/DOM/Node.cpp index 2e7ffd9d8db1..c794c5103253 100644 --- a/Libraries/LibWeb/DOM/Node.cpp +++ b/Libraries/LibWeb/DOM/Node.cpp @@ -1026,150 +1026,194 @@ WebIDL::ExceptionOr> Node::replace_child(GC::Ref node, GC::R } // https://dom.spec.whatwg.org/#concept-node-clone -WebIDL::ExceptionOr> Node::clone_node(Document* document, bool clone_children) +WebIDL::ExceptionOr> Node::clone_node(Document* document, bool subtree, Node* parent) { - // 1. If document is not given, let document be node’s node document. + // To clone a node given a node node and an optional document document (default node’s node document), + // boolean subtree (default false), and node-or-null parent (default null): if (!document) - document = m_document.ptr(); - GC::Ptr copy; + document = m_document; - // 2. If node is an element, then: - if (is(this)) { - // 1. Let copy be the result of creating an element, given document, node’s local name, node’s namespace, node’s namespace prefix, and node’s is value, with the synchronous custom elements flag unset. - auto& element = *verify_cast(this); - auto element_copy = DOM::create_element(*document, element.local_name(), element.namespace_uri(), element.prefix(), element.is_value(), false).release_value_but_fixme_should_propagate_errors(); + // 1. Assert: node is not a document or node is document. + VERIFY(!is_document() || this == document); - // 2. For each attribute in node’s attribute list: - element.for_each_attribute([&](auto& name, auto& value) { - // 1. Let copyAttribute be a clone of attribute. - // 2. Append copyAttribute to copy. - element_copy->append_attribute(name, value); - }); - copy = move(element_copy); + // 2. Let copy be the result of cloning a single node given node and document. + auto copy = TRY(clone_single_node(*document)); - } - // 3. Otherwise, let copy be a node that implements the same interfaces as node, and fulfills these additional requirements, switching on the interface node implements: - else if (is(this)) { - // Document - auto document_ = verify_cast(this); - auto document_copy = [&] -> GC::Ref { - switch (document_->document_type()) { - case Document::Type::XML: - return XMLDocument::create(realm(), document_->url()); - case Document::Type::HTML: - return HTML::HTMLDocument::create(realm(), document_->url()); - default: - return Document::create(realm(), document_->url()); - } - }(); - - // Set copy’s encoding, content type, URL, origin, type, and mode to those of node. - document_copy->set_encoding(document_->encoding()); - document_copy->set_content_type(document_->content_type()); - document_copy->set_url(document_->url()); - document_copy->set_origin(document_->origin()); - document_copy->set_document_type(document_->document_type()); - document_copy->set_quirks_mode(document_->mode()); - copy = move(document_copy); - } else if (is(this)) { - // DocumentType - auto document_type = verify_cast(this); - auto document_type_copy = realm().create(*document); - - // Set copy’s name, public ID, and system ID to those of node. - document_type_copy->set_name(document_type->name()); - document_type_copy->set_public_id(document_type->public_id()); - document_type_copy->set_system_id(document_type->system_id()); - copy = move(document_type_copy); - } else if (is(this)) { - // Attr - // Set copy’s namespace, namespace prefix, local name, and value to those of node. - auto& attr = static_cast(*this); - copy = attr.clone(*document); - } else if (is(this)) { - // Text - auto& text = static_cast(*this); - - // Set copy’s data to that of node. - copy = [&]() -> GC::Ref { - switch (type()) { - case NodeType::TEXT_NODE: - return realm().create(*document, text.data()); - case NodeType::CDATA_SECTION_NODE: - return realm().create(*document, text.data()); - default: - VERIFY_NOT_REACHED(); - } - }(); - } else if (is(this)) { - // Comment - auto comment = verify_cast(this); + // 3. Run any cloning steps defined for node in other applicable specifications and pass node, copy, and subtree as parameters. + TRY(cloned(*copy, subtree)); - // Set copy’s data to that of node. - auto comment_copy = realm().create(*document, comment->data()); - copy = move(comment_copy); - } else if (is(this)) { - // ProcessingInstruction - auto processing_instruction = verify_cast(this); + // 4. If parent is non-null, then append copy to parent. + if (parent) + TRY(parent->append_child(copy)); - // Set copy’s target and data to those of node. - auto processing_instruction_copy = realm().create(*document, processing_instruction->data(), processing_instruction->target()); - copy = processing_instruction_copy; - } - // Otherwise, Do nothing. - else if (is(this)) { - copy = realm().create(*document); + // 5. If subtree is true, then for each child of node’s children, in tree order: + // clone a node given child with document set to document, subtree set to subtree, and parent set to copy. + if (subtree) { + for (auto child = first_child(); child; child = child->next_sibling()) { + TRY(child->clone_node(document, subtree, copy)); + } } - // FIXME: 4. Set copy’s node document and document to copy, if copy is a document, and set copy’s node document to document otherwise. - - // 5. Run any cloning steps defined for node in other applicable specifications and pass copy, node, document and the clone children flag if set, as parameters. - TRY(cloned(*copy, clone_children)); - - // 6. If the clone children flag is set, clone all the children of node and append them to copy, with document as specified and the clone children flag being set. - if (clone_children) { - for (auto child = first_child(); child; child = child->next_sibling()) { - TRY(copy->append_child(TRY(child->clone_node(document, true)))); + // 6. If node is an element, node is a shadow host, and node’s shadow root’s clonable is true: + if (is_element()) { + auto& node_element = verify_cast(*this); + if (node_element.is_shadow_host() && node_element.shadow_root()->clonable()) { + // 1. Assert: copy is not a shadow host. + auto& copy_element = verify_cast(*copy); + VERIFY(!copy_element.is_shadow_host()); + + // 2. Attach a shadow root with copy, node’s shadow root’s mode, true, node’s shadow root’s serializable, node’s shadow root’s delegates focus, and node’s shadow root’s slot assignment. + TRY(copy_element.attach_a_shadow_root(node_element.shadow_root()->mode(), true, node_element.shadow_root()->serializable(), node_element.shadow_root()->delegates_focus(), node_element.shadow_root()->slot_assignment())); + + // 3. Set copy’s shadow root’s declarative to node’s shadow root’s declarative. + copy_element.shadow_root()->set_declarative(node_element.shadow_root()->declarative()); + + // 4. For each child of node’s shadow root’s children, in tree order: + // clone a node given child with document set to document, subtree set to subtree, and parent set to copy’s shadow root. + for (auto child = node_element.shadow_root()->first_child(); child; child = child->next_sibling()) { + TRY(child->clone_node(document, subtree, copy_element.shadow_root())); + } } } - // 7. If node is a shadow host whose shadow root’s clonable is true: - if (is_element() && static_cast(*this).is_shadow_host() && static_cast(*this).shadow_root()->clonable()) { - // 1. Assert: copy is not a shadow host. - VERIFY(!copy->is_element() || !static_cast(*copy).is_shadow_host()); + // 7. Return copy. + return GC::Ref { *copy }; +} + +// https://dom.spec.whatwg.org/#clone-a-single-node +WebIDL::ExceptionOr> Node::clone_single_node(Document& document) +{ + // To clone a single node given a node node and document document: - // 2. Run attach a shadow root with copy, node’s shadow root’s mode, true, node’s shadow root’s serializable, - // node’s shadow root’s delegates focus, and node’s shadow root’s slot assignment. - auto& node_shadow_root = *static_cast(*this).shadow_root(); - TRY(static_cast(*copy).attach_a_shadow_root(node_shadow_root.mode(), true, node_shadow_root.serializable(), node_shadow_root.delegates_focus(), node_shadow_root.slot_assignment())); + // 1. Let copy be null. + GC::Ptr copy = nullptr; - // 3. Set copy’s shadow root’s declarative to node’s shadow root’s declarative. - static_cast(*copy).shadow_root()->set_declarative(node_shadow_root.declarative()); + // 2. If node is an element: + if (is_element()) { + // 1. Set copy to the result of creating an element, given document, node’s local name, node’s namespace, node’s namespace prefix, and node’s is value. + auto& element = *verify_cast(this); + auto element_copy = TRY(DOM::create_element(document, element.local_name(), element.namespace_uri(), element.prefix(), element.is_value())); - // 4. For each child child of node’s shadow root, in tree order: - // append the result of cloning child with document and the clone children flag set, to copy’s shadow root. - for (auto child = node_shadow_root.first_child(); child; child = child->next_sibling()) { - TRY(static_cast(*copy).shadow_root()->append_child(TRY(child->clone_node(document, true)))); + // 2. For each attribute of node’s attribute list: + element.for_each_attribute([&](auto& name, auto& value) { + // FIXME: 1. Let copyAttribute be the result of cloning a single node given attribute and document. + // 2. Append copyAttribute to copy. + element_copy->append_attribute(name, value); + }); + copy = move(element_copy); + } + + // 3. Otherwise, set copy to a node that implements the same interfaces as node, and fulfills these additional requirements, switching on the interface node implements: + else { + if (is_document()) { + // -> Document + auto& document_ = verify_cast(*this); + auto document_copy = [&] -> GC::Ref { + switch (document_.document_type()) { + case Document::Type::XML: + return XMLDocument::create(realm(), document_.url()); + case Document::Type::HTML: + return HTML::HTMLDocument::create(realm(), document_.url()); + default: + return Document::create(realm(), document_.url()); + } + }(); + + // Set copy’s encoding, content type, URL, origin, type, and mode to those of node. + document_copy->set_encoding(document_.encoding()); + document_copy->set_content_type(document_.content_type()); + document_copy->set_url(document_.url()); + document_copy->set_origin(document_.origin()); + document_copy->set_document_type(document_.document_type()); + document_copy->set_quirks_mode(document_.mode()); + copy = move(document_copy); + } else if (is_document_type()) { + // -> DocumentType + auto& document_type = verify_cast(*this); + auto document_type_copy = realm().create(document); + + // Set copy’s name, public ID, and system ID to those of node. + document_type_copy->set_name(document_type.name()); + document_type_copy->set_public_id(document_type.public_id()); + document_type_copy->set_system_id(document_type.system_id()); + copy = move(document_type_copy); + } else if (is_attribute()) { + // -> Attr + // Set copy’s namespace, namespace prefix, local name, and value to those of node. + auto& attr = verify_cast(*this); + copy = attr.clone(document); + } else if (is_text()) { + // -> Text + auto& text = verify_cast(*this); + + // Set copy’s data to that of node. + copy = [&]() -> GC::Ref { + switch (type()) { + case NodeType::TEXT_NODE: + return realm().create(document, text.data()); + case NodeType::CDATA_SECTION_NODE: + return realm().create(document, text.data()); + default: + VERIFY_NOT_REACHED(); + } + }(); + } else if (is_comment()) { + // -> Comment + auto& comment = verify_cast(*this); + + // Set copy’s data to that of node. + auto comment_copy = realm().create(document, comment.data()); + copy = move(comment_copy); + } else if (is(this)) { + // -> ProcessingInstruction + auto& processing_instruction = verify_cast(*this); + + // Set copy’s target and data to those of node. + auto processing_instruction_copy = realm().create(document, processing_instruction.data(), processing_instruction.target()); + copy = move(processing_instruction_copy); + } + // -> Otherwise + // Do nothing. + else if (is(this)) { + copy = realm().create(document); + } else { + dbgln("Missing code for cloning a '{}' node. Please add it to Node::clone_single_node()", class_name()); + VERIFY_NOT_REACHED(); } } - // 7. Return copy. + // 4. Assert: copy is a node. VERIFY(copy); + + // 5. If node is a document, then set document to copy. + Document& document_to_use = is_document() + ? static_cast(*copy) + : document; + + // 6. Set copy’s node document to document. + copy->set_document(document_to_use); + + // 7. Return copy. return GC::Ref { *copy }; } // https://dom.spec.whatwg.org/#dom-node-clonenode -WebIDL::ExceptionOr> Node::clone_node_binding(bool deep) +WebIDL::ExceptionOr> Node::clone_node_binding(bool subtree) { // 1. If this is a shadow root, then throw a "NotSupportedError" DOMException. if (is(*this)) return WebIDL::NotSupportedError::create(realm(), "Cannot clone shadow root"_string); - // 2. Return a clone of this, with the clone children flag set if deep is true. - return clone_node(nullptr, deep); + // 2. Return the result of cloning a node given this with subtree set to subtree. + return clone_node(nullptr, subtree); } void Node::set_document(Badge, Document& document) +{ + set_document(document); +} + +void Node::set_document(Document& document) { if (m_document.ptr() == &document) return; diff --git a/Libraries/LibWeb/DOM/Node.h b/Libraries/LibWeb/DOM/Node.h index dc62b35a9d45..5b97aafe0bc9 100644 --- a/Libraries/LibWeb/DOM/Node.h +++ b/Libraries/LibWeb/DOM/Node.h @@ -192,8 +192,9 @@ class Node : public EventTarget { WebIDL::ExceptionOr> replace_child(GC::Ref node, GC::Ref child); - WebIDL::ExceptionOr> clone_node(Document* document = nullptr, bool clone_children = false); - WebIDL::ExceptionOr> clone_node_binding(bool deep); + WebIDL::ExceptionOr> clone_node(Document* document = nullptr, bool subtree = false, Node* parent = nullptr); + WebIDL::ExceptionOr> clone_single_node(Document&); + WebIDL::ExceptionOr> clone_node_binding(bool subtree); // NOTE: This is intended for the JS bindings. bool has_child_nodes() const { return has_children(); } @@ -742,6 +743,8 @@ class Node : public EventTarget { Node(JS::Realm&, Document&, NodeType); Node(Document&, NodeType); + void set_document(Document&); + virtual void visit_edges(Cell::Visitor&) override; virtual void finalize() override;