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