// 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 "components/content_settings/core/browser/content_settings_pref_provider.h"

#include <memory>

#include "base/auto_reset.h"
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/simple_test_clock.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/browser/prefs/browser_prefs.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h"
#include "chrome/test/base/testing_profile.h"
#include "components/content_settings/core/browser/content_settings_mock_observer.h"
#include "components/content_settings/core/browser/content_settings_observable_provider.h"
#include "components/content_settings/core/browser/content_settings_pref.h"
#include "components/content_settings/core/browser/content_settings_registry.h"
#include "components/content_settings/core/browser/content_settings_rule.h"
#include "components/content_settings/core/browser/permission_settings_registry.h"
#include "components/content_settings/core/browser/website_settings_info.h"
#include "components/content_settings/core/browser/website_settings_registry.h"
#include "components/content_settings/core/common/content_settings.h"
#include "components/content_settings/core/common/content_settings_constraints.h"
#include "components/content_settings/core/common/content_settings_metadata.h"
#include "components/content_settings/core/common/content_settings_pattern.h"
#include "components/content_settings/core/common/content_settings_types.h"
#include "components/content_settings/core/common/features.h"
#include "components/content_settings/core/test/content_settings_test_utils.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/default_pref_store.h"
#include "components/prefs/overlay_user_pref_store.h"
#include "components/prefs/pref_change_registrar.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/prefs/testing_pref_store.h"
#include "components/sync_preferences/pref_service_mock_factory.h"
#include "components/sync_preferences/pref_service_syncable.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "content/public/test/browser_task_environment.h"
#include "services/network/public/cpp/features.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"

using ::testing::_;

namespace content_settings {

class DeadlockCheckerThread : public base::PlatformThread::Delegate {
 public:
  explicit DeadlockCheckerThread(const ContentSettingsPref* pref)
      : pref_(pref) {}

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

  void ThreadMain() override { EXPECT_TRUE(pref_->TryLockForTesting()); }

 private:
  raw_ptr<const ContentSettingsPref> pref_;
};

// A helper for observing an preference changes and testing whether
// |PrefProvider| holds a lock when the preferences change.
class DeadlockCheckerObserver {
 public:
  // |DeadlockCheckerObserver| doesn't take the ownership of |prefs| or
  // |provider|.
  DeadlockCheckerObserver(PrefService* prefs, PrefProvider* provider)
      : provider_(provider), notification_received_(false) {
    pref_change_registrar_.Init(prefs);
    WebsiteSettingsRegistry* registry = WebsiteSettingsRegistry::GetInstance();
    for (const auto& pair : provider_->content_settings_prefs_) {
      const ContentSettingsPref* pref = pair.second.get();
      pref_change_registrar_.Add(
          registry->Get(pair.first)->pref_name(),
          base::BindRepeating(
              &DeadlockCheckerObserver::OnContentSettingsPatternPairsChanged,
              base::Unretained(this), base::Unretained(pref)));
    }
  }

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

  virtual ~DeadlockCheckerObserver() = default;

  bool notification_received() const { return notification_received_; }

 private:
  void OnContentSettingsPatternPairsChanged(const ContentSettingsPref* pref) {
    // Check whether |provider_| holds its lock. For this, we need a
    // separate thread.
    DeadlockCheckerThread thread(pref);
    base::PlatformThreadHandle handle;
    ASSERT_TRUE(base::PlatformThread::Create(0, &thread, &handle));
    base::PlatformThread::Join(handle);
    notification_received_ = true;
  }

  raw_ptr<PrefProvider> provider_;
  PrefChangeRegistrar pref_change_registrar_;
  bool notification_received_;
};

class PrefProviderTest : public testing::Test {
 public:
  PrefProviderTest()
      : task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {
    // Ensure all content settings are initialized.
    ContentSettingsRegistry::GetInstance();
  }

  void FastForwardTime(base::TimeDelta delta) {
    task_environment_.FastForwardBy(delta);
  }

 private:
  content::BrowserTaskEnvironment task_environment_;
};

TEST_F(PrefProviderTest, Observer) {
  TestingProfile profile;
  PrefProvider pref_content_settings_provider(profile.GetPrefs(),
                                              /*off_the_record=*/false,
                                              /*store_last_modified=*/true,
                                              /*restore_session=*/false);

  ContentSettingsPattern pattern =
      ContentSettingsPattern::FromString("[*.]example.com");
  MockObserver mock_observer;
  EXPECT_CALL(mock_observer, OnContentSettingChanged(
                                 pattern, ContentSettingsPattern::Wildcard(),
                                 ContentSettingsType::COOKIES));

  pref_content_settings_provider.AddObserver(&mock_observer);

  pref_content_settings_provider.SetWebsiteSetting(
      pattern, ContentSettingsPattern::Wildcard(), ContentSettingsType::COOKIES,
      base::Value(CONTENT_SETTING_ALLOW), {});

  pref_content_settings_provider.ShutdownOnUIThread();
}

// Tests that obsolete content settings are cleared.
TEST_F(PrefProviderTest, DiscardObsoletePreferences) {
#if !BUILDFLAG(IS_ANDROID)
  const char kObsoleteInstalledWebAppMetadataExceptionsPref[] =
      "profile.content_settings.exceptions.installed_web_app_metadata";
#endif
  static char kObsoletePrivateNetworkChooserDataPref[] =
      "profile.content_settings.exceptions.private_network_chooser_data";
  static char kObsoleteTpcdTrialExceptionsPref[] =
      "profile.content_settings.exceptions.3pcd_support";
  static char kObsoleteTopLevelTpcdTrialExceptionsPref[] =
      "profile.content_settings.exceptions.top_level_3pcd_support";
  static char kObsoleteTopLevelTpcdOriginTrialExceptionsPref[] =
      "profile.content_settings.exceptions.top_level_3pcd_origin_trial";
  static const char kGeolocationPrefPath[] =
      "profile.content_settings.exceptions.geolocation";
  static const char kGetDisplayMediaSetSelectAllScreensAllowedForUrlsPref[] =
      "profile.content_settings.exceptions.get_display_media_set_select_all_"
      "screens";
  static const char kPattern[] = "[*.]example.com";

  TestingProfile profile;
  PrefService* prefs = profile.GetPrefs();

  // Set some pref data. Each content setting type has the following value:
  // {"[*.]example.com": {"setting": 1}}
  base::DictValue plugins_data_pref;
  constexpr char kFlagKey[] = "flashPreviouslyChanged";
  plugins_data_pref.Set(kFlagKey, base::DictValue());

  base::DictValue data_for_pattern;
  data_for_pattern.Set("setting", static_cast<int>(CONTENT_SETTING_ALLOW));
  base::DictValue pref_data;
  base::ListValue pref_list;
  pref_data.Set(kPattern, std::move(data_for_pattern));
#if !BUILDFLAG(IS_ANDROID)
  prefs->SetDict(kObsoleteInstalledWebAppMetadataExceptionsPref,
                 pref_data.Clone());
#endif
  prefs->SetDict(kObsoletePrivateNetworkChooserDataPref, pref_data.Clone());
  prefs->SetDict(kObsoleteTpcdTrialExceptionsPref, pref_data.Clone());
  prefs->SetDict(kObsoleteTopLevelTpcdTrialExceptionsPref, pref_data.Clone());
  prefs->SetDict(kObsoleteTopLevelTpcdOriginTrialExceptionsPref,
                 pref_data.Clone());
  prefs->SetDict(kGeolocationPrefPath, std::move(pref_data));
  prefs->SetList(kGetDisplayMediaSetSelectAllScreensAllowedForUrlsPref,
                 std::move(pref_list));

  // Instantiate a new PrefProvider here, because we want to test the
  // constructor's behavior after setting the above.
  PrefProvider provider(prefs, /*off_the_record=*/false,
                        /*store_last_modified=*/true,
                        /*restore_session=*/false);
  provider.ShutdownOnUIThread();

#if !BUILDFLAG(IS_ANDROID)
  EXPECT_FALSE(
      prefs->HasPrefPath(kObsoleteInstalledWebAppMetadataExceptionsPref));
#endif
  EXPECT_FALSE(prefs->HasPrefPath(kObsoletePrivateNetworkChooserDataPref));
  EXPECT_FALSE(prefs->HasPrefPath(kObsoleteTpcdTrialExceptionsPref));
  EXPECT_FALSE(prefs->HasPrefPath(kObsoleteTopLevelTpcdTrialExceptionsPref));
  EXPECT_FALSE(
      prefs->HasPrefPath(kObsoleteTopLevelTpcdOriginTrialExceptionsPref));
  EXPECT_FALSE(prefs->HasPrefPath(
      kGetDisplayMediaSetSelectAllScreensAllowedForUrlsPref));
  EXPECT_TRUE(prefs->HasPrefPath(kGeolocationPrefPath));
  GURL primary_url("http://example.com/");
  EXPECT_EQ(
      CONTENT_SETTING_ALLOW,
      TestUtils::GetContentSetting(&provider, primary_url, primary_url,
                                   ContentSettingsType::GEOLOCATION, false));
}

// Test for regression in which the PrefProvider modified the user pref store
// of the OTR unintentionally: http://crbug.com/41332768.
TEST_F(PrefProviderTest, Incognito) {
  PersistentPrefStore* user_prefs = new TestingPrefStore();
  OverlayUserPrefStore* otr_user_prefs = new OverlayUserPrefStore(user_prefs);

  sync_preferences::PrefServiceMockFactory factory;
  factory.set_user_prefs(base::WrapRefCounted(user_prefs));
  scoped_refptr<user_prefs::PrefRegistrySyncable> registry(
      new user_prefs::PrefRegistrySyncable);
  sync_preferences::PrefServiceSyncable* regular_prefs =
      factory.CreateSyncable(registry.get()).release();

  RegisterUserProfilePrefs(registry.get());

  sync_preferences::PrefServiceMockFactory otr_factory;
  otr_factory.set_user_prefs(base::WrapRefCounted(otr_user_prefs));
  scoped_refptr<user_prefs::PrefRegistrySyncable> otr_registry(
      new user_prefs::PrefRegistrySyncable);
  sync_preferences::PrefServiceSyncable* otr_prefs =
      otr_factory.CreateSyncable(otr_registry.get()).release();

  RegisterUserProfilePrefs(otr_registry.get());

  TestingProfile::Builder profile_builder;
  profile_builder.SetPrefService(base::WrapUnique(regular_prefs));
  std::unique_ptr<TestingProfile> profile = profile_builder.Build();

  TestingProfile::Builder otr_profile_builder;
  otr_profile_builder.SetPrefService(base::WrapUnique(otr_prefs));
  otr_profile_builder.BuildIncognito(profile.get());

  PrefProvider pref_content_settings_provider(
      regular_prefs, /*off_the_record=*/false, /*store_last_modified=*/true,
      /*restore_session=*/false);
  PrefProvider pref_content_settings_provider_incognito(
      otr_prefs, true /*off_the_record=*/, /*store_last_modified=*/true,
      /*restore_session=*/false);
  ContentSettingsPattern pattern =
      ContentSettingsPattern::FromString("[*.]example.com");
  pref_content_settings_provider.SetWebsiteSetting(
      pattern, pattern, ContentSettingsType::COOKIES,
      base::Value(CONTENT_SETTING_ALLOW), {});

  GURL host("http://example.com/");
  // The value should of course be visible in the regular PrefProvider.
  EXPECT_EQ(
      CONTENT_SETTING_ALLOW,
      TestUtils::GetContentSetting(&pref_content_settings_provider, host, host,
                                   ContentSettingsType::COOKIES, false));
  // And also in the OTR version.
  EXPECT_EQ(CONTENT_SETTING_ALLOW,
            TestUtils::GetContentSetting(
                &pref_content_settings_provider_incognito, host, host,
                ContentSettingsType::COOKIES, false));
  const WebsiteSettingsInfo* info =
      WebsiteSettingsRegistry::GetInstance()->Get(ContentSettingsType::COOKIES);
  // But the value should not be overridden in the OTR user prefs accidentally.
  EXPECT_FALSE(otr_user_prefs->IsSetInOverlay(info->pref_name()));

  pref_content_settings_provider.ShutdownOnUIThread();
  pref_content_settings_provider_incognito.ShutdownOnUIThread();
}

TEST_F(PrefProviderTest, GetContentSettingsValue) {
  TestingProfile testing_profile;
  PrefProvider provider(testing_profile.GetPrefs(), /*off_the_record=*/false,
                        /*store_last_modified=*/true,
                        /*restore_session=*/false);

  GURL primary_url("http://example.com/");
  ContentSettingsPattern primary_pattern =
      ContentSettingsPattern::FromString("[*.]example.com");

  EXPECT_EQ(CONTENT_SETTING_DEFAULT,
            TestUtils::GetContentSetting(&provider, primary_url, primary_url,
                                         ContentSettingsType::COOKIES, false));

  EXPECT_EQ(base::Value(), TestUtils::GetContentSettingValue(
                               &provider, primary_url, primary_url,
                               ContentSettingsType::COOKIES, false));

  provider.SetWebsiteSetting(primary_pattern, primary_pattern,
                             ContentSettingsType::COOKIES,
                             base::Value(CONTENT_SETTING_BLOCK), {});
  EXPECT_EQ(CONTENT_SETTING_BLOCK,
            TestUtils::GetContentSetting(&provider, primary_url, primary_url,
                                         ContentSettingsType::COOKIES, false));
  base::Value value = TestUtils::GetContentSettingValue(
      &provider, primary_url, primary_url, ContentSettingsType::COOKIES, false);
  EXPECT_EQ(CONTENT_SETTING_BLOCK,
            IntToContentSetting(value.GetIfInt().value_or(-1)));

  provider.SetWebsiteSetting(primary_pattern, primary_pattern,
                             ContentSettingsType::COOKIES, base::Value(), {});
  EXPECT_EQ(base::Value(), TestUtils::GetContentSettingValue(
                               &provider, primary_url, primary_url,
                               ContentSettingsType::COOKIES, false));
  provider.ShutdownOnUIThread();
}

TEST_F(PrefProviderTest, Patterns) {
  TestingProfile testing_profile;
  PrefProvider pref_content_settings_provider(testing_profile.GetPrefs(),
                                              /*off_the_record=*/false,
                                              /*store_last_modified=*/true,
                                              /*restore_session=*/false);

  GURL host1("http://example.com/");
  GURL host2("http://www.example.com/");
  GURL host3("http://example.org/");
  GURL host4("file:///tmp/test.html");
  ContentSettingsPattern pattern1 =
      ContentSettingsPattern::FromString("[*.]example.com");
  ContentSettingsPattern pattern2 =
      ContentSettingsPattern::FromString("example.org");
  ContentSettingsPattern pattern3 =
      ContentSettingsPattern::FromString("file:///tmp/test.html");

  EXPECT_EQ(
      CONTENT_SETTING_DEFAULT,
      TestUtils::GetContentSetting(&pref_content_settings_provider, host1,
                                   host1, ContentSettingsType::COOKIES, false));
  pref_content_settings_provider.SetWebsiteSetting(
      pattern1, pattern1, ContentSettingsType::COOKIES,
      base::Value(CONTENT_SETTING_BLOCK), {});
  EXPECT_EQ(
      CONTENT_SETTING_BLOCK,
      TestUtils::GetContentSetting(&pref_content_settings_provider, host1,
                                   host1, ContentSettingsType::COOKIES, false));
  EXPECT_EQ(
      CONTENT_SETTING_BLOCK,
      TestUtils::GetContentSetting(&pref_content_settings_provider, host2,
                                   host2, ContentSettingsType::COOKIES, false));

  EXPECT_EQ(
      CONTENT_SETTING_DEFAULT,
      TestUtils::GetContentSetting(&pref_content_settings_provider, host3,
                                   host3, ContentSettingsType::COOKIES, false));
  pref_content_settings_provider.SetWebsiteSetting(
      pattern2, pattern2, ContentSettingsType::COOKIES,
      base::Value(CONTENT_SETTING_BLOCK), {});
  EXPECT_EQ(
      CONTENT_SETTING_BLOCK,
      TestUtils::GetContentSetting(&pref_content_settings_provider, host3,
                                   host3, ContentSettingsType::COOKIES, false));

  EXPECT_EQ(
      CONTENT_SETTING_DEFAULT,
      TestUtils::GetContentSetting(&pref_content_settings_provider, host4,
                                   host4, ContentSettingsType::COOKIES, false));
  pref_content_settings_provider.SetWebsiteSetting(
      pattern3, pattern3, ContentSettingsType::COOKIES,
      base::Value(CONTENT_SETTING_BLOCK), {});
  EXPECT_EQ(
      CONTENT_SETTING_BLOCK,
      TestUtils::GetContentSetting(&pref_content_settings_provider, host4,
                                   host4, ContentSettingsType::COOKIES, false));

  pref_content_settings_provider.ShutdownOnUIThread();
}

// http://crosbug.com/17760
TEST_F(PrefProviderTest, Deadlock) {
  sync_preferences::TestingPrefServiceSyncable prefs;
  PrefProvider::RegisterProfilePrefs(prefs.registry());

  // Chain of events: a preference changes, |PrefProvider| notices it, and reads
  // and writes the preference. When the preference is written, a notification
  // is sent, and this used to happen when |PrefProvider| was still holding its
  // lock.

  const WebsiteSettingsInfo* info =
      WebsiteSettingsRegistry::GetInstance()->Get(ContentSettingsType::COOKIES);
  PrefProvider provider(&prefs, /*off_the_record=*/false,
                        /*store_last_modified=*/true,
                        /*restore_session=*/false);

  DeadlockCheckerObserver observer(&prefs, &provider);
  {
    ScopedDictPrefUpdate update(&prefs, info->pref_name());
    base::DictValue& mutable_settings = update.Get();
    mutable_settings.Set("www.example.com,*",
                         base::Value(base::Value::Type::DICT));
  }
  EXPECT_TRUE(observer.notification_received());

  provider.ShutdownOnUIThread();
}

TEST_F(PrefProviderTest, IncognitoInheritsValueMap) {
  sync_preferences::TestingPrefServiceSyncable prefs;
  PrefProvider::RegisterProfilePrefs(prefs.registry());

  ContentSettingsPattern pattern_1 =
      ContentSettingsPattern::FromString("google.com");
  ContentSettingsPattern pattern_2 =
      ContentSettingsPattern::FromString("www.google.com");
  ContentSettingsPattern pattern_3 =
      ContentSettingsPattern::FromString("example.com");
  ContentSettingsPattern pattern_4 =
      ContentSettingsPattern::FromString("foo.com");
  ContentSettingsPattern pattern_5 =
      ContentSettingsPattern::FromString("bar.com");

  ContentSettingsPattern wildcard = ContentSettingsPattern::FromString("*");

  // Create a normal provider and set a setting.
  PrefProvider normal_provider(&prefs, /*off_the_record=*/false,
                               /*store_last_modified=*/true,
                               /*restore_session=*/false);

  {
    ContentSettingConstraints constraints;
    constraints.set_session_model(mojom::SessionModel::USER_SESSION);

    normal_provider.SetWebsiteSetting(pattern_1, wildcard,
                                      ContentSettingsType::COOKIES,
                                      base::Value(CONTENT_SETTING_ALLOW), {});
    normal_provider.SetWebsiteSetting(
        pattern_3, pattern_3, ContentSettingsType::COOKIES,
        base::Value(CONTENT_SETTING_BLOCK), constraints);
  }
  {
    // Durable and not expired
    ContentSettingConstraints constraints;
    constraints.set_lifetime(base::Days(1));
    constraints.set_session_model(mojom::SessionModel::DURABLE);
    normal_provider.SetWebsiteSetting(
        pattern_4, pattern_4, ContentSettingsType::COOKIES,
        base::Value(CONTENT_SETTING_BLOCK), constraints);
  }
  {
    // Durable but expired
    ContentSettingConstraints constraints(base::Time::Now() - base::Days(2));
    constraints.set_lifetime(base::Days(1));
    constraints.set_session_model(mojom::SessionModel::DURABLE);
    normal_provider.SetWebsiteSetting(
        pattern_5, pattern_5, ContentSettingsType::COOKIES,
        base::Value(CONTENT_SETTING_BLOCK), constraints);
  }
  // Non-OTR provider, Non-OTR iterator has one setting (pattern 1) using
  // default params and one scoped to a UserSession lifetime model.
  {
    std::unique_ptr<RuleIterator> it(
        normal_provider.GetRuleIterator(ContentSettingsType::COOKIES, false));
    EXPECT_TRUE(it->HasNext());
    EXPECT_EQ(pattern_5, it->Next()->primary_pattern);
    EXPECT_TRUE(it->HasNext());
    EXPECT_EQ(pattern_3, it->Next()->primary_pattern);
    EXPECT_TRUE(it->HasNext());
    EXPECT_EQ(pattern_4, it->Next()->primary_pattern);
    EXPECT_TRUE(it->HasNext());
    EXPECT_EQ(pattern_1, it->Next()->primary_pattern);
    EXPECT_FALSE(it->HasNext());
  }

  // Non-OTR provider, OTR iterator has no settings.
  {
    std::unique_ptr<RuleIterator> it(
        normal_provider.GetRuleIterator(ContentSettingsType::COOKIES, true));
    EXPECT_FALSE(it);
  }

  // Create an incognito provider and set a setting.
  PrefProvider incognito_provider(&prefs, true /* incognito */,
                                  /*store_last_modified=*/true,
                                  /*restore_session=*/false);

  incognito_provider.SetWebsiteSetting(pattern_2, wildcard,
                                       ContentSettingsType::COOKIES,
                                       base::Value(CONTENT_SETTING_ALLOW), {});

  // OTR provider, non-OTR iterator has two settings (pattern 1/3).
  {
    std::unique_ptr<RuleIterator> it(incognito_provider.GetRuleIterator(
        ContentSettingsType::COOKIES, false));
    EXPECT_TRUE(it->HasNext());
    EXPECT_EQ(pattern_3, it->Next()->primary_pattern);
    EXPECT_TRUE(it->HasNext());
    EXPECT_EQ(pattern_4, it->Next()->primary_pattern);
    EXPECT_TRUE(it->HasNext());
    EXPECT_EQ(pattern_1, it->Next()->primary_pattern);
    EXPECT_FALSE(it->HasNext());
  }

  // OTR provider, OTR iterator has one setting (pattern 2).
  {
    std::unique_ptr<RuleIterator> it(
        incognito_provider.GetRuleIterator(ContentSettingsType::COOKIES, true));
    EXPECT_TRUE(it->HasNext());
    EXPECT_EQ(pattern_2, it->Next()->primary_pattern);
    EXPECT_FALSE(it->HasNext());
  }

  incognito_provider.ShutdownOnUIThread();
  normal_provider.ShutdownOnUIThread();
}

TEST_F(PrefProviderTest, ClearAllContentSettingsRules) {
  sync_preferences::TestingPrefServiceSyncable prefs;
  PrefProvider::RegisterProfilePrefs(prefs.registry());

  ContentSettingsPattern pattern =
      ContentSettingsPattern::FromString("google.com");
  ContentSettingsPattern wildcard = ContentSettingsPattern::FromString("*");
  base::Value value(CONTENT_SETTING_ALLOW);

  PrefProvider provider(&prefs, /*off_the_record=*/false,
                        /*store_last_modified=*/true,
                        /*restore_session=*/false);

  // Non-empty pattern, syncable, empty resource identifier.
  provider.SetWebsiteSetting(pattern, wildcard, ContentSettingsType::JAVASCRIPT,
                             value.Clone(), {});

  // Non-empty pattern, non-syncable, empty resource identifier.
  provider.SetWebsiteSetting(
      pattern, wildcard, ContentSettingsType::GEOLOCATION, value.Clone(), {});

  // Non-empty pattern, syncable, empty resource identifier.
  provider.SetWebsiteSetting(pattern, wildcard, ContentSettingsType::COOKIES,
                             value.Clone(), {});

  // Non-empty pattern, non-syncable, empty resource identifier.
  provider.SetWebsiteSetting(
      pattern, wildcard, ContentSettingsType::NOTIFICATIONS, value.Clone(), {});

  // Test that the preferences for images, geolocation and plugins get cleared.
  WebsiteSettingsRegistry* registry = WebsiteSettingsRegistry::GetInstance();
  const char* cleared_prefs[] = {
    registry->Get(ContentSettingsType::JAVASCRIPT)->pref_name().c_str(),
    registry->Get(ContentSettingsType::GEOLOCATION)->pref_name().c_str(),
  };

  // Expect the prefs are not empty before we trigger clearing them.
  for (const char* pref : cleared_prefs) {
    const base::DictValue& dictionary = prefs.GetDict(pref);
    ASSERT_FALSE(dictionary.empty());
  }

  provider.ClearAllContentSettingsRules(ContentSettingsType::JAVASCRIPT);
  provider.ClearAllContentSettingsRules(ContentSettingsType::GEOLOCATION);

  // Ensure they become empty afterwards.
  for (const char* pref : cleared_prefs) {
    const base::DictValue& dictionary = prefs.GetDict(pref);
    EXPECT_TRUE(dictionary.empty());
  }

  // Test that the preferences for cookies and notifications are not empty.
  const char* nonempty_prefs[] = {
      registry->Get(ContentSettingsType::COOKIES)->pref_name().c_str(),
      registry->Get(ContentSettingsType::NOTIFICATIONS)->pref_name().c_str(),
  };

  for (const char* pref : nonempty_prefs) {
    const base::DictValue& dictionary = prefs.GetDict(pref);
    EXPECT_EQ(1u, dictionary.size());
  }

  provider.ShutdownOnUIThread();
}

TEST_F(PrefProviderTest, LastModified) {
  sync_preferences::TestingPrefServiceSyncable prefs;
  PrefProvider::RegisterProfilePrefs(prefs.registry());

  GURL url1("https://google.com");
  GURL url2("https://www.google.com");
  ContentSettingsPattern pattern_1 =
      ContentSettingsPattern::FromString("google.com");
  ContentSettingsPattern pattern_2 =
      ContentSettingsPattern::FromString("www.google.com");
  // Create a  provider and set a few settings.
  PrefProvider provider(&prefs, /*off_the_record=*/false,
                        /*store_last_modified=*/true,
                        /*restore_session=*/false);
  base::SimpleTestClock test_clock;
  test_clock.SetNow(base::Time::Now());
  provider.SetClockForTesting(&test_clock);

  base::Time t1 = test_clock.Now();

  provider.SetWebsiteSetting(pattern_1, ContentSettingsPattern::Wildcard(),
                             ContentSettingsType::COOKIES,
                             base::Value(CONTENT_SETTING_ALLOW), {});
  provider.SetWebsiteSetting(pattern_2, ContentSettingsPattern::Wildcard(),
                             ContentSettingsType::COOKIES,
                             base::Value(CONTENT_SETTING_ALLOW), {});
  // Make sure that the timestamps for pattern_1 and patter_2 are before |t2|.
  test_clock.Advance(base::Seconds(1));
  base::Time t2 = test_clock.Now();

  base::Time last_modified = TestUtils::GetLastModified(
      &provider, url1, url1, ContentSettingsType::COOKIES);
  EXPECT_EQ(t1, last_modified);
  last_modified = TestUtils::GetLastModified(&provider, url2, url2,
                                             ContentSettingsType::COOKIES);
  EXPECT_EQ(t1, last_modified);

  // A change for pattern_1, which will update the last_modified timestamp.
  provider.SetWebsiteSetting(pattern_1, ContentSettingsPattern::Wildcard(),
                             ContentSettingsType::COOKIES,
                             base::Value(CONTENT_SETTING_BLOCK), {});

  last_modified = TestUtils::GetLastModified(&provider, url1, url1,
                                             ContentSettingsType::COOKIES);
  EXPECT_EQ(t2, last_modified);

  // The timestamp of pattern_2 shouldn't change.
  last_modified = TestUtils::GetLastModified(&provider, url2, url2,
                                             ContentSettingsType::COOKIES);
  EXPECT_EQ(t1, last_modified);

  provider.ShutdownOnUIThread();

  // Ensure the timestamps survive roundtrip through a provider reload properly.
  PrefProvider provider2(&prefs, /*off_the_record=*/false,
                         /*store_last_modified=*/true,
                         /*restore_session=*/false);

  last_modified = TestUtils::GetLastModified(&provider, url1, url1,
                                             ContentSettingsType::COOKIES);
  EXPECT_EQ(t2, last_modified);

  // The timestamp of pattern_2 shouldn't change.
  last_modified = TestUtils::GetLastModified(&provider, url2, url2,
                                             ContentSettingsType::COOKIES);
  EXPECT_EQ(t1, last_modified);
  provider2.ShutdownOnUIThread();
}

// If a setting is constrained to a session scope it should only persist in
// memory.
TEST_F(PrefProviderTest, SessionScopeSettingsDontPersist) {
  TestingProfile testing_profile;
  PrefProvider provider(testing_profile.GetPrefs(), /*off_the_record=*/false,
                        /*store_last_modified=*/true,
                        /*restore_session=*/false);

  GURL primary_url("http://example.com/");
  ContentSettingsPattern primary_pattern =
      ContentSettingsPattern::FromString("[*.]example.com");

  EXPECT_EQ(
      CONTENT_SETTING_DEFAULT,
      TestUtils::GetContentSetting(&provider, primary_url, primary_url,
                                   ContentSettingsType::STORAGE_ACCESS, false));

  ContentSettingConstraints constraints;
  constraints.set_session_model(mojom::SessionModel::USER_SESSION);

  provider.SetWebsiteSetting(primary_pattern, primary_pattern,
                             ContentSettingsType::STORAGE_ACCESS,
                             base::Value(CONTENT_SETTING_BLOCK), constraints);
  EXPECT_EQ(
      CONTENT_SETTING_BLOCK,
      TestUtils::GetContentSetting(&provider, primary_url, primary_url,
                                   ContentSettingsType::STORAGE_ACCESS, false));
  base::Value value = TestUtils::GetContentSettingValue(
      &provider, primary_url, primary_url, ContentSettingsType::STORAGE_ACCESS,
      false);
  EXPECT_EQ(CONTENT_SETTING_BLOCK,
            IntToContentSetting(value.GetIfInt().value_or(-1)));

  // Now if we create a new provider, it should not be able to read our setting
  // back.
  provider.ShutdownOnUIThread();

  PrefProvider provider2(testing_profile.GetPrefs(),
                         /*off_the_record=*/false,
                         /*store_last_modified=*/true,
                         /*restore_session=*/false);
  EXPECT_EQ(
      CONTENT_SETTING_DEFAULT,
      TestUtils::GetContentSetting(&provider2, primary_url, primary_url,
                                   ContentSettingsType::STORAGE_ACCESS, false));
  provider2.ShutdownOnUIThread();
}

// If a setting is constrained to a session scope and a provider is made with
// the `restore_Session` flag, the setting should not be cleared.
TEST_F(PrefProviderTest, SessionScopeSettingsRestoreSession) {
  sync_preferences::TestingPrefServiceSyncable prefs;
  PrefProvider::RegisterProfilePrefs(prefs.registry());

  // Create a normal provider and set a setting.
  PrefProvider provider(&prefs, /*off_the_record=*/false,
                        /*store_last_modified=*/true,
                        /*restore_session=*/false);

  GURL primary_url("http://example.com/");
  ContentSettingsPattern primary_pattern =
      ContentSettingsPattern::FromString("[*.]example.com");

  EXPECT_EQ(
      CONTENT_SETTING_DEFAULT,
      TestUtils::GetContentSetting(&provider, primary_url, primary_url,
                                   ContentSettingsType::STORAGE_ACCESS, false));

  ContentSettingConstraints constraints;
  constraints.set_session_model(mojom::SessionModel::USER_SESSION);

  provider.SetWebsiteSetting(primary_pattern, primary_pattern,
                             ContentSettingsType::STORAGE_ACCESS,
                             base::Value(CONTENT_SETTING_BLOCK), constraints);
  EXPECT_EQ(
      CONTENT_SETTING_BLOCK,
      TestUtils::GetContentSetting(&provider, primary_url, primary_url,
                                   ContentSettingsType::STORAGE_ACCESS, false));
  base::Value value(TestUtils::GetContentSettingValue(
      &provider, primary_url, primary_url, ContentSettingsType::STORAGE_ACCESS,
      false));
  EXPECT_EQ(CONTENT_SETTING_BLOCK,
            IntToContentSetting(value.GetIfInt().value_or(-1)));

  // Now if we create a new provider, it should be able to read our setting
  // back.
  provider.ShutdownOnUIThread();

  PrefProvider provider2(&prefs, /*off_the_record=*/false,
                         /*store_last_modified=*/true,
                         /*restore_session=*/true);

  EXPECT_EQ(
      CONTENT_SETTING_BLOCK,
      TestUtils::GetContentSetting(&provider2, primary_url, primary_url,
                                   ContentSettingsType::STORAGE_ACCESS, false));
  provider2.ShutdownOnUIThread();
}

// Validate our settings will properly store our expiry time if specified.
TEST_F(PrefProviderTest, GetContentSettingsExpiry) {
  TestingProfile testing_profile;
  PrefProvider provider(testing_profile.GetPrefs(), /*off_the_record=*/false,
                        /*store_last_modified=*/true,
                        /*restore_session=*/false);

  GURL primary_url("http://example.com/");
  ContentSettingsPattern primary_pattern =
      ContentSettingsPattern::FromString("[*.]example.com");
  ContentSettingConstraints constraints;
  constraints.set_lifetime(base::Seconds(123));
  constraints.set_session_model(mojom::SessionModel::DURABLE);

  provider.SetWebsiteSetting(primary_pattern, primary_pattern,
                             ContentSettingsType::STORAGE_ACCESS,
                             base::Value(CONTENT_SETTING_BLOCK), constraints);
  EXPECT_EQ(
      CONTENT_SETTING_BLOCK,
      TestUtils::GetContentSetting(&provider, primary_url, primary_url,
                                   ContentSettingsType::STORAGE_ACCESS, false));
  base::Value value = TestUtils::GetContentSettingValue(
      &provider, primary_url, primary_url, ContentSettingsType::STORAGE_ACCESS,
      false);
  EXPECT_EQ(CONTENT_SETTING_BLOCK,
            IntToContentSetting(value.GetIfInt().value_or(-1)));

  // Now if we skip ahead our time our setting should be expired and no longer
  // valid.
  FastForwardTime(base::Seconds(200));

  EXPECT_EQ(base::Value(), TestUtils::GetContentSettingValue(
                               &provider, primary_url, primary_url,
                               ContentSettingsType::STORAGE_ACCESS, false));
  EXPECT_EQ(
      CONTENT_SETTING_DEFAULT,
      TestUtils::GetContentSetting(&provider, primary_url, primary_url,
                                   ContentSettingsType::STORAGE_ACCESS, false));
  provider.ShutdownOnUIThread();
}

// Any specified expiry time should persist in our prefs and outlive a restart.
TEST_F(PrefProviderTest, GetContentSettingsExpiryPersists) {
  TestingProfile testing_profile;
  PrefProvider provider(testing_profile.GetPrefs(), /*off_the_record=*/false,
                        /*store_last_modified=*/true,
                        /*restore_session=*/false);

  GURL primary_url("http://example.com/");
  ContentSettingsPattern primary_pattern =
      ContentSettingsPattern::FromString("[*.]example.com");
  ContentSettingConstraints constraints;
  constraints.set_lifetime(base::Seconds(123));
  constraints.set_session_model(mojom::SessionModel::DURABLE);

  provider.SetWebsiteSetting(primary_pattern, primary_pattern,
                             ContentSettingsType::STORAGE_ACCESS,
                             base::Value(CONTENT_SETTING_BLOCK), constraints);
  EXPECT_EQ(
      CONTENT_SETTING_BLOCK,
      TestUtils::GetContentSetting(&provider, primary_url, primary_url,
                                   ContentSettingsType::STORAGE_ACCESS, false));
  base::Value value = TestUtils::GetContentSettingValue(
      &provider, primary_url, primary_url, ContentSettingsType::STORAGE_ACCESS,
      false);
  EXPECT_EQ(CONTENT_SETTING_BLOCK,
            IntToContentSetting(value.GetIfInt().value_or(-1)));

  // Shutdown our provider and we should still have a setting present.
  provider.ShutdownOnUIThread();
  PrefProvider provider2(testing_profile.GetPrefs(), /*off_the_record=*/false,
                         /*store_last_modified=*/true,
                         /*restore_session=*/false);

  EXPECT_EQ(
      CONTENT_SETTING_BLOCK,
      TestUtils::GetContentSetting(&provider2, primary_url, primary_url,
                                   ContentSettingsType::STORAGE_ACCESS, false));

  // Now if we skip ahead our time our setting should be expired and no longer
  // valid.
  FastForwardTime(base::Seconds(200));

  EXPECT_EQ(base::Value(), TestUtils::GetContentSettingValue(
                               &provider2, primary_url, primary_url,
                               ContentSettingsType::STORAGE_ACCESS, false));
  EXPECT_EQ(
      CONTENT_SETTING_DEFAULT,
      TestUtils::GetContentSetting(&provider2, primary_url, primary_url,
                                   ContentSettingsType::STORAGE_ACCESS, false));
  provider2.ShutdownOnUIThread();
}

// Any specified expiry time should persist in our prefs and outlive a restart.
TEST_F(PrefProviderTest, GetContentSettingsExpiryAfterRestore) {
  TestingProfile testing_profile;
  PrefProvider provider(testing_profile.GetPrefs(), /*off_the_record=*/false,
                        /*store_last_modified=*/true,
                        /*restore_session=*/false);

  GURL primary_url("http://example.com/");
  ContentSettingsPattern primary_pattern =
      ContentSettingsPattern::FromString("[*.]example.com");
  ContentSettingConstraints constraints;
  constraints.set_lifetime(base::Seconds(123));
  constraints.set_session_model(mojom::SessionModel::DURABLE);

  provider.SetWebsiteSetting(primary_pattern, primary_pattern,
                             ContentSettingsType::STORAGE_ACCESS,
                             base::Value(CONTENT_SETTING_BLOCK), constraints);
  EXPECT_EQ(
      CONTENT_SETTING_BLOCK,
      TestUtils::GetContentSetting(&provider, primary_url, primary_url,
                                   ContentSettingsType::STORAGE_ACCESS, false));
  base::Value value = TestUtils::GetContentSettingValue(
      &provider, primary_url, primary_url, ContentSettingsType::STORAGE_ACCESS,
      false);
  EXPECT_EQ(CONTENT_SETTING_BLOCK,
            IntToContentSetting(value.GetIfInt().value_or(-1)));

  provider.ShutdownOnUIThread();
  PrefProvider provider2(testing_profile.GetPrefs(), /*off_the_record=*/false,
                         /*store_last_modified=*/true,
                         /*restore_session=*/true);

  // Now if we skip ahead our time our setting should be expired and no longer
  // valid.
  FastForwardTime(base::Seconds(200));

  EXPECT_EQ(base::Value(), TestUtils::GetContentSettingValue(
                               &provider2, primary_url, primary_url,
                               ContentSettingsType::STORAGE_ACCESS, false));
  EXPECT_EQ(
      CONTENT_SETTING_DEFAULT,
      TestUtils::GetContentSetting(&provider2, primary_url, primary_url,
                                   ContentSettingsType::STORAGE_ACCESS, false));
  provider2.ShutdownOnUIThread();
}

// If we update a setting and change the scope from Session to Durable it
// should persist in the same way as an original Durable scoped setting.
TEST_F(PrefProviderTest, ScopeSessionToDurablePersists) {
  TestingProfile testing_profile;
  PrefProvider provider(testing_profile.GetPrefs(), /*off_the_record=*/false,
                        /*store_last_modified=*/true,
                        /*restore_session=*/false);

  GURL primary_url("http://example.com/");
  ContentSettingsPattern primary_pattern =
      ContentSettingsPattern::FromString("[*.]example.com");
  ContentSettingConstraints constraints;
  constraints.set_session_model(mojom::SessionModel::USER_SESSION);

  provider.SetWebsiteSetting(primary_pattern, primary_pattern,
                             ContentSettingsType::STORAGE_ACCESS,
                             base::Value(CONTENT_SETTING_BLOCK), constraints);
  EXPECT_EQ(
      CONTENT_SETTING_BLOCK,
      TestUtils::GetContentSetting(&provider, primary_url, primary_url,
                                   ContentSettingsType::STORAGE_ACCESS, false));

  // Update to Durable and expect that the setting is still there.
  constraints.set_session_model(mojom::SessionModel::DURABLE);
  provider.SetWebsiteSetting(primary_pattern, primary_pattern,
                             ContentSettingsType::STORAGE_ACCESS,
                             base::Value(CONTENT_SETTING_BLOCK), constraints);
  EXPECT_EQ(
      CONTENT_SETTING_BLOCK,
      TestUtils::GetContentSetting(&provider, primary_url, primary_url,
                                   ContentSettingsType::STORAGE_ACCESS, false));

  // Shutdown our provider and we should still have a setting present.
  provider.ShutdownOnUIThread();
  PrefProvider provider2(testing_profile.GetPrefs(), /*off_the_record=*/false,
                         /*store_last_modified=*/true,
                         /*restore_session=*/false);

  EXPECT_EQ(
      CONTENT_SETTING_BLOCK,
      TestUtils::GetContentSetting(&provider2, primary_url, primary_url,
                                   ContentSettingsType::STORAGE_ACCESS, false));
  provider2.ShutdownOnUIThread();
}

// If we update a setting and change the scope from Durable to Session it
// should drop in the same way as an original Session scoped setting would.
TEST_F(PrefProviderTest, ScopeDurableToSessionDrops) {
  TestingProfile testing_profile;
  PrefProvider provider(testing_profile.GetPrefs(), /*off_the_record=*/false,
                        /*store_last_modified=*/true,
                        /*restore_session=*/false);

  GURL primary_url("http://example.com/");
  ContentSettingsPattern primary_pattern =
      ContentSettingsPattern::FromString("[*.]example.com");
  ContentSettingConstraints constraints;
  constraints.set_session_model(mojom::SessionModel::DURABLE);

  provider.SetWebsiteSetting(primary_pattern, primary_pattern,
                             ContentSettingsType::STORAGE_ACCESS,
                             base::Value(CONTENT_SETTING_BLOCK), constraints);
  EXPECT_EQ(
      CONTENT_SETTING_BLOCK,
      TestUtils::GetContentSetting(&provider, primary_url, primary_url,
                                   ContentSettingsType::STORAGE_ACCESS, false));

  // Update to Durable and expect that the setting is still there.
  constraints.set_session_model(mojom::SessionModel::USER_SESSION);
  provider.SetWebsiteSetting(primary_pattern, primary_pattern,
                             ContentSettingsType::STORAGE_ACCESS,
                             base::Value(CONTENT_SETTING_BLOCK), constraints);
  EXPECT_EQ(
      CONTENT_SETTING_BLOCK,
      TestUtils::GetContentSetting(&provider, primary_url, primary_url,
                                   ContentSettingsType::STORAGE_ACCESS, false));

  // Shutdown our provider and we should still have a setting present.
  provider.ShutdownOnUIThread();
  PrefProvider provider2(testing_profile.GetPrefs(), /*off_the_record=*/false,
                         /*store_last_modified=*/true,
                         /*restore_session=*/false);

  EXPECT_EQ(
      CONTENT_SETTING_DEFAULT,
      TestUtils::GetContentSetting(&provider2, primary_url, primary_url,
                                   ContentSettingsType::STORAGE_ACCESS, false));
  provider2.ShutdownOnUIThread();
}

TEST_F(PrefProviderTest, LastVisitedTimeIsTracked) {
  TestingProfile testing_profile;
  PrefProvider provider(testing_profile.GetPrefs(), /*off_the_record=*/false,
                        /*store_last_modified=*/true,
                        /*restore_session=*/false);
  base::SimpleTestClock clock;
  clock.SetNow(base::Time::Now());
  provider.SetClockForTesting(&clock);

  GURL primary_url("http://example.com/");
  ContentSettingsPattern primary_pattern =
      ContentSettingsPattern::FromString("[*.]example.com");

  ContentSettingConstraints constraints;
  constraints.set_track_last_visit_for_autoexpiration(false);

  // Set one setting with track_last_visit_for_autoexpiration enabled and one
  // disabled.
  provider.SetWebsiteSetting(primary_pattern, primary_pattern,
                             ContentSettingsType::MEDIASTREAM_CAMERA,
                             base::Value(CONTENT_SETTING_ALLOW), constraints);

  constraints.set_track_last_visit_for_autoexpiration(true);
  provider.SetWebsiteSetting(primary_pattern, primary_pattern,
                             ContentSettingsType::GEOLOCATION,
                             base::Value(CONTENT_SETTING_ALLOW), constraints);
  RuleMetaData metadata;
  EXPECT_EQ(CONTENT_SETTING_ALLOW,
            TestUtils::GetContentSetting(
                &provider, primary_url, primary_url,
                ContentSettingsType::MEDIASTREAM_CAMERA, false, &metadata));
  EXPECT_EQ(metadata.last_visited(), base::Time());

  EXPECT_EQ(CONTENT_SETTING_ALLOW,
            TestUtils::GetContentSetting(&provider, primary_url, primary_url,
                                         ContentSettingsType::GEOLOCATION,
                                         false, &metadata));
  EXPECT_NE(metadata.last_visited(), base::Time());
  EXPECT_GE(metadata.last_visited(), clock.Now() - base::Days(7));
  EXPECT_LE(metadata.last_visited(), clock.Now());

  provider.ShutdownOnUIThread();
}

TEST_F(PrefProviderTest, RenewContentSetting) {
  TestingProfile testing_profile;
  PrefProvider provider(testing_profile.GetPrefs(), /*off_the_record=*/false,
                        /*store_last_modified=*/true,
                        /*restore_session=*/false);
  base::SimpleTestClock clock;
  clock.SetNow(base::Time::Now());
  provider.SetClockForTesting(&clock);

  GURL primary_url("https://example.com/");
  ContentSettingsPattern primary_pattern =
      ContentSettingsPattern::FromString("https://[*.]example.com");

  ContentSettingConstraints constraints;
  constraints.set_lifetime(base::Days(2));

  ASSERT_TRUE(provider.SetWebsiteSetting(
      primary_pattern, primary_pattern, ContentSettingsType::STORAGE_ACCESS,
      base::Value(CONTENT_SETTING_ALLOW), constraints));

  RuleMetaData metadata;
  EXPECT_EQ(CONTENT_SETTING_ALLOW, TestUtils::GetContentSetting(
                                       &provider, primary_url, primary_url,
                                       ContentSettingsType::STORAGE_ACCESS,
                                       /*include_incognito=*/false, &metadata));
  EXPECT_EQ(metadata.lifetime(), base::Days(2));
  EXPECT_EQ(metadata.expiration(), clock.Now() + base::Days(2));

  clock.Advance(base::Days(1));

  EXPECT_EQ(CONTENT_SETTING_ALLOW, TestUtils::GetContentSetting(
                                       &provider, primary_url, primary_url,
                                       ContentSettingsType::STORAGE_ACCESS,
                                       /*include_incognito=*/false, &metadata));
  EXPECT_EQ(metadata.lifetime(), base::Days(2));
  EXPECT_EQ(metadata.expiration(), clock.Now() + base::Days(1));

  // Wrong ContentSetting, doesn't match.
  EXPECT_FALSE(provider.RenewContentSetting(primary_url, primary_url,
                                            ContentSettingsType::STORAGE_ACCESS,
                                            CONTENT_SETTING_BLOCK));

  EXPECT_TRUE(provider.RenewContentSetting(primary_url, primary_url,
                                           ContentSettingsType::STORAGE_ACCESS,
                                           CONTENT_SETTING_ALLOW));

  EXPECT_EQ(CONTENT_SETTING_ALLOW, TestUtils::GetContentSetting(
                                       &provider, primary_url, primary_url,
                                       ContentSettingsType::STORAGE_ACCESS,
                                       /*include_incognito=*/false, &metadata));
  EXPECT_EQ(metadata.lifetime(), base::Days(2));
  EXPECT_EQ(metadata.expiration(), clock.Now() + base::Days(2));

  provider.ShutdownOnUIThread();
}

TEST_F(PrefProviderTest, LastVisitedTimeStoredOnDisk) {
  TestingProfile testing_profile;
  PrefProvider provider(testing_profile.GetPrefs(), /*off_the_record=*/false,
                        /*store_last_modified=*/true,
                        /*restore_session=*/false);
  GURL primary_url("http://example.com/");
  ContentSettingsPattern primary_pattern =
      ContentSettingsPattern::FromString("[*.]example.com");
  ContentSettingConstraints constraints;
  constraints.set_track_last_visit_for_autoexpiration(true);

  provider.SetWebsiteSetting(primary_pattern, primary_pattern,
                             ContentSettingsType::GEOLOCATION,
                             base::Value(CONTENT_SETTING_ALLOW), constraints);
  RuleMetaData metadata;
  EXPECT_EQ(CONTENT_SETTING_ALLOW,
            TestUtils::GetContentSetting(&provider, primary_url, primary_url,
                                         ContentSettingsType::GEOLOCATION,
                                         false, &metadata));
  EXPECT_NE(metadata.last_visited(), base::Time());

  // Shutdown our provider and we should still have a setting present.
  provider.ShutdownOnUIThread();
  PrefProvider provider2(testing_profile.GetPrefs(), /*off_the_record=*/false,
                         /*store_last_modified=*/true,
                         /*restore_session=*/false);

  RuleMetaData metadata_from_disk;
  EXPECT_EQ(CONTENT_SETTING_ALLOW,
            TestUtils::GetContentSetting(&provider2, primary_url, primary_url,
                                         ContentSettingsType::GEOLOCATION,
                                         false, &metadata_from_disk));
  EXPECT_EQ(metadata.last_visited(), metadata_from_disk.last_visited());

  provider2.ShutdownOnUIThread();
}

TEST_F(PrefProviderTest, LastVisitedTimeUpdating) {
  TestingProfile testing_profile;
  PrefProvider provider(testing_profile.GetPrefs(), /*off_the_record=*/false,
                        /*store_last_modified=*/true,
                        /*restore_session=*/false);
  base::SimpleTestClock clock;
  clock.SetNow(base::Time::Now());
  provider.SetClockForTesting(&clock);

  GURL primary_url("http://example.com/");
  ContentSettingsPattern primary_pattern =
      ContentSettingsPattern::FromString("[*.]example.com");
  ContentSettingConstraints constraints;
  constraints.set_track_last_visit_for_autoexpiration(true);

  provider.SetWebsiteSetting(primary_pattern, primary_pattern,
                             ContentSettingsType::GEOLOCATION,
                             base::Value(CONTENT_SETTING_ALLOW), constraints);
  RuleMetaData metadata;
  EXPECT_EQ(CONTENT_SETTING_ALLOW,
            TestUtils::GetContentSetting(&provider, primary_url, primary_url,
                                         ContentSettingsType::GEOLOCATION,
                                         false, &metadata));
  EXPECT_GE(metadata.last_visited(), clock.Now() - base::Days(7));
  EXPECT_LE(metadata.last_visited(), clock.Now());

  clock.Advance(base::Days(20));
  provider.UpdateLastVisitTime(primary_pattern, primary_pattern,
                               ContentSettingsType::GEOLOCATION);
  EXPECT_EQ(CONTENT_SETTING_ALLOW,
            TestUtils::GetContentSetting(&provider, primary_url, primary_url,
                                         ContentSettingsType::GEOLOCATION,
                                         false, &metadata));
  EXPECT_GE(metadata.last_visited(), clock.Now() - base::Days(7));
  EXPECT_LE(metadata.last_visited(), clock.Now());

  provider.ShutdownOnUIThread();
}

TEST_F(PrefProviderTest, AutorevocationBypassedByUserStoredOnDisk) {
  TestingProfile testing_profile;
  PrefProvider provider(testing_profile.GetPrefs(), /*off_the_record=*/false,
                        /*store_last_modified=*/true,
                        /*restore_session=*/false);
  GURL primary_url("http://example.com/");
  ContentSettingsPattern primary_pattern =
      ContentSettingsPattern::FromString("[*.]example.com");
  ContentSettingConstraints constraints;

  provider.SetWebsiteSetting(primary_pattern, primary_pattern,
                             ContentSettingsType::GEOLOCATION,
                             base::Value(CONTENT_SETTING_ALLOW), constraints);
  provider.SetAutorevocationBypassedByUser(primary_pattern, primary_pattern,
                                           ContentSettingsType::GEOLOCATION);
  RuleMetaData metadata;
  EXPECT_EQ(CONTENT_SETTING_ALLOW,
            TestUtils::GetContentSetting(&provider, primary_url, primary_url,
                                         ContentSettingsType::GEOLOCATION,
                                         false, &metadata));
  EXPECT_TRUE(metadata.autorevocation_bypassed_by_user());

  // Shutdown our provider and we should still have a setting present.
  provider.ShutdownOnUIThread();
  PrefProvider provider2(testing_profile.GetPrefs(), /*off_the_record=*/false,
                         /*store_last_modified=*/true,
                         /*restore_session=*/false);

  RuleMetaData metadata_from_disk;
  EXPECT_EQ(CONTENT_SETTING_ALLOW,
            TestUtils::GetContentSetting(&provider2, primary_url, primary_url,
                                         ContentSettingsType::GEOLOCATION,
                                         false, &metadata_from_disk));
  EXPECT_TRUE(metadata_from_disk.autorevocation_bypassed_by_user());

  provider2.ShutdownOnUIThread();
}

TEST_F(PrefProviderTest, AutorevocationBypassedByUserUpdated) {
  TestingProfile testing_profile;
  PrefProvider provider(testing_profile.GetPrefs(), /*off_the_record=*/false,
                        /*store_last_modified=*/true,
                        /*restore_session=*/false);
  base::SimpleTestClock clock;
  clock.SetNow(base::Time::Now());
  provider.SetClockForTesting(&clock);

  GURL primary_url("http://example.com/");
  ContentSettingsPattern primary_pattern =
      ContentSettingsPattern::FromString("[*.]example.com");
  ContentSettingConstraints constraints;

  provider.SetWebsiteSetting(primary_pattern, primary_pattern,
                             ContentSettingsType::GEOLOCATION,
                             base::Value(CONTENT_SETTING_ALLOW), constraints);
  RuleMetaData metadata;
  EXPECT_EQ(CONTENT_SETTING_ALLOW,
            TestUtils::GetContentSetting(&provider, primary_url, primary_url,
                                         ContentSettingsType::GEOLOCATION,
                                         false, &metadata));
  EXPECT_FALSE(metadata.autorevocation_bypassed_by_user());

  provider.SetAutorevocationBypassedByUser(primary_pattern, primary_pattern,
                                           ContentSettingsType::GEOLOCATION);
  EXPECT_EQ(CONTENT_SETTING_ALLOW,
            TestUtils::GetContentSetting(&provider, primary_url, primary_url,
                                         ContentSettingsType::GEOLOCATION,
                                         false, &metadata));
  EXPECT_TRUE(metadata.autorevocation_bypassed_by_user());

  provider.ShutdownOnUIThread();
}

TEST_F(PrefProviderTest, MigrateGeolocationOnFeatureEnabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndDisableFeature(
      features::kApproximateGeolocationPermission);

  TestingProfile profile;
  PrefService* prefs = profile.GetPrefs();

  ContentSettingsPattern pattern =
      ContentSettingsPattern::FromString("[*.]example.com");
  GURL url("http://example.com");

  {
    PrefProvider provider(prefs, /*off_the_record=*/false,
                          /*store_last_modified=*/true,
                          /*restore_session=*/false);
    provider.SetWebsiteSetting(pattern, pattern,
                               ContentSettingsType::GEOLOCATION,
                               base::Value(CONTENT_SETTING_ALLOW), {});
    provider.ShutdownOnUIThread();
  }

  feature_list.Reset();
  feature_list.InitAndEnableFeature(
      features::kApproximateGeolocationPermission);

  {
    PrefProvider new_provider(prefs, /*off_the_record=*/false,
                              /*store_last_modified=*/true,
                              /*restore_session=*/false);
    auto setting =
        std::get<GeolocationSetting>(*TestUtils::GetPermissionSetting(
            &new_provider, url, url,
            ContentSettingsType::GEOLOCATION_WITH_OPTIONS,
            /*include_incognito=*/false));
    EXPECT_EQ(PermissionOption::kAllowed, setting.precise);
    EXPECT_EQ(PermissionOption::kAllowed, setting.approximate);
    EXPECT_EQ(CONTENT_SETTING_DEFAULT,
              TestUtils::GetContentSetting(&new_provider, url, url,
                                           ContentSettingsType::GEOLOCATION,
                                           /*include_incognito=*/false));
    new_provider.ShutdownOnUIThread();
  }
}

TEST_F(PrefProviderTest, MigrateGeolocationBackOnFeatureDisabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeature(
      features::kApproximateGeolocationPermission);

  TestingProfile profile;
  PrefService* prefs = profile.GetPrefs();

  ContentSettingsPattern pattern =
      ContentSettingsPattern::FromString("[*.]example.com");
  GURL url("http://example.com");

  {
    // Set a setting and enable the feature to trigger the migration.
    PrefProvider provider(prefs, /*off_the_record=*/false,
                          /*store_last_modified=*/true,
                          /*restore_session=*/false);
    auto* info = PermissionSettingsRegistry::GetInstance()->Get(
        ContentSettingsType::GEOLOCATION_WITH_OPTIONS);
    provider.SetWebsiteSetting(
        pattern, pattern, ContentSettingsType::GEOLOCATION_WITH_OPTIONS,
        info->delegate().ToValue(GeolocationSetting{
            PermissionOption::kAllowed, PermissionOption::kAllowed}),
        {});
    provider.ShutdownOnUIThread();
  }

  // Disable the feature to trigger the migration back.
  feature_list.Reset();
  feature_list.InitAndDisableFeature(
      features::kApproximateGeolocationPermission);

  {
    PrefProvider new_provider(prefs, /*off_the_record=*/false,
                              /*store_last_modified=*/true,
                              /*restore_session=*/false);
    EXPECT_EQ(CONTENT_SETTING_ALLOW,
              TestUtils::GetContentSetting(&new_provider, url, url,
                                           ContentSettingsType::GEOLOCATION,
                                           /*include_incognito=*/false));
    EXPECT_FALSE(TestUtils::GetPermissionSetting(
                     &new_provider, url, url,
                     ContentSettingsType::GEOLOCATION_WITH_OPTIONS,
                     /*include_incognito=*/false)
                     .has_value());
    new_provider.ShutdownOnUIThread();
  }
}

TEST_F(PrefProviderTest, MigrateLocalNetworkAccessOnFeatureEnabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{network::features::kLocalNetworkAccessChecks},
      /*disabled_features=*/{});

  TestingProfile profile;
  PrefService* prefs = profile.GetPrefs();

  ContentSettingsPattern allow_pattern =
      ContentSettingsPattern::FromString("[*.]example.com");
  ContentSettingsPattern block_pattern =
      ContentSettingsPattern::FromString("[*.]evil.com");
  GURL allow_url("http://example.com");
  GURL block_url("http://evil.com");

  {
    PrefProvider provider(prefs, /*off_the_record=*/false,
                          /*store_last_modified=*/true,
                          /*restore_session=*/false);
    provider.SetWebsiteSetting(allow_pattern, allow_pattern,
                               ContentSettingsType::LOCAL_NETWORK_ACCESS,
                               base::Value(CONTENT_SETTING_ALLOW), {});
    provider.SetWebsiteSetting(block_pattern, block_pattern,
                               ContentSettingsType::LOCAL_NETWORK_ACCESS,
                               base::Value(CONTENT_SETTING_BLOCK), {});
    provider.ShutdownOnUIThread();
  }

  // Clear the migration pref to force the migration
  static char kLocalNetworkAccessMigrateExceptionsPref[] =
      "profile.content_settings.exceptions.has_migrated_local_network_access";
  prefs->SetBoolean(kLocalNetworkAccessMigrateExceptionsPref, false);

  // Test migration forward.
  {
    PrefProvider new_provider(prefs, /*off_the_record=*/false,
                              /*store_last_modified=*/true,
                              /*restore_session=*/false);
    // CONTENT_SETTING_ALLOW is migrated to both LOCAL_NETWORK and
    // LOOPBACK_NETWORK
    EXPECT_EQ(CONTENT_SETTING_ALLOW,
              TestUtils::GetContentSetting(&new_provider, allow_url, allow_url,
                                           ContentSettingsType::LOCAL_NETWORK,
                                           /*include_incognito=*/false));
    EXPECT_EQ(CONTENT_SETTING_ALLOW, TestUtils::GetContentSetting(
                                         &new_provider, allow_url, allow_url,
                                         ContentSettingsType::LOOPBACK_NETWORK,
                                         /*include_incognito=*/false));
    // CONTENT_SETTING_BLOCK is only migrated to LOCAL_NETWORK
    EXPECT_EQ(CONTENT_SETTING_BLOCK,
              TestUtils::GetContentSetting(&new_provider, block_url, block_url,
                                           ContentSettingsType::LOCAL_NETWORK,
                                           /*include_incognito=*/false));
    EXPECT_EQ(
        CONTENT_SETTING_DEFAULT,
        TestUtils::GetContentSetting(&new_provider, block_url, block_url,
                                     ContentSettingsType::LOOPBACK_NETWORK,
                                     /*include_incognito=*/false));
    new_provider.ShutdownOnUIThread();
  }
}

}  // namespace content_settings
