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(); 44 virtual ~UserStyleSheetLoader() {} 45 46 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 73 UserStyleSheetLoader::UserStyleSheetLoader() 74 : has_loaded_(false) { 75 } 76 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 86 void UserStyleSheetLoader::OnFilePathChanged(const FilePath& path) { 87 LoadStyleSheet(path); 88 } 89 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 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 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 138 UserStyleSheetWatcher::~UserStyleSheetWatcher() { 139 } 140 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 162 GURL UserStyleSheetWatcher::user_style_sheet() const { 163 return loader_->user_style_sheet(); 164 } 165 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