blob: f65d27006f561d4f942f1d04916c02535e8debc5 [file] [log] [blame]
// Copyright 2022 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 "base/base64.h"
#include "base/check.h"
#include "base/metrics/field_trial_params.h"
#include "base/metrics/histogram_functions.h"
#include "base/ranges/algorithm.h"
#include "base/types/pass_key.h"
#include "chrome/browser/password_manager/chrome_password_manager_client.h"
#include "chrome/browser/touch_to_fill/touch_to_fill_controller.h"
#include "components/device_reauth/device_authenticator.h"
#include "components/password_manager/core/browser/affiliation/affiliation_utils.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/password_manager_driver.h"
#include "components/password_manager/core/browser/password_manager_metrics_util.h"
#include "components/password_manager/core/browser/password_manager_util.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/metrics/public/cpp/ukm_recorder.h"
#include "services/metrics/public/cpp/ukm_source_id.h"
#include "url/gurl.h"
namespace {
using ShowVirtualKeyboard =
password_manager::PasswordManagerDriver::ShowVirtualKeyboard;
using autofill::mojom::SubmissionReadinessState;
using password_manager::PasswordManagerDriver;
using password_manager::UiCredential;
// Infers whether a form should be submitted based on the feature's state and
// the form's structure (submission_readiness).
bool ShouldTriggerSubmission(SubmissionReadinessState submission_readiness) {
switch (submission_readiness) {
case SubmissionReadinessState::kNoInformation:
case SubmissionReadinessState::kError:
case SubmissionReadinessState::kNoUsernameField:
case SubmissionReadinessState::kNoPasswordField:
case SubmissionReadinessState::kFieldBetweenUsernameAndPassword:
case SubmissionReadinessState::kFieldAfterPasswordField:
return false;
case SubmissionReadinessState::kEmptyFields:
case SubmissionReadinessState::kMoreThanTwoFields:
case SubmissionReadinessState::kTwoFields:
return true;
}
}
// Returns whether there is at least one credential with a non-empty username.
bool ContainsNonEmptyUsername(
const base::span<const UiCredential>& credentials) {
return base::ranges::any_of(credentials, [](const UiCredential& credential) {
return !credential.username().empty();
});
}
} // namespace
TouchToFillControllerAutofillDelegate::TouchToFillControllerAutofillDelegate(
base::PassKey<TouchToFillControllerAutofillTest>,
password_manager::PasswordManagerClient* password_client,
scoped_refptr<device_reauth::DeviceAuthenticator> authenticator,
base::WeakPtr<password_manager::PasswordManagerDriver> driver,
autofill::mojom::SubmissionReadinessState submission_readiness)
: password_client_(password_client),
authenticator_(std::move(authenticator)),
driver_(std::move(driver)),
submission_readiness_(submission_readiness) {}
TouchToFillControllerAutofillDelegate::TouchToFillControllerAutofillDelegate(
ChromePasswordManagerClient* password_client,
scoped_refptr<device_reauth::DeviceAuthenticator> authenticator,
base::WeakPtr<password_manager::PasswordManagerDriver> driver,
autofill::mojom::SubmissionReadinessState submission_readiness)
: password_client_(password_client),
authenticator_(std::move(authenticator)),
driver_(driver),
submission_readiness_(submission_readiness),
source_id_(password_client->web_contents()
->GetPrimaryMainFrame()
->GetPageUkmSourceId()) {}
TouchToFillControllerAutofillDelegate::
~TouchToFillControllerAutofillDelegate() {
if (authenticator_) {
// This is a noop if no auth triggered by Touch To Fill is in progress.
authenticator_->Cancel(device_reauth::DeviceAuthRequester::kTouchToFill);
}
}
void TouchToFillControllerAutofillDelegate::OnShow(
base::span<const password_manager::UiCredential> credentials,
base::span<password_manager::PasskeyCredential> passkey_credentials) {
DCHECK(driver_);
trigger_submission_ = ::ShouldTriggerSubmission(submission_readiness_) &&
ContainsNonEmptyUsername(credentials);
base::UmaHistogramEnumeration(
"PasswordManager.TouchToFill.SubmissionReadiness", submission_readiness_);
ukm::builders::TouchToFill_SubmissionReadiness(source_id_)
.SetSubmissionReadiness(static_cast<int64_t>(submission_readiness_))
.Record(ukm::UkmRecorder::Get());
base::UmaHistogramCounts100("PasswordManager.TouchToFill.NumCredentialsShown",
credentials.size() + passkey_credentials.size());
}
void TouchToFillControllerAutofillDelegate::OnCredentialSelected(
const UiCredential& credential,
base::OnceClosure action_complete) {
if (!driver_) {
return;
}
action_complete_ = std::move(action_complete);
ukm::builders::TouchToFill_Shown(source_id_)
.SetUserAction(static_cast<int64_t>(UserAction::kSelectedCredential))
.Record(ukm::UkmRecorder::Get());
if (!password_manager_util::CanUseBiometricAuth(authenticator_.get(),
password_client_)) {
FillCredential(credential);
return;
}
// `this` notifies the authenticator when it is destructed, resulting in
// the callback being reset by the authenticator. Therefore, it is safe
// to use base::Unretained.
authenticator_->Authenticate(
device_reauth::DeviceAuthRequester::kTouchToFill,
base::BindOnce(&TouchToFillControllerAutofillDelegate::OnReauthCompleted,
base::Unretained(this), credential),
/*use_last_valid_auth=*/true);
}
void TouchToFillControllerAutofillDelegate::OnPasskeyCredentialSelected(
const password_manager::PasskeyCredential& credential,
base::OnceClosure action_complete) {
if (!driver_)
return;
password_client_->GetWebAuthnCredentialsDelegateForDriver(driver_.get())
->SelectPasskey(base::Base64Encode(credential.credential_id()));
CleanUpDriverAndReportOutcome(TouchToFillOutcome::kPasskeyCredentialSelected,
/*show_virtual_keyboard=*/false);
std::move(action_complete).Run();
}
void TouchToFillControllerAutofillDelegate::OnManagePasswordsSelected(
bool passkeys_shown,
base::OnceClosure action_complete) {
if (!driver_)
return;
CleanUpDriverAndReportOutcome(TouchToFillOutcome::kManagePasswordsSelected,
/*show_virtual_keyboard=*/false);
if (passkeys_shown) {
// On Android there is no passkey management available in Chrome settings
// password management. This will attempt to launch the GMS password
// manager where passkeys can be seen.
password_client_->NavigateToManagePasskeysPage(
password_manager::ManagePasswordsReferrer::kTouchToFill);
} else {
password_client_->NavigateToManagePasswordsPage(
password_manager::ManagePasswordsReferrer::kTouchToFill);
}
ukm::builders::TouchToFill_Shown(source_id_)
.SetUserAction(static_cast<int64_t>(UserAction::kSelectedManagePasswords))
.Record(ukm::UkmRecorder::Get());
std::move(action_complete).Run();
}
void TouchToFillControllerAutofillDelegate::OnDismiss(
base::OnceClosure action_complete) {
if (!driver_)
return;
CleanUpDriverAndReportOutcome(TouchToFillOutcome::kSheetDismissed,
/*show_virtual_keyboard=*/true);
ukm::builders::TouchToFill_Shown(source_id_)
.SetUserAction(static_cast<int64_t>(UserAction::kDismissed))
.Record(ukm::UkmRecorder::Get());
std::move(action_complete).Run();
}
const GURL& TouchToFillControllerAutofillDelegate::GetFrameUrl() {
return driver_->GetLastCommittedURL();
}
bool TouchToFillControllerAutofillDelegate::ShouldTriggerSubmission() {
return trigger_submission_;
}
gfx::NativeView TouchToFillControllerAutofillDelegate::GetNativeView() {
// It is not a |ChromePasswordManagerClient| only in
// TouchToFillControllerTest.
return static_cast<ChromePasswordManagerClient*>(password_client_)
->web_contents()
->GetNativeView();
}
void TouchToFillControllerAutofillDelegate::OnReauthCompleted(
UiCredential credential,
bool auth_successful) {
DCHECK(action_complete_);
if (!driver_)
return;
if (!auth_successful) {
CleanUpDriverAndReportOutcome(TouchToFillOutcome::kReauthenticationFailed,
/*show_virtual_keyboard=*/true);
std::move(action_complete_).Run();
return;
}
FillCredential(credential);
}
void TouchToFillControllerAutofillDelegate::FillCredential(
const UiCredential& credential) {
DCHECK(action_complete_);
DCHECK(driver_);
password_manager::metrics_util::LogFilledCredentialIsFromAndroidApp(
credential.is_affiliation_based_match().value());
driver_->TouchToFillClosed(ShowVirtualKeyboard(false));
driver_->FillSuggestion(credential.username(), credential.password());
trigger_submission_ &= !credential.username().empty();
if (trigger_submission_) {
// TODO(crbug.com/1283004): As auto-submission has been launched, measuring
// the time between filling by TTF and submisionn is not crucial. Remove
// this call, the method itself and the metrics if we are not going to use
// all that for new launches, e.g. crbug.com/1393043.
password_client_->StartSubmissionTrackingAfterTouchToFill(
credential.username());
driver_->TriggerFormSubmission();
}
driver_ = nullptr;
base::UmaHistogramEnumeration("PasswordManager.TouchToFill.Outcome",
TouchToFillOutcome::kCredentialFilled);
std::move(action_complete_).Run();
}
void TouchToFillControllerAutofillDelegate::CleanUpDriverAndReportOutcome(
TouchToFillOutcome outcome,
bool show_virtual_keyboard) {
std::exchange(driver_, nullptr)
->TouchToFillClosed(ShowVirtualKeyboard(show_virtual_keyboard));
base::UmaHistogramEnumeration("PasswordManager.TouchToFill.Outcome", outcome);
}