Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 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 "components/omnibox/browser/keyword_provider.h" | 5 #include "components/omnibox/browser/keyword_provider.h" |
| 6 | 6 |
| 7 #include <algorithm> | 7 #include <algorithm> |
| 8 #include <vector> | 8 #include <vector> |
| 9 | 9 |
| 10 #include "base/strings/string16.h" | 10 #include "base/strings/string16.h" |
| 11 #include "base/strings/string_util.h" | 11 #include "base/strings/string_util.h" |
| 12 #include "base/strings/utf_string_conversions.h" | 12 #include "base/strings/utf_string_conversions.h" |
| 13 #include "components/metrics/proto/omnibox_input_type.pb.h" | 13 #include "components/metrics/proto/omnibox_input_type.pb.h" |
| 14 #include "components/omnibox/browser/autocomplete_match.h" | 14 #include "components/omnibox/browser/autocomplete_match.h" |
| 15 #include "components/omnibox/browser/autocomplete_provider_client.h" | 15 #include "components/omnibox/browser/autocomplete_provider_client.h" |
| 16 #include "components/omnibox/browser/autocomplete_provider_listener.h" | 16 #include "components/omnibox/browser/autocomplete_provider_listener.h" |
| 17 #include "components/omnibox/browser/keyword_extensions_delegate.h" | 17 #include "components/omnibox/browser/keyword_extensions_delegate.h" |
| 18 #include "components/omnibox/browser/omnibox_field_trial.h" | |
| 18 #include "components/search_engines/template_url.h" | 19 #include "components/search_engines/template_url.h" |
| 19 #include "components/search_engines/template_url_service.h" | 20 #include "components/search_engines/template_url_service.h" |
| 20 #include "grit/components_strings.h" | 21 #include "grit/components_strings.h" |
| 21 #include "net/base/escape.h" | 22 #include "net/base/escape.h" |
| 22 #include "net/base/net_util.h" | 23 #include "net/base/net_util.h" |
| 23 #include "ui/base/l10n/l10n_util.h" | 24 #include "ui/base/l10n/l10n_util.h" |
| 24 | 25 |
| 25 namespace { | 26 namespace { |
| 26 | 27 |
| 27 // Helper functor for Start(), for sorting keyword matches by quality. | 28 // Helper functor for Start(), for sorting keyword matches by quality. |
| 28 class CompareQuality { | 29 class CompareQuality { |
| 29 public: | 30 public: |
| 30 // A keyword is of higher quality when a greater fraction of it has been | 31 // A keyword is of higher quality when a greater fraction of the important |
| 31 // typed, that is, when it is shorter. | 32 // part of it has been typed, that is, when the effective keyword length is |
| 33 // shorter. | |
| 32 // | 34 // |
| 33 // TODO(pkasting): Most recent and most frequent keywords are probably | 35 // TODO(pkasting): Most recent and most frequent keywords are probably |
| 34 // better rankings than the fraction of the keyword typed. We should | 36 // better rankings than the fraction of the keyword typed. We should |
| 35 // always put any exact matches first no matter what, since the code in | 37 // always put any exact matches first no matter what, since the code in |
| 36 // Start() assumes this (and it makes sense). | 38 // Start() assumes this (and it makes sense). |
| 37 bool operator()(const TemplateURL* t_url1, const TemplateURL* t_url2) const { | 39 bool operator()( |
| 38 return t_url1->keyword().length() < t_url2->keyword().length(); | 40 const TemplateURLService::TemplateURLAndEffectiveKeywordLength |
| 41 t_url_match1, | |
| 42 const TemplateURLService::TemplateURLAndEffectiveKeywordLength | |
| 43 t_url_match2) const { | |
| 44 return t_url_match1.second < t_url_match2.second; | |
| 39 } | 45 } |
| 40 }; | 46 }; |
| 41 | 47 |
| 42 // Helper for KeywordProvider::Start(), for ending keyword mode unless | 48 // Helper for KeywordProvider::Start(), for ending keyword mode unless |
| 43 // explicitly told otherwise. | 49 // explicitly told otherwise. |
| 44 class ScopedEndExtensionKeywordMode { | 50 class ScopedEndExtensionKeywordMode { |
| 45 public: | 51 public: |
| 46 explicit ScopedEndExtensionKeywordMode(KeywordExtensionsDelegate* delegate); | 52 explicit ScopedEndExtensionKeywordMode(KeywordExtensionsDelegate* delegate); |
| 47 ~ScopedEndExtensionKeywordMode(); | 53 ~ScopedEndExtensionKeywordMode(); |
| 48 | 54 |
| (...skipping 140 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 189 | 195 |
| 190 return keyword; | 196 return keyword; |
| 191 } | 197 } |
| 192 | 198 |
| 193 AutocompleteMatch KeywordProvider::CreateVerbatimMatch( | 199 AutocompleteMatch KeywordProvider::CreateVerbatimMatch( |
| 194 const base::string16& text, | 200 const base::string16& text, |
| 195 const base::string16& keyword, | 201 const base::string16& keyword, |
| 196 const AutocompleteInput& input) { | 202 const AutocompleteInput& input) { |
| 197 // A verbatim match is allowed to be the default match. | 203 // A verbatim match is allowed to be the default match. |
| 198 return CreateAutocompleteMatch( | 204 return CreateAutocompleteMatch( |
| 199 GetTemplateURLService()->GetTemplateURLForKeyword(keyword), input, | 205 GetTemplateURLService()->GetTemplateURLForKeyword(keyword), |
| 200 keyword.length(), SplitReplacementStringFromInput(text, true), true, 0); | 206 keyword.length(), input, keyword.length(), |
| 207 SplitReplacementStringFromInput(text, true), true, 0); | |
| 201 } | 208 } |
| 202 | 209 |
| 203 void KeywordProvider::Start(const AutocompleteInput& input, | 210 void KeywordProvider::Start(const AutocompleteInput& input, |
| 204 bool minimal_changes) { | 211 bool minimal_changes) { |
| 205 // This object ensures we end keyword mode if we exit the function without | 212 // This object ensures we end keyword mode if we exit the function without |
| 206 // toggling keyword mode to on. | 213 // toggling keyword mode to on. |
| 207 ScopedEndExtensionKeywordMode keyword_mode_toggle(extensions_delegate_.get()); | 214 ScopedEndExtensionKeywordMode keyword_mode_toggle(extensions_delegate_.get()); |
| 208 | 215 |
| 209 matches_.clear(); | 216 matches_.clear(); |
| 210 | 217 |
| (...skipping 25 matching lines...) Expand all Loading... | |
| 236 base::string16 keyword, remaining_input; | 243 base::string16 keyword, remaining_input; |
| 237 if (!ExtractKeywordFromInput(input, &keyword, &remaining_input)) | 244 if (!ExtractKeywordFromInput(input, &keyword, &remaining_input)) |
| 238 return; | 245 return; |
| 239 | 246 |
| 240 // Get the best matches for this keyword. | 247 // Get the best matches for this keyword. |
| 241 // | 248 // |
| 242 // NOTE: We could cache the previous keywords and reuse them here in the | 249 // NOTE: We could cache the previous keywords and reuse them here in the |
| 243 // |minimal_changes| case, but since we'd still have to recalculate their | 250 // |minimal_changes| case, but since we'd still have to recalculate their |
| 244 // relevances and we can just recreate the results synchronously anyway, we | 251 // relevances and we can just recreate the results synchronously anyway, we |
| 245 // don't bother. | 252 // don't bother. |
| 246 TemplateURLService::TemplateURLVector matches; | 253 TemplateURLService::TemplateURLMatchesVector matches; |
| 247 GetTemplateURLService()->FindMatchingKeywords( | 254 GetTemplateURLService()->FindMatchingKeywords( |
| 248 keyword, !remaining_input.empty(), &matches); | 255 keyword, !remaining_input.empty(), &matches); |
| 256 if (!OmniboxFieldTrial::KeywordRequiresPrefixBeforeDomain()) { | |
| 257 GetTemplateURLService()->FindMatchingDomainKeywords( | |
| 258 keyword, !remaining_input.empty(), &matches); | |
|
Peter Kasting
2015/11/09 23:11:14
As written this seems to imply that the functions
Mark P
2015/11/11 07:40:59
Revised to AddMatching...() and changed all commen
| |
| 259 } | |
| 249 | 260 |
| 250 for (TemplateURLService::TemplateURLVector::iterator i(matches.begin()); | 261 for (TemplateURLService::TemplateURLMatchesVector::iterator |
| 251 i != matches.end(); ) { | 262 i(matches.begin()); i != matches.end(); ) { |
| 252 const TemplateURL* template_url = *i; | 263 const TemplateURL* template_url = i->first; |
| 253 | 264 |
| 254 // Prune any extension keywords that are disallowed in incognito mode (if | 265 // Prune any extension keywords that are disallowed in incognito mode (if |
| 255 // we're incognito), or disabled. | 266 // we're incognito), or disabled. |
| 256 if (template_url->GetType() == TemplateURL::OMNIBOX_API_EXTENSION && | 267 if (template_url->GetType() == TemplateURL::OMNIBOX_API_EXTENSION && |
| 257 extensions_delegate_ && | 268 extensions_delegate_ && |
| 258 !extensions_delegate_->IsEnabledExtension( | 269 !extensions_delegate_->IsEnabledExtension( |
| 259 template_url->GetExtensionId())) { | 270 template_url->GetExtensionId())) { |
| 260 i = matches.erase(i); | 271 i = matches.erase(i); |
| 261 continue; | 272 continue; |
| 262 } | 273 } |
| (...skipping 10 matching lines...) Expand all Loading... | |
| 273 ++i; | 284 ++i; |
| 274 } | 285 } |
| 275 if (matches.empty()) | 286 if (matches.empty()) |
| 276 return; | 287 return; |
| 277 std::sort(matches.begin(), matches.end(), CompareQuality()); | 288 std::sort(matches.begin(), matches.end(), CompareQuality()); |
| 278 | 289 |
| 279 // Limit to one exact or three inexact matches, and mark them up for display | 290 // Limit to one exact or three inexact matches, and mark them up for display |
| 280 // in the autocomplete popup. | 291 // in the autocomplete popup. |
| 281 // Any exact match is going to be the highest quality match, and thus at the | 292 // Any exact match is going to be the highest quality match, and thus at the |
| 282 // front of our vector. | 293 // front of our vector. |
| 283 if (matches.front()->keyword() == keyword) { | 294 if (matches.front().first->keyword() == keyword) { |
| 284 const TemplateURL* template_url = matches.front(); | 295 const TemplateURL* template_url = matches.front().first; |
| 296 const size_t effective_keyword_length = matches.front().second; | |
| 285 const bool is_extension_keyword = | 297 const bool is_extension_keyword = |
| 286 template_url->GetType() == TemplateURL::OMNIBOX_API_EXTENSION; | 298 template_url->GetType() == TemplateURL::OMNIBOX_API_EXTENSION; |
| 287 | 299 |
| 288 // Only create an exact match if |remaining_input| is empty or if | 300 // Only create an exact match if |remaining_input| is empty or if |
| 289 // this is an extension keyword. If |remaining_input| is a | 301 // this is an extension keyword. If |remaining_input| is a |
| 290 // non-empty non-extension keyword (i.e., a regular keyword that | 302 // non-empty non-extension keyword (i.e., a regular keyword that |
| 291 // supports replacement and that has extra text following it), | 303 // supports replacement and that has extra text following it), |
| 292 // then SearchProvider creates the exact (a.k.a. verbatim) match. | 304 // then SearchProvider creates the exact (a.k.a. verbatim) match. |
| 293 if (!remaining_input.empty() && !is_extension_keyword) | 305 if (!remaining_input.empty() && !is_extension_keyword) |
| 294 return; | 306 return; |
| 295 | 307 |
| 296 // TODO(pkasting): We should probably check that if the user explicitly | 308 // TODO(pkasting): We should probably check that if the user explicitly |
| 297 // typed a scheme, that scheme matches the one in |template_url|. | 309 // typed a scheme, that scheme matches the one in |template_url|. |
| 298 | 310 |
| 299 // When creating an exact match (either for the keyword itself, no | 311 // When creating an exact match (either for the keyword itself, no |
| 300 // remaining query or an extension keyword, possibly with remaining | 312 // remaining query or an extension keyword, possibly with remaining |
| 301 // input), allow the match to be the default match. | 313 // input), allow the match to be the default match. |
| 302 matches_.push_back(CreateAutocompleteMatch( | 314 matches_.push_back(CreateAutocompleteMatch( |
| 303 template_url, input, keyword.length(), remaining_input, true, -1)); | 315 template_url, effective_keyword_length, input, keyword.length(), |
| 316 remaining_input, true, -1)); | |
| 304 | 317 |
| 305 if (is_extension_keyword && extensions_delegate_) { | 318 if (is_extension_keyword && extensions_delegate_) { |
| 306 if (extensions_delegate_->Start(input, minimal_changes, template_url, | 319 if (extensions_delegate_->Start(input, minimal_changes, template_url, |
| 307 remaining_input)) | 320 remaining_input)) |
| 308 keyword_mode_toggle.StayInKeywordMode(); | 321 keyword_mode_toggle.StayInKeywordMode(); |
| 309 } | 322 } |
| 310 } else { | 323 } else { |
| 311 if (matches.size() > kMaxMatches) | 324 for (TemplateURLService::TemplateURLMatchesVector::const_iterator i( |
| 312 matches.erase(matches.begin() + kMaxMatches, matches.end()); | 325 matches.begin()); |
| 313 for (TemplateURLService::TemplateURLVector::const_iterator i( | 326 (i != matches.end()) && (matches.size() <= kMaxMatches); ++i) { |
| 314 matches.begin()); i != matches.end(); ++i) { | 327 // Skip keywords that we've already added. It's possible we may have |
| 315 matches_.push_back(CreateAutocompleteMatch( | 328 // retrieved the same keyword twice. For example, the keyword' |
|
Peter Kasting
2015/11/09 23:11:14
Nit: Mismatched '
Mark P
2015/11/11 07:40:59
Done.
| |
| 316 *i, input, keyword.length(), remaining_input, false, -1)); | 329 // abc.abc.com, may be retrieved for the input abc from the full keyword |
|
Peter Kasting
2015/11/09 23:11:14
Nit: No comma (but consider adding quotes around k
Mark P
2015/11/11 07:40:59
Did both.
| |
| 330 // matching and the domain matching passes. | |
| 331 bool found_duplicate = false; | |
| 332 for (ACMatches::const_iterator j = matches_.begin(); | |
| 333 (j != matches_.end()) && !found_duplicate; ++j) { | |
| 334 if (j->keyword == i->first->keyword()) | |
| 335 found_duplicate = true; | |
| 336 } | |
|
Peter Kasting
2015/11/09 23:11:14
Nit: Use std::find_if() with a lambda instead of t
Mark P
2015/11/11 07:41:00
Done.
| |
| 337 if (!found_duplicate) { | |
| 338 matches_.push_back(CreateAutocompleteMatch( | |
| 339 i->first, i->second, input, keyword.length(), remaining_input, | |
| 340 false, -1)); | |
| 341 } | |
| 317 } | 342 } |
| 318 } | 343 } |
| 319 } | 344 } |
| 320 | 345 |
| 321 void KeywordProvider::Stop(bool clear_cached_results, | 346 void KeywordProvider::Stop(bool clear_cached_results, |
| 322 bool due_to_user_inactivity) { | 347 bool due_to_user_inactivity) { |
| 323 done_ = true; | 348 done_ = true; |
| 324 // Only end an extension's request if the user did something to explicitly | 349 // Only end an extension's request if the user did something to explicitly |
| 325 // cancel it; mere inactivity shouldn't terminate long-running extension | 350 // cancel it; mere inactivity shouldn't terminate long-running extension |
| 326 // operations since the user likely explicitly requested them. | 351 // operations since the user likely explicitly requested them. |
| (...skipping 12 matching lines...) Expand all Loading... | |
| 339 return false; | 364 return false; |
| 340 | 365 |
| 341 *keyword = TemplateURLService::CleanUserInputKeyword( | 366 *keyword = TemplateURLService::CleanUserInputKeyword( |
| 342 SplitKeywordFromInput(input.text(), true, remaining_input)); | 367 SplitKeywordFromInput(input.text(), true, remaining_input)); |
| 343 return !keyword->empty(); | 368 return !keyword->empty(); |
| 344 } | 369 } |
| 345 | 370 |
| 346 // static | 371 // static |
| 347 int KeywordProvider::CalculateRelevance(metrics::OmniboxInputType::Type type, | 372 int KeywordProvider::CalculateRelevance(metrics::OmniboxInputType::Type type, |
| 348 bool complete, | 373 bool complete, |
| 374 bool nearly_complete, | |
| 349 bool supports_replacement, | 375 bool supports_replacement, |
| 350 bool prefer_keyword, | 376 bool prefer_keyword, |
| 351 bool allow_exact_keyword_match) { | 377 bool allow_exact_keyword_match) { |
| 352 // This function is responsible for scoring suggestions of keywords | 378 // This function is responsible for scoring suggestions of keywords |
| 353 // themselves and the suggestion of the verbatim query on an | 379 // themselves and the suggestion of the verbatim query on an |
| 354 // extension keyword. SearchProvider::CalculateRelevanceForKeywordVerbatim() | 380 // extension keyword. SearchProvider::CalculateRelevanceForKeywordVerbatim() |
| 355 // scores verbatim query suggestions for non-extension keywords. | 381 // scores verbatim query suggestions for non-extension keywords. |
| 356 // These two functions are currently in sync, but there's no reason | 382 // These two functions are currently in sync except for the code at the |
| 357 // we couldn't decide in the future to score verbatim matches | 383 // beginning for this function that handles scoring not-fully-typed keywords. |
| 384 // (SearchProvider does not make such suggestions and so has no need to | |
| 385 // score them.) Other than that, they're currently in sync. There's no | |
| 386 // reason we couldn't decide in the future to score verbatim matches | |
| 358 // differently for extension and non-extension keywords. If you | 387 // differently for extension and non-extension keywords. If you |
| 359 // make such a change, however, you should update this comment to | 388 // make such a change, however, you should update this comment to |
| 360 // describe it, so it's clear why the functions diverge. | 389 // describe it, so it's clear why the functions diverge. |
|
Peter Kasting
2015/11/09 23:11:14
Instead of this long comment about what's in sync
Mark P
2015/11/11 07:41:00
Done. Now search_provider.{cc,h} is part of this
| |
| 361 if (!complete) | 390 if (!complete) { |
| 391 const int nearly_complete_score = | |
| 392 OmniboxFieldTrial::KeywordScoreForNearlyCompleteMatch(); | |
| 393 // If we have a special score to apply for nearly-complete matches, do so. | |
| 394 if (nearly_complete && (nearly_complete_score > -1)) | |
| 395 return nearly_complete_score; | |
| 362 return (type == metrics::OmniboxInputType::URL) ? 700 : 450; | 396 return (type == metrics::OmniboxInputType::URL) ? 700 : 450; |
| 397 } | |
| 363 if (!supports_replacement || (allow_exact_keyword_match && prefer_keyword)) | 398 if (!supports_replacement || (allow_exact_keyword_match && prefer_keyword)) |
| 364 return 1500; | 399 return 1500; |
| 365 return (allow_exact_keyword_match && | 400 return (allow_exact_keyword_match && |
| 366 (type == metrics::OmniboxInputType::QUERY)) ? | 401 (type == metrics::OmniboxInputType::QUERY)) ? |
| 367 1450 : 1100; | 402 1450 : 1100; |
| 368 } | 403 } |
| 369 | 404 |
| 370 AutocompleteMatch KeywordProvider::CreateAutocompleteMatch( | 405 AutocompleteMatch KeywordProvider::CreateAutocompleteMatch( |
| 371 const TemplateURL* template_url, | 406 const TemplateURL* template_url, |
| 407 const size_t effective_keyword_length, | |
| 372 const AutocompleteInput& input, | 408 const AutocompleteInput& input, |
| 373 size_t prefix_length, | 409 size_t prefix_length, |
| 374 const base::string16& remaining_input, | 410 const base::string16& remaining_input, |
| 375 bool allowed_to_be_default_match, | 411 bool allowed_to_be_default_match, |
| 376 int relevance) { | 412 int relevance) { |
| 377 DCHECK(template_url); | 413 DCHECK(template_url); |
| 378 const bool supports_replacement = | 414 const bool supports_replacement = |
| 379 template_url->url_ref().SupportsReplacement( | 415 template_url->url_ref().SupportsReplacement( |
| 380 GetTemplateURLService()->search_terms_data()); | 416 GetTemplateURLService()->search_terms_data()); |
| 381 | 417 |
| 382 // Create an edit entry of "[keyword] [remaining input]". This is helpful | 418 // Create an edit entry of "[keyword] [remaining input]". This is helpful |
| 383 // even when [remaining input] is empty, as the user can select the popup | 419 // even when [remaining input] is empty, as the user can select the popup |
| 384 // choice and immediately begin typing in query input. | 420 // choice and immediately begin typing in query input. |
| 385 const base::string16& keyword = template_url->keyword(); | 421 const base::string16& keyword = template_url->keyword(); |
| 386 const bool keyword_complete = (prefix_length == keyword.length()); | 422 const bool keyword_complete = (prefix_length == keyword.length()); |
| 423 const bool nearly_complete = (prefix_length >= effective_keyword_length); | |
| 387 if (relevance < 0) { | 424 if (relevance < 0) { |
| 388 relevance = | 425 relevance = |
| 389 CalculateRelevance(input.type(), keyword_complete, | 426 CalculateRelevance(input.type(), keyword_complete, nearly_complete, |
| 390 // When the user wants keyword matches to take | 427 // When the user wants keyword matches to take |
| 391 // preference, score them highly regardless of | 428 // preference, score them highly regardless of |
| 392 // whether the input provides query text. | 429 // whether the input provides query text. |
| 393 supports_replacement, input.prefer_keyword(), | 430 supports_replacement, input.prefer_keyword(), |
| 394 input.allow_exact_keyword_match()); | 431 input.allow_exact_keyword_match()); |
| 395 } | 432 } |
| 396 AutocompleteMatch match(this, relevance, false, | 433 AutocompleteMatch match(this, relevance, false, |
| 397 supports_replacement ? AutocompleteMatchType::SEARCH_OTHER_ENGINE : | 434 supports_replacement ? AutocompleteMatchType::SEARCH_OTHER_ENGINE : |
| 398 AutocompleteMatchType::HISTORY_KEYWORD); | 435 AutocompleteMatchType::HISTORY_KEYWORD); |
| 399 match.allowed_to_be_default_match = allowed_to_be_default_match; | 436 match.allowed_to_be_default_match = allowed_to_be_default_match; |
| (...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 471 ACMatchClassification::NONE, &match->contents_class); | 508 ACMatchClassification::NONE, &match->contents_class); |
| 472 } | 509 } |
| 473 } | 510 } |
| 474 | 511 |
| 475 TemplateURLService* KeywordProvider::GetTemplateURLService() const { | 512 TemplateURLService* KeywordProvider::GetTemplateURLService() const { |
| 476 // Make sure the model is loaded. This is cheap and quickly bails out if | 513 // Make sure the model is loaded. This is cheap and quickly bails out if |
| 477 // the model is already loaded. | 514 // the model is already loaded. |
| 478 model_->Load(); | 515 model_->Load(); |
| 479 return model_; | 516 return model_; |
| 480 } | 517 } |
| OLD | NEW |