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