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/user_style_sheet_watcher.h"
7 #include "base/base64.h"
9 #include "base/file_util.h"
10 #include "chrome/browser/profiles/profile.h"
11 #include "chrome/browser/chrome_notification_types.h"
12 #include "content/public/browser/notification_service.h"
13 #include "content/public/browser/notification_types.h"
14 #include "content/public/browser/web_contents.h"
16 using ::base::FilePathWatcher
;
17 using content::BrowserThread
;
18 using content::WebContents
;
22 // The subdirectory of the profile that contains the style sheet.
23 const char kStyleSheetDir
[] = "User StyleSheets";
24 // The filename of the stylesheet.
25 const char kUserStyleSheetFile
[] = "Custom.css";
29 // UserStyleSheetLoader is responsible for loading the user style sheet on the
30 // file thread and sends a notification when the style sheet is loaded. It is
31 // a helper to UserStyleSheetWatcher. The reference graph is as follows:
33 // .-----------------------. owns .-----------------.
34 // | UserStyleSheetWatcher |----------->| FilePathWatcher |
35 // '-----------------------' '-----------------'
38 // .----------------------. |
39 // | UserStyleSheetLoader |<--------------------'
40 // '----------------------'
42 // FilePathWatcher's reference to UserStyleSheetLoader is used for delivering
43 // the change notifications. Since they happen asynchronously,
44 // UserStyleSheetWatcher and its FilePathWatcher may be destroyed while a
45 // callback to UserStyleSheetLoader is in progress, in which case the
46 // UserStyleSheetLoader object outlives the watchers.
47 class UserStyleSheetLoader
48 : public base::RefCountedThreadSafe
<UserStyleSheetLoader
> {
50 UserStyleSheetLoader();
52 GURL
user_style_sheet() const {
53 return user_style_sheet_
;
56 // Load the user style sheet on the file thread and convert it to a
57 // base64 URL. Posts the base64 URL back to the UI thread.
58 void LoadStyleSheet(const base::FilePath
& style_sheet_file
);
60 // Send out a notification if the stylesheet has already been loaded.
63 // FilePathWatcher::Callback method:
64 void NotifyPathChanged(const base::FilePath
& path
, bool error
);
67 friend class base::RefCountedThreadSafe
<UserStyleSheetLoader
>;
68 ~UserStyleSheetLoader() {}
70 // Called on the UI thread after the stylesheet has loaded.
71 void SetStyleSheet(const GURL
& url
);
73 // The user style sheet as a base64 data:// URL.
74 GURL user_style_sheet_
;
76 // Whether the stylesheet has been loaded.
79 DISALLOW_COPY_AND_ASSIGN(UserStyleSheetLoader
);
82 UserStyleSheetLoader::UserStyleSheetLoader()
83 : has_loaded_(false) {
86 void UserStyleSheetLoader::NotifyLoaded() {
88 content::NotificationService::current()->Notify(
89 chrome::NOTIFICATION_USER_STYLE_SHEET_UPDATED
,
90 content::Source
<UserStyleSheetLoader
>(this),
91 content::NotificationService::NoDetails());
95 void UserStyleSheetLoader::NotifyPathChanged(const base::FilePath
& path
,
101 void UserStyleSheetLoader::LoadStyleSheet(
102 const base::FilePath
& style_sheet_file
) {
103 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
104 // We keep the user style sheet in a subdir so we can watch for changes
106 base::FilePath style_sheet_dir
= style_sheet_file
.DirName();
107 if (!base::DirectoryExists(style_sheet_dir
)) {
108 if (!file_util::CreateDirectory(style_sheet_dir
))
111 // Create the file if it doesn't exist.
112 if (!base::PathExists(style_sheet_file
))
113 file_util::WriteFile(style_sheet_file
, "", 0);
116 bool rv
= file_util::ReadFileToString(style_sheet_file
, &css
);
117 GURL style_sheet_url
;
118 if (rv
&& !css
.empty()) {
119 std::string css_base64
;
120 rv
= base::Base64Encode(css
, &css_base64
);
122 // WebKit knows about data urls, so convert the file to a data url.
123 const char kDataUrlPrefix
[] = "data:text/css;charset=utf-8;base64,";
124 style_sheet_url
= GURL(kDataUrlPrefix
+ css_base64
);
127 BrowserThread::PostTask(BrowserThread::UI
, FROM_HERE
,
128 base::Bind(&UserStyleSheetLoader::SetStyleSheet
, this,
132 void UserStyleSheetLoader::SetStyleSheet(const GURL
& url
) {
133 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
136 user_style_sheet_
= url
;
140 UserStyleSheetWatcher::UserStyleSheetWatcher(Profile
* profile
,
141 const base::FilePath
& profile_path
)
142 : RefcountedBrowserContextKeyedService(content::BrowserThread::UI
),
144 profile_path_(profile_path
),
145 loader_(new UserStyleSheetLoader
) {
146 // Listen for when the first render view host is created. If we load
147 // too fast, the first tab won't hear the notification and won't get
148 // the user style sheet.
150 content::NOTIFICATION_WEB_CONTENTS_RENDER_VIEW_HOST_CREATED
,
151 content::NotificationService::AllBrowserContextsAndSources());
154 UserStyleSheetWatcher::~UserStyleSheetWatcher() {
157 void UserStyleSheetWatcher::Init() {
158 // Make sure we run on the file thread.
159 if (!BrowserThread::CurrentlyOn(BrowserThread::FILE)) {
160 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE
,
161 base::Bind(&UserStyleSheetWatcher::Init
, this));
165 if (!file_watcher_
.get()) {
166 file_watcher_
.reset(new FilePathWatcher
);
167 base::FilePath style_sheet_file
= profile_path_
.AppendASCII(kStyleSheetDir
)
168 .AppendASCII(kUserStyleSheetFile
);
169 if (!file_watcher_
->Watch(
172 base::Bind(&UserStyleSheetLoader::NotifyPathChanged
,
174 LOG(ERROR
) << "Failed to setup watch for " << style_sheet_file
.value();
176 loader_
->LoadStyleSheet(style_sheet_file
);
180 GURL
UserStyleSheetWatcher::user_style_sheet() const {
181 return loader_
->user_style_sheet();
184 void UserStyleSheetWatcher::Observe(int type
,
185 const content::NotificationSource
& source
,
186 const content::NotificationDetails
& details
) {
187 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
188 DCHECK(type
== content::NOTIFICATION_WEB_CONTENTS_RENDER_VIEW_HOST_CREATED
);
189 if (profile_
->IsSameProfile(Profile::FromBrowserContext(
190 content::Source
<WebContents
>(source
)->GetBrowserContext()))) {
191 loader_
->NotifyLoaded();
192 registrar_
.RemoveAll();
196 void UserStyleSheetWatcher::ShutdownOnUIThread() {
197 registrar_
.RemoveAll();