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