From 3ee5bdcfb7828755f052a3b018f8471fcf03c93b Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Tue, 12 Jul 2022 23:13:57 +0200 Subject: [PATCH] LibWeb: Traverse shadow-including subtree when adopting DOM nodes This takes care of two FIXMEs and fixes an issue on Google Docs where we'd mix boxes from different documents in the same layout tree. (This happened because shadow trees remained attached to their old document when their host was adopted.) --- Userland/Libraries/LibWeb/DOM/Document.cpp | 6 ++---- Userland/Libraries/LibWeb/DOM/Node.h | 4 ++++ Userland/Libraries/LibWeb/DOM/ShadowRoot.h | 18 ++++++++++++++++++ 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/Userland/Libraries/LibWeb/DOM/Document.cpp b/Userland/Libraries/LibWeb/DOM/Document.cpp index f072283a75..46dd53bba0 100644 --- a/Userland/Libraries/LibWeb/DOM/Document.cpp +++ b/Userland/Libraries/LibWeb/DOM/Document.cpp @@ -1116,8 +1116,7 @@ void Document::adopt_node(Node& node) node.remove(); if (&old_document != this) { - // FIXME: This should be shadow-including. - node.for_each_in_inclusive_subtree([&](auto& inclusive_descendant) { + node.for_each_shadow_including_descendant([&](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; @@ -1127,8 +1126,7 @@ void Document::adopt_node(Node& node) // 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) { + node.for_each_shadow_including_descendant([&](auto& inclusive_descendant) { inclusive_descendant.adopted_from(old_document); return IterationDecision::Continue; }); diff --git a/Userland/Libraries/LibWeb/DOM/Node.h b/Userland/Libraries/LibWeb/DOM/Node.h index d30275ef5c..8cd3ffb2c9 100644 --- a/Userland/Libraries/LibWeb/DOM/Node.h +++ b/Userland/Libraries/LibWeb/DOM/Node.h @@ -231,6 +231,10 @@ public: void queue_mutation_record(FlyString const& type, String attribute_name, String attribute_namespace, String old_value, NonnullRefPtr added_nodes, NonnullRefPtr removed_nodes, Node* previous_sibling, Node* next_sibling); + // https://dom.spec.whatwg.org/#concept-shadow-including-descendant + template + IterationDecision for_each_shadow_including_descendant(Callback); + protected: Node(Document&, NodeType); diff --git a/Userland/Libraries/LibWeb/DOM/ShadowRoot.h b/Userland/Libraries/LibWeb/DOM/ShadowRoot.h index b4306c4461..ea561e7b4c 100644 --- a/Userland/Libraries/LibWeb/DOM/ShadowRoot.h +++ b/Userland/Libraries/LibWeb/DOM/ShadowRoot.h @@ -45,4 +45,22 @@ private: template<> inline bool Node::fast_is() const { return is_shadow_root(); } +template +inline IterationDecision Node::for_each_shadow_including_descendant(Callback callback) +{ + if (callback(*this) == IterationDecision::Break) + return IterationDecision::Break; + for (auto* child = first_child(); child; child = child->next_sibling()) { + if (child->is_element()) { + if (RefPtr shadow_root = static_cast(child)->shadow_root()) { + if (shadow_root->for_each_shadow_including_descendant(callback) == IterationDecision::Break) + return IterationDecision::Break; + } + } + if (child->for_each_shadow_including_descendant(callback) == IterationDecision::Break) + return IterationDecision::Break; + } + return IterationDecision::Continue; +} + }