Cast: Stop logging kVideoFrameSentToEncoder and rename a couple events.
[chromium-blink-merge.git] / chrome / browser / themes / theme_service.cc
blob9e3eb987c2fd5df230c2957732d367a6a73e9b27
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::UsingNativeTheme() 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 !UsingNativeTheme()) {
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:
252 // The theme may be initially disabled. Wait till it is loaded (if ever).
253 Details<const extensions::InstalledExtensionInfo> installed_details(
254 details);
255 if (installed_details->extension->is_theme())
256 installed_pending_load_id_ = installed_details->extension->id();
257 break;
259 case chrome::NOTIFICATION_EXTENSION_LOADED_DEPRECATED:
261 const Extension* extension = Details<const Extension>(details).ptr();
262 if (extension->is_theme() &&
263 installed_pending_load_id_ != kDefaultThemeID &&
264 installed_pending_load_id_ == extension->id()) {
265 SetTheme(extension);
267 installed_pending_load_id_ = kDefaultThemeID;
268 break;
270 case chrome::NOTIFICATION_EXTENSION_ENABLED:
272 const Extension* extension = Details<const Extension>(details).ptr();
273 if (extension->is_theme())
274 SetTheme(extension);
275 break;
277 case chrome::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED:
279 Details<const UnloadedExtensionInfo> unloaded_details(details);
280 if (unloaded_details->reason != UnloadedExtensionInfo::REASON_UPDATE &&
281 unloaded_details->extension->is_theme() &&
282 unloaded_details->extension->id() == GetThemeID()) {
283 UseDefaultTheme();
285 break;
290 void ThemeService::SetTheme(const Extension* extension) {
291 DCHECK(extension->is_theme());
292 ExtensionService* service =
293 extensions::ExtensionSystem::Get(profile_)->extension_service();
294 if (!service->IsExtensionEnabled(extension->id())) {
295 // |extension| is disabled when reverting to the previous theme via an
296 // infobar.
297 service->EnableExtension(extension->id());
298 // Enabling the extension will call back to SetTheme().
299 return;
302 std::string previous_theme_id = GetThemeID();
304 // Clear our image cache.
305 FreePlatformCaches();
307 BuildFromExtension(extension);
308 SaveThemeID(extension->id());
310 NotifyThemeChanged();
311 content::RecordAction(UserMetricsAction("Themes_Installed"));
313 if (previous_theme_id != kDefaultThemeID &&
314 previous_theme_id != extension->id()) {
315 // Disable the old theme.
316 service->DisableExtension(previous_theme_id,
317 extensions::Extension::DISABLE_USER_ACTION);
321 void ThemeService::SetCustomDefaultTheme(
322 scoped_refptr<CustomThemeSupplier> theme_supplier) {
323 ClearAllThemeData();
324 SwapThemeSupplier(theme_supplier);
325 NotifyThemeChanged();
328 bool ThemeService::ShouldInitWithNativeTheme() const {
329 return false;
332 void ThemeService::RemoveUnusedThemes(bool ignore_infobars) {
333 // We do not want to garbage collect themes on startup (|ready_| is false).
334 // Themes will get garbage collected after |kRemoveUnusedThemesStartupDelay|.
335 if (!profile_ || !ready_)
336 return;
337 if (!ignore_infobars && number_of_infobars_ != 0)
338 return;
340 ExtensionService* service =
341 extensions::ExtensionSystem::Get(profile_)->extension_service();
342 if (!service)
343 return;
345 std::string current_theme = GetThemeID();
346 std::vector<std::string> remove_list;
347 scoped_ptr<const extensions::ExtensionSet> extensions(
348 extensions::ExtensionRegistry::Get(profile_)
349 ->GenerateInstalledExtensionsSet());
350 extensions::ExtensionPrefs* prefs = extensions::ExtensionPrefs::Get(profile_);
351 for (extensions::ExtensionSet::const_iterator it = extensions->begin();
352 it != extensions->end(); ++it) {
353 const extensions::Extension* extension = *it;
354 if (extension->is_theme() &&
355 extension->id() != current_theme) {
356 // Only uninstall themes which are not disabled or are disabled with
357 // reason DISABLE_USER_ACTION. We cannot blanket uninstall all disabled
358 // themes because externally installed themes are initially disabled.
359 int disable_reason = prefs->GetDisableReasons(extension->id());
360 if (!prefs->IsExtensionDisabled(extension->id()) ||
361 disable_reason == Extension::DISABLE_USER_ACTION) {
362 remove_list.push_back((*it)->id());
366 // TODO: Garbage collect all unused themes. This method misses themes which
367 // are installed but not loaded because they are blacklisted by a management
368 // policy provider.
370 for (size_t i = 0; i < remove_list.size(); ++i)
371 service->UninstallExtension(remove_list[i], false, NULL);
374 void ThemeService::UseDefaultTheme() {
375 if (ready_)
376 content::RecordAction(UserMetricsAction("Themes_Reset"));
377 if (IsManagedUser()) {
378 SetManagedUserTheme();
379 return;
381 ClearAllThemeData();
382 NotifyThemeChanged();
385 void ThemeService::SetNativeTheme() {
386 UseDefaultTheme();
389 bool ThemeService::UsingDefaultTheme() const {
390 std::string id = GetThemeID();
391 return id == ThemeService::kDefaultThemeID ||
392 id == kDefaultThemeGalleryID;
395 std::string ThemeService::GetThemeID() const {
396 return profile_->GetPrefs()->GetString(prefs::kCurrentThemeID);
399 color_utils::HSL ThemeService::GetTint(int id) const {
400 DCHECK(CalledOnValidThread());
402 color_utils::HSL hsl;
403 if (theme_supplier_.get() && theme_supplier_->GetTint(id, &hsl))
404 return hsl;
406 return ThemeProperties::GetDefaultTint(id);
409 void ThemeService::ClearAllThemeData() {
410 if (!ready_)
411 return;
413 SwapThemeSupplier(NULL);
415 // Clear our image cache.
416 FreePlatformCaches();
418 profile_->GetPrefs()->ClearPref(prefs::kCurrentThemePackFilename);
419 SaveThemeID(kDefaultThemeID);
421 // There should be no more infobars. This may not be the case because of
422 // http://crbug.com/62154
423 // RemoveUnusedThemes is called on a task because ClearAllThemeData() may
424 // be called as a result of NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED.
425 base::MessageLoop::current()->PostTask(FROM_HERE,
426 base::Bind(&ThemeService::RemoveUnusedThemes,
427 weak_ptr_factory_.GetWeakPtr(),
428 true));
431 void ThemeService::LoadThemePrefs() {
432 PrefService* prefs = profile_->GetPrefs();
434 std::string current_id = GetThemeID();
435 if (current_id == kDefaultThemeID) {
436 // Managed users have a different default theme.
437 if (IsManagedUser())
438 SetManagedUserTheme();
439 else if (ShouldInitWithNativeTheme())
440 SetNativeTheme();
441 else
442 UseDefaultTheme();
443 set_ready();
444 return;
447 bool loaded_pack = false;
449 // If we don't have a file pack, we're updating from an old version.
450 base::FilePath path = prefs->GetFilePath(prefs::kCurrentThemePackFilename);
451 if (path != base::FilePath()) {
452 SwapThemeSupplier(BrowserThemePack::BuildFromDataPack(path, current_id));
453 loaded_pack = theme_supplier_.get() != NULL;
456 if (loaded_pack) {
457 content::RecordAction(UserMetricsAction("Themes.Loaded"));
458 set_ready();
460 // Else: wait for the extension service to be ready so that the theme pack
461 // can be recreated from the extension.
464 void ThemeService::NotifyThemeChanged() {
465 if (!ready_)
466 return;
468 DVLOG(1) << "Sending BROWSER_THEME_CHANGED";
469 // Redraw!
470 content::NotificationService* service =
471 content::NotificationService::current();
472 service->Notify(chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
473 content::Source<ThemeService>(this),
474 content::NotificationService::NoDetails());
475 #if defined(OS_MACOSX)
476 NotifyPlatformThemeChanged();
477 #endif // OS_MACOSX
479 // Notify sync that theme has changed.
480 if (theme_syncable_service_.get()) {
481 theme_syncable_service_->OnThemeChange();
485 #if defined(USE_AURA)
486 void ThemeService::FreePlatformCaches() {
487 // Views (Skia) has no platform image cache to clear.
489 #endif
491 void ThemeService::OnExtensionServiceReady() {
492 if (!ready_) {
493 // If the ThemeService is not ready yet, the custom theme data pack needs to
494 // be recreated from the extension.
495 MigrateTheme();
496 set_ready();
498 // Send notification in case anyone requested data and cached it when the
499 // theme service was not ready yet.
500 NotifyThemeChanged();
503 registrar_.Add(this,
504 chrome::NOTIFICATION_EXTENSION_INSTALLED,
505 content::Source<Profile>(profile_));
506 registrar_.Add(this,
507 chrome::NOTIFICATION_EXTENSION_LOADED_DEPRECATED,
508 content::Source<Profile>(profile_));
509 registrar_.Add(this,
510 chrome::NOTIFICATION_EXTENSION_ENABLED,
511 content::Source<Profile>(profile_));
512 registrar_.Add(this,
513 chrome::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED,
514 content::Source<Profile>(profile_));
516 base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
517 base::Bind(&ThemeService::RemoveUnusedThemes,
518 weak_ptr_factory_.GetWeakPtr(),
519 false),
520 base::TimeDelta::FromSeconds(kRemoveUnusedThemesStartupDelay));
523 void ThemeService::MigrateTheme() {
524 // TODO(erg): We need to pop up a dialog informing the user that their
525 // theme is being migrated.
526 ExtensionService* service =
527 extensions::ExtensionSystem::Get(profile_)->extension_service();
528 const Extension* extension = service ?
529 service->GetExtensionById(GetThemeID(), false) : NULL;
530 if (extension) {
531 DLOG(ERROR) << "Migrating theme";
532 BuildFromExtension(extension);
533 content::RecordAction(UserMetricsAction("Themes.Migrated"));
534 } else {
535 DLOG(ERROR) << "Theme is mysteriously gone.";
536 ClearAllThemeData();
537 content::RecordAction(UserMetricsAction("Themes.Gone"));
541 void ThemeService::SwapThemeSupplier(
542 scoped_refptr<CustomThemeSupplier> theme_supplier) {
543 if (theme_supplier_.get())
544 theme_supplier_->StopUsingTheme();
545 theme_supplier_ = theme_supplier;
546 if (theme_supplier_.get())
547 theme_supplier_->StartUsingTheme();
550 void ThemeService::SavePackName(const base::FilePath& pack_path) {
551 profile_->GetPrefs()->SetFilePath(
552 prefs::kCurrentThemePackFilename, pack_path);
555 void ThemeService::SaveThemeID(const std::string& id) {
556 profile_->GetPrefs()->SetString(prefs::kCurrentThemeID, id);
559 void ThemeService::BuildFromExtension(const Extension* extension) {
560 scoped_refptr<BrowserThemePack> pack(
561 BrowserThemePack::BuildFromExtension(extension));
562 if (!pack.get()) {
563 // TODO(erg): We've failed to install the theme; perhaps we should tell the
564 // user? http://crbug.com/34780
565 LOG(ERROR) << "Could not load theme.";
566 return;
569 ExtensionService* service =
570 extensions::ExtensionSystem::Get(profile_)->extension_service();
571 if (!service)
572 return;
574 // Write the packed file to disk.
575 base::FilePath pack_path =
576 extension->path().Append(chrome::kThemePackFilename);
577 service->GetFileTaskRunner()->PostTask(
578 FROM_HERE,
579 base::Bind(&WritePackToDiskCallback, pack, pack_path));
581 SavePackName(pack_path);
582 SwapThemeSupplier(pack);
585 bool ThemeService::IsManagedUser() const {
586 return profile_->IsManaged();
589 void ThemeService::SetManagedUserTheme() {
590 SetCustomDefaultTheme(new ManagedUserTheme);
593 void ThemeService::OnInfobarDisplayed() {
594 number_of_infobars_++;
597 void ThemeService::OnInfobarDestroyed() {
598 number_of_infobars_--;
600 if (number_of_infobars_ == 0)
601 RemoveUnusedThemes(false);
604 ThemeSyncableService* ThemeService::GetThemeSyncableService() const {
605 return theme_syncable_service_.get();