| 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/remote_suggestions_fetcher.h" | 5 #include "components/ntp_snippets/remote/remote_suggestions_fetcher.h" |
| 6 | 6 |
| 7 #include <cstdlib> | 7 #include <cstdlib> |
| 8 #include <utility> | 8 #include <utility> |
| 9 | 9 |
| 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/memory/ptr_util.h" | 12 #include "base/memory/ptr_util.h" |
| 13 #include "base/metrics/histogram_macros.h" | 13 #include "base/metrics/histogram_macros.h" |
| 14 #include "base/metrics/sparse_histogram.h" | 14 #include "base/metrics/sparse_histogram.h" |
| 15 #include "base/path_service.h" | 15 #include "base/path_service.h" |
| 16 #include "base/strings/stringprintf.h" | 16 #include "base/strings/stringprintf.h" |
| 17 #include "base/strings/utf_string_conversions.h" | 17 #include "base/strings/utf_string_conversions.h" |
| 18 #include "base/time/default_tick_clock.h" | 18 #include "base/time/default_clock.h" |
| 19 #include "base/time/time.h" | 19 #include "base/time/time.h" |
| 20 #include "base/values.h" | 20 #include "base/values.h" |
| 21 #include "components/data_use_measurement/core/data_use_user_data.h" | 21 #include "components/data_use_measurement/core/data_use_user_data.h" |
| 22 #include "components/ntp_snippets/category.h" | 22 #include "components/ntp_snippets/category.h" |
| 23 #include "components/ntp_snippets/features.h" | 23 #include "components/ntp_snippets/features.h" |
| 24 #include "components/ntp_snippets/ntp_snippets_constants.h" | 24 #include "components/ntp_snippets/ntp_snippets_constants.h" |
| 25 #include "components/ntp_snippets/remote/request_params.h" | 25 #include "components/ntp_snippets/remote/request_params.h" |
| 26 #include "components/ntp_snippets/user_classifier.h" | 26 #include "components/ntp_snippets/user_classifier.h" |
| 27 #include "components/signin/core/browser/signin_manager.h" | 27 #include "components/signin/core/browser/signin_manager.h" |
| 28 #include "components/signin/core/browser/signin_manager_base.h" | 28 #include "components/signin/core/browser/signin_manager_base.h" |
| (...skipping 100 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 129 } | 129 } |
| 130 return true; | 130 return true; |
| 131 } | 131 } |
| 132 | 132 |
| 133 // Creates suggestions from dictionary values in |list| and adds them to | 133 // Creates suggestions from dictionary values in |list| and adds them to |
| 134 // |suggestions|. Returns true on success, false if anything went wrong. | 134 // |suggestions|. Returns true on success, false if anything went wrong. |
| 135 // |remote_category_id| is only used if |content_suggestions_api| is true. | 135 // |remote_category_id| is only used if |content_suggestions_api| is true. |
| 136 bool AddSuggestionsFromListValue(bool content_suggestions_api, | 136 bool AddSuggestionsFromListValue(bool content_suggestions_api, |
| 137 int remote_category_id, | 137 int remote_category_id, |
| 138 const base::ListValue& list, | 138 const base::ListValue& list, |
| 139 RemoteSuggestion::PtrVector* suggestions) { | 139 RemoteSuggestion::PtrVector* suggestions, |
| 140 const base::Time& fetch_time) { |
| 140 for (const auto& value : list) { | 141 for (const auto& value : list) { |
| 141 const base::DictionaryValue* dict = nullptr; | 142 const base::DictionaryValue* dict = nullptr; |
| 142 if (!value->GetAsDictionary(&dict)) { | 143 if (!value->GetAsDictionary(&dict)) { |
| 143 return false; | 144 return false; |
| 144 } | 145 } |
| 145 | 146 |
| 146 std::unique_ptr<RemoteSuggestion> suggestion; | 147 std::unique_ptr<RemoteSuggestion> suggestion; |
| 147 if (content_suggestions_api) { | 148 if (content_suggestions_api) { |
| 148 suggestion = RemoteSuggestion::CreateFromContentSuggestionsDictionary( | 149 suggestion = RemoteSuggestion::CreateFromContentSuggestionsDictionary( |
| 149 *dict, remote_category_id); | 150 *dict, remote_category_id, fetch_time); |
| 150 } else { | 151 } else { |
| 151 suggestion = RemoteSuggestion::CreateFromChromeReaderDictionary(*dict); | 152 suggestion = |
| 153 RemoteSuggestion::CreateFromChromeReaderDictionary(*dict, fetch_time); |
| 152 } | 154 } |
| 153 if (!suggestion) { | 155 if (!suggestion) { |
| 154 return false; | 156 return false; |
| 155 } | 157 } |
| 156 | 158 |
| 157 suggestions->push_back(std::move(suggestion)); | 159 suggestions->push_back(std::move(suggestion)); |
| 158 } | 160 } |
| 159 return true; | 161 return true; |
| 160 } | 162 } |
| 161 | 163 |
| 162 int GetMinuteOfTheDay(bool local_time, bool reduced_resolution) { | 164 int GetMinuteOfTheDay(bool local_time, |
| 163 base::Time now(base::Time::Now()); | 165 bool reduced_resolution, |
| 166 base::Clock* clock) { |
| 167 base::Time now(clock->Now()); |
| 164 base::Time::Exploded now_exploded{}; | 168 base::Time::Exploded now_exploded{}; |
| 165 local_time ? now.LocalExplode(&now_exploded) : now.UTCExplode(&now_exploded); | 169 local_time ? now.LocalExplode(&now_exploded) : now.UTCExplode(&now_exploded); |
| 166 int now_minute = reduced_resolution | 170 int now_minute = reduced_resolution |
| 167 ? now_exploded.minute / kFetchTimeHistogramResolution * | 171 ? now_exploded.minute / kFetchTimeHistogramResolution * |
| 168 kFetchTimeHistogramResolution | 172 kFetchTimeHistogramResolution |
| 169 : now_exploded.minute; | 173 : now_exploded.minute; |
| 170 return now_exploded.hour * 60 + now_minute; | 174 return now_exploded.hour * 60 + now_minute; |
| 171 } | 175 } |
| 172 | 176 |
| 173 // The response from the backend might include suggestions from multiple | 177 // The response from the backend might include suggestions from multiple |
| (...skipping 74 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 248 signin_manager_(signin_manager), | 252 signin_manager_(signin_manager), |
| 249 token_service_(token_service), | 253 token_service_(token_service), |
| 250 url_request_context_getter_(std::move(url_request_context_getter)), | 254 url_request_context_getter_(std::move(url_request_context_getter)), |
| 251 language_model_(language_model), | 255 language_model_(language_model), |
| 252 parse_json_callback_(parse_json_callback), | 256 parse_json_callback_(parse_json_callback), |
| 253 fetch_url_(GetFetchEndpoint()), | 257 fetch_url_(GetFetchEndpoint()), |
| 254 fetch_api_(UsesChromeContentSuggestionsAPI(fetch_url_) | 258 fetch_api_(UsesChromeContentSuggestionsAPI(fetch_url_) |
| 255 ? FetchAPI::CHROME_CONTENT_SUGGESTIONS_API | 259 ? FetchAPI::CHROME_CONTENT_SUGGESTIONS_API |
| 256 : FetchAPI::CHROME_READER_API), | 260 : FetchAPI::CHROME_READER_API), |
| 257 api_key_(api_key), | 261 api_key_(api_key), |
| 258 tick_clock_(new base::DefaultTickClock()), | 262 clock_(new base::DefaultClock()), |
| 259 user_classifier_(user_classifier), | 263 user_classifier_(user_classifier), |
| 260 request_throttler_rare_ntp_user_( | 264 request_throttler_rare_ntp_user_( |
| 261 pref_service, | 265 pref_service, |
| 262 RequestThrottler::RequestType:: | 266 RequestThrottler::RequestType:: |
| 263 CONTENT_SUGGESTION_FETCHER_RARE_NTP_USER), | 267 CONTENT_SUGGESTION_FETCHER_RARE_NTP_USER), |
| 264 request_throttler_active_ntp_user_( | 268 request_throttler_active_ntp_user_( |
| 265 pref_service, | 269 pref_service, |
| 266 RequestThrottler::RequestType:: | 270 RequestThrottler::RequestType:: |
| 267 CONTENT_SUGGESTION_FETCHER_ACTIVE_NTP_USER), | 271 CONTENT_SUGGESTION_FETCHER_ACTIVE_NTP_USER), |
| 268 request_throttler_active_suggestions_consumer_( | 272 request_throttler_active_suggestions_consumer_( |
| (...skipping 14 matching lines...) Expand all Loading... |
| 283 if (!DemandQuotaForRequest(params.interactive_request)) { | 287 if (!DemandQuotaForRequest(params.interactive_request)) { |
| 284 FetchFinished(OptionalFetchedCategories(), std::move(callback), | 288 FetchFinished(OptionalFetchedCategories(), std::move(callback), |
| 285 params.interactive_request | 289 params.interactive_request |
| 286 ? FetchResult::INTERACTIVE_QUOTA_ERROR | 290 ? FetchResult::INTERACTIVE_QUOTA_ERROR |
| 287 : FetchResult::NON_INTERACTIVE_QUOTA_ERROR, | 291 : FetchResult::NON_INTERACTIVE_QUOTA_ERROR, |
| 288 /*error_details=*/std::string()); | 292 /*error_details=*/std::string()); |
| 289 return; | 293 return; |
| 290 } | 294 } |
| 291 | 295 |
| 292 if (!params.interactive_request) { | 296 if (!params.interactive_request) { |
| 293 UMA_HISTOGRAM_SPARSE_SLOWLY("NewTabPage.Snippets.FetchTimeLocal", | 297 UMA_HISTOGRAM_SPARSE_SLOWLY( |
| 294 GetMinuteOfTheDay(/*local_time=*/true, | 298 "NewTabPage.Snippets.FetchTimeLocal", |
| 295 /*reduced_resolution=*/true)); | 299 GetMinuteOfTheDay(/*local_time=*/true, |
| 296 UMA_HISTOGRAM_SPARSE_SLOWLY("NewTabPage.Snippets.FetchTimeUTC", | 300 /*reduced_resolution=*/true, clock_.get())); |
| 297 GetMinuteOfTheDay(/*local_time=*/false, | 301 UMA_HISTOGRAM_SPARSE_SLOWLY( |
| 298 /*reduced_resolution=*/true)); | 302 "NewTabPage.Snippets.FetchTimeUTC", |
| 303 GetMinuteOfTheDay(/*local_time=*/false, |
| 304 /*reduced_resolution=*/true, clock_.get())); |
| 299 } | 305 } |
| 300 | 306 |
| 301 JsonRequest::Builder builder; | 307 JsonRequest::Builder builder; |
| 302 builder.SetFetchAPI(fetch_api_) | 308 builder.SetFetchAPI(fetch_api_) |
| 303 .SetFetchAPI(fetch_api_) | 309 .SetFetchAPI(fetch_api_) |
| 304 .SetLanguageModel(language_model_) | 310 .SetLanguageModel(language_model_) |
| 305 .SetParams(params) | 311 .SetParams(params) |
| 306 .SetParseJsonCallback(parse_json_callback_) | 312 .SetParseJsonCallback(parse_json_callback_) |
| 307 .SetTickClock(tick_clock_.get()) | 313 .SetClock(clock_.get()) |
| 308 .SetUrlRequestContextGetter(url_request_context_getter_) | 314 .SetUrlRequestContextGetter(url_request_context_getter_) |
| 309 .SetUserClassifier(*user_classifier_); | 315 .SetUserClassifier(*user_classifier_); |
| 310 | 316 |
| 311 if (signin_manager_->IsAuthenticated()) { | 317 if (signin_manager_->IsAuthenticated()) { |
| 312 // Signed-in: get OAuth token --> fetch suggestions. | 318 // Signed-in: get OAuth token --> fetch suggestions. |
| 313 oauth_token_retried_ = false; | 319 oauth_token_retried_ = false; |
| 314 pending_requests_.emplace(std::move(builder), std::move(callback)); | 320 pending_requests_.emplace(std::move(builder), std::move(callback)); |
| 315 StartTokenRequest(); | 321 StartTokenRequest(); |
| 316 } else if (signin_manager_->AuthInProgress()) { | 322 } else if (signin_manager_->AuthInProgress()) { |
| 317 // Currently signing in: wait for auth to finish (the refresh token) --> | 323 // Currently signing in: wait for auth to finish (the refresh token) --> |
| (...skipping 117 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 435 StartTokenRequest(); | 441 StartTokenRequest(); |
| 436 } | 442 } |
| 437 | 443 |
| 438 void RemoteSuggestionsFetcher::JsonRequestDone( | 444 void RemoteSuggestionsFetcher::JsonRequestDone( |
| 439 std::unique_ptr<JsonRequest> request, | 445 std::unique_ptr<JsonRequest> request, |
| 440 SnippetsAvailableCallback callback, | 446 SnippetsAvailableCallback callback, |
| 441 std::unique_ptr<base::Value> result, | 447 std::unique_ptr<base::Value> result, |
| 442 FetchResult status_code, | 448 FetchResult status_code, |
| 443 const std::string& error_details) { | 449 const std::string& error_details) { |
| 444 DCHECK(request); | 450 DCHECK(request); |
| 451 // Record the time when request for fetching remote content snippets finished. |
| 452 const base::Time fetch_time = clock_->Now(); |
| 453 |
| 445 last_fetch_json_ = request->GetResponseString(); | 454 last_fetch_json_ = request->GetResponseString(); |
| 446 | 455 |
| 447 UMA_HISTOGRAM_TIMES("NewTabPage.Snippets.FetchTime", | 456 UMA_HISTOGRAM_TIMES("NewTabPage.Snippets.FetchTime", |
| 448 request->GetFetchDuration()); | 457 request->GetFetchDuration()); |
| 449 | 458 |
| 450 if (!result) { | 459 if (!result) { |
| 451 FetchFinished(OptionalFetchedCategories(), std::move(callback), status_code, | 460 FetchFinished(OptionalFetchedCategories(), std::move(callback), status_code, |
| 452 error_details); | 461 error_details); |
| 453 return; | 462 return; |
| 454 } | 463 } |
| 455 FetchedCategoriesVector categories; | 464 FetchedCategoriesVector categories; |
| 456 if (!JsonToSnippets(*result, &categories)) { | 465 |
| 466 if (!JsonToSnippets(*result, &categories, fetch_time)) { |
| 457 LOG(WARNING) << "Received invalid snippets: " << last_fetch_json_; | 467 LOG(WARNING) << "Received invalid snippets: " << last_fetch_json_; |
| 458 FetchFinished(OptionalFetchedCategories(), std::move(callback), | 468 FetchFinished(OptionalFetchedCategories(), std::move(callback), |
| 459 FetchResult::INVALID_SNIPPET_CONTENT_ERROR, std::string()); | 469 FetchResult::INVALID_SNIPPET_CONTENT_ERROR, std::string()); |
| 460 return; | 470 return; |
| 461 } | 471 } |
| 462 // Filter out unwanted categories if necessary. | 472 // Filter out unwanted categories if necessary. |
| 463 // TODO(fhorschig): As soon as the server supports filtering by category, | 473 // TODO(fhorschig): As soon as the server supports filtering by category, |
| 464 // adjust the request instead of over-fetching and filtering here. | 474 // adjust the request instead of over-fetching and filtering here. |
| 465 FilterCategories(&categories, request->exclusive_category()); | 475 FilterCategories(&categories, request->exclusive_category()); |
| 466 | 476 |
| (...skipping 15 matching lines...) Expand all Loading... |
| 482 static_cast<int>(FetchResult::RESULT_MAX)); | 492 static_cast<int>(FetchResult::RESULT_MAX)); |
| 483 | 493 |
| 484 DVLOG(1) << "Fetch finished: " << last_status_; | 494 DVLOG(1) << "Fetch finished: " << last_status_; |
| 485 | 495 |
| 486 std::move(callback).Run(FetchResultToStatus(fetch_result), | 496 std::move(callback).Run(FetchResultToStatus(fetch_result), |
| 487 std::move(categories)); | 497 std::move(categories)); |
| 488 } | 498 } |
| 489 | 499 |
| 490 bool RemoteSuggestionsFetcher::JsonToSnippets( | 500 bool RemoteSuggestionsFetcher::JsonToSnippets( |
| 491 const base::Value& parsed, | 501 const base::Value& parsed, |
| 492 FetchedCategoriesVector* categories) { | 502 FetchedCategoriesVector* categories, |
| 503 const base::Time& fetch_time) { |
| 493 const base::DictionaryValue* top_dict = nullptr; | 504 const base::DictionaryValue* top_dict = nullptr; |
| 494 if (!parsed.GetAsDictionary(&top_dict)) { | 505 if (!parsed.GetAsDictionary(&top_dict)) { |
| 495 return false; | 506 return false; |
| 496 } | 507 } |
| 497 | 508 |
| 498 switch (fetch_api_) { | 509 switch (fetch_api_) { |
| 499 case FetchAPI::CHROME_READER_API: { | 510 case FetchAPI::CHROME_READER_API: { |
| 500 const int kUnusedRemoteCategoryId = -1; | 511 const int kUnusedRemoteCategoryId = -1; |
| 501 categories->push_back(FetchedCategory( | 512 categories->push_back(FetchedCategory( |
| 502 Category::FromKnownCategory(KnownCategories::ARTICLES), | 513 Category::FromKnownCategory(KnownCategories::ARTICLES), |
| 503 BuildArticleCategoryInfo(base::nullopt))); | 514 BuildArticleCategoryInfo(base::nullopt))); |
| 504 | 515 |
| 505 const base::ListValue* recos = nullptr; | 516 const base::ListValue* recos = nullptr; |
| 506 return top_dict->GetList("recos", &recos) && | 517 return top_dict->GetList("recos", &recos) && |
| 507 AddSuggestionsFromListValue(/*content_suggestions_api=*/false, | 518 AddSuggestionsFromListValue( |
| 508 kUnusedRemoteCategoryId, *recos, | 519 /*content_suggestions_api=*/false, kUnusedRemoteCategoryId, |
| 509 &categories->back().suggestions); | 520 *recos, &categories->back().suggestions, fetch_time); |
| 510 } | 521 } |
| 511 | 522 |
| 512 case FetchAPI::CHROME_CONTENT_SUGGESTIONS_API: { | 523 case FetchAPI::CHROME_CONTENT_SUGGESTIONS_API: { |
| 513 const base::ListValue* categories_value = nullptr; | 524 const base::ListValue* categories_value = nullptr; |
| 514 if (!top_dict->GetList("categories", &categories_value)) { | 525 if (!top_dict->GetList("categories", &categories_value)) { |
| 515 return false; | 526 return false; |
| 516 } | 527 } |
| 517 | 528 |
| 518 for (const auto& v : *categories_value) { | 529 for (const auto& v : *categories_value) { |
| 519 std::string utf8_title; | 530 std::string utf8_title; |
| 520 int remote_category_id = -1; | 531 int remote_category_id = -1; |
| 521 const base::DictionaryValue* category_value = nullptr; | 532 const base::DictionaryValue* category_value = nullptr; |
| 522 if (!(v->GetAsDictionary(&category_value) && | 533 if (!(v->GetAsDictionary(&category_value) && |
| 523 category_value->GetString("localizedTitle", &utf8_title) && | 534 category_value->GetString("localizedTitle", &utf8_title) && |
| 524 category_value->GetInteger("id", &remote_category_id) && | 535 category_value->GetInteger("id", &remote_category_id) && |
| 525 (remote_category_id > 0))) { | 536 (remote_category_id > 0))) { |
| 526 return false; | 537 return false; |
| 527 } | 538 } |
| 528 | 539 |
| 529 RemoteSuggestion::PtrVector suggestions; | 540 RemoteSuggestion::PtrVector suggestions; |
| 530 const base::ListValue* suggestions_list = nullptr; | 541 const base::ListValue* suggestions_list = nullptr; |
| 531 // Absence of a list of suggestions is treated as an empty list, which | 542 // Absence of a list of suggestions is treated as an empty list, which |
| 532 // is permissible. | 543 // is permissible. |
| 533 if (category_value->GetList("suggestions", &suggestions_list)) { | 544 if (category_value->GetList("suggestions", &suggestions_list)) { |
| 534 if (!AddSuggestionsFromListValue( | 545 if (!AddSuggestionsFromListValue( |
| 535 /*content_suggestions_api=*/true, remote_category_id, | 546 /*content_suggestions_api=*/true, remote_category_id, |
| 536 *suggestions_list, &suggestions)) { | 547 *suggestions_list, &suggestions, fetch_time)) { |
| 537 return false; | 548 return false; |
| 538 } | 549 } |
| 539 } | 550 } |
| 540 Category category = Category::FromRemoteCategory(remote_category_id); | 551 Category category = Category::FromRemoteCategory(remote_category_id); |
| 541 if (category.IsKnownCategory(KnownCategories::ARTICLES)) { | 552 if (category.IsKnownCategory(KnownCategories::ARTICLES)) { |
| 542 categories->push_back(FetchedCategory( | 553 categories->push_back(FetchedCategory( |
| 543 category, | 554 category, |
| 544 BuildArticleCategoryInfo(base::UTF8ToUTF16(utf8_title)))); | 555 BuildArticleCategoryInfo(base::UTF8ToUTF16(utf8_title)))); |
| 545 } else { | 556 } else { |
| 546 // TODO(tschumann): Right now, the backend does not yet populate this | 557 // TODO(tschumann): Right now, the backend does not yet populate this |
| (...skipping 24 matching lines...) Expand all Loading... |
| 571 interactive_request); | 582 interactive_request); |
| 572 case UserClassifier::UserClass::ACTIVE_SUGGESTIONS_CONSUMER: | 583 case UserClassifier::UserClass::ACTIVE_SUGGESTIONS_CONSUMER: |
| 573 return request_throttler_active_suggestions_consumer_ | 584 return request_throttler_active_suggestions_consumer_ |
| 574 .DemandQuotaForRequest(interactive_request); | 585 .DemandQuotaForRequest(interactive_request); |
| 575 } | 586 } |
| 576 NOTREACHED(); | 587 NOTREACHED(); |
| 577 return false; | 588 return false; |
| 578 } | 589 } |
| 579 | 590 |
| 580 } // namespace ntp_snippets | 591 } // namespace ntp_snippets |
| OLD | NEW |