// Copyright (c) 2011 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/ui/webui/most_visited_handler.h" #include <set> #include "base/callback.h" #include "base/command_line.h" #include "base/md5.h" #include "base/memory/scoped_vector.h" #include "base/memory/singleton.h" #include "base/string16.h" #include "base/string_number_conversions.h" #include "base/threading/thread.h" #include "base/utf_string_conversions.h" #include "base/values.h" #include "chrome/browser/history/page_usage_data.h" #include "chrome/browser/history/top_sites.h" #include "chrome/browser/metrics/user_metrics.h" #include "chrome/browser/prefs/pref_service.h" #include "chrome/browser/prefs/scoped_user_pref_update.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/webui/chrome_url_data_manager.h" #include "chrome/browser/ui/webui/favicon_source.h" #include "chrome/browser/ui/webui/new_tab_ui.h" #include "chrome/browser/ui/webui/thumbnail_source.h" #include "chrome/common/pref_names.h" #include "chrome/common/url_constants.h" #include "content/browser/browser_thread.h" #include "content/common/notification_source.h" #include "content/common/notification_type.h" #include "googleurl/src/gurl.h" #include "grit/chromium_strings.h" #include "grit/generated_resources.h" #include "grit/locale_settings.h" #include "ui/base/l10n/l10n_util.h" namespace { // The number of most visited pages we show. const size_t kMostVisitedPages = 8; // The number of days of history we consider for most visited entries. const int kMostVisitedScope = 90; } // namespace // This struct is used when getting the pre-populated pages in case the user // hasn't filled up his most visited pages. struct MostVisitedHandler::MostVisitedPage { string16 title; GURL url; GURL thumbnail_url; GURL favicon_url; }; MostVisitedHandler::MostVisitedHandler() : got_first_most_visited_request_(false) { } MostVisitedHandler::~MostVisitedHandler() { } WebUIMessageHandler* MostVisitedHandler::Attach(WebUI* web_ui) { Profile* profile = web_ui->GetProfile(); // Set up our sources for thumbnail and favicon data. ThumbnailSource* thumbnail_src = new ThumbnailSource(profile); profile->GetChromeURLDataManager()->AddDataSource(thumbnail_src); profile->GetChromeURLDataManager()->AddDataSource(new FaviconSource(profile)); // Get notifications when history is cleared. registrar_.Add(this, NotificationType::HISTORY_URLS_DELETED, Source<Profile>(profile)); WebUIMessageHandler* result = WebUIMessageHandler::Attach(web_ui); // We pre-emptively make a fetch for the most visited pages so we have the // results sooner. StartQueryForMostVisited(); return result; } void MostVisitedHandler::RegisterMessages() { // Register ourselves as the handler for the "mostvisited" message from // Javascript. web_ui_->RegisterMessageCallback("getMostVisited", NewCallback(this, &MostVisitedHandler::HandleGetMostVisited)); // Register ourselves for any most-visited item blacklisting. web_ui_->RegisterMessageCallback("blacklistURLFromMostVisited", NewCallback(this, &MostVisitedHandler::HandleBlacklistURL)); web_ui_->RegisterMessageCallback("removeURLsFromMostVisitedBlacklist", NewCallback(this, &MostVisitedHandler::HandleRemoveURLsFromBlacklist)); web_ui_->RegisterMessageCallback("clearMostVisitedURLsBlacklist", NewCallback(this, &MostVisitedHandler::HandleClearBlacklist)); // Register ourself for pinned URL messages. web_ui_->RegisterMessageCallback("addPinnedURL", NewCallback(this, &MostVisitedHandler::HandleAddPinnedURL)); web_ui_->RegisterMessageCallback("removePinnedURL", NewCallback(this, &MostVisitedHandler::HandleRemovePinnedURL)); } void MostVisitedHandler::HandleGetMostVisited(const ListValue* args) { if (!got_first_most_visited_request_) { // If our intial data is already here, return it. SendPagesValue(); got_first_most_visited_request_ = true; } else { StartQueryForMostVisited(); } } void MostVisitedHandler::SendPagesValue() { if (pages_value_.get()) { Profile* profile = web_ui_->GetProfile(); const DictionaryValue* url_blacklist = profile->GetPrefs()->GetDictionary(prefs::kNTPMostVisitedURLsBlacklist); bool has_blacklisted_urls = !url_blacklist->empty(); history::TopSites* ts = profile->GetTopSites(); if (ts) has_blacklisted_urls = ts->HasBlacklistedItems(); FundamentalValue first_run(IsFirstRun()); FundamentalValue has_blacklisted_urls_value(has_blacklisted_urls); web_ui_->CallJavascriptFunction("mostVisitedPages", *(pages_value_.get()), first_run, has_blacklisted_urls_value); pages_value_.reset(); } } void MostVisitedHandler::StartQueryForMostVisited() { // Use TopSites. history::TopSites* ts = web_ui_->GetProfile()->GetTopSites(); if (ts) { ts->GetMostVisitedURLs( &topsites_consumer_, NewCallback(this, &MostVisitedHandler::OnMostVisitedURLsAvailable)); } } void MostVisitedHandler::HandleBlacklistURL(const ListValue* args) { std::string url = UTF16ToUTF8(ExtractStringValue(args)); BlacklistURL(GURL(url)); } void MostVisitedHandler::HandleRemoveURLsFromBlacklist(const ListValue* args) { DCHECK(args->GetSize() != 0); for (ListValue::const_iterator iter = args->begin(); iter != args->end(); ++iter) { std::string url; bool r = (*iter)->GetAsString(&url); if (!r) { NOTREACHED(); return; } UserMetrics::RecordAction(UserMetricsAction("MostVisited_UrlRemoved"), web_ui_->GetProfile()); history::TopSites* ts = web_ui_->GetProfile()->GetTopSites(); if (ts) ts->RemoveBlacklistedURL(GURL(url)); } } void MostVisitedHandler::HandleClearBlacklist(const ListValue* args) { UserMetrics::RecordAction(UserMetricsAction("MostVisited_BlacklistCleared"), web_ui_->GetProfile()); history::TopSites* ts = web_ui_->GetProfile()->GetTopSites(); if (ts) ts->ClearBlacklistedURLs(); } void MostVisitedHandler::HandleAddPinnedURL(const ListValue* args) { DCHECK_EQ(5U, args->GetSize()) << "Wrong number of params to addPinnedURL"; MostVisitedPage mvp; std::string tmp_string; string16 tmp_string16; int index; bool r = args->GetString(0, &tmp_string); DCHECK(r) << "Missing URL in addPinnedURL from the NTP Most Visited."; mvp.url = GURL(tmp_string); r = args->GetString(1, &tmp_string16); DCHECK(r) << "Missing title in addPinnedURL from the NTP Most Visited."; mvp.title = tmp_string16; r = args->GetString(2, &tmp_string); DCHECK(r) << "Failed to read the favicon URL in addPinnedURL from the NTP " << "Most Visited."; if (!tmp_string.empty()) mvp.favicon_url = GURL(tmp_string); r = args->GetString(3, &tmp_string); DCHECK(r) << "Failed to read the thumbnail URL in addPinnedURL from the NTP " << "Most Visited."; if (!tmp_string.empty()) mvp.thumbnail_url = GURL(tmp_string); r = args->GetString(4, &tmp_string); DCHECK(r) << "Missing index in addPinnedURL from the NTP Most Visited."; base::StringToInt(tmp_string, &index); AddPinnedURL(mvp, index); } void MostVisitedHandler::AddPinnedURL(const MostVisitedPage& page, int index) { history::TopSites* ts = web_ui_->GetProfile()->GetTopSites(); if (ts) ts->AddPinnedURL(page.url, index); } void MostVisitedHandler::HandleRemovePinnedURL(const ListValue* args) { std::string url = UTF16ToUTF8(ExtractStringValue(args)); RemovePinnedURL(GURL(url)); } void MostVisitedHandler::RemovePinnedURL(const GURL& url) { history::TopSites* ts = web_ui_->GetProfile()->GetTopSites(); if (ts) ts->RemovePinnedURL(url); } bool MostVisitedHandler::GetPinnedURLAtIndex(int index, MostVisitedPage* page) { // This iterates over all the pinned URLs. It might seem like it is worth // having a map from the index to the item but the number of items is limited // to the number of items the most visited section is showing on the NTP so // this will be fast enough for now. PrefService* prefs = web_ui_->GetProfile()->GetPrefs(); const DictionaryValue* pinned_urls = prefs->GetDictionary(prefs::kNTPMostVisitedPinnedURLs); for (DictionaryValue::key_iterator it = pinned_urls->begin_keys(); it != pinned_urls->end_keys(); ++it) { Value* value; if (pinned_urls->GetWithoutPathExpansion(*it, &value)) { if (!value->IsType(DictionaryValue::TYPE_DICTIONARY)) { // Moved on to TopSites and now going back. DictionaryPrefUpdate update(prefs, prefs::kNTPMostVisitedPinnedURLs); update.Get()->Clear(); return false; } int dict_index; const DictionaryValue* dict = static_cast<DictionaryValue*>(value); if (dict->GetInteger("index", &dict_index) && dict_index == index) { // The favicon and thumbnail URLs may be empty. std::string tmp_string; if (dict->GetString("faviconUrl", &tmp_string)) page->favicon_url = GURL(tmp_string); if (dict->GetString("thumbnailUrl", &tmp_string)) page->thumbnail_url = GURL(tmp_string); if (dict->GetString("url", &tmp_string)) page->url = GURL(tmp_string); else return false; return dict->GetString("title", &page->title); } } else { NOTREACHED() << "DictionaryValue iterators are filthy liars."; } } return false; } void MostVisitedHandler::SetPagesValueFromTopSites( const history::MostVisitedURLList& data) { pages_value_.reset(new ListValue); for (size_t i = 0; i < data.size(); i++) { const history::MostVisitedURL& url = data[i]; DictionaryValue* page_value = new DictionaryValue(); if (url.url.is_empty()) { page_value->SetBoolean("filler", true); pages_value_->Append(page_value); continue; } NewTabUI::SetURLTitleAndDirection(page_value, url.title, url.url); if (!url.favicon_url.is_empty()) page_value->SetString("faviconUrl", url.favicon_url.spec()); // Special case for prepopulated pages: thumbnailUrl is different from url. if (url.url.spec() == l10n_util::GetStringUTF8(IDS_CHROME_WELCOME_URL)) { page_value->SetString("thumbnailUrl", "chrome://theme/IDR_NEWTAB_CHROME_WELCOME_PAGE_THUMBNAIL"); } else if (url.url.spec() == l10n_util::GetStringUTF8(IDS_THEMES_GALLERY_URL)) { page_value->SetString("thumbnailUrl", "chrome://theme/IDR_NEWTAB_THEMES_GALLERY_THUMBNAIL"); } history::TopSites* ts = web_ui_->GetProfile()->GetTopSites(); if (ts && ts->IsURLPinned(url.url)) page_value->SetBoolean("pinned", true); pages_value_->Append(page_value); } } void MostVisitedHandler::OnMostVisitedURLsAvailable( const history::MostVisitedURLList& data) { SetPagesValueFromTopSites(data); if (got_first_most_visited_request_) { SendPagesValue(); } } bool MostVisitedHandler::IsFirstRun() { // If we found no pages we treat this as the first run. bool first_run = NewTabUI::NewTabHTMLSource::first_run() && pages_value_->GetSize() == MostVisitedHandler::GetPrePopulatedPages().size(); // but first_run should only be true once. NewTabUI::NewTabHTMLSource::set_first_run(false); return first_run; } // static const std::vector<MostVisitedHandler::MostVisitedPage>& MostVisitedHandler::GetPrePopulatedPages() { // TODO(arv): This needs to get the data from some configurable place. // http://crbug.com/17630 static std::vector<MostVisitedPage> pages; if (pages.empty()) { MostVisitedPage welcome_page = { l10n_util::GetStringUTF16(IDS_NEW_TAB_CHROME_WELCOME_PAGE_TITLE), GURL(l10n_util::GetStringUTF8(IDS_CHROME_WELCOME_URL)), GURL("chrome://theme/IDR_NEWTAB_CHROME_WELCOME_PAGE_THUMBNAIL"), GURL("chrome://theme/IDR_NEWTAB_CHROME_WELCOME_PAGE_FAVICON")}; pages.push_back(welcome_page); MostVisitedPage gallery_page = { l10n_util::GetStringUTF16(IDS_NEW_TAB_THEMES_GALLERY_PAGE_TITLE), GURL(l10n_util::GetStringUTF8(IDS_THEMES_GALLERY_URL)), GURL("chrome://theme/IDR_NEWTAB_THEMES_GALLERY_THUMBNAIL"), GURL("chrome://theme/IDR_NEWTAB_THEMES_GALLERY_FAVICON")}; pages.push_back(gallery_page); } return pages; } void MostVisitedHandler::Observe(NotificationType type, const NotificationSource& source, const NotificationDetails& details) { if (type != NotificationType::HISTORY_URLS_DELETED) { NOTREACHED(); return; } // Some URLs were deleted from history. Reload the most visited list. HandleGetMostVisited(NULL); } void MostVisitedHandler::BlacklistURL(const GURL& url) { history::TopSites* ts = web_ui_->GetProfile()->GetTopSites(); if (ts) ts->AddBlacklistedURL(url); } std::string MostVisitedHandler::GetDictionaryKeyForURL(const std::string& url) { return MD5String(url); } // static void MostVisitedHandler::RegisterUserPrefs(PrefService* prefs) { prefs->RegisterDictionaryPref(prefs::kNTPMostVisitedURLsBlacklist); prefs->RegisterDictionaryPref(prefs::kNTPMostVisitedPinnedURLs); } // static std::vector<GURL> MostVisitedHandler::GetPrePopulatedUrls() { const std::vector<MostVisitedPage> pages = MostVisitedHandler::GetPrePopulatedPages(); std::vector<GURL> page_urls; for (size_t i = 0; i < pages.size(); ++i) page_urls.push_back(pages[i].url); return page_urls; }