Convert events_unittests to run exclusively on Swarming
[chromium-blink-merge.git] / components / omnibox / autocomplete_match.cc
blob13f90d11023f14c89156c7f11e80a52960374aee
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/omnibox/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_piece.h"
12 #include "base/strings/string_util.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "base/time/time.h"
15 #include "components/omnibox/autocomplete_provider.h"
16 #include "components/omnibox/suggestion_answer.h"
17 #include "components/search_engines/template_url.h"
18 #include "components/search_engines/template_url_service.h"
19 #include "grit/components_scaled_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 swap_contents_and_description(false),
48 transition(ui::PAGE_TRANSITION_GENERATED),
49 type(AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED),
50 from_previous(false) {
53 AutocompleteMatch::AutocompleteMatch(AutocompleteProvider* provider,
54 int relevance,
55 bool deletable,
56 Type type)
57 : provider(provider),
58 relevance(relevance),
59 typed_count(-1),
60 deletable(deletable),
61 allowed_to_be_default_match(false),
62 swap_contents_and_description(false),
63 transition(ui::PAGE_TRANSITION_TYPED),
64 type(type),
65 from_previous(false) {
68 AutocompleteMatch::AutocompleteMatch(const AutocompleteMatch& match)
69 : provider(match.provider),
70 relevance(match.relevance),
71 typed_count(match.typed_count),
72 deletable(match.deletable),
73 fill_into_edit(match.fill_into_edit),
74 inline_autocompletion(match.inline_autocompletion),
75 allowed_to_be_default_match(match.allowed_to_be_default_match),
76 destination_url(match.destination_url),
77 stripped_destination_url(match.stripped_destination_url),
78 contents(match.contents),
79 contents_class(match.contents_class),
80 description(match.description),
81 description_class(match.description_class),
82 swap_contents_and_description(match.swap_contents_and_description),
83 answer_contents(match.answer_contents),
84 answer_type(match.answer_type),
85 answer(SuggestionAnswer::copy(match.answer.get())),
86 transition(match.transition),
87 type(match.type),
88 associated_keyword(match.associated_keyword.get() ?
89 new AutocompleteMatch(*match.associated_keyword) : NULL),
90 keyword(match.keyword),
91 from_previous(match.from_previous),
92 search_terms_args(match.search_terms_args.get() ?
93 new TemplateURLRef::SearchTermsArgs(*match.search_terms_args) :
94 NULL),
95 additional_info(match.additional_info),
96 duplicate_matches(match.duplicate_matches) {
99 AutocompleteMatch::~AutocompleteMatch() {
102 AutocompleteMatch& AutocompleteMatch::operator=(
103 const AutocompleteMatch& match) {
104 if (this == &match)
105 return *this;
107 provider = match.provider;
108 relevance = match.relevance;
109 typed_count = match.typed_count;
110 deletable = match.deletable;
111 fill_into_edit = match.fill_into_edit;
112 inline_autocompletion = match.inline_autocompletion;
113 allowed_to_be_default_match = match.allowed_to_be_default_match;
114 destination_url = match.destination_url;
115 stripped_destination_url = match.stripped_destination_url;
116 contents = match.contents;
117 contents_class = match.contents_class;
118 description = match.description;
119 description_class = match.description_class;
120 swap_contents_and_description = match.swap_contents_and_description;
121 answer_contents = match.answer_contents;
122 answer_type = match.answer_type;
123 answer = SuggestionAnswer::copy(match.answer.get());
124 transition = match.transition;
125 type = match.type;
126 associated_keyword.reset(match.associated_keyword.get() ?
127 new AutocompleteMatch(*match.associated_keyword) : NULL);
128 keyword = match.keyword;
129 from_previous = match.from_previous;
130 search_terms_args.reset(match.search_terms_args.get() ?
131 new TemplateURLRef::SearchTermsArgs(*match.search_terms_args) : NULL);
132 additional_info = match.additional_info;
133 duplicate_matches = match.duplicate_matches;
134 return *this;
137 // static
138 int AutocompleteMatch::TypeToIcon(Type type) {
139 #if !defined(OS_IOS)
140 static const int kIcons[] = {
141 IDR_OMNIBOX_HTTP, // URL_WHAT_YOU_TYPE
142 IDR_OMNIBOX_HTTP, // HISTORY_URL
143 IDR_OMNIBOX_HTTP, // HISTORY_TITLE
144 IDR_OMNIBOX_HTTP, // HISTORY_BODY
145 IDR_OMNIBOX_HTTP, // HISTORY_KEYWORD
146 IDR_OMNIBOX_HTTP, // NAVSUGGEST
147 IDR_OMNIBOX_SEARCH, // SEARCH_WHAT_YOU_TYPED
148 IDR_OMNIBOX_SEARCH, // SEARCH_HISTORY
149 IDR_OMNIBOX_SEARCH, // SEARCH_SUGGEST
150 IDR_OMNIBOX_SEARCH, // SEARCH_SUGGEST_ENTITY
151 IDR_OMNIBOX_SEARCH, // SEARCH_SUGGEST_TAIL
152 IDR_OMNIBOX_SEARCH, // SEARCH_SUGGEST_PERSONALIZED
153 IDR_OMNIBOX_SEARCH, // SEARCH_SUGGEST_PROFILE
154 IDR_OMNIBOX_SEARCH, // SEARCH_OTHER_ENGINE
155 IDR_OMNIBOX_EXTENSION_APP, // EXTENSION_APP
156 IDR_OMNIBOX_SEARCH, // CONTACT_DEPRECATED
157 IDR_OMNIBOX_HTTP, // BOOKMARK_TITLE
158 IDR_OMNIBOX_HTTP, // NAVSUGGEST_PERSONALIZED
159 IDR_OMNIBOX_CALCULATOR, // CALCULATOR
161 #else
162 static const int kIcons[] = {
163 IDR_OMNIBOX_HTTP, // URL_WHAT_YOU_TYPE
164 IDR_OMNIBOX_HISTORY, // HISTORY_URL
165 IDR_OMNIBOX_HISTORY, // HISTORY_TITLE
166 IDR_OMNIBOX_HISTORY, // HISTORY_BODY
167 IDR_OMNIBOX_HISTORY, // HISTORY_KEYWORD
168 IDR_OMNIBOX_HTTP, // NAVSUGGEST
169 IDR_OMNIBOX_SEARCH, // SEARCH_WHAT_YOU_TYPED
170 IDR_OMNIBOX_HISTORY, // SEARCH_HISTORY
171 IDR_OMNIBOX_SEARCH, // SEARCH_SUGGEST
172 IDR_OMNIBOX_SEARCH, // SEARCH_SUGGEST_ENTITY
173 IDR_OMNIBOX_SEARCH, // SEARCH_SUGGEST_TAIL
174 IDR_OMNIBOX_SEARCH, // SEARCH_SUGGEST_PERSONALIZED
175 IDR_OMNIBOX_SEARCH, // SEARCH_SUGGEST_PROFILE
176 IDR_OMNIBOX_SEARCH, // SEARCH_OTHER_ENGINE
177 IDR_OMNIBOX_EXTENSION_APP, // EXTENSION_APP
178 IDR_OMNIBOX_SEARCH, // CONTACT_DEPRECATED
179 IDR_OMNIBOX_HTTP, // BOOKMARK_TITLE
180 IDR_OMNIBOX_HTTP, // NAVSUGGEST_PERSONALIZED
181 IDR_OMNIBOX_CALCULATOR, // CALCULATOR
183 #endif
184 static_assert(arraysize(kIcons) == AutocompleteMatchType::NUM_TYPES,
185 "icons array must have NUM_TYPES elements");
186 return kIcons[type];
189 // static
190 bool AutocompleteMatch::MoreRelevant(const AutocompleteMatch& elem1,
191 const AutocompleteMatch& elem2) {
192 // For equal-relevance matches, we sort alphabetically, so that providers
193 // who return multiple elements at the same priority get a "stable" sort
194 // across multiple updates.
195 return (elem1.relevance == elem2.relevance) ?
196 (elem1.contents < elem2.contents) : (elem1.relevance > elem2.relevance);
199 // static
200 bool AutocompleteMatch::DestinationsEqual(const AutocompleteMatch& elem1,
201 const AutocompleteMatch& elem2) {
202 if (elem1.stripped_destination_url.is_empty() &&
203 elem2.stripped_destination_url.is_empty())
204 return false;
205 return elem1.stripped_destination_url == elem2.stripped_destination_url;
208 // static
209 void AutocompleteMatch::ClassifyMatchInString(
210 const base::string16& find_text,
211 const base::string16& text,
212 int style,
213 ACMatchClassifications* classification) {
214 ClassifyLocationInString(text.find(find_text), find_text.length(),
215 text.length(), style, classification);
218 // static
219 void AutocompleteMatch::ClassifyLocationInString(
220 size_t match_location,
221 size_t match_length,
222 size_t overall_length,
223 int style,
224 ACMatchClassifications* classification) {
225 classification->clear();
227 // Don't classify anything about an empty string
228 // (AutocompleteMatch::Validate() checks this).
229 if (overall_length == 0)
230 return;
232 // Mark pre-match portion of string (if any).
233 if (match_location != 0) {
234 classification->push_back(ACMatchClassification(0, style));
237 // Mark matching portion of string.
238 if (match_location == base::string16::npos) {
239 // No match, above classification will suffice for whole string.
240 return;
242 // Classifying an empty match makes no sense and will lead to validation
243 // errors later.
244 DCHECK_GT(match_length, 0U);
245 classification->push_back(ACMatchClassification(match_location,
246 (style | ACMatchClassification::MATCH) & ~ACMatchClassification::DIM));
248 // Mark post-match portion of string (if any).
249 const size_t after_match(match_location + match_length);
250 if (after_match < overall_length) {
251 classification->push_back(ACMatchClassification(after_match, style));
255 // static
256 AutocompleteMatch::ACMatchClassifications
257 AutocompleteMatch::MergeClassifications(
258 const ACMatchClassifications& classifications1,
259 const ACMatchClassifications& classifications2) {
260 // We must return the empty vector only if both inputs are truly empty.
261 // The result of merging an empty vector with a single (0, NONE)
262 // classification is the latter one-entry vector.
263 if (IsTrivialClassification(classifications1))
264 return classifications2.empty() ? classifications1 : classifications2;
265 if (IsTrivialClassification(classifications2))
266 return classifications1;
268 ACMatchClassifications output;
269 for (ACMatchClassifications::const_iterator i = classifications1.begin(),
270 j = classifications2.begin(); i != classifications1.end();) {
271 AutocompleteMatch::AddLastClassificationIfNecessary(&output,
272 std::max(i->offset, j->offset), i->style | j->style);
273 const size_t next_i_offset = (i + 1) == classifications1.end() ?
274 static_cast<size_t>(-1) : (i + 1)->offset;
275 const size_t next_j_offset = (j + 1) == classifications2.end() ?
276 static_cast<size_t>(-1) : (j + 1)->offset;
277 if (next_i_offset >= next_j_offset)
278 ++j;
279 if (next_j_offset >= next_i_offset)
280 ++i;
283 return output;
286 // static
287 std::string AutocompleteMatch::ClassificationsToString(
288 const ACMatchClassifications& classifications) {
289 std::string serialized_classifications;
290 for (size_t i = 0; i < classifications.size(); ++i) {
291 if (i)
292 serialized_classifications += ',';
293 serialized_classifications += base::IntToString(classifications[i].offset) +
294 ',' + base::IntToString(classifications[i].style);
296 return serialized_classifications;
299 // static
300 ACMatchClassifications AutocompleteMatch::ClassificationsFromString(
301 const std::string& serialized_classifications) {
302 ACMatchClassifications classifications;
303 std::vector<std::string> tokens;
304 Tokenize(serialized_classifications, ",", &tokens);
305 DCHECK(!(tokens.size() & 1)); // The number of tokens should be even.
306 for (size_t i = 0; i < tokens.size(); i += 2) {
307 int classification_offset = 0;
308 int classification_style = ACMatchClassification::NONE;
309 if (!base::StringToInt(tokens[i], &classification_offset) ||
310 !base::StringToInt(tokens[i + 1], &classification_style)) {
311 NOTREACHED();
312 return classifications;
314 classifications.push_back(ACMatchClassification(classification_offset,
315 classification_style));
317 return classifications;
320 // static
321 void AutocompleteMatch::AddLastClassificationIfNecessary(
322 ACMatchClassifications* classifications,
323 size_t offset,
324 int style) {
325 DCHECK(classifications);
326 if (classifications->empty() || classifications->back().style != style) {
327 DCHECK(classifications->empty() ||
328 (offset > classifications->back().offset));
329 classifications->push_back(ACMatchClassification(offset, style));
333 // static
334 bool AutocompleteMatch::HasMatchStyle(
335 const ACMatchClassifications& classifications) {
336 for (const auto& it : classifications) {
337 if (it.style & AutocompleteMatch::ACMatchClassification::MATCH)
338 return true;
340 return false;
343 // static
344 base::string16 AutocompleteMatch::SanitizeString(const base::string16& text) {
345 // NOTE: This logic is mirrored by |sanitizeString()| in
346 // omnibox_custom_bindings.js.
347 base::string16 result;
348 base::TrimWhitespace(text, base::TRIM_LEADING, &result);
349 base::RemoveChars(result, kInvalidChars, &result);
350 return result;
353 // static
354 bool AutocompleteMatch::IsSearchType(Type type) {
355 return type == AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED ||
356 type == AutocompleteMatchType::SEARCH_HISTORY ||
357 type == AutocompleteMatchType::SEARCH_SUGGEST ||
358 type == AutocompleteMatchType::SEARCH_OTHER_ENGINE ||
359 type == AutocompleteMatchType::CALCULATOR ||
360 IsSpecializedSearchType(type);
363 // static
364 bool AutocompleteMatch::IsSpecializedSearchType(Type type) {
365 return type == AutocompleteMatchType::SEARCH_SUGGEST_ENTITY ||
366 type == AutocompleteMatchType::SEARCH_SUGGEST_TAIL ||
367 type == AutocompleteMatchType::SEARCH_SUGGEST_PERSONALIZED ||
368 type == AutocompleteMatchType::SEARCH_SUGGEST_PROFILE;
371 // static
372 TemplateURL* AutocompleteMatch::GetTemplateURLWithKeyword(
373 TemplateURLService* template_url_service,
374 const base::string16& keyword,
375 const std::string& host) {
376 if (template_url_service == NULL)
377 return NULL;
378 TemplateURL* template_url = keyword.empty() ?
379 NULL : template_url_service->GetTemplateURLForKeyword(keyword);
380 return (template_url || host.empty()) ?
381 template_url : template_url_service->GetTemplateURLForHost(host);
384 // static
385 GURL AutocompleteMatch::GURLToStrippedGURL(
386 const GURL& url,
387 TemplateURLService* template_url_service,
388 const base::string16& keyword) {
389 if (!url.is_valid())
390 return url;
392 GURL stripped_destination_url = url;
394 // If the destination URL looks like it was generated from a TemplateURL,
395 // remove all substitutions other than the search terms. This allows us
396 // to eliminate cases like past search URLs from history that differ only
397 // by some obscure query param from each other or from the search/keyword
398 // provider matches.
399 TemplateURL* template_url = GetTemplateURLWithKeyword(
400 template_url_service, keyword, stripped_destination_url.host());
401 if (template_url != NULL &&
402 template_url->SupportsReplacement(
403 template_url_service->search_terms_data())) {
404 base::string16 search_terms;
405 if (template_url->ExtractSearchTermsFromURL(
406 stripped_destination_url,
407 template_url_service->search_terms_data(),
408 &search_terms)) {
409 stripped_destination_url =
410 GURL(template_url->url_ref().ReplaceSearchTerms(
411 TemplateURLRef::SearchTermsArgs(search_terms),
412 template_url_service->search_terms_data()));
416 // |replacements| keeps all the substitions we're going to make to
417 // from {destination_url} to {stripped_destination_url}. |need_replacement|
418 // is a helper variable that helps us keep track of whether we need
419 // to apply the replacement.
420 bool needs_replacement = false;
421 GURL::Replacements replacements;
423 // Remove the www. prefix from the host.
424 static const char prefix[] = "www.";
425 static const size_t prefix_len = arraysize(prefix) - 1;
426 std::string host = stripped_destination_url.host();
427 if (host.compare(0, prefix_len, prefix) == 0) {
428 replacements.SetHostStr(base::StringPiece(host).substr(prefix_len));
429 needs_replacement = true;
432 // Replace https protocol with http protocol.
433 if (stripped_destination_url.SchemeIs(url::kHttpsScheme)) {
434 replacements.SetScheme(url::kHttpScheme,
435 url::Component(0, strlen(url::kHttpScheme)));
436 needs_replacement = true;
439 if (needs_replacement)
440 stripped_destination_url = stripped_destination_url.ReplaceComponents(
441 replacements);
442 return stripped_destination_url;
445 void AutocompleteMatch::ComputeStrippedDestinationURL(
446 TemplateURLService* template_url_service) {
447 stripped_destination_url =
448 GURLToStrippedGURL(destination_url, template_url_service, keyword);
451 void AutocompleteMatch::EnsureUWYTIsAllowedToBeDefault(
452 const GURL& canonical_input_url,
453 TemplateURLService* template_url_service) {
454 if (!allowed_to_be_default_match) {
455 const GURL& stripped_canonical_input_url =
456 AutocompleteMatch::GURLToStrippedGURL(
457 canonical_input_url, template_url_service, base::string16());
458 ComputeStrippedDestinationURL(template_url_service);
459 allowed_to_be_default_match =
460 stripped_canonical_input_url == stripped_destination_url;
464 void AutocompleteMatch::GetKeywordUIState(
465 TemplateURLService* template_url_service,
466 base::string16* keyword,
467 bool* is_keyword_hint) const {
468 *is_keyword_hint = associated_keyword.get() != NULL;
469 keyword->assign(*is_keyword_hint ? associated_keyword->keyword :
470 GetSubstitutingExplicitlyInvokedKeyword(template_url_service));
473 base::string16 AutocompleteMatch::GetSubstitutingExplicitlyInvokedKeyword(
474 TemplateURLService* template_url_service) const {
475 if (transition != ui::PAGE_TRANSITION_KEYWORD ||
476 template_url_service == NULL) {
477 return base::string16();
480 const TemplateURL* t_url = GetTemplateURL(template_url_service, false);
481 return (t_url &&
482 t_url->SupportsReplacement(
483 template_url_service->search_terms_data())) ?
484 keyword : base::string16();
487 TemplateURL* AutocompleteMatch::GetTemplateURL(
488 TemplateURLService* template_url_service,
489 bool allow_fallback_to_destination_host) const {
490 return GetTemplateURLWithKeyword(
491 template_url_service, keyword,
492 allow_fallback_to_destination_host ?
493 destination_url.host() : std::string());
496 void AutocompleteMatch::RecordAdditionalInfo(const std::string& property,
497 const std::string& value) {
498 DCHECK(!property.empty());
499 DCHECK(!value.empty());
500 additional_info[property] = value;
503 void AutocompleteMatch::RecordAdditionalInfo(const std::string& property,
504 int value) {
505 RecordAdditionalInfo(property, base::IntToString(value));
508 void AutocompleteMatch::RecordAdditionalInfo(const std::string& property,
509 const base::Time& value) {
510 RecordAdditionalInfo(property,
511 base::UTF16ToUTF8(
512 base::TimeFormatShortDateAndTime(value)));
515 std::string AutocompleteMatch::GetAdditionalInfo(
516 const std::string& property) const {
517 AdditionalInfo::const_iterator i(additional_info.find(property));
518 return (i == additional_info.end()) ? std::string() : i->second;
521 bool AutocompleteMatch::IsVerbatimType() const {
522 const bool is_keyword_verbatim_match =
523 (type == AutocompleteMatchType::SEARCH_OTHER_ENGINE &&
524 provider != NULL &&
525 provider->type() == AutocompleteProvider::TYPE_SEARCH);
526 return type == AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED ||
527 type == AutocompleteMatchType::URL_WHAT_YOU_TYPED ||
528 is_keyword_verbatim_match;
531 bool AutocompleteMatch::SupportsDeletion() const {
532 if (deletable)
533 return true;
535 for (ACMatches::const_iterator it(duplicate_matches.begin());
536 it != duplicate_matches.end(); ++it) {
537 if (it->deletable)
538 return true;
540 return false;
543 void AutocompleteMatch::PossiblySwapContentsAndDescriptionForDisplay() {
544 if (swap_contents_and_description) {
545 std::swap(contents, description);
546 std::swap(contents_class, description_class);
550 #ifndef NDEBUG
551 void AutocompleteMatch::Validate() const {
552 ValidateClassifications(contents, contents_class);
553 ValidateClassifications(description, description_class);
556 void AutocompleteMatch::ValidateClassifications(
557 const base::string16& text,
558 const ACMatchClassifications& classifications) const {
559 if (text.empty()) {
560 DCHECK(classifications.empty());
561 return;
564 // The classifications should always cover the whole string.
565 DCHECK(!classifications.empty()) << "No classification for \"" << text << '"';
566 DCHECK_EQ(0U, classifications[0].offset)
567 << "Classification misses beginning for \"" << text << '"';
568 if (classifications.size() == 1)
569 return;
571 // The classifications should always be sorted.
572 size_t last_offset = classifications[0].offset;
573 for (ACMatchClassifications::const_iterator i(classifications.begin() + 1);
574 i != classifications.end(); ++i) {
575 const char* provider_name = provider ? provider->GetName() : "None";
576 DCHECK_GT(i->offset, last_offset)
577 << " Classification for \"" << text << "\" with offset of " << i->offset
578 << " is unsorted in relation to last offset of " << last_offset
579 << ". Provider: " << provider_name << ".";
580 DCHECK_LT(i->offset, text.length())
581 << " Classification of [" << i->offset << "," << text.length()
582 << "] is out of bounds for \"" << text << "\". Provider: "
583 << provider_name << ".";
584 last_offset = i->offset;
587 #endif