Clean up extension confirmation prompts and make them consistent between Views and...
[chromium-blink-merge.git] / components / omnibox / autocomplete_match.cc
blobf05336b89bbca823e9f92d446d0e279a44fe1097
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/command_line.h"
8 #include "base/i18n/time_formatting.h"
9 #include "base/logging.h"
10 #include "base/strings/string16.h"
11 #include "base/strings/string_number_conversions.h"
12 #include "base/strings/string_piece.h"
13 #include "base/strings/string_util.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "base/time/time.h"
16 #include "components/omnibox/autocomplete_provider.h"
17 #include "components/omnibox/omnibox_switches.h"
18 #include "components/omnibox/suggestion_answer.h"
19 #include "components/search_engines/template_url.h"
20 #include "components/search_engines/template_url_service.h"
21 #include "grit/components_scaled_resources.h"
23 namespace {
25 bool IsTrivialClassification(const ACMatchClassifications& classifications) {
26 return classifications.empty() ||
27 ((classifications.size() == 1) &&
28 (classifications.back().style == ACMatchClassification::NONE));
31 } // namespace
33 // AutocompleteMatch ----------------------------------------------------------
35 // static
36 const base::char16 AutocompleteMatch::kInvalidChars[] = {
37 '\n', '\r', '\t',
38 0x2028, // Line separator
39 0x2029, // Paragraph separator
43 AutocompleteMatch::AutocompleteMatch()
44 : provider(NULL),
45 relevance(0),
46 typed_count(-1),
47 deletable(false),
48 allowed_to_be_default_match(false),
49 transition(ui::PAGE_TRANSITION_GENERATED),
50 type(AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED),
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(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 answer_contents(match.answer_contents),
83 answer_type(match.answer_type),
84 answer(SuggestionAnswer::copy(match.answer.get())),
85 transition(match.transition),
86 type(match.type),
87 associated_keyword(match.associated_keyword.get() ?
88 new AutocompleteMatch(*match.associated_keyword) : NULL),
89 keyword(match.keyword),
90 from_previous(match.from_previous),
91 search_terms_args(match.search_terms_args.get() ?
92 new TemplateURLRef::SearchTermsArgs(*match.search_terms_args) :
93 NULL),
94 additional_info(match.additional_info),
95 duplicate_matches(match.duplicate_matches) {
98 AutocompleteMatch::~AutocompleteMatch() {
101 AutocompleteMatch& AutocompleteMatch::operator=(
102 const AutocompleteMatch& match) {
103 if (this == &match)
104 return *this;
106 provider = match.provider;
107 relevance = match.relevance;
108 typed_count = match.typed_count;
109 deletable = match.deletable;
110 fill_into_edit = match.fill_into_edit;
111 inline_autocompletion = match.inline_autocompletion;
112 allowed_to_be_default_match = match.allowed_to_be_default_match;
113 destination_url = match.destination_url;
114 stripped_destination_url = match.stripped_destination_url;
115 contents = match.contents;
116 contents_class = match.contents_class;
117 description = match.description;
118 description_class = match.description_class;
119 answer_contents = match.answer_contents;
120 answer_type = match.answer_type;
121 answer = SuggestionAnswer::copy(match.answer.get());
122 transition = match.transition;
123 type = match.type;
124 associated_keyword.reset(match.associated_keyword.get() ?
125 new AutocompleteMatch(*match.associated_keyword) : NULL);
126 keyword = match.keyword;
127 from_previous = match.from_previous;
128 search_terms_args.reset(match.search_terms_args.get() ?
129 new TemplateURLRef::SearchTermsArgs(*match.search_terms_args) : NULL);
130 additional_info = match.additional_info;
131 duplicate_matches = match.duplicate_matches;
132 return *this;
135 // static
136 int AutocompleteMatch::TypeToIcon(Type type) {
137 #if !defined(OS_IOS)
138 static const int kIcons[] = {
139 IDR_OMNIBOX_HTTP, // URL_WHAT_YOU_TYPE
140 IDR_OMNIBOX_HTTP, // HISTORY_URL
141 IDR_OMNIBOX_HTTP, // HISTORY_TITLE
142 IDR_OMNIBOX_HTTP, // HISTORY_BODY
143 IDR_OMNIBOX_HTTP, // HISTORY_KEYWORD
144 IDR_OMNIBOX_HTTP, // NAVSUGGEST
145 IDR_OMNIBOX_SEARCH, // SEARCH_WHAT_YOU_TYPED
146 IDR_OMNIBOX_SEARCH, // SEARCH_HISTORY
147 IDR_OMNIBOX_SEARCH, // SEARCH_SUGGEST
148 IDR_OMNIBOX_SEARCH, // SEARCH_SUGGEST_ENTITY
149 IDR_OMNIBOX_SEARCH, // SEARCH_SUGGEST_TAIL
150 IDR_OMNIBOX_SEARCH, // SEARCH_SUGGEST_PERSONALIZED
151 IDR_OMNIBOX_SEARCH, // SEARCH_SUGGEST_PROFILE
152 IDR_OMNIBOX_SEARCH, // SEARCH_OTHER_ENGINE
153 IDR_OMNIBOX_EXTENSION_APP, // EXTENSION_APP
154 IDR_OMNIBOX_SEARCH, // CONTACT_DEPRECATED
155 IDR_OMNIBOX_HTTP, // BOOKMARK_TITLE
156 IDR_OMNIBOX_HTTP, // NAVSUGGEST_PERSONALIZED
157 IDR_OMNIBOX_CALCULATOR, // CALCULATOR
159 #else
160 static const int kIcons[] = {
161 IDR_OMNIBOX_HTTP, // URL_WHAT_YOU_TYPE
162 IDR_OMNIBOX_HISTORY, // HISTORY_URL
163 IDR_OMNIBOX_HISTORY, // HISTORY_TITLE
164 IDR_OMNIBOX_HISTORY, // HISTORY_BODY
165 IDR_OMNIBOX_HISTORY, // HISTORY_KEYWORD
166 IDR_OMNIBOX_HTTP, // NAVSUGGEST
167 IDR_OMNIBOX_SEARCH, // SEARCH_WHAT_YOU_TYPED
168 IDR_OMNIBOX_HISTORY, // SEARCH_HISTORY
169 IDR_OMNIBOX_SEARCH, // SEARCH_SUGGEST
170 IDR_OMNIBOX_SEARCH, // SEARCH_SUGGEST_ENTITY
171 IDR_OMNIBOX_SEARCH, // SEARCH_SUGGEST_TAIL
172 IDR_OMNIBOX_SEARCH, // SEARCH_SUGGEST_PERSONALIZED
173 IDR_OMNIBOX_SEARCH, // SEARCH_SUGGEST_PROFILE
174 IDR_OMNIBOX_SEARCH, // SEARCH_OTHER_ENGINE
175 IDR_OMNIBOX_EXTENSION_APP, // EXTENSION_APP
176 IDR_OMNIBOX_SEARCH, // CONTACT_DEPRECATED
177 IDR_OMNIBOX_HTTP, // BOOKMARK_TITLE
178 IDR_OMNIBOX_HTTP, // NAVSUGGEST_PERSONALIZED
179 IDR_OMNIBOX_CALCULATOR, // CALCULATOR
181 #endif
182 static_assert(arraysize(kIcons) == AutocompleteMatchType::NUM_TYPES,
183 "icons array must have NUM_TYPES elements");
184 return kIcons[type];
187 // static
188 bool AutocompleteMatch::MoreRelevant(const AutocompleteMatch& elem1,
189 const AutocompleteMatch& elem2) {
190 // For equal-relevance matches, we sort alphabetically, so that providers
191 // who return multiple elements at the same priority get a "stable" sort
192 // across multiple updates.
193 return (elem1.relevance == elem2.relevance) ?
194 (elem1.contents < elem2.contents) : (elem1.relevance > elem2.relevance);
197 // static
198 bool AutocompleteMatch::DestinationsEqual(const AutocompleteMatch& elem1,
199 const AutocompleteMatch& elem2) {
200 if (elem1.stripped_destination_url.is_empty() &&
201 elem2.stripped_destination_url.is_empty())
202 return false;
203 return elem1.stripped_destination_url == elem2.stripped_destination_url;
206 // static
207 void AutocompleteMatch::ClassifyMatchInString(
208 const base::string16& find_text,
209 const base::string16& text,
210 int style,
211 ACMatchClassifications* classification) {
212 ClassifyLocationInString(text.find(find_text), find_text.length(),
213 text.length(), style, classification);
216 // static
217 void AutocompleteMatch::ClassifyLocationInString(
218 size_t match_location,
219 size_t match_length,
220 size_t overall_length,
221 int style,
222 ACMatchClassifications* classification) {
223 classification->clear();
225 // Don't classify anything about an empty string
226 // (AutocompleteMatch::Validate() checks this).
227 if (overall_length == 0)
228 return;
230 // Mark pre-match portion of string (if any).
231 if (match_location != 0) {
232 classification->push_back(ACMatchClassification(0, style));
235 // Mark matching portion of string.
236 if (match_location == base::string16::npos) {
237 // No match, above classification will suffice for whole string.
238 return;
240 // Classifying an empty match makes no sense and will lead to validation
241 // errors later.
242 DCHECK_GT(match_length, 0U);
243 classification->push_back(ACMatchClassification(match_location,
244 (style | ACMatchClassification::MATCH) & ~ACMatchClassification::DIM));
246 // Mark post-match portion of string (if any).
247 const size_t after_match(match_location + match_length);
248 if (after_match < overall_length) {
249 classification->push_back(ACMatchClassification(after_match, style));
253 // static
254 AutocompleteMatch::ACMatchClassifications
255 AutocompleteMatch::MergeClassifications(
256 const ACMatchClassifications& classifications1,
257 const ACMatchClassifications& classifications2) {
258 // We must return the empty vector only if both inputs are truly empty.
259 // The result of merging an empty vector with a single (0, NONE)
260 // classification is the latter one-entry vector.
261 if (IsTrivialClassification(classifications1))
262 return classifications2.empty() ? classifications1 : classifications2;
263 if (IsTrivialClassification(classifications2))
264 return classifications1;
266 ACMatchClassifications output;
267 for (ACMatchClassifications::const_iterator i = classifications1.begin(),
268 j = classifications2.begin(); i != classifications1.end();) {
269 AutocompleteMatch::AddLastClassificationIfNecessary(&output,
270 std::max(i->offset, j->offset), i->style | j->style);
271 const size_t next_i_offset = (i + 1) == classifications1.end() ?
272 static_cast<size_t>(-1) : (i + 1)->offset;
273 const size_t next_j_offset = (j + 1) == classifications2.end() ?
274 static_cast<size_t>(-1) : (j + 1)->offset;
275 if (next_i_offset >= next_j_offset)
276 ++j;
277 if (next_j_offset >= next_i_offset)
278 ++i;
281 return output;
284 // static
285 std::string AutocompleteMatch::ClassificationsToString(
286 const ACMatchClassifications& classifications) {
287 std::string serialized_classifications;
288 for (size_t i = 0; i < classifications.size(); ++i) {
289 if (i)
290 serialized_classifications += ',';
291 serialized_classifications += base::IntToString(classifications[i].offset) +
292 ',' + base::IntToString(classifications[i].style);
294 return serialized_classifications;
297 // static
298 ACMatchClassifications AutocompleteMatch::ClassificationsFromString(
299 const std::string& serialized_classifications) {
300 ACMatchClassifications classifications;
301 std::vector<std::string> tokens;
302 Tokenize(serialized_classifications, ",", &tokens);
303 DCHECK(!(tokens.size() & 1)); // The number of tokens should be even.
304 for (size_t i = 0; i < tokens.size(); i += 2) {
305 int classification_offset = 0;
306 int classification_style = ACMatchClassification::NONE;
307 if (!base::StringToInt(tokens[i], &classification_offset) ||
308 !base::StringToInt(tokens[i + 1], &classification_style)) {
309 NOTREACHED();
310 return classifications;
312 classifications.push_back(ACMatchClassification(classification_offset,
313 classification_style));
315 return classifications;
318 // static
319 void AutocompleteMatch::AddLastClassificationIfNecessary(
320 ACMatchClassifications* classifications,
321 size_t offset,
322 int style) {
323 DCHECK(classifications);
324 if (classifications->empty() || classifications->back().style != style) {
325 DCHECK(classifications->empty() ||
326 (offset > classifications->back().offset));
327 classifications->push_back(ACMatchClassification(offset, style));
331 // static
332 base::string16 AutocompleteMatch::SanitizeString(const base::string16& text) {
333 // NOTE: This logic is mirrored by |sanitizeString()| in
334 // omnibox_custom_bindings.js.
335 base::string16 result;
336 base::TrimWhitespace(text, base::TRIM_LEADING, &result);
337 base::RemoveChars(result, kInvalidChars, &result);
338 return result;
341 // static
342 bool AutocompleteMatch::IsSearchType(Type type) {
343 return type == AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED ||
344 type == AutocompleteMatchType::SEARCH_HISTORY ||
345 type == AutocompleteMatchType::SEARCH_SUGGEST ||
346 type == AutocompleteMatchType::SEARCH_OTHER_ENGINE ||
347 type == AutocompleteMatchType::CALCULATOR ||
348 IsSpecializedSearchType(type);
351 // static
352 bool AutocompleteMatch::IsSpecializedSearchType(Type type) {
353 return type == AutocompleteMatchType::SEARCH_SUGGEST_ENTITY ||
354 type == AutocompleteMatchType::SEARCH_SUGGEST_TAIL ||
355 type == AutocompleteMatchType::SEARCH_SUGGEST_PERSONALIZED ||
356 type == AutocompleteMatchType::SEARCH_SUGGEST_PROFILE;
359 // static
360 TemplateURL* AutocompleteMatch::GetTemplateURLWithKeyword(
361 TemplateURLService* template_url_service,
362 const base::string16& keyword,
363 const std::string& host) {
364 if (template_url_service == NULL)
365 return NULL;
366 TemplateURL* template_url = keyword.empty() ?
367 NULL : template_url_service->GetTemplateURLForKeyword(keyword);
368 return (template_url || host.empty()) ?
369 template_url : template_url_service->GetTemplateURLForHost(host);
372 // static
373 GURL AutocompleteMatch::GURLToStrippedGURL(
374 const GURL& url,
375 TemplateURLService* template_url_service,
376 const base::string16& keyword) {
377 if (!url.is_valid())
378 return url;
380 GURL stripped_destination_url = url;
382 // If the destination URL looks like it was generated from a TemplateURL,
383 // remove all substitutions other than the search terms. This allows us
384 // to eliminate cases like past search URLs from history that differ only
385 // by some obscure query param from each other or from the search/keyword
386 // provider matches.
387 TemplateURL* template_url = GetTemplateURLWithKeyword(
388 template_url_service, keyword, stripped_destination_url.host());
389 if (template_url != NULL &&
390 template_url->SupportsReplacement(
391 template_url_service->search_terms_data())) {
392 base::string16 search_terms;
393 if (template_url->ExtractSearchTermsFromURL(
394 stripped_destination_url,
395 template_url_service->search_terms_data(),
396 &search_terms)) {
397 stripped_destination_url =
398 GURL(template_url->url_ref().ReplaceSearchTerms(
399 TemplateURLRef::SearchTermsArgs(search_terms),
400 template_url_service->search_terms_data()));
404 // |replacements| keeps all the substitions we're going to make to
405 // from {destination_url} to {stripped_destination_url}. |need_replacement|
406 // is a helper variable that helps us keep track of whether we need
407 // to apply the replacement.
408 bool needs_replacement = false;
409 GURL::Replacements replacements;
411 // Remove the www. prefix from the host.
412 static const char prefix[] = "www.";
413 static const size_t prefix_len = arraysize(prefix) - 1;
414 std::string host = stripped_destination_url.host();
415 if (host.compare(0, prefix_len, prefix) == 0) {
416 replacements.SetHostStr(base::StringPiece(host).substr(prefix_len));
417 needs_replacement = true;
420 // Remove any trailing slash (if it's not a lone slash), or add a slash (to
421 // make a lone slash) if the path is empty. (We can't unconditionally
422 // remove even lone slashes because for some schemes the path must consist
423 // of at least a slash.)
424 const std::string& path = stripped_destination_url.path();
425 if ((path.length() > 1) && (path[path.length() - 1] == '/')) {
426 replacements.SetPathStr(
427 base::StringPiece(path).substr(0, path.length() - 1));
428 needs_replacement = true;
429 } else if (path.empty()) {
430 static const char slash[] = "/";
431 replacements.SetPathStr(base::StringPiece(slash));
432 needs_replacement = true;
435 // Replace https protocol with http protocol.
436 if (stripped_destination_url.SchemeIs(url::kHttpsScheme)) {
437 replacements.SetScheme(url::kHttpScheme,
438 url::Component(0, strlen(url::kHttpScheme)));
439 needs_replacement = true;
442 if (needs_replacement)
443 stripped_destination_url = stripped_destination_url.ReplaceComponents(
444 replacements);
445 return stripped_destination_url;
448 void AutocompleteMatch::ComputeStrippedDestinationURL(
449 TemplateURLService* template_url_service) {
450 stripped_destination_url =
451 GURLToStrippedGURL(destination_url, template_url_service, keyword);
454 void AutocompleteMatch::EnsureUWYTIsAllowedToBeDefault(
455 const GURL& canonical_input_url,
456 TemplateURLService* template_url_service) {
457 if (!allowed_to_be_default_match) {
458 const GURL& stripped_canonical_input_url =
459 AutocompleteMatch::GURLToStrippedGURL(
460 canonical_input_url, template_url_service, base::string16());
461 ComputeStrippedDestinationURL(template_url_service);
462 allowed_to_be_default_match =
463 stripped_canonical_input_url == stripped_destination_url;
467 void AutocompleteMatch::GetKeywordUIState(
468 TemplateURLService* template_url_service,
469 base::string16* keyword,
470 bool* is_keyword_hint) const {
471 *is_keyword_hint = associated_keyword.get() != NULL;
472 keyword->assign(*is_keyword_hint ? associated_keyword->keyword :
473 GetSubstitutingExplicitlyInvokedKeyword(template_url_service));
476 base::string16 AutocompleteMatch::GetSubstitutingExplicitlyInvokedKeyword(
477 TemplateURLService* template_url_service) const {
478 if (transition != ui::PAGE_TRANSITION_KEYWORD ||
479 template_url_service == NULL) {
480 return base::string16();
483 const TemplateURL* t_url = GetTemplateURL(template_url_service, false);
484 return (t_url &&
485 t_url->SupportsReplacement(
486 template_url_service->search_terms_data())) ?
487 keyword : base::string16();
490 TemplateURL* AutocompleteMatch::GetTemplateURL(
491 TemplateURLService* template_url_service,
492 bool allow_fallback_to_destination_host) const {
493 return GetTemplateURLWithKeyword(
494 template_url_service, keyword,
495 allow_fallback_to_destination_host ?
496 destination_url.host() : std::string());
499 void AutocompleteMatch::RecordAdditionalInfo(const std::string& property,
500 const std::string& value) {
501 DCHECK(!property.empty());
502 DCHECK(!value.empty());
503 additional_info[property] = value;
506 void AutocompleteMatch::RecordAdditionalInfo(const std::string& property,
507 int value) {
508 RecordAdditionalInfo(property, base::IntToString(value));
511 void AutocompleteMatch::RecordAdditionalInfo(const std::string& property,
512 const base::Time& value) {
513 RecordAdditionalInfo(property,
514 base::UTF16ToUTF8(
515 base::TimeFormatShortDateAndTime(value)));
518 std::string AutocompleteMatch::GetAdditionalInfo(
519 const std::string& property) const {
520 AdditionalInfo::const_iterator i(additional_info.find(property));
521 return (i == additional_info.end()) ? std::string() : i->second;
524 bool AutocompleteMatch::IsVerbatimType() const {
525 const bool is_keyword_verbatim_match =
526 (type == AutocompleteMatchType::SEARCH_OTHER_ENGINE &&
527 provider != NULL &&
528 provider->type() == AutocompleteProvider::TYPE_SEARCH);
529 return type == AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED ||
530 type == AutocompleteMatchType::URL_WHAT_YOU_TYPED ||
531 is_keyword_verbatim_match;
534 bool AutocompleteMatch::SupportsDeletion() const {
535 if (deletable)
536 return true;
538 for (ACMatches::const_iterator it(duplicate_matches.begin());
539 it != duplicate_matches.end(); ++it) {
540 if (it->deletable)
541 return true;
543 return false;
546 void AutocompleteMatch::PossiblySwapContentsAndDescriptionForURLSuggestion(
547 const AutocompleteInput& input) {
548 if (!IsSearchType(type) && !description.empty() &&
549 base::CommandLine::ForCurrentProcess()->
550 HasSwitch(switches::kEmphasizeTitlesInOmniboxDropdown) &&
551 ((input.type() == metrics::OmniboxInputType::QUERY) ||
552 (input.type() == metrics::OmniboxInputType::FORCED_QUERY))) {
553 std::swap(contents, description);
554 std::swap(contents_class, description_class);
558 #ifndef NDEBUG
559 void AutocompleteMatch::Validate() const {
560 ValidateClassifications(contents, contents_class);
561 ValidateClassifications(description, description_class);
564 void AutocompleteMatch::ValidateClassifications(
565 const base::string16& text,
566 const ACMatchClassifications& classifications) const {
567 if (text.empty()) {
568 DCHECK(classifications.empty());
569 return;
572 // The classifications should always cover the whole string.
573 DCHECK(!classifications.empty()) << "No classification for \"" << text << '"';
574 DCHECK_EQ(0U, classifications[0].offset)
575 << "Classification misses beginning for \"" << text << '"';
576 if (classifications.size() == 1)
577 return;
579 // The classifications should always be sorted.
580 size_t last_offset = classifications[0].offset;
581 for (ACMatchClassifications::const_iterator i(classifications.begin() + 1);
582 i != classifications.end(); ++i) {
583 const char* provider_name = provider ? provider->GetName() : "None";
584 DCHECK_GT(i->offset, last_offset)
585 << " Classification for \"" << text << "\" with offset of " << i->offset
586 << " is unsorted in relation to last offset of " << last_offset
587 << ". Provider: " << provider_name << ".";
588 DCHECK_LT(i->offset, text.length())
589 << " Classification of [" << i->offset << "," << text.length()
590 << "] is out of bounds for \"" << text << "\". Provider: "
591 << provider_name << ".";
592 last_offset = i->offset;
595 #endif