Chromium Code Reviews| 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 |
| 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 Loading... | |
| 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 Loading... | |
| 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 Loading... | |
| 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 } |
| OLD | NEW |