Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(243)

Side by Side Diff: chrome/browser/autocomplete/keyword_provider.cc

Issue 2078021: First pass at experimental omnibox API. There are plenty of rough edges and (Closed)
Patch Set: no prefer_keyword Created 10 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "chrome/browser/autocomplete/keyword_provider.h" 5 #include "chrome/browser/autocomplete/keyword_provider.h"
6 6
7 #include <algorithm> 7 #include <algorithm>
8 #include <vector> 8 #include <vector>
9 9
10 #include "app/l10n_util.h" 10 #include "app/l10n_util.h"
11 #include "base/string16.h"
11 #include "base/utf_string_conversions.h" 12 #include "base/utf_string_conversions.h"
13 #include "chrome/browser/extensions/extension_omnibox_api.h"
12 #include "chrome/browser/profile.h" 14 #include "chrome/browser/profile.h"
13 #include "chrome/browser/search_engines/template_url.h" 15 #include "chrome/browser/search_engines/template_url.h"
14 #include "chrome/browser/search_engines/template_url_model.h" 16 #include "chrome/browser/search_engines/template_url_model.h"
17 #include "chrome/common/notification_service.h"
15 #include "grit/generated_resources.h" 18 #include "grit/generated_resources.h"
16 #include "net/base/escape.h" 19 #include "net/base/escape.h"
17 #include "net/base/net_util.h" 20 #include "net/base/net_util.h"
18 21
19 // static 22 // static
20 std::wstring KeywordProvider::SplitReplacementStringFromInput( 23 std::wstring KeywordProvider::SplitReplacementStringFromInput(
21 const std::wstring& input) { 24 const std::wstring& input) {
22 // The input may contain leading whitespace, strip it. 25 // The input may contain leading whitespace, strip it.
23 std::wstring trimmed_input; 26 std::wstring trimmed_input;
24 TrimWhitespace(input, TRIM_LEADING, &trimmed_input); 27 TrimWhitespace(input, TRIM_LEADING, &trimmed_input);
25 28
26 // And extract the replacement string. 29 // And extract the replacement string.
27 std::wstring remaining_input; 30 std::wstring remaining_input;
28 SplitKeywordFromInput(trimmed_input, &remaining_input); 31 SplitKeywordFromInput(trimmed_input, &remaining_input);
29 return remaining_input; 32 return remaining_input;
30 } 33 }
31 34
32 KeywordProvider::KeywordProvider(ACProviderListener* listener, Profile* profile) 35 KeywordProvider::KeywordProvider(ACProviderListener* listener, Profile* profile)
33 : AutocompleteProvider(listener, profile, "Keyword"), 36 : AutocompleteProvider(listener, profile, "Keyword"),
34 model_(NULL) { 37 model_(NULL),
38 current_input_id_(0) {
39 registrar_.Add(this, NotificationType::EXTENSION_OMNIBOX_SUGGESTIONS_READY,
40 Source<Profile>(profile));
35 } 41 }
36 42
37 KeywordProvider::KeywordProvider(ACProviderListener* listener, 43 KeywordProvider::KeywordProvider(ACProviderListener* listener,
38 TemplateURLModel* model) 44 TemplateURLModel* model)
39 : AutocompleteProvider(listener, NULL, "Keyword"), 45 : AutocompleteProvider(listener, NULL, "Keyword"),
40 model_(model) { 46 model_(model),
47 current_input_id_(0) {
41 } 48 }
42 49
43 50
44 namespace { 51 namespace {
45 52
46 // Helper functor for Start(), for sorting keyword matches by quality. 53 // Helper functor for Start(), for sorting keyword matches by quality.
47 class CompareQuality { 54 class CompareQuality {
48 public: 55 public:
49 // A keyword is of higher quality when a greater fraction of it has been 56 // A keyword is of higher quality when a greater fraction of it has been
50 // typed, that is, when it is shorter. 57 // typed, that is, when it is shorter.
(...skipping 26 matching lines...) Expand all
77 model->Load(); 84 model->Load();
78 85
79 const TemplateURL* template_url = model->GetTemplateURLForKeyword(keyword); 86 const TemplateURL* template_url = model->GetTemplateURLForKeyword(keyword);
80 return TemplateURL::SupportsReplacement(template_url) ? template_url : NULL; 87 return TemplateURL::SupportsReplacement(template_url) ? template_url : NULL;
81 } 88 }
82 89
83 void KeywordProvider::Start(const AutocompleteInput& input, 90 void KeywordProvider::Start(const AutocompleteInput& input,
84 bool minimal_changes) { 91 bool minimal_changes) {
85 matches_.clear(); 92 matches_.clear();
86 93
94 if (!minimal_changes) {
95 done_ = true;
96
97 // Input has changed. Increment the input ID so that we can discard any
98 // stale extension suggestions that may be incoming.
99 ++current_input_id_;
100 }
101
87 // Split user input into a keyword and some query input. 102 // Split user input into a keyword and some query input.
88 // 103 //
89 // We want to suggest keywords even when users have started typing URLs, on 104 // We want to suggest keywords even when users have started typing URLs, on
90 // the assumption that they might not realize they no longer need to go to a 105 // the assumption that they might not realize they no longer need to go to a
91 // site to be able to search it. So we call CleanUserInputKeyword() to strip 106 // site to be able to search it. So we call CleanUserInputKeyword() to strip
92 // any initial scheme and/or "www.". NOTE: Any heuristics or UI used to 107 // any initial scheme and/or "www.". NOTE: Any heuristics or UI used to
93 // automatically/manually create keywords will need to be in sync with 108 // automatically/manually create keywords will need to be in sync with
94 // whatever we do here! 109 // whatever we do here!
95 // 110 //
96 // TODO(pkasting): http://b/1112681 If someday we remember usage frequency for 111 // TODO(pkasting): http://b/1112681 If someday we remember usage frequency for
(...skipping 27 matching lines...) Expand all
124 return; 139 return;
125 std::sort(keyword_matches.begin(), keyword_matches.end(), CompareQuality()); 140 std::sort(keyword_matches.begin(), keyword_matches.end(), CompareQuality());
126 141
127 // Limit to one exact or three inexact matches, and mark them up for display 142 // Limit to one exact or three inexact matches, and mark them up for display
128 // in the autocomplete popup. 143 // in the autocomplete popup.
129 // Any exact match is going to be the highest quality match, and thus at the 144 // Any exact match is going to be the highest quality match, and thus at the
130 // front of our vector. 145 // front of our vector.
131 if (keyword_matches.front() == keyword) { 146 if (keyword_matches.front() == keyword) {
132 matches_.push_back(CreateAutocompleteMatch(model, keyword, input, 147 matches_.push_back(CreateAutocompleteMatch(model, keyword, input,
133 keyword.length(), 148 keyword.length(),
134 remaining_input)); 149 remaining_input, -1));
150
151 const TemplateURL* template_url(model->GetTemplateURLForKeyword(keyword));
152 if (profile_ &&
153 !input.synchronous_only() && template_url->IsExtensionKeyword()) {
154 if (minimal_changes) {
155 // If the input hasn't significantly changed, we can just use the
156 // suggestions from last time. We need to readjust the relevance to
157 // ensure it is less than the main match's relevance.
158 for (size_t i = 0; i < extension_suggest_matches_.size(); ++i) {
159 matches_.push_back(extension_suggest_matches_[i]);
160 matches_.back().relevance = matches_[0].relevance - (i + 1);
161 }
162 } else {
163 extension_suggest_last_input_ = input;
164 extension_suggest_matches_.clear();
165
166 bool have_listeners = ExtensionOmniboxEventRouter::OnInputChanged(
167 profile_, template_url->GetExtensionId(),
168 WideToUTF8(remaining_input), current_input_id_);
169
170 // We only have to wait for suggest results if there are actually
171 // extensions listening for input changes.
172 if (have_listeners)
173 done_ = false;
174 }
175 }
135 } else { 176 } else {
136 if (keyword_matches.size() > kMaxMatches) { 177 if (keyword_matches.size() > kMaxMatches) {
137 keyword_matches.erase(keyword_matches.begin() + kMaxMatches, 178 keyword_matches.erase(keyword_matches.begin() + kMaxMatches,
138 keyword_matches.end()); 179 keyword_matches.end());
139 } 180 }
140 for (std::vector<std::wstring>::const_iterator i(keyword_matches.begin()); 181 for (std::vector<std::wstring>::const_iterator i(keyword_matches.begin());
141 i != keyword_matches.end(); ++i) { 182 i != keyword_matches.end(); ++i) {
142 matches_.push_back(CreateAutocompleteMatch(model, *i, input, 183 matches_.push_back(CreateAutocompleteMatch(model, *i, input,
143 keyword.length(), 184 keyword.length(),
144 remaining_input)); 185 remaining_input, -1));
145 } 186 }
146 } 187 }
147 } 188 }
148 189
149 // static 190 // static
150 bool KeywordProvider::ExtractKeywordFromInput(const AutocompleteInput& input, 191 bool KeywordProvider::ExtractKeywordFromInput(const AutocompleteInput& input,
151 std::wstring* keyword, 192 std::wstring* keyword,
152 std::wstring* remaining_input) { 193 std::wstring* remaining_input) {
153 if ((input.type() == AutocompleteInput::INVALID) || 194 if ((input.type() == AutocompleteInput::INVALID) ||
154 (input.type() == AutocompleteInput::FORCED_QUERY)) 195 (input.type() == AutocompleteInput::FORCED_QUERY))
(...skipping 27 matching lines...) Expand all
182 } 223 }
183 224
184 // static 225 // static
185 void KeywordProvider::FillInURLAndContents( 226 void KeywordProvider::FillInURLAndContents(
186 const std::wstring& remaining_input, 227 const std::wstring& remaining_input,
187 const TemplateURL* element, 228 const TemplateURL* element,
188 AutocompleteMatch* match) { 229 AutocompleteMatch* match) {
189 DCHECK(!element->short_name().empty()); 230 DCHECK(!element->short_name().empty());
190 DCHECK(element->url()); 231 DCHECK(element->url());
191 DCHECK(element->url()->IsValid()); 232 DCHECK(element->url()->IsValid());
233 int message_id = element->IsExtensionKeyword() ?
234 IDS_EXTENSION_KEYWORD_COMMAND : IDS_KEYWORD_SEARCH;
192 if (remaining_input.empty()) { 235 if (remaining_input.empty()) {
193 if (element->url()->SupportsReplacement()) { 236 if (element->url()->SupportsReplacement()) {
194 // No query input; return a generic, no-destination placeholder. 237 // No query input; return a generic, no-destination placeholder.
195 match->contents.assign(l10n_util::GetStringF(IDS_KEYWORD_SEARCH, 238 match->contents.assign(l10n_util::GetStringF(message_id,
196 element->AdjustedShortNameForLocaleDirection(), 239 element->AdjustedShortNameForLocaleDirection(),
197 l10n_util::GetString(IDS_EMPTY_KEYWORD_VALUE))); 240 l10n_util::GetString(IDS_EMPTY_KEYWORD_VALUE)));
198 match->contents_class.push_back( 241 match->contents_class.push_back(
199 ACMatchClassification(0, ACMatchClassification::DIM)); 242 ACMatchClassification(0, ACMatchClassification::DIM));
200 } else { 243 } else {
201 // Keyword that has no replacement text (aka a shorthand for a URL). 244 // Keyword that has no replacement text (aka a shorthand for a URL).
202 match->destination_url = GURL(WideToUTF8(element->url()->url())); 245 match->destination_url = GURL(WideToUTF8(element->url()->url()));
203 match->contents.assign(element->short_name()); 246 match->contents.assign(element->short_name());
204 AutocompleteMatch::ClassifyLocationInString(0, match->contents.length(), 247 AutocompleteMatch::ClassifyLocationInString(0, match->contents.length(),
205 match->contents.length(), ACMatchClassification::NONE, 248 match->contents.length(), ACMatchClassification::NONE,
206 &match->contents_class); 249 &match->contents_class);
207 } 250 }
208 } else { 251 } else {
209 // Create destination URL by escaping user input and substituting into 252 // Create destination URL by escaping user input and substituting into
210 // keyword template URL. The escaping here handles whitespace in user 253 // keyword template URL. The escaping here handles whitespace in user
211 // input, but we rely on later canonicalization functions to do more 254 // input, but we rely on later canonicalization functions to do more
212 // fixup to make the URL valid if necessary. 255 // fixup to make the URL valid if necessary.
213 DCHECK(element->url()->SupportsReplacement()); 256 DCHECK(element->url()->SupportsReplacement());
214 match->destination_url = GURL(WideToUTF8(element->url()->ReplaceSearchTerms( 257 match->destination_url = GURL(WideToUTF8(element->url()->ReplaceSearchTerms(
215 *element, remaining_input, TemplateURLRef::NO_SUGGESTIONS_AVAILABLE, 258 *element, remaining_input, TemplateURLRef::NO_SUGGESTIONS_AVAILABLE,
216 std::wstring()))); 259 std::wstring())));
217 std::vector<size_t> content_param_offsets; 260 std::vector<size_t> content_param_offsets;
218 match->contents.assign(l10n_util::GetStringF(IDS_KEYWORD_SEARCH, 261 match->contents.assign(l10n_util::GetStringF(message_id,
219 element->short_name(), 262 element->short_name(),
220 remaining_input, 263 remaining_input,
221 &content_param_offsets)); 264 &content_param_offsets));
222 if (content_param_offsets.size() == 2) { 265 if (content_param_offsets.size() == 2) {
223 AutocompleteMatch::ClassifyLocationInString(content_param_offsets[1], 266 AutocompleteMatch::ClassifyLocationInString(content_param_offsets[1],
224 remaining_input.length(), match->contents.length(), 267 remaining_input.length(), match->contents.length(),
225 ACMatchClassification::NONE, &match->contents_class); 268 ACMatchClassification::NONE, &match->contents_class);
226 } else { 269 } else {
227 // See comments on an identical NOTREACHED() in search_provider.cc. 270 // See comments on an identical NOTREACHED() in search_provider.cc.
228 NOTREACHED(); 271 NOTREACHED();
(...skipping 10 matching lines...) Expand all
239 if (no_query_text_needed) 282 if (no_query_text_needed)
240 return 1500; 283 return 1500;
241 return (type == AutocompleteInput::QUERY) ? 1450 : 1100; 284 return (type == AutocompleteInput::QUERY) ? 1450 : 1100;
242 } 285 }
243 286
244 AutocompleteMatch KeywordProvider::CreateAutocompleteMatch( 287 AutocompleteMatch KeywordProvider::CreateAutocompleteMatch(
245 TemplateURLModel* model, 288 TemplateURLModel* model,
246 const std::wstring keyword, 289 const std::wstring keyword,
247 const AutocompleteInput& input, 290 const AutocompleteInput& input,
248 size_t prefix_length, 291 size_t prefix_length,
249 const std::wstring& remaining_input) { 292 const std::wstring& remaining_input,
293 int relevance) {
250 DCHECK(model); 294 DCHECK(model);
251 // Get keyword data from data store. 295 // Get keyword data from data store.
252 const TemplateURL* element(model->GetTemplateURLForKeyword(keyword)); 296 const TemplateURL* element(model->GetTemplateURLForKeyword(keyword));
253 DCHECK(element && element->url()); 297 DCHECK(element && element->url());
254 const bool supports_replacement = element->url()->SupportsReplacement(); 298 const bool supports_replacement = element->url()->SupportsReplacement();
255 299
256 // Create an edit entry of "[keyword] [remaining input]". This is helpful 300 // Create an edit entry of "[keyword] [remaining input]". This is helpful
257 // even when [remaining input] is empty, as the user can select the popup 301 // even when [remaining input] is empty, as the user can select the popup
258 // choice and immediately begin typing in query input. 302 // choice and immediately begin typing in query input.
259 const bool keyword_complete = (prefix_length == keyword.length()); 303 const bool keyword_complete = (prefix_length == keyword.length());
260 AutocompleteMatch result(this, 304 if (relevance < 0) {
261 CalculateRelevance(input.type(), keyword_complete, 305 relevance =
262 // When the user wants keyword matches to take 306 CalculateRelevance(input.type(), keyword_complete,
263 // preference, score them highly regardless of whether 307 // When the user wants keyword matches to take
264 // the input provides query text. 308 // preference, score them highly regardless of
265 input.prefer_keyword() || !supports_replacement), 309 // whether the input provides query text.
266 false, supports_replacement ? AutocompleteMatch::SEARCH_OTHER_ENGINE : 310 input.prefer_keyword() || !supports_replacement);
267 AutocompleteMatch::HISTORY_KEYWORD); 311 }
312 AutocompleteMatch result(this, relevance, false,
313 supports_replacement ? AutocompleteMatch::SEARCH_OTHER_ENGINE :
314 AutocompleteMatch::HISTORY_KEYWORD);
268 result.fill_into_edit.assign(keyword); 315 result.fill_into_edit.assign(keyword);
269 if (!remaining_input.empty() || !keyword_complete || supports_replacement) 316 if (!remaining_input.empty() || !keyword_complete || supports_replacement)
270 result.fill_into_edit.push_back(L' '); 317 result.fill_into_edit.push_back(L' ');
271 result.fill_into_edit.append(remaining_input); 318 result.fill_into_edit.append(remaining_input);
272 if (!input.prevent_inline_autocomplete() && 319 if (!input.prevent_inline_autocomplete() &&
273 (keyword_complete || remaining_input.empty())) 320 (keyword_complete || remaining_input.empty()))
274 result.inline_autocomplete_offset = input.text().length(); 321 result.inline_autocomplete_offset = input.text().length();
275 322
276 // Create destination URL and popup entry content by substituting user input 323 // Create destination URL and popup entry content by substituting user input
277 // into keyword templates. 324 // into keyword templates.
278 FillInURLAndContents(remaining_input, element, &result); 325 FillInURLAndContents(remaining_input, element, &result);
279 326
280 // Create popup entry description based on the keyword name. 327 // Create popup entry description based on the keyword name.
281 result.description.assign(l10n_util::GetStringF( 328 int message_id = element->IsExtensionKeyword() ?
282 IDS_AUTOCOMPLETE_KEYWORD_DESCRIPTION, keyword)); 329 IDS_AUTOCOMPLETE_EXTENSION_KEYWORD_DESCRIPTION :
330 IDS_AUTOCOMPLETE_KEYWORD_DESCRIPTION;
331 result.description.assign(l10n_util::GetStringF(message_id, keyword));
283 if (supports_replacement) 332 if (supports_replacement)
284 result.template_url = element; 333 result.template_url = element;
285 static const std::wstring kKeywordDesc(l10n_util::GetString( 334 static const std::wstring kKeywordDesc(l10n_util::GetString(message_id));
286 IDS_AUTOCOMPLETE_KEYWORD_DESCRIPTION));
287 AutocompleteMatch::ClassifyLocationInString(kKeywordDesc.find(L"%s"), 335 AutocompleteMatch::ClassifyLocationInString(kKeywordDesc.find(L"%s"),
288 prefix_length, 336 prefix_length,
289 result.description.length(), 337 result.description.length(),
290 ACMatchClassification::DIM, 338 ACMatchClassification::DIM,
291 &result.description_class); 339 &result.description_class);
292 340
293 result.transition = PageTransition::KEYWORD; 341 result.transition = PageTransition::KEYWORD;
294 342
295 return result; 343 return result;
296 } 344 }
345
346 void KeywordProvider::Observe(NotificationType type,
347 const NotificationSource& source,
348 const NotificationDetails& details) {
349 // TODO(mpcomplete): consider clamping the number of suggestions to
350 // AutocompleteProvider::kMaxMatches.
351 DCHECK(type == NotificationType::EXTENSION_OMNIBOX_SUGGESTIONS_READY);
352
353 int suggest_id = Details<ExtensionOmniboxSuggestions>(details).ptr()->first;
354 if (suggest_id != current_input_id_)
355 return; // This is an old result. Just ignore.
356
357 const AutocompleteInput& input = extension_suggest_last_input_;
358 std::wstring keyword, remaining_input;
359 if (!ExtractKeywordFromInput(input, &keyword, &remaining_input)) {
360 NOTREACHED();
361 return;
362 }
363
364 TemplateURLModel* model =
365 profile_ ? profile_->GetTemplateURLModel() : model_;
366
367 ListValue* suggestions =
368 Details<ExtensionOmniboxSuggestions>(details).ptr()->second;
369 for (size_t i = 0; i < suggestions->GetSize(); ++i) {
370 DictionaryValue* suggestion;
371 string16 content, description;
372 if (!suggestions->GetDictionary(i, &suggestion) ||
373 !suggestion->GetString("content", &content) ||
374 !suggestion->GetString("description", &description))
375 break;
376
377 // We want to order these suggestions in descending order, so start with
378 // the relevance of the first result (added synchronously in Start()),
379 // and subtract 1 for each subsequent suggestion from the extension.
380 // We know that |complete| is true, because we wouldn't get results from
381 // the extension unless the full keyword had been typed.
382 int first_relevance =
383 CalculateRelevance(input.type(), true, input.prefer_keyword());
384 extension_suggest_matches_.push_back(CreateAutocompleteMatch(
385 model, keyword, input, keyword.length(), UTF16ToWide(content),
386 first_relevance - (i + 1)));
387
388 if (!description.empty()) {
389 AutocompleteMatch* match = &extension_suggest_matches_.back();
390 std::vector<size_t> offsets;
391 match->contents.assign(l10n_util::GetStringF(
392 IDS_AUTOCOMPLETE_EXTENSION_KEYWORD_CONTENT,
393 match->contents, UTF16ToWide(description), &offsets));
394 CHECK_EQ(2U, offsets.size()) <<
395 "Expected 2 params for IDS_AUTOCOMPLETE_EXTENSION_KEYWORD_CONTENT";
396 match->contents_class.push_back(
397 ACMatchClassification(offsets[1], ACMatchClassification::NONE));
398 }
399 }
400
401 done_ = true;
402 matches_.insert(matches_.end(), extension_suggest_matches_.begin(),
403 extension_suggest_matches_.end());
404 listener_->OnProviderUpdate(!extension_suggest_matches_.empty());
405 }
OLDNEW
« no previous file with comments | « chrome/browser/autocomplete/keyword_provider.h ('k') | chrome/browser/cocoa/location_bar_view_mac.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698