| OLD | NEW |
| (Empty) |
| 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 | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "chrome/browser/autocomplete/history_quick_provider.h" | |
| 6 | |
| 7 #include <vector> | |
| 8 | |
| 9 #include "base/basictypes.h" | |
| 10 #include "base/debug/crash_logging.h" | |
| 11 #include "base/i18n/break_iterator.h" | |
| 12 #include "base/logging.h" | |
| 13 #include "base/metrics/field_trial.h" | |
| 14 #include "base/prefs/pref_service.h" | |
| 15 #include "base/strings/string_number_conversions.h" | |
| 16 #include "base/strings/string_util.h" | |
| 17 #include "base/strings/utf_string_conversions.h" | |
| 18 #include "chrome/browser/autocomplete/in_memory_url_index.h" | |
| 19 #include "components/bookmarks/browser/bookmark_model.h" | |
| 20 #include "components/history/core/browser/history_database.h" | |
| 21 #include "components/history/core/browser/history_service.h" | |
| 22 #include "components/metrics/proto/omnibox_input_type.pb.h" | |
| 23 #include "components/omnibox/autocomplete_match_type.h" | |
| 24 #include "components/omnibox/autocomplete_provider_client.h" | |
| 25 #include "components/omnibox/autocomplete_result.h" | |
| 26 #include "components/omnibox/history_url_provider.h" | |
| 27 #include "components/omnibox/crash_keys.h" | |
| 28 #include "components/omnibox/in_memory_url_index_types.h" | |
| 29 #include "components/omnibox/omnibox_field_trial.h" | |
| 30 #include "components/search_engines/template_url.h" | |
| 31 #include "components/search_engines/template_url_service.h" | |
| 32 #include "net/base/escape.h" | |
| 33 #include "net/base/net_util.h" | |
| 34 #include "net/base/registry_controlled_domains/registry_controlled_domain.h" | |
| 35 #include "url/third_party/mozilla/url_parse.h" | |
| 36 #include "url/url_util.h" | |
| 37 | |
| 38 bool HistoryQuickProvider::disabled_ = false; | |
| 39 | |
| 40 HistoryQuickProvider::HistoryQuickProvider( | |
| 41 AutocompleteProviderClient* client, | |
| 42 InMemoryURLIndex* in_memory_url_index) | |
| 43 : HistoryProvider(AutocompleteProvider::TYPE_HISTORY_QUICK, client), | |
| 44 languages_(client->AcceptLanguages()), | |
| 45 in_memory_url_index_(in_memory_url_index) { | |
| 46 } | |
| 47 | |
| 48 void HistoryQuickProvider::Start(const AutocompleteInput& input, | |
| 49 bool minimal_changes, | |
| 50 bool called_due_to_focus) { | |
| 51 matches_.clear(); | |
| 52 if (disabled_ || called_due_to_focus) | |
| 53 return; | |
| 54 | |
| 55 // Don't bother with INVALID and FORCED_QUERY. | |
| 56 if ((input.type() == metrics::OmniboxInputType::INVALID) || | |
| 57 (input.type() == metrics::OmniboxInputType::FORCED_QUERY)) | |
| 58 return; | |
| 59 | |
| 60 autocomplete_input_ = input; | |
| 61 | |
| 62 // TODO(pkasting): We should just block here until this loads. Any time | |
| 63 // someone unloads the history backend, we'll get inconsistent inline | |
| 64 // autocomplete behavior here. | |
| 65 if (in_memory_url_index_) { | |
| 66 DoAutocomplete(); | |
| 67 } | |
| 68 } | |
| 69 | |
| 70 HistoryQuickProvider::~HistoryQuickProvider() { | |
| 71 } | |
| 72 | |
| 73 void HistoryQuickProvider::DoAutocomplete() { | |
| 74 // Get the matching URLs from the DB. | |
| 75 ScoredHistoryMatches matches = in_memory_url_index_->HistoryItemsForTerms( | |
| 76 autocomplete_input_.text(), autocomplete_input_.cursor_position(), | |
| 77 AutocompleteProvider::kMaxMatches); | |
| 78 if (matches.empty()) | |
| 79 return; | |
| 80 | |
| 81 // Figure out if HistoryURL provider has a URL-what-you-typed match | |
| 82 // that ought to go first and what its score will be. | |
| 83 bool will_have_url_what_you_typed_match_first = false; | |
| 84 int url_what_you_typed_match_score = -1; // undefined | |
| 85 // These are necessary (but not sufficient) conditions for the omnibox | |
| 86 // input to be a URL-what-you-typed match. The username test checks that | |
| 87 // either the username does not exist (a regular URL such as http://site/) | |
| 88 // or, if the username exists (http://user@site/), there must be either | |
| 89 // a password or a port. Together these exclude pure username@site | |
| 90 // inputs because these are likely to be an e-mail address. HistoryURL | |
| 91 // provider won't promote the URL-what-you-typed match to first | |
| 92 // for these inputs. | |
| 93 const bool can_have_url_what_you_typed_match_first = | |
| 94 (autocomplete_input_.type() != metrics::OmniboxInputType::QUERY) && | |
| 95 (!autocomplete_input_.parts().username.is_nonempty() || | |
| 96 autocomplete_input_.parts().password.is_nonempty() || | |
| 97 autocomplete_input_.parts().path.is_nonempty()); | |
| 98 if (can_have_url_what_you_typed_match_first) { | |
| 99 history::HistoryService* const history_service = client()->HistoryService(); | |
| 100 // We expect HistoryService to be available. In case it's not, | |
| 101 // (e.g., due to Profile corruption) we let HistoryQuick provider | |
| 102 // completions (which may be available because it's a different | |
| 103 // data structure) compete with the URL-what-you-typed match as | |
| 104 // normal. | |
| 105 if (history_service) { | |
| 106 history::URLDatabase* url_db = history_service->InMemoryDatabase(); | |
| 107 // url_db can be NULL if it hasn't finished initializing (or | |
| 108 // failed to to initialize). In this case, we let HistoryQuick | |
| 109 // provider completions compete with the URL-what-you-typed | |
| 110 // match as normal. | |
| 111 if (url_db) { | |
| 112 const std::string host(base::UTF16ToUTF8( | |
| 113 autocomplete_input_.text().substr( | |
| 114 autocomplete_input_.parts().host.begin, | |
| 115 autocomplete_input_.parts().host.len))); | |
| 116 // We want to put the URL-what-you-typed match first if either | |
| 117 // * the user visited the URL before (intranet or internet). | |
| 118 // * it's a URL on a host that user visited before and this | |
| 119 // is the root path of the host. (If the user types some | |
| 120 // of a path--more than a simple "/"--we let autocomplete compete | |
| 121 // normally with the URL-what-you-typed match.) | |
| 122 // TODO(mpearson): Remove this hacky code and simply score URL-what- | |
| 123 // you-typed in some sane way relative to possible completions: | |
| 124 // URL-what-you-typed should get some sort of a boost relative | |
| 125 // to completions, but completions should naturally win if | |
| 126 // they're a lot more popular. In this process, if the input | |
| 127 // is a bare intranet hostname that has been visited before, we | |
| 128 // may want to enforce that the only completions that can outscore | |
| 129 // the URL-what-you-typed match are on the same host (i.e., aren't | |
| 130 // from a longer internet hostname for which the omnibox input is | |
| 131 // a prefix). | |
| 132 if (url_db->GetRowForURL( | |
| 133 autocomplete_input_.canonicalized_url(), NULL) != 0) { | |
| 134 // We visited this URL before. | |
| 135 will_have_url_what_you_typed_match_first = true; | |
| 136 // HistoryURLProvider gives visited what-you-typed URLs a high score. | |
| 137 url_what_you_typed_match_score = | |
| 138 HistoryURLProvider::kScoreForBestInlineableResult; | |
| 139 } else if (url_db->IsTypedHost(host) && | |
| 140 (!autocomplete_input_.parts().path.is_nonempty() || | |
| 141 ((autocomplete_input_.parts().path.len == 1) && | |
| 142 (autocomplete_input_.text()[ | |
| 143 autocomplete_input_.parts().path.begin] == '/'))) && | |
| 144 !autocomplete_input_.parts().query.is_nonempty() && | |
| 145 !autocomplete_input_.parts().ref.is_nonempty()) { | |
| 146 // Not visited, but we've seen the host before. | |
| 147 will_have_url_what_you_typed_match_first = true; | |
| 148 const size_t registry_length = | |
| 149 net::registry_controlled_domains::GetRegistryLength( | |
| 150 host, | |
| 151 net::registry_controlled_domains::EXCLUDE_UNKNOWN_REGISTRIES, | |
| 152 net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES); | |
| 153 if (registry_length == 0) { | |
| 154 // Known intranet hosts get one score. | |
| 155 url_what_you_typed_match_score = | |
| 156 HistoryURLProvider::kScoreForUnvisitedIntranetResult; | |
| 157 } else { | |
| 158 // Known internet hosts get another. | |
| 159 url_what_you_typed_match_score = | |
| 160 HistoryURLProvider::kScoreForWhatYouTypedResult; | |
| 161 } | |
| 162 } | |
| 163 } | |
| 164 } | |
| 165 } | |
| 166 | |
| 167 // Loop over every result and add it to matches_. In the process, | |
| 168 // guarantee that scores are decreasing. |max_match_score| keeps | |
| 169 // track of the highest score we can assign to any later results we | |
| 170 // see. Also, reduce |max_match_score| if we think there will be | |
| 171 // a URL-what-you-typed match. (We want URL-what-you-typed matches for | |
| 172 // visited URLs to beat out any longer URLs, no matter how frequently | |
| 173 // they're visited.) The strength of this reduction depends on the | |
| 174 // likely score for the URL-what-you-typed result. | |
| 175 | |
| 176 // |template_url_service| or |template_url| can be NULL in unit tests. | |
| 177 TemplateURLService* template_url_service = client()->GetTemplateURLService(); | |
| 178 TemplateURL* template_url = template_url_service ? | |
| 179 template_url_service->GetDefaultSearchProvider() : NULL; | |
| 180 int max_match_score = matches.begin()->raw_score; | |
| 181 if (will_have_url_what_you_typed_match_first) { | |
| 182 max_match_score = std::min(max_match_score, | |
| 183 url_what_you_typed_match_score - 1); | |
| 184 } | |
| 185 for (ScoredHistoryMatches::const_iterator match_iter = matches.begin(); | |
| 186 match_iter != matches.end(); ++match_iter) { | |
| 187 const ScoredHistoryMatch& history_match(*match_iter); | |
| 188 // Culls results corresponding to queries from the default search engine. | |
| 189 // These are low-quality, difficult-to-understand matches for users, and the | |
| 190 // SearchProvider should surface past queries in a better way anyway. | |
| 191 if (!template_url || | |
| 192 !template_url->IsSearchURL(history_match.url_info.url(), | |
| 193 template_url_service->search_terms_data())) { | |
| 194 // Set max_match_score to the score we'll assign this result: | |
| 195 max_match_score = std::min(max_match_score, history_match.raw_score); | |
| 196 matches_.push_back(QuickMatchToACMatch(history_match, max_match_score)); | |
| 197 // Mark this max_match_score as being used: | |
| 198 max_match_score--; | |
| 199 } | |
| 200 } | |
| 201 } | |
| 202 | |
| 203 AutocompleteMatch HistoryQuickProvider::QuickMatchToACMatch( | |
| 204 const ScoredHistoryMatch& history_match, | |
| 205 int score) { | |
| 206 const history::URLRow& info = history_match.url_info; | |
| 207 AutocompleteMatch match( | |
| 208 this, score, !!info.visit_count(), | |
| 209 history_match.url_matches.empty() ? | |
| 210 AutocompleteMatchType::HISTORY_TITLE : | |
| 211 AutocompleteMatchType::HISTORY_URL); | |
| 212 match.typed_count = info.typed_count(); | |
| 213 match.destination_url = info.url(); | |
| 214 DCHECK(match.destination_url.is_valid()); | |
| 215 | |
| 216 // Format the URL autocomplete presentation. | |
| 217 const net::FormatUrlTypes format_types = net::kFormatUrlOmitAll & | |
| 218 ~(!history_match.match_in_scheme ? 0 : net::kFormatUrlOmitHTTP); | |
| 219 match.fill_into_edit = | |
| 220 AutocompleteInput::FormattedStringWithEquivalentMeaning( | |
| 221 info.url(), | |
| 222 net::FormatUrl(info.url(), languages_, format_types, | |
| 223 net::UnescapeRule::SPACES, NULL, NULL, NULL), | |
| 224 client()->SchemeClassifier()); | |
| 225 std::vector<size_t> offsets = | |
| 226 OffsetsFromTermMatches(history_match.url_matches); | |
| 227 base::OffsetAdjuster::Adjustments adjustments; | |
| 228 match.contents = net::FormatUrlWithAdjustments( | |
| 229 info.url(), languages_, format_types, net::UnescapeRule::SPACES, NULL, | |
| 230 NULL, &adjustments); | |
| 231 base::OffsetAdjuster::AdjustOffsets(adjustments, &offsets); | |
| 232 TermMatches new_matches = | |
| 233 ReplaceOffsetsInTermMatches(history_match.url_matches, offsets); | |
| 234 match.contents_class = | |
| 235 SpansFromTermMatch(new_matches, match.contents.length(), true); | |
| 236 | |
| 237 // Set |inline_autocompletion| and |allowed_to_be_default_match| if possible. | |
| 238 if (history_match.can_inline) { | |
| 239 base::debug::ScopedCrashKey crash_info( | |
| 240 omnibox::kBug464926CrashKey, | |
| 241 info.url().spec().substr(0, 30) + " " + | |
| 242 base::UTF16ToUTF8(autocomplete_input_.text()).substr(0, 20) + " " + | |
| 243 base::SizeTToString(history_match.url_matches.size()) + " " + | |
| 244 base::SizeTToString(offsets.size()));; | |
| 245 CHECK(!new_matches.empty()); | |
| 246 size_t inline_autocomplete_offset = new_matches[0].offset + | |
| 247 new_matches[0].length; | |
| 248 // |inline_autocomplete_offset| may be beyond the end of the | |
| 249 // |fill_into_edit| if the user has typed an URL with a scheme and the | |
| 250 // last character typed is a slash. That slash is removed by the | |
| 251 // FormatURLWithOffsets call above. | |
| 252 if (inline_autocomplete_offset < match.fill_into_edit.length()) { | |
| 253 match.inline_autocompletion = | |
| 254 match.fill_into_edit.substr(inline_autocomplete_offset); | |
| 255 } | |
| 256 match.allowed_to_be_default_match = match.inline_autocompletion.empty() || | |
| 257 !PreventInlineAutocomplete(autocomplete_input_); | |
| 258 } | |
| 259 match.EnsureUWYTIsAllowedToBeDefault(autocomplete_input_.canonicalized_url(), | |
| 260 client()->GetTemplateURLService()); | |
| 261 | |
| 262 // Format the description autocomplete presentation. | |
| 263 match.description = info.title(); | |
| 264 match.description_class = SpansFromTermMatch( | |
| 265 history_match.title_matches, match.description.length(), false); | |
| 266 | |
| 267 match.RecordAdditionalInfo("typed count", info.typed_count()); | |
| 268 match.RecordAdditionalInfo("visit count", info.visit_count()); | |
| 269 match.RecordAdditionalInfo("last visit", info.last_visit()); | |
| 270 | |
| 271 return match; | |
| 272 } | |
| OLD | NEW |