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

#include "chrome/browser/themes/theme_service.h"

#include <cmath>
#include <memory>

#include "base/compiler_specific.h"
#include "base/containers/fixed_flat_map.h"
#include "base/files/file_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/scoped_observation.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/task_environment.h"
#include "build/build_config.h"
#include "build/buildflag.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_service_test_base.h"
#include "chrome/browser/search/background/ntp_custom_background_service_factory.h"
#include "chrome/browser/themes/custom_theme_supplier.h"
#include "chrome/browser/themes/test/theme_service_changed_waiter.h"
#include "chrome/browser/themes/theme_properties.h"
#include "chrome/browser/themes/theme_service_factory.h"
#include "chrome/browser/themes/theme_service_test_utils.h"
#include "chrome/browser/themes/theme_syncable_service.h"
#include "chrome/browser/ui/color/chrome_color_id.h"
#include "chrome/browser/ui/color/chrome_color_mixers.h"
#include "chrome/common/buildflags.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "components/color/color_mixers.h"
#include "components/profile_metrics/browser_profile_type.h"
#include "components/sync/base/features.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "components/themes/pref_names.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/test_utils.h"
#include "extensions/browser/extension_registrar.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_util.h"
#include "extensions/browser/test_extension_registry_observer.h"
#include "extensions/browser/uninstall_reason.h"
#include "extensions/browser/unpacked_installer.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_id.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/mojom/themes.mojom.h"
#include "ui/base/ui_base_features.h"
#include "ui/color/color_provider.h"
#include "ui/color/color_provider_manager.h"
#include "ui/gfx/color_palette.h"
#include "ui/gfx/color_utils.h"
#include "ui/native_theme/mock_os_settings_provider.h"
#include "ui/native_theme/native_theme.h"
#include "ui/native_theme/native_theme_observer.h"

#if BUILDFLAG(IS_LINUX)
#include "ui/linux/linux_ui.h"
#include "ui/linux/linux_ui_factory.h"
#include "ui/linux/linux_ui_getter.h"
#include "ui/ozone/public/ozone_platform.h"
#endif

namespace {

enum class SystemTheme { kDefault, kCustom };

// A class that ensures any installed extension is uninstalled before it goes
// out of scope.  This ensures the temporary directory used to load the
// extension is unlocked and can be deleted.
class ThemeScoper {
 public:
  ThemeScoper() = default;
  ThemeScoper(extensions::ExtensionRegistrar* extension_registrar,
              extensions::ExtensionRegistry* extension_registry)
      : extension_registrar_(extension_registrar),
        extension_registry_(extension_registry) {}
  ThemeScoper(ThemeScoper&&) noexcept = default;
  ThemeScoper& operator=(ThemeScoper&&) = default;
  ~ThemeScoper() {
    if (!extension_id_.empty() &&
        extension_registry_->GetInstalledExtension(extension_id_)) {
      extension_registrar_->UninstallExtension(
          extension_id_, extensions::UNINSTALL_REASON_FOR_TESTING, nullptr);
    }
  }

  extensions::ExtensionId extension_id() const { return extension_id_; }
  void set_extension_id(std::string extension_id) {
    extension_id_ = std::move(extension_id);
  }

  base::FilePath GetTempPath() {
    return temp_dir_.CreateUniqueTempDir() ? temp_dir_.GetPath()
                                           : base::FilePath();
  }

 private:
  raw_ptr<extensions::ExtensionRegistrar> extension_registrar_ = nullptr;
  raw_ptr<extensions::ExtensionRegistry> extension_registry_ = nullptr;
  extensions::ExtensionId extension_id_;
  base::ScopedTempDir temp_dir_;
};

#if BUILDFLAG(IS_LINUX)
class LinuxUiGetterImpl : public ui::LinuxUiGetter {
 public:
  explicit LinuxUiGetterImpl(bool use_system_theme)
      : linux_ui_theme_(use_system_theme ? ui::GetDefaultLinuxUiTheme()
                                         : nullptr) {}
  ~LinuxUiGetterImpl() override = default;

  ui::LinuxUiTheme* GetForWindow(aura::Window* window) override {
    return linux_ui_theme_;
  }
  ui::LinuxUiTheme* GetForProfile(Profile* profile) override {
    return linux_ui_theme_;
  }

 private:
  raw_ptr<ui::LinuxUiTheme> linux_ui_theme_;
};
#endif

}  // namespace

namespace theme_service_internal {

class ThemeUpdatedTest : public ::testing::Test,
                         public ::testing::WithParamInterface<SystemTheme> {
 public:
#if BUILDFLAG(IS_LINUX)
  // ::testing::Test:
  void SetUp() override {
    static bool initialized = false;
    if (!initialized) {
      // Ensures LinuxUi is configured on supported linux platforms.
      auto* const linux_ui = ui::GetDefaultLinuxUi();
      ASSERT_TRUE(linux_ui);
      ui::LinuxUi::SetInstance(linux_ui);
      initialized = true;
    }

    linux_ui_getter_ =
        std::make_unique<LinuxUiGetterImpl>(GetParam() == SystemTheme::kCustom);
  }
#endif

  Profile* profile() { return &profile_; }

 private:
  content::BrowserTaskEnvironment task_environment_;
  TestingProfile profile_;
#if BUILDFLAG(IS_LINUX)
  std::unique_ptr<ui::LinuxUiGetter> linux_ui_getter_;
#endif
};

INSTANTIATE_TEST_SUITE_P(,
                         ThemeUpdatedTest,
                         ::testing::Values(SystemTheme::kDefault,
                                           SystemTheme::kCustom));

class ThemeServiceTest : public extensions::ExtensionServiceTestBase {
 public:
  void SetUp() override {
    extensions::ExtensionServiceTestBase::SetUp();
    InitializeExtensionService(ExtensionServiceInitParams());
    service()->Init();
    registry_ = extensions::ExtensionRegistry::Get(profile());
    ASSERT_TRUE(registry_);
    theme_service_ = ThemeServiceFactory::GetForProfile(profile());
    ASSERT_TRUE(theme_service_);
    pref_service_ = profile()->GetPrefs();
    ASSERT_TRUE(pref_service_);
  }

  void TearDown() override {
    pref_service_ = nullptr;
    theme_service_ = nullptr;
    registry_ = nullptr;
    extensions::ExtensionServiceTestBase::TearDown();
  }

 protected:
  void InitThemeServiceFromPrefs() { theme_service_->InitFromPrefs(); }

  const extensions::ExtensionRegistry* registry() const { return registry_; }
  PrefService* pref_service() { return pref_service_; }
  ThemeService* theme_service() { return theme_service_; }

  ThemeScoper LoadUnpackedTheme(const std::string& source_file_path =
                                    "extensions/theme_minimal/manifest.json") {
    ThemeScoper scoper(registrar(), registry_);
    test::ThemeServiceChangedWaiter waiter(theme_service_);
    base::FilePath temp_dir = scoper.GetTempPath();
    base::FilePath dst_manifest_path = temp_dir.AppendASCII("manifest.json");
    base::FilePath test_data_dir;
    EXPECT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir));
    base::FilePath src_manifest_path =
        test_data_dir.AppendASCII(source_file_path);
    EXPECT_TRUE(base::CopyFile(src_manifest_path, dst_manifest_path));

    scoped_refptr<extensions::UnpackedInstaller> installer(
        extensions::UnpackedInstaller::Create(profile()));
    extensions::TestExtensionRegistryObserver observer(registry_);
    installer->Load(temp_dir);
    std::string extenson_id = observer.WaitForExtensionLoaded()->id();
    scoper.set_extension_id(extenson_id);

    waiter.WaitForThemeChanged();

    // Make sure RegisterClient calls for storage are finished to avoid flaky
    // crashes in QuotaManagerImpl::RegisterClient on test shutdown.
    // TODO(crbug.com/40170877) : Remove this when 1182630 is fixed.
    extensions::util::GetStoragePartitionForExtensionId(extenson_id, profile());
    task_environment()->RunUntilIdle();

    return scoper;
  }

  // Update the theme with |extension_id|.
  void UpdateUnpackedTheme(const std::string& extension_id) {
    const base::FilePath& path =
        registry_->GetInstalledExtension(extension_id)->path();

    scoped_refptr<extensions::UnpackedInstaller> installer(
        extensions::UnpackedInstaller::Create(profile()));

    extensions::TestExtensionRegistryObserver observer(registry_);
    installer->Load(path);
    observer.WaitForExtensionInstalled();

    // Let the ThemeService finish creating the theme pack.
    base::RunLoop().RunUntilIdle();
  }

  bool IsExtensionDisabled(const extensions::ExtensionId& id) const {
    return registry_->disabled_extensions().GetByID(id);
  }

  Profile* CreateIsolatedProfile() {
    Profile* isolated_profile = profile()->GetOffTheRecordProfile(
        Profile::OTRProfileID::CreateUniqueForTesting(),
        /*create_if_needed=*/true);
    profile_metrics::SetBrowserProfileType(
        isolated_profile,
        profile_metrics::BrowserProfileType::kEnterpriseIsolated);
    return isolated_profile;
  }

  ui::ColorProviderKey GetColorProviderKey(const ui::ColorProviderKey& base_key,
                                           const Profile* profile) const {
    return theme_service_->GetColorProviderKey(base_key, profile);
  }

 private:
  raw_ptr<extensions::ExtensionRegistry> registry_ = nullptr;
  raw_ptr<PrefService> pref_service_ = nullptr;
  raw_ptr<ThemeService> theme_service_ = nullptr;
};

class ColorProviderTest : public ThemeServiceTest,
                          public testing::WithParamInterface<
                              std::tuple<ui::NativeTheme::PreferredColorScheme,
                                         ui::NativeTheme::PreferredContrast,
                                         SystemTheme>> {
 public:
  static std::string ParamInfoToString(
      ::testing::TestParamInfo<ParamType> param_info) {
    std::string str = (GetPreferredColorScheme(param_info.param) ==
                       ui::NativeTheme::PreferredColorScheme::kDark)
                          ? "Dark"
                          : "Light";
    if (GetPreferredContrast(param_info.param) ==
        ui::NativeTheme::PreferredContrast::kMore) {
      str += "HighContrast";
    }
    if (GetSystemTheme(param_info.param) == SystemTheme::kCustom) {
      str += "SystemTheme";
    }
    return str;
  }

  // ThemeServiceTest:
  void SetUp() override {
    ThemeServiceTest::SetUp();

    // Only perform mixer initialization once.
    static bool initialized_mixers = false;
    if (!initialized_mixers) {
#if BUILDFLAG(IS_LINUX)
      // Ensures LinuxUi is configured on supported linux platforms.
      // Initializing the toolkit also adds the native toolkit ColorMixers.
      ui::OzonePlatform::InitParams ozone_params;
      ozone_params.single_process = true;
      ui::OzonePlatform::InitializeForUI(ozone_params);
      auto* linux_ui = ui::GetDefaultLinuxUi();
      ASSERT_TRUE(linux_ui);
      ui::LinuxUi::SetInstance(linux_ui);
#endif  // BUILDFLAG(IS_LINUX)

      // Add the components and Chrome ColorMixers after native ColorMixers.
      ui::ColorProviderManager::Get().AppendColorProviderInitializer(
          base::BindRepeating(color::AddComponentsColorMixers));
      ui::ColorProviderManager::Get().AppendColorProviderInitializer(
          base::BindRepeating(AddChromeColorMixers));

      initialized_mixers = true;
    }
#if BUILDFLAG(IS_LINUX)
    linux_ui_getter_ = std::make_unique<LinuxUiGetterImpl>(
        GetSystemTheme() == SystemTheme::kCustom);
#endif  // BUILDFLAG(IS_LINUX)

    os_settings_provider_.SetPreferredColorScheme(GetPreferredColorScheme());
    os_settings_provider_.SetPreferredContrast(GetPreferredContrast());
#if BUILDFLAG(IS_WIN)
    os_settings_provider_.SetForcedColorsActive(
        GetPreferredContrast() == ui::NativeTheme::PreferredContrast::kMore);
#endif  // BUILDFLAG(IS_WIN)

    // Update ThemeService to use the system theme if necessary.
    if (GetSystemTheme() == SystemTheme::kCustom) {
      theme_service()->UseSystemTheme();
    } else {
      theme_service()->UseDefaultTheme();
    }
  }

 protected:
  static ui::NativeTheme::PreferredColorScheme GetPreferredColorScheme(
      const ParamType& param = GetParam()) {
    return std::get<ui::NativeTheme::PreferredColorScheme>(param);
  }

  static ui::NativeTheme::PreferredContrast GetPreferredContrast(
      const ParamType& param = GetParam()) {
    return std::get<ui::NativeTheme::PreferredContrast>(param);
  }

  static SystemTheme GetSystemTheme(const ParamType& param = GetParam()) {
    return std::get<SystemTheme>(param);
  }

  SkColor GetColor(ui::ColorId id) const {
    const auto* native_theme = ui::NativeTheme::GetInstanceForNativeUi();
#if BUILDFLAG(IS_LINUX)
    if (GetSystemTheme() == SystemTheme::kCustom) {
      native_theme = ui::GetDefaultLinuxUiTheme()->GetNativeTheme();
    }
#endif
    return ui::ColorProviderManager::Get()
        .GetColorProviderFor(native_theme->GetColorProviderKey(nullptr))
        ->GetColor(id);
  }

 private:
  ui::MockOsSettingsProvider os_settings_provider_;
#if BUILDFLAG(IS_LINUX)
  std::unique_ptr<ui::LinuxUiGetter> linux_ui_getter_;
#endif
};

INSTANTIATE_TEST_SUITE_P(
    ,
    ColorProviderTest,
    ::testing::Combine(
        ::testing::Values(ui::NativeTheme::PreferredColorScheme::kLight,
                          ui::NativeTheme::PreferredColorScheme::kDark),
        ::testing::Values(ui::NativeTheme::PreferredContrast::kNoPreference,
                          ui::NativeTheme::PreferredContrast::kMore),
        ::testing::Values(SystemTheme::kDefault, SystemTheme::kCustom)),
    ColorProviderTest::ParamInfoToString);

TEST_P(ThemeUpdatedTest, NoUpdateOnCreation) {
  // Monitor calls to `OnNativeThemeUpdated()`.
  struct MockObserver : ui::NativeThemeObserver {
    void OnNativeThemeUpdated(ui::NativeTheme* observed_theme) override {
      ++call_count;
    }

    int call_count = 0;
  } observer;
  base::ScopedObservation<ui::NativeTheme, ui::NativeThemeObserver> observation(
      &observer);
  observation.Observe(ui::NativeTheme::GetInstanceForNativeUi());

  // Creating the theme service should not synchronously trigger any NativeTheme
  // notifications.
  ThemeServiceFactory::GetForProfile(profile());
  EXPECT_EQ(observer.call_count, 0);
}

// Installs then uninstalls a theme and makes sure that the ThemeService
// reverts to the default theme after the uninstall.
TEST_F(ThemeServiceTest, ThemeInstallUninstall) {
  ThemeScoper scoper = LoadUnpackedTheme();
  EXPECT_FALSE(theme_service()->UsingDefaultTheme());
  EXPECT_TRUE(theme_service()->UsingExtensionTheme());
  EXPECT_EQ(scoper.extension_id(), theme_service()->GetThemeID());

  // Now uninstall the extension, should revert to the default theme.
  registrar()->UninstallExtension(
      scoper.extension_id(), extensions::UNINSTALL_REASON_FOR_TESTING, nullptr);
  EXPECT_TRUE(theme_service()->UsingDefaultTheme());
  EXPECT_FALSE(theme_service()->UsingExtensionTheme());
}

// Test that a theme extension is disabled when not in use. A theme may be
// installed but not in use if it there is an infobar to revert to the previous
// theme.
TEST_F(ThemeServiceTest, DisableUnusedTheme) {
  // 1) Installing a theme should disable the previously active theme.
  ThemeScoper scoper1 = LoadUnpackedTheme();
  EXPECT_FALSE(theme_service()->UsingDefaultTheme());
  EXPECT_EQ(scoper1.extension_id(), theme_service()->GetThemeID());
  EXPECT_TRUE(registrar()->IsExtensionEnabled(scoper1.extension_id()));

  // Create theme reinstaller to prevent the current theme from being
  // uninstalled.
  std::unique_ptr<ThemeService::ThemeReinstaller> reinstaller =
      theme_service()->BuildReinstallerForCurrentTheme();

  ThemeScoper scoper2 = LoadUnpackedTheme();
  EXPECT_EQ(scoper2.extension_id(), theme_service()->GetThemeID());
  EXPECT_TRUE(registrar()->IsExtensionEnabled(scoper2.extension_id()));
  EXPECT_TRUE(IsExtensionDisabled(scoper1.extension_id()));

  // 2) Enabling a disabled theme extension should swap the current theme.
  {
    test::ThemeServiceChangedWaiter waiter(theme_service());
    registrar()->EnableExtension(scoper1.extension_id());
    waiter.WaitForThemeChanged();
  }
  EXPECT_EQ(scoper1.extension_id(), theme_service()->GetThemeID());
  EXPECT_TRUE(registrar()->IsExtensionEnabled(scoper1.extension_id()));
  EXPECT_TRUE(IsExtensionDisabled(scoper2.extension_id()));

  // 3) Using RevertToExtensionTheme() with a disabled theme should enable and
  // set the theme. This is the case when the user reverts to the previous theme
  // via an infobar.
  {
    test::ThemeServiceChangedWaiter waiter(theme_service());
    theme_service()->RevertToExtensionTheme(scoper2.extension_id());
    waiter.WaitForThemeChanged();
  }
  EXPECT_EQ(scoper2.extension_id(), theme_service()->GetThemeID());
  EXPECT_TRUE(registrar()->IsExtensionEnabled(scoper2.extension_id()));
  EXPECT_TRUE(IsExtensionDisabled(scoper1.extension_id()));

  // 4) Disabling the current theme extension should revert to the default theme
  // and disable any installed theme extensions.
  EXPECT_FALSE(theme_service()->UsingDefaultTheme());
  registrar()->DisableExtension(
      scoper2.extension_id(),
      {extensions::disable_reason::DISABLE_USER_ACTION});
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(theme_service()->UsingDefaultTheme());
  EXPECT_FALSE(registrar()->IsExtensionEnabled(scoper1.extension_id()));
  EXPECT_FALSE(registrar()->IsExtensionEnabled(scoper2.extension_id()));
}

// Test the ThemeService's behavior when a theme is upgraded.
TEST_F(ThemeServiceTest, ThemeUpgrade) {
  std::unique_ptr<ThemeService::ThemeReinstaller> reinstaller =
      theme_service()->BuildReinstallerForCurrentTheme();

  ThemeScoper scoper1 = LoadUnpackedTheme();
  ThemeScoper scoper2 = LoadUnpackedTheme();

  // Test the initial state.
  EXPECT_TRUE(IsExtensionDisabled(scoper1.extension_id()));
  EXPECT_EQ(scoper2.extension_id(), theme_service()->GetThemeID());

  // 1) Upgrading the current theme should not revert to the default theme.
  test::ThemeServiceChangedWaiter waiter(theme_service());
  UpdateUnpackedTheme(scoper2.extension_id());

  // The ThemeService should have sent an theme change notification even though
  // the id of the current theme did not change.
  waiter.WaitForThemeChanged();

  EXPECT_EQ(scoper2.extension_id(), theme_service()->GetThemeID());
  EXPECT_TRUE(IsExtensionDisabled(scoper1.extension_id()));

  // 2) Upgrading a disabled theme should not change the current theme.
  UpdateUnpackedTheme(scoper1.extension_id());
  EXPECT_EQ(scoper2.extension_id(), theme_service()->GetThemeID());
  EXPECT_TRUE(IsExtensionDisabled(scoper1.extension_id()));
}

TEST_F(ThemeServiceTest, NTPLogoAlternate) {
  // TODO(crbug.com/40666609): Fix ScopedTempDir deletion errors on Win.

  const ui::ThemeProvider& theme_provider =
      ThemeService::GetThemeProviderForProfile(profile());
  {
    ThemeScoper scoper =
        LoadUnpackedTheme("extensions/theme_grey_ntp/manifest.json");
    // When logo alternate is not specified and ntp is grey, logo should be
    // colorful.
    EXPECT_EQ(0, theme_provider.GetDisplayProperty(
                     ThemeProperties::NTP_LOGO_ALTERNATE));
  }

  {
    ThemeScoper scoper =
        LoadUnpackedTheme("extensions/theme_grey_ntp_white_logo/manifest.json");
    // Logo alternate should match what is specified in the manifest.
    EXPECT_EQ(1, theme_provider.GetDisplayProperty(
                     ThemeProperties::NTP_LOGO_ALTERNATE));
  }

  {
    ThemeScoper scoper = LoadUnpackedTheme(
        "extensions/theme_color_ntp_white_logo/manifest.json");
    // When logo alternate is not specified and ntp is colorful, logo should be
    // white.
    EXPECT_EQ(1, theme_provider.GetDisplayProperty(
                     ThemeProperties::NTP_LOGO_ALTERNATE));
  }

  {
    ThemeScoper scoper = LoadUnpackedTheme(
        "extensions/theme_color_ntp_colorful_logo/manifest.json");
    // Logo alternate should match what is specified in the manifest.
    EXPECT_EQ(0, theme_provider.GetDisplayProperty(
                     ThemeProperties::NTP_LOGO_ALTERNATE));
  }
}

// crbug.com/41163322
TEST_F(ThemeServiceTest, UninstallThemeWhenNoReinstallers) {
  ThemeScoper scoper1 = LoadUnpackedTheme();
  ASSERT_EQ(scoper1.extension_id(), theme_service()->GetThemeID());

  ThemeScoper scoper2;
  {
    // Show an infobar.
    std::unique_ptr<ThemeService::ThemeReinstaller> reinstaller =
        theme_service()->BuildReinstallerForCurrentTheme();

    // Install another theme. The first extension shouldn't be uninstalled yet
    // as it should be possible to revert to it.
    scoper2 = LoadUnpackedTheme();
    EXPECT_TRUE(IsExtensionDisabled(scoper1.extension_id()));
    EXPECT_EQ(scoper2.extension_id(), theme_service()->GetThemeID());

    test::ThemeServiceChangedWaiter waiter(theme_service());
    reinstaller->Reinstall();
    waiter.WaitForThemeChanged();
    EXPECT_TRUE(IsExtensionDisabled(scoper2.extension_id()));
    EXPECT_EQ(scoper1.extension_id(), theme_service()->GetThemeID());
  }

  // extension 2 should get uninstalled as no reinstallers are in scope.
  base::RunLoop().RunUntilIdle();
  EXPECT_FALSE(registry()->GetInstalledExtension(scoper2.extension_id()));
}

TEST_F(ThemeServiceTest, BuildFromColorTest) {
  // Set theme from color.
  theme_service()->UseDefaultTheme();
  EXPECT_TRUE(theme_service()->UsingDefaultTheme());
  EXPECT_FALSE(theme_service()->UsingAutogeneratedTheme());
  theme_service()->BuildAutogeneratedThemeFromColor(
      SkColorSetRGB(100, 100, 100));
  EXPECT_FALSE(theme_service()->UsingDefaultTheme());
  EXPECT_TRUE(theme_service()->UsingAutogeneratedTheme());

  // Set theme from data pack and then override it with theme from color.
  ThemeScoper scoper = LoadUnpackedTheme();
  EXPECT_EQ(scoper.extension_id(), theme_service()->GetThemeID());
  EXPECT_FALSE(theme_service()->UsingDefaultTheme());
  EXPECT_FALSE(theme_service()->UsingAutogeneratedTheme());
  base::FilePath path =
      pref_service()->GetFilePath(prefs::kCurrentThemePackFilename);
  EXPECT_FALSE(path.empty());

  theme_service()->BuildAutogeneratedThemeFromColor(
      SkColorSetRGB(100, 100, 100));
  EXPECT_FALSE(theme_service()->UsingDefaultTheme());
  EXPECT_TRUE(theme_service()->UsingAutogeneratedTheme());
  EXPECT_EQ(ThemeService::kAutogeneratedThemeID, theme_service()->GetThemeID());
  path = pref_service()->GetFilePath(prefs::kCurrentThemePackFilename);
  EXPECT_TRUE(path.empty());
}

TEST_F(ThemeServiceTest, BuildFromColor_DisableExtensionTest) {
  ThemeScoper scoper = LoadUnpackedTheme();
  EXPECT_EQ(scoper.extension_id(), theme_service()->GetThemeID());
  EXPECT_TRUE(registrar()->IsExtensionEnabled(scoper.extension_id()));

  // Setting autogenerated theme should disable previous theme.
  theme_service()->BuildAutogeneratedThemeFromColor(
      SkColorSetRGB(100, 100, 100));
  EXPECT_TRUE(theme_service()->UsingAutogeneratedTheme());
  EXPECT_FALSE(registrar()->IsExtensionEnabled(scoper.extension_id()));
}

TEST_F(ThemeServiceTest, UseDefaultTheme_DisableExtensionTest) {
  ThemeScoper scoper = LoadUnpackedTheme();
  EXPECT_EQ(scoper.extension_id(), theme_service()->GetThemeID());
  EXPECT_TRUE(registrar()->IsExtensionEnabled(scoper.extension_id()));

  // Resetting to default theme should disable previous theme.
  theme_service()->UseDefaultTheme();
  EXPECT_FALSE(registrar()->IsExtensionEnabled(scoper.extension_id()));
}

// Test that setting theme to default resets the NTP theme as well.
TEST_F(ThemeServiceTest, UseDefaultTheme_DisableNtpThemeTest) {
  NtpCustomBackgroundServiceFactory::GetForProfile(profile());
  base::DictValue test_background_info;
  test_background_info.Set("test_data", "foo");
  pref_service()->SetDict(prefs::kNtpCustomBackgroundDict,
                          std::move(test_background_info));

  const base::DictValue& background_info_with_theme =
      pref_service()->GetDict(prefs::kNtpCustomBackgroundDict);
  const base::Value* test_data = background_info_with_theme.Find("test_data");
  EXPECT_NE(test_data, nullptr);
  EXPECT_NE(test_data->GetIfString(), nullptr);

  theme_service()->UseDefaultTheme();
  const base::DictValue& background_info_without_theme =
      pref_service()->GetDict(prefs::kNtpCustomBackgroundDict);
  EXPECT_EQ(background_info_without_theme.Find("test_data"), nullptr);
}

TEST_P(ColorProviderTest, OmniboxContrast) {
  // TODO(crbug.com/40847629): Windows platform high contrast colors are
  // not sufficiently high-contrast to pass this test.
#if BUILDFLAG(IS_WIN)
  if (GetPreferredContrast() == ui::NativeTheme::PreferredContrast::kMore) {
    return;
  }
#endif
#if BUILDFLAG(IS_LINUX)
  // TODO(crbug.com/41494383): Linux platform native dark mode colors aren't
  //                      sufficiently high contrast to pass.
  if (GetSystemTheme() == SystemTheme::kCustom &&
      GetPreferredColorScheme() ==
          ui::NativeTheme::PreferredColorScheme::kDark) {
    return;
  }
#endif

  constexpr ui::ColorId contrasting_ids[][2] = {
      {kColorOmniboxResultsIcon, kColorOmniboxResultsBackground},
      {kColorOmniboxResultsIcon, kColorOmniboxResultsBackgroundHovered},
      {kColorOmniboxResultsIconSelected,
       kColorOmniboxResultsBackgroundSelected},
      {kColorOmniboxResultsTextSelected,
       kColorOmniboxResultsBackgroundSelected},
      {kColorOmniboxResultsTextDimmed, kColorOmniboxResultsBackground},
      {kColorOmniboxResultsTextDimmed, kColorOmniboxResultsBackgroundHovered},
      {kColorOmniboxResultsTextDimmedSelected,
       kColorOmniboxResultsBackgroundSelected},
      {kColorOmniboxResultsTextNegative, kColorOmniboxResultsBackground},
      {kColorOmniboxResultsTextNegative, kColorOmniboxResultsBackgroundHovered},
      {kColorOmniboxResultsTextNegativeSelected,
       kColorOmniboxResultsBackgroundSelected},
      {kColorOmniboxResultsTextPositive, kColorOmniboxResultsBackground},
      {kColorOmniboxResultsTextPositive, kColorOmniboxResultsBackgroundHovered},
      {kColorOmniboxResultsTextPositiveSelected,
       kColorOmniboxResultsBackgroundSelected},
      {kColorOmniboxResultsTextSecondary, kColorOmniboxResultsBackground},
      {kColorOmniboxResultsTextSecondary,
       kColorOmniboxResultsBackgroundHovered},
      {kColorOmniboxResultsTextSecondarySelected,
       kColorOmniboxResultsBackgroundSelected},
      {kColorOmniboxResultsUrl, kColorOmniboxResultsBackground},
      {kColorOmniboxResultsUrl, kColorOmniboxResultsBackgroundHovered},
      {kColorOmniboxResultsUrlSelected, kColorOmniboxResultsBackgroundSelected},
      {kColorOmniboxBubbleOutline, kColorOmniboxResultsBackground},
      {kColorOmniboxSecurityChipDefault, kColorToolbarBackgroundSubtleEmphasis},
      {kColorOmniboxSecurityChipDefault,
       kColorToolbarBackgroundSubtleEmphasisHovered},
      {kColorOmniboxSecurityChipSecure, kColorToolbarBackgroundSubtleEmphasis},
      {kColorOmniboxSecurityChipSecure,
       kColorToolbarBackgroundSubtleEmphasisHovered},
      {kColorOmniboxSecurityChipDangerous,
       kColorToolbarBackgroundSubtleEmphasis},
      {kColorOmniboxSecurityChipDangerous,
       kColorToolbarBackgroundSubtleEmphasisHovered},
      {kColorOmniboxKeywordSelected, kColorToolbarBackgroundSubtleEmphasis},
      {kColorOmniboxKeywordSelected,
       kColorToolbarBackgroundSubtleEmphasisHovered},
      {kColorOmniboxText, kColorToolbarBackgroundSubtleEmphasis},
      {kColorOmniboxText, kColorToolbarBackgroundSubtleEmphasisHovered},
      {kColorOmniboxText, kColorOmniboxResultsBackground},
      {kColorOmniboxText, kColorOmniboxResultsBackgroundHovered},
      {kColorOmniboxTextDimmed, kColorToolbarBackgroundSubtleEmphasis},
      {kColorOmniboxTextDimmed, kColorToolbarBackgroundSubtleEmphasisHovered},
  };
  auto check_sufficient_contrast =
      [&](ui::ColorId id1, ui::ColorId id2,
          float expected_contrast_ratio =
              color_utils::kMinimumReadableContrastRatio) {
        const theme_service::test::PrintableSkColor color1{GetColor(id1)};
        const theme_service::test::PrintableSkColor color2{GetColor(id2)};
        const float contrast =
            color_utils::GetContrastRatio(color1.color, color2.color);
        EXPECT_GE(contrast, expected_contrast_ratio)
            << "Color 1: " << theme_service::test::ColorIdToString(id1) << " - "
            << color1
            << "\nColor 2: " << theme_service::test::ColorIdToString(id2)
            << " - " << color2;
      };
  for (const ui::ColorId* ids : contrasting_ids) {
    check_sufficient_contrast(ids[0], UNSAFE_TODO(ids[1]));
  }
#if !BUILDFLAG(USE_GTK)
  // TODO(crbug.com/40847971): GTK does not have a sufficiently
  // high-contrast selected row color to pass this test.
  if (GetPreferredContrast() == ui::NativeTheme::PreferredContrast::kMore) {
    check_sufficient_contrast(kColorOmniboxResultsBackgroundSelected,
                              kColorOmniboxResultsBackground,
                              color_utils::kMinimumVisibleContrastRatio);
  }
#endif
}

// Sets and unsets themes using the BrowserThemeColor policy.
TEST_F(ThemeServiceTest, PolicyThemeColorSet) {
  theme_service()->UseDefaultTheme();
  EXPECT_TRUE(theme_service()->UsingDefaultTheme());
  EXPECT_FALSE(theme_service()->UsingAutogeneratedTheme());
  EXPECT_FALSE(theme_service()->UsingPolicyTheme());

  // Setting a blank policy color shouldn't cause any theme updates.
  pref_service()->ClearPref(themes::prefs::kPolicyThemeColor);
  EXPECT_TRUE(theme_service()->UsingDefaultTheme());
  EXPECT_FALSE(theme_service()->UsingAutogeneratedTheme());
  EXPECT_FALSE(theme_service()->UsingPolicyTheme());

  // Setting a valid policy color causes theme to update. The applied theme is
  // autogenerated based on the policy color.
  testing_profile()->GetTestingPrefService()->SetManagedPref(
      themes::prefs::kPolicyThemeColor, std::make_unique<base::Value>(100));
  EXPECT_FALSE(theme_service()->UsingDefaultTheme());
  EXPECT_TRUE(theme_service()->UsingAutogeneratedTheme());
  EXPECT_TRUE(theme_service()->UsingPolicyTheme());
  // Policy theme is not saved in prefs.
  EXPECT_EQ(theme_service()->GetThemeID(), std::string());

  // Unsetting policy theme and setting autogenerated theme.
  testing_profile()->GetTestingPrefService()->RemoveManagedPref(
      themes::prefs::kPolicyThemeColor);
  theme_service()->BuildAutogeneratedThemeFromColor(
      SkColorSetRGB(100, 100, 100));
  EXPECT_FALSE(theme_service()->UsingDefaultTheme());
  EXPECT_TRUE(theme_service()->UsingAutogeneratedTheme());
  EXPECT_FALSE(theme_service()->UsingPolicyTheme());
  EXPECT_EQ(ThemeService::kAutogeneratedThemeID, theme_service()->GetThemeID());

  // Setting a different policy color.
  testing_profile()->GetTestingPrefService()->SetManagedPref(
      themes::prefs::kPolicyThemeColor, std::make_unique<base::Value>(-100));
  EXPECT_FALSE(theme_service()->UsingDefaultTheme());
  EXPECT_TRUE(theme_service()->UsingAutogeneratedTheme());
  EXPECT_TRUE(theme_service()->UsingPolicyTheme());
  EXPECT_EQ(ThemeService::kAutogeneratedThemeID, theme_service()->GetThemeID());

  // Removing policy color reverts the theme to the one saved in prefs, or
  // the default theme if prefs are empty.
  testing_profile()->GetTestingPrefService()->RemoveManagedPref(
      themes::prefs::kPolicyThemeColor);
  EXPECT_FALSE(theme_service()->UsingDefaultTheme());
  EXPECT_TRUE(theme_service()->UsingAutogeneratedTheme());
  EXPECT_FALSE(theme_service()->UsingPolicyTheme());
  EXPECT_EQ(ThemeService::kAutogeneratedThemeID, theme_service()->GetThemeID());

  // Install extension theme.
  ThemeScoper scoper = LoadUnpackedTheme();
  EXPECT_TRUE(theme_service()->UsingExtensionTheme());
  EXPECT_FALSE(theme_service()->UsingPolicyTheme());
  EXPECT_EQ(scoper.extension_id(), theme_service()->GetThemeID());
  EXPECT_TRUE(registrar()->IsExtensionEnabled(scoper.extension_id()));
  EXPECT_TRUE(registry()->GetInstalledExtension(scoper.extension_id()));

  // Applying policy theme should unset the extension theme but not disable the
  // extension..
  testing_profile()->GetTestingPrefService()->SetManagedPref(
      themes::prefs::kPolicyThemeColor, std::make_unique<base::Value>(100));
  EXPECT_FALSE(theme_service()->UsingExtensionTheme());
  EXPECT_TRUE(theme_service()->UsingPolicyTheme());
  EXPECT_EQ(scoper.extension_id(), theme_service()->GetThemeID());
  EXPECT_TRUE(registrar()->IsExtensionEnabled(scoper.extension_id()));
  EXPECT_TRUE(registry()->GetInstalledExtension(scoper.extension_id()));

  // Cannot set other themes while a policy theme is applied.
  theme_service()->BuildAutogeneratedThemeFromColor(
      SkColorSetRGB(100, 100, 100));
  theme_service()->UseDefaultTheme();
  EXPECT_FALSE(theme_service()->UsingDefaultTheme());
  EXPECT_TRUE(theme_service()->UsingAutogeneratedTheme());
  EXPECT_FALSE(theme_service()->UsingExtensionTheme());
  EXPECT_TRUE(theme_service()->UsingPolicyTheme());

  // Removing policy color unsets the policy theme and restores the extension
  // theme.
  testing_profile()->GetTestingPrefService()->RemoveManagedPref(
      themes::prefs::kPolicyThemeColor);
  EXPECT_FALSE(theme_service()->UsingDefaultTheme());
  EXPECT_FALSE(theme_service()->UsingAutogeneratedTheme());
  EXPECT_TRUE(theme_service()->UsingExtensionTheme());
  EXPECT_FALSE(theme_service()->UsingPolicyTheme());
  EXPECT_EQ(scoper.extension_id(), theme_service()->GetThemeID());
  EXPECT_TRUE(registrar()->IsExtensionEnabled(scoper.extension_id()));
  EXPECT_TRUE(registry()->GetInstalledExtension(scoper.extension_id()));
}

TEST_F(ThemeServiceTest, UserColor) {
  // Default should be empty.
  EXPECT_EQ(theme_service()->GetUserColor(), std::nullopt);

  // Set an autogenerated theme with background prefs.
  base::DictValue background_info;
  background_info.Set("test_data", "foo");
  pref_service()->SetDict(prefs::kNtpCustomBackgroundDict,
                          std::move(background_info));
  theme_service()->BuildAutogeneratedThemeFromColor(SK_ColorBLUE);
  EXPECT_EQ(theme_service()->GetAutogeneratedThemeColor(), SK_ColorBLUE);

  // Set kUserColor. It should remove the above theme but not the background.
  theme_service()->SetUserColor(SK_ColorGREEN);
  EXPECT_EQ(theme_service()->GetUserColor(), SK_ColorGREEN);
  EXPECT_EQ(theme_service()->GetAutogeneratedThemeColor(), SK_ColorTRANSPARENT);
  EXPECT_EQ(*pref_service()
                 ->GetDict(prefs::kNtpCustomBackgroundDict)
                 .FindString("test_data"),
            "foo");
  EXPECT_EQ(ThemeService::kUserColorThemeID, theme_service()->GetThemeID());
}

TEST_F(ThemeServiceTest, SetUserColorNullopt) {
  // Default should be empty.
  EXPECT_EQ(theme_service()->GetUserColor(), std::nullopt);

  // Set an autogenerated theme with background prefs.
  base::DictValue background_info;
  background_info.Set("test_data", "foo");
  pref_service()->SetDict(prefs::kNtpCustomBackgroundDict,
                          std::move(background_info));
  theme_service()->BuildAutogeneratedThemeFromColor(SK_ColorBLUE);
  EXPECT_EQ(theme_service()->GetAutogeneratedThemeColor(), SK_ColorBLUE);

  // Set nullopt user color. It should remove the above theme but not the
  // background.
  theme_service()->SetUserColor(std::nullopt);
  EXPECT_EQ(theme_service()->GetUserColor(), std::nullopt);
  EXPECT_EQ(theme_service()->GetAutogeneratedThemeColor(), SK_ColorTRANSPARENT);
  EXPECT_EQ(*pref_service()
                 ->GetDict(prefs::kNtpCustomBackgroundDict)
                 .FindString("test_data"),
            "foo");

  // Set nullopt user color. It should switch to kDefaultThemeID.
  EXPECT_EQ(ThemeHelper::kDefaultThemeID, theme_service()->GetThemeID());
}

TEST_F(ThemeServiceTest, ThemeResetClearsUserColor) {
  theme_service()->SetUserColor(SK_ColorGREEN);
  EXPECT_EQ(theme_service()->GetUserColor(), SK_ColorGREEN);

  // Check that kUserColor is removed as part of theme reset.
  theme_service()->SetIsGrayscale(true);
  EXPECT_EQ(std::nullopt, theme_service()->GetUserColor());
  EXPECT_EQ(ThemeHelper::kDefaultThemeID, theme_service()->GetThemeID());
}

TEST_F(ThemeServiceTest, IsBaseline) {
  theme_service()->SetUserColor(SK_ColorGREEN);
  EXPECT_FALSE(theme_service()->GetIsBaseline());

  theme_service()->SetUserColor(std::nullopt);
  EXPECT_TRUE(theme_service()->GetIsBaseline());
}

TEST_F(ThemeServiceTest, IsGrayscale) {
  // Default should be false.
  EXPECT_FALSE(theme_service()->GetIsGrayscale());

  // Set an autogenerated theme with background prefs.
  base::DictValue background_info;
  background_info.Set("test_data", "foo");
  pref_service()->SetDict(prefs::kNtpCustomBackgroundDict,
                          std::move(background_info));
  theme_service()->BuildAutogeneratedThemeFromColor(SK_ColorBLUE);
  EXPECT_EQ(theme_service()->GetAutogeneratedThemeColor(), SK_ColorBLUE);

  // Set kIsGrayscale. It should remove the above theme but not the background.
  theme_service()->SetIsGrayscale(true);
  EXPECT_TRUE(theme_service()->GetIsGrayscale());
  EXPECT_EQ(theme_service()->GetAutogeneratedThemeColor(), SK_ColorTRANSPARENT);
  EXPECT_EQ(*pref_service()
                 ->GetDict(prefs::kNtpCustomBackgroundDict)
                 .FindString("test_data"),
            "foo");
}

TEST_F(ThemeServiceTest, ThemeResetClearsIsGrayscale) {
  theme_service()->SetIsGrayscale(true);
  EXPECT_TRUE(theme_service()->GetIsGrayscale());

  // Check that kIsGrayscale is removed as part of theme reset.
  theme_service()->SetUserColor(SK_ColorBLUE);
  EXPECT_FALSE(theme_service()->GetIsGrayscale());
}

TEST_F(ThemeServiceTest, SetUserColorAndBrowserColorVariant) {
  // Default should be empty and system.
  EXPECT_EQ(theme_service()->GetUserColor(), std::nullopt);
  EXPECT_EQ(theme_service()->GetBrowserColorVariant(),
            ui::mojom::BrowserColorVariant::kSystem);

  // Set an autogenerated theme with background prefs.
  base::DictValue background_info;
  background_info.Set("test_data", "foo");
  pref_service()->SetDict(prefs::kNtpCustomBackgroundDict,
                          std::move(background_info));
  theme_service()->BuildAutogeneratedThemeFromColor(SK_ColorBLUE);
  EXPECT_EQ(theme_service()->GetAutogeneratedThemeColor(), SK_ColorBLUE);

  // Set kUserColor and kBrowserColorVariant. It should remove the above
  // theme but not the background.
  theme_service()->SetUserColorAndBrowserColorVariant(
      SK_ColorGREEN, ui::mojom::BrowserColorVariant::kTonalSpot);
  EXPECT_EQ(theme_service()->GetUserColor(), SK_ColorGREEN);
  EXPECT_EQ(theme_service()->GetBrowserColorVariant(),
            ui::mojom::BrowserColorVariant::kTonalSpot);
  EXPECT_EQ(theme_service()->GetAutogeneratedThemeColor(), SK_ColorTRANSPARENT);
  EXPECT_EQ(*pref_service()
                 ->GetDict(prefs::kNtpCustomBackgroundDict)
                 .FindString("test_data"),
            "foo");
  EXPECT_EQ(ThemeService::kUserColorThemeID, theme_service()->GetThemeID());
}

TEST_F(ThemeServiceTest, ThemeResetClearsBrowserColorVariant) {
  theme_service()->SetBrowserColorVariant(
      ui::mojom::BrowserColorVariant::kTonalSpot);
  EXPECT_EQ(theme_service()->GetBrowserColorVariant(),
            ui::mojom::BrowserColorVariant::kTonalSpot);

  // Check that kBrowserColorVariant is removed as part of theme reset.
  theme_service()->SetIsGrayscale(true);
  EXPECT_EQ(theme_service()->GetBrowserColorVariant(),
            ui::mojom::BrowserColorVariant::kSystem);
  EXPECT_EQ(ThemeHelper::kDefaultThemeID, theme_service()->GetThemeID());
}

TEST_F(ThemeServiceTest, UseDeviceTheme_DefaultValue) {
  EXPECT_EQ(BUILDFLAG(IS_CHROMEOS), theme_service()->UsingDeviceTheme());
}

#if BUILDFLAG(IS_CHROMEOS)
// Verify that if uses device theme is unset and the user has a chosen color,
// they retain their chosen color.
TEST_F(ThemeServiceTest, UseDeviceTheme_DisabledByUserColor) {
  theme_service()->SetUserColor(SK_ColorGREEN);
  EXPECT_FALSE(theme_service()->UsingDeviceTheme());
}

// Verify that if the user has a theme loaded via extension and uses device
// theme is unset, they retain the extension.
TEST_F(ThemeServiceTest, UseDeviceTheme_DisabledByExtension) {
  ThemeScoper scoper = LoadUnpackedTheme();
  EXPECT_FALSE(theme_service()->UsingDeviceTheme());
}

TEST_F(ThemeServiceTest, UseDeviceTheme_DisabledByAutogeneratedTheme) {
  theme_service()->BuildAutogeneratedThemeFromColor(
      SkColorSetRGB(100, 100, 100));
  ASSERT_TRUE(theme_service()->UsingAutogeneratedTheme());
  EXPECT_FALSE(theme_service()->UsingDeviceTheme());
}

// Verify that if uses device theme has been set by the user, it is still
// true even if the user has a color from a different device.
TEST_F(ThemeServiceTest, UseDeviceTheme_ExplicitlyTrue) {
  theme_service()->UseDeviceTheme(true);
  theme_service()->SetUserColor(SK_ColorGREEN);
  EXPECT_TRUE(theme_service()->UsingDeviceTheme());
}
#endif  // IS_CHROMEOS

#if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_WIN)
// Verify that UseDefaultTheme() clears the system theme follow preference.
TEST_F(ThemeServiceTest, UseDefaultTheme_ClearsSystemThemeFollow) {
  // Set the preference to true first
  theme_service()->UseDeviceTheme(true);
  EXPECT_FALSE(profile()
                   ->GetPrefs()
                   ->FindPreference(prefs::kBrowserFollowsSystemThemeColors)
                   ->IsDefaultValue());

  // Call UseDefaultTheme()
  theme_service()->UseDefaultTheme();

  // Verify the preference was cleared (not set to false, but actually cleared)
  EXPECT_TRUE(profile()
                  ->GetPrefs()
                  ->FindPreference(prefs::kBrowserFollowsSystemThemeColors)
                  ->IsDefaultValue());
}
#endif

TEST_F(ThemeServiceTest, SetUseDeviceTheme) {
  theme_service()->UseDeviceTheme(false);
  EXPECT_FALSE(theme_service()->UsingDeviceTheme());
}

TEST_F(ThemeServiceTest, SetBrowserColorScheme) {
  // Default without anything explicitly set should be kSystem.
  ThemeService::BrowserColorScheme color_scheme =
      theme_service()->GetBrowserColorScheme();
  EXPECT_EQ(color_scheme, ThemeService::BrowserColorScheme::kSystem);

  // Set browser color scheme to light mode.
  theme_service()->SetBrowserColorScheme(
      ThemeService::BrowserColorScheme::kLight);
  color_scheme = theme_service()->GetBrowserColorScheme();

  EXPECT_EQ(color_scheme, ThemeService::BrowserColorScheme::kLight);
}

TEST_F(ThemeServiceTest, ReinstallerRecoversExtension) {
  ThemeScoper scoper1 = LoadUnpackedTheme();
  ASSERT_EQ(scoper1.extension_id(), theme_service()->GetThemeID());

  std::unique_ptr<ThemeService::ThemeReinstaller> reinstaller =
      theme_service()->BuildReinstallerForCurrentTheme();

  // Install another theme.
  ThemeScoper scoper2 = LoadUnpackedTheme();

  EXPECT_TRUE(IsExtensionDisabled(scoper1.extension_id()));
  EXPECT_EQ(scoper2.extension_id(), theme_service()->GetThemeID());

  test::ThemeServiceChangedWaiter waiter(theme_service());
  reinstaller->Reinstall();
  waiter.WaitForThemeChanged();

  EXPECT_TRUE(IsExtensionDisabled(scoper2.extension_id()));
  EXPECT_EQ(scoper1.extension_id(), theme_service()->GetThemeID());
}

TEST_F(ThemeServiceTest, ReinstallerRecoversAutogeneratedColor) {
  theme_service()->BuildAutogeneratedThemeFromColor(SK_ColorBLUE);
  ASSERT_EQ(ThemeService::kAutogeneratedThemeID, theme_service()->GetThemeID());
  ASSERT_EQ(theme_service()->GetAutogeneratedThemeColor(), SK_ColorBLUE);

  std::unique_ptr<ThemeService::ThemeReinstaller> reinstaller =
      theme_service()->BuildReinstallerForCurrentTheme();

  // Install theme.
  ThemeScoper scoper = LoadUnpackedTheme();
  EXPECT_EQ(scoper.extension_id(), theme_service()->GetThemeID());

  test::ThemeServiceChangedWaiter waiter(theme_service());
  reinstaller->Reinstall();
  waiter.WaitForThemeChanged();

  EXPECT_TRUE(IsExtensionDisabled(scoper.extension_id()));
  EXPECT_EQ(ThemeService::kAutogeneratedThemeID, theme_service()->GetThemeID());
  EXPECT_EQ(theme_service()->GetAutogeneratedThemeColor(), SK_ColorBLUE);
}

TEST_F(ThemeServiceTest, ReinstallerRecoversUserColor) {
  theme_service()->SetUserColorAndBrowserColorVariant(
      SK_ColorGREEN, ui::mojom::BrowserColorVariant::kTonalSpot);
  ASSERT_EQ(ThemeService::kUserColorThemeID, theme_service()->GetThemeID());
  ASSERT_EQ(theme_service()->GetUserColor(), SK_ColorGREEN);
  ASSERT_EQ(theme_service()->GetBrowserColorVariant(),
            ui::mojom::BrowserColorVariant::kTonalSpot);
  ASSERT_EQ(theme_service()->GetAutogeneratedThemeColor(), SK_ColorTRANSPARENT);

  std::unique_ptr<ThemeService::ThemeReinstaller> reinstaller =
      theme_service()->BuildReinstallerForCurrentTheme();

  // Create autogenerated theme.
  theme_service()->BuildAutogeneratedThemeFromColor(SK_ColorBLUE);
  EXPECT_EQ(ThemeService::kAutogeneratedThemeID, theme_service()->GetThemeID());
  EXPECT_EQ(theme_service()->GetAutogeneratedThemeColor(), SK_ColorBLUE);

  test::ThemeServiceChangedWaiter waiter(theme_service());
  reinstaller->Reinstall();
  waiter.WaitForThemeChanged();

  EXPECT_EQ(ThemeService::kUserColorThemeID, theme_service()->GetThemeID());
  EXPECT_EQ(theme_service()->GetUserColor(), SK_ColorGREEN);
  EXPECT_EQ(theme_service()->GetBrowserColorVariant(),
            ui::mojom::BrowserColorVariant::kTonalSpot);
  EXPECT_EQ(theme_service()->GetAutogeneratedThemeColor(), SK_ColorTRANSPARENT);
}

TEST_F(ThemeServiceTest, ReinstallerRecoversDefaultTheme) {
  // Default should be empty and system.
  ASSERT_EQ(theme_service()->GetUserColor(), std::nullopt);
  ASSERT_EQ(theme_service()->GetBrowserColorVariant(),
            ui::mojom::BrowserColorVariant::kSystem);

  std::unique_ptr<ThemeService::ThemeReinstaller> reinstaller =
      theme_service()->BuildReinstallerForCurrentTheme();

  // Set user color.
  theme_service()->SetUserColorAndBrowserColorVariant(
      SK_ColorGREEN, ui::mojom::BrowserColorVariant::kTonalSpot);
  EXPECT_EQ(ThemeService::kUserColorThemeID, theme_service()->GetThemeID());
  EXPECT_EQ(theme_service()->GetUserColor(), SK_ColorGREEN);
  EXPECT_EQ(theme_service()->GetBrowserColorVariant(),
            ui::mojom::BrowserColorVariant::kTonalSpot);

  test::ThemeServiceChangedWaiter waiter(theme_service());
  reinstaller->Reinstall();
  waiter.WaitForThemeChanged();

  EXPECT_EQ(ThemeHelper::kDefaultThemeID, theme_service()->GetThemeID());
  EXPECT_EQ(theme_service()->GetUserColor(), std::nullopt);
  EXPECT_EQ(theme_service()->GetBrowserColorVariant(),
            ui::mojom::BrowserColorVariant::kSystem);
}

TEST_F(ThemeServiceTest, RemoveUnusedThemesExemptsSavedLocalTheme) {
  base::test::ScopedFeatureList feature_list{
      syncer::kSeparateLocalAndAccountThemes};

  ThemeScoper scoper1 = LoadUnpackedTheme();
  ASSERT_EQ(scoper1.extension_id(), theme_service()->GetThemeID());

  // Save the first theme as the saved local theme. This is achieved by invoking
  // `WillStartInitialSync()` which this test assumes does precisely this
  // (verified via ASSERT_EQ below).
  theme_service()->GetThemeSyncableService()->WillStartInitialSync();
  ASSERT_EQ(scoper1.extension_id(), theme_service()
                                        ->GetThemeSyncableService()
                                        ->GetSavedLocalThemeExtensionID());

  // Install another theme. The first extension shouldn't be uninstalled as it's
  // the current theme (when it was installed) and also because it's the saved
  // local theme.
  ThemeScoper scoper2 = LoadUnpackedTheme();
  EXPECT_TRUE(IsExtensionDisabled(scoper1.extension_id()));
  EXPECT_EQ(scoper2.extension_id(), theme_service()->GetThemeID());

  // Both themes should still be installed.
  EXPECT_TRUE(registry()->GetInstalledExtension(scoper1.extension_id()));
  EXPECT_TRUE(registry()->GetInstalledExtension(scoper2.extension_id()));

  // Trigger RemoveUnusedThemes. scoper2 is the current theme, so it shouldn't
  // be removed. scoper1 is the saved local theme, so it also shouldn't be
  // removed.
  theme_service()->RemoveUnusedThemes();
  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(registry()->GetInstalledExtension(scoper1.extension_id()));
  EXPECT_TRUE(registry()->GetInstalledExtension(scoper2.extension_id()));

  // Now clear the saved local theme and try again. scoper1 should be removed
  // now because it's neither the current theme nor the saved local theme.
  pref_service()->ClearPref(prefs::kSavedLocalTheme);
  ASSERT_TRUE(theme_service()
                  ->GetThemeSyncableService()
                  ->GetSavedLocalThemeExtensionID()
                  .empty());
  theme_service()->RemoveUnusedThemes();
  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(registry()->GetInstalledExtension(scoper1.extension_id()));
  EXPECT_TRUE(registry()->GetInstalledExtension(scoper2.extension_id()));
}

TEST_F(ThemeServiceTest, RecordColorSchemeOnLoad) {
  {
    base::HistogramTester histogram_tester;
    theme_service()->SetBrowserColorScheme(
        ThemeService::BrowserColorScheme::kLight);
    InitThemeServiceFromPrefs();
    histogram_tester.ExpectUniqueSample(
        "ChromeColors.ColorSchemeOnLoad",
        ThemeService::BrowserColorScheme::kLight, 1);
  }

  {
    base::HistogramTester histogram_tester;
    theme_service()->SetBrowserColorScheme(
        ThemeService::BrowserColorScheme::kDark);
    InitThemeServiceFromPrefs();
    histogram_tester.ExpectUniqueSample("ChromeColors.ColorSchemeOnLoad",
                                        ThemeService::BrowserColorScheme::kDark,
                                        1);
  }

  {
    base::HistogramTester histogram_tester;
    theme_service()->SetBrowserColorScheme(
        ThemeService::BrowserColorScheme::kSystem);
    InitThemeServiceFromPrefs();
    histogram_tester.ExpectUniqueSample(
        "ChromeColors.ColorSchemeOnLoad",
        ThemeService::BrowserColorScheme::kSystem, 1);
  }
}

TEST_F(ThemeServiceTest, IsolatedProfile_GetThemeProvider) {
  Profile* isolated_profile = CreateIsolatedProfile();

  EXPECT_EQ(&ThemeService::GetThemeProviderForProfile(isolated_profile),
            &theme_service()->GetDefaultThemeProvider());
}

TEST_F(ThemeServiceTest, IsolatedProfile_GetColorProviderKey) {
  Profile* isolated_profile = CreateIsolatedProfile();

  ui::ColorProviderKey key =
      GetColorProviderKey(ui::ColorProviderKey(), isolated_profile);
  EXPECT_EQ(key.color_mode, ui::ColorProviderKey::ColorMode::kLight);
  EXPECT_FALSE(key.user_color.has_value());
  EXPECT_EQ(key.user_color_source,
            ui::ColorProviderKey::UserColorSource::kBaseline);
  EXPECT_FALSE(key.scheme_variant.has_value());
  EXPECT_EQ(key.custom_theme, nullptr);
}

#if BUILDFLAG(IS_LINUX)
TEST(LinuxUiFactoryTest, GetDefaultLinuxUiWithoutTaskRunner) {
  // Ensure no current default sequenced task runner is set.
  ASSERT_FALSE(base::SequencedTaskRunner::HasCurrentDefault());

  // Calling ui::GetDefaultLinuxUi() shouldn't crash, even when there's no
  // SequencedTaskRunner.
  (void)ui::GetDefaultLinuxUi();
  // The return value may be null or non-null depending on environment, but must
  // not crash.
}
#endif

}  // namespace theme_service_internal
