// 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/browser_theme_pack.h"

#include <stddef.h>

#include <algorithm>
#include <array>
#include <memory>
#include <string_view>

#include "base/containers/flat_map.h"
#include "base/containers/span.h"
#include "base/files/scoped_temp_dir.h"
#include "base/json/json_file_value_serializer.h"
#include "base/json/json_reader.h"
#include "base/path_service.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/browser/themes/theme_properties.h"
#include "chrome/browser/ui/color/chrome_color_id.h"
#include "chrome/browser/ui/frame/window_frame_util.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/extensions/manifest_handlers/theme_handler.h"
#include "chrome/common/themes/autogenerated_theme_util.h"
#include "chrome/grit/theme_resources.h"
#include "components/tab_groups/tab_group_color.h"
#include "content/public/test/browser_task_environment.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_resource.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/color/color_mixer.h"
#include "ui/color/color_provider.h"
#include "ui/color/color_provider_key.h"
#include "ui/color/color_recipe.h"
#include "ui/color/color_test_ids.h"
#include "ui/color/dynamic_color/palette_factory.h"
#include "ui/gfx/color_palette.h"
#include "ui/gfx/color_utils.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/image/image_skia_rep.h"

using extensions::Extension;
using TP = ThemeProperties;
using ThemeType = ui::ColorProviderKey::ThemeInitializerSupplier::ThemeType;

// Maps scale factors (enum values) to file path.
// A similar typedef in BrowserThemePack is private.
using TestScaleFactorToFileMap =
    base::flat_map<ui::ResourceScaleFactor, base::FilePath>;

// Maps image ids to maps of scale factors to file paths.
// A similar typedef in BrowserThemePack is private.
using TestFilePathMap =
    base::flat_map<BrowserThemePack::PersistentID, TestScaleFactorToFileMap>;

namespace {

extensions::ExtensionResource ExtensionResourceFromFilePath(
    const base::FilePath& file_path) {
  const extensions::ExtensionId kExtensionId =
      "12345678901234567890123456789012";
  const base::FilePath kExtensionRoot(FILE_PATH_LITERAL("extension_root"));
  return extensions::ExtensionResource(kExtensionId, kExtensionRoot, file_path);
}

extensions::ThemeInfo::ThemeResource ThemeResourceFromRelativePath(
    std::string_view relative_path,
    std::string_view scale = "") {
  return {ExtensionResourceFromFilePath(
              base::FilePath::FromUTF8Unsafe(relative_path)),
          std::string(scale)};
}

}  // namespace

// BrowserThemePackTest --------------------------------------------------------

class BrowserThemePackTest : public ::testing::Test {
 public:
  BrowserThemePackTest();
  ~BrowserThemePackTest() override = default;

 protected:
  // Returns a mapping from each COLOR_* constant to the default value for this
  // constant. Callers get this map, and then modify expected values and then
  // run the resulting thing through VerifyColorMap().
  static std::map<int, SkColor> GetDefaultColorMap();

  void VerifyColorMap(const std::map<int, SkColor>& color_map);
  void LoadColorJSON(const std::string& json);
  void LoadColorDictionary(const base::DictValue* value);
  void LoadTintJSON(const std::string& json);
  void LoadTintDictionary(const base::DictValue* value);
  void LoadDisplayPropertiesJSON(const std::string& json);
  void LoadDisplayPropertiesDictionary(const base::DictValue* value);
  void ParseThemeImages(const extensions::ThemeInfo::ThemeImages* value,
                        TestFilePathMap* out_file_paths);

  bool LoadRawBitmapsTo(const TestFilePathMap& out_file_paths);

  // This function returns void in order to be able use ASSERT_...
  // The BrowserThemePack is returned in |pack|.
  static void BuildFromUnpackedExtension(const base::FilePath& extension_path,
                                         BrowserThemePack* pack);

  // Same as BuildFromUnpackedExtension() but also asserts
  // BrowserThemeBack::is_valid() on the returned browser theme pack.
  static void BuildFromUnpackedExtensionCheckValid(
      const base::FilePath& extension_path,
      BrowserThemePack* pack);

  // Builds the theme represented by an unpacked extension (located in
  // {DIR_TEST_DATA}/extensions/|theme_folder|).
  // Asserts BrowserThemeBack::is_valid().
  // The BrowserThemePack is returned in |pack|.
  static void BuildTestExtensionThemeCheckValid(std::string_view theme_folder,
                                                BrowserThemePack* pack);

  static base::FilePath GetTestExtensionThemePath(
      std::string_view theme_folder);
  static base::FilePath GetStarGazingPath();
  static base::FilePath GetHiDpiThemePath();

  // Verifies the data in star gazing. We do this multiple times for different
  // BrowserThemePack objects to make sure it works in generated and mmapped
  // mode correctly.
  static void VerifyStarGazing(BrowserThemePack* pack);

  static void VerifyHiDpiTheme(BrowserThemePack* pack);

  // Verify that the colors in the theme for |color_id_a| and |color_id_b| are
  // the same.
  static void VerifyColorsMatch(BrowserThemePack* pack,
                                int color_id_a,
                                int color_id_b);

  // Verify calculated color contrasts for autogenerated themes.
  static void VerifyCalculatedColorContrast(base::span<const SkColor> colors,
                                            float contrast_ratio);

  const BrowserThemePack& theme_pack() const { return *theme_pack_; }

  base::FilePath GetTemporaryPakFile(base::FilePath::StringViewType name) {
    if (dir_.IsValid() || dir_.CreateUniqueTempDir()) {
      return dir_.GetPath().Append(name);
    }
    ADD_FAILURE() << "Couldn't create temp dir";
    return base::FilePath();
  }

 private:
  // Transformation for link underline colors.
  static SkColor BuildThirdOpacity(SkColor color_link);

  // Returns the appropriate default color for |id|.
  static SkColor GetDefaultColor(int id);

  static void GenerateDefaultFrameColor(std::map<int, SkColor>* colors,
                                        int color,
                                        int tint,
                                        bool otr);

  ui::test::ScopedSetSupportedResourceScaleFactors
      scoped_set_supported_scale_factors_{{ui::k100Percent, ui::k200Percent}};

  base::ScopedTempDir dir_;
  content::BrowserTaskEnvironment task_environment_;
  scoped_refptr<BrowserThemePack> theme_pack_;
};

BrowserThemePackTest::BrowserThemePackTest()
    : theme_pack_(new BrowserThemePack(ThemeType::kExtension)) {
  theme_pack_->InitEmptyPack();
}

// static
std::map<int, SkColor> BrowserThemePackTest::GetDefaultColorMap() {
  std::map<int, SkColor> colors;

  for (int i = TP::COLOR_BOOKMARK_TEXT; i <= TP::COLOR_TOOLBAR_TEXT; ++i) {
    colors[i] = GetDefaultColor(i);
  }

  GenerateDefaultFrameColor(&colors, TP::COLOR_FRAME_ACTIVE, TP::TINT_FRAME,
                            false);
  GenerateDefaultFrameColor(&colors, TP::COLOR_FRAME_INACTIVE,
                            TP::TINT_FRAME_INACTIVE, false);
  GenerateDefaultFrameColor(&colors, TP::COLOR_FRAME_ACTIVE_INCOGNITO,
                            TP::TINT_FRAME, true);
  GenerateDefaultFrameColor(&colors, TP::COLOR_FRAME_INACTIVE_INCOGNITO,
                            TP::TINT_FRAME_INACTIVE, true);

  return colors;
}

void BrowserThemePackTest::VerifyColorMap(
    const std::map<int, SkColor>& color_map) {
  for (auto it = color_map.begin(); it != color_map.end(); ++it) {
    SkColor color;
    if (!theme_pack_->GetColor(it->first, &color)) {
      color = GetDefaultColor(it->first);
    }
    EXPECT_EQ(it->second, color) << "Color id = " << it->first;
  }
}

void BrowserThemePackTest::LoadColorJSON(const std::string& json) {
  LoadColorDictionary(
      &base::JSONReader::Read(json, base::JSON_PARSE_CHROMIUM_EXTENSIONS)
           ->GetDict());
}

void BrowserThemePackTest::LoadColorDictionary(const base::DictValue* value) {
  theme_pack_->SetColorsFromJSON(value);
  theme_pack_->GenerateFrameColorsFromTints();
}

void BrowserThemePackTest::LoadTintJSON(const std::string& json) {
  LoadTintDictionary(
      &base::JSONReader::Read(json, base::JSON_PARSE_CHROMIUM_EXTENSIONS)
           ->GetDict());
}

void BrowserThemePackTest::LoadTintDictionary(const base::DictValue* value) {
  theme_pack_->SetTintsFromJSON(value);
}

void BrowserThemePackTest::LoadDisplayPropertiesJSON(const std::string& json) {
  LoadDisplayPropertiesDictionary(
      &base::JSONReader::Read(json, base::JSON_PARSE_CHROMIUM_EXTENSIONS)
           ->GetDict());
}

void BrowserThemePackTest::LoadDisplayPropertiesDictionary(
    const base::DictValue* value) {
  theme_pack_->SetDisplayPropertiesFromJSON(value);
}

void BrowserThemePackTest::ParseThemeImages(
    const extensions::ThemeInfo::ThemeImages* value,
    TestFilePathMap* out_file_paths) {
  theme_pack_->ParseImageNamesFromJSON(value, base::FilePath(), out_file_paths);

  // Build the source image list for HasCustomImage().
  theme_pack_->BuildSourceImagesArray(*out_file_paths);
}



bool BrowserThemePackTest::LoadRawBitmapsTo(
    const TestFilePathMap& out_file_paths) {
  return theme_pack_->LoadRawBitmapsTo(out_file_paths, &theme_pack_->images_);
}

// static
void BrowserThemePackTest::BuildFromUnpackedExtension(
    const base::FilePath& extension_path,
    BrowserThemePack* pack) {
  base::FilePath manifest_path = extension_path.AppendASCII("manifest.json");
  std::string error;
  JSONFileValueDeserializer deserializer(manifest_path);
  std::unique_ptr<base::Value> valid_value =
      deserializer.Deserialize(nullptr, &error);
  EXPECT_EQ("", error);
  ASSERT_TRUE(valid_value.get() && valid_value->is_dict());
  std::u16string utf16_error;
  scoped_refptr<Extension> extension(Extension::Create(
      extension_path, extensions::mojom::ManifestLocation::kInvalidLocation,
      valid_value->GetDict(), Extension::NO_FLAGS, &utf16_error));
  ASSERT_TRUE(extension.get());
  ASSERT_EQ(u"", utf16_error);
  BrowserThemePack::BuildFromExtension(extension.get(), pack);
}

// static
void BrowserThemePackTest::BuildFromUnpackedExtensionCheckValid(
    const base::FilePath& extension_path,
    BrowserThemePack* pack) {
  BuildFromUnpackedExtension(extension_path, pack);
  ASSERT_TRUE(pack->is_valid());
}

// static
void BrowserThemePackTest::BuildTestExtensionThemeCheckValid(
    std::string_view theme_folder,
    BrowserThemePack* pack) {
  base::FilePath contrast_theme_path = GetTestExtensionThemePath(theme_folder);
  BuildFromUnpackedExtensionCheckValid(contrast_theme_path, pack);
}

// static
base::FilePath BrowserThemePackTest::GetTestExtensionThemePath(
    std::string_view theme_folder) {
  base::FilePath test_path;
  const bool result = base::PathService::Get(chrome::DIR_TEST_DATA, &test_path);
  DCHECK(result);

  test_path = test_path.AppendASCII("extensions");
  test_path = test_path.AppendASCII(theme_folder);
  return base::FilePath(test_path);
}

// static
base::FilePath BrowserThemePackTest::GetStarGazingPath() {
  base::FilePath test_path;
  const bool result = base::PathService::Get(chrome::DIR_TEST_DATA, &test_path);
  DCHECK(result);

  test_path = test_path.AppendASCII("profiles");
  test_path = test_path.AppendASCII("profile_with_complex_theme");
  test_path = test_path.AppendASCII("Default");
  test_path = test_path.AppendASCII("Extensions");
  test_path = test_path.AppendASCII("mblmlcbknbnfebdfjnolmcapmdofhmme");
  test_path = test_path.AppendASCII("1.1");
  return base::FilePath(test_path);
}

// static
base::FilePath BrowserThemePackTest::GetHiDpiThemePath() {
  base::FilePath test_path;
  const bool result = base::PathService::Get(chrome::DIR_TEST_DATA, &test_path);
  DCHECK(result);

  test_path = test_path.AppendASCII("extensions");
  test_path = test_path.AppendASCII("theme_hidpi");
  return base::FilePath(test_path);
}

// static
void BrowserThemePackTest::VerifyStarGazing(BrowserThemePack* pack) {
  // First check that values we know exist, exist.
  SkColor color;
  EXPECT_TRUE(pack->GetColor(TP::COLOR_BOOKMARK_TEXT, &color));
  EXPECT_EQ(SK_ColorBLACK, color);

  EXPECT_TRUE(pack->GetColor(TP::COLOR_NTP_BACKGROUND, &color));
  EXPECT_EQ(SkColorSetRGB(57, 137, 194), color);

  color_utils::HSL expected = {0.6, 0.553, 0.5};
  color_utils::HSL actual;
  EXPECT_TRUE(pack->GetTint(TP::TINT_BUTTONS, &actual));
  EXPECT_DOUBLE_EQ(expected.h, actual.h);
  EXPECT_DOUBLE_EQ(expected.s, actual.s);
  EXPECT_DOUBLE_EQ(expected.l, actual.l);

  int val;
  EXPECT_TRUE(pack->GetDisplayProperty(TP::NTP_BACKGROUND_ALIGNMENT, &val));
  EXPECT_EQ(TP::ALIGN_TOP, val);

  // The stargazing theme defines the following images:
  EXPECT_TRUE(pack->HasCustomImage(IDR_THEME_BUTTON_BACKGROUND));
  EXPECT_TRUE(pack->HasCustomImage(IDR_THEME_FRAME));
  EXPECT_TRUE(pack->HasCustomImage(IDR_THEME_NTP_BACKGROUND));
  EXPECT_TRUE(pack->HasCustomImage(IDR_THEME_TAB_BACKGROUND));
  EXPECT_TRUE(pack->HasCustomImage(IDR_THEME_TOOLBAR));
  EXPECT_TRUE(pack->HasCustomImage(IDR_THEME_WINDOW_CONTROL_BACKGROUND));

  // Here are a few images that we shouldn't expect because even though
  // they're included in the theme pack, they were autogenerated and
  // therefore shouldn't show up when calling HasCustomImage().
  // Verify they do appear when calling GetImageNamed(), though.
  EXPECT_FALSE(pack->HasCustomImage(IDR_THEME_FRAME_INACTIVE));
  EXPECT_FALSE(pack->GetImageNamed(IDR_THEME_FRAME_INACTIVE).IsEmpty());
  EXPECT_FALSE(pack->HasCustomImage(IDR_THEME_FRAME_INCOGNITO));
  EXPECT_FALSE(pack->GetImageNamed(IDR_THEME_FRAME_INCOGNITO).IsEmpty());
  EXPECT_FALSE(pack->HasCustomImage(IDR_THEME_FRAME_INCOGNITO_INACTIVE));
  EXPECT_FALSE(
      pack->GetImageNamed(IDR_THEME_FRAME_INCOGNITO_INACTIVE).IsEmpty());

  // The overlay images are missing and they do not fall back to the active
  // frame image.
  EXPECT_FALSE(pack->HasCustomImage(IDR_THEME_FRAME_OVERLAY));
  EXPECT_TRUE(pack->GetImageNamed(IDR_THEME_FRAME_OVERLAY).IsEmpty());
  EXPECT_FALSE(pack->HasCustomImage(IDR_THEME_FRAME_OVERLAY_INACTIVE));
  EXPECT_TRUE(pack->GetImageNamed(IDR_THEME_FRAME_OVERLAY_INACTIVE).IsEmpty());

  // The incognito and inactive tab background images are missing, but will
  // still be generated in CreateTabBackgroundImages based on the frame
  // images.
  EXPECT_FALSE(pack->HasCustomImage(IDR_THEME_TAB_BACKGROUND_INACTIVE));
  EXPECT_FALSE(
      pack->GetImageNamed(IDR_THEME_TAB_BACKGROUND_INACTIVE).IsEmpty());
  EXPECT_FALSE(pack->HasCustomImage(IDR_THEME_TAB_BACKGROUND_INCOGNITO));
  EXPECT_FALSE(
      pack->GetImageNamed(IDR_THEME_TAB_BACKGROUND_INCOGNITO).IsEmpty());
  EXPECT_FALSE(
      pack->HasCustomImage(IDR_THEME_TAB_BACKGROUND_INCOGNITO_INACTIVE));
  EXPECT_FALSE(pack->GetImageNamed(IDR_THEME_TAB_BACKGROUND_INCOGNITO_INACTIVE)
                   .IsEmpty());

  // Make sure we don't have phantom data.
  EXPECT_FALSE(pack->GetTint(TP::TINT_FRAME, &actual));
}

// static
void BrowserThemePackTest::VerifyHiDpiTheme(BrowserThemePack* pack) {
  // The high DPI theme defines the following images:
  EXPECT_TRUE(pack->HasCustomImage(IDR_THEME_FRAME));
  EXPECT_TRUE(pack->HasCustomImage(IDR_THEME_FRAME_INACTIVE));
  EXPECT_TRUE(pack->HasCustomImage(IDR_THEME_FRAME_INCOGNITO));
  EXPECT_TRUE(pack->HasCustomImage(IDR_THEME_FRAME_INCOGNITO_INACTIVE));
  EXPECT_TRUE(pack->HasCustomImage(IDR_THEME_TOOLBAR));

  // The high DPI theme does not define the following images:
  EXPECT_FALSE(pack->HasCustomImage(IDR_THEME_TAB_BACKGROUND));
#if !BUILDFLAG(IS_MAC)
  EXPECT_FALSE(pack->HasCustomImage(IDR_THEME_TAB_BACKGROUND_INCOGNITO));
#endif
  EXPECT_FALSE(pack->HasCustomImage(IDR_THEME_NTP_BACKGROUND));
  EXPECT_FALSE(pack->HasCustomImage(IDR_THEME_FRAME_OVERLAY));
  EXPECT_FALSE(pack->HasCustomImage(IDR_THEME_FRAME_OVERLAY_INACTIVE));
  EXPECT_FALSE(pack->HasCustomImage(IDR_THEME_BUTTON_BACKGROUND));
  EXPECT_FALSE(pack->HasCustomImage(IDR_THEME_NTP_ATTRIBUTION));
  EXPECT_FALSE(pack->HasCustomImage(IDR_THEME_WINDOW_CONTROL_BACKGROUND));

  // Compare some known pixel colors at know locations for a theme
  // image where two different PNG files were specified for scales 100%
  // and 200% respectively.
  int idr = IDR_THEME_FRAME;
  gfx::Image image = pack->GetImageNamed(idr);
  EXPECT_FALSE(image.IsEmpty());
  const gfx::ImageSkia* image_skia = image.ToImageSkia();
  ASSERT_TRUE(image_skia);
  // Scale 100%.
  const gfx::ImageSkiaRep& rep1 = image_skia->GetRepresentation(1.0f);
  ASSERT_FALSE(rep1.is_null());
  EXPECT_EQ(80, rep1.GetBitmap().width());
  // Bitmap height won't be cropped now that Vertical Tabs has launched.
  EXPECT_EQ(80, rep1.GetBitmap().height());
  EXPECT_EQ(SkColorSetRGB(255, 255, 255), rep1.GetBitmap().getColor(4, 4));
  EXPECT_EQ(SkColorSetRGB(255, 255, 255), rep1.GetBitmap().getColor(8, 8));
  EXPECT_EQ(SkColorSetRGB(0, 241, 237), rep1.GetBitmap().getColor(16, 16));
  EXPECT_EQ(SkColorSetRGB(255, 255, 255), rep1.GetBitmap().getColor(24, 24));
  EXPECT_EQ(SkColorSetRGB(0, 241, 237), rep1.GetBitmap().getColor(32, 32));
  // Scale 200%.
  const gfx::ImageSkiaRep& rep2 = image_skia->GetRepresentation(2.0f);
  ASSERT_FALSE(rep2.is_null());
  EXPECT_EQ(160, rep2.GetBitmap().width());
  // Bitmap height won't be cropped now that Vertical Tabs has launched.
  EXPECT_EQ(160, rep2.GetBitmap().height());
  EXPECT_EQ(SkColorSetRGB(255, 255, 255), rep2.GetBitmap().getColor(4, 4));
  EXPECT_EQ(SkColorSetRGB(223, 42, 0), rep2.GetBitmap().getColor(8, 8));
  EXPECT_EQ(SkColorSetRGB(223, 42, 0), rep2.GetBitmap().getColor(16, 16));
  EXPECT_EQ(SkColorSetRGB(223, 42, 0), rep2.GetBitmap().getColor(24, 24));
  EXPECT_EQ(SkColorSetRGB(255, 255, 255), rep2.GetBitmap().getColor(32, 32));

  // TODO(sschmitz): I plan to remove the following (to the end of the fct)
  // Reason: this test may be brittle. It depends on details of how we scale
  // an 100% image to an 200% image. If there's filtering etc, this test would
  // break. Also High DPI is new, but scaling from 100% to 200% is not new
  // and need not be tested here. But in the interrim it is useful to verify
  // that this image was scaled and did not appear in the input.

  // Compare pixel colors and locations for a theme image that had
  // only one PNG file specified (for scale 100%). The representation
  // for scale of 200% was computed.
  idr = IDR_THEME_FRAME_INCOGNITO_INACTIVE;
  image = pack->GetImageNamed(idr);
  EXPECT_FALSE(image.IsEmpty());
  image_skia = image.ToImageSkia();
  ASSERT_TRUE(image_skia);
  // Scale 100%.
  const gfx::ImageSkiaRep& rep3 = image_skia->GetRepresentation(1.0f);
  ASSERT_FALSE(rep3.is_null());
  EXPECT_EQ(80, rep3.GetBitmap().width());
  // Bitmap height won't be cropped now that Vertical Tabs has launched.
  EXPECT_EQ(80, rep3.GetBitmap().height());
  // We take samples of colors and locations along the diagonal whenever
  // the color changes. Note these colors are slightly different from
  // the input PNG file due to input processing.
  std::vector<std::pair<int, SkColor>> normal;
  int xy = 0;
  SkColor color = rep3.GetBitmap().getColor(xy, xy);
  normal.emplace_back(xy, color);
  for (xy = 0; xy < 40; ++xy) {
    SkColor next_color = rep3.GetBitmap().getColor(xy, xy);
    if (next_color != color) {
      color = next_color;
      normal.emplace_back(xy, color);
    }
  }
  EXPECT_EQ(static_cast<size_t>(9), normal.size());
  // Scale 200%.
  const gfx::ImageSkiaRep& rep4 = image_skia->GetRepresentation(2.0f);
  ASSERT_FALSE(rep4.is_null());
  EXPECT_EQ(160, rep4.GetBitmap().width());
  // Bitmap height won't be cropped now that Vertical Tabs has launched.
  EXPECT_EQ(160, rep4.GetBitmap().height());
  // We expect the same colors and at locations scaled by 2
  // since this bitmap was scaled by 2.
  for (auto& i : normal) {
    xy = 2 * i.first;
    color = i.second;
    EXPECT_EQ(color, rep4.GetBitmap().getColor(xy, xy));
  }
}

// static
void BrowserThemePackTest::VerifyColorsMatch(BrowserThemePack* pack,
                                             int color_id_a,
                                             int color_id_b) {
  SkColor color_a;
  SkColor color_b;

  bool color_a_set = pack->GetColor(color_id_a, &color_a);
  bool color_b_set = pack->GetColor(color_id_b, &color_b);

  SCOPED_TRACE(testing::Message()
               << "Color A: " << std::hex << color_a << " (ID: " << std::dec
               << color_id_a << "), Color B: " << std::hex << color_b
               << " (ID: " << std::dec << color_id_b << ")");
  EXPECT_TRUE(color_a_set);
  EXPECT_TRUE(color_b_set);
  EXPECT_EQ(color_a, color_b);
}

// static
void BrowserThemePackTest::VerifyCalculatedColorContrast(
    base::span<const SkColor> colors,
    float contrast_ratio) {
  for (const auto& color : colors) {
    scoped_refptr<BrowserThemePack> pack(
        new BrowserThemePack(ThemeType::kAutogenerated));
    BrowserThemePack::BuildFromColor(color, pack.get());

    SkColor frame_color, toolbar_color;
    EXPECT_TRUE(pack->GetColor(TP::COLOR_FRAME_ACTIVE, &frame_color));
    EXPECT_TRUE(pack->GetColor(TP::COLOR_TOOLBAR, &toolbar_color));
    EXPECT_GE(color_utils::GetContrastRatio(frame_color, toolbar_color),
              contrast_ratio);
  }
}

// static
SkColor BrowserThemePackTest::BuildThirdOpacity(SkColor color_link) {
  return SkColorSetA(color_link, SkColorGetA(color_link) / 3);
}

// static
SkColor BrowserThemePackTest::GetDefaultColor(int id) {
  // These colors are no longer provided by `ThemeProperties`, since the theme
  // code does not query for them directly.
  if (id == TP::COLOR_BOOKMARK_TEXT || id == TP::COLOR_OMNIBOX_BACKGROUND ||
      id == TP::COLOR_OMNIBOX_TEXT ||
      id == TP::COLOR_TAB_FOREGROUND_ACTIVE_FRAME_ACTIVE ||
      id == TP::COLOR_TOOLBAR_BUTTON_ICON) {
    return gfx::kPlaceholderColor;
  }

  // Direct incognito IDs need to be mapped back to the non-incognito versions
  // (plus passing "true" for |incognito|) to avoid DCHECK failures.
  switch (id) {
    case TP::COLOR_FRAME_ACTIVE_INCOGNITO:
      return TP::GetDefaultColor(TP::COLOR_FRAME_ACTIVE, true);
    case TP::COLOR_FRAME_INACTIVE_INCOGNITO:
      return TP::GetDefaultColor(TP::COLOR_FRAME_INACTIVE, true);
    case TP::COLOR_TAB_BACKGROUND_INACTIVE_FRAME_ACTIVE_INCOGNITO:
      return TP::GetDefaultColor(TP::COLOR_TAB_BACKGROUND_INACTIVE_FRAME_ACTIVE,
                                 true);
    case TP::COLOR_TAB_BACKGROUND_INACTIVE_FRAME_INACTIVE_INCOGNITO:
      return TP::GetDefaultColor(
          TP::COLOR_TAB_BACKGROUND_INACTIVE_FRAME_INACTIVE, true);
    case TP::COLOR_TAB_FOREGROUND_INACTIVE_FRAME_ACTIVE_INCOGNITO:
      return TP::GetDefaultColor(TP::COLOR_TAB_FOREGROUND_INACTIVE_FRAME_ACTIVE,
                                 true);
    case TP::COLOR_TAB_FOREGROUND_INACTIVE_FRAME_INACTIVE_INCOGNITO:
      return TP::GetDefaultColor(
          TP::COLOR_TAB_FOREGROUND_INACTIVE_FRAME_INACTIVE, true);
    default:
      return TP::GetDefaultColor(id, false);
  }
}

// static
void BrowserThemePackTest::GenerateDefaultFrameColor(
    std::map<int, SkColor>* colors,
    int color,
    int tint,
    bool otr) {
  (*colors)[color] = HSLShift(GetDefaultColor(TP::COLOR_FRAME_ACTIVE),
                              TP::GetDefaultTint(tint, otr));
}

// Actual tests ----------------------------------------------------------------

// 'ntp_section' used to correspond to COLOR_NTP_SECTION, but COLOR_NTP_SECTION
// was since removed because it was never used.  While it was in use,
// COLOR_NTP_HEADER used 'ntp_section' as a fallback when 'ntp_header' was
// absent.  We still preserve this fallback for themes that relied on this.
TEST_F(BrowserThemePackTest, UseSectionColorAsNTPHeader) {
  std::string color_json = "{ \"ntp_section\": [190, 190, 190] }";
  LoadColorJSON(color_json);

  std::map<int, SkColor> colors = GetDefaultColorMap();
  SkColor ntp_color = SkColorSetRGB(190, 190, 190);
  colors[TP::COLOR_NTP_HEADER] = ntp_color;
  VerifyColorMap(colors);
}

TEST_F(BrowserThemePackTest, ProvideNtpHeaderColor) {
  std::string color_json =
      "{ \"ntp_header\": [120, 120, 120], "
      "  \"ntp_section\": [190, 190, 190] }";
  LoadColorJSON(color_json);

  std::map<int, SkColor> colors = GetDefaultColorMap();
  colors[TP::COLOR_NTP_HEADER] = SkColorSetRGB(120, 120, 120);
  VerifyColorMap(colors);
}

TEST_F(BrowserThemePackTest, SupportsAlpha) {
  std::string color_json =
      "{ \"toolbar\": [0, 20, 40, 1], "
      "  \"tab_text\": [60, 80, 100, 1], "
      "  \"tab_background_text\": [120, 140, 160, 0.0], "
      "  \"bookmark_text\": [180, 200, 220, 1.0], "
      "  \"ntp_text\": [240, 255, 0, 0.5] }";
  LoadColorJSON(color_json);

  std::map<int, SkColor> colors = GetDefaultColorMap();
  // Verify that valid alpha values are parsed correctly.
  // The toolbar color's alpha value is intentionally ignored by theme provider.
  colors[TP::COLOR_TOOLBAR] = SkColorSetARGB(255, 0, 20, 40);
  colors[TP::COLOR_TAB_FOREGROUND_ACTIVE_FRAME_ACTIVE] =
      SkColorSetARGB(255, 60, 80, 100);
  colors[TP::COLOR_TAB_FOREGROUND_INACTIVE_FRAME_ACTIVE] =
      SkColorSetARGB(0, 120, 140, 160);
  colors[TP::COLOR_BOOKMARK_TEXT] = SkColorSetARGB(255, 180, 200, 220);
  colors[TP::COLOR_NTP_TEXT] = SkColorSetARGB(128, 240, 255, 0);
  VerifyColorMap(colors);
}

TEST_F(BrowserThemePackTest, OutOfRangeColors) {
  // Ensure colors with out-of-range values are simply ignored.
  std::string color_json =
      "{ \"toolbar\": [0, 20, 40, -1], "
      "  \"tab_text\": [60, 80, 100, 2], "
      "  \"tab_background_text\": [120, 140, 160, 47.6], "
      "  \"bookmark_text\": [256, 0, 0], "
      "  \"ntp_text\": [0, -100, 100] }";
  LoadColorJSON(color_json);

  VerifyColorMap(GetDefaultColorMap());
}

TEST_F(BrowserThemePackTest, CanReadTints) {
  std::string tint_json = "{ \"buttons\": [ 0.5, 0.5, 0.5 ] }";
  LoadTintJSON(tint_json);

  color_utils::HSL expected = {0.5, 0.5, 0.5};
  color_utils::HSL actual = {-1, -1, -1};
  EXPECT_TRUE(theme_pack().GetTint(TP::TINT_BUTTONS, &actual));
  EXPECT_DOUBLE_EQ(expected.h, actual.h);
  EXPECT_DOUBLE_EQ(expected.s, actual.s);
  EXPECT_DOUBLE_EQ(expected.l, actual.l);
}

TEST_F(BrowserThemePackTest, CanReadDisplayProperties) {
  std::string json =
      "{ \"ntp_background_alignment\": \"bottom\", "
      "  \"ntp_background_repeat\": \"repeat-x\", "
      "  \"ntp_logo_alternate\": 0 }";
  LoadDisplayPropertiesJSON(json);

  int out_val;
  EXPECT_TRUE(
      theme_pack().GetDisplayProperty(TP::NTP_BACKGROUND_ALIGNMENT, &out_val));
  EXPECT_EQ(TP::ALIGN_BOTTOM, out_val);

  EXPECT_TRUE(
      theme_pack().GetDisplayProperty(TP::NTP_BACKGROUND_TILING, &out_val));
  EXPECT_EQ(TP::REPEAT_X, out_val);

  EXPECT_TRUE(
      theme_pack().GetDisplayProperty(TP::NTP_LOGO_ALTERNATE, &out_val));
  EXPECT_EQ(0, out_val);
}

TEST_F(BrowserThemePackTest, CanParsePaths) {
  extensions::ThemeInfo::ThemeImages theme_images{
      {"theme_button_background", {ThemeResourceFromRelativePath("one")}},
      {"theme_toolbar", {ThemeResourceFromRelativePath("two", "200")}},
  };
  TestFilePathMap out_file_paths;
  ParseThemeImages(&theme_images, &out_file_paths);

  size_t expected_file_paths = 2u;
  EXPECT_EQ(expected_file_paths, out_file_paths.size());
  // "12" and "5" are internal constants to BrowserThemePack and are
  // PRS_THEME_BUTTON_BACKGROUND and PRS_THEME_TOOLBAR, but they are
  // implementation details that shouldn't be exported.
  // By default the scale factor is for 100%.
  EXPECT_TRUE(base::FilePath(FILE_PATH_LITERAL("one")) ==
              out_file_paths[static_cast<BrowserThemePack::PersistentID>(12)]
                            [ui::k100Percent]);
  EXPECT_TRUE(base::FilePath(FILE_PATH_LITERAL("two")) ==
              out_file_paths[static_cast<BrowserThemePack::PersistentID>(5)]
                            [ui::k200Percent]);
}

TEST_F(BrowserThemePackTest, InvalidPathNames) {
  extensions::ThemeInfo::ThemeImages theme_images{
      {"theme_button_background", {ThemeResourceFromRelativePath("one")}},
      {"not_a_thing", {ThemeResourceFromRelativePath("blah")}},
  };
  TestFilePathMap out_file_paths;
  ParseThemeImages(&theme_images, &out_file_paths);

  // We should have only parsed one valid path out of that mess above.
  EXPECT_EQ(1u, out_file_paths.size());
}

TEST_F(BrowserThemePackTest, InvalidColors) {
  std::string invalid_color =
      "{ \"toolbar\": [\"dog\", \"cat\", [12]], "
      "  \"sound\": \"woof\" }";
  LoadColorJSON(invalid_color);
  std::map<int, SkColor> colors = GetDefaultColorMap();
  VerifyColorMap(colors);
}

TEST_F(BrowserThemePackTest, InvalidTints) {
  std::string tints =
      "{ \"buttons\": [ \"dog\", \"cat\", [\"x\"]], "
      " \"frame\": [-2, 2, 3],"
      " \"frame_incognito_inactive\": [-1, 2, 0.6],"
      " \"invalid\": \"entry\" }";
  LoadTintJSON(tints);

  // We should ignore completely invalid (non-numeric) tints.
  color_utils::HSL actual = {-1, -1, -1};
  EXPECT_FALSE(theme_pack().GetTint(TP::TINT_BUTTONS, &actual));

  // We should change invalid numeric HSL tint components to the special -1 "no
  // change" value.
  EXPECT_TRUE(theme_pack().GetTint(TP::TINT_FRAME, &actual));
  EXPECT_EQ(-1, actual.h);
  EXPECT_EQ(-1, actual.s);
  EXPECT_EQ(-1, actual.l);

  // We should correct partially incorrect inputs as well.
  EXPECT_TRUE(theme_pack().GetTint(TP::TINT_FRAME_INCOGNITO_INACTIVE, &actual));
  EXPECT_EQ(-1, actual.h);
  EXPECT_EQ(-1, actual.s);
  EXPECT_EQ(0.6, actual.l);
}

TEST_F(BrowserThemePackTest, InvalidDisplayProperties) {
  std::string invalid_properties =
      "{ \"ntp_background_alignment\": [15], "
      "  \"junk\": [15.3] }";
  LoadDisplayPropertiesJSON(invalid_properties);

  int out_val;
  EXPECT_FALSE(
      theme_pack().GetDisplayProperty(TP::NTP_BACKGROUND_ALIGNMENT, &out_val));
}

// These four tests should just not cause a segmentation fault.
TEST_F(BrowserThemePackTest, NullPaths) {
  TestFilePathMap out_file_paths;
  ParseThemeImages(nullptr, &out_file_paths);
}

TEST_F(BrowserThemePackTest, NullTints) {
  LoadTintDictionary(nullptr);
}

TEST_F(BrowserThemePackTest, NullColors) {
  LoadColorDictionary(nullptr);
}

TEST_F(BrowserThemePackTest, NullDisplayProperties) {
  LoadDisplayPropertiesDictionary(nullptr);
}

TEST_F(BrowserThemePackTest, TestHasCustomImage) {
  // HasCustomImage should only return true for images that exist in the
  // extension and not for autogenerated images.
  extensions::ThemeInfo::ThemeImages theme_images{
      {"theme_frame", {ThemeResourceFromRelativePath("one")}},
  };
  TestFilePathMap out_file_paths;
  ParseThemeImages(&theme_images, &out_file_paths);

  EXPECT_TRUE(theme_pack().HasCustomImage(IDR_THEME_FRAME));
  EXPECT_FALSE(theme_pack().HasCustomImage(IDR_THEME_FRAME_INCOGNITO));
}

TEST_F(BrowserThemePackTest, TestNonExistantImages) {
  extensions::ThemeInfo::ThemeImages theme_images{
      {"theme_frame", {ThemeResourceFromRelativePath("does_not_exist")}},
  };
  TestFilePathMap out_file_paths;
  ParseThemeImages(&theme_images, &out_file_paths);

  EXPECT_FALSE(LoadRawBitmapsTo(out_file_paths));
}

TEST_F(BrowserThemePackTest, TestCreateColorMixersOmniboxAllValues) {
  // Tests to make sure that all available colors are properly loaded into the
  // color provider.
  ui::ColorProvider provider;
  ui::ColorMixer& mixer = provider.AddMixer();
  mixer[kColorToolbar] = {SK_ColorRED};
  mixer[kColorOmniboxText] = {SK_ColorGREEN};
  mixer[kColorOmniboxResultsBackground] = {SK_ColorBLUE};
  std::string color_json = R"({ "toolbar": [0, 20, 40],
                                "omnibox_text": [60, 80, 100],
                                "omnibox_background": [120, 140, 160] })";
  LoadColorJSON(color_json);
  theme_pack().AddColorMixers(&provider, ui::ColorProviderKey());
  EXPECT_EQ(SkColorSetRGB(0, 20, 40), provider.GetColor(kColorToolbar));
  EXPECT_EQ(SkColorSetRGB(60, 80, 100), provider.GetColor(kColorOmniboxText));
  EXPECT_EQ(SkColorSetRGB(120, 140, 160),
            provider.GetColor(kColorOmniboxResultsBackground));
}

// TODO(erg): This test should actually test more of the built resources from
// the extension data, but for now, exists so valgrind can test some of the
// tricky memory stuff that BrowserThemePack does.
TEST_F(BrowserThemePackTest, CanBuildAndReadPack) {
  base::FilePath file = GetTemporaryPakFile(FILE_PATH_LITERAL("data.pak"));
  ASSERT_FALSE(file.empty());

  // Part 1: Build the pack from an extension.
  {
    base::FilePath star_gazing_path = GetStarGazingPath();
    scoped_refptr<BrowserThemePack> pack(
        new BrowserThemePack(ThemeType::kExtension));
    BuildFromUnpackedExtensionCheckValid(star_gazing_path, pack.get());
    ASSERT_TRUE(pack->WriteToDisk(file));
    VerifyStarGazing(pack.get());
  }

  // Part 2: Try to read back the data pack that we just wrote to disk.
  {
    scoped_refptr<BrowserThemePack> pack = BrowserThemePack::BuildFromDataPack(
        file, "mblmlcbknbnfebdfjnolmcapmdofhmme");
    ASSERT_TRUE(pack.get());
    VerifyStarGazing(pack.get());
  }
}

TEST_F(BrowserThemePackTest, HiDpiThemeTest) {
  base::FilePath file =
      GetTemporaryPakFile(FILE_PATH_LITERAL("theme_data.pak"));
  ASSERT_FALSE(file.empty());

  // Part 1: Build the pack from an extension.
  {
    base::FilePath hidpi_path = GetHiDpiThemePath();
    scoped_refptr<BrowserThemePack> pack(
        new BrowserThemePack(ThemeType::kExtension));
    BuildFromUnpackedExtensionCheckValid(hidpi_path, pack.get());
    ASSERT_TRUE(pack->WriteToDisk(file));
    VerifyHiDpiTheme(pack.get());
  }

  // Part 2: Try to read back the data pack that we just wrote to disk.
  {
    scoped_refptr<BrowserThemePack> pack =
        BrowserThemePack::BuildFromDataPack(file, "gllekhaobjnhgeag");
    ASSERT_TRUE(pack.get());
    VerifyHiDpiTheme(pack.get());
  }
}

// Ensure that, given a theme which only specifies a frame color, the calculated
// caption button background colors appropriately match the frame color.
TEST_F(BrowserThemePackTest, TestWindowControlButtonBGColor_FrameColor) {
  scoped_refptr<BrowserThemePack> pack(
      new BrowserThemePack(ThemeType::kExtension));
  BuildTestExtensionThemeCheckValid("theme_test_captionbutton_framecolor",
                                    pack.get());

  // Verify that control button background colors are matching the frame colors.
  VerifyColorsMatch(pack.get(),
                    TP::COLOR_WINDOW_CONTROL_BUTTON_BACKGROUND_ACTIVE,
                    TP::COLOR_FRAME_ACTIVE);
  VerifyColorsMatch(pack.get(),
                    TP::COLOR_WINDOW_CONTROL_BUTTON_BACKGROUND_INACTIVE,
                    TP::COLOR_FRAME_INACTIVE);
  VerifyColorsMatch(pack.get(),
                    TP::COLOR_WINDOW_CONTROL_BUTTON_BACKGROUND_INCOGNITO_ACTIVE,
                    TP::COLOR_FRAME_ACTIVE_INCOGNITO);
  VerifyColorsMatch(
      pack.get(), TP::COLOR_WINDOW_CONTROL_BUTTON_BACKGROUND_INCOGNITO_INACTIVE,
      TP::COLOR_FRAME_INACTIVE_INCOGNITO);
}

// Ensure that, given a theme which specifies a button background color, the
// calculated caption button background colors appropriately match the button
// background color blended with the frame color.
TEST_F(BrowserThemePackTest, TestWindowControlButtonBGColor_ButtonBGColor) {
  scoped_refptr<BrowserThemePack> pack(
      new BrowserThemePack(ThemeType::kExtension));
  BuildTestExtensionThemeCheckValid("theme_test_captionbutton_buttoncolor",
                                    pack.get());

  SkColor button_bg_color;
  const bool has_button_bg_color =
      pack->GetColor(TP::COLOR_CONTROL_BUTTON_BACKGROUND, &button_bg_color);
  ASSERT_TRUE(has_button_bg_color);
  SkAlpha button_bg_alpha = SkColorGetA(button_bg_color);

  // Account for the alpha modification that happens in WindowsCaptionButton.
  button_bg_alpha =
      WindowFrameUtil::CalculateWindowsCaptionButtonBackgroundAlpha(
          button_bg_alpha);

  struct CaptionButtonColorPair {
    int caption_button_bg_color_id;
    int frame_color_id;
  };
  const CaptionButtonColorPair color_pairs_to_check[] = {
      {TP::COLOR_WINDOW_CONTROL_BUTTON_BACKGROUND_ACTIVE,
       TP::COLOR_FRAME_ACTIVE},
      {TP::COLOR_WINDOW_CONTROL_BUTTON_BACKGROUND_INACTIVE,
       TP::COLOR_FRAME_INACTIVE},
      {TP::COLOR_WINDOW_CONTROL_BUTTON_BACKGROUND_INCOGNITO_ACTIVE,
       TP::COLOR_FRAME_ACTIVE_INCOGNITO},
      {TP::COLOR_WINDOW_CONTROL_BUTTON_BACKGROUND_INCOGNITO_INACTIVE,
       TP::COLOR_FRAME_INACTIVE_INCOGNITO},
  };

  for (const CaptionButtonColorPair& current_pair : color_pairs_to_check) {
    SkColor calculated_button_bg_color;
    SkColor frame_color;

    pack->GetColor(current_pair.caption_button_bg_color_id,
                   &calculated_button_bg_color);
    pack->GetColor(current_pair.frame_color_id, &frame_color);

    SkColor result_color =
        color_utils::AlphaBlend(button_bg_color, frame_color, button_bg_alpha);

    EXPECT_EQ(calculated_button_bg_color, result_color);
  }
}

// Ensure that, given a theme which specifies a light frame color, but a dark
// caption button image, the calculated caption button background color is dark
// (to match the bg image).
TEST_F(BrowserThemePackTest, TestWindowControlButtonBGColor_ButtonBGImage) {
  scoped_refptr<BrowserThemePack> pack(
      new BrowserThemePack(ThemeType::kExtension));
  BuildTestExtensionThemeCheckValid("theme_test_captionbutton_buttonimage",
                                    pack.get());

  // Verify that all of the calculated button background colors are on the
  // 'dark' end of the spectrum.
  int colors_to_check[] = {
      TP::COLOR_WINDOW_CONTROL_BUTTON_BACKGROUND_ACTIVE,
      TP::COLOR_WINDOW_CONTROL_BUTTON_BACKGROUND_INACTIVE,
      TP::COLOR_WINDOW_CONTROL_BUTTON_BACKGROUND_INCOGNITO_ACTIVE,
      TP::COLOR_WINDOW_CONTROL_BUTTON_BACKGROUND_INCOGNITO_INACTIVE,
  };
  for (int color_id : colors_to_check) {
    SkColor control_button_color;
    const bool has_color = pack->GetColor(color_id, &control_button_color);
    EXPECT_TRUE(has_color);
    EXPECT_EQ(SkColorGetA(control_button_color), SK_AlphaOPAQUE);
    EXPECT_TRUE(color_utils::IsDark(control_button_color));
  }
}

// Ensure that specified 'toolbar' button and foreground colors are propagated
// correctly to their dependent colors
TEST_F(BrowserThemePackTest, TestFrameAndToolbarColorPropagation) {
  scoped_refptr<BrowserThemePack> pack(
      new BrowserThemePack(ThemeType::kExtension));
  BuildTestExtensionThemeCheckValid("theme_test_toolbar_color_no_image",
                                    pack.get());

  // Toolbar button icon colors.
  SkColor toolbar_button_icon_hovered_color;
  SkColor toolbar_button_icon_pressed_color;

  EXPECT_TRUE(pack->GetColor(TP::COLOR_TOOLBAR_BUTTON_ICON_HOVERED,
                             &toolbar_button_icon_hovered_color));
  EXPECT_TRUE(pack->GetColor(TP::COLOR_TOOLBAR_BUTTON_ICON_PRESSED,
                             &toolbar_button_icon_pressed_color));

  constexpr SkColor kExpectedToolbarButtonIconColor = SkColorSetRGB(0, 0, 255);
  EXPECT_EQ(toolbar_button_icon_hovered_color, kExpectedToolbarButtonIconColor);
  EXPECT_EQ(toolbar_button_icon_pressed_color, kExpectedToolbarButtonIconColor);

  // Active tab active frame foreground colors.
  SkColor tab_foreground_color;

  EXPECT_TRUE(pack->GetColor(TP::COLOR_TAB_FOREGROUND_ACTIVE_FRAME_INACTIVE,
                             &tab_foreground_color));

  constexpr SkColor kExpectedTabForegroundColor = SkColorSetRGB(255, 0, 0);
  EXPECT_EQ(tab_foreground_color, kExpectedTabForegroundColor);
}

// Ensure that, given an explicit toolbar color and a toolbar image, the output
// color in COLOR_TOOLBAR reflects the explicit color.
TEST_F(BrowserThemePackTest,
       TestToolbarColorComputedFromImageOverridesInputColor) {
  scoped_refptr<BrowserThemePack> pack(
      new BrowserThemePack(ThemeType::kExtension));
  BuildTestExtensionThemeCheckValid(
      "theme_test_toolbar_frame_images_and_colors", pack.get());

  SkColor toolbar_color;
  EXPECT_TRUE(pack->GetColor(TP::COLOR_TOOLBAR, &toolbar_color));

  constexpr SkColor kExplicitColor = SkColorSetRGB(0, 255, 0);
  EXPECT_EQ(toolbar_color, kExplicitColor);
}

// Ensure that, given an explicit frame color and a frame image, the output
// color in COLOR_FRAME_ACTIVE reflects the explicit color.
TEST_F(BrowserThemePackTest,
       TestFrameColorComputedFromImageOverridesInputColor) {
  scoped_refptr<BrowserThemePack> pack(
      new BrowserThemePack(ThemeType::kExtension));
  BuildTestExtensionThemeCheckValid(
      "theme_test_toolbar_frame_images_and_colors", pack.get());

  SkColor frame_color;
  EXPECT_TRUE(pack->GetColor(TP::COLOR_FRAME_ACTIVE, &frame_color));

  constexpr SkColor kExplicitColor = SkColorSetRGB(255, 0, 255);
  EXPECT_EQ(frame_color, kExplicitColor);
}

// Test theme generation for a given color.
TEST_F(BrowserThemePackTest, TestBuildFromColor) {
  scoped_refptr<BrowserThemePack> pack(
      new BrowserThemePack(ThemeType::kAutogenerated));
  SkColor color = SkColorSetRGB(100, 100, 200);
  BrowserThemePack::BuildFromColor(color, pack.get());

  SkColor frame_color;
  EXPECT_TRUE(pack->GetColor(TP::COLOR_FRAME_ACTIVE, &frame_color));
  EXPECT_TRUE(pack->GetColor(TP::COLOR_TOOLBAR, &frame_color));
  EXPECT_TRUE(pack->GetColor(TP::COLOR_NTP_BACKGROUND, &frame_color));
}

TEST_F(BrowserThemePackTest, BuildFromColor_BasicTestColors) {
  constexpr SkColor backgrounds[] = {SK_ColorBLACK,
                                     SK_ColorWHITE,
                                     SkColorSetRGB(50, 0, 50),
                                     SkColorSetRGB(0, 130, 130),
                                     SkColorSetRGB(0, 180, 180),
                                     SkColorSetRGB(0, 200, 200),
                                     SkColorSetRGB(120, 120, 120),
                                     SkColorSetRGB(125, 125, 125),
                                     SkColorSetRGB(128, 128, 128),
                                     SkColorSetRGB(240, 255, 255)};
  auto has_readable_contrast = [](SkColor foreground_color,
                                  SkColor background_color) {
    return color_utils::GetContrastRatio(foreground_color, background_color) >=
           kAutogeneratedThemeTextPreferredContrast;
  };

  for (SkColor color : backgrounds) {
    scoped_refptr<BrowserThemePack> pack(
        new BrowserThemePack(ThemeType::kAutogenerated));
    BrowserThemePack::BuildFromColor(color, pack.get());

    SkColor frame_color, background_tab, background_tab_text;
    EXPECT_TRUE(pack->GetColor(TP::COLOR_FRAME_ACTIVE, &frame_color));
    EXPECT_TRUE(pack->GetColor(TP::COLOR_TAB_BACKGROUND_INACTIVE_FRAME_ACTIVE,
                               &background_tab));
    EXPECT_TRUE(pack->GetColor(TP::COLOR_TAB_FOREGROUND_INACTIVE_FRAME_ACTIVE,
                               &background_tab_text));
    EXPECT_EQ(frame_color, background_tab);
    EXPECT_TRUE(has_readable_contrast(background_tab_text, background_tab));

    SkColor ntp_background, toolbar_color, toolbar_button_icon, toolbar_text;
    EXPECT_TRUE(pack->GetColor(TP::COLOR_NTP_BACKGROUND, &ntp_background));
    EXPECT_TRUE(pack->GetColor(TP::COLOR_TOOLBAR, &toolbar_color));
    EXPECT_TRUE(
        pack->GetColor(TP::COLOR_TOOLBAR_BUTTON_ICON, &toolbar_button_icon));
    EXPECT_TRUE(pack->GetColor(TP::COLOR_TOOLBAR_TEXT, &toolbar_text));

    EXPECT_EQ(toolbar_color, ntp_background);
    EXPECT_EQ(toolbar_text, toolbar_button_icon);
    EXPECT_TRUE(has_readable_contrast(toolbar_text, toolbar_color));

    EXPECT_NE(frame_color, toolbar_color);
    EXPECT_GE(color_utils::GetContrastRatio(frame_color, toolbar_color),
              kAutogeneratedThemeActiveTabMinContrast);

    int ntp_logo_alternate = 0;
    pack->GetDisplayProperty(TP::NTP_LOGO_ALTERNATE, &ntp_logo_alternate);
    int expected_logo = ntp_background == SK_ColorWHITE ? 0 : 1;
    EXPECT_EQ(expected_logo, ntp_logo_alternate);

    color_utils::HSL hsl;
    EXPECT_TRUE(pack->GetTint(TP::TINT_FRAME_INACTIVE, &hsl));
    EXPECT_EQ(-1, hsl.h);
    EXPECT_EQ(-1, hsl.s);
    EXPECT_EQ(-1, hsl.l);

    color_utils::HSL incognito_hsl;
    EXPECT_TRUE(
        pack->GetTint(TP::TINT_FRAME_INCOGNITO_INACTIVE, &incognito_hsl));
    EXPECT_EQ(-1, incognito_hsl.h);
    EXPECT_EQ(-1, incognito_hsl.s);
    EXPECT_EQ(-1, incognito_hsl.l);
  }
}

TEST_F(BrowserThemePackTest, BuildFromColor_TestAdjustedFrameColor) {
  // Colors close to midpoint that don't have sufficient contrast with white or
  // dark grey should be adjusted.
  constexpr SkColor dark_backgrounds[] = {SkColorSetRGB(0, 130, 130),
                                          SkColorSetRGB(120, 120, 120)};
  constexpr SkColor light_backgrounds[] = {SkColorSetRGB(0, 180, 180),
                                           SkColorSetRGB(128, 128, 128)};

  for (SkColor color : dark_backgrounds) {
    scoped_refptr<BrowserThemePack> pack(
        new BrowserThemePack(ThemeType::kAutogenerated));
    BrowserThemePack::BuildFromColor(color, pack.get());

    SkColor frame_color;
    EXPECT_TRUE(pack->GetColor(TP::COLOR_FRAME_ACTIVE, &frame_color));

    // Dark backgrounds should get even darker.
    EXPECT_GT(color_utils::GetRelativeLuminance(color),
              color_utils::GetRelativeLuminance(frame_color));
  }

  for (SkColor color : light_backgrounds) {
    scoped_refptr<BrowserThemePack> pack(
        new BrowserThemePack(ThemeType::kAutogenerated));
    BrowserThemePack::BuildFromColor(color, pack.get());

    SkColor frame_color;
    EXPECT_TRUE(pack->GetColor(TP::COLOR_FRAME_ACTIVE, &frame_color));

    // Light backgrounds should get even lighter.
    EXPECT_LT(color_utils::GetRelativeLuminance(color),
              color_utils::GetRelativeLuminance(frame_color));
  }
}

// TODO(gayane): Consider moving color calculation tests to
// autogenerated_theme_util_unittest.cc.
TEST_F(BrowserThemePackTest, BuildFromColor_TestPreferredActiveTabContrast) {
  constexpr SkColor dark_backgrounds[] = {SK_ColorBLACK,
                                          SkColorSetRGB(0, 10, 17)};
  constexpr SkColor medium_backgrounds[] = {SkColorSetRGB(0, 130, 130),
                                            SkColorSetRGB(120, 120, 120)};
  constexpr SkColor light_backgrounds[] = {SK_ColorWHITE,
                                           SkColorSetRGB(240, 255, 255)};
  VerifyCalculatedColorContrast(
      dark_backgrounds, kAutogeneratedThemeActiveTabPreferredContrastForDark);
  VerifyCalculatedColorContrast(medium_backgrounds,
                                kAutogeneratedThemeActiveTabPreferredContrast);
  VerifyCalculatedColorContrast(light_backgrounds,
                                kAutogeneratedThemeActiveTabMinContrast);
}

TEST_F(BrowserThemePackTest, TestToolbarButtonColor) {
  scoped_refptr<BrowserThemePack> pack(
      new BrowserThemePack(ThemeType::kExtension));
  BuildTestExtensionThemeCheckValid("theme_test_toolbar_button_color",
                                    pack.get());

  SkColor button_color;
  EXPECT_TRUE(pack->GetColor(TP::COLOR_TOOLBAR_BUTTON_ICON, &button_color));
  EXPECT_EQ(button_color, SkColorSetRGB(255, 0, 0));

  color_utils::HSL hsl;
  EXPECT_TRUE(pack->GetTint(TP::TINT_BUTTONS, &hsl));
}

TEST_F(BrowserThemePackTest, TestNtpTextCaclulation) {
  // If ntp_text is not specified then it should be calculated based on NTP
  // background image.
  scoped_refptr<BrowserThemePack> pack(
      new BrowserThemePack(ThemeType::kExtension));
  BuildTestExtensionThemeCheckValid("theme_ntp_background_image", pack.get());

  SkColor ntp_text;
  EXPECT_TRUE(pack->GetColor(TP::COLOR_NTP_TEXT, &ntp_text));

  // If ntp_text is not specified then it should be calculated based on NTP
  // background color.
  scoped_refptr<BrowserThemePack> pack_autogenerated(
      new BrowserThemePack(ThemeType::kAutogenerated));
  BrowserThemePack::BuildFromColor(SkColorSetRGB(0, 130, 130),
                                   pack_autogenerated.get());

  EXPECT_TRUE(pack_autogenerated->GetColor(TP::COLOR_NTP_TEXT, &ntp_text));
}

TEST_F(BrowserThemePackTest, TestLogoColors) {
  // For themes with no background image and no background color, nothing should
  // be specified.
  scoped_refptr<BrowserThemePack> theme_minimal(
      new BrowserThemePack(ThemeType::kExtension));
  BuildTestExtensionThemeCheckValid("theme_minimal", theme_minimal.get());
  SkColor color;
  EXPECT_FALSE(theme_minimal->GetColor(TP::COLOR_NTP_LOGO, &color));

  // For themes with image logo, colors shouldn't be set.
  scoped_refptr<BrowserThemePack> theme_with_image(
      new BrowserThemePack(ThemeType::kExtension));
  BuildTestExtensionThemeCheckValid("theme_ntp_background_image",
                                    theme_with_image.get());
  EXPECT_FALSE(theme_with_image->GetColor(TP::COLOR_NTP_LOGO, &color));

  // For themes with no image but with colorful logo, the logo color shouldn't
  // be specified.
  scoped_refptr<BrowserThemePack> theme_colorful_logo(
      new BrowserThemePack(ThemeType::kExtension));
  BuildTestExtensionThemeCheckValid("theme_color_ntp_colorful_logo",
                                    theme_colorful_logo.get());
  EXPECT_FALSE(theme_colorful_logo->GetColor(TP::COLOR_NTP_LOGO, &color));

  // For themes with no image and with alternate logo, color should be set.
  scoped_refptr<BrowserThemePack> theme_alternate_logo(
      new BrowserThemePack(ThemeType::kExtension));
  BuildTestExtensionThemeCheckValid("theme_color_ntp_white_logo",
                                    theme_alternate_logo.get());
  EXPECT_TRUE(theme_alternate_logo->GetColor(TP::COLOR_NTP_LOGO, &color));
  EXPECT_NE(SK_ColorWHITE, color);

  // For darker then midpoint themes the logo color should be white.
  scoped_refptr<BrowserThemePack> dark_theme(
      new BrowserThemePack(ThemeType::kAutogenerated));
  BrowserThemePack::BuildFromColor(SkColorSetRGB(120, 120, 120),
                                   dark_theme.get());
  EXPECT_TRUE(dark_theme->GetColor(TP::COLOR_NTP_LOGO, &color));
  EXPECT_EQ(SK_ColorWHITE, color);

  // For themes with white NTP background the Logo color shouldn't be set
  // because the colorful logo should be used on white background.
  scoped_refptr<BrowserThemePack> white_theme(
      new BrowserThemePack(ThemeType::kAutogenerated));
  BrowserThemePack::BuildFromColor(SK_ColorWHITE, white_theme.get());
  ASSERT_TRUE(white_theme->GetColor(TP::COLOR_NTP_BACKGROUND, &color));
  ASSERT_EQ(SK_ColorWHITE, color);
  EXPECT_FALSE(white_theme->GetColor(TP::COLOR_NTP_LOGO, &color));
}

// Test that building a theme from an extension where "theme_ntp_background"
// points to a jpg does not crash.
TEST_F(BrowserThemePackTest, JpegNtpBackground) {
  scoped_refptr<BrowserThemePack> theme(
      new BrowserThemePack(ThemeType::kExtension));
  BuildFromUnpackedExtensionCheckValid(
      GetTestExtensionThemePath("theme_jpeg_ntp_background"), theme.get());
}

// Test that building a theme from an extension where "theme_frame" points to a
// jpg fails.
TEST_F(BrowserThemePackTest, JpegThemeFrame) {
  scoped_refptr<BrowserThemePack> theme(
      new BrowserThemePack(ThemeType::kExtension));
  BuildFromUnpackedExtension(
      GetTestExtensionThemePath("theme_jpeg_theme_frame"), theme.get());
  EXPECT_FALSE(theme->is_valid());
}
