// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/profiles/profile_destroyer.h"

#include <array>
#include <vector>

#include "base/feature_list.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "build/build_config.h"
#include "build/buildflag.h"
#include "chrome/browser/browser_features.h"
#include "chrome/browser/profiles/keep_alive/profile_keep_alive_types.h"
#include "chrome/browser/profiles/keep_alive/scoped_profile_keep_alive.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/site_instance.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/test_renderer_host.h"
#include "testing/gtest/include/gtest/gtest.h"

class ProfileDestroyerTest : public testing::Test,
                             public testing::WithParamInterface<bool> {
 public:
  ProfileDestroyerTest() = default;
  ProfileDestroyerTest(const ProfileDestroyerTest&) = delete;
  ProfileDestroyerTest& operator=(const ProfileDestroyerTest&) = delete;

  void SetUp() override { ASSERT_TRUE(profile_manager_.SetUp()); }

  TestingProfile* original_profile() { return original_profile_; }
  TestingProfile* OtrProfile(size_t i) {
    return otr_profiles_.size() > i ? otr_profiles_[i] : nullptr;
  }

  void CreateOriginalProfile() {
    original_profile_ = profile_manager_.CreateTestingProfile("foo");
    original_profile_->SetProfileDestructionObserver(
        base::BindOnce(&ProfileDestroyerTest::SetOriginalProfileDestroyed,
                       base::Unretained(this)));
    original_profile_keep_alive_ = std::make_unique<ScopedProfileKeepAlive>(
        original_profile_, ProfileKeepAliveOrigin::kBrowserWindow);
  }

  void CreateOTRProfile() {
    Profile::OTRProfileID profile_id =
        (is_primary_otr_ && otr_profiles_.size() == 0)
            ? Profile::OTRProfileID::PrimaryID()
            : Profile::OTRProfileID::CreateUniqueForTesting();

    TestingProfile::Builder builder;
    builder.SetPath(original_profile_->GetPath());
    TestingProfile* otr_profile =
        builder.BuildOffTheRecord(original_profile_, profile_id);
    otr_profile->SetProfileDestructionObserver(
        base::BindOnce(&ProfileDestroyerTest::SetOTRProfileDestroyed,
                       base::Unretained(this), base::Unretained(otr_profile)));
    otr_profiles_.push_back(otr_profile);
  }

  void SetOriginalProfileDestroyed() { original_profile_ = nullptr; }
  void SetOTRProfileDestroyed(TestingProfile* destroyed_profile) {
    for (auto& profile : otr_profiles_) {
      if (profile == destroyed_profile) {
        profile = nullptr;
      }
    }
  }

  // Creates a render process host based on a new site instance given the
  // |profile| and mark it as used. Returns a reference to it.
  content::RenderProcessHost* CreatedRendererProcessHost(Profile* profile) {
    site_instances_.emplace_back(content::SiteInstance::Create(profile));

    content::RenderProcessHost* rph =
        site_instances_.back()->GetOrCreateProcessForTesting();
    EXPECT_TRUE(rph);
    rph->SetIsUsed();
    return rph;
  }

  void StopKeepingAliveOriginalProfile() {
    original_profile_keep_alive_.reset();
  }

  // Destroying profile is still not universally supported. We need to disable
  // some tests, because it isn't possible to start destroying the profile.
  bool IsScopedProfileKeepAliveSupported() {
#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_CHROMEOS)
    return false;
#else
    return base::FeatureList::IsEnabled(
        features::kDestroyProfileOnBrowserClose);
#endif
  }

 protected:
  const bool is_primary_otr_ = GetParam();

  raw_ptr<TestingProfile> original_profile_;
  std::vector<raw_ptr<TestingProfile>> otr_profiles_;

  content::BrowserTaskEnvironment task_environment_;
  TestingProfileManager profile_manager_{TestingBrowserProcess::GetGlobal()};
  content::RenderViewHostTestEnabler rvh_test_enabler_;

  std::unique_ptr<ScopedProfileKeepAlive> original_profile_keep_alive_;
  std::vector<scoped_refptr<content::SiteInstance>> site_instances_;
};

TEST_P(ProfileDestroyerTest, DestroyOriginalProfileImmediately) {
  if (!IsScopedProfileKeepAliveSupported())
    return;
  CreateOriginalProfile();
  CreateOTRProfile();

  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(original_profile());
  EXPECT_TRUE(OtrProfile(0));

  StopKeepingAliveOriginalProfile();
  EXPECT_TRUE(original_profile());
  EXPECT_TRUE(OtrProfile(0));

  // This doesn't really match real-world scenarios, because TestingProfile is
  // different from OffTheRecordProfileImpl. The real impl acquires a keepalive
  // on the parent profile, whereas OTR TestingProfile doesn't do that.
  base::RunLoop().RunUntilIdle();
  EXPECT_FALSE(original_profile());
  EXPECT_FALSE(OtrProfile(0));
}

TEST_P(ProfileDestroyerTest, DestroyOriginalProfileDeferedByRenderProcessHost) {
  if (!IsScopedProfileKeepAliveSupported())
    return;
  CreateOriginalProfile();
  CreateOTRProfile();
  content::RenderProcessHost* render_process_host =
      CreatedRendererProcessHost(original_profile());

  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(original_profile());
  EXPECT_TRUE(OtrProfile(0));

  // The original profile is not destroyed, because of the RenderProcessHost.
  StopKeepingAliveOriginalProfile();
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(original_profile());
  EXPECT_TRUE(OtrProfile(0));

  // Releasing the RenderProcessHost triggers the deletion of the Profile. It
  // happens in a posted task.
  render_process_host->Cleanup();
  EXPECT_TRUE(original_profile());
  EXPECT_TRUE(OtrProfile(0));
  base::RunLoop().RunUntilIdle();
  EXPECT_FALSE(original_profile());
  EXPECT_FALSE(OtrProfile(0));
}

TEST_P(ProfileDestroyerTest,
       DestroyOriginalProfileDeferedByOffTheRecordRenderProcessHost) {
  if (!IsScopedProfileKeepAliveSupported())
    return;
  CreateOriginalProfile();
  CreateOTRProfile();
  content::RenderProcessHost* render_process_host =
      CreatedRendererProcessHost(OtrProfile(0));

  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(original_profile());
  EXPECT_TRUE(OtrProfile(0));

  // The original profile is not destroyed, because of the RenderProcessHost.
  StopKeepingAliveOriginalProfile();
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(original_profile());
  EXPECT_TRUE(OtrProfile(0));

  // Releasing the RenderProcessHost triggers the deletion of the Profile. It
  // happens in a posted task.
  render_process_host->Cleanup();
  EXPECT_TRUE(original_profile());
  EXPECT_TRUE(OtrProfile(0));
  base::RunLoop().RunUntilIdle();
  EXPECT_FALSE(original_profile());
  EXPECT_FALSE(OtrProfile(0));
}

TEST_P(ProfileDestroyerTest,
       DetroyBothProfileDeferedByMultipleRenderProcessHost) {
  if (!IsScopedProfileKeepAliveSupported())
    return;
  CreateOriginalProfile();
  CreateOTRProfile();
  content::RenderProcessHost* rph_otr_profile =
      CreatedRendererProcessHost(OtrProfile(0));
  content::RenderProcessHost* rph_original_profile =
      CreatedRendererProcessHost(original_profile());

  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(original_profile());
  EXPECT_TRUE(OtrProfile(0));

  // No profile are destroyed, because of the RenderProcessHosts.
  StopKeepingAliveOriginalProfile();
  ProfileDestroyer::DestroyOTRProfileWhenAppropriate(OtrProfile(0));
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(original_profile());
  EXPECT_TRUE(OtrProfile(0));

  // Release the first process. It causes the associated profile to be released.
  // This happens in a posted task.
  rph_otr_profile->Cleanup();
  EXPECT_TRUE(original_profile());
  EXPECT_TRUE(OtrProfile(0));
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(original_profile());
  EXPECT_FALSE(OtrProfile(0));

  // Release the second process. It causes the associated profile to be
  // released. This happens in a posted task.
  rph_original_profile->Cleanup();
  EXPECT_TRUE(original_profile());
  EXPECT_FALSE(OtrProfile(0));
  base::RunLoop().RunUntilIdle();
  EXPECT_FALSE(original_profile());
  EXPECT_FALSE(OtrProfile(0));
}

// Expect immediate OTR profile destruction when requested.
TEST_P(ProfileDestroyerTest, ImmediateOTRProfileDestructionNowWithNoHost) {
  CreateOriginalProfile();
  CreateOTRProfile();
  EXPECT_TRUE(original_profile());
  EXPECT_TRUE(OtrProfile(0));

  // Ask for immediate destruction of OTR profile with no hosts and expect
  // immediate destruction.
  ProfileDestroyer::DestroyOTRProfileImmediately(OtrProfile(0));
  EXPECT_FALSE(OtrProfile(0));
}

#if defined(GTEST_HAS_DEATH_TEST)
// Expect immediate OTR profile destruction when requested.
TEST_P(ProfileDestroyerTest, CrashOTRProfileDestructionNowWithHosts) {
  CreateOriginalProfile();
  CreateOTRProfile();
  EXPECT_TRUE(original_profile());
  EXPECT_TRUE(OtrProfile(0));

  CreatedRendererProcessHost(OtrProfile(0));

  // Immediate destruction of OTR profile with hosts will crash.
  EXPECT_DEATH(ProfileDestroyer::DestroyOTRProfileImmediately(OtrProfile(0)),
               "");
}
#endif  // defined(GTEST_HAS_DEATH_TEST)

// Expect immediate OTR profile destruction when no pending renderer
// process host exists.
TEST_P(ProfileDestroyerTest, ImmediateOTRProfileDestructionWithNoHosts) {
  CreateOriginalProfile();
  CreateOTRProfile();
  EXPECT_TRUE(original_profile());
  EXPECT_TRUE(OtrProfile(0));

  // Ask for destruction of OTR profile, and expect immediate destruction.
  ProfileDestroyer::DestroyOTRProfileWhenAppropriate(OtrProfile(0));
  EXPECT_FALSE(OtrProfile(0));
}

// Expect pending renderer process hosts delay OTR profile destruction.
TEST_P(ProfileDestroyerTest, DelayedOTRProfileDestruction) {
  CreateOriginalProfile();
  CreateOTRProfile();

  // Create two render process hosts.
  content::RenderProcessHost* render_process_host1 =
      CreatedRendererProcessHost(OtrProfile(0));
  content::RenderProcessHost* render_process_host2 =
      CreatedRendererProcessHost(OtrProfile(0));

  // Ask for destruction of OTR profile, but expect it to be delayed.
  ProfileDestroyer::DestroyOTRProfileWhenAppropriate(OtrProfile(0));
  EXPECT_TRUE(OtrProfile(0));

  // Destroy the first pending render process host, and expect it not to destroy
  // the OTR profile.
  render_process_host1->Cleanup();
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(OtrProfile(0));

  // Destroy the other renderer process, and expect destruction of OTR
  // profile.
  render_process_host2->Cleanup();
  base::RunLoop().RunUntilIdle();
  EXPECT_FALSE(OtrProfile(0));
}

TEST_P(ProfileDestroyerTest, RenderProcessAddedAfterDestroyRequested) {
  if (!IsScopedProfileKeepAliveSupported())
    return;
  CreateOriginalProfile();

  content::RenderProcessHost* render_process_host_1 =
      CreatedRendererProcessHost(original_profile());
  StopKeepingAliveOriginalProfile();

  EXPECT_TRUE(original_profile());
  content::RenderProcessHost* render_process_host_2 =
      CreatedRendererProcessHost(original_profile());

  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(original_profile());  // Waiting for 2 processes to be released

  render_process_host_1->Cleanup();
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(original_profile());  // Waiting for 1 process to be released.

  render_process_host_2->Cleanup();
  base::RunLoop().RunUntilIdle();
  EXPECT_FALSE(original_profile());  // Destroyed.
}

// Regression test for:
// https://crbug.com/40059994#comment12
TEST_P(ProfileDestroyerTest, DestructionRequestedTwiceWhileDelayedOTRProfile) {
  CreateOriginalProfile();
  CreateOTRProfile();

  content::RenderProcessHost* render_process_host =
      CreatedRendererProcessHost(OtrProfile(0));

  ProfileDestroyer::DestroyOTRProfileWhenAppropriate(OtrProfile(0));
  EXPECT_TRUE(OtrProfile(0));
  ProfileDestroyer::DestroyOTRProfileWhenAppropriate(OtrProfile(0));
  EXPECT_TRUE(OtrProfile(0));

  render_process_host->Cleanup();
  base::RunLoop().RunUntilIdle();
  EXPECT_FALSE(OtrProfile(0));
}

// Regression for: https://crbug.com/40236665
// When two OTR profile are associated with the same original profile,
// requesting the destruction of one, was incorrectly causing the
// ProfileDestroyer to wait for the destruction of the RenderProcessHost of the
// second OTR profile.
TEST_P(ProfileDestroyerTest, MultipleOTRPRofile) {
  CreateOriginalProfile();
  CreateOTRProfile();
  CreateOTRProfile();
  CreateOTRProfile();

  // Create a renderer process associated with every OTR profiles.
  std::array<content::RenderProcessHost*, 3> render_process_host = {
      CreatedRendererProcessHost(OtrProfile(0)),
      CreatedRendererProcessHost(OtrProfile(1)),
      CreatedRendererProcessHost(OtrProfile(2)),
  };

  // Ask for the destruction of two of them. The destruction is delayed, because
  // they are kept alive by two RenderProcessHost depending on them.
  ProfileDestroyer::DestroyOTRProfileWhenAppropriate(OtrProfile(0));
  ProfileDestroyer::DestroyOTRProfileWhenAppropriate(OtrProfile(1));
  EXPECT_TRUE(original_profile());
  EXPECT_TRUE(OtrProfile(0));
  EXPECT_TRUE(OtrProfile(1));
  EXPECT_TRUE(OtrProfile(2));

  // Destroy the RenderProcessHost keeping alive the first OTR profile.
  render_process_host[0]->Cleanup();
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(original_profile());
  EXPECT_FALSE(OtrProfile(0));
  EXPECT_TRUE(OtrProfile(1));
  EXPECT_TRUE(OtrProfile(2));

  // Destroy the RenderProcessHost keeping alive the second OTR profile.
  render_process_host[1]->Cleanup();
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(original_profile());
  EXPECT_FALSE(OtrProfile(0));
  EXPECT_FALSE(OtrProfile(1));
  EXPECT_TRUE(OtrProfile(2));

  // Destroy the third RenderProcessHost.
  render_process_host[2]->Cleanup();
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(original_profile());
  EXPECT_FALSE(OtrProfile(0));
  EXPECT_FALSE(OtrProfile(1));
  EXPECT_TRUE(OtrProfile(2));

  // Allow the deletion of the last OTR profile:
  ProfileDestroyer::DestroyOTRProfileWhenAppropriate(OtrProfile(2));
  EXPECT_TRUE(original_profile());
  EXPECT_FALSE(OtrProfile(0));
  EXPECT_FALSE(OtrProfile(1));
  EXPECT_FALSE(OtrProfile(2));
}

#if defined(GTEST_HAS_DEATH_TEST)
// Crash if original profile has hosts at shutdown.
TEST_P(ProfileDestroyerTest, CrashShutdownAllPendingProfilesOriginalWithHosts) {
  CreateOriginalProfile();
  EXPECT_TRUE(original_profile());
  CreatedRendererProcessHost(original_profile());

  StopKeepingAliveOriginalProfile();
  EXPECT_TRUE(original_profile());

  EXPECT_DEATH(ProfileDestroyer::DestroyPendingProfilesForShutdown(), "");
}

// Crash if off-the-record profile has hosts at shutdown.
TEST_P(ProfileDestroyerTest, CrashShutdownAllPendingProfilesOTRWithHosts) {
  CreateOriginalProfile();
  CreateOTRProfile();
  EXPECT_TRUE(original_profile());
  EXPECT_TRUE(OtrProfile(0));
  CreatedRendererProcessHost(OtrProfile(0));

  StopKeepingAliveOriginalProfile();
  EXPECT_TRUE(original_profile());
  EXPECT_TRUE(OtrProfile(0));

  // Ask for destruction of OTR profile with hosts and expect it will crash.
  EXPECT_DEATH(ProfileDestroyer::DestroyPendingProfilesForShutdown(), "");
}
#endif  // defined(GTEST_HAS_DEATH_TEST)

INSTANTIATE_TEST_SUITE_P(AllOTRProfileTypes,
                         ProfileDestroyerTest,
                         /*is_primary_otr=*/testing::Bool());
