// Copyright 2020 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/android_autofill_provider.h"
#include <memory>
#include "base/android/build_info.h"
#include "base/containers/span.h"
#include "base/memory/raw_ptr.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/types/cxx23_to_underlying.h"
#include "components/android_autofill/browser/android_autofill_bridge_factory.h"
#include "components/android_autofill/browser/android_autofill_features.h"
#include "components/android_autofill/browser/android_autofill_manager.h"
#include "components/android_autofill/browser/android_autofill_provider_bridge.h"
#include "components/android_autofill/browser/android_autofill_provider_test_api.h"
#include "components/android_autofill/browser/autofill_provider.h"
#include "components/android_autofill/browser/form_data_android.h"
#include "components/android_autofill/browser/form_data_android_test_api.h"
#include "components/android_autofill/browser/mock_form_field_data_android_bridge.h"
#include "components/autofill/android/touch_to_fill_keyboard_suppressor.h"
#include "components/autofill/content/browser/test_autofill_client_injector.h"
#include "components/autofill/content/browser/test_autofill_driver_injector.h"
#include "components/autofill/content/browser/test_autofill_manager_injector.h"
#include "components/autofill/content/browser/test_content_autofill_client.h"
#include "components/autofill/core/browser/autofill_form_test_utils.h"
#include "components/autofill/core/browser/autofill_manager_test_api.h"
#include "components/autofill/core/browser/test_autofill_manager_waiter.h"
#include "components/autofill/core/common/autofill_test_utils.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/signatures.h"
#include "components/autofill/core/common/unique_ids.h"
#include "components/webauthn/android/mock_webauthn_cred_man_delegate.h"
#include "components/webauthn/android/webauthn_cred_man_delegate_factory.h"
#include "components/webauthn/android/webauthn_cred_man_delegate_factory_test_api.h"
#include "content/public/test/navigation_simulator.h"
#include "content/public/test/test_renderer_host.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/origin.h"
namespace autofill {
namespace {
using ::testing::_;
using ::testing::AllOf;
using ::testing::AtLeast;
using ::testing::ElementsAre;
using ::testing::Eq;
using ::testing::Field;
using ::testing::InSequence;
using ::testing::Mock;
using ::testing::MockFunction;
using ::testing::NiceMock;
using ::testing::Optional;
using ::testing::Property;
using ::testing::ResultOf;
using ::testing::Return;
using ::testing::SaveArg;
using ::testing::Truly;
using ::testing::UnorderedElementsAre;
using ::testing::WithArg;
using FieldInfo = AndroidAutofillProviderBridge::FieldInfo;
using PrefillRequestState = AndroidAutofillProvider::PrefillRequestState;
using test::CreateFormDataForFrame;
using test::CreateTestCreditCardFormData;
using test::CreateTestFormField;
using test::CreateTestPasswordFormData;
using test::CreateTestPersonalInformationFormData;
auto EqualsFieldInfo(size_t index) {
return Field("index", &AndroidAutofillProviderBridge::FieldInfo::index,
Eq(index));
}
// Creates a matcher that compares a `FormDataAndroid::form()` to `expected`.
auto EqualsFormData(const FormData& expected) {
return ResultOf(
[expected](const FormDataAndroid& actual) {
return FormData::DeepEqual(expected, actual.form());
},
true);
}
auto EqualsFormDataWithFields(const FormData& form, auto fields_matcher) {
return AllOf(
EqualsFormData(form),
ResultOf(
[](FormDataAndroid& form_android)
-> const std::vector<std::unique_ptr<FormFieldDataAndroid>>& {
return test_api(form_android).fields();
},
fields_matcher));
}
// Creates a matcher that compares the results of a `FormDataAndroid`'s `form()`
// and `session_id()` methods to `form` and `session_id_matcher`.
auto EqualsFormDataWithSessionId(const FormData& form,
auto session_id_matcher) {
return AllOf(EqualsFormData(form),
Property(&FormDataAndroid::session_id, session_id_matcher));
}
// Returns an action that writes the `SessionId` of a `FormDataAndroid` into the
// out parameter `session_id`. Note that `session_id` must be valid at least
// until the action is executed.
auto SaveSessionId(SessionId* session_id) {
return [session_id](const FormDataAndroid& form_android) {
*session_id = form_android.session_id();
};
}
FormData CreateTestBasicForm() {
FormData form;
form.set_renderer_id(test::MakeFormRendererId());
form.set_url(GURL("https://foo.com/form.html"));
form.set_action(GURL("https://foo.com/submit.html"));
form.set_main_frame_origin(url::Origin::Create(form.url()));
return form;
}
FormData CreateTestLoginForm() {
FormData form = CreateTestBasicForm();
form.set_name(u"login_form");
form.set_fields(
{CreateTestFormField(/*label=*/"Username", /*name=*/"username",
/*value=*/"", FormControlType::kInputText),
CreateTestFormField(/*label=*/"Password", /*name=*/"password",
/*value=*/"", FormControlType::kInputPassword)});
return form;
}
FormData CreateTestChangePasswordForm() {
FormData form = CreateTestBasicForm();
form.set_name(u"change_password_form");
form.set_fields(
{CreateTestFormField(/*label=*/"Password", /*name=*/"password1",
/*value=*/"", FormControlType::kInputPassword),
CreateTestFormField(/*label=*/"Password", /*name=*/"password2",
/*value=*/"", FormControlType::kInputPassword)});
return form;
}
FormData CreateTestWebAuthnPasswordFormData() {
std::vector<FormFieldData> fields;
fields.push_back(CreateTestFormField(
/*label=*/"Username:", /*name=*/"username",
/*value=*/"", FormControlType::kInputText, /*autocomplete=*/"webauthn"));
fields.push_back(
CreateTestFormField(/*label=*/"Password:", /*name=*/"password",
/*value=*/"", FormControlType::kInputPassword));
FormData form;
form.set_renderer_id(test::MakeFormRendererId());
form.set_url(GURL("https://foo.com/form.html"));
form.set_action(GURL("https://foo.com/submit.html"));
form.set_main_frame_origin(url::Origin::Create(form.url()));
form.set_fields(std::move(fields));
return form;
}
class TestAndroidAutofillManager : public AndroidAutofillManager {
public:
explicit TestAndroidAutofillManager(ContentAutofillDriver* driver)
: AndroidAutofillManager(driver) {}
void OnFormsSeen(const std::vector<FormData>& updated_forms,
const std::vector<FormGlobalId>& removed_forms) override {
TestAutofillManagerWaiter waiter(*this, {AutofillManagerEvent::kFormsSeen});
AutofillManager::OnFormsSeen(updated_forms, removed_forms);
ASSERT_TRUE(waiter.Wait());
}
void SimulatePropagateAutofillPredictions(FormGlobalId form_id) {
NotifyObservers(&Observer::OnFieldTypesDetermined, form_id,
Observer::FieldTypeSource::kAutofillServer);
}
void SimulateOnAskForValuesToFill(const FormData& form,
const FormFieldData& field) {
gfx::PointF p = field.bounds().origin();
gfx::Rect caret_bounds(gfx::Point(p.x(), p.y()), gfx::Size(0, 10));
OnAskForValuesToFillImpl(
form, field.global_id(), caret_bounds,
AutofillSuggestionTriggerSource::kTextFieldDidChange);
}
void SimulateOnFocusOnFormField(const FormData& form,
const FormFieldData& field) {
OnFocusOnFormFieldImpl(form, field.global_id());
}
void SimulateOnFormSubmitted(const FormData& form,
bool known_success,
mojom::SubmissionSource source) {
OnFormSubmittedImpl(form, known_success, source);
}
void SimulateOnTextFieldDidChange(const FormData& form,
const FormFieldData& field) {
OnTextFieldDidChangeImpl(form, field.global_id(), base::TimeTicks::Now());
}
void SimulateOnTextFieldDidScroll(const FormData& form,
const FormFieldData& field) {
OnTextFieldDidScrollImpl(form, field.global_id());
}
};
class MockAndroidAutofillProviderBridge : public AndroidAutofillProviderBridge {
public:
explicit MockAndroidAutofillProviderBridge() = default;
~MockAndroidAutofillProviderBridge() override = default;
MOCK_METHOD(void,
AttachToJavaAutofillProvider,
(JNIEnv*, const base::android::JavaRef<jobject>&),
(override));
MOCK_METHOD(void, SendPrefillRequest, (FormDataAndroid&), (override));
MOCK_METHOD(void,
StartAutofillSession,
(FormDataAndroid&, const FieldInfo&, bool),
(override));
MOCK_METHOD(void, OnServerPredictionsAvailable, (), (override));
MOCK_METHOD(void,
ShowDatalistPopup,
(base::span<const SelectOption>, bool),
(override));
MOCK_METHOD(void, HideDatalistPopup, (), (override));
MOCK_METHOD(void,
OnFocusChanged,
(const std::optional<FieldInfo>&),
(override));
MOCK_METHOD(void, OnFormFieldDidChange, (const FieldInfo&), (override));
MOCK_METHOD(void,
OnFormFieldVisibilitiesDidChange,
(base::span<const int>),
(override));
MOCK_METHOD(void, OnTextFieldDidScroll, (const FieldInfo&), (override));
MOCK_METHOD(void, OnFormSubmitted, (mojom::SubmissionSource), (override));
MOCK_METHOD(void, OnDidFillAutofillFormData, (), (override));
MOCK_METHOD(void, CancelSession, (), (override));
MOCK_METHOD(void, Reset, (), (override));
};
content::RenderFrameHost* NavigateAndCommitFrame(content::RenderFrameHost* rfh,
const GURL& url) {
std::unique_ptr<content::NavigationSimulator> simulator =
content::NavigationSimulator::CreateRendererInitiated(url, rfh);
simulator->Commit();
return simulator->GetFinalRenderFrameHost();
}
} // namespace
class AndroidAutofillProviderTestBase
: public content::RenderViewHostTestHarness {
public:
void SetUp() override {
content::RenderViewHostTestHarness::SetUp();
// Set up mock bridges.
AndroidAutofillBridgeFactory::GetInstance()
.SetFormFieldDataAndroidTestingFactory(base::BindLambdaForTesting(
[]() -> std::unique_ptr<FormFieldDataAndroidBridge> {
return std::make_unique<
NiceMock<MockFormFieldDataAndroidBridge>>();
}));
AndroidAutofillBridgeFactory::GetInstance()
.SetAndroidAutofillProviderTestingFactory(base::BindLambdaForTesting(
[&bridge_ptr = provider_bridge_](
AndroidAutofillProviderBridge::Delegate* delegate)
-> std::unique_ptr<AndroidAutofillProviderBridge> {
auto bridge = std::make_unique<
NiceMock<MockAndroidAutofillProviderBridge>>();
bridge_ptr = bridge.get();
return bridge;
}));
// Create the provider.
AndroidAutofillProvider::CreateForWebContents(web_contents());
}
void TearDown() override {
provider_bridge_ = nullptr;
content::RenderViewHostTestHarness::TearDown();
}
content::RenderFrameHost* main_frame() {
return web_contents()->GetPrimaryMainFrame();
}
TestAndroidAutofillManager& android_autofill_manager(
content::RenderFrameHost* rfh = nullptr) {
return *autofill_manager_injector_[rfh ? rfh : main_frame()];
}
AndroidAutofillProvider& autofill_provider() {
return *AndroidAutofillProvider::FromWebContents(web_contents());
}
AndroidAutofillProviderBridge::Delegate& provider_bridge_delegate() {
return static_cast<AndroidAutofillProviderBridge::Delegate&>(
autofill_provider());
}
// Returns the local frame token of the primary main frame.
LocalFrameToken main_frame_token() {
return LocalFrameToken(main_frame()->GetFrameToken().value());
}
MockAndroidAutofillProviderBridge& provider_bridge() {
return *provider_bridge_;
}
private:
test::AutofillUnitTestEnvironment autofill_environment_;
TestAutofillClientInjector<TestContentAutofillClient>
autofill_client_injector_;
TestAutofillManagerInjector<TestAndroidAutofillManager>
autofill_manager_injector_;
raw_ptr<MockAndroidAutofillProviderBridge> provider_bridge_ = nullptr;
};
class AndroidAutofillProviderTest : public AndroidAutofillProviderTestBase {
public:
void SetUp() override {
AndroidAutofillProviderTestBase::SetUp();
// Navigation forces the creation of an AndroidAutofillManager for the main
// frame.
NavigateAndCommit(GURL("about:blank"));
FocusWebContentsOnMainFrame();
}
};
// Tests that AndroidAutofillManager keeps track of the predictions it is
// informed about.
TEST_F(AndroidAutofillProviderTest, HasServerPrediction) {
FormData form = CreateTestPersonalInformationFormData();
EXPECT_FALSE(
android_autofill_manager().has_server_prediction(form.global_id()));
android_autofill_manager().SimulatePropagateAutofillPredictions(
form.global_id());
EXPECT_TRUE(
android_autofill_manager().has_server_prediction(form.global_id()));
// Resetting removes prediction state.
test_api(android_autofill_manager()).Reset();
EXPECT_FALSE(
android_autofill_manager().has_server_prediction(form.global_id()));
}
// Tests that triggering `OnAskForValuesToFill` results in starting an Autofill
// session for the focused form and field.
TEST_F(AndroidAutofillProviderTest, OnAskForValuesToFillStartsSession) {
base::HistogramTester histogram_tester;
FormData form = CreateFormDataForFrame(
CreateTestPersonalInformationFormData(), main_frame_token());
android_autofill_manager().OnFormsSeen({form}, /*removed_forms=*/{});
EXPECT_CALL(
provider_bridge(),
StartAutofillSession(EqualsFormData(form), EqualsFieldInfo(/*index=*/0),
/*has_server_predictions=*/false));
android_autofill_manager().SimulateOnAskForValuesToFill(
form, form.fields().front());
}
// Tests that a focus change within the form of an ongoing autofill session
// results in a focus change event that is sent to Java.
TEST_F(AndroidAutofillProviderTest, OnFocusChangeInsideCurrentAutofillForm) {
base::HistogramTester histogram_tester;
FormData form = CreateFormDataForFrame(
CreateTestPersonalInformationFormData(), main_frame_token());
android_autofill_manager().OnFormsSeen({form}, /*removed_forms=*/{});
android_autofill_manager().SimulateOnAskForValuesToFill(
form, form.fields().front());
MockFunction<void(int)> check;
{
InSequence s;
EXPECT_CALL(provider_bridge(),
OnFocusChanged(Optional(EqualsFieldInfo(/*index=*/1))));
EXPECT_CALL(check, Call(1));
EXPECT_CALL(provider_bridge(), OnFocusChanged(Eq(std::nullopt)));
EXPECT_CALL(check, Call(2));
}
android_autofill_manager().SimulateOnFocusOnFormField(form, form.fields()[1]);
check.Call(1);
android_autofill_manager().OnFocusOnNonFormFieldImpl();
check.Call(2);
}
// Tests that triggering `OnAskForValuesToFill` with a field results in an
// update to last_focused_field_id. The update is important so that
// `AutofillProvider::RendererShouldAcceptDatalistSuggestion` is passed the
// correct field ID.
TEST_F(AndroidAutofillProviderTest, OnAskForValuesToFillFindsCorrectFieldId) {
base::HistogramTester histogram_tester;
FormData form =
CreateFormDataForFrame(CreateTestLoginForm(), main_frame_token());
android_autofill_manager().OnFormsSeen({form}, /*removed_forms=*/{});
android_autofill_manager().SimulateOnAskForValuesToFill(form,
form.fields()[0]);
EXPECT_EQ(test_api(autofill_provider()).last_focused_field_id(),
form.fields()[0].global_id());
android_autofill_manager().SimulateOnAskForValuesToFill(form,
form.fields()[1]);
EXPECT_EQ(test_api(autofill_provider()).last_focused_field_id(),
form.fields()[1].global_id());
}
// Tests that Java is informed about visibility changes of form fields connected
// to the current Autofill session if they are detected in focus change events.
TEST_F(AndroidAutofillProviderTest, NotifyAboutVisibilityChangeOnFocus) {
FormData form = CreateFormDataForFrame(
CreateTestPersonalInformationFormData(), main_frame_token());
// For Android Autofill, focusability is the same as visibility.
test_api(form).field(0).set_is_focusable(false);
test_api(form).field(2).set_is_focusable(false);
// Start an Autofill session.
android_autofill_manager().SimulateOnAskForValuesToFill(form,
form.fields()[1]);
test_api(form).field(0).set_is_focusable(true);
test_api(form).field(2).set_is_focusable(true);
EXPECT_CALL(provider_bridge(), OnFormFieldVisibilitiesDidChange(
/*indices=*/UnorderedElementsAre(0, 2)));
EXPECT_CALL(provider_bridge(),
OnFocusChanged(Optional(EqualsFieldInfo(/*index=*/0))));
android_autofill_manager().SimulateOnFocusOnFormField(form, form.fields()[0]);
}
// Tests that asking for values to fill for a different form than that of the
// current Autofill session results in a restart of the session.
TEST_F(AndroidAutofillProviderTest, OnAskForValuesToFillOnOtherForm) {
base::HistogramTester histogram_tester;
FormData form1 = CreateFormDataForFrame(
CreateTestPersonalInformationFormData(), main_frame_token());
FormData form2 = CreateFormDataForFrame(
CreateTestCreditCardFormData(/*is_https=*/true, /*use_month_type=*/true),
main_frame_token());
android_autofill_manager().OnFormsSeen({form1, form2}, /*removed_forms=*/{});
MockFunction<void()> check;
{
InSequence s;
EXPECT_CALL(provider_bridge(),
StartAutofillSession(EqualsFormData(form1),
EqualsFieldInfo(/*index=*/1),
/*has_server_predictions=*/false));
EXPECT_CALL(check, Call);
EXPECT_CALL(provider_bridge(),
StartAutofillSession(EqualsFormData(form2),
EqualsFieldInfo(/*index=*/0),
/*has_server_predictions=*/false));
EXPECT_CALL(check, Call);
}
android_autofill_manager().SimulateOnAskForValuesToFill(form1,
form1.fields()[1]);
check.Call();
android_autofill_manager().SimulateOnAskForValuesToFill(form2,
form2.fields()[0]);
check.Call();
}
// Tests that asking for values to fill on the same form as that of the current
// Autofill session results in a restart of the session if the form has changed.
TEST_F(AndroidAutofillProviderTest, OnAskForValuesToFillOnChangedForm) {
base::HistogramTester histogram_tester;
FormData form = CreateFormDataForFrame(
CreateTestPersonalInformationFormData(), main_frame_token());
form.set_name_attribute(u"old_name");
FormData form_changed = form;
form_changed.set_name_attribute(u"changed_name");
android_autofill_manager().OnFormsSeen({form}, /*removed_forms=*/{});
MockFunction<void()> check;
{
InSequence s;
EXPECT_CALL(
provider_bridge(),
StartAutofillSession(EqualsFormData(form), EqualsFieldInfo(/*index=*/1),
/*has_server_predictions=*/false));
EXPECT_CALL(check, Call);
EXPECT_CALL(provider_bridge(),
StartAutofillSession(EqualsFormData(form_changed),
EqualsFieldInfo(/*index=*/1),
/*has_server_predictions=*/false));
EXPECT_CALL(check, Call);
}
android_autofill_manager().SimulateOnAskForValuesToFill(form,
form.fields()[1]);
check.Call();
android_autofill_manager().OnFormsSeen({form_changed}, /*removed_forms=*/{});
android_autofill_manager().SimulateOnAskForValuesToFill(
form_changed, form_changed.fields()[1]);
check.Call();
}
// Tests that asking for values to fill on the same form as that of the current
// Autofill session does not result in a restart of the session if the form
// has not changed.
TEST_F(AndroidAutofillProviderTest, OnAskForValuesToFillOnSameForm) {
base::HistogramTester histogram_tester;
FormData form = CreateFormDataForFrame(
CreateTestPersonalInformationFormData(), main_frame_token());
android_autofill_manager().OnFormsSeen({form}, /*removed_forms=*/{});
MockFunction<void()> check;
{
InSequence s;
EXPECT_CALL(
provider_bridge(),
StartAutofillSession(EqualsFormData(form), EqualsFieldInfo(/*index=*/1),
/*has_server_predictions=*/false));
EXPECT_CALL(check, Call);
}
android_autofill_manager().SimulateOnAskForValuesToFill(form,
form.fields()[1]);
check.Call();
android_autofill_manager().SimulateOnAskForValuesToFill(form,
form.fields()[0]);
}
// Tests that value changes in the form of the Autofill session are propagated
// to Java and to the state that `AndroidAutofillProvider` keeps.
TEST_F(AndroidAutofillProviderTest, OnTextFieldDidChange) {
FormData form = CreateFormDataForFrame(
CreateTestPersonalInformationFormData(), main_frame_token());
android_autofill_manager().OnFormsSeen({form}, /*removed_forms=*/{});
// Start Autofill session.
android_autofill_manager().SimulateOnAskForValuesToFill(form,
form.fields()[1]);
// Simulate a value change.
EXPECT_CALL(provider_bridge(),
OnFormFieldDidChange(EqualsFieldInfo(/*index=*/1)));
test_api(form).field(1).set_value(form.fields()[1].value() + u"x");
android_autofill_manager().SimulateOnTextFieldDidChange(form,
form.fields()[1]);
// The `FormDataAndroid` object owned by the provider is also updated.
ASSERT_TRUE(test_api(autofill_provider()).form());
EXPECT_EQ(test_api(autofill_provider()).form()->form().fields()[1].value(),
form.fields()[1].value());
}
// Tests that value changes in a form that is not part of the current Autofill
// session are ignored.
TEST_F(AndroidAutofillProviderTest, OnTextFieldDidChangeInUnrelatedForm) {
FormData form1 = CreateFormDataForFrame(
CreateTestPersonalInformationFormData(), main_frame_token());
FormData form2 = CreateFormDataForFrame(
CreateTestCreditCardFormData(/*is_https=*/true, /*use_month_type=*/true),
main_frame_token());
android_autofill_manager().OnFormsSeen({form1, form2}, /*removed_forms=*/{});
// Start the Autofill session.
android_autofill_manager().SimulateOnAskForValuesToFill(form1,
form1.fields()[1]);
// Simulate a value change in a different form.
EXPECT_CALL(provider_bridge(), OnFormFieldDidChange).Times(0);
test_api(form2).field(1).set_value(form2.fields()[1].value() + u"x");
android_autofill_manager().SimulateOnTextFieldDidChange(form2,
form2.fields()[1]);
}
// Tests that scrolling events in the form of the Autofill session are
// propagated to Java.
TEST_F(AndroidAutofillProviderTest, OnTextFieldDidScroll) {
FormData form = CreateFormDataForFrame(
CreateTestPersonalInformationFormData(), main_frame_token());
android_autofill_manager().OnFormsSeen({form}, /*removed_forms=*/{});
// Start the Autofill session.
android_autofill_manager().SimulateOnAskForValuesToFill(form,
form.fields()[2]);
// Simulate scrolling.
EXPECT_CALL(provider_bridge(),
OnTextFieldDidScroll(EqualsFieldInfo(/*index=*/2)));
android_autofill_manager().SimulateOnTextFieldDidScroll(form,
form.fields()[2]);
}
// Tests that scrolling envets in a form that is not part of the current
// Autofill session are ignored.
TEST_F(AndroidAutofillProviderTest, OnTextFieldDidScrollInUnrelatedForm) {
FormData form1 = CreateFormDataForFrame(
CreateTestPersonalInformationFormData(), main_frame_token());
FormData form2 = CreateFormDataForFrame(
CreateTestCreditCardFormData(/*is_https=*/true, /*use_month_type=*/true),
main_frame_token());
android_autofill_manager().OnFormsSeen({form1, form2}, /*removed_forms=*/{});
// Start the Autofill session.
android_autofill_manager().SimulateOnAskForValuesToFill(form1,
form1.fields()[1]);
// Simulate a scroll event in a different form.
EXPECT_CALL(provider_bridge(), OnFormFieldDidChange).Times(0);
android_autofill_manager().SimulateOnTextFieldDidScroll(form2,
form2.fields()[1]);
}
// Tests that a form submission of an ongoing Autofill session is propagated to
// Java if `known_success` is true.
TEST_F(AndroidAutofillProviderTest, OnFormSubmittedWithKnownSuccess) {
FormData form = CreateFormDataForFrame(
CreateTestPersonalInformationFormData(), main_frame_token());
android_autofill_manager().OnFormsSeen({form}, /*removed_forms=*/{});
// Start an Autofill session.
android_autofill_manager().SimulateOnAskForValuesToFill(form,
form.fields()[0]);
EXPECT_CALL(provider_bridge(),
OnFormSubmitted(mojom::SubmissionSource::FORM_SUBMISSION));
android_autofill_manager().SimulateOnFormSubmitted(
form, /*known_success=*/true, mojom::SubmissionSource::FORM_SUBMISSION);
}
// Tests that a form submission of an ongoing Autofill session with source
// DOM_MUTATION_AFTER_AUTOFILL is propagated to Java for password forms.
TEST_F(AndroidAutofillProviderTest,
DomMutationAfterAutofillFormSubmission_PasswordForm) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitWithFeatures(
/*enabled_features=*/
{features::kAutofillUnifyAndFixFormTracking,
features::kAutofillAcceptDomMutationAfterAutofillSubmission},
/*disabled_features=*/{});
FormData form =
CreateFormDataForFrame(CreateTestPasswordFormData(), main_frame_token());
android_autofill_manager().OnFormsSeen({form}, /*removed_forms=*/{});
// Start an Autofill session.
android_autofill_manager().SimulateOnAskForValuesToFill(form,
form.fields()[0]);
EXPECT_CALL(
provider_bridge(),
OnFormSubmitted(mojom::SubmissionSource::DOM_MUTATION_AFTER_AUTOFILL))
.Times(1);
android_autofill_manager().SimulateOnFormSubmitted(
form, /*known_success=*/true,
mojom::SubmissionSource::DOM_MUTATION_AFTER_AUTOFILL);
}
// Tests that a form submission of an ongoing Autofill session with source
// DOM_MUTATION_AFTER_AUTOFILL is not propagated to Java for non-password forms.
TEST_F(AndroidAutofillProviderTest,
DomMutationAfterAutofillFormSubmission_NonPasswordForm) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitWithFeatures(
/*enabled_features=*/
{features::kAutofillUnifyAndFixFormTracking,
features::kAutofillAcceptDomMutationAfterAutofillSubmission},
/*disabled_features=*/{});
FormData form = CreateFormDataForFrame(
CreateTestPersonalInformationFormData(), main_frame_token());
android_autofill_manager().OnFormsSeen({form}, /*removed_forms=*/{});
// Start an Autofill session.
android_autofill_manager().SimulateOnAskForValuesToFill(form,
form.fields()[0]);
EXPECT_CALL(
provider_bridge(),
OnFormSubmitted(mojom::SubmissionSource::DOM_MUTATION_AFTER_AUTOFILL))
.Times(0);
android_autofill_manager().SimulateOnFormSubmitted(
form, /*known_success=*/true,
mojom::SubmissionSource::DOM_MUTATION_AFTER_AUTOFILL);
}
// Tests that a form submission of an ongoing Autofill session is propagated to
// Java when the `AutofillManager` of the tab is reset, even if the form
// submission was not known to be a success.
TEST_F(AndroidAutofillProviderTest, FormSubmissionHappensOnReset) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndDisableFeature(
features::kAndroidAutofillDirectFormSubmission);
FormData form = CreateFormDataForFrame(
CreateTestPersonalInformationFormData(), main_frame_token());
android_autofill_manager().OnFormsSeen({form}, /*removed_forms=*/{});
// Start an Autofill session.
android_autofill_manager().SimulateOnAskForValuesToFill(form,
form.fields()[0]);
EXPECT_CALL(provider_bridge(), OnFormSubmitted).Times(0);
android_autofill_manager().SimulateOnFormSubmitted(
form, /*known_success=*/false,
mojom::SubmissionSource::PROBABLY_FORM_SUBMITTED);
Mock::VerifyAndClearExpectations(&provider_bridge());
EXPECT_CALL(
provider_bridge(),
OnFormSubmitted(mojom::SubmissionSource::PROBABLY_FORM_SUBMITTED));
test_api(android_autofill_manager()).Reset();
}
// Tests that a form submission of an ongoing Autofill session is propagated to
// Java directly on submission, even if the form submission was not known to be
// a success.
TEST_F(AndroidAutofillProviderTest, FormSubmissionHappensDirectly) {
base::test::ScopedFeatureList scoped_feature_list{
features::kAndroidAutofillDirectFormSubmission};
FormData form = CreateFormDataForFrame(
CreateTestPersonalInformationFormData(), main_frame_token());
android_autofill_manager().OnFormsSeen({form}, /*removed_forms=*/{});
// Start an Autofill session.
android_autofill_manager().SimulateOnAskForValuesToFill(form,
form.fields()[0]);
EXPECT_CALL(
provider_bridge(),
OnFormSubmitted(mojom::SubmissionSource::PROBABLY_FORM_SUBMITTED));
android_autofill_manager().SimulateOnFormSubmitted(
form, /*known_success=*/false,
mojom::SubmissionSource::PROBABLY_FORM_SUBMITTED);
}
// Tests that a form submission of an ongoing Autofill session is propagated to
// Java when the `AutofillManager` of the tab is destroyed. Put differently,
// it tests that the `AutofillManager` is reset on destruction.
TEST_F(AndroidAutofillProviderTest, FormSubmissionHappensOnFrameDestruction) {
content::RenderFrameHost* child_rfh =
content::RenderFrameHostTester::For(main_frame())
->AppendChild(std::string("child"));
child_rfh = content::NavigationSimulator::NavigateAndCommitFromDocument(
GURL("https://foo.bar"), child_rfh);
// Force creation of driver.
ASSERT_TRUE(ContentAutofillDriver::GetForRenderFrameHost(child_rfh));
FormData form = CreateFormDataForFrame(
CreateTestPersonalInformationFormData(),
LocalFrameToken(child_rfh->GetFrameToken().value()));
android_autofill_manager(child_rfh).OnFormsSeen({form},
/*removed_forms=*/{});
// Start an Autofill session.
android_autofill_manager(child_rfh).SimulateOnAskForValuesToFill(
form, form.fields()[0]);
EXPECT_CALL(provider_bridge(), OnFormSubmitted).Times(0);
android_autofill_manager(child_rfh).SimulateOnFormSubmitted(
form, /*known_success=*/false, mojom::SubmissionSource::XHR_SUCCEEDED);
Mock::VerifyAndClearExpectations(&provider_bridge());
EXPECT_CALL(provider_bridge(),
OnFormSubmitted(mojom::SubmissionSource::XHR_SUCCEEDED));
content::RenderFrameHostTester::For(std::exchange(child_rfh, nullptr))
->Detach();
}
// Tests the predictions from `password_manager::FormDataParser` are used to
// overwrite all type predictions of the respective `FormDataAndroidField`s.
TEST_F(AndroidAutofillProviderTest,
UsePasswordManagerOverridesInPrefillRequest) {
if (base::android::BuildInfo::GetInstance()->sdk_int() <
base::android::SdkVersion::SDK_VERSION_U) {
GTEST_SKIP();
}
FormData form =
CreateFormDataForFrame(CreateTestLoginForm(), main_frame_token());
android_autofill_manager().OnFormsSeen({form}, /*removed_forms=*/{});
ASSERT_TRUE(android_autofill_manager().FindCachedFormById(form.global_id()));
auto has_field_type = [](FieldType field_type) {
return Pointee(Property(&FormFieldDataAndroid::field_types,
Eq(AutofillType(field_type))));
};
EXPECT_CALL(provider_bridge(),
SendPrefillRequest(EqualsFormDataWithFields(
form, ElementsAre(has_field_type(FieldType::USERNAME),
has_field_type(FieldType::PASSWORD)))));
android_autofill_manager().SimulatePropagateAutofillPredictions(
form.global_id());
}
// Tests that the session id used in a prefill request is also used for starting
// the Autofill session even if the forms are not similar as long as their form
// signatures (and predictions) match.
TEST_F(AndroidAutofillProviderTest,
SessionIdIsReusedForCachedFormsAsLongAsPredictionsAgree) {
if (base::android::BuildInfo::GetInstance()->sdk_int() <
base::android::SdkVersion::SDK_VERSION_U) {
GTEST_SKIP();
}
FormData form =
CreateFormDataForFrame(CreateTestLoginForm(), main_frame_token());
android_autofill_manager().OnFormsSeen({form}, /*removed_forms=*/{});
ASSERT_TRUE(android_autofill_manager().FindCachedFormById(form.global_id()));
FormData changed_form = form;
changed_form.set_name_attribute(changed_form.name_attribute() +
u"some-suffix");
SessionId cache_session_id = SessionId(0);
MockFunction<void()> check;
{
InSequence s;
EXPECT_CALL(provider_bridge(), SendPrefillRequest(EqualsFormData(form)))
.WillOnce(SaveSessionId(&cache_session_id));
EXPECT_CALL(check, Call);
// Pass a lambda to perform the check because passing `cache_session_id`
// would match against the current value of cache_session_id (0).
EXPECT_CALL(provider_bridge(),
StartAutofillSession(
EqualsFormDataWithSessionId(
changed_form, Truly([&cache_session_id](SessionId id) {
return id == cache_session_id;
})),
EqualsFieldInfo(/*index=*/0),
/*has_server_predictions=*/true));
}
// Upon receiving server predictions a prefill request should be sent.
android_autofill_manager().SimulatePropagateAutofillPredictions(
form.global_id());
check.Call();
// The changed form has the same signature as the cached form - therefore it
// should have the session id of the cached form.
ASSERT_EQ(CalculateFormSignature(form), CalculateFormSignature(changed_form));
android_autofill_manager().OnFormsSeen({changed_form}, /*removed_forms=*/{});
android_autofill_manager().SimulateOnAskForValuesToFill(
changed_form, changed_form.fields().front());
}
// Tests that new document navigation (manager reset) cancels the ongoing
// autofill session
TEST_F(AndroidAutofillProviderTest, CancelSessionOnNavigation) {
FormData form = CreateFormDataForFrame(
CreateTestPersonalInformationFormData(), main_frame_token());
android_autofill_manager().OnFormsSeen({form}, /*removed_forms=*/{});
EXPECT_CALL(
provider_bridge(),
StartAutofillSession(EqualsFormData(form), EqualsFieldInfo(/*index=*/0),
/*has_server_predictions=*/false));
android_autofill_manager().SimulateOnAskForValuesToFill(
form, form.fields().front());
EXPECT_CALL(provider_bridge(), CancelSession());
test_api(android_autofill_manager()).Reset();
}
class AndroidAutofillProviderWithCredManTest
: public AndroidAutofillProviderTestBase {
public:
void SetUp() override {
AndroidAutofillProviderTestBase::SetUp();
autofill_provider().MaybeInitKeyboardSuppressor();
// Navigation creates the AndroidAutofillManager for the main frame.
NavigateAndCommit(GURL("about:blank"));
FocusWebContentsOnMainFrame();
// Load a form with webuthn-annotated username and regular password fields.
test_webauthn_form_ = CreateFormDataForFrame(
CreateTestWebAuthnPasswordFormData(), main_frame_token());
InitializeWebAuthnFactoryWithMock();
}
void InitializeWebAuthnFactoryWithMock() {
auto mock_cred_man_delegate =
std::make_unique<NiceMock<webauthn::MockWebAuthnCredManDelegate>>();
webauthn::test_api(web_authn_delegate_factory())
.EmplaceDelegateForFrame(main_frame(),
std::move(mock_cred_man_delegate));
ON_CALL(cred_man_delegate(), HasPasskeys())
.WillByDefault(
Return(webauthn::WebAuthnCredManDelegate::State::kHasPasskeys));
}
webauthn::WebAuthnCredManDelegateFactory* web_authn_delegate_factory() {
return webauthn::WebAuthnCredManDelegateFactory::GetFactory(web_contents());
}
const FormData& test_form() const { return test_webauthn_form_; }
const FormFieldData& webauthn_email_field() const {
return test_form().fields()[0];
}
const FormFieldData& non_webauthn_password_field() const {
return test_form().fields()[1];
}
void FocusFormField(const FormFieldData& field) {
keyboard_suppressor().OnBeforeAskForValuesToFill(
android_autofill_manager(), test_webauthn_form_.global_id(),
field.global_id(), test_webauthn_form_);
android_autofill_manager().SimulateOnAskForValuesToFill(test_webauthn_form_,
field);
android_autofill_manager().SimulateOnFocusOnFormField(test_webauthn_form_,
field);
keyboard_suppressor().OnAfterAskForValuesToFill(
android_autofill_manager(), test_webauthn_form_.global_id(),
field.global_id());
}
webauthn::MockWebAuthnCredManDelegate& cred_man_delegate() {
return *static_cast<webauthn::MockWebAuthnCredManDelegate*>(
web_authn_delegate_factory()->GetRequestDelegate(main_frame()));
}
TouchToFillKeyboardSuppressor& keyboard_suppressor() {
return test_api(autofill_provider()).keyboard_suppressor();
}
private:
FormData test_webauthn_form_;
base::test::ScopedFeatureList scoped_feature_list_{
features::kAutofillVirtualViewStructureAndroid};
};
TEST_F(AndroidAutofillProviderWithCredManTest, CallsCredManOnlyOnce) {
EXPECT_CALL(cred_man_delegate(),
TriggerCredManUi(Eq(
webauthn::WebAuthnCredManDelegate::RequestPasswords(false))));
FocusFormField(webauthn_email_field());
// No further call!
FocusFormField(non_webauthn_password_field());
FocusFormField(webauthn_email_field());
}
TEST_F(AndroidAutofillProviderWithCredManTest,
CallsCredManAgainAfterNavigation) {
EXPECT_CALL(cred_man_delegate(),
TriggerCredManUi(Eq(
webauthn::WebAuthnCredManDelegate::RequestPasswords(false))));
FocusFormField(webauthn_email_field());
// Navigate which allows CredMan to be shown again.
NavigateAndCommit(GURL("about:to-do-stuff"));
EXPECT_CALL(cred_man_delegate(),
TriggerCredManUi(Eq(
webauthn::WebAuthnCredManDelegate::RequestPasswords(false))));
FocusFormField(webauthn_email_field());
}
TEST_F(AndroidAutofillProviderWithCredManTest,
SuppressesKeyboardForCredManCall) {
EXPECT_CALL(cred_man_delegate(),
TriggerCredManUi(Eq(
webauthn::WebAuthnCredManDelegate::RequestPasswords(false))));
base::RepeatingCallback<void(bool)> completed_callback;
EXPECT_CALL(cred_man_delegate(), SetRequestCompletionCallback)
.WillOnce(SaveArg<0>(&completed_callback));
FocusFormField(webauthn_email_field());
// Keyboard is suppressed while CredMan is showing.
EXPECT_TRUE(keyboard_suppressor().is_suppressing());
Mock::VerifyAndClearExpectations(&cred_man_delegate());
// After resetting CredMan, the keyboard suppressing stopped.
completed_callback.Run(/*success=*/true);
EXPECT_FALSE(keyboard_suppressor().is_suppressing());
}
TEST_F(AndroidAutofillProviderWithCredManTest, NoCredManWithoutAnnotation) {
EXPECT_CALL(cred_man_delegate(), TriggerCredManUi).Times(0);
FocusFormField(non_webauthn_password_field());
EXPECT_FALSE(keyboard_suppressor().is_suppressing());
}
TEST_F(AndroidAutofillProviderWithCredManTest, SkipsCredManCallBeforeReady) {
ON_CALL(cred_man_delegate(), HasPasskeys())
.WillByDefault(
Return(webauthn::WebAuthnCredManDelegate::State::kNotReady));
EXPECT_CALL(cred_man_delegate(), TriggerCredManUi).Times(0);
FocusFormField(webauthn_email_field());
}
using AndroidAutofillProviderPrefillRequestTest = AndroidAutofillProviderTest;
// Tests that we can send another prefill request after navigation.
TEST_F(AndroidAutofillProviderPrefillRequestTest,
MultiplePrefillRequestsOnNavigation) {
if (base::android::BuildInfo::GetInstance()->sdk_int() <
base::android::SdkVersion::SDK_VERSION_U) {
GTEST_SKIP();
}
FormData form = test::GetFormData(
{.fields = {{.autocomplete_attribute = "username"},
{.autocomplete_attribute = "current-password",
.form_control_type = FormControlType::kInputPassword}}});
EXPECT_CALL(provider_bridge(), SendPrefillRequest).Times(2);
android_autofill_manager().OnFormsSeen({form}, /*removed_forms=*/{});
android_autofill_manager().SimulatePropagateAutofillPredictions(
form.global_id());
android_autofill_manager().SimulateOnAskForValuesToFill(
form, form.fields().front());
test_api(android_autofill_manager()).Reset();
android_autofill_manager().OnFormsSeen({form}, /*removed_forms=*/{});
android_autofill_manager().SimulatePropagateAutofillPredictions(
form.global_id());
}
// Tests that a metric is emitted if prefill requests are supported and there
// was not enough time to send a prefill request.
TEST_F(AndroidAutofillProviderPrefillRequestTest,
OnAskForValuesToFillRecordsPrefillRequestStateUmaMetric) {
if (base::android::BuildInfo::GetInstance()->sdk_int() <
base::android::SdkVersion::SDK_VERSION_U) {
GTEST_SKIP();
}
base::HistogramTester histogram_tester;
FormData form =
CreateFormDataForFrame(CreateTestLoginForm(), main_frame_token());
android_autofill_manager().OnFormsSeen({form}, /*removed_forms=*/{});
android_autofill_manager().SimulateOnAskForValuesToFill(
form, form.fields().front());
histogram_tester.ExpectUniqueSample(
AndroidAutofillProvider::kPrefillRequestStateUma,
PrefillRequestState::kRequestNotSentNoTime, 1);
}
// Tests that no prefill requests are sent on Android versions prior to U even
// if all other requirements are satisfied.
TEST_F(AndroidAutofillProviderPrefillRequestTest,
NoPrefillRequestOnVersionsPriorToU) {
// This test only makes sense on Android versions smaller than U.
if (base::android::BuildInfo::GetInstance()->sdk_int() >=
base::android::SdkVersion::SDK_VERSION_U) {
GTEST_SKIP();
}
FormData form =
CreateFormDataForFrame(CreateTestLoginForm(), main_frame_token());
android_autofill_manager().OnFormsSeen({form}, /*removed_forms=*/{});
ASSERT_TRUE(android_autofill_manager().FindCachedFormById(form.global_id()));
// No prefill request is ever sent.
EXPECT_CALL(provider_bridge(), SendPrefillRequest).Times(0);
android_autofill_manager().SimulatePropagateAutofillPredictions(
form.global_id());
}
// Tests that a prefill request is sent if all requirements for it are
// satisfied.
TEST_F(AndroidAutofillProviderPrefillRequestTest, SendPrefillRequest) {
if (base::android::BuildInfo::GetInstance()->sdk_int() <
base::android::SdkVersion::SDK_VERSION_U) {
GTEST_SKIP();
}
FormData form =
CreateFormDataForFrame(CreateTestLoginForm(), main_frame_token());
android_autofill_manager().OnFormsSeen({form}, /*removed_forms=*/{});
ASSERT_TRUE(android_autofill_manager().FindCachedFormById(form.global_id()));
// Upon receiving server predictions a prefill request should be sent.
EXPECT_CALL(provider_bridge(), SendPrefillRequest(EqualsFormData(form)));
android_autofill_manager().SimulatePropagateAutofillPredictions(
form.global_id());
}
// Tests that no prefill request is sent if there is already an ongoing Autofill
// session.
TEST_F(AndroidAutofillProviderPrefillRequestTest,
NoPrefillRequestIfOngoingSession) {
if (base::android::BuildInfo::GetInstance()->sdk_int() <
base::android::SdkVersion::SDK_VERSION_U) {
GTEST_SKIP();
}
base::HistogramTester histogram_tester;
FormData login_form1 =
CreateFormDataForFrame(CreateTestLoginForm(), main_frame_token());
android_autofill_manager().OnFormsSeen({login_form1}, /*removed_forms=*/{});
EXPECT_CALL(provider_bridge(), StartAutofillSession);
android_autofill_manager().SimulateOnAskForValuesToFill(
login_form1, login_form1.fields().front());
histogram_tester.ExpectUniqueSample(
AndroidAutofillProvider::kPrefillRequestStateUma,
PrefillRequestState::kRequestNotSentNoTime, 1);
FormData login_form2 =
CreateFormDataForFrame(CreateTestLoginForm(), main_frame_token());
android_autofill_manager().OnFormsSeen({login_form2}, /*removed_forms=*/{});
ASSERT_TRUE(
android_autofill_manager().FindCachedFormById(login_form2.global_id()));
// No prefill request is ever sent.
EXPECT_CALL(provider_bridge(), SendPrefillRequest).Times(0);
android_autofill_manager().SimulatePropagateAutofillPredictions(
login_form2.global_id());
}
// Tests that no prefill request is sent if there has already been another
// prefill request.
TEST_F(AndroidAutofillProviderPrefillRequestTest, NoSecondPrefillRequest) {
if (base::android::BuildInfo::GetInstance()->sdk_int() <
base::android::SdkVersion::SDK_VERSION_U) {
GTEST_SKIP();
}
base::HistogramTester histogram_tester;
FormData login_form1 =
CreateFormDataForFrame(CreateTestLoginForm(), main_frame_token());
android_autofill_manager().OnFormsSeen({login_form1}, /*removed_forms=*/{});
ASSERT_TRUE(
android_autofill_manager().FindCachedFormById(login_form1.global_id()));
FormData login_form2 =
CreateFormDataForFrame(CreateTestLoginForm(), main_frame_token());
android_autofill_manager().OnFormsSeen({login_form2}, /*removed_forms=*/{});
ASSERT_TRUE(
android_autofill_manager().FindCachedFormById(login_form2.global_id()));
// The helper method should generate different ids every time it is called.
ASSERT_FALSE(FormData::DeepEqual(login_form1, login_form2));
EXPECT_CALL(provider_bridge(),
SendPrefillRequest(EqualsFormData(login_form1)));
android_autofill_manager().SimulatePropagateAutofillPredictions(
login_form1.global_id());
Mock::VerifyAndClearExpectations(&provider_bridge());
EXPECT_CALL(provider_bridge(), SendPrefillRequest).Times(0);
android_autofill_manager().SimulatePropagateAutofillPredictions(
login_form2.global_id());
android_autofill_manager().SimulateOnAskForValuesToFill(
login_form2, login_form2.fields().front());
histogram_tester.ExpectUniqueSample(
AndroidAutofillProvider::kPrefillRequestStateUma,
PrefillRequestState::kRequestNotSentMaxNumberReached, 1);
}
// Tests that the session id used in a prefill request is also used for starting
// the Autofill session for that form.
TEST_F(AndroidAutofillProviderPrefillRequestTest,
SessionIdIsReusedForCachedForms) {
if (base::android::BuildInfo::GetInstance()->sdk_int() <
base::android::SdkVersion::SDK_VERSION_U) {
GTEST_SKIP();
}
FormData form =
CreateFormDataForFrame(CreateTestLoginForm(), main_frame_token());
android_autofill_manager().OnFormsSeen({form}, /*removed_forms=*/{});
ASSERT_TRUE(android_autofill_manager().FindCachedFormById(form.global_id()));
// Upon receiving server predictions a prefill request should be sent.
SessionId cache_session_id = SessionId(0);
EXPECT_CALL(provider_bridge(), SendPrefillRequest(EqualsFormData(form)))
.WillOnce(SaveSessionId(&cache_session_id));
android_autofill_manager().SimulatePropagateAutofillPredictions(
form.global_id());
Mock::VerifyAndClearExpectations(&provider_bridge());
EXPECT_CALL(
provider_bridge(),
StartAutofillSession(EqualsFormDataWithSessionId(form, cache_session_id),
EqualsFieldInfo(/*index=*/0),
/*has_server_predictions=*/true));
android_autofill_manager().SimulateOnAskForValuesToFill(
form, form.fields().front());
}
// Tests that the session id used in a prefill request is not reused when
// starting a session on a form with the same id, but changed field content.
TEST_F(AndroidAutofillProviderPrefillRequestTest,
SessionIdIsNotReusedForCachedFormsIfContentHasChanged) {
if (base::android::BuildInfo::GetInstance()->sdk_int() <
base::android::SdkVersion::SDK_VERSION_U) {
GTEST_SKIP();
}
base::HistogramTester histogram_tester;
FormData form =
CreateFormDataForFrame(CreateTestLoginForm(), main_frame_token());
android_autofill_manager().OnFormsSeen({form}, /*removed_forms=*/{});
// Upon receiving server predictions a prefill request should be sent.
SessionId cache_session_id = SessionId(0);
EXPECT_CALL(provider_bridge(), SendPrefillRequest(EqualsFormData(form)))
.WillOnce(SaveSessionId(&cache_session_id));
android_autofill_manager().SimulatePropagateAutofillPredictions(
form.global_id());
Mock::VerifyAndClearExpectations(&provider_bridge());
FormData changed_form = form;
test_api(changed_form).Remove(-1);
android_autofill_manager().OnFormsSeen({changed_form},
/*removed_forms=*/{});
SessionId autofill_session_id = SessionId(0);
EXPECT_CALL(provider_bridge(),
StartAutofillSession(EqualsFormData(changed_form),
EqualsFieldInfo(/*index=*/0),
/*has_server_predictions=*/true))
.WillOnce(WithArg<0>(SaveSessionId(&autofill_session_id)));
android_autofill_manager().SimulateOnAskForValuesToFill(
changed_form, changed_form.fields().front());
Mock::VerifyAndClearExpectations(&provider_bridge());
// A new session id is used to start the Autofill session.
EXPECT_NE(cache_session_id, autofill_session_id);
histogram_tester.ExpectUniqueSample(
AndroidAutofillProvider::kPrefillRequestStateUma,
PrefillRequestState::kRequestSentFormChanged, 1);
}
// Tests that the session id used in a prefill request is only used once to
// start an Autofill session. If the user then focuses on a different form
// before returning to the (formerly) cached form, a new session is started.
TEST_F(AndroidAutofillProviderPrefillRequestTest,
SessionIdIsNotReusedMultipleAutofillSessions) {
if (base::android::BuildInfo::GetInstance()->sdk_int() <
base::android::SdkVersion::SDK_VERSION_U) {
GTEST_SKIP();
}
FormData pw_form =
CreateFormDataForFrame(CreateTestLoginForm(), main_frame_token());
FormData pi_form = CreateFormDataForFrame(
CreateTestPersonalInformationFormData(), main_frame_token());
android_autofill_manager().OnFormsSeen({pw_form, pi_form},
/*removed_forms=*/{});
// Upon receiving server predictions a prefill request should be sent.
SessionId cache_session_id = SessionId(0);
EXPECT_CALL(provider_bridge(), SendPrefillRequest(EqualsFormData(pw_form)))
.WillOnce(SaveSessionId(&cache_session_id));
android_autofill_manager().SimulatePropagateAutofillPredictions(
pw_form.global_id());
Mock::VerifyAndClearExpectations(&provider_bridge());
EXPECT_CALL(provider_bridge(),
StartAutofillSession(
EqualsFormDataWithSessionId(pw_form, cache_session_id),
EqualsFieldInfo(/*index=*/0),
/*has_server_predictions=*/true));
android_autofill_manager().SimulateOnAskForValuesToFill(
pw_form, pw_form.fields().front());
Mock::VerifyAndClearExpectations(&provider_bridge());
// Now focus on a different form.
SessionId pi_form_session_id = SessionId(0);
EXPECT_CALL(provider_bridge(),
StartAutofillSession(EqualsFormData(pi_form),
EqualsFieldInfo(/*index=*/0),
/*has_server_predictions=*/false))
.WillOnce(WithArg<0>(SaveSessionId(&pi_form_session_id)));
android_autofill_manager().SimulateOnAskForValuesToFill(
pi_form, pi_form.fields().front());
Mock::VerifyAndClearExpectations(&provider_bridge());
// Unrelated forms should have different session ids.
EXPECT_NE(cache_session_id, pi_form_session_id);
// Focus back on the original password form.
SessionId pw_form_second_session_id = SessionId(0);
EXPECT_CALL(provider_bridge(),
StartAutofillSession(EqualsFormData(pw_form),
EqualsFieldInfo(/*index=*/0),
/*has_server_predictions=*/true))
.WillOnce(WithArg<0>(SaveSessionId(&pw_form_second_session_id)));
android_autofill_manager().SimulateOnAskForValuesToFill(
pw_form, pw_form.fields().front());
Mock::VerifyAndClearExpectations(&provider_bridge());
// The session id used when focusing back should be different from both those
// before.
EXPECT_NE(cache_session_id, pw_form_second_session_id);
EXPECT_NE(pi_form_session_id, pw_form_second_session_id);
}
// Tests that the prefill request is sent for a Change Password form.
TEST_F(AndroidAutofillProviderPrefillRequestTest,
PrefillRequestSentForChangePasswordForm) {
if (base::android::BuildInfo::GetInstance()->sdk_int() <
base::android::SdkVersion::SDK_VERSION_U) {
GTEST_SKIP();
}
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(
features::kAndroidAutofillPrefillRequestsForChangePassword);
FormData form = CreateFormDataForFrame(CreateTestChangePasswordForm(),
main_frame_token());
android_autofill_manager().OnFormsSeen({form}, /*removed_forms=*/{});
ASSERT_TRUE(android_autofill_manager().FindCachedFormById(form.global_id()));
EXPECT_CALL(provider_bridge(), SendPrefillRequest(EqualsFormData(form)));
android_autofill_manager().SimulatePropagateAutofillPredictions(
form.global_id());
}
// Tests that starting an autofill session for a change password form works.
TEST_F(AndroidAutofillProviderPrefillRequestTest,
SessionStartForChangePasswordForm) {
if (base::android::BuildInfo::GetInstance()->sdk_int() <
base::android::SdkVersion::SDK_VERSION_U) {
GTEST_SKIP();
}
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(
features::kAndroidAutofillPrefillRequestsForChangePassword);
FormData form = CreateFormDataForFrame(CreateTestChangePasswordForm(),
main_frame_token());
android_autofill_manager().OnFormsSeen({form}, /*removed_forms=*/{});
ASSERT_TRUE(android_autofill_manager().FindCachedFormById(form.global_id()));
// Upon receiving server predictions a prefill request should be sent.
SessionId cache_session_id = SessionId(0);
EXPECT_CALL(provider_bridge(), SendPrefillRequest(EqualsFormData(form)))
.WillOnce(SaveSessionId(&cache_session_id));
android_autofill_manager().SimulatePropagateAutofillPredictions(
form.global_id());
Mock::VerifyAndClearExpectations(&provider_bridge());
EXPECT_CALL(
provider_bridge(),
StartAutofillSession(EqualsFormDataWithSessionId(form, cache_session_id),
EqualsFieldInfo(/*index=*/0),
/*has_server_predictions=*/true));
android_autofill_manager().SimulateOnAskForValuesToFill(
form, form.fields().front());
}
// Tests that metrics are emitted when the bottom sheet is shown.
TEST_F(AndroidAutofillProviderPrefillRequestTest,
PrefillRequestStateEmittedOnShowingBottomSheet) {
if (base::android::BuildInfo::GetInstance()->sdk_int() <
base::android::SdkVersion::SDK_VERSION_U) {
GTEST_SKIP();
}
base::HistogramTester histogram_tester;
FormData login_form =
CreateFormDataForFrame(CreateTestLoginForm(), main_frame_token());
android_autofill_manager().OnFormsSeen({login_form}, /*removed_forms=*/{});
android_autofill_manager().SimulatePropagateAutofillPredictions(
login_form.global_id());
EXPECT_CALL(provider_bridge(), StartAutofillSession);
android_autofill_manager().SimulateOnAskForValuesToFill(
login_form, login_form.fields().front());
// Simulate a successfully shown bottom sheet.
provider_bridge_delegate().OnShowBottomSheetResult(
/*is_shown=*/true, /*provided_autofill_structure=*/true);
histogram_tester.ExpectUniqueSample(
AndroidAutofillProvider::kPrefillRequestStateUma,
PrefillRequestState::kRequestSentStructureProvidedBottomSheetShown, 1);
}
// Tests that the correct metrics are emitted when the bottom sheet is not shown
// and no view structure was provided to the Android framework.
TEST_F(AndroidAutofillProviderPrefillRequestTest,
PrefillRequestStateEmittedOnNotShowingBottomSheetWithoutViewStructure) {
if (base::android::BuildInfo::GetInstance()->sdk_int() <
base::android::SdkVersion::SDK_VERSION_U) {
GTEST_SKIP();
}
base::HistogramTester histogram_tester;
FormData login_form =
CreateFormDataForFrame(CreateTestLoginForm(), main_frame_token());
android_autofill_manager().OnFormsSeen({login_form}, /*removed_forms=*/{});
android_autofill_manager().SimulatePropagateAutofillPredictions(
login_form.global_id());
android_autofill_manager().SimulateOnAskForValuesToFill(
login_form, login_form.fields().front());
// Simulate a successfully shown bottom sheet.
provider_bridge_delegate().OnShowBottomSheetResult(
/*is_shown=*/false, /*provided_autofill_structure=*/false);
histogram_tester.ExpectUniqueSample(
AndroidAutofillProvider::kPrefillRequestStateUma,
PrefillRequestState::kRequestSentStructureNotProvided, 1);
histogram_tester.ExpectTotalCount(
AndroidAutofillProvider::
kPrefillRequestBottomsheetNoViewStructureDelayUma,
1);
}
// Tests that the correct metrics are emitted when the bottom sheet is not shown
// and a view structure was provided to the Android framework.
TEST_F(AndroidAutofillProviderPrefillRequestTest,
PrefillRequestStateEmittedOnNotShowingBottomSheetWithViewStructure) {
if (base::android::BuildInfo::GetInstance()->sdk_int() <
base::android::SdkVersion::SDK_VERSION_U) {
GTEST_SKIP();
}
base::HistogramTester histogram_tester;
FormData login_form =
CreateFormDataForFrame(CreateTestLoginForm(), main_frame_token());
android_autofill_manager().OnFormsSeen({login_form}, /*removed_forms=*/{});
android_autofill_manager().SimulatePropagateAutofillPredictions(
login_form.global_id());
android_autofill_manager().SimulateOnAskForValuesToFill(
login_form, login_form.fields().front());
// Simulate a successfully shown bottom sheet.
provider_bridge_delegate().OnShowBottomSheetResult(
/*is_shown=*/false, /*provided_autofill_structure=*/true);
histogram_tester.ExpectUniqueSample(
AndroidAutofillProvider::kPrefillRequestStateUma,
PrefillRequestState::kRequestSentStructureProvidedBottomSheetNotShown, 1);
}
class AndroidAutofillProviderTestHidingLogic
: public AndroidAutofillProviderTest {
public:
void SetUp() override {
AndroidAutofillProviderTest::SetUp();
NavigateAndCommit(GURL("https://foo.com"));
sub_frame_ = content::RenderFrameHostTester::For(main_frame())
->AppendChild(std::string("child"));
sub_frame_ = NavigateAndCommitFrame(sub_frame_, GURL("https://bar.com"));
}
void TearDown() override {
sub_frame_ = nullptr;
AndroidAutofillProviderTest::TearDown();
}
void AskForValuesToFill(content::RenderFrameHost* rfh) {
FocusWebContentsOnFrame(rfh);
FormData form =
CreateFormDataForFrame(CreateTestPersonalInformationFormData(),
LocalFrameToken(rfh->GetFrameToken().value()));
android_autofill_manager(rfh).OnFormsSeen({form},
/*removed_forms=*/{});
// Start an Autofill session.
android_autofill_manager(rfh).SimulateOnAskForValuesToFill(
form, form.fields()[0]);
}
protected:
raw_ptr<content::RenderFrameHost> sub_frame_ = nullptr;
};
// Tests that if the popup is shown in the *main frame*, destruction of the
// *sub frame* does not hide the popup.
TEST_F(AndroidAutofillProviderTestHidingLogic,
KeepOpenInMainFrameOnSubFrameDestruction) {
AskForValuesToFill(main_frame());
EXPECT_CALL(provider_bridge(), HideDatalistPopup).Times(0);
content::RenderFrameHostTester::For(sub_frame_)->Detach();
// Verify and clear before TearDown() closes the popup.
Mock::VerifyAndClearExpectations(&provider_bridge());
}
// Tests that if the popup is shown in the *main frame*, a navigation in the
// *sub frame* does not hide the popup.
TEST_F(AndroidAutofillProviderTestHidingLogic,
KeepOpenInMainFrameOnSubFrameNavigation) {
AskForValuesToFill(main_frame());
EXPECT_CALL(provider_bridge(), HideDatalistPopup).Times(0);
NavigateAndCommitFrame(sub_frame_, GURL("https://bar.com/"));
// Verify and clear before TearDown() closes the popup.
Mock::VerifyAndClearExpectations(&provider_bridge());
}
// Tests that if the popup is shown in the *main frame*, destruction of the
// *main frame* resets the java instance which hides the popup.
TEST_F(AndroidAutofillProviderTestHidingLogic, HideInMainFrameOnDestruction) {
AskForValuesToFill(main_frame());
EXPECT_CALL(provider_bridge(), Reset);
// TearDown() destructs the main frame.
}
// Tests that if the popup is shown in the *sub frame*, destruction of the
// *sub frame* hides the popup.
TEST_F(AndroidAutofillProviderTestHidingLogic, HideInSubFrameOnDestruction) {
AskForValuesToFill(sub_frame_);
EXPECT_CALL(provider_bridge(), Reset);
NavigateAndCommitFrame(sub_frame_, GURL("https://bar.com/"));
// Verify and clear before TearDown() closes the popup.
Mock::VerifyAndClearExpectations(&provider_bridge());
}
// Tests that if the popup is shown in the *main frame*, a navigation in the
// *main frame* hides the popup.
TEST_F(AndroidAutofillProviderTestHidingLogic,
HideInMainFrameOnMainFrameNavigation) {
AskForValuesToFill(main_frame());
EXPECT_CALL(provider_bridge(), HideDatalistPopup).Times(AtLeast(1));
NavigateAndCommitFrame(main_frame(), GURL("https://bar.com/"));
}
// Tests that if the popup is shown in the *sub frame*, a navigation in the
// *sub frame* hides the popup.
//
// TODO(crbug.com/40283554): Disabled because AndroidAutofillProvider::Reset()
// resets AndroidAutofillProvider::field_rfh_ before RenderFrameDeleted(), which
// prevents OnPopupHidden().
TEST_F(AndroidAutofillProviderTestHidingLogic,
DISABLED_HideInSubFrameOnSubFrameNavigation) {
AskForValuesToFill(sub_frame_);
EXPECT_CALL(provider_bridge(), HideDatalistPopup).Times(AtLeast(1));
NavigateAndCommitFrame(sub_frame_, GURL("https://bar.com/"));
}
// Tests that if the popup is shown in the *sub frame*, a navigation in the
// *main frame* hides the popup.
TEST_F(AndroidAutofillProviderTestHidingLogic,
HideInSubFrameOnMainFrameNavigation) {
AskForValuesToFill(main_frame());
EXPECT_CALL(provider_bridge(), HideDatalistPopup).Times(AtLeast(1));
NavigateAndCommitFrame(main_frame(), GURL("https://bar.com/"));
}
// Tests that AndroidAutofillProvider::last_queried_field_rfh_id_ is updated
// when different frames are queried.
TEST_F(AndroidAutofillProviderTestHidingLogic,
FollowAskForValuesInDifferentFrames) {
AskForValuesToFill(main_frame());
AskForValuesToFill(sub_frame_);
EXPECT_CALL(provider_bridge(), Reset);
NavigateAndCommitFrame(sub_frame_, GURL("https://bar.com/"));
}
} // namespace autofill