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 <algorithm>
      8 
      9 #include "base/bind.h"
     10 #include "base/memory/ref_counted_memory.h"
     11 #include "base/message_loop/message_loop.h"
     12 #include "base/prefs/pref_service.h"
     13 #include "base/sequenced_task_runner.h"
     14 #include "base/strings/string_util.h"
     15 #include "base/strings/utf_string_conversions.h"
     16 #include "chrome/browser/chrome_notification_types.h"
     17 #include "chrome/browser/extensions/extension_service.h"
     18 #include "chrome/browser/profiles/profile.h"
     19 #include "chrome/browser/supervised_user/supervised_user_theme.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/pref_names.h"
     26 #include "content/public/browser/notification_service.h"
     27 #include "content/public/browser/user_metrics.h"
     28 #include "extensions/browser/extension_prefs.h"
     29 #include "extensions/browser/extension_registry.h"
     30 #include "extensions/browser/extension_system.h"
     31 #include "extensions/browser/uninstall_reason.h"
     32 #include "extensions/common/extension.h"
     33 #include "extensions/common/extension_set.h"
     34 #include "grit/theme_resources.h"
     35 #include "ui/base/layout.h"
     36 #include "ui/base/resource/resource_bundle.h"
     37 #include "ui/gfx/image/image_skia.h"
     38 
     39 #if defined(OS_WIN)
     40 #include "ui/base/win/shell.h"
     41 #endif
     42 
     43 using base::UserMetricsAction;
     44 using content::BrowserThread;
     45 using extensions::Extension;
     46 using extensions::UnloadedExtensionInfo;
     47 using ui::ResourceBundle;
     48 
     49 typedef ThemeProperties Properties;
     50 
     51 // The default theme if we haven't installed a theme yet or if we've clicked
     52 // the "Use Classic" button.
     53 const char* ThemeService::kDefaultThemeID = "";
     54 
     55 namespace {
     56 
     57 // The default theme if we've gone to the theme gallery and installed the
     58 // "Default" theme. We have to detect this case specifically. (By the time we
     59 // realize we've installed the default theme, we already have an extension
     60 // unpacked on the filesystem.)
     61 const char* kDefaultThemeGalleryID = "hkacjpbfdknhflllbcmjibkdeoafencn";
     62 
     63 // Wait this many seconds after startup to garbage collect unused themes.
     64 // Removing unused themes is done after a delay because there is no
     65 // reason to do it at startup.
     66 // ExtensionService::GarbageCollectExtensions() does something similar.
     67 const int kRemoveUnusedThemesStartupDelay = 30;
     68 
     69 SkColor IncreaseLightness(SkColor color, double percent) {
     70   color_utils::HSL result;
     71   color_utils::SkColorToHSL(color, &result);
     72   result.l += (1 - result.l) * percent;
     73   return color_utils::HSLToSkColor(result, SkColorGetA(color));
     74 }
     75 
     76 // Writes the theme pack to disk on a separate thread.
     77 void WritePackToDiskCallback(BrowserThemePack* pack,
     78                              const base::FilePath& path) {
     79   if (!pack->WriteToDisk(path))
     80     NOTREACHED() << "Could not write theme pack to disk";
     81 }
     82 
     83 // Heuristic to determine if color is grayscale. This is used to decide whether
     84 // to use the colorful or white logo, if a theme fails to specify which.
     85 bool IsColorGrayscale(SkColor color) {
     86   const int kChannelTolerance = 9;
     87   int r = SkColorGetR(color);
     88   int g = SkColorGetG(color);
     89   int b = SkColorGetB(color);
     90   int range = std::max(r, std::max(g, b)) - std::min(r, std::min(g, b));
     91   return range < kChannelTolerance;
     92 }
     93 
     94 }  // namespace
     95 
     96 ThemeService::ThemeService()
     97     : ready_(false),
     98       rb_(ResourceBundle::GetSharedInstance()),
     99       profile_(NULL),
    100       installed_pending_load_id_(kDefaultThemeID),
    101       number_of_infobars_(0),
    102       weak_ptr_factory_(this) {
    103 }
    104 
    105 ThemeService::~ThemeService() {
    106   FreePlatformCaches();
    107 }
    108 
    109 void ThemeService::Init(Profile* profile) {
    110   DCHECK(CalledOnValidThread());
    111   profile_ = profile;
    112 
    113   LoadThemePrefs();
    114 
    115   registrar_.Add(this,
    116                  extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED,
    117                  content::Source<Profile>(profile_));
    118 
    119   theme_syncable_service_.reset(new ThemeSyncableService(profile_, this));
    120 }
    121 
    122 gfx::Image ThemeService::GetImageNamed(int id) const {
    123   DCHECK(CalledOnValidThread());
    124 
    125   gfx::Image image;
    126   if (theme_supplier_.get())
    127     image = theme_supplier_->GetImageNamed(id);
    128 
    129   if (image.IsEmpty())
    130     image = rb_.GetNativeImageNamed(id);
    131 
    132   return image;
    133 }
    134 
    135 bool ThemeService::IsSystemThemeDistinctFromDefaultTheme() const {
    136   return false;
    137 }
    138 
    139 bool ThemeService::UsingSystemTheme() const {
    140   return UsingDefaultTheme();
    141 }
    142 
    143 gfx::ImageSkia* ThemeService::GetImageSkiaNamed(int id) const {
    144   gfx::Image image = GetImageNamed(id);
    145   if (image.IsEmpty())
    146     return NULL;
    147   // TODO(pkotwicz): Remove this const cast.  The gfx::Image interface returns
    148   // its images const. GetImageSkiaNamed() also should but has many callsites.
    149   return const_cast<gfx::ImageSkia*>(image.ToImageSkia());
    150 }
    151 
    152 SkColor ThemeService::GetColor(int id) const {
    153   DCHECK(CalledOnValidThread());
    154   SkColor color;
    155   if (theme_supplier_.get() && theme_supplier_->GetColor(id, &color))
    156     return color;
    157 
    158   // For backward compat with older themes, some newer colors are generated from
    159   // older ones if they are missing.
    160   switch (id) {
    161     case Properties::COLOR_NTP_SECTION_HEADER_TEXT:
    162       return IncreaseLightness(GetColor(Properties::COLOR_NTP_TEXT), 0.30);
    163     case Properties::COLOR_NTP_SECTION_HEADER_TEXT_HOVER:
    164       return GetColor(Properties::COLOR_NTP_TEXT);
    165     case Properties::COLOR_NTP_SECTION_HEADER_RULE:
    166       return IncreaseLightness(GetColor(Properties::COLOR_NTP_TEXT), 0.70);
    167     case Properties::COLOR_NTP_SECTION_HEADER_RULE_LIGHT:
    168       return IncreaseLightness(GetColor(Properties::COLOR_NTP_TEXT), 0.86);
    169     case Properties::COLOR_NTP_TEXT_LIGHT:
    170       return IncreaseLightness(GetColor(Properties::COLOR_NTP_TEXT), 0.40);
    171     case Properties::COLOR_SUPERVISED_USER_LABEL:
    172       return color_utils::GetReadableColor(
    173           SK_ColorWHITE,
    174           GetColor(Properties::COLOR_SUPERVISED_USER_LABEL_BACKGROUND));
    175     case Properties::COLOR_SUPERVISED_USER_LABEL_BACKGROUND:
    176       return color_utils::BlendTowardOppositeLuminance(
    177           GetColor(Properties::COLOR_FRAME), 0x80);
    178     case Properties::COLOR_SUPERVISED_USER_LABEL_BORDER:
    179       return color_utils::AlphaBlend(
    180           GetColor(Properties::COLOR_SUPERVISED_USER_LABEL_BACKGROUND),
    181           SK_ColorBLACK,
    182           230);
    183     case Properties::COLOR_STATUS_BAR_TEXT: {
    184       // A long time ago, we blended the toolbar and the tab text together to
    185       // get the status bar text because, at the time, our text rendering in
    186       // views couldn't do alpha blending. Even though this is no longer the
    187       // case, this blending decision is built into the majority of themes that
    188       // exist, and we must keep doing it.
    189       SkColor toolbar_color = GetColor(Properties::COLOR_TOOLBAR);
    190       SkColor text_color = GetColor(Properties::COLOR_TAB_TEXT);
    191       return SkColorSetARGB(
    192           SkColorGetA(text_color),
    193           (SkColorGetR(text_color) + SkColorGetR(toolbar_color)) / 2,
    194           (SkColorGetG(text_color) + SkColorGetR(toolbar_color)) / 2,
    195           (SkColorGetB(text_color) + SkColorGetR(toolbar_color)) / 2);
    196     }
    197   }
    198 
    199   return Properties::GetDefaultColor(id);
    200 }
    201 
    202 int ThemeService::GetDisplayProperty(int id) const {
    203   int result = 0;
    204   if (theme_supplier_.get() &&
    205       theme_supplier_->GetDisplayProperty(id, &result)) {
    206     return result;
    207   }
    208 
    209   if (id == Properties::NTP_LOGO_ALTERNATE) {
    210     if (UsingDefaultTheme() || UsingSystemTheme())
    211       return 0;  // Colorful logo.
    212 
    213     if (HasCustomImage(IDR_THEME_NTP_BACKGROUND))
    214       return 1;  // White logo.
    215 
    216     SkColor background_color = GetColor(Properties::COLOR_NTP_BACKGROUND);
    217     return IsColorGrayscale(background_color) ? 0 : 1;
    218   }
    219 
    220   return Properties::GetDefaultDisplayProperty(id);
    221 }
    222 
    223 bool ThemeService::ShouldUseNativeFrame() const {
    224   if (HasCustomImage(IDR_THEME_FRAME))
    225     return false;
    226 #if defined(OS_WIN)
    227   return ui::win::IsAeroGlassEnabled();
    228 #else
    229   return false;
    230 #endif
    231 }
    232 
    233 bool ThemeService::HasCustomImage(int id) const {
    234   if (!Properties::IsThemeableImage(id))
    235     return false;
    236 
    237   if (theme_supplier_.get())
    238     return theme_supplier_->HasCustomImage(id);
    239 
    240   return false;
    241 }
    242 
    243 base::RefCountedMemory* ThemeService::GetRawData(
    244     int id,
    245     ui::ScaleFactor scale_factor) const {
    246   // Check to see whether we should substitute some images.
    247   int ntp_alternate = GetDisplayProperty(Properties::NTP_LOGO_ALTERNATE);
    248   if (id == IDR_PRODUCT_LOGO && ntp_alternate != 0)
    249     id = IDR_PRODUCT_LOGO_WHITE;
    250 
    251   base::RefCountedMemory* data = NULL;
    252   if (theme_supplier_.get())
    253     data = theme_supplier_->GetRawData(id, scale_factor);
    254   if (!data)
    255     data = rb_.LoadDataResourceBytesForScale(id, ui::SCALE_FACTOR_100P);
    256 
    257   return data;
    258 }
    259 
    260 void ThemeService::Observe(int type,
    261                            const content::NotificationSource& source,
    262                            const content::NotificationDetails& details) {
    263   using content::Details;
    264   switch (type) {
    265     case extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED:
    266       registrar_.Remove(this,
    267                         extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED,
    268                         content::Source<Profile>(profile_));
    269       OnExtensionServiceReady();
    270       break;
    271     case extensions::NOTIFICATION_EXTENSION_WILL_BE_INSTALLED_DEPRECATED: {
    272       // The theme may be initially disabled. Wait till it is loaded (if ever).
    273       Details<const extensions::InstalledExtensionInfo> installed_details(
    274           details);
    275       if (installed_details->extension->is_theme())
    276         installed_pending_load_id_ = installed_details->extension->id();
    277       break;
    278     }
    279     case extensions::NOTIFICATION_EXTENSION_LOADED_DEPRECATED: {
    280       const Extension* extension = Details<const Extension>(details).ptr();
    281       if (extension->is_theme() &&
    282           installed_pending_load_id_ != kDefaultThemeID &&
    283           installed_pending_load_id_ == extension->id()) {
    284         SetTheme(extension);
    285       }
    286       installed_pending_load_id_ = kDefaultThemeID;
    287       break;
    288     }
    289     case extensions::NOTIFICATION_EXTENSION_ENABLED: {
    290       const Extension* extension = Details<const Extension>(details).ptr();
    291       if (extension->is_theme())
    292         SetTheme(extension);
    293       break;
    294     }
    295     case extensions::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED: {
    296       Details<const UnloadedExtensionInfo> unloaded_details(details);
    297       if (unloaded_details->reason != UnloadedExtensionInfo::REASON_UPDATE &&
    298           unloaded_details->extension->is_theme() &&
    299           unloaded_details->extension->id() == GetThemeID()) {
    300         UseDefaultTheme();
    301       }
    302       break;
    303     }
    304   }
    305 }
    306 
    307 void ThemeService::SetTheme(const Extension* extension) {
    308   DCHECK(extension->is_theme());
    309   ExtensionService* service =
    310       extensions::ExtensionSystem::Get(profile_)->extension_service();
    311   if (!service->IsExtensionEnabled(extension->id())) {
    312     // |extension| is disabled when reverting to the previous theme via an
    313     // infobar.
    314     service->EnableExtension(extension->id());
    315     // Enabling the extension will call back to SetTheme().
    316     return;
    317   }
    318 
    319   std::string previous_theme_id = GetThemeID();
    320 
    321   // Clear our image cache.
    322   FreePlatformCaches();
    323 
    324   BuildFromExtension(extension);
    325   SaveThemeID(extension->id());
    326 
    327   NotifyThemeChanged();
    328   content::RecordAction(UserMetricsAction("Themes_Installed"));
    329 
    330   if (previous_theme_id != kDefaultThemeID &&
    331       previous_theme_id != extension->id()) {
    332     // Disable the old theme.
    333     service->DisableExtension(previous_theme_id,
    334                               extensions::Extension::DISABLE_USER_ACTION);
    335   }
    336 }
    337 
    338 void ThemeService::SetCustomDefaultTheme(
    339     scoped_refptr<CustomThemeSupplier> theme_supplier) {
    340   ClearAllThemeData();
    341   SwapThemeSupplier(theme_supplier);
    342   NotifyThemeChanged();
    343 }
    344 
    345 bool ThemeService::ShouldInitWithSystemTheme() const {
    346   return false;
    347 }
    348 
    349 void ThemeService::RemoveUnusedThemes(bool ignore_infobars) {
    350   // We do not want to garbage collect themes on startup (|ready_| is false).
    351   // Themes will get garbage collected after |kRemoveUnusedThemesStartupDelay|.
    352   if (!profile_ || !ready_)
    353     return;
    354   if (!ignore_infobars && number_of_infobars_ != 0)
    355     return;
    356 
    357   ExtensionService* service =
    358       extensions::ExtensionSystem::Get(profile_)->extension_service();
    359   if (!service)
    360     return;
    361 
    362   std::string current_theme = GetThemeID();
    363   std::vector<std::string> remove_list;
    364   scoped_ptr<const extensions::ExtensionSet> extensions(
    365       extensions::ExtensionRegistry::Get(profile_)
    366           ->GenerateInstalledExtensionsSet());
    367   extensions::ExtensionPrefs* prefs = extensions::ExtensionPrefs::Get(profile_);
    368   for (extensions::ExtensionSet::const_iterator it = extensions->begin();
    369        it != extensions->end(); ++it) {
    370     const extensions::Extension* extension = it->get();
    371     if (extension->is_theme() &&
    372         extension->id() != current_theme) {
    373       // Only uninstall themes which are not disabled or are disabled with
    374       // reason DISABLE_USER_ACTION. We cannot blanket uninstall all disabled
    375       // themes because externally installed themes are initially disabled.
    376       int disable_reason = prefs->GetDisableReasons(extension->id());
    377       if (!prefs->IsExtensionDisabled(extension->id()) ||
    378           disable_reason == Extension::DISABLE_USER_ACTION) {
    379         remove_list.push_back((*it)->id());
    380       }
    381     }
    382   }
    383   // TODO: Garbage collect all unused themes. This method misses themes which
    384   // are installed but not loaded because they are blacklisted by a management
    385   // policy provider.
    386 
    387   for (size_t i = 0; i < remove_list.size(); ++i) {
    388     service->UninstallExtension(remove_list[i],
    389                                 extensions::UNINSTALL_REASON_ORPHANED_THEME,
    390                                 base::Bind(&base::DoNothing),
    391                                 NULL);
    392   }
    393 }
    394 
    395 void ThemeService::UseDefaultTheme() {
    396   if (ready_)
    397     content::RecordAction(UserMetricsAction("Themes_Reset"));
    398   if (IsSupervisedUser()) {
    399     SetSupervisedUserTheme();
    400     return;
    401   }
    402   ClearAllThemeData();
    403   NotifyThemeChanged();
    404 }
    405 
    406 void ThemeService::UseSystemTheme() {
    407   UseDefaultTheme();
    408 }
    409 
    410 bool ThemeService::UsingDefaultTheme() const {
    411   std::string id = GetThemeID();
    412   return id == ThemeService::kDefaultThemeID ||
    413       id == kDefaultThemeGalleryID;
    414 }
    415 
    416 std::string ThemeService::GetThemeID() const {
    417   return profile_->GetPrefs()->GetString(prefs::kCurrentThemeID);
    418 }
    419 
    420 color_utils::HSL ThemeService::GetTint(int id) const {
    421   DCHECK(CalledOnValidThread());
    422 
    423   color_utils::HSL hsl;
    424   if (theme_supplier_.get() && theme_supplier_->GetTint(id, &hsl))
    425     return hsl;
    426 
    427   return ThemeProperties::GetDefaultTint(id);
    428 }
    429 
    430 void ThemeService::ClearAllThemeData() {
    431   if (!ready_)
    432     return;
    433 
    434   SwapThemeSupplier(NULL);
    435 
    436   // Clear our image cache.
    437   FreePlatformCaches();
    438 
    439   profile_->GetPrefs()->ClearPref(prefs::kCurrentThemePackFilename);
    440   SaveThemeID(kDefaultThemeID);
    441 
    442   // There should be no more infobars. This may not be the case because of
    443   // http://crbug.com/62154
    444   // RemoveUnusedThemes is called on a task because ClearAllThemeData() may
    445   // be called as a result of NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED.
    446   base::MessageLoop::current()->PostTask(FROM_HERE,
    447       base::Bind(&ThemeService::RemoveUnusedThemes,
    448                  weak_ptr_factory_.GetWeakPtr(),
    449                  true));
    450 }
    451 
    452 void ThemeService::LoadThemePrefs() {
    453   PrefService* prefs = profile_->GetPrefs();
    454 
    455   std::string current_id = GetThemeID();
    456   if (current_id == kDefaultThemeID) {
    457     // Supervised users have a different default theme.
    458     if (IsSupervisedUser())
    459       SetSupervisedUserTheme();
    460     else if (ShouldInitWithSystemTheme())
    461       UseSystemTheme();
    462     else
    463       UseDefaultTheme();
    464     set_ready();
    465     return;
    466   }
    467 
    468   bool loaded_pack = false;
    469 
    470   // If we don't have a file pack, we're updating from an old version.
    471   base::FilePath path = prefs->GetFilePath(prefs::kCurrentThemePackFilename);
    472   if (path != base::FilePath()) {
    473     SwapThemeSupplier(BrowserThemePack::BuildFromDataPack(path, current_id));
    474     loaded_pack = theme_supplier_.get() != NULL;
    475   }
    476 
    477   if (loaded_pack) {
    478     content::RecordAction(UserMetricsAction("Themes.Loaded"));
    479     set_ready();
    480   }
    481   // Else: wait for the extension service to be ready so that the theme pack
    482   // can be recreated from the extension.
    483 }
    484 
    485 void ThemeService::NotifyThemeChanged() {
    486   if (!ready_)
    487     return;
    488 
    489   DVLOG(1) << "Sending BROWSER_THEME_CHANGED";
    490   // Redraw!
    491   content::NotificationService* service =
    492       content::NotificationService::current();
    493   service->Notify(chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
    494                   content::Source<ThemeService>(this),
    495                   content::NotificationService::NoDetails());
    496 #if defined(OS_MACOSX)
    497   NotifyPlatformThemeChanged();
    498 #endif  // OS_MACOSX
    499 
    500   // Notify sync that theme has changed.
    501   if (theme_syncable_service_.get()) {
    502     theme_syncable_service_->OnThemeChange();
    503   }
    504 }
    505 
    506 #if defined(USE_AURA)
    507 void ThemeService::FreePlatformCaches() {
    508   // Views (Skia) has no platform image cache to clear.
    509 }
    510 #endif
    511 
    512 void ThemeService::OnExtensionServiceReady() {
    513   if (!ready_) {
    514     // If the ThemeService is not ready yet, the custom theme data pack needs to
    515     // be recreated from the extension.
    516     MigrateTheme();
    517     set_ready();
    518 
    519     // Send notification in case anyone requested data and cached it when the
    520     // theme service was not ready yet.
    521     NotifyThemeChanged();
    522   }
    523 
    524   registrar_.Add(
    525       this,
    526       extensions::NOTIFICATION_EXTENSION_WILL_BE_INSTALLED_DEPRECATED,
    527       content::Source<Profile>(profile_));
    528   registrar_.Add(this,
    529                  extensions::NOTIFICATION_EXTENSION_LOADED_DEPRECATED,
    530                  content::Source<Profile>(profile_));
    531   registrar_.Add(this,
    532                  extensions::NOTIFICATION_EXTENSION_ENABLED,
    533                  content::Source<Profile>(profile_));
    534   registrar_.Add(this,
    535                  extensions::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED,
    536                  content::Source<Profile>(profile_));
    537 
    538   base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
    539       base::Bind(&ThemeService::RemoveUnusedThemes,
    540                  weak_ptr_factory_.GetWeakPtr(),
    541                  false),
    542       base::TimeDelta::FromSeconds(kRemoveUnusedThemesStartupDelay));
    543 }
    544 
    545 void ThemeService::MigrateTheme() {
    546   // TODO(erg): We need to pop up a dialog informing the user that their
    547   // theme is being migrated.
    548   ExtensionService* service =
    549       extensions::ExtensionSystem::Get(profile_)->extension_service();
    550   const Extension* extension = service ?
    551       service->GetExtensionById(GetThemeID(), false) : NULL;
    552   if (extension) {
    553     DLOG(ERROR) << "Migrating theme";
    554     BuildFromExtension(extension);
    555     content::RecordAction(UserMetricsAction("Themes.Migrated"));
    556   } else {
    557     DLOG(ERROR) << "Theme is mysteriously gone.";
    558     ClearAllThemeData();
    559     content::RecordAction(UserMetricsAction("Themes.Gone"));
    560   }
    561 }
    562 
    563 void ThemeService::SwapThemeSupplier(
    564     scoped_refptr<CustomThemeSupplier> theme_supplier) {
    565   if (theme_supplier_.get())
    566     theme_supplier_->StopUsingTheme();
    567   theme_supplier_ = theme_supplier;
    568   if (theme_supplier_.get())
    569     theme_supplier_->StartUsingTheme();
    570 }
    571 
    572 void ThemeService::SavePackName(const base::FilePath& pack_path) {
    573   profile_->GetPrefs()->SetFilePath(
    574       prefs::kCurrentThemePackFilename, pack_path);
    575 }
    576 
    577 void ThemeService::SaveThemeID(const std::string& id) {
    578   profile_->GetPrefs()->SetString(prefs::kCurrentThemeID, id);
    579 }
    580 
    581 void ThemeService::BuildFromExtension(const Extension* extension) {
    582   scoped_refptr<BrowserThemePack> pack(
    583       BrowserThemePack::BuildFromExtension(extension));
    584   if (!pack.get()) {
    585     // TODO(erg): We've failed to install the theme; perhaps we should tell the
    586     // user? http://crbug.com/34780
    587     LOG(ERROR) << "Could not load theme.";
    588     return;
    589   }
    590 
    591   ExtensionService* service =
    592       extensions::ExtensionSystem::Get(profile_)->extension_service();
    593   if (!service)
    594     return;
    595 
    596   // Write the packed file to disk.
    597   base::FilePath pack_path =
    598       extension->path().Append(chrome::kThemePackFilename);
    599   service->GetFileTaskRunner()->PostTask(
    600       FROM_HERE,
    601       base::Bind(&WritePackToDiskCallback, pack, pack_path));
    602 
    603   SavePackName(pack_path);
    604   SwapThemeSupplier(pack);
    605 }
    606 
    607 bool ThemeService::IsSupervisedUser() const {
    608   return profile_->IsSupervised();
    609 }
    610 
    611 void ThemeService::SetSupervisedUserTheme() {
    612   SetCustomDefaultTheme(new SupervisedUserTheme);
    613 }
    614 
    615 void ThemeService::OnInfobarDisplayed() {
    616   number_of_infobars_++;
    617 }
    618 
    619 void ThemeService::OnInfobarDestroyed() {
    620   number_of_infobars_--;
    621 
    622   if (number_of_infobars_ == 0)
    623     RemoveUnusedThemes(false);
    624 }
    625 
    626 ThemeSyncableService* ThemeService::GetThemeSyncableService() const {
    627   return theme_syncable_service_.get();
    628 }
    629