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
40namespace {
41
42// The number of most visited pages we show.
43const size_t kMostVisitedPages = 8;
44
45// The number of days of history we consider for most visited entries.
46const 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.
52struct MostVisitedHandler::MostVisitedPage {
53  string16 title;
54  GURL url;
55  GURL thumbnail_url;
56  GURL favicon_url;
57};
58
59MostVisitedHandler::MostVisitedHandler()
60    : got_first_most_visited_request_(false) {
61}
62
63MostVisitedHandler::~MostVisitedHandler() {
64}
65
66WebUIMessageHandler* 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
86void 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
107void 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
117void 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
136void 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
146void MostVisitedHandler::HandleBlacklistURL(const ListValue* args) {
147  std::string url = UTF16ToUTF8(ExtractStringValue(args));
148  BlacklistURL(GURL(url));
149}
150
151void 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
170void 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
179void 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
213void 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
219void MostVisitedHandler::HandleRemovePinnedURL(const ListValue* args) {
220  std::string url = UTF16ToUTF8(ExtractStringValue(args));
221  RemovePinnedURL(GURL(url));
222}
223
224void MostVisitedHandler::RemovePinnedURL(const GURL& url) {
225  history::TopSites* ts = web_ui_->GetProfile()->GetTopSites();
226  if (ts)
227    ts->RemovePinnedURL(url);
228}
229
230bool 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
275void 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
310void MostVisitedHandler::OnMostVisitedURLsAvailable(
311    const history::MostVisitedURLList& data) {
312  SetPagesValueFromTopSites(data);
313  if (got_first_most_visited_request_) {
314    SendPagesValue();
315  }
316}
317
318bool 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
329const 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
353void 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
365void MostVisitedHandler::BlacklistURL(const GURL& url) {
366  history::TopSites* ts = web_ui_->GetProfile()->GetTopSites();
367  if (ts)
368    ts->AddBlacklistedURL(url);
369}
370
371std::string MostVisitedHandler::GetDictionaryKeyForURL(const std::string& url) {
372  return MD5String(url);
373}
374
375// static
376void MostVisitedHandler::RegisterUserPrefs(PrefService* prefs) {
377  prefs->RegisterDictionaryPref(prefs::kNTPMostVisitedURLsBlacklist);
378  prefs->RegisterDictionaryPref(prefs::kNTPMostVisitedPinnedURLs);
379}
380
381// static
382std::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