OLD | NEW |
| (Empty) |
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. | |
4 | |
5 #include "chrome/browser/user_style_sheet_watcher.h" | |
6 | |
7 #include "base/base64.h" | |
8 #include "base/bind.h" | |
9 #include "base/file_util.h" | |
10 #include "chrome/browser/profiles/profile.h" | |
11 #include "content/public/browser/notification_service.h" | |
12 #include "content/public/browser/notification_types.h" | |
13 #include "content/public/browser/web_contents.h" | |
14 | |
15 using ::base::FilePathWatcher; | |
16 using content::BrowserThread; | |
17 using content::WebContents; | |
18 | |
19 namespace { | |
20 | |
21 // The subdirectory of the profile that contains the style sheet. | |
22 const char kStyleSheetDir[] = "User StyleSheets"; | |
23 // The filename of the stylesheet. | |
24 const char kUserStyleSheetFile[] = "Custom.css"; | |
25 | |
26 } // namespace | |
27 | |
28 // UserStyleSheetLoader is responsible for loading the user style sheet on the | |
29 // file thread and sends a notification when the style sheet is loaded. It is | |
30 // a helper to UserStyleSheetWatcher. The reference graph is as follows: | |
31 // | |
32 // .-----------------------. owns .-----------------. | |
33 // | UserStyleSheetWatcher |----------->| FilePathWatcher | | |
34 // '-----------------------' '-----------------' | |
35 // | | | |
36 // V | | |
37 // .----------------------. | | |
38 // | UserStyleSheetLoader |<--------------------' | |
39 // '----------------------' | |
40 // | |
41 // FilePathWatcher's reference to UserStyleSheetLoader is used for delivering | |
42 // the change notifications. Since they happen asynchronously, | |
43 // UserStyleSheetWatcher and its FilePathWatcher may be destroyed while a | |
44 // callback to UserStyleSheetLoader is in progress, in which case the | |
45 // UserStyleSheetLoader object outlives the watchers. | |
46 class UserStyleSheetLoader | |
47 : public base::RefCountedThreadSafe<UserStyleSheetLoader> { | |
48 public: | |
49 UserStyleSheetLoader(); | |
50 | |
51 GURL user_style_sheet() const { | |
52 return user_style_sheet_; | |
53 } | |
54 | |
55 // Load the user style sheet on the file thread and convert it to a | |
56 // base64 URL. Posts the base64 URL back to the UI thread. | |
57 void LoadStyleSheet(const base::FilePath& style_sheet_file); | |
58 | |
59 // Register a callback to be called whenever the stylesheet gets updated. | |
60 scoped_ptr<base::CallbackList<void(void)>::Subscription> | |
61 RegisterOnStyleSheetUpdatedCallback(const base::Closure& callback); | |
62 | |
63 // Send out a notification if the stylesheet has already been loaded. | |
64 void NotifyLoaded(); | |
65 | |
66 // FilePathWatcher::Callback method: | |
67 void NotifyPathChanged(const base::FilePath& path, bool error); | |
68 | |
69 private: | |
70 friend class base::RefCountedThreadSafe<UserStyleSheetLoader>; | |
71 ~UserStyleSheetLoader(); | |
72 | |
73 // Called on the UI thread after the stylesheet has loaded. | |
74 void SetStyleSheet(const GURL& url); | |
75 | |
76 // The user style sheet as a base64 data:// URL. | |
77 GURL user_style_sheet_; | |
78 | |
79 // Whether the stylesheet has been loaded. | |
80 bool has_loaded_; | |
81 | |
82 base::CallbackList<void(void)> style_sheet_updated_callbacks_; | |
83 | |
84 DISALLOW_COPY_AND_ASSIGN(UserStyleSheetLoader); | |
85 }; | |
86 | |
87 UserStyleSheetLoader::UserStyleSheetLoader() | |
88 : has_loaded_(false) { | |
89 } | |
90 | |
91 UserStyleSheetLoader::~UserStyleSheetLoader() { | |
92 } | |
93 | |
94 scoped_ptr<base::CallbackList<void(void)>::Subscription> | |
95 UserStyleSheetLoader::RegisterOnStyleSheetUpdatedCallback( | |
96 const base::Closure& callback) { | |
97 return style_sheet_updated_callbacks_.Add(callback); | |
98 } | |
99 | |
100 void UserStyleSheetLoader::NotifyLoaded() { | |
101 if (has_loaded_) { | |
102 style_sheet_updated_callbacks_.Notify(); | |
103 } | |
104 } | |
105 | |
106 void UserStyleSheetLoader::NotifyPathChanged(const base::FilePath& path, | |
107 bool error) { | |
108 if (!error) | |
109 LoadStyleSheet(path); | |
110 } | |
111 | |
112 void UserStyleSheetLoader::LoadStyleSheet( | |
113 const base::FilePath& style_sheet_file) { | |
114 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); | |
115 // We keep the user style sheet in a subdir so we can watch for changes | |
116 // to the file. | |
117 base::FilePath style_sheet_dir = style_sheet_file.DirName(); | |
118 if (!base::DirectoryExists(style_sheet_dir)) { | |
119 if (!file_util::CreateDirectory(style_sheet_dir)) | |
120 return; | |
121 } | |
122 // Create the file if it doesn't exist. | |
123 if (!base::PathExists(style_sheet_file)) | |
124 file_util::WriteFile(style_sheet_file, "", 0); | |
125 | |
126 std::string css; | |
127 bool rv = base::ReadFileToString(style_sheet_file, &css); | |
128 GURL style_sheet_url; | |
129 if (rv && !css.empty()) { | |
130 std::string css_base64; | |
131 rv = base::Base64Encode(css, &css_base64); | |
132 if (rv) { | |
133 // WebKit knows about data urls, so convert the file to a data url. | |
134 const char kDataUrlPrefix[] = "data:text/css;charset=utf-8;base64,"; | |
135 style_sheet_url = GURL(kDataUrlPrefix + css_base64); | |
136 } | |
137 } | |
138 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, | |
139 base::Bind(&UserStyleSheetLoader::SetStyleSheet, this, | |
140 style_sheet_url)); | |
141 } | |
142 | |
143 void UserStyleSheetLoader::SetStyleSheet(const GURL& url) { | |
144 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
145 | |
146 has_loaded_ = true; | |
147 user_style_sheet_ = url; | |
148 NotifyLoaded(); | |
149 } | |
150 | |
151 UserStyleSheetWatcher::UserStyleSheetWatcher(Profile* profile, | |
152 const base::FilePath& profile_path) | |
153 : RefcountedBrowserContextKeyedService(content::BrowserThread::UI), | |
154 profile_(profile), | |
155 profile_path_(profile_path), | |
156 loader_(new UserStyleSheetLoader) { | |
157 // Listen for when the first render view host is created. If we load | |
158 // too fast, the first tab won't hear the notification and won't get | |
159 // the user style sheet. | |
160 registrar_.Add(this, | |
161 content::NOTIFICATION_WEB_CONTENTS_RENDER_VIEW_HOST_CREATED, | |
162 content::NotificationService::AllBrowserContextsAndSources()); | |
163 } | |
164 | |
165 UserStyleSheetWatcher::~UserStyleSheetWatcher() { | |
166 } | |
167 | |
168 void UserStyleSheetWatcher::Init() { | |
169 // Make sure we run on the file thread. | |
170 if (!BrowserThread::CurrentlyOn(BrowserThread::FILE)) { | |
171 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, | |
172 base::Bind(&UserStyleSheetWatcher::Init, this)); | |
173 return; | |
174 } | |
175 | |
176 if (!file_watcher_.get()) { | |
177 file_watcher_.reset(new FilePathWatcher); | |
178 base::FilePath style_sheet_file = profile_path_.AppendASCII(kStyleSheetDir) | |
179 .AppendASCII(kUserStyleSheetFile); | |
180 if (!file_watcher_->Watch( | |
181 style_sheet_file, | |
182 false, | |
183 base::Bind(&UserStyleSheetLoader::NotifyPathChanged, | |
184 loader_.get()))) { | |
185 LOG(ERROR) << "Failed to setup watch for " << style_sheet_file.value(); | |
186 } | |
187 loader_->LoadStyleSheet(style_sheet_file); | |
188 } | |
189 } | |
190 | |
191 GURL UserStyleSheetWatcher::user_style_sheet() const { | |
192 return loader_->user_style_sheet(); | |
193 } | |
194 | |
195 scoped_ptr<base::CallbackList<void(void)>::Subscription> | |
196 UserStyleSheetWatcher::RegisterOnStyleSheetUpdatedCallback( | |
197 const base::Closure& callback) { | |
198 return loader_->RegisterOnStyleSheetUpdatedCallback(callback); | |
199 } | |
200 | |
201 void UserStyleSheetWatcher::Observe(int type, | |
202 const content::NotificationSource& source, | |
203 const content::NotificationDetails& details) { | |
204 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
205 DCHECK(type == content::NOTIFICATION_WEB_CONTENTS_RENDER_VIEW_HOST_CREATED); | |
206 if (profile_->IsSameProfile(Profile::FromBrowserContext( | |
207 content::Source<WebContents>(source)->GetBrowserContext()))) { | |
208 loader_->NotifyLoaded(); | |
209 registrar_.RemoveAll(); | |
210 } | |
211 } | |
212 | |
213 void UserStyleSheetWatcher::ShutdownOnUIThread() { | |
214 registrar_.RemoveAll(); | |
215 } | |
OLD | NEW |