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/keyword_provider.h"
      6 
      7 #include <algorithm>
      8 #include <vector>
      9 
     10 #include "base/strings/string16.h"
     11 #include "base/strings/string_util.h"
     12 #include "base/strings/utf_string_conversions.h"
     13 #include "chrome/browser/autocomplete/autocomplete_match.h"
     14 #include "chrome/browser/autocomplete/autocomplete_provider_listener.h"
     15 #include "chrome/browser/chrome_notification_types.h"
     16 #include "chrome/browser/extensions/api/omnibox/omnibox_api.h"
     17 #include "chrome/browser/extensions/extension_service.h"
     18 #include "chrome/browser/extensions/extension_system.h"
     19 #include "chrome/browser/profiles/profile.h"
     20 #include "chrome/browser/search_engines/template_url.h"
     21 #include "chrome/browser/search_engines/template_url_service.h"
     22 #include "chrome/browser/search_engines/template_url_service_factory.h"
     23 #include "content/public/browser/notification_details.h"
     24 #include "content/public/browser/notification_source.h"
     25 #include "grit/generated_resources.h"
     26 #include "net/base/escape.h"
     27 #include "net/base/net_util.h"
     28 #include "ui/base/l10n/l10n_util.h"
     29 
     30 namespace omnibox_api = extensions::api::omnibox;
     31 
     32 // Helper functor for Start(), for ending keyword mode unless explicitly told
     33 // otherwise.
     34 class KeywordProvider::ScopedEndExtensionKeywordMode {
     35  public:
     36   explicit ScopedEndExtensionKeywordMode(KeywordProvider* provider)
     37       : provider_(provider) { }
     38   ~ScopedEndExtensionKeywordMode() {
     39     if (provider_)
     40       provider_->MaybeEndExtensionKeywordMode();
     41   }
     42 
     43   void StayInKeywordMode() {
     44     provider_ = NULL;
     45   }
     46  private:
     47   KeywordProvider* provider_;
     48 };
     49 
     50 KeywordProvider::KeywordProvider(AutocompleteProviderListener* listener,
     51                                  Profile* profile)
     52     : AutocompleteProvider(listener, profile,
     53           AutocompleteProvider::TYPE_KEYWORD),
     54       model_(NULL),
     55       current_input_id_(0) {
     56   // Extension suggestions always come from the original profile, since that's
     57   // where extensions run. We use the input ID to distinguish whether the
     58   // suggestions are meant for us.
     59   registrar_.Add(this,
     60                  chrome::NOTIFICATION_EXTENSION_OMNIBOX_SUGGESTIONS_READY,
     61                  content::Source<Profile>(profile->GetOriginalProfile()));
     62   registrar_.Add(
     63       this, chrome::NOTIFICATION_EXTENSION_OMNIBOX_DEFAULT_SUGGESTION_CHANGED,
     64       content::Source<Profile>(profile->GetOriginalProfile()));
     65   registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_OMNIBOX_INPUT_ENTERED,
     66                  content::Source<Profile>(profile));
     67 }
     68 
     69 KeywordProvider::KeywordProvider(AutocompleteProviderListener* listener,
     70                                  TemplateURLService* model)
     71     : AutocompleteProvider(listener, NULL, AutocompleteProvider::TYPE_KEYWORD),
     72       model_(model),
     73       current_input_id_(0) {
     74 }
     75 
     76 
     77 namespace {
     78 
     79 // Helper functor for Start(), for sorting keyword matches by quality.
     80 class CompareQuality {
     81  public:
     82   // A keyword is of higher quality when a greater fraction of it has been
     83   // typed, that is, when it is shorter.
     84   //
     85   // TODO(pkasting): http://b/740691 Most recent and most frequent keywords are
     86   // probably better rankings than the fraction of the keyword typed.  We should
     87   // always put any exact matches first no matter what, since the code in
     88   // Start() assumes this (and it makes sense).
     89   bool operator()(const TemplateURL* t_url1, const TemplateURL* t_url2) const {
     90     return t_url1->keyword().length() < t_url2->keyword().length();
     91   }
     92 };
     93 
     94 // We need our input IDs to be unique across all profiles, so we keep a global
     95 // UID that each provider uses.
     96 static int global_input_uid_;
     97 
     98 }  // namespace
     99 
    100 // static
    101 string16 KeywordProvider::SplitKeywordFromInput(
    102     const string16& input,
    103     bool trim_leading_whitespace,
    104     string16* remaining_input) {
    105   // Find end of first token.  The AutocompleteController has trimmed leading
    106   // whitespace, so we need not skip over that.
    107   const size_t first_white(input.find_first_of(kWhitespaceUTF16));
    108   DCHECK_NE(0U, first_white);
    109   if (first_white == string16::npos)
    110     return input;  // Only one token provided.
    111 
    112   // Set |remaining_input| to everything after the first token.
    113   DCHECK(remaining_input != NULL);
    114   const size_t remaining_start = trim_leading_whitespace ?
    115       input.find_first_not_of(kWhitespaceUTF16, first_white) : first_white + 1;
    116 
    117   if (remaining_start < input.length())
    118     remaining_input->assign(input.begin() + remaining_start, input.end());
    119 
    120   // Return first token as keyword.
    121   return input.substr(0, first_white);
    122 }
    123 
    124 // static
    125 string16 KeywordProvider::SplitReplacementStringFromInput(
    126     const string16& input,
    127     bool trim_leading_whitespace) {
    128   // The input may contain leading whitespace, strip it.
    129   string16 trimmed_input;
    130   TrimWhitespace(input, TRIM_LEADING, &trimmed_input);
    131 
    132   // And extract the replacement string.
    133   string16 remaining_input;
    134   SplitKeywordFromInput(trimmed_input, trim_leading_whitespace,
    135       &remaining_input);
    136   return remaining_input;
    137 }
    138 
    139 // static
    140 const TemplateURL* KeywordProvider::GetSubstitutingTemplateURLForInput(
    141     TemplateURLService* model,
    142     AutocompleteInput* input) {
    143   if (!input->allow_exact_keyword_match())
    144     return NULL;
    145 
    146   string16 keyword, remaining_input;
    147   if (!ExtractKeywordFromInput(*input, &keyword, &remaining_input))
    148     return NULL;
    149 
    150   DCHECK(model);
    151   const TemplateURL* template_url = model->GetTemplateURLForKeyword(keyword);
    152   if (template_url && template_url->SupportsReplacement()) {
    153     // Adjust cursor position iff it was set before, otherwise leave it as is.
    154     size_t cursor_position = string16::npos;
    155     // The adjustment assumes that the keyword was stripped from the beginning
    156     // of the original input.
    157     if (input->cursor_position() != string16::npos &&
    158         !remaining_input.empty() &&
    159         EndsWith(input->text(), remaining_input, true)) {
    160       int offset = input->text().length() - input->cursor_position();
    161       // The cursor should never be past the last character.
    162       DCHECK_GE(offset, 0);
    163       // The cursor should never be in the keyword part, which is guaranteed
    164       // by OmniboxEditModel implementation (see omnibox_edit_model.cc).
    165       DCHECK_LE(offset, static_cast<int>(remaining_input.length()));
    166       if (offset <= 0) {
    167         // Normalize the cursor to be exactly after the last character.
    168         cursor_position = remaining_input.length();
    169       } else {
    170         // If somehow the cursor was before the remaining text, set it to 0,
    171         // otherwise adjust it relative to the remaining text.
    172         cursor_position = offset > static_cast<int>(remaining_input.length()) ?
    173             0u : remaining_input.length() - offset;
    174       }
    175     }
    176     input->UpdateText(remaining_input, cursor_position, input->parts());
    177     return template_url;
    178   }
    179 
    180   return NULL;
    181 }
    182 
    183 string16 KeywordProvider::GetKeywordForText(const string16& text) const {
    184   const string16 keyword(TemplateURLService::CleanUserInputKeyword(text));
    185 
    186   if (keyword.empty())
    187     return keyword;
    188 
    189   TemplateURLService* url_service = GetTemplateURLService();
    190   if (!url_service)
    191     return string16();
    192 
    193   // Don't provide a keyword if it doesn't support replacement.
    194   const TemplateURL* const template_url =
    195       url_service->GetTemplateURLForKeyword(keyword);
    196   if (!template_url || !template_url->SupportsReplacement())
    197     return string16();
    198 
    199   // Don't provide a keyword for inactive/disabled extension keywords.
    200   if (template_url->IsExtensionKeyword()) {
    201     ExtensionService* extension_service =
    202         extensions::ExtensionSystem::Get(profile_)->extension_service();
    203     const extensions::Extension* extension = extension_service->
    204         GetExtensionById(template_url->GetExtensionId(), false);
    205     if (!extension ||
    206         (profile_->IsOffTheRecord() &&
    207         !extension_service->IsIncognitoEnabled(extension->id())))
    208       return string16();
    209   }
    210 
    211   return keyword;
    212 }
    213 
    214 AutocompleteMatch KeywordProvider::CreateVerbatimMatch(
    215     const string16& text,
    216     const string16& keyword,
    217     const AutocompleteInput& input) {
    218   // A verbatim match is allowed to be the default match.
    219   return CreateAutocompleteMatch(
    220       GetTemplateURLService()->GetTemplateURLForKeyword(keyword), input,
    221       keyword.length(), SplitReplacementStringFromInput(text, true), true, 0);
    222 }
    223 
    224 void KeywordProvider::Start(const AutocompleteInput& input,
    225                             bool minimal_changes) {
    226   // This object ensures we end keyword mode if we exit the function without
    227   // toggling keyword mode to on.
    228   ScopedEndExtensionKeywordMode keyword_mode_toggle(this);
    229 
    230   matches_.clear();
    231 
    232   if (!minimal_changes) {
    233     done_ = true;
    234 
    235     // Input has changed. Increment the input ID so that we can discard any
    236     // stale extension suggestions that may be incoming.
    237     current_input_id_ = ++global_input_uid_;
    238   }
    239 
    240   // Split user input into a keyword and some query input.
    241   //
    242   // We want to suggest keywords even when users have started typing URLs, on
    243   // the assumption that they might not realize they no longer need to go to a
    244   // site to be able to search it.  So we call CleanUserInputKeyword() to strip
    245   // any initial scheme and/or "www.".  NOTE: Any heuristics or UI used to
    246   // automatically/manually create keywords will need to be in sync with
    247   // whatever we do here!
    248   //
    249   // TODO(pkasting): http://b/1112681 If someday we remember usage frequency for
    250   // keywords, we might suggest keywords that haven't even been partially typed,
    251   // if the user uses them enough and isn't obviously typing something else.  In
    252   // this case we'd consider all input here to be query input.
    253   string16 keyword, remaining_input;
    254   if (!ExtractKeywordFromInput(input, &keyword, &remaining_input))
    255     return;
    256 
    257   // Get the best matches for this keyword.
    258   //
    259   // NOTE: We could cache the previous keywords and reuse them here in the
    260   // |minimal_changes| case, but since we'd still have to recalculate their
    261   // relevances and we can just recreate the results synchronously anyway, we
    262   // don't bother.
    263   //
    264   // TODO(pkasting): http://b/893701 We should remember the user's use of a
    265   // search query both from the autocomplete popup and from web pages
    266   // themselves.
    267   TemplateURLService::TemplateURLVector matches;
    268   GetTemplateURLService()->FindMatchingKeywords(
    269       keyword, !remaining_input.empty(), &matches);
    270 
    271   for (TemplateURLService::TemplateURLVector::iterator i(matches.begin());
    272        i != matches.end(); ) {
    273     const TemplateURL* template_url = *i;
    274 
    275     // Prune any extension keywords that are disallowed in incognito mode (if
    276     // we're incognito), or disabled.
    277     if (profile_ && template_url->IsExtensionKeyword()) {
    278       ExtensionService* service = extensions::ExtensionSystem::Get(profile_)->
    279           extension_service();
    280       const extensions::Extension* extension =
    281           service->GetExtensionById(template_url->GetExtensionId(), false);
    282       bool enabled =
    283           extension && (!profile_->IsOffTheRecord() ||
    284                         service->IsIncognitoEnabled(extension->id()));
    285       if (!enabled) {
    286         i = matches.erase(i);
    287         continue;
    288       }
    289     }
    290 
    291     // Prune any substituting keywords if there is no substitution.
    292     if (template_url->SupportsReplacement() && remaining_input.empty() &&
    293         !input.allow_exact_keyword_match()) {
    294       i = matches.erase(i);
    295       continue;
    296     }
    297 
    298     ++i;
    299   }
    300   if (matches.empty())
    301     return;
    302   std::sort(matches.begin(), matches.end(), CompareQuality());
    303 
    304   // Limit to one exact or three inexact matches, and mark them up for display
    305   // in the autocomplete popup.
    306   // Any exact match is going to be the highest quality match, and thus at the
    307   // front of our vector.
    308   if (matches.front()->keyword() == keyword) {
    309     const TemplateURL* template_url = matches.front();
    310     const bool is_extension_keyword = template_url->IsExtensionKeyword();
    311 
    312     // Only create an exact match if |remaining_input| is empty or if
    313     // this is an extension keyword.  If |remaining_input| is a
    314     // non-empty non-extension keyword (i.e., a regular keyword that
    315     // supports replacement and that has extra text following it),
    316     // then SearchProvider creates the exact (a.k.a. verbatim) match.
    317     if (!remaining_input.empty() && !is_extension_keyword)
    318       return;
    319 
    320     // TODO(pkasting): We should probably check that if the user explicitly
    321     // typed a scheme, that scheme matches the one in |template_url|.
    322 
    323     // When creating an exact match (either for the keyword itself, no
    324     // remaining query or an extension keyword, possibly with remaining
    325     // input), allow the match to be the default match.
    326     matches_.push_back(CreateAutocompleteMatch(
    327         template_url, input, keyword.length(), remaining_input, true, -1));
    328 
    329     if (profile_ && is_extension_keyword) {
    330       if (input.matches_requested() == AutocompleteInput::ALL_MATCHES) {
    331         if (template_url->GetExtensionId() != current_keyword_extension_id_)
    332           MaybeEndExtensionKeywordMode();
    333         if (current_keyword_extension_id_.empty())
    334           EnterExtensionKeywordMode(template_url->GetExtensionId());
    335         keyword_mode_toggle.StayInKeywordMode();
    336       }
    337 
    338       extensions::ApplyDefaultSuggestionForExtensionKeyword(
    339           profile_, template_url,
    340           remaining_input,
    341           &matches_[0]);
    342 
    343       if (minimal_changes &&
    344           (input.matches_requested() != AutocompleteInput::BEST_MATCH)) {
    345         // If the input hasn't significantly changed, we can just use the
    346         // suggestions from last time. We need to readjust the relevance to
    347         // ensure it is less than the main match's relevance.
    348         for (size_t i = 0; i < extension_suggest_matches_.size(); ++i) {
    349           matches_.push_back(extension_suggest_matches_[i]);
    350           matches_.back().relevance = matches_[0].relevance - (i + 1);
    351         }
    352       } else if (input.matches_requested() == AutocompleteInput::ALL_MATCHES) {
    353         extension_suggest_last_input_ = input;
    354         extension_suggest_matches_.clear();
    355 
    356         bool have_listeners =
    357           extensions::ExtensionOmniboxEventRouter::OnInputChanged(
    358               profile_, template_url->GetExtensionId(),
    359               UTF16ToUTF8(remaining_input), current_input_id_);
    360 
    361         // We only have to wait for suggest results if there are actually
    362         // extensions listening for input changes.
    363         if (have_listeners)
    364           done_ = false;
    365       }
    366     }
    367   } else {
    368     if (matches.size() > kMaxMatches)
    369       matches.erase(matches.begin() + kMaxMatches, matches.end());
    370     for (TemplateURLService::TemplateURLVector::const_iterator i(
    371          matches.begin()); i != matches.end(); ++i) {
    372       matches_.push_back(CreateAutocompleteMatch(
    373           *i, input, keyword.length(), remaining_input, false, -1));
    374     }
    375   }
    376 }
    377 
    378 void KeywordProvider::Stop(bool clear_cached_results) {
    379   done_ = true;
    380   MaybeEndExtensionKeywordMode();
    381 }
    382 
    383 KeywordProvider::~KeywordProvider() {}
    384 
    385 // static
    386 bool KeywordProvider::ExtractKeywordFromInput(const AutocompleteInput& input,
    387                                               string16* keyword,
    388                                               string16* remaining_input) {
    389   if ((input.type() == AutocompleteInput::INVALID) ||
    390       (input.type() == AutocompleteInput::FORCED_QUERY))
    391     return false;
    392 
    393   string16 trimmed_input;
    394   TrimWhitespace(input.text(), TRIM_TRAILING, &trimmed_input);
    395   *keyword = TemplateURLService::CleanUserInputKeyword(
    396       SplitKeywordFromInput(trimmed_input, true, remaining_input));
    397   return !keyword->empty();
    398 }
    399 
    400 // static
    401 int KeywordProvider::CalculateRelevance(AutocompleteInput::Type type,
    402                                         bool complete,
    403                                         bool supports_replacement,
    404                                         bool prefer_keyword,
    405                                         bool allow_exact_keyword_match) {
    406   // This function is responsible for scoring suggestions of keywords
    407   // themselves and the suggestion of the verbatim query on an
    408   // extension keyword.  SearchProvider::CalculateRelevanceForKeywordVerbatim()
    409   // scores verbatim query suggestions for non-extension keywords.
    410   // These two functions are currently in sync, but there's no reason
    411   // we couldn't decide in the future to score verbatim matches
    412   // differently for extension and non-extension keywords.  If you
    413   // make such a change, however, you should update this comment to
    414   // describe it, so it's clear why the functions diverge.
    415   if (!complete)
    416     return (type == AutocompleteInput::URL) ? 700 : 450;
    417   if (!supports_replacement || (allow_exact_keyword_match && prefer_keyword))
    418     return 1500;
    419   return (allow_exact_keyword_match && (type == AutocompleteInput::QUERY)) ?
    420       1450 : 1100;
    421 }
    422 
    423 AutocompleteMatch KeywordProvider::CreateAutocompleteMatch(
    424     const TemplateURL* template_url,
    425     const AutocompleteInput& input,
    426     size_t prefix_length,
    427     const string16& remaining_input,
    428     bool allowed_to_be_default_match,
    429     int relevance) {
    430   DCHECK(template_url);
    431   const bool supports_replacement =
    432       template_url->url_ref().SupportsReplacement();
    433 
    434   // Create an edit entry of "[keyword] [remaining input]".  This is helpful
    435   // even when [remaining input] is empty, as the user can select the popup
    436   // choice and immediately begin typing in query input.
    437   const string16& keyword = template_url->keyword();
    438   const bool keyword_complete = (prefix_length == keyword.length());
    439   if (relevance < 0) {
    440     relevance =
    441         CalculateRelevance(input.type(), keyword_complete,
    442                            // When the user wants keyword matches to take
    443                            // preference, score them highly regardless of
    444                            // whether the input provides query text.
    445                            supports_replacement, input.prefer_keyword(),
    446                            input.allow_exact_keyword_match());
    447   }
    448   AutocompleteMatch match(this, relevance, false,
    449       supports_replacement ? AutocompleteMatchType::SEARCH_OTHER_ENGINE :
    450                              AutocompleteMatchType::HISTORY_KEYWORD);
    451   match.allowed_to_be_default_match = allowed_to_be_default_match;
    452   match.fill_into_edit = keyword;
    453   if (!remaining_input.empty() || !keyword_complete || supports_replacement)
    454     match.fill_into_edit.push_back(L' ');
    455   match.fill_into_edit.append(remaining_input);
    456   // If we wanted to set |result.inline_autocompletion| correctly, we'd need
    457   // CleanUserInputKeyword() to return the amount of adjustment it's made to
    458   // the user's input.  Because right now inexact keyword matches can't score
    459   // more highly than a "what you typed" match from one of the other providers,
    460   // we just don't bother to do this, and leave inline autocompletion off.
    461 
    462   // Create destination URL and popup entry content by substituting user input
    463   // into keyword templates.
    464   FillInURLAndContents(remaining_input, template_url, &match);
    465 
    466   match.keyword = keyword;
    467   match.transition = content::PAGE_TRANSITION_KEYWORD;
    468 
    469   return match;
    470 }
    471 
    472 void KeywordProvider::FillInURLAndContents(const string16& remaining_input,
    473                                            const TemplateURL* element,
    474                                            AutocompleteMatch* match) const {
    475   DCHECK(!element->short_name().empty());
    476   const TemplateURLRef& element_ref = element->url_ref();
    477   DCHECK(element_ref.IsValid());
    478   int message_id = element->IsExtensionKeyword() ?
    479       IDS_EXTENSION_KEYWORD_COMMAND : IDS_KEYWORD_SEARCH;
    480   if (remaining_input.empty()) {
    481     // Allow extension keyword providers to accept empty string input. This is
    482     // useful to allow extensions to do something in the case where no input is
    483     // entered.
    484     if (element_ref.SupportsReplacement() && !element->IsExtensionKeyword()) {
    485       // No query input; return a generic, no-destination placeholder.
    486       match->contents.assign(
    487           l10n_util::GetStringFUTF16(message_id,
    488               element->AdjustedShortNameForLocaleDirection(),
    489               l10n_util::GetStringUTF16(IDS_EMPTY_KEYWORD_VALUE)));
    490       match->contents_class.push_back(
    491           ACMatchClassification(0, ACMatchClassification::DIM));
    492     } else {
    493       // Keyword that has no replacement text (aka a shorthand for a URL).
    494       match->destination_url = GURL(element->url());
    495       match->contents.assign(element->short_name());
    496       AutocompleteMatch::ClassifyLocationInString(0, match->contents.length(),
    497           match->contents.length(), ACMatchClassification::NONE,
    498           &match->contents_class);
    499     }
    500   } else {
    501     // Create destination URL by escaping user input and substituting into
    502     // keyword template URL.  The escaping here handles whitespace in user
    503     // input, but we rely on later canonicalization functions to do more
    504     // fixup to make the URL valid if necessary.
    505     DCHECK(element_ref.SupportsReplacement());
    506     TemplateURLRef::SearchTermsArgs search_terms_args(remaining_input);
    507     search_terms_args.append_extra_query_params =
    508         element == GetTemplateURLService()->GetDefaultSearchProvider();
    509     match->destination_url =
    510         GURL(element_ref.ReplaceSearchTerms(search_terms_args));
    511     std::vector<size_t> content_param_offsets;
    512     match->contents.assign(l10n_util::GetStringFUTF16(message_id,
    513                                                       element->short_name(),
    514                                                       remaining_input,
    515                                                       &content_param_offsets));
    516     DCHECK_EQ(2U, content_param_offsets.size());
    517     AutocompleteMatch::ClassifyLocationInString(content_param_offsets[1],
    518         remaining_input.length(), match->contents.length(),
    519         ACMatchClassification::NONE, &match->contents_class);
    520   }
    521 }
    522 
    523 void KeywordProvider::Observe(int type,
    524                               const content::NotificationSource& source,
    525                               const content::NotificationDetails& details) {
    526   TemplateURLService* model = GetTemplateURLService();
    527   const AutocompleteInput& input = extension_suggest_last_input_;
    528 
    529   switch (type) {
    530     case chrome::NOTIFICATION_EXTENSION_OMNIBOX_INPUT_ENTERED:
    531       // Input has been accepted, so we're done with this input session. Ensure
    532       // we don't send the OnInputCancelled event, or handle any more stray
    533       // suggestions_ready events.
    534       current_keyword_extension_id_.clear();
    535       current_input_id_ = 0;
    536       return;
    537 
    538     case chrome::NOTIFICATION_EXTENSION_OMNIBOX_DEFAULT_SUGGESTION_CHANGED: {
    539       // It's possible to change the default suggestion while not in an editing
    540       // session.
    541       string16 keyword, remaining_input;
    542       if (matches_.empty() || current_keyword_extension_id_.empty() ||
    543           !ExtractKeywordFromInput(input, &keyword, &remaining_input))
    544         return;
    545 
    546       const TemplateURL* template_url(
    547           model->GetTemplateURLForKeyword(keyword));
    548       extensions::ApplyDefaultSuggestionForExtensionKeyword(
    549           profile_, template_url,
    550           remaining_input,
    551           &matches_[0]);
    552       listener_->OnProviderUpdate(true);
    553       return;
    554     }
    555 
    556     case chrome::NOTIFICATION_EXTENSION_OMNIBOX_SUGGESTIONS_READY: {
    557       const omnibox_api::SendSuggestions::Params& suggestions =
    558           *content::Details<
    559               omnibox_api::SendSuggestions::Params>(details).ptr();
    560       if (suggestions.request_id != current_input_id_)
    561         return;  // This is an old result. Just ignore.
    562 
    563       string16 keyword, remaining_input;
    564       bool result = ExtractKeywordFromInput(input, &keyword, &remaining_input);
    565       DCHECK(result);
    566       const TemplateURL* template_url =
    567           model->GetTemplateURLForKeyword(keyword);
    568 
    569       // TODO(mpcomplete): consider clamping the number of suggestions to
    570       // AutocompleteProvider::kMaxMatches.
    571       for (size_t i = 0; i < suggestions.suggest_results.size(); ++i) {
    572         const omnibox_api::SuggestResult& suggestion =
    573             *suggestions.suggest_results[i];
    574         // We want to order these suggestions in descending order, so start with
    575         // the relevance of the first result (added synchronously in Start()),
    576         // and subtract 1 for each subsequent suggestion from the extension.
    577         // We recompute the first match's relevance; we know that |complete|
    578         // is true, because we wouldn't get results from the extension unless
    579         // the full keyword had been typed.
    580         int first_relevance = CalculateRelevance(input.type(), true, true,
    581             input.prefer_keyword(), input.allow_exact_keyword_match());
    582         // Because these matches are async, we should never let them become the
    583         // default match, lest we introduce race conditions in the omnibox user
    584         // interaction.
    585         extension_suggest_matches_.push_back(CreateAutocompleteMatch(
    586             template_url, input, keyword.length(),
    587             UTF8ToUTF16(suggestion.content), false, first_relevance - (i + 1)));
    588 
    589         AutocompleteMatch* match = &extension_suggest_matches_.back();
    590         match->contents.assign(UTF8ToUTF16(suggestion.description));
    591         match->contents_class =
    592             extensions::StyleTypesToACMatchClassifications(suggestion);
    593         match->description.clear();
    594         match->description_class.clear();
    595       }
    596 
    597       done_ = true;
    598       matches_.insert(matches_.end(), extension_suggest_matches_.begin(),
    599                       extension_suggest_matches_.end());
    600       listener_->OnProviderUpdate(!extension_suggest_matches_.empty());
    601       return;
    602     }
    603 
    604     default:
    605       NOTREACHED();
    606       return;
    607   }
    608 }
    609 
    610 TemplateURLService* KeywordProvider::GetTemplateURLService() const {
    611   TemplateURLService* service = profile_ ?
    612       TemplateURLServiceFactory::GetForProfile(profile_) : model_;
    613   // Make sure the model is loaded. This is cheap and quickly bails out if
    614   // the model is already loaded.
    615   DCHECK(service);
    616   service->Load();
    617   return service;
    618 }
    619 
    620 void KeywordProvider::EnterExtensionKeywordMode(
    621     const std::string& extension_id) {
    622   DCHECK(current_keyword_extension_id_.empty());
    623   current_keyword_extension_id_ = extension_id;
    624 
    625   extensions::ExtensionOmniboxEventRouter::OnInputStarted(
    626       profile_, current_keyword_extension_id_);
    627 }
    628 
    629 void KeywordProvider::MaybeEndExtensionKeywordMode() {
    630   if (!current_keyword_extension_id_.empty()) {
    631     extensions::ExtensionOmniboxEventRouter::OnInputCancelled(
    632         profile_, current_keyword_extension_id_);
    633 
    634     current_keyword_extension_id_.clear();
    635   }
    636 }
    637