Home | History | Annotate | Download | only in themes
      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/strings/string_util.h"
     12 #include "base/strings/utf_string_conversions.h"
     13 #include "chrome/browser/chrome_notification_types.h"
     14 #include "chrome/browser/extensions/extension_service.h"
     15 #include "chrome/browser/extensions/extension_system.h"
     16 #include "chrome/browser/managed_mode/managed_user_service.h"
     17 #include "chrome/browser/managed_mode/managed_user_service_factory.h"
     18 #include "chrome/browser/managed_mode/managed_user_theme.h"
     19 #include "chrome/browser/profiles/profile.h"
     20 #include "chrome/browser/themes/browser_theme_pack.h"
     21 #include "chrome/browser/themes/custom_theme_supplier.h"
     22 #include "chrome/browser/themes/theme_properties.h"
     23 #include "chrome/browser/themes/theme_syncable_service.h"
     24 #include "chrome/common/chrome_constants.h"
     25 #include "chrome/common/extensions/extension_manifest_constants.h"
     26 #include "chrome/common/pref_names.h"
     27 #include "content/public/browser/notification_service.h"
     28 #include "content/public/browser/user_metrics.h"
     29 #include "grit/theme_resources.h"
     30 #include "grit/ui_resources.h"
     31 #include "ui/base/layout.h"
     32 #include "ui/base/resource/resource_bundle.h"
     33 #include "ui/gfx/image/image_skia.h"
     34 
     35 #if defined(OS_WIN)
     36 #include "ui/base/win/shell.h"
     37 #endif
     38 
     39 using content::BrowserThread;
     40 using content::UserMetricsAction;
     41 using extensions::Extension;
     42 using ui::ResourceBundle;
     43 
     44 typedef 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.
     48 const char* ThemeService::kDefaultThemeID = "";
     49 
     50 namespace {
     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.)
     56 const char* kDefaultThemeGalleryID = "hkacjpbfdknhflllbcmjibkdeoafencn";
     57 
     58 SkColor TintForUnderline(SkColor input) {
     59   return SkColorSetA(input, SkColorGetA(input) / 3);
     60 }
     61 
     62 SkColor 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.
     70 void 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 
     78 ThemeService::ThemeService()
     79     : ready_(false),
     80       rb_(ResourceBundle::GetSharedInstance()),
     81       profile_(NULL),
     82       number_of_infobars_(0),
     83       weak_ptr_factory_(this) {
     84 }
     85 
     86 ThemeService::~ThemeService() {
     87   FreePlatformCaches();
     88 }
     89 
     90 void ThemeService::Init(Profile* profile) {
     91   DCHECK(CalledOnValidThread());
     92   profile_ = profile;
     93 
     94   ManagedUserServiceFactory::GetForProfile(profile)->AddInitCallback(base::Bind(
     95       &ThemeService::OnManagedUserInitialized, weak_ptr_factory_.GetWeakPtr()));
     96 
     97   LoadThemePrefs();
     98 
     99   if (!ready_) {
    100     registrar_.Add(this,
    101                    chrome::NOTIFICATION_EXTENSIONS_READY,
    102                    content::Source<Profile>(profile_));
    103   }
    104 
    105   theme_syncable_service_.reset(new ThemeSyncableService(profile_, this));
    106 }
    107 
    108 gfx::Image ThemeService::GetImageNamed(int id) const {
    109   DCHECK(CalledOnValidThread());
    110 
    111   gfx::Image image;
    112   if (theme_supplier_.get())
    113     image = theme_supplier_->GetImageNamed(id);
    114 
    115   if (image.IsEmpty())
    116     image = rb_.GetNativeImageNamed(id);
    117 
    118   return image;
    119 }
    120 
    121 gfx::ImageSkia* ThemeService::GetImageSkiaNamed(int id) const {
    122   gfx::Image image = GetImageNamed(id);
    123   if (image.IsEmpty())
    124     return NULL;
    125   // TODO(pkotwicz): Remove this const cast.  The gfx::Image interface returns
    126   // its images const. GetImageSkiaNamed() also should but has many callsites.
    127   return const_cast<gfx::ImageSkia*>(image.ToImageSkia());
    128 }
    129 
    130 SkColor ThemeService::GetColor(int id) const {
    131   DCHECK(CalledOnValidThread());
    132   SkColor color;
    133   if (theme_supplier_.get() && theme_supplier_->GetColor(id, &color))
    134     return color;
    135 
    136   // For backward compat with older themes, some newer colors are generated from
    137   // older ones if they are missing.
    138   switch (id) {
    139     case Properties::COLOR_NTP_SECTION_HEADER_TEXT:
    140       return IncreaseLightness(GetColor(Properties::COLOR_NTP_TEXT), 0.30);
    141     case Properties::COLOR_NTP_SECTION_HEADER_TEXT_HOVER:
    142       return GetColor(Properties::COLOR_NTP_TEXT);
    143     case Properties::COLOR_NTP_SECTION_HEADER_RULE:
    144       return IncreaseLightness(GetColor(Properties::COLOR_NTP_TEXT), 0.70);
    145     case Properties::COLOR_NTP_SECTION_HEADER_RULE_LIGHT:
    146       return IncreaseLightness(GetColor(Properties::COLOR_NTP_TEXT), 0.86);
    147     case Properties::COLOR_NTP_TEXT_LIGHT:
    148       return IncreaseLightness(GetColor(Properties::COLOR_NTP_TEXT), 0.40);
    149     case Properties::COLOR_MANAGED_USER_LABEL:
    150       return color_utils::GetReadableColor(
    151           SK_ColorWHITE,
    152           GetColor(Properties::COLOR_MANAGED_USER_LABEL_BACKGROUND));
    153     case Properties::COLOR_MANAGED_USER_LABEL_BACKGROUND:
    154       return color_utils::BlendTowardOppositeLuminance(
    155           GetColor(Properties::COLOR_FRAME), 0x80);
    156     case Properties::COLOR_MANAGED_USER_LABEL_BORDER:
    157       return color_utils::AlphaBlend(
    158           GetColor(Properties::COLOR_MANAGED_USER_LABEL_BACKGROUND),
    159           SK_ColorBLACK,
    160           230);
    161   }
    162 
    163   return Properties::GetDefaultColor(id);
    164 }
    165 
    166 bool ThemeService::GetDisplayProperty(int id, int* result) const {
    167   if (theme_supplier_.get())
    168     return theme_supplier_->GetDisplayProperty(id, result);
    169 
    170   return Properties::GetDefaultDisplayProperty(id, result);
    171 }
    172 
    173 bool ThemeService::ShouldUseNativeFrame() const {
    174   if (HasCustomImage(IDR_THEME_FRAME))
    175     return false;
    176 #if defined(OS_WIN)
    177   return ui::win::IsAeroGlassEnabled();
    178 #else
    179   return false;
    180 #endif
    181 }
    182 
    183 bool ThemeService::HasCustomImage(int id) const {
    184   if (!Properties::IsThemeableImage(id))
    185     return false;
    186 
    187   if (theme_supplier_.get())
    188     return theme_supplier_->HasCustomImage(id);
    189 
    190   return false;
    191 }
    192 
    193 base::RefCountedMemory* ThemeService::GetRawData(
    194     int id,
    195     ui::ScaleFactor scale_factor) const {
    196   // Check to see whether we should substitute some images.
    197   int ntp_alternate;
    198   GetDisplayProperty(Properties::NTP_LOGO_ALTERNATE, &ntp_alternate);
    199   if (id == IDR_PRODUCT_LOGO && ntp_alternate != 0)
    200     id = IDR_PRODUCT_LOGO_WHITE;
    201 
    202   base::RefCountedMemory* data = NULL;
    203   if (theme_supplier_.get())
    204     data = theme_supplier_->GetRawData(id, scale_factor);
    205   if (!data)
    206     data = rb_.LoadDataResourceBytesForScale(id, ui::SCALE_FACTOR_100P);
    207 
    208   return data;
    209 }
    210 
    211 void ThemeService::Observe(int type,
    212                            const content::NotificationSource& source,
    213                            const content::NotificationDetails& details) {
    214   DCHECK(type == chrome::NOTIFICATION_EXTENSIONS_READY);
    215   registrar_.Remove(this, chrome::NOTIFICATION_EXTENSIONS_READY,
    216       content::Source<Profile>(profile_));
    217 
    218   MigrateTheme();
    219   set_ready();
    220 
    221   // Send notification in case anyone requested data and cached it when the
    222   // theme service was not ready yet.
    223   NotifyThemeChanged();
    224 }
    225 
    226 void ThemeService::SetTheme(const Extension* extension) {
    227   // Clear our image cache.
    228   FreePlatformCaches();
    229 
    230   DCHECK(extension);
    231   DCHECK(extension->is_theme());
    232   if (DCHECK_IS_ON()) {
    233     ExtensionService* service =
    234         extensions::ExtensionSystem::Get(profile_)->extension_service();
    235     DCHECK(service);
    236     DCHECK(service->GetExtensionById(extension->id(), false));
    237   }
    238 
    239   BuildFromExtension(extension);
    240   SaveThemeID(extension->id());
    241 
    242   NotifyThemeChanged();
    243   content::RecordAction(UserMetricsAction("Themes_Installed"));
    244 }
    245 
    246 void ThemeService::SetCustomDefaultTheme(
    247     scoped_refptr<CustomThemeSupplier> theme_supplier) {
    248   ClearAllThemeData();
    249   SwapThemeSupplier(theme_supplier);
    250   NotifyThemeChanged();
    251 }
    252 
    253 bool ThemeService::ShouldInitWithNativeTheme() const {
    254   return false;
    255 }
    256 
    257 void ThemeService::RemoveUnusedThemes() {
    258   // We do not want to garbage collect themes on startup (|ready_| is false).
    259   // Themes will get garbage collected once
    260   // ExtensionService::GarbageCollectExtensions() runs.
    261   if (!profile_ || !ready_)
    262     return;
    263 
    264   ExtensionService* service = profile_->GetExtensionService();
    265   if (!service)
    266     return;
    267   std::string current_theme = GetThemeID();
    268   std::vector<std::string> remove_list;
    269   const ExtensionSet* extensions = service->extensions();
    270   for (ExtensionSet::const_iterator it = extensions->begin();
    271        it != extensions->end(); ++it) {
    272     if ((*it)->is_theme() && (*it)->id() != current_theme) {
    273       remove_list.push_back((*it)->id());
    274     }
    275   }
    276   for (size_t i = 0; i < remove_list.size(); ++i)
    277     service->UninstallExtension(remove_list[i], false, NULL);
    278 }
    279 
    280 void ThemeService::UseDefaultTheme() {
    281   if (ready_)
    282     content::RecordAction(UserMetricsAction("Themes_Reset"));
    283   if (IsManagedUser()) {
    284     SetManagedUserTheme();
    285     return;
    286   }
    287   ClearAllThemeData();
    288   NotifyThemeChanged();
    289 }
    290 
    291 void ThemeService::SetNativeTheme() {
    292   UseDefaultTheme();
    293 }
    294 
    295 bool ThemeService::UsingDefaultTheme() const {
    296   std::string id = GetThemeID();
    297   return id == ThemeService::kDefaultThemeID ||
    298       (id == kDefaultThemeGalleryID && !IsManagedUser());
    299 }
    300 
    301 bool ThemeService::UsingNativeTheme() const {
    302   return UsingDefaultTheme();
    303 }
    304 
    305 std::string ThemeService::GetThemeID() const {
    306   return profile_->GetPrefs()->GetString(prefs::kCurrentThemeID);
    307 }
    308 
    309 color_utils::HSL ThemeService::GetTint(int id) const {
    310   DCHECK(CalledOnValidThread());
    311 
    312   color_utils::HSL hsl;
    313   if (theme_supplier_.get() && theme_supplier_->GetTint(id, &hsl))
    314     return hsl;
    315 
    316   return ThemeProperties::GetDefaultTint(id);
    317 }
    318 
    319 void ThemeService::ClearAllThemeData() {
    320   if (!ready_)
    321     return;
    322 
    323   SwapThemeSupplier(NULL);
    324 
    325   // Clear our image cache.
    326   FreePlatformCaches();
    327 
    328   profile_->GetPrefs()->ClearPref(prefs::kCurrentThemePackFilename);
    329   SaveThemeID(kDefaultThemeID);
    330 
    331   RemoveUnusedThemes();
    332 }
    333 
    334 void ThemeService::LoadThemePrefs() {
    335   PrefService* prefs = profile_->GetPrefs();
    336 
    337   std::string current_id = GetThemeID();
    338   if (current_id == kDefaultThemeID) {
    339     // Managed users have a different default theme.
    340     if (IsManagedUser())
    341       SetManagedUserTheme();
    342     else if (ShouldInitWithNativeTheme())
    343       SetNativeTheme();
    344     else
    345       UseDefaultTheme();
    346     set_ready();
    347     return;
    348   }
    349 
    350   bool loaded_pack = false;
    351 
    352   // If we don't have a file pack, we're updating from an old version.
    353   base::FilePath path = prefs->GetFilePath(prefs::kCurrentThemePackFilename);
    354   if (path != base::FilePath()) {
    355     SwapThemeSupplier(BrowserThemePack::BuildFromDataPack(path, current_id));
    356     loaded_pack = theme_supplier_.get() != NULL;
    357   }
    358 
    359   if (loaded_pack) {
    360     content::RecordAction(UserMetricsAction("Themes.Loaded"));
    361     set_ready();
    362   } else {
    363     // TODO(erg): We need to pop up a dialog informing the user that their
    364     // theme is being migrated.
    365     ExtensionService* service =
    366         extensions::ExtensionSystem::Get(profile_)->extension_service();
    367     if (service && service->is_ready()) {
    368       MigrateTheme();
    369       set_ready();
    370     }
    371   }
    372 }
    373 
    374 void ThemeService::NotifyThemeChanged() {
    375   if (!ready_)
    376     return;
    377 
    378   DVLOG(1) << "Sending BROWSER_THEME_CHANGED";
    379   // Redraw!
    380   content::NotificationService* service =
    381       content::NotificationService::current();
    382   service->Notify(chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
    383                   content::Source<ThemeService>(this),
    384                   content::NotificationService::NoDetails());
    385 #if defined(OS_MACOSX)
    386   NotifyPlatformThemeChanged();
    387 #endif  // OS_MACOSX
    388 
    389   // Notify sync that theme has changed.
    390   if (theme_syncable_service_.get()) {
    391     theme_syncable_service_->OnThemeChange();
    392   }
    393 }
    394 
    395 #if defined(OS_WIN) || defined(USE_AURA)
    396 void ThemeService::FreePlatformCaches() {
    397   // Views (Skia) has no platform image cache to clear.
    398 }
    399 #endif
    400 
    401 void ThemeService::SwapThemeSupplier(
    402     scoped_refptr<CustomThemeSupplier> theme_supplier) {
    403   if (theme_supplier_.get())
    404     theme_supplier_->StopUsingTheme();
    405   theme_supplier_ = theme_supplier;
    406   if (theme_supplier_.get())
    407     theme_supplier_->StartUsingTheme();
    408 }
    409 
    410 void ThemeService::MigrateTheme() {
    411   ExtensionService* service =
    412       extensions::ExtensionSystem::Get(profile_)->extension_service();
    413   const Extension* extension = service ?
    414       service->GetExtensionById(GetThemeID(), false) : NULL;
    415   if (extension) {
    416     DLOG(ERROR) << "Migrating theme";
    417     BuildFromExtension(extension);
    418     content::RecordAction(UserMetricsAction("Themes.Migrated"));
    419   } else {
    420     DLOG(ERROR) << "Theme is mysteriously gone.";
    421     ClearAllThemeData();
    422     content::RecordAction(UserMetricsAction("Themes.Gone"));
    423   }
    424 }
    425 
    426 void ThemeService::SavePackName(const base::FilePath& pack_path) {
    427   profile_->GetPrefs()->SetFilePath(
    428       prefs::kCurrentThemePackFilename, pack_path);
    429 }
    430 
    431 void ThemeService::SaveThemeID(const std::string& id) {
    432   profile_->GetPrefs()->SetString(prefs::kCurrentThemeID, id);
    433 }
    434 
    435 void ThemeService::BuildFromExtension(const Extension* extension) {
    436   scoped_refptr<BrowserThemePack> pack(
    437       BrowserThemePack::BuildFromExtension(extension));
    438   if (!pack.get()) {
    439     // TODO(erg): We've failed to install the theme; perhaps we should tell the
    440     // user? http://crbug.com/34780
    441     LOG(ERROR) << "Could not load theme.";
    442     return;
    443   }
    444 
    445   ExtensionService* service =
    446       extensions::ExtensionSystem::Get(profile_)->extension_service();
    447   if (!service)
    448     return;
    449 
    450   // Write the packed file to disk.
    451   base::FilePath pack_path =
    452       extension->path().Append(chrome::kThemePackFilename);
    453   service->GetFileTaskRunner()->PostTask(
    454       FROM_HERE,
    455       base::Bind(&WritePackToDiskCallback, pack, pack_path));
    456 
    457   SavePackName(pack_path);
    458   SwapThemeSupplier(pack);
    459 }
    460 
    461 bool ThemeService::IsManagedUser() const {
    462   return profile_->IsManaged();
    463 }
    464 
    465 void ThemeService::SetManagedUserTheme() {
    466   SetCustomDefaultTheme(new ManagedUserTheme);
    467 }
    468 
    469 void ThemeService::OnManagedUserInitialized() {
    470   // Currently when creating a supervised user, the ThemeService is initialized
    471   // before the boolean flag indicating the profile belongs to a supervised
    472   // user gets set. In order to get the custom managed user theme, we get a
    473   // callback when ManagedUserService is initialized, which happens some time
    474   // after the boolean flag has been set in
    475   // ProfileManager::InitProfileUserPrefs() and after the
    476   // NOTIFICATION_EXTENSIONS_READY notification is sent.
    477   if ((theme_supplier_.get() &&
    478        (theme_supplier_->get_theme_type() == CustomThemeSupplier::EXTENSION ||
    479         theme_supplier_->get_theme_type() ==
    480             CustomThemeSupplier::MANAGED_USER_THEME)) ||
    481       !IsManagedUser()) {
    482     return;
    483   }
    484 
    485   SetManagedUserTheme();
    486 }
    487 
    488 void ThemeService::OnInfobarDisplayed() {
    489   number_of_infobars_++;
    490 }
    491 
    492 void ThemeService::OnInfobarDestroyed() {
    493   number_of_infobars_--;
    494 
    495   if (number_of_infobars_ == 0)
    496     RemoveUnusedThemes();
    497 }
    498 
    499 ThemeSyncableService* ThemeService::GetThemeSyncableService() const {
    500   return theme_syncable_service_.get();
    501 }
    502