Commit 4f8469f3 authored by Dominic Battre's avatar Dominic Battre Committed by Commit Bot

Protobuf conversion of autofill messages.

This CL introduces conversion methods to convert from one
protobuf representation of autofill queries/responses to
another. The representations reflect the communication with
the legacy autofill server and the new API server.

Bug: 1079488
Change-Id: Id32688926ae28a1f7d90b3590b70783a7b201bce
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2207224
Commit-Queue: Dominic Battré <battre@chromium.org>
Reviewed-by: default avatarVincent Boisselle <vincb@google.com>
Cr-Commit-Position: refs/heads/master@{#771588}
parent dec175ad
......@@ -489,6 +489,169 @@ ServerCacheReplayer::Status PopulateCacheFromJSONFile(
} // namespace
// Convert query protobuf from one environment to another.
template <typename ReadEnv, typename WriteEnv>
typename WriteEnv::Query ConvertQuery(const typename ReadEnv::Query& in) {
VLOG(1) << "ConvertQuery: identity";
// Easy case: ReadEnv and WriteEnv are identical, no conversion necessary.
return in;
}
// Instantiate functions that are not specialized.
template typename ApiEnv::Query ConvertQuery<ApiEnv, ApiEnv>(
const typename ApiEnv::Query& in);
template typename LegacyEnv::Query ConvertQuery<LegacyEnv, LegacyEnv>(
const typename LegacyEnv::Query& in);
template <>
typename ApiEnv::Query ConvertQuery<LegacyEnv, ApiEnv>(
const typename LegacyEnv::Query& in) {
VLOG(1) << "ConvertQuery: legacy->api";
ApiEnv::Query out;
for (const auto& in_form : in.form()) {
auto* out_form = out.add_forms();
out_form->set_signature(in_form.signature());
if (in_form.has_form_metadata())
out_form->mutable_metadata()->CopyFrom(in_form.form_metadata());
for (const auto& in_field : in_form.field()) {
auto* out_field = out_form->add_fields();
out_field->set_signature(in_field.signature());
if (in_field.has_name())
out_field->set_name(in_field.name());
if (in_field.has_type())
out_field->set_control_type(in_field.type());
if (in_field.has_field_metadata())
out_field->mutable_metadata()->CopyFrom(in_field.field_metadata());
}
}
if (in.experiments_size() > 0)
out.mutable_experiments()->CopyFrom(in.experiments());
return out;
}
template <>
typename LegacyEnv::Query ConvertQuery<ApiEnv, LegacyEnv>(
const typename ApiEnv::Query& in) {
VLOG(1) << "ConvertQuery: api->legacy";
LegacyEnv::Query out;
out.set_client_version("DummyClient");
for (const auto& in_form : in.forms()) {
auto* out_form = out.add_form();
out_form->set_signature(in_form.signature());
if (in_form.has_metadata())
out_form->mutable_form_metadata()->CopyFrom(in_form.metadata());
for (const auto& in_field : in_form.fields()) {
auto* out_field = out_form->add_field();
out_field->set_signature(in_field.signature());
if (in_field.has_name())
out_field->set_name(in_field.name());
if (in_field.has_control_type())
out_field->set_type(in_field.control_type());
if (in_field.has_metadata())
out_field->mutable_field_metadata()->CopyFrom(in_field.metadata());
}
}
if (in.experiments_size() > 0)
out.mutable_experiments()->CopyFrom(in.experiments());
return out;
}
// Convert response protobuf from one environment to another.
// The |query| is passed as a helper because the legacy response does not
// contain enough information to create an api response.
template <typename ReadEnv, typename WriteEnv>
typename WriteEnv::Response ConvertResponse(
const typename ReadEnv::Response& in,
const typename ReadEnv::Query& query) {
// Easy case: ReadEnv and WriteEnv are identical, no conversion necessary.
VLOG(1) << "ConvertResponse: identity";
return in;
}
// Instantiate functions that are not specialized.
template typename LegacyEnv::Response ConvertResponse<LegacyEnv, LegacyEnv>(
const typename LegacyEnv::Response& in,
const typename LegacyEnv::Query& query);
template typename ApiEnv::Response ConvertResponse<ApiEnv, ApiEnv>(
const typename ApiEnv::Response& in,
const typename ApiEnv::Query& query);
template <>
typename ApiEnv::Response ConvertResponse<LegacyEnv, ApiEnv>(
const typename LegacyEnv::Response& in,
const typename LegacyEnv::Query& query) {
VLOG(1) << "ConvertResponse: legacy->api";
ApiEnv::Response out;
// The Legacy response does not carry enough information to create an api
// server response. Therefore, we wal the legacy query.
int in_field_index = 0;
for (const auto& query_form : query.form()) {
auto* out_form = out.add_form_suggestions();
for (const auto& query_field : query_form.field()) {
const auto& in_field = in.field(in_field_index);
auto* out_field = out_form->add_field_suggestions();
out_field->set_field_signature(query_field.signature());
if (in_field.has_overall_type_prediction()) {
out_field->set_primary_type_prediction(
in_field.overall_type_prediction());
} else if (in_field.predictions_size() > 0) {
out_field->set_primary_type_prediction(in_field.predictions(0).type());
}
for (const auto& in_prediction : in_field.predictions())
out_field->add_predictions()->set_type(in_prediction.type());
if (in_field.predictions().size() > 0 &&
in_field.predictions(0).has_may_use_prefilled_placeholder()) {
out_field->set_may_use_prefilled_placeholder(
in_field.predictions(0).may_use_prefilled_placeholder());
}
if (in_field.has_password_requirements()) {
out_field->mutable_password_requirements()->CopyFrom(
in_field.password_requirements());
}
++in_field_index;
}
}
return out;
}
template <>
typename LegacyEnv::Response ConvertResponse<ApiEnv, LegacyEnv>(
const typename ApiEnv::Response& in,
const typename ApiEnv::Query& query) {
VLOG(1) << "ConvertResponse: api->legacy";
LegacyEnv::Response out;
for (const auto& in_form : in.form_suggestions()) {
for (const auto& in_field : in_form.field_suggestions()) {
auto* out_field = out.add_field();
out_field->set_overall_type_prediction(
in_field.primary_type_prediction());
for (const auto& in_prediction : in_field.predictions()) {
auto* out_prediction = out_field->add_predictions();
out_prediction->set_type(in_prediction.type());
if (in_field.has_may_use_prefilled_placeholder()) {
out_prediction->set_may_use_prefilled_placeholder(
in_field.may_use_prefilled_placeholder());
}
}
// In case the predictions were not filled for sloppyness,
// just rely on the main prediction.
if (out_field->predictions_size() == 0) {
auto* out_prediction = out_field->add_predictions();
out_prediction->set_type(in_field.primary_type_prediction());
if (in_field.has_may_use_prefilled_placeholder()) {
out_prediction->set_may_use_prefilled_placeholder(
in_field.may_use_prefilled_placeholder());
}
}
if (in_field.has_password_requirements()) {
out_field->mutable_password_requirements()->CopyFrom(
in_field.password_requirements());
}
}
}
return out;
}
// Decompressed HTTP response read from WPR capture file. Will set
// |decompressed_http| to "" and return false if there is an error.
bool ServerCacheReplayer::RetrieveAndDecompressStoredHTTP(
......@@ -581,6 +744,21 @@ std::ostream& operator<<(std::ostream& out,
return out;
}
std::ostream& operator<<(std::ostream& out,
const autofill::AutofillPageQueryRequest& query) {
for (const auto& form : query.forms()) {
out << "\nForm\n signature: " << form.signature();
for (const auto& field : form.fields()) {
out << "\n Field\n signature: " << field.signature();
if (!field.name().empty())
out << "\n name: " << field.name();
if (!field.control_type().empty())
out << "\n control_type: " << field.control_type();
}
}
return out;
}
// Streams in text format. For consistency, taken from anonymous namespace in
// components/autofill/core/browser/form_structure.cc
std::ostream& operator<<(
......@@ -588,6 +766,23 @@ std::ostream& operator<<(
const autofill::AutofillQueryResponseContents& response) {
for (const auto& field : response.field()) {
out << "\nautofill_type: " << field.overall_type_prediction();
for (const auto& prediction : field.predictions())
out << "\n prediction: " << prediction.type();
}
return out;
}
std::ostream& operator<<(std::ostream& out,
const autofill::AutofillQueryResponse& response) {
for (const auto& form : response.form_suggestions()) {
out << "\nForm";
for (const auto& field : form.field_suggestions()) {
out << "\n Field\n signature: " << field.field_signature();
if (field.has_primary_type_prediction())
out << "\n primary_type_prediction: "
<< field.primary_type_prediction();
for (const auto& prediction : field.predictions())
out << "\n prediction: " << prediction.type();
}
}
return out;
}
......
......@@ -10,6 +10,7 @@
#include <utility>
#include "base/files/file_path.h"
#include "components/autofill/core/browser/proto/api_v1.pb.h"
#include "components/autofill/core/browser/proto/server.pb.h"
#include "content/public/test/url_loader_interceptor.h"
......@@ -27,18 +28,57 @@ std::pair<std::string, std::string> SplitHTTP(const std::string& http_text);
// components/autofill/core/browser/autofill_download_manager.cc
std::ostream& operator<<(std::ostream& out,
const autofill::AutofillQueryContents& query);
std::ostream& operator<<(std::ostream& out,
const autofill::AutofillPageQueryRequest& query);
// Streams in text format. For consistency, taken from anonymous namespace in
// components/autofill/core/browser/form_structure.cc
std::ostream& operator<<(
std::ostream& out,
const autofill::AutofillQueryResponseContents& response);
std::ostream& operator<<(std::ostream& out,
const autofill::AutofillQueryResponse& response);
class LegacyEnv {
public:
using Query = AutofillQueryContents;
using Response = AutofillQueryResponseContents;
};
// Gets a key from a given query request.
std::string GetKeyFromQueryRequest(const AutofillQueryContents& query_request);
class ApiEnv {
public:
using Query = AutofillPageQueryRequest;
using Response = AutofillQueryResponse;
};
enum class RequestType { kLegacyQueryProtoGET, kLegacyQueryProtoPOST };
// Conversion between different versions of queries.
template <typename ReadEnv, typename WriteEnv>
typename WriteEnv::Query ConvertQuery(const typename ReadEnv::Query& in);
template <>
typename ApiEnv::Query ConvertQuery<LegacyEnv, ApiEnv>(
const typename LegacyEnv::Query& in);
template <>
typename LegacyEnv::Query ConvertQuery<ApiEnv, LegacyEnv>(
const typename ApiEnv::Query& in);
// Conversion between different versions of responses.
template <typename ReadEnv, typename WriteEnv>
typename WriteEnv::Response ConvertResponse(
const typename ReadEnv::Response& in,
const typename ReadEnv::Query& query);
template <>
typename ApiEnv::Response ConvertResponse<LegacyEnv, ApiEnv>(
const typename LegacyEnv::Response& in,
const typename LegacyEnv::Query& query);
template <>
typename LegacyEnv::Response ConvertResponse<ApiEnv, LegacyEnv>(
const typename ApiEnv::Response& in,
const typename ApiEnv::Query& query);
// Switch `--autofill-server-type` is used to override the default behavior of
// using the cached responses from the wpr archive. The valid values match the
// enum AutofillServerBehaviorType below. Options are:
......
......@@ -342,6 +342,155 @@ TEST(AutofillCacheReplayerTest,
cache_replayer.GetResponseForQuery(query_with_no_match, &http_text));
}
template <typename U, typename V>
bool ProtobufsEqual(const U& u, const V& v) {
// Unfortunately, Chrome uses MessageLite, so we cannot use DebugString or the
// MessageDifferencer.
std::string u_serialized, v_serialized;
u.SerializeToString(&u_serialized);
v.SerializeToString(&v_serialized);
if (u_serialized != v_serialized) {
LOG(ERROR) << "Expected protobufs to be equal:\n" << u << "and:\n" << v;
LOG(ERROR) << "Note that this output is based on custom written string "
"serializers and the protobufs may be different in ways that "
"are not shown here.";
}
return u_serialized == v_serialized;
}
TEST(AutofillCacheReplayerTest, ProtobufConversion) {
AutofillRandomizedFormMetadata form_metadata;
form_metadata.mutable_id()->set_encoded_bits("foobar");
AutofillRandomizedFieldMetadata field_metadata;
field_metadata.mutable_id()->set_encoded_bits("foobarbaz");
// Form 1 (fields 101, 102), Form 2 (fields 201).
LegacyEnv::Query legacy_query;
{
legacy_query.set_client_version("DummyClient");
auto* form1 = legacy_query.add_form();
form1->set_signature(1);
form1->mutable_form_metadata()->CopyFrom(form_metadata);
auto* field101 = form1->add_field();
field101->set_signature(101);
field101->set_name("field_101");
field101->set_type("text");
field101->mutable_field_metadata()->CopyFrom(field_metadata);
auto* field102 = form1->add_field();
field102->set_signature(102);
field102->set_name("field_102");
field102->set_type("text");
auto* form2 = legacy_query.add_form();
form2->set_signature(2);
auto* field201 = form2->add_field();
field201->set_signature(201);
field201->set_name("field_201");
field201->set_type("text");
legacy_query.add_experiments(50);
legacy_query.add_experiments(51);
}
ApiEnv::Query api_query;
{
auto* form1 = api_query.add_forms();
form1->set_signature(1);
form1->mutable_metadata()->CopyFrom(form_metadata);
auto* field101 = form1->add_fields();
field101->set_signature(101);
field101->set_name("field_101");
field101->set_control_type("text");
field101->mutable_metadata()->CopyFrom(field_metadata);
auto* field102 = form1->add_fields();
field102->set_signature(102);
field102->set_name("field_102");
field102->set_control_type("text");
auto* form2 = api_query.add_forms();
form2->set_signature(2);
auto* field201 = form2->add_fields();
field201->set_signature(201);
field201->set_name("field_201");
field201->set_control_type("text");
api_query.add_experiments(50);
api_query.add_experiments(51);
}
LegacyEnv::Response legacy_response;
{
auto* field101 = legacy_response.add_field();
field101->set_overall_type_prediction(101);
auto* field101_prediction = field101->add_predictions();
field101_prediction->set_type(101);
field101_prediction->set_may_use_prefilled_placeholder(true);
field101_prediction = field101->add_predictions();
field101_prediction->set_type(1010);
field101_prediction->set_may_use_prefilled_placeholder(true);
// Todo: Password requirements
auto* field102 = legacy_response.add_field();
field102->set_overall_type_prediction(102);
auto* field102_prediction = field102->add_predictions();
field102_prediction->set_type(102);
field102_prediction->set_may_use_prefilled_placeholder(false);
auto* field201 = legacy_response.add_field();
field201->set_overall_type_prediction(201);
field201->add_predictions()->set_type(201);
}
ApiEnv::Response api_response;
{
auto* form1 = api_response.add_form_suggestions();
auto* field101 = form1->add_field_suggestions();
field101->set_field_signature(101);
field101->set_primary_type_prediction(101);
field101->add_predictions()->set_type(101);
field101->add_predictions()->set_type(1010);
field101->set_may_use_prefilled_placeholder(true);
// Todo: Password requirements
auto* field102 = form1->add_field_suggestions();
field102->set_field_signature(102);
field102->set_primary_type_prediction(102);
field102->add_predictions()->set_type(102);
field102->set_may_use_prefilled_placeholder(false);
auto* form2 = api_response.add_form_suggestions();
auto* field201 = form2->add_field_suggestions();
field201->set_field_signature(201);
field201->set_primary_type_prediction(201);
field201->add_predictions()->set_type(201);
}
// Verify equivalence of converted queries.
EXPECT_TRUE(ProtobufsEqual(legacy_query, legacy_query));
EXPECT_TRUE(ProtobufsEqual(legacy_query,
ConvertQuery<LegacyEnv, LegacyEnv>(legacy_query)));
EXPECT_TRUE(
ProtobufsEqual(legacy_query, ConvertQuery<ApiEnv, LegacyEnv>(api_query)));
EXPECT_TRUE(ProtobufsEqual(api_query, api_query));
EXPECT_TRUE(
ProtobufsEqual(api_query, ConvertQuery<ApiEnv, ApiEnv>(api_query)));
EXPECT_TRUE(
ProtobufsEqual(api_query, ConvertQuery<LegacyEnv, ApiEnv>(legacy_query)));
// Verify equivalence of converted responses.
EXPECT_TRUE(ProtobufsEqual(legacy_response, legacy_response));
EXPECT_TRUE(ProtobufsEqual(
legacy_response,
ConvertResponse<LegacyEnv, LegacyEnv>(legacy_response, legacy_query)));
EXPECT_TRUE(ProtobufsEqual(
legacy_response,
ConvertResponse<ApiEnv, LegacyEnv>(api_response, api_query)));
EXPECT_TRUE(ProtobufsEqual(api_response, api_response));
EXPECT_TRUE(ProtobufsEqual(
api_response, ConvertResponse<ApiEnv, ApiEnv>(api_response, api_query)));
EXPECT_TRUE(ProtobufsEqual(api_response, ConvertResponse<LegacyEnv, ApiEnv>(
legacy_response, legacy_query)));
}
// Test suite for Query response retrieval test.
class AutofillCacheReplayerGetResponseForQueryTest
: public testing::TestWithParam<RequestType> {};
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment