[Vertical Tabs] Add TabCollectionNode support for existing components.

This integrates TabCollectionNode with VerticalTabStripRegionView,
VerticalTabStripView, VerticalPinnedTabContainerView, and
VerticalUnpinnedTabContainerView.

Bug: 441086907, 441087017, 441087251, 441086217
Change-Id: I8a1b25199ace475ead5cf70fb6939759234aa202
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6907294
Reviewed-by: David Pennington <dpenning@chromium.org>
Commit-Queue: Caroline Rising <corising@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1511590}
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 3461ae3..77cacb5 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -4623,6 +4623,7 @@
       "//chrome/browser/ui/sharing_hub",
       "//chrome/browser/ui/sharing_hub:impl",
       "//chrome/browser/ui/side_search",
+      "//chrome/browser/ui/tabs/tab_strip_api",
       "//chrome/browser/ui/views",
       "//chrome/browser/ui/views/bubble",
       "//chrome/browser/ui/views/download",
diff --git a/chrome/browser/ui/views/frame/browser_view.cc b/chrome/browser/ui/views/frame/browser_view.cc
index 43fe605..b91025c 100644
--- a/chrome/browser/ui/views/frame/browser_view.cc
+++ b/chrome/browser/ui/views/frame/browser_view.cc
@@ -98,6 +98,7 @@
 #include "chrome/browser/ui/tabs/features.h"
 #include "chrome/browser/ui/tabs/saved_tab_groups/collaboration_messaging_tab_data.h"
 #include "chrome/browser/ui/tabs/tab_enums.h"
+#include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_feature.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/browser/ui/tabs/tab_utils.h"
 #include "chrome/browser/ui/tabs/vertical_tab_strip_state_controller.h"
@@ -1011,6 +1012,9 @@
   if (tabs::AreVerticalTabsEnabled()) {
     auto vertical_tab_strip_container =
         std::make_unique<VerticalTabStripRegionView>(
+            browser_->GetFeatures()
+                .tab_strip_service_feature()
+                ->GetTabStripService(),
             browser_->GetFeatures().vertical_tab_strip_state_controller());
     vertical_tab_strip_container_ =
         AddChildView(std::move(vertical_tab_strip_container));
diff --git a/chrome/browser/ui/views/frame/browser_view.h b/chrome/browser/ui/views/frame/browser_view.h
index 4d2a7b3..702ac11 100644
--- a/chrome/browser/ui/views/frame/browser_view.h
+++ b/chrome/browser/ui/views/frame/browser_view.h
@@ -90,6 +90,7 @@
 class TopContainerView;
 class TopControlsSlideController;
 class TopControlsSlideControllerTest;
+class VerticalTabStripRegionView;
 class WebAppFrameToolbarView;
 class WebUITabStripContainerView;
 
@@ -255,6 +256,10 @@
     return tab_strip_region_view_;
   }
 
+  VerticalTabStripRegionView* vertical_tab_strip_region_view() const {
+    return vertical_tab_strip_container_;
+  }
+
   // Accessor for the TabStrip.
   TabStrip* tabstrip() { return tab_strip_region_view_->tab_strip(); }
 
@@ -1228,7 +1233,7 @@
   raw_ptr<ContentsContainerView> contents_container_view_ = nullptr;
 
   // The view responsible for housing the contents of the vertical tab strip.
-  raw_ptr<views::View> vertical_tab_strip_container_ = nullptr;
+  raw_ptr<VerticalTabStripRegionView> vertical_tab_strip_container_ = nullptr;
 
   // The side panel aligned to the left or the right side of the browser window
   // depending on the kSidePanelHorizontalAlignment pref's value.
diff --git a/chrome/browser/ui/views/frame/vertical_tab_strip_region_view.cc b/chrome/browser/ui/views/frame/vertical_tab_strip_region_view.cc
index 03063c6..1ab1890 100644
--- a/chrome/browser/ui/views/frame/vertical_tab_strip_region_view.cc
+++ b/chrome/browser/ui/views/frame/vertical_tab_strip_region_view.cc
@@ -9,7 +9,9 @@
 #include "chrome/browser/ui/browser_element_identifiers.h"
 #include "chrome/browser/ui/color/chrome_color_id.h"
 #include "chrome/browser/ui/layout_constants.h"
+#include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_service.h"
 #include "chrome/browser/ui/tabs/vertical_tab_strip_state_controller.h"
+#include "chrome/browser/ui/views/tabs/vertical/root_tab_collection_node.h"
 #include "chrome/browser/ui/views/tabs/vertical/vertical_tab_strip_view.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/color/color_id.h"
@@ -22,6 +24,7 @@
 #include "ui/views/layout/layout_types.h"
 #include "ui/views/view.h"
 #include "ui/views/view_class_properties.h"
+#include "ui/views/view_utils.h"
 
 namespace {
 constexpr gfx::Insets kRegionInteriorMargins = gfx::Insets::VH(8, 0);
@@ -30,7 +33,9 @@
 }  // namespace
 
 VerticalTabStripRegionView::VerticalTabStripRegionView(
-    tabs::VerticalTabStripStateController* state_controller) {
+    tabs_api::TabStripService* service_register,
+    tabs::VerticalTabStripStateController* state_controller)
+    : state_controller_(state_controller) {
   SetBackground(views::CreateSolidBackground(ui::kColorFrameActive));
   SetLayoutManager(std::make_unique<views::FlexLayout>())
       ->SetOrientation(views::LayoutOrientation::kVertical)
@@ -51,18 +56,6 @@
   top_button_separator_ = AddChildView(std::make_unique<views::Separator>());
   top_button_separator_->SetColorId(kColorTabDividerFrameActive);
 
-  tab_strip_view_ = AddChildView(std::make_unique<VerticalTabStripView>());
-  tab_strip_view_->SetCollapsedState(state_controller->IsCollapsed());
-  gfx::Insets tab_container_margins = gfx::Insets::TLBR(
-      kRegionVerticalPadding,
-      GetLayoutConstant(VERTICAL_TAB_STRIP_HORIZONTAL_PADDING),
-      kRegionVerticalPadding, 0);
-  tab_strip_view_->SetProperty(
-      views::kFlexBehaviorKey,
-      views::FlexSpecification(views::MinimumFlexSizeRule::kScaleToZero,
-                               views::MaximumFlexSizeRule::kUnbounded));
-  tab_strip_view_->SetProperty(views::kMarginsKey, tab_container_margins);
-
   segmented_button_ = AddChildView(std::make_unique<views::View>());
 
   gemini_button_ = AddChildView(std::make_unique<views::View>());
@@ -76,6 +69,11 @@
           base::Unretained(this)));
 
   SetProperty(views::kElementIdentifierKey, kVerticalTabStripRegionElementId);
+
+  root_node_ = std::make_unique<RootTabCollectionNode>(
+      service_register, this,
+      base::BindRepeating(&VerticalTabStripRegionView::SetTabStripView,
+                          base::Unretained(this)));
 }
 
 VerticalTabStripRegionView::~VerticalTabStripRegionView() = default;
@@ -92,6 +90,27 @@
 void VerticalTabStripRegionView::OnResize(int resize_amount,
                                           bool done_resizing) {}
 
+views::View* VerticalTabStripRegionView::SetTabStripView(
+    std::unique_ptr<views::View> view) {
+  CHECK(views::IsViewClass<VerticalTabStripView>(view.get()));
+  tab_strip_view_ =
+      static_cast<VerticalTabStripView*>(AddChildView(std::move(view)));
+  tab_strip_view_->SetCollapsedState(state_controller_->IsCollapsed());
+  gfx::Insets tab_container_margins = gfx::Insets::TLBR(
+      kRegionVerticalPadding,
+      GetLayoutConstant(VERTICAL_TAB_STRIP_HORIZONTAL_PADDING),
+      kRegionVerticalPadding, 0);
+  tab_strip_view_->SetProperty(
+      views::kFlexBehaviorKey,
+      views::FlexSpecification(views::MinimumFlexSizeRule::kScaleToZero,
+                               views::MaximumFlexSizeRule::kUnbounded));
+  tab_strip_view_->SetProperty(views::kMarginsKey, tab_container_margins);
+  std::optional<size_t> separator_index = GetIndexOf(top_button_separator_);
+  CHECK(separator_index.has_value());
+  ReorderChildView(tab_strip_view_, separator_index.value() + 1);
+  return tab_strip_view_;
+}
+
 void VerticalTabStripRegionView::OnCollapsedStateChanged(
     tabs::VerticalTabStripStateController* state_controller) {
   tab_strip_view_->SetCollapsedState(state_controller->IsCollapsed());
diff --git a/chrome/browser/ui/views/frame/vertical_tab_strip_region_view.h b/chrome/browser/ui/views/frame/vertical_tab_strip_region_view.h
index c80f7c7f..6d9ce2e 100644
--- a/chrome/browser/ui/views/frame/vertical_tab_strip_region_view.h
+++ b/chrome/browser/ui/views/frame/vertical_tab_strip_region_view.h
@@ -12,6 +12,7 @@
 #include "ui/views/accessible_pane_view.h"
 #include "ui/views/controls/resize_area_delegate.h"
 
+class RootTabCollectionNode;
 class VerticalUnpinnedTabContainerView;
 class VerticalPinnedTabContainerView;
 
@@ -19,6 +20,10 @@
 class VerticalTabStripStateController;
 }  // namespace tabs
 
+namespace tabs_api {
+class TabStripService;
+}
+
 namespace views {
 class ResizeArea;
 class Separator;
@@ -35,6 +40,7 @@
   static constexpr int kResizeAreaWidth = 6;
 
   explicit VerticalTabStripRegionView(
+      tabs_api::TabStripService* service_register,
       tabs::VerticalTabStripStateController* state_controller);
   VerticalTabStripRegionView(const VerticalTabStripRegionView&) = delete;
   VerticalTabStripRegionView& operator=(const VerticalTabStripRegionView&) =
@@ -46,10 +52,10 @@
   }
   views::ResizeArea* resize_area_for_testing() { return resize_area_; }
   VerticalPinnedTabContainerView* pinned_tabs_container_for_testing() {
-    return tab_strip_view_->pinned_tabs_container_for_testing();
+    return tab_strip_view_->GetPinnedTabsContainerForTesting();
   }
   VerticalUnpinnedTabContainerView* unpinned_tabs_container_for_testing() {
-    return tab_strip_view_->unpinned_tabs_container_for_testing();
+    return tab_strip_view_->GetUnpinnedTabsContainerForTesting();
   }
 
   // views::View:
@@ -59,6 +65,8 @@
   void OnResize(int resize_amount, bool done_resizing) override;
 
  private:
+  views::View* SetTabStripView(std::unique_ptr<views::View> view);
+
   void OnCollapsedStateChanged(
       tabs::VerticalTabStripStateController* state_controller);
 
@@ -68,7 +76,9 @@
   raw_ptr<views::View> segmented_button_ = nullptr;
   raw_ptr<views::View> gemini_button_ = nullptr;
   raw_ptr<views::ResizeArea> resize_area_ = nullptr;
+  std::unique_ptr<RootTabCollectionNode> root_node_;
 
+  raw_ptr<tabs::VerticalTabStripStateController> state_controller_;
   base::CallbackListSubscription collapsed_state_changed_subscription_;
 };
 
diff --git a/chrome/browser/ui/views/frame/vertical_tab_strip_region_view_unittest.cc b/chrome/browser/ui/views/frame/vertical_tab_strip_region_view_browsertest.cc
similarity index 67%
rename from chrome/browser/ui/views/frame/vertical_tab_strip_region_view_unittest.cc
rename to chrome/browser/ui/views/frame/vertical_tab_strip_region_view_browsertest.cc
index c890a26..6dac34c1 100644
--- a/chrome/browser/ui/views/frame/vertical_tab_strip_region_view_unittest.cc
+++ b/chrome/browser/ui/views/frame/vertical_tab_strip_region_view_browsertest.cc
@@ -4,52 +4,47 @@
 
 #include "chrome/browser/ui/views/frame/vertical_tab_strip_region_view.h"
 
+#include "base/test/scoped_feature_list.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_window/public/browser_window_features.h"
+#include "chrome/browser/ui/tabs/features.h"
 #include "chrome/browser/ui/tabs/vertical_tab_strip_state.h"
 #include "chrome/browser/ui/tabs/vertical_tab_strip_state_controller.h"
+#include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/browser/ui/views/tabs/vertical/vertical_pinned_tab_container_view.h"
 #include "chrome/browser/ui/views/tabs/vertical/vertical_unpinned_tab_container_view.h"
-#include "chrome/common/pref_names.h"
-#include "components/pref_registry/pref_registry_syncable.h"
-#include "components/sync_preferences/testing_pref_service_syncable.h"
-#include "testing/gmock/include/gmock/gmock.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "content/public/test/browser_test.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/views/controls/resize_area.h"
 #include "ui/views/controls/separator.h"
 
-class VerticalTabStripRegionViewTest : public testing::Test {
+class VerticalTabStripRegionViewTest : public InProcessBrowserTest {
  public:
   VerticalTabStripRegionViewTest() = default;
   ~VerticalTabStripRegionViewTest() override = default;
 
   void SetUp() override {
-    testing::Test::SetUp();
-    pref_service_.registry()->RegisterBooleanPref(
-        prefs::kVerticalTabsEnabled, true,
-        user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
-    controller_ =
-        std::make_unique<tabs::VerticalTabStripStateController>(&pref_service_);
-    region_view_ =
-        std::make_unique<VerticalTabStripRegionView>(controller_.get());
+    scoped_feature_list_.InitAndEnableFeature(tabs::kVerticalTabs);
+    InProcessBrowserTest::SetUp();
   }
 
-  void TearDown() override {
-    controller_.reset();
-    testing::Test::TearDown();
+  VerticalTabStripRegionView* region_view() {
+    return BrowserView::GetBrowserViewForBrowser(browser())
+        ->vertical_tab_strip_region_view();
   }
-
-  VerticalTabStripRegionView* region_view() { return region_view_.get(); }
   tabs::VerticalTabStripStateController* controller() {
-    return controller_.get();
+    return browser()
+        ->browser_window_features()
+        ->vertical_tab_strip_state_controller();
   }
 
  private:
-  std::unique_ptr<tabs::VerticalTabStripStateController> controller_;
-  std::unique_ptr<VerticalTabStripRegionView> region_view_;
-  sync_preferences::TestingPrefServiceSyncable pref_service_;
+  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
-TEST_F(VerticalTabStripRegionViewTest,
-       SeparatorVisibilityChangesWithCollapsedState) {
+IN_PROC_BROWSER_TEST_F(VerticalTabStripRegionViewTest,
+                       SeparatorVisibilityChangesWithCollapsedState) {
   controller()->SetCollapsed(true);
   EXPECT_TRUE(controller()->IsCollapsed());
   EXPECT_TRUE(region_view()->tabs_separator_for_testing()->GetVisible());
@@ -59,7 +54,7 @@
   EXPECT_FALSE(region_view()->tabs_separator_for_testing()->GetVisible());
 }
 
-TEST_F(VerticalTabStripRegionViewTest, ResizeAreaBounds) {
+IN_PROC_BROWSER_TEST_F(VerticalTabStripRegionViewTest, ResizeAreaBounds) {
   region_view()->SetBounds(0, 0, 200, 600);
   // Verify resize area is on the right side of the VerticalTabStripRegionView.
   EXPECT_EQ(region_view()->bounds().right(),
@@ -75,7 +70,8 @@
 
 // Verify that the pinned tabs container will never be larger than the unpinned
 // tabs area.
-TEST_F(VerticalTabStripRegionViewTest, PinnedTabsAreaSmallerThanUnpinned) {
+IN_PROC_BROWSER_TEST_F(VerticalTabStripRegionViewTest,
+                       PinnedTabsAreaSmallerThanUnpinned) {
   region_view()->SetBounds(0, 0, 200, 600);
   region_view()->pinned_tabs_container_for_testing()->SetPreferredSize(
       gfx::Size(100, 500));
diff --git a/chrome/browser/ui/views/tabs/vertical/tab_collection_node.cc b/chrome/browser/ui/views/tabs/vertical/tab_collection_node.cc
index 486624d..108aa9e 100644
--- a/chrome/browser/ui/views/tabs/vertical/tab_collection_node.cc
+++ b/chrome/browser/ui/views/tabs/vertical/tab_collection_node.cc
@@ -4,10 +4,16 @@
 
 #include "chrome/browser/ui/views/tabs/vertical/tab_collection_node.h"
 
+#include <vector>
+
 #include "base/functional/bind.h"
 #include "base/no_destructor.h"
 #include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_api_types.mojom.h"
+#include "chrome/browser/ui/views/tabs/vertical/vertical_pinned_tab_container_view.h"
+#include "chrome/browser/ui/views/tabs/vertical/vertical_tab_strip_view.h"
+#include "chrome/browser/ui/views/tabs/vertical/vertical_unpinned_tab_container_view.h"
 #include "ui/views/view.h"
+#include "vertical_unpinned_tab_container_view.h"
 
 namespace {
 
@@ -30,6 +36,11 @@
 
 }  // anonymous namespace
 
+base::CallbackListSubscription TabCollectionNode::RegisterWillDestroyCallback(
+    base::OnceClosure callback) {
+  return on_will_destroy_callback_list_.Add(std::move(callback));
+}
+
 // static
 void TabCollectionNode::SetViewFactoryForTesting(ViewFactory factory) {
   GetViewFactory() = std::move(factory);
@@ -41,6 +52,23 @@
   if (GetViewFactory()) {
     return GetViewFactory().Run(node_for_view);
   }
+  switch (node_for_view->GetType()) {
+    case Type::kTabStrip:
+      return std::make_unique<VerticalTabStripView>(node_for_view);
+    case Type::kPinnedTabs:
+      return std::make_unique<VerticalPinnedTabContainerView>(node_for_view);
+    case Type::kUnpinnedTabs:
+      return std::make_unique<VerticalUnpinnedTabContainerView>(node_for_view);
+    case Type::kSplitTab:
+      // TODO(crbug.com/442568605): support split tabs.
+      break;
+    case Type::kTabGroup:
+      // TODO(crbug.com/442567916): support tab groups.
+      break;
+    case Type::kTab:
+      // TODO(crbug.com/442567140): support tabs.
+      break;
+  }
   return std::make_unique<CollectionTestViewImpl>(node_for_view);
 }
 
@@ -50,7 +78,9 @@
     CustomAddChildView add_node_to_parent_callback)
     : add_node_to_parent_(std::move(add_node_to_parent_callback)) {}
 
-TabCollectionNode::~TabCollectionNode() = default;
+TabCollectionNode::~TabCollectionNode() {
+  on_will_destroy_callback_list_.Notify();
+}
 
 void TabCollectionNode::Initialize(
     tabs_api::mojom::ContainerPtr container,
diff --git a/chrome/browser/ui/views/tabs/vertical/tab_collection_node.h b/chrome/browser/ui/views/tabs/vertical/tab_collection_node.h
index 51a534e8..d6a80153 100644
--- a/chrome/browser/ui/views/tabs/vertical/tab_collection_node.h
+++ b/chrome/browser/ui/views/tabs/vertical/tab_collection_node.h
@@ -8,6 +8,7 @@
 #include <memory>
 #include <vector>
 
+#include "base/callback_list.h"
 #include "base/functional/callback_forward.h"
 #include "base/memory/raw_ptr.h"
 #include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_api.mojom.h"
@@ -49,12 +50,18 @@
     add_child_to_node_ = std::move(add_child_to_node);
   }
 
+  base::CallbackListSubscription RegisterWillDestroyCallback(
+      base::OnceClosure callback);
+
   static void SetViewFactoryForTesting(ViewFactory factory);
+  views::View* get_view_for_testing() { return node_view_; }
 
  protected:
   static std::unique_ptr<views::View> CreateViewForNode(
       TabCollectionNode* node_for_view);
 
+  base::OnceClosureList on_will_destroy_callback_list_;
+
   // the current collection_data object. provided by snapshot and updated
   // through TabObserver.
   tabs_api::mojom::DataPtr data_;
diff --git a/chrome/browser/ui/views/tabs/vertical/tab_collection_node_browsertest.cc b/chrome/browser/ui/views/tabs/vertical/tab_collection_node_browsertest.cc
index 2f6ff64d..6b41889 100644
--- a/chrome/browser/ui/views/tabs/vertical/tab_collection_node_browsertest.cc
+++ b/chrome/browser/ui/views/tabs/vertical/tab_collection_node_browsertest.cc
@@ -16,9 +16,13 @@
 #include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/browser/ui/views/tabs/browser_tab_strip_controller.h"
 #include "chrome/browser/ui/views/tabs/vertical/root_tab_collection_node.h"
+#include "chrome/browser/ui/views/tabs/vertical/vertical_pinned_tab_container_view.h"
+#include "chrome/browser/ui/views/tabs/vertical/vertical_tab_strip_view.h"
+#include "chrome/browser/ui/views/tabs/vertical/vertical_unpinned_tab_container_view.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "content/public/test/browser_test.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "ui/views/controls/scroll_view.h"
 #include "ui/views/view.h"
 
 class TabCollectionNodeBrowserTest : public InProcessBrowserTest {
@@ -362,6 +366,74 @@
             TabCollectionNode::Type::kTab);
 }
 
+IN_PROC_BROWSER_TEST_F(TabCollectionNodeWithSplitTabBrowserTest,
+                       RootNodePopulatesWithTabs_ViewClasses) {
+  AppendPinnedTab();
+  AppendTabToNewGroup();
+  AppendSplitTab();
+  auto parent_view = std::make_unique<views::View>();
+
+  RootTabCollectionNode root_node(
+      browser()
+          ->GetFeatures()
+          .tab_strip_service_feature()
+          ->GetTabStripService(),
+      parent_view.get(),
+      base::BindRepeating(static_cast<views::View* (
+                              views::View::*)(std::unique_ptr<views::View>)>(
+                              &views::View::AddChildView),
+                          base::Unretained(parent_view.get())));
+
+  ASSERT_TRUE(
+      base::test::RunUntil([&]() { return !root_node.children().empty(); }));
+
+  // The root node should contain two nodes: one for pinned
+  // (VerticalPinnedTabContainerView), one for unpinned
+  // (VerticalUnpinnedTabContainerView).
+  ASSERT_EQ(root_node.children().size(), 2u);
+  EXPECT_TRUE(views::IsViewClass<VerticalTabStripView>(
+      root_node.get_view_for_testing()));
+  const auto& pinned_node = root_node.children()[0];
+  const auto& unpinned_node = root_node.children()[1];
+  EXPECT_EQ(pinned_node->GetType(), TabCollectionNode::Type::kPinnedTabs);
+  EXPECT_TRUE(views::IsViewClass<VerticalPinnedTabContainerView>(
+      pinned_node->get_view_for_testing()));
+  EXPECT_EQ(unpinned_node->GetType(), TabCollectionNode::Type::kUnpinnedTabs);
+  EXPECT_TRUE(views::IsViewClass<VerticalUnpinnedTabContainerView>(
+      unpinned_node->get_view_for_testing()));
+
+  // The pinned Node should be have one tab.
+  ASSERT_EQ(pinned_node->children().size(), 1u);
+  EXPECT_EQ(pinned_node->children()[0]->GetType(),
+            TabCollectionNode::Type::kTab);
+  // TODO(crbug.com/442567140): Verify tab view is created.
+
+  // The unpinned Node should contain a tab, a tab group, and a split tab.
+  ASSERT_EQ(unpinned_node->children().size(), 3u);
+  EXPECT_EQ(unpinned_node->children()[0]->GetType(),
+            TabCollectionNode::Type::kTab);
+  // TODO(crbug.com/442567140): Verify tab view is created.
+
+  const auto& group_node = unpinned_node->children()[1];
+  EXPECT_EQ(group_node->GetType(), TabCollectionNode::Type::kTabGroup);
+  // TODO(crbug.com/442567916): Verify tab group view is created.
+  ASSERT_EQ(group_node->children().size(), 1u);
+  EXPECT_EQ(group_node->children()[0]->GetType(),
+            TabCollectionNode::Type::kTab);
+  // TODO(crbug.com/442567140): Verify tab view is created.
+
+  const auto& split_node = unpinned_node->children()[2];
+  EXPECT_EQ(split_node->GetType(), TabCollectionNode::Type::kSplitTab);
+  // TODO(crbug.com/442568605): Verify split tab view is created.
+  ASSERT_EQ(split_node->children().size(), 2u);
+  EXPECT_EQ(split_node->children()[0]->GetType(),
+            TabCollectionNode::Type::kTab);
+  // TODO(crbug.com/442567140): Verify tab view is created.
+  EXPECT_EQ(split_node->children()[1]->GetType(),
+            TabCollectionNode::Type::kTab);
+  // TODO(crbug.com/442567140): Verify tab view is created.
+}
+
 IN_PROC_BROWSER_TEST_F(TabCollectionNodeBrowserTest,
                        RootNodePopulatesWithTabs_ViewHierarchy) {
   AppendTab();
@@ -385,16 +457,30 @@
   ASSERT_EQ(parent_view->children().size(), 1u);
   const auto root_node_view = parent_view->children()[0];
 
-  // The root_node_view should have two children, the pinned and unpinned views.
-  ASSERT_EQ(root_node_view->children().size(), 2u);
-  const auto pinned_node_view = root_node_view->children()[0];
-  const auto unpinned_node_view = root_node_view->children()[1];
+  // The root_node_view should have three children, the pinned and unpinned
+  // views and a separator.
+  ASSERT_EQ(root_node_view->children().size(), 3u);
+  const auto pinned_node_scroll_view = root_node_view->children()[0];
+  ASSERT_TRUE(
+      views::IsViewClass<views::Separator>(root_node_view->children()[1]));
+  const auto unpinned_node_scroll_view = root_node_view->children()[2];
 
-  // The pinned_node_view should have no children.
-  ASSERT_EQ(pinned_node_view->children().size(), 0u);
+  // The pinned_node_scroll_view's contents should have no children.
+  ASSERT_TRUE(views::IsViewClass<views::ScrollView>(pinned_node_scroll_view));
+  ASSERT_EQ(static_cast<views::ScrollView*>(pinned_node_scroll_view)
+                ->contents()
+                ->children()
+                .size(),
+            0u);
 
-  // The unpinned_node_view should have two children, the two tab views.
-  ASSERT_EQ(unpinned_node_view->children().size(), 2u);
+  // The unpinned_node_scroll_view's contents should have two children, the two
+  // tab views.
+  ASSERT_TRUE(views::IsViewClass<views::ScrollView>(unpinned_node_scroll_view));
+  ASSERT_EQ(static_cast<views::ScrollView*>(unpinned_node_scroll_view)
+                ->contents()
+                ->children()
+                .size(),
+            2u);
 }
 
 namespace {
@@ -461,9 +547,23 @@
   ASSERT_EQ(unpinned_node_view->children().size(), 2u);
 }
 
+namespace {
+
+std::unique_ptr<views::View> CreateView(TabCollectionNode* node) {
+  auto view = std::make_unique<views::View>();
+  node->set_add_child_to_node(base::BindRepeating(
+      static_cast<views::View* (views::View::*)(std::unique_ptr<views::View>)>(
+          &views::View::AddChildView),
+      base::Unretained(view.get())));
+  return view;
+}
+
+}  // namespace
+
 IN_PROC_BROWSER_TEST_F(TabCollectionNodeBrowserTest, GetDirectChildren) {
   AppendTab();
   auto parent_view = std::make_unique<views::View>();
+  TabCollectionNode::SetViewFactoryForTesting(base::BindRepeating(&CreateView));
 
   RootTabCollectionNode root_node(
       browser()
@@ -499,6 +599,7 @@
                        CollectionReturnsOnlyCollectionItems) {
   AppendTab();
   auto parent_view = std::make_unique<views::View>();
+  TabCollectionNode::SetViewFactoryForTesting(base::BindRepeating(&CreateView));
   views::View* non_collection_view =
       parent_view->AddChildView(std::make_unique<views::View>());
 
diff --git a/chrome/browser/ui/views/tabs/vertical/vertical_pinned_tab_container_view.cc b/chrome/browser/ui/views/tabs/vertical/vertical_pinned_tab_container_view.cc
index becf02f..1430fc8 100644
--- a/chrome/browser/ui/views/tabs/vertical/vertical_pinned_tab_container_view.cc
+++ b/chrome/browser/ui/views/tabs/vertical/vertical_pinned_tab_container_view.cc
@@ -4,7 +4,9 @@
 
 #include "chrome/browser/ui/views/tabs/vertical/vertical_pinned_tab_container_view.h"
 
+#include "base/functional/callback_forward.h"
 #include "chrome/browser/ui/layout_constants.h"
+#include "chrome/browser/ui/views/tabs/vertical/tab_collection_node.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/color/color_id.h"
 #include "ui/gfx/geometry/rect.h"
@@ -17,8 +19,13 @@
 constexpr int kTabVerticalPadding = 4;
 }  // namespace
 
-VerticalPinnedTabContainerView::VerticalPinnedTabContainerView() {
+VerticalPinnedTabContainerView::VerticalPinnedTabContainerView(
+    TabCollectionNode* collection_node)
+    : collection_node_(collection_node) {
   SetLayoutManager(std::make_unique<views::DelegatingLayoutManager>(this));
+  node_destroyed_subscription_ = collection_node_->RegisterWillDestroyCallback(
+      base::BindOnce(&VerticalPinnedTabContainerView::ResetCollectionNode,
+                     base::Unretained(this)));
 }
 
 VerticalPinnedTabContainerView::~VerticalPinnedTabContainerView() = default;
@@ -29,15 +36,15 @@
   int total_width = 0;
   int total_height = 0;
 
+  const auto children = collection_node_->GetDirectChildren();
+
   int x = 0;
   int y = 0;
-  // TODO(crbug.com/441086907): Update all |children()| callers in this method
-  // to instead read from the TabCollectionNode.
-  int children_on_row = children().size();
+  int children_on_row = children.size();
 
   // Child width will be uniform and match the largest child's width.
   int child_width = 0;
-  for (views::View* child : children()) {
+  for (auto* child : children) {
     // TODO(corising): look into caching this value and only recomputing if the
     // children change.
     child_width = std::max(child_width, child->GetPreferredSize().width());
@@ -59,7 +66,7 @@
   }
 
   int row_index = 0;
-  for (views::View* child : children()) {
+  for (auto* child : children) {
     gfx::Rect bounds = gfx::Rect(child->GetPreferredSize());
     bounds.set_width(child_width);
     if (row_index != 0) {
@@ -82,5 +89,9 @@
   return layouts;
 }
 
+void VerticalPinnedTabContainerView::ResetCollectionNode() {
+  collection_node_ = nullptr;
+}
+
 BEGIN_METADATA(VerticalPinnedTabContainerView)
 END_METADATA
diff --git a/chrome/browser/ui/views/tabs/vertical/vertical_pinned_tab_container_view.h b/chrome/browser/ui/views/tabs/vertical/vertical_pinned_tab_container_view.h
index 1cbcfac..67f5c60 100644
--- a/chrome/browser/ui/views/tabs/vertical/vertical_pinned_tab_container_view.h
+++ b/chrome/browser/ui/views/tabs/vertical/vertical_pinned_tab_container_view.h
@@ -5,17 +5,20 @@
 #ifndef CHROME_BROWSER_UI_VIEWS_TABS_VERTICAL_VERTICAL_PINNED_TAB_CONTAINER_VIEW_H_
 #define CHROME_BROWSER_UI_VIEWS_TABS_VERTICAL_VERTICAL_PINNED_TAB_CONTAINER_VIEW_H_
 
+#include "base/callback_list.h"
 #include "ui/base/metadata/metadata_header_macros.h"
 #include "ui/views/layout/delegating_layout_manager.h"
 #include "ui/views/view.h"
 
+class TabCollectionNode;
+
 // Container for the vertical tabstrip's pinned tabs.
 class VerticalPinnedTabContainerView : public views::View,
                                        public views::LayoutDelegate {
   METADATA_HEADER(VerticalPinnedTabContainerView, views::View)
 
  public:
-  VerticalPinnedTabContainerView();
+  explicit VerticalPinnedTabContainerView(TabCollectionNode* collection_node);
   VerticalPinnedTabContainerView(const VerticalPinnedTabContainerView&) =
       delete;
   VerticalPinnedTabContainerView& operator=(
@@ -25,6 +28,13 @@
   // LayoutDelegate:
   views::ProposedLayout CalculateProposedLayout(
       const views::SizeBounds& size_bounds) const override;
+
+ private:
+  void ResetCollectionNode();
+
+  raw_ptr<TabCollectionNode> collection_node_;
+
+  base::CallbackListSubscription node_destroyed_subscription_;
 };
 
 #endif  // CHROME_BROWSER_UI_VIEWS_TABS_VERTICAL_VERTICAL_PINNED_TAB_CONTAINER_VIEW_H_
diff --git a/chrome/browser/ui/views/tabs/vertical/vertical_tab_strip_view.cc b/chrome/browser/ui/views/tabs/vertical/vertical_tab_strip_view.cc
index 3f25be0d..0c1ef88a 100644
--- a/chrome/browser/ui/views/tabs/vertical/vertical_tab_strip_view.cc
+++ b/chrome/browser/ui/views/tabs/vertical/vertical_tab_strip_view.cc
@@ -8,6 +8,7 @@
 #include "base/functional/bind.h"
 #include "chrome/browser/ui/color/chrome_color_id.h"
 #include "chrome/browser/ui/layout_constants.h"
+#include "chrome/browser/ui/views/tabs/vertical/tab_collection_node.h"
 #include "chrome/browser/ui/views/tabs/vertical/vertical_pinned_tab_container_view.h"
 #include "chrome/browser/ui/views/tabs/vertical/vertical_unpinned_tab_container_view.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
@@ -33,24 +34,23 @@
 }
 }  // namespace
 
-VerticalTabStripView::VerticalTabStripView() {
+VerticalTabStripView::VerticalTabStripView(TabCollectionNode* collection_node) {
   SetLayoutManager(std::make_unique<views::DelegatingLayoutManager>(this));
 
-  views::ScrollView* pinned_tabs_scroll_view =
+  pinned_tabs_scroll_view_ =
       AddChildView(std::make_unique<views::ScrollView>());
-  SetScrollViewProperties(pinned_tabs_scroll_view);
-  pinned_tabs_container_ = pinned_tabs_scroll_view->SetContents(
-      std::make_unique<VerticalPinnedTabContainerView>());
+  SetScrollViewProperties(pinned_tabs_scroll_view_);
 
   auto tabs_separator = std::make_unique<views::Separator>();
   tabs_separator->SetColorId(kColorTabDividerFrameActive);
   tabs_separator_ = AddChildView(std::move(tabs_separator));
 
-  views::ScrollView* unpinned_tabs_scroll_view =
+  unpinned_tabs_scroll_view_ =
       AddChildView(std::make_unique<views::ScrollView>());
-  SetScrollViewProperties(unpinned_tabs_scroll_view);
-  unpinned_tabs_container_ = unpinned_tabs_scroll_view->SetContents(
-      std::make_unique<VerticalUnpinnedTabContainerView>());
+  SetScrollViewProperties(unpinned_tabs_scroll_view_);
+
+  collection_node->set_add_child_to_node(base::BindRepeating(
+      &VerticalTabStripView::AddScrollViewContents, base::Unretained(this)));
 }
 
 VerticalTabStripView::~VerticalTabStripView() = default;
@@ -77,16 +77,14 @@
   }
 
   // Place the pinned container.
-  views::ScrollView* pinned_tabs_scroll_view =
-      views::ScrollView::GetScrollViewForContents(pinned_tabs_container_.get());
   gfx::Rect pinned_container_bounds(
       0, y, tab_container_size_bounds.width().value(),
-      pinned_tabs_scroll_view->GetPreferredSize(tab_container_size_bounds)
+      pinned_tabs_scroll_view_->GetPreferredSize(tab_container_size_bounds)
           .height());
   pinned_container_bounds.set_height(
       std::min(pinned_container_bounds.height(), (remaining_height / 2)));
-  layouts.child_layouts.emplace_back(pinned_tabs_scroll_view,
-                                     pinned_tabs_scroll_view->GetVisible(),
+  layouts.child_layouts.emplace_back(pinned_tabs_scroll_view_.get(),
+                                     pinned_tabs_scroll_view_->GetVisible(),
                                      pinned_container_bounds);
 
   remaining_height -= pinned_container_bounds.height();
@@ -108,11 +106,8 @@
   // Place the unpinned container.
   gfx::Rect unpinned_container_bounds(
       0, y, tab_container_size_bounds.width().value(), remaining_height);
-  views::ScrollView* unpinned_tabs_scroll_view =
-      views::ScrollView::GetScrollViewForContents(
-          unpinned_tabs_container_.get());
-  layouts.child_layouts.emplace_back(unpinned_tabs_scroll_view,
-                                     unpinned_tabs_scroll_view->GetVisible(),
+  layouts.child_layouts.emplace_back(unpinned_tabs_scroll_view_.get(),
+                                     unpinned_tabs_scroll_view_->GetVisible(),
                                      unpinned_container_bounds);
 
   layouts.host_size =
@@ -120,11 +115,38 @@
   return layouts;
 }
 
+VerticalPinnedTabContainerView*
+VerticalTabStripView::GetPinnedTabsContainerForTesting() {
+  if (views::View* contents = pinned_tabs_scroll_view_->contents()) {
+    return static_cast<VerticalPinnedTabContainerView*>(contents);
+  }
+  return nullptr;
+}
+
+VerticalUnpinnedTabContainerView*
+VerticalTabStripView::GetUnpinnedTabsContainerForTesting() {
+  if (views::View* contents = unpinned_tabs_scroll_view_->contents()) {
+    return static_cast<VerticalUnpinnedTabContainerView*>(contents);
+  }
+  return nullptr;
+}
+
 void VerticalTabStripView::SetCollapsedState(bool is_collapsed) {
   if (is_collapsed != tabs_separator_->GetVisible()) {
     tabs_separator_->SetVisible(is_collapsed);
   }
 }
 
+views::View* VerticalTabStripView::AddScrollViewContents(
+    std::unique_ptr<views::View> view) {
+  if (views::IsViewClass<VerticalUnpinnedTabContainerView>(view.get())) {
+    return unpinned_tabs_scroll_view_->SetContents(std::move(view));
+  }
+  // |view| should only ever be VerticalUnpinnedTabContainerView or
+  // VerticalPinnedTabContainerView.
+  CHECK(views::IsViewClass<VerticalPinnedTabContainerView>(view.get()));
+  return pinned_tabs_scroll_view_->SetContents(std::move(view));
+}
+
 BEGIN_METADATA(VerticalTabStripView)
 END_METADATA
diff --git a/chrome/browser/ui/views/tabs/vertical/vertical_tab_strip_view.h b/chrome/browser/ui/views/tabs/vertical/vertical_tab_strip_view.h
index bb04e47..ce125596 100644
--- a/chrome/browser/ui/views/tabs/vertical/vertical_tab_strip_view.h
+++ b/chrome/browser/ui/views/tabs/vertical/vertical_tab_strip_view.h
@@ -10,10 +10,12 @@
 #include "ui/views/layout/delegating_layout_manager.h"
 #include "ui/views/view.h"
 
+class TabCollectionNode;
 class VerticalPinnedTabContainerView;
 class VerticalUnpinnedTabContainerView;
 
 namespace views {
+class ScrollView;
 class Separator;
 class View;
 }  // namespace views
@@ -24,18 +26,14 @@
   METADATA_HEADER(VerticalTabStripView, views::View)
 
  public:
-  VerticalTabStripView();
+  explicit VerticalTabStripView(TabCollectionNode* collection_node);
   VerticalTabStripView(const VerticalTabStripView&) = delete;
   VerticalTabStripView& operator=(const VerticalTabStripView&) = delete;
   ~VerticalTabStripView() override;
 
   views::Separator* tabs_separator_for_testing() { return tabs_separator_; }
-  VerticalPinnedTabContainerView* pinned_tabs_container_for_testing() {
-    return pinned_tabs_container_;
-  }
-  VerticalUnpinnedTabContainerView* unpinned_tabs_container_for_testing() {
-    return unpinned_tabs_container_;
-  }
+  VerticalPinnedTabContainerView* GetPinnedTabsContainerForTesting();
+  VerticalUnpinnedTabContainerView* GetUnpinnedTabsContainerForTesting();
 
   void SetCollapsedState(bool is_collapsed);
 
@@ -44,9 +42,11 @@
       const views::SizeBounds& size_bounds) const override;
 
  private:
-  raw_ptr<VerticalPinnedTabContainerView> pinned_tabs_container_ = nullptr;
+  views::View* AddScrollViewContents(std::unique_ptr<views::View> view);
+
+  raw_ptr<views::ScrollView> pinned_tabs_scroll_view_ = nullptr;
   raw_ptr<views::Separator> tabs_separator_ = nullptr;
-  raw_ptr<VerticalUnpinnedTabContainerView> unpinned_tabs_container_ = nullptr;
+  raw_ptr<views::ScrollView> unpinned_tabs_scroll_view_ = nullptr;
 };
 
 #endif  // CHROME_BROWSER_UI_VIEWS_TABS_VERTICAL_VERTICAL_TAB_STRIP_VIEW_H_
diff --git a/chrome/browser/ui/views/tabs/vertical/vertical_unpinned_tab_container_view.cc b/chrome/browser/ui/views/tabs/vertical/vertical_unpinned_tab_container_view.cc
index 3cd68d4..85304b1 100644
--- a/chrome/browser/ui/views/tabs/vertical/vertical_unpinned_tab_container_view.cc
+++ b/chrome/browser/ui/views/tabs/vertical/vertical_unpinned_tab_container_view.cc
@@ -4,7 +4,9 @@
 
 #include "chrome/browser/ui/views/tabs/vertical/vertical_unpinned_tab_container_view.h"
 
+#include "base/functional/callback_forward.h"
 #include "chrome/browser/ui/layout_constants.h"
+#include "chrome/browser/ui/views/tabs/vertical/tab_collection_node.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/views/layout/delegating_layout_manager.h"
@@ -16,8 +18,13 @@
 constexpr int kTabVerticalPadding = 2;
 }  // namespace
 
-VerticalUnpinnedTabContainerView::VerticalUnpinnedTabContainerView() {
+VerticalUnpinnedTabContainerView::VerticalUnpinnedTabContainerView(
+    TabCollectionNode* collection_node)
+    : collection_node_(collection_node) {
   SetLayoutManager(std::make_unique<views::DelegatingLayoutManager>(this));
+  node_destroyed_subscription_ = collection_node_->RegisterWillDestroyCallback(
+      base::BindOnce(&VerticalUnpinnedTabContainerView::ResetCollectionNode,
+                     base::Unretained(this)));
 }
 
 VerticalUnpinnedTabContainerView::~VerticalUnpinnedTabContainerView() = default;
@@ -30,9 +37,11 @@
   int horizontal_padding =
       GetLayoutConstant(VERTICAL_TAB_STRIP_HORIZONTAL_PADDING);
 
+  const auto children = collection_node_->GetDirectChildren();
+
   // Layout children in order. Children will have their preferred height and
   // fill available width.
-  for (const auto& child : children()) {
+  for (auto* child : children) {
     gfx::Rect bounds = gfx::Rect(child->GetPreferredSize());
     bounds.set_y(height);
     // If fully bounded, child views should respect width constraints and take
@@ -45,7 +54,7 @@
     width = std::max(width, bounds.width() + horizontal_padding);
   }
   // Remove excess padding if needed.
-  if (!children().empty()) {
+  if (!children.empty()) {
     height -= kTabVerticalPadding;
   }
 
@@ -53,5 +62,9 @@
   return layouts;
 }
 
+void VerticalUnpinnedTabContainerView::ResetCollectionNode() {
+  collection_node_ = nullptr;
+}
+
 BEGIN_METADATA(VerticalUnpinnedTabContainerView)
 END_METADATA
diff --git a/chrome/browser/ui/views/tabs/vertical/vertical_unpinned_tab_container_view.h b/chrome/browser/ui/views/tabs/vertical/vertical_unpinned_tab_container_view.h
index e55b8ef..6f579fd 100644
--- a/chrome/browser/ui/views/tabs/vertical/vertical_unpinned_tab_container_view.h
+++ b/chrome/browser/ui/views/tabs/vertical/vertical_unpinned_tab_container_view.h
@@ -5,18 +5,21 @@
 #ifndef CHROME_BROWSER_UI_VIEWS_TABS_VERTICAL_VERTICAL_UNPINNED_TAB_CONTAINER_VIEW_H_
 #define CHROME_BROWSER_UI_VIEWS_TABS_VERTICAL_VERTICAL_UNPINNED_TAB_CONTAINER_VIEW_H_
 
+#include "base/callback_list.h"
 #include "base/memory/raw_ptr.h"
 #include "ui/base/metadata/metadata_header_macros.h"
 #include "ui/views/layout/delegating_layout_manager.h"
 #include "ui/views/view.h"
 
+class TabCollectionNode;
+
 // Container for the vertical tabstrip's unpinned tabs.
 class VerticalUnpinnedTabContainerView : public views::View,
                                          public views::LayoutDelegate {
   METADATA_HEADER(VerticalUnpinnedTabContainerView, views::View)
 
  public:
-  VerticalUnpinnedTabContainerView();
+  explicit VerticalUnpinnedTabContainerView(TabCollectionNode* collection_node);
   VerticalUnpinnedTabContainerView(const VerticalUnpinnedTabContainerView&) =
       delete;
   VerticalUnpinnedTabContainerView& operator=(
@@ -26,6 +29,13 @@
   // LayoutDelegate:
   views::ProposedLayout CalculateProposedLayout(
       const views::SizeBounds& size_bounds) const override;
+
+ private:
+  void ResetCollectionNode();
+
+  raw_ptr<TabCollectionNode> collection_node_;
+
+  base::CallbackListSubscription node_destroyed_subscription_;
 };
 
 #endif  // CHROME_BROWSER_UI_VIEWS_TABS_VERTICAL_VERTICAL_UNPINNED_TAB_CONTAINER_VIEW_H_
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 39013354..981da97c 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -3631,6 +3631,7 @@
       "../browser/ui/views/frame/multi_contents_view_drop_target_controller_browsertest.cc",
       "../browser/ui/views/frame/system_menu_model_builder_browsertest.cc",
       "../browser/ui/views/frame/tab_strip_region_view_browsertest.cc",
+      "../browser/ui/views/frame/vertical_tab_strip_region_view_browsertest.cc",
       "../browser/ui/views/hats/hats_browsertest.cc",
       "../browser/ui/views/incognito_clear_browsing_data_dialog_browsertest.cc",
       "../browser/ui/views/infobars/confirm_infobar_custom_layout_browsertest.cc",
@@ -10463,7 +10464,6 @@
       "../browser/ui/views/frame/browser_view_unittest.cc",
       "../browser/ui/views/frame/multi_contents_drop_target_view_unittest.cc",
       "../browser/ui/views/frame/multi_contents_view_drop_target_controller_unittest.cc",
-      "../browser/ui/views/frame/vertical_tab_strip_region_view_unittest.cc",
       "../browser/ui/views/frame/web_contents_close_handler_unittest.cc",
       "../browser/ui/views/global_media_controls/cast_device_footer_view_unittest.cc",
       "../browser/ui/views/global_media_controls/cast_device_selector_view_unittest.cc",