| // Copyright 2018 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/previews/content/previews_hints.h" |
| |
| #include <array> |
| #include <string> |
| |
| #include "base/command_line.h" |
| #include "base/files/file.h" |
| #include "base/files/file_util.h" |
| #include "base/metrics/field_trial_params.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/optional.h" |
| #include "base/strings/stringprintf.h" |
| #include "components/optimization_guide/hints_component_info.h" |
| #include "components/optimization_guide/hints_component_util.h" |
| #include "components/optimization_guide/url_pattern_with_wildcards.h" |
| #include "components/previews/core/bloom_filter.h" |
| #include "components/previews/core/previews_features.h" |
| #include "components/previews/core/previews_switches.h" |
| #include "url/gurl.h" |
| |
| namespace previews { |
| |
| namespace { |
| |
| // Name of sentinel file to guard potential crash loops while processing |
| // the config into hints. It holds the version of the config that is/was |
| // being processed into hints. |
| const base::FilePath::CharType kSentinelFileName[] = |
| FILE_PATH_LITERAL("previews_config_sentinel.txt"); |
| |
| // Creates the sentinel file (at |sentinel_path|) to persistently mark the |
| // beginning of processing the configuration data for Previews hints. It |
| // records the configuration version in the file. Returns true when the |
| // sentinel file is successfully created and processing should continue. |
| // Returns false if the processing should not continue because the |
| // file exists with the same version (indicating that processing that version |
| // failed previously (possibly crash or shutdown). Should be run in the |
| // background (e.g., same task as PreviewsHints::CreateFromHintsComponent()). |
| bool CreateSentinelFile(const base::FilePath& sentinel_path, |
| const base::Version& version) { |
| DCHECK(version.IsValid()); |
| |
| if (base::PathExists(sentinel_path)) { |
| // Processing apparently did not complete previously, check its version. |
| std::string content; |
| if (!base::ReadFileToString(sentinel_path, &content)) { |
| DLOG(WARNING) << "Error reading previews config sentinel file"; |
| // Attempt to delete sentinel for fresh start next time. |
| base::DeleteFile(sentinel_path, false /* recursive */); |
| return false; |
| } |
| base::Version previous_attempted_version(content); |
| if (!previous_attempted_version.IsValid()) { |
| DLOG(ERROR) << "Bad contents in previews config sentinel file"; |
| // Attempt to delete sentinel for fresh start next time. |
| base::DeleteFile(sentinel_path, false /* recursive */); |
| return false; |
| } |
| if (previous_attempted_version.CompareTo(version) == 0) { |
| // Previously attempted same version without completion. |
| return false; |
| } |
| } |
| |
| // Write config version in the sentinel file. |
| std::string new_sentinel_value = version.GetString(); |
| if (base::WriteFile(sentinel_path, new_sentinel_value.data(), |
| new_sentinel_value.length()) <= 0) { |
| DLOG(ERROR) << "Failed to create sentinel file " << sentinel_path; |
| return false; |
| } |
| return true; |
| } |
| |
| // Deletes the sentinel file. This should be done once processing the |
| // configuration is complete and should be done in the background (e.g., |
| // same task as Hints.CreateFromConfig). |
| void DeleteSentinelFile(const base::FilePath& sentinel_path) { |
| if (!base::DeleteFile(sentinel_path, false /* recursive */)) |
| DLOG(ERROR) << "Error deleting sentinel file"; |
| } |
| |
| // Enumerates the possible outcomes of processing previews hints. Used in UMA |
| // histograms, so the order of enumerators should not be changed. |
| // |
| // Keep in sync with PreviewsProcessHintsResult in |
| // tools/metrics/histograms/enums.xml. |
| enum class PreviewsProcessHintsResult { |
| kProcessedNoPreviewsHints = 0, |
| kProcessedPreviewsHints = 1, |
| kFailedFinishProcessing = 2, |
| kMaxValue = kFailedFinishProcessing |
| }; |
| |
| // Enumerates status event of processing optimization filters (such as the |
| // lite page redirect blacklist). Used in UMA histograms, so the order of |
| // enumerators should not be changed. |
| // |
| // Keep in sync with PreviewsOptimizationFilterStatus in |
| // tools/metrics/histograms/enums.xml. |
| enum class PreviewsOptimizationFilterStatus { |
| kFoundServerBlacklistConfig = 0, |
| kCreatedServerBlacklist = 1, |
| kFailedServerBlacklistBadConfig = 2, |
| kFailedServerBlacklistTooBig = 3, |
| kFailedServerBlacklistDuplicateConfig = 4, |
| kMaxValue = kFailedServerBlacklistDuplicateConfig |
| }; |
| |
| // Returns base::nullopt if |optimization_type| can't be converted. |
| base::Optional<PreviewsType> ConvertProtoOptimizationTypeToPreviewsType( |
| optimization_guide::proto::OptimizationType optimization_type) { |
| switch (optimization_type) { |
| case optimization_guide::proto::TYPE_UNSPECIFIED: |
| return base::nullopt; |
| case optimization_guide::proto::NOSCRIPT: |
| return PreviewsType::NOSCRIPT; |
| case optimization_guide::proto::RESOURCE_LOADING: |
| return PreviewsType::RESOURCE_LOADING_HINTS; |
| case optimization_guide::proto::LITE_PAGE_REDIRECT: |
| return PreviewsType::LITE_PAGE_REDIRECT; |
| } |
| } |
| |
| net::EffectiveConnectionType ConvertProtoEffectiveConnectionType( |
| optimization_guide::proto::EffectiveConnectionType proto_ect) { |
| switch (proto_ect) { |
| case optimization_guide::proto::EffectiveConnectionType:: |
| EFFECTIVE_CONNECTION_TYPE_UNKNOWN: |
| return net::EffectiveConnectionType::EFFECTIVE_CONNECTION_TYPE_UNKNOWN; |
| case optimization_guide::proto::EffectiveConnectionType:: |
| EFFECTIVE_CONNECTION_TYPE_OFFLINE: |
| return net::EffectiveConnectionType::EFFECTIVE_CONNECTION_TYPE_OFFLINE; |
| case optimization_guide::proto::EffectiveConnectionType:: |
| EFFECTIVE_CONNECTION_TYPE_SLOW_2G: |
| return net::EffectiveConnectionType::EFFECTIVE_CONNECTION_TYPE_SLOW_2G; |
| case optimization_guide::proto::EffectiveConnectionType:: |
| EFFECTIVE_CONNECTION_TYPE_2G: |
| return net::EffectiveConnectionType::EFFECTIVE_CONNECTION_TYPE_2G; |
| case optimization_guide::proto::EffectiveConnectionType:: |
| EFFECTIVE_CONNECTION_TYPE_3G: |
| return net::EffectiveConnectionType::EFFECTIVE_CONNECTION_TYPE_3G; |
| case optimization_guide::proto::EffectiveConnectionType:: |
| EFFECTIVE_CONNECTION_TYPE_4G: |
| return net::EffectiveConnectionType::EFFECTIVE_CONNECTION_TYPE_4G; |
| } |
| } |
| |
| bool IsDisabledExperimentalOptimization( |
| const optimization_guide::proto::Optimization& optimization) { |
| // If this optimization has been marked with an experiment name, consider it |
| // disabled unless an experiment with that name is running. Experiment names |
| // are configured with the experiment_name parameter to the |
| // kOptimizationHintsExperiments feature. |
| // |
| // If kOptimizationHintsExperiments is disabled, getting the param value |
| // returns an empty string. Since experiment names are not allowed to be |
| // empty strings, all experiments will be disabled if the feature is |
| // disabled. |
| if (optimization.has_experiment_name() && |
| !optimization.experiment_name().empty() && |
| optimization.experiment_name() != |
| base::GetFieldTrialParamValueByFeature( |
| features::kOptimizationHintsExperiments, |
| features::kOptimizationHintsExperimentNameParam)) { |
| return true; |
| } |
| return false; |
| } |
| |
| // If |hint| contains page hints, then this function adds a pared down version |
| // of the hint to |stripped_hints_with_page_hints|, removing all of the hint's |
| // top-level optimizations and only retaining the first enabled optimization of |
| // each preview type within each page hint. |
| // |total_page_patterns_with_resource_loading_hints_received| and |
| // |total_resource_loading_hints_received| have their totals updated as |
| // resource loading hints are encountered. |
| void MaybeAddHintToStrippedHintsToHintCacheData( |
| const optimization_guide::proto::Hint& hint, |
| HintCache::Data* hint_cache_data, |
| size_t* total_page_patterns_with_resource_loading_hints_received, |
| size_t* total_resource_loading_hints_received) { |
| DCHECK(hint_cache_data); |
| DCHECK(total_page_patterns_with_resource_loading_hints_received); |
| DCHECK(total_resource_loading_hints_received); |
| |
| if (hint.page_hints().empty()) { |
| return; |
| } |
| |
| if (previews::params::IsResourceLoadingHintsEnabled()) { |
| UMA_HISTOGRAM_COUNTS("ResourceLoadingHints.PageHints.ProcessedCount", |
| hint.page_hints().size()); |
| } |
| |
| // |stripped_hint| is a copy of |hint| with top-level optimizations and |
| // disabled experimental optimizations within the page hints stripped out. |
| optimization_guide::proto::Hint stripped_hint; |
| |
| for (const auto& page_hint : hint.page_hints()) { |
| // Track the preview types encountered for |page_hint|. Only the first |
| // supported one will be kept for any one preview type. |
| std::array<bool, static_cast<int>(PreviewsType::LAST)> |
| encountered_preview_types; |
| encountered_preview_types.fill(false); |
| |
| // Initially set the added page hint to nullptr. This will be set the first |
| // time an enabled optimization is encountered. |
| optimization_guide::proto::PageHint* added_page_hint = nullptr; |
| |
| for (const auto& optimization : page_hint.whitelisted_optimizations()) { |
| if (IsDisabledExperimentalOptimization(optimization)) { |
| continue; |
| } |
| |
| base::Optional<PreviewsType> previews_type = |
| ConvertProtoOptimizationTypeToPreviewsType( |
| optimization.optimization_type()); |
| |
| // Only add the first encountered optimization of a previews type. |
| if (!previews_type || |
| encountered_preview_types[static_cast<int>(*previews_type)]) { |
| continue; |
| } |
| encountered_preview_types[static_cast<int>(*previews_type)] = true; |
| |
| // If this is a resource loading hints optimization, then add it to the |
| // resource loading hints totals. |
| if (previews_type == PreviewsType::RESOURCE_LOADING_HINTS) { |
| // Always skip over resource loading hints when they're disabled. |
| if (!previews::params::IsResourceLoadingHintsEnabled()) { |
| continue; |
| } |
| |
| (*total_page_patterns_with_resource_loading_hints_received)++; |
| (*total_resource_loading_hints_received) += |
| optimization.resource_loading_hints_size(); |
| |
| // If the total page patterns with resource loading hints has reached |
| // the cap, then no additional resource loading hints optimizations can |
| // be added to page hints. |
| if (*total_page_patterns_with_resource_loading_hints_received > |
| previews::params::GetMaxPageHintsInMemoryThreshhold()) { |
| continue; |
| } |
| } else { |
| DCHECK_EQ(optimization.resource_loading_hints_size(), 0); |
| } |
| |
| // If this page hint hasn't been added to the stripped hint yet, then add |
| // it now and populate its non-whitelisted optimization fields. |
| if (!added_page_hint) { |
| added_page_hint = stripped_hint.add_page_hints(); |
| added_page_hint->set_page_pattern(page_hint.page_pattern()); |
| if (page_hint.has_max_ect_trigger()) { |
| added_page_hint->set_max_ect_trigger(page_hint.max_ect_trigger()); |
| } |
| } |
| auto* added_optimization = |
| added_page_hint->add_whitelisted_optimizations(); |
| *added_optimization = optimization; |
| } |
| } |
| |
| if (stripped_hint.page_hints_size() > 0) { |
| stripped_hint.set_key_representation(hint.key_representation()); |
| stripped_hint.set_key(hint.key()); |
| hint_cache_data->AddHint(stripped_hint); |
| } |
| } |
| |
| void RecordProcessHintsResult(PreviewsProcessHintsResult result) { |
| base::UmaHistogramEnumeration("Previews.ProcessHintsResult", result); |
| } |
| |
| void RecordOptimizationFilterStatus(PreviewsType previews_type, |
| PreviewsOptimizationFilterStatus status) { |
| std::string histogram_name = |
| base::StringPrintf("Previews.OptimizationFilterStatus.%s", |
| GetStringNameForType(previews_type).c_str()); |
| base::UmaHistogramEnumeration(histogram_name, status); |
| } |
| |
| } // namespace |
| |
| PreviewsHints::PreviewsHints() { |
| DETACH_FROM_SEQUENCE(sequence_checker_); |
| } |
| |
| PreviewsHints::~PreviewsHints() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| } |
| |
| // static |
| std::unique_ptr<PreviewsHints> PreviewsHints::CreateFromHintsComponent( |
| const optimization_guide::HintsComponentInfo& info) { |
| std::unique_ptr<optimization_guide::proto::Configuration> config = |
| ProcessHintsComponent(info); |
| if (!config) { |
| return nullptr; |
| } |
| |
| base::FilePath sentinel_path(info.path.DirName().Append(kSentinelFileName)); |
| if (!CreateSentinelFile(sentinel_path, info.version)) { |
| RecordProcessHintsResult( |
| PreviewsProcessHintsResult::kFailedFinishProcessing); |
| return nullptr; |
| } |
| |
| std::unique_ptr<PreviewsHints> hints(new PreviewsHints()); |
| HintCache::Data hint_cache_data; |
| |
| // The condition set ID is a simple increasing counter that matches the |
| // order of hints in the config (where earlier hints in the config take |
| // precedence over later hints in the config if there are multiple matches). |
| url_matcher::URLMatcherConditionSet::ID id = 0; |
| url_matcher::URLMatcherConditionFactory* condition_factory = |
| hints->url_matcher_.condition_factory(); |
| url_matcher::URLMatcherConditionSet::Vector all_conditions; |
| std::set<std::string> seen_host_suffixes; |
| |
| size_t total_page_patterns_with_resource_loading_hints_received = 0; |
| size_t total_resource_loading_hints_received = 0; |
| // Process hint configuration. |
| for (const auto& hint : config->hints()) { |
| // We only support host suffixes at the moment. Skip anything else. |
| // One |hint| applies to one host URL suffix. |
| if (hint.key_representation() != optimization_guide::proto::HOST_SUFFIX) { |
| continue; |
| } |
| |
| const std::string& hint_key = hint.key(); |
| |
| // Validate configuration keys. |
| DCHECK(!hint_key.empty()); |
| if (hint_key.empty()) { |
| continue; |
| } |
| |
| auto seen_host_suffixes_iter = seen_host_suffixes.find(hint_key); |
| DCHECK(seen_host_suffixes_iter == seen_host_suffixes.end()); |
| if (seen_host_suffixes_iter != seen_host_suffixes.end()) { |
| DLOG(WARNING) << "Received config with duplicate key"; |
| continue; |
| } |
| seen_host_suffixes.insert(hint_key); |
| |
| // Only process legacy top level hints if specifically enabled. |
| if (previews::params::NoScriptPreviewsUsesTopLevelHints()) { |
| // Create whitelist condition set out of the optimizations that are |
| // whitelisted for the host suffix at the top level (i.e., not within |
| // PageHints). |
| std::set<std::pair<PreviewsType, int>> whitelisted_optimizations; |
| for (const auto& optimization : hint.whitelisted_optimizations()) { |
| if (IsDisabledExperimentalOptimization(optimization)) { |
| continue; |
| } |
| base::Optional<PreviewsType> previews_type = |
| ConvertProtoOptimizationTypeToPreviewsType( |
| optimization.optimization_type()); |
| if (!previews_type.has_value()) { |
| continue; |
| } |
| // Resource loading hints should always be page hints; if they appear as |
| // top-level whitelisted optimizations, then it indicates a bug. |
| DCHECK(previews_type != PreviewsType::RESOURCE_LOADING_HINTS); |
| |
| whitelisted_optimizations.insert(std::make_pair( |
| previews_type.value(), optimization.inflation_percent())); |
| } |
| |
| url_matcher::URLMatcherCondition condition = |
| condition_factory->CreateHostSuffixCondition(hint_key); |
| all_conditions.push_back(new url_matcher::URLMatcherConditionSet( |
| id, std::set<url_matcher::URLMatcherCondition>{condition})); |
| hints->whitelist_[id] = whitelisted_optimizations; |
| id++; |
| } |
| |
| // If this hint contains page hints, then add a pared down version of the |
| // hint to the initial hints that are used to populate the hint cache, |
| // removing all of the hint's top-level optimizations and only retaining the |
| // first enabled optimization of each preview type within each page hint. |
| MaybeAddHintToStrippedHintsToHintCacheData( |
| hint, &hint_cache_data, |
| &total_page_patterns_with_resource_loading_hints_received, |
| &total_resource_loading_hints_received); |
| } |
| |
| if (previews::params::IsResourceLoadingHintsEnabled()) { |
| UMA_HISTOGRAM_COUNTS_1000( |
| "ResourceLoadingHints.PageHints.TotalReceived", |
| total_page_patterns_with_resource_loading_hints_received); |
| UMA_HISTOGRAM_COUNTS_100000( |
| "ResourceLoadingHints.ResourceHints.TotalReceived", |
| total_resource_loading_hints_received); |
| } |
| |
| if (!all_conditions.empty()) { |
| hints->url_matcher_.AddConditionSets(all_conditions); |
| } |
| |
| if (hint_cache_data.HasHints()) { |
| hints->hint_cache_ = |
| std::make_unique<HintCache>(std::move(hint_cache_data)); |
| } |
| |
| // Extract any supported large scale blacklists from the configuration. |
| hints->ParseOptimizationFilters(*config); |
| |
| // Completed processing hints data without crashing so clear sentinel. |
| DeleteSentinelFile(sentinel_path); |
| RecordProcessHintsResult( |
| all_conditions.empty() && !hints->hint_cache_ |
| ? PreviewsProcessHintsResult::kProcessedNoPreviewsHints |
| : PreviewsProcessHintsResult::kProcessedPreviewsHints); |
| return hints; |
| } |
| |
| void PreviewsHints::ParseOptimizationFilters( |
| const optimization_guide::proto::Configuration& config) { |
| for (const auto blacklist : config.optimization_blacklists()) { |
| base::Optional<PreviewsType> previews_type = |
| ConvertProtoOptimizationTypeToPreviewsType( |
| blacklist.optimization_type()); |
| if (previews_type == PreviewsType::LITE_PAGE_REDIRECT && |
| previews::params::IsLitePageServerPreviewsEnabled() && |
| blacklist.has_bloom_filter()) { |
| RecordOptimizationFilterStatus( |
| previews_type.value(), |
| PreviewsOptimizationFilterStatus::kFoundServerBlacklistConfig); |
| if (lite_page_redirect_blacklist_) { |
| DLOG(WARNING) |
| << "Found multiple blacklist configs for LITE_PAGE_REDIRECT"; |
| RecordOptimizationFilterStatus( |
| previews_type.value(), PreviewsOptimizationFilterStatus:: |
| kFailedServerBlacklistDuplicateConfig); |
| continue; |
| } |
| auto bloom_filter_proto = blacklist.bloom_filter(); |
| DCHECK_GT(bloom_filter_proto.num_hash_functions(), 0u); |
| DCHECK_GT(bloom_filter_proto.num_bits(), 0u); |
| DCHECK(bloom_filter_proto.has_data()); |
| if (!bloom_filter_proto.has_data() || |
| bloom_filter_proto.num_bits() <= 0 || |
| bloom_filter_proto.num_bits() > |
| bloom_filter_proto.data().size() * 8) { |
| DLOG(ERROR) << "Bloom filter config issue"; |
| RecordOptimizationFilterStatus( |
| previews_type.value(), |
| PreviewsOptimizationFilterStatus::kFailedServerBlacklistBadConfig); |
| continue; |
| } |
| if (static_cast<int>(bloom_filter_proto.num_bits()) > |
| previews::params:: |
| LitePageRedirectPreviewMaxServerBlacklistByteSize() / |
| 8) { |
| DLOG(ERROR) << "Bloom filter data exceeds maximum size of " |
| << previews::params::PreviewServerLoadshedMaxSeconds() |
| << " bytes"; |
| RecordOptimizationFilterStatus( |
| previews_type.value(), |
| PreviewsOptimizationFilterStatus::kFailedServerBlacklistTooBig); |
| continue; |
| } |
| std::unique_ptr<BloomFilter> bloom_filter = std::make_unique<BloomFilter>( |
| bloom_filter_proto.num_hash_functions(), |
| bloom_filter_proto.num_bits(), bloom_filter_proto.data()); |
| lite_page_redirect_blacklist_ = |
| std::make_unique<HostFilter>(std::move(bloom_filter)); |
| RecordOptimizationFilterStatus( |
| previews_type.value(), |
| PreviewsOptimizationFilterStatus::kCreatedServerBlacklist); |
| } |
| } |
| } |
| |
| // static |
| const optimization_guide::proto::PageHint* PreviewsHints::FindPageHint( |
| const GURL& document_url, |
| const optimization_guide::proto::Hint& hint) { |
| if (hint.page_hints_size() == 0) |
| return nullptr; |
| std::string url = document_url.spec(); |
| for (const auto& page_hint : hint.page_hints()) { |
| if (page_hint.page_pattern().empty()) |
| continue; |
| optimization_guide::URLPatternWithWildcards url_pattern( |
| page_hint.page_pattern()); |
| if (url_pattern.Matches(url)) { |
| // Return the first matching page hint. |
| return &page_hint; |
| } |
| } |
| return nullptr; |
| } |
| |
| bool PreviewsHints::IsWhitelisted( |
| const GURL& url, |
| PreviewsType type, |
| int* out_inflation_percent, |
| net::EffectiveConnectionType* out_ect_threshold) const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (!url.has_host()) { |
| return false; |
| } |
| |
| return IsWhitelistedAtTopLevel(url, type, out_inflation_percent) || |
| IsWhitelistedInPageHints(url, type, out_inflation_percent, |
| out_ect_threshold); |
| } |
| |
| bool PreviewsHints::IsWhitelistedAtTopLevel(const GURL& url, |
| PreviewsType type, |
| int* out_inflation_percent) const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| // Top level hints are deprecated so only check here if specifically enabled |
| // for NoScript. |
| if (!previews::params::NoScriptPreviewsUsesTopLevelHints()) { |
| return false; |
| } |
| |
| // Resource loading hints are not processed in the top-level whitelist. |
| if (type == PreviewsType::RESOURCE_LOADING_HINTS) { |
| return false; |
| } |
| |
| std::set<url_matcher::URLMatcherConditionSet::ID> matches = |
| url_matcher_.MatchURL(url); |
| |
| // Only consider the first match in iteration order as it takes precedence |
| // if there are multiple matches for the top-level whitelist. |
| const auto& first_match = matches.begin(); |
| if (first_match != matches.end()) { |
| const auto whitelist_iter = whitelist_.find(*first_match); |
| if (whitelist_iter != whitelist_.end()) { |
| const auto& whitelisted_optimizations = whitelist_iter->second; |
| for (auto optimization_iter = whitelisted_optimizations.begin(); |
| optimization_iter != whitelisted_optimizations.end(); |
| ++optimization_iter) { |
| if (optimization_iter->first == type) { |
| *out_inflation_percent = optimization_iter->second; |
| // Whitelisted on top level whitelist. |
| return true; |
| } |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| bool PreviewsHints::IsWhitelistedInPageHints( |
| const GURL& url, |
| PreviewsType type, |
| int* out_inflation_percent, |
| net::EffectiveConnectionType* out_ect_threshold) const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (!hint_cache_) { |
| return false; |
| } |
| |
| // Now check HintCache for a loaded entry with a PageHint that matches |url| |
| // that whitelists the optimization. |
| std::string host = url.host(); |
| if (!hint_cache_->IsHintLoaded(host)) { |
| // TODO(dougarnett): Add UMA histogram counts for both cases of HasHint(). |
| return false; |
| } |
| |
| const optimization_guide::proto::Hint* hint = hint_cache_->GetHint(host); |
| const optimization_guide::proto::PageHint* matched_page_hint = |
| FindPageHint(url, *hint); |
| if (!matched_page_hint) { |
| return false; |
| } |
| |
| for (const auto& optimization : |
| matched_page_hint->whitelisted_optimizations()) { |
| if (ConvertProtoOptimizationTypeToPreviewsType( |
| optimization.optimization_type()) == type) { |
| // TODO(jegray): When persistence is added for hints, address handling of |
| // disabled experimental optimizations. |
| // Found whitelisted optimization. |
| *out_inflation_percent = optimization.inflation_percent(); |
| if (matched_page_hint->has_max_ect_trigger()) { |
| *out_ect_threshold = ConvertProtoEffectiveConnectionType( |
| matched_page_hint->max_ect_trigger()); |
| } |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| bool PreviewsHints::IsBlacklisted(const GURL& url, PreviewsType type) const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (!url.has_host()) { |
| return false; |
| } |
| |
| // Check large scale blacklists received from the server. |
| // (At some point, we may have blacklisting to check in HintCache as well.) |
| if (type == PreviewsType::LITE_PAGE_REDIRECT) { |
| if (base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kIgnoreLitePageRedirectOptimizationBlacklist)) { |
| return false; |
| } |
| |
| if (lite_page_redirect_blacklist_) |
| return lite_page_redirect_blacklist_->ContainsHostSuffix(url); |
| } |
| |
| return false; |
| } |
| |
| bool PreviewsHints::MaybeLoadOptimizationHints( |
| const GURL& url, |
| HintLoadedCallback callback) const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (!hint_cache_ || !url.has_host()) { |
| return false; |
| } |
| |
| hint_cache_->LoadHint(url.host(), std::move(callback)); |
| return hint_cache_->HasHint(url.host()); |
| } |
| |
| void PreviewsHints::LogHintCacheMatch(const GURL& url, |
| bool is_committed, |
| net::EffectiveConnectionType ect) const { |
| if (!hint_cache_) { |
| return; |
| } |
| |
| if (hint_cache_->HasHint(url.host())) { |
| if (!is_committed) { |
| UMA_HISTOGRAM_ENUMERATION( |
| "Previews.OptimizationGuide.HintCache.HasHint.BeforeCommit", ect, |
| net::EffectiveConnectionType::EFFECTIVE_CONNECTION_TYPE_LAST); |
| } else { |
| UMA_HISTOGRAM_ENUMERATION( |
| "Previews.OptimizationGuide.HintCache.HasHint.AtCommit", ect, |
| net::EffectiveConnectionType::EFFECTIVE_CONNECTION_TYPE_LAST); |
| if (hint_cache_->IsHintLoaded(url.host())) { |
| UMA_HISTOGRAM_ENUMERATION( |
| "Previews.OptimizationGuide.HintCache.HostMatch.AtCommit", ect, |
| net::EffectiveConnectionType::EFFECTIVE_CONNECTION_TYPE_LAST); |
| const optimization_guide::proto::Hint* hint = |
| hint_cache_->GetHint(url.host()); |
| if (FindPageHint(url, *hint) != nullptr) { |
| UMA_HISTOGRAM_ENUMERATION( |
| "Previews.OptimizationGuide.HintCache.PageMatch.AtCommit", ect, |
| net::EffectiveConnectionType::EFFECTIVE_CONNECTION_TYPE_LAST); |
| } |
| } |
| } |
| } |
| } |
| |
| } // namespace previews |