text: // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  // 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 "base/files/file_util.h"
  #include "base/path_service.h"
  #include "chrome/browser/chrome_notification_types.h"
  #include "chrome/browser/extensions/extension_service.h"
  #include "chrome/browser/extensions/extension_service_test_base.h"
  #include "chrome/browser/extensions/unpacked_installer.h"
  #include "chrome/browser/themes/custom_theme_supplier.h"
  #include "chrome/browser/themes/theme_service_factory.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 "content/public/test/test_utils.h"
  #include "extensions/browser/extension_registry.h"
  #include "extensions/browser/uninstall_reason.h"
  #include "extensions/common/extension.h"
  #include "testing/gtest/include/gtest/gtest.h"
  
  #if defined(ENABLE_SUPERVISED_USERS)
  #include "chrome/browser/supervised_user/supervised_user_service.h"
  #include "chrome/browser/supervised_user/supervised_user_service_factory.h"
  #endif
  
  using extensions::ExtensionRegistry;
  
  namespace theme_service_internal {
  
  class ThemeServiceTest : public extensions::ExtensionServiceTestBase {
   public:
    ThemeServiceTest() : is_supervised_(false),
                         registry_(NULL) {}
    ~ThemeServiceTest() override {}
  
    // Moves a minimal theme to |temp_dir_path| and unpacks it from that
    // directory.
    std::string LoadUnpackedThemeAt(const base::FilePath& temp_dir) {
      base::FilePath dst_manifest_path = temp_dir.AppendASCII("manifest.json");
      base::FilePath test_data_dir;
      EXPECT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir));
      base::FilePath src_manifest_path =
          test_data_dir.AppendASCII("extensions/theme_minimal/manifest.json");
      EXPECT_TRUE(base::CopyFile(src_manifest_path, dst_manifest_path));
  
      scoped_refptr<extensions::UnpackedInstaller> installer(
          extensions::UnpackedInstaller::Create(service_));
      content::WindowedNotificationObserver observer(
          extensions::NOTIFICATION_EXTENSION_LOADED_DEPRECATED,
          content::Source<Profile>(profile_.get()));
      installer->Load(temp_dir);
      observer.Wait();
  
      std::string extension_id =
          content::Details<extensions::Extension>(observer.details())->id();
  
      // Let the ThemeService finish creating the theme pack.
      base::MessageLoop::current()->RunUntilIdle();
  
      return extension_id;
    }
  
    // Update the theme with |extension_id|.
    void UpdateUnpackedTheme(const std::string& extension_id) {
      int updated_notification =
          service_->IsExtensionEnabled(extension_id)
              ? extensions::NOTIFICATION_EXTENSION_LOADED_DEPRECATED
              : extensions::NOTIFICATION_EXTENSION_UPDATE_DISABLED;
  
      const base::FilePath& path =
          service_->GetInstalledExtension(extension_id)->path();
  
      scoped_refptr<extensions::UnpackedInstaller> installer(
          extensions::UnpackedInstaller::Create(service_));
      content::WindowedNotificationObserver observer(updated_notification,
          content::Source<Profile>(profile_.get()));
      installer->Load(path);
      observer.Wait();
  
      // Let the ThemeService finish creating the theme pack.
      base::MessageLoop::current()->RunUntilIdle();
    }
  
    void SetUp() override {
      extensions::ExtensionServiceTestBase::SetUp();
      extensions::ExtensionServiceTestBase::ExtensionServiceInitParams params =
          CreateDefaultInitParams();
      params.profile_is_supervised = is_supervised_;
      InitializeExtensionService(params);
      service_->Init();
      registry_ = ExtensionRegistry::Get(profile_.get());
      ASSERT_TRUE(registry_);
    }
  
    const CustomThemeSupplier* get_theme_supplier(ThemeService* theme_service) {
      return theme_service->get_theme_supplier();
    }
  
   protected:
    bool is_supervised_;
    ExtensionRegistry* registry_;
  
  };
  
  // Installs then uninstalls a theme and makes sure that the ThemeService
  // reverts to the default theme after the uninstall.
  TEST_F(ThemeServiceTest, ThemeInstallUninstall) {
    ThemeService* theme_service =
        ThemeServiceFactory::GetForProfile(profile_.get());
    theme_service->UseDefaultTheme();
    // Let the ThemeService uninstall unused themes.
    base::MessageLoop::current()->RunUntilIdle();
  
    base::ScopedTempDir temp_dir;
    ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
    const std::string& extension_id = LoadUnpackedThemeAt(temp_dir.path());
    EXPECT_FALSE(theme_service->UsingDefaultTheme());
    EXPECT_EQ(extension_id, theme_service->GetThemeID());
  
    // Now uninstall the extension, should revert to the default theme.
    service_->UninstallExtension(extension_id,
                                 extensions::UNINSTALL_REASON_FOR_TESTING,
                                 base::Bind(&base::DoNothing),
                                 NULL);
    EXPECT_TRUE(theme_service->UsingDefaultTheme());
  }
  
  // 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) {
    ThemeService* theme_service =
        ThemeServiceFactory::GetForProfile(profile_.get());
    theme_service->UseDefaultTheme();
    // Let the ThemeService uninstall unused themes.
    base::MessageLoop::current()->RunUntilIdle();
  
    base::ScopedTempDir temp_dir1;
    ASSERT_TRUE(temp_dir1.CreateUniqueTempDir());
    base::ScopedTempDir temp_dir2;
    ASSERT_TRUE(temp_dir2.CreateUniqueTempDir());
  
    // 1) Installing a theme should disable the previously active theme.
    const std::string& extension1_id = LoadUnpackedThemeAt(temp_dir1.path());
    EXPECT_FALSE(theme_service->UsingDefaultTheme());
    EXPECT_EQ(extension1_id, theme_service->GetThemeID());
    EXPECT_TRUE(service_->IsExtensionEnabled(extension1_id));
  
    // Show an infobar to prevent the current theme from being uninstalled.
    theme_service->OnInfobarDisplayed();
  
    const std::string& extension2_id = LoadUnpackedThemeAt(temp_dir2.path());
    EXPECT_EQ(extension2_id, theme_service->GetThemeID());
    EXPECT_TRUE(service_->IsExtensionEnabled(extension2_id));
    EXPECT_TRUE(registry_->GetExtensionById(extension1_id,
                                            ExtensionRegistry::DISABLED));
  
    // 2) Enabling a disabled theme extension should swap the current theme.
    service_->EnableExtension(extension1_id);
    base::MessageLoop::current()->RunUntilIdle();
    EXPECT_EQ(extension1_id, theme_service->GetThemeID());
    EXPECT_TRUE(service_->IsExtensionEnabled(extension1_id));
    EXPECT_TRUE(registry_->GetExtensionById(extension2_id,
                                            ExtensionRegistry::DISABLED));
  
    // 3) Using SetTheme() 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.
    const extensions::Extension* extension2 =
        service_->GetInstalledExtension(extension2_id);
    theme_service->SetTheme(extension2);
    base::MessageLoop::current()->RunUntilIdle();
    EXPECT_EQ(extension2_id, theme_service->GetThemeID());
    EXPECT_TRUE(service_->IsExtensionEnabled(extension2_id));
    EXPECT_TRUE(registry_->GetExtensionById(extension1_id,
                                            ExtensionRegistry::DISABLED));
  
    // 4) Disabling the current theme extension should revert to the default theme
    // and uninstall any installed theme extensions.
    theme_service->OnInfobarDestroyed();
    EXPECT_FALSE(theme_service->UsingDefaultTheme());
    service_->DisableExtension(extension2_id,
        extensions::Extension::DISABLE_USER_ACTION);
    base::MessageLoop::current()->RunUntilIdle();
    EXPECT_TRUE(theme_service->UsingDefaultTheme());
    EXPECT_FALSE(service_->GetInstalledExtension(extension1_id));
    EXPECT_FALSE(service_->GetInstalledExtension(extension2_id));
  }
  
  // Test the ThemeService's behavior when a theme is upgraded.
  TEST_F(ThemeServiceTest, ThemeUpgrade) {
    // Setup.
    ThemeService* theme_service =
        ThemeServiceFactory::GetForProfile(profile_.get());
    theme_service->UseDefaultTheme();
    // Let the ThemeService uninstall unused themes.
    base::MessageLoop::current()->RunUntilIdle();
  
    theme_service->OnInfobarDisplayed();
  
    base::ScopedTempDir temp_dir1;
    ASSERT_TRUE(temp_dir1.CreateUniqueTempDir());
    base::ScopedTempDir temp_dir2;
    ASSERT_TRUE(temp_dir2.CreateUniqueTempDir());
  
    const std::string& extension1_id = LoadUnpackedThemeAt(temp_dir1.path());
    const std::string& extension2_id = LoadUnpackedThemeAt(temp_dir2.path());
  
    // Test the initial state.
    EXPECT_TRUE(registry_->GetExtensionById(extension1_id,
                                            ExtensionRegistry::DISABLED));
    EXPECT_EQ(extension2_id, theme_service->GetThemeID());
  
    // 1) Upgrading the current theme should not revert to the default theme.
    content::WindowedNotificationObserver theme_change_observer(
        chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
        content::Source<ThemeService>(theme_service));
    UpdateUnpackedTheme(extension2_id);
  
    // The ThemeService should have sent an theme change notification even though
    // the id of the current theme did not change.
    theme_change_observer.Wait();
  
    EXPECT_EQ(extension2_id, theme_service->GetThemeID());
    EXPECT_TRUE(registry_->GetExtensionById(extension1_id,
                                            ExtensionRegistry::DISABLED));
  
    // 2) Upgrading a disabled theme should not change the current theme.
    UpdateUnpackedTheme(extension1_id);
    EXPECT_EQ(extension2_id, theme_service->GetThemeID());
    EXPECT_TRUE(registry_->GetExtensionById(extension1_id,
                                            ExtensionRegistry::DISABLED));
  }
  
  #if defined(ENABLE_SUPERVISED_USERS)
  class ThemeServiceSupervisedUserTest : public ThemeServiceTest {
   public:
    ThemeServiceSupervisedUserTest() {}
    ~ThemeServiceSupervisedUserTest() override {}
  
    void SetUp() override {
      is_supervised_ = true;
      ThemeServiceTest::SetUp();
    }
  };
  
  // Checks that supervised users have their own default theme.
  TEST_F(ThemeServiceSupervisedUserTest,
         SupervisedUserThemeReplacesDefaultTheme) {
    ThemeService* theme_service =
        ThemeServiceFactory::GetForProfile(profile_.get());
    theme_service->UseDefaultTheme();
    EXPECT_TRUE(theme_service->UsingDefaultTheme());
    EXPECT_TRUE(get_theme_supplier(theme_service));
    EXPECT_EQ(get_theme_supplier(theme_service)->get_theme_type(),
              CustomThemeSupplier::SUPERVISED_USER_THEME);
  }
  
  #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
  // Checks that supervised users don't use the system theme even if it is the
  // default. The system theme is only available on Linux.
  TEST_F(ThemeServiceSupervisedUserTest, SupervisedUserThemeReplacesNativeTheme) {
    profile_->GetPrefs()->SetBoolean(prefs::kUsesSystemTheme, true);
    ThemeService* theme_service =
        ThemeServiceFactory::GetForProfile(profile_.get());
    theme_service->UseDefaultTheme();
    EXPECT_TRUE(theme_service->UsingDefaultTheme());
    EXPECT_TRUE(get_theme_supplier(theme_service));
    EXPECT_EQ(get_theme_supplier(theme_service)->get_theme_type(),
              CustomThemeSupplier::SUPERVISED_USER_THEME);
  }
  #endif // defined(OS_LINUX) && !defined(OS_CHROMEOS)
  #endif // defined(ENABLE_SUPERVISED_USERS)
  
  }; // namespace theme_service_internal
  