| OLD | NEW |
| (Empty) |
| 1 // Copyright 2015 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_tiles/popular_sites.h" | |
| 6 | |
| 7 #include <stddef.h> | |
| 8 | |
| 9 #include "base/bind.h" | |
| 10 #include "base/command_line.h" | |
| 11 #include "base/files/file_path.h" | |
| 12 #include "base/files/file_util.h" | |
| 13 #include "base/files/important_file_writer.h" | |
| 14 #include "base/json/json_reader.h" | |
| 15 #include "base/json/json_writer.h" | |
| 16 #include "base/path_service.h" | |
| 17 #include "base/strings/string_util.h" | |
| 18 #include "base/strings/stringprintf.h" | |
| 19 #include "base/task_runner_util.h" | |
| 20 #include "base/time/time.h" | |
| 21 #include "base/values.h" | |
| 22 #include "components/data_use_measurement/core/data_use_user_data.h" | |
| 23 #include "components/google/core/browser/google_util.h" | |
| 24 #include "components/ntp_tiles/constants.h" | |
| 25 #include "components/ntp_tiles/pref_names.h" | |
| 26 #include "components/ntp_tiles/switches.h" | |
| 27 #include "components/pref_registry/pref_registry_syncable.h" | |
| 28 #include "components/prefs/pref_service.h" | |
| 29 #include "components/search_engines/search_engine_type.h" | |
| 30 #include "components/search_engines/template_url_service.h" | |
| 31 #include "components/variations/service/variations_service.h" | |
| 32 #include "components/variations/variations_associated_data.h" | |
| 33 #include "net/base/load_flags.h" | |
| 34 #include "net/http/http_status_code.h" | |
| 35 | |
| 36 #if defined(OS_IOS) | |
| 37 #include "components/ntp_tiles/country_code_ios.h" | |
| 38 #endif | |
| 39 | |
| 40 using net::URLFetcher; | |
| 41 using variations::VariationsService; | |
| 42 | |
| 43 namespace ntp_tiles { | |
| 44 | |
| 45 namespace { | |
| 46 | |
| 47 const char kPopularSitesURLFormat[] = | |
| 48 "https://www.gstatic.com/chrome/ntp/suggested_sites_%s_%s.json"; | |
| 49 const char kPopularSitesDefaultCountryCode[] = "DEFAULT"; | |
| 50 const char kPopularSitesDefaultVersion[] = "5"; | |
| 51 const char kPopularSitesLocalFilename[] = "suggested_sites.json"; | |
| 52 const int kPopularSitesRedownloadIntervalHours = 24; | |
| 53 | |
| 54 const char kPopularSitesLastDownloadPref[] = "popular_sites_last_download"; | |
| 55 const char kPopularSitesURLPref[] = "popular_sites_url"; | |
| 56 | |
| 57 GURL GetPopularSitesURL(const std::string& country, | |
| 58 const std::string& version) { | |
| 59 return GURL(base::StringPrintf(kPopularSitesURLFormat, country.c_str(), | |
| 60 version.c_str())); | |
| 61 } | |
| 62 | |
| 63 // Extract the country from the default search engine if the default search | |
| 64 // engine is Google. | |
| 65 std::string GetDefaultSearchEngineCountryCode( | |
| 66 const TemplateURLService* template_url_service) { | |
| 67 DCHECK(template_url_service); | |
| 68 | |
| 69 base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess(); | |
| 70 if (!cmd_line->HasSwitch( | |
| 71 ntp_tiles::switches::kEnableNTPSearchEngineCountryDetection)) | |
| 72 return std::string(); | |
| 73 | |
| 74 const TemplateURL* default_provider = | |
| 75 template_url_service->GetDefaultSearchProvider(); | |
| 76 // It's possible to not have a default provider in the case that the default | |
| 77 // search engine is defined by policy. | |
| 78 if (default_provider) { | |
| 79 bool is_google_search_engine = | |
| 80 default_provider->GetEngineType( | |
| 81 template_url_service->search_terms_data()) == | |
| 82 SearchEngineType::SEARCH_ENGINE_GOOGLE; | |
| 83 | |
| 84 if (is_google_search_engine) { | |
| 85 GURL search_url = default_provider->GenerateSearchURL( | |
| 86 template_url_service->search_terms_data()); | |
| 87 return google_util::GetGoogleCountryCode(search_url); | |
| 88 } | |
| 89 } | |
| 90 | |
| 91 return std::string(); | |
| 92 } | |
| 93 | |
| 94 std::string GetVariationCountry() { | |
| 95 return variations::GetVariationParamValue(kPopularSitesFieldTrialName, | |
| 96 "country"); | |
| 97 } | |
| 98 | |
| 99 std::string GetVariationVersion() { | |
| 100 return variations::GetVariationParamValue(kPopularSitesFieldTrialName, | |
| 101 "version"); | |
| 102 } | |
| 103 | |
| 104 // Must run on the blocking thread pool. | |
| 105 bool WriteJsonToFile(const base::FilePath& local_path, | |
| 106 const base::Value* json) { | |
| 107 std::string json_string; | |
| 108 return base::JSONWriter::Write(*json, &json_string) && | |
| 109 base::ImportantFileWriter::WriteFileAtomically(local_path, | |
| 110 json_string); | |
| 111 } | |
| 112 | |
| 113 } // namespace | |
| 114 | |
| 115 PopularSites::Site::Site(const base::string16& title, | |
| 116 const GURL& url, | |
| 117 const GURL& favicon_url, | |
| 118 const GURL& large_icon_url, | |
| 119 const GURL& thumbnail_url) | |
| 120 : title(title), | |
| 121 url(url), | |
| 122 favicon_url(favicon_url), | |
| 123 large_icon_url(large_icon_url), | |
| 124 thumbnail_url(thumbnail_url) {} | |
| 125 | |
| 126 PopularSites::Site::Site(const Site& other) = default; | |
| 127 | |
| 128 PopularSites::Site::~Site() {} | |
| 129 | |
| 130 PopularSitesImpl::PopularSitesImpl( | |
| 131 const scoped_refptr<base::SequencedWorkerPool>& blocking_pool, | |
| 132 PrefService* prefs, | |
| 133 const TemplateURLService* template_url_service, | |
| 134 VariationsService* variations_service, | |
| 135 net::URLRequestContextGetter* download_context, | |
| 136 const base::FilePath& directory, | |
| 137 ParseJSONCallback parse_json) | |
| 138 : blocking_runner_(blocking_pool->GetTaskRunnerWithShutdownBehavior( | |
| 139 base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN)), | |
| 140 prefs_(prefs), | |
| 141 template_url_service_(template_url_service), | |
| 142 variations_(variations_service), | |
| 143 download_context_(download_context), | |
| 144 local_path_(directory.empty() | |
| 145 ? base::FilePath() | |
| 146 : directory.AppendASCII(kPopularSitesLocalFilename)), | |
| 147 parse_json_(std::move(parse_json)), | |
| 148 is_fallback_(false), | |
| 149 weak_ptr_factory_(this) {} | |
| 150 | |
| 151 PopularSitesImpl::~PopularSitesImpl() {} | |
| 152 | |
| 153 void PopularSitesImpl::StartFetch(bool force_download, | |
| 154 const FinishedCallback& callback) { | |
| 155 DCHECK(!callback_); | |
| 156 callback_ = callback; | |
| 157 | |
| 158 const base::Time last_download_time = base::Time::FromInternalValue( | |
| 159 prefs_->GetInt64(kPopularSitesLastDownloadPref)); | |
| 160 const base::TimeDelta time_since_last_download = | |
| 161 base::Time::Now() - last_download_time; | |
| 162 const base::TimeDelta redownload_interval = | |
| 163 base::TimeDelta::FromHours(kPopularSitesRedownloadIntervalHours); | |
| 164 const bool download_time_is_future = base::Time::Now() < last_download_time; | |
| 165 | |
| 166 pending_url_ = GetURLToFetch(); | |
| 167 const bool url_changed = | |
| 168 pending_url_.spec() != prefs_->GetString(kPopularSitesURLPref); | |
| 169 | |
| 170 // No valid path to save to. Immediately post failure. | |
| 171 if (local_path_.empty()) { | |
| 172 base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, | |
| 173 base::Bind(callback_, false)); | |
| 174 return; | |
| 175 } | |
| 176 | |
| 177 // Download forced, or we need to download a new file. | |
| 178 if (force_download || download_time_is_future || | |
| 179 (time_since_last_download > redownload_interval) || url_changed) { | |
| 180 FetchPopularSites(); | |
| 181 return; | |
| 182 } | |
| 183 | |
| 184 std::unique_ptr<std::string> file_data(new std::string); | |
| 185 std::string* file_data_ptr = file_data.get(); | |
| 186 base::PostTaskAndReplyWithResult( | |
| 187 blocking_runner_.get(), FROM_HERE, | |
| 188 base::Bind(&base::ReadFileToString, local_path_, file_data_ptr), | |
| 189 base::Bind(&PopularSitesImpl::OnReadFileDone, | |
| 190 weak_ptr_factory_.GetWeakPtr(), | |
| 191 base::Passed(std::move(file_data)))); | |
| 192 } | |
| 193 | |
| 194 const PopularSites::SitesVector& PopularSitesImpl::sites() const { | |
| 195 return sites_; | |
| 196 } | |
| 197 | |
| 198 GURL PopularSitesImpl::GetLastURLFetched() const { | |
| 199 return GURL(prefs_->GetString(kPopularSitesURLPref)); | |
| 200 } | |
| 201 | |
| 202 const base::FilePath& PopularSitesImpl::local_path() const { | |
| 203 return local_path_; | |
| 204 } | |
| 205 | |
| 206 GURL PopularSitesImpl::GetURLToFetch() { | |
| 207 const std::string country = GetCountryToFetch(); | |
| 208 const std::string version = GetVersionToFetch(); | |
| 209 | |
| 210 const GURL override_url = | |
| 211 GURL(prefs_->GetString(ntp_tiles::prefs::kPopularSitesOverrideURL)); | |
| 212 return override_url.is_valid() ? override_url | |
| 213 : GetPopularSitesURL(country, version); | |
| 214 } | |
| 215 | |
| 216 // Determine the country code to use. In order of precedence: | |
| 217 // - The explicit "override country" pref set by the user. | |
| 218 // - The country code from the field trial config (variation parameter). | |
| 219 // - The Google country code if Google is the default search engine (and the | |
| 220 // "--enable-ntp-search-engine-country-detection" switch is present). | |
| 221 // - The country provided by the VariationsService. | |
| 222 // - A default fallback. | |
| 223 std::string PopularSitesImpl::GetCountryToFetch() { | |
| 224 std::string country_code = | |
| 225 prefs_->GetString(ntp_tiles::prefs::kPopularSitesOverrideCountry); | |
| 226 | |
| 227 if (country_code.empty()) | |
| 228 country_code = GetVariationCountry(); | |
| 229 | |
| 230 if (country_code.empty()) | |
| 231 country_code = GetDefaultSearchEngineCountryCode(template_url_service_); | |
| 232 | |
| 233 if (country_code.empty() && variations_) | |
| 234 country_code = variations_->GetStoredPermanentCountry(); | |
| 235 | |
| 236 #if defined(OS_IOS) | |
| 237 if (country_code.empty()) | |
| 238 country_code = GetDeviceCountryCode(); | |
| 239 #endif | |
| 240 | |
| 241 if (country_code.empty()) | |
| 242 country_code = kPopularSitesDefaultCountryCode; | |
| 243 | |
| 244 return base::ToUpperASCII(country_code); | |
| 245 } | |
| 246 | |
| 247 // Determine the version to use. In order of precedence: | |
| 248 // - The explicit "override version" pref set by the user. | |
| 249 // - The version from the field trial config (variation parameter). | |
| 250 // - A default fallback. | |
| 251 std::string PopularSitesImpl::GetVersionToFetch() { | |
| 252 std::string version = | |
| 253 prefs_->GetString(ntp_tiles::prefs::kPopularSitesOverrideVersion); | |
| 254 | |
| 255 if (version.empty()) | |
| 256 version = GetVariationVersion(); | |
| 257 | |
| 258 if (version.empty()) | |
| 259 version = kPopularSitesDefaultVersion; | |
| 260 | |
| 261 return version; | |
| 262 } | |
| 263 | |
| 264 // static | |
| 265 void PopularSitesImpl::RegisterProfilePrefs( | |
| 266 user_prefs::PrefRegistrySyncable* user_prefs) { | |
| 267 user_prefs->RegisterStringPref(ntp_tiles::prefs::kPopularSitesOverrideURL, | |
| 268 std::string()); | |
| 269 user_prefs->RegisterStringPref(ntp_tiles::prefs::kPopularSitesOverrideCountry, | |
| 270 std::string()); | |
| 271 user_prefs->RegisterStringPref(ntp_tiles::prefs::kPopularSitesOverrideVersion, | |
| 272 std::string()); | |
| 273 | |
| 274 user_prefs->RegisterInt64Pref(kPopularSitesLastDownloadPref, 0); | |
| 275 user_prefs->RegisterStringPref(kPopularSitesURLPref, std::string()); | |
| 276 } | |
| 277 | |
| 278 void PopularSitesImpl::OnReadFileDone(std::unique_ptr<std::string> data, | |
| 279 bool success) { | |
| 280 if (success) { | |
| 281 auto json = base::JSONReader::Read(*data, base::JSON_ALLOW_TRAILING_COMMAS); | |
| 282 if (json) { | |
| 283 ParseSiteList(std::move(json)); | |
| 284 } else { | |
| 285 OnJsonParseFailed("previously-fetched JSON was no longer parseable"); | |
| 286 } | |
| 287 } else { | |
| 288 // File didn't exist, or couldn't be read for some other reason. | |
| 289 FetchPopularSites(); | |
| 290 } | |
| 291 } | |
| 292 | |
| 293 void PopularSitesImpl::FetchPopularSites() { | |
| 294 fetcher_ = URLFetcher::Create(pending_url_, URLFetcher::GET, this); | |
| 295 data_use_measurement::DataUseUserData::AttachToFetcher( | |
| 296 fetcher_.get(), data_use_measurement::DataUseUserData::NTP_TILES); | |
| 297 fetcher_->SetRequestContext(download_context_); | |
| 298 fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES | | |
| 299 net::LOAD_DO_NOT_SAVE_COOKIES); | |
| 300 fetcher_->SetAutomaticallyRetryOnNetworkChanges(1); | |
| 301 fetcher_->Start(); | |
| 302 } | |
| 303 | |
| 304 void PopularSitesImpl::OnURLFetchComplete(const net::URLFetcher* source) { | |
| 305 DCHECK_EQ(fetcher_.get(), source); | |
| 306 std::unique_ptr<net::URLFetcher> free_fetcher = std::move(fetcher_); | |
| 307 | |
| 308 std::string json_string; | |
| 309 if (!(source->GetStatus().is_success() && | |
| 310 source->GetResponseCode() == net::HTTP_OK && | |
| 311 source->GetResponseAsString(&json_string))) { | |
| 312 OnDownloadFailed(); | |
| 313 return; | |
| 314 } | |
| 315 | |
| 316 parse_json_.Run(json_string, base::Bind(&PopularSitesImpl::OnJsonParsed, | |
| 317 weak_ptr_factory_.GetWeakPtr()), | |
| 318 base::Bind(&PopularSitesImpl::OnJsonParseFailed, | |
| 319 weak_ptr_factory_.GetWeakPtr())); | |
| 320 } | |
| 321 | |
| 322 void PopularSitesImpl::OnJsonParsed(std::unique_ptr<base::Value> json) { | |
| 323 const base::Value* json_ptr = json.get(); | |
| 324 base::PostTaskAndReplyWithResult( | |
| 325 blocking_runner_.get(), FROM_HERE, | |
| 326 base::Bind(&WriteJsonToFile, local_path_, json_ptr), | |
| 327 base::Bind(&PopularSitesImpl::OnFileWriteDone, | |
| 328 weak_ptr_factory_.GetWeakPtr(), | |
| 329 base::Passed(std::move(json)))); | |
| 330 } | |
| 331 | |
| 332 void PopularSitesImpl::OnJsonParseFailed(const std::string& error_message) { | |
| 333 DLOG(WARNING) << "JSON parsing failed: " << error_message; | |
| 334 OnDownloadFailed(); | |
| 335 } | |
| 336 | |
| 337 void PopularSitesImpl::OnFileWriteDone(std::unique_ptr<base::Value> json, | |
| 338 bool success) { | |
| 339 if (success) { | |
| 340 prefs_->SetInt64(kPopularSitesLastDownloadPref, | |
| 341 base::Time::Now().ToInternalValue()); | |
| 342 prefs_->SetString(kPopularSitesURLPref, pending_url_.spec()); | |
| 343 ParseSiteList(std::move(json)); | |
| 344 } else { | |
| 345 DLOG(WARNING) << "Could not write file to " | |
| 346 << local_path_.LossyDisplayName(); | |
| 347 OnDownloadFailed(); | |
| 348 } | |
| 349 } | |
| 350 | |
| 351 void PopularSitesImpl::ParseSiteList(std::unique_ptr<base::Value> json) { | |
| 352 base::ListValue* list = nullptr; | |
| 353 if (!json || !json->GetAsList(&list)) { | |
| 354 DLOG(WARNING) << "JSON is not a list"; | |
| 355 sites_.clear(); | |
| 356 callback_.Run(false); | |
| 357 return; | |
| 358 } | |
| 359 | |
| 360 SitesVector sites; | |
| 361 for (size_t i = 0; i < list->GetSize(); i++) { | |
| 362 base::DictionaryValue* item; | |
| 363 if (!list->GetDictionary(i, &item)) | |
| 364 continue; | |
| 365 base::string16 title; | |
| 366 std::string url; | |
| 367 if (!item->GetString("title", &title) || !item->GetString("url", &url)) | |
| 368 continue; | |
| 369 std::string favicon_url; | |
| 370 item->GetString("favicon_url", &favicon_url); | |
| 371 std::string thumbnail_url; | |
| 372 item->GetString("thumbnail_url", &thumbnail_url); | |
| 373 std::string large_icon_url; | |
| 374 item->GetString("large_icon_url", &large_icon_url); | |
| 375 | |
| 376 sites.push_back(PopularSitesImpl::Site(title, GURL(url), GURL(favicon_url), | |
| 377 GURL(large_icon_url), | |
| 378 GURL(thumbnail_url))); | |
| 379 } | |
| 380 | |
| 381 sites_.swap(sites); | |
| 382 callback_.Run(true); | |
| 383 } | |
| 384 | |
| 385 void PopularSitesImpl::OnDownloadFailed() { | |
| 386 if (!is_fallback_) { | |
| 387 DLOG(WARNING) << "Download country site list failed"; | |
| 388 is_fallback_ = true; | |
| 389 pending_url_ = GetPopularSitesURL(kPopularSitesDefaultCountryCode, | |
| 390 kPopularSitesDefaultVersion); | |
| 391 FetchPopularSites(); | |
| 392 } else { | |
| 393 DLOG(WARNING) << "Download fallback site list failed"; | |
| 394 callback_.Run(false); | |
| 395 } | |
| 396 } | |
| 397 | |
| 398 } // namespace ntp_tiles | |
| OLD | NEW |