hints_manager.cc 69 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756
  1. // Copyright 2021 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_manager.h"
  5. #include <string>
  6. #include <utility>
  7. #include "base/bind.h"
  8. #include "base/callback_helpers.h"
  9. #include "base/command_line.h"
  10. #include "base/memory/raw_ptr.h"
  11. #include "base/metrics/histogram_functions.h"
  12. #include "base/metrics/histogram_macros.h"
  13. #include "base/metrics/histogram_macros_local.h"
  14. #include "base/notreached.h"
  15. #include "base/rand_util.h"
  16. #include "base/strings/string_number_conversions.h"
  17. #include "base/task/sequenced_task_runner.h"
  18. #include "base/task/task_runner_util.h"
  19. #include "base/task/thread_pool.h"
  20. #include "base/time/default_clock.h"
  21. #include "base/time/time.h"
  22. #include "build/build_config.h"
  23. #include "components/optimization_guide/core/bloom_filter.h"
  24. #include "components/optimization_guide/core/hint_cache.h"
  25. #include "components/optimization_guide/core/hints_component_util.h"
  26. #include "components/optimization_guide/core/hints_fetcher_factory.h"
  27. #include "components/optimization_guide/core/hints_processing_util.h"
  28. #include "components/optimization_guide/core/insertion_ordered_set.h"
  29. #include "components/optimization_guide/core/optimization_filter.h"
  30. #include "components/optimization_guide/core/optimization_guide_constants.h"
  31. #include "components/optimization_guide/core/optimization_guide_decision.h"
  32. #include "components/optimization_guide/core/optimization_guide_enums.h"
  33. #include "components/optimization_guide/core/optimization_guide_features.h"
  34. #include "components/optimization_guide/core/optimization_guide_logger.h"
  35. #include "components/optimization_guide/core/optimization_guide_navigation_data.h"
  36. #include "components/optimization_guide/core/optimization_guide_permissions_util.h"
  37. #include "components/optimization_guide/core/optimization_guide_prefs.h"
  38. #include "components/optimization_guide/core/optimization_guide_store.h"
  39. #include "components/optimization_guide/core/optimization_guide_switches.h"
  40. #include "components/optimization_guide/core/optimization_guide_util.h"
  41. #include "components/optimization_guide/core/optimization_hints_component_update_listener.h"
  42. #include "components/optimization_guide/core/optimization_metadata.h"
  43. #include "components/optimization_guide/core/tab_url_provider.h"
  44. #include "components/optimization_guide/core/top_host_provider.h"
  45. #include "components/optimization_guide/proto/models.pb.h"
  46. #include "components/prefs/pref_service.h"
  47. #include "components/prefs/scoped_user_pref_update.h"
  48. #include "services/metrics/public/cpp/ukm_builders.h"
  49. #include "services/metrics/public/cpp/ukm_recorder.h"
  50. #include "services/metrics/public/cpp/ukm_source.h"
  51. #include "services/metrics/public/cpp/ukm_source_id.h"
  52. #include "services/network/public/cpp/shared_url_loader_factory.h"
  53. namespace optimization_guide {
  54. namespace {
  55. // The component version used with a manual config. This ensures that any hint
  56. // component received from the Optimization Hints component on a subsequent
  57. // startup will have a newer version than it.
  58. constexpr char kManualConfigComponentVersion[] = "0.0.0";
  59. // Provides a random time delta in seconds between |kFetchRandomMinDelay| and
  60. // |kFetchRandomMaxDelay|.
  61. base::TimeDelta RandomFetchDelay() {
  62. return base::Seconds(
  63. base::RandInt(features::ActiveTabsHintsFetchRandomMinDelaySecs(),
  64. features::ActiveTabsHintsFetchRandomMaxDelaySecs()));
  65. }
  66. void MaybeRunUpdateClosure(base::OnceClosure update_closure) {
  67. if (update_closure)
  68. std::move(update_closure).Run();
  69. }
  70. absl::optional<base::Version>
  71. GetPendingOptimizationHintsComponentVersionFromPref(PrefService* pref_service) {
  72. const std::string previous_attempted_version_string =
  73. pref_service->GetString(prefs::kPendingHintsProcessingVersion);
  74. if (!previous_attempted_version_string.empty()) {
  75. const base::Version previous_attempted_version =
  76. base::Version(previous_attempted_version_string);
  77. if (!previous_attempted_version.IsValid()) {
  78. DLOG(ERROR) << "Bad contents in hints processing pref";
  79. // Clear pref for fresh start next time.
  80. pref_service->ClearPref(prefs::kPendingHintsProcessingVersion);
  81. return absl::nullopt;
  82. }
  83. return absl::make_optional(previous_attempted_version);
  84. }
  85. return absl::nullopt;
  86. }
  87. // Returns whether |optimization_type| is allowlisted by |optimizations|. If
  88. // it is allowlisted, this will return true and |optimization_metadata| will be
  89. // populated with the metadata provided by the hint, if applicable. If
  90. // |page_hint| is not provided or |optimization_type| is not allowlisted, this
  91. // will return false.
  92. bool IsOptimizationTypeAllowed(const google::protobuf::RepeatedPtrField<
  93. proto::Optimization>& optimizations,
  94. proto::OptimizationType optimization_type,
  95. OptimizationMetadata* optimization_metadata) {
  96. for (const auto& optimization : optimizations) {
  97. if (optimization_type != optimization.optimization_type())
  98. continue;
  99. // We found an optimization that can be applied. Populate optimization
  100. // metadata if applicable and return.
  101. if (optimization_metadata) {
  102. switch (optimization.metadata_case()) {
  103. case proto::Optimization::kPerformanceHintsMetadata:
  104. optimization_metadata->set_performance_hints_metadata(
  105. optimization.performance_hints_metadata());
  106. break;
  107. case proto::Optimization::kPublicImageMetadata:
  108. optimization_metadata->set_public_image_metadata(
  109. optimization.public_image_metadata());
  110. break;
  111. case proto::Optimization::kLoadingPredictorMetadata:
  112. optimization_metadata->set_loading_predictor_metadata(
  113. optimization.loading_predictor_metadata());
  114. break;
  115. case proto::Optimization::kAnyMetadata:
  116. optimization_metadata->set_any_metadata(optimization.any_metadata());
  117. break;
  118. case proto::Optimization::METADATA_NOT_SET:
  119. // Some optimization types do not have metadata, make sure we do not
  120. // DCHECK.
  121. break;
  122. }
  123. }
  124. return true;
  125. }
  126. return false;
  127. }
  128. // Util class for recording whether a hints fetch race against the current
  129. // navigation was attempted. The result is recorded when it goes out of scope
  130. // and its destructor is called.
  131. class ScopedHintsManagerRaceNavigationHintsFetchAttemptRecorder {
  132. public:
  133. explicit ScopedHintsManagerRaceNavigationHintsFetchAttemptRecorder(
  134. OptimizationGuideNavigationData* navigation_data)
  135. : race_attempt_status_(RaceNavigationFetchAttemptStatus::kUnknown),
  136. navigation_data_(navigation_data) {}
  137. ~ScopedHintsManagerRaceNavigationHintsFetchAttemptRecorder() {
  138. DCHECK_NE(race_attempt_status_, RaceNavigationFetchAttemptStatus::kUnknown);
  139. DCHECK_NE(
  140. race_attempt_status_,
  141. RaceNavigationFetchAttemptStatus::
  142. kDeprecatedRaceNavigationFetchNotAttemptedTooManyConcurrentFetches);
  143. base::UmaHistogramEnumeration(
  144. "OptimizationGuide.HintsManager.RaceNavigationFetchAttemptStatus",
  145. race_attempt_status_);
  146. if (navigation_data_)
  147. navigation_data_->set_hints_fetch_attempt_status(race_attempt_status_);
  148. }
  149. void set_race_attempt_status(
  150. RaceNavigationFetchAttemptStatus race_attempt_status) {
  151. race_attempt_status_ = race_attempt_status;
  152. }
  153. private:
  154. RaceNavigationFetchAttemptStatus race_attempt_status_;
  155. raw_ptr<OptimizationGuideNavigationData> navigation_data_;
  156. };
  157. // Returns true if the optimization type should be ignored when is newly
  158. // registered as the optimization type is likely launched.
  159. bool ShouldIgnoreNewlyRegisteredOptimizationType(
  160. proto::OptimizationType optimization_type) {
  161. switch (optimization_type) {
  162. case proto::NOSCRIPT:
  163. case proto::RESOURCE_LOADING:
  164. case proto::LITE_PAGE_REDIRECT:
  165. case proto::DEFER_ALL_SCRIPT:
  166. return true;
  167. default:
  168. return false;
  169. }
  170. }
  171. class ScopedCanApplyOptimizationLogger {
  172. public:
  173. ScopedCanApplyOptimizationLogger(
  174. proto::OptimizationType opt_type,
  175. GURL url,
  176. OptimizationGuideLogger* optimization_guide_logger)
  177. : decision_(OptimizationGuideDecision::kUnknown),
  178. type_decision_(OptimizationTypeDecision::kUnknown),
  179. opt_type_(opt_type),
  180. has_metadata_(false),
  181. url_(url),
  182. optimization_guide_logger_(optimization_guide_logger) {}
  183. ~ScopedCanApplyOptimizationLogger() {
  184. if (!optimization_guide_logger_->ShouldEnableDebugLogs())
  185. return;
  186. DCHECK_NE(type_decision_, OptimizationTypeDecision::kUnknown);
  187. OPTIMIZATION_GUIDE_LOGGER(
  188. optimization_guide_common::mojom::LogSource::HINTS,
  189. optimization_guide_logger_)
  190. << "CanApplyOptimization: " << opt_type_ << "\nqueried on: " << url_
  191. << "\nDecision: " << decision_ << "\nTypeDecision: " << type_decision_
  192. << "\nHas Metadata: " << (has_metadata_ ? "True" : "False");
  193. }
  194. void set_has_metadata() { has_metadata_ = true; }
  195. void set_type_decision(OptimizationTypeDecision type_decision) {
  196. type_decision_ = type_decision;
  197. decision_ =
  198. HintsManager::GetOptimizationGuideDecisionFromOptimizationTypeDecision(
  199. type_decision_);
  200. }
  201. private:
  202. OptimizationGuideDecision decision_;
  203. OptimizationTypeDecision type_decision_;
  204. proto::OptimizationType opt_type_;
  205. bool has_metadata_;
  206. GURL url_;
  207. // Not owned. Guaranteed to outlive |this| scoped object.
  208. raw_ptr<OptimizationGuideLogger> optimization_guide_logger_;
  209. };
  210. // Reads component file and parses it into a Configuration proto. Should not be
  211. // called on the UI thread.
  212. std::unique_ptr<proto::Configuration> ReadComponentFile(
  213. const HintsComponentInfo& info) {
  214. ProcessHintsComponentResult out_result;
  215. std::unique_ptr<proto::Configuration> config =
  216. ProcessHintsComponent(info, &out_result);
  217. if (!config) {
  218. RecordProcessHintsComponentResult(out_result);
  219. return nullptr;
  220. }
  221. // Do not record the process hints component result for success cases until
  222. // we processed all of the hints and filters in it.
  223. return config;
  224. }
  225. // Logs information that will be requested from the remote Optimization Guide
  226. // service.
  227. void MaybeLogGetHintRequestInfo(
  228. proto::RequestContext request_context,
  229. const base::flat_set<proto::OptimizationType>&
  230. registered_optimization_types,
  231. const std::vector<GURL>& urls_to_fetch,
  232. const std::vector<std::string>& hosts_to_fetch,
  233. OptimizationGuideLogger* optimization_guide_logger) {
  234. if (!optimization_guide_logger->ShouldEnableDebugLogs())
  235. return;
  236. OPTIMIZATION_GUIDE_LOGGER(optimization_guide_common::mojom::LogSource::HINTS,
  237. optimization_guide_logger)
  238. << "Starting fetch for request context " << request_context;
  239. OPTIMIZATION_GUIDE_LOG(optimization_guide_common::mojom::LogSource::HINTS,
  240. optimization_guide_logger,
  241. "Registered Optimization Types: ");
  242. if (optimization_guide_logger->ShouldEnableDebugLogs()) {
  243. for (const auto& optimization_type : registered_optimization_types) {
  244. OPTIMIZATION_GUIDE_LOGGER(
  245. optimization_guide_common::mojom::LogSource::HINTS,
  246. optimization_guide_logger)
  247. << "Optimization Type: " << optimization_type;
  248. }
  249. OPTIMIZATION_GUIDE_LOG(optimization_guide_common::mojom::LogSource::HINTS,
  250. optimization_guide_logger, " URLs and Hosts: ");
  251. for (const auto& url : urls_to_fetch) {
  252. OPTIMIZATION_GUIDE_LOGGER(
  253. optimization_guide_common::mojom::LogSource::HINTS,
  254. optimization_guide_logger)
  255. << "URL: " << url;
  256. }
  257. for (const auto& host : hosts_to_fetch) {
  258. OPTIMIZATION_GUIDE_LOGGER(
  259. optimization_guide_common::mojom::LogSource::HINTS,
  260. optimization_guide_logger)
  261. << "Host: " << host;
  262. }
  263. }
  264. }
  265. } // namespace
  266. HintsManager::HintsManager(
  267. bool is_off_the_record,
  268. const std::string& application_locale,
  269. PrefService* pref_service,
  270. base::WeakPtr<OptimizationGuideStore> hint_store,
  271. TopHostProvider* top_host_provider,
  272. TabUrlProvider* tab_url_provider,
  273. scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
  274. std::unique_ptr<PushNotificationManager> push_notification_manager,
  275. OptimizationGuideLogger* optimization_guide_logger)
  276. : failed_component_version_(
  277. GetPendingOptimizationHintsComponentVersionFromPref(pref_service)),
  278. is_off_the_record_(is_off_the_record),
  279. application_locale_(application_locale),
  280. pref_service_(pref_service),
  281. hint_cache_(
  282. std::make_unique<HintCache>(hint_store,
  283. features::MaxHostKeyedHintCacheSize())),
  284. batch_update_hints_fetchers_(features::MaxConcurrentBatchUpdateFetches()),
  285. page_navigation_hints_fetchers_(
  286. features::MaxConcurrentPageNavigationFetches()),
  287. hints_fetcher_factory_(std::make_unique<HintsFetcherFactory>(
  288. url_loader_factory,
  289. features::GetOptimizationGuideServiceGetHintsURL(),
  290. pref_service)),
  291. top_host_provider_(top_host_provider),
  292. tab_url_provider_(tab_url_provider),
  293. push_notification_manager_(std::move(push_notification_manager)),
  294. optimization_guide_logger_(optimization_guide_logger),
  295. clock_(base::DefaultClock::GetInstance()),
  296. background_task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
  297. {base::MayBlock(), base::TaskPriority::BEST_EFFORT})) {
  298. if (push_notification_manager_)
  299. push_notification_manager_->SetDelegate(this);
  300. // Register as an observer to get updates for the component. This is
  301. // needed as a signal during testing.
  302. OptimizationHintsComponentUpdateListener::GetInstance()->AddObserver(this);
  303. hint_cache_->Initialize(
  304. switches::ShouldPurgeOptimizationGuideStoreOnStartup(),
  305. base::BindOnce(&HintsManager::OnHintCacheInitialized,
  306. weak_ptr_factory_.GetWeakPtr()));
  307. }
  308. HintsManager::~HintsManager() {
  309. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  310. }
  311. void HintsManager::Shutdown() {
  312. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  313. OptimizationHintsComponentUpdateListener::GetInstance()->RemoveObserver(this);
  314. base::UmaHistogramBoolean(
  315. "OptimizationGuide.ProcessingComponentAtShutdown",
  316. currently_processing_component_version_.has_value());
  317. if (currently_processing_component_version_) {
  318. // If we are currently processing the component and we are asked to shut
  319. // down, we should clear the pref since the function to clear the pref will
  320. // not run after shut down and we will think that we failed to process the
  321. // component due to a crash.
  322. pref_service_->ClearPref(prefs::kPendingHintsProcessingVersion);
  323. }
  324. }
  325. // static
  326. OptimizationGuideDecision
  327. HintsManager::GetOptimizationGuideDecisionFromOptimizationTypeDecision(
  328. OptimizationTypeDecision optimization_type_decision) {
  329. switch (optimization_type_decision) {
  330. case OptimizationTypeDecision::kAllowedByOptimizationFilter:
  331. case OptimizationTypeDecision::kAllowedByHint:
  332. return OptimizationGuideDecision::kTrue;
  333. case OptimizationTypeDecision::kUnknown:
  334. case OptimizationTypeDecision::kHadOptimizationFilterButNotLoadedInTime:
  335. case OptimizationTypeDecision::kHadHintButNotLoadedInTime:
  336. case OptimizationTypeDecision::kHintFetchStartedButNotAvailableInTime:
  337. case OptimizationTypeDecision::kDeciderNotInitialized:
  338. return OptimizationGuideDecision::kUnknown;
  339. case OptimizationTypeDecision::kNotAllowedByHint:
  340. case OptimizationTypeDecision::kNoMatchingPageHint:
  341. case OptimizationTypeDecision::kNoHintAvailable:
  342. case OptimizationTypeDecision::kNotAllowedByOptimizationFilter:
  343. return OptimizationGuideDecision::kFalse;
  344. }
  345. }
  346. void HintsManager::OnHintsComponentAvailable(const HintsComponentInfo& info) {
  347. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  348. if (currently_processing_component_version_ &&
  349. *currently_processing_component_version_ == info.version) {
  350. OPTIMIZATION_GUIDE_LOGGER(
  351. optimization_guide_common::mojom::LogSource::HINTS,
  352. optimization_guide_logger_)
  353. << "Already in the middle of processing OptimizationHints component "
  354. "version: "
  355. << info.version.GetString();
  356. return;
  357. }
  358. OPTIMIZATION_GUIDE_LOGGER(optimization_guide_common::mojom::LogSource::HINTS,
  359. optimization_guide_logger_)
  360. << "Received OptimizationHints component version: "
  361. << info.version.GetString();
  362. // Check for if hint component is disabled. This check is needed because the
  363. // optimization guide still registers with the service as an observer for
  364. // components as a signal during testing.
  365. if (switches::IsHintComponentProcessingDisabled()) {
  366. MaybeRunUpdateClosure(std::move(next_update_closure_));
  367. return;
  368. }
  369. if (features::ShouldCheckFailedComponentVersionPref() &&
  370. failed_component_version_ &&
  371. failed_component_version_->CompareTo(info.version) >= 0) {
  372. OPTIMIZATION_GUIDE_LOGGER(
  373. optimization_guide_common::mojom::LogSource::HINTS,
  374. optimization_guide_logger_)
  375. << "Skipping processing OptimizationHints component version: "
  376. << info.version.GetString()
  377. << " as it had failed in a previous session";
  378. RecordProcessHintsComponentResult(
  379. ProcessHintsComponentResult::kFailedFinishProcessing);
  380. MaybeRunUpdateClosure(std::move(next_update_closure_));
  381. return;
  382. }
  383. // Write version that we are currently processing to prefs.
  384. pref_service_->SetString(prefs::kPendingHintsProcessingVersion,
  385. info.version.GetString());
  386. std::unique_ptr<StoreUpdateData> update_data =
  387. is_off_the_record_
  388. ? nullptr
  389. : hint_cache_->MaybeCreateUpdateDataForComponentHints(info.version);
  390. // Processes the hints from the newly available component on a background
  391. // thread, providing a StoreUpdateData for component update from the hint
  392. // cache, so that each hint within the component can be moved into it. In the
  393. // case where the component's version is not newer than the optimization guide
  394. // store's component version, StoreUpdateData will be a nullptr and hint
  395. // processing will be skipped.
  396. // base::Unretained(this) is safe since |this| owns |background_task_runner_|
  397. // and the callback will be canceled if destroyed.
  398. currently_processing_component_version_ = info.version;
  399. OPTIMIZATION_GUIDE_LOGGER(optimization_guide_common::mojom::LogSource::HINTS,
  400. optimization_guide_logger_)
  401. << "Processing OptimizationHints component version: "
  402. << currently_processing_component_version_->GetString();
  403. background_task_runner_->PostTaskAndReplyWithResult(
  404. FROM_HERE, base::BindOnce(&ReadComponentFile, info),
  405. base::BindOnce(&HintsManager::UpdateComponentHints,
  406. weak_ptr_factory_.GetWeakPtr(),
  407. std::move(next_update_closure_), std::move(update_data)));
  408. // Only replace hints component info if it is not the same - otherwise we will
  409. // destruct the object and it will be invalid later.
  410. if (!hints_component_info_ ||
  411. hints_component_info_->version.CompareTo(info.version) != 0) {
  412. hints_component_info_.emplace(info.version, info.path);
  413. }
  414. }
  415. void HintsManager::ProcessOptimizationFilters(
  416. const google::protobuf::RepeatedPtrField<proto::OptimizationFilter>&
  417. allowlist_optimization_filters,
  418. const google::protobuf::RepeatedPtrField<proto::OptimizationFilter>&
  419. blocklist_optimization_filters) {
  420. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  421. optimization_types_with_filter_.clear();
  422. allowlist_optimization_filters_.clear();
  423. blocklist_optimization_filters_.clear();
  424. ProcessOptimizationFilterSet(allowlist_optimization_filters,
  425. /*is_allowlist=*/true);
  426. ProcessOptimizationFilterSet(blocklist_optimization_filters,
  427. /*is_allowlist=*/false);
  428. }
  429. void HintsManager::ProcessOptimizationFilterSet(
  430. const google::protobuf::RepeatedPtrField<proto::OptimizationFilter>&
  431. filters,
  432. bool is_allowlist) {
  433. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  434. for (const auto& filter : filters) {
  435. if (filter.optimization_type() != proto::TYPE_UNSPECIFIED) {
  436. optimization_types_with_filter_.insert(filter.optimization_type());
  437. }
  438. // Do not put anything in memory that we don't have registered.
  439. if (registered_optimization_types_.find(filter.optimization_type()) ==
  440. registered_optimization_types_.end()) {
  441. continue;
  442. }
  443. RecordOptimizationFilterStatus(
  444. filter.optimization_type(),
  445. OptimizationFilterStatus::kFoundServerFilterConfig);
  446. // Do not parse duplicate optimization filters.
  447. if (allowlist_optimization_filters_.find(filter.optimization_type()) !=
  448. allowlist_optimization_filters_.end() ||
  449. blocklist_optimization_filters_.find(filter.optimization_type()) !=
  450. blocklist_optimization_filters_.end()) {
  451. RecordOptimizationFilterStatus(
  452. filter.optimization_type(),
  453. OptimizationFilterStatus::kFailedServerFilterDuplicateConfig);
  454. continue;
  455. }
  456. // Parse optimization filter.
  457. OptimizationFilterStatus status;
  458. std::unique_ptr<OptimizationFilter> optimization_filter =
  459. ProcessOptimizationFilter(filter, &status);
  460. if (optimization_filter) {
  461. OPTIMIZATION_GUIDE_LOGGER(
  462. optimization_guide_common::mojom::LogSource::HINTS,
  463. optimization_guide_logger_)
  464. << "Loaded optimization filter for " << filter.optimization_type();
  465. if (is_allowlist) {
  466. allowlist_optimization_filters_.insert(
  467. {filter.optimization_type(), std::move(optimization_filter)});
  468. } else {
  469. blocklist_optimization_filters_.insert(
  470. {filter.optimization_type(), std::move(optimization_filter)});
  471. }
  472. }
  473. RecordOptimizationFilterStatus(filter.optimization_type(), status);
  474. }
  475. }
  476. void HintsManager::OnHintCacheInitialized() {
  477. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  478. OPTIMIZATION_GUIDE_LOGGER(optimization_guide_common::mojom::LogSource::HINTS,
  479. optimization_guide_logger_)
  480. << "Hint cache initialized";
  481. if (push_notification_manager_) {
  482. push_notification_manager_->OnDelegateReady();
  483. }
  484. // Check if there is a valid hint proto given on the command line first. We
  485. // don't normally expect one, but if one is provided then use that and do not
  486. // register as an observer as the opt_guide service.
  487. std::unique_ptr<proto::Configuration> manual_config =
  488. switches::ParseComponentConfigFromCommandLine();
  489. if (manual_config) {
  490. std::unique_ptr<StoreUpdateData> update_data =
  491. is_off_the_record_
  492. ? nullptr
  493. : hint_cache_->MaybeCreateUpdateDataForComponentHints(
  494. base::Version(kManualConfigComponentVersion));
  495. // Allow |UpdateComponentHints| to block startup so that the first
  496. // navigation gets the hints when a command line hint proto is provided.
  497. UpdateComponentHints(base::DoNothing(), std::move(update_data),
  498. std::move(manual_config));
  499. }
  500. // If the store is available, clear all hint state so newly registered types
  501. // can have their hints immediately included in hint fetches.
  502. if (hint_cache_->IsHintStoreAvailable() && should_clear_hints_for_new_type_) {
  503. ClearHostKeyedHints();
  504. should_clear_hints_for_new_type_ = false;
  505. }
  506. // This is used as a signal for testing so that tests can push hints via the
  507. // component. Fixing the logic appropriately is a lot more work for something
  508. // we don't actually do in the wild.
  509. LOCAL_HISTOGRAM_BOOLEAN("OptimizationGuide.HintsManager.HintCacheInitialized",
  510. true);
  511. }
  512. void HintsManager::UpdateComponentHints(
  513. base::OnceClosure update_closure,
  514. std::unique_ptr<StoreUpdateData> update_data,
  515. std::unique_ptr<proto::Configuration> config) {
  516. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  517. // If we get here, the component file has been processed correctly and did not
  518. // crash the device.
  519. OPTIMIZATION_GUIDE_LOGGER(optimization_guide_common::mojom::LogSource::HINTS,
  520. optimization_guide_logger_)
  521. << "Component successfully processed";
  522. currently_processing_component_version_ = absl::nullopt;
  523. pref_service_->ClearPref(prefs::kPendingHintsProcessingVersion);
  524. if (!config) {
  525. MaybeRunUpdateClosure(std::move(update_closure));
  526. return;
  527. }
  528. ProcessOptimizationFilters(config->optimization_allowlists(),
  529. config->optimization_blocklists());
  530. // Don't store hints in the store if it's off the record.
  531. if (update_data && !is_off_the_record_) {
  532. bool did_process_hints = hint_cache_->ProcessAndCacheHints(
  533. config->mutable_hints(), update_data.get());
  534. RecordProcessHintsComponentResult(
  535. did_process_hints ? ProcessHintsComponentResult::kSuccess
  536. : ProcessHintsComponentResult::kProcessedNoHints);
  537. } else {
  538. RecordProcessHintsComponentResult(
  539. ProcessHintsComponentResult::kSkippedProcessingHints);
  540. }
  541. if (update_data) {
  542. hint_cache_->UpdateComponentHints(
  543. std::move(update_data),
  544. base::BindOnce(&HintsManager::OnComponentHintsUpdated,
  545. weak_ptr_factory_.GetWeakPtr(),
  546. std::move(update_closure),
  547. /* hints_updated=*/true));
  548. } else {
  549. OnComponentHintsUpdated(std::move(update_closure), /*hints_updated=*/false);
  550. }
  551. }
  552. void HintsManager::OnComponentHintsUpdated(base::OnceClosure update_closure,
  553. bool hints_updated) {
  554. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  555. // Record the result of updating the hints. This is used as a signal for the
  556. // hints being fully processed in testing.
  557. LOCAL_HISTOGRAM_BOOLEAN(kComponentHintsUpdatedResultHistogramString,
  558. hints_updated);
  559. // Initiate the hints fetch scheduling if deferred startup handling is not
  560. // enabled. Otherwise OnDeferredStartup() will iniitate it.
  561. if (!features::ShouldDeferStartupActiveTabsHintsFetch())
  562. InitiateHintsFetchScheduling();
  563. MaybeRunUpdateClosure(std::move(update_closure));
  564. }
  565. void HintsManager::InitiateHintsFetchScheduling() {
  566. if (features::ShouldBatchUpdateHintsForActiveTabsAndTopHosts()) {
  567. SetLastHintsFetchAttemptTime(clock_->Now());
  568. if (switches::ShouldOverrideFetchHintsTimer() ||
  569. features::ShouldDeferStartupActiveTabsHintsFetch()) {
  570. FetchHintsForActiveTabs();
  571. } else if (!active_tabs_hints_fetch_timer_.IsRunning()) {
  572. // Batch update hints with a random delay.
  573. active_tabs_hints_fetch_timer_.Start(
  574. FROM_HERE, RandomFetchDelay(), this,
  575. &HintsManager::FetchHintsForActiveTabs);
  576. }
  577. }
  578. }
  579. void HintsManager::ListenForNextUpdateForTesting(
  580. base::OnceClosure next_update_closure) {
  581. DCHECK(!next_update_closure_)
  582. << "Only one update closure is supported at a time";
  583. next_update_closure_ = std::move(next_update_closure);
  584. }
  585. void HintsManager::SetHintsFetcherFactoryForTesting(
  586. std::unique_ptr<HintsFetcherFactory> hints_fetcher_factory) {
  587. hints_fetcher_factory_ = std::move(hints_fetcher_factory);
  588. }
  589. void HintsManager::SetClockForTesting(const base::Clock* clock) {
  590. clock_ = clock;
  591. }
  592. void HintsManager::ScheduleActiveTabsHintsFetch() {
  593. DCHECK(!active_tabs_hints_fetch_timer_.IsRunning());
  594. const base::TimeDelta active_tabs_refresh_duration =
  595. features::GetActiveTabsFetchRefreshDuration();
  596. const base::TimeDelta time_since_last_fetch =
  597. clock_->Now() - GetLastHintsFetchAttemptTime();
  598. if (time_since_last_fetch >= active_tabs_refresh_duration) {
  599. // Fetched hints in the store should be updated and an attempt has not
  600. // been made in last
  601. // |features::GetActiveTabsFetchRefreshDuration()|.
  602. SetLastHintsFetchAttemptTime(clock_->Now());
  603. active_tabs_hints_fetch_timer_.Start(
  604. FROM_HERE, RandomFetchDelay(), this,
  605. &HintsManager::FetchHintsForActiveTabs);
  606. } else {
  607. // If the fetched hints in the store are still up-to-date, set a timer
  608. // for when the hints need to be updated.
  609. base::TimeDelta fetcher_delay =
  610. active_tabs_refresh_duration - time_since_last_fetch;
  611. active_tabs_hints_fetch_timer_.Start(
  612. FROM_HERE, fetcher_delay, this,
  613. &HintsManager::ScheduleActiveTabsHintsFetch);
  614. }
  615. }
  616. const std::vector<GURL> HintsManager::GetActiveTabURLsToRefresh() {
  617. if (!tab_url_provider_)
  618. return {};
  619. std::vector<GURL> active_tab_urls = tab_url_provider_->GetUrlsOfActiveTabs(
  620. features::GetActiveTabsStalenessTolerance());
  621. std::set<GURL> urls_to_refresh;
  622. for (const auto& url : active_tab_urls) {
  623. if (!IsValidURLForURLKeyedHint(url))
  624. continue;
  625. if (!hint_cache_->HasURLKeyedEntryForURL(url))
  626. urls_to_refresh.insert(url);
  627. }
  628. return std::vector<GURL>(urls_to_refresh.begin(), urls_to_refresh.end());
  629. }
  630. void HintsManager::FetchHintsForActiveTabs() {
  631. active_tabs_hints_fetch_timer_.Stop();
  632. active_tabs_hints_fetch_timer_.Start(
  633. FROM_HERE, features::GetActiveTabsFetchRefreshDuration(), this,
  634. &HintsManager::ScheduleActiveTabsHintsFetch);
  635. if (!IsUserPermittedToFetchFromRemoteOptimizationGuide(is_off_the_record_,
  636. pref_service_)) {
  637. return;
  638. }
  639. if (!HasOptimizationTypeToFetchFor())
  640. return;
  641. std::vector<std::string> top_hosts;
  642. if (top_host_provider_)
  643. top_hosts = top_host_provider_->GetTopHosts();
  644. const std::vector<GURL> active_tab_urls_to_refresh =
  645. GetActiveTabURLsToRefresh();
  646. base::UmaHistogramCounts100(
  647. "OptimizationGuide.HintsManager.ActiveTabUrlsToFetchFor",
  648. active_tab_urls_to_refresh.size());
  649. if (top_hosts.empty() && active_tab_urls_to_refresh.empty())
  650. return;
  651. // Add hosts of active tabs to list of hosts to fetch for. Since we are mainly
  652. // fetching for updated information on tabs, add those to the front of the
  653. // list.
  654. base::flat_set<std::string> top_hosts_set =
  655. base::flat_set<std::string>(top_hosts.begin(), top_hosts.end());
  656. for (const auto& url : active_tab_urls_to_refresh) {
  657. if (!url.has_host() ||
  658. top_hosts_set.find(url.host()) == top_hosts_set.end()) {
  659. continue;
  660. }
  661. if (!hint_cache_->HasHint(url.host())) {
  662. top_hosts_set.insert(url.host());
  663. top_hosts.insert(top_hosts.begin(), url.host());
  664. }
  665. }
  666. MaybeLogGetHintRequestInfo(
  667. proto::CONTEXT_BATCH_UPDATE_ACTIVE_TABS, registered_optimization_types_,
  668. active_tab_urls_to_refresh, top_hosts, optimization_guide_logger_);
  669. if (!active_tabs_batch_update_hints_fetcher_) {
  670. DCHECK(hints_fetcher_factory_);
  671. active_tabs_batch_update_hints_fetcher_ =
  672. hints_fetcher_factory_->BuildInstance(optimization_guide_logger_);
  673. }
  674. active_tabs_batch_update_hints_fetcher_->FetchOptimizationGuideServiceHints(
  675. top_hosts, active_tab_urls_to_refresh, registered_optimization_types_,
  676. proto::CONTEXT_BATCH_UPDATE_ACTIVE_TABS, application_locale_,
  677. base::BindOnce(&HintsManager::OnHintsForActiveTabsFetched,
  678. weak_ptr_factory_.GetWeakPtr(), top_hosts_set,
  679. base::flat_set<GURL>(active_tab_urls_to_refresh.begin(),
  680. active_tab_urls_to_refresh.end())));
  681. }
  682. void HintsManager::OnHintsForActiveTabsFetched(
  683. const base::flat_set<std::string>& hosts_fetched,
  684. const base::flat_set<GURL>& urls_fetched,
  685. absl::optional<std::unique_ptr<proto::GetHintsResponse>>
  686. get_hints_response) {
  687. if (!get_hints_response) {
  688. OPTIMIZATION_GUIDE_LOG(optimization_guide_common::mojom::LogSource::HINTS,
  689. optimization_guide_logger_,
  690. "OnHintsForActiveTabsFetched failed");
  691. return;
  692. }
  693. hint_cache_->UpdateFetchedHints(
  694. std::move(*get_hints_response),
  695. clock_->Now() + features::GetActiveTabsFetchRefreshDuration(),
  696. hosts_fetched, urls_fetched,
  697. base::BindOnce(&HintsManager::OnFetchedActiveTabsHintsStored,
  698. weak_ptr_factory_.GetWeakPtr()));
  699. OPTIMIZATION_GUIDE_LOG(optimization_guide_common::mojom::LogSource::HINTS,
  700. optimization_guide_logger_,
  701. "OnHintsForActiveTabsFetched complete");
  702. }
  703. void HintsManager::OnPageNavigationHintsFetched(
  704. base::WeakPtr<OptimizationGuideNavigationData> navigation_data_weak_ptr,
  705. const absl::optional<GURL>& navigation_url,
  706. const base::flat_set<GURL>& page_navigation_urls_requested,
  707. const base::flat_set<std::string>& page_navigation_hosts_requested,
  708. absl::optional<std::unique_ptr<proto::GetHintsResponse>>
  709. get_hints_response) {
  710. if (navigation_url) {
  711. CleanUpFetcherForNavigation(*navigation_url);
  712. }
  713. if (!get_hints_response.has_value() || !get_hints_response.value()) {
  714. OPTIMIZATION_GUIDE_LOG(optimization_guide_common::mojom::LogSource::HINTS,
  715. optimization_guide_logger_,
  716. "OnPageNavigationHintsFetched failed");
  717. if (navigation_url) {
  718. PrepareToInvokeRegisteredCallbacks(*navigation_url);
  719. }
  720. return;
  721. }
  722. hint_cache_->UpdateFetchedHints(
  723. std::move(*get_hints_response),
  724. clock_->Now() + features::GetActiveTabsFetchRefreshDuration(),
  725. page_navigation_hosts_requested, page_navigation_urls_requested,
  726. base::BindOnce(&HintsManager::OnFetchedPageNavigationHintsStored,
  727. weak_ptr_factory_.GetWeakPtr(), navigation_data_weak_ptr,
  728. navigation_url, page_navigation_hosts_requested));
  729. OPTIMIZATION_GUIDE_LOG(optimization_guide_common::mojom::LogSource::HINTS,
  730. optimization_guide_logger_,
  731. "OnPageNavigationHintsFetched complete");
  732. }
  733. void HintsManager::OnFetchedActiveTabsHintsStored() {
  734. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  735. LOCAL_HISTOGRAM_BOOLEAN("OptimizationGuide.FetchedHints.Stored", true);
  736. if (!features::ShouldPersistHintsToDisk()) {
  737. // If we aren't persisting hints to disk, there's no point in purging
  738. // hints from disk or starting a new fetch since at this point we should
  739. // just be fetching everything on page navigation and only storing
  740. // in-memory.
  741. return;
  742. }
  743. hint_cache_->PurgeExpiredFetchedHints();
  744. }
  745. void HintsManager::OnFetchedPageNavigationHintsStored(
  746. base::WeakPtr<OptimizationGuideNavigationData> navigation_data_weak_ptr,
  747. const absl::optional<GURL>& navigation_url,
  748. const base::flat_set<std::string>& page_navigation_hosts_requested) {
  749. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  750. if (navigation_data_weak_ptr) {
  751. navigation_data_weak_ptr->set_hints_fetch_end(base::TimeTicks::Now());
  752. }
  753. base::UmaHistogramBoolean(
  754. "OptimizationGuide.HintsManager."
  755. "PageNavigationHintsReturnedBeforeDataFlushed",
  756. navigation_data_weak_ptr.MaybeValid());
  757. if (navigation_url) {
  758. PrepareToInvokeRegisteredCallbacks(*navigation_url);
  759. }
  760. }
  761. bool HintsManager::IsHintBeingFetchedForNavigation(const GURL& navigation_url) {
  762. return page_navigation_hints_fetchers_.Get(navigation_url) !=
  763. page_navigation_hints_fetchers_.end();
  764. }
  765. void HintsManager::CleanUpFetcherForNavigation(const GURL& navigation_url) {
  766. auto it = page_navigation_hints_fetchers_.Peek(navigation_url);
  767. if (it != page_navigation_hints_fetchers_.end())
  768. page_navigation_hints_fetchers_.Erase(it);
  769. }
  770. base::Time HintsManager::GetLastHintsFetchAttemptTime() const {
  771. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  772. return base::Time::FromDeltaSinceWindowsEpoch(base::Microseconds(
  773. pref_service_->GetInt64(prefs::kHintsFetcherLastFetchAttempt)));
  774. }
  775. void HintsManager::SetLastHintsFetchAttemptTime(base::Time last_attempt_time) {
  776. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  777. pref_service_->SetInt64(
  778. prefs::kHintsFetcherLastFetchAttempt,
  779. last_attempt_time.ToDeltaSinceWindowsEpoch().InMicroseconds());
  780. }
  781. void HintsManager::LoadHintForURL(const GURL& url, base::OnceClosure callback) {
  782. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  783. if (!url.has_host()) {
  784. MaybeRunUpdateClosure(std::move(callback));
  785. return;
  786. }
  787. LoadHintForHost(url.host(), std::move(callback));
  788. }
  789. void HintsManager::LoadHintForHost(const std::string& host,
  790. base::OnceClosure callback) {
  791. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  792. hint_cache_->LoadHint(host, base::BindOnce(&HintsManager::OnHintLoaded,
  793. weak_ptr_factory_.GetWeakPtr(),
  794. std::move(callback)));
  795. }
  796. void HintsManager::FetchHintsForURLs(const std::vector<GURL>& urls,
  797. proto::RequestContext request_context) {
  798. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  799. // Collect hosts, stripping duplicates, but preserving the ordering.
  800. InsertionOrderedSet<GURL> target_urls;
  801. InsertionOrderedSet<std::string> target_hosts;
  802. for (const auto& url : urls) {
  803. target_hosts.insert(url.host());
  804. target_urls.insert(url);
  805. }
  806. if (target_hosts.empty() && target_urls.empty())
  807. return;
  808. MaybeLogGetHintRequestInfo(request_context, registered_optimization_types_,
  809. target_urls.vector(), target_hosts.vector(),
  810. optimization_guide_logger_);
  811. std::pair<int32_t, HintsFetcher*> request_id_and_fetcher =
  812. CreateAndTrackBatchUpdateHintsFetcher();
  813. // Use the batch update hints fetcher for fetches off the SRP since we are
  814. // not fetching for the current navigation.
  815. //
  816. // Caller does not expect to be notified when relevant hints have been fetched
  817. // and stored.
  818. request_id_and_fetcher.second->FetchOptimizationGuideServiceHints(
  819. target_hosts.vector(), target_urls.vector(),
  820. registered_optimization_types_, request_context, application_locale_,
  821. base::BindOnce(
  822. &HintsManager::OnBatchUpdateHintsFetched,
  823. weak_ptr_factory_.GetWeakPtr(), request_id_and_fetcher.first,
  824. request_context, target_hosts.set(), target_urls.set(),
  825. target_urls.set(), registered_optimization_types_,
  826. base::DoNothingAs<void(
  827. const GURL&, const base::flat_map<
  828. proto::OptimizationType,
  829. OptimizationGuideDecisionWithMetadata>&)>()));
  830. }
  831. void HintsManager::OnHintLoaded(base::OnceClosure callback,
  832. const proto::Hint* loaded_hint) const {
  833. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  834. // Record the result of loading a hint. This is used as a signal for testing.
  835. LOCAL_HISTOGRAM_BOOLEAN(kLoadedHintLocalHistogramString, loaded_hint);
  836. // Run the callback now that the hint is loaded. This is used as a signal by
  837. // tests.
  838. MaybeRunUpdateClosure(std::move(callback));
  839. }
  840. void HintsManager::RegisterOptimizationTypes(
  841. const std::vector<proto::OptimizationType>& optimization_types) {
  842. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  843. bool should_load_new_optimization_filter = false;
  844. DictionaryPrefUpdate previously_registered_opt_types(
  845. pref_service_, prefs::kPreviouslyRegisteredOptimizationTypes);
  846. for (const auto optimization_type : optimization_types) {
  847. if (optimization_type == proto::TYPE_UNSPECIFIED)
  848. continue;
  849. if (registered_optimization_types_.find(optimization_type) !=
  850. registered_optimization_types_.end()) {
  851. continue;
  852. }
  853. registered_optimization_types_.insert(optimization_type);
  854. if (optimization_guide_logger_->ShouldEnableDebugLogs()) {
  855. OPTIMIZATION_GUIDE_LOGGER(
  856. optimization_guide_common::mojom::LogSource::HINTS,
  857. optimization_guide_logger_)
  858. << "Registered new OptimizationType: " << optimization_type;
  859. }
  860. absl::optional<double> value = previously_registered_opt_types->FindBoolKey(
  861. proto::OptimizationType_Name(optimization_type));
  862. if (!value) {
  863. if (!is_off_the_record_ &&
  864. !ShouldIgnoreNewlyRegisteredOptimizationType(optimization_type) &&
  865. !base::CommandLine::ForCurrentProcess()->HasSwitch(
  866. switches::kHintsProtoOverride)) {
  867. should_clear_hints_for_new_type_ = true;
  868. }
  869. previously_registered_opt_types->SetBoolKey(
  870. proto::OptimizationType_Name(optimization_type), true);
  871. }
  872. if (!should_load_new_optimization_filter) {
  873. if (optimization_types_with_filter_.find(optimization_type) !=
  874. optimization_types_with_filter_.end()) {
  875. should_load_new_optimization_filter = true;
  876. }
  877. }
  878. }
  879. // If the store is available, clear all hint state so newly registered types
  880. // can have their hints immediately included in hint fetches.
  881. if (hint_cache_->IsHintStoreAvailable() && should_clear_hints_for_new_type_) {
  882. ClearHostKeyedHints();
  883. should_clear_hints_for_new_type_ = false;
  884. }
  885. if (should_load_new_optimization_filter) {
  886. if (switches::IsHintComponentProcessingDisabled()) {
  887. std::unique_ptr<proto::Configuration> manual_config =
  888. switches::ParseComponentConfigFromCommandLine();
  889. if (manual_config->optimization_allowlists_size() > 0 ||
  890. manual_config->optimization_blocklists_size() > 0) {
  891. ProcessOptimizationFilters(manual_config->optimization_allowlists(),
  892. manual_config->optimization_blocklists());
  893. }
  894. } else {
  895. DCHECK(hints_component_info_);
  896. OnHintsComponentAvailable(*hints_component_info_);
  897. }
  898. } else {
  899. MaybeRunUpdateClosure(std::move(next_update_closure_));
  900. }
  901. }
  902. bool HintsManager::HasLoadedOptimizationAllowlist(
  903. proto::OptimizationType optimization_type) {
  904. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  905. return allowlist_optimization_filters_.find(optimization_type) !=
  906. allowlist_optimization_filters_.end();
  907. }
  908. bool HintsManager::HasLoadedOptimizationBlocklist(
  909. proto::OptimizationType optimization_type) {
  910. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  911. return blocklist_optimization_filters_.find(optimization_type) !=
  912. blocklist_optimization_filters_.end();
  913. }
  914. base::flat_map<proto::OptimizationType, OptimizationGuideDecisionWithMetadata>
  915. HintsManager::GetDecisionsWithCachedInformationForURLAndOptimizationTypes(
  916. const GURL& url,
  917. const base::flat_set<proto::OptimizationType>& optimization_types) {
  918. base::flat_map<proto::OptimizationType, OptimizationGuideDecisionWithMetadata>
  919. decisions;
  920. for (const auto optimization_type : optimization_types) {
  921. OptimizationMetadata metadata;
  922. OptimizationTypeDecision type_decision =
  923. CanApplyOptimization(url, optimization_type, &metadata);
  924. OptimizationGuideDecision decision =
  925. GetOptimizationGuideDecisionFromOptimizationTypeDecision(type_decision);
  926. decisions[optimization_type] = {decision, metadata};
  927. }
  928. return decisions;
  929. }
  930. void HintsManager::CanApplyOptimizationOnDemand(
  931. const std::vector<GURL>& urls,
  932. const base::flat_set<proto::OptimizationType>& optimization_types,
  933. proto::RequestContext request_context,
  934. OnDemandOptimizationGuideDecisionRepeatingCallback callback) {
  935. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  936. // TODO(crbug/1275612): Check whether we have consent to fetch.
  937. // This set contains URLs that require some information to be fetched, whether
  938. // that be a URL-keyed hint or a host-keyed hint.
  939. base::flat_set<GURL> urls_with_pending_callback;
  940. InsertionOrderedSet<GURL> urls_to_fetch;
  941. InsertionOrderedSet<std::string> hosts_to_fetch;
  942. for (const auto& url : urls) {
  943. if (!hint_cache_->HasURLKeyedEntryForURL(url)) {
  944. urls_to_fetch.insert(url);
  945. urls_with_pending_callback.insert(url);
  946. }
  947. // We check for the hint being loaded mostly for code simplicity. If we just
  948. // check for the presence in the cache and the hint wasn't loaded, we need
  949. // to run the load callback and then invoke the callbacks. However, as the
  950. // fetch will just ignore the host if cached, this is ok since the callback
  951. // from the fetch will initiate the loading of the hint and the resulting
  952. // callback to be run.
  953. if (!hint_cache_->HasHint(url.host()) ||
  954. (hint_cache_->GetHostKeyedHintIfLoaded(url.host()) == nullptr)) {
  955. hosts_to_fetch.insert(url.host());
  956. urls_with_pending_callback.insert(url);
  957. }
  958. if (!urls_with_pending_callback.contains(url)) {
  959. // Only get decisions if we know we do not need to fetch for anything.
  960. base::flat_map<proto::OptimizationType,
  961. OptimizationGuideDecisionWithMetadata>
  962. decisions =
  963. GetDecisionsWithCachedInformationForURLAndOptimizationTypes(
  964. url, optimization_types);
  965. callback.Run(url, decisions);
  966. }
  967. }
  968. if (urls_with_pending_callback.empty()) {
  969. // Nothing to fetch for.
  970. return;
  971. }
  972. MaybeLogGetHintRequestInfo(request_context, registered_optimization_types_,
  973. urls_to_fetch.vector(), hosts_to_fetch.vector(),
  974. optimization_guide_logger_);
  975. // Fetch the data for the entries we don't have all information for.
  976. std::pair<int32_t, HintsFetcher*> request_id_and_fetcher =
  977. CreateAndTrackBatchUpdateHintsFetcher();
  978. request_id_and_fetcher.second->FetchOptimizationGuideServiceHints(
  979. hosts_to_fetch.vector(), urls_to_fetch.vector(),
  980. registered_optimization_types_, request_context, application_locale_,
  981. base::BindOnce(&HintsManager::OnBatchUpdateHintsFetched,
  982. weak_ptr_factory_.GetWeakPtr(),
  983. request_id_and_fetcher.first, request_context,
  984. hosts_to_fetch.set(), urls_to_fetch.set(),
  985. urls_with_pending_callback, optimization_types, callback));
  986. }
  987. // TODO(1313521): Improve metrics coverage between all of these apis.
  988. void HintsManager::CanApplyOptimization(
  989. const GURL& url,
  990. proto::OptimizationType optimization_type,
  991. OptimizationGuideDecisionCallback callback) {
  992. // Check if there is a pending fetcher for the specified URL. If there is, use
  993. // the async API, otherwise use the synchronous one.
  994. // TODO(1312035): We should record instances of this API being used prior to a
  995. // fetch for the URL being initiated.
  996. if (IsHintBeingFetchedForNavigation(url)) {
  997. CanApplyOptimizationAsync(url, optimization_type, std::move(callback));
  998. } else {
  999. OptimizationMetadata meta;
  1000. OptimizationGuideDecision decision =
  1001. GetOptimizationGuideDecisionFromOptimizationTypeDecision(
  1002. CanApplyOptimization(url, optimization_type, &meta));
  1003. base::UmaHistogramEnumeration(
  1004. "OptimizationGuide.ApplyDecision." +
  1005. optimization_guide::GetStringNameForOptimizationType(
  1006. optimization_type),
  1007. decision);
  1008. std::move(callback).Run(decision, meta);
  1009. }
  1010. }
  1011. void HintsManager::OnBatchUpdateHintsFetched(
  1012. int32_t request_id,
  1013. proto::RequestContext request_context,
  1014. const base::flat_set<std::string>& hosts_requested,
  1015. const base::flat_set<GURL>& urls_requested,
  1016. const base::flat_set<GURL>& urls_with_pending_callback,
  1017. const base::flat_set<proto::OptimizationType>& optimization_types,
  1018. OnDemandOptimizationGuideDecisionRepeatingCallback callback,
  1019. absl::optional<std::unique_ptr<proto::GetHintsResponse>>
  1020. get_hints_response) {
  1021. CleanUpBatchUpdateHintsFetcher(request_id);
  1022. if (!get_hints_response.has_value() || !get_hints_response.value()) {
  1023. if (optimization_guide_logger_->ShouldEnableDebugLogs()) {
  1024. OPTIMIZATION_GUIDE_LOGGER(
  1025. optimization_guide_common::mojom::LogSource::HINTS,
  1026. optimization_guide_logger_)
  1027. << "OnBatchUpdateHintsFetched failed for " << request_context;
  1028. }
  1029. OnBatchUpdateHintsStored(urls_with_pending_callback, optimization_types,
  1030. callback);
  1031. return;
  1032. }
  1033. // TODO(crbug/1278015): Figure out if the update time duration is the right
  1034. // one.
  1035. hint_cache_->UpdateFetchedHints(
  1036. std::move(*get_hints_response),
  1037. clock_->Now() + features::GetActiveTabsFetchRefreshDuration(),
  1038. hosts_requested, urls_requested,
  1039. base::BindOnce(&HintsManager::OnBatchUpdateHintsStored,
  1040. weak_ptr_factory_.GetWeakPtr(), urls_with_pending_callback,
  1041. optimization_types, callback));
  1042. if (optimization_guide_logger_->ShouldEnableDebugLogs()) {
  1043. OPTIMIZATION_GUIDE_LOGGER(
  1044. optimization_guide_common::mojom::LogSource::HINTS,
  1045. optimization_guide_logger_)
  1046. << "OnBatchUpdateHintsFetched for " << request_context << " complete";
  1047. }
  1048. }
  1049. void HintsManager::OnBatchUpdateHintsStored(
  1050. const base::flat_set<GURL>& urls,
  1051. const base::flat_set<proto::OptimizationType>& optimization_types,
  1052. OnDemandOptimizationGuideDecisionRepeatingCallback callback) {
  1053. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  1054. for (const auto& url : urls) {
  1055. // Load the hint for host if we have a host-keyed hint before invoking the
  1056. // callbacks so we have all the necessary information to make the decision.
  1057. LoadHintForHost(
  1058. url.host(),
  1059. base::BindOnce(&HintsManager::InvokeOnDemandHintsCallbackForURL,
  1060. weak_ptr_factory_.GetWeakPtr(), url, optimization_types,
  1061. callback));
  1062. }
  1063. }
  1064. std::pair<int32_t, HintsFetcher*>
  1065. HintsManager::CreateAndTrackBatchUpdateHintsFetcher() {
  1066. DCHECK(hints_fetcher_factory_);
  1067. std::unique_ptr<HintsFetcher> hints_fetcher =
  1068. hints_fetcher_factory_->BuildInstance(optimization_guide_logger_);
  1069. HintsFetcher* hints_fetcher_ptr = hints_fetcher.get();
  1070. batch_update_hints_fetchers_.Put(batch_update_hints_fetcher_request_id_++,
  1071. std::move(hints_fetcher));
  1072. UMA_HISTOGRAM_COUNTS_100(
  1073. "OptimizationGuide.HintsManager.ConcurrentBatchUpdateFetches",
  1074. batch_update_hints_fetchers_.size());
  1075. return std::make_pair(batch_update_hints_fetcher_request_id_,
  1076. hints_fetcher_ptr);
  1077. }
  1078. void HintsManager::CleanUpBatchUpdateHintsFetcher(int32_t request_id) {
  1079. auto it = batch_update_hints_fetchers_.Peek(request_id);
  1080. if (it != batch_update_hints_fetchers_.end())
  1081. batch_update_hints_fetchers_.Erase(it);
  1082. }
  1083. void HintsManager::InvokeOnDemandHintsCallbackForURL(
  1084. const GURL& url,
  1085. const base::flat_set<proto::OptimizationType>& optimization_types,
  1086. OnDemandOptimizationGuideDecisionRepeatingCallback callback) {
  1087. base::flat_map<proto::OptimizationType, OptimizationGuideDecisionWithMetadata>
  1088. decisions = GetDecisionsWithCachedInformationForURLAndOptimizationTypes(
  1089. url, optimization_types);
  1090. callback.Run(url, decisions);
  1091. }
  1092. void HintsManager::CanApplyOptimizationAsync(
  1093. const GURL& navigation_url,
  1094. proto::OptimizationType optimization_type,
  1095. OptimizationGuideDecisionCallback callback) {
  1096. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  1097. OptimizationMetadata metadata;
  1098. OptimizationTypeDecision type_decision =
  1099. CanApplyOptimization(navigation_url, optimization_type, &metadata);
  1100. OptimizationGuideDecision decision =
  1101. GetOptimizationGuideDecisionFromOptimizationTypeDecision(type_decision);
  1102. // It's possible that a hint that applies to |navigation_url| will come in
  1103. // later, so only run the callback if we are sure we can apply the decision.
  1104. if (decision == OptimizationGuideDecision::kTrue ||
  1105. HasAllInformationForDecisionAvailable(navigation_url,
  1106. optimization_type)) {
  1107. base::UmaHistogramEnumeration(
  1108. "OptimizationGuide.ApplyDecisionAsync." +
  1109. GetStringNameForOptimizationType(optimization_type),
  1110. type_decision);
  1111. std::move(callback).Run(decision, metadata);
  1112. return;
  1113. }
  1114. registered_callbacks_[navigation_url][optimization_type].push_back(
  1115. std::move(callback));
  1116. }
  1117. OptimizationTypeDecision HintsManager::CanApplyOptimization(
  1118. const GURL& navigation_url,
  1119. proto::OptimizationType optimization_type,
  1120. OptimizationMetadata* optimization_metadata) {
  1121. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  1122. ScopedCanApplyOptimizationLogger scoped_logger(
  1123. optimization_type, navigation_url, optimization_guide_logger_);
  1124. // Clear out optimization metadata if provided.
  1125. if (optimization_metadata)
  1126. *optimization_metadata = {};
  1127. // Ensure that developers register their opt types before asking the
  1128. // optimization guide for data for that type.
  1129. DCHECK(registered_optimization_types_.find(optimization_type) !=
  1130. registered_optimization_types_.end());
  1131. // If the type is not registered, we probably don't have a hint for it, so
  1132. // just return.
  1133. if (registered_optimization_types_.find(optimization_type) ==
  1134. registered_optimization_types_.end()) {
  1135. scoped_logger.set_type_decision(OptimizationTypeDecision::kNoHintAvailable);
  1136. return OptimizationTypeDecision::kNoHintAvailable;
  1137. }
  1138. // If the URL doesn't have a host, we cannot query the hint for it, so just
  1139. // return early.
  1140. if (!navigation_url.has_host()) {
  1141. scoped_logger.set_type_decision(OptimizationTypeDecision::kNoHintAvailable);
  1142. return OptimizationTypeDecision::kNoHintAvailable;
  1143. }
  1144. const auto& host = navigation_url.host();
  1145. // Check if the URL should be filtered out if we have an optimization filter
  1146. // for the type.
  1147. // Check if we have an allowlist loaded into memory for it, and if we do,
  1148. // see if the URL matches anything in the filter.
  1149. if (allowlist_optimization_filters_.find(optimization_type) !=
  1150. allowlist_optimization_filters_.end()) {
  1151. const auto type_decision =
  1152. allowlist_optimization_filters_[optimization_type]->Matches(
  1153. navigation_url)
  1154. ? OptimizationTypeDecision::kAllowedByOptimizationFilter
  1155. : OptimizationTypeDecision::kNotAllowedByOptimizationFilter;
  1156. scoped_logger.set_type_decision(type_decision);
  1157. return type_decision;
  1158. }
  1159. // Check if we have a blocklist loaded into memory for it, and if we do, see
  1160. // if the URL matches anything in the filter.
  1161. if (blocklist_optimization_filters_.find(optimization_type) !=
  1162. blocklist_optimization_filters_.end()) {
  1163. const auto type_decision =
  1164. blocklist_optimization_filters_[optimization_type]->Matches(
  1165. navigation_url)
  1166. ? OptimizationTypeDecision::kNotAllowedByOptimizationFilter
  1167. : OptimizationTypeDecision::kAllowedByOptimizationFilter;
  1168. scoped_logger.set_type_decision(type_decision);
  1169. return type_decision;
  1170. }
  1171. // Check if we had an optimization filter for it, but it was not loaded into
  1172. // memory.
  1173. if (optimization_types_with_filter_.find(optimization_type) !=
  1174. optimization_types_with_filter_.end()) {
  1175. scoped_logger.set_type_decision(
  1176. OptimizationTypeDecision::kHadOptimizationFilterButNotLoadedInTime);
  1177. return OptimizationTypeDecision::kHadOptimizationFilterButNotLoadedInTime;
  1178. }
  1179. // First, check if the optimization type is allowlisted by a URL-keyed hint.
  1180. const proto::Hint* url_keyed_hint =
  1181. hint_cache_->GetURLKeyedHint(navigation_url);
  1182. if (url_keyed_hint) {
  1183. DCHECK_EQ(url_keyed_hint->page_hints_size(), 1);
  1184. if (url_keyed_hint->page_hints_size() > 0) {
  1185. if (IsOptimizationTypeAllowed(
  1186. url_keyed_hint->page_hints(0).allowlisted_optimizations(),
  1187. optimization_type, optimization_metadata)) {
  1188. scoped_logger.set_type_decision(
  1189. OptimizationTypeDecision::kAllowedByHint);
  1190. if (optimization_metadata && !optimization_metadata->empty())
  1191. scoped_logger.set_has_metadata();
  1192. return OptimizationTypeDecision::kAllowedByHint;
  1193. }
  1194. }
  1195. }
  1196. // Check if we have a hint already loaded for this navigation.
  1197. const proto::Hint* loaded_hint = hint_cache_->GetHostKeyedHintIfLoaded(host);
  1198. if (!loaded_hint) {
  1199. if (hint_cache_->HasHint(host)) {
  1200. // If we do not have a hint already loaded and we do not have one in the
  1201. // cache, we do not know what to do with the URL so just return.
  1202. // Otherwise, we do have information, but we just do not know it yet.
  1203. if (features::ShouldPersistHintsToDisk()) {
  1204. scoped_logger.set_type_decision(
  1205. OptimizationTypeDecision::kHadHintButNotLoadedInTime);
  1206. return OptimizationTypeDecision::kHadHintButNotLoadedInTime;
  1207. } else {
  1208. scoped_logger.set_type_decision(
  1209. OptimizationTypeDecision::kNoHintAvailable);
  1210. return OptimizationTypeDecision::kNoHintAvailable;
  1211. }
  1212. }
  1213. if (IsHintBeingFetchedForNavigation(navigation_url)) {
  1214. scoped_logger.set_type_decision(
  1215. OptimizationTypeDecision::kHintFetchStartedButNotAvailableInTime);
  1216. return OptimizationTypeDecision::kHintFetchStartedButNotAvailableInTime;
  1217. }
  1218. scoped_logger.set_type_decision(OptimizationTypeDecision::kNoHintAvailable);
  1219. return OptimizationTypeDecision::kNoHintAvailable;
  1220. }
  1221. if (IsOptimizationTypeAllowed(loaded_hint->allowlisted_optimizations(),
  1222. optimization_type, optimization_metadata)) {
  1223. scoped_logger.set_type_decision(OptimizationTypeDecision::kAllowedByHint);
  1224. if (optimization_metadata && !optimization_metadata->empty())
  1225. scoped_logger.set_has_metadata();
  1226. return OptimizationTypeDecision::kAllowedByHint;
  1227. }
  1228. const proto::PageHint* matched_page_hint =
  1229. loaded_hint ? FindPageHintForURL(navigation_url, loaded_hint) : nullptr;
  1230. if (!matched_page_hint) {
  1231. scoped_logger.set_type_decision(
  1232. OptimizationTypeDecision::kNotAllowedByHint);
  1233. return OptimizationTypeDecision::kNotAllowedByHint;
  1234. }
  1235. bool is_allowed =
  1236. IsOptimizationTypeAllowed(matched_page_hint->allowlisted_optimizations(),
  1237. optimization_type, optimization_metadata);
  1238. const auto type_decision = is_allowed
  1239. ? OptimizationTypeDecision::kAllowedByHint
  1240. : OptimizationTypeDecision::kNotAllowedByHint;
  1241. scoped_logger.set_type_decision(type_decision);
  1242. if (optimization_metadata && !optimization_metadata->empty())
  1243. scoped_logger.set_has_metadata();
  1244. return type_decision;
  1245. }
  1246. void HintsManager::PrepareToInvokeRegisteredCallbacks(
  1247. const GURL& navigation_url) {
  1248. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  1249. if (registered_callbacks_.find(navigation_url) == registered_callbacks_.end())
  1250. return;
  1251. LoadHintForHost(
  1252. navigation_url.host(),
  1253. base::BindOnce(&HintsManager::OnReadyToInvokeRegisteredCallbacks,
  1254. weak_ptr_factory_.GetWeakPtr(), navigation_url));
  1255. }
  1256. void HintsManager::OnReadyToInvokeRegisteredCallbacks(
  1257. const GURL& navigation_url) {
  1258. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  1259. if (registered_callbacks_.find(navigation_url) ==
  1260. registered_callbacks_.end()) {
  1261. return;
  1262. }
  1263. for (auto& opt_type_and_callbacks :
  1264. registered_callbacks_.at(navigation_url)) {
  1265. proto::OptimizationType opt_type = opt_type_and_callbacks.first;
  1266. for (auto& callback : opt_type_and_callbacks.second) {
  1267. OptimizationMetadata metadata;
  1268. OptimizationTypeDecision type_decision =
  1269. CanApplyOptimization(navigation_url, opt_type, &metadata);
  1270. OptimizationGuideDecision decision =
  1271. GetOptimizationGuideDecisionFromOptimizationTypeDecision(
  1272. type_decision);
  1273. base::UmaHistogramEnumeration(
  1274. "OptimizationGuide.ApplyDecisionAsync." +
  1275. GetStringNameForOptimizationType(opt_type),
  1276. type_decision);
  1277. std::move(callback).Run(decision, metadata);
  1278. }
  1279. }
  1280. registered_callbacks_.erase(navigation_url);
  1281. }
  1282. bool HintsManager::HasOptimizationTypeToFetchFor() {
  1283. if (registered_optimization_types_.empty())
  1284. return false;
  1285. for (const auto& optimization_type : registered_optimization_types_) {
  1286. if (optimization_types_with_filter_.find(optimization_type) ==
  1287. optimization_types_with_filter_.end()) {
  1288. return true;
  1289. }
  1290. }
  1291. return false;
  1292. }
  1293. bool HintsManager::IsAllowedToFetchNavigationHints(const GURL& url) {
  1294. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  1295. if (!HasOptimizationTypeToFetchFor())
  1296. return false;
  1297. if (!IsUserPermittedToFetchFromRemoteOptimizationGuide(is_off_the_record_,
  1298. pref_service_)) {
  1299. return false;
  1300. }
  1301. DCHECK(!is_off_the_record_);
  1302. return url.is_valid() && url.SchemeIsHTTPOrHTTPS();
  1303. }
  1304. void HintsManager::OnNavigationStartOrRedirect(
  1305. OptimizationGuideNavigationData* navigation_data,
  1306. base::OnceClosure callback) {
  1307. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  1308. if (registered_optimization_types_.empty()) {
  1309. // Do not attempt to load anything since we have nothing we need to get data
  1310. // for.
  1311. std::move(callback).Run();
  1312. return;
  1313. }
  1314. LoadHintForURL(navigation_data->navigation_url(), std::move(callback));
  1315. if (switches::DisableFetchingHintsAtNavigationStartForTesting()) {
  1316. return;
  1317. }
  1318. MaybeFetchHintsForNavigation(navigation_data);
  1319. }
  1320. void HintsManager::MaybeFetchHintsForNavigation(
  1321. OptimizationGuideNavigationData* navigation_data) {
  1322. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  1323. if (registered_optimization_types_.empty())
  1324. return;
  1325. const GURL url = navigation_data->navigation_url();
  1326. if (!IsAllowedToFetchNavigationHints(url))
  1327. return;
  1328. ScopedHintsManagerRaceNavigationHintsFetchAttemptRecorder
  1329. race_navigation_recorder(navigation_data);
  1330. // We expect that if the URL is being fetched for, we have already run through
  1331. // the logic to decide if we also require fetching hints for the host.
  1332. if (IsHintBeingFetchedForNavigation(url)) {
  1333. race_navigation_recorder.set_race_attempt_status(
  1334. RaceNavigationFetchAttemptStatus::
  1335. kRaceNavigationFetchAlreadyInProgress);
  1336. // Just set the hints fetch start to the start of the navigation, so we can
  1337. // track whether the hint came back before commit or not.
  1338. navigation_data->set_hints_fetch_start(navigation_data->navigation_start());
  1339. return;
  1340. }
  1341. std::vector<std::string> hosts;
  1342. std::vector<GURL> urls;
  1343. if (!hint_cache_->HasHint(url.host())) {
  1344. hosts.push_back(url.host());
  1345. race_navigation_recorder.set_race_attempt_status(
  1346. RaceNavigationFetchAttemptStatus::kRaceNavigationFetchHost);
  1347. }
  1348. if (!hint_cache_->HasURLKeyedEntryForURL(url)) {
  1349. urls.push_back(url);
  1350. race_navigation_recorder.set_race_attempt_status(
  1351. RaceNavigationFetchAttemptStatus::kRaceNavigationFetchURL);
  1352. }
  1353. if (hosts.empty() && urls.empty()) {
  1354. race_navigation_recorder.set_race_attempt_status(
  1355. RaceNavigationFetchAttemptStatus::kRaceNavigationFetchNotAttempted);
  1356. return;
  1357. }
  1358. DCHECK(hints_fetcher_factory_);
  1359. auto it = page_navigation_hints_fetchers_.Put(
  1360. url, hints_fetcher_factory_->BuildInstance(optimization_guide_logger_));
  1361. UMA_HISTOGRAM_COUNTS_100(
  1362. "OptimizationGuide.HintsManager.ConcurrentPageNavigationFetches",
  1363. page_navigation_hints_fetchers_.size());
  1364. MaybeLogGetHintRequestInfo(proto::CONTEXT_PAGE_NAVIGATION,
  1365. registered_optimization_types_, urls, hosts,
  1366. optimization_guide_logger_);
  1367. bool fetch_attempted = it->second->FetchOptimizationGuideServiceHints(
  1368. hosts, urls, registered_optimization_types_,
  1369. proto::CONTEXT_PAGE_NAVIGATION, application_locale_,
  1370. base::BindOnce(&HintsManager::OnPageNavigationHintsFetched,
  1371. weak_ptr_factory_.GetWeakPtr(),
  1372. navigation_data->GetWeakPtr(), url,
  1373. base::flat_set<GURL>(urls.begin(), urls.end()),
  1374. base::flat_set<std::string>(hosts.begin(), hosts.end())));
  1375. if (fetch_attempted) {
  1376. navigation_data->set_hints_fetch_start(base::TimeTicks::Now());
  1377. if (!hosts.empty() && !urls.empty()) {
  1378. race_navigation_recorder.set_race_attempt_status(
  1379. RaceNavigationFetchAttemptStatus::kRaceNavigationFetchHostAndURL);
  1380. }
  1381. } else {
  1382. race_navigation_recorder.set_race_attempt_status(
  1383. RaceNavigationFetchAttemptStatus::kRaceNavigationFetchNotAttempted);
  1384. }
  1385. }
  1386. void HintsManager::OnNavigationFinish(
  1387. const std::vector<GURL>& navigation_redirect_chain) {
  1388. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  1389. // The callbacks will be invoked when the fetch request comes back, so it
  1390. // will be cleaned up later.
  1391. for (const auto& url : navigation_redirect_chain) {
  1392. if (IsHintBeingFetchedForNavigation(url))
  1393. continue;
  1394. PrepareToInvokeRegisteredCallbacks(url);
  1395. }
  1396. }
  1397. void HintsManager::OnDeferredStartup() {
  1398. if (features::ShouldDeferStartupActiveTabsHintsFetch())
  1399. InitiateHintsFetchScheduling();
  1400. }
  1401. base::WeakPtr<OptimizationGuideStore> HintsManager::hint_store() {
  1402. return hint_cache_->hint_store();
  1403. }
  1404. HintCache* HintsManager::hint_cache() {
  1405. return hint_cache_.get();
  1406. }
  1407. PushNotificationManager* HintsManager::push_notification_manager() {
  1408. return push_notification_manager_.get();
  1409. }
  1410. HintsFetcherFactory* HintsManager::GetHintsFetcherFactory() {
  1411. return hints_fetcher_factory_.get();
  1412. }
  1413. bool HintsManager::HasAllInformationForDecisionAvailable(
  1414. const GURL& navigation_url,
  1415. proto::OptimizationType optimization_type) {
  1416. if (HasLoadedOptimizationAllowlist(optimization_type) ||
  1417. HasLoadedOptimizationBlocklist(optimization_type)) {
  1418. // If we have an optimization filter for the optimization type, it is
  1419. // consulted instead of any hints that may be available.
  1420. return true;
  1421. }
  1422. bool has_host_keyed_hint = hint_cache_->HasHint(navigation_url.host());
  1423. const auto* host_keyed_hint =
  1424. hint_cache_->GetHostKeyedHintIfLoaded(navigation_url.host());
  1425. if (has_host_keyed_hint && host_keyed_hint == nullptr) {
  1426. // If we have a host-keyed hint in the cache and it is not loaded, we do not
  1427. // have all information available, regardless of whether we can fetch hints
  1428. // or not.
  1429. return false;
  1430. }
  1431. if (!IsAllowedToFetchNavigationHints(navigation_url)) {
  1432. // If we are not allowed to fetch hints for the navigation, we have all
  1433. // information available if the host-keyed hint we have has been loaded
  1434. // already or we don't have a hint available.
  1435. return host_keyed_hint != nullptr || !has_host_keyed_hint;
  1436. }
  1437. if (IsHintBeingFetchedForNavigation(navigation_url)) {
  1438. // If a hint is being fetched for the navigation, then we do not have all
  1439. // information available yet.
  1440. return false;
  1441. }
  1442. // If we are allowed to fetch hints for the navigation, we only have all
  1443. // information available for certain if we have attempted to get the URL-keyed
  1444. // hint and if the host-keyed hint is loaded.
  1445. return hint_cache_->HasURLKeyedEntryForURL(navigation_url) &&
  1446. host_keyed_hint != nullptr;
  1447. }
  1448. void HintsManager::ClearFetchedHints() {
  1449. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  1450. hint_cache_->ClearFetchedHints();
  1451. HintsFetcher::ClearHostsSuccessfullyFetched(pref_service_);
  1452. }
  1453. void HintsManager::ClearHostKeyedHints() {
  1454. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  1455. hint_cache_->ClearHostKeyedHints();
  1456. HintsFetcher::ClearHostsSuccessfullyFetched(pref_service_);
  1457. }
  1458. void HintsManager::AddHintForTesting(
  1459. const GURL& url,
  1460. proto::OptimizationType optimization_type,
  1461. const absl::optional<OptimizationMetadata>& metadata) {
  1462. std::unique_ptr<proto::Hint> hint = std::make_unique<proto::Hint>();
  1463. hint->set_key(url.spec());
  1464. proto::PageHint* page_hint = hint->add_page_hints();
  1465. page_hint->set_page_pattern("*");
  1466. proto::Optimization* optimization =
  1467. page_hint->add_allowlisted_optimizations();
  1468. optimization->set_optimization_type(optimization_type);
  1469. if (!metadata) {
  1470. hint_cache_->AddHintForTesting(url, std::move(hint)); // IN-TEST
  1471. PrepareToInvokeRegisteredCallbacks(url);
  1472. return;
  1473. }
  1474. if (metadata->loading_predictor_metadata()) {
  1475. *optimization->mutable_loading_predictor_metadata() =
  1476. *metadata->loading_predictor_metadata();
  1477. } else if (metadata->performance_hints_metadata()) {
  1478. *optimization->mutable_performance_hints_metadata() =
  1479. *metadata->performance_hints_metadata();
  1480. } else if (metadata->public_image_metadata()) {
  1481. *optimization->mutable_public_image_metadata() =
  1482. *metadata->public_image_metadata();
  1483. } else if (metadata->any_metadata()) {
  1484. *optimization->mutable_any_metadata() = *metadata->any_metadata();
  1485. } else {
  1486. NOTREACHED();
  1487. }
  1488. hint_cache_->AddHintForTesting(url, std::move(hint)); // IN-TEST
  1489. PrepareToInvokeRegisteredCallbacks(url);
  1490. }
  1491. void HintsManager::RemoveFetchedEntriesByHintKeys(
  1492. base::OnceClosure on_success,
  1493. proto::KeyRepresentation key_representation,
  1494. const base::flat_set<std::string>& hint_keys) {
  1495. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  1496. // Make sure the key representation is something that we expect.
  1497. switch (key_representation) {
  1498. case proto::KeyRepresentation::HOST:
  1499. case proto::KeyRepresentation::FULL_URL:
  1500. break;
  1501. default:
  1502. NOTREACHED();
  1503. return;
  1504. }
  1505. if (key_representation == proto::FULL_URL) {
  1506. base::flat_set<GURL> urls_to_remove;
  1507. base::flat_set<std::string> hosts_to_remove;
  1508. // It is possible that the hints may have upgraded from being HOST keyed to
  1509. // URL keyed on the server at any time. To protect against this, also remove
  1510. // the host of the GURL from storage.
  1511. // However, note that the opposite is not likely to happen since URL-keyed
  1512. // hints are not persisted to disk.
  1513. for (const std::string& url : hint_keys) {
  1514. GURL gurl(url);
  1515. if (!gurl.is_valid()) {
  1516. continue;
  1517. }
  1518. hosts_to_remove.insert(gurl.host());
  1519. urls_to_remove.insert(gurl);
  1520. }
  1521. // Also clear the HintFetcher's host pref.
  1522. for (const std::string& host : hosts_to_remove) {
  1523. HintsFetcher::ClearSingleFetchedHost(pref_service_, host);
  1524. }
  1525. hint_cache_->RemoveHintsForURLs(urls_to_remove);
  1526. hint_cache_->RemoveHintsForHosts(std::move(on_success), hosts_to_remove);
  1527. return;
  1528. }
  1529. // Also clear the HintFetcher's host pref.
  1530. for (const std::string& host : hint_keys) {
  1531. HintsFetcher::ClearSingleFetchedHost(pref_service_, host);
  1532. }
  1533. hint_cache_->RemoveHintsForHosts(std::move(on_success), hint_keys);
  1534. }
  1535. } // namespace optimization_guide