• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
3 // found in the LICENSE file.
4 
5 #include "chrome/browser/autocomplete/history_contents_provider.h"
6 
7 #include "base/callback.h"
8 #include "base/metrics/histogram.h"
9 #include "base/string_util.h"
10 #include "base/utf_string_conversions.h"
11 #include "chrome/browser/autocomplete/autocomplete_match.h"
12 #include "chrome/browser/bookmarks/bookmark_model.h"
13 #include "chrome/browser/bookmarks/bookmark_utils.h"
14 #include "chrome/browser/history/query_parser.h"
15 #include "chrome/browser/profiles/profile.h"
16 #include "chrome/common/url_constants.h"
17 #include "googleurl/src/url_util.h"
18 #include "net/base/net_util.h"
19 
20 using base::TimeTicks;
21 
22 namespace {
23 
24 // Number of days to search for full text results. The longer this is, the more
25 // time it will take.
26 const int kDaysToSearch = 30;
27 
28 // When processing the results from the history query, this structure points to
29 // a single result. It allows the results to be sorted and processed without
30 // modifying the larger and slower results structure.
31 struct MatchReference {
MatchReference__anon0e05c9280111::MatchReference32   MatchReference(const history::URLResult* result, int relevance)
33       : result(result),
34         relevance(relevance) {
35   }
36 
37   const history::URLResult* result;
38   int relevance;  // Score of relevance computed by CalculateRelevance.
39 };
40 
41 // This is a > operator for MatchReference.
CompareMatchRelevance(const MatchReference & a,const MatchReference & b)42 bool CompareMatchRelevance(const MatchReference& a, const MatchReference& b) {
43   if (a.relevance != b.relevance)
44     return a.relevance > b.relevance;
45 
46   // Want results in reverse-chronological order all else being equal.
47   return a.result->last_visit() > b.result->last_visit();
48 }
49 
50 }  // namespace
51 
52 using history::HistoryDatabase;
53 
HistoryContentsProvider(ACProviderListener * listener,Profile * profile)54 HistoryContentsProvider::HistoryContentsProvider(ACProviderListener* listener,
55                                                  Profile* profile)
56     : HistoryProvider(listener, profile, "HistoryContents"),
57       star_title_count_(0),
58       star_contents_count_(0),
59       title_count_(0),
60       contents_count_(0),
61       input_type_(AutocompleteInput::INVALID),
62       trim_http_(false),
63       have_results_(false) {
64 }
65 
Start(const AutocompleteInput & input,bool minimal_changes)66 void HistoryContentsProvider::Start(const AutocompleteInput& input,
67                                     bool minimal_changes) {
68   matches_.clear();
69 
70   if (input.text().empty() || (input.type() == AutocompleteInput::INVALID) ||
71       !profile_ ||
72       // The history service or bookmark bar model must exist.
73       !(profile_->GetHistoryService(Profile::EXPLICIT_ACCESS) ||
74         profile_->GetBookmarkModel())) {
75     Stop();
76     return;
77   }
78 
79   // TODO(pkasting): http://b/888148 We disallow URL input and "URL-like" input
80   // (REQUESTED_URL or UNKNOWN with dots) because we get poor results for it,
81   // but we could get better results if we did better tokenizing instead.
82   if ((input.type() == AutocompleteInput::URL) ||
83       (((input.type() == AutocompleteInput::REQUESTED_URL) ||
84         (input.type() == AutocompleteInput::UNKNOWN)) &&
85        (input.text().find('.') != string16::npos))) {
86     Stop();
87     return;
88   }
89 
90   if (input.matches_requested() == AutocompleteInput::BEST_MATCH) {
91     // None of our results are applicable for best match.
92     Stop();
93     return;
94   }
95 
96   // Change input type so matches will be marked up properly.
97   input_type_ = input.type();
98   trim_http_ = !HasHTTPScheme(input.text());
99 
100   // Decide what to do about any previous query/results.
101   if (!minimal_changes) {
102     // Any in-progress request is irrelevant, cancel it.
103     Stop();
104   } else if (have_results_) {
105     // We finished the previous query and still have its results.  Mark them up
106     // again for the new input.
107     ConvertResults();
108     return;
109   } else if (!done_) {
110     // We're still running the previous query on the HistoryService.  If we're
111     // allowed to keep running it, do so, and when it finishes, its results will
112     // get marked up for this new input.  In synchronous_only mode, cancel the
113     // history query.
114     if (input.matches_requested() != AutocompleteInput::ALL_MATCHES) {
115       done_ = true;
116       request_consumer_.CancelAllRequests();
117     }
118     ConvertResults();
119     return;
120   }
121 
122   if (!results_.empty()) {
123     // Clear the results. We swap in an empty one as the easy way to clear it.
124     history::QueryResults empty_results;
125     results_.Swap(&empty_results);
126   }
127 
128   // Querying bookmarks is synchronous, so we always do it.
129   QueryBookmarks(input);
130 
131   // Convert the bookmark results.
132   ConvertResults();
133 
134   if (input.matches_requested() == AutocompleteInput::ALL_MATCHES) {
135     HistoryService* history =
136         profile_->GetHistoryService(Profile::EXPLICIT_ACCESS);
137     if (history) {
138       done_ = false;
139 
140       history::QueryOptions options;
141       options.SetRecentDayRange(kDaysToSearch);
142       options.max_count = kMaxMatches;
143       history->QueryHistory(input.text(), options,
144           &request_consumer_,
145           NewCallback(this, &HistoryContentsProvider::QueryComplete));
146     }
147   }
148 }
149 
Stop()150 void HistoryContentsProvider::Stop() {
151   done_ = true;
152   request_consumer_.CancelAllRequests();
153 
154   // Clear the results. We swap in an empty one as the easy way to clear it.
155   history::QueryResults empty_results;
156   results_.Swap(&empty_results);
157   have_results_ = false;
158 }
159 
~HistoryContentsProvider()160 HistoryContentsProvider::~HistoryContentsProvider() {
161 }
162 
QueryComplete(HistoryService::Handle handle,history::QueryResults * results)163 void HistoryContentsProvider::QueryComplete(HistoryService::Handle handle,
164                                             history::QueryResults* results) {
165   results_.AppendResultsBySwapping(results, true);
166   have_results_ = true;
167   ConvertResults();
168 
169   done_ = true;
170   if (listener_)
171     listener_->OnProviderUpdate(!matches_.empty());
172 }
173 
ConvertResults()174 void HistoryContentsProvider::ConvertResults() {
175   // Reset the relevance counters so that result relevance won't vary on
176   // subsequent passes of ConvertResults.
177   star_title_count_ = star_contents_count_ = title_count_ = contents_count_ = 0;
178 
179   // Make the result references and score the results.
180   std::vector<MatchReference> result_refs;
181   result_refs.reserve(results_.size());
182 
183   // Results are sorted in decreasing order so we run the loop backwards so that
184   // the relevance increment favors the higher ranked results.
185   for (std::vector<history::URLResult*>::const_reverse_iterator i =
186        results_.rbegin(); i != results_.rend(); ++i) {
187     history::URLResult* result = *i;
188     MatchReference ref(result, CalculateRelevance(*result));
189     result_refs.push_back(ref);
190   }
191 
192   // Get the top matches and add them.
193   size_t max_for_provider = std::min(kMaxMatches, result_refs.size());
194   std::partial_sort(result_refs.begin(), result_refs.begin() + max_for_provider,
195                     result_refs.end(), &CompareMatchRelevance);
196   matches_.clear();
197   for (size_t i = 0; i < max_for_provider; i++) {
198     matches_.push_back(ResultToMatch(*result_refs[i].result,
199                                      result_refs[i].relevance));
200   }
201 }
202 
MatchInTitle(const history::URLResult & result)203 static bool MatchInTitle(const history::URLResult& result) {
204   return !result.title_match_positions().empty();
205 }
206 
ResultToMatch(const history::URLResult & result,int score)207 AutocompleteMatch HistoryContentsProvider::ResultToMatch(
208     const history::URLResult& result,
209     int score) {
210   // TODO(sky): if matched title highlight matching words in title.
211   // Also show star in popup.
212   AutocompleteMatch match(this, score, true, MatchInTitle(result) ?
213       AutocompleteMatch::HISTORY_TITLE : AutocompleteMatch::HISTORY_BODY);
214   match.contents = StringForURLDisplay(result.url(), true, trim_http_);
215   match.fill_into_edit =
216       AutocompleteInput::FormattedStringWithEquivalentMeaning(result.url(),
217                                                               match.contents);
218   match.destination_url = result.url();
219   match.contents_class.push_back(
220       ACMatchClassification(0, ACMatchClassification::URL));
221   match.description = result.title();
222   match.starred =
223       (profile_->GetBookmarkModel() &&
224        profile_->GetBookmarkModel()->IsBookmarked(result.url()));
225 
226   ClassifyDescription(result, &match);
227   return match;
228 }
229 
ClassifyDescription(const history::URLResult & result,AutocompleteMatch * match) const230 void HistoryContentsProvider::ClassifyDescription(
231     const history::URLResult& result,
232     AutocompleteMatch* match) const {
233   const Snippet::MatchPositions& title_matches = result.title_match_positions();
234 
235   size_t offset = 0;
236   if (!title_matches.empty()) {
237     // Classify matches in the title.
238     for (Snippet::MatchPositions::const_iterator i = title_matches.begin();
239          i != title_matches.end(); ++i) {
240       if (i->first != offset) {
241         match->description_class.push_back(
242             ACMatchClassification(offset, ACMatchClassification::NONE));
243       }
244       match->description_class.push_back(
245             ACMatchClassification(i->first, ACMatchClassification::MATCH));
246       offset = i->second;
247     }
248   }
249   if (offset != result.title().size()) {
250     match->description_class.push_back(
251         ACMatchClassification(offset, ACMatchClassification::NONE));
252   }
253 }
254 
CalculateRelevance(const history::URLResult & result)255 int HistoryContentsProvider::CalculateRelevance(
256     const history::URLResult& result) {
257   const bool in_title = MatchInTitle(result);
258   if (!profile_->GetBookmarkModel() ||
259       !profile_->GetBookmarkModel()->IsBookmarked(result.url()))
260     return in_title ? (700 + title_count_++) : (500 + contents_count_++);
261   return in_title ?
262       (1000 + star_title_count_++) : (550 + star_contents_count_++);
263 }
264 
QueryBookmarks(const AutocompleteInput & input)265 void HistoryContentsProvider::QueryBookmarks(const AutocompleteInput& input) {
266   BookmarkModel* bookmark_model = profile_->GetBookmarkModel();
267   if (!bookmark_model)
268     return;
269 
270   DCHECK(results_.empty());
271 
272   TimeTicks start_time = TimeTicks::Now();
273   std::vector<bookmark_utils::TitleMatch> matches;
274   bookmark_model->GetBookmarksWithTitlesMatching(input.text(),
275                                                  kMaxMatches, &matches);
276   for (size_t i = 0; i < matches.size(); ++i)
277     AddBookmarkTitleMatchToResults(matches[i]);
278   UMA_HISTOGRAM_TIMES("Omnibox.QueryBookmarksTime",
279                       TimeTicks::Now() - start_time);
280 }
281 
AddBookmarkTitleMatchToResults(const bookmark_utils::TitleMatch & match)282 void HistoryContentsProvider::AddBookmarkTitleMatchToResults(
283     const bookmark_utils::TitleMatch& match) {
284   history::URLResult url_result(match.node->GetURL(), match.match_positions);
285   url_result.set_title(match.node->GetTitle());
286   results_.AppendURLBySwapping(&url_result);
287 }
288