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