OLD | NEW |
1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "components/ntp_snippets/remote/ntp_snippets_fetcher.h" | 5 #include "components/ntp_snippets/remote/ntp_snippets_fetcher.h" |
6 | 6 |
7 #include <cstdlib> | 7 #include <cstdlib> |
8 | 8 |
9 #include "base/command_line.h" | 9 #include "base/command_line.h" |
10 #include "base/files/file_path.h" | 10 #include "base/files/file_path.h" |
11 #include "base/files/file_util.h" | 11 #include "base/files/file_util.h" |
12 #include "base/json/json_writer.h" | 12 #include "base/json/json_writer.h" |
13 #include "base/memory/ptr_util.h" | 13 #include "base/memory/ptr_util.h" |
14 #include "base/metrics/histogram_macros.h" | 14 #include "base/metrics/histogram_macros.h" |
15 #include "base/metrics/sparse_histogram.h" | 15 #include "base/metrics/sparse_histogram.h" |
16 #include "base/path_service.h" | 16 #include "base/path_service.h" |
17 #include "base/strings/string_number_conversions.h" | 17 #include "base/strings/string_number_conversions.h" |
18 #include "base/strings/string_util.h" | 18 #include "base/strings/string_util.h" |
19 #include "base/strings/stringprintf.h" | 19 #include "base/strings/stringprintf.h" |
20 #include "base/strings/utf_string_conversions.h" | 20 #include "base/strings/utf_string_conversions.h" |
21 #include "base/time/default_tick_clock.h" | 21 #include "base/time/default_tick_clock.h" |
22 #include "base/values.h" | 22 #include "base/values.h" |
23 #include "components/data_use_measurement/core/data_use_user_data.h" | 23 #include "components/data_use_measurement/core/data_use_user_data.h" |
24 #include "components/ntp_snippets/category_factory.h" | 24 #include "components/ntp_snippets/category_factory.h" |
| 25 #include "components/ntp_snippets/features.h" |
25 #include "components/ntp_snippets/ntp_snippets_constants.h" | 26 #include "components/ntp_snippets/ntp_snippets_constants.h" |
26 #include "components/signin/core/browser/profile_oauth2_token_service.h" | 27 #include "components/signin/core/browser/profile_oauth2_token_service.h" |
27 #include "components/signin/core/browser/signin_manager.h" | 28 #include "components/signin/core/browser/signin_manager.h" |
28 #include "components/signin/core/browser/signin_manager_base.h" | 29 #include "components/signin/core/browser/signin_manager_base.h" |
29 #include "components/variations/net/variations_http_headers.h" | 30 #include "components/variations/net/variations_http_headers.h" |
30 #include "components/variations/variations_associated_data.h" | 31 #include "components/variations/variations_associated_data.h" |
31 #include "net/base/load_flags.h" | 32 #include "net/base/load_flags.h" |
32 #include "net/http/http_request_headers.h" | 33 #include "net/http/http_request_headers.h" |
33 #include "net/http/http_response_headers.h" | 34 #include "net/http/http_response_headers.h" |
34 #include "net/http/http_status_code.h" | 35 #include "net/http/http_status_code.h" |
35 #include "net/url_request/url_fetcher.h" | 36 #include "net/url_request/url_fetcher.h" |
36 #include "third_party/icu/source/common/unicode/uloc.h" | 37 #include "third_party/icu/source/common/unicode/uloc.h" |
37 #include "third_party/icu/source/common/unicode/utypes.h" | 38 #include "third_party/icu/source/common/unicode/utypes.h" |
38 | 39 |
39 using net::URLFetcher; | 40 using net::URLFetcher; |
40 using net::URLRequestContextGetter; | 41 using net::URLRequestContextGetter; |
41 using net::HttpRequestHeaders; | 42 using net::HttpRequestHeaders; |
42 using net::URLRequestStatus; | 43 using net::URLRequestStatus; |
| 44 using translate::LanguageModel; |
43 | 45 |
44 namespace ntp_snippets { | 46 namespace ntp_snippets { |
45 | 47 |
46 namespace { | 48 namespace { |
47 | 49 |
48 const char kChromeReaderApiScope[] = | 50 const char kChromeReaderApiScope[] = |
49 "https://www.googleapis.com/auth/webhistory"; | 51 "https://www.googleapis.com/auth/webhistory"; |
50 const char kContentSuggestionsApiScope[] = | 52 const char kContentSuggestionsApiScope[] = |
51 "https://www.googleapis.com/auth/chrome-content-suggestions"; | 53 "https://www.googleapis.com/auth/chrome-content-suggestions"; |
52 const char kSnippetsServerNonAuthorizedFormat[] = "%s?key=%s"; | 54 const char kSnippetsServerNonAuthorizedFormat[] = "%s?key=%s"; |
53 const char kAuthorizationRequestHeaderFormat[] = "Bearer %s"; | 55 const char kAuthorizationRequestHeaderFormat[] = "Bearer %s"; |
54 | 56 |
55 // Variation parameter for personalizing fetching of snippets. | 57 // Variation parameter for personalizing fetching of snippets. |
56 const char kPersonalizationName[] = "fetching_personalization"; | 58 const char kPersonalizationName[] = "fetching_personalization"; |
57 | 59 |
58 // Variation parameter for chrome-content-suggestions backend. | 60 // Variation parameter for chrome-content-suggestions backend. |
59 const char kContentSuggestionsBackend[] = "content_suggestions_backend"; | 61 const char kContentSuggestionsBackend[] = "content_suggestions_backend"; |
60 | 62 |
61 // Constants for possible values of the "fetching_personalization" parameter. | 63 // Constants for possible values of the "fetching_personalization" parameter. |
62 const char kPersonalizationPersonalString[] = "personal"; | 64 const char kPersonalizationPersonalString[] = "personal"; |
63 const char kPersonalizationNonPersonalString[] = "non_personal"; | 65 const char kPersonalizationNonPersonalString[] = "non_personal"; |
64 const char kPersonalizationBothString[] = "both"; // the default value | 66 const char kPersonalizationBothString[] = "both"; // the default value |
65 | 67 |
66 const int kMaxExcludedIds = 100; | 68 const int kMaxExcludedIds = 100; |
67 | 69 |
| 70 // Variation parameter for sending LanguageModel info to the server. |
| 71 const char kSendTopLanguagesName[] = "send_top_languages"; |
| 72 const char kSendTopLanguagesEnabled[] = "true"; |
| 73 const char kSendTopLanguagesDisabled[] = "false"; |
| 74 |
68 std::string FetchResultToString(NTPSnippetsFetcher::FetchResult result) { | 75 std::string FetchResultToString(NTPSnippetsFetcher::FetchResult result) { |
69 switch (result) { | 76 switch (result) { |
70 case NTPSnippetsFetcher::FetchResult::SUCCESS: | 77 case NTPSnippetsFetcher::FetchResult::SUCCESS: |
71 return "OK"; | 78 return "OK"; |
72 case NTPSnippetsFetcher::FetchResult::DEPRECATED_EMPTY_HOSTS: | 79 case NTPSnippetsFetcher::FetchResult::DEPRECATED_EMPTY_HOSTS: |
73 return "Cannot fetch for empty hosts list."; | 80 return "Cannot fetch for empty hosts list."; |
74 case NTPSnippetsFetcher::FetchResult::URL_REQUEST_STATUS_ERROR: | 81 case NTPSnippetsFetcher::FetchResult::URL_REQUEST_STATUS_ERROR: |
75 return "URLRequestStatus error"; | 82 return "URLRequestStatus error"; |
76 case NTPSnippetsFetcher::FetchResult::HTTP_ERROR: | 83 case NTPSnippetsFetcher::FetchResult::HTTP_ERROR: |
77 return "HTTP error"; | 84 return "HTTP error"; |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
110 NOTREACHED(); | 117 NOTREACHED(); |
111 return true; | 118 return true; |
112 } | 119 } |
113 | 120 |
114 std::string GetFetchEndpoint() { | 121 std::string GetFetchEndpoint() { |
115 std::string endpoint = variations::GetVariationParamValue( | 122 std::string endpoint = variations::GetVariationParamValue( |
116 ntp_snippets::kStudyName, kContentSuggestionsBackend); | 123 ntp_snippets::kStudyName, kContentSuggestionsBackend); |
117 return endpoint.empty() ? kChromeReaderServer : endpoint; | 124 return endpoint.empty() ? kChromeReaderServer : endpoint; |
118 } | 125 } |
119 | 126 |
| 127 bool IsSendingTopLanguagesEnabled() { |
| 128 std::string send_top_languages = variations::GetVariationParamValueByFeature( |
| 129 ntp_snippets::kArticleSuggestionsFeature, kSendTopLanguagesName); |
| 130 if (send_top_languages == kSendTopLanguagesEnabled) |
| 131 return true; |
| 132 if (!send_top_languages.empty() && |
| 133 send_top_languages != kSendTopLanguagesDisabled) { |
| 134 LOG(WARNING) << "Invalid value \"" << send_top_languages |
| 135 << "\" for variation parameter " << kSendTopLanguagesName; |
| 136 } |
| 137 return false; |
| 138 } |
| 139 |
120 bool UsesChromeContentSuggestionsAPI(const GURL& endpoint) { | 140 bool UsesChromeContentSuggestionsAPI(const GURL& endpoint) { |
121 if (endpoint == GURL(kChromeReaderServer)) | 141 if (endpoint == GURL(kChromeReaderServer)) |
122 return false; | 142 return false; |
123 | 143 |
124 if (endpoint != GURL(kContentSuggestionsServer) && | 144 if (endpoint != GURL(kContentSuggestionsServer) && |
125 endpoint != GURL(kContentSuggestionsDevServer) && | 145 endpoint != GURL(kContentSuggestionsDevServer) && |
126 endpoint != GURL(kContentSuggestionsAlphaServer)) { | 146 endpoint != GURL(kContentSuggestionsAlphaServer)) { |
127 LOG(WARNING) << "Unknown value for " << kContentSuggestionsBackend << ": " | 147 LOG(WARNING) << "Unknown value for " << kContentSuggestionsBackend << ": " |
128 << "assuming chromecontentsuggestions-style API"; | 148 << "assuming chromecontentsuggestions-style API"; |
129 } | 149 } |
(...skipping 26 matching lines...) Expand all Loading... |
156 return true; | 176 return true; |
157 } | 177 } |
158 | 178 |
159 // Translate the BCP 47 |language_code| into a posix locale string. | 179 // Translate the BCP 47 |language_code| into a posix locale string. |
160 std::string PosixLocaleFromBCP47Language(const std::string& language_code) { | 180 std::string PosixLocaleFromBCP47Language(const std::string& language_code) { |
161 char locale[ULOC_FULLNAME_CAPACITY]; | 181 char locale[ULOC_FULLNAME_CAPACITY]; |
162 UErrorCode error = U_ZERO_ERROR; | 182 UErrorCode error = U_ZERO_ERROR; |
163 // Translate the input to a posix locale. | 183 // Translate the input to a posix locale. |
164 uloc_forLanguageTag(language_code.c_str(), locale, ULOC_FULLNAME_CAPACITY, | 184 uloc_forLanguageTag(language_code.c_str(), locale, ULOC_FULLNAME_CAPACITY, |
165 nullptr, &error); | 185 nullptr, &error); |
166 DLOG_IF(WARNING, U_ZERO_ERROR != error) | 186 if (error != U_ZERO_ERROR) { |
167 << "Error in translating language code to a locale string: " << error; | 187 DLOG(WARNING) << "Error in translating language code to a locale string: " |
| 188 << error; |
| 189 return std::string(); |
| 190 } |
168 return locale; | 191 return locale; |
169 } | 192 } |
170 | 193 |
| 194 std::string ISO639FromPosixLocale(const std::string& locale) { |
| 195 char language[ULOC_LANG_CAPACITY]; |
| 196 UErrorCode error = U_ZERO_ERROR; |
| 197 uloc_getLanguage(locale.c_str(), language, ULOC_LANG_CAPACITY, &error); |
| 198 if (error != U_ZERO_ERROR) { |
| 199 DLOG(WARNING) |
| 200 << "Error in translating locale string to a ISO639 language code: " |
| 201 << error; |
| 202 return std::string(); |
| 203 } |
| 204 return language; |
| 205 } |
| 206 |
| 207 void AppendLanguageInfoToList(base::ListValue* list, |
| 208 const LanguageModel::LanguageInfo& info) { |
| 209 auto lang = base::MakeUnique<base::DictionaryValue>(); |
| 210 lang->SetString("language", info.language_code); |
| 211 lang->SetDouble("frequency", info.frequency); |
| 212 list->Append(std::move(lang)); |
| 213 } |
| 214 |
171 } // namespace | 215 } // namespace |
172 | 216 |
173 NTPSnippetsFetcher::FetchedCategory::FetchedCategory(Category c) | 217 NTPSnippetsFetcher::FetchedCategory::FetchedCategory(Category c) |
174 : category(c) {} | 218 : category(c) {} |
175 | 219 |
176 NTPSnippetsFetcher::FetchedCategory::FetchedCategory(FetchedCategory&&) = | 220 NTPSnippetsFetcher::FetchedCategory::FetchedCategory(FetchedCategory&&) = |
177 default; | 221 default; |
178 NTPSnippetsFetcher::FetchedCategory::~FetchedCategory() = default; | 222 NTPSnippetsFetcher::FetchedCategory::~FetchedCategory() = default; |
179 NTPSnippetsFetcher::FetchedCategory& NTPSnippetsFetcher::FetchedCategory:: | 223 NTPSnippetsFetcher::FetchedCategory& NTPSnippetsFetcher::FetchedCategory:: |
180 operator=(FetchedCategory&&) = default; | 224 operator=(FetchedCategory&&) = default; |
181 | 225 |
182 NTPSnippetsFetcher::NTPSnippetsFetcher( | 226 NTPSnippetsFetcher::NTPSnippetsFetcher( |
183 SigninManagerBase* signin_manager, | 227 SigninManagerBase* signin_manager, |
184 OAuth2TokenService* token_service, | 228 OAuth2TokenService* token_service, |
185 scoped_refptr<URLRequestContextGetter> url_request_context_getter, | 229 scoped_refptr<URLRequestContextGetter> url_request_context_getter, |
186 PrefService* pref_service, | 230 PrefService* pref_service, |
187 CategoryFactory* category_factory, | 231 CategoryFactory* category_factory, |
| 232 LanguageModel* language_model, |
188 const ParseJSONCallback& parse_json_callback, | 233 const ParseJSONCallback& parse_json_callback, |
189 const std::string& api_key) | 234 const std::string& api_key) |
190 : OAuth2TokenService::Consumer("ntp_snippets"), | 235 : OAuth2TokenService::Consumer("ntp_snippets"), |
191 signin_manager_(signin_manager), | 236 signin_manager_(signin_manager), |
192 token_service_(token_service), | 237 token_service_(token_service), |
193 waiting_for_refresh_token_(false), | 238 waiting_for_refresh_token_(false), |
194 url_request_context_getter_(std::move(url_request_context_getter)), | 239 url_request_context_getter_(std::move(url_request_context_getter)), |
195 category_factory_(category_factory), | 240 category_factory_(category_factory), |
| 241 language_model_(language_model), |
196 parse_json_callback_(parse_json_callback), | 242 parse_json_callback_(parse_json_callback), |
197 count_to_fetch_(0), | 243 count_to_fetch_(0), |
198 fetch_url_(GetFetchEndpoint()), | 244 fetch_url_(GetFetchEndpoint()), |
199 fetch_api_(UsesChromeContentSuggestionsAPI(fetch_url_) | 245 fetch_api_(UsesChromeContentSuggestionsAPI(fetch_url_) |
200 ? CHROME_CONTENT_SUGGESTIONS_API | 246 ? CHROME_CONTENT_SUGGESTIONS_API |
201 : CHROME_READER_API), | 247 : CHROME_READER_API), |
202 api_key_(api_key), | 248 api_key_(api_key), |
203 interactive_request_(false), | 249 interactive_request_(false), |
204 tick_clock_(new base::DefaultTickClock()), | 250 tick_clock_(new base::DefaultTickClock()), |
205 request_throttler_( | 251 request_throttler_( |
(...skipping 146 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
352 : "BACKGROUND_PREFETCH"); | 398 : "BACKGROUND_PREFETCH"); |
353 | 399 |
354 auto excluded = base::MakeUnique<base::ListValue>(); | 400 auto excluded = base::MakeUnique<base::ListValue>(); |
355 for (const auto& id : excluded_ids) { | 401 for (const auto& id : excluded_ids) { |
356 excluded->AppendString(id); | 402 excluded->AppendString(id); |
357 if (excluded->GetSize() >= kMaxExcludedIds) | 403 if (excluded->GetSize() >= kMaxExcludedIds) |
358 break; | 404 break; |
359 } | 405 } |
360 request->Set("excludedSuggestionIds", std::move(excluded)); | 406 request->Set("excludedSuggestionIds", std::move(excluded)); |
361 | 407 |
| 408 if (ui_language.frequency == 0 && other_top_language.frequency == 0) |
| 409 break; |
| 410 |
| 411 auto language_list = base::MakeUnique<base::ListValue>(); |
| 412 if (ui_language.frequency > 0) |
| 413 AppendLanguageInfoToList(language_list.get(), ui_language); |
| 414 if (other_top_language.frequency > 0) |
| 415 AppendLanguageInfoToList(language_list.get(), other_top_language); |
| 416 request->Set("top_languages", std::move(language_list)); |
| 417 |
362 // TODO(sfiera): support authentication and personalization | 418 // TODO(sfiera): support authentication and personalization |
363 // TODO(sfiera): support count_to_fetch | 419 // TODO(sfiera): support count_to_fetch |
364 break; | 420 break; |
365 } | 421 } |
366 } | 422 } |
367 | 423 |
368 std::string request_json; | 424 std::string request_json; |
369 bool success = base::JSONWriter::WriteWithOptions( | 425 bool success = base::JSONWriter::WriteWithOptions( |
370 *request, base::JSONWriter::OPTIONS_PRETTY_PRINT, &request_json); | 426 *request, base::JSONWriter::OPTIONS_PRETTY_PRINT, &request_json); |
371 DCHECK(success); | 427 DCHECK(success); |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
404 // Try to make fetching the files bit more robust even with poor connection. | 460 // Try to make fetching the files bit more robust even with poor connection. |
405 url_fetcher_->SetMaxRetriesOn5xx(3); | 461 url_fetcher_->SetMaxRetriesOn5xx(3); |
406 url_fetcher_->Start(); | 462 url_fetcher_->Start(); |
407 } | 463 } |
408 | 464 |
409 bool NTPSnippetsFetcher::UsesAuthentication() const { | 465 bool NTPSnippetsFetcher::UsesAuthentication() const { |
410 return (personalization_ == Personalization::kPersonal || | 466 return (personalization_ == Personalization::kPersonal || |
411 personalization_ == Personalization::kBoth); | 467 personalization_ == Personalization::kBoth); |
412 } | 468 } |
413 | 469 |
| 470 void NTPSnippetsFetcher::SetUpCommonFetchingParameters( |
| 471 RequestParams* params) const { |
| 472 params->fetch_api = fetch_api_; |
| 473 params->host_restricts = hosts_; |
| 474 params->user_locale = locale_; |
| 475 params->excluded_ids = excluded_ids_; |
| 476 params->count_to_fetch = count_to_fetch_; |
| 477 params->interactive_request = interactive_request_; |
| 478 // TODO(jkrcal): add the initializers into the struct and remove it from here |
| 479 // and from the unit-tests (building the request). |
| 480 params->ui_language.frequency = 0; |
| 481 params->other_top_language.frequency = 0; |
| 482 |
| 483 // TODO(jkrcal): Add language model factory for iOS and add fakes to tests so |
| 484 // that |language_model_| is never nullptr. Remove this check and add a DCHECK |
| 485 // into the constructor. |
| 486 if (!language_model_ || !IsSendingTopLanguagesEnabled()) |
| 487 return; |
| 488 |
| 489 params->ui_language.language_code = ISO639FromPosixLocale(locale_); |
| 490 params->ui_language.frequency = |
| 491 language_model_->GetLanguageFrequency(params->ui_language.language_code); |
| 492 |
| 493 std::vector<LanguageModel::LanguageInfo> top_languages = |
| 494 language_model_->GetTopLanguages(); |
| 495 for (const LanguageModel::LanguageInfo& info : top_languages) { |
| 496 if (info.language_code != params->ui_language.language_code) { |
| 497 params->other_top_language = info; |
| 498 break; |
| 499 } |
| 500 } |
| 501 } |
| 502 |
414 void NTPSnippetsFetcher::FetchSnippetsNonAuthenticated() { | 503 void NTPSnippetsFetcher::FetchSnippetsNonAuthenticated() { |
415 // When not providing OAuth token, we need to pass the Google API key. | 504 // When not providing OAuth token, we need to pass the Google API key. |
416 GURL url(base::StringPrintf(kSnippetsServerNonAuthorizedFormat, | 505 GURL url(base::StringPrintf(kSnippetsServerNonAuthorizedFormat, |
417 fetch_url_.spec().c_str(), api_key_.c_str())); | 506 fetch_url_.spec().c_str(), api_key_.c_str())); |
418 | 507 |
419 RequestParams params; | 508 RequestParams params; |
420 params.fetch_api = fetch_api_; | 509 SetUpCommonFetchingParameters(¶ms); |
421 params.host_restricts = hosts_; | |
422 params.excluded_ids = excluded_ids_; | |
423 params.count_to_fetch = count_to_fetch_; | |
424 params.interactive_request = interactive_request_; | |
425 params.user_locale = locale_; | |
426 FetchSnippetsImpl(url, std::string(), params.BuildRequest()); | 510 FetchSnippetsImpl(url, std::string(), params.BuildRequest()); |
427 } | 511 } |
428 | 512 |
429 void NTPSnippetsFetcher::FetchSnippetsAuthenticated( | 513 void NTPSnippetsFetcher::FetchSnippetsAuthenticated( |
430 const std::string& account_id, | 514 const std::string& account_id, |
431 const std::string& oauth_access_token) { | 515 const std::string& oauth_access_token) { |
432 RequestParams params; | 516 RequestParams params; |
433 params.fetch_api = fetch_api_; | 517 SetUpCommonFetchingParameters(¶ms); |
434 params.obfuscated_gaia_id = account_id; | 518 params.obfuscated_gaia_id = account_id; |
435 params.only_return_personalized_results = | 519 params.only_return_personalized_results = |
436 personalization_ == Personalization::kPersonal; | 520 personalization_ == Personalization::kPersonal; |
437 params.user_locale = locale_; | |
438 params.host_restricts = hosts_; | |
439 params.excluded_ids = excluded_ids_; | |
440 params.count_to_fetch = count_to_fetch_; | |
441 params.interactive_request = interactive_request_; | |
442 // TODO(jkrcal, treib): Add unit-tests for authenticated fetches. | 521 // TODO(jkrcal, treib): Add unit-tests for authenticated fetches. |
443 FetchSnippetsImpl(fetch_url_, | 522 FetchSnippetsImpl(fetch_url_, |
444 base::StringPrintf(kAuthorizationRequestHeaderFormat, | 523 base::StringPrintf(kAuthorizationRequestHeaderFormat, |
445 oauth_access_token.c_str()), | 524 oauth_access_token.c_str()), |
446 params.BuildRequest()); | 525 params.BuildRequest()); |
447 } | 526 } |
448 | 527 |
449 void NTPSnippetsFetcher::StartTokenRequest() { | 528 void NTPSnippetsFetcher::StartTokenRequest() { |
450 OAuth2TokenService::ScopeSet scopes; | 529 OAuth2TokenService::ScopeSet scopes; |
451 scopes.insert(fetch_api_ == CHROME_CONTENT_SUGGESTIONS_API | 530 scopes.insert(fetch_api_ == CHROME_CONTENT_SUGGESTIONS_API |
(...skipping 184 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
636 UMA_HISTOGRAM_ENUMERATION("NewTabPage.Snippets.FetchResult", | 715 UMA_HISTOGRAM_ENUMERATION("NewTabPage.Snippets.FetchResult", |
637 static_cast<int>(result), | 716 static_cast<int>(result), |
638 static_cast<int>(FetchResult::RESULT_MAX)); | 717 static_cast<int>(FetchResult::RESULT_MAX)); |
639 | 718 |
640 DVLOG(1) << "Fetch finished: " << last_status_; | 719 DVLOG(1) << "Fetch finished: " << last_status_; |
641 if (!snippets_available_callback_.is_null()) | 720 if (!snippets_available_callback_.is_null()) |
642 snippets_available_callback_.Run(std::move(fetched_categories)); | 721 snippets_available_callback_.Run(std::move(fetched_categories)); |
643 } | 722 } |
644 | 723 |
645 } // namespace ntp_snippets | 724 } // namespace ntp_snippets |
OLD | NEW |