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