OLD | NEW |
---|---|
1 // Copyright (c) 2011 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/search_provider.h" | 5 #include "chrome/browser/autocomplete/search_provider.h" |
6 | 6 |
7 #include <algorithm> | 7 #include <algorithm> |
8 #include <cmath> | 8 #include <cmath> |
9 | 9 |
10 #include "base/callback.h" | 10 #include "base/callback.h" |
11 #include "base/i18n/break_iterator.h" | |
11 #include "base/i18n/case_conversion.h" | 12 #include "base/i18n/case_conversion.h" |
12 #include "base/i18n/icu_string_conversions.h" | 13 #include "base/i18n/icu_string_conversions.h" |
13 #include "base/message_loop.h" | 14 #include "base/message_loop.h" |
14 #include "base/string16.h" | 15 #include "base/string16.h" |
15 #include "base/utf_string_conversions.h" | 16 #include "base/utf_string_conversions.h" |
16 #include "chrome/browser/autocomplete/autocomplete_classifier.h" | 17 #include "chrome/browser/autocomplete/autocomplete_classifier.h" |
17 #include "chrome/browser/autocomplete/keyword_provider.h" | 18 #include "chrome/browser/autocomplete/keyword_provider.h" |
18 #include "chrome/browser/autocomplete/autocomplete_match.h" | 19 #include "chrome/browser/autocomplete/autocomplete_match.h" |
19 #include "chrome/browser/history/history.h" | 20 #include "chrome/browser/history/history.h" |
20 #include "chrome/browser/instant/instant_controller.h" | 21 #include "chrome/browser/instant/instant_controller.h" |
21 #include "chrome/browser/net/url_fixer_upper.h" | 22 #include "chrome/browser/net/url_fixer_upper.h" |
22 #include "chrome/browser/prefs/pref_service.h" | 23 #include "chrome/browser/prefs/pref_service.h" |
23 #include "chrome/browser/profiles/profile.h" | 24 #include "chrome/browser/profiles/profile.h" |
24 #include "chrome/browser/history/in_memory_database.h" | 25 #include "chrome/browser/history/in_memory_database.h" |
25 #include "chrome/browser/search_engines/template_url_service.h" | 26 #include "chrome/browser/search_engines/template_url_service.h" |
26 #include "chrome/browser/search_engines/template_url_service_factory.h" | 27 #include "chrome/browser/search_engines/template_url_service_factory.h" |
27 #include "chrome/common/pref_names.h" | 28 #include "chrome/common/pref_names.h" |
28 #include "chrome/common/url_constants.h" | 29 #include "chrome/common/url_constants.h" |
29 #include "content/common/json_value_serializer.h" | 30 #include "content/common/json_value_serializer.h" |
30 #include "googleurl/src/url_util.h" | 31 #include "googleurl/src/url_util.h" |
31 #include "grit/generated_resources.h" | 32 #include "grit/generated_resources.h" |
32 #include "net/base/escape.h" | 33 #include "net/base/escape.h" |
33 #include "net/http/http_response_headers.h" | 34 #include "net/http/http_response_headers.h" |
34 #include "net/url_request/url_request_status.h" | 35 #include "net/url_request/url_request_status.h" |
35 #include "ui/base/l10n/l10n_util.h" | 36 #include "ui/base/l10n/l10n_util.h" |
36 | 37 |
37 using base::Time; | 38 using base::Time; |
38 using base::TimeDelta; | 39 using base::TimeDelta; |
39 | 40 |
41 namespace { | |
42 | |
43 bool HasMultipleWords(const string16& text) { | |
44 base::i18n::BreakIterator i(text, base::i18n::BreakIterator::BREAK_WORD); | |
45 bool found_word = false; | |
sky
2011/07/07 02:49:15
nit: move this inside the if.
| |
46 if (i.Init()) { | |
47 while (i.Advance()) { | |
48 if (i.IsWord()) { | |
49 if (found_word) | |
50 return true; | |
51 found_word = true; | |
52 } | |
53 } | |
54 } | |
55 return false; | |
56 } | |
57 | |
58 }; | |
59 | |
40 // static | 60 // static |
41 const int SearchProvider::kDefaultProviderURLFetcherID = 1; | 61 const int SearchProvider::kDefaultProviderURLFetcherID = 1; |
42 // static | 62 // static |
43 const int SearchProvider::kKeywordProviderURLFetcherID = 2; | 63 const int SearchProvider::kKeywordProviderURLFetcherID = 2; |
44 | 64 |
45 // static | 65 // static |
46 bool SearchProvider::query_suggest_immediately_ = false; | 66 bool SearchProvider::query_suggest_immediately_ = false; |
47 | 67 |
48 void SearchProvider::Providers::Set(const TemplateURL* default_provider, | 68 void SearchProvider::Providers::Set(const TemplateURL* default_provider, |
49 const TemplateURL* keyword_provider) { | 69 const TemplateURL* keyword_provider) { |
(...skipping 546 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
596 matches_.push_back(NavigationToMatch(navigation_results.front(), | 616 matches_.push_back(NavigationToMatch(navigation_results.front(), |
597 CalculateRelevanceForNavigation(num_results, 0, is_keyword), | 617 CalculateRelevanceForNavigation(num_results, 0, is_keyword), |
598 is_keyword)); | 618 is_keyword)); |
599 } | 619 } |
600 } | 620 } |
601 | 621 |
602 void SearchProvider::AddHistoryResultsToMap(const HistoryResults& results, | 622 void SearchProvider::AddHistoryResultsToMap(const HistoryResults& results, |
603 bool is_keyword, | 623 bool is_keyword, |
604 int did_not_accept_suggestion, | 624 int did_not_accept_suggestion, |
605 MatchMap* map) { | 625 MatchMap* map) { |
626 bool base_prevent_inline_autocomplete = | |
627 (input_.type() == AutocompleteInput::URL) || | |
628 input_.prevent_inline_autocomplete(); | |
629 const string16& input_text( | |
630 is_keyword ? keyword_input_text_ : input_.text()); | |
631 bool input_not_multiple_words = !HasMultipleWords(input_text); | |
632 bool is_primary_provider = providers_.is_primary_provider(is_keyword); | |
633 AutocompleteClassifier* classifier = profile_->GetAutocompleteClassifier(); | |
606 int last_relevance = 0; | 634 int last_relevance = 0; |
607 AutocompleteClassifier* classifier = profile_->GetAutocompleteClassifier(); | |
608 for (HistoryResults::const_iterator i(results.begin()); i != results.end(); | 635 for (HistoryResults::const_iterator i(results.begin()); i != results.end(); |
609 ++i) { | 636 ++i) { |
637 // Don't autocomplete multi-word queries that have only been seen once | |
638 // unless the user has typed more than one word. | |
639 bool prevent_inline_autocomplete = base_prevent_inline_autocomplete || | |
640 (input_not_multiple_words && (i->visits < 2) && | |
641 HasMultipleWords(i->term)); | |
642 | |
643 // Don't autocomplete search terms that would normally be treated as URLs | |
644 // when typed. For example, if the user searched for "google.com" and types | |
645 // "goog", don't autocomplete to the search term "google.com". Otherwise, | |
646 // the input will look like a URL but act like a search, which is confusing. | |
647 // NOTE: We don't check this in the following cases: | |
648 // * When inline autocomplete is disabled, we won't be inline | |
649 // autocompleting this term, so we don't need to worry about confusion as | |
650 // much. This also prevents calling Classify() again from inside the | |
651 // classifier (which will corrupt state and likely crash), since the | |
652 // classifier always disables inline autocomplete. | |
653 // * When the user has typed the whole term, the "what you typed" history | |
654 // match will outrank us for URL-like inputs anyway, so we need not do | |
655 // anything special. | |
656 if (!prevent_inline_autocomplete && classifier && (i->term != input_text)) { | |
657 AutocompleteMatch match; | |
658 classifier->Classify(i->term, string16(), false, false, &match, NULL); | |
659 prevent_inline_autocomplete = match.transition == PageTransition::TYPED; | |
660 } | |
661 | |
662 int relevance = CalculateRelevanceForHistory(i->time, is_primary_provider, | |
663 prevent_inline_autocomplete); | |
610 // History returns results sorted for us. We force the relevance to decrease | 664 // History returns results sorted for us. We force the relevance to decrease |
611 // so that the sort from history is honored. We should never end up with a | 665 // so that the sort from history is honored. We should never end up with a |
612 // match having a relevance greater than the previous, but they might be | 666 // match having a relevance greater than the previous, but they might be |
613 // equal. If we didn't force the relevance to decrease and we ended up in a | 667 // equal. If we didn't force the relevance to decrease and we ended up in a |
614 // situation where the relevance was equal, then which was shown first would | 668 // situation where the relevance was equal, then which was shown first would |
615 // be random. | 669 // be random. |
616 // This uses >= to handle the case where 3 or more results have the same | 670 // This uses >= to handle the case where 3 or more results have the same |
617 // relevance. | 671 // relevance. |
618 bool term_looks_like_url = false; | |
619 // Don't autocomplete search terms that would normally be treated as URLs | |
620 // when typed. For example, if the user searched for google.com and types | |
621 // goog, don't autocomplete to the search term google.com. Otherwise, the | |
622 // input will look like a URL but act like a search, which is confusing. | |
623 // NOTE: We don't check this in the following cases: | |
624 // * When inline autocomplete is disabled, we won't be inline | |
625 // autocompleting this term, so we don't need to worry about confusion as | |
626 // much. This also prevents calling Classify() again from inside the | |
627 // classifier (which will corrupt state and likely crash), since the | |
628 // classifier always disabled inline autocomplete. | |
629 // * When the user has typed the whole term, the "what you typed" history | |
630 // match will outrank us for URL-like inputs anyway, so we need not do | |
631 // anything special. | |
632 if (!input_.prevent_inline_autocomplete() && classifier && | |
633 i->term != input_.text()) { | |
634 AutocompleteMatch match; | |
635 classifier->Classify(i->term, string16(), false, false, &match, NULL); | |
636 term_looks_like_url = match.transition == PageTransition::TYPED; | |
637 } | |
638 int relevance = CalculateRelevanceForHistory(i->time, term_looks_like_url, | |
639 is_keyword); | |
640 if (i != results.begin() && relevance >= last_relevance) | 672 if (i != results.begin() && relevance >= last_relevance) |
641 relevance = last_relevance - 1; | 673 relevance = last_relevance - 1; |
642 last_relevance = relevance; | 674 last_relevance = relevance; |
643 AddMatchToMap(i->term, | 675 |
644 is_keyword ? keyword_input_text_ : input_.text(), | 676 AddMatchToMap(i->term, input_text, relevance, |
645 relevance, | |
646 AutocompleteMatch::SEARCH_HISTORY, did_not_accept_suggestion, | 677 AutocompleteMatch::SEARCH_HISTORY, did_not_accept_suggestion, |
647 is_keyword, input_.prevent_inline_autocomplete(), | 678 is_keyword, input_.prevent_inline_autocomplete(), map); |
648 map); | |
649 } | 679 } |
650 } | 680 } |
651 | 681 |
652 void SearchProvider::AddSuggestResultsToMap( | 682 void SearchProvider::AddSuggestResultsToMap( |
653 const SuggestResults& suggest_results, | 683 const SuggestResults& suggest_results, |
654 bool is_keyword, | 684 bool is_keyword, |
655 int did_not_accept_suggestion, | 685 int did_not_accept_suggestion, |
656 MatchMap* map) { | 686 MatchMap* map) { |
657 for (size_t i = 0; i < suggest_results.size(); ++i) { | 687 for (size_t i = 0; i < suggest_results.size(); ++i) { |
658 AddMatchToMap(suggest_results[i], | 688 AddMatchToMap(suggest_results[i], |
(...skipping 21 matching lines...) Expand all Loading... | |
680 | 710 |
681 case AutocompleteInput::URL: | 711 case AutocompleteInput::URL: |
682 return 850; | 712 return 850; |
683 | 713 |
684 default: | 714 default: |
685 NOTREACHED(); | 715 NOTREACHED(); |
686 return 0; | 716 return 0; |
687 } | 717 } |
688 } | 718 } |
689 | 719 |
690 int SearchProvider::CalculateRelevanceForHistory(const Time& time, | 720 int SearchProvider::CalculateRelevanceForHistory( |
691 bool looks_like_url, | 721 const Time& time, |
692 bool is_keyword) const { | 722 bool is_primary_provider, |
723 bool prevent_inline_autocomplete) const { | |
693 // The relevance of past searches falls off over time. There are two distinct | 724 // The relevance of past searches falls off over time. There are two distinct |
694 // equations used. If the first equation is used (searches to the primary | 725 // equations used. If the first equation is used (searches to the primary |
695 // provider with a type other than URL that don't autocomplete to a url) the | 726 // provider that we want to inline autocomplete), the score starts at 1399 and |
696 // score starts at 1399 and falls to 1300. If the second equation is used the | 727 // falls to 1300. If the second equation is used the relevance of a search 15 |
697 // relevance of a search 15 minutes ago is discounted about 50 points, while | 728 // minutes ago is discounted 50 points, while the relevance of a search two |
698 // the relevance of a search two weeks ago is discounted about 450 points. | 729 // weeks ago is discounted 450 points. |
699 double elapsed_time = std::max((Time::Now() - time).InSecondsF(), 0.); | 730 double elapsed_time = std::max((Time::Now() - time).InSecondsF(), 0.); |
700 | 731 if (is_primary_provider && !prevent_inline_autocomplete) { |
701 if (providers_.is_primary_provider(is_keyword) && | |
702 input_.type() != AutocompleteInput::URL && | |
703 !input_.prevent_inline_autocomplete() && !looks_like_url) { | |
704 // Searches with the past two days get a different curve. | 732 // Searches with the past two days get a different curve. |
705 const double autocomplete_time= 2 * 24 * 60 * 60; | 733 const double autocomplete_time = 2 * 24 * 60 * 60; |
706 if (elapsed_time < autocomplete_time) { | 734 if (elapsed_time < autocomplete_time) { |
707 return 1399 - static_cast<int>(99 * | 735 return 1399 - static_cast<int>(99 * |
708 std::pow(elapsed_time / autocomplete_time, 2.5)); | 736 std::pow(elapsed_time / autocomplete_time, 2.5)); |
709 } | 737 } |
710 elapsed_time -= autocomplete_time; | 738 elapsed_time -= autocomplete_time; |
711 } | 739 } |
712 | 740 |
713 const int score_discount = | 741 const int score_discount = |
714 static_cast<int>(6.5 * std::pow(elapsed_time, 0.3)); | 742 static_cast<int>(6.5 * std::pow(elapsed_time, 0.3)); |
715 | 743 |
716 // Don't let scores go below 0. Negative relevance scores are meaningful in | 744 // Don't let scores go below 0. Negative relevance scores are meaningful in |
717 // a different way. | 745 // a different way. |
718 int base_score; | 746 int base_score; |
719 if (!providers_.is_primary_provider(is_keyword)) | 747 if (is_primary_provider) |
748 base_score = (input_.type() == AutocompleteInput::URL) ? 750 : 1050; | |
749 else | |
720 base_score = 200; | 750 base_score = 200; |
721 else | |
722 base_score = (input_.type() == AutocompleteInput::URL) ? 750 : 1050; | |
723 return std::max(0, base_score - score_discount); | 751 return std::max(0, base_score - score_discount); |
724 } | 752 } |
725 | 753 |
726 int SearchProvider::CalculateRelevanceForSuggestion(size_t num_results, | 754 int SearchProvider::CalculateRelevanceForSuggestion(size_t num_results, |
727 size_t result_number, | 755 size_t result_number, |
728 bool is_keyword) const { | 756 bool is_keyword) const { |
729 DCHECK(result_number < num_results); | 757 DCHECK(result_number < num_results); |
730 int base_score; | 758 int base_score; |
731 if (!providers_.is_primary_provider(is_keyword)) | 759 if (!providers_.is_primary_provider(is_keyword)) |
732 base_score = 100; | 760 base_score = 100; |
(...skipping 173 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
906 match.description_class.push_back( | 934 match.description_class.push_back( |
907 ACMatchClassification(0, ACMatchClassification::DIM)); | 935 ACMatchClassification(0, ACMatchClassification::DIM)); |
908 // Only the first search match gets a description. | 936 // Only the first search match gets a description. |
909 return; | 937 return; |
910 | 938 |
911 default: | 939 default: |
912 break; | 940 break; |
913 } | 941 } |
914 } | 942 } |
915 } | 943 } |
OLD | NEW |