// Copyright 2023 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/android_autofill/browser/form_data_android.h"
#include <memory>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "base/test/bind.h"
#include "base/types/cxx23_to_underlying.h"
#include "components/android_autofill/browser/android_autofill_bridge_factory.h"
#include "components/android_autofill/browser/form_field_data_android.h"
#include "components/android_autofill/browser/mock_form_data_android_bridge.h"
#include "components/android_autofill/browser/mock_form_field_data_android_bridge.h"
#include "components/autofill/core/browser/autofill_type.h"
#include "components/autofill/core/browser/form_structure.h"
#include "components/autofill/core/common/autofill_test_utils.h"
#include "components/autofill/core/common/form_data.h"
#include "components/autofill/core/common/form_data_test_api.h"
#include "components/autofill/core/common/form_field_data.h"
#include "components/autofill/core/common/unique_ids.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace autofill {
namespace {
using ::autofill::test::DeepEqualsFormData;
using ::testing::_;
using ::testing::Eq;
using ::testing::InSequence;
using ::testing::MockFunction;
using ::testing::Pointwise;
using ::testing::SizeIs;
constexpr SessionId kSampleSessionId(123);
MATCHER(SimilarFieldAs, "") {
// `std::get<0>(arg)` is a `std::unique_ptr<FormFieldDataAndroid>`, while
// `std::get<1>(arg)` is a `FormFieldData`.
return std::get<0>(arg) && std::get<0>(arg)->SimilarFieldAs(std::get<1>(arg));
}
FormFieldData CreateTestField(std::u16string name = u"SomeName") {
static uint64_t renderer_id = 1;
FormFieldData f;
f.set_name(std::move(name));
f.set_name_attribute(f.name());
f.set_id_attribute(u"some_id");
f.set_form_control_type(FormControlType::kInputText);
f.set_check_status(FormFieldData::CheckStatus::kChecked);
f.set_role(FormFieldData::RoleAttribute::kOther);
f.set_is_focusable(true);
f.set_renderer_id(FieldRendererId(renderer_id++));
return f;
}
FormData CreateTestForm() {
FormData f;
f.set_name(u"FormName");
f.set_name_attribute(f.name());
f.set_id_attribute(u"form_id");
f.set_url(GURL("https://foo.com"));
f.set_action(GURL("https://bar.com"));
f.set_renderer_id(test::MakeFormRendererId());
return f;
}
} // namespace
class FormDataAndroidTest : public ::testing::Test {
public:
FormDataAndroidTest() = default;
~FormDataAndroidTest() override = default;
void SetUp() override {
// Registers a testing factory for `FormDataAndroidBridge` that creates a
// mocked bridge and always writes the pointer to the last created bridge
// into `form_bridge_`.
AndroidAutofillBridgeFactory::GetInstance()
.SetFormDataAndroidTestingFactory(base::BindLambdaForTesting(
[this]() -> std::unique_ptr<FormDataAndroidBridge> {
auto bridge = std::make_unique<MockFormDataAndroidBridge>();
form_bridge_ = bridge.get();
return bridge;
}));
// Registers a testing factory for `FormFieldDataAndroidBridge` that creates
// a mocked bridge and appends the pointers to the bridges to
// `field_bridges_`.
AndroidAutofillBridgeFactory::GetInstance()
.SetFormFieldDataAndroidTestingFactory(base::BindLambdaForTesting(
[this]() -> std::unique_ptr<FormFieldDataAndroidBridge> {
auto bridge = std::make_unique<MockFormFieldDataAndroidBridge>();
field_bridges_.push_back(bridge.get());
return bridge;
}));
}
void TearDown() override {
form_bridge_ = nullptr;
field_bridges_.clear();
AndroidAutofillBridgeFactory::GetInstance()
.SetFormDataAndroidTestingFactory({});
AndroidAutofillBridgeFactory::GetInstance()
.SetFormFieldDataAndroidTestingFactory({});
}
protected:
const std::vector<MockFormFieldDataAndroidBridge*>& field_bridges() {
return field_bridges_;
}
MockFormDataAndroidBridge& form_bridge() { return *form_bridge_; }
private:
test::AutofillUnitTestEnvironment autofill_test_environment_;
std::vector<MockFormFieldDataAndroidBridge*> field_bridges_;
raw_ptr<MockFormDataAndroidBridge> form_bridge_;
};
// Tests that `FormDataAndroid` creates a copy of its argument.
TEST_F(FormDataAndroidTest, Form) {
FormData form = CreateTestForm();
FormDataAndroid form_android(form, kSampleSessionId);
EXPECT_TRUE(FormData::DeepEqual(form, form_android.form()));
form.set_name(form.name() + u"x");
EXPECT_FALSE(FormData::DeepEqual(form, form_android.form()));
}
// Tests that form similarity checks include name, name_attribute, id_attribute,
// url, and action.
// Similarity checks are used to determine whether a web page has modified a
// field significantly enough to warrant restarting an ongoing Autofill session,
// e.g., because their change would lead to a change in type predictions. As a
// result, this check includes attributes that the user cannot change and that
// are unlikely to have been superficial dynamic changes by Javascript on the
// website.
TEST_F(FormDataAndroidTest, SimilarFormAs) {
FormData f = CreateTestForm();
FormDataAndroid af(f, kSampleSessionId);
// If forms are the same, they are similar.
EXPECT_TRUE(af.SimilarFormAs(f));
// If names differ, they are not similar.
f.set_name(af.form().name() + u"x");
EXPECT_FALSE(af.SimilarFormAs(f));
// If name attributes differ, they are not similar.
f = af.form();
f.set_name_attribute(af.form().name_attribute() + u"x");
EXPECT_FALSE(af.SimilarFormAs(f));
// If id attributes differ, they are not similar.
f = af.form();
f.set_id_attribute(af.form().id_attribute() + u"x");
EXPECT_FALSE(af.SimilarFormAs(f));
// If urls differ, they are not similar.
f = af.form();
f.set_url(GURL("https://other.com"));
EXPECT_FALSE(af.SimilarFormAs(f));
// If actions differ, they are not similar.
f = af.form();
f.set_action(GURL("https://other.com"));
EXPECT_FALSE(af.SimilarFormAs(f));
// If their global ids differ, they are not similar.
f = af.form();
f.set_renderer_id(FormRendererId(f.renderer_id().value() + 1));
EXPECT_FALSE(af.SimilarFormAs(f));
}
// Tests that form similarity checks similarity of the fields.
TEST_F(FormDataAndroidTest, SimilarFormAs_Fields) {
FormData f = CreateTestForm();
f.set_fields({CreateTestField()});
FormDataAndroid af(f, kSampleSessionId);
EXPECT_TRUE(af.SimilarFormAs(f));
// Forms with different numbers of fields are not similar.
f.set_fields({CreateTestField(), CreateTestField()});
EXPECT_FALSE(af.SimilarFormAs(f));
// Forms with similar fields are similar.
f = af.form();
test_api(f).field(0).set_value(f.fields().front().value() + u"x");
EXPECT_TRUE(af.SimilarFormAs(f));
// Forms with fields that are not similar, are not similar either.
f = af.form();
test_api(f).field(0).set_name(f.fields().front().name() + u"x");
EXPECT_FALSE(af.SimilarFormAs(f));
}
TEST_F(FormDataAndroidTest, GetFieldIndex) {
FormData f = CreateTestForm();
f.set_fields({CreateTestField(u"name1"), CreateTestField(u"name2")});
FormDataAndroid af(f, kSampleSessionId);
size_t index = 100;
EXPECT_TRUE(af.GetFieldIndex(f.fields()[1], &index));
EXPECT_EQ(index, 1u);
// As updates in `f` are not propagated to the Android version `af`, the
// lookup fails.
test_api(f).field(1).set_name(u"name3");
EXPECT_FALSE(af.GetFieldIndex(f.fields()[1], &index));
}
// Tests that `GetSimilarFieldIndex` only checks field similarity.
TEST_F(FormDataAndroidTest, GetSimilarFieldIndex) {
FormData f = CreateTestForm();
f.set_fields({CreateTestField(u"name1"), CreateTestField(u"name2")});
FormDataAndroid af(f, kSampleSessionId);
size_t index = 100;
// Value is not part of a field similarity check, so this field is similar to
// af.form().fields[1].
test_api(f).field(1).set_value(u"some value");
EXPECT_TRUE(af.GetSimilarFieldIndex(f.fields()[1], &index));
EXPECT_EQ(index, 1u);
// Name is a part of the field similarity check, so there is no field similar
// to this one.
test_api(f).field(1).set_name(u"name3");
EXPECT_FALSE(af.GetSimilarFieldIndex(f.fields()[1], &index));
}
// Tests that calling `OnFormFieldDidChange` propagates the changes to the
// affected field.
TEST_F(FormDataAndroidTest, OnFormFieldDidChange) {
FormData form = CreateTestForm();
form.set_fields({CreateTestField(), CreateTestField()});
FormDataAndroid form_android(form, kSampleSessionId);
ASSERT_THAT(field_bridges(), SizeIs(2));
ASSERT_TRUE(field_bridges()[0]);
ASSERT_TRUE(field_bridges()[1]);
constexpr std::u16string_view kNewValue = u"SomeNewValue";
EXPECT_CALL(*field_bridges()[0], UpdateValue).Times(0);
EXPECT_CALL(*field_bridges()[1], UpdateValue(kNewValue));
form_android.OnFormFieldDidChange(1, kNewValue);
EXPECT_EQ(form_android.form().fields()[1].value(), kNewValue);
}
// Tests that the calls to update field types are propagated to the fields.
TEST_F(FormDataAndroidTest, UpdateFieldTypes) {
FormData form = CreateTestForm();
form.set_fields({CreateTestField(), CreateTestField()});
FormDataAndroid form_android(form, kSampleSessionId);
ASSERT_THAT(field_bridges(), SizeIs(2));
ASSERT_TRUE(field_bridges()[0]);
ASSERT_TRUE(field_bridges()[1]);
EXPECT_CALL(*field_bridges()[0], UpdateFieldTypes);
EXPECT_CALL(*field_bridges()[1], UpdateFieldTypes);
form_android.UpdateFieldTypes(FormStructure(form));
}
// Tests that `UpdateFieldTypes(base::flat_map<FieldGlobalId, AutofillType))`
// - sets all types (heuristic, server, computed),
// - only calls the JNI bridge for fields whose types differ.
TEST_F(FormDataAndroidTest, UpdateFieldTypesWithExplicitType) {
const AutofillType kUsername(FieldType::USERNAME);
const AutofillType kPassword(FieldType::PASSWORD);
FormData form = CreateTestForm();
form.set_fields({CreateTestField(), CreateTestField()});
FormDataAndroid form_android(form, kSampleSessionId);
ASSERT_THAT(field_bridges(), SizeIs(2));
MockFunction<void(int)> check;
{
InSequence s;
EXPECT_CALL(*field_bridges()[0], UpdateFieldTypes(Eq(kUsername)));
EXPECT_CALL(*field_bridges()[1], UpdateFieldTypes(Eq(kPassword)));
EXPECT_CALL(check, Call(1));
EXPECT_CALL(check, Call(2));
EXPECT_CALL(*field_bridges()[0], UpdateFieldTypes(Eq(kPassword)));
EXPECT_CALL(check, Call(3));
EXPECT_CALL(*field_bridges()[0], UpdateFieldTypes(Eq(kUsername)));
}
// Update all the fields to new types.
form_android.UpdateFieldTypes({{form.fields()[0].global_id(), kUsername},
{form.fields()[1].global_id(), kPassword}});
check.Call(1);
// Update to the same type - this should not trigger calls to JNI.
form_android.UpdateFieldTypes({{form.fields()[0].global_id(), kUsername},
{form.fields()[1].global_id(), kPassword}});
check.Call(2);
// Update only one field.
FieldGlobalId unknown_id = CreateTestField().global_id();
form_android.UpdateFieldTypes(
{{form.fields()[0].global_id(), kPassword}, {unknown_id, kUsername}});
check.Call(3);
// Update both, but only the first one has changes.
form_android.UpdateFieldTypes({{form.fields()[0].global_id(), kUsername},
{form.fields()[1].global_id(), kPassword}});
}
// Tests that the calls to update field types are propagated to the fields.
TEST_F(FormDataAndroidTest, UpdateFieldTypes_ChangedForm) {
FormData form = CreateTestForm();
form.set_fields({CreateTestField(), CreateTestField()});
FormStructure form_structure(form);
ASSERT_EQ(form_structure.field_count(), 2u);
form.set_fields({CreateTestField(), form.fields()[1], form.fields()[0]});
FormDataAndroid form_android(form, kSampleSessionId);
ASSERT_THAT(field_bridges(), SizeIs(3));
ASSERT_TRUE(field_bridges()[0]);
ASSERT_TRUE(field_bridges()[1]);
ASSERT_TRUE(field_bridges()[2]);
EXPECT_CALL(*field_bridges()[0], UpdateFieldTypes).Times(0);
EXPECT_CALL(*field_bridges()[1], UpdateFieldTypes);
EXPECT_CALL(*field_bridges()[2], UpdateFieldTypes);
form_android.UpdateFieldTypes(form_structure);
}
// Tests that calling `UpdateFieldVisibilities` propagates the visibility to the
// affected fields and returns their indices.
TEST_F(FormDataAndroidTest, UpdateFieldVisibilities) {
FormData form = CreateTestForm();
form.set_fields({CreateTestField(), CreateTestField(), CreateTestField()});
test_api(form).field(0).set_role(FormFieldData::RoleAttribute::kPresentation);
test_api(form).field(1).set_is_focusable(false);
EXPECT_FALSE(form.fields()[0].IsFocusable());
EXPECT_FALSE(form.fields()[1].IsFocusable());
EXPECT_TRUE(form.fields()[2].IsFocusable());
FormDataAndroid form_android(form, kSampleSessionId);
ASSERT_THAT(field_bridges(), SizeIs(3));
ASSERT_TRUE(field_bridges()[0]);
ASSERT_TRUE(field_bridges()[1]);
ASSERT_TRUE(field_bridges()[2]);
// `form_android` created a copy of `form` - therefore modifying the fields
// here does not change the values inside `form_android`.
test_api(form).field(0).set_role(FormFieldData::RoleAttribute::kOther);
test_api(form).field(1).set_is_focusable(true);
EXPECT_TRUE(form.fields()[0].IsFocusable());
EXPECT_TRUE(form.fields()[1].IsFocusable());
EXPECT_TRUE(form.fields()[2].IsFocusable());
EXPECT_CALL(*field_bridges()[0], UpdateVisible(true));
EXPECT_CALL(*field_bridges()[1], UpdateVisible(true));
EXPECT_CALL(*field_bridges()[2], UpdateVisible).Times(0);
form_android.UpdateFieldVisibilities(form);
EXPECT_TRUE(FormData::DeepEqual(form, form_android.form()));
}
// Tests that `GetJavaPeer` passes the correct `FormData`, `SessionId` and
// `FormFieldDataAndroid` parameters to the Java bridge.
TEST_F(FormDataAndroidTest, GetJavaPeer) {
FormData form = CreateTestForm();
FormDataAndroid af(form, kSampleSessionId);
EXPECT_CALL(form_bridge(),
GetOrCreateJavaPeer(DeepEqualsFormData(form), kSampleSessionId,
Pointwise(SimilarFieldAs(), form.fields())));
af.GetJavaPeer();
}
} // namespace autofill