Increase number of matches SearchProvider can return to the full popup size,
assuming:

(1) Instant Extended is enabled (so we can get metrics comparing before/after)
(2) The extra matches come from server-scored suggestions.  We allow the highest
ranking 3 non-verbatim matches to come from any source, but after that we only
allow server-scored matches (and verbatim matches) until we hit the result set
limit.

This requires extending the Result class in the search provider to track the
origin of each match's score, as well as recording this information in the
AutocompleteMatch's |additional_info| field.

Note that https://codereview.chromium.org/17391005/ will simplify this somewhat
since we'll be able to stop worrying about FinalizeInstantQuery() etc.

BUG=252506
TEST=With instant extended on, type a query that generates lots of suggestions and see you get a full dropdown worth.
[email protected], [email protected]

Review URL: https://codereview.chromium.org/17382015

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@208567 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/chrome/browser/autocomplete/search_provider.cc b/chrome/browser/autocomplete/search_provider.cc
index 45f7803..6c19708b 100644
--- a/chrome/browser/autocomplete/search_provider.cc
+++ b/chrome/browser/autocomplete/search_provider.cc
@@ -118,9 +118,11 @@
 // SearchProvider::Result -----------------------------------------------------
 
 SearchProvider::Result::Result(bool from_keyword_provider,
-                               int relevance)
+                               int relevance,
+                               bool relevance_from_server)
     : from_keyword_provider_(from_keyword_provider),
-      relevance_(relevance) {
+      relevance_(relevance),
+      relevance_from_server_(relevance_from_server) {
 }
 
 SearchProvider::Result::~Result() {
@@ -131,8 +133,9 @@
 
 SearchProvider::SuggestResult::SuggestResult(const string16& suggestion,
                                              bool from_keyword_provider,
-                                             int relevance)
-    : Result(from_keyword_provider, relevance),
+                                             int relevance,
+                                             bool relevance_from_server)
+    : Result(from_keyword_provider, relevance, relevance_from_server),
       suggestion_(suggestion) {
 }
 
@@ -159,8 +162,9 @@
     const GURL& url,
     const string16& description,
     bool from_keyword_provider,
-    int relevance)
-    : Result(from_keyword_provider, relevance),
+    int relevance,
+    bool relevance_from_server)
+    : Result(from_keyword_provider, relevance, relevance_from_server),
       url_(url),
       formatted_url_(AutocompleteInput::FormattedStringWithEquivalentMeaning(
           url, provider.StringForURLDisplay(url, true, false))),
@@ -196,9 +200,7 @@
 
 // SearchProvider::Results ----------------------------------------------------
 
-SearchProvider::Results::Results()
-    : has_suggested_relevance(false),
-      verbatim_relevance(-1) {
+SearchProvider::Results::Results() : verbatim_relevance(-1) {
 }
 
 SearchProvider::Results::~Results() {
@@ -207,7 +209,6 @@
 void SearchProvider::Results::Clear() {
   suggest_results.clear();
   navigation_results.clear();
-  has_suggested_relevance = false;
   verbatim_relevance = -1;
 }
 
@@ -215,14 +216,33 @@
   if (verbatim_relevance >= 0)
     return true;
 
-  return has_suggested_relevance;
+  // Right now either all results of one type will be server-scored or they will
+  // all be locally scored, but in case we change this later, we'll just check
+  // them all.
+  for (SuggestResults::const_iterator i(suggest_results.begin());
+       i != suggest_results.end(); ++i) {
+    if (i->relevance_from_server())
+      return true;
+  }
+  for (NavigationResults::const_iterator i(navigation_results.begin());
+       i != navigation_results.end(); ++i) {
+    if (i->relevance_from_server())
+      return true;
+  }
+
+  return false;
 }
+
+
 // SearchProvider -------------------------------------------------------------
 
 // static
 const int SearchProvider::kDefaultProviderURLFetcherID = 1;
 const int SearchProvider::kKeywordProviderURLFetcherID = 2;
 int SearchProvider::kMinimumTimeBetweenSuggestQueriesMs = 100;
+const char SearchProvider::kRelevanceFromServerKey[] = "relevance_from_server";
+const char SearchProvider::kTrue[] = "true";
+const char SearchProvider::kFalse[] = "false";
 
 SearchProvider::SearchProvider(AutocompleteProviderListener* listener,
                                Profile* profile)
@@ -394,27 +414,29 @@
   if (suggestion.type == INSTANT_SUGGESTION_SEARCH) {
     // Instant has a query suggestion. Rank it higher than SEARCH_WHAT_YOU_TYPED
     // so that it gets autocompleted.
-    const int verbatim_relevance = GetVerbatimRelevance();
+    bool relevance_from_server;
+    const int verbatim_relevance = GetVerbatimRelevance(&relevance_from_server);
     int did_not_accept_default_suggestion =
         default_results_.suggest_results.empty() ?
         TemplateURLRef::NO_SUGGESTIONS_AVAILABLE :
         TemplateURLRef::NO_SUGGESTION_CHOSEN;
     MatchMap match_map;
     AddMatchToMap(text, adjusted_input_text, verbatim_relevance + 1,
-                  AutocompleteMatchType::SEARCH_SUGGEST,
+                  relevance_from_server, AutocompleteMatchType::SEARCH_SUGGEST,
                   did_not_accept_default_suggestion, false, &match_map);
     if (!match_map.empty()) {
       matches_.push_back(match_map.begin()->second);
       results_updated = true;
     }
   } else {
-    // Instant has a URL suggestion. Rank it higher than URL_WHAT_YOU_TYPED so
-    // it gets autocompleted; use kNonURLVerbatimRelevance rather than
-    // verbatim_relevance so that the score does not change if the user keeps
-    // typing and the input changes from type UNKNOWN to URL.
+    // Instant has a URL suggestion. Rank it higher than other providers would
+    // rank URL_WHAT_YOU_TYPED so it gets autocompleted; use
+    // kNonURLVerbatimRelevance rather than |verbatim_relevance| so that the
+    // score does not change if the user keeps typing and the input changes from
+    // type UNKNOWN to URL.
     matches_.push_back(NavigationToMatch(NavigationResult(
         *this, GURL(UTF16ToUTF8(suggestion.text)), string16(), false,
-        kNonURLVerbatimRelevance + 1)));
+        kNonURLVerbatimRelevance + 1, false)));
     results_updated = true;
   }
 
@@ -869,11 +891,11 @@
   // and ease in reasoning about the invariants involved, this code
   // removes stales results from the keyword provider and default
   // provider independently.
-  RemoveStaleResults(input_.text(), GetVerbatimRelevance(),
+  RemoveStaleResults(input_.text(), GetVerbatimRelevance(NULL),
                      &default_results_.suggest_results,
                      &default_results_.navigation_results);
   if (!keyword_input_.text().empty()) {
-    RemoveStaleResults(keyword_input_.text(), GetKeywordVerbatimRelevance(),
+    RemoveStaleResults(keyword_input_.text(), GetKeywordVerbatimRelevance(NULL),
                        &keyword_results_.suggest_results,
                        &keyword_results_.navigation_results);
   } else {
@@ -889,7 +911,7 @@
   if (default_provider_suggestion_.type == INSTANT_SUGGESTION_URL) {
     // Description and relevance do not matter in the check for staleness.
     NavigationResult result(*this, GURL(default_provider_suggestion_.text),
-                            string16(), false, 100);
+                            string16(), false, 100, false);
     // If navigation suggestion is stale, clear |default_provider_suggestion_|.
     if (!result.IsInlineable(current_input))
       default_provider_suggestion_ = InstantSuggestion();
@@ -921,8 +943,6 @@
   ApplyCalculatedSuggestRelevance(&default_results_.suggest_results);
   ApplyCalculatedNavigationRelevance(&keyword_results_.navigation_results);
   ApplyCalculatedNavigationRelevance(&default_results_.navigation_results);
-  default_results_.has_suggested_relevance = false;
-  keyword_results_.has_suggested_relevance = false;
   default_results_.verbatim_relevance = -1;
   keyword_results_.verbatim_relevance = -1;
 }
@@ -933,6 +953,7 @@
     result.set_relevance(
         result.CalculateRelevance(input_, providers_.has_keyword_provider()) +
         (list->size() - i - 1));
+    result.set_relevance_from_server(false);
   }
 }
 
@@ -943,6 +964,7 @@
     result.set_relevance(
         result.CalculateRelevance(input_, providers_.has_keyword_provider()) +
         (list->size() - i - 1));
+    result.set_relevance_from_server(false);
   }
 }
 
@@ -995,7 +1017,6 @@
 
   // Reset suggested relevance information from the default provider.
   Results* results = is_keyword ? &keyword_results_ : &default_results_;
-  results->has_suggested_relevance = false;
   results->verbatim_relevance = -1;
 
   // 5th element: Optional key-value pairs from the Suggest server.
@@ -1042,13 +1063,13 @@
       if (url.is_valid()) {
         if (descriptions != NULL)
           descriptions->GetString(index, &title);
-        results->navigation_results.push_back(
-            NavigationResult(*this, url, title, is_keyword, relevance));
+        results->navigation_results.push_back(NavigationResult(
+            *this, url, title, is_keyword, relevance, true));
       }
     } else {
       // TODO(kochi): Improve calculator result presentation.
       results->suggest_results.push_back(
-          SuggestResult(result, is_keyword, relevance));
+          SuggestResult(result, is_keyword, relevance, true));
     }
   }
 
@@ -1056,8 +1077,6 @@
   if (relevances == NULL) {
     ApplyCalculatedSuggestRelevance(&results->suggest_results);
     ApplyCalculatedNavigationRelevance(&results->navigation_results);
-  } else {
-    results->has_suggested_relevance = true;
   }
   // Keep the result lists sorted.
   const CompareScoredResults comparator = CompareScoredResults();
@@ -1080,13 +1099,15 @@
       TemplateURLRef::NO_SUGGESTIONS_AVAILABLE :
       TemplateURLRef::NO_SUGGESTION_CHOSEN;
 
-  int verbatim_relevance = GetVerbatimRelevance();
+  bool relevance_from_server;
+  int verbatim_relevance = GetVerbatimRelevance(&relevance_from_server);
   int did_not_accept_default_suggestion =
       default_results_.suggest_results.empty() ?
       TemplateURLRef::NO_SUGGESTIONS_AVAILABLE :
       TemplateURLRef::NO_SUGGESTION_CHOSEN;
   if (verbatim_relevance > 0) {
     AddMatchToMap(input_.text(), input_.text(), verbatim_relevance,
+                  relevance_from_server,
                   AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED,
                   did_not_accept_default_suggestion, false, &map);
   }
@@ -1099,21 +1120,22 @@
     // to the keyword verbatim search query.  Do not create other matches
     // of type SEARCH_OTHER_ENGINE.
     if (keyword_url && !keyword_url->IsExtensionKeyword()) {
-      const int keyword_verbatim_relevance = GetKeywordVerbatimRelevance();
+      bool keyword_relevance_from_server;
+      const int keyword_verbatim_relevance =
+          GetKeywordVerbatimRelevance(&keyword_relevance_from_server);
       if (keyword_verbatim_relevance > 0) {
         AddMatchToMap(keyword_input_.text(), keyword_input_.text(),
-                      keyword_verbatim_relevance,
+                      keyword_verbatim_relevance, keyword_relevance_from_server,
                       AutocompleteMatchType::SEARCH_OTHER_ENGINE,
                       did_not_accept_keyword_suggestion, true, &map);
       }
     }
   }
-  const size_t verbatim_matches_size = map.size();
   if (!default_provider_suggestion_.text.empty() &&
       default_provider_suggestion_.type == INSTANT_SUGGESTION_SEARCH &&
       !input_.prevent_inline_autocomplete())
     AddMatchToMap(input_.text() + default_provider_suggestion_.text,
-                  input_.text(), verbatim_relevance + 1,
+                  input_.text(), verbatim_relevance + 1, relevance_from_server,
                   AutocompleteMatchType::SEARCH_SUGGEST,
                   did_not_accept_default_suggestion, false, &map);
 
@@ -1125,31 +1147,58 @@
   AddSuggestResultsToMap(keyword_results_.suggest_results, &map);
   AddSuggestResultsToMap(default_results_.suggest_results, &map);
 
-  // Now add the most relevant matches from the map to |matches_|.
-  matches_.clear();
+  ACMatches matches;
   for (MatchMap::const_iterator i(map.begin()); i != map.end(); ++i)
-    matches_.push_back(i->second);
+    matches.push_back(i->second);
 
   if (!default_provider_suggestion_.text.empty() &&
       default_provider_suggestion_.type == INSTANT_SUGGESTION_URL &&
       !input_.prevent_inline_autocomplete()) {
     // See comment in FinalizeInstantQuery() for why we don't use
     // |verbatim_relevance| here.
-    matches_.push_back(NavigationToMatch(NavigationResult(
+    matches.push_back(NavigationToMatch(NavigationResult(
         *this, GURL(UTF16ToUTF8(default_provider_suggestion_.text)), string16(),
-        false, kNonURLVerbatimRelevance + 1)));
+        false, kNonURLVerbatimRelevance + 1, false)));
   }
-  AddNavigationResultsToMatches(keyword_results_.navigation_results);
-  AddNavigationResultsToMatches(default_results_.navigation_results);
+  AddNavigationResultsToMatches(keyword_results_.navigation_results, &matches);
+  AddNavigationResultsToMatches(default_results_.navigation_results, &matches);
 
-  // Allow additional match(es) for verbatim results if present.
-  const size_t max_total_matches = kMaxMatches + verbatim_matches_size;
-  std::partial_sort(matches_.begin(),
-      matches_.begin() + std::min(max_total_matches, matches_.size()),
-      matches_.end(), &AutocompleteMatch::MoreRelevant);
+  // Now add the most relevant matches to |matches_|.  We take up to kMaxMatches
+  // suggest/navsuggest matches, regardless of origin.  If Instant Extended is
+  // enabled and we have server-provided (and thus hopefully more accurate)
+  // scores for some suggestions, we allow more of those, until we reach
+  // AutocompleteResult::kMaxMatches total matches (that is, enough to fill the
+  // whole popup).
+  //
+  // We will always return any verbatim matches, no matter how we obtained their
+  // scores, unless we have already accepted AutocompleteResult::kMaxMatches
+  // higher-scoring matches under the conditions above.
+  std::sort(matches.begin(), matches.end(), &AutocompleteMatch::MoreRelevant);
+  matches_.clear();
 
-  if (matches_.size() > max_total_matches)
-    matches_.resize(max_total_matches);
+  size_t num_suggestions = 0;
+  for (ACMatches::const_iterator i(matches.begin());
+       (i != matches.end()) &&
+           (matches_.size() < AutocompleteResult::kMaxMatches);
+       ++i) {
+    // SEARCH_OTHER_ENGINE is only used in the SearchProvider for the keyword
+    // verbatim result, so this condition basically means "if this match is a
+    // suggestion of some sort".
+    if ((i->type != AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED) &&
+        (i->type != AutocompleteMatchType::SEARCH_OTHER_ENGINE)) {
+      // If we've already hit the limit on non-server-scored suggestions, and
+      // this isn't a server-scored suggestion we can add, skip it.
+      if ((num_suggestions >= kMaxMatches) &&
+          (!chrome::IsInstantExtendedAPIEnabled() ||
+           (i->GetAdditionalInfo(kRelevanceFromServerKey) != kTrue))) {
+        continue;
+      }
+
+      ++num_suggestions;
+    }
+
+    matches_.push_back(*i);
+  }
 }
 
 bool SearchProvider::IsTopMatchNavigationInKeywordMode() const {
@@ -1186,7 +1235,6 @@
   // not create any other match of type SEARCH_OTHER_ENGINE.
   return
       matches_.front().type != AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED &&
-      matches_.front().type != AutocompleteMatchType::URL_WHAT_YOU_TYPED &&
       matches_.front().type != AutocompleteMatchType::SEARCH_OTHER_ENGINE &&
       matches_.front().inline_autocomplete_offset == string16::npos &&
       matches_.front().fill_into_edit != input_.text();
@@ -1235,11 +1283,10 @@
       ConvertResultsToAutocompleteMatches();
     }
     if (IsTopMatchNotInlinable()) {
-      // Disregard suggested relevances if the top match is not a verbatim
-      // match, inlinable, or URL_WHAT_YOU_TYPED (which may be top match
-      // regardless of inlining).  For example, input "foo" should not
-      // invoke a search for "bar", which would happen if the "bar" search
-      // match outranked all other matches.
+      // Disregard suggested relevances if the top match is not a verbatim match
+      // or inlinable.  For example, input "foo" should not invoke a search for
+      // "bar", which would happen if the "bar" search match outranked all other
+      // matches.
       ApplyCalculatedRelevance();
       ConvertResultsToAutocompleteMatches();
     }
@@ -1254,15 +1301,14 @@
 }
 
 void SearchProvider::AddNavigationResultsToMatches(
-    const NavigationResults& navigation_results) {
+    const NavigationResults& navigation_results,
+    ACMatches* matches) {
   for (NavigationResults::const_iterator it = navigation_results.begin();
         it != navigation_results.end(); ++it) {
-    matches_.push_back(NavigationToMatch(*it));
+    matches->push_back(NavigationToMatch(*it));
     // In the absence of suggested relevance scores, use only the single
     // highest-scoring result.  (The results are already sorted by relevance.)
-    if (!(it->from_keyword_provider() ?
-        keyword_results_.has_suggested_relevance :
-        default_results_.has_suggested_relevance))
+    if (!it->relevance_from_server())
       return;
   }
 }
@@ -1302,7 +1348,7 @@
                                          is_keyword);
   for (SuggestResults::const_iterator i(scored_results.begin());
        i != scored_results.end(); ++i) {
-    AddMatchToMap(i->suggestion(), input_text, i->relevance(),
+    AddMatchToMap(i->suggestion(), input_text, i->relevance(), false,
                   AutocompleteMatchType::SEARCH_HISTORY,
                   did_not_accept_suggestion,
                   is_keyword, map);
@@ -1347,7 +1393,8 @@
 
     int relevance = CalculateRelevanceForHistory(i->time, is_keyword,
                                                  prevent_inline_autocomplete);
-    scored_results.push_back(SuggestResult(i->term, is_keyword, relevance));
+    scored_results.push_back(
+        SuggestResult(i->term, is_keyword, relevance, false));
   }
 
   // History returns results sorted for us.  However, we may have docked some
@@ -1374,11 +1421,12 @@
     const bool is_keyword = results[i].from_keyword_provider();
     const string16& input = is_keyword ? keyword_input_.text() : input_.text();
     AddMatchToMap(results[i].suggestion(), input, results[i].relevance(),
+                  results[i].relevance_from_server(),
                   AutocompleteMatchType::SEARCH_SUGGEST, i, is_keyword, map);
   }
 }
 
-int SearchProvider::GetVerbatimRelevance() const {
+int SearchProvider::GetVerbatimRelevance(bool* relevance_from_server) const {
   // Use the suggested verbatim relevance score if it is non-negative (valid),
   // if inline autocomplete isn't prevented (always show verbatim on backspace),
   // and if it won't suppress verbatim, leaving no default provider matches.
@@ -1393,6 +1441,8 @@
       ((default_results_.verbatim_relevance > 0) ||
        !default_results_.suggest_results.empty() ||
        !default_results_.navigation_results.empty());
+  if (relevance_from_server)
+    *relevance_from_server = use_server_relevance;
   return use_server_relevance ?
       default_results_.verbatim_relevance : CalculateRelevanceForVerbatim();
 }
@@ -1420,7 +1470,8 @@
   }
 }
 
-int SearchProvider::GetKeywordVerbatimRelevance() const {
+int SearchProvider::GetKeywordVerbatimRelevance(
+    bool* relevance_from_server) const {
   // Use the suggested verbatim relevance score if it is non-negative (valid),
   // if inline autocomplete isn't prevented (always show verbatim on backspace),
   // and if it won't suppress verbatim, leaving no keyword provider matches.
@@ -1435,6 +1486,8 @@
       ((keyword_results_.verbatim_relevance > 0) ||
        !keyword_results_.suggest_results.empty() ||
        !keyword_results_.navigation_results.empty());
+  if (relevance_from_server)
+    *relevance_from_server = use_server_relevance;
   return use_server_relevance ?
       keyword_results_.verbatim_relevance :
       CalculateRelevanceForKeywordVerbatim(keyword_input_.type(),
@@ -1479,6 +1532,7 @@
 void SearchProvider::AddMatchToMap(const string16& query_string,
                                    const string16& input_text,
                                    int relevance,
+                                   bool relevance_from_server,
                                    AutocompleteMatch::Type type,
                                    int accepted_suggestion,
                                    bool is_keyword,
@@ -1487,7 +1541,7 @@
   // -- they should always use grey text if they are to autocomplete at all. So
   // we clamp non-verbatim results to just below the verbatim score to ensure
   // that none of them are inline autocompleted.
-  // TODO(pkasting): This shouldn't depend on instant extended.  If we think
+  // TODO(pkasting): This shouldn't depend on Instant Extended.  If we think
   // this change is a win, let's change the suggest server to not send
   // high-ranking suggest scores, and change CalculateRelevanceForHistory() to
   // use lower curves.  This code is also buggy as-is because it can demote many
@@ -1505,6 +1559,8 @@
       is_keyword, keyword, omnibox_start_margin_);
   if (!match.destination_url.is_valid())
     return;
+  match.RecordAdditionalInfo(kRelevanceFromServerKey,
+                             relevance_from_server ? kTrue : kFalse);
 
   // Try to add |match| to |map|.  If a match for |query_string| is already in
   // |map|, replace it if |match| is more relevant.
@@ -1580,17 +1636,28 @@
   match.description = navigation.description();
   AutocompleteMatch::ClassifyMatchInString(input, match.description,
       ACMatchClassification::NONE, &match.description_class);
+
+  match.RecordAdditionalInfo(
+      kRelevanceFromServerKey,
+      navigation.relevance_from_server() ? kTrue : kFalse);
+
   return match;
 }
 
 void SearchProvider::DemoteKeywordNavigationMatchesPastTopQuery() {
   // First, determine the maximum score of any keyword query match (verbatim or
   // query suggestion).
-  int max_query_relevance = GetKeywordVerbatimRelevance();
+  bool relevance_from_server;
+  int max_query_relevance = GetKeywordVerbatimRelevance(&relevance_from_server);
   if (!keyword_results_.suggest_results.empty()) {
-    max_query_relevance =
-        std::max(keyword_results_.suggest_results.front().relevance(),
-                 max_query_relevance);
+    const SuggestResult& top_keyword = keyword_results_.suggest_results.front();
+    const int suggest_relevance = top_keyword.relevance();
+    if (suggest_relevance > max_query_relevance) {
+      max_query_relevance = suggest_relevance;
+      relevance_from_server = top_keyword.relevance_from_server();
+    } else if (suggest_relevance == max_query_relevance) {
+      relevance_from_server |= top_keyword.relevance_from_server();
+    }
   }
   // If no query is supposed to appear, then navigational matches cannot
   // be demoted past it.  Get rid of suggested relevance scores for
@@ -1614,6 +1681,7 @@
       return;
     max_query_relevance = std::max(max_query_relevance - 1, 0);
     it->set_relevance(max_query_relevance);
+    it->set_relevance_from_server(relevance_from_server);
   }
 }