blob: 541339f284d0c55b1ce66d6b7b156112b3fa40d1 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/omnibox/browser/search_suggestion_parser.h"
#include <stddef.h>
#include <algorithm>
#include <memory>
#include "base/check.h"
#include "base/i18n/icu_string_conversions.h"
#include "base/json/json_reader.h"
#include "base/json/json_string_value_serializer.h"
#include "base/json/json_writer.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "components/omnibox/browser/autocomplete_i18n.h"
#include "components/omnibox/browser/autocomplete_input.h"
#include "components/omnibox/browser/autocomplete_match_classification.h"
#include "components/omnibox/browser/autocomplete_provider.h"
#include "components/omnibox/browser/omnibox_field_trial.h"
#include "components/omnibox/browser/url_prefix.h"
#include "components/url_formatter/url_fixer.h"
#include "components/url_formatter/url_formatter.h"
#include "net/http/http_response_headers.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
#include "ui/base/device_form_factor.h"
#include "url/url_constants.h"
namespace {
AutocompleteMatchType::Type GetAutocompleteMatchType(const std::string& type) {
if (type == "CALCULATOR")
return AutocompleteMatchType::CALCULATOR;
if (type == "ENTITY")
return AutocompleteMatchType::SEARCH_SUGGEST_ENTITY;
if (type == "TAIL")
return AutocompleteMatchType::SEARCH_SUGGEST_TAIL;
if (type == "PERSONALIZED_QUERY")
return AutocompleteMatchType::SEARCH_SUGGEST_PERSONALIZED;
if (type == "PROFILE")
return AutocompleteMatchType::SEARCH_SUGGEST_PROFILE;
if (type == "NAVIGATION")
return AutocompleteMatchType::NAVSUGGEST;
if (type == "PERSONALIZED_NAVIGATION")
return AutocompleteMatchType::NAVSUGGEST_PERSONALIZED;
return AutocompleteMatchType::SEARCH_SUGGEST;
}
// Convert the supplied Json::Value representation of list-of-lists-of-integers
// to a vector-of-vecrors-of-integers, containing (ideally) one vector of
// integers per match.
// The logic here does not validate if the length of top level vector is same as
// number of returned matches and will supply empty vector for any item that is
// either invalid or missing.
// The function will always return a valid and properly sized vector of vectors,
// equal in length to |expected_size|, even if the input |subtypes_value| is not
// valid.
std::vector<std::vector<int>> ParseMatchSubtypes(
const base::Value* subtypes_value,
size_t expected_size) {
std::vector<std::vector<int>> result(expected_size);
if (subtypes_value == nullptr || !subtypes_value->is_list())
return result;
auto subtypes_list = subtypes_value->GetListDeprecated();
if (!subtypes_list.empty() && subtypes_list.size() != expected_size) {
LOG(WARNING) << "The length of reported subtypes (" << subtypes_list.size()
<< ") does not match the expected length (" << expected_size
<< ')';
}
const auto num_items = std::min(expected_size, subtypes_list.size());
for (auto index = 0u; index < num_items; index++) {
const auto& subtypes_item = subtypes_list[index];
// Permissive: ignore subtypes that are not in a form of a list.
if (!subtypes_item.is_list())
continue;
auto subtype_list = subtypes_item.GetListDeprecated();
auto& result_subtypes = result[index];
result_subtypes.reserve(subtype_list.size());
for (const auto& subtype : subtype_list) {
// Permissive: Skip over any item that is not an integer.
if (!subtype.is_int())
continue;
result_subtypes.emplace_back(subtype.GetInt());
}
}
return result;
}
std::string FindStringKeyOrEmpty(const base::Value& value, std::string key) {
auto* ptr = value.FindStringKey(key);
return ptr ? *ptr : "";
}
} // namespace
// Value chosen based on SuggestionGroupIds::INVALID in suggestion_config.proto.
const int SearchSuggestionParser::kNoSuggestionGroupId = -1;
// SearchSuggestionParser::Result ----------------------------------------------
SearchSuggestionParser::Result::Result(bool from_keyword,
int relevance,
bool relevance_from_server,
AutocompleteMatchType::Type type,
std::vector<int> subtypes,
const std::string& deletion_url)
: from_keyword_(from_keyword),
type_(type),
subtypes_(std::move(subtypes)),
relevance_(relevance),
relevance_from_server_(relevance_from_server),
received_after_last_keystroke_(true),
deletion_url_(deletion_url) {}
SearchSuggestionParser::Result::Result(const Result& other) = default;
SearchSuggestionParser::Result::~Result() {}
// SearchSuggestionParser::SuggestResult ---------------------------------------
SearchSuggestionParser::SuggestResult::SuggestResult(
const std::u16string& suggestion,
AutocompleteMatchType::Type type,
std::vector<int> subtypes,
bool from_keyword,
int relevance,
bool relevance_from_server,
const std::u16string& input_text)
: SuggestResult(suggestion,
type,
std::move(subtypes),
suggestion,
/*match_contents_prefix=*/std::u16string(),
/*annotation=*/std::u16string(),
/*suggest_query_params=*/"",
/*deletion_url=*/"",
/*image_dominant_color=*/"",
/*image_url=*/"",
from_keyword,
relevance,
relevance_from_server,
/*should_prefetch=*/false,
/*should_prerender=*/false,
input_text) {}
SearchSuggestionParser::SuggestResult::SuggestResult(
const std::u16string& suggestion,
AutocompleteMatchType::Type type,
std::vector<int> subtypes,
const std::u16string& match_contents,
const std::u16string& match_contents_prefix,
const std::u16string& annotation,
const std::string& additional_query_params,
const std::string& deletion_url,
const std::string& image_dominant_color,
const std::string& image_url,
bool from_keyword,
int relevance,
bool relevance_from_server,
bool should_prefetch,
bool should_prerender,
const std::u16string& input_text)
: Result(from_keyword,
relevance,
relevance_from_server,
type,
std::move(subtypes),
deletion_url),
suggestion_(suggestion),
match_contents_prefix_(match_contents_prefix),
annotation_(annotation),
additional_query_params_(additional_query_params),
image_dominant_color_(image_dominant_color),
image_url_(GURL(image_url)),
should_prefetch_(should_prefetch),
should_prerender_(should_prerender) {
match_contents_ = match_contents;
DCHECK(!match_contents_.empty());
ClassifyMatchContents(true, input_text);
}
SearchSuggestionParser::SuggestResult::SuggestResult(
const SuggestResult& result) = default;
SearchSuggestionParser::SuggestResult::~SuggestResult() {}
SearchSuggestionParser::SuggestResult& SearchSuggestionParser::SuggestResult::
operator=(const SuggestResult& rhs) = default;
void SearchSuggestionParser::SuggestResult::ClassifyMatchContents(
const bool allow_bolding_all,
const std::u16string& input_text) {
DCHECK(!match_contents_.empty());
// In case of zero-suggest results, do not highlight matches.
if (input_text.empty()) {
match_contents_class_ = {
ACMatchClassification(0, ACMatchClassification::NONE)};
return;
}
std::u16string lookup_text = input_text;
if (type_ == AutocompleteMatchType::SEARCH_SUGGEST_TAIL) {
const size_t contents_index =
suggestion_.length() - match_contents_.length();
// Ensure the query starts with the input text, and ends with the match
// contents, and the input text has an overlap with contents.
if (base::StartsWith(suggestion_, input_text,
base::CompareCase::SENSITIVE) &&
base::EndsWith(suggestion_, match_contents_,
base::CompareCase::SENSITIVE) &&
(input_text.length() > contents_index)) {
lookup_text = input_text.substr(contents_index);
}
}
// Do a case-insensitive search for |lookup_text|.
std::u16string::const_iterator lookup_position = std::search(
match_contents_.begin(), match_contents_.end(), lookup_text.begin(),
lookup_text.end(), SimpleCaseInsensitiveCompareUCS2());
if (!allow_bolding_all && (lookup_position == match_contents_.end())) {
// Bail if the code below to update the bolding would bold the whole
// string. Note that the string may already be entirely bolded; if
// so, leave it as is.
return;
}
// Note we discard our existing match_contents_class_ with this call.
match_contents_class_ = AutocompleteProvider::ClassifyAllMatchesInString(
input_text, match_contents_, true);
}
void SearchSuggestionParser::SuggestResult::SetAnswer(
const SuggestionAnswer& answer) {
answer_ = answer;
}
int SearchSuggestionParser::SuggestResult::CalculateRelevance(
const AutocompleteInput& input,
bool keyword_provider_requested) const {
if (!from_keyword_ && keyword_provider_requested)
return 100;
return ((input.type() == metrics::OmniboxInputType::URL) ? 300 : 600);
}
// SearchSuggestionParser::NavigationResult ------------------------------------
SearchSuggestionParser::NavigationResult::NavigationResult(
const AutocompleteSchemeClassifier& scheme_classifier,
const GURL& url,
AutocompleteMatchType::Type match_type,
std::vector<int> subtypes,
const std::u16string& description,
const std::string& deletion_url,
bool from_keyword,
int relevance,
bool relevance_from_server,
const std::u16string& input_text)
: Result(from_keyword,
relevance,
relevance_from_server,
match_type,
std::move(subtypes),
deletion_url),
url_(url),
formatted_url_(AutocompleteInput::FormattedStringWithEquivalentMeaning(
url,
url_formatter::FormatUrl(url,
url_formatter::kFormatUrlOmitDefaults &
~url_formatter::kFormatUrlOmitHTTP,
base::UnescapeRule::SPACES,
nullptr,
nullptr,
nullptr),
scheme_classifier,
nullptr)),
description_(description) {
DCHECK(url_.is_valid());
CalculateAndClassifyMatchContents(true, input_text);
ClassifyDescription(input_text);
}
SearchSuggestionParser::NavigationResult::NavigationResult(
const NavigationResult& other) = default;
SearchSuggestionParser::NavigationResult::~NavigationResult() {}
void SearchSuggestionParser::NavigationResult::
CalculateAndClassifyMatchContents(const bool allow_bolding_nothing,
const std::u16string& input_text) {
// Start with the trivial nothing-bolded classification.
DCHECK(url_.is_valid());
// In case of zero-suggest results, do not highlight matches.
if (input_text.empty()) {
// TODO(tommycli): Maybe this should actually return
// ACMatchClassification::URL. I'm not changing this now because this CL
// is meant to fix a regression only, but we should consider this for
// consistency with other |input_text| that matches nothing.
match_contents_class_ = {
ACMatchClassification(0, ACMatchClassification::NONE)};
return;
}
// Set contents to the formatted URL while ensuring the scheme and subdomain
// are kept if the user text seems to include them. E.g., for the user text
// 'http google.com', the contents should not trim 'http'.
bool match_in_scheme = false;
bool match_in_subdomain = false;
TermMatches term_matches_in_url = FindTermMatches(input_text, formatted_url_);
// Convert TermMatches (offset, length) to MatchPosition (start, end).
std::vector<AutocompleteMatch::MatchPosition> match_positions;
for (auto match : term_matches_in_url)
match_positions.emplace_back(match.offset, match.offset + match.length);
AutocompleteMatch::GetMatchComponents(GURL(formatted_url_), match_positions,
&match_in_scheme, &match_in_subdomain);
auto format_types = AutocompleteMatch::GetFormatTypes(
GURL(input_text).has_scheme(), match_in_subdomain);
// Find matches in the potentially new match_contents
std::u16string match_contents =
url_formatter::FormatUrl(url_, format_types, base::UnescapeRule::SPACES,
nullptr, nullptr, nullptr);
TermMatches term_matches = FindTermMatches(input_text, match_contents);
// Update |match_contents_| and |match_contents_class_| if it's allowed.
if (allow_bolding_nothing || !term_matches.empty()) {
match_contents_ = match_contents;
match_contents_class_ = ClassifyTermMatches(
term_matches, match_contents.size(),
ACMatchClassification::MATCH | ACMatchClassification::URL,
ACMatchClassification::URL);
}
}
int SearchSuggestionParser::NavigationResult::CalculateRelevance(
const AutocompleteInput& input,
bool keyword_provider_requested) const {
return (from_keyword_ || !keyword_provider_requested) ? 800 : 150;
}
void SearchSuggestionParser::NavigationResult::ClassifyDescription(
const std::u16string& input_text) {
TermMatches term_matches = FindTermMatches(input_text, description_);
description_class_ = ClassifyTermMatches(term_matches, description_.size(),
ACMatchClassification::MATCH,
ACMatchClassification::NONE);
}
// SearchSuggestionParser::Results ---------------------------------------------
SearchSuggestionParser::Results::Results()
: verbatim_relevance(-1),
field_trial_triggered(false),
relevances_from_server(false) {}
SearchSuggestionParser::Results::~Results() {}
void SearchSuggestionParser::Results::Clear() {
suggest_results.clear();
navigation_results.clear();
verbatim_relevance = -1;
metadata.clear();
}
bool SearchSuggestionParser::Results::HasServerProvidedScores() const {
if (verbatim_relevance >= 0)
return true;
// 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 (auto i(suggest_results.begin()); i != suggest_results.end(); ++i) {
if (i->relevance_from_server())
return true;
}
for (auto i(navigation_results.begin()); i != navigation_results.end(); ++i) {
if (i->relevance_from_server())
return true;
}
return false;
}
// SearchSuggestionParser ------------------------------------------------------
// static
std::string SearchSuggestionParser::ExtractJsonData(
const network::SimpleURLLoader* source,
std::unique_ptr<std::string> response_body) {
const net::HttpResponseHeaders* response_headers = nullptr;
if (source && source->ResponseInfo())
response_headers = source->ResponseInfo()->headers.get();
if (!response_body)
return std::string();
std::string json_data = std::move(*response_body);
// JSON is supposed to be UTF-8, but some suggest service providers send
// JSON files in non-UTF-8 encodings. The actual encoding is usually
// specified in the Content-Type header field.
if (response_headers) {
std::string charset;
if (response_headers->GetCharset(&charset)) {
std::u16string data_16;
// TODO(jungshik): Switch to CodePageToUTF8 after it's added.
if (base::CodepageToUTF16(json_data, charset.c_str(),
base::OnStringConversionError::FAIL, &data_16))
json_data = base::UTF16ToUTF8(data_16);
}
}
return json_data;
}
// static
std::unique_ptr<base::Value> SearchSuggestionParser::DeserializeJsonData(
base::StringPiece json_data) {
// The JSON response should be an array.
for (size_t response_start_index = json_data.find("["), i = 0;
response_start_index != base::StringPiece::npos && i < 5;
response_start_index = json_data.find("[", 1), i++) {
// Remove any XSSI guards to allow for JSON parsing.
json_data.remove_prefix(response_start_index);
JSONStringValueDeserializer deserializer(json_data,
base::JSON_ALLOW_TRAILING_COMMAS);
int error_code = 0;
std::unique_ptr<base::Value> data =
deserializer.Deserialize(&error_code, nullptr);
if (error_code == 0)
return data;
}
return nullptr;
}
// static
bool SearchSuggestionParser::ParseSuggestResults(
const base::Value& root_val,
const AutocompleteInput& input,
const AutocompleteSchemeClassifier& scheme_classifier,
int default_result_relevance,
bool is_keyword_result,
Results* results) {
if (!root_val.is_list())
return false;
auto root_list = root_val.GetListDeprecated();
// 1st element: query.
if (root_list.empty() || !root_list[0].is_string())
return false;
std::u16string query = base::UTF8ToUTF16(root_list[0].GetString());
if (query != input.text())
return false;
// 2nd element: suggestions list.
if (root_list.size() < 2u || !root_list[1].is_list())
return false;
auto results_list = root_list[1].GetListDeprecated();
// 3rd element: Ignore the optional description list for now.
// 4th element: Disregard the query URL list.
// 5th element: Disregard the optional key-value pairs from the server.
// Reset suggested relevance information.
results->verbatim_relevance = -1;
const base::Value* suggest_types = nullptr;
const base::Value* suggest_subtypes = nullptr;
const base::Value* relevances = nullptr;
const base::Value* suggestion_details = nullptr;
const base::Value* subtype_identifiers = nullptr;
int prefetch_index = -1;
int prerender_index = -1;
if (root_list.size() > 4u && root_list[4].is_dict()) {
const base::Value& extras = root_list[4];
suggest_types = extras.FindListKey("google:suggesttype");
suggest_subtypes = extras.FindListKey("google:suggestsubtypes");
relevances = extras.FindListKey("google:suggestrelevance");
// Discard this list if its size does not match that of the suggestions.
if (relevances &&
relevances->GetListDeprecated().size() != results_list.size()) {
relevances = nullptr;
}
if (absl::optional<int> relevance =
extras.FindIntKey("google:verbatimrelevance")) {
results->verbatim_relevance = *relevance;
}
// Check if the active suggest field trial (if any) has triggered either
// for the default provider or keyword provider.
absl::optional<bool> field_trial_triggered =
extras.FindBoolKey("google:fieldtrialtriggered");
results->field_trial_triggered = field_trial_triggered.value_or(false);
results->experiment_stats.clear();
const base::Value* experiment_stats =
extras.FindListKey("google:experimentstats");
if (experiment_stats) {
for (const auto& experiment_stat : experiment_stats->GetListDeprecated())
results->experiment_stats.push_back(experiment_stat.Clone());
}
const base::Value* header_texts = extras.FindDictKey("google:headertexts");
if (header_texts) {
const base::Value* headers = header_texts->FindDictKey("a");
if (headers) {
for (auto it : headers->DictItems()) {
int suggestion_group_id;
base::StringToInt(it.first, &suggestion_group_id);
results->headers_map[suggestion_group_id] =
base::UTF8ToUTF16(it.second.GetString());
}
}
const base::Value* hidden_group_ids = header_texts->FindListKey("h");
if (hidden_group_ids) {
for (const auto& value : hidden_group_ids->GetListDeprecated()) {
if (value.is_int())
results->hidden_group_ids.emplace_back(value.GetInt());
}
}
}
const base::Value* client_data = extras.FindDictKey("google:clientdata");
if (client_data) {
prefetch_index = client_data->FindIntKey("phi").value_or(-1);
prerender_index = client_data->FindIntKey("pre").value_or(-1);
}
suggestion_details = extras.FindListKey("google:suggestdetail");
// Discard this list if its size does not match that of the suggestions.
if (suggestion_details &&
suggestion_details->GetListDeprecated().size() != results_list.size()) {
suggestion_details = nullptr;
}
// Legacy code: Get subtype identifiers.
subtype_identifiers = extras.FindListKey("google:subtypeid");
// Discard this list if its size does not match that of the suggestions.
if (subtype_identifiers &&
subtype_identifiers->GetListDeprecated().size() !=
results_list.size()) {
subtype_identifiers = nullptr;
}
// 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);
}
// Processed list of match subtypes, one vector per match.
// Note: ParseMatchSubtypes will handle the cases where the key does not
// exist or contains malformed data.
std::vector<std::vector<int>> subtypes =
ParseMatchSubtypes(suggest_subtypes, results_list.size());
// Clear the previous results now that new results are available.
results->suggest_results.clear();
results->navigation_results.clear();
std::string type;
int relevance = default_result_relevance;
const std::u16string& trimmed_input =
base::CollapseWhitespace(input.text(), false);
for (size_t index = 0;
index < results_list.size() && results_list[index].is_string();
++index) {
std::u16string suggestion =
base::UTF8ToUTF16(results_list[index].GetString());
// Google search may return empty suggestions for weird input characters,
// they make no sense at all and can cause problems in our code.
suggestion = base::CollapseWhitespace(suggestion, false);
if (suggestion.empty())
continue;
// Apply valid suggested relevance scores; discard invalid lists.
if (relevances) {
if (!relevances->GetListDeprecated()[index].is_int()) {
relevances = nullptr;
} else {
relevance = relevances->GetListDeprecated()[index].GetInt();
}
}
AutocompleteMatchType::Type match_type =
AutocompleteMatchType::SEARCH_SUGGEST;
// Legacy code: if the server sends us a single subtype ID, place it beside
// other subtypes.
if (subtype_identifiers &&
index < subtype_identifiers->GetListDeprecated().size() &&
subtype_identifiers->GetListDeprecated()[index].is_int()) {
subtypes[index].emplace_back(
subtype_identifiers->GetListDeprecated()[index].GetInt());
}
if (suggest_types && index < suggest_types->GetListDeprecated().size() &&
suggest_types->GetListDeprecated()[index].is_string()) {
match_type = GetAutocompleteMatchType(
suggest_types->GetListDeprecated()[index].GetString());
}
std::string deletion_url;
if (suggestion_details &&
index < suggestion_details->GetListDeprecated().size() &&
suggestion_details->GetListDeprecated()[index].is_dict()) {
const base::Value& suggestion_detail =
suggestion_details->GetListDeprecated()[index];
deletion_url = FindStringKeyOrEmpty(suggestion_detail, "du");
}
if ((match_type == AutocompleteMatchType::NAVSUGGEST) ||
(match_type == AutocompleteMatchType::NAVSUGGEST_PERSONALIZED)) {
// Do not blindly trust the URL coming from the server to be valid.
GURL url(url_formatter::FixupURL(base::UTF16ToUTF8(suggestion),
std::string()));
if (url.is_valid()) {
std::u16string title;
// 3rd element: optional descriptions list
if (root_list.size() > 2u && root_list[2].is_list()) {
auto descriptions = root_list[2].GetListDeprecated();
if (index < descriptions.size() && descriptions[index].is_string()) {
title = base::UTF8ToUTF16(descriptions[index].GetString());
}
}
results->navigation_results.push_back(NavigationResult(
scheme_classifier, url, match_type, subtypes[index], title,
deletion_url, is_keyword_result, relevance, relevances != nullptr,
input.text()));
}
} else {
std::u16string annotation;
std::u16string match_contents = suggestion;
if (match_type == AutocompleteMatchType::CALCULATOR) {
const bool has_equals_prefix = !suggestion.compare(0, 2, u"= ");
if (has_equals_prefix) {
// Calculator results include a "= " prefix but we don't want to
// include this in the search terms.
suggestion.erase(0, 2);
// Unlikely to happen, but better to be safe.
if (base::CollapseWhitespace(suggestion, false).empty())
continue;
}
if (ui::GetDeviceFormFactor() == ui::DEVICE_FORM_FACTOR_DESKTOP) {
annotation = has_equals_prefix ? suggestion : match_contents;
match_contents = query;
}
}
std::u16string match_contents_prefix;
SuggestionAnswer answer;
bool answer_parsed_successfully = false;
std::string image_dominant_color;
std::string image_url;
std::string additional_query_params;
absl::optional<int> suggestion_group_id;
if (suggestion_details &&
suggestion_details->GetListDeprecated()[index].is_dict() &&
!suggestion_details->GetListDeprecated()[index].DictEmpty()) {
const base::Value& suggestion_detail =
suggestion_details->GetListDeprecated()[index];
match_contents =
base::UTF8ToUTF16(FindStringKeyOrEmpty(suggestion_detail, "t"));
if (match_contents.empty()) {
match_contents = suggestion;
}
match_contents_prefix =
base::UTF8ToUTF16(FindStringKeyOrEmpty(suggestion_detail, "mp"));
annotation =
base::UTF8ToUTF16(FindStringKeyOrEmpty(suggestion_detail, "a"));
image_dominant_color = FindStringKeyOrEmpty(suggestion_detail, "dc");
image_url = FindStringKeyOrEmpty(suggestion_detail, "i");
additional_query_params = FindStringKeyOrEmpty(suggestion_detail, "q");
// Suggestion group Id.
suggestion_group_id = suggestion_detail.FindIntKey("zl");
// Extract the Answer, if provided.
const base::Value* answer_json = suggestion_detail.FindDictKey("ansa");
const std::string* answer_type =
suggestion_detail.FindStringKey("ansb");
if (answer_json && answer_type) {
if (SuggestionAnswer::ParseAnswer(answer_json->GetDict(),
base::UTF8ToUTF16(*answer_type),
&answer)) {
base::UmaHistogramSparse("Omnibox.AnswerParseType", answer.type());
answer_parsed_successfully = true;
}
UMA_HISTOGRAM_BOOLEAN("Omnibox.AnswerParseSuccess",
answer_parsed_successfully);
}
}
int int_index = static_cast<int>(index);
bool should_prefetch = int_index == prefetch_index;
bool should_prerender = int_index == prerender_index;
results->suggest_results.push_back(SuggestResult(
suggestion, match_type, subtypes[index],
base::CollapseWhitespace(match_contents, false),
match_contents_prefix, annotation, additional_query_params,
deletion_url, image_dominant_color, image_url, is_keyword_result,
relevance, relevances != nullptr, should_prefetch, should_prerender,
trimmed_input));
if (suggestion_group_id) {
results->suggest_results.back().set_suggestion_group_id(
*suggestion_group_id);
}
if (answer_parsed_successfully)
results->suggest_results.back().SetAnswer(answer);
}
}
results->relevances_from_server = relevances != nullptr;
return true;
}