hints_fetcher.cc 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533
  1. // Copyright 2019 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. #include "components/optimization_guide/core/hints_fetcher.h"
  5. #include <memory>
  6. #include <utility>
  7. #include "base/command_line.h"
  8. #include "base/feature_list.h"
  9. #include "base/metrics/histogram_functions.h"
  10. #include "base/metrics/histogram_macros.h"
  11. #include "base/strings/strcat.h"
  12. #include "base/time/default_clock.h"
  13. #include "components/optimization_guide/core/hints_processing_util.h"
  14. #include "components/optimization_guide/core/optimization_guide_common.mojom.h"
  15. #include "components/optimization_guide/core/optimization_guide_features.h"
  16. #include "components/optimization_guide/core/optimization_guide_logger.h"
  17. #include "components/optimization_guide/core/optimization_guide_prefs.h"
  18. #include "components/optimization_guide/core/optimization_guide_switches.h"
  19. #include "components/optimization_guide/core/optimization_guide_util.h"
  20. #include "components/optimization_guide/proto/hints.pb.h"
  21. #include "components/prefs/pref_service.h"
  22. #include "components/prefs/scoped_user_pref_update.h"
  23. #include "components/variations/net/variations_http_headers.h"
  24. #include "net/base/load_flags.h"
  25. #include "net/base/url_util.h"
  26. #include "net/http/http_request_headers.h"
  27. #include "net/http/http_response_headers.h"
  28. #include "net/http/http_status_code.h"
  29. #include "net/traffic_annotation/network_traffic_annotation.h"
  30. #include "services/network/public/cpp/resource_request.h"
  31. #include "services/network/public/cpp/shared_url_loader_factory.h"
  32. #include "services/network/public/cpp/simple_url_loader.h"
  33. #include "services/network/public/mojom/url_response_head.mojom.h"
  34. namespace optimization_guide {
  35. namespace {
  36. // Returns the string that can be used to record histograms for the request
  37. // context.
  38. //
  39. // Keep in sync with RequestContext variant list in
  40. // //tools/metrics/histograms/metadata/optimization/histograms.xml.
  41. std::string GetStringNameForRequestContext(
  42. proto::RequestContext request_context) {
  43. switch (request_context) {
  44. case proto::RequestContext::CONTEXT_UNSPECIFIED:
  45. case proto::RequestContext::CONTEXT_BATCH_UPDATE_MODELS:
  46. NOTREACHED();
  47. return "Unknown";
  48. case proto::RequestContext::CONTEXT_PAGE_NAVIGATION:
  49. return "PageNavigation";
  50. case proto::RequestContext::CONTEXT_BATCH_UPDATE_GOOGLE_SRP:
  51. return "BatchUpdateGoogleSRP";
  52. case proto::RequestContext::CONTEXT_BATCH_UPDATE_ACTIVE_TABS:
  53. return "BatchUpdateActiveTabs";
  54. case proto::RequestContext::CONTEXT_BOOKMARKS:
  55. return "Bookmarks";
  56. }
  57. NOTREACHED();
  58. return std::string();
  59. }
  60. void RecordRequestStatusHistogram(proto::RequestContext request_context,
  61. HintsFetcherRequestStatus status) {
  62. DCHECK_NE(status, HintsFetcherRequestStatus::kDeprecatedNetworkOffline);
  63. base::UmaHistogramEnumeration(
  64. "OptimizationGuide.HintsFetcher.RequestStatus." +
  65. GetStringNameForRequestContext(request_context),
  66. status);
  67. }
  68. } // namespace
  69. HintsFetcher::HintsFetcher(
  70. scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
  71. const GURL& optimization_guide_service_url,
  72. PrefService* pref_service,
  73. OptimizationGuideLogger* optimization_guide_logger)
  74. : optimization_guide_service_url_(net::AppendOrReplaceQueryParameter(
  75. optimization_guide_service_url,
  76. "key",
  77. features::GetOptimizationGuideServiceAPIKey())),
  78. pref_service_(pref_service),
  79. time_clock_(base::DefaultClock::GetInstance()),
  80. optimization_guide_logger_(optimization_guide_logger) {
  81. url_loader_factory_ = std::move(url_loader_factory);
  82. // Allow non-https scheme only when it is overridden in command line. This is
  83. // needed for iOS EG2 tests which don't support HTTPS embedded test servers
  84. // due to ssl certificate validation. So, the EG2 tests use HTTP hints
  85. // servers.
  86. CHECK(optimization_guide_service_url_.SchemeIs(url::kHttpsScheme) ||
  87. base::CommandLine::ForCurrentProcess()->HasSwitch(
  88. switches::kOptimizationGuideServiceGetHintsURL));
  89. DCHECK(features::IsRemoteFetchingEnabled());
  90. }
  91. HintsFetcher::~HintsFetcher() {
  92. if (active_url_loader_) {
  93. if (hints_fetched_callback_)
  94. std::move(hints_fetched_callback_).Run(absl::nullopt);
  95. base::UmaHistogramExactLinear(
  96. "OptimizationGuide.HintsFetcher.GetHintsRequest."
  97. "ActiveRequestCanceled." +
  98. GetStringNameForRequestContext(request_context_),
  99. 1, 1);
  100. }
  101. }
  102. // static
  103. void HintsFetcher::ClearHostsSuccessfullyFetched(PrefService* pref_service) {
  104. DictionaryPrefUpdate hosts_fetched_list(
  105. pref_service, prefs::kHintsFetcherHostsSuccessfullyFetched);
  106. hosts_fetched_list->DictClear();
  107. }
  108. void HintsFetcher::SetTimeClockForTesting(const base::Clock* time_clock) {
  109. time_clock_ = time_clock;
  110. }
  111. // static
  112. bool HintsFetcher::WasHostCoveredByFetch(PrefService* pref_service,
  113. const std::string& host) {
  114. return WasHostCoveredByFetch(pref_service, host,
  115. base::DefaultClock::GetInstance());
  116. }
  117. // static
  118. bool HintsFetcher::WasHostCoveredByFetch(PrefService* pref_service,
  119. const std::string& host,
  120. const base::Clock* time_clock) {
  121. if (!optimization_guide::features::ShouldPersistHintsToDisk()) {
  122. // Don't consult the pref if we aren't even persisting hints to disk.
  123. return false;
  124. }
  125. DictionaryPrefUpdate hosts_fetched(
  126. pref_service, prefs::kHintsFetcherHostsSuccessfullyFetched);
  127. absl::optional<double> value =
  128. hosts_fetched->FindDoubleKey(HashHostForDictionary(host));
  129. if (!value)
  130. return false;
  131. base::Time host_valid_time =
  132. base::Time::FromDeltaSinceWindowsEpoch(base::Seconds(*value));
  133. return host_valid_time > time_clock->Now();
  134. }
  135. // static
  136. void HintsFetcher::ClearSingleFetchedHost(PrefService* pref_service,
  137. const std::string& host) {
  138. DictionaryPrefUpdate hosts_fetched_list(
  139. pref_service, prefs::kHintsFetcherHostsSuccessfullyFetched);
  140. hosts_fetched_list->RemovePath(HashHostForDictionary(host));
  141. }
  142. // static
  143. void HintsFetcher::AddFetchedHostForTesting(PrefService* pref_service,
  144. const std::string& host,
  145. base::Time time) {
  146. DictionaryPrefUpdate hosts_fetched_list(
  147. pref_service, prefs::kHintsFetcherHostsSuccessfullyFetched);
  148. hosts_fetched_list->SetDoubleKey(
  149. HashHostForDictionary(host),
  150. time.ToDeltaSinceWindowsEpoch().InSecondsF());
  151. }
  152. bool HintsFetcher::FetchOptimizationGuideServiceHints(
  153. const std::vector<std::string>& hosts,
  154. const std::vector<GURL>& urls,
  155. const base::flat_set<optimization_guide::proto::OptimizationType>&
  156. optimization_types,
  157. optimization_guide::proto::RequestContext request_context,
  158. const std::string& locale,
  159. HintsFetchedCallback hints_fetched_callback) {
  160. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  161. DCHECK_GT(optimization_types.size(), 0u);
  162. request_context_ = request_context;
  163. if (active_url_loader_) {
  164. OPTIMIZATION_GUIDE_LOG(
  165. optimization_guide_common::mojom::LogSource::HINTS,
  166. optimization_guide_logger_,
  167. "No hints fetched: HintsFetcher busy in another fetch");
  168. RecordRequestStatusHistogram(request_context_,
  169. HintsFetcherRequestStatus::kFetcherBusy);
  170. std::move(hints_fetched_callback).Run(absl::nullopt);
  171. return false;
  172. }
  173. std::vector<std::string> filtered_hosts =
  174. GetSizeLimitedHostsDueForHintsRefresh(hosts);
  175. std::vector<GURL> valid_urls = GetSizeLimitedURLsForFetching(urls);
  176. if (filtered_hosts.empty() && valid_urls.empty()) {
  177. OPTIMIZATION_GUIDE_LOG(optimization_guide_common::mojom::LogSource::HINTS,
  178. optimization_guide_logger_,
  179. "No hints fetched: No hosts/URLs");
  180. RecordRequestStatusHistogram(
  181. request_context_, HintsFetcherRequestStatus::kNoHostsOrURLsToFetch);
  182. std::move(hints_fetched_callback).Run(absl::nullopt);
  183. return false;
  184. }
  185. DCHECK_GE(features::MaxHostsForOptimizationGuideServiceHintsFetch(),
  186. filtered_hosts.size());
  187. DCHECK_GE(features::MaxUrlsForOptimizationGuideServiceHintsFetch(),
  188. valid_urls.size());
  189. if (optimization_types.empty()) {
  190. OPTIMIZATION_GUIDE_LOG(optimization_guide_common::mojom::LogSource::HINTS,
  191. optimization_guide_logger_,
  192. "No hints fetched: No supported optimization types");
  193. RecordRequestStatusHistogram(
  194. request_context_,
  195. HintsFetcherRequestStatus::kNoSupportedOptimizationTypes);
  196. std::move(hints_fetched_callback).Run(absl::nullopt);
  197. return false;
  198. }
  199. hints_fetch_start_time_ = base::TimeTicks::Now();
  200. proto::GetHintsRequest get_hints_request;
  201. get_hints_request.add_supported_key_representations(proto::HOST);
  202. get_hints_request.add_supported_key_representations(proto::FULL_URL);
  203. for (const auto& optimization_type : optimization_types)
  204. get_hints_request.add_supported_optimizations(optimization_type);
  205. get_hints_request.set_context(request_context_);
  206. get_hints_request.set_locale(locale);
  207. *get_hints_request.mutable_origin_info() =
  208. optimization_guide::GetClientOriginInfo();
  209. for (const auto& url : valid_urls)
  210. get_hints_request.add_urls()->set_url(url.spec());
  211. for (const auto& host : filtered_hosts) {
  212. proto::HostInfo* host_info = get_hints_request.add_hosts();
  213. host_info->set_host(host);
  214. }
  215. std::string serialized_request;
  216. get_hints_request.SerializeToString(&serialized_request);
  217. net::NetworkTrafficAnnotationTag traffic_annotation =
  218. net::DefineNetworkTrafficAnnotation("hintsfetcher_gethintsrequest", R"(
  219. semantics {
  220. sender: "HintsFetcher"
  221. description:
  222. "Requests Hints from the Optimization Guide Service for use in "
  223. "providing metadata about page loads for Chrome for features such "
  224. "as price tracking and trust information about the websites users "
  225. "visit."
  226. trigger:
  227. "Requested once per page load if user has consented to sending "
  228. "URLs to Google to make browsing better and the browser does not "
  229. "have a valid hint for the current page load."
  230. data: "The URL and host of the current page load."
  231. destination: GOOGLE_OWNED_SERVICE
  232. }
  233. policy {
  234. cookies_allowed: NO
  235. setting:
  236. "Users can control this via the Make searches and browsing better' "
  237. "under Settings > Sync and Google services > Make searches and "
  238. "browsing better."
  239. chrome_policy {
  240. UrlKeyedAnonymizedDataCollectionEnabled {
  241. policy_options {mode: MANDATORY}
  242. UrlKeyedAnonymizedDataCollectionEnabled: false
  243. }
  244. }
  245. })");
  246. auto resource_request = std::make_unique<network::ResourceRequest>();
  247. resource_request->url = optimization_guide_service_url_;
  248. resource_request->method = "POST";
  249. resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
  250. active_url_loader_ = variations::CreateSimpleURLLoaderWithVariationsHeader(
  251. std::move(resource_request),
  252. // This is always InIncognito::kNo as the OptimizationGuideKeyedService is
  253. // not enabled on incognito sessions and is rechecked before each fetch.
  254. variations::InIncognito::kNo, variations::SignedIn::kNo,
  255. traffic_annotation);
  256. active_url_loader_->AttachStringForUpload(serialized_request,
  257. "application/x-protobuf");
  258. UMA_HISTOGRAM_COUNTS_100(
  259. "OptimizationGuide.HintsFetcher.GetHintsRequest.HostCount",
  260. filtered_hosts.size());
  261. UMA_HISTOGRAM_COUNTS_100(
  262. "OptimizationGuide.HintsFetcher.GetHintsRequest.UrlCount",
  263. valid_urls.size());
  264. // It's safe to use |base::Unretained(this)| here because |this| owns
  265. // |active_url_loader_| and the callback will be canceled if
  266. // |active_url_loader_| is destroyed.
  267. active_url_loader_->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
  268. url_loader_factory_.get(),
  269. base::BindOnce(&HintsFetcher::OnURLLoadComplete, base::Unretained(this)));
  270. hints_fetched_callback_ = std::move(hints_fetched_callback);
  271. hosts_fetched_ = filtered_hosts;
  272. return true;
  273. }
  274. void HintsFetcher::HandleResponse(const std::string& get_hints_response_data,
  275. int net_status,
  276. int response_code) {
  277. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  278. std::unique_ptr<proto::GetHintsResponse> get_hints_response =
  279. std::make_unique<proto::GetHintsResponse>();
  280. UMA_HISTOGRAM_ENUMERATION(
  281. "OptimizationGuide.HintsFetcher.GetHintsRequest.Status",
  282. static_cast<net::HttpStatusCode>(response_code),
  283. net::HTTP_VERSION_NOT_SUPPORTED);
  284. // Net error codes are negative but histogram enums must be positive.
  285. base::UmaHistogramSparse(
  286. "OptimizationGuide.HintsFetcher.GetHintsRequest.NetErrorCode",
  287. -net_status);
  288. if (net_status == net::OK && response_code == net::HTTP_OK &&
  289. get_hints_response->ParseFromString(get_hints_response_data)) {
  290. UMA_HISTOGRAM_COUNTS_100(
  291. "OptimizationGuide.HintsFetcher.GetHintsRequest.HintCount",
  292. get_hints_response->hints_size());
  293. base::TimeDelta fetch_latency =
  294. base::TimeTicks::Now() - hints_fetch_start_time_;
  295. UMA_HISTOGRAM_MEDIUM_TIMES(
  296. "OptimizationGuide.HintsFetcher.GetHintsRequest.FetchLatency",
  297. fetch_latency);
  298. base::UmaHistogramMediumTimes(
  299. "OptimizationGuide.HintsFetcher.GetHintsRequest.FetchLatency." +
  300. GetStringNameForRequestContext(request_context_),
  301. fetch_latency);
  302. base::TimeDelta valid_duration =
  303. features::StoredFetchedHintsFreshnessDuration();
  304. if (get_hints_response->has_max_cache_duration()) {
  305. valid_duration =
  306. base::Seconds(get_hints_response->max_cache_duration().seconds());
  307. }
  308. UpdateHostsSuccessfullyFetched(valid_duration);
  309. RecordRequestStatusHistogram(request_context_,
  310. HintsFetcherRequestStatus::kSuccess);
  311. std::move(hints_fetched_callback_).Run(std::move(get_hints_response));
  312. } else {
  313. hosts_fetched_.clear();
  314. RecordRequestStatusHistogram(request_context_,
  315. HintsFetcherRequestStatus::kResponseError);
  316. std::move(hints_fetched_callback_).Run(absl::nullopt);
  317. }
  318. }
  319. void HintsFetcher::UpdateHostsSuccessfullyFetched(
  320. base::TimeDelta valid_duration) {
  321. if (!optimization_guide::features::ShouldPersistHintsToDisk()) {
  322. // Do not persist any state if we aren't persisting hints to disk.
  323. return;
  324. }
  325. DictionaryPrefUpdate hosts_fetched_list(
  326. pref_service_, prefs::kHintsFetcherHostsSuccessfullyFetched);
  327. // Remove any expired hosts.
  328. std::vector<std::string> entries_to_remove;
  329. for (auto it : hosts_fetched_list->DictItems()) {
  330. if (base::Time::FromDeltaSinceWindowsEpoch(
  331. base::Seconds(it.second.GetDouble())) < time_clock_->Now()) {
  332. entries_to_remove.emplace_back(it.first);
  333. }
  334. }
  335. for (const auto& host : entries_to_remove) {
  336. hosts_fetched_list->RemovePath(host);
  337. }
  338. if (hosts_fetched_.empty())
  339. return;
  340. // Ensure there is enough space in the dictionary pref for the
  341. // most recent set of hosts to be stored.
  342. if (hosts_fetched_list->DictSize() + hosts_fetched_.size() >
  343. features::MaxHostsForRecordingSuccessfullyCovered()) {
  344. entries_to_remove.clear();
  345. size_t num_entries_to_remove =
  346. hosts_fetched_list->DictSize() + hosts_fetched_.size() -
  347. features::MaxHostsForRecordingSuccessfullyCovered();
  348. for (auto it : hosts_fetched_list->DictItems()) {
  349. if (entries_to_remove.size() >= num_entries_to_remove)
  350. break;
  351. entries_to_remove.emplace_back(it.first);
  352. }
  353. for (const auto& host : entries_to_remove) {
  354. hosts_fetched_list->RemovePath(host);
  355. }
  356. }
  357. // Add the covered hosts in |hosts_fetched_| to the dictionary pref.
  358. base::Time host_invalid_time = time_clock_->Now() + valid_duration;
  359. for (const std::string& host : hosts_fetched_) {
  360. hosts_fetched_list->SetDoubleKey(
  361. HashHostForDictionary(host),
  362. host_invalid_time.ToDeltaSinceWindowsEpoch().InSecondsF());
  363. }
  364. DCHECK_LE(hosts_fetched_list->DictSize(),
  365. features::MaxHostsForRecordingSuccessfullyCovered());
  366. hosts_fetched_.clear();
  367. }
  368. // Callback is only invoked if |active_url_loader_| is bound and still alive.
  369. void HintsFetcher::OnURLLoadComplete(
  370. std::unique_ptr<std::string> response_body) {
  371. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  372. int response_code = -1;
  373. if (active_url_loader_->ResponseInfo() &&
  374. active_url_loader_->ResponseInfo()->headers) {
  375. response_code =
  376. active_url_loader_->ResponseInfo()->headers->response_code();
  377. }
  378. auto net_error = active_url_loader_->NetError();
  379. // Reset the active URL loader here since actions happening during response
  380. // handling may destroy |this|.
  381. active_url_loader_.reset();
  382. HandleResponse(response_body ? *response_body : "", net_error, response_code);
  383. }
  384. // Returns the subset of URLs from |urls| for which the URL is considered
  385. // valid and can be included in a hints fetch.
  386. std::vector<GURL> HintsFetcher::GetSizeLimitedURLsForFetching(
  387. const std::vector<GURL>& urls) const {
  388. std::vector<GURL> valid_urls;
  389. for (size_t i = 0; i < urls.size(); i++) {
  390. if (valid_urls.size() >=
  391. features::MaxUrlsForOptimizationGuideServiceHintsFetch()) {
  392. base::UmaHistogramCounts100(
  393. "OptimizationGuide.HintsFetcher.GetHintsRequest.DroppedUrls." +
  394. GetStringNameForRequestContext(request_context_),
  395. urls.size() - i);
  396. OPTIMIZATION_GUIDE_LOG(
  397. optimization_guide_common::mojom::LogSource::HINTS,
  398. optimization_guide_logger_,
  399. base::StrCat({"Skipped adding URL due to limit, context:",
  400. GetStringNameForRequestContext(request_context_),
  401. " URL:", urls[i].possibly_invalid_spec()}));
  402. break;
  403. }
  404. if (IsValidURLForURLKeyedHint(urls[i])) {
  405. valid_urls.push_back(urls[i]);
  406. } else {
  407. OPTIMIZATION_GUIDE_LOG(
  408. optimization_guide_common::mojom::LogSource::HINTS,
  409. optimization_guide_logger_,
  410. base::StrCat({"Skipped adding invalid URL, context:",
  411. GetStringNameForRequestContext(request_context_),
  412. " URL:", urls[i].possibly_invalid_spec()}));
  413. }
  414. }
  415. return valid_urls;
  416. }
  417. std::vector<std::string> HintsFetcher::GetSizeLimitedHostsDueForHintsRefresh(
  418. const std::vector<std::string>& hosts) const {
  419. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  420. DictionaryPrefUpdate hosts_fetched(
  421. pref_service_, prefs::kHintsFetcherHostsSuccessfullyFetched);
  422. std::vector<std::string> target_hosts;
  423. target_hosts.reserve(hosts.size());
  424. for (size_t i = 0; i < hosts.size(); i++) {
  425. if (target_hosts.size() >=
  426. features::MaxHostsForOptimizationGuideServiceHintsFetch()) {
  427. base::UmaHistogramCounts100(
  428. "OptimizationGuide.HintsFetcher.GetHintsRequest.DroppedHosts." +
  429. GetStringNameForRequestContext(request_context_),
  430. hosts.size() - i);
  431. break;
  432. }
  433. std::string host = hosts[i];
  434. // Skip over localhosts, IP addresses, and invalid hosts.
  435. if (net::HostStringIsLocalhost(host))
  436. continue;
  437. url::CanonHostInfo host_info;
  438. std::string canonicalized_host(net::CanonicalizeHost(host, &host_info));
  439. if (host_info.IsIPAddress() ||
  440. !net::IsCanonicalizedHostCompliant(canonicalized_host)) {
  441. OPTIMIZATION_GUIDE_LOG(
  442. optimization_guide_common::mojom::LogSource::HINTS,
  443. optimization_guide_logger_,
  444. base::StrCat({"Skipped adding invalid host:", host}));
  445. continue;
  446. }
  447. bool host_hints_due_for_refresh = true;
  448. absl::optional<double> value =
  449. hosts_fetched->FindDoubleKey(HashHostForDictionary(host));
  450. if (value && optimization_guide::features::ShouldPersistHintsToDisk()) {
  451. base::Time host_valid_time =
  452. base::Time::FromDeltaSinceWindowsEpoch(base::Seconds(*value));
  453. host_hints_due_for_refresh =
  454. (host_valid_time - features::GetHostHintsFetchRefreshDuration() <=
  455. time_clock_->Now());
  456. }
  457. if (host_hints_due_for_refresh) {
  458. target_hosts.push_back(host);
  459. } else {
  460. OPTIMIZATION_GUIDE_LOG(
  461. optimization_guide_common::mojom::LogSource::HINTS,
  462. optimization_guide_logger_,
  463. base::StrCat({"Skipped refreshing hints for host:", host}));
  464. }
  465. }
  466. DCHECK_GE(features::MaxHostsForOptimizationGuideServiceHintsFetch(),
  467. target_hosts.size());
  468. return target_hosts;
  469. }
  470. } // namespace optimization_guide