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/user_style_sheet_watcher.h"
6
7 #include "base/base64.h"
8 #include "base/file_util.h"
9 #include "content/common/notification_service.h"
10 #include "content/common/notification_type.h"
11
12 using ::base::files::FilePathWatcher;
13
14 namespace {
15
16 // The subdirectory of the profile that contains the style sheet.
17 const char kStyleSheetDir[] = "User StyleSheets";
18 // The filename of the stylesheet.
19 const char kUserStyleSheetFile[] = "Custom.css";
20
21 } // namespace
22
23 // UserStyleSheetLoader is responsible for loading the user style sheet on the
24 // file thread and sends a notification when the style sheet is loaded. It is
25 // a helper to UserStyleSheetWatcher. The reference graph is as follows:
26 //
27 // .-----------------------. owns .-----------------.
28 // | UserStyleSheetWatcher |----------->| FilePathWatcher |
29 // '-----------------------' '-----------------'
30 // | |
31 // V |
32 // .----------------------. |
33 // | UserStyleSheetLoader |<--------------------'
34 // '----------------------'
35 //
36 // FilePathWatcher's reference to UserStyleSheetLoader is used for delivering
37 // the change notifications. Since they happen asynchronously,
38 // UserStyleSheetWatcher and its FilePathWatcher may be destroyed while a
39 // callback to UserStyleSheetLoader is in progress, in which case the
40 // UserStyleSheetLoader object outlives the watchers.
41 class UserStyleSheetLoader : public FilePathWatcher::Delegate {
42 public:
43 UserStyleSheetLoader();
~UserStyleSheetLoader()44 virtual ~UserStyleSheetLoader() {}
45
user_style_sheet() const46 GURL user_style_sheet() const {
47 return user_style_sheet_;
48 }
49
50 // Load the user style sheet on the file thread and convert it to a
51 // base64 URL. Posts the base64 URL back to the UI thread.
52 void LoadStyleSheet(const FilePath& style_sheet_file);
53
54 // Send out a notification if the stylesheet has already been loaded.
55 void NotifyLoaded();
56
57 // FilePathWatcher::Delegate interface
58 virtual void OnFilePathChanged(const FilePath& path);
59
60 private:
61 // Called on the UI thread after the stylesheet has loaded.
62 void SetStyleSheet(const GURL& url);
63
64 // The user style sheet as a base64 data:// URL.
65 GURL user_style_sheet_;
66
67 // Whether the stylesheet has been loaded.
68 bool has_loaded_;
69
70 DISALLOW_COPY_AND_ASSIGN(UserStyleSheetLoader);
71 };
72
UserStyleSheetLoader()73 UserStyleSheetLoader::UserStyleSheetLoader()
74 : has_loaded_(false) {
75 }
76
NotifyLoaded()77 void UserStyleSheetLoader::NotifyLoaded() {
78 if (has_loaded_) {
79 NotificationService::current()->Notify(
80 NotificationType::USER_STYLE_SHEET_UPDATED,
81 Source<UserStyleSheetLoader>(this),
82 NotificationService::NoDetails());
83 }
84 }
85
OnFilePathChanged(const FilePath & path)86 void UserStyleSheetLoader::OnFilePathChanged(const FilePath& path) {
87 LoadStyleSheet(path);
88 }
89
LoadStyleSheet(const FilePath & style_sheet_file)90 void UserStyleSheetLoader::LoadStyleSheet(const FilePath& style_sheet_file) {
91 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
92 // We keep the user style sheet in a subdir so we can watch for changes
93 // to the file.
94 FilePath style_sheet_dir = style_sheet_file.DirName();
95 if (!file_util::DirectoryExists(style_sheet_dir)) {
96 if (!file_util::CreateDirectory(style_sheet_dir))
97 return;
98 }
99 // Create the file if it doesn't exist.
100 if (!file_util::PathExists(style_sheet_file))
101 file_util::WriteFile(style_sheet_file, "", 0);
102
103 std::string css;
104 bool rv = file_util::ReadFileToString(style_sheet_file, &css);
105 GURL style_sheet_url;
106 if (rv && !css.empty()) {
107 std::string css_base64;
108 rv = base::Base64Encode(css, &css_base64);
109 if (rv) {
110 // WebKit knows about data urls, so convert the file to a data url.
111 const char kDataUrlPrefix[] = "data:text/css;charset=utf-8;base64,";
112 style_sheet_url = GURL(kDataUrlPrefix + css_base64);
113 }
114 }
115 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
116 NewRunnableMethod(this, &UserStyleSheetLoader::SetStyleSheet,
117 style_sheet_url));
118 }
119
SetStyleSheet(const GURL & url)120 void UserStyleSheetLoader::SetStyleSheet(const GURL& url) {
121 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
122
123 has_loaded_ = true;
124 user_style_sheet_ = url;
125 NotifyLoaded();
126 }
127
UserStyleSheetWatcher(const FilePath & profile_path)128 UserStyleSheetWatcher::UserStyleSheetWatcher(const FilePath& profile_path)
129 : profile_path_(profile_path),
130 loader_(new UserStyleSheetLoader) {
131 // Listen for when the first render view host is created. If we load
132 // too fast, the first tab won't hear the notification and won't get
133 // the user style sheet.
134 registrar_.Add(this, NotificationType::RENDER_VIEW_HOST_CREATED_FOR_TAB,
135 NotificationService::AllSources());
136 }
137
~UserStyleSheetWatcher()138 UserStyleSheetWatcher::~UserStyleSheetWatcher() {
139 }
140
Init()141 void UserStyleSheetWatcher::Init() {
142 // Make sure we run on the file thread.
143 if (!BrowserThread::CurrentlyOn(BrowserThread::FILE)) {
144 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
145 NewRunnableMethod(this, &UserStyleSheetWatcher::Init));
146 return;
147 }
148
149 if (!file_watcher_.get()) {
150 file_watcher_.reset(new FilePathWatcher);
151 FilePath style_sheet_file = profile_path_.AppendASCII(kStyleSheetDir)
152 .AppendASCII(kUserStyleSheetFile);
153 if (!file_watcher_->Watch(
154 style_sheet_file,
155 loader_.get())) {
156 LOG(ERROR) << "Failed to setup watch for " << style_sheet_file.value();
157 }
158 loader_->LoadStyleSheet(style_sheet_file);
159 }
160 }
161
user_style_sheet() const162 GURL UserStyleSheetWatcher::user_style_sheet() const {
163 return loader_->user_style_sheet();
164 }
165
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)166 void UserStyleSheetWatcher::Observe(NotificationType type,
167 const NotificationSource& source, const NotificationDetails& details) {
168 DCHECK(type == NotificationType::RENDER_VIEW_HOST_CREATED_FOR_TAB);
169 loader_->NotifyLoaded();
170 registrar_.RemoveAll();
171 }
172