| // Copyright 2014 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/omnibox/browser/omnibox_field_trial.h" |
| |
| #include <algorithm> |
| #include <cmath> |
| #include <functional> |
| #include <string> |
| |
| #include "base/command_line.h" |
| #include "base/feature_list.h" |
| #include "base/metrics/field_trial.h" |
| #include "base/metrics/field_trial_params.h" |
| #include "base/no_destructor.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_split.h" |
| #include "base/strings/string_util.h" |
| #include "base/system/sys_info.h" |
| #include "base/time/time.h" |
| #include "base/trace_event/memory_usage_estimator.h" |
| #include "build/build_config.h" |
| #include "components/history/core/browser/url_database.h" |
| #include "components/omnibox/browser/aim_eligibility_service.h" |
| #include "components/omnibox/browser/autocomplete_provider.h" |
| #include "components/omnibox/browser/autocomplete_provider_client.h" |
| #include "components/omnibox/browser/page_classification_functions.h" |
| #include "components/omnibox/browser/url_index_private_data.h" |
| #include "components/omnibox/common/omnibox_features.h" |
| #include "components/optimization_guide/machine_learning_tflite_buildflags.h" |
| #include "components/search/search.h" |
| #include "components/variations/active_field_trials.h" |
| #include "components/variations/hashing.h" |
| #include "components/variations/variations_associated_data.h" |
| #include "third_party/metrics_proto/omnibox_event.pb.h" |
| #include "ui/base/device_form_factor.h" |
| #include "ui/base/ui_base_features.h" |
| |
| using metrics::OmniboxEventProto; |
| |
| namespace { |
| |
| typedef std::map<std::string, std::string> VariationParams; |
| typedef HUPScoringParams::ScoreBuckets ScoreBuckets; |
| |
| void InitializeBucketsFromString(const std::string& bucket_string, |
| ScoreBuckets* score_buckets) { |
| // Clear the buckets. |
| score_buckets->buckets().clear(); |
| base::StringPairs kv_pairs; |
| if (base::SplitStringIntoKeyValuePairs(bucket_string, ':', ',', &kv_pairs)) { |
| for (base::StringPairs::const_iterator it = kv_pairs.begin(); |
| it != kv_pairs.end(); ++it) { |
| ScoreBuckets::CountMaxRelevance bucket; |
| base::StringToDouble(it->first, &bucket.first); |
| base::StringToInt(it->second, &bucket.second); |
| score_buckets->buckets().push_back(bucket); |
| } |
| std::sort(score_buckets->buckets().begin(), score_buckets->buckets().end(), |
| std::greater<ScoreBuckets::CountMaxRelevance>()); |
| } |
| } |
| |
| void InitializeScoreBuckets(const VariationParams& params, |
| const char* relevance_cap_param, |
| const char* half_life_param, |
| const char* score_buckets_param, |
| const char* use_decay_factor_param, |
| ScoreBuckets* score_buckets) { |
| auto it = params.find(relevance_cap_param); |
| if (it != params.end()) { |
| int relevance_cap; |
| if (base::StringToInt(it->second, &relevance_cap)) { |
| score_buckets->set_relevance_cap(relevance_cap); |
| } |
| } |
| |
| it = params.find(use_decay_factor_param); |
| if (it != params.end()) { |
| int use_decay_factor; |
| if (base::StringToInt(it->second, &use_decay_factor)) { |
| score_buckets->set_use_decay_factor(use_decay_factor != 0); |
| } |
| } |
| |
| it = params.find(half_life_param); |
| if (it != params.end()) { |
| int half_life_days; |
| if (base::StringToInt(it->second, &half_life_days)) { |
| score_buckets->set_half_life_days(half_life_days); |
| } |
| } |
| |
| it = params.find(score_buckets_param); |
| if (it != params.end()) { |
| // The value of the score bucket is a comma-separated list of |
| // {DecayedCount/DecayedFactor + ":" + MaxRelevance}. |
| InitializeBucketsFromString(it->second, score_buckets); |
| } |
| } |
| |
| OmniboxFieldTrial::MLConfig& GetMLConfigInternal() { |
| static base::NoDestructor<OmniboxFieldTrial::MLConfig> s_config; |
| return *s_config; |
| } |
| |
| bool IsKoreanLocale(const std::string& locale) { |
| return locale == "ko" || locale == "ko-KR"; |
| } |
| |
| #if !BUILDFLAG(IS_IOS) |
| bool IsEnglishLocale(const std::string& locale) { |
| return base::StartsWith(locale, "en", base::CompareCase::SENSITIVE); |
| } |
| #endif // !BUILDFLAG(IS_IOS) |
| |
| } // namespace |
| |
| HUPScoringParams::ScoreBuckets::ScoreBuckets() |
| : relevance_cap_(-1), half_life_days_(-1), use_decay_factor_(false) {} |
| |
| HUPScoringParams::ScoreBuckets::ScoreBuckets(const ScoreBuckets& other) = |
| default; |
| |
| HUPScoringParams::ScoreBuckets::~ScoreBuckets() = default; |
| |
| size_t HUPScoringParams::ScoreBuckets::EstimateMemoryUsage() const { |
| return base::trace_event::EstimateMemoryUsage(buckets_); |
| } |
| |
| double HUPScoringParams::ScoreBuckets::HalfLifeTimeDecay( |
| const base::TimeDelta& elapsed_time) const { |
| double time_ms; |
| if ((half_life_days_ <= 0) || |
| ((time_ms = elapsed_time.InMillisecondsF()) <= 0)) { |
| return 1.0; |
| } |
| |
| const double half_life_intervals = |
| time_ms / base::Days(half_life_days_).InMillisecondsF(); |
| return pow(2.0, -half_life_intervals); |
| } |
| |
| size_t HUPScoringParams::EstimateMemoryUsage() const { |
| size_t res = 0; |
| |
| res += base::trace_event::EstimateMemoryUsage(typed_count_buckets); |
| res += base::trace_event::EstimateMemoryUsage(visited_count_buckets); |
| |
| return res; |
| } |
| |
| int OmniboxFieldTrial::GetDisabledProviderTypes() { |
| const std::string& types_string = base::GetFieldTrialParamValue( |
| kBundledExperimentFieldTrialName, kDisableProvidersRule); |
| int types = 0; |
| if (types_string.empty() || !base::StringToInt(types_string, &types)) { |
| return 0; |
| } |
| return types; |
| } |
| |
| void OmniboxFieldTrial::GetActiveSuggestFieldTrialHashes( |
| std::vector<uint32_t>* field_trial_hashes) { |
| field_trial_hashes->clear(); |
| if (base::FieldTrialList::TrialExists(kBundledExperimentFieldTrialName)) { |
| field_trial_hashes->push_back( |
| variations::HashName(kBundledExperimentFieldTrialName)); |
| } |
| } |
| |
| size_t OmniboxFieldTrial::GetProviderMaxMatches( |
| AutocompleteProvider::Type provider) { |
| size_t default_max_matches_per_provider = 3; |
| |
| std::string param_value; |
| if (OmniboxFieldTrial::IsMlUrlScoringEnabled()) { |
| param_value = |
| OmniboxFieldTrial::GetMLConfig().ml_url_scoring_max_matches_by_provider; |
| } else { |
| param_value = base::GetFieldTrialParamValueByFeature( |
| omnibox::kUIExperimentMaxAutocompleteMatches, |
| OmniboxFieldTrial::kUIMaxAutocompleteMatchesByProviderParam); |
| } |
| |
| // If the experiment param specifies a max results for |provider|, return the |
| // specified limit. |
| // E.g., if param_value = '3:2' and provider = 3, return 2. |
| // Otherwise, if the experiment param specifies a default value for |
| // unspecified providers, return the default value. |
| // E.g., if param_value = '3:3,*:4' and provider = 1, return 4, |
| // Otherwise, return |default_max_matches_per_provider|. |
| base::StringPairs kv_pairs; |
| if (base::SplitStringIntoKeyValuePairs(param_value, ':', ',', &kv_pairs)) { |
| for (const auto& kv_pair : kv_pairs) { |
| int k; |
| base::StringToInt(kv_pair.first, &k); |
| size_t v; |
| base::StringToSizeT(kv_pair.second, &v); |
| |
| if (kv_pair.first == "*") { |
| default_max_matches_per_provider = v; |
| } else if (k == provider) { |
| return v; |
| } |
| } |
| } |
| |
| return default_max_matches_per_provider; |
| } |
| |
| bool OmniboxFieldTrial::IsMaxURLMatchesFeatureEnabled() { |
| return base::FeatureList::IsEnabled(omnibox::kOmniboxMaxURLMatches); |
| } |
| |
| size_t OmniboxFieldTrial::GetMaxURLMatches() { |
| #if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS) |
| constexpr size_t kDefaultMaxURLMatches = 5; |
| #else |
| constexpr size_t kDefaultMaxURLMatches = 7; |
| #endif |
| return base::GetFieldTrialParamByFeatureAsInt( |
| omnibox::kOmniboxMaxURLMatches, |
| OmniboxFieldTrial::kOmniboxMaxURLMatchesParam, kDefaultMaxURLMatches); |
| } |
| |
| void OmniboxFieldTrial::GetDefaultHUPScoringParams( |
| HUPScoringParams* scoring_params) { |
| ScoreBuckets* type_score_buckets = &scoring_params->typed_count_buckets; |
| type_score_buckets->set_half_life_days(30); |
| type_score_buckets->set_use_decay_factor(false); |
| // Default typed count buckets based on decayed typed count. The |
| // values here are based on the results of field trials to determine what |
| // maximized overall result quality. |
| const std::string& typed_count_score_buckets_str = |
| "1.0:1413,0.97:1390,0.93:1360,0.85:1340,0.72:1320,0.50:1250,0.0:1203"; |
| InitializeBucketsFromString(typed_count_score_buckets_str, |
| type_score_buckets); |
| |
| ScoreBuckets* visit_score_buckets = &scoring_params->visited_count_buckets; |
| visit_score_buckets->set_half_life_days(30); |
| visit_score_buckets->set_use_decay_factor(false); |
| // Buckets based on visit count. Like the typed count buckets above, the |
| // values here were chosen based on field trials. Note that when a URL hasn't |
| // been visited in the last 30 days, we clamp its score to 100, which |
| // basically demotes it below any other results in the dropdown. |
| const std::string& visit_count_score_buckets_str = "4.0:790,0.5:590,0.0:100"; |
| InitializeBucketsFromString(visit_count_score_buckets_str, |
| visit_score_buckets); |
| } |
| |
| void OmniboxFieldTrial::GetExperimentalHUPScoringParams( |
| HUPScoringParams* scoring_params) { |
| VariationParams params; |
| if (!base::GetFieldTrialParams(kBundledExperimentFieldTrialName, ¶ms)) { |
| return; |
| } |
| |
| InitializeScoreBuckets(params, kHUPNewScoringTypedCountRelevanceCapParam, |
| kHUPNewScoringTypedCountHalfLifeTimeParam, |
| kHUPNewScoringTypedCountScoreBucketsParam, |
| kHUPNewScoringTypedCountUseDecayFactorParam, |
| &scoring_params->typed_count_buckets); |
| InitializeScoreBuckets(params, kHUPNewScoringVisitedCountRelevanceCapParam, |
| kHUPNewScoringVisitedCountHalfLifeTimeParam, |
| kHUPNewScoringVisitedCountScoreBucketsParam, |
| kHUPNewScoringVisitedCountUseDecayFactorParam, |
| &scoring_params->visited_count_buckets); |
| } |
| |
| float OmniboxFieldTrial::HQPBookmarkValue() { |
| std::string bookmark_value_str = base::GetFieldTrialParamValue( |
| kBundledExperimentFieldTrialName, kHQPBookmarkValueRule); |
| if (bookmark_value_str.empty()) { |
| return 10; |
| } |
| // This is a best-effort conversion; we trust the hand-crafted parameters |
| // downloaded from the server to be perfect. There's no need for handle |
| // errors smartly. |
| double bookmark_value; |
| base::StringToDouble(bookmark_value_str, &bookmark_value); |
| return bookmark_value; |
| } |
| |
| bool OmniboxFieldTrial::HQPAllowMatchInTLDValue() { |
| return base::GetFieldTrialParamValue(kBundledExperimentFieldTrialName, |
| kHQPAllowMatchInTLDRule) == "true"; |
| } |
| |
| bool OmniboxFieldTrial::HQPAllowMatchInSchemeValue() { |
| return base::GetFieldTrialParamValue(kBundledExperimentFieldTrialName, |
| kHQPAllowMatchInSchemeRule) == "true"; |
| } |
| |
| void OmniboxFieldTrial::GetSuggestPollingStrategy(bool* from_last_keystroke, |
| int* polling_delay_ms) { |
| *from_last_keystroke = |
| base::GetFieldTrialParamValue( |
| kBundledExperimentFieldTrialName, |
| kMeasureSuggestPollingDelayFromLastKeystrokeRule) == "true"; |
| |
| const std::string& polling_delay_string = base::GetFieldTrialParamValue( |
| kBundledExperimentFieldTrialName, kSuggestPollingDelayMsRule); |
| if (polling_delay_string.empty() || |
| !base::StringToInt(polling_delay_string, polling_delay_ms) || |
| (*polling_delay_ms <= 0)) { |
| *polling_delay_ms = kDefaultMinimumTimeBetweenSuggestQueriesMs; |
| } |
| } |
| |
| std::string OmniboxFieldTrial::HQPExperimentalScoringBuckets() { |
| return base::GetFieldTrialParamValue(kBundledExperimentFieldTrialName, |
| kHQPExperimentalScoringBucketsParam); |
| } |
| |
| float OmniboxFieldTrial::HQPExperimentalTopicalityThreshold() { |
| std::string topicality_threshold_str = base::GetFieldTrialParamValue( |
| kBundledExperimentFieldTrialName, |
| kHQPExperimentalScoringTopicalityThresholdParam); |
| |
| double topicality_threshold; |
| if (topicality_threshold_str.empty() || |
| !base::StringToDouble(topicality_threshold_str, &topicality_threshold)) { |
| return 0.5f; |
| } |
| |
| return static_cast<float>(topicality_threshold); |
| } |
| |
| int OmniboxFieldTrial::MaxNumHQPUrlsIndexedAtStartup() { |
| #if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS) |
| // Limits on Android and iOS are chosen based on experiment results. See |
| // crbug.com/715852#c18 and crbug.com/1141539#c31. |
| constexpr int kDefaultOnLowEndDevices = 100; |
| constexpr int kDefaultOnNonLowEndDevices = 400; |
| #else |
| // Use 20,000 entries as a safety cap for users with spammed history, |
| // such as users who were stuck in a redirect loop with autogenerated URLs. |
| // This limit will only affect 0.01% of Windows users. crbug.com/750845. |
| constexpr int kDefaultOnLowEndDevices = 20000; |
| constexpr int kDefaultOnNonLowEndDevices = 20000; |
| #endif |
| |
| if (base::SysInfo::IsLowEndDeviceOrPartialLowEndModeEnabled()) { |
| return kDefaultOnLowEndDevices; |
| } else { |
| return kDefaultOnNonLowEndDevices; |
| } |
| } |
| |
| size_t OmniboxFieldTrial::HQPMaxVisitsToScore() { |
| std::string max_visits_str = base::GetFieldTrialParamValue( |
| kBundledExperimentFieldTrialName, kHQPMaxVisitsToScoreRule); |
| constexpr size_t kDefaultMaxVisitsToScore = 10; |
| static_assert( |
| URLIndexPrivateData::kMaxVisitsToStoreInCache >= kDefaultMaxVisitsToScore, |
| "HQP should store at least as many visits as it expects to score"); |
| if (max_visits_str.empty()) { |
| return kDefaultMaxVisitsToScore; |
| } |
| // This is a best-effort conversion; we trust the hand-crafted parameters |
| // downloaded from the server to be perfect. There's no need for handle |
| // errors smartly. |
| size_t max_visits_value; |
| base::StringToSizeT(max_visits_str, &max_visits_value); |
| return max_visits_value; |
| } |
| |
| float OmniboxFieldTrial::HQPTypedValue() { |
| std::string typed_value_str = base::GetFieldTrialParamValue( |
| kBundledExperimentFieldTrialName, kHQPTypedValueRule); |
| if (typed_value_str.empty()) { |
| return 1.5; |
| } |
| // This is a best-effort conversion; we trust the hand-crafted parameters |
| // downloaded from the server to be perfect. There's no need for handle |
| // errors smartly. |
| double typed_value; |
| base::StringToDouble(typed_value_str, &typed_value); |
| return typed_value; |
| } |
| |
| OmniboxFieldTrial::NumMatchesScores OmniboxFieldTrial::HQPNumMatchesScores() { |
| std::string str = base::GetFieldTrialParamValue( |
| kBundledExperimentFieldTrialName, kHQPNumMatchesScoresRule); |
| static constexpr char kDefaultNumMatchesScores[] = "1:3,2:2.5,3:2,4:1.5"; |
| if (str.empty()) { |
| str = kDefaultNumMatchesScores; |
| } |
| // The parameter is a comma-separated list of (number, value) pairs such as |
| // listed above. |
| // This is a best-effort conversion; we trust the hand-crafted parameters |
| // downloaded from the server to be perfect. There's no need to handle |
| // errors smartly. |
| base::StringPairs kv_pairs; |
| if (!base::SplitStringIntoKeyValuePairs(str, ':', ',', &kv_pairs)) { |
| return NumMatchesScores{}; |
| } |
| NumMatchesScores num_matches_scores(kv_pairs.size()); |
| for (size_t i = 0; i < kv_pairs.size(); ++i) { |
| base::StringToSizeT(kv_pairs[i].first, &num_matches_scores[i].first); |
| // The input must be sorted by number of matches. |
| DCHECK((i == 0) || |
| (num_matches_scores[i].first > num_matches_scores[i - 1].first)); |
| base::StringToDouble(kv_pairs[i].second, &num_matches_scores[i].second); |
| } |
| return num_matches_scores; |
| } |
| |
| size_t OmniboxFieldTrial::HQPNumTitleWordsToAllow() { |
| // The value of the rule is a string that encodes an integer (actually |
| // size_t) containing the number of words. |
| size_t num_title_words; |
| if (!base::StringToSizeT( |
| base::GetFieldTrialParamValue(kBundledExperimentFieldTrialName, |
| kHQPNumTitleWordsRule), |
| &num_title_words)) { |
| return 20; |
| } |
| return num_title_words; |
| } |
| |
| bool OmniboxFieldTrial::HQPAlsoDoHUPLikeScoring() { |
| return base::GetFieldTrialParamValue(kBundledExperimentFieldTrialName, |
| kHQPAlsoDoHUPLikeScoringRule) == "true"; |
| } |
| |
| bool OmniboxFieldTrial::HUPSearchDatabase() { |
| const std::string& value = base::GetFieldTrialParamValue( |
| kBundledExperimentFieldTrialName, kHUPSearchDatabaseRule); |
| return value.empty() || (value == "true"); |
| } |
| |
| bool OmniboxFieldTrial::IsOnDeviceHeadSuggestEnabledForIncognito() { |
| return base::FeatureList::IsEnabled(omnibox::kOnDeviceHeadProviderIncognito); |
| } |
| |
| bool OmniboxFieldTrial::IsOnDeviceHeadSuggestEnabledForNonIncognito() { |
| return base::FeatureList::IsEnabled( |
| omnibox::kOnDeviceHeadProviderNonIncognito); |
| } |
| |
| bool OmniboxFieldTrial::IsOnDeviceHeadSuggestEnabledForAnyMode() { |
| return IsOnDeviceHeadSuggestEnabledForIncognito() || |
| IsOnDeviceHeadSuggestEnabledForNonIncognito(); |
| } |
| |
| bool OmniboxFieldTrial::IsOnDeviceTailSuggestEnabled( |
| const std::string& locale) { |
| // Tail model will only be enabled when head provider is also enabled. |
| if (!IsOnDeviceHeadSuggestEnabledForAnyMode()) { |
| return false; |
| } |
| |
| // Currently only launch for English locales. Remove this flag once i18n is |
| // also launched. |
| // Do not launch for iOS since the feature is not supported in iOS yet. |
| #if !BUILDFLAG(IS_IOS) |
| if (IsEnglishLocale(locale)) { |
| return base::FeatureList::IsEnabled( |
| omnibox::kOnDeviceTailEnableEnglishModel); |
| } |
| #endif // !BUILDFLAG(IS_IOS) |
| |
| return base::FeatureList::IsEnabled(omnibox::kOnDeviceTailModel); |
| } |
| |
| bool OmniboxFieldTrial::ShouldEncodeLeadingSpaceForOnDeviceTailSuggest() { |
| return base::GetFieldTrialParamByFeatureAsBool(omnibox::kOnDeviceTailModel, |
| "ShouldEncodeLeadingSpace", |
| /*default_value=*/false); |
| } |
| |
| bool OmniboxFieldTrial::ShouldApplyOnDeviceHeadModelSelectionFix() { |
| return base::GetFieldTrialParamByFeatureAsBool( |
| omnibox::kOnDeviceHeadProviderNonIncognito, |
| OmniboxFieldTrial::kOnDeviceHeadModelSelectionFix, |
| /*default_value=*/true) || |
| base::GetFieldTrialParamByFeatureAsBool( |
| omnibox::kOnDeviceHeadProviderIncognito, |
| OmniboxFieldTrial::kOnDeviceHeadModelSelectionFix, |
| /*default_value=*/true); |
| } |
| |
| bool OmniboxFieldTrial::IsOnDeviceHeadSuggestEnabledForLocale( |
| const std::string& locale) { |
| if (IsKoreanLocale(locale)) { |
| return false; |
| } |
| return IsOnDeviceHeadSuggestEnabledForAnyMode(); |
| } |
| |
| std::string OmniboxFieldTrial::OnDeviceHeadModelLocaleConstraint( |
| bool is_incognito) { |
| const base::Feature* feature = |
| is_incognito ? &omnibox::kOnDeviceHeadProviderIncognito |
| : &omnibox::kOnDeviceHeadProviderNonIncognito; |
| std::string constraint = base::GetFieldTrialParamValueByFeature( |
| *feature, kOnDeviceHeadModelLocaleConstraint); |
| if (constraint.empty()) { |
| constraint = "500000"; |
| } |
| return constraint; |
| } |
| |
| const char OmniboxFieldTrial::kBundledExperimentFieldTrialName[] = |
| "OmniboxBundledExperimentV1"; |
| const char OmniboxFieldTrial::kDisableProvidersRule[] = "DisableProviders"; |
| const char OmniboxFieldTrial::kHQPBookmarkValueRule[] = "HQPBookmarkValue"; |
| const char OmniboxFieldTrial::kHQPTypedValueRule[] = "HQPTypedValue"; |
| const char OmniboxFieldTrial::kHQPAllowMatchInTLDRule[] = "HQPAllowMatchInTLD"; |
| const char OmniboxFieldTrial::kHQPAllowMatchInSchemeRule[] = |
| "HQPAllowMatchInScheme"; |
| const char |
| OmniboxFieldTrial::kMeasureSuggestPollingDelayFromLastKeystrokeRule[] = |
| "MeasureSuggestPollingDelayFromLastKeystroke"; |
| const char OmniboxFieldTrial::kSuggestPollingDelayMsRule[] = |
| "SuggestPollingDelayMs"; |
| const char OmniboxFieldTrial::kHQPMaxVisitsToScoreRule[] = |
| "HQPMaxVisitsToScoreRule"; |
| const char OmniboxFieldTrial::kHQPNumMatchesScoresRule[] = |
| "HQPNumMatchesScores"; |
| const char OmniboxFieldTrial::kHQPNumTitleWordsRule[] = "HQPNumTitleWords"; |
| const char OmniboxFieldTrial::kHQPAlsoDoHUPLikeScoringRule[] = |
| "HQPAlsoDoHUPLikeScoring"; |
| const char OmniboxFieldTrial::kHUPSearchDatabaseRule[] = "HUPSearchDatabase"; |
| const char OmniboxFieldTrial::kHUPNewScoringTypedCountRelevanceCapParam[] = |
| "TypedCountRelevanceCap"; |
| const char OmniboxFieldTrial::kHUPNewScoringTypedCountHalfLifeTimeParam[] = |
| "TypedCountHalfLifeTime"; |
| const char OmniboxFieldTrial::kHUPNewScoringTypedCountScoreBucketsParam[] = |
| "TypedCountScoreBuckets"; |
| const char OmniboxFieldTrial::kHUPNewScoringTypedCountUseDecayFactorParam[] = |
| "TypedCountUseDecayFactor"; |
| const char OmniboxFieldTrial::kHUPNewScoringVisitedCountRelevanceCapParam[] = |
| "VisitedCountRelevanceCap"; |
| const char OmniboxFieldTrial::kHUPNewScoringVisitedCountHalfLifeTimeParam[] = |
| "VisitedCountHalfLifeTime"; |
| const char OmniboxFieldTrial::kHUPNewScoringVisitedCountScoreBucketsParam[] = |
| "VisitedCountScoreBuckets"; |
| const char OmniboxFieldTrial::kHUPNewScoringVisitedCountUseDecayFactorParam[] = |
| "VisitedCountUseDecayFactor"; |
| |
| const char OmniboxFieldTrial::kHQPExperimentalScoringBucketsParam[] = |
| "HQPExperimentalScoringBuckets"; |
| const char |
| OmniboxFieldTrial::kHQPExperimentalScoringTopicalityThresholdParam[] = |
| "HQPExperimentalScoringTopicalityThreshold"; |
| |
| const char |
| OmniboxFieldTrial::kMaxNumHQPUrlsIndexedAtStartupOnLowEndDevicesParam[] = |
| "MaxNumHQPUrlsIndexedAtStartupOnLowEndDevices"; |
| const char |
| OmniboxFieldTrial::kMaxNumHQPUrlsIndexedAtStartupOnNonLowEndDevicesParam[] = |
| "MaxNumHQPUrlsIndexedAtStartupOnNonLowEndDevices"; |
| |
| const char OmniboxFieldTrial::kMaxZeroSuggestMatchesParam[] = |
| "MaxZeroSuggestMatches"; |
| const char OmniboxFieldTrial::kOmniboxMaxURLMatchesParam[] = |
| "OmniboxMaxURLMatches"; |
| const char OmniboxFieldTrial::kUIMaxAutocompleteMatchesByProviderParam[] = |
| "UIMaxAutocompleteMatchesByProvider"; |
| const char OmniboxFieldTrial::kUIMaxAutocompleteMatchesParam[] = |
| "UIMaxAutocompleteMatches"; |
| const char OmniboxFieldTrial::kDynamicMaxAutocompleteUrlCutoffParam[] = |
| "OmniboxDynamicMaxAutocompleteUrlCutoff"; |
| const char OmniboxFieldTrial::kDynamicMaxAutocompleteIncreasedLimitParam[] = |
| "OmniboxDynamicMaxAutocompleteIncreasedLimit"; |
| const char OmniboxFieldTrial::kSuppressPsuggestBackfillWithMIAParam[] = |
| "SuppressPsuggestBackfillWithMIA"; |
| |
| const char OmniboxFieldTrial::kOnDeviceHeadModelLocaleConstraint[] = |
| "ForceModelLocaleConstraint"; |
| const char OmniboxFieldTrial::kOnDeviceHeadModelSelectionFix[] = "SelectionFix"; |
| |
| int OmniboxFieldTrial::kDefaultMinimumTimeBetweenSuggestQueriesMs = 100; |
| |
| namespace OmniboxFieldTrial { |
| |
| // Local history zero-prefix (aka zero-suggest) and prefix suggestions: |
| |
| // Whether to ignore all ZPS prefetch responses received from the Suggest |
| // service when the user is on a Google SRP. This can be used, for example, |
| // during experimentation to measure the performance impact of only the |
| // request/response portion of ZPS prefetching (i.e. without updating the |
| // user-visible list of suggestions in the Omnibox). |
| const base::FeatureParam<bool> kZeroSuggestPrefetchingOnSRPCounterfactual( |
| &omnibox::kZeroSuggestPrefetchingOnSRP, |
| "ZeroSuggestPrefetchingOnSRPCounterfactual", |
| false); |
| |
| // The debouncing delay (in milliseconds) to use when throttling ZPS prefetch |
| // requests. |
| const base::FeatureParam<int> kZeroSuggestPrefetchDebounceDelay( |
| &omnibox::kZeroSuggestPrefetchDebouncing, |
| "ZeroSuggestPrefetchDebounceDelay", |
| 300); |
| |
| // Whether to calculate debouncing delay relative to the latest successful run |
| // (instead of the latest run request). |
| const base::FeatureParam<bool> kZeroSuggestPrefetchDebounceFromLastRun( |
| &omnibox::kZeroSuggestPrefetchDebouncing, |
| "ZeroSuggestPrefetchDebounceFromLastRun", |
| true); |
| |
| // The maximum number of entries stored by the in-memory zero-suggest cache at |
| // at any given time (LRU eviction policy is used to enforce this limit). |
| const base::FeatureParam<int> kZeroSuggestCacheMaxSize( |
| &omnibox::kZeroSuggestInMemoryCaching, |
| "ZeroSuggestCacheMaxSize", |
| 5); |
| |
| bool IsZeroSuggestPrefetchingEnabled() { |
| return base::FeatureList::IsEnabled(omnibox::kZeroSuggestPrefetching) || |
| base::FeatureList::IsEnabled(omnibox::kZeroSuggestPrefetchingOnSRP) || |
| base::FeatureList::IsEnabled(omnibox::kZeroSuggestPrefetchingOnWeb); |
| } |
| |
| bool IsZeroSuggestPrefetchingEnabledInContext( |
| metrics::OmniboxEventProto::PageClassification page_classification) { |
| switch (page_classification) { |
| case metrics::OmniboxEventProto::NTP_ZPS_PREFETCH: |
| return base::FeatureList::IsEnabled(omnibox::kZeroSuggestPrefetching); |
| case metrics::OmniboxEventProto::SRP_ZPS_PREFETCH: |
| return base::FeatureList::IsEnabled( |
| omnibox::kZeroSuggestPrefetchingOnSRP); |
| case metrics::OmniboxEventProto::OTHER_ZPS_PREFETCH: |
| return base::FeatureList::IsEnabled( |
| omnibox::kZeroSuggestPrefetchingOnWeb); |
| default: |
| return false; |
| } |
| } |
| |
| bool IsOnFocusZeroSuggestEnabledInContext( |
| metrics::OmniboxEventProto::PageClassification page_classification) { |
| static bool enabled = |
| base::FeatureList::IsEnabled(omnibox::kFocusTriggersWebAndSRPZeroSuggest); |
| |
| switch (page_classification) { |
| case metrics::OmniboxEventProto:: |
| SEARCH_RESULT_PAGE_NO_SEARCH_TERM_REPLACEMENT: |
| case metrics::OmniboxEventProto::OTHER: |
| return enabled; |
| default: |
| return false; |
| } |
| } |
| |
| bool IsHideSuggestionGroupHeadersEnabledInContext( |
| metrics::OmniboxEventProto::PageClassification page_classification) { |
| static bool enabled = |
| base::FeatureList::IsEnabled(omnibox::kHideSuggestionGroupHeaders); |
| |
| switch (page_classification) { |
| case metrics::OmniboxEventProto:: |
| SEARCH_RESULT_PAGE_NO_SEARCH_TERM_REPLACEMENT: |
| case metrics::OmniboxEventProto::OTHER: |
| return enabled; |
| default: |
| return false; |
| } |
| } |
| |
| bool IsDeterministicAimActionInTypedStateEnabled(AutocompleteProviderClient* client) { |
| ui::DeviceFormFactor factor = ui::GetDeviceFormFactor(); |
| if (!(factor == ui::DEVICE_FORM_FACTOR_PHONE || factor == ui::DEVICE_FORM_FACTOR_FOLDABLE)) { |
| return false; |
| } |
| |
| // If the feature is overridden to be false, return false. |
| auto* feature_list = base::FeatureList::GetInstance(); |
| if (feature_list && |
| feature_list->IsFeatureOverridden(omnibox::kOmniboxAimShortcutTypedState.name) && |
| !base::FeatureList::IsEnabled(omnibox::kOmniboxAimShortcutTypedState)) { |
| return false; |
| } |
| |
| const auto* aim_eligibility_service = client->GetAimEligibilityService(); |
| #if BUILDFLAG(IS_IOS) |
| // TODO (ameurhosni): Remove this once AimEligibilityService is ready in iOS. |
| if (!aim_eligibility_service) { |
| return base::FeatureList::IsEnabled(omnibox::kOmniboxAimShortcutTypedState); |
| } |
| #endif |
| |
| // If the server eligibility is enabled, check overall eligibility alone. |
| // The service will control locale rollout so there's no need to check the |
| // state of omnibox::kOmniboxAimShortcutTypedState below. |
| if (aim_eligibility_service->IsServerEligibilityEnabled()) { |
| return aim_eligibility_service->IsAimEligible(); |
| } |
| |
| // If not locally eligible, return false. |
| if (!aim_eligibility_service->IsAimLocallyEligible()) { |
| return false; |
| } |
| |
| // Otherwise, check the feature state. |
| return base::FeatureList::IsEnabled(omnibox::kOmniboxAimShortcutTypedState); |
| } |
| |
| bool IsAimOmniboxEntrypointEnabled( |
| const AimEligibilityService* aim_eligibility_service) { |
| // If the generic entrypoint feature is overridden to be false, return false. |
| auto* feature_list = base::FeatureList::GetInstance(); |
| if (feature_list && |
| feature_list->IsFeatureOverridden( |
| omnibox::kAiModeOmniboxEntryPoint.name) && |
| !base::FeatureList::IsEnabled(omnibox::kAiModeOmniboxEntryPoint)) { |
| return false; |
| } |
| |
| // If the server eligibility is enabled, check overall eligibility alone. |
| // The service will control locale rollout so there's no need to check locale |
| // or the state of kAiModeOmniboxEntryPoint below. |
| if (aim_eligibility_service->IsServerEligibilityEnabled()) { |
| return aim_eligibility_service->IsAimEligible(); |
| } |
| |
| // If not locally eligible, return false. |
| if (!aim_eligibility_service->IsAimLocallyEligible()) { |
| return false; |
| } |
| |
| // Otherwise, check the generic entrypoint feature. |
| return base::FeatureList::IsEnabled(omnibox::kAiModeOmniboxEntryPoint); |
| } |
| |
| // Rich autocompletion. |
| |
| bool IsRichAutocompletionEnabled() { |
| return base::FeatureList::IsEnabled(omnibox::kRichAutocompletion); |
| } |
| |
| const base::FeatureParam<size_t> kRichAutocompletionAutocompleteTitlesMinChar( |
| &omnibox::kRichAutocompletion, |
| "RichAutocompletionAutocompleteTitlesMinChar", |
| 3); |
| |
| const base::FeatureParam<size_t> |
| kRichAutocompletionAutocompleteShortcutTextMinChar( |
| &omnibox::kRichAutocompletion, |
| "RichAutocompletionAutocompleteShortcutTextMinChar", |
| 3); |
| |
| // --------------------------------------------------------- |
| // ML Relevance Scoring -> |
| |
| // If true, enables scoring signal annotators for logging Omnibox scoring |
| // signals to OmniboxEventProto. |
| const base::FeatureParam<bool> kEnableScoringSignalsAnnotatorsForLogging( |
| &omnibox::kLogUrlScoringSignals, |
| "enable_scoring_signals_annotators", |
| false); |
| |
| // If true, enables scoring signal annotators for ML scoring. |
| const base::FeatureParam<bool> kEnableScoringSignalsAnnotatorsForMlScoring( |
| &omnibox::kMlUrlScoring, |
| "enable_scoring_signals_annotators_for_ml_scoring", |
| true); |
| |
| // If true, runs the ML scoring model but does not assign new relevance scores |
| // to the URL suggestions and does not rerank them. |
| const base::FeatureParam<bool> kMlUrlScoringCounterfactual( |
| &omnibox::kMlUrlScoring, |
| "MlUrlScoringCounterfactual", |
| false); |
| |
| // If true, increases the number of candidates the URL autocomplete providers |
| // pass to the controller beyond `provider_max_matches`. |
| const base::FeatureParam<bool> kMlUrlScoringUnlimitedNumCandidates( |
| &omnibox::kMlUrlScoring, |
| "MlUrlScoringUnlimitedNumCandidates", |
| false); |
| |
| const base::FeatureParam<std::string> kMlUrlScoringMaxMatchesByProvider( |
| &omnibox::kMlUrlScoring, |
| "MlUrlScoringMaxMatchesByProvider", |
| ""); |
| |
| MLConfig::MLConfig() { |
| log_url_scoring_signals = |
| base::FeatureList::IsEnabled(omnibox::kLogUrlScoringSignals); |
| enable_history_scoring_signals_annotator_for_searches = |
| base::FeatureList::IsEnabled( |
| omnibox::kEnableHistoryScoringSignalsAnnotatorForSearches); |
| enable_scoring_signals_annotators = |
| kEnableScoringSignalsAnnotatorsForLogging.Get() || |
| kEnableScoringSignalsAnnotatorsForMlScoring.Get(); |
| shortcut_document_signals = |
| base::FeatureParam<bool>(&omnibox::kLogUrlScoringSignals, |
| "MlUrlScoringShortcutDocumentSignals", false) |
| .Get() || |
| base::FeatureParam<bool>(&omnibox::kMlUrlScoring, |
| "MlUrlScoringShortcutDocumentSignals", true) |
| .Get(); |
| |
| ml_url_scoring = base::FeatureList::IsEnabled(omnibox::kMlUrlScoring); |
| ml_url_scoring_counterfactual = kMlUrlScoringCounterfactual.Get(); |
| ml_url_scoring_unlimited_num_candidates = |
| kMlUrlScoringUnlimitedNumCandidates.Get(); |
| ml_url_scoring_max_matches_by_provider = |
| kMlUrlScoringMaxMatchesByProvider.Get(); |
| |
| // `kMlUrlSearchBlending` parameters. |
| mapped_search_blending = |
| base::FeatureParam<bool>(&omnibox::kMlUrlSearchBlending, |
| "MlUrlSearchBlending_MappedSearchBlending", |
| mapped_search_blending) |
| .Get(); |
| mapped_search_blending_min = |
| base::FeatureParam<int>(&omnibox::kMlUrlSearchBlending, |
| "MlUrlSearchBlending_MappedSearchBlendingMin", |
| mapped_search_blending_min) |
| .Get(); |
| mapped_search_blending_max = |
| base::FeatureParam<int>(&omnibox::kMlUrlSearchBlending, |
| "MlUrlSearchBlending_MappedSearchBlendingMax", |
| mapped_search_blending_max) |
| .Get(); |
| mapped_search_blending_grouping_threshold = |
| base::FeatureParam<int>( |
| &omnibox::kMlUrlSearchBlending, |
| "MlUrlSearchBlending_MappedSearchBlendingGroupingThreshold", |
| mapped_search_blending_grouping_threshold) |
| .Get(); |
| |
| // `kMlUrlPiecewiseMappedSearchBlending` parameters. |
| piecewise_mapped_search_blending = |
| base::FeatureParam<bool>(&omnibox::kMlUrlPiecewiseMappedSearchBlending, |
| "MlUrlPiecewiseMappedSearchBlending", |
| piecewise_mapped_search_blending) |
| .Get(); |
| piecewise_mapped_search_blending_grouping_threshold = |
| base::FeatureParam<int>( |
| &omnibox::kMlUrlPiecewiseMappedSearchBlending, |
| "MlUrlPiecewiseMappedSearchBlending_GroupingThreshold", |
| piecewise_mapped_search_blending_grouping_threshold) |
| .Get(); |
| piecewise_mapped_search_blending_break_points = |
| base::FeatureParam<std::string>( |
| &omnibox::kMlUrlPiecewiseMappedSearchBlending, |
| "MlUrlPiecewiseMappedSearchBlending_BreakPoints", "") |
| .Get(); |
| piecewise_mapped_search_blending_break_points_verbatim_url = |
| base::FeatureParam<std::string>( |
| &omnibox::kMlUrlPiecewiseMappedSearchBlending, |
| "MlUrlPiecewiseMappedSearchBlending_BreakPoints_VerbatimUrl", |
| piecewise_mapped_search_blending_break_points.c_str()) |
| .Get(); |
| piecewise_mapped_search_blending_break_points_search = |
| base::FeatureParam<std::string>( |
| &omnibox::kMlUrlPiecewiseMappedSearchBlending, |
| "MlUrlPiecewiseMappedSearchBlending_BreakPoints_Search", "0,0;1,1400") |
| .Get(); |
| piecewise_mapped_search_blending_relevance_bias = |
| base::FeatureParam<int>( |
| &omnibox::kMlUrlPiecewiseMappedSearchBlending, |
| "MlUrlPiecewiseMappedSearchBlending_RelevanceBias", |
| piecewise_mapped_search_blending_relevance_bias) |
| .Get(); |
| |
| url_scoring_model = base::FeatureList::IsEnabled(omnibox::kUrlScoringModel); |
| |
| ml_url_score_caching = |
| base::FeatureList::IsEnabled(omnibox::kMlUrlScoreCaching); |
| max_ml_score_cache_size = |
| base::FeatureParam<int>(&omnibox::kMlUrlScoreCaching, |
| "MlUrlScoreCaching_MaxMlScoreCacheSize", |
| max_ml_score_cache_size) |
| .Get(); |
| |
| enable_ml_scoring_for_searches = |
| base::FeatureParam<bool>(&omnibox::kMlUrlScoring, |
| "MlUrlScoring_EnableMlScoringForSearches", false) |
| .Get(); |
| enable_ml_scoring_for_verbatim_urls = |
| base::FeatureParam<bool>(&omnibox::kMlUrlScoring, |
| "MlUrlScoring_EnableMlScoringForVerbatimUrls", |
| false) |
| .Get(); |
| } |
| |
| MLConfig::~MLConfig() = default; |
| |
| MLConfig::MLConfig(const MLConfig&) = default; |
| |
| MLConfig& MLConfig::operator=(const MLConfig& other) = default; |
| |
| ScopedMLConfigForTesting::ScopedMLConfigForTesting() |
| : original_config_(std::make_unique<MLConfig>(GetMLConfig())) { |
| GetMLConfigInternal() = {}; |
| } |
| |
| ScopedMLConfigForTesting::~ScopedMLConfigForTesting() { |
| GetMLConfigInternal() = *original_config_; |
| } |
| |
| MLConfig& ScopedMLConfigForTesting::GetMLConfig() { |
| return GetMLConfigInternal(); |
| } |
| |
| const MLConfig& GetMLConfig() { |
| return GetMLConfigInternal(); |
| } |
| |
| bool IsReportingUrlScoringSignalsEnabled() { |
| return GetMLConfig().log_url_scoring_signals; |
| } |
| |
| bool IsPopulatingUrlScoringSignalsEnabled() { |
| return IsReportingUrlScoringSignalsEnabled() || IsMlUrlScoringEnabled(); |
| } |
| |
| bool AreScoringSignalsAnnotatorsEnabled() { |
| return GetMLConfig().enable_scoring_signals_annotators; |
| } |
| bool IsMlUrlScoringEnabled() { |
| #if BUILDFLAG(BUILD_WITH_TFLITE_LIB) |
| return IsUrlScoringModelEnabled() && GetMLConfig().ml_url_scoring; |
| #else |
| return false; |
| #endif // BUILDFLAG(BUILD_WITH_TFLITE_LIB) |
| } |
| bool IsMlUrlScoringCounterfactual() { |
| return IsMlUrlScoringEnabled() && GetMLConfig().ml_url_scoring_counterfactual; |
| } |
| bool IsMlUrlScoringUnlimitedNumCandidatesEnabled() { |
| return IsMlUrlScoringEnabled() && |
| GetMLConfig().ml_url_scoring_unlimited_num_candidates; |
| } |
| bool IsUrlScoringModelEnabled() { |
| return GetMLConfig().url_scoring_model; |
| } |
| bool IsMlUrlScoreCachingEnabled() { |
| return GetMLConfig().ml_url_score_caching; |
| } |
| |
| std::vector<std::pair<double, int>> GetPiecewiseMappingBreakPoints( |
| PiecewiseMappingVariant mapping_variant) { |
| std::vector<std::pair<double, int>> break_points; |
| |
| std::string param_value; |
| switch (mapping_variant) { |
| case PiecewiseMappingVariant::kRegular: |
| param_value = OmniboxFieldTrial::GetMLConfig() |
| .piecewise_mapped_search_blending_break_points; |
| break; |
| case PiecewiseMappingVariant::kVerbatimUrl: |
| param_value = |
| OmniboxFieldTrial::GetMLConfig() |
| .piecewise_mapped_search_blending_break_points_verbatim_url; |
| break; |
| case PiecewiseMappingVariant::kSearch: |
| param_value = OmniboxFieldTrial::GetMLConfig() |
| .piecewise_mapped_search_blending_break_points_search; |
| break; |
| default: |
| NOTREACHED(); |
| } |
| |
| base::StringPairs pairs; |
| if (base::SplitStringIntoKeyValuePairs(param_value, ',', ';', &pairs)) { |
| for (const auto& p : pairs) { |
| double ml_score; |
| base::StringToDouble(p.first, &ml_score); |
| int relevance; |
| base::StringToInt(p.second, &relevance); |
| |
| break_points.push_back(std::make_pair(ml_score, relevance)); |
| } |
| } |
| |
| return break_points; |
| } |
| |
| // <- ML Relevance Scoring |
| // --------------------------------------------------------- |
| // Touch Down Trigger For Prefetch -> |
| const base::FeatureParam<int> |
| kTouchDownTriggerForPrefetchMaxPrefetchesPerOmniboxSession( |
| &omnibox::kOmniboxTouchDownTriggerForPrefetch, |
| "max_prefetches_per_omnibox_session", |
| 5); |
| // <- Touch Down Trigger For Prefetch |
| // --------------------------------------------------------- |
| // Site Search Starter Pack -> |
| const base::FeatureParam<std::string> kGeminiUrlOverride( |
| &omnibox::kStarterPackExpansion, |
| "StarterPackGeminiUrlOverride", |
| "https://gemini.google.com/prompt?" |
| "utm_source=chrome_omnibox&utm_medium=owned&utm_campaign=gemini_shortcut"); |
| |
| bool IsStarterPackExpansionEnabled() { |
| return base::FeatureList::IsEnabled(omnibox::kStarterPackExpansion); |
| } |
| |
| bool IsStarterPackIPHEnabled() { |
| return base::FeatureList::IsEnabled(omnibox::kStarterPackIPH); |
| } |
| |
| // <- Site Search Starter Pack |
| } // namespace OmniboxFieldTrial |