(Try 2) Reland InstantExtended: Simplified Instant search.
Enables result page to prefetch results for certain search suggestions via field trial.
Omnibox suggest response includes metadata regarding the prefetch query. SearchProvider parses the suggest response and records the additional details in AutocompleteMatch. When the user types in the omnibox, browser sends a message to the renderer to set the prefetch query on the server which in turn sends the prefetched search results back to Chrome.
Original Review CL: crrev.com/19772008
Reverted CL: crrev.com/23511003
BUG=269185
TEST=none
[email protected],[email protected]
Review URL: https://chromiumcodereview.appspot.com/23620004
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@219949 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/chrome/browser/autocomplete/search_provider.cc b/chrome/browser/autocomplete/search_provider.cc
index 38f0b9e..5b366b6 100644
--- a/chrome/browser/autocomplete/search_provider.cc
+++ b/chrome/browser/autocomplete/search_provider.cc
@@ -136,9 +136,11 @@
SearchProvider::SuggestResult::SuggestResult(const string16& suggestion,
bool from_keyword_provider,
int relevance,
- bool relevance_from_server)
+ bool relevance_from_server,
+ bool should_prefetch)
: Result(from_keyword_provider, relevance, relevance_from_server),
- suggestion_(suggestion) {
+ suggestion_(suggestion),
+ should_prefetch_(should_prefetch) {
}
SearchProvider::SuggestResult::~SuggestResult() {
@@ -212,6 +214,7 @@
suggest_results.clear();
navigation_results.clear();
verbatim_relevance = -1;
+ metadata.clear();
}
bool SearchProvider::Results::HasServerProvidedScores() const {
@@ -243,6 +246,8 @@
const int SearchProvider::kKeywordProviderURLFetcherID = 2;
int SearchProvider::kMinimumTimeBetweenSuggestQueriesMs = 100;
const char SearchProvider::kRelevanceFromServerKey[] = "relevance_from_server";
+const char SearchProvider::kShouldPrefetchKey[] = "should_prefetch";
+const char SearchProvider::kSuggestMetadataKey[] = "suggest_metadata";
const char SearchProvider::kTrue[] = "true";
const char SearchProvider::kFalse[] = "false";
@@ -352,6 +357,16 @@
return match;
}
+// static
+bool SearchProvider::ShouldPrefetch(const AutocompleteMatch& match) {
+ return match.GetAdditionalInfo(kShouldPrefetchKey) == kTrue;
+}
+
+// static
+std::string SearchProvider::GetSuggestMetadata(const AutocompleteMatch& match) {
+ return match.GetAdditionalInfo(kSuggestMetadataKey);
+}
+
void SearchProvider::AddProviderInfo(ProvidersInfo* provider_info) const {
provider_info->push_back(metrics::OmniboxEventProto_ProviderInfo());
metrics::OmniboxEventProto_ProviderInfo& new_entry = provider_info->back();
@@ -881,6 +896,7 @@
ListValue* types = NULL;
ListValue* relevances = NULL;
DictionaryValue* extras = NULL;
+ int prefetch_index = -1;
if (root_list->GetDictionary(4, &extras)) {
extras->GetList("google:suggesttype", &types);
@@ -897,6 +913,16 @@
extras->GetBoolean("google:fieldtrialtriggered", &triggered);
field_trial_triggered_ |= triggered;
field_trial_triggered_in_session_ |= triggered;
+
+ // Extract the prefetch hint.
+ DictionaryValue* client_data = NULL;
+ if (extras->GetDictionary("google:clientdata", &client_data) && client_data)
+ client_data->GetInteger("phi", &prefetch_index);
+
+ // Store the metadata that came with the response in case we need to pass it
+ // along with the prefetch query to Instant.
+ JSONStringValueSerializer json_serializer(&results->metadata);
+ json_serializer.Serialize(*extras);
}
// Clear the previous results now that new results are available.
@@ -925,9 +951,10 @@
*this, url, title, is_keyword, relevance, true));
}
} else {
+ bool should_prefetch = static_cast<int>(index) == prefetch_index;
// TODO(kochi): Improve calculator result presentation.
results->suggest_results.push_back(
- SuggestResult(result, is_keyword, relevance, true));
+ SuggestResult(result, is_keyword, relevance, true, should_prefetch));
}
}
@@ -965,7 +992,7 @@
TemplateURLRef::NO_SUGGESTION_CHOSEN;
if (verbatim_relevance > 0) {
AddMatchToMap(input_.text(), input_.text(), verbatim_relevance,
- relevance_from_server,
+ relevance_from_server, false, std::string(),
AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED,
did_not_accept_default_suggestion, false, &map);
}
@@ -984,6 +1011,7 @@
if (keyword_verbatim_relevance > 0) {
AddMatchToMap(keyword_input_.text(), keyword_input_.text(),
keyword_verbatim_relevance, keyword_relevance_from_server,
+ false, std::string(),
AutocompleteMatchType::SEARCH_OTHER_ENGINE,
did_not_accept_keyword_suggestion, true, &map);
}
@@ -994,8 +1022,9 @@
AddHistoryResultsToMap(default_history_results_, false,
did_not_accept_default_suggestion, &map);
- AddSuggestResultsToMap(keyword_results_.suggest_results, &map);
- AddSuggestResultsToMap(default_results_.suggest_results, &map);
+ AddSuggestResultsToMap(keyword_results_.suggest_results, std::string(), &map);
+ AddSuggestResultsToMap(default_results_.suggest_results,
+ default_results_.metadata, &map);
ACMatches matches;
for (MatchMap::const_iterator i(map.begin()); i != map.end(); ++i)
@@ -1209,9 +1238,8 @@
for (SuggestResults::const_iterator i(scored_results.begin());
i != scored_results.end(); ++i) {
AddMatchToMap(i->suggestion(), input_text, i->relevance(), false,
- AutocompleteMatchType::SEARCH_HISTORY,
- did_not_accept_suggestion,
- is_keyword, map);
+ false, std::string(), AutocompleteMatchType::SEARCH_HISTORY,
+ did_not_accept_suggestion, is_keyword, map);
}
}
@@ -1258,7 +1286,7 @@
i->time, is_keyword, !prevent_inline_autocomplete,
prevent_search_history_inlining);
scored_results.push_back(
- SuggestResult(i->term, is_keyword, relevance, false));
+ SuggestResult(i->term, is_keyword, relevance, false, false));
}
// History returns results sorted for us. However, we may have docked some
@@ -1280,12 +1308,14 @@
}
void SearchProvider::AddSuggestResultsToMap(const SuggestResults& results,
+ const std::string& metadata,
MatchMap* map) {
for (size_t i = 0; i < results.size(); ++i) {
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(),
+ results[i].should_prefetch(), metadata,
AutocompleteMatchType::SEARCH_SUGGEST, i, is_keyword, map);
}
}
@@ -1402,6 +1432,8 @@
const string16& input_text,
int relevance,
bool relevance_from_server,
+ bool should_prefetch,
+ const std::string& metadata,
AutocompleteMatch::Type type,
int accepted_suggestion,
bool is_keyword,
@@ -1431,22 +1463,50 @@
return;
match.RecordAdditionalInfo(kRelevanceFromServerKey,
relevance_from_server ? kTrue : kFalse);
+ match.RecordAdditionalInfo(kShouldPrefetchKey,
+ should_prefetch ? kTrue : kFalse);
+
+ // Metadata is needed only for prefetching queries.
+ if (should_prefetch)
+ match.RecordAdditionalInfo(kSuggestMetadataKey, metadata);
// Try to add |match| to |map|. If a match for |query_string| is already in
// |map|, replace it if |match| is more relevant.
// NOTE: Keep this ToLower() call in sync with url_database.cc.
const std::pair<MatchMap::iterator, bool> i(
map->insert(std::make_pair(base::i18n::ToLower(query_string), match)));
- // NOTE: We purposefully do a direct relevance comparison here instead of
- // using AutocompleteMatch::MoreRelevant(), so that we'll prefer "items added
- // first" rather than "items alphabetically first" when the scores are equal.
- // The only case this matters is when a user has results with the same score
- // that differ only by capitalization; because the history system returns
- // results sorted by recency, this means we'll pick the most recent such
- // result even if the precision of our relevance score is too low to
- // distinguish the two.
- if (!i.second && (match.relevance > i.first->second.relevance))
- i.first->second = match;
+
+ if (!i.second) {
+ // NOTE: We purposefully do a direct relevance comparison here instead of
+ // using AutocompleteMatch::MoreRelevant(), so that we'll prefer "items
+ // added first" rather than "items alphabetically first" when the scores are
+ // equal. The only case this matters is when a user has results with the
+ // same score that differ only by capitalization; because the history system
+ // returns results sorted by recency, this means we'll pick the most
+ // recent such result even if the precision of our relevance score is too
+ // low to distinguish the two.
+ if (match.relevance > i.first->second.relevance) {
+ i.first->second = match;
+ } else if (match.keyword == i.first->second.keyword) {
+ // Old and new matches are from the same search provider. It is okay to
+ // record one match's prefetch data onto a different match (for the same
+ // query string) for the following reasons:
+ // 1. Because the suggest server only sends down a query string from which
+ // we construct a URL, rather than sending a full URL, and because we
+ // construct URLs from query strings in the same way every time, the URLs
+ // for the two matches will be the same. Therefore, we won't end up
+ // prefetching something the server didn't intend.
+ // 2. Presumably the server sets the prefetch bit on a match it things is
+ // sufficiently relevant that the user is likely to choose it. Surely
+ // setting the prefetch bit on a match of even higher relevance won't
+ // violate this assumption.
+ should_prefetch |= ShouldPrefetch(i.first->second);
+ i.first->second.RecordAdditionalInfo(kShouldPrefetchKey,
+ should_prefetch ? kTrue : kFalse);
+ if (should_prefetch)
+ i.first->second.RecordAdditionalInfo(kSuggestMetadataKey, metadata);
+ }
+ }
}
AutocompleteMatch SearchProvider::NavigationToMatch(
@@ -1513,6 +1573,7 @@
match.RecordAdditionalInfo(
kRelevanceFromServerKey,
navigation.relevance_from_server() ? kTrue : kFalse);
+ match.RecordAdditionalInfo(kShouldPrefetchKey, kFalse);
return match;
}
diff --git a/chrome/browser/autocomplete/search_provider.h b/chrome/browser/autocomplete/search_provider.h
index 281684d..5993a46 100644
--- a/chrome/browser/autocomplete/search_provider.h
+++ b/chrome/browser/autocomplete/search_provider.h
@@ -90,6 +90,14 @@
int omnibox_start_margin,
bool append_extra_query_params);
+ // Returns whether the SearchProvider previously flagged |match| as a query
+ // that should be prefetched.
+ static bool ShouldPrefetch(const AutocompleteMatch& match);
+
+ // Extracts the suggest response metadata which SearchProvider previously
+ // stored for |match|.
+ static std::string GetSuggestMetadata(const AutocompleteMatch& match);
+
// AutocompleteProvider:
virtual void AddProviderInfo(ProvidersInfo* provider_info) const OVERRIDE;
virtual void ResetSession() OVERRIDE;
@@ -109,6 +117,8 @@
FRIEND_TEST_ALL_PREFIXES(SearchProviderTest, RemoveStaleResultsTest);
FRIEND_TEST_ALL_PREFIXES(SearchProviderTest, SuggestRelevanceExperiment);
FRIEND_TEST_ALL_PREFIXES(AutocompleteProviderTest, GetDestinationURL);
+ FRIEND_TEST_ALL_PREFIXES(InstantExtendedPrefetchTest, ClearPrefetchedResults);
+ FRIEND_TEST_ALL_PREFIXES(InstantExtendedPrefetchTest, SetPrefetchQuery);
// Manages the providers (TemplateURLs) used by SearchProvider. Two providers
// may be used:
@@ -213,10 +223,12 @@
SuggestResult(const string16& suggestion,
bool from_keyword_provider,
int relevance,
- bool relevance_from_server);
+ bool relevance_from_server,
+ bool should_prefetch);
virtual ~SuggestResult();
const string16& suggestion() const { return suggestion_; }
+ bool should_prefetch() const { return should_prefetch_; }
// Result:
virtual bool IsInlineable(const string16& input) const OVERRIDE;
@@ -227,6 +239,9 @@
private:
// The search suggestion string.
string16 suggestion_;
+
+ // Should this result be prefetched?
+ bool should_prefetch_;
};
class NavigationResult : public Result {
@@ -298,6 +313,9 @@
// suppresses the verbatim result.
int verbatim_relevance;
+ // The JSON metadata associated with this server response.
+ std::string metadata;
+
private:
DISALLOW_COPY_AND_ASSIGN(Results);
};
@@ -404,7 +422,9 @@
bool is_keyword);
// Adds matches for |results| to |map|.
- void AddSuggestResultsToMap(const SuggestResults& results, MatchMap* map);
+ void AddSuggestResultsToMap(const SuggestResults& results,
+ const std::string& metadata,
+ MatchMap* map);
// Gets the relevance score for the verbatim result. This value may be
// provided by the suggest server or calculated locally; if
@@ -449,6 +469,8 @@
const string16& input_text,
int relevance,
bool relevance_from_server,
+ bool should_prefetch,
+ const std::string& metadata,
AutocompleteMatch::Type type,
int accepted_suggestion,
bool is_keyword,
@@ -474,10 +496,20 @@
// previous one. Non-const because some unittests modify this value.
static int kMinimumTimeBetweenSuggestQueriesMs;
+ // The following keys are used to record additional information on matches.
+
// We annotate our AutocompleteMatches with whether their relevance scores
// were server-provided using this key in the |additional_info| field.
static const char kRelevanceFromServerKey[];
- // These are the values we record with the above key.
+
+ // Indicates whether the server said a match should be prefetched.
+ static const char kShouldPrefetchKey[];
+
+ // Used to store metadata from the server response, which is needed for
+ // prefetching.
+ static const char kSuggestMetadataKey[];
+
+ // These are the values for the above keys.
static const char kTrue[];
static const char kFalse[];
diff --git a/chrome/browser/autocomplete/search_provider_unittest.cc b/chrome/browser/autocomplete/search_provider_unittest.cc
index 5aa485f..7df07d2 100644
--- a/chrome/browser/autocomplete/search_provider_unittest.cc
+++ b/chrome/browser/autocomplete/search_provider_unittest.cc
@@ -2429,7 +2429,7 @@
provider_->default_results_.suggest_results.push_back(
SearchProvider::SuggestResult(ASCIIToUTF16(suggestion), false,
cases[i].results[j].relevance,
- false));
+ false, false));
}
}
@@ -2468,3 +2468,137 @@
EXPECT_EQ(nav_end, nav_it);
}
}
+
+// A basic test that verifies the prefetch metadata parsing logic.
+TEST_F(SearchProviderTest, PrefetchMetadataParsing) {
+ struct Match {
+ std::string contents;
+ bool allowed_to_be_prefetched;
+ AutocompleteMatchType::Type type;
+ bool from_keyword;
+ };
+ const Match kEmptyMatch = { kNotApplicable,
+ false,
+ AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED,
+ false };
+
+ struct {
+ const std::string input_text;
+ bool prefer_keyword_provider_results;
+ const std::string default_provider_response_json;
+ const std::string keyword_provider_response_json;
+ const Match matches[5];
+ } cases[] = {
+ // Default provider response does not have prefetch details. Ensure that the
+ // suggestions are not marked as prefetch query.
+ { "a",
+ false,
+ "[\"a\",[\"b\", \"c\"],[],[],{\"google:suggestrelevance\":[1, 2]}]",
+ std::string(),
+ { { "a", false, AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED, false },
+ { "c", false, AutocompleteMatchType::SEARCH_SUGGEST, false },
+ { "b", false, AutocompleteMatchType::SEARCH_SUGGEST, false },
+ kEmptyMatch,
+ kEmptyMatch
+ },
+ },
+ // Ensure that default provider suggest response prefetch details are
+ // parsed and recorded in AutocompleteMatch.
+ { "ab",
+ false,
+ "[\"ab\",[\"abc\", \"http://b.com\", \"http://c.com\"],[],[],"
+ "{\"google:clientdata\":{\"phi\": 0},"
+ "\"google:suggesttype\":[\"QUERY\", \"NAVIGATION\", \"NAVIGATION\"],"
+ "\"google:suggestrelevance\":[999, 12, 1]}]",
+ std::string(),
+ { { "ab", false, AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED, false },
+ { "abc", true, AutocompleteMatchType::SEARCH_SUGGEST, false },
+ { "b.com", false, AutocompleteMatchType::NAVSUGGEST, false },
+ { "c.com", false, AutocompleteMatchType::NAVSUGGEST, false },
+ kEmptyMatch
+ },
+ },
+ // Default provider suggest response has prefetch details.
+ // SEARCH_WHAT_YOU_TYPE suggestion outranks SEARCH_SUGGEST suggestion for
+ // the same query string. Ensure that the prefetch details from
+ // SEARCH_SUGGEST match are set onto SEARCH_WHAT_YOU_TYPE match.
+ { "ab",
+ false,
+ "[\"ab\",[\"ab\", \"http://ab.com\"],[],[],"
+ "{\"google:clientdata\":{\"phi\": 0},"
+ "\"google:suggesttype\":[\"QUERY\", \"NAVIGATION\"],"
+ "\"google:suggestrelevance\":[99, 98]}]",
+ std::string(),
+ { {"ab", true, AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED, false },
+ {"ab.com", false, AutocompleteMatchType::NAVSUGGEST, false },
+ kEmptyMatch,
+ kEmptyMatch,
+ kEmptyMatch
+ },
+ },
+ // Default provider response has prefetch details. We prefer keyword
+ // provider results. Ensure that prefetch bit for a suggestion from the
+ // default search provider does not get copied onto a higher-scoring match
+ // for the same query string from the keyword provider.
+ { "k a",
+ true,
+ "[\"k a\",[\"a\", \"ab\"],[],[], {\"google:clientdata\":{\"phi\": 0},"
+ "\"google:suggesttype\":[\"QUERY\", \"QUERY\"],"
+ "\"google:suggestrelevance\":[9, 12]}]",
+ "[\"a\",[\"b\", \"c\"],[],[],{\"google:suggestrelevance\":[1, 2]}]",
+ { { "a", false, AutocompleteMatchType::SEARCH_OTHER_ENGINE, true},
+ { "k a", false, AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED, false },
+ { "ab", false, AutocompleteMatchType::SEARCH_SUGGEST, false },
+ { "c", false, AutocompleteMatchType::SEARCH_SUGGEST, true },
+ { "b", false, AutocompleteMatchType::SEARCH_SUGGEST, true }
+ },
+ }
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); i++) {
+ QueryForInput(ASCIIToUTF16(cases[i].input_text), false,
+ cases[i].prefer_keyword_provider_results);
+
+ // Set up a default fetcher with provided results.
+ net::TestURLFetcher* fetcher =
+ test_factory_.GetFetcherByID(
+ SearchProvider::kDefaultProviderURLFetcherID);
+ ASSERT_TRUE(fetcher);
+ fetcher->set_response_code(200);
+ fetcher->SetResponseString(cases[i].default_provider_response_json);
+ fetcher->delegate()->OnURLFetchComplete(fetcher);
+
+ if (cases[i].prefer_keyword_provider_results) {
+ // Set up a keyword fetcher with provided results.
+ net::TestURLFetcher* keyword_fetcher =
+ test_factory_.GetFetcherByID(
+ SearchProvider::kKeywordProviderURLFetcherID);
+ ASSERT_TRUE(keyword_fetcher);
+ keyword_fetcher->set_response_code(200);
+ keyword_fetcher->SetResponseString(
+ cases[i].keyword_provider_response_json);
+ keyword_fetcher->delegate()->OnURLFetchComplete(keyword_fetcher);
+ keyword_fetcher = NULL;
+ }
+
+ RunTillProviderDone();
+
+ const std::string description =
+ "for input with json =" + cases[i].default_provider_response_json;
+ const ACMatches& matches = provider_->matches();
+ // The top match must inline and score as highly as calculated verbatim.
+ ASSERT_FALSE(matches.empty());
+ EXPECT_GE(matches[0].relevance, 1300);
+
+ // Ensure that the returned matches equal the expectations.
+ for (size_t j = 0; j < matches.size(); ++j) {
+ SCOPED_TRACE(description);
+ EXPECT_EQ(cases[i].matches[j].contents, UTF16ToUTF8(matches[j].contents));
+ EXPECT_EQ(cases[i].matches[j].allowed_to_be_prefetched,
+ SearchProvider::ShouldPrefetch(matches[j]));
+ EXPECT_EQ(cases[i].matches[j].type, matches[j].type);
+ EXPECT_EQ(cases[i].matches[j].from_keyword,
+ matches[j].keyword == ASCIIToUTF16("k"));
+ }
+ }
+}
diff --git a/chrome/browser/autocomplete/zero_suggest_provider.cc b/chrome/browser/autocomplete/zero_suggest_provider.cc
index 4369aba6..b4cdb55 100644
--- a/chrome/browser/autocomplete/zero_suggest_provider.cc
+++ b/chrome/browser/autocomplete/zero_suggest_provider.cc
@@ -296,7 +296,7 @@
}
} else {
suggest_results->push_back(SearchProvider::SuggestResult(
- result, false, relevance, relevances != NULL));
+ result, false, relevance, relevances != NULL, false));
}
}
}