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/ui/webui/most_visited_handler.h" 6 7 #include <set> 8 9 #include "base/callback.h" 10 #include "base/command_line.h" 11 #include "base/md5.h" 12 #include "base/memory/scoped_vector.h" 13 #include "base/memory/singleton.h" 14 #include "base/string16.h" 15 #include "base/string_number_conversions.h" 16 #include "base/threading/thread.h" 17 #include "base/utf_string_conversions.h" 18 #include "base/values.h" 19 #include "chrome/browser/history/page_usage_data.h" 20 #include "chrome/browser/history/top_sites.h" 21 #include "chrome/browser/metrics/user_metrics.h" 22 #include "chrome/browser/prefs/pref_service.h" 23 #include "chrome/browser/prefs/scoped_user_pref_update.h" 24 #include "chrome/browser/profiles/profile.h" 25 #include "chrome/browser/ui/webui/chrome_url_data_manager.h" 26 #include "chrome/browser/ui/webui/favicon_source.h" 27 #include "chrome/browser/ui/webui/new_tab_ui.h" 28 #include "chrome/browser/ui/webui/thumbnail_source.h" 29 #include "chrome/common/pref_names.h" 30 #include "chrome/common/url_constants.h" 31 #include "content/browser/browser_thread.h" 32 #include "content/common/notification_source.h" 33 #include "content/common/notification_type.h" 34 #include "googleurl/src/gurl.h" 35 #include "grit/chromium_strings.h" 36 #include "grit/generated_resources.h" 37 #include "grit/locale_settings.h" 38 #include "ui/base/l10n/l10n_util.h" 39 40 namespace { 41 42 // The number of most visited pages we show. 43 const size_t kMostVisitedPages = 8; 44 45 // The number of days of history we consider for most visited entries. 46 const int kMostVisitedScope = 90; 47 48 } // namespace 49 50 // This struct is used when getting the pre-populated pages in case the user 51 // hasn't filled up his most visited pages. 52 struct MostVisitedHandler::MostVisitedPage { 53 string16 title; 54 GURL url; 55 GURL thumbnail_url; 56 GURL favicon_url; 57 }; 58 59 MostVisitedHandler::MostVisitedHandler() 60 : got_first_most_visited_request_(false) { 61 } 62 63 MostVisitedHandler::~MostVisitedHandler() { 64 } 65 66 WebUIMessageHandler* MostVisitedHandler::Attach(WebUI* web_ui) { 67 Profile* profile = web_ui->GetProfile(); 68 // Set up our sources for thumbnail and favicon data. 69 ThumbnailSource* thumbnail_src = new ThumbnailSource(profile); 70 profile->GetChromeURLDataManager()->AddDataSource(thumbnail_src); 71 72 profile->GetChromeURLDataManager()->AddDataSource(new FaviconSource(profile)); 73 74 // Get notifications when history is cleared. 75 registrar_.Add(this, NotificationType::HISTORY_URLS_DELETED, 76 Source<Profile>(profile)); 77 78 WebUIMessageHandler* result = WebUIMessageHandler::Attach(web_ui); 79 80 // We pre-emptively make a fetch for the most visited pages so we have the 81 // results sooner. 82 StartQueryForMostVisited(); 83 return result; 84 } 85 86 void MostVisitedHandler::RegisterMessages() { 87 // Register ourselves as the handler for the "mostvisited" message from 88 // Javascript. 89 web_ui_->RegisterMessageCallback("getMostVisited", 90 NewCallback(this, &MostVisitedHandler::HandleGetMostVisited)); 91 92 // Register ourselves for any most-visited item blacklisting. 93 web_ui_->RegisterMessageCallback("blacklistURLFromMostVisited", 94 NewCallback(this, &MostVisitedHandler::HandleBlacklistURL)); 95 web_ui_->RegisterMessageCallback("removeURLsFromMostVisitedBlacklist", 96 NewCallback(this, &MostVisitedHandler::HandleRemoveURLsFromBlacklist)); 97 web_ui_->RegisterMessageCallback("clearMostVisitedURLsBlacklist", 98 NewCallback(this, &MostVisitedHandler::HandleClearBlacklist)); 99 100 // Register ourself for pinned URL messages. 101 web_ui_->RegisterMessageCallback("addPinnedURL", 102 NewCallback(this, &MostVisitedHandler::HandleAddPinnedURL)); 103 web_ui_->RegisterMessageCallback("removePinnedURL", 104 NewCallback(this, &MostVisitedHandler::HandleRemovePinnedURL)); 105 } 106 107 void MostVisitedHandler::HandleGetMostVisited(const ListValue* args) { 108 if (!got_first_most_visited_request_) { 109 // If our intial data is already here, return it. 110 SendPagesValue(); 111 got_first_most_visited_request_ = true; 112 } else { 113 StartQueryForMostVisited(); 114 } 115 } 116 117 void MostVisitedHandler::SendPagesValue() { 118 if (pages_value_.get()) { 119 Profile* profile = web_ui_->GetProfile(); 120 const DictionaryValue* url_blacklist = 121 profile->GetPrefs()->GetDictionary(prefs::kNTPMostVisitedURLsBlacklist); 122 bool has_blacklisted_urls = !url_blacklist->empty(); 123 history::TopSites* ts = profile->GetTopSites(); 124 if (ts) 125 has_blacklisted_urls = ts->HasBlacklistedItems(); 126 FundamentalValue first_run(IsFirstRun()); 127 FundamentalValue has_blacklisted_urls_value(has_blacklisted_urls); 128 web_ui_->CallJavascriptFunction("mostVisitedPages", 129 *(pages_value_.get()), 130 first_run, 131 has_blacklisted_urls_value); 132 pages_value_.reset(); 133 } 134 } 135 136 void MostVisitedHandler::StartQueryForMostVisited() { 137 // Use TopSites. 138 history::TopSites* ts = web_ui_->GetProfile()->GetTopSites(); 139 if (ts) { 140 ts->GetMostVisitedURLs( 141 &topsites_consumer_, 142 NewCallback(this, &MostVisitedHandler::OnMostVisitedURLsAvailable)); 143 } 144 } 145 146 void MostVisitedHandler::HandleBlacklistURL(const ListValue* args) { 147 std::string url = UTF16ToUTF8(ExtractStringValue(args)); 148 BlacklistURL(GURL(url)); 149 } 150 151 void MostVisitedHandler::HandleRemoveURLsFromBlacklist(const ListValue* args) { 152 DCHECK(args->GetSize() != 0); 153 154 for (ListValue::const_iterator iter = args->begin(); 155 iter != args->end(); ++iter) { 156 std::string url; 157 bool r = (*iter)->GetAsString(&url); 158 if (!r) { 159 NOTREACHED(); 160 return; 161 } 162 UserMetrics::RecordAction(UserMetricsAction("MostVisited_UrlRemoved"), 163 web_ui_->GetProfile()); 164 history::TopSites* ts = web_ui_->GetProfile()->GetTopSites(); 165 if (ts) 166 ts->RemoveBlacklistedURL(GURL(url)); 167 } 168 } 169 170 void MostVisitedHandler::HandleClearBlacklist(const ListValue* args) { 171 UserMetrics::RecordAction(UserMetricsAction("MostVisited_BlacklistCleared"), 172 web_ui_->GetProfile()); 173 174 history::TopSites* ts = web_ui_->GetProfile()->GetTopSites(); 175 if (ts) 176 ts->ClearBlacklistedURLs(); 177 } 178 179 void MostVisitedHandler::HandleAddPinnedURL(const ListValue* args) { 180 DCHECK_EQ(5U, args->GetSize()) << "Wrong number of params to addPinnedURL"; 181 MostVisitedPage mvp; 182 std::string tmp_string; 183 string16 tmp_string16; 184 int index; 185 186 bool r = args->GetString(0, &tmp_string); 187 DCHECK(r) << "Missing URL in addPinnedURL from the NTP Most Visited."; 188 mvp.url = GURL(tmp_string); 189 190 r = args->GetString(1, &tmp_string16); 191 DCHECK(r) << "Missing title in addPinnedURL from the NTP Most Visited."; 192 mvp.title = tmp_string16; 193 194 r = args->GetString(2, &tmp_string); 195 DCHECK(r) << "Failed to read the favicon URL in addPinnedURL from the NTP " 196 << "Most Visited."; 197 if (!tmp_string.empty()) 198 mvp.favicon_url = GURL(tmp_string); 199 200 r = args->GetString(3, &tmp_string); 201 DCHECK(r) << "Failed to read the thumbnail URL in addPinnedURL from the NTP " 202 << "Most Visited."; 203 if (!tmp_string.empty()) 204 mvp.thumbnail_url = GURL(tmp_string); 205 206 r = args->GetString(4, &tmp_string); 207 DCHECK(r) << "Missing index in addPinnedURL from the NTP Most Visited."; 208 base::StringToInt(tmp_string, &index); 209 210 AddPinnedURL(mvp, index); 211 } 212 213 void MostVisitedHandler::AddPinnedURL(const MostVisitedPage& page, int index) { 214 history::TopSites* ts = web_ui_->GetProfile()->GetTopSites(); 215 if (ts) 216 ts->AddPinnedURL(page.url, index); 217 } 218 219 void MostVisitedHandler::HandleRemovePinnedURL(const ListValue* args) { 220 std::string url = UTF16ToUTF8(ExtractStringValue(args)); 221 RemovePinnedURL(GURL(url)); 222 } 223 224 void MostVisitedHandler::RemovePinnedURL(const GURL& url) { 225 history::TopSites* ts = web_ui_->GetProfile()->GetTopSites(); 226 if (ts) 227 ts->RemovePinnedURL(url); 228 } 229 230 bool MostVisitedHandler::GetPinnedURLAtIndex(int index, 231 MostVisitedPage* page) { 232 // This iterates over all the pinned URLs. It might seem like it is worth 233 // having a map from the index to the item but the number of items is limited 234 // to the number of items the most visited section is showing on the NTP so 235 // this will be fast enough for now. 236 PrefService* prefs = web_ui_->GetProfile()->GetPrefs(); 237 const DictionaryValue* pinned_urls = 238 prefs->GetDictionary(prefs::kNTPMostVisitedPinnedURLs); 239 for (DictionaryValue::key_iterator it = pinned_urls->begin_keys(); 240 it != pinned_urls->end_keys(); ++it) { 241 Value* value; 242 if (pinned_urls->GetWithoutPathExpansion(*it, &value)) { 243 if (!value->IsType(DictionaryValue::TYPE_DICTIONARY)) { 244 // Moved on to TopSites and now going back. 245 DictionaryPrefUpdate update(prefs, prefs::kNTPMostVisitedPinnedURLs); 246 update.Get()->Clear(); 247 return false; 248 } 249 250 int dict_index; 251 const DictionaryValue* dict = static_cast<DictionaryValue*>(value); 252 if (dict->GetInteger("index", &dict_index) && dict_index == index) { 253 // The favicon and thumbnail URLs may be empty. 254 std::string tmp_string; 255 if (dict->GetString("faviconUrl", &tmp_string)) 256 page->favicon_url = GURL(tmp_string); 257 if (dict->GetString("thumbnailUrl", &tmp_string)) 258 page->thumbnail_url = GURL(tmp_string); 259 260 if (dict->GetString("url", &tmp_string)) 261 page->url = GURL(tmp_string); 262 else 263 return false; 264 265 return dict->GetString("title", &page->title); 266 } 267 } else { 268 NOTREACHED() << "DictionaryValue iterators are filthy liars."; 269 } 270 } 271 272 return false; 273 } 274 275 void MostVisitedHandler::SetPagesValueFromTopSites( 276 const history::MostVisitedURLList& data) { 277 pages_value_.reset(new ListValue); 278 for (size_t i = 0; i < data.size(); i++) { 279 const history::MostVisitedURL& url = data[i]; 280 DictionaryValue* page_value = new DictionaryValue(); 281 if (url.url.is_empty()) { 282 page_value->SetBoolean("filler", true); 283 pages_value_->Append(page_value); 284 continue; 285 } 286 287 NewTabUI::SetURLTitleAndDirection(page_value, 288 url.title, 289 url.url); 290 if (!url.favicon_url.is_empty()) 291 page_value->SetString("faviconUrl", url.favicon_url.spec()); 292 293 // Special case for prepopulated pages: thumbnailUrl is different from url. 294 if (url.url.spec() == l10n_util::GetStringUTF8(IDS_CHROME_WELCOME_URL)) { 295 page_value->SetString("thumbnailUrl", 296 "chrome://theme/IDR_NEWTAB_CHROME_WELCOME_PAGE_THUMBNAIL"); 297 } else if (url.url.spec() == 298 l10n_util::GetStringUTF8(IDS_THEMES_GALLERY_URL)) { 299 page_value->SetString("thumbnailUrl", 300 "chrome://theme/IDR_NEWTAB_THEMES_GALLERY_THUMBNAIL"); 301 } 302 303 history::TopSites* ts = web_ui_->GetProfile()->GetTopSites(); 304 if (ts && ts->IsURLPinned(url.url)) 305 page_value->SetBoolean("pinned", true); 306 pages_value_->Append(page_value); 307 } 308 } 309 310 void MostVisitedHandler::OnMostVisitedURLsAvailable( 311 const history::MostVisitedURLList& data) { 312 SetPagesValueFromTopSites(data); 313 if (got_first_most_visited_request_) { 314 SendPagesValue(); 315 } 316 } 317 318 bool MostVisitedHandler::IsFirstRun() { 319 // If we found no pages we treat this as the first run. 320 bool first_run = NewTabUI::NewTabHTMLSource::first_run() && 321 pages_value_->GetSize() == 322 MostVisitedHandler::GetPrePopulatedPages().size(); 323 // but first_run should only be true once. 324 NewTabUI::NewTabHTMLSource::set_first_run(false); 325 return first_run; 326 } 327 328 // static 329 const std::vector<MostVisitedHandler::MostVisitedPage>& 330 MostVisitedHandler::GetPrePopulatedPages() { 331 // TODO(arv): This needs to get the data from some configurable place. 332 // http://crbug.com/17630 333 static std::vector<MostVisitedPage> pages; 334 if (pages.empty()) { 335 MostVisitedPage welcome_page = { 336 l10n_util::GetStringUTF16(IDS_NEW_TAB_CHROME_WELCOME_PAGE_TITLE), 337 GURL(l10n_util::GetStringUTF8(IDS_CHROME_WELCOME_URL)), 338 GURL("chrome://theme/IDR_NEWTAB_CHROME_WELCOME_PAGE_THUMBNAIL"), 339 GURL("chrome://theme/IDR_NEWTAB_CHROME_WELCOME_PAGE_FAVICON")}; 340 pages.push_back(welcome_page); 341 342 MostVisitedPage gallery_page = { 343 l10n_util::GetStringUTF16(IDS_NEW_TAB_THEMES_GALLERY_PAGE_TITLE), 344 GURL(l10n_util::GetStringUTF8(IDS_THEMES_GALLERY_URL)), 345 GURL("chrome://theme/IDR_NEWTAB_THEMES_GALLERY_THUMBNAIL"), 346 GURL("chrome://theme/IDR_NEWTAB_THEMES_GALLERY_FAVICON")}; 347 pages.push_back(gallery_page); 348 } 349 350 return pages; 351 } 352 353 void MostVisitedHandler::Observe(NotificationType type, 354 const NotificationSource& source, 355 const NotificationDetails& details) { 356 if (type != NotificationType::HISTORY_URLS_DELETED) { 357 NOTREACHED(); 358 return; 359 } 360 361 // Some URLs were deleted from history. Reload the most visited list. 362 HandleGetMostVisited(NULL); 363 } 364 365 void MostVisitedHandler::BlacklistURL(const GURL& url) { 366 history::TopSites* ts = web_ui_->GetProfile()->GetTopSites(); 367 if (ts) 368 ts->AddBlacklistedURL(url); 369 } 370 371 std::string MostVisitedHandler::GetDictionaryKeyForURL(const std::string& url) { 372 return MD5String(url); 373 } 374 375 // static 376 void MostVisitedHandler::RegisterUserPrefs(PrefService* prefs) { 377 prefs->RegisterDictionaryPref(prefs::kNTPMostVisitedURLsBlacklist); 378 prefs->RegisterDictionaryPref(prefs::kNTPMostVisitedPinnedURLs); 379 } 380 381 // static 382 std::vector<GURL> MostVisitedHandler::GetPrePopulatedUrls() { 383 const std::vector<MostVisitedPage> pages = 384 MostVisitedHandler::GetPrePopulatedPages(); 385 std::vector<GURL> page_urls; 386 for (size_t i = 0; i < pages.size(); ++i) 387 page_urls.push_back(pages[i].url); 388 return page_urls; 389 } 390