Introduce template GCedTreeNodeMixin<>.
This mixin class is required should we attempt to make CXFA_Node
be garbage-collected, since it is a subclass of CFXA_Object which
is already garbage collected. This is the Oilpan technique for
adding a second garbage-collected class to an already garbage
collected class.
Change-Id: Ic3ecfdfb8103bac7ccfbea17b674a5ab416f951b
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/72291
Commit-Queue: Tom Sepez <tsepez@chromium.org>
Reviewed-by: Lei Zhang <thestig@chromium.org>
diff --git a/fxjs/BUILD.gn b/fxjs/BUILD.gn
index bb85b32..7a8f961 100644
--- a/fxjs/BUILD.gn
+++ b/fxjs/BUILD.gn
@@ -115,6 +115,7 @@
if (pdf_enable_xfa) {
sources += [
"gc/gced_tree_node.h",
+ "gc/gced_tree_node_mixin.h",
"gc/heap.cpp",
"gc/heap.h",
"xfa/cfxjse_class.cpp",
@@ -229,6 +230,7 @@
pdfium_root_dir = "../"
if (pdf_enable_xfa) {
sources += [
+ "gc/gced_tree_node_mixin_unittest.cpp",
"gc/gced_tree_node_unittest.cpp",
"gc/heap_unittest.cpp",
]
diff --git a/fxjs/gc/gced_tree_node.h b/fxjs/gc/gced_tree_node.h
index 337f73a..319e2ca 100644
--- a/fxjs/gc/gced_tree_node.h
+++ b/fxjs/gc/gced_tree_node.h
@@ -6,8 +6,7 @@
#define FXJS_GC_GCED_TREE_NODE_H_
#include "core/fxcrt/tree_node.h"
-#include "third_party/base/logging.h"
-#include "v8/include/cppgc/allocation.h"
+#include "v8/include/cppgc/garbage-collected.h"
#include "v8/include/cppgc/member.h"
#include "v8/include/cppgc/visitor.h"
diff --git a/fxjs/gc/gced_tree_node_mixin.h b/fxjs/gc/gced_tree_node_mixin.h
new file mode 100644
index 0000000..3ed9e8c
--- /dev/null
+++ b/fxjs/gc/gced_tree_node_mixin.h
@@ -0,0 +1,41 @@
+// Copyright 2020 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 FXJS_GC_GCED_TREE_NODE_MIXIN_H_
+#define FXJS_GC_GCED_TREE_NODE_MIXIN_H_
+
+#include "core/fxcrt/tree_node.h"
+#include "v8/include/cppgc/garbage-collected.h"
+#include "v8/include/cppgc/member.h"
+#include "v8/include/cppgc/visitor.h"
+
+namespace fxjs {
+
+// For DOM/XML-ish trees, where references outside the tree are Persistent<>,
+// usable by classes that are already garbage collected themselves.
+template <typename T>
+class GCedTreeNodeMixin : public cppgc::GarbageCollectedMixin,
+ public TreeNode<T, cppgc::Member<T>> {
+ public:
+ using TreeNode<T, cppgc::Member<T>>::RemoveChild;
+
+ virtual void Trace(cppgc::Visitor* visitor) const {
+ visitor->Trace(TreeNode<T, cppgc::Member<T>>::RawParent());
+ visitor->Trace(TreeNode<T, cppgc::Member<T>>::RawFirstChild());
+ visitor->Trace(TreeNode<T, cppgc::Member<T>>::RawLastChild());
+ visitor->Trace(TreeNode<T, cppgc::Member<T>>::RawNextSibling());
+ visitor->Trace(TreeNode<T, cppgc::Member<T>>::RawPrevSibling());
+ }
+
+ protected:
+ GCedTreeNodeMixin() = default;
+ GCedTreeNodeMixin(const GCedTreeNodeMixin& that) = delete;
+ GCedTreeNodeMixin& operator=(const GCedTreeNodeMixin& that) = delete;
+};
+
+} // namespace fxjs
+
+using fxjs::GCedTreeNodeMixin;
+
+#endif // FXJS_GC_GCED_TREE_NODE_MIXIN_H_
diff --git a/fxjs/gc/gced_tree_node_mixin_unittest.cpp b/fxjs/gc/gced_tree_node_mixin_unittest.cpp
new file mode 100644
index 0000000..fd240e9
--- /dev/null
+++ b/fxjs/gc/gced_tree_node_mixin_unittest.cpp
@@ -0,0 +1,157 @@
+// Copyright 2020 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 "fxjs/gc/gced_tree_node_mixin.h"
+
+#include <map>
+
+#include "core/fxcrt/observed_ptr.h"
+#include "fxjs/gc/heap.h"
+#include "testing/fxgc_unittest.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/v8_test_environment.h"
+#include "third_party/base/stl_util.h"
+#include "v8/include/cppgc/allocation.h"
+#include "v8/include/cppgc/persistent.h"
+
+namespace {
+
+class ObservableGCedTreeNodeMixinForTest
+ : public cppgc::GarbageCollected<ObservableGCedTreeNodeMixinForTest>,
+ public GCedTreeNodeMixin<ObservableGCedTreeNodeMixinForTest>,
+ public Observable {
+ public:
+ CONSTRUCT_VIA_MAKE_GARBAGE_COLLECTED;
+
+ // GCedTreeNodeMixin:
+ void Trace(cppgc::Visitor* visitor) const override {
+ GCedTreeNodeMixin<ObservableGCedTreeNodeMixinForTest>::Trace(visitor);
+ }
+
+ private:
+ ObservableGCedTreeNodeMixinForTest() = default;
+};
+
+} // namespace
+
+class GCedTreeNodeMixinUnitTest : public FXGCUnitTest {
+ public:
+ static cppgc::Persistent<ObservableGCedTreeNodeMixinForTest> s_root;
+
+ GCedTreeNodeMixinUnitTest() = default;
+ ~GCedTreeNodeMixinUnitTest() override = default;
+
+ // FXGCUnitTest:
+ void TearDown() override {
+ s_root = nullptr; // Can't (yet) outlive |heap_|.
+ FXGCUnitTest::TearDown();
+ }
+
+ ObservableGCedTreeNodeMixinForTest* CreateNode() {
+ return cppgc::MakeGarbageCollected<ObservableGCedTreeNodeMixinForTest>(
+ heap()->GetAllocationHandle());
+ }
+
+ void ForceGCAndPump() {
+ FXGC_ForceGarbageCollection(heap());
+ V8TestEnvironment::PumpPlatformMessageLoop(isolate());
+ }
+
+ void AddClutterToFront(ObservableGCedTreeNodeMixinForTest* parent) {
+ for (int i = 0; i < 4; ++i) {
+ parent->AppendFirstChild(
+ cppgc::MakeGarbageCollected<ObservableGCedTreeNodeMixinForTest>(
+ heap()->GetAllocationHandle()));
+ }
+ }
+
+ void AddClutterToBack(ObservableGCedTreeNodeMixinForTest* parent) {
+ for (int i = 0; i < 4; ++i) {
+ parent->AppendLastChild(
+ cppgc::MakeGarbageCollected<ObservableGCedTreeNodeMixinForTest>(
+ heap()->GetAllocationHandle()));
+ }
+ }
+
+ private:
+ FXGCScopedHeap heap_;
+};
+
+cppgc::Persistent<ObservableGCedTreeNodeMixinForTest>
+ GCedTreeNodeMixinUnitTest::s_root;
+
+TEST_F(GCedTreeNodeMixinUnitTest, OneRefence) {
+ s_root = CreateNode();
+ ObservedPtr<ObservableGCedTreeNodeMixinForTest> watcher(s_root);
+ ForceGCAndPump();
+ EXPECT_TRUE(watcher);
+}
+
+TEST_F(GCedTreeNodeMixinUnitTest, NoReferences) {
+ ObservedPtr<ObservableGCedTreeNodeMixinForTest> watcher(CreateNode());
+ ForceGCAndPump();
+ EXPECT_FALSE(watcher);
+}
+
+TEST_F(GCedTreeNodeMixinUnitTest, FirstHasParent) {
+ s_root = CreateNode();
+ ObservedPtr<ObservableGCedTreeNodeMixinForTest> watcher(CreateNode());
+ s_root->AppendFirstChild(watcher.Get());
+ ForceGCAndPump();
+ ASSERT_TRUE(s_root);
+ EXPECT_TRUE(watcher);
+ s_root->RemoveChild(watcher.Get());
+ ForceGCAndPump();
+ ASSERT_TRUE(s_root);
+ EXPECT_FALSE(watcher);
+
+ // Now add some clutter.
+ watcher.Reset(CreateNode());
+ s_root->AppendFirstChild(watcher.Get());
+ AddClutterToFront(s_root);
+ AddClutterToBack(s_root);
+ ForceGCAndPump();
+ ASSERT_TRUE(s_root);
+ EXPECT_TRUE(watcher);
+ s_root->RemoveChild(watcher.Get());
+ ForceGCAndPump();
+ EXPECT_TRUE(s_root);
+ EXPECT_FALSE(watcher);
+}
+
+TEST_F(GCedTreeNodeMixinUnitTest, RemoveSelf) {
+ s_root = CreateNode();
+ ObservedPtr<ObservableGCedTreeNodeMixinForTest> watcher(CreateNode());
+ s_root->AppendFirstChild(watcher.Get());
+ ForceGCAndPump();
+ EXPECT_TRUE(s_root);
+ ASSERT_TRUE(watcher);
+ watcher->RemoveSelfIfParented();
+ ForceGCAndPump();
+ EXPECT_TRUE(s_root);
+ EXPECT_FALSE(watcher);
+}
+
+TEST_F(GCedTreeNodeMixinUnitTest, InsertBeforeAfter) {
+ s_root = CreateNode();
+ AddClutterToFront(s_root);
+ ObservedPtr<ObservableGCedTreeNodeMixinForTest> watcher(CreateNode());
+ s_root->AppendFirstChild(watcher.Get());
+ s_root->InsertBefore(s_root->GetFirstChild(), s_root->GetLastChild());
+ s_root->InsertAfter(s_root->GetLastChild(), s_root->GetFirstChild());
+ ForceGCAndPump();
+ ASSERT_TRUE(s_root);
+ EXPECT_TRUE(watcher);
+ s_root->RemoveChild(watcher.Get());
+ ForceGCAndPump();
+ EXPECT_TRUE(s_root);
+ EXPECT_FALSE(watcher);
+}
+
+TEST_F(GCedTreeNodeMixinUnitTest, AsMapKey) {
+ std::map<cppgc::Persistent<ObservableGCedTreeNodeMixinForTest>, int> score;
+ ObservableGCedTreeNodeMixinForTest* node = CreateNode();
+ score[node] = 100;
+ EXPECT_EQ(100, score[node]);
+}