1// Copyright (c) 2011 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/string_split.h"
8#include "base/string_util.h"
9#include "base/utf_string_conversions.h"
10#include "chrome/browser/extensions/extension_service.h"
11#include "chrome/browser/metrics/user_metrics.h"
12#include "chrome/browser/profiles/profile.h"
13#include "chrome/browser/themes/browser_theme_pack.h"
14#include "chrome/common/chrome_constants.h"
15#include "chrome/common/pref_names.h"
16#include "content/common/notification_service.h"
17#include "content/common/notification_type.h"
18#include "grit/app_resources.h"
19#include "grit/theme_resources.h"
20#include "ui/base/resource/resource_bundle.h"
21
22#if defined(OS_WIN)
23#include "views/widget/widget_win.h"
24#endif
25
26// Strings used in alignment properties.
27const char* ThemeService::kAlignmentTop = "top";
28const char* ThemeService::kAlignmentBottom = "bottom";
29const char* ThemeService::kAlignmentLeft = "left";
30const char* ThemeService::kAlignmentRight = "right";
31
32// Strings used in background tiling repetition properties.
33const char* ThemeService::kTilingNoRepeat = "no-repeat";
34const char* ThemeService::kTilingRepeatX = "repeat-x";
35const char* ThemeService::kTilingRepeatY = "repeat-y";
36const char* ThemeService::kTilingRepeat = "repeat";
37
38// The default theme if we haven't installed a theme yet or if we've clicked
39// the "Use Classic" button.
40const char* ThemeService::kDefaultThemeID = "";
41
42namespace {
43
44// The default theme if we've gone to the theme gallery and installed the
45// "Default" theme. We have to detect this case specifically. (By the time we
46// realize we've installed the default theme, we already have an extension
47// unpacked on the filesystem.)
48const char* kDefaultThemeGalleryID = "hkacjpbfdknhflllbcmjibkdeoafencn";
49
50SkColor TintForUnderline(SkColor input) {
51  return SkColorSetA(input, SkColorGetA(input) / 3);
52}
53
54SkColor IncreaseLightness(SkColor color, double percent) {
55  color_utils::HSL result;
56  color_utils::SkColorToHSL(color, &result);
57  result.l += (1 - result.l) * percent;
58  return color_utils::HSLToSkColor(result, SkColorGetA(color));
59}
60
61// Default colors.
62const SkColor kDefaultColorFrame = SkColorSetRGB(66, 116, 201);
63const SkColor kDefaultColorFrameInactive = SkColorSetRGB(161, 182, 228);
64const SkColor kDefaultColorFrameIncognito = SkColorSetRGB(83, 106, 139);
65const SkColor kDefaultColorFrameIncognitoInactive =
66    SkColorSetRGB(126, 139, 156);
67#if defined(OS_MACOSX)
68const SkColor kDefaultColorToolbar = SkColorSetRGB(230, 230, 230);
69#else
70const SkColor kDefaultColorToolbar = SkColorSetRGB(223, 223, 223);
71#endif
72const SkColor kDefaultColorTabText = SK_ColorBLACK;
73#if defined(OS_MACOSX)
74const SkColor kDefaultColorBackgroundTabText = SK_ColorBLACK;
75const SkColor kDefaultColorBookmarkText = SK_ColorBLACK;
76#else
77const SkColor kDefaultColorBackgroundTabText = SkColorSetRGB(64, 64, 64);
78const SkColor kDefaultColorBookmarkText = SkColorSetRGB(18, 50, 114);
79#endif
80#if defined(OS_WIN)
81const SkColor kDefaultColorNTPBackground =
82    color_utils::GetSysSkColor(COLOR_WINDOW);
83const SkColor kDefaultColorNTPText =
84    color_utils::GetSysSkColor(COLOR_WINDOWTEXT);
85const SkColor kDefaultColorNTPLink =
86    color_utils::GetSysSkColor(COLOR_HOTLIGHT);
87#else
88// TODO(beng): source from theme provider.
89const SkColor kDefaultColorNTPBackground = SK_ColorWHITE;
90const SkColor kDefaultColorNTPText = SK_ColorBLACK;
91const SkColor kDefaultColorNTPLink = SkColorSetRGB(6, 55, 116);
92#endif
93const SkColor kDefaultColorNTPHeader = SkColorSetRGB(150, 150, 150);
94const SkColor kDefaultColorNTPSection = SkColorSetRGB(229, 229, 229);
95const SkColor kDefaultColorNTPSectionText = SK_ColorBLACK;
96const SkColor kDefaultColorNTPSectionLink = SkColorSetRGB(6, 55, 116);
97const SkColor kDefaultColorControlBackground = SkColorSetARGB(0, 0, 0, 0);
98const SkColor kDefaultColorButtonBackground = SkColorSetARGB(0, 0, 0, 0);
99#if defined(OS_MACOSX)
100const SkColor kDefaultColorToolbarButtonStroke = SkColorSetARGB(75, 81, 81, 81);
101const SkColor kDefaultColorToolbarButtonStrokeInactive =
102    SkColorSetARGB(75, 99, 99, 99);
103const SkColor kDefaultColorToolbarBezel = SkColorSetRGB(247, 247, 247);
104const SkColor kDefaultColorToolbarStroke = SkColorSetRGB(103, 103, 103);
105const SkColor kDefaultColorToolbarStrokeInactive = SkColorSetRGB(123, 123, 123);
106#endif
107
108// Default tints.
109const color_utils::HSL kDefaultTintButtons = { -1, -1, -1 };
110const color_utils::HSL kDefaultTintFrame = { -1, -1, -1 };
111const color_utils::HSL kDefaultTintFrameInactive = { -1, -1, 0.75f };
112const color_utils::HSL kDefaultTintFrameIncognito = { -1, 0.2f, 0.35f };
113const color_utils::HSL kDefaultTintFrameIncognitoInactive = { -1, 0.3f, 0.6f };
114const color_utils::HSL kDefaultTintBackgroundTab = { -1, 0.5, 0.75 };
115
116// Default display properties.
117const int kDefaultDisplayPropertyNTPAlignment =
118    ThemeService::ALIGN_BOTTOM;
119const int kDefaultDisplayPropertyNTPTiling =
120    ThemeService::NO_REPEAT;
121const int kDefaultDisplayPropertyNTPInverseLogo = 0;
122
123// The sum of kFrameBorderThickness and kNonClientRestoredExtraThickness from
124// OpaqueBrowserFrameView.
125const int kRestoredTabVerticalOffset = 15;
126
127// The image resources we will allow people to theme.
128const int kThemeableImages[] = {
129  IDR_THEME_FRAME,
130  IDR_THEME_FRAME_INACTIVE,
131  IDR_THEME_FRAME_INCOGNITO,
132  IDR_THEME_FRAME_INCOGNITO_INACTIVE,
133  IDR_THEME_TOOLBAR,
134  IDR_THEME_TAB_BACKGROUND,
135  IDR_THEME_TAB_BACKGROUND_INCOGNITO,
136  IDR_THEME_TAB_BACKGROUND_V,
137  IDR_THEME_NTP_BACKGROUND,
138  IDR_THEME_FRAME_OVERLAY,
139  IDR_THEME_FRAME_OVERLAY_INACTIVE,
140  IDR_THEME_BUTTON_BACKGROUND,
141  IDR_THEME_NTP_ATTRIBUTION,
142  IDR_THEME_WINDOW_CONTROL_BACKGROUND
143};
144
145bool HasThemeableImage(int themeable_image_id) {
146  static std::set<int> themeable_images;
147  if (themeable_images.empty()) {
148    themeable_images.insert(
149        kThemeableImages, kThemeableImages + arraysize(kThemeableImages));
150  }
151  return themeable_images.count(themeable_image_id) > 0;
152}
153
154// The image resources that will be tinted by the 'button' tint value.
155// If you change this list, you must increment the version number in
156// browser_theme_pack.cc, and you should assign persistent IDs to the
157// data table at the start of said file or else tinted versions of
158// these resources will not be created.
159const int kToolbarButtonIDs[] = {
160  IDR_BACK, IDR_BACK_D, IDR_BACK_H, IDR_BACK_P,
161  IDR_FORWARD, IDR_FORWARD_D, IDR_FORWARD_H, IDR_FORWARD_P,
162  IDR_HOME, IDR_HOME_H, IDR_HOME_P,
163  IDR_RELOAD, IDR_RELOAD_H, IDR_RELOAD_P,
164  IDR_STOP, IDR_STOP_D, IDR_STOP_H, IDR_STOP_P,
165  IDR_LOCATIONBG_C, IDR_LOCATIONBG_L, IDR_LOCATIONBG_R,
166  IDR_BROWSER_ACTIONS_OVERFLOW, IDR_BROWSER_ACTIONS_OVERFLOW_H,
167  IDR_BROWSER_ACTIONS_OVERFLOW_P,
168  IDR_TOOLS, IDR_TOOLS_H, IDR_TOOLS_P,
169  IDR_MENU_DROPARROW,
170  IDR_THROBBER, IDR_THROBBER_WAITING, IDR_THROBBER_LIGHT,
171};
172
173// Writes the theme pack to disk on a separate thread.
174class WritePackToDiskTask : public Task {
175 public:
176  WritePackToDiskTask(BrowserThemePack* pack, const FilePath& path)
177      : theme_pack_(pack), pack_path_(path) {}
178
179  virtual void Run() {
180    if (!theme_pack_->WriteToDisk(pack_path_)) {
181      NOTREACHED() << "Could not write theme pack to disk";
182    }
183  }
184
185 private:
186  scoped_refptr<BrowserThemePack> theme_pack_;
187  FilePath pack_path_;
188};
189
190}  // namespace
191
192bool ThemeService::IsThemeableImage(int resource_id) {
193  return HasThemeableImage(resource_id);
194}
195
196ThemeService::ThemeService()
197    : rb_(ResourceBundle::GetSharedInstance()),
198      profile_(NULL),
199      number_of_infobars_(0) {
200  // Initialize the themeable image map so we can use it on other threads.
201  HasThemeableImage(0);
202}
203
204ThemeService::~ThemeService() {
205  FreePlatformCaches();
206}
207
208void ThemeService::Init(Profile* profile) {
209  DCHECK(CalledOnValidThread());
210  profile_ = profile;
211
212  // Listen to EXTENSION_LOADED instead of EXTENSION_INSTALLED because
213  // the extension cannot yet be found via GetExtensionById() if it is
214  // installed but not loaded (which may confuse listeners to
215  // BROWSER_THEME_CHANGED).
216  registrar_.Add(this,
217                 NotificationType::EXTENSION_LOADED,
218                 Source<Profile>(profile_));
219
220  LoadThemePrefs();
221}
222
223SkBitmap* ThemeService::GetBitmapNamed(int id) const {
224  DCHECK(CalledOnValidThread());
225
226  SkBitmap* bitmap = NULL;
227
228  if (theme_pack_.get())
229    bitmap = theme_pack_->GetBitmapNamed(id);
230
231  if (!bitmap)
232    bitmap = rb_.GetBitmapNamed(id);
233
234  return bitmap;
235}
236
237SkColor ThemeService::GetColor(int id) const {
238  DCHECK(CalledOnValidThread());
239
240  SkColor color;
241  if (theme_pack_.get() && theme_pack_->GetColor(id, &color))
242    return color;
243
244  // For backward compat with older themes, some newer colors are generated from
245  // older ones if they are missing.
246  switch (id) {
247    case COLOR_NTP_SECTION_HEADER_TEXT:
248      return IncreaseLightness(GetColor(COLOR_NTP_TEXT), 0.30);
249    case COLOR_NTP_SECTION_HEADER_TEXT_HOVER:
250      return GetColor(COLOR_NTP_TEXT);
251    case COLOR_NTP_SECTION_HEADER_RULE:
252      return IncreaseLightness(GetColor(COLOR_NTP_TEXT), 0.70);
253    case COLOR_NTP_SECTION_HEADER_RULE_LIGHT:
254      return IncreaseLightness(GetColor(COLOR_NTP_TEXT), 0.86);
255    case COLOR_NTP_TEXT_LIGHT:
256      return IncreaseLightness(GetColor(COLOR_NTP_TEXT), 0.40);
257  }
258
259  return GetDefaultColor(id);
260}
261
262bool ThemeService::GetDisplayProperty(int id, int* result) const {
263  if (theme_pack_.get())
264    return theme_pack_->GetDisplayProperty(id, result);
265
266  return GetDefaultDisplayProperty(id, result);
267}
268
269bool ThemeService::ShouldUseNativeFrame() const {
270  if (HasCustomImage(IDR_THEME_FRAME))
271    return false;
272#if defined(OS_WIN)
273  return views::WidgetWin::IsAeroGlassEnabled();
274#else
275  return false;
276#endif
277}
278
279bool ThemeService::HasCustomImage(int id) const {
280  if (!HasThemeableImage(id))
281    return false;
282
283  if (theme_pack_)
284    return theme_pack_->HasCustomImage(id);
285
286  return false;
287}
288
289RefCountedMemory* ThemeService::GetRawData(int id) const {
290  // Check to see whether we should substitute some images.
291  int ntp_alternate;
292  GetDisplayProperty(NTP_LOGO_ALTERNATE, &ntp_alternate);
293  if (id == IDR_PRODUCT_LOGO && ntp_alternate != 0)
294    id = IDR_PRODUCT_LOGO_WHITE;
295
296  RefCountedMemory* data = NULL;
297  if (theme_pack_.get())
298    data = theme_pack_->GetRawData(id);
299  if (!data)
300    data = rb_.LoadDataResourceBytes(id);
301
302  return data;
303}
304
305void ThemeService::SetTheme(const Extension* extension) {
306  // Clear our image cache.
307  FreePlatformCaches();
308
309  DCHECK(extension);
310  DCHECK(extension->is_theme());
311
312  BuildFromExtension(extension);
313  SaveThemeID(extension->id());
314
315  NotifyThemeChanged();
316  UserMetrics::RecordAction(UserMetricsAction("Themes_Installed"), profile_);
317}
318
319void ThemeService::RemoveUnusedThemes() {
320  if (!profile_)
321    return;
322  ExtensionService* service = profile_->GetExtensionService();
323  if (!service)
324    return;
325  std::string current_theme = GetThemeID();
326  std::vector<std::string> remove_list;
327  const ExtensionList* extensions = service->extensions();
328  for (ExtensionList::const_iterator it = extensions->begin();
329       it != extensions->end(); ++it) {
330    if ((*it)->is_theme() && (*it)->id() != current_theme) {
331      remove_list.push_back((*it)->id());
332    }
333  }
334  for (size_t i = 0; i < remove_list.size(); ++i)
335    service->UninstallExtension(remove_list[i], false, NULL);
336}
337
338void ThemeService::UseDefaultTheme() {
339  ClearAllThemeData();
340  NotifyThemeChanged();
341  UserMetrics::RecordAction(UserMetricsAction("Themes_Reset"), profile_);
342}
343
344void ThemeService::SetNativeTheme() {
345  UseDefaultTheme();
346}
347
348bool ThemeService::UsingDefaultTheme() {
349  std::string id = GetThemeID();
350  return id == ThemeService::kDefaultThemeID ||
351      id == kDefaultThemeGalleryID;
352}
353
354std::string ThemeService::GetThemeID() const {
355  return profile_->GetPrefs()->GetString(prefs::kCurrentThemeID);
356}
357
358// static
359std::string ThemeService::AlignmentToString(int alignment) {
360  // Convert from an AlignmentProperty back into a string.
361  std::string vertical_string;
362  std::string horizontal_string;
363
364  if (alignment & ThemeService::ALIGN_TOP)
365    vertical_string = kAlignmentTop;
366  else if (alignment & ThemeService::ALIGN_BOTTOM)
367    vertical_string = kAlignmentBottom;
368
369  if (alignment & ThemeService::ALIGN_LEFT)
370    horizontal_string = kAlignmentLeft;
371  else if (alignment & ThemeService::ALIGN_RIGHT)
372    horizontal_string = kAlignmentRight;
373
374  if (vertical_string.empty())
375    return horizontal_string;
376  if (horizontal_string.empty())
377    return vertical_string;
378  return vertical_string + " " + horizontal_string;
379}
380
381// static
382int ThemeService::StringToAlignment(const std::string& alignment) {
383  std::vector<std::wstring> split;
384  base::SplitStringAlongWhitespace(UTF8ToWide(alignment), &split);
385
386  int alignment_mask = 0;
387  for (std::vector<std::wstring>::iterator alignments(split.begin());
388       alignments != split.end(); ++alignments) {
389    std::string comp = WideToUTF8(*alignments);
390    const char* component = comp.c_str();
391
392    if (base::strcasecmp(component, kAlignmentTop) == 0)
393      alignment_mask |= ThemeService::ALIGN_TOP;
394    else if (base::strcasecmp(component, kAlignmentBottom) == 0)
395      alignment_mask |= ThemeService::ALIGN_BOTTOM;
396
397    if (base::strcasecmp(component, kAlignmentLeft) == 0)
398      alignment_mask |= ThemeService::ALIGN_LEFT;
399    else if (base::strcasecmp(component, kAlignmentRight) == 0)
400      alignment_mask |= ThemeService::ALIGN_RIGHT;
401  }
402  return alignment_mask;
403}
404
405// static
406std::string ThemeService::TilingToString(int tiling) {
407  // Convert from a TilingProperty back into a string.
408  if (tiling == ThemeService::REPEAT_X)
409    return kTilingRepeatX;
410  if (tiling == ThemeService::REPEAT_Y)
411    return kTilingRepeatY;
412  if (tiling == ThemeService::REPEAT)
413    return kTilingRepeat;
414  return kTilingNoRepeat;
415}
416
417// static
418int ThemeService::StringToTiling(const std::string& tiling) {
419  const char* component = tiling.c_str();
420
421  if (base::strcasecmp(component, kTilingRepeatX) == 0)
422    return ThemeService::REPEAT_X;
423  if (base::strcasecmp(component, kTilingRepeatY) == 0)
424    return ThemeService::REPEAT_Y;
425  if (base::strcasecmp(component, kTilingRepeat) == 0)
426    return ThemeService::REPEAT;
427  // NO_REPEAT is the default choice.
428  return ThemeService::NO_REPEAT;
429}
430
431// static
432color_utils::HSL ThemeService::GetDefaultTint(int id) {
433  switch (id) {
434    case TINT_FRAME:
435      return kDefaultTintFrame;
436    case TINT_FRAME_INACTIVE:
437      return kDefaultTintFrameInactive;
438    case TINT_FRAME_INCOGNITO:
439      return kDefaultTintFrameIncognito;
440    case TINT_FRAME_INCOGNITO_INACTIVE:
441      return kDefaultTintFrameIncognitoInactive;
442    case TINT_BUTTONS:
443      return kDefaultTintButtons;
444    case TINT_BACKGROUND_TAB:
445      return kDefaultTintBackgroundTab;
446    default:
447      color_utils::HSL result = {-1, -1, -1};
448      return result;
449  }
450}
451
452// static
453SkColor ThemeService::GetDefaultColor(int id) {
454  switch (id) {
455    case COLOR_FRAME:
456      return kDefaultColorFrame;
457    case COLOR_FRAME_INACTIVE:
458      return kDefaultColorFrameInactive;
459    case COLOR_FRAME_INCOGNITO:
460      return kDefaultColorFrameIncognito;
461    case COLOR_FRAME_INCOGNITO_INACTIVE:
462      return kDefaultColorFrameIncognitoInactive;
463    case COLOR_TOOLBAR:
464      return kDefaultColorToolbar;
465    case COLOR_TAB_TEXT:
466      return kDefaultColorTabText;
467    case COLOR_BACKGROUND_TAB_TEXT:
468      return kDefaultColorBackgroundTabText;
469    case COLOR_BOOKMARK_TEXT:
470      return kDefaultColorBookmarkText;
471    case COLOR_NTP_BACKGROUND:
472      return kDefaultColorNTPBackground;
473    case COLOR_NTP_TEXT:
474      return kDefaultColorNTPText;
475    case COLOR_NTP_LINK:
476      return kDefaultColorNTPLink;
477    case COLOR_NTP_LINK_UNDERLINE:
478      return TintForUnderline(kDefaultColorNTPLink);
479    case COLOR_NTP_HEADER:
480      return kDefaultColorNTPHeader;
481    case COLOR_NTP_SECTION:
482      return kDefaultColorNTPSection;
483    case COLOR_NTP_SECTION_TEXT:
484      return kDefaultColorNTPSectionText;
485    case COLOR_NTP_SECTION_LINK:
486      return kDefaultColorNTPSectionLink;
487    case COLOR_NTP_SECTION_LINK_UNDERLINE:
488      return TintForUnderline(kDefaultColorNTPSectionLink);
489    case COLOR_CONTROL_BACKGROUND:
490      return kDefaultColorControlBackground;
491    case COLOR_BUTTON_BACKGROUND:
492      return kDefaultColorButtonBackground;
493#if defined(OS_MACOSX)
494    case COLOR_TOOLBAR_BUTTON_STROKE:
495      return kDefaultColorToolbarButtonStroke;
496    case COLOR_TOOLBAR_BUTTON_STROKE_INACTIVE:
497      return kDefaultColorToolbarButtonStrokeInactive;
498    case COLOR_TOOLBAR_BEZEL:
499      return kDefaultColorToolbarBezel;
500    case COLOR_TOOLBAR_STROKE:
501      return kDefaultColorToolbarStroke;
502    case COLOR_TOOLBAR_STROKE_INACTIVE:
503      return kDefaultColorToolbarStrokeInactive;
504#endif
505    default:
506      // Return a debugging red color.
507      return 0xffff0000;
508  }
509}
510
511// static
512bool ThemeService::GetDefaultDisplayProperty(int id, int* result) {
513  switch (id) {
514    case NTP_BACKGROUND_ALIGNMENT:
515      *result = kDefaultDisplayPropertyNTPAlignment;
516      return true;
517    case NTP_BACKGROUND_TILING:
518      *result = kDefaultDisplayPropertyNTPTiling;
519      return true;
520    case NTP_LOGO_ALTERNATE:
521      *result = kDefaultDisplayPropertyNTPInverseLogo;
522      return true;
523  }
524
525  return false;
526}
527
528// static
529const std::set<int>& ThemeService::GetTintableToolbarButtons() {
530  static std::set<int> button_set;
531  if (button_set.empty()) {
532    button_set = std::set<int>(
533        kToolbarButtonIDs,
534        kToolbarButtonIDs + arraysize(kToolbarButtonIDs));
535  }
536
537  return button_set;
538}
539
540color_utils::HSL ThemeService::GetTint(int id) const {
541  DCHECK(CalledOnValidThread());
542
543  color_utils::HSL hsl;
544  if (theme_pack_.get() && theme_pack_->GetTint(id, &hsl))
545    return hsl;
546
547  return GetDefaultTint(id);
548}
549
550void ThemeService::ClearAllThemeData() {
551  // Clear our image cache.
552  FreePlatformCaches();
553  theme_pack_ = NULL;
554
555  profile_->GetPrefs()->ClearPref(prefs::kCurrentThemePackFilename);
556  SaveThemeID(kDefaultThemeID);
557}
558
559void ThemeService::LoadThemePrefs() {
560  PrefService* prefs = profile_->GetPrefs();
561
562  std::string current_id = GetThemeID();
563  if (current_id != kDefaultThemeID) {
564    bool loaded_pack = false;
565
566    // If we don't have a file pack, we're updating from an old version.
567    FilePath path = prefs->GetFilePath(prefs::kCurrentThemePackFilename);
568    if (path != FilePath()) {
569      theme_pack_ = BrowserThemePack::BuildFromDataPack(path, current_id);
570      loaded_pack = theme_pack_.get() != NULL;
571    }
572
573    if (loaded_pack) {
574      UserMetrics::RecordAction(UserMetricsAction("Themes.Loaded"), profile_);
575    } else {
576      // TODO(erg): We need to pop up a dialog informing the user that their
577      // theme is being migrated.
578      ExtensionService* service = profile_->GetExtensionService();
579      if (service) {
580        const Extension* extension =
581            service->GetExtensionById(current_id, false);
582        if (extension) {
583          DLOG(ERROR) << "Migrating theme";
584          BuildFromExtension(extension);
585          UserMetrics::RecordAction(UserMetricsAction("Themes.Migrated"),
586                                    profile_);
587        } else {
588          DLOG(ERROR) << "Theme is mysteriously gone.";
589          ClearAllThemeData();
590          UserMetrics::RecordAction(UserMetricsAction("Themes.Gone"), profile_);
591        }
592      }
593    }
594  }
595}
596
597void ThemeService::NotifyThemeChanged() {
598  VLOG(1) << "Sending BROWSER_THEME_CHANGED";
599  // Redraw!
600  NotificationService* service = NotificationService::current();
601  service->Notify(NotificationType::BROWSER_THEME_CHANGED,
602                  Source<ThemeService>(this),
603                  NotificationService::NoDetails());
604#if defined(OS_MACOSX)
605  NotifyPlatformThemeChanged();
606#endif  // OS_MACOSX
607}
608
609#if defined(OS_WIN)
610void ThemeService::FreePlatformCaches() {
611  // Views (Skia) has no platform image cache to clear.
612}
613#endif
614
615void ThemeService::Observe(NotificationType type,
616                           const NotificationSource& source,
617                           const NotificationDetails& details) {
618  DCHECK(type == NotificationType::EXTENSION_LOADED);
619  const Extension* extension = Details<const Extension>(details).ptr();
620  if (!extension->is_theme()) {
621    return;
622  }
623  SetTheme(extension);
624}
625
626void ThemeService::SavePackName(const FilePath& pack_path) {
627  profile_->GetPrefs()->SetFilePath(
628      prefs::kCurrentThemePackFilename, pack_path);
629}
630
631void ThemeService::SaveThemeID(const std::string& id) {
632  profile_->GetPrefs()->SetString(prefs::kCurrentThemeID, id);
633}
634
635void ThemeService::BuildFromExtension(const Extension* extension) {
636  scoped_refptr<BrowserThemePack> pack(
637      BrowserThemePack::BuildFromExtension(extension));
638  if (!pack.get()) {
639    // TODO(erg): We've failed to install the theme; perhaps we should tell the
640    // user? http://crbug.com/34780
641    LOG(ERROR) << "Could not load theme.";
642    return;
643  }
644
645  // Write the packed file to disk.
646  FilePath pack_path = extension->path().Append(chrome::kThemePackFilename);
647  BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
648                          new WritePackToDiskTask(pack, pack_path));
649
650  SavePackName(pack_path);
651  theme_pack_ = pack;
652}
653
654void ThemeService::OnInfobarDisplayed() {
655  number_of_infobars_++;
656}
657
658void ThemeService::OnInfobarDestroyed() {
659  number_of_infobars_--;
660
661  if (number_of_infobars_ == 0)
662    RemoveUnusedThemes();
663}
664