• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
IncreaseLightness(SkColor color,double percent)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.
WritePackToDiskCallback(BrowserThemePack * pack,const base::FilePath & path)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 
ThemeService()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 
~ThemeService()92 ThemeService::~ThemeService() {
93   FreePlatformCaches();
94 }
95 
Init(Profile * profile)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 
GetImageNamed(int id) const109 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 
UsingSystemTheme() const122 bool ThemeService::UsingSystemTheme() const {
123   return UsingDefaultTheme();
124 }
125 
GetImageSkiaNamed(int id) const126 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 
GetColor(int id) const135 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 
GetDisplayProperty(int id) const185 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 
ShouldUseNativeFrame() const203 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 
HasCustomImage(int id) const213 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 
GetRawData(int id,ui::ScaleFactor scale_factor) const223 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 
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)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 
SetTheme(const Extension * extension)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 
SetCustomDefaultTheme(scoped_refptr<CustomThemeSupplier> theme_supplier)320 void ThemeService::SetCustomDefaultTheme(
321     scoped_refptr<CustomThemeSupplier> theme_supplier) {
322   ClearAllThemeData();
323   SwapThemeSupplier(theme_supplier);
324   NotifyThemeChanged();
325 }
326 
ShouldInitWithSystemTheme() const327 bool ThemeService::ShouldInitWithSystemTheme() const {
328   return false;
329 }
330 
RemoveUnusedThemes(bool ignore_infobars)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 
UseDefaultTheme()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 
UseSystemTheme()384 void ThemeService::UseSystemTheme() {
385   UseDefaultTheme();
386 }
387 
UsingDefaultTheme() const388 bool ThemeService::UsingDefaultTheme() const {
389   std::string id = GetThemeID();
390   return id == ThemeService::kDefaultThemeID ||
391       id == kDefaultThemeGalleryID;
392 }
393 
GetThemeID() const394 std::string ThemeService::GetThemeID() const {
395   return profile_->GetPrefs()->GetString(prefs::kCurrentThemeID);
396 }
397 
GetTint(int id) const398 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 
ClearAllThemeData()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 
LoadThemePrefs()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 
NotifyThemeChanged()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)
FreePlatformCaches()485 void ThemeService::FreePlatformCaches() {
486   // Views (Skia) has no platform image cache to clear.
487 }
488 #endif
489 
OnExtensionServiceReady()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 
MigrateTheme()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 
SwapThemeSupplier(scoped_refptr<CustomThemeSupplier> theme_supplier)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 
SavePackName(const base::FilePath & pack_path)549 void ThemeService::SavePackName(const base::FilePath& pack_path) {
550   profile_->GetPrefs()->SetFilePath(
551       prefs::kCurrentThemePackFilename, pack_path);
552 }
553 
SaveThemeID(const std::string & id)554 void ThemeService::SaveThemeID(const std::string& id) {
555   profile_->GetPrefs()->SetString(prefs::kCurrentThemeID, id);
556 }
557 
BuildFromExtension(const Extension * extension)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 
IsSupervisedUser() const584 bool ThemeService::IsSupervisedUser() const {
585   return profile_->IsSupervised();
586 }
587 
SetSupervisedUserTheme()588 void ThemeService::SetSupervisedUserTheme() {
589   SetCustomDefaultTheme(new SupervisedUserTheme);
590 }
591 
OnInfobarDisplayed()592 void ThemeService::OnInfobarDisplayed() {
593   number_of_infobars_++;
594 }
595 
OnInfobarDestroyed()596 void ThemeService::OnInfobarDestroyed() {
597   number_of_infobars_--;
598 
599   if (number_of_infobars_ == 0)
600     RemoveUnusedThemes(false);
601 }
602 
GetThemeSyncableService() const603 ThemeSyncableService* ThemeService::GetThemeSyncableService() const {
604   return theme_syncable_service_.get();
605 }
606