diff --git a/Libraries/LibWeb/RefCountedTreeNode.h b/Libraries/LibWeb/RefCountedTreeNode.h new file mode 100644 index 0000000000..4688990fbe --- /dev/null +++ b/Libraries/LibWeb/RefCountedTreeNode.h @@ -0,0 +1,298 @@ +/* + * Copyright (c) 2026, the Ladybird developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace Web { + +template +TraversalDecision traverse_ref_counted_preorder(RefPtr root, Callback callback) +{ + auto current = root; + while (current) { + TraversalDecision decision = callback(*current); + if (decision == TraversalDecision::Break) + return TraversalDecision::Break; + + if (decision != TraversalDecision::SkipChildrenAndContinue) { + if (auto first_child = current->first_child()) { + current = move(first_child); + continue; + } + } + if (current == root) + break; + + if (auto next_sibling = current->next_sibling()) { + current = move(next_sibling); + continue; + } + + while (current != root && !current->next_sibling()) + current = current->parent(); + if (current == root) + break; + + current = current->next_sibling(); + } + return TraversalDecision::Continue; +} + +template +class WEB_API RefCountedTreeNode { +public: + RefPtr parent() { return m_parent.strong_ref(); } + RefPtr parent() const { return m_parent.strong_ref(); } + + bool has_children() const { return m_first_child; } + + RefPtr first_child() + { + return m_first_child; + } + RefPtr first_child() const + { + return const_cast(*this).first_child(); + } + + RefPtr last_child() + { + return m_last_child.strong_ref(); + } + RefPtr last_child() const + { + return const_cast(*this).last_child(); + } + + RefPtr next_sibling() + { + return m_next_sibling; + } + RefPtr next_sibling() const + { + return const_cast(*this).next_sibling(); + } + + RefPtr previous_sibling() + { + return m_previous_sibling.strong_ref(); + } + RefPtr previous_sibling() const + { + return const_cast(*this).previous_sibling(); + } + + void append_child(NonnullRefPtr node) + { + VERIFY(!node->parent()); + VERIFY(!node->next_sibling()); + VERIFY(!node->previous_sibling()); + + auto previous_last_child = last_child(); + node->m_previous_sibling = previous_last_child; + node->m_parent = static_cast(*this); + m_last_child = node; + + if (previous_last_child) + previous_last_child->m_next_sibling = move(node); + else + m_first_child = move(node); + } + + void remove_child(T& node) + { + RefPtr self = static_cast(*this); + RefPtr child_to_remove = node; + VERIFY(node.parent() == self); + + auto previous_sibling = node.previous_sibling(); + auto next_sibling = node.next_sibling(); + if (previous_sibling) { + VERIFY(previous_sibling->m_next_sibling == child_to_remove); + previous_sibling->m_next_sibling = next_sibling; + } else { + VERIFY(m_first_child == child_to_remove); + m_first_child = next_sibling; + } + + if (next_sibling) { + VERIFY(next_sibling->previous_sibling() == child_to_remove); + next_sibling->m_previous_sibling = previous_sibling; + } else { + VERIFY(last_child() == child_to_remove); + m_last_child = previous_sibling; + } + + node.m_next_sibling.clear(); + node.m_previous_sibling.clear(); + node.m_parent.clear(); + } + + void remove() + { + auto parent = this->parent(); + VERIFY(parent); + parent->remove_child(static_cast(*this)); + } + + template + TraversalDecision for_each_in_inclusive_subtree(Callback callback) const + { + return traverse_ref_counted_preorder(RefPtr { static_cast(*this) }, callback); + } + + template + TraversalDecision for_each_in_inclusive_subtree(Callback callback) + { + return traverse_ref_counted_preorder(RefPtr { static_cast(*this) }, callback); + } + + template + TraversalDecision for_each_in_inclusive_subtree_of_type(Callback callback) + { + return for_each_in_inclusive_subtree([callback = move(callback)](T& node) { + if (auto* node_of_type = as_if(node)) + return callback(*node_of_type); + return TraversalDecision::Continue; + }); + } + + template + TraversalDecision for_each_in_inclusive_subtree_of_type(Callback callback) const + { + return for_each_in_inclusive_subtree([callback = move(callback)](T const& node) { + if (auto const* node_of_type = as_if(node)) + return callback(*node_of_type); + return TraversalDecision::Continue; + }); + } + + template + TraversalDecision for_each_in_subtree(Callback callback) const + { + for (auto child = first_child(); child; child = child->next_sibling()) { + if (child->for_each_in_inclusive_subtree(callback) == TraversalDecision::Break) + return TraversalDecision::Break; + } + return TraversalDecision::Continue; + } + + template + TraversalDecision for_each_in_subtree(Callback callback) + { + for (auto child = first_child(); child; child = child->next_sibling()) { + if (child->for_each_in_inclusive_subtree(callback) == TraversalDecision::Break) + return TraversalDecision::Break; + } + return TraversalDecision::Continue; + } + + template + TraversalDecision for_each_in_subtree_of_type(Callback callback) + { + for (auto child = first_child(); child; child = child->next_sibling()) { + if (child->template for_each_in_inclusive_subtree_of_type(callback) == TraversalDecision::Break) + return TraversalDecision::Break; + } + return TraversalDecision::Continue; + } + + template + TraversalDecision for_each_in_subtree_of_type(Callback callback) const + { + for (auto child = first_child(); child; child = child->next_sibling()) { + if (child->template for_each_in_inclusive_subtree_of_type(callback) == TraversalDecision::Break) + return TraversalDecision::Break; + } + return TraversalDecision::Continue; + } + + template + void for_each_child(Callback callback) const + { + return const_cast(*this).for_each_child(move(callback)); + } + + template + void for_each_child(Callback callback) + { + for (auto node = first_child(); node; node = node->next_sibling()) { + if (callback(*node) == IterationDecision::Break) + return; + } + } + + template + void for_each_child_of_type(Callback callback) + { + for (auto node = first_child(); node; node = node->next_sibling()) { + auto* node_of_type = as_if(*node); + if (!node_of_type) + continue; + if (callback(*node_of_type) == IterationDecision::Break) + return; + } + } + + template + void for_each_child_of_type(Callback callback) const + { + return const_cast(*this).template for_each_child_of_type(move(callback)); + } + + template + RefPtr first_ancestor_of_type() const + { + return const_cast(*this).template first_ancestor_of_type(); + } + + template + RefPtr first_ancestor_of_type() + { + for (auto ancestor = parent(); ancestor; ancestor = ancestor->parent()) { + if (auto* ancestor_of_type = as_if(*ancestor)) + return *ancestor_of_type; + } + return nullptr; + } + + ~RefCountedTreeNode() + { + if (auto parent = this->parent()) + parent->remove_child(static_cast(*this)); + + while (m_first_child) { + auto child = m_first_child; + m_first_child = child->m_next_sibling; + if (m_first_child) + m_first_child->m_previous_sibling.clear(); + child->m_next_sibling.clear(); + child->m_previous_sibling.clear(); + child->m_parent.clear(); + } + m_last_child.clear(); + } + +protected: + RefCountedTreeNode() = default; + +private: + WeakPtr m_parent; + RefPtr m_first_child; + WeakPtr m_last_child; + RefPtr m_next_sibling; + WeakPtr m_previous_sibling; +}; + +} diff --git a/Tests/LibWeb/CMakeLists.txt b/Tests/LibWeb/CMakeLists.txt index 57651fbb0b..b675501b93 100644 --- a/Tests/LibWeb/CMakeLists.txt +++ b/Tests/LibWeb/CMakeLists.txt @@ -12,6 +12,7 @@ set(TEST_SOURCES TestMicrosyntax.cpp TestMimeSniff.cpp TestNumbers.cpp + TestRefCountedTreeNode.cpp TestSourceHighlighter.cpp TestStrings.cpp ) diff --git a/Tests/LibWeb/TestRefCountedTreeNode.cpp b/Tests/LibWeb/TestRefCountedTreeNode.cpp new file mode 100644 index 0000000000..8a4a79b1c9 --- /dev/null +++ b/Tests/LibWeb/TestRefCountedTreeNode.cpp @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2026, the Ladybird developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include + +class TestNode + : public RefCounted + , public Weakable + , public Web::RefCountedTreeNode { +public: + static NonnullRefPtr create(int value) + { + return adopt_ref(*new TestNode(value)); + } + + ~TestNode() = default; + + int value() const { return m_value; } + +private: + explicit TestNode(int value) + : m_value(value) + { + } + + int m_value { 0 }; +}; + +TEST_CASE(sibling_links_are_updated_when_appending) +{ + auto root = TestNode::create(0); + auto first = TestNode::create(1); + auto second = TestNode::create(2); + auto third = TestNode::create(3); + + root->append_child(first); + root->append_child(second); + root->append_child(third); + + EXPECT_EQ(root->first_child().ptr(), first.ptr()); + EXPECT_EQ(root->last_child().ptr(), third.ptr()); + + EXPECT_EQ(first->previous_sibling().ptr(), nullptr); + EXPECT_EQ(first->next_sibling().ptr(), second.ptr()); + EXPECT_EQ(second->previous_sibling().ptr(), first.ptr()); + EXPECT_EQ(second->next_sibling().ptr(), third.ptr()); + EXPECT_EQ(third->previous_sibling().ptr(), second.ptr()); + EXPECT_EQ(third->next_sibling().ptr(), nullptr); +} + +TEST_CASE(sibling_links_are_updated_when_removing) +{ + auto root = TestNode::create(0); + auto first = TestNode::create(1); + auto second = TestNode::create(2); + auto third = TestNode::create(3); + + root->append_child(first); + root->append_child(second); + root->append_child(third); + + root->remove_child(*second); + EXPECT_EQ(first->next_sibling().ptr(), third.ptr()); + EXPECT_EQ(third->previous_sibling().ptr(), first.ptr()); + EXPECT_EQ(second->parent().ptr(), nullptr); + EXPECT_EQ(second->previous_sibling().ptr(), nullptr); + EXPECT_EQ(second->next_sibling().ptr(), nullptr); + + root->remove_child(*first); + EXPECT_EQ(root->first_child().ptr(), third.ptr()); + EXPECT_EQ(third->previous_sibling().ptr(), nullptr); + + root->remove_child(*third); + EXPECT_EQ(root->first_child().ptr(), nullptr); + EXPECT_EQ(root->last_child().ptr(), nullptr); + EXPECT_EQ(third->parent().ptr(), nullptr); +} + +TEST_CASE(tree_owns_children_through_forward_links) +{ + WeakPtr first; + WeakPtr second; + WeakPtr third; + + { + auto root = TestNode::create(0); + + auto first_child = TestNode::create(1); + first = first_child; + root->append_child(move(first_child)); + + auto second_child = TestNode::create(2); + second = second_child; + root->append_child(move(second_child)); + + auto third_child = TestNode::create(3); + third = third_child; + root->append_child(move(third_child)); + + EXPECT(first); + EXPECT(second); + EXPECT(third); + } + + EXPECT(first.is_null()); + EXPECT(second.is_null()); + EXPECT(third.is_null()); +} + +TEST_CASE(removing_child_releases_tree_ownership) +{ + auto root = TestNode::create(0); + WeakPtr child_weak; + + { + auto child = TestNode::create(1); + child_weak = child; + root->append_child(move(child)); + } + + EXPECT(child_weak); + + { + auto child = root->first_child(); + root->remove_child(*child); + EXPECT_EQ(root->first_child().ptr(), nullptr); + EXPECT(child_weak); + } + + EXPECT(child_weak.is_null()); +} + +TEST_CASE(parent_destruction_detaches_children_and_siblings) +{ + auto first = TestNode::create(1); + auto second = TestNode::create(2); + auto third = TestNode::create(3); + + { + auto root = TestNode::create(0); + root->append_child(first); + root->append_child(second); + root->append_child(third); + } + + EXPECT_EQ(first->parent().ptr(), nullptr); + EXPECT_EQ(first->next_sibling().ptr(), nullptr); + EXPECT_EQ(second->parent().ptr(), nullptr); + EXPECT_EQ(second->previous_sibling().ptr(), nullptr); + EXPECT_EQ(second->next_sibling().ptr(), nullptr); + EXPECT_EQ(third->parent().ptr(), nullptr); + EXPECT_EQ(third->previous_sibling().ptr(), nullptr); +} + +TEST_CASE(preorder_traversal_uses_sibling_links) +{ + auto root = TestNode::create(0); + auto first = TestNode::create(1); + auto second = TestNode::create(2); + auto third = TestNode::create(3); + auto second_child = TestNode::create(4); + + root->append_child(first); + root->append_child(second); + root->append_child(third); + second->append_child(second_child); + + Vector values; + root->for_each_in_inclusive_subtree([&](TestNode& node) { + values.append(node.value()); + return Web::TraversalDecision::Continue; + }); + + EXPECT_EQ(values.size(), 5u); + EXPECT_EQ(values[0], 0); + EXPECT_EQ(values[1], 1); + EXPECT_EQ(values[2], 2); + EXPECT_EQ(values[3], 4); + EXPECT_EQ(values[4], 3); +}