Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / autocomplete / keyword_provider.cc
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_util.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 "extensions/browser/extension_system.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         !extensions::util::IsIncognitoEnabled(extension->id(), profile_)))
210       return base::string16();
211   }
212
213   return keyword;
214 }
215
216 AutocompleteMatch KeywordProvider::CreateVerbatimMatch(
217     const base::string16& text,
218     const base::string16& keyword,
219     const AutocompleteInput& input) {
220   // A verbatim match is allowed to be the default match.
221   return CreateAutocompleteMatch(
222       GetTemplateURLService()->GetTemplateURLForKeyword(keyword), input,
223       keyword.length(), SplitReplacementStringFromInput(text, true), true, 0);
224 }
225
226 void KeywordProvider::Start(const AutocompleteInput& input,
227                             bool minimal_changes) {
228   // This object ensures we end keyword mode if we exit the function without
229   // toggling keyword mode to on.
230   ScopedEndExtensionKeywordMode keyword_mode_toggle(this);
231
232   matches_.clear();
233
234   if (!minimal_changes) {
235     done_ = true;
236
237     // Input has changed. Increment the input ID so that we can discard any
238     // stale extension suggestions that may be incoming.
239     current_input_id_ = ++global_input_uid_;
240   }
241
242   // Split user input into a keyword and some query input.
243   //
244   // We want to suggest keywords even when users have started typing URLs, on
245   // the assumption that they might not realize they no longer need to go to a
246   // site to be able to search it.  So we call CleanUserInputKeyword() to strip
247   // any initial scheme and/or "www.".  NOTE: Any heuristics or UI used to
248   // automatically/manually create keywords will need to be in sync with
249   // whatever we do here!
250   //
251   // TODO(pkasting): http://b/1112681 If someday we remember usage frequency for
252   // keywords, we might suggest keywords that haven't even been partially typed,
253   // if the user uses them enough and isn't obviously typing something else.  In
254   // this case we'd consider all input here to be query input.
255   base::string16 keyword, remaining_input;
256   if (!ExtractKeywordFromInput(input, &keyword, &remaining_input))
257     return;
258
259   // Get the best matches for this keyword.
260   //
261   // NOTE: We could cache the previous keywords and reuse them here in the
262   // |minimal_changes| case, but since we'd still have to recalculate their
263   // relevances and we can just recreate the results synchronously anyway, we
264   // don't bother.
265   //
266   // TODO(pkasting): http://b/893701 We should remember the user's use of a
267   // search query both from the autocomplete popup and from web pages
268   // themselves.
269   TemplateURLService::TemplateURLVector matches;
270   GetTemplateURLService()->FindMatchingKeywords(
271       keyword, !remaining_input.empty(), &matches);
272
273   for (TemplateURLService::TemplateURLVector::iterator i(matches.begin());
274        i != matches.end(); ) {
275     const TemplateURL* template_url = *i;
276
277     // Prune any extension keywords that are disallowed in incognito mode (if
278     // we're incognito), or disabled.
279     if (profile_ &&
280         (template_url->GetType() == TemplateURL::OMNIBOX_API_EXTENSION)) {
281       ExtensionService* service = extensions::ExtensionSystem::Get(profile_)->
282           extension_service();
283       const extensions::Extension* extension =
284           service->GetExtensionById(template_url->GetExtensionId(), false);
285       bool enabled =
286           extension && (!profile_->IsOffTheRecord() ||
287                         extensions::util::IsIncognitoEnabled(
288                             extension->id(), profile_));
289       if (!enabled) {
290         i = matches.erase(i);
291         continue;
292       }
293     }
294
295     // Prune any substituting keywords if there is no substitution.
296     if (template_url->SupportsReplacement() && remaining_input.empty() &&
297         !input.allow_exact_keyword_match()) {
298       i = matches.erase(i);
299       continue;
300     }
301
302     ++i;
303   }
304   if (matches.empty())
305     return;
306   std::sort(matches.begin(), matches.end(), CompareQuality());
307
308   // Limit to one exact or three inexact matches, and mark them up for display
309   // in the autocomplete popup.
310   // Any exact match is going to be the highest quality match, and thus at the
311   // front of our vector.
312   if (matches.front()->keyword() == keyword) {
313     const TemplateURL* template_url = matches.front();
314     const bool is_extension_keyword =
315         template_url->GetType() == TemplateURL::OMNIBOX_API_EXTENSION;
316
317     // Only create an exact match if |remaining_input| is empty or if
318     // this is an extension keyword.  If |remaining_input| is a
319     // non-empty non-extension keyword (i.e., a regular keyword that
320     // supports replacement and that has extra text following it),
321     // then SearchProvider creates the exact (a.k.a. verbatim) match.
322     if (!remaining_input.empty() && !is_extension_keyword)
323       return;
324
325     // TODO(pkasting): We should probably check that if the user explicitly
326     // typed a scheme, that scheme matches the one in |template_url|.
327
328     // When creating an exact match (either for the keyword itself, no
329     // remaining query or an extension keyword, possibly with remaining
330     // input), allow the match to be the default match.
331     matches_.push_back(CreateAutocompleteMatch(
332         template_url, input, keyword.length(), remaining_input, true, -1));
333
334     if (profile_ && is_extension_keyword) {
335       if (input.matches_requested() == AutocompleteInput::ALL_MATCHES) {
336         if (template_url->GetExtensionId() != current_keyword_extension_id_)
337           MaybeEndExtensionKeywordMode();
338         if (current_keyword_extension_id_.empty())
339           EnterExtensionKeywordMode(template_url->GetExtensionId());
340         keyword_mode_toggle.StayInKeywordMode();
341       }
342
343       extensions::ApplyDefaultSuggestionForExtensionKeyword(
344           profile_, template_url,
345           remaining_input,
346           &matches_[0]);
347
348       if (minimal_changes &&
349           (input.matches_requested() != AutocompleteInput::BEST_MATCH)) {
350         // If the input hasn't significantly changed, we can just use the
351         // suggestions from last time. We need to readjust the relevance to
352         // ensure it is less than the main match's relevance.
353         for (size_t i = 0; i < extension_suggest_matches_.size(); ++i) {
354           matches_.push_back(extension_suggest_matches_[i]);
355           matches_.back().relevance = matches_[0].relevance - (i + 1);
356         }
357       } else if (input.matches_requested() == AutocompleteInput::ALL_MATCHES) {
358         extension_suggest_last_input_ = input;
359         extension_suggest_matches_.clear();
360
361         bool have_listeners =
362           extensions::ExtensionOmniboxEventRouter::OnInputChanged(
363               profile_, template_url->GetExtensionId(),
364               base::UTF16ToUTF8(remaining_input), current_input_id_);
365
366         // We only have to wait for suggest results if there are actually
367         // extensions listening for input changes.
368         if (have_listeners)
369           done_ = false;
370       }
371     }
372   } else {
373     if (matches.size() > kMaxMatches)
374       matches.erase(matches.begin() + kMaxMatches, matches.end());
375     for (TemplateURLService::TemplateURLVector::const_iterator i(
376          matches.begin()); i != matches.end(); ++i) {
377       matches_.push_back(CreateAutocompleteMatch(
378           *i, input, keyword.length(), remaining_input, false, -1));
379     }
380   }
381 }
382
383 void KeywordProvider::Stop(bool clear_cached_results) {
384   done_ = true;
385   MaybeEndExtensionKeywordMode();
386 }
387
388 KeywordProvider::~KeywordProvider() {}
389
390 // static
391 bool KeywordProvider::ExtractKeywordFromInput(const AutocompleteInput& input,
392                                               base::string16* keyword,
393                                               base::string16* remaining_input) {
394   if ((input.type() == AutocompleteInput::INVALID) ||
395       (input.type() == AutocompleteInput::FORCED_QUERY))
396     return false;
397
398   *keyword = TemplateURLService::CleanUserInputKeyword(
399       SplitKeywordFromInput(input.text(), true, remaining_input));
400   return !keyword->empty();
401 }
402
403 // static
404 int KeywordProvider::CalculateRelevance(AutocompleteInput::Type type,
405                                         bool complete,
406                                         bool supports_replacement,
407                                         bool prefer_keyword,
408                                         bool allow_exact_keyword_match) {
409   // This function is responsible for scoring suggestions of keywords
410   // themselves and the suggestion of the verbatim query on an
411   // extension keyword.  SearchProvider::CalculateRelevanceForKeywordVerbatim()
412   // scores verbatim query suggestions for non-extension keywords.
413   // These two functions are currently in sync, but there's no reason
414   // we couldn't decide in the future to score verbatim matches
415   // differently for extension and non-extension keywords.  If you
416   // make such a change, however, you should update this comment to
417   // describe it, so it's clear why the functions diverge.
418   if (!complete)
419     return (type == AutocompleteInput::URL) ? 700 : 450;
420   if (!supports_replacement || (allow_exact_keyword_match && prefer_keyword))
421     return 1500;
422   return (allow_exact_keyword_match && (type == AutocompleteInput::QUERY)) ?
423       1450 : 1100;
424 }
425
426 AutocompleteMatch KeywordProvider::CreateAutocompleteMatch(
427     const TemplateURL* template_url,
428     const AutocompleteInput& input,
429     size_t prefix_length,
430     const base::string16& remaining_input,
431     bool allowed_to_be_default_match,
432     int relevance) {
433   DCHECK(template_url);
434   const bool supports_replacement =
435       template_url->url_ref().SupportsReplacement();
436
437   // Create an edit entry of "[keyword] [remaining input]".  This is helpful
438   // even when [remaining input] is empty, as the user can select the popup
439   // choice and immediately begin typing in query input.
440   const base::string16& keyword = template_url->keyword();
441   const bool keyword_complete = (prefix_length == keyword.length());
442   if (relevance < 0) {
443     relevance =
444         CalculateRelevance(input.type(), keyword_complete,
445                            // When the user wants keyword matches to take
446                            // preference, score them highly regardless of
447                            // whether the input provides query text.
448                            supports_replacement, input.prefer_keyword(),
449                            input.allow_exact_keyword_match());
450   }
451   AutocompleteMatch match(this, relevance, false,
452       supports_replacement ? AutocompleteMatchType::SEARCH_OTHER_ENGINE :
453                              AutocompleteMatchType::HISTORY_KEYWORD);
454   match.allowed_to_be_default_match = allowed_to_be_default_match;
455   match.fill_into_edit = keyword;
456   if (!remaining_input.empty() || supports_replacement)
457     match.fill_into_edit.push_back(L' ');
458   match.fill_into_edit.append(remaining_input);
459   // If we wanted to set |result.inline_autocompletion| correctly, we'd need
460   // CleanUserInputKeyword() to return the amount of adjustment it's made to
461   // the user's input.  Because right now inexact keyword matches can't score
462   // more highly than a "what you typed" match from one of the other providers,
463   // we just don't bother to do this, and leave inline autocompletion off.
464
465   // Create destination URL and popup entry content by substituting user input
466   // into keyword templates.
467   FillInURLAndContents(remaining_input, template_url, &match);
468
469   match.keyword = keyword;
470   match.transition = content::PAGE_TRANSITION_KEYWORD;
471
472   return match;
473 }
474
475 void KeywordProvider::FillInURLAndContents(
476     const base::string16& remaining_input,
477     const TemplateURL* element,
478     AutocompleteMatch* match) const {
479   DCHECK(!element->short_name().empty());
480   const TemplateURLRef& element_ref = element->url_ref();
481   DCHECK(element_ref.IsValid());
482   int message_id = (element->GetType() == TemplateURL::OMNIBOX_API_EXTENSION) ?
483       IDS_EXTENSION_KEYWORD_COMMAND : IDS_KEYWORD_SEARCH;
484   if (remaining_input.empty()) {
485     // Allow extension keyword providers to accept empty string input. This is
486     // useful to allow extensions to do something in the case where no input is
487     // entered.
488     if (element_ref.SupportsReplacement() &&
489         (element->GetType() != TemplateURL::OMNIBOX_API_EXTENSION)) {
490       // No query input; return a generic, no-destination placeholder.
491       match->contents.assign(
492           l10n_util::GetStringFUTF16(message_id,
493               element->AdjustedShortNameForLocaleDirection(),
494               l10n_util::GetStringUTF16(IDS_EMPTY_KEYWORD_VALUE)));
495       match->contents_class.push_back(
496           ACMatchClassification(0, ACMatchClassification::DIM));
497     } else {
498       // Keyword that has no replacement text (aka a shorthand for a URL).
499       match->destination_url = GURL(element->url());
500       match->contents.assign(element->short_name());
501       AutocompleteMatch::ClassifyLocationInString(0, match->contents.length(),
502           match->contents.length(), ACMatchClassification::NONE,
503           &match->contents_class);
504     }
505   } else {
506     // Create destination URL by escaping user input and substituting into
507     // keyword template URL.  The escaping here handles whitespace in user
508     // input, but we rely on later canonicalization functions to do more
509     // fixup to make the URL valid if necessary.
510     DCHECK(element_ref.SupportsReplacement());
511     TemplateURLRef::SearchTermsArgs search_terms_args(remaining_input);
512     search_terms_args.append_extra_query_params =
513         element == GetTemplateURLService()->GetDefaultSearchProvider();
514     match->destination_url =
515         GURL(element_ref.ReplaceSearchTerms(search_terms_args));
516     std::vector<size_t> content_param_offsets;
517     match->contents.assign(l10n_util::GetStringFUTF16(message_id,
518                                                       element->short_name(),
519                                                       remaining_input,
520                                                       &content_param_offsets));
521     DCHECK_EQ(2U, content_param_offsets.size());
522     AutocompleteMatch::ClassifyLocationInString(content_param_offsets[1],
523         remaining_input.length(), match->contents.length(),
524         ACMatchClassification::NONE, &match->contents_class);
525   }
526 }
527
528 void KeywordProvider::Observe(int type,
529                               const content::NotificationSource& source,
530                               const content::NotificationDetails& details) {
531   TemplateURLService* model = GetTemplateURLService();
532   const AutocompleteInput& input = extension_suggest_last_input_;
533
534   switch (type) {
535     case chrome::NOTIFICATION_EXTENSION_OMNIBOX_INPUT_ENTERED:
536       // Input has been accepted, so we're done with this input session. Ensure
537       // we don't send the OnInputCancelled event, or handle any more stray
538       // suggestions_ready events.
539       current_keyword_extension_id_.clear();
540       current_input_id_ = 0;
541       return;
542
543     case chrome::NOTIFICATION_EXTENSION_OMNIBOX_DEFAULT_SUGGESTION_CHANGED: {
544       // It's possible to change the default suggestion while not in an editing
545       // session.
546       base::string16 keyword, remaining_input;
547       if (matches_.empty() || current_keyword_extension_id_.empty() ||
548           !ExtractKeywordFromInput(input, &keyword, &remaining_input))
549         return;
550
551       const TemplateURL* template_url(
552           model->GetTemplateURLForKeyword(keyword));
553       extensions::ApplyDefaultSuggestionForExtensionKeyword(
554           profile_, template_url,
555           remaining_input,
556           &matches_[0]);
557       listener_->OnProviderUpdate(true);
558       return;
559     }
560
561     case chrome::NOTIFICATION_EXTENSION_OMNIBOX_SUGGESTIONS_READY: {
562       const omnibox_api::SendSuggestions::Params& suggestions =
563           *content::Details<
564               omnibox_api::SendSuggestions::Params>(details).ptr();
565       if (suggestions.request_id != current_input_id_)
566         return;  // This is an old result. Just ignore.
567
568       base::string16 keyword, remaining_input;
569       bool result = ExtractKeywordFromInput(input, &keyword, &remaining_input);
570       DCHECK(result);
571       const TemplateURL* template_url =
572           model->GetTemplateURLForKeyword(keyword);
573
574       // TODO(mpcomplete): consider clamping the number of suggestions to
575       // AutocompleteProvider::kMaxMatches.
576       for (size_t i = 0; i < suggestions.suggest_results.size(); ++i) {
577         const omnibox_api::SuggestResult& suggestion =
578             *suggestions.suggest_results[i];
579         // We want to order these suggestions in descending order, so start with
580         // the relevance of the first result (added synchronously in Start()),
581         // and subtract 1 for each subsequent suggestion from the extension.
582         // We recompute the first match's relevance; we know that |complete|
583         // is true, because we wouldn't get results from the extension unless
584         // the full keyword had been typed.
585         int first_relevance = CalculateRelevance(input.type(), true, true,
586             input.prefer_keyword(), input.allow_exact_keyword_match());
587         // Because these matches are async, we should never let them become the
588         // default match, lest we introduce race conditions in the omnibox user
589         // interaction.
590         extension_suggest_matches_.push_back(CreateAutocompleteMatch(
591             template_url, input, keyword.length(),
592             base::UTF8ToUTF16(suggestion.content), false,
593             first_relevance - (i + 1)));
594
595         AutocompleteMatch* match = &extension_suggest_matches_.back();
596         match->contents.assign(base::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 }