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