Home | History | Annotate | Download | only in browser
      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