blob: 2ef853ac62366c61a5a9dffdc1c19af52ea3ab6e [file] [log] [blame] [edit]
// 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::Value::Dict* value);
void LoadTintJSON(const std::string& json);
void LoadTintDictionary(const base::Value::Dict* value);
void LoadDisplayPropertiesJSON(const std::string& json);
void LoadDisplayPropertiesDictionary(const base::Value::Dict* value);
void ParseThemeImages(const extensions::ThemeInfo::ThemeImages* value,
TestFilePathMap* out_file_paths);
void ResetTabGroupColorPaletteShades();
void LoadTabGroupColorPaletteShadesJSON(const std::string& json);
void LoadTabGroupColorPaletteShadesDictionary(const base::Value::Dict* value);
void VerifyTabGroupColorPaletteShades();
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,
int colors_num,
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::Value::Dict* 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::Value::Dict* 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::Value::Dict* 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);
}
void BrowserThemePackTest::ResetTabGroupColorPaletteShades() {
theme_pack_->tab_group_color_palette_shades_ =
std::array<BrowserThemePack::TabGroupColorPaletteShadesPair,
BrowserThemePack::kTabGroupColorPaletteLength>{};
}
void BrowserThemePackTest::LoadTabGroupColorPaletteShadesJSON(
const std::string& json) {
LoadTabGroupColorPaletteShadesDictionary(
&base::JSONReader::Read(json, base::JSON_PARSE_CHROMIUM_EXTENSIONS)
->GetDict());
}
void BrowserThemePackTest::LoadTabGroupColorPaletteShadesDictionary(
const base::Value::Dict* value) {
theme_pack_->SetTabGroupColorPaletteShadesFromJSON(value);
}
void BrowserThemePackTest::VerifyTabGroupColorPaletteShades() {
const std::array<BrowserThemePack::TabGroupColorPaletteShadesPair,
BrowserThemePack::kTabGroupColorPaletteLength>&
tab_group_color_palette_shades =
theme_pack_->tab_group_color_palette_shades_;
std::array<BrowserThemePack::TabGroupColorPaletteShadesPair,
BrowserThemePack::kTabGroupColorPaletteLength>
expected_tab_group_color_palette_shades{};
expected_tab_group_color_palette_shades[0].id =
static_cast<int>(tab_groups::TabGroupColorId::kRed);
// Expected standard shades for hue = 40
constexpr std::array<SkColor, ui::kGeneratedShadesCount> kExpectedShades = {
SkColorSetRGB(0xFF, 0xED, 0xE7), // Shade 50
SkColorSetRGB(0xFF, 0xDC, 0xD0), // Shade 100
SkColorSetRGB(0xFF, 0xC1, 0xAA), // Shade 200
SkColorSetRGB(0xFF, 0xA6, 0x83), // Shade 300
SkColorSetRGB(0xFF, 0x87, 0x56), // Shade 400
SkColorSetRGB(0xFA, 0x6E, 0x2F), // Shade 500
SkColorSetRGB(0xE4, 0x5F, 0x20), // Shade 600
SkColorSetRGB(0xCB, 0x52, 0x19), // Shade 700
SkColorSetRGB(0xB3, 0x46, 0x11), // Shade 800
SkColorSetRGB(0x9F, 0x3D, 0x0E), // Shade 900
SkColorSetRGB(0x5A, 0x3D, 0x32), // Shade 1000
};
std::copy(kExpectedShades.begin(), kExpectedShades.end(),
expected_tab_group_color_palette_shades[0].shades.begin());
for (size_t i = 0; i < BrowserThemePack::kTabGroupColorPaletteLength; ++i) {
const auto& actual = tab_group_color_palette_shades[i];
const auto& expected = expected_tab_group_color_palette_shades[i];
EXPECT_EQ(actual.id, expected.id) << "Mismatch at index " << i << " for ID";
for (size_t j = 0; j < ui::kGeneratedShadesCount; ++j) {
EXPECT_EQ(actual.shades[j], expected.shades[j])
<< "Mismatch at index " << i << " shade " << j;
}
}
}
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 will be cropped at 60 - kTallestTabHeight + 19.
EXPECT_EQ(60, 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());
// Cropped height will be 2 * 60.
EXPECT_EQ(120, 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 will be cropped at 60 - kTallestTabHeight + 19.
EXPECT_EQ(60, 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());
// Cropped height will be 2 * 60.
EXPECT_EQ(120, 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,
int spanification_suspected_redundant_colors_num,
float contrast_ratio) {
// TODO(crbug.com/431824301): Remove unneeded parameter once validated to be
// redundant in M143.
CHECK(base::checked_cast<size_t>(
spanification_suspected_redundant_colors_num) == colors.size(),
base::NotFatalUntil::M143);
for (int i = 0; i < spanification_suspected_redundant_colors_num; i++) {
SkColor color = colors[i];
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[kColorToolbarBackgroundSubtleEmphasis] = {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(kColorToolbarBackgroundSubtleEmphasis));
}
// 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, 2,
kAutogeneratedThemeActiveTabPreferredContrastForDark);
VerifyCalculatedColorContrast(medium_backgrounds, 2,
kAutogeneratedThemeActiveTabPreferredContrast);
VerifyCalculatedColorContrast(light_backgrounds, 2,
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());
}
TEST_F(BrowserThemePackTest, SetTabGroupColorPaletteShadesFromJSONTest) {
ResetTabGroupColorPaletteShades();
std::string tab_group_color_palette_json =
// invalid key with valid value, will be ignored.
"{ \"invalid_key\": 23, "
// valid key with invalid value, will be ignored.
" \"grey_override\": 361, "
// valid key with valid value, will be translated.
" \"red_override\": 40 } ";
LoadTabGroupColorPaletteShadesJSON(tab_group_color_palette_json);
VerifyTabGroupColorPaletteShades();
ResetTabGroupColorPaletteShades();
}
TEST_F(BrowserThemePackTest, TabGroupColorPaletteCustomizationTest) {
// Tests whether the Tab Group-specific colorIds are set correctly based on
// the background color and theme provided.
ui::ColorProvider provider;
ui::ColorMixer& mixer = provider.AddMixer();
// Randomly set the background colors that influence Tab Group-specific
// ColorIds. The selected shade depends on whether the background color is
// considered dark.
mixer[kColorTabBackgroundInactiveFrameActive] = {SkColorSetRGB(20, 20, 20)};
mixer[kColorTabBackgroundInactiveFrameInactive] = {SkColorSetRGB(34, 34, 34)};
mixer[ui::kColorMenuBackground] = {SkColorSetRGB(15, 23, 30)};
mixer[kColorBookmarkBarBackground] = {SkColorSetRGB(255, 250, 200)};
mixer[kColorThumbnailTabStripBackgroundActive] = {
SkColorSetRGB(220, 230, 240)};
mixer[kColorThumbnailTabStripBackgroundInactive] = {
SkColorSetRGB(245, 245, 245)};
// Customizing the red Tab Group color using hue 40.
std::string tab_group_color_palette_json = R"({ "red_override": 40 })";
LoadTabGroupColorPaletteShadesJSON(tab_group_color_palette_json);
theme_pack().AddColorMixers(&provider, ui::ColorProviderKey());
// Standard shades of hue 40.
constexpr std::array<SkColor, ui::kGeneratedShadesCount> shades = {
SkColorSetRGB(0xFF, 0xED, 0xE7), // Shade 50
SkColorSetRGB(0xFF, 0xDC, 0xD0), // Shade 100
SkColorSetRGB(0xFF, 0xC1, 0xAA), // Shade 200
SkColorSetRGB(0xFF, 0xA6, 0x83), // Shade 300
SkColorSetRGB(0xFF, 0x87, 0x56), // Shade 400
SkColorSetRGB(0xFA, 0x6E, 0x2F), // Shade 500
SkColorSetRGB(0xE4, 0x5F, 0x20), // Shade 600
SkColorSetRGB(0xCB, 0x52, 0x19), // Shade 700
SkColorSetRGB(0xB3, 0x46, 0x11), // Shade 800
SkColorSetRGB(0x9F, 0x3D, 0x0E), // Shade 900
SkColorSetRGB(0x5A, 0x3D, 0x32), // Shade 1000
};
enum ShadeIndex {
k50,
k100,
k200,
k300,
k400,
k500,
k600,
k700,
k800,
k900,
k1000
};
SkColor expected;
SkColor actual;
// kColorTabGroupTabStripFrameActiveRed
expected = color_utils::IsDark(
provider.GetColor(kColorTabBackgroundInactiveFrameActive))
? shades[k300]
: shades[k600];
actual = provider.GetColor(kColorTabGroupTabStripFrameActiveRed);
EXPECT_EQ(expected, actual);
// kColorTabGroupTabStripFrameInactiveRed
expected = color_utils::IsDark(
provider.GetColor(kColorTabBackgroundInactiveFrameInactive))
? shades[k300]
: shades[k600];
actual = provider.GetColor(kColorTabGroupTabStripFrameInactiveRed);
EXPECT_EQ(expected, actual);
// kColorTabGroupDialogRed
expected = provider.GetColor(kColorTabGroupContextMenuRed);
actual = provider.GetColor(kColorTabGroupDialogRed);
EXPECT_EQ(expected, actual);
// kColorTabGroupContextMenuRed
expected = color_utils::IsDark(provider.GetColor(ui::kColorMenuBackground))
? shades[k300]
: shades[k600];
actual = provider.GetColor(kColorTabGroupContextMenuRed);
EXPECT_EQ(expected, actual);
// kColorSavedTabGroupForegroundRed
expected = color_utils::IsDark(provider.GetColor(kColorBookmarkBarBackground))
? shades[k100]
: shades[k800];
actual = provider.GetColor(kColorSavedTabGroupForegroundRed);
EXPECT_EQ(expected, actual);
// kColorSavedTabGroupOutlineRed
expected = color_utils::IsDark(provider.GetColor(kColorBookmarkBarBackground))
? shades[k300]
: shades[k700];
actual = provider.GetColor(kColorSavedTabGroupOutlineRed);
EXPECT_EQ(expected, actual);
// kColorTabGroupBookmarkBarRed
expected = color_utils::IsDark(provider.GetColor(kColorBookmarkBarBackground))
? shades[k1000]
: shades[k50];
actual = provider.GetColor(kColorTabGroupBookmarkBarRed);
EXPECT_EQ(expected, actual);
// kColorThumbnailTabStripTabGroupFrameActiveRed
expected = color_utils::IsDark(
provider.GetColor(kColorThumbnailTabStripBackgroundActive))
? shades[k300]
: shades[k600];
actual = provider.GetColor(kColorThumbnailTabStripTabGroupFrameActiveRed);
EXPECT_EQ(expected, actual);
// kColorThumbnailTabStripTabGroupFrameInactiveRed
expected = color_utils::IsDark(
provider.GetColor(kColorThumbnailTabStripBackgroundInactive))
? shades[k300]
: shades[k600];
actual = provider.GetColor(kColorThumbnailTabStripTabGroupFrameInactiveRed);
EXPECT_EQ(expected, actual);
}