172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen// Copyright (c) 2011 The Chromium Authors. All rights reserved.
2c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// Use of this source code is governed by a BSD-style license that can be
3c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// found in the LICENSE file.
4c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
5c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#include "chrome/browser/autocomplete/keyword_provider.h"
6c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
7c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#include <algorithm>
8c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#include <vector>
9c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
10c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#include "base/string16.h"
11c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#include "base/utf_string_conversions.h"
12513209b27ff55e2841eac0e4120199c23acce758Ben Murdoch#include "chrome/browser/autocomplete/autocomplete_match.h"
13c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#include "chrome/browser/extensions/extension_omnibox_api.h"
1421d179b334e59e9a3bfcaed4c4430bef1bc5759dKristian Monsen#include "chrome/browser/extensions/extension_service.h"
1521d179b334e59e9a3bfcaed4c4430bef1bc5759dKristian Monsen#include "chrome/browser/profiles/profile.h"
16c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#include "chrome/browser/search_engines/template_url.h"
17c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#include "chrome/browser/search_engines/template_url_model.h"
18ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen#include "content/common/notification_details.h"
19ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen#include "content/common/notification_source.h"
20c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#include "grit/generated_resources.h"
21c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#include "net/base/escape.h"
22c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#include "net/base/net_util.h"
2372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen#include "ui/base/l10n/l10n_util.h"
24c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
25c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// Helper functor for Start(), for ending keyword mode unless explicitly told
26c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// otherwise.
27c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochclass KeywordProvider::ScopedEndExtensionKeywordMode {
28c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch public:
293345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick  explicit ScopedEndExtensionKeywordMode(KeywordProvider* provider)
30c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      : provider_(provider) { }
31c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  ~ScopedEndExtensionKeywordMode() {
32c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    if (provider_)
33c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      provider_->MaybeEndExtensionKeywordMode();
34c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  }
35c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
36c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  void StayInKeywordMode() {
37c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    provider_ = NULL;
38c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  }
39c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch private:
40c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  KeywordProvider* provider_;
41c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch};
42c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
43c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// static
4472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsenstring16 KeywordProvider::SplitReplacementStringFromInput(
4572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    const string16& input,
4672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    bool trim_leading_whitespace) {
47c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // The input may contain leading whitespace, strip it.
4872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  string16 trimmed_input;
49c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  TrimWhitespace(input, TRIM_LEADING, &trimmed_input);
50c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
51c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // And extract the replacement string.
5272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  string16 remaining_input;
5372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  SplitKeywordFromInput(trimmed_input, trim_leading_whitespace,
5472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen                        &remaining_input);
55c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  return remaining_input;
56c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}
57c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
58c407dc5cd9bdc5668497f21b26b09d988ab439deBen MurdochKeywordProvider::KeywordProvider(ACProviderListener* listener, Profile* profile)
59c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    : AutocompleteProvider(listener, profile, "Keyword"),
60c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      model_(NULL),
61c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      current_input_id_(0) {
62c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // Extension suggestions always come from the original profile, since that's
63c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // where extensions run. We use the input ID to distinguish whether the
64c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // suggestions are meant for us.
65c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  registrar_.Add(this, NotificationType::EXTENSION_OMNIBOX_SUGGESTIONS_READY,
66c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch                 Source<Profile>(profile->GetOriginalProfile()));
67201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch  registrar_.Add(this,
68201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch                 NotificationType::EXTENSION_OMNIBOX_DEFAULT_SUGGESTION_CHANGED,
69201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch                 Source<Profile>(profile->GetOriginalProfile()));
70c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  registrar_.Add(this, NotificationType::EXTENSION_OMNIBOX_INPUT_ENTERED,
71c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch                 Source<Profile>(profile));
72c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}
73c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
74c407dc5cd9bdc5668497f21b26b09d988ab439deBen MurdochKeywordProvider::KeywordProvider(ACProviderListener* listener,
75c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch                                 TemplateURLModel* model)
76c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    : AutocompleteProvider(listener, NULL, "Keyword"),
77c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      model_(model),
78c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      current_input_id_(0) {
79c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}
80c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
81c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
82c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochnamespace {
83c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
84c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// Helper functor for Start(), for sorting keyword matches by quality.
85c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochclass CompareQuality {
86c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch public:
87c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // A keyword is of higher quality when a greater fraction of it has been
88c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // typed, that is, when it is shorter.
89c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  //
90c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // TODO(pkasting): http://b/740691 Most recent and most frequent keywords are
91c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // probably better rankings than the fraction of the keyword typed.  We should
92c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // always put any exact matches first no matter what, since the code in
93c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // Start() assumes this (and it makes sense).
9472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  bool operator()(const string16& keyword1,
9572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen                  const string16& keyword2) const {
96c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    return keyword1.length() < keyword2.length();
97c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  }
98c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch};
99c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
100c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// We need our input IDs to be unique across all profiles, so we keep a global
101c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// UID that each provider uses.
102c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochstatic int global_input_uid_;
103c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
104c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}  // namespace
105c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
106c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// static
107c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochconst TemplateURL* KeywordProvider::GetSubstitutingTemplateURLForInput(
108c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    Profile* profile,
109c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    const AutocompleteInput& input,
11072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    string16* remaining_input) {
1114a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch  if (!input.allow_exact_keyword_match())
1124a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    return NULL;
1134a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
11472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  string16 keyword;
115c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  if (!ExtractKeywordFromInput(input, &keyword, remaining_input))
116c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    return NULL;
117c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
118c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // Make sure the model is loaded. This is cheap and quickly bails out if
119c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // the model is already loaded.
120c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  TemplateURLModel* model = profile->GetTemplateURLModel();
121c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  DCHECK(model);
122c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  model->Load();
123c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
124c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  const TemplateURL* template_url = model->GetTemplateURLForKeyword(keyword);
125c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  return TemplateURL::SupportsReplacement(template_url) ? template_url : NULL;
126c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}
127c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
128c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochvoid KeywordProvider::Start(const AutocompleteInput& input,
129c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch                            bool minimal_changes) {
130c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // This object ensures we end keyword mode if we exit the function without
131c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // toggling keyword mode to on.
132c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  ScopedEndExtensionKeywordMode keyword_mode_toggle(this);
133c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
134c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  matches_.clear();
135c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
136c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  if (!minimal_changes) {
137c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    done_ = true;
138c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
139c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    // Input has changed. Increment the input ID so that we can discard any
140c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    // stale extension suggestions that may be incoming.
141c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    current_input_id_ = ++global_input_uid_;
142c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  }
143c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
144c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // Split user input into a keyword and some query input.
145c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  //
146c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // We want to suggest keywords even when users have started typing URLs, on
147c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // the assumption that they might not realize they no longer need to go to a
148c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // site to be able to search it.  So we call CleanUserInputKeyword() to strip
149c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // any initial scheme and/or "www.".  NOTE: Any heuristics or UI used to
150c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // automatically/manually create keywords will need to be in sync with
151c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // whatever we do here!
152c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  //
153c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // TODO(pkasting): http://b/1112681 If someday we remember usage frequency for
154c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // keywords, we might suggest keywords that haven't even been partially typed,
155c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // if the user uses them enough and isn't obviously typing something else.  In
156c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // this case we'd consider all input here to be query input.
15772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  string16 keyword, remaining_input;
158c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  if (!ExtractKeywordFromInput(input, &keyword, &remaining_input))
159c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    return;
160c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
161c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // Make sure the model is loaded. This is cheap and quickly bails out if
162c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // the model is already loaded.
163c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  TemplateURLModel* model = profile_ ? profile_->GetTemplateURLModel() : model_;
164c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  DCHECK(model);
165c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  model->Load();
166c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
167c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // Get the best matches for this keyword.
168c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  //
169c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // NOTE: We could cache the previous keywords and reuse them here in the
170c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // |minimal_changes| case, but since we'd still have to recalculate their
171c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // relevances and we can just recreate the results synchronously anyway, we
172c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // don't bother.
173c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  //
174c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // TODO(pkasting): http://b/893701 We should remember the user's use of a
175c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // search query both from the autocomplete popup and from web pages
176c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // themselves.
17772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  std::vector<string16> keyword_matches;
17872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  model->FindMatchingKeywords(keyword,
17972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen                              !remaining_input.empty(),
180c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch                              &keyword_matches);
181513209b27ff55e2841eac0e4120199c23acce758Ben Murdoch
182513209b27ff55e2841eac0e4120199c23acce758Ben Murdoch  // Prune any extension keywords that are disallowed in incognito mode (if
183513209b27ff55e2841eac0e4120199c23acce758Ben Murdoch  // we're incognito), or disabled.
18472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  for (std::vector<string16>::iterator i(keyword_matches.begin());
185513209b27ff55e2841eac0e4120199c23acce758Ben Murdoch       i != keyword_matches.end(); ) {
186513209b27ff55e2841eac0e4120199c23acce758Ben Murdoch    const TemplateURL* template_url(model->GetTemplateURLForKeyword(*i));
187513209b27ff55e2841eac0e4120199c23acce758Ben Murdoch    if (profile_ &&
188ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        input.matches_requested() == AutocompleteInput::ALL_MATCHES &&
189ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        template_url->IsExtensionKeyword()) {
19021d179b334e59e9a3bfcaed4c4430bef1bc5759dKristian Monsen      ExtensionService* service = profile_->GetExtensionService();
191513209b27ff55e2841eac0e4120199c23acce758Ben Murdoch      const Extension* extension = service->GetExtensionById(
192513209b27ff55e2841eac0e4120199c23acce758Ben Murdoch          template_url->GetExtensionId(), false);
193ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      bool enabled =
194ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen          extension && (!profile_->IsOffTheRecord() ||
195ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen                        service->IsIncognitoEnabled(extension->id()));
196513209b27ff55e2841eac0e4120199c23acce758Ben Murdoch      if (!enabled) {
197513209b27ff55e2841eac0e4120199c23acce758Ben Murdoch        i = keyword_matches.erase(i);
198513209b27ff55e2841eac0e4120199c23acce758Ben Murdoch        continue;
199513209b27ff55e2841eac0e4120199c23acce758Ben Murdoch      }
200513209b27ff55e2841eac0e4120199c23acce758Ben Murdoch    }
201513209b27ff55e2841eac0e4120199c23acce758Ben Murdoch    ++i;
202513209b27ff55e2841eac0e4120199c23acce758Ben Murdoch  }
203c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  if (keyword_matches.empty())
204c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    return;
205c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  std::sort(keyword_matches.begin(), keyword_matches.end(), CompareQuality());
206c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
207c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // Limit to one exact or three inexact matches, and mark them up for display
208c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // in the autocomplete popup.
209c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // Any exact match is going to be the highest quality match, and thus at the
210c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // front of our vector.
211c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  if (keyword_matches.front() == keyword) {
212c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    const TemplateURL* template_url(model->GetTemplateURLForKeyword(keyword));
2133345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick    // TODO(pkasting): We should probably check that if the user explicitly
2143345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick    // typed a scheme, that scheme matches the one in |template_url|.
215513209b27ff55e2841eac0e4120199c23acce758Ben Murdoch    matches_.push_back(CreateAutocompleteMatch(model, keyword, input,
216513209b27ff55e2841eac0e4120199c23acce758Ben Murdoch                                               keyword.length(),
217513209b27ff55e2841eac0e4120199c23acce758Ben Murdoch                                               remaining_input, -1));
2183345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick
219c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    if (profile_ &&
220ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        input.matches_requested() == AutocompleteInput::ALL_MATCHES &&
221ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        template_url->IsExtensionKeyword()) {
222513209b27ff55e2841eac0e4120199c23acce758Ben Murdoch      if (template_url->GetExtensionId() != current_keyword_extension_id_)
223c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        MaybeEndExtensionKeywordMode();
224c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      if (current_keyword_extension_id_.empty())
225513209b27ff55e2841eac0e4120199c23acce758Ben Murdoch        EnterExtensionKeywordMode(template_url->GetExtensionId());
226c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      keyword_mode_toggle.StayInKeywordMode();
227c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
228201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch      ApplyDefaultSuggestionForExtensionKeyword(profile_, template_url,
22972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen                                                remaining_input,
230201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch                                                &matches_[0]);
231201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch
232c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      if (minimal_changes) {
233c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        // If the input hasn't significantly changed, we can just use the
234c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        // suggestions from last time. We need to readjust the relevance to
235c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        // ensure it is less than the main match's relevance.
236c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        for (size_t i = 0; i < extension_suggest_matches_.size(); ++i) {
237c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          matches_.push_back(extension_suggest_matches_[i]);
238c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          matches_.back().relevance = matches_[0].relevance - (i + 1);
239c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        }
240c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      } else {
241c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        extension_suggest_last_input_ = input;
242c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        extension_suggest_matches_.clear();
243c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
244c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        bool have_listeners = ExtensionOmniboxEventRouter::OnInputChanged(
245c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch            profile_, template_url->GetExtensionId(),
24672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen            UTF16ToUTF8(remaining_input), current_input_id_);
247c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
248c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        // We only have to wait for suggest results if there are actually
249c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        // extensions listening for input changes.
250c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        if (have_listeners)
251c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          done_ = false;
252c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      }
253c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    }
254c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  } else {
255c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    if (keyword_matches.size() > kMaxMatches) {
256c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      keyword_matches.erase(keyword_matches.begin() + kMaxMatches,
257c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch                            keyword_matches.end());
258c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    }
25972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    for (std::vector<string16>::const_iterator i(keyword_matches.begin());
260c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch         i != keyword_matches.end(); ++i) {
26172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      matches_.push_back(CreateAutocompleteMatch(model, *i,
26272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen                                                 input, keyword.length(),
263c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch                                                 remaining_input, -1));
264c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    }
265c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  }
266c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}
267c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
268c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochvoid KeywordProvider::Stop() {
269c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  done_ = true;
270c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  MaybeEndExtensionKeywordMode();
271c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}
272c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
273731df977c0511bca2206b5f333555b1205ff1f43Iain MerrickKeywordProvider::~KeywordProvider() {}
274731df977c0511bca2206b5f333555b1205ff1f43Iain Merrick
275c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// static
276c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochbool KeywordProvider::ExtractKeywordFromInput(const AutocompleteInput& input,
27772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen                                              string16* keyword,
27872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen                                              string16* remaining_input) {
279c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  if ((input.type() == AutocompleteInput::INVALID) ||
280c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      (input.type() == AutocompleteInput::FORCED_QUERY))
281c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    return false;
282c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
283c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  *keyword = TemplateURLModel::CleanUserInputKeyword(
28472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      SplitKeywordFromInput(input.text(), true, remaining_input));
285c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  return !keyword->empty();
286c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}
287c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
288c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// static
28972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsenstring16 KeywordProvider::SplitKeywordFromInput(
29072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    const string16& input,
29172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    bool trim_leading_whitespace,
29272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    string16* remaining_input) {
293c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // Find end of first token.  The AutocompleteController has trimmed leading
294c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // whitespace, so we need not skip over that.
29572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  const size_t first_white(input.find_first_of(kWhitespaceUTF16));
296c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  DCHECK_NE(0U, first_white);
29772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  if (first_white == string16::npos)
298c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    return input;  // Only one token provided.
299c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
300c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // Set |remaining_input| to everything after the first token.
301c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  DCHECK(remaining_input != NULL);
30272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  const size_t remaining_start = trim_leading_whitespace ?
30372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    input.find_first_not_of(kWhitespaceUTF16, first_white) : first_white + 1;
30472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
30572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  if (remaining_start < input.length())
30672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    remaining_input->assign(input.begin() + remaining_start, input.end());
307c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
308c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // Return first token as keyword.
309c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  return input.substr(0, first_white);
310c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}
311c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
312c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// static
313c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochvoid KeywordProvider::FillInURLAndContents(
31472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    const string16& remaining_input,
315c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    const TemplateURL* element,
316c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    AutocompleteMatch* match) {
317c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  DCHECK(!element->short_name().empty());
318c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  DCHECK(element->url());
319c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  DCHECK(element->url()->IsValid());
320c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  int message_id = element->IsExtensionKeyword() ?
321c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      IDS_EXTENSION_KEYWORD_COMMAND : IDS_KEYWORD_SEARCH;
322c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  if (remaining_input.empty()) {
3234a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    // Allow extension keyword providers to accept empty string input. This is
3244a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    // useful to allow extensions to do something in the case where no input is
3254a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    // entered.
3264a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    if (element->url()->SupportsReplacement() &&
3274a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch        !element->IsExtensionKeyword()) {
328c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      // No query input; return a generic, no-destination placeholder.
32972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      match->contents.assign(
3303f50c38dc070f4bb515c1b64450dae14f316474eKristian Monsen          l10n_util::GetStringFUTF16(message_id,
33172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen              element->AdjustedShortNameForLocaleDirection(),
33272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen              l10n_util::GetStringUTF16(IDS_EMPTY_KEYWORD_VALUE)));
333c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      match->contents_class.push_back(
334c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          ACMatchClassification(0, ACMatchClassification::DIM));
335c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    } else {
336c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      // Keyword that has no replacement text (aka a shorthand for a URL).
337c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      match->destination_url = GURL(element->url()->url());
338c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      match->contents.assign(element->short_name());
339c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      AutocompleteMatch::ClassifyLocationInString(0, match->contents.length(),
340c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          match->contents.length(), ACMatchClassification::NONE,
341c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          &match->contents_class);
342c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    }
343c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  } else {
344c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    // Create destination URL by escaping user input and substituting into
345c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    // keyword template URL.  The escaping here handles whitespace in user
346c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    // input, but we rely on later canonicalization functions to do more
347c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    // fixup to make the URL valid if necessary.
348c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    DCHECK(element->url()->SupportsReplacement());
349c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    match->destination_url = GURL(element->url()->ReplaceSearchTerms(
35072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen        *element, remaining_input,
35172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen        TemplateURLRef::NO_SUGGESTIONS_AVAILABLE, string16()));
352c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    std::vector<size_t> content_param_offsets;
35372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    match->contents.assign(l10n_util::GetStringFUTF16(message_id,
35472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen                                                      element->short_name(),
35572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen                                                      remaining_input,
35672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen                                                      &content_param_offsets));
357c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    if (content_param_offsets.size() == 2) {
358c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      AutocompleteMatch::ClassifyLocationInString(content_param_offsets[1],
359c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          remaining_input.length(), match->contents.length(),
360c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          ACMatchClassification::NONE, &match->contents_class);
361c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    } else {
362c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      // See comments on an identical NOTREACHED() in search_provider.cc.
363c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      NOTREACHED();
364c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    }
365c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  }
366c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}
367c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
368c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// static
369c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochint KeywordProvider::CalculateRelevance(AutocompleteInput::Type type,
370c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch                                        bool complete,
37172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen                                        bool supports_replacement,
37272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen                                        bool prefer_keyword,
3734a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch                                        bool allow_exact_keyword_match) {
374c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  if (!complete)
375c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    return (type == AutocompleteInput::URL) ? 700 : 450;
37672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  if (!supports_replacement || (allow_exact_keyword_match && prefer_keyword))
377c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    return 1500;
37872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  return (allow_exact_keyword_match && (type == AutocompleteInput::QUERY)) ?
37972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      1450 : 1100;
380c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}
381c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
382c407dc5cd9bdc5668497f21b26b09d988ab439deBen MurdochAutocompleteMatch KeywordProvider::CreateAutocompleteMatch(
383c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    TemplateURLModel* model,
38472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    const string16& keyword,
385c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    const AutocompleteInput& input,
386c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    size_t prefix_length,
38772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    const string16& remaining_input,
388c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    int relevance) {
389c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  DCHECK(model);
390c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // Get keyword data from data store.
39172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  const TemplateURL* element(
39272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      model->GetTemplateURLForKeyword(keyword));
393c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  DCHECK(element && element->url());
394c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  const bool supports_replacement = element->url()->SupportsReplacement();
395c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
396c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // Create an edit entry of "[keyword] [remaining input]".  This is helpful
397c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // even when [remaining input] is empty, as the user can select the popup
398c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // choice and immediately begin typing in query input.
399c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  const bool keyword_complete = (prefix_length == keyword.length());
400c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  if (relevance < 0) {
401c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    relevance =
402c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        CalculateRelevance(input.type(), keyword_complete,
403c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch                           // When the user wants keyword matches to take
404c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch                           // preference, score them highly regardless of
405c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch                           // whether the input provides query text.
40672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen                           supports_replacement, input.prefer_keyword(),
4074a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch                           input.allow_exact_keyword_match());
408c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  }
409c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  AutocompleteMatch result(this, relevance, false,
410c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      supports_replacement ? AutocompleteMatch::SEARCH_OTHER_ENGINE :
411c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch                             AutocompleteMatch::HISTORY_KEYWORD);
412c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  result.fill_into_edit.assign(keyword);
413c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  if (!remaining_input.empty() || !keyword_complete || supports_replacement)
414c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    result.fill_into_edit.push_back(L' ');
415c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  result.fill_into_edit.append(remaining_input);
4163345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick  // If we wanted to set |result.inline_autocomplete_offset| correctly, we'd
4173345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick  // need CleanUserInputKeyword() to return the amount of adjustment it's made
4183345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick  // to the user's input.  Because right now inexact keyword matches can't score
4193345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick  // more highly than a "what you typed" match from one of the other providers,
4203345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick  // we just don't bother to do this, and leave inline autocompletion off.
42172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  result.inline_autocomplete_offset = string16::npos;
422c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
423c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // Create destination URL and popup entry content by substituting user input
424c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // into keyword templates.
425c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  FillInURLAndContents(remaining_input, element, &result);
426c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
427c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  if (supports_replacement)
428c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    result.template_url = element;
429c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  result.transition = PageTransition::KEYWORD;
430c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
431c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // Create popup entry description based on the keyword name.
432c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  if (!element->IsExtensionKeyword()) {
43372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    result.description.assign(l10n_util::GetStringFUTF16(
43472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen        IDS_AUTOCOMPLETE_KEYWORD_DESCRIPTION, keyword));
4353f50c38dc070f4bb515c1b64450dae14f316474eKristian Monsen    string16 keyword_desc(
4363f50c38dc070f4bb515c1b64450dae14f316474eKristian Monsen        l10n_util::GetStringUTF16(IDS_AUTOCOMPLETE_KEYWORD_DESCRIPTION));
4373f50c38dc070f4bb515c1b64450dae14f316474eKristian Monsen    AutocompleteMatch::ClassifyLocationInString(
4383f50c38dc070f4bb515c1b64450dae14f316474eKristian Monsen        keyword_desc.find(ASCIIToUTF16("%s")),
4393f50c38dc070f4bb515c1b64450dae14f316474eKristian Monsen        prefix_length,
4403f50c38dc070f4bb515c1b64450dae14f316474eKristian Monsen        result.description.length(),
4413f50c38dc070f4bb515c1b64450dae14f316474eKristian Monsen        ACMatchClassification::DIM,
4423f50c38dc070f4bb515c1b64450dae14f316474eKristian Monsen        &result.description_class);
443c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  }
444c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
445c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  return result;
446c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}
447c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
448c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochvoid KeywordProvider::Observe(NotificationType type,
449c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch                              const NotificationSource& source,
450c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch                              const NotificationDetails& details) {
451201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch  TemplateURLModel* model = profile_ ? profile_->GetTemplateURLModel() : model_;
452201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch  const AutocompleteInput& input = extension_suggest_last_input_;
453c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
454201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch  switch (type.value) {
455201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch    case NotificationType::EXTENSION_OMNIBOX_INPUT_ENTERED:
456201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch      // Input has been accepted, so we're done with this input session. Ensure
457201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch      // we don't send the OnInputCancelled event.
458201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch      current_keyword_extension_id_.clear();
459201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch      return;
460201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch
461201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch    case NotificationType::EXTENSION_OMNIBOX_DEFAULT_SUGGESTION_CHANGED: {
462201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch      // It's possible to change the default suggestion while not in an editing
463201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch      // session.
46472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      string16 keyword, remaining_input;
465201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch      if (matches_.empty() || current_keyword_extension_id_.empty() ||
466201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch          !ExtractKeywordFromInput(input, &keyword, &remaining_input))
467201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch        return;
468201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch
46972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      const TemplateURL* template_url(
47072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen          model->GetTemplateURLForKeyword(keyword));
471201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch      ApplyDefaultSuggestionForExtensionKeyword(profile_, template_url,
47272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen                                                remaining_input,
473201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch                                                &matches_[0]);
474201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch      listener_->OnProviderUpdate(true);
475201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch      return;
476201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch    }
477c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
478201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch    case NotificationType::EXTENSION_OMNIBOX_SUGGESTIONS_READY: {
479201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch      const ExtensionOmniboxSuggestions& suggestions =
480201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch        *Details<ExtensionOmniboxSuggestions>(details).ptr();
481201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch      if (suggestions.request_id != current_input_id_)
482201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch        return;  // This is an old result. Just ignore.
483c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
48472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      string16 keyword, remaining_input;
485201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch      if (!ExtractKeywordFromInput(input, &keyword, &remaining_input)) {
486201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch        NOTREACHED();
487201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch        return;
488201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch      }
489c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
490201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch      // TODO(mpcomplete): consider clamping the number of suggestions to
491201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch      // AutocompleteProvider::kMaxMatches.
492201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch      for (size_t i = 0; i < suggestions.suggestions.size(); ++i) {
493201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch        const ExtensionOmniboxSuggestion& suggestion =
494201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch            suggestions.suggestions[i];
495201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch        // We want to order these suggestions in descending order, so start with
496201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch        // the relevance of the first result (added synchronously in Start()),
497201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch        // and subtract 1 for each subsequent suggestion from the extension.
498201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch        // We know that |complete| is true, because we wouldn't get results from
499201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch        // the extension unless the full keyword had been typed.
50072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen        int first_relevance = CalculateRelevance(input.type(), true, true,
501201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch            input.prefer_keyword(), input.allow_exact_keyword_match());
502201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch        extension_suggest_matches_.push_back(CreateAutocompleteMatch(
503201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch            model, keyword, input, keyword.length(),
50472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen            suggestion.content, first_relevance - (i + 1)));
505201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch
506201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch        AutocompleteMatch* match = &extension_suggest_matches_.back();
50772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen        match->contents.assign(suggestion.description);
508201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch        match->contents_class = suggestion.description_styles;
509201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch        match->description.clear();
510201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch        match->description_class.clear();
511201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch      }
512c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
513201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch      done_ = true;
514201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch      matches_.insert(matches_.end(), extension_suggest_matches_.begin(),
515201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch                      extension_suggest_matches_.end());
516201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch      listener_->OnProviderUpdate(!extension_suggest_matches_.empty());
517201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch      return;
518201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch    }
519201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch
520201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch    default:
521201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch      NOTREACHED();
522201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch      return;
523201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch  }
524c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}
525c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
526c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochvoid KeywordProvider::EnterExtensionKeywordMode(
527c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    const std::string& extension_id) {
528c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  DCHECK(current_keyword_extension_id_.empty());
529c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  current_keyword_extension_id_ = extension_id;
530c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
531c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  ExtensionOmniboxEventRouter::OnInputStarted(
532c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      profile_, current_keyword_extension_id_);
533c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}
534c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
535c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochvoid KeywordProvider::MaybeEndExtensionKeywordMode() {
536c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  if (!current_keyword_extension_id_.empty()) {
537c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    ExtensionOmniboxEventRouter::OnInputCancelled(
538c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        profile_, current_keyword_extension_id_);
539c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
540c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    current_keyword_extension_id_.clear();
541c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  }
542c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}
543