[Metrics] Make MetricsStateManager take a callback param to check if UMA is enabled.
[chromium-blink-merge.git] / chrome / browser / autocomplete / autocomplete_match.cc
blob6911e44bd07ac8a03ee2715575240a3b4e46e422
1 // Copyright (c) 2012 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 "chrome/browser/autocomplete/autocomplete_match.h"
7 #include "base/i18n/time_formatting.h"
8 #include "base/logging.h"
9 #include "base/strings/string16.h"
10 #include "base/strings/string_number_conversions.h"
11 #include "base/strings/string_util.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "base/time/time.h"
14 #include "chrome/browser/autocomplete/autocomplete_provider.h"
15 #include "chrome/browser/search_engines/template_url.h"
16 #include "chrome/browser/search_engines/template_url_service.h"
17 #include "chrome/browser/search_engines/template_url_service_factory.h"
18 #include "content/public/common/url_constants.h"
19 #include "grit/theme_resources.h"
21 namespace {
23 bool IsTrivialClassification(const ACMatchClassifications& classifications) {
24 return classifications.empty() ||
25 ((classifications.size() == 1) &&
26 (classifications.back().style == ACMatchClassification::NONE));
29 } // namespace
31 // AutocompleteMatch ----------------------------------------------------------
33 // static
34 const base::char16 AutocompleteMatch::kInvalidChars[] = {
35 '\n', '\r', '\t',
36 0x2028, // Line separator
37 0x2029, // Paragraph separator
41 AutocompleteMatch::AutocompleteMatch()
42 : provider(NULL),
43 relevance(0),
44 typed_count(-1),
45 deletable(false),
46 allowed_to_be_default_match(false),
47 transition(content::PAGE_TRANSITION_GENERATED),
48 is_history_what_you_typed_match(false),
49 type(AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED),
50 starred(false),
51 from_previous(false) {
54 AutocompleteMatch::AutocompleteMatch(AutocompleteProvider* provider,
55 int relevance,
56 bool deletable,
57 Type type)
58 : provider(provider),
59 relevance(relevance),
60 typed_count(-1),
61 deletable(deletable),
62 allowed_to_be_default_match(false),
63 transition(content::PAGE_TRANSITION_TYPED),
64 is_history_what_you_typed_match(false),
65 type(type),
66 starred(false),
67 from_previous(false) {
70 AutocompleteMatch::AutocompleteMatch(const AutocompleteMatch& match)
71 : provider(match.provider),
72 relevance(match.relevance),
73 typed_count(match.typed_count),
74 deletable(match.deletable),
75 fill_into_edit(match.fill_into_edit),
76 inline_autocompletion(match.inline_autocompletion),
77 allowed_to_be_default_match(match.allowed_to_be_default_match),
78 destination_url(match.destination_url),
79 stripped_destination_url(match.stripped_destination_url),
80 contents(match.contents),
81 contents_class(match.contents_class),
82 description(match.description),
83 description_class(match.description_class),
84 answer_contents(match.answer_contents),
85 answer_type(match.answer_type),
86 transition(match.transition),
87 is_history_what_you_typed_match(match.is_history_what_you_typed_match),
88 type(match.type),
89 associated_keyword(match.associated_keyword.get() ?
90 new AutocompleteMatch(*match.associated_keyword) : NULL),
91 keyword(match.keyword),
92 starred(match.starred),
93 from_previous(match.from_previous),
94 search_terms_args(match.search_terms_args.get() ?
95 new TemplateURLRef::SearchTermsArgs(*match.search_terms_args) :
96 NULL),
97 additional_info(match.additional_info),
98 duplicate_matches(match.duplicate_matches) {
101 AutocompleteMatch::~AutocompleteMatch() {
104 AutocompleteMatch& AutocompleteMatch::operator=(
105 const AutocompleteMatch& match) {
106 if (this == &match)
107 return *this;
109 provider = match.provider;
110 relevance = match.relevance;
111 typed_count = match.typed_count;
112 deletable = match.deletable;
113 fill_into_edit = match.fill_into_edit;
114 inline_autocompletion = match.inline_autocompletion;
115 allowed_to_be_default_match = match.allowed_to_be_default_match;
116 destination_url = match.destination_url;
117 stripped_destination_url = match.stripped_destination_url;
118 contents = match.contents;
119 contents_class = match.contents_class;
120 description = match.description;
121 description_class = match.description_class;
122 answer_contents = match.answer_contents;
123 answer_type = match.answer_type;
124 transition = match.transition;
125 is_history_what_you_typed_match = match.is_history_what_you_typed_match;
126 type = match.type;
127 associated_keyword.reset(match.associated_keyword.get() ?
128 new AutocompleteMatch(*match.associated_keyword) : NULL);
129 keyword = match.keyword;
130 starred = match.starred;
131 from_previous = match.from_previous;
132 search_terms_args.reset(match.search_terms_args.get() ?
133 new TemplateURLRef::SearchTermsArgs(*match.search_terms_args) : NULL);
134 additional_info = match.additional_info;
135 duplicate_matches = match.duplicate_matches;
136 return *this;
139 // static
140 int AutocompleteMatch::TypeToIcon(Type type) {
141 int icons[] = {
142 IDR_OMNIBOX_HTTP,
143 IDR_OMNIBOX_HTTP,
144 IDR_OMNIBOX_HTTP,
145 IDR_OMNIBOX_HTTP,
146 IDR_OMNIBOX_HTTP,
147 IDR_OMNIBOX_HTTP,
148 IDR_OMNIBOX_SEARCH,
149 IDR_OMNIBOX_SEARCH,
150 IDR_OMNIBOX_SEARCH,
151 IDR_OMNIBOX_SEARCH,
152 IDR_OMNIBOX_SEARCH,
153 IDR_OMNIBOX_SEARCH,
154 IDR_OMNIBOX_SEARCH,
155 IDR_OMNIBOX_SEARCH,
156 IDR_OMNIBOX_EXTENSION_APP,
157 IDR_OMNIBOX_SEARCH,
158 IDR_OMNIBOX_HTTP,
159 IDR_OMNIBOX_HTTP,
161 COMPILE_ASSERT(arraysize(icons) == AutocompleteMatchType::NUM_TYPES,
162 icons_array_must_match_type_enum);
163 return icons[type];
166 // static
167 int AutocompleteMatch::TypeToLocationBarIcon(Type type) {
168 int id = TypeToIcon(type);
169 if (id == IDR_OMNIBOX_HTTP)
170 return IDR_LOCATION_BAR_HTTP;
171 return id;
174 // static
175 bool AutocompleteMatch::MoreRelevant(const AutocompleteMatch& elem1,
176 const AutocompleteMatch& elem2) {
177 // For equal-relevance matches, we sort alphabetically, so that providers
178 // who return multiple elements at the same priority get a "stable" sort
179 // across multiple updates.
180 return (elem1.relevance == elem2.relevance) ?
181 (elem1.contents < elem2.contents) : (elem1.relevance > elem2.relevance);
184 // static
185 bool AutocompleteMatch::DestinationsEqual(const AutocompleteMatch& elem1,
186 const AutocompleteMatch& elem2) {
187 if (elem1.stripped_destination_url.is_empty() &&
188 elem2.stripped_destination_url.is_empty())
189 return false;
190 return elem1.stripped_destination_url == elem2.stripped_destination_url;
193 // static
194 void AutocompleteMatch::ClassifyMatchInString(
195 const base::string16& find_text,
196 const base::string16& text,
197 int style,
198 ACMatchClassifications* classification) {
199 ClassifyLocationInString(text.find(find_text), find_text.length(),
200 text.length(), style, classification);
203 // static
204 void AutocompleteMatch::ClassifyLocationInString(
205 size_t match_location,
206 size_t match_length,
207 size_t overall_length,
208 int style,
209 ACMatchClassifications* classification) {
210 classification->clear();
212 // Don't classify anything about an empty string
213 // (AutocompleteMatch::Validate() checks this).
214 if (overall_length == 0)
215 return;
217 // Mark pre-match portion of string (if any).
218 if (match_location != 0) {
219 classification->push_back(ACMatchClassification(0, style));
222 // Mark matching portion of string.
223 if (match_location == base::string16::npos) {
224 // No match, above classification will suffice for whole string.
225 return;
227 // Classifying an empty match makes no sense and will lead to validation
228 // errors later.
229 DCHECK_GT(match_length, 0U);
230 classification->push_back(ACMatchClassification(match_location,
231 (style | ACMatchClassification::MATCH) & ~ACMatchClassification::DIM));
233 // Mark post-match portion of string (if any).
234 const size_t after_match(match_location + match_length);
235 if (after_match < overall_length) {
236 classification->push_back(ACMatchClassification(after_match, style));
240 // static
241 AutocompleteMatch::ACMatchClassifications
242 AutocompleteMatch::MergeClassifications(
243 const ACMatchClassifications& classifications1,
244 const ACMatchClassifications& classifications2) {
245 // We must return the empty vector only if both inputs are truly empty.
246 // The result of merging an empty vector with a single (0, NONE)
247 // classification is the latter one-entry vector.
248 if (IsTrivialClassification(classifications1))
249 return classifications2.empty() ? classifications1 : classifications2;
250 if (IsTrivialClassification(classifications2))
251 return classifications1;
253 ACMatchClassifications output;
254 for (ACMatchClassifications::const_iterator i = classifications1.begin(),
255 j = classifications2.begin(); i != classifications1.end();) {
256 AutocompleteMatch::AddLastClassificationIfNecessary(&output,
257 std::max(i->offset, j->offset), i->style | j->style);
258 const size_t next_i_offset = (i + 1) == classifications1.end() ?
259 static_cast<size_t>(-1) : (i + 1)->offset;
260 const size_t next_j_offset = (j + 1) == classifications2.end() ?
261 static_cast<size_t>(-1) : (j + 1)->offset;
262 if (next_i_offset >= next_j_offset)
263 ++j;
264 if (next_j_offset >= next_i_offset)
265 ++i;
268 return output;
271 // static
272 std::string AutocompleteMatch::ClassificationsToString(
273 const ACMatchClassifications& classifications) {
274 std::string serialized_classifications;
275 for (size_t i = 0; i < classifications.size(); ++i) {
276 if (i)
277 serialized_classifications += ',';
278 serialized_classifications += base::IntToString(classifications[i].offset) +
279 ',' + base::IntToString(classifications[i].style);
281 return serialized_classifications;
284 // static
285 ACMatchClassifications AutocompleteMatch::ClassificationsFromString(
286 const std::string& serialized_classifications) {
287 ACMatchClassifications classifications;
288 std::vector<std::string> tokens;
289 Tokenize(serialized_classifications, ",", &tokens);
290 DCHECK(!(tokens.size() & 1)); // The number of tokens should be even.
291 for (size_t i = 0; i < tokens.size(); i += 2) {
292 int classification_offset = 0;
293 int classification_style = ACMatchClassification::NONE;
294 if (!base::StringToInt(tokens[i], &classification_offset) ||
295 !base::StringToInt(tokens[i + 1], &classification_style)) {
296 NOTREACHED();
297 return classifications;
299 classifications.push_back(ACMatchClassification(classification_offset,
300 classification_style));
302 return classifications;
305 // static
306 void AutocompleteMatch::AddLastClassificationIfNecessary(
307 ACMatchClassifications* classifications,
308 size_t offset,
309 int style) {
310 DCHECK(classifications);
311 if (classifications->empty() || classifications->back().style != style) {
312 DCHECK(classifications->empty() ||
313 (offset > classifications->back().offset));
314 classifications->push_back(ACMatchClassification(offset, style));
318 // static
319 base::string16 AutocompleteMatch::SanitizeString(const base::string16& text) {
320 // NOTE: This logic is mirrored by |sanitizeString()| in
321 // omnibox_custom_bindings.js.
322 base::string16 result;
323 base::TrimWhitespace(text, base::TRIM_LEADING, &result);
324 base::RemoveChars(result, kInvalidChars, &result);
325 return result;
328 // static
329 bool AutocompleteMatch::IsSearchType(Type type) {
330 return type == AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED ||
331 type == AutocompleteMatchType::SEARCH_HISTORY ||
332 type == AutocompleteMatchType::SEARCH_SUGGEST ||
333 type == AutocompleteMatchType::SEARCH_OTHER_ENGINE ||
334 IsSpecializedSearchType(type);
337 // static
338 bool AutocompleteMatch::IsSpecializedSearchType(Type type) {
339 return type == AutocompleteMatchType::SEARCH_SUGGEST_ENTITY ||
340 type == AutocompleteMatchType::SEARCH_SUGGEST_INFINITE ||
341 type == AutocompleteMatchType::SEARCH_SUGGEST_PERSONALIZED ||
342 type == AutocompleteMatchType::SEARCH_SUGGEST_PROFILE;
345 void AutocompleteMatch::ComputeStrippedDestinationURL(Profile* profile) {
346 stripped_destination_url = destination_url;
347 if (!stripped_destination_url.is_valid())
348 return;
350 // If the destination URL looks like it was generated from a TemplateURL,
351 // remove all substitutions other than the search terms. This allows us
352 // to eliminate cases like past search URLs from history that differ only
353 // by some obscure query param from each other or from the search/keyword
354 // provider matches.
355 TemplateURL* template_url = GetTemplateURL(profile, true);
356 if (template_url != NULL && template_url->SupportsReplacement()) {
357 base::string16 search_terms;
358 if (template_url->ExtractSearchTermsFromURL(stripped_destination_url,
359 &search_terms)) {
360 stripped_destination_url =
361 GURL(template_url->url_ref().ReplaceSearchTerms(
362 TemplateURLRef::SearchTermsArgs(search_terms)));
366 // |replacements| keeps all the substitions we're going to make to
367 // from {destination_url} to {stripped_destination_url}. |need_replacement|
368 // is a helper variable that helps us keep track of whether we need
369 // to apply the replacement.
370 bool needs_replacement = false;
371 GURL::Replacements replacements;
373 // Remove the www. prefix from the host.
374 static const char prefix[] = "www.";
375 static const size_t prefix_len = arraysize(prefix) - 1;
376 std::string host = stripped_destination_url.host();
377 if (host.compare(0, prefix_len, prefix) == 0) {
378 host = host.substr(prefix_len);
379 replacements.SetHostStr(host);
380 needs_replacement = true;
383 // Replace https protocol with http protocol.
384 if (stripped_destination_url.SchemeIs(url::kHttpsScheme)) {
385 replacements.SetScheme(url::kHttpScheme,
386 url::Component(0, strlen(url::kHttpScheme)));
387 needs_replacement = true;
390 if (needs_replacement)
391 stripped_destination_url = stripped_destination_url.ReplaceComponents(
392 replacements);
395 void AutocompleteMatch::GetKeywordUIState(Profile* profile,
396 base::string16* keyword,
397 bool* is_keyword_hint) const {
398 *is_keyword_hint = associated_keyword.get() != NULL;
399 keyword->assign(*is_keyword_hint ? associated_keyword->keyword :
400 GetSubstitutingExplicitlyInvokedKeyword(profile));
403 base::string16 AutocompleteMatch::GetSubstitutingExplicitlyInvokedKeyword(
404 Profile* profile) const {
405 if (transition != content::PAGE_TRANSITION_KEYWORD)
406 return base::string16();
407 const TemplateURL* t_url = GetTemplateURL(profile, false);
408 return (t_url && t_url->SupportsReplacement()) ? keyword : base::string16();
411 TemplateURL* AutocompleteMatch::GetTemplateURL(
412 Profile* profile, bool allow_fallback_to_destination_host) const {
413 DCHECK(profile);
414 TemplateURLService* template_url_service =
415 TemplateURLServiceFactory::GetForProfile(profile);
416 if (template_url_service == NULL)
417 return NULL;
418 TemplateURL* template_url = keyword.empty() ? NULL :
419 template_url_service->GetTemplateURLForKeyword(keyword);
420 if (template_url == NULL && allow_fallback_to_destination_host) {
421 template_url = template_url_service->GetTemplateURLForHost(
422 destination_url.host());
424 return template_url;
427 void AutocompleteMatch::RecordAdditionalInfo(const std::string& property,
428 const std::string& value) {
429 DCHECK(!property.empty());
430 DCHECK(!value.empty());
431 additional_info[property] = value;
434 void AutocompleteMatch::RecordAdditionalInfo(const std::string& property,
435 int value) {
436 RecordAdditionalInfo(property, base::IntToString(value));
439 void AutocompleteMatch::RecordAdditionalInfo(const std::string& property,
440 const base::Time& value) {
441 RecordAdditionalInfo(property,
442 base::UTF16ToUTF8(
443 base::TimeFormatShortDateAndTime(value)));
446 std::string AutocompleteMatch::GetAdditionalInfo(
447 const std::string& property) const {
448 AdditionalInfo::const_iterator i(additional_info.find(property));
449 return (i == additional_info.end()) ? std::string() : i->second;
452 bool AutocompleteMatch::IsVerbatimType() const {
453 const bool is_keyword_verbatim_match =
454 (type == AutocompleteMatchType::SEARCH_OTHER_ENGINE &&
455 provider != NULL &&
456 provider->type() == AutocompleteProvider::TYPE_SEARCH);
457 return type == AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED ||
458 type == AutocompleteMatchType::URL_WHAT_YOU_TYPED ||
459 is_keyword_verbatim_match;
462 bool AutocompleteMatch::SupportsDeletion() const {
463 if (deletable)
464 return true;
466 for (ACMatches::const_iterator it(duplicate_matches.begin());
467 it != duplicate_matches.end(); ++it) {
468 if (it->deletable)
469 return true;
471 return false;
474 #ifndef NDEBUG
475 void AutocompleteMatch::Validate() const {
476 ValidateClassifications(contents, contents_class);
477 ValidateClassifications(description, description_class);
480 void AutocompleteMatch::ValidateClassifications(
481 const base::string16& text,
482 const ACMatchClassifications& classifications) const {
483 if (text.empty()) {
484 DCHECK(classifications.empty());
485 return;
488 // The classifications should always cover the whole string.
489 DCHECK(!classifications.empty()) << "No classification for \"" << text << '"';
490 DCHECK_EQ(0U, classifications[0].offset)
491 << "Classification misses beginning for \"" << text << '"';
492 if (classifications.size() == 1)
493 return;
495 // The classifications should always be sorted.
496 size_t last_offset = classifications[0].offset;
497 for (ACMatchClassifications::const_iterator i(classifications.begin() + 1);
498 i != classifications.end(); ++i) {
499 const char* provider_name = provider ? provider->GetName() : "None";
500 DCHECK_GT(i->offset, last_offset)
501 << " Classification for \"" << text << "\" with offset of " << i->offset
502 << " is unsorted in relation to last offset of " << last_offset
503 << ". Provider: " << provider_name << ".";
504 DCHECK_LT(i->offset, text.length())
505 << " Classification of [" << i->offset << "," << text.length()
506 << "] is out of bounds for \"" << text << "\". Provider: "
507 << provider_name << ".";
508 last_offset = i->offset;
511 #endif