OLD | NEW |
1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 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/string16.h" |
(...skipping 72 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
84 // Helper functor for Start(), for sorting keyword matches by quality. | 84 // Helper functor for Start(), for sorting keyword matches by quality. |
85 class CompareQuality { | 85 class CompareQuality { |
86 public: | 86 public: |
87 // A keyword is of higher quality when a greater fraction of it has been | 87 // A keyword is of higher quality when a greater fraction of it has been |
88 // typed, that is, when it is shorter. | 88 // typed, that is, when it is shorter. |
89 // | 89 // |
90 // TODO(pkasting): http://b/740691 Most recent and most frequent keywords are | 90 // TODO(pkasting): http://b/740691 Most recent and most frequent keywords are |
91 // probably better rankings than the fraction of the keyword typed. We should | 91 // probably better rankings than the fraction of the keyword typed. We should |
92 // always put any exact matches first no matter what, since the code in | 92 // always put any exact matches first no matter what, since the code in |
93 // Start() assumes this (and it makes sense). | 93 // Start() assumes this (and it makes sense). |
94 bool operator()(const std::wstring& keyword1, | 94 bool operator()(const string16& keyword1, |
95 const std::wstring& keyword2) const { | 95 const string16& keyword2) const { |
96 return keyword1.length() < keyword2.length(); | 96 return keyword1.length() < keyword2.length(); |
97 } | 97 } |
98 }; | 98 }; |
99 | 99 |
100 // We need our input IDs to be unique across all profiles, so we keep a global | 100 // We need our input IDs to be unique across all profiles, so we keep a global |
101 // UID that each provider uses. | 101 // UID that each provider uses. |
102 static int global_input_uid_; | 102 static int global_input_uid_; |
103 | 103 |
104 } // namespace | 104 } // namespace |
105 | 105 |
106 // static | 106 // static |
107 const TemplateURL* KeywordProvider::GetSubstitutingTemplateURLForInput( | 107 const TemplateURL* KeywordProvider::GetSubstitutingTemplateURLForInput( |
108 Profile* profile, | 108 Profile* profile, |
109 const AutocompleteInput& input, | 109 const AutocompleteInput& input, |
110 std::wstring* remaining_input) { | 110 std::wstring* remaining_input) { |
111 if (!input.allow_exact_keyword_match()) | 111 if (!input.allow_exact_keyword_match()) |
112 return NULL; | 112 return NULL; |
113 | 113 |
114 std::wstring keyword; | 114 std::wstring keyword; |
115 if (!ExtractKeywordFromInput(input, &keyword, remaining_input)) | 115 if (!ExtractKeywordFromInput(input, &keyword, remaining_input)) |
116 return NULL; | 116 return NULL; |
117 | 117 |
118 // Make sure the model is loaded. This is cheap and quickly bails out if | 118 // Make sure the model is loaded. This is cheap and quickly bails out if |
119 // the model is already loaded. | 119 // the model is already loaded. |
120 TemplateURLModel* model = profile->GetTemplateURLModel(); | 120 TemplateURLModel* model = profile->GetTemplateURLModel(); |
121 DCHECK(model); | 121 DCHECK(model); |
122 model->Load(); | 122 model->Load(); |
123 | 123 |
124 const TemplateURL* template_url = model->GetTemplateURLForKeyword(keyword); | 124 const TemplateURL* template_url = |
| 125 model->GetTemplateURLForKeyword(WideToUTF16Hack(keyword)); |
125 return TemplateURL::SupportsReplacement(template_url) ? template_url : NULL; | 126 return TemplateURL::SupportsReplacement(template_url) ? template_url : NULL; |
126 } | 127 } |
127 | 128 |
128 void KeywordProvider::Start(const AutocompleteInput& input, | 129 void KeywordProvider::Start(const AutocompleteInput& input, |
129 bool minimal_changes) { | 130 bool minimal_changes) { |
130 // This object ensures we end keyword mode if we exit the function without | 131 // This object ensures we end keyword mode if we exit the function without |
131 // toggling keyword mode to on. | 132 // toggling keyword mode to on. |
132 ScopedEndExtensionKeywordMode keyword_mode_toggle(this); | 133 ScopedEndExtensionKeywordMode keyword_mode_toggle(this); |
133 | 134 |
134 matches_.clear(); | 135 matches_.clear(); |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
167 // Get the best matches for this keyword. | 168 // Get the best matches for this keyword. |
168 // | 169 // |
169 // NOTE: We could cache the previous keywords and reuse them here in the | 170 // NOTE: We could cache the previous keywords and reuse them here in the |
170 // |minimal_changes| case, but since we'd still have to recalculate their | 171 // |minimal_changes| case, but since we'd still have to recalculate their |
171 // relevances and we can just recreate the results synchronously anyway, we | 172 // relevances and we can just recreate the results synchronously anyway, we |
172 // don't bother. | 173 // don't bother. |
173 // | 174 // |
174 // TODO(pkasting): http://b/893701 We should remember the user's use of a | 175 // TODO(pkasting): http://b/893701 We should remember the user's use of a |
175 // search query both from the autocomplete popup and from web pages | 176 // search query both from the autocomplete popup and from web pages |
176 // themselves. | 177 // themselves. |
177 std::vector<std::wstring> keyword_matches; | 178 std::vector<string16> keyword_matches; |
178 model->FindMatchingKeywords(keyword, !remaining_input.empty(), | 179 model->FindMatchingKeywords(WideToUTF16Hack(keyword), |
| 180 !remaining_input.empty(), |
179 &keyword_matches); | 181 &keyword_matches); |
180 | 182 |
181 // Prune any extension keywords that are disallowed in incognito mode (if | 183 // Prune any extension keywords that are disallowed in incognito mode (if |
182 // we're incognito), or disabled. | 184 // we're incognito), or disabled. |
183 for (std::vector<std::wstring>::iterator i(keyword_matches.begin()); | 185 for (std::vector<string16>::iterator i(keyword_matches.begin()); |
184 i != keyword_matches.end(); ) { | 186 i != keyword_matches.end(); ) { |
185 const TemplateURL* template_url(model->GetTemplateURLForKeyword(*i)); | 187 const TemplateURL* template_url(model->GetTemplateURLForKeyword(*i)); |
186 if (profile_ && | 188 if (profile_ && |
187 !input.synchronous_only() && template_url->IsExtensionKeyword()) { | 189 !input.synchronous_only() && template_url->IsExtensionKeyword()) { |
188 ExtensionService* service = profile_->GetExtensionService(); | 190 ExtensionService* service = profile_->GetExtensionService(); |
189 const Extension* extension = service->GetExtensionById( | 191 const Extension* extension = service->GetExtensionById( |
190 template_url->GetExtensionId(), false); | 192 template_url->GetExtensionId(), false); |
191 bool enabled = extension && (!profile_->IsOffTheRecord() || | 193 bool enabled = extension && (!profile_->IsOffTheRecord() || |
192 service->IsIncognitoEnabled(extension)); | 194 service->IsIncognitoEnabled(extension)); |
193 if (!enabled) { | 195 if (!enabled) { |
194 i = keyword_matches.erase(i); | 196 i = keyword_matches.erase(i); |
195 continue; | 197 continue; |
196 } | 198 } |
197 } | 199 } |
198 ++i; | 200 ++i; |
199 } | 201 } |
200 if (keyword_matches.empty()) | 202 if (keyword_matches.empty()) |
201 return; | 203 return; |
202 std::sort(keyword_matches.begin(), keyword_matches.end(), CompareQuality()); | 204 std::sort(keyword_matches.begin(), keyword_matches.end(), CompareQuality()); |
203 | 205 |
204 // Limit to one exact or three inexact matches, and mark them up for display | 206 // Limit to one exact or three inexact matches, and mark them up for display |
205 // in the autocomplete popup. | 207 // in the autocomplete popup. |
206 // Any exact match is going to be the highest quality match, and thus at the | 208 // Any exact match is going to be the highest quality match, and thus at the |
207 // front of our vector. | 209 // front of our vector. |
208 if (keyword_matches.front() == keyword) { | 210 if (keyword_matches.front() == WideToUTF16Hack(keyword)) { |
209 const TemplateURL* template_url(model->GetTemplateURLForKeyword(keyword)); | 211 const TemplateURL* template_url( |
| 212 model->GetTemplateURLForKeyword(WideToUTF16Hack(keyword))); |
210 // TODO(pkasting): We should probably check that if the user explicitly | 213 // TODO(pkasting): We should probably check that if the user explicitly |
211 // typed a scheme, that scheme matches the one in |template_url|. | 214 // typed a scheme, that scheme matches the one in |template_url|. |
212 matches_.push_back(CreateAutocompleteMatch(model, keyword, input, | 215 matches_.push_back(CreateAutocompleteMatch(model, keyword, input, |
213 keyword.length(), | 216 keyword.length(), |
214 remaining_input, -1)); | 217 remaining_input, -1)); |
215 | 218 |
216 if (profile_ && | 219 if (profile_ && |
217 !input.synchronous_only() && template_url->IsExtensionKeyword()) { | 220 !input.synchronous_only() && template_url->IsExtensionKeyword()) { |
218 if (template_url->GetExtensionId() != current_keyword_extension_id_) | 221 if (template_url->GetExtensionId() != current_keyword_extension_id_) |
219 MaybeEndExtensionKeywordMode(); | 222 MaybeEndExtensionKeywordMode(); |
(...skipping 25 matching lines...) Expand all Loading... |
245 // extensions listening for input changes. | 248 // extensions listening for input changes. |
246 if (have_listeners) | 249 if (have_listeners) |
247 done_ = false; | 250 done_ = false; |
248 } | 251 } |
249 } | 252 } |
250 } else { | 253 } else { |
251 if (keyword_matches.size() > kMaxMatches) { | 254 if (keyword_matches.size() > kMaxMatches) { |
252 keyword_matches.erase(keyword_matches.begin() + kMaxMatches, | 255 keyword_matches.erase(keyword_matches.begin() + kMaxMatches, |
253 keyword_matches.end()); | 256 keyword_matches.end()); |
254 } | 257 } |
255 for (std::vector<std::wstring>::const_iterator i(keyword_matches.begin()); | 258 for (std::vector<string16>::const_iterator i(keyword_matches.begin()); |
256 i != keyword_matches.end(); ++i) { | 259 i != keyword_matches.end(); ++i) { |
257 matches_.push_back(CreateAutocompleteMatch(model, *i, input, | 260 matches_.push_back(CreateAutocompleteMatch(model, UTF16ToWideHack(*i), |
258 keyword.length(), | 261 input, keyword.length(), |
259 remaining_input, -1)); | 262 remaining_input, -1)); |
260 } | 263 } |
261 } | 264 } |
262 } | 265 } |
263 | 266 |
264 void KeywordProvider::Stop() { | 267 void KeywordProvider::Stop() { |
265 done_ = true; | 268 done_ = true; |
266 MaybeEndExtensionKeywordMode(); | 269 MaybeEndExtensionKeywordMode(); |
267 } | 270 } |
268 | 271 |
269 KeywordProvider::~KeywordProvider() {} | 272 KeywordProvider::~KeywordProvider() {} |
270 | 273 |
271 // static | 274 // static |
272 bool KeywordProvider::ExtractKeywordFromInput(const AutocompleteInput& input, | 275 bool KeywordProvider::ExtractKeywordFromInput(const AutocompleteInput& input, |
273 std::wstring* keyword, | 276 std::wstring* keyword, |
274 std::wstring* remaining_input) { | 277 std::wstring* remaining_input) { |
275 if ((input.type() == AutocompleteInput::INVALID) || | 278 if ((input.type() == AutocompleteInput::INVALID) || |
276 (input.type() == AutocompleteInput::FORCED_QUERY)) | 279 (input.type() == AutocompleteInput::FORCED_QUERY)) |
277 return false; | 280 return false; |
278 | 281 |
279 *keyword = TemplateURLModel::CleanUserInputKeyword( | 282 *keyword = |
280 SplitKeywordFromInput(input.text(), true, remaining_input)); | 283 UTF16ToWideHack(TemplateURLModel::CleanUserInputKeyword(WideToUTF16Hack( |
| 284 SplitKeywordFromInput(input.text(), true, remaining_input)))); |
281 return !keyword->empty(); | 285 return !keyword->empty(); |
282 } | 286 } |
283 | 287 |
284 // static | 288 // static |
285 std::wstring KeywordProvider::SplitKeywordFromInput( | 289 std::wstring KeywordProvider::SplitKeywordFromInput( |
286 const std::wstring& input, | 290 const std::wstring& input, |
287 bool trim_leading_whitespace, | 291 bool trim_leading_whitespace, |
288 std::wstring* remaining_input) { | 292 std::wstring* remaining_input) { |
289 // Find end of first token. The AutocompleteController has trimmed leading | 293 // Find end of first token. The AutocompleteController has trimmed leading |
290 // whitespace, so we need not skip over that. | 294 // whitespace, so we need not skip over that. |
(...skipping 26 matching lines...) Expand all Loading... |
317 IDS_EXTENSION_KEYWORD_COMMAND : IDS_KEYWORD_SEARCH; | 321 IDS_EXTENSION_KEYWORD_COMMAND : IDS_KEYWORD_SEARCH; |
318 if (remaining_input.empty()) { | 322 if (remaining_input.empty()) { |
319 // Allow extension keyword providers to accept empty string input. This is | 323 // Allow extension keyword providers to accept empty string input. This is |
320 // useful to allow extensions to do something in the case where no input is | 324 // useful to allow extensions to do something in the case where no input is |
321 // entered. | 325 // entered. |
322 if (element->url()->SupportsReplacement() && | 326 if (element->url()->SupportsReplacement() && |
323 !element->IsExtensionKeyword()) { | 327 !element->IsExtensionKeyword()) { |
324 // No query input; return a generic, no-destination placeholder. | 328 // No query input; return a generic, no-destination placeholder. |
325 match->contents.assign(UTF16ToWideHack( | 329 match->contents.assign(UTF16ToWideHack( |
326 l10n_util::GetStringFUTF16(message_id, | 330 l10n_util::GetStringFUTF16(message_id, |
327 WideToUTF16Hack(element->AdjustedShortNameForLocaleDirection()), | 331 element->AdjustedShortNameForLocaleDirection(), |
328 l10n_util::GetStringUTF16(IDS_EMPTY_KEYWORD_VALUE)))); | 332 l10n_util::GetStringUTF16(IDS_EMPTY_KEYWORD_VALUE)))); |
329 match->contents_class.push_back( | 333 match->contents_class.push_back( |
330 ACMatchClassification(0, ACMatchClassification::DIM)); | 334 ACMatchClassification(0, ACMatchClassification::DIM)); |
331 } else { | 335 } else { |
332 // Keyword that has no replacement text (aka a shorthand for a URL). | 336 // Keyword that has no replacement text (aka a shorthand for a URL). |
333 match->destination_url = GURL(element->url()->url()); | 337 match->destination_url = GURL(element->url()->url()); |
334 match->contents.assign(element->short_name()); | 338 match->contents.assign(UTF16ToWideHack(element->short_name())); |
335 AutocompleteMatch::ClassifyLocationInString(0, match->contents.length(), | 339 AutocompleteMatch::ClassifyLocationInString(0, match->contents.length(), |
336 match->contents.length(), ACMatchClassification::NONE, | 340 match->contents.length(), ACMatchClassification::NONE, |
337 &match->contents_class); | 341 &match->contents_class); |
338 } | 342 } |
339 } else { | 343 } else { |
340 // Create destination URL by escaping user input and substituting into | 344 // Create destination URL by escaping user input and substituting into |
341 // keyword template URL. The escaping here handles whitespace in user | 345 // keyword template URL. The escaping here handles whitespace in user |
342 // input, but we rely on later canonicalization functions to do more | 346 // input, but we rely on later canonicalization functions to do more |
343 // fixup to make the URL valid if necessary. | 347 // fixup to make the URL valid if necessary. |
344 DCHECK(element->url()->SupportsReplacement()); | 348 DCHECK(element->url()->SupportsReplacement()); |
345 match->destination_url = GURL(element->url()->ReplaceSearchTerms( | 349 match->destination_url = GURL(element->url()->ReplaceSearchTerms( |
346 *element, remaining_input, TemplateURLRef::NO_SUGGESTIONS_AVAILABLE, | 350 *element, WideToUTF16Hack(remaining_input), |
347 std::wstring())); | 351 TemplateURLRef::NO_SUGGESTIONS_AVAILABLE, string16())); |
348 std::vector<size_t> content_param_offsets; | 352 std::vector<size_t> content_param_offsets; |
349 match->contents.assign(UTF16ToWideHack( | 353 match->contents.assign(UTF16ToWideHack( |
350 l10n_util::GetStringFUTF16(message_id, | 354 l10n_util::GetStringFUTF16(message_id, |
351 WideToUTF16Hack(element->short_name()), | 355 element->short_name(), |
352 WideToUTF16Hack(remaining_input), | 356 WideToUTF16Hack(remaining_input), |
353 &content_param_offsets))); | 357 &content_param_offsets))); |
354 if (content_param_offsets.size() == 2) { | 358 if (content_param_offsets.size() == 2) { |
355 AutocompleteMatch::ClassifyLocationInString(content_param_offsets[1], | 359 AutocompleteMatch::ClassifyLocationInString(content_param_offsets[1], |
356 remaining_input.length(), match->contents.length(), | 360 remaining_input.length(), match->contents.length(), |
357 ACMatchClassification::NONE, &match->contents_class); | 361 ACMatchClassification::NONE, &match->contents_class); |
358 } else { | 362 } else { |
359 // See comments on an identical NOTREACHED() in search_provider.cc. | 363 // See comments on an identical NOTREACHED() in search_provider.cc. |
360 NOTREACHED(); | 364 NOTREACHED(); |
361 } | 365 } |
(...skipping 16 matching lines...) Expand all Loading... |
378 | 382 |
379 AutocompleteMatch KeywordProvider::CreateAutocompleteMatch( | 383 AutocompleteMatch KeywordProvider::CreateAutocompleteMatch( |
380 TemplateURLModel* model, | 384 TemplateURLModel* model, |
381 const std::wstring& keyword, | 385 const std::wstring& keyword, |
382 const AutocompleteInput& input, | 386 const AutocompleteInput& input, |
383 size_t prefix_length, | 387 size_t prefix_length, |
384 const std::wstring& remaining_input, | 388 const std::wstring& remaining_input, |
385 int relevance) { | 389 int relevance) { |
386 DCHECK(model); | 390 DCHECK(model); |
387 // Get keyword data from data store. | 391 // Get keyword data from data store. |
388 const TemplateURL* element(model->GetTemplateURLForKeyword(keyword)); | 392 const TemplateURL* element( |
| 393 model->GetTemplateURLForKeyword(WideToUTF16Hack(keyword))); |
389 DCHECK(element && element->url()); | 394 DCHECK(element && element->url()); |
390 const bool supports_replacement = element->url()->SupportsReplacement(); | 395 const bool supports_replacement = element->url()->SupportsReplacement(); |
391 | 396 |
392 // Create an edit entry of "[keyword] [remaining input]". This is helpful | 397 // Create an edit entry of "[keyword] [remaining input]". This is helpful |
393 // even when [remaining input] is empty, as the user can select the popup | 398 // even when [remaining input] is empty, as the user can select the popup |
394 // choice and immediately begin typing in query input. | 399 // choice and immediately begin typing in query input. |
395 const bool keyword_complete = (prefix_length == keyword.length()); | 400 const bool keyword_complete = (prefix_length == keyword.length()); |
396 if (relevance < 0) { | 401 if (relevance < 0) { |
397 relevance = | 402 relevance = |
398 CalculateRelevance(input.type(), keyword_complete, | 403 CalculateRelevance(input.type(), keyword_complete, |
(...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
455 return; | 460 return; |
456 | 461 |
457 case NotificationType::EXTENSION_OMNIBOX_DEFAULT_SUGGESTION_CHANGED: { | 462 case NotificationType::EXTENSION_OMNIBOX_DEFAULT_SUGGESTION_CHANGED: { |
458 // It's possible to change the default suggestion while not in an editing | 463 // It's possible to change the default suggestion while not in an editing |
459 // session. | 464 // session. |
460 std::wstring keyword, remaining_input; | 465 std::wstring keyword, remaining_input; |
461 if (matches_.empty() || current_keyword_extension_id_.empty() || | 466 if (matches_.empty() || current_keyword_extension_id_.empty() || |
462 !ExtractKeywordFromInput(input, &keyword, &remaining_input)) | 467 !ExtractKeywordFromInput(input, &keyword, &remaining_input)) |
463 return; | 468 return; |
464 | 469 |
465 const TemplateURL* template_url(model->GetTemplateURLForKeyword(keyword)); | 470 const TemplateURL* template_url( |
| 471 model->GetTemplateURLForKeyword(WideToUTF16Hack(keyword))); |
466 ApplyDefaultSuggestionForExtensionKeyword(profile_, template_url, | 472 ApplyDefaultSuggestionForExtensionKeyword(profile_, template_url, |
467 WideToUTF16(remaining_input), | 473 WideToUTF16(remaining_input), |
468 &matches_[0]); | 474 &matches_[0]); |
469 listener_->OnProviderUpdate(true); | 475 listener_->OnProviderUpdate(true); |
470 return; | 476 return; |
471 } | 477 } |
472 | 478 |
473 case NotificationType::EXTENSION_OMNIBOX_SUGGESTIONS_READY: { | 479 case NotificationType::EXTENSION_OMNIBOX_SUGGESTIONS_READY: { |
474 const ExtensionOmniboxSuggestions& suggestions = | 480 const ExtensionOmniboxSuggestions& suggestions = |
475 *Details<ExtensionOmniboxSuggestions>(details).ptr(); | 481 *Details<ExtensionOmniboxSuggestions>(details).ptr(); |
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
528 } | 534 } |
529 | 535 |
530 void KeywordProvider::MaybeEndExtensionKeywordMode() { | 536 void KeywordProvider::MaybeEndExtensionKeywordMode() { |
531 if (!current_keyword_extension_id_.empty()) { | 537 if (!current_keyword_extension_id_.empty()) { |
532 ExtensionOmniboxEventRouter::OnInputCancelled( | 538 ExtensionOmniboxEventRouter::OnInputCancelled( |
533 profile_, current_keyword_extension_id_); | 539 profile_, current_keyword_extension_id_); |
534 | 540 |
535 current_keyword_extension_id_.clear(); | 541 current_keyword_extension_id_.clear(); |
536 } | 542 } |
537 } | 543 } |
OLD | NEW |