1// Copyright (c) 2012 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/ntp/most_visited_handler.h"
6
7#include <set>
8
9#include "base/bind.h"
10#include "base/bind_helpers.h"
11#include "base/command_line.h"
12#include "base/md5.h"
13#include "base/memory/scoped_vector.h"
14#include "base/memory/singleton.h"
15#include "base/metrics/histogram.h"
16#include "base/prefs/pref_service.h"
17#include "base/prefs/scoped_user_pref_update.h"
18#include "base/strings/string16.h"
19#include "base/strings/string_number_conversions.h"
20#include "base/strings/utf_string_conversions.h"
21#include "base/threading/thread.h"
22#include "base/values.h"
23#include "chrome/browser/chrome_notification_types.h"
24#include "chrome/browser/history/top_sites.h"
25#include "chrome/browser/profiles/profile.h"
26#include "chrome/browser/thumbnails/thumbnail_list_source.h"
27#include "chrome/browser/ui/browser.h"
28#include "chrome/browser/ui/browser_finder.h"
29#include "chrome/browser/ui/tabs/tab_strip_model.h"
30#include "chrome/browser/ui/tabs/tab_strip_model_utils.h"
31#include "chrome/browser/ui/webui/favicon_source.h"
32#include "chrome/browser/ui/webui/ntp/new_tab_ui.h"
33#include "chrome/browser/ui/webui/ntp/ntp_stats.h"
34#include "chrome/browser/ui/webui/ntp/thumbnail_source.h"
35#include "chrome/common/pref_names.h"
36#include "chrome/common/url_constants.h"
37#include "components/history/core/browser/page_usage_data.h"
38#include "components/pref_registry/pref_registry_syncable.h"
39#include "content/public/browser/navigation_controller.h"
40#include "content/public/browser/navigation_entry.h"
41#include "content/public/browser/notification_source.h"
42#include "content/public/browser/url_data_source.h"
43#include "content/public/browser/user_metrics.h"
44#include "content/public/browser/web_contents.h"
45#include "content/public/browser/web_ui.h"
46#include "url/gurl.h"
47
48using base::UserMetricsAction;
49
50MostVisitedHandler::MostVisitedHandler()
51    : got_first_most_visited_request_(false),
52      most_visited_viewed_(false),
53      user_action_logged_(false),
54      weak_ptr_factory_(this) {
55}
56
57MostVisitedHandler::~MostVisitedHandler() {
58  if (!user_action_logged_ && most_visited_viewed_) {
59    const GURL ntp_url = GURL(chrome::kChromeUINewTabURL);
60    int action_id = NTP_FOLLOW_ACTION_OTHER;
61    content::NavigationEntry* entry =
62        web_ui()->GetWebContents()->GetController().GetLastCommittedEntry();
63    if (entry && (entry->GetURL() != ntp_url)) {
64      action_id =
65          ui::PageTransitionStripQualifier(entry->GetTransitionType());
66    }
67
68    UMA_HISTOGRAM_ENUMERATION("NewTabPage.MostVisitedAction", action_id,
69                              NUM_NTP_FOLLOW_ACTIONS);
70  }
71}
72
73void MostVisitedHandler::RegisterMessages() {
74  Profile* profile = Profile::FromWebUI(web_ui());
75  // Set up our sources for thumbnail and favicon data.
76  content::URLDataSource::Add(profile, new ThumbnailSource(profile, false));
77  content::URLDataSource::Add(profile, new ThumbnailSource(profile, true));
78
79  // Set up our sources for top-sites data.
80  content::URLDataSource::Add(profile, new ThumbnailListSource(profile));
81
82  // Register chrome://favicon as a data source for favicons.
83  content::URLDataSource::Add(
84      profile, new FaviconSource(profile, FaviconSource::FAVICON));
85
86  history::TopSites* ts = profile->GetTopSites();
87  if (ts) {
88    // TopSites updates itself after a delay. This is especially noticable when
89    // your profile is empty. Ask TopSites to update itself when we're about to
90    // show the new tab page.
91    ts->SyncWithHistory();
92
93    // Register for notification when TopSites changes so that we can update
94    // ourself.
95    registrar_.Add(this, chrome::NOTIFICATION_TOP_SITES_CHANGED,
96                   content::Source<history::TopSites>(ts));
97  }
98
99  // We pre-emptively make a fetch for the most visited pages so we have the
100  // results sooner.
101  StartQueryForMostVisited();
102
103  web_ui()->RegisterMessageCallback("getMostVisited",
104      base::Bind(&MostVisitedHandler::HandleGetMostVisited,
105                 base::Unretained(this)));
106
107  // Register ourselves for any most-visited item blacklisting.
108  web_ui()->RegisterMessageCallback("blacklistURLFromMostVisited",
109      base::Bind(&MostVisitedHandler::HandleBlacklistUrl,
110                 base::Unretained(this)));
111  web_ui()->RegisterMessageCallback("removeURLsFromMostVisitedBlacklist",
112      base::Bind(&MostVisitedHandler::HandleRemoveUrlsFromBlacklist,
113                 base::Unretained(this)));
114  web_ui()->RegisterMessageCallback("clearMostVisitedURLsBlacklist",
115      base::Bind(&MostVisitedHandler::HandleClearBlacklist,
116                 base::Unretained(this)));
117  web_ui()->RegisterMessageCallback("mostVisitedAction",
118      base::Bind(&MostVisitedHandler::HandleMostVisitedAction,
119                 base::Unretained(this)));
120  web_ui()->RegisterMessageCallback("mostVisitedSelected",
121      base::Bind(&MostVisitedHandler::HandleMostVisitedSelected,
122                 base::Unretained(this)));
123}
124
125void MostVisitedHandler::HandleGetMostVisited(const base::ListValue* args) {
126  if (!got_first_most_visited_request_) {
127    // If our initial data is already here, return it.
128    SendPagesValue();
129    got_first_most_visited_request_ = true;
130  } else {
131    StartQueryForMostVisited();
132  }
133}
134
135void MostVisitedHandler::SendPagesValue() {
136  if (pages_value_) {
137    Profile* profile = Profile::FromWebUI(web_ui());
138    const base::DictionaryValue* url_blacklist =
139        profile->GetPrefs()->GetDictionary(prefs::kNtpMostVisitedURLsBlacklist);
140    bool has_blacklisted_urls = !url_blacklist->empty();
141    history::TopSites* ts = profile->GetTopSites();
142    if (ts)
143      has_blacklisted_urls = ts->HasBlacklistedItems();
144
145    base::FundamentalValue has_blacklisted_urls_value(has_blacklisted_urls);
146    web_ui()->CallJavascriptFunction("ntp.setMostVisitedPages",
147                                     *pages_value_,
148                                     has_blacklisted_urls_value);
149    pages_value_.reset();
150  }
151}
152
153void MostVisitedHandler::StartQueryForMostVisited() {
154  history::TopSites* ts = Profile::FromWebUI(web_ui())->GetTopSites();
155  if (ts) {
156    ts->GetMostVisitedURLs(
157        base::Bind(&MostVisitedHandler::OnMostVisitedUrlsAvailable,
158                   weak_ptr_factory_.GetWeakPtr()), false);
159  }
160}
161
162void MostVisitedHandler::HandleBlacklistUrl(const base::ListValue* args) {
163  std::string url = base::UTF16ToUTF8(ExtractStringValue(args));
164  BlacklistUrl(GURL(url));
165}
166
167void MostVisitedHandler::HandleRemoveUrlsFromBlacklist(
168    const base::ListValue* args) {
169  DCHECK(args->GetSize() != 0);
170
171  for (base::ListValue::const_iterator iter = args->begin();
172       iter != args->end(); ++iter) {
173    std::string url;
174    bool r = (*iter)->GetAsString(&url);
175    if (!r) {
176      NOTREACHED();
177      return;
178    }
179    content::RecordAction(UserMetricsAction("MostVisited_UrlRemoved"));
180    history::TopSites* ts = Profile::FromWebUI(web_ui())->GetTopSites();
181    if (ts)
182      ts->RemoveBlacklistedURL(GURL(url));
183  }
184}
185
186void MostVisitedHandler::HandleClearBlacklist(const base::ListValue* args) {
187  content::RecordAction(UserMetricsAction("MostVisited_BlacklistCleared"));
188
189  history::TopSites* ts = Profile::FromWebUI(web_ui())->GetTopSites();
190  if (ts)
191    ts->ClearBlacklistedURLs();
192}
193
194void MostVisitedHandler::HandleMostVisitedAction(const base::ListValue* args) {
195  DCHECK(args);
196
197  double action_id;
198  if (!args->GetDouble(0, &action_id))
199    NOTREACHED();
200
201  UMA_HISTOGRAM_ENUMERATION("NewTabPage.MostVisitedAction",
202                            static_cast<int>(action_id),
203                            NUM_NTP_FOLLOW_ACTIONS);
204  most_visited_viewed_ = true;
205  user_action_logged_ = true;
206}
207
208void MostVisitedHandler::HandleMostVisitedSelected(
209    const base::ListValue* args) {
210  most_visited_viewed_ = true;
211}
212
213void MostVisitedHandler::SetPagesValueFromTopSites(
214    const history::MostVisitedURLList& data) {
215  pages_value_.reset(new base::ListValue);
216
217  history::MostVisitedURLList top_sites(data);
218  for (size_t i = 0; i < top_sites.size(); i++) {
219    const history::MostVisitedURL& url = top_sites[i];
220    base::DictionaryValue* page_value = new base::DictionaryValue();
221    if (url.url.is_empty()) {
222      page_value->SetBoolean("filler", true);
223      pages_value_->Append(page_value);
224      continue;
225    }
226
227    NewTabUI::SetUrlTitleAndDirection(page_value,
228                                      url.title,
229                                      url.url);
230    pages_value_->Append(page_value);
231  }
232}
233
234void MostVisitedHandler::OnMostVisitedUrlsAvailable(
235    const history::MostVisitedURLList& data) {
236  SetPagesValueFromTopSites(data);
237  if (got_first_most_visited_request_) {
238    SendPagesValue();
239  }
240}
241
242void MostVisitedHandler::Observe(int type,
243                                 const content::NotificationSource& source,
244                                 const content::NotificationDetails& details) {
245  DCHECK_EQ(type, chrome::NOTIFICATION_TOP_SITES_CHANGED);
246
247  // Most visited urls changed, query again.
248  StartQueryForMostVisited();
249}
250
251void MostVisitedHandler::BlacklistUrl(const GURL& url) {
252  history::TopSites* ts = Profile::FromWebUI(web_ui())->GetTopSites();
253  if (ts)
254    ts->AddBlacklistedURL(url);
255  content::RecordAction(UserMetricsAction("MostVisited_UrlBlacklisted"));
256}
257
258std::string MostVisitedHandler::GetDictionaryKeyForUrl(const std::string& url) {
259  return base::MD5String(url);
260}
261
262// static
263void MostVisitedHandler::RegisterProfilePrefs(
264    user_prefs::PrefRegistrySyncable* registry) {
265  registry->RegisterDictionaryPref(
266      prefs::kNtpMostVisitedURLsBlacklist,
267      user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
268}
269