• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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