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