Introduce fxcrt::RetainedTreeNode
Builds trees the way they were intended, almost. Similar to
what Blink did prior to the Oilpan garbage collector.
Change-Id: I24b0523bb6585d280e458254c3303ce9162b4654
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/52331
Commit-Queue: Tom Sepez <tsepez@chromium.org>
Reviewed-by: Lei Zhang <thestig@chromium.org>
diff --git a/core/fxcrt/BUILD.gn b/core/fxcrt/BUILD.gn
index d395e4d..2221af1 100644
--- a/core/fxcrt/BUILD.gn
+++ b/core/fxcrt/BUILD.gn
@@ -56,6 +56,7 @@
"observable.h",
"pauseindicator_iface.h",
"retain_ptr.h",
+ "retained_tree_node.h",
"shared_copy_on_write.h",
"string_data_template.h",
"string_pool_template.h",
@@ -156,6 +157,7 @@
"observable_unittest.cpp",
"pdfium_span_unittest.cpp",
"retain_ptr_unittest.cpp",
+ "retained_tree_node_unittest.cpp",
"shared_copy_on_write_unittest.cpp",
"string_pool_template_unittest.cpp",
"unowned_ptr_unittest.cpp",
diff --git a/core/fxcrt/retained_tree_node.h b/core/fxcrt/retained_tree_node.h
new file mode 100644
index 0000000..8b29e69
--- /dev/null
+++ b/core/fxcrt/retained_tree_node.h
@@ -0,0 +1,146 @@
+// Copyright 2019 PDFium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CORE_FXCRT_RETAINED_TREE_NODE_H_
+#define CORE_FXCRT_RETAINED_TREE_NODE_H_
+
+#include "core/fxcrt/retain_ptr.h"
+#include "third_party/base/logging.h"
+
+namespace fxcrt {
+
+// For DOM/XML-ish trees, where references outside the tree are RetainPtr<T>,
+// and the parent node also "retains" its children but doesn't always have
+// a direct pointer to them.
+template <typename T>
+class RetainedTreeNode {
+ public:
+ template <typename U, typename... Args>
+ friend RetainPtr<U> pdfium::MakeRetain(Args&&... args);
+
+ T* GetParent() const { return m_pParent; }
+ T* GetFirstChild() const { return m_pFirstChild; }
+ T* GetLastChild() const { return m_pLastChild; }
+ T* GetNextSibling() const { return m_pNextSibling; }
+ T* GetPrevSibling() const { return m_pPrevSibling; }
+
+ void AppendFirstChild(const RetainPtr<T>& child) {
+ BecomeParent(child);
+ if (m_pFirstChild) {
+ m_pFirstChild->m_pPrevSibling = child.Get();
+ child->m_pNextSibling = m_pFirstChild;
+ m_pFirstChild = child.Get();
+ } else {
+ m_pFirstChild = child.Get();
+ m_pLastChild = child.Get();
+ }
+ }
+
+ void AppendLastChild(const RetainPtr<T>& child) {
+ BecomeParent(child);
+ if (m_pLastChild) {
+ m_pLastChild->m_pNextSibling = child.Get();
+ child->m_pPrevSibling = m_pLastChild;
+ m_pLastChild = child.Get();
+ } else {
+ m_pFirstChild = child.Get();
+ m_pLastChild = child.Get();
+ }
+ }
+
+ void InsertBefore(const RetainPtr<T>& child, T* other) {
+ if (!other) {
+ AppendLastChild(child);
+ return;
+ }
+ CHECK(other->m_pParent == this);
+ BecomeParent(child);
+ child->m_pNextSibling = other;
+ child->m_pPrevSibling = other->m_pPrevSibling;
+ if (other->m_pPrevSibling)
+ other->m_pPrevSibling->m_pNextSibling = child.Get();
+ else
+ m_pFirstChild = child.Get();
+ other->m_pPrevSibling = child.Get();
+ }
+
+ void InsertAfter(const RetainPtr<T>& child, T* other) {
+ if (!other) {
+ AppendFirstChild(child);
+ return;
+ }
+ CHECK(other->m_pParent == this);
+ BecomeParent(child);
+ child->m_pNextSibling = other->m_pNextSibling;
+ child->m_pPrevSibling = other;
+ if (other->m_pNextSibling)
+ other->m_pNextSibling->m_pPrevSibling = child.Get();
+ else
+ m_pLastChild = child.Get();
+ other->m_pNextSibling = child.Get();
+ }
+
+ void RemoveChild(const RetainPtr<T>& child) {
+ CHECK(child->m_pParent == this);
+ if (child->m_pNextSibling)
+ child->m_pNextSibling->m_pPrevSibling = child->m_pPrevSibling;
+ else
+ m_pLastChild = child->m_pPrevSibling;
+
+ if (child->m_pPrevSibling)
+ child->m_pPrevSibling->m_pNextSibling = child->m_pNextSibling;
+ else
+ m_pFirstChild = child->m_pNextSibling;
+
+ child->m_pParent = nullptr;
+ child->m_pPrevSibling = nullptr;
+ child->m_pNextSibling = nullptr;
+ }
+
+ protected:
+ RetainedTreeNode() = default;
+ virtual ~RetainedTreeNode() {
+ while (m_pFirstChild)
+ RemoveChild(pdfium::WrapRetain(m_pFirstChild));
+ }
+
+ private:
+ template <typename U>
+ friend struct ReleaseDeleter;
+
+ template <typename U>
+ friend class RetainPtr;
+
+ RetainedTreeNode(const RetainedTreeNode& that) = delete;
+ RetainedTreeNode& operator=(const RetainedTreeNode& that) = delete;
+
+ void Retain() { ++m_nRefCount; }
+ void Release() {
+ ASSERT(m_nRefCount > 0);
+ if (--m_nRefCount == 0 && !m_pParent)
+ delete this;
+ }
+
+ // Child left in state where sibling members need subsequent adjustment.
+ void BecomeParent(const RetainPtr<T>& child) {
+ if (child->m_pParent)
+ child->m_pParent->RemoveChild(child);
+ child->m_pParent = static_cast<T*>(this);
+ ASSERT(!child->m_pNextSibling);
+ ASSERT(!child->m_pPrevSibling);
+ }
+
+ intptr_t m_nRefCount = 0;
+ T* m_pParent = nullptr; // Raw, intra-tree pointer.
+ T* m_pFirstChild = nullptr; // Raw, intra-tree pointer.
+ T* m_pLastChild = nullptr; // Raw, intra-tree pointer.
+ T* m_pNextSibling = nullptr; // Raw, intra-tree pointer
+ T* m_pPrevSibling = nullptr; // Raw, intra-tree pointer
+};
+
+} // namespace fxcrt
+
+using fxcrt::RetainedTreeNode;
+
+#endif // CORE_FXCRT_RETAINED_TREE_NODE_H_
diff --git a/core/fxcrt/retained_tree_node_unittest.cpp b/core/fxcrt/retained_tree_node_unittest.cpp
new file mode 100644
index 0000000..c41b5ef
--- /dev/null
+++ b/core/fxcrt/retained_tree_node_unittest.cpp
@@ -0,0 +1,170 @@
+// Copyright 2019 PDFium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "core/fxcrt/retained_tree_node.h"
+
+#include "core/fxcrt/observable.h"
+#include "core/fxcrt/retain_ptr.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace fxcrt {
+namespace {
+
+class ObservableRetainedTreeNodeForTest
+ : public RetainedTreeNode<ObservableRetainedTreeNodeForTest>,
+ public Observable<ObservableRetainedTreeNodeForTest> {
+ public:
+ template <typename T, typename... Args>
+ friend RetainPtr<T> pdfium::MakeRetain(Args&&... args);
+
+ private:
+ ObservableRetainedTreeNodeForTest() = default;
+};
+
+void AddClutterToFront(
+ const RetainPtr<ObservableRetainedTreeNodeForTest>& parent) {
+ for (int i = 0; i < 4; ++i) {
+ parent->AppendFirstChild(
+ pdfium::MakeRetain<ObservableRetainedTreeNodeForTest>());
+ }
+}
+
+void AddClutterToBack(
+ const RetainPtr<ObservableRetainedTreeNodeForTest>& parent) {
+ for (int i = 0; i < 4; ++i) {
+ parent->AppendLastChild(
+ pdfium::MakeRetain<ObservableRetainedTreeNodeForTest>());
+ }
+}
+
+} // namespace
+
+TEST(RetainedTreeNode, NoParent) {
+ ObservableRetainedTreeNodeForTest::ObservedPtr watcher;
+ {
+ RetainPtr<ObservableRetainedTreeNodeForTest> ptr =
+ pdfium::MakeRetain<ObservableRetainedTreeNodeForTest>();
+ watcher = ObservableRetainedTreeNodeForTest::ObservedPtr(ptr.Get());
+ EXPECT_TRUE(watcher.Get());
+ }
+ EXPECT_FALSE(watcher.Get());
+}
+
+TEST(RetainedTreeNode, FirstHasParent) {
+ ObservableRetainedTreeNodeForTest::ObservedPtr watcher;
+ RetainPtr<ObservableRetainedTreeNodeForTest> parent =
+ pdfium::MakeRetain<ObservableRetainedTreeNodeForTest>();
+ {
+ RetainPtr<ObservableRetainedTreeNodeForTest> ptr =
+ pdfium::MakeRetain<ObservableRetainedTreeNodeForTest>();
+ watcher = ObservableRetainedTreeNodeForTest::ObservedPtr(ptr.Get());
+ parent->AppendFirstChild(ptr);
+ EXPECT_TRUE(watcher.Get());
+ }
+ EXPECT_TRUE(watcher.Get());
+ parent->RemoveChild(pdfium::WrapRetain(watcher.Get()));
+ EXPECT_FALSE(watcher.Get());
+ // Now add some clutter.
+ {
+ RetainPtr<ObservableRetainedTreeNodeForTest> ptr =
+ pdfium::MakeRetain<ObservableRetainedTreeNodeForTest>();
+ watcher = ObservableRetainedTreeNodeForTest::ObservedPtr(ptr.Get());
+ parent->AppendFirstChild(ptr);
+ AddClutterToFront(parent);
+ AddClutterToBack(parent);
+ EXPECT_TRUE(watcher.Get());
+ }
+ EXPECT_TRUE(watcher.Get());
+ parent->RemoveChild(pdfium::WrapRetain(watcher.Get()));
+ EXPECT_FALSE(watcher.Get());
+}
+
+TEST(RetainedTreeNode, LastHasParent) {
+ ObservableRetainedTreeNodeForTest::ObservedPtr watcher;
+ RetainPtr<ObservableRetainedTreeNodeForTest> parent =
+ pdfium::MakeRetain<ObservableRetainedTreeNodeForTest>();
+ {
+ RetainPtr<ObservableRetainedTreeNodeForTest> ptr =
+ pdfium::MakeRetain<ObservableRetainedTreeNodeForTest>();
+ watcher = ObservableRetainedTreeNodeForTest::ObservedPtr(ptr.Get());
+ parent->AppendLastChild(ptr);
+ EXPECT_TRUE(watcher.Get());
+ }
+ {
+ // Now add some clutter.
+ RetainPtr<ObservableRetainedTreeNodeForTest> ptr =
+ pdfium::MakeRetain<ObservableRetainedTreeNodeForTest>();
+ watcher = ObservableRetainedTreeNodeForTest::ObservedPtr(ptr.Get());
+ parent->AppendLastChild(ptr);
+ AddClutterToFront(parent);
+ AddClutterToBack(parent);
+ EXPECT_TRUE(watcher.Get());
+ }
+ EXPECT_TRUE(watcher.Get());
+ parent->RemoveChild(pdfium::WrapRetain(watcher.Get()));
+ EXPECT_FALSE(watcher.Get());
+}
+
+TEST(RetainedTreeNode, GrandChildCleanedUp) {
+ ObservableRetainedTreeNodeForTest::ObservedPtr watcher;
+ RetainPtr<ObservableRetainedTreeNodeForTest> grandparent =
+ pdfium::MakeRetain<ObservableRetainedTreeNodeForTest>();
+ {
+ RetainPtr<ObservableRetainedTreeNodeForTest> parent =
+ pdfium::MakeRetain<ObservableRetainedTreeNodeForTest>();
+ grandparent->AppendFirstChild(parent);
+ {
+ RetainPtr<ObservableRetainedTreeNodeForTest> ptr =
+ pdfium::MakeRetain<ObservableRetainedTreeNodeForTest>();
+ watcher = ObservableRetainedTreeNodeForTest::ObservedPtr(ptr.Get());
+ parent->AppendFirstChild(ptr);
+ EXPECT_TRUE(watcher.Get());
+ }
+ grandparent->RemoveChild(parent);
+ EXPECT_TRUE(watcher.Get());
+ }
+ EXPECT_FALSE(watcher.Get());
+}
+
+TEST(RetainedTreeNode, InsertBeforeAfter) {
+ ObservableRetainedTreeNodeForTest::ObservedPtr watcher;
+ RetainPtr<ObservableRetainedTreeNodeForTest> parent =
+ pdfium::MakeRetain<ObservableRetainedTreeNodeForTest>();
+
+ AddClutterToFront(parent);
+ {
+ RetainPtr<ObservableRetainedTreeNodeForTest> ptr =
+ pdfium::MakeRetain<ObservableRetainedTreeNodeForTest>();
+ watcher = ObservableRetainedTreeNodeForTest::ObservedPtr(ptr.Get());
+ parent->AppendFirstChild(ptr);
+ parent->InsertBefore(pdfium::WrapRetain(parent->GetFirstChild()),
+ parent->GetLastChild());
+ parent->InsertAfter(pdfium::WrapRetain(parent->GetLastChild()),
+ parent->GetFirstChild());
+ EXPECT_TRUE(watcher.Get());
+ }
+ parent->RemoveChild(pdfium::WrapRetain(watcher.Get()));
+ EXPECT_FALSE(watcher.Get());
+}
+
+TEST(RetainedTreeNode, Traversal) {
+ RetainPtr<ObservableRetainedTreeNodeForTest> parent =
+ pdfium::MakeRetain<ObservableRetainedTreeNodeForTest>();
+
+ AddClutterToFront(parent);
+ int count = 0;
+ for (ObservableRetainedTreeNodeForTest* pNode = parent->GetFirstChild();
+ pNode; pNode = pNode->GetNextSibling()) {
+ ++count;
+ }
+ EXPECT_EQ(4, count);
+ count = 0;
+ for (ObservableRetainedTreeNodeForTest* pNode = parent->GetLastChild(); pNode;
+ pNode = pNode->GetPrevSibling()) {
+ ++count;
+ }
+ EXPECT_EQ(4, count);
+}
+
+} // namespace fxcrt