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
21class 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
201TEST_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
220TEST_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
241TEST_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
252TEST_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
265TEST_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
278TEST_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
298TEST_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
312TEST_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
323TEST_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
331TEST_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
342TEST_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.
353TEST_F(BrowserThemePackTest, NullPaths) {
354  std::map<int, FilePath> out_file_paths;
355  ParseImageNamesDictionary(NULL, &out_file_paths);
356}
357
358TEST_F(BrowserThemePackTest, NullTints) {
359  LoadTintDictionary(NULL);
360}
361
362TEST_F(BrowserThemePackTest, NullColors) {
363  LoadColorDictionary(NULL);
364}
365
366TEST_F(BrowserThemePackTest, NullDisplayProperties) {
367  LoadDisplayPropertiesDictionary(NULL);
368}
369
370TEST_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
381TEST_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.
392TEST_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