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
MostVisitedHandler()59 MostVisitedHandler::MostVisitedHandler()
60 : got_first_most_visited_request_(false) {
61 }
62
~MostVisitedHandler()63 MostVisitedHandler::~MostVisitedHandler() {
64 }
65
Attach(WebUI * web_ui)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
RegisterMessages()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
HandleGetMostVisited(const ListValue * args)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
SendPagesValue()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
StartQueryForMostVisited()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
HandleBlacklistURL(const ListValue * args)146 void MostVisitedHandler::HandleBlacklistURL(const ListValue* args) {
147 std::string url = UTF16ToUTF8(ExtractStringValue(args));
148 BlacklistURL(GURL(url));
149 }
150
HandleRemoveURLsFromBlacklist(const ListValue * args)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
HandleClearBlacklist(const ListValue * args)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
HandleAddPinnedURL(const ListValue * args)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
AddPinnedURL(const MostVisitedPage & page,int index)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
HandleRemovePinnedURL(const ListValue * args)219 void MostVisitedHandler::HandleRemovePinnedURL(const ListValue* args) {
220 std::string url = UTF16ToUTF8(ExtractStringValue(args));
221 RemovePinnedURL(GURL(url));
222 }
223
RemovePinnedURL(const GURL & url)224 void MostVisitedHandler::RemovePinnedURL(const GURL& url) {
225 history::TopSites* ts = web_ui_->GetProfile()->GetTopSites();
226 if (ts)
227 ts->RemovePinnedURL(url);
228 }
229
GetPinnedURLAtIndex(int index,MostVisitedPage * page)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
SetPagesValueFromTopSites(const history::MostVisitedURLList & data)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
OnMostVisitedURLsAvailable(const history::MostVisitedURLList & data)310 void MostVisitedHandler::OnMostVisitedURLsAvailable(
311 const history::MostVisitedURLList& data) {
312 SetPagesValueFromTopSites(data);
313 if (got_first_most_visited_request_) {
314 SendPagesValue();
315 }
316 }
317
IsFirstRun()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>&
GetPrePopulatedPages()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
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)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
BlacklistURL(const GURL & url)365 void MostVisitedHandler::BlacklistURL(const GURL& url) {
366 history::TopSites* ts = web_ui_->GetProfile()->GetTopSites();
367 if (ts)
368 ts->AddBlacklistedURL(url);
369 }
370
GetDictionaryKeyForURL(const std::string & url)371 std::string MostVisitedHandler::GetDictionaryKeyForURL(const std::string& url) {
372 return MD5String(url);
373 }
374
375 // static
RegisterUserPrefs(PrefService * prefs)376 void MostVisitedHandler::RegisterUserPrefs(PrefService* prefs) {
377 prefs->RegisterDictionaryPref(prefs::kNTPMostVisitedURLsBlacklist);
378 prefs->RegisterDictionaryPref(prefs::kNTPMostVisitedPinnedURLs);
379 }
380
381 // static
GetPrePopulatedUrls()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