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 "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" 15 16 using ::base::FilePathWatcher; 17 using content::BrowserThread; 18 using content::WebContents; 19 20 namespace { 21 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"; 26 27 } // namespace 28 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: 32 // 33 // .-----------------------. owns .-----------------. 34 // | UserStyleSheetWatcher |----------->| FilePathWatcher | 35 // '-----------------------' '-----------------' 36 // | | 37 // V | 38 // .----------------------. | 39 // | UserStyleSheetLoader |<--------------------' 40 // '----------------------' 41 // 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> { 49 public: 50 UserStyleSheetLoader(); 51 52 GURL user_style_sheet() const { 53 return user_style_sheet_; 54 } 55 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); 59 60 // Send out a notification if the stylesheet has already been loaded. 61 void NotifyLoaded(); 62 63 // FilePathWatcher::Callback method: 64 void NotifyPathChanged(const base::FilePath& path, bool error); 65 66 private: 67 friend class base::RefCountedThreadSafe<UserStyleSheetLoader>; 68 ~UserStyleSheetLoader() {} 69 70 // Called on the UI thread after the stylesheet has loaded. 71 void SetStyleSheet(const GURL& url); 72 73 // The user style sheet as a base64 data:// URL. 74 GURL user_style_sheet_; 75 76 // Whether the stylesheet has been loaded. 77 bool has_loaded_; 78 79 DISALLOW_COPY_AND_ASSIGN(UserStyleSheetLoader); 80 }; 81 82 UserStyleSheetLoader::UserStyleSheetLoader() 83 : has_loaded_(false) { 84 } 85 86 void UserStyleSheetLoader::NotifyLoaded() { 87 if (has_loaded_) { 88 content::NotificationService::current()->Notify( 89 chrome::NOTIFICATION_USER_STYLE_SHEET_UPDATED, 90 content::Source<UserStyleSheetLoader>(this), 91 content::NotificationService::NoDetails()); 92 } 93 } 94 95 void UserStyleSheetLoader::NotifyPathChanged(const base::FilePath& path, 96 bool error) { 97 if (!error) 98 LoadStyleSheet(path); 99 } 100 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 105 // to the file. 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)) 109 return; 110 } 111 // Create the file if it doesn't exist. 112 if (!base::PathExists(style_sheet_file)) 113 file_util::WriteFile(style_sheet_file, "", 0); 114 115 std::string css; 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); 121 if (rv) { 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); 125 } 126 } 127 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, 128 base::Bind(&UserStyleSheetLoader::SetStyleSheet, this, 129 style_sheet_url)); 130 } 131 132 void UserStyleSheetLoader::SetStyleSheet(const GURL& url) { 133 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 134 135 has_loaded_ = true; 136 user_style_sheet_ = url; 137 NotifyLoaded(); 138 } 139 140 UserStyleSheetWatcher::UserStyleSheetWatcher(Profile* profile, 141 const base::FilePath& profile_path) 142 : RefcountedBrowserContextKeyedService(content::BrowserThread::UI), 143 profile_(profile), 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. 149 registrar_.Add(this, 150 content::NOTIFICATION_WEB_CONTENTS_RENDER_VIEW_HOST_CREATED, 151 content::NotificationService::AllBrowserContextsAndSources()); 152 } 153 154 UserStyleSheetWatcher::~UserStyleSheetWatcher() { 155 } 156 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)); 162 return; 163 } 164 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( 170 style_sheet_file, 171 false, 172 base::Bind(&UserStyleSheetLoader::NotifyPathChanged, 173 loader_.get()))) { 174 LOG(ERROR) << "Failed to setup watch for " << style_sheet_file.value(); 175 } 176 loader_->LoadStyleSheet(style_sheet_file); 177 } 178 } 179 180 GURL UserStyleSheetWatcher::user_style_sheet() const { 181 return loader_->user_style_sheet(); 182 } 183 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(); 193 } 194 } 195 196 void UserStyleSheetWatcher::ShutdownOnUIThread() { 197 registrar_.RemoveAll(); 198 } 199