From 1c5f0e8daff6c18abda1b83ba6d2abc51d608f1d Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Tue, 25 Jun 2024 11:06:41 +0200 Subject: [PATCH] LibWeb: Update DOM cloning algorithm for declarative shadow DOM (cherry picked from commit c7d9c1c0b21ad54894984dd73c775e225f82af09) --- Userland/Libraries/LibWeb/DOM/Node.cpp | 33 +++++++++++++++---- Userland/Libraries/LibWeb/DOM/Node.h | 4 +-- Userland/Libraries/LibWeb/DOM/Range.cpp | 22 ++++++------- .../LibWeb/HTML/HTMLTemplateElement.cpp | 22 ++++++------- .../LibWeb/HTML/HTMLTemplateElement.h | 2 +- .../Libraries/LibWeb/SVG/SVGUseElement.cpp | 2 +- .../WebContent/ConnectionFromClient.cpp | 2 +- 7 files changed, 53 insertions(+), 34 deletions(-) diff --git a/Userland/Libraries/LibWeb/DOM/Node.cpp b/Userland/Libraries/LibWeb/DOM/Node.cpp index a44f7d9a39..fe5a37374d 100644 --- a/Userland/Libraries/LibWeb/DOM/Node.cpp +++ b/Userland/Libraries/LibWeb/DOM/Node.cpp @@ -817,7 +817,7 @@ WebIDL::ExceptionOr> Node::replace_child(JS::NonnullGCPtr } // https://dom.spec.whatwg.org/#concept-node-clone -JS::NonnullGCPtr Node::clone_node(Document* document, bool clone_children) +WebIDL::ExceptionOr> Node::clone_node(Document* document, bool clone_children) { // 1. If document is not given, let document be node’s node document. if (!document) @@ -898,18 +898,37 @@ JS::NonnullGCPtr Node::clone_node(Document* document, bool clone_children) // 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. - cloned(*copy, clone_children); + 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_each_child([&](auto& child) { - MUST(copy->append_child(child.clone_node(document, true))); - return IterationDecision::Continue; - }); + for (auto child = first_child(); child; child = child->next_sibling()) { + TRY(copy->append_child(TRY(child->clone_node(document, true)))); + } + } + + // 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()); + + // 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())); + + // 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()); + + // 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)))); + } } // 7. Return copy. - return *copy; + return JS::NonnullGCPtr { *copy }; } // https://dom.spec.whatwg.org/#dom-node-clonenode diff --git a/Userland/Libraries/LibWeb/DOM/Node.h b/Userland/Libraries/LibWeb/DOM/Node.h index f907951452..0b09dd3d70 100644 --- a/Userland/Libraries/LibWeb/DOM/Node.h +++ b/Userland/Libraries/LibWeb/DOM/Node.h @@ -140,7 +140,7 @@ public: WebIDL::ExceptionOr> replace_child(JS::NonnullGCPtr node, JS::NonnullGCPtr child); - JS::NonnullGCPtr clone_node(Document* document = nullptr, bool clone_children = false); + WebIDL::ExceptionOr> clone_node(Document* document = nullptr, bool clone_children = false); WebIDL::ExceptionOr> clone_node_binding(bool deep); // NOTE: This is intended for the JS bindings. @@ -198,7 +198,7 @@ public: virtual void removed_from(Node*); virtual void children_changed() { } virtual void adopted_from(Document&) { } - virtual void cloned(Node&, bool) {}; + virtual WebIDL::ExceptionOr cloned(Node&, bool) { return {}; } Layout::Node const* layout_node() const { return m_layout_node; } Layout::Node* layout_node() { return m_layout_node; } diff --git a/Userland/Libraries/LibWeb/DOM/Range.cpp b/Userland/Libraries/LibWeb/DOM/Range.cpp index 9b48ec688f..259d5a5dd8 100644 --- a/Userland/Libraries/LibWeb/DOM/Range.cpp +++ b/Userland/Libraries/LibWeb/DOM/Range.cpp @@ -632,7 +632,7 @@ WebIDL::ExceptionOr> Range::extract() // 4. If original start node is original end node and it is a CharacterData node, then: if (original_start_node.ptr() == original_end_node.ptr() && is(*original_start_node)) { // 1. Let clone be a clone of original start node. - auto clone = original_start_node->clone_node(); + auto clone = TRY(original_start_node->clone_node()); // 2. Set the data of clone to the result of substringing data with node original start node, // offset original start offset, and count original end offset minus original start offset. @@ -722,7 +722,7 @@ WebIDL::ExceptionOr> Range::extract() // 15. If first partially contained child is a CharacterData node, then: if (first_partially_contained_child && is(*first_partially_contained_child)) { // 1. Let clone be a clone of original start node. - auto clone = original_start_node->clone_node(); + auto clone = TRY(original_start_node->clone_node()); // 2. Set the data of clone to the result of substringing data with node original start node, offset original start offset, // and count original start node’s length minus original start offset. @@ -738,7 +738,7 @@ WebIDL::ExceptionOr> Range::extract() // 16. Otherwise, if first partially contained child is not null: else if (first_partially_contained_child) { // 1. Let clone be a clone of first partially contained child. - auto clone = first_partially_contained_child->clone_node(); + auto clone = TRY(first_partially_contained_child->clone_node()); // 2. Append clone to fragment. TRY(fragment->append_child(clone)); @@ -761,7 +761,7 @@ WebIDL::ExceptionOr> Range::extract() // 18. If last partially contained child is a CharacterData node, then: if (last_partially_contained_child && is(*last_partially_contained_child)) { // 1. Let clone be a clone of original end node. - auto clone = original_end_node->clone_node(); + auto clone = TRY(original_end_node->clone_node()); // 2. Set the data of clone to the result of substringing data with node original end node, offset 0, and count original end offset. auto result = TRY(static_cast(*original_end_node).substring_data(0, original_end_offset)); @@ -776,7 +776,7 @@ WebIDL::ExceptionOr> Range::extract() // 19. Otherwise, if last partially contained child is not null: else if (last_partially_contained_child) { // 1. Let clone be a clone of last partially contained child. - auto clone = last_partially_contained_child->clone_node(); + auto clone = TRY(last_partially_contained_child->clone_node()); // 2. Append clone to fragment. TRY(fragment->append_child(clone)); @@ -961,7 +961,7 @@ WebIDL::ExceptionOr> Range::clone_the_content // 4. If original start node is original end node and it is a CharacterData node, then: if (original_start_node.ptr() == original_end_node.ptr() && is(*original_start_node)) { // 1. Let clone be a clone of original start node. - auto clone = original_start_node->clone_node(); + auto clone = TRY(original_start_node->clone_node()); // 2. Set the data of clone to the result of substringing data with node original start node, // offset original start offset, and count original end offset minus original start offset. @@ -1026,7 +1026,7 @@ WebIDL::ExceptionOr> Range::clone_the_content // 13. If first partially contained child is a CharacterData node, then: if (first_partially_contained_child && is(*first_partially_contained_child)) { // 1. Let clone be a clone of original start node. - auto clone = original_start_node->clone_node(); + auto clone = TRY(original_start_node->clone_node()); // 2. Set the data of clone to the result of substringing data with node original start node, offset original start offset, // and count original start node’s length minus original start offset. @@ -1039,7 +1039,7 @@ WebIDL::ExceptionOr> Range::clone_the_content // 14. Otherwise, if first partially contained child is not null: else if (first_partially_contained_child) { // 1. Let clone be a clone of first partially contained child. - auto clone = first_partially_contained_child->clone_node(); + auto clone = TRY(first_partially_contained_child->clone_node()); // 2. Append clone to fragment. TRY(fragment->append_child(clone)); @@ -1057,7 +1057,7 @@ WebIDL::ExceptionOr> Range::clone_the_content // 15. For each contained child in contained children. for (auto& contained_child : contained_children) { // 1. Let clone be a clone of contained child with the clone children flag set. - auto clone = contained_child->clone_node(nullptr, true); + auto clone = TRY(contained_child->clone_node(nullptr, true)); // 2. Append clone to fragment. TRY(fragment->append_child(move(clone))); @@ -1066,7 +1066,7 @@ WebIDL::ExceptionOr> Range::clone_the_content // 16. If last partially contained child is a CharacterData node, then: if (last_partially_contained_child && is(*last_partially_contained_child)) { // 1. Let clone be a clone of original end node. - auto clone = original_end_node->clone_node(); + auto clone = TRY(original_end_node->clone_node()); // 2. Set the data of clone to the result of substringing data with node original end node, offset 0, and count original end offset. auto result = TRY(static_cast(*original_end_node).substring_data(0, original_end_offset)); @@ -1078,7 +1078,7 @@ WebIDL::ExceptionOr> Range::clone_the_content // 17. Otherwise, if last partially contained child is not null: else if (last_partially_contained_child) { // 1. Let clone be a clone of last partially contained child. - auto clone = last_partially_contained_child->clone_node(); + auto clone = TRY(last_partially_contained_child->clone_node()); // 2. Append clone to fragment. TRY(fragment->append_child(clone)); diff --git a/Userland/Libraries/LibWeb/HTML/HTMLTemplateElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLTemplateElement.cpp index eeb35798c3..f9666c80cb 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLTemplateElement.cpp +++ b/Userland/Libraries/LibWeb/HTML/HTMLTemplateElement.cpp @@ -46,21 +46,21 @@ void HTMLTemplateElement::adopted_from(DOM::Document&) } // https://html.spec.whatwg.org/multipage/scripting.html#the-template-element:concept-node-clone-ext -void HTMLTemplateElement::cloned(Node& copy, bool clone_children) +WebIDL::ExceptionOr HTMLTemplateElement::cloned(Node& copy, bool clone_children) { + // 1. If the clone children flag is not set in the calling clone algorithm, return. if (!clone_children) - return; + return {}; + // 2. Let copied contents be the result of cloning all the children of node's template contents, + // with document set to copy's template contents's node document, and with the clone children flag set. + // 3. Append copied contents to copy's template contents. auto& template_clone = verify_cast(copy); - - content()->for_each_child([&](auto& child) { - auto cloned_child = child.clone_node(&template_clone.content()->document(), true); - - // FIXME: Should this use TreeNode::append_child instead? - MUST(template_clone.content()->append_child(cloned_child)); - - return IterationDecision::Continue; - }); + for (auto child = content()->first_child(); child; child = child->next_sibling()) { + auto cloned_child = TRY(child->clone_node(&template_clone.content()->document(), true)); + TRY(template_clone.content()->append_child(cloned_child)); + } + return {}; } void HTMLTemplateElement::set_template_contents(JS::NonnullGCPtr contents) diff --git a/Userland/Libraries/LibWeb/HTML/HTMLTemplateElement.h b/Userland/Libraries/LibWeb/HTML/HTMLTemplateElement.h index c5ddb0f01c..39947a2d9c 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLTemplateElement.h +++ b/Userland/Libraries/LibWeb/HTML/HTMLTemplateElement.h @@ -24,7 +24,7 @@ public: void set_template_contents(JS::NonnullGCPtr); virtual void adopted_from(DOM::Document&) override; - virtual void cloned(Node& copy, bool clone_children) override; + virtual WebIDL::ExceptionOr cloned(Node& copy, bool clone_children) override; private: HTMLTemplateElement(DOM::Document&, DOM::QualifiedName); diff --git a/Userland/Libraries/LibWeb/SVG/SVGUseElement.cpp b/Userland/Libraries/LibWeb/SVG/SVGUseElement.cpp index 076786ae6a..dedccee460 100644 --- a/Userland/Libraries/LibWeb/SVG/SVGUseElement.cpp +++ b/Userland/Libraries/LibWeb/SVG/SVGUseElement.cpp @@ -130,7 +130,7 @@ void SVGUseElement::clone_element_tree_as_our_shadow_tree(Element* to_clone) con if (to_clone && is_valid_reference_element(to_clone)) { // The ‘use’ element references another element, a copy of which is rendered in place of the ‘use’ in the document. - auto cloned_reference_node = to_clone->clone_node(nullptr, true); + auto cloned_reference_node = MUST(to_clone->clone_node(nullptr, true)); shadow_root()->append_child(cloned_reference_node).release_value_but_fixme_should_propagate_errors(); } } diff --git a/Userland/Services/WebContent/ConnectionFromClient.cpp b/Userland/Services/WebContent/ConnectionFromClient.cpp index 6a9160e7ba..87000a6406 100644 --- a/Userland/Services/WebContent/ConnectionFromClient.cpp +++ b/Userland/Services/WebContent/ConnectionFromClient.cpp @@ -715,7 +715,7 @@ void ConnectionFromClient::clone_dom_node(u64 page_id, i32 node_id) return; } - auto dom_node_clone = dom_node->clone_node(nullptr, true); + auto dom_node_clone = MUST(dom_node->clone_node(nullptr, true)); dom_node->parent_node()->insert_before(dom_node_clone, dom_node->next_sibling()); async_did_finish_editing_dom_node(page_id, dom_node_clone->unique_id());