1ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen// Copyright (c) 2011 The Chromium Authors. All rights reserved. 2c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// Use of this source code is governed by a BSD-style license that can be 3c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// found in the LICENSE file. 4c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 5c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#include "chrome/browser/user_style_sheet_watcher.h" 6c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 7c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#include "base/base64.h" 8c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#include "base/file_util.h" 9ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen#include "content/common/notification_service.h" 10ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen#include "content/common/notification_type.h" 11ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 12ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsenusing ::base::files::FilePathWatcher; 13c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 14c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochnamespace { 15c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 16c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// The subdirectory of the profile that contains the style sheet. 17c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochconst char kStyleSheetDir[] = "User StyleSheets"; 18c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// The filename of the stylesheet. 19c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochconst char kUserStyleSheetFile[] = "Custom.css"; 20c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 21c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch} // namespace 22c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 233345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick// UserStyleSheetLoader is responsible for loading the user style sheet on the 243345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick// file thread and sends a notification when the style sheet is loaded. It is 253345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick// a helper to UserStyleSheetWatcher. The reference graph is as follows: 263345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick// 273345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick// .-----------------------. owns .-----------------. 283345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick// | UserStyleSheetWatcher |----------->| FilePathWatcher | 293345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick// '-----------------------' '-----------------' 303345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick// | | 313345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick// V | 323345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick// .----------------------. | 333345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick// | UserStyleSheetLoader |<--------------------' 343345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick// '----------------------' 353345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick// 363345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick// FilePathWatcher's reference to UserStyleSheetLoader is used for delivering 373345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick// the change notifications. Since they happen asynchronously, 383345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick// UserStyleSheetWatcher and its FilePathWatcher may be destroyed while a 393345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick// callback to UserStyleSheetLoader is in progress, in which case the 403345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick// UserStyleSheetLoader object outlives the watchers. 413345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrickclass UserStyleSheetLoader : public FilePathWatcher::Delegate { 423345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick public: 433345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick UserStyleSheetLoader(); 443345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick virtual ~UserStyleSheetLoader() {} 453345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick 463345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick GURL user_style_sheet() const { 473345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick return user_style_sheet_; 483345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick } 49c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 503345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick // Load the user style sheet on the file thread and convert it to a 513345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick // base64 URL. Posts the base64 URL back to the UI thread. 523345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick void LoadStyleSheet(const FilePath& style_sheet_file); 533345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick 543345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick // Send out a notification if the stylesheet has already been loaded. 553345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick void NotifyLoaded(); 563345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick 573345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick // FilePathWatcher::Delegate interface 583345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick virtual void OnFilePathChanged(const FilePath& path); 59c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 603345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick private: 613345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick // Called on the UI thread after the stylesheet has loaded. 623345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick void SetStyleSheet(const GURL& url); 633345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick 643345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick // The user style sheet as a base64 data:// URL. 653345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick GURL user_style_sheet_; 663345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick 673345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick // Whether the stylesheet has been loaded. 683345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick bool has_loaded_; 693345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick 703345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick DISALLOW_COPY_AND_ASSIGN(UserStyleSheetLoader); 713345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick}; 723345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick 733345a6884c488ff3a535c2c9acdd33d74b37e311Iain MerrickUserStyleSheetLoader::UserStyleSheetLoader() 743345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick : has_loaded_(false) { 753345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick} 763345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick 773345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrickvoid UserStyleSheetLoader::NotifyLoaded() { 78c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if (has_loaded_) { 79c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch NotificationService::current()->Notify( 80c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch NotificationType::USER_STYLE_SHEET_UPDATED, 813345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick Source<UserStyleSheetLoader>(this), 82c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch NotificationService::NoDetails()); 83c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch } 84c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch} 85c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 863345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrickvoid UserStyleSheetLoader::OnFilePathChanged(const FilePath& path) { 873345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick LoadStyleSheet(path); 88c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch} 89c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 903345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrickvoid UserStyleSheetLoader::LoadStyleSheet(const FilePath& style_sheet_file) { 91731df977c0511bca2206b5f333555b1205ff1f43Iain Merrick DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 92c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch // We keep the user style sheet in a subdir so we can watch for changes 93c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch // to the file. 943345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick FilePath style_sheet_dir = style_sheet_file.DirName(); 95c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if (!file_util::DirectoryExists(style_sheet_dir)) { 96c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if (!file_util::CreateDirectory(style_sheet_dir)) 97c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return; 98c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch } 99c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch // Create the file if it doesn't exist. 1003345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick if (!file_util::PathExists(style_sheet_file)) 1013345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick file_util::WriteFile(style_sheet_file, "", 0); 102c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 103c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch std::string css; 1043345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick bool rv = file_util::ReadFileToString(style_sheet_file, &css); 105c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch GURL style_sheet_url; 106c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if (rv && !css.empty()) { 107c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch std::string css_base64; 108c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch rv = base::Base64Encode(css, &css_base64); 109c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if (rv) { 110c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch // WebKit knows about data urls, so convert the file to a data url. 111c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch const char kDataUrlPrefix[] = "data:text/css;charset=utf-8;base64,"; 112c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch style_sheet_url = GURL(kDataUrlPrefix + css_base64); 113c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch } 114c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch } 115731df977c0511bca2206b5f333555b1205ff1f43Iain Merrick BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, 1163345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick NewRunnableMethod(this, &UserStyleSheetLoader::SetStyleSheet, 117c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch style_sheet_url)); 118c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch} 119c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 1203345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrickvoid UserStyleSheetLoader::SetStyleSheet(const GURL& url) { 121731df977c0511bca2206b5f333555b1205ff1f43Iain Merrick DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 122c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 123c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch has_loaded_ = true; 124c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch user_style_sheet_ = url; 1253345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick NotifyLoaded(); 1263345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick} 1273345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick 1283345a6884c488ff3a535c2c9acdd33d74b37e311Iain MerrickUserStyleSheetWatcher::UserStyleSheetWatcher(const FilePath& profile_path) 1293345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick : profile_path_(profile_path), 1303345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick loader_(new UserStyleSheetLoader) { 1313345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick // Listen for when the first render view host is created. If we load 1323345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick // too fast, the first tab won't hear the notification and won't get 1333345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick // the user style sheet. 1343345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick registrar_.Add(this, NotificationType::RENDER_VIEW_HOST_CREATED_FOR_TAB, 1353345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick NotificationService::AllSources()); 1363345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick} 1373345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick 1383345a6884c488ff3a535c2c9acdd33d74b37e311Iain MerrickUserStyleSheetWatcher::~UserStyleSheetWatcher() { 1393345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick} 1403345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick 1413345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrickvoid UserStyleSheetWatcher::Init() { 1423345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick // Make sure we run on the file thread. 143731df977c0511bca2206b5f333555b1205ff1f43Iain Merrick if (!BrowserThread::CurrentlyOn(BrowserThread::FILE)) { 144731df977c0511bca2206b5f333555b1205ff1f43Iain Merrick BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, 1453345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick NewRunnableMethod(this, &UserStyleSheetWatcher::Init)); 1463345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick return; 1473345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick } 1483345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick 1493345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick if (!file_watcher_.get()) { 1503345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick file_watcher_.reset(new FilePathWatcher); 1513345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick FilePath style_sheet_file = profile_path_.AppendASCII(kStyleSheetDir) 1523345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick .AppendASCII(kUserStyleSheetFile); 153ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen if (!file_watcher_->Watch( 154ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen style_sheet_file, 155ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen loader_.get())) { 1563345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick LOG(ERROR) << "Failed to setup watch for " << style_sheet_file.value(); 157ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen } 1583345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick loader_->LoadStyleSheet(style_sheet_file); 1593345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick } 1603345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick} 1613345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick 1623345a6884c488ff3a535c2c9acdd33d74b37e311Iain MerrickGURL UserStyleSheetWatcher::user_style_sheet() const { 1633345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick return loader_->user_style_sheet(); 1643345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick} 1653345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick 1663345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrickvoid UserStyleSheetWatcher::Observe(NotificationType type, 1673345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick const NotificationSource& source, const NotificationDetails& details) { 1683345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick DCHECK(type == NotificationType::RENDER_VIEW_HOST_CREATED_FOR_TAB); 1693345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick loader_->NotifyLoaded(); 1703345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick registrar_.RemoveAll(); 171c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch} 172