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

#include "base/base_switches.h"
#include "base/byte_size.h"
#include "base/command_line.h"
#include "base/system/sys_info.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "chrome/browser/chrome_content_browser_client.h"
#include "chrome/common/chrome_features.h"
#include "components/site_isolation/features.h"
#include "components/site_isolation/preloaded_isolated_origins.h"
#include "components/site_isolation/site_isolation_policy.h"
#include "content/public/browser/child_process_security_policy.h"
#include "content/public/browser/site_isolation_policy.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/test_utils.h"
#include "extensions/buildflags/buildflags.h"
#include "google_apis/gaia/gaia_urls.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

#if BUILDFLAG(ENABLE_EXTENSIONS_CORE)
#include "extensions/common/extension_urls.h"
#endif

namespace {

// Some command-line switches override field trials - the tests need to be
// skipped in this case.
bool ShouldSkipBecauseOfConflictingCommandLineSwitches() {
  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
          switches::kSitePerProcess)) {
    return true;
  }

  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
          switches::kDisableSiteIsolation)) {
    return true;
  }

  return false;
}

}  // namespace

class ChromeSiteIsolationPolicyTest : public testing::Test {
 public:
  ChromeSiteIsolationPolicyTest() = default;

  ChromeSiteIsolationPolicyTest(const ChromeSiteIsolationPolicyTest&) = delete;
  ChromeSiteIsolationPolicyTest& operator=(
      const ChromeSiteIsolationPolicyTest&) = delete;

  void SetUp() override {
    // This way the test always sees the same amount of physical memory
    // (kLowMemoryDeviceThresholdMB = 512MB), regardless of how much memory is
    // available in the testing environment.
    base::CommandLine::ForCurrentProcess()->AppendSwitch(
        switches::kEnableLowEndDeviceMode);
    EXPECT_EQ(512u, base::SysInfo::AmountOfTotalPhysicalMemory().InMiB());

    mode_feature_.InitWithFeatures(
        {features::kSitePerProcess, features::kOriginKeyedProcessesByDefault},
        {});
    site_isolation::SiteIsolationPolicy::
        SetDisallowMemoryThresholdCachingForTesting(true);
    content::SiteIsolationPolicy::IgnoreOriginKeyedProcessOverridesForTesting();
  }

  void TearDown() override {
    site_isolation::SiteIsolationPolicy::
        SetDisallowMemoryThresholdCachingForTesting(false);
  }

#if BUILDFLAG(IS_ANDROID)
  // Note that this only sets the memory threshold for strict site isolation.
  void SetMemoryThreshold(const std::string& threshold) {
    threshold_feature_.InitAndEnableFeatureWithParameters(
        site_isolation::features::kSiteIsolationMemoryThresholdsAndroid,
        {{site_isolation::features::
              kStrictSiteIsolationMemoryThresholdParamName,
          threshold}});
  }
#endif  // BUILDFLAG(IS_ANDROID)

  // Note that this only sets the memory threshold for
  // kOriginKeyedProcessesByDefault isolation.
  void SetOriginMemoryThreshold(const std::string& threshold) {
    origin_threshold_feature_.InitAndEnableFeatureWithParameters(
        site_isolation::features::kOriginIsolationMemoryThreshold,
        {{site_isolation::features::kOriginIsolationMemoryThresholdParamName,
          threshold}});
  }

 private:
  content::BrowserTaskEnvironment task_environment_;
  base::test::ScopedFeatureList mode_feature_;
  base::test::ScopedFeatureList threshold_feature_;
  base::test::ScopedFeatureList origin_threshold_feature_;
};

#if BUILDFLAG(IS_ANDROID)
// kSiteIsolationMemoryThresholdsAndroid only affects Android.
// kOriginIsolationMemoryThreshold only affects Desktop.
TEST_F(ChromeSiteIsolationPolicyTest, NoAndroidIsolationBelowMemoryThreshold) {
  if (ShouldSkipBecauseOfConflictingCommandLineSwitches()) {
    return;
  }

  SetMemoryThreshold("768");
  EXPECT_FALSE(
      content::SiteIsolationPolicy::UseDedicatedProcessesForAllSites());
}

TEST_F(ChromeSiteIsolationPolicyTest, AndroidIsolationAboveMemoryThreshold) {
  if (ShouldSkipBecauseOfConflictingCommandLineSwitches()) {
    return;
  }

  SetMemoryThreshold("128");
  EXPECT_TRUE(content::SiteIsolationPolicy::UseDedicatedProcessesForAllSites());
}
#endif  // BUILDFLAG(IS_ANDROID)

TEST_F(ChromeSiteIsolationPolicyTest, NoOriginIsolationBelowMemoryThreshold) {
  if (ShouldSkipBecauseOfConflictingCommandLineSwitches()) {
    GTEST_SKIP();
  }

  SetOriginMemoryThreshold("768");
  EXPECT_FALSE(
      content::SiteIsolationPolicy::AreOriginKeyedProcessesEnabledByDefault(
          nullptr));
}

TEST_F(ChromeSiteIsolationPolicyTest, OriginIsolationAboveMemoryThreshold) {
  if (ShouldSkipBecauseOfConflictingCommandLineSwitches()) {
    GTEST_SKIP();
  }

  SetOriginMemoryThreshold("128");
  // Android browser client allows it, but
  // content::SiteIsolationPolicy::UseDedicatedProcessesForAllSites disables it
  // unless --site-per-process is specified. So, for an above-threshold case,
  // UseDedicatedProcessesForAllSites() controls the value of
  // AreOriginKeyedProcessesEnabledByDefault().
  EXPECT_EQ(
      content::SiteIsolationPolicy::UseDedicatedProcessesForAllSites(),
      content::SiteIsolationPolicy::AreOriginKeyedProcessesEnabledByDefault(
          nullptr));
}

TEST_F(ChromeSiteIsolationPolicyTest, IsolatedOriginsContainChromeOrigins) {
  if (ShouldSkipBecauseOfConflictingCommandLineSwitches()) {
    return;
  }

  content::SiteIsolationPolicy::ApplyGlobalIsolatedOrigins();

  // On Android official builds, we expect to isolate an additional set of
  // built-in origins.
  std::vector<url::Origin> expected_embedder_origins =
      site_isolation::GetBrowserSpecificBuiltInIsolatedOrigins();

  if (ChromeContentBrowserClient::DoesGaiaOriginRequireDedicatedProcess()) {
    expected_embedder_origins.push_back(GaiaUrls::GetInstance()->gaia_origin());
  }

#if BUILDFLAG(ENABLE_EXTENSIONS_CORE)
  expected_embedder_origins.push_back(
      url::Origin::Create(extension_urls::GetWebstoreLaunchURL()));
  expected_embedder_origins.push_back(
      url::Origin::Create(extension_urls::GetNewWebstoreLaunchURL()));
#endif
  auto* cpsp = content::ChildProcessSecurityPolicy::GetInstance();
  std::vector<url::Origin> isolated_origins = cpsp->GetIsolatedOrigins();
  EXPECT_EQ(expected_embedder_origins.size(), isolated_origins.size());

  // Verify that the expected embedder origins are present even though site
  // isolation has been disabled and the trial origins should not be present.
  EXPECT_THAT(expected_embedder_origins,
              ::testing::UnorderedElementsAreArray(isolated_origins));
}
