1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "components/search/search.h"
7 #include "base/command_line.h"
8 #include "base/metrics/field_trial.h"
9 #include "base/strings/string_number_conversions.h"
10 #include "base/strings/string_split.h"
11 #include "base/strings/string_util.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "components/google/core/browser/google_util.h"
14 #include "components/search/search_switches.h"
15 #include "components/search_engines/template_url.h"
22 // Configuration options for Embedded Search.
23 // EmbeddedSearch field trials are named in such a way that we can parse out
24 // the experiment configuration from the trial's group name in order to give
25 // us maximum flexability in running experiments.
26 // Field trial groups should be named things like "Group7 espv:2 instant:1".
27 // The first token is always GroupN for some integer N, followed by a
28 // space-delimited list of key:value pairs which correspond to these flags:
29 const char kEmbeddedPageVersionFlagName
[] = "espv";
32 const uint64 kEmbeddedPageVersionDefault
= 1;
33 #elif defined(OS_ANDROID)
34 const uint64 kEmbeddedPageVersionDefault
= 1;
35 // Use this variant to enable EmbeddedSearch SearchBox API in the results page.
36 const uint64 kEmbeddedSearchEnabledVersion
= 2;
38 const uint64 kEmbeddedPageVersionDefault
= 2;
41 // Constants for the field trial name and group prefix.
42 // Note in M30 and below this field trial was named "InstantExtended" and in
43 // M31 was renamed to EmbeddedSearch for clarity and cleanliness. Since we
44 // can't easilly sync up Finch configs with the pushing of this change to
45 // Dev & Canary, for now the code accepts both names.
46 // TODO(dcblack): Remove the InstantExtended name once M31 hits the Beta
48 const char kInstantExtendedFieldTrialName
[] = "InstantExtended";
49 const char kEmbeddedSearchFieldTrialName
[] = "EmbeddedSearch";
51 // If the field trial's group name ends with this string its configuration will
52 // be ignored and Instant Extended will not be enabled by default.
53 const char kDisablingSuffix
[] = "DISABLED";
55 #if !defined(OS_IOS) && !defined(OS_ANDROID)
56 const char kEnableQueryExtractionFlagName
[] = "query_extraction";
59 const char kAllowPrefetchNonDefaultMatch
[] = "allow_prefetch_non_default_match";
61 #if defined(OS_ANDROID)
62 const char kPrefetchSearchResultsFlagName
[] = "prefetch_results";
64 // Controls whether to reuse prerendered Instant Search base page to commit any
66 const char kReuseInstantSearchBasePage
[] = "reuse_instant_search_base_page";
71 // Negative start-margin values prevent the "es_sm" parameter from being used.
72 const int kDisableStartMargin
= -1;
74 bool IsInstantExtendedAPIEnabled() {
77 #elif defined(OS_ANDROID)
78 return EmbeddedSearchPageVersion() == kEmbeddedSearchEnabledVersion
;
81 #endif // defined(OS_IOS)
84 // Determine what embedded search page version to request from the user's
85 // default search provider. If 0, the embedded search UI should not be enabled.
86 uint64
EmbeddedSearchPageVersion() {
87 #if defined(OS_ANDROID)
88 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
89 switches::kEnableEmbeddedSearchAPI
)) {
90 return kEmbeddedSearchEnabledVersion
;
94 FieldTrialFlags flags
;
95 if (GetFieldTrialInfo(&flags
)) {
96 return GetUInt64ValueForFlagWithDefault(kEmbeddedPageVersionFlagName
,
97 kEmbeddedPageVersionDefault
,
100 return kEmbeddedPageVersionDefault
;
103 bool GetFieldTrialInfo(FieldTrialFlags
* flags
) {
104 // Get the group name. If the EmbeddedSearch trial doesn't exist, look for
105 // the older InstantExtended name.
106 std::string group_name
= base::FieldTrialList::FindFullName(
107 kEmbeddedSearchFieldTrialName
);
108 if (group_name
.empty()) {
109 group_name
= base::FieldTrialList::FindFullName(
110 kInstantExtendedFieldTrialName
);
113 if (base::EndsWith(group_name
, kDisablingSuffix
,
114 base::CompareCase::SENSITIVE
))
117 // We have a valid trial that isn't disabled. Extract the flags.
118 std::string
group_prefix(group_name
);
119 size_t first_space
= group_name
.find(" ");
120 if (first_space
!= std::string::npos
) {
121 // There is a flags section of the group name. Split that out and parse it.
122 group_prefix
= group_name
.substr(0, first_space
);
123 if (!base::SplitStringIntoKeyValuePairs(group_name
.substr(first_space
),
125 // Failed to parse the flags section. Assume the whole group name is
133 // Given a FieldTrialFlags object, returns the string value of the provided
135 std::string
GetStringValueForFlagWithDefault(const std::string
& flag
,
136 const std::string
& default_value
,
137 const FieldTrialFlags
& flags
) {
138 FieldTrialFlags::const_iterator i
;
139 for (i
= flags
.begin(); i
!= flags
.end(); i
++) {
140 if (i
->first
== flag
)
143 return default_value
;
146 // Given a FieldTrialFlags object, returns the uint64 value of the provided
148 uint64
GetUInt64ValueForFlagWithDefault(const std::string
& flag
,
149 uint64 default_value
,
150 const FieldTrialFlags
& flags
) {
152 std::string str_value
=
153 GetStringValueForFlagWithDefault(flag
, std::string(), flags
);
154 if (base::StringToUint64(str_value
, &value
))
156 return default_value
;
159 // Given a FieldTrialFlags object, returns the boolean value of the provided
161 bool GetBoolValueForFlagWithDefault(const std::string
& flag
,
163 const FieldTrialFlags
& flags
) {
164 return !!GetUInt64ValueForFlagWithDefault(flag
, default_value
? 1 : 0, flags
);
167 std::string
InstantExtendedEnabledParam(bool for_search
) {
168 if (for_search
&& !IsQueryExtractionEnabled())
169 return std::string();
170 return std::string(google_util::kInstantExtendedAPIParam
) + "=" +
171 base::Uint64ToString(EmbeddedSearchPageVersion()) + "&";
174 std::string
ForceInstantResultsParam(bool for_prerender
) {
175 return (for_prerender
|| !IsInstantExtendedAPIEnabled()) ? "ion=1&"
179 bool IsQueryExtractionEnabled() {
180 #if defined(OS_IOS) || defined(OS_ANDROID)
183 if (!IsInstantExtendedAPIEnabled())
186 const base::CommandLine
* command_line
=
187 base::CommandLine::ForCurrentProcess();
188 if (command_line
->HasSwitch(switches::kEnableQueryExtraction
))
191 FieldTrialFlags flags
;
192 return GetFieldTrialInfo(&flags
) &&
193 GetBoolValueForFlagWithDefault(kEnableQueryExtractionFlagName
, false,
195 #endif // defined(OS_IOS) || defined(OS_ANDROID)
198 bool ShouldPrefetchSearchResults() {
199 if (!IsInstantExtendedAPIEnabled())
202 #if defined(OS_ANDROID)
203 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
204 switches::kPrefetchSearchResults
)) {
208 FieldTrialFlags flags
;
209 return GetFieldTrialInfo(&flags
) &&
210 GetBoolValueForFlagWithDefault(kPrefetchSearchResultsFlagName
, false,
217 bool ShouldReuseInstantSearchBasePage() {
218 if (!ShouldPrefetchSearchResults())
221 #if defined(OS_ANDROID)
222 FieldTrialFlags flags
;
223 return GetFieldTrialInfo(&flags
) &&
224 GetBoolValueForFlagWithDefault(kReuseInstantSearchBasePage
, false,
231 bool ShouldAllowPrefetchNonDefaultMatch() {
232 if (!ShouldPrefetchSearchResults())
235 FieldTrialFlags flags
;
236 return GetFieldTrialInfo(&flags
) &&
237 GetBoolValueForFlagWithDefault(kAllowPrefetchNonDefaultMatch
, false,
241 // |url| should either have a secure scheme or have a non-HTTPS base URL that
242 // the user specified using --google-base-url. (This allows testers to use
243 // --google-base-url to point at non-HTTPS servers, which eases testing.)
244 bool IsSuitableURLForInstant(const GURL
& url
, const TemplateURL
* template_url
) {
245 return template_url
->HasSearchTermsReplacementKey(url
) &&
246 (url
.SchemeIsCryptographic() ||
247 google_util::StartsWithCommandLineGoogleBaseURL(url
));
250 void EnableQueryExtractionForTesting() {
251 #if !defined(OS_IOS) && !defined(OS_ANDROID)
252 base::CommandLine
* cl
= base::CommandLine::ForCurrentProcess();
253 cl
->AppendSwitch(switches::kEnableQueryExtraction
);
257 } // namespace search