blob: 75456dd219415a798318eaec1a044456f686de9e [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/custom_home_pages_table_model.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/i18n/rtl.h"
#include "base/prefs/pref_service.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/history/history_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_iterator.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/settings_window_manager.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h"
#include "chrome/grit/generated_resources.h"
#include "components/history/core/browser/history_service.h"
#include "components/url_formatter/url_formatter.h"
#include "content/public/browser/web_contents.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/models/table_model_observer.h"
#include "ui/gfx/codec/png_codec.h"
#include "url/gurl.h"
namespace {
// Checks whether the given URL should count as one of the "current" pages.
// Returns true for all pages except dev tools and settings.
bool ShouldAddPage(const GURL& url) {
if (url.is_empty())
return false;
if (url.SchemeIs(content::kChromeDevToolsScheme))
return false;
if (url.SchemeIs(content::kChromeUIScheme)) {
if (url.host() == chrome::kChromeUISettingsHost ||
url.host() == chrome::kChromeUISettingsFrameHost) {
return false;
}
// For a settings page, the path will start with "/settings" not "settings"
// so find() will return 1, not 0.
if (url.host() == chrome::kChromeUIUberHost &&
url.path().find(chrome::kChromeUISettingsHost) == 1) {
return false;
}
}
return true;
}
} // namespace
struct CustomHomePagesTableModel::Entry {
Entry() : task_id(base::CancelableTaskTracker::kBadTaskId) {}
// URL of the page.
GURL url;
// Page title. If this is empty, we'll display the URL as the entry.
base::string16 title;
// If not |base::CancelableTaskTracker::kBadTaskId|, indicates we're loading
// the title for the page.
base::CancelableTaskTracker::TaskId task_id;
};
CustomHomePagesTableModel::CustomHomePagesTableModel(Profile* profile)
: profile_(profile),
observer_(NULL),
num_outstanding_title_lookups_(0) {
}
CustomHomePagesTableModel::~CustomHomePagesTableModel() {
}
void CustomHomePagesTableModel::SetURLs(const std::vector<GURL>& urls) {
entries_.resize(urls.size());
for (size_t i = 0; i < urls.size(); ++i) {
entries_[i].url = urls[i];
entries_[i].title.erase();
}
LoadAllTitles();
}
/**
* Move a number of existing entries to a new position, reordering the table.
*
* We determine the range of elements affected by the move, save the moved
* elements, compact the remaining ones, and re-insert moved elements.
* Expects |index_list| to be ordered ascending.
*/
void CustomHomePagesTableModel::MoveURLs(int insert_before,
const std::vector<int>& index_list) {
if (index_list.empty()) return;
DCHECK(insert_before >= 0 && insert_before <= RowCount());
// The range of elements that needs to be reshuffled is [ |first|, |last| ).
int first = std::min(insert_before, index_list.front());
int last = std::max(insert_before, index_list.back() + 1);
// Save the dragged elements. Also, adjust insertion point if it is before a
// dragged element.
std::vector<Entry> moved_entries;
for (size_t i = 0; i < index_list.size(); ++i) {
moved_entries.push_back(entries_[index_list[i]]);
if (index_list[i] == insert_before)
insert_before++;
}
// Compact the range between beginning and insertion point, moving downwards.
size_t skip_count = 0;
for (int i = first; i < insert_before; ++i) {
if (skip_count < index_list.size() && index_list[skip_count] == i)
skip_count++;
else
entries_[i - skip_count] = entries_[i];
}
// Moving items down created a gap. We start compacting up after it.
first = insert_before;
insert_before -= skip_count;
// Now compact up for elements after the insertion point.
skip_count = 0;
for (int i = last - 1; i >= first; --i) {
if (skip_count < index_list.size() &&
index_list[index_list.size() - skip_count - 1] == i) {
skip_count++;
} else {
entries_[i + skip_count] = entries_[i];
}
}
// Insert moved elements.
std::copy(moved_entries.begin(), moved_entries.end(),
entries_.begin() + insert_before);
// Possibly large change, so tell the view to just rebuild itself.
if (observer_)
observer_->OnModelChanged();
}
void CustomHomePagesTableModel::AddWithoutNotification(
int index, const GURL& url) {
DCHECK(index >= 0 && index <= RowCount());
entries_.insert(entries_.begin() + static_cast<size_t>(index), Entry());
entries_[index].url = url;
}
void CustomHomePagesTableModel::Add(int index, const GURL& url) {
AddWithoutNotification(index, url);
LoadTitle(&(entries_[index]));
if (observer_)
observer_->OnItemsAdded(index, 1);
}
void CustomHomePagesTableModel::RemoveWithoutNotification(int index) {
DCHECK(index >= 0 && index < RowCount());
Entry* entry = &(entries_[index]);
// Cancel any pending load requests now so we don't deref a bogus pointer when
// we get the loaded notification.
if (entry->task_id != base::CancelableTaskTracker::kBadTaskId) {
task_tracker_.TryCancel(entry->task_id);
entry->task_id = base::CancelableTaskTracker::kBadTaskId;
}
entries_.erase(entries_.begin() + static_cast<size_t>(index));
}
void CustomHomePagesTableModel::Remove(int index) {
RemoveWithoutNotification(index);
if (observer_)
observer_->OnItemsRemoved(index, 1);
}
void CustomHomePagesTableModel::SetToCurrentlyOpenPages() {
// Remove the current entries.
while (RowCount())
RemoveWithoutNotification(0);
// Add tabs from appropriate browser windows.
int add_index = 0;
for (chrome::BrowserIterator it; !it.done(); it.Next()) {
Browser* browser = *it;
if (!ShouldIncludeBrowser(browser))
continue;
for (int tab_index = 0;
tab_index < browser->tab_strip_model()->count();
++tab_index) {
const GURL url =
browser->tab_strip_model()->GetWebContentsAt(tab_index)->GetURL();
if (ShouldAddPage(url))
AddWithoutNotification(add_index++, url);
}
}
LoadAllTitles();
}
std::vector<GURL> CustomHomePagesTableModel::GetURLs() {
std::vector<GURL> urls(entries_.size());
for (size_t i = 0; i < entries_.size(); ++i)
urls[i] = entries_[i].url;
return urls;
}
int CustomHomePagesTableModel::RowCount() {
return static_cast<int>(entries_.size());
}
base::string16 CustomHomePagesTableModel::GetText(int row, int column_id) {
DCHECK(column_id == 0);
DCHECK(row >= 0 && row < RowCount());
return entries_[row].title.empty() ? FormattedURL(row) : entries_[row].title;
}
base::string16 CustomHomePagesTableModel::GetTooltip(int row) {
return entries_[row].title.empty() ? base::string16() :
l10n_util::GetStringFUTF16(IDS_OPTIONS_STARTUP_PAGE_TOOLTIP,
entries_[row].title, FormattedURL(row));
}
void CustomHomePagesTableModel::SetObserver(ui::TableModelObserver* observer) {
observer_ = observer;
}
bool CustomHomePagesTableModel::ShouldIncludeBrowser(Browser* browser) {
// Do not include incognito browsers.
if (browser->profile() != profile_)
return false;
// Do not include the Settings window.
if (chrome::SettingsWindowManager::GetInstance()->IsSettingsBrowser(
browser)) {
return false;
}
return true;
}
void CustomHomePagesTableModel::LoadTitle(Entry* entry) {
history::HistoryService* history_service =
HistoryServiceFactory::GetForProfile(profile_,
ServiceAccessType::EXPLICIT_ACCESS);
if (history_service) {
entry->task_id = history_service->QueryURL(
entry->url,
false,
base::Bind(&CustomHomePagesTableModel::OnGotTitle,
base::Unretained(this),
entry->url,
false),
&task_tracker_);
}
}
void CustomHomePagesTableModel::LoadAllTitles() {
history::HistoryService* history_service =
HistoryServiceFactory::GetForProfile(profile_,
ServiceAccessType::EXPLICIT_ACCESS);
// It's possible for multiple LoadAllTitles() queries to be inflight we want
// to make sure everything is resolved before updating the observer or we risk
// getting rendering glitches.
num_outstanding_title_lookups_ += entries_.size();
for (Entry& entry : entries_) {
if (history_service) {
entry.task_id = history_service->QueryURL(
entry.url,
false,
base::Bind(&CustomHomePagesTableModel::OnGotOneOfManyTitles,
base::Unretained(this),
entry.url),
&task_tracker_);
}
}
if (entries_.empty())
observer_->OnModelChanged();
}
void CustomHomePagesTableModel::OnGotOneOfManyTitles(const GURL& entry_url,
bool found_url,
const history::URLRow& row,
const history::VisitVector& visits) {
OnGotTitle(entry_url, false, found_url, row, visits);
DCHECK_GE(num_outstanding_title_lookups_, 1);
if (--num_outstanding_title_lookups_ == 0 && observer_)
observer_->OnModelChanged();
}
void CustomHomePagesTableModel::OnGotTitle(const GURL& entry_url,
bool observable,
bool found_url,
const history::URLRow& row,
const history::VisitVector& visits) {
Entry* entry = NULL;
size_t entry_index = 0;
for (size_t i = 0; i < entries_.size(); ++i) {
if (entries_[i].url == entry_url) {
entry = &entries_[i];
entry_index = i;
break;
}
}
if (!entry) {
// The URLs changed before we were called back.
return;
}
entry->task_id = base::CancelableTaskTracker::kBadTaskId;
if (found_url && !row.title().empty()) {
entry->title = row.title();
if (observer_ && observable)
observer_->OnItemsChanged(static_cast<int>(entry_index), 1);
}
}
base::string16 CustomHomePagesTableModel::FormattedURL(int row) const {
std::string languages =
profile_->GetPrefs()->GetString(prefs::kAcceptLanguages);
base::string16 url = url_formatter::FormatUrl(entries_[row].url, languages);
url = base::i18n::GetDisplayStringInLTRDirectionality(url);
return url;
}