Upstream version 9.37.197.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / search / suggestions / suggestions_service.cc
1 // Copyright 2014 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/search/suggestions/suggestions_service.h"
6
7 #include "base/memory/scoped_ptr.h"
8 #include "base/metrics/field_trial.h"
9 #include "base/metrics/histogram.h"
10 #include "base/metrics/sparse_histogram.h"
11 #include "base/strings/string_number_conversions.h"
12 #include "base/strings/string_util.h"
13 #include "base/time/time.h"
14 #include "chrome/browser/browser_process.h"
15 #include "chrome/browser/history/history_types.h"
16 #include "chrome/browser/metrics/variations/variations_http_header_provider.h"
17 #include "chrome/browser/profiles/profile.h"
18 #include "chrome/browser/search/suggestions/suggestions_store.h"
19 #include "components/pref_registry/pref_registry_syncable.h"
20 #include "components/variations/variations_associated_data.h"
21 #include "content/public/browser/browser_thread.h"
22 #include "net/base/escape.h"
23 #include "net/base/load_flags.h"
24 #include "net/base/net_errors.h"
25 #include "net/http/http_response_headers.h"
26 #include "net/http/http_status_code.h"
27 #include "net/http/http_util.h"
28 #include "net/url_request/url_fetcher.h"
29 #include "net/url_request/url_request_status.h"
30 #include "url/gurl.h"
31
32 using base::CancelableClosure;
33 using content::BrowserThread;
34
35 namespace suggestions {
36
37 namespace {
38
39 // Used to UMA log the state of the last response from the server.
40 enum SuggestionsResponseState {
41   RESPONSE_EMPTY,
42   RESPONSE_INVALID,
43   RESPONSE_VALID,
44   RESPONSE_STATE_SIZE
45 };
46
47 // Will log the supplied response |state|.
48 void LogResponseState(SuggestionsResponseState state) {
49   UMA_HISTOGRAM_ENUMERATION("Suggestions.ResponseState", state,
50                             RESPONSE_STATE_SIZE);
51 }
52
53 // Obtains the experiment parameter under the supplied |key|, or empty string
54 // if the parameter does not exist.
55 std::string GetExperimentParam(const std::string& key) {
56   return chrome_variations::GetVariationParamValue(kSuggestionsFieldTrialName,
57                                                    key);
58 }
59
60 // Runs each callback in |requestors| on |suggestions|, then deallocates
61 // |requestors|.
62 void DispatchRequestsAndClear(
63     const SuggestionsProfile& suggestions,
64     std::vector<SuggestionsService::ResponseCallback>* requestors) {
65   std::vector<SuggestionsService::ResponseCallback>::iterator it;
66   for (it = requestors->begin(); it != requestors->end(); ++it) {
67     it->Run(suggestions);
68   }
69   std::vector<SuggestionsService::ResponseCallback>().swap(*requestors);
70 }
71
72 const int kDefaultRequestTimeoutMs = 200;
73
74 }  // namespace
75
76 const char kSuggestionsFieldTrialName[] = "ChromeSuggestions";
77 const char kSuggestionsFieldTrialURLParam[] = "url";
78 const char kSuggestionsFieldTrialSuggestionsSuffixParam[] =
79     "suggestions_suffix";
80 const char kSuggestionsFieldTrialBlacklistSuffixParam[] = "blacklist_suffix";
81 const char kSuggestionsFieldTrialStateParam[] = "state";
82 const char kSuggestionsFieldTrialControlParam[] = "control";
83 const char kSuggestionsFieldTrialStateEnabled[] = "enabled";
84 const char kSuggestionsFieldTrialTimeoutMs[] = "timeout_ms";
85
86 SuggestionsService::SuggestionsService(
87     Profile* profile, scoped_ptr<SuggestionsStore> suggestions_store)
88     : suggestions_store_(suggestions_store.Pass()),
89       thumbnail_manager_(new ThumbnailManager(profile)),
90       profile_(profile),
91       weak_ptr_factory_(this),
92       request_timeout_ms_(kDefaultRequestTimeoutMs) {
93   // Obtain various parameters from Variations.
94   suggestions_url_ =
95       GURL(GetExperimentParam(kSuggestionsFieldTrialURLParam) +
96            GetExperimentParam(kSuggestionsFieldTrialSuggestionsSuffixParam));
97   blacklist_url_prefix_ =
98       GetExperimentParam(kSuggestionsFieldTrialURLParam) +
99       GetExperimentParam(kSuggestionsFieldTrialBlacklistSuffixParam);
100   std::string timeout = GetExperimentParam(kSuggestionsFieldTrialTimeoutMs);
101   int temp_timeout;
102   if (!timeout.empty() && base::StringToInt(timeout, &temp_timeout)) {
103     request_timeout_ms_ = temp_timeout;
104   }
105 }
106
107 SuggestionsService::~SuggestionsService() {}
108
109 // static
110 bool SuggestionsService::IsEnabled() {
111   return GetExperimentParam(kSuggestionsFieldTrialStateParam) ==
112          kSuggestionsFieldTrialStateEnabled;
113 }
114
115 // static
116 bool SuggestionsService::IsControlGroup() {
117   return GetExperimentParam(kSuggestionsFieldTrialControlParam) ==
118          kSuggestionsFieldTrialStateEnabled;
119 }
120
121 void SuggestionsService::FetchSuggestionsData(
122     SuggestionsService::ResponseCallback callback) {
123   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
124
125   FetchSuggestionsDataNoTimeout(callback);
126
127   // Post a task to serve the cached suggestions if the request hasn't completed
128   // after some time. Cancels the previous such task, if one existed.
129   pending_timeout_closure_.reset(new CancelableClosure(base::Bind(
130       &SuggestionsService::OnRequestTimeout, weak_ptr_factory_.GetWeakPtr())));
131   BrowserThread::PostDelayedTask(
132       BrowserThread::UI, FROM_HERE, pending_timeout_closure_->callback(),
133       base::TimeDelta::FromMilliseconds(request_timeout_ms_));
134 }
135
136 void SuggestionsService::FetchSuggestionsDataNoTimeout(
137     SuggestionsService::ResponseCallback callback) {
138   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
139   if (pending_request_.get()) {
140     // Request already exists, so just add requestor to queue.
141     waiting_requestors_.push_back(callback);
142     return;
143   }
144
145   // Form new request.
146   DCHECK(waiting_requestors_.empty());
147   waiting_requestors_.push_back(callback);
148
149   pending_request_.reset(CreateSuggestionsRequest(suggestions_url_));
150   pending_request_->Start();
151   last_request_started_time_ = base::TimeTicks::Now();
152 }
153
154 void SuggestionsService::GetPageThumbnail(
155     const GURL& url,
156     base::Callback<void(const GURL&, const SkBitmap*)> callback) {
157   thumbnail_manager_->GetPageThumbnail(url, callback);
158 }
159
160 void SuggestionsService::BlacklistURL(
161     const GURL& candidate_url, SuggestionsService::ResponseCallback callback) {
162   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
163   waiting_requestors_.push_back(callback);
164
165   if (pending_request_.get()) {
166     if (IsBlacklistRequest(pending_request_.get())) {
167       // Pending request is a blacklist request. Silently drop the new blacklist
168       // request. TODO - handle this case.
169       return;
170     } else {
171       // Pending request is not a blacklist request - cancel it and go on to
172       // issuing a blacklist request. Also ensure the timeout closure does not
173       // run; instead we'll wait for the updated suggestions before servicing
174       // requestors.
175       pending_request_.reset(NULL);
176       pending_timeout_closure_.reset(NULL);
177     }
178   }
179
180   // Send blacklisting request.
181   // TODO(manzagop): make this a PUT request instead of a GET request.
182   GURL url(blacklist_url_prefix_ +
183            net::EscapeQueryParamValue(candidate_url.spec(), true));
184   pending_request_.reset(CreateSuggestionsRequest(url));
185   pending_request_->Start();
186   last_request_started_time_ = base::TimeTicks::Now();
187 }
188
189 // static
190 void SuggestionsService::RegisterProfilePrefs(
191     user_prefs::PrefRegistrySyncable* registry) {
192   SuggestionsStore::RegisterProfilePrefs(registry);
193 }
194
195 void SuggestionsService::OnRequestTimeout() {
196   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
197   ServeFromCache();
198 }
199
200 void SuggestionsService::OnURLFetchComplete(const net::URLFetcher* source) {
201   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
202   DCHECK_EQ(pending_request_.get(), source);
203
204   // We no longer need the timeout closure. Delete it whether or not it has run
205   // (if it hasn't, this cancels it).
206   pending_timeout_closure_.reset();
207
208   // The fetcher will be deleted when the request is handled.
209   scoped_ptr<const net::URLFetcher> request(pending_request_.release());
210   const net::URLRequestStatus& request_status = request->GetStatus();
211   if (request_status.status() != net::URLRequestStatus::SUCCESS) {
212     UMA_HISTOGRAM_SPARSE_SLOWLY("Suggestions.FailedRequestErrorCode",
213                                 -request_status.error());
214     DVLOG(1) << "Suggestions server request failed with error: "
215              << request_status.error() << ": "
216              << net::ErrorToString(request_status.error());
217     // Dispatch the cached profile on error.
218     ServeFromCache();
219     return;
220   }
221
222   // Log the response code.
223   const int response_code = request->GetResponseCode();
224   UMA_HISTOGRAM_SPARSE_SLOWLY("Suggestions.FetchResponseCode", response_code);
225   if (response_code != net::HTTP_OK) {
226     // Aggressively clear the store.
227     suggestions_store_->ClearSuggestions();
228     DispatchRequestsAndClear(SuggestionsProfile(), &waiting_requestors_);
229     return;
230   }
231
232   const base::TimeDelta latency =
233       base::TimeTicks::Now() - last_request_started_time_;
234   UMA_HISTOGRAM_MEDIUM_TIMES("Suggestions.FetchSuccessLatency", latency);
235
236   std::string suggestions_data;
237   bool success = request->GetResponseAsString(&suggestions_data);
238   DCHECK(success);
239
240   // Compute suggestions, and dispatch then to requestors. On error still
241   // dispatch empty suggestions.
242   SuggestionsProfile suggestions;
243   if (suggestions_data.empty()) {
244     LogResponseState(RESPONSE_EMPTY);
245     suggestions_store_->ClearSuggestions();
246   } else if (suggestions.ParseFromString(suggestions_data)) {
247     LogResponseState(RESPONSE_VALID);
248     thumbnail_manager_->InitializeThumbnailMap(suggestions);
249     suggestions_store_->StoreSuggestions(suggestions);
250   } else {
251     LogResponseState(RESPONSE_INVALID);
252     suggestions_store_->LoadSuggestions(&suggestions);
253   }
254
255   DispatchRequestsAndClear(suggestions, &waiting_requestors_);
256 }
257
258 void SuggestionsService::Shutdown() {
259   // Cancel pending request and timeout closure, then serve existing requestors
260   // from cache.
261   pending_request_.reset(NULL);
262   pending_timeout_closure_.reset(NULL);
263   ServeFromCache();
264 }
265
266 bool SuggestionsService::IsBlacklistRequest(net::URLFetcher* request) const {
267   DCHECK(request);
268   return StartsWithASCII(request->GetOriginalURL().spec(),
269                          blacklist_url_prefix_, true);
270 }
271
272 net::URLFetcher* SuggestionsService::CreateSuggestionsRequest(const GURL& url) {
273   net::URLFetcher* request =
274       net::URLFetcher::Create(0, url, net::URLFetcher::GET, this);
275   request->SetLoadFlags(net::LOAD_DISABLE_CACHE);
276   request->SetRequestContext(profile_->GetRequestContext());
277   // Add Chrome experiment state to the request headers.
278   net::HttpRequestHeaders headers;
279   chrome_variations::VariationsHttpHeaderProvider::GetInstance()->AppendHeaders(
280       request->GetOriginalURL(), profile_->IsOffTheRecord(), false, &headers);
281   request->SetExtraRequestHeaders(headers.ToString());
282   return request;
283 }
284
285 void SuggestionsService::ServeFromCache() {
286   SuggestionsProfile suggestions;
287   suggestions_store_->LoadSuggestions(&suggestions);
288   DispatchRequestsAndClear(suggestions, &waiting_requestors_);
289 }
290
291 }  // namespace suggestions