blob: b1a9c991d2b50283ea009f047a49b20ca4e96270 [file] [log] [blame]
// Copyright 2014 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/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/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/sys_info.h"
#include "base/time/time.h"
#include "components/omnibox/browser/omnibox_switches.h"
#include "components/omnibox/browser/url_index_private_data.h"
#include "components/prefs/pref_service.h"
#include "components/search/search.h"
#include "components/variations/active_field_trials.h"
#include "components/variations/metrics_util.h"
#include "components/variations/variations_associated_data.h"
#include "third_party/metrics_proto/omnibox_event.pb.h"
#if defined(OS_ANDROID)
#include "components/omnibox/browser/omnibox_pref_names.h"
#include "components/pref_registry/pref_registry_syncable.h"
#endif
using metrics::OmniboxEventProto;
namespace omnibox {
// Feature used to enable the new set of answers in suggest types (currency,
// dictionary, sports, translation, when is). Note that the state of this
// Feature is not consulted anywhere in the code. It is only used to force a
// Finch experiment arm which sends an experiment ID to GWS which triggers
// serving the new types.
const base::Feature kNewOmniboxAnswerTypes{"NewOmniboxAnswerTypes",
base::FEATURE_DISABLED_BY_DEFAULT};
// Feature used to enable the transmission of entity suggestions from GWS
// to this client.
const base::Feature kOmniboxEntitySuggestions{
"OmniboxEntitySuggestions", base::FEATURE_DISABLED_BY_DEFAULT};
// Feature used to force on the experiment of transmission of tail suggestions
// from GWS to this client, currently testing for desktop.
const base::Feature kOmniboxTailSuggestions{
"OmniboxTailSuggestions", base::FEATURE_DISABLED_BY_DEFAULT};
// Feature used to enable the identification of open tabs given URLs in
// suggestions, and converting those suggestions to ones that allow switching to
// the tab if found. Currently only on the desktop.
const base::Feature kOmniboxTabSwitchSuggestions{
"OmniboxTabSwitchSuggestions", base::FEATURE_DISABLED_BY_DEFAULT};
// Feature used to enable clipboard provider, which provides the user with
// suggestions of the URL in the user's clipboard (if any) upon omnibox focus.
const base::Feature kEnableClipboardProvider {
"OmniboxEnableClipboardProvider",
#if defined(OS_IOS) || defined(OS_ANDROID)
base::FEATURE_ENABLED_BY_DEFAULT
#else
base::FEATURE_DISABLED_BY_DEFAULT
#endif
};
// Feature to enable personalized omnibox suggestions on focus when Android's
// Chrome Home feature is enabled.
const base::Feature kAndroidChromeHomePersonalizedSuggestions{
"ChromeHomePersonalizedOmniboxSuggestions",
base::FEATURE_DISABLED_BY_DEFAULT};
// Feature to enable the search provider to send a request to the suggest
// server on focus. This allows the suggest server to warm up, by, for
// example, loading per-user models into memory. Having a per-user model
// in memory allows the suggest server to respond more quickly with
// personalized suggestions as the user types.
const base::Feature kSearchProviderWarmUpOnFocus{
"OmniboxWarmUpSearchProviderOnFocus",
#if defined(OS_IOS)
base::FEATURE_DISABLED_BY_DEFAULT
#else
base::FEATURE_ENABLED_BY_DEFAULT
#endif
};
// Feature used to enable the transmission of HTTPS URLs as part of the
// context to the suggest server (assuming SearchProvider is permitted to
// transmit URLs for context in the first place).
const base::Feature kSearchProviderContextAllowHttpsUrls{
"OmniboixSearchProviderContextAllowHttpsUrls",
base::FEATURE_ENABLED_BY_DEFAULT};
// Feature used for the Zero Suggest Redirect to Chrome Field Trial.
const base::Feature kZeroSuggestRedirectToChrome{
"ZeroSuggestRedirectToChrome", base::FEATURE_DISABLED_BY_DEFAULT};
// Feature used to swap the title and URL when providing zero suggest
// suggestions.
const base::Feature kZeroSuggestSwapTitleAndUrl{
"ZeroSuggestSwapTitleAndUrl", base::FEATURE_DISABLED_BY_DEFAULT};
// Feature used to display the title of the current URL match.
const base::Feature kDisplayTitleForCurrentUrl{
"OmniboxDisplayTitleForCurrentUrl", base::FEATURE_DISABLED_BY_DEFAULT};
// Feature used for the max autocomplete matches UI experiment.
const base::Feature kUIExperimentMaxAutocompleteMatches{
"OmniboxUIExperimentMaxAutocompleteMatches",
base::FEATURE_DISABLED_BY_DEFAULT};
// Feature used for eliding the suggestion URL after the host as a UI
// experiment.
const base::Feature kUIExperimentElideSuggestionUrlAfterHost{
"OmniboxUIExperimentElideSuggestionUrlAfterHost",
base::FEATURE_DISABLED_BY_DEFAULT};
// Feature used for hiding the suggestion URL scheme as a UI experiment.
const base::Feature kUIExperimentHideSuggestionUrlScheme{
"OmniboxUIExperimentHideSuggestionUrlScheme",
base::FEATURE_DISABLED_BY_DEFAULT};
// Feature used for hiding the suggestion URL subdomain as a UI experiment.
// This only hides some trivially informative subdomains such as "www" or "m".
const base::Feature kUIExperimentHideSuggestionUrlTrivialSubdomains{
"OmniboxUIExperimentHideSuggestionUrlTrivialSubdomains",
base::FEATURE_DISABLED_BY_DEFAULT};
// Feature used for the omnibox narrow suggestions dropdown UI experiment.
const base::Feature kUIExperimentNarrowDropdown{
"OmniboxUIExperimentNarrowDropdown", base::FEATURE_DISABLED_BY_DEFAULT};
// Feature used for showing the URL suggestion favicons as a UI experiment.
const base::Feature kUIExperimentShowSuggestionFavicons{
"OmniboxUIExperimentShowSuggestionFavicons",
base::FEATURE_DISABLED_BY_DEFAULT};
// Feature used to always swap the title and URL.
const base::Feature kUIExperimentSwapTitleAndUrl{
"OmniboxUIExperimentSwapTitleAndUrl", base::FEATURE_DISABLED_BY_DEFAULT};
// Feature used for the vertical margin UI experiment.
const base::Feature kUIExperimentVerticalLayout{
"OmniboxUIExperimentVerticalLayout", base::FEATURE_DISABLED_BY_DEFAULT};
// Feature used for the vertical margin UI experiment.
const base::Feature kUIExperimentVerticalMargin{
"OmniboxUIExperimentVerticalMargin", base::FEATURE_DISABLED_BY_DEFAULT};
// Feature used to enable speculatively starting a service worker associated
// with the destination of the default match when the user's input looks like a
// query.
const base::Feature kSpeculativeServiceWorkerStartOnQueryInput{
"OmniboxSpeculativeServiceWorkerStartOnQueryInput",
#if defined(OS_ANDROID)
base::FEATURE_ENABLED_BY_DEFAULT
#else
base::FEATURE_DISABLED_BY_DEFAULT
#endif
};
#if defined(OS_IOS)
// Feature used to enable ZeroSuggestProvider on iOS.
const base::Feature kZeroSuggestProviderIOS{"ZeroSuggestProviderIOS",
base::FEATURE_DISABLED_BY_DEFAULT};
#endif
} // namespace omnibox
namespace {
typedef std::map<std::string, std::string> VariationParams;
typedef HUPScoringParams::ScoreBuckets ScoreBuckets;
// Field trial names.
const char kStopTimerFieldTrialName[] = "OmniboxStopTimer";
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) {
VariationParams::const_iterator 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);
}
}
} // 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() {
}
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::TimeDelta::FromDays(half_life_days_).InMillisecondsF();
return pow(2.0, -half_life_intervals);
}
#if defined(OS_ANDROID)
// static
void OmniboxFieldTrial::RegisterProfilePrefs(
user_prefs::PrefRegistrySyncable* registry) {
registry->RegisterBooleanPref(omnibox::kZeroSuggestChromeHomePersonalized,
false);
}
#endif
int OmniboxFieldTrial::GetDisabledProviderTypes() {
const std::string& types_string = variations::GetVariationParamValue(
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(
metrics::HashName(kBundledExperimentFieldTrialName));
}
}
base::TimeDelta OmniboxFieldTrial::StopTimerFieldTrialDuration() {
int stop_timer_ms;
if (base::StringToInt(
base::FieldTrialList::FindFullName(kStopTimerFieldTrialName),
&stop_timer_ms))
return base::TimeDelta::FromMilliseconds(stop_timer_ms);
return base::TimeDelta::FromMilliseconds(1500);
}
bool OmniboxFieldTrial::InZeroSuggestMostVisitedFieldTrial(PrefService* prefs) {
return InZeroSuggestMostVisitedWithoutSerpFieldTrial(prefs) ||
variations::GetVariationParamValue(
kBundledExperimentFieldTrialName,
kZeroSuggestVariantRule) == "MostVisited";
}
bool OmniboxFieldTrial::InZeroSuggestMostVisitedWithoutSerpFieldTrial(
PrefService* prefs) {
std::string variant(variations::GetVariationParamValue(
kBundledExperimentFieldTrialName,
kZeroSuggestVariantRule));
if (variant == "MostVisitedWithoutSERP")
return true;
#if defined(OS_ANDROID)
// Android defaults to MostVisitedWithoutSERP
if (variant.empty() && !InChromeHomePersonalizedZeroSuggest(prefs))
return true;
return false;
#elif defined(OS_IOS)
// iOS defaults to MostVisitedWithoutSERP
return variant.empty();
#else
return false;
#endif
}
bool OmniboxFieldTrial::InZeroSuggestPersonalizedFieldTrial(
PrefService* prefs) {
std::string variant(variations::GetVariationParamValue(
kBundledExperimentFieldTrialName, kZeroSuggestVariantRule));
if (variant == "Personalized")
return true;
#if defined(OS_ANDROID)
if (variant.empty() && InChromeHomePersonalizedZeroSuggest(prefs))
return true;
return false;
#else
return false;
#endif
}
// static
int OmniboxFieldTrial::GetZeroSuggestRedirectToChromeExperimentId() {
return base::GetFieldTrialParamByFeatureAsInt(
omnibox::kZeroSuggestRedirectToChrome,
kZeroSuggestRedirectToChromeExperimentIdParam,
/*default_value=*/-1);
}
// static
std::string OmniboxFieldTrial::GetZeroSuggestRedirectToChromeServerAddress() {
return base::GetFieldTrialParamValueByFeature(
omnibox::kZeroSuggestRedirectToChrome,
kZeroSuggestRedirectToChromeServerAddressParam);
}
bool OmniboxFieldTrial::ShortcutsScoringMaxRelevance(
OmniboxEventProto::PageClassification current_page_classification,
int* max_relevance) {
// The value of the rule is a string that encodes an integer containing
// the max relevance.
const std::string& max_relevance_str =
OmniboxFieldTrial::GetValueForRuleInContext(
kShortcutsScoringMaxRelevanceRule, current_page_classification);
if (max_relevance_str.empty())
return false;
if (!base::StringToInt(max_relevance_str, max_relevance))
return false;
return true;
}
bool OmniboxFieldTrial::SearchHistoryPreventInlining(
OmniboxEventProto::PageClassification current_page_classification) {
return OmniboxFieldTrial::GetValueForRuleInContext(
kSearchHistoryRule, current_page_classification) == "PreventInlining";
}
bool OmniboxFieldTrial::SearchHistoryDisable(
OmniboxEventProto::PageClassification current_page_classification) {
return OmniboxFieldTrial::GetValueForRuleInContext(
kSearchHistoryRule, current_page_classification) == "Disable";
}
void OmniboxFieldTrial::GetDemotionsByType(
OmniboxEventProto::PageClassification current_page_classification,
DemotionMultipliers* demotions_by_type) {
demotions_by_type->clear();
std::string demotion_rule = OmniboxFieldTrial::GetValueForRuleInContext(
kDemoteByTypeRule, current_page_classification);
// If there is no demotion rule for this context, then use the default
// value for that context.
if (demotion_rule.empty()) {
// This rule demotes URLs as strongly as possible without violating user
// expectations. In particular, for URL-seeking inputs, if the user would
// likely expect a URL first (i.e., it would be inline autocompleted), then
// that URL will still score strongly enough to be first. This is done
// using a demotion multipler of 0.61. If a URL would get a score high
// enough to be inline autocompleted (1400+), even after demotion it will
// score above 850 ( 1400 * 0.61 > 850). 850 is the maximum score for
// queries when the input has been detected as URL-seeking.
constexpr char kDemoteURLs[] = "1:61,2:61,3:61,4:61,16:61";
#if defined(OS_ANDROID)
if (current_page_classification == OmniboxEventProto::
SEARCH_RESULT_PAGE_NO_SEARCH_TERM_REPLACEMENT)
demotion_rule = kDemoteURLs;
#endif
if (current_page_classification ==
OmniboxEventProto::INSTANT_NTP_WITH_FAKEBOX_AS_STARTING_FOCUS)
demotion_rule = kDemoteURLs;
}
// The value of the DemoteByType rule is a comma-separated list of
// {ResultType + ":" + Number} where ResultType is an AutocompleteMatchType::
// Type enum represented as an integer and Number is an integer number
// between 0 and 100 inclusive. Relevance scores of matches of that result
// type are multiplied by Number / 100. 100 means no change.
base::StringPairs kv_pairs;
if (base::SplitStringIntoKeyValuePairs(demotion_rule, ':', ',', &kv_pairs)) {
for (base::StringPairs::const_iterator it = kv_pairs.begin();
it != kv_pairs.end(); ++it) {
// 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.
int k, v;
base::StringToInt(it->first, &k);
base::StringToInt(it->second, &v);
(*demotions_by_type)[static_cast<AutocompleteMatchType::Type>(k)] =
static_cast<float>(v) / 100.0f;
}
}
}
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 (!variations::GetVariationParams(kBundledExperimentFieldTrialName,
&params))
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 =
variations::GetVariationParamValue(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 variations::GetVariationParamValue(
kBundledExperimentFieldTrialName,
kHQPAllowMatchInTLDRule) == "true";
}
bool OmniboxFieldTrial::HQPAllowMatchInSchemeValue() {
return variations::GetVariationParamValue(
kBundledExperimentFieldTrialName,
kHQPAllowMatchInSchemeRule) == "true";
}
bool OmniboxFieldTrial::DisableResultsCaching() {
return variations::GetVariationParamValue(
kBundledExperimentFieldTrialName,
kDisableResultsCachingRule) == "true";
}
void OmniboxFieldTrial::GetSuggestPollingStrategy(bool* from_last_keystroke,
int* polling_delay_ms) {
*from_last_keystroke = variations::GetVariationParamValue(
kBundledExperimentFieldTrialName,
kMeasureSuggestPollingDelayFromLastKeystrokeRule) == "true";
const std::string& polling_delay_string = variations::GetVariationParamValue(
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 variations::GetVariationParamValue(
kBundledExperimentFieldTrialName,
kHQPExperimentalScoringBucketsParam);
}
float OmniboxFieldTrial::HQPExperimentalTopicalityThreshold() {
std::string topicality_threshold_str = variations::GetVariationParamValue(
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() {
const char* param = kMaxNumHQPUrlsIndexedAtStartupOnNonLowEndDevicesParam;
if (base::SysInfo::IsLowEndDevice())
param = kMaxNumHQPUrlsIndexedAtStartupOnLowEndDevicesParam;
std::string param_value(variations::GetVariationParamValue(
kBundledExperimentFieldTrialName, param));
int num_urls;
if (base::StringToInt(param_value, &num_urls))
return num_urls;
// Default value is set to -1 for unlimited number of urls.
return -1;
}
size_t OmniboxFieldTrial::HQPMaxVisitsToScore() {
std::string max_visits_str = variations::GetVariationParamValue(
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 = variations::GetVariationParamValue(
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 = variations::GetVariationParamValue(
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(
variations::GetVariationParamValue(kBundledExperimentFieldTrialName,
kHQPNumTitleWordsRule),
&num_title_words))
return 20;
return num_title_words;
}
bool OmniboxFieldTrial::HQPAlsoDoHUPLikeScoring() {
return variations::GetVariationParamValue(
kBundledExperimentFieldTrialName,
kHQPAlsoDoHUPLikeScoringRule) == "true";
}
bool OmniboxFieldTrial::HUPSearchDatabase() {
const std::string& value = variations::GetVariationParamValue(
kBundledExperimentFieldTrialName,
kHUPSearchDatabaseRule);
return value.empty() || (value == "true");
}
bool OmniboxFieldTrial::KeywordRequiresRegistry() {
const std::string& value = variations::GetVariationParamValue(
kBundledExperimentFieldTrialName,
kKeywordRequiresRegistryRule);
return value.empty() || (value == "true");
}
bool OmniboxFieldTrial::KeywordRequiresPrefixMatch() {
const std::string& value = variations::GetVariationParamValue(
kBundledExperimentFieldTrialName,
kKeywordRequiresPrefixMatchRule);
return value.empty() || (value == "true");
}
int OmniboxFieldTrial::KeywordScoreForSufficientlyCompleteMatch() {
std::string value_str = variations::GetVariationParamValue(
kBundledExperimentFieldTrialName,
kKeywordScoreForSufficientlyCompleteMatchRule);
if (value_str.empty())
return -1;
// 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.
int value;
base::StringToInt(value_str, &value);
return value;
}
OmniboxFieldTrial::EmphasizeTitlesCondition
OmniboxFieldTrial::GetEmphasizeTitlesConditionForInput(
const AutocompleteInput& input) {
// Check the feature that always swaps title and URL (assuming the title is
// non-empty).
if (base::FeatureList::IsEnabled(omnibox::kUIExperimentSwapTitleAndUrl))
return EMPHASIZE_WHEN_NONEMPTY;
// Check the feature that swaps the title and URL only for zero suggest
// suggestions.
if (input.from_omnibox_focus() &&
base::FeatureList::IsEnabled(omnibox::kZeroSuggestSwapTitleAndUrl))
return EMPHASIZE_WHEN_NONEMPTY;
// Look up the parameter named kEmphasizeTitlesRule + "_" + input.type(),
// find its value, and return that value as an enum. If the parameter
// isn't redefined, fall back to the generic rule kEmphasizeTitlesRule + "_*"
std::string value_str(variations::GetVariationParamValue(
kBundledExperimentFieldTrialName,
std::string(kEmphasizeTitlesRule) + "_" +
base::IntToString(static_cast<int>(input.type()))));
if (value_str.empty()) {
value_str = variations::GetVariationParamValue(
kBundledExperimentFieldTrialName,
std::string(kEmphasizeTitlesRule) + "_*");
}
if (value_str.empty())
return EMPHASIZE_NEVER;
// 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.
int value;
base::StringToInt(value_str, &value);
return static_cast<EmphasizeTitlesCondition>(value);
}
// static
bool OmniboxFieldTrial::InPhysicalWebZeroSuggestFieldTrial() {
return variations::GetVariationParamValue(kBundledExperimentFieldTrialName,
kPhysicalWebZeroSuggestRule) ==
"true";
}
// static
bool OmniboxFieldTrial::InPhysicalWebAfterTypingFieldTrial() {
return variations::GetVariationParamValue(kBundledExperimentFieldTrialName,
kPhysicalWebAfterTypingRule) ==
"true";
}
// static
int OmniboxFieldTrial::GetPhysicalWebZeroSuggestBaseRelevance() {
std::string param_value(variations::GetVariationParamValue(
kBundledExperimentFieldTrialName,
kPhysicalWebZeroSuggestBaseRelevanceParam));
int base_relevance;
if (!param_value.empty() && base::StringToInt(param_value, &base_relevance))
return base_relevance;
// Default relevance score of the first Physical Web URL autocomplete match
// when the user has not typed in the omnibox. This score is intended to be
// between ClipboardURLProvider and ZeroSuggestProvider.
return 700;
}
// static
int OmniboxFieldTrial::GetPhysicalWebAfterTypingBaseRelevance() {
std::string param_value(variations::GetVariationParamValue(
kBundledExperimentFieldTrialName,
kPhysicalWebAfterTypingBaseRelevanceParam));
int base_relevance;
if (!param_value.empty() && base::StringToInt(param_value, &base_relevance))
return base_relevance;
// Default relevance score of the first Physical Web URL autocomplete match
// when the user is typing in the omnibox.
return 700;
}
const char OmniboxFieldTrial::kBundledExperimentFieldTrialName[] =
"OmniboxBundledExperimentV1";
const char OmniboxFieldTrial::kDisableProvidersRule[] = "DisableProviders";
const char OmniboxFieldTrial::kShortcutsScoringMaxRelevanceRule[] =
"ShortcutsScoringMaxRelevance";
const char OmniboxFieldTrial::kSearchHistoryRule[] = "SearchHistory";
const char OmniboxFieldTrial::kDemoteByTypeRule[] = "DemoteByType";
const char OmniboxFieldTrial::kHQPBookmarkValueRule[] =
"HQPBookmarkValue";
const char OmniboxFieldTrial::kHQPTypedValueRule[] = "HQPTypedValue";
const char OmniboxFieldTrial::kHQPAllowMatchInTLDRule[] = "HQPAllowMatchInTLD";
const char OmniboxFieldTrial::kHQPAllowMatchInSchemeRule[] =
"HQPAllowMatchInScheme";
const char OmniboxFieldTrial::kZeroSuggestVariantRule[] = "ZeroSuggestVariant";
const char OmniboxFieldTrial::kDisableResultsCachingRule[] =
"DisableResultsCaching";
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::kKeywordRequiresRegistryRule[] =
"KeywordRequiresRegistry";
const char OmniboxFieldTrial::kKeywordRequiresPrefixMatchRule[] =
"KeywordRequiresPrefixMatch";
const char OmniboxFieldTrial::kKeywordScoreForSufficientlyCompleteMatchRule[] =
"KeywordScoreForSufficientlyCompleteMatch";
const char OmniboxFieldTrial::kEmphasizeTitlesRule[] = "EmphasizeTitles";
const char OmniboxFieldTrial::kPhysicalWebZeroSuggestRule[] =
"PhysicalWebZeroSuggest";
const char OmniboxFieldTrial::kPhysicalWebAfterTypingRule[] =
"PhysicalWebAfterTyping";
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::kPhysicalWebZeroSuggestBaseRelevanceParam[] =
"PhysicalWebZeroSuggestBaseRelevance";
const char OmniboxFieldTrial::kPhysicalWebAfterTypingBaseRelevanceParam[] =
"PhysicalWebAfterTypingBaseRelevanceParam";
const char OmniboxFieldTrial::kUIMaxAutocompleteMatchesParam[] =
"UIMaxAutocompleteMatches";
const char OmniboxFieldTrial::kUIVerticalMarginParam[] = "UIVerticalMargin";
const char OmniboxFieldTrial::kZeroSuggestRedirectToChromeExperimentIdParam[] =
"ZeroSuggestRedirectToChromeExperimentID";
const char OmniboxFieldTrial::kZeroSuggestRedirectToChromeServerAddressParam[] =
"ZeroSuggestRedirectToChromeServerAddress";
// static
int OmniboxFieldTrial::kDefaultMinimumTimeBetweenSuggestQueriesMs = 100;
// Background and implementation details:
//
// Each experiment group in any field trial can come with an optional set of
// parameters (key-value pairs). In the bundled omnibox experiment
// (kBundledExperimentFieldTrialName), each experiment group comes with a
// list of parameters in the form:
// key=<Rule>:
// <OmniboxEventProto::PageClassification (as an int)>:
// <whether Instant Extended is enabled (as a 1 or 0)>
// (note that there are no linebreaks in keys; this format is for
// presentation only>
// value=<arbitrary string>
// Both the OmniboxEventProto::PageClassification and the Instant Extended
// entries can be "*", which means this rule applies for all values of the
// matching portion of the context.
// One example parameter is
// key=SearchHistory:6:1
// value=PreventInlining
// This means in page classification context 6 (a search result page doing
// search term replacement) with Instant Extended enabled, the SearchHistory
// experiment should PreventInlining.
//
// When an exact match to the rule in the current context is missing, we
// give preference to a wildcard rule that matches the instant extended
// context over a wildcard rule that matches the page classification
// context. Hopefully, though, users will write their field trial configs
// so as not to rely on this fall back order.
//
// In short, this function tries to find the value associated with key
// |rule|:|page_classification|:|instant_extended|, failing that it looks up
// |rule|:*:|instant_extended|, failing that it looks up
// |rule|:|page_classification|:*, failing that it looks up |rule|:*:*,
// and failing that it returns the empty string.
std::string OmniboxFieldTrial::GetValueForRuleInContext(
const std::string& rule,
OmniboxEventProto::PageClassification page_classification) {
VariationParams params;
if (!variations::GetVariationParams(kBundledExperimentFieldTrialName,
&params)) {
return std::string();
}
const std::string page_classification_str =
base::IntToString(static_cast<int>(page_classification));
const std::string instant_extended =
search::IsInstantExtendedAPIEnabled() ? "1" : "0";
// Look up rule in this exact context.
VariationParams::const_iterator it = params.find(
rule + ":" + page_classification_str + ":" + instant_extended);
if (it != params.end())
return it->second;
// Fall back to the global page classification context.
it = params.find(rule + ":*:" + instant_extended);
if (it != params.end())
return it->second;
// Fall back to the global instant extended context.
it = params.find(rule + ":" + page_classification_str + ":*");
if (it != params.end())
return it->second;
// Look up rule in the global context.
it = params.find(rule + ":*:*");
return (it != params.end()) ? it->second : std::string();
}
#if defined(OS_ANDROID)
// static
bool OmniboxFieldTrial::InChromeHomePersonalizedZeroSuggest(
PrefService* prefs) {
// Android's Java code sets a preference controlling whether personalized
// suggestions are enabled based on whether Chrome Home is enabled and
// whether the |kAndroidChromeHomePersonalizedSuggestions| feature is
// enabled.
return prefs->GetBoolean(omnibox::kZeroSuggestChromeHomePersonalized) &&
base::FeatureList::IsEnabled(
omnibox::kAndroidChromeHomePersonalizedSuggestions);
}
#endif