Rename NOTIFICATION_EXTENSION_INSTALLED to NOTIFICATION_EXTENSION_INSTALLED_DEPRECATED
[chromium-blink-merge.git] / chrome / browser / themes / theme_service.cc
blob3b22ccb149447c0ad46ada91a343a425d5b84bf3
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.
5 #include "chrome/browser/themes/theme_service.h"
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/managed_mode/managed_user_theme.h"
17 #include "chrome/browser/profiles/profile.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"
37 #if defined(OS_WIN)
38 #include "ui/base/win/shell.h"
39 #endif
41 using base::UserMetricsAction;
42 using content::BrowserThread;
43 using extensions::Extension;
44 using extensions::UnloadedExtensionInfo;
45 using ui::ResourceBundle;
47 typedef ThemeProperties Properties;
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 = "";
53 namespace {
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";
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;
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));
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";
81 } // namespace
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) {
92 ThemeService::~ThemeService() {
93 FreePlatformCaches();
96 void ThemeService::Init(Profile* profile) {
97 DCHECK(CalledOnValidThread());
98 profile_ = profile;
100 LoadThemePrefs();
102 registrar_.Add(this,
103 chrome::NOTIFICATION_EXTENSIONS_READY,
104 content::Source<Profile>(profile_));
106 theme_syncable_service_.reset(new ThemeSyncableService(profile_, this));
109 gfx::Image ThemeService::GetImageNamed(int id) const {
110 DCHECK(CalledOnValidThread());
112 gfx::Image image;
113 if (theme_supplier_.get())
114 image = theme_supplier_->GetImageNamed(id);
116 if (image.IsEmpty())
117 image = rb_.GetNativeImageNamed(id);
119 return image;
122 bool ThemeService::UsingSystemTheme() const {
123 return UsingDefaultTheme();
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());
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;
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_MANAGED_USER_LABEL:
155 return color_utils::GetReadableColor(
156 SK_ColorWHITE,
157 GetColor(Properties::COLOR_MANAGED_USER_LABEL_BACKGROUND));
158 case Properties::COLOR_MANAGED_USER_LABEL_BACKGROUND:
159 return color_utils::BlendTowardOppositeLuminance(
160 GetColor(Properties::COLOR_FRAME), 0x80);
161 case Properties::COLOR_MANAGED_USER_LABEL_BORDER:
162 return color_utils::AlphaBlend(
163 GetColor(Properties::COLOR_MANAGED_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);
182 return Properties::GetDefaultColor(id);
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;
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;
200 return Properties::GetDefaultDisplayProperty(id);
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
213 bool ThemeService::HasCustomImage(int id) const {
214 if (!Properties::IsThemeableImage(id))
215 return false;
217 if (theme_supplier_.get())
218 return theme_supplier_->HasCustomImage(id);
220 return false;
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;
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);
237 return data;
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;
258 case chrome::NOTIFICATION_EXTENSION_LOADED_DEPRECATED:
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);
266 installed_pending_load_id_ = kDefaultThemeID;
267 break;
269 case chrome::NOTIFICATION_EXTENSION_ENABLED:
271 const Extension* extension = Details<const Extension>(details).ptr();
272 if (extension->is_theme())
273 SetTheme(extension);
274 break;
276 case chrome::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED:
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();
284 break;
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;
301 std::string previous_theme_id = GetThemeID();
303 // Clear our image cache.
304 FreePlatformCaches();
306 BuildFromExtension(extension);
307 SaveThemeID(extension->id());
309 NotifyThemeChanged();
310 content::RecordAction(UserMetricsAction("Themes_Installed"));
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);
320 void ThemeService::SetCustomDefaultTheme(
321 scoped_refptr<CustomThemeSupplier> theme_supplier) {
322 ClearAllThemeData();
323 SwapThemeSupplier(theme_supplier);
324 NotifyThemeChanged();
327 bool ThemeService::ShouldInitWithSystemTheme() const {
328 return false;
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;
339 ExtensionService* service =
340 extensions::ExtensionSystem::Get(profile_)->extension_service();
341 if (!service)
342 return;
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());
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.
369 for (size_t i = 0; i < remove_list.size(); ++i)
370 service->UninstallExtension(remove_list[i], false, NULL);
373 void ThemeService::UseDefaultTheme() {
374 if (ready_)
375 content::RecordAction(UserMetricsAction("Themes_Reset"));
376 if (IsManagedUser()) {
377 SetManagedUserTheme();
378 return;
380 ClearAllThemeData();
381 NotifyThemeChanged();
384 void ThemeService::UseSystemTheme() {
385 UseDefaultTheme();
388 bool ThemeService::UsingDefaultTheme() const {
389 std::string id = GetThemeID();
390 return id == ThemeService::kDefaultThemeID ||
391 id == kDefaultThemeGalleryID;
394 std::string ThemeService::GetThemeID() const {
395 return profile_->GetPrefs()->GetString(prefs::kCurrentThemeID);
398 color_utils::HSL ThemeService::GetTint(int id) const {
399 DCHECK(CalledOnValidThread());
401 color_utils::HSL hsl;
402 if (theme_supplier_.get() && theme_supplier_->GetTint(id, &hsl))
403 return hsl;
405 return ThemeProperties::GetDefaultTint(id);
408 void ThemeService::ClearAllThemeData() {
409 if (!ready_)
410 return;
412 SwapThemeSupplier(NULL);
414 // Clear our image cache.
415 FreePlatformCaches();
417 profile_->GetPrefs()->ClearPref(prefs::kCurrentThemePackFilename);
418 SaveThemeID(kDefaultThemeID);
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));
430 void ThemeService::LoadThemePrefs() {
431 PrefService* prefs = profile_->GetPrefs();
433 std::string current_id = GetThemeID();
434 if (current_id == kDefaultThemeID) {
435 // Managed users have a different default theme.
436 if (IsManagedUser())
437 SetManagedUserTheme();
438 else if (ShouldInitWithSystemTheme())
439 UseSystemTheme();
440 else
441 UseDefaultTheme();
442 set_ready();
443 return;
446 bool loaded_pack = false;
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;
455 if (loaded_pack) {
456 content::RecordAction(UserMetricsAction("Themes.Loaded"));
457 set_ready();
459 // Else: wait for the extension service to be ready so that the theme pack
460 // can be recreated from the extension.
463 void ThemeService::NotifyThemeChanged() {
464 if (!ready_)
465 return;
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
478 // Notify sync that theme has changed.
479 if (theme_syncable_service_.get()) {
480 theme_syncable_service_->OnThemeChange();
484 #if defined(USE_AURA)
485 void ThemeService::FreePlatformCaches() {
486 // Views (Skia) has no platform image cache to clear.
488 #endif
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();
497 // Send notification in case anyone requested data and cached it when the
498 // theme service was not ready yet.
499 NotifyThemeChanged();
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_));
515 base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
516 base::Bind(&ThemeService::RemoveUnusedThemes,
517 weak_ptr_factory_.GetWeakPtr(),
518 false),
519 base::TimeDelta::FromSeconds(kRemoveUnusedThemesStartupDelay));
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"));
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();
549 void ThemeService::SavePackName(const base::FilePath& pack_path) {
550 profile_->GetPrefs()->SetFilePath(
551 prefs::kCurrentThemePackFilename, pack_path);
554 void ThemeService::SaveThemeID(const std::string& id) {
555 profile_->GetPrefs()->SetString(prefs::kCurrentThemeID, id);
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;
568 ExtensionService* service =
569 extensions::ExtensionSystem::Get(profile_)->extension_service();
570 if (!service)
571 return;
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));
580 SavePackName(pack_path);
581 SwapThemeSupplier(pack);
584 bool ThemeService::IsManagedUser() const {
585 return profile_->IsManaged();
588 void ThemeService::SetManagedUserTheme() {
589 SetCustomDefaultTheme(new ManagedUserTheme);
592 void ThemeService::OnInfobarDisplayed() {
593 number_of_infobars_++;
596 void ThemeService::OnInfobarDestroyed() {
597 number_of_infobars_--;
599 if (number_of_infobars_ == 0)
600 RemoveUnusedThemes(false);
603 ThemeSyncableService* ThemeService::GetThemeSyncableService() const {
604 return theme_syncable_service_.get();