| // Copyright 2022 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/history_clusters/core/config.h" |
| |
| #include "base/command_line.h" |
| #include "base/containers/contains.h" |
| #include "base/feature_list.h" |
| #include "base/metrics/field_trial_params.h" |
| #include "base/no_destructor.h" |
| #include "base/strings/string_piece_forward.h" |
| #include "base/strings/string_split.h" |
| #include "build/build_config.h" |
| #include "components/history_clusters/core/features.h" |
| #include "components/history_clusters/core/history_clusters_prefs.h" |
| #include "components/history_clusters/core/history_clusters_service.h" |
| #include "components/history_clusters/core/on_device_clustering_features.h" |
| #include "components/prefs/pref_service.h" |
| #include "ui/base/l10n/l10n_util.h" |
| |
| namespace history_clusters { |
| |
| namespace { |
| |
| Config& GetConfigInternal() { |
| static base::NoDestructor<Config> s_config; |
| return *s_config; |
| } |
| |
| } // namespace |
| |
| namespace switches { |
| |
| const char kShouldShowAllClustersOnProminentUiSurfaces[] = |
| "history-clusters-should-show-all-clusters-on-prominent-ui-surfaces"; |
| |
| } // namespace switches |
| |
| Config::Config() { |
| // Override any parameters that may be provided by Finch. |
| |
| // The `kJourneys` feature and child params. |
| { |
| is_journeys_enabled_no_locale_check = |
| base::FeatureList::IsEnabled(internal::kJourneys); |
| |
| max_visits_to_cluster = base::GetFieldTrialParamByFeatureAsInt( |
| internal::kJourneys, "JourneysMaxVisitsToCluster", |
| max_visits_to_cluster); |
| |
| max_keyword_phrases = base::GetFieldTrialParamByFeatureAsInt( |
| internal::kJourneys, "JourneysMaxKeywordPhrases", max_keyword_phrases); |
| |
| min_score_to_always_show_above_the_fold = |
| base::GetFieldTrialParamByFeatureAsDouble( |
| internal::kJourneys, "JourneysMinScoreToAlwaysShowAboveTheFold", |
| min_score_to_always_show_above_the_fold); |
| |
| num_visits_to_always_show_above_the_fold = |
| base::GetFieldTrialParamByFeatureAsInt( |
| internal::kJourneys, "JourneysNumVisitsToAlwaysShowAboveTheFold", |
| num_visits_to_always_show_above_the_fold); |
| |
| rescore_visits_within_clusters_for_query = |
| base::GetFieldTrialParamByFeatureAsBool( |
| internal::kJourneys, "JourneysRescoreVisitsWithinClustersForQuery", |
| rescore_visits_within_clusters_for_query); |
| |
| sort_clusters_within_batch_for_query = |
| base::GetFieldTrialParamByFeatureAsBool( |
| internal::kJourneys, "JourneysSortClustersWithinBatchForQuery", |
| sort_clusters_within_batch_for_query); |
| } |
| |
| // The `kJourneysLabels` feature and child params. |
| { |
| labels_from_hostnames = GetFieldTrialParamByFeatureAsBool( |
| internal::kJourneysLabels, "labels_from_hostnames", |
| labels_from_hostnames); |
| |
| labels_from_entities = GetFieldTrialParamByFeatureAsBool( |
| internal::kJourneysLabels, "labels_from_entities", |
| labels_from_entities); |
| } |
| |
| // The `kJourneysImages` feature. |
| { images = base::FeatureList::IsEnabled(internal::kJourneysImages); } |
| |
| // The `kPersistedClusters` feature and child params. |
| { |
| persist_clusters_in_history_db = |
| base::FeatureList::IsEnabled(internal::kPersistedClusters); |
| |
| persist_clusters_in_history_db_after_startup_delay_minutes = |
| base::GetFieldTrialParamByFeatureAsInt( |
| internal::kPersistedClusters, |
| "JourneysPersistClustersInHistoryDbAfterStartupDelayMinutes", |
| persist_clusters_in_history_db_after_startup_delay_minutes); |
| |
| persist_clusters_in_history_db_period_minutes = |
| base::GetFieldTrialParamByFeatureAsInt( |
| internal::kPersistedClusters, |
| "JourneysPersistClustersInHistoryDbPeriodMinutes", |
| persist_clusters_in_history_db_period_minutes); |
| |
| persist_on_query = base::GetFieldTrialParamByFeatureAsBool( |
| internal::kPersistedClusters, "persist_on_query", persist_on_query); |
| |
| max_persisted_clusters_to_fetch = base::GetFieldTrialParamByFeatureAsInt( |
| internal::kPersistedClusters, "max_persisted_clusters_to_fetch", |
| max_persisted_clusters_to_fetch); |
| |
| max_persisted_cluster_visits_to_fetch_soft_cap = |
| base::GetFieldTrialParamByFeatureAsInt( |
| internal::kPersistedClusters, |
| "max_persisted_cluster_visits_to_fetch_soft_cap", |
| max_persisted_cluster_visits_to_fetch_soft_cap); |
| |
| persist_clusters_recluster_window_days = |
| base::GetFieldTrialParamByFeatureAsInt( |
| internal::kPersistedClusters, |
| "persist_clusters_recluster_window_days", |
| persist_clusters_recluster_window_days); |
| } |
| |
| // The `kOmniboxAction` feature and child params. |
| { |
| omnibox_action = base::FeatureList::IsEnabled(internal::kOmniboxAction); |
| |
| omnibox_action_on_urls = base::GetFieldTrialParamByFeatureAsBool( |
| internal::kOmniboxAction, "omnibox_action_on_urls", |
| omnibox_action_on_urls); |
| |
| omnibox_action_on_noisy_urls = base::GetFieldTrialParamByFeatureAsBool( |
| internal::kOmniboxAction, "omnibox_action_on_noisy_urls", |
| omnibox_action_on_noisy_urls); |
| |
| omnibox_action_with_pedals = base::GetFieldTrialParamByFeatureAsBool( |
| internal::kOmniboxAction, "omnibox_action_with_pedals", |
| omnibox_action_with_pedals); |
| |
| omnibox_action_on_navigation_intents = |
| base::GetFieldTrialParamByFeatureAsBool( |
| internal::kOmniboxAction, "omnibox_action_on_navigation_intents", |
| omnibox_action_on_navigation_intents); |
| |
| omnibox_action_navigation_intent_score_threshold = |
| base::GetFieldTrialParamByFeatureAsInt( |
| internal::kOmniboxAction, |
| "omnibox_action_on_navigation_intent_score_threshold", |
| omnibox_action_navigation_intent_score_threshold); |
| } |
| |
| // The `kOmniboxHistoryClusterProvider` feature and child params. |
| { |
| omnibox_history_cluster_provider = |
| base::FeatureList::IsEnabled(internal::kOmniboxHistoryClusterProvider); |
| |
| omnibox_history_cluster_provider_counterfactual = |
| base::GetFieldTrialParamByFeatureAsBool( |
| internal::kOmniboxHistoryClusterProvider, |
| "omnibox_history_cluster_provider_counterfactual", |
| omnibox_history_cluster_provider_counterfactual); |
| |
| omnibox_history_cluster_provider_score = |
| base::GetFieldTrialParamByFeatureAsInt( |
| internal::kOmniboxHistoryClusterProvider, |
| "omnibox_history_cluster_provider_score", |
| omnibox_history_cluster_provider_score); |
| |
| omnibox_history_cluster_provider_inherit_search_match_score = |
| base::GetFieldTrialParamByFeatureAsBool( |
| internal::kOmniboxHistoryClusterProvider, |
| "omnibox_history_cluster_provider_inherit_search_match_score", |
| omnibox_history_cluster_provider_inherit_search_match_score); |
| |
| omnibox_history_cluster_provider_rank_above_searches = |
| base::GetFieldTrialParamByFeatureAsBool( |
| internal::kOmniboxHistoryClusterProvider, |
| "omnibox_history_cluster_provider_rank_above_searches", |
| omnibox_history_cluster_provider_rank_above_searches); |
| |
| omnibox_history_cluster_provider_shortcuts = |
| base::GetFieldTrialParamByFeatureAsBool( |
| internal::kOmniboxHistoryClusterProvider, |
| "omnibox_history_cluster_provider_shortcuts", |
| omnibox_history_cluster_provider_shortcuts); |
| |
| omnibox_history_cluster_provider_allow_default = |
| base::GetFieldTrialParamByFeatureAsBool( |
| internal::kOmniboxHistoryClusterProvider, |
| "omnibox_history_cluster_provider_allow_default", |
| omnibox_history_cluster_provider_allow_default); |
| |
| omnibox_history_cluster_provider_navigation_intent_score_threshold = |
| base::GetFieldTrialParamByFeatureAsInt( |
| internal::kOmniboxHistoryClusterProvider, |
| "omnibox_history_cluster_provider_navigation_intent_score_" |
| "threshold", |
| omnibox_history_cluster_provider_navigation_intent_score_threshold); |
| |
| omnibox_history_cluster_provider_on_navigation_intents = |
| base::GetFieldTrialParamByFeatureAsBool( |
| internal::kOmniboxHistoryClusterProvider, |
| "omnibox_history_cluster_provider_on_navigation_intents", |
| omnibox_history_cluster_provider_on_navigation_intents); |
| } |
| |
| // The `kOnDeviceClusteringKeywordFiltering` feature and child params. |
| { |
| keyword_filter_on_entity_aliases = base::GetFieldTrialParamByFeatureAsBool( |
| history_clusters::features::kOnDeviceClusteringKeywordFiltering, |
| "keyword_filter_on_entity_aliases", keyword_filter_on_entity_aliases); |
| |
| max_entity_aliases_in_keywords = base::GetFieldTrialParamByFeatureAsInt( |
| history_clusters::features::kOnDeviceClusteringKeywordFiltering, |
| "max_entity_aliases_in_keywords", max_entity_aliases_in_keywords); |
| if (max_entity_aliases_in_keywords <= 0) { |
| max_entity_aliases_in_keywords = SIZE_MAX; |
| } |
| |
| keyword_filter_on_noisy_visits = GetFieldTrialParamByFeatureAsBool( |
| history_clusters::features::kOnDeviceClusteringKeywordFiltering, |
| "keyword_filter_on_noisy_visits", keyword_filter_on_noisy_visits); |
| |
| max_num_keywords_per_cluster = GetFieldTrialParamByFeatureAsInt( |
| features::kOnDeviceClusteringKeywordFiltering, |
| "max_num_keywords_per_cluster", max_num_keywords_per_cluster); |
| } |
| |
| // The `kOnDeviceClustering` feature and child params. |
| { |
| cluster_navigation_time_cutoff = |
| base::Minutes(GetFieldTrialParamByFeatureAsInt( |
| features::kOnDeviceClustering, "navigation_time_cutoff_minutes", |
| cluster_navigation_time_cutoff.InMinutes())); |
| |
| entity_relevance_threshold = GetFieldTrialParamByFeatureAsInt( |
| features::kOnDeviceClustering, "entity_relevance_threshold", |
| entity_relevance_threshold); |
| // Ensure that the value is [0 and 100]. |
| DCHECK_GE(entity_relevance_threshold, 0); |
| DCHECK_LE(entity_relevance_threshold, 100); |
| |
| content_visibility_threshold = GetFieldTrialParamByFeatureAsDouble( |
| features::kOnDeviceClustering, "content_visibility_threshold", 0.7); |
| // Ensure that the value is [0.0 and 1.0]. |
| DCHECK_GE(content_visibility_threshold, 0.0f); |
| DCHECK_LE(content_visibility_threshold, 1.0f); |
| |
| noisy_cluster_visits_engagement_threshold = |
| GetFieldTrialParamByFeatureAsDouble( |
| features::kOnDeviceClustering, |
| "noisy_cluster_visit_engagement_threshold", |
| noisy_cluster_visits_engagement_threshold); |
| |
| number_interesting_visits_filter_threshold = |
| GetFieldTrialParamByFeatureAsInt( |
| features::kOnDeviceClustering, |
| "num_interesting_visits_filter_threshold", |
| number_interesting_visits_filter_threshold); |
| } |
| |
| // The `kUseEngagementScoreCache` feature and child params. |
| { |
| engagement_score_cache_size = GetFieldTrialParamByFeatureAsInt( |
| features::kUseEngagementScoreCache, "engagement_score_cache_size", |
| engagement_score_cache_size); |
| |
| engagement_score_cache_refresh_duration = |
| base::Minutes(GetFieldTrialParamByFeatureAsInt( |
| features::kUseEngagementScoreCache, |
| "engagement_score_cache_refresh_duration_minutes", |
| engagement_score_cache_refresh_duration.InMinutes())); |
| } |
| |
| // The `kOnDeviceClusteringContentClustering` feature and child params. |
| { |
| content_clustering_enabled = base::FeatureList::IsEnabled( |
| features::kOnDeviceClusteringContentClustering); |
| |
| content_clustering_similarity_threshold = |
| GetFieldTrialParamByFeatureAsDouble( |
| features::kOnDeviceClusteringContentClustering, |
| "content_clustering_similarity_threshold", |
| content_clustering_similarity_threshold); |
| // Ensure that the value is [0.0 and 1.0]. |
| DCHECK_GE(content_clustering_similarity_threshold, 0.0f); |
| DCHECK_LE(content_clustering_similarity_threshold, 1.0f); |
| |
| exclude_entities_that_have_no_collections_from_content_clustering = |
| GetFieldTrialParamByFeatureAsBool( |
| features::kOnDeviceClusteringContentClustering, |
| "exclude_entities_that_have_no_collections", |
| exclude_entities_that_have_no_collections_from_content_clustering); |
| |
| collections_to_block_from_content_clustering = |
| JourneysCollectionContentClusteringBlocklist( |
| collections_to_block_from_content_clustering); |
| |
| use_pairwise_merge = GetFieldTrialParamByFeatureAsBool( |
| features::kOnDeviceClusteringContentClustering, "use_pairwise_merge", |
| use_pairwise_merge); |
| |
| max_pairwise_merge_iterations = GetFieldTrialParamByFeatureAsInt( |
| features::kOnDeviceClusteringContentClustering, |
| "max_pairwise_merge_iterations", max_pairwise_merge_iterations); |
| } |
| |
| // The `kHistoryClustersVisitDeduping` feature and child params. |
| { |
| use_host_for_visit_deduping = GetFieldTrialParamByFeatureAsBool( |
| internal::kHistoryClustersVisitDeduping, "use_host_for_visit_deduping", |
| use_host_for_visit_deduping); |
| } |
| |
| // The `kOnDeviceClusteringVisitRanking` feature and child params. |
| { |
| visit_duration_ranking_weight = GetFieldTrialParamByFeatureAsDouble( |
| features::kOnDeviceClusteringVisitRanking, |
| "visit_duration_ranking_weight", visit_duration_ranking_weight); |
| DCHECK_GE(visit_duration_ranking_weight, 0.0f); |
| |
| foreground_duration_ranking_weight = GetFieldTrialParamByFeatureAsDouble( |
| features::kOnDeviceClusteringVisitRanking, |
| "foreground_duration_ranking_weight", |
| foreground_duration_ranking_weight); |
| DCHECK_GE(foreground_duration_ranking_weight, 0.0f); |
| |
| bookmark_ranking_weight = GetFieldTrialParamByFeatureAsDouble( |
| features::kOnDeviceClusteringVisitRanking, "bookmark_ranking_weight", |
| bookmark_ranking_weight); |
| DCHECK_GE(bookmark_ranking_weight, 0.0f); |
| |
| search_results_page_ranking_weight = GetFieldTrialParamByFeatureAsDouble( |
| features::kOnDeviceClusteringVisitRanking, |
| "search_results_page_ranking_weight", |
| search_results_page_ranking_weight); |
| DCHECK_GE(search_results_page_ranking_weight, 0.0f); |
| } |
| |
| // The `kHistoryClustersNavigationContextClustering` feature and child params. |
| { |
| use_navigation_context_clusters = base::FeatureList::IsEnabled( |
| internal::kHistoryClustersNavigationContextClustering); |
| |
| context_clustering_clean_up_duration = |
| base::Minutes(GetFieldTrialParamByFeatureAsInt( |
| internal::kHistoryClustersNavigationContextClustering, |
| "clean_up_duration_minutes", |
| context_clustering_clean_up_duration.InMinutes())); |
| |
| cluster_triggerability_cutoff_duration = |
| base::Minutes(GetFieldTrialParamByFeatureAsInt( |
| internal::kHistoryClustersNavigationContextClustering, |
| "cluster_triggerability_cutoff_duration_minutes", |
| cluster_triggerability_cutoff_duration.InMinutes())); |
| } |
| |
| // WebUI features and params. |
| { |
| hide_visits = base::FeatureList::IsEnabled(internal::kHideVisits); |
| |
| hide_visits_icon = GetFieldTrialParamByFeatureAsBool( |
| internal::kHideVisits, "hide_visits_icon", hide_visits_icon); |
| } |
| |
| // Lonely features without child params. |
| { |
| non_user_visible_debug = |
| base::FeatureList::IsEnabled(internal::kNonUserVisibleDebug); |
| |
| user_visible_debug = |
| base::FeatureList::IsEnabled(internal::kUserVisibleDebug); |
| |
| persist_context_annotations_in_history_db = base::FeatureList::IsEnabled( |
| internal::kPersistContextAnnotationsInHistoryDb); |
| |
| history_clusters_internals_page = |
| base::FeatureList::IsEnabled(internal::kHistoryClustersInternalsPage); |
| |
| should_check_hosts_to_skip_clustering_for = |
| base::FeatureList::IsEnabled(features::kOnDeviceClusteringBlocklists); |
| |
| use_continue_on_shutdown = base::FeatureList::IsEnabled( |
| internal::kHistoryClustersUseContinueOnShutdown); |
| |
| should_show_all_clusters_unconditionally_on_prominent_ui_surfaces = |
| base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kShouldShowAllClustersOnProminentUiSurfaces); |
| |
| include_synced_visits = |
| base::FeatureList::IsEnabled(internal::kJourneysIncludeSyncedVisits); |
| } |
| } |
| |
| Config::Config(const Config& other) = default; |
| Config::~Config() = default; |
| |
| void SetConfigForTesting(const Config& config) { |
| GetConfigInternal() = config; |
| } |
| |
| base::flat_set<std::string> JourneysCollectionContentClusteringBlocklist( |
| const base::flat_set<std::string>& default_value) { |
| const base::FeatureParam<std::string> |
| kJourneysCollectionContentClusteringBlocklist{ |
| &features::kOnDeviceClusteringContentClustering, |
| "collections_blocklist", ""}; |
| std::string blocklist_string = |
| kJourneysCollectionContentClusteringBlocklist.Get(); |
| if (blocklist_string.empty()) |
| return default_value; |
| |
| auto blocklist = base::SplitString(blocklist_string, ",", |
| base::WhitespaceHandling::TRIM_WHITESPACE, |
| base::SplitResult::SPLIT_WANT_NONEMPTY); |
| |
| return blocklist.empty() |
| ? default_value |
| : base::flat_set<std::string>(blocklist.begin(), blocklist.end()); |
| } |
| |
| base::flat_set<std::string> JourneysMidBlocklist() { |
| const base::FeatureParam<std::string> kJourneysMidBlocklist{ |
| &internal::kHistoryClustersKeywordFiltering, "JourneysMidBlocklist", ""}; |
| std::string blocklist_string = kJourneysMidBlocklist.Get(); |
| if (blocklist_string.empty()) |
| return {}; |
| |
| auto blocklist = base::SplitString(blocklist_string, ",", |
| base::WhitespaceHandling::TRIM_WHITESPACE, |
| base::SplitResult::SPLIT_WANT_NONEMPTY); |
| |
| return blocklist.empty() |
| ? base::flat_set<std::string>() |
| : base::flat_set<std::string>(blocklist.begin(), blocklist.end()); |
| } |
| |
| bool IsApplicationLocaleSupportedByJourneys( |
| const std::string& application_locale) { |
| // Application locale support should be checked only if the Journeys feature |
| // is enabled. |
| DCHECK(GetConfig().is_journeys_enabled_no_locale_check); |
| |
| // Note, we now set a default value for the allowlist, which means that when |
| // the feature parameter is undefined, the below allowlist is enabled. |
| const base::FeatureParam<std::string> kLocaleOrLanguageAllowlist{ |
| &internal::kJourneys, "JourneysLocaleOrLanguageAllowlist", |
| "de:en:es:fr:it:nl:pt:tr"}; |
| |
| // To allow for using any locale, we also interpret the special '*' value. |
| auto allowlist_string = kLocaleOrLanguageAllowlist.Get(); |
| if (allowlist_string == "*") |
| return true; |
| |
| // Allow comma and colon as delimiters to the language list. |
| auto allowlist = base::SplitString( |
| allowlist_string, ",:", base::WhitespaceHandling::TRIM_WHITESPACE, |
| base::SplitResult::SPLIT_WANT_NONEMPTY); |
| |
| // Allow any exact locale matches, and also allow any users where the |
| // primary language subtag, e.g. "en" from "en-US" to match any element of |
| // the list. |
| return allowlist.empty() || base::Contains(allowlist, application_locale) || |
| base::Contains(allowlist, l10n_util::GetLanguage(application_locale)); |
| } |
| |
| bool IsJourneysEnabledInOmnibox(HistoryClustersService* service, |
| PrefService* prefs) { |
| if (!service) |
| return false; |
| |
| if (!service->IsJourneysEnabled()) |
| return false; |
| |
| if (!prefs->GetBoolean(history_clusters::prefs::kVisible)) |
| return false; |
| |
| return true; |
| } |
| |
| const Config& GetConfig() { |
| return GetConfigInternal(); |
| } |
| |
| } // namespace history_clusters |