Update comments of TabObserver#onLoadStarted and rename onContentChanged
[chromium-blink-merge.git] / components / omnibox / autocomplete_match.cc
blobd19f8e9b56031961555e2f2db05fc075080bfaa3
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 transition(ui::PAGE_TRANSITION_GENERATED),
48 type(AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED),
49 from_previous(false) {
52 AutocompleteMatch::AutocompleteMatch(AutocompleteProvider* provider,
53 int relevance,
54 bool deletable,
55 Type type)
56 : provider(provider),
57 relevance(relevance),
58 typed_count(-1),
59 deletable(deletable),
60 allowed_to_be_default_match(false),
61 transition(ui::PAGE_TRANSITION_TYPED),
62 type(type),
63 from_previous(false) {
66 AutocompleteMatch::AutocompleteMatch(const AutocompleteMatch& match)
67 : provider(match.provider),
68 relevance(match.relevance),
69 typed_count(match.typed_count),
70 deletable(match.deletable),
71 fill_into_edit(match.fill_into_edit),
72 inline_autocompletion(match.inline_autocompletion),
73 allowed_to_be_default_match(match.allowed_to_be_default_match),
74 destination_url(match.destination_url),
75 stripped_destination_url(match.stripped_destination_url),
76 contents(match.contents),
77 contents_class(match.contents_class),
78 description(match.description),
79 description_class(match.description_class),
80 answer_contents(match.answer_contents),
81 answer_type(match.answer_type),
82 answer(SuggestionAnswer::copy(match.answer.get())),
83 transition(match.transition),
84 type(match.type),
85 associated_keyword(match.associated_keyword.get() ?
86 new AutocompleteMatch(*match.associated_keyword) : NULL),
87 keyword(match.keyword),
88 from_previous(match.from_previous),
89 search_terms_args(match.search_terms_args.get() ?
90 new TemplateURLRef::SearchTermsArgs(*match.search_terms_args) :
91 NULL),
92 additional_info(match.additional_info),
93 duplicate_matches(match.duplicate_matches) {
96 AutocompleteMatch::~AutocompleteMatch() {
99 AutocompleteMatch& AutocompleteMatch::operator=(
100 const AutocompleteMatch& match) {
101 if (this == &match)
102 return *this;
104 provider = match.provider;
105 relevance = match.relevance;
106 typed_count = match.typed_count;
107 deletable = match.deletable;
108 fill_into_edit = match.fill_into_edit;
109 inline_autocompletion = match.inline_autocompletion;
110 allowed_to_be_default_match = match.allowed_to_be_default_match;
111 destination_url = match.destination_url;
112 stripped_destination_url = match.stripped_destination_url;
113 contents = match.contents;
114 contents_class = match.contents_class;
115 description = match.description;
116 description_class = match.description_class;
117 answer_contents = match.answer_contents;
118 answer_type = match.answer_type;
119 answer = SuggestionAnswer::copy(match.answer.get());
120 transition = match.transition;
121 type = match.type;
122 associated_keyword.reset(match.associated_keyword.get() ?
123 new AutocompleteMatch(*match.associated_keyword) : NULL);
124 keyword = match.keyword;
125 from_previous = match.from_previous;
126 search_terms_args.reset(match.search_terms_args.get() ?
127 new TemplateURLRef::SearchTermsArgs(*match.search_terms_args) : NULL);
128 additional_info = match.additional_info;
129 duplicate_matches = match.duplicate_matches;
130 return *this;
133 // static
134 int AutocompleteMatch::TypeToIcon(Type type) {
135 #if !defined(OS_IOS)
136 static const int kIcons[] = {
137 IDR_OMNIBOX_HTTP, // URL_WHAT_YOU_TYPE
138 IDR_OMNIBOX_HTTP, // HISTORY_URL
139 IDR_OMNIBOX_HTTP, // HISTORY_TITLE
140 IDR_OMNIBOX_HTTP, // HISTORY_BODY
141 IDR_OMNIBOX_HTTP, // HISTORY_KEYWORD
142 IDR_OMNIBOX_HTTP, // NAVSUGGEST
143 IDR_OMNIBOX_SEARCH, // SEARCH_WHAT_YOU_TYPED
144 IDR_OMNIBOX_SEARCH, // SEARCH_HISTORY
145 IDR_OMNIBOX_SEARCH, // SEARCH_SUGGEST
146 IDR_OMNIBOX_SEARCH, // SEARCH_SUGGEST_ENTITY
147 IDR_OMNIBOX_SEARCH, // SEARCH_SUGGEST_TAIL
148 IDR_OMNIBOX_SEARCH, // SEARCH_SUGGEST_PERSONALIZED
149 IDR_OMNIBOX_SEARCH, // SEARCH_SUGGEST_PROFILE
150 IDR_OMNIBOX_SEARCH, // SEARCH_OTHER_ENGINE
151 IDR_OMNIBOX_EXTENSION_APP, // EXTENSION_APP
152 IDR_OMNIBOX_SEARCH, // CONTACT_DEPRECATED
153 IDR_OMNIBOX_HTTP, // BOOKMARK_TITLE
154 IDR_OMNIBOX_HTTP, // NAVSUGGEST_PERSONALIZED
155 IDR_OMNIBOX_CALCULATOR, // CALCULATOR
157 #else
158 static const int kIcons[] = {
159 IDR_OMNIBOX_HTTP, // URL_WHAT_YOU_TYPE
160 IDR_OMNIBOX_HISTORY, // HISTORY_URL
161 IDR_OMNIBOX_HISTORY, // HISTORY_TITLE
162 IDR_OMNIBOX_HISTORY, // HISTORY_BODY
163 IDR_OMNIBOX_HISTORY, // HISTORY_KEYWORD
164 IDR_OMNIBOX_HTTP, // NAVSUGGEST
165 IDR_OMNIBOX_SEARCH, // SEARCH_WHAT_YOU_TYPED
166 IDR_OMNIBOX_HISTORY, // SEARCH_HISTORY
167 IDR_OMNIBOX_SEARCH, // SEARCH_SUGGEST
168 IDR_OMNIBOX_SEARCH, // SEARCH_SUGGEST_ENTITY
169 IDR_OMNIBOX_SEARCH, // SEARCH_SUGGEST_TAIL
170 IDR_OMNIBOX_SEARCH, // SEARCH_SUGGEST_PERSONALIZED
171 IDR_OMNIBOX_SEARCH, // SEARCH_SUGGEST_PROFILE
172 IDR_OMNIBOX_SEARCH, // SEARCH_OTHER_ENGINE
173 IDR_OMNIBOX_EXTENSION_APP, // EXTENSION_APP
174 IDR_OMNIBOX_SEARCH, // CONTACT_DEPRECATED
175 IDR_OMNIBOX_HTTP, // BOOKMARK_TITLE
176 IDR_OMNIBOX_HTTP, // NAVSUGGEST_PERSONALIZED
177 IDR_OMNIBOX_CALCULATOR, // CALCULATOR
179 #endif
180 static_assert(arraysize(kIcons) == AutocompleteMatchType::NUM_TYPES,
181 "icons array must have NUM_TYPES elements");
182 return kIcons[type];
185 // static
186 bool AutocompleteMatch::MoreRelevant(const AutocompleteMatch& elem1,
187 const AutocompleteMatch& elem2) {
188 // For equal-relevance matches, we sort alphabetically, so that providers
189 // who return multiple elements at the same priority get a "stable" sort
190 // across multiple updates.
191 return (elem1.relevance == elem2.relevance) ?
192 (elem1.contents < elem2.contents) : (elem1.relevance > elem2.relevance);
195 // static
196 bool AutocompleteMatch::DestinationsEqual(const AutocompleteMatch& elem1,
197 const AutocompleteMatch& elem2) {
198 if (elem1.stripped_destination_url.is_empty() &&
199 elem2.stripped_destination_url.is_empty())
200 return false;
201 return elem1.stripped_destination_url == elem2.stripped_destination_url;
204 // static
205 void AutocompleteMatch::ClassifyMatchInString(
206 const base::string16& find_text,
207 const base::string16& text,
208 int style,
209 ACMatchClassifications* classification) {
210 ClassifyLocationInString(text.find(find_text), find_text.length(),
211 text.length(), style, classification);
214 // static
215 void AutocompleteMatch::ClassifyLocationInString(
216 size_t match_location,
217 size_t match_length,
218 size_t overall_length,
219 int style,
220 ACMatchClassifications* classification) {
221 classification->clear();
223 // Don't classify anything about an empty string
224 // (AutocompleteMatch::Validate() checks this).
225 if (overall_length == 0)
226 return;
228 // Mark pre-match portion of string (if any).
229 if (match_location != 0) {
230 classification->push_back(ACMatchClassification(0, style));
233 // Mark matching portion of string.
234 if (match_location == base::string16::npos) {
235 // No match, above classification will suffice for whole string.
236 return;
238 // Classifying an empty match makes no sense and will lead to validation
239 // errors later.
240 DCHECK_GT(match_length, 0U);
241 classification->push_back(ACMatchClassification(match_location,
242 (style | ACMatchClassification::MATCH) & ~ACMatchClassification::DIM));
244 // Mark post-match portion of string (if any).
245 const size_t after_match(match_location + match_length);
246 if (after_match < overall_length) {
247 classification->push_back(ACMatchClassification(after_match, style));
251 // static
252 AutocompleteMatch::ACMatchClassifications
253 AutocompleteMatch::MergeClassifications(
254 const ACMatchClassifications& classifications1,
255 const ACMatchClassifications& classifications2) {
256 // We must return the empty vector only if both inputs are truly empty.
257 // The result of merging an empty vector with a single (0, NONE)
258 // classification is the latter one-entry vector.
259 if (IsTrivialClassification(classifications1))
260 return classifications2.empty() ? classifications1 : classifications2;
261 if (IsTrivialClassification(classifications2))
262 return classifications1;
264 ACMatchClassifications output;
265 for (ACMatchClassifications::const_iterator i = classifications1.begin(),
266 j = classifications2.begin(); i != classifications1.end();) {
267 AutocompleteMatch::AddLastClassificationIfNecessary(&output,
268 std::max(i->offset, j->offset), i->style | j->style);
269 const size_t next_i_offset = (i + 1) == classifications1.end() ?
270 static_cast<size_t>(-1) : (i + 1)->offset;
271 const size_t next_j_offset = (j + 1) == classifications2.end() ?
272 static_cast<size_t>(-1) : (j + 1)->offset;
273 if (next_i_offset >= next_j_offset)
274 ++j;
275 if (next_j_offset >= next_i_offset)
276 ++i;
279 return output;
282 // static
283 std::string AutocompleteMatch::ClassificationsToString(
284 const ACMatchClassifications& classifications) {
285 std::string serialized_classifications;
286 for (size_t i = 0; i < classifications.size(); ++i) {
287 if (i)
288 serialized_classifications += ',';
289 serialized_classifications += base::IntToString(classifications[i].offset) +
290 ',' + base::IntToString(classifications[i].style);
292 return serialized_classifications;
295 // static
296 ACMatchClassifications AutocompleteMatch::ClassificationsFromString(
297 const std::string& serialized_classifications) {
298 ACMatchClassifications classifications;
299 std::vector<std::string> tokens;
300 Tokenize(serialized_classifications, ",", &tokens);
301 DCHECK(!(tokens.size() & 1)); // The number of tokens should be even.
302 for (size_t i = 0; i < tokens.size(); i += 2) {
303 int classification_offset = 0;
304 int classification_style = ACMatchClassification::NONE;
305 if (!base::StringToInt(tokens[i], &classification_offset) ||
306 !base::StringToInt(tokens[i + 1], &classification_style)) {
307 NOTREACHED();
308 return classifications;
310 classifications.push_back(ACMatchClassification(classification_offset,
311 classification_style));
313 return classifications;
316 // static
317 void AutocompleteMatch::AddLastClassificationIfNecessary(
318 ACMatchClassifications* classifications,
319 size_t offset,
320 int style) {
321 DCHECK(classifications);
322 if (classifications->empty() || classifications->back().style != style) {
323 DCHECK(classifications->empty() ||
324 (offset > classifications->back().offset));
325 classifications->push_back(ACMatchClassification(offset, style));
329 // static
330 base::string16 AutocompleteMatch::SanitizeString(const base::string16& text) {
331 // NOTE: This logic is mirrored by |sanitizeString()| in
332 // omnibox_custom_bindings.js.
333 base::string16 result;
334 base::TrimWhitespace(text, base::TRIM_LEADING, &result);
335 base::RemoveChars(result, kInvalidChars, &result);
336 return result;
339 // static
340 bool AutocompleteMatch::IsSearchType(Type type) {
341 return type == AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED ||
342 type == AutocompleteMatchType::SEARCH_HISTORY ||
343 type == AutocompleteMatchType::SEARCH_SUGGEST ||
344 type == AutocompleteMatchType::SEARCH_OTHER_ENGINE ||
345 type == AutocompleteMatchType::CALCULATOR ||
346 IsSpecializedSearchType(type);
349 // static
350 bool AutocompleteMatch::IsSpecializedSearchType(Type type) {
351 return type == AutocompleteMatchType::SEARCH_SUGGEST_ENTITY ||
352 type == AutocompleteMatchType::SEARCH_SUGGEST_TAIL ||
353 type == AutocompleteMatchType::SEARCH_SUGGEST_PERSONALIZED ||
354 type == AutocompleteMatchType::SEARCH_SUGGEST_PROFILE;
357 // static
358 TemplateURL* AutocompleteMatch::GetTemplateURLWithKeyword(
359 TemplateURLService* template_url_service,
360 const base::string16& keyword,
361 const std::string& host) {
362 if (template_url_service == NULL)
363 return NULL;
364 TemplateURL* template_url = keyword.empty() ?
365 NULL : template_url_service->GetTemplateURLForKeyword(keyword);
366 return (template_url || host.empty()) ?
367 template_url : template_url_service->GetTemplateURLForHost(host);
370 // static
371 GURL AutocompleteMatch::GURLToStrippedGURL(
372 const GURL& url,
373 TemplateURLService* template_url_service,
374 const base::string16& keyword) {
375 if (!url.is_valid())
376 return url;
378 GURL stripped_destination_url = url;
380 // If the destination URL looks like it was generated from a TemplateURL,
381 // remove all substitutions other than the search terms. This allows us
382 // to eliminate cases like past search URLs from history that differ only
383 // by some obscure query param from each other or from the search/keyword
384 // provider matches.
385 TemplateURL* template_url = GetTemplateURLWithKeyword(
386 template_url_service, keyword, stripped_destination_url.host());
387 if (template_url != NULL &&
388 template_url->SupportsReplacement(
389 template_url_service->search_terms_data())) {
390 base::string16 search_terms;
391 if (template_url->ExtractSearchTermsFromURL(
392 stripped_destination_url,
393 template_url_service->search_terms_data(),
394 &search_terms)) {
395 stripped_destination_url =
396 GURL(template_url->url_ref().ReplaceSearchTerms(
397 TemplateURLRef::SearchTermsArgs(search_terms),
398 template_url_service->search_terms_data()));
402 // |replacements| keeps all the substitions we're going to make to
403 // from {destination_url} to {stripped_destination_url}. |need_replacement|
404 // is a helper variable that helps us keep track of whether we need
405 // to apply the replacement.
406 bool needs_replacement = false;
407 GURL::Replacements replacements;
409 // Remove the www. prefix from the host.
410 static const char prefix[] = "www.";
411 static const size_t prefix_len = arraysize(prefix) - 1;
412 std::string host = stripped_destination_url.host();
413 if (host.compare(0, prefix_len, prefix) == 0) {
414 replacements.SetHostStr(base::StringPiece(host).substr(prefix_len));
415 needs_replacement = true;
418 // Replace https protocol with http protocol.
419 if (stripped_destination_url.SchemeIs(url::kHttpsScheme)) {
420 replacements.SetScheme(url::kHttpScheme,
421 url::Component(0, strlen(url::kHttpScheme)));
422 needs_replacement = true;
425 if (needs_replacement)
426 stripped_destination_url = stripped_destination_url.ReplaceComponents(
427 replacements);
428 return stripped_destination_url;
431 void AutocompleteMatch::ComputeStrippedDestinationURL(
432 TemplateURLService* template_url_service) {
433 stripped_destination_url =
434 GURLToStrippedGURL(destination_url, template_url_service, keyword);
437 void AutocompleteMatch::EnsureUWYTIsAllowedToBeDefault(
438 const GURL& canonical_input_url,
439 TemplateURLService* template_url_service) {
440 if (!allowed_to_be_default_match) {
441 const GURL& stripped_canonical_input_url =
442 AutocompleteMatch::GURLToStrippedGURL(
443 canonical_input_url, template_url_service, base::string16());
444 ComputeStrippedDestinationURL(template_url_service);
445 allowed_to_be_default_match =
446 stripped_canonical_input_url == stripped_destination_url;
450 void AutocompleteMatch::GetKeywordUIState(
451 TemplateURLService* template_url_service,
452 base::string16* keyword,
453 bool* is_keyword_hint) const {
454 *is_keyword_hint = associated_keyword.get() != NULL;
455 keyword->assign(*is_keyword_hint ? associated_keyword->keyword :
456 GetSubstitutingExplicitlyInvokedKeyword(template_url_service));
459 base::string16 AutocompleteMatch::GetSubstitutingExplicitlyInvokedKeyword(
460 TemplateURLService* template_url_service) const {
461 if (transition != ui::PAGE_TRANSITION_KEYWORD ||
462 template_url_service == NULL) {
463 return base::string16();
466 const TemplateURL* t_url = GetTemplateURL(template_url_service, false);
467 return (t_url &&
468 t_url->SupportsReplacement(
469 template_url_service->search_terms_data())) ?
470 keyword : base::string16();
473 TemplateURL* AutocompleteMatch::GetTemplateURL(
474 TemplateURLService* template_url_service,
475 bool allow_fallback_to_destination_host) const {
476 return GetTemplateURLWithKeyword(
477 template_url_service, keyword,
478 allow_fallback_to_destination_host ?
479 destination_url.host() : std::string());
482 void AutocompleteMatch::RecordAdditionalInfo(const std::string& property,
483 const std::string& value) {
484 DCHECK(!property.empty());
485 DCHECK(!value.empty());
486 additional_info[property] = value;
489 void AutocompleteMatch::RecordAdditionalInfo(const std::string& property,
490 int value) {
491 RecordAdditionalInfo(property, base::IntToString(value));
494 void AutocompleteMatch::RecordAdditionalInfo(const std::string& property,
495 const base::Time& value) {
496 RecordAdditionalInfo(property,
497 base::UTF16ToUTF8(
498 base::TimeFormatShortDateAndTime(value)));
501 std::string AutocompleteMatch::GetAdditionalInfo(
502 const std::string& property) const {
503 AdditionalInfo::const_iterator i(additional_info.find(property));
504 return (i == additional_info.end()) ? std::string() : i->second;
507 bool AutocompleteMatch::IsVerbatimType() const {
508 const bool is_keyword_verbatim_match =
509 (type == AutocompleteMatchType::SEARCH_OTHER_ENGINE &&
510 provider != NULL &&
511 provider->type() == AutocompleteProvider::TYPE_SEARCH);
512 return type == AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED ||
513 type == AutocompleteMatchType::URL_WHAT_YOU_TYPED ||
514 is_keyword_verbatim_match;
517 bool AutocompleteMatch::SupportsDeletion() const {
518 if (deletable)
519 return true;
521 for (ACMatches::const_iterator it(duplicate_matches.begin());
522 it != duplicate_matches.end(); ++it) {
523 if (it->deletable)
524 return true;
526 return false;
529 #ifndef NDEBUG
530 void AutocompleteMatch::Validate() const {
531 ValidateClassifications(contents, contents_class);
532 ValidateClassifications(description, description_class);
535 void AutocompleteMatch::ValidateClassifications(
536 const base::string16& text,
537 const ACMatchClassifications& classifications) const {
538 if (text.empty()) {
539 DCHECK(classifications.empty());
540 return;
543 // The classifications should always cover the whole string.
544 DCHECK(!classifications.empty()) << "No classification for \"" << text << '"';
545 DCHECK_EQ(0U, classifications[0].offset)
546 << "Classification misses beginning for \"" << text << '"';
547 if (classifications.size() == 1)
548 return;
550 // The classifications should always be sorted.
551 size_t last_offset = classifications[0].offset;
552 for (ACMatchClassifications::const_iterator i(classifications.begin() + 1);
553 i != classifications.end(); ++i) {
554 const char* provider_name = provider ? provider->GetName() : "None";
555 DCHECK_GT(i->offset, last_offset)
556 << " Classification for \"" << text << "\" with offset of " << i->offset
557 << " is unsorted in relation to last offset of " << last_offset
558 << ". Provider: " << provider_name << ".";
559 DCHECK_LT(i->offset, text.length())
560 << " Classification of [" << i->offset << "," << text.length()
561 << "] is out of bounds for \"" << text << "\". Provider: "
562 << provider_name << ".";
563 last_offset = i->offset;
566 #endif