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