Home | History | Annotate | Download | only in autocomplete
      1 // Copyright (c) 2012 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 "chrome/browser/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 "chrome/browser/autocomplete/autocomplete_provider.h"
     15 #include "chrome/browser/search_engines/template_url.h"
     16 #include "chrome/browser/search_engines/template_url_service.h"
     17 #include "chrome/browser/search_engines/template_url_service_factory.h"
     18 #include "content/public/common/url_constants.h"
     19 #include "grit/theme_resources.h"
     20 
     21 namespace {
     22 
     23 bool IsTrivialClassification(const ACMatchClassifications& classifications) {
     24   return classifications.empty() ||
     25       ((classifications.size() == 1) &&
     26        (classifications.back().style == ACMatchClassification::NONE));
     27 }
     28 
     29 }  // namespace
     30 
     31 // AutocompleteMatch ----------------------------------------------------------
     32 
     33 // static
     34 const char16 AutocompleteMatch::kInvalidChars[] = {
     35   '\n', '\r', '\t',
     36   0x2028,  // Line separator
     37   0x2029,  // Paragraph separator
     38   0
     39 };
     40 
     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(content::PAGE_TRANSITION_GENERATED),
     48       is_history_what_you_typed_match(false),
     49       type(AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED),
     50       starred(false),
     51       from_previous(false) {
     52 }
     53 
     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(content::PAGE_TRANSITION_TYPED),
     64       is_history_what_you_typed_match(false),
     65       type(type),
     66       starred(false),
     67       from_previous(false) {
     68 }
     69 
     70 AutocompleteMatch::AutocompleteMatch(const AutocompleteMatch& match)
     71     : provider(match.provider),
     72       relevance(match.relevance),
     73       typed_count(match.typed_count),
     74       deletable(match.deletable),
     75       fill_into_edit(match.fill_into_edit),
     76       inline_autocompletion(match.inline_autocompletion),
     77       allowed_to_be_default_match(match.allowed_to_be_default_match),
     78       destination_url(match.destination_url),
     79       stripped_destination_url(match.stripped_destination_url),
     80       contents(match.contents),
     81       contents_class(match.contents_class),
     82       description(match.description),
     83       description_class(match.description_class),
     84       transition(match.transition),
     85       is_history_what_you_typed_match(match.is_history_what_you_typed_match),
     86       type(match.type),
     87       associated_keyword(match.associated_keyword.get() ?
     88           new AutocompleteMatch(*match.associated_keyword) : NULL),
     89       keyword(match.keyword),
     90       starred(match.starred),
     91       from_previous(match.from_previous),
     92       search_terms_args(match.search_terms_args.get() ?
     93           new TemplateURLRef::SearchTermsArgs(*match.search_terms_args) :
     94           NULL),
     95       additional_info(match.additional_info) {
     96 }
     97 
     98 AutocompleteMatch::~AutocompleteMatch() {
     99 }
    100 
    101 AutocompleteMatch& AutocompleteMatch::operator=(
    102     const AutocompleteMatch& match) {
    103   if (this == &match)
    104     return *this;
    105 
    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   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   starred = match.starred;
    126   from_previous = match.from_previous;
    127   search_terms_args.reset(match.search_terms_args.get() ?
    128       new TemplateURLRef::SearchTermsArgs(*match.search_terms_args) : NULL);
    129   additional_info = match.additional_info;
    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     // ContactProvider isn't used by the omnibox, so this icon is never
    152     // displayed.
    153     IDR_OMNIBOX_SEARCH,
    154     IDR_OMNIBOX_HTTP,
    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 int AutocompleteMatch::TypeToLocationBarIcon(Type type) {
    163   int id = TypeToIcon(type);
    164   if (id == IDR_OMNIBOX_HTTP)
    165     return IDR_LOCATION_BAR_HTTP;
    166   return id;
    167 }
    168 
    169 // static
    170 bool AutocompleteMatch::MoreRelevant(const AutocompleteMatch& elem1,
    171                                      const AutocompleteMatch& elem2) {
    172   // For equal-relevance matches, we sort alphabetically, so that providers
    173   // who return multiple elements at the same priority get a "stable" sort
    174   // across multiple updates.
    175   return (elem1.relevance == elem2.relevance) ?
    176       (elem1.contents < elem2.contents) : (elem1.relevance > elem2.relevance);
    177 }
    178 
    179 // static
    180 bool AutocompleteMatch::DestinationSortFunc(const AutocompleteMatch& elem1,
    181                                             const AutocompleteMatch& elem2) {
    182   // Sort identical destination_urls together.  Place the most relevant matches
    183   // first, so that when we call std::unique(), these are the ones that get
    184   // preserved.
    185   if (DestinationsEqual(elem1, elem2) ||
    186       (elem1.stripped_destination_url.is_empty() &&
    187        elem2.stripped_destination_url.is_empty()))
    188     return MoreRelevant(elem1, elem2);
    189   return elem1.stripped_destination_url < elem2.stripped_destination_url;
    190 }
    191 
    192 // static
    193 bool AutocompleteMatch::DestinationsEqual(const AutocompleteMatch& elem1,
    194                                           const AutocompleteMatch& elem2) {
    195   if (elem1.stripped_destination_url.is_empty() &&
    196       elem2.stripped_destination_url.is_empty())
    197     return false;
    198   return elem1.stripped_destination_url == elem2.stripped_destination_url;
    199 }
    200 
    201 // static
    202 void AutocompleteMatch::ClassifyMatchInString(
    203     const base::string16& find_text,
    204     const base::string16& text,
    205     int style,
    206     ACMatchClassifications* classification) {
    207   ClassifyLocationInString(text.find(find_text), find_text.length(),
    208                            text.length(), style, classification);
    209 }
    210 
    211 // static
    212 void AutocompleteMatch::ClassifyLocationInString(
    213     size_t match_location,
    214     size_t match_length,
    215     size_t overall_length,
    216     int style,
    217     ACMatchClassifications* classification) {
    218   classification->clear();
    219 
    220   // Don't classify anything about an empty string
    221   // (AutocompleteMatch::Validate() checks this).
    222   if (overall_length == 0)
    223     return;
    224 
    225   // Mark pre-match portion of string (if any).
    226   if (match_location != 0) {
    227     classification->push_back(ACMatchClassification(0, style));
    228   }
    229 
    230   // Mark matching portion of string.
    231   if (match_location == base::string16::npos) {
    232     // No match, above classification will suffice for whole string.
    233     return;
    234   }
    235   // Classifying an empty match makes no sense and will lead to validation
    236   // errors later.
    237   DCHECK_GT(match_length, 0U);
    238   classification->push_back(ACMatchClassification(match_location,
    239       (style | ACMatchClassification::MATCH) & ~ACMatchClassification::DIM));
    240 
    241   // Mark post-match portion of string (if any).
    242   const size_t after_match(match_location + match_length);
    243   if (after_match < overall_length) {
    244     classification->push_back(ACMatchClassification(after_match, style));
    245   }
    246 }
    247 
    248 // static
    249 AutocompleteMatch::ACMatchClassifications
    250     AutocompleteMatch::MergeClassifications(
    251     const ACMatchClassifications& classifications1,
    252     const ACMatchClassifications& classifications2) {
    253   // We must return the empty vector only if both inputs are truly empty.
    254   // The result of merging an empty vector with a single (0, NONE)
    255   // classification is the latter one-entry vector.
    256   if (IsTrivialClassification(classifications1))
    257     return classifications2.empty() ? classifications1 : classifications2;
    258   if (IsTrivialClassification(classifications2))
    259     return classifications1;
    260 
    261   ACMatchClassifications output;
    262   for (ACMatchClassifications::const_iterator i = classifications1.begin(),
    263        j = classifications2.begin(); i != classifications1.end();) {
    264     AutocompleteMatch::AddLastClassificationIfNecessary(&output,
    265         std::max(i->offset, j->offset), i->style | j->style);
    266     const size_t next_i_offset = (i + 1) == classifications1.end() ?
    267         static_cast<size_t>(-1) : (i + 1)->offset;
    268     const size_t next_j_offset = (j + 1) == classifications2.end() ?
    269         static_cast<size_t>(-1) : (j + 1)->offset;
    270     if (next_i_offset >= next_j_offset)
    271       ++j;
    272     if (next_j_offset >= next_i_offset)
    273       ++i;
    274   }
    275 
    276   return output;
    277 }
    278 
    279 // static
    280 std::string AutocompleteMatch::ClassificationsToString(
    281     const ACMatchClassifications& classifications) {
    282   std::string serialized_classifications;
    283   for (size_t i = 0; i < classifications.size(); ++i) {
    284     if (i)
    285       serialized_classifications += ',';
    286     serialized_classifications += base::IntToString(classifications[i].offset) +
    287         ',' + base::IntToString(classifications[i].style);
    288   }
    289   return serialized_classifications;
    290 }
    291 
    292 // static
    293 ACMatchClassifications AutocompleteMatch::ClassificationsFromString(
    294     const std::string& serialized_classifications) {
    295   ACMatchClassifications classifications;
    296   std::vector<std::string> tokens;
    297   Tokenize(serialized_classifications, ",", &tokens);
    298   DCHECK(!(tokens.size() & 1));  // The number of tokens should be even.
    299   for (size_t i = 0; i < tokens.size(); i += 2) {
    300     int classification_offset = 0;
    301     int classification_style = ACMatchClassification::NONE;
    302     if (!base::StringToInt(tokens[i], &classification_offset) ||
    303         !base::StringToInt(tokens[i + 1], &classification_style)) {
    304       NOTREACHED();
    305       return classifications;
    306     }
    307     classifications.push_back(ACMatchClassification(classification_offset,
    308                                                     classification_style));
    309   }
    310   return classifications;
    311 }
    312 
    313 // static
    314 void AutocompleteMatch::AddLastClassificationIfNecessary(
    315     ACMatchClassifications* classifications,
    316     size_t offset,
    317     int style) {
    318   DCHECK(classifications);
    319   if (classifications->empty() || classifications->back().style != style) {
    320     DCHECK(classifications->empty() ||
    321            (offset > classifications->back().offset));
    322     classifications->push_back(ACMatchClassification(offset, style));
    323   }
    324 }
    325 
    326 // static
    327 base::string16 AutocompleteMatch::SanitizeString(const base::string16& text) {
    328   // NOTE: This logic is mirrored by |sanitizeString()| in
    329   // omnibox_custom_bindings.js.
    330   base::string16 result;
    331   TrimWhitespace(text, TRIM_LEADING, &result);
    332   base::RemoveChars(result, kInvalidChars, &result);
    333   return result;
    334 }
    335 
    336 // static
    337 bool AutocompleteMatch::IsSearchType(Type type) {
    338   return type == AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED ||
    339          type == AutocompleteMatchType::SEARCH_HISTORY ||
    340          type == AutocompleteMatchType::SEARCH_SUGGEST ||
    341          type == AutocompleteMatchType::SEARCH_OTHER_ENGINE;
    342 }
    343 
    344 void AutocompleteMatch::ComputeStrippedDestinationURL(Profile* profile) {
    345   stripped_destination_url = destination_url;
    346   if (!stripped_destination_url.is_valid())
    347     return;
    348 
    349   // If the destination URL looks like it was generated from a TemplateURL,
    350   // remove all substitutions other than the search terms.  This allows us
    351   // to eliminate cases like past search URLs from history that differ only
    352   // by some obscure query param from each other or from the search/keyword
    353   // provider matches.
    354   TemplateURL* template_url = GetTemplateURL(profile, true);
    355   if (template_url != NULL && template_url->SupportsReplacement()) {
    356     base::string16 search_terms;
    357     if (template_url->ExtractSearchTermsFromURL(stripped_destination_url,
    358                                                 &search_terms)) {
    359       stripped_destination_url =
    360           GURL(template_url->url_ref().ReplaceSearchTerms(
    361               TemplateURLRef::SearchTermsArgs(search_terms)));
    362     }
    363   }
    364 
    365   // |replacements| keeps all the substitions we're going to make to
    366   // from {destination_url} to {stripped_destination_url}.  |need_replacement|
    367   // is a helper variable that helps us keep track of whether we need
    368   // to apply the replacement.
    369   bool needs_replacement = false;
    370   GURL::Replacements replacements;
    371 
    372   // Remove the www. prefix from the host.
    373   static const char prefix[] = "www.";
    374   static const size_t prefix_len = arraysize(prefix) - 1;
    375   std::string host = stripped_destination_url.host();
    376   if (host.compare(0, prefix_len, prefix) == 0) {
    377     host = host.substr(prefix_len);
    378     replacements.SetHostStr(host);
    379     needs_replacement = true;
    380   }
    381 
    382   // Replace https protocol with http protocol.
    383   if (stripped_destination_url.SchemeIs(content::kHttpsScheme)) {
    384     replacements.SetScheme(
    385         content::kHttpScheme,
    386         url_parse::Component(0, strlen(content::kHttpScheme)));
    387     needs_replacement = true;
    388   }
    389 
    390   if (needs_replacement)
    391     stripped_destination_url = stripped_destination_url.ReplaceComponents(
    392         replacements);
    393 }
    394 
    395 void AutocompleteMatch::GetKeywordUIState(Profile* profile,
    396                                           base::string16* keyword,
    397                                           bool* is_keyword_hint) const {
    398   *is_keyword_hint = associated_keyword.get() != NULL;
    399   keyword->assign(*is_keyword_hint ? associated_keyword->keyword :
    400       GetSubstitutingExplicitlyInvokedKeyword(profile));
    401 }
    402 
    403 base::string16 AutocompleteMatch::GetSubstitutingExplicitlyInvokedKeyword(
    404     Profile* profile) const {
    405   if (transition != content::PAGE_TRANSITION_KEYWORD)
    406     return base::string16();
    407   const TemplateURL* t_url = GetTemplateURL(profile, false);
    408   return (t_url && t_url->SupportsReplacement()) ? keyword : base::string16();
    409 }
    410 
    411 TemplateURL* AutocompleteMatch::GetTemplateURL(
    412     Profile* profile, bool allow_fallback_to_destination_host) const {
    413   DCHECK(profile);
    414   TemplateURLService* template_url_service =
    415       TemplateURLServiceFactory::GetForProfile(profile);
    416   if (template_url_service == NULL)
    417     return NULL;
    418   TemplateURL* template_url = keyword.empty() ? NULL :
    419       template_url_service->GetTemplateURLForKeyword(keyword);
    420   if (template_url == NULL && allow_fallback_to_destination_host) {
    421     template_url = template_url_service->GetTemplateURLForHost(
    422         destination_url.host());
    423   }
    424   return template_url;
    425 }
    426 
    427 void AutocompleteMatch::RecordAdditionalInfo(const std::string& property,
    428                                              const std::string& value) {
    429   DCHECK(!property.empty());
    430   DCHECK(!value.empty());
    431   additional_info[property] = value;
    432 }
    433 
    434 void AutocompleteMatch::RecordAdditionalInfo(const std::string& property,
    435                                              int value) {
    436   RecordAdditionalInfo(property, base::IntToString(value));
    437 }
    438 
    439 void AutocompleteMatch::RecordAdditionalInfo(const std::string& property,
    440                                              const base::Time& value) {
    441   RecordAdditionalInfo(property,
    442                        UTF16ToUTF8(base::TimeFormatShortDateAndTime(value)));
    443 }
    444 
    445 std::string AutocompleteMatch::GetAdditionalInfo(
    446     const std::string& property) const {
    447   AdditionalInfo::const_iterator i(additional_info.find(property));
    448   return (i == additional_info.end()) ? std::string() : i->second;
    449 }
    450 
    451 bool AutocompleteMatch::IsVerbatimType() const {
    452   const bool is_keyword_verbatim_match =
    453       (type == AutocompleteMatchType::SEARCH_OTHER_ENGINE &&
    454        provider != NULL &&
    455        provider->type() == AutocompleteProvider::TYPE_SEARCH);
    456   return type == AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED ||
    457       type == AutocompleteMatchType::URL_WHAT_YOU_TYPED ||
    458       is_keyword_verbatim_match;
    459 }
    460 
    461 #ifndef NDEBUG
    462 void AutocompleteMatch::Validate() const {
    463   ValidateClassifications(contents, contents_class);
    464   ValidateClassifications(description, description_class);
    465 }
    466 
    467 void AutocompleteMatch::ValidateClassifications(
    468     const base::string16& text,
    469     const ACMatchClassifications& classifications) const {
    470   if (text.empty()) {
    471     DCHECK(classifications.empty());
    472     return;
    473   }
    474 
    475   // The classifications should always cover the whole string.
    476   DCHECK(!classifications.empty()) << "No classification for \"" << text << '"';
    477   DCHECK_EQ(0U, classifications[0].offset)
    478       << "Classification misses beginning for \"" << text << '"';
    479   if (classifications.size() == 1)
    480     return;
    481 
    482   // The classifications should always be sorted.
    483   size_t last_offset = classifications[0].offset;
    484   for (ACMatchClassifications::const_iterator i(classifications.begin() + 1);
    485        i != classifications.end(); ++i) {
    486     const char* provider_name = provider ? provider->GetName() : "None";
    487     DCHECK_GT(i->offset, last_offset)
    488         << " Classification for \"" << text << "\" with offset of " << i->offset
    489         << " is unsorted in relation to last offset of " << last_offset
    490         << ". Provider: " << provider_name << ".";
    491     DCHECK_LT(i->offset, text.length())
    492         << " Classification of [" << i->offset << "," << text.length()
    493         << "] is out of bounds for \"" << text << "\". Provider: "
    494         << provider_name << ".";
    495     last_offset = i->offset;
    496   }
    497 }
    498 #endif
    499