Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(227)

Side by Side Diff: components/omnibox/browser/shortcuts_provider.cc

Issue 1877833002: Optimize shortcuts provider (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Fixes after review, round 2 Created 4 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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
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
50 } // namespace 51 } // namespace
51 52
52 const int ShortcutsProvider::kShortcutsProviderDefaultMaxRelevance = 1199; 53 const int ShortcutsProvider::kShortcutsProviderDefaultMaxRelevance = 1199;
53 54
55 ShortcutsProvider::ShortcutMatch::ShortcutMatch(
56 int relevance, const GURL& stripped_destination_url,
57 const ShortcutsDatabase::Shortcut* shortcut) :
Peter Kasting 2016/04/14 23:52:33 Nit: ':' goes at the beginning of the next line, n
Alexander Yashkin 2016/04/15 09:14:14 Done.
58 relevance(relevance),
Peter Kasting 2016/04/14 23:52:33 Nit: These lines are not sufficiently indented
Alexander Yashkin 2016/04/15 09:14:15 Done.
59 stripped_destination_url(stripped_destination_url),
60 shortcut(shortcut),
61 contents(shortcut->match_core.contents),
62 type(static_cast<AutocompleteMatch::Type>(shortcut->match_core.type)) {}
63
54 ShortcutsProvider::ShortcutsProvider(AutocompleteProviderClient* client) 64 ShortcutsProvider::ShortcutsProvider(AutocompleteProviderClient* client)
55 : AutocompleteProvider(AutocompleteProvider::TYPE_SHORTCUTS), 65 : AutocompleteProvider(AutocompleteProvider::TYPE_SHORTCUTS),
56 client_(client), 66 client_(client),
57 languages_(client_->GetAcceptLanguages()), 67 languages_(client_->GetAcceptLanguages()),
58 initialized_(false) { 68 initialized_(false) {
59 scoped_refptr<ShortcutsBackend> backend = client_->GetShortcutsBackend(); 69 scoped_refptr<ShortcutsBackend> backend = client_->GetShortcutsBackend();
60 if (backend.get()) { 70 if (backend.get()) {
61 backend->AddObserver(this); 71 backend->AddObserver(this);
62 if (backend->initialized()) 72 if (backend->initialized())
63 initialized_ = true; 73 initialized_ = true;
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after
111 history_service->DeleteURL(url); 121 history_service->DeleteURL(url);
112 } 122 }
113 123
114 ShortcutsProvider::~ShortcutsProvider() { 124 ShortcutsProvider::~ShortcutsProvider() {
115 scoped_refptr<ShortcutsBackend> backend = 125 scoped_refptr<ShortcutsBackend> backend =
116 client_->GetShortcutsBackendIfExists(); 126 client_->GetShortcutsBackendIfExists();
117 if (backend.get()) 127 if (backend.get())
118 backend->RemoveObserver(this); 128 backend->RemoveObserver(this);
119 } 129 }
120 130
131 // static
Peter Kasting 2016/04/14 23:52:33 Nit: Might want to move these functions up in a se
Alexander Yashkin 2016/04/15 09:14:14 Reverted move.
132 ShortcutsProvider::WordMap ShortcutsProvider::CreateWordMapForString(
133 const base::string16& text) {
134 // First, convert |text| to a vector of the unique words in it.
135 WordMap word_map;
136 base::i18n::BreakIterator word_iter(text,
137 base::i18n::BreakIterator::BREAK_WORD);
138 if (!word_iter.Init())
139 return word_map;
140 std::vector<base::string16> words;
141 while (word_iter.Advance()) {
142 if (word_iter.IsWord())
143 words.push_back(word_iter.GetString());
144 }
145 if (words.empty())
146 return word_map;
147 std::sort(words.begin(), words.end());
148 words.erase(std::unique(words.begin(), words.end()), words.end());
149
150 // Now create a map from (first character) to (words beginning with that
151 // character). We insert in reverse lexicographical order and rely on the
152 // multimap preserving insertion order for values with the same key. (This
153 // is mandated in C++11, and part of that decision was based on a survey of
154 // existing implementations that found that it was already true everywhere.)
155 std::reverse(words.begin(), words.end());
156 for (std::vector<base::string16>::const_iterator i(words.begin());
157 i != words.end(); ++i)
158 word_map.insert(std::make_pair((*i)[0], *i));
159 return word_map;
160 }
161
162 // static
163 ACMatchClassifications ShortcutsProvider::ClassifyAllMatchesInString(
164 const base::string16& find_text,
165 const WordMap& find_words,
166 const base::string16& text,
167 const ACMatchClassifications& original_class) {
168 DCHECK(!find_text.empty());
169 DCHECK(!find_words.empty());
170
171 // The code below assumes |text| is nonempty and therefore the resulting
172 // classification vector should always be nonempty as well. Returning early
173 // if |text| is empty assures we'll return the (correct) empty vector rather
174 // than a vector with a single (0, NONE) match.
175 if (text.empty())
176 return original_class;
177
178 // First check whether |text| begins with |find_text| and mark that whole
179 // section as a match if so.
180 base::string16 text_lowercase(base::i18n::ToLower(text));
181 ACMatchClassifications match_class;
182 size_t last_position = 0;
183 if (base::StartsWith(text_lowercase, find_text,
184 base::CompareCase::SENSITIVE)) {
185 match_class.push_back(
186 ACMatchClassification(0, ACMatchClassification::MATCH));
187 last_position = find_text.length();
188 // If |text_lowercase| is actually equal to |find_text|, we don't need to
189 // (and in fact shouldn't) put a trailing NONE classification after the end
190 // of the string.
191 if (last_position < text_lowercase.length()) {
192 match_class.push_back(
193 ACMatchClassification(last_position, ACMatchClassification::NONE));
194 }
195 } else {
196 // |match_class| should start at position 0. If the first matching word is
197 // found at position 0, this will be popped from the vector further down.
198 match_class.push_back(
199 ACMatchClassification(0, ACMatchClassification::NONE));
200 }
201
202 // Now, starting with |last_position|, check each character in
203 // |text_lowercase| to see if we have words starting with that character in
204 // |find_words|. If so, check each of them to see if they match the portion
205 // of |text_lowercase| beginning with |last_position|. Accept the first
206 // matching word found (which should be the longest possible match at this
207 // location, given the construction of |find_words|) and add a MATCH region to
208 // |match_class|, moving |last_position| to be after the matching word. If we
209 // found no matching words, move to the next character and repeat.
210 while (last_position < text_lowercase.length()) {
211 std::pair<WordMap::const_iterator, WordMap::const_iterator> range(
212 find_words.equal_range(text_lowercase[last_position]));
213 size_t next_character = last_position + 1;
214 for (WordMap::const_iterator i(range.first); i != range.second; ++i) {
215 const base::string16& word = i->second;
216 size_t word_end = last_position + word.length();
217 if ((word_end <= text_lowercase.length()) &&
218 !text_lowercase.compare(last_position, word.length(), word)) {
219 // Collapse adjacent ranges into one.
220 if (match_class.back().offset == last_position)
221 match_class.pop_back();
222
223 AutocompleteMatch::AddLastClassificationIfNecessary(
224 &match_class, last_position, ACMatchClassification::MATCH);
225 if (word_end < text_lowercase.length()) {
226 match_class.push_back(
227 ACMatchClassification(word_end, ACMatchClassification::NONE));
228 }
229 last_position = word_end;
230 break;
231 }
232 }
233 last_position = std::max(last_position, next_character);
234 }
235
236 return AutocompleteMatch::MergeClassifications(original_class, match_class);
237 }
238
239 // static
240 void ShortcutsProvider::SortAndDedupMatches(
241 OmniboxEventProto::PageClassification page_classification,
242 std::vector<ShortcutMatch>* matches) {
243 // Sort matches such that duplicate matches are consecutive.
244 std::sort(matches->begin(), matches->end(),
245 DestinationSort<ShortcutMatch>(page_classification));
246
247 // Erase duplicate matches. Duplicate matches are those with
248 // stripped_destination_url fields equal and non empty.
249 matches->erase(
250 std::unique(matches->begin(), matches->end(),
251 [](const ShortcutMatch& elem1, const ShortcutMatch& elem2) {
252 return !elem1.stripped_destination_url.is_empty() &&
253 (elem1.stripped_destination_url ==
254 elem2.stripped_destination_url);
255 }),
256 matches->end());
257 }
258
121 void ShortcutsProvider::OnShortcutsLoaded() { 259 void ShortcutsProvider::OnShortcutsLoaded() {
122 initialized_ = true; 260 initialized_ = true;
123 } 261 }
124 262
125 void ShortcutsProvider::GetMatches(const AutocompleteInput& input) { 263 void ShortcutsProvider::GetMatches(const AutocompleteInput& input) {
126 scoped_refptr<ShortcutsBackend> backend = 264 scoped_refptr<ShortcutsBackend> backend =
127 client_->GetShortcutsBackendIfExists(); 265 client_->GetShortcutsBackendIfExists();
128 if (!backend.get()) 266 if (!backend.get())
129 return; 267 return;
130 // Get the URLs from the shortcuts database with keys that partially or 268 // Get the URLs from the shortcuts database with keys that partially or
131 // completely match the search term. 269 // completely match the search term.
132 base::string16 term_string(base::i18n::ToLower(input.text())); 270 base::string16 term_string(base::i18n::ToLower(input.text()));
133 DCHECK(!term_string.empty()); 271 DCHECK(!term_string.empty());
134 272
135 int max_relevance; 273 int max_relevance;
136 if (!OmniboxFieldTrial::ShortcutsScoringMaxRelevance( 274 if (!OmniboxFieldTrial::ShortcutsScoringMaxRelevance(
137 input.current_page_classification(), &max_relevance)) 275 input.current_page_classification(), &max_relevance))
138 max_relevance = kShortcutsProviderDefaultMaxRelevance; 276 max_relevance = kShortcutsProviderDefaultMaxRelevance;
139 TemplateURLService* template_url_service = client_->GetTemplateURLService(); 277 TemplateURLService* template_url_service = client_->GetTemplateURLService();
140 const base::string16 fixed_up_input(FixupUserInput(input).second); 278 const base::string16 fixed_up_input(FixupUserInput(input).second);
279
280 std::vector<ShortcutMatch> shortcut_matches;
141 for (ShortcutsBackend::ShortcutMap::const_iterator it = 281 for (ShortcutsBackend::ShortcutMap::const_iterator it =
142 FindFirstMatch(term_string, backend.get()); 282 FindFirstMatch(term_string, backend.get());
143 it != backend->shortcuts_map().end() && 283 it != backend->shortcuts_map().end() &&
144 base::StartsWith(it->first, term_string, 284 base::StartsWith(it->first, term_string,
145 base::CompareCase::SENSITIVE); 285 base::CompareCase::SENSITIVE);
146 ++it) { 286 ++it) {
147 // Don't return shortcuts with zero relevance. 287 // Don't return shortcuts with zero relevance.
148 int relevance = CalculateScore(term_string, it->second, max_relevance); 288 int relevance = CalculateScore(term_string, it->second, max_relevance);
149 if (relevance) { 289 if (relevance) {
150 matches_.push_back( 290 const ShortcutsDatabase::Shortcut& shortcut = it->second;
151 ShortcutToACMatch(it->second, relevance, input, fixed_up_input)); 291 GURL stripped_destination_url(AutocompleteMatch::GURLToStrippedGURL(
152 matches_.back().ComputeStrippedDestinationURL( 292 shortcut.match_core.destination_url,
153 input, client_->GetAcceptLanguages(), template_url_service); 293 input,
294 languages_,
295 template_url_service,
296 shortcut.match_core.keyword));
297 shortcut_matches.push_back(
298 ShortcutMatch(relevance, stripped_destination_url, &(it->second)));
Peter Kasting 2016/04/14 23:52:33 Nit: Parens around -> operator not necessary
Alexander Yashkin 2016/04/15 09:14:14 Done.
154 } 299 }
155 } 300 }
156 // Remove duplicates. This is important because it's common to have multiple 301 // Remove duplicates. This is important because it's common to have multiple
157 // shortcuts pointing to the same URL, e.g., ma, mai, and mail all pointing 302 // shortcuts pointing to the same URL, e.g., ma, mai, and mail all pointing
158 // to mail.google.com, so typing "m" will return them all. If we then simply 303 // to mail.google.com, so typing "m" will return them all. If we then simply
159 // clamp to kMaxMatches and let the AutocompleteResult take care of 304 // clamp to kMaxMatches and let the SortAndDedupMatches take care of
160 // collapsing the duplicates, we'll effectively only be returning one match, 305 // collapsing the duplicates, we'll effectively only be returning one match,
161 // instead of several possibilities. 306 // instead of several possibilities.
162 // 307 //
163 // Note that while removing duplicates, we don't populate a match's 308 // Note that while removing duplicates, we don't populate a match's
164 // |duplicate_matches| field--duplicates don't need to be preserved in the 309 // |duplicate_matches| field--duplicates don't need to be preserved in the
165 // matches because they are only used for deletions, and this provider 310 // matches because they are only used for deletions, and this provider
166 // deletes matches based on the URL. 311 // deletes matches based on the URL.
167 AutocompleteResult::DedupMatchesByDestination( 312 SortAndDedupMatches(input.current_page_classification(), &shortcut_matches);
168 input.current_page_classification(), false, &matches_); 313
169 // Find best matches. 314 // Find best matches.
170 std::partial_sort( 315 std::partial_sort(
171 matches_.begin(), 316 shortcut_matches.begin(),
172 matches_.begin() + 317 shortcut_matches.begin() +
173 std::min(AutocompleteProvider::kMaxMatches, matches_.size()), 318 std::min(AutocompleteProvider::kMaxMatches, shortcut_matches.size()),
174 matches_.end(), &AutocompleteMatch::MoreRelevant); 319 shortcut_matches.end(),
175 if (matches_.size() > AutocompleteProvider::kMaxMatches) { 320 [](const ShortcutMatch& elem1, const ShortcutMatch& elem2) {
176 matches_.erase(matches_.begin() + AutocompleteProvider::kMaxMatches, 321 // For equal-relevance matches, we sort alphabetically, so that
177 matches_.end()); 322 // providers who return multiple elements at the same priority get a
323 // "stable" sort across multiple updates.
Peter Kasting 2016/04/14 23:52:33 Nit: This comment is a bit odd since this code is
Alexander Yashkin 2016/04/15 09:14:14 Done.
324 return elem1.relevance == elem2.relevance ?
325 elem1.contents < elem2.contents :
326 elem1.relevance > elem2.relevance;
327 });
328 if (shortcut_matches.size() > AutocompleteProvider::kMaxMatches) {
329 shortcut_matches.erase(
330 shortcut_matches.begin() + AutocompleteProvider::kMaxMatches,
331 shortcut_matches.end());
178 } 332 }
179 // Guarantee that all scores are decreasing (but do not assign any scores 333 // Create and initialize autocomplete matches from shortcut matches.
Peter Kasting 2016/04/14 23:52:33 Nit: Wrap all comment lines as close to 80 columns
Alexander Yashkin 2016/04/15 09:14:14 Done.
180 // below 1). 334 // Also guarantee that all relevance scores are decreasing
181 for (ACMatches::iterator it = matches_.begin(); it != matches_.end(); ++it) { 335 // (but do not assign any scores below 1).
182 max_relevance = std::min(max_relevance, it->relevance); 336 WordMap terms_map(CreateWordMapForString(term_string));
183 it->relevance = max_relevance; 337 matches_.reserve(shortcut_matches.size());
338 for (ShortcutMatch& match : shortcut_matches) {
339 max_relevance = std::min(max_relevance, match.relevance);
340 match.relevance = max_relevance;
Peter Kasting 2016/04/14 23:52:33 Rather than update match.relevance, it would be be
Alexander Yashkin 2016/04/15 09:14:14 Done.
184 if (max_relevance > 1) 341 if (max_relevance > 1)
185 --max_relevance; 342 --max_relevance;
343 matches_.push_back(ShortcutMatchToACMatch(match, input, fixed_up_input,
344 term_string, terms_map));
186 } 345 }
187 } 346 }
188 347
189 AutocompleteMatch ShortcutsProvider::ShortcutToACMatch( 348 AutocompleteMatch ShortcutsProvider::ShortcutMatchToACMatch(
190 const ShortcutsDatabase::Shortcut& shortcut, 349 const ShortcutMatch& shortcut_match,
191 int relevance,
192 const AutocompleteInput& input, 350 const AutocompleteInput& input,
193 const base::string16& fixed_up_input_text) { 351 const base::string16& fixed_up_input_text,
352 const base::string16 term_string,
353 const WordMap& terms_map) {
194 DCHECK(!input.text().empty()); 354 DCHECK(!input.text().empty());
355 const ShortcutsDatabase::Shortcut& shortcut = *shortcut_match.shortcut;
195 AutocompleteMatch match; 356 AutocompleteMatch match;
196 match.provider = this; 357 match.provider = this;
197 match.relevance = relevance; 358 match.relevance = shortcut_match.relevance;
198 match.deletable = true; 359 match.deletable = true;
199 match.fill_into_edit = shortcut.match_core.fill_into_edit; 360 match.fill_into_edit = shortcut.match_core.fill_into_edit;
200 match.destination_url = shortcut.match_core.destination_url; 361 match.destination_url = shortcut.match_core.destination_url;
201 DCHECK(match.destination_url.is_valid()); 362 DCHECK(match.destination_url.is_valid());
202 match.contents = shortcut.match_core.contents; 363 match.contents = shortcut.match_core.contents;
203 match.contents_class = AutocompleteMatch::ClassificationsFromString( 364 match.contents_class = AutocompleteMatch::ClassificationsFromString(
204 shortcut.match_core.contents_class); 365 shortcut.match_core.contents_class);
205 match.description = shortcut.match_core.description; 366 match.description = shortcut.match_core.description;
206 match.description_class = AutocompleteMatch::ClassificationsFromString( 367 match.description_class = AutocompleteMatch::ClassificationsFromString(
207 shortcut.match_core.description_class); 368 shortcut.match_core.description_class);
208 match.transition = ui::PageTransitionFromInt(shortcut.match_core.transition); 369 match.transition = ui::PageTransitionFromInt(shortcut.match_core.transition);
209 match.type = static_cast<AutocompleteMatch::Type>(shortcut.match_core.type); 370 match.type = static_cast<AutocompleteMatch::Type>(shortcut.match_core.type);
210 match.keyword = shortcut.match_core.keyword; 371 match.keyword = shortcut.match_core.keyword;
372 match.stripped_destination_url = shortcut_match.stripped_destination_url;
Peter Kasting 2016/04/14 23:52:33 Does this matter? It seems likely that Autocomple
Alexander Yashkin 2016/04/15 09:14:14 Done.
211 match.RecordAdditionalInfo("number of hits", shortcut.number_of_hits); 373 match.RecordAdditionalInfo("number of hits", shortcut.number_of_hits);
212 match.RecordAdditionalInfo("last access time", shortcut.last_access_time); 374 match.RecordAdditionalInfo("last access time", shortcut.last_access_time);
213 match.RecordAdditionalInfo("original input text", 375 match.RecordAdditionalInfo("original input text",
214 base::UTF16ToUTF8(shortcut.text)); 376 base::UTF16ToUTF8(shortcut.text));
215 377
216 // Set |inline_autocompletion| and |allowed_to_be_default_match| if possible. 378 // Set |inline_autocompletion| and |allowed_to_be_default_match| if possible.
217 // If the match is a search query this is easy: simply check whether the 379 // If the match is a search query this is easy: simply check whether the
218 // user text is a prefix of the query. If the match is a navigation, we 380 // user text is a prefix of the query. If the match is a navigation, we
219 // assume the fill_into_edit looks something like a URL, so we use 381 // assume the fill_into_edit looks something like a URL, so we use
220 // URLPrefix::GetInlineAutocompleteOffset() to try and strip off any prefixes 382 // URLPrefix::GetInlineAutocompleteOffset() to try and strip off any prefixes
(...skipping 19 matching lines...) Expand all
240 input.text(), fixed_up_input_text, true, match.fill_into_edit); 402 input.text(), fixed_up_input_text, true, match.fill_into_edit);
241 if (inline_autocomplete_offset != base::string16::npos) { 403 if (inline_autocomplete_offset != base::string16::npos) {
242 match.inline_autocompletion = 404 match.inline_autocompletion =
243 match.fill_into_edit.substr(inline_autocomplete_offset); 405 match.fill_into_edit.substr(inline_autocomplete_offset);
244 match.allowed_to_be_default_match = 406 match.allowed_to_be_default_match =
245 !HistoryProvider::PreventInlineAutocomplete(input) || 407 !HistoryProvider::PreventInlineAutocomplete(input) ||
246 match.inline_autocompletion.empty(); 408 match.inline_autocompletion.empty();
247 } 409 }
248 } 410 }
249 match.EnsureUWYTIsAllowedToBeDefault(input, 411 match.EnsureUWYTIsAllowedToBeDefault(input,
250 client_->GetAcceptLanguages(), 412 languages_,
Peter Kasting 2016/04/14 23:52:33 Nit: Put on previous line
Alexander Yashkin 2016/04/15 09:14:14 Done.
251 client_->GetTemplateURLService()); 413 client_->GetTemplateURLService());
252 414
253 // Try to mark pieces of the contents and description as matches if they 415 // Try to mark pieces of the contents and description as matches if they
254 // appear in |input.text()|. 416 // appear in |input.text()|.
255 const base::string16 term_string = base::i18n::ToLower(input.text());
256 WordMap terms_map(CreateWordMapForString(term_string));
257 if (!terms_map.empty()) { 417 if (!terms_map.empty()) {
258 match.contents_class = ClassifyAllMatchesInString( 418 match.contents_class = ClassifyAllMatchesInString(
259 term_string, terms_map, match.contents, match.contents_class); 419 term_string, terms_map, match.contents, match.contents_class);
260 match.description_class = ClassifyAllMatchesInString( 420 match.description_class = ClassifyAllMatchesInString(
261 term_string, terms_map, match.description, match.description_class); 421 term_string, terms_map, match.description, match.description_class);
262 } 422 }
263 return match; 423 return match;
264 } 424 }
265 425
266 // static
267 ShortcutsProvider::WordMap ShortcutsProvider::CreateWordMapForString(
268 const base::string16& text) {
269 // First, convert |text| to a vector of the unique words in it.
270 WordMap word_map;
271 base::i18n::BreakIterator word_iter(text,
272 base::i18n::BreakIterator::BREAK_WORD);
273 if (!word_iter.Init())
274 return word_map;
275 std::vector<base::string16> words;
276 while (word_iter.Advance()) {
277 if (word_iter.IsWord())
278 words.push_back(word_iter.GetString());
279 }
280 if (words.empty())
281 return word_map;
282 std::sort(words.begin(), words.end());
283 words.erase(std::unique(words.begin(), words.end()), words.end());
284
285 // Now create a map from (first character) to (words beginning with that
286 // character). We insert in reverse lexicographical order and rely on the
287 // multimap preserving insertion order for values with the same key. (This
288 // is mandated in C++11, and part of that decision was based on a survey of
289 // existing implementations that found that it was already true everywhere.)
290 std::reverse(words.begin(), words.end());
291 for (std::vector<base::string16>::const_iterator i(words.begin());
292 i != words.end(); ++i)
293 word_map.insert(std::make_pair((*i)[0], *i));
294 return word_map;
295 }
296
297 // static
298 ACMatchClassifications ShortcutsProvider::ClassifyAllMatchesInString(
299 const base::string16& find_text,
300 const WordMap& find_words,
301 const base::string16& text,
302 const ACMatchClassifications& original_class) {
303 DCHECK(!find_text.empty());
304 DCHECK(!find_words.empty());
305
306 // The code below assumes |text| is nonempty and therefore the resulting
307 // classification vector should always be nonempty as well. Returning early
308 // if |text| is empty assures we'll return the (correct) empty vector rather
309 // than a vector with a single (0, NONE) match.
310 if (text.empty())
311 return original_class;
312
313 // First check whether |text| begins with |find_text| and mark that whole
314 // section as a match if so.
315 base::string16 text_lowercase(base::i18n::ToLower(text));
316 ACMatchClassifications match_class;
317 size_t last_position = 0;
318 if (base::StartsWith(text_lowercase, find_text,
319 base::CompareCase::SENSITIVE)) {
320 match_class.push_back(
321 ACMatchClassification(0, ACMatchClassification::MATCH));
322 last_position = find_text.length();
323 // If |text_lowercase| is actually equal to |find_text|, we don't need to
324 // (and in fact shouldn't) put a trailing NONE classification after the end
325 // of the string.
326 if (last_position < text_lowercase.length()) {
327 match_class.push_back(
328 ACMatchClassification(last_position, ACMatchClassification::NONE));
329 }
330 } else {
331 // |match_class| should start at position 0. If the first matching word is
332 // found at position 0, this will be popped from the vector further down.
333 match_class.push_back(
334 ACMatchClassification(0, ACMatchClassification::NONE));
335 }
336
337 // Now, starting with |last_position|, check each character in
338 // |text_lowercase| to see if we have words starting with that character in
339 // |find_words|. If so, check each of them to see if they match the portion
340 // of |text_lowercase| beginning with |last_position|. Accept the first
341 // matching word found (which should be the longest possible match at this
342 // location, given the construction of |find_words|) and add a MATCH region to
343 // |match_class|, moving |last_position| to be after the matching word. If we
344 // found no matching words, move to the next character and repeat.
345 while (last_position < text_lowercase.length()) {
346 std::pair<WordMap::const_iterator, WordMap::const_iterator> range(
347 find_words.equal_range(text_lowercase[last_position]));
348 size_t next_character = last_position + 1;
349 for (WordMap::const_iterator i(range.first); i != range.second; ++i) {
350 const base::string16& word = i->second;
351 size_t word_end = last_position + word.length();
352 if ((word_end <= text_lowercase.length()) &&
353 !text_lowercase.compare(last_position, word.length(), word)) {
354 // Collapse adjacent ranges into one.
355 if (match_class.back().offset == last_position)
356 match_class.pop_back();
357
358 AutocompleteMatch::AddLastClassificationIfNecessary(
359 &match_class, last_position, ACMatchClassification::MATCH);
360 if (word_end < text_lowercase.length()) {
361 match_class.push_back(
362 ACMatchClassification(word_end, ACMatchClassification::NONE));
363 }
364 last_position = word_end;
365 break;
366 }
367 }
368 last_position = std::max(last_position, next_character);
369 }
370
371 return AutocompleteMatch::MergeClassifications(original_class, match_class);
372 }
373
374 ShortcutsBackend::ShortcutMap::const_iterator ShortcutsProvider::FindFirstMatch( 426 ShortcutsBackend::ShortcutMap::const_iterator ShortcutsProvider::FindFirstMatch(
375 const base::string16& keyword, 427 const base::string16& keyword,
376 ShortcutsBackend* backend) { 428 ShortcutsBackend* backend) {
377 DCHECK(backend); 429 DCHECK(backend);
378 ShortcutsBackend::ShortcutMap::const_iterator it = 430 ShortcutsBackend::ShortcutMap::const_iterator it =
379 backend->shortcuts_map().lower_bound(keyword); 431 backend->shortcuts_map().lower_bound(keyword);
380 // Lower bound not necessarily matches the keyword, check for item pointed by 432 // Lower bound not necessarily matches the keyword, check for item pointed by
381 // the lower bound iterator to at least start with keyword. 433 // the lower bound iterator to at least start with keyword.
382 return ((it == backend->shortcuts_map().end()) || 434 return ((it == backend->shortcuts_map().end()) ||
383 base::StartsWith(it->first, keyword, base::CompareCase::SENSITIVE)) 435 base::StartsWith(it->first, keyword, base::CompareCase::SENSITIVE))
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after
416 const double kMaxDecaySpeedDivisor = 5.0; 468 const double kMaxDecaySpeedDivisor = 5.0;
417 const double kNumUsesPerDecaySpeedDivisorIncrement = 5.0; 469 const double kNumUsesPerDecaySpeedDivisorIncrement = 5.0;
418 double decay_divisor = std::min( 470 double decay_divisor = std::min(
419 kMaxDecaySpeedDivisor, 471 kMaxDecaySpeedDivisor,
420 (shortcut.number_of_hits + kNumUsesPerDecaySpeedDivisorIncrement - 1) / 472 (shortcut.number_of_hits + kNumUsesPerDecaySpeedDivisorIncrement - 1) /
421 kNumUsesPerDecaySpeedDivisorIncrement); 473 kNumUsesPerDecaySpeedDivisorIncrement);
422 474
423 return static_cast<int>((base_score / exp(decay_exponent / decay_divisor)) + 475 return static_cast<int>((base_score / exp(decay_exponent / decay_divisor)) +
424 0.5); 476 0.5);
425 } 477 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698