| 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 |