theme_service.cc revision c2e0dbddbe15c98d52c4786dac06cb8952a8ae6d
1// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "chrome/browser/themes/theme_service.h" 6 7#include "base/bind.h" 8#include "base/memory/ref_counted_memory.h" 9#include "base/prefs/pref_service.h" 10#include "base/sequenced_task_runner.h" 11#include "base/string_util.h" 12#include "base/utf_string_conversions.h" 13#include "chrome/browser/extensions/extension_service.h" 14#include "chrome/browser/extensions/extension_system.h" 15#include "chrome/browser/profiles/profile.h" 16#include "chrome/browser/themes/browser_theme_pack.h" 17#include "chrome/browser/themes/theme_properties.h" 18#include "chrome/browser/themes/theme_syncable_service.h" 19#include "chrome/common/chrome_constants.h" 20#include "chrome/common/chrome_notification_types.h" 21#include "chrome/common/extensions/extension_manifest_constants.h" 22#include "chrome/common/pref_names.h" 23#include "content/public/browser/notification_service.h" 24#include "content/public/browser/user_metrics.h" 25#include "grit/theme_resources.h" 26#include "grit/ui_resources.h" 27#include "ui/base/layout.h" 28#include "ui/base/resource/resource_bundle.h" 29#include "ui/gfx/image/image_skia.h" 30 31#if defined(OS_WIN) 32#include "ui/base/win/shell.h" 33#endif 34 35#if defined(USE_AURA) && !defined(USE_ASH) && defined(OS_LINUX) 36#include "ui/linux_ui/linux_ui.h" 37#endif 38 39using content::BrowserThread; 40using content::UserMetricsAction; 41using extensions::Extension; 42using ui::ResourceBundle; 43 44typedef ThemeProperties Properties; 45 46// The default theme if we haven't installed a theme yet or if we've clicked 47// the "Use Classic" button. 48const char* ThemeService::kDefaultThemeID = ""; 49 50namespace { 51 52// The default theme if we've gone to the theme gallery and installed the 53// "Default" theme. We have to detect this case specifically. (By the time we 54// realize we've installed the default theme, we already have an extension 55// unpacked on the filesystem.) 56const char* kDefaultThemeGalleryID = "hkacjpbfdknhflllbcmjibkdeoafencn"; 57 58SkColor TintForUnderline(SkColor input) { 59 return SkColorSetA(input, SkColorGetA(input) / 3); 60} 61 62SkColor IncreaseLightness(SkColor color, double percent) { 63 color_utils::HSL result; 64 color_utils::SkColorToHSL(color, &result); 65 result.l += (1 - result.l) * percent; 66 return color_utils::HSLToSkColor(result, SkColorGetA(color)); 67} 68 69// Writes the theme pack to disk on a separate thread. 70void WritePackToDiskCallback(BrowserThemePack* pack, 71 const base::FilePath& path) { 72 if (!pack->WriteToDisk(path)) 73 NOTREACHED() << "Could not write theme pack to disk"; 74} 75 76} // namespace 77 78ThemeService::ThemeService() 79 : rb_(ResourceBundle::GetSharedInstance()), 80 profile_(NULL), 81 ready_(false), 82 number_of_infobars_(0) { 83} 84 85ThemeService::~ThemeService() { 86 FreePlatformCaches(); 87} 88 89void ThemeService::Init(Profile* profile) { 90 DCHECK(CalledOnValidThread()); 91 profile_ = profile; 92 93 LoadThemePrefs(); 94 95 if (!ready_) { 96 registrar_.Add(this, 97 chrome::NOTIFICATION_EXTENSIONS_READY, 98 content::Source<Profile>(profile_)); 99 } 100 101 theme_syncable_service_.reset(new ThemeSyncableService(profile_, this)); 102} 103 104gfx::Image ThemeService::GetImageNamed(int id) const { 105 DCHECK(CalledOnValidThread()); 106 107 gfx::Image image; 108 if (theme_pack_.get()) 109 image = theme_pack_->GetImageNamed(id); 110 111#if defined(USE_AURA) && !defined(USE_ASH) && defined(OS_LINUX) 112 const ui::LinuxUI* linux_ui = ui::LinuxUI::instance(); 113 if (image.IsEmpty() && linux_ui) 114 image = linux_ui->GetThemeImageNamed(id); 115#endif 116 117 if (image.IsEmpty()) 118 image = rb_.GetNativeImageNamed(id); 119 120 return image; 121} 122 123gfx::ImageSkia* ThemeService::GetImageSkiaNamed(int id) const { 124 gfx::Image image = GetImageNamed(id); 125 if (image.IsEmpty()) 126 return NULL; 127 // TODO(pkotwicz): Remove this const cast. The gfx::Image interface returns 128 // its images const. GetImageSkiaNamed() also should but has many callsites. 129 return const_cast<gfx::ImageSkia*>(image.ToImageSkia()); 130} 131 132SkColor ThemeService::GetColor(int id) const { 133 DCHECK(CalledOnValidThread()); 134 135 SkColor color; 136 if (theme_pack_.get() && theme_pack_->GetColor(id, &color)) 137 return color; 138 139#if defined(USE_AURA) && !defined(USE_ASH) && defined(OS_LINUX) 140 const ui::LinuxUI* linux_ui = ui::LinuxUI::instance(); 141 if (linux_ui && linux_ui->GetColor(id, &color)) 142 return color; 143#endif 144 145 // For backward compat with older themes, some newer colors are generated from 146 // older ones if they are missing. 147 switch (id) { 148 case Properties::COLOR_NTP_SECTION_HEADER_TEXT: 149 return IncreaseLightness(GetColor(Properties::COLOR_NTP_TEXT), 0.30); 150 case Properties::COLOR_NTP_SECTION_HEADER_TEXT_HOVER: 151 return GetColor(Properties::COLOR_NTP_TEXT); 152 case Properties::COLOR_NTP_SECTION_HEADER_RULE: 153 return IncreaseLightness(GetColor(Properties::COLOR_NTP_TEXT), 0.70); 154 case Properties::COLOR_NTP_SECTION_HEADER_RULE_LIGHT: 155 return IncreaseLightness(GetColor(Properties::COLOR_NTP_TEXT), 0.86); 156 case Properties::COLOR_NTP_TEXT_LIGHT: 157 return IncreaseLightness(GetColor(Properties::COLOR_NTP_TEXT), 0.40); 158 } 159 160 return Properties::GetDefaultColor(id); 161} 162 163bool ThemeService::GetDisplayProperty(int id, int* result) const { 164 if (theme_pack_.get()) 165 return theme_pack_->GetDisplayProperty(id, result); 166 167 return Properties::GetDefaultDisplayProperty(id, result); 168} 169 170bool ThemeService::ShouldUseNativeFrame() const { 171 if (HasCustomImage(IDR_THEME_FRAME)) 172 return false; 173#if defined(OS_WIN) 174 return ui::win::IsAeroGlassEnabled(); 175#else 176 return false; 177#endif 178} 179 180bool ThemeService::HasCustomImage(int id) const { 181 if (!Properties::IsThemeableImage(id)) 182 return false; 183 184 if (theme_pack_) 185 return theme_pack_->HasCustomImage(id); 186 187 return false; 188} 189 190base::RefCountedMemory* ThemeService::GetRawData( 191 int id, 192 ui::ScaleFactor scale_factor) const { 193 // Check to see whether we should substitute some images. 194 int ntp_alternate; 195 GetDisplayProperty(Properties::NTP_LOGO_ALTERNATE, &ntp_alternate); 196 if (id == IDR_PRODUCT_LOGO && ntp_alternate != 0) 197 id = IDR_PRODUCT_LOGO_WHITE; 198 199 base::RefCountedMemory* data = NULL; 200 if (theme_pack_.get()) 201 data = theme_pack_->GetRawData(id, scale_factor); 202 if (!data) 203 data = rb_.LoadDataResourceBytesForScale(id, ui::SCALE_FACTOR_100P); 204 205 return data; 206} 207 208void ThemeService::Observe(int type, 209 const content::NotificationSource& source, 210 const content::NotificationDetails& details) { 211 DCHECK(type == chrome::NOTIFICATION_EXTENSIONS_READY); 212 registrar_.Remove(this, chrome::NOTIFICATION_EXTENSIONS_READY, 213 content::Source<Profile>(profile_)); 214 215 MigrateTheme(); 216 set_ready(); 217 218 // Send notification in case anyone requested data and cached it when the 219 // theme service was not ready yet. 220 NotifyThemeChanged(); 221} 222 223void ThemeService::SetTheme(const Extension* extension) { 224 // Clear our image cache. 225 FreePlatformCaches(); 226 227 DCHECK(extension); 228 DCHECK(extension->is_theme()); 229 if (DCHECK_IS_ON()) { 230 ExtensionService* service = 231 extensions::ExtensionSystem::Get(profile_)->extension_service(); 232 DCHECK(service); 233 DCHECK(service->GetExtensionById(extension->id(), false)); 234 } 235 236 BuildFromExtension(extension); 237 SaveThemeID(extension->id()); 238 239 NotifyThemeChanged(); 240 content::RecordAction(UserMetricsAction("Themes_Installed")); 241} 242 243void ThemeService::RemoveUnusedThemes() { 244 if (!profile_) 245 return; 246 ExtensionService* service = profile_->GetExtensionService(); 247 if (!service) 248 return; 249 std::string current_theme = GetThemeID(); 250 std::vector<std::string> remove_list; 251 const ExtensionSet* extensions = service->extensions(); 252 for (ExtensionSet::const_iterator it = extensions->begin(); 253 it != extensions->end(); ++it) { 254 if ((*it)->is_theme() && (*it)->id() != current_theme) { 255 remove_list.push_back((*it)->id()); 256 } 257 } 258 for (size_t i = 0; i < remove_list.size(); ++i) 259 service->UninstallExtension(remove_list[i], false, NULL); 260} 261 262void ThemeService::UseDefaultTheme() { 263 ClearAllThemeData(); 264 NotifyThemeChanged(); 265 content::RecordAction(UserMetricsAction("Themes_Reset")); 266} 267 268void ThemeService::SetNativeTheme() { 269 UseDefaultTheme(); 270} 271 272bool ThemeService::UsingDefaultTheme() const { 273 std::string id = GetThemeID(); 274 return id == ThemeService::kDefaultThemeID || 275 id == kDefaultThemeGalleryID; 276} 277 278bool ThemeService::UsingNativeTheme() const { 279 return UsingDefaultTheme(); 280} 281 282std::string ThemeService::GetThemeID() const { 283 return profile_->GetPrefs()->GetString(prefs::kCurrentThemeID); 284} 285 286color_utils::HSL ThemeService::GetTint(int id) const { 287 DCHECK(CalledOnValidThread()); 288 289 color_utils::HSL hsl; 290 if (theme_pack_.get() && theme_pack_->GetTint(id, &hsl)) 291 return hsl; 292 293 return ThemeProperties::GetDefaultTint(id); 294} 295 296void ThemeService::ClearAllThemeData() { 297 // Clear our image cache. 298 FreePlatformCaches(); 299 theme_pack_ = NULL; 300 301 profile_->GetPrefs()->ClearPref(prefs::kCurrentThemePackFilename); 302 SaveThemeID(kDefaultThemeID); 303 304 RemoveUnusedThemes(); 305} 306 307void ThemeService::LoadThemePrefs() { 308 PrefService* prefs = profile_->GetPrefs(); 309 310 std::string current_id = GetThemeID(); 311 if (current_id == kDefaultThemeID) { 312 set_ready(); 313 return; 314 } 315 316 bool loaded_pack = false; 317 318 // If we don't have a file pack, we're updating from an old version. 319 base::FilePath path = prefs->GetFilePath(prefs::kCurrentThemePackFilename); 320 if (path != base::FilePath()) { 321 theme_pack_ = BrowserThemePack::BuildFromDataPack(path, current_id); 322 loaded_pack = theme_pack_.get() != NULL; 323 } 324 325 if (loaded_pack) { 326 content::RecordAction(UserMetricsAction("Themes.Loaded")); 327 set_ready(); 328 } else { 329 // TODO(erg): We need to pop up a dialog informing the user that their 330 // theme is being migrated. 331 ExtensionService* service = 332 extensions::ExtensionSystem::Get(profile_)->extension_service(); 333 if (service && service->is_ready()) { 334 MigrateTheme(); 335 set_ready(); 336 } 337 } 338} 339 340void ThemeService::NotifyThemeChanged() { 341 DVLOG(1) << "Sending BROWSER_THEME_CHANGED"; 342 // Redraw! 343 content::NotificationService* service = 344 content::NotificationService::current(); 345 service->Notify(chrome::NOTIFICATION_BROWSER_THEME_CHANGED, 346 content::Source<ThemeService>(this), 347 content::NotificationService::NoDetails()); 348#if defined(OS_MACOSX) 349 NotifyPlatformThemeChanged(); 350#endif // OS_MACOSX 351 352 // Notify sync that theme has changed. 353 if (theme_syncable_service_.get()) { 354 theme_syncable_service_->OnThemeChange(); 355 } 356} 357 358#if defined(OS_WIN) || defined(USE_AURA) 359void ThemeService::FreePlatformCaches() { 360 // Views (Skia) has no platform image cache to clear. 361} 362#endif 363 364void ThemeService::MigrateTheme() { 365 ExtensionService* service = 366 extensions::ExtensionSystem::Get(profile_)->extension_service(); 367 const Extension* extension = service ? 368 service->GetExtensionById(GetThemeID(), false) : NULL; 369 if (extension) { 370 DLOG(ERROR) << "Migrating theme"; 371 BuildFromExtension(extension); 372 content::RecordAction(UserMetricsAction("Themes.Migrated")); 373 } else { 374 DLOG(ERROR) << "Theme is mysteriously gone."; 375 ClearAllThemeData(); 376 content::RecordAction(UserMetricsAction("Themes.Gone")); 377 } 378} 379 380void ThemeService::SavePackName(const base::FilePath& pack_path) { 381 profile_->GetPrefs()->SetFilePath( 382 prefs::kCurrentThemePackFilename, pack_path); 383} 384 385void ThemeService::SaveThemeID(const std::string& id) { 386 profile_->GetPrefs()->SetString(prefs::kCurrentThemeID, id); 387} 388 389void ThemeService::BuildFromExtension(const Extension* extension) { 390 scoped_refptr<BrowserThemePack> pack( 391 BrowserThemePack::BuildFromExtension(extension)); 392 if (!pack.get()) { 393 // TODO(erg): We've failed to install the theme; perhaps we should tell the 394 // user? http://crbug.com/34780 395 LOG(ERROR) << "Could not load theme."; 396 return; 397 } 398 399 ExtensionService* service = 400 extensions::ExtensionSystem::Get(profile_)->extension_service(); 401 if (!service) 402 return; 403 404 // Write the packed file to disk. 405 base::FilePath pack_path = 406 extension->path().Append(chrome::kThemePackFilename); 407 service->GetFileTaskRunner()->PostTask( 408 FROM_HERE, 409 base::Bind(&WritePackToDiskCallback, pack, pack_path)); 410 411 SavePackName(pack_path); 412 theme_pack_ = pack; 413} 414 415void ThemeService::OnInfobarDisplayed() { 416 number_of_infobars_++; 417} 418 419void ThemeService::OnInfobarDestroyed() { 420 number_of_infobars_--; 421 422 if (number_of_infobars_ == 0) 423 RemoveUnusedThemes(); 424} 425 426ThemeSyncableService* ThemeService::GetThemeSyncableService() const { 427 return theme_syncable_service_.get(); 428} 429