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/shortcuts_provider.h" | 5 #include "components/omnibox/browser/shortcuts_provider.h" |
6 | 6 |
7 #include <stddef.h> | 7 #include <stddef.h> |
8 | 8 |
9 #include <algorithm> | 9 #include <algorithm> |
10 #include <cmath> | 10 #include <cmath> |
(...skipping 10 matching lines...) Expand all Loading... |
21 #include "base/time/time.h" | 21 #include "base/time/time.h" |
22 #include "base/trace_event/trace_event.h" | 22 #include "base/trace_event/trace_event.h" |
23 #include "components/history/core/browser/history_service.h" | 23 #include "components/history/core/browser/history_service.h" |
24 #include "components/metrics/proto/omnibox_input_type.pb.h" | 24 #include "components/metrics/proto/omnibox_input_type.pb.h" |
25 #include "components/omnibox/browser/autocomplete_i18n.h" | 25 #include "components/omnibox/browser/autocomplete_i18n.h" |
26 #include "components/omnibox/browser/autocomplete_input.h" | 26 #include "components/omnibox/browser/autocomplete_input.h" |
27 #include "components/omnibox/browser/autocomplete_match.h" | 27 #include "components/omnibox/browser/autocomplete_match.h" |
28 #include "components/omnibox/browser/autocomplete_provider_client.h" | 28 #include "components/omnibox/browser/autocomplete_provider_client.h" |
29 #include "components/omnibox/browser/autocomplete_result.h" | 29 #include "components/omnibox/browser/autocomplete_result.h" |
30 #include "components/omnibox/browser/history_provider.h" | 30 #include "components/omnibox/browser/history_provider.h" |
| 31 #include "components/omnibox/browser/match_compare.h" |
31 #include "components/omnibox/browser/omnibox_field_trial.h" | 32 #include "components/omnibox/browser/omnibox_field_trial.h" |
32 #include "components/omnibox/browser/url_prefix.h" | 33 #include "components/omnibox/browser/url_prefix.h" |
33 #include "components/prefs/pref_service.h" | 34 #include "components/prefs/pref_service.h" |
34 #include "components/url_formatter/url_fixer.h" | 35 #include "components/url_formatter/url_fixer.h" |
35 #include "url/third_party/mozilla/url_parse.h" | 36 #include "url/third_party/mozilla/url_parse.h" |
36 | 37 |
37 namespace { | 38 namespace { |
38 | 39 |
39 class DestinationURLEqualsURL { | 40 class DestinationURLEqualsURL { |
40 public: | 41 public: |
41 explicit DestinationURLEqualsURL(const GURL& url) : url_(url) {} | 42 explicit DestinationURLEqualsURL(const GURL& url) : url_(url) {} |
42 bool operator()(const AutocompleteMatch& match) const { | 43 bool operator()(const AutocompleteMatch& match) const { |
43 return match.destination_url == url_; | 44 return match.destination_url == url_; |
44 } | 45 } |
45 | 46 |
46 private: | 47 private: |
47 const GURL url_; | 48 const GURL url_; |
48 }; | 49 }; |
49 | 50 |
| 51 // ShortcutMatch holds sufficient information about a single match from the |
| 52 // shortcut database to allow for destination deduping and relevance sorting. |
| 53 // After those stages the top matches are converted to the more heavyweight |
| 54 // AutocompleteMatch struct. Avoiding constructing the larger struct for |
| 55 // every such match can save significant time when there are many shortcut |
| 56 // matches to process. |
| 57 struct ShortcutMatch { |
| 58 ShortcutMatch(int relevance, |
| 59 const GURL& stripped_destination_url, |
| 60 const ShortcutsDatabase::Shortcut* shortcut) |
| 61 : relevance(relevance), |
| 62 stripped_destination_url(stripped_destination_url), |
| 63 shortcut(shortcut), |
| 64 contents(shortcut->match_core.contents), |
| 65 type(static_cast<AutocompleteMatch::Type>(shortcut->match_core.type)) {} |
| 66 |
| 67 int relevance; |
| 68 GURL stripped_destination_url; |
| 69 const ShortcutsDatabase::Shortcut* shortcut; |
| 70 base::string16 contents; |
| 71 AutocompleteMatch::Type type; |
| 72 }; |
| 73 |
| 74 // Sorts |matches| by destination, taking into account demotions based on |
| 75 // |page_classification| when resolving ties about which of several |
| 76 // duplicates to keep. The matches are also deduplicated. |
| 77 void SortAndDedupMatches( |
| 78 metrics::OmniboxEventProto::PageClassification page_classification, |
| 79 std::vector<ShortcutMatch>* matches) { |
| 80 // Sort matches such that duplicate matches are consecutive. |
| 81 std::sort(matches->begin(), matches->end(), |
| 82 DestinationSort<ShortcutMatch>(page_classification)); |
| 83 |
| 84 // Erase duplicate matches. Duplicate matches are those with |
| 85 // stripped_destination_url fields equal and non empty. |
| 86 matches->erase( |
| 87 std::unique(matches->begin(), matches->end(), |
| 88 [](const ShortcutMatch& elem1, const ShortcutMatch& elem2) { |
| 89 return !elem1.stripped_destination_url.is_empty() && |
| 90 (elem1.stripped_destination_url == |
| 91 elem2.stripped_destination_url); |
| 92 }), |
| 93 matches->end()); |
| 94 } |
| 95 |
50 } // namespace | 96 } // namespace |
51 | 97 |
52 const int ShortcutsProvider::kShortcutsProviderDefaultMaxRelevance = 1199; | 98 const int ShortcutsProvider::kShortcutsProviderDefaultMaxRelevance = 1199; |
53 | 99 |
54 ShortcutsProvider::ShortcutsProvider(AutocompleteProviderClient* client) | 100 ShortcutsProvider::ShortcutsProvider(AutocompleteProviderClient* client) |
55 : AutocompleteProvider(AutocompleteProvider::TYPE_SHORTCUTS), | 101 : AutocompleteProvider(AutocompleteProvider::TYPE_SHORTCUTS), |
56 client_(client), | 102 client_(client), |
57 initialized_(false) { | 103 initialized_(false) { |
58 scoped_refptr<ShortcutsBackend> backend = client_->GetShortcutsBackend(); | 104 scoped_refptr<ShortcutsBackend> backend = client_->GetShortcutsBackend(); |
59 if (backend.get()) { | 105 if (backend.get()) { |
(...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
130 // completely match the search term. | 176 // completely match the search term. |
131 base::string16 term_string(base::i18n::ToLower(input.text())); | 177 base::string16 term_string(base::i18n::ToLower(input.text())); |
132 DCHECK(!term_string.empty()); | 178 DCHECK(!term_string.empty()); |
133 | 179 |
134 int max_relevance; | 180 int max_relevance; |
135 if (!OmniboxFieldTrial::ShortcutsScoringMaxRelevance( | 181 if (!OmniboxFieldTrial::ShortcutsScoringMaxRelevance( |
136 input.current_page_classification(), &max_relevance)) | 182 input.current_page_classification(), &max_relevance)) |
137 max_relevance = kShortcutsProviderDefaultMaxRelevance; | 183 max_relevance = kShortcutsProviderDefaultMaxRelevance; |
138 TemplateURLService* template_url_service = client_->GetTemplateURLService(); | 184 TemplateURLService* template_url_service = client_->GetTemplateURLService(); |
139 const base::string16 fixed_up_input(FixupUserInput(input).second); | 185 const base::string16 fixed_up_input(FixupUserInput(input).second); |
| 186 |
| 187 std::vector<ShortcutMatch> shortcut_matches; |
140 for (ShortcutsBackend::ShortcutMap::const_iterator it = | 188 for (ShortcutsBackend::ShortcutMap::const_iterator it = |
141 FindFirstMatch(term_string, backend.get()); | 189 FindFirstMatch(term_string, backend.get()); |
142 it != backend->shortcuts_map().end() && | 190 it != backend->shortcuts_map().end() && |
143 base::StartsWith(it->first, term_string, | 191 base::StartsWith(it->first, term_string, |
144 base::CompareCase::SENSITIVE); | 192 base::CompareCase::SENSITIVE); |
145 ++it) { | 193 ++it) { |
146 // Don't return shortcuts with zero relevance. | 194 // Don't return shortcuts with zero relevance. |
147 int relevance = CalculateScore(term_string, it->second, max_relevance); | 195 int relevance = CalculateScore(term_string, it->second, max_relevance); |
148 if (relevance) { | 196 if (relevance) { |
149 matches_.push_back( | 197 const ShortcutsDatabase::Shortcut& shortcut = it->second; |
150 ShortcutToACMatch(it->second, relevance, input, fixed_up_input)); | 198 GURL stripped_destination_url(AutocompleteMatch::GURLToStrippedGURL( |
151 matches_.back().ComputeStrippedDestinationURL(input, | 199 shortcut.match_core.destination_url, input, template_url_service, |
152 template_url_service); | 200 shortcut.match_core.keyword)); |
| 201 shortcut_matches.push_back( |
| 202 ShortcutMatch(relevance, stripped_destination_url, &it->second)); |
153 } | 203 } |
154 } | 204 } |
155 // Remove duplicates. This is important because it's common to have multiple | 205 // Remove duplicates. This is important because it's common to have multiple |
156 // shortcuts pointing to the same URL, e.g., ma, mai, and mail all pointing | 206 // shortcuts pointing to the same URL, e.g., ma, mai, and mail all pointing |
157 // to mail.google.com, so typing "m" will return them all. If we then simply | 207 // to mail.google.com, so typing "m" will return them all. If we then simply |
158 // clamp to kMaxMatches and let the AutocompleteResult take care of | 208 // clamp to kMaxMatches and let the SortAndDedupMatches take care of |
159 // collapsing the duplicates, we'll effectively only be returning one match, | 209 // collapsing the duplicates, we'll effectively only be returning one match, |
160 // instead of several possibilities. | 210 // instead of several possibilities. |
161 // | 211 // |
162 // Note that while removing duplicates, we don't populate a match's | 212 // Note that while removing duplicates, we don't populate a match's |
163 // |duplicate_matches| field--duplicates don't need to be preserved in the | 213 // |duplicate_matches| field--duplicates don't need to be preserved in the |
164 // matches because they are only used for deletions, and this provider | 214 // matches because they are only used for deletions, and this provider |
165 // deletes matches based on the URL. | 215 // deletes matches based on the URL. |
166 AutocompleteResult::DedupMatchesByDestination( | 216 SortAndDedupMatches(input.current_page_classification(), &shortcut_matches); |
167 input.current_page_classification(), false, &matches_); | 217 |
168 // Find best matches. | 218 // Find best matches. |
169 std::partial_sort( | 219 std::partial_sort( |
170 matches_.begin(), | 220 shortcut_matches.begin(), |
171 matches_.begin() + | 221 shortcut_matches.begin() + |
172 std::min(AutocompleteProvider::kMaxMatches, matches_.size()), | 222 std::min(AutocompleteProvider::kMaxMatches, shortcut_matches.size()), |
173 matches_.end(), &AutocompleteMatch::MoreRelevant); | 223 shortcut_matches.end(), |
174 if (matches_.size() > AutocompleteProvider::kMaxMatches) { | 224 [](const ShortcutMatch& elem1, const ShortcutMatch& elem2) { |
175 matches_.erase(matches_.begin() + AutocompleteProvider::kMaxMatches, | 225 // Ensure a stable sort by sorting equal-relevance matches |
176 matches_.end()); | 226 // alphabetically. |
| 227 return elem1.relevance == elem2.relevance |
| 228 ? elem1.contents < elem2.contents |
| 229 : elem1.relevance > elem2.relevance; |
| 230 }); |
| 231 if (shortcut_matches.size() > AutocompleteProvider::kMaxMatches) { |
| 232 shortcut_matches.erase( |
| 233 shortcut_matches.begin() + AutocompleteProvider::kMaxMatches, |
| 234 shortcut_matches.end()); |
177 } | 235 } |
178 // Guarantee that all scores are decreasing (but do not assign any scores | 236 // Create and initialize autocomplete matches from shortcut matches. |
179 // below 1). | 237 // Also guarantee that all relevance scores are decreasing (but do not assign |
180 for (ACMatches::iterator it = matches_.begin(); it != matches_.end(); ++it) { | 238 // any scores below 1). |
181 max_relevance = std::min(max_relevance, it->relevance); | 239 WordMap terms_map(CreateWordMapForString(term_string)); |
182 it->relevance = max_relevance; | 240 matches_.reserve(shortcut_matches.size()); |
| 241 for (ShortcutMatch& match : shortcut_matches) { |
| 242 max_relevance = std::min(max_relevance, match.relevance); |
| 243 matches_.push_back(ShortcutToACMatch(*match.shortcut, max_relevance, input, |
| 244 fixed_up_input, term_string, |
| 245 terms_map)); |
183 if (max_relevance > 1) | 246 if (max_relevance > 1) |
184 --max_relevance; | 247 --max_relevance; |
185 } | 248 } |
186 } | 249 } |
187 | 250 |
188 AutocompleteMatch ShortcutsProvider::ShortcutToACMatch( | 251 AutocompleteMatch ShortcutsProvider::ShortcutToACMatch( |
189 const ShortcutsDatabase::Shortcut& shortcut, | 252 const ShortcutsDatabase::Shortcut& shortcut, |
190 int relevance, | 253 int relevance, |
191 const AutocompleteInput& input, | 254 const AutocompleteInput& input, |
192 const base::string16& fixed_up_input_text) { | 255 const base::string16& fixed_up_input_text, |
| 256 const base::string16 term_string, |
| 257 const WordMap& terms_map) { |
193 DCHECK(!input.text().empty()); | 258 DCHECK(!input.text().empty()); |
194 AutocompleteMatch match; | 259 AutocompleteMatch match; |
195 match.provider = this; | 260 match.provider = this; |
196 match.relevance = relevance; | 261 match.relevance = relevance; |
197 match.deletable = true; | 262 match.deletable = true; |
198 match.fill_into_edit = shortcut.match_core.fill_into_edit; | 263 match.fill_into_edit = shortcut.match_core.fill_into_edit; |
199 match.destination_url = shortcut.match_core.destination_url; | 264 match.destination_url = shortcut.match_core.destination_url; |
200 DCHECK(match.destination_url.is_valid()); | 265 DCHECK(match.destination_url.is_valid()); |
201 match.contents = shortcut.match_core.contents; | 266 match.contents = shortcut.match_core.contents; |
202 match.contents_class = AutocompleteMatch::ClassificationsFromString( | 267 match.contents_class = AutocompleteMatch::ClassificationsFromString( |
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
238 URLPrefix::GetInlineAutocompleteOffset( | 303 URLPrefix::GetInlineAutocompleteOffset( |
239 input.text(), fixed_up_input_text, true, match.fill_into_edit); | 304 input.text(), fixed_up_input_text, true, match.fill_into_edit); |
240 if (inline_autocomplete_offset != base::string16::npos) { | 305 if (inline_autocomplete_offset != base::string16::npos) { |
241 match.inline_autocompletion = | 306 match.inline_autocompletion = |
242 match.fill_into_edit.substr(inline_autocomplete_offset); | 307 match.fill_into_edit.substr(inline_autocomplete_offset); |
243 match.allowed_to_be_default_match = | 308 match.allowed_to_be_default_match = |
244 !HistoryProvider::PreventInlineAutocomplete(input) || | 309 !HistoryProvider::PreventInlineAutocomplete(input) || |
245 match.inline_autocompletion.empty(); | 310 match.inline_autocompletion.empty(); |
246 } | 311 } |
247 } | 312 } |
248 match.EnsureUWYTIsAllowedToBeDefault(input, | 313 |
249 client_->GetTemplateURLService()); | 314 match.EnsureUWYTIsAllowedToBeDefault(input, client_->GetTemplateURLService()); |
250 | 315 |
251 // Try to mark pieces of the contents and description as matches if they | 316 // Try to mark pieces of the contents and description as matches if they |
252 // appear in |input.text()|. | 317 // appear in |input.text()|. |
253 const base::string16 term_string = base::i18n::ToLower(input.text()); | |
254 WordMap terms_map(CreateWordMapForString(term_string)); | |
255 if (!terms_map.empty()) { | 318 if (!terms_map.empty()) { |
256 match.contents_class = ClassifyAllMatchesInString( | 319 match.contents_class = ClassifyAllMatchesInString( |
257 term_string, terms_map, match.contents, match.contents_class); | 320 term_string, terms_map, match.contents, match.contents_class); |
258 match.description_class = ClassifyAllMatchesInString( | 321 match.description_class = ClassifyAllMatchesInString( |
259 term_string, terms_map, match.description, match.description_class); | 322 term_string, terms_map, match.description, match.description_class); |
260 } | 323 } |
261 return match; | 324 return match; |
262 } | 325 } |
263 | 326 |
264 // static | 327 // static |
(...skipping 149 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
414 const double kMaxDecaySpeedDivisor = 5.0; | 477 const double kMaxDecaySpeedDivisor = 5.0; |
415 const double kNumUsesPerDecaySpeedDivisorIncrement = 5.0; | 478 const double kNumUsesPerDecaySpeedDivisorIncrement = 5.0; |
416 double decay_divisor = std::min( | 479 double decay_divisor = std::min( |
417 kMaxDecaySpeedDivisor, | 480 kMaxDecaySpeedDivisor, |
418 (shortcut.number_of_hits + kNumUsesPerDecaySpeedDivisorIncrement - 1) / | 481 (shortcut.number_of_hits + kNumUsesPerDecaySpeedDivisorIncrement - 1) / |
419 kNumUsesPerDecaySpeedDivisorIncrement); | 482 kNumUsesPerDecaySpeedDivisorIncrement); |
420 | 483 |
421 return static_cast<int>((base_score / exp(decay_exponent / decay_divisor)) + | 484 return static_cast<int>((base_score / exp(decay_exponent / decay_divisor)) + |
422 0.5); | 485 0.5); |
423 } | 486 } |
OLD | NEW |