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