OLD | NEW |
(Empty) | |
| 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 |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "components/ntp_snippets/remote/ntp_snippets_json_request.h" |
| 6 |
| 7 #include <algorithm> |
| 8 #include <utility> |
| 9 #include <vector> |
| 10 |
| 11 #include "base/command_line.h" |
| 12 #include "base/json/json_writer.h" |
| 13 #include "base/memory/ptr_util.h" |
| 14 #include "base/metrics/histogram_macros.h" |
| 15 #include "base/metrics/sparse_histogram.h" |
| 16 #include "base/strings/stringprintf.h" |
| 17 #include "base/time/tick_clock.h" |
| 18 #include "base/time/time.h" |
| 19 #include "base/values.h" |
| 20 #include "components/data_use_measurement/core/data_use_user_data.h" |
| 21 #include "components/ntp_snippets/category_info.h" |
| 22 #include "components/ntp_snippets/features.h" |
| 23 #include "components/ntp_snippets/remote/ntp_snippets_request_params.h" |
| 24 #include "components/ntp_snippets/user_classifier.h" |
| 25 #include "components/signin/core/browser/profile_oauth2_token_service.h" |
| 26 #include "components/signin/core/browser/signin_manager.h" |
| 27 #include "components/signin/core/browser/signin_manager_base.h" |
| 28 #include "components/variations/net/variations_http_headers.h" |
| 29 #include "components/variations/variations_associated_data.h" |
| 30 #include "grit/components_strings.h" |
| 31 #include "net/base/load_flags.h" |
| 32 #include "net/http/http_response_headers.h" |
| 33 #include "net/http/http_status_code.h" |
| 34 #include "net/url_request/url_fetcher.h" |
| 35 #include "net/url_request/url_request_context_getter.h" |
| 36 #include "third_party/icu/source/common/unicode/uloc.h" |
| 37 #include "third_party/icu/source/common/unicode/utypes.h" |
| 38 #include "ui/base/l10n/l10n_util.h" |
| 39 |
| 40 using net::URLFetcher; |
| 41 using net::URLRequestContextGetter; |
| 42 using net::HttpRequestHeaders; |
| 43 using net::URLRequestStatus; |
| 44 using translate::LanguageModel; |
| 45 |
| 46 namespace ntp_snippets { |
| 47 |
| 48 namespace internal { |
| 49 |
| 50 namespace { |
| 51 |
| 52 // Variation parameter for disabling the retry. |
| 53 const char kBackground5xxRetriesName[] = "background_5xx_retries_count"; |
| 54 |
| 55 const int kMaxExcludedIds = 100; |
| 56 |
| 57 // Variation parameter for sending LanguageModel info to the server. |
| 58 const char kSendTopLanguagesName[] = "send_top_languages"; |
| 59 |
| 60 // Variation parameter for sending UserClassifier info to the server. |
| 61 const char kSendUserClassName[] = "send_user_class"; |
| 62 |
| 63 const char kBooleanParameterEnabled[] = "true"; |
| 64 const char kBooleanParameterDisabled[] = "false"; |
| 65 |
| 66 bool IsBooleanParameterEnabled(const std::string& param_name, |
| 67 bool default_value) { |
| 68 std::string param_value = variations::GetVariationParamValueByFeature( |
| 69 ntp_snippets::kArticleSuggestionsFeature, param_name); |
| 70 if (param_value == kBooleanParameterEnabled) { |
| 71 return true; |
| 72 } |
| 73 if (param_value == kBooleanParameterDisabled) { |
| 74 return false; |
| 75 } |
| 76 if (!param_value.empty()) { |
| 77 LOG(WARNING) << "Invalid value \"" << param_value |
| 78 << "\" for variation parameter " << param_name; |
| 79 } |
| 80 return default_value; |
| 81 } |
| 82 |
| 83 int Get5xxRetryCount(bool interactive_request) { |
| 84 if (interactive_request) { |
| 85 return 2; |
| 86 } |
| 87 return std::max(0, variations::GetVariationParamByFeatureAsInt( |
| 88 ntp_snippets::kArticleSuggestionsFeature, |
| 89 kBackground5xxRetriesName, 0)); |
| 90 } |
| 91 |
| 92 bool IsSendingTopLanguagesEnabled() { |
| 93 return IsBooleanParameterEnabled(kSendTopLanguagesName, |
| 94 /*default_value=*/false); |
| 95 } |
| 96 |
| 97 bool IsSendingUserClassEnabled() { |
| 98 return IsBooleanParameterEnabled(kSendUserClassName, |
| 99 /*default_value=*/false); |
| 100 } |
| 101 |
| 102 // Translate the BCP 47 |language_code| into a posix locale string. |
| 103 std::string PosixLocaleFromBCP47Language(const std::string& language_code) { |
| 104 char locale[ULOC_FULLNAME_CAPACITY]; |
| 105 UErrorCode error = U_ZERO_ERROR; |
| 106 // Translate the input to a posix locale. |
| 107 uloc_forLanguageTag(language_code.c_str(), locale, ULOC_FULLNAME_CAPACITY, |
| 108 nullptr, &error); |
| 109 if (error != U_ZERO_ERROR) { |
| 110 DLOG(WARNING) << "Error in translating language code to a locale string: " |
| 111 << error; |
| 112 return std::string(); |
| 113 } |
| 114 return locale; |
| 115 } |
| 116 |
| 117 std::string ISO639FromPosixLocale(const std::string& locale) { |
| 118 char language[ULOC_LANG_CAPACITY]; |
| 119 UErrorCode error = U_ZERO_ERROR; |
| 120 uloc_getLanguage(locale.c_str(), language, ULOC_LANG_CAPACITY, &error); |
| 121 if (error != U_ZERO_ERROR) { |
| 122 DLOG(WARNING) |
| 123 << "Error in translating locale string to a ISO639 language code: " |
| 124 << error; |
| 125 return std::string(); |
| 126 } |
| 127 return language; |
| 128 } |
| 129 |
| 130 void AppendLanguageInfoToList(base::ListValue* list, |
| 131 const LanguageModel::LanguageInfo& info) { |
| 132 auto lang = base::MakeUnique<base::DictionaryValue>(); |
| 133 lang->SetString("language", info.language_code); |
| 134 lang->SetDouble("frequency", info.frequency); |
| 135 list->Append(std::move(lang)); |
| 136 } |
| 137 |
| 138 std::string GetUserClassString(UserClassifier::UserClass user_class) { |
| 139 switch (user_class) { |
| 140 case UserClassifier::UserClass::RARE_NTP_USER: |
| 141 return "RARE_NTP_USER"; |
| 142 case UserClassifier::UserClass::ACTIVE_NTP_USER: |
| 143 return "ACTIVE_NTP_USER"; |
| 144 case UserClassifier::UserClass::ACTIVE_SUGGESTIONS_CONSUMER: |
| 145 return "ACTIVE_SUGGESTIONS_CONSUMER"; |
| 146 } |
| 147 NOTREACHED(); |
| 148 return std::string(); |
| 149 } |
| 150 |
| 151 } // namespace |
| 152 |
| 153 CategoryInfo BuildArticleCategoryInfo( |
| 154 const base::Optional<base::string16>& title) { |
| 155 return CategoryInfo( |
| 156 title.has_value() ? title.value() |
| 157 : l10n_util::GetStringUTF16( |
| 158 IDS_NTP_ARTICLE_SUGGESTIONS_SECTION_HEADER), |
| 159 ContentSuggestionsCardLayout::FULL_CARD, |
| 160 // TODO(dgn): merge has_more_action and has_reload_action when we remove |
| 161 // the kFetchMoreFeature flag. See https://crbug.com/667752 |
| 162 /*has_more_action=*/base::FeatureList::IsEnabled(kFetchMoreFeature), |
| 163 /*has_reload_action=*/true, |
| 164 /*has_view_all_action=*/false, |
| 165 /*show_if_empty=*/true, |
| 166 l10n_util::GetStringUTF16(IDS_NTP_ARTICLE_SUGGESTIONS_SECTION_EMPTY)); |
| 167 } |
| 168 |
| 169 CategoryInfo BuildRemoteCategoryInfo(const base::string16& title, |
| 170 bool allow_fetching_more_results) { |
| 171 return CategoryInfo( |
| 172 title, ContentSuggestionsCardLayout::FULL_CARD, |
| 173 // TODO(dgn): merge has_more_action and has_reload_action when we remove |
| 174 // the kFetchMoreFeature flag. See https://crbug.com/667752 |
| 175 /*has_more_action=*/allow_fetching_more_results && |
| 176 base::FeatureList::IsEnabled(kFetchMoreFeature), |
| 177 /*has_reload_action=*/allow_fetching_more_results, |
| 178 /*has_view_all_action=*/false, |
| 179 /*show_if_empty=*/false, |
| 180 // TODO(tschumann): The message for no-articles is likely wrong |
| 181 // and needs to be added to the stubby protocol if we want to |
| 182 // support it. |
| 183 l10n_util::GetStringUTF16(IDS_NTP_ARTICLE_SUGGESTIONS_SECTION_EMPTY)); |
| 184 } |
| 185 |
| 186 NTPSnippetsJsonRequest::NTPSnippetsJsonRequest( |
| 187 base::Optional<Category> exclusive_category, |
| 188 base::TickClock* tick_clock, // Needed until destruction of the request. |
| 189 const ParseJSONCallback& callback) |
| 190 : exclusive_category_(exclusive_category), |
| 191 tick_clock_(tick_clock), |
| 192 parse_json_callback_(callback), |
| 193 weak_ptr_factory_(this) { |
| 194 creation_time_ = tick_clock_->NowTicks(); |
| 195 } |
| 196 |
| 197 NTPSnippetsJsonRequest::~NTPSnippetsJsonRequest() { |
| 198 LOG_IF(DFATAL, !request_completed_callback_.is_null()) |
| 199 << "The CompletionCallback was never called!"; |
| 200 } |
| 201 |
| 202 void NTPSnippetsJsonRequest::Start(CompletedCallback callback) { |
| 203 request_completed_callback_ = std::move(callback); |
| 204 url_fetcher_->Start(); |
| 205 } |
| 206 |
| 207 base::TimeDelta NTPSnippetsJsonRequest::GetFetchDuration() const { |
| 208 return tick_clock_->NowTicks() - creation_time_; |
| 209 } |
| 210 |
| 211 std::string NTPSnippetsJsonRequest::GetResponseString() const { |
| 212 std::string response; |
| 213 url_fetcher_->GetResponseAsString(&response); |
| 214 return response; |
| 215 } |
| 216 |
| 217 //////////////////////////////////////////////////////////////////////////////// |
| 218 // URLFetcherDelegate overrides |
| 219 void NTPSnippetsJsonRequest::OnURLFetchComplete(const net::URLFetcher* source) { |
| 220 DCHECK_EQ(url_fetcher_.get(), source); |
| 221 const URLRequestStatus& status = url_fetcher_->GetStatus(); |
| 222 int response = url_fetcher_->GetResponseCode(); |
| 223 UMA_HISTOGRAM_SPARSE_SLOWLY( |
| 224 "NewTabPage.Snippets.FetchHttpResponseOrErrorCode", |
| 225 status.is_success() ? response : status.error()); |
| 226 |
| 227 if (!status.is_success()) { |
| 228 std::move(request_completed_callback_) |
| 229 .Run(/*result=*/nullptr, FetchResult::URL_REQUEST_STATUS_ERROR, |
| 230 /*error_details=*/base::StringPrintf(" %d", status.error())); |
| 231 } else if (response != net::HTTP_OK) { |
| 232 // TODO(jkrcal): https://crbug.com/609084 |
| 233 // We need to deal with the edge case again where the auth |
| 234 // token expires just before we send the request (in which case we need to |
| 235 // fetch a new auth token). We should extract that into a common class |
| 236 // instead of adding it to every single class that uses auth tokens. |
| 237 std::move(request_completed_callback_) |
| 238 .Run(/*result=*/nullptr, FetchResult::HTTP_ERROR, |
| 239 /*error_details=*/base::StringPrintf(" %d", response)); |
| 240 } else { |
| 241 ParseJsonResponse(); |
| 242 } |
| 243 } |
| 244 |
| 245 void NTPSnippetsJsonRequest::ParseJsonResponse() { |
| 246 std::string json_string; |
| 247 bool stores_result_to_string = |
| 248 url_fetcher_->GetResponseAsString(&json_string); |
| 249 DCHECK(stores_result_to_string); |
| 250 |
| 251 parse_json_callback_.Run(json_string, |
| 252 base::Bind(&NTPSnippetsJsonRequest::OnJsonParsed, |
| 253 weak_ptr_factory_.GetWeakPtr()), |
| 254 base::Bind(&NTPSnippetsJsonRequest::OnJsonError, |
| 255 weak_ptr_factory_.GetWeakPtr())); |
| 256 } |
| 257 |
| 258 void NTPSnippetsJsonRequest::OnJsonParsed(std::unique_ptr<base::Value> result) { |
| 259 std::move(request_completed_callback_) |
| 260 .Run(std::move(result), FetchResult::SUCCESS, |
| 261 /*error_details=*/std::string()); |
| 262 } |
| 263 |
| 264 void NTPSnippetsJsonRequest::OnJsonError(const std::string& error) { |
| 265 std::string json_string; |
| 266 url_fetcher_->GetResponseAsString(&json_string); |
| 267 LOG(WARNING) << "Received invalid JSON (" << error << "): " << json_string; |
| 268 std::move(request_completed_callback_) |
| 269 .Run(/*result=*/nullptr, FetchResult::JSON_PARSE_ERROR, |
| 270 /*error_details=*/base::StringPrintf(" (error %s)", error.c_str())); |
| 271 } |
| 272 |
| 273 NTPSnippetsJsonRequest::Builder::Builder() |
| 274 : fetch_api_(CHROME_READER_API), |
| 275 personalization_(Personalization::kBoth), |
| 276 language_model_(nullptr) {} |
| 277 NTPSnippetsJsonRequest::Builder::Builder(NTPSnippetsJsonRequest::Builder&&) = |
| 278 default; |
| 279 NTPSnippetsJsonRequest::Builder::~Builder() = default; |
| 280 |
| 281 std::unique_ptr<NTPSnippetsJsonRequest> NTPSnippetsJsonRequest::Builder::Build() |
| 282 const { |
| 283 DCHECK(!url_.is_empty()); |
| 284 DCHECK(url_request_context_getter_); |
| 285 DCHECK(tick_clock_); |
| 286 auto request = base::MakeUnique<NTPSnippetsJsonRequest>( |
| 287 params_.exclusive_category, tick_clock_, parse_json_callback_); |
| 288 std::string body = BuildBody(); |
| 289 std::string headers = BuildHeaders(); |
| 290 request->url_fetcher_ = BuildURLFetcher(request.get(), headers, body); |
| 291 |
| 292 // Log the request for debugging network issues. |
| 293 VLOG(1) << "Sending a NTP snippets request to " << url_ << ":\n" |
| 294 << headers << "\n" |
| 295 << body; |
| 296 |
| 297 return request; |
| 298 } |
| 299 |
| 300 NTPSnippetsJsonRequest::Builder& |
| 301 NTPSnippetsJsonRequest::Builder::SetAuthentication( |
| 302 const std::string& account_id, |
| 303 const std::string& auth_header) { |
| 304 obfuscated_gaia_id_ = account_id; |
| 305 auth_header_ = auth_header; |
| 306 return *this; |
| 307 } |
| 308 |
| 309 NTPSnippetsJsonRequest::Builder& NTPSnippetsJsonRequest::Builder::SetFetchAPI( |
| 310 FetchAPI fetch_api) { |
| 311 fetch_api_ = fetch_api; |
| 312 return *this; |
| 313 } |
| 314 |
| 315 NTPSnippetsJsonRequest::Builder& |
| 316 NTPSnippetsJsonRequest::Builder::SetLanguageModel( |
| 317 const translate::LanguageModel* language_model) { |
| 318 language_model_ = language_model; |
| 319 return *this; |
| 320 } |
| 321 |
| 322 NTPSnippetsJsonRequest::Builder& NTPSnippetsJsonRequest::Builder::SetParams( |
| 323 const NTPSnippetsRequestParams& params) { |
| 324 params_ = params; |
| 325 return *this; |
| 326 } |
| 327 |
| 328 NTPSnippetsJsonRequest::Builder& |
| 329 NTPSnippetsJsonRequest::Builder::SetParseJsonCallback( |
| 330 ParseJSONCallback callback) { |
| 331 parse_json_callback_ = callback; |
| 332 return *this; |
| 333 } |
| 334 |
| 335 NTPSnippetsJsonRequest::Builder& |
| 336 NTPSnippetsJsonRequest::Builder::SetPersonalization( |
| 337 Personalization personalization) { |
| 338 personalization_ = personalization; |
| 339 return *this; |
| 340 } |
| 341 |
| 342 NTPSnippetsJsonRequest::Builder& NTPSnippetsJsonRequest::Builder::SetTickClock( |
| 343 base::TickClock* tick_clock) { |
| 344 tick_clock_ = tick_clock; |
| 345 return *this; |
| 346 } |
| 347 |
| 348 NTPSnippetsJsonRequest::Builder& NTPSnippetsJsonRequest::Builder::SetUrl( |
| 349 const GURL& url) { |
| 350 url_ = url; |
| 351 return *this; |
| 352 } |
| 353 |
| 354 NTPSnippetsJsonRequest::Builder& |
| 355 NTPSnippetsJsonRequest::Builder::SetUrlRequestContextGetter( |
| 356 const scoped_refptr<net::URLRequestContextGetter>& context_getter) { |
| 357 url_request_context_getter_ = context_getter; |
| 358 return *this; |
| 359 } |
| 360 |
| 361 NTPSnippetsJsonRequest::Builder& |
| 362 NTPSnippetsJsonRequest::Builder::SetUserClassifier( |
| 363 const UserClassifier& user_classifier) { |
| 364 if (IsSendingUserClassEnabled()) { |
| 365 user_class_ = GetUserClassString(user_classifier.GetUserClass()); |
| 366 } |
| 367 return *this; |
| 368 } |
| 369 |
| 370 std::string NTPSnippetsJsonRequest::Builder::BuildHeaders() const { |
| 371 net::HttpRequestHeaders headers; |
| 372 headers.SetHeader("Content-Type", "application/json; charset=UTF-8"); |
| 373 if (!auth_header_.empty()) { |
| 374 headers.SetHeader("Authorization", auth_header_); |
| 375 } |
| 376 // Add X-Client-Data header with experiment IDs from field trials. |
| 377 // Note: It's fine to pass in |is_signed_in| false, which does not affect |
| 378 // transmission of experiment ids coming from the variations server. |
| 379 bool is_signed_in = false; |
| 380 variations::AppendVariationHeaders(url_, |
| 381 false, // incognito |
| 382 false, // uma_enabled |
| 383 is_signed_in, &headers); |
| 384 return headers.ToString(); |
| 385 } |
| 386 |
| 387 std::string NTPSnippetsJsonRequest::Builder::BuildBody() const { |
| 388 auto request = base::MakeUnique<base::DictionaryValue>(); |
| 389 std::string user_locale = PosixLocaleFromBCP47Language(params_.language_code); |
| 390 switch (fetch_api_) { |
| 391 case CHROME_READER_API: { |
| 392 auto content_params = base::MakeUnique<base::DictionaryValue>(); |
| 393 content_params->SetBoolean("only_return_personalized_results", |
| 394 ReturnOnlyPersonalizedResults()); |
| 395 |
| 396 auto content_restricts = base::MakeUnique<base::ListValue>(); |
| 397 for (const auto* metadata : {"TITLE", "SNIPPET", "THUMBNAIL"}) { |
| 398 auto entry = base::MakeUnique<base::DictionaryValue>(); |
| 399 entry->SetString("type", "METADATA"); |
| 400 entry->SetString("value", metadata); |
| 401 content_restricts->Append(std::move(entry)); |
| 402 } |
| 403 |
| 404 auto local_scoring_params = base::MakeUnique<base::DictionaryValue>(); |
| 405 local_scoring_params->Set("content_params", std::move(content_params)); |
| 406 local_scoring_params->Set("content_restricts", |
| 407 std::move(content_restricts)); |
| 408 |
| 409 auto global_scoring_params = base::MakeUnique<base::DictionaryValue>(); |
| 410 global_scoring_params->SetInteger("num_to_return", |
| 411 params_.count_to_fetch); |
| 412 global_scoring_params->SetInteger("sort_type", 1); |
| 413 |
| 414 auto advanced = base::MakeUnique<base::DictionaryValue>(); |
| 415 advanced->Set("local_scoring_params", std::move(local_scoring_params)); |
| 416 advanced->Set("global_scoring_params", std::move(global_scoring_params)); |
| 417 |
| 418 request->SetString("response_detail_level", "STANDARD"); |
| 419 request->Set("advanced_options", std::move(advanced)); |
| 420 if (!obfuscated_gaia_id_.empty()) { |
| 421 request->SetString("obfuscated_gaia_id", obfuscated_gaia_id_); |
| 422 } |
| 423 if (!user_locale.empty()) { |
| 424 request->SetString("user_locale", user_locale); |
| 425 } |
| 426 break; |
| 427 } |
| 428 |
| 429 case CHROME_CONTENT_SUGGESTIONS_API: { |
| 430 if (!user_locale.empty()) { |
| 431 request->SetString("uiLanguage", user_locale); |
| 432 } |
| 433 |
| 434 request->SetString("priority", params_.interactive_request |
| 435 ? "USER_ACTION" |
| 436 : "BACKGROUND_PREFETCH"); |
| 437 |
| 438 auto excluded = base::MakeUnique<base::ListValue>(); |
| 439 for (const auto& id : params_.excluded_ids) { |
| 440 excluded->AppendString(id); |
| 441 if (excluded->GetSize() >= kMaxExcludedIds) { |
| 442 break; |
| 443 } |
| 444 } |
| 445 request->Set("excludedSuggestionIds", std::move(excluded)); |
| 446 |
| 447 if (!user_class_.empty()) { |
| 448 request->SetString("userActivenessClass", user_class_); |
| 449 } |
| 450 |
| 451 translate::LanguageModel::LanguageInfo ui_language; |
| 452 translate::LanguageModel::LanguageInfo other_top_language; |
| 453 PrepareLanguages(&ui_language, &other_top_language); |
| 454 |
| 455 if (ui_language.frequency == 0 && other_top_language.frequency == 0) { |
| 456 break; |
| 457 } |
| 458 |
| 459 auto language_list = base::MakeUnique<base::ListValue>(); |
| 460 if (ui_language.frequency > 0) { |
| 461 AppendLanguageInfoToList(language_list.get(), ui_language); |
| 462 } |
| 463 if (other_top_language.frequency > 0) { |
| 464 AppendLanguageInfoToList(language_list.get(), other_top_language); |
| 465 } |
| 466 request->Set("topLanguages", std::move(language_list)); |
| 467 |
| 468 // TODO(sfiera): Support only_return_personalized_results. |
| 469 // TODO(sfiera): Support count_to_fetch. |
| 470 break; |
| 471 } |
| 472 } |
| 473 |
| 474 std::string request_json; |
| 475 bool success = base::JSONWriter::WriteWithOptions( |
| 476 *request, base::JSONWriter::OPTIONS_PRETTY_PRINT, &request_json); |
| 477 DCHECK(success); |
| 478 return request_json; |
| 479 } |
| 480 |
| 481 std::unique_ptr<net::URLFetcher> |
| 482 NTPSnippetsJsonRequest::Builder::BuildURLFetcher( |
| 483 net::URLFetcherDelegate* delegate, |
| 484 const std::string& headers, |
| 485 const std::string& body) const { |
| 486 std::unique_ptr<net::URLFetcher> url_fetcher = |
| 487 net::URLFetcher::Create(url_, net::URLFetcher::POST, delegate); |
| 488 url_fetcher->SetRequestContext(url_request_context_getter_.get()); |
| 489 url_fetcher->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES | |
| 490 net::LOAD_DO_NOT_SAVE_COOKIES); |
| 491 data_use_measurement::DataUseUserData::AttachToFetcher( |
| 492 url_fetcher.get(), data_use_measurement::DataUseUserData::NTP_SNIPPETS); |
| 493 |
| 494 url_fetcher->SetExtraRequestHeaders(headers); |
| 495 url_fetcher->SetUploadData("application/json", body); |
| 496 |
| 497 // Fetchers are sometimes cancelled because a network change was detected. |
| 498 url_fetcher->SetAutomaticallyRetryOnNetworkChanges(3); |
| 499 url_fetcher->SetMaxRetriesOn5xx( |
| 500 Get5xxRetryCount(params_.interactive_request)); |
| 501 return url_fetcher; |
| 502 } |
| 503 |
| 504 void NTPSnippetsJsonRequest::Builder::PrepareLanguages( |
| 505 translate::LanguageModel::LanguageInfo* ui_language, |
| 506 translate::LanguageModel::LanguageInfo* other_top_language) const { |
| 507 // TODO(jkrcal): Add language model factory for iOS and add fakes to tests so |
| 508 // that |language_model| is never nullptr. Remove this check and add a DCHECK |
| 509 // into the constructor. |
| 510 if (!language_model_ || !IsSendingTopLanguagesEnabled()) { |
| 511 return; |
| 512 } |
| 513 |
| 514 // TODO(jkrcal): Is this back-and-forth converting necessary? |
| 515 ui_language->language_code = ISO639FromPosixLocale( |
| 516 PosixLocaleFromBCP47Language(params_.language_code)); |
| 517 ui_language->frequency = |
| 518 language_model_->GetLanguageFrequency(ui_language->language_code); |
| 519 |
| 520 std::vector<LanguageModel::LanguageInfo> top_languages = |
| 521 language_model_->GetTopLanguages(); |
| 522 for (const LanguageModel::LanguageInfo& info : top_languages) { |
| 523 if (info.language_code != ui_language->language_code) { |
| 524 *other_top_language = info; |
| 525 |
| 526 // Report to UMA how important the UI language is. |
| 527 DCHECK_GT(other_top_language->frequency, 0) |
| 528 << "GetTopLanguages() should not return languages with 0 frequency"; |
| 529 float ratio_ui_in_both_languages = |
| 530 ui_language->frequency / |
| 531 (ui_language->frequency + other_top_language->frequency); |
| 532 UMA_HISTOGRAM_PERCENTAGE( |
| 533 "NewTabPage.Languages.UILanguageRatioInTwoTopLanguages", |
| 534 ratio_ui_in_both_languages * 100); |
| 535 break; |
| 536 } |
| 537 } |
| 538 } |
| 539 |
| 540 } // namespace internal |
| 541 |
| 542 } // namespace ntp_snippets |
OLD | NEW |