| OLD | NEW |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 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/history/url_index_private_data.h" | 5 #include "chrome/browser/history/url_index_private_data.h" |
| 6 | 6 |
| 7 #include <algorithm> | 7 #include <algorithm> |
| 8 #include <functional> | 8 #include <functional> |
| 9 #include <iterator> | 9 #include <iterator> |
| 10 #include <limits> | 10 #include <limits> |
| 11 #include <numeric> | 11 #include <numeric> |
| 12 #include <vector> | 12 #include <vector> |
| 13 | 13 |
| 14 #include <math.h> | |
| 15 | |
| 16 #include "base/basictypes.h" | 14 #include "base/basictypes.h" |
| 17 #include "base/command_line.h" | |
| 18 #include "base/file_util.h" | 15 #include "base/file_util.h" |
| 19 #include "base/i18n/case_conversion.h" | 16 #include "base/i18n/case_conversion.h" |
| 20 #include "base/metrics/histogram.h" | 17 #include "base/metrics/histogram.h" |
| 21 #include "base/string_util.h" | 18 #include "base/string_util.h" |
| 22 #include "base/time.h" | 19 #include "base/time.h" |
| 23 #include "base/utf_string_conversions.h" | 20 #include "base/utf_string_conversions.h" |
| 24 #include "chrome/browser/autocomplete/autocomplete.h" | 21 #include "chrome/browser/autocomplete/autocomplete.h" |
| 25 #include "chrome/browser/autocomplete/url_prefix.h" | 22 #include "chrome/browser/autocomplete/url_prefix.h" |
| 26 #include "chrome/browser/history/history_database.h" | 23 #include "chrome/browser/history/history_database.h" |
| 27 #include "chrome/browser/history/in_memory_url_index.h" | 24 #include "chrome/browser/history/in_memory_url_index.h" |
| 28 #include "chrome/common/chrome_switches.h" | |
| 29 #include "content/public/browser/browser_thread.h" | 25 #include "content/public/browser/browser_thread.h" |
| 30 #include "content/public/browser/notification_details.h" | 26 #include "content/public/browser/notification_details.h" |
| 31 #include "content/public/browser/notification_service.h" | 27 #include "content/public/browser/notification_service.h" |
| 32 #include "content/public/browser/notification_source.h" | 28 #include "content/public/browser/notification_source.h" |
| 33 #include "net/base/net_util.h" | 29 #include "net/base/net_util.h" |
| 34 #include "third_party/protobuf/src/google/protobuf/repeated_field.h" | 30 #include "third_party/protobuf/src/google/protobuf/repeated_field.h" |
| 35 | 31 |
| 36 using google::protobuf::RepeatedField; | 32 using google::protobuf::RepeatedField; |
| 37 using google::protobuf::RepeatedPtrField; | 33 using google::protobuf::RepeatedPtrField; |
| 38 using in_memory_url_index::InMemoryURLIndexCacheItem; | 34 using in_memory_url_index::InMemoryURLIndexCacheItem; |
| (...skipping 11 matching lines...) Expand all Loading... |
| 50 typedef imui:: | 46 typedef imui:: |
| 51 InMemoryURLIndexCacheItem_WordIDHistoryMapItem_WordIDHistoryMapEntry | 47 InMemoryURLIndexCacheItem_WordIDHistoryMapItem_WordIDHistoryMapEntry |
| 52 WordIDHistoryMapEntry; | 48 WordIDHistoryMapEntry; |
| 53 typedef imui::InMemoryURLIndexCacheItem_HistoryInfoMapItem HistoryInfoMapItem; | 49 typedef imui::InMemoryURLIndexCacheItem_HistoryInfoMapItem HistoryInfoMapItem; |
| 54 typedef imui::InMemoryURLIndexCacheItem_HistoryInfoMapItem_HistoryInfoMapEntry | 50 typedef imui::InMemoryURLIndexCacheItem_HistoryInfoMapItem_HistoryInfoMapEntry |
| 55 HistoryInfoMapEntry; | 51 HistoryInfoMapEntry; |
| 56 typedef imui::InMemoryURLIndexCacheItem_WordStartsMapItem WordStartsMapItem; | 52 typedef imui::InMemoryURLIndexCacheItem_WordStartsMapItem WordStartsMapItem; |
| 57 typedef imui::InMemoryURLIndexCacheItem_WordStartsMapItem_WordStartsMapEntry | 53 typedef imui::InMemoryURLIndexCacheItem_WordStartsMapItem_WordStartsMapEntry |
| 58 WordStartsMapEntry; | 54 WordStartsMapEntry; |
| 59 | 55 |
| 60 // The maximum score any candidate result can achieve. | |
| 61 const int kMaxTotalScore = 1425; | |
| 62 | |
| 63 // Score ranges used to get a 'base' score for each of the scoring factors | |
| 64 // (such as recency of last visit, times visited, times the URL was typed, | |
| 65 // and the quality of the string match). There is a matching value range for | |
| 66 // each of these scores for each factor. Note that the top score is greater | |
| 67 // than |kMaxTotalScore|. The score for each candidate will be capped in the | |
| 68 // final calculation. | |
| 69 const int kScoreRank[] = { 1450, 1200, 900, 400 }; | |
| 70 | |
| 71 // SearchTermCacheItem --------------------------------------------------------- | 56 // SearchTermCacheItem --------------------------------------------------------- |
| 72 | 57 |
| 73 URLIndexPrivateData::SearchTermCacheItem::SearchTermCacheItem( | 58 URLIndexPrivateData::SearchTermCacheItem::SearchTermCacheItem( |
| 74 const WordIDSet& word_id_set, | 59 const WordIDSet& word_id_set, |
| 75 const HistoryIDSet& history_id_set) | 60 const HistoryIDSet& history_id_set) |
| 76 : word_id_set_(word_id_set), | 61 : word_id_set_(word_id_set), |
| 77 history_id_set_(history_id_set), | 62 history_id_set_(history_id_set), |
| 78 used_(true) {} | 63 used_(true) {} |
| 79 | 64 |
| 80 URLIndexPrivateData::SearchTermCacheItem::SearchTermCacheItem() | 65 URLIndexPrivateData::SearchTermCacheItem::SearchTermCacheItem() |
| 81 : used_(true) {} | 66 : used_(true) {} |
| 82 | 67 |
| 83 URLIndexPrivateData::SearchTermCacheItem::~SearchTermCacheItem() {} | 68 URLIndexPrivateData::SearchTermCacheItem::~SearchTermCacheItem() {} |
| 84 | 69 |
| 85 // Algorithm Functions --------------------------------------------------------- | 70 // Algorithm Functions --------------------------------------------------------- |
| 86 | 71 |
| 87 // Comparison function for sorting search terms by descending length. | 72 // Comparison function for sorting search terms by descending length. |
| 88 bool LengthGreater(const string16& string_a, const string16& string_b) { | 73 bool LengthGreater(const string16& string_a, const string16& string_b) { |
| 89 return string_a.length() > string_b.length(); | 74 return string_a.length() > string_b.length(); |
| 90 } | 75 } |
| 91 | 76 |
| 92 // std::accumulate helper function to add up TermMatches' lengths. | |
| 93 int AccumulateMatchLength(int total, const TermMatch& match) { | |
| 94 return total + match.length; | |
| 95 } | |
| 96 | |
| 97 // Converts a raw value for some particular scoring factor into a score | |
| 98 // component for that factor. The conversion function is piecewise linear, with | |
| 99 // input values provided in |value_ranks| and resulting output scores from | |
| 100 // |kScoreRank| (mathematically, f(value_rank[i]) = kScoreRank[i]). A score | |
| 101 // cannot be higher than kScoreRank[0], and drops directly to 0 if lower than | |
| 102 // kScoreRank[3]. | |
| 103 // | |
| 104 // For example, take |value| == 70 and |value_ranks| == { 100, 50, 30, 10 }. | |
| 105 // Because 70 falls between ranks 0 (100) and 1 (50), the score is given by the | |
| 106 // linear function: | |
| 107 // score = m * value + b, where | |
| 108 // m = (kScoreRank[0] - kScoreRank[1]) / (value_ranks[0] - value_ranks[1]) | |
| 109 // b = value_ranks[1] | |
| 110 // Any value higher than 100 would be scored as if it were 100, and any value | |
| 111 // lower than 10 scored 0. | |
| 112 int ScoreForValue(int value, const int* value_ranks) { | |
| 113 int i = 0; | |
| 114 int rank_count = arraysize(kScoreRank); | |
| 115 while ((i < rank_count) && ((value_ranks[0] < value_ranks[1]) ? | |
| 116 (value > value_ranks[i]) : (value < value_ranks[i]))) | |
| 117 ++i; | |
| 118 if (i >= rank_count) | |
| 119 return 0; | |
| 120 int score = kScoreRank[i]; | |
| 121 if (i > 0) { | |
| 122 score += (value - value_ranks[i]) * | |
| 123 (kScoreRank[i - 1] - kScoreRank[i]) / | |
| 124 (value_ranks[i - 1] - value_ranks[i]); | |
| 125 } | |
| 126 return score; | |
| 127 } | |
| 128 | |
| 129 // InMemoryURLIndex's Private Data --------------------------------------------- | 77 // InMemoryURLIndex's Private Data --------------------------------------------- |
| 130 | 78 |
| 131 URLIndexPrivateData::URLIndexPrivateData() | 79 URLIndexPrivateData::URLIndexPrivateData() |
| 132 : restored_cache_version_(0), | 80 : restored_cache_version_(0), |
| 133 use_new_scoring_(false), | |
| 134 saved_cache_version_(kCurrentCacheFileVersion), | 81 saved_cache_version_(kCurrentCacheFileVersion), |
| 135 pre_filter_item_count_(0), | 82 pre_filter_item_count_(0), |
| 136 post_filter_item_count_(0), | 83 post_filter_item_count_(0), |
| 137 post_scoring_item_count_(0) { | 84 post_scoring_item_count_(0) { |
| 138 const std::string switch_value = CommandLine::ForCurrentProcess()-> | |
| 139 GetSwitchValueASCII(switches::kOmniboxHistoryQuickProviderNewScoring); | |
| 140 if (switch_value == switches::kOmniboxHistoryQuickProviderNewScoringEnabled) | |
| 141 use_new_scoring_ = true; | |
| 142 } | 85 } |
| 143 | 86 |
| 144 URLIndexPrivateData::~URLIndexPrivateData() {} | 87 URLIndexPrivateData::~URLIndexPrivateData() {} |
| 145 | 88 |
| 146 void URLIndexPrivateData::Clear() { | 89 void URLIndexPrivateData::Clear() { |
| 147 word_list_.clear(); | 90 word_list_.clear(); |
| 148 available_words_.clear(); | 91 available_words_.clear(); |
| 149 word_map_.clear(); | 92 word_map_.clear(); |
| 150 char_word_map_.clear(); | 93 char_word_map_.clear(); |
| 151 word_id_history_map_.clear(); | 94 word_id_history_map_.clear(); |
| (...skipping 428 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 580 lower_string_(lower_string), | 523 lower_string_(lower_string), |
| 581 lower_terms_(lower_terms), | 524 lower_terms_(lower_terms), |
| 582 now_(now) {} | 525 now_(now) {} |
| 583 | 526 |
| 584 URLIndexPrivateData::AddHistoryMatch::~AddHistoryMatch() {} | 527 URLIndexPrivateData::AddHistoryMatch::~AddHistoryMatch() {} |
| 585 | 528 |
| 586 void URLIndexPrivateData::AddHistoryMatch::operator()( | 529 void URLIndexPrivateData::AddHistoryMatch::operator()( |
| 587 const HistoryID history_id) { | 530 const HistoryID history_id) { |
| 588 HistoryInfoMap::const_iterator hist_pos = | 531 HistoryInfoMap::const_iterator hist_pos = |
| 589 private_data_.history_info_map_.find(history_id); | 532 private_data_.history_info_map_.find(history_id); |
| 590 // Note that a history_id may be present in the word_id_history_map_ yet not | |
| 591 // be found in the history_info_map_. This occurs when an item has been | |
| 592 // deleted by the user or the item no longer qualifies as a quick result. | |
| 593 if (hist_pos != private_data_.history_info_map_.end()) { | 533 if (hist_pos != private_data_.history_info_map_.end()) { |
| 594 const URLRow& hist_item = hist_pos->second; | 534 const URLRow& hist_item = hist_pos->second; |
| 595 WordStartsMap::const_iterator starts_pos = | 535 WordStartsMap::const_iterator starts_pos = |
| 596 private_data_.word_starts_map_.find(history_id); | 536 private_data_.word_starts_map_.find(history_id); |
| 597 DCHECK(starts_pos != private_data_.word_starts_map_.end()); | 537 DCHECK(starts_pos != private_data_.word_starts_map_.end()); |
| 598 ScoredHistoryMatch match(private_data_.ScoredMatchForURL( | 538 ScoredHistoryMatch match(hist_item, lower_string_, lower_terms_, |
| 599 hist_item, lower_string_, lower_terms_, starts_pos->second, now_)); | 539 starts_pos->second, now_); |
| 600 if (match.raw_score > 0) | 540 if (match.raw_score > 0) |
| 601 scored_matches_.push_back(match); | 541 scored_matches_.push_back(match); |
| 602 } | 542 } |
| 603 } | 543 } |
| 604 | 544 |
| 605 // TODO(mrossetti): This can be made a ctor for ScoredHistoryMatch. | |
| 606 ScoredHistoryMatch URLIndexPrivateData::ScoredMatchForURL( | |
| 607 const URLRow& row, | |
| 608 const string16& lower_string, | |
| 609 const String16Vector& terms, | |
| 610 const RowWordStarts& word_starts, | |
| 611 const base::Time now) const { | |
| 612 ScoredHistoryMatch match(row); | |
| 613 GURL gurl = row.url(); | |
| 614 if (!gurl.is_valid()) | |
| 615 return match; | |
| 616 | |
| 617 // Figure out where each search term appears in the URL and/or page title | |
| 618 // so that we can score as well as provide autocomplete highlighting. | |
| 619 string16 url = base::i18n::ToLower(UTF8ToUTF16(gurl.spec())); | |
| 620 string16 title = base::i18n::ToLower(row.title()); | |
| 621 int term_num = 0; | |
| 622 for (String16Vector::const_iterator iter = terms.begin(); iter != terms.end(); | |
| 623 ++iter, ++term_num) { | |
| 624 string16 term = *iter; | |
| 625 TermMatches url_term_matches = MatchTermInString(term, url, term_num); | |
| 626 TermMatches title_term_matches = MatchTermInString(term, title, term_num); | |
| 627 if (url_term_matches.empty() && title_term_matches.empty()) | |
| 628 return match; // A term was not found in either URL or title - reject. | |
| 629 match.url_matches.insert(match.url_matches.end(), url_term_matches.begin(), | |
| 630 url_term_matches.end()); | |
| 631 match.title_matches.insert(match.title_matches.end(), | |
| 632 title_term_matches.begin(), | |
| 633 title_term_matches.end()); | |
| 634 } | |
| 635 | |
| 636 // Sort matches by offset and eliminate any which overlap. | |
| 637 // TODO(mpearson): Investigate whether this has any meaningful | |
| 638 // effect on scoring. (It's necessary at some point: removing | |
| 639 // overlaps and sorting is needed to decide what to highlight in the | |
| 640 // suggestion string. But this sort and de-overlap doesn't have to | |
| 641 // be done before scoring.) | |
| 642 match.url_matches = SortAndDeoverlapMatches(match.url_matches); | |
| 643 match.title_matches = SortAndDeoverlapMatches(match.title_matches); | |
| 644 | |
| 645 // We can inline autocomplete a result if: | |
| 646 // 1) there is only one search term | |
| 647 // 2) AND EITHER: | |
| 648 // 2a) the first match starts at the beginning of the candidate URL, OR | |
| 649 // 2b) the candidate URL starts with one of the standard URL prefixes with | |
| 650 // the URL match immediately following that prefix. | |
| 651 // 3) AND the search string does not end in whitespace (making it look to | |
| 652 // the IMUI as though there is a single search term when actually there | |
| 653 // is a second, empty term). | |
| 654 match.can_inline = !match.url_matches.empty() && terms.size() == 1 && | |
| 655 (match.url_matches[0].offset == 0 || | |
| 656 URLPrefix::IsURLPrefix(url.substr(0, match.url_matches[0].offset))) && | |
| 657 !IsWhitespace(*(lower_string.rbegin())); | |
| 658 match.match_in_scheme = match.can_inline && match.url_matches[0].offset == 0; | |
| 659 | |
| 660 if (use_new_scoring_) { | |
| 661 const float topicality_score = GetTopicalityScore( | |
| 662 terms.size(), url, match.url_matches, match.title_matches, word_starts); | |
| 663 const float recency_score = GetRecencyScore( | |
| 664 (now - row.last_visit()).InDays()); | |
| 665 const float popularity_score = GetPopularityScore( | |
| 666 row.typed_count(), row.visit_count()); | |
| 667 | |
| 668 // Combine recency, popularity, and topicality scores into one. | |
| 669 // Example of how this functions: Suppose the omnibox has one | |
| 670 // input term. Suppose we have a URL that has 4 typed visits with | |
| 671 // the most recent being within a day and the omnibox input term | |
| 672 // has a single URL hostname hit at a word boundary. Then this | |
| 673 // URL will score 1400 ( = 4 * 350), which is exactly the value of | |
| 674 // search what you type. That is, it's the boundary of what might | |
| 675 // end up being inlined. | |
| 676 const float raw_score = | |
| 677 350 * topicality_score * recency_score * popularity_score; | |
| 678 match.raw_score = | |
| 679 (raw_score <= kint32max) ? static_cast<int>(raw_score) : kint32max; | |
| 680 } else { // "old" scoring | |
| 681 // Get partial scores based on term matching. Note that the score for | |
| 682 // each of the URL and title are adjusted by the fraction of the | |
| 683 // terms appearing in each. | |
| 684 int url_score = ScoreComponentForMatches(match.url_matches, url.length()) * | |
| 685 std::min(match.url_matches.size(), terms.size()) / terms.size(); | |
| 686 int title_score = | |
| 687 ScoreComponentForMatches(match.title_matches, title.length()) * | |
| 688 std::min(match.title_matches.size(), terms.size()) / terms.size(); | |
| 689 // Arbitrarily pick the best. | |
| 690 // TODO(mrossetti): It might make sense that a term which appears | |
| 691 // in both the URL and the Title should boost the score a bit. | |
| 692 int term_score = std::max(url_score, title_score); | |
| 693 if (term_score == 0) | |
| 694 return match; | |
| 695 | |
| 696 // Determine scoring factors for the recency of visit, visit count | |
| 697 // and typed count attributes of the URLRow. | |
| 698 const int kDaysAgoLevel[] = { 1, 10, 20, 30 }; | |
| 699 int days_ago_value = ScoreForValue( | |
| 700 (now - row.last_visit()).InDays(), kDaysAgoLevel); | |
| 701 const int kVisitCountLevel[] = { 50, 30, 10, 5 }; | |
| 702 int visit_count_value = ScoreForValue(row.visit_count(), kVisitCountLevel); | |
| 703 const int kTypedCountLevel[] = { 50, 30, 10, 5 }; | |
| 704 int typed_count_value = ScoreForValue(row.typed_count(), kTypedCountLevel); | |
| 705 | |
| 706 // The final raw score is calculated by: | |
| 707 // - multiplying each factor by a 'relevance' | |
| 708 // - calculating the average. | |
| 709 // Note that visit_count is reduced by typed_count because both are bumped | |
| 710 // when a typed URL is recorded thus giving visit_count too much weight. | |
| 711 const int kTermScoreRelevance = 4; | |
| 712 const int kDaysAgoRelevance = 2; | |
| 713 const int kVisitCountRelevance = 2; | |
| 714 const int kTypedCountRelevance = 5; | |
| 715 int effective_visit_count_value = | |
| 716 std::max(0, visit_count_value - typed_count_value); | |
| 717 match.raw_score = term_score * kTermScoreRelevance + | |
| 718 days_ago_value * kDaysAgoRelevance + | |
| 719 effective_visit_count_value * kVisitCountRelevance + | |
| 720 typed_count_value * kTypedCountRelevance; | |
| 721 match.raw_score /= (kTermScoreRelevance + kDaysAgoRelevance + | |
| 722 kVisitCountRelevance + kTypedCountRelevance); | |
| 723 match.raw_score = std::min(kMaxTotalScore, match.raw_score); | |
| 724 } | |
| 725 return match; | |
| 726 } | |
| 727 | |
| 728 int URLIndexPrivateData::ScoreComponentForMatches(const TermMatches& matches, | |
| 729 size_t max_length) { | |
| 730 if (matches.empty()) | |
| 731 return 0; | |
| 732 | |
| 733 // Score component for whether the input terms (if more than one) were found | |
| 734 // in the same order in the match. Start with kOrderMaxValue points divided | |
| 735 // equally among (number of terms - 1); then discount each of those terms that | |
| 736 // is out-of-order in the match. | |
| 737 const int kOrderMaxValue = 1000; | |
| 738 int order_value = kOrderMaxValue; | |
| 739 if (matches.size() > 1) { | |
| 740 int max_possible_out_of_order = matches.size() - 1; | |
| 741 int out_of_order = 0; | |
| 742 for (size_t i = 1; i < matches.size(); ++i) { | |
| 743 if (matches[i - 1].term_num > matches[i].term_num) | |
| 744 ++out_of_order; | |
| 745 } | |
| 746 order_value = (max_possible_out_of_order - out_of_order) * kOrderMaxValue / | |
| 747 max_possible_out_of_order; | |
| 748 } | |
| 749 | |
| 750 // Score component for how early in the match string the first search term | |
| 751 // appears. Start with kStartMaxValue points and discount by | |
| 752 // kStartMaxValue/kMaxSignificantChars points for each character later than | |
| 753 // the first at which the term begins. No points are earned if the start of | |
| 754 // the match occurs at or after kMaxSignificantChars. | |
| 755 const int kStartMaxValue = 1000; | |
| 756 int start_value = (kMaxSignificantChars - | |
| 757 std::min(kMaxSignificantChars, matches[0].offset)) * kStartMaxValue / | |
| 758 kMaxSignificantChars; | |
| 759 | |
| 760 // Score component for how much of the matched string the input terms cover. | |
| 761 // kCompleteMaxValue points times the fraction of the URL/page title string | |
| 762 // that was matched. | |
| 763 size_t term_length_total = std::accumulate(matches.begin(), matches.end(), | |
| 764 0, AccumulateMatchLength); | |
| 765 const size_t kMaxSignificantLength = 50; | |
| 766 size_t max_significant_length = | |
| 767 std::min(max_length, std::max(term_length_total, kMaxSignificantLength)); | |
| 768 const int kCompleteMaxValue = 1000; | |
| 769 int complete_value = | |
| 770 term_length_total * kCompleteMaxValue / max_significant_length; | |
| 771 | |
| 772 const int kOrderRelevance = 1; | |
| 773 const int kStartRelevance = 6; | |
| 774 const int kCompleteRelevance = 3; | |
| 775 int raw_score = order_value * kOrderRelevance + | |
| 776 start_value * kStartRelevance + | |
| 777 complete_value * kCompleteRelevance; | |
| 778 raw_score /= (kOrderRelevance + kStartRelevance + kCompleteRelevance); | |
| 779 | |
| 780 // Scale the raw score into a single score component in the same manner as | |
| 781 // used in ScoredMatchForURL(). | |
| 782 const int kTermScoreLevel[] = { 1000, 750, 500, 200 }; | |
| 783 return ScoreForValue(raw_score, kTermScoreLevel); | |
| 784 } | |
| 785 | |
| 786 // static | |
| 787 float URLIndexPrivateData::GetTopicalityScore( | |
| 788 const int num_terms, | |
| 789 const string16& url, | |
| 790 const TermMatches& url_matches, | |
| 791 const TermMatches& title_matches, | |
| 792 const RowWordStarts& word_starts) { | |
| 793 // Because the below thread is not thread safe, we check that we're | |
| 794 // only calling it from one thread: the UI thread. Specifically, | |
| 795 // we check "if we've heard of the UI thread then we'd better | |
| 796 // be on it." The first part is necessary so unit tests pass. (Many | |
| 797 // unit tests don't set up the threading naming system; hence | |
| 798 // CurrentlyOn(UI thread) will fail.) | |
| 799 DCHECK( | |
| 800 !content::BrowserThread::IsWellKnownThread(content::BrowserThread::UI) || | |
| 801 content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | |
| 802 if (raw_term_score_to_topicality_score_ == NULL) { | |
| 803 raw_term_score_to_topicality_score_ = new float[kMaxRawTermScore]; | |
| 804 FillInTermScoreToTopicalityScoreArray(); | |
| 805 } | |
| 806 // A vector that accumulates per-term scores. The strongest match--a | |
| 807 // match in the hostname at a word boundary--is worth 10 points. | |
| 808 // Everything else is less. In general, a match that's not at a word | |
| 809 // boundary is worth about 1/4th or 1/5th of a match at the word boundary | |
| 810 // in the same part of the URL/title. | |
| 811 std::vector<int> term_scores(num_terms, 0); | |
| 812 std::vector<size_t>::const_iterator next_word_starts = | |
| 813 word_starts.url_word_starts_.begin(); | |
| 814 std::vector<size_t>::const_iterator end_word_starts = | |
| 815 word_starts.url_word_starts_.end(); | |
| 816 const size_t question_mark_pos = url.find('?'); | |
| 817 const size_t colon_pos = url.find(':'); | |
| 818 // The + 3 skips the // that probably appears in the protocol | |
| 819 // after the colon. If the protocol doesn't have two slashes after | |
| 820 // the colon, that's okay--all this ends up doing is starting our | |
| 821 // search for the next / a few characters into the hostname. The | |
| 822 // only times this can cause problems is if we have a protocol without | |
| 823 // a // after the colon and the hostname is only one or two characters. | |
| 824 // This isn't worth worrying about. | |
| 825 const size_t end_of_hostname_pos = (colon_pos != std::string::npos) ? | |
| 826 url.find('/', colon_pos + 3) : url.find('/'); | |
| 827 // Loop through all URL matches and score them appropriately. | |
| 828 for (TermMatches::const_iterator iter = url_matches.begin(); | |
| 829 iter != url_matches.end(); ++iter) { | |
| 830 // Advance next_word_starts until it's >= the position of the term | |
| 831 // we're considering. | |
| 832 while ((next_word_starts != end_word_starts) && | |
| 833 (*next_word_starts < iter->offset)) { | |
| 834 ++next_word_starts; | |
| 835 } | |
| 836 const bool at_word_boundary = (next_word_starts != end_word_starts) && | |
| 837 (*next_word_starts == iter->offset); | |
| 838 if ((question_mark_pos != std::string::npos) && | |
| 839 (iter->offset > question_mark_pos)) { | |
| 840 // match in CGI ?... fragment | |
| 841 term_scores[iter->term_num] += at_word_boundary ? 5 : 0; | |
| 842 } else if ((end_of_hostname_pos != std::string::npos) && | |
| 843 (iter->offset > end_of_hostname_pos)) { | |
| 844 // match in path | |
| 845 term_scores[iter->term_num] += at_word_boundary ? 8 : 1; | |
| 846 } else if ((colon_pos == std::string::npos) || | |
| 847 (iter->offset > colon_pos)) { | |
| 848 // match in hostname | |
| 849 term_scores[iter->term_num] += at_word_boundary ? 10 : 2; | |
| 850 } // else: match in protocol. Do not count this match for scoring. | |
| 851 } | |
| 852 // Now do the analogous loop over all matches in the title. | |
| 853 next_word_starts = word_starts.title_word_starts_.begin(); | |
| 854 end_word_starts = word_starts.title_word_starts_.end(); | |
| 855 int word_num = 0; | |
| 856 for (TermMatches::const_iterator iter = title_matches.begin(); | |
| 857 iter != title_matches.end(); ++iter) { | |
| 858 // Advance next_word_starts until it's >= the position of the term | |
| 859 // we're considering. | |
| 860 while ((next_word_starts != end_word_starts) && | |
| 861 (*next_word_starts < iter->offset)) { | |
| 862 ++next_word_starts; | |
| 863 ++word_num; | |
| 864 } | |
| 865 if (word_num >= 10) break; // only count the first ten words | |
| 866 const bool at_word_boundary = (next_word_starts != end_word_starts) && | |
| 867 (*next_word_starts == iter->offset); | |
| 868 term_scores[iter->term_num] += at_word_boundary ? 8 : 2; | |
| 869 } | |
| 870 // TODO(mpearson): Restore logic for penalizing out-of-order matches. | |
| 871 // (Perhaps discount them by 0.8?) | |
| 872 // TODO(mpearson): Consider: if the earliest match occurs late in the string, | |
| 873 // should we discount it? | |
| 874 // TODO(mpearson): Consider: do we want to score based on how much of the | |
| 875 // input string the input covers? (I'm leaning toward no.) | |
| 876 | |
| 877 // Compute the topicality_score as the sum of transformed term_scores. | |
| 878 float topicality_score = 0; | |
| 879 for (size_t i = 0; i < term_scores.size(); ++i) { | |
| 880 topicality_score += raw_term_score_to_topicality_score_[ | |
| 881 (term_scores[i] >= kMaxRawTermScore)? kMaxRawTermScore - 1: | |
| 882 term_scores[i]]; | |
| 883 } | |
| 884 // TODO(mpearson): If there are multiple terms, consider taking the | |
| 885 // geometric mean of per-term scores rather than sum as we're doing now | |
| 886 // (which is equivalent to the arthimatic mean). | |
| 887 | |
| 888 return topicality_score; | |
| 889 } | |
| 890 | |
| 891 // static | |
| 892 float* URLIndexPrivateData::raw_term_score_to_topicality_score_ = NULL; | |
| 893 | |
| 894 // static | |
| 895 void URLIndexPrivateData::FillInTermScoreToTopicalityScoreArray() { | |
| 896 for (int term_score = 0; term_score < kMaxRawTermScore; ++term_score) { | |
| 897 float topicality_score; | |
| 898 if (term_score < 10) { | |
| 899 // If the term scores less than 10 points (no full-credit hit, or | |
| 900 // no combination of hits that score that well), then the topicality | |
| 901 // score is linear in the term score. | |
| 902 topicality_score = 0.1 * term_score; | |
| 903 } else { | |
| 904 // For term scores of at least ten points, pass them through a log | |
| 905 // function so a score of 10 points gets a 1.0 (to meet up exactly | |
| 906 // with the linear component) and increases logarithmically until | |
| 907 // maxing out at 30 points, with computes to a score around 2.1. | |
| 908 topicality_score = (1.0 + 2.25 * log10(0.1 * | |
| 909 ((term_score <= 30) ? term_score : 30))); | |
| 910 } | |
| 911 raw_term_score_to_topicality_score_[term_score] = topicality_score; | |
| 912 } | |
| 913 } | |
| 914 | |
| 915 // static | |
| 916 float* URLIndexPrivateData::days_ago_to_recency_score_ = NULL; | |
| 917 | |
| 918 // static | |
| 919 float URLIndexPrivateData::GetRecencyScore(int last_visit_days_ago) { | |
| 920 // Because the below thread is not thread safe, we check that we're | |
| 921 // only calling it from one thread: the UI thread. Specifically, | |
| 922 // we check "if we've heard of the UI thread then we'd better | |
| 923 // be on it." The first part is necessary so unit tests pass. (Many | |
| 924 // unit tests don't set up the threading naming system; hence | |
| 925 // CurrentlyOn(UI thread) will fail.) | |
| 926 DCHECK( | |
| 927 !content::BrowserThread::IsWellKnownThread(content::BrowserThread::UI) || | |
| 928 content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | |
| 929 if (days_ago_to_recency_score_ == NULL) { | |
| 930 days_ago_to_recency_score_ = new float[kDaysToPrecomputeRecencyScoresFor]; | |
| 931 FillInDaysAgoToRecencyScoreArray(); | |
| 932 } | |
| 933 // Lookup the score in days_ago_to_recency_score_, treating | |
| 934 // everything older than what we've precomputed as the oldest thing | |
| 935 // we've precomputed. The std::max is to protect against corruption | |
| 936 // in the database (in case last_visit_days_ago is negative). | |
| 937 return days_ago_to_recency_score_[ | |
| 938 std::max( | |
| 939 std::min(last_visit_days_ago, kDaysToPrecomputeRecencyScoresFor - 1), | |
| 940 0)]; | |
| 941 } | |
| 942 | |
| 943 void URLIndexPrivateData::FillInDaysAgoToRecencyScoreArray() { | |
| 944 for (int days_ago = 0; days_ago < kDaysToPrecomputeRecencyScoresFor; | |
| 945 days_ago++) { | |
| 946 int unnormalized_recency_score; | |
| 947 if (days_ago <= 1) { | |
| 948 unnormalized_recency_score = 100; | |
| 949 } else if (days_ago <= 7) { | |
| 950 // Linearly extrapolate between 1 and 7 days so 7 days has a score of 70. | |
| 951 unnormalized_recency_score = 70 + (7 - days_ago) * (100 - 70) / (7 - 1); | |
| 952 } else if (days_ago <= 30) { | |
| 953 // Linearly extrapolate between 7 and 30 days so 30 days has a score | |
| 954 // of 50. | |
| 955 unnormalized_recency_score = 50 + (30 - days_ago) * (70 - 50) / (30 - 7); | |
| 956 } else if (days_ago <= 90) { | |
| 957 // Linearly extrapolate between 30 and 90 days so 90 days has a score | |
| 958 // of 20. | |
| 959 unnormalized_recency_score = 20 + (90 - days_ago) * (50 - 20) / (90 - 30); | |
| 960 } else if (days_ago <= 365) { | |
| 961 // Linearly extrapolate between 90 and 365 days so 365 days has a score | |
| 962 // of 10. | |
| 963 unnormalized_recency_score = | |
| 964 10 + (365 - days_ago) * (20 - 10) / (365 - 90); | |
| 965 } else { | |
| 966 // greater than a year. | |
| 967 unnormalized_recency_score = 10; | |
| 968 } | |
| 969 days_ago_to_recency_score_[days_ago] = unnormalized_recency_score / 100.0; | |
| 970 if (days_ago > 0) { | |
| 971 DCHECK_LE(days_ago_to_recency_score_[days_ago], | |
| 972 days_ago_to_recency_score_[days_ago - 1]); | |
| 973 } | |
| 974 } | |
| 975 } | |
| 976 | |
| 977 // static | |
| 978 float URLIndexPrivateData::GetPopularityScore(int typed_count, | |
| 979 int visit_count) { | |
| 980 // The max()s are to guard against database corruption. | |
| 981 return (std::max(typed_count, 0) * 5.0 + std::max(visit_count, 0) * 3.0) / | |
| 982 (5.0 + 3.0); | |
| 983 } | |
| 984 | |
| 985 void URLIndexPrivateData::ResetSearchTermCache() { | 545 void URLIndexPrivateData::ResetSearchTermCache() { |
| 986 for (SearchTermCacheMap::iterator iter = search_term_cache_.begin(); | 546 for (SearchTermCacheMap::iterator iter = search_term_cache_.begin(); |
| 987 iter != search_term_cache_.end(); ++iter) | 547 iter != search_term_cache_.end(); ++iter) |
| 988 iter->second.used_ = false; | 548 iter->second.used_ = false; |
| 989 } | 549 } |
| 990 | 550 |
| 991 HistoryIDSet URLIndexPrivateData::HistoryIDSetFromWords( | 551 HistoryIDSet URLIndexPrivateData::HistoryIDSetFromWords( |
| 992 const String16Vector& unsorted_words) { | 552 const String16Vector& unsorted_words) { |
| 993 // Break the terms down into individual terms (words), get the candidate | 553 // Break the terms down into individual terms (words), get the candidate |
| 994 // set for each term, and intersect each to get a final candidate list. | 554 // set for each term, and intersect each to get a final candidate list. |
| (...skipping 588 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1583 } | 1143 } |
| 1584 | 1144 |
| 1585 // static | 1145 // static |
| 1586 bool URLIndexPrivateData::URLSchemeIsWhitelisted( | 1146 bool URLIndexPrivateData::URLSchemeIsWhitelisted( |
| 1587 const GURL& gurl, | 1147 const GURL& gurl, |
| 1588 const std::set<std::string>& whitelist) { | 1148 const std::set<std::string>& whitelist) { |
| 1589 return whitelist.find(gurl.scheme()) != whitelist.end(); | 1149 return whitelist.find(gurl.scheme()) != whitelist.end(); |
| 1590 } | 1150 } |
| 1591 | 1151 |
| 1592 } // namespace history | 1152 } // namespace history |
| OLD | NEW |