blob: 9aa9815358303c2b6d6aeef42b3c658805c943e1 [file] [log] [blame]
[email protected]4b56c602014-08-14 17:02:311// Copyright 2014 The Chromium Authors. All rights reserved.
[email protected]73c2b1632012-07-02 22:51:382// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
blundell2102f7c2015-07-09 10:00:535#include "components/omnibox/browser/autocomplete_result.h"
[email protected]73c2b1632012-07-02 22:51:386
7#include <algorithm>
8#include <iterator>
9
mpearsone18665262015-06-24 05:12:4010#include "base/command_line.h"
[email protected]73c2b1632012-07-02 22:51:3811#include "base/logging.h"
[email protected]8b38b392013-10-30 04:20:0312#include "base/strings/utf_string_conversions.h"
[email protected]332d17d22014-06-20 16:56:0313#include "components/metrics/proto/omnibox_event.pb.h"
[email protected]3dc75b12014-06-08 00:02:2214#include "components/metrics/proto/omnibox_input_type.pb.h"
blundell2102f7c2015-07-09 10:00:5315#include "components/omnibox/browser/autocomplete_input.h"
16#include "components/omnibox/browser/autocomplete_match.h"
17#include "components/omnibox/browser/autocomplete_provider.h"
a-v-y31940122016-04-16 23:19:1118#include "components/omnibox/browser/match_compare.h"
blundell2102f7c2015-07-09 10:00:5319#include "components/omnibox/browser/omnibox_field_trial.h"
20#include "components/omnibox/browser/omnibox_switches.h"
[email protected]1736b4ed2014-08-12 14:39:2221#include "components/search/search.h"
rsleevi24f64dc22015-08-07 21:39:2122#include "components/url_formatter/url_fixer.h"
[email protected]73c2b1632012-07-02 22:51:3823
[email protected]73c2b1632012-07-02 22:51:3824// static
25const size_t AutocompleteResult::kMaxMatches = 6;
[email protected]73c2b1632012-07-02 22:51:3826
27void AutocompleteResult::Selection::Clear() {
28 destination_url = GURL();
29 provider_affinity = NULL;
30 is_history_what_you_typed_match = false;
31}
32
33AutocompleteResult::AutocompleteResult() {
34 // Reserve space for the max number of matches we'll show.
35 matches_.reserve(kMaxMatches);
36
37 // It's probably safe to do this in the initializer list, but there's little
38 // penalty to doing it here and it ensures our object is fully constructed
39 // before calling member functions.
40 default_match_ = end();
41}
42
43AutocompleteResult::~AutocompleteResult() {}
44
[email protected]cbb7dbd2014-07-02 22:52:5745void AutocompleteResult::CopyOldMatches(
46 const AutocompleteInput& input,
47 const AutocompleteResult& old_matches,
48 TemplateURLService* template_url_service) {
[email protected]73c2b1632012-07-02 22:51:3849 if (old_matches.empty())
50 return;
51
52 if (empty()) {
53 // If we've got no matches we can copy everything from the last result.
54 CopyFrom(old_matches);
55 for (ACMatches::iterator i(begin()); i != end(); ++i)
56 i->from_previous = true;
57 return;
58 }
59
60 // In hopes of providing a stable popup we try to keep the number of matches
61 // per provider consistent. Other schemes (such as blindly copying the most
62 // relevant matches) typically result in many successive 'What You Typed'
63 // results filling all the matches, which looks awful.
64 //
65 // Instead of starting with the current matches and then adding old matches
66 // until we hit our overall limit, we copy enough old matches so that each
67 // provider has at least as many as before, and then use SortAndCull() to
68 // clamp globally. This way, old high-relevance matches will starve new
69 // low-relevance matches, under the assumption that the new matches will
70 // ultimately be similar. If the assumption holds, this prevents seeing the
71 // new low-relevance match appear and then quickly get pushed off the bottom;
72 // if it doesn't, then once the providers are done and we expire the old
73 // matches, the new ones will all become visible, so we won't have lost
74 // anything permanently.
75 ProviderToMatches matches_per_provider, old_matches_per_provider;
76 BuildProviderToMatches(&matches_per_provider);
77 old_matches.BuildProviderToMatches(&old_matches_per_provider);
78 for (ProviderToMatches::const_iterator i(old_matches_per_provider.begin());
79 i != old_matches_per_provider.end(); ++i) {
[email protected]d92134c2013-08-09 07:37:2980 MergeMatchesByProvider(input.current_page_classification(),
81 i->second, matches_per_provider[i->first]);
[email protected]73c2b1632012-07-02 22:51:3882 }
83
jshin1fb76462016-04-05 22:13:0384 SortAndCull(input, template_url_service);
[email protected]73c2b1632012-07-02 22:51:3885}
86
mpearson2a18bc72015-05-25 05:37:1087void AutocompleteResult::AppendMatches(const AutocompleteInput& input,
88 const ACMatches& matches) {
jifc43858f2015-06-17 19:11:4689 for (const auto& i : matches) {
[email protected]73c2b1632012-07-02 22:51:3890#ifndef NDEBUG
mpearson2a18bc72015-05-25 05:37:1091 DCHECK_EQ(AutocompleteMatch::SanitizeString(i.contents), i.contents);
92 DCHECK_EQ(AutocompleteMatch::SanitizeString(i.description),
93 i.description);
[email protected]73c2b1632012-07-02 22:51:3894#endif
mpearson2a18bc72015-05-25 05:37:1095 matches_.push_back(i);
mpearsonb8be515b2016-01-12 19:11:5196 if (!AutocompleteMatch::IsSearchType(i.type)) {
97 const OmniboxFieldTrial::EmphasizeTitlesCondition condition(
98 OmniboxFieldTrial::GetEmphasizeTitlesConditionForInput(input.type()));
99 bool emphasize = false;
100 switch (condition) {
101 case OmniboxFieldTrial::EMPHASIZE_WHEN_NONEMPTY:
102 emphasize = !i.description.empty();
103 break;
104 case OmniboxFieldTrial::EMPHASIZE_WHEN_TITLE_MATCHES:
105 emphasize = !i.description.empty() &&
106 AutocompleteMatch::HasMatchStyle(i.description_class);
107 break;
108 case OmniboxFieldTrial::EMPHASIZE_WHEN_ONLY_TITLE_MATCHES:
109 emphasize = !i.description.empty() &&
110 AutocompleteMatch::HasMatchStyle(i.description_class) &&
111 !AutocompleteMatch::HasMatchStyle(i.contents_class);
112 break;
113 case OmniboxFieldTrial::EMPHASIZE_NEVER:
114 break;
115 default:
116 NOTREACHED();
117 }
118 matches_.back().swap_contents_and_description = emphasize;
mpearsone18665262015-06-24 05:12:40119 }
mpearson2a18bc72015-05-25 05:37:10120 }
[email protected]73c2b1632012-07-02 22:51:38121 default_match_ = end();
122 alternate_nav_url_ = GURL();
123}
124
[email protected]cbb7dbd2014-07-02 22:52:57125void AutocompleteResult::SortAndCull(
126 const AutocompleteInput& input,
127 TemplateURLService* template_url_service) {
[email protected]73c2b1632012-07-02 22:51:38128 for (ACMatches::iterator i(matches_.begin()); i != matches_.end(); ++i)
jshin1fb76462016-04-05 22:13:03129 i->ComputeStrippedDestinationURL(input, template_url_service);
[email protected]73c2b1632012-07-02 22:51:38130
a-v-y31940122016-04-16 23:19:11131 SortAndDedupMatches(input.current_page_classification(), &matches_);
[email protected]73c2b1632012-07-02 22:51:38132
133 // Sort and trim to the most relevant kMaxMatches matches.
[email protected]d92134c2013-08-09 07:37:29134 size_t max_num_matches = std::min(kMaxMatches, matches_.size());
a-v-y31940122016-04-16 23:19:11135 CompareWithDemoteByType<AutocompleteMatch> comparing_object(
136 input.current_page_classification());
[email protected]b99ba4142014-04-17 03:37:43137 std::sort(matches_.begin(), matches_.end(), comparing_object);
mpearson43e0be22015-01-27 22:13:14138 if (!matches_.empty() && !matches_.begin()->allowed_to_be_default_match) {
[email protected]45f89a92013-08-12 13:41:36139 // Top match is not allowed to be the default match. Find the most
140 // relevant legal match and shift it to the front.
141 for (AutocompleteResult::iterator it = matches_.begin() + 1;
142 it != matches_.end(); ++it) {
mpearson43e0be22015-01-27 22:13:14143 if (it->allowed_to_be_default_match) {
[email protected]45f89a92013-08-12 13:41:36144 std::rotate(matches_.begin(), it, it + 1);
145 break;
146 }
147 }
148 }
[email protected]d92134c2013-08-09 07:37:29149 // In the process of trimming, drop all matches with a demoted relevance
150 // score of 0.
151 size_t num_matches;
152 for (num_matches = 0u; (num_matches < max_num_matches) &&
153 (comparing_object.GetDemotedRelevance(*match_at(num_matches)) > 0);
154 ++num_matches) {}
[email protected]73c2b1632012-07-02 22:51:38155 matches_.resize(num_matches);
156
[email protected]45f89a92013-08-12 13:41:36157 default_match_ = matches_.begin();
[email protected]23797be72013-10-22 00:24:25158
159 if (default_match_ != matches_.end()) {
[email protected]670d3232013-12-24 17:58:58160 const base::string16 debug_info =
161 base::ASCIIToUTF16("fill_into_edit=") +
162 default_match_->fill_into_edit +
163 base::ASCIIToUTF16(", provider=") +
164 ((default_match_->provider != NULL)
165 ? base::ASCIIToUTF16(default_match_->provider->GetName())
166 : base::string16()) +
167 base::ASCIIToUTF16(", input=") +
168 input.text();
mattreynolds2b530de2016-09-02 18:20:16169
170 // We should only get here with an empty omnibox for automatic suggestions
171 // on focus on the NTP; in these cases hitting enter should do nothing, so
172 // there should be no default match. Otherwise, we're doing automatic
173 // suggestions for the currently visible URL (and hitting enter should
174 // reload it), or the user is typing; in either of these cases, there should
175 // be a default match.
176 DCHECK_NE(input.text().empty(), default_match_->allowed_to_be_default_match)
177 << debug_info;
178
179 // For navigable default matches, make sure the destination type is what the
180 // user would expect given the input.
181 if (default_match_->allowed_to_be_default_match &&
182 default_match_->destination_url.is_valid()) {
[email protected]5889bfb2014-03-19 00:26:48183 if (AutocompleteMatch::IsSearchType(default_match_->type)) {
thomasanderson00687d02016-06-08 16:06:34184 // We shouldn't get query matches for URL inputs.
[email protected]3dc75b12014-06-08 00:02:22185 DCHECK_NE(metrics::OmniboxInputType::URL, input.type()) << debug_info;
[email protected]5889bfb2014-03-19 00:26:48186 } else {
[email protected]c7b8be02014-07-11 19:46:34187 // If the user explicitly typed a scheme, the default match should
188 // have the same scheme.
189 if ((input.type() == metrics::OmniboxInputType::URL) &&
190 input.parts().scheme.is_nonempty()) {
191 const std::string& in_scheme = base::UTF16ToUTF8(input.scheme());
192 const std::string& dest_scheme =
193 default_match_->destination_url.scheme();
rsleevi24f64dc22015-08-07 21:39:21194 DCHECK(url_formatter::IsEquivalentScheme(in_scheme, dest_scheme))
[email protected]8f6e5322014-08-11 08:06:08195 << debug_info;
[email protected]c7b8be02014-07-11 19:46:34196 }
[email protected]5889bfb2014-03-19 00:26:48197 }
[email protected]23797be72013-10-22 00:24:25198 }
199 }
[email protected]73c2b1632012-07-02 22:51:38200
201 // Set the alternate nav URL.
[email protected]45f89a92013-08-12 13:41:36202 alternate_nav_url_ = (default_match_ == matches_.end()) ?
[email protected]6f09ee02013-06-20 14:09:13203 GURL() : ComputeAlternateNavUrl(input, *default_match_);
[email protected]73c2b1632012-07-02 22:51:38204}
205
206bool AutocompleteResult::HasCopiedMatches() const {
207 for (ACMatches::const_iterator i(begin()); i != end(); ++i) {
208 if (i->from_previous)
209 return true;
210 }
211 return false;
212}
213
214size_t AutocompleteResult::size() const {
215 return matches_.size();
216}
217
218bool AutocompleteResult::empty() const {
219 return matches_.empty();
220}
221
222AutocompleteResult::const_iterator AutocompleteResult::begin() const {
223 return matches_.begin();
224}
225
226AutocompleteResult::iterator AutocompleteResult::begin() {
227 return matches_.begin();
228}
229
230AutocompleteResult::const_iterator AutocompleteResult::end() const {
231 return matches_.end();
232}
233
234AutocompleteResult::iterator AutocompleteResult::end() {
235 return matches_.end();
236}
237
238// Returns the match at the given index.
239const AutocompleteMatch& AutocompleteResult::match_at(size_t index) const {
240 DCHECK_LT(index, matches_.size());
241 return matches_[index];
242}
243
244AutocompleteMatch* AutocompleteResult::match_at(size_t index) {
245 DCHECK_LT(index, matches_.size());
246 return &matches_[index];
247}
248
[email protected]b6cb588f2013-12-19 03:31:06249bool AutocompleteResult::TopMatchIsStandaloneVerbatimMatch() const {
[email protected]0366dfd2014-03-26 00:25:08250 if (empty() || !match_at(0).IsVerbatimType())
251 return false;
252
253 // Skip any copied matches, under the assumption that they'll be expired and
254 // disappear. We don't want this disappearance to cause the visibility of the
255 // top match to change.
256 for (const_iterator i(begin() + 1); i != end(); ++i) {
257 if (!i->from_previous)
258 return !i->IsVerbatimType();
259 }
260 return true;
[email protected]1fce7dd42013-08-06 02:29:17261}
262
[email protected]73c2b1632012-07-02 22:51:38263void AutocompleteResult::Reset() {
264 matches_.clear();
265 default_match_ = end();
266}
267
268void AutocompleteResult::Swap(AutocompleteResult* other) {
269 const size_t default_match_offset = default_match_ - begin();
270 const size_t other_default_match_offset =
271 other->default_match_ - other->begin();
272 matches_.swap(other->matches_);
273 default_match_ = begin() + other_default_match_offset;
274 other->default_match_ = other->begin() + default_match_offset;
275 alternate_nav_url_.Swap(&(other->alternate_nav_url_));
276}
277
278#ifndef NDEBUG
279void AutocompleteResult::Validate() const {
280 for (const_iterator i(begin()); i != end(); ++i)
281 i->Validate();
282}
283#endif
284
[email protected]31511bb2013-06-18 17:40:57285// static
286GURL AutocompleteResult::ComputeAlternateNavUrl(
287 const AutocompleteInput& input,
288 const AutocompleteMatch& match) {
[email protected]3dc75b12014-06-08 00:02:22289 return ((input.type() == metrics::OmniboxInputType::UNKNOWN) &&
[email protected]6f09ee02013-06-20 14:09:13290 (AutocompleteMatch::IsSearchType(match.type)) &&
mastiz7eddb5f72016-06-23 09:52:45291 !ui::PageTransitionCoreTypeIs(match.transition,
292 ui::PAGE_TRANSITION_KEYWORD) &&
[email protected]6f09ee02013-06-20 14:09:13293 (input.canonicalized_url() != match.destination_url)) ?
[email protected]31511bb2013-06-18 17:40:57294 input.canonicalized_url() : GURL();
295}
296
a-v-y31940122016-04-16 23:19:11297void AutocompleteResult::SortAndDedupMatches(
298 metrics::OmniboxEventProto::PageClassification page_classification,
299 ACMatches* matches) {
[email protected]8ba2c262014-04-09 20:32:47300 // Sort matches such that duplicate matches are consecutive.
a-v-y31940122016-04-16 23:19:11301 std::sort(matches->begin(), matches->end(),
302 DestinationSort<AutocompleteMatch>(page_classification));
[email protected]8ba2c262014-04-09 20:32:47303
a-v-y31940122016-04-16 23:19:11304 // Set duplicate_matches for the first match before erasing duplicate
305 // matches.
306 for (ACMatches::iterator i(matches->begin()); i != matches->end(); ++i) {
307 for (int j = 1; (i + j != matches->end()) &&
308 AutocompleteMatch::DestinationsEqual(*i, *(i + j));
309 ++j) {
310 AutocompleteMatch& dup_match(*(i + j));
311 i->duplicate_matches.insert(i->duplicate_matches.end(),
312 dup_match.duplicate_matches.begin(),
313 dup_match.duplicate_matches.end());
314 dup_match.duplicate_matches.clear();
315 i->duplicate_matches.push_back(dup_match);
[email protected]8ba2c262014-04-09 20:32:47316 }
317 }
318
319 // Erase duplicate matches.
320 matches->erase(std::unique(matches->begin(), matches->end(),
321 &AutocompleteMatch::DestinationsEqual),
322 matches->end());
323}
324
[email protected]d7681212013-07-12 08:06:06325void AutocompleteResult::CopyFrom(const AutocompleteResult& rhs) {
326 if (this == &rhs)
327 return;
328
329 matches_ = rhs.matches_;
330 // Careful! You can't just copy iterators from another container, you have to
331 // reconstruct them.
332 default_match_ = (rhs.default_match_ == rhs.end()) ?
333 end() : (begin() + (rhs.default_match_ - rhs.begin()));
334
335 alternate_nav_url_ = rhs.alternate_nav_url_;
336}
337
[email protected]73c2b1632012-07-02 22:51:38338void AutocompleteResult::BuildProviderToMatches(
339 ProviderToMatches* provider_to_matches) const {
340 for (ACMatches::const_iterator i(begin()); i != end(); ++i)
341 (*provider_to_matches)[i->provider].push_back(*i);
342}
343
344// static
345bool AutocompleteResult::HasMatchByDestination(const AutocompleteMatch& match,
346 const ACMatches& matches) {
347 for (ACMatches::const_iterator i(matches.begin()); i != matches.end(); ++i) {
348 if (i->destination_url == match.destination_url)
349 return true;
350 }
351 return false;
352}
353
[email protected]d92134c2013-08-09 07:37:29354void AutocompleteResult::MergeMatchesByProvider(
a-v-y31940122016-04-16 23:19:11355 metrics::OmniboxEventProto::PageClassification page_classification,
[email protected]d92134c2013-08-09 07:37:29356 const ACMatches& old_matches,
357 const ACMatches& new_matches) {
[email protected]73c2b1632012-07-02 22:51:38358 if (new_matches.size() >= old_matches.size())
359 return;
360
mpearsonc28dea42015-04-10 21:30:39361 // Prevent old matches from this provider from outranking new ones and
362 // becoming the default match by capping old matches' scores to be less than
363 // the highest-scoring allowed-to-be-default match from this provider.
364 ACMatches::const_iterator i = std::find_if(
365 new_matches.begin(), new_matches.end(),
366 [] (const AutocompleteMatch& m) {
367 return m.allowed_to_be_default_match;
368 });
369
370 // If the provider doesn't have any matches that are allowed-to-be-default,
371 // cap scores below the global allowed-to-be-default match.
372 // AutocompleteResult maintains the invariant that the first item in
373 // |matches_| is always such a match.
374 if (i == new_matches.end())
375 i = matches_.begin();
376
377 DCHECK(i->allowed_to_be_default_match);
378 const int max_relevance = i->relevance - 1;
379
[email protected]73c2b1632012-07-02 22:51:38380 // Because the goal is a visibly-stable popup, rather than one that preserves
381 // the highest-relevance matches, we copy in the lowest-relevance matches
382 // first. This means that within each provider's "group" of matches, any
383 // synchronous matches (which tend to have the highest scores) will
384 // "overwrite" the initial matches from that provider's previous results,
385 // minimally disturbing the rest of the matches.
mpearsonc28dea42015-04-10 21:30:39386 size_t delta = old_matches.size() - new_matches.size();
[email protected]73c2b1632012-07-02 22:51:38387 for (ACMatches::const_reverse_iterator i(old_matches.rbegin());
388 i != old_matches.rend() && delta > 0; ++i) {
389 if (!HasMatchByDestination(*i, new_matches)) {
390 AutocompleteMatch match = *i;
391 match.relevance = std::min(max_relevance, match.relevance);
392 match.from_previous = true;
[email protected]9ba66612014-06-12 16:54:38393 matches_.push_back(match);
[email protected]73c2b1632012-07-02 22:51:38394 delta--;
395 }
396 }
397}