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