| 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/ntp_snippets_fetcher.h" | 5 #include "components/ntp_snippets/ntp_snippets_fetcher.h" |
| 6 | 6 |
| 7 #include <stdlib.h> |
| 8 |
| 7 #include "base/command_line.h" | 9 #include "base/command_line.h" |
| 8 #include "base/files/file_path.h" | 10 #include "base/files/file_path.h" |
| 9 #include "base/files/file_util.h" | 11 #include "base/files/file_util.h" |
| 10 #include "base/metrics/histogram_macros.h" | 12 #include "base/metrics/histogram_macros.h" |
| 11 #include "base/metrics/sparse_histogram.h" | 13 #include "base/metrics/sparse_histogram.h" |
| 12 #include "base/path_service.h" | 14 #include "base/path_service.h" |
| 13 #include "base/strings/string_number_conversions.h" | 15 #include "base/strings/string_number_conversions.h" |
| 14 #include "base/strings/string_util.h" | 16 #include "base/strings/string_util.h" |
| 15 #include "base/strings/stringprintf.h" | 17 #include "base/strings/stringprintf.h" |
| 16 #include "base/time/default_tick_clock.h" | 18 #include "base/time/default_tick_clock.h" |
| 17 #include "base/values.h" | 19 #include "base/values.h" |
| 18 #include "components/data_use_measurement/core/data_use_user_data.h" | 20 #include "components/data_use_measurement/core/data_use_user_data.h" |
| 21 #include "components/ntp_snippets/ntp_snippets_constants.h" |
| 19 #include "components/ntp_snippets/switches.h" | 22 #include "components/ntp_snippets/switches.h" |
| 23 #include "components/signin/core/browser/profile_oauth2_token_service.h" |
| 24 #include "components/signin/core/browser/signin_manager.h" |
| 25 #include "components/signin/core/browser/signin_manager_base.h" |
| 26 #include "components/variations/variations_associated_data.h" |
| 20 #include "google_apis/google_api_keys.h" | 27 #include "google_apis/google_api_keys.h" |
| 21 #include "net/base/load_flags.h" | 28 #include "net/base/load_flags.h" |
| 22 #include "net/http/http_request_headers.h" | 29 #include "net/http/http_request_headers.h" |
| 23 #include "net/http/http_response_headers.h" | 30 #include "net/http/http_response_headers.h" |
| 24 #include "net/http/http_status_code.h" | 31 #include "net/http/http_status_code.h" |
| 25 #include "net/url_request/url_fetcher.h" | 32 #include "net/url_request/url_fetcher.h" |
| 33 #include "third_party/icu/source/common/unicode/uloc.h" |
| 34 #include "third_party/icu/source/common/unicode/utypes.h" |
| 26 | 35 |
| 27 using net::URLFetcher; | 36 using net::URLFetcher; |
| 28 using net::URLRequestContextGetter; | 37 using net::URLRequestContextGetter; |
| 29 using net::HttpRequestHeaders; | 38 using net::HttpRequestHeaders; |
| 30 using net::URLRequestStatus; | 39 using net::URLRequestStatus; |
| 31 | 40 |
| 32 namespace ntp_snippets { | 41 namespace ntp_snippets { |
| 33 | 42 |
| 34 namespace { | 43 namespace { |
| 35 | 44 |
| 36 const char kContentSnippetsServerFormat[] = | 45 const char kApiScope[] = "https://www.googleapis.com/auth/webhistory"; |
| 37 "https://chromereader-pa.googleapis.com/v1/fetch?key=%s"; | 46 const char kSnippetsServer[] = |
| 47 "https://chromereader-pa.googleapis.com/v1/fetch"; |
| 48 const char kSnippetsServerNonAuthorizedFormat[] = "%s?key=%s"; |
| 49 const char kAuthorizationRequestHeaderFormat[] = "Bearer %s"; |
| 50 |
| 51 // Variation parameter for the variant of fetching to use. |
| 52 const char kVariantName[] = "fetching_variant"; |
| 53 |
| 54 // Constants listing possible values of the "fetching_variant" parameter. |
| 55 const char kVariantRestrictedString[] = "restricted"; |
| 56 const char kVariantPersonalizedString[] = "personalized"; |
| 57 const char kVariantRestrictedPersonalizedString[] = "restricted_personalized"; |
| 38 | 58 |
| 39 const char kRequestParameterFormat[] = | 59 const char kRequestParameterFormat[] = |
| 40 "{" | 60 "{" |
| 41 " \"response_detail_level\": \"STANDARD\"," | 61 " \"response_detail_level\": \"STANDARD\"," |
| 62 "%s" // If authenticated - an obfuscated Gaia ID will be inserted here. |
| 42 " \"advanced_options\": {" | 63 " \"advanced_options\": {" |
| 43 " \"local_scoring_params\": {" | 64 " \"local_scoring_params\": {" |
| 44 " \"content_params\": {" | 65 " \"content_params\": {" |
| 45 " \"only_return_personalized_results\": false" | 66 " \"only_return_personalized_results\": false" |
| 67 "%s" // If authenticated - user segment (lang code) will be inserted here. |
| 46 " }," | 68 " }," |
| 47 " \"content_restricts\": {" | 69 " \"content_restricts\": {" |
| 48 " \"type\": \"METADATA\"," | 70 " \"type\": \"METADATA\"," |
| 49 " \"value\": \"TITLE\"" | 71 " \"value\": \"TITLE\"" |
| 50 " }," | 72 " }," |
| 51 " \"content_restricts\": {" | 73 " \"content_restricts\": {" |
| 52 " \"type\": \"METADATA\"," | 74 " \"type\": \"METADATA\"," |
| 53 " \"value\": \"SNIPPET\"" | 75 " \"value\": \"SNIPPET\"" |
| 54 " }," | 76 " }," |
| 55 " \"content_restricts\": {" | 77 " \"content_restricts\": {" |
| 56 " \"type\": \"METADATA\"," | 78 " \"type\": \"METADATA\"," |
| 57 " \"value\": \"THUMBNAIL\"" | 79 " \"value\": \"THUMBNAIL\"" |
| 58 " }" | 80 " }" |
| 59 "%s" | 81 "%s" // If host restricted - host restrictions will be inserted here. |
| 60 " }," | 82 " }," |
| 61 " \"global_scoring_params\": {" | 83 " \"global_scoring_params\": {" |
| 62 " \"num_to_return\": %i" | 84 " \"num_to_return\": %i," |
| 85 " \"sort_type\": 1" |
| 63 " }" | 86 " }" |
| 64 " }" | 87 " }" |
| 65 "}"; | 88 "}"; |
| 66 | 89 |
| 90 const char kGaiaIdFormat[] = " \"obfuscated_gaia_id\": \"%s\","; |
| 91 const char kUserSegmentFormat[] = " ,\"user_segment\": \"%s\""; |
| 67 const char kHostRestrictFormat[] = | 92 const char kHostRestrictFormat[] = |
| 68 " ,\"content_selectors\": {" | 93 " ,\"content_selectors\": {" |
| 69 " \"type\": \"HOST_RESTRICT\"," | 94 " \"type\": \"HOST_RESTRICT\"," |
| 70 " \"value\": \"%s\"" | 95 " \"value\": \"%s\"" |
| 71 " }"; | 96 " }"; |
| 72 | 97 |
| 73 std::string FetchResultToString(NTPSnippetsFetcher::FetchResult result) { | 98 std::string FetchResultToString(NTPSnippetsFetcher::FetchResult result) { |
| 74 switch (result) { | 99 switch (result) { |
| 75 case NTPSnippetsFetcher::FetchResult::SUCCESS: | 100 case NTPSnippetsFetcher::FetchResult::SUCCESS: |
| 76 return "OK"; | 101 return "OK"; |
| (...skipping 10 matching lines...) Expand all Loading... |
| 87 case NTPSnippetsFetcher::FetchResult::RESULT_MAX: | 112 case NTPSnippetsFetcher::FetchResult::RESULT_MAX: |
| 88 break; | 113 break; |
| 89 } | 114 } |
| 90 NOTREACHED(); | 115 NOTREACHED(); |
| 91 return "Unknown error"; | 116 return "Unknown error"; |
| 92 } | 117 } |
| 93 | 118 |
| 94 } // namespace | 119 } // namespace |
| 95 | 120 |
| 96 NTPSnippetsFetcher::NTPSnippetsFetcher( | 121 NTPSnippetsFetcher::NTPSnippetsFetcher( |
| 122 SigninManagerBase* signin_manager, |
| 123 OAuth2TokenService* token_service, |
| 97 scoped_refptr<URLRequestContextGetter> url_request_context_getter, | 124 scoped_refptr<URLRequestContextGetter> url_request_context_getter, |
| 98 const ParseJSONCallback& parse_json_callback, | 125 const ParseJSONCallback& parse_json_callback, |
| 99 bool is_stable_channel) | 126 bool is_stable_channel) |
| 100 : url_request_context_getter_(url_request_context_getter), | 127 : OAuth2TokenService::Consumer("ntp_snippets"), |
| 128 signin_manager_(signin_manager), |
| 129 token_service_(token_service), |
| 130 waiting_for_refresh_token_(false), |
| 131 url_request_context_getter_(url_request_context_getter), |
| 101 parse_json_callback_(parse_json_callback), | 132 parse_json_callback_(parse_json_callback), |
| 102 is_stable_channel_(is_stable_channel), | 133 is_stable_channel_(is_stable_channel), |
| 103 tick_clock_(new base::DefaultTickClock()), | 134 tick_clock_(new base::DefaultTickClock()), |
| 104 weak_ptr_factory_(this) {} | 135 weak_ptr_factory_(this) { |
| 136 // Parse the variation parameters and set the defaults if missing. |
| 137 std::string variant = variations::GetVariationParamValue( |
| 138 ntp_snippets::kStudyName, kVariantName); |
| 139 if (variant == kVariantRestrictedString) { |
| 140 variant_ = Variant::kRestricted; |
| 141 } else if (variant == kVariantPersonalizedString) { |
| 142 variant_ = Variant::kPersonalized; |
| 143 } else { |
| 144 variant_ = Variant::kRestrictedPersonalized; |
| 145 LOG_IF(WARNING, |
| 146 !variant.empty() && variant != kVariantRestrictedPersonalizedString) |
| 147 << "Unknown fetching variant provided: " << variant; |
| 148 } |
| 149 } |
| 105 | 150 |
| 106 NTPSnippetsFetcher::~NTPSnippetsFetcher() {} | 151 NTPSnippetsFetcher::~NTPSnippetsFetcher() { |
| 152 if (waiting_for_refresh_token_) |
| 153 token_service_->RemoveObserver(this); |
| 154 } |
| 107 | 155 |
| 108 void NTPSnippetsFetcher::SetCallback( | 156 void NTPSnippetsFetcher::SetCallback( |
| 109 const SnippetsAvailableCallback& callback) { | 157 const SnippetsAvailableCallback& callback) { |
| 110 snippets_available_callback_ = callback; | 158 snippets_available_callback_ = callback; |
| 111 } | 159 } |
| 112 | 160 |
| 113 void NTPSnippetsFetcher::FetchSnippetsFromHosts( | 161 void NTPSnippetsFetcher::FetchSnippetsFromHosts( |
| 114 const std::set<std::string>& hosts, int count) { | 162 const std::set<std::string>& hosts, |
| 115 std::string host_restricts; | 163 const std::string& language_code, |
| 164 int count) { |
| 165 hosts_ = hosts; |
| 116 fetch_start_time_ = tick_clock_->NowTicks(); | 166 fetch_start_time_ = tick_clock_->NowTicks(); |
| 117 if (!base::CommandLine::ForCurrentProcess()->HasSwitch( | 167 |
| 118 switches::kDontRestrict)) { | 168 if (UseHostRestriction() && hosts_.empty()) { |
| 119 if (hosts.empty()) { | 169 FetchFinished(OptionalSnippets(), FetchResult::EMPTY_HOSTS, |
| 120 FetchFinished(OptionalSnippets(), FetchResult::EMPTY_HOSTS, | 170 /*extra_message=*/std::string()); |
| 121 /*extra_message=*/std::string()); | 171 return; |
| 122 return; | 172 } |
| 173 |
| 174 // Translate the BCP 47 |language_code| into a posix locale string. |
| 175 char locale[ULOC_FULLNAME_CAPACITY]; |
| 176 UErrorCode error; |
| 177 uloc_forLanguageTag(language_code.c_str(), locale, ULOC_FULLNAME_CAPACITY, |
| 178 nullptr, &error); |
| 179 DLOG_IF(WARNING, U_ZERO_ERROR != error) |
| 180 << "Error in translating language code to a locale string: " << error; |
| 181 locale_ = locale; |
| 182 |
| 183 count_to_fetch_ = count; |
| 184 |
| 185 bool use_authentication = UseAuthentication(); |
| 186 |
| 187 if (use_authentication && signin_manager_->IsAuthenticated()) { |
| 188 // Signed-in: get OAuth token --> fetch snippets. |
| 189 StartTokenRequest(); |
| 190 } else if (use_authentication && signin_manager_->AuthInProgress()) { |
| 191 // Currently signing in: wait for auth to finish (the refresh token) --> |
| 192 // get OAuth token --> fetch snippets. |
| 193 if (!waiting_for_refresh_token_) { |
| 194 // Wait until we get a refresh token. |
| 195 waiting_for_refresh_token_ = true; |
| 196 token_service_->AddObserver(this); |
| 123 } | 197 } |
| 124 for (const std::string& host : hosts) | 198 } else { |
| 125 host_restricts += base::StringPrintf(kHostRestrictFormat, host.c_str()); | 199 // Not signed in: fetch snippets (without authentication). |
| 200 FetchSnippetsNonAuthenticated(); |
| 126 } | 201 } |
| 127 const std::string& key = is_stable_channel_ | 202 } |
| 128 ? google_apis::GetAPIKey() | 203 |
| 129 : google_apis::GetNonStableAPIKey(); | 204 void NTPSnippetsFetcher::FetchSnippetsImpl(const GURL& url, |
| 130 std::string url = | 205 const std::string& auth_header, |
| 131 base::StringPrintf(kContentSnippetsServerFormat, key.c_str()); | 206 const std::string& request) { |
| 132 url_fetcher_ = URLFetcher::Create(GURL(url), URLFetcher::POST, this); | 207 url_fetcher_ = URLFetcher::Create(url, URLFetcher::POST, this); |
| 208 |
| 133 url_fetcher_->SetRequestContext(url_request_context_getter_.get()); | 209 url_fetcher_->SetRequestContext(url_request_context_getter_.get()); |
| 134 url_fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES | | 210 url_fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES | |
| 135 net::LOAD_DO_NOT_SAVE_COOKIES); | 211 net::LOAD_DO_NOT_SAVE_COOKIES); |
| 212 |
| 136 data_use_measurement::DataUseUserData::AttachToFetcher( | 213 data_use_measurement::DataUseUserData::AttachToFetcher( |
| 137 url_fetcher_.get(), data_use_measurement::DataUseUserData::NTP_SNIPPETS); | 214 url_fetcher_.get(), data_use_measurement::DataUseUserData::NTP_SNIPPETS); |
| 215 |
| 138 HttpRequestHeaders headers; | 216 HttpRequestHeaders headers; |
| 217 if (!auth_header.empty()) |
| 218 headers.SetHeader("Authorization", auth_header); |
| 139 headers.SetHeader("Content-Type", "application/json; charset=UTF-8"); | 219 headers.SetHeader("Content-Type", "application/json; charset=UTF-8"); |
| 140 url_fetcher_->SetExtraRequestHeaders(headers.ToString()); | 220 url_fetcher_->SetExtraRequestHeaders(headers.ToString()); |
| 141 url_fetcher_->SetUploadData("application/json", | 221 url_fetcher_->SetUploadData("application/json", request); |
| 142 base::StringPrintf(kRequestParameterFormat, | |
| 143 host_restricts.c_str(), | |
| 144 count)); | |
| 145 | |
| 146 // Fetchers are sometimes cancelled because a network change was detected. | 222 // Fetchers are sometimes cancelled because a network change was detected. |
| 147 url_fetcher_->SetAutomaticallyRetryOnNetworkChanges(3); | 223 url_fetcher_->SetAutomaticallyRetryOnNetworkChanges(3); |
| 148 // Try to make fetching the files bit more robust even with poor connection. | 224 // Try to make fetching the files bit more robust even with poor connection. |
| 149 url_fetcher_->SetMaxRetriesOn5xx(3); | 225 url_fetcher_->SetMaxRetriesOn5xx(3); |
| 150 url_fetcher_->Start(); | 226 url_fetcher_->Start(); |
| 151 } | 227 } |
| 152 | 228 |
| 229 std::string NTPSnippetsFetcher::GetHostRestricts() const { |
| 230 std::string host_restricts; |
| 231 if (UseHostRestriction()) { |
| 232 for (const std::string& host : hosts_) |
| 233 host_restricts += base::StringPrintf(kHostRestrictFormat, host.c_str()); |
| 234 } |
| 235 return host_restricts; |
| 236 } |
| 237 |
| 238 bool NTPSnippetsFetcher::UseHostRestriction() const { |
| 239 return (variant_ == Variant::kRestricted || |
| 240 variant_ == Variant::kRestrictedPersonalized) && |
| 241 !base::CommandLine::ForCurrentProcess()->HasSwitch( |
| 242 switches::kDontRestrict); |
| 243 } |
| 244 |
| 245 bool NTPSnippetsFetcher::UseAuthentication() const { |
| 246 return (variant_ == Variant::kPersonalized || |
| 247 variant_ == Variant::kRestrictedPersonalized); |
| 248 } |
| 249 |
| 250 void NTPSnippetsFetcher::FetchSnippetsNonAuthenticated() { |
| 251 // When not providing OAuth token, we need to pass the Google API key. |
| 252 const std::string& key = is_stable_channel_ |
| 253 ? google_apis::GetAPIKey() |
| 254 : google_apis::GetNonStableAPIKey(); |
| 255 GURL url(base::StringPrintf(kSnippetsServerNonAuthorizedFormat, |
| 256 kSnippetsServer, key.c_str())); |
| 257 |
| 258 FetchSnippetsImpl( |
| 259 url, std::string(), |
| 260 base::StringPrintf(kRequestParameterFormat, "", "", |
| 261 GetHostRestricts().c_str(), count_to_fetch_)); |
| 262 } |
| 263 |
| 264 void NTPSnippetsFetcher::FetchSnippetsAuthenticated( |
| 265 const std::string& account_id, |
| 266 const std::string& oauth_access_token) { |
| 267 std::string auth = base::StringPrintf(kGaiaIdFormat, account_id.c_str()); |
| 268 std::string user_segment = |
| 269 base::StringPrintf(kUserSegmentFormat, locale_.c_str()); |
| 270 |
| 271 FetchSnippetsImpl( |
| 272 GURL(kSnippetsServer), |
| 273 base::StringPrintf(kAuthorizationRequestHeaderFormat, |
| 274 oauth_access_token.c_str()), |
| 275 base::StringPrintf(kRequestParameterFormat, auth.c_str(), |
| 276 user_segment.c_str(), GetHostRestricts().c_str(), |
| 277 count_to_fetch_)); |
| 278 } |
| 279 |
| 280 void NTPSnippetsFetcher::StartTokenRequest() { |
| 281 OAuth2TokenService::ScopeSet scopes; |
| 282 scopes.insert(kApiScope); |
| 283 oauth_request_ = token_service_->StartRequest( |
| 284 signin_manager_->GetAuthenticatedAccountId(), scopes, this); |
| 285 } |
| 286 |
| 287 //////////////////////////////////////////////////////////////////////////////// |
| 288 // OAuth2TokenService::Consumer overrides |
| 289 void NTPSnippetsFetcher::OnGetTokenSuccess( |
| 290 const OAuth2TokenService::Request* request, |
| 291 const std::string& access_token, |
| 292 const base::Time& expiration_time) { |
| 293 // Delete the request after we leave this method. |
| 294 std::unique_ptr<OAuth2TokenService::Request> oauth_request( |
| 295 std::move(oauth_request_)); |
| 296 DCHECK_EQ(oauth_request.get(), request) |
| 297 << "Got tokens from some previous request"; |
| 298 |
| 299 FetchSnippetsAuthenticated(oauth_request->GetAccountId(), access_token); |
| 300 } |
| 301 |
| 302 void NTPSnippetsFetcher::OnGetTokenFailure( |
| 303 const OAuth2TokenService::Request* request, |
| 304 const GoogleServiceAuthError& error) { |
| 305 oauth_request_.reset(); |
| 306 DLOG(ERROR) << "Unable to get token: " << error.ToString() |
| 307 << " - fetching the snippets without authentication."; |
| 308 |
| 309 // Fallback to fetching non-authenticated tokens. |
| 310 FetchSnippetsNonAuthenticated(); |
| 311 } |
| 312 |
| 313 //////////////////////////////////////////////////////////////////////////////// |
| 314 // OAuth2TokenService::Observer overrides |
| 315 void NTPSnippetsFetcher::OnRefreshTokenAvailable( |
| 316 const std::string& account_id) { |
| 317 // Only react on tokens for the account the user has signed in with. |
| 318 if (account_id != signin_manager_->GetAuthenticatedAccountId()) |
| 319 return; |
| 320 |
| 321 token_service_->RemoveObserver(this); |
| 322 waiting_for_refresh_token_ = false; |
| 323 StartTokenRequest(); |
| 324 } |
| 325 |
| 153 //////////////////////////////////////////////////////////////////////////////// | 326 //////////////////////////////////////////////////////////////////////////////// |
| 154 // URLFetcherDelegate overrides | 327 // URLFetcherDelegate overrides |
| 155 void NTPSnippetsFetcher::OnURLFetchComplete(const URLFetcher* source) { | 328 void NTPSnippetsFetcher::OnURLFetchComplete(const URLFetcher* source) { |
| 156 DCHECK_EQ(url_fetcher_.get(), source); | 329 DCHECK_EQ(url_fetcher_.get(), source); |
| 157 | 330 |
| 158 const URLRequestStatus& status = source->GetStatus(); | 331 const URLRequestStatus& status = source->GetStatus(); |
| 159 | 332 |
| 160 UMA_HISTOGRAM_SPARSE_SLOWLY( | 333 UMA_HISTOGRAM_SPARSE_SLOWLY( |
| 161 "NewTabPage.Snippets.FetchHttpResponseOrErrorCode", | 334 "NewTabPage.Snippets.FetchHttpResponseOrErrorCode", |
| 162 status.is_success() ? source->GetResponseCode() : status.error()); | 335 status.is_success() ? source->GetResponseCode() : status.error()); |
| 163 | 336 |
| 164 if (!status.is_success()) { | 337 if (!status.is_success()) { |
| 165 FetchFinished(OptionalSnippets(), FetchResult::URL_REQUEST_STATUS_ERROR, | 338 FetchFinished(OptionalSnippets(), FetchResult::URL_REQUEST_STATUS_ERROR, |
| 166 /*extra_message=*/base::StringPrintf(" %d", status.error())); | 339 /*extra_message=*/base::StringPrintf(" %d", status.error())); |
| 167 } else if (source->GetResponseCode() != net::HTTP_OK) { | 340 } else if (source->GetResponseCode() != net::HTTP_OK) { |
| 341 // TODO(jkrcal): https://crbug.com/609084 |
| 342 // We need to deal with the edge case again where the auth |
| 343 // token expires just before we send the request (in which case we need to |
| 344 // fetch a new auth token). We should extract that into a common class |
| 345 // instead of adding it to every single class that uses auth tokens. |
| 168 FetchFinished( | 346 FetchFinished( |
| 169 OptionalSnippets(), FetchResult::HTTP_ERROR, | 347 OptionalSnippets(), FetchResult::HTTP_ERROR, |
| 170 /*extra_message=*/base::StringPrintf(" %d", source->GetResponseCode())); | 348 /*extra_message=*/base::StringPrintf(" %d", source->GetResponseCode())); |
| 171 } else { | 349 } else { |
| 172 bool stores_result_to_string = source->GetResponseAsString( | 350 bool stores_result_to_string = source->GetResponseAsString( |
| 173 &last_fetch_json_); | 351 &last_fetch_json_); |
| 174 DCHECK(stores_result_to_string); | 352 DCHECK(stores_result_to_string); |
| 175 | 353 |
| 176 parse_json_callback_.Run( | 354 parse_json_callback_.Run( |
| 177 last_fetch_json_, | 355 last_fetch_json_, |
| (...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 217 tick_clock_->NowTicks() - fetch_start_time_); | 395 tick_clock_->NowTicks() - fetch_start_time_); |
| 218 UMA_HISTOGRAM_ENUMERATION("NewTabPage.Snippets.FetchResult", | 396 UMA_HISTOGRAM_ENUMERATION("NewTabPage.Snippets.FetchResult", |
| 219 static_cast<int>(result), | 397 static_cast<int>(result), |
| 220 static_cast<int>(FetchResult::RESULT_MAX)); | 398 static_cast<int>(FetchResult::RESULT_MAX)); |
| 221 | 399 |
| 222 if (!snippets_available_callback_.is_null()) | 400 if (!snippets_available_callback_.is_null()) |
| 223 snippets_available_callback_.Run(std::move(snippets)); | 401 snippets_available_callback_.Run(std::move(snippets)); |
| 224 } | 402 } |
| 225 | 403 |
| 226 } // namespace ntp_snippets | 404 } // namespace ntp_snippets |
| OLD | NEW |