| OLD | NEW |
| (Empty) |
| 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. | |
| 4 | |
| 5 #include "components/autocomplete/autocomplete_match.h" | |
| 6 | |
| 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 "components/autocomplete/autocomplete_provider.h" | |
| 15 #include "components/search_engines/template_url.h" | |
| 16 #include "components/search_engines/template_url_service.h" | |
| 17 #include "grit/component_scaled_resources.h" | |
| 18 | |
| 19 namespace { | |
| 20 | |
| 21 bool IsTrivialClassification(const ACMatchClassifications& classifications) { | |
| 22 return classifications.empty() || | |
| 23 ((classifications.size() == 1) && | |
| 24 (classifications.back().style == ACMatchClassification::NONE)); | |
| 25 } | |
| 26 | |
| 27 } // namespace | |
| 28 | |
| 29 // AutocompleteMatch ---------------------------------------------------------- | |
| 30 | |
| 31 // static | |
| 32 const base::char16 AutocompleteMatch::kInvalidChars[] = { | |
| 33 '\n', '\r', '\t', | |
| 34 0x2028, // Line separator | |
| 35 0x2029, // Paragraph separator | |
| 36 0 | |
| 37 }; | |
| 38 | |
| 39 AutocompleteMatch::AutocompleteMatch() | |
| 40 : provider(NULL), | |
| 41 relevance(0), | |
| 42 typed_count(-1), | |
| 43 deletable(false), | |
| 44 allowed_to_be_default_match(false), | |
| 45 transition(content::PAGE_TRANSITION_GENERATED), | |
| 46 is_history_what_you_typed_match(false), | |
| 47 type(AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED), | |
| 48 from_previous(false) { | |
| 49 } | |
| 50 | |
| 51 AutocompleteMatch::AutocompleteMatch(AutocompleteProvider* provider, | |
| 52 int relevance, | |
| 53 bool deletable, | |
| 54 Type type) | |
| 55 : provider(provider), | |
| 56 relevance(relevance), | |
| 57 typed_count(-1), | |
| 58 deletable(deletable), | |
| 59 allowed_to_be_default_match(false), | |
| 60 transition(content::PAGE_TRANSITION_TYPED), | |
| 61 is_history_what_you_typed_match(false), | |
| 62 type(type), | |
| 63 from_previous(false) { | |
| 64 } | |
| 65 | |
| 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 transition(match.transition), | |
| 83 is_history_what_you_typed_match(match.is_history_what_you_typed_match), | |
| 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) { | |
| 94 } | |
| 95 | |
| 96 AutocompleteMatch::~AutocompleteMatch() { | |
| 97 } | |
| 98 | |
| 99 AutocompleteMatch& AutocompleteMatch::operator=( | |
| 100 const AutocompleteMatch& match) { | |
| 101 if (this == &match) | |
| 102 return *this; | |
| 103 | |
| 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 transition = match.transition; | |
| 120 is_history_what_you_typed_match = match.is_history_what_you_typed_match; | |
| 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; | |
| 131 } | |
| 132 | |
| 133 // static | |
| 134 int AutocompleteMatch::TypeToIcon(Type type) { | |
| 135 int icons[] = { | |
| 136 IDR_OMNIBOX_HTTP, | |
| 137 IDR_OMNIBOX_HTTP, | |
| 138 IDR_OMNIBOX_HTTP, | |
| 139 IDR_OMNIBOX_HTTP, | |
| 140 IDR_OMNIBOX_HTTP, | |
| 141 IDR_OMNIBOX_HTTP, | |
| 142 IDR_OMNIBOX_SEARCH, | |
| 143 IDR_OMNIBOX_SEARCH, | |
| 144 IDR_OMNIBOX_SEARCH, | |
| 145 IDR_OMNIBOX_SEARCH, | |
| 146 IDR_OMNIBOX_SEARCH, | |
| 147 IDR_OMNIBOX_SEARCH, | |
| 148 IDR_OMNIBOX_SEARCH, | |
| 149 IDR_OMNIBOX_SEARCH, | |
| 150 IDR_OMNIBOX_EXTENSION_APP, | |
| 151 IDR_OMNIBOX_SEARCH, | |
| 152 IDR_OMNIBOX_HTTP, | |
| 153 IDR_OMNIBOX_HTTP, | |
| 154 IDR_OMNIBOX_SEARCH, | |
| 155 }; | |
| 156 COMPILE_ASSERT(arraysize(icons) == AutocompleteMatchType::NUM_TYPES, | |
| 157 icons_array_must_match_type_enum); | |
| 158 return icons[type]; | |
| 159 } | |
| 160 | |
| 161 // static | |
| 162 bool AutocompleteMatch::MoreRelevant(const AutocompleteMatch& elem1, | |
| 163 const AutocompleteMatch& elem2) { | |
| 164 // For equal-relevance matches, we sort alphabetically, so that providers | |
| 165 // who return multiple elements at the same priority get a "stable" sort | |
| 166 // across multiple updates. | |
| 167 return (elem1.relevance == elem2.relevance) ? | |
| 168 (elem1.contents < elem2.contents) : (elem1.relevance > elem2.relevance); | |
| 169 } | |
| 170 | |
| 171 // static | |
| 172 bool AutocompleteMatch::DestinationsEqual(const AutocompleteMatch& elem1, | |
| 173 const AutocompleteMatch& elem2) { | |
| 174 if (elem1.stripped_destination_url.is_empty() && | |
| 175 elem2.stripped_destination_url.is_empty()) | |
| 176 return false; | |
| 177 return elem1.stripped_destination_url == elem2.stripped_destination_url; | |
| 178 } | |
| 179 | |
| 180 // static | |
| 181 void AutocompleteMatch::ClassifyMatchInString( | |
| 182 const base::string16& find_text, | |
| 183 const base::string16& text, | |
| 184 int style, | |
| 185 ACMatchClassifications* classification) { | |
| 186 ClassifyLocationInString(text.find(find_text), find_text.length(), | |
| 187 text.length(), style, classification); | |
| 188 } | |
| 189 | |
| 190 // static | |
| 191 void AutocompleteMatch::ClassifyLocationInString( | |
| 192 size_t match_location, | |
| 193 size_t match_length, | |
| 194 size_t overall_length, | |
| 195 int style, | |
| 196 ACMatchClassifications* classification) { | |
| 197 classification->clear(); | |
| 198 | |
| 199 // Don't classify anything about an empty string | |
| 200 // (AutocompleteMatch::Validate() checks this). | |
| 201 if (overall_length == 0) | |
| 202 return; | |
| 203 | |
| 204 // Mark pre-match portion of string (if any). | |
| 205 if (match_location != 0) { | |
| 206 classification->push_back(ACMatchClassification(0, style)); | |
| 207 } | |
| 208 | |
| 209 // Mark matching portion of string. | |
| 210 if (match_location == base::string16::npos) { | |
| 211 // No match, above classification will suffice for whole string. | |
| 212 return; | |
| 213 } | |
| 214 // Classifying an empty match makes no sense and will lead to validation | |
| 215 // errors later. | |
| 216 DCHECK_GT(match_length, 0U); | |
| 217 classification->push_back(ACMatchClassification(match_location, | |
| 218 (style | ACMatchClassification::MATCH) & ~ACMatchClassification::DIM)); | |
| 219 | |
| 220 // Mark post-match portion of string (if any). | |
| 221 const size_t after_match(match_location + match_length); | |
| 222 if (after_match < overall_length) { | |
| 223 classification->push_back(ACMatchClassification(after_match, style)); | |
| 224 } | |
| 225 } | |
| 226 | |
| 227 // static | |
| 228 AutocompleteMatch::ACMatchClassifications | |
| 229 AutocompleteMatch::MergeClassifications( | |
| 230 const ACMatchClassifications& classifications1, | |
| 231 const ACMatchClassifications& classifications2) { | |
| 232 // We must return the empty vector only if both inputs are truly empty. | |
| 233 // The result of merging an empty vector with a single (0, NONE) | |
| 234 // classification is the latter one-entry vector. | |
| 235 if (IsTrivialClassification(classifications1)) | |
| 236 return classifications2.empty() ? classifications1 : classifications2; | |
| 237 if (IsTrivialClassification(classifications2)) | |
| 238 return classifications1; | |
| 239 | |
| 240 ACMatchClassifications output; | |
| 241 for (ACMatchClassifications::const_iterator i = classifications1.begin(), | |
| 242 j = classifications2.begin(); i != classifications1.end();) { | |
| 243 AutocompleteMatch::AddLastClassificationIfNecessary(&output, | |
| 244 std::max(i->offset, j->offset), i->style | j->style); | |
| 245 const size_t next_i_offset = (i + 1) == classifications1.end() ? | |
| 246 static_cast<size_t>(-1) : (i + 1)->offset; | |
| 247 const size_t next_j_offset = (j + 1) == classifications2.end() ? | |
| 248 static_cast<size_t>(-1) : (j + 1)->offset; | |
| 249 if (next_i_offset >= next_j_offset) | |
| 250 ++j; | |
| 251 if (next_j_offset >= next_i_offset) | |
| 252 ++i; | |
| 253 } | |
| 254 | |
| 255 return output; | |
| 256 } | |
| 257 | |
| 258 // static | |
| 259 std::string AutocompleteMatch::ClassificationsToString( | |
| 260 const ACMatchClassifications& classifications) { | |
| 261 std::string serialized_classifications; | |
| 262 for (size_t i = 0; i < classifications.size(); ++i) { | |
| 263 if (i) | |
| 264 serialized_classifications += ','; | |
| 265 serialized_classifications += base::IntToString(classifications[i].offset) + | |
| 266 ',' + base::IntToString(classifications[i].style); | |
| 267 } | |
| 268 return serialized_classifications; | |
| 269 } | |
| 270 | |
| 271 // static | |
| 272 ACMatchClassifications AutocompleteMatch::ClassificationsFromString( | |
| 273 const std::string& serialized_classifications) { | |
| 274 ACMatchClassifications classifications; | |
| 275 std::vector<std::string> tokens; | |
| 276 Tokenize(serialized_classifications, ",", &tokens); | |
| 277 DCHECK(!(tokens.size() & 1)); // The number of tokens should be even. | |
| 278 for (size_t i = 0; i < tokens.size(); i += 2) { | |
| 279 int classification_offset = 0; | |
| 280 int classification_style = ACMatchClassification::NONE; | |
| 281 if (!base::StringToInt(tokens[i], &classification_offset) || | |
| 282 !base::StringToInt(tokens[i + 1], &classification_style)) { | |
| 283 NOTREACHED(); | |
| 284 return classifications; | |
| 285 } | |
| 286 classifications.push_back(ACMatchClassification(classification_offset, | |
| 287 classification_style)); | |
| 288 } | |
| 289 return classifications; | |
| 290 } | |
| 291 | |
| 292 // static | |
| 293 void AutocompleteMatch::AddLastClassificationIfNecessary( | |
| 294 ACMatchClassifications* classifications, | |
| 295 size_t offset, | |
| 296 int style) { | |
| 297 DCHECK(classifications); | |
| 298 if (classifications->empty() || classifications->back().style != style) { | |
| 299 DCHECK(classifications->empty() || | |
| 300 (offset > classifications->back().offset)); | |
| 301 classifications->push_back(ACMatchClassification(offset, style)); | |
| 302 } | |
| 303 } | |
| 304 | |
| 305 // static | |
| 306 base::string16 AutocompleteMatch::SanitizeString(const base::string16& text) { | |
| 307 // NOTE: This logic is mirrored by |sanitizeString()| in | |
| 308 // omnibox_custom_bindings.js. | |
| 309 base::string16 result; | |
| 310 base::TrimWhitespace(text, base::TRIM_LEADING, &result); | |
| 311 base::RemoveChars(result, kInvalidChars, &result); | |
| 312 return result; | |
| 313 } | |
| 314 | |
| 315 // static | |
| 316 bool AutocompleteMatch::IsSearchType(Type type) { | |
| 317 return type == AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED || | |
| 318 type == AutocompleteMatchType::SEARCH_HISTORY || | |
| 319 type == AutocompleteMatchType::SEARCH_SUGGEST || | |
| 320 type == AutocompleteMatchType::SEARCH_OTHER_ENGINE || | |
| 321 IsSpecializedSearchType(type); | |
| 322 } | |
| 323 | |
| 324 // static | |
| 325 bool AutocompleteMatch::IsSpecializedSearchType(Type type) { | |
| 326 return type == AutocompleteMatchType::SEARCH_SUGGEST_ENTITY || | |
| 327 type == AutocompleteMatchType::SEARCH_SUGGEST_INFINITE || | |
| 328 type == AutocompleteMatchType::SEARCH_SUGGEST_PERSONALIZED || | |
| 329 type == AutocompleteMatchType::SEARCH_SUGGEST_PROFILE || | |
| 330 type == AutocompleteMatchType::SEARCH_SUGGEST_ANSWER; | |
| 331 } | |
| 332 | |
| 333 // static | |
| 334 TemplateURL* AutocompleteMatch::GetTemplateURLWithKeyword( | |
| 335 TemplateURLService* template_url_service, | |
| 336 const base::string16& keyword, | |
| 337 const std::string& host) { | |
| 338 if (template_url_service == NULL) | |
| 339 return NULL; | |
| 340 TemplateURL* template_url = keyword.empty() ? | |
| 341 NULL : template_url_service->GetTemplateURLForKeyword(keyword); | |
| 342 return (template_url || host.empty()) ? | |
| 343 template_url : template_url_service->GetTemplateURLForHost(host); | |
| 344 } | |
| 345 | |
| 346 // static | |
| 347 GURL AutocompleteMatch::GURLToStrippedGURL( | |
| 348 const GURL& url, | |
| 349 TemplateURLService* template_url_service, | |
| 350 const base::string16& keyword) { | |
| 351 if (!url.is_valid()) | |
| 352 return url; | |
| 353 | |
| 354 GURL stripped_destination_url = url; | |
| 355 | |
| 356 // If the destination URL looks like it was generated from a TemplateURL, | |
| 357 // remove all substitutions other than the search terms. This allows us | |
| 358 // to eliminate cases like past search URLs from history that differ only | |
| 359 // by some obscure query param from each other or from the search/keyword | |
| 360 // provider matches. | |
| 361 TemplateURL* template_url = GetTemplateURLWithKeyword( | |
| 362 template_url_service, keyword, stripped_destination_url.host()); | |
| 363 if (template_url != NULL && | |
| 364 template_url->SupportsReplacement( | |
| 365 template_url_service->search_terms_data())) { | |
| 366 base::string16 search_terms; | |
| 367 if (template_url->ExtractSearchTermsFromURL( | |
| 368 stripped_destination_url, | |
| 369 template_url_service->search_terms_data(), | |
| 370 &search_terms)) { | |
| 371 stripped_destination_url = | |
| 372 GURL(template_url->url_ref().ReplaceSearchTerms( | |
| 373 TemplateURLRef::SearchTermsArgs(search_terms), | |
| 374 template_url_service->search_terms_data())); | |
| 375 } | |
| 376 } | |
| 377 | |
| 378 // |replacements| keeps all the substitions we're going to make to | |
| 379 // from {destination_url} to {stripped_destination_url}. |need_replacement| | |
| 380 // is a helper variable that helps us keep track of whether we need | |
| 381 // to apply the replacement. | |
| 382 bool needs_replacement = false; | |
| 383 GURL::Replacements replacements; | |
| 384 | |
| 385 // Remove the www. prefix from the host. | |
| 386 static const char prefix[] = "www."; | |
| 387 static const size_t prefix_len = arraysize(prefix) - 1; | |
| 388 std::string host = stripped_destination_url.host(); | |
| 389 if (host.compare(0, prefix_len, prefix) == 0) { | |
| 390 host = host.substr(prefix_len); | |
| 391 replacements.SetHostStr(host); | |
| 392 needs_replacement = true; | |
| 393 } | |
| 394 | |
| 395 // Replace https protocol with http protocol. | |
| 396 if (stripped_destination_url.SchemeIs(url::kHttpsScheme)) { | |
| 397 replacements.SetScheme(url::kHttpScheme, | |
| 398 url::Component(0, strlen(url::kHttpScheme))); | |
| 399 needs_replacement = true; | |
| 400 } | |
| 401 | |
| 402 if (needs_replacement) | |
| 403 stripped_destination_url = stripped_destination_url.ReplaceComponents( | |
| 404 replacements); | |
| 405 return stripped_destination_url; | |
| 406 } | |
| 407 | |
| 408 void AutocompleteMatch::ComputeStrippedDestinationURL( | |
| 409 TemplateURLService* template_url_service) { | |
| 410 stripped_destination_url = | |
| 411 GURLToStrippedGURL(destination_url, template_url_service, keyword); | |
| 412 } | |
| 413 | |
| 414 void AutocompleteMatch::EnsureUWYTIsAllowedToBeDefault( | |
| 415 const GURL& canonical_input_url, | |
| 416 TemplateURLService* template_url_service) { | |
| 417 if (!allowed_to_be_default_match) { | |
| 418 const GURL& stripped_canonical_input_url = | |
| 419 AutocompleteMatch::GURLToStrippedGURL( | |
| 420 canonical_input_url, template_url_service, base::string16()); | |
| 421 ComputeStrippedDestinationURL(template_url_service); | |
| 422 allowed_to_be_default_match = | |
| 423 stripped_canonical_input_url == stripped_destination_url; | |
| 424 } | |
| 425 } | |
| 426 | |
| 427 void AutocompleteMatch::GetKeywordUIState( | |
| 428 TemplateURLService* template_url_service, | |
| 429 base::string16* keyword, | |
| 430 bool* is_keyword_hint) const { | |
| 431 *is_keyword_hint = associated_keyword.get() != NULL; | |
| 432 keyword->assign(*is_keyword_hint ? associated_keyword->keyword : | |
| 433 GetSubstitutingExplicitlyInvokedKeyword(template_url_service)); | |
| 434 } | |
| 435 | |
| 436 base::string16 AutocompleteMatch::GetSubstitutingExplicitlyInvokedKeyword( | |
| 437 TemplateURLService* template_url_service) const { | |
| 438 if (transition != content::PAGE_TRANSITION_KEYWORD || | |
| 439 template_url_service == NULL) { | |
| 440 return base::string16(); | |
| 441 } | |
| 442 | |
| 443 const TemplateURL* t_url = GetTemplateURL(template_url_service, false); | |
| 444 return (t_url && | |
| 445 t_url->SupportsReplacement( | |
| 446 template_url_service->search_terms_data())) ? | |
| 447 keyword : base::string16(); | |
| 448 } | |
| 449 | |
| 450 TemplateURL* AutocompleteMatch::GetTemplateURL( | |
| 451 TemplateURLService* template_url_service, | |
| 452 bool allow_fallback_to_destination_host) const { | |
| 453 return GetTemplateURLWithKeyword( | |
| 454 template_url_service, keyword, | |
| 455 allow_fallback_to_destination_host ? | |
| 456 destination_url.host() : std::string()); | |
| 457 } | |
| 458 | |
| 459 void AutocompleteMatch::RecordAdditionalInfo(const std::string& property, | |
| 460 const std::string& value) { | |
| 461 DCHECK(!property.empty()); | |
| 462 DCHECK(!value.empty()); | |
| 463 additional_info[property] = value; | |
| 464 } | |
| 465 | |
| 466 void AutocompleteMatch::RecordAdditionalInfo(const std::string& property, | |
| 467 int value) { | |
| 468 RecordAdditionalInfo(property, base::IntToString(value)); | |
| 469 } | |
| 470 | |
| 471 void AutocompleteMatch::RecordAdditionalInfo(const std::string& property, | |
| 472 const base::Time& value) { | |
| 473 RecordAdditionalInfo(property, | |
| 474 base::UTF16ToUTF8( | |
| 475 base::TimeFormatShortDateAndTime(value))); | |
| 476 } | |
| 477 | |
| 478 std::string AutocompleteMatch::GetAdditionalInfo( | |
| 479 const std::string& property) const { | |
| 480 AdditionalInfo::const_iterator i(additional_info.find(property)); | |
| 481 return (i == additional_info.end()) ? std::string() : i->second; | |
| 482 } | |
| 483 | |
| 484 bool AutocompleteMatch::IsVerbatimType() const { | |
| 485 const bool is_keyword_verbatim_match = | |
| 486 (type == AutocompleteMatchType::SEARCH_OTHER_ENGINE && | |
| 487 provider != NULL && | |
| 488 provider->type() == AutocompleteProvider::TYPE_SEARCH); | |
| 489 return type == AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED || | |
| 490 type == AutocompleteMatchType::URL_WHAT_YOU_TYPED || | |
| 491 is_keyword_verbatim_match; | |
| 492 } | |
| 493 | |
| 494 bool AutocompleteMatch::SupportsDeletion() const { | |
| 495 if (deletable) | |
| 496 return true; | |
| 497 | |
| 498 for (ACMatches::const_iterator it(duplicate_matches.begin()); | |
| 499 it != duplicate_matches.end(); ++it) { | |
| 500 if (it->deletable) | |
| 501 return true; | |
| 502 } | |
| 503 return false; | |
| 504 } | |
| 505 | |
| 506 #ifndef NDEBUG | |
| 507 void AutocompleteMatch::Validate() const { | |
| 508 ValidateClassifications(contents, contents_class); | |
| 509 ValidateClassifications(description, description_class); | |
| 510 } | |
| 511 | |
| 512 void AutocompleteMatch::ValidateClassifications( | |
| 513 const base::string16& text, | |
| 514 const ACMatchClassifications& classifications) const { | |
| 515 if (text.empty()) { | |
| 516 DCHECK(classifications.empty()); | |
| 517 return; | |
| 518 } | |
| 519 | |
| 520 // The classifications should always cover the whole string. | |
| 521 DCHECK(!classifications.empty()) << "No classification for \"" << text << '"'; | |
| 522 DCHECK_EQ(0U, classifications[0].offset) | |
| 523 << "Classification misses beginning for \"" << text << '"'; | |
| 524 if (classifications.size() == 1) | |
| 525 return; | |
| 526 | |
| 527 // The classifications should always be sorted. | |
| 528 size_t last_offset = classifications[0].offset; | |
| 529 for (ACMatchClassifications::const_iterator i(classifications.begin() + 1); | |
| 530 i != classifications.end(); ++i) { | |
| 531 const char* provider_name = provider ? provider->GetName() : "None"; | |
| 532 DCHECK_GT(i->offset, last_offset) | |
| 533 << " Classification for \"" << text << "\" with offset of " << i->offset | |
| 534 << " is unsorted in relation to last offset of " << last_offset | |
| 535 << ". Provider: " << provider_name << "."; | |
| 536 DCHECK_LT(i->offset, text.length()) | |
| 537 << " Classification of [" << i->offset << "," << text.length() | |
| 538 << "] is out of bounds for \"" << text << "\". Provider: " | |
| 539 << provider_name << "."; | |
| 540 last_offset = i->offset; | |
| 541 } | |
| 542 } | |
| 543 #endif | |
| OLD | NEW |