Home | History | Annotate | Download | only in themes
      1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/themes/browser_theme_pack.h"
      6 
      7 #include "base/file_util.h"
      8 #include "base/json/json_reader.h"
      9 #include "base/memory/scoped_temp_dir.h"
     10 #include "base/message_loop.h"
     11 #include "base/path_service.h"
     12 #include "base/values.h"
     13 #include "chrome/browser/themes/theme_service.h"
     14 #include "chrome/common/chrome_paths.h"
     15 #include "content/browser/browser_thread.h"
     16 #include "content/common/json_value_serializer.h"
     17 #include "grit/theme_resources.h"
     18 #include "testing/gtest/include/gtest/gtest.h"
     19 #include "ui/gfx/color_utils.h"
     20 
     21 class BrowserThemePackTest : public ::testing::Test {
     22  public:
     23   BrowserThemePackTest()
     24       : message_loop(),
     25         fake_ui_thread(BrowserThread::UI, &message_loop),
     26         fake_file_thread(BrowserThread::FILE, &message_loop),
     27         theme_pack_(new BrowserThemePack) {
     28   }
     29 
     30   // Transformation for link underline colors.
     31   SkColor BuildThirdOpacity(SkColor color_link) {
     32     return SkColorSetA(color_link, SkColorGetA(color_link) / 3);
     33   }
     34 
     35   void GenerateDefaultFrameColor(std::map<int, SkColor>* colors,
     36                                  int color, int tint) {
     37     (*colors)[color] = HSLShift(
     38         ThemeService::GetDefaultColor(
     39             ThemeService::COLOR_FRAME),
     40         ThemeService::GetDefaultTint(tint));
     41   }
     42 
     43   // Returns a mapping from each COLOR_* constant to the default value for this
     44   // constant. Callers get this map, and then modify expected values and then
     45   // run the resulting thing through VerifyColorMap().
     46   std::map<int, SkColor> GetDefaultColorMap() {
     47     std::map<int, SkColor> colors;
     48     for (int i = ThemeService::COLOR_FRAME;
     49          i <= ThemeService::COLOR_BUTTON_BACKGROUND; ++i) {
     50       colors[i] = ThemeService::GetDefaultColor(i);
     51     }
     52 
     53     GenerateDefaultFrameColor(&colors, ThemeService::COLOR_FRAME,
     54                               ThemeService::TINT_FRAME);
     55     GenerateDefaultFrameColor(&colors,
     56                               ThemeService::COLOR_FRAME_INACTIVE,
     57                               ThemeService::TINT_FRAME_INACTIVE);
     58     GenerateDefaultFrameColor(&colors,
     59                               ThemeService::COLOR_FRAME_INCOGNITO,
     60                               ThemeService::TINT_FRAME_INCOGNITO);
     61     GenerateDefaultFrameColor(
     62         &colors,
     63         ThemeService::COLOR_FRAME_INCOGNITO_INACTIVE,
     64         ThemeService::TINT_FRAME_INCOGNITO_INACTIVE);
     65 
     66     return colors;
     67   }
     68 
     69   void VerifyColorMap(const std::map<int, SkColor>& color_map) {
     70     for (std::map<int, SkColor>::const_iterator it = color_map.begin();
     71          it != color_map.end(); ++it) {
     72       SkColor color = ThemeService::GetDefaultColor(it->first);
     73       theme_pack_->GetColor(it->first, &color);
     74       EXPECT_EQ(it->second, color) << "Color id = " << it->first;
     75     }
     76   }
     77 
     78   void LoadColorJSON(const std::string& json) {
     79     scoped_ptr<Value> value(base::JSONReader::Read(json, false));
     80     ASSERT_TRUE(value->IsType(Value::TYPE_DICTIONARY));
     81     LoadColorDictionary(static_cast<DictionaryValue*>(value.get()));
     82   }
     83 
     84   void LoadColorDictionary(DictionaryValue* value) {
     85     theme_pack_->BuildColorsFromJSON(value);
     86   }
     87 
     88   void LoadTintJSON(const std::string& json) {
     89     scoped_ptr<Value> value(base::JSONReader::Read(json, false));
     90     ASSERT_TRUE(value->IsType(Value::TYPE_DICTIONARY));
     91     LoadTintDictionary(static_cast<DictionaryValue*>(value.get()));
     92   }
     93 
     94   void LoadTintDictionary(DictionaryValue* value) {
     95     theme_pack_->BuildTintsFromJSON(value);
     96   }
     97 
     98   void LoadDisplayPropertiesJSON(const std::string& json) {
     99     scoped_ptr<Value> value(base::JSONReader::Read(json, false));
    100     ASSERT_TRUE(value->IsType(Value::TYPE_DICTIONARY));
    101     LoadDisplayPropertiesDictionary(static_cast<DictionaryValue*>(value.get()));
    102   }
    103 
    104   void LoadDisplayPropertiesDictionary(DictionaryValue* value) {
    105     theme_pack_->BuildDisplayPropertiesFromJSON(value);
    106   }
    107 
    108   void ParseImageNamesJSON(const std::string& json,
    109                        std::map<int, FilePath>* out_file_paths) {
    110     scoped_ptr<Value> value(base::JSONReader::Read(json, false));
    111     ASSERT_TRUE(value->IsType(Value::TYPE_DICTIONARY));
    112     ParseImageNamesDictionary(static_cast<DictionaryValue*>(value.get()),
    113                               out_file_paths);
    114   }
    115 
    116   void ParseImageNamesDictionary(DictionaryValue* value,
    117                                  std::map<int, FilePath>* out_file_paths) {
    118     theme_pack_->ParseImageNamesFromJSON(value, FilePath(), out_file_paths);
    119 
    120     // Build the source image list for HasCustomImage().
    121     theme_pack_->BuildSourceImagesArray(*out_file_paths);
    122   }
    123 
    124   bool LoadRawBitmapsTo(const std::map<int, FilePath>& out_file_paths) {
    125     return theme_pack_->LoadRawBitmapsTo(out_file_paths,
    126                                          &theme_pack_->prepared_images_);
    127   }
    128 
    129   FilePath GetStarGazingPath() {
    130     FilePath test_path;
    131     if (!PathService::Get(chrome::DIR_TEST_DATA, &test_path)) {
    132       NOTREACHED();
    133       return test_path;
    134     }
    135 
    136     test_path = test_path.AppendASCII("profiles");
    137     test_path = test_path.AppendASCII("complex_theme");
    138     test_path = test_path.AppendASCII("Default");
    139     test_path = test_path.AppendASCII("Extensions");
    140     test_path = test_path.AppendASCII("mblmlcbknbnfebdfjnolmcapmdofhmme");
    141     test_path = test_path.AppendASCII("1.1");
    142     return FilePath(test_path);
    143   }
    144 
    145   // Verifies the data in star gazing. We do this multiple times for different
    146   // BrowserThemePack objects to make sure it works in generated and mmapped
    147   // mode correctly.
    148   void VerifyStarGazing(BrowserThemePack* pack) {
    149     // First check that values we know exist, exist.
    150     SkColor color;
    151     EXPECT_TRUE(pack->GetColor(ThemeService::COLOR_BOOKMARK_TEXT,
    152                                &color));
    153     EXPECT_EQ(SK_ColorBLACK, color);
    154 
    155     EXPECT_TRUE(pack->GetColor(ThemeService::COLOR_NTP_BACKGROUND,
    156                                &color));
    157     EXPECT_EQ(SkColorSetRGB(57, 137, 194), color);
    158 
    159     color_utils::HSL expected = { 0.6, 0.553, 0.5 };
    160     color_utils::HSL actual;
    161     EXPECT_TRUE(pack->GetTint(ThemeService::TINT_BUTTONS, &actual));
    162     EXPECT_DOUBLE_EQ(expected.h, actual.h);
    163     EXPECT_DOUBLE_EQ(expected.s, actual.s);
    164     EXPECT_DOUBLE_EQ(expected.l, actual.l);
    165 
    166     int val;
    167     EXPECT_TRUE(pack->GetDisplayProperty(
    168         ThemeService::NTP_BACKGROUND_ALIGNMENT, &val));
    169     EXPECT_EQ(ThemeService::ALIGN_TOP, val);
    170 
    171     // The stargazing theme defines the following images:
    172     EXPECT_TRUE(pack->HasCustomImage(IDR_THEME_BUTTON_BACKGROUND));
    173     EXPECT_TRUE(pack->HasCustomImage(IDR_THEME_FRAME));
    174     EXPECT_TRUE(pack->HasCustomImage(IDR_THEME_NTP_BACKGROUND));
    175     EXPECT_TRUE(pack->HasCustomImage(IDR_THEME_TAB_BACKGROUND));
    176     EXPECT_TRUE(pack->HasCustomImage(IDR_THEME_TOOLBAR));
    177     EXPECT_TRUE(pack->HasCustomImage(IDR_THEME_WINDOW_CONTROL_BACKGROUND));
    178 
    179     // Here are a few images that we shouldn't expect because even though
    180     // they're included in the theme pack, they were autogenerated and
    181     // therefore shouldn't show up when calling HasCustomImage().
    182     EXPECT_FALSE(pack->HasCustomImage(IDR_THEME_FRAME_INACTIVE));
    183     EXPECT_FALSE(pack->HasCustomImage(IDR_THEME_FRAME_INCOGNITO));
    184     EXPECT_FALSE(pack->HasCustomImage(IDR_THEME_FRAME_INCOGNITO_INACTIVE));
    185     EXPECT_FALSE(pack->HasCustomImage(IDR_THEME_TAB_BACKGROUND_INCOGNITO));
    186 
    187     // Make sure we don't have phantom data.
    188     EXPECT_FALSE(pack->GetColor(ThemeService::COLOR_CONTROL_BACKGROUND,
    189                                 &color));
    190     EXPECT_FALSE(pack->GetTint(ThemeService::TINT_FRAME, &actual));
    191   }
    192 
    193   MessageLoop message_loop;
    194   BrowserThread fake_ui_thread;
    195   BrowserThread fake_file_thread;
    196 
    197   scoped_refptr<BrowserThemePack> theme_pack_;
    198 };
    199 
    200 
    201 TEST_F(BrowserThemePackTest, DeriveUnderlineLinkColor) {
    202   // If we specify a link color, but don't specify the underline color, the
    203   // theme provider should create one.
    204   std::string color_json = "{ \"ntp_link\": [128, 128, 128],"
    205                            "  \"ntp_section_link\": [128, 128, 128] }";
    206   LoadColorJSON(color_json);
    207 
    208   std::map<int, SkColor> colors = GetDefaultColorMap();
    209   SkColor link_color = SkColorSetRGB(128, 128, 128);
    210   colors[ThemeService::COLOR_NTP_LINK] = link_color;
    211   colors[ThemeService::COLOR_NTP_LINK_UNDERLINE] =
    212       BuildThirdOpacity(link_color);
    213   colors[ThemeService::COLOR_NTP_SECTION_LINK] = link_color;
    214   colors[ThemeService::COLOR_NTP_SECTION_LINK_UNDERLINE] =
    215       BuildThirdOpacity(link_color);
    216 
    217   VerifyColorMap(colors);
    218 }
    219 
    220 TEST_F(BrowserThemePackTest, ProvideUnderlineLinkColor) {
    221   // If we specify the underline color, it shouldn't try to generate one.
    222   std::string color_json = "{ \"ntp_link\": [128, 128, 128],"
    223                            "  \"ntp_link_underline\": [255, 255, 255],"
    224                            "  \"ntp_section_link\": [128, 128, 128],"
    225                            "  \"ntp_section_link_underline\": [255, 255, 255]"
    226                            "}";
    227   LoadColorJSON(color_json);
    228 
    229   std::map<int, SkColor> colors = GetDefaultColorMap();
    230   SkColor link_color = SkColorSetRGB(128, 128, 128);
    231   SkColor underline_color = SkColorSetRGB(255, 255, 255);
    232   colors[ThemeService::COLOR_NTP_LINK] = link_color;
    233   colors[ThemeService::COLOR_NTP_LINK_UNDERLINE] = underline_color;
    234   colors[ThemeService::COLOR_NTP_SECTION_LINK] = link_color;
    235   colors[ThemeService::COLOR_NTP_SECTION_LINK_UNDERLINE] =
    236       underline_color;
    237 
    238   VerifyColorMap(colors);
    239 }
    240 
    241 TEST_F(BrowserThemePackTest, UseSectionColorAsNTPHeader) {
    242   std::string color_json = "{ \"ntp_section\": [190, 190, 190] }";
    243   LoadColorJSON(color_json);
    244 
    245   std::map<int, SkColor> colors = GetDefaultColorMap();
    246   SkColor ntp_color = SkColorSetRGB(190, 190, 190);
    247   colors[ThemeService::COLOR_NTP_HEADER] = ntp_color;
    248   colors[ThemeService::COLOR_NTP_SECTION] = ntp_color;
    249   VerifyColorMap(colors);
    250 }
    251 
    252 TEST_F(BrowserThemePackTest, ProvideNtpHeaderColor) {
    253   std::string color_json = "{ \"ntp_header\": [120, 120, 120], "
    254                            "  \"ntp_section\": [190, 190, 190] }";
    255   LoadColorJSON(color_json);
    256 
    257   std::map<int, SkColor> colors = GetDefaultColorMap();
    258   SkColor ntp_header = SkColorSetRGB(120, 120, 120);
    259   SkColor ntp_section = SkColorSetRGB(190, 190, 190);
    260   colors[ThemeService::COLOR_NTP_HEADER] = ntp_header;
    261   colors[ThemeService::COLOR_NTP_SECTION] = ntp_section;
    262   VerifyColorMap(colors);
    263 }
    264 
    265 TEST_F(BrowserThemePackTest, CanReadTints) {
    266   std::string tint_json = "{ \"buttons\": [ 0.5, 0.5, 0.5 ] }";
    267   LoadTintJSON(tint_json);
    268 
    269   color_utils::HSL expected = { 0.5, 0.5, 0.5 };
    270   color_utils::HSL actual = { -1, -1, -1 };
    271   EXPECT_TRUE(theme_pack_->GetTint(
    272       ThemeService::TINT_BUTTONS, &actual));
    273   EXPECT_DOUBLE_EQ(expected.h, actual.h);
    274   EXPECT_DOUBLE_EQ(expected.s, actual.s);
    275   EXPECT_DOUBLE_EQ(expected.l, actual.l);
    276 }
    277 
    278 TEST_F(BrowserThemePackTest, CanReadDisplayProperties) {
    279   std::string json = "{ \"ntp_background_alignment\": \"bottom\", "
    280                      "  \"ntp_background_repeat\": \"repeat-x\", "
    281                      "  \"ntp_logo_alternate\": 0 }";
    282   LoadDisplayPropertiesJSON(json);
    283 
    284   int out_val;
    285   EXPECT_TRUE(theme_pack_->GetDisplayProperty(
    286       ThemeService::NTP_BACKGROUND_ALIGNMENT, &out_val));
    287   EXPECT_EQ(ThemeService::ALIGN_BOTTOM, out_val);
    288 
    289   EXPECT_TRUE(theme_pack_->GetDisplayProperty(
    290       ThemeService::NTP_BACKGROUND_TILING, &out_val));
    291   EXPECT_EQ(ThemeService::REPEAT_X, out_val);
    292 
    293   EXPECT_TRUE(theme_pack_->GetDisplayProperty(
    294       ThemeService::NTP_LOGO_ALTERNATE, &out_val));
    295   EXPECT_EQ(0, out_val);
    296 }
    297 
    298 TEST_F(BrowserThemePackTest, CanParsePaths) {
    299   std::string path_json = "{ \"theme_button_background\": \"one\", "
    300                           "  \"theme_toolbar\": \"two\" }";
    301   std::map<int, FilePath> out_file_paths;
    302   ParseImageNamesJSON(path_json, &out_file_paths);
    303 
    304   EXPECT_EQ(2u, out_file_paths.size());
    305   // "12" and "5" are internal constants to BrowserThemePack and are
    306   // PRS_THEME_BUTTON_BACKGROUND and PRS_THEME_TOOLBAR, but they are
    307   // implementation details that shouldn't be exported.
    308   EXPECT_TRUE(FilePath(FILE_PATH_LITERAL("one")) == out_file_paths[12]);
    309   EXPECT_TRUE(FilePath(FILE_PATH_LITERAL("two")) == out_file_paths[5]);
    310 }
    311 
    312 TEST_F(BrowserThemePackTest, InvalidPathNames) {
    313   std::string path_json = "{ \"wrong\": [1], "
    314                           "  \"theme_button_background\": \"one\", "
    315                           "  \"not_a_thing\": \"blah\" }";
    316   std::map<int, FilePath> out_file_paths;
    317   ParseImageNamesJSON(path_json, &out_file_paths);
    318 
    319   // We should have only parsed one valid path out of that mess above.
    320   EXPECT_EQ(1u, out_file_paths.size());
    321 }
    322 
    323 TEST_F(BrowserThemePackTest, InvalidColors) {
    324   std::string invalid_color = "{ \"toolbar\": [\"dog\", \"cat\", [12]], "
    325                               "  \"sound\": \"woof\" }";
    326   LoadColorJSON(invalid_color);
    327   std::map<int, SkColor> colors = GetDefaultColorMap();
    328   VerifyColorMap(colors);
    329 }
    330 
    331 TEST_F(BrowserThemePackTest, InvalidTints) {
    332   std::string invalid_tints = "{ \"buttons\": [ \"dog\", \"cat\", [\"x\"]], "
    333                               "  \"invalid\": \"entry\" }";
    334   LoadTintJSON(invalid_tints);
    335 
    336   // We shouldn't have a buttons tint, as it was invalid.
    337   color_utils::HSL actual = { -1, -1, -1 };
    338   EXPECT_FALSE(theme_pack_->GetTint(ThemeService::TINT_BUTTONS,
    339                                     &actual));
    340 }
    341 
    342 TEST_F(BrowserThemePackTest, InvalidDisplayProperties) {
    343   std::string invalid_properties = "{ \"ntp_background_alignment\": [15], "
    344                                    "  \"junk\": [15.3] }";
    345   LoadDisplayPropertiesJSON(invalid_properties);
    346 
    347   int out_val;
    348   EXPECT_FALSE(theme_pack_->GetDisplayProperty(
    349       ThemeService::NTP_BACKGROUND_ALIGNMENT, &out_val));
    350 }
    351 
    352 // These three tests should just not cause a segmentation fault.
    353 TEST_F(BrowserThemePackTest, NullPaths) {
    354   std::map<int, FilePath> out_file_paths;
    355   ParseImageNamesDictionary(NULL, &out_file_paths);
    356 }
    357 
    358 TEST_F(BrowserThemePackTest, NullTints) {
    359   LoadTintDictionary(NULL);
    360 }
    361 
    362 TEST_F(BrowserThemePackTest, NullColors) {
    363   LoadColorDictionary(NULL);
    364 }
    365 
    366 TEST_F(BrowserThemePackTest, NullDisplayProperties) {
    367   LoadDisplayPropertiesDictionary(NULL);
    368 }
    369 
    370 TEST_F(BrowserThemePackTest, TestHasCustomImage) {
    371   // HasCustomImage should only return true for images that exist in the
    372   // extension and not for autogenerated images.
    373   std::string images = "{ \"theme_frame\": \"one\" }";
    374   std::map<int, FilePath> out_file_paths;
    375   ParseImageNamesJSON(images, &out_file_paths);
    376 
    377   EXPECT_TRUE(theme_pack_->HasCustomImage(IDR_THEME_FRAME));
    378   EXPECT_FALSE(theme_pack_->HasCustomImage(IDR_THEME_FRAME_INCOGNITO));
    379 }
    380 
    381 TEST_F(BrowserThemePackTest, TestNonExistantImages) {
    382   std::string images = "{ \"theme_frame\": \"does_not_exist\" }";
    383   std::map<int, FilePath> out_file_paths;
    384   ParseImageNamesJSON(images, &out_file_paths);
    385 
    386   EXPECT_FALSE(LoadRawBitmapsTo(out_file_paths));
    387 }
    388 
    389 // TODO(erg): This test should actually test more of the built resources from
    390 // the extension data, but for now, exists so valgrind can test some of the
    391 // tricky memory stuff that BrowserThemePack does.
    392 TEST_F(BrowserThemePackTest, CanBuildAndReadPack) {
    393   ScopedTempDir dir;
    394   ASSERT_TRUE(dir.CreateUniqueTempDir());
    395   FilePath file = dir.path().Append(FILE_PATH_LITERAL("data.pak"));
    396 
    397   // Part 1: Build the pack from an extension.
    398   {
    399     FilePath star_gazing_path = GetStarGazingPath();
    400     FilePath manifest_path =
    401         star_gazing_path.AppendASCII("manifest.json");
    402     std::string error;
    403     JSONFileValueSerializer serializer(manifest_path);
    404     scoped_ptr<DictionaryValue> valid_value(
    405         static_cast<DictionaryValue*>(serializer.Deserialize(NULL, &error)));
    406     EXPECT_EQ("", error);
    407     ASSERT_TRUE(valid_value.get());
    408     scoped_refptr<Extension> extension(Extension::Create(
    409         star_gazing_path, Extension::INVALID, *valid_value,
    410         Extension::REQUIRE_KEY | Extension::STRICT_ERROR_CHECKS, &error));
    411     ASSERT_TRUE(extension.get());
    412     ASSERT_EQ("", error);
    413 
    414     scoped_refptr<BrowserThemePack> pack(
    415         BrowserThemePack::BuildFromExtension(extension.get()));
    416     ASSERT_TRUE(pack.get());
    417     ASSERT_TRUE(pack->WriteToDisk(file));
    418     VerifyStarGazing(pack.get());
    419   }
    420 
    421   // Part 2: Try to read back the data pack that we just wrote to disk.
    422   {
    423     scoped_refptr<BrowserThemePack> pack =
    424         BrowserThemePack::BuildFromDataPack(
    425             file, "mblmlcbknbnfebdfjnolmcapmdofhmme");
    426     ASSERT_TRUE(pack.get());
    427     VerifyStarGazing(pack.get());
    428   }
    429 }
    430