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