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 "components/omnibox/browser/bookmark_provider.h" | 5 #include "components/omnibox/browser/bookmark_provider.h" |
6 | 6 |
7 #include <algorithm> | 7 #include <algorithm> |
8 #include <functional> | 8 #include <functional> |
9 #include <vector> | 9 #include <vector> |
10 | 10 |
11 #include "base/macros.h" | 11 #include "base/macros.h" |
12 #include "base/strings/string_util.h" | 12 #include "base/strings/string_util.h" |
13 #include "base/strings/utf_string_conversions.h" | 13 #include "base/strings/utf_string_conversions.h" |
14 #include "base/trace_event/trace_event.h" | 14 #include "base/trace_event/trace_event.h" |
15 #include "components/bookmarks/browser/bookmark_model.h" | 15 #include "components/bookmarks/browser/bookmark_model.h" |
16 #include "components/bookmarks/browser/titled_url_match.h" | 16 #include "components/bookmarks/browser/titled_url_match.h" |
17 #include "components/metrics/proto/omnibox_input_type.pb.h" | 17 #include "components/metrics/proto/omnibox_input_type.pb.h" |
18 #include "components/omnibox/browser/autocomplete_provider_client.h" | 18 #include "components/omnibox/browser/autocomplete_provider_client.h" |
19 #include "components/omnibox/browser/autocomplete_provider_utils.h" | |
19 #include "components/omnibox/browser/autocomplete_result.h" | 20 #include "components/omnibox/browser/autocomplete_result.h" |
20 #include "components/omnibox/browser/history_provider.h" | |
21 #include "components/omnibox/browser/url_prefix.h" | |
22 #include "components/prefs/pref_service.h" | 21 #include "components/prefs/pref_service.h" |
23 #include "components/url_formatter/url_formatter.h" | |
24 #include "url/url_constants.h" | 22 #include "url/url_constants.h" |
25 | 23 |
26 using bookmarks::BookmarkNode; | 24 using bookmarks::BookmarkNode; |
27 using bookmarks::TitledUrlMatch; | 25 using bookmarks::TitledUrlMatch; |
28 using TitledUrlMatches = std::vector<TitledUrlMatch>; | 26 using TitledUrlMatches = std::vector<TitledUrlMatch>; |
29 | 27 |
30 namespace { | |
31 | |
32 // Removes leading spaces from |title| before displaying, otherwise it looks | |
33 // funny. In the process, corrects |title_match_positions| so the correct | |
34 // characters are highlighted. | |
35 void CorrectTitleAndMatchPositions( | |
36 base::string16* title, | |
37 TitledUrlMatch::MatchPositions* title_match_positions) { | |
38 size_t leading_whitespace_chars = title->length(); | |
39 base::TrimWhitespace(*title, base::TRIM_LEADING, title); | |
40 leading_whitespace_chars-= title->length(); | |
41 if (leading_whitespace_chars == 0) | |
42 return; | |
43 for (query_parser::Snippet::MatchPositions::iterator it = | |
44 title_match_positions->begin(); | |
45 it != title_match_positions->end(); ++it) { | |
46 (*it) = query_parser::Snippet::MatchPosition( | |
47 it->first - leading_whitespace_chars, | |
48 it->second - leading_whitespace_chars); | |
49 } | |
50 } | |
51 | |
52 } // namespace | |
53 | |
54 // BookmarkProvider ------------------------------------------------------------ | 28 // BookmarkProvider ------------------------------------------------------------ |
55 | 29 |
56 BookmarkProvider::BookmarkProvider(AutocompleteProviderClient* client) | 30 BookmarkProvider::BookmarkProvider(AutocompleteProviderClient* client) |
57 : AutocompleteProvider(AutocompleteProvider::TYPE_BOOKMARK), | 31 : AutocompleteProvider(AutocompleteProvider::TYPE_BOOKMARK), |
58 client_(client), | 32 client_(client), |
59 bookmark_model_(client ? client_->GetBookmarkModel() : nullptr) {} | 33 bookmark_model_(client ? client_->GetBookmarkModel() : nullptr) {} |
60 | 34 |
61 void BookmarkProvider::Start(const AutocompleteInput& input, | 35 void BookmarkProvider::Start(const AutocompleteInput& input, |
62 bool minimal_changes) { | 36 bool minimal_changes) { |
63 TRACE_EVENT0("omnibox", "BookmarkProvider::Start"); | 37 TRACE_EVENT0("omnibox", "BookmarkProvider::Start"); |
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
100 // | 74 // |
101 // Please refer to the code for TitledUrlIndex::GetResultsMatching for | 75 // Please refer to the code for TitledUrlIndex::GetResultsMatching for |
102 // complete details of how searches are performed against the user's | 76 // complete details of how searches are performed against the user's |
103 // bookmarks. | 77 // bookmarks. |
104 bookmark_model_->GetBookmarksMatching(input.text(), | 78 bookmark_model_->GetBookmarksMatching(input.text(), |
105 kMaxBookmarkMatches, | 79 kMaxBookmarkMatches, |
106 &matches); | 80 &matches); |
107 if (matches.empty()) | 81 if (matches.empty()) |
108 return; // There were no matches. | 82 return; // There were no matches. |
109 const base::string16 fixed_up_input(FixupUserInput(input).second); | 83 const base::string16 fixed_up_input(FixupUserInput(input).second); |
110 for (TitledUrlMatches::const_iterator i = matches.begin(); i != matches.end(); | 84 for (auto bookmark_match : matches) { |
Mark P
2016/12/23 22:38:50
Can this auto be const and/or &?
mattreynolds
2017/01/04 00:58:03
Done.
| |
111 ++i) { | 85 // Score the TitledUrlMatch. If its score is greater than 0 then the |
112 // Create and score the AutocompleteMatch. If its score is 0 then the | 86 // AutocompleteMatch is created and added to matches_. |
113 // match is discarded. | 87 int relevance = CalculateBookmarkMatchRelevance(bookmark_match); |
114 AutocompleteMatch match(TitledUrlMatchToACMatch(input, fixed_up_input, *i)); | 88 if (relevance > 0) { |
115 if (match.relevance > 0) | 89 matches_.push_back(TitledUrlMatchToAutocompleteMatch( |
116 matches_.push_back(match); | 90 this, client_->GetSchemeClassifier(), input, fixed_up_input, |
91 bookmark_match, AutocompleteMatchType::BOOKMARK_TITLE, relevance)); | |
92 } | |
117 } | 93 } |
118 | 94 |
119 // Sort and clip the resulting matches. | 95 // Sort and clip the resulting matches. |
120 size_t num_matches = | 96 size_t num_matches = |
121 std::min(matches_.size(), AutocompleteProvider::kMaxMatches); | 97 std::min(matches_.size(), AutocompleteProvider::kMaxMatches); |
122 std::partial_sort(matches_.begin(), matches_.begin() + num_matches, | 98 std::partial_sort(matches_.begin(), matches_.begin() + num_matches, |
123 matches_.end(), AutocompleteMatch::MoreRelevant); | 99 matches_.end(), AutocompleteMatch::MoreRelevant); |
124 matches_.resize(num_matches); | 100 matches_.resize(num_matches); |
125 } | 101 } |
126 | 102 |
(...skipping 21 matching lines...) Expand all Loading... | |
148 | 124 |
149 double ScoringFactor() { return scoring_factor_; } | 125 double ScoringFactor() { return scoring_factor_; } |
150 | 126 |
151 private: | 127 private: |
152 double title_length_; | 128 double title_length_; |
153 double scoring_factor_; | 129 double scoring_factor_; |
154 }; | 130 }; |
155 | 131 |
156 } // namespace | 132 } // namespace |
157 | 133 |
158 AutocompleteMatch BookmarkProvider::TitledUrlMatchToACMatch( | 134 int BookmarkProvider::CalculateBookmarkMatchRelevance( |
159 const AutocompleteInput& input, | |
160 const base::string16& fixed_up_input_text, | |
161 const TitledUrlMatch& bookmark_match) { | 135 const TitledUrlMatch& bookmark_match) { |
162 // The AutocompleteMatch we construct is non-deletable because the only | |
163 // way to support this would be to delete the underlying bookmark, which is | |
164 // unlikely to be what the user intends. | |
165 AutocompleteMatch match(this, 0, false, | |
166 AutocompleteMatchType::BOOKMARK_TITLE); | |
167 base::string16 title(bookmark_match.node->GetTitledUrlNodeTitle()); | |
168 TitledUrlMatch::MatchPositions new_title_match_positions = | |
169 bookmark_match.title_match_positions; | |
170 CorrectTitleAndMatchPositions(&title, &new_title_match_positions); | |
171 const GURL& url(bookmark_match.node->GetTitledUrlNodeUrl()); | |
172 const base::string16& url_utf16 = base::UTF8ToUTF16(url.spec()); | |
173 size_t inline_autocomplete_offset = URLPrefix::GetInlineAutocompleteOffset( | |
174 input.text(), fixed_up_input_text, false, url_utf16); | |
175 match.destination_url = url; | |
176 const size_t match_start = bookmark_match.url_match_positions.empty() ? | |
177 0 : bookmark_match.url_match_positions[0].first; | |
178 const bool trim_http = !AutocompleteInput::HasHTTPScheme(input.text()) && | |
179 ((match_start == base::string16::npos) || (match_start != 0)); | |
180 std::vector<size_t> offsets = TitledUrlMatch::OffsetsFromMatchPositions( | |
181 bookmark_match.url_match_positions); | |
182 // In addition to knowing how |offsets| is transformed, we need to know how | |
183 // |inline_autocomplete_offset| is transformed. We add it to the end of | |
184 // |offsets|, compute how everything is transformed, then remove it from the | |
185 // end. | |
186 offsets.push_back(inline_autocomplete_offset); | |
187 match.contents = url_formatter::FormatUrlWithOffsets( | |
188 url, url_formatter::kFormatUrlOmitAll & | |
189 ~(trim_http ? 0 : url_formatter::kFormatUrlOmitHTTP), | |
190 net::UnescapeRule::SPACES, nullptr, nullptr, &offsets); | |
191 inline_autocomplete_offset = offsets.back(); | |
192 offsets.pop_back(); | |
193 TitledUrlMatch::MatchPositions new_url_match_positions = | |
194 TitledUrlMatch::ReplaceOffsetsInMatchPositions( | |
195 bookmark_match.url_match_positions, offsets); | |
196 match.contents_class = | |
197 ClassificationsFromMatch(new_url_match_positions, | |
198 match.contents.size(), | |
199 true); | |
200 match.fill_into_edit = | |
201 AutocompleteInput::FormattedStringWithEquivalentMeaning( | |
202 url, match.contents, client_->GetSchemeClassifier()); | |
203 if (inline_autocomplete_offset != base::string16::npos) { | |
204 // |inline_autocomplete_offset| may be beyond the end of the | |
205 // |fill_into_edit| if the user has typed an URL with a scheme and the | |
206 // last character typed is a slash. That slash is removed by the | |
207 // FormatURLWithOffsets call above. | |
208 if (inline_autocomplete_offset < match.fill_into_edit.length()) { | |
209 match.inline_autocompletion = | |
210 match.fill_into_edit.substr(inline_autocomplete_offset); | |
211 } | |
212 match.allowed_to_be_default_match = match.inline_autocompletion.empty() || | |
213 !HistoryProvider::PreventInlineAutocomplete(input); | |
214 } | |
215 match.description = title; | |
216 match.description_class = | |
217 ClassificationsFromMatch(bookmark_match.title_match_positions, | |
218 match.description.size(), | |
219 false); | |
220 | |
221 // Summary on how a relevance score is determined for the match: | 136 // Summary on how a relevance score is determined for the match: |
222 // | 137 // |
223 // For each match within the bookmark's title or URL (or both), calculate a | 138 // For each match within the bookmark's title or URL (or both), calculate a |
224 // 'factor', sum up those factors, then use the sum to figure out a value | 139 // 'factor', sum up those factors, then use the sum to figure out a value |
225 // between the base score and the maximum score. | 140 // between the base score and the maximum score. |
226 // | 141 // |
227 // The factor for each match is the product of: | 142 // The factor for each match is the product of: |
228 // | 143 // |
229 // 1) how many characters in the bookmark's title/URL are part of this match. | 144 // 1) how many characters in the bookmark's title/URL are part of this match. |
230 // This is capped at the length of the bookmark's title | 145 // This is capped at the length of the bookmark's title |
(...skipping 30 matching lines...) Expand all Loading... | |
261 // possible score. This product is added to the minimum possible score to | 176 // possible score. This product is added to the minimum possible score to |
262 // give the preliminary score. | 177 // give the preliminary score. |
263 // | 178 // |
264 // If the preliminary score is less than the maximum possible score, 1199, | 179 // If the preliminary score is less than the maximum possible score, 1199, |
265 // it can be boosted up to that maximum possible score if the URL referenced | 180 // it can be boosted up to that maximum possible score if the URL referenced |
266 // by the bookmark is also referenced by any of the user's other bookmarks. | 181 // by the bookmark is also referenced by any of the user's other bookmarks. |
267 // A count of how many times the bookmark's URL is referenced is determined | 182 // A count of how many times the bookmark's URL is referenced is determined |
268 // and, for each additional reference beyond the one for the bookmark being | 183 // and, for each additional reference beyond the one for the bookmark being |
269 // scored up to a maximum of three, the score is boosted by a fixed amount | 184 // scored up to a maximum of three, the score is boosted by a fixed amount |
270 // given by |kURLCountBoost|, below. | 185 // given by |kURLCountBoost|, below. |
271 // | 186 |
187 base::string16 title(bookmark_match.node->GetTitledUrlNodeTitle()); | |
188 const GURL& url(bookmark_match.node->GetTitledUrlNodeUrl()); | |
272 | 189 |
273 // Pretend empty titles are identical to the URL. | 190 // Pretend empty titles are identical to the URL. |
274 if (title.empty()) | 191 if (title.empty()) |
275 title = base::ASCIIToUTF16(url.spec()); | 192 title = base::ASCIIToUTF16(url.spec()); |
276 ScoringFunctor title_position_functor = | 193 ScoringFunctor title_position_functor = |
277 for_each(bookmark_match.title_match_positions.begin(), | 194 for_each(bookmark_match.title_match_positions.begin(), |
278 bookmark_match.title_match_positions.end(), | 195 bookmark_match.title_match_positions.end(), |
279 ScoringFunctor(title.size())); | 196 ScoringFunctor(title.size())); |
280 ScoringFunctor url_position_functor = | 197 ScoringFunctor url_position_functor = |
281 for_each(bookmark_match.url_match_positions.begin(), | 198 for_each(bookmark_match.url_match_positions.begin(), |
282 bookmark_match.url_match_positions.end(), | 199 bookmark_match.url_match_positions.end(), |
283 ScoringFunctor( | 200 ScoringFunctor( |
284 bookmark_match.node->GetTitledUrlNodeUrl().spec().length())); | 201 bookmark_match.node->GetTitledUrlNodeUrl().spec().length())); |
285 const double title_match_strength = title_position_functor.ScoringFactor(); | 202 const double title_match_strength = title_position_functor.ScoringFactor(); |
286 const double summed_factors = title_match_strength + | 203 const double summed_factors = title_match_strength + |
287 url_position_functor.ScoringFactor(); | 204 url_position_functor.ScoringFactor(); |
288 const double normalized_sum = | 205 const double normalized_sum = |
289 std::min(summed_factors / (title.size() + 10), 1.0); | 206 std::min(summed_factors / (title.size() + 10), 1.0); |
290 // Bookmarks with javascript scheme ("bookmarklets") that do not have title | 207 // Bookmarks with javascript scheme ("bookmarklets") that do not have title |
291 // matches get a lower base and lower maximum score because returning them | 208 // matches get a lower base and lower maximum score because returning them |
292 // for matches in their (often very long) URL looks stupid and is often not | 209 // for matches in their (often very long) URL looks stupid and is often not |
293 // intended by the user. | 210 // intended by the user. |
294 const bool bookmarklet_without_title_match = | 211 const bool bookmarklet_without_title_match = |
295 url.SchemeIs(url::kJavaScriptScheme) && (title_match_strength == 0.0); | 212 url.SchemeIs(url::kJavaScriptScheme) && (title_match_strength == 0.0); |
296 const int kBaseBookmarkScore = bookmarklet_without_title_match ? 400 : 900; | 213 const int kBaseBookmarkScore = bookmarklet_without_title_match ? 400 : 900; |
297 const int kMaxBookmarkScore = bookmarklet_without_title_match ? 799 : 1199; | 214 const int kMaxBookmarkScore = bookmarklet_without_title_match ? 799 : 1199; |
298 const double kBookmarkScoreRange = | 215 const double kBookmarkScoreRange = |
299 static_cast<double>(kMaxBookmarkScore - kBaseBookmarkScore); | 216 static_cast<double>(kMaxBookmarkScore - kBaseBookmarkScore); |
300 match.relevance = static_cast<int>(normalized_sum * kBookmarkScoreRange) + | 217 int relevance = static_cast<int>(normalized_sum * kBookmarkScoreRange) + |
301 kBaseBookmarkScore; | 218 kBaseBookmarkScore; |
302 // Don't waste any time searching for additional referenced URLs if we | 219 // Don't waste any time searching for additional referenced URLs if we |
303 // already have a perfect title match. | 220 // already have a perfect title match. |
304 if (match.relevance >= kMaxBookmarkScore) | 221 if (relevance >= kMaxBookmarkScore) |
305 return match; | 222 return relevance; |
306 // Boost the score if the bookmark's URL is referenced by other bookmarks. | 223 // Boost the score if the bookmark's URL is referenced by other bookmarks. |
307 const int kURLCountBoost[4] = { 0, 75, 125, 150 }; | 224 const int kURLCountBoost[4] = { 0, 75, 125, 150 }; |
308 std::vector<const BookmarkNode*> nodes; | 225 std::vector<const BookmarkNode*> nodes; |
309 bookmark_model_->GetNodesByURL(url, &nodes); | 226 bookmark_model_->GetNodesByURL(url, &nodes); |
310 DCHECK_GE(std::min(arraysize(kURLCountBoost), nodes.size()), 1U); | 227 DCHECK_GE(std::min(arraysize(kURLCountBoost), nodes.size()), 1U); |
311 match.relevance += | 228 relevance += |
312 kURLCountBoost[std::min(arraysize(kURLCountBoost), nodes.size()) - 1]; | 229 kURLCountBoost[std::min(arraysize(kURLCountBoost), nodes.size()) - 1]; |
313 match.relevance = std::min(kMaxBookmarkScore, match.relevance); | 230 relevance = std::min(kMaxBookmarkScore, relevance); |
314 return match; | 231 return relevance; |
315 } | 232 } |
316 | |
317 // static | |
318 ACMatchClassifications BookmarkProvider::ClassificationsFromMatch( | |
319 const query_parser::Snippet::MatchPositions& positions, | |
320 size_t text_length, | |
321 bool is_url) { | |
322 ACMatchClassification::Style url_style = | |
323 is_url ? ACMatchClassification::URL : ACMatchClassification::NONE; | |
324 ACMatchClassifications classifications; | |
325 if (positions.empty()) { | |
326 if (text_length > 0) | |
327 classifications.push_back(ACMatchClassification(0, url_style)); | |
328 return classifications; | |
329 } | |
330 | |
331 for (query_parser::Snippet::MatchPositions::const_iterator i = | |
332 positions.begin(); | |
333 i != positions.end(); | |
334 ++i) { | |
335 AutocompleteMatch::ACMatchClassifications new_class; | |
336 AutocompleteMatch::ClassifyLocationInString(i->first, i->second - i->first, | |
337 text_length, url_style, &new_class); | |
338 classifications = AutocompleteMatch::MergeClassifications( | |
339 classifications, new_class); | |
340 } | |
341 return classifications; | |
342 } | |
OLD | NEW |