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