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:
BrowserThemePackTest()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.
BuildThirdOpacity(SkColor color_link)31 SkColor BuildThirdOpacity(SkColor color_link) {
32 return SkColorSetA(color_link, SkColorGetA(color_link) / 3);
33 }
34
GenerateDefaultFrameColor(std::map<int,SkColor> * colors,int color,int tint)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().
GetDefaultColorMap()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
VerifyColorMap(const std::map<int,SkColor> & color_map)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
LoadColorJSON(const std::string & json)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
LoadColorDictionary(DictionaryValue * value)84 void LoadColorDictionary(DictionaryValue* value) {
85 theme_pack_->BuildColorsFromJSON(value);
86 }
87
LoadTintJSON(const std::string & json)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
LoadTintDictionary(DictionaryValue * value)94 void LoadTintDictionary(DictionaryValue* value) {
95 theme_pack_->BuildTintsFromJSON(value);
96 }
97
LoadDisplayPropertiesJSON(const std::string & json)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
LoadDisplayPropertiesDictionary(DictionaryValue * value)104 void LoadDisplayPropertiesDictionary(DictionaryValue* value) {
105 theme_pack_->BuildDisplayPropertiesFromJSON(value);
106 }
107
ParseImageNamesJSON(const std::string & json,std::map<int,FilePath> * out_file_paths)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
ParseImageNamesDictionary(DictionaryValue * value,std::map<int,FilePath> * out_file_paths)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
LoadRawBitmapsTo(const std::map<int,FilePath> & out_file_paths)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
GetStarGazingPath()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.
VerifyStarGazing(BrowserThemePack * pack)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
TEST_F(BrowserThemePackTest,DeriveUnderlineLinkColor)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
TEST_F(BrowserThemePackTest,ProvideUnderlineLinkColor)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
TEST_F(BrowserThemePackTest,UseSectionColorAsNTPHeader)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
TEST_F(BrowserThemePackTest,ProvideNtpHeaderColor)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
TEST_F(BrowserThemePackTest,CanReadTints)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
TEST_F(BrowserThemePackTest,CanReadDisplayProperties)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
TEST_F(BrowserThemePackTest,CanParsePaths)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
TEST_F(BrowserThemePackTest,InvalidPathNames)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
TEST_F(BrowserThemePackTest,InvalidColors)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
TEST_F(BrowserThemePackTest,InvalidTints)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
TEST_F(BrowserThemePackTest,InvalidDisplayProperties)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.
TEST_F(BrowserThemePackTest,NullPaths)353 TEST_F(BrowserThemePackTest, NullPaths) {
354 std::map<int, FilePath> out_file_paths;
355 ParseImageNamesDictionary(NULL, &out_file_paths);
356 }
357
TEST_F(BrowserThemePackTest,NullTints)358 TEST_F(BrowserThemePackTest, NullTints) {
359 LoadTintDictionary(NULL);
360 }
361
TEST_F(BrowserThemePackTest,NullColors)362 TEST_F(BrowserThemePackTest, NullColors) {
363 LoadColorDictionary(NULL);
364 }
365
TEST_F(BrowserThemePackTest,NullDisplayProperties)366 TEST_F(BrowserThemePackTest, NullDisplayProperties) {
367 LoadDisplayPropertiesDictionary(NULL);
368 }
369
TEST_F(BrowserThemePackTest,TestHasCustomImage)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
TEST_F(BrowserThemePackTest,TestNonExistantImages)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.
TEST_F(BrowserThemePackTest,CanBuildAndReadPack)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