blob: b4db0abda44f68ff2ca8abe637a8400fc6b5545e [file] [log] [blame]
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/omnibox/browser/suggestion_answer.h"
#include <stddef.h>
#include <memory>
#include "base/feature_list.h"
#include "base/i18n/rtl.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/escape.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/trace_event/memory_usage_estimator.h"
#include "build/build_config.h"
#include "third_party/omnibox_proto/answer_type.pb.h"
#include "url/url_constants.h"
namespace {
// All of these are defined here (even though most are only used once each) so
// the format details are easy to locate and update or compare to the spec doc.
static constexpr char kAnswerJsonLines[] = "l";
static constexpr char kAnswerJsonImageLine[] = "il";
static constexpr char kAnswerJsonText[] = "t";
static constexpr char kAnswerJsonAdditionalText[] = "at";
static constexpr char kAnswerJsonStatusText[] = "st";
static constexpr char kAnswerJsonAccessibilityLabel[] = "al";
static constexpr char kAnswerJsonTextType[] = "tt";
static constexpr char kAnswerJsonImage[] = "i";
static constexpr char kAnswerJsonImageData[] = "d";
constexpr char kAnswerUsedUmaHistogramName[] =
"Omnibox.SuggestionUsed.AnswerInSuggest";
} // namespace
namespace omnibox::answer_data_parser {
// If necessary, concatenate scheme and host/path using only ':' as
// separator. This is due to the results delivering strings of the form
// "//host/path", which is web-speak for "use the enclosing page's scheme",
// but not a valid path of a URL. The GWS frontend commonly (always?)
// redirects to HTTPS, so we just default to that here.
GURL GetFormattedURL(const std::string* url_string) {
return GURL(base::StartsWith(*url_string, "//", base::CompareCase::SENSITIVE)
? (std::string(url::kHttpsScheme) + ":" + *url_string)
: *url_string);
}
void SetColorType(int text_type,
omnibox::FormattedString::FormattedStringFragment* fragment) {
switch (text_type) {
case DESCRIPTION_NEGATIVE: {
fragment->set_color(omnibox::FormattedString::COLOR_ON_SURFACE_NEGATIVE);
break;
}
case DESCRIPTION_POSITIVE: {
fragment->set_color(omnibox::FormattedString::COLOR_ON_SURFACE_POSITIVE);
break;
}
default:
break;
}
}
bool ParseJsonToFormattedStringFragment(
const base::Value::Dict& field_json,
omnibox::FormattedString* formatted_string) {
const std::string* text = field_json.FindString(kAnswerJsonText);
std::optional<int> type = field_json.FindInt(kAnswerJsonTextType);
const bool parsed = text && !text->empty() && type;
if (!parsed) {
return false;
}
omnibox::FormattedString::FormattedStringFragment* fragment =
formatted_string->add_fragments();
std::string unescaped_text =
base::UTF16ToUTF8(base::UnescapeForHTML(base::UTF8ToUTF16(*text)));
if (formatted_string->has_text()) {
// Append fragment text to FormattedString text.
fragment->set_start_index(formatted_string->text().size() + 1);
std::string full_text = formatted_string->text() + " " + unescaped_text;
formatted_string->set_text(full_text);
} else {
fragment->set_start_index(0);
formatted_string->set_text(unescaped_text);
}
fragment->set_text(unescaped_text);
SetColorType(type.value(), fragment);
return true;
}
bool ParseJsonToFormattedString(const base::Value::Dict& line_json,
omnibox::FormattedString* formatted_string,
omnibox::Image* image) {
const base::Value::Dict* inner_json =
line_json.FindDict(kAnswerJsonImageLine);
if (!inner_json) {
return false;
}
const base::Value::List* fields_json = inner_json->FindList(kAnswerJsonText);
if (!fields_json || fields_json->empty()) {
return false;
}
for (const base::Value& field_json : *fields_json) {
if (!field_json.is_dict() || !ParseJsonToFormattedStringFragment(
field_json.GetDict(), formatted_string)) {
return false;
}
}
const base::Value::Dict* additional_text_json =
inner_json->FindDict(kAnswerJsonAdditionalText);
if (additional_text_json && !ParseJsonToFormattedStringFragment(
*additional_text_json, formatted_string)) {
return false;
}
const std::string* accessibility_label =
inner_json->FindString(kAnswerJsonAccessibilityLabel);
if (accessibility_label) {
formatted_string->set_a11y_text(*accessibility_label);
}
const base::Value::Dict* status_text_json =
inner_json->FindDict(kAnswerJsonStatusText);
if (status_text_json && !ParseJsonToFormattedStringFragment(
*status_text_json, formatted_string)) {
return false;
}
const base::Value::Dict* image_json = inner_json->FindDict(kAnswerJsonImage);
if (image_json) {
const std::string* url_string =
image_json->FindString(kAnswerJsonImageData);
if (!url_string || url_string->empty()) {
return false;
}
GURL image_url = GetFormattedURL(url_string);
if (!image_url.is_valid()) {
return false;
}
image->set_url(image_url.spec());
}
return true;
}
bool ParseJsonToAnswerData(const base::Value::Dict& answer_json,
omnibox::RichAnswerTemplate* answer_template) {
// Ensure there are exactly two lines in the response.
const base::Value::List* lines_json = answer_json.FindList(kAnswerJsonLines);
if (!lines_json || lines_json->size() != 2) {
return false;
}
const base::Value::Dict* first_line_dict = (*lines_json)[0].GetIfDict();
omnibox::AnswerData* answer_data = answer_template->add_answers();
if (!first_line_dict || !ParseJsonToFormattedString(
*first_line_dict, answer_data->mutable_headline(),
answer_data->mutable_image())) {
return false;
}
const base::Value::Dict* second_line_dict = (*lines_json)[1].GetIfDict();
if (!second_line_dict ||
!ParseJsonToFormattedString(*second_line_dict,
answer_data->mutable_subhead(),
answer_data->mutable_image())) {
return false;
}
const std::string* image_url;
const base::Value::Dict* optional_image =
answer_json.FindDict(kAnswerJsonImage);
if (optional_image &&
(image_url = optional_image->FindString(kAnswerJsonImageData)) &&
!answer_data->image().has_url()) {
answer_data->mutable_image()->set_url(*image_url);
}
return true;
}
void LogAnswerUsed(omnibox::AnswerType answer_type) {
UMA_HISTOGRAM_ENUMERATION(kAnswerUsedUmaHistogramName, answer_type,
omnibox::AnswerType_MAX);
}
} // namespace omnibox::answer_data_parser