blob: ca2f8c951e7e53d0f2ddeec59e9a6aaa50527e5c [file] [log] [blame]
// 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 <type_traits>
#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_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.name = std::move(name);
f.name_attribute = f.name;
f.id_attribute = u"some_id";
f.form_control_type = FormControlType::kInputText;
f.check_status = FormFieldData::CheckStatus::kChecked;
f.role = FormFieldData::RoleAttribute::kOther;
f.is_focusable = true;
f.renderer_id = FieldRendererId(renderer_id++);
return f;
}
FormData CreateTestForm() {
FormData f;
f.name = u"FormName";
f.name_attribute = f.name;
f.id_attribute = u"form_id";
f.url = GURL("https://foo.com");
f.action = GURL("https://bar.com");
f.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.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.name = af.form().name + u"x";
EXPECT_FALSE(af.SimilarFormAs(f));
// If name attributes differ, they are not similar.
f = af.form();
f.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.id_attribute = af.form().id_attribute + u"x";
EXPECT_FALSE(af.SimilarFormAs(f));
// If urls differ, they are not similar.
f = af.form();
f.url = GURL("https://other.com");
EXPECT_FALSE(af.SimilarFormAs(f));
// If actions differ, they are not similar.
f = af.form();
f.action = GURL("https://other.com");
EXPECT_FALSE(af.SimilarFormAs(f));
// If their global ids differ, they are not similar.
f = af.form();
f.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.fields = {CreateTestField()};
FormDataAndroid af(f, kSampleSessionId);
EXPECT_TRUE(af.SimilarFormAs(f));
// Forms with different numbers of fields are not similar.
f.fields = {CreateTestField(), CreateTestField()};
EXPECT_FALSE(af.SimilarFormAs(f));
// Forms with similar fields are similar.
f = af.form();
f.fields.front().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();
f.fields.front().name += u"x";
EXPECT_FALSE(af.SimilarFormAs(f));
}
// Tests that `SimilarFormAsWithDiagnosis` returns the correct reason why two
// forms are not considered similar.
TEST_F(FormDataAndroidTest, SimilarFormAsWithDiagnosis) {
using SimilarityCheckComponent = FormDataAndroid::SimilarityCheckComponent;
using SimilarityCheckResult = FormDataAndroid::SimilarityCheckResult;
// Returns the bitwise or of the underlying types of the passed enums.
auto to_check_result = [](auto... components) {
return SimilarityCheckResult((base::to_underlying(components) | ...));
};
FormData f = CreateTestForm();
FormDataAndroid af(f, kSampleSessionId);
EXPECT_EQ(af.SimilarFormAsWithDiagnosis(f),
FormDataAndroid::kFormsAreSimilar);
f = af.form();
f.renderer_id = FormRendererId(f.renderer_id.value() + 1);
EXPECT_EQ(af.SimilarFormAsWithDiagnosis(f),
to_check_result(SimilarityCheckComponent::kGlobalId));
f = af.form();
f.name = af.form().name + u"x";
EXPECT_EQ(af.SimilarFormAsWithDiagnosis(f),
to_check_result(SimilarityCheckComponent::kName));
f = af.form();
f.name_attribute = af.form().name_attribute + u"x";
EXPECT_EQ(af.SimilarFormAsWithDiagnosis(f),
to_check_result(SimilarityCheckComponent::kNameAttribute));
f = af.form();
f.id_attribute = af.form().id_attribute + u"x";
EXPECT_EQ(af.SimilarFormAsWithDiagnosis(f),
to_check_result(SimilarityCheckComponent::kIdAttribute));
f = af.form();
f.url = GURL("https://other.com");
EXPECT_EQ(af.SimilarFormAsWithDiagnosis(f),
to_check_result(SimilarityCheckComponent::kUrl));
f = af.form();
f.action = GURL("https://other.com");
EXPECT_EQ(af.SimilarFormAsWithDiagnosis(f),
to_check_result(SimilarityCheckComponent::kAction));
f = af.form();
f.name_attribute = af.form().name_attribute + u"x";
f.id_attribute = af.form().id_attribute + u"x";
EXPECT_EQ(af.SimilarFormAsWithDiagnosis(f),
to_check_result(SimilarityCheckComponent::kIdAttribute,
SimilarityCheckComponent::kNameAttribute));
}
TEST_F(FormDataAndroidTest, GetFieldIndex) {
FormData f = CreateTestForm();
f.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.
f.fields[1].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.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].
f.fields[1].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.
f.fields[1].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.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.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.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.fields = {CreateTestField(), CreateTestField()};
FormStructure form_structure(form);
ASSERT_EQ(form_structure.field_count(), 2u);
form.fields.push_back(CreateTestField());
std::swap(form.fields.front(), form.fields.back());
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.fields = {CreateTestField(), CreateTestField(), CreateTestField()};
form.fields[0].role = FormFieldData::RoleAttribute::kPresentation;
form.fields[1].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`.
form.fields[0].role = FormFieldData::RoleAttribute::kOther;
form.fields[1].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