Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / components / omnibox / browser / autocomplete_match.cc
blobcd0ed71b2e6dcd5d55745916685889fbb42061a9
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/browser/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_split.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/browser/autocomplete_provider.h"
17 #include "components/omnibox/browser/suggestion_answer.h"
18 #include "components/search_engines/template_url.h"
19 #include "components/search_engines/template_url_service.h"
20 #include "components/url_formatter/url_formatter.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 // Returns true if one of the |terms_prefixed_by_http_or_https| matches the
32 // beginning of the URL (sans scheme). (Recall that
33 // |terms_prefixed_by_http_or_https|, for the input "http://a b" will be
34 // ["a"].) This suggests that the user wants a particular URL with a scheme
35 // in mind, hence the caller should not consider another URL like this one
36 // but with a different scheme to be a duplicate. |languages| is used to
37 // format punycoded URLs to decide if they match.
38 bool WordMatchesURLContent(
39 const std::vector<base::string16>& terms_prefixed_by_http_or_https,
40 const std::string& languages,
41 const GURL& url) {
42 size_t prefix_length =
43 url.scheme().length() + strlen(url::kStandardSchemeSeparator);
44 DCHECK_GE(url.spec().length(), prefix_length);
45 const base::string16& formatted_url = url_formatter::FormatUrl(
46 url, languages, url_formatter::kFormatUrlOmitNothing,
47 net::UnescapeRule::NORMAL, nullptr, nullptr, &prefix_length);
48 if (prefix_length == base::string16::npos)
49 return false;
50 const base::string16& formatted_url_without_scheme =
51 formatted_url.substr(prefix_length);
52 for (const auto& term : terms_prefixed_by_http_or_https) {
53 if (base::StartsWith(formatted_url_without_scheme, term,
54 base::CompareCase::SENSITIVE))
55 return true;
57 return false;
60 } // namespace
62 // AutocompleteMatch ----------------------------------------------------------
64 // static
65 const base::char16 AutocompleteMatch::kInvalidChars[] = {
66 '\n', '\r', '\t',
67 0x2028, // Line separator
68 0x2029, // Paragraph separator
72 AutocompleteMatch::AutocompleteMatch()
73 : provider(NULL),
74 relevance(0),
75 typed_count(-1),
76 deletable(false),
77 allowed_to_be_default_match(false),
78 swap_contents_and_description(false),
79 transition(ui::PAGE_TRANSITION_GENERATED),
80 type(AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED),
81 from_previous(false) {
84 AutocompleteMatch::AutocompleteMatch(AutocompleteProvider* provider,
85 int relevance,
86 bool deletable,
87 Type type)
88 : provider(provider),
89 relevance(relevance),
90 typed_count(-1),
91 deletable(deletable),
92 allowed_to_be_default_match(false),
93 swap_contents_and_description(false),
94 transition(ui::PAGE_TRANSITION_TYPED),
95 type(type),
96 from_previous(false) {
99 AutocompleteMatch::AutocompleteMatch(const AutocompleteMatch& match)
100 : provider(match.provider),
101 relevance(match.relevance),
102 typed_count(match.typed_count),
103 deletable(match.deletable),
104 fill_into_edit(match.fill_into_edit),
105 inline_autocompletion(match.inline_autocompletion),
106 allowed_to_be_default_match(match.allowed_to_be_default_match),
107 destination_url(match.destination_url),
108 stripped_destination_url(match.stripped_destination_url),
109 contents(match.contents),
110 contents_class(match.contents_class),
111 description(match.description),
112 description_class(match.description_class),
113 swap_contents_and_description(match.swap_contents_and_description),
114 answer_contents(match.answer_contents),
115 answer_type(match.answer_type),
116 answer(SuggestionAnswer::copy(match.answer.get())),
117 transition(match.transition),
118 type(match.type),
119 associated_keyword(match.associated_keyword.get() ?
120 new AutocompleteMatch(*match.associated_keyword) : NULL),
121 keyword(match.keyword),
122 from_previous(match.from_previous),
123 search_terms_args(match.search_terms_args.get() ?
124 new TemplateURLRef::SearchTermsArgs(*match.search_terms_args) :
125 NULL),
126 additional_info(match.additional_info),
127 duplicate_matches(match.duplicate_matches) {
130 AutocompleteMatch::~AutocompleteMatch() {
133 AutocompleteMatch& AutocompleteMatch::operator=(
134 const AutocompleteMatch& match) {
135 if (this == &match)
136 return *this;
138 provider = match.provider;
139 relevance = match.relevance;
140 typed_count = match.typed_count;
141 deletable = match.deletable;
142 fill_into_edit = match.fill_into_edit;
143 inline_autocompletion = match.inline_autocompletion;
144 allowed_to_be_default_match = match.allowed_to_be_default_match;
145 destination_url = match.destination_url;
146 stripped_destination_url = match.stripped_destination_url;
147 contents = match.contents;
148 contents_class = match.contents_class;
149 description = match.description;
150 description_class = match.description_class;
151 swap_contents_and_description = match.swap_contents_and_description;
152 answer_contents = match.answer_contents;
153 answer_type = match.answer_type;
154 answer = SuggestionAnswer::copy(match.answer.get());
155 transition = match.transition;
156 type = match.type;
157 associated_keyword.reset(match.associated_keyword.get() ?
158 new AutocompleteMatch(*match.associated_keyword) : NULL);
159 keyword = match.keyword;
160 from_previous = match.from_previous;
161 search_terms_args.reset(match.search_terms_args.get() ?
162 new TemplateURLRef::SearchTermsArgs(*match.search_terms_args) : NULL);
163 additional_info = match.additional_info;
164 duplicate_matches = match.duplicate_matches;
165 return *this;
168 // static
169 int AutocompleteMatch::TypeToIcon(Type type) {
170 #if !defined(OS_IOS)
171 static const int kIcons[] = {
172 IDR_OMNIBOX_HTTP, // URL_WHAT_YOU_TYPE
173 IDR_OMNIBOX_HTTP, // HISTORY_URL
174 IDR_OMNIBOX_HTTP, // HISTORY_TITLE
175 IDR_OMNIBOX_HTTP, // HISTORY_BODY
176 IDR_OMNIBOX_HTTP, // HISTORY_KEYWORD
177 IDR_OMNIBOX_HTTP, // NAVSUGGEST
178 IDR_OMNIBOX_SEARCH, // SEARCH_WHAT_YOU_TYPED
179 IDR_OMNIBOX_SEARCH, // SEARCH_HISTORY
180 IDR_OMNIBOX_SEARCH, // SEARCH_SUGGEST
181 IDR_OMNIBOX_SEARCH, // SEARCH_SUGGEST_ENTITY
182 IDR_OMNIBOX_SEARCH, // SEARCH_SUGGEST_TAIL
183 IDR_OMNIBOX_SEARCH, // SEARCH_SUGGEST_PERSONALIZED
184 IDR_OMNIBOX_SEARCH, // SEARCH_SUGGEST_PROFILE
185 IDR_OMNIBOX_SEARCH, // SEARCH_OTHER_ENGINE
186 IDR_OMNIBOX_EXTENSION_APP, // EXTENSION_APP
187 IDR_OMNIBOX_SEARCH, // CONTACT_DEPRECATED
188 IDR_OMNIBOX_HTTP, // BOOKMARK_TITLE
189 IDR_OMNIBOX_HTTP, // NAVSUGGEST_PERSONALIZED
190 IDR_OMNIBOX_CALCULATOR, // CALCULATOR
191 IDR_OMNIBOX_HTTP, // CLIPBOARD
193 #else
194 static const int kIcons[] = {
195 IDR_OMNIBOX_HTTP, // URL_WHAT_YOU_TYPE
196 IDR_OMNIBOX_HISTORY, // HISTORY_URL
197 IDR_OMNIBOX_HISTORY, // HISTORY_TITLE
198 IDR_OMNIBOX_HISTORY, // HISTORY_BODY
199 IDR_OMNIBOX_HISTORY, // HISTORY_KEYWORD
200 IDR_OMNIBOX_HTTP, // NAVSUGGEST
201 IDR_OMNIBOX_SEARCH, // SEARCH_WHAT_YOU_TYPED
202 IDR_OMNIBOX_HISTORY, // SEARCH_HISTORY
203 IDR_OMNIBOX_SEARCH, // SEARCH_SUGGEST
204 IDR_OMNIBOX_SEARCH, // SEARCH_SUGGEST_ENTITY
205 IDR_OMNIBOX_SEARCH, // SEARCH_SUGGEST_TAIL
206 IDR_OMNIBOX_SEARCH, // SEARCH_SUGGEST_PERSONALIZED
207 IDR_OMNIBOX_SEARCH, // SEARCH_SUGGEST_PROFILE
208 IDR_OMNIBOX_SEARCH, // SEARCH_OTHER_ENGINE
209 IDR_OMNIBOX_EXTENSION_APP, // EXTENSION_APP
210 IDR_OMNIBOX_SEARCH, // CONTACT_DEPRECATED
211 IDR_OMNIBOX_HTTP, // BOOKMARK_TITLE
212 IDR_OMNIBOX_HTTP, // NAVSUGGEST_PERSONALIZED
213 IDR_OMNIBOX_CALCULATOR, // CALCULATOR
214 IDR_OMNIBOX_HTTP, // CLIPBOARD
216 #endif
217 static_assert(arraysize(kIcons) == AutocompleteMatchType::NUM_TYPES,
218 "icons array must have NUM_TYPES elements");
219 return kIcons[type];
222 // static
223 bool AutocompleteMatch::MoreRelevant(const AutocompleteMatch& elem1,
224 const AutocompleteMatch& elem2) {
225 // For equal-relevance matches, we sort alphabetically, so that providers
226 // who return multiple elements at the same priority get a "stable" sort
227 // across multiple updates.
228 return (elem1.relevance == elem2.relevance) ?
229 (elem1.contents < elem2.contents) : (elem1.relevance > elem2.relevance);
232 // static
233 bool AutocompleteMatch::DestinationsEqual(const AutocompleteMatch& elem1,
234 const AutocompleteMatch& elem2) {
235 if (elem1.stripped_destination_url.is_empty() &&
236 elem2.stripped_destination_url.is_empty())
237 return false;
238 return elem1.stripped_destination_url == elem2.stripped_destination_url;
241 // static
242 void AutocompleteMatch::ClassifyMatchInString(
243 const base::string16& find_text,
244 const base::string16& text,
245 int style,
246 ACMatchClassifications* classification) {
247 ClassifyLocationInString(text.find(find_text), find_text.length(),
248 text.length(), style, classification);
251 // static
252 void AutocompleteMatch::ClassifyLocationInString(
253 size_t match_location,
254 size_t match_length,
255 size_t overall_length,
256 int style,
257 ACMatchClassifications* classification) {
258 classification->clear();
260 // Don't classify anything about an empty string
261 // (AutocompleteMatch::Validate() checks this).
262 if (overall_length == 0)
263 return;
265 // Mark pre-match portion of string (if any).
266 if (match_location != 0) {
267 classification->push_back(ACMatchClassification(0, style));
270 // Mark matching portion of string.
271 if (match_location == base::string16::npos) {
272 // No match, above classification will suffice for whole string.
273 return;
275 // Classifying an empty match makes no sense and will lead to validation
276 // errors later.
277 DCHECK_GT(match_length, 0U);
278 classification->push_back(ACMatchClassification(match_location,
279 (style | ACMatchClassification::MATCH) & ~ACMatchClassification::DIM));
281 // Mark post-match portion of string (if any).
282 const size_t after_match(match_location + match_length);
283 if (after_match < overall_length) {
284 classification->push_back(ACMatchClassification(after_match, style));
288 // static
289 AutocompleteMatch::ACMatchClassifications
290 AutocompleteMatch::MergeClassifications(
291 const ACMatchClassifications& classifications1,
292 const ACMatchClassifications& classifications2) {
293 // We must return the empty vector only if both inputs are truly empty.
294 // The result of merging an empty vector with a single (0, NONE)
295 // classification is the latter one-entry vector.
296 if (IsTrivialClassification(classifications1))
297 return classifications2.empty() ? classifications1 : classifications2;
298 if (IsTrivialClassification(classifications2))
299 return classifications1;
301 ACMatchClassifications output;
302 for (ACMatchClassifications::const_iterator i = classifications1.begin(),
303 j = classifications2.begin(); i != classifications1.end();) {
304 AutocompleteMatch::AddLastClassificationIfNecessary(&output,
305 std::max(i->offset, j->offset), i->style | j->style);
306 const size_t next_i_offset = (i + 1) == classifications1.end() ?
307 static_cast<size_t>(-1) : (i + 1)->offset;
308 const size_t next_j_offset = (j + 1) == classifications2.end() ?
309 static_cast<size_t>(-1) : (j + 1)->offset;
310 if (next_i_offset >= next_j_offset)
311 ++j;
312 if (next_j_offset >= next_i_offset)
313 ++i;
316 return output;
319 // static
320 std::string AutocompleteMatch::ClassificationsToString(
321 const ACMatchClassifications& classifications) {
322 std::string serialized_classifications;
323 for (size_t i = 0; i < classifications.size(); ++i) {
324 if (i)
325 serialized_classifications += ',';
326 serialized_classifications += base::IntToString(classifications[i].offset) +
327 ',' + base::IntToString(classifications[i].style);
329 return serialized_classifications;
332 // static
333 ACMatchClassifications AutocompleteMatch::ClassificationsFromString(
334 const std::string& serialized_classifications) {
335 ACMatchClassifications classifications;
336 std::vector<base::StringPiece> tokens = base::SplitStringPiece(
337 serialized_classifications, ",", base::KEEP_WHITESPACE,
338 base::SPLIT_WANT_NONEMPTY);
339 DCHECK(!(tokens.size() & 1)); // The number of tokens should be even.
340 for (size_t i = 0; i < tokens.size(); i += 2) {
341 int classification_offset = 0;
342 int classification_style = ACMatchClassification::NONE;
343 if (!base::StringToInt(tokens[i], &classification_offset) ||
344 !base::StringToInt(tokens[i + 1], &classification_style)) {
345 NOTREACHED();
346 return classifications;
348 classifications.push_back(ACMatchClassification(classification_offset,
349 classification_style));
351 return classifications;
354 // static
355 void AutocompleteMatch::AddLastClassificationIfNecessary(
356 ACMatchClassifications* classifications,
357 size_t offset,
358 int style) {
359 DCHECK(classifications);
360 if (classifications->empty() || classifications->back().style != style) {
361 DCHECK(classifications->empty() ||
362 (offset > classifications->back().offset));
363 classifications->push_back(ACMatchClassification(offset, style));
367 // static
368 bool AutocompleteMatch::HasMatchStyle(
369 const ACMatchClassifications& classifications) {
370 for (const auto& it : classifications) {
371 if (it.style & AutocompleteMatch::ACMatchClassification::MATCH)
372 return true;
374 return false;
377 // static
378 base::string16 AutocompleteMatch::SanitizeString(const base::string16& text) {
379 // NOTE: This logic is mirrored by |sanitizeString()| in
380 // omnibox_custom_bindings.js.
381 base::string16 result;
382 base::TrimWhitespace(text, base::TRIM_LEADING, &result);
383 base::RemoveChars(result, kInvalidChars, &result);
384 return result;
387 // static
388 bool AutocompleteMatch::IsSearchType(Type type) {
389 return type == AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED ||
390 type == AutocompleteMatchType::SEARCH_HISTORY ||
391 type == AutocompleteMatchType::SEARCH_SUGGEST ||
392 type == AutocompleteMatchType::SEARCH_OTHER_ENGINE ||
393 type == AutocompleteMatchType::CALCULATOR ||
394 IsSpecializedSearchType(type);
397 // static
398 bool AutocompleteMatch::IsSpecializedSearchType(Type type) {
399 return type == AutocompleteMatchType::SEARCH_SUGGEST_ENTITY ||
400 type == AutocompleteMatchType::SEARCH_SUGGEST_TAIL ||
401 type == AutocompleteMatchType::SEARCH_SUGGEST_PERSONALIZED ||
402 type == AutocompleteMatchType::SEARCH_SUGGEST_PROFILE;
405 // static
406 TemplateURL* AutocompleteMatch::GetTemplateURLWithKeyword(
407 TemplateURLService* template_url_service,
408 const base::string16& keyword,
409 const std::string& host) {
410 if (template_url_service == NULL)
411 return NULL;
412 TemplateURL* template_url = keyword.empty() ?
413 NULL : template_url_service->GetTemplateURLForKeyword(keyword);
414 return (template_url || host.empty()) ?
415 template_url : template_url_service->GetTemplateURLForHost(host);
418 // static
419 GURL AutocompleteMatch::GURLToStrippedGURL(
420 const GURL& url,
421 const AutocompleteInput& input,
422 const std::string& languages,
423 TemplateURLService* template_url_service,
424 const base::string16& keyword) {
425 if (!url.is_valid())
426 return url;
428 GURL stripped_destination_url = url;
430 // If the destination URL looks like it was generated from a TemplateURL,
431 // remove all substitutions other than the search terms. This allows us
432 // to eliminate cases like past search URLs from history that differ only
433 // by some obscure query param from each other or from the search/keyword
434 // provider matches.
435 TemplateURL* template_url = GetTemplateURLWithKeyword(
436 template_url_service, keyword, stripped_destination_url.host());
437 if (template_url != NULL &&
438 template_url->SupportsReplacement(
439 template_url_service->search_terms_data())) {
440 base::string16 search_terms;
441 if (template_url->ExtractSearchTermsFromURL(
442 stripped_destination_url,
443 template_url_service->search_terms_data(),
444 &search_terms)) {
445 stripped_destination_url =
446 GURL(template_url->url_ref().ReplaceSearchTerms(
447 TemplateURLRef::SearchTermsArgs(search_terms),
448 template_url_service->search_terms_data()));
452 // |replacements| keeps all the substitions we're going to make to
453 // from {destination_url} to {stripped_destination_url}. |need_replacement|
454 // is a helper variable that helps us keep track of whether we need
455 // to apply the replacement.
456 bool needs_replacement = false;
457 GURL::Replacements replacements;
459 // Remove the www. prefix from the host.
460 static const char prefix[] = "www.";
461 static const size_t prefix_len = arraysize(prefix) - 1;
462 std::string host = stripped_destination_url.host();
463 if (host.compare(0, prefix_len, prefix) == 0) {
464 replacements.SetHostStr(base::StringPiece(host).substr(prefix_len));
465 needs_replacement = true;
468 // Remove any trailing slash (if it's not a lone slash), or add a slash (to
469 // make a lone slash) if the path is empty. (We can't unconditionally
470 // remove even lone slashes because for some schemes the path must consist
471 // of at least a slash.)
472 const std::string& path = stripped_destination_url.path();
473 if ((path.length() > 1) && (path[path.length() - 1] == '/')) {
474 replacements.SetPathStr(
475 base::StringPiece(path).substr(0, path.length() - 1));
476 needs_replacement = true;
477 } else if (path.empty()) {
478 static const char slash[] = "/";
479 replacements.SetPathStr(base::StringPiece(slash));
480 needs_replacement = true;
483 // Replace https protocol with http, as long as the user didn't explicitly
484 // specify one of the two.
485 if (stripped_destination_url.SchemeIs(url::kHttpsScheme) &&
486 (input.terms_prefixed_by_http_or_https().empty() ||
487 !WordMatchesURLContent(
488 input.terms_prefixed_by_http_or_https(), languages, url))) {
489 replacements.SetScheme(url::kHttpScheme,
490 url::Component(0, strlen(url::kHttpScheme)));
491 needs_replacement = true;
494 if (needs_replacement)
495 stripped_destination_url = stripped_destination_url.ReplaceComponents(
496 replacements);
497 return stripped_destination_url;
500 void AutocompleteMatch::ComputeStrippedDestinationURL(
501 const AutocompleteInput& input,
502 const std::string& languages,
503 TemplateURLService* template_url_service) {
504 stripped_destination_url = GURLToStrippedGURL(
505 destination_url, input, languages, template_url_service, keyword);
508 void AutocompleteMatch::EnsureUWYTIsAllowedToBeDefault(
509 const AutocompleteInput& input,
510 const std::string& languages,
511 TemplateURLService* template_url_service) {
512 if (!allowed_to_be_default_match) {
513 const GURL& stripped_canonical_input_url =
514 AutocompleteMatch::GURLToStrippedGURL(
515 input.canonicalized_url(), input, languages, template_url_service,
516 base::string16());
517 ComputeStrippedDestinationURL(input, languages, template_url_service);
518 allowed_to_be_default_match =
519 stripped_canonical_input_url == stripped_destination_url;
523 void AutocompleteMatch::GetKeywordUIState(
524 TemplateURLService* template_url_service,
525 base::string16* keyword,
526 bool* is_keyword_hint) const {
527 *is_keyword_hint = associated_keyword.get() != NULL;
528 keyword->assign(*is_keyword_hint ? associated_keyword->keyword :
529 GetSubstitutingExplicitlyInvokedKeyword(template_url_service));
532 base::string16 AutocompleteMatch::GetSubstitutingExplicitlyInvokedKeyword(
533 TemplateURLService* template_url_service) const {
534 if (transition != ui::PAGE_TRANSITION_KEYWORD ||
535 template_url_service == NULL) {
536 return base::string16();
539 const TemplateURL* t_url = GetTemplateURL(template_url_service, false);
540 return (t_url &&
541 t_url->SupportsReplacement(
542 template_url_service->search_terms_data())) ?
543 keyword : base::string16();
546 TemplateURL* AutocompleteMatch::GetTemplateURL(
547 TemplateURLService* template_url_service,
548 bool allow_fallback_to_destination_host) const {
549 return GetTemplateURLWithKeyword(
550 template_url_service, keyword,
551 allow_fallback_to_destination_host ?
552 destination_url.host() : std::string());
555 void AutocompleteMatch::RecordAdditionalInfo(const std::string& property,
556 const std::string& value) {
557 DCHECK(!property.empty());
558 DCHECK(!value.empty());
559 additional_info[property] = value;
562 void AutocompleteMatch::RecordAdditionalInfo(const std::string& property,
563 int value) {
564 RecordAdditionalInfo(property, base::IntToString(value));
567 void AutocompleteMatch::RecordAdditionalInfo(const std::string& property,
568 const base::Time& value) {
569 RecordAdditionalInfo(property,
570 base::UTF16ToUTF8(
571 base::TimeFormatShortDateAndTime(value)));
574 std::string AutocompleteMatch::GetAdditionalInfo(
575 const std::string& property) const {
576 AdditionalInfo::const_iterator i(additional_info.find(property));
577 return (i == additional_info.end()) ? std::string() : i->second;
580 bool AutocompleteMatch::IsVerbatimType() const {
581 const bool is_keyword_verbatim_match =
582 (type == AutocompleteMatchType::SEARCH_OTHER_ENGINE &&
583 provider != NULL &&
584 provider->type() == AutocompleteProvider::TYPE_SEARCH);
585 return type == AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED ||
586 type == AutocompleteMatchType::URL_WHAT_YOU_TYPED ||
587 is_keyword_verbatim_match;
590 bool AutocompleteMatch::SupportsDeletion() const {
591 if (deletable)
592 return true;
594 for (ACMatches::const_iterator it(duplicate_matches.begin());
595 it != duplicate_matches.end(); ++it) {
596 if (it->deletable)
597 return true;
599 return false;
602 void AutocompleteMatch::PossiblySwapContentsAndDescriptionForDisplay() {
603 if (swap_contents_and_description) {
604 std::swap(contents, description);
605 std::swap(contents_class, description_class);
609 #ifndef NDEBUG
610 void AutocompleteMatch::Validate() const {
611 ValidateClassifications(contents, contents_class);
612 ValidateClassifications(description, description_class);
615 void AutocompleteMatch::ValidateClassifications(
616 const base::string16& text,
617 const ACMatchClassifications& classifications) const {
618 if (text.empty()) {
619 DCHECK(classifications.empty());
620 return;
623 // The classifications should always cover the whole string.
624 DCHECK(!classifications.empty()) << "No classification for \"" << text << '"';
625 DCHECK_EQ(0U, classifications[0].offset)
626 << "Classification misses beginning for \"" << text << '"';
627 if (classifications.size() == 1)
628 return;
630 // The classifications should always be sorted.
631 size_t last_offset = classifications[0].offset;
632 for (ACMatchClassifications::const_iterator i(classifications.begin() + 1);
633 i != classifications.end(); ++i) {
634 const char* provider_name = provider ? provider->GetName() : "None";
635 DCHECK_GT(i->offset, last_offset)
636 << " Classification for \"" << text << "\" with offset of " << i->offset
637 << " is unsorted in relation to last offset of " << last_offset
638 << ". Provider: " << provider_name << ".";
639 DCHECK_LT(i->offset, text.length())
640 << " Classification of [" << i->offset << "," << text.length()
641 << "] is out of bounds for \"" << text << "\". Provider: "
642 << provider_name << ".";
643 last_offset = i->offset;
646 #endif