// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/fast_checkout/fast_checkout_trigger_validator_impl.h"
#include "chrome/browser/fast_checkout/fast_checkout_capabilities_fetcher.h"
#include "components/autofill/core/browser/logging/log_manager.h"
#include "components/autofill/core/browser/personal_data_manager.h"
#include "components/autofill/core/common/autofill_internals/log_message.h"
#include "components/autofill/core/common/autofill_internals/logging_scope.h"
#include "components/autofill/core/common/logging/log_macros.h"
using ::autofill::FastCheckoutTriggerOutcome;
using ::autofill::FastCheckoutUIState;
FastCheckoutTriggerValidatorImpl::FastCheckoutTriggerValidatorImpl(
autofill::AutofillClient* autofill_client,
FastCheckoutCapabilitiesFetcher* capabilities_fetcher,
FastCheckoutPersonalDataHelper* personal_data_helper)
: autofill_client_(autofill_client),
capabilities_fetcher_(capabilities_fetcher),
personal_data_helper_(personal_data_helper) {}
FastCheckoutTriggerOutcome FastCheckoutTriggerValidatorImpl::ShouldRun(
const autofill::FormData& form,
const autofill::FormFieldData& field,
const FastCheckoutUIState ui_state,
const bool is_running,
const autofill::AutofillManager& autofill_manager) const {
LogAutofillInternals(
"Start of checking whether a Fast Checkout run should be permitted.");
// Trigger only if there is no ongoing run.
if (is_running) {
LogAutofillInternals(
"not triggered because Fast Checkout is already running.");
return FastCheckoutTriggerOutcome::kUnsupportedFieldType;
}
// Trigger only if the URL scheme is cryptographic and security level is not
// dangerous.
if (!autofill_client_->IsContextSecure()) {
LogAutofillInternals(
"not triggered because context is not secure, e.g. not https or "
"dangerous security level.");
return FastCheckoutTriggerOutcome::kUnsupportedFieldType;
}
// Trigger only if the form is a trigger form for Fast Checkout.
if (!IsTriggerForm(form, field)) {
return FastCheckoutTriggerOutcome::kUnsupportedFieldType;
}
if (autofill_client_->GetVariationConfigCountryCode() !=
GeoIpCountryCode("US")) {
return FastCheckoutTriggerOutcome::kUnsupportedCountry;
}
// UMA drop out metrics are recorded after this point only to avoid collecting
// unnecessary metrics that would dominate the other data points.
// Trigger only if not shown before.
if (ui_state != FastCheckoutUIState::kNotShownYet) {
LogAutofillInternals("not triggered because it was shown before.");
return FastCheckoutTriggerOutcome::kFailureShownBefore;
}
// Trigger only on focusable fields.
if (!field.is_focusable()) {
LogAutofillInternals("not triggered because field was not focusable.");
return FastCheckoutTriggerOutcome::kFailureFieldNotFocusable;
}
// Trigger only on empty fields.
if (!field.value().empty()) {
LogAutofillInternals("not triggered because field was not empty.");
return FastCheckoutTriggerOutcome::kFailureFieldNotEmpty;
}
// Trigger only if the UI is available.
if (!autofill_manager.CanShowAutofillUi()) {
LogAutofillInternals("not triggered because Autofill UI cannot be shown.");
return FastCheckoutTriggerOutcome::kFailureCannotShowAutofillUi;
}
FastCheckoutTriggerOutcome result = HasValidPersonalData();
if (result != FastCheckoutTriggerOutcome::kSuccess) {
return result;
}
LogAutofillInternals("was triggered successfully.");
return FastCheckoutTriggerOutcome::kSuccess;
}
bool FastCheckoutTriggerValidatorImpl::IsTriggerForm(
const autofill::FormData& form,
const autofill::FormFieldData& field) const {
if (!capabilities_fetcher_) {
return false;
}
// TODO(crbug.com/40236321): Stop calculating the signature once the form
// signature has been moved to `form_data`.
// Check browser form's signature and renderer form's signature.
autofill::FormSignature form_signature =
autofill::CalculateFormSignature(form);
bool is_trigger_form =
capabilities_fetcher_->IsTriggerFormSupported(form.main_frame_origin(),
form_signature) ||
capabilities_fetcher_->IsTriggerFormSupported(
form.main_frame_origin(), field.host_form_signature());
if (!is_trigger_form) {
LogAutofillInternals(
"not triggered because there is no Fast Checkout support for form "
"signatures {" +
base::NumberToString(form_signature.value()) + ", " +
base::NumberToString(field.host_form_signature().value()) +
"} on origin " + form.main_frame_origin().Serialize() + ".");
}
return is_trigger_form;
}
FastCheckoutTriggerOutcome
FastCheckoutTriggerValidatorImpl::HasValidPersonalData() const {
autofill::PersonalDataManager* pdm =
personal_data_helper_->GetPersonalDataManager();
if (!pdm->address_data_manager().IsAutofillProfileEnabled()) {
LogAutofillInternals("not triggered because Autofill profile is disabled.");
return FastCheckoutTriggerOutcome::kFailureAutofillProfileDisabled;
}
if (!pdm->payments_data_manager().IsAutofillPaymentMethodsEnabled()) {
LogAutofillInternals(
"not triggered because Autofill credit card is disabled.");
return FastCheckoutTriggerOutcome::kFailureAutofillCreditCardDisabled;
}
// Trigger only if there is at least 1 valid Autofill profile on file.
if (personal_data_helper_->GetValidAddressProfiles().empty()) {
LogAutofillInternals(
"not triggered because the client does not have at least one valid "
"Autofill profile stored.");
return FastCheckoutTriggerOutcome::kFailureNoValidAutofillProfile;
}
// Trigger only if there is at least 1 complete valid credit card on file.
if (personal_data_helper_->GetValidCreditCards().empty()) {
LogAutofillInternals(
"not triggered because the client does not have at least one "
"valid Autofill credit card stored.");
return FastCheckoutTriggerOutcome::kFailureNoValidCreditCard;
}
return FastCheckoutTriggerOutcome::kSuccess;
}
void FastCheckoutTriggerValidatorImpl::LogAutofillInternals(
std::string message) const {
LOG_AF(autofill_client_->GetLogManager())
<< autofill::LoggingScope::kFastCheckout
<< autofill::LogMessage::kFastCheckout << message;
}