blob: ab304eb8fbf80ba1932a08cf63f6e58624b3c964 [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/touch_to_fill/touch_to_fill_controller_autofill_delegate.h"
#include <memory>
#include <tuple>
#include "base/base64.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/string_piece.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "base/types/pass_key.h"
#include "chrome/browser/password_manager/android/password_manager_launcher_android.h"
#include "chrome/browser/password_manager/chrome_password_manager_client.h"
#include "chrome/browser/touch_to_fill/touch_to_fill_controller.h"
#include "components/autofill/core/common/mojom/autofill_types.mojom.h"
#include "components/device_reauth/device_authenticator.h"
#include "components/device_reauth/mock_device_authenticator.h"
#include "components/password_manager/core/browser/mock_webauthn_credentials_delegate.h"
#include "components/password_manager/core/browser/origin_credential_store.h"
#include "components/password_manager/core/browser/passkey_credential.h"
#include "components/password_manager/core/browser/stub_password_manager_client.h"
#include "components/password_manager/core/browser/stub_password_manager_driver.h"
#include "components/password_manager/core/common/password_manager_features.h"
#include "components/ukm/test_ukm_recorder.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace {
using ShowVirtualKeyboard =
password_manager::PasswordManagerDriver::ShowVirtualKeyboard;
using autofill::mojom::SubmissionReadinessState;
using base::test::RunOnceCallback;
using device_reauth::DeviceAuthRequester;
using device_reauth::MockDeviceAuthenticator;
using password_manager::PasskeyCredential;
using password_manager::UiCredential;
using ::testing::_;
using ::testing::ElementsAreArray;
using ::testing::Eq;
using ::testing::Return;
using ::testing::ReturnRefOfCopy;
using ::testing::WithArg;
using IsOriginSecure = TouchToFillView::IsOriginSecure;
using IsPublicSuffixMatch = UiCredential::IsPublicSuffixMatch;
using IsAffiliationBasedMatch = UiCredential::IsAffiliationBasedMatch;
constexpr char kExampleCom[] = "https://example.com/";
class MockPasswordManagerClient
: public password_manager::StubPasswordManagerClient {
public:
MOCK_METHOD(void,
StartSubmissionTrackingAfterTouchToFill,
(const std::u16string& filled_username),
(override));
MOCK_METHOD(void,
NavigateToManagePasswordsPage,
(password_manager::ManagePasswordsReferrer),
(override));
MOCK_METHOD(password_manager::WebAuthnCredentialsDelegate*,
GetWebAuthnCredentialsDelegateForDriver,
(password_manager::PasswordManagerDriver*),
(override));
};
struct MockPasswordManagerDriver : password_manager::StubPasswordManagerDriver {
MOCK_METHOD(void,
FillSuggestion,
(const std::u16string&, const std::u16string&),
(override));
MOCK_METHOD(void, TouchToFillClosed, (ShowVirtualKeyboard), (override));
MOCK_METHOD(void, TriggerFormSubmission, (), (override));
MOCK_METHOD(const GURL&, GetLastCommittedURL, (), (const override));
};
struct MockTouchToFillView : TouchToFillView {
MOCK_METHOD(void,
Show,
(const GURL&,
IsOriginSecure,
base::span<const UiCredential>,
base::span<const PasskeyCredential>,
bool,
bool),
(override));
MOCK_METHOD(void, OnCredentialSelected, (const UiCredential&));
MOCK_METHOD(void, OnDismiss, ());
};
struct MakeUiCredentialParams {
base::StringPiece username;
base::StringPiece password;
base::StringPiece origin = kExampleCom;
bool is_public_suffix_match = false;
bool is_affiliation_based_match = false;
base::TimeDelta time_since_last_use;
};
UiCredential MakeUiCredential(MakeUiCredentialParams params) {
return UiCredential(
base::UTF8ToUTF16(params.username), base::UTF8ToUTF16(params.password),
url::Origin::Create(GURL(params.origin)),
IsPublicSuffixMatch(params.is_public_suffix_match),
IsAffiliationBasedMatch(params.is_affiliation_based_match),
base::Time::Now() - params.time_since_last_use);
}
} // namespace
class TouchToFillControllerAutofillTest : public testing::Test {
protected:
using UkmBuilder = ukm::builders::TouchToFill_Shown;
TouchToFillControllerAutofillTest() {
password_manager_launcher::
OverrideManagePasswordWhenPasskeysPresentForTesting(false);
auto mock_view = std::make_unique<MockTouchToFillView>();
mock_view_ = mock_view.get();
touch_to_fill_controller().set_view(std::move(mock_view));
ON_CALL(driver_, GetLastCommittedURL())
.WillByDefault(ReturnRefOfCopy(GURL(kExampleCom)));
// By default, disable biometric authentication.
ON_CALL(*authenticator(), CanAuthenticateWithBiometrics)
.WillByDefault(Return(false));
// By default, don't trigger a form submission.
EXPECT_CALL(driver_, TriggerFormSubmission()).Times(0);
webauthn_credentials_delegate_ =
std::make_unique<password_manager::MockWebAuthnCredentialsDelegate>();
ON_CALL(client_, GetWebAuthnCredentialsDelegateForDriver)
.WillByDefault(Return(webauthn_credentials_delegate_.get()));
scoped_feature_list_.InitAndEnableFeature(
password_manager::features::kBiometricTouchToFill);
}
MockPasswordManagerDriver& driver() { return driver_; }
MockPasswordManagerClient& client() { return client_; }
MockTouchToFillView& view() { return *mock_view_; }
MockDeviceAuthenticator* authenticator() { return authenticator_.get(); }
ukm::TestAutoSetUkmRecorder& test_recorder() { return test_recorder_; }
base::HistogramTester& histogram_tester() { return histogram_tester_; }
TouchToFillController& touch_to_fill_controller() {
return touch_to_fill_controller_;
}
std::unique_ptr<TouchToFillControllerAutofillDelegate>
MakeTouchToFillControllerDelegate(
autofill::mojom::SubmissionReadinessState submission_readiness) {
return std::make_unique<TouchToFillControllerAutofillDelegate>(
base::PassKey<TouchToFillControllerAutofillTest>(), &client_,
authenticator_, driver().AsWeakPtr(), submission_readiness);
}
password_manager::MockWebAuthnCredentialsDelegate*
webauthn_credentials_delegate() {
return webauthn_credentials_delegate_.get();
}
base::test::ScopedFeatureList& scoped_feature_list() {
return scoped_feature_list_;
}
private:
base::test::TaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
raw_ptr<MockTouchToFillView> mock_view_ = nullptr;
scoped_refptr<MockDeviceAuthenticator> authenticator_ =
base::MakeRefCounted<MockDeviceAuthenticator>();
MockPasswordManagerDriver driver_;
MockPasswordManagerClient client_;
std::unique_ptr<password_manager::MockWebAuthnCredentialsDelegate>
webauthn_credentials_delegate_;
base::HistogramTester histogram_tester_;
ukm::TestAutoSetUkmRecorder test_recorder_;
TouchToFillController touch_to_fill_controller_;
base::test::ScopedFeatureList scoped_feature_list_;
};
TEST_F(TouchToFillControllerAutofillTest, Show_And_Fill_No_Auth) {
std::unique_ptr<MockTouchToFillView> mock_view =
std::make_unique<MockTouchToFillView>();
MockTouchToFillView* weak_view = mock_view.get();
touch_to_fill_controller().set_view(std::move(mock_view));
UiCredential credentials[] = {
MakeUiCredential({.username = "alice", .password = "p4ssw0rd"})};
EXPECT_CALL(*weak_view,
Show(Eq(GURL(kExampleCom)), IsOriginSecure(true),
ElementsAreArray(credentials),
ElementsAreArray(std::vector<PasskeyCredential>()),
/*trigger_submission=*/false,
/*can_manage_passwords_when_passkeys_present*/ false));
touch_to_fill_controller().Show(
credentials, {},
MakeTouchToFillControllerDelegate(
autofill::mojom::SubmissionReadinessState::kNoInformation));
// Test that we correctly log the absence of an Android credential.
EXPECT_CALL(driver(), FillSuggestion(std::u16string(u"alice"),
std::u16string(u"p4ssw0rd")));
EXPECT_CALL(driver(), TouchToFillClosed(ShowVirtualKeyboard(false)));
touch_to_fill_controller().OnCredentialSelected(credentials[0]);
histogram_tester().ExpectUniqueSample(
"PasswordManager.TouchToFill.NumCredentialsShown", 1, 1);
histogram_tester().ExpectUniqueSample(
"PasswordManager.FilledCredentialWasFromAndroidApp", false, 1);
histogram_tester().ExpectUniqueSample(
"PasswordManager.TouchToFill.Outcome",
TouchToFillControllerAutofillDelegate::TouchToFillOutcome::
kCredentialFilled,
1);
auto entries = test_recorder().GetEntriesByName(UkmBuilder::kEntryName);
ASSERT_EQ(1u, entries.size());
test_recorder().ExpectEntryMetric(
entries[0], UkmBuilder::kUserActionName,
static_cast<int64_t>(TouchToFillControllerAutofillDelegate::UserAction::
kSelectedCredential));
}
TEST_F(TouchToFillControllerAutofillTest, Show_Fill_And_Submit) {
std::unique_ptr<MockTouchToFillView> mock_view =
std::make_unique<MockTouchToFillView>();
MockTouchToFillView* weak_view = mock_view.get();
touch_to_fill_controller().set_view(std::move(mock_view));
UiCredential credentials[] = {
MakeUiCredential({.username = "alice", .password = "p4ssw0rd"})};
EXPECT_CALL(*weak_view,
Show(Eq(GURL(kExampleCom)), IsOriginSecure(true),
ElementsAreArray(credentials),
ElementsAreArray(std::vector<PasskeyCredential>()),
/*trigger_submission=*/true,
/*can_manage_passwords_when_passkeys_present*/ false));
touch_to_fill_controller().Show(
credentials, {},
MakeTouchToFillControllerDelegate(
autofill::mojom::SubmissionReadinessState::kTwoFields));
EXPECT_CALL(driver(), FillSuggestion(std::u16string(u"alice"),
std::u16string(u"p4ssw0rd")));
EXPECT_CALL(driver(), TouchToFillClosed(ShowVirtualKeyboard(false)));
EXPECT_CALL(driver(), TriggerFormSubmission());
EXPECT_CALL(client(), StartSubmissionTrackingAfterTouchToFill(Eq(u"alice")));
touch_to_fill_controller().OnCredentialSelected(credentials[0]);
}
TEST_F(TouchToFillControllerAutofillTest, Show_Fill_And_Dont_Submit) {
std::unique_ptr<MockTouchToFillView> mock_view =
std::make_unique<MockTouchToFillView>();
MockTouchToFillView* weak_view = mock_view.get();
touch_to_fill_controller().set_view(std::move(mock_view));
UiCredential credentials[] = {
MakeUiCredential({.username = "alice", .password = "p4ssw0rd"})};
EXPECT_CALL(*weak_view,
Show(Eq(GURL(kExampleCom)), IsOriginSecure(true),
ElementsAreArray(credentials),
ElementsAreArray(std::vector<PasskeyCredential>()),
/*trigger_submission=*/false,
/*can_manage_passwords_when_passkeys_present*/ false));
touch_to_fill_controller().Show(
credentials, {},
MakeTouchToFillControllerDelegate(
autofill::mojom::SubmissionReadinessState::kNoInformation));
EXPECT_CALL(driver(), FillSuggestion(std::u16string(u"alice"),
std::u16string(u"p4ssw0rd")));
EXPECT_CALL(driver(), TouchToFillClosed(ShowVirtualKeyboard(false)));
EXPECT_CALL(driver(), TriggerFormSubmission()).Times(0);
EXPECT_CALL(client(), StartSubmissionTrackingAfterTouchToFill(_)).Times(0);
touch_to_fill_controller().OnCredentialSelected(credentials[0]);
}
TEST_F(TouchToFillControllerAutofillTest, Dont_Submit_With_Empty_Username) {
std::unique_ptr<MockTouchToFillView> mock_view =
std::make_unique<MockTouchToFillView>();
MockTouchToFillView* weak_view = mock_view.get();
touch_to_fill_controller().set_view(std::move(mock_view));
UiCredential credentials[] = {
MakeUiCredential({.username = "", .password = "p4ssw0rd"}),
MakeUiCredential({.username = "username", .password = "p4ssw0rd"})};
// As we don't know which credential will be selected, don't disable
// submission for now.
EXPECT_CALL(*weak_view,
Show(Eq(GURL(kExampleCom)), IsOriginSecure(true),
ElementsAreArray(credentials),
ElementsAreArray(std::vector<PasskeyCredential>()),
/*trigger_submission=*/true,
/*can_manage_passwords_when_passkeys_present*/ false));
touch_to_fill_controller().Show(
credentials, {},
MakeTouchToFillControllerDelegate(
autofill::mojom::SubmissionReadinessState::kTwoFields));
// The user picks the credential with an empty username, submission should not
// be triggered.
EXPECT_CALL(driver(), TriggerFormSubmission()).Times(0);
EXPECT_CALL(driver(),
FillSuggestion(std::u16string(u""), std::u16string(u"p4ssw0rd")));
EXPECT_CALL(driver(), TouchToFillClosed(ShowVirtualKeyboard(false)));
touch_to_fill_controller().OnCredentialSelected(credentials[0]);
}
TEST_F(TouchToFillControllerAutofillTest,
Single_Credential_With_Empty_Username) {
std::unique_ptr<MockTouchToFillView> mock_view =
std::make_unique<MockTouchToFillView>();
MockTouchToFillView* weak_view = mock_view.get();
touch_to_fill_controller().set_view(std::move(mock_view));
UiCredential credentials[] = {
MakeUiCredential({.username = "", .password = "p4ssw0rd"})};
// Only one credential with empty username - submission is impossible.
EXPECT_CALL(*weak_view,
Show(Eq(GURL(kExampleCom)), IsOriginSecure(true),
ElementsAreArray(credentials),
ElementsAreArray(std::vector<PasskeyCredential>()),
/*trigger_submission=*/false,
/*can_manage_passwords_when_passkeys_present*/ false));
touch_to_fill_controller().Show(
credentials, {},
MakeTouchToFillControllerDelegate(
autofill::mojom::SubmissionReadinessState::kTwoFields));
// Filling doesn't trigger submission.
EXPECT_CALL(driver(), TriggerFormSubmission()).Times(0);
EXPECT_CALL(driver(),
FillSuggestion(std::u16string(u""), std::u16string(u"p4ssw0rd")));
EXPECT_CALL(driver(), TouchToFillClosed(ShowVirtualKeyboard(false)));
touch_to_fill_controller().OnCredentialSelected(credentials[0]);
}
TEST_F(TouchToFillControllerAutofillTest, Show_And_Fill_No_Auth_Available) {
UiCredential credentials[] = {
MakeUiCredential({.username = "alice", .password = "p4ssw0rd"})};
EXPECT_CALL(view(),
Show(Eq(GURL(kExampleCom)), IsOriginSecure(true),
ElementsAreArray(credentials),
ElementsAreArray(std::vector<PasskeyCredential>()),
/*trigger_submission=*/false,
/*can_manage_passwords_when_passkeys_present*/ false));
touch_to_fill_controller().Show(
credentials, {},
MakeTouchToFillControllerDelegate(
autofill::mojom::SubmissionReadinessState::kNoInformation));
// Test that we correctly log the absence of an Android credential.
EXPECT_CALL(driver(), FillSuggestion(std::u16string(u"alice"),
std::u16string(u"p4ssw0rd")));
EXPECT_CALL(driver(), TouchToFillClosed(ShowVirtualKeyboard(false)));
EXPECT_CALL(*authenticator(), CanAuthenticateWithBiometrics)
.WillOnce(Return(false));
touch_to_fill_controller().OnCredentialSelected(credentials[0]);
histogram_tester().ExpectUniqueSample(
"PasswordManager.TouchToFill.NumCredentialsShown", 1, 1);
histogram_tester().ExpectUniqueSample(
"PasswordManager.FilledCredentialWasFromAndroidApp", false, 1);
auto entries = test_recorder().GetEntriesByName(UkmBuilder::kEntryName);
ASSERT_EQ(1u, entries.size());
test_recorder().ExpectEntryMetric(
entries[0], UkmBuilder::kUserActionName,
static_cast<int64_t>(TouchToFillControllerAutofillDelegate::UserAction::
kSelectedCredential));
}
TEST_F(TouchToFillControllerAutofillTest,
Show_And_Fill_Auth_Available_Success) {
UiCredential credentials[] = {
MakeUiCredential({.username = "alice", .password = "p4ssw0rd"})};
EXPECT_CALL(view(),
Show(Eq(GURL(kExampleCom)), IsOriginSecure(true),
ElementsAreArray(credentials),
ElementsAreArray(std::vector<PasskeyCredential>()),
/*trigger_submission=*/true,
/*can_manage_passwords_when_passkeys_present*/ false));
touch_to_fill_controller().Show(
credentials, {},
MakeTouchToFillControllerDelegate(
autofill::mojom::SubmissionReadinessState::kTwoFields));
EXPECT_CALL(driver(), FillSuggestion(std::u16string(u"alice"),
std::u16string(u"p4ssw0rd")));
EXPECT_CALL(driver(), TouchToFillClosed(ShowVirtualKeyboard(false)));
EXPECT_CALL(*authenticator(), CanAuthenticateWithBiometrics)
.WillOnce(Return(true));
EXPECT_CALL(*authenticator(),
Authenticate(DeviceAuthRequester::kTouchToFill, _,
/*use_last_valid_auth=*/true))
.WillOnce(RunOnceCallback<1>(true));
EXPECT_CALL(driver(), TriggerFormSubmission());
EXPECT_CALL(client(), StartSubmissionTrackingAfterTouchToFill(Eq(u"alice")));
touch_to_fill_controller().OnCredentialSelected(credentials[0]);
}
TEST_F(TouchToFillControllerAutofillTest,
Show_And_Fill_Auth_Available_Failure) {
UiCredential credentials[] = {
MakeUiCredential({.username = "alice", .password = "p4ssw0rd"})};
EXPECT_CALL(view(),
Show(Eq(GURL(kExampleCom)), IsOriginSecure(true),
ElementsAreArray(credentials),
ElementsAreArray(std::vector<PasskeyCredential>()),
/*trigger_submission=*/false,
/*can_manage_passwords_when_passkeys_present*/ false));
touch_to_fill_controller().Show(
credentials, {},
MakeTouchToFillControllerDelegate(
autofill::mojom::SubmissionReadinessState::kNoInformation));
EXPECT_CALL(driver(), FillSuggestion(_, _)).Times(0);
EXPECT_CALL(driver(), TouchToFillClosed(ShowVirtualKeyboard(true)));
EXPECT_CALL(*authenticator(), CanAuthenticateWithBiometrics)
.WillOnce(Return(true));
EXPECT_CALL(*authenticator(),
Authenticate(DeviceAuthRequester::kTouchToFill, _,
/*use_last_valid_auth=*/true))
.WillOnce(RunOnceCallback<1>(false));
touch_to_fill_controller().OnCredentialSelected(credentials[0]);
histogram_tester().ExpectUniqueSample(
"PasswordManager.TouchToFill.Outcome",
TouchToFillControllerAutofillDelegate::TouchToFillOutcome::
kReauthenticationFailed,
1);
}
TEST_F(TouchToFillControllerAutofillTest, Show_Empty) {
EXPECT_CALL(view(), Show).Times(0);
touch_to_fill_controller().Show(
{}, {},
MakeTouchToFillControllerDelegate(
autofill::mojom::SubmissionReadinessState::kNoInformation));
histogram_tester().ExpectUniqueSample(
"PasswordManager.TouchToFill.NumCredentialsShown", 0, 1);
}
TEST_F(TouchToFillControllerAutofillTest, Show_Insecure_Origin) {
EXPECT_CALL(driver(), GetLastCommittedURL())
.WillOnce(ReturnRefOfCopy(GURL("http://example.com")));
UiCredential credentials[] = {
MakeUiCredential({.username = "alice", .password = "p4ssw0rd"})};
EXPECT_CALL(view(),
Show(Eq(GURL("http://example.com")), IsOriginSecure(false),
ElementsAreArray(credentials),
ElementsAreArray(std::vector<PasskeyCredential>()),
/*trigger_submission=*/false,
/*can_manage_passwords_when_passkeys_present*/ false));
touch_to_fill_controller().Show(
credentials, {},
MakeTouchToFillControllerDelegate(
autofill::mojom::SubmissionReadinessState::kNoInformation));
}
TEST_F(TouchToFillControllerAutofillTest, Show_And_Fill_Android_Credential) {
// Test multiple credentials with one of them being an Android credential.
UiCredential credentials[] = {
MakeUiCredential({
.username = "alice",
.password = "p4ssw0rd",
.time_since_last_use = base::Minutes(2),
}),
MakeUiCredential({
.username = "bob",
.password = "s3cr3t",
.origin = "",
.is_affiliation_based_match = true,
.time_since_last_use = base::Minutes(3),
}),
};
EXPECT_CALL(view(),
Show(Eq(GURL(kExampleCom)), IsOriginSecure(true),
ElementsAreArray(credentials),
ElementsAreArray(std::vector<PasskeyCredential>()),
/*trigger_submission=*/false,
/*can_manage_passwords_when_passkeys_present*/ false));
touch_to_fill_controller().Show(
credentials, {},
MakeTouchToFillControllerDelegate(
autofill::mojom::SubmissionReadinessState::kNoInformation));
// Test that we correctly log the presence of an Android credential.
EXPECT_CALL(driver(), FillSuggestion(std::u16string(u"bob"),
std::u16string(u"s3cr3t")));
EXPECT_CALL(driver(), TouchToFillClosed(ShowVirtualKeyboard(false)));
EXPECT_CALL(*authenticator(), CanAuthenticateWithBiometrics)
.WillOnce(Return(false));
touch_to_fill_controller().OnCredentialSelected(credentials[1]);
histogram_tester().ExpectUniqueSample(
"PasswordManager.TouchToFill.NumCredentialsShown", 2, 1);
histogram_tester().ExpectUniqueSample(
"PasswordManager.FilledCredentialWasFromAndroidApp", true, 1);
auto entries = test_recorder().GetEntriesByName(UkmBuilder::kEntryName);
ASSERT_EQ(1u, entries.size());
test_recorder().ExpectEntryMetric(
entries[0], UkmBuilder::kUserActionName,
static_cast<int64_t>(TouchToFillControllerAutofillDelegate::UserAction::
kSelectedCredential));
}
// Verify that the credentials are ordered by their PSL match bit and last
// time used before being passed to the view.
TEST_F(TouchToFillControllerAutofillTest, Show_Orders_Credentials) {
auto alice = MakeUiCredential({
.username = "alice",
.password = "p4ssw0rd",
.time_since_last_use = base::Minutes(3),
});
auto bob = MakeUiCredential({
.username = "bob",
.password = "s3cr3t",
.is_public_suffix_match = true,
.time_since_last_use = base::Minutes(1),
});
auto charlie = MakeUiCredential({
.username = "charlie",
.password = "very_s3cr3t",
.time_since_last_use = base::Minutes(2),
});
auto david = MakeUiCredential({
.username = "david",
.password = "even_more_s3cr3t",
.is_public_suffix_match = true,
.time_since_last_use = base::Minutes(4),
});
UiCredential credentials[] = {alice, bob, charlie, david};
EXPECT_CALL(view(),
Show(Eq(GURL(kExampleCom)), IsOriginSecure(true),
testing::ElementsAre(charlie, alice, bob, david),
ElementsAreArray(std::vector<PasskeyCredential>()),
/*trigger_submission=*/false,
/*can_manage_passwords_when_passkeys_present*/ false));
touch_to_fill_controller().Show(
credentials, {},
MakeTouchToFillControllerDelegate(
autofill::mojom::SubmissionReadinessState::kNoInformation));
}
TEST_F(TouchToFillControllerAutofillTest, Dismiss) {
UiCredential credentials[] = {
MakeUiCredential({.username = "alice", .password = "p4ssw0rd"})};
EXPECT_CALL(view(),
Show(Eq(GURL(kExampleCom)), IsOriginSecure(true),
ElementsAreArray(credentials),
ElementsAreArray(std::vector<PasskeyCredential>()),
/*trigger_submission=*/false,
/*can_manage_passwords_when_passkeys_present*/ false));
touch_to_fill_controller().Show(
credentials, {},
MakeTouchToFillControllerDelegate(
autofill::mojom::SubmissionReadinessState::kNoInformation));
EXPECT_CALL(driver(), TouchToFillClosed(ShowVirtualKeyboard(true)));
touch_to_fill_controller().OnDismiss();
auto entries = test_recorder().GetEntriesByName(UkmBuilder::kEntryName);
ASSERT_EQ(1u, entries.size());
test_recorder().ExpectEntryMetric(
entries[0], UkmBuilder::kUserActionName,
static_cast<int64_t>(
TouchToFillControllerAutofillDelegate::UserAction::kDismissed));
histogram_tester().ExpectUniqueSample("PasswordManager.TouchToFill.Outcome",
TouchToFillControllerAutofillDelegate::
TouchToFillOutcome::kSheetDismissed,
1);
}
TEST_F(TouchToFillControllerAutofillTest, ManagePasswordsSelected) {
std::unique_ptr<MockTouchToFillView> mock_view =
std::make_unique<MockTouchToFillView>();
MockTouchToFillView* weak_view = mock_view.get();
touch_to_fill_controller().set_view(std::move(mock_view));
UiCredential credentials[] = {
MakeUiCredential({.username = "alice", .password = "p4ssw0rd"})};
EXPECT_CALL(*weak_view,
Show(Eq(GURL(kExampleCom)), IsOriginSecure(true),
ElementsAreArray(credentials),
ElementsAreArray(std::vector<PasskeyCredential>()),
/*trigger_submission=*/false,
/*can_manage_passwords_when_passkeys_present*/ false));
touch_to_fill_controller().Show(
credentials, {},
MakeTouchToFillControllerDelegate(
autofill::mojom::SubmissionReadinessState::kNoInformation));
EXPECT_CALL(driver(), TouchToFillClosed(ShowVirtualKeyboard(false)));
EXPECT_CALL(client(),
NavigateToManagePasswordsPage(
password_manager::ManagePasswordsReferrer::kTouchToFill));
touch_to_fill_controller().OnManagePasswordsSelected(
/*passkeys_shown=*/false);
histogram_tester().ExpectUniqueSample(
"PasswordManager.TouchToFill.Outcome",
TouchToFillControllerAutofillDelegate::TouchToFillOutcome::
kManagePasswordsSelected,
1);
auto entries = test_recorder().GetEntriesByName(UkmBuilder::kEntryName);
ASSERT_EQ(1u, entries.size());
test_recorder().ExpectEntryMetric(
entries[0], UkmBuilder::kUserActionName,
static_cast<int64_t>(TouchToFillControllerAutofillDelegate::UserAction::
kSelectedManagePasswords));
}
TEST_F(TouchToFillControllerAutofillTest, DestroyedWhileAuthRunning) {
UiCredential credentials[] = {
MakeUiCredential({.username = "alice", .password = "p4ssw0rd"})};
EXPECT_CALL(view(),
Show(Eq(GURL(kExampleCom)), IsOriginSecure(true),
ElementsAreArray(credentials),
ElementsAreArray(std::vector<PasskeyCredential>()),
/*trigger_submission=*/false,
/*can_manage_passwords_when_passkeys_present*/ false));
touch_to_fill_controller().Show(
credentials, {},
MakeTouchToFillControllerDelegate(
autofill::mojom::SubmissionReadinessState::kNoInformation));
EXPECT_CALL(*authenticator(), CanAuthenticateWithBiometrics)
.WillOnce(Return(true));
EXPECT_CALL(*authenticator(),
Authenticate(DeviceAuthRequester::kTouchToFill, _,
/*use_last_valid_auth=*/true));
touch_to_fill_controller().OnCredentialSelected(credentials[0]);
EXPECT_CALL(*authenticator(), Cancel(DeviceAuthRequester::kTouchToFill));
}
TEST_F(TouchToFillControllerAutofillTest, ShowWebAuthnCredential) {
std::unique_ptr<MockTouchToFillView> mock_view =
std::make_unique<MockTouchToFillView>();
MockTouchToFillView* weak_view = mock_view.get();
touch_to_fill_controller().set_view(std::move(mock_view));
PasskeyCredential credential(PasskeyCredential::Source::kAndroidPhone,
"example.com", {1, 2, 3, 4}, {5, 6, 7, 8},
"alice@example.com");
std::vector<PasskeyCredential> credentials({credential});
EXPECT_CALL(*weak_view,
Show(Eq(GURL(kExampleCom)), IsOriginSecure(true),
ElementsAreArray(std::vector<UiCredential>()),
ElementsAreArray(credentials),
/*trigger_submission=*/false,
/*can_manage_passwords_when_passkeys_present*/ false));
touch_to_fill_controller().Show(
{}, credentials,
MakeTouchToFillControllerDelegate(
autofill::mojom::SubmissionReadinessState::kNoInformation));
EXPECT_CALL(*webauthn_credentials_delegate(),
SelectPasskey(base::Base64Encode(credential.credential_id())));
EXPECT_CALL(driver(), TouchToFillClosed(ShowVirtualKeyboard(false)));
touch_to_fill_controller().OnPasskeyCredentialSelected(credentials[0]);
histogram_tester().ExpectUniqueSample(
"PasswordManager.TouchToFill.NumCredentialsShown", 1, 1);
histogram_tester().ExpectUniqueSample(
"PasswordManager.TouchToFill.Outcome",
TouchToFillControllerAutofillDelegate::TouchToFillOutcome::
kPasskeyCredentialSelected,
1);
}
class TouchToFillControllerAutofillTestWithSubmissionReadinessVariationTest
: public TouchToFillControllerAutofillTest,
public testing::WithParamInterface<SubmissionReadinessState> {};
TEST_P(TouchToFillControllerAutofillTestWithSubmissionReadinessVariationTest,
SubmissionReadiness) {
SubmissionReadinessState submission_readiness = GetParam();
const UiCredential credential =
MakeUiCredential({.username = "alice", .password = "p4ssw0rd"});
const UiCredential credentials[] = {credential};
// If there is no field after the password and both username and password
// fields are there, then submit the form.
bool submission_expected =
submission_readiness == SubmissionReadinessState::kEmptyFields ||
submission_readiness == SubmissionReadinessState::kMoreThanTwoFields ||
submission_readiness == SubmissionReadinessState::kTwoFields;
EXPECT_CALL(view(),
Show(Eq(GURL(kExampleCom)), IsOriginSecure(true),
ElementsAreArray(credentials),
ElementsAreArray(std::vector<PasskeyCredential>()),
/*trigger_submission=*/submission_expected,
/*can_manage_passwords_when_passkeys_present*/ false));
touch_to_fill_controller().Show(
credentials, {}, MakeTouchToFillControllerDelegate(submission_readiness));
EXPECT_CALL(driver(), TouchToFillClosed(ShowVirtualKeyboard(false)));
EXPECT_CALL(driver(),
FillSuggestion(credential.username(), credential.password()));
EXPECT_CALL(driver(), TriggerFormSubmission())
.Times(submission_expected ? 1 : 0);
touch_to_fill_controller().OnCredentialSelected(credential);
}
TEST_P(TouchToFillControllerAutofillTestWithSubmissionReadinessVariationTest,
SubmissionReadinessMetrics) {
SubmissionReadinessState submission_readiness = GetParam();
base::HistogramTester uma_recorder;
UiCredential credentials[] = {
MakeUiCredential({.username = "alice", .password = "p4ssw0rd"})};
EXPECT_CALL(view(),
Show(Eq(GURL(kExampleCom)), IsOriginSecure(true),
ElementsAreArray(credentials),
ElementsAreArray(std::vector<PasskeyCredential>()),
/*trigger_submission=*/_,
/*can_manage_passwords_when_passkeys_present*/ false));
touch_to_fill_controller().Show(
credentials, {}, MakeTouchToFillControllerDelegate(submission_readiness));
EXPECT_CALL(driver(), TouchToFillClosed(ShowVirtualKeyboard(true)));
touch_to_fill_controller().OnDismiss();
uma_recorder.ExpectUniqueSample(
"PasswordManager.TouchToFill.SubmissionReadiness", submission_readiness,
1);
auto entries = test_recorder().GetEntriesByName(
ukm::builders::TouchToFill_SubmissionReadiness::kEntryName);
ASSERT_EQ(1u, entries.size());
test_recorder().ExpectEntryMetric(
entries[0],
ukm::builders::TouchToFill_SubmissionReadiness::kSubmissionReadinessName,
static_cast<int64_t>(submission_readiness));
}
INSTANTIATE_TEST_SUITE_P(
SubmissionReadinessVariation,
TouchToFillControllerAutofillTestWithSubmissionReadinessVariationTest,
testing::Values(SubmissionReadinessState::kNoInformation,
SubmissionReadinessState::kError,
SubmissionReadinessState::kNoUsernameField,
SubmissionReadinessState::kFieldBetweenUsernameAndPassword,
SubmissionReadinessState::kFieldAfterPasswordField,
SubmissionReadinessState::kEmptyFields,
SubmissionReadinessState::kMoreThanTwoFields,
SubmissionReadinessState::kTwoFields,
SubmissionReadinessState::kNoPasswordField));