Home | History | Annotate | Download | only in browser
      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/custom_home_pages_table_model.h"
      6 
      7 #include "base/bind.h"
      8 #include "base/bind_helpers.h"
      9 #include "base/i18n/rtl.h"
     10 #include "base/prefs/pref_service.h"
     11 #include "base/strings/utf_string_conversions.h"
     12 #include "chrome/browser/history/history_service.h"
     13 #include "chrome/browser/history/history_service_factory.h"
     14 #include "chrome/browser/profiles/profile.h"
     15 #include "chrome/browser/ui/browser.h"
     16 #include "chrome/browser/ui/browser_iterator.h"
     17 #include "chrome/browser/ui/browser_list.h"
     18 #include "chrome/browser/ui/tabs/tab_strip_model.h"
     19 #include "chrome/common/pref_names.h"
     20 #include "chrome/common/url_constants.h"
     21 #include "chrome/grit/generated_resources.h"
     22 #include "content/public/browser/web_contents.h"
     23 #include "net/base/net_util.h"
     24 #include "ui/base/l10n/l10n_util.h"
     25 #include "ui/base/models/table_model_observer.h"
     26 #include "ui/gfx/codec/png_codec.h"
     27 #include "url/gurl.h"
     28 
     29 namespace {
     30 
     31 // Checks whether the given URL should count as one of the "current" pages.
     32 // Returns true for all pages except dev tools and settings.
     33 bool ShouldAddPage(const GURL& url) {
     34   if (url.is_empty())
     35     return false;
     36 
     37   if (url.SchemeIs(content::kChromeDevToolsScheme))
     38     return false;
     39 
     40   if (url.SchemeIs(content::kChromeUIScheme)) {
     41     if (url.host() == chrome::kChromeUISettingsHost)
     42       return false;
     43 
     44     // For a settings page, the path will start with "/settings" not "settings"
     45     // so find() will return 1, not 0.
     46     if (url.host() == chrome::kChromeUIUberHost &&
     47         url.path().find(chrome::kChromeUISettingsHost) == 1) {
     48       return false;
     49     }
     50   }
     51 
     52   return true;
     53 }
     54 
     55 }  // namespace
     56 
     57 struct CustomHomePagesTableModel::Entry {
     58   Entry() : task_id(base::CancelableTaskTracker::kBadTaskId) {}
     59 
     60   // URL of the page.
     61   GURL url;
     62 
     63   // Page title.  If this is empty, we'll display the URL as the entry.
     64   base::string16 title;
     65 
     66   // If not |base::CancelableTaskTracker::kBadTaskId|, indicates we're loading
     67   // the title for the page.
     68   base::CancelableTaskTracker::TaskId task_id;
     69 };
     70 
     71 CustomHomePagesTableModel::CustomHomePagesTableModel(Profile* profile)
     72     : profile_(profile),
     73       observer_(NULL) {
     74 }
     75 
     76 CustomHomePagesTableModel::~CustomHomePagesTableModel() {
     77 }
     78 
     79 void CustomHomePagesTableModel::SetURLs(const std::vector<GURL>& urls) {
     80   entries_.resize(urls.size());
     81   for (size_t i = 0; i < urls.size(); ++i) {
     82     entries_[i].url = urls[i];
     83     entries_[i].title.erase();
     84     LoadTitle(&(entries_[i]));
     85   }
     86   // Complete change, so tell the view to just rebuild itself.
     87   if (observer_)
     88     observer_->OnModelChanged();
     89 }
     90 
     91 /**
     92  * Move a number of existing entries to a new position, reordering the table.
     93  *
     94  * We determine the range of elements affected by the move, save the moved
     95  * elements, compact the remaining ones, and re-insert moved elements.
     96  * Expects |index_list| to be ordered ascending.
     97  */
     98 void CustomHomePagesTableModel::MoveURLs(int insert_before,
     99                                          const std::vector<int>& index_list) {
    100   if (index_list.empty()) return;
    101   DCHECK(insert_before >= 0 && insert_before <= RowCount());
    102 
    103   // The range of elements that needs to be reshuffled is [ |first|, |last| ).
    104   int first = std::min(insert_before, index_list.front());
    105   int last = std::max(insert_before, index_list.back() + 1);
    106 
    107   // Save the dragged elements. Also, adjust insertion point if it is before a
    108   // dragged element.
    109   std::vector<Entry> moved_entries;
    110   for (size_t i = 0; i < index_list.size(); ++i) {
    111     moved_entries.push_back(entries_[index_list[i]]);
    112     if (index_list[i] == insert_before)
    113       insert_before++;
    114   }
    115 
    116   // Compact the range between beginning and insertion point, moving downwards.
    117   size_t skip_count = 0;
    118   for (int i = first; i < insert_before; ++i) {
    119     if (skip_count < index_list.size() && index_list[skip_count] == i)
    120       skip_count++;
    121     else
    122       entries_[i - skip_count] = entries_[i];
    123   }
    124 
    125   // Moving items down created a gap. We start compacting up after it.
    126   first = insert_before;
    127   insert_before -= skip_count;
    128 
    129   // Now compact up for elements after the insertion point.
    130   skip_count = 0;
    131   for (int i = last - 1; i >= first; --i) {
    132     if (skip_count < index_list.size() &&
    133         index_list[index_list.size() - skip_count - 1] == i) {
    134       skip_count++;
    135     } else {
    136       entries_[i + skip_count] = entries_[i];
    137     }
    138   }
    139 
    140   // Insert moved elements.
    141   std::copy(moved_entries.begin(), moved_entries.end(),
    142       entries_.begin() + insert_before);
    143 
    144   // Possibly large change, so tell the view to just rebuild itself.
    145   if (observer_)
    146     observer_->OnModelChanged();
    147 }
    148 
    149 void CustomHomePagesTableModel::Add(int index, const GURL& url) {
    150   DCHECK(index >= 0 && index <= RowCount());
    151   entries_.insert(entries_.begin() + static_cast<size_t>(index), Entry());
    152   entries_[index].url = url;
    153   LoadTitle(&(entries_[index]));
    154   if (observer_)
    155     observer_->OnItemsAdded(index, 1);
    156 }
    157 
    158 void CustomHomePagesTableModel::Remove(int index) {
    159   DCHECK(index >= 0 && index < RowCount());
    160   Entry* entry = &(entries_[index]);
    161   // Cancel any pending load requests now so we don't deref a bogus pointer when
    162   // we get the loaded notification.
    163   if (entry->task_id != base::CancelableTaskTracker::kBadTaskId) {
    164     task_tracker_.TryCancel(entry->task_id);
    165     entry->task_id = base::CancelableTaskTracker::kBadTaskId;
    166   }
    167   entries_.erase(entries_.begin() + static_cast<size_t>(index));
    168   if (observer_)
    169     observer_->OnItemsRemoved(index, 1);
    170 }
    171 
    172 void CustomHomePagesTableModel::SetToCurrentlyOpenPages() {
    173   // Remove the current entries.
    174   while (RowCount())
    175     Remove(0);
    176 
    177   // And add all tabs for all open browsers with our profile.
    178   int add_index = 0;
    179   for (chrome::BrowserIterator it; !it.done(); it.Next()) {
    180     Browser* browser = *it;
    181     if (browser->profile() != profile_)
    182       continue;  // Skip incognito browsers.
    183 
    184     for (int tab_index = 0;
    185          tab_index < browser->tab_strip_model()->count();
    186          ++tab_index) {
    187       const GURL url =
    188           browser->tab_strip_model()->GetWebContentsAt(tab_index)->GetURL();
    189       if (ShouldAddPage(url))
    190         Add(add_index++, url);
    191     }
    192   }
    193 }
    194 
    195 std::vector<GURL> CustomHomePagesTableModel::GetURLs() {
    196   std::vector<GURL> urls(entries_.size());
    197   for (size_t i = 0; i < entries_.size(); ++i)
    198     urls[i] = entries_[i].url;
    199   return urls;
    200 }
    201 
    202 int CustomHomePagesTableModel::RowCount() {
    203   return static_cast<int>(entries_.size());
    204 }
    205 
    206 base::string16 CustomHomePagesTableModel::GetText(int row, int column_id) {
    207   DCHECK(column_id == 0);
    208   DCHECK(row >= 0 && row < RowCount());
    209   return entries_[row].title.empty() ? FormattedURL(row) : entries_[row].title;
    210 }
    211 
    212 base::string16 CustomHomePagesTableModel::GetTooltip(int row) {
    213   return entries_[row].title.empty() ? base::string16() :
    214       l10n_util::GetStringFUTF16(IDS_OPTIONS_STARTUP_PAGE_TOOLTIP,
    215                                  entries_[row].title, FormattedURL(row));
    216 }
    217 
    218 void CustomHomePagesTableModel::SetObserver(ui::TableModelObserver* observer) {
    219   observer_ = observer;
    220 }
    221 
    222 void CustomHomePagesTableModel::LoadTitle(Entry* entry) {
    223     HistoryService* history_service = HistoryServiceFactory::GetForProfile(
    224         profile_, Profile::EXPLICIT_ACCESS);
    225   if (history_service) {
    226     entry->task_id = history_service->QueryURL(
    227         entry->url,
    228         false,
    229         base::Bind(&CustomHomePagesTableModel::OnGotTitle,
    230                    base::Unretained(this),
    231                    entry->url),
    232         &task_tracker_);
    233   }
    234 }
    235 
    236 void CustomHomePagesTableModel::OnGotTitle(const GURL& entry_url,
    237                                            bool found_url,
    238                                            const history::URLRow& row,
    239                                            const history::VisitVector& visits) {
    240   Entry* entry = NULL;
    241   size_t entry_index = 0;
    242   for (size_t i = 0; i < entries_.size(); ++i) {
    243     if (entries_[i].url == entry_url) {
    244       entry = &entries_[i];
    245       entry_index = i;
    246       break;
    247     }
    248   }
    249   if (!entry) {
    250     // The URLs changed before we were called back.
    251     return;
    252   }
    253   entry->task_id = base::CancelableTaskTracker::kBadTaskId;
    254   if (found_url && !row.title().empty()) {
    255     entry->title = row.title();
    256     if (observer_)
    257       observer_->OnItemsChanged(static_cast<int>(entry_index), 1);
    258   }
    259 }
    260 
    261 base::string16 CustomHomePagesTableModel::FormattedURL(int row) const {
    262   std::string languages =
    263       profile_->GetPrefs()->GetString(prefs::kAcceptLanguages);
    264   base::string16 url = net::FormatUrl(entries_[row].url, languages);
    265   url = base::i18n::GetDisplayStringInLTRDirectionality(url);
    266   return url;
    267 }
    268