browser_theme_pack.cc revision bda42a81ee5f9b20d2bebedcf0bbef1e30e5b293
1// Copyright (c) 2010 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 "app/resource_bundle.h"
8#include "base/data_pack.h"
9#include "base/stl_util-inl.h"
10#include "base/string_util.h"
11#include "base/utf_string_conversions.h"
12#include "base/values.h"
13#include "chrome/browser/chrome_thread.h"
14#include "chrome/browser/themes/browser_theme_provider.h"
15#include "gfx/codec/png_codec.h"
16#include "gfx/skbitmap_operations.h"
17#include "grit/app_resources.h"
18#include "grit/theme_resources.h"
19#include "net/base/file_stream.h"
20#include "net/base/net_errors.h"
21#include "third_party/skia/include/core/SkCanvas.h"
22
23namespace {
24
25// Version number of the current theme pack. We just throw out and rebuild
26// theme packs that aren't int-equal to this.
27const int kThemePackVersion = 15;
28
29// IDs that are in the DataPack won't clash with the positive integer
30// int32_t. kHeaderID should always have the maximum value because we want the
31// "header" to be written last. That way we can detect whether the pack was
32// successfully written and ignore and regenerate if it was only partially
33// written (i.e. chrome crashed on a different thread while writing the pack).
34const int kHeaderID = UINT_MAX - 1;
35const int kTintsID = UINT_MAX - 2;
36const int kColorsID = UINT_MAX - 3;
37const int kDisplayPropertiesID = UINT_MAX - 4;
38const int kSourceImagesID = UINT_MAX - 5;
39
40// Static size of the tint/color/display property arrays that are mmapped.
41const int kTintArraySize = 6;
42const int kColorArraySize = 19;
43const int kDisplayPropertySize = 3;
44
45// The sum of kFrameBorderThickness and kNonClientRestoredExtraThickness from
46// OpaqueBrowserFrameView.
47const int kRestoredTabVerticalOffset = 15;
48
49// Persistent constants for the main images that we need. These have the same
50// names as their IDR_* counterparts but these values will always stay the
51// same.
52const int PRS_THEME_FRAME = 1;
53const int PRS_THEME_FRAME_INACTIVE = 2;
54const int PRS_THEME_FRAME_INCOGNITO = 3;
55const int PRS_THEME_FRAME_INCOGNITO_INACTIVE = 4;
56const int PRS_THEME_TOOLBAR = 5;
57const int PRS_THEME_TAB_BACKGROUND = 6;
58const int PRS_THEME_TAB_BACKGROUND_INCOGNITO = 7;
59const int PRS_THEME_TAB_BACKGROUND_V = 8;
60const int PRS_THEME_NTP_BACKGROUND = 9;
61const int PRS_THEME_FRAME_OVERLAY = 10;
62const int PRS_THEME_FRAME_OVERLAY_INACTIVE = 11;
63const int PRS_THEME_BUTTON_BACKGROUND = 12;
64const int PRS_THEME_NTP_ATTRIBUTION = 13;
65const int PRS_THEME_WINDOW_CONTROL_BACKGROUND = 14;
66
67struct PersistingImagesTable {
68  // A non-changing integer ID meant to be saved in theme packs. This ID must
69  // not change between versions of chrome.
70  int persistent_id;
71
72  // The IDR that depends on the whims of GRIT and therefore changes whenever
73  // someone adds a new resource.
74  int idr_id;
75
76  // String to check for when parsing theme manifests or NULL if this isn't
77  // supposed to be changeable by the user.
78  const char* key;
79};
80
81// IDR_* resource names change whenever new resources are added; use persistent
82// IDs when storing to a cached pack.
83PersistingImagesTable kPersistingImages[] = {
84  { PRS_THEME_FRAME, IDR_THEME_FRAME,
85    "theme_frame" },
86  { PRS_THEME_FRAME_INACTIVE, IDR_THEME_FRAME_INACTIVE,
87    "theme_frame_inactive" },
88  { PRS_THEME_FRAME_INCOGNITO, IDR_THEME_FRAME_INCOGNITO,
89    "theme_frame_incognito" },
90  { PRS_THEME_FRAME_INCOGNITO_INACTIVE, IDR_THEME_FRAME_INCOGNITO_INACTIVE,
91    "theme_frame_incognito_inactive" },
92  { PRS_THEME_TOOLBAR, IDR_THEME_TOOLBAR,
93    "theme_toolbar" },
94  { PRS_THEME_TAB_BACKGROUND, IDR_THEME_TAB_BACKGROUND,
95    "theme_tab_background" },
96  { PRS_THEME_TAB_BACKGROUND_INCOGNITO, IDR_THEME_TAB_BACKGROUND_INCOGNITO,
97    "theme_tab_background_incognito" },
98  { PRS_THEME_TAB_BACKGROUND_V, IDR_THEME_TAB_BACKGROUND_V,
99    "theme_tab_background_v"},
100  { PRS_THEME_NTP_BACKGROUND, IDR_THEME_NTP_BACKGROUND,
101    "theme_ntp_background" },
102  { PRS_THEME_FRAME_OVERLAY, IDR_THEME_FRAME_OVERLAY,
103    "theme_frame_overlay" },
104  { PRS_THEME_FRAME_OVERLAY_INACTIVE, IDR_THEME_FRAME_OVERLAY_INACTIVE,
105    "theme_frame_overlay_inactive" },
106  { PRS_THEME_BUTTON_BACKGROUND, IDR_THEME_BUTTON_BACKGROUND,
107    "theme_button_background" },
108  { PRS_THEME_NTP_ATTRIBUTION, IDR_THEME_NTP_ATTRIBUTION,
109    "theme_ntp_attribution" },
110  { PRS_THEME_WINDOW_CONTROL_BACKGROUND, IDR_THEME_WINDOW_CONTROL_BACKGROUND,
111    "theme_window_control_background"},
112
113  // The rest of these entries have no key because they can't be overridden
114  // from the json manifest.
115  { 15, IDR_BACK, NULL },
116  { 16, IDR_BACK_D, NULL },
117  { 17, IDR_BACK_H, NULL },
118  { 18, IDR_BACK_P, NULL },
119  { 19, IDR_FORWARD, NULL },
120  { 20, IDR_FORWARD_D, NULL },
121  { 21, IDR_FORWARD_H, NULL },
122  { 22, IDR_FORWARD_P, NULL },
123  { 23, IDR_HOME, NULL },
124  { 24, IDR_HOME_H, NULL },
125  { 25, IDR_HOME_P, NULL },
126  { 26, IDR_RELOAD, NULL },
127  { 27, IDR_RELOAD_H, NULL },
128  { 28, IDR_RELOAD_P, NULL },
129  { 29, IDR_STOP, NULL },
130  { 30, IDR_STOP_D, NULL },
131  { 31, IDR_STOP_H, NULL },
132  { 32, IDR_STOP_P, NULL },
133  { 33, IDR_LOCATIONBG_C, NULL },
134  { 34, IDR_LOCATIONBG_L, NULL },
135  { 35, IDR_LOCATIONBG_R, NULL },
136  { 36, IDR_BROWSER_ACTIONS_OVERFLOW, NULL },
137  { 37, IDR_BROWSER_ACTIONS_OVERFLOW_H, NULL },
138  { 38, IDR_BROWSER_ACTIONS_OVERFLOW_P, NULL },
139  { 39, IDR_TOOLS, NULL },
140  { 40, IDR_TOOLS_H, NULL },
141  { 41, IDR_TOOLS_P, NULL },
142  { 42, IDR_MENU_DROPARROW, NULL },
143  { 43, IDR_THROBBER, NULL },
144  { 44, IDR_THROBBER_WAITING, NULL },
145  { 45, IDR_THROBBER_LIGHT, NULL },
146};
147
148int GetPersistentIDByName(const std::string& key) {
149  for (size_t i = 0; i < arraysize(kPersistingImages); ++i) {
150    if (kPersistingImages[i].key != NULL &&
151        base::strcasecmp(key.c_str(), kPersistingImages[i].key) == 0) {
152      return kPersistingImages[i].persistent_id;
153    }
154  }
155
156  return -1;
157}
158
159int GetPersistentIDByIDR(int idr) {
160  for (size_t i = 0; i < arraysize(kPersistingImages); ++i) {
161    if (kPersistingImages[i].idr_id == idr) {
162      return kPersistingImages[i].persistent_id;
163    }
164  }
165
166  return -1;
167}
168
169struct StringToIntTable {
170  const char* key;
171  int id;
172};
173
174// Strings used by themes to identify tints in the JSON.
175StringToIntTable kTintTable[] = {
176  { "buttons", BrowserThemeProvider::TINT_BUTTONS },
177  { "frame", BrowserThemeProvider::TINT_FRAME },
178  { "frame_inactive", BrowserThemeProvider::TINT_FRAME_INACTIVE },
179  { "frame_incognito", BrowserThemeProvider::TINT_FRAME_INCOGNITO },
180  { "frame_incognito_inactive",
181    BrowserThemeProvider::TINT_FRAME_INCOGNITO_INACTIVE },
182  { "background_tab", BrowserThemeProvider::TINT_BACKGROUND_TAB },
183  { NULL, 0 }
184};
185
186// Strings used by themes to identify colors in the JSON.
187StringToIntTable kColorTable[] = {
188  { "frame", BrowserThemeProvider::COLOR_FRAME },
189  { "frame_inactive", BrowserThemeProvider::COLOR_FRAME_INACTIVE },
190  { "frame_incognito", BrowserThemeProvider::COLOR_FRAME_INCOGNITO },
191  { "frame_incognito_inactive",
192    BrowserThemeProvider::COLOR_FRAME_INCOGNITO_INACTIVE },
193  { "toolbar", BrowserThemeProvider::COLOR_TOOLBAR },
194  { "tab_text", BrowserThemeProvider::COLOR_TAB_TEXT },
195  { "tab_background_text", BrowserThemeProvider::COLOR_BACKGROUND_TAB_TEXT },
196  { "bookmark_text", BrowserThemeProvider::COLOR_BOOKMARK_TEXT },
197  { "ntp_background", BrowserThemeProvider::COLOR_NTP_BACKGROUND },
198  { "ntp_text", BrowserThemeProvider::COLOR_NTP_TEXT },
199  { "ntp_link", BrowserThemeProvider::COLOR_NTP_LINK },
200  { "ntp_link_underline", BrowserThemeProvider::COLOR_NTP_LINK_UNDERLINE },
201  { "ntp_header", BrowserThemeProvider::COLOR_NTP_HEADER },
202  { "ntp_section", BrowserThemeProvider::COLOR_NTP_SECTION },
203  { "ntp_section_text", BrowserThemeProvider::COLOR_NTP_SECTION_TEXT },
204  { "ntp_section_link", BrowserThemeProvider::COLOR_NTP_SECTION_LINK },
205  { "ntp_section_link_underline",
206    BrowserThemeProvider::COLOR_NTP_SECTION_LINK_UNDERLINE },
207  { "control_background", BrowserThemeProvider::COLOR_CONTROL_BACKGROUND },
208  { "button_background", BrowserThemeProvider::COLOR_BUTTON_BACKGROUND },
209  { NULL, 0 }
210};
211
212// Strings used by themes to identify display properties keys in JSON.
213StringToIntTable kDisplayProperties[] = {
214  { "ntp_background_alignment",
215    BrowserThemeProvider::NTP_BACKGROUND_ALIGNMENT },
216  { "ntp_background_repeat", BrowserThemeProvider::NTP_BACKGROUND_TILING },
217  { "ntp_logo_alternate", BrowserThemeProvider::NTP_LOGO_ALTERNATE },
218  { NULL, 0 }
219};
220
221// Strings used by the tiling values in JSON.
222StringToIntTable kTilingStrings[] = {
223  { "no-repeat", BrowserThemeProvider::NO_REPEAT },
224  { "repeat-x", BrowserThemeProvider::REPEAT_X },
225  { "repeat-y", BrowserThemeProvider::REPEAT_Y },
226  { "repeat", BrowserThemeProvider::REPEAT },
227  { NULL, 0 }
228};
229
230int GetIntForString(const std::string& key, StringToIntTable* table) {
231  for (int i = 0; table[i].key != NULL; ++i) {
232    if (base::strcasecmp(key.c_str(), table[i].key) == 0) {
233      return table[i].id;
234    }
235  }
236
237  return -1;
238}
239
240struct IntToIntTable {
241  int key;
242  int value;
243};
244
245// Mapping used in GenerateFrameImages() to associate frame images with the
246// tint ID that should maybe be applied to it.
247IntToIntTable kFrameTintMap[] = {
248  { PRS_THEME_FRAME, BrowserThemeProvider::TINT_FRAME },
249  { PRS_THEME_FRAME_INACTIVE, BrowserThemeProvider::TINT_FRAME_INACTIVE },
250  { PRS_THEME_FRAME_OVERLAY, BrowserThemeProvider::TINT_FRAME },
251  { PRS_THEME_FRAME_OVERLAY_INACTIVE,
252    BrowserThemeProvider::TINT_FRAME_INACTIVE },
253  { PRS_THEME_FRAME_INCOGNITO, BrowserThemeProvider::TINT_FRAME_INCOGNITO },
254  { PRS_THEME_FRAME_INCOGNITO_INACTIVE,
255    BrowserThemeProvider::TINT_FRAME_INCOGNITO_INACTIVE }
256};
257
258// Mapping used in GenerateTabBackgroundImages() to associate what frame image
259// goes with which tab background.
260IntToIntTable kTabBackgroundMap[] = {
261  { PRS_THEME_TAB_BACKGROUND, PRS_THEME_FRAME },
262  { PRS_THEME_TAB_BACKGROUND_INCOGNITO, PRS_THEME_FRAME_INCOGNITO }
263};
264
265// A list of images that don't need tinting or any other modification and can
266// be byte-copied directly into the finished DataPack. This should contain the
267// persistent IDs for all themeable image IDs that aren't in kFrameTintMap or
268// kTabBackgroundMap.
269const int kPreloadIDs[] = {
270  PRS_THEME_TOOLBAR,
271  PRS_THEME_NTP_BACKGROUND,
272  PRS_THEME_BUTTON_BACKGROUND,
273  PRS_THEME_NTP_ATTRIBUTION,
274  PRS_THEME_WINDOW_CONTROL_BACKGROUND
275};
276
277// Returns a piece of memory with the contents of the file |path|.
278RefCountedMemory* ReadFileData(const FilePath& path) {
279  if (!path.empty()) {
280    net::FileStream file;
281    int flags = base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ;
282    if (file.Open(path, flags) == net::OK) {
283      int64 avail = file.Available();
284      if (avail > 0 && avail < INT_MAX) {
285        size_t size = static_cast<size_t>(avail);
286        std::vector<unsigned char> raw_data;
287        raw_data.resize(size);
288        char* data = reinterpret_cast<char*>(&(raw_data.front()));
289        if (file.ReadUntilComplete(data, size) == avail)
290          return RefCountedBytes::TakeVector(&raw_data);
291      }
292    }
293  }
294
295  return NULL;
296}
297
298// Does error checking for invalid incoming data while trying to read an
299// floating point value.
300bool ValidRealValue(ListValue* tint_list, int index, double* out) {
301  if (tint_list->GetReal(index, out))
302    return true;
303
304  int value = 0;
305  if (tint_list->GetInteger(index, &value)) {
306    *out = value;
307    return true;
308  }
309
310  return false;
311}
312
313}  // namespace
314
315BrowserThemePack::~BrowserThemePack() {
316  if (!data_pack_.get()) {
317    delete header_;
318    delete [] tints_;
319    delete [] colors_;
320    delete [] display_properties_;
321    delete [] source_images_;
322  }
323
324  STLDeleteValues(&prepared_images_);
325  STLDeleteValues(&loaded_images_);
326}
327
328// static
329BrowserThemePack* BrowserThemePack::BuildFromExtension(Extension* extension) {
330  DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
331  DCHECK(extension);
332  DCHECK(extension->is_theme());
333
334  BrowserThemePack* pack = new BrowserThemePack;
335  pack->BuildHeader(extension);
336  pack->BuildTintsFromJSON(extension->GetThemeTints());
337  pack->BuildColorsFromJSON(extension->GetThemeColors());
338  pack->BuildDisplayPropertiesFromJSON(extension->GetThemeDisplayProperties());
339
340  // Builds the images. (Image building is dependent on tints).
341  FilePathMap file_paths;
342  pack->ParseImageNamesFromJSON(extension->GetThemeImages(),
343                                extension->path(),
344                                &file_paths);
345  pack->BuildSourceImagesArray(file_paths);
346
347  if (!pack->LoadRawBitmapsTo(file_paths, &pack->prepared_images_))
348    return NULL;
349
350  pack->GenerateFrameImages(&pack->prepared_images_);
351
352#if !defined(OS_MACOSX)
353  // OSX uses its own special buttons that are PDFs that do odd sorts of vector
354  // graphics tricks. Other platforms use bitmaps and we must pre-tint them.
355  pack->GenerateTintedButtons(
356      pack->GetTintInternal(BrowserThemeProvider::TINT_BUTTONS),
357      &pack->prepared_images_);
358#endif
359
360  pack->GenerateTabBackgroundImages(&pack->prepared_images_);
361
362  // The BrowserThemePack is now in a consistent state.
363  return pack;
364}
365
366// static
367scoped_refptr<BrowserThemePack> BrowserThemePack::BuildFromDataPack(
368    FilePath path, const std::string& expected_id) {
369  DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
370  scoped_refptr<BrowserThemePack> pack = new BrowserThemePack;
371  pack->data_pack_.reset(new base::DataPack);
372
373  if (!pack->data_pack_->Load(path)) {
374    LOG(ERROR) << "Failed to load theme data pack.";
375    return NULL;
376  }
377
378  base::StringPiece pointer;
379  if (!pack->data_pack_->GetStringPiece(kHeaderID, &pointer))
380    return NULL;
381  pack->header_ = reinterpret_cast<BrowserThemePackHeader*>(const_cast<char*>(
382      pointer.data()));
383
384  if (pack->header_->version != kThemePackVersion) {
385    DLOG(ERROR) << "BuildFromDataPack failure! Version mismatch!";
386    return NULL;
387  }
388  // TODO(erg): Check endianess once DataPack works on the other endian.
389  std::string theme_id(reinterpret_cast<char*>(pack->header_->theme_id),
390                       Extension::kIdSize);
391  std::string truncated_id = expected_id.substr(0, Extension::kIdSize);
392  if (theme_id != truncated_id) {
393    DLOG(ERROR) << "Wrong id: " << theme_id << " vs " << expected_id;
394    return NULL;
395  }
396
397  if (!pack->data_pack_->GetStringPiece(kTintsID, &pointer))
398    return NULL;
399  pack->tints_ = reinterpret_cast<TintEntry*>(const_cast<char*>(
400      pointer.data()));
401
402  if (!pack->data_pack_->GetStringPiece(kColorsID, &pointer))
403    return NULL;
404  pack->colors_ =
405      reinterpret_cast<ColorPair*>(const_cast<char*>(pointer.data()));
406
407  if (!pack->data_pack_->GetStringPiece(kDisplayPropertiesID, &pointer))
408    return NULL;
409  pack->display_properties_ = reinterpret_cast<DisplayPropertyPair*>(
410      const_cast<char*>(pointer.data()));
411
412  if (!pack->data_pack_->GetStringPiece(kSourceImagesID, &pointer))
413    return NULL;
414  pack->source_images_ = reinterpret_cast<int*>(
415      const_cast<char*>(pointer.data()));
416
417  return pack;
418}
419
420bool BrowserThemePack::WriteToDisk(FilePath path) const {
421  DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE));
422  // Add resources for each of the property arrays.
423  RawDataForWriting resources;
424  resources[kHeaderID] = base::StringPiece(
425      reinterpret_cast<const char*>(header_), sizeof(BrowserThemePackHeader));
426  resources[kTintsID] = base::StringPiece(
427      reinterpret_cast<const char*>(tints_), sizeof(TintEntry[kTintArraySize]));
428  resources[kColorsID] = base::StringPiece(
429      reinterpret_cast<const char*>(colors_),
430      sizeof(ColorPair[kColorArraySize]));
431  resources[kDisplayPropertiesID] = base::StringPiece(
432      reinterpret_cast<const char*>(display_properties_),
433      sizeof(DisplayPropertyPair[kDisplayPropertySize]));
434
435  int source_count = 1;
436  int* end = source_images_;
437  for (; *end != -1 ; end++)
438    source_count++;
439  resources[kSourceImagesID] = base::StringPiece(
440      reinterpret_cast<const char*>(source_images_),
441      source_count * sizeof(*source_images_));
442
443  AddRawImagesTo(image_memory_, &resources);
444
445  RawImages reencoded_images;
446  RepackImages(prepared_images_, &reencoded_images);
447  AddRawImagesTo(reencoded_images, &resources);
448
449  return base::DataPack::WritePack(path, resources);
450}
451
452bool BrowserThemePack::GetTint(int id, color_utils::HSL* hsl) const {
453  if (tints_) {
454    for (int i = 0; i < kTintArraySize; ++i) {
455      if (tints_[i].id == id) {
456        hsl->h = tints_[i].h;
457        hsl->s = tints_[i].s;
458        hsl->l = tints_[i].l;
459        return true;
460      }
461    }
462  }
463
464  return false;
465}
466
467bool BrowserThemePack::GetColor(int id, SkColor* color) const {
468  if (colors_) {
469    for (int i = 0; i < kColorArraySize; ++i) {
470      if (colors_[i].id == id) {
471        *color = colors_[i].color;
472        return true;
473      }
474    }
475  }
476
477  return false;
478}
479
480bool BrowserThemePack::GetDisplayProperty(int id, int* result) const {
481  if (display_properties_) {
482    for (int i = 0; i < kDisplayPropertySize; ++i) {
483      if (display_properties_[i].id == id) {
484        *result = display_properties_[i].property;
485        return true;
486      }
487    }
488  }
489
490  return false;
491}
492
493SkBitmap* BrowserThemePack::GetBitmapNamed(int idr_id) const {
494  int prs_id = GetPersistentIDByIDR(idr_id);
495  if (prs_id == -1)
496    return NULL;
497
498  // Check our cache of prepared images, first.
499  ImageCache::const_iterator image_iter = prepared_images_.find(prs_id);
500  if (image_iter != prepared_images_.end())
501    return image_iter->second;
502
503  // Check if we've already loaded this image.
504  image_iter = loaded_images_.find(prs_id);
505  if (image_iter != loaded_images_.end())
506    return image_iter->second;
507
508  scoped_refptr<RefCountedMemory> memory;
509  if (data_pack_.get()) {
510    memory = data_pack_->GetStaticMemory(prs_id);
511  } else {
512    RawImages::const_iterator it = image_memory_.find(prs_id);
513    if (it != image_memory_.end()) {
514      memory = it->second;
515    }
516  }
517
518  if (memory.get()) {
519    // Decode the PNG.
520    SkBitmap bitmap;
521    if (!gfx::PNGCodec::Decode(memory->front(), memory->size(),
522                               &bitmap)) {
523      NOTREACHED() << "Unable to decode theme image resource " << idr_id
524                   << " from saved DataPack.";
525      return NULL;
526    }
527
528    SkBitmap* ret = new SkBitmap(bitmap);
529    loaded_images_[prs_id] = ret;
530
531    return ret;
532  }
533
534  return NULL;
535}
536
537RefCountedMemory* BrowserThemePack::GetRawData(int idr_id) const {
538  RefCountedMemory* memory = NULL;
539  int prs_id = GetPersistentIDByIDR(idr_id);
540
541  if (prs_id != -1) {
542    if (data_pack_.get()) {
543      memory = data_pack_->GetStaticMemory(prs_id);
544    } else {
545      RawImages::const_iterator it = image_memory_.find(prs_id);
546      if (it != image_memory_.end()) {
547        memory = it->second;
548      }
549    }
550  }
551
552  return memory;
553}
554
555bool BrowserThemePack::HasCustomImage(int idr_id) const {
556  int prs_id = GetPersistentIDByIDR(idr_id);
557  if (prs_id == -1)
558    return false;
559
560  int* img = source_images_;
561  for (; *img != -1; ++img) {
562    if (*img == prs_id)
563      return true;
564  }
565
566  return false;
567}
568
569// private:
570
571BrowserThemePack::BrowserThemePack()
572    : header_(NULL),
573      tints_(NULL),
574      colors_(NULL),
575      display_properties_(NULL),
576      source_images_(NULL) {
577}
578
579void BrowserThemePack::BuildHeader(Extension* extension) {
580  header_ = new BrowserThemePackHeader;
581  header_->version = kThemePackVersion;
582
583  // TODO(erg): Need to make this endian safe on other computers. Prerequisite
584  // is that base::DataPack removes this same check.
585#if defined(__BYTE_ORDER)
586  // Linux check
587  COMPILE_ASSERT(__BYTE_ORDER == __LITTLE_ENDIAN,
588                 datapack_assumes_little_endian);
589#elif defined(__BIG_ENDIAN__)
590  // Mac check
591  #error DataPack assumes little endian
592#endif
593  header_->little_endian = 1;
594
595  const std::string& id = extension->id();
596  memcpy(header_->theme_id, id.c_str(), Extension::kIdSize);
597}
598
599void BrowserThemePack::BuildTintsFromJSON(DictionaryValue* tints_value) {
600  tints_ = new TintEntry[kTintArraySize];
601  for (int i = 0; i < kTintArraySize; ++i) {
602    tints_[i].id = -1;
603    tints_[i].h = -1;
604    tints_[i].s = -1;
605    tints_[i].l = -1;
606  }
607
608  if (!tints_value)
609    return;
610
611  // Parse the incoming data from |tints_value| into an intermediary structure.
612  std::map<int, color_utils::HSL> temp_tints;
613  for (DictionaryValue::key_iterator iter(tints_value->begin_keys());
614       iter != tints_value->end_keys(); ++iter) {
615    ListValue* tint_list;
616    if (tints_value->GetList(*iter, &tint_list) &&
617        (tint_list->GetSize() == 3)) {
618      color_utils::HSL hsl = { -1, -1, -1 };
619
620      if (ValidRealValue(tint_list, 0, &hsl.h) &&
621          ValidRealValue(tint_list, 1, &hsl.s) &&
622          ValidRealValue(tint_list, 2, &hsl.l)) {
623        int id = GetIntForString(*iter, kTintTable);
624        if (id != -1) {
625          temp_tints[id] = hsl;
626        }
627      }
628    }
629  }
630
631  // Copy data from the intermediary data structure to the array.
632  int count = 0;
633  for (std::map<int, color_utils::HSL>::const_iterator it =
634           temp_tints.begin(); it != temp_tints.end() && count < kTintArraySize;
635       ++it, ++count) {
636    tints_[count].id = it->first;
637    tints_[count].h = it->second.h;
638    tints_[count].s = it->second.s;
639    tints_[count].l = it->second.l;
640  }
641}
642
643void BrowserThemePack::BuildColorsFromJSON(DictionaryValue* colors_value) {
644  colors_ = new ColorPair[kColorArraySize];
645  for (int i = 0; i < kColorArraySize; ++i) {
646    colors_[i].id = -1;
647    colors_[i].color = SkColorSetRGB(0, 0, 0);
648  }
649
650  std::map<int, SkColor> temp_colors;
651  if (colors_value)
652    ReadColorsFromJSON(colors_value, &temp_colors);
653  GenerateMissingColors(&temp_colors);
654
655  // Copy data from the intermediary data structure to the array.
656  int count = 0;
657  for (std::map<int, SkColor>::const_iterator it = temp_colors.begin();
658       it != temp_colors.end() && count < kColorArraySize; ++it, ++count) {
659    colors_[count].id = it->first;
660    colors_[count].color = it->second;
661  }
662}
663
664void BrowserThemePack::ReadColorsFromJSON(
665    DictionaryValue* colors_value,
666    std::map<int, SkColor>* temp_colors) {
667  // Parse the incoming data from |colors_value| into an intermediary structure.
668  for (DictionaryValue::key_iterator iter(colors_value->begin_keys());
669       iter != colors_value->end_keys(); ++iter) {
670    ListValue* color_list;
671    if (colors_value->GetList(*iter, &color_list) &&
672        ((color_list->GetSize() == 3) || (color_list->GetSize() == 4))) {
673      SkColor color = SK_ColorWHITE;
674      int r, g, b;
675      if (color_list->GetInteger(0, &r) &&
676          color_list->GetInteger(1, &g) &&
677          color_list->GetInteger(2, &b)) {
678        if (color_list->GetSize() == 4) {
679          double alpha;
680          int alpha_int;
681          if (color_list->GetReal(3, &alpha)) {
682            color = SkColorSetARGB(static_cast<int>(alpha * 255), r, g, b);
683          } else if (color_list->GetInteger(3, &alpha_int) &&
684                     (alpha_int == 0 || alpha_int == 1)) {
685            color = SkColorSetARGB(alpha_int ? 255 : 0, r, g, b);
686          } else {
687            // Invalid entry for part 4.
688            continue;
689          }
690        } else {
691          color = SkColorSetRGB(r, g, b);
692        }
693
694        int id = GetIntForString(*iter, kColorTable);
695        if (id != -1) {
696          (*temp_colors)[id] = color;
697        }
698      }
699    }
700  }
701}
702
703void BrowserThemePack::GenerateMissingColors(
704    std::map<int, SkColor>* colors) {
705  // Generate link colors, if missing. (See GetColor()).
706  if (!colors->count(BrowserThemeProvider::COLOR_NTP_HEADER) &&
707      colors->count(BrowserThemeProvider::COLOR_NTP_SECTION)) {
708    (*colors)[BrowserThemeProvider::COLOR_NTP_HEADER] =
709        (*colors)[BrowserThemeProvider::COLOR_NTP_SECTION];
710  }
711
712  if (!colors->count(BrowserThemeProvider::COLOR_NTP_SECTION_LINK_UNDERLINE) &&
713      colors->count(BrowserThemeProvider::COLOR_NTP_SECTION_LINK)) {
714    SkColor color_section_link =
715        (*colors)[BrowserThemeProvider::COLOR_NTP_SECTION_LINK];
716    (*colors)[BrowserThemeProvider::COLOR_NTP_SECTION_LINK_UNDERLINE] =
717        SkColorSetA(color_section_link, SkColorGetA(color_section_link) / 3);
718  }
719
720  if (!colors->count(BrowserThemeProvider::COLOR_NTP_LINK_UNDERLINE) &&
721      colors->count(BrowserThemeProvider::COLOR_NTP_LINK)) {
722    SkColor color_link = (*colors)[BrowserThemeProvider::COLOR_NTP_LINK];
723    (*colors)[BrowserThemeProvider::COLOR_NTP_LINK_UNDERLINE] =
724        SkColorSetA(color_link, SkColorGetA(color_link) / 3);
725  }
726
727  // Generate frame colors, if missing. (See GenerateFrameColors()).
728  SkColor frame;
729  std::map<int, SkColor>::const_iterator it =
730      colors->find(BrowserThemeProvider::COLOR_FRAME);
731  if (it != colors->end()) {
732    frame = it->second;
733  } else {
734    frame = BrowserThemeProvider::GetDefaultColor(
735        BrowserThemeProvider::COLOR_FRAME);
736  }
737
738  if (!colors->count(BrowserThemeProvider::COLOR_FRAME)) {
739    (*colors)[BrowserThemeProvider::COLOR_FRAME] =
740        HSLShift(frame, GetTintInternal(BrowserThemeProvider::TINT_FRAME));
741  }
742  if (!colors->count(BrowserThemeProvider::COLOR_FRAME_INACTIVE)) {
743    (*colors)[BrowserThemeProvider::COLOR_FRAME_INACTIVE] =
744        HSLShift(frame, GetTintInternal(
745            BrowserThemeProvider::TINT_FRAME_INACTIVE));
746  }
747  if (!colors->count(BrowserThemeProvider::COLOR_FRAME_INCOGNITO)) {
748    (*colors)[BrowserThemeProvider::COLOR_FRAME_INCOGNITO] =
749        HSLShift(frame, GetTintInternal(
750            BrowserThemeProvider::TINT_FRAME_INCOGNITO));
751  }
752  if (!colors->count(BrowserThemeProvider::COLOR_FRAME_INCOGNITO_INACTIVE)) {
753    (*colors)[BrowserThemeProvider::COLOR_FRAME_INCOGNITO_INACTIVE] =
754        HSLShift(frame, GetTintInternal(
755            BrowserThemeProvider::TINT_FRAME_INCOGNITO_INACTIVE));
756  }
757}
758
759void BrowserThemePack::BuildDisplayPropertiesFromJSON(
760    DictionaryValue* display_properties_value) {
761  display_properties_ = new DisplayPropertyPair[kDisplayPropertySize];
762  for (int i = 0; i < kDisplayPropertySize; ++i) {
763    display_properties_[i].id = -1;
764    display_properties_[i].property = 0;
765  }
766
767  if (!display_properties_value)
768    return;
769
770  std::map<int, int> temp_properties;
771  for (DictionaryValue::key_iterator iter(
772       display_properties_value->begin_keys());
773       iter != display_properties_value->end_keys(); ++iter) {
774    int property_id = GetIntForString(*iter, kDisplayProperties);
775    switch (property_id) {
776      case BrowserThemeProvider::NTP_BACKGROUND_ALIGNMENT: {
777        std::string val;
778        if (display_properties_value->GetString(*iter, &val)) {
779          temp_properties[BrowserThemeProvider::NTP_BACKGROUND_ALIGNMENT] =
780              BrowserThemeProvider::StringToAlignment(val);
781        }
782        break;
783      }
784      case BrowserThemeProvider::NTP_BACKGROUND_TILING: {
785        std::string val;
786        if (display_properties_value->GetString(*iter, &val)) {
787          temp_properties[BrowserThemeProvider::NTP_BACKGROUND_TILING] =
788              GetIntForString(val, kTilingStrings);
789        }
790        break;
791      }
792      case BrowserThemeProvider::NTP_LOGO_ALTERNATE: {
793        int val = 0;
794        if (display_properties_value->GetInteger(*iter, &val))
795          temp_properties[BrowserThemeProvider::NTP_LOGO_ALTERNATE] = val;
796        break;
797      }
798    }
799  }
800
801  // Copy data from the intermediary data structure to the array.
802  int count = 0;
803  for (std::map<int, int>::const_iterator it = temp_properties.begin();
804       it != temp_properties.end() && count < kDisplayPropertySize;
805       ++it, ++count) {
806    display_properties_[count].id = it->first;
807    display_properties_[count].property = it->second;
808  }
809}
810
811void BrowserThemePack::ParseImageNamesFromJSON(
812    DictionaryValue* images_value,
813    FilePath images_path,
814    FilePathMap* file_paths) const {
815  if (!images_value)
816    return;
817
818  for (DictionaryValue::key_iterator iter(images_value->begin_keys());
819       iter != images_value->end_keys(); ++iter) {
820    std::string val;
821    if (images_value->GetString(*iter, &val)) {
822      int id = GetPersistentIDByName(*iter);
823      if (id != -1)
824        (*file_paths)[id] = images_path.AppendASCII(val);
825    }
826  }
827}
828
829void BrowserThemePack::BuildSourceImagesArray(const FilePathMap& file_paths) {
830  std::vector<int> ids;
831  for (FilePathMap::const_iterator it = file_paths.begin();
832       it != file_paths.end(); ++it) {
833    ids.push_back(it->first);
834  }
835
836  source_images_ = new int[ids.size() + 1];
837  std::copy(ids.begin(), ids.end(), source_images_);
838  source_images_[ids.size()] = -1;
839}
840
841bool BrowserThemePack::LoadRawBitmapsTo(
842    const FilePathMap& file_paths,
843    ImageCache* raw_bitmaps) {
844  for (FilePathMap::const_iterator it = file_paths.begin();
845       it != file_paths.end(); ++it) {
846    scoped_refptr<RefCountedMemory> raw_data(ReadFileData(it->second));
847    if (!raw_data.get()) {
848      LOG(ERROR) << "Could not load theme image";
849      return false;
850    }
851
852    int id = it->first;
853
854    // Some images need to go directly into |image_memory_|. No modification is
855    // necessary or desirable.
856    bool is_copyable = false;
857    for (size_t i = 0; i < arraysize(kPreloadIDs); ++i) {
858      if (kPreloadIDs[i] == id) {
859        is_copyable = true;
860        break;
861      }
862    }
863
864    if (is_copyable) {
865      image_memory_[id] = raw_data;
866    } else if (raw_data.get() && raw_data->size()) {
867      // Decode the PNG.
868      SkBitmap bitmap;
869      if (gfx::PNGCodec::Decode(raw_data->front(), raw_data->size(),
870                                &bitmap)) {
871        (*raw_bitmaps)[it->first] = new SkBitmap(bitmap);
872      } else {
873        NOTREACHED() << "Unable to decode theme image resource " << it->first;
874      }
875    }
876  }
877
878  return true;
879}
880
881void BrowserThemePack::GenerateFrameImages(ImageCache* bitmaps) const {
882  ResourceBundle& rb = ResourceBundle::GetSharedInstance();
883
884  // Create all the output bitmaps in a separate cache and move them back into
885  // the input bitmaps because there can be name collisions.
886  ImageCache temp_output;
887
888  for (size_t i = 0; i < arraysize(kFrameTintMap); ++i) {
889    int prs_id = kFrameTintMap[i].key;
890    scoped_ptr<SkBitmap> frame;
891    // If there's no frame image provided for the specified id, then load
892    // the default provided frame. If that's not provided, skip this whole
893    // thing and just use the default images.
894    int prs_base_id;
895
896    if (prs_id == PRS_THEME_FRAME_INCOGNITO_INACTIVE) {
897      prs_base_id = bitmaps->count(PRS_THEME_FRAME_INCOGNITO) ?
898                    PRS_THEME_FRAME_INCOGNITO : PRS_THEME_FRAME;
899    } else if (prs_id == PRS_THEME_FRAME_OVERLAY_INACTIVE) {
900      prs_base_id = PRS_THEME_FRAME_OVERLAY;
901    } else if (prs_id == PRS_THEME_FRAME_INACTIVE) {
902      prs_base_id = PRS_THEME_FRAME;
903    } else if (prs_id == PRS_THEME_FRAME_INCOGNITO &&
904               !bitmaps->count(PRS_THEME_FRAME_INCOGNITO)) {
905      prs_base_id = PRS_THEME_FRAME;
906    } else {
907      prs_base_id = prs_id;
908    }
909
910    if (bitmaps->count(prs_id)) {
911      frame.reset(new SkBitmap(*(*bitmaps)[prs_id]));
912    } else if (prs_base_id != prs_id && bitmaps->count(prs_base_id)) {
913      frame.reset(new SkBitmap(*(*bitmaps)[prs_base_id]));
914    } else if (prs_base_id == PRS_THEME_FRAME_OVERLAY &&
915               bitmaps->count(PRS_THEME_FRAME)) {
916      // If there is no theme overlay, don't tint the default frame,
917      // because it will overwrite the custom frame image when we cache and
918      // reload from disk.
919      frame.reset(NULL);
920    } else {
921      // If the theme doesn't specify an image, then apply the tint to
922      // the default frame.
923      frame.reset(new SkBitmap(*rb.GetBitmapNamed(IDR_THEME_FRAME)));
924    }
925
926    if (frame.get()) {
927      temp_output[prs_id] = new SkBitmap(
928          SkBitmapOperations::CreateHSLShiftedBitmap(
929              *frame, GetTintInternal(kFrameTintMap[i].value)));
930    }
931  }
932
933  MergeImageCaches(temp_output, bitmaps);
934}
935
936void BrowserThemePack::GenerateTintedButtons(
937    color_utils::HSL button_tint,
938    ImageCache* processed_bitmaps) const {
939  if (button_tint.h != -1 || button_tint.s != -1 || button_tint.l != -1) {
940    ResourceBundle& rb = ResourceBundle::GetSharedInstance();
941    const std::set<int>& idr_ids =
942        BrowserThemeProvider::GetTintableToolbarButtons();
943    for (std::set<int>::const_iterator it = idr_ids.begin();
944         it != idr_ids.end(); ++it) {
945      int prs_id = GetPersistentIDByIDR(*it);
946      DCHECK(prs_id > 0);
947
948      // Fetch the image by IDR...
949      scoped_ptr<SkBitmap> button(new SkBitmap(*rb.GetBitmapNamed(*it)));
950
951      // but save a version with the persistent ID.
952      (*processed_bitmaps)[prs_id] = new SkBitmap(
953          SkBitmapOperations::CreateHSLShiftedBitmap(*button, button_tint));
954    }
955  }
956}
957
958void BrowserThemePack::GenerateTabBackgroundImages(ImageCache* bitmaps) const {
959  ImageCache temp_output;
960  for (size_t i = 0; i < arraysize(kTabBackgroundMap); ++i) {
961    int prs_id = kTabBackgroundMap[i].key;
962    int prs_base_id = kTabBackgroundMap[i].value;
963
964    // We only need to generate the background tab images if we were provided
965    // with a PRS_THEME_FRAME.
966    ImageCache::const_iterator it = bitmaps->find(prs_base_id);
967    if (it != bitmaps->end()) {
968      SkBitmap bg_tint = SkBitmapOperations::CreateHSLShiftedBitmap(
969          *(it->second), GetTintInternal(
970              BrowserThemeProvider::TINT_BACKGROUND_TAB));
971      int vertical_offset = bitmaps->count(prs_id)
972                            ? kRestoredTabVerticalOffset : 0;
973      SkBitmap* bg_tab = new SkBitmap(SkBitmapOperations::CreateTiledBitmap(
974          bg_tint, 0, vertical_offset, bg_tint.width(), bg_tint.height()));
975
976      // If they've provided a custom image, overlay it.
977      ImageCache::const_iterator overlay_it = bitmaps->find(prs_id);
978      if (overlay_it != bitmaps->end()) {
979        SkBitmap* overlay = overlay_it->second;
980        SkCanvas canvas(*bg_tab);
981        for (int x = 0; x < bg_tab->width(); x += overlay->width())
982          canvas.drawBitmap(*overlay, static_cast<SkScalar>(x), 0, NULL);
983      }
984
985      temp_output[prs_id] = bg_tab;
986    }
987  }
988
989  MergeImageCaches(temp_output, bitmaps);
990}
991
992void BrowserThemePack::RepackImages(const ImageCache& images,
993                                    RawImages* reencoded_images) const {
994  DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE));
995  for (ImageCache::const_iterator it = images.begin();
996       it != images.end(); ++it) {
997    std::vector<unsigned char> image_data;
998    if (!gfx::PNGCodec::EncodeBGRASkBitmap(*(it->second), false, &image_data)) {
999      NOTREACHED() << "Image file for resource " << it->first
1000                   << " could not be encoded.";
1001    } else {
1002      (*reencoded_images)[it->first] = RefCountedBytes::TakeVector(&image_data);
1003    }
1004  }
1005}
1006
1007void BrowserThemePack::MergeImageCaches(
1008    const ImageCache& source, ImageCache* destination) const {
1009
1010  for (ImageCache::const_iterator it = source.begin(); it != source.end();
1011       ++it) {
1012    ImageCache::const_iterator bitmap_it = destination->find(it->first);
1013    if (bitmap_it != destination->end())
1014      delete bitmap_it->second;
1015
1016    (*destination)[it->first] = it->second;
1017  }
1018}
1019
1020void BrowserThemePack::AddRawImagesTo(const RawImages& images,
1021                                      RawDataForWriting* out) const {
1022  DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE));
1023  for (RawImages::const_iterator it = images.begin(); it != images.end();
1024       ++it) {
1025    (*out)[it->first] = base::StringPiece(
1026        reinterpret_cast<const char*>(it->second->front()), it->second->size());
1027  }
1028}
1029
1030color_utils::HSL BrowserThemePack::GetTintInternal(int id) const {
1031  if (tints_) {
1032    for (int i = 0; i < kTintArraySize; ++i) {
1033      if (tints_[i].id == id) {
1034        color_utils::HSL hsl;
1035        hsl.h = tints_[i].h;
1036        hsl.s = tints_[i].s;
1037        hsl.l = tints_[i].l;
1038        return hsl;
1039      }
1040    }
1041  }
1042
1043  return BrowserThemeProvider::GetDefaultTint(id);
1044}
1045