// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/autocomplete/autocomplete_match.h" #include "base/logging.h" #include "base/string_number_conversions.h" #include "base/string_util.h" #include "chrome/browser/autocomplete/autocomplete.h" #include "chrome/browser/search_engines/template_url.h" #include "chrome/browser/search_engines/template_url_service.h" #include "chrome/browser/search_engines/template_url_service_factory.h" #include "grit/theme_resources.h" // AutocompleteMatch ---------------------------------------------------------- // static const char16 AutocompleteMatch::kInvalidChars[] = { '\n', '\r', '\t', 0x2028, // Line separator 0x2029, // Paragraph separator 0 }; AutocompleteMatch::AutocompleteMatch() : provider(NULL), relevance(0), typed_count(-1), deletable(false), inline_autocomplete_offset(string16::npos), transition(content::PAGE_TRANSITION_GENERATED), is_history_what_you_typed_match(false), type(SEARCH_WHAT_YOU_TYPED), starred(false), from_previous(false) { } AutocompleteMatch::AutocompleteMatch(AutocompleteProvider* provider, int relevance, bool deletable, Type type) : provider(provider), relevance(relevance), typed_count(-1), deletable(deletable), inline_autocomplete_offset(string16::npos), transition(content::PAGE_TRANSITION_TYPED), is_history_what_you_typed_match(false), type(type), starred(false), from_previous(false) { } AutocompleteMatch::AutocompleteMatch(const AutocompleteMatch& match) : provider(match.provider), relevance(match.relevance), typed_count(match.typed_count), deletable(match.deletable), fill_into_edit(match.fill_into_edit), inline_autocomplete_offset(match.inline_autocomplete_offset), destination_url(match.destination_url), stripped_destination_url(match.stripped_destination_url), contents(match.contents), contents_class(match.contents_class), description(match.description), description_class(match.description_class), transition(match.transition), is_history_what_you_typed_match(match.is_history_what_you_typed_match), type(match.type), keyword(match.keyword), starred(match.starred), from_previous(match.from_previous) { if (match.associated_keyword.get()) associated_keyword.reset(new AutocompleteMatch(*match.associated_keyword)); } AutocompleteMatch::~AutocompleteMatch() { } AutocompleteMatch& AutocompleteMatch::operator=( const AutocompleteMatch& match) { if (this == &match) return *this; provider = match.provider; relevance = match.relevance; typed_count = match.typed_count; deletable = match.deletable; fill_into_edit = match.fill_into_edit; inline_autocomplete_offset = match.inline_autocomplete_offset; destination_url = match.destination_url; stripped_destination_url = match.stripped_destination_url; contents = match.contents; contents_class = match.contents_class; description = match.description; description_class = match.description_class; transition = match.transition; is_history_what_you_typed_match = match.is_history_what_you_typed_match; type = match.type; associated_keyword.reset(match.associated_keyword.get() ? new AutocompleteMatch(*match.associated_keyword) : NULL); keyword = match.keyword; starred = match.starred; from_previous = match.from_previous; return *this; } // static std::string AutocompleteMatch::TypeToString(Type type) { const char* strings[] = { "url-what-you-typed", "history-url", "history-title", "history-body", "history-keyword", "navsuggest", "search-what-you-typed", "search-history", "search-suggest", "search-other-engine", "extension-app", }; COMPILE_ASSERT(arraysize(strings) == NUM_TYPES, strings_array_must_match_type_enum); return strings[type]; } // static int AutocompleteMatch::TypeToIcon(Type type) { int icons[] = { IDR_OMNIBOX_HTTP, IDR_OMNIBOX_HTTP, IDR_OMNIBOX_HTTP, IDR_OMNIBOX_HTTP, IDR_OMNIBOX_HTTP, IDR_OMNIBOX_HTTP, IDR_OMNIBOX_SEARCH, IDR_OMNIBOX_SEARCH, IDR_OMNIBOX_SEARCH, IDR_OMNIBOX_SEARCH, IDR_OMNIBOX_EXTENSION_APP, }; COMPILE_ASSERT(arraysize(icons) == NUM_TYPES, icons_array_must_match_type_enum); return icons[type]; } // static bool AutocompleteMatch::MoreRelevant(const AutocompleteMatch& elem1, const AutocompleteMatch& elem2) { // For equal-relevance matches, we sort alphabetically, so that providers // who return multiple elements at the same priority get a "stable" sort // across multiple updates. return (elem1.relevance == elem2.relevance) ? (elem1.contents < elem2.contents) : (elem1.relevance > elem2.relevance); } // static bool AutocompleteMatch::DestinationSortFunc(const AutocompleteMatch& elem1, const AutocompleteMatch& elem2) { // Sort identical destination_urls together. Place the most relevant matches // first, so that when we call std::unique(), these are the ones that get // preserved. return DestinationsEqual(elem1, elem2) ? MoreRelevant(elem1, elem2) : (elem1.stripped_destination_url < elem2.stripped_destination_url); } // static bool AutocompleteMatch::DestinationsEqual(const AutocompleteMatch& elem1, const AutocompleteMatch& elem2) { return elem1.stripped_destination_url == elem2.stripped_destination_url; } // static void AutocompleteMatch::ClassifyMatchInString( const string16& find_text, const string16& text, int style, ACMatchClassifications* classification) { ClassifyLocationInString(text.find(find_text), find_text.length(), text.length(), style, classification); } // static void AutocompleteMatch::ClassifyLocationInString( size_t match_location, size_t match_length, size_t overall_length, int style, ACMatchClassifications* classification) { classification->clear(); // Don't classify anything about an empty string // (AutocompleteMatch::Validate() checks this). if (overall_length == 0) return; // Mark pre-match portion of string (if any). if (match_location != 0) { classification->push_back(ACMatchClassification(0, style)); } // Mark matching portion of string. if (match_location == string16::npos) { // No match, above classification will suffice for whole string. return; } // Classifying an empty match makes no sense and will lead to validation // errors later. DCHECK_GT(match_length, 0U); classification->push_back(ACMatchClassification(match_location, (style | ACMatchClassification::MATCH) & ~ACMatchClassification::DIM)); // Mark post-match portion of string (if any). const size_t after_match(match_location + match_length); if (after_match < overall_length) { classification->push_back(ACMatchClassification(after_match, style)); } } // static std::string AutocompleteMatch::ClassificationsToString( const ACMatchClassifications& classifications) { std::string serialized_classifications; for (size_t i = 0; i < classifications.size(); ++i) { if (i) serialized_classifications += ','; serialized_classifications += base::IntToString(classifications[i].offset) + ',' + base::IntToString(classifications[i].style); } return serialized_classifications; } // static ACMatchClassifications AutocompleteMatch::ClassificationsFromString( const std::string& serialized_classifications) { ACMatchClassifications classifications; std::vector tokens; Tokenize(serialized_classifications, ",", &tokens); DCHECK(!(tokens.size() & 1)); // The number of tokens should be even. for (size_t i = 0; i < tokens.size(); i += 2) { int classification_offset = 0; int classification_style = ACMatchClassification::NONE; if (!base::StringToInt(tokens[i], &classification_offset) || !base::StringToInt(tokens[i + 1], &classification_style)) { NOTREACHED(); return classifications; } classifications.push_back(ACMatchClassification(classification_offset, classification_style)); } return classifications; } // static void AutocompleteMatch::AddLastClassificationIfNecessary( ACMatchClassifications* classifications, size_t offset, int style) { DCHECK(classifications); if (classifications->empty() || classifications->back().style != style) { DCHECK(classifications->empty() || (offset > classifications->back().offset)); classifications->push_back(ACMatchClassification(offset, style)); } } // static string16 AutocompleteMatch::SanitizeString(const string16& text) { // NOTE: This logic is mirrored by |sanitizeString()| in // schema_generated_bindings.js. string16 result; TrimWhitespace(text, TRIM_LEADING, &result); RemoveChars(result, kInvalidChars, &result); return result; } void AutocompleteMatch::ComputeStrippedDestinationURL() { static const char prefix[] = "www."; static const size_t prefix_len = arraysize(prefix) - 1; std::string host = destination_url.host(); if (destination_url.is_valid() && host.compare(0, prefix_len, prefix) == 0) { host = host.substr(prefix_len); GURL::Replacements replace_host; replace_host.SetHostStr(host); stripped_destination_url = destination_url.ReplaceComponents(replace_host); } else { stripped_destination_url = destination_url; } } void AutocompleteMatch::GetKeywordUIState(Profile* profile, string16* keyword, bool* is_keyword_hint) const { *is_keyword_hint = associated_keyword.get() != NULL; keyword->assign(*is_keyword_hint ? associated_keyword->keyword : GetSubstitutingExplicitlyInvokedKeyword(profile)); } string16 AutocompleteMatch::GetSubstitutingExplicitlyInvokedKeyword( Profile* profile) const { if (transition != content::PAGE_TRANSITION_KEYWORD) return string16(); const TemplateURL* t_url = GetTemplateURL(profile); return (t_url && t_url->SupportsReplacement()) ? keyword : string16(); } TemplateURL* AutocompleteMatch::GetTemplateURL(Profile* profile) const { DCHECK(profile); return keyword.empty() ? NULL : TemplateURLServiceFactory::GetForProfile(profile)-> GetTemplateURLForKeyword(keyword); } #ifndef NDEBUG void AutocompleteMatch::Validate() const { ValidateClassifications(contents, contents_class); ValidateClassifications(description, description_class); } void AutocompleteMatch::ValidateClassifications( const string16& text, const ACMatchClassifications& classifications) const { if (text.empty()) { DCHECK(classifications.empty()); return; } // The classifications should always cover the whole string. DCHECK(!classifications.empty()) << "No classification for \"" << text << '"'; DCHECK_EQ(0U, classifications[0].offset) << "Classification misses beginning for \"" << text << '"'; if (classifications.size() == 1) return; // The classifications should always be sorted. size_t last_offset = classifications[0].offset; for (ACMatchClassifications::const_iterator i(classifications.begin() + 1); i != classifications.end(); ++i) { DCHECK_GT(i->offset, last_offset) << " Classification for \"" << text << "\" with offset of " << i->offset << " is unsorted in relation to last offset of " << last_offset << ". Provider: " << (provider ? provider->name() : "None") << "."; DCHECK_LT(i->offset, text.length()) << " Classification of [" << i->offset << "," << text.length() << "] is out of bounds for \"" << text << "\". Provider: " << (provider ? provider->name() : "None") << "."; last_offset = i->offset; } } #endif