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

#include "components/site_isolation/site_isolation_policy.h"

#include "base/base_switches.h"
#include "base/byte_size.h"
#include "base/command_line.h"
#include "base/json/values_util.h"
#include "base/memory/raw_ptr.h"
#include "base/no_destructor.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/system/sys_info.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/mock_entropy_provider.h"
#include "base/test/scoped_feature_list.h"
#include "base/time/time.h"
#include "build/branding_buildflags.h"
#include "build/build_config.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/prefs/testing_pref_service.h"
#include "components/site_isolation/features.h"
#include "components/site_isolation/pref_names.h"
#include "components/site_isolation/preloaded_isolated_origins.h"
#include "components/user_prefs/user_prefs.h"
#include "components/variations/variations_switches.h"
#include "content/public/browser/child_process_security_policy.h"
#include "content/public/browser/site_instance.h"
#include "content/public/browser/site_isolation_policy.h"
#include "content/public/common/content_client.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/navigation_simulator.h"
#include "content/public/test/test_browser_context.h"
#include "content/public/test/test_renderer_host.h"
#include "content/public/test/test_utils.h"
#include "content/public/test/web_contents_tester.h"
#include "net/http/http_response_headers.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace site_isolation {
namespace {

using IsolatedOriginSource =
    content::ChildProcessSecurityPolicy::IsolatedOriginSource;

// 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;
}

// Simulate navigation to a URL that serves an Origin-Agent-Cluster header.
std::unique_ptr<content::NavigationSimulator>
SimulateNavigationToURLWithOriginAgentClusterHeader(
    GURL url,
    content::WebContents* web_contents) {
  std::unique_ptr<content::NavigationSimulator> simulator =
      content::NavigationSimulator::CreateBrowserInitiated(url, web_contents);
  simulator->Start();
  auto response_headers =
      base::MakeRefCounted<net::HttpResponseHeaders>("HTTP/1.1 200 OK");
  response_headers->SetHeader("Origin-Agent-Cluster", "?1");
  simulator->SetResponseHeaders(response_headers);
  simulator->Commit();

  return simulator;
}

}  // namespace

// Base class for site isolation tests which handles setting a
// ContentBrowserClient with logic for enabling/disabling site isolation.
class BaseSiteIsolationTest : public testing::Test {
 public:
  BaseSiteIsolationTest() {
    original_client_ = content::SetBrowserClientForTesting(&browser_client_);
    SetEnableStrictSiteIsolation(
        original_client_->ShouldEnableStrictSiteIsolation());
  }

  ~BaseSiteIsolationTest() override {
    content::SetBrowserClientForTesting(original_client_);
  }

  void SetUp() override {
    SiteIsolationPolicy::SetDisallowMemoryThresholdCachingForTesting(true);
  }

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

 protected:
  void SetEnableStrictSiteIsolation(bool enable) {
    browser_client_.strict_isolation_enabled_ = enable;
  }

 private:
  class SiteIsolationContentBrowserClient
      : public content::ContentBrowserClient {
   public:
    bool ShouldEnableStrictSiteIsolation() override {
      return strict_isolation_enabled_;
    }

    bool ShouldDisableSiteIsolation(
        content::SiteIsolationMode site_isolation_mode) override {
      return SiteIsolationPolicy::
          ShouldDisableSiteIsolationDueToMemoryThreshold(site_isolation_mode);
    }

    std::vector<url::Origin> GetOriginsRequiringDedicatedProcess() override {
      return GetBrowserSpecificBuiltInIsolatedOrigins();
    }

    bool ShouldDisableOriginIsolation() override {
      return site_isolation::SiteIsolationPolicy::
          ShouldDisableOriginIsolationDueToMemoryThreshold();
    }

    bool strict_isolation_enabled_ = false;
  };

  SiteIsolationContentBrowserClient browser_client_;
  raw_ptr<content::ContentBrowserClient> original_client_ = nullptr;
};

class OriginIsolationForJsOptExceptionsTest : public BaseSiteIsolationTest {};
// IsOriginIsolationForJsOptExceptionsEnabled returns false when any of :
//   kOriginIsolationForJsOptExceptions is disabled
//   SiteIsolationPolicy::IsStrictOriginIsolationEnabled() returns
//   false AreDynamicIsolatedOriginsEnabled() is true
//   AreOriginKeyedProcessesEnabledByDefault returns true
TEST_F(OriginIsolationForJsOptExceptionsTest,
       ReturnsFalseWhenFeatureIsDisabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndDisableFeature(
      site_isolation::features::kOriginIsolationForJsOptExceptions);
  EXPECT_FALSE(
      SiteIsolationPolicy::IsOriginIsolationForJsOptExceptionsEnabled(nullptr));
}

TEST_F(OriginIsolationForJsOptExceptionsTest, ReturnsTrueWhenFeatureEnabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeature(
      site_isolation::features::kOriginIsolationForJsOptExceptions);
  EXPECT_TRUE(
      SiteIsolationPolicy::IsOriginIsolationForJsOptExceptionsEnabled(nullptr));
}

TEST_F(OriginIsolationForJsOptExceptionsTest,
       ReturnsFalseWhenStrictOriginIsolationEnabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      {site_isolation::features::kOriginIsolationForJsOptExceptions,
       ::features::kStrictOriginIsolation},
      {});
  EXPECT_FALSE(
      SiteIsolationPolicy::IsOriginIsolationForJsOptExceptionsEnabled(nullptr));
}

TEST_F(OriginIsolationForJsOptExceptionsTest,
       ReturnsFalseWhenOriginKeyedProcessesEnabledByDefault) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      {site_isolation::features::kOriginIsolationForJsOptExceptions,
       ::features::kOriginKeyedProcessesByDefault},
      {::features::kStrictOriginIsolation});
  base::CommandLine::ForCurrentProcess()->AppendSwitch(
      switches::kSitePerProcess);
  SetEnableStrictSiteIsolation(true);
  EXPECT_FALSE(
      SiteIsolationPolicy::IsOriginIsolationForJsOptExceptionsEnabled(nullptr));
}

#if BUILDFLAG(IS_ANDROID)
class OriginIsolationForJsOptExceptionsLowMemoryTest
    : public OriginIsolationForJsOptExceptionsTest {
 public:
  void SetUp() override {
    base::CommandLine::ForCurrentProcess()->AppendSwitch(
        switches::kEnableLowEndDeviceMode);
    // Without strict site isolation, AreOriginKeyedProcessesByDefaultEnabled()
    // will always be false. This is needed to run the test on Android.
    base::CommandLine::ForCurrentProcess()->AppendSwitch(
        switches::kSitePerProcess);
    EXPECT_EQ(512u, base::SysInfo::AmountOfTotalPhysicalMemory().InMiB());
    OriginIsolationForJsOptExceptionsTest::SetUp();
  }
};

TEST_F(OriginIsolationForJsOptExceptionsLowMemoryTest,
       ReturnsFalseOnLowMemoryDevice) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      {site_isolation::features::kOriginIsolationForJsOptExceptions},
      {::features::kStrictOriginIsolation,
       ::features::kOriginKeyedProcessesByDefault});
  EXPECT_FALSE(
      SiteIsolationPolicy::IsOriginIsolationForJsOptExceptionsEnabled(nullptr));
}
#endif

// Tests with OriginKeyedProcessesByDefault enabled.
class OriginKeyedProcessesByDefaultSiteIsolationPolicyTest
    : public BaseSiteIsolationTest {
 public:
  OriginKeyedProcessesByDefaultSiteIsolationPolicyTest() {
    feature_list_.InitWithFeatures(
        /*enabled_features=*/{::features::kOriginKeyedProcessesByDefault},
        /*disabled_features=*/{});
  }

 private:
  base::test::ScopedFeatureList feature_list_;
};

// Make sure AreOriginKeyedProcessesEnabledByDefault() only returns true when
// StrictSiteIsolation is enabled.
TEST_F(OriginKeyedProcessesByDefaultSiteIsolationPolicyTest,
       RequiresStrictSiteIsolation) {
  SetEnableStrictSiteIsolation(false);
  // Even though we've disabled ShouldEnableStrictSiteIsolation via the test
  // ContentBrowserClient, if this test runs on a bot where --site-per-process
  // is specified on the command line, UseDedicatedProcessesForAllSites() will
  // still be true, which will enable AreOriginKeyedProcessesEnabledByDefault().
  EXPECT_EQ(
      content::SiteIsolationPolicy::UseDedicatedProcessesForAllSites(),
      content::SiteIsolationPolicy::AreOriginKeyedProcessesEnabledByDefault(
          nullptr));
  SetEnableStrictSiteIsolation(true);
  // When this runs on Android, the return value from
  // SiteIsolationContentBrowserClient::ShouldDisableSiteIsolation() may still
  // override our attempt to SetEnableStrictSiteIsolation(true).
  EXPECT_EQ(
      content::SiteIsolationPolicy::UseDedicatedProcessesForAllSites(),
      content::SiteIsolationPolicy::AreOriginKeyedProcessesEnabledByDefault(
          nullptr));
}

class SiteIsolationPolicyTest : public BaseSiteIsolationTest {
 public:
  explicit SiteIsolationPolicyTest(
      content::BrowserTaskEnvironment::TimeSource time_source =
          content::BrowserTaskEnvironment::TimeSource::DEFAULT)
      : task_environment_(time_source) {
    prefs_.registry()->RegisterListPref(prefs::kUserTriggeredIsolatedOrigins);
    prefs_.registry()->RegisterDictionaryPref(
        prefs::kWebTriggeredIsolatedOrigins);
    user_prefs::UserPrefs::Set(&browser_context_, &prefs_);
  }

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

 protected:
  content::BrowserContext* browser_context() { return &browser_context_; }

  PrefService* prefs() { return &prefs_; }

  content::BrowserTaskEnvironment* task_environment() {
    return &task_environment_;
  }

 private:
  content::BrowserTaskEnvironment task_environment_;
  content::TestBrowserContext browser_context_;
  TestingPrefServiceSimple prefs_;
};

class WebTriggeredIsolatedOriginsPolicyTest : public SiteIsolationPolicyTest {
 public:
  WebTriggeredIsolatedOriginsPolicyTest()
      : SiteIsolationPolicyTest(
            content::BrowserTaskEnvironment::TimeSource::MOCK_TIME) {}

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

  void PersistOrigin(const std::string& origin) {
    SiteIsolationPolicy::PersistIsolatedOrigin(
        browser_context(), url::Origin::Create(GURL(origin)),
        IsolatedOriginSource::WEB_TRIGGERED);
    task_environment()->FastForwardBy(base::Milliseconds(1));
  }

  std::vector<std::string> GetStoredOrigins() {
    std::vector<std::string> origins;
    const auto& dict = user_prefs::UserPrefs::Get(browser_context())
                           ->GetDict(prefs::kWebTriggeredIsolatedOrigins);
    for (auto pair : dict) {
      origins.push_back(pair.first);
    }
    return origins;
  }

 protected:
  void SetUp() override {
    // Set up the COOP isolation feature with persistence enabled and a maximum
    // of 3 stored sites.
    base::test::FeatureRefAndParams coop_feature = {
        ::features::kSiteIsolationForCrossOriginOpenerPolicy,
        {{::features::kSiteIsolationForCrossOriginOpenerPolicyMaxSitesParam
              .name,
          base::NumberToString(3)},
         {::features::kSiteIsolationForCrossOriginOpenerPolicyShouldPersistParam
              .name,
          "true"}}};

    std::vector<base::test::FeatureRefAndParams> enabled_features = {
        coop_feature};

#if BUILDFLAG(IS_ANDROID)
    // Some machines running this test may be below the default memory
    // threshold.  To ensure that COOP isolation is also enabled on those
    // machines, set a very low 128MB threshold.
    base::test::FeatureRefAndParams memory_threshold_feature = {
        site_isolation::features::kSiteIsolationMemoryThresholdsAndroid,
        { {site_isolation::features::
               kPartialSiteIsolationMemoryThresholdParamName,
           "128"} }};
    enabled_features.push_back(memory_threshold_feature);
#endif  // BUILDFLAG(IS_ANDROID)

    feature_list_.InitWithFeaturesAndParameters(enabled_features,
                                                /* disabled_features = */ {});

    // Disable strict site isolation to observe effects of COOP isolation.
    SetEnableStrictSiteIsolation(false);
    SiteIsolationPolicyTest::SetUp();
  }

 private:
  base::test::ScopedFeatureList feature_list_;
};

// Verify that persisting web-triggered isolated origins properly saves the
// origins to prefs and respects the maximum number of entries (3 in this
// test).
TEST_F(WebTriggeredIsolatedOriginsPolicyTest, PersistIsolatedOrigin) {
  PersistOrigin("https://foo1.com");
  PersistOrigin("https://foo2.com");
  PersistOrigin("https://foo3.com");

  EXPECT_THAT(GetStoredOrigins(),
              testing::UnorderedElementsAre(
                  "https://foo1.com", "https://foo2.com", "https://foo3.com"));

  // Adding foo4.com should evict the oldest entry (foo1.com).
  PersistOrigin("https://foo4.com");
  EXPECT_THAT(GetStoredOrigins(),
              testing::UnorderedElementsAre(
                  "https://foo2.com", "https://foo3.com", "https://foo4.com"));

  // Adding foo5.com and foo6.com should evict the next two oldest entries.
  PersistOrigin("https://foo5.com");
  PersistOrigin("https://foo6.com");
  EXPECT_THAT(GetStoredOrigins(),
              testing::UnorderedElementsAre(
                  "https://foo4.com", "https://foo5.com", "https://foo6.com"));

  // Updating the timestamp on foo5.com should keep the current three entries.
  PersistOrigin("https://foo5.com");
  EXPECT_THAT(GetStoredOrigins(),
              testing::UnorderedElementsAre(
                  "https://foo4.com", "https://foo5.com", "https://foo6.com"));

  // Adding two new entries should now evict foo4.com and foo6.com, since
  // foo5.com has a more recent timestamp.
  PersistOrigin("https://foo7.com");
  PersistOrigin("https://foo8.com");
  EXPECT_THAT(GetStoredOrigins(),
              testing::UnorderedElementsAre(
                  "https://foo5.com", "https://foo7.com", "https://foo8.com"));
}

// Verify that when origins stored in prefs contain more than the current
// maximum number of entries, we clean up older entries when adding a new one
// to go back under the size limit.
TEST_F(WebTriggeredIsolatedOriginsPolicyTest, UpdatedMaxSize) {
  // Populate the pref manually with more entries than the 3 allowed by the
  // field trial param.
  ScopedDictPrefUpdate update(
      user_prefs::UserPrefs::Get(browser_context()),
      site_isolation::prefs::kWebTriggeredIsolatedOrigins);
  base::DictValue& dict = update.Get();
  dict.Set("https://foo1.com", base::TimeToValue(base::Time::Now()));
  dict.Set("https://foo2.com", base::TimeToValue(base::Time::Now()));
  dict.Set("https://foo3.com", base::TimeToValue(base::Time::Now()));
  dict.Set("https://foo4.com", base::TimeToValue(base::Time::Now()));
  dict.Set("https://foo5.com", base::TimeToValue(base::Time::Now()));
  EXPECT_THAT(GetStoredOrigins(),
              testing::UnorderedElementsAre(
                  "https://foo1.com", "https://foo2.com", "https://foo3.com",
                  "https://foo4.com", "https://foo5.com"));

  // Now, attempt to save a new origin.  This should evict the three oldest
  // entries to make room for the new origin.
  PersistOrigin("https://foo6.com");
  EXPECT_THAT(GetStoredOrigins(),
              testing::UnorderedElementsAre(
                  "https://foo4.com", "https://foo5.com", "https://foo6.com"));
}

// Verify that when origins stored in prefs expire, we don't apply them when
// loading persisted isolated origins, and we remove them from prefs.
TEST_F(WebTriggeredIsolatedOriginsPolicyTest, Expiration) {
  // Running this test with a command-line --site-per-process flag (which might
  // be the case on some bots) conflicts with the feature configuration in this
  // test.
  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
          switches::kSitePerProcess)) {
    return;
  }

  EXPECT_TRUE(content::SiteIsolationPolicy::IsSiteIsolationForCOOPEnabled());
  EXPECT_TRUE(content::SiteIsolationPolicy::ShouldPersistIsolatedCOOPSites());

  // Persist two origins which will eventually expire.
  PersistOrigin("https://foo1.com");
  PersistOrigin("https://foo2.com");
  EXPECT_THAT(GetStoredOrigins(), testing::UnorderedElementsAre(
                                      "https://foo1.com", "https://foo2.com"));

  // Fast-forward time so we exceed the default expiration timeout.
  base::TimeDelta default_timeout =
      ::features::kSiteIsolationForCrossOriginOpenerPolicyExpirationTimeoutParam
          .default_value;
  task_environment()->FastForwardBy(default_timeout + base::Days(1));

  // foo1.com and foo2.com should still be in prefs. (Expired entries are only
  // removed when we try to load them from prefs.)
  EXPECT_THAT(GetStoredOrigins(), testing::UnorderedElementsAre(
                                      "https://foo1.com", "https://foo2.com"));

  // Persist another origin which should remain below expiration threshold.
  PersistOrigin("https://foo3.com");
  EXPECT_THAT(GetStoredOrigins(),
              testing::UnorderedElementsAre(
                  "https://foo1.com", "https://foo2.com", "https://foo3.com"));

  // Loading persisted isolated origins should only load foo3.com.  Also,
  // it should remove foo1.com and foo2.com from prefs.
  SiteIsolationPolicy::ApplyPersistedIsolatedOrigins(browser_context());

  auto* cpsp = content::ChildProcessSecurityPolicy::GetInstance();
  std::vector<url::Origin> isolated_origins = cpsp->GetIsolatedOrigins(
      IsolatedOriginSource::WEB_TRIGGERED, browser_context());
  EXPECT_THAT(isolated_origins,
              testing::UnorderedElementsAre(
                  url::Origin::Create(GURL("https://foo3.com"))));
  EXPECT_THAT(GetStoredOrigins(),
              testing::UnorderedElementsAre("https://foo3.com"));
}

// Helper class that enables site isolation for password sites.
class PasswordSiteIsolationPolicyTest : public SiteIsolationPolicyTest {
 public:
  PasswordSiteIsolationPolicyTest() = default;

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

 protected:
  void SetUp() override {
    feature_list_.InitAndEnableFeature(
        features::kSiteIsolationForPasswordSites);
    SetEnableStrictSiteIsolation(false);
    SiteIsolationPolicyTest::SetUp();
  }

 private:
  base::test::ScopedFeatureList feature_list_;
};

// Verifies that SiteIsolationPolicy::ApplyPersistedIsolatedOrigins applies
// stored isolated origins correctly when using site isolation for password
// sites.
TEST_F(PasswordSiteIsolationPolicyTest, ApplyPersistedIsolatedOrigins) {
  EXPECT_TRUE(SiteIsolationPolicy::IsIsolationForPasswordSitesEnabled());

  // Add foo.com and bar.com to stored isolated origins.
  {
    ScopedListPrefUpdate update(prefs(), prefs::kUserTriggeredIsolatedOrigins);
    base::ListValue& list = update.Get();
    list.Append("http://foo.com");
    list.Append("https://bar.com");
  }

  // New SiteInstances for foo.com and bar.com shouldn't require a dedicated
  // process to start with.  An exception is if this test runs with a
  // command-line --site-per-process flag (which might be the case on some
  // bots).  This will override the feature configuration in this test and make
  // all sites isolated.
  if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
          switches::kSitePerProcess)) {
    scoped_refptr<content::SiteInstance> foo_instance =
        content::SiteInstance::CreateForURL(browser_context(),
                                            GURL("http://foo.com/1"));
    EXPECT_FALSE(foo_instance->RequiresDedicatedProcess());

    scoped_refptr<content::SiteInstance> bar_instance =
        content::SiteInstance::CreateForURL(browser_context(),
                                            GURL("https://baz.bar.com/2"));
    EXPECT_FALSE(bar_instance->RequiresDedicatedProcess());
  }

  // Apply isolated origins and ensure that they take effect for SiteInstances
  // in new BrowsingInstances.
  base::HistogramTester histograms;
  SiteIsolationPolicy::ApplyPersistedIsolatedOrigins(browser_context());
  histograms.ExpectUniqueSample(
      "SiteIsolation.SavedUserTriggeredIsolatedOrigins.Size", 2, 1);
  {
    scoped_refptr<content::SiteInstance> foo_instance =
        content::SiteInstance::CreateForURL(browser_context(),
                                            GURL("http://foo.com/1"));
    EXPECT_TRUE(foo_instance->RequiresDedicatedProcess());

    scoped_refptr<content::SiteInstance> bar_instance =
        content::SiteInstance::CreateForURL(browser_context(),
                                            GURL("https://baz.bar.com/2"));
    EXPECT_TRUE(bar_instance->RequiresDedicatedProcess());
  }
}

// Helper class that disables strict site isolation as well as site isolation
// for password sites.
class NoPasswordSiteIsolationPolicyTest : public SiteIsolationPolicyTest {
 public:
  NoPasswordSiteIsolationPolicyTest() = default;

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

 protected:
  void SetUp() override {
    feature_list_.InitAndDisableFeature(
        features::kSiteIsolationForPasswordSites);
    SetEnableStrictSiteIsolation(false);
    SiteIsolationPolicyTest::SetUp();
  }

 private:
  base::test::ScopedFeatureList feature_list_;
};

// Verifies that SiteIsolationPolicy::ApplyPersistedIsolatedOrigins ignores
// stored isolated origins when site isolation for password sites is off.
TEST_F(NoPasswordSiteIsolationPolicyTest,
       PersistedIsolatedOriginsIgnoredWithoutPasswordIsolation) {
  // Running this test with a command-line --site-per-process flag (which might
  // be the case on some bots) doesn't make sense, as that will make all sites
  // isolated, overriding the feature configuration in this test.
  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
          switches::kSitePerProcess)) {
    return;
  }

  EXPECT_FALSE(SiteIsolationPolicy::IsIsolationForPasswordSitesEnabled());

  // Add foo.com to stored isolated origins.
  {
    ScopedListPrefUpdate update(prefs(), prefs::kUserTriggeredIsolatedOrigins);
    update->Append("http://foo.com");
  }

  // Applying saved isolated origins should have no effect, since site
  // isolation for password sites is off.
  SiteIsolationPolicy::ApplyPersistedIsolatedOrigins(browser_context());
  scoped_refptr<content::SiteInstance> foo_instance =
      content::SiteInstance::CreateForURL(browser_context(),
                                          GURL("http://foo.com/"));
  EXPECT_FALSE(foo_instance->RequiresDedicatedProcess());
}

enum class OriginIsolationProcessMemoryThreshold {
  kNone,
  k128MB,
  k768MB,
};

struct OriginIsolationMemoryThresholdBrowserTestParams {
  OriginIsolationProcessMemoryThreshold threshold;
  bool enabled;
};

class OriginIsolationMemoryThresholdBrowserTest
    : public BaseSiteIsolationTest,
      public ::testing::WithParamInterface<
          OriginIsolationMemoryThresholdBrowserTestParams> {
 public:
  OriginIsolationMemoryThresholdBrowserTest() {
    switch (GetParam().threshold) {
      case OriginIsolationProcessMemoryThreshold::kNone:
        break;
      case OriginIsolationProcessMemoryThreshold::k128MB:
        threshold_feature_.InitAndEnableFeatureWithParameters(
            features::kOriginIsolationMemoryThreshold,
            {{features::kOriginIsolationMemoryThresholdParamName, "128"}});
        break;
      case OriginIsolationProcessMemoryThreshold::k768MB:
        threshold_feature_.InitAndEnableFeatureWithParameters(
            features::kOriginIsolationMemoryThreshold,
            {{features::kOriginIsolationMemoryThresholdParamName, "768"}});
        break;
    }

    if (GetParam().enabled) {
      mode_feature_.InitAndEnableFeature(
          ::features::kOriginKeyedProcessesByDefault);
    } else {
      mode_feature_.InitAndDisableFeature(
          ::features::kOriginKeyedProcessesByDefault);
    }
  }

  OriginIsolationMemoryThresholdBrowserTest(
      const OriginIsolationMemoryThresholdBrowserTest&) = delete;
  OriginIsolationMemoryThresholdBrowserTest& operator=(
      const OriginIsolationMemoryThresholdBrowserTest&) = 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);
    // Without strict site isolation, AreOriginKeyedProcessesByDefaultEnabled()
    // will always be false. This is needed to run the test on Android.
    base::CommandLine::ForCurrentProcess()->AppendSwitch(
        switches::kSitePerProcess);
    EXPECT_EQ(512u, base::SysInfo::AmountOfTotalPhysicalMemory().InMiB());
    BaseSiteIsolationTest::SetUp();
  }

 private:
  base::test::ScopedFeatureList threshold_feature_;
  base::test::ScopedFeatureList mode_feature_;
};  // class OriginIsolationMemoryThresholdBrowserTest

TEST_P(OriginIsolationMemoryThresholdBrowserTest, IsolationThresholds) {
  // OAC opt-in is enabled by default, and since the test forces strict site
  // isolation on, we expect OAC opt-in process isolation to always work.
  bool expected_oac_optin_process = true;
#if BUILDFLAG(IS_ANDROID)
  // Android doesn't enable kOriginKeyedProcessesByDefault, so if it's on it's
  // because it was explicitly enable, in which case we don't do memory checks.
  bool expected_process_origin_isolation = GetParam().enabled;
#else
  // If enabled, two of the three threshold settings are compatible with the
  // kLowEndDeviceMode switch, which simulated 512MB or RAM.
  OriginIsolationProcessMemoryThreshold threshold = GetParam().threshold;
  bool expected_process_origin_isolation =
      GetParam().enabled &&
      (threshold == OriginIsolationProcessMemoryThreshold::kNone ||
       threshold == OriginIsolationProcessMemoryThreshold::k128MB);
#endif
  EXPECT_EQ(expected_oac_optin_process,
            content::SiteIsolationPolicy::
                IsProcessIsolationForOriginAgentClusterEnabled());
  EXPECT_EQ(
      expected_process_origin_isolation,
      content::SiteIsolationPolicy::AreOriginKeyedProcessesEnabledByDefault(
          nullptr));
}

INSTANTIATE_TEST_SUITE_P(
    All,
    OriginIsolationMemoryThresholdBrowserTest,
    testing::Values(
        OriginIsolationMemoryThresholdBrowserTestParams{
            OriginIsolationProcessMemoryThreshold::kNone, true},
        OriginIsolationMemoryThresholdBrowserTestParams{
            OriginIsolationProcessMemoryThreshold::k128MB, true},
        OriginIsolationMemoryThresholdBrowserTestParams{
            OriginIsolationProcessMemoryThreshold::k768MB, true},
        OriginIsolationMemoryThresholdBrowserTestParams{
            OriginIsolationProcessMemoryThreshold::kNone, false},
        OriginIsolationMemoryThresholdBrowserTestParams{
            OriginIsolationProcessMemoryThreshold::k128MB, false},
        OriginIsolationMemoryThresholdBrowserTestParams{
            OriginIsolationProcessMemoryThreshold::k768MB, false}),
    [](const testing::TestParamInfo<
        OriginIsolationMemoryThresholdBrowserTestParams>& info) {
      std::string threshold_str;
      switch (info.param.threshold) {
        case OriginIsolationProcessMemoryThreshold::kNone:
          threshold_str = "none";
          break;
        case OriginIsolationProcessMemoryThreshold::k128MB:
          threshold_str = "128MB";
          break;
        case OriginIsolationProcessMemoryThreshold::k768MB:
          threshold_str = "768MB";
          break;
      }
      std::string label = base::StringPrintf(
          "OriginIsolationThresholdTest_%s_Threshold_%s",
          (info.param.enabled ? "Enabled" : "Disabled"), threshold_str.c_str());
      return label;
    });

enum class SitePerProcessMemoryThreshold {
  kNone,
#if BUILDFLAG(IS_ANDROID)
  k128MB,
  k768MB,
#endif  // BUILDFLAG(IS_ANDROID)
};

enum class SitePerProcessMode {
  kDisabled,
  kEnabled,
  kIsolatedOrigin,
};

struct SitePerProcessMemoryThresholdBrowserTestParams {
  SitePerProcessMemoryThreshold threshold;
  SitePerProcessMode mode;
};

const url::Origin& GetTrialOrigin() {
  static base::NoDestructor<url::Origin> origin{
      url::Origin::Create(GURL("http://foo.com/"))};
  return *origin;
}

// Helper class to run tests on a simulated 512MB low-end device.
class SitePerProcessMemoryThresholdBrowserTest
    : public BaseSiteIsolationTest,
      public ::testing::WithParamInterface<
          SitePerProcessMemoryThresholdBrowserTestParams> {
 public:
  SitePerProcessMemoryThresholdBrowserTest() {
    // When a memory threshold is specified, set it for both strict site
    // isolation and partial site isolation modes, since these tests care about
    // both. For example, UseDedicatedProcessesForAllSites() depends on the
    // former, while isolated origins specified via field trials use the
    // latter.
    switch (GetParam().threshold) {
      case SitePerProcessMemoryThreshold::kNone:
        break;
#if BUILDFLAG(IS_ANDROID)
      case SitePerProcessMemoryThreshold::k128MB:
        threshold_feature_.InitAndEnableFeatureWithParameters(
            features::kSiteIsolationMemoryThresholdsAndroid,
            {{features::kStrictSiteIsolationMemoryThresholdParamName, "128"},
             {features::kPartialSiteIsolationMemoryThresholdParamName, "128"}});
        break;
      case SitePerProcessMemoryThreshold::k768MB:
        threshold_feature_.InitAndEnableFeatureWithParameters(
            features::kSiteIsolationMemoryThresholdsAndroid,
            {{features::kStrictSiteIsolationMemoryThresholdParamName, "768"},
             {features::kPartialSiteIsolationMemoryThresholdParamName, "768"}});
        break;
#endif  // BUILDFLAG(IS_ANDROID)
    }

    switch (GetParam().mode) {
      case SitePerProcessMode::kDisabled:
        SetEnableStrictSiteIsolation(false);
        break;
      case SitePerProcessMode::kEnabled:
        SetEnableStrictSiteIsolation(true);
        break;
      case SitePerProcessMode::kIsolatedOrigin:
        mode_feature_.InitAndEnableFeatureWithParameters(
            ::features::kIsolateOrigins,
            {{::features::kIsolateOriginsFieldTrialParamName,
              GetTrialOrigin().Serialize()}});
        break;
    }
  }

  SitePerProcessMemoryThresholdBrowserTest(
      const SitePerProcessMemoryThresholdBrowserTest&) = delete;
  SitePerProcessMemoryThresholdBrowserTest& operator=(
      const SitePerProcessMemoryThresholdBrowserTest&) = 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());

    // On Android official builds, we expect to isolate an additional set of
    // built-in origins.
    expected_embedder_origins_ = GetBrowserSpecificBuiltInIsolatedOrigins();
    BaseSiteIsolationTest::SetUp();
  }

 protected:
  content::BrowserTaskEnvironment task_environment_;

  // These are the origins we expect to be returned by
  // content::ChildProcessSecurityPolicy::GetIsolatedOrigins() even if
  // ContentBrowserClient::ShouldDisableSiteIsolation() returns true.
  std::vector<url::Origin> expected_embedder_origins_;

#if BUILDFLAG(IS_ANDROID)
  // On Android we don't expect any trial origins because the 512MB
  // physical memory used for testing is below the Android specific
  // hardcoded 1024MB memory limit that disables site isolation.
  const std::size_t kExpectedTrialOrigins = 0;
#else
  // All other platforms expect the single trial origin to be returned because
  // they don't have the memory limit that disables site isolation.
  const std::size_t kExpectedTrialOrigins = 1;
#endif

 private:
  base::test::ScopedFeatureList threshold_feature_;
  base::test::ScopedFeatureList mode_feature_;
};

using SitePerProcessMemoryThresholdBrowserTestNoIsolation =
    SitePerProcessMemoryThresholdBrowserTest;
TEST_P(SitePerProcessMemoryThresholdBrowserTestNoIsolation, NoIsolation) {
  if (ShouldSkipBecauseOfConflictingCommandLineSwitches()) {
    return;
  }

  // Isolation should be disabled given the set of parameters used to
  // instantiate these tests.
  EXPECT_FALSE(
      content::SiteIsolationPolicy::UseDedicatedProcessesForAllSites());
}

using SitePerProcessMemoryThresholdBrowserTestIsolation =
    SitePerProcessMemoryThresholdBrowserTest;
TEST_P(SitePerProcessMemoryThresholdBrowserTestIsolation, Isolation) {
  if (ShouldSkipBecauseOfConflictingCommandLineSwitches()) {
    return;
  }

  // Isolation should be enabled given the set of parameters used to
  // instantiate these tests.
  EXPECT_TRUE(content::SiteIsolationPolicy::UseDedicatedProcessesForAllSites());
}

INSTANTIATE_TEST_SUITE_P(
    NoIsolation,
    SitePerProcessMemoryThresholdBrowserTestNoIsolation,
    testing::Values(
#if BUILDFLAG(IS_ANDROID)
        // Expect no isolation on Android because 512MB physical memory
        // triggered by kEnableLowEndDeviceMode in SetUp() is below the 1024MB
        // Android specific memory limit which disables site isolation for all
        // sites.
        SitePerProcessMemoryThresholdBrowserTestParams{
            SitePerProcessMemoryThreshold::kNone, SitePerProcessMode::kEnabled},
        SitePerProcessMemoryThresholdBrowserTestParams{
            SitePerProcessMemoryThreshold::k768MB,
            SitePerProcessMode::kEnabled},
        SitePerProcessMemoryThresholdBrowserTestParams{
            SitePerProcessMemoryThreshold::k128MB,
            SitePerProcessMode::kDisabled},
        SitePerProcessMemoryThresholdBrowserTestParams{
            SitePerProcessMemoryThreshold::k768MB,
            SitePerProcessMode::kDisabled},
#endif  // BUILDFLAG(IS_ANDROID)
        SitePerProcessMemoryThresholdBrowserTestParams{
            SitePerProcessMemoryThreshold::kNone,
            SitePerProcessMode::kDisabled}));

INSTANTIATE_TEST_SUITE_P(Isolation,
                         SitePerProcessMemoryThresholdBrowserTestIsolation,
                         testing::Values(
#if BUILDFLAG(IS_ANDROID)
                             SitePerProcessMemoryThresholdBrowserTestParams{
                                 SitePerProcessMemoryThreshold::k128MB,
                                 SitePerProcessMode::kEnabled}));
#else
                             // See the note above regarding why this
                             // expectation is different on Android.
                             SitePerProcessMemoryThresholdBrowserTestParams{
                                 SitePerProcessMemoryThreshold::kNone,
                                 SitePerProcessMode::kEnabled}));
#endif

#if BUILDFLAG(IS_ANDROID)
using SitePerProcessMemoryThresholdBrowserTestNoIsolatedOrigin =
    SitePerProcessMemoryThresholdBrowserTest;
TEST_P(SitePerProcessMemoryThresholdBrowserTestNoIsolatedOrigin,
       TrialNoIsolatedOrigin) {
  if (ShouldSkipBecauseOfConflictingCommandLineSwitches()) {
    return;
  }

  content::SiteIsolationPolicy::ApplyGlobalIsolatedOrigins();

  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::IsSubsetOf(isolated_origins));

  // Verify that the trial origin is not present.
  EXPECT_THAT(isolated_origins,
              ::testing::Not(::testing::Contains(GetTrialOrigin())));
}
#endif  // BUILDFLAG(IS_ANDROID)

using SitePerProcessMemoryThresholdBrowserTestIsolatedOrigin =
    SitePerProcessMemoryThresholdBrowserTest;
TEST_P(SitePerProcessMemoryThresholdBrowserTestIsolatedOrigin,
       TrialIsolatedOrigin) {
  if (ShouldSkipBecauseOfConflictingCommandLineSwitches()) {
    return;
  }

  content::SiteIsolationPolicy::ApplyGlobalIsolatedOrigins();

  auto* cpsp = content::ChildProcessSecurityPolicy::GetInstance();
  std::vector<url::Origin> isolated_origins = cpsp->GetIsolatedOrigins();
  EXPECT_EQ(1u + expected_embedder_origins_.size(), isolated_origins.size());
  EXPECT_THAT(expected_embedder_origins_,
              ::testing::IsSubsetOf(isolated_origins));

  // Verify that the trial origin is present.
  EXPECT_THAT(isolated_origins, ::testing::Contains(GetTrialOrigin()));
}

#if BUILDFLAG(IS_ANDROID)
INSTANTIATE_TEST_SUITE_P(
    TrialNoIsolatedOrigin,
    SitePerProcessMemoryThresholdBrowserTestNoIsolatedOrigin,
    testing::Values(
        // When the memory threshold is not explicitly specified, Android uses
        // a 1900MB global memory threshold.  The 512MB simulated device memory
        // is below 1900MB, so the test origin should not be isolated.
        SitePerProcessMemoryThresholdBrowserTestParams{
            SitePerProcessMemoryThreshold::kNone,
            SitePerProcessMode::kIsolatedOrigin},
        // The 512MB simulated device memory is under the explicit 768MB memory
        // threshold below, so the test origin should not be isolated.
        SitePerProcessMemoryThresholdBrowserTestParams{
            SitePerProcessMemoryThreshold::k768MB,
            SitePerProcessMode::kIsolatedOrigin}));
#endif  // BUILDFLAG(IS_ANDROID)

INSTANTIATE_TEST_SUITE_P(
    TrialIsolatedOrigin,
    SitePerProcessMemoryThresholdBrowserTestIsolatedOrigin,
#if BUILDFLAG(IS_ANDROID)
    // The 512MB simulated device memory is above the explicit 128MB memory
    // threshold below, so the test origin should be isolated both on desktop
    // and Android.
    testing::Values(SitePerProcessMemoryThresholdBrowserTestParams{
        SitePerProcessMemoryThreshold::k128MB,
        SitePerProcessMode::kIsolatedOrigin}));
#else
    testing::Values(SitePerProcessMemoryThresholdBrowserTestParams{
        SitePerProcessMemoryThreshold::kNone,
        SitePerProcessMode::kIsolatedOrigin}));
#endif

// Helper class to run tests with password-triggered site isolation initialized
// via a regular field trial and *not* via a command-line override.  It
// creates a new field trial (with 100% probability of being in the group), and
// initializes the test class's ScopedFeatureList using it.  Two derived
// classes below control are used to initialize the feature to either enabled
// or disabled state.
class PasswordSiteIsolationFieldTrialTest : public BaseSiteIsolationTest {
 public:
  explicit PasswordSiteIsolationFieldTrialTest(bool should_enable) {
    empty_feature_scope_.InitWithEmptyFeatureAndFieldTrialLists();

    const std::string kTrialName = "PasswordSiteIsolation";
    const std::string kGroupName = "FooGroup";  // unused
    scoped_refptr<base::FieldTrial> trial =
        base::FieldTrialList::CreateFieldTrial(kTrialName, kGroupName);

    std::unique_ptr<base::FeatureList> feature_list(new base::FeatureList);
    feature_list->RegisterFieldTrialOverride(
        features::kSiteIsolationForPasswordSites.name,
        should_enable
            ? base::FeatureList::OverrideState::OVERRIDE_ENABLE_FEATURE
            : base::FeatureList::OverrideState::OVERRIDE_DISABLE_FEATURE,
        trial.get());

    feature_list_.InitWithFeatureList(std::move(feature_list));
  }

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

  void SetUp() override {
    // This test creates and tests its own field trial group, so it needs to
    // disable the field trial testing config, which might define an
    // incompatible trial name/group.
    base::CommandLine::ForCurrentProcess()->AppendSwitch(
        variations::switches::kDisableFieldTrialTestingConfig);

    // 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());
    BaseSiteIsolationTest::SetUp();
  }

 protected:
  // |empty_feature_scope_| is used to prepare an environment with empty
  // features and field trial lists.
  base::test::ScopedFeatureList empty_feature_scope_;
  // |feature_list_| is used to enable and disable features for
  // PasswordSiteIsolationFieldTrialTest.
  base::test::ScopedFeatureList feature_list_;
};

class EnabledPasswordSiteIsolationFieldTrialTest
    : public PasswordSiteIsolationFieldTrialTest {
 public:
  EnabledPasswordSiteIsolationFieldTrialTest()
      : PasswordSiteIsolationFieldTrialTest(true /* should_enable */) {}

  EnabledPasswordSiteIsolationFieldTrialTest(
      const EnabledPasswordSiteIsolationFieldTrialTest&) = delete;
  EnabledPasswordSiteIsolationFieldTrialTest& operator=(
      const EnabledPasswordSiteIsolationFieldTrialTest&) = delete;
};

class DisabledPasswordSiteIsolationFieldTrialTest
    : public PasswordSiteIsolationFieldTrialTest {
 public:
  DisabledPasswordSiteIsolationFieldTrialTest()
      : PasswordSiteIsolationFieldTrialTest(false /* should_enable */) {}

  DisabledPasswordSiteIsolationFieldTrialTest(
      const DisabledPasswordSiteIsolationFieldTrialTest&) = delete;
  DisabledPasswordSiteIsolationFieldTrialTest& operator=(
      const DisabledPasswordSiteIsolationFieldTrialTest&) = delete;
};

#if BUILDFLAG(IS_ANDROID)
TEST_F(EnabledPasswordSiteIsolationFieldTrialTest, BelowThreshold_Android) {
  if (ShouldSkipBecauseOfConflictingCommandLineSwitches()) {
    return;
  }

  // If no memory threshold is defined, password site isolation should be
  // disabled on Android, because Android defaults to a 1900MB memory threshold,
  // which is above the 512MB physical memory that this test simulates.
  EXPECT_FALSE(SiteIsolationPolicy::IsIsolationForPasswordSitesEnabled());

  // Define a memory threshold at 768MB.  Since this is above the 512MB of
  // physical memory that this test simulates, password site isolation should
  // now be disabled on Android.
  base::test::ScopedFeatureList memory_feature;
  memory_feature.InitAndEnableFeatureWithParameters(
      features::kSiteIsolationMemoryThresholdsAndroid,
      {{features::kPartialSiteIsolationMemoryThresholdParamName, "768"}});

  EXPECT_FALSE(SiteIsolationPolicy::IsIsolationForPasswordSitesEnabled());

  // Simulate enabling password site isolation from command line.  (Note that
  // InitAndEnableFeature uses ScopedFeatureList::InitFromCommandLine
  // internally, and that triggering the feature via chrome://flags follows the
  // same override path as well.)
  base::test::ScopedFeatureList password_site_isolation_feature;
  password_site_isolation_feature.InitAndEnableFeature(
      features::kSiteIsolationForPasswordSites);

  // This should override the memory threshold and enable password site
  // isolation.
  EXPECT_TRUE(SiteIsolationPolicy::IsIsolationForPasswordSitesEnabled());
}

TEST_F(EnabledPasswordSiteIsolationFieldTrialTest, AboveThreshold_Android) {
  if (ShouldSkipBecauseOfConflictingCommandLineSwitches()) {
    return;
  }

  // If no memory threshold is defined, password site isolation should be
  // disabled on Android, because Android defaults to a 1900MB memory threshold,
  // which is above the 512MB physical memory that this test simulates.
  EXPECT_FALSE(SiteIsolationPolicy::IsIsolationForPasswordSitesEnabled());

  // Define a memory threshold at 128MB.  Since this is below the 512MB of
  // physical memory that this test simulates, password site isolation should
  // still be enabled.
  base::test::ScopedFeatureList memory_feature;
  memory_feature.InitAndEnableFeatureWithParameters(
      features::kSiteIsolationMemoryThresholdsAndroid,
      {{features::kPartialSiteIsolationMemoryThresholdParamName, "128"}});

  EXPECT_TRUE(SiteIsolationPolicy::IsIsolationForPasswordSitesEnabled());

  // Simulate disabling password site isolation from command line.  (Note that
  // InitAndEnableFeature uses ScopedFeatureList::InitFromCommandLine
  // internally, and that triggering the feature via chrome://flags follows the
  // same override path as well.)  This should take precedence over the regular
  // field trial behavior.
  base::test::ScopedFeatureList password_site_isolation_feature;
  password_site_isolation_feature.InitAndDisableFeature(
      features::kSiteIsolationForPasswordSites);
  EXPECT_FALSE(SiteIsolationPolicy::IsIsolationForPasswordSitesEnabled());
}
#else  // BUILDFLAG(IS_ANDROID)
TEST_F(EnabledPasswordSiteIsolationFieldTrialTest, NonAndroid) {
  if (ShouldSkipBecauseOfConflictingCommandLineSwitches()) {
    return;
  }

  EXPECT_TRUE(SiteIsolationPolicy::IsIsolationForPasswordSitesEnabled());

  // Simulate disabling password site isolation from command line.  (Note that
  // InitAndEnableFeature uses ScopedFeatureList::InitFromCommandLine
  // internally, and that triggering the feature via chrome://flags follows the
  // same override path as well.)  This should take precedence over the regular
  // field trial behavior.
  base::test::ScopedFeatureList password_site_isolation_feature;
  password_site_isolation_feature.InitAndDisableFeature(
      features::kSiteIsolationForPasswordSites);
  EXPECT_FALSE(SiteIsolationPolicy::IsIsolationForPasswordSitesEnabled());
}
#endif

// This test verifies that when password-triggered site isolation is disabled
// via field trials but force-enabled via command line, it takes effect even
// when below the memory threshold.  See https://crbug.com/1009828.
TEST_F(DisabledPasswordSiteIsolationFieldTrialTest,
       CommandLineOverride_BelowThreshold) {
  if (ShouldSkipBecauseOfConflictingCommandLineSwitches()) {
    return;
  }

  // Password site isolation should be disabled at this point.
  EXPECT_FALSE(SiteIsolationPolicy::IsIsolationForPasswordSitesEnabled());

  // Simulate enabling password site isolation from command line.  (Note that
  // InitAndEnableFeature uses ScopedFeatureList::InitFromCommandLine
  // internally, and that triggering the feature via chrome://flags follows the
  // same override path as well.)
  base::test::ScopedFeatureList password_site_isolation_feature;
  password_site_isolation_feature.InitAndEnableFeature(
      features::kSiteIsolationForPasswordSites);

  // If no memory threshold is defined, password site isolation should be
  // enabled.
  EXPECT_TRUE(SiteIsolationPolicy::IsIsolationForPasswordSitesEnabled());

#if BUILDFLAG(IS_ANDROID)
  // Define a memory threshold at 768MB.  This is above the 512MB of physical
  // memory that this test simulates, but password site isolation should still
  // be enabled, because the test has simulated the user manually overriding
  // this feature via command line.
  base::test::ScopedFeatureList memory_feature;
  memory_feature.InitAndEnableFeatureWithParameters(
      features::kSiteIsolationMemoryThresholdsAndroid,
      {{features::kPartialSiteIsolationMemoryThresholdParamName, "768"}});

  EXPECT_TRUE(SiteIsolationPolicy::IsIsolationForPasswordSitesEnabled());
#endif  // BUILDFLAG(IS_ANDROID)
}

// Similar to the test above, but with device memory being above memory
// threshold.
TEST_F(DisabledPasswordSiteIsolationFieldTrialTest,
       CommandLineOverride_AboveThreshold) {
  if (ShouldSkipBecauseOfConflictingCommandLineSwitches()) {
    return;
  }

  EXPECT_FALSE(SiteIsolationPolicy::IsIsolationForPasswordSitesEnabled());

  base::test::ScopedFeatureList password_site_isolation_feature;
  password_site_isolation_feature.InitAndEnableFeature(
      features::kSiteIsolationForPasswordSites);

  // If no memory threshold is defined, password site isolation should be
  // enabled.
  EXPECT_TRUE(SiteIsolationPolicy::IsIsolationForPasswordSitesEnabled());

#if BUILDFLAG(IS_ANDROID)
  base::test::ScopedFeatureList memory_feature;
  memory_feature.InitAndEnableFeatureWithParameters(
      features::kSiteIsolationMemoryThresholdsAndroid,
      {{features::kPartialSiteIsolationMemoryThresholdParamName, "128"}});

  EXPECT_TRUE(SiteIsolationPolicy::IsIsolationForPasswordSitesEnabled());
#endif  // BUILDFLAG(IS_ANDROID)
}

// Helper class to run tests with strict origin isolation initialized via
// a regular field trial and *not* via a command-line override.  It creates a
// new field trial (with 100% probability of being in the group), and
// initializes the test class's ScopedFeatureList using it.  Two derived
// classes below control are used to initialize the feature to either enabled
// or disabled state.
class StrictOriginIsolationFieldTrialTest : public BaseSiteIsolationTest {
 public:
  explicit StrictOriginIsolationFieldTrialTest(bool should_enable) {
    empty_feature_scope_.InitWithEmptyFeatureAndFieldTrialLists();

    const std::string kTrialName = "StrictOriginIsolation";
    const std::string kGroupName = "FooGroup";  // unused
    scoped_refptr<base::FieldTrial> trial =
        base::FieldTrialList::CreateFieldTrial(kTrialName, kGroupName);

    std::unique_ptr<base::FeatureList> feature_list(new base::FeatureList);
    feature_list->RegisterFieldTrialOverride(
        ::features::kStrictOriginIsolation.name,
        should_enable
            ? base::FeatureList::OverrideState::OVERRIDE_ENABLE_FEATURE
            : base::FeatureList::OverrideState::OVERRIDE_DISABLE_FEATURE,
        trial.get());

    feature_list_.InitWithFeatureList(std::move(feature_list));
  }

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

  void SetUp() override {
    // This test creates and tests its own field trial group, so it needs to
    // disable the field trial testing config, which might define an
    // incompatible trial name/group.
    base::CommandLine::ForCurrentProcess()->AppendSwitch(
        variations::switches::kDisableFieldTrialTestingConfig);

    // 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());
    BaseSiteIsolationTest::SetUp();
  }

 protected:
  // |empty_feature_scope_| is used to prepare an environment with empty
  // features and field trial lists.
  base::test::ScopedFeatureList empty_feature_scope_;
  // |feature_list_| is used to enable and disable features for
  // StrictOriginIsolationFieldTrialTest.
  base::test::ScopedFeatureList feature_list_;
};

class EnabledStrictOriginIsolationFieldTrialTest
    : public StrictOriginIsolationFieldTrialTest {
 public:
  EnabledStrictOriginIsolationFieldTrialTest()
      : StrictOriginIsolationFieldTrialTest(true /* should_enable */) {}

  EnabledStrictOriginIsolationFieldTrialTest(
      const EnabledStrictOriginIsolationFieldTrialTest&) = delete;
  EnabledStrictOriginIsolationFieldTrialTest& operator=(
      const EnabledStrictOriginIsolationFieldTrialTest&) = delete;
};

class DisabledStrictOriginIsolationFieldTrialTest
    : public StrictOriginIsolationFieldTrialTest {
 public:
  DisabledStrictOriginIsolationFieldTrialTest()
      : StrictOriginIsolationFieldTrialTest(false /* should_enable */) {}

  DisabledStrictOriginIsolationFieldTrialTest(
      const DisabledStrictOriginIsolationFieldTrialTest&) = delete;
  DisabledStrictOriginIsolationFieldTrialTest& operator=(
      const DisabledStrictOriginIsolationFieldTrialTest&) = delete;
};

// Check that when strict origin isolation is enabled via a field trial, and
// the device is above the memory threshold, disabling it via the command line
// takes precedence.
TEST_F(EnabledStrictOriginIsolationFieldTrialTest,
       DisabledViaCommandLineOverride) {
  if (ShouldSkipBecauseOfConflictingCommandLineSwitches()) {
    return;
  }

#if BUILDFLAG(IS_ANDROID)
  // If no memory threshold is defined, strict origin isolation should be
  // disabled on Android, because Android defaults to a 1900MB memory threshold,
  // which is above the 512MB physical memory that this test simulates.
  EXPECT_FALSE(content::SiteIsolationPolicy::IsStrictOriginIsolationEnabled());

  // Define a memory threshold at 128MB.  Since this is below the 512MB of
  // physical memory that this test simulates, strict origin isolation should
  // now be enabled. String origin isolation should be enabled on desktop
  // platforms regardless, since they do not respect memory thresholds.
  base::test::ScopedFeatureList memory_feature;
  memory_feature.InitAndEnableFeatureWithParameters(
      features::kSiteIsolationMemoryThresholdsAndroid,
      {{features::kStrictSiteIsolationMemoryThresholdParamName, "128"}});
#endif  //  BUILDFLAG(IS_ANDROID)

  EXPECT_TRUE(content::SiteIsolationPolicy::IsStrictOriginIsolationEnabled());

  // Simulate disabling strict origin isolation from command line.  (Note that
  // InitAndEnableFeature uses ScopedFeatureList::InitFromCommandLine
  // internally, and that disabling the feature via chrome://flags follows the
  // same override path as well.)
  base::test::ScopedFeatureList strict_origin_isolation_feature;
  strict_origin_isolation_feature.InitAndDisableFeature(
      ::features::kStrictOriginIsolation);
  EXPECT_FALSE(content::SiteIsolationPolicy::IsStrictOriginIsolationEnabled());
}

// This test verifies that when strict origin isolation is disabled
// via field trials but force-enabled via command line, it takes effect even
// when below the memory threshold.  See https://crbug.com/1009828.
TEST_F(DisabledStrictOriginIsolationFieldTrialTest,
       EnabledViaCommandLineOverride_BelowThreshold) {
  if (ShouldSkipBecauseOfConflictingCommandLineSwitches()) {
    return;
  }

  // Strict origin isolation should be disabled at this point.
  EXPECT_FALSE(content::SiteIsolationPolicy::IsStrictOriginIsolationEnabled());

  // Simulate enabling strict origin isolation from command line.  (Note that
  // InitAndEnableFeature uses ScopedFeatureList::InitFromCommandLine
  // internally, and that triggering the feature via chrome://flags follows the
  // same override path as well.)
  base::test::ScopedFeatureList strict_origin_isolation_feature;
  strict_origin_isolation_feature.InitAndEnableFeature(
      ::features::kStrictOriginIsolation);

  // If no memory threshold is defined, strict origin isolation should be
  // enabled.
  EXPECT_TRUE(content::SiteIsolationPolicy::IsStrictOriginIsolationEnabled());

#if BUILDFLAG(IS_ANDROID)
  // Define a memory threshold at 768MB.  This is above the 512MB of physical
  // memory that this test simulates, but strict origin isolation should still
  // be enabled, because the test has simulated the user manually overriding
  // this feature via command line.
  base::test::ScopedFeatureList memory_feature;
  memory_feature.InitAndEnableFeatureWithParameters(
      features::kSiteIsolationMemoryThresholdsAndroid,
      {{features::kStrictSiteIsolationMemoryThresholdParamName, "768"}});

  EXPECT_TRUE(content::SiteIsolationPolicy::IsStrictOriginIsolationEnabled());
#endif  // BUILDFLAG(IS_ANDROID)
}

// The following tests verify that the list of Android's built-in isolated
// origins takes effect. This list is only used in official builds, and only
// when above the memory threshold.
#if BUILDFLAG(GOOGLE_CHROME_BRANDING) && BUILDFLAG(IS_ANDROID)
class BuiltInIsolatedOriginsTest : public SiteIsolationPolicyTest {
 public:
  BuiltInIsolatedOriginsTest() = default;

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

 protected:
  void SetUp() override {
    // Simulate a 512MB device.
    base::CommandLine::ForCurrentProcess()->AppendSwitch(
        switches::kEnableLowEndDeviceMode);
    EXPECT_EQ(512u, base::SysInfo::AmountOfTotalPhysicalMemory().InMiB());
    SiteIsolationPolicyTest::SetUp();
  }
};

// Check that the list of preloaded isolated origins is properly applied when
// device RAM is above the site isolation memory threshold.
TEST_F(BuiltInIsolatedOriginsTest, DefaultThreshold) {
  if (ShouldSkipBecauseOfConflictingCommandLineSwitches()) {
    return;
  }

  // Define a memory threshold at 128MB.  This is below the 512MB of physical
  // memory that this test simulates, so preloaded isolated origins should take
  // effect.
  base::test::ScopedFeatureList memory_feature;
  memory_feature.InitAndEnableFeatureWithParameters(
      features::kSiteIsolationMemoryThresholdsAndroid,
      {{features::kPartialSiteIsolationMemoryThresholdParamName, "128"}});

  // Ensure that isolated origins that are normally loaded on browser
  // startup are applied.
  content::SiteIsolationPolicy::ApplyGlobalIsolatedOrigins();

  EXPECT_TRUE(
      content::SiteIsolationPolicy::ArePreloadedIsolatedOriginsEnabled());

  auto* cpsp = content::ChildProcessSecurityPolicy::GetInstance();
  std::vector<url::Origin> isolated_origins = cpsp->GetIsolatedOrigins(
      content::ChildProcessSecurityPolicy::IsolatedOriginSource::BUILT_IN);

  // The list of built-in origins is fairly large; we don't want to hardcode
  // the size here as it might change, so just check that there are at least 10
  // origins.
  EXPECT_GT(isolated_origins.size(), 10u);

  // Check that a couple of well-known origins are on the list.
  EXPECT_THAT(
      isolated_origins,
      ::testing::Contains(url::Origin::Create(GURL("https://google.com/"))));
  EXPECT_THAT(
      isolated_origins,
      ::testing::Contains(url::Origin::Create(GURL("https://amazon.com/"))));
  EXPECT_THAT(
      isolated_origins,
      ::testing::Contains(url::Origin::Create(GURL("https://facebook.com/"))));

  cpsp->ClearIsolatedOriginsForTesting();
}

TEST_F(BuiltInIsolatedOriginsTest, BelowThreshold) {
  if (ShouldSkipBecauseOfConflictingCommandLineSwitches()) {
    return;
  }

  // Define a memory threshold at 768MB.  This is above the 512MB of physical
  // memory that this test simulates, so preloaded isolated origins shouldn't
  // take effect.
  base::test::ScopedFeatureList memory_feature;
  memory_feature.InitAndEnableFeatureWithParameters(
      features::kSiteIsolationMemoryThresholdsAndroid,
      {{features::kPartialSiteIsolationMemoryThresholdParamName, "768"}});

  // Ensure that isolated origins that are normally loaded on browser
  // startup are applied.
  content::SiteIsolationPolicy::ApplyGlobalIsolatedOrigins();

  EXPECT_FALSE(
      content::SiteIsolationPolicy::ArePreloadedIsolatedOriginsEnabled());

  auto* cpsp = content::ChildProcessSecurityPolicy::GetInstance();
  std::vector<url::Origin> isolated_origins = cpsp->GetIsolatedOrigins(
      content::ChildProcessSecurityPolicy::IsolatedOriginSource::BUILT_IN);

  // There shouldn't be any built-in origins on Android. (Note that desktop has
  // some built-in origins that are applied regardless of memory threshold.)
  EXPECT_EQ(isolated_origins.size(), 0u);

  cpsp->ClearIsolatedOriginsForTesting();
}

// Check that the list of preloaded isolated origins is not applied when full
// site isolation is used, since in that case the list is redundant.
TEST_F(BuiltInIsolatedOriginsTest, NotAppliedWithFullSiteIsolation) {
  // Force full site-per-process mode.
  content::IsolateAllSitesForTesting(base::CommandLine::ForCurrentProcess());

  // Define a memory threshold at 128MB.  This is below the 512MB of physical
  // memory that this test simulates, so preloaded isolated origins shouldn't
  // be disabled by the memory threshold.
  base::test::ScopedFeatureList memory_feature;
  memory_feature.InitAndEnableFeatureWithParameters(
      features::kSiteIsolationMemoryThresholdsAndroid,
      {{features::kPartialSiteIsolationMemoryThresholdParamName, "128"}});

  // Ensure that isolated origins that are normally loaded on browser
  // startup are applied.
  content::SiteIsolationPolicy::ApplyGlobalIsolatedOrigins();

  // Because full site-per-process is used, the preloaded isolated origins are
  // redundant and should not be applied.
  EXPECT_FALSE(
      content::SiteIsolationPolicy::ArePreloadedIsolatedOriginsEnabled());

  auto* cpsp = content::ChildProcessSecurityPolicy::GetInstance();
  std::vector<url::Origin> isolated_origins = cpsp->GetIsolatedOrigins(
      content::ChildProcessSecurityPolicy::IsolatedOriginSource::BUILT_IN);
  EXPECT_EQ(isolated_origins.size(), 0u);
}
#endif

// Helper class for tests that use header-based opt-in origin isolation and
// simulate a 512MB device, while turning off strict site isolation.  This is
// used for checking how opt-in origin isolation behaves with site isolation
// memory thresholds.
class OptInOriginIsolationPolicyTest : public BaseSiteIsolationTest {
 public:
  OptInOriginIsolationPolicyTest() = default;

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

 protected:
  void SetUp() override {
    // Simulate a 512MB device.
    base::CommandLine::ForCurrentProcess()->AppendSwitch(
        switches::kEnableLowEndDeviceMode);
    EXPECT_EQ(512u, base::SysInfo::AmountOfTotalPhysicalMemory().InMiB());
    // Turn off strict site isolation.  This simulates what would happen on
    // Android.
    SetEnableStrictSiteIsolation(false);
    // Enable Origin-Agent-Cluster header.
    feature_list_.InitAndEnableFeature(::features::kOriginIsolationHeader);
    BaseSiteIsolationTest::SetUp();
  }

  content::BrowserContext* browser_context() { return &browser_context_; }

 private:
  content::BrowserTaskEnvironment task_environment_;
  content::TestBrowserContext browser_context_;
  content::RenderViewHostTestEnabler rvh_test_enabler_;

  base::test::ScopedFeatureList feature_list_;
};

#if BUILDFLAG(IS_ANDROID)
// Check that opt-in origin isolation is not applied when below the memory
// threshold (and when full site isolation is not used).
TEST_F(OptInOriginIsolationPolicyTest, BelowThreshold_Android) {
  if (ShouldSkipBecauseOfConflictingCommandLineSwitches()) {
    return;
  }

  // Define a memory threshold at 768MB.  This is above the 512MB of physical
  // memory that this test simulates, so process isolation for
  // Origin-Agent-Cluster (OAC) should be disabled. But other aspects of OAC
  // should still take effect.
  base::test::ScopedFeatureList memory_feature;
  memory_feature.InitAndEnableFeatureWithParameters(
      features::kSiteIsolationMemoryThresholdsAndroid,
      {{features::kPartialSiteIsolationMemoryThresholdParamName, "768"}});

  EXPECT_FALSE(content::SiteIsolationPolicy::
                   IsProcessIsolationForOriginAgentClusterEnabled());
  EXPECT_TRUE(content::SiteIsolationPolicy::IsOriginAgentClusterEnabled());

  // Simulate a navigation to a URL that serves an Origin-Agent-Cluster header.
  // Since we're outside of content/, it's difficult to verify that internal
  // ChildProcessSecurityPolicy state wasn't changed by opt-in origin
  // isolation.  Instead, verify that the resulting SiteInstance doesn't
  // require a dedicated process.  This should be the end result, and it
  // implicitly checks that ChildProcessSecurityPolicy::IsIsolatedOrigin()
  // doesn't return true for this origin.
  const GURL kUrl("https://www.google.com/");
  std::unique_ptr<content::WebContents> web_contents =
      content::WebContentsTester::CreateTestWebContents(browser_context(),
                                                        nullptr);
  std::unique_ptr<content::NavigationSimulator> simulator =
      SimulateNavigationToURLWithOriginAgentClusterHeader(kUrl,
                                                          web_contents.get());

  content::SiteInstance* site_instance =
      simulator->GetFinalRenderFrameHost()->GetSiteInstance();
  EXPECT_FALSE(site_instance->RequiresDedicatedProcess());
  // Despite not getting process isolation, the origin will still get logical
  // isolation in Blink, and should still be tracked by
  // ChildProcessSecurityPolicy to ensure consistent OAC behavior for this
  // origin within this BrowsingInstance.
  EXPECT_TRUE(IsOriginAgentClusterEnabledForOrigin(site_instance,
                                                   url::Origin::Create(kUrl)));
}

// Counterpart to the test above, but verifies that opt-in origin isolation is
// enabled when above the memory threshold.
TEST_F(OptInOriginIsolationPolicyTest, AboveThreshold_Android) {
  if (ShouldSkipBecauseOfConflictingCommandLineSwitches()) {
    return;
  }

  // Define a memory threshold at 128MB.  This is below the 512MB of physical
  // memory that this test simulates, so opt-in origin isolation should be
  // enabled.
  base::test::ScopedFeatureList memory_feature;
  memory_feature.InitAndEnableFeatureWithParameters(
      features::kSiteIsolationMemoryThresholdsAndroid,
      {{features::kPartialSiteIsolationMemoryThresholdParamName, "128"}});

  EXPECT_TRUE(content::SiteIsolationPolicy::
                  IsProcessIsolationForOriginAgentClusterEnabled());
  EXPECT_TRUE(content::SiteIsolationPolicy::IsOriginAgentClusterEnabled());

  // Simulate a navigation to a URL that serves an Origin-Agent-Cluster header.
  // Verify that the resulting SiteInstance requires a dedicated process.  Note
  // that this test disables strict site isolation, so this would happen only
  // if opt-in isolation took place.
  const GURL kUrl("https://www.google.com/");
  std::unique_ptr<content::WebContents> web_contents =
      content::WebContentsTester::CreateTestWebContents(browser_context(),
                                                        nullptr);
  std::unique_ptr<content::NavigationSimulator> simulator =
      SimulateNavigationToURLWithOriginAgentClusterHeader(kUrl,
                                                          web_contents.get());
  content::SiteInstance* site_instance =
      simulator->GetFinalRenderFrameHost()->GetSiteInstance();
  EXPECT_TRUE(site_instance->RequiresDedicatedProcess());
}
#else  // BUILDFLAG(IS_ANDROID)
// Check that opt-in origin isolation is applied on non-Android.
TEST_F(OptInOriginIsolationPolicyTest, NonAndroid) {
  if (ShouldSkipBecauseOfConflictingCommandLineSwitches()) {
    return;
  }

  EXPECT_TRUE(content::SiteIsolationPolicy::
                  IsProcessIsolationForOriginAgentClusterEnabled());
  EXPECT_TRUE(content::SiteIsolationPolicy::IsOriginAgentClusterEnabled());

  // Simulate a navigation to a URL that serves an Origin-Agent-Cluster header.
  // Verify that the resulting SiteInstance requires a dedicated process.  Note
  // that this test disables strict site isolation, so this would happen only
  // if opt-in isolation took place.
  const GURL kUrl("https://www.google.com/");
  std::unique_ptr<content::WebContents> web_contents =
      content::WebContentsTester::CreateTestWebContents(browser_context(),
                                                        nullptr);
  std::unique_ptr<content::NavigationSimulator> simulator =
      SimulateNavigationToURLWithOriginAgentClusterHeader(kUrl,
                                                          web_contents.get());

  content::SiteInstance* site_instance =
      simulator->GetFinalRenderFrameHost()->GetSiteInstance();
  EXPECT_TRUE(site_instance->RequiresDedicatedProcess());
}
#endif

}  // namespace site_isolation
