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]);
+}