OLD | NEW |
| (Empty) |
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 "components/suggestions/suggestions_service.h" | |
6 | |
7 #include <memory> | |
8 #include <utility> | |
9 | |
10 #include "base/feature_list.h" | |
11 #include "base/location.h" | |
12 #include "base/metrics/histogram_macros.h" | |
13 #include "base/metrics/sparse_histogram.h" | |
14 #include "base/strings/string_number_conversions.h" | |
15 #include "base/strings/string_util.h" | |
16 #include "base/strings/stringprintf.h" | |
17 #include "base/threading/thread_task_runner_handle.h" | |
18 #include "build/build_config.h" | |
19 #include "components/data_use_measurement/core/data_use_user_data.h" | |
20 #include "components/google/core/browser/google_util.h" | |
21 #include "components/pref_registry/pref_registry_syncable.h" | |
22 #include "components/signin/core/browser/signin_manager_base.h" | |
23 #include "components/suggestions/blacklist_store.h" | |
24 #include "components/suggestions/image_manager.h" | |
25 #include "components/suggestions/suggestions_store.h" | |
26 #include "components/sync/driver/sync_service.h" | |
27 #include "components/variations/net/variations_http_headers.h" | |
28 #include "google_apis/gaia/gaia_constants.h" | |
29 #include "google_apis/gaia/oauth2_token_service.h" | |
30 #include "net/base/escape.h" | |
31 #include "net/base/load_flags.h" | |
32 #include "net/base/net_errors.h" | |
33 #include "net/base/url_util.h" | |
34 #include "net/http/http_response_headers.h" | |
35 #include "net/http/http_status_code.h" | |
36 #include "net/http/http_util.h" | |
37 #include "net/url_request/url_fetcher.h" | |
38 #include "net/url_request/url_request_status.h" | |
39 | |
40 using base::TimeDelta; | |
41 using base::TimeTicks; | |
42 | |
43 namespace suggestions { | |
44 | |
45 namespace { | |
46 | |
47 // Establishes the different sync states that matter to SuggestionsService. | |
48 // There are three different concepts in the sync service: initialized, sync | |
49 // enabled and history sync enabled. | |
50 enum SyncState { | |
51 // State: Sync service is not initialized, yet not disabled. History sync | |
52 // state is unknown (since not initialized). | |
53 // Behavior: Does not issue a server request, but serves from cache if | |
54 // available. | |
55 NOT_INITIALIZED_ENABLED, | |
56 | |
57 // State: Sync service is initialized, sync is enabled and history sync is | |
58 // enabled. | |
59 // Behavior: Update suggestions from the server. Serve from cache on timeout. | |
60 INITIALIZED_ENABLED_HISTORY, | |
61 | |
62 // State: Sync service is disabled or history sync is disabled. | |
63 // Behavior: Do not issue a server request. Clear the cache. Serve empty | |
64 // suggestions. | |
65 SYNC_OR_HISTORY_SYNC_DISABLED, | |
66 }; | |
67 | |
68 SyncState GetSyncState(syncer::SyncService* sync) { | |
69 if (!sync || !sync->CanSyncStart()) | |
70 return SYNC_OR_HISTORY_SYNC_DISABLED; | |
71 if (!sync->IsSyncActive() || !sync->ConfigurationDone()) | |
72 return NOT_INITIALIZED_ENABLED; | |
73 return sync->GetActiveDataTypes().Has(syncer::HISTORY_DELETE_DIRECTIVES) | |
74 ? INITIALIZED_ENABLED_HISTORY | |
75 : SYNC_OR_HISTORY_SYNC_DISABLED; | |
76 } | |
77 | |
78 // Used to UMA log the state of the last response from the server. | |
79 enum SuggestionsResponseState { | |
80 RESPONSE_EMPTY, | |
81 RESPONSE_INVALID, | |
82 RESPONSE_VALID, | |
83 RESPONSE_STATE_SIZE | |
84 }; | |
85 | |
86 // Will log the supplied response |state|. | |
87 void LogResponseState(SuggestionsResponseState state) { | |
88 UMA_HISTOGRAM_ENUMERATION("Suggestions.ResponseState", state, | |
89 RESPONSE_STATE_SIZE); | |
90 } | |
91 | |
92 // Default delay used when scheduling a request. | |
93 const int kDefaultSchedulingDelaySec = 1; | |
94 | |
95 // Multiplier on the delay used when re-scheduling a failed request. | |
96 const int kSchedulingBackoffMultiplier = 2; | |
97 | |
98 // Maximum valid delay for scheduling a request. Candidate delays larger than | |
99 // this are rejected. This means the maximum backoff is at least 5 / 2 minutes. | |
100 const int kSchedulingMaxDelaySec = 5 * 60; | |
101 | |
102 const char kDefaultGoogleBaseURL[] = "https://www.google.com/"; | |
103 | |
104 GURL GetGoogleBaseURL() { | |
105 GURL url(google_util::CommandLineGoogleBaseURL()); | |
106 if (url.is_valid()) | |
107 return url; | |
108 return GURL(kDefaultGoogleBaseURL); | |
109 } | |
110 | |
111 // Format strings for the various suggestions URLs. They all have two string | |
112 // params: The Google base URL and the device type. | |
113 // TODO(mathp): Put this in TemplateURL. | |
114 const char kSuggestionsURLFormat[] = | |
115 "%schromesuggestions?t=%s"; | |
116 const char kSuggestionsBlacklistURLPrefixFormat[] = | |
117 "%schromesuggestions/blacklist?t=%s&url="; | |
118 const char kSuggestionsBlacklistClearURLFormat[] = | |
119 "%schromesuggestions/blacklist/clear?t=%s"; | |
120 | |
121 const char kSuggestionsBlacklistURLParam[] = "url"; | |
122 | |
123 #if defined(OS_ANDROID) || defined(OS_IOS) | |
124 const char kDeviceType[] = "2"; | |
125 #else | |
126 const char kDeviceType[] = "1"; | |
127 #endif | |
128 | |
129 // Format string for OAuth2 authentication headers. | |
130 const char kAuthorizationHeaderFormat[] = "Authorization: Bearer %s"; | |
131 | |
132 const char kFaviconURL[] = | |
133 "https://s2.googleusercontent.com/s2/favicons?domain_url=%s&alt=s&sz=32"; | |
134 | |
135 // The default expiry timeout is 168 hours. | |
136 const int64_t kDefaultExpiryUsec = 168 * base::Time::kMicrosecondsPerHour; | |
137 | |
138 } // namespace | |
139 | |
140 // Helper class for fetching OAuth2 access tokens. | |
141 // To get a token, call |GetAccessToken|. Does not support multiple concurrent | |
142 // token requests, i.e. check |HasPendingRequest| first. | |
143 class SuggestionsService::AccessTokenFetcher | |
144 : public OAuth2TokenService::Consumer { | |
145 public: | |
146 using TokenCallback = base::Callback<void(const std::string&)>; | |
147 | |
148 AccessTokenFetcher(const SigninManagerBase* signin_manager, | |
149 OAuth2TokenService* token_service) | |
150 : OAuth2TokenService::Consumer("suggestions_service"), | |
151 signin_manager_(signin_manager), | |
152 token_service_(token_service) {} | |
153 | |
154 void GetAccessToken(const TokenCallback& callback) { | |
155 callback_ = callback; | |
156 std::string account_id; | |
157 // |signin_manager_| can be null in unit tests. | |
158 if (signin_manager_) | |
159 account_id = signin_manager_->GetAuthenticatedAccountId(); | |
160 OAuth2TokenService::ScopeSet scopes; | |
161 scopes.insert(GaiaConstants::kChromeSyncOAuth2Scope); | |
162 token_request_ = token_service_->StartRequest(account_id, scopes, this); | |
163 } | |
164 | |
165 bool HasPendingRequest() const { | |
166 return !!token_request_.get(); | |
167 } | |
168 | |
169 private: | |
170 void OnGetTokenSuccess(const OAuth2TokenService::Request* request, | |
171 const std::string& access_token, | |
172 const base::Time& expiration_time) override { | |
173 DCHECK_EQ(request, token_request_.get()); | |
174 callback_.Run(access_token); | |
175 token_request_.reset(nullptr); | |
176 } | |
177 | |
178 void OnGetTokenFailure(const OAuth2TokenService::Request* request, | |
179 const GoogleServiceAuthError& error) override { | |
180 DCHECK_EQ(request, token_request_.get()); | |
181 LOG(WARNING) << "Token error: " << error.ToString(); | |
182 callback_.Run(std::string()); | |
183 token_request_.reset(nullptr); | |
184 } | |
185 | |
186 const SigninManagerBase* signin_manager_; | |
187 OAuth2TokenService* token_service_; | |
188 | |
189 TokenCallback callback_; | |
190 std::unique_ptr<OAuth2TokenService::Request> token_request_; | |
191 }; | |
192 | |
193 SuggestionsService::SuggestionsService( | |
194 const SigninManagerBase* signin_manager, | |
195 OAuth2TokenService* token_service, | |
196 syncer::SyncService* sync_service, | |
197 net::URLRequestContextGetter* url_request_context, | |
198 std::unique_ptr<SuggestionsStore> suggestions_store, | |
199 std::unique_ptr<ImageManager> thumbnail_manager, | |
200 std::unique_ptr<BlacklistStore> blacklist_store) | |
201 : sync_service_(sync_service), | |
202 sync_service_observer_(this), | |
203 url_request_context_(url_request_context), | |
204 suggestions_store_(std::move(suggestions_store)), | |
205 thumbnail_manager_(std::move(thumbnail_manager)), | |
206 blacklist_store_(std::move(blacklist_store)), | |
207 scheduling_delay_(TimeDelta::FromSeconds(kDefaultSchedulingDelaySec)), | |
208 token_fetcher_(new AccessTokenFetcher(signin_manager, token_service)), | |
209 weak_ptr_factory_(this) { | |
210 // |sync_service_| is null if switches::kDisableSync is set (tests use that). | |
211 if (sync_service_) | |
212 sync_service_observer_.Add(sync_service_); | |
213 // Immediately get the current sync state, so we'll flush the cache if | |
214 // necessary. | |
215 OnStateChanged(); | |
216 } | |
217 | |
218 SuggestionsService::~SuggestionsService() {} | |
219 | |
220 bool SuggestionsService::FetchSuggestionsData() { | |
221 DCHECK(thread_checker_.CalledOnValidThread()); | |
222 // If sync state allows, issue a network request to refresh the suggestions. | |
223 if (GetSyncState(sync_service_) != INITIALIZED_ENABLED_HISTORY) | |
224 return false; | |
225 IssueRequestIfNoneOngoing(BuildSuggestionsURL()); | |
226 return true; | |
227 } | |
228 | |
229 SuggestionsProfile SuggestionsService::GetSuggestionsDataFromCache() const { | |
230 SuggestionsProfile suggestions; | |
231 // In case of empty cache or error, |suggestions| stays empty. | |
232 suggestions_store_->LoadSuggestions(&suggestions); | |
233 thumbnail_manager_->Initialize(suggestions); | |
234 blacklist_store_->FilterSuggestions(&suggestions); | |
235 return suggestions; | |
236 } | |
237 | |
238 std::unique_ptr<SuggestionsService::ResponseCallbackList::Subscription> | |
239 SuggestionsService::AddCallback(const ResponseCallback& callback) { | |
240 return callback_list_.Add(callback); | |
241 } | |
242 | |
243 void SuggestionsService::GetPageThumbnail(const GURL& url, | |
244 const BitmapCallback& callback) { | |
245 thumbnail_manager_->GetImageForURL(url, callback); | |
246 } | |
247 | |
248 void SuggestionsService::GetPageThumbnailWithURL( | |
249 const GURL& url, | |
250 const GURL& thumbnail_url, | |
251 const BitmapCallback& callback) { | |
252 thumbnail_manager_->AddImageURL(url, thumbnail_url); | |
253 GetPageThumbnail(url, callback); | |
254 } | |
255 | |
256 bool SuggestionsService::BlacklistURL(const GURL& candidate_url) { | |
257 DCHECK(thread_checker_.CalledOnValidThread()); | |
258 | |
259 if (!blacklist_store_->BlacklistUrl(candidate_url)) | |
260 return false; | |
261 | |
262 callback_list_.Notify(GetSuggestionsDataFromCache()); | |
263 | |
264 // Blacklist uploads are scheduled on any request completion, so only schedule | |
265 // an upload if there is no ongoing request. | |
266 if (!pending_request_.get()) | |
267 ScheduleBlacklistUpload(); | |
268 | |
269 return true; | |
270 } | |
271 | |
272 bool SuggestionsService::UndoBlacklistURL(const GURL& url) { | |
273 DCHECK(thread_checker_.CalledOnValidThread()); | |
274 TimeDelta time_delta; | |
275 if (blacklist_store_->GetTimeUntilURLReadyForUpload(url, &time_delta) && | |
276 time_delta > TimeDelta::FromSeconds(0) && | |
277 blacklist_store_->RemoveUrl(url)) { | |
278 // The URL was not yet candidate for upload to the server and could be | |
279 // removed from the blacklist. | |
280 callback_list_.Notify(GetSuggestionsDataFromCache()); | |
281 return true; | |
282 } | |
283 return false; | |
284 } | |
285 | |
286 void SuggestionsService::ClearBlacklist() { | |
287 DCHECK(thread_checker_.CalledOnValidThread()); | |
288 blacklist_store_->ClearBlacklist(); | |
289 callback_list_.Notify(GetSuggestionsDataFromCache()); | |
290 IssueRequestIfNoneOngoing(BuildSuggestionsBlacklistClearURL()); | |
291 } | |
292 | |
293 // static | |
294 bool SuggestionsService::GetBlacklistedUrl(const net::URLFetcher& request, | |
295 GURL* url) { | |
296 bool is_blacklist_request = base::StartsWith( | |
297 request.GetOriginalURL().spec(), BuildSuggestionsBlacklistURLPrefix(), | |
298 base::CompareCase::SENSITIVE); | |
299 if (!is_blacklist_request) return false; | |
300 | |
301 // Extract the blacklisted URL from the blacklist request. | |
302 std::string blacklisted; | |
303 if (!net::GetValueForKeyInQuery( | |
304 request.GetOriginalURL(), | |
305 kSuggestionsBlacklistURLParam, | |
306 &blacklisted)) { | |
307 return false; | |
308 } | |
309 | |
310 GURL blacklisted_url(blacklisted); | |
311 blacklisted_url.Swap(url); | |
312 return true; | |
313 } | |
314 | |
315 // static | |
316 void SuggestionsService::RegisterProfilePrefs( | |
317 user_prefs::PrefRegistrySyncable* registry) { | |
318 SuggestionsStore::RegisterProfilePrefs(registry); | |
319 BlacklistStore::RegisterProfilePrefs(registry); | |
320 } | |
321 | |
322 // static | |
323 GURL SuggestionsService::BuildSuggestionsURL() { | |
324 return GURL(base::StringPrintf(kSuggestionsURLFormat, | |
325 GetGoogleBaseURL().spec().c_str(), | |
326 kDeviceType)); | |
327 } | |
328 | |
329 // static | |
330 std::string SuggestionsService::BuildSuggestionsBlacklistURLPrefix() { | |
331 return base::StringPrintf(kSuggestionsBlacklistURLPrefixFormat, | |
332 GetGoogleBaseURL().spec().c_str(), kDeviceType); | |
333 } | |
334 | |
335 // static | |
336 GURL SuggestionsService::BuildSuggestionsBlacklistURL( | |
337 const GURL& candidate_url) { | |
338 return GURL(BuildSuggestionsBlacklistURLPrefix() + | |
339 net::EscapeQueryParamValue(candidate_url.spec(), true)); | |
340 } | |
341 | |
342 // static | |
343 GURL SuggestionsService::BuildSuggestionsBlacklistClearURL() { | |
344 return GURL(base::StringPrintf(kSuggestionsBlacklistClearURLFormat, | |
345 GetGoogleBaseURL().spec().c_str(), | |
346 kDeviceType)); | |
347 } | |
348 | |
349 void SuggestionsService::OnStateChanged() { | |
350 switch (GetSyncState(sync_service_)) { | |
351 case SYNC_OR_HISTORY_SYNC_DISABLED: | |
352 // Cancel any ongoing request, to stop interacting with the server. | |
353 pending_request_.reset(nullptr); | |
354 suggestions_store_->ClearSuggestions(); | |
355 callback_list_.Notify(SuggestionsProfile()); | |
356 break; | |
357 case NOT_INITIALIZED_ENABLED: | |
358 // Keep the cache (if any), but don't refresh. | |
359 break; | |
360 case INITIALIZED_ENABLED_HISTORY: | |
361 // If we have any observers, issue a network request to refresh the | |
362 // suggestions in the cache. | |
363 if (!callback_list_.empty()) | |
364 IssueRequestIfNoneOngoing(BuildSuggestionsURL()); | |
365 break; | |
366 } | |
367 } | |
368 | |
369 void SuggestionsService::SetDefaultExpiryTimestamp( | |
370 SuggestionsProfile* suggestions, | |
371 int64_t default_timestamp_usec) { | |
372 for (int i = 0; i < suggestions->suggestions_size(); ++i) { | |
373 ChromeSuggestion* suggestion = suggestions->mutable_suggestions(i); | |
374 // Do not set expiry if the server has already provided a more specific | |
375 // expiry time for this suggestion. | |
376 if (!suggestion->has_expiry_ts()) { | |
377 suggestion->set_expiry_ts(default_timestamp_usec); | |
378 } | |
379 } | |
380 } | |
381 | |
382 void SuggestionsService::IssueRequestIfNoneOngoing(const GURL& url) { | |
383 // If there is an ongoing request, let it complete. | |
384 if (pending_request_.get()) { | |
385 return; | |
386 } | |
387 // If there is an ongoing token request, also wait for that. | |
388 if (token_fetcher_->HasPendingRequest()) { | |
389 return; | |
390 } | |
391 token_fetcher_->GetAccessToken( | |
392 base::Bind(&SuggestionsService::IssueSuggestionsRequest, | |
393 base::Unretained(this), url)); | |
394 } | |
395 | |
396 void SuggestionsService::IssueSuggestionsRequest( | |
397 const GURL& url, | |
398 const std::string& access_token) { | |
399 if (access_token.empty()) { | |
400 UpdateBlacklistDelay(false); | |
401 ScheduleBlacklistUpload(); | |
402 return; | |
403 } | |
404 pending_request_ = CreateSuggestionsRequest(url, access_token); | |
405 pending_request_->Start(); | |
406 last_request_started_time_ = TimeTicks::Now(); | |
407 } | |
408 | |
409 std::unique_ptr<net::URLFetcher> SuggestionsService::CreateSuggestionsRequest( | |
410 const GURL& url, | |
411 const std::string& access_token) { | |
412 std::unique_ptr<net::URLFetcher> request = | |
413 net::URLFetcher::Create(0, url, net::URLFetcher::GET, this); | |
414 data_use_measurement::DataUseUserData::AttachToFetcher( | |
415 request.get(), data_use_measurement::DataUseUserData::SUGGESTIONS); | |
416 int load_flags = net::LOAD_DISABLE_CACHE | net::LOAD_DO_NOT_SEND_COOKIES | | |
417 net::LOAD_DO_NOT_SAVE_COOKIES; | |
418 | |
419 request->SetLoadFlags(load_flags); | |
420 request->SetRequestContext(url_request_context_); | |
421 // Add Chrome experiment state to the request headers. | |
422 net::HttpRequestHeaders headers; | |
423 variations::AppendVariationHeaders(request->GetOriginalURL(), false, false, | |
424 &headers); | |
425 request->SetExtraRequestHeaders(headers.ToString()); | |
426 if (!access_token.empty()) { | |
427 request->AddExtraRequestHeader( | |
428 base::StringPrintf(kAuthorizationHeaderFormat, access_token.c_str())); | |
429 } | |
430 return request; | |
431 } | |
432 | |
433 void SuggestionsService::OnURLFetchComplete(const net::URLFetcher* source) { | |
434 DCHECK(thread_checker_.CalledOnValidThread()); | |
435 DCHECK_EQ(pending_request_.get(), source); | |
436 | |
437 // The fetcher will be deleted when the request is handled. | |
438 std::unique_ptr<const net::URLFetcher> request(std::move(pending_request_)); | |
439 | |
440 const net::URLRequestStatus& request_status = request->GetStatus(); | |
441 if (request_status.status() != net::URLRequestStatus::SUCCESS) { | |
442 // This represents network errors (i.e. the server did not provide a | |
443 // response). | |
444 UMA_HISTOGRAM_SPARSE_SLOWLY("Suggestions.FailedRequestErrorCode", | |
445 -request_status.error()); | |
446 DVLOG(1) << "Suggestions server request failed with error: " | |
447 << request_status.error() << ": " | |
448 << net::ErrorToString(request_status.error()); | |
449 UpdateBlacklistDelay(false); | |
450 ScheduleBlacklistUpload(); | |
451 return; | |
452 } | |
453 | |
454 const int response_code = request->GetResponseCode(); | |
455 UMA_HISTOGRAM_SPARSE_SLOWLY("Suggestions.FetchResponseCode", response_code); | |
456 if (response_code != net::HTTP_OK) { | |
457 // A non-200 response code means that server has no (longer) suggestions for | |
458 // this user. Aggressively clear the cache. | |
459 suggestions_store_->ClearSuggestions(); | |
460 UpdateBlacklistDelay(false); | |
461 ScheduleBlacklistUpload(); | |
462 return; | |
463 } | |
464 | |
465 const TimeDelta latency = TimeTicks::Now() - last_request_started_time_; | |
466 UMA_HISTOGRAM_MEDIUM_TIMES("Suggestions.FetchSuccessLatency", latency); | |
467 | |
468 // Handle a successful blacklisting. | |
469 GURL blacklisted_url; | |
470 if (GetBlacklistedUrl(*source, &blacklisted_url)) { | |
471 blacklist_store_->RemoveUrl(blacklisted_url); | |
472 } | |
473 | |
474 std::string suggestions_data; | |
475 bool success = request->GetResponseAsString(&suggestions_data); | |
476 DCHECK(success); | |
477 | |
478 // Parse the received suggestions and update the cache, or take proper action | |
479 // in the case of invalid response. | |
480 SuggestionsProfile suggestions; | |
481 if (suggestions_data.empty()) { | |
482 LogResponseState(RESPONSE_EMPTY); | |
483 suggestions_store_->ClearSuggestions(); | |
484 } else if (suggestions.ParseFromString(suggestions_data)) { | |
485 LogResponseState(RESPONSE_VALID); | |
486 int64_t now_usec = | |
487 (base::Time::NowFromSystemTime() - base::Time::UnixEpoch()) | |
488 .ToInternalValue(); | |
489 SetDefaultExpiryTimestamp(&suggestions, now_usec + kDefaultExpiryUsec); | |
490 PopulateExtraData(&suggestions); | |
491 suggestions_store_->StoreSuggestions(suggestions); | |
492 } else { | |
493 LogResponseState(RESPONSE_INVALID); | |
494 } | |
495 | |
496 callback_list_.Notify(GetSuggestionsDataFromCache()); | |
497 | |
498 UpdateBlacklistDelay(true); | |
499 ScheduleBlacklistUpload(); | |
500 } | |
501 | |
502 void SuggestionsService::PopulateExtraData(SuggestionsProfile* suggestions) { | |
503 for (int i = 0; i < suggestions->suggestions_size(); ++i) { | |
504 suggestions::ChromeSuggestion* s = suggestions->mutable_suggestions(i); | |
505 if (!s->has_favicon_url() || s->favicon_url().empty()) { | |
506 s->set_favicon_url(base::StringPrintf(kFaviconURL, s->url().c_str())); | |
507 } | |
508 } | |
509 } | |
510 | |
511 void SuggestionsService::Shutdown() { | |
512 // Cancel pending request. | |
513 pending_request_.reset(nullptr); | |
514 } | |
515 | |
516 void SuggestionsService::ScheduleBlacklistUpload() { | |
517 DCHECK(thread_checker_.CalledOnValidThread()); | |
518 TimeDelta time_delta; | |
519 if (blacklist_store_->GetTimeUntilReadyForUpload(&time_delta)) { | |
520 // Blacklist cache is not empty: schedule. | |
521 base::Closure blacklist_cb = | |
522 base::Bind(&SuggestionsService::UploadOneFromBlacklist, | |
523 weak_ptr_factory_.GetWeakPtr()); | |
524 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( | |
525 FROM_HERE, blacklist_cb, time_delta + scheduling_delay_); | |
526 } | |
527 } | |
528 | |
529 void SuggestionsService::UploadOneFromBlacklist() { | |
530 DCHECK(thread_checker_.CalledOnValidThread()); | |
531 | |
532 GURL blacklisted_url; | |
533 if (blacklist_store_->GetCandidateForUpload(&blacklisted_url)) { | |
534 // Issue a blacklisting request. Even if this request ends up not being sent | |
535 // because of an ongoing request, a blacklist request is later scheduled. | |
536 IssueRequestIfNoneOngoing(BuildSuggestionsBlacklistURL(blacklisted_url)); | |
537 return; | |
538 } | |
539 | |
540 // Even though there's no candidate for upload, the blacklist might not be | |
541 // empty. | |
542 ScheduleBlacklistUpload(); | |
543 } | |
544 | |
545 void SuggestionsService::UpdateBlacklistDelay(bool last_request_successful) { | |
546 DCHECK(thread_checker_.CalledOnValidThread()); | |
547 | |
548 if (last_request_successful) { | |
549 scheduling_delay_ = TimeDelta::FromSeconds(kDefaultSchedulingDelaySec); | |
550 } else { | |
551 TimeDelta candidate_delay = | |
552 scheduling_delay_ * kSchedulingBackoffMultiplier; | |
553 if (candidate_delay < TimeDelta::FromSeconds(kSchedulingMaxDelaySec)) | |
554 scheduling_delay_ = candidate_delay; | |
555 } | |
556 } | |
557 | |
558 } // namespace suggestions | |
OLD | NEW |